Compare commits

..

293 Commits

Author SHA1 Message Date
Wolfsblvt
51a43d1ff0 Add chat stats popup 2024-05-02 05:35:07 +02:00
Wolfsblvt
04a798b229 Switch word counting to Segmenter 2024-05-02 02:02:12 +02:00
Wolfsblvt
0799090a1a Finalize stats tooltips 2024-05-02 01:52:26 +02:00
Wolfsblvt
8c51ea15b2 Merge branch 'staging' into stats-2.0 2024-05-02 00:52:38 +02:00
Cohee
f796387e7e Limit background title height 2024-05-01 23:43:11 +03:00
Cohee
9f1c306920 I really have to spell it out 2024-05-01 23:42:50 +03:00
Cohee
2f85e50c6f Merge pull request #2171 from 24adamcho/generic-card-download
Character card import from generic sources (specifically Discord, Catbox.moe)
2024-05-01 19:58:17 +03:00
Cohee
eb4cae4e6d Add WL to config. Code clean-up. 2024-05-01 19:52:34 +03:00
Cohee
e4e6882f12 Fix scroll to bottom on chat open 2024-05-01 14:56:55 +03:00
Cohee
15a288b63d Remove shadow from top bar drawers 2024-05-01 14:52:17 +03:00
Cohee
620cd6dfc2 Move persona functions from script. Clean-up exports 2024-05-01 14:03:24 +03:00
Cohee
a5475e7752 Merge pull request #2165 from Wolfsblvt/scored-search-sorting
Scored search sorting
2024-05-01 13:44:08 +03:00
Cohee
bddfd5763b Fix persona filter rule 2024-05-01 12:49:53 +03:00
Cohee
21edb655d3 Merge branch 'staging' into scored-search-sorting 2024-05-01 12:48:26 +03:00
Cohee
51f0d1f33e Merge pull request #2169 from Wolfsblvt/fix-wi-whole-world
Fixes WI word matching not working for non-words
2024-05-01 11:07:31 +03:00
Cohee
da31b6fda8 Merge pull request #2170 from Wolfsblvt/wi-panel-performance
Improve performance of drawing WI panel
2024-05-01 10:31:41 +03:00
Cohee
2b071bed90 Format style 2024-05-01 10:25:42 +03:00
Cohee
1cf935eaf3 Merge pull request #2168 from Wolfsblvt/world-info-search-resizing
WI search bar now flexibly scales width
2024-05-01 10:24:08 +03:00
Wolfsblvt
b33b5264e5 Improve performance of drawing WI panel
- Fix performance issue by unsubscribing events before redrawing the panel
2024-05-01 02:08:52 +02:00
Wolfsblvt
8ca50098d5 Fixes WI word matching not working for non-words
- Fixes the regex that matched WI keys as "whole words" not working correctly if the key itself was not a word
2024-04-30 23:51:47 +02:00
Wolfsblvt
d82ed50fa4 Enable unix-like extended search for fuzzy search 2024-04-30 23:12:52 +02:00
Wolfsblvt
f894237a12 Tweaked weighting scores more 2024-04-30 22:12:49 +02:00
Wolfsblvt
9d8ebd7bd2 WI search bar now flexibly scales width 2024-04-30 21:35:27 +02:00
Adam
5c552a3d53 added examples for catbox/discord import links 2024-04-29 23:44:15 -05:00
Adam
300b68177b #1958 added support for generic url downloading (current whitelist: discordapp.com, catbox.moe) 2024-04-29 23:42:50 -05:00
Wolfsblvt
83f79c1466 Fix non-fuzzy char search
- Utilize new utility function that checks insensitive and without accents
2024-04-30 06:03:41 +02:00
Wolfsblvt
bc94e3992f Modify weightings for fuzzy group search 2024-04-30 05:40:31 +02:00
Wolfsblvt
1c44df8079 Modify weightings for fuzzy WI search 2024-04-30 05:30:24 +02:00
Wolfsblvt
b6b1df6a7c Fuzzy char search searches tags too 2024-04-30 05:14:01 +02:00
Wolfsblvt
b4aa7831e7 Scored search sorting for char list 2024-04-30 04:30:39 +02:00
Wolfsblvt
d1cdd60883 Scored search sorting for personas 2024-04-30 02:27:44 +02:00
Wolfsblvt
a850352eab Scored search sorting for world info 2024-04-30 01:39:47 +02:00
Cohee
d9d76ba16d #2164 Add error toasts to VecStore 2024-04-30 00:17:39 +03:00
Cohee
993284f9c1 #2164 Disable-able data bank attachments 2024-04-30 00:06:14 +03:00
Cohee
a7d3130f9a Remove non-existent foreign lorebook extensions 2024-04-29 15:33:56 +03:00
Cohee
e0df5783f8 Allow macros in positive and negative prompts 2024-04-29 13:50:55 +03:00
Cohee
e4de6da5b8 Add server plugin support for MS Edge TTS 2024-04-29 01:07:19 +03:00
Cohee
87219f897e Check that char.list has any filters before applying hidden block. 2024-04-28 21:33:37 +03:00
Cohee
73cf58826f Pause autoplay on external media removal 2024-04-28 20:11:58 +03:00
Cohee
be4637a3a0 Handle <br> in message texts with Showdown instead of manually 2024-04-28 20:00:22 +03:00
Cohee
6ac6c7cfda #2159 Move debounce constants to a separate module 2024-04-28 19:47:53 +03:00
Cohee
94e9b8f4b1 Merge branch 'staging' of https://github.com/SillyTavern/SillyTavern into staging 2024-04-28 19:29:12 +03:00
Cohee
bc6149deeb Merge pull request #2158 from racinmat/racinsky/itemization
refactor: prompt itemization split to multiple functions
2024-04-28 18:55:10 +03:00
Cohee
a0d975c3c0 Add bottom margin to in-chat tables 2024-04-28 18:39:57 +03:00
Cohee
d51b155e52 Add ability for extensions to intercept edited message text 2024-04-28 18:39:32 +03:00
Cohee
fb1b327f9a [skip ci] ESLint 2024-04-28 16:58:28 +03:00
Matěj Račinský
754cdc4d58 refactor: prompt itemization split to multiple functions 2024-04-28 14:09:10 +02:00
Cohee
a73cb9ad3d Merge pull request #2154 from Bronya-Rand/staging
chore: disable merge conflict workflow on forks
2024-04-28 14:50:28 +03:00
Cohee
58ecc0dc0d Merge pull request #2155 from Wolfsblvt/fix-bogus-folder-select
Fix bogus folder not working if tag was cut off
2024-04-28 14:43:07 +03:00
Cohee
3821e91be0 Merge pull request #2156 from Wolfsblvt/debounce-some-searches
Debounce WI, Character and Persona search + common debounce timeouts
2024-04-28 14:38:50 +03:00
Cohee
de2bb7938a Utilize import for vector store 2024-04-28 14:35:35 +03:00
Wolfsblvt
61e2877c4b Debounce Character and Persona search 2024-04-28 06:27:55 +02:00
Wolfsblvt
d7ade487b8 Refactor common enum for debounce timeouts 2024-04-28 06:21:47 +02:00
Wolfsblvt
6d04e93f34 Debounce WI search 2024-04-28 05:42:15 +02:00
Bronya-Rand
d7a7af756a chore: disable docker publish on forks 2024-04-28 03:55:49 +01:00
Wolfsblvt
0c5fe3d637 Fix bogus folder not working if tag was cut off 2024-04-28 04:47:16 +02:00
Bronya-Rand
eb0a116cc7 chore: only allow merge conflicts to run in ST repo 2024-04-28 03:41:50 +01:00
Cohee
e08a21ebe7 Deprecate old /sendas syntax.
"name" arg is now required, but defaults to {{char}} for compatibility
2024-04-28 03:53:17 +03:00
Cohee
49074effce Merge pull request #2119 from Bronya-Rand/staging
feat: Third-Party Parser Support
2024-04-28 00:15:08 +03:00
Bronya-Rand
ffe8b3c909 chore: leftover cleanup 2024-04-27 22:09:11 +01:00
Bronya-Rand
7856afee92 chore: remove mihoyo scraper 2024-04-27 22:08:38 +01:00
Bronya-Rand
fe533b7c7f chore: revert back to typedef 2024-04-27 22:01:15 +01:00
Azariel Del Carmen
fc158ca176 Merge branch 'staging' into staging 2024-04-27 21:49:02 +01:00
Cohee
f632888b4c Move scripts init at the end of HTML page 2024-04-27 23:44:08 +03:00
Bronya-Rand
8324632e4e chore: add iconAvailable to ScraperInfo 2024-04-27 21:43:53 +01:00
Bronya-Rand
be4b20af97 chore: remove mihoyo icon 2024-04-27 21:42:03 +01:00
Cohee
5a4e0a06e6 Better icon for YT captioner 2024-04-27 23:27:53 +03:00
Bronya-Rand
fb71d3b562 chore: remove miHoYo parser from first-party scrapers 2024-04-27 21:27:14 +01:00
Bronya-Rand
b96d1e79a0 feat: create proper classes and export for extension use 2024-04-27 21:26:39 +01:00
Cohee
0d310c434d Update FontAwesome 2024-04-27 23:25:35 +03:00
Cohee
b111834122 Insert custom prompts to the start of the list 2024-04-27 23:16:44 +03:00
Cohee
2847b5ee45 [skip ci] Fix format 2024-04-27 23:02:51 +03:00
Cohee
943906d8a3 Fix UTF-8 file name uploads
https://github.com/expressjs/multer/issues/1104
2024-04-27 22:58:32 +03:00
Cohee
cbedfa4664 Use atomic write 2024-04-27 22:02:04 +03:00
Cohee
01ccc32274 Cache config.yaml reads 2024-04-27 21:59:57 +03:00
Cohee
3b153a6c9b Check that path exists before serving 2024-04-27 21:54:28 +03:00
Cohee
1bcdc2652c Split pre and post listen setup tasks. Only shutdown plugins once 2024-04-27 21:41:32 +03:00
Cohee
ea050b98ef Merge pull request #2150 from evpeople/release
add a button to translate input message
2024-04-27 21:23:26 +03:00
Cohee
b30d69b2a6 Clean-up styles and JQuery use 2024-04-27 21:22:50 +03:00
Cohee
60e099e852 Clean-up diff pt.2 2024-04-27 21:15:44 +03:00
Cohee
c49b37f968 Clean-up diff 2024-04-27 21:11:41 +03:00
Cohee
404d9db359 Merge pull request #2147 from Wolfsblvt/wi-entry-inclusion-prio
World Info inclusion group prio toggle
2024-04-27 21:09:43 +03:00
Cohee
5ac0390446 Fix naming convention for LB extension fields 2024-04-27 21:03:55 +03:00
Cohee
6e98fb1c5e Clean-up debug logs 2024-04-27 20:42:49 +03:00
Cohee
053d7f9eaa Remove the /inject when value is empty 2024-04-27 20:25:55 +03:00
Cohee
5dcfda0514 Cut UI labels. Add expand to custom CSS 2024-04-27 20:02:30 +03:00
Cohee
b42125a654 Fix content index 2024-04-27 18:03:14 +03:00
Cohee
413cec8a9f Merge branch 'staging' into wi-entry-inclusion-prio 2024-04-27 18:00:00 +03:00
Cohee
8e7ffab793 Merge pull request #2149 from Wolfsblvt/duplicate-wi-entries
Button to duplicate WI entries
2024-04-27 17:57:59 +03:00
Cohee
770aee4953 Adjust title widths 2024-04-27 17:52:47 +03:00
Cohee
f479901c87 Merge pull request #2152 from Wolfsblvt/auto-sort-tags-option
Option to auto-sort tags (+UI improvements)
2024-04-27 17:45:23 +03:00
Cohee
1dbe7897d4 Prevent ticking if confirm canceled 2024-04-27 17:41:27 +03:00
Cohee
c95956766e Don't need a hack since you're not awaiting the popup 2024-04-27 17:33:52 +03:00
Cohee
e92c0db6a2 Merge pull request #2148 from HiroseKoichi/staging
Use names in place of roles for ChatML and LLama-3-Instruct
2024-04-27 17:17:22 +03:00
Hirose
3a8b8ed639 Skill Issue 2024-04-27 08:20:44 -05:00
Hirose
3a78d69b5b Use {{name}} macro, create new templates 2024-04-27 07:39:52 -05:00
Wolfsblvt
2e562d187a Option to auto-sort tags (+UI improvements)
- Toggle to auto-sort tags alphabetically
- Init auto-sort based on current sorted state, if not chosen before
- Tag management redraw list if changes happen
- Tag management highlight renamed rows on auto-sort if they get automatically reordered
- Manual drag&drop of tags disables auto-sort option
- Small fixes to popup tag management pop drawing
- Utility function to flash highlight via CSS
2024-04-27 10:26:01 +02:00
evpeople
4521dde455 add a button to translate input message 2024-04-27 13:46:13 +08:00
Wolfsblvt
b64b0e3362 Button to duplicate WI entries
- Add an option to duplicate a WI entry, copying everything besides UID
- moved UI move action on new WI entry to the UI function, not inside utility
2024-04-27 06:18:26 +02:00
RossAscends
f8ca73265b userSettings expandables get borders 2024-04-27 13:13:54 +09:00
RossAscends
1f7614af33 re-order/style User Settings Panel 2024-04-27 12:50:33 +09:00
Wolfsblvt
a48a9318c1 Add groupOverride to server endpoint too 2024-04-27 04:49:08 +02:00
Wolfsblvt
dcb042681d Change group prio name, add default value set 2024-04-27 04:40:35 +02:00
Wolfsblvt
7df2f7e752 WI inclusion groups will never roll for trigger% 2024-04-27 03:44:00 +02:00
Hirose
c3578d2cda Use names in place of role for ChatML and LLama-3-Instruct 2024-04-26 20:14:51 -05:00
Wolfsblvt
8db39a58fb World Info inclusion group prio toggle 2024-04-27 02:23:37 +02:00
Cohee
bbdbb08301 Fix main prompt clearing on disabling 2024-04-27 00:08:30 +03:00
Cohee
b06e09c030 Merge pull request #2131 from Yokayo/staging
Localization enhancements
2024-04-26 23:05:55 +03:00
Cohee
bb2bcdbf61 The dot went MIA 2024-04-26 23:04:11 +03:00
Cohee
2e278e7323 Fix missing localization for unknown locale 2024-04-26 22:57:42 +03:00
Cohee
4c9d52422b [chore] ESLint and JSDoc 2024-04-26 22:46:13 +03:00
Cohee
f4ba1f68ef Merge pull request #2136 from BlueprintCoding/release
Added import function for AICharacterCards.com cards
2024-04-26 22:42:04 +03:00
Cohee
12497e8fb1 Merge pull request #2141 from valadaptive/generate-cleanups-4
Clean up Generate(), part 4
2024-04-26 22:40:04 +03:00
Cohee
8153e747ef Merge pull request #2135 from johnflux/staging
Fix SillyTavern being launched from a different working directory
2024-04-26 22:06:40 +03:00
Cohee
63b597beb8 Fix node serve startup 2024-04-26 22:02:46 +03:00
Cohee
cdbb0b21da Merge pull request #2145 from sirius422/fix-regex-filename-non-eng-characters
Change the naming rule of regex exporting
2024-04-26 21:59:05 +03:00
Cohee
b2f40e490b Fix mobile-styles.css for waifuMode
Mobile bros want a waifu too
2024-04-26 21:51:28 +03:00
sirius422
a96e1903a3 Change the naming rule of regex exporting 2024-04-27 00:05:10 +08:00
Cohee
be7eb8b2b5 Merge pull request #2143 from aisu-wata0/style_mes_block_overflow_y
style: `.mes_block { overflow-y: clip; }`
2024-04-26 18:36:17 +03:00
Cohee
3b6372431a Merge pull request #2144 from sirius422/fix-json-export-extension
Add json extension to exported oai and LogitBias presets
2024-04-26 18:30:55 +03:00
sirius422
389ee7917f Add json extension to exported oai and LogitBias presets 2024-04-26 23:07:25 +08:00
Cohee
212e61d2a1 Lazy initialization of Claude tokenizer. Add JSDoc for tokenizer handlers 2024-04-26 15:17:02 +03:00
Cohee
1b60e4a013 Init user storage module before server listening 2024-04-26 14:09:40 +03:00
Aisu Wata
93cd93ada3 style: .mes_block { overflow-y: clip; } 2024-04-25 21:49:12 -03:00
Cohee
babb4cb57b Fix tag key for 0-index 2024-04-25 18:15:38 +03:00
valadaptive
dbcc75471f Refactor CFG prompt gen in getCombinedPrompt
We don't need to create the cfgPrompt variable unless useCfgPrompt is
true, so move it inside the if-block.
2024-04-25 09:09:30 -04:00
valadaptive
2a0497ca9e Only generate negative prompt for textgen API
The original comment mentions that we need to get the negative prompt
first since it "has the unmodified mesSend array", but we've cloned the
mesSend array since forever, so I don't think mutation is an issue
anymore.
2024-04-25 09:09:30 -04:00
valadaptive
2d0767306e Remove unnecessary cfgPrompt null-chains
We already check if cfgPrompt exists.
2024-04-25 09:09:30 -04:00
valadaptive
8ca83bb255 Extract CFG check 2024-04-25 09:09:30 -04:00
valadaptive
80a6406062 Don't reassign thisPromptBits
Instead, just use additionalPromptStuff where thisPromptBits was used
after the assignment.
2024-04-25 09:09:30 -04:00
valadaptive
ff9345a843 Make generate_data preparation a switch-case
We switch based on main_api. In the future, I'd like to move the
openai-specific token count stuff outside the switch case and extract
the generate_data preparation into its own function that we can pass
main_api into.
2024-04-25 09:09:30 -04:00
valadaptive
fe663c4f04 Move auto_adjust_response_length logic
This if-block only applies to Kobold Horde, so move it inside the Kobold
and Horde-specific case in the else-if chain.
2024-04-25 09:09:30 -04:00
Cohee
9fbb012697 Merge branch 'release' into staging 2024-04-25 12:56:17 +03:00
Cohee
0070950911 Revert grid view spacing 2024-04-25 12:26:21 +03:00
Cohee
62cf611fdc Merge pull request #2138 from Wolfsblvt/fix-expression-list-resolve
Fix expression list resolve
2024-04-25 11:00:34 +03:00
RossAscends
75814433a6 dont default to hiding avatars on new installs 2024-04-25 14:42:48 +09:00
RossAscends
e59a5b4449 toggle to hide chat avatars 2024-04-25 12:51:56 +09:00
Wolfsblvt
161e512805 Fix expression list resolve
- New expression api "LLM" still queried local classify model for expressions, fixed by returning default list
- Fixed failed API calls crashing Expressions extension
2024-04-25 04:29:20 +02:00
Wolfsblvt
3adb955a14 Persona stats with avatar, more doc improvements 2024-04-25 03:53:06 +02:00
Blueprint Coding
305afb3713 Added import function for AICharacterCards.com cards
Added ability to import cards directly from aicharactercards.com via it's api like Chub and Janny.
Video of it in action: https://streamable.com/gbfdtw

