Compare commits

...

465 Commits

Author SHA1 Message Date
Cohee
ba1c69d7a7 [wip] Update transformers.js 2025-04-15 21:14:09 +03:00
Cohee
5510e6da31 Enable multi-swipe for xAI 2025-04-14 22:36:56 +03:00
Cohee
36e3627705 gpt-4.1 2025-04-14 20:54:18 +03:00
Cohee
0895bc6c1d Replace getExtensionPromptMaxDepth with a constant 2025-04-14 09:57:10 +03:00
Cohee
78bda9954d Increase maximum injection depth and WI order (#3800) 2025-04-13 21:31:57 +03:00
Cohee
57d30dae59 Merge pull request #3846 from invisietch/staging
chore: add llama 4 chat templates & separate system from user role in chat template for llama 3
2025-04-13 19:22:25 +03:00
invisietch
897632b583 fix: system prompt not the same as user prompt for L3/4 2025-04-13 15:12:09 +01:00
invisietch
6f05c087b9 chore: update index 2025-04-13 14:26:20 +01:00
invisietch
fc9b2173c0 fix: add <|begin_of_text|> to story string 2025-04-13 14:14:43 +01:00
invisietch
61ca7775d2 fix: start/end header tags 2025-04-13 14:09:54 +01:00
invisietch
f95077ac9f chore: add llama 4 chat templates 2025-04-13 14:04:52 +01:00
Cohee
22f1aee70b Add web search fee notice for OpenRouter
Closes #3833
2025-04-13 14:15:49 +03:00
Cohee
35395fcb39 Increase unlocked slider limits 2025-04-13 13:35:50 +03:00
Cohee
4dfc33b2a0 Merge pull request #3839 from SillyTavern/gork-ai
xAI grok
2025-04-13 13:28:35 +03:00
Cohee
5eeba8894e Fix xAI example messages conversion 2025-04-12 14:14:57 +03:00
Cohee
91fc50b82d Merge branch 'staging' into gork-ai 2025-04-11 21:15:54 +03:00
Cohee
2982d7af52 Merge pull request #3838 from bmen25124/custom_req_proxy
Added proxy support to ChatCompletionService
2025-04-11 21:14:26 +03:00
Cohee
93acbcaa76 Merge pull request #3836 from Erquint/staging
Enable image inlining for visual models when connected to Mistral AI Le Platforme.
2025-04-11 21:10:27 +03:00
Cohee
1f27a39f29 Refactor mistral max context 2025-04-11 21:09:06 +03:00
Cohee
70d65f2d05 Remove tools from grok-vision requests 2025-04-11 20:41:20 +03:00
Cohee
173207ec54 Fix Together image models listing 2025-04-11 20:36:28 +03:00
Cohee
0c4c86ef06 Add xAI for image generation extension 2025-04-11 20:32:06 +03:00
Cohee
c1544fb60c Add logo 2025-04-11 20:06:04 +03:00
Cohee
6adce75933 Remove penalties from 3-mini requests 2025-04-11 20:02:42 +03:00
Cohee
30a9fa2b9d Fix bad copy paste 2025-04-11 19:53:34 +03:00
bmen25124
fc5e0563ba Added ability to override request payload 2025-04-11 19:07:00 +03:00
Cohee
17cdc78a91 Add xAI for image captioning 2025-04-11 19:05:03 +03:00
bmen25124
4736f533a5 Added proxy support to ChatCompletionService 2025-04-11 19:04:32 +03:00
Gness Erquint
1d2122b867 Correct editing mistake in "Set correct Mistral AI token context limits." 2025-04-11 18:01:42 +03:00
Gness Erquint
2040c43371 Revert "Powers of 2 for token context limits. No -1 offset."
This reverts commit 2d77fb3e30.
2025-04-11 17:58:39 +03:00
Gness Erquint
2d77fb3e30 Powers of 2 for token context limits. No -1 offset. 2025-04-11 17:40:53 +03:00
Gness Erquint
0c4b0cfb03 Set correct Mistral AI token context limits. 2025-04-11 17:20:39 +03:00
Cohee
c0228861e5 Merge pull request #3837 from Dakraid/chore/update-fal-filter-openrouter-providers
Update FAL.AI model filter and OpenRouter Providers
2025-04-11 15:15:59 +03:00
Cohee
797de862cf Fix linter 2025-04-11 15:15:29 +03:00
Kristian Schlikow
e062a33f56 Use the proper quotes 2025-04-11 12:14:15 +00:00
Kristian Schlikow
f238e58731 Remove unnecessary new line 2025-04-11 12:12:14 +00:00
Kristian Schlikow
c5ebe4b4b1 Update the model filter for FAL.AI and update the list of providers for OpenRouter 2025-04-11 12:09:13 +00:00
Gness Erquint
43caa0c6d4 Enable image inlining for visual models when connected to Mistral AI Le Platforme. 2025-04-11 11:09:10 +03:00
Cohee
1c52099ed6 Add xAI as chat completion source 2025-04-10 22:59:10 +03:00
Cohee
c3b1573c91 Force resave CC preset after renaming
Fixes #3828
2025-04-09 22:09:10 +03:00
Cohee
820f4439ad Exclude .ts files from PR lint check 2025-04-09 21:58:32 +03:00
Cohee
f302c67b95 Merge pull request #3827 from SillyTavern/fix/wi-rename-same-name
Prevent similarily-ish world info, preset and chat file renames (preventing data loss on on case-insensitive systems)
2025-04-09 19:35:16 +03:00
Cohee
c522baf4f7 Make texts translatable 2025-04-09 19:18:05 +03:00
Wolfsblvt
ceceb8f3f0 Change same name logs to toast 2025-04-09 01:21:20 +02:00
Cohee
b5f430de6f Merge pull request #3826 from SillyTavern/custom-png-encode
Replace png encode dependency with optimized CRC32 calculation
2025-04-08 22:49:35 +03:00
Wolfsblvt
47652d7fe9 Prevent similarily-ish chat name renames 2025-04-08 21:16:11 +02:00
Wolfsblvt
55ed5b325c Prevent similarily-ish preset renames
Adds validation to prevent renaming presets to names that are identical when ignoring case and accents
Avoids accidental duplicates by ensuring meaningful name changes during preset renames
2025-04-08 21:14:09 +02:00
Wolfsblvt
6895892d5a Prevent similarily-ish world info renames
Adds validation to block renaming world info entries when new name matches existing name after ignoring case and accents

This avoids unnecessary operations and potential data duplication issues caused by superficial name changes that don't meaningfully differ in normalized form
2025-04-08 20:58:22 +02:00
Murad "Gness Erquint" Beybalaev
d3ab02df5a "Zoom right in" tooltip for media embeds. (#3804)
* "Zoom right in" tooltip for media embeds.

* Let's not say "right in" in the expand&zoom tooltip then.

Maintainer request in PR: https://github.com/SillyTavern/SillyTavern/pull/3804#discussion_r2032999775

* Update public/index.html

Yeah

Co-authored-by: Murad "Gness Erquint" Beybalaev <gness.na@gmail.com>

---------

Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
2025-04-08 21:51:47 +03:00
Cohee
ba397fa70a Replace png encode dependency with optimized CRC32 calculation 2025-04-08 21:50:33 +03:00
Cohee
5929d5c0e4 Groq: sync supported models 2025-04-06 23:08:31 +03:00
Cohee
965c2ee29a Merge pull request #3819 from SillyTavern/feat/reasoning-set-collapse-state
Add collapse argument to `/reasoning-set` slash command
2025-04-06 23:01:23 +03:00
Wolfsblvt
6c8714c016 Add collapse argument to /reasoning-set command
Allows controlling visibility of reasoning blocks through slash commands by adding a boolean 'collapse' parameter
Respects default expansion settings when not explicitly provided

This gives users direct control over block visibility during command execution rather than relying solely on global preferences
2025-04-06 21:21:37 +02:00
Cohee
3d79885ddd Merge pull request #3816 from Ecalose/gemini_sys_prompt
fix gemini system prompt
2025-04-06 13:53:36 +03:00
Cohee
b8057b3c4f Decrease session extension interval to 10 minutes 2025-04-06 13:52:57 +03:00
Cohee
44f27a1cb7 Merge branch 'staging' into gemini_sys_prompt 2025-04-06 13:33:30 +03:00
Cohee
d2f2fee989 Merge pull request #3815 from gwentman/gemini2.5propreview
Add support of  gemini-2.5-pro-preview-03-25 API
2025-04-06 13:32:32 +03:00
Cohee
df7c2226f6 Fix Gemini captioning 2025-04-06 13:31:16 +03:00
Ecalose
ec02547b0e fix gemini system prompt 2025-04-06 16:10:49 +08:00
gwentman
18cc17142f add support to the gemini 2.5 pro preview api 2025-04-06 17:10:24 +10:00
Cohee
e753e432be Refactor redirectToHome function to use URL object 2025-04-05 21:54:01 +03:00
Cohee
571356ffd9 Merge pull request #3810 from SillyTavern/onbeforeunload
Add beforeunload event listener to prompt user when chat is saving
2025-04-04 21:56:57 +03:00
Cohee
6f543e860f #3809 Add beforeunload event listener to prompt user when chat is saving 2025-04-04 15:58:06 +00:00
Cohee
7987f02dee Exclude <style> tags from quote and underscore italics processing
Fixes #3808
2025-04-04 13:53:56 +00:00
Cohee
c7ce0fe66a Fix localized API URL examples
Fixes #3807
2025-04-04 10:57:22 +00:00
Cohee
d50698861a Merge pull request #3799 from SillyTavern/export-unset-chat
Unset chat field on import/export
2025-04-03 09:00:26 +03:00
Cohee
b26369ca86 Unset private fields from JSON exports 2025-04-02 22:09:48 +03:00
Cohee
76c59cbc52 Unset chat field on import/export
Closes #3781
2025-04-02 22:08:20 +03:00
Cohee
ee26581df1 Fix WASM load hanging on some Node versions <20.6.0 2025-04-02 17:48:13 +00:00
Cohee
0a521d7a35 Merge pull request #3793 from Erquint/staging
Clickable image attachments — no more fiddling with the spyglass button.
2025-04-01 22:26:52 +03:00
Cohee
943e14f797 Fix style 2025-04-01 22:03:14 +03:00
Cohee
b31a53ab99 Fix selector 2025-04-01 22:01:12 +03:00
Cohee
058fef1146 Update Jimp and add WASM plugins (#3784)
* Update jimp, use WASM format plugins

* Fix Jimp import path in thumbnails endpoint

* Fix size variable

* Add fetch patch to handle file URLs

* Fix JPEG thumbnailing

* Enhance fetch patch to validate file paths and support specific extensions

* Add default msBmp format

* Update jsconfig

* Update JPEG color space in thumbnail generation to YCbCr

* Install jimp plugins explicitly

* Refactor fetch patch utility functions
2025-04-01 21:55:21 +03:00
Cohee
70fe5b6e01 Add Gemini embedding model for vector storage 2025-04-01 21:42:26 +03:00
Cohee
80e821d12d Add support for KoboldCpp embeddings in Vector Storage (#3795)
* Add support for KoboldCpp embeddings in vector processing

* Add validation for KoboldCpp embeddings to handle empty data

* Improve toast handling
2025-04-01 21:21:29 +03:00
Cohee
9c4404cae9 Remove unused imports 2025-04-01 20:24:59 +03:00
Cohee
3458f58c63 Refactor ensureThumbnailCache to accept directoriesList parameter 2025-04-01 11:16:00 +03:00
Cohee
f0338cc325 Merge pull request #3792 from bmen25124/con_prof_api_err
Added api check for ConnectionManagerRequestService.handleDropdown
2025-04-01 10:24:35 +03:00
Gness Erquint
864859dd6b Made attached images clickable — no more fiddling with the spyglass pictogram. Augmented the "Enlarge" button's function to retain it. 2025-04-01 05:35:24 +03:00
bmen25124
50b2eeb61f Added api check for ConnectionManagerRequestService.handleDropdown 2025-04-01 04:39:41 +03:00
Cohee
26d0f01d69 Merge pull request #3775 from SillyTavern/feat/ext-manager-toolbar
Move extension buttons to a separate toolbar
2025-03-30 19:22:19 +03:00
Cohee
469c731ff4 Merge pull request #3786 from SillyTavern/tilde-codeblock
Improve tilde code blocks in message formatting and TTS processing
2025-03-30 14:03:12 +03:00
Cohee
65a6e428d1 Improve tilde code blocks in message formatting and TTS processing 2025-03-30 13:25:15 +03:00
Cohee
60603a008c Update GitHub Actions workflow to use the current branch reference for checkout 2025-03-29 19:27:47 +02:00
Cohee
2271d7a220 Merge pull request #3776 from SillyTavern/fix/raw-quotes
Add opt-in for rawQuotes in SlashCommand registration
2025-03-29 19:07:09 +02:00
Cohee
819ce198a8 Add raw quotes indicator in slash command autocomplete 2025-03-29 18:04:11 +02:00
Cohee
c673ebcc22 Merge branch 'staging' into fix/raw-quotes 2025-03-29 17:51:34 +02:00
Cohee
572b60d0c1 Merge branch 'staging' into feat/ext-manager-toolbar 2025-03-29 17:43:18 +02:00
Cohee
c85e55bcca Merge pull request #3783 from SillyTavern/fix/remove-pm-export-popup
Remove prompt manager export popup
2025-03-29 17:41:26 +02:00
Cohee
1323ac1d13 Remove prompt manager export popup
Fixes #3782
2025-03-29 16:05:31 +02:00
Cohee
157046ff46 Merge pull request #3778 from SillyTavern/fix/persona-select-rerender-first-message
Fix persona select on new chat not rerendering first message (and not replacing `{{user}}` macro)
2025-03-28 23:42:58 +02:00
Cohee
bdcf9b088e Revert "Fix backward compatibility with escaped leading quote"
This reverts commit deb13e9c97.
2025-03-28 21:00:02 +02:00
Cohee
deb13e9c97 Fix backward compatibility with escaped leading quote 2025-03-28 10:29:32 +02:00
Cohee
c05b6b0ae8 Add JSDoc to rawQuotes 2025-03-28 10:28:02 +02:00
Wolfsblvt
97040a98a0 Remove 'In Progress' label when marking issues as done
Automatically removes the '🧑‍💻 In Progress' label while adding completion labels
to keep issue tracking clean and accurate after merging. Updates log messages
to reflect both label additions and removals consistently across workflows
2025-03-28 01:58:22 +01:00
Wolfsblvt
d95524032e Centralize first message retrigger on persona changes
Moves retrigger logic to setUserAvatar entry point
to avoid duplicate calls when updating personas
Removes redundant triggers from name/avatar handlers

Fixes #3774
2025-03-28 01:28:36 +01:00
Wolfsblvt
6dc33e9637 Cast this_chid to number in personas.js
Ensure numeric type for character ID comparisons
Explicitly converts this_chid to Number in multiple functions
to prevent type mismatch issues when checking character connections
and persona states
2025-03-28 01:25:13 +01:00
Cohee
533aeffa36 Add opt-in for rawQuotes in SlashCommand registration
Closes #2739
Supersedes #2921
2025-03-27 23:22:38 +02:00
Cohee
0a85178846 Merge pull request #3766 from bmen25124/custom_request_stop_string_cleanup
Added stop string cleanup, better stopping string param
2025-03-27 23:06:43 +02:00
Cohee
d93aba5706 Fix useStopStrings defaulting 2025-03-27 22:52:01 +02:00
Cohee
68c572f2eb Merge branch 'staging' into custom_request_stop_string_cleanup 2025-03-27 22:41:11 +02:00
Cohee
74efb598f1 Merge pull request #3768 from bmen25124/move_lorebook_entry
Added move button for lorebook entries
2025-03-27 22:36:28 +02:00
Cohee
62395f409f There's no ==== operator (yet) 2025-03-27 22:34:30 +02:00
Cohee
561bed9cc2 Replace goofy comment 2025-03-27 22:30:06 +02:00
Cohee
5f00f2beb0 Add quotes to item title being moved 2025-03-27 22:28:14 +02:00
Cohee
496f86e16e Use non-strict equality instead to avoid explicit typecast 2025-03-27 22:27:23 +02:00
Cohee
3ce715c52f Fix deletion from originalData 2025-03-27 22:24:39 +02:00
Cohee
f4d467048b Fix type error 2025-03-27 22:20:28 +02:00
Cohee
280608f061 Use real class name 2025-03-27 22:19:49 +02:00
Cohee
e9c8b8c24e Save both files immediately 2025-03-27 22:17:53 +02:00
Cohee
ae050be2d1 Lint fix 2025-03-27 22:02:24 +02:00
Cohee
02df7d78e2 Move extension buttons to a separate toolbar 2025-03-27 21:51:35 +02:00
Cohee
216c698610 Merge pull request #3771 from Erquint/no_updates_for_disabled_ext
No updates for disabled extensions, unless you insist.
2025-03-27 21:49:45 +02:00
Cohee
01c793eae2 Merge branch 'staging' into no_updates_for_disabled_ext 2025-03-27 20:59:49 +02:00
Cohee
a8558ba6fb Merge pull request #3765 from SillyTavern/feat/wi-content-expando
Add expand editor for WI content
2025-03-27 20:57:57 +02:00
Cohee
a2e3519218 Fix eslint 2025-03-27 20:54:06 +02:00
Cohee
1639289b18 Merge pull request #3763 from qvink/empty_message_injection
Fix for generation interceptors messing with WI timed effects
2025-03-27 20:53:39 +02:00
Cohee
e5d5f953ec Add symbols to getContext 2025-03-27 20:48:13 +02:00
Cohee
de091daa40 Clean-up comments 2025-03-27 20:46:27 +02:00
Cohee
f1479de5bd Merge pull request #3773 from equal-l2/remove-block-entropy
Remove Block Entropy references
2025-03-27 20:44:31 +02:00
Cohee
0d111652b5 Remove from auto-connect logic 2025-03-27 20:38:34 +02:00
equal-l2
2a31f6af2d Remove Block Entropy references
Block Entropy shut down their service at the end of 2024.
2025-03-28 00:47:30 +09:00
qvink
dac5f6910c adding comments 2025-03-27 09:40:42 -06:00
qvink
1dcd837eb1 lint 2025-03-27 09:38:14 -06:00
qvink
f1a053c3b8 using symbols instead 2025-03-27 09:31:52 -06:00
Gness Erquint
976d4f39e6 No updates for disabled extensions, unless you insist. 2025-03-27 17:27:17 +03:00
bmen25124
f33b31dc19 Removed type from icon element 2025-03-27 14:42:50 +03:00
bmen25124
8970c8274c Removed jqueryElement.data usages 2025-03-27 14:38:54 +03:00
bmen25124
bc08d42d0e Progress on move entry
* Fixed header alignment
* Building HTML with browser api instead of string.
* jqueryElement.data(key, value) usages converted to jqueryElement.attr(data-key, value)
* Logs simplified
* Removed success toastry message
2025-03-27 06:21:52 +03:00
bmen25124
f14c73bfcc Added move button for lorebook entries 2025-03-27 04:53:16 +03:00
bmen25124
4e207c2cf0 Removed duplicate codes 2025-03-26 23:38:02 +03:00
bmen25124
972b1e5fa7 Fixed variable naming, better jsdoc 2025-03-26 23:30:09 +03:00
bmen25124
a7d48b1aed Added overridable instruct settings, removed macro override 2025-03-26 23:21:48 +03:00
bmen25124
c5f251c6e3 Added stop string cleanup, better stopping string param 2025-03-26 22:35:10 +03:00
Cohee
8d06582b58 Add expand editor for WI content
Closes #3764
2025-03-26 21:15:41 +02:00
qvink
251d242a0d custom flag in message.extra, also apply to chat completion 2025-03-26 13:10:50 -06:00
qvink
72f91a4994 use custom flag instead 2025-03-26 09:28:42 -06:00
Cohee
f4eb32c71c Merge pull request #3760 from SillyTavern/feat/expressions-none-default-option
Add 'none' expression classifier API option and set as default
2025-03-26 11:10:13 +02:00
qvink
abb908e62c semi 2025-03-25 22:36:05 -06:00
qvink
9d88c1578b don't format messages with undefined content 2025-03-25 22:31:44 -06:00
Wolfsblvt
ebf3920f9f Merge pull request #3761 from Bronya-Rand/staging
chore: make layer updates exportable
2025-03-26 03:14:07 +01:00
Azariel Del Carmen
b96bed7240 chore: make layer updates exportable 2025-03-25 20:09:48 -05:00
Wolfsblvt
cbfc1f7a0e Add 'none' classifier API option and set as default
Introduces a no-op API selection to disable expression classification
Shows warnings when no valid API is selected to prevent silent failures
Updates migration logic and settings UI to use new default value

This allows users to explicitly opt-out of automatic expression detection
while maintaining backwards compatibility with existing configurations
2025-03-26 01:50:26 +01:00
Cohee
2588646b0f Merge pull request #3759 from cloak1505/staging
Fix typo for 2.5 Pro Exp vision support
2025-03-25 23:40:56 +02:00
Cohee
75be96e1f7 Merge pull request #3754 from SillyTavern/fix-openrouter-oauth
OpenRouter: Fix OAuth flow with enabled accounts
2025-03-25 23:20:42 +02:00
cloak1505
b6e5df1983 Fix typo for 2.5 Pro Exp vision support 2025-03-25 15:07:59 -05:00
Cohee
264d77414a Gemini 2.5 Pro 2025-03-25 21:21:23 +02:00
Wolfsblvt
d70c346b12 Expression API on new install defaults to 'local' 2025-03-25 05:17:06 +01:00
Cohee
4ced7abaa3 OpenRouter: Fix OAuth flow with enabled accounts 2025-03-24 20:22:59 +02:00
Cohee
c2e6593343 Add error handling to autorun 2025-03-24 10:03:23 +00:00
Cohee
be7750d6fd Limit cache read parallelism
#3747
2025-03-24 02:21:48 +02:00
Cohee
c69623278f Revert to sync file read in card parser
#3747
2025-03-24 02:03:05 +02:00
Cohee
d4b8983d47 Add forgiveParseErrors option to DiskCache instance creation
#3747
2025-03-23 23:20:38 +02:00
Cohee
921850a62b Split accessing cache instance and processing data
#3747
2025-03-23 23:09:21 +02:00
Cohee
4048a0a09a Merge pull request #3717 from Scarlet-t/staging
added button to rename chat completion preset
2025-03-23 21:29:24 +02:00
Cohee
b9c5703568 Return to old id for delete 2025-03-23 21:26:27 +02:00
Cohee
a070d13723 Revert non-rename changes 2025-03-23 21:25:50 +02:00
Cohee
6d7943d2a0 Clean-up changes diff 2025-03-23 21:24:14 +02:00
Cohee
2af33a9e18 Add error handling to accessing disk cache
Fixes #3747
2025-03-23 21:05:36 +02:00
Cohee
9717259776 Merge pull request #3737 from qvink/staging
Adding option to disable removal of "{{user}}:" and "{{char}}:" in generateRaw()
2025-03-23 18:05:12 +02:00
qvink
66832fc98a removing whitespace trimming in trimWrongNames block 2025-03-23 09:41:03 -06:00
Cohee
e44b123330 Add no select style to select2-dropdown
Fixes #3745
2025-03-23 14:49:20 +02:00
Cohee
0e76530f43 Merge pull request #3746 from InspectorCaracal/patch-1
Fix group chid data attr in `performGroupMemberAction`
2025-03-23 14:43:14 +02:00
Cohee
a8f1bf7a17 Merge pull request #3744 from bmen25124/updateEditor_export_new_parm
Exported reloadEditor
2025-03-23 14:42:07 +02:00
InspectorCaracal
6bb91dd0df fix group chid data attr 2025-03-22 23:41:29 -06:00
bmen25124
80e1226d28 Exported reloadEditor 2025-03-23 03:40:04 +03:00
qvink
36adb6992e the cooler ternary 2025-03-22 16:43:08 -06:00
qvink
900575ee1a adding missing colon 2025-03-22 16:30:54 -06:00
qvink
9a3e3433c7 Adding missing doc string 2025-03-22 15:54:57 -06:00
qvink
841f814137 Reverting cleanUpMessage() functionality changes. 2025-03-22 15:52:37 -06:00
Cohee
ce2fd8800d Merge branch 'release' into staging 2025-03-22 19:39:34 +02:00
Cohee
74addf1241 Fix TogetherAI models list 2025-03-22 19:39:23 +02:00
Cohee
5969bf3992 Merge pull request #3732 from SillyTavern/disk-cache
Add disk cache for parsed character JSONs
2025-03-22 19:11:04 +02:00
Cohee
834c9751f8 Merge branch 'staging' into disk-cache 2025-03-22 18:52:01 +02:00
Cohee
50334890a6 Merge pull request #3742 from bmen25124/custom_request_stream
Added stream support to "custom-request"
2025-03-22 18:44:02 +02:00
Cohee
0b937237c3 Refactor getStreamingReply to use nullish coalescing for show_thoughts 2025-03-22 18:27:49 +02:00
Cohee
a20c8978f9 Refactor DiskCache to use a synchronization queue and update cache key generation 2025-03-22 17:03:41 +02:00
Cohee
4f52c369fa Merge pull request #3743 from BrendanMcCauley/3710
Fix activatePooledorder()
2025-03-22 11:14:08 +02:00
Brendan McCauley
5472ddc8e6 Fix activatePooledorder() 2025-03-21 19:58:26 -04:00
Cohee
50340103de Increase cache clean-up interval 2025-03-22 01:18:23 +02:00
Cohee
b625319f0c Optimize cache verification 2025-03-22 01:10:22 +02:00
Cohee
8e66ab4d51 Fix cache removal queue processing 2025-03-21 22:47:45 +02:00
Wolfsblvt
1f9472e7b2 Try fix linter with explicit permissions 2025-03-21 21:00:53 +01:00
Cohee
82b885e5ef Sorry we don't know why lint didn't work 2025-03-21 21:49:26 +02:00
bmen25124
7df6a78f33 Better overrideShowThoughts value 2025-03-21 22:19:13 +03:00
bmen25124
17e0058763 Changed options type 2025-03-21 22:16:57 +03:00
bmen25124
7619396053 Better naming 2025-03-21 22:09:41 +03:00
qvink
f713948a1b formatting 2025-03-21 12:32:06 -06:00
qvink
c4175697ef cleanUpMessages() now uses object args. Adding option to trim names. 2025-03-21 12:18:48 -06:00
bmen25124
ec474f5571 Added stream support to "custom-request" 2025-03-21 20:44:09 +03:00
Cohee
3ca6e0e8fc Update readme.md 2025-03-21 10:46:27 +02:00
qvink
ff21ee24d1 removing redundant .indexOf(). Adding extra length to account for the colon. 2025-03-20 21:46:16 -06:00
qvink
39ebffa282 Fix for removal of "{{user}}:" from generations 2025-03-20 20:00:26 -06:00
Cohee
ef56eda2c2 Merge pull request #3730 from SillyTavern/feat/command-buttons-multiple
Add support for toggleable buttons/multiselect in `/buttons` command
2025-03-20 10:42:57 +02:00
Cohee
e3c465258e Merge pull request #3731 from SillyTavern/fix-srw-trim
Fix replacing user_prompt_bias on display
2025-03-20 10:37:46 +02:00
Cohee
4490c3edd5 Dispose diskCache during preSetupTasks cleanup 2025-03-20 10:24:27 +02:00
Cohee
3355c682ca Convert to class 2025-03-20 10:21:41 +02:00
Wolfsblvt
40f2eae3f3 Make it return empty array on cancel 2025-03-19 22:46:11 +01:00
Cohee
df4700914c Remove debug log 2025-03-19 23:33:15 +02:00
Cohee
60e4993a89 Refactor cache entry removal to use Set for removalQueue 2025-03-19 23:32:37 +02:00
Cohee
587c528f6d Implement cache entry removal queue in diskCache 2025-03-19 23:31:30 +02:00
Cohee
9c7d3d7400 Add cache validation func to startup 2025-03-19 23:03:39 +02:00
Cohee
0e2290dacf Enable disk cache by default 2025-03-19 22:45:26 +02:00
Cohee
1a2c54ce39 Disable TTL on disk cache 2025-03-19 22:40:49 +02:00
Cohee
694df8ca55 Merge branch 'staging' into disk-cache 2025-03-19 22:40:11 +02:00
Cohee
c5d4bdcd0b Merge branch 'staging' into fix-srw-trim 2025-03-19 22:28:27 +02:00
Cohee
fb2f8dce10 Merge pull request #3721 from SillyTavern/or-prompt-post-processing
OpenRouter: Allow applying prompt post-processing
2025-03-19 22:16:09 +02:00
Wolfsblvt
8eee1d09a8 Add support for toggleable /buttons
Enhances the buttons slash command with toggleable multi-select capability
Introduces a 'multiple' parameter to enable selecting several options
Updates button styling with visual indicators for toggle states
Modifies return value to handle array of selections when multiple is enabled
2025-03-19 21:13:28 +01:00
Cohee
297a767746 Merge branch 'staging' into or-prompt-post-processing 2025-03-19 22:07:35 +02:00
Cohee
c047e1acd9 Merge pull request #3729 from Erquint/staging
Use Node.js path parser to extract the filename stem in the chats search.
2025-03-19 22:07:06 +02:00
Cohee
ec98746ac6 Merge pull request #3728 from SillyTavern/actions/eslint-checker
Add ESLint PR check action
2025-03-19 22:04:15 +02:00
Cohee
af0939038b Fix undefined type references 2025-03-19 21:26:38 +02:00
Wolfsblvt
df5b79a1a4 Move run-eslint to PR Manager workflow 2025-03-19 20:24:26 +01:00
Cohee
a183c8f69a Fix the rest of lints 2025-03-19 21:21:04 +02:00
Cohee
d7dbe736f8 Downgrade jsdoc plugin 2025-03-19 21:20:28 +02:00
Cohee
0af4a3ebd7 Fix unfixable lints 2025-03-19 21:10:42 +02:00
Wolfsblvt
73520b923f break some linting... 2025-03-19 20:01:31 +01:00
Wolfsblvt
b88f9fb1ff break some linting 2025-03-19 19:59:17 +01:00
Wolfsblvt
39a385ab04 Update eslint workflows version + formatting
- Add explicit version numbers (to prevent update conflicts)
- Separate the steps and give them names, for better readability on workflow log
- Only run on opened/synchronize
2025-03-19 19:58:00 +01:00
Murad "Gness Erquint" Beybalaev
6872ad9489 More pointless lint. 2025-03-19 21:26:43 +03:00
Murad "Gness Erquint" Beybalaev
5a2311b806 Linter's gonna lint… 2025-03-19 21:24:08 +03:00
Gness Erquint
cc54158f09 Use Node.js path parser to extract the stem in the chats search. 2025-03-19 21:17:42 +03:00
Cohee
20cdcc37fc Fix fixable lints 2025-03-19 20:00:33 +02:00
Cohee
8a4a338455 Add unlinted file 2025-03-19 19:51:29 +02:00
Cohee
a7a3badb8e Add ESLint PR check action 2025-03-19 19:46:32 +02:00
Cohee
9b76b6dd3c Merge branch 'staging' into or-prompt-post-processing 2025-03-19 19:32:55 +02:00
Cohee
190f2f9085 Merge pull request #3727 from Erquint/staging
Chat titles are now included in the search. Query terms don't have to occur in the same message anymore.
2025-03-19 19:32:21 +02:00
Cohee
d3e62fe56c Fix lint 2025-03-19 19:31:09 +02:00
Murad "Gness Erquint" Beybalaev
a3c57fb05f Correct the chat search comment. 2025-03-19 20:27:35 +03:00
Wolfsblvt
0e746f0368 Fix workflow permissions for done labeling 2025-03-19 18:26:42 +01:00
Cohee
6bfa54e9b4 Merge pull request #3725 from SillyTavern/feat/expressions-filter-available
Adds filtering to expressions to ignore labels that do not have sprites available
2025-03-19 19:18:02 +02:00
Cohee
f5ecd1fa5f Reduce warning level to debug 2025-03-19 19:15:07 +02:00
Gness Erquint
bbdd98a155 Prepend title to chat messages array before concatenating the string. 2025-03-19 19:40:28 +03:00
Cohee
0607ac98db Fix replacing user_prompt_bias on display 2025-03-19 15:52:51 +00:00
Murad "Gness Erquint" Beybalaev
d48ebdb0d4 Join searched messages by linebreak to avoid searching for implicit whitespace. 2025-03-19 18:38:05 +03:00
Wolfsblvt
2e936e804a Fix default value for filter argument 2025-03-19 10:17:08 +01:00
Wolfsblvt
42730bbe92 Fix sprite upload on empty expression
- When no sprite was defined before, it falsely tried to derive the filename from the existing filename when "allow multiple" was not enabled. It now correctly just utilizes the expression name, not relying on filename anymore.
2025-03-19 09:52:20 +02:00
Cohee
d5d3516e18 Merge pull request #3724 from SillyTavern/fix/expressions-upload-new
Fix not being able to upload sprite when no sprite existed for an expression
2025-03-19 09:51:45 +02:00
Cohee
7784b71c55 Merge pull request #3726 from SillyTavern/feat/expressions-remove-reasoning
Clean reasoning from LLM/webLLM classify response on expression classification
2025-03-19 09:44:05 +02:00
Gness Erquint
35fa634f1d Chat titles are now included in the search. Query terms don't have to occur in the same message anymore. 2025-03-19 06:13:54 +03:00
Wolfsblvt
a8899f7d81 Clean reasoning from classify response
- If a reasoning model is used (via LLM, or R1 distill via webLLM), it'll likely return reasoning. That should not be used for the search of classification inside the response
2025-03-19 03:30:53 +01:00
Wolfsblvt
5a6058d319 Adds sprite-based filtering for expressions
- Functionality only available for LLM/webLLM
- New toggle to filter expressions on availalbe sprites
- `getExpressionsList` filters cached expressions when checked (using sprite folder name/override)
- `/expression-list` slash command has "filter" arg to filter list
- `/expression-classify` slash command has "filter" arg now, to use filtered list for classification
- `getExpressionLabel` uses filtered expressions when LLM/webLLM
2025-03-19 03:06:50 +01:00
Wolfsblvt
8366b7de60 Fix workflow circular dependency 2025-03-19 02:38:09 +01:00
Wolfsblvt
0be952b9a0 Fix sprite upload on empty expression
- When no sprite was defined before, it falsely tried to derive the filename from the existing filename when "allow multiple" was not enabled. It now correctly just utilizes the expression name, not relying on filename anymore.
2025-03-19 02:17:17 +01:00
Wolfsblvt
33a72d10a0 Label PR by size last (and pray) 2025-03-18 23:03:02 +01:00
Jenny
ab2ff7ee34 added back import/export logic for cc presets 2025-03-18 21:17:44 +00:00
Cohee
fcaea46a54 Apply post-process before setting cache at depth 2025-03-18 23:17:26 +02:00
Cohee
7d034cba11 Remove forced newline separator from group join wrappers (#3722)
* Remove forced newline separator from group join wrappers

* Remove unnecessary ternary

* Do not trim field wrappers
2025-03-18 23:12:49 +02:00
Cohee
c1697dc3b3 Merge pull request #3720 from bmen25124/group_wrapper_events
Added group wrapper events
2025-03-18 21:45:34 +02:00
Cohee
46d5f79fd9 OpenRouter: Allow applying prompt post-processing
Fixes #3689
2025-03-18 21:33:11 +02:00
bmen25124
6a4b8c3870 Added group wrapper events 2025-03-18 22:21:25 +03:00
Cohee
283edd94b9 Merge pull request #3718 from SillyTavern/feat/an-macro
Add macros for Author's Notes
2025-03-18 21:08:18 +02:00
Wolfsblvt
909c7f1037 Merge pull request #3719 from SillyTavern/fix-workflows-v2-v1.1
Fixing GitHub Workflows - again (maybe this time it works)
2025-03-18 19:51:26 +01:00
Wolfsblvt
5bda6eac13 Switch pr workflow back to pull_request_target 2025-03-18 19:49:30 +01:00
Wolfsblvt
8d37f9f38c Replace workflow secrets with automatic secret 2025-03-18 19:48:04 +01:00
Wolfsblvt
68965d0791 Update auto labeling
- Fix labeling of issues not using word boundaries
- Add new PR by branch name labels
2025-03-18 19:45:02 +01:00
Cohee
8309c30a3e Merge branch 'staging' into feat/an-macro 2025-03-18 20:27:37 +02:00
Cohee
4df98b0341 Add macro for charDepthPrompt 2025-03-18 20:27:15 +02:00
Wolfsblvt
490aa991d2 Maybe it's a permissions issue 2025-03-18 19:24:07 +01:00
Wolfsblvt
819d698938 Try fix PR size labeling by conditional 2025-03-18 19:14:44 +01:00
Cohee
b6c1c9a40d MistralAI: Add new models 2025-03-18 19:53:02 +02:00
Cohee
c716494622 Merge pull request #3711 from qvink/fix_genraw_prompt_bias
Fixing user_prompt_bias being incorrectly added using generateRaw()
2025-03-18 19:11:50 +02:00
Cohee
6e4bd00ef8 Update parameter naming 2025-03-18 19:09:57 +02:00
Cohee
1122c675e5 Merge pull request #3715 from GhostXia/staging
change 01ai endpoint
2025-03-18 19:02:04 +02:00
Cohee
e2eec77a19 Update HTML link 2025-03-18 19:00:01 +02:00
Cohee
3c61074d78 Add macros for Author's Notes
Closes #3716
2025-03-18 18:15:59 +02:00
Jenny
1943325653 added button to rename chat completion preset 2025-03-18 15:15:51 +00:00
Cohee
8d279dd94d Fix saveChat positional argument migration 2025-03-18 10:33:52 +00:00
Cohee
e2f9489684 Merge pull request #3698 from SillyTavern/integrity
Add integrity check when saving solo chat files
2025-03-18 12:28:38 +02:00
GhostXia
eb52872b13 change 01ai endpoint 2025-03-18 17:19:22 +08:00
qvink
3412d1ce1f adding option to cleanUpMessage() to not include the user prompt bias, and removing the user prompt bias from generateRaw() results. 2025-03-17 20:44:32 -06:00
Cohee
c92ca8dbfb Merge branch 'staging' into integrity 2025-03-18 01:51:07 +02:00
Cohee
03d590415b Merge pull request #3700 from SillyTavern/reasoning-template
Reasoning templates
2025-03-18 01:49:06 +02:00
Cohee
4965ada785 Shorten the popup header text 2025-03-18 01:41:27 +02:00
Cohee
785cacbcb6 Add migration of positional arguments 2025-03-18 01:39:23 +02:00
Cohee
cbb69adf53 Merge pull request #3709 from SillyTavern/fix-expression-override-display
Fix Expression Override not resetting if empty
2025-03-18 01:05:58 +02:00
Cohee
b3406f8abf Merge branch 'release' into staging 2025-03-18 00:59:15 +02:00
Cohee
594a3720ad Fix TTS 2025-03-18 00:59:04 +02:00
Cohee
b1346910a4 Adjust reasoning template migration procedure 2025-03-18 00:47:20 +02:00
Cohee
271c93a504 Rename DeepSeek template, add Blank reasoning template 2025-03-18 00:32:31 +02:00
Cohee
6ff06ff04b Use performFuzzySearch 2025-03-18 00:29:03 +02:00
Wolfsblvt
5585220d0a Fix Expression Override not resetting if empty
- When switching chars, override field gets correctly loaded. The display value won't be reset when the override was empty. This was likely unintended.
2025-03-17 23:23:58 +01:00
Cohee
49949f2f8e Merge pull request #3705 from Yokayo/staging
Update ru-ru translation
2025-03-18 00:01:15 +02:00
Cohee
058bddfe81 Merge pull request #3708 from SillyTavern/fix-swipes-reasoning
Fix deleting swipes overwriting reasoning
2025-03-17 23:53:36 +02:00
Wolfsblvt
ca53323a08 Fix deleting swipes overwriting reasoning
- Well, someone forgot about syncing extras and mes data again....
- Built the oppositive function of `syncMesToSwipe`, so we can now call `syncSwipeToMes`
2025-03-17 20:59:08 +01:00
Yokayo
e7c9960a45 Fix 2025-03-18 02:15:13 +07:00
Cohee
3f95f30a2b Merge pull request #3706 from bmen25124/custom_request_chat_comp_url
Added "custom_url" to ChatCompletionService
2025-03-17 15:37:58 +02:00
Yokayo
1dade2970b Reasoning keys 2025-03-17 20:21:11 +07:00
bmen25124
86de927ab9 Added "custom_url" to ChatCompletionService 2025-03-17 14:54:59 +03:00
Cohee
fba2d809d0 Merge branch 'release' into staging 2025-03-17 09:47:11 +00:00
Cohee
b01e2824be Remove trim from /start-reply-with 2025-03-17 09:46:52 +00:00
Yokayo
40c3674da1 Update tl 2025-03-17 16:09:36 +07:00
Cohee
df48428d1d Merge pull request #3702 from SillyTavern/hotfix-pr-workflow-v2
Hotfix pr workflow v2
2025-03-16 23:46:11 +02:00
Cohee
f245c48b17 Merge pull request #3701 from bmen25124/exports_for_chat_rebuild
New exported methods
2025-03-16 23:43:57 +02:00
Cohee
c022858e5b Fix structuredClone 2025-03-16 23:37:18 +02:00
Cohee
0bdb131c22 Refine Docker CLI documentation 2025-03-16 23:25:42 +02:00
bmen25124
8dc66bd21b Better WI type readability, fixed clone and type opeators. 2025-03-17 00:13:39 +03:00
Wolfsblvt
111fa0dee5 Use older version of PR size labeler 2025-03-16 22:09:28 +01:00
Wolfsblvt
d887eb2258 Revert "Fix PR workflow by chaining actions"
This reverts commit aacbc5b6db.
2025-03-16 22:08:22 +01:00
bmen25124
1593951281 Cloned preset before using 2025-03-17 00:00:37 +03:00
Cohee
cf2671c6d7 Merge branch 'staging' into reasoning-template 2025-03-16 22:51:28 +02:00
Cohee
a8716d7c69 Merge branch 'release' into staging 2025-03-16 22:51:09 +02:00
Cohee
ecfad12b59 Merge pull request #3699 from SillyTavern/hotfix-pr-workflow
Fix PR workflow by chaining actions
2025-03-16 22:50:52 +02:00
Wolfsblvt
aacbc5b6db Fix PR workflow by chaining actions 2025-03-16 21:48:50 +01:00
bmen25124
0e41db615e New exports 2025-03-16 23:44:02 +03:00
Cohee
d314752547 Add reasoning template to connection profiles 2025-03-16 22:39:43 +02:00
Cohee
fa641e9946 Handle empty files in readFirstLine 2025-03-16 22:16:53 +02:00
Cohee
8bd17de2f3 Invert conditions 2025-03-16 21:49:36 +02:00
Cohee
5145e30be3 Don't bail early if empty token provided from UI 2025-03-16 21:45:07 +02:00
Cohee
5fca39fae7 Fix force flag not being passed 2025-03-16 21:39:31 +02:00
Cohee
5ab284f1f5 Merge branch 'staging' into integrity 2025-03-16 21:28:37 +02:00
Cohee
8ec83fd5d9 Fix corruption due to this_chid shift (#3669)
* continue works same as swipe continued message isn't depth counted

* correct early-out check

* update regex depth setting tooltips for accuracy

* update max tooltip

* remove redundant check

* Fix corruption due to this_chid shift
Fixes #3667

* Unshallow current character on reload

* Allow -1 as a min depth value

* Use selectCharacterById, fix rename logic

* Remove pointless local variables

* Add 'switchMenu' param to selectCharacterById

---------

Co-authored-by: Reithan <bo122081@hotmail.com>
2025-03-16 19:14:27 +02:00
bmen25124
d42a81f97c New connection manager events, ConnectionManagerRequestService (#3603) 2025-03-16 16:58:34 +02:00
Cohee
62342b35e2 Reasoning template 2025-03-16 15:01:31 +02:00
Cohee
46b9bb7854 Merge pull request #3695 from Succubyss/gpt_tokenizer_tweak
gpt-4.5 detection tweak
2025-03-16 02:42:55 +02:00
Succubyss
fff1dd59c3 minor 4.5 detection tweak 2025-03-15 19:27:42 -05:00
Cohee
f88e95d9be Merge branch 'release' into staging 2025-03-16 02:25:20 +02:00
Cohee
248132dd89 Set debug level to unshallow warnings 2025-03-16 02:25:07 +02:00
Cohee
400d29e97e Add chat integrity check to saveChat 2025-03-16 02:24:20 +02:00
Cohee
ead37defeb Merge pull request #3694 from SillyTavern/workflow-hotfixes
Workflow hotfixes
2025-03-16 01:06:19 +02:00
Wolfsblvt
f18cb91ef9 on push, check all pushed commits - duh 2025-03-16 00:02:24 +01:00
Wolfsblvt
892fe7bd34 Workflows ensure explicit versions of actions 2025-03-16 00:02:24 +01:00
Wolfsblvt
0126e5e5a3 Add explicit workflow permissions 2025-03-16 00:02:24 +01:00
Cohee
b8afa96de5 Replace link to docs about regex flags 2025-03-15 23:07:58 +02:00
Cohee
9c42391706 Uncomment cookieSecret config removal 2025-03-15 23:04:02 +02:00
Cohee
d8c8bfa8a4 Merge pull request #3690 from SillyTavern/staging
Staging
2025-03-15 22:47:35 +02:00
Cohee
1041d2ae9d Update README 2025-03-15 19:52:57 +02:00
Cohee
684ee98168 Add config, increase cache TTL, use async file reads 2025-03-15 19:43:26 +02:00
Cohee
b8f8be8cf9 Merge branch 'staging' into disk-cache 2025-03-15 19:27:56 +02:00
Cohee
a1586694b6 Fix start script for electron 2025-03-15 15:41:03 +02:00
Cohee
a7aa1ff79d Bump release version 2025-03-15 15:37:01 +02:00
Cohee
0c7d5c76e2 gemini-2.0-flash-exp-image-generation 2025-03-15 14:58:06 +02:00
Cohee
aa94774963 Add command-a tokenizer to manual selection list 2025-03-15 13:46:13 +02:00
Cohee
628fc810c7 Add docker-compose install guide 2025-03-15 13:13:33 +02:00
Cohee
fb9b0569b6 The numbers must go up 2025-03-15 12:58:36 +02:00
Cohee
dc4612a0e2 Merge pull request #3688 from Erquint/staging
No swiping hotkeys when modifiers are held.
2025-03-15 01:08:34 +02:00
Gness Erquint
e57396040d No swiping hotkeys when modifiers are held. 2025-03-15 00:43:37 +03:00
Wolfsblvt
bafb2cba95 Happy little icons for workflows 2025-03-14 21:34:48 +01:00
Cohee
f607c3bc0d Gemma 3 (#3686)
* Gemma 3

* Adjust safetySettings

* Disable sysprompt

* Add isGemma check to tool processing logic

* Disable a google search tool for gemma
2025-03-14 21:41:28 +02:00
Cohee
0017358f8b Gemini inline images (#3681)
* Gemini images for non-streaming

* Parse images on stream

* Add toggle for image request

* Add extraction params to extractImageFromData

* Add explicit break and return

* Add more JSdoc to processImageAttachment

* Add file name prefix

* Add object argument for saveReply

* Add defaults to saveReply params

* Use type for saveReply result

* Change type check in saveReply backward compat
2025-03-14 20:15:04 +02:00
Cohee
0d2bf00810 Decrease checks interval 2025-03-14 10:44:48 +02:00
Cohee
f362f94c2d Decrease connection timeout, set 'valid' status on 'invalid URL', don't wait if not needed
Fixes #3683
2025-03-14 10:43:00 +02:00
Cohee
c9e716d42f Merge pull request #3682 from SillyTavern/feat-add-regex-toggle-command
Add `/regex-toggle` slash command
2025-03-14 10:15:00 +02:00
Cohee
bef466a5a1 Merge pull request #3685 from kallewoof/202503-gemma-3
chat-template: gemma 3
2025-03-14 10:13:49 +02:00
Karl-Johan Alm
f180d22680 chat-template: gemma 3 2025-03-14 13:57:48 +09:00
Wolfsblvt
18fa33d816 On review feedback of /regex-toggle
- Add quiet arg to suppress success toast
- Fix return values
- Switch-case instead of nested ternaries
- state uses onOfToggle
2025-03-14 01:03:08 +01:00
Cohee
e60796548b Skip status check of invalid custom endpoint URLs
Fixes #3683
2025-03-14 01:40:38 +02:00
Wolfsblvt
0f5f8e163d Merge branch 'staging' into feat-add-regex-toggle-command 2025-03-14 00:26:33 +01:00
Wolfsblvt
95e60b0f79 Switch to action to label maintainers, duh 2025-03-14 00:24:11 +01:00
Wolfsblvt
669cd49574 god give me hope this is right? 2025-03-14 00:11:28 +01:00
Wolfsblvt
9917be0233 Another attempt at fixing maintainer labeleng 2025-03-14 00:09:57 +01:00
Wolfsblvt
7537192c9a Add /regex-toggle slash command
- Add /regex-toggle command, similarly to /extension-toggle
- toggles the state of both global and character-bound scripts
- Update jsdoc being inconsistent

Closes #3613
2025-03-13 23:55:08 +01:00
Wolfsblvt
eaa7b91f1d Workflows use checkout step for label apply
- Checkout might be needed/useful, otherwise config files are not present
- Do not remove labels from PR updates anymore. Has to be done manually. Otherwise manual labelling really isn't possible.
2025-03-13 23:47:11 +01:00
Wolfsblvt
ea989df6a1 Hard-code maintainer list for maintainer label 2025-03-13 23:08:29 +01:00
Wolfsblvt
166b404ea7 Remove PR auto-labels when not relevant anymore 2025-03-13 22:37:58 +01:00
Wolfsblvt
25792b53f2 Improve logging and workflow names 2025-03-13 22:28:32 +01:00
Wolfsblvt
aad4b449ee Merge pull request #3678 from SillyTavern/update-git-workflows
Update github workflows for better issue & PR management, automating chores
2025-03-13 22:14:56 +01:00
Wolfsblvt
5743972a26 Extract workflows for merge conflicts + issue done
- Extract merge conflicts check to its own workflow, plus make it run on push
- Add issues update as Done or Done staging based on extracted commit messages
2025-03-13 22:13:00 +01:00
Cohee
be37b6ff8f Merge pull request #3679 from bmen25124/command_a
Added command-a-03-2025 and command-a tokenizer
2025-03-13 22:14:03 +02:00
Wolfsblvt
cd0ca0363e Fix /inject id not being required, cause undefined 2025-03-13 20:35:39 +01:00
bmen25124
fdcff7a7f0 Fixd "model.id" check 2025-03-13 22:25:20 +03:00
Wolfsblvt
d9c868b2fe Move PR size message to auto label replies 2025-03-13 20:12:55 +01:00
Wolfsblvt
15dbadbfe0 Move unstale to the issue/pr manager
Doesn't make sense to run the scheduled workflow and then skip most of it's jobs.
And it looks weird in the PR boxy thingy
2025-03-13 19:57:30 +01:00
Wolfsblvt
707efac5b9 Fix PR labeling failing 2025-03-13 19:40:41 +01:00
Wolfsblvt
972eea1efd Rename some labels, add config label
- Rename PR size labels
- Add config.yaml label on config changes
- Add config.yaml to the backened changes label
- Awaiting User Response will not be blocked by Keep Open
2025-03-13 19:28:23 +01:00
bmen25124
a77f4045f8 Added command-a-03-2025 and command-a tokenizer 2025-03-13 21:16:08 +03:00
Wolfsblvt
cc3e08ddaa Merge branch 'staging' into update-git-workflows 2025-03-13 16:43:23 +01:00
Wolfsblvt
eb17e37002 Fix maintainer label, minor docs 2025-03-13 16:42:49 +01:00
Wolfsblvt
ecaee2dbbf Directly remove stale labels on comments 2025-03-13 16:35:15 +01:00
Wolfsblvt
84e7ddbf74 PR workflow, add labels based on files/branch name
- Add PR labels based on changed files
- Add PR labels based on branch names
2025-03-13 16:33:47 +01:00
Wolfsblvt
4f7695b0ce Update auto comments to make them more uniform 2025-03-13 16:28:28 +01:00
Wolfsblvt
5f726d2b25 A bit more auto-labeling on issue open 2025-03-13 12:08:49 +01:00
Wolfsblvt
e43b9a3d2c Rework auto labels based on labels a bit 2025-03-13 12:08:17 +01:00
Wolfsblvt
34363e6875 Add issue types to issue templates 2025-03-13 11:38:40 +01:00
Cohee
79a8080b7d Merge pull request #3671 from bmen25124/events_type_param
New context methods, added type parameter to message events
2025-03-13 01:50:59 +02:00
bmen25124
92dacdb386 better type name, simplified context 2025-03-13 02:27:03 +03:00
Cohee
4fdc28afbc Merge pull request #3673 from qvink/export_parse_reasoning
exporting parseReasoningFromString()
2025-03-13 01:20:53 +02:00
qvink
874affb2f2 exporting parseReasoningFromString() 2025-03-12 17:01:03 -06:00
Cohee
37819df542 Move reasoning continue regex depth adjustment 2025-03-12 23:59:53 +02:00
Cohee
160f7431d6 Fix isPrefix for continue on reasoning 2025-03-12 23:55:16 +02:00
Cohee
12824bb680 Clear cached WI on deletion
Fixes #3672
2025-03-12 23:38:10 +02:00
Cohee
6af3f2ee7e Remove unused splitSentences function from utils.js 2025-03-12 23:29:55 +02:00
Cohee
d7085b119d Fix npm audit in tests 2025-03-12 22:09:50 +02:00
Cohee
1b817cd897 Kokoro: chunk generation, add pre-process func
#3412
2025-03-12 21:35:09 +02:00
Cohee
e65b72ea41 Fix global.d.ts var declarations 2025-03-12 20:54:07 +02:00
Cohee
c9c5dfa8c0 Bump external packages 2025-03-12 20:53:35 +02:00
Cohee
f11ebb032b Merge branch 'release' into staging 2025-03-12 20:43:47 +02:00
Cohee
58e714fce4 Fix npm audit 2025-03-12 20:43:38 +02:00
bmen25124
ddb77732f2 New context methods, added type parameters to message events 2025-03-12 21:25:16 +03:00
Reithan
01ef823da9 Dont count Continue as message 0 (#3594)
* continue works same as swipe continued message isn't depth counted

* correct early-out check

* update regex depth setting tooltips for accuracy

* update max tooltip

* remove redundant check

* Allow -1 as a min depth value

---------

Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
2025-03-12 19:59:15 +02:00
Cohee
1b02426df1 Unshallow current character on selectCharacterById 2025-03-12 11:13:48 +02:00
Cohee
c4021525ac Merge pull request #3664 from SillyTavern/hide-name
Add "name" argument to /hide and /unhide
2025-03-12 10:48:19 +02:00
Cohee
e7189a1260 Refactor hideMessageCallback and unhideMessageCallback to remove unnecessary console logs. Introduce onlyUniqueJson and sortIgnoreCaseAndAccents utility functions 2025-03-12 10:18:02 +02:00
Wolfsblvt
843bd1cf3c Add check for PRs with merge-blocking labels 2025-03-12 04:02:14 +01:00
Wolfsblvt
fc151284e4 update labels based on other labels 2025-03-12 03:21:23 +01:00
Wolfsblvt
f6f87f6d5b unstale issues/prs on comments right away 2025-03-12 02:56:38 +01:00
Wolfsblvt
7543a24ca7 Rewrite and expand auto comments 2025-03-12 02:44:24 +01:00
Wolfsblvt
89ab138882 Auto-label PRs based on files + targeting release 2025-03-12 01:55:37 +01:00
Wolfsblvt
0237b6a872 Auto-comment/label issues once on staging 2025-03-12 00:59:23 +01:00
Wolfsblvt
c22ad7c2e8 auto ask for feedback on "Alternative Exists" 2025-03-12 00:06:33 +01:00
Wolfsblvt
26c4d231a8 "add Maintainer label" job via on open workflow 2025-03-11 23:43:35 +01:00
Cohee
1026e1f8e9 Add "name" argument to /hide and /unhide. Add default value for unnamed argument 2025-03-11 23:14:31 +02:00
Wolfsblvt
d6dcededc9 Refactor workflows into more structured files 2025-03-11 21:43:27 +01:00
Cohee
ebe30dceac Merge pull request #3660 from SillyTavern/group-pooled-order
Add group pooled order
2025-03-11 22:17:31 +02:00
Cohee
370bd9a3a8 Refactor to not use array 2025-03-11 22:16:26 +02:00
Cohee
137927bb43 Merge pull request #3662 from SillyTavern/enable-md-hotkeys-in-expando
Add markdown hotkeys support for expando editor on textareas that support markdown
2025-03-11 13:03:23 +02:00
Wolfsblvt
90cfdebff8 Remove unnecessary markdown icon 2025-03-11 10:39:14 +01:00
Wolfsblvt
0cde7e7a7f Add md hotkey support for expando editor
- When original textarea supports markdown, the textarea of the expanded popup will also have markdown support
- Also add the small markdown icon at the top
2025-03-11 04:42:03 +01:00
Cohee
cf63b70997 Fix instruct macro checkbox missing control bindings
Fixes #3643
2025-03-11 01:31:28 +02:00
Cohee
ad225138b4 Add group pooled order
Closes #3650
2025-03-11 01:24:46 +02:00
Cohee
68b5be063f TC: Increased unlocked response to 32k 2025-03-11 00:24:28 +02:00
Cohee
bdbe043259 Fix "Response Length" slider showing wrong value (#3659)
- "Mad Lab Mode" was falsely "resetting" the slider control, for no obvious reason besides weird implementation.
2025-03-11 00:21:59 +02:00
Wolfsblvt
57a229d5fd Fix "Response Length" slider showing wrong value
- "Mad Lab Mode" was falsely "resetting" the slider control, for no obvious reason besides weird implementation.
2025-03-10 22:21:36 +01:00
felger
e23f3a6314 feature: 'kokoro-js' supports TTS #3412 (#3656)
* feature: 'kokoro-js' supports TTS #3412

* Linting, add credits for kokoro library

* Fix voice preview

* Fix display languages on previews

* Fix settings restoration. Debounce model init on settings change

* Fix engine sorting

* Move TTS processing to a web worker. Remove unused gain setting

* Speaking rate fix

* Update status when recreating a worker

* Pass voices list from TTS engine

* Call dispose function on provider change

* Extend worker init timeout to 10 minutes

---------

Co-authored-by: ryan <1014670860@qq.com>
Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
2025-03-10 22:54:54 +02:00
Wolfsblvt
c722d251ff Update merge conflicts workflow
- Change the action being used
- Add comment to notify PR author on merge conflicts
2025-03-10 20:33:09 +01:00
Wolfsblvt
a104de38b6 Remove unused maintainers response workflow 2025-03-10 20:11:24 +01:00
Wolfsblvt
26093c1ae4 Update remove labels on close workflow
- Added more labels that should be removed
- Included PRs and their labels to be auto-removed
2025-03-10 20:11:05 +01:00
Wolfsblvt
c8fbd51554 Update stale workflow
- Remove not needed "Maintainer Response" labeling
- Update to latest version
- Add link to source
2025-03-10 19:53:50 +01:00
Wolfsblvt
bf75e88931 Update PR size workflow 2025-03-10 19:40:02 +01:00
Cohee
13a3f4772e Merge pull request #3653 from SillyTavern/fix-group-create-tags
Fix group creation inheriting tags from characters[0]
2025-03-10 20:30:23 +02:00
Cohee
a3b2cc456f Clear group tags list on create click 2025-03-10 20:30:03 +02:00
Wolfsblvt
1e6f8c6637 Better wording for /input arg descriptions 2025-03-10 17:41:18 +01:00
Cohee
594cba30ca Merge pull request #3657 from SillyTavern/fix-buttons-popup-display
Fix `/buttons` buttons not being displayed on log popup texts
2025-03-10 17:41:16 +02:00
Wolfsblvt
e3bcc79bb7 Fix /buttons buttons not being displayed
- On long popup texts, the buttons of the `/popup` slash command will not be displayed, cause by an unneeded overflow CSS property

Fixes #3654
2025-03-10 15:35:01 +01:00
Cohee
73230272f3 Apply regex to multiswipe reasoning, set reasoning_type to Parsed 2025-03-10 10:48:31 +00:00
Cohee
71a3aefe86 Replace link to regex editor docs 2025-03-10 10:40:25 +00:00
Cohee
6024b29ea7 Merge pull request #3652 from SillyTavern/default-middleware
Use default middleware for parsing request body
2025-03-10 12:34:01 +02:00
Cohee
ee13cef37f Fix group creation inheriting tags from characters[0] 2025-03-10 01:37:25 +02:00
Cohee
67d013e40a Use default middleware for parsing request body 2025-03-10 00:48:58 +02:00
Cohee
64206d6f47 Merge pull request #3651 from SillyTavern/input-handling-args
Add `/input` arguments to execute closures on success and on cancel
2025-03-10 00:45:47 +02:00
Wolfsblvt
fc1c767280 onCancel and onSuccess handlers for /input command 2025-03-09 23:10:56 +01:00
Cohee
d4f6373bbc Merge pull request #3648 from SillyTavern/regex-info-block
Add info block for find regex flags
2025-03-09 23:30:38 +02:00
Wolfsblvt
fa03443fe7 Switch /input command definition to fromProps 2025-03-09 22:29:40 +01:00
Cohee
0f8a17b652 Refactor regex info block to use a shared function 2025-03-09 23:29:00 +02:00
Cohee
3c65b2dcf3 Implement disk caching for character data using node-persist 2025-03-09 20:41:04 +02:00
Cohee
070de9df2d (CC) Move continue nudge at the end of completion (#3611)
* Move continue nudge at the end of completion
Closes #3607

* Move continue message together with nudge
2025-03-09 18:17:02 +02:00
Cohee
34b2ef0fd7 Clean-up code 2025-03-09 17:47:23 +02:00
Cohee
eef9c3ef62 Add info block for find regex flags
#3647
2025-03-09 17:41:38 +02:00
Cohee
6f0f32d83d Add a second row to profile editing name 2025-03-09 16:59:58 +02:00
Cohee
7845994315 Add 'start-reply-with' to Connection Profiles (#3632)
* Connection Profiles: Add support for 'start-reply-with' command and allow empty values for 'stop-strings' command

* Add handling for empty profile values in makeFancyProfile function

* Fix application of empty values

* Handle undefined values

* Improve argument validation

* Replace || with &&

* I got it right this time, swear

* Who wrote this?
2025-03-09 16:55:49 +02:00
Cohee
d94c301b10 Merge pull request #3646 from SillyTavern/fix-persona-auto-lock
Fix persona auto-lock to chat not working when the persona was already selected
2025-03-09 16:48:21 +02:00
Wolfsblvt
67699d9cfa Fix persona auto-lock to chat not working
- When auto lock was enabled, it didn't auto-lock to chat when the persona was already selected (like choosing the same persona you have used before)
2025-03-09 15:27:48 +01:00
Cohee
a392593e53 Merge pull request #3634 from SillyTavern/continue-from-reasoning
Fix auto-parsing of continue from reasoning
2025-03-09 16:22:02 +02:00
Cohee
cabd6de85b Prevent rollover on keyboard left swipe if repeating (#3644)
* Prevent rollover on keyboard left swipe if repeating
Closes #3636

* Ditto for right swipe

* Remove goofy comment leftover
2025-03-09 16:12:24 +02:00
Cohee
c03da65821 Run parsing at least once before rendering reasoning DOM on continue 2025-03-09 16:08:29 +02:00
Cohee
5d74507e50 Remove goofy comment leftover 2025-03-09 14:09:39 +02:00
Cohee
19b7deaed0 Ditto for right swipe 2025-03-09 14:08:24 +02:00
Cohee
6aaa533410 Prevent rollover on keyboard left swipe if repeating
Closes #3636
2025-03-09 13:42:30 +02:00
Cohee
96d79ac4e9 Merge branch 'staging' into continue-from-reasoning 2025-03-09 01:19:25 +02:00
Cohee
b52b11d7bb Force settings content check on user creation
Closes #3641
2025-03-09 00:56:23 +02:00
Cohee
1cb9287684 Vectors WebLLM (#3631)
* Add WebLLM support for vectorization

* Load models when WebLLM extension installed

* Consistency updated

* Move checkWebLlm to initEngine

* Refactor vector request handling to use getAdditionalArgs

* Add error handling for unsupported WebLLM extension

* Add prefix to error causes
2025-03-09 00:51:44 +02:00
Cohee
0ea64050ff Parse reasoning in multi-swipe swipes 2025-03-08 23:06:56 +02:00
Cohee
d0068ecbab Clean-up swipe_info of multi-swipes 2025-03-08 22:48:42 +02:00
Cohee
ca14352972 Fix syncMesToSwipe checks
Ported from #3634
2025-03-08 22:33:26 +02:00
Cohee
50a0f41736 Sync mes to swipe on stream finished 2025-03-08 22:29:18 +02:00
Cohee
7e3946c152 Add tools parameter to AI21 request 2025-03-08 22:26:32 +02:00
Cohee
edabd1128b Add tags in toShallow
Closes #3638
2025-03-08 22:04:49 +02:00
Cohee
f38898e03f Merge pull request #3640 from Succubyss/patch-7
Re-enable logit bias and stop strings for 4.5
2025-03-08 21:56:11 +02:00
Cohee
98f92f6270 Fix syntax of model name check 2025-03-08 21:50:39 +02:00
Cohee
5d275998ed Merge branch 'staging' into patch-7 2025-03-08 21:46:38 +02:00
Succubyss
c3b5382882 Re-enable logit bias and stop strings for 4.5 2025-03-08 12:57:11 -06:00
Cohee
de0e65fe13 Enable tool calling for ai21 2025-03-08 17:25:37 +02:00
Cohee
ff5835278b Add Jamba 1.6 models
Closes #3633
2025-03-08 15:16:49 +02:00
Cohee
980ed76cc3 Fix auto-parsing of continue from reasoning
Continues #3606
2025-03-08 12:58:26 +02:00
Wolfsblvt
91fe2841e3 Fix reasoning rendering on auto-save message 2025-03-08 05:52:11 +01:00
208 changed files with 8224 additions and 3037 deletions

View File

@@ -3,6 +3,9 @@ module.exports = {
extends: [
'eslint:recommended',
],
plugins: [
'jsdoc',
],
env: {
es6: true,
},
@@ -75,8 +78,10 @@ module.exports = {
'plugins/**',
'**/*.min.js',
'public/scripts/extensions/quick-reply/lib/**',
'public/scripts/extensions/tts/lib/**',
],
rules: {
'jsdoc/no-undefined-types': ['warn', { disableReporting: true, markVariablesAsUsed: true }],
'no-unused-vars': ['error', { args: 'none' }],
'no-control-regex': 'off',
'no-constant-condition': ['error', { checkLoops: false }],

View File

@@ -1,4 +1,5 @@
name: Bug Report 🐛
type: Bug
description: Report something that's not working the intended way. Support requests for external programs (reverse proxies, 3rd party servers, other peoples' forks) will be refused! Please use English only.
title: '[BUG] <title>'
labels: ['🐛 Bug']

View File

@@ -1,4 +1,5 @@
name: Feature Request ✨
type: Feature
description: Suggest an idea for future development of this project. Please use English only.
title: '[FEATURE_REQUEST] <title>'
labels: ['🦄 Feature Request']
@@ -32,7 +33,7 @@ body:
id: solution
attributes:
label: Describe the solution you'd like
placeholder: An outline of how you would like this to be implemented, include as much details as possible
placeholder: An outline of how you would like this to be implemented, include as much details as possible
validations:
required: true

View File

@@ -1,2 +0,0 @@
🐛 Bug: ✅ Fixed
🦄 Feature Request: ✅ Implemented

View File

@@ -1,62 +0,0 @@
comment:
footer: |
---
> I am a bot, and this is an automated message 🤖
labels:
- name: ✖️ Invalid
labeled:
issue:
action: close
body: >
Hello @{{ issue.user.login }} your ticket has been marked as invalid.
Please ensure you follow the issue template, provide all requested info,
and be sure to check the docs + previous issues prior to raising tickets.
pr:
body: Thank you @{{ pull_request.user.login }} for suggesting this. Please follow the pull request templates.
action: close
- name: 👩‍💻 Good First Issue
labeled:
issue:
body: >
This issue has been marked as a good first issue for first-time contributors to implement!
This is a great way to support the project, while also improving your skills, you'll also be credited as a contributor once your PR is merged.
If you're new to SillyTavern [here are a collection of resources](https://docs.sillytavern.app/)
If you need any support at all, feel free to reach out via [Discord](https://discord.gg/sillytavern).
- name: ❌ wontfix
labeled:
issue:
action: close
body: >
This ticked has been marked as 'wontfix', which usually means it is out-of-scope, or not feasible at this time.
You can still fork the project and make the changes yourself.
- name: ✅ Fixed
labeled:
issue:
body: >
Hello @{{ issue.user.login }}! It looks like all or part of this issue has now been implemented.
- name: ‼️ High Priority
labeled:
issue:
body: >
This ticket has been marked as high priority, and has been bumped to the top of the priority list.
You should expect an implementation to be pushed out soon. Thank you for your patience.
- name: 💀 Spam
labeled:
issue:
action: close
locking: lock
lock_reason: spam
body: >
This issue has been identified as spam, and is now locked.
Users who repeatedly raise spam issues may be blocked or reported.
- name: ⛔ Don't Merge
labeled:
pr:
body: This PR has been temporarily blocked from merging.

69
.github/issues-auto-comments.yml vendored Normal file
View File

@@ -0,0 +1,69 @@
labels:
- name: ✖️ Invalid
labeled:
issue:
action: close
body: >
Hey @{{ issue.user.login }}, this issue has been marked as invalid.
Please double-check that you've followed the issue template, included all necessary details, and reviewed the docs & previous issues before submitting.
If provided, follow the instructions given by maintainers.
- name: 👩‍💻 Good First Issue
labeled:
issue:
body: >
🏆 This issue has been marked as a good first issue for contributors to implement!
This is a great way to support the project. While also improving your skills, you'll also be credited as a contributor once your PR is merged.
If you're new to SillyTavern [here is the official documentation](https://docs.sillytavern.app/). The official contribution guide can be found [here](https://github.com/SillyTavern/SillyTavern/blob/release/CONTRIBUTING.md).
If you need any support, feel free to reach out via [Discord](https://discord.gg/sillytavern), or let us know in this issue or via [discussions](https://github.com/SillyTavern/SillyTavern/discussions).
- name: ❌ wontfix
labeled:
issue:
action: close
body: >
❌ This issue has been marked as 'wontfix', which usually means it is out-of-scope, not feasible at this time or will not be implemented for various reasons.
If you have any questions about this, feel free to reach out.
- name: 🛑 Out of Scope
labeled:
issue:
action: close
body: >
🛑 This issue has been marked as 'out of scope', as this can't or won't be implemented.
If you have any questions about this, feel free to reach out.
- name: ✅ Done (staging)
labeled:
issue:
body: >
✅ It looks like all or part of this issue has now been implemented as part of the `staging` branch.
If you currently are on the `release` branch, you can switch to `staging` to test this right away.
Note that `staging` is considered less stable than the official releases. To switch, follow existing instructions,
or simply enter the following command: `git switch staging`
- name: ✅ Done
labeled:
issue:
body: >
✅ It looks like all or part of this issue has now been implemented as part of the latest release.
- name: ‼️ High Priority
labeled:
issue:
body: >
🚨 This issue has been marked high priority, meaning it's important to the maintainers or community.
While we can't promise immediate changes, it is on our radar and will be addressed whenever possible. Thanks for your patience!
- name: 💀 Spam
labeled:
issue:
action: close
locking: lock
lock_reason: spam
body: >
💀 This issue has been flagged as spam and is now locked.
Please avoid posting spam - it disrupts the community and wastes everyone's time.

View File

@@ -1,7 +1,3 @@
# Add/remove 'critical' label if issue contains the words 'urgent' or 'critical'
#critical:
# - '(critical|urgent)'
🪟 Windows:
- '(🪟 Windows)'
@@ -15,4 +11,10 @@
- '(📱 Termux)'
🐧 Linux:
- '(🐧 Linux)'
- '(🐧 Linux)'
🦊 Firefox:
- '\b(firefox|mozilla)\b'
📱 Mobile:
- '\b(iphone|ios|android|📱 Termux)\b'

51
.github/pr-auto-comments.yml vendored Normal file
View File

@@ -0,0 +1,51 @@
labels:
- name: ✖️ Invalid
labeled:
pr:
action: close
body: >
Hey @{{ pull_request.user.login }}, thanks for your contribution!
Unfortunately, this PR has been marked as invalid.
Please check that you've followed the PR template, included all relevant details, and are targeting the correct branch (`staging` for regular contributions, `release` only for hotfixes).
If you need help, feel free to ask!
- name: ⛔ Don't Merge
labeled:
pr:
body: >
🚨 This PR has been temporarily blocked from merging.
- name: 💥💣 Breaking Changes
labeled:
pr:
body: >
⚠️ Heads up! This PR introduces breaking changes.
Make sure these changes are well-documented and that users will be properly informed when this is released.
- name: ⛔ Waiting For External/Upstream
labeled:
pr:
body: >
⛔ This PR is awaiting external or upstream changes or approval.
It can only be merged once those changes have been implemented and approved.
Please inform us of any progress on the upstream changes or approval.
- name: 🔬 Needs Testing
labeled:
pr:
body: >
🔬 This PR needs testing!
Any contributor can test and leave reviews, so feel free to help us out!
- name: 🟥 ⬤⬤⬤⬤⬤
labeled:
pr:
body: >
⚠️ This PR is over 1000 lines, which is larger than recommended.
Please make sure that it only addresses a single issue - PRs this large are hard to test and may be rejected.

83
.github/pr-auto-labels-by-branch.yml vendored Normal file
View File

@@ -0,0 +1,83 @@
####################################
# Label PRs against 'release' #
####################################
❗ Against Release Branch:
- base-branch: 'release'
####################################
# Labels based on PR branch name #
####################################
🦋 Bug Fix:
- head-branch: ['^fix[/-]', '\bfixes\b']
🚑 Hot Fix:
- head-branch: ['^hotfix[/-]']
✨ New Feature:
- head-branch: ['^feat(ure)?[/-].*?\badd', '^add-']
✨ Feature Changes:
- head-branch: ['^feat(ure)?[/-](?!.*\badd\b)', '\bchanges?\b']
🤖 API / Model:
- head-branch: ['\bapi\b', '\bmodels?\b']
🏭 Backend Changes:
- head-branch: ['\bbackend\b', '\bendpoints?\b']
🐋 Docker:
- head-branch: ['\bdocker\b']
Extension:
- head-branch: ['\bextension\b', '\bext\b']
🦊 Firefox:
- head-branch: ['\bfirefox\b']
🧑‍🤝‍🧑 Group Chat:
- head-branch: ['\bgroups?\b']
🖼️ Image Gen:
- head-branch: ['\bimage-gen\b']
🌐 Language:
- head-branch: ['\btranslations?\b', '\blanguages?\b']
🐧 Linux:
- head-branch: ['\blinux\b']
🧩 Macros:
- head-branch: ['\bmacros?\b']
📱 Mobile:
- head-branch: ['\bmobile\b', '\bios\b', '\bandroid\b']
🚄 Performance:
- head-branch: ['\bperformance\b']
⚙️ Preset:
- head-branch: ['\bpresets?\b']
📜 Prompt:
- head-branch: ['\bprompt\b']
🧠 Reasoning:
- head-branch: ['\breasoning\b', '\breason\b', '\bthinking\b']
🚚 Refactor:
- head-branch: ['\brefactor(s|ed)?\b']
📜 STscript:
- head-branch: ['\bstscript\b', '\bslash-commands\b']
🏷️ Tags / Folders:
- head-branch: ['\btags\b']
🎙️ TTS / Voice:
- head-branch: ['\btts\b', '\bvoice\b']
🌟 UX:
- head-branch: ['\bux\b']
🗺️ World Info:
- head-branch: ['\bworld-info\b', '\bwi\b']

46
.github/pr-auto-labels-by-files.yml vendored Normal file
View File

@@ -0,0 +1,46 @@
####################################
# Labels based on changed files #
####################################
🏭 Backend Changes:
- changed-files:
- any-glob-to-any-file:
- "src/**"
- "default/config.yaml"
- "server.js"
- "plugins.js"
- "recover.js"
- "webpack.config.js"
- "Start.bat"
- "start.sh"
- "UpdateAndStart.bat"
- "UpdateForkAndStart.bat"
⚙️ config.yaml:
- changed-files:
- any-glob-to-any-file:
- "default/config.yaml"
🛠️ Build Changes:
- changed-files:
- any-glob-to-any-file:
- ".github/workflows/**"
- "docker/**"
- ".dockerignore"
- "Dockerfile"
- "webpack.config.js"
🌐 Language:
- changed-files:
- any-glob-to-any-file:
- "public/locales/**"
📥 Dependencies:
- changed-files:
- any-glob-to-any-file:
- "public/lib/**" # Every frontend lib counts as a dependency as well
- "package.json"
- "package-lock.json"
- "tests/package.json"
- "tests/package-lock.json"
- "src/electron/package.json"
- "src/electron/package-lock.json"

120
.github/readme.md vendored
View File

@@ -23,7 +23,7 @@ We have a [Documentation website](https://docs.sillytavern.app/) to answer most
SillyTavern (or ST for short) is a locally installed user interface that allows you to interact with text generation LLMs, image generation engines, and TTS voice models.
Beginning in February 2023 as a fork of TavernAI 1.2.8, SillyTavern now has over 100 contributors and 2 years of independent development under its belt, and continues to serve as a leading software for savvy AI hobbyists.
Beginning in February 2023 as a fork of TavernAI 1.2.8, SillyTavern now has over 200 contributors and 2 years of independent development under its belt, and continues to serve as a leading software for savvy AI hobbyists.
## Our Vision
@@ -113,7 +113,9 @@ SillyTavern has extensibility support.
Tutorials on how to use them can be found in the [Docs](https://docs.sillytavern.app/).
# ⌛ Installation
## ⌛ Installation
### 🪟 Windows
> \[!WARNING]
>
@@ -121,9 +123,7 @@ Tutorials on how to use them can be found in the [Docs](https://docs.sillytavern
> * DO NOT RUN START.BAT WITH ADMIN PERMISSIONS
> * INSTALLATION ON WINDOWS 7 IS IMPOSSIBLE AS IT CAN NOT RUN NODEJS 18.16
## 🪟 Windows
### Installing via Git
#### Installing via Git (recommended)
1. Install [NodeJS](https://nodejs.org/en) (latest LTS version is recommended)
2. Install [Git for Windows](https://gitforwindows.org/)
@@ -138,7 +138,7 @@ Tutorials on how to use them can be found in the [Docs](https://docs.sillytavern
7. Once everything is cloned, double-click `Start.bat` to make NodeJS install its requirements.
8. The server will then start, and SillyTavern will pop up in your browser.
### Installing via GitHub Desktop
#### 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/))
@@ -152,7 +152,7 @@ Tutorials on how to use them can be found in the [Docs](https://docs.sillytavern
9. After the installation process, if everything is working, the command console window should look like this and a SillyTavern tab should be open in your browser:
10. Connect to any of the [supported APIs](https://docs.sillytavern.app/usage/api-connections/) and start chatting!
## 🐧 Linux & 🍎 MacOS
### 🐧 Linux & 🍎 MacOS
For MacOS / Linux all of these will be done in a Terminal.
@@ -168,6 +168,72 @@ For MacOS / Linux all of these will be done in a Terminal.
* `./start.sh`
* `bash start.sh`
## 🐋 Installing via Docker
These instructions assume you have installed Docker, are able to access your command line for the installation of containers, and familiar with their general operation.
### Using the GitHub Container Registry
#### Docker Compose (easiest)
Grab the `docker-compose.yml` file from the [GitHub Repository](https://github.com/SillyTavern/SillyTavern/blob/release/docker/docker-compose.yml) and run the following command in the directory where the file is located. This will pull the latest release image from the GitHub Container Registry and start the container, automatically creating the necessary volumes.
```shell
docker-compose up
```
Customize the `docker-compose.yml` file to your needs. The default port is 8000. If you want to adjust the server configuration using environment variables, read the documentation [here](https://docs.sillytavern.app/administration/config-yaml/#environment-variables).
#### Docker CLI (advanced)
You will need two mandatory directory mappings and a port mapping to allow SillyTavern to function. In the command, replace your selections in the following places:
#### Container Variables
##### Volume Mappings
* `CONFIG_PATH` - The directory where SillyTavern configuration files will be stored on your host machine
* `DATA_PATH` - The directory where SillyTavern user data (including characters) will be stored on your host machine
* `PLUGINS_PATH` - (optional) The directory where SillyTavern server plugins will be stored on your host machine
* `EXTENSIONS_PATH` - (optional) The directory where global UI extensions will be stored on your host machine
##### Port Mappings
* `PUBLIC_PORT` - The port to expose the traffic on. This is mandatory, as you will be accessing the instance from outside of its virtual machine container. DO NOT expose this to the internet without implementing a separate service for security.
##### Additional Settings
* `SILLYTAVERN_VERSION` - On the right-hand side of this GitHub page, you'll see "Packages". Select the "sillytavern" package and you'll see the image versions. The image tag "latest" will keep you up-to-date with the current release. You can also utilize "staging" that points to the nightly image of the respective branch.
#### Running the container
1. Open your Command Line
2. Run the following command in a folder where you want to store the configuration and data files:
```bash
SILLYTAVERN_VERSION="latest"
PUBLIC_PORT="8000"
CONFIG_PATH="./config"
DATA_PATH="./data"
PLUGINS_PATH="./plugins"
EXTENSIONS_PATH="./extensions"
docker run \
--name="sillytavern" \
-p "$PUBLIC_PORT:8000/tcp" \
-v "$CONFIG_PATH:/home/node/app/config:rw" \
-v "$DATA_PATH:/home/node/app/data:rw" \
-v "$EXTENSIONS_PATH:/home/node/app/public/scripts/extensions/third-party:rw" \
-v "$PLUGINS_PATH:/home/node/app/plugins:rw" \
ghcr.io/sillytavern/sillytavern:"$SILLYTAVERN_VERSION"
```
> By default the container will run in the foreground. If you want to run it in the background, add the `-d` flag to the `docker run` command.
### Building the image yourself
We have a comprehensive guide on using SillyTavern in Docker [here](http://docs.sillytavern.app/installation/docker/) which covers installations on Windows, macOS and Linux! Give it a read if you wish to build the image yourself.
## ⚡ Installing via SillyTavern Launcher
SillyTavern Launcher is an installation wizard that will help you get setup with many options, including installing a backend for local inference.
@@ -239,45 +305,6 @@ chmod +x install.sh && ./install.sh
chmod +x launcher.sh && ./launcher.sh
```
## 🐋 Installing via Docker
These instructions assume you have installed Docker, are able to access your command line for the installation of containers, and familiar with their general operation.
### Building the image yourself
We have a comprehensive guide on using SillyTavern in Docker [here](http://docs.sillytavern.app/installation/docker/) which covers installations on Windows, macOS and Linux! Give it a read if you wish to build the image yourself.
### Using the GitHub Container Registry (easiest)
You will need two mandatory directory mappings and a port mapping to allow SillyTavern to function. In the command, replace your selections in the following places:
#### Container Variables
##### Volume Mappings
* [config] - The directory where SillyTavern configuration files will be stored on your host machine
* [data] - The directory where SillyTavern user data (including characters) will be stored on your host machine
* [plugins] - (optional) The directory where SillyTavern server plugins will be stored on your host machine
* [extensions] - (optional) The directory where global UI extensions will be stored on your host machine
##### Port Mappings
* [PublicPort] - The port to expose the traffic on. This is mandatory, as you will be accessing the instance from outside of its virtual machine container. DO NOT expose this to the internet without implementing a separate service for security.
##### Additional Settings
* [DockerNet] - The docker network that the container should be created with a connection to. If you don't know what it is, see the [official Docker documentation](https://docs.docker.com/reference/cli/docker/network/).
* [version] - On the right-hand side of this GitHub page, you'll see "Packages". Select the "sillytavern" package and you'll see the image versions. The image tag "latest" will keep you up-to-date with the current release. You can also utilize "staging" and "release" tags that point to the nightly images of the respective branches, but this may not be appropriate, if you are utilizing extensions that could be broken, and may need time to update.
#### Install command
1. Open your Command Line
2. Run the following command
`docker run --name='sillytavern' --net='[DockerNet]' -p '8000:8000/tcp' -v '[plugins]':'/home/node/app/plugins':'rw' -v '[config]':'/home/node/app/config':'rw' -v '[data]':'/home/node/app/data':'rw' -v '[extensions]':'/home/node/app/public/scripts/extensions/third-party':'rw' 'ghcr.io/sillytavern/sillytavern:[version]'`
> Note that 8000 is a default listening port. Don't forget to use an appropriate port if you change it in the config.
## 📱 Installing via Termux on Android OS
> \[!NOTE]
@@ -393,6 +420,7 @@ GNU Affero General Public License for more details.**
* Icon theme by Font Awesome <https://fontawesome.com> (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* Default content by @OtisAlejandro (Seraphina character and lorebook) and @kallmeflocc (10K Discord Users Celebratory Background)
* Docker guide by [@mrguymiah](https://github.com/mrguymiah) and [@Bronya-Rand](https://github.com/Bronya-Rand)
* kokoro-js library by [@hexgrad](https://github.com/hexgrad) (Apache-2.0 License)
## Top Contributors

View File

@@ -1,28 +0,0 @@
# Based on a label applied to an issue, the bot will add a comment with some additional info
name: 🎯 Auto-Reply to Labeled Tickets
on:
issues:
types:
- labeled
- unlabeled
pull_request_target:
types:
- labeled
- unlabeled
permissions:
contents: read
issues: write
pull-requests: write
jobs:
comment:
runs-on: ubuntu-20.04
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Label Commenter
uses: peaceiris/actions-label-commenter@v1
with:
config_file: .github/issue-auto-comments.yml
github_token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}

View File

@@ -1,17 +0,0 @@
# Detect and label pull requests that have merge conflicts
name: 🏗️ Check Merge Conflicts
on:
push:
branches:
- staging
jobs:
check-conflicts:
if: github.repository == 'SillyTavern/SillyTavern'
runs-on: ubuntu-latest
steps:
- uses: mschilde/auto-label-merge-conflicts@master
with:
CONFLICT_LABEL_NAME: "🚫 Merge Conflicts"
GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
MAX_RETRIES: 5
WAIT_MS: 5000

View File

@@ -1,82 +0,0 @@
# Closes any issues that no longer have user interaction
name: 🎯 Close Stale Issues
on:
workflow_dispatch:
schedule:
- cron: '0 0 * * *' # Runs every day at midnight UTC
jobs:
stale:
runs-on: ubuntu-latest
steps:
# Comment on, then close issues that haven't been updated for ages
- name: Close Stale Issues
uses: actions/stale@v4
with:
repo-token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
days-before-stale: 183
days-before-close: 7
operations-per-run: 30
remove-stale-when-updated: true
enable-statistics: true
stale-issue-message: >
This issue has gone 6 months without an update. To keep the ticket open, please indicate that it is still relevant in a comment below.
Otherwise it will be closed in 7 days.
stale-pr-message: >
This PR is stale because it has been open 6 months with no activity. Either remove the stale label or comment below with a short update,
otherwise this PR will be closed in 7 days.
close-issue-message: >
This issue was automatically closed because it has been stalled for over 6 months with no activity.
close-pr-message: >
This pull request was automatically closed because it has been stalled for over 6 months with no activity.
stale-issue-label: '⚰️ Stale'
close-issue-label: '🕸️ Inactive'
stale-pr-label: '⚰️ Stale'
close-pr-label: '🕸️ Inactive'
exempt-issue-labels: '📌 Keep Open'
exempt-pr-labels: '📌 Keep Open'
labels-to-add-when-unstale: '📌 Keep Open'
# Comment on, then close issues that required a response from the user, but didn't get one
- name: Close Issues without Response
uses: actions/stale@v4
with:
repo-token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
days-before-stale: 7
days-before-close: 7
operations-per-run: 30
remove-stale-when-updated: true
stale-issue-message: >
Hi! Looks like additional info is required for this issue to be addressed.
Don't forget to provide this within the next few days to keep your ticket open.
close-issue-message: 'Issue closed due to no response from user.'
only-labels: '🚏 Awaiting User Response'
labels-to-remove-when-unstale: '🚏 Awaiting User Response, 🛑 No Response'
stale-issue-label: '🛑 No Response'
close-issue-label: '🕸️ Inactive'
exempt-issue-labels: '📌 Keep Open'
exempt-pr-labels: '📌 Keep Open'
# Comment on issues that we should have replied to
- name: Notify Repo Owner to Respond
uses: actions/stale@v4
with:
repo-token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
days-before-stale: 7
days-before-close: 183
operations-per-run: 30
remove-stale-when-updated: true
stale-issue-message: Hey SillyTavern, - Don't forget to respond!
stale-pr-message: Hey SillyTavern, - Don't forget to respond!
only-labels: '👤 Awaiting Maintainer Response'
labels-to-remove-when-unstale: '👤 Awaiting Maintainer Response'
close-issue-message: 'Closed due to no response from repo author for over a year'
close-pr-message: 'Closed due to no response from repo author for over a year'
stale-issue-label: '👤 Awaiting Maintainer Response'
stale-pr-label: '👤 Awaiting Maintainer Response'
close-issue-label: '🕸️ Inactive'
close-pr-label: '🕸️ Inactive'
exempt-issue-labels: '📌 Keep Open'
exempt-pr-labels: '📌 Keep Open'

View File

@@ -1,39 +0,0 @@
# Adds a comment to new PRs, showing the compressed size and size difference of new code
# And also labels the PR based on the number of lines changes
name: 🌈 Check PR Size
on: [pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
# Find and comment with compressed size
- name: Get Compressed Size
uses: preactjs/compressed-size-action@v2
with:
repo-token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
pattern: './dist/**/*.{js,css,html}'
strip-hash: '\\b\\w{8}\\.'
exclude: '**/node_modules/**'
minimum-change-threshold: 100
# Check number of lines of code added
- name: Label based on Lines of Code
uses: codelytv/pr-size-labeler@v1
with:
GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
xs_max_size: '10'
s_max_size: '100'
m_max_size: '500'
l_max_size: '1000'
s_label: '🟩 PR - Small'
m_label: '🟨 PR - Medium'
l_label: '🟧 PR - Large'
xl_label: '🟥 PR - XL'
fail_if_xl: 'false'
message_if_xl: >
It looks like this PR is very large (over 1000 lines).
Try to avoid addressing multiple issues in a single PR, and
in the future consider breaking large tasks down into smaller steps.
This it to make reviewing, testing, reverting and general quality management easier.

View File

@@ -0,0 +1,116 @@
name: 🛠️ Issues Manager
on:
issues:
types: [opened, edited, labeled, unlabeled]
# Re also listen to comments, to remove stale labels right away
issue_comment:
types: [created]
permissions:
contents: read
issues: write
jobs:
label-on-content:
name: 🏷️ Label Issues by Content
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
# Checkout
# https://github.com/marketplace/actions/checkout
uses: actions/checkout@v4.2.2
- name: Auto-Label Issues (Based on Issue Content)
# only auto label based on issue content once, on open (to prevent re-labeling removed labels)
if: github.event.action == 'opened'
# Issue Labeler
# https://github.com/marketplace/actions/regex-issue-labeler
uses: github/issue-labeler@v3.4
with:
configuration-path: .github/issues-auto-labels.yml
enable-versioned-regex: 0
repo-token: ${{ secrets.GITHUB_TOKEN }}
label-on-labels:
name: 🏷️ Label Issues by Labels
runs-on: ubuntu-latest
steps:
- name: ✅ Add "👍 Approved" for relevant labels
if: contains(fromJSON('["👩‍💻 Good First Issue", "🙏 Help Wanted", "🪲 Confirmed", "⚠️ High Priority", "❕ Medium Priority", "💤 Low Priority"]'), github.event.label.name)
# 🤖 Issues Helper
# https://github.com/marketplace/actions/issues-helper
uses: actions-cool/issues-helper@v3.6.0
with:
actions: 'add-labels'
token: ${{ secrets.GITHUB_TOKEN }}
labels: '👍 Approved'
- name: ❌ Remove progress labels when issue is marked done or stale
if: contains(fromJSON('["✅ Done", "✅ Done (staging)", "⚰️ Stale", "❌ wontfix"]'), github.event.label.name)
# 🤖 Issues Helper
# https://github.com/marketplace/actions/issues-helper
uses: actions-cool/issues-helper@v3.6.0
with:
actions: 'remove-labels'
token: ${{ secrets.GITHUB_TOKEN }}
labels: '🧑‍💻 In Progress,🤔 Unsure,🤔 Under Consideration'
- name: ❌ Remove temporary labels when confirmed labels are added
if: contains(fromJSON('["❌ wontfix","👍 Approved","👩‍💻 Good First Issue"]'), github.event.label.name)
# 🤖 Issues Helper
# https://github.com/marketplace/actions/issues-helper
uses: actions-cool/issues-helper@v3.6.0
with:
actions: 'remove-labels'
token: ${{ secrets.GITHUB_TOKEN }}
labels: '🤔 Unsure,🤔 Under Consideration'
- name: ❌ Remove no bug labels when "🪲 Confirmed" is added
if: github.event.label.name == '🪲 Confirmed'
# 🤖 Issues Helper
# https://github.com/marketplace/actions/issues-helper
uses: actions-cool/issues-helper@v3.6.0
with:
actions: 'remove-labels'
token: ${{ secrets.GITHUB_TOKEN }}
labels: '✖️ Not Reproducible,✖️ Not A Bug'
remove-stale-label:
name: 🗑️ Remove Stale Label on Comment
runs-on: ubuntu-latest
# Only run this on new comments, to automatically remove the stale label
if: github.event_name == 'issue_comment' && github.actor != 'github-actions[bot]'
steps:
- name: Remove Stale Label
# 🤖 Issues Helper
# https://github.com/marketplace/actions/issues-helper
uses: actions-cool/issues-helper@v3.6.0
with:
actions: 'remove-labels'
token: ${{ secrets.GITHUB_TOKEN }}
issue-number: ${{ github.event.issue.number }}
labels: '⚰️ Stale,🕸️ Inactive,🚏 Awaiting User Response,🛑 No Response'
write-auto-comments:
name: 💬 Post Issue Comments Based on Labels
needs: [label-on-content, label-on-labels]
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
# Checkout
# https://github.com/marketplace/actions/checkout
uses: actions/checkout@v4.2.2
- name: Post Issue Comments Based on Labels
# Label Commenter
# https://github.com/marketplace/actions/label-commenter
uses: peaceiris/actions-label-commenter@v1.10.0
with:
config_file: .github/issues-auto-comments.yml
github_token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -0,0 +1,45 @@
name: 🔄 Update Issues on Push
on:
push:
branches:
- staging
- release
permissions:
contents: read
issues: write
jobs:
# This runs commits to staging/release, reading the commit messages. Check `pr-auto-manager.yml`:`update-linked-issues` for PR-linked updates.
update-linked-issues:
name: 🔗 Mark Linked Issues Done on Push
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
# Checkout
# https://github.com/marketplace/actions/checkout
uses: actions/checkout@v4.2.2
- name: Extract Linked Issues from Commit Message
id: extract_issues
run: |
ISSUES=$(git log ${{ github.event.before }}..${{ github.event.after }} --pretty=%B | grep -oiE '(close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved) #([0-9]+)' | awk '{print $2}' | tr -d '#' | jq -R -s -c 'split("\n")[:-1]')
echo "issues=$ISSUES" >> $GITHUB_ENV
- name: Label Linked Issues
id: label_linked_issues
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
for ISSUE in $(echo $issues | jq -r '.[]'); do
if [ "${{ github.ref }}" == "refs/heads/staging" ]; then
LABEL="✅ Done (staging)"
gh issue edit $ISSUE -R ${{ github.repository }} --add-label "$LABEL" --remove-label "🧑‍💻 In Progress"
elif [ "${{ github.ref }}" == "refs/heads/release" ]; then
LABEL="✅ Done"
gh issue edit $ISSUE -R ${{ github.repository }} --add-label "$LABEL" --remove-label "🧑‍💻 In Progress"
fi
echo "Added label '$LABEL' (and removed '🧑‍💻 In Progress' if present) in issue #$ISSUE"
done

100
.github/workflows/job-close-stale.yml vendored Normal file
View File

@@ -0,0 +1,100 @@
name: 🕒 Close Stale Issues/PRs Workflow
on:
# Run the workflow every day
workflow_dispatch:
schedule:
- cron: '0 0 * * *' # Runs every day at midnight UTC
permissions:
contents: read
issues: write
pull-requests: write
jobs:
mark-inactivity:
name: ⏳ Mark Issues/PRs without Activity
runs-on: ubuntu-latest
steps:
- name: Mark Issues/PRs without Activity
# Close Stale Issues and PRs
# https://github.com/marketplace/actions/close-stale-issues
uses: actions/stale@v9.1.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 183
days-before-close: 7
operations-per-run: 30
remove-stale-when-updated: true
enable-statistics: true
stale-issue-message: >
⏳ This issue has been inactive for 6 months. If it's still relevant, drop a comment below to keep it open.
Otherwise, it will be auto-closed in 7 days.
stale-pr-message: >
⏳ This PR has been inactive for 6 months. If it's still relevant, update it or remove the stale label.
Otherwise, it will be auto-closed in 7 days.
close-issue-message: >
🔒 This issue was auto-closed due to inactivity for over 6 months.
close-pr-message: >
🔒 This PR was auto-closed due to inactivity for over 6 months.
stale-issue-label: '⚰️ Stale'
close-issue-label: '🕸️ Inactive'
stale-pr-label: '⚰️ Stale'
close-pr-label: '🕸️ Inactive'
exempt-issue-labels: '📌 Keep Open'
exempt-pr-labels: '📌 Keep Open'
await-user-response:
name: ⚠️ Mark Issues/PRs Awaiting User Response
runs-on: ubuntu-latest
needs: mark-inactivity
steps:
- name: Mark Issues/PRs Awaiting User Response
# Close Stale Issues and PRs
# https://github.com/marketplace/actions/close-stale-issues
uses: actions/stale@v9.1.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 7
days-before-close: 7
operations-per-run: 30
remove-stale-when-updated: true
stale-issue-message: >
⚠️ Hey! We need some more info to move forward with this issue.
Please provide the requested details in the next few days to keep this ticket open.
close-issue-message: >
🔒 This issue was auto-closed due to no response from user.
only-labels: '🚏 Awaiting User Response'
labels-to-remove-when-unstale: '🚏 Awaiting User Response'
stale-issue-label: '🛑 No Response'
close-issue-label: '🕸️ Inactive'
exempt-issue-labels: '🚧 Alternative Exists'
alternative-exists:
name: 🔄 Mark Issues with Alternative Exists
runs-on: ubuntu-latest
needs: await-user-response
steps:
- name: Mark Issues with Alternative Exists
# Close Stale Issues and PRs
# https://github.com/marketplace/actions/close-stale-issues
uses: actions/stale@v9.1.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 7
days-before-close: 7
operations-per-run: 30
remove-stale-when-updated: true
stale-issue-message: >
🔄 An alternative solution has been provided for this issue.
Did this solve your problem? If so, we'll go ahead and close it.
If you still need help, drop a comment within the next 7 days to keep this open.
close-issue-message: >
✅ Closing this issue due to no confirmation on the alternative solution.
only-labels: '🚧 Alternative Exists'
stale-issue-label: '🚏 Awaiting User Response'
close-issue-label: '🕸️ Inactive'
exempt-issue-labels: '📌 Keep Open'

View File

@@ -1,19 +0,0 @@
name: "Issue Labeler"
on:
issues:
types: [opened, edited]
permissions:
issues: write
contents: read
jobs:
triage:
runs-on: ubuntu-latest
steps:
- uses: github/issue-labeler@v3.4
with:
configuration-path: .github/labeler.yml
# not-before: 2020-01-15T02:54:32Z # optional and will result in any issues prior to this timestamp to be ignored.
enable-versioned-regex: 0
repo-token: ${{ github.token }}

View File

@@ -1,17 +0,0 @@
# When a new comment is added to an issue, if it had the Stale or Awaiting User Response labels, then those labels will be removed
name: 🎯 Remove Pending Labels on Close
on:
issues:
types: [closed]
jobs:
remove-labels:
runs-on: ubuntu-latest
steps:
- name: Remove Labels when Closed
uses: actions-cool/issues-helper@v2
with:
actions: remove-labels
token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
issue-number: ${{ github.event.issue.number }}
labels: '🚏 Awaiting User Response,⚰️ Stale,👤 Awaiting Maintainer Response'

View File

@@ -1,42 +0,0 @@
# When a new comment is added to an issue, if it had the Stale or Awaiting User Response labels, then those labels will be removed
name: 🎯 Add/ Remove Awaiting Response Labels
on:
issue_comment:
types: [created]
jobs:
remove-stale:
runs-on: ubuntu-latest
if: ${{ github.event.comment.author_association != 'COLLABORATOR' && github.event.comment.author_association != 'OWNER' }}
steps:
- name: Remove Stale labels when Updated
uses: actions-cool/issues-helper@v2
with:
actions: remove-labels
token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
issue-number: ${{ github.event.issue.number }}
labels: '🚏 Awaiting User Response,⚰️ Stale'
add-awaiting-author:
runs-on: ubuntu-latest
if: ${{!github.event.issue.pull_request && github.event.comment.author_association != 'COLLABORATOR' && github.event.comment.author_association != 'OWNER' && github.event.issue.state == 'open' }}
steps:
- name: Add Awaiting Author labels when Updated
uses: actions-cool/issues-helper@v2
with:
actions: add-labels
token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
issue-number: ${{ github.event.issue.number }}
labels: '👤 Awaiting Maintainer Response'
remove-awaiting-author:
runs-on: ubuntu-latest
if: ${{ github.event.comment.author_association == 'OWNER' }}
steps:
- name: Remove Awaiting Author labels when Updated
uses: actions-cool/issues-helper@v2
with:
actions: remove-labels
token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
issue-number: ${{ github.event.issue.number }}
labels: '👤 Awaiting Maintainer Response'

28
.github/workflows/on-close-handler.yml vendored Normal file
View File

@@ -0,0 +1,28 @@
name: 🚪 Issues/PRs On Close Handler
on:
issues:
types: [closed]
pull_request_target:
types: [closed]
permissions:
contents: read
issues: write
pull-requests: write
jobs:
remove-labels:
name: 🗑️ Remove Pending Labels on Close
runs-on: ubuntu-latest
steps:
- name: Remove Pending Labels on Close
# 🤖 Issues Helper
# https://github.com/marketplace/actions/issues-helper
uses: actions-cool/issues-helper@v3.6.0
with:
actions: remove-labels
token: ${{ secrets.GITHUB_TOKEN }}
issue-number: ${{ github.event.issue.number || github.event.pull_request.number }}
labels: '🚏 Awaiting User Response,🧑‍💻 In Progress,📌 Keep Open,🚫 Merge Conflicts,🔬 Needs Testing,🔨 Needs Work,⚰️ Stale,⛔ Waiting For External/Upstream'

29
.github/workflows/on-open-handler.yml vendored Normal file
View File

@@ -0,0 +1,29 @@
name: 📨 Issues/PRs Open Handler
on:
issues:
types: [opened]
pull_request_target:
types: [opened]
permissions:
contents: read
issues: write
pull-requests: write
jobs:
label-maintainer:
name: 🏷️ Label if Author is a Repo Maintainer
runs-on: ubuntu-latest
if: contains(fromJson('["Cohee1207", "RossAscends", "Wolfsblvt"]'), github.actor)
steps:
- name: Label if Author is a Repo Maintainer
# 🤖 Issues Helper
# https://github.com/marketplace/actions/issues-helper
uses: actions-cool/issues-helper@v3.6.0
with:
actions: 'add-labels'
token: ${{ secrets.GITHUB_TOKEN }}
issue-number: ${{ github.event.issue.number || github.event.pull_request.number }}
labels: '👷 Maintainer'

269
.github/workflows/pr-auto-manager.yml vendored Normal file
View File

@@ -0,0 +1,269 @@
name: 🔀 Pull Request Manager
on:
workflow_dispatch: # Allow to manually call this workflow
pull_request_target:
types: [opened, synchronize, reopened, edited, labeled, unlabeled, closed]
pull_request_review_comment:
types: [created]
permissions:
contents: read
pull-requests: write
jobs:
run-eslint:
name: ✅ Check ESLint on PR
runs-on: ubuntu-latest
# Only needs to run when code is changed
if: github.event.action == 'opened' || github.event.action == 'synchronize'
# Override permissions, linter likely needs write access to issues
permissions:
contents: read
issues: write
pull-requests: write
steps:
- name: Checkout Repository
# Checkout
# https://github.com/marketplace/actions/checkout
uses: actions/checkout@v4.2.2
with:
ref: ${{ github.head_ref }}
- name: Setup Node.js
# Setup Node.js environment
# https://github.com/marketplace/actions/setup-node-js-environment
uses: actions/setup-node@v4.3.0
with:
node-version: 20
- name: Run npm install
run: npm ci
- name: Run ESLint
# Action ESLint
# https://github.com/marketplace/actions/action-eslint
uses: sibiraj-s/action-eslint@v3.0.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
eslint-args: '--ignore-path=.gitignore --quiet'
extensions: 'js'
annotations: true
ignore-patterns: |
dist/
lib/
label-by-size:
name: 🏷️ Label PR by Size
# This job should run after all others, to prevent possible concurrency issues
needs: [label-by-branches, label-by-files, remove-stale-label, check-merge-blocking-labels, write-auto-comments]
runs-on: ubuntu-latest
# Only needs to run when code is changed
if: always() && (github.event.action == 'opened' || github.event.action == 'synchronize')
# Override permissions, the labeler needs issues write access
permissions:
contents: read
issues: write
pull-requests: write
steps:
- name: Label PR Size
# Pull Request Size Labeler
# https://github.com/marketplace/actions/pull-request-size-labeler
uses: codelytv/pr-size-labeler@v1.10.2
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
xs_label: '🟩 ⬤○○○○'
xs_max_size: '20'
s_label: '🟩 ⬤⬤○○○'
s_max_size: '100'
m_label: '🟨 ⬤⬤⬤○○'
m_max_size: '500'
l_label: '🟧 ⬤⬤⬤⬤○'
l_max_size: '1000'
xl_label: '🟥 ⬤⬤⬤⬤⬤'
fail_if_xl: 'false'
files_to_ignore: |
"package-lock.json"
"public/lib/*"
label-by-branches:
name: 🏷️ Label PR by Branches
runs-on: ubuntu-latest
# Only label once when PR is created or when base branch is changed, to allow manual label removal
if: github.event.action == 'opened' || (github.event.action == 'synchronize' && github.event.changes.base)
steps:
- name: Checkout Repository
# Checkout
# https://github.com/marketplace/actions/checkout
uses: actions/checkout@v4.2.2
- name: Apply Labels Based on Branch Name and Target Branch
# Pull Request Labeler
# https://github.com/marketplace/actions/labeler
uses: actions/labeler@v5.0.0
with:
configuration-path: .github/pr-auto-labels-by-branch.yml
repo-token: ${{ secrets.GITHUB_TOKEN }}
label-by-files:
name: 🏷️ Label PR by Files
runs-on: ubuntu-latest
# Only needs to run when code is changed
if: github.event.action == 'opened' || github.event.action == 'synchronize'
steps:
- name: Checkout Repository
# Checkout
# https://github.com/marketplace/actions/checkout
uses: actions/checkout@v4.2.2
- name: Apply Labels Based on Changed Files
# Pull Request Labeler
# https://github.com/marketplace/actions/labeler
uses: actions/labeler@v5.0.0
with:
configuration-path: .github/pr-auto-labels-by-files.yml
repo-token: ${{ secrets.GITHUB_TOKEN }}
remove-stale-label:
name: 🗑️ Remove Stale Label on Comment
runs-on: ubuntu-latest
# Only runs on comments not done by the github actions bot
if: github.event_name == 'pull_request_review_comment' && github.actor != 'github-actions[bot]'
# Override permissions, issue labeler needs issues write access
permissions:
contents: read
issues: write
pull-requests: write
steps:
- name: Remove Stale Label
# 🤖 Issues Helper
# https://github.com/marketplace/actions/issues-helper
uses: actions-cool/issues-helper@v3.6.0
with:
actions: 'remove-labels'
token: ${{ secrets.GITHUB_TOKEN }}
issue-number: ${{ github.event.pull_request.number }}
labels: '⚰️ Stale'
check-merge-blocking-labels:
name: 🚫 Check Merge Blocking Labels
needs: [label-by-branches, label-by-files]
runs-on: ubuntu-latest
# Run, even if the previous jobs were skipped/failed
if: always()
# Override permissions, as this needs to write a check
permissions:
checks: write
contents: read
pull-requests: read
steps:
- name: Check Merge Blocking
# GitHub Script
# https://github.com/marketplace/actions/github-script
id: label-check
uses: actions/github-script@v7.0.1
with:
script: |
const prLabels = context.payload.pull_request.labels.map(label => label.name);
const blockingLabels = [
"⛔ Don't Merge",
"🔨 Needs Work",
"🔬 Needs Testing",
"⛔ Waiting For External/Upstream",
"❗ Against Release Branch",
"💥💣 Breaking Changes"
];
const hasBlockingLabel = prLabels.some(label => blockingLabels.includes(label));
if (hasBlockingLabel) {
console.log("Blocking label detected. Setting warning status.");
await github.rest.checks.create({
owner: context.repo.owner,
repo: context.repo.repo,
name: "PR Label Warning",
head_sha: context.payload.pull_request.head.sha,
status: "completed",
conclusion: "neutral",
output: {
title: "Potential Merge Issue",
summary: "This PR has a merge-blocking label. Proceed with caution."
}
});
} else {
console.log("No merge-blocking labels found.");
}
write-auto-comments:
name: 💬 Post PR Comments Based on Labels
needs: [label-by-branches, label-by-files]
runs-on: ubuntu-latest
# Run, even if the previous jobs were skipped/failed
if: always()
steps:
- name: Checkout Repository
# Checkout
# https://github.com/marketplace/actions/checkout
uses: actions/checkout@v4.2.2
- name: Post PR Comments Based on Labels
# Label Commenter for PRs
# https://github.com/marketplace/actions/label-commenter
uses: peaceiris/actions-label-commenter@v1.10.0
with:
config_file: .github/pr-auto-comments.yml
github_token: ${{ secrets.GITHUB_TOKEN }}
# This runs on merged PRs to staging, reading the PR body and directly linked issues. Check `issues-updates-on-merge.yml`:`update-linked-issues` for commit-based updates.
update-linked-issues:
name: 🔗 Mark Linked Issues Done on Staging Merge
runs-on: ubuntu-latest
if: github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'staging'
# Override permissions, We need to be able to write to issues
permissions:
contents: read
issues: write
pull-requests: write
steps:
- name: Extract Linked Issues From PR Description
id: extract_issues
run: |
ISSUES=$(jq -r '.pull_request.body' "$GITHUB_EVENT_PATH" | grep -oiE '(close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved) #([0-9]+)' | awk '{print $2}' | tr -d '#' | jq -R -s -c 'split("\n")[:-1]')
echo "issues=$ISSUES" >> $GITHUB_ENV
- name: Fetch Directly Linked Issues
id: fetch_linked_issues
run: |
PR_NUMBER=${{ github.event.pull_request.number }}
REPO=${{ github.repository }}
API_URL="https://api.github.com/repos/$REPO/pulls/$PR_NUMBER/issues"
ISSUES=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" "$API_URL" | jq -r '.[].number' | jq -R -s -c 'split("\n")[:-1]')
echo "linked_issues=$ISSUES" >> $GITHUB_ENV
- name: Merge Issue Lists
id: merge_issues
run: |
ISSUES=$(jq -c -n --argjson a "$issues" --argjson b "$linked_issues" '$a + $b | unique')
echo "final_issues=$ISSUES" >> $GITHUB_ENV
- name: Label Linked Issues
id: label_linked_issues
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
for ISSUE in $(echo $final_issues | jq -r '.[]'); do
gh issue edit $ISSUE -R ${{ github.repository }} --add-label "✅ Done (staging)" --remove-label "🧑‍💻 In Progress"
echo "Added label '✅ Done (staging)' (and removed '🧑‍💻 In Progress' if present) in issue #$ISSUE"
done

View File

@@ -0,0 +1,28 @@
name: ⚔️ Check Merge Conflicts
on:
# So that PRs touching the same files as the push are updated
push:
# So that the `dirtyLabel` is removed if conflicts are resolved
pull_request_target:
types: [synchronize]
permissions:
contents: read
pull-requests: write
jobs:
check-merge-conflicts:
name: ⚔️ Check Merge Conflicts
runs-on: ubuntu-latest
steps:
- name: Check Merge Conflicts
# Label Conflicting Pull Requests
# https://github.com/marketplace/actions/label-conflicting-pull-requests
uses: eps1lon/actions-label-merge-conflict@v3.0.3
with:
dirtyLabel: '🚫 Merge Conflicts'
repoToken: ${{ secrets.GITHUB_TOKEN }}
commentOnDirty: >
⚠️ This PR has conflicts that need to be resolved before it can be merged.

View File

@@ -114,6 +114,8 @@ backups:
chat:
# Enable automatic chat backups
enabled: true
# Verify integrity of chat files before saving
checkIntegrity: true
# Maximum number of chat backups to keep per user (starting from the most recent). Set to -1 to keep all backups.
maxTotalBackups: -1
# Interval in milliseconds to throttle chat backups per user
@@ -140,6 +142,8 @@ performance:
lazyLoadCharacters: false
# The maximum amount of memory that parsed character cards can use. Set to 0 to disable memory caching.
memoryCacheCapacity: '100mb'
# Enables disk caching for character cards. Improves performances with large card libraries.
useDiskCache: true
# Allow secret keys exposure via API
allowKeysExposure: false

View File

@@ -563,6 +563,10 @@
"filename": "presets/context/Llama 3 Instruct.json",
"type": "context"
},
{
"filename": "presets/context/Llama 4 Instruct.json",
"type": "context"
},
{
"filename": "presets/context/Phi.json",
"type": "context"
@@ -663,6 +667,10 @@
"filename": "presets/instruct/Llama 3 Instruct.json",
"type": "instruct"
},
{
"filename": "presets/instruct/Llama 4 Instruct.json",
"type": "instruct"
},
{
"filename": "presets/instruct/Phi.json",
"type": "instruct"
@@ -786,5 +794,13 @@
{
"filename": "presets/context/DeepSeek-V2.5.json",
"type": "context"
},
{
"filename": "presets/reasoning/DeepSeek.json",
"type": "reasoning"
},
{
"filename": "presets/reasoning/Blank.json",
"type": "reasoning"
}
]

View File

@@ -0,0 +1,11 @@
{
"story_string": "<|begin_of_text|><|header_start|>system<|header_end|>\n\n{{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}{{trim}}<|eot|>",
"example_separator": "",
"chat_start": "",
"use_stop_strings": false,
"allow_jailbreak": false,
"always_force_name2": true,
"trim_sentences": false,
"single_line": false,
"name": "Llama 4 Instruct"
}

View File

@@ -16,7 +16,7 @@
"input_suffix": "<|eot_id|>",
"system_suffix": "<|eot_id|>",
"user_alignment_message": "",
"system_same_as_user": true,
"system_same_as_user": false,
"last_system_sequence": "",
"name": "Llama 3 Instruct"
}

View File

@@ -0,0 +1,22 @@
{
"input_sequence": "<|header_start|>user<|header_end|>\n\n",
"output_sequence": "<|header_start|>assistant<|header_end|>\n\n",
"last_output_sequence": "",
"system_sequence": "<|header_start|>system<|header_end|>\n\n",
"stop_sequence": "<|eot|>",
"wrap": false,
"macro": true,
"names_behavior": "always",
"activation_regex": "",
"system_sequence_prefix": "",
"system_sequence_suffix": "",
"first_output_sequence": "",
"skip_examples": false,
"output_suffix": "<|eot|>",
"input_suffix": "<|eot|>",
"system_suffix": "<|eot|>",
"user_alignment_message": "",
"system_same_as_user": false,
"last_system_sequence": "",
"name": "Llama 4 Instruct"
}

View File

@@ -32,7 +32,7 @@
"new_chat_prompt": "[Start a new Chat]",
"new_group_chat_prompt": "[Start a new group chat. Group members: {{group}}]",
"new_example_chat_prompt": "[Example Chat]",
"continue_nudge_prompt": "[Continue the following message. Do not include ANY parts of the original message. Use capitalization and punctuation as if your reply is a part of the original message: {{lastChatMessage}}]",
"continue_nudge_prompt": "[Continue your last message without repeating its original content.]",
"bias_preset_selected": "Default (none)",
"reverse_proxy": "",
"proxy_password": "",

View File

@@ -0,0 +1,6 @@
{
"name": "Blank",
"prefix": "",
"suffix": "",
"separator": ""
}

View File

@@ -0,0 +1,6 @@
{
"name": "DeepSeek",
"prefix": "<think>\n",
"suffix": "\n</think>",
"separator": "\n\n"
}

View File

@@ -593,7 +593,7 @@
"new_chat_prompt": "[Start a new Chat]",
"new_group_chat_prompt": "[Start a new group chat. Group members: {{group}}]",
"new_example_chat_prompt": "[Example Chat]",
"continue_nudge_prompt": "[Continue the following message. Do not include ANY parts of the original message. Use capitalization and punctuation as if your reply is a part of the original message: {{lastChatMessage}}]",
"continue_nudge_prompt": "[Continue your last message without repeating its original content.]",
"bias_preset_selected": "Default (none)",
"bias_presets": {
"Default (none)": [],

View File

@@ -2,7 +2,7 @@
"compilerOptions": {
"module": "ESNext",
"target": "ES2023",
"moduleResolution": "Node",
"moduleResolution": "Bundler",
"strictNullChecks": true,
"strictFunctionTypes": true,
"checkJs": true,

2095
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,8 +3,29 @@
"@adobe/css-tools": "^4.4.2",
"@agnai/sentencepiece-js": "^1.1.1",
"@agnai/web-tokenizers": "^0.1.3",
"@huggingface/transformers": "^3.4.2",
"@iconfu/svg-inject": "^1.2.3",
"@mozilla/readability": "^0.5.0",
"@jimp/core": "^1.6.0",
"@jimp/js-bmp": "^1.6.0",
"@jimp/js-gif": "^1.6.0",
"@jimp/js-tiff": "^1.6.0",
"@jimp/plugin-circle": "^1.6.0",
"@jimp/plugin-color": "^1.6.0",
"@jimp/plugin-contain": "^1.6.0",
"@jimp/plugin-cover": "^1.6.0",
"@jimp/plugin-crop": "^1.6.0",
"@jimp/plugin-displace": "^1.6.0",
"@jimp/plugin-fisheye": "^1.6.0",
"@jimp/plugin-flip": "^1.6.0",
"@jimp/plugin-mask": "^1.6.0",
"@jimp/plugin-quantize": "^1.6.0",
"@jimp/plugin-rotate": "^1.6.0",
"@jimp/plugin-threshold": "^1.6.0",
"@jimp/wasm-avif": "^1.6.0",
"@jimp/wasm-jpeg": "^1.6.0",
"@jimp/wasm-png": "^1.6.0",
"@jimp/wasm-webp": "^1.6.0",
"@mozilla/readability": "^0.6.0",
"@popperjs/core": "^2.11.8",
"@zeldafan0225/ai_horde": "^5.2.0",
"archiver": "^7.0.1",
@@ -18,6 +39,7 @@
"cookie-parser": "^1.4.6",
"cookie-session": "^2.1.0",
"cors": "^2.8.5",
"crc": "^4.3.2",
"csrf-sync": "^4.0.3",
"diff-match-patch": "^1.0.5",
"dompurify": "^3.2.4",
@@ -36,7 +58,6 @@
"ip-regex": "^5.0.0",
"ipaddr.js": "^2.2.0",
"is-docker": "^3.0.0",
"jimp": "^0.22.10",
"localforage": "^1.10.0",
"lodash": "^4.17.21",
"mime-types": "^2.1.35",
@@ -47,7 +68,6 @@
"node-persist": "^4.0.4",
"open": "^8.4.2",
"png-chunk-text": "^1.0.0",
"png-chunks-encode": "^1.0.0",
"png-chunks-extract": "^1.0.0",
"proxy-agent": "^6.5.0",
"rate-limiter-flexible": "^5.0.5",
@@ -55,7 +75,6 @@
"sanitize-filename": "^1.6.3",
"seedrandom": "^3.0.5",
"showdown": "^2.1.0",
"sillytavern-transformers": "2.14.6",
"simple-git": "^3.27.0",
"slidetoggle": "^4.0.0",
"tiktoken": "^1.0.20",
@@ -81,6 +100,9 @@
},
"node-fetch": {
"whatwg-url": "^14.0.0"
},
"@huggingface/transformers": {
"onnxruntime-node": "https://github.com/Cohee1207/onnxruntime/releases/download/1.20.1/onnxruntime-node-1.20.1.tgz"
}
},
"name": "sillytavern",
@@ -90,11 +112,11 @@
"type": "git",
"url": "https://github.com/SillyTavern/SillyTavern.git"
},
"version": "1.12.12",
"version": "1.12.13",
"scripts": {
"start": "node server.js",
"debug": "node --inspect server.js",
"electron": "electron ./src/electron",
"start:electron": "cd ./src/electron && npm run start",
"start:deno": "deno run --allow-run --allow-net --allow-read --allow-write --allow-sys --allow-env server.js",
"start:bun": "bun server.js",
"start:no-csrf": "node server.js --disableCsrf",
@@ -125,14 +147,13 @@
"@types/jquery": "^3.5.32",
"@types/jquery-cropper": "^1.0.4",
"@types/jquery.transit": "^0.9.33",
"@types/jqueryui": "^1.12.23",
"@types/jqueryui": "^1.12.24",
"@types/lodash": "^4.17.16",
"@types/mime-types": "^2.1.4",
"@types/multer": "^1.4.12",
"@types/node": "^18.19.78",
"@types/node": "^18.19.80",
"@types/node-persist": "^3.1.8",
"@types/png-chunk-text": "^1.0.3",
"@types/png-chunks-encode": "^1.0.2",
"@types/png-chunks-extract": "^1.0.2",
"@types/response-time": "^2.3.8",
"@types/select2": "^4.0.63",
@@ -140,6 +161,7 @@
"@types/write-file-atomic": "^4.0.3",
"@types/yargs": "^17.0.33",
"@types/yauzl": "^2.10.3",
"eslint": "^8.57.1"
"eslint": "^8.57.1",
"eslint-plugin-jsdoc": "^48.10.0"
}
}

View File

@@ -101,15 +101,12 @@ const keyMigrationMap = [
newKey: 'performance.memoryCacheCapacity',
migrate: (value) => `${value}mb`,
},
// uncomment one release after 1.12.13
/*
{
oldKey: 'cookieSecret',
newKey: 'cookieSecret',
migrate: () => void 0,
remove: true,
},
*/
];
/**
@@ -335,7 +332,7 @@ try {
// 1. Create default config files
createDefaultFiles();
// 2. Copy transformers WASM binaries from node_modules
copyWasmFiles();
// copyWasmFiles();
// 3. Add missing config values
addMissingConfigValues();
} catch (error) {

View File

@@ -146,3 +146,15 @@ input.extension_missing[type="checkbox"] {
.extensions_info .extension_actions {
flex-wrap: nowrap;
}
.extensions_toolbar {
top: 0;
position: sticky;
display: flex;
flex-direction: row;
background-color: var(--SmartThemeBlurTintColor);
gap: 5px;
z-index: 1;
margin-bottom: 10px;
padding: 5px;
}

View File

@@ -1,6 +1,5 @@
.scrollable-buttons-container {
max-height: 50vh; /* Use viewport height instead of fixed pixels */
overflow-y: auto;
-webkit-overflow-scrolling: touch; /* Momentum scrolling on iOS */
margin-top: 1rem; /* m-t-1 is equivalent to margin-top: 1rem; */
flex-shrink: 1;

View File

@@ -13,6 +13,7 @@
backdrop-filter: blur(calc(var(--SmartThemeBlurStrength)*2));
color: var(--SmartThemeBodyColor);
z-index: 40000;
user-select: none;
}
.select2-container .select2-selection .select2-selection__clear {

29
public/global.d.ts vendored
View File

@@ -1,18 +1,25 @@
import libs from './lib';
import getContext from './scripts/st-context';
// Global namespace modules
declare var ai;
declare var pdfjsLib;
declare var ePub;
declare var SillyTavern: {
getContext(): typeof getContext;
llm: any;
libs: typeof libs;
};
import { power_user } from './scripts/power-user';
declare global {
// Custom types
declare type InstructSettings = typeof power_user.instruct;
// Global namespace modules
interface Window {
ai: any;
}
declare var pdfjsLib;
declare var ePub;
declare var SillyTavern: {
getContext(): typeof getContext;
llm: any;
libs: typeof libs;
};
// Jquery plugins
interface JQuery {
nanogallery2(options?: any): JQuery;

46
public/img/xai.svg Normal file
View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 27.5.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
version="1.1"
id="katman_1"
x="0px"
y="0px"
viewBox="0 0 438.67001 481.44999"
xml:space="preserve"
sodipodi:docname="XAI_Logo.svg"
width="438.67001"
height="481.45001"
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="defs4" /><sodipodi:namedview
id="namedview4"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="0.39645207"
inkscape:cx="219.44645"
inkscape:cy="238.36425"
inkscape:window-width="1512"
inkscape:window-height="856"
inkscape:window-x="0"
inkscape:window-y="38"
inkscape:window-maximized="1"
inkscape:current-layer="katman_1" />&#10;<g
id="g4"
transform="translate(-201.61,-56.91)">&#10; <polygon
points="631.96,538.36 640.28,93.18 557.09,211.99 565.4,538.36 "
id="polygon1" />&#10; <polygon
points="379.35,284.53 430.13,357.05 640.28,56.91 538.72,56.91 "
id="polygon2" />&#10; <polygon
points="353.96,465.84 303.17,393.31 201.61,538.36 303.17,538.36 "
id="polygon3" />&#10; <polygon
points="531.69,538.36 303.17,211.99 201.61,211.99 430.13,538.36 "
id="polygon4" />&#10;</g>&#10;</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -197,6 +197,9 @@
<div id="update_oai_preset" class="menu_button menu_button_icon" title="Update current preset" data-i18n="[title]Update current preset">
<i class="fa-fw fa-solid fa-save"></i>
</div>
<div data-preset-manager-rename="openai" class="menu_button menu_button_icon" title="Rename current preset" data-i18n="[title]Rename current preset">
<i class="fa-fw fa-solid fa-pencil"></i>
</div>
<div id="new_oai_preset" class="menu_button menu_button_icon" title="Save preset as" data-i18n="[title]Save preset as">
<i class="fa-fw fa-solid fa-file-circle-plus"></i>
</div>
@@ -643,7 +646,7 @@
<input type="number" id="openai_max_tokens" name="openai_max_tokens" class="text_pole" min="1" max="65536">
</div>
</div>
<div class="range-block" data-source="openai,custom">
<div class="range-block" data-source="openai,custom,xai">
<div class="range-block-title" data-i18n="Multiple swipes per generation">
Multiple swipes per generation
</div>
@@ -682,7 +685,7 @@
</span>
</div>
</div>
<div class="range-block" data-source="openai,claude,windowai,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt,deepseek">
<div class="range-block" data-source="openai,claude,windowai,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt,deepseek,xai">
<div class="range-block-title" data-i18n="Temperature">
Temperature
</div>
@@ -695,7 +698,7 @@
</div>
</div>
</div>
<div class="range-block" data-source="openai,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt,deepseek">
<div class="range-block" data-source="openai,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt,deepseek,xai">
<div class="range-block-title" data-i18n="Frequency Penalty">
Frequency Penalty
</div>
@@ -708,7 +711,7 @@
</div>
</div>
</div>
<div class="range-block" data-source="openai,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt,deepseek">
<div class="range-block" data-source="openai,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt,deepseek,xai">
<div class="range-block-title" data-i18n="Presence Penalty">
Presence Penalty
</div>
@@ -734,7 +737,7 @@
</div>
</div>
</div>
<div class="range-block" data-source="openai,claude,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt,deepseek">
<div class="range-block" data-source="openai,claude,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt,deepseek,xai">
<div class="range-block-title" data-i18n="Top P">
Top P
</div>
@@ -971,7 +974,7 @@
</div>
</div>
</div>
<div class="range-block" data-source="openai,openrouter,mistralai,custom,cohere,groq,nanogpt">
<div class="range-block" data-source="openai,openrouter,mistralai,custom,cohere,groq,nanogpt,xai">
<div class="range-block-title justifyLeft" data-i18n="Seed">
Seed
</div>
@@ -1957,12 +1960,15 @@
<span data-i18n="Enable web search">Enable web search</span>
</label>
<div class="flexBasis100p toggle-description justifyLeft">
<span>
<span data-i18n="Use search capabilities provided by the backend.">
Use search capabilities provided by the backend.
</span>
<b data-source="openrouter" data-i18n="openrouter_web_search_fee">
Not free, adds a $0.02 fee to each prompt.
</b>
</div>
</div>
<div class="range-block" data-source="openai,cohere,mistralai,custom,claude,openrouter,groq,deepseek,makersuite">
<div class="range-block" data-source="openai,cohere,mistralai,custom,claude,openrouter,groq,deepseek,makersuite,ai21,xai">
<label for="openai_function_calling" class="checkbox_label flexWrap widthFreeExpand">
<input id="openai_function_calling" type="checkbox" />
<span data-i18n="Enable function calling">Enable function calling</span>
@@ -1972,7 +1978,7 @@
<span data-i18n="enable_functions_desc_3">Can be utilized by various extensions to provide additional functionality.</span>
</div>
</div>
<div class="range-block" data-source="openai,openrouter,makersuite,claude,custom,01ai">
<div class="range-block" data-source="openai,openrouter,mistralai,makersuite,claude,custom,01ai,xai">
<label for="openai_image_inlining" class="checkbox_label flexWrap widthFreeExpand">
<input id="openai_image_inlining" type="checkbox" />
<span data-i18n="Send inline images">Send inline images</span>
@@ -1984,7 +1990,7 @@
<code><i class="fa-solid fa-wand-magic-sparkles"></i></code>
<span data-i18n="image_inlining_hint_3">menu to attach an image file to the chat.</span>
</div>
<div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom">
<div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,xai">
<div class="flex-container oneline-dropdown">
<label for="openai_inline_image_quality" data-i18n="Inline Image Quality">
Inline Image Quality
@@ -1997,6 +2003,23 @@
</div>
</div>
</div>
<div class="range-block" data-source="makersuite">
<label for="openai_request_images" class="checkbox_label widthFreeExpand">
<input id="openai_request_images" type="checkbox" />
<span>
<span data-i18n="Request inline images">Request inline images</span>
<i class="opacity50p fa-solid fa-circle-info" title="Gemini 2.0 Flash Experimental"></i>
</span>
</label>
<div class="toggle-description justifyLeft marginBot5">
<span data-i18n="Allows the model to return image attachments.">
Allows the model to return image attachments.
</span>
<em data-source="makersuite" data-i18n="Request inline images_desc_2">
Incompatible with the following features: function calling, web search, system prompt.
</em>
</div>
</div>
<div class="range-block" data-source="makersuite">
<label for="use_makersuite_sysprompt" class="checkbox_label widthFreeExpand">
<input id="use_makersuite_sysprompt" type="checkbox" />
@@ -2011,7 +2034,7 @@
</span>
</div>
</div>
<div class="range-block" data-source="deepseek,openrouter,custom,claude">
<div class="range-block" data-source="deepseek,openrouter,custom,claude,xai">
<label for="openai_show_thoughts" class="checkbox_label widthFreeExpand">
<input id="openai_show_thoughts" type="checkbox" />
<span>
@@ -2025,7 +2048,7 @@
</span>
</div>
</div>
<div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,claude">
<div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,claude,xai">
<div class="flex-container oneline-dropdown" title="Constrains effort on reasoning for reasoning models.&#10;Currently supported values are low, medium, and high.&#10;Reducing reasoning effort can result in faster responses and fewer tokens used on reasoning in a response." data-i18n="[title]Constrains effort on reasoning for reasoning models.">
<label for="openai_reasoning_effort">
<span data-i18n="Reasoning Effort">Reasoning Effort</span>
@@ -2171,7 +2194,7 @@
<input id="horde_trusted_workers_only" type="checkbox" />
<span data-i18n="Trusted workers only">Trusted workers only</span>
</label>
<small id="adjustedHordeParams">Context: --, Response: --</small>
<small id="adjustedHordeParams"><span data-i18n="Context">Context</span>: --, <span data-i18n="Response">Response</span>: --</small>
<h4 data-i18n="API key">API key</h4>
<small>
<span data-i18n="Get it here:">Get it here: </span> <a target="_blank" href="https://aihorde.net/register" data-i18n="Register">Register</a> (<a id="horde_kudos" href="javascript:void(0);" data-i18n="View my Kudos">View my Kudos</a>)<br>
@@ -2412,7 +2435,7 @@
</div>
<div class="flex1">
<h4 data-i18n="Server url">Server URL</h4>
<small data-i18n="Example: 127.0.0.1:5000">Example: http://127.0.0.1:5000</small>
<small data-i18n="Example: http://127.0.0.1:5000">Example: http://127.0.0.1:5000</small>
<input id="generic_api_url_text" name="generic_api_url" class="text_pole wide100p" value="" autocomplete="off" data-server-history="generic">
</div>
<datalist id="generic_model_fill"></datalist>
@@ -2441,7 +2464,7 @@
</div>
<div class="flex1">
<h4 data-i18n="Server url">Server URL</h4>
<small data-i18n="Example: 127.0.0.1:5000">Example: http://127.0.0.1:5000</small>
<small data-i18n="Example: http://127.0.0.1:5000">Example: http://127.0.0.1:5000</small>
<input id="textgenerationwebui_api_url_text" name="textgenerationwebui_api_url" class="text_pole wide100p" value="" autocomplete="off" data-server-history="ooba_blocking">
</div>
<input id="custom_model_textgenerationwebui" class="text_pole wide100p" placeholder="Custom model (optional)" data-i18n="[placeholder]Custom model (optional)" type="text">
@@ -2514,7 +2537,7 @@
</div>
<div class="flex1">
<h4 data-i18n="API url">API URL</h4>
<small data-i18n="Example: 127.0.0.1:8000">Example: http://127.0.0.1:8000</small>
<small data-i18n="Example: http://127.0.0.1:8000">Example: http://127.0.0.1:8000</small>
<input id="vllm_api_url_text" class="text_pole wide100p" value="" autocomplete="off" data-server-history="vllm">
</div>
<div>
@@ -2560,7 +2583,7 @@
</div>
<div class="flex1">
<h4 data-i18n="API url">API URL</h4>
<small data-i18n="Example: 127.0.0.1:5000">Example: http://127.0.0.1:5000</small>
<small data-i18n="Example: http://127.0.0.1:5000">Example: http://127.0.0.1:5000</small>
<input id="aphrodite_api_url_text" class="text_pole wide100p" value="" autocomplete="off" data-server-history="aphrodite">
</div>
<div>
@@ -2589,7 +2612,7 @@
</div>
<div class="flex1">
<h4 data-i18n="API url">API URL</h4>
<small data-i18n="Example: 127.0.0.1:8080">Example: http://127.0.0.1:8080</small>
<small data-i18n="Example: http://127.0.0.1:8080">Example: http://127.0.0.1:8080</small>
<input id="llamacpp_api_url_text" class="text_pole wide100p" value="" autocomplete="off" data-server-history="llamacpp">
</div>
</div>
@@ -2601,7 +2624,7 @@
</div>
<div class="flex1">
<h4 data-i18n="API url">API URL</h4>
<small data-i18n="Example: 127.0.0.1:11434">Example: http://127.0.0.1:11434</small>
<small data-i18n="Example: http://127.0.0.1:11434">Example: http://127.0.0.1:11434</small>
<input id="ollama_api_url_text" class="text_pole wide100p" value="" autocomplete="off" data-server-history="ollama">
</div>
<div class="flex1">
@@ -2636,7 +2659,7 @@
</div>
<div class="flex1">
<h4 data-i18n="API url">API URL</h4>
<small data-i18n="Example: 127.0.0.1:5000">Example: http://127.0.0.1:5000</small>
<small data-i18n="Example: http://127.0.0.1:5000">Example: http://127.0.0.1:5000</small>
<input id="tabby_api_url_text" class="text_pole wide100p" value="" autocomplete="off" data-server-history="tabby">
</div>
<div class="flex1">
@@ -2688,7 +2711,7 @@
</div>
<div class="flex1">
<h4 data-i18n="API url">API URL</h4>
<small data-i18n="Example: 127.0.0.1:5001">Example: http://127.0.0.1:5001</small>
<small data-i18n="Example: http://127.0.0.1:5001">Example: http://127.0.0.1:5001</small>
<input id="koboldcpp_api_url_text" class="text_pole wide100p" value="" autocomplete="off" data-server-history="koboldcpp">
</div>
</div>
@@ -2725,7 +2748,6 @@
<optgroup>
<option value="01ai">01.AI (Yi)</option>
<option value="ai21">AI21</option>
<option value="blockentropy">Block Entropy</option>
<option value="claude">Claude</option>
<option value="cohere">Cohere</option>
<option value="deepseek">DeepSeek</option>
@@ -2737,6 +2759,7 @@
<option value="perplexity">Perplexity</option>
<option value="scale">Scale</option>
<option value="windowai">Window AI</option>
<option value="xai">xAI (Grok)</option>
</optgroup>
</select>
<div class="inline-drawer wide100p" data-source="openai,claude,mistralai,makersuite,deepseek">
@@ -2866,6 +2889,14 @@
<option value="gpt-4o-2024-05-13">gpt-4o-2024-05-13</option>
<option value="chatgpt-4o-latest">chatgpt-4o-latest</option>
</optgroup>
<optgroup label="GPT-4.1">
<option value="gpt-4.1">gpt-4.1</option>
<option value="gpt-4.1-2025-04-14">gpt-4.1-2025-04-14</option>
<option value="gpt-4.1-mini">gpt-4.1-mini</option>
<option value="gpt-4.1-mini-2025-04-14">gpt-4.1-mini-2025-04-14</option>
<option value="gpt-4.1-nano">gpt-4.1-nano</option>
<option value="gpt-4.1-nano-2025-04-14">gpt-4.1-nano-2025-04-14</option>
</optgroup>
<optgroup label="o1 and o1-mini">
<option value="o1">o1</option>
<option value="o1-2024-12-17">o1-2024-12-17</option>
@@ -3078,7 +3109,15 @@
<div>
<h4 data-i18n="AI21 Model">AI21 Model</h4>
<select id="model_ai21_select">
<optgroup label="Jamba 1.5">
<optgroup label="Jamba (Latest)">
<option value="jamba-mini">jamba-mini</option>
<option value="jamba-large">jamba-large</option>
</optgroup>
<optgroup label="Jamba 1.6">
<option value="jamba-1.6-mini">jamba-1.6-mini</option>
<option value="jamba-1.6-large">jamba-1.6-large</option>
</optgroup>
<optgroup label="Jamba 1.5 (Deprecated)">
<option value="jamba-1.5-mini">jamba-1.5-mini</option>
<option value="jamba-1.5-large">jamba-1.5-large</option>
</optgroup>
@@ -3109,7 +3148,12 @@
<option value="gemini-ultra">Gemini Ultra (1.0)</option>
<option value="gemini-1.0-ultra-latest">Gemini 1.0 Ultra</option>
</optgroup>
<optgroup label="Gemma">
<option value="gemma-3-27b-it">Gemma 3 27B</option>
</optgroup>
<optgroup label="Subversions">
<option value="gemini-2.5-pro-preview-03-25">Gemini 2.5 Pro Preview 2025-03-25</option>
<option value="gemini-2.5-pro-exp-03-25">Gemini 2.5 Pro Experimental 2025-03-25</option>
<option value="gemini-2.0-pro-exp">Gemini 2.0 Pro Experimental</option>
<option value="gemini-2.0-pro-exp-02-05">Gemini 2.0 Pro Experimental 2025-02-05</option>
<option value="gemini-2.0-flash-lite-preview">Gemini 2.0 Flash-Lite Preview</option>
@@ -3119,6 +3163,7 @@
<option value="gemini-2.0-flash-thinking-exp-01-21">Gemini 2.0 Flash Thinking Experimental 2025-01-21</option>
<option value="gemini-2.0-flash-thinking-exp-1219">Gemini 2.0 Flash Thinking Experimental 2024-12-19</option>
<option value="gemini-2.0-flash-exp">Gemini 2.0 Flash Experimental</option>
<option value="gemini-2.0-flash-exp-image-generation">Gemini 2.0 Flash (Image Generation) Experimental</option>
<option value="gemini-exp-1114">Gemini Experimental 2024-11-14</option>
<option value="gemini-exp-1121">Gemini Experimental 2024-11-21</option>
<option value="gemini-exp-1206">Gemini Experimental 2024-12-06</option>
@@ -3164,6 +3209,7 @@
<option value="mistral-small-latest">mistral-small-latest</option>
<option value="mistral-medium-latest">mistral-medium-latest</option>
<option value="mistral-large-latest">mistral-large-latest</option>
<option value="mistral-saba-latest">mistral-saba-latest</option>
<option value="codestral-latest">codestral-latest</option>
<option value="codestral-mamba-latest">codestral-mamba-latest</option>
<option value="pixtral-12b-latest">pixtral-12b-latest</option>
@@ -3179,13 +3225,20 @@
<option value="mistral-small-2312">mistral-small-2312</option>
<option value="mistral-small-2402">mistral-small-2402</option>
<option value="mistral-small-2409">mistral-small-2409</option>
<option value="mistral-small-2501">mistral-small-2501</option>
<option value="mistral-small-2503">mistral-small-2503</option>
<option value="mistral-medium-2312">mistral-medium-2312</option>
<option value="mistral-large-2402">mistral-large-2402</option>
<option value="mistral-large-2407">mistral-large-2407</option>
<option value="mistral-large-2411">mistral-large-2411</option>
<option value="mistral-large-pixtral-2411">mistral-large-pixtral-2411</option>
<option value="mistral-saba-2502">mistral-saba-2502</option>
<option value="codestral-2405">codestral-2405</option>
<option value="codestral-2405-blue">codestral-2405-blue</option>
<option value="codestral-mamba-2407">codestral-mamba-2407</option>
<option value="codestral-2411-rc5">codestral-2411-rc5</option>
<option value="codestral-2412">codestral-2412</option>
<option value="codestral-2501">codestral-2501</option>
<option value="pixtral-12b-2409">pixtral-12b-2409</option>
<option value="pixtral-large-2411">pixtral-large-2411</option>
</optgroup>
@@ -3208,28 +3261,29 @@
<option value="qwen-2.5-32b">qwen-2.5-32b</option>
<option value="qwen-2.5-coder-32b">qwen-2.5-coder-32b</option>
</optgroup>
<optgroup label="DeepSeek / Alibaba Cloud">
<optgroup label="DeepSeek">
<option value="deepseek-r1-distill-qwen-32b">deepseek-r1-distill-qwen-32b</option>
</optgroup>
<optgroup label="DeepSeek / Meta">
<option value="deepseek-r1-distill-llama-70b">deepseek-r1-distill-llama-70b</option>
</optgroup>
<optgroup label="Google">
<option value="gemma2-9b-it">gemma2-9b-it</option>
</optgroup>
<optgroup label="Meta">
<option value="llama-3.1-8b-instant">llama-3.1-8b-instant </option>
<option value="meta-llama/llama-4-scout-17b-16e-instruct">meta-llama/llama-4-scout-17b-16e-instruct</option>
<option value="meta-llama/llama-4-maverick-17b-128e-instruct">meta-llama/llama-4-maverick-17b-128e-instruct</option>
<option value="llama-3.1-8b-instant">llama-3.1-8b-instant</option>
<option value="llama-3.2-11b-vision-preview">llama-3.2-11b-vision-preview </option>
<option value="llama-3.2-1b-preview">llama-3.2-1b-preview </option>
<option value="llama-3.2-3b-preview">llama-3.2-3b-preview </option>
<option value="llama-3.2-90b-vision-preview">llama-3.2-90b-vision-preview </option>
<option value="llama-3.3-70b-specdec">llama-3.3-70b-specdec </option>
<option value="llama-3.3-70b-specdec">llama-3.3-70b-specdec</option>
<option value="llama-3.3-70b-versatile">llama-3.3-70b-versatile </option>
<option value="llama-guard-3-8b">llama-guard-3-8b </option>
<option value="llama3-70b-8192">llama3-70b-8192 </option>
<option value="llama3-8b-8192">llama3-8b-8192 </option>
<option value="llama-guard-3-8b">llama-guard-3-8b</option>
<option value="llama3-70b-8192">llama3-70b-8192</option>
<option value="llama3-8b-8192">llama3-8b-8192</option>
</optgroup>
<optgroup label="Mistral AI">
<option value="mistral-saba-24b">mistral-saba-24b</option>
<option value="mixtral-8x7b-32768">mixtral-8x7b-32768</option>
</optgroup>
</select>
@@ -3325,6 +3379,7 @@
<option value="command-r-08-2024">command-r-08-2024</option>
<option value="command-r-plus-08-2024">command-r-plus-08-2024</option>
<option value="command-r7b-12-2024">command-r7b-12-2024</option>
<option value="command-a-03-2025">command-a-03-2025</option>
</optgroup>
<optgroup label="Nightly">
<option value="command-light-nightly">command-light-nightly</option>
@@ -3333,20 +3388,6 @@
</select>
</div>
</form>
<form id="blockentropy_form" data-source="blockentropy">
<h4 data-i18n="Block Entropy API Key">Block Entropy API Key</h4>
<div class="flex-container">
<input id="api_key_blockentropy" name="api_key_blockentropy" class="text_pole flex1" 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_blockentropy"></div>
</div>
<div data-for="api_key_blockentropy" 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="Select a Model">Select a Model</h4>
<div class="flex-container">
<select id="model_blockentropy_select" class="text_pole"></select>
</div>
</form>
<form id="custom_form" data-source="custom">
<h4 data-i18n="Custom Endpoint (Base URL)">Custom Endpoint (Base URL)</h4>
<div class="flex-container">
@@ -3377,17 +3418,10 @@
<div class="flex-container">
<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 data-i18n="prompt_post_processing_none" value="">None</option>
<option data-i18n="prompt_post_processing_merge" value="merge">Merge consecutive roles</option>
<option data-i18n="prompt_post_processing_semi" value="semi">Semi-strict (alternating roles)</option>
<option data-i18n="prompt_post_processing_strict" value="strict">Strict (user first, alternating roles)</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">
<a data-i18n="01.AI API Key" href="https://platform.lingyiwanwu.com/" target="_blank" rel="noopener noreferrer">
01.AI API Key
</a>
</h4>
@@ -3402,6 +3436,40 @@
<select id="model_01ai_select">
</select>
</div>
<div id="xai_form" data-source="xai">
<h4>
<a data-i18n="xAI API Key" href="https://console.x.ai/" target="_blank" rel="noopener noreferrer">
xAI API Key
</a>
</h4>
<div class="flex-container">
<input id="api_key_xai" name="api_key_xai" class="text_pole flex1" 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_xai"></div>
</div>
<div data-for="api_key_xai" 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="xAI Model">xAI Model</h4>
<select id="model_xai_select">
<option value="grok-3-beta">grok-3-beta</option>
<option value="grok-3-fast-beta">grok-3-fast-beta</option>
<option value="grok-3-mini-beta">grok-3-mini-beta</option>
<option value="grok-3-mini-fast-beta">grok-3-mini-fast-beta</option>
<option value="grok-2-vision-1212">grok-2-vision-1212</option>
<option value="grok-2-1212">grok-2-1212</option>
<option value="grok-vision-beta">grok-vision-beta</option>
<option value="grok-beta">grok-beta</option>
</select>
</div>
<div id="prompt_post_porcessing_form" data-source="custom,openrouter">
<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 data-i18n="prompt_post_processing_none" value="">None</option>
<option data-i18n="prompt_post_processing_merge" value="merge">Merge consecutive roles</option>
<option data-i18n="prompt_post_processing_semi" value="semi">Semi-strict (alternating roles)</option>
<option data-i18n="prompt_post_processing_strict" value="strict">Strict (user first, alternating roles)</option>
</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 menu_button_icon" data-i18n="Cancel">Cancel</div>
@@ -3826,6 +3894,7 @@
<option value="14">Jamba</option>
<option value="15">Qwen2</option>
<option value="16">Command-R</option>
<option value="19">Command-A</option>
<option value="4">NerdStash (NovelAI Clio)</option>
<option value="5">NerdStash v2 (NovelAI Kayra)</option>
<option value="7">Mistral V1</option>
@@ -3886,6 +3955,19 @@
<summary data-i18n="Reasoning Formatting">
Reasoning Formatting
</summary>
<div class="flex-container" title="Select your current Reasoning Template" data-i18n="[title]Select your current Reasoning Template">
<select id="reasoning_select" data-preset-manager-for="reasoning" class="flex1 text_pole"></select>
<div class="flex-container margin0 justifyCenter gap3px">
<input type="file" hidden data-preset-manager-file="reasoning" accept=".json, .settings">
<i data-preset-manager-update="reasoning" class="menu_button fa-solid fa-save" title="Update current template" data-i18n="[title]Update current template"></i>
<i data-preset-manager-rename="reasoning" class="menu_button fa-pencil fa-solid" title="Rename current template" data-i18n="[title]Rename current template"></i>
<i data-preset-manager-new="reasoning" class="menu_button fa-solid fa-file-circle-plus" title="Save template as" data-i18n="[title]Save template as"></i>
<i data-preset-manager-import="reasoning" class="displayNone menu_button fa-solid fa-file-import" title="Import template" data-i18n="[title]Import template"></i>
<i data-preset-manager-export="reasoning" class="displayNone menu_button fa-solid fa-file-export" title="Export template" data-i18n="[title]Export template"></i>
<i data-preset-manager-restore="reasoning" class="menu_button fa-solid fa-recycle" title="Restore current template" data-i18n="[title]Restore current template"></i>
<i data-preset-manager-delete="reasoning" class="menu_button fa-solid fa-trash-can" title="Delete template" data-i18n="[title]Delete template"></i>
</div>
</div>
<div class="flex-container">
<div class="flex1" title="Inserted before the reasoning content." data-i18n="[title]reasoning_prefix">
<small data-i18n="Prefix">Prefix</small>
@@ -5068,7 +5150,7 @@
<div id="persona_depth_position_settings" class="flex-container">
<div class="flex1">
<label for="persona_depth_value" data-i18n="Depth:">Depth:</label>
<input id="persona_depth_value" class="text_pole" type="number" min="0" max="999" step="1">
<input id="persona_depth_value" class="text_pole" type="number" min="0" max="9999" step="1">
</div>
<div class="flex1">
<label for="persona_depth_role" data-i18n="Role:">Role:</label>
@@ -5374,6 +5456,7 @@
<option value="2" data-i18n="Manual">Manual</option>
<option value="0" data-i18n="Natural order">Natural order</option>
<option value="1" data-i18n="List order">List order</option>
<option value="3" data-i18n="Pooled order">Pooled order</option>
</select>
</div>
<div class="flex1 flexGap5">
@@ -5720,7 +5803,7 @@
@ Depth
</span>
</h4>
<input id="depth_prompt_depth" name="depth_prompt_depth" class="text_pole textarea_compact m-t-0" type="number" min="0" max="999" value="4" form="form_create" />
<input id="depth_prompt_depth" name="depth_prompt_depth" class="text_pole textarea_compact m-t-0" type="number" min="0" max="9999" value="4" form="form_create" />
<h4>
<span data-i18n="Role">
Role
@@ -5941,11 +6024,11 @@
</div>
<div class="world_entry_form_control wi-enter-footer-text flex-container flexNoGap">
<label for="depth" class="WIEntryHeaderTitleMobile" data-i18n="Depth:">Depth:</label>
<input title="Depth" class="text_pole wideMax100px margin0" type="number" name="depth" data-i18n="[title]Depth" placeholder="" min="0" max="999" />
<input title="Depth" class="text_pole wideMax100px margin0" type="number" name="depth" data-i18n="[title]Depth" placeholder="" min="0" max="9999" />
</div>
<div class="world_entry_form_control wi-enter-footer-text flex-container flexNoGap">
<label for="order" class="WIEntryHeaderTitleMobile" data-i18n="Order:">Order:</label>
<input title="Order" data-i18n="[title]Order" class="text_pole wideMax100px margin0" type="number" name="order" placeholder="" min="0" max="999" />
<input title="Order" data-i18n="[title]Order" class="text_pole wideMax100px margin0" type="number" name="order" placeholder="" min="0" max="9999" />
</div>
<div class="world_entry_form_control wi-enter-footer-text flex-container flexNoGap probabilityContainer">
<label for="order" class="WIEntryHeaderTitleMobile" data-i18n="Trigger %:">Trigger %:</label>
@@ -5954,6 +6037,7 @@
</div>
</div>
</div>
<i class="menu_button move_entry_button fa-solid fa-right-left" title="Move Entry to Another Lorebook" data-i18n="[title]Move Entry to Another Lorebook"></i>
<i class="menu_button duplicate_entry_button fa-solid fa-paste" title="Duplicate world info entry" data-i18n="[title]Duplicate world info entry" type="submit" value=""></i>
<i class="menu_button delete_entry_button fa-solid fa-trash-can" title="Delete world info entry" data-i18n="[title]Delete world info entry" type="submit" value=""></i>
</div>
@@ -6042,8 +6126,11 @@
<label for="content ">
<small>
<span class="alignitemscenter flex-container flexnowrap wide100p justifySpaceBetween">
<span data-i18n="Content" class="alignitemscenter flex-container flexNoGap mdhotkey_location">
Content
<span class="alignitemscenter flex-container">
<span data-i18n="Content" class="mdhotkey_location">
Content
</span>
<i class="editor_maximize fa-solid fa-maximize right_menu_button" title="Expand the editor" data-i18n="[title]Expand the editor"></i>
</span>
<span>
(<span data-i18n="extension_token_counter">Tokens:</span>&nbsp; <span class="world_entry_form_token_counter" data-first-run="true">counting...</span>)&nbsp;
@@ -6303,7 +6390,7 @@
<span data-i18n="prompt_manager_depth">Depth</span>
</label>
<div class="text_muted" data-i18n="Injection depth. 0 = after the last message, 1 = before the last message, etc.">Injection depth. 0 = after the last message, 1 = before the last message, etc.</div>
<input id="completion_prompt_manager_popup_entry_form_injection_depth" class="text_pole" type="number" name="injection_depth" min="0" max="999" value="4" />
<input id="completion_prompt_manager_popup_entry_form_injection_depth" class="text_pole" type="number" name="injection_depth" min="0" max="9999" value="4" />
</div>
</div>
<div class="completion_prompt_manager_popup_entry_form_control">
@@ -6402,7 +6489,7 @@
<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="Expand and zoom" class="right_menu_button fa-lg fa-solid fa-magnifying-glass mes_img_enlarge" data-i18n="[title]Expand and zoom"></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>
@@ -6531,7 +6618,7 @@
<div class="ch_name"></div>
<small class="ch_additional_info group_select_counter"></small>
</div>
<small class="character_name_block_sub_line">in this group</small>
<small class="character_name_block_sub_line" data-i18n="in this group">in this group</small>
<i class='group_fav_icon fa-solid fa-star'></i>
<input class="ch_fav" value="" hidden />
<div class="group_select_block_list ch_description"></div>
@@ -6663,7 +6750,7 @@
<label class="checkbox_label alignItemsCenter" for="extension_floating_position_depth">
<input type="radio" id="extension_floating_position_depth" name="extension_floating_position" value="1" />
<span data-i18n="In-chat @ Depth">In-chat @ Depth</span>
<input id="extension_floating_depth" class="text_pole textarea_compact widthNatural" type="number" min="0" max="999" />
<input id="extension_floating_depth" class="text_pole textarea_compact widthNatural" type="number" min="0" max="9999" />
<span data-i18n="as">as</span>
<select id="extension_floating_role" class="text_pole widthNatural">
<option data-i18n="System" value="0">System</option>
@@ -6678,7 +6765,7 @@
<span data-i18n="Insertion Frequency">Insertion Frequency</span>
<small data-i18n="(0 = Disable, 1 = Always)">(0 = Disable, 1 = Always)</small>
</label>
<input id="extension_floating_interval" class="text_pole widthUnset" type="number" min="0" max="999" />
<input id="extension_floating_interval" class="text_pole widthUnset" type="number" min="0" max="9999" />
</div>
<br>
<span><span data-i18n="User inputs until next insertion:">User inputs until next insertion:</span> <span id="extension_floating_counter">(disabled)</span></span>
@@ -6748,7 +6835,7 @@
<label class="checkbox_label alignItemsCenter" for="extension_default_position_depth">
<input type="radio" id="extension_default_position_depth" name="extension_default_position" value="1" />
<span data-i18n="In-chat @ Depth">In-chat @ Depth</span>
<input id="extension_default_depth" class="text_pole textarea_compact widthNatural" type="number" min="0" max="999" />
<input id="extension_default_depth" class="text_pole textarea_compact widthNatural" type="number" min="0" max="9999" />
<span data-i18n="as">as</span>
<select id="extension_default_role" class="text_pole widthNatural">
<option data-i18n="System" value="0">System</option>
@@ -6762,7 +6849,7 @@
<span data-i18n="Insertion Frequency">Insertion Frequency</span>
<small data-i18n="(0 = Disable, 1 = Always)">(0 = Disable, 1 = Always)</small>
</label>
<input id="extension_default_interval" class="text_pole widthUnset" type="number" min="0" max="999" />
<input id="extension_default_interval" class="text_pole widthUnset" type="number" min="0" max="9999" />
</div>
</div>
</div>

View File

@@ -14,7 +14,8 @@
"**/.git/**",
"lib/**",
"**/*.min.js",
"scripts/extensions/quick-reply/lib/**"
"scripts/extensions/quick-reply/lib/**",
"scripts/extensions/tts/lib/**"
],
"typeAcquisition": {
"include": []

View File

@@ -318,23 +318,23 @@
"flag": "وضع علامة",
"API key (optional)": "مفتاح API (اختياري)",
"Server url": "رابط الخادم",
"Example: 127.0.0.1:5000": "مثال: 127.0.0.1:5000",
"Example: http://127.0.0.1:5000": "مثال: http://127.0.0.1:5000",
"Custom model (optional)": "نموذج مخصص (اختياري)",
"vllm-project/vllm": "vllm-project/vllm (وضع غلاف OpenAI API)",
"vLLM API key": "مفتاح واجهة برمجة التطبيقات vLLM",
"Example: 127.0.0.1:8000": "مثال: http://127.0.0.1:8000",
"Example: http://127.0.0.1:8000": "مثال: http://127.0.0.1:8000",
"vLLM Model": "نموذج vLLM",
"PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (وضع التغليف لواجهة برمجة التطبيقات OpenAI)",
"Aphrodite API key": "مفتاح واجهة برمجة التطبيقات Aphrodite",
"Aphrodite Model": "نموذج أفروديت",
"ggerganov/llama.cpp": "ggerganov/llama.cpp (خادم إخراج)",
"Example: 127.0.0.1:8080": "مثال: 127.0.0.1:8080",
"Example: 127.0.0.1:11434": "مثال: 127.0.0.1:11434",
"Example: http://127.0.0.1:8080": "مثال: http://127.0.0.1:8080",
"Example: http://127.0.0.1:11434": "مثال: http://127.0.0.1:11434",
"Ollama Model": "نموذج Ollama",
"Download": "تحميل",
"Tabby API key": "مفتاح API لـ Tabby",
"koboldcpp API key (optional)": "مفتاح koboldcpp API (اختياري)",
"Example: 127.0.0.1:5001": "مثال: 127.0.0.1:5001",
"Example: http://127.0.0.1:5001": "مثال: http://127.0.0.1:5001",
"Authorize": "تفويض",
"Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "احصل على رمز واجهة برمجة التطبيقات الخاص بك لموزع الاتصالات باستخدام تدفق OAuth. سيتم توجيهك إلى openrouter.ai",
"Bypass status check": "تجاوز فحص الحالة",

View File

@@ -318,23 +318,23 @@
"flag": "Flagge",
"API key (optional)": "API-Schlüssel (optional)",
"Server url": "Server-URL",
"Example: 127.0.0.1:5000": "Beispiel: 127.0.0.1:5000",
"Example: http://127.0.0.1:5000": "Beispiel: http://127.0.0.1:5000",
"Custom model (optional)": "Benutzerdefiniertes Modell (optional)",
"vllm-project/vllm": "vllm-project/vllm (OpenAI API-Wrappermodus)",
"vLLM API key": "vLLM-API-Schlüssel",
"Example: 127.0.0.1:8000": "Beispiel: http://127.0.0.1:8000",
"Example: http://127.0.0.1:8000": "Beispiel: http://127.0.0.1:8000",
"vLLM Model": "vLLM-Modell",
"PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (Wrappermodus für OpenAI API)",
"Aphrodite API key": "Aphrodite API-Schlüssel",
"Aphrodite Model": "Aphrodite-Modell",
"ggerganov/llama.cpp": "ggerganov/llama.cpp (Output-Server)",
"Example: 127.0.0.1:8080": "Beispiel: 127.0.0.1:8080",
"Example: 127.0.0.1:11434": "Beispiel: 127.0.0.1:11434",
"Example: http://127.0.0.1:8080": "Beispiel: http://127.0.0.1:8080",
"Example: http://127.0.0.1:11434": "Beispiel: http://127.0.0.1:11434",
"Ollama Model": "Ollama-Modell",
"Download": "Herunterladen",
"Tabby API key": "Tabby API-Schlüssel",
"koboldcpp API key (optional)": "koboldcpp API-Schlüssel (optional)",
"Example: 127.0.0.1:5001": "Beispiel: 127.0.0.1:5001",
"Example: http://127.0.0.1:5001": "Beispiel: http://127.0.0.1:5001",
"Authorize": "Autorisieren",
"Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "Hole dein OpenRouter-API-Token mit OAuth-Fluss. Du wirst zu openrouter.ai weitergeleitet",
"Bypass status check": "Umgehe Statusüberprüfung",

View File

@@ -318,23 +318,23 @@
"flag": "bandera",
"API key (optional)": "Clave API (opcional)",
"Server url": "URL del servidor",
"Example: 127.0.0.1:5000": "Ejemplo: 127.0.0.1:5000",
"Example: http://127.0.0.1:5000": "Ejemplo: http://127.0.0.1:5000",
"Custom model (optional)": "Modelo personalizado (opcional)",
"vllm-project/vllm": "vllm-project/vllm (modo contenedor de API OpenAI)",
"vLLM API key": "Clave API vLLM",
"Example: 127.0.0.1:8000": "Ejemplo: http://127.0.0.1:8000",
"Example: http://127.0.0.1:8000": "Ejemplo: http://127.0.0.1:8000",
"vLLM Model": "Modelo vLLM",
"PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (Modo envolvente para API de OpenAI)",
"Aphrodite API key": "Clave de API de Aphrodite",
"Aphrodite Model": "Modelo Afrodita",
"ggerganov/llama.cpp": "ggerganov/llama.cpp (Servidor de salida)",
"Example: 127.0.0.1:8080": "Ejemplo: 127.0.0.1:8080",
"Example: 127.0.0.1:11434": "Ejemplo: 127.0.0.1:11434",
"Example: http://127.0.0.1:8080": "Ejemplo: http://127.0.0.1:8080",
"Example: http://127.0.0.1:11434": "Ejemplo: http://127.0.0.1:11434",
"Ollama Model": "Modelo Ollama",
"Download": "Descargar",
"Tabby API key": "Clave API de Tabby",
"koboldcpp API key (optional)": "Clave API de koboldcpp (opcional)",
"Example: 127.0.0.1:5001": "Ejemplo: 127.0.0.1:5001",
"Example: http://127.0.0.1:5001": "Ejemplo: http://127.0.0.1:5001",
"Authorize": "Autorizar",
"Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "Obtenga su token de API de OpenRouter utilizando el flujo OAuth. Será redirigido a openrouter.ai",
"Bypass status check": "Saltar la verificación del estado",

View File

@@ -301,23 +301,23 @@
"flag": "fanion",
"API key (optional)": "Clé API (optionnelle)",
"Server url": "URL du serveur",
"Example: 127.0.0.1:5000": "Exemple : 127.0.0.1:5000",
"Example: http://127.0.0.1:5000": "Exemple : http://127.0.0.1:5000",
"Custom model (optional)": "Modèle personnalisé (optionnel)",
"vllm-project/vllm": "vllm-project/vllm (mode wrapper de l'API OpenAI)",
"vLLM API key": "Clé API vLLM",
"Example: 127.0.0.1:8000": "Exemple : http://127.0.0.1:8000",
"Example: http://127.0.0.1:8000": "Exemple : http://127.0.0.1:8000",
"vLLM Model": "Modèle vLLM",
"PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (mode wrapper pour l'API OpenAI)",
"Aphrodite API key": "Clé API Aphrodite",
"Aphrodite Model": "Modèle Aphrodite",
"ggerganov/llama.cpp": "ggerganov/llama.cpp",
"Example: 127.0.0.1:8080": "Exemple : 127.0.0.1:8080",
"Example: 127.0.0.1:11434": "Exemple : 127.0.0.1:11434",
"Example: http://127.0.0.1:8080": "Exemple : http://127.0.0.1:8080",
"Example: http://127.0.0.1:11434": "Exemple : http://127.0.0.1:11434",
"Ollama Model": "Modèle Ollama",
"Download": "Télécharger",
"Tabby API key": "Clé API de Tabby",
"koboldcpp API key (optional)": "Clé API koboldcpp (facultatif)",
"Example: 127.0.0.1:5001": "Exemple : 127.0.0.1:5001",
"Example: http://127.0.0.1:5001": "Exemple : http://127.0.0.1:5001",
"Authorize": "Autoriser",
"Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "Obtenez votre jeton API OpenRouter en utilisant le flux OAuth. Vous serez redirigé vers openrouter.ai",
"Bypass status check": "Contourner la vérification de l'état",

View File

@@ -318,23 +318,23 @@
"flag": "merki",
"API key (optional)": "API lykill (valkvæmt)",
"Server url": "URL þjóns",
"Example: 127.0.0.1:5000": "Dæmi: 127.0.0.1:5000",
"Example: http://127.0.0.1:5000": "Dæmi: http://127.0.0.1:5000",
"Custom model (optional)": "Sérsniðið módel (valkvæmt)",
"vllm-project/vllm": "vllm-project/vllm (OpenAI API umbúðastilling)",
"vLLM API key": "vLLM API lykill",
"Example: 127.0.0.1:8000": "Dæmi: http://127.0.0.1:8000",
"Example: http://127.0.0.1:8000": "Dæmi: http://127.0.0.1:8000",
"vLLM Model": "vLLM líkan",
"PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (OpenAI forritunargrensl)",
"Aphrodite API key": "Aphrodite API lykill",
"Aphrodite Model": "Afródíta fyrirmynd",
"ggerganov/llama.cpp": "ggerganov/llama.cpp (úttak þjónn)",
"Example: 127.0.0.1:8080": "Dæmi: 127.0.0.1:8080",
"Example: 127.0.0.1:11434": "Dæmi: 127.0.0.1:11434",
"Example: http://127.0.0.1:8080": "Dæmi: http://127.0.0.1:8080",
"Example: http://127.0.0.1:11434": "Dæmi: http://127.0.0.1:11434",
"Ollama Model": "Ollama módel",
"Download": "Niðurhal",
"Tabby API key": "Tabby API lykill",
"koboldcpp API key (optional)": "koboldcpp API lykill (valfrjálst)",
"Example: 127.0.0.1:5001": "Dæmi: 127.0.0.1:5001",
"Example: http://127.0.0.1:5001": "Dæmi: http://127.0.0.1:5001",
"Authorize": "Heimild",
"Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "Fáðu API lykilinn þinn fyrir OpenRouter með því að nota OAuth strauminn. Þú verður endurvísað(ð/ur) á openrouter.ai",
"Bypass status check": "Hlaupa framhjá stöðutík",

View File

@@ -318,23 +318,23 @@
"flag": "bandiera",
"API key (optional)": "Chiave API (opzionale)",
"Server url": "URL del server",
"Example: 127.0.0.1:5000": "Esempio: 127.0.0.1:5000",
"Example: http://127.0.0.1:5000": "Esempio: http://127.0.0.1:5000",
"Custom model (optional)": "Modello personalizzato (opzionale)",
"vllm-project/vllm": "vllm-project/vllm (modalità wrapper API OpenAI)",
"vLLM API key": "Chiave API vLLM",
"Example: 127.0.0.1:8000": "Esempio: http://127.0.0.1:8000",
"Example: http://127.0.0.1:8000": "Esempio: http://127.0.0.1:8000",
"vLLM Model": "Modello vLLM",
"PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (Modalità wrapper per l'API OpenAI)",
"Aphrodite API key": "Chiave API di Aphrodite",
"Aphrodite Model": "Modello di Afrodite",
"ggerganov/llama.cpp": "ggerganov/llama.cpp (Server di output)",
"Example: 127.0.0.1:8080": "Esempio: 127.0.0.1:8080",
"Example: 127.0.0.1:11434": "Esempio: 127.0.0.1:11434",
"Example: http://127.0.0.1:8080": "Esempio: http://127.0.0.1:8080",
"Example: http://127.0.0.1:11434": "Esempio: http://127.0.0.1:11434",
"Ollama Model": "Modello Ollama",
"Download": "Scarica",
"Tabby API key": "Chiave API di Tabby",
"koboldcpp API key (optional)": "Chiave API koboldcpp (opzionale)",
"Example: 127.0.0.1:5001": "Esempio: 127.0.0.1:5001",
"Example: http://127.0.0.1:5001": "Esempio: http://127.0.0.1:5001",
"Authorize": "Autorizzare",
"Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "Ottieni il tuo token API di OpenRouter utilizzando il flusso OAuth. Sarai reindirizzato su openrouter.ai",
"Bypass status check": "Ignora controllo stato",

View File

@@ -318,23 +318,23 @@
"flag": "フラグ",
"API key (optional)": "APIキーオプション",
"Server url": "サーバーURL",
"Example: 127.0.0.1:5000": "例: 127.0.0.1:5000",
"Example: http://127.0.0.1:5000": "例: http://127.0.0.1:5000",
"Custom model (optional)": "カスタムモデル(オプション)",
"vllm-project/vllm": "vllm-project/vllm (OpenAI API ラッパーモード)",
"vLLM API key": "vLLM API キー",
"Example: 127.0.0.1:8000": "例: http://127.0.0.1:8000",
"Example: http://127.0.0.1:8000": "例: http://127.0.0.1:8000",
"vLLM Model": "vLLM モデル",
"PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engineOpenAI APIエンドポイントのパッケージングモード",
"Aphrodite API key": "アフロディーテAPIキー",
"Aphrodite Model": "アフロディーテモデル",
"ggerganov/llama.cpp": "ggerganov/llama.cpp出力サーバー",
"Example: 127.0.0.1:8080": "例: 127.0.0.1:8080",
"Example: 127.0.0.1:11434": "例: 127.0.0.1:11434",
"Example: http://127.0.0.1:8080": "例: http://127.0.0.1:8080",
"Example: http://127.0.0.1:11434": "例: http://127.0.0.1:11434",
"Ollama Model": "Ollamaモデル",
"Download": "ダウンロード",
"Tabby API key": "TabbyのAPIキー",
"koboldcpp API key (optional)": "koboldcpp API キー (オプション)",
"Example: 127.0.0.1:5001": "例: 127.0.0.1:5001",
"Example: http://127.0.0.1:5001": "例: http://127.0.0.1:5001",
"Authorize": "承認",
"Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "OAuthフローを使用してOpenRouter APIトークンを取得します。 openrouter.aiにリダイレクトされます",
"Bypass status check": "ステータスのチェックをバイパスする",

View File

@@ -320,23 +320,23 @@
"flag": "깃발",
"API key (optional)": "API 키 (선택 사항)",
"Server url": "서버 URL",
"Example: 127.0.0.1:5000": "예시: 127.0.0.1:5000",
"Example: http://127.0.0.1:5000": "예시: http://127.0.0.1:5000",
"Custom model (optional)": "사용자 정의 모델 (선택 사항)",
"vllm-project/vllm": "vllm-project/vllm(OpenAI API 래퍼 모드)",
"vLLM API key": "vLLM API 키",
"Example: 127.0.0.1:8000": "예: http://127.0.0.1:8000",
"Example: http://127.0.0.1:8000": "예: http://127.0.0.1:8000",
"vLLM Model": "vLLM 모델",
"PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (OpenAI API의 래퍼 모드)",
"Aphrodite API key": "Aphrodite API 키",
"Aphrodite Model": "Aphrodite 모델",
"ggerganov/llama.cpp": "ggerganov/llama.cpp (출력 서버)",
"Example: 127.0.0.1:8080": "예: 127.0.0.1:8080",
"Example: 127.0.0.1:11434": "예: 127.0.0.1:11434",
"Example: http://127.0.0.1:8080": "예: http://127.0.0.1:8080",
"Example: http://127.0.0.1:11434": "예: http://127.0.0.1:11434",
"Ollama Model": "Ollama 모델",
"Download": "다운로드",
"Tabby API key": "Tabby API 키",
"koboldcpp API key (optional)": "koboldcpp API 키(선택사항)",
"Example: 127.0.0.1:5001": "예: 127.0.0.1:5001",
"Example: http://127.0.0.1:5001": "예: http://127.0.0.1:5001",
"Authorize": "승인하기",
"Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "OAuth 플로우를 사용하여 OpenRouter API 토큰을 가져옵니다. openrouter.ai로 리디렉션됩니다.",
"Legacy API (pre-OAI, no streaming)": "레거시 API (OAI 이전, 스트리밍 없음)",

View File

@@ -318,23 +318,23 @@
"flag": "vlag",
"API key (optional)": "API-sleutel (optioneel)",
"Server url": "Server-URL",
"Example: 127.0.0.1:5000": "Voorbeeld: 127.0.0.1:5000",
"Example: http://127.0.0.1:5000": "Voorbeeld: http://127.0.0.1:5000",
"Custom model (optional)": "Aangepast model (optioneel)",
"vllm-project/vllm": "vllm-project/vllm (OpenAI API-wrappermodus)",
"vLLM API key": "vLLM API-sleutel",
"Example: 127.0.0.1:8000": "Voorbeeld: http://127.0.0.1:8000",
"Example: http://127.0.0.1:8000": "Voorbeeld: http://127.0.0.1:8000",
"vLLM Model": "vLLM-model",
"PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (Wrappermodus voor OpenAI API)",
"Aphrodite API key": "Aphrodite API-sleutel",
"Aphrodite Model": "Aphrodite-model",
"ggerganov/llama.cpp": "ggerganov/llama.cpp (Output-server)",
"Example: 127.0.0.1:8080": "Voorbeeld: 127.0.0.1:8080",
"Example: 127.0.0.1:11434": "Voorbeeld: 127.0.0.1:11434",
"Example: http://127.0.0.1:8080": "Voorbeeld: http://127.0.0.1:8080",
"Example: http://127.0.0.1:11434": "Voorbeeld: http://127.0.0.1:11434",
"Ollama Model": "Ollama-model",
"Download": "Downloaden",
"Tabby API key": "Tabby API-sleutel",
"koboldcpp API key (optional)": "koboldcpp API-sleutel (optioneel)",
"Example: 127.0.0.1:5001": "Voorbeeld: 127.0.0.1:5001",
"Example: http://127.0.0.1:5001": "Voorbeeld: http://127.0.0.1:5001",
"Authorize": "Toestemming geven",
"Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "Haal uw OpenRouter API-token op met behulp van OAuth-flow. U wordt doorgestuurd naar openrouter.ai",
"Bypass status check": "Omzeil statuscontrole",

View File

@@ -318,23 +318,23 @@
"flag": "bandeira",
"API key (optional)": "Chave da API (opcional)",
"Server url": "URL do servidor",
"Example: 127.0.0.1:5000": "Exemplo: 127.0.0.1:5000",
"Example: http://127.0.0.1:5000": "Exemplo: http://127.0.0.1:5000",
"Custom model (optional)": "Modelo personalizado (opcional)",
"vllm-project/vllm": "vllm-project/vllm (modo wrapper da API OpenAI)",
"vLLM API key": "Chave de API vLLM",
"Example: 127.0.0.1:8000": "Exemplo: http://127.0.0.1:8000",
"Example: http://127.0.0.1:8000": "Exemplo: http://127.0.0.1:8000",
"vLLM Model": "Modelo vLLM",
"PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (Modo Wrapper para API OpenAI)",
"Aphrodite API key": "Chave da API Aphrodite",
"Aphrodite Model": "Modelo Afrodite",
"ggerganov/llama.cpp": "ggerganov/llama.cpp (Servidor de Saída)",
"Example: 127.0.0.1:8080": "Exemplo: 127.0.0.1:8080",
"Example: 127.0.0.1:11434": "Exemplo: 127.0.0.1:11434",
"Example: http://127.0.0.1:8080": "Exemplo: http://127.0.0.1:8080",
"Example: http://127.0.0.1:11434": "Exemplo: http://127.0.0.1:11434",
"Ollama Model": "Modelo Ollama",
"Download": "Baixar",
"Tabby API key": "Chave da API do Tabby",
"koboldcpp API key (optional)": "Chave API koboldcpp (opcional)",
"Example: 127.0.0.1:5001": "Exemplo: 127.0.0.1:5001",
"Example: http://127.0.0.1:5001": "Exemplo: http://127.0.0.1:5001",
"Authorize": "Autorizar",
"Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "Obtenha seu token da API do OpenRouter usando o fluxo OAuth. Você será redirecionado para openrouter.ai",
"Bypass status check": "Ignorar verificação de status",

View File

@@ -23,9 +23,8 @@
"Mirostat Mode": "Режим",
"Mirostat Tau": "Tau",
"Mirostat Eta": "Eta",
"Variability parameter for Mirostat outputs": "Параметр изменчивости для выходных данных Mirostat.",
"Variability parameter for Mirostat outputs": "Вариативность для выходных данных Mirostat.",
"Learning rate of Mirostat": "Скорость обучения Mirostat.",
"Strength of the Contrastive Search regularization term. Set to 0 to disable CS": "Сила условия регуляризации контрастивного поиска. Установите значение 0, чтобы отключить CS.",
"Temperature Last": "Температура последней",
"LLaMA / Mistral / Yi models only": "Только для моделей LLaMA / Mistral / Yi. Перед этим обязательно выберите подходящий токенизатор.\nПоследовательности, которых не должно быть на выходе.\nОдна на строку. Текст или [идентификаторы токенов].\nМногие токены имеют пробел впереди. Используйте счетчик токенов, если не уверены.",
"Example: some text [42, 69, 1337]": "Пример:\nкакой-то текст\n[42, 69, 1337]",
@@ -60,13 +59,11 @@
"Add BOS Token": "Добавлять BOS-токен",
"Add the bos_token to the beginning of prompts. Disabling this can make the replies more creative": "Добавлять BOS-токен в начале промпта. Если выключить, ответы могут стать более креативными.",
"Ban EOS Token": "Запретить EOS-токен",
"Ban the eos_token. This forces the model to never end the generation prematurely": "Запрет EOS-токена не позволит модели завершить генерацию преждевременно",
"Ban the eos_token. This forces the model to never end the generation prematurely": "Запрет EOS-токена не позволит модели завершить генерацию самостоятельно (только при достижении лимита токенов)",
"Skip Special Tokens": "Пропускать спец. токены",
"Beam search": "Поиск Beam",
"Number of Beams": "Количество Beam",
"Beam search": "Beam Search",
"Length Penalty": "Штраф за длину",
"Early Stopping": "Преждевременная остановка",
"Contrastive search": "Контрастный поиск",
"Early Stopping": "Прекращать сразу",
"Penalty Alpha": "Penalty Alpha",
"Seed": "Зерно",
"Epsilon Cutoff": "Epsilon Cutoff",
@@ -89,7 +86,7 @@
"Text Completion presets": "Пресеты для Text Completion",
"Documentation on sampling parameters": "Документация по параметрам сэмплеров",
"Set all samplers to their neutral/disabled state.": "Установить все сэмплеры в нейтральное/отключенное состояние.",
"Only enable this if your model supports context sizes greater than 8192 tokens": "Включайте эту опцию, только если ваша модель поддерживает размер контекста более 8192 токенов.\nУвеличивайте только если вы знаете, что делаете.",
"Only enable this if your model supports context sizes greater than 8192 tokens": "Включайте эту опцию, только если ваша модель поддерживает размер контекста более 8192 токенов.\nУвеличивайте только если вы понимаете, что делаете.",
"Wrap in Quotes": "Заключать в кавычки",
"Wrap entire user message in quotes before sending.": "Перед отправкой заключать всё сообщение пользователя в кавычки.",
"Leave off if you use quotes manually for speech.": "Оставьте выключенным, если вручную выставляете кавычки для прямой речи.",
@@ -109,13 +106,13 @@
"Adjust response length to worker capabilities": "Подстраивать длину ответа под возможности рабочих машин",
"API key": "API-ключ",
"Tabby API key": "Tabby API-ключ",
"Get it here:": "Получить здесь:",
"Get it here:": "Получите здесь:",
"Register": "Зарегистрироваться",
"TogetherAI Model": "Модель TogetherAI",
"Example: 127.0.0.1:5001": "Пример: http://127.0.0.1:5001",
"Example: http://127.0.0.1:5001": "Пример: http://127.0.0.1:5001",
"ggerganov/llama.cpp": "ggerganov/llama.cpp (сервер вывода)",
"Example: 127.0.0.1:8080": "Пример: http://127.0.0.1:8080",
"Example: 127.0.0.1:11434": "Пример: http://127.0.0.1:11434",
"Example: http://127.0.0.1:8080": "Пример: http://127.0.0.1:8080",
"Example: http://127.0.0.1:11434": "Пример: http://127.0.0.1:11434",
"Ollama Model": "Модель Ollama",
"Download": "Скачать",
"TogetherAI API Key": "TogetherAI API-ключ",
@@ -136,7 +133,7 @@
"Server url": "URL-адрес сервера",
"Custom model (optional)": "Пользовательская модель (необязательно)",
"Bypass API status check": "Обход проверки статуса API",
"Example: 127.0.0.1:5000": "Пример: http://127.0.0.1:5000",
"Example: http://127.0.0.1:5000": "Пример: http://127.0.0.1:5000",
"Bypass status check": "Обход проверки статуса",
"Mancer API key": "Ключ от Mancer API",
"to get your OpenAI API key.": "для получения ключа от API OpenAI",
@@ -289,10 +286,10 @@
"Author's Note": "Заметки автора",
"Replace empty message": "Заменять пустые сообщения",
"Send this text instead of nothing when the text box is empty.": "Этот текст будет отправлен в случае отсутствия текста на отправку.",
"Unrestricted maximum value for the context slider": "Убрать потолок для ползунка контекста. Включайте только если точно знаете, что делаете",
"Unrestricted maximum value for the context slider": "Убрать потолок для ползунка контекста. Включайте только если точно понимаете, что делаете",
"Chat Completion Source": "Источник для Chat Completion",
"Avoid sending sensitive information to the Horde.": "Избегайте отправки личной информации Horde",
"Review the Privacy statement": "Ознакомиться с заявлением о конфиденциальности",
"Avoid sending sensitive information to the Horde.": "Избегайте отправки личной информации Horde.",
"Review the Privacy statement": "Ознакомьтесь с заявлением о конфиденциальности",
"Trusted workers only": "Только доверенные рабочие машины",
"For privacy reasons, your API key will be hidden after you reload the page.": "Из соображений безопасности ваш API-ключ будет скрыт после перезагрузки страницы.",
"-- Horde models not loaded --": "--Модель Horde не загружена--",
@@ -699,7 +696,7 @@
"Aggressive": "Агрессивный",
"Very aggressive": "Очень агрессивный",
"Eta_Cutoff_desc": "Eta cutoff - основной параметр специальной техники сэмплинга под названием Eta Sampling.&#13;В единицах 1e-4; разумное значение - 3.&#13;Установите в 0, чтобы отключить.&#13;См. статью Truncation Sampling as Language Model Desmoothing от Хьюитт и др. (2022) для получения подробной информации.",
"Learn how to contribute your idle GPU cycles to the Horde": "Узнайте, как внести свой вклад в свои свободные GPU-циклы в орду",
"Learn how to contribute your idle GPU cycles to the Horde": "Узнайте, как использовать время простоя вашего GPU для помощи Horde",
"Use the appropriate tokenizer for Google models via their API. Slower prompt processing, but offers much more accurate token counting.": "Используйте соответствующий токенизатор для моделей Google через их API. Медленная обработка подсказок, но предлагает намного более точный подсчет токенов.",
"Load koboldcpp order": "Загрузить порядок из koboldcpp",
"Use Google Tokenizer": "Использовать токенизатор Google",
@@ -744,7 +741,7 @@
"Last Assistant Prefix": "Последний префикс ассистента",
"System Instruction Prefix": "Префикс системной инструкции",
"User Filler Message": "Принудительное сообщение пользователя",
"Permanent": "перманентных",
"Permanent": "постоянных",
"Alt. Greetings": "Др. варианты",
"Smooth Streaming": "Плавный стриминг",
"Save checkpoint": "Сохранить чекпоинт",
@@ -1227,7 +1224,6 @@
"JSON-serialized array of strings.": "Список строк в формате JSON.",
"Mirostat_desc": "Mirostat - своего рода термометр, измеряющий перплексию для выводимого текста.\nMirostat подгоняет перплексию генерируемого текста к перплексии входного текста, что позволяет избежать повторов.\n(когда по мере генерации текста авторегрессионным инференсом, перплексия всё больше приближается к нулю)\n а также ловушки перплексии (когда перплексия начинает уходить в сторону)\nБолее подробное описание в статье Mirostat: A Neural Text Decoding Algorithm that Directly Controls Perplexity by Basu et al. (2020).\nРежим выбирает версию Mirostat. 0=отключить, 1=Mirostat 1.0 (только llama.cpp), 2=Mirostat 2.0.",
"Helpful tip coming soon.": "Подсказку скоро добавим.",
"Temperature_Last_desc": "Использовать Temperature сэмплер в последнюю очередь. Это почти всегда разумно.\nПри включении: сначала выборка набора правдоподобных токенов, затем применение Temperature для корректировки их относительных вероятностей (технически, логитов).\nПри отключении: сначала применение Temperature для корректировки относительных вероятностей ВСЕХ токенов, затем выборка правдоподобных токенов из этого.\nОтключение Temperature Last увеличивает вероятности в хвосте распределения, что увеличивает шансы получить несогласованный ответ.",
"Speculative Ngram": "Speculative Ngram",
"Use a different speculative decoding method without a draft model": "Use a different speculative decoding method without a draft model.\rUsing a draft model is preferred. Speculative ngram is not as effective.",
"Spaces Between Special Tokens": "Spaces Between Special Tokens",
@@ -1256,7 +1252,6 @@
"DreamGen Model": "Модель DreamGen",
"vllm-project/vllm": "vllm-project/vllm (режим враппера OpenAI API)",
"vLLM API key": "Ключ от API vLLM",
"Example: 127.0.0.1:8000": "Example: http://127.0.0.1:8000",
"vLLM Model": "Модель vLLM",
"Aphrodite Model": "Модель Aphrodite",
"Peek a password": "Посмотреть пароль",
@@ -1734,7 +1729,7 @@
"markdown_hotkeys_desc": "Включить горячие клавиши для вставки символов разметки в некоторых полях ввода. См. '/help hotkeys'.",
"Save and Update": "Сохранить и обновить",
"Profile name:": "Название профиля:",
"API returned an error": "API вернуло ошибку",
"API returned an error": "API ответило ошибкой",
"Failed to save preset": "Не удалось сохранить пресет",
"Preset name should be unique.": "Название пресета должно быть уникальным.",
"Invalid file": "Невалидный файл",
@@ -1756,8 +1751,7 @@
"dot quota_error": "имеется достаточно кредитов.",
"If you have sufficient credits, please try again later.": "Если кредитов достаточно, то повторите попытку позднее.",
"Proxy preset '${0}' not found": "Пресет '${0}' не найден",
"Window.ai returned an error": "Window.ai вернул ошибку",
"Get it here:": "Загрузите здесь:",
"Window.ai returned an error": "Window.ai ответил ошибкой",
"Extension is not installed": "Расширение не установлено",
"Update or remove your reverse proxy settings.": "Измените или удалите ваши настройки прокси.",
"An error occurred while importing prompts. More info available in console.": "В процессе импорта произошла ошибка. Подробную информацию см. в консоли.",
@@ -1866,7 +1860,7 @@
"Group Chat could not be saved": "Не удалось сохранить групповой чат",
"Deleted group member swiped. To get a reply, add them back to the group.": "Вы пытаетесь свайпнуть удалённого члена группы. Чтобы получить ответ, добавьте этого персонажа обратно в группу.",
"Currently no group selected.": "В данный момент не выбрано ни одной группы.",
"Not so fast! Wait for the characters to stop typing before deleting the group.": "Чуть помедленнее! Перед удалением группы дождитесь, пока персонаж закончит печатать.",
"Not so fast! Wait for the characters to stop typing before deleting the group.": "Чуть помедленнее! Перед удалением группы дождитесь, пока персонажи закончат печатать.",
"Delete the group?": "Удалить группу?",
"This will also delete all your chats with that group. If you want to delete a single conversation, select a \"View past chats\" option in the lower left menu.": "Вместе с ней будут удалены и все её чаты. Если требуется удалить только один чат, воспользуйтесь кнопкой \"Все чаты\" в меню в левом нижнем углу.",
"Can't peek a character while group reply is being generated": "Невозможно открыть карточку персонажа во время генерации ответа",
@@ -1997,7 +1991,7 @@
"Default persona deleted": "Удалена персона по умолчанию",
"The locked persona was deleted. You will need to set a new persona for this chat.": "Удалена привязанная к чату персона. Вам будет необходимо выбрать новую фиксированную персону для этого чата.",
"Persona deleted": "Персона удалена",
"You must bind a name to this persona before you can set it as the default.": "Прежде чем установить эту персону в качестве персоны по умолчанию, ей необходимо задать имя.",
"You must bind a name to this persona before you can set it as the default.": "Прежде чем установить эту персону в качестве персоны по умолчанию, ей необходимо присвоить имя.",
"Persona name not set": "У персоны отсутствует имя",
"Are you sure you want to remove the default persona?": "Вы точно хотите снять статус персоны по умолчанию?",
"This persona will no longer be used by default when you open a new chat.": "Эта персона больше не будет автоматически выбираться при старте нового чата",
@@ -2038,7 +2032,7 @@
"[Currently loaded]": "[Загруженная сейчас]",
"Search providers...": "Искать по провайдерам...",
"Automatically chooses an alternative provider if chosen providers can't serve your request.": "Автоматически переключаться на другого провайдера, если текущий не может обслужить запрос.",
"Example: 127.0.0.1:8000": "Пример: 127.0.0.1:8000",
"Example: http://127.0.0.1:8000": "Пример: http://127.0.0.1:8000",
"Edit a connection profile": "Редактировать профиль соединения",
"System Prompt Name": "Название системного промпта",
"Use System Prompt": "Использовать системный промпт",
@@ -2203,5 +2197,146 @@
"Input:": "Входные данные:",
"Tokenized text:": "Токенизированный текст:",
"Token IDs:": "Идентификаторы токенов:",
"Tokens:": "Токенов:"
"Tokens:": "Токенов:",
"Max prompt cost:": "Макс. стоимость промпта:",
"Reset custom sampler selection": "Сбросить подборку семплеров",
"Here you can toggle the display of individual samplers. (WIP)": "Здесь можно включить или выключить отображение каждого из сэмплеров отдельно. (WIP)",
"Request Model Reasoning": "Запрашивать цепочку рассуждений",
"Reasoning": "Рассуждения / Reasoning",
"Auto-Parse": "Авто-парсинг",
"reasoning_auto_parse": "Автоматически считывать блоки рассуждений, расположенные между префиксом и суффиксом рассуждений. Для работы должно быть указано и то, и другое.",
"Auto-Expand": "Разворачивать",
"reasoning_auto_expand": "Автоматически разворачивать блоки рассуждений.",
"Show Hidden": "Показывать время",
"reasoning_show_hidden": "Отображать затраченное на рассуждения время для моделей со скрытой цепочкой рассуждений",
"Add to Prompts": "Добавлять в промпт",
"reasoning_add_to_prompts": "Добавлять существующие блоки рассуждений в промпт. Для добавления новых используйте меню редактирования сообщений.",
"reasoning_max_additions": "Макс. кол-во блоков рассуждений в промпте, считается от последнего сообщения",
"Max": "Макс.",
"Reasoning Formatting": "Форматирование рассуждений",
"Prefix": "Префикс",
"Suffix": "Постфикс",
"Separator": "Разделитель",
"reasoning_separator": "Вставляется между рассуждениями и содержанием самого сообщения.",
"reasoning_prefix": "Вставляется перед рассуждениями.",
"reasoning_suffix": "Вставляется после рассуждений.",
"Seed_desc": "Фиксированное значение зерна позволяет получать предсказуемые, одинаковые результаты на одинаковых настройках. Поставьте -1 для рандомного зерна.",
"# of Beams": "Кол-во лучей",
"The number of sequences generated at each step with Beam Search.": "Кол-во вариантов, генерируемых Beam Search на каждом шаге работы.",
"Penalize sequences based on their length.": "Штрафует строки в зависимости от длины",
"Controls the stopping condition for beam search. If checked, the generation stops as soon as there are '# of Beams' sequences. If not checked, a heuristic is applied and the generation is stopped when it's very unlikely to find better candidates.": "Определяет, когда останавливать работу Beam Search. Поставив галочку, вы укажете поиску остановиться тогда, когда будет достигнуто кол-во лучей из соответствующего поля. Если галочку не отмечать, то генерация остановится тогда, когда сочтёт, что дальше найти лучших кандидатов слишком маловероятно.",
"A greedy, brute-force algorithm used in LLM sampling to find the most likely sequence of words or tokens. It expands multiple candidate sequences at once, maintaining a fixed number (beam width) of top sequences at each step.": "Жадный алгоритм LLM-сэмплинга, подбирающий наиболее вероятную последовательность слов или токенов путём исследования и расширения сразу нескольких вариантов. На каждом шаге он удерживает фиксированное кол-во самых подходящих вариантов (ширина луча).",
"Smooth_Sampling_desc": "Изменяет распределение с помощью квадратичных и кубических преобразований. Снижение Коэффициента сглаживания даёт более креативные ответы, обычно идеальное значение находится в диапазоне 0.2-0.3 (при кривой сглаживания=1.0). Повышение значения Кривой сглаживания сделает кривую круче, что приведёт к более агрессивной фильтрации маловероятных вариантов. Установив Кривую сглаживания = 1.0, вы фактически нейтрализуете этот параметр и будете работать только с Коэффициентом",
"Temperature_Last_desc": "Применять сэмплер Температуры в последнюю очередь. Почти всегда оправдано.\nПри включении: сначала все токены семплируются, и затем температура регулирует распределение у оставшихся (технически, у оставшихся логитов).\nПри выключении: сначала температура настраивает распределение ВСЕХ токенов, и потом они семплируются уже с этим обновлённым распределением.\nПри отключении этой опции токены в хвосте получают больше шансов попасть в итоговую последовательность, что может привести к менее связным и логичным ответам.",
"Swipe # for All Messages": "Номер свайпа на всех сообщениях",
"Display swipe numbers for all messages, not just the last.": "Отображать номер свайпа для всех сообщений, а не только для последнего.",
"Penalty Range": "Окно для штрафа",
"Never": "Никогда",
"Groups and Past Personas": "Для групп и прошлых персон",
"Always": "Всегда",
"Request model reasoning": "Запрашивать рассуждения",
"Allows the model to return its thinking process.": "Позволяет модели высылать в ответе свою цепочку рассуждений.",
"Rename Persona": "Переименовать персону",
"Change Persona Image": "Изменить изображение персоны",
"Duplicate Persona": "Клонировать персону",
"Delete Persona": "Удалить персону",
"Enter a new name for this persona:": "Введите новое имя персоны:",
"Connections": "Связи",
"Click to select this as default persona for the new chats. Click again to remove it.": "Нажмите, чтобы установить эту персону стандартной для всех новых чатов. Нажмите ещё раз, чтобы отключить.",
"Character": "Персонаж",
"Click to lock your selected persona to the current character. Click again to remove the lock.": "Нажмите, чтобы закрепить эту персону для текущего персонажа. Нажмите ещё раз, чтобы открепить.",
"Chat": "Чат",
"[No character connections. Click one of the buttons above to connect this persona.]": "[Связи отсутствуют. Нажмите на одну из кнопок выше, чтобы создать.]",
"Global Settings": "Общие настройки",
"Allow multiple persona connections per character": "Разрешить привязывать несколько персон к одному персонажу",
"When multiple personas are connected to a character, a popup will appear to select which one to use": "При связывании нескольких персон с персонажем, будет появляться окошко с предложением выбрать нужную.",
"Auto-lock a chosen persona to the chat": "Автоматически привязывать выбранную персону к чату",
"Whenever a persona is selected, it will be locked to the current chat and automatically selected when the chat is opened.": "При выборе новой персоны она автоматически будет привязана к текущему чату, и будет выбираться при его открытии.",
"Current Persona": "Текущая персона",
"The chat has been successfully converted!": "Чат успешно преобразован!",
"Manual": "Когда вы скажете",
"Auto Mode delay": "Задержка авто-режима",
"Use tag as folder": "Тег-папка",
"All connections to ${0} have been removed.": "Все связи с персонажем ${0} были удалены.",
"Personas Unlocked": "Персоны отвязаны",
"Remove All Connections": "Удалить все связи",
"Persona ${0} selected and auto-locked to current chat": "Персона ${0} выбрана и автоматически закреплена за этим чатом",
"This persona is only temporarily chosen. Click for more info.": "Данная персона выбрана лишь временно. Нажмите, чтобы узнать больше.",
"Temporary Persona": "Временная персона",
"A different persona is locked to this chat, or you have a different default persona set. The currently selected persona will only be temporary, and resets on reload. Consider locking this persona to the chat if you want to permanently use it.": "К этому чату уже привязана иная персона, либо у вас выбрана иная персона по-умолчанию. Выбранная в данный момент персона будет временной, и сбросится после перезагрузки. Если хотите всегда использовать её в этом чате, советуем её прикрепить.",
"Current Persona: ${0}": "Выбранная персона: ${0}",
"Chat persona: ${0}": "Персона для этого чата: ${0}",
"Default persona: ${0}": "Персона по умолчанию (стандартная): ${0}",
"Persona ${0} is now unlocked from this chat.": "Персона ${0} отвязана от этого чата.",
"Persona Unlocked": "Персона отвязана",
"Persona ${0} is now unlocked from character ${1}.": "Персона ${0} отвязана от персонажа ${1}.",
"Persona Not Found": "Персона не найдена",
"Persona Locked": "Персона закреплена",
"User persona ${0} is locked to character ${1}${2}": "Персона ${0} прикреплена к персонажу ${1}${2}",
"Persona Name Not Set": "У персоны отсутствует имя",
"You must bind a name to this persona before you can set a lorebook.": "Перед привязкой лорбука персоне необходимо присвоить имя.",
"Default Persona Removed": "Персона по умолчанию снята",
"Persona is locked to the current character": "Персона закреплена за этим персонажем",
"Persona is locked to the current chat": "Персона закреплена за этим чатом",
"characters": "перс.",
"character": "персонаж",
"in this group": "в группе",
"Chatting Since": "Первая беседа",
"Context": "Контекст",
"Response": "Ответ",
"Connected": "Подключено",
"Enter new background name:": "Введите новое название для фона:",
"AI Horde Website": "Сайт AI Horde",
"Enable web search": "Включить поиск в Интернете",
"Use search capabilities provided by the backend.": "Разрешить использование предоставляемых бэкендом функций поиска.",
"Request inline images": "Запрашивать inline-изображения",
"Allows the model to return image attachments.": "Разрешить модели отправлять вложения в виде картинок.",
"Request inline images_desc_2": "Не совместимо со следующим функционалом: вызов функций, поиск в Интернете, системный промпт.",
"Connected Personas": "Связанные персоны",
"[Currently no personas connected]": "[Связанных персон нет]",
"The following personas are connected to the current character.\n\nClick on a persona to select it for the current character.\nShift + Click to unlink the persona from the character.": "С этим персонажем связаны следующие персоны.\n\nНажмите на персону, чтобы выбрать её для данного персонажа.\nShift + ЛКМ, чтобы её отвязать.",
"Persona Connections": "Связи с персонами",
"Pooled order": "Если уже давно не отвечали",
"Attach a File": "Приложить файл",
"Attach a file or image to a current chat.": "Приложить файл или изображение к текущему чату",
"Remove the file": "Удалить файл",
"Delete the Chat File?": "Удалить чат?",
"Forbidden": "Доступ запрещён",
"To view your API keys here, set the value of allowKeysExposure to true in config.yaml file and restart the SillyTavern server.": "Чтобы видеть здесь ваши API-ключи, установите параметр allowKeysExposure в config.yaml в положение true, после чего перезапустите сервер SillyTavern.",
"Invalid endpoint URL. Requests may fail.": "Некорректный адрес эндпоинта. Запросы могут не проходить.",
"How to install extensions?": "Как устанавливать расширения?",
"Click the flashing button to install extensions.": "Чтобы их установить, нажмите на мигающую кнопку.",
"ext_regex_reasoning_desc": "Содержимое блоков рассуждений. При отмеченной галочке \"Только промпт\" будут также обработаны добавленные в промпт рассуждения.",
"Macro in Find Regex": "Макросы в рег. выражении",
"Don't substitute": "Не заменять",
"Substitute (raw)": "Заменять в \"чистом\" виде",
"Substitute (escaped)": "Заменять после экранирования",
"ext_regex_other_options_desc": "По умолчанию, расширение вносит изменения в сам файл чата.\nПри включении одной из опций (или обеих), файл чата останется нетронутым, при этом сами изменения по-прежнему будут действовать.",
"ext_regex_flags_help": "Нажмите, чтобы узнать больше о флагах в рег. выражениях.",
"Applies to all matches": "Заменяет все вхождения",
"Applies to the first match": "Заменяет первое вхождение",
"Case insensitive": "Не чувствительно к регистру",
"Case sensitive": "Чувствительно к регистру",
"Find Regex is empty": "Рег. выражение не указано",
"Click the button to save it as a file.": "Нажмите на кнопку справа, чтобы сохранить его в файл.",
"Export as JSONL": "Экспорт в формате JSONL",
"Thought for some time": "Какое-то время заняли размышления",
"Thinking...": "В раздумьях...",
"Thought for ${0}": "Размышления заняли ${0}",
"Hidden reasoning - Add reasoning block": "Рассуждения скрыты - Добавить блок рассуждений",
"Add reasoning block": "Добавить блок рассуждений",
"Edit reasoning": "Редактировать рассуждения",
"Copy reasoning": "Скопировать рассуждения",
"Confirm Edit": "Подтвердить",
"Remove reasoning": "Удалить рассуждения",
"Cancel edit": "Отменить редактирование",
"Remove Reasoning": "Удалить рассуждения",
"Are you sure you want to clear the reasoning?<br />Visible message contents will stay intact.": "Вы точно хотите удалить блок рассуждений?<br />Основное сообщение останется на месте.",
"Reasoning Parse": "Парсинг рассуждений",
"Both prefix and suffix must be set in the Reasoning Formatting settings.": "В настройках форматирования рассуждений должны быть заданы префикс и суффикс.",
"Invalid return type '${0}', defaulting to 'reasoning'.": "Некорректный возвращаемый тип, используем стандартный 'reasoning'.",
"Reasoning already exists.": "Рассуждения уже присутствуют.",
"Edit Message": "Редактирование",
"Status check bypassed": "Проверка статуса отключена",
"Valid": "Работает"
}

View File

@@ -318,23 +318,23 @@
"flag": "прапорцем",
"API key (optional)": "Ключ API (необов'язково)",
"Server url": "URL-адреса сервера",
"Example: 127.0.0.1:5000": "Приклад: 127.0.0.1:5000",
"Example: http://127.0.0.1:5000": "Приклад: http://127.0.0.1:5000",
"Custom model (optional)": "Власна модель (необов'язково)",
"vllm-project/vllm": "vllm-project/vllm (режим оболонки OpenAI API)",
"vLLM API key": "Ключ API vLLM",
"Example: 127.0.0.1:8000": "Приклад: http://127.0.0.1:8000",
"Example: http://127.0.0.1:8000": "Приклад: http://127.0.0.1:8000",
"vLLM Model": "Модель vLLM",
"PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (режим OpenAI API)",
"Aphrodite API key": "Ключ API для Aphrodite",
"Aphrodite Model": "Модель Афродіта",
"ggerganov/llama.cpp": "ggerganov/llama.cpp (сервер виведення)",
"Example: 127.0.0.1:8080": "Приклад: 127.0.0.1:8080",
"Example: 127.0.0.1:11434": "Приклад: 127.0.0.1:11434",
"Example: http://127.0.0.1:8080": "Приклад: http://127.0.0.1:8080",
"Example: http://127.0.0.1:11434": "Приклад: http://127.0.0.1:11434",
"Ollama Model": "Модель Ollama",
"Download": "Завантажити",
"Tabby API key": "Ключ API для Tabby",
"koboldcpp API key (optional)": "API-ключ koboldcpp (необов’язково)",
"Example: 127.0.0.1:5001": "Приклад: 127.0.0.1:5001",
"Example: http://127.0.0.1:5001": "Приклад: http://127.0.0.1:5001",
"Authorize": "Авторизувати",
"Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "Отримайте свій токен API OpenRouter за допомогою OAuth. Вас буде перенаправлено на openrouter.ai",
"Bypass status check": "Обійти перевірку статусу",

View File

@@ -318,23 +318,23 @@
"flag": "cờ",
"API key (optional)": "Key API (tùy chọn)",
"Server url": "URL máy chủ",
"Example: 127.0.0.1:5000": "Ví dụ: 127.0.0.1:5000",
"Example: http://127.0.0.1:5000": "Ví dụ: http://127.0.0.1:5000",
"Custom model (optional)": "Model tùy chỉnh (tùy chọn)",
"vllm-project/vllm": "vllm-project/vllm (Chế độ trình bao bọc API OpenAI)",
"vLLM API key": "Key API vLLM",
"Example: 127.0.0.1:8000": "Ví dụ: http://127.0.0.1:8000",
"Example: http://127.0.0.1:8000": "Ví dụ: http://127.0.0.1:8000",
"vLLM Model": "Model vLLM",
"PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (Chế độ đóng gói cho Giao diện lập trình ứng dụng OpenAI)",
"Aphrodite API key": "Key API Aphrodite",
"Aphrodite Model": "Moddel cho Aphrodite",
"ggerganov/llama.cpp": "ggerganov/llama.cpp",
"Example: 127.0.0.1:8080": "Ví dụ: 127.0.0.1:8080",
"Example: 127.0.0.1:11434": "Ví dụ: 127.0.0.1:11434",
"Example: http://127.0.0.1:8080": "Ví dụ: http://127.0.0.1:8080",
"Example: http://127.0.0.1:11434": "Ví dụ: http://127.0.0.1:11434",
"Ollama Model": "Model Ollama",
"Download": "Tải xuống",
"Tabby API key": "Key API Tabby",
"koboldcpp API key (optional)": "Key API koboldcpp (tùy chọn)",
"Example: 127.0.0.1:5001": "Ví dụ: 127.0.0.1:5001",
"Example: http://127.0.0.1:5001": "Ví dụ: http://127.0.0.1:5001",
"Cho phép": "Ủy quyền",
"Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "Nhận mã thông báo API OpenRouter của bạn bằng cách sử dụng luồng OAuth. Bạn sẽ được chuyển hướng đến openrouter.ai",
"Bypass status check": "Bỏ qua check trạng thái",

View File

@@ -347,7 +347,7 @@
"Mancer Model": "Mancer 模型",
"API key (optional)": "API密钥可选",
"Server url": "服务器URL",
"Example: 127.0.0.1:5000": "示例127.0.0.1:5000",
"Example: http://127.0.0.1:5000": "示例:http://127.0.0.1:5000",
"Model ID (optional)": "模型 ID可选",
"Make sure you run it with": "确保您在运行时加上",
"flag": "标志",
@@ -364,7 +364,7 @@
"No model description": "[无描述]",
"vllm-project/vllm": "vllm-project/vllmOpenAI API 包装器模式)",
"vLLM API key": "vLLM API 密钥",
"Example: 127.0.0.1:8000": "示例http://127.0.0.1:8000",
"Example: http://127.0.0.1:8000": "示例http://127.0.0.1:8000",
"vLLM Model": "vLLM 模型",
"HuggingFace Token": "HuggingFace 代币",
"Endpoint URL": "端点 URL",
@@ -373,8 +373,8 @@
"Aphrodite API key": "Aphrodite API 密钥",
"Aphrodite Model": "Aphrodite 模型",
"ggerganov/llama.cpp": "ggerganov/llama.cpp",
"Example: 127.0.0.1:8080": "示例127.0.0.1:8080",
"Example: 127.0.0.1:11434": "示例127.0.0.1:11434",
"Example: http://127.0.0.1:8080": "示例:http://127.0.0.1:8080",
"Example: http://127.0.0.1:11434": "示例:http://127.0.0.1:11434",
"Ollama Model": "Ollama 模型",
"Download": "下载",
"Tabby API key": "Tabby API 密钥",
@@ -382,7 +382,7 @@
"must be set in Tabby's config.yml to switch models.": "必须在Tabby的config.yml内设置以切换模型",
"Use an admin API key.": "使用管理员API密钥。",
"koboldcpp API key (optional)": "koboldcpp API 密钥(可选)",
"Example: 127.0.0.1:5001": "示例127.0.0.1:5001",
"Example: http://127.0.0.1:5001": "示例:http://127.0.0.1:5001",
"Bypass status check": "跳过状态检查",
"Derive context size from backend": "从后端获取上下文长度",
"Authorize": "授权",

View File

@@ -319,23 +319,23 @@
"flag": "旗標",
"API key (optional)": "API 金鑰(可選)",
"Server url": "伺服器 URL",
"Example: 127.0.0.1:5000": "範例127.0.0.1:5000",
"Example: http://127.0.0.1:5000": "範例:http://127.0.0.1:5000",
"Custom model (optional)": "自訂模型(選填)",
"vllm-project/vllm": "vllm-project/vllm",
"vLLM API key": "vLLM API 金鑰",
"Example: 127.0.0.1:8000": "範例127.0.0.1:8000",
"Example: http://127.0.0.1:8000": "範例:http://127.0.0.1:8000",
"vLLM Model": "vLLM 模型",
"PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite 引擎",
"Aphrodite API key": "Aphrodite API 金鑰",
"Aphrodite Model": "Aphrodite 模型",
"ggerganov/llama.cpp": "ggerganov/llama.cpp",
"Example: 127.0.0.1:8080": "範例127.0.0.1:8080",
"Example: 127.0.0.1:11434": "範例127.0.0.1:11434",
"Example: http://127.0.0.1:8080": "範例:http://127.0.0.1:8080",
"Example: http://127.0.0.1:11434": "範例:http://127.0.0.1:11434",
"Ollama Model": "Ollama 模型",
"Download": "下載",
"Tabby API key": "Tabby API 金鑰",
"koboldcpp API key (optional)": "KoboldCpp API 金鑰(可選)",
"Example: 127.0.0.1:5001": "範例127.0.0.1:5001",
"Example: http://127.0.0.1:5001": "範例:http://127.0.0.1:5001",
"Authorize": "授權",
"Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "使用 OAuth 流程取得您的 OpenRouter API 符元。您將被重新導向到 openrouter.ai",
"Bypass status check": "繞過狀態檢查",

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,10 @@
'use strict';
import { DOMPurify, Popper } from '../lib.js';
import { DOMPurify } from '../lib.js';
import { event_types, eventSource, is_send_press, main_api, substituteParams } from '../script.js';
import { is_group_generating } from './group-chats.js';
import { Message, TokenHandler } from './openai.js';
import { Message, MessageCollection, TokenHandler } from './openai.js';
import { power_user } from './power-user.js';
import { debounce, waitUntilCondition, escapeHtml } from './utils.js';
import { debounce_timeout } from './constants.js';
@@ -1440,36 +1440,8 @@ class PromptManager {
footerDiv.querySelector('select').selectedIndex = selectedPromptIndex;
// Add prompt export dialogue and options
const exportForCharacter = await renderTemplateAsync('promptManagerExportForCharacter');
const exportPopup = await renderTemplateAsync('promptManagerExportPopup', { isGlobalStrategy: 'global' === this.configuration.promptOrder.strategy, exportForCharacter });
rangeBlockDiv.insertAdjacentHTML('beforeend', exportPopup);
// Destroy previous popper instance if it exists
if (this.exportPopper) {
this.exportPopper.destroy();
}
this.exportPopper = Popper.createPopper(
document.getElementById('prompt-manager-export'),
document.getElementById('prompt-manager-export-format-popup'),
{ placement: 'bottom' },
);
const showExportSelection = () => {
const popup = document.getElementById('prompt-manager-export-format-popup');
const show = popup.hasAttribute('data-show');
if (show) popup.removeAttribute('data-show');
else popup.setAttribute('data-show', '');
this.exportPopper.update();
};
footerDiv.querySelector('#prompt-manager-import').addEventListener('click', this.handleImport);
footerDiv.querySelector('#prompt-manager-export').addEventListener('click', showExportSelection);
rangeBlockDiv.querySelector('.export-promptmanager-prompts-full').addEventListener('click', this.handleFullExport);
rangeBlockDiv.querySelector('.export-promptmanager-prompts-character')?.addEventListener('click', this.handleCharacterExport);
footerDiv.querySelector('#prompt-manager-export').addEventListener('click', this.handleFullExport);
}
}

View File

@@ -407,9 +407,9 @@ function RA_autoconnect(PrevApi) {
|| (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)
|| (secret_state[SECRET_KEYS.BLOCKENTROPY] && oai_settings.chat_completion_source == chat_completion_sources.BLOCKENTROPY)
|| (secret_state[SECRET_KEYS.NANOGPT] && oai_settings.chat_completion_source == chat_completion_sources.NANOGPT)
|| (secret_state[SECRET_KEYS.DEEPSEEK] && oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK)
|| (secret_state[SECRET_KEYS.XAI] && oai_settings.chat_completion_source == chat_completion_sources.XAI)
|| (isValidUrl(oai_settings.custom_url) && oai_settings.chat_completion_source == chat_completion_sources.CUSTOM)
) {
$('#api_button_openai').trigger('click');
@@ -1018,6 +1018,14 @@ export function initRossMods() {
return false;
}
function isModifiedKeyboardEvent(event) {
return (event instanceof KeyboardEvent &&
event.shiftKey ||
event.ctrlKey ||
event.altKey ||
event.metaKey);
}
$(document).on('keydown', async function (event) {
await processHotkeys(event.originalEvent);
});
@@ -1141,9 +1149,10 @@ export function initRossMods() {
$('#send_textarea').val() === '' &&
$('#character_popup').css('display') === 'none' &&
$('#shadow_select_chat_popup').css('display') === 'none' &&
!isInputElementInFocus()
!isInputElementInFocus() &&
!isModifiedKeyboardEvent(event)
) {
$('.swipe_left:last').click();
$('.swipe_left:last').trigger('click', { source: 'keyboard', repeated: event.repeat });
return;
}
}
@@ -1154,9 +1163,10 @@ export function initRossMods() {
$('#send_textarea').val() === '' &&
$('#character_popup').css('display') === 'none' &&
$('#shadow_select_chat_popup').css('display') === 'none' &&
!isInputElementInFocus()
!isInputElementInFocus() &&
!isModifiedKeyboardEvent(event)
) {
$('.swipe_right:last').click();
$('.swipe_right:last').trigger('click', { source: 'keyboard', repeated: event.repeat });
return;
}
}

View File

@@ -17,6 +17,7 @@ import { SlashCommand } from './slash-commands/SlashCommand.js';
import { ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js';
export { MODULE_NAME as NOTE_MODULE_NAME };
import { t } from './i18n.js';
import { MacrosParser } from './macros.js';
const MODULE_NAME = '2_floating_prompt'; // <= Deliberate, for sorting lower than memory
@@ -576,4 +577,8 @@ export function initAuthorsNote() {
`,
}));
eventSource.on(event_types.CHAT_CHANGED, onChatChanged);
MacrosParser.registerMacro('authorsNote', () => chat_metadata[metadata_keys.prompt] ?? '', t`The contents of the Author's Note`);
MacrosParser.registerMacro('charAuthorsNote', () => this_chid !== undefined ? (extension_settings.note.chara.find((e) => e.name === getCharaFilename())?.prompt ?? '') : '', t`The contents of the Character Author's Note`);
MacrosParser.registerMacro('defaultAuthorsNote', () => extension_settings.note.default ?? '', t`The contents of the Default Author's Note`);
}

View File

@@ -3,10 +3,8 @@ import { debounce, escapeRegex } from '../utils.js';
import { AutoCompleteOption } from './AutoCompleteOption.js';
import { AutoCompleteFuzzyScore } from './AutoCompleteFuzzyScore.js';
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}*/

View File

@@ -1,4 +1,3 @@
import { SlashCommandNamedArgumentAutoCompleteOption } from '../slash-commands/SlashCommandNamedArgumentAutoCompleteOption.js';
import { AutoCompleteOption } from './AutoCompleteOption.js';

View File

@@ -1,19 +1,18 @@
import { SlashCommand } from '../slash-commands/SlashCommand.js';
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;
/**@type {HTMLElement}*/ dom;
/**@type {(input:string)=>boolean}*/ matchProvider;
/**@type {(input:string)=>string}*/ valueProvider;
/**@type {boolean}*/ makeSelectable = false;
/** @type {string} */ name;
/** @type {string} */ typeIcon;
/** @type {string} */ type;
/** @type {number} */ nameOffset = 0;
/** @type {AutoCompleteFuzzyScore} */ score;
/** @type {string} */ replacer;
/** @type {HTMLElement} */ dom;
/** @type {(input:string)=>boolean} */ matchProvider;
/** @type {(input:string)=>string} */ valueProvider;
/** @type {boolean} */ makeSelectable = false;
/**

View File

@@ -5,6 +5,7 @@ import { saveMetadataDebounced } from './extensions.js';
import { SlashCommand } from './slash-commands/SlashCommand.js';
import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
import { flashHighlight, stringFormat } from './utils.js';
import { t } from './i18n.js';
const BG_METADATA_KEY = 'custom_background';
const LIST_METADATA_KEY = 'chat_backgrounds';
@@ -243,7 +244,7 @@ async function getNewBackgroundName(referenceElement) {
const fileExtension = oldBg.split('.').pop();
const fileNameBase = isCustom ? oldBg.split('/').pop() : oldBg;
const oldBgExtensionless = fileNameBase.replace(`.${fileExtension}`, '');
const newBgExtensionless = await callPopup('<h3>Enter new background name:</h3>', 'input', oldBgExtensionless);
const newBgExtensionless = await callPopup('<h3>' + t`Enter new background name:` + '</h3>', 'input', oldBgExtensionless);
if (!newBgExtensionless) {
console.debug('no new_bg_extensionless');

View File

@@ -1,7 +1,6 @@
import {
characters,
saveChat,
system_messages,
system_message_types,
this_chid,
openCharacterChat,
@@ -13,7 +12,7 @@ import {
saveChatConditional,
saveItemizedPrompts,
} from '../script.js';
import { humanizedDateTime, getMessageTimeStamp } from './RossAscends-mods.js';
import { humanizedDateTime } from './RossAscends-mods.js';
import {
getGroupPastChats,
group_activation_strategy,
@@ -156,7 +155,7 @@ export async function createBranch(mesId) {
if (selected_group) {
await saveGroupBookmarkChat(selected_group, name, newMetadata, mesId);
} else {
await saveChat(name, newMetadata, mesId);
await saveChat({ chatName: name, withMetadata: newMetadata, mesId });
}
// append to branches list if it exists
// otherwise create it
@@ -212,7 +211,7 @@ export async function createNewBookmark(mesId, { forceName = null } = {}) {
if (selected_group) {
await saveGroupBookmarkChat(selected_group, name, newMetadata, mesId);
} else {
await saveChat(name, newMetadata, mesId);
await saveChat({ chatName: name, withMetadata: newMetadata, mesId });
}
lastMes.extra['bookmark_link'] = name;
@@ -358,7 +357,7 @@ export async function convertSoloToGroupChat() {
// Click on the freshly selected group to open it
await openGroupById(group.id);
toastr.success('The chat has been successfully converted!');
toastr.success(t`The chat has been successfully converted!`);
}
/**

View File

@@ -47,6 +47,10 @@ const hash_derivations = {
// gemma-2-2b-it
'Gemma 2'
,
'7de1c58e208eda46e9c7f86397df37ec49883aeece39fb961e0a6b24088dd3c4':
// gemma-3
'Gemma 2'
,
// Cohere
'3b54f5c219ae1caa5c0bb2cdc7c001863ca6807cf888e4240e8739fa7eb9e02e':

View File

@@ -130,9 +130,10 @@ function getConverter(type) {
* @param {number} start Starting message ID
* @param {number} end Ending message ID (inclusive)
* @param {boolean} unhide If true, unhide the messages instead.
* @param {string} nameFitler Optional name filter
* @returns {Promise<void>}
*/
export async function hideChatMessageRange(start, end, unhide) {
export async function hideChatMessageRange(start, end, unhide, nameFitler = null) {
if (isNaN(start)) return;
if (!end) end = start;
const hide = !unhide;
@@ -140,6 +141,7 @@ export async function hideChatMessageRange(start, end, unhide) {
for (let messageId = start; messageId <= end; messageId++) {
const message = chat[messageId];
if (!message) continue;
if (nameFitler && message.name !== nameFitler) continue;
message.is_system = hide;
@@ -457,7 +459,7 @@ export async function appendFileContent(message, messageText) {
* @copyright https://github.com/kwaroran/risuAI
*/
export function encodeStyleTags(text) {
const styleRegex = /<style>(.+?)<\/style>/gms;
const styleRegex = /<style>(.+?)<\/style>/gims;
return text.replaceAll(styleRegex, (_, match) => {
return `<custom-style>${escape(match)}</custom-style>`;
});
@@ -573,8 +575,8 @@ export function isExternalMediaAllowed() {
return !power_user.forbid_external_media;
}
async function enlargeMessageImage() {
const mesBlock = $(this).closest('.mes');
function expandMessageImage(event) {
const mesBlock = $(event.currentTarget).closest('.mes');
const mesId = mesBlock.attr('mesid');
const message = chat[mesId];
const imgSrc = message?.extra?.image;
@@ -618,7 +620,12 @@ async function enlargeMessageImage() {
popup.completeCancelled();
});
await popup.show();
popup.show();
return img;
}
function expandAndZoomMessageImage(event) {
expandMessageImage(event).click();
}
async function deleteMessageImage() {
@@ -1506,7 +1513,7 @@ jQuery(function () {
embedMessageFile(messageId, messageBlock);
});
$(document).on('click', '.editor_maximize', function () {
$(document).on('click', '.editor_maximize', async function () {
const broId = $(this).attr('data-for');
const bro = $(`#${broId}`);
const contentEditable = bro.is('[contenteditable]');
@@ -1525,6 +1532,7 @@ jQuery(function () {
textarea.value = String(contentEditable ? bro[0].innerText : bro.val());
textarea.classList.add('height100p', 'wide100p', 'maximized_textarea');
bro.hasClass('monospace') && textarea.classList.add('monospace');
bro.hasClass('mdHotkeys') && textarea.classList.add('mdHotkeys');
textarea.addEventListener('input', function () {
if (contentEditable) {
bro[0].innerText = textarea.value;
@@ -1565,7 +1573,7 @@ jQuery(function () {
});
}
callGenericPopup(wrapper, POPUP_TYPE.TEXT, '', { wide: true, large: true });
await callGenericPopup(wrapper, POPUP_TYPE.TEXT, '', { wide: true, large: true });
});
$(document).on('click', 'body.documentstyle .mes .mes_text', function () {
@@ -1600,7 +1608,8 @@ jQuery(function () {
reloadCurrentChat();
});
$(document).on('click', '.mes_img_enlarge', enlargeMessageImage);
$(document).on('click', '.mes_img', expandMessageImage);
$(document).on('click', '.mes_img_enlarge', expandAndZoomMessageImage);
$(document).on('click', '.mes_img_delete', deleteMessageImage);
$('#file_form_input').on('change', async () => {

View File

@@ -14,3 +14,11 @@ export const debounce_timeout = {
/** [5 sec] For delayed tasks, like auto-saving or completing batch operations that need a significant pause. */
extended: 5000,
};
/**
* Used as an ephemeral key in message extra metadata.
* When set, the message will be excluded from generation
* prompts without affecting the number of chat messages,
* which is needed to preserve world info timed effects.
*/
export const IGNORE_SYMBOL = Symbol.for('ignore');

View File

@@ -1,22 +1,26 @@
import { getPresetManager } from './preset-manager.js';
import { extractMessageFromData, getGenerateUrl, getRequestHeaders } from '../script.js';
import { getTextGenServer } from './textgen-settings.js';
import { extractReasoningFromData } from './reasoning.js';
import { formatInstructModeChat, formatInstructModePrompt, getInstructStoppingSequences, names_behavior_types } from './instruct-mode.js';
import { getStreamingReply, tryParseStreamingError } from './openai.js';
import EventSourceStream from './sse-stream.js';
// #region Type Definitions
/**
* @typedef {Object} TextCompletionRequestBase
* @property {string} prompt - The text prompt for completion
* @property {boolean?} [stream=false] - Whether to stream the response
* @property {number} max_tokens - Maximum number of tokens to generate
* @property {string} [model] - Optional model name
* @property {string} api_type - Type of API to use
* @property {string} [api_server] - Optional API server URL
* @property {number} [temperature] - Optional temperature parameter
* @property {number} [min_p] - Optional min_p parameter
*/
/** @typedef {Record<string, any> & TextCompletionRequestBase} TextCompletionRequest */
/**
* @typedef {Object} TextCompletionPayloadBase
* @property {boolean?} [stream=false] - Whether to stream the response
* @property {string} prompt - The text prompt for completion
* @property {number} max_tokens - Maximum number of tokens to generate
* @property {number} max_new_tokens - Alias for max_tokens
@@ -36,29 +40,49 @@ import { getTextGenServer } from './textgen-settings.js';
/**
* @typedef {Object} ChatCompletionPayloadBase
* @property {boolean?} [stream=false] - Whether to stream the response
* @property {ChatCompletionMessage[]} messages - Array of chat messages
* @property {string} [model] - Optional model name to use for completion
* @property {string} chat_completion_source - Source provider for chat completion
* @property {string} chat_completion_source - Source provider
* @property {number} max_tokens - Maximum number of tokens to generate
* @property {number} [temperature] - Optional temperature parameter for response randomness
* @property {string} [custom_url] - Optional custom URL
* @property {string} [reverse_proxy] - Optional reverse proxy URL
* @property {string} [proxy_password] - Optional proxy password
*/
/** @typedef {Record<string, any> & ChatCompletionPayloadBase} ChatCompletionPayload */
/**
* @typedef {Object} ExtractedData
* @property {string} content - Extracted content.
* @property {string} reasoning - Extracted reasoning.
*/
/**
* @typedef {Object} StreamResponse
* @property {string} text - Generated text.
* @property {string[]} swipes - Generated swipes
* @property {Object} state - Generated state
* @property {string?} [state.reasoning] - Generated reasoning
* @property {string?} [state.image] - Generated image
*/
// #endregion
/**
* Creates & sends a text completion request. Streaming is not supported.
* Creates & sends a text completion request.
*/
export class TextCompletionService {
static TYPE = 'textgenerationwebui';
/**
* @param {TextCompletionRequest} custom
* @param {Record<string, any> & TextCompletionRequestBase & {prompt: string}} custom
* @returns {TextCompletionPayload}
*/
static createRequestData({ prompt, max_tokens, model, api_type, api_server, temperature, ...props }) {
return {
...props,
static createRequestData({ stream = false, prompt, max_tokens, model, api_type, api_server, temperature, min_p, ...props }) {
const payload = {
stream,
prompt,
max_tokens,
max_new_tokens: max_tokens,
@@ -66,60 +90,297 @@ export class TextCompletionService {
api_type,
api_server: api_server ?? getTextGenServer(api_type),
temperature,
stream: false,
min_p,
...props,
};
// Remove undefined values to avoid API errors
Object.keys(payload).forEach(key => {
if (payload[key] === undefined) {
delete payload[key];
}
});
return payload;
}
/**
* Sends a text completion request to the specified server
* @param {TextCompletionPayload} data Request data
* @param {boolean?} extractData Extract message from the response. Default true
* @returns {Promise<string | any>} Extracted data or the raw response
* @param {AbortSignal?} signal
* @returns {Promise<ExtractedData | (() => AsyncGenerator<StreamResponse>)>} If not streaming, returns extracted data; if streaming, returns a function that creates an AsyncGenerator
* @throws {Error}
*/
static async sendRequest(data, extractData = true) {
const response = await fetch(getGenerateUrl(this.TYPE), {
static async sendRequest(data, extractData = true, signal = null) {
if (!data.stream) {
const response = await fetch(getGenerateUrl(this.TYPE), {
method: 'POST',
headers: getRequestHeaders(),
cache: 'no-cache',
body: JSON.stringify(data),
signal: signal ?? new AbortController().signal,
});
const json = await response.json();
if (!response.ok || json.error) {
throw json;
}
if (!extractData) {
return json;
}
return {
content: extractMessageFromData(json, this.TYPE),
reasoning: extractReasoningFromData(json, {
mainApi: this.TYPE,
textGenType: data.api_type,
ignoreShowThoughts: true,
}),
};
}
const response = await fetch('/api/backends/text-completions/generate', {
method: 'POST',
headers: getRequestHeaders(),
cache: 'no-cache',
body: JSON.stringify(data),
signal: new AbortController().signal,
signal: signal ?? new AbortController().signal,
});
const json = await response.json();
if (!response.ok || json.error) {
throw json;
if (!response.ok) {
const text = await response.text();
tryParseStreamingError(response, text, { quiet: true });
throw new Error(`Got response status ${response.status}`);
}
return extractData ? extractMessageFromData(json, this.TYPE) : json;
const eventStream = new EventSourceStream();
response.body.pipeThrough(eventStream);
const reader = eventStream.readable.getReader();
return async function* streamData() {
let text = '';
const swipes = [];
const state = { reasoning: '' };
while (true) {
const { done, value } = await reader.read();
if (done) return;
if (value.data === '[DONE]') return;
tryParseStreamingError(response, value.data, { quiet: true });
let data = JSON.parse(value.data);
if (data?.choices?.[0]?.index > 0) {
const swipeIndex = data.choices[0].index - 1;
swipes[swipeIndex] = (swipes[swipeIndex] || '') + data.choices[0].text;
} else {
const newText = data?.choices?.[0]?.text || data?.content || '';
text += newText;
state.reasoning += data?.choices?.[0]?.reasoning ?? '';
}
yield { text, swipes, state };
}
};
}
/**
* @param {string} presetName
* @param {TextCompletionRequest} custom
* @param {boolean?} extractData Extract message from the response. Default true
* @returns {Promise<string | any>} Extracted data or the raw response
* Process and send a text completion request with optional preset & instruct
* @param {Record<string, any> & TextCompletionRequestBase & {prompt: (ChatCompletionMessage & {ignoreInstruct?: boolean})[] |string}} custom
* @param {Object} options - Configuration options
* @param {string?} [options.presetName] - Name of the preset to use for generation settings
* @param {string?} [options.instructName] - Name of instruct preset for message formatting
* @param {Partial<InstructSettings>?} [options.instructSettings] - Override instruct settings
* @param {boolean} extractData - Whether to extract structured data from response
* @param {AbortSignal?} [signal]
* @returns {Promise<ExtractedData | (() => AsyncGenerator<StreamResponse>)>} If not streaming, returns extracted data; if streaming, returns a function that creates an AsyncGenerator
* @throws {Error}
*/
static async sendRequestWithPreset(presetName, custom, extractData = true) {
const presetManager = getPresetManager(this.TYPE);
if (!presetManager) {
throw new Error('Preset manager not found');
static async processRequest(
custom,
options = {},
extractData = true,
signal = null,
) {
const { presetName, instructName } = options;
let requestData = { ...custom };
const prompt = custom.prompt;
// Apply generation preset if specified
if (presetName) {
const presetManager = getPresetManager(this.TYPE);
if (presetManager) {
const preset = presetManager.getCompletionPresetByName(presetName);
if (preset) {
// Convert preset to payload and merge with custom parameters
const presetPayload = this.presetToGeneratePayload(preset, {});
requestData = { ...presetPayload, ...requestData };
} else {
console.warn(`Preset "${presetName}" not found, continuing with default settings`);
}
} else {
console.warn('Preset manager not found, continuing with default settings');
}
}
const preset = presetManager.getCompletionPresetByName(presetName);
if (!preset) {
throw new Error('Preset not found');
/** @type {InstructSettings | undefined} */
let instructPreset;
// Handle instruct formatting if requested
if (Array.isArray(prompt) && instructName) {
const instructPresetManager = getPresetManager('instruct');
instructPreset = instructPresetManager?.getCompletionPresetByName(instructName);
if (instructPreset) {
// Clone the preset to avoid modifying the original
instructPreset = structuredClone(instructPreset);
instructPreset.names_behavior = names_behavior_types.NONE;
if (options.instructSettings) {
Object.assign(instructPreset, options.instructSettings);
}
// Format messages using instruct formatting
const formattedMessages = [];
for (const message of prompt) {
let messageContent = message.content;
if (!message.ignoreInstruct) {
messageContent = formatInstructModeChat(
message.role,
message.content,
message.role === 'user',
false,
undefined,
undefined,
undefined,
undefined,
instructPreset,
);
// Add prompt formatting for the last message
if (message === prompt[prompt.length - 1]) {
messageContent += formatInstructModePrompt(
undefined,
false,
undefined,
undefined,
undefined,
false,
false,
instructPreset,
);
}
}
formattedMessages.push(messageContent);
}
requestData.prompt = formattedMessages.join('');
const stoppingStrings = getInstructStoppingSequences({ customInstruct: instructPreset, useStopStrings: false });
requestData.stop = stoppingStrings;
requestData.stopping_strings = stoppingStrings;
} else {
console.warn(`Instruct preset "${instructName}" not found, using basic formatting`);
requestData.prompt = prompt.map(x => x.content).join('\n\n');
}
} else if (typeof prompt === 'string') {
requestData.prompt = prompt;
} else {
requestData.prompt = prompt.map(x => x.content).join('\n\n');
}
const data = this.createRequestData({ ...preset, ...custom });
// @ts-ignore
const data = this.createRequestData(requestData);
return await this.sendRequest(data, extractData);
const response = await this.sendRequest(data, extractData, signal);
// Remove stopping strings from the end
if (!data.stream && extractData) {
/** @type {ExtractedData} */
// @ts-ignore
const extractedData = response;
let message = extractedData.content;
message = message.replace(/[^\S\r\n]+$/gm, '');
if (requestData.stopping_strings) {
for (const stoppingString of requestData.stopping_strings) {
if (stoppingString.length) {
for (let j = stoppingString.length; j > 0; j--) {
if (message.slice(-j) === stoppingString.slice(0, j)) {
message = message.slice(0, -j);
break;
}
}
}
}
}
if (instructPreset) {
[
instructPreset.stop_sequence,
instructPreset.input_sequence,
].forEach(sequence => {
if (sequence?.trim()) {
const index = message.indexOf(sequence);
if (index !== -1) {
message = message.substring(0, index);
}
}
});
[
instructPreset.output_sequence,
instructPreset.last_output_sequence,
].forEach(sequences => {
if (sequences) {
sequences.split('\n')
.filter(line => line.trim() !== '')
.forEach(line => {
message = message.replaceAll(line, '');
});
}
});
}
extractedData.content = message;
}
return response;
}
/**
* Converts a preset to a valid text completion payload.
* Only supports temperature.
* @param {Object} preset - The preset configuration
* @param {Object} customPreset - Additional parameters to override preset values
* @returns {Object} - Formatted payload for text completion API
*/
static presetToGeneratePayload(preset, customPreset = {}) {
if (!preset || typeof preset !== 'object') {
throw new Error('Invalid preset: must be an object');
}
// Merge preset with custom parameters
const settings = { ...preset, ...customPreset };
// Initialize base payload with common parameters
let payload = {
'temperature': settings.temp ? Number(settings.temp) : undefined,
'min_p': settings.min_p ? Number(settings.min_p) : undefined,
};
// Remove undefined values to avoid API errors
Object.keys(payload).forEach(key => {
if (payload[key] === undefined) {
delete payload[key];
}
});
return payload;
}
}
/**
* Creates & sends a chat completion request. Streaming is not supported.
* Creates & sends a chat completion request.
*/
export class ChatCompletionService {
static TYPE = 'openai';
@@ -128,62 +389,170 @@ export class ChatCompletionService {
* @param {ChatCompletionPayload} custom
* @returns {ChatCompletionPayload}
*/
static createRequestData({ messages, model, chat_completion_source, max_tokens, temperature, ...props }) {
return {
...props,
static createRequestData({ stream = false, messages, model, chat_completion_source, max_tokens, temperature, custom_url, reverse_proxy, proxy_password, ...props }) {
const payload = {
stream,
messages,
model,
chat_completion_source,
max_tokens,
temperature,
stream: false,
custom_url,
reverse_proxy,
proxy_password,
use_makersuite_sysprompt: true,
claude_use_sysprompt: true,
...props,
};
// Remove undefined values to avoid API errors
Object.keys(payload).forEach(key => {
if (payload[key] === undefined) {
delete payload[key];
}
});
return payload;
}
/**
* Sends a chat completion request
* @param {ChatCompletionPayload} data Request data
* @param {boolean?} extractData Extract message from the response. Default true
* @returns {Promise<string | any>} Extracted data or the raw response
* @param {AbortSignal?} signal Abort signal
* @returns {Promise<ExtractedData | (() => AsyncGenerator<StreamResponse>)>} If not streaming, returns extracted data; if streaming, returns a function that creates an AsyncGenerator
* @throws {Error}
*/
static async sendRequest(data, extractData = true) {
static async sendRequest(data, extractData = true, signal = null) {
const response = await fetch('/api/backends/chat-completions/generate', {
method: 'POST',
headers: getRequestHeaders(),
cache: 'no-cache',
body: JSON.stringify(data),
signal: new AbortController().signal,
signal: signal ?? new AbortController().signal,
});
const json = await response.json();
if (!response.ok || json.error) {
throw json;
if (!data.stream) {
const json = await response.json();
if (!response.ok || json.error) {
throw json;
}
if (!extractData) {
return json;
}
return {
content: extractMessageFromData(json, this.TYPE),
reasoning: extractReasoningFromData(json, {
mainApi: this.TYPE,
textGenType: data.chat_completion_source,
ignoreShowThoughts: true,
}),
};
}
return extractData ? extractMessageFromData(json, this.TYPE) : json;
if (!response.ok) {
const text = await response.text();
tryParseStreamingError(response, text, { quiet: true });
throw new Error(`Got response status ${response.status}`);
}
const eventStream = new EventSourceStream();
response.body.pipeThrough(eventStream);
const reader = eventStream.readable.getReader();
return async function* streamData() {
let text = '';
const swipes = [];
const state = { reasoning: '', image: '' };
while (true) {
const { done, value } = await reader.read();
if (done) return;
const rawData = value.data;
if (rawData === '[DONE]') return;
tryParseStreamingError(response, rawData, { quiet: true });
const parsed = JSON.parse(rawData);
const reply = getStreamingReply(parsed, state, {
chatCompletionSource: data.chat_completion_source,
overrideShowThoughts: true,
});
if (Array.isArray(parsed?.choices) && parsed?.choices?.[0]?.index > 0) {
const swipeIndex = parsed.choices[0].index - 1;
swipes[swipeIndex] = (swipes[swipeIndex] || '') + reply;
} else {
text += reply;
}
yield { text, swipes: swipes, state };
}
};
}
/**
* @param {string} presetName
* Process and send a chat completion request with optional preset
* @param {ChatCompletionPayload} custom
* @param {boolean} extractData Extract message from the response. Default true
* @returns {Promise<string | any>} Extracted data or the raw response
* @param {Object} options - Configuration options
* @param {string?} [options.presetName] - Name of the preset to use for generation settings
* @param {boolean} [extractData=true] - Whether to extract structured data from response
* @param {AbortSignal?} [signal] - Abort signal
* @returns {Promise<ExtractedData | (() => AsyncGenerator<StreamResponse>)>} If not streaming, returns extracted data; if streaming, returns a function that creates an AsyncGenerator
* @throws {Error}
*/
static async sendRequestWithPreset(presetName, custom, extractData = true) {
const presetManager = getPresetManager(this.TYPE);
if (!presetManager) {
throw new Error('Preset manager not found');
static async processRequest(custom, options, extractData = true, signal = null) {
const { presetName } = options;
let requestData = { ...custom };
// Apply generation preset if specified
if (presetName) {
const presetManager = getPresetManager(this.TYPE);
if (presetManager) {
const preset = presetManager.getCompletionPresetByName(presetName);
if (preset) {
// Convert preset to payload and merge with custom parameters
const presetPayload = this.presetToGeneratePayload(preset, {});
requestData = { ...presetPayload, ...requestData };
} else {
console.warn(`Preset "${presetName}" not found, continuing with default settings`);
}
} else {
console.warn('Preset manager not found, continuing with default settings');
}
}
const preset = presetManager.getCompletionPresetByName(presetName);
if (!preset) {
throw new Error('Preset not found');
const data = this.createRequestData(requestData);
return await this.sendRequest(data, extractData, signal);
}
/**
* Converts a preset to a valid chat completion payload
* Only supports temperature.
* @param {Object} preset - The preset configuration
* @param {Object} customParams - Additional parameters to override preset values
* @returns {Object} - Formatted payload for chat completion API
*/
static presetToGeneratePayload(preset, customParams = {}) {
if (!preset || typeof preset !== 'object') {
throw new Error('Invalid preset: must be an object');
}
const data = this.createRequestData({ ...preset, ...custom });
// Merge preset with custom parameters
const settings = { ...preset, ...customParams };
return await this.sendRequest(data, extractData);
// Initialize base payload with common parameters
const payload = {
temperature: settings.temperature ? Number(settings.temperature) : undefined,
};
// Remove undefined values to avoid API errors
Object.keys(payload).forEach(key => {
if (payload[key] === undefined) {
delete payload[key];
}
});
return payload;
}
}

View File

@@ -783,25 +783,41 @@ async function showExtensionsDetails() {
.append(htmlExternal)
.append(getModuleInformation());
/** @type {import('./popup.js').CustomPopupButton} */
const updateAllButton = {
text: t`Update all`,
action: async () => {
{
const updateAction = async (force) => {
requiresReload = true;
await autoUpdateExtensions(true);
await autoUpdateExtensions(force);
await popup.complete(POPUP_RESULT.AFFIRMATIVE);
},
};
};
/** @type {import('./popup.js').CustomPopupButton} */
const sortOrderButton = {
text: sortByName ? t`Sort: Display Name` : t`Sort: Loading Order`,
action: async () => {
const toolbar = document.createElement('div');
toolbar.classList.add('extensions_toolbar');
const updateAllButton = document.createElement('button');
updateAllButton.classList.add('menu_button', 'menu_button_icon');
updateAllButton.textContent = t`Update all`;
updateAllButton.addEventListener('click', () => updateAction(true));
const updateEnabledOnlyButton = document.createElement('button');
updateEnabledOnlyButton.classList.add('menu_button', 'menu_button_icon');
updateEnabledOnlyButton.textContent = t`Update enabled`;
updateEnabledOnlyButton.addEventListener('click', () => updateAction(false));
const flexExpander = document.createElement('div');
flexExpander.classList.add('expander');
const sortOrderButton = document.createElement('button');
sortOrderButton.classList.add('menu_button', 'menu_button_icon');
sortOrderButton.textContent = sortByName ? t`Sort: Display Name` : t`Sort: Loading Order`;
sortOrderButton.addEventListener('click', async () => {
abortController.abort();
accountStorage.setItem(sortOrderKey, sortByName ? 'false' : 'true');
await showExtensionsDetails();
},
};
});
toolbar.append(updateAllButton, updateEnabledOnlyButton, flexExpander, sortOrderButton);
html.prepend(toolbar);
}
let waitingForSave = false;
@@ -809,7 +825,7 @@ async function showExtensionsDetails() {
okButton: t`Close`,
wide: true,
large: true,
customButtons: [sortOrderButton, updateAllButton],
customButtons: [],
allowVerticalScrolling: true,
onClosing: async () => {
if (waitingForSave) {
@@ -1070,7 +1086,7 @@ export async function installExtension(url, global) {
toastr.success(t`Extension '${response.display_name}' by ${response.author} (version ${response.version}) has been installed successfully!`, t`Extension installation successful`);
console.debug(`Extension "${response.display_name}" has been installed successfully at ${response.extensionPath}`);
await loadExtensionSettings({}, false, false);
await eventSource.emit(event_types.EXTENSION_SETTINGS_LOADED);
await eventSource.emit(event_types.EXTENSION_SETTINGS_LOADED, response);
}
/**
@@ -1196,7 +1212,7 @@ async function checkForUpdatesManual(sortFn, abortSignal) {
}
/**
* Checks if there are updates available for 3rd-party extensions.
* Checks if there are updates available for enabled 3rd-party extensions.
* @param {boolean} force Skip nag check
* @returns {Promise<any>}
*/
@@ -1218,6 +1234,11 @@ async function checkForExtensionUpdates(force) {
const promises = [];
for (const [id, manifest] of Object.entries(manifests)) {
const isDisabled = extension_settings.disabledExtensions.includes(id);
if (isDisabled) {
console.debug(`Skipping extension: ${manifest.display_name} (${id}) for non-admin user`);
continue;
}
const isGlobal = getExtensionType(id) === 'global';
if (isGlobal && !isCurrentUserAdmin) {
console.debug(`Skipping global extension: ${manifest.display_name} (${id}) for non-admin user`);
@@ -1247,8 +1268,8 @@ async function checkForExtensionUpdates(force) {
}
/**
* Updates all 3rd-party extensions that have auto-update enabled.
* @param {boolean} forceAll Force update all even if not auto-updating
* Updates all enabled 3rd-party extensions that have auto-update enabled.
* @param {boolean} forceAll Include disabled and not auto-updating
* @returns {Promise<void>}
*/
async function autoUpdateExtensions(forceAll) {
@@ -1260,6 +1281,11 @@ async function autoUpdateExtensions(forceAll) {
const isCurrentUserAdmin = isAdmin();
const promises = [];
for (const [id, manifest] of Object.entries(manifests)) {
const isDisabled = extension_settings.disabledExtensions.includes(id);
if (!forceAll && isDisabled) {
console.debug(`Skipping extension: ${manifest.display_name} (${id}) for non-admin user`);
continue;
}
const isGlobal = getExtensionType(id) === 'global';
if (isGlobal && !isCurrentUserAdmin) {
console.debug(`Skipping global extension: ${manifest.display_name} (${id}) for non-admin user`);

View File

@@ -424,7 +424,7 @@ jQuery(async () => {
installHintButton.on('click', async function () {
const installButton = $('#third_party_extension_button');
flashHighlight(installButton, 5000);
toastr.info('Click the flashing button to install extensions.', 'How to install extensions?');
toastr.info(t`Click the flashing button to install extensions.`, t`How to install extensions?`);
});
const connectButton = windowHtml.find('#assets-connect-button');

View File

@@ -1,4 +1,4 @@
<div id="attachFile" class="list-group-item flex-container flexGap5" title="Attach a file or image to a current chat.">
<div id="attachFile" class="list-group-item flex-container flexGap5" data-i18n="[title]Attach a file or image to a current chat." 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

@@ -428,6 +428,7 @@ jQuery(async function () {
'zerooneai': SECRET_KEYS.ZEROONEAI,
'groq': SECRET_KEYS.GROQ,
'cohere': SECRET_KEYS.COHERE,
'xai': SECRET_KEYS.XAI,
};
if (chatCompletionApis[api] && secret_state[chatCompletionApis[api]]) {

View File

@@ -31,6 +31,7 @@
<option value="openrouter">OpenRouter</option>
<option value="ooba" data-i18n="Text Generation WebUI (oobabooga)">Text Generation WebUI (oobabooga)</option>
<option value="vllm">vLLM</option>
<option value="xai">xAI (Grok)</option>
</select>
</div>
<div class="flex1 flex-container flexFlowColumn flexNoGap">
@@ -42,7 +43,16 @@
<option data-type="mistral" value="pixtral-12b-2409">pixtral-12b-2409</option>
<option data-type="mistral" value="pixtral-large-latest">pixtral-large-latest</option>
<option data-type="mistral" value="pixtral-large-2411">pixtral-large-2411</option>
<option data-type="mistral" value="mistral-large-pixtral-2411">mistral-large-pixtral-2411</option>
<option data-type="mistral" value="mistral-small-2503">mistral-small-2503</option>
<option data-type="mistral" value="mistral-small-latest">mistral-small-latest</option>
<option data-type="zerooneai" value="yi-vision">yi-vision</option>
<option data-type="openai" value="gpt-4.1">gpt-4.1</option>
<option data-type="openai" value="gpt-4.1-2025-04-14">gpt-4.1-2025-04-14</option>
<option data-type="openai" value="gpt-4.1-mini">gpt-4.1-mini</option>
<option data-type="openai" value="gpt-4.1-mini-2025-04-14">gpt-4.1-mini-2025-04-14</option>
<option data-type="openai" value="gpt-4.1-nano">gpt-4.1-nano</option>
<option data-type="openai" value="gpt-4.1-nano-2025-04-14">gpt-4.1-nano-2025-04-14</option>
<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>
@@ -62,6 +72,8 @@
<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>
<option data-type="google" value="gemini-2.5-pro-preview-03-25">gemini-2.5-pro-preview-03-25</option>
<option data-type="google" value="gemini-2.5-pro-exp-03-25">gemini-2.5-pro-exp-03-25</option>
<option data-type="google" value="gemini-2.0-pro-exp">gemini-2.0-pro-exp</option>
<option data-type="google" value="gemini-2.0-pro-exp-02-05">gemini-2.0-pro-exp-02-05</option>
<option data-type="google" value="gemini-2.0-flash-lite-preview">gemini-2.0-flash-lite-preview</option>
@@ -69,6 +81,7 @@
<option data-type="google" value="gemini-2.0-flash">gemini-2.0-flash</option>
<option data-type="google" value="gemini-2.0-flash-001">gemini-2.0-flash-001</option>
<option data-type="google" value="gemini-2.0-flash-exp">gemini-2.0-flash-exp</option>
<option data-type="google" value="gemini-2.0-flash-exp-image-generation">gemini-2.0-flash-exp-image-generation</option>
<option data-type="google" value="gemini-2.0-flash-thinking-exp">gemini-2.0-flash-thinking-exp</option>
<option data-type="google" value="gemini-2.0-flash-thinking-exp-01-21">gemini-2.0-flash-thinking-exp-01-21</option>
<option data-type="google" value="gemini-2.0-flash-thinking-exp-1219">gemini-2.0-flash-thinking-exp-1219</option>
@@ -128,6 +141,8 @@
<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>
<option data-type="xai" value="grok-2-vision-1212">grok-2-vision-1212</option>
<option data-type="xai" value="grok-vision-beta">grok-vision-beta</option>
</select>
</div>
<div data-type="ollama">

View File

@@ -1,4 +1,4 @@
import { Fuse } from '../../../lib.js';
import { DOMPurify, Fuse } from '../../../lib.js';
import { event_types, eventSource, main_api, saveSettingsDebounced } from '../../../script.js';
import { extension_settings, renderExtensionTemplateAsync } from '../../extensions.js';
@@ -16,12 +16,19 @@ import { t } from '../../i18n.js';
const MODULE_NAME = 'connection-manager';
const NONE = '<None>';
const EMPTY = '<Empty>';
const DEFAULT_SETTINGS = {
profiles: [],
selectedProfile: null,
};
// Commands that can record an empty value into the profile
const ALLOW_EMPTY = [
'stop-strings',
'start-reply-with',
];
const CC_COMMANDS = [
'api',
'preset',
@@ -31,6 +38,8 @@ const CC_COMMANDS = [
'model',
'proxy',
'stop-strings',
'start-reply-with',
'reasoning-template',
];
const TC_COMMANDS = [
@@ -45,6 +54,8 @@ const TC_COMMANDS = [
'instruct-state',
'tokenizer',
'stop-strings',
'start-reply-with',
'reasoning-template',
];
const FANCY_NAMES = {
@@ -60,6 +71,8 @@ const FANCY_NAMES = {
'context': 'Context Template',
'tokenizer': 'Tokenizer',
'stop-strings': 'Custom Stopping Strings',
'start-reply-with': 'Start Reply With',
'reasoning-template': 'Reasoning Template',
};
/**
@@ -107,6 +120,7 @@ class ConnectionManagerSpinner {
/**
* Get named arguments for the command callback.
* @param {object} [args] Additional named arguments
* @param {string} [args.force] Whether to force setting the value
* @returns {object} Named arguments
*/
function getNamedArguments(args = {}) {
@@ -142,6 +156,8 @@ const profilesProvider = () => [
* @property {string} [instruct-state] Instruct Mode
* @property {string} [tokenizer] Tokenizer
* @property {string} [stop-strings] Custom Stopping Strings
* @property {string} [start-reply-with] Start Reply With
* @property {string} [reasoning-template] Reasoning Template
* @property {string[]} [exclude] Commands to exclude
*/
@@ -186,9 +202,10 @@ async function readProfileFromCommands(mode, profile, cleanUp = false) {
continue;
}
const allowEmpty = ALLOW_EMPTY.includes(command);
const args = getNamedArguments();
const result = await SlashCommandParser.commands[command].callback(args, '');
if (result) {
if (result || (allowEmpty && result === '')) {
profile[command] = result;
continue;
}
@@ -254,11 +271,16 @@ async function createConnectionProfile(forceName = null) {
});
const isNameTaken = (n) => extension_settings.connectionManager.profiles.some(p => p.name === n);
const suggestedName = getUniqueName(collapseSpaces(`${profile.api ?? ''} ${profile.model ?? ''} - ${profile.preset ?? ''}`), isNameTaken);
const name = forceName ?? await callGenericPopup(template, POPUP_TYPE.INPUT, suggestedName, { rows: 2 });
let name = forceName ?? await callGenericPopup(template, POPUP_TYPE.INPUT, suggestedName, { rows: 2 });
// If it's cancelled, it will be false
if (!name) {
return null;
}
name = DOMPurify.sanitize(String(name));
if (!name) {
toastr.error('Name cannot be empty.');
return null;
}
if (isNameTaken(name) || name === NONE) {
toastr.error('A profile with the same name already exists.');
@@ -290,7 +312,8 @@ async function deleteConnectionProfile() {
return;
}
const name = extension_settings.connectionManager.profiles[index].name;
const profile = extension_settings.connectionManager.profiles[index];
const name = profile.name;
const confirm = await Popup.show.confirm(t`Are you sure you want to delete the selected profile?`, name);
if (!confirm) {
@@ -300,6 +323,8 @@ async function deleteConnectionProfile() {
extension_settings.connectionManager.profiles.splice(index, 1);
extension_settings.connectionManager.selectedProfile = null;
saveSettingsDebounced();
await eventSource.emit(event_types.CONNECTION_PROFILE_DELETED, profile);
}
/**
@@ -309,7 +334,14 @@ async function deleteConnectionProfile() {
*/
function makeFancyProfile(profile) {
return Object.entries(FANCY_NAMES).reduce((acc, [key, value]) => {
if (!profile[key]) return acc;
const allowEmpty = ALLOW_EMPTY.includes(key);
if (!profile[key]) {
if (profile[key] === '' && allowEmpty) {
acc[value] = EMPTY;
}
return acc;
}
acc[value] = profile[key];
return acc;
}, {});
@@ -339,11 +371,12 @@ async function applyConnectionProfile(profile) {
}
const argument = profile[command];
if (!argument) {
const allowEmpty = ALLOW_EMPTY.includes(command);
if (!argument && !(allowEmpty && argument === '')) {
continue;
}
try {
const args = getNamedArguments();
const args = getNamedArguments(allowEmpty ? { force: 'true' } : {});
await SlashCommandParser.commands[command].callback(args, argument);
} catch (error) {
console.error(`Failed to execute command: ${command} ${argument}`, error);
@@ -491,6 +524,7 @@ async function renderDetailsContent(detailsContent) {
saveSettingsDebounced();
renderConnectionProfiles(profiles);
await renderDetailsContent(detailsContent);
await eventSource.emit(event_types.CONNECTION_PROFILE_CREATED, profile);
await eventSource.emit(event_types.CONNECTION_PROFILE_LOADED, profile.name);
});
@@ -502,9 +536,11 @@ async function renderDetailsContent(detailsContent) {
console.log('No profile selected');
return;
}
const oldProfile = structuredClone(profile);
await updateConnectionProfile(profile);
await renderDetailsContent(detailsContent);
saveSettingsDebounced();
await eventSource.emit(event_types.CONNECTION_PROFILE_UPDATED, oldProfile, profile);
await eventSource.emit(event_types.CONNECTION_PROFILE_LOADED, profile.name);
toastr.success('Connection profile updated', '', { timeOut: 1500 });
});
@@ -538,7 +574,8 @@ async function renderDetailsContent(detailsContent) {
return acc;
}, {});
const template = $(await renderExtensionTemplateAsync(MODULE_NAME, 'edit', { name: profile.name, settings }));
const newName = await callGenericPopup(template, POPUP_TYPE.INPUT, profile.name, {
let newName = await callGenericPopup(template, POPUP_TYPE.INPUT, profile.name, {
rows: 2,
customButtons: [{
text: t`Save and Update`,
classes: ['popup-button-ok'],
@@ -549,9 +586,15 @@ async function renderDetailsContent(detailsContent) {
}],
});
// If it's cancelled, it will be false
if (!newName) {
return;
}
newName = DOMPurify.sanitize(String(newName));
if (!newName) {
toastr.error('Name cannot be empty.');
return;
}
if (profile.name !== newName && extension_settings.connectionManager.profiles.some(p => p.name === newName)) {
toastr.error('A profile with the same name already exists.');
@@ -562,6 +605,7 @@ async function renderDetailsContent(detailsContent) {
return Object.entries(FANCY_NAMES).find(x => x[1] === String($(this).val()))?.[0];
}).get();
const oldProfile = structuredClone(profile);
if (newExcludeList.length !== profile.exclude.length || !newExcludeList.every(e => profile.exclude.includes(e))) {
profile.exclude = newExcludeList;
for (const command of newExcludeList) {
@@ -576,10 +620,11 @@ async function renderDetailsContent(detailsContent) {
if (profile.name !== newName) {
toastr.success('Connection profile renamed.');
profile.name = String(newName);
profile.name = newName;
}
saveSettingsDebounced();
await eventSource.emit(event_types.CONNECTION_PROFILE_UPDATED, oldProfile, profile);
renderConnectionProfiles(profiles);
await renderDetailsContent(detailsContent);
});
@@ -682,6 +727,7 @@ async function renderDetailsContent(detailsContent) {
saveSettingsDebounced();
renderConnectionProfiles(profiles);
await renderDetailsContent(detailsContent);
await eventSource.emit(event_types.CONNECTION_PROFILE_CREATED, profile);
return profile.name;
},
}));
@@ -696,9 +742,11 @@ async function renderDetailsContent(detailsContent) {
toastr.warning('No profile selected.');
return '';
}
const oldProfile = structuredClone(profile);
await updateConnectionProfile(profile);
await renderDetailsContent(detailsContent);
saveSettingsDebounced();
await eventSource.emit(event_types.CONNECTION_PROFILE_UPDATED, oldProfile, profile);
return profile.name;
},
}));

View File

@@ -4,7 +4,7 @@ import { characters, eventSource, event_types, generateRaw, getRequestHeaders, m
import { dragElement, isMobile } from '../../RossAscends-mods.js';
import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch, renderExtensionTemplateAsync } from '../../extensions.js';
import { loadMovingUIState, performFuzzySearch, power_user } from '../../power-user.js';
import { onlyUnique, debounce, getCharaFilename, trimToEndSentence, trimToStartSentence, waitUntilCondition, findChar } from '../../utils.js';
import { onlyUnique, debounce, getCharaFilename, trimToEndSentence, trimToStartSentence, waitUntilCondition, findChar, isFalseBoolean } from '../../utils.js';
import { hideMutedSprites, selected_group } from '../../group-chats.js';
import { isJsonSchemaSupported } from '../../textgen-settings.js';
import { debounce_timeout } from '../../constants.js';
@@ -17,6 +17,7 @@ import { slashCommandReturnHelper } from '../../slash-commands/SlashCommandRetur
import { generateWebLlmChatPrompt, isWebLlmSupported } from '../shared.js';
import { Popup, POPUP_RESULT } from '../../popup.js';
import { t } from '../../i18n.js';
import { removeReasoningFromString } from '../../reasoning.js';
export { MODULE_NAME };
/**
@@ -82,6 +83,7 @@ const EXPRESSION_API = {
extras: 1,
llm: 2,
webllm: 3,
none: 99,
};
let expressionsList = null;
@@ -273,7 +275,7 @@ async function getLastMessageSprite(avatar) {
return null;
}
async function visualNovelUpdateLayers(container) {
export async function visualNovelUpdateLayers(container) {
const context = getContext();
const group = context.groups.find(x => x.id == context.groupId);
const recentMessages = context.chat.map(x => x.original_avatar).filter(x => x).reverse().filter(onlyUnique);
@@ -678,7 +680,7 @@ async function setSpriteFolderCommand(_, folder) {
return '';
}
async function classifyCallback(/** @type {{api: string?, prompt: string?}} */ { api = null, prompt = null }, text) {
async function classifyCallback(/** @type {{api: string?, filter: string?, prompt: string?}} */ { api = null, filter = null, prompt = null }, text) {
if (!text) {
toastr.error('No text provided');
return '';
@@ -689,13 +691,19 @@ async function classifyCallback(/** @type {{api: string?, prompt: string?}} */ {
}
const expressionApi = EXPRESSION_API[api] || extension_settings.expressions.api;
const filterAvailable = !isFalseBoolean(filter);
if (expressionApi === EXPRESSION_API.none) {
toastr.warning('No classifier API selected');
return '';
}
if (!modules.includes('classify') && expressionApi == EXPRESSION_API.extras) {
toastr.warning('Text classification is disabled or not available');
return '';
}
const label = await getExpressionLabel(text, expressionApi, { customPrompt: prompt });
const label = await getExpressionLabel(text, expressionApi, { filterAvailable: filterAvailable, customPrompt: prompt });
console.debug(`Classification result for "${text}": ${label}`);
return label;
}
@@ -928,6 +936,9 @@ function parseLlmResponse(emotionResponse, labels) {
return response;
} catch {
// Clean possible reasoning from response
emotionResponse = removeReasoningFromString(emotionResponse);
const fuse = new Fuse(labels, { includeScore: true });
console.debug('Using fuzzy search in labels:', labels);
const result = fuse.search(emotionResponse);
@@ -988,10 +999,11 @@ function onTextGenSettingsReady(args) {
* @param {string} text - The text to classify and retrieve the expression label for.
* @param {EXPRESSION_API} [expressionsApi=extension_settings.expressions.api] - The expressions API to use for classification.
* @param {object} [options={}] - Optional arguments.
* @param {boolean?} [options.filterAvailable=null] - Whether to filter available expressions. If not specified, uses the extension setting.
* @param {string?} [options.customPrompt=null] - The custom prompt to use for classification.
* @returns {Promise<string?>} - The label of the expression.
*/
export async function getExpressionLabel(text, expressionsApi = extension_settings.expressions.api, { customPrompt = null } = {}) {
export async function getExpressionLabel(text, expressionsApi = extension_settings.expressions.api, { filterAvailable = null, customPrompt = null } = {}) {
// Return if text is undefined, saving a costly fetch request
if ((!modules.includes('classify') && expressionsApi == EXPRESSION_API.extras) || !text) {
return extension_settings.expressions.fallback_expression;
@@ -1003,6 +1015,11 @@ export async function getExpressionLabel(text, expressionsApi = extension_settin
text = sampleClassifyText(text);
filterAvailable ??= extension_settings.expressions.filterAvailable;
if (filterAvailable && ![EXPRESSION_API.llm, EXPRESSION_API.webllm].includes(expressionsApi)) {
console.debug('Filter available is only supported for LLM and WebLLM expressions');
}
try {
switch (expressionsApi) {
// Local BERT pipeline
@@ -1027,7 +1044,7 @@ export async function getExpressionLabel(text, expressionsApi = extension_settin
return extension_settings.expressions.fallback_expression;
}
const expressionsList = await getExpressionsList();
const expressionsList = await getExpressionsList({ filterAvailable: filterAvailable });
const prompt = substituteParamsExtended(customPrompt, { labels: expressionsList }) || await getLlmPrompt(expressionsList);
eventSource.once(event_types.TEXT_COMPLETION_SETTINGS_READY, onTextGenSettingsReady);
const emotionResponse = await generateRaw(text, main_api, false, false, prompt);
@@ -1040,7 +1057,7 @@ export async function getExpressionLabel(text, expressionsApi = extension_settin
return extension_settings.expressions.fallback_expression;
}
const expressionsList = await getExpressionsList();
const expressionsList = await getExpressionsList({ filterAvailable: filterAvailable });
const prompt = substituteParamsExtended(customPrompt, { labels: expressionsList }) || await getLlmPrompt(expressionsList);
const messages = [
{ role: 'user', content: text + '\n\n' + prompt },
@@ -1050,7 +1067,7 @@ export async function getExpressionLabel(text, expressionsApi = extension_settin
return parseLlmResponse(emotionResponse, expressionsList);
}
// Extras
default: {
case EXPRESSION_API.extras: {
const url = new URL(getApiUrl());
url.pathname = '/api/classify';
@@ -1068,6 +1085,15 @@ export async function getExpressionLabel(text, expressionsApi = extension_settin
return data.classification[0].label;
}
} break;
// None
case EXPRESSION_API.none: {
// Return empty, the fallback expression will be used
return '';
}
default: {
toastr.error('Invalid API selected');
return '';
}
}
} catch (error) {
toastr.error('Could not classify expression. Check the console or your backend for more information.');
@@ -1320,12 +1346,28 @@ function getCachedExpressions() {
return [...expressionsList, ...extension_settings.expressions.custom].filter(onlyUnique);
}
export async function getExpressionsList() {
// Return cached list if available
if (Array.isArray(expressionsList)) {
return getCachedExpressions();
export async function getExpressionsList({ filterAvailable = false } = {}) {
// If there is no cached list, load and cache it
if (!Array.isArray(expressionsList)) {
expressionsList = await resolveExpressionsList();
}
const expressions = getCachedExpressions();
// Filtering is only available for llm and webllm APIs
if (!filterAvailable || ![EXPRESSION_API.llm, EXPRESSION_API.webllm].includes(extension_settings.expressions.api)) {
return expressions;
}
// Get expressions with available sprites
const currentLastMessage = selected_group ? getLastCharacterMessage() : null;
const spriteFolderName = getSpriteFolderName(currentLastMessage, currentLastMessage?.name);
return expressions.filter(label => {
const expression = spriteCache[spriteFolderName]?.find(x => x.label === label);
return (expression?.files.length ?? 0) > 0;
});
/**
* Returns the list of expressions from the API or fallback in offline mode.
* @returns {Promise<string[]>}
@@ -1372,9 +1414,6 @@ export async function getExpressionsList() {
expressionsList = DEFAULT_EXPRESSIONS.slice();
return expressionsList;
}
const result = await resolveExpressionsList();
return [...result, ...extension_settings.expressions.custom].filter(onlyUnique);
}
/**
@@ -1810,7 +1849,7 @@ async function onClickExpressionUpload(event) {
}
}
} else {
spriteName = withoutExtension(clickedFileName);
spriteName = withoutExtension(expression);
}
if (!spriteName) {
@@ -2036,7 +2075,7 @@ async function fetchImagesNoCache() {
function migrateSettings() {
if (extension_settings.expressions.api === undefined) {
extension_settings.expressions.api = EXPRESSION_API.extras;
extension_settings.expressions.api = EXPRESSION_API.none;
saveSettingsDebounced();
}
@@ -2102,6 +2141,10 @@ function migrateSettings() {
extension_settings.expressions.rerollIfSame = !!$(this).prop('checked');
saveSettingsDebounced();
});
$('#expressions_filter_available').prop('checked', extension_settings.expressions.filterAvailable).on('input', function () {
extension_settings.expressions.filterAvailable = !!$(this).prop('checked');
saveSettingsDebounced();
});
$('#expression_override_cleanup_button').on('click', onClickExpressionOverrideRemoveAllButton);
$(document).on('dragstart', '.expression', (e) => {
e.preventDefault();
@@ -2114,7 +2157,7 @@ function migrateSettings() {
$('#open_chat_expressions').hide();
await renderAdditionalExpressionSettings();
$('#expression_api').val(extension_settings.expressions.api ?? EXPRESSION_API.extras);
$('#expression_api').val(extension_settings.expressions.api ?? EXPRESSION_API.none);
$('.expression_llm_prompt_block').toggle([EXPRESSION_API.llm, EXPRESSION_API.webllm].includes(extension_settings.expressions.api));
$('#expression_llm_prompt').val(extension_settings.expressions.llmPrompt ?? '');
$('#expression_llm_prompt').on('input', function () {
@@ -2154,7 +2197,7 @@ function migrateSettings() {
imgElement.src = '';
}
setExpressionOverrideHtml();
setExpressionOverrideHtml(true); // force-clear, as the character might not have an override defined
if (isVisualNovelMode()) {
$('#visual-novel-wrapper').empty();
@@ -2279,13 +2322,13 @@ function migrateSettings() {
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'expression-list',
aliases: ['expressions'],
/** @type {(args: {return: string}) => Promise<string>} */
/** @type {(args: {return: string, filter: string}) => Promise<string>} */
callback: async (args) => {
let returnType =
/** @type {import('../../slash-commands/SlashCommandReturnHelper.js').SlashCommandReturnType} */
(args.return);
const list = await getExpressionsList();
const list = await getExpressionsList({ filterAvailable: !isFalseBoolean(args.filter) });
return await slashCommandReturnHelper.doReturn(returnType ?? 'pipe', list, { objectToStringFunc: list => list.join(', ') });
},
@@ -2298,6 +2341,13 @@ function migrateSettings() {
enumList: slashCommandReturnHelper.enumList({ allowObject: true }),
forceEnum: true,
}),
SlashCommandNamedArgument.fromProps({
name: 'filter',
description: 'Filter the list to only include expressions that have available sprites for the current character.',
typeList: [ARGUMENT_TYPE.BOOLEAN],
enumList: commonEnumProviders.boolean('trueFalse')(),
defaultValue: 'true',
}),
],
returns: 'The comma-separated list of available expressions, including custom expressions.',
helpString: 'Returns a list of available expressions, including custom expressions.',
@@ -2313,6 +2363,13 @@ function migrateSettings() {
typeList: [ARGUMENT_TYPE.STRING],
enumList: Object.keys(EXPRESSION_API).map(api => new SlashCommandEnumValue(api, null, enumTypes.enum)),
}),
SlashCommandNamedArgument.fromProps({
name: 'filter',
description: 'Filter the list to only include expressions that have available sprites for the current character.',
typeList: [ARGUMENT_TYPE.BOOLEAN],
enumList: commonEnumProviders.boolean('trueFalse')(),
defaultValue: 'true',
}),
SlashCommandNamedArgument.fromProps({
name: 'prompt',
description: 'Custom prompt for classification. Only relevant if Classifier API is set to LLM.',

View File

@@ -22,6 +22,7 @@
<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="99" data-i18n="[ None ]">[ None ]</option>
<option value="0" data-i18n="Local">Local</option>
<option value="1" data-i18n="Extras">Extras (deprecated)</option>
<option value="2" data-i18n="Main API">Main API</option>
@@ -29,7 +30,11 @@
</select>
</div>
<div class="expression_llm_prompt_block m-b-1 m-t-1">
<label for="expression_llm_prompt" class="title_restorable">
<label class="checkbox_label" for="expressions_filter_available" title="When using LLM or WebLLM classifier, only show and use expressions that have sprites assigned to them." data-i18n="[title]When using LLM or WebLLM classifier, only show and use expressions that have sprites assigned to them.">
<input id="expressions_filter_available" type="checkbox">
<span data-i18n="Filter expressions for available sprites">Filter expressions for available sprites</span>
</label>
<label for="expression_llm_prompt" class="title_restorable m-t-1">
<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>

View File

@@ -132,7 +132,7 @@
</label>
<label class="flex-container alignItemsCenter" title="How many messages before the current end of the chat." data-i18n="[title]How many messages before the current end of the chat.">
<input type="radio" name="memory_position" value="1" />
<span data-i18n="In-chat @ Depth">In-chat @ Depth</span> <input id="memory_depth" class="text_pole widthUnset" type="number" min="0" max="999" />
<span data-i18n="In-chat @ Depth">In-chat @ Depth</span> <input id="memory_depth" class="text_pole widthUnset" type="number" min="0" max="9999" />
<span data-i18n="as">as</span>
<select id="memory_role" class="text_pole widthNatural">
<option value="0" data-i18n="System">System</option>

View File

@@ -1,21 +1,18 @@
// eslint-disable-next-line no-unused-vars
import { QuickReply } from '../src/QuickReply.js';
import { QuickReplyContextLink } from '../src/QuickReplyContextLink.js';
import { QuickReplySet } from '../src/QuickReplySet.js';
// eslint-disable-next-line no-unused-vars
import { QuickReplySettings } from '../src/QuickReplySettings.js';
// eslint-disable-next-line no-unused-vars
import { SettingsUi } from '../src/ui/SettingsUi.js';
import { onlyUnique } from '../../../utils.js';
export class QuickReplyApi {
/**@type {QuickReplySettings}*/ settings;
/**@type {SettingsUi}*/ settingsUi;
/** @type {QuickReplySettings} */ settings;
/** @type {SettingsUi} */ settingsUi;
constructor(/**@type {QuickReplySettings}*/settings, /**@type {SettingsUi}*/settingsUi) {
constructor(/** @type {QuickReplySettings} */settings, /** @type {SettingsUi} */settingsUi) {
this.settings = settings;
this.settingsUi = settingsUi;
}

View File

@@ -9,6 +9,8 @@ import { QuickReplySettings } from './src/QuickReplySettings.js';
import { SlashCommandHandler } from './src/SlashCommandHandler.js';
import { ButtonUi } from './src/ui/ButtonUi.js';
import { SettingsUi } from './src/ui/SettingsUi.js';
import { debounceAsync } from '../../utils.js';
export { debounceAsync };
@@ -17,32 +19,6 @@ const _VERBOSE = true;
export const debug = (...msg) => _VERBOSE ? console.debug('[QR2]', ...msg) : null;
export const log = (...msg) => _VERBOSE ? console.log('[QR2]', ...msg) : null;
export const warn = (...msg) => _VERBOSE ? console.warn('[QR2]', ...msg) : null;
/**
* Creates a debounced function that delays invoking func until after wait milliseconds have elapsed since the last time the debounced function was invoked.
* @param {Function} func The function to debounce.
* @param {Number} [timeout=300] The timeout in milliseconds.
* @returns {Function} The debounced function.
*/
export function debounceAsync(func, timeout = 300) {
let timer;
/**@type {Promise}*/
let debouncePromise;
/**@type {Function}*/
let debounceResolver;
return (...args) => {
clearTimeout(timer);
if (!debouncePromise) {
debouncePromise = new Promise(resolve => {
debounceResolver = resolve;
});
}
timer = setTimeout(() => {
debounceResolver(func.apply(this, args));
debouncePromise = null;
}, timeout);
return debouncePromise;
};
}
const defaultConfig = {

View File

@@ -1,18 +1,16 @@
import { warn } from '../index.js';
// eslint-disable-next-line no-unused-vars
import { QuickReply } from './QuickReply.js';
// eslint-disable-next-line no-unused-vars
import { QuickReplySettings } from './QuickReplySettings.js';
export class AutoExecuteHandler {
/**@type {QuickReplySettings}*/ settings;
/** @type {QuickReplySettings} */ settings;
/**@type {Boolean[]}*/ preventAutoExecuteStack = [];
/** @type {Boolean[]}*/ preventAutoExecuteStack = [];
constructor(/**@type {QuickReplySettings}*/settings) {
constructor(/** @type {QuickReplySettings} */settings) {
this.settings = settings;
}
@@ -24,7 +22,7 @@ export class AutoExecuteHandler {
async performAutoExecute(/**@type {QuickReply[]}*/qrList) {
async performAutoExecute(/** @type {QuickReply[]} */qrList) {
for (const qr of qrList) {
this.preventAutoExecuteStack.push(qr.preventAutoExecute);
try {

View File

@@ -49,7 +49,7 @@ export class QuickReply {
/**@type {string}*/ automationId = '';
/**@type {function}*/ onExecute;
/**@type {(qr:QuickReply)=>AsyncGenerator<SlashCommandClosureResult|{closure:SlashCommandClosure, executor:SlashCommandExecutor|SlashCommandClosureResult}, SlashCommandClosureResult, boolean>}*/ onDebug;
/** @type {(qr:QuickReply)=>AsyncGenerator<SlashCommandClosureResult|{closure:SlashCommandClosure, executor:SlashCommandExecutor|SlashCommandClosureResult}, SlashCommandClosureResult, boolean>} */ onDebug;
/**@type {function}*/ onDelete;
/**@type {function}*/ onUpdate;
/**@type {function}*/ onInsertBefore;
@@ -635,7 +635,6 @@ export class QuickReply {
}, { passive:true });
const getLineStart = ()=>{
const start = message.selectionStart;
const end = message.selectionEnd;
let lineStart;
if (start == 0 || message.value[start - 1] == '\n') {
// cursor is already at beginning of line
@@ -701,7 +700,6 @@ export class QuickReply {
} else if (evt.key == 'Enter' && !evt.ctrlKey && !evt.shiftKey && !evt.altKey && !(ac.isReplaceable && ac.isActive)) {
// new line, keep indent
const start = message.selectionStart;
const end = message.selectionEnd;
let lineStart = getLineStart();
const indent = /^([^\S\n]*)/.exec(message.value.slice(lineStart))[1] ?? '';
if (indent.length) {

View File

@@ -8,18 +8,17 @@ import { SlashCommandEnumValue, enumTypes } from '../../../slash-commands/SlashC
import { SlashCommandParser } from '../../../slash-commands/SlashCommandParser.js';
import { SlashCommandScope } from '../../../slash-commands/SlashCommandScope.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;
/** @type {QuickReplyApi} */ api;
constructor(/**@type {QuickReplyApi}*/api) {
constructor(/** @type {QuickReplyApi} */api) {
this.api = api;
}
@@ -27,7 +26,7 @@ export class SlashCommandHandler {
init() {
function getExecutionIcons(/**@type {QuickReply} */ qr) {
function getExecutionIcons(/** @type {QuickReply} */ qr) {
let icons = '';
if (qr.preventAutoExecute) icons += '🚫';
if (qr.isHidden) icons += '👁️';

View File

@@ -1,11 +1,10 @@
import { animation_duration } from '../../../../../script.js';
import { dragElement } from '../../../../RossAscends-mods.js';
import { loadMovingUIState } from '../../../../power-user.js';
// eslint-disable-next-line no-unused-vars
import { QuickReplySettings } from '../QuickReplySettings.js';
export class ButtonUi {
/**@type {QuickReplySettings}*/ settings;
/** @type {QuickReplySettings} */ settings;
/**@type {HTMLElement}*/ dom;
/**@type {HTMLElement}*/ popoutDom;

View File

@@ -3,14 +3,13 @@ import { getSortableDelay } from '../../../../utils.js';
import { log, warn } from '../../index.js';
import { QuickReply } from '../QuickReply.js';
import { QuickReplySet } from '../QuickReplySet.js';
// eslint-disable-next-line no-unused-vars
import { QuickReplySettings } from '../QuickReplySettings.js';
export class SettingsUi {
/**@type {QuickReplySettings}*/ settings;
/** @type {QuickReplySettings} */ settings;
/**@type {HTMLElement}*/ template;
/**@type {HTMLElement}*/ dom;
/** @type {HTMLElement} */ template;
/** @type {HTMLElement} */ dom;
/**@type {HTMLInputElement}*/ isEnabled;
/**@type {HTMLInputElement}*/ isCombined;

View File

@@ -1,5 +1,4 @@
import { QuickReply } from '../../QuickReply.js';
// eslint-disable-next-line no-unused-vars
import { QuickReplySet } from '../../QuickReplySet.js';
import { MenuHeader } from './MenuHeader.js';
import { MenuItem } from './MenuItem.js';

View File

@@ -2,7 +2,7 @@
<div class="regex_editor">
<h3 class="flex-container justifyCenter alignItemsBaseline">
<strong data-i18n="Regex Editor">Regex Editor</strong>
<a href="https://regexr.com/" class="notes-link" target="_blank">
<a href="https://docs.sillytavern.app/extensions/regex/" class="notes-link" target="_blank" rel="noopener noreferrer">
<span class="note-link-span">?</span>
</a>
<div id="regex_test_mode_toggle" class="menu_button menu_button_icon">
@@ -16,6 +16,13 @@
</small>
<hr />
<div id="regex_info_block_wrapper">
<div id="regex_info_block" class="info-block"></div>
<a id="regex_info_block_flags_hint" href="https://docs.sillytavern.app/extensions/regex/#flags" target="_blank" rel="noopener noreferrer">
<i class="fa-solid fa-circle-info" data-i18n="[title]ext_regex_flags_help" title="Click here to learn more about regex flags."></i>
</a>
</div>
<div id="regex_test_mode" class="flex1 flex-container displayNone">
<div class="flex1">
<label class="title_restorable" for="regex_test_input">
@@ -102,18 +109,18 @@
</div>
<div class="flex-container wide100p marginTop5">
<div class="flex1 flex-container flexNoGap">
<small data-i18n="[title]ext_regex_min_depth_desc" title="When applied to prompts or display, only affect messages that are at least N levels deep. 0 = last message, 1 = penultimate message, etc. Only counts WI entries @Depth and usable messages, i.e. not hidden or system.">
<small data-i18n="[title]ext_regex_min_depth_desc" title="When applied to prompts or display, only affect messages that are at least N levels deep. 0 = last message, 1 = penultimate message, etc. System prompt and utility prompts are not affected. When blank / 'Unlimited' or -1, also affect message to continue on Continue.">
<span data-i18n="Min Depth">Min Depth</span>
<span class="fa-solid fa-circle-question note-link-span"></span>
</small>
<input name="min_depth" class="text_pole textarea_compact" type="number" min="0" max="999" data-i18n="[placeholder]ext_regex_min_depth_placeholder" placeholder="Unlimited" />
<input name="min_depth" class="text_pole textarea_compact" type="number" min="-1" max="9999" data-i18n="[placeholder]ext_regex_min_depth_placeholder" placeholder="Unlimited" />
</div>
<div class="flex1 flex-container flexNoGap">
<small data-i18n="[title]ext_regex_max_depth_desc" title="When applied to prompts or display, only affect messages no more than N levels deep. 0 = last message, 1 = penultimate message, etc. Only counts WI entries @Depth and usable messages, i.e. not hidden or system.">
<small data-i18n="[title]ext_regex_max_depth_desc" title="When applied to prompts or display, only affect messages no more than N levels deep. 0 = last message, 1 = penultimate message, etc. System prompt and utility prompts are not affected. Max must be greater than Min for regex to apply.">
<span data-i18n="Max Depth">Max Depth</span>
<span class="fa-solid fa-circle-question note-link-span"></span>
</small>
<input name="max_depth" class="text_pole textarea_compact" type="number" min="0" max="999" data-i18n="[placeholder]ext_regex_min_depth_placeholder" placeholder="Unlimited" />
<input name="max_depth" class="text_pole textarea_compact" type="number" min="0" max="9999" data-i18n="[placeholder]ext_regex_min_depth_placeholder" placeholder="Unlimited" />
</div>
</div>
</div>
@@ -140,7 +147,7 @@
</label>
<span>
<small data-i18n="ext_regex_other_options" data-i18n="Ephemerality">Ephemerality</small>
<span class="fa-solid fa-circle-question note-link-span" title="By default, regex scripts alter the chat file directly and irreversibly.&#13;Enabling either (or both) of the options below will prevent chat file alteration, while still altering the specified item(s)."></span>
<span class="fa-solid fa-circle-question note-link-span" data-i18n="[title]ext_regex_other_options_desc" title="By default, regex scripts alter the chat file directly and irreversibly.&#13;Enabling either (or both) of the options below will prevent chat file alteration, while still altering the specified item(s)."></span>
</span>
<label class="checkbox flex-container" data-i18n="[title]ext_regex_only_format_visual_desc" title="Chat history file contents won't change, but regex will be applied to the messages displayed in the Chat UI.">
<input type="checkbox" name="only_format_display" />

View File

@@ -103,8 +103,8 @@ function getRegexedString(rawString, placement, { characterOverride, isMarkdown,
}
// Check if the depth is within the min/max depth
if (typeof depth === 'number' && depth >= 0) {
if (!isNaN(script.minDepth) && script.minDepth !== null && script.minDepth >= 0 && depth < script.minDepth) {
if (typeof depth === 'number') {
if (!isNaN(script.minDepth) && script.minDepth !== null && script.minDepth >= -1 && depth < script.minDepth) {
console.debug(`getRegexedString: Skipping script ${script.scriptName} because depth ${depth} is less than minDepth ${script.minDepth}`);
return;
}
@@ -139,7 +139,7 @@ function runRegexScript(regexScript, rawString, { characterOverride } = {}) {
}
const getRegexString = () => {
switch(Number(regexScript.substituteRegex)) {
switch (Number(regexScript.substituteRegex)) {
case substitute_find_regex.NONE:
return regexScript.findRegex;
case substitute_find_regex.RAW:

View File

@@ -4,22 +4,16 @@ import { selected_group } from '../../group-chats.js';
import { callGenericPopup, POPUP_TYPE } from '../../popup.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 { commonEnumProviders, 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 { download, equalsIgnoreCaseAndAccents, getFileText, getSortableDelay, isFalseBoolean, isTrueBoolean, regexFromString, setInfoBlock, uuidv4 } from '../../utils.js';
import { regex_placement, runRegexScript, substitute_find_regex } from './engine.js';
import { t } from '../../i18n.js';
import { accountStorage } from '../../util/AccountStorage.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 {number?} substituteRegex - The substitute regex
* @typedef {import('../../char-data.js').RegexScriptData} RegexScript
*/
/**
@@ -258,6 +252,8 @@ async function onRegexEditorOpenClick(existingId, isScoped) {
});
function updateTestResult() {
updateInfoBlock(editorHtml);
if (!editorHtml.find('#regex_test_mode').is(':visible')) {
return;
}
@@ -276,6 +272,7 @@ async function onRegexEditorOpenClick(existingId, isScoped) {
}
editorHtml.find('input, textarea, select').on('input', updateTestResult);
updateInfoBlock(editorHtml);
const popupResult = await callPopup(editorHtml, 'confirm', undefined, { okButton: t`Save` });
if (popupResult) {
@@ -305,6 +302,40 @@ async function onRegexEditorOpenClick(existingId, isScoped) {
}
}
/**
* Updates the info block in the regex editor with hints regarding the find regex.
* @param {JQuery<HTMLElement>} editorHtml The editor HTML
*/
function updateInfoBlock(editorHtml) {
const infoBlock = editorHtml.find('.info-block').get(0);
const infoBlockFlagsHint = editorHtml.find('#regex_info_block_flags_hint');
const findRegex = String(editorHtml.find('.find_regex').val());
infoBlockFlagsHint.hide();
// Clear the info block if the find regex is empty
if (!findRegex) {
setInfoBlock(infoBlock, t`Find Regex is empty`, 'info');
return;
}
try {
const regex = regexFromString(findRegex);
if (!regex) {
throw new Error(t`Invalid Find Regex`);
}
const flagInfo = [];
flagInfo.push(regex.flags.includes('g') ? t`Applies to all matches` : t`Applies to the first match`);
flagInfo.push(regex.flags.includes('i') ? t`Case insensitive` : t`Case sensitive`);
setInfoBlock(infoBlock, flagInfo.join('. '), 'hint');
infoBlockFlagsHint.show();
} catch (error) {
setInfoBlock(infoBlock, error.message, 'error');
}
}
// Common settings migration function. Some parts will eventually be removed
// TODO: Maybe migrate placement to strings?
function migrateSettings() {
@@ -367,7 +398,7 @@ function runRegexCallback(args, value) {
for (const script of scripts) {
if (script.scriptName.toLowerCase() === scriptName.toLowerCase()) {
if (script.disabled) {
toastr.warning(`Regex script "${scriptName}" is disabled.`);
toastr.warning(t`Regex script "${scriptName}" is disabled.`);
return value;
}
@@ -380,6 +411,53 @@ function runRegexCallback(args, value) {
return value;
}
/**
* /regex-toggle slash command callback
* @param {{state: string, quiet: string}} args Named arguments
* @param {string} scriptName The name of the script to toggle
* @returns {Promise<string>} The name of the script
*/
async function toggleRegexCallback(args, scriptName) {
if (typeof scriptName !== 'string') throw new Error('Script name must be a string.');
const quiet = isTrueBoolean(args?.quiet);
const action = isTrueBoolean(args?.state) ? 'enable' :
isFalseBoolean(args?.state) ? 'disable' :
'toggle';
const scripts = getRegexScripts();
const script = scripts.find(s => equalsIgnoreCaseAndAccents(s.scriptName, scriptName));
if (!script) {
toastr.warning(t`Regex script '${scriptName}' not found.`);
return '';
}
switch (action) {
case 'enable':
script.disabled = false;
break;
case 'disable':
script.disabled = true;
break;
default:
script.disabled = !script.disabled;
break;
}
const isScoped = characters[this_chid]?.data?.extensions?.regex_scripts?.some(s => s.id === script.id);
const index = isScoped ? characters[this_chid]?.data?.extensions?.regex_scripts?.indexOf(script) : scripts.indexOf(script);
await saveRegexScript(script, index, isScoped);
if (script.disabled) {
!quiet && toastr.success(t`Regex script '${scriptName}' has been disabled.`);
} else {
!quiet && toastr.success(t`Regex script '${scriptName}' has been enabled.`);
}
return script.scriptName || '';
}
/**
* Performs the import of the regex file.
* @param {File} file Input file
@@ -418,7 +496,7 @@ async function onRegexImportFileChange(file, isScoped) {
}
}
function purgeEmbeddedRegexScripts( { character }){
function purgeEmbeddedRegexScripts({ character }) {
const avatar = character?.avatar;
if (avatar && extension_settings.character_allowed_regex?.includes(avatar)) {
@@ -602,6 +680,51 @@ jQuery(async () => {
],
helpString: 'Runs a Regex extension script by name on the provided string. The script must be enabled.',
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'regex-toggle',
callback: toggleRegexCallback,
returns: 'The name of the script that was toggled',
namedArgumentList: [
SlashCommandNamedArgument.fromProps({
name: 'state',
description: 'Explicitly set the state of the script (\'on\' to enable, \'off\' to disable). If not provided, the state will be toggled to the opposite of the current state.',
typeList: [ARGUMENT_TYPE.BOOLEAN],
defaultValue: 'toggle',
enumList: commonEnumProviders.boolean('onOffToggle')(),
}),
SlashCommandNamedArgument.fromProps({
name: 'quiet',
description: 'Suppress the toast message script toggled',
typeList: [ARGUMENT_TYPE.BOOLEAN],
defaultValue: 'false',
enumList: commonEnumProviders.boolean('trueFalse')(),
}),
],
unnamedArgumentList: [
SlashCommandArgument.fromProps({
description: 'script name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: localEnumProviders.regexScripts,
}),
],
helpString: `
<div>
Toggles the state of a specified regex script.
</div>
<div>
<strong>Example:</strong>
<ul>
<li>
<pre><code class="language-stscript">/regex-toggle MyScript</code></pre>
</li>
<li>
<pre><code class="language-stscript">/regex-toggle state=off Character-specific Script</code></pre>
</li>
</ul>
</div>
`,
}));
eventSource.on(event_types.CHAT_CHANGED, checkEmbeddedRegexScripts);
eventSource.on(event_types.CHARACTER_DELETED, purgeEmbeddedRegexScripts);

View File

@@ -105,3 +105,24 @@ input.enable_scoped {
.disable_regex:not(:checked) ~ .regex-toggle-on {
display: block;
}
#regex_info_block_wrapper {
position: relative;
}
#regex_info_block {
margin: 10px 0;
padding: 5px 20px;
font-size: 0.9em;
}
#regex_info_block_wrapper:has(#regex_info_block:empty) {
display: none;
}
#regex_info_block_flags_hint {
position: absolute;
top: 50%;
right: 10px;
transform: translateY(-50%);
}

View File

@@ -1,6 +1,7 @@
import { getRequestHeaders } from '../../script.js';
import { CONNECT_API_MAP, getRequestHeaders } from '../../script.js';
import { extension_settings, openThirdPartyExtensionMenu } from '../extensions.js';
import { oai_settings } from '../openai.js';
import { t } from '../i18n.js';
import { oai_settings, proxies } from '../openai.js';
import { SECRET_KEYS, secret_state } from '../secrets.js';
import { textgen_types, textgenerationwebui_settings } from '../textgen-settings.js';
import { getTokenCountAsync } from '../tokenizers.js';
@@ -152,6 +153,10 @@ function throwIfInvalidModel(useReverseProxy) {
throw new Error('Cohere API key is not set.');
}
if (extension_settings.caption.multimodal_api === 'xai' && !secret_state[SECRET_KEYS.XAI]) {
throw new Error('xAI API key is not set.');
}
if (extension_settings.caption.multimodal_api === 'ollama' && !textgenerationwebui_settings.server_urls[textgen_types.OLLAMA]) {
throw new Error('Ollama server URL is not set.');
}
@@ -273,3 +278,329 @@ export async function getWebLlmContextSize() {
const model = await engine.getCurrentModelInfo();
return model?.context_size;
}
/**
* It uses the profiles to send a generate request to the API.
*/
export class ConnectionManagerRequestService {
static defaultSendRequestParams = {
stream: false,
signal: null,
extractData: true,
includePreset: true,
includeInstruct: true,
instructSettings: {},
};
static getAllowedTypes() {
return {
openai: t`Chat Completion`,
textgenerationwebui: t`Text Completion`,
};
}
/**
* @param {string} profileId
* @param {string | (import('../custom-request.js').ChatCompletionMessage & {ignoreInstruct?: boolean})[]} prompt
* @param {number} maxTokens
* @param {Object} custom
* @param {boolean?} [custom.stream=false]
* @param {AbortSignal?} [custom.signal]
* @param {boolean?} [custom.extractData=true]
* @param {boolean?} [custom.includePreset=true]
* @param {boolean?} [custom.includeInstruct=true]
* @param {Partial<InstructSettings>?} [custom.instructSettings] Override instruct settings
* @param {Record<string, any>} [overridePayload] - Override payload for the request
* @returns {Promise<import('../custom-request.js').ExtractedData | (() => AsyncGenerator<import('../custom-request.js').StreamResponse>)>} If not streaming, returns extracted data; if streaming, returns a function that creates an AsyncGenerator
*/
static async sendRequest(profileId, prompt, maxTokens, custom = this.defaultSendRequestParams, overridePayload = {}) {
const { stream, signal, extractData, includePreset, includeInstruct, instructSettings } = { ...this.defaultSendRequestParams, ...custom };
const context = SillyTavern.getContext();
if (context.extensionSettings.disabledExtensions.includes('connection-manager')) {
throw new Error('Connection Manager is not available');
}
const profile = context.extensionSettings.connectionManager.profiles.find((p) => p.id === profileId);
const selectedApiMap = this.validateProfile(profile);
try {
switch (selectedApiMap.selected) {
case 'openai': {
if (!selectedApiMap.source) {
throw new Error(`API type ${selectedApiMap.selected} does not support chat completions`);
}
const proxyPreset = proxies.find((p) => p.name === profile.proxy);
const messages = Array.isArray(prompt) ? prompt : [{ role: 'user', content: prompt }];
return await context.ChatCompletionService.processRequest({
stream,
messages,
max_tokens: maxTokens,
model: profile.model,
chat_completion_source: selectedApiMap.source,
custom_url: profile['api-url'],
reverse_proxy: proxyPreset?.url,
proxy_password: proxyPreset?.password,
...overridePayload,
}, {
presetName: includePreset ? profile.preset : undefined,
}, extractData, signal);
}
case 'textgenerationwebui': {
if (!selectedApiMap.type) {
throw new Error(`API type ${selectedApiMap.selected} does not support text completions`);
}
return await context.TextCompletionService.processRequest({
stream,
prompt,
max_tokens: maxTokens,
model: profile.model,
api_type: selectedApiMap.type,
api_server: profile['api-url'],
...overridePayload,
}, {
instructName: includeInstruct ? profile.instruct : undefined,
presetName: includePreset ? profile.preset : undefined,
instructSettings: includeInstruct ? instructSettings : undefined,
}, extractData, signal);
}
default: {
throw new Error(`Unknown API type ${selectedApiMap.selected}`);
}
}
} catch (error) {
throw new Error('API request failed', { cause: error });
}
}
/**
* Respects allowed types.
* @returns {import('./connection-manager/index.js').ConnectionProfile[]}
*/
static getSupportedProfiles() {
const context = SillyTavern.getContext();
if (context.extensionSettings.disabledExtensions.includes('connection-manager')) {
throw new Error('Connection Manager is not available');
}
const profiles = context.extensionSettings.connectionManager.profiles;
return profiles.filter((p) => this.isProfileSupported(p));
}
/**
* @param {import('./connection-manager/index.js').ConnectionProfile?} [profile]
* @returns {boolean}
*/
static isProfileSupported(profile) {
if (!profile || !profile.api) {
return false;
}
const apiMap = CONNECT_API_MAP[profile.api];
if (!Object.hasOwn(this.getAllowedTypes(), apiMap.selected)) {
return false;
}
// Some providers not need model, like koboldcpp. But I don't want to check by provider.
switch (apiMap.selected) {
case 'openai':
return !!apiMap.source;
case 'textgenerationwebui':
return !!apiMap.type;
}
return false;
}
/**
* @param {import('./connection-manager/index.js').ConnectionProfile?} [profile]
* @return {import('../../script.js').ConnectAPIMap}
* @throws {Error}
*/
static validateProfile(profile) {
if (!profile) {
throw new Error('Could not find profile.');
}
if (!profile.api) {
throw new Error('Select a connection profile that has an API');
}
const context = SillyTavern.getContext();
const selectedApiMap = context.CONNECT_API_MAP[profile.api];
if (!selectedApiMap) {
throw new Error(`Unknown API type ${profile.api}`);
}
if (!Object.hasOwn(this.getAllowedTypes(), selectedApiMap.selected)) {
throw new Error(`API type ${selectedApiMap.selected} is not supported. Supported types: ${Object.values(this.getAllowedTypes()).join(', ')}`);
}
return selectedApiMap;
}
/**
* Create profiles dropdown and updates select element accordingly. Use onChange, onCreate, unUpdate, onDelete callbacks for custom behaviour. e.g updating extension settings.
* @param {string} selector
* @param {string} initialSelectedProfileId
* @param {(profile?: import('./connection-manager/index.js').ConnectionProfile) => Promise<void> | void} onChange - 3 cases. 1- When user selects new profile. 2- When user deletes selected profile. 3- When user updates selected profile.
* @param {(profile: import('./connection-manager/index.js').ConnectionProfile) => Promise<void> | void} onCreate
* @param {(oldProfile: import('./connection-manager/index.js').ConnectionProfile, newProfile: import('./connection-manager/index.js').ConnectionProfile) => Promise<void> | void} unUpdate
* @param {(profile: import('./connection-manager/index.js').ConnectionProfile) => Promise<void> | void} onDelete
*/
static handleDropdown(
selector,
initialSelectedProfileId,
onChange = () => { },
onCreate = () => { },
unUpdate = () => { },
onDelete = () => { },
) {
const context = SillyTavern.getContext();
if (context.extensionSettings.disabledExtensions.includes('connection-manager')) {
throw new Error('Connection Manager is not available');
}
/**
* @type {JQuery<HTMLSelectElement>}
*/
const dropdown = $(selector);
if (!dropdown || !dropdown.length) {
throw new Error(`Could not find dropdown with selector ${selector}`);
}
dropdown.empty();
// Create default option using document.createElement
const defaultOption = document.createElement('option');
defaultOption.value = '';
defaultOption.textContent = 'Select a Connection Profile';
defaultOption.dataset.i18n = 'Select a Connection Profile';
dropdown.append(defaultOption);
const profiles = context.extensionSettings.connectionManager.profiles;
// Create optgroups using document.createElement
const groups = {};
for (const [apiType, groupLabel] of Object.entries(this.getAllowedTypes())) {
const optgroup = document.createElement('optgroup');
optgroup.label = groupLabel;
groups[apiType] = optgroup;
}
const sortedProfilesByGroup = {};
for (const apiType of Object.keys(this.getAllowedTypes())) {
sortedProfilesByGroup[apiType] = [];
}
for (const profile of profiles) {
if (this.isProfileSupported(profile)) {
const apiMap = CONNECT_API_MAP[profile.api];
if (sortedProfilesByGroup[apiMap.selected]) {
sortedProfilesByGroup[apiMap.selected].push(profile);
}
}
}
// Sort each group alphabetically and add to dropdown
for (const [apiType, groupProfiles] of Object.entries(sortedProfilesByGroup)) {
if (groupProfiles.length === 0) continue;
groupProfiles.sort((a, b) => a.name.localeCompare(b.name));
const group = groups[apiType];
for (const profile of groupProfiles) {
const option = document.createElement('option');
option.value = profile.id;
option.textContent = profile.name;
group.appendChild(option);
}
}
for (const group of Object.values(groups)) {
if (group.children.length > 0) {
dropdown.append(group);
}
}
const selectedProfile = profiles.find((p) => p.id === initialSelectedProfileId);
if (selectedProfile) {
dropdown.val(selectedProfile.id);
}
context.eventSource.on(context.eventTypes.CONNECTION_PROFILE_CREATED, async (profile) => {
const isSupported = this.isProfileSupported(profile);
if (!isSupported) {
return;
}
const group = groups[CONNECT_API_MAP[profile.api].selected];
const option = document.createElement('option');
option.value = profile.id;
option.textContent = profile.name;
group.appendChild(option);
await onCreate(profile);
});
context.eventSource.on(context.eventTypes.CONNECTION_PROFILE_UPDATED, async (oldProfile, newProfile) => {
const currentSelected = dropdown.val();
const isSelectedProfile = currentSelected === oldProfile.id;
await unUpdate(oldProfile, newProfile);
if (!this.isProfileSupported(newProfile)) {
if (isSelectedProfile) {
dropdown.val('');
dropdown.trigger('change');
}
return;
}
const group = groups[CONNECT_API_MAP[newProfile.api].selected];
const oldOption = group.querySelector(`option[value="${oldProfile.id}"]`);
if (oldOption) {
oldOption.remove();
}
const option = document.createElement('option');
option.value = newProfile.id;
option.textContent = newProfile.name;
group.appendChild(option);
if (isSelectedProfile) {
// Ackchyually, we don't need to reselect but what if id changes? It is not possible for now I couldn't stop myself.
dropdown.val(newProfile.id);
dropdown.trigger('change');
}
});
context.eventSource.on(context.eventTypes.CONNECTION_PROFILE_DELETED, async (profile) => {
const currentSelected = dropdown.val();
const isSelectedProfile = currentSelected === profile.id;
if (!this.isProfileSupported(profile)) {
return;
}
const group = groups[CONNECT_API_MAP[profile.api].selected];
const optionToRemove = group.querySelector(`option[value="${profile.id}"]`);
if (optionToRemove) {
optionToRemove.remove();
}
if (isSelectedProfile) {
dropdown.val('');
dropdown.trigger('change');
}
await onDelete(profile);
});
dropdown.on('change', async () => {
const profileId = dropdown.val();
const profile = context.extensionSettings.connectionManager.profiles.find((p) => p.id === profileId);
await onChange(profile);
});
}
}

View File

@@ -77,11 +77,11 @@ const sources = {
drawthings: 'drawthings',
pollinations: 'pollinations',
stability: 'stability',
blockentropy: 'blockentropy',
huggingface: 'huggingface',
nanogpt: 'nanogpt',
bfl: 'bfl',
falai: 'falai',
xai: 'xai',
};
const initiators = {
@@ -1300,11 +1300,11 @@ async function onModelChange() {
sources.togetherai,
sources.pollinations,
sources.stability,
sources.blockentropy,
sources.huggingface,
sources.nanogpt,
sources.bfl,
sources.falai,
sources.xai,
];
if (cloudSources.includes(extension_settings.sd.source)) {
@@ -1511,9 +1511,6 @@ async function loadSamplers() {
case sources.stability:
samplers = ['N/A'];
break;
case sources.blockentropy:
samplers = ['N/A'];
break;
case sources.huggingface:
samplers = ['N/A'];
break;
@@ -1523,6 +1520,9 @@ async function loadSamplers() {
case sources.bfl:
samplers = ['N/A'];
break;
case sources.xai:
samplers = ['N/A'];
break;
}
for (const sampler of samplers) {
@@ -1701,9 +1701,6 @@ async function loadModels() {
case sources.stability:
models = await loadStabilityModels();
break;
case sources.blockentropy:
models = await loadBlockEntropyModels();
break;
case sources.huggingface:
models = [{ value: '', text: '<Enter Model ID above>' }];
break;
@@ -1716,6 +1713,9 @@ async function loadModels() {
case sources.falai:
models = await loadFalaiModels();
break;
case sources.xai:
models = await loadXAIModels();
break;
}
for (const model of models) {
@@ -1768,6 +1768,12 @@ async function loadFalaiModels() {
return [];
}
async function loadXAIModels() {
return [
{ value: 'grok-2-image-1212', text: 'grok-2-image-1212' },
];
}
async function loadPollinationsModels() {
const result = await fetch('/api/sd/pollinations/models', {
method: 'POST',
@@ -1799,26 +1805,6 @@ async function loadTogetherAIModels() {
return [];
}
async function loadBlockEntropyModels() {
if (!secret_state[SECRET_KEYS.BLOCKENTROPY]) {
console.debug('Block Entropy API key is not set.');
return [];
}
const result = await fetch('/api/sd/blockentropy/models', {
method: 'POST',
headers: getRequestHeaders(),
});
console.log(result);
if (result.ok) {
const data = await result.json();
console.log(data);
return data;
}
return [];
}
async function loadNanoGPTModels() {
if (!secret_state[SECRET_KEYS.NANOGPT]) {
console.debug('NanoGPT API key is not set.');
@@ -2097,9 +2083,6 @@ async function loadSchedulers() {
case sources.stability:
schedulers = ['N/A'];
break;
case sources.blockentropy:
schedulers = ['N/A'];
break;
case sources.huggingface:
schedulers = ['N/A'];
break;
@@ -2112,6 +2095,9 @@ async function loadSchedulers() {
case sources.falai:
schedulers = ['N/A'];
break;
case sources.xai:
schedulers = ['N/A'];
break;
}
for (const scheduler of schedulers) {
@@ -2188,9 +2174,6 @@ async function loadVaes() {
case sources.stability:
vaes = ['N/A'];
break;
case sources.blockentropy:
vaes = ['N/A'];
break;
case sources.huggingface:
vaes = ['N/A'];
break;
@@ -2200,6 +2183,12 @@ async function loadVaes() {
case sources.bfl:
vaes = ['N/A'];
break;
case sources.falai:
vaes = ['N/A'];
break;
case sources.xai:
vaes = ['N/A'];
break;
}
for (const vae of vaes) {
@@ -2337,10 +2326,10 @@ function processReply(str) {
str = str.replaceAll('“', '');
str = str.replaceAll('\n', ', ');
str = str.normalize('NFD');
// Strip out non-alphanumeric characters barring model syntax exceptions
str = str.replace(/[^a-zA-Z0-9.,:_(){}<>[\]\-'|#]+/g, ' ');
str = str.replace(/\s+/g, ' '); // Collapse multiple whitespaces into one
str = str.trim();
@@ -2757,9 +2746,6 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP
case sources.stability:
result = await generateStabilityImage(prefixedPrompt, negativePrompt, signal);
break;
case sources.blockentropy:
result = await generateBlockEntropyImage(prefixedPrompt, negativePrompt, signal);
break;
case sources.huggingface:
result = await generateHuggingFaceImage(prefixedPrompt, signal);
break;
@@ -2772,6 +2758,9 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP
case sources.falai:
result = await generateFalaiImage(prefixedPrompt, negativePrompt, signal);
break;
case sources.xai:
result = await generateXAIImage(prefixedPrompt, negativePrompt, signal);
break;
}
if (!result.data) {
@@ -2828,40 +2817,6 @@ async function generateTogetherAIImage(prompt, negativePrompt, signal) {
}
}
async function generateBlockEntropyImage(prompt, negativePrompt, signal) {
const result = await fetch('/api/sd/blockentropy/generate', {
method: 'POST',
headers: getRequestHeaders(),
signal: signal,
body: JSON.stringify({
prompt: prompt,
negative_prompt: negativePrompt,
model: extension_settings.sd.model,
steps: extension_settings.sd.steps,
width: extension_settings.sd.width,
height: extension_settings.sd.height,
seed: extension_settings.sd.seed >= 0 ? extension_settings.sd.seed : undefined,
}),
});
if (result.ok) {
const data = await result.json();
// Default format is 'jpg'
let format = 'jpg';
// Check if a format is specified in the result
if (data.format) {
format = data.format.toLowerCase();
}
return { format: format, data: data.images[0] };
} else {
const text = await result.text();
throw new Error(text);
}
}
/**
* Generates an image using the Pollinations API.
* @param {string} prompt - The main instruction used to guide the image generation.
@@ -3234,7 +3189,7 @@ function getNovelParams() {
extension_settings.sd.scheduler = 'karras';
}
if (extension_settings.sd.sampler === 'ddim' ||
if (extension_settings.sd.sampler === 'ddim' ||
['nai-diffusion-4-curated-preview', 'nai-diffusion-4-full'].includes(extension_settings.sd.model)) {
sm = false;
sm_dyn = false;
@@ -3534,6 +3489,33 @@ async function generateBflImage(prompt, signal) {
}
}
/**
* Generates an image using the xAI API.
* @param {string} prompt The main instruction used to guide the image generation.
* @param {string} _negativePrompt Negative prompt is not used in this API
* @param {AbortSignal} signal An AbortSignal object that can be used to cancel the request.
* @returns {Promise<{format: string, data: string}>} A promise that resolves when the image generation and processing are complete.
*/
async function generateXAIImage(prompt, _negativePrompt, signal) {
const result = await fetch('/api/sd/xai/generate', {
method: 'POST',
headers: getRequestHeaders(),
signal: signal,
body: JSON.stringify({
prompt: prompt,
model: extension_settings.sd.model,
}),
});
if (result.ok) {
const data = await result.json();
return { format: 'jpg', data: data.image };
} else {
const text = await result.text();
throw new Error(text);
}
}
/**
* Generates an image using the FAL.AI API.
* @param {string} prompt - The main instruction used to guide the image generation.
@@ -3739,9 +3721,9 @@ async function sendMessage(prompt, image, generationType, additionalNegativePref
};
context.chat.push(message);
const messageId = context.chat.length - 1;
await eventSource.emit(event_types.MESSAGE_RECEIVED, messageId);
await eventSource.emit(event_types.MESSAGE_RECEIVED, messageId, 'extension');
context.addOneMessage(message);
await eventSource.emit(event_types.CHARACTER_MESSAGE_RENDERED, messageId);
await eventSource.emit(event_types.CHARACTER_MESSAGE_RENDERED, messageId, 'extension');
await context.saveChat();
}
@@ -3772,7 +3754,6 @@ async function addSDGenButtons() {
$('#sd_wand_container').append(buttonHtml);
$(document.body).append(dropdownHtml);
const messageButton = $('.sd_message_gen');
const button = $('#sd_gen');
const dropdown = $('#sd_dropdown');
dropdown.hide();
@@ -3846,8 +3827,6 @@ function isValidState() {
return true;
case sources.stability:
return secret_state[SECRET_KEYS.STABILITY];
case sources.blockentropy:
return secret_state[SECRET_KEYS.BLOCKENTROPY];
case sources.huggingface:
return secret_state[SECRET_KEYS.HUGGINGFACE];
case sources.nanogpt:
@@ -3856,6 +3835,8 @@ function isValidState() {
return secret_state[SECRET_KEYS.BFL];
case sources.falai:
return secret_state[SECRET_KEYS.FALAI];
case sources.xai:
return secret_state[SECRET_KEYS.XAI];
}
}

View File

@@ -38,7 +38,6 @@
<label for="sd_source" data-i18n="Source">Source</label>
<select id="sd_source">
<option value="bfl">BFL (Black Forest Labs)</option>
<option value="blockentropy">Block Entropy</option>
<option value="comfy">ComfyUI</option>
<option value="drawthings">DrawThings HTTP API</option>
<option value="extras">Extras API (deprecated)</option>
@@ -53,6 +52,7 @@
<option value="auto">Stable Diffusion Web UI (AUTOMATIC1111)</option>
<option value="horde">Stable Horde</option>
<option value="togetherai">TogetherAI</option>
<option value="xai">xAI (Grok)</option>
</select>
<div data-sd-source="auto">
<label for="sd_auto_url">SD Web UI URL</label>
@@ -422,7 +422,7 @@
</label>
</div>
<div data-sd-source="novel,togetherai,pollinations,comfy,drawthings,vlad,auto,horde,extras,stability,blockentropy,bfl" class="marginTop5">
<div data-sd-source="novel,togetherai,pollinations,comfy,drawthings,vlad,auto,horde,extras,stability,bfl" class="marginTop5">
<label for="sd_seed">
<span data-i18n="Seed">Seed</span>
<small data-i18n="(-1 for random)">(-1 for random)</small>

View File

@@ -24,7 +24,7 @@ $('button').click(function () {
async function doTokenCounter() {
const { tokenizerName, tokenizerId } = getFriendlyTokenizerName(main_api);
const html = await renderExtensionTemplateAsync('token-counter', 'window', {tokenizerName});
const html = await renderExtensionTemplateAsync('token-counter', 'window', { tokenizerName });
const dialog = $(html);
const countDebounced = debounce(async () => {

View File

@@ -183,8 +183,6 @@ class GptSovitsV2Provider {
let prompt_text = replaceSpeaker(voiceId);
const streaming = this.settings.streaming;
const params = {
text: inputText,
prompt_text: prompt_text,

View File

@@ -1,5 +1,5 @@
import { cancelTtsPlay, eventSource, event_types, getCurrentChatId, isStreamingEnabled, name2, saveSettingsDebounced, substituteParams } from '../../../script.js';
import { ModuleWorkerWrapper, doExtrasFetch, extension_settings, getApiUrl, getContext, modules, renderExtensionTemplateAsync } from '../../extensions.js';
import { ModuleWorkerWrapper, extension_settings, getContext, renderExtensionTemplateAsync } from '../../extensions.js';
import { delay, escapeRegex, getBase64Async, getStringHash, onlyUnique } from '../../utils.js';
import { EdgeTtsProvider } from './edge.js';
import { ElevenLabsTtsProvider } from './elevenlabs.js';
@@ -27,6 +27,7 @@ import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashComm
import { enumIcons } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
import { POPUP_TYPE, callGenericPopup } from '../../popup.js';
import { GoogleTranslateTtsProvider } from './google-translate.js';
import { KokoroTtsProvider } from './kokoro.js';
const UPDATE_INTERVAL = 1000;
const wrapper = new ModuleWorkerWrapper(moduleWorker);
@@ -94,6 +95,7 @@ const ttsProviders = {
'Google Translate': GoogleTranslateTtsProvider,
GSVI: GSVITtsProvider,
'GPT-SoVITS-V2 (Unofficial)': GptSovitsV2Provider,
Kokoro: KokoroTtsProvider,
Novel: NovelTtsProvider,
OpenAI: OpenAITtsProvider,
'OpenAI Compatible': OpenAICompatibleTtsProvider,
@@ -469,6 +471,7 @@ async function processTtsQueue() {
if (extension_settings.tts.skip_codeblocks) {
text = text.replace(/^\s{4}.*$/gm, '').trim();
text = text.replace(/```.*?```/gs, '').trim();
text = text.replace(/~~~.*?~~~/gs, '').trim();
}
if (extension_settings.tts.skip_tags) {
@@ -716,6 +719,9 @@ async function loadTtsProvider(provider) {
}
function onTtsProviderChange() {
if (typeof ttsProvider?.dispose === 'function') {
ttsProvider.dispose();
}
const ttsProviderSelection = $('#tts_provider').val();
extension_settings.tts.currentProvider = ttsProviderSelection;
$('#playback_rate_block').toggle(extension_settings.tts.currentProvider !== 'System');
@@ -1202,8 +1208,8 @@ jQuery(async function () {
eventSource.on(event_types.GROUP_UPDATED, onChatChanged);
eventSource.on(event_types.GENERATION_STARTED, onGenerationStarted);
eventSource.on(event_types.GENERATION_ENDED, onGenerationEnded);
eventSource.makeLast(event_types.CHARACTER_MESSAGE_RENDERED, onMessageEvent);
eventSource.makeLast(event_types.USER_MESSAGE_RENDERED, onMessageEvent);
eventSource.makeLast(event_types.CHARACTER_MESSAGE_RENDERED, (messageId) => onMessageEvent(messageId));
eventSource.makeLast(event_types.USER_MESSAGE_RENDERED, (messageId) => onMessageEvent(messageId));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'speak',
callback: async (args, value) => {

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