Compare commits

..

405 Commits

Author SHA1 Message Date
Cohee
6eb74cb715 Merge pull request #2446 from SillyTavern/staging
Staging
2024-06-30 18:22:35 +03:00
Cohee
4d67d7d748 Bump package version 2024-06-30 14:16:08 +03:00
Cohee
cee304fe29 Merge pull request #2445 from Risenafis/fix-initvoicemap
Prevent concurrent execution of initVoiceMap
2024-06-30 14:08:30 +03:00
Cohee
2a3e71bf6e Force reinitialize when chat changed 2024-06-30 14:06:31 +03:00
Risenafis
d3a7466929 remove line break 2024-06-30 16:22:05 +09:00
Risenafis
26d506874f prevent parallel execution of initVoiceMap 2024-06-30 13:58:51 +09:00
Cohee
4828bd95f3 Animate recaption image 2024-06-30 01:19:43 +03:00
Cohee
336674b724 Ensure unique files name for pasted images 2024-06-30 01:00:43 +03:00
Cohee
f181d1a847 Add button to re-caption message image 2024-06-30 00:31:54 +03:00
Cohee
7149f46c9a Add automatic image captioning mode 2024-06-30 00:06:17 +03:00
Cohee
2670709237 Fix caption template references 2024-06-29 23:22:29 +03:00
Cohee
7fe329b5cf Allow paste file and images into chat input form 2024-06-29 23:14:55 +03:00
Cohee
175a91f979 Merge pull request #2436 from SillyTavern/translation-improvements
Translation improvements
2024-06-29 22:38:27 +03:00
Cohee
fcc00e0b26 Merge branch 'staging' into translation-improvements 2024-06-29 14:36:12 +03:00
Cohee
2b50ab398b Add jsconfig exclusions 2024-06-29 14:35:16 +03:00
Cohee
bf28ae07b3 Merge pull request #2438 from SillyTavern/commands-send-at-supports-depth
Various commands with 'at' support depth values
2024-06-29 14:33:22 +03:00
Cohee
76b822c627 Merge pull request #2437 from SillyTavern/echo-command-args
Add optional args to /echo command
2024-06-29 14:26:10 +03:00
Risenafis
bb39e852b8 TTS: Add support for VITS/W2V2-VITS/Bert-VITS2 (#2439)
* add VITSTtsProvider

* add options

* fix params, drop reference_audio_path

* post with body

* improve preview lang

* add space in label
2024-06-29 14:15:37 +03:00
Wolfsblvt
f7d3a1c942 Various commands with 'at' support depth values 2024-06-29 02:52:30 +02:00
Wolfsblvt
75099d3a22 Fix oversight in forceEnum slash commands 2024-06-29 02:42:01 +02:00
Wolfsblvt
5c3b799d65 Fix naming inconsistencies 2024-06-29 02:24:20 +02:00
Wolfsblvt
1d6f038601 Add optional args to /echo command 2024-06-29 02:16:36 +02:00
Wolfsblvt
cba2b54531 Refactor main slassh-commands into init func 2024-06-29 00:25:10 +02:00
Wolfsblvt
d994528548 Extend i18n with translate and template literal 2024-06-28 23:53:25 +02:00
Cohee
003066a036 Add vLLM as multimodal captioning source 2024-06-29 00:33:12 +03:00
Wolfsblvt
be08e62fc1 Observer to find new elements with i18n attribute 2024-06-28 23:01:54 +02:00
Cohee
a287ccfca2 Merge pull request #2435 from SillyTavern/remove-var-from-at-arg
Remove variable usage of 'at' arg in send commands
2024-06-28 22:54:40 +03:00
Wolfsblvt
38792d071b Remove variable usage of 'at' arg in send commands 2024-06-28 20:48:55 +02:00
Cohee
c34150fef0 Limit height to match large popup 2024-06-28 21:32:39 +03:00
Cohee
e98f38b6da Final(?) iOS cope 2024-06-28 21:27:48 +03:00
Cohee
1c69ba1ae3 Change mobile cope styles 2024-06-28 20:51:10 +03:00
Cohee
3803714465 Limit height of enlarged image prompt 2024-06-28 20:46:41 +03:00
Cohee
fa1d45635b Put mobile height fix under a breakpoint 2024-06-28 20:40:57 +03:00
Cohee
54c772622e Add you-know-what for featherless 2024-06-28 20:26:25 +03:00
Cohee
1ab674ba28 Merge pull request #2434 from DarokCx/release
Added featherless to the list of providers
2024-06-28 20:16:37 +03:00
Cohee
b3577024f4 Merge branch 'staging' into DarokCx/release 2024-06-28 19:46:57 +03:00
Cohee
7ea560307c Add third-party extensions links to gitignore 2024-06-28 19:45:47 +03:00
Cohee
cc9eca8427 Apply select2 to model selection 2024-06-28 19:13:46 +03:00
Cohee
4e33253a91 Code clean-up 2024-06-28 19:12:56 +03:00
Cohee
b62cbdeebd Merge branch 'staging' into DarokCx/release 2024-06-28 19:09:12 +03:00
Cohee
0c129f6dbe Rename PULL_REQUEST_TEMPLATE.md to pull_request_template.md 2024-06-28 18:31:16 +03:00
Cohee
c69c5e07e3 Update PULL_REQUEST_TEMPLATE.md 2024-06-28 18:25:07 +03:00
Cohee
62a14fb74b Create PULL_REQUEST_TEMPLATE.md 2024-06-28 18:22:29 +03:00
Cohee
bbb1a6e578 Add huggingface inference as text completion source 2024-06-28 18:17:27 +03:00
Cohee
6b204ada9f Fix no-blur not being respected in popups 2024-06-28 17:01:05 +03:00
Cohee
889a552629 Fix popup height on iPhone 2024-06-28 16:58:37 +03:00
Cohee
89e5562494 Explicitly set min and max dimensions for popup dialog 2024-06-28 13:10:15 +00:00
DarokCx
29ff0876a7 Added additional headers 2024-06-28 08:20:15 -04:00
Cohee
8136293593 Merge pull request #2431 from SillyTavern/remove-slash-command-var-usages
Remove slash command var usages
2024-06-28 13:50:06 +03:00
Cohee
902dfbcdcc Add theme contest winners, pt.1 2024-06-28 10:30:32 +00:00
Cohee
e713b32bdc Fix empty toast suppression 2024-06-28 10:22:26 +00:00
Cohee
d3be6caaa1 Create CONTRIBUTING.md 2024-06-28 12:22:35 +03:00
Cohee
46830a27d0 Merge pull request #2430 from SillyTavern/toast-save-errors
Improve logs (+add toasts) on save calls
2024-06-28 12:03:42 +03:00
Cohee
190bed8025 Fix theme and movingUI toasts 2024-06-28 09:01:28 +00:00
Cohee
2293828f8e Ditto, for chat completion 2024-06-28 08:14:38 +00:00
Cohee
985c2dd031 Don't indicate success in saving presets 2024-06-28 08:09:22 +00:00
Cohee
043eead149 Don't show empty toasts in slash command executor 2024-06-28 08:06:33 +00:00
Wolfsblvt
6653757c5c Remove /imagine's variable usage in 'negative' 2024-06-28 04:06:22 +02:00
Wolfsblvt
b29d32d518 Remove /regex's variable usage in 'name' argument 2024-06-28 04:04:58 +02:00
Wolfsblvt
b97dceeb7a Remove /flushinject's variable usage in unnamed 2024-06-28 04:00:32 +02:00
Wolfsblvt
11ca0dd22e Remove /send's variable usage in 'name' argument 2024-06-28 03:58:04 +02:00
Wolfsblvt
9666b9920a Remove /summarize's variable usage in 'prompt' 2024-06-28 03:55:03 +02:00
Wolfsblvt
7cf5a4cb2e Remove /inject's variable usage in 'id' argument 2024-06-28 03:53:23 +02:00
Wolfsblvt
54e111886b Improve logs (+add toasts) on save calls
- Fixes #2429
2024-06-28 03:28:16 +02:00
Wolfsblvt
b8295ac8f5 Unregister function for macro registration 2024-06-28 03:01:33 +02:00
Cohee
cf56bfb6a9 Add 01.AI as a chat completion source 2024-06-28 00:51:09 +03:00
Cohee
537cfbc027 Remove commented theme toggles 2024-06-27 23:40:47 +03:00
Cohee
599c55938b Merge pull request #2427 from SillyTavern/more-popups
More popups
2024-06-27 23:20:10 +03:00
DarokCx
8608bc92ae no authorization 2024-06-27 10:02:28 -04:00
DarokCx
bd5592de7b Added featherless, connect button not working 2024-06-27 09:06:11 -04:00
Cohee
79b8dc98eb Fix performance of WI editor when adding a new entry 2024-06-27 10:04:49 +00:00
Cohee
4e083ebd4f Allow vertical scrolling in settings snapshots 2024-06-27 08:45:09 +00:00
Cohee
5075534b2e Fix vertical scrolling in data bank 2024-06-27 08:42:23 +00:00
Wolfsblvt
b8ae54fb2c Add i18n to popup controls 2024-06-27 03:01:07 +02:00
Wolfsblvt
d5016ad672 Update tag import popup to new input 2024-06-27 02:52:34 +02:00
Wolfsblvt
d737c0f285 Update Regenerate popup to use input control 2024-06-27 02:40:47 +02:00
Wolfsblvt
d084f579c5 Refactor Popup with private and readonly modifiers 2024-06-27 02:39:59 +02:00
Wolfsblvt
124cbfdfa4 Update Popup to support custom input checkboxes 2024-06-27 02:28:25 +02:00
Wolfsblvt
7bf793d2be Update /newchat command skip popup 2024-06-27 01:39:05 +02:00
Cohee
f73986d23f Remove extra linebreak 2024-06-27 02:25:08 +03:00
Wolfsblvt
1c6c9efba1 Refactor convert to group chat to new popup 2024-06-27 01:01:43 +02:00
Wolfsblvt
efb9fbcc7e Refactor new chat to new popup 2024-06-27 00:45:26 +02:00
Wolfsblvt
360c2985f5 Switch char deletion to new popup
- New popup
- Move char CHARACTER_DELETED to after char deleting, and inside the correct function
2024-06-27 00:29:25 +02:00
Wolfsblvt
d64d16bdf2 Fix popup onClose executing after resolver 2024-06-27 00:27:55 +02:00
Cohee
52a803b6ab Merge pull request #2426 from SillyTavern/dataroot-uploads
Move uploads to data root
2024-06-27 00:54:22 +03:00
Cohee
3b7540da05 Merge pull request #2425 from SillyTavern/wi-delay
Add WI entry delay
2024-06-27 00:36:23 +03:00
Cohee
b80b2d9a74 Fix imported chats not deleting itself after upload 2024-06-26 23:25:00 +03:00
Cohee
5b002c6e46 #2422 Move uploads under the data root 2024-06-26 23:22:42 +03:00
Cohee
886b6fee64 Add WI entry delay 2024-06-26 22:43:30 +03:00
Cohee
54fb7a9030 Add 'online_status_changed' event 2024-06-26 22:11:22 +03:00
Cohee
aceca89080 Merge pull request #2421 from SillyTavern/macro-register
Add simple custom macros registration
2024-06-26 22:01:24 +03:00
Cohee
719539c2ab Improve types and sanitation of macro values 2024-06-26 21:58:57 +03:00
Cohee
ef0772bc9f Merge branch 'staging' into macro-register 2024-06-26 21:54:00 +03:00
Cohee
8b1492a2d9 Mono font family for kbd 2024-06-26 20:39:39 +03:00
Cohee
b1fa4d3038 Remove font-family we don't vendor 2024-06-26 20:37:29 +03:00
Cohee
d9536ae3a8 Add tri-state argument for /lock command 2024-06-26 20:18:05 +03:00
Cohee
112e26a0ff Model icon for slash command messages 2024-06-26 19:48:45 +03:00
Cohee
584d0e6222 Only add missing modules string if there are any modules to report 2024-06-26 12:49:23 +00:00
Wolfsblvt
4e7232f13e Move group chat popups to new popup 2024-06-26 05:56:15 +02:00
Wolfsblvt
ec58d9272a Move ctrl+enter regenerate to new popup 2024-06-26 05:46:34 +02:00
Wolfsblvt
ff5f89bd5e Move overwrite data confirm to new popup 2024-06-26 05:35:41 +02:00
Wolfsblvt
cd9013cf73 Update some WI confirm/input popups to new popup 2024-06-26 05:29:08 +02:00
Wolfsblvt
717c524b01 Update copy message to new popup 2024-06-26 05:01:58 +02:00
Wolfsblvt
c55452d0ea Update prompt itemization to new popup 2024-06-26 04:49:07 +02:00
Wolfsblvt
c8411b6dfb Update delete message to new popup 2024-06-26 03:36:06 +02:00
Wolfsblvt
0c402e2a5f Setup swipe data on /sendas 2024-06-26 03:13:05 +02:00
Wolfsblvt
9113fae4fe Fix /addswipe error if swipe_info not initialized 2024-06-26 02:46:53 +02:00
Wolfsblvt
071a77fe1a Fix deleting swipe not clean swipe_info 2024-06-26 02:14:26 +02:00
Cohee
8034564c3e Update TTS voice preview to new popup 2024-06-26 00:41:37 +03:00
Cohee
2ef6004bd5 Update translation extension to new popup 2024-06-26 00:40:13 +03:00
Cohee
d188795591 Allow vertical scrolling in new popups 2024-06-26 00:35:21 +03:00
Cohee
3e1b54c6f0 Update token counter to new popup 2024-06-26 00:26:31 +03:00
Cohee
c3461307a0 Update debug menu, theme and MUI naming popups 2024-06-26 00:24:21 +03:00
Cohee
1188cb46b8 Update theme import warning to new popup 2024-06-26 00:18:44 +03:00
Cohee
0298849953 Update theme delete confirm to new popup 2024-06-26 00:15:16 +03:00
Cohee
a030237641 Evaluate macro functions for every instance 2024-06-25 22:44:00 +03:00
Cohee
24ae2b6fa6 Add sanitation of macro values 2024-06-25 22:15:40 +03:00
Cohee
01d38f9218 Additional validation of custom macro keys 2024-06-25 22:02:05 +03:00
Cohee
8dab4ecb06 Add simple custom macros registration 2024-06-25 21:53:10 +03:00
Cohee
083ea43971 Update dupe character to use new popup 2024-06-25 21:34:08 +03:00
Cohee
d0f59edf09 Update forbid media and field extend to new popup 2024-06-25 21:24:03 +03:00
steve green
2687618840 Update zh-cn.json (#2411)
* update zh-cn.json

* fix

* another fix
2024-06-25 17:51:47 +03:00
Cohee
20a23c5e31 Merge pull request #2419 from Risenafis/fix-sanitize
Fix non-ASCII name character voice setting
2024-06-25 16:50:10 +03:00
Risenafis
0276a2ef71 fix sanitizeId 2024-06-25 22:23:55 +09:00
Cohee
0f00adca0c Merge pull request #2418 from Risenafis/fix-sbvits-style
Fix Style-Bert-VITS2 style
2024-06-25 15:07:47 +03:00
Risenafis
bbd4d7e2fd rejoin style 2024-06-25 20:05:41 +09:00
Cohee
6b716980be Update extension manager to use new popup 2024-06-25 11:54:59 +03:00
Cohee
0e0bd0d3d9 Update char avatar crop. Remove old cropper 2024-06-25 02:25:38 +03:00
Cohee
650755198d Update persona image upload to new cropper 2024-06-25 02:18:10 +03:00
Cohee
45ae8d1060 Update group custom avatar to new cropper 2024-06-25 02:10:11 +03:00
Cohee
e0000bade6 Port image cropper to new popup 2024-06-25 02:05:35 +03:00
Cohee
974b98ed8e Remove green border from selected tags 2024-06-25 01:03:09 +03:00
Cohee
3b1bd97845 Somewhat restore old wand order 2024-06-24 23:51:18 +03:00
Cohee
990130d7c2 Clean-up wand menu styles 2024-06-24 23:32:24 +03:00
Cohee
62a1cb1dce Move built-in extensions to fixed wand containers 2024-06-24 23:17:58 +03:00
Cohee
444705a5f8 RVC exists too.. 2024-06-24 22:41:19 +03:00
Wolfsblvt
675e7b1de3 Fix stscript autocomplete theme setting 2024-06-24 21:35:09 +02:00
Cohee
1efc26759f Forgot chromadb still exists... 2024-06-24 22:30:14 +03:00
Cohee
d2b2856630 Move TTS settings to HTML template 2024-06-24 22:19:21 +03:00
Cohee
c8b9b62d8a Update built-in extensions to use fixed order in extensions menu 2024-06-24 22:15:08 +03:00
Cohee
508b685fdc Add css, less, html to editorconfig 2024-06-24 21:49:15 +03:00
Cohee
cf9a5383a9 Fix QR .less format 2024-06-24 21:43:13 +03:00
Cohee
7a27c29695 #2416 Recompile popup styles 2024-06-24 21:37:21 +03:00
Cohee
db8fec7757 Add mrcrowl.easy-less to recommended vscode extensions 2024-06-24 21:34:05 +03:00
Cohee
41ab90bb8e Support more parameters for Infermatic 2024-06-24 19:16:20 +03:00
Wolfsblvt
b188c176fd Don't show tag import if no tags to import 2024-06-24 17:17:42 +02:00
Cohee
4b58a822db Fix off-by-one in timed effects. Add protected status 2024-06-24 11:51:04 +00:00
Wolfsblvt
e7ab43527a Fix /echo command falsely stripping HTML-like text 2024-06-24 03:09:46 +02:00
Cohee
a3dbcf3c2a Fix context and response size not being passed to Ollama 2024-06-24 03:48:34 +03:00
Cohee
b89afe6d13 Ignore advancement requirement for immediately set cooldown 2024-06-24 03:13:27 +03:00
Cohee
55483e76e0 Merge pull request #2408 from SillyTavern/timed-wi
Timed Effects for World Info
2024-06-24 02:37:57 +03:00
Cohee
8b9afff30d Generalize onEnded callbacks 2024-06-24 02:33:51 +03:00
Cohee
893f4f3ed6 Use raw metadata for set effect command 2024-06-24 01:28:37 +03:00
Wolfsblvt
9059621dab Fix wider popup overflowing on small screens
-Fixes #2414
2024-06-24 00:28:02 +02:00
Cohee
14879af678 Add format variable to get timed effect command 2024-06-24 01:20:39 +03:00
Cohee
80496db482 Remove resolveVariable calls 2024-06-24 01:08:24 +03:00
Cohee
3b03561d27 Merge branch 'staging' into timed-wi 2024-06-24 01:07:44 +03:00
Cohee
7e3e75875d Remove undocumented calls to resolveVariable in WI slash commands 2024-06-24 01:07:24 +03:00
Cohee
e0a404e099 Fix popup input going off on click 2024-06-23 23:09:22 +03:00
Cohee
66210e9c0f Add command for checking WI state 2024-06-23 21:35:31 +03:00
Cohee
8b5224e274 Add slash command for setting sticky/cooldown. Normalize naming: timed event => timed effect 2024-06-23 21:18:18 +03:00
Cohee
5db2254548 Generalize effect methods 2024-06-23 20:34:07 +03:00
Cohee
d1dd3a5433 Merge branch 'staging' into timed-wi 2024-06-23 20:24:53 +03:00
Cohee
59e1f9cca1 Accept JSON-serialized string arrays for /setentryfield. Return JSON lists in /getentryfield 2024-06-23 19:43:56 +03:00
Cohee
7c57132710 Don't auto-add boolean provider if there are more than one type in the list 2024-06-23 19:35:14 +03:00
Cohee
89a2e266a0 Merge pull request #2412 from harrisonvanderbyl/modelsearch
add search functionality for models
2024-06-23 19:19:46 +03:00
Cohee
de7a5085b1 Partial revert of class/id distinction 2024-06-23 19:18:40 +03:00
Cohee
f2d64a7d08 Merge branch 'staging' into modelsearch 2024-06-23 19:05:00 +03:00
Cohee
3dcb4dee59 Pre-cache related WI entries on chat load 2024-06-23 18:50:40 +03:00
Cohee
39362fd566 Fix some type errors 2024-06-23 18:41:49 +03:00
Cohee
5de80f4c6d Merge branch 'staging' into timed-wi 2024-06-23 18:31:40 +03:00
Cohee
fa9ae4c979 Specify that flushvar supports closures 2024-06-23 18:28:22 +03:00
Cohee
946994af22 /input should return empty string if canceled 2024-06-23 18:16:15 +03:00
Cohee
bd9c10c2eb /messages text consistency 2024-06-23 18:14:10 +03:00
Cohee
b105a2ef24 Add missing /trigger unnamed type, fix names for ask and delname 2024-06-23 18:10:56 +03:00
Cohee
a85ac96f82 Fix /peek typings. Clarify hints for member indices 2024-06-23 18:01:16 +03:00
Cohee
4d493ca733 Merge pull request #2403 from SillyTavern/slash-command-enums
Expand slash commands with enums/enum providers
2024-06-23 17:49:57 +03:00
Harrison
9b17f4e0c0 add search functionality for models 2024-06-24 00:47:06 +10:00
Cohee
eb8f4bebe0 Argument for API is not required 2024-06-23 17:42:00 +03:00
Cohee
f3327c06ab Update trim direction emoji 2024-06-23 17:39:57 +03:00
Cohee
6594b3c7fa Inject supports variables for ID 2024-06-23 17:21:55 +03:00
Cohee
2012bb49d2 Make /member command indices 0-based to match autocomplete 2024-06-23 16:47:07 +03:00
Cohee
e736283785 Return cache to WI 2024-06-23 16:24:33 +03:00
Cohee
38cc4f789b Fix string quotes 2024-06-23 15:57:07 +03:00
Cohee
e2593215bf Fix speak command broken 2024-06-23 15:44:53 +03:00
Cohee
278b526898 Add icon for voice command 2024-06-23 15:43:57 +03:00
Cohee
5a50ed97be Clean-up /sd command help 2024-06-23 15:27:19 +03:00
Cohee
d0b6243f77 Add character filter to lastsprite 2024-06-23 15:23:03 +03:00
Cohee
eba0f54477 Merge branch 'staging' into slash-command-enums 2024-06-23 15:01:55 +03:00
Cohee
3a15e44d0f Merge pull request #2410 from SillyTavern/inline-image-enlarge-rework
Inline image enlarge rework
2024-06-23 14:59:24 +03:00
Cohee
03cfbca7cf Distraction-free image zooming 2024-06-23 14:58:08 +03:00
Cohee
a161ebfcaf Up visibility of close button 2024-06-23 14:53:01 +03:00
Cohee
58a85fa0c8 Remove focus outline from transparent popups 2024-06-23 14:11:00 +03:00
Wolfsblvt
7642b66a0e Improve enlarge inline image
- Make enlarge inline image popup zoomable
- Add optional popup class for transparent popups
2024-06-23 12:26:52 +02:00
Wolfsblvt
48621f1d50 Fix scaling of enlarged popup image 2024-06-23 02:43:37 +02:00
Wolfsblvt
42766a715d Popup type "DISPLAY" & image enlarge changes
- New popup type "DISPLAY", for showing content with an X in the corner, without buttons
- Rework popup result controls to automatically support click (or other) events to close complete the popup
- Fix inline image icons/actions being keyboard interactable
- Switch inline image enlarge popup to new DISPLAY type
2024-06-23 02:32:06 +02:00
Cohee
3e27f0213a Fix cooldown debug log 2024-06-23 01:58:18 +03:00
Cohee
9ec8aa3bf9 Fix JSDocs 2024-06-23 01:53:45 +03:00
Cohee
7875a65b44 Kinda misleading comment 2024-06-23 01:52:10 +03:00
Cohee
461b73facf Adjust min values.
For easier disabling
2024-06-23 01:50:17 +03:00
Cohee
4b4ee7409b Merge branch 'staging' into timed-wi 2024-06-23 01:49:49 +03:00
Cohee
323f34f5d4 Fix QR breaking when no sets 2024-06-23 01:34:10 +03:00
Cohee
de1910268a Add missing macro reference 2024-06-23 01:26:25 +03:00
Cohee
a39a1a7cec Fix odd-named preset selection via command 2024-06-22 17:44:08 +03:00
Cohee
d64647280a Fix method deprecation warning 2024-06-22 17:41:40 +03:00
Cohee
8564d6faa8 Debug function to purge all vectors 2024-06-22 17:41:02 +03:00
Cohee
b8830e34d3 Add ollama download shortcut to vector storage 2024-06-22 16:38:00 +03:00
Cohee
b448568aa3 More ollama multimodal models 2024-06-22 16:28:57 +03:00
Cohee
b513043e4a Prevent sticky from using probability 2024-06-22 15:04:49 +03:00
Cohee
0fe19bca47 Non-static map. Better typing. 2024-06-22 14:56:46 +03:00
Cohee
b4559d3fd8 Use WeakMap for WI cache 2024-06-22 14:43:15 +03:00
Cohee
6c9f3a868d Merge branch 'staging' into timed-wi 2024-06-22 14:39:38 +03:00
Cohee
0f92c90b71 Merge pull request #2405 from SillyTavern/tag-import-setting
Tag import setting
2024-06-22 13:04:14 +03:00
Cohee
36ecf8a717 Update UI when remembering tag import setting 2024-06-22 12:56:57 +03:00
Cohee
aa16ac446d Migrate preference for existing users 2024-06-22 12:53:03 +03:00
Cohee
a6e2692e52 Merge branch 'staging' into tag-import-setting 2024-06-22 12:48:08 +03:00
Cohee
26eb5f0926 Merge pull request #2406 from SillyTavern/gray-out-useless-folders
Gray out bogus folders if they don't drill down
2024-06-22 12:46:06 +03:00
Wolfsblvt
07da2461d0 Fix vertical scaling of images in enlarge popup 2024-06-22 10:04:14 +02:00
Wolfsblvt
c79f1e4360 Fix image enlarge popup image sizing 2024-06-22 08:52:13 +02:00
Wolfsblvt
87915a5f79 Gray out bogus folders if they don't drill down
- Implements and resolves #2404
- Common CSS class for entities in char list
- entity flag to lower opacity for entities that aren't really useful for navigation
2024-06-22 08:15:31 +02:00
Wolfsblvt
d64b265a39 Tag import popup improvements
- Save "remember" setting of tag import popup
- Add user option to change the tag import setting
- Improve tag import popup with adding drilled down bogus folders as auto-added tags
- Extract tag import popup to template
- Force-open popup no matter the setting on char dropdown button option
2024-06-22 05:03:05 +02:00
Wolfsblvt
7c2b475e46 Improve popup class with close event handlers 2024-06-22 04:54:13 +02:00
Cohee
d02fbbb42f Change type of timed events metadata 2024-06-22 03:54:54 +03:00
Cohee
37930caade Refactor timed events funcs 2024-06-22 03:15:13 +03:00
Wolfsblvt
c6c8f91c99 forceEnum:false as default & enum icon changes
- Set forceEnum to false, for now
- Switch some icons around
2024-06-22 01:04:03 +02:00
Cohee
473e11c773 New OpenRouter providers 2024-06-22 02:03:39 +03:00
Cohee
9c2de78ad3 Fix OpenRouter caption headers 2024-06-22 01:42:28 +03:00
Cohee
abb186db01 He warned me. I didn't listen. 2024-06-22 00:46:14 +03:00
Cohee
a00560d2b3 Ensure format supported before captioning 2024-06-22 00:36:29 +03:00
Cohee
791ce3da86 Add stopgaps to fill the second row 2024-06-22 00:11:08 +03:00
Cohee
6380e0a062 Add fallback source for attachments. Fix typo 2024-06-21 23:48:11 +03:00
Cohee
62bc550d3a Open enums with Alt+Space (also non-breaking) 2024-06-21 23:32:17 +03:00
Cohee
e3714e9b6a Fix search provider 2024-06-21 22:31:34 +03:00
Wolfsblvt
da6d77cffd Enum provider for /model 2024-06-21 21:24:37 +02:00
Wolfsblvt
824d0a9b63 Small fix to boolean automatic enums 2024-06-21 20:40:58 +02:00
Cohee
1ede346cbc Merge branch 'staging' into timed-wi 2024-06-21 21:18:12 +03:00
Wolfsblvt
3ab5cc1766 Merge branch 'staging' into slash-command-enums 2024-06-21 20:07:19 +02:00
Wolfsblvt
48077d200b More slash command enums (nearly done) 2024-06-21 20:04:55 +02:00
Cohee
30765550c8 Place sticky + cooldown entry on cooldown when unstuck 2024-06-21 20:50:18 +03:00
Cohee
f2cc66d414 Add console logs to search module 2024-06-21 17:27:28 +03:00
Cohee
56710fee39 Apply fix for group chats 2024-06-21 13:09:41 +00:00
Cohee
7667231137 Merge pull request #2402 from splitclover/staging
Added events for creating new chats
2024-06-21 16:07:38 +03:00
splitclover
0c69b698b9 Removed redundant emmiter 2024-06-21 14:16:59 +02:00
splitclover
feb8321147 Fix emmiter for imported cards 2024-06-21 13:53:31 +02:00
splitclover
3092c68a05 Fixed typo 2024-06-21 13:19:36 +02:00
splitclover
2a1704add0 Added event emmiters when creating new chats 2024-06-21 13:12:57 +02:00
Cohee
9c3cad2df2 Fix sticky/cooldown interaction. Add icons 2024-06-21 02:42:15 +03:00
Cohee
aa473dd749 Move new fields to the right 2024-06-21 01:06:00 +03:00
Cohee
ab7b07ba28 Add sticky and cooldown for timed WI entries 2024-06-21 00:53:00 +03:00
Cohee
e9f93ba748 Remove legacy Claude stop sequences 2024-06-20 22:53:07 +03:00
Wolfsblvt
a5baa3605f Fix closing popup sometimes being stuck via Escape 2024-06-20 21:43:13 +02:00
Wolfsblvt
f092269c01 Fix toastr alignment in popups 2024-06-20 21:02:50 +02:00
Wolfsblvt
ffc84f5118 Merge branch 'staging' into slash-command-enums 2024-06-20 20:38:55 +02:00
Wolfsblvt
461b1a9d87 Even more enum refactorings (not done yet)
- Add common enum icons
- enum def for existing enum types, with color description
2024-06-20 20:33:45 +02:00
Cohee
514ac27d00 Remove unmatched closing div 2024-06-20 20:40:37 +03:00
Cohee
2a4d11e6a6 Merge pull request #2399 from conornash/claude_sonnet_3point5
Claude Sonnet 3.5
2024-06-20 18:43:57 +03:00
Conor Nash
c684bfbf52 Claude Sonnet 3.5 2024-06-20 16:31:55 +01:00
Cohee
0ffad7f4fe Add endpoint for Searxng search 2024-06-19 23:21:40 +03:00
Cohee
8d5876c2c8 Rename endpoints for websearch 2024-06-19 22:37:51 +03:00
Cohee
75dfe87054 Fix character_message_rendered firing twice on new chat 2024-06-19 21:34:46 +03:00
Cohee
8e8b6b353a Fix deleting first message with /del 2024-06-19 21:28:07 +03:00
Cohee
10fd2e1334 #2395 Treat default example as empty 2024-06-19 21:23:25 +03:00
Cohee
41befc3587 Merge branch 'staging' of https://github.com/SillyTavern/SillyTavern into staging 2024-06-19 13:27:27 +03:00
Cohee
8812e09e8d Merge branch 'release' into staging 2024-06-19 13:27:14 +03:00
Cohee
00b44071a6 Merge branch 'release' of https://github.com/SillyTavern/SillyTavern into release 2024-06-19 13:25:42 +03:00
Cohee
9923018a49 Update ws dependency 2024-06-19 13:25:29 +03:00
Cohee
e6bd46acef Merge pull request #2392 from dllt98/migrate-janitor-endpoint
Update JanitorAI.me endpoint to JannyAI.com endpoint
2024-06-19 13:23:49 +03:00
Cohee
190400eb6b Merge pull request #2391 from Wolfsblvt/popup-quickfixes
Small fixes to /popup, /buttons and QR popup
2024-06-19 13:14:27 +03:00
Cohee
e5c8a920ee Remove debug toast(?) 2024-06-19 13:13:34 +03:00
dllt98
fc488574c6 update endpoint 2024-06-19 02:22:05 +00:00
Wolfsblvt
dca81aef3d Small fixes to /popup, /buttons and QR popup
- FIx /popup and /buttons commands to use the new popups
- Change /buttons to utilize data results
- Fix "hide while executing" option from QR editor
- Fix QR editor throwing an error on execution
2024-06-19 01:40:22 +02:00
Cohee
e7772f04a4 Don't require a primary WI to use additionals 2024-06-18 12:09:28 +03:00
Cohee
7249294ffd Fix startup on old firefox 2024-06-18 11:32:50 +03:00
Cohee
e861a406a3 Don't auto-switch to a new theme on import 2024-06-18 02:14:22 +03:00
Cohee
22d598c0f5 Fainter faint outline 2024-06-18 02:01:39 +03:00
Cohee
c3cbf33ba0 Merge pull request #2330 from Wolfsblvt/smol-tag-improvements
Smol tag improvements & Huuuuuge popup/dialog rework (heh)
2024-06-18 01:58:52 +03:00
Wolfsblvt
dadef92fdf Switch focus styles to :focus-visible
- Switched dynamic styles to :focus-visible to let the browser decide when to display
- Changed most existing :focus CSS selectors to also use :focus-visible
-Made style variables for focus outlines (main and a faint one)
- Remove focus outline from chat bar buttons
- Fix focus of chat bar highlight, moved to outer border
- Fix buttons in chat backgrounds with keyboard navigation
2024-06-18 00:14:15 +02:00
Cohee
88b6331aed Add swap SD dimensions button 2024-06-18 01:10:23 +03:00
Cohee
a9c4422c87 Redesign SD settings 2024-06-18 00:49:42 +03:00
Cohee
08a0b1e828 Merge branch 'staging' into smol-tag-improvements 2024-06-17 21:52:36 +03:00
Cohee
b09e86fb53 TTS code clean-up 2024-06-17 21:21:41 +03:00
Cohee
feb7675d2f Auto-set language on exported translate function. 2024-06-17 21:18:30 +03:00
Casey Haralson
4d25856b4f periodically calls the tts module when a paragraph has been generated (#2381)
* periodically calls the tts module when a paragraph has been generated

* adding option for periodic auto generation

* updating find end paragraph logic and config label

---------

Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
2024-06-17 21:17:31 +03:00
DreamGenX
c8eaa15f18 Add DreamGen llama 3 templates (#2389) 2024-06-17 20:54:08 +03:00
Cohee
fe5289c495 Fix TTS audio preview 2024-06-17 20:28:19 +03:00
Cohee
5843bb788f Update readme.md 2024-06-17 14:31:12 +03:00
Cohee
4607b79a83 Update readme.md 2024-06-17 14:27:50 +03:00
deffcolony
bca99a4d7f Update readme.md
+ Updated install instructions for SillyTavern Launcher
2024-06-17 13:04:06 +02:00
Cohee
1d3914324f Fix data-i18n removing options 2024-06-17 11:25:13 +03:00
Cohee
be9f34ab8a Fix data-i18n removing options 2024-06-17 11:23:59 +03:00
Wolfsblvt
7f7ecdcca8 Merge branch 'staging' into slash-command-enums 2024-06-17 07:06:26 +02:00
Wolfsblvt
66d609c35f Several million refactoring of existing slash commands with enums (really) 2024-06-17 07:04:10 +02:00
Wolfsblvt
6f7ef25369 Rework slash command enum values pt.2
- Fix jsconfig module resolution for imports in frontend scripts
- Add file with common slash command enum values
2024-06-17 03:30:52 +02:00
Wolfsblvt
fca626d246 Add style on hover over focused popup button
- Add CSS styling rule to still highlight a button on hover when it is being focused already (Otherwise you won't get any effect on hovering and clicking on the default button if a popup has just opened)
- Dynamic styles extended to ignore rules where both hover and focus is used
2024-06-16 23:33:07 +02:00
Cohee
065d453477 Use main font variable where possible 2024-06-17 00:25:08 +03:00
Wolfsblvt
316df6ed17 Remove remnant CSS classes, whoops 2024-06-16 23:20:24 +02:00
Wolfsblvt
f67ed6d22a Fix tabbing with hotswap multiple rows
- Add scroll-reset-container CSS class and handling
- Fix tabbing through hotswap favs by resetting position
2024-06-16 23:13:36 +02:00
Cohee
1a061c6ae5 Skip connect on max context unlock via preset 2024-06-16 22:16:47 +03:00
steve green
1467c4539e Update zh-cn.json (#2385)
* Update zh-cn.json

* other fixes

* typo fix
2024-06-16 22:06:34 +03:00
Cohee
60b09a431a Fix 2024-06-16 16:06:15 +03:00
Cohee
e3a46df010 Merge branch 'staging' into smol-tag-improvements 2024-06-16 16:04:37 +03:00
Cohee
5ed4bd8748 Merge pull request #2374 from Yokayo/staging
More localizable texts
2024-06-16 16:01:41 +03:00
Cohee
a9143e8ea2 ??? 2024-06-16 15:57:16 +03:00
Cohee
037ba84916 Merge branch 'staging' of https://github.com/Yokayo/SillyTavern into ruRuNew 2024-06-16 15:56:37 +03:00
Cohee
c7dc63200a Merge branch 'staging' into ruRuNew 2024-06-16 15:56:07 +03:00
Yokayo
084aa794f8 Merge branch 'staging' into staging 2024-06-16 19:52:08 +07:00
Cohee
d75b30d51a Don't auto-adjust scroll height if not in viewport 2024-06-16 14:56:08 +03:00
steve green
5e44403346 fixup translates (#2382)
* Update i18n.js

* fixup

* update i18n CI

* fix trigger?

* i18n changes

* Update zh-cn.json

* add missing keys

* Revert "i18n changes"

This reverts commit ebe0ede6e1.

* Revert "update i18n CI"

This reverts commit ac923c8bd6.

* Revert "Update i18n.js"

This reverts commit 14a845836b.

* typo fix

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Touch-Night <1762918301@qq.com>
2024-06-16 14:39:46 +03:00
Cohee
861decd5c9 Refactor prompt entry callback 2024-06-16 13:56:43 +03:00
Cohee
0fe579e782 Merge pull request #2383 from splitclover/staging
Add /setpromptentry command to manipulate individual chat completion preset entries
2024-06-16 13:42:51 +03:00
splitclover
aa513e1e3d Fix helpString 2024-06-16 12:36:52 +02:00
splitclover
7f284a3752 Merged redundant args for /setpromptentry 2024-06-16 12:16:56 +02:00
splitclover
e5d9f2937e Merge branch 'SillyTavern:staging' into staging 2024-06-16 12:03:22 +02:00
Cohee
974d6275bf Revert "#2369 Ignore invalid characters in getLastCharacterMessage" 2024-06-16 11:27:11 +03:00
splitclover
e5ba96d6aa Clarify /setpromptentry descriptions 2024-06-16 04:43:45 +02:00
splitclover
716d407753 Add /setpromptentry command to manipulate chat completion presets 2024-06-16 04:37:42 +02:00
Wolfsblvt
34e8cf476a Default enum values for STScript boolean argument 2024-06-16 03:14:43 +02:00
Wolfsblvt
51d7ba728f Tag commands enum providers 2024-06-16 02:59:37 +02:00
Cohee
bba16f5263 Custom vector chunk boundary 2024-06-16 02:16:27 +03:00
Cohee
67b7cbe920 Cut text for group prioritize 2024-06-16 01:05:01 +03:00
Cohee
4892f04a2a Add enum providers for /theme and /movingui commands 2024-06-15 22:35:38 +03:00
Cohee
5860719780 Fix return type of /translate command 2024-06-15 21:58:15 +03:00
Cohee
101c735d91 Add enum provider for /emote 2024-06-15 18:21:44 +03:00
Cohee
3ca6795cde #2369 Ignore invalid characters in getLastCharacterMessage 2024-06-15 18:13:59 +03:00
Cohee
339428a4e9 #2379 Move continue prefill to end of completion 2024-06-15 18:00:09 +03:00
Cohee
3cafc22e1d Fix textarea height adjust 2024-06-15 16:48:35 +03:00
Cohee
788a313024 Combine char. negative prompts in free mode 2024-06-15 16:24:32 +03:00
Cohee
67dc5e5252 Toggle SD message visibility by source 2024-06-15 15:57:50 +03:00
Cohee
2ea0d6466c Console log anthropic multimodal caption errors 2024-06-15 15:55:02 +03:00
Yokayo
86ad8416df Small fix 2024-06-15 18:46:01 +07:00
Yokayo
a91ba2a277 Remove outdated entries, fix a few keys 2024-06-15 18:40:50 +07:00
Cohee
1ac2241d2c Lower main text intensity in Cappuccino theme 2024-06-15 13:30:03 +03:00
Cohee
405fc1458c Use ext. macros in prompt manager 2024-06-15 13:15:52 +03:00
Cohee
b22bc47c4f Update ext.macro calls in built-in extensions 2024-06-15 01:40:16 +03:00
Cohee
bda4958cb3 Visible SD message template 2024-06-15 01:23:51 +03:00
Cohee
2aa8564522 Substitute params with addl. macro 2024-06-15 01:22:15 +03:00
Len
5cb319771d Parser followup (#2377)
* set pipe to empty string on empty closure

* fix missing parser flags and scope

* add closure serializing

* add enum provider function to slash command arguments

* add enum providers for /bg, /ask, and /go

* fix index out of bounds returning undefined

* keep whitespace as is in mixed unnamed args (string+closure)

* add _hasUnnamedArgument to named arguments dictionary

* allow /var key=x retrieval

* add enum provider to /tag-add

* fix typo (case)

* add option to make enum matching optional

* add executor to enum provider

* change /tag-add enum provider to only show tags not already assigned

* add enum provider to /tag-remove

* fix name enum provider excluding groups

* remove void from slash command callback return types

* Lint undefined and null pipes

* enable pointer events in chat autocomplete

* fix type hint

---------

Co-authored-by: LenAnderson <Anderson.Len@outlook.com>
Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
2024-06-15 00:48:41 +03:00
Cohee
cef65a17f9 Fix SD prefix combiner for free mode 2024-06-15 00:03:25 +03:00
steve green
3ede4aafbe Update zh-cn.json (#2376)
* Update zh-cn.json

* html changes

* `div`->`span`

* fixes

* fixes

* more fixes

* Revert last divs

---------

Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
2024-06-14 22:46:40 +03:00
Cohee
560119bc3e LLM extended free mode SD prompts 2024-06-14 22:15:29 +03:00
RossAscends
4c81215a60 fix theme-disrespecting text colorizing 2024-06-14 21:50:54 +09:00
Cohee
5699eb115d Merge pull request #2378 from Wolfsblvt/wi-fix-regex-tokenizer
Fix WI keys regex tokenization breaking falsely on comma
2024-06-14 12:46:46 +03:00
Wolfsblvt
64698ac073 Fix WI keys regex tokenization breaking falsely
- Change regex tokenization to check commas inside regexes via opening and closing delimiter.
- Fixes #2375
2024-06-14 01:02:59 +02:00
Wolfsblvt
bdf7fccbae Fix small things from code review
- Remove unused tailwind classes
- Added comment about timeout jsdoc
- Moved new CSS files to @import
2024-06-13 23:55:28 +02:00
Wolfsblvt
96f04a1c49 Merge branch 'staging' into smol-tag-improvements 2024-06-13 23:40:19 +02:00
Len
aa4bdec79c Fix syntax highlight editor (#2300)
* add Noto Sans Mono as default monospace font

* fix ::selection for syntax highlighted editor

* add full noto sans mono

* add explicit "overflow: auto" to textarea to stop Firefox from freaking out

* add syntax hightlight disable toggle

* fix noto sans mono path

* fix details position on scroll

* disable pointer events on autocomplete wrap

* fix for Firefox bug using relative colors

* Shorten font file names.
So that I won't have to scroll the list horizontally

---------

Co-authored-by: LenAnderson <Anderson.Len@outlook.com>
Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
2024-06-13 21:05:50 +03:00
Yokayo
10a4e54a3b Another small fix 2024-06-12 17:15:56 +07:00
Yokayo
839d79f407 Small fix 2024-06-12 16:50:08 +07:00
Yokayo
a1a9f0002c Merge branch 'staging' of https://github.com/Yokayo/SillyTavern into staging 2024-06-12 16:31:11 +07:00
Yokayo
84ee968ab4 More localizable text 2024-06-12 16:30:32 +07:00
Cohee
9c3176b29f Preserve scroll position of the prompt manager 2024-06-12 00:26:31 +03:00
Cohee
d69263923a Include emoji into trim to end sentence 2024-06-11 23:37:00 +03:00
Cohee
abed49c277 JSDoc, toast capitalization 2024-06-11 09:55:37 +03:00
Cohee
75512842d0 Merge pull request #2366 from Wolfsblvt/persona-command-changes
Update /persona slash command with arguments
2024-06-11 09:53:13 +03:00
Cohee
9a5d0e829b Merge pull request #2367 from Wolfsblvt/html-attr-for-menu-type
Add data attribute for the currently open menu_type
2024-06-11 09:50:16 +03:00
Wolfsblvt
679b3587b5 Data attribute for the currently open menu_type
- Add data attribute to the right nav panel for the currently open menu type
- JSDoc of possible menu_type values
- Refactor using the menu_type setter
- Remove legacy "settings" menu type, as that one is not part of those really
2024-06-11 02:54:06 +02:00
Wolfsblvt
860a2f6929 Fix naming 2024-06-11 02:25:01 +02:00
Wolfsblvt
bb09f5a292 Prep to fix AutoComplete display in popups 2024-06-11 02:22:46 +02:00
Wolfsblvt
9fb9253dcc Update /persona slash command with arguments 2024-06-11 01:00:13 +02:00
Cohee
ea21de89c3 Merge pull request #2365 from Risenafis/fix-sbvits-splitting
Fix Style-Bert-VITS2 auto splitting
2024-06-10 23:23:27 +03:00
Risenafis
593f9b5832 fix sbvits2 auto splitting by backup/restore 2024-06-11 01:17:35 +09:00
Risenafis
4e447a59b5 fix sbvits2 auto splitting 2024-06-11 00:21:05 +09:00
Cohee
b3e57dae85 Use crypto UUID for UI 2024-06-10 14:20:52 +03:00
Cohee
d1ed983106 Use crypto UUID if available 2024-06-10 14:18:38 +03:00
Cohee
a20b2a566d Merge pull request #2364 from Yokayo/staging
Add support for Yandex Translate API
2024-06-10 14:10:23 +03:00
Yokayo
fc03fea00a Add support for Yandex Translate API 2024-06-10 17:12:09 +07:00
Wolfsblvt
b814ba5b35 Merge branch 'staging' into smol-tag-improvements 2024-06-10 01:45:15 +02:00
Wolfsblvt
d14af1592e Refactor naming/structure of popup CSS classes
- Refactor naming/structure of popup CSS classes
- Prepare Popup utility of opening/showing dialogs
2024-06-09 22:02:51 +02:00
Cohee
3e60d9e4d8 (chore) Run ESLint 2024-06-09 22:13:20 +03:00
Cohee
c8f3a0be40 Merge pull request #2358 from Risenafis/staging-sbvits2
TTS: Add support for Style-Bert-VITS2
2024-06-09 22:11:33 +03:00
Cohee
de7f8de3e3 Fix elevenlabs v2 settings 2024-06-09 21:04:35 +03:00
Cohee
230215a211 Bulk enable/disable databank attachments 2024-06-09 19:10:18 +03:00
Cohee
bb48dfe084 Add await arg to /imp command 2024-06-09 18:36:52 +03:00
Cohee
1685f6ded0 #1069 Convert to number 2024-06-09 14:41:49 +03:00
Cohee
3dfe10815d Spelling 2024-06-09 14:26:49 +03:00
Cohee
67f2c380a3 #1069 Handle string timestamps as numbers 2024-06-09 11:15:17 +03:00
Risenafis
1ae6f05d09 change the location of the link 2024-06-09 16:14:35 +09:00
Risenafis
2c171fdcfd add project page url 2024-06-09 12:19:09 +09:00
Risenafis
305d60a28e add SBVits2TtsProvider 2024-06-09 12:03:09 +09:00
Cohee
4e822eeebb Add VLLM as vector source 2024-06-09 01:03:22 +03:00
Cohee
1dd21caa66 Adjust number of VLLM logprobs 2024-06-09 00:59:40 +03:00
Cohee
60b7164c28 Import character icon from CHARX 2024-06-08 22:37:17 +03:00
Wolfsblvt
10da7eb474 Comment interactable selectors & more small fixes 2024-06-08 21:15:37 +02:00
Wolfsblvt
d98d811cc1 Merge branch 'staging' into smol-tag-improvements 2024-06-08 21:13:11 +02:00
Wolfsblvt
d1824acee0 More small improvements on interactable styling 2024-06-08 07:13:52 +02:00
Wolfsblvt
61906d8dbe Dynamic focus styles from existing hover styles 2024-06-08 05:10:44 +02:00
Wolfsblvt
e2089b1e44 Dozens new keyboard interactables
- Made dozens of existing controls keyboard interactable
- Tweaked styling so the keyboard focus looks more pleasant
2024-06-06 02:48:06 +02:00
Wolfsblvt
55a95c910f Refactor keyboard controls to name "interactable" 2024-06-04 04:05:40 +02:00
Wolfsblvt
e9d4a982c0 Small tweaks to (focus) element styling 2024-06-03 06:19:41 +02:00
Wolfsblvt
5cc4242c6f Make ST-style buttons keyboard selectable
- Make ST-style buttons keyboard selectable
- Allow custom classes/selectors to be set as buttons too, with the same functionality (CSS selector-based)
- Observer to automatically add functionality to any button added to the DOM
- Allow buttons to not be selectable via 'disabled' or 'not_focusable'
2024-06-03 02:52:54 +02:00
Wolfsblvt
9318f94f08 Merge branch 'staging' into smol-tag-improvements 2024-06-02 21:07:46 +02:00
Wolfsblvt
89d1bc8341 Drag&Drop handler utility and animation 2024-06-02 05:54:41 +02:00
Wolfsblvt
6ca71c3e2c Popup styling changes for smaller content & small fixes 2024-06-02 00:09:25 +02:00
Wolfsblvt
c939c544e2 Rename popup css classes 2024-05-31 21:59:26 +02:00
Wolfsblvt
d3327f7829 Create popup and animation css files 2024-05-31 03:30:41 +02:00
Wolfsblvt
1c6671df31 popup fix scrollbar style, align toastr 2024-05-30 21:03:52 +02:00
Wolfsblvt
6c3118549f Make generic popups be modal dialogs
- Switch generic popups to actual <dialog> elements
- Move toastr settings from html to JS
- Add style variable for animation duration (to re-use in CSS)
- Remember focus of popup on stacking pop-up close to switch back to the element you started out in
- Fix keybinds of popups to only act on actual result-triggering controls
- Fix toastr appearing behind popups by dynamically moving the container inside the currently open dialog
- Improve autofocus on popup open
- Make cleaner and prettier popup animations, and tie them to the animation speed
-
2024-05-30 05:11:23 +02:00
Wolfsblvt
311fb261a4 Allow re-linking tag colors to theme
- Add button to link tag color back to theme color, but explicitly setting it to empty again
- Debounce redrawing of tag color for performance
2024-05-27 05:02:00 +02:00
Wolfsblvt
24224dc0b1 Fix and improve more tag popups
- Rework tag color pickers to... actually work without hacks
- Color picker default to main text color and tag default background. If default color is chosen, sets "empty" in tag, for possible style changes
- Fix tabbing on tag name in tag view list being broken
- Unique names on new tag click
- Several fixes on tags popups
- Animation utility functions (for popup, heh)
- Utility function to get free (unique) name
2024-05-27 03:35:03 +02:00
Wolfsblvt
35e21c3568 WIP: Rework import tags popup for more options
- Rework "import tags" dialog, providing options which tags to import, and rendering the tags there, for manual management
- Refactor tag list function to allow custom remove actions
- Refactor functions to allow adding of multiple tags at once
2024-05-26 20:29:50 +02:00
Wolfsblvt
4f2543f7ae Fix popup custom buttons 2024-05-25 01:02:13 +02:00
Wolfsblvt
d9582062d2 Expand popup functionality
- Add "custom buttons" functionality, each with their own popup result
- Handle 'Enter' by defining a default action
- Using default action to style the default button to make the default action visible
- Allow override of ok/cancel button on any popup type to display those
- Allow multiple popups to overlay each other
- Small styling changes for bottom spacing on non-input popups
2024-05-25 00:44:09 +02:00
Wolfsblvt
26572458b6 Do not allow same-ish tag names / allow same-ish tag search 2024-05-23 03:34:35 +02:00
Wolfsblvt
3a5dfadac5 Fix group tag list not updating 2024-05-23 02:45:23 +02:00
Wolfsblvt
33cec69df9 Add option to merge into other tag on delete 2024-05-23 01:55:43 +02:00
189 changed files with 13300 additions and 5721 deletions

View File

@@ -8,3 +8,5 @@ Start.bat
cloudflared.exe
access.log
/data
/cache
.DS_Store

View File

@@ -5,7 +5,7 @@ end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.{js, conf, json}]
[*.{js, conf, json, css, less, html}]
charset = utf-8
indent_style = space
indent_size = 4

3
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,3 @@
## Checklist:
- [ ] I have read the [Contributing guidelines](https://github.com/SillyTavern/SillyTavern/blob/release/CONTRIBUTING.md).

60
.github/readme.md vendored
View File

@@ -144,12 +144,14 @@ A full list of included extensions and tutorials on how to use them can be found
8. The server will then start, and SillyTavern will pop up in your browser.
## Installing via SillyTavern Launcher
1. Install [Git for Windows](https://gitforwindows.org/)
2. Open Windows Explorer (`Win+E`) and make or choose a folder where you wanna install the launcher to
3. Open a Command Prompt inside that folder by clicking in the 'Address Bar' at the top, typing `cmd`, and pressing Enter.
4. When you see a black box, insert the following command: `git clone https://github.com/SillyTavern/SillyTavern-Launcher.git`
5. Double-click on `installer.bat` and choose what you wanna install
6. After installation double-click on `launcher.bat`
1. On your keyboard: press **`WINDOWS + R`** to open Run dialog box. Then, run the following command to install git:
```shell
cmd /c winget install -e --id Git.Git
```
2. On your keyboard: press **`WINDOWS + E`** to open File Explorer, then navigate to the folder where you want to install the launcher. Once in the desired folder, type `cmd` into the address bar and press enter. Then, run the following command:
```shell
git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher && start installer.bat
```
## Installing via GitHub Desktop
(This allows git usage **only** in GitHub Desktop, if you want to use `git` on the command line too, you also need to install [Git for Windows](https://gitforwindows.org/))
@@ -183,18 +185,40 @@ For MacOS / Linux all of these will be done in a Terminal.
### For Linux users
1. Open your favorite terminal and install git
2. Download Sillytavern Launcher with: `git clone https://github.com/SillyTavern/SillyTavern-Launcher.git`
3. Navigate to the SillyTavern-Launcher with: `cd SillyTavern-Launcher`
4. Start the install launcher with: `chmod +x install.sh && ./install.sh` and choose what you wanna install
5. After installation start the launcher with: `chmod +x launcher.sh && ./launcher.sh`
2. Git clone the Sillytavern-Launcher with:
```shell
git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher
```
3. Start the installer.sh with:
```shell
chmod +x install.sh && ./install.sh
```
4. After installation start the launcher.sh with:
```shell
chmod +x launcher.sh && ./launcher.sh
```
### For Mac users
1. Open a terminal and install brew with: `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"`
2. Then install git with: `brew install git`
3. Download Sillytavern Launcher with: `git clone https://github.com/SillyTavern/SillyTavern-Launcher.git`
4. Navigate to the SillyTavern-Launcher with: `cd SillyTavern-Launcher`
5. Start the install launcher with: `chmod +x install.sh && ./install.sh` and choose what you wanna install
6. After installation start the launcher with: `chmod +x launcher.sh && ./launcher.sh`
1. Open a terminal and install brew with:
```shell
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
```
2. Install git with:
```shell
brew install git
```
3. Git clone the Sillytavern-Launcher with:
```shell
git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher
```
4. Start the installer.sh with:
```shell
chmod +x install.sh && ./install.sh
```
5. After installation start the launcher.sh with:
```shell
chmod +x launcher.sh && ./launcher.sh
```
## 🐋 Installing via Docker
@@ -244,7 +268,7 @@ You will need two mandatory directory mappings and a port mapping to allow Silly
## API keys management
SillyTavern saves your API keys to a `secrets.json` file in the server directory.
SillyTavern saves your API keys to a `secrets.json` file in the user data directory (`/data/default-user/secrets.json` is the default path).
By default, they will not be exposed to a frontend after you enter them and reload the page.
@@ -259,7 +283,7 @@ Most often this is for people who want to use SillyTavern on their mobile phones
However, it can be used to allow remote connections from anywhere as well.
**IMPORTANT: SillyTavern is a single-user program, so anyone who logs in will be able to see all characters and chats, and be able to change any settings inside the UI.**
**IMPORTANT: Refer to the official guide if you want to configure SillyTavern user accounts with (optional) password protection: [Users](https://docs.sillytavern.app/installation/st-1.12.0-migration-guide/#users).**
### 1. Managing whitelisted IPs

View File

@@ -1,7 +1,6 @@
name: Update i18n data
on:
workflow_dispatch:
on: workflow_dispatch
jobs:
build:

1
.gitignore vendored
View File

@@ -48,3 +48,4 @@ public/css/user.css
/plugins/
/data
/default/scaffold
public/scripts/extensions/third-party

View File

@@ -5,4 +5,6 @@ node_modules/
secrets.json
/dist
/backups/
/data
/cache
access.log

View File

@@ -4,7 +4,8 @@
// List of extensions which should be recommended for users of this workspace.
"recommendations": [
"dbaeumer.vscode-eslint",
"EditorConfig.EditorConfig"
"EditorConfig.EditorConfig",
"mrcrowl.easy-less"
],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": []

32
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,32 @@
# How to contribute to SillyTavern
## Setting up the dev environment
1. Required software: git and node.
2. Recommended editor: Visual Studio Code.
3. You can also use GitHub Codespaces which sets up everything for you.
## Getting the code ready
1. Register a GitHub account.
2. Fork this repository under your account.
3. Clone the fork onto your machine.
4. Open the cloned repository in the code editor.
5. Create a git branch (recommended).
6. Make your changes and test them locally.
7. Commit the changes and push the branch to the remote repo.
8. Go to GitHub, and open a pull request, targeting the upstream branch.
## Contribution guidelines
1. Our standards are pretty low, but make sure the code is not too ugly:
- Run VS Code's autoformat when you're done.
- Check with ESLint by running `npm run lint`, then fix the errors.
- Use common sense and follow existing naming conventions.
2. Create pull requests for the staging branch, 99% of contributions should go there. That way people could test your code before the next stable release.
3. You can still send a pull request for release in the following scenarios:
- Updating README.
- Updating GitHub Actions.
- Hotfixing a critical bug.
4. Project maintainers will test and can change your code before merging.
5. Mind the license. Your contributions will be licensed under the GNU Affero General Public License. If you don't know what that implies, consult your lawyer.

View File

@@ -11,6 +11,14 @@
"filename": "themes/Cappuccino.json",
"type": "theme"
},
{
"filename": "themes/Celestial Macaron.json",
"type": "theme"
},
{
"filename": "themes/Dark V 1.0.json",
"type": "theme"
},
{
"filename": "backgrounds/__transparent.png",
"type": "background"
@@ -476,7 +484,11 @@
"type": "context"
},
{
"filename": "presets/context/DreamGen Role-Play V1.json",
"filename": "presets/context/DreamGen Role-Play V1 ChatML.json",
"type": "context"
},
{
"filename": "presets/context/DreamGen Role-Play V1 Llama3.json",
"type": "context"
},
{
@@ -556,7 +568,11 @@
"type": "instruct"
},
{
"filename": "presets/instruct/DreamGen Role-Play V1.json",
"filename": "presets/instruct/DreamGen Role-Play V1 ChatML.json",
"type": "instruct"
},
{
"filename": "presets/instruct/DreamGen Role-Play V1 Llama3.json",
"type": "instruct"
},
{

View File

@@ -8,5 +8,5 @@
"trim_sentences": true,
"include_newline": false,
"single_line": false,
"name": "DreamGen Role-Play V1"
"name": "DreamGen Role-Play V1 ChatML"
}

View File

@@ -0,0 +1,12 @@
{
"story_string": "<|start_header_id|>system<|end_header_id|>\n\n{{#if system}}{{system}}\n\n\n{{/if}}## Overall plot description:\n\n{{#if scenario}}{{scenario}}{{else}}Conversation between {{char}} and {{user}}.{{/if}}{{#if wiBefore}}\n\n{{wiBefore}}{{/if}}\n\n\n## Characters:\n\n### {{char}}\n\n{{#if description}}{{description}}\n\n{{/if}}{{#if personality}}{{personality}}\n\n{{/if}}### {{user}}\n\n{{#if persona}}{{persona}}{{else}}{{user}} is the protagonist of the role-play.{{/if}}{{#if wiAfter}}\n\n{{wiAfter}}{{/if}}{{#if mesExamples}}\n\n{{mesExamples}}{{/if}}",
"example_separator": "<|eot_id|>\n<|start_header_id|>user<|end_header_id|>\n\nWrite an example narrative / conversation that is not part of the main story.",
"chat_start": "<|eot_id|>\n<|start_header_id|>user<|end_header_id|>\n\nStart the role-play between {{char}} and {{user}}.",
"use_stop_strings": false,
"allow_jailbreak": false,
"always_force_name2": false,
"trim_sentences": true,
"include_newline": false,
"single_line": false,
"name": "DreamGen Role-Play V1 Llama3"
}

View File

@@ -20,5 +20,5 @@
"user_alignment_message": "",
"system_same_as_user": true,
"last_system_sequence": "",
"name": "DreamGen Role-Play V1"
"name": "DreamGen Role-Play V1 ChatML"
}

View File

@@ -0,0 +1,18 @@
{
"system_prompt": "You are an intelligent, skilled, versatile writer.\n\nYour task is to write a role-play based on the information below.",
"input_sequence": "<|eot_id|>\n<|start_header_id|>writer character: {{user}}<|end_header_id|>\n\n",
"output_sequence": "<|eot_id|>\n<|start_header_id|>writer character: {{char}}<|end_header_id|>\n\n",
"first_output_sequence": "",
"last_output_sequence": "",
"system_sequence_prefix": "",
"system_sequence_suffix": "",
"stop_sequence": "",
"separator_sequence": "",
"wrap": false,
"macro": true,
"names": false,
"names_force_groups": false,
"activation_regex": "",
"skip_examples": false,
"name": "DreamGen Role-Play V1 Llama3"
}

View File

@@ -1,7 +1,7 @@
{
"name": "Cappuccino",
"blur_strength": 3,
"main_text_color": "rgba(255, 255, 255, 1)",
"main_text_color": "rgba(235, 235, 235, 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)",

View File

@@ -0,0 +1,37 @@
{
"name": "Celestial Macaron",
"blur_strength": 10,
"main_text_color": "rgba(229, 175, 162, 1)",
"italics_text_color": "rgba(146, 147, 161, 1)",
"underline_text_color": "rgba(157, 215, 198, 1)",
"quote_text_color": "rgba(197, 202, 206, 1)",
"blur_tint_color": "rgba(23, 36, 55, 0.9)",
"chat_tint_color": "rgba(18, 26, 40, 0.9)",
"user_mes_blur_tint_color": "rgba(51, 67, 90, 0.7)",
"bot_mes_blur_tint_color": "rgba(23, 36, 55, 0.75)",
"shadow_color": "rgba(0, 0, 0, 0.3)",
"shadow_width": 1,
"border_color": "rgba(60, 74, 110, 0.93)",
"font_scale": 1,
"fast_ui_mode": false,
"waifuMode": false,
"avatar_style": 0,
"chat_display": 1,
"noShadows": true,
"chat_width": 58,
"timer_enabled": true,
"timestamps_enabled": true,
"timestamp_model_icon": false,
"mesIDDisplay_enabled": true,
"hideChatAvatars_enabled": false,
"message_token_count_enabled": true,
"expand_message_actions": true,
"enableZenSliders": false,
"enableLabMode": false,
"hotswap_enabled": true,
"custom_css": "",
"bogus_folders": true,
"zoomed_avatar_magnification": false,
"reduced_motion": false,
"compact_input_area": true
}

View File

@@ -0,0 +1,37 @@
{
"name": "Dark V 1.0",
"blur_strength": 13,
"main_text_color": "rgba(207, 207, 197, 1)",
"italics_text_color": "rgba(145, 145, 145, 1)",
"underline_text_color": "rgba(145, 145, 145, 1)",
"quote_text_color": "rgba(198, 193, 151, 1)",
"blur_tint_color": "rgba(29, 33, 40, 0.9)",
"chat_tint_color": "rgba(29, 33, 40, 0.9)",
"user_mes_blur_tint_color": "rgba(29, 33, 40, 0.9)",
"bot_mes_blur_tint_color": "rgba(29, 33, 40, 0.9)",
"shadow_color": "rgba(0, 0, 0, 0.9)",
"shadow_width": 2,
"border_color": "rgba(0, 0, 0, 1)",
"font_scale": 1,
"fast_ui_mode": false,
"waifuMode": false,
"avatar_style": 0,
"chat_display": 0,
"noShadows": false,
"chat_width": 55,
"timer_enabled": false,
"timestamps_enabled": false,
"timestamp_model_icon": false,
"mesIDDisplay_enabled": false,
"hideChatAvatars_enabled": false,
"message_token_count_enabled": false,
"expand_message_actions": false,
"enableZenSliders": false,
"enableLabMode": false,
"hotswap_enabled": true,
"custom_css": "",
"bogus_folders": true,
"zoomed_avatar_magnification": true,
"reduced_motion": true,
"compact_input_area": false
}

View File

@@ -15,6 +15,11 @@
"**/node_modules/*",
"public/lib",
"backups/*",
"data/*"
"data/*",
"**/dist/*",
"dist/*",
"cache/*",
"src/tokenizers/*",
"docker/*",
]
}

11
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "sillytavern",
"version": "1.12.1",
"version": "1.12.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "sillytavern",
"version": "1.12.1",
"version": "1.12.2",
"hasInstallScript": true,
"license": "AGPL-3.0",
"dependencies": {
@@ -48,7 +48,7 @@
"vectra": "^0.2.2",
"wavefile": "^11.0.0",
"write-file-atomic": "^5.0.1",
"ws": "^8.13.0",
"ws": "^8.17.1",
"yaml": "^2.3.4",
"yargs": "^17.7.1",
"yauzl": "^2.10.0"
@@ -4659,8 +4659,9 @@
}
},
"node_modules/ws": {
"version": "8.13.0",
"license": "MIT",
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"engines": {
"node": ">=10.0.0"
},

View File

@@ -38,7 +38,7 @@
"vectra": "^0.2.2",
"wavefile": "^11.0.0",
"write-file-atomic": "^5.0.1",
"ws": "^8.13.0",
"ws": "^8.17.1",
"yaml": "^2.3.4",
"yargs": "^17.7.1",
"yauzl": "^2.10.0"
@@ -70,7 +70,7 @@
"type": "git",
"url": "https://github.com/SillyTavern/SillyTavern.git"
},
"version": "1.12.1",
"version": "1.12.2",
"scripts": {
"start": "node server.js",
"start:no-csrf": "node server.js --disableCsrf",

122
public/css/animations.css Normal file
View File

@@ -0,0 +1,122 @@
/* Fade animations with opacity */
@keyframes fade-in {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes fade-out {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
/* Pop animations with opacity and vertical scaling */
@keyframes pop-in {
0% {
opacity: 0;
transform: scaleY(0);
}
/* Make the scaling faster on pop-in, otherwise it looks a bit weird */
33% {
transform: scaleY(1);
}
100% {
opacity: 1;
transform: scaleY(1);
}
}
@keyframes pop-out {
0% {
opacity: 1;
transform: scaleY(1);
}
100% {
opacity: 0;
transform: scaleY(0);
}
}
/* Flashing for highlighting animation */
@keyframes flash {
20%,
60%,
100% {
opacity: 1;
}
0%,
40%,
80% {
opacity: 0.2;
}
}
/* Pulsing highlight, slightly resizing the element */
@keyframes pulse {
from {
transform: scale(1);
filter: brightness(1.1);
}
to {
transform: scale(1.01);
filter: brightness(1.3);
}
}
/* Ellipsis animation */
@keyframes ellipsis {
0% {
content: ""
}
25% {
content: "."
}
50% {
content: ".."
}
75% {
content: "..."
}
}
/* HEINOUS */
@keyframes infinite-spinning {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
/* STscript animation */
@keyframes script_progress_pulse {
0%,
100% {
border-top-color: var(--progColor);
}
50% {
border-top-color: var(--progFlashColor);
}
}

View File

@@ -36,7 +36,6 @@ label[for="extensions_autoconnect"] {
.extensions_info {
text-align: left;
margin: 0 1em;
}
.extensions_info h3 {
@@ -97,114 +96,11 @@ input.extension_missing[type="checkbox"] {
flex-direction: column;
}
/** LEFT COLUMN **/
/* Must be always on top */
#extensions_settings>#assets_ui {
order: -1;
/* Fixes order of settings for extensions */
.extension_container {
display: contents;
}
#extensions_settings>.expression_settings {
order: 2;
}
#extensions_settings>.background_settings {
order: 3;
}
#extensions_settings>.sd_settings {
order: 4;
}
#extensions_settings>#tts_settings {
order: 5;
}
#extensions_settings>#rvc_settings {
order: 6;
}
#extensions_settings>.objective-settings {
order: 7;
}
#extensions_settings>#speech_recognition_settings {
order: 8;
}
#extensions_settings>#audio_settings {
order: 9;
}
/** RIGHT COLUMN **/
#extensions_settings2>.translation_settings {
order: 1;
}
#extensions_settings2>.caption_settings {
order: 2;
}
#extensions_settings2>.quickReplySettings {
order: 3;
}
#extensions_settings2>.idle-settings {
order: 4;
}
#extensions_settings2>#memory_settings {
order: 5;
}
#extensions_settings2>.hypebot_settings {
order: 6;
}
#extensions_settings2>.regex_settings {
order: 7;
}
#extensions_settings2>.vectors_settings {
order: 8;
}
#extensions_settings2>.chromadb_settings {
order: 9;
}
#extensions_settings2>.randomizer_settings {
order: 10;
}
/** WAND MENU **/
#extensionsMenu>#ttsExtensionMenuItem {
order: 1;
}
#extensionsMenu>#sd_gen {
order: 2;
}
#extensionsMenu>#send_picture {
order: 3;
}
#extensionsMenu>#token_counter {
order: 4;
}
#extensionsMenu>#objective-task-manual-check-menu-item {
order: 5;
}
#extensionsMenu>#roll_dice {
order: 6;
}
#extensionsMenu>#translate_chat {
order: 7;
}
#extensionsMenu>#translate_input_message {
order: 8;
#extensionsMenu>div.extension_container:empty {
display: none;
}

View File

@@ -98,7 +98,7 @@
font-weight: bold;
}
.logprobs_top_candidate:not([disabled]):hover, .logprobs_top_candidate:not([disabled]):focus {
.logprobs_top_candidate:not([disabled]):hover {
background-color: rgba(0, 0, 0, 0.3);
}

View File

@@ -0,0 +1,8 @@
/* iPhone copium land */
@media screen and (max-width: 1000px) {
.ios .popup .popup-body {
height: fit-content;
max-height: 90vh;
max-height: 90svh;
}
}

175
public/css/popup.css Normal file
View File

@@ -0,0 +1,175 @@
@import url('./popup-safari-fix.css');
dialog {
color: var(--SmartThemeBodyColor);
}
/* Closed state of the dialog */
.popup {
width: 500px;
text-align: center;
box-shadow: 0px 0px 14px var(--black70a);
border: 1px solid var(--SmartThemeBorderColor);
padding: 4px 14px;
background-color: var(--SmartThemeBlurTintColor);
border-radius: 10px;
display: flex;
flex-direction: column;
max-height: calc(100svh - 2em);
max-width: calc(100svw - 2em);
min-height: fit-content;
/* Overflow visible so elements (like toasts) can appear outside of the dialog. '.popup-body' is hiding overflow for the real content. */
overflow: visible;
/* Fix weird animation issue with font-scaling during popup open */
backface-visibility: hidden;
transform: translateZ(0);
-webkit-font-smoothing: subpixel-antialiased;
}
.popup .popup-body {
display: flex;
flex-direction: column;
overflow: hidden;
width: 100%;
height: 100%;
padding: 1px;
}
.popup .popup-content {
margin-top: 10px;
padding: 0 8px;
overflow: hidden;
flex-grow: 1;
}
.popup .popup-content h3:first-child {
/* No double spacing for the first heading needed, the .popup-content already has margin */
margin-top: 0px;
}
.popup.vertical_scrolling_dialogue_popup .popup-content {
overflow-y: auto;
}
.popup.horizontal_scrolling_dialogue_popup .popup-content {
overflow-x: auto;
}
/* Opening animation */
.popup[opening] {
animation: pop-in var(--animation-duration-slow) ease-in-out;
}
.popup[opening]::backdrop {
animation: fade-in var(--animation-duration-slow) ease-in-out;
}
/* Open state of the dialog */
.popup[open] {
color: var(--SmartThemeBodyColor);
}
.popup[open]::backdrop {
backdrop-filter: blur(calc(var(--SmartThemeBlurStrength) * 2));
-webkit-backdrop-filter: blur(calc(var(--SmartThemeBlurStrength) * 2));
background-color: var(--black30a);
}
body.no-blur .popup[open]::backdrop {
backdrop-filter: none;
-webkit-backdrop-filter: none;
}
/* Closing animation */
.popup[closing] {
animation: pop-out var(--animation-duration-slow) ease-in-out;
}
.popup[closing]::backdrop {
animation: fade-out var(--animation-duration-slow) ease-in-out;
}
.popup #toast-container {
/* Fix toastr in dialogs by actually placing it at the top of the screen via transform */
height: 100svh;
top: calc(50% + var(--topBarBlockSize));
left: 50%;
transform: translate(-50%, -50%);
/* Fix text align, popups are centered by default. toasts should not. */
text-align: left;
}
.popup-crop-wrap {
margin: 10px auto;
max-height: 75vh;
max-height: 75svh;
max-width: 100%;
}
.popup-crop-wrap img {
max-width: 100%;
/* This rule is very important, please do not ignore this! */
}
.popup-inputs {
margin-top: 10px;
font-size: smaller;
opacity: 0.7;
}
.popup-input {
margin-top: 10px;
}
.popup-controls {
margin-top: 10px;
display: flex;
align-self: center;
gap: 20px;
}
.menu_button.menu_button_default {
box-shadow: 0 0 5px var(--white20a);
}
.menu_button.popup-button-ok {
background-color: var(--crimson70a);
}
.menu_button.popup-button-ok:hover {
background-color: var(--crimson-hover);
}
.popup-controls .menu_button {
/* Popup buttons should not scale to smallest size, otherwise they will always break to multiline if multiple words */
width: unset;
/* Fix weird animation issue with fonts on brightness filter */
backface-visibility: hidden;
transform: translateZ(0);
-webkit-font-smoothing: subpixel-antialiased;
}
.popup-controls .menu_button:hover:focus-visible {
filter: brightness(1.3) saturate(1.3);
}
.popup .popup-button-close {
position: absolute;
top: -6px;
right: -6px;
width: 24px;
height: 24px;
font-size: 20px;
padding: 2px 3px 3px 2px;
filter: brightness(0.8);
/* Fix weird animation issue with font-scaling during popup open */
backface-visibility: hidden;
}

View File

@@ -23,6 +23,14 @@
opacity: 0.8;
}
.select2-selection--single .select2-selection__placeholder {
color: var(--SmartThemeEmColor);
}
.select2-container--classic .select2-selection--single .select2-selection__placeholder {
color: var(--SmartThemeEmColor);
}
.select2-container .select2-selection--single .select2-selection__rendered {
color: var(--SmartThemeBodyColor);
line-height: revert;
@@ -49,7 +57,7 @@
color: var(--SmartThemeBodyColor);
border: 1px solid var(--SmartThemeBorderColor);
border-radius: 7px;
font-family: "Noto Sans", "Noto Color Emoji", sans-serif;
font-family: var(--mainFontFamily);
padding: 3px 5px;
}
@@ -77,7 +85,7 @@
color: var(--SmartThemeBodyColor);
border: 1px solid var(--SmartThemeBorderColor);
border-radius: 7px;
font-family: "Noto Sans", "Noto Color Emoji", sans-serif;
font-family: var(--mainFontFamily);
padding: 3px 5px;
}
@@ -173,8 +181,9 @@
}
.select2-selection__choice__display {
/* Fix weird alignment on the left side */
margin-left: 1px;
/* Fix weird alignment of the inside block */
margin-left: 3px;
margin-right: 1px;
}
/* Styling for choice remove icon */
@@ -194,6 +203,7 @@ span.select2.select2-container .select2-selection__choice__remove:hover {
.select2_choice_clickable+span.select2-container .select2-selection__choice__display {
cursor: pointer;
}
.select2_choice_clickable_buttonstyle+span.select2-container .select2-selection__choice__display {
cursor: pointer;
transition: background-color 0.3s;
@@ -211,7 +221,9 @@ span.select2.select2-container .select2-selection__choice__remove:hover {
.select2_multi_sameline+span.select2-container .select2-selection--multiple {
display: flex;
flex-wrap: wrap;
}.select2_multi_sameline+span.select2-container .select2-selection--multiple .select2-search--inline {
}
.select2_multi_sameline+span.select2-container .select2-selection--multiple .select2-search--inline {
/* Allow search placeholder to take up all space if needed */
flex-grow: 1;
}

View File

@@ -220,7 +220,7 @@
}
.monospace {
font-family: monospace;
font-family: var(--monoFontFamily);
}
.expander {
@@ -292,6 +292,14 @@
flex-wrap: nowrap;
}
.inline-flex {
display: inline-flex;
}
.inline-block {
display: inline-block;
}
.alignitemscenter,
.alignItemsCenter {
align-items: center;
@@ -348,6 +356,10 @@
margin-right: 5px;
}
.margin-r2 {
margin-right: 2px;
}
.flex0 {
flex: 0;
}

View File

@@ -14,7 +14,7 @@
display: flex;
flex-direction: row;
align-items: center;
gap: 10px;
gap: 6px;
margin-bottom: 5px;
}
@@ -27,8 +27,19 @@
flex: 1;
}
.tag_view_color_picker {
position: relative;
}
.tag_view_color_picker .link_icon {
position: absolute;
top: 50%;
right: 0px;
opacity: 0.5;
}
.tag_delete {
padding-right: 0;
padding: 2px 4px;
color: var(--SmartThemeBodyColor) !important;
}
@@ -108,6 +119,14 @@
opacity: 0.6;
}
#tagList .tag:has(.tag_remove:hover) {
opacity: 1;
}
#tagList .tag:has(.tag_remove:hover) .tag_name {
opacity: 0.6;
}
.tags.tags_inline {
opacity: 0.6;
column-gap: 0.2rem;

59
public/img/01ai.svg Normal file
View File

@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="363.44339"
height="375.68854"
viewBox="0 0 363.44339 375.68854"
version="1.1"
id="svg2"
sodipodi:docname="Yi_logo_icon_dark.svg"
inkscape:version="1.3 (0e150ed, 2023-07-21)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs2" />
<sodipodi:namedview
id="namedview2"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="1.1073359"
inkscape:cx="192.35355"
inkscape:cy="196.86889"
inkscape:window-width="1512"
inkscape:window-height="857"
inkscape:window-x="0"
inkscape:window-y="38"
inkscape:window-maximized="1"
inkscape:current-layer="svg2" />
<rect
x="287.14771"
y="224.04056"
width="42.3862"
height="151.64799"
rx="21.1931"
id="rect1" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="m 299.41969,17.362538 c -8.916,-7.5830004 -22.291,-6.503 -29.874,2.414 l -118.432,139.253002 c -3.056,3.593 -4.705,7.911 -5.001,12.281 -0.166,1.069 -0.252,2.164 -0.252,3.279 v 178.022 c 0,11.705 9.488,21.193 21.193,21.193 11.705,0 21.193,-9.488 21.193,-21.193 v -171.819 l 113.587,-133.556002 c 7.583,-8.916 6.502,-22.291 -2.414,-29.874 z"
id="path1" />
<rect
x="-18.236605"
y="8.6596518"
width="42.3862"
height="174.745"
rx="21.1931"
transform="rotate(-39.3441)"
id="rect2" />
<circle
cx="337.54071"
cy="163.28656"
r="25.9027"
id="circle2" />
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
class="logo"
width="36"
height="30.9767"
viewBox="0 0 36 30.9767"
version="1.1"
id="svg2"
sodipodi:docname="featherless.svg"
inkscape:version="1.3 (0e150ed, 2023-07-21)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs2" />
<sodipodi:namedview
id="namedview2"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="4.0920245"
inkscape:cx="75.268366"
inkscape:cy="15.151424"
inkscape:window-width="1512"
inkscape:window-height="857"
inkscape:window-x="0"
inkscape:window-y="38"
inkscape:window-maximized="1"
inkscape:current-layer="svg2" />
<path
d="M 34.0866,1.68482 C 32.2902,0.5825 29.863,0 27.0672,0 22.7842,0 18.0653,1.35865 13.8276,3.72206 L 13.7979,3.71083 c 0,0 -0.0042,0.02261 -0.0065,0.0334 C 12.5086,4.4617 11.2656,5.2629 10.0981,6.15731 3.22112,11.4248 1.29519,17.6748 2.92004,21.0156 1.14142,24.0728 0.0457,27.2332 0,30.9767 3.41949,24.421 5.4719,19.108 16.6146,10.1637 13.4309,10.8501 7.9281,14.1057 4.2271,19.0459 3.87793,16.156 6.1477,11.4895 11.2033,7.6174 11.8435,7.127 12.5092,6.66864 13.1886,6.23374 12.6577,7.8934 12.8269,7.4806 11.7254,9.8076 c 1.6289,-1.551 2.7014,-2.5081 4.3096,-5.16615 2.088,-1.03181 4.2598,-1.80301 6.4132,-2.2691 -0.3563,1.18836 -1.0345,3.20231 -1.9527,4.79455 0,0 2.3303,-0.50255 4.2563,-0.38902 -1.0523,1.16802 -1.9991,2.43152 -2.9592,3.72332 -1.3149,1.7684 -2.6742,3.5971 -4.4148,5.2993 -0.2095,0.2049 -0.4098,0.3907 -0.6129,0.5825 -2.6747,-0.2576 -4.4414,0.7485 -6.0966,2.5259 1.3054,-0.6123 3.059,-1.1165 4.1583,-0.813 -2.0258,1.662 -5.216,3.8529 -7.8373,3.6725 -0.4971,0.7611 -0.5285,0.7844 -1.0749,1.7038 4.252,1.0648 9.5926,-3.2817 12.7354,-6.3561 1.8428,-1.803 3.2466,-3.6904 4.6036,-5.5149 2.7947,-3.7585 5.2082,-7.0038 10.5619,-8.2388 L 36,2.85877 Z"
class="logo-mark"
id="path1"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="88.001465mm"
height="81.280983mm"
version="1.1"
id="svg9"
sodipodi:docname="huggingface.svg"
inkscape:version="1.3 (0e150ed, 2023-07-21)"
viewBox="0 0 88.001465 81.280983"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs9" />
<sodipodi:namedview
id="namedview9"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="0.68605868"
inkscape:cx="424.16197"
inkscape:cy="154.50573"
inkscape:window-width="1512"
inkscape:window-height="857"
inkscape:window-x="0"
inkscape:window-y="38"
inkscape:window-maximized="1"
inkscape:current-layer="svg9"
inkscape:clip-to-page="false"
inkscape:document-units="mm" />
<path
id="path2-9"
style="display:inline;"
d="M 40.855186,0.10840487 A 38.75,38.75 0 0 0 5.0016702,38.750983 a 38.75,38.75 0 0 0 1.7871095,11.589844 7.1,7.1 0 0 1 1.871094,0.291015 5.97,5.97 0 0 1 1.330078,-3.761718 c 0.02089,-0.02502 0.04515,-0.04576 0.06641,-0.07031 a 34.75,34.75 0 0 1 -1.0547201,-8.048831 34.750014,34.750014 0 0 1 69.5000291,0 34.75,34.75 0 0 1 -0.957032,7.630859 c 0.163358,0.152193 0.321565,0.31255 0.466797,0.488282 a 5.97,5.97 0 0 1 1.330078,3.761718 7.1,7.1 0 0 1 1.337891,-0.207031 A 38.75,38.75 0 0 0 82.501671,38.750983 38.75,38.75 0 0 0 40.855186,0.10840487 Z M 48.015342,73.165045 a 34.75,34.75 0 0 1 -8.044921,0.03906 c -0.396448,0.901178 -0.898324,1.811009 -1.529297,2.736328 -0.233308,0.342701 -0.489288,0.664577 -0.75586,0.974609 a 38.75,38.75 0 0 0 12.574219,-0.06641 c -0.245421,-0.290144 -0.482504,-0.589875 -0.699219,-0.908203 -0.639915,-0.938432 -1.14623,-1.86177 -1.544922,-2.775391 z M 73.940733,45.000983 c 1.62,0 3.07,0.66 4.07,1.87 a 5.97,5.97 0 0 1 1.33,3.76 7.1,7.1 0 0 1 1.95,-0.3 c 1.55,0 2.95,0.59 3.94,1.66 a 5.8,5.8 0 0 1 0.8,7 5.3,5.3 0 0 1 1.78,2.82 c 0.24,0.9 0.48,2.8 -0.8,4.74 a 5.22,5.22 0 0 1 0.37,5.02 c -1.02,2.32 -3.57,4.14 -8.51,6.1 -3.08,1.22 -5.9,2 -5.92,2.01 a 44.33,44.33 0 0 1 -10.93,1.6 c -5.86,0 -10.05,-1.8 -12.46,-5.34 -3.88,-5.69 -3.33,-10.9 1.7,-15.92 2.78,-2.78 4.63,-6.87 5.01,-7.77 0.78,-2.66 2.83,-5.62 6.24,-5.62 a 5.7,5.7 0 0 1 4.6,2.46 c 1,-1.26 1.98,-2.25 2.87,-2.82 a 7.4,7.4 0 0 1 3.96,-1.27 z m 0,4 c -0.51,0 -1.13,0.22 -1.82,0.65 -2.13,1.36 -6.25,8.43 -7.76,11.18 a 2.43,2.43 0 0 1 -2.14,1.31 c -1.54,0 -2.75,-1.53 -0.14,-3.48 3.91,-2.93 2.54,-7.72 0.67,-8.01 a 1.54,1.54 0 0 0 -0.24,-0.02 c -1.7,0 -2.45,2.93 -2.45,2.93 0,0 -2.2,5.52 -5.97,9.3 -3.78,3.77 -3.98,6.8 -1.22,10.83 1.87,2.75 5.47,3.58 9.15,3.58 3.82,0 7.73,-0.9 9.93,-1.46 0.1,-0.03 13.45,-3.8 11.76,-7 -0.29,-0.54 -0.75,-0.76 -1.34,-0.76 -2.38,0 -6.71,3.54 -8.57,3.54 -0.42,0 -0.71,-0.17 -0.83,-0.6 -0.8,-2.85 12.05,-4.05 10.97,-8.17 -0.19,-0.73 -0.7,-1.02 -1.44,-1.02 -3.14,0 -10.2,5.53 -11.68,5.53 -0.1,0 -0.19,-0.03 -0.23,-0.1 -0.74,-1.2 -0.34,-2.04 4.88,-5.2 5.23,-3.16 8.9,-5.06 6.8,-7.33 -0.23,-0.26 -0.57,-0.38 -0.98,-0.38 -3.18,0 -10.67,6.82 -10.67,6.82 0,0 -2.02,2.1 -3.24,2.1 a 0.74,0.74 0 0 1 -0.68,-0.38 c -0.87,-1.46 8.05,-8.22 8.55,-11.01 0.34,-1.9 -0.24,-2.85 -1.31,-2.85 z m -6.69,-15 a 3.25,3.25 0 1 0 0,-6.5 3.25,3.25 0 0 0 0,6.5 z m -46.5,0 a 3.25,3.25 0 1 0 0,-6.5 3.25,3.25 0 0 0 0,6.5 z m -6.69,11 c -1.62,0 -3.06,0.66 -4.0700003,1.87 a 5.97,5.97 0 0 0 -1.33,3.76 7.1,7.1 0 0 0 -1.94,-0.3 c -1.55,0 -2.95,0.59 -3.94,1.66 a 5.8,5.8 0 0 0 -0.8,7 5.3,5.3 0 0 0 -1.79000004,2.82 c -0.24,0.9 -0.48,2.8 0.8,4.74 a 5.22,5.22 0 0 0 -0.37,5.02 c 1.02000004,2.32 3.57000004,4.14 8.52000004,6.1 3.0700003,1.22 5.8900003,2 5.9100003,2.01 a 44.33,44.33 0 0 0 10.93,1.6 c 5.86,0 10.05,-1.8 12.46,-5.34 3.88,-5.69 3.33,-10.9 -1.7,-15.92 -2.77,-2.78 -4.62,-6.87 -5,-7.77 -0.78,-2.66 -2.84,-5.62 -6.25,-5.62 a 5.7,5.7 0 0 0 -4.6,2.46 c -1,-1.26 -1.98,-2.25 -2.86,-2.82 a 7.4,7.4 0 0 0 -3.97,-1.27 z m 0,4 c 0.51,0 1.14,0.22 1.82,0.65 2.14,1.36 6.25,8.43 7.76,11.18 0.5,0.92 1.37,1.31 2.14,1.31 1.55,0 2.75,-1.53 0.15,-3.48 -3.92,-2.93 -2.55,-7.72 -0.68,-8.01 0.08,-0.02 0.17,-0.02 0.24,-0.02 1.7,0 2.45,2.93 2.45,2.93 0,0 2.2,5.52 5.98,9.3 3.77,3.77 3.97,6.8 1.22,10.83 -1.88,2.75 -5.47,3.58 -9.16,3.58 -3.81,0 -7.73,-0.9 -9.92,-1.46 -0.11,-0.03 -13.4500003,-3.8 -11.7600003,-7 0.28,-0.54 0.75,-0.76 1.34,-0.76 2.38,0 6.7000003,3.54 8.5700003,3.54 0.41,0 0.7,-0.17 0.83,-0.6 0.79,-2.85 -12.0600003,-4.05 -10.9800003,-8.17 0.2,-0.73 0.71,-1.02 1.44,-1.02 3.14,0 10.2000003,5.53 11.6800003,5.53 0.11,0 0.2,-0.03 0.24,-0.1 0.74,-1.2 0.33,-2.04 -4.9,-5.2 -5.2100003,-3.16 -8.8800003,-5.06 -6.8000003,-7.33 0.24,-0.26 0.58,-0.38 1,-0.38 3.17,0 10.6600003,6.82 10.6600003,6.82 0,0 2.02,2.1 3.25,2.1 0.28,0 0.52,-0.1 0.68,-0.38 0.86,-1.46 -8.06,-8.22 -8.56,-11.01 -0.34,-1.9 0.24,-2.85 1.31,-2.85 z m 21.91,2 a 8.7,8.7 0 0 1 5.3,-4.49 c 0.4,-0.12 0.81,0.57 1.24,1.28 0.4,0.68 0.82,1.37 1.24,1.37 0.45,0 0.9,-0.68 1.33,-1.35 0.45,-0.7 0.89,-1.38 1.32,-1.25 a 8.61,8.61 0 0 1 5,4.17 c 3.73,-2.94 5.1,-7.74 5.1,-10.7 0,-2.34 -1.57,-1.6 -4.09,-0.36 l -0.14,0.07 c -2.31,1.15 -5.39,2.67 -8.77,2.67 -3.38,0 -6.45,-1.52 -8.77,-2.67 -2.6,-1.29 -4.23,-2.1 -4.23,0.29 0,3.05 1.46,8.06 5.47,10.97 z m 19.07,-21.7 c 1.28,0.44 1.78,3.06 3.07,2.38 a 5,5 0 1 0 -6.76,-2.07 c 0.61,1.15 2.55,-0.72 3.7,-0.32 z m -23.55,0 c -1.28,0.44 -1.79,3.06 -3.07,2.38 a 5,5 0 1 1 6.76,-2.07 c -0.61,1.15 -2.56,-0.72 -3.7,-0.32 z" />
</svg>

After

Width:  |  Height:  |  Size: 5.6 KiB

6
public/img/manual.svg Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<svg width="800px" height="800px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M13.3252 3.05011L8.66765 20.4323L10.5995 20.9499L15.257 3.56775L13.3252 3.05011Z" />
<path d="M7.61222 18.3608L8.97161 16.9124L8.9711 16.8933L3.87681 12.1121L8.66724 7.00798L7.20892 5.63928L1.0498 12.2017L7.61222 18.3608Z" />
<path d="M16.3883 18.3608L15.0289 16.9124L15.0294 16.8933L20.1237 12.1121L15.3333 7.00798L16.7916 5.63928L22.9507 12.2017L16.3883 18.3608Z" />
</svg>

After

Width:  |  Height:  |  Size: 514 B

View File

@@ -18,6 +18,7 @@
<link rel="preload" as="style" href="style.css">
<link rel="manifest" crossorigin="use-credentials" href="manifest.json">
<link href="webfonts/NotoSans/stylesheet.css" rel="stylesheet">
<link href="webfonts/NotoSansMono/stylesheet.css" rel="stylesheet">
<link href="css/fontawesome.min.css" rel="stylesheet">
<link href="css/solid.min.css" rel="stylesheet">
<link href="css/brands.min.css" rel="stylesheet">
@@ -32,7 +33,6 @@
<link rel="apple-touch-icon" sizes="144x144" href="img/apple-icon-144x144.png" />
<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/tags.css">
<link rel="stylesheet" type="text/css" href="css/rm-groups.css">
<link rel="stylesheet" type="text/css" href="css/group-avatars.css">
<link rel="stylesheet" type="text/css" href="css/toggle-dependent.css">
@@ -72,8 +72,8 @@
<div id="lm_button_panel_pin_div" title="Locked = AI Configuration panel will stay open" data-i18n="[title]AI Configuration panel will stay open">
<input type="checkbox" id="lm_button_panel_pin">
<label for="lm_button_panel_pin">
<div class="unchecked fa-solid fa-unlock "></div>
<div class="checked fa-solid fa-lock "></div>
<div class="unchecked fa-solid fa-unlock right_menu_button"></div>
<div class="checked fa-solid fa-lock right_menu_button"></div>
</label>
</div>
<div id="clickSlidersTips" data-i18n="clickslidertips" class="toggle-description wide100p editable-slider-notification">
@@ -423,7 +423,7 @@
</span>
</div>
</div>
<div class="range-block" data-source="openai,claude,windowai,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq">
<div class="range-block" data-source="openai,claude,windowai,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai">
<div class="range-block-title" data-i18n="Temperature">
Temperature
</div>
@@ -488,7 +488,7 @@
</div>
</div>
</div>
<div data-newbie-hidden class="range-block" data-source="openai,claude,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq">
<div data-newbie-hidden class="range-block" data-source="openai,claude,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai">
<div class="range-block-title" data-i18n="Top P">
Top P
</div>
@@ -1141,7 +1141,7 @@
<div class="fa-solid fa-circle-info opacity50p" title="Customize displayed samplers or add custom samplers." data-i18n="[title]Customize displayed samplers or add custom samplers."></div>
</small>
</div>
<div data-newbie-hidden data-tg-type="mancer, vllm, aphrodite, tabby" class="flex-container flexFlowColumn alignitemscenter flexBasis100p flexGrow flexShrink gap0">
<div data-newbie-hidden data-tg-type="mancer, vllm, aphrodite, tabby, infermaticai" class="flex-container flexFlowColumn alignitemscenter flexBasis100p flexGrow flexShrink gap0">
<small data-i18n="Multiple swipes per generation">Multiple swipes per generation</small>
<input type="number" id="n_textgenerationwebui" class="text_pole textAlignCenter" min="1" value="1" step="1" />
</div>
@@ -1263,7 +1263,7 @@
<input class="neo-range-slider" type="range" id="skew_textgenerationwebui" name="volume" min="-5" max="5" step="0.01" />
<input class="neo-range-input" type="number" min="-5" max="5" step="0.01" data-for="skew_textgenerationwebui" id="skew_counter_textgenerationwebui">
</div>
<div data-newbie-hidden data-tg-type="mancer, ooba, tabby, dreamgen" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
<div data-newbie-hidden data-tg-type="mancer, ooba, tabby, dreamgen, infermaticai" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
<small data-i18n="Min Length">Min Length</small>
<input class="neo-range-slider" type="range" id="min_length_textgenerationwebui" name="volume" min="0" max="2000" step="1" />
<input class="neo-range-input" type="number" min="0" max="2000" step="1" data-for="min_length_textgenerationwebui" id="min_length_counter_textgenerationwebui">
@@ -1446,7 +1446,7 @@
<div class="fa-solid fa-circle-info opacity50p " data-i18n="[title]Ban the eos_token. This forces the model to never end the generation prematurely" title="Ban the eos_token. This forces the model to never end the generation prematurely."></div>
</label>
</label>
<label data-tg-type="vllm, aphrodite" class="checkbox_label" for="ignore_eos_token_textgenerationwebui">
<label data-tg-type="vllm, aphrodite, infermaticai" class="checkbox_label" for="ignore_eos_token_textgenerationwebui">
<input type="checkbox" id="ignore_eos_token_textgenerationwebui" />
<small data-i18n="Ignore EOS Token">Ignore EOS Token
<div class="fa-solid fa-circle-info opacity50p " data-i18n="[title]Ignore the EOS Token even if it generates." title="Ignore the EOS Token even if it generates."></div>
@@ -1471,13 +1471,13 @@
</label>
</label>
<label data-tg-type="vllm, aphrodite" class="checkbox_label" for="spaces_between_special_tokens_textgenerationwebui">
<label data-tg-type="vllm, aphrodite, infermaticai" class="checkbox_label" for="spaces_between_special_tokens_textgenerationwebui">
<input type="checkbox" id="spaces_between_special_tokens_textgenerationwebui" />
<small data-i18n="Spaces Between Special Tokens">Spaces Between Special Tokens</small>
</label>
</div>
</div>
<div data-tg-type="mancer, ooba, koboldcpp, vllm, aphrodite, llamacpp, ollama" data-newbie-hidden class="flex-container flexFlowColumn alignitemscenter flexBasis48p flexGrow flexShrink gap0">
<div data-tg-type="mancer, ooba, koboldcpp, vllm, aphrodite, llamacpp, ollama, infermaticai, huggingface" data-newbie-hidden class="flex-container flexFlowColumn alignitemscenter flexBasis48p flexGrow flexShrink gap0">
<small data-i18n="Seed" class="textAlignCenter">Seed</small>
<input type="number" id="seed_textgenerationwebui" class="text_pole textAlignCenter" min="-1" value="-1" maxlength="100" />
</div>
@@ -1753,8 +1753,8 @@
<input id="openai_function_calling" type="checkbox" />
<span data-i18n="Enable function calling">Enable function calling</span>
<div class="flexBasis100p toggle-description justifyLeft">
Allows using <a href="https://platform.openai.com/docs/guides/function-calling" target="_blank">function tools</a>.
Can be utilized by various extensions to provide additional functionality.
<span data-i18n="enable_functions_desc_1">Allows using </span><a href="https://platform.openai.com/docs/guides/function-calling" target="_blank" data-i18n="enable_functions_desc_2">function tools</a>.
<span data-i18n="enable_functions_desc_3">Can be utilized by various extensions to provide additional functionality.</span>
</div>
</label>
</div>
@@ -1863,7 +1863,7 @@
<div id="openai_logit_bias_new_entry" class="menu_button wide100p flex-container justifyCenter" data-i18n="Add bias entry">
Add bias entry
</div>
<div class="openai_logit_bias_list"></div>
<div class="openai_logit_bias_list" no_items_text="No items" data-i18n="[no_items_text]openai_logit_bias_no_items"></div>
<div class="m-t-1">
<small>
<i class="fa-solid fa-lightbulb"></i>
@@ -1887,7 +1887,7 @@
</div>
<div id="sys-settings-button" class="drawer">
<div class="drawer-toggle drawer-header">
<div id="API-status-top" class="drawer-icon fa-solid fa-plug-circle-exclamation closedIcon" title="API Connections" data-i18n="[title]API Connections"></div>
<div id="API-status-top" class="drawer-icon fa-solid fa-plug-circle-exclamation closedIcon" title="API Connections" data-i18n="[title]API Connections;[no_connection_text]api_no_connection" no_connection_text="No connection..."></div>
</div>
<div id="rm_api_block" class="drawer-content closedDrawer">
<h3 class="margin0" id="title_api">API</h3>
@@ -2029,6 +2029,8 @@
<option value="ooba" data-i18n="Default (completions compatible)">Default [OpenAI /completions compatible: oobabooga, LM Studio, etc.]</option>
<option value="aphrodite">Aphrodite</option>
<option value="dreamgen">DreamGen</option>
<option value="featherless">Featherless</option>
<option value="huggingface">HuggingFace (Inference Endpoint)</option>
<option value="infermaticai">InfermaticAI</option>
<option value="koboldcpp">KoboldCpp</option>
<option value="llamacpp">llama.cpp</option>
@@ -2182,6 +2184,27 @@
</div>
<input id="custom_model_textgenerationwebui" class="text_pole wide100p" maxlength="500" placeholder="Custom model (optional)" data-i18n="[placeholder]Custom model (optional)" type="text">
</div>
<div data-tg-type="featherless" class="flex-container flexFlowColumn">
<div class="flex-container flexFlowColumn">
<a href="https://featherless.ai/models/" target="_blank" rel="noopener noreferrer">
featherless.ai
</a>
</div>
<h4 data-i18n="API key (optional)">API key</h4>
<div class="flex-container">
<input id="api_key_featherless" name="api_key_featherless" class="text_pole flex1 wide100p" maxlength="500" size="35" type="text" autocomplete="off">
<div title="Clear your API key" data-i18n="[title]Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_featherless">
</div>
</div>
<div data-for="api_key_featherless" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">
For privacy reasons, your API key will be hidden after you reload the page.
</div>
<select id="featherless_model">
<option data-i18n="-- Connect to the API --">
-- Connect to the API --
</option>
</select>
</div>
<div data-tg-type="vllm">
<div class="flex-container flexFlowColumn">
<a href="https://github.com/vllm-project/vllm" target="_blank" data-i18n="vllm-project/vllm">
@@ -2211,6 +2234,22 @@
</select>
</div>
</div>
<div data-tg-type="huggingface">
<h4 data-i18n="HuggingFace Token">HuggingFace Token</h4>
<div class="flex-container">
<input id="api_key_huggingface" name="api_key_huggingface" class="text_pole flex1 wide100p" maxlength="500" size="35" type="text" autocomplete="off">
<div title="Clear your API key" data-i18n="[title]Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_huggingface">
</div>
</div>
<div data-for="api_key_huggingface" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">
For privacy reasons, your API key will be hidden after you reload the page.
</div>
<div class="flex1">
<h4 data-i18n="Endpoint URL">Endpoint URL</h4>
<small data-i18n="Example: https://****.endpoints.huggingface.cloud">Example: https://****.endpoints.huggingface.cloud</small>
<input id="huggingface_api_url_text" class="text_pole wide100p" maxlength="500" value="" autocomplete="off" data-server-history="huggingface">
</div>
</div>
<div data-tg-type="aphrodite">
<div class="flex-container flexFlowColumn">
@@ -2332,7 +2371,7 @@
</div>
</div>
<div class="flex-container">
<div id="api_button_textgenerationwebui" class="api_button menu_button" type="submit" data-i18n="Connect" data-server-connect="ooba_blocking,vllm,aphrodite,tabby,koboldcpp,ollama,llamacpp">Connect</div>
<div id="api_button_textgenerationwebui" class="api_button menu_button" type="submit" data-i18n="Connect" data-server-connect="ooba_blocking,vllm,aphrodite,tabby,koboldcpp,ollama,llamacpp,huggingface">Connect</div>
<div data-tg-type="openrouter" class="menu_button menu_button_icon openrouter_authorize" title="Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai" data-i18n="Authorize;[title]Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai">Authorize</div>
<div class="api_loading menu_button" data-i18n="Cancel">Cancel</div>
</div>
@@ -2357,9 +2396,10 @@
<select id="chat_completion_source">
<optgroup>
<option value="openai">OpenAI</option>
<option value="custom">Custom (OpenAI-compatible)</option>
<option value="custom" data-i18n="Custom (OpenAI-compatible)">Custom (OpenAI-compatible)</option>
</optgroup>
<optgroup>
<option value="01ai">01.AI (Yi)</option>
<option value="ai21">AI21</option>
<option value="claude">Claude</option>
<option value="cohere">Cohere</option>
@@ -2551,6 +2591,7 @@
<h4 data-i18n="Claude Model">Claude Model</h4>
<select id="model_claude_select">
<optgroup label="Versions">
<option value="claude-3-5-sonnet-20240620">claude-3-5-sonnet-20240620</option>
<option value="claude-3-opus-20240229">claude-3-opus-20240229</option>
<option value="claude-3-sonnet-20240229">claude-3-sonnet-20240229</option>
<option value="claude-3-haiku-20240307">claude-3-haiku-20240307</option>
@@ -2856,16 +2897,16 @@
<form id="custom_form" data-source="custom">
<h4 data-i18n="Custom Endpoint (Base URL)">Custom Endpoint (Base URL)</h4>
<div class="flex-container">
<input id="custom_api_url_text" class="text_pole wide100p" maxlength="5000" value="" autocomplete="off" placeholder="Example: http://localhost:1234/v1">
<input id="custom_api_url_text" class="text_pole wide100p" maxlength="5000" value="" autocomplete="off" data-i18n="[placeholder]Example: http://localhost:1234/v1" placeholder="Example: http://localhost:1234/v1">
</div>
<div>
<small>
Doesn't work? Try adding <code>/v1</code> at the end of the URL!
<span data-i18n="Doesn't work? Try adding">Doesn't work? Try adding</span> <code>/v1</code> <span data-i18n="at the end of the URL!">at the end of the URL!</span>
</small>
</div>
<h4>
<span data-i18n="Custom API Key">Custom API Key</span>
<small>(Optional)</small>
<small data-i18n="(Optional)">(Optional)</small>
</h4>
<div class="flex-container">
<input id="api_key_custom" name="api_key_custom" class="text_pole flex1" maxlength="5000" value="" type="text" autocomplete="off">
@@ -2874,24 +2915,42 @@
<div data-for="api_key_custom" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">
For privacy reasons, your API key will be hidden after you reload the page.
</div>
<h4>Enter a Model ID</h4>
<h4 data-i18n="Enter a Model ID">Enter a Model ID</h4>
<div class="flex-container">
<input id="custom_model_id" class="text_pole wide100p" maxlength="500" value="" autocomplete="off" placeholder="Example: gpt-3.5-turbo">
<input list="model_custom_select_fill" id="custom_model_id" class="text_pole wide100p" maxlength="500" value="" autocomplete="off" data-i18n="[placeholder]Example: gpt-3.5-turbo" placeholder="Example: gpt-3.5-turbo">
<datalist id="model_custom_select_fill" class="text_pole model_custom_select"></datalist>
</div>
<h4 data-i18n="Available Models">Available Models</h4>
<div class="flex-container">
<select id="model_custom_select" class="text_pole"></select>
<select id="model_custom_select" class="text_pole model_custom_select"></select>
</div>
<h4 data-i18n="Prompt Post-Processing">Prompt Post-Processing</h4>
<select id="custom_prompt_post_processing" class="text_pole" title="Applies additional processing to the prompt before sending it to the API." data-i18n="[title]Applies additional processing to the prompt before sending it to the API.">
<option value="">None</option>
<option data-i18n="prompt_post_processing_none" value="">None</option>
<option value="claude">Claude</option>
</select>
</form>
<div id="01ai_form" data-source="01ai">
<h4>
<a data-i18n="01.AI API Key" href="https://platform.01.ai/" target="_blank" rel="noopener noreferrer">
01.AI API Key
</a>
</h4>
<div class="flex-container">
<input id="api_key_01ai" name="api_key_01ai" class="text_pole flex1" maxlength="500" value="" type="text" autocomplete="off">
<div title="Clear your API key" data-i18n="[title]Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_01ai"></div>
</div>
<div data-for="api_key_01ai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">
For privacy reasons, your API key will be hidden after you reload the page.
</div>
<h4 data-i18n="01.AI Model">01.AI Model</h4>
<select id="model_01ai_select">
</select>
</div>
<div class="flex-container flex">
<div id="api_button_openai" class="api_button menu_button menu_button_icon" type="submit" data-i18n="Connect">Connect</div>
<div class="api_loading menu_button" data-i18n="Cancel">Cancel</div>
<div data-source="custom" id="customize_additional_parameters" class="menu_button menu_button_icon">Additional Parameters</div>
<div data-source="custom" id="customize_additional_parameters" class="menu_button menu_button_icon" data-i18n="Additional Parameters">Additional Parameters</div>
<div data-source="openrouter" class="menu_button menu_button_icon openrouter_authorize" title="Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai" data-i18n="Authorize;[title]Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai">Authorize</div>
<div id="test_api_button" class="menu_button menu_button_icon" title="Verifies your API connection by sending a short test message. Be aware that you'll be credited for it!" data-i18n="[title]Verifies your API connection by sending a short test message. Be aware that you'll be credited for it!"><span data-i18n="Test Message">Test Message</span></div>
</div>
@@ -3747,95 +3806,6 @@
<input class="neo-range-slider" type="range" id="shadow_width" name="shadow_width" min="0" max="5" step="1">
<input class="neo-range-input" type="number" min="0" max="5" step="1" data-for="shadow_width" id="shadow_width_counter">
</div>
<!-- <div data-newbie-hidden class="range-block">
<label for="compact_input_area" class="range-block-title">
<span data-i18n="Chat Width (PC)">
Chat Width
</span>
<i class="fa-solid fa-desktop"></i>
</label>
<div class="range-block-range-and-counter">
<div class="range-block-range">
<input id="chat_width_slider" class="wide100p" type="range" min="25" max="100" step="1" value="50">
</div>
<div class="range-block-counter">
<input type="number" min="25" max="100" step="1" value="50" data-for="chat_width_slider" id="chat_width_slider_counter">
</div>
</div>
</div> -->
<!-- <div id="font-scale-block" class="range-block">
<div class="range-block-title" data-i18n="Font Scale">
Font Scale
</div>
<div class="range-block-range-and-counter">
<div class="range-block-range">
<input type="range" id="font_scale" name="font_scale" min="0.8" max="1.2" step="0.01">
</div>
<div class="range-block-counter">
<input type="number" min="0.8" max="1.2" step="0.01" data-for="font_scale" id="font_scale_counter">
</div>
</div>
</div> -->
<!-- <div id="blur-strength-block" class="range-block">
<div class="range-block-title" data-i18n="Blur Strength">
Blur Strength
</div>
<div class="range-block-range-and-counter">
<div class="range-block-range">
<input type="range" id="blur_strength" name="blur_strength" min="0" max="30" step="1">
</div>
<div class="range-block-counter">
<input type="number" min="0" max="30" step="1" data-for="blur_strength" id="blur_strength_counter">
</div>
</div>
</div> -->
<!-- <div id="shadow-width-block" class="range-block">
<div class="range-block-title" data-i18n="Text Shadow Width">
Text Shadow Width
</div>
<div class="range-block-range-and-counter">
<div class="range-block-range">
<input type="range" id="shadow_width" name="shadow_width" min="0" max="5" step="1">
</div>
<div class="range-block-counter">
<input type="number" min="0" max="5" step="1" data-for="shadow_width" id="shadow_width_counter">
</div>
</div>
</div> -->
<!-- <div id="chat-truncation-block" class="range-block">
<label for="compact_input_area" class="range-block-title">
<span data-i18n="Chat Truncation">
Chat Truncation
</span>
<small data-i18n="(0 = unlimited)">(0 = unlimited)</small>
</label>
<div class="range-block-range-and-counter">
<div class="range-block-range">
<input type="range" id="chat_truncation" name="chat_truncation" min="0" max="1000" step="25">
</div>
<div class="range-block-counter">
<input type="number" min="0" max="1000" step="1" data-for="chat_truncation" id="chat_truncation_counter">
</div>
</div>
</div> -->
<!-- <div id="streaming-fps" class="range-block">
<div class="range-block-title" data-i18n="Streaming FPS">
Streaming FPS
</div>
<div class="range-block-range-and-counter">
<div class="range-block-range">
<input type="range" id="streaming_fps" name="streaming_fps" min="5" max="100" step="5">
</div>
<div class="range-block-counter">
<input type="number" min="5" max="100" step="1" data-for="streaming_fps" id="streaming_fps_counter">
</div>
</div>
</div> -->
</div>
<hr>
<div name="themeToggles">
@@ -3922,13 +3892,22 @@
<h4 data-i18n="Character Handling">
Character Handling
</h4>
<div title="If set in the advanced character definitions, this field will be displayed in the characters list." data-i18n="[title]If set in the advanced character definitions, this field will be displayed in the characters list.">
<div class="flex-container alignitemscenter" title="If set in the advanced character definitions, this field will be displayed in the characters list." data-i18n="[title]If set in the advanced character definitions, this field will be displayed in the characters list.">
<label for="aux_field"><small data-i18n="Char List Subheader">Char List Subheader</small></label>
<select id="aux_field">
<select id="aux_field" class="widthNatural flex1 margin0">
<option data-i18n="Character Version" value="character_version">Character Version</option>
<option data-i18n="Created by" value="creator">Created by</option>
</select>
</div>
<div data-newbie-hidden class="flex-container alignitemscenter" title="Defines on importing cards which action should be chosen for importing its listed tags. 'Ask' will always display the dialog." data-i18n="[title]Defines on importing cards which action should be chosen for importing its listed tags. 'Ask' will always display the dialog.">
<label for="tag_import_setting"><small data-i18n="Import Card Tags">Import Card Tags</small></label>
<select id="tag_import_setting" class="widthNatural flex1 margin0">
<option data-i18n="Ask" value="1">Ask</option>
<option data-i18n="None" value="2">None</option>
<option data-i18n="All" value="3">All</option>
<option data-i18n="Existing" value="4">Existing</option>
</select>
</div>
<label data-newbie-hidden class="checkbox_label" for="fuzzy_search_checkbox" title="Use fuzzy matching, and search characters in the list by all data fields, not just by a name substring." data-i18n="[title]Use fuzzy matching, and search characters in the list by all data fields, not just by a name substring">
<input id="fuzzy_search_checkbox" type="checkbox" />
<small data-i18n="Advanced Character Search">Advanced Character Search</small>
@@ -3949,10 +3928,6 @@
<input id="show_card_avatar_urls" type="checkbox" />
<small data-i18n="Show avatar filenames">Show avatar filenames</small>
</label>
<label data-newbie-hidden class="checkbox_label" for="import_card_tags" title="Prompt to import embedded card tags on character import. Otherwise embedded tags are ignored." data-i18n="[title]Prompt to import embedded card tags on character import. Otherwise embedded tags are ignored">
<input id="import_card_tags" type="checkbox" />
<small data-i18n="Import Card Tags">Import Card Tags</small>
</label>
<label data-newbie-hidden class="checkbox_label" for="spoiler_free_mode" title="Hide character definitions from the editor panel behind a spoiler button." data-i18n="[title]Hide character definitions from the editor panel behind a spoiler button">
<input id="spoiler_free_mode" type="checkbox" />
<small data-i18n="Spoiler Free Mode">Spoiler Free Mode</small>
@@ -4246,7 +4221,7 @@
<div name="STscriptToggles">
<h4 data-i18n="STscript Settings">STscript Settings</h4>
<div title="Sets default flags for the STscript parser." data-i18n="[title]Sets default flags for the STscript parser.">
<label data-i18n="Parser Flags"><small>Parser Flags</small></label>
<label><small data-i18n="Parser Flags">Parser Flags</small></label>
<label class="checkbox_label" title="Switch to stricter escaping, allowing all delimiting characters to be escaped with a backslash, and backslashes to be escaped as well." data-i18n="[title]Switch to stricter escaping, allowing all delimiting characters to be escaped with a backslash, and backslashes to be escaped as well.">
<input id="stscript_parser_flag_strict_escaping" type="checkbox" />
<span data-i18n="STRICT_ESCAPING"><small>STRICT_ESCAPING</small></span>
@@ -4327,8 +4302,33 @@
</div>
</div>
<div id="extensions_settings" class="flex1 wide50p">
<div id="assets_container" class="extension_container"></div>
<div id="expressions_container" class="extension_container"></div>
<div id="sd_container" class="extension_container"></div>
<div id="tts_container" class="extension_container"></div>
<div id="rvc_container" class="extension_container"></div>
<div id="stt_container" class="extension_container"></div>
<div id="audio_container" class="extension_container"></div>
<div id="silence_container" class="extension_container"></div>
<div id="objective_container" class="extension_container"></div>
<div id="blip_container" class="extension_container"></div>
<div id="live2d_container" class="extension_container"></div>
<div id="vrm_container" class="extension_container"></div>
<div id="timelines_container" class="extension_container"></div>
</div>
<div id="extensions_settings2" class="flex1 wide50p">
<div id="websearch_container" class="extension_container"></div>
<div id="emulatorjs_container" class="extension_container"></div>
<div id="qr_container" class="extension_container"></div>
<div id="translation_container" class="extension_container"></div>
<div id="caption_container" class="extension_container"></div>
<div id="idle_container" class="extension_container"></div>
<div id="summarize_container" class="extension_container"></div>
<div id="hypebot_container" class="extension_container"></div>
<div id="regex_container" class="extension_container"></div>
<div id="vectors_container" class="extension_container"></div>
<div id="randomizer_container" class="extension_container"></div>
<div id="chromadb_container" class="extension_container"></div>
</div>
<hr class="wide100p margin0">
<div class="alignitemscenter flex-container justifyCenter wide100p" style="justify-content: space-between;">
@@ -4475,14 +4475,14 @@
<div id="rm_button_panel_pin_div" class="alignitemsflexstart" title="Locked = Character Management panel will stay open" data-i18n="[title]Locked = Character Management panel will stay open">
<input type="checkbox" id="rm_button_panel_pin">
<label for="rm_button_panel_pin">
<div class="fa-solid unchecked fa-unlock" alt=""></div>
<div class="fa-solid checked fa-lock" alt=""></div>
<div class="fa-solid unchecked fa-unlock right_menu_button" alt=""></div>
<div class="fa-solid checked fa-lock right_menu_button" alt=""></div>
</label>
</div>
<div class="right_menu_button fa-solid fa-list-ul" id="rm_button_characters" title="Select/Create Characters" data-i18n="[title]Select/Create Characters"></div>
</div>
<div id="HotSwapWrapper" class="alignitemscenter flex-container margin0auto wide100p">
<div class="hotswap avatars_inline flex-container expander" data-i18n="[no_favs]Favorite characters to add them to HotSwaps" no_favs="Favorite characters to add them to HotSwaps"></div>
<div class="hotswap avatars_inline flex-container scroll-reset-container expander" data-i18n="[no_favs]Favorite characters to add them to HotSwaps" no_favs="Favorite characters to add them to HotSwaps"></div>
</div>
</div>
<hr>
@@ -4490,7 +4490,7 @@
<div id="rm_PinAndTabs">
<div id="right-nav-panel-tabs" class="">
<div id="rm_button_selected_ch">
<h2></h2>
<h2 class="interactable"></h2>
</div>
<div id="result_info" class="flex-container" style="display: none;">
<div id="result_info_text" title="Token counts may be inaccurate and provided just for reference." data-i18n="[title]Token counts may be inaccurate and provided just for reference.">
@@ -4714,7 +4714,7 @@
<div class="flex1 flexGap5" title="Inserted before each part of the joined fields." data-i18n="[title]Inserted before each part of the joined fields.">
<label for="rm_group_generation_mode_join_prefix" class="flexnowrap width100p whitespacenowrap">
<span data-i18n="Join Prefix">Join Prefix</span>
<div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]When 'Join character cards' is selected, all respective fields of the characters are being joined together.&#13;This means that in the story string for example all character descriptions will be joined to one big text.&#13;If you want those fields to be separated, you can define a prefix or suffix here.&#13;&#13;This value supports normal macros and will also replace {{char}} with the relevant char's name and &lt;FIELDNAME&gt; with the name of the part (e.g.: description, personality, scenario, etc.)" title="When 'Join character cards' is selected, all respective fields of the characters are being joined together.&#13;This means that in the story string for example all character descriptions will be joined to one big text.&#13;If you want those fields to be separated, you can define a prefix or suffix here.&#13;&#13;This value supports normal macros and will also replace {{char}} with the relevant char's name and &lt;FIELDNAME&gt; with the name of the part (e.g.: description, personality, scenario, etc.)">
<div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]When 'Join character cards' is selected, all respective fields of the characters are being joined together.This means that in the story string for example all character descriptions will be joined to one big text.If you want those fields to be separated, you can define a prefix or suffix here.This value supports normal macros and will also replace {{char}} with the relevant char's name and &lt;FIELDNAME&gt; with the name of the part (e.g.: description, personality, scenario, etc.)" title="When 'Join character cards' is selected, all respective fields of the characters are being joined together.&#13;This means that in the story string for example all character descriptions will be joined to one big text.&#13;If you want those fields to be separated, you can define a prefix or suffix here.&#13;&#13;This value supports normal macros and will also replace {{char}} with the relevant char's name and &lt;FIELDNAME&gt; with the name of the part (e.g.: description, personality, scenario, etc.)">
</div>
</label>
<textarea id="rm_group_generation_mode_join_prefix" class="text_pole wide100p textarea_compact autoSetHeight" maxlength="2000" placeholder="&mdash;" rows="1"></textarea>
@@ -4722,7 +4722,7 @@
<div class="flex1 flexGap5" title="Inserted after each part of the joined fields." data-i18n="[title]Inserted after each part of the joined fields.">
<label for="rm_group_generation_mode_join_suffix" class="flexnowrap width100p whitespacenowrap">
<span data-i18n="Join Suffix">Join Suffix</span>
<div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]When 'Join character cards' is selected, all respective fields of the characters are being joined together.&#13;This means that in the story string for example all character descriptions will be joined to one big text.&#13;If you want those fields to be separated, you can define a prefix or suffix here.&#13;&#13;This value supports normal macros and will also replace {{char}} with the relevant char's name and &lt;FIELDNAME&gt; with the name of the part (e.g.: description, personality, scenario, etc.)" title="When 'Join character cards' is selected, all respective fields of the characters are being joined together.&#13;This means that in the story string for example all character descriptions will be joined to one big text.&#13;If you want those fields to be separated, you can define a prefix or suffix here.&#13;&#13;This value supports normal macros and will also replace {{char}} with the relevant char's name and &lt;FIELDNAME&gt; with the name of the part (e.g.: description, personality, scenario, etc.)">
<div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]When 'Join character cards' is selected, all respective fields of the characters are being joined together.This means that in the story string for example all character descriptions will be joined to one big text.If you want those fields to be separated, you can define a prefix or suffix here.This value supports normal macros and will also replace {{char}} with the relevant char's name and &lt;FIELDNAME&gt; with the name of the part (e.g.: description, personality, scenario, etc.)" title="When 'Join character cards' is selected, all respective fields of the characters are being joined together.&#13;This means that in the story string for example all character descriptions will be joined to one big text.&#13;If you want those fields to be separated, you can define a prefix or suffix here.&#13;&#13;This value supports normal macros and will also replace {{char}} with the relevant char's name and &lt;FIELDNAME&gt; with the name of the part (e.g.: description, personality, scenario, etc.)">
</div>
</label>
<textarea id="rm_group_generation_mode_join_suffix" class="text_pole wide100p textarea_compact autoSetHeight" maxlength="2000" placeholder="&mdash;" rows="1"></textarea>
@@ -4841,21 +4841,24 @@
</div>
</div>
<!-- various fullscreen popups -->
<template id="shadow_popup_template" data-i18n="[popup_text_save]popup_text_save;[popup_text_yes]popup_text_yes;[popup_text_no]popup_text_no;[popup_text_cancel]popup_text_cancel;[popup_text_import]popup_text_import" popup_text_save="Save" popup_text_yes="Yes" popup_text_no="No" popup_text_cancel="Cancel" popup_text_import="Import"> <!-- localization data holder for popups -->
<div class="shadow_popup">
<div class="dialogue_popup">
<div class="dialogue_popup_holder">
<div class="dialogue_popup_text">
<h3 class="margin0">text</h3>
</div>
<textarea class="dialogue_popup_input text_pole" rows="1"></textarea>
<div class="dialogue_popup_controls">
<div class="dialogue_popup_ok menu_button" data-i18n="Delete">Delete</div>
<div class="dialogue_popup_cancel menu_button" data-i18n="Cancel">Cancel</div>
</div>
<template id="popup_template" data-i18n="[popup-button-save]popup-button-save;[popup-button-yes]popup-button-yes;[popup-button-no]popup-button-no;[popup-button-cancel]popup-button-cancel;[popup-button-import]popup-button-import;[popup-button-crop]popup-button-crop" popup-button-save="Save" popup-button-yes="Yes" popup-button-no="No" popup-button-cancel="Cancel" popup-button-import="Import" popup-button-crop="Crop"> <!-- localization data holder for popups -->
<dialog class="popup">
<div class="popup-body">
<div class="popup-content">
<h3 class="popup-header">text</h3>
</div>
<div class="popup-crop-wrap">
<img class="popup-crop-image" src="">
</div>
<textarea class="popup-input text_pole result-control" rows="1" data-result="1" data-result-event="submit"></textarea>
<div class="popup-inputs"></div>
<div class="popup-controls">
<div class="popup-button-ok menu_button result-control" data-result="1" data-i18n="Delete">Delete</div>
<div class="popup-button-cancel menu_button result-control" data-result="0" data-i18n="Cancel">Cancel</div>
</div>
</div>
</div>
<div class="popup-button-close right_menu_button fa-solid fa-circle-xmark" data-result="0" title="Close popup" data-i18n="[title]Close popup"></div>
</dialog>
</template>
<div id="shadow_popup">
<div id="dialogue_popup">
@@ -4865,15 +4868,15 @@
</div>
<textarea id="dialogue_popup_input" class="text_pole" rows="1"></textarea>
<div id="dialogue_popup_controls">
<div id="dialogue_popup_ok" class="menu_button" data-i18n="Delete">Delete</div>
<div id="dialogue_popup_cancel" class="menu_button" data-i18n="Cancel">Cancel</div>
<div id="dialogue_popup_ok" class="menu_button" data-i18n="Delete" data-result="1">Delete</div>
<div id="dialogue_popup_cancel" class="menu_button" data-i18n="Cancel" data-result="0">Cancel</div>
</div>
</div>
</div>
</div>
<div id="character_popup" class="flex-container flexFlowColumn flexNoGap">
<div id="character_popup_text">
<h3 id="character_popup_text_h3" class="margin0"></h3> <span data-i18n="Advanced Defininitions">- Advanced
<h3 id="character_popup-button-h3" class="margin0"></h3> <span data-i18n="Advanced Defininitions">- Advanced
Definitions</span>
</div>
<hr class="margin-bot-10px">
@@ -5192,8 +5195,8 @@
<div title="Tag as folder" class="tag_as_folder fa-solid fa-folder-open right_menu_button" data-i18n="[title]Use tag as folder">
<span class="tag_folder_indicator"></span>
</div>
<div class="tagColorPickerHolder"></div>
<div class="tagColorPicker2Holder"></div>
<div class="tag_view_color_picker" data-value="color"></div>
<div class="tag_view_color_picker" data-value="color2"></div>
<div class="tag_view_name" contenteditable="true"></div>
<div class="tag_view_counter"><span class="tag_view_counter_value"></span>&nbsp;entries</div>
<div title="Delete tag" class="tag_delete fa-solid fa-trash-can right_menu_button" data-i18n="[title]Delete tag"></div>
@@ -5390,6 +5393,79 @@
<div class="world_entry_thin_controls commentContainer">
</div>
</div>
<div class="flex-container wide100p flexGap10">
<div class="flex4 flex-container flexFlowColumn flexNoGap">
<div class="flex-container justifySpaceBetween">
<small for="group">
<span data-i18n="Inclusion Group">Inclusion Group</span>
<a href="https://docs.sillytavern.app/usage/core-concepts/worldinfo/#inclusion-group" class="notes-link" target="_blank" title="Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.&#13;Supports multiple comma-separated groups.&#13;&#13;Documentation: World Info - Inclusion Group" data-i18n="[title]Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.Documentation: World Info - Inclusion Group">
<span class="fa-solid fa-circle-question note-link-span"></span>
</a>
</small>
<label class="checkbox_label flexNoGap margin-r5" for="groupOverride">
<input type="checkbox" name="groupOverride" />
<span>
<small title="Prioritize this entry: When checked, this entry is prioritized out of all selections.&#13;If multiple are prioritized, the one with the highest 'Order' is chosen.&#13;" data-i18n="[title]Prioritize this entry: When checked, this entry is prioritized out of all selections.If multiple are prioritized, the one with the highest 'Order' is chosen.">
<span data-i18n="Prioritize">Prioritize</span>
<div class="fa-solid fa-circle-info opacity50p"></div>
</small>
</span>
</label>
</div>
<div class="range-block-range">
<input type="text" class="text_pole margin0" name="group" rows="1" data-i18n="[placeholder]Only one entry with the same label will be activated" placeholder="Only one entry with the same label will be activated">
</div>
</div>
<div class="flex2 flex-container flexFlowColumn flexNoGap" data-i18n="[title]A relative likelihood of entry activation within the group" title="A relative likelihood of entry activation within the group">
<div class="flex-container justifySpaceBetween marginBot5">
<small for="groupWeight" data-i18n="Group Weight">
Group Weight
</small>
</div>
<div class="range-block-range">
<input type="number" class="text_pole margin0" name="groupWeight" rows="1" placeholder="100" min="1" max="999999">
</div>
</div>
<div class="flex2 flex-container flexFlowColumn flexNoGap" data-i18n="[title]Sticky entries will stay active for N messages after being triggered." title="Sticky entries will stay active for N messages after being triggered.">
<div class="flex-container justifySpaceBetween marginBot5">
<small class="flex-container alignItemsBaseline" for="sticky" data-i18n="Sticky">
<span data-i18n="Sticky">
Sticky
</span>
<i class="fa-solid fa-comments fa-xs"></i>
</small>
</div>
<div class="range-block-range">
<input class="text_pole margin0" name="sticky" type="number" placeholder="Non-sticky" min="0" max="999999">
</div>
</div>
<div class="flex2 flex-container flexFlowColumn flexNoGap" data-i18n="[title]Entries with a cooldown can't be activated N messages after being triggered." title="Entries with a cooldown can't be activated N messages after being triggered.">
<div class="flex-container justifySpaceBetween marginBot5">
<small class="flex-container alignItemsBaseline" for="cooldown" data-i18n="Cooldown">
<span data-i18n="Cooldown">
Cooldown
</span>
<i class="fa-solid fa-comments fa-xs"></i>
</small>
</div>
<div class="range-block-range">
<input class="text_pole margin0" name="cooldown" type="number" placeholder="No cooldown" min="0" max="999999">
</div>
</div>
<div class="flex2 flex-container flexFlowColumn flexNoGap" data-i18n="[title]Entries with a delay can't be activated until there are N messages present in the chat." title="Entries with a delay can't be activated until there are N messages present in the chat.">
<div class="flex-container justifySpaceBetween marginBot5">
<small class="flex-container alignItemsBaseline" for="delay" data-i18n="Delay">
<span data-i18n="Delay">
Delay
</span>
<i class="fa-solid fa-comments fa-xs"></i>
</small>
</div>
<div class="range-block-range">
<input class="text_pole margin0" name="delay" type="number" placeholder="No delay" min="0" max="999999">
</div>
</div>
</div>
<div class="flex-container wide100p flexGap10">
<div class="flex4 flex-container flexFlowColumn flexNoGap">
<div class="flex-container justifySpaceBetween">
@@ -5411,38 +5487,6 @@
</select>
</div>
</div>
<div class="flex3 flex-container flexFlowColumn flexNoGap">
<div class="flex-container justifySpaceBetween">
<small for="group">
<span data-i18n="Inclusion Group">Inclusion Group</span>
<a href="https://docs.sillytavern.app/usage/core-concepts/worldinfo/#inclusion-group" class="notes-link" target="_blank" title="Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.&#13;Supports multiple comma-separated groups.&#13;&#13;Documentation: World Info - Inclusion Group" data-i18n="[title]Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.&#13;&#13;Documentation: World Info - Inclusion Group">
<span class="fa-solid fa-circle-question note-link-span"></span>
</a>
</small>
<label class="checkbox_label flexNoGap margin-r5" for="groupOverride">
<input type="checkbox" name="groupOverride" />
<span>
<small data-i18n="Prioritize Inclusion" title="Prioritize this entry: When checked, this entry is prioritized out of all selections.&#13;If multiple are prioritized, the one with the highest 'Order' is chosen.&#13;" data-i18n="[title]Prioritize this entry: When checked, this entry is prioritized out of all selections.&#13;If multiple are prioritized, the one with the highest 'Order' is chosen.">
Prioritize Inclusion
<div class="fa-solid fa-circle-info opacity50p"></div>
</small>
</span>
</label>
</div>
<div class="range-block-range">
<input type="text" class="text_pole margin0" name="group" rows="1" data-i18n="[placeholder]Only one entry with the same label will be activated" placeholder="Only one entry with the same label will be activated">
</div>
</div>
<div class="flex1 flex-container flexFlowColumn flexNoGap" data-i18n="[title]A relative likelihood of entry activation within the group" title="A relative likelihood of entry activation within the group">
<div class="flex-container justifySpaceBetween marginBot5">
<small for="groupWeight" data-i18n="Group Weight">
Group Weight
</small>
</div>
<div class="range-block-range">
<input type="number" class="text_pole margin0" name="groupWeight" rows="1" placeholder="100" min="1" max="999999">
</div>
</div>
</div>
<div name="WIEntryBottomControls" class="flex-container flex1 justifySpaceBetween world_entry_form_horizontal">
<div class="flex-container flexFlowColumn flexNoGap wi-enter-footer-text ">
@@ -5466,7 +5510,7 @@
</div>
</div>
<div id="character_template" class="template_element">
<div class="character_select flex-container wide100p alignitemsflexstart" chid="" id="">
<div class="character_select entity_block flex-container wide100p alignitemsflexstart" chid="" id="">
<div class="avatar" title="">
<img src="">
</div>
@@ -5498,7 +5542,6 @@
</form>
</div>
</div>
</div>
<div id="logit_bias_template" class="template_element">
<div class="logit_bias_form">
<input class="logit_bias_text text_pole" data-i18n="[placeholder]Type here..." placeholder="type here..." />
@@ -5616,21 +5659,21 @@
</div>
</div>
<div class="mes_buttons">
<div title="Message Actions" class="extraMesButtonsHint fa-solid fa-ellipsis" data-i18n="[title]Message Actions"></div>
<div title="Message Actions" class="mes_button extraMesButtonsHint fa-solid fa-ellipsis" data-i18n="[title]Message Actions"></div>
<div class="extraMesButtons">
<div title="Translate message" class="mes_translate fa-solid fa-language" data-i18n="[title]Translate message"></div>
<div title="Generate Image" class="sd_message_gen fa-solid fa-paintbrush" data-i18n="[title]Generate Image"></div>
<div title="Narrate" class="mes_narrate fa-solid fa-bullhorn" data-i18n="[title]Narrate"></div>
<div title="Prompt" class="mes_prompt fa-solid fa-square-poll-horizontal " data-i18n="[title]Prompt" style="display: none;"></div>
<div title="Exclude message from prompts" class="mes_hide fa-solid fa-eye" data-i18n="[title]Exclude message from prompts"></div>
<div title="Include message in prompts" class="mes_unhide fa-solid fa-eye-slash" data-i18n="[title]Include message in prompts"></div>
<div title="Embed file or image" class="mes_embed fa-solid fa-paperclip" data-i18n="[title]Embed file or image"></div>
<div title="Create checkpoint" class="mes_create_bookmark fa-regular fa-solid fa-flag-checkered" data-i18n="[title]Create checkpoint"></div>
<div title="Create branch" class="mes_create_branch fa-regular fa-code-branch" data-i18n="[title]Create Branch"></div>
<div title="Copy" class="mes_copy fa-solid fa-copy " data-i18n="[title]Copy"></div>
<div title="Translate message" class="mes_button mes_translate fa-solid fa-language" data-i18n="[title]Translate message"></div>
<div title="Generate Image" class="mes_button sd_message_gen fa-solid fa-paintbrush" data-i18n="[title]Generate Image"></div>
<div title="Narrate" class="mes_button mes_narrate fa-solid fa-bullhorn" data-i18n="[title]Narrate"></div>
<div title="Prompt" class="mes_button mes_prompt fa-solid fa-square-poll-horizontal " data-i18n="[title]Prompt" style="display: none;"></div>
<div title="Exclude message from prompts" class="mes_button mes_hide fa-solid fa-eye" data-i18n="[title]Exclude message from prompts"></div>
<div title="Include message in prompts" class="mes_button mes_unhide fa-solid fa-eye-slash" data-i18n="[title]Include message in prompts"></div>
<div title="Embed file or image" class="mes_button mes_embed fa-solid fa-paperclip" data-i18n="[title]Embed file or image"></div>
<div title="Create checkpoint" class="mes_button mes_create_bookmark fa-regular fa-solid fa-flag-checkered" data-i18n="[title]Create checkpoint"></div>
<div title="Create branch" class="mes_button mes_create_branch fa-regular fa-code-branch" data-i18n="[title]Create Branch"></div>
<div title="Copy" class="mes_button mes_copy fa-solid fa-copy " data-i18n="[title]Copy"></div>
</div>
<div title="Open checkpoint chat" class="mes_bookmark fa-solid fa-flag" data-i18n="[title]Open checkpoint chat"></div>
<div title="Edit" class="mes_edit fa-solid fa-pencil " data-i18n="[title]Edit"></div>
<div title="Open checkpoint chat" class="mes_button mes_bookmark fa-solid fa-flag" data-i18n="[title]Open checkpoint chat"></div>
<div title="Edit" class="mes_button mes_edit fa-solid fa-pencil " data-i18n="[title]Edit"></div>
</div>
<div class="mes_edit_buttons">
<div class="mes_edit_done menu_button fa-solid fa-check" title="Confirm" data-i18n="[title]Confirm"></div>
@@ -5643,10 +5686,11 @@
<div class="mes_edit_cancel menu_button fa-solid fa-xmark" title="Cancel" data-i18n="[title]Cancel"></div>
</div>
</div>
<div class=mes_text></div>
<div class="mes_text"></div>
<div class="mes_img_container">
<div class="mes_img_controls">
<div title="Enlarge" class="right_menu_button fa-lg fa-solid fa-magnifying-glass mes_img_enlarge" data-i18n="[title]Enlarge"></div>
<div title="Caption" class="right_menu_button fa-lg fa-solid fa-envelope-open-text mes_img_caption" data-i18n="[title]Caption"></div>
<div title="Delete" class="right_menu_button fa-lg fa-solid fa-trash-can mes_img_delete" data-i18n="[title]Delete"></div>
</div>
<img class="mes_img" src="" />
@@ -5768,7 +5812,7 @@
</div>
</div>
<div id="group_list_template" class="template_element">
<div class="group_select flex-container wide100p alignitemsflexstart">
<div class="group_select entity_block flex-container wide100p alignitemsflexstart">
<div class="avatar">
<img src="">
</div>
@@ -5786,7 +5830,7 @@
</div>
</div>
<div id="bogus_folder_template" class="template_element">
<div class="bogus_folder_select flex-container wide100p alignitemsflexstart">
<div class="bogus_folder_select entity_block flex-container wide100p alignitemsflexstart">
<div class="avatar flex alignitemscenter textAlignCenter">
<i class="bogus_folder_icon fa-solid fa-xl"></i>
</div>
@@ -5832,8 +5876,8 @@
</small>
<hr>
<div class="alternate_greetings_list flexFlowColumn flex-container wide100p">
<strong class="alternate_grettings_hint margin-bot-10px" data-i18n="Alternate Greetings Hint">
Click the <i class="fa-solid fa-plus"></i> button to get started!
<strong class="alternate_grettings_hint margin-bot-10px">
<span data-i18n="alternate_greetings_hint_1">Click the</span> <i class="fa-solid fa-plus"></i> <span data-i18n="alternate_greetings_hint_2">button to get started!</span>
</strong>
</div>
</div>
@@ -5841,38 +5885,13 @@
<div id="alternate_greeting_form_template" class="template_element">
<div class="alternate_greeting">
<div class="title_restorable">
<strong>Alternate Greeting #<span class="greeting_index"></span></strong>
<strong><span data-i18n="Alternate Greeting #">Alternate Greeting #</span><span class="greeting_index"></span></strong>
<div class="menu_button fa-solid fa-trash-alt delete_alternate_greeting"></div>
</div>
<textarea name="alternate_greetings" data-i18n="[placeholder](This will be the first message from the character that starts every chat)" placeholder="(This will be the first message from the character that starts every chat)" class="text_pole textarea_compact alternate_greeting_text" maxlength="50000" value="" autocomplete="off" rows="16"></textarea>
</div>
</div>
<div id="forbid_media_override_template" class="template_element">
<div class="forbid_media_override flex-container flexFlowColumn">
<h4 data-i18n="Forbid Media Override explanation" class="margin0">
Ability of the current character/group to use external media in chats.
</h4>
<small data-i18n="Forbid Media Override subtitle" class="marginBot5">
Media: images, videos, audio. External: not hosted on the local server.
</small>
<label class="checkbox_label" for="forbid_media_override_global">
<input type="radio" id="forbid_media_override_global" name="forbid_media_override" />
<span>
<span data-i18n="Use global setting">Use global setting</span>
<b class="forbid_media_global_state_forbidden">(forbidden)</b>
<b class="forbid_media_global_state_allowed">(allowed)</b>
</span>
</label>
<label class="checkbox_label" for="forbid_media_override_forbidden">
<input type="radio" id="forbid_media_override_forbidden" name="forbid_media_override" />
<span data-i18n="Always forbidden">Always forbidden</span>
</label>
<label class="checkbox_label" for="forbid_media_override_allowed">
<input type="radio" id="forbid_media_override_allowed" name="forbid_media_override" />
<span data-i18n="Always allowed">Always allowed</span>
</label>
</div>
</div>
<!-- chat and input bar -->
<div id="typing_indicator_template" class="template_element">
<div class="typing_indicator"><span class="typing_indicator_name">CHAR</span> is typing</div>
@@ -6250,7 +6269,7 @@
</form>
<div id="nonQRFormItems">
<div id="leftSendForm" class="alignContentCenter">
<div id="options_button" class="fa-solid fa-bars"></div>
<div id="options_button" class="fa-solid fa-bars interactable"></div>
</div>
<textarea id="send_textarea" name="text" data-i18n="[no_connection_text]Not connected to API!;[connected_text]Type a message, or /? for help" placeholder="Not connected to API!" no_connection_text="Not connected to API!" connected_text="Type a message, or /? for help"></textarea>
<div id="rightSendForm" class="alignContentCenter">
@@ -6266,8 +6285,8 @@
<div id="mes_stop" title="Abort request" class="mes_stop" data-i18n="[title]Abort request">
<i class="fa-solid fa-circle-stop"></i>
</div>
<div id="mes_continue" class="fa-fw fa-solid fa-arrow-right displayNone" title="Continue the last message" data-i18n="[title]Continue the last message"></div>
<div id="send_but" class="fa-solid fa-paper-plane displayNone" title="Send a message" data-i18n="[title]Send a message"></div>
<div id="mes_continue" class="fa-fw fa-solid fa-arrow-right interactable displayNone" title="Continue the last message" data-i18n="[title]Continue the last message"></div>
<div id="send_but" class="fa-solid fa-paper-plane interactable displayNone" title="Send a message" data-i18n="[title]Send a message"></div>
</div>
</div>
</div>
@@ -6364,9 +6383,6 @@
</div>
</div>
</template>
<div id="rawPromptPopup" class="list-group">
<div id="rawPromptWrapper" class="tokenItemizingSubclass"></div>
</div>
<div id="user_avatar_template" class="template_element">
<div class="avatar-container">
<div imgfile="" class="avatar">
@@ -6431,15 +6447,6 @@
<script type="module" src="scripts/setting-search.js"></script>
<script type="module" src="scripts/server-history.js"></script>
<script type="module" src="script.js"></script>
<script>
// Configure toast library:
toastr.options.escapeHtml = true; // Prevent raw HTML inserts
toastr.options.timeOut = 4000; // How long the toast will display without user interaction
toastr.options.extendedTimeOut = 10000; // How long the toast will display after a user hovers over it
toastr.options.progressBar = true; // Visually indicate how long before a toast expires.
toastr.options.closeButton = true; // enable a close button
toastr.options.positionClass = "toast-top-center"; // Where to position the toast container
</script>
<script>
window.addEventListener('load', (event) => {
const documentHeight = () => {

View File

@@ -3,6 +3,7 @@
"checkJs": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "node",
"allowUmdGlobalAccess": true,
"allowSyntheticDefaultImports": true
},

View File

@@ -853,7 +853,7 @@
"Join character cards (include muted)": "انضم إلى بطاقات الشخصيات (بما في ذلك البطاقات الصامتة)",
"Inserted before each part of the joined fields.": "تم إدراجه قبل كل جزء من الحقول المرتبطة.",
"Join Prefix": "الانضمام إلى البادئة",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.\rThis means that in the story string for example all character descriptions will be joined to one big text.\rIf you want those fields to be separated, you can define a prefix or suffix here.\r\rThis value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "عند تحديد \"الانضمام إلى بطاقات الأحرف\"، يتم ضم كافة الحقول الخاصة بالأحرف معًا.\rوهذا يعني أنه في سلسلة القصة على سبيل المثال، سيتم دمج جميع أوصاف الشخصيات في نص واحد كبير.\rإذا كنت تريد فصل هذه الحقول، فيمكنك تحديد بادئة أو لاحقة هنا.\r\rتدعم هذه القيمة وحدات الماكرو العادية وستستبدل أيضًا {{char}} باسم الحرف ذي الصلة و<FIELDNAME> باسم الجزء (على سبيل المثال: الوصف، والشخصية، والسيناريو، وما إلى ذلك)",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.This means that in the story string for example all character descriptions will be joined to one big text.If you want those fields to be separated, you can define a prefix or suffix here.This value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "عند تحديد \"الانضمام إلى بطاقات الأحرف\"، يتم ضم كافة الحقول الخاصة بالأحرف معًا.\rوهذا يعني أنه في سلسلة القصة على سبيل المثال، سيتم دمج جميع أوصاف الشخصيات في نص واحد كبير.\rإذا كنت تريد فصل هذه الحقول، فيمكنك تحديد بادئة أو لاحقة هنا.\r\rتدعم هذه القيمة وحدات الماكرو العادية وستستبدل أيضًا {{char}} باسم الحرف ذي الصلة و<FIELDNAME> باسم الجزء (على سبيل المثال: الوصف، والشخصية، والسيناريو، وما إلى ذلك)",
"Inserted after each part of the joined fields.": "يتم إدراجه بعد كل جزء من الحقول المرتبطة.",
"Join Suffix": "انضم إلى لاحقة",
"Set a group chat scenario": "تعيين سيناريو للمحادثة الجماعية",
@@ -885,11 +885,11 @@
"Bulk_edit_characters": "تحرير الشخصيات جميعها",
"Bulk select all characters": "تحديد كافة الشخصيات بالجملة",
"Bulk delete characters": "حذف الشخصيات جميعها",
"popup_text_save": "يحفظ",
"popup_text_yes": "نعم",
"popup_text_no": "لا",
"popup_text_cancel": "يلغي",
"popup_text_import": "يستورد",
"popup-button-save": "يحفظ",
"popup-button-yes": "نعم",
"popup-button-no": "لا",
"popup-button-cancel": "يلغي",
"popup-button-import": "يستورد",
"Advanced Defininitions": "تعريفات متقدمة",
"Prompt Overrides": "التجاوزات السريعة",
"(For Chat Completion and Instruct Mode)": "(لاستكمال الدردشة ووضع التعليمات)",
@@ -1005,8 +1005,8 @@
"Character Exclusion": "استبعاد الشخصيات",
"-- Characters not found --": "-- الشخصيات غير موجودة --",
"Inclusion Group": "مجموعة الإدراج",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.\r\rDocumentation: World Info - Inclusion Group": "تضمن مجموعات التضمين تنشيط إدخال واحد فقط من المجموعة في المرة الواحدة، إذا تم تشغيل عدة إدخالات.\rيدعم مجموعات متعددة مفصولة بفواصل.\r\rالتوثيق: معلومات العالم - مجموعة الإدماج",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.\rIf multiple are prioritized, the one with the highest 'Order' is chosen.": "إعطاء الأولوية لهذا الإدخال: عند تحديده، يتم إعطاء الأولوية لهذا الإدخال من بين جميع التحديدات.\rإذا تم تحديد الأولوية لأكثر من مجموعة، فسيتم اختيار \"الترتيب\" الأعلى.",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.Documentation: World Info - Inclusion Group": "تضمن مجموعات التضمين تنشيط إدخال واحد فقط من المجموعة في المرة الواحدة، إذا تم تشغيل عدة إدخالات.\rيدعم مجموعات متعددة مفصولة بفواصل.\r\rالتوثيق: معلومات العالم - مجموعة الإدماج",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.If multiple are prioritized, the one with the highest 'Order' is chosen.": "إعطاء الأولوية لهذا الإدخال: عند تحديده، يتم إعطاء الأولوية لهذا الإدخال من بين جميع التحديدات.\rإذا تم تحديد الأولوية لأكثر من مجموعة، فسيتم اختيار \"الترتيب\" الأعلى.",
"Only one entry with the same label will be activated": "سيتم تنشيط مدخل واحد فقط بنفس العلامة",
"A relative likelihood of entry activation within the group": "احتمال نسبي لتفعيل الدخول داخل المجموعة",
"Group Weight": "وزن المجموعة",
@@ -1436,7 +1436,7 @@
"Reset Code:": "إعادة تعيين الرمز:",
"Want to update?": "هل ترغب في التحديث؟",
"How to start chatting?": "كيف تبدأ في المحادثة؟",
"Click ": "انقر",
"Click _space": "انقر",
"and select a": "وحدد أ",
"Chat API": "واجهة برمجة تطبيقات الدردشة",
"and pick a character.": "واختر شخصية.",

View File

@@ -853,7 +853,7 @@
"Join character cards (include muted)": "Charakterkarten beitreten (auch stummgeschaltet)",
"Inserted before each part of the joined fields.": "Wird vor jedem Teil der verbundenen Felder eingefügt.",
"Join Prefix": "Präfix beitreten",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.\rThis means that in the story string for example all character descriptions will be joined to one big text.\rIf you want those fields to be separated, you can define a prefix or suffix here.\r\rThis value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "Wenn „Charakterkarten zusammenfügen“ ausgewählt ist, werden alle entsprechenden Felder der Charaktere zusammengefügt. Das bedeutet, dass im Story-String beispielsweise alle Charakterbeschreibungen zu einem großen Text zusammengefügt werden. Wenn Sie möchten, dass diese Felder getrennt werden, können Sie hier ein Präfix oder Suffix definieren. Dieser Wert unterstützt normale Makros und ersetzt außerdem {{char}} durch den Namen des entsprechenden Charakters und <FIELDNAME> durch den Namen des Teils (z. B.: Beschreibung, Persönlichkeit, Szenario usw.).",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.This means that in the story string for example all character descriptions will be joined to one big text.If you want those fields to be separated, you can define a prefix or suffix here.This value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "Wenn „Charakterkarten zusammenfügen“ ausgewählt ist, werden alle entsprechenden Felder der Charaktere zusammengefügt. Das bedeutet, dass im Story-String beispielsweise alle Charakterbeschreibungen zu einem großen Text zusammengefügt werden. Wenn Sie möchten, dass diese Felder getrennt werden, können Sie hier ein Präfix oder Suffix definieren. Dieser Wert unterstützt normale Makros und ersetzt außerdem {{char}} durch den Namen des entsprechenden Charakters und <FIELDNAME> durch den Namen des Teils (z. B.: Beschreibung, Persönlichkeit, Szenario usw.).",
"Inserted after each part of the joined fields.": "Wird nach jedem Teil der verbundenen Felder eingefügt.",
"Join Suffix": "Join-Suffix",
"Set a group chat scenario": "Setze ein Gruppenchat-Szenario",
@@ -885,11 +885,11 @@
"Bulk_edit_characters": "Massenbearbeitung von Charakteren",
"Bulk select all characters": "Massenauswahl aller Zeichen",
"Bulk delete characters": "Massenlöschung von Charakteren",
"popup_text_save": "Speichern",
"popup_text_yes": "Ja",
"popup_text_no": "NEIN",
"popup_text_cancel": "Stornieren",
"popup_text_import": "Importieren",
"popup-button-save": "Speichern",
"popup-button-yes": "Ja",
"popup-button-no": "NEIN",
"popup-button-cancel": "Stornieren",
"popup-button-import": "Importieren",
"Advanced Defininitions": "Erweiterte Definitionen",
"Prompt Overrides": "Eingabeaufforderungsüberschreibungen",
"(For Chat Completion and Instruct Mode)": "(Für Chat-Abschluss und Anweisungsmodus)",
@@ -1005,8 +1005,8 @@
"Character Exclusion": "Charakterausschluss",
"-- Characters not found --": "-- Charaktere nicht gefunden --",
"Inclusion Group": "Einschlussgruppe",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.\r\rDocumentation: World Info - Inclusion Group": "Inklusionsgruppen stellen sicher, dass immer nur ein Eintrag aus einer Gruppe aktiviert wird, wenn mehrere ausgelöst werden. Unterstützt mehrere durch Kommas getrennte Gruppen. Dokumentation: World Info - Inclusion Group",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.\rIf multiple are prioritized, the one with the highest 'Order' is chosen.": "Diesen Eintrag priorisieren: Wenn diese Option aktiviert ist, wird dieser Eintrag aus allen Auswahlen priorisiert. Wenn mehrere priorisiert sind, wird der Eintrag mit der höchsten „Reihenfolge“ ausgewählt.",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.Documentation: World Info - Inclusion Group": "Inklusionsgruppen stellen sicher, dass immer nur ein Eintrag aus einer Gruppe aktiviert wird, wenn mehrere ausgelöst werden. Unterstützt mehrere durch Kommas getrennte Gruppen. Dokumentation: World Info - Inclusion Group",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.If multiple are prioritized, the one with the highest 'Order' is chosen.": "Diesen Eintrag priorisieren: Wenn diese Option aktiviert ist, wird dieser Eintrag aus allen Auswahlen priorisiert. Wenn mehrere priorisiert sind, wird der Eintrag mit der höchsten „Reihenfolge“ ausgewählt.",
"Only one entry with the same label will be activated": "Nur ein Eintrag mit demselben Label wird aktiviert",
"A relative likelihood of entry activation within the group": "Eine relative Wahrscheinlichkeit der Eintrittsaktivierung innerhalb der Gruppe",
"Group Weight": "Gruppengewicht",
@@ -1436,7 +1436,7 @@
"Reset Code:": "Code zurücksetzen:",
"Want to update?": "Möchten Sie aktualisieren?",
"How to start chatting?": "Wie fange ich an zu chatten?",
"Click ": "Klicken",
"Click _space": "Klicken",
"and select a": "und wähle eine",
"Chat API": "Chat-API",
"and pick a character.": "und wähle einen Charakter aus.",

View File

@@ -853,7 +853,7 @@
"Join character cards (include muted)": "Unirse a tarjetas de personajes (incluidas silenciadas)",
"Inserted before each part of the joined fields.": "Insertado antes de cada parte de los campos unidos.",
"Join Prefix": "Unirse al prefijo",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.\rThis means that in the story string for example all character descriptions will be joined to one big text.\rIf you want those fields to be separated, you can define a prefix or suffix here.\r\rThis value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "Cuando se selecciona 'Unir tarjetas de personajes', todos los campos respectivos de los personajes se unen.\rEsto significa que en la cadena de la historia, por ejemplo, todas las descripciones de los personajes se unirán en un texto grande.\rSi desea que esos campos estén separados, puede definir un prefijo o sufijo aquí.\r\rEste valor admite macros normales y también reemplazará {{char}} con el nombre del personaje relevante y <FIELDNAME> con el nombre de la parte (por ejemplo: descripción, personalidad, escenario, etc.)",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.This means that in the story string for example all character descriptions will be joined to one big text.If you want those fields to be separated, you can define a prefix or suffix here.This value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "Cuando se selecciona 'Unir tarjetas de personajes', todos los campos respectivos de los personajes se unen.\rEsto significa que en la cadena de la historia, por ejemplo, todas las descripciones de los personajes se unirán en un texto grande.\rSi desea que esos campos estén separados, puede definir un prefijo o sufijo aquí.\r\rEste valor admite macros normales y también reemplazará {{char}} con el nombre del personaje relevante y <FIELDNAME> con el nombre de la parte (por ejemplo: descripción, personalidad, escenario, etc.)",
"Inserted after each part of the joined fields.": "Insertado después de cada parte de los campos unidos.",
"Join Suffix": "Unirse al sufijo",
"Set a group chat scenario": "Establecer un escenario de chat grupal",
@@ -885,11 +885,11 @@
"Bulk_edit_characters": "Editar personajes masivamente",
"Bulk select all characters": "Seleccionar de forma masiva todos los personajes",
"Bulk delete characters": "Eliminar personajes masivamente",
"popup_text_save": "Ahorrar",
"popup_text_yes": "Sí",
"popup_text_no": "No",
"popup_text_cancel": "Cancelar",
"popup_text_import": "Importar",
"popup-button-save": "Ahorrar",
"popup-button-yes": "Sí",
"popup-button-no": "No",
"popup-button-cancel": "Cancelar",
"popup-button-import": "Importar",
"Advanced Defininitions": "Definiciones avanzadas",
"Prompt Overrides": "Anulaciones rápidas",
"(For Chat Completion and Instruct Mode)": "(Para completar el chat y el modo de instrucción)",
@@ -1005,8 +1005,8 @@
"Character Exclusion": "Exclusión de Personaje",
"-- Characters not found --": "-- Personajes no encontrados --",
"Inclusion Group": "Grupo de Inclusión",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.\r\rDocumentation: World Info - Inclusion Group": "Los grupos de inclusión garantizan que solo se active una entrada de un grupo a la vez, si se activan varias.\rAdmite múltiples grupos separados por comas.\r\rDocumentación: World Info - Grupo de Inclusión",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.\rIf multiple are prioritized, the one with the highest 'Order' is chosen.": "Priorizar esta entrada: cuando está marcada, esta entrada tiene prioridad entre todas las selecciones.\rSi se priorizan varios, se elige el que tenga el 'Orden' más alto.",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.Documentation: World Info - Inclusion Group": "Los grupos de inclusión garantizan que solo se active una entrada de un grupo a la vez, si se activan varias.\rAdmite múltiples grupos separados por comas.\r\rDocumentación: World Info - Grupo de Inclusión",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.If multiple are prioritized, the one with the highest 'Order' is chosen.": "Priorizar esta entrada: cuando está marcada, esta entrada tiene prioridad entre todas las selecciones.\rSi se priorizan varios, se elige el que tenga el 'Orden' más alto.",
"Only one entry with the same label will be activated": "Solo se activará una entrada con la misma etiqueta",
"A relative likelihood of entry activation within the group": "Una probabilidad relativa de activación de entrada dentro del grupo.",
"Group Weight": "Peso del grupo",
@@ -1436,7 +1436,7 @@
"Reset Code:": "Restablecer Código:",
"Want to update?": "¿Quieres actualizar?",
"How to start chatting?": "¿Cómo empezar a chatear?",
"Click ": "Hacer clic",
"Click _space": "Hacer clic",
"and select a": "y selecciona una",
"Chat API": " API de chat",
"and pick a character.": "y elige un personaje.",

View File

@@ -853,7 +853,7 @@
"Join character cards (include muted)": "Rejoignez les cartes de personnage (y compris en sourdine)",
"Inserted before each part of the joined fields.": "Inséré avant chaque partie des champs joints.",
"Join Prefix": "Rejoindre le préfixe",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.\rThis means that in the story string for example all character descriptions will be joined to one big text.\rIf you want those fields to be separated, you can define a prefix or suffix here.\r\rThis value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "Lorsque « Rejoindre les cartes de personnage » est sélectionné, tous les champs respectifs des personnages sont réunis.\rCela signifie que dans la chaîne d'histoire, par exemple, toutes les descriptions des personnages seront réunies en un seul grand texte.\rSi vous souhaitez que ces champs soient séparés, vous pouvez définir ici un préfixe ou un suffixe.\r\rCette valeur prend en charge les macros normales et remplacera également {{char}} par le nom du caractère concerné et <FIELDNAME> par le nom de la pièce (par exemple : description, personnalité, scénario, etc.)",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.This means that in the story string for example all character descriptions will be joined to one big text.If you want those fields to be separated, you can define a prefix or suffix here.This value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "Lorsque « Rejoindre les cartes de personnage » est sélectionné, tous les champs respectifs des personnages sont réunis.\rCela signifie que dans la chaîne d'histoire, par exemple, toutes les descriptions des personnages seront réunies en un seul grand texte.\rSi vous souhaitez que ces champs soient séparés, vous pouvez définir ici un préfixe ou un suffixe.\r\rCette valeur prend en charge les macros normales et remplacera également {{char}} par le nom du caractère concerné et <FIELDNAME> par le nom de la pièce (par exemple : description, personnalité, scénario, etc.)",
"Inserted after each part of the joined fields.": "Inséré après chaque partie des champs joints.",
"Join Suffix": "Rejoindre le suffixe",
"Set a group chat scenario": "Définir un scénario de discussion de groupe",
@@ -885,11 +885,11 @@
"Bulk_edit_characters": "Édition en masse des personnages",
"Bulk select all characters": "Sélection groupée de tous les caractères",
"Bulk delete characters": "Suppression en masse des personnages",
"popup_text_save": "Sauvegarder",
"popup_text_yes": "Oui",
"popup_text_no": "Non",
"popup_text_cancel": "Annuler",
"popup_text_import": "Importer",
"popup-button-save": "Sauvegarder",
"popup-button-yes": "Oui",
"popup-button-no": "Non",
"popup-button-cancel": "Annuler",
"popup-button-import": "Importer",
"Advanced Defininitions": "Définitions avancées",
"Prompt Overrides": "Remplacements d'invite",
"(For Chat Completion and Instruct Mode)": "(Pour l'achèvement du chat et le mode instruction)",
@@ -1005,8 +1005,8 @@
"Character Exclusion": "Exclusion de personnage",
"-- Characters not found --": "-- Personnages non trouvés --",
"Inclusion Group": "Groupe d'inclusion",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.\r\rDocumentation: World Info - Inclusion Group": "Les groupes d'inclusion garantissent qu'une seule entrée d'un groupe est activée à la fois, si plusieurs sont déclenchées.\rPrend en charge plusieurs groupes séparés par des virgules.\r\rDocumentation : Info Monde - Groupe Inclusion",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.\rIf multiple are prioritized, the one with the highest 'Order' is chosen.": "Prioriser cette entrée : Lorsque cette case est cochée, cette entrée est prioritaire parmi toutes les sélections.\rSi plusieurs sont prioritaires, celui avec l'ordre le plus élevé est choisi.",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.Documentation: World Info - Inclusion Group": "Les groupes d'inclusion garantissent qu'une seule entrée d'un groupe est activée à la fois, si plusieurs sont déclenchées.\rPrend en charge plusieurs groupes séparés par des virgules.\r\rDocumentation : Info Monde - Groupe Inclusion",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.If multiple are prioritized, the one with the highest 'Order' is chosen.": "Prioriser cette entrée : Lorsque cette case est cochée, cette entrée est prioritaire parmi toutes les sélections.\rSi plusieurs sont prioritaires, celui avec l'ordre le plus élevé est choisi.",
"Only one entry with the same label will be activated": "Seule une entrée avec la même étiquette sera activée",
"A relative likelihood of entry activation within the group": "Une probabilité relative dactivation dentrée au sein du groupe",
"Group Weight": "Poids du groupe",
@@ -1436,7 +1436,7 @@
"Reset Code:": "Code de réinitialisation :",
"Want to update?": "Envie de mettre à jour ?",
"How to start chatting?": "Comment commencer à discuter ?",
"Click ": "Cliquez sur",
"Click _space": "Cliquez sur",
"and select a": "et sélectionnez un",
"Chat API": "API de chat",
"and pick a character.": "et choisissez un personnage.",

View File

@@ -853,7 +853,7 @@
"Join character cards (include muted)": "Tengdu persónuspjöld (innifalin þögguð)",
"Inserted before each part of the joined fields.": "Sett inn fyrir hvern hluta sameinaðra reita.",
"Join Prefix": "Skráðu þig í Forskeyti",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.\rThis means that in the story string for example all character descriptions will be joined to one big text.\rIf you want those fields to be separated, you can define a prefix or suffix here.\r\rThis value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "Þegar 'Tengdu persónukort' er valið er verið að tengja saman alla viðkomandi reitir persónanna.\rÞetta þýðir að í sögustrengnum verða til dæmis allar persónulýsingar sameinaðar í einn stóran texta.\rEf þú vilt að þessir reitir séu aðskildir geturðu skilgreint forskeyti eða viðskeyti hér.\r\rÞetta gildi styður venjulega fjölva og mun einnig skipta út {{char}} fyrir viðkomandi bleikjunafni og <FIELDNAME> fyrir nafn hlutans (t.d.: lýsing, persónuleiki, atburðarás osfrv.)",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.This means that in the story string for example all character descriptions will be joined to one big text.If you want those fields to be separated, you can define a prefix or suffix here.This value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "Þegar 'Tengdu persónukort' er valið er verið að tengja saman alla viðkomandi reitir persónanna.\rÞetta þýðir að í sögustrengnum verða til dæmis allar persónulýsingar sameinaðar í einn stóran texta.\rEf þú vilt að þessir reitir séu aðskildir geturðu skilgreint forskeyti eða viðskeyti hér.\r\rÞetta gildi styður venjulega fjölva og mun einnig skipta út {{char}} fyrir viðkomandi bleikjunafni og <FIELDNAME> fyrir nafn hlutans (t.d.: lýsing, persónuleiki, atburðarás osfrv.)",
"Inserted after each part of the joined fields.": "Sett inn á eftir hverjum hluta sameinaðra reita.",
"Join Suffix": "Skráðu þig í viðskeyti",
"Set a group chat scenario": "Setja hópspjallsskipulag",
@@ -885,11 +885,11 @@
"Bulk_edit_characters": "Breyta mörgum persónum í einu",
"Bulk select all characters": "Velja alla stafi í magni",
"Bulk delete characters": "Eyða mörgum persónum í einu",
"popup_text_save": "Vista",
"popup_text_yes": "Já",
"popup_text_no": "Nei",
"popup_text_cancel": "Hætta við",
"popup_text_import": "Flytja inn",
"popup-button-save": "Vista",
"popup-button-yes": "Já",
"popup-button-no": "Nei",
"popup-button-cancel": "Hætta við",
"popup-button-import": "Flytja inn",
"Advanced Defininitions": "Ítarleg skilgreiningar",
"Prompt Overrides": "Hnekkja hvetjandi",
"(For Chat Completion and Instruct Mode)": "(Til að ljúka spjalli og leiðbeiningarham)",
@@ -1005,8 +1005,8 @@
"Character Exclusion": "Persónuúteslutningur",
"-- Characters not found --": "-- Persónur finnast ekki --",
"Inclusion Group": "Innifólgur Hópur",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.\r\rDocumentation: World Info - Inclusion Group": "Inntökuhópar tryggja að aðeins ein færsla úr hópi sé virkjuð í einu, ef margar eru ræstar.\rStyður marga hópa aðskilda með kommum.\r\rSkjöl: World Info - Inclusion Group",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.\rIf multiple are prioritized, the one with the highest 'Order' is chosen.": "Forgangsraða þessari færslu: Þegar hakað er við þá er þessari færslu forgangsraðað úr öllu vali.\rEf mörgum er forgangsraðað er sá sem hefur hæstu 'pöntun' valin.",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.Documentation: World Info - Inclusion Group": "Inntökuhópar tryggja að aðeins ein færsla úr hópi sé virkjuð í einu, ef margar eru ræstar.\rStyður marga hópa aðskilda með kommum.\r\rSkjöl: World Info - Inclusion Group",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.If multiple are prioritized, the one with the highest 'Order' is chosen.": "Forgangsraða þessari færslu: Þegar hakað er við þá er þessari færslu forgangsraðað úr öllu vali.\rEf mörgum er forgangsraðað er sá sem hefur hæstu 'pöntun' valin.",
"Only one entry with the same label will be activated": "Aðeins ein skrá með sömu merki verður virk",
"A relative likelihood of entry activation within the group": "Hlutfallslegar líkur á inngönguvirkjun innan hópsins",
"Group Weight": "Þyngd hópa",
@@ -1436,7 +1436,7 @@
"Reset Code:": "Endurstilla kóða:",
"Want to update?": "Viltu uppfæra?",
"How to start chatting?": "Hvernig á að byrja að spjalla?",
"Click ": "Smellur",
"Click _space": "Smellur",
"and select a": "og veldu",
"Chat API": "Spjall API",
"and pick a character.": "og veldu karakter.",

View File

@@ -853,7 +853,7 @@
"Join character cards (include muted)": "Unisci le carte dei personaggi (includi disattivate)",
"Inserted before each part of the joined fields.": "Inserito prima di ogni parte dei campi uniti.",
"Join Prefix": "Unisciti al prefisso",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.\rThis means that in the story string for example all character descriptions will be joined to one big text.\rIf you want those fields to be separated, you can define a prefix or suffix here.\r\rThis value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "Quando viene selezionato \"Unisci carte personaggio\", tutti i rispettivi campi dei personaggi verranno uniti.\rCiò significa che nella stringa della storia, ad esempio, tutte le descrizioni dei personaggi verranno unite in un unico grande testo.\rSe desideri che questi campi siano separati, puoi definire un prefisso o un suffisso qui.\r\rQuesto valore supporta le macro normali e sostituirà anche {{char}} con il nome del carattere rilevante e <FIELDNAME> con il nome della parte (es.: descrizione, personalità, scenario, ecc.)",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.This means that in the story string for example all character descriptions will be joined to one big text.If you want those fields to be separated, you can define a prefix or suffix here.This value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "Quando viene selezionato \"Unisci carte personaggio\", tutti i rispettivi campi dei personaggi verranno uniti.\rCiò significa che nella stringa della storia, ad esempio, tutte le descrizioni dei personaggi verranno unite in un unico grande testo.\rSe desideri che questi campi siano separati, puoi definire un prefisso o un suffisso qui.\r\rQuesto valore supporta le macro normali e sostituirà anche {{char}} con il nome del carattere rilevante e <FIELDNAME> con il nome della parte (es.: descrizione, personalità, scenario, ecc.)",
"Inserted after each part of the joined fields.": "Inserito dopo ogni parte dei campi uniti.",
"Join Suffix": "Unisciti al suffisso",
"Set a group chat scenario": "Imposta uno scenario di chat di gruppo",
@@ -885,11 +885,11 @@
"Bulk_edit_characters": "Modifica personaggi in blocco",
"Bulk select all characters": "Seleziona in blocco tutti i personaggi",
"Bulk delete characters": "Elimina personaggi in blocco",
"popup_text_save": "Salva",
"popup_text_yes": "SÌ",
"popup_text_no": "NO",
"popup_text_cancel": "Annulla",
"popup_text_import": "Importare",
"popup-button-save": "Salva",
"popup-button-yes": "SÌ",
"popup-button-no": "NO",
"popup-button-cancel": "Annulla",
"popup-button-import": "Importare",
"Advanced Defininitions": "Definizioni avanzate",
"Prompt Overrides": "Sostituzioni richieste",
"(For Chat Completion and Instruct Mode)": "(Per il completamento della chat e la modalità istruzione)",
@@ -1005,8 +1005,8 @@
"Character Exclusion": "Esclusione Personaggio",
"-- Characters not found --": "-- Personaggi non trovati --",
"Inclusion Group": "Gruppo di Inclusione",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.\r\rDocumentation: World Info - Inclusion Group": "I gruppi di inclusione garantiscono che venga attivata solo una voce di un gruppo alla volta, se ne vengono attivate più.\rSupporta più gruppi separati da virgole.\r\rDocumentazione: World Info Gruppo Inclusione",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.\rIf multiple are prioritized, the one with the highest 'Order' is chosen.": "Dai priorità a questa voce: se selezionata, questa voce ha la priorità tra tutte le selezioni. Se vengono date priorità a più voci, viene scelta quella con l'\"Ordine\" più alto.",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.Documentation: World Info - Inclusion Group": "I gruppi di inclusione garantiscono che venga attivata solo una voce di un gruppo alla volta, se ne vengono attivate più.\rSupporta più gruppi separati da virgole.\r\rDocumentazione: World Info Gruppo Inclusione",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.If multiple are prioritized, the one with the highest 'Order' is chosen.": "Dai priorità a questa voce: se selezionata, questa voce ha la priorità tra tutte le selezioni. Se vengono date priorità a più voci, viene scelta quella con l'\"Ordine\" più alto.",
"Only one entry with the same label will be activated": "Sarà attivato solo un unico ingresso con lo stesso etichetta",
"A relative likelihood of entry activation within the group": "Una probabilità relativa di attivazione dell'ingresso all'interno del gruppo",
"Group Weight": "Peso del gruppo",
@@ -1436,7 +1436,7 @@
"Reset Code:": "Codice di ripristino:",
"Want to update?": "Vuoi aggiornare?",
"How to start chatting?": "Come iniziare a chattare?",
"Click ": "Clic",
"Click _space": "Clic",
"and select a": " e seleziona un",
"Chat API": " API di chat",
"and pick a character.": "e scegli un personaggio.",

View File

@@ -853,7 +853,7 @@
"Join character cards (include muted)": "キャラクターカードに参加(ミュート含む)",
"Inserted before each part of the joined fields.": "結合されたフィールドの各部分の前に挿入されます。",
"Join Prefix": "結合プレフィックス",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.\rThis means that in the story string for example all character descriptions will be joined to one big text.\rIf you want those fields to be separated, you can define a prefix or suffix here.\r\rThis value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "「キャラクターカードを結合」を選択すると、キャラクターのそれぞれのフィールドがすべて結合されます。つまり、たとえばストーリー文字列では、すべてのキャラクターの説明が 1 つの大きなテキストに結合されます。これらのフィールドを分離したい場合は、ここでプレフィックスまたはサフィックスを定義できます。この値は通常のマクロをサポートし、{{char}} を関連するキャラクターの名前に置き換え、<FIELDNAME> をパーツの名前 (例: 説明、性格、シナリオなど) に置き換えます。",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.This means that in the story string for example all character descriptions will be joined to one big text.If you want those fields to be separated, you can define a prefix or suffix here.This value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "「キャラクターカードを結合」を選択すると、キャラクターのそれぞれのフィールドがすべて結合されます。つまり、たとえばストーリー文字列では、すべてのキャラクターの説明が 1 つの大きなテキストに結合されます。これらのフィールドを分離したい場合は、ここでプレフィックスまたはサフィックスを定義できます。この値は通常のマクロをサポートし、{{char}} を関連するキャラクターの名前に置き換え、<FIELDNAME> をパーツの名前 (例: 説明、性格、シナリオなど) に置き換えます。",
"Inserted after each part of the joined fields.": "結合されたフィールドの各部分の後に挿入されます。",
"Join Suffix": "結合サフィックス",
"Set a group chat scenario": "グループチャットのシナリオを設定",
@@ -885,11 +885,11 @@
"Bulk_edit_characters": "キャラクターを一括編集",
"Bulk select all characters": "すべての文字を一括選択",
"Bulk delete characters": "キャラクターを一括削除",
"popup_text_save": "保存",
"popup_text_yes": "はい",
"popup_text_no": "いいえ",
"popup_text_cancel": "キャンセル",
"popup_text_import": "輸入",
"popup-button-save": "保存",
"popup-button-yes": "はい",
"popup-button-no": "いいえ",
"popup-button-cancel": "キャンセル",
"popup-button-import": "輸入",
"Advanced Defininitions": "高度な定義",
"Prompt Overrides": "プロンプトのオーバーライド",
"(For Chat Completion and Instruct Mode)": "(チャット補完と指示モード用)",
@@ -1005,8 +1005,8 @@
"Character Exclusion": "キャラクターの除外",
"-- Characters not found --": "-- キャラクターが見つかりません --",
"Inclusion Group": "含蓋グループ",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.\r\rDocumentation: World Info - Inclusion Group": "包含グループにより、複数のエントリがトリガーされた場合、一度に 1 つのグループから 1 つのエントリのみがアクティブ化されます。カンマで区切られた複数のグループをサポートします。ドキュメント: World Info - 包含グループ",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.\rIf multiple are prioritized, the one with the highest 'Order' is chosen.": "このエントリを優先する: チェックすると、このエントリがすべての選択項目の中で優先されます。複数のエントリを優先する場合は、「順序」が最も高いエントリが選択されます。",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.Documentation: World Info - Inclusion Group": "包含グループにより、複数のエントリがトリガーされた場合、一度に 1 つのグループから 1 つのエントリのみがアクティブ化されます。カンマで区切られた複数のグループをサポートします。ドキュメント: World Info - 包含グループ",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.If multiple are prioritized, the one with the highest 'Order' is chosen.": "このエントリを優先する: チェックすると、このエントリがすべての選択項目の中で優先されます。複数のエントリを優先する場合は、「順序」が最も高いエントリが選択されます。",
"Only one entry with the same label will be activated": "同じラベルのエントリが1つだけ有効になります",
"A relative likelihood of entry activation within the group": "グループ内でのエントリー活性化の相対的な可能性",
"Group Weight": "グループの重み",
@@ -1436,7 +1436,7 @@
"Reset Code:": "リセットコード:",
"Want to update?": "更新しますか?",
"How to start chatting?": "チャットを開始する方法は?",
"Click ": "クリック",
"Click _space": "クリック",
"and select a": "そして選択します ",
"Chat API": "チャットAPI",
"and pick a character.": "キャラクターを選択します。",

View File

@@ -853,7 +853,7 @@
"Join character cards (include muted)": "캐릭터 카드 가입(뮤트 포함)",
"Inserted before each part of the joined fields.": "결합된 필드의 각 부분 앞에 삽입됩니다.",
"Join Prefix": "접두사 가입",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.\rThis means that in the story string for example all character descriptions will be joined to one big text.\rIf you want those fields to be separated, you can define a prefix or suffix here.\r\rThis value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "'캐릭터 카드 합치기'를 선택하면 캐릭터의 모든 해당 필드가 합쳐집니다. 즉, 스토리 문자열에서 예를 들어 모든 캐릭터 설명이 하나의 큰 텍스트로 합쳐집니다. 이러한 필드를 분리하려면 여기에서 접두사나 접미사를 정의할 수 있습니다. 이 값은 일반 매크로를 지원하고 {{char}}를 해당 캐릭터의 이름으로, <FIELDNAME>을 해당 부분의 이름(예: 설명, 성격, 시나리오 등)으로 대체합니다.",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.This means that in the story string for example all character descriptions will be joined to one big text.If you want those fields to be separated, you can define a prefix or suffix here.This value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "'캐릭터 카드 합치기'를 선택하면 캐릭터의 모든 해당 필드가 합쳐집니다. 즉, 스토리 문자열에서 예를 들어 모든 캐릭터 설명이 하나의 큰 텍스트로 합쳐집니다. 이러한 필드를 분리하려면 여기에서 접두사나 접미사를 정의할 수 있습니다. 이 값은 일반 매크로를 지원하고 {{char}}를 해당 캐릭터의 이름으로, <FIELDNAME>을 해당 부분의 이름(예: 설명, 성격, 시나리오 등)으로 대체합니다.",
"Inserted after each part of the joined fields.": "결합된 필드의 각 부분 뒤에 삽입됩니다.",
"Join Suffix": "접미사 가입",
"Set a group chat scenario": "그룹 채팅 시나리오 설정",
@@ -885,11 +885,11 @@
"Bulk_edit_characters": "대량 캐릭터 편집",
"Bulk select all characters": "모든 문자 일괄 선택",
"Bulk delete characters": "대량 캐릭터 삭제",
"popup_text_save": "구하다",
"popup_text_yes": "예",
"popup_text_no": "아니요",
"popup_text_cancel": "취소",
"popup_text_import": "수입",
"popup-button-save": "구하다",
"popup-button-yes": "예",
"popup-button-no": "아니요",
"popup-button-cancel": "취소",
"popup-button-import": "수입",
"Advanced Defininitions": "고급 정의",
"Prompt Overrides": "프롬프트 무시",
"(For Chat Completion and Instruct Mode)": "(채팅 완료 및 지시 모드의 경우)",
@@ -1005,8 +1005,8 @@
"Character Exclusion": "캐릭터 제외",
"-- Characters not found --": "-- 캐릭터를 찾을 수 없음 --",
"Inclusion Group": "포함 그룹",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.\r\rDocumentation: World Info - Inclusion Group": "포함 그룹은 여러 항목이 트리거되는 경우 그룹에서 한 번에 하나의 항목만 활성화되도록 합니다.\r쉼표로 구분된 여러 그룹을 지원합니다.\r\r문서: 세계 정보 - 포함 그룹",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.\rIf multiple are prioritized, the one with the highest 'Order' is chosen.": "이 항목 우선순위 지정: 선택하면 모든 선택 항목 중에서 이 항목의 우선순위가 지정됩니다.\r여러 항목의 우선순위가 높은 경우 '순서'가 가장 높은 항목이 선택됩니다.",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.Documentation: World Info - Inclusion Group": "포함 그룹은 여러 항목이 트리거되는 경우 그룹에서 한 번에 하나의 항목만 활성화되도록 합니다.\r쉼표로 구분된 여러 그룹을 지원합니다.\r\r문서: 세계 정보 - 포함 그룹",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.If multiple are prioritized, the one with the highest 'Order' is chosen.": "이 항목 우선순위 지정: 선택하면 모든 선택 항목 중에서 이 항목의 우선순위가 지정됩니다.\r여러 항목의 우선순위가 높은 경우 '순서'가 가장 높은 항목이 선택됩니다.",
"Only one entry with the same label will be activated": "동일한 라벨을 가진 항목은 하나만 활성화됩니다",
"A relative likelihood of entry activation within the group": "그룹 내 항목 활성화의 상대적 가능성",
"Group Weight": "그룹 가중치",
@@ -1436,7 +1436,7 @@
"Reset Code:": "재설정 코드:",
"Want to update?": "업데이트 하시겠습니까?",
"How to start chatting?": "채팅을 시작하는 방법은?",
"Click ": "딸깍 하는 소리",
"Click _space": "딸깍 하는 소리",
"and select a": "그리고 선택하십시오",
"Chat API": "채팅 API",
"and pick a character.": "그리고 캐릭터를 선택하세요.",

View File

@@ -853,7 +853,7 @@
"Join character cards (include muted)": "Sluit je aan bij karakterkaarten (inclusief gedempt)",
"Inserted before each part of the joined fields.": "Ingevoegd vóór elk deel van de samengevoegde velden.",
"Join Prefix": "Sluit je aan bij Voorvoegsel",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.\rThis means that in the story string for example all character descriptions will be joined to one big text.\rIf you want those fields to be separated, you can define a prefix or suffix here.\r\rThis value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "Wanneer 'Verbind karakterkaarten' is geselecteerd, worden alle respectieve velden van de karakters samengevoegd.\rDit betekent dat in de verhaalreeks bijvoorbeeld alle karakterbeschrijvingen worden samengevoegd tot één grote tekst.\rAls u wilt dat deze velden gescheiden worden, kunt u hier een voor- of achtervoegsel definiëren.\r\rDeze waarde ondersteunt normale macro's en zal ook {{char}} vervangen door de relevante naam van het char en <FIELDNAME> door de naam van het onderdeel (bijvoorbeeld: beschrijving, persoonlijkheid, scenario, etc.)",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.This means that in the story string for example all character descriptions will be joined to one big text.If you want those fields to be separated, you can define a prefix or suffix here.This value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "Wanneer 'Verbind karakterkaarten' is geselecteerd, worden alle respectieve velden van de karakters samengevoegd.\rDit betekent dat in de verhaalreeks bijvoorbeeld alle karakterbeschrijvingen worden samengevoegd tot één grote tekst.\rAls u wilt dat deze velden gescheiden worden, kunt u hier een voor- of achtervoegsel definiëren.\r\rDeze waarde ondersteunt normale macro's en zal ook {{char}} vervangen door de relevante naam van het char en <FIELDNAME> door de naam van het onderdeel (bijvoorbeeld: beschrijving, persoonlijkheid, scenario, etc.)",
"Inserted after each part of the joined fields.": "Ingevoegd na elk deel van de samengevoegde velden.",
"Join Suffix": "Sluit je aan bij het achtervoegsel",
"Set a group chat scenario": "Stel een scenario voor groepschat in",
@@ -885,11 +885,11 @@
"Bulk_edit_characters": "Massaal bewerken personages",
"Bulk select all characters": "Selecteer alle tekens in bulk",
"Bulk delete characters": "Massaal verwijderen personages",
"popup_text_save": "Redden",
"popup_text_yes": "Ja",
"popup_text_no": "Nee",
"popup_text_cancel": "Annuleren",
"popup_text_import": "Importeren",
"popup-button-save": "Redden",
"popup-button-yes": "Ja",
"popup-button-no": "Nee",
"popup-button-cancel": "Annuleren",
"popup-button-import": "Importeren",
"Advanced Defininitions": "Geavanceerde definities",
"Prompt Overrides": "Prompt-overschrijvingen",
"(For Chat Completion and Instruct Mode)": "(Voor voltooiing van chat en instructiemodus)",
@@ -1005,8 +1005,8 @@
"Character Exclusion": "Personage uitsluiting",
"-- Characters not found --": "-- Personages niet gevonden --",
"Inclusion Group": "Insluitingsgroep",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.\r\rDocumentation: World Info - Inclusion Group": "Inclusiegroepen zorgen ervoor dat slechts één item uit een groep tegelijk wordt geactiveerd, als er meerdere worden geactiveerd.\rOndersteunt meerdere door komma's gescheiden groepen.\r\rDocumentatie: World Info - Inclusion Group",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.\rIf multiple are prioritized, the one with the highest 'Order' is chosen.": "Geef prioriteit aan deze invoer: Als dit selectievakje is aangevinkt, krijgt deze invoer prioriteit boven alle selecties. Als er meerdere items prioriteit hebben, wordt de invoer met de hoogste 'volgorde' gekozen.",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.Documentation: World Info - Inclusion Group": "Inclusiegroepen zorgen ervoor dat slechts één item uit een groep tegelijk wordt geactiveerd, als er meerdere worden geactiveerd.\rOndersteunt meerdere door komma's gescheiden groepen.\r\rDocumentatie: World Info - Inclusion Group",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.If multiple are prioritized, the one with the highest 'Order' is chosen.": "Geef prioriteit aan deze invoer: Als dit selectievakje is aangevinkt, krijgt deze invoer prioriteit boven alle selecties. Als er meerdere items prioriteit hebben, wordt de invoer met de hoogste 'volgorde' gekozen.",
"Only one entry with the same label will be activated": "Slechts één item met hetzelfde label zal worden geactiveerd",
"A relative likelihood of entry activation within the group": "Een relatieve waarschijnlijkheid van activatie binnen de groep",
"Group Weight": "Groepsgewicht",
@@ -1436,7 +1436,7 @@
"Reset Code:": "Reset code:",
"Want to update?": "Wil je SillyTavern updaten?",
"How to start chatting?": "Hoe begin je met chatten?",
"Click ": "Klik",
"Click _space": "Klik",
"and select a": " en selecteer een",
"Chat API": " Chat-API",
"and pick a character.": "en kies een personage.",

View File

@@ -853,7 +853,7 @@
"Join character cards (include muted)": "Junte-se a cartões de personagem (inclua silenciado)",
"Inserted before each part of the joined fields.": "Inserido antes de cada parte dos campos unidos.",
"Join Prefix": "Prefixo de adesão",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.\rThis means that in the story string for example all character descriptions will be joined to one big text.\rIf you want those fields to be separated, you can define a prefix or suffix here.\r\rThis value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "Quando 'Unir cartas de personagem' é selecionado, todos os respectivos campos dos personagens são unidos.\rIsto significa que na sequência da história, por exemplo, todas as descrições dos personagens serão unidas a um grande texto.\rSe quiser que esses campos sejam separados, você pode definir um prefixo ou sufixo aqui.\r\rEste valor suporta macros normais e também substituirá {{char}} pelo nome do char relevante e <FIELDNAME> pelo nome da parte (por exemplo: descrição, personalidade, cenário, etc.)",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.This means that in the story string for example all character descriptions will be joined to one big text.If you want those fields to be separated, you can define a prefix or suffix here.This value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "Quando 'Unir cartas de personagem' é selecionado, todos os respectivos campos dos personagens são unidos.\rIsto significa que na sequência da história, por exemplo, todas as descrições dos personagens serão unidas a um grande texto.\rSe quiser que esses campos sejam separados, você pode definir um prefixo ou sufixo aqui.\r\rEste valor suporta macros normais e também substituirá {{char}} pelo nome do char relevante e <FIELDNAME> pelo nome da parte (por exemplo: descrição, personalidade, cenário, etc.)",
"Inserted after each part of the joined fields.": "Inserido após cada parte dos campos unidos.",
"Join Suffix": "Junte-se ao sufixo",
"Set a group chat scenario": "Definir um cenário de bate-papo em grupo",
@@ -885,11 +885,11 @@
"Bulk_edit_characters": "Editar personagens em massa",
"Bulk select all characters": "Selecione em massa todos os caracteres",
"Bulk delete characters": "Excluir personagens em massa",
"popup_text_save": "Salvar",
"popup_text_yes": "Sim",
"popup_text_no": "Não",
"popup_text_cancel": "Cancelar",
"popup_text_import": "Importar",
"popup-button-save": "Salvar",
"popup-button-yes": "Sim",
"popup-button-no": "Não",
"popup-button-cancel": "Cancelar",
"popup-button-import": "Importar",
"Advanced Defininitions": "Definições Avançadas",
"Prompt Overrides": "Substituições de prompt",
"(For Chat Completion and Instruct Mode)": "(Para conclusão de bate-papo e modo de instrução)",
@@ -1005,8 +1005,8 @@
"Character Exclusion": "Exclusão de personagem",
"-- Characters not found --": "-- Personagens não encontrados --",
"Inclusion Group": "Grupo de inclusão",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.\r\rDocumentation: World Info - Inclusion Group": "Os Grupos de Inclusão garantem que apenas uma entrada de um grupo seja ativada por vez, se várias forem acionadas.\rSuporta vários grupos separados por vírgula.\r\rDocumentação: World Info - Grupo de Inclusão",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.\rIf multiple are prioritized, the one with the highest 'Order' is chosen.": "Priorizar esta entrada: quando marcada, esta entrada é priorizada entre todas as seleções.\rSe vários forem priorizados, aquele com a “Ordem” mais alta será escolhido.",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.Documentation: World Info - Inclusion Group": "Os Grupos de Inclusão garantem que apenas uma entrada de um grupo seja ativada por vez, se várias forem acionadas.\rSuporta vários grupos separados por vírgula.\r\rDocumentação: World Info - Grupo de Inclusão",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.If multiple are prioritized, the one with the highest 'Order' is chosen.": "Priorizar esta entrada: quando marcada, esta entrada é priorizada entre todas as seleções.\rSe vários forem priorizados, aquele com a “Ordem” mais alta será escolhido.",
"Only one entry with the same label will be activated": "Apenas uma entrada com o mesmo rótulo será ativada",
"A relative likelihood of entry activation within the group": "Uma probabilidade relativa de ativação de entrada dentro do grupo",
"Group Weight": "Peso do grupo",
@@ -1436,7 +1436,7 @@
"Reset Code:": "Reiniciar código:",
"Want to update?": "Quer atualizar?",
"How to start chatting?": "Como começar a conversar?",
"Click ": "Clique",
"Click _space": "Clique",
"and select a": "e selecione um",
"Chat API": "API de Chat",
"and pick a character.": "e escolha um personagem.",

File diff suppressed because it is too large Load Diff

View File

@@ -853,7 +853,7 @@
"Join character cards (include muted)": "Приєднатися до карток персонажів (включно з приглушеними)",
"Inserted before each part of the joined fields.": "Вставляється перед кожною частиною об’єднаних полів.",
"Join Prefix": "Префікс приєднання",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.\rThis means that in the story string for example all character descriptions will be joined to one big text.\rIf you want those fields to be separated, you can define a prefix or suffix here.\r\rThis value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "Якщо вибрано «Об’єднати картки персонажів», усі відповідні поля персонажів об’єднуються.\rЦе означає, що, наприклад, у рядку історії всі описи персонажів будуть об’єднані в один великий текст.\rЯкщо ви хочете, щоб ці поля були розділені, ви можете визначити тут префікс або суфікс.\r\rЦе значення підтримує звичайні макроси, а також замінює {{char}} на ім’я відповідного символу, а <FIELDNAME> — на назву частини (наприклад, опис, особистість, сценарій тощо).",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.This means that in the story string for example all character descriptions will be joined to one big text.If you want those fields to be separated, you can define a prefix or suffix here.This value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "Якщо вибрано «Об’єднати картки персонажів», усі відповідні поля персонажів об’єднуються.\rЦе означає, що, наприклад, у рядку історії всі описи персонажів будуть об’єднані в один великий текст.\rЯкщо ви хочете, щоб ці поля були розділені, ви можете визначити тут префікс або суфікс.\r\rЦе значення підтримує звичайні макроси, а також замінює {{char}} на ім’я відповідного символу, а <FIELDNAME> — на назву частини (наприклад, опис, особистість, сценарій тощо).",
"Inserted after each part of the joined fields.": "Вставляється після кожної частини об’єднаних полів.",
"Join Suffix": "Суфікс приєднання",
"Set a group chat scenario": "Встановити сценарій групового чату",
@@ -885,11 +885,11 @@
"Bulk_edit_characters": "Масове редагування персонажів",
"Bulk select all characters": "Масове виділення всіх символів",
"Bulk delete characters": "Масове видалення персонажів",
"popup_text_save": "зберегти",
"popup_text_yes": "Так",
"popup_text_no": "Немає",
"popup_text_cancel": "Скасувати",
"popup_text_import": "Імпорт",
"popup-button-save": "зберегти",
"popup-button-yes": "Так",
"popup-button-no": "Немає",
"popup-button-cancel": "Скасувати",
"popup-button-import": "Імпорт",
"Advanced Defininitions": "Розширені визначення",
"Prompt Overrides": "Перевизначення підказок",
"(For Chat Completion and Instruct Mode)": "(Для завершення чату та режиму інструктажу)",
@@ -1005,8 +1005,8 @@
"Character Exclusion": "Виключення персонажів",
"-- Characters not found --": "-- Персонажі не знайдені --",
"Inclusion Group": "Група включення",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.\r\rDocumentation: World Info - Inclusion Group": "Групи включення гарантують, що одночасно активується лише один запис із групи, якщо їх активовано декілька.\rПідтримує кілька груп, розділених комами.\r\rДокументація: World Info - Inclusion Group",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.\rIf multiple are prioritized, the one with the highest 'Order' is chosen.": "Пріоритезувати цей запис: якщо позначено, цей запис має пріоритет серед усіх вибраних.\rЯкщо кілька пріоритетів, вибирається той, який має найвищий «порядок».",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.Documentation: World Info - Inclusion Group": "Групи включення гарантують, що одночасно активується лише один запис із групи, якщо їх активовано декілька.\rПідтримує кілька груп, розділених комами.\r\rДокументація: World Info - Inclusion Group",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.If multiple are prioritized, the one with the highest 'Order' is chosen.": "Пріоритезувати цей запис: якщо позначено, цей запис має пріоритет серед усіх вибраних.\rЯкщо кілька пріоритетів, вибирається той, який має найвищий «порядок».",
"Only one entry with the same label will be activated": "Буде активовано лише один запис з однією міткою",
"A relative likelihood of entry activation within the group": "Відносна ймовірність активації входу в групу",
"Group Weight": "Вага групи",
@@ -1436,7 +1436,7 @@
"Reset Code:": "Скинути код:",
"Want to update?": "Хочете оновити?",
"How to start chatting?": "Як почати спілкування?",
"Click ": "Натисніть",
"Click _space": "Натисніть",
"and select a": "і виберіть",
"Chat API": "API чату",
"and pick a character.": "і виберіть персонажа.",

View File

@@ -853,7 +853,7 @@
"Join character cards (include muted)": "Tham gia thẻ nhân vật (bao gồm cả tắt tiếng)",
"Inserted before each part of the joined fields.": "Được chèn trước mỗi phần của các trường đã nối.",
"Join Prefix": "Tham gia tiền tố",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.\rThis means that in the story string for example all character descriptions will be joined to one big text.\rIf you want those fields to be separated, you can define a prefix or suffix here.\r\rThis value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "Khi chọn 'Tham gia thẻ nhân vật', tất cả các trường tương ứng của các nhân vật sẽ được nối với nhau.\rĐiều này có nghĩa là trong chuỗi câu chuyện chẳng hạn, tất cả các mô tả nhân vật sẽ được nối thành một văn bản lớn.\rNếu muốn tách các trường đó, bạn có thể xác định tiền tố hoặc hậu tố tại đây.\r\rGiá trị này hỗ trợ các macro thông thường và cũng sẽ thay thế {{char}} bằng tên của char có liên quan và <FIELDNAME> bằng tên của phần đó (ví dụ: mô tả, tính cách, kịch bản, v.v.)",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.This means that in the story string for example all character descriptions will be joined to one big text.If you want those fields to be separated, you can define a prefix or suffix here.This value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "Khi chọn 'Tham gia thẻ nhân vật', tất cả các trường tương ứng của các nhân vật sẽ được nối với nhau.\rĐiều này có nghĩa là trong chuỗi câu chuyện chẳng hạn, tất cả các mô tả nhân vật sẽ được nối thành một văn bản lớn.\rNếu muốn tách các trường đó, bạn có thể xác định tiền tố hoặc hậu tố tại đây.\r\rGiá trị này hỗ trợ các macro thông thường và cũng sẽ thay thế {{char}} bằng tên của char có liên quan và <FIELDNAME> bằng tên của phần đó (ví dụ: mô tả, tính cách, kịch bản, v.v.)",
"Inserted after each part of the joined fields.": "Được chèn sau mỗi phần của các trường đã nối.",
"Join Suffix": "Tham gia hậu tố",
"Set a group chat scenario": "Đặt một kịch bản trò chuyện nhóm",
@@ -885,11 +885,11 @@
"Bulk_edit_characters": "Chỉnh sửa nhân vật theo lô",
"Bulk select all characters": "Chọn hàng loạt tất cả các ký tự",
"Bulk delete characters": "Xóa nhân vật theo lô",
"popup_text_save": "Cứu",
"popup_text_yes": "Đúng",
"popup_text_no": "KHÔNG",
"popup_text_cancel": "Hủy bỏ",
"popup_text_import": "Nhập khẩu",
"popup-button-save": "Cứu",
"popup-button-yes": "Đúng",
"popup-button-no": "KHÔNG",
"popup-button-cancel": "Hủy bỏ",
"popup-button-import": "Nhập khẩu",
"Advanced Defininitions": "Các Định nghĩa Nâng cao",
"Prompt Overrides": "Ghi đè nhắc nhở",
"(For Chat Completion and Instruct Mode)": "(Đối với chế độ hoàn thành trò chuyện và hướng dẫn)",
@@ -1005,8 +1005,8 @@
"Character Exclusion": "Loại trừ Nhân vật",
"-- Characters not found --": "-- Không tìm thấy Nhân vật --",
"Inclusion Group": "Nhóm Bao gồm",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.\r\rDocumentation: World Info - Inclusion Group": "Nhóm Bao gồm đảm bảo chỉ một mục từ một nhóm được kích hoạt tại một thời điểm, nếu nhiều mục được kích hoạt.\rHỗ trợ nhiều nhóm được phân tách bằng dấu phẩy.\r\rTài liệu: World Info - Inclusion Group",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.\rIf multiple are prioritized, the one with the highest 'Order' is chosen.": "Ưu tiên mục nhập này: Khi được chọn, mục nhập này được ưu tiên trong số tất cả các lựa chọn.\rNếu nhiều ưu tiên được ưu tiên thì cái có 'Thứ tự' cao nhất sẽ được chọn.",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.Documentation: World Info - Inclusion Group": "Nhóm Bao gồm đảm bảo chỉ một mục từ một nhóm được kích hoạt tại một thời điểm, nếu nhiều mục được kích hoạt.\rHỗ trợ nhiều nhóm được phân tách bằng dấu phẩy.\r\rTài liệu: World Info - Inclusion Group",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.If multiple are prioritized, the one with the highest 'Order' is chosen.": "Ưu tiên mục nhập này: Khi được chọn, mục nhập này được ưu tiên trong số tất cả các lựa chọn.\rNếu nhiều ưu tiên được ưu tiên thì cái có 'Thứ tự' cao nhất sẽ được chọn.",
"Only one entry with the same label will be activated": "Chỉ một mục có cùng nhãn sẽ được kích hoạt",
"A relative likelihood of entry activation within the group": "Khả năng tương đối của việc kích hoạt mục nhập trong nhóm",
"Group Weight": "Trọng lượng nhóm",
@@ -1436,7 +1436,7 @@
"Reset Code:": "Đặt lại mã:",
"Want to update?": "Muốn cập nhật?",
"How to start chatting?": "Làm thế nào để bắt đầu trò chuyện?",
"Click ": "Nhấp chuột",
"Click _space": "Nhấp chuột",
"and select a": "và chọn một",
"Chat API": "API Trò chuyện",
"and pick a character.": "và chọn một nhân vật.",

File diff suppressed because it is too large Load Diff

View File

@@ -854,7 +854,7 @@
"Join character cards (include muted)": "加入角色人物卡(包括靜音)",
"Inserted before each part of the joined fields.": "插入在合併欄位的每一部分之前。",
"Join Prefix": "加入前綴",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.\rThis means that in the story string for example all character descriptions will be joined to one big text.\rIf you want those fields to be separated, you can define a prefix or suffix here.\r\rThis value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "當選擇“加入角色卡”時,角色的所有對應欄位將連接在一起。\r這意味著例如在故事字串中所有角色描述都將連接到一個大文字中。\r如果您希望分隔這些字段可以在此處定義前綴或後綴。\r\r該值支援普通宏並且還將 {{char}} 替換為相關字元的名稱,並將 <FIELDNAME> 替換為部分的名稱(例如:描述、個性、場景等)",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.This means that in the story string for example all character descriptions will be joined to one big text.If you want those fields to be separated, you can define a prefix or suffix here.This value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "當選擇“加入角色卡”時,角色的所有對應欄位將連接在一起。\r這意味著例如在故事字串中所有角色描述都將連接到一個大文字中。\r如果您希望分隔這些字段可以在此處定義前綴或後綴。\r\r該值支援普通宏並且還將 {{char}} 替換為相關字元的名稱,並將 <FIELDNAME> 替換為部分的名稱(例如:描述、個性、場景等)",
"Inserted after each part of the joined fields.": "插入在合併欄位的每一部分之後。",
"Join Suffix": "加入後綴",
"Set a group chat scenario": "設定群組聊天情境",
@@ -886,11 +886,11 @@
"Bulk_edit_characters": "批量編輯角色人物\n\n點選以切換角色人物\nShift + 點選以選擇/取消選擇一範圍的角色人物\n右鍵以查看動作",
"Bulk select all characters": "全選所有角色人物",
"Bulk delete characters": "批量刪除角色人物",
"popup_text_save": "儲存",
"popup_text_yes": "是",
"popup_text_no": "否",
"popup_text_cancel": "取消",
"popup_text_import": "匯入",
"popup-button-save": "儲存",
"popup-button-yes": "是",
"popup-button-no": "否",
"popup-button-cancel": "取消",
"popup-button-import": "匯入",
"Advanced Defininitions": "- 進階定義",
"Prompt Overrides": "提示詞覆寫",
"(For Chat Completion and Instruct Mode)": "(用於聊天補充和指令模式)",
@@ -1007,8 +1007,8 @@
"Character Exclusion": "角色人物排除",
"-- Characters not found --": "-- 未找到角色人物 --",
"Inclusion Group": "包含的群組",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.\r\rDocumentation: World Info - Inclusion Group": "如果觸發多個條目,包含群組可確保一次僅啟動一組中的一個條目。\r支援多個以逗號分隔的群組。\r\r文件世界資訊 - 包容性集團",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.\rIf multiple are prioritized, the one with the highest 'Order' is chosen.": "優先考慮此條目:選取後,此條目將在所有選擇中優先。\r如果有多個優先級則選擇「順序」最高的一個。",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.Documentation: World Info - Inclusion Group": "如果觸發多個條目,包含群組可確保一次僅啟動一組中的一個條目。\r支援多個以逗號分隔的群組。\r\r文件世界資訊 - 包容性集團",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.If multiple are prioritized, the one with the highest 'Order' is chosen.": "優先考慮此條目:選取後,此條目將在所有選擇中優先。\r如果有多個優先級則選擇「順序」最高的一個。",
"Only one entry with the same label will be activated": "僅會啟用具有相同標籤的一個條目",
"A relative likelihood of entry activation within the group": "群組內條目啟用的相對可能性",
"Group Weight": "群組權重",
@@ -1439,7 +1439,7 @@
"Reset Code:": "重設驗證碼:",
"Want to update?": "想要更新嗎?",
"How to start chatting?": "如何開始聊天?",
"Click ": "點選",
"Click _space": "點選",
"and select a": "並選擇一個",
"Chat API": "聊天 API",
"and pick a character.": "並選擇一個角色人物。",

View File

@@ -26,6 +26,7 @@
<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">
<link href="webfonts/NotoSansMono/stylesheet.css" rel="stylesheet">
<!-- fontawesome webfonts-->
<link href="css/fontawesome.min.css" rel="stylesheet">
<link href="css/solid.min.css" rel="stylesheet">

File diff suppressed because it is too large Load Diff

View File

@@ -685,6 +685,23 @@ class PromptManager {
this.log('Initialized');
}
/**
* Get the scroll position of the prompt manager
* @returns {number} - Scroll position of the prompt manager
*/
#getScrollPosition() {
return document.getElementById(this.configuration.prefix + 'prompt_manager')?.closest('.scrollableInner')?.scrollTop;
}
/**
* Set the scroll position of the prompt manager
* @param {number} scrollPosition - The scroll position to set
*/
#setScrollPosition(scrollPosition) {
if (scrollPosition === undefined || scrollPosition === null) return;
document.getElementById(this.configuration.prefix + 'prompt_manager')?.closest('.scrollableInner')?.scrollTo(0, scrollPosition);
}
/**
* Main rendering function
*
@@ -703,17 +720,21 @@ class PromptManager {
this.tryGenerate().finally(async () => {
this.profileEnd('filling context');
this.profileStart('render');
const scrollPosition = this.#getScrollPosition();
await this.renderPromptManager();
await this.renderPromptManagerListItems();
this.makeDraggable();
this.#setScrollPosition(scrollPosition);
this.profileEnd('render');
});
} else {
// Executed during live communication
this.profileStart('render');
const scrollPosition = this.#getScrollPosition();
await this.renderPromptManager();
await this.renderPromptManagerListItems();
this.makeDraggable();
this.#setScrollPosition(scrollPosition);
this.profileEnd('render');
}
}).catch(() => {

View File

@@ -16,7 +16,6 @@ import {
eventSource,
menu_type,
substituteParams,
callPopup,
sendTextareaMessage,
} from '../script.js';
@@ -39,6 +38,7 @@ import { textgen_types, textgenerationwebui_settings as textgen_settings, getTex
import { debounce_timeout } from './constants.js';
import Bowser from '../lib/bowser.min.js';
import { Popup } from './popup.js';
var RPanelPin = document.getElementById('rm_button_panel_pin');
var LPanelPin = document.getElementById('lm_button_panel_pin');
@@ -303,7 +303,7 @@ export async function favsToHotswap() {
return;
}
buildAvatarList(container, favs, { selectable: true, highlightFavs: false });
buildAvatarList(container, favs, { interactable: true, highlightFavs: false });
}
//changes input bar and send button display depending on connection status
@@ -360,6 +360,7 @@ function RA_autoconnect(PrevApi) {
|| (textgen_settings.type === textgen_types.INFERMATICAI && secret_state[SECRET_KEYS.INFERMATICAI])
|| (textgen_settings.type === textgen_types.DREAMGEN && secret_state[SECRET_KEYS.DREAMGEN])
|| (textgen_settings.type === textgen_types.OPENROUTER && secret_state[SECRET_KEYS.OPENROUTER])
|| (textgen_settings.type === textgen_types.FEATHERLESS && secret_state[SECRET_KEYS.FEATHERLESS])
) {
$('#api_button_textgenerationwebui').trigger('click');
}
@@ -379,6 +380,7 @@ function RA_autoconnect(PrevApi) {
|| (secret_state[SECRET_KEYS.COHERE] && oai_settings.chat_completion_source == chat_completion_sources.COHERE)
|| (secret_state[SECRET_KEYS.PERPLEXITY] && oai_settings.chat_completion_source == chat_completion_sources.PERPLEXITY)
|| (secret_state[SECRET_KEYS.GROQ] && oai_settings.chat_completion_source == chat_completion_sources.GROQ)
|| (secret_state[SECRET_KEYS.ZEROONEAI] && oai_settings.chat_completion_source == chat_completion_sources.ZEROONEAI)
|| (isValidUrl(oai_settings.custom_url) && oai_settings.chat_completion_source == chat_completion_sources.CUSTOM)
) {
$('#api_button_openai').trigger('click');
@@ -476,8 +478,8 @@ export function dragElement(elmnt) {
}
const style = getComputedStyle(target);
height = parseInt(style.height)
width = parseInt(style.width)
height = parseInt(style.height);
width = parseInt(style.width);
top = parseInt(style.top);
left = parseInt(style.left);
right = parseInt(style.right);
@@ -723,6 +725,10 @@ export function initRossMods() {
RA_autoconnect();
}
if (getParsedUA()?.os?.name === 'iOS') {
document.body.classList.add('ios');
}
$('#main_api').change(function () {
var PrevAPI = main_api;
setTimeout(() => RA_autoconnect(PrevAPI), 100);
@@ -926,8 +932,8 @@ export function initRossMods() {
return false;
}
$(document).on('keydown', function (event) {
processHotkeys(event.originalEvent);
$(document).on('keydown', async function (event) {
await processHotkeys(event.originalEvent);
});
const hotkeyTargets = {
@@ -939,7 +945,7 @@ export function initRossMods() {
/**
* @param {KeyboardEvent} event
*/
function processHotkeys(event) {
async function processHotkeys(event) {
//Enter to send when send_textarea in focus
if (document.activeElement == hotkeyTargets['send_textarea']) {
const sendOnEnter = shouldSendOnEnter();
@@ -1003,20 +1009,17 @@ export function initRossMods() {
if (skipConfirm) {
doRegenerate();
} else {
const popupText = `
<div class="marginBot10">Are you sure you want to regenerate the latest message?</div>
<label class="checkbox_label justifyCenter" for="regenerateWithCtrlEnter">
<input type="checkbox" id="regenerateWithCtrlEnter">
Don't ask again
</label>`;
callPopup(popupText, 'confirm').then(result => {
if (!result) {
return;
}
const regenerateWithCtrlEnter = $('#regenerateWithCtrlEnter').prop('checked');
SaveLocal(skipConfirmKey, regenerateWithCtrlEnter);
doRegenerate();
let regenerateWithCtrlEnter = false;
const result = await Popup.show.confirm('Regenerate Message', 'Are you sure you want to regenerate the latest message?', {
customInputs: [{ id: 'regenerateWithCtrlEnter', label: 'Don\'t ask again' }],
onClose: (popup) => regenerateWithCtrlEnter = popup.inputResults.get('regenerateWithCtrlEnter') ?? false,
});
if (!result) {
return;
}
SaveLocal(skipConfirmKey, regenerateWithCtrlEnter);
doRegenerate();
}
return;
} else {
@@ -1096,6 +1099,9 @@ export function initRossMods() {
}
if (event.key == 'Escape') { //closes various panels
// Do not close panels if we are currently inside a popup
if (Popup.util.isPopupOpen())
return;
//dont override Escape hotkey functions from script.js
//"close edit box" and "cancel stream generation".

View File

@@ -38,6 +38,7 @@ const chara_note_position = {
function setNoteTextCommand(_, text) {
$('#extension_floating_prompt').val(text).trigger('input');
toastr.success('Author\'s Note text updated');
return '';
}
function setNoteDepthCommand(_, text) {
@@ -50,6 +51,7 @@ function setNoteDepthCommand(_, text) {
$('#extension_floating_depth').val(Math.abs(value)).trigger('input');
toastr.success('Author\'s Note depth updated');
return '';
}
function setNoteIntervalCommand(_, text) {
@@ -62,6 +64,7 @@ function setNoteIntervalCommand(_, text) {
$('#extension_floating_interval').val(Math.abs(value)).trigger('input');
toastr.success('Author\'s Note frequency updated');
return '';
}
function setNotePositionCommand(_, text) {
@@ -79,6 +82,7 @@ function setNotePositionCommand(_, text) {
$(`input[name="extension_floating_position"][value="${position}"]`).prop('checked', true).trigger('input');
toastr.info('Author\'s Note position updated');
return '';
}
function updateSettings() {

View File

@@ -6,6 +6,7 @@ import { BlankAutoCompleteOption } from './BlankAutoCompleteOption.js';
// eslint-disable-next-line no-unused-vars
import { AutoCompleteNameResult } from './AutoCompleteNameResult.js';
import { AutoCompleteSecondaryNameResult } from './AutoCompleteSecondaryNameResult.js';
import { Popup, getTopmostModalLayer } from '../popup.js';
/**@readonly*/
/**@enum {Number}*/
@@ -386,11 +387,15 @@ export class AutoComplete {
// no result and no input? hide autocomplete
return this.hide();
}
if (this.effectiveParserResult instanceof AutoCompleteSecondaryNameResult && !this.effectiveParserResult.forceMatch) {
// no result and matching is no forced? hide autocomplete
return this.hide();
}
// otherwise add "no match" notice
const option = new BlankAutoCompleteOption(
this.name.length ?
this.effectiveParserResult.makeNoMatchText()
: this.effectiveParserResult.makeNoOptionstext()
: this.effectiveParserResult.makeNoOptionsText()
,
);
this.result.push(option);
@@ -438,7 +443,7 @@ export class AutoComplete {
}
this.dom.append(frag);
this.updatePosition();
document.body.append(this.domWrap);
getTopmostModalLayer().append(this.domWrap);
} else {
this.domWrap.remove();
}
@@ -453,7 +458,7 @@ export class AutoComplete {
if (!this.isShowingDetails && this.isReplaceable) return this.detailsWrap.remove();
this.detailsDom.innerHTML = '';
this.detailsDom.append(this.selectedItem?.renderDetails() ?? 'NO ITEM');
document.body.append(this.detailsWrap);
getTopmostModalLayer().append(this.detailsWrap);
this.updateDetailsPositionDebounced();
}
@@ -469,7 +474,7 @@ export class AutoComplete {
const rect = {};
rect[AUTOCOMPLETE_WIDTH.INPUT] = this.textarea.getBoundingClientRect();
rect[AUTOCOMPLETE_WIDTH.CHAT] = document.querySelector('#sheld').getBoundingClientRect();
rect[AUTOCOMPLETE_WIDTH.FULL] = document.body.getBoundingClientRect();
rect[AUTOCOMPLETE_WIDTH.FULL] = getTopmostModalLayer().getBoundingClientRect();
this.domWrap.style.setProperty('--bottom', `${window.innerHeight - rect[AUTOCOMPLETE_WIDTH.INPUT].top}px`);
this.dom.style.setProperty('--bottom', `${window.innerHeight - rect[AUTOCOMPLETE_WIDTH.INPUT].top}px`);
this.domWrap.style.bottom = `${window.innerHeight - rect[AUTOCOMPLETE_WIDTH.INPUT].top}px`;
@@ -481,8 +486,8 @@ export class AutoComplete {
this.domWrap.style.setProperty('--leftOffset', `max(1vw, ${rect[power_user.stscript.autocomplete.width.left].left}px)`);
this.domWrap.style.setProperty('--rightOffset', `calc(100vw - min(99vw, ${rect[power_user.stscript.autocomplete.width.right].right}px)`);
}
this.updateDetailsPosition();
}
this.updateDetailsPosition();
}
/**
@@ -496,7 +501,7 @@ export class AutoComplete {
const rect = {};
rect[AUTOCOMPLETE_WIDTH.INPUT] = this.textarea.getBoundingClientRect();
rect[AUTOCOMPLETE_WIDTH.CHAT] = document.querySelector('#sheld').getBoundingClientRect();
rect[AUTOCOMPLETE_WIDTH.FULL] = document.body.getBoundingClientRect();
rect[AUTOCOMPLETE_WIDTH.FULL] = getTopmostModalLayer().getBoundingClientRect();
if (this.isReplaceable) {
this.detailsWrap.classList.remove('full');
const selRect = this.selectedItem.dom.children[0].getBoundingClientRect();
@@ -592,7 +597,7 @@ export class AutoComplete {
}
this.clone.style.position = 'fixed';
this.clone.style.visibility = 'hidden';
document.body.append(this.clone);
getTopmostModalLayer().append(this.clone);
const mo = new MutationObserver(muts=>{
if (muts.find(it=>Array.from(it.removedNodes).includes(this.textarea))) {
this.clone.remove();
@@ -745,8 +750,10 @@ export class AutoComplete {
}
// autocomplete shown or not, cursor anywhere
switch (evt.key) {
// The first is a non-breaking space, the second is a regular space.
case ' ':
case ' ': {
if (evt.ctrlKey) {
if (evt.ctrlKey || evt.altKey) {
if (this.isActive && this.isReplaceable) {
// ctrl-space to toggle details for selected item
this.toggleDetails();
@@ -754,6 +761,8 @@ export class AutoComplete {
// ctrl-space to force show autocomplete
this.show(false, true);
}
evt.preventDefault();
evt.stopPropagation();
return;
}
break;

View File

@@ -10,7 +10,7 @@ export class AutoCompleteNameResult {
/**@type {AutoCompleteOption[]} */ optionList = [];
/**@type {boolean} */ canBeQuoted = false;
/**@type {()=>string} */ makeNoMatchText = ()=>`No matches found for "${this.name}"`;
/**@type {()=>string} */ makeNoOptionstext = ()=>'No options';
/**@type {()=>string} */ makeNoOptionsText = ()=>'No options';
/**
@@ -27,7 +27,7 @@ export class AutoCompleteNameResult {
this.optionList = optionList;
this.canBeQuoted = canBeQuoted;
this.noMatchText = makeNoMatchText ?? this.makeNoMatchText;
this.noOptionstext = makeNoOptionsText ?? this.makeNoOptionstext;
this.noOptionstext = makeNoOptionsText ?? this.makeNoOptionsText;
}

View File

@@ -6,6 +6,7 @@ import { AutoCompleteFuzzyScore } from './AutoCompleteFuzzyScore.js';
export class AutoCompleteOption {
/**@type {string}*/ name;
/**@type {string}*/ typeIcon;
/**@type {string}*/ type;
/**@type {number}*/ nameOffset = 0;
/**@type {AutoCompleteFuzzyScore}*/ score;
/**@type {string}*/ replacer;
@@ -24,9 +25,10 @@ export class AutoCompleteOption {
/**
* @param {string} name
*/
constructor(name, typeIcon = ' ') {
constructor(name, typeIcon = ' ', type = '') {
this.name = name;
this.typeIcon = typeIcon;
this.type = type;
}
@@ -141,6 +143,11 @@ export class AutoCompleteOption {
}
li.append(specs);
}
const stopgap = document.createElement('span'); {
stopgap.classList.add('stopgap');
stopgap.textContent = '';
li.append(stopgap);
}
const help = document.createElement('span'); {
help.classList.add('help');
const content = document.createElement('span'); {
@@ -181,6 +188,7 @@ export class AutoCompleteOption {
let li;
li = this.makeItem(this.name, this.typeIcon, true);
li.setAttribute('data-name', this.name);
li.setAttribute('data-option-type', this.type);
return li;
}

View File

@@ -2,4 +2,5 @@ import { AutoCompleteNameResult } from './AutoCompleteNameResult.js';
export class AutoCompleteSecondaryNameResult extends AutoCompleteNameResult {
/**@type {boolean}*/ isRequired = false;
/**@type {boolean}*/ forceMatch = true;
}

View File

@@ -95,7 +95,7 @@ function onLockBackgroundClick(e) {
if (!chatName) {
toastr.warning('Select a chat to lock the background for it');
return;
return '';
}
const relativeBgImage = getUrlParameter(this);
@@ -103,6 +103,7 @@ function onLockBackgroundClick(e) {
saveBackgroundMetadata(relativeBgImage);
setCustomBackground();
highlightLockedBackground();
return '';
}
function onUnlockBackgroundClick(e) {
@@ -110,6 +111,7 @@ function onUnlockBackgroundClick(e) {
removeBackgroundMetadata();
unsetCustomBackground();
highlightLockedBackground();
return '';
}
function hasCustomBackground() {
@@ -319,7 +321,7 @@ async function autoBackgroundCommand() {
const options = bgTitles.map(x => ({ element: x, text: x.innerText.trim() })).filter(x => x.text.length > 0);
if (options.length == 0) {
toastr.warning('No backgrounds to choose from. Please upload some images to the "backgrounds" folder.');
return;
return '';
}
const list = options.map(option => `- ${option.text}`).join('\n');
@@ -330,11 +332,12 @@ async function autoBackgroundCommand() {
if (bestMatch.length == 0) {
toastr.warning('No match found. Please try again.');
return;
return '';
}
console.debug('Automatically choosing background:', bestMatch);
bestMatch[0].item.element.click();
return '';
}
export async function getBackgrounds() {

View File

@@ -24,6 +24,7 @@ import {
saveGroupBookmarkChat,
selected_group,
} from './group-chats.js';
import { Popup } from './popup.js';
import { createTagMapFromList } from './tags.js';
import {
@@ -239,8 +240,7 @@ async function convertSoloToGroupChat() {
return;
}
const confirm = await callPopup('Are you sure you want to convert this chat to a group chat?', 'confirm');
const confirm = await Popup.show.confirm('Convert to group chat', 'Are you sure you want to convert this chat to a group chat?<br />This cannot be reverted.');
if (!confirm) {
return;
}
@@ -336,6 +336,7 @@ async function convertSoloToGroupChat() {
if (!createChatResponse.ok) {
console.error('Group chat creation unsuccessful');
toastr.error('Group chat creation unsuccessful');
return;
}

View File

@@ -94,6 +94,9 @@ function enableBulkSelect() {
});
$(el).prepend(checkbox);
});
$('#rm_print_characters_block.group_overlay_mode_select .bogus_folder_select, #rm_print_characters_block.group_overlay_mode_select .group_select')
.addClass('disabled');
$('#rm_print_characters_block').addClass('bulk_select');
// We also need to disable the default click event for the character_select divs
$(document).on('click', '.bulk_select_checkbox', function (event) {
@@ -106,6 +109,8 @@ function enableBulkSelect() {
*/
function disableBulkSelect() {
$('.bulk_select_checkbox').remove();
$('#rm_print_characters_block.group_overlay_mode_select .bogus_folder_select, #rm_print_characters_block.group_overlay_mode_select .group_select')
.removeClass('disabled');
$('#rm_print_characters_block').removeClass('bulk_select');
}

View File

@@ -4,7 +4,6 @@ import css from '../lib/css-parser.mjs';
import {
addCopyToCodeBlocks,
appendMediaToMessage,
callPopup,
characters,
chat,
eventSource,
@@ -35,8 +34,10 @@ import {
extractTextFromOffice,
} from './utils.js';
import { extension_settings, renderExtensionTemplateAsync, saveMetadataDebounced } from './extensions.js';
import { POPUP_RESULT, POPUP_TYPE, callGenericPopup } from './popup.js';
import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
import { ScraperManager } from './scrapers.js';
import { DragAndDropHandler } from './dragdrop.js';
import { renderTemplateAsync } from './templates.js';
/**
* @typedef {Object} FileAttachment
@@ -184,18 +185,19 @@ export async function populateFileAttachment(message, inputId = 'file_form_input
const file = fileInput.files[0];
if (!file) return;
const slug = getStringHash(file.name);
const fileNamePrefix = `${Date.now()}_${slug}`;
const fileBase64 = await getBase64Async(file);
let base64Data = fileBase64.split(',')[1];
// If file is image
if (file.type.startsWith('image/')) {
const extension = file.type.split('/')[1];
const imageUrl = await saveBase64AsFile(base64Data, name2, file.name, extension);
const imageUrl = await saveBase64AsFile(base64Data, name2, fileNamePrefix, extension);
message.extra.image = imageUrl;
message.extra.inline_image = true;
} else {
const slug = getStringHash(file.name);
const uniqueFileName = `${Date.now()}_${slug}.txt`;
const uniqueFileName = `${fileNamePrefix}.txt`;
if (isConvertible(file.type)) {
try {
@@ -318,12 +320,10 @@ export function hasPendingFileAttachment() {
/**
* Displays file information in the message sending form.
* @param {File} file File object
* @returns {Promise<void>}
*/
async function onFileAttach() {
const fileInput = document.getElementById('file_form_input');
if (!(fileInput instanceof HTMLInputElement)) return;
const file = fileInput.files[0];
async function onFileAttach(file) {
if (!file) return;
const isValid = await validateFile(file);
@@ -418,6 +418,7 @@ function embedMessageFile(messageId, messageBlock) {
}
await populateFileAttachment(message, 'embed_file_input');
await eventSource.emit(event_types.MESSAGE_FILE_EMBEDDED, messageId);
appendMediaToMessage(message, messageBlock);
await saveChatConditional();
}
@@ -523,7 +524,7 @@ async function openExternalMediaOverridesDialog() {
return;
}
const template = $('#forbid_media_override_template > .forbid_media_override').clone();
const template = $(await renderTemplateAsync('forbidMedia'));
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);
@@ -537,7 +538,7 @@ async function openExternalMediaOverridesDialog() {
template.find('#forbid_media_override_global').prop('checked', true);
}
callPopup(template, 'text', '', { wide: false, large: false });
callGenericPopup(template, POPUP_TYPE.TEXT, '', { wide: false, large: false });
}
export function getCurrentEntityId() {
@@ -565,7 +566,7 @@ export function isExternalMediaAllowed() {
return !power_user.forbid_external_media;
}
function enlargeMessageImage() {
async function enlargeMessageImage() {
const mesBlock = $(this).closest('.mes');
const mesId = mesBlock.attr('mesid');
const message = chat[mesId];
@@ -579,14 +580,28 @@ function enlargeMessageImage() {
const img = document.createElement('img');
img.classList.add('img_enlarged');
img.src = imgSrc;
const imgHolder = document.createElement('div');
imgHolder.classList.add('img_enlarged_holder');
imgHolder.append(img);
const imgContainer = $('<div><pre><code></code></pre></div>');
imgContainer.prepend(img);
imgContainer.prepend(imgHolder);
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 });
const popup = new Popup(imgContainer, POPUP_TYPE.DISPLAY, '', { large: true, transparent: true });
popup.dlg.style.width = 'unset';
popup.dlg.style.height = 'unset';
img.addEventListener('click', () => {
const shouldZoom = !img.classList.contains('zoomed');
img.classList.toggle('zoomed', shouldZoom);
});
await popup.show();
}
async function deleteMessageImage() {
@@ -601,6 +616,8 @@ async function deleteMessageImage() {
const message = chat[mesId];
delete message.extra.image;
delete message.extra.inline_image;
delete message.extra.title;
delete message.extra.append_title;
mesBlock.find('.mes_img_container').removeClass('img_extra');
mesBlock.find('.mes_img').attr('src', '');
await saveChatConditional();
@@ -991,49 +1008,24 @@ async function openAttachmentManager() {
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');
const dragDropHandler = new DragAndDropHandler('.popup', async (files, event) => {
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());
});
$(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');
}
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();
});
let sortField = localStorage.getItem('DataBank_sortField') || 'created';
let sortOrder = localStorage.getItem('DataBank_sortOrder') || 'desc';
@@ -1056,43 +1048,61 @@ async function openAttachmentManager() {
localStorage.setItem('DataBank_sortOrder', sortOrder);
renderAttachments();
});
template.find('.bulkActionDelete').on('click', async () => {
const selectedAttachments = document.querySelectorAll('.attachmentListItemCheckboxContainer .attachmentListItemCheckbox:checked');
function handleBulkAction(action) {
return async () => {
const selectedAttachments = document.querySelectorAll('.attachmentListItemCheckboxContainer .attachmentListItemCheckbox:checked');
if (selectedAttachments.length === 0) {
toastr.info('No attachments selected.', 'Data Bank');
return;
}
const confirm = await callGenericPopup('Are you sure you want to delete the selected attachments?', POPUP_TYPE.CONFIRM);
if (confirm !== POPUP_RESULT.AFFIRMATIVE) {
return;
}
const attachments = getDataBankAttachments();
selectedAttachments.forEach(async (checkbox) => {
const listItem = checkbox.closest('.attachmentListItem');
if (!(listItem instanceof HTMLElement)) {
if (selectedAttachments.length === 0) {
toastr.info('No attachments selected.', 'Data Bank');
return;
}
const url = listItem.dataset.attachmentUrl;
const source = listItem.dataset.attachmentSource;
const attachment = attachments.find(a => a.url === url);
if (!attachment) {
return;
}
await deleteAttachment(attachment, source, () => {}, false);
});
document.querySelectorAll('.attachmentListItemCheckbox, .attachmentsBulkEditCheckbox').forEach(checkbox => {
if (checkbox instanceof HTMLInputElement) {
checkbox.checked = false;
if (action.confirmMessage) {
const confirm = await callGenericPopup(action.confirmMessage, POPUP_TYPE.CONFIRM);
if (confirm !== POPUP_RESULT.AFFIRMATIVE) {
return;
}
}
});
await renderAttachments();
});
const includeDisabled = true;
const attachments = getDataBankAttachments(includeDisabled);
selectedAttachments.forEach(async (checkbox) => {
const listItem = checkbox.closest('.attachmentListItem');
if (!(listItem instanceof HTMLElement)) {
return;
}
const url = listItem.dataset.attachmentUrl;
const source = listItem.dataset.attachmentSource;
const attachment = attachments.find(a => a.url === url);
if (!attachment) {
return;
}
await action.perform(attachment, source);
});
document.querySelectorAll('.attachmentListItemCheckbox, .attachmentsBulkEditCheckbox').forEach(checkbox => {
if (checkbox instanceof HTMLInputElement) {
checkbox.checked = false;
}
});
await renderAttachments();
};
}
template.find('.bulkActionDisable').on('click', handleBulkAction({
perform: (attachment) => disableAttachment(attachment, () => { }),
}));
template.find('.bulkActionEnable').on('click', handleBulkAction({
perform: (attachment) => enableAttachment(attachment, () => { }),
}));
template.find('.bulkActionDelete').on('click', handleBulkAction({
confirmMessage: 'Are you sure you want to delete the selected attachments?',
perform: async (attachment, source) => await deleteAttachment(attachment, source, () => { }, false),
}));
template.find('.bulkActionSelectAll').on('click', () => {
$('.attachmentListItemCheckbox:visible').each((_, checkbox) => {
if (checkbox instanceof HTMLInputElement) {
@@ -1111,11 +1121,10 @@ async function openAttachmentManager() {
const cleanupFn = await renderButtons();
await verifyAttachments();
await renderAttachments();
addDragAndDrop();
await callGenericPopup(template, POPUP_TYPE.TEXT, '', { wide: true, large: true, okButton: 'Close' });
await callGenericPopup(template, POPUP_TYPE.TEXT, '', { wide: true, large: true, okButton: 'Close', allowVerticalScrolling: true });
cleanupFn();
removeDragAndDrop();
dragDropHandler.destroy();
}
/**
@@ -1458,7 +1467,7 @@ jQuery(function () {
});
}
callPopup(wrapper, 'text', '', { wide: true, large: true });
callGenericPopup(wrapper, POPUP_TYPE.TEXT, '', { wide: true, large: true });
});
$(document).on('click', 'body.documentstyle .mes .mes_text', function () {
@@ -1496,8 +1505,28 @@ jQuery(function () {
$(document).on('click', '.mes_img_enlarge', enlargeMessageImage);
$(document).on('click', '.mes_img_delete', deleteMessageImage);
$('#file_form_input').on('change', onFileAttach);
$('#file_form_input').on('change', async () => {
const fileInput = document.getElementById('file_form_input');
if (!(fileInput instanceof HTMLInputElement)) return;
const file = fileInput.files[0];
await onFileAttach(file);
});
$('#file_form').on('reset', function () {
$('#file_form').addClass('displayNone');
});
document.getElementById('send_textarea').addEventListener('paste', async function (event) {
if (event.clipboardData.files.length === 0) {
return;
}
event.preventDefault();
event.stopPropagation();
const fileInput = document.getElementById('file_form_input');
if (!(fileInput instanceof HTMLInputElement)) return;
fileInput.files = event.clipboardData.files;
await onFileAttach(fileInput.files[0]);
});
});

107
public/scripts/dragdrop.js vendored Normal file
View File

@@ -0,0 +1,107 @@
import { debounce_timeout } from './constants.js';
/**
* Drag and drop handler
*
* Can be used on any element, enabling drag&drop styling and callback on drop.
*/
export class DragAndDropHandler {
/** @private @type {JQuery.Selector} */ selector;
/** @private @type {(files: File[], event:JQuery.DropEvent<HTMLElement, undefined, any, any>) => void} */ onDropCallback;
/** @private @type {NodeJS.Timeout} Remark: Not actually NodeJS timeout, but it's close */ dragLeaveTimeout;
/** @private @type {boolean} */ noAnimation;
/**
* Create a DragAndDropHandler
* @param {JQuery.Selector} selector - The CSS selector for the elements to enable drag and drop
* @param {(files: File[], event:JQuery.DropEvent<HTMLElement, undefined, any, any>) => void} onDropCallback - The callback function to handle the drop event
*/
constructor(selector, onDropCallback, { noAnimation = false } = {}) {
this.selector = selector;
this.onDropCallback = onDropCallback;
this.dragLeaveTimeout = null;
this.noAnimation = noAnimation;
this.init();
}
/**
* Destroy the drag and drop functionality
*/
destroy() {
if (this.selector === 'body') {
$(document.body).off('dragover', this.handleDragOver.bind(this));
$(document.body).off('dragleave', this.handleDragLeave.bind(this));
$(document.body).off('drop', this.handleDrop.bind(this));
} else {
$(document.body).off('dragover', this.selector, this.handleDragOver.bind(this));
$(document.body).off('dragleave', this.selector, this.handleDragLeave.bind(this));
$(document.body).off('drop', this.selector, this.handleDrop.bind(this));
}
$(this.selector).remove('drop_target no_animation');
}
/**
* Initialize the drag and drop functionality
* Automatically called on construction
* @private
*/
init() {
if (this.selector === 'body') {
$(document.body).on('dragover', this.handleDragOver.bind(this));
$(document.body).on('dragleave', this.handleDragLeave.bind(this));
$(document.body).on('drop', this.handleDrop.bind(this));
} else {
$(document.body).on('dragover', this.selector, this.handleDragOver.bind(this));
$(document.body).on('dragleave', this.selector, this.handleDragLeave.bind(this));
$(document.body).on('drop', this.selector, this.handleDrop.bind(this));
}
$(this.selector).addClass('drop_target');
if (this.noAnimation) $(this.selector).addClass('no_animation');
}
/**
* @param {JQuery.DragOverEvent<HTMLElement, undefined, any, any>} event - The dragover event
* @private
*/
handleDragOver(event) {
event.preventDefault();
event.stopPropagation();
clearTimeout(this.dragLeaveTimeout);
$(this.selector).addClass('drop_target dragover');
if (this.noAnimation) $(this.selector).addClass('no_animation');
}
/**
* @param {JQuery.DragLeaveEvent<HTMLElement, undefined, any, any>} event - The dragleave event
* @private
*/
handleDragLeave(event) {
event.preventDefault();
event.stopPropagation();
// Debounce the removal of the class, so it doesn't "flicker" on dragging over
clearTimeout(this.dragLeaveTimeout);
this.dragLeaveTimeout = setTimeout(() => {
$(this.selector).removeClass('dragover');
}, debounce_timeout.quick);
}
/**
* @param {JQuery.DropEvent<HTMLElement, undefined, any, any>} event - The drop event
* @private
*/
handleDrop(event) {
event.preventDefault();
event.stopPropagation();
clearTimeout(this.dragLeaveTimeout);
$(this.selector).removeClass('dragover');
const files = Array.from(event.originalEvent.dataTransfer.files);
this.onDropCallback(files, event);
}
}

View File

@@ -0,0 +1,162 @@
/** @type {CSSStyleSheet} */
let dynamicStyleSheet = null;
/** @type {CSSStyleSheet} */
let dynamicExtensionStyleSheet = null;
/**
* An observer that will check if any new stylesheets are added to the head
* @type {MutationObserver}
*/
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
if (mutation.type !== 'childList') return;
mutation.addedNodes.forEach(node => {
if (node instanceof HTMLLinkElement && node.tagName === 'LINK' && node.rel === 'stylesheet') {
node.addEventListener('load', () => {
try {
applyDynamicFocusStyles(node.sheet);
} catch (e) {
console.warn('Failed to process new stylesheet:', e);
}
});
}
});
});
});
/**
* Generates dynamic focus styles based on the given stylesheet, taking its hover styles as reference
*
* @param {CSSStyleSheet} styleSheet - The stylesheet to process
* @param {object} [options] - Optional configuration options
* @param {boolean} [options.fromExtension=false] - Indicates if the styles are from an extension
*/
function applyDynamicFocusStyles(styleSheet, { fromExtension = false } = {}) {
/** @type {{baseSelector: string, rule: CSSStyleRule}[]} */
const hoverRules = [];
/** @type {Set<string>} */
const focusRules = new Set();
const PLACEHOLDER = ':__PLACEHOLDER__';
/**
* Processes the CSS rules and separates selectors for hover and focus
* @param {CSSRuleList} rules - The CSS rules to process
*/
function processRules(rules) {
Array.from(rules).forEach(rule => {
if (rule instanceof CSSImportRule) {
// Make sure that @import rules are processed recursively
processImportedStylesheet(rule.styleSheet);
} else if (rule instanceof CSSStyleRule) {
// Separate multiple selectors on a rule
const selectors = rule.selectorText.split(',').map(s => s.trim());
// We collect all hover and focus rules to be able to later decide which hover rules don't have a matching focus rule
selectors.forEach(selector => {
const isHover = selector.includes(':hover'), isFocus = selector.includes(':focus');
if (isHover && isFocus) {
// We currently do nothing here. Rules containing both hover and focus are very specific and should never be automatically touched
}
else if (isHover) {
const baseSelector = selector.replace(':hover', PLACEHOLDER).trim();
hoverRules.push({ baseSelector, rule });
} else if (isFocus) {
// We need to make sure that we remember all existing :focus, :focus-within and :focus-visible rules
const baseSelector = selector.replace(':focus-within', PLACEHOLDER).replace(':focus-visible', PLACEHOLDER).replace(':focus', PLACEHOLDER).trim();
focusRules.add(baseSelector);
}
});
} else if (rule instanceof CSSMediaRule || rule instanceof CSSSupportsRule) {
// Recursively process nested rules
processRules(rule.cssRules);
}
});
}
/**
* Processes the CSS rules of an imported stylesheet recursively
* @param {CSSStyleSheet} sheet - The imported stylesheet to process
*/
function processImportedStylesheet(sheet) {
if (sheet && sheet.cssRules) {
processRules(sheet.cssRules);
}
}
processRules(styleSheet.cssRules);
/** @type {CSSStyleSheet} */
let targetStyleSheet = null;
// Now finally create the dynamic focus rules
hoverRules.forEach(({ baseSelector, rule }) => {
if (!focusRules.has(baseSelector)) {
// Only initialize the dynamic stylesheet if needed
targetStyleSheet ??= getDynamicStyleSheet({ fromExtension });
// The closest keyboard-equivalent to :hover styling is utilizing the :focus-visible rule from modern browsers.
// It let's the browser decide whether a focus highlighting is expected and makes sense.
// So we take all :hover rules that don't have a manually defined focus rule yet, and create their
// :focus-visible counterpart, which will make the styling work the same for keyboard and mouse.
// If something like :focus-within or a more specific selector like `.blah:has(:focus-visible)` for elements inside,
// it should be manually defined in CSS.
const focusSelector = rule.selectorText.replace(/:hover/g, ':focus-visible');
const focusRule = `${focusSelector} { ${rule.style.cssText} }`;
try {
targetStyleSheet.insertRule(focusRule, targetStyleSheet.cssRules.length);
} catch (e) {
console.warn('Failed to insert focus rule:', e);
}
}
});
}
/**
* Retrieves the stylesheet that should be used for dynamic rules
*
* @param {object} options - The options object
* @param {boolean} [options.fromExtension=false] - Indicates whether the rules are coming from extensions
* @return {CSSStyleSheet} The dynamic stylesheet
*/
function getDynamicStyleSheet({ fromExtension = false } = {}) {
if (fromExtension) {
if (!dynamicExtensionStyleSheet) {
const styleSheetElement = document.createElement('style');
styleSheetElement.setAttribute('id', 'dynamic-extension-styles');
document.head.appendChild(styleSheetElement);
dynamicExtensionStyleSheet = styleSheetElement.sheet;
}
return dynamicExtensionStyleSheet;
} else {
if (!dynamicStyleSheet) {
const styleSheetElement = document.createElement('style');
styleSheetElement.setAttribute('id', 'dynamic-styles');
document.head.appendChild(styleSheetElement);
dynamicStyleSheet = styleSheetElement.sheet;
}
return dynamicStyleSheet;
}
}
/**
* Initializes dynamic styles for ST
*/
export function initDynamicStyles() {
// Start observing the head for any new added stylesheets
observer.observe(document.head, {
childList: true,
subtree: true
});
// Process all stylesheets on initial load
Array.from(document.styleSheets).forEach(sheet => {
try {
applyDynamicFocusStyles(sheet, { fromExtension: sheet.href.toLowerCase().includes('scripts/extensions') });
} catch (e) {
console.warn('Failed to process stylesheet on initial load:', e);
}
});
}

View File

@@ -1,5 +1,6 @@
import { callPopup, eventSource, event_types, saveSettings, saveSettingsDebounced, getRequestHeaders, animation_duration } from '../script.js';
import { eventSource, event_types, saveSettings, saveSettingsDebounced, getRequestHeaders, animation_duration } from '../script.js';
import { hideLoader, showLoader } from './loader.js';
import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
import { renderTemplate, renderTemplateAsync } from './templates.js';
import { isSubsetOf, setValueByPath } from './utils.js';
export {
@@ -347,14 +348,12 @@ function autoConnectInputHandler() {
saveSettingsDebounced();
}
function addExtensionsButtonAndMenu() {
const buttonHTML =
'<div id="extensionsMenuButton" style="display: none;" class="fa-solid fa-magic-wand-sparkles" title="Extras Extensions" /></div>';
const extensionsMenuHTML = '<div id="extensionsMenu" class="options-content" style="display: none;"></div>';
async function addExtensionsButtonAndMenu() {
const buttonHTML = await renderTemplateAsync('wandButton');
const extensionsMenuHTML = await renderTemplateAsync('wandMenu');
$(document.body).append(extensionsMenuHTML);
$('#leftSendForm').prepend(buttonHTML);
$('#leftSendForm').append(buttonHTML);
const button = $('#extensionsMenuButton');
const dropdown = $('#extensionsMenu');
@@ -505,7 +504,7 @@ function addExtensionScript(name, manifest) {
* @param {boolean} isDisabled - Whether the extension is disabled or not.
* @param {boolean} isExternal - Whether the extension is external or not.
* @param {string} checkboxClass - The class for the checkbox HTML element.
* @return {string} - The HTML string that represents the extension.
* @return {Promise<string>} - The HTML string that represents the extension.
*/
async function generateExtensionHtml(name, manifest, isActive, isDisabled, isExternal, checkboxClass) {
const displayName = manifest.display_name;
@@ -557,8 +556,10 @@ async function generateExtensionHtml(name, manifest, isActive, isDisabled, isExt
} else if (!isDisabled) { // Neither active nor disabled
const requirements = new Set(manifest.requires);
modules.forEach(x => requirements.delete(x));
const requirementsString = DOMPurify.sanitize([...requirements].join(', '));
extensionHtml += `<p>Missing modules: <span class="failure">${requirementsString}</span></p>`;
if (requirements.size > 0) {
const requirementsString = DOMPurify.sanitize([...requirements].join(', '));
extensionHtml += `<p>Missing modules: <span class="failure">${requirementsString}</span></p>`;
}
}
return extensionHtml;
@@ -633,7 +634,18 @@ async function showExtensionsDetails() {
${htmlDefault}
${htmlExternal}
`;
popupPromise = callPopup(`<div class="extensions_info">${html}</div>`, 'text', '', { okButton: 'Close', wide: true, large: true });
/** @type {import('./popup.js').CustomPopupButton} */
const updateAllButton = {
text: 'Update all',
appendAtEnd: true,
action: async () => {
requiresReload = true;
await autoUpdateExtensions(true);
popup.complete(POPUP_RESULT.AFFIRMATIVE);
},
};
const popup = new Popup(`<div class="extensions_info">${html}</div>`, POPUP_TYPE.TEXT, '', { okButton: 'Close', wide: true, large: true, customButtons: [updateAllButton], allowVerticalScrolling: true });
popupPromise = popup.show();
} catch (error) {
toastr.error('Error loading extensions. See browser console for details.');
console.error(error);
@@ -702,8 +714,8 @@ async function updateExtension(extensionName, quiet) {
async function onDeleteClick() {
const extensionName = $(this).data('name');
// use callPopup to create a popup for the user to confirm before delete
const confirmation = await callPopup(`Are you sure you want to delete ${extensionName}?`, 'delete_extension');
if (confirmation) {
const confirmation = await callGenericPopup(`Are you sure you want to delete ${extensionName}?`, POPUP_TYPE.CONFIRM, '', {});
if (confirmation === POPUP_RESULT.AFFIRMATIVE) {
await deleteExtension(extensionName);
}
}
@@ -799,7 +811,7 @@ async function loadExtensionSettings(settings, versionChanged) {
manifests = await getManifests(extensionNames);
if (versionChanged) {
await autoUpdateExtensions();
await autoUpdateExtensions(false);
}
await activateExtensions();
@@ -862,7 +874,12 @@ async function checkForExtensionUpdates(force) {
}
}
async function autoUpdateExtensions() {
/**
* Updates all 3rd-party extensions that have auto-update enabled.
* @param {boolean} forceAll Force update all even if not auto-updating
* @returns {Promise<void>}
*/
async function autoUpdateExtensions(forceAll) {
if (!Object.values(manifests).some(x => x.auto_update)) {
return;
}
@@ -870,7 +887,7 @@ async function autoUpdateExtensions() {
const banner = toastr.info('Auto-updating extensions. This may take several minutes.', 'Please wait...', { timeOut: 10000, extendedTimeOut: 10000 });
const promises = [];
for (const [id, manifest] of Object.entries(manifests)) {
if (manifest.auto_update && id.startsWith('third-party')) {
if ((forceAll || manifest.auto_update) && id.startsWith('third-party')) {
console.debug(`Auto-updating 3rd-party extension: ${manifest.display_name} (${id})`);
promises.push(updateExtension(id.replace('third-party', ''), true));
}
@@ -961,8 +978,8 @@ export async function writeExtensionField(characterId, key, value) {
}
}
jQuery(function () {
addExtensionsButtonAndMenu();
jQuery(async function () {
await addExtensionsButtonAndMenu();
$('#extensionsMenuButton').css('display', 'flex');
$('#extensions_connect').on('click', connectClickHandler);
@@ -990,14 +1007,14 @@ jQuery(function () {
<p><b>Disclaimer:</b> Please be aware that using external extensions can have unintended side effects and may pose security risks. Always make sure you trust the source before importing an extension. We are not responsible for any damage caused by third-party extensions.</p>
<br>
<p>Example: <tt> https://github.com/author/extension-name </tt></p>`;
const input = await callPopup(html, 'input');
const input = await callGenericPopup(html, POPUP_TYPE.INPUT, '');
if (!input) {
console.debug('Extension install cancelled');
return;
}
const url = input.trim();
const url = String(input).trim();
await installExtension(url);
});
});

View File

@@ -440,7 +440,7 @@ jQuery(async () => {
});
windowHtml.find('#assets_filters').hide();
$('#extensions_settings').append(windowHtml);
$('#assets_container').append(windowHtml);
eventSource.on(event_types.OPEN_CHARACTER_LIBRARY, async (forceDefault) => {
openCharacterBrowser(forceDefault);

View File

@@ -1,11 +1,11 @@
<div id="assets_ui">
<div class="inline-drawer">
<div class="inline-drawer-toggle inline-drawer-header">
<b>Download Extensions & Assets</b>
<b data-i18n="Download Extensions & Assets">Download Extensions & Assets</b>
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
</div>
<div class="inline-drawer-content">
<label for="assets-json-url-field">Assets URL</label>
<label for="assets-json-url-field" data-i18n="Assets URL">Assets URL</label>
<div class="assets-connect-div">
<input id="assets-json-url-field" class="text_pole widthUnset flex1">
<i id="assets-connect-button" class="menu_button fa-solid fa-plug-circle-exclamation fa-xl redOverlayGlow"></i>
@@ -16,7 +16,7 @@
<input id="assets_search" class="text_pole flex1" placeholder="Search" type="search">
<div id="assets-characters-button" class="menu_button menu_button_icon">
<i class="fa-solid fa-image-portrait"></i>
Characters
<span data-i18n="Characters">Characters</span>
</div>
</div>
<div class="inline-drawer-content" id="assets_menu">

View File

@@ -0,0 +1,4 @@
<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>

View File

@@ -2,6 +2,10 @@ import { deleteAttachment, getDataBankAttachments, getDataBankAttachmentsForSour
import { extension_settings, renderExtensionTemplateAsync } from '../../extensions.js';
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
import { SlashCommandClosure } from '../../slash-commands/SlashCommandClosure.js';
import { enumIcons } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashCommandEnumValue.js';
import { SlashCommandExecutor } from '../../slash-commands/SlashCommandExecutor.js';
import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
/**
@@ -193,12 +197,36 @@ async function enableDataBankAttachment(args, value) {
}
jQuery(async () => {
const buttons = await renderExtensionTemplateAsync('attachments', 'buttons', {});
$('#extensionsMenu').prepend(buttons);
const manageButton = await renderExtensionTemplateAsync('attachments', 'manage-button', {});
const attachButton = await renderExtensionTemplateAsync('attachments', 'attach-button', {});
$('#data_bank_wand_container').append(manageButton);
$('#attach_file_wand_container').append(attachButton);
/** A collection of local enum providers for this context of data bank */
const localEnumProviders = {
/**
* All attachments in the data bank based on the source argument. If not provided, defaults to 'chat'.
* @param {'name' | 'url'} returnField - Whether the enum should return the 'name' field or the 'url'
* @param {'chat' | 'character' | 'global' | ''} fallbackSource - The source to use if the source argument is not provided. Empty string to use all sources.
* */
attachments: (returnField = 'name', fallbackSource = 'chat') => (/** @type {SlashCommandExecutor} */ executor) => {
const source = executor.namedArgumentList.find(it => it.name == 'source')?.value ?? fallbackSource;
if (source instanceof SlashCommandClosure) throw new Error('Argument \'source\' does not support closures');
const attachments = getAttachments(source);
return attachments.map(attachment => new SlashCommandEnumValue(
returnField === 'name' ? attachment.name : attachment.url,
`${enumIcons.getStateIcon(!extension_settings.disabled_attachments.includes(attachment.url))} [${source}] ${returnField === 'url' ? attachment.name : attachment.url}`,
enumTypes.enum, enumIcons.file));
},
};
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'db',
callback: () => document.getElementById('manageAttachments')?.click(),
callback: () => {
document.getElementById('manageAttachments')?.click();
return '';
},
aliases: ['databank', 'data-bank'],
helpString: 'Open the data bank',
}));
@@ -224,7 +252,13 @@ jQuery(async () => {
new SlashCommandNamedArgument('source', 'The source of the attachment.', ARGUMENT_TYPE.STRING, false, false, '', TYPES),
],
unnamedArgumentList: [
new SlashCommandArgument('The name or URL of the attachment.', ARGUMENT_TYPE.STRING, true, false),
SlashCommandArgument.fromProps({
description: 'The name or URL of the attachment.',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
acceptsMultiple: false,
enumProvider: localEnumProviders.attachments('name', ''),
}),
],
returns: ARGUMENT_TYPE.STRING,
}));
@@ -251,8 +285,18 @@ jQuery(async () => {
helpString: 'Update an attachment in the Data Bank, preserving its name. Returns a new URL of the attachment.',
namedArgumentList: [
new SlashCommandNamedArgument('source', 'The source for the attachment.', ARGUMENT_TYPE.STRING, false, false, 'chat', TYPES),
new SlashCommandNamedArgument('name', 'The name of the attachment.', ARGUMENT_TYPE.STRING, false, false),
new SlashCommandNamedArgument('url', 'The URL of the attachment to update.', ARGUMENT_TYPE.STRING, false, false),
SlashCommandNamedArgument.fromProps({
name: 'name',
description: 'The name of the attachment.',
typeList: [ARGUMENT_TYPE.STRING],
enumProvider: localEnumProviders.attachments('name'),
}),
SlashCommandNamedArgument.fromProps({
name: 'url',
description: 'The URL of the attachment.',
typeList: [ARGUMENT_TYPE.STRING],
enumProvider: localEnumProviders.attachments('url'),
}),
],
unnamedArgumentList: [
new SlashCommandArgument('The content of the file attachment.', ARGUMENT_TYPE.STRING, true, false),
@@ -269,7 +313,12 @@ jQuery(async () => {
new SlashCommandNamedArgument('source', 'The source of the attachment.', ARGUMENT_TYPE.STRING, false, false, '', TYPES),
],
unnamedArgumentList: [
new SlashCommandArgument('The name or URL of the attachment.', ARGUMENT_TYPE.STRING, true, false),
SlashCommandArgument.fromProps({
description: 'The name or URL of the attachment.',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: localEnumProviders.attachments('name', ''),
}),
],
}));
@@ -282,7 +331,12 @@ jQuery(async () => {
new SlashCommandNamedArgument('source', 'The source of the attachment.', ARGUMENT_TYPE.STRING, false, false, '', TYPES),
],
unnamedArgumentList: [
new SlashCommandArgument('The name or URL of the attachment.', ARGUMENT_TYPE.STRING, true, false),
SlashCommandArgument.fromProps({
description: 'The name or URL of the attachment.',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: localEnumProviders.attachments('name', ''),
}),
],
}));
@@ -295,7 +349,12 @@ jQuery(async () => {
new SlashCommandNamedArgument('source', 'The source of the attachment.', ARGUMENT_TYPE.STRING, false, false, 'chat', TYPES),
],
unnamedArgumentList: [
new SlashCommandArgument('The name or URL of the attachment.', ARGUMENT_TYPE.STRING, true, false),
SlashCommandArgument.fromProps({
description: 'The name or URL of the attachment.',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: localEnumProviders.attachments(),
}),
],
}));
});

View File

@@ -1,7 +1,4 @@
<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>

View File

@@ -53,6 +53,14 @@
<i class="fa-solid fa-square"></i>
<span data-i18n="Select None">Select None</span>
</div>
<div class="menu_button menu_button_icon bulkActionDisable" title="Disable selected attachments">
<i class="fa-solid fa-comment-slash"></i>
<span data-i18n="Disable">Disable</span>
</div>
<div class="menu_button menu_button_icon bulkActionEnable" title="Enable selected attachments">
<i class="fa-solid fa-comment"></i>
<span data-i18n="Enable">Enable</span>
</div>
<div class="menu_button menu_button_icon bulkActionDelete" title="Delete selected attachments">
<i class="fa-solid fa-trash"></i>
<span data-i18n="Delete">Delete</span>
@@ -88,8 +96,8 @@
<div class="flex-container flexFlowColumn">
<strong><small class="characterAttachmentsName"></small></strong>
<small>
<span data-i18n="These files are available the current character in all chats they are in.">
These files are available the current character in all chats they are in.
<span data-i18n="These files are available for the current character in all chats they are in.">
These files are available for the current character in all chats they are in.
</span>
<span>
<span data-i18n="Saved locally. Not exported.">
@@ -113,8 +121,8 @@
</h3>
<div class="flex-container flexFlowColumn">
<strong><small class="chatAttachmentsName"></small></strong>
<small data-i18n="These files are available to all characters in the current chat.">
These files are available to all characters in the current chat.
<small data-i18n="These files are available for all characters in the current chat.">
These files are available for all characters in the current chat.
</small>
</div>
<div class="chatAttachmentsList attachmentsList"></div>

View File

@@ -1,6 +1,6 @@
import { getBase64Async, isTrueBoolean, saveBase64AsFile } from '../../utils.js';
import { ensureImageFormatSupported, getBase64Async, isTrueBoolean, saveBase64AsFile } from '../../utils.js';
import { getContext, getApiUrl, doExtrasFetch, extension_settings, modules, renderExtensionTemplateAsync } from '../../extensions.js';
import { callPopup, getRequestHeaders, saveSettingsDebounced, substituteParams } from '../../../script.js';
import { appendMediaToMessage, callPopup, eventSource, event_types, getRequestHeaders, saveChatConditional, saveSettingsDebounced, substituteParamsExtended } from '../../../script.js';
import { getMessageTimeStamp } from '../../RossAscends-mods.js';
import { SECRET_KEYS, secret_state } from '../../secrets.js';
import { getMultimodalCaption } from '../shared.js';
@@ -8,6 +8,8 @@ import { textgen_types, textgenerationwebui_settings } from '../../textgen-setti
import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
import { SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js';
import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
export { MODULE_NAME };
const MODULE_NAME = 'caption';
@@ -82,12 +84,11 @@ async function setSpinnerIcon() {
}
/**
* Sends a captioned message to the chat.
* @param {string} caption Caption text
* @param {string} image Image URL
* Wraps a caption with a message template.
* @param {string} caption Raw caption
* @returns {Promise<string>} Wrapped caption
*/
async function sendCaptionedMessage(caption, image) {
const context = getContext();
async function wrapCaptionTemplate(caption) {
let template = extension_settings.caption.template || TEMPLATE_DEFAULT;
if (!/{{caption}}/i.test(template)) {
@@ -95,11 +96,11 @@ async function sendCaptionedMessage(caption, image) {
template += ' {{caption}}';
}
let messageText = substituteParams(template).replace(/{{caption}}/i, caption);
let messageText = substituteParamsExtended(template, { caption: caption });
if (extension_settings.caption.refine_mode) {
messageText = await callPopup(
'<h3>Review and edit the generated message:</h3>Press "Cancel" to abort the caption sending.',
'<h3>Review and edit the generated caption:</h3>Press "Cancel" to abort the caption sending.',
'input',
messageText,
{ rows: 5, okButton: 'Send' });
@@ -109,6 +110,55 @@ async function sendCaptionedMessage(caption, image) {
}
}
return messageText;
}
/**
* Appends caption to an existing message.
* @param {Object} data Message data
* @returns {Promise<void>}
*/
async function captionExistingMessage(data) {
if (!(data?.extra?.image)) {
return;
}
const imageData = await fetch(data.extra.image);
const blob = await imageData.blob();
const type = imageData.headers.get('Content-Type');
const file = new File([blob], 'image.png', { type });
const caption = await getCaptionForFile(file, null, true);
if (!caption) {
console.warn('Failed to generate a caption for the image.');
return;
}
const wrappedCaption = await wrapCaptionTemplate(caption);
const messageText = String(data.mes).trim();
if (!messageText) {
data.extra.inline_image = false;
data.mes = wrappedCaption;
data.extra.title = wrappedCaption;
}
else {
data.extra.inline_image = true;
data.extra.append_title = true;
data.extra.title = wrappedCaption;
}
}
/**
* Sends a captioned message to the chat.
* @param {string} caption Caption text
* @param {string} image Image URL
*/
async function sendCaptionedMessage(caption, image) {
const messageText = await wrapCaptionTemplate(caption);
const context = getContext();
const message = {
name: context.name1,
is_user: true,
@@ -272,7 +322,7 @@ async function getCaptionForFile(file, prompt, quiet) {
try {
setSpinnerIcon();
const context = getContext();
const fileData = await getBase64Async(file);
const fileData = await getBase64Async(await ensureImageFormatSupported(file));
const base64Format = fileData.split(',')[0].split(';')[0].split('/')[1];
const base64Data = fileData.split(',')[1];
const { caption } = await doCaptionRequest(base64Data, fileData, prompt);
@@ -342,7 +392,7 @@ jQuery(async function () {
Generate Caption
</div>`);
$('#extensionsMenu').prepend(sendButton);
$('#caption_wand_container').append(sendButton);
$(sendButton).on('click', () => {
const hasCaptionModule =
(modules.includes('caption') && extension_settings.caption.source === 'extras') ||
@@ -354,6 +404,7 @@ jQuery(async function () {
(extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'llamacpp' && textgenerationwebui_settings.server_urls[textgen_types.LLAMACPP]) ||
(extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'ooba' && textgenerationwebui_settings.server_urls[textgen_types.OOBA]) ||
(extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'koboldcpp' && textgenerationwebui_settings.server_urls[textgen_types.KOBOLDCPP]) ||
(extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'vllm' && textgenerationwebui_settings.server_urls[textgen_types.VLLM]) ||
(extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'custom') ||
extension_settings.caption.source === 'local' ||
extension_settings.caption.source === 'horde';
@@ -377,6 +428,12 @@ jQuery(async function () {
}
function switchMultimodalBlocks() {
const isMultimodal = extension_settings.caption.source === 'multimodal';
$('#caption_ollama_pull').on('click', (e) => {
const presetModel = extension_settings.caption.multimodal_model !== 'ollama_current' ? extension_settings.caption.multimodal_model : '';
e.preventDefault();
$('#ollama_download_model').trigger('click');
$('#dialogue_popup_input').val(presetModel);
});
$('#caption_multimodal_block').toggle(isMultimodal);
$('#caption_prompt_block').toggle(isMultimodal);
$('#caption_multimodal_api').val(extension_settings.caption.multimodal_api);
@@ -400,8 +457,8 @@ jQuery(async function () {
});
}
async function addSettings() {
const html = await renderExtensionTemplateAsync('caption', 'settings');
$('#extensions_settings2').append(html);
const html = await renderExtensionTemplateAsync('caption', 'settings', { TEMPLATE_DEFAULT, PROMPT_DEFAULT });
$('#caption_container').append(html);
}
await addSettings();
@@ -414,6 +471,7 @@ jQuery(async function () {
$('#caption_refine_mode').prop('checked', !!(extension_settings.caption.refine_mode));
$('#caption_allow_reverse_proxy').prop('checked', !!(extension_settings.caption.allow_reverse_proxy));
$('#caption_prompt_ask').prop('checked', !!(extension_settings.caption.prompt_ask));
$('#caption_auto_mode').prop('checked', !!(extension_settings.caption.auto_mode));
$('#caption_source').val(extension_settings.caption.source);
$('#caption_prompt').val(extension_settings.caption.prompt);
$('#caption_template').val(extension_settings.caption.template);
@@ -439,17 +497,50 @@ jQuery(async function () {
extension_settings.caption.prompt_ask = $('#caption_prompt_ask').prop('checked');
saveSettingsDebounced();
});
$('#caption_auto_mode').on('input', () => {
extension_settings.caption.auto_mode = !!$('#caption_auto_mode').prop('checked');
saveSettingsDebounced();
});
const onMessageEvent = async (index) => {
if (!extension_settings.caption.auto_mode) {
return;
}
const data = getContext().chat[index];
await captionExistingMessage(data);
};
eventSource.on(event_types.MESSAGE_SENT, onMessageEvent);
eventSource.on(event_types.MESSAGE_FILE_EMBEDDED, onMessageEvent);
$(document).on('click', '.mes_img_caption', async function () {
const animationClass = 'fa-fade';
const messageBlock = $(this).closest('.mes');
const messageImg = messageBlock.find('.mes_img');
if (messageImg.hasClass(animationClass)) return;
messageImg.addClass(animationClass);
const index = Number(messageBlock.attr('mesid'));
const data = getContext().chat[index];
await captionExistingMessage(data);
appendMediaToMessage(data, messageBlock, false);
await saveChatConditional();
messageImg.removeClass(animationClass);
});
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'caption',
callback: captionCommandCallback,
returns: 'caption',
namedArgumentList: [
new SlashCommandNamedArgument(
'quiet', 'suppress sending a captioned message', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false', ['true', 'false'],
),
new SlashCommandNamedArgument(
'id', 'get image from a message with this ID', [ARGUMENT_TYPE.NUMBER], false, false,
'quiet', 'suppress sending a captioned message', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false',
),
SlashCommandNamedArgument.fromProps({
name: 'id',
description: 'get image from a message with this ID',
typeList: [ARGUMENT_TYPE.NUMBER],
enumProvider: commonEnumProviders.messages(),
}),
],
unnamedArgumentList: [
new SlashCommandArgument(
@@ -471,4 +562,6 @@ jQuery(async function () {
</div>
`,
}));
document.body.classList.add('caption');
});

View File

@@ -2,38 +2,40 @@
<div class="caption_settings">
<div class="inline-drawer">
<div class="inline-drawer-toggle inline-drawer-header">
<b>Image Captioning</b>
<b data-i18n="Image Captioning">Image Captioning</b>
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
</div>
<div class="inline-drawer-content">
<label for="caption_source">Source</label>
<label for="caption_source" data-i18n="Source">Source</label>
<select id="caption_source" class="text_pole">
<option value="local">Local</option>
<option value="multimodal">Multimodal (OpenAI / Anthropic / llama / Google)</option>
<option value="extras">Extras</option>
<option value="horde">Horde</option>
<option value="local" data-i18n="Local">Local</option>
<option value="multimodal" data-i18n="Multimodal (OpenAI / Anthropic / llama / Google)">Multimodal (OpenAI / Anthropic / llama / Google)</option>
<option value="extras" data-i18n="Extras">Extras</option>
<option value="horde" data-i18n="Horde">Horde</option>
</select>
<div id="caption_multimodal_block" class="flex-container wide100p">
<div class="flex1 flex-container flexFlowColumn flexNoGap">
<label for="caption_multimodal_api">API</label>
<label for="caption_multimodal_api" data-i18n="API">API</label>
<select id="caption_multimodal_api" class="flex1 text_pole">
<option value="anthropic">Anthropic</option>
<option value="custom">Custom (OpenAI-compatible)</option>
<option value="custom" data-i18n="Custom (OpenAI-compatible)">Custom (OpenAI-compatible)</option>
<option value="google">Google MakerSuite</option>
<option value="koboldcpp">KoboldCpp</option>
<option value="llamacpp">llama.cpp</option>
<option value="ollama">Ollama</option>
<option value="openai">OpenAI</option>
<option value="openrouter">OpenRouter</option>
<option value="ooba">Text Generation WebUI (oobabooga)</option>
<option value="ooba" data-i18n="Text Generation WebUI (oobabooga)">Text Generation WebUI (oobabooga)</option>
<option value="vllm">vLLM</option>
</select>
</div>
<div class="flex1 flex-container flexFlowColumn flexNoGap">
<label for="caption_multimodal_model">Model</label>
<label for="caption_multimodal_model" data-i18n="Model">Model</label>
<select id="caption_multimodal_model" class="flex1 text_pole">
<option data-type="openai" value="gpt-4-vision-preview">gpt-4-vision-preview</option>
<option data-type="openai" value="gpt-4-turbo">gpt-4-turbo</option>
<option data-type="openai" value="gpt-4o">gpt-4o</option>
<option data-type="anthropic" value="claude-3-5-sonnet-20240620">claude-3-5-sonnet-20240620</option>
<option data-type="anthropic" value="claude-3-opus-20240229">claude-3-opus-20240229</option>
<option data-type="anthropic" value="claude-3-sonnet-20240229">claude-3-sonnet-20240229</option>
<option data-type="anthropic" value="claude-3-haiku-20240307">claude-3-haiku-20240307</option>
@@ -44,9 +46,11 @@
<option data-type="openrouter" value="openai/gpt-4-turbo">openai/gpt-4-turbo</option>
<option data-type="openrouter" value="haotian-liu/llava-13b">haotian-liu/llava-13b</option>
<option data-type="openrouter" value="fireworks/firellava-13b">fireworks/firellava-13b</option>
<option data-type="openrouter" value="anthropic/claude-3.5-sonnet">anthropic/claude-3.5-sonnet</option>
<option data-type="openrouter" value="anthropic/claude-3-haiku">anthropic/claude-3-haiku</option>
<option data-type="openrouter" value="anthropic/claude-3-sonnet">anthropic/claude-3-sonnet</option>
<option data-type="openrouter" value="anthropic/claude-3-opus">anthropic/claude-3-opus</option>
<option data-type="openrouter" value="anthropic/claude-3.5-sonnet:beta">anthropic/claude-3.5-sonnet:beta</option>
<option data-type="openrouter" value="anthropic/claude-3-haiku:beta">anthropic/claude-3-haiku:beta</option>
<option data-type="openrouter" value="anthropic/claude-3-sonnet:beta">anthropic/claude-3-sonnet:beta</option>
<option data-type="openrouter" value="anthropic/claude-3-opus:beta">anthropic/claude-3-opus:beta</option>
@@ -54,36 +58,48 @@
<option data-type="openrouter" value="google/gemini-pro-vision">google/gemini-pro-vision</option>
<option data-type="openrouter" value="google/gemini-flash-1.5">google/gemini-flash-1.5</option>
<option data-type="openrouter" value="liuhaotian/llava-yi-34b">liuhaotian/llava-yi-34b</option>
<option data-type="ollama" value="ollama_current">[Currently selected]</option>
<option data-type="ollama" value="bakllava:latest">bakllava:latest</option>
<option data-type="ollama" value="llava:latest">llava:latest</option>
<option data-type="llamacpp" value="llamacpp_current">[Currently loaded]</option>
<option data-type="ooba" value="ooba_current">[Currently loaded]</option>
<option data-type="koboldcpp" value="koboldcpp_current">[Currently loaded]</option>
<option data-type="custom" value="custom_current">[Currently selected]</option>
<option data-type="ollama" value="ollama_current" data-i18n="currently_selected">[Currently selected]</option>
<option data-type="ollama" value="bakllava">bakllava</option>
<option data-type="ollama" value="llava">llava</option>
<option data-type="ollama" value="llava-llama3">llava-llama3</option>
<option data-type="ollama" value="llava-phi3">llava-phi3</option>
<option data-type="ollama" value="moondream">moondream</option>
<option data-type="llamacpp" value="llamacpp_current" data-i18n="currently_loaded">[Currently loaded]</option>
<option data-type="ooba" value="ooba_current" data-i18n="currently_loaded">[Currently loaded]</option>
<option data-type="koboldcpp" value="koboldcpp_current" data-i18n="currently_loaded">[Currently loaded]</option>
<option data-type="vllm" value="vllm_current" data-i18n="currently_selected">[Currently selected]</option>
<option data-type="custom" value="custom_current" data-i18n="currently_selected">[Currently selected]</option>
</select>
</div>
<div data-type="ollama">
The model must be downloaded first! Do it with the <code>ollama pull</code> command or <a href="#" id="caption_ollama_pull">click here</a>.
</div>
<label data-type="openai,anthropic,google" class="checkbox_label flexBasis100p" for="caption_allow_reverse_proxy" title="Allow using reverse proxy if defined and valid.">
<input id="caption_allow_reverse_proxy" type="checkbox" class="checkbox">
Allow reverse proxy
<span data-i18n="Allow reverse proxy">Allow reverse proxy</span>
</label>
<div class="flexBasis100p m-b-1">
<small><b>Hint:</b> Set your API keys and endpoints in the 'API Connections' tab first.</small>
<small><b data-i18n="Hint:">Hint:</b> <span data-i18n="Set your API keys and endpoints in the 'API Connections' tab first.">Set your API keys and endpoints in the 'API Connections' tab first.</span></small>
</div>
</div>
<div id="caption_prompt_block">
<label for="caption_prompt">Caption Prompt</label>
<textarea id="caption_prompt" class="text_pole" rows="1" placeholder="&lt; Use default &gt;">${PROMPT_DEFAULT}</textarea>
<label for="caption_prompt" data-i18n="Caption Prompt">Caption Prompt</label>
<textarea id="caption_prompt" class="text_pole" rows="1" placeholder="&lt; Use default &gt;">{{PROMPT_DEFAULT}}</textarea>
<label class="checkbox_label margin-bot-10px" for="caption_prompt_ask" title="Ask for a custom prompt every time an image is captioned.">
<input id="caption_prompt_ask" type="checkbox" class="checkbox">
Ask every time
<span data-i18n="Ask every time">Ask every time</span>
</label>
</div>
<label for="caption_template">Message Template <small>(use <code>&lcub;&lcub;caption&rcub;&rcub;</code> macro)</small></label>
<textarea id="caption_template" class="text_pole" rows="2" placeholder="&lt; Use default &gt;">${TEMPLATE_DEFAULT}</textarea>
<label for="caption_template"><span data-i18n="Message Template">Message Template</span> <small><span data-i18n="(use _space">(use </span> <code>&lcub;&lcub;caption&rcub;&rcub;</code> <span data-i18n="macro)">macro)</span></small></label>
<textarea id="caption_template" class="text_pole" rows="2" placeholder="&lt; Use default &gt;">{{TEMPLATE_DEFAULT}}</textarea>
<label class="checkbox_label" for="caption_auto_mode">
<input id="caption_auto_mode" type="checkbox" class="checkbox">
<span data-i18n="Automatically caption images">Automatically caption images</span>
<i class="fa-solid fa-info-circle" title="Automatically caption images when they are pasted into the chat or attached to messages."></i>
</label>
<label class="checkbox_label margin-bot-10px" for="caption_refine_mode">
<input id="caption_refine_mode" type="checkbox" class="checkbox">
Edit captions before saving
<span data-i18n="Edit captions before saving">Edit captions before saving</span>
</label>
</div>
</div>

View File

@@ -1,4 +1,4 @@
import { callPopup, eventSource, event_types, generateQuietPrompt, getRequestHeaders, online_status, saveSettingsDebounced, substituteParams } from '../../../script.js';
import { callPopup, eventSource, event_types, generateQuietPrompt, getRequestHeaders, online_status, saveSettingsDebounced, substituteParams, substituteParamsExtended, system_message_types } from '../../../script.js';
import { dragElement, isMobile } from '../../RossAscends-mods.js';
import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch, renderExtensionTemplateAsync } from '../../extensions.js';
import { loadMovingUIState, power_user } from '../../power-user.js';
@@ -10,6 +10,8 @@ import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
import { ARGUMENT_TYPE, SlashCommandArgument } from '../../slash-commands/SlashCommandArgument.js';
import { isFunctionCallingSupported } from '../../openai.js';
import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashCommandEnumValue.js';
import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
export { MODULE_NAME };
const MODULE_NAME = 'expressions';
@@ -87,6 +89,7 @@ function getFallbackExpression() {
*/
function toggleTalkingHeadCommand(_) {
setTalkingHeadState(!extension_settings.expressions.talkinghead);
return String(extension_settings.expressions.talkinghead);
}
function isVisualNovelMode() {
@@ -914,6 +917,7 @@ async function setSpriteSetCommand(_, folder) {
// moduleWorker();
const vnMode = isVisualNovelMode();
await sendExpressionCall(folder, lastExpression, true, vnMode);
return '';
}
async function classifyCommand(_, text) {
@@ -935,7 +939,7 @@ async function classifyCommand(_, text) {
async function setSpriteSlashCommand(_, spriteId) {
if (!spriteId) {
console.log('No sprite id provided');
return;
return '';
}
spriteId = spriteId.trim().toLowerCase();
@@ -955,7 +959,7 @@ async function setSpriteSlashCommand(_, spriteId) {
if (!spriteItem) {
console.log('No sprite found for search term ' + spriteId);
return;
return '';
}
label = spriteItem.label;
@@ -963,6 +967,7 @@ async function setSpriteSlashCommand(_, spriteId) {
const vnMode = isVisualNovelMode();
await sendExpressionCall(spriteFolderName, label, true, vnMode);
return label;
}
/**
@@ -1008,8 +1013,7 @@ async function getLlmPrompt(labels) {
}
const labelsString = labels.map(x => `"${x}"`).join(', ');
const prompt = substituteParams(String(extension_settings.expressions.llmPrompt))
.replace(/{{labels}}/gi, labelsString);
const prompt = substituteParamsExtended(String(extension_settings.expressions.llmPrompt), { labels: labelsString });
return prompt;
}
@@ -1187,7 +1191,7 @@ function getLastCharacterMessage() {
const reversedChat = context.chat.slice().reverse();
for (let mes of reversedChat) {
if (mes.is_user || mes.is_system) {
if (mes.is_user || mes.is_system || mes.extra?.type === system_message_types.NARRATOR) {
continue;
}
@@ -1326,10 +1330,18 @@ async function renderFallbackExpressionPicker() {
}
}
function getCachedExpressions() {
if (!Array.isArray(expressionsList)) {
return [];
}
return [...expressionsList, ...extension_settings.expressions.custom].filter(onlyUnique);
}
async function getExpressionsList() {
// Return cached list if available
if (Array.isArray(expressionsList)) {
return [...expressionsList, ...extension_settings.expressions.custom].filter(onlyUnique);
return getCachedExpressions();
}
/**
@@ -1917,7 +1929,7 @@ function migrateSettings() {
}
async function addSettings() {
const template = await renderExtensionTemplateAsync(MODULE_NAME, 'settings');
$('#extensions_settings').append(template);
$('#expressions_container').append(template);
$('#expression_override_button').on('click', onClickExpressionOverrideButton);
$('#expressions_show_default').on('input', onExpressionsShowDefaultInput);
$('#expression_upload_pack_button').on('click', onClickExpressionUploadPackButton);
@@ -2033,17 +2045,31 @@ function migrateSettings() {
});
eventSource.on(event_types.MOVABLE_PANELS_RESET, updateVisualNovelModeDebounced);
eventSource.on(event_types.GROUP_UPDATED, updateVisualNovelModeDebounced);
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'sprite',
const localEnumProviders = {
expressions: () => getCachedExpressions().map(expression => {
const isCustom = extension_settings.expressions.custom?.includes(expression);
return new SlashCommandEnumValue(expression, null, isCustom ? enumTypes.name : enumTypes.enum, isCustom ? 'C' : 'D');
}),
};
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'sprite',
aliases: ['emote'],
callback: setSpriteSlashCommand,
unnamedArgumentList: [
new SlashCommandArgument(
'spriteId', [ARGUMENT_TYPE.STRING], true,
),
SlashCommandArgument.fromProps({
description: 'spriteId',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: localEnumProviders.expressions,
}),
],
helpString: 'Force sets the sprite for the current character.',
returns: 'label',
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'spriteoverride',
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'spriteoverride',
aliases: ['costume'],
callback: setSpriteSetCommand,
unnamedArgumentList: [
@@ -2053,22 +2079,29 @@ function migrateSettings() {
],
helpString: 'Sets an override sprite folder for the current character. If the name starts with a slash or a backslash, selects a sub-folder in the character-named folder. Empty value to reset to default.',
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'lastsprite',
callback: (_, value) => lastExpression[value.trim()] ?? '',
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'lastsprite',
callback: (_, value) => lastExpression[String(value).trim()] ?? '',
returns: 'sprite',
unnamedArgumentList: [
new SlashCommandArgument(
'charName', [ARGUMENT_TYPE.STRING], true,
),
SlashCommandArgument.fromProps({
description: 'character name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: commonEnumProviders.characters('character'),
}),
],
helpString: 'Returns the last set sprite / expression for the named character.',
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'th',
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'th',
callback: toggleTalkingHeadCommand,
aliases: ['talkinghead'],
helpString: 'Character Expressions: toggles <i>Image Type - talkinghead (extras)</i> on/off.',
returns: ARGUMENT_TYPE.BOOLEAN,
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'classify',
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'classify',
callback: classifyCommand,
unnamedArgumentList: [
new SlashCommandArgument(

View File

@@ -1,65 +1,65 @@
<div class="expression_settings">
<div class="inline-drawer">
<div class="inline-drawer-toggle inline-drawer-header">
<b>Character Expressions</b>
<b data-i18n="Character Expressions">Character Expressions</b>
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
</div>
<div class="inline-drawer-content">
<label class="checkbox_label" for="expression_translate" title="Use the selected API from Chat Translation extension settings.">
<input id="expression_translate" type="checkbox">
<span>Translate text to English before classification</span>
<span data-i18n="Translate text to English before classification">Translate text to English before classification</span>
</label>
<label class="checkbox_label" for="expressions_show_default">
<input id="expressions_show_default" type="checkbox">
<span>Show default images (emojis) if sprite missing</span>
<span data-i18n="Show default images (emojis) if sprite missing">Show default images (emojis) if sprite missing</span>
</label>
<label id="image_type_block" class="checkbox_label" for="image_type_toggle">
<input id="image_type_toggle" type="checkbox">
<span>Image Type - talkinghead (extras)</span>
<span data-i18n="Image Type - talkinghead (extras)">Image Type - talkinghead (extras)</span>
</label>
<div class="expression_api_block m-b-1 m-t-1">
<label for="expression_api">Classifier API</label>
<small>Select the API for classifying expressions.</small>
<select id="expression_api" class="flex1 margin0" data-i18n="Expression API" placeholder="Expression API">
<option value="0">Local</option>
<option value="1">Extras</option>
<option value="2">LLM</option>
<label for="expression_api" data-i18n="Classifier API">Classifier API</label>
<small data-i18n="Select the API for classifying expressions.">Select the API for classifying expressions.</small>
<select id="expression_api" class="flex1 margin0">
<option value="0" data-i18n="Local">Local</option>
<option value="1" data-i18n="Extras">Extras</option>
<option value="2" data-i18n="LLM">LLM</option>
</select>
</div>
<div class="expression_llm_prompt_block m-b-1 m-t-1">
<label for="expression_llm_prompt" class="title_restorable">
<span>LLM Prompt</span>
<span data-i18n="LLM Prompt">LLM Prompt</span>
<div id="expression_llm_prompt_restore" title="Restore default value" class="right_menu_button">
<i class="fa-solid fa-clock-rotate-left fa-sm"></i>
</div>
</label>
<small>Will be used if the API doesn't support JSON schemas or function calling.</small>
<small data-i18n="Will be used if the API doesn't support JSON schemas or function calling.">Will be used if the API doesn't support JSON schemas or function calling.</small>
<textarea id="expression_llm_prompt" type="text" class="text_pole textarea_compact" rows="2" placeholder="Use &lcub;&lcub;labels&rcub;&rcub; special macro."></textarea>
</div>
<div class="expression_fallback_block m-b-1 m-t-1">
<label for="expression_fallback">Default / Fallback Expression</label>
<small>Set the default and fallback expression being used when no matching expression is found.</small>
<label for="expression_fallback" data-i18n="Default / Fallback Expression">Default / Fallback Expression</label>
<small data-i18n="Set the default and fallback expression being used when no matching expression is found.">Set the default and fallback expression being used when no matching expression is found.</small>
<select id="expression_fallback" class="flex1 margin0" data-i18n="Fallback Expression" placeholder="Fallback Expression"></select>
</div>
<div class="expression_custom_block m-b-1 m-t-1">
<label for="expression_custom">Custom Expressions</label>
<small>Can be set manually or with an <tt>/emote</tt> slash command.</small>
<label for="expression_custom" data-i18n="Custom Expressions">Custom Expressions</label>
<small><span data-i18n="Can be set manually or with an _space">Can be set manually or with an </span><tt>/emote</tt><span data-i18n="space_ slash command."> slash command.</span></small>
<div class="flex-container">
<select id="expression_custom" class="flex1 margin0"><select>
<i id="expression_custom_add" class="menu_button fa-solid fa-plus margin0" title="Add"></i>
<i id="expression_custom_remove" class="menu_button fa-solid fa-xmark margin0" title="Remove"></i>
</div>
</div>
<div id="no_chat_expressions">
<div id="no_chat_expressions" data-i18n="Open a chat to see the character expressions.">
Open a chat to see the character expressions.
</div>
<div id="open_chat_expressions">
<div class="offline_mode">
<small>You are in offline mode. Click on the image below to set the expression.</small>
<small data-i18n="You are in offline mode. Click on the image below to set the expression.">You are in offline mode. Click on the image below to set the expression.</small>
</div>
<label for="expression_override">Sprite Folder Override</label>
<small>Use a forward slash to specify a subfolder. Example: <tt>Bob/formal</tt></small>
<label for="expression_override" data-i18n="Sprite Folder Override">Sprite Folder Override</label>
<small><span data-i18n="Use a forward slash to specify a subfolder. Example: _space">Use a forward slash to specify a subfolder. Example: </span><tt>Bob/formal</tt></small>
<div class="flex-container flexnowrap">
<input id="expression_override" type="text" class="text_pole" placeholder="Override folder name" />
<input id="expression_override_button" class="menu_button" type="submit" value="Submit" />
@@ -67,17 +67,17 @@
<div class="expression_buttons flex-container spaceEvenly">
<div id="expression_upload_pack_button" class="menu_button">
<i class="fa-solid fa-file-zipper"></i>
<span>Upload sprite pack (ZIP)</span>
<span data-i18n="Upload sprite pack (ZIP)">Upload sprite pack (ZIP)</span>
</div>
<div id="expression_override_cleanup_button" class="menu_button">
<i class="fa-solid fa-trash-can"></i>
<span>Remove all image overrides</span>
<span data-i18n="Remove all image overrides">Remove all image overrides</span>
</div>
</div>
<p class="hint"><b>Hint:</b> <i>Create new folder in the <b>/characters/</b> folder of your user data directory and name it as the name of the character.
Put images with expressions there. File names should follow the pattern: <tt>[expression_label].[image_format]</tt></i></p>
<p class="hint"><b data-i18n="Hint:">Hint:</b> <i><span data-i18n="Create new folder in the _space">Create new folder in the </span><b>/characters/</b> <span data-i18n="folder of your user data directory and name it as the name of the character.">folder of your user data directory and name it as the name of the character.</span>
<span data-i18n="Put images with expressions there. File names should follow the pattern:">Put images with expressions there. File names should follow the pattern: </span><tt data-i18n="expression_label_pattern">[expression_label].[image_format]</tt></i></p>
<h3 id="image_list_header">
<strong>Sprite set:</strong>&nbsp;<span id="image_list_header_name"></span>
<strong data-i18n="Sprite set:">Sprite set:</strong>&nbsp;<span id="image_list_header_name"></span>
</h3>
<div id="image_list"></div>

View File

@@ -11,6 +11,8 @@ import { dragElement } from '../../RossAscends-mods.js';
import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
import { ARGUMENT_TYPE, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
import { DragAndDropHandler } from '../../dragdrop.js';
import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
const extensionName = 'gallery';
const extensionFolderPath = `scripts/extensions/${extensionName}/`;
@@ -56,7 +58,8 @@ async function getGalleryItems(url) {
* @returns {Promise<void>} - Promise representing the completion of the gallery initialization.
*/
async function initGallery(items, url) {
$('#dragGallery').nanogallery2({
const gallery = $('#dragGallery');
gallery.nanogallery2({
'items': items,
thumbnailWidth: 'auto',
thumbnailHeight: thumbnailHeight,
@@ -80,44 +83,24 @@ async function initGallery(items, url) {
eventSource.on('resizeUI', function (elmntName) {
jQuery('#dragGallery').nanogallery2('resize');
gallery.nanogallery2('resize');
});
const dropZone = $('#dragGallery');
//remove any existing handlers
dropZone.off('dragover');
dropZone.off('dragleave');
dropZone.off('drop');
// Set dropzone height to be the same as the parent
dropZone.css('height', dropZone.parent().css('height'));
// Initialize dropzone handlers
dropZone.on('dragover', function (e) {
e.stopPropagation(); // Ensure this event doesn't propagate
e.preventDefault();
$(this).addClass('dragging'); // Add a CSS class to change appearance during drag-over
});
dropZone.on('dragleave', function (e) {
e.stopPropagation(); // Ensure this event doesn't propagate
$(this).removeClass('dragging');
});
dropZone.on('drop', function (e) {
e.stopPropagation(); // Ensure this event doesn't propagate
e.preventDefault();
$(this).removeClass('dragging');
let file = e.originalEvent.dataTransfer.files[0];
const dragDropHandler = new DragAndDropHandler('#dragGallery', async (files, event) => {
let file = files[0];
uploadFile(file, url); // Added url parameter to know where to upload
});
// Set dropzone height to be the same as the parent
gallery.css('height', gallery.parent().css('height'));
//let images populate first
await delay(100);
//unset the height (which must be getting set by the gallery library at some point)
$('#dragGallery').css('height', 'unset');
gallery.css('height', 'unset');
//force a resize to make images display correctly
jQuery('#dragGallery').nanogallery2('resize');
gallery.nanogallery2('resize');
}
/**
@@ -419,7 +402,10 @@ function viewWithDragbox(items) {
// Registers a simple command for opening the char gallery.
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'show-gallery',
aliases: ['sg'],
callback: showGalleryCommand,
callback: () => {
showCharGallery();
return '';
},
helpString: 'Shows the gallery.',
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'list-gallery',
@@ -427,21 +413,22 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'list-gallery
callback: listGalleryCommand,
returns: 'list of images',
namedArgumentList: [
new SlashCommandNamedArgument(
'char', 'character name', [ARGUMENT_TYPE.STRING], false,
),
new SlashCommandNamedArgument(
'group', 'group name', [ARGUMENT_TYPE.STRING], false,
),
SlashCommandNamedArgument.fromProps({
name: 'char',
description: 'character name',
typeList: [ARGUMENT_TYPE.STRING],
enumProvider: commonEnumProviders.characters('character'),
}),
SlashCommandNamedArgument.fromProps({
name: 'group',
description: 'group name',
typeList: [ARGUMENT_TYPE.STRING],
enumProvider: commonEnumProviders.characters('group'),
}),
],
helpString: 'List images in the gallery of the current char / group or a specified char / group.',
}));
function showGalleryCommand(args) {
showCharGallery();
}
async function listGalleryCommand(args) {
try {
let url = args.char ?? (args.group ? groups.find(it=>it.name == args.group)?.id : null) ?? (selected_group || this_chid);

View File

@@ -11,7 +11,7 @@ import {
generateQuietPrompt,
is_send_press,
saveSettingsDebounced,
substituteParams,
substituteParamsExtended,
generateRaw,
getMaxContextSize,
} from '../../../script.js';
@@ -24,6 +24,7 @@ import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
import { resolveVariable } from '../../variables.js';
import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
export { MODULE_NAME };
const MODULE_NAME = '1_memory';
@@ -43,8 +44,7 @@ const formatMemoryValue = function (value) {
value = value.trim();
if (extension_settings.memory.template) {
let result = extension_settings.memory.template.replace(/{{summary}}/i, value);
return substituteParams(result);
return substituteParamsExtended(extension_settings.memory.template, { summary: value });
} else {
return `Summary: ${value}`;
}
@@ -447,7 +447,7 @@ async function summarizeCallback(args, text) {
}
const source = args.source || extension_settings.memory.source;
const prompt = substituteParams((resolveVariable(args.prompt) || extension_settings.memory.prompt)?.replace(/{{words}}/gi, extension_settings.memory.promptWords));
const prompt = substituteParamsExtended((args.prompt || extension_settings.memory.prompt), { words: extension_settings.memory.promptWords });
try {
switch (source) {
@@ -534,7 +534,7 @@ async function summarizeChatMain(context, force, skipWIAN) {
}
console.log('Summarizing chat, messages since last summary: ' + messagesSinceLastSummary, 'words since last summary: ' + wordsSinceLastSummary);
const prompt = extension_settings.memory.prompt?.replace(/{{words}}/gi, extension_settings.memory.promptWords);
const prompt = substituteParamsExtended(extension_settings.memory.prompt, { words: extension_settings.memory.promptWords });
if (!prompt) {
console.debug('Summarization prompt is empty. Skipping summarization.');
@@ -900,7 +900,7 @@ function setupListeners() {
jQuery(async function () {
async function addExtensionControls() {
const settingsHtml = await renderExtensionTemplateAsync('memory', 'settings', { defaultSettings });
$('#extensions_settings2').append(settingsHtml);
$('#summarize_container').append(settingsHtml);
setupListeners();
$('#summaryExtensionPopoutButton').off('click').on('click', function (e) {
doPopout(e);
@@ -920,7 +920,12 @@ jQuery(async function () {
callback: summarizeCallback,
namedArgumentList: [
new SlashCommandNamedArgument('source', 'API to use for summarization', [ARGUMENT_TYPE.STRING], false, false, '', ['main', 'extras']),
new SlashCommandNamedArgument('prompt', 'prompt to use for summarization', [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.VARIABLE_NAME], false, false, ''),
SlashCommandNamedArgument.fromProps({
name: 'prompt',
description: 'prompt to use for summarization',
typeList: [ARGUMENT_TYPE.STRING],
defaultValue: '',
}),
],
unnamedArgumentList: [
new SlashCommandArgument('text to summarize', [ARGUMENT_TYPE.STRING], false, false, ''),

View File

@@ -2,7 +2,7 @@
<div class="inline-drawer">
<div class="inline-drawer-toggle inline-drawer-header">
<div class="flex-container alignitemscenter margin0">
<b>Summarize</b>
<b data-i18n="ext_sum_title">Summarize</b>
<i id="summaryExtensionPopoutButton" class="fa-solid fa-window-restore menu_button margin0"></i>
</div>
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
@@ -24,7 +24,7 @@
<textarea id="memory_contents" class="text_pole textarea_compact" rows="6" data-i18n="[placeholder]ext_sum_memory_placeholder" placeholder="Summary will be generated here..."></textarea>
<div class="memory_contents_controls">
<div id="memory_force_summarize" data-summary-source="main" class="menu_button menu_button_icon" data-i18n="[title]ext_sum_force_tip" title="Trigger a summary update right now." data-i18n="Trigger a summary update right now.">
<div id="memory_force_summarize" data-summary-source="main" class="menu_button menu_button_icon" title="Trigger a summary update right now." data-i18n="[title]ext_sum_force_tip">
<i class="fa-solid fa-database"></i>
<span data-i18n="ext_sum_force_text">Summarize now</span>
</div>

View File

@@ -1,38 +1,42 @@
<div id="qr--modalEditor">
<div id="qr--main">
<h3>Labels and Message</h3>
<h3 data-i18n="Labels and Message">Labels and Message</h3>
<div class="qr--labels">
<label>
<span class="qr--labelText">Label</span>
<span class="qr--labelText" data-i18n="Label">Label</span>
<input type="text" class="text_pole" id="qr--modal-label">
</label>
<label>
<span class="qr--labelText">Title</span>
<small class="qr--labelHint">(tooltip, leave empty to show message or /command)</small>
<span class="qr--labelText" data-i18n="Title">Title</span>
<small class="qr--labelHint" data-i18n="(tooltip, leave empty to show message or /command)">(tooltip, leave empty to show message or /command)</small>
<input type="text" class="text_pole" id="qr--modal-title">
</label>
</div>
<div class="qr--modal-messageContainer">
<label for="qr--modal-message">
<label for="qr--modal-message" data-i18n="Message / Command:">
Message / Command:
</label>
<div class="qr--modal-editorSettings">
<label class="checkbox_label">
<input type="checkbox" id="qr--modal-wrap">
<span>Word wrap</span>
<span data-i18n="Word wrap">Word wrap</span>
</label>
<label class="checkbox_label">
<span>Tab size:</span>
<span data-i18n="Tab size:">Tab size:</span>
<input type="number" min="1" max="9" id="qr--modal-tabSize" class="text_pole">
</label>
<label class="checkbox_label">
<input type="checkbox" id="qr--modal-executeShortcut">
<span>Ctrl+Enter to execute</span>
<span data-i18n="Ctrl+Enter to execute">Ctrl+Enter to execute</span>
</label>
<label class="checkbox_label">
<input type="checkbox" id="qr--modal-syntax">
<span>Syntax highlight</span>
</label>
</div>
<div id="qr--modal-messageHolder">
<pre id="qr--modal-messageSyntax"><code id="qr--modal-messageSyntaxInner" class="hljs language-stscript"></code></pre>
<textarea class="monospace" id="qr--modal-message" spellcheck="false"></textarea>
<textarea id="qr--modal-message" spellcheck="false"></textarea>
</div>
</div>
</div>
@@ -40,14 +44,14 @@
<div id="qr--qrOptions">
<h3>Context Menu</h3>
<h3 data-i18n="Context Menu">Context Menu</h3>
<div id="qr--ctxEditor">
<template id="qr--ctxItem">
<div class="qr--ctxItem" data-order="0">
<div class="drag-handle ui-sortable-handle"></div>
<select class="qr--set"></select>
<label class="qr--isChainedLabel checkbox_label" title="When enabled, the current Quick Reply will be sent together with (before) the clicked QR from the context menu.">
Chaining:
<span data-i18n="Chaining:">Chaining:</span>
<input type="checkbox" class="qr--isChained">
</label>
<div class="qr--delete menu_button menu_button_icon fa-solid fa-trash-can" title="Remove entry"></div>
@@ -59,48 +63,48 @@
</div>
<h3>Auto-Execute</h3>
<h3 data-i18n="Auto-Execute">Auto-Execute</h3>
<div class="flex-container flexFlowColumn">
<label class="checkbox_label" title="Prevent this quick reply from triggering other auto-executed quick replies while auto-executing (i.e., prevent recursive auto-execution)">
<input type="checkbox" id="qr--preventAutoExecute" >
<span><i class="fa-solid fa-fw fa-plane-slash"></i> Don't trigger auto-execute</span>
<span><i class="fa-solid fa-fw fa-plane-slash"></i><span data-i18n="Don't trigger auto-execute">Don't trigger auto-execute</span></span>
</label>
<label class="checkbox_label">
<input type="checkbox" id="qr--isHidden" >
<span><i class="fa-solid fa-fw fa-eye-slash"></i> Invisible (auto-execute only)</span>
<span><i class="fa-solid fa-fw fa-eye-slash"></i><span data-i18n="Invisible (auto-execute only)">Invisible (auto-execute only)</span></span>
</label>
<label class="checkbox_label">
<input type="checkbox" id="qr--executeOnStartup" >
<span><i class="fa-solid fa-fw fa-rocket"></i> Execute on app startup</span>
<span><i class="fa-solid fa-fw fa-rocket"></i><span data-i18n="Execute on startup">Execute on startup</span></span>
</label>
<label class="checkbox_label">
<input type="checkbox" id="qr--executeOnUser" >
<span><i class="fa-solid fa-fw fa-user"></i> Execute on user message</span>
<span><i class="fa-solid fa-fw fa-user"></i><span data-i18n="Execute on user message">Execute on user message</span></span>
</label>
<label class="checkbox_label">
<input type="checkbox" id="qr--executeOnAi" >
<span><i class="fa-solid fa-fw fa-robot"></i> Execute on AI message</span>
<span><i class="fa-solid fa-fw fa-robot"></i><span data-i18n="Execute on AI message">Execute on AI message</span></span>
</label>
<label class="checkbox_label">
<input type="checkbox" id="qr--executeOnChatChange" >
<span><i class="fa-solid fa-fw fa-message"></i> Execute on opening chat</span>
<span><i class="fa-solid fa-fw fa-message"></i><span data-i18n="Execute on chat change">Execute on chat change</span></span>
</label>
<label class="checkbox_label">
<input type="checkbox" id="qr--executeOnGroupMemberDraft">
<span><i class="fa-solid fa-fw fa-people-group"></i> Execute before group member message</span>
<span><i class="fa-solid fa-fw fa-people-group"></i><span data-i18n="Execute on group member draft">Execute on group member draft</span></span>
</label>
<div class="flex-container alignItemsBaseline flexFlowColumn flexNoGap" title="Activate this quick reply when a World Info entry with the same Automation ID is triggered.">
<small>Automation ID</small>
<small data-i18n="Automation ID:">Automation ID</small>
<input type="text" id="qr--automationId" class="text_pole flex1" placeholder="( None )">
</div>
</div>
<h3>Testing</h3>
<h3 data-i18n="Testing">Testing</h3>
<div id="qr--modal-executeButtons">
<div id="qr--modal-execute" class="qr--modal-executeButton menu_button" title="Execute the quick reply now">
<i class="fa-solid fa-play"></i>
Execute
<span data-i18n="Execute">Execute</span>
</div>
<div id="qr--modal-pause" class="qr--modal-executeButton menu_button" title="Pause / continue execution">
<span class="qr--modal-executeComboIcon">
@@ -115,7 +119,7 @@
<div id="qr--modal-executeProgress"></div>
<label class="checkbox_label">
<input type="checkbox" id="qr--modal-executeHide">
<span> Hide editor while executing</span>
<span title="Hide editor while executing"> Hide editor while executing</span>
</label>
<div id="qr--modal-executeErrors"></div>
<div id="qr--modal-executeResult"></div>

View File

@@ -1,22 +1,22 @@
<div id="qr--settings">
<div class="inline-drawer">
<div class="inline-drawer-toggle inline-drawer-header">
<strong>Quick Reply</strong>
<strong data-i18n="Quick Reply">Quick Reply</strong>
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
</div>
<div class="inline-drawer-content">
<label class="flex-container">
<input type="checkbox" id="qr--isEnabled"> Enable Quick Replies
<input type="checkbox" id="qr--isEnabled"><span data-i18n="Enable Quick Replies">Enable Quick Replies</span>
</label>
<label class="flex-container">
<input type="checkbox" id="qr--isCombined"> Combine buttons from all active sets
<input type="checkbox" id="qr--isCombined"><span data-i18n="Combine Quick Replies">Combine Quick Replies</span>
</label>
<hr>
<div id="qr--global">
<div class="qr--head">
<div class="qr--title">Global Quick Reply Sets</div>
<div class="qr--title" data-i18n="Global Quick Reply Sets">Global Quick Reply Sets</div>
<div class="qr--actions">
<div class="qr--setListAdd menu_button menu_button_icon fa-solid fa-plus" id="qr--global-setListAdd" title="Add quick reply set"></div>
</div>
@@ -28,7 +28,7 @@
<div id="qr--chat">
<div class="qr--head">
<div class="qr--title">Chat Quick Reply Sets</div>
<div class="qr--title" data-i18n="Chat Quick Reply Sets">Chat Quick Reply Sets</div>
<div class="qr--actions">
<div class="qr--setListAdd menu_button menu_button_icon fa-solid fa-plus" id="qr--chat-setListAdd" title="Add quick reply set"></div>
</div>
@@ -40,7 +40,7 @@
<div id="qr--editor">
<div class="qr--head">
<div class="qr--title">Edit Quick Replies</div>
<div class="qr--title" data-i18n="Edit Quick Replies">Edit Quick Replies</div>
<div class="qr--actions">
<select id="qr--set" class="text_pole"></select>
<div class="qr--add menu_button menu_button_icon fa-solid fa-plus" id="qr--set-new" title="Create new quick reply set"></div>
@@ -52,13 +52,13 @@
</div>
<div id="qr--set-settings">
<label class="flex-container">
<input type="checkbox" id="qr--disableSend"> <span>Disable send (insert into input field)</span>
<input type="checkbox" id="qr--disableSend"> <span data-i18n="Disable Send (Insert Into Input Field)">Disable send (insert into input field)</span>
</label>
<label class="flex-container">
<input type="checkbox" id="qr--placeBeforeInput"> <span>Place quick reply before input</span>
<input type="checkbox" id="qr--placeBeforeInput"> <span data-i18n="Place Quick Reply Before Input">Place quick reply before input</span>
</label>
<label class="flex-container" id="qr--injectInputContainer">
<input type="checkbox" id="qr--injectInput"> <span>Inject user input automatically <small>(if disabled, use <code>{{input}}</code> macro for manual injection)</small></span>
<input type="checkbox" id="qr--injectInput"> <span><span data-i18n="Inject user input automatically">Inject user input automatically</span> <small><span data-i18n="(if disabled, use ">(if disabled, use</span><code>{{input}}</code> <span data-i18n="macro for manual injection)">macro for manual injection)</span></small></span>
</label>
</div>
<div id="qr--set-qrList" class="qr--qrList"></div>

View File

@@ -169,7 +169,7 @@ const init = async () => {
log('settings: ', settings);
manager = new SettingsUi(settings);
document.querySelector('#extensions_settings2').append(await manager.render());
document.querySelector('#qr_container').append(await manager.render());
buttons = new ButtonUi(settings);
buttons.show();

View File

@@ -264,6 +264,13 @@ export class QuickReply {
const updateSyntax = ()=>{
messageSyntaxInner.innerHTML = hljs.highlight(`${message.value}${message.value.slice(-1) == '\n' ? ' ' : ''}`, { language:'stscript', ignoreIllegals:true })?.value;
};
const updateSyntaxEnabled = ()=>{
if (JSON.parse(localStorage.getItem('qr--syntax'))) {
dom.querySelector('#qr--modal-messageHolder').classList.remove('qr--noSyntax');
} else {
dom.querySelector('#qr--modal-messageHolder').classList.add('qr--noSyntax');
}
};
/**@type {HTMLInputElement}*/
const tabSize = dom.querySelector('#qr--modal-tabSize');
tabSize.value = JSON.parse(localStorage.getItem('qr--tabSize') ?? '4');
@@ -282,6 +289,13 @@ export class QuickReply {
executeShortcut.addEventListener('click', () => {
localStorage.setItem('qr--executeShortcut', JSON.stringify(executeShortcut.checked));
});
/**@type {HTMLInputElement}*/
const syntax = dom.querySelector('#qr--modal-syntax');
syntax.checked = JSON.parse(localStorage.getItem('qr--syntax') ?? 'true');
syntax.addEventListener('click', () => {
localStorage.setItem('qr--syntax', JSON.stringify(syntax.checked));
updateSyntaxEnabled();
});
/**@type {HTMLTextAreaElement}*/
const message = dom.querySelector('#qr--modal-message');
message.value = this.message;
@@ -352,8 +366,7 @@ export class QuickReply {
}
});
window.addEventListener('resize', resizeListener);
message.style.color = 'transparent';
message.style.background = 'transparent';
updateSyntaxEnabled();
message.style.setProperty('text-shadow', 'none', 'important');
/**@type {HTMLElement}*/
const messageSyntaxInner = dom.querySelector('#qr--modal-messageSyntaxInner');
@@ -544,7 +557,7 @@ export class QuickReply {
this.editorExecuteErrors.innerHTML = '';
this.editorExecuteResult.innerHTML = '';
if (this.editorExecuteHide.checked) {
this.editorPopup.dom.classList.add('qr--hide');
this.editorPopup.dlg.classList.add('qr--hide');
}
try {
this.editorExecutePromise = this.execute({}, true);
@@ -575,7 +588,7 @@ export class QuickReply {
}
this.editorExecutePromise = null;
this.editorExecuteBtn.classList.remove('qr--busy');
this.editorPopup.dom.classList.remove('qr--hide');
this.editorPopup.dlg.classList.remove('qr--hide');
}
updateEditorProgress(done, total) {

View File

@@ -231,6 +231,7 @@ export class QuickReplySet {
this.rerender();
} else {
warn(`Failed to save Quick Reply Set: ${this.name}`);
console.error('QR could not be saved', response);
}
}

View File

@@ -1,9 +1,13 @@
import { SlashCommand } from '../../../slash-commands/SlashCommand.js';
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../../slash-commands/SlashCommandArgument.js';
import { enumIcons } from '../../../slash-commands/SlashCommandCommonEnumsProvider.js';
import { SlashCommandEnumValue, enumTypes } from '../../../slash-commands/SlashCommandEnumValue.js';
import { SlashCommandParser } from '../../../slash-commands/SlashCommandParser.js';
import { isTrueBoolean } from '../../../utils.js';
// eslint-disable-next-line no-unused-vars
import { QuickReplyApi } from '../api/QuickReplyApi.js';
import { QuickReply } from './QuickReply.js';
import { QuickReplySet } from './QuickReplySet.js';
export class SlashCommandHandler {
/**@type {QuickReplyApi}*/ api;
@@ -19,6 +23,50 @@ export class SlashCommandHandler {
init() {
function getExecutionIcons(/**@type {QuickReply} */ qr) {
let icons = '';
if (qr.preventAutoExecute) icons += '🚫';
if (qr.isHidden) icons += '👁️';
if (qr.executeOnStartup) icons += '🚀';
if (qr.executeOnUser) icons += enumIcons.user;
if (qr.executeOnAi) icons += enumIcons.assistant;
if (qr.executeOnChatChange) icons += '💬';
if (qr.executeOnGroupMemberDraft) icons += enumIcons.group;
return icons;
}
const localEnumProviders = {
/** All quick reply sets, optionally filtering out sets that wer already used in the "set" named argument */
qrSets: (executor) => QuickReplySet.list.filter(qrSet => qrSet.name != String(executor.namedArgumentList.find(x => x.name == 'set')?.value))
.map(qrSet => new SlashCommandEnumValue(qrSet.name, null, enumTypes.enum, 'S')),
/** All QRs inside a set, utilizing the "set" named argument */
qrEntries: (executor) => QuickReplySet.get(String(executor.namedArgumentList.find(x => x.name == 'set')?.value))?.qrList.map(qr => {
const icons = getExecutionIcons(qr);
const message = `${qr.automationId ? `[${qr.automationId}]` : ''}${icons ? `[auto: ${icons}]` : ''} ${qr.title || qr.message}`.trim();
return new SlashCommandEnumValue(qr.label, message, enumTypes.enum, enumIcons.qr);
}) ?? [],
/** All QRs as a set.name string, to be able to execute, for example via the /run command */
qrExecutables: () => {
const globalSetList = this.api.settings.config.setList;
const chatSetList = this.api.settings.chatConfig?.setList;
const globalQrs = globalSetList.map(link => link.set.qrList.map(qr => ({ set: link.set, qr }))).flat();
const chatQrs = chatSetList?.map(link => link.set.qrList.map(qr => ({ set: link.set, qr }))).flat() ?? [];
const otherQrs = QuickReplySet.list.filter(set => !globalSetList.some(link => link.set.name === set.name && !chatSetList?.some(link => link.set.name === set.name)))
.map(set => set.qrList.map(qr => ({ set, qr }))).flat();
return [
...globalQrs.map(x => new SlashCommandEnumValue(`${x.set.name}.${x.qr.label}`, `[global] ${x.qr.title || x.qr.message}`, enumTypes.name, enumIcons.qr)),
...chatQrs.map(x => new SlashCommandEnumValue(`${x.set.name}.${x.qr.label}`, `[chat] ${x.qr.title || x.qr.message}`, enumTypes.enum, enumIcons.qr)),
...otherQrs.map(x => new SlashCommandEnumValue(`${x.set.name}.${x.qr.label}`, `${x.qr.title || x.qr.message}`, enumTypes.qr, enumIcons.qr)),
];
},
}
window['qrEnumProviderExecutables'] = localEnumProviders.qrExecutables;
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr',
callback: (_, value) => this.executeQuickReplyByIndex(Number(value)),
unnamedArgumentList: [
@@ -29,110 +77,166 @@ export class SlashCommandHandler {
helpString: 'Activates the specified Quick Reply',
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qrset',
callback: () => toastr.warning('The command /qrset has been deprecated. Use /qr-set, /qr-set-on, and /qr-set-off instead.'),
callback: () => {
toastr.warning('The command /qrset has been deprecated. Use /qr-set, /qr-set-on, and /qr-set-off instead.');
return '';
},
helpString: '<strong>DEPRECATED</strong> The command /qrset has been deprecated. Use /qr-set, /qr-set-on, and /qr-set-off instead.',
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set',
callback: (args, value) => this.toggleGlobalSet(value, args),
callback: (args, value) => {
this.toggleGlobalSet(value, args);
return '';
},
namedArgumentList: [
new SlashCommandNamedArgument(
'visible', 'set visibility', [ARGUMENT_TYPE.BOOLEAN], false, false, 'true',
),
],
unnamedArgumentList: [
new SlashCommandArgument(
'QR set name', [ARGUMENT_TYPE.STRING], true,
),
SlashCommandArgument.fromProps({
description: 'QR set name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: localEnumProviders.qrSets,
}),
],
helpString: 'Toggle global QR set',
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-on',
callback: (args, value) => this.addGlobalSet(value, args),
callback: (args, value) => {
this.addGlobalSet(value, args);
return '';
},
namedArgumentList: [
new SlashCommandNamedArgument(
'visible', 'set visibility', [ARGUMENT_TYPE.BOOLEAN], false, false, 'true',
),
],
unnamedArgumentList: [
new SlashCommandArgument(
'QR set name', [ARGUMENT_TYPE.STRING], true,
),
SlashCommandArgument.fromProps({
description: 'QR set name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: localEnumProviders.qrSets,
}),
],
helpString: 'Activate global QR set',
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-off',
callback: (_, value) => this.removeGlobalSet(value),
callback: (_, value) => {
this.removeGlobalSet(value);
return '';
},
unnamedArgumentList: [
new SlashCommandArgument(
'QR set name', [ARGUMENT_TYPE.STRING], true,
),
SlashCommandArgument.fromProps({
description: 'QR set name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: localEnumProviders.qrSets,
}),
],
helpString: 'Deactivate global QR set',
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-chat-set',
callback: (args, value) => this.toggleChatSet(value, args),
callback: (args, value) => {
this.toggleChatSet(value, args);
return '';
},
namedArgumentList: [
new SlashCommandNamedArgument(
'visible', 'set visibility', [ARGUMENT_TYPE.BOOLEAN], false, false, 'true',
),
],
unnamedArgumentList: [
new SlashCommandArgument(
'QR set name', [ARGUMENT_TYPE.STRING], true,
),
SlashCommandArgument.fromProps({
description: 'QR set name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: localEnumProviders.qrSets,
}),
],
helpString: 'Toggle chat QR set',
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-chat-set-on',
callback: (args, value) => this.addChatSet(value, args),
callback: (args, value) => {
this.addChatSet(value, args);
return '';
},
namedArgumentList: [
new SlashCommandNamedArgument(
'visible', 'whether the QR set should be visible', [ARGUMENT_TYPE.BOOLEAN], false, false, 'true', ['true', 'false'],
'visible', 'whether the QR set should be visible', [ARGUMENT_TYPE.BOOLEAN], false, false, 'true',
),
],
unnamedArgumentList: [
new SlashCommandArgument(
'QR set name', [ARGUMENT_TYPE.STRING], true,
),
SlashCommandArgument.fromProps({
description: 'QR set name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: localEnumProviders.qrSets,
}),
],
helpString: 'Activate chat QR set',
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-chat-set-off',
callback: (_, value) => this.removeChatSet(value),
callback: (_, value) => {
this.removeChatSet(value);
return '';
},
unnamedArgumentList: [
new SlashCommandArgument(
'QR set name', [ARGUMENT_TYPE.STRING], true,
),
SlashCommandArgument.fromProps({
description: 'QR set name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: localEnumProviders.qrSets,
}),
],
helpString: 'Deactivate chat QR set',
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-list',
callback: (_, value) => this.listSets(value ?? 'all'),
callback: (_, value) => JSON.stringify(this.listSets(value ?? 'all')),
returns: 'list of QR sets',
namedArgumentList: [],
unnamedArgumentList: [
new SlashCommandArgument(
'set type', [ARGUMENT_TYPE.STRING], false, false, null, ['all', 'global', 'chat'],
'set type', [ARGUMENT_TYPE.STRING], false, false, 'all', ['all', 'global', 'chat'],
),
],
helpString: 'Gets a list of the names of all quick reply sets.',
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-list',
callback: (_, value) => this.listQuickReplies(value),
callback: (_, value) => {
return JSON.stringify(this.listQuickReplies(value));
},
returns: 'list of QRs',
namedArgumentList: [],
unnamedArgumentList: [
new SlashCommandArgument(
'set name', [ARGUMENT_TYPE.STRING], true,
),
SlashCommandArgument.fromProps({
description: 'QR set name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: localEnumProviders.qrSets,
}),
],
helpString: 'Gets a list of the names of all quick replies in this quick reply set.',
}));
const qrArgs = [
new SlashCommandNamedArgument('label', 'text on the button, e.g., label=MyButton', [ARGUMENT_TYPE.STRING]),
new SlashCommandNamedArgument('set', 'name of the QR set, e.g., set=PresetName1', [ARGUMENT_TYPE.STRING]),
SlashCommandNamedArgument.fromProps({
name: 'set',
description: 'name of the QR set, e.g., set=PresetName1',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: localEnumProviders.qrSets,
}),
SlashCommandNamedArgument.fromProps({
name: 'label',
description: 'text on the button, e.g., label=MyButton',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: localEnumProviders.qrLabels,
}),
new SlashCommandNamedArgument('hidden', 'whether the button should be hidden, e.g., hidden=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'),
new SlashCommandNamedArgument('startup', 'auto execute on app startup, e.g., startup=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'),
new SlashCommandNamedArgument('user', 'auto execute on user message, e.g., user=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'),
@@ -144,8 +248,12 @@ export class SlashCommandHandler {
const qrUpdateArgs = [
new SlashCommandNamedArgument('newlabel', 'new text for the button', [ARGUMENT_TYPE.STRING], false),
];
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-create',
callback: (args, message) => this.createQuickReply(args, message),
callback: (args, message) => {
this.createQuickReply(args, message);
return '';
},
namedArgumentList: qrArgs,
unnamedArgumentList: [
new SlashCommandArgument(
@@ -165,9 +273,15 @@ export class SlashCommandHandler {
`,
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-update',
callback: (args, message) => this.updateQuickReply(args, message),
callback: (args, message) => {
this.updateQuickReply(args, message);
return '';
},
returns: 'updated quick reply',
namedArgumentList: [...qrUpdateArgs, ...qrArgs],
unnamedArgumentList: [
new SlashCommandArgument('command', [ARGUMENT_TYPE.STRING]),
],
helpString: `
<div>
Updates Quick Reply.
@@ -183,34 +297,57 @@ export class SlashCommandHandler {
`,
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-delete',
callback: (args, name) => this.deleteQuickReply(args, name),
callback: (args, name) => {
this.deleteQuickReply(args, name);
return '';
},
namedArgumentList: [
new SlashCommandNamedArgument(
'set', 'Quick Reply set', [ARGUMENT_TYPE.STRING], true,
),
new SlashCommandNamedArgument(
'label', 'Quick Reply label', [ARGUMENT_TYPE.STRING], false,
),
SlashCommandNamedArgument.fromProps({
name: 'set',
description: 'QR set name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: localEnumProviders.qrSets,
}),
SlashCommandNamedArgument.fromProps({
name: 'label',
description: 'Quick Reply label',
typeList: [ARGUMENT_TYPE.STRING],
enumProvider: localEnumProviders.qrEntries,
}),
],
helpString: 'Deletes a Quick Reply from the specified set. If no label is provided, the entire set is deleted.',
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-contextadd',
callback: (args, name) => this.createContextItem(args, name),
callback: (args, name) => {
this.createContextItem(args, name);
return '';
},
namedArgumentList: [
new SlashCommandNamedArgument(
'set', 'string', [ARGUMENT_TYPE.STRING], true,
),
new SlashCommandNamedArgument(
'label', 'string', [ARGUMENT_TYPE.STRING], true,
),
SlashCommandNamedArgument.fromProps({
name: 'set',
description: 'QR set name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: localEnumProviders.qrSets,
}),
SlashCommandNamedArgument.fromProps({
name: 'label',
description: 'Quick Reply label',
typeList: [ARGUMENT_TYPE.STRING],
enumProvider: localEnumProviders.qrEntries,
}),
new SlashCommandNamedArgument(
'chain', 'boolean', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false',
),
],
unnamedArgumentList: [
new SlashCommandArgument(
'preset name', [ARGUMENT_TYPE.STRING], true,
),
SlashCommandArgument.fromProps({
description: 'QR set name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: localEnumProviders.qrSets,
}),
],
helpString: `
<div>
@@ -227,19 +364,32 @@ export class SlashCommandHandler {
`,
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-contextdel',
callback: (args, name) => this.deleteContextItem(args, name),
callback: (args, name) => {
this.deleteContextItem(args, name);
return '';
},
namedArgumentList: [
new SlashCommandNamedArgument(
'set', 'string', [ARGUMENT_TYPE.STRING], true,
),
new SlashCommandNamedArgument(
'label', 'string', [ARGUMENT_TYPE.STRING], true,
),
SlashCommandNamedArgument.fromProps({
name: 'set',
description: 'QR set name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: localEnumProviders.qrSets,
}),
SlashCommandNamedArgument.fromProps({
name: 'label',
description: 'Quick Reply label',
typeList: [ARGUMENT_TYPE.STRING],
enumProvider: localEnumProviders.qrEntries,
}),
],
unnamedArgumentList: [
new SlashCommandArgument(
'preset name', [ARGUMENT_TYPE.STRING], true,
),
SlashCommandArgument.fromProps({
description: 'QR set name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: localEnumProviders.qrSets,
}),
],
helpString: `
<div>
@@ -256,16 +406,25 @@ export class SlashCommandHandler {
`,
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-contextclear',
callback: (args, label) => this.clearContextMenu(args, label),
callback: (args, label) => {
this.clearContextMenu(args, label);
return '';
},
namedArgumentList: [
new SlashCommandNamedArgument(
'set', 'context menu preset name', [ARGUMENT_TYPE.STRING], true,
),
SlashCommandNamedArgument.fromProps({
name: 'set',
description: 'QR set name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: localEnumProviders.qrSets,
}),
],
unnamedArgumentList: [
new SlashCommandArgument(
'label', [ARGUMENT_TYPE.STRING], true,
),
SlashCommandArgument.fromProps({
description: 'Quick Reply label',
typeList: [ARGUMENT_TYPE.STRING],
enumProvider: localEnumProviders.qrEntries,
}),
],
helpString: `
<div>
@@ -288,13 +447,20 @@ export class SlashCommandHandler {
new SlashCommandNamedArgument('inject', 'inject user input automatically (if disabled use {{input}})', [ARGUMENT_TYPE.BOOLEAN], false),
];
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-create',
callback: (args, name) => this.createSet(name, args),
callback: (args, name) => {
this.createSet(name, args);
return '';
},
aliases: ['qr-presetadd'],
namedArgumentList: presetArgs,
unnamedArgumentList: [
new SlashCommandArgument(
'name', [ARGUMENT_TYPE.STRING], true,
),
SlashCommandArgument.fromProps({
description: 'QR set name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: localEnumProviders.qrSets,
forceEnum: false,
}),
],
helpString: `
<div>
@@ -312,11 +478,19 @@ export class SlashCommandHandler {
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-update',
callback: (args, name) => this.updateSet(name, args),
callback: (args, name) => {
this.updateSet(name, args);
return '';
},
aliases: ['qr-presetupdate'],
namedArgumentList: presetArgs,
unnamedArgumentList: [
new SlashCommandArgument('name', [ARGUMENT_TYPE.STRING], true),
SlashCommandArgument.fromProps({
description: 'QR set name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: localEnumProviders.qrSets,
}),
],
helpString: `
<div>
@@ -329,10 +503,18 @@ export class SlashCommandHandler {
`,
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-delete',
callback: (args, name) => this.deleteSet(name),
callback: (_, name) => {
this.deleteSet(name);
return '';
},
aliases: ['qr-presetdelete'],
unnamedArgumentList: [
new SlashCommandArgument('name', [ARGUMENT_TYPE.STRING], true),
SlashCommandArgument.fromProps({
description: 'QR set name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: localEnumProviders.qrSets,
}),
],
helpString: `
<div>
@@ -468,7 +650,7 @@ export class SlashCommandHandler {
}
deleteQuickReply(args, label) {
try {
this.api.deleteQuickReply(args.set, label);
this.api.deleteQuickReply(args.set, args.label ?? label);
} catch (ex) {
toastr.error(ex.message);
}

View File

@@ -148,7 +148,7 @@ export class SettingsUi {
this.onQrSetChange();
}
onQrSetChange() {
this.currentQrSet = QuickReplySet.get(this.currentSet.value);
this.currentQrSet = QuickReplySet.get(this.currentSet.value) ?? new QuickReplySet();
this.disableSend.checked = this.currentQrSet.disableSend;
this.placeBeforeInput.checked = this.currentQrSet.placeBeforeInput;
this.injectInput.checked = this.currentQrSet.injectInput;

View File

@@ -220,68 +220,68 @@
align-items: baseline;
}
@media screen and (max-width: 750px) {
body .dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor {
body .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor {
flex-direction: column;
overflow: auto;
}
body .dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main {
body .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main {
flex: 0 0 auto;
}
body .dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels {
body .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels {
flex-direction: column;
}
body .dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder {
body .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder {
min-height: 50svh;
height: 50svh;
}
}
.dialogue_popup:has(#qr--modalEditor) {
.popup:has(#qr--modalEditor) {
aspect-ratio: unset;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text {
.popup:has(#qr--modalEditor) .popup-content {
display: flex;
flex-direction: column;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor {
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor {
flex: 1 1 auto;
display: flex;
flex-direction: row;
gap: 1em;
overflow: hidden;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main {
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main {
flex: 1 1 auto;
display: flex;
flex-direction: column;
overflow: hidden;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels {
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels {
flex: 0 0 auto;
display: flex;
flex-direction: row;
gap: 0.5em;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels > label {
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label {
flex: 1 1 1px;
display: flex;
flex-direction: column;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels > label > .qr--labelText {
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label > .qr--labelText {
flex: 1 1 auto;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels > label > .qr--labelHint {
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label > .qr--labelHint {
flex: 1 1 auto;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels > label > input {
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label > input {
flex: 0 0 auto;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer {
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer {
flex: 1 1 auto;
display: flex;
flex-direction: column;
overflow: hidden;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings {
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings {
display: flex;
flex-direction: row;
gap: 1em;
@@ -289,19 +289,35 @@
font-size: smaller;
align-items: baseline;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings > .checkbox_label {
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings > .checkbox_label {
white-space: nowrap;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings > .checkbox_label > input {
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings > .checkbox_label > input {
font-size: inherit;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder {
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder {
flex: 1 1 auto;
display: grid;
text-align: left;
overflow: hidden;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder > #qr--modal-messageSyntax {
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder.qr--noSyntax > #qr--modal-messageSyntax {
display: none;
}
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder.qr--noSyntax > #qr--modal-message {
background-color: var(--ac-style-color-background);
color: var(--ac-style-color-text);
}
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder.qr--noSyntax > #qr--modal-message::selection {
color: unset;
background-color: rgba(108 171 251 / 0.25);
}
@supports (color: rgb(from white r g b / 0.25)) {
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder.qr--noSyntax > #qr--modal-message::selection {
background-color: rgb(from var(--ac-style-color-matchedText) r g b / 0.25);
}
}
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder > #qr--modal-messageSyntax {
grid-column: 1;
grid-row: 1;
padding: 0;
@@ -311,22 +327,34 @@
min-width: 100%;
width: 0;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder > #qr--modal-messageSyntax > #qr--modal-messageSyntaxInner {
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder > #qr--modal-messageSyntax > #qr--modal-messageSyntaxInner {
height: 100%;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder > #qr--modal-message {
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder > #qr--modal-message {
background-color: transparent;
color: transparent;
grid-column: 1;
grid-row: 1;
caret-color: white;
mix-blend-mode: difference;
caret-color: var(--ac-style-color-text);
overflow: auto;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder > #qr--modal-message::-webkit-scrollbar,
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder > #qr--modal-message::-webkit-scrollbar-thumb {
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder > #qr--modal-message::-webkit-scrollbar,
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder > #qr--modal-message::-webkit-scrollbar-thumb {
visibility: hidden;
cursor: default;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder #qr--modal-message,
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder #qr--modal-messageSyntaxInner {
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder > #qr--modal-message::selection {
color: transparent;
background-color: rgba(108 171 251 / 0.25);
}
@supports (color: rgb(from white r g b / 0.25)) {
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder > #qr--modal-message::selection {
background-color: rgb(from var(--ac-style-color-matchedText) r g b / 0.25);
}
}
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder #qr--modal-message,
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder #qr--modal-messageSyntaxInner {
font-family: var(--monoFontFamily);
padding: 0.75em;
margin: 0;
border: none;
@@ -335,11 +363,11 @@
border: 1px solid var(--SmartThemeBorderColor);
border-radius: 5px;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeButtons {
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons {
display: flex;
gap: 1em;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeButtons .qr--modal-executeButton {
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons .qr--modal-executeButton {
border-width: 2px;
border-style: solid;
display: flex;
@@ -347,42 +375,42 @@
gap: 0.5em;
padding: 0.5em 0.75em;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeButtons .qr--modal-executeButton .qr--modal-executeComboIcon {
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons .qr--modal-executeButton .qr--modal-executeComboIcon {
display: flex;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeButtons #qr--modal-execute {
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons #qr--modal-execute {
transition: 200ms;
filter: grayscale(0);
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeButtons #qr--modal-execute.qr--busy {
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons #qr--modal-execute.qr--busy {
cursor: wait;
opacity: 0.5;
filter: grayscale(1);
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeButtons #qr--modal-execute {
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons #qr--modal-execute {
border-color: #51a351;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeButtons #qr--modal-pause,
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeButtons #qr--modal-stop {
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons #qr--modal-pause,
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons #qr--modal-stop {
cursor: default;
opacity: 0.5;
filter: grayscale(1);
pointer-events: none;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeButtons .qr--busy ~ #qr--modal-pause,
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeButtons .qr--busy ~ #qr--modal-stop {
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons .qr--busy ~ #qr--modal-pause,
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons .qr--busy ~ #qr--modal-stop {
cursor: pointer;
opacity: 1;
filter: grayscale(0);
pointer-events: all;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeButtons #qr--modal-pause {
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons #qr--modal-pause {
border-color: #92befc;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeButtons #qr--modal-stop {
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons #qr--modal-stop {
border-color: #d78872;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeProgress {
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeProgress {
--prog: 0;
--progColor: #92befc;
--progFlashColor: #d78872;
@@ -393,7 +421,7 @@
background-color: var(--black50a);
position: relative;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeProgress:after {
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeProgress:after {
content: '';
background-color: var(--progColor);
position: absolute;
@@ -401,23 +429,23 @@
right: calc(100% - var(--prog) * 1%);
transition: 200ms;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeProgress.qr--paused:after {
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeProgress.qr--paused:after {
animation-name: qr--progressPulse;
animation-duration: 1500ms;
animation-timing-function: ease-in-out;
animation-delay: 0s;
animation-iteration-count: infinite;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeProgress.qr--aborted:after {
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeProgress.qr--aborted:after {
background-color: var(--progAbortedColor);
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeProgress.qr--success:after {
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeProgress.qr--success:after {
background-color: var(--progSuccessColor);
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeProgress.qr--error:after {
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeProgress.qr--error:after {
background-color: var(--progErrorColor);
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeErrors {
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeErrors {
display: none;
text-align: left;
font-size: smaller;
@@ -428,10 +456,10 @@
min-width: 100%;
width: 0;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeErrors.qr--hasErrors {
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeErrors.qr--hasErrors {
display: block;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeResult {
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeResult {
display: none;
text-align: left;
font-size: smaller;
@@ -442,10 +470,10 @@
min-width: 100%;
width: 0;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeResult.qr--hasResult {
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeResult.qr--hasResult {
display: block;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeResult:before {
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeResult:before {
content: 'Result: ';
}
@keyframes qr--progressPulse {
@@ -457,6 +485,9 @@
background-color: var(--progFlashColor);
}
}
.shadow_popup.qr--hide {
.popup.qr--hide {
opacity: 0 !important;
}
.popup.qr--hide::backdrop {
opacity: 0 !important;
}

View File

@@ -1,6 +1,6 @@
#qr--bar {
outline: none;
margin: 0;
outline: none;
margin: 0;
transition: 0.3s;
opacity: 0.7;
display: flex;
@@ -10,88 +10,99 @@
max-width: 100%;
overflow-x: auto;
order: 1;
padding-right: 2.5em;
padding-right: 2.5em;
position: relative;
> #qr--popoutTrigger {
position: absolute;
right: 0.25em;
top: 0;
}
>#qr--popoutTrigger {
position: absolute;
right: 0.25em;
top: 0;
}
}
#qr--popout {
display: flex;
flex-direction: column;
padding: 0;
z-index: 31;
> .qr--header {
flex: 0 0 auto;
height: 2em;
position: relative;
> .qr--controls {
> .qr--close {
height: 15px;
aspect-ratio: 1 / 1;
font-size: 20px;
opacity: 0.5;
transition: all 250ms;
}
}
}
> .qr--body {
overflow-y: auto;
}
display: flex;
flex-direction: column;
padding: 0;
z-index: 31;
>.qr--header {
flex: 0 0 auto;
height: 2em;
position: relative;
>.qr--controls {
>.qr--close {
height: 15px;
aspect-ratio: 1 / 1;
font-size: 20px;
opacity: 0.5;
transition: all 250ms;
}
}
}
>.qr--body {
overflow-y: auto;
}
}
#qr--bar, #qr--popout > .qr--body {
> .qr--buttons {
margin: 0;
padding: 0;
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 5px;
width: 100%;
> .qr--buttons {
display: contents;
}
#qr--bar,
#qr--popout>.qr--body {
>.qr--buttons {
margin: 0;
padding: 0;
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 5px;
width: 100%;
.qr--button {
color: var(--SmartThemeBodyColor);
// background-color: var(--black50a);
border: 1px solid var(--SmartThemeBorderColor);
border-radius: 10px;
padding: 3px 5px;
margin: 3px 0;
cursor: pointer;
transition: 0.3s;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
&:hover {
opacity: 1;
filter: brightness(1.2);
}
> .qr--button-expander {
display: none;
}
&.qr--hasCtx {
> .qr--button-expander {
display: block;
}
}
}
}
>.qr--buttons {
display: contents;
}
.qr--button {
color: var(--SmartThemeBodyColor);
// background-color: var(--black50a);
border: 1px solid var(--SmartThemeBorderColor);
border-radius: 10px;
padding: 3px 5px;
margin: 3px 0;
cursor: pointer;
transition: 0.3s;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
&:hover {
opacity: 1;
filter: brightness(1.2);
}
>.qr--button-expander {
display: none;
}
&.qr--hasCtx {
>.qr--button-expander {
display: block;
}
}
}
}
}
.qr--button-expander {
border-left: 1px solid;
margin-left: 1em;
text-align: center;
width: 2em;
&:hover {
font-weight: bold;
}
border-left: 1px solid;
margin-left: 1em;
text-align: center;
width: 2em;
&:hover {
font-weight: bold;
}
}
.ctx-blocker {
@@ -153,75 +164,103 @@
#qr--settings {
.qr--head {
display: flex;
align-items: baseline;
gap: 1em;
> .qr--title {
font-weight: bold;
}
> .qr--actions {
display: flex;
flex-direction: row;
align-items: baseline;
gap: 0.5em;
}
}
.qr--setList {
> .qr--item {
display: flex;
flex-direction: row;
gap: 0.5em;
align-items: baseline;
padding: 0 0.5em;
> .drag-handle {
padding: 0.75em;
}
> .qr--visible {
flex: 0 0 auto;
display: flex;
flex-direction: row;
}
}
}
#qr--set-settings {
#qr--injectInputContainer {
flex-wrap: nowrap;
}
}
#qr--set-qrList {
.qr--set-qrListContents > {
padding: 0 0.5em;
> .qr--set-item {
display: flex;
flex-direction: row;
gap: 0.5em;
align-items: baseline;
padding: 0.25em 0;
> :nth-child(1) { flex: 0 0 auto; }
> :nth-child(2) { flex: 1 1 25%; }
> :nth-child(3) { flex: 0 0 auto; }
> :nth-child(4) { flex: 1 1 75%; }
> :nth-child(5) { flex: 0 0 auto; }
> .drag-handle {
padding: 0.75em;
}
.qr--set-itemLabel, .qr--action {
margin: 0;
}
.qr--set-itemMessage {
font-size: smaller;
}
}
}
}
.qr--set-qrListActions {
display: flex;
flex-direction: row;
gap: 0.5em;
justify-content: center;
padding-bottom: 0.5em;
}
.qr--head {
display: flex;
align-items: baseline;
gap: 1em;
>.qr--title {
font-weight: bold;
}
>.qr--actions {
display: flex;
flex-direction: row;
align-items: baseline;
gap: 0.5em;
}
}
.qr--setList {
>.qr--item {
display: flex;
flex-direction: row;
gap: 0.5em;
align-items: baseline;
padding: 0 0.5em;
>.drag-handle {
padding: 0.75em;
}
>.qr--visible {
flex: 0 0 auto;
display: flex;
flex-direction: row;
}
}
}
#qr--set-settings {
#qr--injectInputContainer {
flex-wrap: nowrap;
}
}
#qr--set-qrList {
.qr--set-qrListContents> {
padding: 0 0.5em;
>.qr--set-item {
display: flex;
flex-direction: row;
gap: 0.5em;
align-items: baseline;
padding: 0.25em 0;
> :nth-child(1) {
flex: 0 0 auto;
}
> :nth-child(2) {
flex: 1 1 25%;
}
> :nth-child(3) {
flex: 0 0 auto;
}
> :nth-child(4) {
flex: 1 1 75%;
}
> :nth-child(5) {
flex: 0 0 auto;
}
>.drag-handle {
padding: 0.75em;
}
.qr--set-itemLabel,
.qr--action {
margin: 0;
}
.qr--set-itemMessage {
font-size: smaller;
}
}
}
}
.qr--set-qrListActions {
display: flex;
flex-direction: row;
gap: 0.5em;
justify-content: center;
padding-bottom: 0.5em;
}
}
@@ -229,258 +268,347 @@
#qr--qrOptions {
display: flex;
flex-direction: column;
> #qr--ctxEditor {
.qr--ctxItem {
display: flex;
flex-direction: row;
gap: 0.5em;
align-items: baseline;
}
}
display: flex;
flex-direction: column;
>#qr--ctxEditor {
.qr--ctxItem {
display: flex;
flex-direction: row;
gap: 0.5em;
align-items: baseline;
}
}
}
@media screen and (max-width: 750px) {
body .dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor {
flex-direction: column;
overflow: auto;
> #qr--main {
flex: 0 0 auto;
}
> #qr--main > .qr--labels {
flex-direction: column;
}
> #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder {
min-height: 50svh;
height: 50svh;
}
}
body .popup:has(#qr--modalEditor) .popup-content>#qr--modalEditor {
flex-direction: column;
overflow: auto;
>#qr--main {
flex: 0 0 auto;
}
>#qr--main>.qr--labels {
flex-direction: column;
}
>#qr--main>.qr--modal-messageContainer>#qr--modal-messageHolder {
min-height: 50svh;
height: 50svh;
}
}
}
.dialogue_popup:has(#qr--modalEditor) {
aspect-ratio: unset;
.dialogue_popup_text {
display: flex;
flex-direction: column;
.popup:has(#qr--modalEditor) {
aspect-ratio: unset;
> #qr--modalEditor {
flex: 1 1 auto;
display: flex;
flex-direction: row;
gap: 1em;
overflow: hidden;
.popup-content {
display: flex;
flex-direction: column;
> #qr--main {
flex: 1 1 auto;
display: flex;
flex-direction: column;
overflow: hidden;
> .qr--labels {
flex: 0 0 auto;
display: flex;
flex-direction: row;
gap: 0.5em;
> label {
flex: 1 1 1px;
display: flex;
flex-direction: column;
> .qr--labelText {
flex: 1 1 auto;
}
> .qr--labelHint {
flex: 1 1 auto;
}
> input {
flex: 0 0 auto;
}
}
}
> .qr--modal-messageContainer {
flex: 1 1 auto;
display: flex;
flex-direction: column;
overflow: hidden;
> .qr--modal-editorSettings {
display: flex;
flex-direction: row;
gap: 1em;
color: var(--grey70);
font-size: smaller;
align-items: baseline;
> .checkbox_label {
white-space: nowrap;
> input {
font-size: inherit;
}
}
}
> #qr--modal-messageHolder {
flex: 1 1 auto;
display: grid;
text-align: left;
overflow: hidden;
> #qr--modal-messageSyntax {
grid-column: 1;
grid-row: 1;
padding: 0;
margin: 0;
border: none;
overflow: hidden;
min-width: 100%;
width: 0;
> #qr--modal-messageSyntaxInner {
height: 100%;
}
}
> #qr--modal-message {
grid-column: 1;
grid-row: 1;
caret-color: white;
mix-blend-mode: difference;
&::-webkit-scrollbar, &::-webkit-scrollbar-thumb {
visibility: hidden;
cursor: default;
}
}
#qr--modal-message, #qr--modal-messageSyntaxInner {
padding: 0.75em;
margin: 0;
border: none;
resize: none;
line-height: 1.2;
border: 1px solid var(--SmartThemeBorderColor);
border-radius: 5px;
}
}
}
}
>#qr--modalEditor {
flex: 1 1 auto;
display: flex;
flex-direction: row;
gap: 1em;
overflow: hidden;
#qr--modal-executeButtons {
display: flex;
gap: 1em;
.qr--modal-executeButton {
border-width: 2px;
border-style: solid;
display: flex;
flex-direction: row;
gap: 0.5em;
padding: 0.5em 0.75em;
.qr--modal-executeComboIcon {
display: flex;
}
}
#qr--modal-execute {
transition: 200ms;
filter: grayscale(0);
&.qr--busy {
cursor: wait;
opacity: 0.5;
filter: grayscale(1);
}
}
#qr--modal-execute {
border-color: rgb(81, 163, 81);
}
#qr--modal-pause, #qr--modal-stop {
cursor: default;
opacity: 0.5;
filter: grayscale(1);
pointer-events: none;
}
.qr--busy {
~ #qr--modal-pause, ~ #qr--modal-stop {
cursor: pointer;
opacity: 1;
filter: grayscale(0);
pointer-events: all;
}
}
#qr--modal-pause {
border-color: rgb(146, 190, 252);
}
#qr--modal-stop {
border-color: rgb(215, 136, 114);
}
}
#qr--modal-executeProgress {
--prog: 0;
--progColor: rgb(146, 190, 252);
--progFlashColor: rgb(215, 136, 114);
--progSuccessColor: rgb(81, 163, 81);
--progErrorColor: rgb(189, 54, 47);
--progAbortedColor: rgb(215, 136, 114);
height: 0.5em;
background-color: var(--black50a);
position: relative;
&:after {
content: '';
background-color: var(--progColor);
position: absolute;
inset: 0;
right: calc(100% - var(--prog) * 1%);
transition: 200ms;
}
&.qr--paused:after {
animation-name: qr--progressPulse;
animation-duration: 1500ms;
animation-timing-function: ease-in-out;
animation-delay: 0s;
animation-iteration-count: infinite;
}
&.qr--aborted:after {
background-color: var(--progAbortedColor);
}
&.qr--success:after {
background-color: var(--progSuccessColor);
}
&.qr--error:after {
background-color: var(--progErrorColor);
}
}
#qr--modal-executeErrors {
display: none;
&.qr--hasErrors {
display: block;
}
text-align: left;
font-size: smaller;
background-color: rgb(189, 54, 47);
color: white;
padding: 0.5em;
overflow: auto;
min-width: 100%;
width: 0;
}
#qr--modal-executeResult {
display: none;
&.qr--hasResult {
display: block;
}
&:before { content: 'Result: '; }
text-align: left;
font-size: smaller;
background-color: rgb(81, 163, 81);
color: white;
padding: 0.5em;
overflow: auto;
min-width: 100%;
width: 0;
}
}
}
>#qr--main {
flex: 1 1 auto;
display: flex;
flex-direction: column;
overflow: hidden;
>.qr--labels {
flex: 0 0 auto;
display: flex;
flex-direction: row;
gap: 0.5em;
>label {
flex: 1 1 1px;
display: flex;
flex-direction: column;
>.qr--labelText {
flex: 1 1 auto;
}
>.qr--labelHint {
flex: 1 1 auto;
}
>input {
flex: 0 0 auto;
}
}
}
>.qr--modal-messageContainer {
flex: 1 1 auto;
display: flex;
flex-direction: column;
overflow: hidden;
>.qr--modal-editorSettings {
display: flex;
flex-direction: row;
gap: 1em;
color: var(--grey70);
font-size: smaller;
align-items: baseline;
>.checkbox_label {
white-space: nowrap;
>input {
font-size: inherit;
}
}
}
>#qr--modal-messageHolder {
flex: 1 1 auto;
display: grid;
text-align: left;
overflow: hidden;
&.qr--noSyntax {
>#qr--modal-messageSyntax {
display: none;
}
>#qr--modal-message {
background-color: var(--ac-style-color-background);
color: var(--ac-style-color-text);
&::selection {
color: unset;
background-color: rgba(108 171 251 / 0.25);
@supports (color: rgb(from white r g b / 0.25)) {
background-color: rgb(from var(--ac-style-color-matchedText) r g b / 0.25);
}
}
}
}
>#qr--modal-messageSyntax {
grid-column: 1;
grid-row: 1;
padding: 0;
margin: 0;
border: none;
overflow: hidden;
min-width: 100%;
width: 0;
>#qr--modal-messageSyntaxInner {
height: 100%;
}
}
>#qr--modal-message {
background-color: transparent;
color: transparent;
grid-column: 1;
grid-row: 1;
caret-color: var(--ac-style-color-text);
overflow: auto;
&::-webkit-scrollbar,
&::-webkit-scrollbar-thumb {
visibility: hidden;
cursor: default;
}
&::selection {
color: transparent;
background-color: rgba(108 171 251 / 0.25);
@supports (color: rgb(from white r g b / 0.25)) {
background-color: rgb(from var(--ac-style-color-matchedText) r g b / 0.25);
}
}
}
#qr--modal-message,
#qr--modal-messageSyntaxInner {
font-family: var(--monoFontFamily);
padding: 0.75em;
margin: 0;
border: none;
resize: none;
line-height: 1.2;
border: 1px solid var(--SmartThemeBorderColor);
border-radius: 5px;
}
}
}
}
#qr--modal-executeButtons {
display: flex;
gap: 1em;
.qr--modal-executeButton {
border-width: 2px;
border-style: solid;
display: flex;
flex-direction: row;
gap: 0.5em;
padding: 0.5em 0.75em;
.qr--modal-executeComboIcon {
display: flex;
}
}
#qr--modal-execute {
transition: 200ms;
filter: grayscale(0);
&.qr--busy {
cursor: wait;
opacity: 0.5;
filter: grayscale(1);
}
}
#qr--modal-execute {
border-color: rgb(81, 163, 81);
}
#qr--modal-pause,
#qr--modal-stop {
cursor: default;
opacity: 0.5;
filter: grayscale(1);
pointer-events: none;
}
.qr--busy {
~#qr--modal-pause,
~#qr--modal-stop {
cursor: pointer;
opacity: 1;
filter: grayscale(0);
pointer-events: all;
}
}
#qr--modal-pause {
border-color: rgb(146, 190, 252);
}
#qr--modal-stop {
border-color: rgb(215, 136, 114);
}
}
#qr--modal-executeProgress {
--prog: 0;
--progColor: rgb(146, 190, 252);
--progFlashColor: rgb(215, 136, 114);
--progSuccessColor: rgb(81, 163, 81);
--progErrorColor: rgb(189, 54, 47);
--progAbortedColor: rgb(215, 136, 114);
height: 0.5em;
background-color: var(--black50a);
position: relative;
&:after {
content: '';
background-color: var(--progColor);
position: absolute;
inset: 0;
right: calc(100% - var(--prog) * 1%);
transition: 200ms;
}
&.qr--paused:after {
animation-name: qr--progressPulse;
animation-duration: 1500ms;
animation-timing-function: ease-in-out;
animation-delay: 0s;
animation-iteration-count: infinite;
}
&.qr--aborted:after {
background-color: var(--progAbortedColor);
}
&.qr--success:after {
background-color: var(--progSuccessColor);
}
&.qr--error:after {
background-color: var(--progErrorColor);
}
}
#qr--modal-executeErrors {
display: none;
&.qr--hasErrors {
display: block;
}
text-align: left;
font-size: smaller;
background-color: rgb(189, 54, 47);
color: white;
padding: 0.5em;
overflow: auto;
min-width: 100%;
width: 0;
}
#qr--modal-executeResult {
display: none;
&.qr--hasResult {
display: block;
}
&:before {
content: 'Result: ';
}
text-align: left;
font-size: smaller;
background-color: rgb(81, 163, 81);
color: white;
padding: 0.5em;
overflow: auto;
min-width: 100%;
width: 0;
}
}
}
}
@keyframes qr--progressPulse {
0%, 100% {
0%,
100% {
background-color: var(--progColor);
}
50% {
background-color: var(--progFlashColor);
}
}
.shadow_popup.qr--hide {
opacity: 0 !important;
.popup.qr--hide {
opacity: 0 !important;
}
.popup.qr--hide::backdrop {
opacity: 0 !important;
}

View File

@@ -8,11 +8,11 @@
</div>
<div class="inline-drawer-content">
<div class="flex-container">
<div id="open_regex_editor" class="menu_button menu_button_icon" title="New global regex script">
<div id="open_regex_editor" class="menu_button menu_button_icon" data-i18n="[title]ext_regex_new_global_script_desc" title="New global regex script">
<i class="fa-solid fa-pen-to-square"></i>
<small data-i18n="ext_regex_new_global_script">+ Global</small>
</div>
<div id="open_scoped_editor" class="menu_button menu_button_icon" title="New scoped regex script">
<div id="open_scoped_editor" class="menu_button menu_button_icon" data-i18n="[title]ext_regex_new_scoped_script_desc" title="New scoped regex script">
<i class="fa-solid fa-address-card"></i>
<small data-i18n="ext_regex_new_scoped_script">+ Scoped</small>
</div>
@@ -39,7 +39,7 @@
<label id="toggle_scoped_regex" class="checkbox flex-container" for="regex_scoped_toggle">
<input type="checkbox" id="regex_scoped_toggle" class="enable_scoped" />
<span class="regex-toggle-on fa-solid fa-toggle-on fa-lg" title="Disallow using scoped regex"></span>
<span class="regex-toggle-off fa-solid fa-toggle-off fa-lg" title="Allow using scoped regex"></span>
<span class="regex-toggle-off fa-solid fa-toggle-off fa-lg" data-i18n="[title]ext_regex_allow_scoped" title="Allow using scoped regex"></span>
</label>
</div>
<small data-i18n="ext_regex_scoped_scripts_desc">

View File

@@ -92,7 +92,7 @@ function getRegexedString(rawString, placement, { characterOverride, isMarkdown,
/**
* Runs the provided regex script on the given string
* @param {object} regexScript The regex script to run
* @param {import('./index.js').RegexScript} regexScript The regex script to run
* @param {string} rawString The string to run the regex script on
* @param {RegexScriptParams} params The parameters to use for the regex script
* @returns {string} The new string

View File

@@ -3,11 +3,32 @@ import { extension_settings, renderExtensionTemplateAsync, writeExtensionField }
import { selected_group } from '../../group-chats.js';
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
import { enumIcons } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashCommandEnumValue.js';
import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
import { download, getFileText, getSortableDelay, uuidv4 } from '../../utils.js';
import { resolveVariable } from '../../variables.js';
import { regex_placement, runRegexScript } from './engine.js';
/**
* @typedef {object} RegexScript
* @property {string} scriptName - The name of the script
* @property {boolean} disabled - Whether the script is disabled
* @property {string} replaceString - The replace string
* @property {string[]} trimStrings - The trim strings
* @property {string?} findRegex - The find regex
* @property {string?} substituteRegex - The substitute regex
*/
/**
* Retrieves the list of regex scripts by combining the scripts from the extension settings and the character data
*
* @return {RegexScript[]} An array of regex scripts, where each script is an object containing the necessary information.
*/
export function getRegexScripts() {
return [...(extension_settings.regex ?? []), ...(characters[this_chid]?.data?.extensions?.regex_scripts ?? [])];
}
/**
* Saves a regex script to the extension settings or character data.
* @param {import('../../char-data.js').RegexScriptData} regexScript
@@ -328,7 +349,7 @@ function migrateSettings() {
/**
* /regex slash command callback
* @param {object} args Named arguments
* @param {{name: string}} args Named arguments
* @param {string} value Unnamed argument
* @returns {string} The regexed string
*/
@@ -338,11 +359,11 @@ function runRegexCallback(args, value) {
return value;
}
const scriptName = String(resolveVariable(args.name));
const scripts = [...(extension_settings.regex ?? []), ...(characters[this_chid]?.data?.extensions?.regex_scripts ?? [])];
const scriptName = args.name;
const scripts = getRegexScripts();
for (const script of scripts) {
if (String(script.scriptName).toLowerCase() === String(scriptName).toLowerCase()) {
if (script.scriptName.toLowerCase() === scriptName.toLowerCase()) {
if (script.disabled) {
toastr.warning(`Regex script "${scriptName}" is disabled.`);
return value;
@@ -449,7 +470,7 @@ jQuery(async () => {
}
const settingsHtml = $(await renderExtensionTemplateAsync('regex', 'dropdown'));
$('#extensions_settings2').append(settingsHtml);
$('#regex_container').append(settingsHtml);
$('#open_regex_editor').on('click', function () {
onRegexEditorOpenClick(false, false);
});
@@ -551,14 +572,26 @@ jQuery(async () => {
await loadRegexScripts();
$('#saved_regex_scripts').sortable('enable');
const localEnumProviders = {
regexScripts: () => getRegexScripts().map(script => {
const isGlobal = extension_settings.regex?.some(x => x.scriptName === script.scriptName);
return new SlashCommandEnumValue(script.scriptName, `${enumIcons.getStateIcon(!script.disabled)} [${isGlobal ? 'global' : 'scoped'}] ${script.findRegex}`,
isGlobal ? enumTypes.enum : enumTypes.name, isGlobal ? 'G' : 'S');
}),
};
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'regex',
callback: runRegexCallback,
returns: 'replaced text',
namedArgumentList: [
new SlashCommandNamedArgument(
'name', 'script name', [ARGUMENT_TYPE.STRING], true,
),
SlashCommandNamedArgument.fromProps({
name: 'name',
description: 'script name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: localEnumProviders.regexScripts,
}),
],
unnamedArgumentList: [
new SlashCommandArgument(

View File

@@ -34,6 +34,7 @@ export async function getMultimodalCaption(base64Img, prompt) {
const isCustom = extension_settings.caption.multimodal_api === 'custom';
const isOoba = extension_settings.caption.multimodal_api === 'ooba';
const isKoboldCpp = extension_settings.caption.multimodal_api === 'koboldcpp';
const isVllm = extension_settings.caption.multimodal_api === 'vllm';
const base64Bytes = base64Img.length * 0.75;
const compressionLimit = 2 * 1024 * 1024;
if ((['google', 'openrouter'].includes(extension_settings.caption.multimodal_api) && base64Bytes > compressionLimit) || isOoba || isKoboldCpp) {
@@ -65,6 +66,14 @@ export async function getMultimodalCaption(base64Img, prompt) {
requestBody.server_url = textgenerationwebui_settings.server_urls[textgen_types.OLLAMA];
}
if (isVllm) {
if (extension_settings.caption.multimodal_model === 'vllm_current') {
requestBody.model = textgenerationwebui_settings.vllm_model;
}
requestBody.server_url = textgenerationwebui_settings.server_urls[textgen_types.VLLM];
}
if (isLlamaCpp) {
requestBody.server_url = textgenerationwebui_settings.server_urls[textgen_types.LLAMACPP];
}
@@ -151,6 +160,14 @@ function throwIfInvalidModel(useReverseProxy) {
throw new Error('KoboldCpp server URL is not set.');
}
if (extension_settings.caption.multimodal_api === 'vllm' && !textgenerationwebui_settings.server_urls[textgen_types.VLLM]) {
throw new Error('vLLM server URL is not set.');
}
if (extension_settings.caption.multimodal_api === 'vllm' && extension_settings.caption.multimodal_model === 'vllm_current' && !textgenerationwebui_settings.vllm_model) {
throw new Error('vLLM model is not set.');
}
if (extension_settings.caption.multimodal_api === 'custom' && !oai_settings.custom_url) {
throw new Error('Custom API URL is not set.');
}

View File

@@ -17,6 +17,7 @@ import {
getCharacterAvatar,
formatCharacterAvatar,
substituteParams,
substituteParamsExtended,
} from '../../../script.js';
import { getApiUrl, getContext, extension_settings, doExtrasFetch, modules, renderExtensionTemplateAsync, writeExtensionField } from '../../extensions.js';
import { selected_group } from '../../group-chats.js';
@@ -30,6 +31,8 @@ import { SlashCommand } from '../../slash-commands/SlashCommand.js';
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
import { resolveVariable } from '../../variables.js';
import { debounce_timeout } from '../../constants.js';
import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
import { SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js';
export { MODULE_NAME };
const MODULE_NAME = 'sd';
@@ -50,7 +53,15 @@ const sources = {
pollinations: 'pollinations',
};
const initiators = {
command: 'command',
action: 'action',
interactive: 'interactive',
wand: 'wand',
};
const generationMode = {
MESSAGE: -1,
CHARACTER: 0,
USER: 1,
SCENARIO: 2,
@@ -62,6 +73,7 @@ const generationMode = {
CHARACTER_MULTIMODAL: 8,
USER_MULTIMODAL: 9,
FACE_MULTIMODAL: 10,
FREE_EXTENDED: 11,
};
const multimodalMap = {
@@ -71,6 +83,7 @@ const multimodalMap = {
};
const modeLabels = {
[generationMode.MESSAGE]: 'Chat Message Template',
[generationMode.CHARACTER]: 'Character ("Yourself")',
[generationMode.FACE]: 'Portrait ("Your Face")',
[generationMode.USER]: 'User ("Me")',
@@ -81,6 +94,7 @@ const modeLabels = {
[generationMode.CHARACTER_MULTIMODAL]: 'Character (Multimodal Mode)',
[generationMode.FACE_MULTIMODAL]: 'Portrait (Multimodal Mode)',
[generationMode.USER_MULTIMODAL]: 'User (Multimodal Mode)',
[generationMode.FREE_EXTENDED]: 'Free Mode (LLM-Extended)',
};
const triggerWords = {
@@ -94,7 +108,7 @@ const triggerWords = {
};
const messageTrigger = {
activationRegex: /\b(send|mail|imagine|generate|make|create|draw|paint|render)\b.{0,10}\b(pic|picture|image|drawing|painting|photo|photograph)\b(?:\s+of)?(?:\s+(?:a|an|the|this|that|those)?)?(.+)/i,
activationRegex: /\b(send|mail|imagine|generate|make|create|draw|paint|render|show)\b.{0,10}\b(pic|picture|image|drawing|painting|photo|photograph)\b(?:\s+of)?(?:\s+(?:a|an|the|this|that|those|your)?)?(.+)/i,
specialCases: {
[generationMode.CHARACTER]: ['you', 'yourself'],
[generationMode.USER]: ['me', 'myself'],
@@ -106,6 +120,8 @@ const messageTrigger = {
};
const promptTemplates = {
// Not really a prompt template, rather an outcome message template
[generationMode.MESSAGE]: '[{{char}} sends a picture that contains: {{prompt}}].',
/*OLD: [generationMode.CHARACTER]: "Pause your roleplay and provide comma-delimited list of phrases and keywords which describe {{char}}'s physical appearance and clothing. Ignore {{char}}'s personality traits, and chat history when crafting this description. End your response once the comma-delimited list is complete. Do not roleplay when writing this description, and do not attempt to continue the story.", */
[generationMode.CHARACTER]: '[In the next response I want you to provide only a detailed comma-delimited list of keywords and phrases which describe {{char}}. The list must include all of the following items in this order: name, species and race, gender, age, clothing, occupation, physical features and appearances. Do not include descriptions of non-visual qualities such as personality, movements, scents, mental traits, or anything which could not be seen in a still photograph. Do not write in full sentences. Prefix your description with the phrase \'full body portrait,\']',
//face-specific prompt
@@ -143,6 +159,7 @@ const promptTemplates = {
[generationMode.FACE_MULTIMODAL]: 'Provide an exhaustive comma-separated list of tags describing the appearance of the character on this image in great detail. Start with "close-up portrait".',
[generationMode.CHARACTER_MULTIMODAL]: 'Provide an exhaustive comma-separated list of tags describing the appearance of the character on this image in great detail. Start with "full body portrait".',
[generationMode.USER_MULTIMODAL]: 'Provide an exhaustive comma-separated list of tags describing the appearance of the character on this image in great detail. Start with "full body portrait".',
[generationMode.FREE_EXTENDED]: 'Pause your roleplay and provide an exhaustive comma-separated list of tags describing the appearance of "{0}" in great detail. Start with {{charPrefix}} (sic) if the subject is associated with {{char}}.',
};
const defaultPrefix = 'best quality, absurdres, aesthetic,';
@@ -204,6 +221,7 @@ const defaultSettings = {
interactive_mode: false,
multimodal_captioning: false,
snap: false,
free_extend: false,
prompts: promptTemplates,
@@ -261,6 +279,11 @@ const defaultSettings = {
// Pollinations settings
pollinations_enhance: false,
pollinations_refine: false,
// Visibility toggles
wand_visible: false,
command_visible: false,
interactive_visible: false,
};
const writePromptFieldsDebounced = debounce(writePromptFields, debounce_timeout.relaxed);
@@ -312,7 +335,7 @@ function processTriggers(chat, _, abort) {
}
abort(true);
setTimeout(() => generatePicture('sd', subject, message), 1);
setTimeout(() => generatePicture(initiators.interactive, {}, subject, message), 1);
} catch {
console.log('SD: Failed to process triggers.');
return;
@@ -417,8 +440,12 @@ async function loadSettings() {
$('#sd_comfy_prompt').val(extension_settings.sd.comfy_prompt);
$('#sd_snap').prop('checked', extension_settings.sd.snap);
$('#sd_clip_skip').val(extension_settings.sd.clip_skip);
$('#sd_clip_skip_value').text(extension_settings.sd.clip_skip);
$('#sd_clip_skip_value').val(extension_settings.sd.clip_skip);
$('#sd_seed').val(extension_settings.sd.seed);
$('#sd_free_extend').prop('checked', extension_settings.sd.free_extend);
$('#sd_wand_visible').prop('checked', extension_settings.sd.wand_visible);
$('#sd_command_visible').prop('checked', extension_settings.sd.command_visible);
$('#sd_interactive_visible').prop('checked', extension_settings.sd.interactive_visible);
for (const style of extension_settings.sd.styles) {
const option = document.createElement('option');
@@ -476,7 +503,7 @@ async function loadSettingOptions() {
function addPromptTemplates() {
$('#sd_prompt_templates').empty();
for (const [name, prompt] of Object.entries(extension_settings.sd.prompts)) {
for (const [name, prompt] of Object.entries(extension_settings.sd.prompts).sort((a, b) => Number(a[0]) - Number(b[0]))) {
const label = $('<label></label>')
.text(modeLabels[name])
.attr('for', `sd_prompt_${name}`)
@@ -683,6 +710,18 @@ function onChatChanged() {
$('#sd_character_prompt').val(characterPrompt);
$('#sd_character_negative_prompt').val(negativePrompt);
$('#sd_character_prompt_share').prop('checked', hasSharedData);
adjustElementScrollHeight();
}
function adjustElementScrollHeight() {
if (!$('.sd_settings').is(':visible')) {
return;
}
resetScrollHeight($('#sd_prompt_prefix'));
resetScrollHeight($('#sd_negative_prompt'));
resetScrollHeight($('#sd_character_prompt'));
resetScrollHeight($('#sd_character_negative_prompt'));
}
function onCharacterPromptInput() {
@@ -762,9 +801,29 @@ function onRefineModeInput() {
saveSettingsDebounced();
}
function onFreeExtendInput() {
extension_settings.sd.free_extend = !!$('#sd_free_extend').prop('checked');
saveSettingsDebounced();
}
function onWandVisibleInput() {
extension_settings.sd.wand_visible = !!$('#sd_wand_visible').prop('checked');
saveSettingsDebounced();
}
function onCommandVisibleInput() {
extension_settings.sd.command_visible = !!$('#sd_command_visible').prop('checked');
saveSettingsDebounced();
}
function onInteractiveVisibleInput() {
extension_settings.sd.interactive_visible = !!$('#sd_interactive_visible').prop('checked');
saveSettingsDebounced();
}
function onClipSkipInput() {
extension_settings.sd.clip_skip = Number($('#sd_clip_skip').val());
$('#sd_clip_skip_value').text(extension_settings.sd.clip_skip);
$('#sd_clip_skip_value').val(extension_settings.sd.clip_skip);
saveSettingsDebounced();
}
@@ -775,13 +834,13 @@ function onSeedInput() {
function onScaleInput() {
extension_settings.sd.scale = Number($('#sd_scale').val());
$('#sd_scale_value').text(extension_settings.sd.scale.toFixed(1));
$('#sd_scale_value').val(extension_settings.sd.scale.toFixed(1));
saveSettingsDebounced();
}
function onStepsInput() {
extension_settings.sd.steps = Number($('#sd_steps').val());
$('#sd_steps_value').text(extension_settings.sd.steps);
$('#sd_steps_value').val(extension_settings.sd.steps);
saveSettingsDebounced();
}
@@ -844,13 +903,23 @@ function onSchedulerChange() {
function onWidthInput() {
extension_settings.sd.width = Number($('#sd_width').val());
$('#sd_width_value').text(extension_settings.sd.width);
$('#sd_width_value').val(extension_settings.sd.width);
saveSettingsDebounced();
}
function onHeightInput() {
extension_settings.sd.height = Number($('#sd_height').val());
$('#sd_height_value').text(extension_settings.sd.height);
$('#sd_height_value').val(extension_settings.sd.height);
saveSettingsDebounced();
}
function onSwapDimensionsClick() {
const w = extension_settings.sd.height;
const h = extension_settings.sd.width;
extension_settings.sd.width = w;
extension_settings.sd.height = h;
$('#sd_width').val(w).trigger('input');
$('#sd_height').val(h).trigger('input');
saveSettingsDebounced();
}
@@ -890,7 +959,7 @@ async function onViewAnlasClick() {
function onNovelUpscaleRatioInput() {
extension_settings.sd.novel_upscale_ratio = Number($('#sd_novel_upscale_ratio').val());
$('#sd_novel_upscale_ratio_value').text(extension_settings.sd.novel_upscale_ratio.toFixed(1));
$('#sd_novel_upscale_ratio_value').val(extension_settings.sd.novel_upscale_ratio.toFixed(1));
saveSettingsDebounced();
}
@@ -992,19 +1061,19 @@ function onHrUpscalerChange() {
function onHrScaleInput() {
extension_settings.sd.hr_scale = Number($('#sd_hr_scale').val());
$('#sd_hr_scale_value').text(extension_settings.sd.hr_scale.toFixed(1));
$('#sd_hr_scale_value').val(extension_settings.sd.hr_scale.toFixed(1));
saveSettingsDebounced();
}
function onDenoisingStrengthInput() {
extension_settings.sd.denoising_strength = Number($('#sd_denoising_strength').val());
$('#sd_denoising_strength_value').text(extension_settings.sd.denoising_strength.toFixed(2));
$('#sd_denoising_strength_value').val(extension_settings.sd.denoising_strength.toFixed(2));
saveSettingsDebounced();
}
function onHrSecondPassStepsInput() {
extension_settings.sd.hr_second_pass_steps = Number($('#sd_hr_second_pass_steps').val());
$('#sd_hr_second_pass_steps_value').text(extension_settings.sd.hr_second_pass_steps);
$('#sd_hr_second_pass_steps_value').val(extension_settings.sd.hr_second_pass_steps);
saveSettingsDebounced();
}
@@ -1026,6 +1095,7 @@ async function changeComfyWorkflow(_, name) {
} else {
toastr.error(`ComfyUI Workflow "${name}" does not exist.`);
}
return '';
}
async function validateAutoUrl() {
@@ -2016,6 +2086,10 @@ function getGenerationType(prompt) {
mode = multimodalMap[mode];
}
if (mode === generationMode.FREE && extension_settings.sd.free_extend) {
mode = generationMode.FREE_EXTENDED;
}
return mode;
}
@@ -2080,7 +2154,16 @@ function getRawLastMessage() {
return `((${processReply(lastMessage.mes)})), (${processReply(character.scenario)}:0.7), (${processReply(character.description)}:0.5)`;
}
async function generatePicture(args, trigger, message, callback) {
/**
* Generates an image based on the given trigger word.
* @param {string} initiator The initiator of the image generation
* @param {Record<string, object>} args Command arguments
* @param {string} trigger Subject trigger word
* @param {string} [message] Chat message
* @param {function} [callback] Callback function
* @returns {Promise<string>} Image path
*/
async function generatePicture(initiator, args, trigger, message, callback) {
if (!trigger || trigger.trim().length === 0) {
console.log('Trigger word empty, aborting');
return;
@@ -2111,9 +2194,9 @@ async function generatePicture(args, trigger, message, callback) {
eventSource.emit(event_types.FORCE_SET_BACKGROUND, { url: imgUrl, path: imagePath });
if (typeof callbackOriginal === 'function') {
callbackOriginal(prompt, imagePath, generationType, negativePromptPrefix);
callbackOriginal(prompt, imagePath, generationType, negativePromptPrefix, initiator);
} else {
sendMessage(prompt, imagePath, generationType, negativePromptPrefix);
sendMessage(prompt, imagePath, generationType, negativePromptPrefix, initiator);
}
};
}
@@ -2122,18 +2205,19 @@ async function generatePicture(args, trigger, message, callback) {
callback = () => { };
}
const negativePromptPrefix = resolveVariable(args?.negative) || '';
const dimensions = setTypeSpecificDimensions(generationType);
let negativePromptPrefix = args?.negative || '';
let imagePath = '';
try {
const prompt = await getPrompt(generationType, message, trigger, quietPrompt);
const combineNegatives = (prefix) => { negativePromptPrefix = combinePrefixes(negativePromptPrefix, prefix); };
const prompt = await getPrompt(generationType, message, trigger, quietPrompt, combineNegatives);
console.log('Processed image prompt:', prompt);
context.deactivateSendButtons();
hideSwipeButtons();
imagePath = await sendGenerationRequest(generationType, prompt, negativePromptPrefix, characterName, callback);
imagePath = await sendGenerationRequest(generationType, prompt, negativePromptPrefix, characterName, callback, initiator);
} catch (err) {
console.trace(err);
throw new Error('SD prompt text generation failed.');
@@ -2196,7 +2280,16 @@ function restoreOriginalDimensions(savedParams) {
extension_settings.sd.width = savedParams.width;
}
async function getPrompt(generationType, message, trigger, quietPrompt) {
/**
* Generates a prompt for image generation.
* @param {number} generationType The type of image generation to perform.
* @param {string} message A message text to use for the image generation.
* @param {string} trigger A trigger string to use for the image generation.
* @param {string} quietPrompt A quiet prompt to use for the image generation.
* @param {function} combineNegatives A function that combines the negative prompt with other prompts.
* @returns {Promise<string>} - A promise that resolves when the prompt generation completes.
*/
async function getPrompt(generationType, message, trigger, quietPrompt, combineNegatives) {
let prompt;
switch (generationType) {
@@ -2204,7 +2297,7 @@ async function getPrompt(generationType, message, trigger, quietPrompt) {
prompt = message || getRawLastMessage();
break;
case generationMode.FREE:
prompt = generateFreeModePrompt(trigger.trim());
prompt = generateFreeModePrompt(trigger.trim(), combineNegatives);
break;
case generationMode.FACE_MULTIMODAL:
case generationMode.CHARACTER_MULTIMODAL:
@@ -2216,6 +2309,10 @@ async function getPrompt(generationType, message, trigger, quietPrompt) {
break;
}
if (generationType === generationMode.FREE_EXTENDED) {
prompt = generateFreeModePrompt(prompt.trim(), combineNegatives);
}
if (generationType !== generationMode.FREE) {
prompt = await refinePrompt(prompt, true);
}
@@ -2226,9 +2323,10 @@ async function getPrompt(generationType, message, trigger, quietPrompt) {
/**
* Generates a free prompt with a character-specific prompt prefix support.
* @param {string} trigger - The prompt to use for the image generation.
* @param {function} combineNegatives - A function that combines the negative prompt with other prompts.
* @returns {string}
*/
function generateFreeModePrompt(trigger) {
function generateFreeModePrompt(trigger, combineNegatives) {
return trigger
.replace(/(?:^char(\s|,)|\{\{charPrefix\}\})/gi, (_, suffix) => {
const getLastCharacterKey = () => {
@@ -2249,7 +2347,9 @@ function generateFreeModePrompt(trigger) {
const key = getLastCharacterKey();
const value = (extension_settings.sd.character_prompts[key] || '').trim();
return value ? value + (suffix || '') : '';
const negativeValue = (extension_settings.sd.character_negative_prompts[key] || '').trim();
typeof combineNegatives === 'function' && negativeValue ? combineNegatives(negativeValue) : void 0;
return value ? combinePrefixes(value, (suffix || '')) : '';
});
}
@@ -2335,12 +2435,13 @@ async function generatePrompt(quietPrompt) {
* @param {number} generationType Type of image generation
* @param {string} prompt Prompt to be used for image generation
* @param {string} additionalNegativePrefix Additional negative prompt to be used for image generation
* @param {string} [characterName] Name of the character
* @param {function} [callback] Callback function to be called after image generation
* @param {string} characterName Name of the character
* @param {function} callback Callback function to be called after image generation
* @param {string} initiator The initiator of the image generation
* @returns
*/
async function sendGenerationRequest(generationType, prompt, additionalNegativePrefix, characterName = null, callback) {
const noCharPrefix = [generationMode.FREE, generationMode.BACKGROUND, generationMode.USER, generationMode.USER_MULTIMODAL];
async function sendGenerationRequest(generationType, prompt, additionalNegativePrefix, characterName, callback, initiator) {
const noCharPrefix = [generationMode.FREE, generationMode.BACKGROUND, generationMode.USER, generationMode.USER_MULTIMODAL, generationMode.FREE_EXTENDED];
const prefix = noCharPrefix.includes(generationType)
? extension_settings.sd.prompt_prefix
: combinePrefixes(extension_settings.sd.prompt_prefix, getCharacterPrefix());
@@ -2405,7 +2506,7 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP
const filename = `${characterName}_${humanizedDateTime()}`;
const base64Image = await saveBase64AsFile(result.data, characterName, filename, result.format);
callback ? callback(prompt, base64Image, generationType, additionalNegativePrefix) : sendMessage(prompt, base64Image, generationType, additionalNegativePrefix);
callback ? callback(prompt, base64Image, generationType, additionalNegativePrefix, initiator) : sendMessage(prompt, base64Image, generationType, additionalNegativePrefix, initiator);
return base64Image;
}
@@ -3009,15 +3110,17 @@ async function onComfyDeleteWorkflowClick() {
* @param {string} image Base64 encoded image
* @param {number} generationType Generation type of the image
* @param {string} additionalNegativePrefix Additional negative prompt used for the image generation
* @param {string} initiator The initiator of the image generation
*/
async function sendMessage(prompt, image, generationType, additionalNegativePrefix) {
async function sendMessage(prompt, image, generationType, additionalNegativePrefix, initiator) {
const context = getContext();
const name = context.groupId ? systemUserName : context.name2;
const messageText = `[${name} sends a picture that contains: ${prompt}]`;
const template = extension_settings.sd.prompts[generationMode.MESSAGE] || '{{prompt}}';
const messageText = substituteParamsExtended(template, { char: name, prompt: prompt });
const message = {
name: name,
is_user: false,
is_system: true,
is_system: !getVisibilityByInitiator(initiator),
send_date: getMessageTimeStamp(),
mes: messageText,
extra: {
@@ -3033,11 +3136,29 @@ async function sendMessage(prompt, image, generationType, additionalNegativePref
context.saveChat();
}
/**
* Gets the visibility of the resulting message based on the initiator.
* @param {string} initiator Generation initiator
* @returns {boolean} Is resulting message visible
*/
function getVisibilityByInitiator(initiator) {
switch (initiator) {
case initiators.interactive:
return !!extension_settings.sd.interactive_visible;
case initiators.wand:
return !!extension_settings.sd.wand_visible;
case initiators.command:
return !!extension_settings.sd.command_visible;
default:
return false;
}
}
async function addSDGenButtons() {
const buttonHtml = await renderExtensionTemplateAsync('stable-diffusion', 'button');
const dropdownHtml = await renderExtensionTemplateAsync('stable-diffusion', 'dropdown');
$('#extensionsMenu').prepend(buttonHtml);
$('#sd_wand_container').append(buttonHtml);
$(document.body).append(dropdownHtml);
const messageButton = $('.sd_message_gen');
@@ -3082,7 +3203,7 @@ async function addSDGenButtons() {
if (param) {
console.log('doing /sd ' + param);
generatePicture('sd', param);
generatePicture(initiators.wand, {}, param);
}
});
}
@@ -3159,11 +3280,11 @@ async function sdMessageButton(e) {
const generationType = message?.extra?.generationType ?? generationMode.FREE;
console.log('Regenerating an image, using existing prompt:', prompt);
dimensions = setTypeSpecificDimensions(generationType);
await sendGenerationRequest(generationType, prompt, negative, characterFileName, saveGeneratedImage);
await sendGenerationRequest(generationType, prompt, negative, characterFileName, saveGeneratedImage, initiators.action);
}
else {
console.log('doing /sd raw last');
await generatePicture('sd', 'raw_last', messageText, saveGeneratedImage);
await generatePicture(initiators.action, {}, 'raw_last', messageText, saveGeneratedImage);
}
}
catch (error) {
@@ -3226,15 +3347,17 @@ jQuery(async () => {
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'imagine',
callback: generatePicture,
callback: (args, trigger) => generatePicture(initiators.command, args, String(trigger)),
aliases: ['sd', 'img', 'image'],
namedArgumentList: [
new SlashCommandNamedArgument(
'quiet', 'whether to post the generated image to chat', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false', ['false', 'true'],
),
new SlashCommandNamedArgument(
'negative', 'negative prompt prefix', [ARGUMENT_TYPE.STRING], false, false, '',
'quiet', 'whether to post the generated image to chat', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false',
),
SlashCommandNamedArgument.fromProps({
name: 'negative',
description: 'negative prompt prefix',
typeList: [ARGUMENT_TYPE.STRING],
}),
],
unnamedArgumentList: [
new SlashCommandArgument(
@@ -3243,7 +3366,7 @@ jQuery(async () => {
],
helpString: `
<div>
Requests to generate an image and posts it to chat (unless quiet=true argument is specified). Supported arguments: <code>${Object.values(triggerWords).flat().join(', ')}</code>.
Requests to generate an image and posts it to chat (unless <code>quiet=true</code> argument is specified).</code>.
</div>
<div>
Anything else would trigger a "free mode" to make generate whatever you prompted. Example: <code>/imagine apple tree</code> would generate a picture of an apple tree. Returns a link to the generated image.
@@ -3256,16 +3379,19 @@ jQuery(async () => {
callback: changeComfyWorkflow,
aliases: ['icw'],
unnamedArgumentList: [
new SlashCommandArgument(
'workflowName', [ARGUMENT_TYPE.STRING], true,
),
SlashCommandArgument.fromProps({
description: 'workflow name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: () => Array.from(document.querySelectorAll('#sd_comfy_workflow > [value]')).map(x => x.getAttribute('value')).map(workflow => new SlashCommandEnumValue(workflow)),
}),
],
helpString: '(workflowName) - change the workflow to be used for image generation with ComfyUI, e.g. <pre><code>/imagine-comfy-workflow MyWorkflow</code></pre>',
}));
const template = await renderExtensionTemplateAsync('stable-diffusion', 'settings', defaultSettings);
$('#extensions_settings').append(template);
$('#sd_container').append(template);
$('#sd_source').on('change', onSourceChange);
$('#sd_scale').on('input', onScaleInput);
$('#sd_steps').on('input', onStepsInput);
@@ -3326,6 +3452,11 @@ jQuery(async () => {
$('#sd_clip_skip').on('input', onClipSkipInput);
$('#sd_seed').on('input', onSeedInput);
$('#sd_character_prompt_share').on('input', onCharacterPromptShareInput);
$('#sd_free_extend').on('input', onFreeExtendInput);
$('#sd_wand_visible').on('input', onWandVisibleInput);
$('#sd_command_visible').on('input', onCommandVisibleInput);
$('#sd_interactive_visible').on('input', onInteractiveVisibleInput);
$('#sd_swap_dimensions').on('click', onSwapDimensionsClick);
$('.sd_settings .inline-drawer-toggle').on('click', function () {
initScrollHeight($('#sd_prompt_prefix'));

View File

@@ -22,6 +22,11 @@
<input id="sd_multimodal_captioning" type="checkbox" />
<span data-i18n="sd_multimodal_captioning_txt">Use multimodal captioning for portraits</span>
</label>
<label for="sd_free_extend" class="checkbox_label" data-i18n="[title]sd_free_extend" title="Automatically extend free mode subject prompts (not portraits or backgrounds) using a currently selected LLM.">
<input id="sd_free_extend" type="checkbox" />
<span data-i18n="sd_free_extend_txt">Extend free mode prompts</span>
<small data-i18n="sd_free_extend_small">(interactive/commands)</small>
</label>
<label for="sd_expand" class="checkbox_label" data-i18n="[title]sd_expand" title="Automatically extend prompts using text generation model">
<input id="sd_expand" type="checkbox" />
<span data-i18n="sd_expand_txt">Auto-enhance prompts</span>
@@ -123,16 +128,20 @@
<div data-sd-source="openai">
<small data-i18n="These settings only apply to DALL-E 3">These settings only apply to DALL-E 3</small>
<div class="flex-container">
<label for="sd_openai_style" data-i18n="Image Style">Image Style</label>
<select id="sd_openai_style">
<option value="vivid">Vivid</option>
<option value="natural">Natural</option>
</select>
<label for="sd_openai_quality" data-i18n="Image Quality">Image Quality</label>
<select id="sd_openai_quality">
<option value="standard" data-i18n="Standard">Standard</option>
<option value="hd" data-i18n="HD">HD</option>
</select>
<div class="flex1">
<label for="sd_openai_style" data-i18n="Image Style">Image Style</label>
<select id="sd_openai_style">
<option value="vivid">Vivid</option>
<option value="natural">Natural</option>
</select>
</div>
<div class="flex1">
<label for="sd_openai_quality" data-i18n="Image Quality">Image Quality</label>
<select id="sd_openai_quality">
<option value="standard" data-i18n="Standard">Standard</option>
<option value="hd" data-i18n="HD">HD</option>
</select>
</div>
</div>
</div>
<div data-sd-source="comfy">
@@ -180,91 +189,164 @@
</label>
</div>
</div>
<label for="sd_scale" data-i18n="CFG Scale">CFG Scale (<span id="sd_scale_value"></span>)</label>
<input id="sd_scale" type="range" min="{{scale_min}}" max="{{scale_max}}" step="{{scale_step}}" value="{{scale}}" />
<div data-sd-source="novel" class="marginTopBot5">
<label class="checkbox_label" for="sd_novel_decrisper" title="Reduce artifacts caused by high guidance values.">
<input id="sd_novel_decrisper" type="checkbox" />
<span data-i18n="Decrisper">
Decrisper
</span>
</label>
<div class="flex-container">
<div class="flex1">
<label for="sd_model" data-i18n="Model">Model</label>
<select id="sd_model"></select>
</div>
<div class="flex1" data-sd-source="comfy">
<label for="sd_vae">VAE</label>
<select id="sd_vae"></select>
</div>
</div>
<label for="sd_steps" data-i18n="Sampling steps">Sampling steps (<span id="sd_steps_value"></span>)</label>
<input id="sd_steps" type="range" min="{{steps_min}}" max="{{steps_max}}" step="{{steps_step}}" value="{{steps}}" />
<label for="sd_width" data-i18n="Width">Width (<span id="sd_width_value"></span>)</label>
<input id="sd_width" type="range" max="{{dimension_max}}" min="{{dimension_min}}" step="{{dimension_step}}" value="{{width}}" />
<label for="sd_height" data-i18n="Height">Height (<span id="sd_height_value"></span>)</label>
<input id="sd_height" type="range" max="{{dimension_max}}" min="{{dimension_min}}" step="{{dimension_step}}" value="{{height}}" />
<label for="sd_resolution" data-i18n="Resolution">Resolution</label>
<select id="sd_resolution"><!-- Populated in JS --></select>
<label for="sd_model" data-i18n="Model">Model</label>
<select id="sd_model"></select>
<label for="sd_sampler" data-i18n="Sampling method">Sampling method</label>
<select id="sd_sampler"></select>
<label data-sd-source="horde" for="sd_horde_karras" class="checkbox_label marginTopBot5">
<input id="sd_horde_karras" type="checkbox" />
<span data-i18n="Karras (not all samplers supported)">
Karras (not all samplers supported)
</span>
</label>
<div data-sd-source="novel" class="flex-container marginTopBot5">
<label class="flex1 checkbox_label" data-i18n="[title]SMEA versions of samplers are modified to perform better at high resolution." title="SMEA versions of samplers are modified to perform better at high resolution.">
<input id="sd_novel_sm" type="checkbox" />
<span data-i18n="SMEA">
SMEA
</span>
</label>
<label class="flex1 checkbox_label" data-i18n="[title]DYN variants of SMEA samplers often lead to more varied output, but may fail at very high resolutions." title="DYN variants of SMEA samplers often lead to more varied output, but may fail at very high resolutions.">
<input id="sd_novel_sm_dyn" type="checkbox" />
<span data-i18n="DYN">
DYN
</span>
</label>
<div class="flex-container">
<div class="flex1">
<label for="sd_sampler" data-i18n="Sampling method">Sampling method</label>
<select id="sd_sampler"></select>
</div>
<div class="flex1" data-sd-source="comfy,auto">
<label for="sd_scheduler" data-i18n="Scheduler">Scheduler</label>
<select id="sd_scheduler"></select>
</div>
</div>
<div data-sd-source="comfy,auto">
<label for="sd_scheduler" data-i18n="Scheduler">Scheduler</label>
<select id="sd_scheduler"></select>
<div class="flex-container">
<div class="flex1">
<label for="sd_resolution" data-i18n="Resolution">Resolution</label>
<select id="sd_resolution"><!-- Populated in JS --></select>
</div>
<div class="flex1" data-sd-source="auto,vlad,drawthings">
<label for="sd_hr_upscaler" data-i18n="Upscaler">Upscaler</label>
<select id="sd_hr_upscaler"></select>
</div>
</div>
<div data-sd-source="comfy">
<label for="sd_vae">VAE</label>
<select id="sd_vae"></select>
<div class="flex-container">
<div class="alignitemscenter flex-container flexFlowColumn flexGrow flexShrink gap0 flexBasis48p">
<small>
<span data-i18n="Sampling steps">Sampling steps</span>
</small>
<input class="neo-range-slider" type="range" id="sd_steps" name="sd_steps" min="{{steps_min}}" max="{{steps_max}}" step="{{steps_step}}" value="{{steps}}" >
<input class="neo-range-input" type="number" id="sd_steps_value" data-for="sd_steps" min="{{steps_min}}" max="{{steps_max}}" step="{{steps_step}}" value="{{steps}}" >
</div>
<div class="alignitemscenter flex-container flexFlowColumn flexGrow flexShrink gap0 flexBasis48p">
<small>
<span data-i18n="CFG Scale">CFG Scale</span>
</small>
<input class="neo-range-slider" type="range" id="sd_scale" name="sd_scale" min="{{scale_min}}" max="{{scale_max}}" step="{{scale_step}}" value="{{scale}}" >
<input class="neo-range-input" type="number" id="sd_scale_value" data-for="sd_scale" min="{{scale_min}}" max="{{scale_max}}" step="{{scale_step}}" value="{{scale}}" >
</div>
</div>
<div class="flex-container marginTopBot5">
<div id="sd_dimensions_block" class="flex-container">
<div class="alignitemscenter flex-container flexFlowColumn flexGrow flexShrink gap0 flexBasis48p">
<small>
<span data-i18n="Width">Width</span>
</small>
<input class="neo-range-slider" type="range" id="sd_width" name="sd_width" max="{{dimension_max}}" min="{{dimension_min}}" step="{{dimension_step}}" value="{{width}}" >
<input class="neo-range-input" type="number" id="sd_width_value" data-for="sd_width" max="{{dimension_max}}" min="{{dimension_min}}" step="{{dimension_step}}" value="{{width}}" >
</div>
<div class="alignitemscenter flex-container flexFlowColumn flexGrow flexShrink gap0 flexBasis48p">
<small>
<span data-i18n="Height">Height</span>
</small>
<input class="neo-range-slider" type="range" id="sd_height" name="sd_height" max="{{dimension_max}}" min="{{dimension_min}}" step="{{dimension_step}}" value="{{height}}" >
<input class="neo-range-input" type="number" id="sd_height_value" data-for="sd_height" max="{{dimension_max}}" min="{{dimension_min}}" step="{{dimension_step}}" value="{{height}}" >
</div>
<div id="sd_swap_dimensions" class="right_menu_button" title="Swap width and height" data-i18n="[title]Swap width and height">
<i class="fa-solid fa-arrow-right-arrow-left"></i>
</div>
</div>
<div class="flex-container">
<div class="alignitemscenter flex-container flexFlowColumn flexGrow flexShrink gap0 flexBasis48p" data-sd-source="auto,vlad,drawthings">
<small>
<span data-i18n="Upscale by">Upscale by</span>
</small>
<input class="neo-range-slider" type="range" id="sd_hr_scale" name="sd_hr_scale" min="{{hr_scale_min}}" max="{{hr_scale_max}}" step="{{hr_scale_step}}" value="{{hr_scale}}" >
<input class="neo-range-input" type="number" id="sd_hr_scale_value" data-for="sd_hr_scale" min="{{hr_scale_min}}" max="{{hr_scale_max}}" step="{{hr_scale_step}}" value="{{hr_scale}}" >
</div>
<div class="alignitemscenter flex-container flexFlowColumn flexGrow flexShrink gap0 flexBasis48p" data-sd-source="auto,vlad">
<small>
<span data-i18n="Denoising strength">Denoising strength</span>
</small>
<input class="neo-range-slider" type="range" id="sd_denoising_strength" name="sd_denoising_strength" min="{{denoising_strength_min}}" max="{{denoising_strength_max}}" step="{{denoising_strength_step}}" value="{{denoising_strength}}" >
<input class="neo-range-input" type="number" id="sd_denoising_strength_value" data-for="sd_denoising_strength" min="{{denoising_strength_min}}" max="{{denoising_strength_max}}" step="{{denoising_strength_step}}" value="{{denoising_strength}}" >
</div>
<div class="alignitemscenter flex-container flexFlowColumn flexGrow flexShrink gap0 flexBasis48p" data-sd-source="auto,vlad">
<small>
<span data-i18n="Hires steps (2nd pass)">Hires steps (2nd pass)</span>
</small>
<input class="neo-range-slider" type="range" id="sd_hr_second_pass_steps" name="sd_hr_second_pass_steps" max="{{hr_second_pass_steps_max}}" step="{{hr_second_pass_steps_step}}" value="{{hr_second_pass_steps}}" >
<input class="neo-range-input" type="number" id="sd_hr_second_pass_steps_value" data-for="sd_hr_second_pass_steps" max="{{hr_second_pass_steps_max}}" step="{{hr_second_pass_steps_step}}" value="{{hr_second_pass_steps}}" >
</div>
<div class="alignitemscenter flex-container flexFlowColumn flexGrow flexShrink gap0 flexBasis48p" data-sd-source="novel">
<small>
<span data-i18n="Upscale by">Upscale by</span>
</small>
<input class="neo-range-slider" type="range" id="sd_novel_upscale_ratio" name="sd_novel_upscale_ratio" min="{{novel_upscale_ratio_min}}" max="{{novel_upscale_ratio_max}}" step="{{novel_upscale_ratio_step}}" value="{{novel_upscale_ratio}}" >
<input class="neo-range-input" type="number" id="sd_novel_upscale_ratio_value" data-for="sd_novel_upscale_ratio" min="{{novel_upscale_ratio_min}}" max="{{novel_upscale_ratio_max}}" step="{{novel_upscale_ratio_step}}" value="{{novel_upscale_ratio}}" >
</div>
<div class="alignitemscenter flex-container flexFlowColumn flexGrow flexShrink gap0 flexBasis48p" data-sd-source="auto,vlad,comfy,horde,drawthings,extras">
<small>
<span data-i18n="CLIP Skip">CLIP Skip</span>
</small>
<input class="neo-range-slider" type="range" id="sd_clip_skip" name="sd_clip_skip" min="{{clip_skip_min}}" max="{{clip_skip_max}}" step="{{clip_skip_step}}" value="{{clip_skip}}" >
<input class="neo-range-input" type="number" id="sd_clip_skip_value" data-for="sd_clip_skip" min="{{clip_skip_min}}" max="{{clip_skip_max}}" step="{{clip_skip_step}}" value="{{clip_skip}}" >
</div>
</div>
<div class="flex-container marginTopBot5" data-sd-source="auto,vlad,extras,horde,drawthings,comfy">
<label class="flex1 checkbox_label">
<input id="sd_restore_faces" type="checkbox" />
<span data-i18n="Restore Faces">Restore Faces</span>
<small data-i18n="Restore Faces">Restore Faces</small>
</label>
<label class="flex1 checkbox_label">
<input id="sd_enable_hr" type="checkbox" />
<span data-i18n="Hires. Fix">Hires. Fix</span>
<small data-i18n="Hires. Fix">Hires. Fix</small>
</label>
<label data-sd-source="horde" for="sd_horde_karras" class="flex1 checkbox_label">
<input id="sd_horde_karras" type="checkbox" />
<small data-i18n="Karras">Karras</small>
<i class="fa-solid fa-info-circle fa-sm opacity50p" data-i18n="[title]Not all samplers supported." title="Not all samplers supported."></i>
</label>
</div>
<div data-sd-source="auto,vlad,comfy,horde,drawthings,extras" class="marginTopBot5">
<label for="sd_clip_skip">CLIP Skip (<span id="sd_clip_skip_value"></span>)</label>
<input type="range" id="sd_clip_skip" min="{{clip_skip_min}}" max="{{clip_skip_max}}" step="{{clip_skip_step}}" value="{{clip_skip}}" />
</div>
<div data-sd-source="auto,vlad,drawthings">
<label for="sd_hr_upscaler" data-i18n="Upscaler">Upscaler</label>
<select id="sd_hr_upscaler"></select>
<label for="sd_hr_scale"><span data-i18n="Upscale by">Upscale by</span> (<span id="sd_hr_scale_value"></span>)</label>
<input id="sd_hr_scale" type="range" min="{{hr_scale_min}}" max="{{hr_scale_max}}" step="{{hr_scale_step}}" value="{{hr_scale}}" />
</div>
<div data-sd-source="auto,vlad">
<label for="sd_denoising_strength"><span data-i18n="Denoising strength">Denoising strength</span> (<span id="sd_denoising_strength_value"></span>)</label>
<input id="sd_denoising_strength" type="range" min="{{denoising_strength_min}}" max="{{denoising_strength_max}}" step="{{denoising_strength_step}}" value="{{denoising_strength}}" />
<label for="sd_hr_second_pass_steps"><span data-i18n="Hires steps (2nd pass)">Hires steps (2nd pass)</span> (<span id="sd_hr_second_pass_steps_value"></span>)</label>
<input id="sd_hr_second_pass_steps" type="range" min="{{hr_second_pass_steps_min}}" max="{{hr_second_pass_steps_max}}" step="{{hr_second_pass_steps_step}}" value="{{hr_second_pass_steps}}" />
</div>
<div data-sd-source="novel">
<label for="sd_novel_upscale_ratio"><span data-i18n="Upscale by">Upscale by</span> (<span id="sd_novel_upscale_ratio_value"></span>)</label>
<input id="sd_novel_upscale_ratio" type="range" min="{{novel_upscale_ratio_min}}" max="{{novel_upscale_ratio_max}}" step="{{novel_upscale_ratio_step}}" value="{{novel_upscale_ratio}}" />
<div class="flex-container marginTopBot5" data-sd-source="novel">
<label class="flex1 checkbox_label" data-i18n="[title]SMEA versions of samplers are modified to perform better at high resolution." title="SMEA versions of samplers are modified to perform better at high resolution.">
<input id="sd_novel_sm" type="checkbox" />
<small data-i18n="SMEA">SMEA</small>
</label>
<label class="flex1 checkbox_label" data-i18n="[title]DYN variants of SMEA samplers often lead to more varied output, but may fail at very high resolutions." title="DYN variants of SMEA samplers often lead to more varied output, but may fail at very high resolutions.">
<input id="sd_novel_sm_dyn" type="checkbox" />
<small data-i18n="DYN">DYN</small>
</label>
<label class="flex1 checkbox_label" for="sd_novel_decrisper" title="Reduce artifacts caused by high guidance values.">
<input id="sd_novel_decrisper" type="checkbox" />
<small data-i18n="Decrisper">Decrisper</small>
</label>
</div>
<div data-sd-source="novel,togetherai,pollinations,comfy,drawthings,vlad,auto,horde,extras" class="marginTop5">
<label for="sd_seed">Seed</label>
<small>(-1 for random)</small>
<label for="sd_seed">
<span data-i18n="Seed">Seed</span>
<small data-i18n="(-1 for random)">(-1 for random)</small>
</label>
<input id="sd_seed" type="number" class="text_pole" min="-1" max="9999999999" step="1" />
</div>
<hr>
<h4 data-i18n="[title]Preset for prompt prefix and negative prompt" title="Preset for prompt prefix and negative prompt">
<span data-i18n="Style">Style</span>
@@ -279,16 +361,16 @@
</div>
</div>
<label for="sd_prompt_prefix" data-i18n="Common prompt prefix">Common prompt prefix</label>
<textarea id="sd_prompt_prefix" class="text_pole textarea_compact" rows="3" data-i18n="[placeholder]sd_prompt_prefix_placeholder" placeholder="Use {prompt} to specify where the generated prompt will be inserted"></textarea>
<textarea id="sd_prompt_prefix" class="text_pole textarea_compact" data-i18n="[placeholder]sd_prompt_prefix_placeholder" placeholder="Use {prompt} to specify where the generated prompt will be inserted"></textarea>
<label for="sd_negative_prompt" data-i18n="Negative common prompt prefix">Negative common prompt prefix</label>
<textarea id="sd_negative_prompt" class="text_pole textarea_compact" rows="3"></textarea>
<textarea id="sd_negative_prompt" class="text_pole textarea_compact"></textarea>
<div id="sd_character_prompt_block">
<label for="sd_character_prompt" data-i18n="Character-specific prompt prefix">Character-specific prompt prefix</label>
<small data-i18n="Won't be used in groups.">Won't be used in groups.</small>
<textarea id="sd_character_prompt" class="text_pole textarea_compact" rows="3" data-i18n="[placeholder]sd_character_prompt_placeholder" placeholder="Any characteristics that describe the currently selected character. Will be added after a common prompt prefix.&#10;Example: female, green eyes, brown hair, pink shirt"></textarea>
<textarea id="sd_character_prompt" class="text_pole textarea_compact" data-i18n="[placeholder]sd_character_prompt_placeholder" placeholder="Any characteristics that describe the currently selected character. Will be added after a common prompt prefix.&#10;Example: female, green eyes, brown hair, pink shirt"></textarea>
<label for="sd_character_negative_prompt" data-i18n="Character-specific negative prompt prefix">Character-specific negative prompt prefix</label>
<small data-i18n="Won't be used in groups.">Won't be used in groups.</small>
<textarea id="sd_character_negative_prompt" class="text_pole textarea_compact" rows="3" data-i18n="[placeholder]sd_character_negative_prompt_placeholder" placeholder="Any characteristics that should not appear for the selected character. Will be added after a negative common prompt prefix.&#10;Example: jewellery, shoes, glasses"></textarea>
<textarea id="sd_character_negative_prompt" class="text_pole textarea_compact" data-i18n="[placeholder]sd_character_negative_prompt_placeholder" placeholder="Any characteristics that should not appear for the selected character. Will be added after a negative common prompt prefix.&#10;Example: jewellery, shoes, glasses"></textarea>
<label for="sd_character_prompt_share" class="checkbox_label flexWrap marginTop5">
<input id="sd_character_prompt_share" type="checkbox" />
<span data-i18n="Shareable">
@@ -299,6 +381,36 @@
</small>
</label>
</div>
<hr>
<h4 data-i18n="Chat Message Visibility (by source)">
Chat Message Visibility (by source)
</h4>
<small data-i18n="Uncheck to hide the extension's messages in chat prompts.">
Uncheck to hide the extension's messages in chat prompts.
</small>
<div class="flex-container flexFlowColumn marginTopBot5 flexGap10">
<label for="sd_wand_visible" class="checkbox_label">
<span class="flex1 flex-container alignItemsCenter">
<i class="fa-solid fa-wand-magic-sparkles"></i>
<span data-i18n="Extensions Menu">Extensions Menu</span>
</span>
<input id="sd_wand_visible" type="checkbox" />
</label>
<label for="sd_command_visible" class="checkbox_label">
<span class="flex1 flex-container alignItemsCenter">
<i class="fa-solid fa-terminal"></i>
<span data-i18n="Slash Command">Slash Command</span>
</span>
<input id="sd_command_visible" type="checkbox" />
</label>
<label for="sd_interactive_visible" class="checkbox_label">
<span class="flex1 flex-container alignItemsCenter">
<i class="fa-solid fa-message"></i>
<span data-i18n="Interactive Mode">Interactive Mode</span>
</span>
<input id="sd_interactive_visible" type="checkbox" />
</label>
</div>
</div>
</div>
<div class="inline-drawer">

View File

@@ -2,27 +2,6 @@
display: block;
}
#sd_gen {
/*order: 100;
width: 40px;
height: 40px;
margin: 0;
padding: 1px; */
outline: none;
border: none;
cursor: pointer;
transition: 0.3s;
opacity: 0.7;
display: flex;
align-items: center;
/* justify-content: center; */
}
#sd_gen:hover {
opacity: 1;
filter: brightness(1.2);
}
#sd_dropdown {
z-index: 30000;
backdrop-filter: blur(--SmartThemeBlurStrength);
@@ -96,3 +75,19 @@
opacity: 1;
}
}
.sd_settings .flex1.checkbox_label input[type="checkbox"] {
margin-right: 5px;
margin-left: 5px;
}
#sd_dimensions_block {
position: relative;
}
#sd_swap_dimensions {
position: absolute;
left: 50%;
top: 0;
transform: translateX(-50%);
}

View File

@@ -1,10 +1,11 @@
import { callPopup, main_api } from '../../../script.js';
import { main_api } from '../../../script.js';
import { getContext } from '../../extensions.js';
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
import { getFriendlyTokenizerName, getTextTokens, getTokenCountAsync, tokenizers } from '../../tokenizers.js';
import { resetScrollHeight, debounce } from '../../utils.js';
import { debounce_timeout } from '../../constants.js';
import { POPUP_TYPE, callGenericPopup } from '../../popup.js';
function rgb2hex(rgb) {
rgb = rgb.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i);
@@ -63,8 +64,7 @@ async function doTokenCounter() {
}, debounce_timeout.relaxed);
dialog.find('#token_counter_textarea').on('input', () => countDebounced());
$('#dialogue_popup').addClass('wide_dialogue_popup');
callPopup(dialog, 'text', '', { wide: true, large: true });
callGenericPopup(dialog, POPUP_TYPE.TEXT, '', { wide: true, large: true, allowVerticalScrolling: true });
}
/**
@@ -123,6 +123,7 @@ async function doCount() {
//toastr success with the token count of the chat
const count = await getTokenCountAsync(allMessages);
toastr.success(`Token count: ${count}`);
return count;
}
jQuery(() => {
@@ -131,10 +132,10 @@ jQuery(() => {
<div class="fa-solid fa-1 extensionsMenuExtensionButton" /></div>
Token Counter
</div>`;
$('#extensionsMenu').prepend(buttonHtml);
$('#token_counter_wand_container').append(buttonHtml);
$('#token_counter').on('click', doTokenCounter);
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'count',
callback: doCount,
callback: async () => String(await doCount()),
returns: 'number of tokens',
helpString: 'Counts the number of tokens in the current chat.',
}));

View File

@@ -0,0 +1,8 @@
<div id="translate_chat" class="list-group-item flex-container flexGap5">
<div class="fa-solid fa-language extensionsMenuExtensionButton" /></div>
<span data-i18n="ext_translate_btn_chat">Translate Chat</span>
</div>
<div id="translate_input_message" class="list-group-item flex-container flexGap5">
<div class="fa-solid fa-keyboard extensionsMenuExtensionButton" /></div>
<span data-i18n="ext_translate_btn_input">Translate Input</span>
</div>

View File

@@ -0,0 +1 @@
<h3 data-i18n="ext_translate_delete_confirm_1">Are you sure?</h3><span data-i18n="ext_translate_delete_confirm_2">This will remove translated text from all messages in the current chat. This action cannot be undone.</span>

View File

@@ -0,0 +1,38 @@
<div class="translation_settings">
<div class="inline-drawer">
<div class="inline-drawer-toggle inline-drawer-header">
<b data-i18n="ext_translate_title">Chat Translation</b>
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
</div>
<div class="inline-drawer-content">
<label for="translation_auto_mode" class="checkbox_label" data-i18n="ext_translate_auto_mode">Auto-mode</label>
<select id="translation_auto_mode">
<option data-i18n="ext_translate_mode_none" value="none">None</option>
<option data-i18n="ext_translate_mode_responses" value="responses">Translate responses</option>
<option data-i18n="ext_translate_mode_inputs" value="inputs">Translate inputs</option>
<option data-i18n="ext_translate_mode_both" value="both">Translate both</option>
</select>
<label data-i18n="ext_translate_mode_provider" for="translation_provider">Provider</label>
<div class="flex-container gap5px flexnowrap marginBot5">
<select id="translation_provider" name="provider" class="margin0">
<option value="libre">Libre</option>
<option value="google">Google</option>
<option value="lingva">Lingva</option>
<option value="deepl">DeepL</option>
<option value="deeplx">DeepLX</option>
<option value="bing">Bing</option>
<option value="oneringtranslator">OneRingTranslator</option>
<option value="yandex">Yandex</option>
<select>
<div id="translate_key_button" class="menu_button fa-solid fa-key margin0"></div>
<div id="translate_url_button" class="menu_button fa-solid fa-link margin0"></div>
</div>
<label data-i18n="ext_translate_target_lang" for="translation_target_language">Target Language</label>
<select id="translation_target_language" name="target_language"></select>
<div id="translation_clear" class="menu_button">
<i class="fa-solid fa-trash-can"></i>
<span data-i18n="ext_translate_clear">Clear Translations</span>
</div>
</div>
</div>
</div>

View File

@@ -1,7 +1,6 @@
export { translate };
import {
callPopup,
eventSource,
event_types,
getRequestHeaders,
@@ -10,7 +9,8 @@ import {
substituteParams,
updateMessageBlock,
} from '../../../script.js';
import { extension_settings, getContext } from '../../extensions.js';
import { extension_settings, getContext, renderExtensionTemplateAsync } from '../../extensions.js';
import { POPUP_TYPE, callGenericPopup } from '../../popup.js';
import { findSecret, secret_state, writeSecret } from '../../secrets.js';
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
@@ -340,6 +340,34 @@ async function translateProviderBing(text, lang) {
throw new Error(response.statusText);
}
/**
* Translates text using the Yandex Translate API
* @param {string} text Text to translate
* @param {string} lang Target language code
* @returns {Promise<string>} Translated text
*/
async function translateProviderYandex(text, lang) {
let chunks = [];
const chunkSize = 5000;
if (text.length <= chunkSize) {
chunks.push(text);
} else {
chunks = splitRecursive(text, chunkSize);
}
const response = await fetch('/api/translate/yandex', {
method: 'POST',
headers: getRequestHeaders(),
body: JSON.stringify({ chunks: chunks, lang: lang }),
});
if (response.ok) {
const result = await response.text();
return result;
}
throw new Error(response.statusText);
}
/**
* Splits text into chunks and translates each chunk separately
* @param {string} text Text to translate
@@ -374,6 +402,10 @@ async function translate(text, lang) {
return '';
}
if (!lang) {
lang = extension_settings.translate.target_language;
}
switch (extension_settings.translate.provider) {
case 'libre':
return await translateProviderLibre(text, lang);
@@ -389,6 +421,8 @@ async function translate(text, lang) {
return await translateProviderOneRing(text, lang);
case 'bing':
return await chunkedTranslate(text, lang, translateProviderBing, 1000);
case 'yandex':
return await translateProviderYandex(text, lang);
default:
console.error('Unknown translation provider', extension_settings.translate.provider);
return text;
@@ -442,6 +476,7 @@ async function onTranslateInputMessageClick() {
const toast = toastr.info('Input Message is translating', 'Please wait...');
const translatedText = await translate(textarea.value, extension_settings.translate.internal_language);
textarea.value = translatedText;
textarea.dispatchEvent(new Event('input', { bubbles: true }));
toastr.clear(toast);
}
@@ -474,7 +509,8 @@ async function onTranslateChatClick() {
}
async function onTranslationsClearClick() {
const confirm = await callPopup('<h3>Are you sure?</h3>This will remove translated text from all messages in the current chat. This action cannot be undone.', 'confirm');
const popupHtml = await renderExtensionTemplateAsync('translate', 'deleteConfirmation');
const confirm = await callGenericPopup(popupHtml, POPUP_TYPE.CONFIRM);
if (!confirm) {
return;
@@ -532,58 +568,12 @@ const handleMessageEdit = createEventHandler(translateMessageEdit, () => true);
window['translate'] = translate;
jQuery(() => {
const html = `
<div class="translation_settings">
<div class="inline-drawer">
<div class="inline-drawer-toggle inline-drawer-header">
<b>Chat Translation</b>
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
</div>
<div class="inline-drawer-content">
<label for="translation_auto_mode" class="checkbox_label">Auto-mode</label>
<select id="translation_auto_mode">
<option value="none">None</option>
<option value="responses">Translate responses</option>
<option value="inputs">Translate inputs</option>
<option value="both">Translate both</option>
</select>
<label for="translation_provider">Provider</label>
<div class="flex-container gap5px flexnowrap marginBot5">
<select id="translation_provider" name="provider" class="margin0">
<option value="libre">Libre</option>
<option value="google">Google</option>
<option value="lingva">Lingva</option>
<option value="deepl">DeepL</option>
<option value="deeplx">DeepLX</option>
<option value="bing">Bing</option>
<option value="oneringtranslator">OneRingTranslator</option>
<select>
<div id="translate_key_button" class="menu_button fa-solid fa-key margin0"></div>
<div id="translate_url_button" class="menu_button fa-solid fa-link margin0"></div>
</div>
<label for="translation_target_language">Target Language</label>
<select id="translation_target_language" name="target_language"></select>
<div id="translation_clear" class="menu_button">
<i class="fa-solid fa-trash-can"></i>
<span>Clear Translations</span>
</div>
</div>
</div>
</div>`;
jQuery(async () => {
const html = await renderExtensionTemplateAsync('translate', 'index');
const buttonHtml = await renderExtensionTemplateAsync('translate', 'buttons');
const buttonHtml = `
<div id="translate_chat" class="list-group-item flex-container flexGap5">
<div class="fa-solid fa-language extensionsMenuExtensionButton" /></div>
Translate Chat
</div>
<div id="translate_input_message" class="list-group-item flex-container flexGap5">
<div class="fa-solid fa-keyboard extensionsMenuExtensionButton" /></div>
Translate Input
</div>
`;
$('#extensionsMenu').append(buttonHtml);
$('#extensions_settings2').append(html);
$('#translate_wand_container').append(buttonHtml);
$('#translation_container').append(html);
$('#translate_chat').on('click', onTranslateChatClick);
$('#translate_input_message').on('click', onTranslateInputMessageClick);
$('#translation_clear').on('click', onTranslationsClearClick);
@@ -608,7 +598,7 @@ jQuery(() => {
$(document).on('click', '.mes_translate', onMessageTranslateClick);
$('#translate_key_button').on('click', async () => {
const optionText = $('#translation_provider option:selected').text();
const key = await callPopup(`<h3>${optionText} API Key</h3>`, 'input');
const key = await callGenericPopup(`<h3>${optionText} API Key</h3>`, POPUP_TYPE.INPUT);
if (key == false) {
return;
@@ -631,7 +621,7 @@ jQuery(() => {
const secretKey = extension_settings.translate.provider + '_url';
const savedUrl = secret_state[secretKey] ? await findSecret(secretKey) : '';
const url = await callPopup(popupText, 'input', savedUrl);
const url = await callGenericPopup(popupText, POPUP_TYPE.INPUT, savedUrl);
if (url == false || url == '') {
return;
@@ -668,5 +658,6 @@ jQuery(() => {
: extension_settings.translate.target_language;
return await translate(String(value), target);
},
returns: ARGUMENT_TYPE.STRING,
}));
});

View File

@@ -175,7 +175,7 @@ class AzureTtsProvider {
const url = URL.createObjectURL(audio);
this.audioElement.src = url;
this.audioElement.play();
URL.revokeObjectURL(url);
this.audioElement.onended = () => URL.revokeObjectURL(url);
}
async fetchTtsGeneration(text, voiceId) {

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