Just pass the last two slash vars from the url (the author and card title) from a page. EX: aicharcards/the-game-master to:
https://aicharactercards.com/wp-json/pngapi/v1/image/

In this example: https://aicharactercards.com/wp-json/pngapi/v1/image/aicharcards/the-game-master
2024-04-24 18:04:17 -06:00
John Tapsell
1acbef1890 Fix SillyTavern being launched from a different working directory
Fixes launching ST from ST launcher on mac
2024-04-24 16:15:32 -07:00
Cohee
f90f370fed Merge pull request #2134 from StefanDanielSchwarz/Phi-Instruct-presets
Phi Instruct context+instruct presets
2024-04-25 01:44:12 +03:00
Stefan Daniel Schwarz
d34a0ee20e Phi Instruct context+instruct presets 2024-04-24 23:47:04 +02:00
Cohee
01e3964232 Auto-backup settings every 10 minutes. Increase backups limit to 50. 2024-04-24 23:45:49 +03:00
Cohee
153638c2cd Add error handling to auto login 2024-04-24 23:14:26 +03:00
Yokayo
4bb719359c Fix tabs 2024-04-24 21:19:26 +07:00
Yokayo
847eb60806 Update ru-ru.json 2024-04-24 21:14:03 +07:00
Yokayo
e799bd3920 Fix getMissingTranslations() and change its behavior 2024-04-24 21:12:40 +07:00
Yokayo
2b1aee9e71 Localize two hard-coded strings 2024-04-24 21:07:42 +07:00
Yokayo
d65f068310 More localizable strings 2024-04-24 20:58:24 +07:00
Yokayo
b1c199e650 Add more localizable strings 2024-04-24 19:02:00 +07:00
Cohee
51014e7a8d Fix VRM assets console spam 2024-04-24 10:54:55 +03:00
Cohee
530bf81940 #2127 Encode export PNG name 2024-04-24 10:48:08 +03:00
Cohee
2bba186c9e Add slash command and d&d hint for data bank 2024-04-24 02:37:57 +03:00
Cohee
61241df0d4 Add download and move for DB attachments 2024-04-24 02:33:16 +03:00
Cohee
b6b9b542d7 Add drag&drop to data bank 2024-04-24 01:51:54 +03:00
Cohee
71f41d5233 Fix server crash in auto login 2024-04-23 21:11:47 +03:00
Cohee
a421af9ea9 Increase max attachment size 2024-04-23 21:06:59 +03:00
Cohee
75372ad0cc Use Map for caches instead of objects 2024-04-23 16:15:54 +03:00
Cohee
d1f292f462 Merge pull request #2122 from joenunezb/fix-informaticai-missing-choices-message
Fix: Handle InformaticAI Endpoint response without message in response payload
2024-04-23 14:07:27 +03:00
joenunezb
890cf81627 Fix: InformaticAI response without message in choices 2024-04-23 03:56:50 -07:00
Wolfsblvt
b9f31d5066 Stats: Implement data retrieval into char popup
- Parse json with Date objects
- Fix char directory
- Add sub info for aggregated stats
- Correctly pull names out of the chat files
- Rework humanized duration, humanized timespan, humanized filesize
- Add smart truncate and sensible round
- Implement/Fix values into the character stat popup
- Implement correct stat API calls on client side
2024-04-23 06:26:57 +02:00
Cohee
d97f0a4c4d Add new NAI Diffusion model 2024-04-23 03:18:45 +03:00
Cohee
4370db6bdc Implement World Info activation using Vector Storage 2024-04-23 03:09:52 +03:00
Bronya-Rand
770f3e5da3 chore: apply align-items center and img sample for img only scraper icons 2024-04-22 19:12:02 +01:00
Bronya-Rand
0f0895f345 feat: implement miHoYo scraper 2024-04-22 19:11:00 +01:00
Cohee
6d1933c8f3 Escape name regex in message formatting function 2024-04-22 17:35:42 +03:00
Cohee
776260c85a Add Data Bank to attachments extension display name 2024-04-22 16:25:46 +03:00
Cohee
5a5463bd5d #2095 Suppress auto-execution on streamed swiped generations. 2024-04-22 16:02:50 +03:00
Cohee
2f45f50d37 Add config value for forwarded IPs whitelisting 2024-04-22 15:52:59 +03:00
Cohee
41ad7c5d26 Verify data bank attachments 2024-04-22 02:34:50 +03:00
Wolfsblvt
c5dff7b5d4 Refactor in endpoint changes of user-folders from neo-server 2024-04-22 00:44:15 +02:00
Cohee
df93d43c36 Remove obnoxious mobile padding on right panel 2024-04-22 00:02:48 +03:00
Cohee
bc9c70556e Clean-up mentions of /public/ 2024-04-21 23:53:46 +03:00
Cohee
f75daba6c0 Image inlining hint always visible 2024-04-21 23:38:18 +03:00
Wolfsblvt
67e57ffd58 Merge branch 'staging' into stats-2.0 2024-04-21 21:54:51 +02:00
Cohee
80ff8383fe Merge pull request #2113 from SillyTavern/neo-server
Neo server
2024-04-21 22:36:28 +03:00
Cohee
5fd6202e60 Merge branch 'staging' into neo-server 2024-04-21 21:56:36 +03:00
Cohee
ef5d505de3 Merge branch 'staging' into neo-server 2024-04-21 18:28:56 +03:00
Cohee
5992c34fb5 Add DB attachment editor 2024-04-21 18:23:41 +03:00
Cohee
bae74fbbd7 Add notepad data bank file creator 2024-04-21 18:11:03 +03:00
Cohee
4264d170e2 Add support for Office plugin 2024-04-21 16:27:44 +03:00
Cohee
ca89be8930 Add experimental setting for file translation 2024-04-21 03:24:01 +03:00
Cohee
c2256c2ac7 Fix data bank text clean-up 2024-04-21 02:05:59 +03:00
Cohee
78ce23750e Add function to get data bank contents by source 2024-04-21 01:40:11 +03:00
Cohee
e6ddbd1418 Export Data Bank upload function 2024-04-21 01:15:29 +03:00
Cohee
344146d837 Merge branch 'staging' into neo-server 2024-04-21 01:04:03 +03:00
Cohee
15f0e491bf Fix Perplexity generation on neo-server 2024-04-21 00:52:03 +03:00
Cohee
70c4e82b89 Merge branch 'staging' into neo-server 2024-04-20 21:10:43 +03:00
Cohee
db78346bef Add YT script loader for data bank 2024-04-20 19:58:29 +03:00
Wolfsblvt
08f6f8c405 Calculate global stats, prepare stats endpoints 2024-04-20 05:56:26 +02:00
Cohee
b3bbec83b6 Merge branch 'staging' into neo-server 2024-04-20 02:56:05 +03:00
Cohee
78d1d48ea9 Add EPUB import for data bank 2024-04-20 01:24:46 +03:00
Cohee
3ff5884112 Forbid external media by default 2024-04-20 01:11:37 +03:00
Wolfsblvt
0d4cbf7da6 Merge branch 'staging' into stats-2.0 2024-04-19 21:59:33 +02:00
Cohee
a3f6ce52e4 Fix manual vectorization of files 2024-04-19 18:43:35 +03:00
Cohee
19ea1ee56c Fix field style 2024-04-19 18:41:40 +03:00
Cohee
09d43403b2 Merge branch 'staging' into neo-server 2024-04-19 15:07:38 +03:00
Cohee
dee8f45986 Merge branch 'staging' into neo-server 2024-04-19 01:57:01 +03:00
Cohee
9d6a791443 Merge branch 'staging' into neo-server 2024-04-19 01:15:30 +03:00
Cohee
d6fd351330 Merge pull request #2105 from isaac-mcfadyen/fs-renamefile-fix
Changed fs.renameSync() to fs.copyFileSync()
2024-04-19 01:13:50 +03:00
Cohee
80de3fdd4c Add buttons to process and purge file vectors for current chat 2024-04-19 00:16:23 +03:00
Cohee
25cb598694 Add Cohere as embedding source 2024-04-19 00:07:12 +03:00
Cohee
b69493d252 Merge branch 'staging' into neo-server 2024-04-18 23:34:34 +03:00
Cohee
2eafa2a212 Clean-up vectors upon deleting a file from Data Bank 2024-04-18 23:07:16 +03:00
Isaac McFadyen
15a8adb0b9 Changed fs.cpSync to use recursive copying 2024-04-18 16:04:04 -04:00
Cohee
8434f6e6cf Clear toast upon inserting file 2024-04-18 22:59:42 +03:00
Cohee
fa66f39790 Merge branch 'staging' into neo-server 2024-04-18 22:58:23 +03:00
Cohee
16785ae005 Merge branch 'staging' into neo-server 2024-04-18 22:57:27 +03:00
Isaac McFadyen
3822ae9356 Switched fs.renameSync to fs.copyFileSync 2024-04-18 15:50:27 -04:00
Cohee
f4f0a59e90 Save character data bank attachments as non-exportable 2024-04-18 22:16:51 +03:00
Cohee
59bb04f1b3 Implement generic interface for adding Data Bank scrapers 2024-04-18 00:14:41 +03:00
Cohee
47a06c14d9 Merge branch 'staging' into neo-server 2024-04-17 21:59:30 +03:00
Cohee
88637adfe2 Merge branch 'staging' into neo-server 2024-04-17 19:39:57 +03:00
Cohee
9a1ea7f226 Implement Data Bank vectors querying 2024-04-17 02:09:22 +03:00
Cohee
4665db62f4 #1954 Remove backtick wrapping for inserted files 2024-04-16 22:28:10 +03:00
Cohee
ab5b497562 Add filters to data bank manager 2024-04-16 22:23:59 +03:00
Cohee
5a614b5173 Integrate data bank with Fandom plugin 2024-04-16 20:16:21 +03:00
Cohee
8546490bcc Improve Scale JWT error handling 2024-04-16 18:59:01 +03:00
Cohee
3dcea41c4e Preserve a query string when redirecting to and from login 2024-04-16 18:44:11 +03:00
Cohee
f947c1304a Disable TTL on user accounts.
The biggest skill issue so far.
2024-04-16 02:49:30 +03:00
Cohee
57314443ed Add names display to data bank 2024-04-16 02:36:46 +03:00
Cohee
242d57c14b Add Data Bank manager 2024-04-16 02:14:34 +03:00
Cohee
71041ec764 Properly unset temp cap for all Chat Comp sources 2024-04-15 02:00:56 +03:00
Cohee
2b12d3f8e8 Merge branch 'staging' into neo-server 2024-04-15 01:23:36 +03:00
Cohee
022c180b62 Lint and clean-up 2024-04-15 00:39:15 +03:00
Cohee
0263be8c1f Merge branch 'staging' into neo-server 2024-04-15 00:26:52 +03:00
Cohee
a8c118fd4a Fix login text 2024-04-14 23:30:11 +03:00
Cohee
ddc55c7c22 Merge branch 'staging' into neo-server 2024-04-14 15:00:50 +03:00
Cohee
0ad4f78a51 Merge branch 'staging' into neo-server 2024-04-14 14:49:17 +03:00
Cohee
4e1a9da840 Merge branch 'staging' into neo-server 2024-04-13 21:52:23 +03:00
Cohee
e8e3834fc0 Merge branch 'staging' into neo-server 2024-04-13 20:06:47 +03:00
Cohee
790185f9e9 Add disable CSRF to config.yaml. Add basicAuthMode to console args. 2024-04-13 19:35:27 +03:00
Cohee
d02f81974c Don't dump config to docker console 2024-04-13 19:27:52 +03:00
Cohee
b340863d52 Auto-extend session if loading the home page 2024-04-13 19:12:50 +03:00
Cohee
1a372abaff Customizable avatars for users 2024-04-13 17:52:37 +03:00
Cohee
10aa268ea2 Filter out invalid character files 2024-04-13 16:21:41 +03:00
Cohee
59657766b5 Switch password hashing function to scrypt 2024-04-13 15:40:47 +03:00
Cohee
716d1fc988 Merge branch 'staging' into neo-server 2024-04-13 15:26:48 +03:00
Cohee
e82fc8d617 Add ignore eslint 2024-04-13 13:29:52 +03:00
Cohee
2661f00dd4 Fix loading plugins from default exports 2024-04-13 13:29:41 +03:00
Cohee
afad169118 Default whitelist to null 2024-04-13 02:23:38 +03:00
Cohee
dcd89f2295 Fix public facing messages 2024-04-13 00:13:36 +03:00
Cohee
53386b35c9 Make Reset account functional 2024-04-13 00:11:20 +03:00
Cohee
2e14132a20 Add config hint 2024-04-12 23:18:43 +03:00
Cohee
2fbcbe86d2 Bump package version 2024-04-12 22:15:50 +03:00
Cohee
3f65051bd4 Merge branch 'staging' into neo-server 2024-04-12 22:14:32 +03:00
Cohee
7183416d1f Check account protection status on startup 2024-04-12 22:04:20 +03:00
Cohee
0662b5b4ae Add account recovery console script 2024-04-12 21:31:43 +03:00
Cohee
dcbeab0aef Fix absolute paths for data root. Allow setting data root via console args. 2024-04-12 19:53:46 +03:00
Cohee
3e1ff9bc25 Merge branch 'staging' into neo-server 2024-04-12 19:23:10 +03:00
Cohee
58359c9682 Control whitelist mode with console flag 2024-04-12 01:33:39 +03:00
Cohee
a3da248e3c Adapt Docker files to neo-server data migration 2024-04-12 01:32:40 +03:00
Cohee
396eeca73a Change default user handle. Use async template renderer 2024-04-12 00:35:51 +03:00
Cohee
d8092ec3eb Merge branch 'staging' into neo-server 2024-04-12 00:01:52 +03:00
Cohee
31ba3cf039 Merge branch 'staging' into neo-server 2024-04-11 21:47:10 +03:00
Wolfsblvt
9cef0d8346 Temp commit
- Fixed "old" popup resizing and scroll bars (now actually respecting the chosen setting)
2024-04-11 20:43:20 +02:00
Cohee
ed14be08b9 Fix closing tag missing 2024-04-11 19:09:53 +03:00
Cohee
1990a2d9bd Add user snapshot settings management 2024-04-11 01:44:48 +03:00
Cohee
c92df1168d Implement change display name 2024-04-11 00:40:01 +03:00
Cohee
01a4aa51f7 Ask for password before resetting settings 2024-04-10 22:34:51 +03:00
Cohee
2306a4e34d Add discreet login mode 2024-04-10 22:00:08 +03:00
Cohee
bd4d8847ce Merge branch 'staging' into neo-server 2024-04-10 21:41:03 +03:00
Cohee
2b29e14e9f Reset settings option 2024-04-10 03:29:38 +03:00
Cohee
14d7665072 Merge branch 'staging' into neo-server 2024-04-10 02:44:10 +03:00
Cohee
09b44075ed User profile view 2024-04-10 02:09:38 +03:00
Cohee
8f1d2e0163 Generic popup as a notarget for panel closing 2024-04-10 01:35:59 +03:00
Cohee
accebd00f5 Stricter handle cleanup 2024-04-10 01:29:35 +03:00
Cohee
4f3780979e Admin delete user flow 2024-04-10 01:01:32 +03:00
Cohee
56a72eea5c Merge branch 'staging' into neo-server 2024-04-10 00:38:35 +03:00
Cohee
189d096834 Admin change password flow 2024-04-10 00:01:03 +03:00
Cohee
31cc6e51b5 Add user backups download 2024-04-09 22:43:47 +03:00
Cohee
411a8ef8a7 Enable CSRF for public endpoints. Split users module. Add rate limiter. 2024-04-09 21:58:16 +03:00
Cohee
497f38111f Merge branch 'staging' into neo-server 2024-04-09 20:26:03 +03:00
Cohee
72792ae9f9 Basic account management 2024-04-08 02:38:20 +03:00
Cohee
3f3e23420d Working login flow 2024-04-07 23:08:19 +03:00
RossAscends
af8627b999 Merge branch 'neo-server' of https://github.com/SillyTavern/SillyTavern into neo-server 2024-04-08 03:07:55 +09:00
RossAscends
6ad0364ace add login 2024-04-08 03:07:53 +09:00
Cohee
0230177d27 Optimize server user storage use 2024-04-07 20:36:07 +03:00
RossAscends
f8bf70f0cb Merge branch 'neo-server' of https://github.com/SillyTavern/SillyTavern into neo-server 2024-04-08 02:22:46 +09:00
RossAscends
f0aa0c5540 imp user creation, split out from loader.js (still disabled) 2024-04-08 02:22:44 +09:00
Cohee
6be86be0a7 Save user session to cookies 2024-04-07 19:12:22 +03:00
RossAscends
5ad498f3ca Merge branch 'neo-server' of https://github.com/SillyTavern/SillyTavern into neo-server 2024-04-08 00:18:23 +09:00
RossAscends
c0264f1cd6 mockup user select modal (disabled) 2024-04-08 00:18:21 +09:00
Cohee
0f105e0300 Fix circular deps, add Helmet https://helmetjs.github.io/ 2024-04-07 18:11:23 +03:00
Cohee
c6ffe4502a Add user management endpoints 2024-04-07 17:44:40 +03:00
Cohee
b07aef02c7 Persist CSRF and cookie secrets across server launches 2024-04-07 16:41:23 +03:00
Cohee
11193896b2 Add data migration procedure 2024-04-07 03:01:55 +03:00
Cohee
b07a6a9a78 Update all endpoints to use user directories 2024-04-07 01:47:07 +03:00
Cohee
cd5aec7368 Split user directories from public, part 1 2024-04-06 20:09:39 +03:00
Cohee
b3b7017bf2 Move default QR and MovingUI to content manager 2024-04-06 17:55:53 +03:00
Cohee
59daeeb37a Move default backgrounds to content manager 2024-04-06 17:43:59 +03:00
Cohee
ec896b8a12 Add themes to content manager 2024-04-06 17:28:57 +03:00
219 changed files with 13189 additions and 12067 deletions

View File

@@ -4,6 +4,7 @@ npm-debug.log
readme*
Start.bat
/dist
/backups/
/backups
cloudflared.exe
access.log
/data

View File

@@ -42,11 +42,21 @@ module.exports = {
showdownKatex: 'readonly',
SVGInject: 'readonly',
toastr: 'readonly',
Readability: 'readonly',
isProbablyReaderable: 'readonly',
},
},
],
// There are various vendored libraries that shouldn't be linted
ignorePatterns: ['public/lib/**/*', '*.min.js', 'src/ai_horde/**/*'],
ignorePatterns: [
'public/lib/**/*',
'*.min.js',
'src/ai_horde/**/*',
'plugins/**/*',
'data/**/*',
'backups/**/*',
'node_modules/**/*',
],
rules: {
'no-unused-vars': ['error', { args: 'none' }],
'no-control-regex': 'off',

View File

@@ -6,6 +6,7 @@ on:
- staging
jobs:
check-conflicts:
if: github.repository == 'SillyTavern/SillyTavern'
runs-on: ubuntu-latest
steps:
- uses: mschilde/auto-label-merge-conflicts@master

View File

@@ -21,6 +21,7 @@ env:
jobs:
build:
if: github.repository == 'SillyTavern/SillyTavern'
runs-on: ubuntu-latest
steps:

View File

@@ -1,43 +0,0 @@
name: Update SillyTavern-Docs
on:
push:
branches:
- main
jobs:
update_docs:
runs-on: ubuntu-latest
steps:
- name: Checkout current repository
uses: actions/checkout@v2
- name: Checkout SillyTavern-Docs repository
uses: actions/checkout@v2
with:
repository: SillyTavern/SillyTavern-Docs
path: SillyTavern-Docs
- name: Clone SillyTavern wiki into SillyTavern-Docs/extensions
run: rm -rf SillyTavern-Docs/extensions && git clone https://github.com/SillyTavern/SillyTavern.wiki.git SillyTavern-Docs/extensions && rm -rf SillyTavern-Docs/extensions/.git
- name: Copy files
run: |
cp public/notes/content.md SillyTavern-Docs/guidebook.md
cp faq.md SillyTavern-Docs/faq.md
cp readme.md SillyTavern-Docs/readme.md
cp public/notes/update.md SillyTavern-Docs/update.md
- name: Deploy to external repository
uses: cpina/github-action-push-to-another-repository@main
env:
SSH_DEPLOY_KEY: ${{ secrets.SSH_DEPLOY_KEY }}
with:
# GitHub Action output files
source-directory: SillyTavern-Docs/
destination-github-username: SillyTavern
destination-repository-name: SillyTavern-Docs
user-email: github-actions[bot]@users.noreply.github.com
user-name: "GitHub Actions"
target-branch: "main"

1
.gitignore vendored
View File

@@ -25,6 +25,7 @@ public/stats.json
/docker/config
/docker/user
/docker/extensions
/docker/data
.DS_Store
public/settings.json
/thumbnails

View File

@@ -1,4 +1,4 @@
FROM node:19.1.0-alpine3.16
FROM node:lts-alpine3.18
# Arguments
ARG APP_HOME=/home/node/app
@@ -26,19 +26,9 @@ COPY . ./
# Copy default chats, characters and user avatars to <folder>.default folder
RUN \
IFS="," RESOURCES="assets,backgrounds,user,context,instruct,QuickReplies,movingUI,themes,characters,chats,groups,group chats,User Avatars,worlds,OpenAI Settings,NovelAI Settings,KoboldAI Settings,TextGen Settings" && \
\
echo "*** Store default $RESOURCES in <folder>.default ***" && \
for R in $RESOURCES; do mv "public/$R" "public/$R.default"; done || true && \
\
echo "*** Create symbolic links to config directory ***" && \
for R in $RESOURCES; do ln -s "../config/$R" "public/$R"; done || true && \
\
rm -f "config.yaml" "public/settings.json" || true && \
rm -f "config.yaml" || true && \
ln -s "./config/config.yaml" "config.yaml" || true && \
ln -s "../config/settings.json" "public/settings.json" || true && \
mkdir "config" || true && \
mkdir -p "public/user" || true
mkdir "config" || true
# Cleanup unnecessary files
RUN \

View File

@@ -33,7 +33,14 @@ If you insist on installing via a zip, here is the tedious process for doing the
2. Unzip it into a folder OUTSIDE of your current ST installation.
3. Do the usual setup procedure for your OS to install the NodeJS requirements.
4. Copy the following files/folders as necessary(*) from your old ST installation:
4a. Updating 1.12.0 and above
Copy the user data directory from your data root into the data root of the new install.
By default: /data/default-user
4a. Migrating from <1.12.0 to >=1.20.0
Copy the following files/folders as necessary(*) from your old ST installation:
- Assets
- Backgrounds
@@ -54,16 +61,15 @@ If you insist on installing via a zip, here is the tedious process for doing the
- Worlds
- User
- settings.json
- secrets.json <---- this one is in the base folder, not /public/
- secrets.json <---- This one is in the base folder, not /public/
(*) 'As necessary' = "If you made any custom content related to those folders".
None of the folders are mandatory, so only copy what you need.
**NB: DO NOT COPY THE ENTIRE /PUBLIC/ FOLDER.**
Doing so could break the new install and prevent new features from being present.
Paste those items into the /data/default-user folder of the new install.
5. Paste those items into the /Public/ folder of the new install.
5. Start SillyTavern once again with the method appropriate to your OS, and pray you got it right.
6. Start SillyTavern once again with the method appropriate to your OS, and pray you got it right.
7. If everything shows up, you can safely delete the old ST folder.
6. If everything shows up, you can safely delete the old ST folder.

View File

@@ -1,10 +1,16 @@
# -- NETWORK CONFIGURATION --
# -- DATA CONFIGURATION --
# Root directory for user data storage
dataRoot: ./data
# -- SERVER CONFIGURATION --
# Listen for incoming connections
listen: false
# Server port
port: 8000
# -- SECURITY CONFIGURATION --
# Toggle whitelist mode
whitelistMode: true
# Whitelist will also verify IP in X-Forwarded-For / X-Real-IP headers
enableForwardedWhitelist: true
# Whitelist of allowed IP addresses
whitelist:
- 127.0.0.1
@@ -16,7 +22,15 @@ basicAuthUser:
password: "password"
# Enables CORS proxy middleware
enableCorsProxy: false
# Disable security checks - NOT RECOMMENDED
# Enable multi-user mode
enableUserAccounts: false
# Enable discreet login mode: hides user list on the login screen
enableDiscreetLogin: false
# Used to sign session cookies. Will be auto-generated if not set
cookieSecret: ''
# Disable CSRF protection - NOT RECOMMENDED
disableCsrfProtection: false
# Disable startup security checks - NOT RECOMMENDED
securityOverride: false
# -- ADVANCED CONFIGURATION --
# Open the browser automatically
@@ -34,6 +48,12 @@ allowKeysExposure: false
skipContentCheck: false
# Disable automatic chats backup
disableChatBackup: false
# Allowed hosts for card downloads
whitelistImportDomains:
- localhost
- cdn.discordapp.com
- files.catbox.moe
- raw.githubusercontent.com
# API request overrides (for KoboldAI and Text Completion APIs)
## Note: host includes the port number if it's not the default (80 or 443)
## Format is an array of objects:

View File

Before

Width:  |  Height:  |  Size: 68 B

After

Width:  |  Height:  |  Size: 68 B

View File

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

View File

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

Before

Width:  |  Height:  |  Size: 384 KiB

After

Width:  |  Height:  |  Size: 384 KiB

View File

Before

Width:  |  Height:  |  Size: 487 KiB

After

Width:  |  Height:  |  Size: 487 KiB

View File

Before

Width:  |  Height:  |  Size: 307 KiB

After

Width:  |  Height:  |  Size: 307 KiB

View File

Before

Width:  |  Height:  |  Size: 318 KiB

After

Width:  |  Height:  |  Size: 318 KiB

View File

Before

Width:  |  Height:  |  Size: 581 KiB

After

Width:  |  Height:  |  Size: 581 KiB

View File

Before

Width:  |  Height:  |  Size: 561 KiB

After

Width:  |  Height:  |  Size: 561 KiB

View File

Before

Width:  |  Height:  |  Size: 505 KiB

After

Width:  |  Height:  |  Size: 505 KiB

View File

Before

Width:  |  Height:  |  Size: 443 KiB

After

Width:  |  Height:  |  Size: 443 KiB

View File

Before

Width:  |  Height:  |  Size: 480 KiB

After

Width:  |  Height:  |  Size: 480 KiB

View File

Before

Width:  |  Height:  |  Size: 660 KiB

After

Width:  |  Height:  |  Size: 660 KiB

View File

Before

Width:  |  Height:  |  Size: 371 KiB

After

Width:  |  Height:  |  Size: 371 KiB

View File

Before

Width:  |  Height:  |  Size: 616 KiB

After

Width:  |  Height:  |  Size: 616 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 MiB

After

Width:  |  Height:  |  Size: 2.2 MiB

View File

Before

Width:  |  Height:  |  Size: 305 KiB

After

Width:  |  Height:  |  Size: 305 KiB

View File

Before

Width:  |  Height:  |  Size: 436 KiB

After

Width:  |  Height:  |  Size: 436 KiB

View File

Before

Width:  |  Height:  |  Size: 426 KiB

After

Width:  |  Height:  |  Size: 426 KiB

View File

Before

Width:  |  Height:  |  Size: 629 KiB

After

Width:  |  Height:  |  Size: 629 KiB

View File

Before

Width:  |  Height:  |  Size: 656 KiB

After

Width:  |  Height:  |  Size: 656 KiB

View File

Before

Width:  |  Height:  |  Size: 528 KiB

After

Width:  |  Height:  |  Size: 528 KiB

View File

@@ -1,4 +1,108 @@
[
{
"filename": "settings.json",
"type": "settings"
},
{
"filename": "themes/Dark Lite.json",
"type": "theme"
},
{
"filename": "themes/Cappuccino.json",
"type": "theme"
},
{
"filename": "backgrounds/__transparent.png",
"type": "background"
},
{
"filename": "backgrounds/_black.jpg",
"type": "background"
},
{
"filename": "backgrounds/_white.jpg",
"type": "background"
},
{
"filename": "backgrounds/bedroom clean.jpg",
"type": "background"
},
{
"filename": "backgrounds/bedroom cyberpunk.jpg",
"type": "background"
},
{
"filename": "backgrounds/bedroom red.jpg",
"type": "background"
},
{
"filename": "backgrounds/bedroom tatami.jpg",
"type": "background"
},
{
"filename": "backgrounds/cityscape medieval market.jpg",
"type": "background"
},
{
"filename": "backgrounds/cityscape medieval night.jpg",
"type": "background"
},
{
"filename": "backgrounds/cityscape postapoc.jpg",
"type": "background"
},
{
"filename": "backgrounds/forest treehouse fireworks air baloons (by kallmeflocc).jpg",
"type": "background"
},
{
"filename": "backgrounds/japan classroom side.jpg",
"type": "background"
},
{
"filename": "backgrounds/japan classroom.jpg",
"type": "background"
},
{
"filename": "backgrounds/japan path cherry blossom.jpg",
"type": "background"
},
{
"filename": "backgrounds/japan university.jpg",
"type": "background"
},
{
"filename": "backgrounds/landscape autumn great tree.jpg",
"type": "background"
},
{
"filename": "backgrounds/landscape beach day.png",
"type": "background"
},
{
"filename": "backgrounds/landscape beach night.jpg",
"type": "background"
},
{
"filename": "backgrounds/landscape mountain lake.jpg",
"type": "background"
},
{
"filename": "backgrounds/landscape postapoc.jpg",
"type": "background"
},
{
"filename": "backgrounds/landscape winter lake house.jpg",
"type": "background"
},
{
"filename": "backgrounds/royal.jpg",
"type": "background"
},
{
"filename": "backgrounds/tavern day.jpg",
"type": "background"
},
{
"filename": "default_Seraphina.png",
"type": "character"
@@ -211,7 +315,6 @@
"filename": "presets/novel/Writers-Daemon-Kayra.json",
"type": "novel_preset"
},
{
"filename": "presets/textgen/Asterism.json",
"type": "textgen_preset"
@@ -436,6 +539,10 @@
"filename": "presets/context/Llama 3 Instruct.json",
"type": "context"
},
{
"filename": "presets/context/Phi.json",
"type": "context"
},
{
"filename": "presets/instruct/Adventure.json",
"type": "instruct"
@@ -527,5 +634,37 @@
{
"filename": "presets/instruct/Llama 3 Instruct.json",
"type": "instruct"
},
{
"filename": "presets/instruct/Phi.json",
"type": "instruct"
},
{
"filename": "presets/moving-ui/Default.json",
"type": "moving_ui"
},
{
"filename": "presets/moving-ui/Black Magic Time.json",
"type": "moving_ui"
},
{
"filename": "presets/quick-replies/Default.json",
"type": "quick_replies"
},
{
"filename": "presets/instruct/Llama-3-Instruct-Names.json",
"type": "instruct"
},
{
"filename": "presets/instruct/ChatML-Names.json",
"type": "instruct"
},
{
"filename": "presets/context/Llama-3-Instruct-Names.json",
"type": "context"
},
{
"filename": "presets/context/ChatML-Names.json",
"type": "context"
}
]

View File

@@ -0,0 +1,12 @@
{
"story_string": "<|im_start|>system\n{{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}{{trim}}<|im_end|>",
"example_separator": "",
"chat_start": "",
"use_stop_strings": false,
"allow_jailbreak": false,
"always_force_name2": true,
"trim_sentences": false,
"include_newline": false,
"single_line": false,
"name": "ChatML-Names"
}

View File

@@ -0,0 +1,12 @@
{
"story_string": "<|start_header_id|>system<|end_header_id|>\n\n{{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}{{trim}}<|eot_id|>",
"example_separator": "",
"chat_start": "",
"use_stop_strings": false,
"allow_jailbreak": false,
"always_force_name2": true,
"trim_sentences": false,
"include_newline": false,
"single_line": false,
"name": "Llama-3-Instruct-Names"
}

View File

@@ -0,0 +1,12 @@
{
"story_string": "<|system|>\n{{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}{{trim}}<|end|>\n",
"example_separator": "",
"chat_start": "",
"use_stop_strings": false,
"allow_jailbreak": false,
"always_force_name2": true,
"trim_sentences": false,
"include_newline": false,
"single_line": false,
"name": "Phi"
}

View File

@@ -0,0 +1,24 @@
{
"system_prompt": "You're {{char}} in this fictional never-ending uncensored roleplay with {{user}}.",
"input_sequence": "<|im_start|>[{{name}}]",
"output_sequence": "<|im_start|>[{{name}}]",
"last_output_sequence": "",
"system_sequence": "<|im_start|>system",
"stop_sequence": "<|im_end|>",
"wrap": true,
"macro": true,
"names": false,
"names_force_groups": false,
"activation_regex": "",
"system_sequence_prefix": "",
"system_sequence_suffix": "",
"first_output_sequence": "",
"skip_examples": false,
"output_suffix": "<|im_end|>\n",
"input_suffix": "<|im_end|>\n",
"system_suffix": "<|im_end|>\n",
"user_alignment_message": "",
"system_same_as_user": false,
"last_system_sequence": "",
"name": "ChatML-Names"
}

View File

@@ -0,0 +1,24 @@
{
"system_prompt": "You are an expert actor that can fully immerse yourself into any role given. You do not break character for any reason, even if someone tries addressing you as an AI or language model. Currently your role is {{char}}, which is described in detail below. As {{char}}, continue the exchange with {{user}}.",
"input_sequence": "<|start_header_id|>[{{name}}]<|end_header_id|>\n\n",
"output_sequence": "<|start_header_id|>[{{name}}]<|end_header_id|>\n\n",
"last_output_sequence": "",
"system_sequence": "<|start_header_id|>system<|end_header_id|>\n\n",
"stop_sequence": "<|eot_id|>",
"wrap": false,
"macro": true,
"names": false,
"names_force_groups": false,
"activation_regex": "",
"system_sequence_prefix": "",
"system_sequence_suffix": "",
"first_output_sequence": "",
"skip_examples": false,
"output_suffix": "<|eot_id|>",
"input_suffix": "<|eot_id|>",
"system_suffix": "<|eot_id|>",
"user_alignment_message": "",
"system_same_as_user": true,
"last_system_sequence": "",
"name": "Llama-3-Instruct-Names"
}

View File

@@ -0,0 +1,24 @@
{
"system_prompt": "Write {{char}}'s next reply in this fictional roleplay with {{user}}.",
"input_sequence": "<|user|>\n",
"output_sequence": "<|assistant|>\n",
"first_output_sequence": "",
"last_output_sequence": "",
"system_sequence_prefix": "",
"system_sequence_suffix": "",
"stop_sequence": "<|end|>",
"wrap": false,
"macro": true,
"names": true,
"names_force_groups": true,
"activation_regex": "",
"skip_examples": false,
"output_suffix": "<|end|>\n",
"input_suffix": "<|end|>\n",
"system_sequence": "<|system|>\n",
"system_suffix": "<|end|>\n",
"user_alignment_message": "",
"last_system_sequence": "",
"system_same_as_user": false,
"name": "Phi"
}

View File

@@ -95,7 +95,7 @@
"user_prompt_bias": "",
"show_user_prompt_bias": true,
"markdown_escape_strings": "",
"fast_ui_mode": false,
"fast_ui_mode": true,
"avatar_style": 0,
"chat_display": 0,
"chat_width": 50,
@@ -115,16 +115,17 @@
"italics_text_color": "rgba(145, 145, 145, 1)",
"underline_text_color": "rgba(188, 231, 207, 1)",
"quote_text_color": "rgba(225, 138, 36, 1)",
"chat_tint_color": "rgba(23, 23, 23, 1)",
"blur_tint_color": "rgba(23, 23, 23, 1)",
"user_mes_blur_tint_color": "rgba(0, 0, 0, 0.9)",
"bot_mes_blur_tint_color": "rgba(0, 0, 0, 0.9)",
"user_mes_blur_tint_color": "rgba(30, 30, 30, 0.9)",
"bot_mes_blur_tint_color": "rgba(30, 30, 30, 0.9)",
"shadow_color": "rgba(0, 0, 0, 1)",
"waifuMode": false,
"movingUI": false,
"movingUIState": {},
"movingUIPreset": "Default",
"noShadows": true,
"theme": "Default (Dark) 1.7.1",
"theme": "Dark Lite",
"auto_swipe": false,
"auto_swipe_minimum_length": 0,
"auto_swipe_blacklist": [],
@@ -139,7 +140,7 @@
"hotswap_enabled": true,
"timer_enabled": false,
"timestamps_enabled": true,
"timestamp_model_icon": false,
"timestamp_model_icon": true,
"mesIDDisplay_enabled": false,
"max_context_unlocked": false,
"prefer_character_prompt": true,
@@ -193,7 +194,8 @@
"encode_tags": false,
"enableLabMode": false,
"enableZenSliders": false,
"ui_mode": 1
"ui_mode": 1,
"forbid_external_media": true
},
"extension_settings": {
"apiUrl": "http://localhost:5100",

View File

@@ -0,0 +1,35 @@
{
"name": "Cappuccino",
"blur_strength": 3,
"main_text_color": "rgba(255, 255, 255, 1)",
"italics_text_color": "rgba(230, 210, 190, 1)",
"underline_text_color": "rgba(205, 180, 160, 1)",
"quote_text_color": "rgba(165, 140, 115, 1)",
"blur_tint_color": "rgba(34, 30, 32, 0.95)",
"chat_tint_color": "rgba(50, 45, 50, 0.75)",
"user_mes_blur_tint_color": "rgba(34, 30, 32, 0.75)",
"bot_mes_blur_tint_color": "rgba(34, 30, 32, 0.75)",
"shadow_color": "rgba(0, 0, 0, 0.3)",
"shadow_width": 1,
"border_color": "rgba(80, 80, 80, 0.89)",
"font_scale": 1,
"fast_ui_mode": false,
"waifuMode": false,
"avatar_style": 0,
"chat_display": 1,
"noShadows": false,
"chat_width": 50,
"timer_enabled": false,
"timestamps_enabled": true,
"timestamp_model_icon": true,
"mesIDDisplay_enabled": true,
"message_token_count_enabled": false,
"expand_message_actions": false,
"enableZenSliders": false,
"enableLabMode": false,
"hotswap_enabled": true,
"custom_css": "",
"bogus_folders": true,
"reduced_motion": false,
"compact_input_area": true
}

View File

@@ -0,0 +1,35 @@
{
"name": "Dark Lite",
"blur_strength": 10,
"main_text_color": "rgba(220, 220, 210, 1)",
"italics_text_color": "rgba(145, 145, 145, 1)",
"underline_text_color": "rgba(188, 231, 207, 1)",
"quote_text_color": "rgba(225, 138, 36, 1)",
"blur_tint_color": "rgba(23, 23, 23, 1)",
"chat_tint_color": "rgba(23, 23, 23, 1)",
"user_mes_blur_tint_color": "rgba(30, 30, 30, 0.9)",
"bot_mes_blur_tint_color": "rgba(30, 30, 30, 0.9)",
"shadow_color": "rgba(0, 0, 0, 1)",
"shadow_width": 2,
"border_color": "rgba(0, 0, 0, 1)",
"font_scale": 1,
"fast_ui_mode": true,
"waifuMode": false,
"avatar_style": 0,
"chat_display": 0,
"noShadows": true,
"chat_width": 50,
"timer_enabled": false,
"timestamps_enabled": true,
"timestamp_model_icon": true,
"mesIDDisplay_enabled": false,
"message_token_count_enabled": false,
"expand_message_actions": false,
"enableZenSliders": "",
"enableLabMode": "",
"hotswap_enabled": true,
"custom_css": "",
"bogus_folders": true,
"reduced_motion": false,
"compact_input_area": true
}

View File

@@ -8,7 +8,6 @@ services:
ports:
- "8000:8000"
volumes:
- "./extensions:/home/node/app/public/scripts/extensions/third-party"
- "./config:/home/node/app/config"
- "./user:/home/node/app/public/user"
- "./data:/home/node/app/data"
restart: unless-stopped

View File

@@ -1,38 +1,9 @@
#!/bin/sh
# Initialize missing user files
IFS="," RESOURCES="assets,backgrounds,user,context,instruct,QuickReplies,movingUI,themes,characters,chats,groups,group chats,User Avatars,worlds,OpenAI Settings,NovelAI Settings,KoboldAI Settings,TextGen Settings"
for R in $RESOURCES; do
if [ ! -e "config/$R" ]; then
echo "Resource not found, copying from defaults: $R"
cp -r "public/$R.default" "config/$R"
fi
done
if [ ! -e "config/config.yaml" ]; then
echo "Resource not found, copying from defaults: config.yaml"
cp -r "default/config.yaml" "config/config.yaml"
fi
if [ ! -e "config/settings.json" ]; then
echo "Resource not found, copying from defaults: settings.json"
cp -r "default/settings.json" "config/settings.json"
fi
CONFIG_FILE="config.yaml"
echo "Starting with the following config:"
cat $CONFIG_FILE
if grep -q "listen: false" $CONFIG_FILE; then
echo -e "\033[1;31mThe listen parameter is set to false. If you can't connect to the server, edit the \"docker/config/config.yaml\" file and restart the container.\033[0m"
sleep 5
fi
if grep -q "whitelistMode: true" $CONFIG_FILE; then
echo -e "\033[1;31mThe whitelistMode parameter is set to true. If you can't connect to the server, edit the \"docker/config/config.yaml\" file and restart the container.\033[0m"
sleep 5
fi
# Start the server
exec node server.js
exec node server.js --listen

20
index.d.ts vendored Normal file
View File

@@ -0,0 +1,20 @@
import { UserDirectoryList, User } from "./src/users";
declare global {
namespace Express {
export interface Request {
user: {
profile: User;
directories: UserDirectoryList;
};
}
}
}
declare module 'express-session' {
export interface SessionData {
handle: string;
touch: number;
// other properties...
}
}

View File

@@ -12,6 +12,9 @@
},
"exclude": [
"node_modules",
"**/node_modules/*"
"**/node_modules/*",
"public/lib",
"backups/*",
"data/*"
]
}
}

947
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,17 +4,21 @@
"@agnai/web-tokenizers": "^0.1.3",
"@dqbd/tiktoken": "^1.0.13",
"@zeldafan0225/ai_horde": "^4.0.1",
"archiver": "^7.0.1",
"bing-translate-api": "^2.9.1",
"body-parser": "^1.20.2",
"command-exists": "^1.2.9",
"compression": "^1",
"cookie-parser": "^1.4.6",
"cookie-session": "^2.1.0",
"cors": "^2.8.5",
"csrf-csrf": "^2.2.3",
"express": "^4.19.2",
"form-data": "^4.0.0",
"google-translate-api-browser": "^3.0.1",
"gpt3-tokenizer": "^1.1.5",
"he": "^1.2.0",
"helmet": "^7.1.0",
"ip-matching": "^2.1.2",
"ipaddr.js": "^2.0.1",
"jimp": "^0.22.10",
@@ -22,10 +26,12 @@
"mime-types": "^2.1.35",
"multer": "^1.4.5-lts.1",
"node-fetch": "^2.6.11",
"node-persist": "^4.0.1",
"open": "^8.4.2",
"png-chunk-text": "^1.0.0",
"png-chunks-encode": "^1.0.0",
"png-chunks-extract": "^1.0.0",
"rate-limiter-flexible": "^5.0.0",
"response-time": "^2.3.2",
"sanitize-filename": "^1.6.3",
"sillytavern-transformers": "^2.14.6",
@@ -62,7 +68,7 @@
"type": "git",
"url": "https://github.com/SillyTavern/SillyTavern.git"
},
"version": "1.11.8",
"version": "1.12.0-preview",
"scripts": {
"start": "node server.js",
"start-multi": "node server.js --disableCsrf",
@@ -79,6 +85,7 @@
},
"main": "server.js",
"devDependencies": {
"@types/jquery": "^3.5.29",
"eslint": "^8.55.0",
"jquery": "^3.6.4"
}

View File

@@ -60,7 +60,8 @@ function convertConfig() {
try {
console.log(color.blue('Converting config.conf to config.yaml. Your old config.conf will be renamed to config.conf.bak'));
const config = require(path.join(process.cwd(), './config.conf'));
fs.renameSync('./config.conf', './config.conf.bak');
fs.copyFileSync('./config.conf', './config.conf.bak');
fs.rmSync('./config.conf');
fs.writeFileSync('./config.yaml', yaml.stringify(config));
console.log(color.green('Conversion successful. Please check your config.yaml and fix it if necessary.'));
} catch (error) {
@@ -106,7 +107,6 @@ function addMissingConfigValues() {
*/
function createDefaultFiles() {
const files = {
settings: './public/settings.json',
config: './config.yaml',
user: './public/css/user.css',
};
@@ -167,29 +167,6 @@ function copyWasmFiles() {
}
}
/**
* Moves the custom background into settings.json.
*/
function migrateBackground() {
if (!fs.existsSync('./public/css/bg_load.css')) return;
const bgCSS = fs.readFileSync('./public/css/bg_load.css', 'utf-8');
const bgMatch = /url\('([^']*)'\)/.exec(bgCSS);
if (!bgMatch) return;
const bgFilename = bgMatch[1].replace('../backgrounds/', '');
const settings = fs.readFileSync('./public/settings.json', 'utf-8');
const settingsJSON = JSON.parse(settings);
if (Object.hasOwn(settingsJSON, 'background')) {
console.log(color.yellow('Both bg_load.css and the "background" setting exist. Please delete bg_load.css manually.'));
return;
}
settingsJSON.background = { name: bgFilename, url: `url('backgrounds/${bgFilename}')` };
fs.writeFileSync('./public/settings.json', JSON.stringify(settingsJSON, null, 4));
fs.rmSync('./public/css/bg_load.css');
}
try {
// 0. Convert config.conf to config.yaml
convertConfig();
@@ -199,8 +176,6 @@ try {
copyWasmFiles();
// 3. Add missing config values
addMissingConfigValues();
// 4. Migrate bg_load.css to settings.json
migrateBackground();
} catch (error) {
console.error(error);
}

View File

@@ -1 +0,0 @@
# Put images here to select them as a user persona avatar.

View File

@@ -1 +0,0 @@
Put ambient audio files here.

View File

@@ -1 +0,0 @@
Put bgm audio files here

View File

@@ -1 +0,0 @@
Put blip audio files here

View File

@@ -1 +0,0 @@
Put live2d model folders here

View File

@@ -1 +0,0 @@
Put VRM animation files here

View File

@@ -1 +0,0 @@
Put VRM model files here

View File

@@ -1,8 +0,0 @@
# Put PNG character cards here.
To create a sprites folder, name it the same as your character (NOT the PNG file).
For example:
- Character: /characters/Asuka Langley.png
- Sprite: /characters/Asuka Langley/joy.png

View File

@@ -1,5 +0,0 @@
# Put Chat JSONL files here in subfolders corresponding to character names
For example:
- /chats/Robot/chat.jsonl

5
public/css/accounts.css Normal file
View File

@@ -0,0 +1,5 @@
.userAccount {
border: 1px solid var(--SmartThemeBorderColor);
padding: 5px 10px;
border-radius: 5px;
}

6
public/css/brands.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -204,3 +204,7 @@ input.extension_missing[type="checkbox"] {
#extensionsMenu>#translate_chat {
order: 7;
}
#extensionsMenu>#translate_input_message {
order: 8;
}

File diff suppressed because it is too large Load Diff

9
public/css/fontawesome.min.css vendored Normal file

File diff suppressed because one or more lines are too long

44
public/css/login.css Normal file
View File

@@ -0,0 +1,44 @@
body.login #shadow_popup {
opacity: 1;
display: flex;
}
body.login .logo {
max-width: 30px;
}
body.login #logoBlock {
align-items: center;
margin: 0 auto;
gap: 10px;
}
body.login .userSelect {
display: flex;
flex-direction: column;
color: var(--SmartThemeBodyColor);
border: 1px solid var(--SmartThemeBorderColor);
border-radius: 5px;
padding: 3px 5px;
width: 30%;
cursor: pointer;
margin: 5px 0;
transition: background-color 0.15s ease-in-out;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
overflow: hidden;
}
body.login .userSelect .userName,
body.login .userSelect .userHandle {
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
body.login .userSelect:hover {
background-color: var(--black30a);
}

View File

@@ -231,9 +231,11 @@
backdrop-filter: blur(calc(var(--SmartThemeBlurStrength) * 2));
}
/*
#right-nav-panel {
padding-right: 15px;
}
*/
#floatingPrompt,
#cfgConfig,
@@ -307,6 +309,10 @@
object-fit: cover;
}
body.waifuMode .zoomed_avatar_container {
height: 100%;
}
body.waifuMode .zoomed_avatar {
width: fit-content;
max-height: calc(60vh - 60px);

View File

@@ -1,24 +0,0 @@
:root,
:host {
--fa-style-family-classic: 'Font Awesome 6 Free';
--fa-font-solid: normal 900 1em/1 'Font Awesome 6 Free';
}
@font-face {
font-family: 'Font Awesome 6 Free';
font-style: normal;
font-weight: 900;
font-display: block;
src: url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.ttf") format("truetype");
}
.fas,
.fa-solid {
font-weight: 900;
}
/*!
* Font Awesome Free 6.4.0 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* Copyright 2023 Fonticons, Inc.
*/

6
public/css/solid.min.css vendored Normal file
View File

@@ -0,0 +1,6 @@
/*!
* Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* Copyright 2024 Fonticons, Inc.
*/
:host,:root{--fa-style-family-classic:"Font Awesome 6 Free";--fa-font-solid:normal 900 1em/1 "Font Awesome 6 Free"}@font-face{font-family:"Font Awesome 6 Free";font-style:normal;font-weight:900;font-display:block;src:url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.ttf) format("truetype")}.fa-solid,.fas{font-weight:900}

View File

@@ -102,6 +102,14 @@
justify-content: space-between;
}
.justifySpaceEvenly {
justify-content: space-evenly;
}
.justifySpaceAround {
justify-content: space-around;
}
.alignitemsflexstart {
align-items: flex-start !important;
}
@@ -491,6 +499,10 @@ textarea:disabled {
font-size: calc(var(--mainFontSize) * 1.2) !important;
}
.fontsize90p {
font-size: calc(var(--mainFontSize) * 0.9) !important;
}
.fontsize80p {
font-size: calc(var(--mainFontSize) * 0.8) !important;
}

112
public/css/stats.css Normal file
View File

@@ -0,0 +1,112 @@
.rm_stat_popup_header {
margin-bottom: 0px;
}
.rm_stats_button {
cursor: pointer;
}
.rm_stat_block {
display: flex;
}
.rm_stat_block_data_row:hover {
background-color: var(--grey5020a);
filter: drop-shadow(0px 0px 5px var(--SmartThemeShadowColor));
}
.rm_stat_name {
flex: 1;
}
.rm_stat_values {
flex: 2;
display: flex;
align-items: center;
}
.rm_stat_block.rm_stat_right_spacing .rm_stat_values {
flex: 1;
}
.rm_stat_name .rm_stat_header {
height: calc(var(--mainFontSize) * 1.33333333333 + 3px);
padding-bottom: 3px;
border-bottom: 2px solid;
}
.rm_stat_name .rm_stat_field {
text-align: left;
}
.rm_stat_field.rm_stat_field_lefty {
text-align: left;
padding-left: 6px;
}
.rm_stat_field {
flex: 1;
height: calc(var(--mainFontSize) * 1.33333333333);
text-align: right;
overflow: hidden;
padding-left: 2px;
padding-right: 2px;
}
.rm_stat_field_smaller {
color: var(--grey70);
font-size: smaller;
}
.rm_stat_header {
margin-bottom: 3px;
font-weight: bold;
}
.rm_stat_spacer {
height: 12px;
}
.rm_stat_bar {
width: 100%;
height: calc(var(--mainFontSize) * 1.33333333333 - 4px);
display: flex;
margin-top: 2px;
margin-bottom: 2px;
padding-left: 6px;
}
.rm_stat_bar_user {
background-color: rgba(130, 178, 140, 0.9);
}
.rm_stat_bar_char {
background-color: rgba(178, 140, 130, 0.9);
}
.rm_stat_block.rm_stat_right_spacing {
margin-right: 33.33333333333%;
}
.rm_stat_avatar_block {
position: absolute;
top: calc(10px + 1.17em + 12.5px + 2* 7px);
right: 0px;
height: calc(8px + calc(calc(var(--mainFontSize) * 1.33333333333) * 7) + calc(12px * 3));
width: calc(33.33333333333% - 10px);
display: flex;
justify-content: center;
align-items: center;
}
.rm_stat_avatar_block .avatar {
scale: 2;
flex: unset;
}
.rm_stat_footer {
justify-content: right;
color: var(--grey70);
font-size: smaller;
font-style: italic;
}

View File

@@ -19,7 +19,8 @@ body.no-timer .mes_timer,
body.no-timestamps .timestamp,
body.no-tokenCount .tokenCounterDisplay,
body.no-mesIDDisplay .mesIDDisplay,
body.no-modelIcons .icon-svg {
body.no-modelIcons .icon-svg,
body.hideChatAvatars .mesAvatarWrapper .avatar {
display: none !important;
}
@@ -123,10 +124,16 @@ body.charListGrid #rm_print_characters_block .bogus_folder_select_back .avatar {
}
/* Hack for keeping the spacing */
/*
body.charListGrid #rm_print_characters_block .ch_add_placeholder {
display: flex !important;
opacity: 0;
}
*/
body.charListGrid #rm_print_characters_block .ch_additional_info {
display: none;
}
/*big avatars mode page-wide changes*/
@@ -139,7 +146,6 @@ body.big-avatars .bogus_folder_select .avatar {
body.big-avatars .avatar {
width: calc(var(--avatar-base-width) * var(--big-avatar-width-factor));
height: calc(var(--avatar-base-height) * var(--big-avatar-height-factor));
/* width: unset; */
border-style: none;
display: flex;
justify-content: center;
@@ -433,14 +439,6 @@ body.expandMessageActions .mes .mes_buttons .extraMesButtonsHint {
display: none !important;
}
#openai_image_inlining:not(:checked)~#image_inlining_hint {
display: none;
}
#openai_image_inlining:checked~#image_inlining_hint {
display: block;
}
#smooth_streaming:not(:checked)~#smooth_streaming_speed_control {
display: none;
}

View File

@@ -157,7 +157,12 @@
width: 10em;
}
#world_info_search,
#world_info_search {
width: 10em;
min-width: 10em;
flex-grow: 1;
}
#world_info_sort_order {
width: 7em;
}

View File

@@ -1 +0,0 @@
# Put Group Chat JSONL files here

View File

@@ -1 +0,0 @@
# Put Group JSON files here

BIN
public/img/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

File diff suppressed because it is too large Load Diff

1
public/lib/epub.min.js vendored Normal file

File diff suppressed because one or more lines are too long

13
public/lib/jszip.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -38,7 +38,7 @@
"LLaMA / Mistral / Yi models only": "Только для моделей LLaMA / Mistral / Yi. Перед этим обязательно выберите подходящий токенизатор.\nПоследовательности, которых не должно быть на выходе.\nОдна на строку. Текст или [идентификаторы токенов].\nМногие токены имеют пробел впереди. Используйте счетчик токенов, если не уверены.",
"Example: some text [42, 69, 1337]": "Пример:\nкакой-то текст\n[42, 69, 1337]",
"Classifier Free Guidance. More helpful tip coming soon": "Classifier Free Guidance. Чуть позже опишем более подробно",
"Scale": "Масштаб",
"Scale": "Scale",
"GBNF Grammar": "Грамматика GBNF",
"Usage Stats": "Статистика исп.",
"Click for stats!": "Нажмите для получения статистики!",
@@ -97,7 +97,7 @@
"Sequences you don't want to appear in the output. One per line.": "Строки, которых не должно быть в выходном тексте. По одной на строчку.",
"AI Module": "Модуль ИИ",
"Changes the style of the generated text.": "Изменяет стиль создаваемого текста.",
"Used if CFG Scale is unset globally, per chat or character": "Используется, если масштаб CFG не установлен глобально, для каждого чата или персонажа.",
"Used if CFG Scale is unset globally, per chat or character": "Используется, если CFG Scale не установлен глобально, для каждого чата или персонажа.",
"Inserts jailbreak as a last system message.": "Вставлять JailBreak последним системным сообщением.",
"This tells the AI to ignore its usual content restrictions.": "Сообщает AI о необходимости игнорировать стандартные ограничения контента.",
"NSFW Encouraged": "Поощрять NSFW",
@@ -262,7 +262,7 @@
"Auto-Continue": "Авто-продолжение",
"Collapse Consecutive Newlines": "Сворачивать последовательные новые строки",
"Allow for Chat Completion APIs": "Разрешить для API Chat Completion",
"Target length (tokens)": "Целевая длина (токены)",
"Target length (tokens)": "Целевая длина (в токенах)",
"Keep Example Messages in Prompt": "Сохранять примеры сообщений в промпте",
"Remove Empty New Lines from Output": "Удалять пустые строчки из вывода",
"Disabled for all models": "Выключено для всех моделей",
@@ -300,11 +300,11 @@
"Chat Style": "Стиль чата",
"Default": "По умолчанию",
"Bubbles": "Пузыри",
"No Blur Effect": "Отключить эффект размытия",
"No Text Shadows": "Отключить тень от текста",
"No Blur Effect": "Отключить размытие",
"No Text Shadows": "Отключить тень текста",
"Waifu Mode": "Рeжим Вайфу",
"Message Timer": "Таймер сообщений",
"Model Icon": "Показать значки модели",
"Model Icon": "Значки моделей",
"# of messages (0 = disabled)": "# сообщений (0 = отключено)",
"Advanced Character Search": "Расширенный поиск по персонажам",
"Allow {{char}}: in bot messages": "Показывать {{char}}: в ответах",
@@ -314,7 +314,7 @@
"Lorebook Import Dialog": "Показывать окно импорта лорбука",
"MUI Preset": "Пресет MUI:",
"If set in the advanced character definitions, this field will be displayed in the characters list.": "Если это поле задано в расширенных параметрах персонажа, оно будет отображаться в списке персонажей.",
"Relaxed API URLS": "Смягченные URL-адреса API",
"Relaxed API URLS": "Смягчённые адреса API",
"Custom CSS": "Пользовательский CSS",
"Default (oobabooga)": "По умолчанию (oobabooga)",
"Mancer Model": "Модель Mancer",
@@ -381,7 +381,7 @@
"text": "текст",
"Delete": "Удалить",
"Cancel": "Отменить",
"Advanced Defininitions": "Продвинутое описание",
"Advanced Defininitions": "Расширенное описание",
"Personality summary": "Сводка по личности",
"A brief description of the personality": "Краткое описание личности",
"Scenario": "Сценарий",
@@ -431,7 +431,7 @@
"JSON": "JSON",
"presets": "Пресеты",
"Message Sound": "Звук сообщения",
"Author's Note": "Пометки автора",
"Author's Note": "Заметки автора",
"Send Jailbreak": "Отправлять джейлбрейк",
"Replace empty message": "Заменять пустые сообщения",
"Send this text instead of nothing when the text box is empty.": "Этот текст будет отправлен в случае отсутствия текста на отправку.",
@@ -475,7 +475,7 @@
"--- Pick to Edit ---": "--- Выберите для редактирования ---",
"or": "или",
"New": "Новый",
"Priority": "Приритет",
"Priority": "Приоритет",
"Custom": "Пользовательский",
"Title A-Z": "Название от A до Z",
"Title Z-A": "Название от Z до A",
@@ -528,7 +528,7 @@
"UI Border": "Границы UI",
"Chat Style:": "Стиль чата",
"Chat Width (PC)": "Ширина чата (для ПК)",
"Chat Timestamps": "Временные метки в чате",
"Chat Timestamps": "Метки времени в чате",
"Tags as Folders": "Теги как папки",
"Chat Truncation": "Усечение чата",
"(0 = unlimited)": "(0 = неограниченное)",
@@ -559,8 +559,8 @@
"Disables animations and transitions": "Отключение анимаций и переходов.",
"removes blur from window backgrounds": "Убрать размытие с фона окон, чтобы ускорить рендеринг.",
"Remove text shadow effect": "Удаление эффекта тени от текста.",
"Reduce chat height, and put a static sprite behind the chat window": "Уменьшитm высоту чата и поместить статичный спрайт за окном чата.",
"Always show the full list of the Message Actions context items for chat messages, instead of hiding them behind '...'": "Всегда показывать полный список контекстных элементов 'Действия с сообщением' для сообщений чата, а не прятать их за '...'.",
"Reduce chat height, and put a static sprite behind the chat window": "Уменьшить высоту чата и поместить статичный спрайт за окном чата.",
"Always show the full list of the Message Actions context items for chat messages, instead of hiding them behind '...'": "Всегда показывать полный список действий с сообщением, а не прятать их за '...'.",
"Alternative UI for numeric sampling parameters with fewer steps": "Альтернативный пользовательский интерфейс для числовых параметров выборки с меньшим количеством шагов.",
"Entirely unrestrict all numeric sampling parameters": "Полностью разграничить все числовые параметры выборки.",
"Time the AI's message generation, and show the duration in the chat log": "Время генерации сообщений ИИ и его показ в журнале чата.",
@@ -600,7 +600,7 @@
"Enable the auto-swipe function. Settings in this section only have an effect when auto-swipe is enabled": "Включить авто-свайп. Настройки в этом разделе действуют только при включенном авто-свайпе.",
"If the generated message is shorter than this, trigger an auto-swipe": "Если сгенерированное сообщение короче этого значения, срабатывает авто-свайп.",
"Reload and redraw the currently open chat": "Перезагрузить и перерисовать открытый в данный момент чат.",
"Auto-Expand Message Actions": "Развернуть контекстные элементы",
"Auto-Expand Message Actions": "Развернуть действия",
"Not Connected": "Не подключено",
"Persona Management": "Управление персоной",
"Persona Description": "Описание персоны",
@@ -629,16 +629,15 @@
"Most chats": "Больше всего чатов",
"Least chats": "Меньше всего чатов",
"Back": "Назад",
"Prompt Overrides (For OpenAI/Claude/Scale APIs, Window/OpenRouter, and Instruct mode)": "Перезапись промпта (Для OpenAI/Claude/Scale API, Window/OpenRouter, и режима Instruct)",
"Insert {{original}} into either box to include the respective default prompt from system settings.": "Введите {{original}} в любое поле, чтобы использовать соответствующий промпт из системных настроек",
"Prompt Overrides": "Индивидуальный промпт",
"(For OpenAI/Claude/Scale APIs, Window/OpenRouter, and Instruct Mode)": "(для API OpenAI/Claude/Scale, Window/OpenRouter, а также режима Instruct)",
"Insert {{original}} into either box to include the respective default prompt from system settings.": "Введите {{original}} в любое поле, чтобы вставить соответствующий промпт из системных настроек",
"Main Prompt": "Основной промпт",
"Jailbreak": "Джейлбрейк",
"Creator's Metadata (Not sent with the AI prompt)": "Метаданные (не отправляются ИИ)",
"Everything here is optional": "Все поля необязательные",
"Created by": "Автор",
"Character Version": "Версия персонажа",
"Tags to Embed": "Встраиваемые теги",
"How often the character speaks in group chats!": "Как часто персонаж говорит в групповых чатах",
"Important to set the character's writing style.": "Серьёзно влияет на стиль письма персонажа.",
"ATTENTION!": "ВНИМАНИЕ!",
"Samplers Order": "Порядок сэмплеров",
@@ -655,7 +654,7 @@
"Use 'Unlocked Context' to enable chunked generation.": "Использовать 'Неограниченный контекст' для активации кусочной генерации",
"It extends the context window in exchange for reply generation speed.": "Увеличивает размер контекста в обмен на скорость генерации.",
"Continue": "Продолжить",
"CFG Scale": "Масштаб CFG",
"CFG Scale": "CFG Scale",
"Editing:": "Изменения",
"AI reply prefix": "Префикс для ответа ИИ",
"Custom Stopping Strings": "Стоп-строки",
@@ -671,9 +670,9 @@
"Chat Name (Optional)": "Название чата (необязательно)",
"Filter...": "Фильтры...",
"Search...": "Поиск...",
"Any contents here will replace the default Main Prompt used for this character. (v2 spec: system_prompt)": "Все содержание этой ячейки будет заменять стандартный Промт",
"Any contents here will replace the default Jailbreak Prompt used for this character. (v2 spec: post_history_instructions)": "Все содержание этой ячейки будет заменять стандартный Джейлбрейк",
"(Botmaker's name / Contact Info)": "(Имя автора / Контакты)",
"Any contents here will replace the default Main Prompt used for this character. (v2 spec: system_prompt)": "Все содержимое этого поля будет заменять стандартный промпт",
"Any contents here will replace the default Jailbreak Prompt used for this character. (v2 spec: post_history_instructions)": "Все содержимое этого поля будет заменять стандартный джейлбрейк",
"(Botmaker's name / Contact Info)": "(Имя автора, контакты)",
"(If you want to track character versions)": "Если вы хотите отслеживать версии персонажа",
"(Describe the bot, give use tips, or list the chat models it has been tested on. This will be displayed in the character list.)": "(Описание персонажа, советы по использованию, список моделей, на которых он тестировался. Информация будет отображаться в списке персонажей)",
"(Write a comma-separated list of tags)": "(Список тегов через запятую)",
@@ -713,12 +712,12 @@
"Restore defaul note": "Восстановить стандартную заметку",
"API Connections": "Соединения с API",
"Can help with bad responses by queueing only the approved workers. May slowdown the response time.": "Может помочь с плохими ответами ставя в очередь только подтвержденных работников. Может замедлить время ответа.",
"Clear your API key": "Очистите свой ключ от API",
"Clear your API key": "Стереть ключ от API",
"Refresh models": "Обновить модели",
"Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "Получите свой OpenRouter API токен используя OAuth. У вас будет открыта вкладка openrouter.ai",
"Verifies your API connection by sending a short test message. Be aware that you'll be credited for it!": "Проверка работоспособности вашего соединения с API. Знайте, что оно будет отправлено от вашего лица.",
"Create New": "Создать новое",
"Edit": "Изменить",
"Edit": "Редактировать",
"Locked = World Editor will stay open": "Закреплено = Редактор мира останется открытым",
"Entries can activate other entries by mentioning their keywords": "Записи могут активировать другие записи, если в них содержатся ключевые слова",
"Lookup for the entry keys in the context will respect the case": "Большая буква имеет значение при активации ключевого слова",
@@ -847,7 +846,7 @@
"Underlined Text": "Подчёркнутый",
"Token Probabilities": "Вероятности токенов",
"Close chat": "Закрыть чат",
"Manage chat files": "Управление файлами чата",
"Manage chat files": "Управление чатами",
"Import Extension From Git Repo": "Импортировать расширение из Git Repository",
"Install extension": "Установить расширение",
"Manage extensions": "Управление расширениями",
@@ -863,12 +862,12 @@
"When this is off, responses will be displayed all at once when they are complete.": "Если параметр выключен, ответы будут отображаться сразу целиком, и только после полного завершения генерации.",
"Quick Prompts Edit": "Быстрое редактирование промптов",
"Enable OpenAI completion streaming": "Включить стриминг OpenAI",
"Main": "Главное",
"Main": "Основной",
"Utility Prompts": "Служебные промпты",
"Add character names": "Добавить имена персонажей",
"Send names in the message objects. Helps the model to associate messages with characters.": "Отправить имена в объектах сообщений. Помогает модели ассоциировать сообщения с персонажами.",
"Continue prefill": "Префилл для продолжения",
"Continue sends the last message as assistant role instead of system message with instruction.": "Продолжение отправляет последнее сообщение в роли ассистента, а не системное сообщение с инструкцией.",
"Continue sends the last message as assistant role instead of system message with instruction.": "Продолжение отправляет последнее сообщение в роли ассистента, вместо системного сообщения с инструкцией.",
"Squash system messages": "Склеивать сообщения системыы",
"Combines consecutive system messages into one (excluding example dialogues). May improve coherence for some models.": "Объединяет последовательные системные сообщения в одно (за исключением примеров диалогов). Может улучшить согласованность для некоторых моделей.",
"Send inline images": "Отправлять встроенные изображения",
@@ -973,12 +972,128 @@
"Most tokens have a leading space.": "У большинства токенов в начале пробел.",
"Prompts": "Промпты",
"Text or token ids": "Текст или [идентификаторы токенов]",
"World Info Format Template": "Шаблон форматирования информации о мире",
"World Info Format Template": "Шаблон оформления информации о мире",
"Wraps activated World Info entries before inserting into the prompt.": "Дополняет информацию об активном на данный момент мире перед её отправкой в промпт.",
"Doesn't work? Try adding": "Не работает? Попробуйте добавить в конце",
"at the end!": "!",
"Authorize": "Авторизоваться",
"No persona description": "[Нет описания]",
"Not connected to API!": "Нет соединения с API!",
"Type a message, or /? for help": "Введите сообщение, или /? для получения справки по командам"
"Type a message, or /? for help": "Введите сообщение, или /? для получения справки по командам",
"Welcome to SillyTavern!": "Добро пожаловать в SillyTavern!",
"Won't be shared with the character card on export.": "Не попадут в карточку персонажа при экспорте.",
"Web-search": "Веб-поиск",
"Persona Name:": "Имя персоны:",
"User first message": "Первое сообщение пользователя",
"extension_token_counter": "Токенов:",
"Character's Note": "Заметка о персонаже",
"(Text to be inserted in-chat @ designated depth and role)": "Этот текст будет вставлен в чат на заданную глубину и с определённой ролью",
"@ Depth": "Глубина",
"Role": "Роль",
"System": "Система",
"User": "Пользователь",
"Assistant": "Ассистент",
"How often the character speaks in": "Как часто персонаж говорит в",
"group chats!": "групповых чатах!",
"Creator's Metadata": "Метаданные",
"(Not sent with the AI Prompt)": "(не отправляются ИИ)",
"New Chat": "Новый чат",
"Import Chat": "Импорт чата",
"Chat Lore": "Лор чата",
"Chat Lorebook for": "Лорбук для чата",
"A selected World Info will be bound to this chat.": "Выбранный мир будет привязан к этому чату. При генерации ответа ИИ он будет совмещён с записями из глобального лорбука и лорбука персонажа.",
"Missing key": "❌ Ключа нет",
"Key saved": "✔️ Ключ сохранён",
"Use the appropriate tokenizer for Jurassic models, which is more efficient than GPT's.": "Использовать токенайзер для моделей Jurassic, эффективнее GPT-токенайзера",
"Use system prompt (Gemini 1.5 pro+ only)": "Использовать системный промпт (только для Gemini 1.5 pro и выше)",
"Experimental feature. May not work for all backends.": "Экспериментальная возможность, на некоторых бэкендах может не работать.",
"Avatar Hover Magnification": "Зум аватарки по наведению",
"Enable magnification for zoomed avatar display.": "Добавляет возможность приближать увеличенную версию аватарки.",
"Unique to this chat": "Только для текущего чата",
"Checkpoints inherit the Note from their parent, and can be changed individually after that.": "Чекпоинты наследуют заметки от родительского чата, но впоследствие их всегда можно изменить.",
"Include in World Info Scanning": "Учитывать при сканировании Информации о мире",
"Before Main Prompt / Story String": "Перед основным промптом / строкой истории",
"After Main Prompt / Story String": "После основного промпта / строки истории",
"In-chat @ Depth": "Встав. на глуб.",
"as": "роль:",
"Insertion Frequency": "Частота вставки",
"(0 = Disable, 1 = Always)": "(0 = никогда, 1 = всегда)",
"User inputs until next insertion:": "Ваших сообщений до след. вставки:",
"Character Author's Note (Private)": "Заметки автора персонажа (личные)",
"Will be automatically added as the author's note for this character. Will be used in groups, but can't be modified when a group chat is open.": "Автоматически применятся к этому персонажу в качестве заметок автора. Будут использоваться в группах, но при активном групповом чате к редактированию недоступны.",
"Use character author's note": "Использовать заметки автора персонажа",
"Replace Author's Note": "Вместо заметок автора",
"Top of Author's Note": "Сверху от заметок автора",
"Bottom of Author's Note": "Снизу от заметок автора",
"Default Author's Note": "Стандартные заметки автора",
"Will be automatically added as the Author's Note for all new chats.": "Будут автоматически добавляться во все новые чаты в качестве Заметок автора",
"1 = disabled": "1 = откл.",
"write short replies, write replies using past tense": "пиши короткие ответы, пиши в настоящем времени",
"Positive Prompt": "Положительный промпт",
"Character CFG": "CFG для персонажа",
"Will be automatically added as the CFG for this character.": "Автоматически применится к персонажу как его CFG.",
"Global CFG": "Глобальный CFG",
"Will be used as the default CFG options for every chat unless overridden.": "Будет применяться как стандартный CFG для всех чатов, если не указаны индивидуальные настройки.",
"CFG Prompt Cascading": "Совмещение CFG-промптов",
"Combine positive/negative prompts from other boxes.": "Комбинировать различные положительные и негативные промпты.",
"For example, ticking the chat, global, and character boxes combine all negative prompts into a comma-separated string.": "К примеру, если отметить галочки с чатом, персонажем и глобальной настройкой, то все эти негативы соберутся в одну строку, разделённую запятыми.",
"Always Include": "Всегда применять",
"Chat Negatives": "Негативы от чата",
"Character Negatives": "Негативы от персонажа",
"Global Negatives": "Глобальные негативы",
"Custom Separator:": "Кастомный разделитель:",
"Insertion Depth:": "Глубина вставки:",
"Chat CFG": "CFG для чата",
"Chat backgrounds generated with the": "Здесь будут появляться фоны, сгенерированные расширением",
"extension will appear here.": ".",
"Prevent further recursion (this entry will not activate others)": "Пресечь дальнейшую рекурсию (эта запись не будет активировать другие)",
"Alert if your world info is greater than the allocated budget.": "Оповещать, если ваш мир выходит за выделенный бюджет.",
"Convert to Persona": "Преобразовать в персону",
"Link to Source": "Ссылка на источник",
"Replace / Update": "Заменить / Обновить",
"Smoothing Curve": "Кривая сглаживания",
"Message Actions": "Действия с сообщением",
"SillyTavern is aimed at advanced users.": "SillyTavern рассчитана на продвинутых пользователей.",
"If you're new to this, enable the simplified UI mode below.": "Если вы новичок, советуем включить упрощённый UI.",
"Enable simple UI mode": "Включить упрощённый UI",
"welcome_message_part_1": "Ознакомьтесь с",
"welcome_message_part_2": "официальной документацией",
"welcome_message_part_3": ".",
"welcome_message_part_4": "Введите",
"welcome_message_part_5": "в чате, чтобы получить справку по командам и макросам.",
"welcome_message_part_6": "Заходите на наш",
"Discord server": "Discord-сервер,",
"welcome_message_part_7": "там публикуется много разной полезной информации, в том числе анонсы.",
"Before you get started, you must select a persona name.": "Для начала вам следует выбрать имя своей персоны.",
"welcome_message_part_8": "Его можно будет изменить в любое время через иконку",
"welcome_message_part_9": ".",
"UI Language:": "Язык интерфейса:",
"Ignore EOS Token": "Игнорировать EOS-токен",
"Ignore the EOS Token even if it generates.": "Игнорировать EOS-токен, даже если он сгенерировался.",
"Hide Muted Member Sprites": "Скрыть спрайты заглушенных участников",
"Group generation handling mode": "Генерировать ответы путём...",
"Swap character cards": "Подмены карточки персонажа",
"Join character cards (exclude muted)": "Совмещения карточек (кроме заглушенных)",
"Join character cards (include muted)": "Совмещения карточек (включая заглушенных)",
"Click to allow/forbid the use of external media for this group.": "Нажмите, чтобы разрешить/запретить использование внешних медиа в этой группе.",
"Scenario Format Template": "Шаблон оформления сценария",
"scenario_format_template_part_1": "Используйте",
"scenario_format_template_part_2": "чтобы указать, куда именно вставляется основное содержимое.",
"Personality Format Template": "Шаблон оформления характера",
"Group Nudge Prompt Template": "Шаблон промпта-подсказки для групп",
"Sent at the end of the group chat history to force reply from a specific character.": "Добавляется в конец истории сообщений в групповом чате, чтобы запросить ответ от конкретного персонажа.",
"Set at the beginning of the chat history to indicate that a new chat is about to start.": "Добавляется в начале истории сообщений в качестве указания на то, что дальше начнётся новый чат.",
"New Group Chat": "Новый групповой чат",
"Set at the beginning of the chat history to indicate that a new group chat is about to start.": "Добавляется в начале истории сообщений в качестве указания на то, что дальше начнётся новый групповой чат.",
"New Example Chat": "Новый образец чата",
"Set at the beginning of Dialogue examples to indicate that a new example chat is about to start.": "Добавляется в начале примеров диалогов в качестве указания на то, что дальше начнётся новый чат-пример.",
"Continue nudge": "Подсказка для продолжения",
"Set at the end of the chat history when the continue button is pressed.": "Добавляется в конец истории чата, когда отправлен запрос на продолжение текущего сообщения.",
"Prompts": "Промпты",
"Your Persona": "Ваша персона",
"Continue Postfix": "Постфикс для продолжения",
"Space": "Пробел",
"Newline": "Новая строка",
"Double Newline": "Две новые строки",
"The next chunk of the continued message will be appended using this as a separator.": "Используется в качестве разделителя между уже имеющимся сообщением и его новым отрывком, при генерации продолжения"
}

81
public/login.html Normal file
View File

@@ -0,0 +1,81 @@
<!DOCTYPE html>
<html>
<head>
<base href="/">
<meta charset="utf-8">
<meta name="viewport"
content="width=device-width, viewport-fit=cover, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="darkreader-lock">
<meta name="robots" content="noindex, nofollow" />
<link rel="apple-touch-icon" sizes="57x57" href="img/apple-icon-57x57.png" />
<link rel="apple-touch-icon" sizes="72x72" href="img/apple-icon-72x72.png" />
<link rel="apple-touch-icon" sizes="114x114" href="img/apple-icon-114x114.png" />
<link rel="apple-touch-icon" sizes="144x144" href="img/apple-icon-144x144.png" />
<link rel="icon" href="favicon.ico" type="image/x-icon">
<link rel="stylesheet" type="text/css" href="style.css">
<link rel="stylesheet" type="text/css" href="css/st-tailwind.css">
<link rel="stylesheet" type="text/css" href="css/login.css">
<link rel="manifest" crossorigin="use-credentials" href="manifest.json">
<link href="webfonts/NotoSans/stylesheet.css" rel="stylesheet">
<!-- fontawesome webfonts-->
<link href="css/fontawesome.css" rel="stylesheet">
<link href="css/solid.css" rel="stylesheet">
<link href="css/user.css" rel="stylesheet">
<script src="lib/jquery-3.5.1.min.js"></script>
<script src="scripts/login.js"></script>
<title>SillyTavern</title>
</head>
<body class="login">
<div id="shadow_popup" style="opacity: 0;">
<div id="dialogue_popup">
<div id="dialogue_popup_holder">
<div id="dialogue_popup_text">
<div id="userSelectBlock" class="flex-container flexFlowColumn alignItemsCenter">
<h2 id="logoBlock" class="flex-container">
<img src="img/logo.png" alt="SillyTavern" class="logo">
<span>Welcome to SillyTavern</span>
</h2>
<h3 id="normalLoginPrompt">
Select an Account
</h3>
<h3 id="discreetLoginPrompt">
Enter Login Details
</h3>
<div id="userListBlock" class="wide100p">
<div id="userList" class="flex-container justifySpaceEvenly"></div>
<div id="handleEntryBlock" style="display:none;" class="flex-container flexFlowColumn alignItemsCenter">
<input id="userHandle" class="text_pole" type="text" placeholder="User handle" autocomplete="username">
</div>
<div id="passwordEntryBlock" style="display:none;"
class="flex-container flexFlowColumn alignItemsCenter">
<input id="userPassword" class="text_pole" type="password" placeholder="Password" autocomplete="current-password">
<a id="recoverPassword" href="#" onclick="return false;">Forgot password?</a>
<div class="flex-container">
<div id="loginButton" class="menu_button">Login</div>
</div>
</div>
<div id="passwordRecoveryBlock" style="display:none;"
class="flex-container flexFlowColumn alignItemsCenter">
<div id="recoverMessage">
Recovery code has been posted to the server console.
</div>
<input id="recoveryCode" class="text_pole" type="text" placeholder="Recovery code">
<input id="newPassword" class="text_pole" type="password" placeholder="New password" autocomplete="new-password">
<div class="flex-container flexGap10">
<div id="sendRecovery" class="menu_button">Send</div>
<div id="cancelRecovery" class="menu_button">Cancel</div>
</div>
</div>
</div>
<div class="neutral_warning" id="errorMessage">
</div>
</div>
</div>
</div>
</div>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -5,6 +5,7 @@ import { is_group_generating } from './group-chats.js';
import { Message, TokenHandler } from './openai.js';
import { power_user } from './power-user.js';
import { debounce, waitUntilCondition, escapeHtml } from './utils.js';
import { debounce_timeout } from './constants.js';
function debouncePromise(func, delay) {
let timeoutId;
@@ -294,7 +295,7 @@ class PromptManager {
this.handleCharacterReset = () => { };
/** Debounced version of render */
this.renderDebounced = debounce(this.render.bind(this), 1000);
this.renderDebounced = debounce(this.render.bind(this), debounce_timeout.relaxed);
}
@@ -776,7 +777,7 @@ class PromptManager {
const promptOrder = this.getPromptOrderForCharacter(character);
const index = promptOrder.findIndex(entry => entry.identifier === prompt.identifier);
if (-1 === index) promptOrder.push({ identifier: prompt.identifier, enabled: false });
if (-1 === index) promptOrder.unshift({ identifier: prompt.identifier, enabled: false });
}
/**
@@ -1286,7 +1287,7 @@ class PromptManager {
} else if (!entry.enabled && entry.identifier === 'main') {
// Some extensions require main prompt to be present for relative inserts.
// So we make a GMO-free vegan replacement.
const prompt = this.getPromptById(entry.identifier);
const prompt = structuredClone(this.getPromptById(entry.identifier));
prompt.content = '';
if (prompt) promptCollection.add(this.preparePrompt(prompt));
}

View File

@@ -32,10 +32,11 @@ import {
SECRET_KEYS,
secret_state,
} from './secrets.js';
import { debounce, delay, getStringHash, isValidUrl } from './utils.js';
import { debounce, getStringHash, isValidUrl } from './utils.js';
import { chat_completion_sources, oai_settings } from './openai.js';
import { getTokenCountAsync } from './tokenizers.js';
import { textgen_types, textgenerationwebui_settings as textgen_settings, getTextGenServer } from './textgen-settings.js';
import { debounce_timeout } from './constants.js';
import Bowser from '../lib/bowser.min.js';
@@ -54,7 +55,7 @@ var retry_delay = 500;
let counterNonce = Date.now();
const observerConfig = { childList: true, subtree: true };
const countTokensDebounced = debounce(RA_CountCharTokens, 1000);
const countTokensDebounced = debounce(RA_CountCharTokens, debounce_timeout.relaxed);
const observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
@@ -80,19 +81,21 @@ observer.observe(document.documentElement, observerConfig);
/**
* Converts generation time from milliseconds to a human-readable format.
* Converts a timespan from milliseconds to a human-readable format.
*
* The function takes total generation time as an input, then converts it to a format
* The function takes a total timespan as an input, then converts it to a format
* of "_ Days, _ Hours, _ Minutes, _ Seconds". If the generation time does not exceed a
* particular measure (like days or hours), that measure will not be included in the output.
*
* @param {number} total_gen_time - The total generation time in milliseconds.
* @returns {string} - A human-readable string that represents the time spent generating characters.
* @param {number} timespan - The total timespan in milliseconds.
* @param {object} [options] - Optional parameters
* @param {boolean} [options.short=false] - Flag indicating whether short form should be used. ('2h' instead of '2 Hours')
* @param {number} [options.onlyHighest] - Number of maximum blocks to be returned. (If, and daya is the highest matching unit, only returns days and hours, cutting of minutes and seconds)
* @returns {string} - A human-readable string that represents the timespan.
*/
export function humanizeGenTime(total_gen_time) {
export function humanizeTimespan(timespan, { short = false, onlyHighest = 2 } = {}) {
//convert time_spent to humanized format of "_ Hours, _ Minutes, _ Seconds" from milliseconds
let time_spent = total_gen_time || 0;
let time_spent = timespan || 0;
time_spent = Math.floor(time_spent / 1000);
let seconds = time_spent % 60;
time_spent = Math.floor(time_spent / 60);
@@ -101,12 +104,36 @@ export function humanizeGenTime(total_gen_time) {
let hours = time_spent % 24;
time_spent = Math.floor(time_spent / 24);
let days = time_spent;
time_spent = '';
if (days > 0) { time_spent += `${days} Days, `; }
if (hours > 0) { time_spent += `${hours} Hours, `; }
if (minutes > 0) { time_spent += `${minutes} Minutes, `; }
time_spent += `${seconds} Seconds`;
return time_spent;
let parts = [
{ singular: 'Day', plural: 'Days', short: 'd', value: days },
{ singular: 'Hour', plural: 'Hours', short: 'h', value: hours },
{ singular: 'Minute', plural: 'Minutes', short: 'm', value: minutes },
{ singular: 'Second', plural: 'Seconds', short: 's', value: seconds },
];
// Build the final string based on the highest significant units and respecting zeros
let resultParts = [];
let count = 0;
for (let part of parts) {
if (part.value > 0) {
resultParts.push(part);
}
// If we got a match, we count from there. Take a maximum of X elements
if (resultParts.length) count++;
if (count >= onlyHighest) {
break;
}
}
if (!resultParts.length) {
return short ? '&lt;1s' : 'Instant';
}
return resultParts.map(part => {
return short ? `${part.value}${part.short}` : `${part.value} ${part.value === 1 ? part.singular : part.plural}`;
}).join(short ? ' ' : ', ');
}
/**

View File

@@ -12,6 +12,7 @@ import { extension_settings, getContext, saveMetadataDebounced } from './extensi
import { registerSlashCommand } from './slash-commands.js';
import { getCharaFilename, debounce, delay } from './utils.js';
import { getTokenCountAsync } from './tokenizers.js';
import { debounce_timeout } from './constants.js';
export { MODULE_NAME as NOTE_MODULE_NAME };
const MODULE_NAME = '2_floating_prompt'; // <= Deliberate, for sorting lower than memory
@@ -84,9 +85,9 @@ function updateSettings() {
setFloatingPrompt();
}
const setMainPromptTokenCounterDebounced = debounce(async (value) => $('#extension_floating_prompt_token_counter').text(await getTokenCountAsync(value)), 1000);
const setCharaPromptTokenCounterDebounced = debounce(async (value) => $('#extension_floating_chara_token_counter').text(await getTokenCountAsync(value)), 1000);
const setDefaultPromptTokenCounterDebounced = debounce(async (value) => $('#extension_floating_default_token_counter').text(await getTokenCountAsync(value)), 1000);
const setMainPromptTokenCounterDebounced = debounce(async (value) => $('#extension_floating_prompt_token_counter').text(await getTokenCountAsync(value)), debounce_timeout.relaxed);
const setCharaPromptTokenCounterDebounced = debounce(async (value) => $('#extension_floating_chara_token_counter').text(await getTokenCountAsync(value)), debounce_timeout.relaxed);
const setDefaultPromptTokenCounterDebounced = debounce(async (value) => $('#extension_floating_default_token_counter').text(await getTokenCountAsync(value)), debounce_timeout.relaxed);
async function onExtensionFloatingPromptInput() {
chat_metadata[metadata_keys.prompt] = $(this).val();

View File

@@ -1,7 +1,7 @@
import { callPopup, chat_metadata, eventSource, event_types, generateQuietPrompt, getCurrentChatId, getRequestHeaders, getThumbnailUrl, saveSettingsDebounced } from '../script.js';
import { saveMetadataDebounced } from './extensions.js';
import { registerSlashCommand } from './slash-commands.js';
import { stringFormat } from './utils.js';
import { flashHighlight, stringFormat } from './utils.js';
const BG_METADATA_KEY = 'custom_background';
const LIST_METADATA_KEY = 'chat_backgrounds';
@@ -453,8 +453,7 @@ function highlightNewBackground(bg) {
const newBg = $(`.bg_example[bgfile="${bg}"]`);
const scrollOffset = newBg.offset().top - newBg.parent().offset().top;
$('#Backgrounds').scrollTop(scrollOffset);
newBg.addClass('flash animated');
setTimeout(() => newBg.removeClass('flash animated'), 2000);
flashHighlight(newBg);
}
function onBackgroundFilterInput() {

View File

@@ -1,4 +1,4 @@
import { characters, getCharacters, handleDeleteCharacter, callPopup, characterGroupOverlay } from '../script.js';
import { characterGroupOverlay } from '../script.js';
import { BulkEditOverlay, BulkEditOverlayState } from './BulkEditOverlay.js';
@@ -69,15 +69,6 @@ function onSelectAllButtonClick() {
}
}
/**
* Deletes the character with the given chid.
*
* @param {string} this_chid - The chid of the character to delete.
*/
async function deleteCharacter(this_chid) {
await handleDeleteCharacter('del_ch', this_chid, false);
}
/**
* Deletes all characters that have been selected via the bulk checkboxes.
*/

View File

@@ -18,6 +18,8 @@ import {
saveSettingsDebounced,
showSwipeButtons,
this_chid,
saveChatConditional,
chat_metadata,
} from '../script.js';
import { selected_group } from './group-chats.js';
import { power_user } from './power-user.js';
@@ -25,22 +27,93 @@ import {
extractTextFromHTML,
extractTextFromMarkdown,
extractTextFromPDF,
extractTextFromEpub,
getBase64Async,
getStringHash,
humanFileSize,
saveBase64AsFile,
extractTextFromOffice,
} from './utils.js';
import { extension_settings, renderExtensionTemplateAsync, saveMetadataDebounced } from './extensions.js';
import { POPUP_RESULT, POPUP_TYPE, callGenericPopup } from './popup.js';
import { ScraperManager } from './scrapers.js';
const fileSizeLimit = 1024 * 1024 * 10; // 10 MB
/**
* @typedef {Object} FileAttachment
* @property {string} url File URL
* @property {number} size File size
* @property {string} name File name
* @property {number} created Timestamp
* @property {string} [text] File text
*/
/**
* @typedef {function} ConverterFunction
* @param {File} file File object
* @returns {Promise<string>} Converted file text
*/
const fileSizeLimit = 1024 * 1024 * 100; // 100 MB
const ATTACHMENT_SOURCE = {
GLOBAL: 'global',
CHARACTER: 'character',
CHAT: 'chat',
};
/**
* @type {Record<string, ConverterFunction>} File converters
*/
const converters = {
'application/pdf': extractTextFromPDF,
'text/html': extractTextFromHTML,
'text/markdown': extractTextFromMarkdown,
'application/epub+zip': extractTextFromEpub,
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': extractTextFromOffice,
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': extractTextFromOffice,
'application/vnd.openxmlformats-officedocument.presentationml.presentation': extractTextFromOffice,
'application/vnd.oasis.opendocument.text': extractTextFromOffice,
'application/vnd.oasis.opendocument.presentation': extractTextFromOffice,
'application/vnd.oasis.opendocument.spreadsheet': extractTextFromOffice,
};
/**
* Finds a matching key in the converters object.
* @param {string} type MIME type
* @returns {string} Matching key
*/
function findConverterKey(type) {
return Object.keys(converters).find((key) => {
// Match exact type
if (type === key) {
return true;
}
// Match wildcards
if (key.endsWith('*')) {
return type.startsWith(key.substring(0, key.length - 1));
}
return false;
});
}
/**
* Determines if the file type has a converter function.
* @param {string} type MIME type
* @returns {boolean} True if the file type is convertible, false otherwise.
*/
function isConvertible(type) {
return Object.keys(converters).includes(type);
return Boolean(findConverterKey(type));
}
/**
* Gets the converter function for a file type.
* @param {string} type MIME type
* @returns {ConverterFunction} Converter function
*/
function getConverter(type) {
const key = findConverterKey(type);
return key && converters[key];
}
/**
@@ -126,7 +199,7 @@ export async function populateFileAttachment(message, inputId = 'file_form_input
if (isConvertible(file.type)) {
try {
const converter = converters[file.type];
const converter = getConverter(file.type);
const fileText = await converter(file);
base64Data = window.btoa(unescape(encodeURIComponent(fileText)));
} catch (error) {
@@ -145,6 +218,7 @@ export async function populateFileAttachment(message, inputId = 'file_form_input
url: fileUrl,
size: file.size,
name: file.name,
created: Date.now(),
};
}
@@ -275,9 +349,9 @@ async function onFileAttach() {
* @param {number} messageId Message ID
*/
async function deleteMessageFile(messageId) {
const confirm = await callPopup('Are you sure you want to delete this file?', 'confirm');
const confirm = await callGenericPopup('Are you sure you want to delete this file?', POPUP_TYPE.CONFIRM);
if (!confirm) {
if (confirm !== POPUP_RESULT.AFFIRMATIVE) {
console.debug('Delete file cancelled');
return;
}
@@ -289,11 +363,15 @@ async function deleteMessageFile(messageId) {
return;
}
const url = message.extra.file.url;
delete message.extra.file;
$(`.mes[mesid="${messageId}"] .mes_file_container`).remove();
saveChatDebounced();
await saveChatConditional();
await deleteFileFromServer(url);
}
/**
* Opens file from message in a modal.
* @param {number} messageId Message ID
@@ -306,14 +384,7 @@ async function viewMessageFile(messageId) {
return;
}
const fileText = messageFile.text || (await getFileAttachment(messageFile.url));
const modalTemplate = $('<div><pre><code></code></pre></div>');
modalTemplate.find('code').addClass('txt').text(fileText);
modalTemplate.addClass('file_modal');
addCopyToCodeBlocks(modalTemplate);
callPopup(modalTemplate, 'text', '', { wide: true, large: true });
await openFilePopup(messageFile);
}
/**
@@ -348,7 +419,7 @@ function embedMessageFile(messageId, messageBlock) {
await populateFileAttachment(message, 'embed_file_input');
appendMediaToMessage(message, messageBlock);
saveChatDebounced();
await saveChatConditional();
}
}
@@ -363,7 +434,7 @@ export async function appendFileContent(message, messageText) {
const fileText = message.extra.file.text || (await getFileAttachment(message.extra.file.url));
if (fileText) {
const fileWrapped = `\`\`\`\n${fileText}\n\`\`\`\n\n`;
const fileWrapped = `${fileText}\n\n`;
message.extra.fileLength = fileWrapped.length;
messageText = fileWrapped + messageText;
}
@@ -395,7 +466,7 @@ export function decodeStyleTags(text) {
return text.replaceAll(styleDecodeRegex, (_, style) => {
try {
let styleCleaned = unescape(style).replaceAll(/<br\/>/g, '');
let styleCleaned = unescape(style).replaceAll(/<br\/>/g, '');
const ast = css.parse(styleCleaned);
const rules = ast?.stylesheet?.rules;
if (rules) {
@@ -436,8 +507,8 @@ async function openExternalMediaOverridesDialog() {
}
const template = $('#forbid_media_override_template > .forbid_media_override').clone();
template.find('.forbid_media_global_state_forbidden').toggle(power_user.forbid_external_images);
template.find('.forbid_media_global_state_allowed').toggle(!power_user.forbid_external_images);
template.find('.forbid_media_global_state_forbidden').toggle(power_user.forbid_external_media);
template.find('.forbid_media_global_state_allowed').toggle(!power_user.forbid_external_media);
if (power_user.external_media_allowed_overrides.includes(entityId)) {
template.find('#forbid_media_override_allowed').prop('checked', true);
@@ -463,7 +534,7 @@ export function getCurrentEntityId() {
export function isExternalMediaAllowed() {
const entityId = getCurrentEntityId();
if (!entityId) {
return !power_user.forbid_external_images;
return !power_user.forbid_external_media;
}
if (power_user.external_media_allowed_overrides.includes(entityId)) {
@@ -474,7 +545,736 @@ export function isExternalMediaAllowed() {
return false;
}
return !power_user.forbid_external_images;
return !power_user.forbid_external_media;
}
function enlargeMessageImage() {
const mesBlock = $(this).closest('.mes');
const mesId = mesBlock.attr('mesid');
const message = chat[mesId];
const imgSrc = message?.extra?.image;
const title = message?.extra?.title;
if (!imgSrc) {
return;
}
const img = document.createElement('img');
img.classList.add('img_enlarged');
img.src = imgSrc;
const imgContainer = $('<div><pre><code></code></pre></div>');
imgContainer.prepend(img);
imgContainer.addClass('img_enlarged_container');
imgContainer.find('code').addClass('txt').text(title);
const titleEmpty = !title || title.trim().length === 0;
imgContainer.find('pre').toggle(!titleEmpty);
addCopyToCodeBlocks(imgContainer);
callGenericPopup(imgContainer, POPUP_TYPE.TEXT, '', { wide: true, large: true });
}
async function deleteMessageImage() {
const value = await callGenericPopup('<h3>Delete image from message?<br>This action can\'t be undone.</h3>', POPUP_TYPE.CONFIRM);
if (value !== POPUP_RESULT.AFFIRMATIVE) {
return;
}
const mesBlock = $(this).closest('.mes');
const mesId = mesBlock.attr('mesid');
const message = chat[mesId];
delete message.extra.image;
delete message.extra.inline_image;
mesBlock.find('.mes_img_container').removeClass('img_extra');
mesBlock.find('.mes_img').attr('src', '');
await saveChatConditional();
}
/**
* Deletes file from the server.
* @param {string} url Path to the file on the server
* @param {boolean} [silent=false] If true, do not show error messages
* @returns {Promise<boolean>} True if file was deleted, false otherwise.
*/
async function deleteFileFromServer(url, silent = false) {
try {
const result = await fetch('/api/files/delete', {
method: 'POST',
headers: getRequestHeaders(),
body: JSON.stringify({ path: url }),
});
if (!result.ok && !silent) {
const error = await result.text();
throw new Error(error);
}
await eventSource.emit(event_types.FILE_ATTACHMENT_DELETED, url);
return true;
} catch (error) {
toastr.error(String(error), 'Could not delete file');
console.error('Could not delete file', error);
return false;
}
}
/**
* Opens file attachment in a modal.
* @param {FileAttachment} attachment File attachment
*/
async function openFilePopup(attachment) {
const fileText = attachment.text || (await getFileAttachment(attachment.url));
const modalTemplate = $('<div><pre><code></code></pre></div>');
modalTemplate.find('code').addClass('txt').text(fileText);
modalTemplate.addClass('file_modal').addClass('textarea_compact').addClass('fontsize90p');
addCopyToCodeBlocks(modalTemplate);
callGenericPopup(modalTemplate, POPUP_TYPE.TEXT, '', { wide: true, large: true });
}
/**
* Edit a file attachment in a notepad-like modal.
* @param {FileAttachment} attachment Attachment to edit
* @param {string} source Attachment source
* @param {function} callback Callback function
*/
async function editAttachment(attachment, source, callback) {
const originalFileText = attachment.text || (await getFileAttachment(attachment.url));
const template = $(await renderExtensionTemplateAsync('attachments', 'notepad'));
let editedFileText = originalFileText;
template.find('[name="notepadFileContent"]').val(editedFileText).on('input', function () {
editedFileText = String($(this).val());
});
let editedFileName = attachment.name;
template.find('[name="notepadFileName"]').val(editedFileName).on('input', function () {
editedFileName = String($(this).val());
});
const result = await callGenericPopup(template, POPUP_TYPE.CONFIRM, '', { wide: true, large: true, okButton: 'Save', cancelButton: 'Cancel' });
if (result !== POPUP_RESULT.AFFIRMATIVE) {
return;
}
if (editedFileText === originalFileText && editedFileName === attachment.name) {
return;
}
const nullCallback = () => { };
await deleteAttachment(attachment, source, nullCallback, false);
const file = new File([editedFileText], editedFileName, { type: 'text/plain' });
await uploadFileAttachmentToServer(file, source);
callback();
}
/**
* Downloads an attachment to the user's device.
* @param {FileAttachment} attachment Attachment to download
*/
async function downloadAttachment(attachment) {
const fileText = attachment.text || (await getFileAttachment(attachment.url));
const blob = new Blob([fileText], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = attachment.name;
a.click();
URL.revokeObjectURL(url);
}
/**
* Removes an attachment from the disabled list.
* @param {FileAttachment} attachment Attachment to enable
* @param {function} callback Success callback
*/
function enableAttachment(attachment, callback) {
ensureAttachmentsExist();
extension_settings.disabled_attachments = extension_settings.disabled_attachments.filter(url => url !== attachment.url);
saveSettingsDebounced();
callback();
}
/**
* Adds an attachment to the disabled list.
* @param {FileAttachment} attachment Attachment to disable
* @param {function} callback Success callback
*/
function disableAttachment(attachment, callback) {
ensureAttachmentsExist();
extension_settings.disabled_attachments.push(attachment.url);
saveSettingsDebounced();
callback();
}
/**
* Moves a file attachment to a different source.
* @param {FileAttachment} attachment Attachment to moves
* @param {string} source Source of the attachment
* @param {function} callback Success callback
* @returns {Promise<void>} A promise that resolves when the attachment is moved.
*/
async function moveAttachment(attachment, source, callback) {
let selectedTarget = source;
const targets = getAvailableTargets();
const template = $(await renderExtensionTemplateAsync('attachments', 'move-attachment', { name: attachment.name, targets }));
template.find('.moveAttachmentTarget').val(source).on('input', function () {
selectedTarget = String($(this).val());
});
const result = await callGenericPopup(template, POPUP_TYPE.CONFIRM, '', { wide: false, large: false, okButton: 'Move', cancelButton: 'Cancel' });
if (result !== POPUP_RESULT.AFFIRMATIVE) {
console.debug('Move attachment cancelled');
return;
}
if (selectedTarget === source) {
console.debug('Move attachment cancelled: same source and target');
return;
}
const content = await getFileAttachment(attachment.url);
const file = new File([content], attachment.name, { type: 'text/plain' });
await deleteAttachment(attachment, source, () => { }, false);
await uploadFileAttachmentToServer(file, selectedTarget);
callback();
}
/**
* Deletes an attachment from the server and the chat.
* @param {FileAttachment} attachment Attachment to delete
* @param {string} source Source of the attachment
* @param {function} callback Callback function
* @param {boolean} [confirm=true] If true, show a confirmation dialog
* @returns {Promise<void>} A promise that resolves when the attachment is deleted.
*/
async function deleteAttachment(attachment, source, callback, confirm = true) {
if (confirm) {
const result = await callGenericPopup('Are you sure you want to delete this attachment?', POPUP_TYPE.CONFIRM);
if (result !== POPUP_RESULT.AFFIRMATIVE) {
return;
}
}
ensureAttachmentsExist();
switch (source) {
case 'global':
extension_settings.attachments = extension_settings.attachments.filter((a) => a.url !== attachment.url);
saveSettingsDebounced();
break;
case 'chat':
chat_metadata.attachments = chat_metadata.attachments.filter((a) => a.url !== attachment.url);
saveMetadataDebounced();
break;
case 'character':
extension_settings.character_attachments[characters[this_chid]?.avatar] = extension_settings.character_attachments[characters[this_chid]?.avatar].filter((a) => a.url !== attachment.url);
break;
}
if (Array.isArray(extension_settings.disabled_attachments) && extension_settings.disabled_attachments.includes(attachment.url)) {
extension_settings.disabled_attachments = extension_settings.disabled_attachments.filter(url => url !== attachment.url);
saveSettingsDebounced();
}
const silent = confirm === false;
await deleteFileFromServer(attachment.url, silent);
callback();
}
/**
* Determines if the attachment is disabled.
* @param {FileAttachment} attachment Attachment to check
* @returns {boolean} True if attachment is disabled, false otherwise.
*/
function isAttachmentDisabled(attachment) {
return extension_settings.disabled_attachments.some(url => url === attachment?.url);
}
/**
* Opens the attachment manager.
*/
async function openAttachmentManager() {
/**
* Renders a list of attachments.
* @param {FileAttachment[]} attachments List of attachments
* @param {string} source Source of the attachments
*/
async function renderList(attachments, source) {
/**
* Sorts attachments by sortField and sortOrder.
* @param {FileAttachment} a First attachment
* @param {FileAttachment} b Second attachment
* @returns {number} Sort order
*/
function sortFn(a, b) {
const sortValueA = a[sortField];
const sortValueB = b[sortField];
if (typeof sortValueA === 'string' && typeof sortValueB === 'string') {
return sortValueA.localeCompare(sortValueB) * (sortOrder === 'asc' ? 1 : -1);
}
return (sortValueA - sortValueB) * (sortOrder === 'asc' ? 1 : -1);
}
/**
* Filters attachments by name.
* @param {FileAttachment} a Attachment
* @returns {boolean} True if attachment matches the filter, false otherwise.
*/
function filterFn(a) {
if (!filterString) {
return true;
}
return a.name.toLowerCase().includes(filterString.toLowerCase());
}
const sources = {
[ATTACHMENT_SOURCE.GLOBAL]: '.globalAttachmentsList',
[ATTACHMENT_SOURCE.CHARACTER]: '.characterAttachmentsList',
[ATTACHMENT_SOURCE.CHAT]: '.chatAttachmentsList',
};
template.find(sources[source]).empty();
// Sort attachments by sortField and sortOrder, and apply filter
const sortedAttachmentList = attachments.slice().filter(filterFn).sort(sortFn);
for (const attachment of sortedAttachmentList) {
const isDisabled = isAttachmentDisabled(attachment);
const attachmentTemplate = template.find('.attachmentListItemTemplate .attachmentListItem').clone();
attachmentTemplate.toggleClass('disabled', isDisabled);
attachmentTemplate.find('.attachmentFileIcon').attr('title', attachment.url);
attachmentTemplate.find('.attachmentListItemName').text(attachment.name);
attachmentTemplate.find('.attachmentListItemSize').text(humanFileSize(attachment.size));
attachmentTemplate.find('.attachmentListItemCreated').text(new Date(attachment.created).toLocaleString());
attachmentTemplate.find('.viewAttachmentButton').on('click', () => openFilePopup(attachment));
attachmentTemplate.find('.editAttachmentButton').on('click', () => editAttachment(attachment, source, renderAttachments));
attachmentTemplate.find('.deleteAttachmentButton').on('click', () => deleteAttachment(attachment, source, renderAttachments));
attachmentTemplate.find('.downloadAttachmentButton').on('click', () => downloadAttachment(attachment));
attachmentTemplate.find('.moveAttachmentButton').on('click', () => moveAttachment(attachment, source, renderAttachments));
attachmentTemplate.find('.enableAttachmentButton').toggle(isDisabled).on('click', () => enableAttachment(attachment, renderAttachments));
attachmentTemplate.find('.disableAttachmentButton').toggle(!isDisabled).on('click', () => disableAttachment(attachment, renderAttachments));
template.find(sources[source]).append(attachmentTemplate);
}
}
/**
* Renders buttons for the attachment manager.
*/
async function renderButtons() {
const sources = {
[ATTACHMENT_SOURCE.GLOBAL]: '.globalAttachmentsTitle',
[ATTACHMENT_SOURCE.CHARACTER]: '.characterAttachmentsTitle',
[ATTACHMENT_SOURCE.CHAT]: '.chatAttachmentsTitle',
};
const modal = template.find('.actionButtonsModal').hide();
const scrapers = ScraperManager.getDataBankScrapers();
for (const scraper of scrapers) {
const isAvailable = await ScraperManager.isScraperAvailable(scraper.id);
if (!isAvailable) {
continue;
}
const buttonTemplate = template.find('.actionButtonTemplate .actionButton').clone();
if (scraper.iconAvailable) {
buttonTemplate.find('.actionButtonIcon').addClass(scraper.iconClass);
buttonTemplate.find('.actionButtonImg').remove();
} else {
buttonTemplate.find('.actionButtonImg').attr('src', scraper.iconClass);
buttonTemplate.find('.actionButtonIcon').remove();
}
buttonTemplate.find('.actionButtonText').text(scraper.name);
buttonTemplate.attr('title', scraper.description);
buttonTemplate.on('click', () => {
const target = modal.attr('data-attachment-manager-target');
runScraper(scraper.id, target, renderAttachments);
});
modal.append(buttonTemplate);
}
const modalButtonData = Object.entries(sources).map(entry => {
const [source, selector] = entry;
const button = template.find(selector).find('.openActionModalButton').get(0);
if (!button) {
return;
}
const bodyListener = (e) => {
if (modal.is(':visible') && (!$(e.target).closest('.openActionModalButton').length)) {
modal.hide();
}
// Replay a click if the modal was already open by another button
if ($(e.target).closest('.openActionModalButton').length && !modal.is(':visible')) {
modal.show();
}
};
document.body.addEventListener('click', bodyListener);
const popper = Popper.createPopper(button, modal.get(0), { placement: 'bottom-end' });
button.addEventListener('click', () => {
modal.attr('data-attachment-manager-target', source);
modal.toggle();
popper.update();
});
return [popper, bodyListener];
}).filter(Boolean);
return () => {
modalButtonData.forEach(p => {
const [popper, bodyListener] = p;
popper.destroy();
document.body.removeEventListener('click', bodyListener);
});
modal.remove();
};
}
async function renderAttachments() {
/** @type {FileAttachment[]} */
const globalAttachments = extension_settings.attachments ?? [];
/** @type {FileAttachment[]} */
const chatAttachments = chat_metadata.attachments ?? [];
/** @type {FileAttachment[]} */
const characterAttachments = extension_settings.character_attachments?.[characters[this_chid]?.avatar] ?? [];
await renderList(globalAttachments, ATTACHMENT_SOURCE.GLOBAL);
await renderList(chatAttachments, ATTACHMENT_SOURCE.CHAT);
await renderList(characterAttachments, ATTACHMENT_SOURCE.CHARACTER);
const isNotCharacter = this_chid === undefined || selected_group;
const isNotInChat = getCurrentChatId() === undefined;
template.find('.characterAttachmentsBlock').toggle(!isNotCharacter);
template.find('.chatAttachmentsBlock').toggle(!isNotInChat);
const characterName = characters[this_chid]?.name || 'Anonymous';
template.find('.characterAttachmentsName').text(characterName);
const chatName = getCurrentChatId() || 'Unnamed chat';
template.find('.chatAttachmentsName').text(chatName);
}
function addDragAndDrop() {
$(document.body).on('dragover', '.dialogue_popup', (event) => {
event.preventDefault();
event.stopPropagation();
$(event.target).closest('.dialogue_popup').addClass('dragover');
});
$(document.body).on('dragleave', '.dialogue_popup', (event) => {
event.preventDefault();
event.stopPropagation();
$(event.target).closest('.dialogue_popup').removeClass('dragover');
});
$(document.body).on('drop', '.dialogue_popup', async (event) => {
event.preventDefault();
event.stopPropagation();
$(event.target).closest('.dialogue_popup').removeClass('dragover');
const files = Array.from(event.originalEvent.dataTransfer.files);
let selectedTarget = ATTACHMENT_SOURCE.GLOBAL;
const targets = getAvailableTargets();
const targetSelectTemplate = $(await renderExtensionTemplateAsync('attachments', 'files-dropped', { count: files.length, targets: targets }));
targetSelectTemplate.find('.droppedFilesTarget').on('input', function () {
selectedTarget = String($(this).val());
});
const result = await callGenericPopup(targetSelectTemplate, POPUP_TYPE.CONFIRM, '', { wide: false, large: false, okButton: 'Upload', cancelButton: 'Cancel' });
if (result !== POPUP_RESULT.AFFIRMATIVE) {
console.log('File upload cancelled');
return;
}
for (const file of files) {
await uploadFileAttachmentToServer(file, selectedTarget);
}
renderAttachments();
});
}
function removeDragAndDrop() {
$(document.body).off('dragover', '.shadow_popup');
$(document.body).off('dragleave', '.shadow_popup');
$(document.body).off('drop', '.shadow_popup');
}
let sortField = localStorage.getItem('DataBank_sortField') || 'created';
let sortOrder = localStorage.getItem('DataBank_sortOrder') || 'desc';
let filterString = '';
const template = $(await renderExtensionTemplateAsync('attachments', 'manager', {}));
template.find('.attachmentSearch').on('input', function () {
filterString = String($(this).val());
renderAttachments();
});
template.find('.attachmentSort').on('change', function () {
if (!(this instanceof HTMLSelectElement) || this.selectedOptions.length === 0) {
return;
}
sortField = this.selectedOptions[0].dataset.sortField;
sortOrder = this.selectedOptions[0].dataset.sortOrder;
localStorage.setItem('DataBank_sortField', sortField);
localStorage.setItem('DataBank_sortOrder', sortOrder);
renderAttachments();
});
const cleanupFn = await renderButtons();
await verifyAttachments();
await renderAttachments();
addDragAndDrop();
await callGenericPopup(template, POPUP_TYPE.TEXT, '', { wide: true, large: true, okButton: 'Close' });
cleanupFn();
removeDragAndDrop();
}
/**
* Gets a list of available targets for attachments.
* @returns {string[]} List of available targets
*/
function getAvailableTargets() {
const targets = Object.values(ATTACHMENT_SOURCE);
const isNotCharacter = this_chid === undefined || selected_group;
const isNotInChat = getCurrentChatId() === undefined;
if (isNotCharacter) {
targets.splice(targets.indexOf(ATTACHMENT_SOURCE.CHARACTER), 1);
}
if (isNotInChat) {
targets.splice(targets.indexOf(ATTACHMENT_SOURCE.CHAT), 1);
}
return targets;
}
/**
* Runs a known scraper on a source and saves the result as an attachment.
* @param {string} scraperId Id of the scraper
* @param {string} target Target for the attachment
* @param {function} callback Callback function
* @returns {Promise<void>} A promise that resolves when the source is scraped.
*/
async function runScraper(scraperId, target, callback) {
try {
console.log(`Running scraper ${scraperId} for ${target}`);
const files = await ScraperManager.runDataBankScraper(scraperId);
if (!Array.isArray(files)) {
console.warn('Scraping returned nothing');
return;
}
if (files.length === 0) {
console.warn('Scraping returned no files');
toastr.info('No files were scraped.', 'Data Bank');
return;
}
for (const file of files) {
await uploadFileAttachmentToServer(file, target);
}
toastr.success(`Scraped ${files.length} files from ${scraperId} to ${target}.`, 'Data Bank');
callback();
}
catch (error) {
console.error('Scraping failed', error);
toastr.error('Check browser console for details.', 'Scraping failed');
}
}
/**
* Uploads a file attachment to the server.
* @param {File} file File to upload
* @param {string} target Target for the attachment
* @returns
*/
export async function uploadFileAttachmentToServer(file, target) {
const isValid = await validateFile(file);
if (!isValid) {
return;
}
let base64Data = await getBase64Async(file);
const slug = getStringHash(file.name);
const uniqueFileName = `${Date.now()}_${slug}.txt`;
if (isConvertible(file.type)) {
try {
const converter = getConverter(file.type);
const fileText = await converter(file);
base64Data = window.btoa(unescape(encodeURIComponent(fileText)));
} catch (error) {
toastr.error(String(error), 'Could not convert file');
console.error('Could not convert file', error);
}
} else {
const fileText = await file.text();
base64Data = window.btoa(unescape(encodeURIComponent(fileText)));
}
const fileUrl = await uploadFileAttachment(uniqueFileName, base64Data);
const convertedSize = Math.round(base64Data.length * 0.75);
if (!fileUrl) {
return;
}
const attachment = {
url: fileUrl,
size: convertedSize,
name: file.name,
created: Date.now(),
};
ensureAttachmentsExist();
switch (target) {
case ATTACHMENT_SOURCE.GLOBAL:
extension_settings.attachments.push(attachment);
saveSettingsDebounced();
break;
case ATTACHMENT_SOURCE.CHAT:
chat_metadata.attachments.push(attachment);
saveMetadataDebounced();
break;
case ATTACHMENT_SOURCE.CHARACTER:
extension_settings.character_attachments[characters[this_chid]?.avatar].push(attachment);
saveSettingsDebounced();
break;
}
}
function ensureAttachmentsExist() {
if (!Array.isArray(extension_settings.disabled_attachments)) {
extension_settings.disabled_attachments = [];
}
if (!Array.isArray(extension_settings.attachments)) {
extension_settings.attachments = [];
}
if (!Array.isArray(chat_metadata.attachments)) {
chat_metadata.attachments = [];
}
if (this_chid !== undefined && characters[this_chid]) {
if (!extension_settings.character_attachments) {
extension_settings.character_attachments = {};
}
if (!Array.isArray(extension_settings.character_attachments[characters[this_chid].avatar])) {
extension_settings.character_attachments[characters[this_chid].avatar] = [];
}
}
}
/**
* Gets all currently available attachments. Ignores disabled attachments.
* @returns {FileAttachment[]} List of attachments
*/
export function getDataBankAttachments() {
ensureAttachmentsExist();
const globalAttachments = extension_settings.attachments ?? [];
const chatAttachments = chat_metadata.attachments ?? [];
const characterAttachments = extension_settings.character_attachments?.[characters[this_chid]?.avatar] ?? [];
return [...globalAttachments, ...chatAttachments, ...characterAttachments].filter(x => !isAttachmentDisabled(x));
}
/**
* Gets all attachments for a specific source. Includes disabled attachments.
* @param {string} source Attachment source
* @returns {FileAttachment[]} List of attachments
*/
export function getDataBankAttachmentsForSource(source) {
ensureAttachmentsExist();
switch (source) {
case ATTACHMENT_SOURCE.GLOBAL:
return extension_settings.attachments ?? [];
case ATTACHMENT_SOURCE.CHAT:
return chat_metadata.attachments ?? [];
case ATTACHMENT_SOURCE.CHARACTER:
return extension_settings.character_attachments?.[characters[this_chid]?.avatar] ?? [];
}
return [];
}
/**
* Verifies all attachments in the Data Bank.
* @returns {Promise<void>} A promise that resolves when attachments are verified.
*/
async function verifyAttachments() {
for (const source of Object.values(ATTACHMENT_SOURCE)) {
await verifyAttachmentsForSource(source);
}
}
/**
* Verifies all attachments for a specific source.
* @param {string} source Attachment source
* @returns {Promise<void>} A promise that resolves when attachments are verified.
*/
async function verifyAttachmentsForSource(source) {
try {
const attachments = getDataBankAttachmentsForSource(source);
const urls = attachments.map(a => a.url);
const response = await fetch('/api/files/verify', {
method: 'POST',
headers: getRequestHeaders(),
body: JSON.stringify({ urls }),
});
if (!response.ok) {
const error = await response.text();
throw new Error(error);
}
const verifiedUrls = await response.json();
for (const attachment of attachments) {
if (verifiedUrls[attachment.url] === false) {
console.log('Deleting orphaned attachment', attachment);
await deleteAttachment(attachment, source, () => { }, false);
}
}
} catch (error) {
console.error('Attachment verification failed', error);
}
}
/**
* Registers a file converter function.
* @param {string} mimeType MIME type
* @param {ConverterFunction} converter Function to convert file
* @returns {void}
*/
export function registerFileConverter(mimeType, converter) {
if (typeof mimeType !== 'string' || typeof converter !== 'function') {
console.error('Invalid converter registration');
return;
}
if (Object.keys(converters).includes(mimeType)) {
console.error('Converter already registered');
return;
}
converters[mimeType] = converter;
}
jQuery(function () {
@@ -507,6 +1307,11 @@ jQuery(function () {
$('#file_form_input').trigger('click');
});
// Do not change. #manageAttachments is added by extension.
$(document).on('click', '#manageAttachments', function () {
openAttachmentManager();
});
$(document).on('click', '.mes_embed', function () {
const messageBlock = $(this).closest('.mes');
const messageId = Number(messageBlock.attr('mesid'));
@@ -529,6 +1334,7 @@ jQuery(function () {
const textarea = document.createElement('textarea');
textarea.value = String(bro.val());
textarea.classList.add('height100p', 'wide100p');
bro.hasClass('monospace') && textarea.classList.add('monospace');
textarea.addEventListener('input', function () {
bro.val(textarea.value).trigger('input');
});
@@ -598,6 +1404,9 @@ jQuery(function () {
reloadCurrentChat();
});
$(document).on('click', '.mes_img_enlarge', enlargeMessageImage);
$(document).on('click', '.mes_img_delete', deleteMessageImage);
$('#file_form_input').on('change', onFileAttach);
$('#file_form').on('reset', function () {
$('#file_form').addClass('displayNone');

View File

@@ -0,0 +1,14 @@
/**
* Common debounce timeout values to use with `debounce` calls.
* @enum {number}
*/
export const debounce_timeout = {
/** [100 ms] For ultra-fast responses, typically for keypresses or executions that might happen multiple times in a loop or recursion. */
quick: 100,
/** [300 ms] Default time for general use, good balance between responsiveness and performance. */
standard: 300,
/** [1.000 ms] For situations where the function triggers more intensive tasks. */
relaxed: 1000,
/** [5 sec] For delayed tasks, like auto-saving or completing batch operations that need a significant pause. */
extended: 5000,
};

View File

@@ -145,6 +145,18 @@ const extension_settings = {
variables: {
global: {},
},
/**
* @type {import('./chats.js').FileAttachment[]}
*/
attachments: [],
/**
* @type {Record<string, import('./chats.js').FileAttachment[]>}
*/
character_attachments: {},
/**
* @type {string[]}
*/
disabled_attachments: [],
};
let modules = [];

View File

@@ -0,0 +1,9 @@
<div id="attachFile" class="list-group-item flex-container flexGap5" title="Attach a file or image to a current chat.">
<div class="fa-fw fa-solid fa-paperclip extensionsMenuExtensionButton"></div>
<span data-i18n="Attach a File">Attach a File</span>
</div>
<div id="manageAttachments" class="list-group-item flex-container flexGap5" title="View global, character, or data files.">
<div class="fa-fw fa-solid fa-book-open-reader extensionsMenuExtensionButton"></div>
<span data-i18n="Open Data Bank">Open Data Bank</span>
</div>

View File

@@ -0,0 +1,51 @@
<div>
<div class="flex-container flexFlowColumn">
<label for="fandomScrapeInput" data-i18n="Enter a URL or the ID of a Fandom wiki page to scrape:">
Enter a URL or the ID of a Fandom wiki page to scrape:
</label>
<small>
<span data-i18n=Examples:">Examples:</span>
<code>https://harrypotter.fandom.com/</code>
<span data-i18n="or">or</span>
<code>harrypotter</code>
</small>
<input type="text" id="fandomScrapeInput" name="fandomScrapeInput" class="text_pole" placeholder="">
</div>
<div class="flex-container flexFlowColumn">
<label for="fandomScrapeFilter">
Optional regex to pick the content by its title:
</label>
<small>
<span data-i18n="Example:">Example:</span>
<code>/(Azkaban|Weasley)/gi</code>
</small>
<input type="text" id="fandomScrapeFilter" name="fandomScrapeFilter" class="text_pole" placeholder="">
</div>
<div class="flex-container flexFlowColumn">
<label>
Output format:
</label>
<label class="checkbox_label justifyLeft" for="fandomScrapeOutputSingle">
<input id="fandomScrapeOutputSingle" type="radio" name="fandomScrapeOutput" value="single" checked>
<div class="flex-container flexFlowColumn flexNoGap">
<span data-i18n="Single file">
Single file
</span>
<small data-i18n="All articles will be concatenated into a single file.">
All articles will be concatenated into a single file.
</small>
</div>
</label>
<label class="checkbox_label justifyLeft" for="fandomScrapeOutputMulti">
<input id="fandomScrapeOutputMulti" type="radio" name="fandomScrapeOutput" value="multi">
<div class="flex-container flexFlowColumn flexNoGap">
<span data-i18n="File per article">
File per article
</span>
<small data-i18n="Each article will be saved as a separate file.">
Not recommended. Each article will be saved as a separate file.
</small>
</div>
</label>
</div>
</div>

Some files were not shown because too many files have changed in this diff Show More