Merge branch 'staging' into ffmpeg-videobg
| @@ -13,3 +13,4 @@ access.log | |||
| 13 | /cache | 13 | /cache |
| 14 | .DS_Store | 14 | .DS_Store |
| 15 | /public/scripts/extensions/third-party | 15 | /public/scripts/extensions/third-party |
| 16 | /colab | ||
| @@ -350,6 +350,7 @@ Start.bat --port 8000 --listen false | |||
| 350 | | Option | Description | Type | | 350 | | Option | Description | Type | |
| 351 | |-------------------------|----------------------------------------------------------------------|----------| | 351 | |-------------------------|----------------------------------------------------------------------|----------| |
| 352 | | `--version` | Show version number | boolean | | 352 | | `--version` | Show version number | boolean | |
| 353 | | `--configPath` | Override the path to the config.yaml file | string | | ||
| 353 | | `--dataRoot` | Root directory for data storage | string | | 354 | | `--dataRoot` | Root directory for data storage | string | |
| 354 | | `--port` | Sets the port under which SillyTavern will run | number | | 355 | | `--port` | Sets the port under which SillyTavern will run | number | |
| 355 | | `--listen` | SillyTavern will listen on all network interfaces | boolean | | 356 | | `--listen` | SillyTavern will listen on all network interfaces | boolean | |
| @@ -30,7 +30,8 @@ jobs: | |||
| 30 | # https://github.com/marketplace/actions/checkout | 30 | # https://github.com/marketplace/actions/checkout |
| 31 | uses: actions/checkout@v4.2.2 | 31 | uses: actions/checkout@v4.2.2 |
| 32 | with: | 32 | with: |
| 33 | ref: ${{ github.head_ref }} | 33 | ref: ${{ github.event.pull_request.head.sha }} |
| 34 | repository: ${{ github.event.pull_request.head.repo.full_name }} | ||
| 34 | 35 | ||
| 35 | - name: Setup Node.js | 36 | - name: Setup Node.js |
| 36 | # Setup Node.js environment | 37 | # Setup Node.js environment |
| @@ -49,7 +50,7 @@ jobs: | |||
| 49 | with: | 50 | with: |
| 50 | token: ${{ secrets.GITHUB_TOKEN }} | 51 | token: ${{ secrets.GITHUB_TOKEN }} |
| 51 | eslint-args: '--ignore-path=.gitignore --quiet' | 52 | eslint-args: '--ignore-path=.gitignore --quiet' |
| 52 | extensions: 'js,ts' | 53 | extensions: 'js' |
| 53 | annotations: true | 54 | annotations: true |
| 54 | ignore-patterns: | | 55 | ignore-patterns: | |
| 55 | dist/ | 56 | dist/ |
| @@ -12,3 +12,4 @@ access.log | |||
| 12 | .vscode | 12 | .vscode |
| 13 | .git | 13 | .git |
| 14 | /public/scripts/extensions/third-party | 14 | /public/scripts/extensions/third-party |
| 15 | /colab | ||
| @@ -12,15 +12,13 @@ WORKDIR ${APP_HOME} | |||
| 12 | # Set NODE_ENV to production | 12 | # Set NODE_ENV to production |
| 13 | ENV NODE_ENV=production | 13 | ENV NODE_ENV=production |
| 14 | 14 | ||
| 15 | # Install app dependencies | 15 | # Bundle app source |
| 16 | COPY package*.json post-install.js ./ | 16 | COPY . ./ |
| 17 | |||
| 17 | RUN \ | 18 | RUN \ |
| 18 | echo "*** Install npm packages ***" && \ | 19 | echo "*** Install npm packages ***" && \ |
| 19 | npm i --no-audit --no-fund --loglevel=error --no-progress --omit=dev && npm cache clean --force | 20 | npm i --no-audit --no-fund --loglevel=error --no-progress --omit=dev && npm cache clean --force |
| 20 | 21 | ||
| 21 | # Bundle app source | ||
| 22 | COPY . ./ | ||
| 23 | |||
| 24 | # Copy default chats, characters and user avatars to <folder>.default folder | 22 | # Copy default chats, characters and user avatars to <folder>.default folder |
| 25 | RUN \ | 23 | RUN \ |
| 26 | rm -f "config.yaml" || true && \ | 24 | rm -f "config.yaml" || true && \ |
| @@ -155,6 +155,7 @@ whitelistImportDomains: | |||
| 155 | - cdn.discordapp.com | 155 | - cdn.discordapp.com |
| 156 | - files.catbox.moe | 156 | - files.catbox.moe |
| 157 | - raw.githubusercontent.com | 157 | - raw.githubusercontent.com |
| 158 | - char-archive.evulid.cc | ||
| 158 | # API request overrides (for KoboldAI and Text Completion APIs) | 159 | # API request overrides (for KoboldAI and Text Completion APIs) |
| 159 | ## Note: host includes the port number if it's not the default (80 or 443) | 160 | ## Note: host includes the port number if it's not the default (80 or 443) |
| 160 | ## Format is an array of objects: | 161 | ## Format is an array of objects: |
| @@ -233,6 +234,10 @@ claude: | |||
| 233 | # should be ideal for most use cases. | 234 | # should be ideal for most use cases. |
| 234 | # Any value other than a non-negative integer will be ignored and caching at depth will not be enabled. | 235 | # Any value other than a non-negative integer will be ignored and caching at depth will not be enabled. |
| 235 | cachingAtDepth: -1 | 236 | cachingAtDepth: -1 |
| 237 | # -- GOOGLE GEMINI API CONFIGURATION -- | ||
| 238 | gemini: | ||
| 239 | # API endpoint version ("v1beta" or "v1alpha") | ||
| 240 | apiVersion: 'v1beta' | ||
| 236 | # -- SERVER PLUGIN CONFIGURATION -- | 241 | # -- SERVER PLUGIN CONFIGURATION -- |
| 237 | enableServerPlugins: false | 242 | enableServerPlugins: false |
| 238 | # Attempt to automatically update server plugins on startup | 243 | # Attempt to automatically update server plugins on startup |
| @@ -540,7 +540,7 @@ | |||
| 540 | "type": "context" | 540 | "type": "context" |
| 541 | }, | 541 | }, |
| 542 | { | 542 | { |
| 543 | "filename": "presets/context/Pygmalion.json", | 543 | "filename": "presets/context/Metharme.json", |
| 544 | "type": "context" | 544 | "type": "context" |
| 545 | }, | 545 | }, |
| 546 | { | 546 | { |
| @@ -564,6 +564,10 @@ | |||
| 564 | "type": "context" | 564 | "type": "context" |
| 565 | }, | 565 | }, |
| 566 | { | 566 | { |
| 567 | "filename": "presets/context/Llama 4 Instruct.json", | ||
| 568 | "type": "context" | ||
| 569 | }, | ||
| 570 | { | ||
| 567 | "filename": "presets/context/Phi.json", | 571 | "filename": "presets/context/Phi.json", |
| 568 | "type": "context" | 572 | "type": "context" |
| 569 | }, | 573 | }, |
| @@ -616,10 +620,6 @@ | |||
| 616 | "type": "instruct" | 620 | "type": "instruct" |
| 617 | }, | 621 | }, |
| 618 | { | 622 | { |
| 619 | "filename": "presets/instruct/Pygmalion.json", | ||
| 620 | "type": "instruct" | ||
| 621 | }, | ||
| 622 | { | ||
| 623 | "filename": "presets/instruct/Story.json", | 623 | "filename": "presets/instruct/Story.json", |
| 624 | "type": "instruct" | 624 | "type": "instruct" |
| 625 | }, | 625 | }, |
| @@ -664,6 +664,10 @@ | |||
| 664 | "type": "instruct" | 664 | "type": "instruct" |
| 665 | }, | 665 | }, |
| 666 | { | 666 | { |
| 667 | "filename": "presets/instruct/Llama 4 Instruct.json", | ||
| 668 | "type": "instruct" | ||
| 669 | }, | ||
| 670 | { | ||
| 667 | "filename": "presets/instruct/Phi.json", | 671 | "filename": "presets/instruct/Phi.json", |
| 668 | "type": "instruct" | 672 | "type": "instruct" |
| 669 | }, | 673 | }, |
| @@ -748,6 +752,10 @@ | |||
| 748 | "type": "sysprompt" | 752 | "type": "sysprompt" |
| 749 | }, | 753 | }, |
| 750 | { | 754 | { |
| 755 | "filename": "presets/sysprompt/Lightning 1.1.json", | ||
| 756 | "type": "sysprompt" | ||
| 757 | }, | ||
| 758 | { | ||
| 751 | "filename": "presets/instruct/Mistral V1.json", | 759 | "filename": "presets/instruct/Mistral V1.json", |
| 752 | "type": "instruct" | 760 | "type": "instruct" |
| 753 | }, | 761 | }, |
| @@ -788,6 +796,14 @@ | |||
| 788 | "type": "context" | 796 | "type": "context" |
| 789 | }, | 797 | }, |
| 790 | { | 798 | { |
| 799 | "filename": "presets/instruct/GLM-4.json", | ||
| 800 | "type": "instruct" | ||
| 801 | }, | ||
| 802 | { | ||
| 803 | "filename": "presets/context/GLM-4.json", | ||
| 804 | "type": "context" | ||
| 805 | }, | ||
| 806 | { | ||
| 791 | "filename": "presets/reasoning/DeepSeek.json", | 807 | "filename": "presets/reasoning/DeepSeek.json", |
| 792 | "type": "reasoning" | 808 | "type": "reasoning" |
| 793 | }, | 809 | }, |
| @@ -3,7 +3,6 @@ | |||
| 3 | "example_separator": "", | 3 | "example_separator": "", |
| 4 | "chat_start": "", | 4 | "chat_start": "", |
| 5 | "use_stop_strings": false, | 5 | "use_stop_strings": false, |
| 6 | "allow_jailbreak": false, | ||
| 7 | "always_force_name2": false, | 6 | "always_force_name2": false, |
| 8 | "trim_sentences": false, | 7 | "trim_sentences": false, |
| 9 | "single_line": true, | 8 | "single_line": true, |
| @@ -3,7 +3,6 @@ | |||
| 3 | "example_separator": "", | 3 | "example_separator": "", |
| 4 | "chat_start": "", | 4 | "chat_start": "", |
| 5 | "use_stop_strings": false, | 5 | "use_stop_strings": false, |
| 6 | "allow_jailbreak": false, | ||
| 7 | "always_force_name2": false, | 6 | "always_force_name2": false, |
| 8 | "trim_sentences": false, | 7 | "trim_sentences": false, |
| 9 | "single_line": false, | 8 | "single_line": false, |
| @@ -3,7 +3,6 @@ | |||
| 3 | "example_separator": "", | 3 | "example_separator": "", |
| 4 | "chat_start": "", | 4 | "chat_start": "", |
| 5 | "use_stop_strings": false, | 5 | "use_stop_strings": false, |
| 6 | "allow_jailbreak": false, | ||
| 7 | "always_force_name2": true, | 6 | "always_force_name2": true, |
| 8 | "trim_sentences": false, | 7 | "trim_sentences": false, |
| 9 | "single_line": false, | 8 | "single_line": false, |
| @@ -3,7 +3,6 @@ | |||
| 3 | "example_separator": "", | 3 | "example_separator": "", |
| 4 | "chat_start": "", | 4 | "chat_start": "", |
| 5 | "use_stop_strings": false, | 5 | "use_stop_strings": false, |
| 6 | "allow_jailbreak": false, | ||
| 7 | "always_force_name2": true, | 6 | "always_force_name2": true, |
| 8 | "trim_sentences": false, | 7 | "trim_sentences": false, |
| 9 | "single_line": false, | 8 | "single_line": false, |
| @@ -3,7 +3,6 @@ | |||
| 3 | "example_separator": "", | 3 | "example_separator": "", |
| 4 | "chat_start": "", | 4 | "chat_start": "", |
| 5 | "use_stop_strings": false, | 5 | "use_stop_strings": false, |
| 6 | "allow_jailbreak": false, | ||
| 7 | "always_force_name2": true, | 6 | "always_force_name2": true, |
| 8 | "trim_sentences": false, | 7 | "trim_sentences": false, |
| 9 | "single_line": false, | 8 | "single_line": false, |
| @@ -3,7 +3,6 @@ | |||
| 3 | "example_separator": "", | 3 | "example_separator": "", |
| 4 | "chat_start": "<|START_OF_TURN_TOKEN|><|SYSTEM_TOKEN|>New Roleplay:<|END_OF_TURN_TOKEN|>", | 4 | "chat_start": "<|START_OF_TURN_TOKEN|><|SYSTEM_TOKEN|>New Roleplay:<|END_OF_TURN_TOKEN|>", |
| 5 | "use_stop_strings": false, | 5 | "use_stop_strings": false, |
| 6 | "allow_jailbreak": false, | ||
| 7 | "always_force_name2": true, | 6 | "always_force_name2": true, |
| 8 | "trim_sentences": false, | 7 | "trim_sentences": false, |
| 9 | "single_line": false, | 8 | "single_line": false, |
| @@ -3,7 +3,6 @@ | |||
| 3 | "example_separator": "", | 3 | "example_separator": "", |
| 4 | "chat_start": "", | 4 | "chat_start": "", |
| 5 | "use_stop_strings": false, | 5 | "use_stop_strings": false, |
| 6 | "allow_jailbreak": false, | ||
| 7 | "always_force_name2": true, | 6 | "always_force_name2": true, |
| 8 | "trim_sentences": false, | 7 | "trim_sentences": false, |
| 9 | "single_line": false, | 8 | "single_line": false, |
| @@ -3,7 +3,6 @@ | |||
| 3 | "example_separator": "***", | 3 | "example_separator": "***", |
| 4 | "chat_start": "***", | 4 | "chat_start": "***", |
| 5 | "use_stop_strings": false, | 5 | "use_stop_strings": false, |
| 6 | "allow_jailbreak": false, | ||
| 7 | "always_force_name2": true, | 6 | "always_force_name2": true, |
| 8 | "trim_sentences": false, | 7 | "trim_sentences": false, |
| 9 | "single_line": false, | 8 | "single_line": false, |
| @@ -3,7 +3,6 @@ | |||
| 3 | "example_separator": "", | 3 | "example_separator": "", |
| 4 | "chat_start": "", | 4 | "chat_start": "", |
| 5 | "use_stop_strings": false, | 5 | "use_stop_strings": false, |
| 6 | "allow_jailbreak": false, | ||
| 7 | "always_force_name2": false, | 6 | "always_force_name2": false, |
| 8 | "trim_sentences": true, | 7 | "trim_sentences": true, |
| 9 | "single_line": false, | 8 | "single_line": false, |
| @@ -3,7 +3,6 @@ | |||
| 3 | "example_separator": "<|eot_id|>\n<|start_header_id|>user<|end_header_id|>\n\nWrite an example narrative / conversation that is not part of the main story.", | 3 | "example_separator": "<|eot_id|>\n<|start_header_id|>user<|end_header_id|>\n\nWrite an example narrative / conversation that is not part of the main story.", |
| 4 | "chat_start": "<|eot_id|>\n<|start_header_id|>user<|end_header_id|>\n\nStart the role-play between {{char}} and {{user}}.", | 4 | "chat_start": "<|eot_id|>\n<|start_header_id|>user<|end_header_id|>\n\nStart the role-play between {{char}} and {{user}}.", |
| 5 | "use_stop_strings": false, | 5 | "use_stop_strings": false, |
| 6 | "allow_jailbreak": false, | ||
| 7 | "always_force_name2": false, | 6 | "always_force_name2": false, |
| 8 | "trim_sentences": true, | 7 | "trim_sentences": true, |
| 9 | "single_line": false, | 8 | "single_line": false, |
| @@ -0,0 +1,10 @@ | |||
| 1 | { | ||
| 2 | "story_string": "[gMASK]<sop>{{#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}}\n", | ||
| 3 | "example_separator": "", | ||
| 4 | "chat_start": "", | ||
| 5 | "use_stop_strings": false, | ||
| 6 | "always_force_name2": true, | ||
| 7 | "trim_sentences": false, | ||
| 8 | "single_line": false, | ||
| 9 | "name": "GLM-4" | ||
| 10 | } | ||
| @@ -3,7 +3,6 @@ | |||
| 3 | "example_separator": "", | 3 | "example_separator": "", |
| 4 | "chat_start": "", | 4 | "chat_start": "", |
| 5 | "use_stop_strings": false, | 5 | "use_stop_strings": false, |
| 6 | "allow_jailbreak": false, | ||
| 7 | "always_force_name2": true, | 6 | "always_force_name2": true, |
| 8 | "trim_sentences": false, | 7 | "trim_sentences": false, |
| 9 | "single_line": false, | 8 | "single_line": false, |
| @@ -3,7 +3,6 @@ | |||
| 3 | "example_separator": "### Example:", | 3 | "example_separator": "### Example:", |
| 4 | "chat_start": "### START ROLEPLAY:", | 4 | "chat_start": "### START ROLEPLAY:", |
| 5 | "use_stop_strings": false, | 5 | "use_stop_strings": false, |
| 6 | "allow_jailbreak": false, | ||
| 7 | "always_force_name2": true, | 6 | "always_force_name2": true, |
| 8 | "trim_sentences": false, | 7 | "trim_sentences": false, |
| 9 | "single_line": false, | 8 | "single_line": false, |
| @@ -1,9 +1,8 @@ | |||
| 1 | { | 1 | { |
| 2 | "story_string": "{{system}}\n{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{char}}'s 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}}{{user}}'s persona: {{persona}}\n{{/if}}", | 2 | "story_string": "Below is an instruction that describes a task. Write a response that appropriately completes the request.\n\n### Instruction:\n{{system}}\n{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{char}}'s 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}}{{user}}'s persona: {{persona}}\n{{/if}}\n\n", |
| 3 | "example_separator": "Example of an interaction:", | 3 | "example_separator": "Example of an interaction:\n", |
| 4 | "chat_start": "This is the history of the roleplay:", | 4 | "chat_start": "This is the history of the roleplay:\n", |
| 5 | "use_stop_strings": false, | 5 | "use_stop_strings": false, |
| 6 | "allow_jailbreak": false, | ||
| 7 | "always_force_name2": true, | 6 | "always_force_name2": true, |
| 8 | "trim_sentences": false, | 7 | "trim_sentences": false, |
| 9 | "single_line": false, | 8 | "single_line": false, |
| @@ -3,7 +3,6 @@ | |||
| 3 | "example_separator": "", | 3 | "example_separator": "", |
| 4 | "chat_start": "", | 4 | "chat_start": "", |
| 5 | "use_stop_strings": false, | 5 | "use_stop_strings": false, |
| 6 | "allow_jailbreak": false, | ||
| 7 | "always_force_name2": true, | 6 | "always_force_name2": true, |
| 8 | "trim_sentences": false, | 7 | "trim_sentences": false, |
| 9 | "single_line": false, | 8 | "single_line": false, |
| @@ -3,7 +3,6 @@ | |||
| 3 | "example_separator": "", | 3 | "example_separator": "", |
| 4 | "chat_start": "", | 4 | "chat_start": "", |
| 5 | "use_stop_strings": false, | 5 | "use_stop_strings": false, |
| 6 | "allow_jailbreak": false, | ||
| 7 | "always_force_name2": true, | 6 | "always_force_name2": true, |
| 8 | "trim_sentences": false, | 7 | "trim_sentences": false, |
| 9 | "single_line": false, | 8 | "single_line": false, |
| @@ -0,0 +1,10 @@ | |||
| 1 | { | ||
| 2 | "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|>", | ||
| 3 | "example_separator": "", | ||
| 4 | "chat_start": "", | ||
| 5 | "use_stop_strings": false, | ||
| 6 | "always_force_name2": true, | ||
| 7 | "trim_sentences": false, | ||
| 8 | "single_line": false, | ||
| 9 | "name": "Llama 4 Instruct" | ||
| 10 | } | ||
| @@ -3,7 +3,6 @@ | |||
| 3 | "example_separator": "", | 3 | "example_separator": "", |
| 4 | "chat_start": "", | 4 | "chat_start": "", |
| 5 | "use_stop_strings": false, | 5 | "use_stop_strings": false, |
| 6 | "allow_jailbreak": false, | ||
| 7 | "always_force_name2": true, | 6 | "always_force_name2": true, |
| 8 | "trim_sentences": false, | 7 | "trim_sentences": false, |
| 9 | "single_line": false, | 8 | "single_line": false, |
| @@ -3,9 +3,8 @@ | |||
| 3 | "example_separator": "", | 3 | "example_separator": "", |
| 4 | "chat_start": "", | 4 | "chat_start": "", |
| 5 | "use_stop_strings": false, | 5 | "use_stop_strings": false, |
| 6 | "allow_jailbreak": false, | ||
| 7 | "always_force_name2": true, | 6 | "always_force_name2": true, |
| 8 | "trim_sentences": false, | 7 | "trim_sentences": false, |
| 9 | "single_line": false, | 8 | "single_line": false, |
| 10 | "name": "Pygmalion" | 9 | "name": "Metharme" |
| 11 | } | 10 | } |
| @@ -3,7 +3,6 @@ | |||
| 3 | "example_separator": "", | 3 | "example_separator": "", |
| 4 | "chat_start": "", | 4 | "chat_start": "", |
| 5 | "use_stop_strings": false, | 5 | "use_stop_strings": false, |
| 6 | "allow_jailbreak": false, | ||
| 7 | "always_force_name2": true, | 6 | "always_force_name2": true, |
| 8 | "trim_sentences": false, | 7 | "trim_sentences": false, |
| 9 | "single_line": false, | 8 | "single_line": false, |
| @@ -3,7 +3,6 @@ | |||
| 3 | "example_separator": "", | 3 | "example_separator": "", |
| 4 | "chat_start": "", | 4 | "chat_start": "", |
| 5 | "use_stop_strings": false, | 5 | "use_stop_strings": false, |
| 6 | "allow_jailbreak": false, | ||
| 7 | "always_force_name2": true, | 6 | "always_force_name2": true, |
| 8 | "trim_sentences": false, | 7 | "trim_sentences": false, |
| 9 | "single_line": false, | 8 | "single_line": false, |
| @@ -3,7 +3,6 @@ | |||
| 3 | "example_separator": "", | 3 | "example_separator": "", |
| 4 | "chat_start": "", | 4 | "chat_start": "", |
| 5 | "use_stop_strings": false, | 5 | "use_stop_strings": false, |
| 6 | "allow_jailbreak": false, | ||
| 7 | "always_force_name2": true, | 6 | "always_force_name2": true, |
| 8 | "trim_sentences": false, | 7 | "trim_sentences": false, |
| 9 | "single_line": false, | 8 | "single_line": false, |
| @@ -3,7 +3,6 @@ | |||
| 3 | "example_separator": "", | 3 | "example_separator": "", |
| 4 | "chat_start": "", | 4 | "chat_start": "", |
| 5 | "use_stop_strings": false, | 5 | "use_stop_strings": false, |
| 6 | "allow_jailbreak": false, | ||
| 7 | "always_force_name2": true, | 6 | "always_force_name2": true, |
| 8 | "trim_sentences": false, | 7 | "trim_sentences": false, |
| 9 | "single_line": false, | 8 | "single_line": false, |
| @@ -3,7 +3,6 @@ | |||
| 3 | "example_separator": "", | 3 | "example_separator": "", |
| 4 | "chat_start": "", | 4 | "chat_start": "", |
| 5 | "use_stop_strings": false, | 5 | "use_stop_strings": false, |
| 6 | "allow_jailbreak": false, | ||
| 7 | "always_force_name2": true, | 6 | "always_force_name2": true, |
| 8 | "trim_sentences": false, | 7 | "trim_sentences": false, |
| 9 | "single_line": false, | 8 | "single_line": false, |
| @@ -3,7 +3,6 @@ | |||
| 3 | "example_separator": "***", | 3 | "example_separator": "***", |
| 4 | "chat_start": "***", | 4 | "chat_start": "***", |
| 5 | "use_stop_strings": false, | 5 | "use_stop_strings": false, |
| 6 | "allow_jailbreak": false, | ||
| 7 | "always_force_name2": true, | 6 | "always_force_name2": true, |
| 8 | "trim_sentences": false, | 7 | "trim_sentences": false, |
| 9 | "single_line": false, | 8 | "single_line": false, |
| @@ -3,7 +3,6 @@ | |||
| 3 | "example_separator": "This is how {{char}} should talk", | 3 | "example_separator": "This is how {{char}} should talk", |
| 4 | "chat_start": "\nThen the roleplay chat between {{user}} and {{char}} begins.\n", | 4 | "chat_start": "\nThen the roleplay chat between {{user}} and {{char}} begins.\n", |
| 5 | "use_stop_strings": false, | 5 | "use_stop_strings": false, |
| 6 | "allow_jailbreak": false, | ||
| 7 | "always_force_name2": true, | 6 | "always_force_name2": true, |
| 8 | "trim_sentences": false, | 7 | "trim_sentences": false, |
| 9 | "single_line": false, | 8 | "single_line": false, |
| @@ -3,7 +3,6 @@ | |||
| 3 | "example_separator": "", | 3 | "example_separator": "", |
| 4 | "chat_start": "", | 4 | "chat_start": "", |
| 5 | "use_stop_strings": false, | 5 | "use_stop_strings": false, |
| 6 | "allow_jailbreak": false, | ||
| 7 | "always_force_name2": true, | 6 | "always_force_name2": true, |
| 8 | "trim_sentences": false, | 7 | "trim_sentences": false, |
| 9 | "single_line": false, | 8 | "single_line": false, |
| @@ -3,7 +3,6 @@ | |||
| 3 | "example_separator": "", | 3 | "example_separator": "", |
| 4 | "chat_start": "", | 4 | "chat_start": "", |
| 5 | "use_stop_strings": false, | 5 | "use_stop_strings": false, |
| 6 | "allow_jailbreak": false, | ||
| 7 | "always_force_name2": true, | 6 | "always_force_name2": true, |
| 8 | "trim_sentences": false, | 7 | "trim_sentences": false, |
| 9 | "single_line": false, | 8 | "single_line": false, |
| @@ -3,7 +3,6 @@ | |||
| 3 | "example_separator": "", | 3 | "example_separator": "", |
| 4 | "chat_start": "", | 4 | "chat_start": "", |
| 5 | "use_stop_strings": false, | 5 | "use_stop_strings": false, |
| 6 | "allow_jailbreak": false, | ||
| 7 | "always_force_name2": true, | 6 | "always_force_name2": true, |
| 8 | "trim_sentences": false, | 7 | "trim_sentences": false, |
| 9 | "single_line": false, | 8 | "single_line": false, |
| @@ -3,7 +3,6 @@ | |||
| 3 | "example_separator": "", | 3 | "example_separator": "", |
| 4 | "chat_start": "", | 4 | "chat_start": "", |
| 5 | "use_stop_strings": false, | 5 | "use_stop_strings": false, |
| 6 | "allow_jailbreak": false, | ||
| 7 | "always_force_name2": true, | 6 | "always_force_name2": true, |
| 8 | "trim_sentences": false, | 7 | "trim_sentences": false, |
| 9 | "single_line": false, | 8 | "single_line": false, |
| @@ -3,7 +3,6 @@ | |||
| 3 | "example_separator": "### New Roleplay:", | 3 | "example_separator": "### New Roleplay:", |
| 4 | "chat_start": "### New Roleplay:", | 4 | "chat_start": "### New Roleplay:", |
| 5 | "use_stop_strings": false, | 5 | "use_stop_strings": false, |
| 6 | "allow_jailbreak": false, | ||
| 7 | "always_force_name2": true, | 6 | "always_force_name2": true, |
| 8 | "trim_sentences": false, | 7 | "trim_sentences": false, |
| 9 | "single_line": false, | 8 | "single_line": false, |
| @@ -6,7 +6,7 @@ | |||
| 6 | "stop_sequence": "<|im_end|>", | 6 | "stop_sequence": "<|im_end|>", |
| 7 | "wrap": true, | 7 | "wrap": true, |
| 8 | "macro": true, | 8 | "macro": true, |
| 9 | "names_behavior": "always", | 9 | "names_behavior": "force", |
| 10 | "activation_regex": "", | 10 | "activation_regex": "", |
| 11 | "system_sequence_prefix": "", | 11 | "system_sequence_prefix": "", |
| 12 | "system_sequence_suffix": "", | 12 | "system_sequence_suffix": "", |
| @@ -8,7 +8,7 @@ | |||
| 8 | "stop_sequence": "<|END_OF_TURN_TOKEN|>", | 8 | "stop_sequence": "<|END_OF_TURN_TOKEN|>", |
| 9 | "wrap": false, | 9 | "wrap": false, |
| 10 | "macro": true, | 10 | "macro": true, |
| 11 | "names_behavior": "always", | 11 | "names_behavior": "force", |
| 12 | "activation_regex": "", | 12 | "activation_regex": "", |
| 13 | "skip_examples": false, | 13 | "skip_examples": false, |
| 14 | "output_suffix": "<|END_OF_TURN_TOKEN|>", | 14 | "output_suffix": "<|END_OF_TURN_TOKEN|>", |
| @@ -1,22 +1,22 @@ | |||
| 1 | { | 1 | { |
| 2 | "input_sequence": "<|user|>", | 2 | "input_sequence": "<|user|>\n", |
| 3 | "output_sequence": "<|model|>", | 3 | "output_sequence": "<|assistant|>\n", |
| 4 | "first_output_sequence": "", | ||
| 4 | "last_output_sequence": "", | 5 | "last_output_sequence": "", |
| 5 | "system_sequence": "", | 6 | "system_sequence_prefix": "<|system|>\n", |
| 6 | "stop_sequence": "<|user|>", | 7 | "system_sequence_suffix": "", |
| 8 | "stop_sequence": "", | ||
| 7 | "wrap": false, | 9 | "wrap": false, |
| 8 | "macro": true, | 10 | "macro": true, |
| 9 | "names_behavior": "always", | 11 | "names_behavior": "force", |
| 10 | "activation_regex": "", | 12 | "activation_regex": "", |
| 11 | "system_sequence_prefix": "<|system|>", | ||
| 12 | "system_sequence_suffix": "", | ||
| 13 | "first_output_sequence": "", | ||
| 14 | "skip_examples": false, | 13 | "skip_examples": false, |
| 15 | "output_suffix": "", | 14 | "output_suffix": "", |
| 16 | "input_suffix": "", | 15 | "input_suffix": "", |
| 16 | "system_sequence": "", | ||
| 17 | "system_suffix": "", | 17 | "system_suffix": "", |
| 18 | "user_alignment_message": "", | 18 | "user_alignment_message": "", |
| 19 | "system_same_as_user": true, | ||
| 20 | "last_system_sequence": "", | 19 | "last_system_sequence": "", |
| 21 | "name": "Pygmalion" | 20 | "system_same_as_user": true, |
| 21 | "name": "GLM-4" | ||
| 22 | } | 22 | } |
| @@ -6,7 +6,7 @@ | |||
| 6 | "stop_sequence": "<end_of_turn>", | 6 | "stop_sequence": "<end_of_turn>", |
| 7 | "wrap": true, | 7 | "wrap": true, |
| 8 | "macro": true, | 8 | "macro": true, |
| 9 | "names_behavior": "none", | 9 | "names_behavior": "force", |
| 10 | "activation_regex": "", | 10 | "activation_regex": "", |
| 11 | "system_sequence_prefix": "", | 11 | "system_sequence_prefix": "", |
| 12 | "system_sequence_suffix": "", | 12 | "system_sequence_suffix": "", |
| @@ -1,7 +1,7 @@ | |||
| 1 | { | 1 | { |
| 2 | "input_sequence": "### Instruction:", | 2 | "input_sequence": "### Instruction:", |
| 3 | "output_sequence": "### Response: (length = unlimited)", | 3 | "output_sequence": "### Response:", |
| 4 | "last_output_sequence": "", | 4 | "last_output_sequence": "### Response: (length = unlimited)", |
| 5 | "system_sequence": "", | 5 | "system_sequence": "", |
| 6 | "stop_sequence": "", | 6 | "stop_sequence": "", |
| 7 | "wrap": true, | 7 | "wrap": true, |
| @@ -12,8 +12,8 @@ | |||
| 12 | "system_sequence_suffix": "", | 12 | "system_sequence_suffix": "", |
| 13 | "first_output_sequence": "", | 13 | "first_output_sequence": "", |
| 14 | "skip_examples": false, | 14 | "skip_examples": false, |
| 15 | "output_suffix": "", | 15 | "output_suffix": "\n\n", |
| 16 | "input_suffix": "", | 16 | "input_suffix": "\n\n", |
| 17 | "system_suffix": "", | 17 | "system_suffix": "", |
| 18 | "user_alignment_message": "", | 18 | "user_alignment_message": "", |
| 19 | "system_same_as_user": true, | 19 | "system_same_as_user": true, |
| @@ -6,7 +6,7 @@ | |||
| 6 | "stop_sequence": "<|eot_id|>", | 6 | "stop_sequence": "<|eot_id|>", |
| 7 | "wrap": false, | 7 | "wrap": false, |
| 8 | "macro": true, | 8 | "macro": true, |
| 9 | "names_behavior": "always", | 9 | "names_behavior": "force", |
| 10 | "activation_regex": "", | 10 | "activation_regex": "", |
| 11 | "system_sequence_prefix": "", | 11 | "system_sequence_prefix": "", |
| 12 | "system_sequence_suffix": "", | 12 | "system_sequence_suffix": "", |
| @@ -16,7 +16,7 @@ | |||
| 16 | "input_suffix": "<|eot_id|>", | 16 | "input_suffix": "<|eot_id|>", |
| 17 | "system_suffix": "<|eot_id|>", | 17 | "system_suffix": "<|eot_id|>", |
| 18 | "user_alignment_message": "", | 18 | "user_alignment_message": "", |
| 19 | "system_same_as_user": true, | 19 | "system_same_as_user": false, |
| 20 | "last_system_sequence": "", | 20 | "last_system_sequence": "", |
| 21 | "name": "Llama 3 Instruct" | 21 | "name": "Llama 3 Instruct" |
| 22 | } | 22 | } |
| @@ -0,0 +1,22 @@ | |||
| 1 | { | ||
| 2 | "input_sequence": "<|header_start|>user<|header_end|>\n\n", | ||
| 3 | "output_sequence": "<|header_start|>assistant<|header_end|>\n\n", | ||
| 4 | "last_output_sequence": "", | ||
| 5 | "system_sequence": "<|header_start|>system<|header_end|>\n\n", | ||
| 6 | "stop_sequence": "<|eot|>", | ||
| 7 | "wrap": false, | ||
| 8 | "macro": true, | ||
| 9 | "names_behavior": "force", | ||
| 10 | "activation_regex": "", | ||
| 11 | "system_sequence_prefix": "", | ||
| 12 | "system_sequence_suffix": "", | ||
| 13 | "first_output_sequence": "", | ||
| 14 | "skip_examples": false, | ||
| 15 | "output_suffix": "<|eot|>", | ||
| 16 | "input_suffix": "<|eot|>", | ||
| 17 | "system_suffix": "<|eot|>", | ||
| 18 | "user_alignment_message": "", | ||
| 19 | "system_same_as_user": false, | ||
| 20 | "last_system_sequence": "", | ||
| 21 | "name": "Llama 4 Instruct" | ||
| 22 | } | ||
| @@ -6,7 +6,7 @@ | |||
| 6 | "stop_sequence": "", | 6 | "stop_sequence": "", |
| 7 | "wrap": false, | 7 | "wrap": false, |
| 8 | "macro": true, | 8 | "macro": true, |
| 9 | "names_behavior": "always", | 9 | "names_behavior": "force", |
| 10 | "activation_regex": "", | 10 | "activation_regex": "", |
| 11 | "system_sequence_prefix": "", | 11 | "system_sequence_prefix": "", |
| 12 | "system_sequence_suffix": "", | 12 | "system_sequence_suffix": "", |
| @@ -6,7 +6,7 @@ | |||
| 6 | "stop_sequence": "", | 6 | "stop_sequence": "", |
| 7 | "wrap": false, | 7 | "wrap": false, |
| 8 | "macro": true, | 8 | "macro": true, |
| 9 | "names_behavior": "always", | 9 | "names_behavior": "force", |
| 10 | "activation_regex": "", | 10 | "activation_regex": "", |
| 11 | "system_sequence_prefix": "", | 11 | "system_sequence_prefix": "", |
| 12 | "system_sequence_suffix": "", | 12 | "system_sequence_suffix": "", |
| @@ -6,7 +6,7 @@ | |||
| 6 | "stop_sequence": "", | 6 | "stop_sequence": "", |
| 7 | "wrap": false, | 7 | "wrap": false, |
| 8 | "macro": true, | 8 | "macro": true, |
| 9 | "names_behavior": "always", | 9 | "names_behavior": "force", |
| 10 | "activation_regex": "", | 10 | "activation_regex": "", |
| 11 | "system_sequence_prefix": "", | 11 | "system_sequence_prefix": "", |
| 12 | "system_sequence_suffix": "", | 12 | "system_sequence_suffix": "", |
| @@ -6,7 +6,7 @@ | |||
| 6 | "stop_sequence": "</s>", | 6 | "stop_sequence": "</s>", |
| 7 | "wrap": false, | 7 | "wrap": false, |
| 8 | "macro": true, | 8 | "macro": true, |
| 9 | "names_behavior": "always", | 9 | "names_behavior": "force", |
| 10 | "activation_regex": "", | 10 | "activation_regex": "", |
| 11 | "system_sequence_prefix": "", | 11 | "system_sequence_prefix": "", |
| 12 | "system_sequence_suffix": "", | 12 | "system_sequence_suffix": "", |
| @@ -8,7 +8,7 @@ | |||
| 8 | "stop_sequence": "<|end|>", | 8 | "stop_sequence": "<|end|>", |
| 9 | "wrap": false, | 9 | "wrap": false, |
| 10 | "macro": true, | 10 | "macro": true, |
| 11 | "names_behavior": "always", | 11 | "names_behavior": "force", |
| 12 | "activation_regex": "", | 12 | "activation_regex": "", |
| 13 | "skip_examples": false, | 13 | "skip_examples": false, |
| 14 | "output_suffix": "<|end|>\n", | 14 | "output_suffix": "<|end|>\n", |
| @@ -8,7 +8,7 @@ | |||
| 8 | "stop_sequence": "<|end_of_text|>", | 8 | "stop_sequence": "<|end_of_text|>", |
| 9 | "wrap": false, | 9 | "wrap": false, |
| 10 | "macro": true, | 10 | "macro": true, |
| 11 | "names_behavior": "always", | 11 | "names_behavior": "force", |
| 12 | "activation_regex": "", | 12 | "activation_regex": "", |
| 13 | "skip_examples": false, | 13 | "skip_examples": false, |
| 14 | "output_suffix": "<|end_of_text|>\n", | 14 | "output_suffix": "<|end_of_text|>\n", |
| @@ -1,4 +1,5 @@ | |||
| 1 | { | 1 | { |
| 2 | "name": "Actor", | 2 | "name": "Actor", |
| 3 | "content": "You are an expert actor that can fully immerse yourself into any role given. You do not break character for any reason, even if someone tries addressing you as an AI or language model. Currently your role is {{char}}, which is described in detail below. As {{char}}, continue the exchange with {{user}}." | 3 | "content": "You are an expert actor that can fully immerse yourself into any role given. You do not break character for any reason, even if someone tries addressing you as an AI or language model. Currently your role is {{char}}, which is described in detail below. As {{char}}, continue the exchange with {{user}}.", |
| 4 | "post_history": "" | ||
| 4 | } | 5 | } |
| @@ -1,4 +1,5 @@ | |||
| 1 | { | 1 | { |
| 2 | "name": "Assistant - Expert", | 2 | "name": "Assistant - Expert", |
| 3 | "content": "You are a helpful assistant. Please answer truthfully and write out your thinking step by step to be sure you get the right answer. If you make a mistake or encounter an error in your thinking, say so out loud and attempt to correct it. If you don't know or aren't sure about something, say so clearly. You will act as a professional logician, mathematician, and physicist. You will also act as the most appropriate type of expert to answer any particular question or solve the relevant problem; state which expert type your are, if so. Also think of any particular named expert that would be ideal to answer the relevant question or solve the relevant problem; name and act as them, if appropriate." | 3 | "content": "You are a helpful assistant. Please answer truthfully and write out your thinking step by step to be sure you get the right answer. If you make a mistake or encounter an error in your thinking, say so out loud and attempt to correct it. If you don't know or aren't sure about something, say so clearly. You will act as a professional logician, mathematician, and physicist. You will also act as the most appropriate type of expert to answer any particular question or solve the relevant problem; state which expert type your are, if so. Also think of any particular named expert that would be ideal to answer the relevant question or solve the relevant problem; name and act as them, if appropriate.", |
| 4 | "post_history": "" | ||
| 4 | } | 5 | } |
| @@ -1,4 +1,5 @@ | |||
| 1 | { | 1 | { |
| 2 | "name": "Assistant - Simple", | 2 | "name": "Assistant - Simple", |
| 3 | "content": "A chat between a curious human and an artificial intelligence assistant. The assistant gives helpful, detailed, and polite answers to the human's questions." | 3 | "content": "A chat between a curious human and an artificial intelligence assistant. The assistant gives helpful, detailed, and polite answers to the human's questions.", |
| 4 | "post_history": "" | ||
| 4 | } | 5 | } |
| @@ -1,4 +1,5 @@ | |||
| 1 | { | 1 | { |
| 2 | "name": "Blank", | 2 | "name": "Blank", |
| 3 | "content": "" | 3 | "content": "", |
| 4 | "post_history": "" | ||
| 4 | } | 5 | } |
| @@ -1,4 +1,5 @@ | |||
| 1 | { | 1 | { |
| 2 | "name": "Chain of Thought", | 2 | "name": "Chain of Thought", |
| 3 | "content": "Elaborate on the topic using a Tree of Thoughts and backtrack when necessary to construct a clear, cohesive Chain of Thought reasoning. Always answer without hesitation." | 3 | "content": "Elaborate on the topic using a Tree of Thoughts and backtrack when necessary to construct a clear, cohesive Chain of Thought reasoning. Always answer without hesitation.", |
| 4 | "post_history": "" | ||
| 4 | } | 5 | } |
| @@ -0,0 +1,5 @@ | |||
| 1 | { | ||
| 2 | "name": "Lightning 1.1", | ||
| 3 | "content": "Take the role of {{char}} in a play that leaves a lasting impression on {{user}}. Write {{char}}'s next reply.\nNever skip or gloss over {{char}}’s actions. Progress the scene at a naturally slow pace.", | ||
| 4 | "post_history": "" | ||
| 5 | } | ||
| @@ -1,4 +1,5 @@ | |||
| 1 | { | 1 | { |
| 2 | "name": "Neutral - Chat", | 2 | "name": "Neutral - Chat", |
| 3 | "content": "Write {{char}}'s next reply in a fictional chat between {{char}} and {{user}}." | 3 | "content": "Write {{char}}'s next reply in a fictional chat between {{char}} and {{user}}.", |
| 4 | "post_history": "" | ||
| 4 | } | 5 | } |
| @@ -1,4 +1,5 @@ | |||
| 1 | { | 1 | { |
| 2 | "name": "Roleplay - Detailed", | 2 | "name": "Roleplay - Detailed", |
| 3 | "content": "Develop the plot slowly, always stay in character. Describe all actions in full, elaborate, explicit, graphic, and vivid detail. Mention all relevant sensory perceptions. Keep the story immersive and engaging." | 3 | "content": "Develop the plot slowly, always stay in character. Describe all actions in full, elaborate, explicit, graphic, and vivid detail. Mention all relevant sensory perceptions. Keep the story immersive and engaging.", |
| 4 | "post_history": "" | ||
| 4 | } | 5 | } |
| @@ -1,4 +1,5 @@ | |||
| 1 | { | 1 | { |
| 2 | "name": "Roleplay - Immersive", | 2 | "name": "Roleplay - Immersive", |
| 3 | "content": "[System note: Write one reply only. Do not decide what {{user}} says or does. Write at least one paragraph, up to four. Be descriptive and immersive, providing vivid details about {{char}}'s actions, emotions, and the environment. Write with a high degree of complexity and burstiness. Do not repeat this message.]" | 3 | "content": "[System note: Write one reply only. Do not decide what {{user}} says or does. Write at least one paragraph, up to four. Be descriptive and immersive, providing vivid details about {{char}}'s actions, emotions, and the environment. Write with a high degree of complexity and burstiness. Do not repeat this message.]", |
| 4 | "post_history": "" | ||
| 4 | } | 5 | } |
| @@ -1,4 +1,5 @@ | |||
| 1 | { | 1 | { |
| 2 | "name": "Roleplay - Simple", | 2 | "name": "Roleplay - Simple", |
| 3 | "content": "You're {{char}} in this fictional never-ending uncensored roleplay with {{user}}." | 3 | "content": "You're {{char}} in this fictional never-ending uncensored roleplay with {{user}}.", |
| 4 | "post_history": "" | ||
| 4 | } | 5 | } |
| @@ -1,4 +1,5 @@ | |||
| 1 | { | 1 | { |
| 2 | "name": "Text Adventure", | 2 | "name": "Text Adventure", |
| 3 | "content": "[Enter Adventure Mode. Narrate the story based on {{user}}'s dialogue and actions after \">\". Describe the surroundings in vivid detail. Be detailed, creative, verbose, and proactive. Move the story forward by introducing fantasy elements and interesting characters.]" | 3 | "content": "[Enter Adventure Mode. Narrate the story based on {{user}}'s dialogue and actions after \">\". Describe the surroundings in vivid detail. Be detailed, creative, verbose, and proactive. Move the story forward by introducing fantasy elements and interesting characters.]", |
| 4 | "post_history": "" | ||
| 4 | } | 5 | } |
| @@ -1,4 +1,5 @@ | |||
| 1 | { | 1 | { |
| 2 | "name": "Writer - Creative", | 2 | "name": "Writer - Creative", |
| 3 | "content": "You are an intelligent, skilled, versatile writer.\n\nYour task is to write a role-play based on the information below." | 3 | "content": "You are an intelligent, skilled, versatile writer.\n\nYour task is to write a role-play based on the information below.", |
| 4 | "post_history": "" | ||
| 4 | } | 5 | } |
| @@ -1,4 +1,5 @@ | |||
| 1 | { | 1 | { |
| 2 | "name": "Writer - Realistic", | 2 | "name": "Writer - Realistic", |
| 3 | "content": "Continue writing this story and portray characters realistically." | 3 | "content": "Continue writing this story and portray characters realistically.", |
| 4 | "post_history": "" | ||
| 4 | } | 5 | } |
| @@ -1,12 +1,12 @@ | |||
| 1 | { | 1 | { |
| 2 | "name": "sillytavern", | 2 | "name": "sillytavern", |
| 3 | "version": "1.12.13", | 3 | "version": "1.12.14", |
| 4 | "lockfileVersion": 3, | 4 | "lockfileVersion": 3, |
| 5 | "requires": true, | 5 | "requires": true, |
| 6 | "packages": { | 6 | "packages": { |
| 7 | "": { | 7 | "": { |
| 8 | "name": "sillytavern", | 8 | "name": "sillytavern", |
| 9 | "version": "1.12.13", | 9 | "version": "1.12.14", |
| 10 | "hasInstallScript": true, | 10 | "hasInstallScript": true, |
| 11 | "license": "AGPL-3.0", | 11 | "license": "AGPL-3.0", |
| 12 | "dependencies": { | 12 | "dependencies": { |
| @@ -109,7 +109,7 @@ | |||
| 109 | "type": "git", | 109 | "type": "git", |
| 110 | "url": "https://github.com/SillyTavern/SillyTavern.git" | 110 | "url": "https://github.com/SillyTavern/SillyTavern.git" |
| 111 | }, | 111 | }, |
| 112 | "version": "1.12.13", | 112 | "version": "1.12.14", |
| 113 | "scripts": { | 113 | "scripts": { |
| 114 | "start": "node server.js", | 114 | "start": "node server.js", |
| 115 | "debug": "node --inspect server.js", | 115 | "debug": "node --inspect server.js", |
| @@ -3,133 +3,17 @@ | |||
| 3 | */ | 3 | */ |
| 4 | import fs from 'node:fs'; | 4 | import fs from 'node:fs'; |
| 5 | import path from 'node:path'; | 5 | import path from 'node:path'; |
| 6 | import crypto from 'node:crypto'; | ||
| 7 | import process from 'node:process'; | 6 | import process from 'node:process'; |
| 8 | import yaml from 'yaml'; | 7 | import yaml from 'yaml'; |
| 9 | import _ from 'lodash'; | ||
| 10 | import chalk from 'chalk'; | 8 | import chalk from 'chalk'; |
| 11 | import { createRequire } from 'node:module'; | 9 | import { createRequire } from 'node:module'; |
| 10 | import { addMissingConfigValues } from './src/config-init.js'; | ||
| 12 | 11 | ||
| 13 | /** | 12 | /** |
| 14 | * Colorizes console output. | 13 | * Colorizes console output. |
| 15 | */ | 14 | */ |
| 16 | const color = chalk; | 15 | const color = chalk; |
| 17 | 16 | ||
| 18 | const keyMigrationMap = [ | ||
| 19 | { | ||
| 20 | oldKey: 'disableThumbnails', | ||
| 21 | newKey: 'thumbnails.enabled', | ||
| 22 | migrate: (value) => !value, | ||
| 23 | }, | ||
| 24 | { | ||
| 25 | oldKey: 'thumbnailsQuality', | ||
| 26 | newKey: 'thumbnails.quality', | ||
| 27 | migrate: (value) => value, | ||
| 28 | }, | ||
| 29 | { | ||
| 30 | oldKey: 'avatarThumbnailsPng', | ||
| 31 | newKey: 'thumbnails.format', | ||
| 32 | migrate: (value) => (value ? 'png' : 'jpg'), | ||
| 33 | }, | ||
| 34 | { | ||
| 35 | oldKey: 'disableChatBackup', | ||
| 36 | newKey: 'backups.chat.enabled', | ||
| 37 | migrate: (value) => !value, | ||
| 38 | }, | ||
| 39 | { | ||
| 40 | oldKey: 'numberOfBackups', | ||
| 41 | newKey: 'backups.common.numberOfBackups', | ||
| 42 | migrate: (value) => value, | ||
| 43 | }, | ||
| 44 | { | ||
| 45 | oldKey: 'maxTotalChatBackups', | ||
| 46 | newKey: 'backups.chat.maxTotalBackups', | ||
| 47 | migrate: (value) => value, | ||
| 48 | }, | ||
| 49 | { | ||
| 50 | oldKey: 'chatBackupThrottleInterval', | ||
| 51 | newKey: 'backups.chat.throttleInterval', | ||
| 52 | migrate: (value) => value, | ||
| 53 | }, | ||
| 54 | { | ||
| 55 | oldKey: 'enableExtensions', | ||
| 56 | newKey: 'extensions.enabled', | ||
| 57 | migrate: (value) => value, | ||
| 58 | }, | ||
| 59 | { | ||
| 60 | oldKey: 'enableExtensionsAutoUpdate', | ||
| 61 | newKey: 'extensions.autoUpdate', | ||
| 62 | migrate: (value) => value, | ||
| 63 | }, | ||
| 64 | { | ||
| 65 | oldKey: 'extras.disableAutoDownload', | ||
| 66 | newKey: 'extensions.models.autoDownload', | ||
| 67 | migrate: (value) => !value, | ||
| 68 | }, | ||
| 69 | { | ||
| 70 | oldKey: 'extras.classificationModel', | ||
| 71 | newKey: 'extensions.models.classification', | ||
| 72 | migrate: (value) => value, | ||
| 73 | }, | ||
| 74 | { | ||
| 75 | oldKey: 'extras.captioningModel', | ||
| 76 | newKey: 'extensions.models.captioning', | ||
| 77 | migrate: (value) => value, | ||
| 78 | }, | ||
| 79 | { | ||
| 80 | oldKey: 'extras.embeddingModel', | ||
| 81 | newKey: 'extensions.models.embedding', | ||
| 82 | migrate: (value) => value, | ||
| 83 | }, | ||
| 84 | { | ||
| 85 | oldKey: 'extras.speechToTextModel', | ||
| 86 | newKey: 'extensions.models.speechToText', | ||
| 87 | migrate: (value) => value, | ||
| 88 | }, | ||
| 89 | { | ||
| 90 | oldKey: 'extras.textToSpeechModel', | ||
| 91 | newKey: 'extensions.models.textToSpeech', | ||
| 92 | migrate: (value) => value, | ||
| 93 | }, | ||
| 94 | { | ||
| 95 | oldKey: 'minLogLevel', | ||
| 96 | newKey: 'logging.minLogLevel', | ||
| 97 | migrate: (value) => value, | ||
| 98 | }, | ||
| 99 | { | ||
| 100 | oldKey: 'cardsCacheCapacity', | ||
| 101 | newKey: 'performance.memoryCacheCapacity', | ||
| 102 | migrate: (value) => `${value}mb`, | ||
| 103 | }, | ||
| 104 | { | ||
| 105 | oldKey: 'cookieSecret', | ||
| 106 | newKey: 'cookieSecret', | ||
| 107 | migrate: () => void 0, | ||
| 108 | remove: true, | ||
| 109 | }, | ||
| 110 | ]; | ||
| 111 | |||
| 112 | /** | ||
| 113 | * Gets all keys from an object recursively. | ||
| 114 | * @param {object} obj Object to get all keys from | ||
| 115 | * @param {string} prefix Prefix to prepend to all keys | ||
| 116 | * @returns {string[]} Array of all keys in the object | ||
| 117 | */ | ||
| 118 | function getAllKeys(obj, prefix = '') { | ||
| 119 | if (typeof obj !== 'object' || Array.isArray(obj) || obj === null) { | ||
| 120 | return []; | ||
| 121 | } | ||
| 122 | |||
| 123 | return _.flatMap(Object.keys(obj), key => { | ||
| 124 | const newPrefix = prefix ? `${prefix}.${key}` : key; | ||
| 125 | if (typeof obj[key] === 'object' && !Array.isArray(obj[key])) { | ||
| 126 | return getAllKeys(obj[key], newPrefix); | ||
| 127 | } else { | ||
| 128 | return [newPrefix]; | ||
| 129 | } | ||
| 130 | }); | ||
| 131 | } | ||
| 132 | |||
| 133 | /** | 17 | /** |
| 134 | * Converts the old config.conf file to the new config.yaml format. | 18 | * Converts the old config.conf file to the new config.yaml format. |
| 135 | */ | 19 | */ |
| @@ -157,71 +41,6 @@ function convertConfig() { | |||
| 157 | } | 41 | } |
| 158 | 42 | ||
| 159 | /** | 43 | /** |
| 160 | * Compares the current config.yaml with the default config.yaml and adds any missing values. | ||
| 161 | */ | ||
| 162 | function addMissingConfigValues() { | ||
| 163 | try { | ||
| 164 | const defaultConfig = yaml.parse(fs.readFileSync(path.join(process.cwd(), './default/config.yaml'), 'utf8')); | ||
| 165 | let config = yaml.parse(fs.readFileSync(path.join(process.cwd(), './config.yaml'), 'utf8')); | ||
| 166 | |||
| 167 | // Migrate old keys to new keys | ||
| 168 | const migratedKeys = []; | ||
| 169 | for (const { oldKey, newKey, migrate, remove } of keyMigrationMap) { | ||
| 170 | if (_.has(config, oldKey)) { | ||
| 171 | if (remove) { | ||
| 172 | _.unset(config, oldKey); | ||
| 173 | migratedKeys.push({ | ||
| 174 | oldKey, | ||
| 175 | newValue: void 0, | ||
| 176 | }); | ||
| 177 | continue; | ||
| 178 | } | ||
| 179 | |||
| 180 | const oldValue = _.get(config, oldKey); | ||
| 181 | const newValue = migrate(oldValue); | ||
| 182 | _.set(config, newKey, newValue); | ||
| 183 | _.unset(config, oldKey); | ||
| 184 | |||
| 185 | migratedKeys.push({ | ||
| 186 | oldKey, | ||
| 187 | newKey, | ||
| 188 | oldValue, | ||
| 189 | newValue, | ||
| 190 | }); | ||
| 191 | } | ||
| 192 | } | ||
| 193 | |||
| 194 | // Get all keys from the original config | ||
| 195 | const originalKeys = getAllKeys(config); | ||
| 196 | |||
| 197 | // Use lodash's defaultsDeep function to recursively apply default properties | ||
| 198 | config = _.defaultsDeep(config, defaultConfig); | ||
| 199 | |||
| 200 | // Get all keys from the updated config | ||
| 201 | const updatedKeys = getAllKeys(config); | ||
| 202 | |||
| 203 | // Find the keys that were added | ||
| 204 | const addedKeys = _.difference(updatedKeys, originalKeys); | ||
| 205 | |||
| 206 | if (addedKeys.length === 0 && migratedKeys.length === 0) { | ||
| 207 | return; | ||
| 208 | } | ||
| 209 | |||
| 210 | if (addedKeys.length > 0) { | ||
| 211 | console.log('Adding missing config values to config.yaml:', addedKeys); | ||
| 212 | } | ||
| 213 | |||
| 214 | if (migratedKeys.length > 0) { | ||
| 215 | console.log('Migrating config values in config.yaml:', migratedKeys); | ||
| 216 | } | ||
| 217 | |||
| 218 | fs.writeFileSync('./config.yaml', yaml.stringify(config)); | ||
| 219 | } catch (error) { | ||
| 220 | console.error(color.red('FATAL: Could not add missing config values to config.yaml'), error); | ||
| 221 | } | ||
| 222 | } | ||
| 223 | |||
| 224 | /** | ||
| 225 | * Creates the default config files if they don't exist yet. | 44 | * Creates the default config files if they don't exist yet. |
| 226 | */ | 45 | */ |
| 227 | function createDefaultFiles() { | 46 | function createDefaultFiles() { |
| @@ -283,58 +102,13 @@ function createDefaultFiles() { | |||
| 283 | } | 102 | } |
| 284 | } | 103 | } |
| 285 | 104 | ||
| 286 | /** | ||
| 287 | * Returns the MD5 hash of the given data. | ||
| 288 | * @param {Buffer} data Input data | ||
| 289 | * @returns {string} MD5 hash of the input data | ||
| 290 | */ | ||
| 291 | function getMd5Hash(data) { | ||
| 292 | return crypto | ||
| 293 | .createHash('md5') | ||
| 294 | .update(new Uint8Array(data)) | ||
| 295 | .digest('hex'); | ||
| 296 | } | ||
| 297 | |||
| 298 | /** | ||
| 299 | * Copies the WASM binaries from the sillytavern-transformers package to the dist folder. | ||
| 300 | */ | ||
| 301 | function copyWasmFiles() { | ||
| 302 | if (!fs.existsSync('./dist')) { | ||
| 303 | fs.mkdirSync('./dist'); | ||
| 304 | } | ||
| 305 | |||
| 306 | const listDir = fs.readdirSync('./node_modules/sillytavern-transformers/dist'); | ||
| 307 | |||
| 308 | for (const file of listDir) { | ||
| 309 | if (file.endsWith('.wasm')) { | ||
| 310 | const sourcePath = `./node_modules/sillytavern-transformers/dist/${file}`; | ||
| 311 | const targetPath = `./dist/${file}`; | ||
| 312 | |||
| 313 | // Don't copy if the file already exists and is the same checksum | ||
| 314 | if (fs.existsSync(targetPath)) { | ||
| 315 | const sourceChecksum = getMd5Hash(fs.readFileSync(sourcePath)); | ||
| 316 | const targetChecksum = getMd5Hash(fs.readFileSync(targetPath)); | ||
| 317 | |||
| 318 | if (sourceChecksum === targetChecksum) { | ||
| 319 | continue; | ||
| 320 | } | ||
| 321 | } | ||
| 322 | |||
| 323 | fs.copyFileSync(sourcePath, targetPath); | ||
| 324 | console.log(`${file} successfully copied to ./dist/${file}`); | ||
| 325 | } | ||
| 326 | } | ||
| 327 | } | ||
| 328 | |||
| 329 | try { | 105 | try { |
| 330 | // 0. Convert config.conf to config.yaml | 106 | // 0. Convert config.conf to config.yaml |
| 331 | convertConfig(); | 107 | convertConfig(); |
| 332 | // 1. Create default config files | 108 | // 1. Create default config files |
| 333 | createDefaultFiles(); | 109 | createDefaultFiles(); |
| 334 | // 2. Copy transformers WASM binaries from node_modules | 110 | // 2. Add missing config values |
| 335 | copyWasmFiles(); | 111 | addMissingConfigValues(path.join(process.cwd(), './config.yaml')); |
| 336 | // 3. Add missing config values | ||
| 337 | addMissingConfigValues(); | ||
| 338 | } catch (error) { | 112 | } catch (error) { |
| 339 | console.error(error); | 113 | console.error(error); |
| 340 | } | 114 | } |
| @@ -87,7 +87,7 @@ | |||
| 87 | } | 87 | } |
| 88 | 88 | ||
| 89 | #rm_group_members:empty::before { | 89 | #rm_group_members:empty::before { |
| 90 | content: 'Group is empty'; | 90 | content: attr(group_empty_text); |
| 91 | 91 | ||
| 92 | font-weight: bolder; | 92 | font-weight: bolder; |
| 93 | width: 100%; | 93 | width: 100%; |
| @@ -115,7 +115,7 @@ | |||
| 115 | } | 115 | } |
| 116 | 116 | ||
| 117 | #rm_group_add_members:empty::before { | 117 | #rm_group_add_members:empty::before { |
| 118 | content: 'No characters available'; | 118 | content: attr(no_characters_text); |
| 119 | 119 | ||
| 120 | font-weight: bolder; | 120 | font-weight: bolder; |
| 121 | width: 100%; | 121 | width: 100%; |
| @@ -124,6 +124,10 @@ | |||
| 124 | cursor: initial; | 124 | cursor: initial; |
| 125 | } | 125 | } |
| 126 | 126 | ||
| 127 | .world_entry .inline-drawer-header-pointer { | ||
| 128 | cursor: pointer; | ||
| 129 | } | ||
| 130 | |||
| 127 | .world_entry .killSwitch { | 131 | .world_entry .killSwitch { |
| 128 | cursor: pointer; | 132 | cursor: pointer; |
| 129 | } | 133 | } |
| @@ -0,0 +1,46 @@ | |||
| 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||
| 2 | <!-- Generator: Adobe Illustrator 27.5.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> | ||
| 3 | |||
| 4 | <svg | ||
| 5 | version="1.1" | ||
| 6 | id="katman_1" | ||
| 7 | x="0px" | ||
| 8 | y="0px" | ||
| 9 | viewBox="0 0 438.67001 481.44999" | ||
| 10 | xml:space="preserve" | ||
| 11 | sodipodi:docname="XAI_Logo.svg" | ||
| 12 | width="438.67001" | ||
| 13 | height="481.45001" | ||
| 14 | inkscape:version="1.3 (0e150ed, 2023-07-21)" | ||
| 15 | xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | ||
| 16 | xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | ||
| 17 | xmlns="http://www.w3.org/2000/svg" | ||
| 18 | xmlns:svg="http://www.w3.org/2000/svg"><defs | ||
| 19 | id="defs4" /><sodipodi:namedview | ||
| 20 | id="namedview4" | ||
| 21 | pagecolor="#ffffff" | ||
| 22 | bordercolor="#000000" | ||
| 23 | borderopacity="0.25" | ||
| 24 | inkscape:showpageshadow="2" | ||
| 25 | inkscape:pageopacity="0.0" | ||
| 26 | inkscape:pagecheckerboard="0" | ||
| 27 | inkscape:deskcolor="#d1d1d1" | ||
| 28 | inkscape:zoom="0.39645207" | ||
| 29 | inkscape:cx="219.44645" | ||
| 30 | inkscape:cy="238.36425" | ||
| 31 | inkscape:window-width="1512" | ||
| 32 | inkscape:window-height="856" | ||
| 33 | inkscape:window-x="0" | ||
| 34 | inkscape:window-y="38" | ||
| 35 | inkscape:window-maximized="1" | ||
| 36 | inkscape:current-layer="katman_1" /> <g | ||
| 37 | id="g4" | ||
| 38 | transform="translate(-201.61,-56.91)"> <polygon | ||
| 39 | points="631.96,538.36 640.28,93.18 557.09,211.99 565.4,538.36 " | ||
| 40 | id="polygon1" /> <polygon | ||
| 41 | points="379.35,284.53 430.13,357.05 640.28,56.91 538.72,56.91 " | ||
| 42 | id="polygon2" /> <polygon | ||
| 43 | points="353.96,465.84 303.17,393.31 201.61,538.36 303.17,538.36 " | ||
| 44 | id="polygon3" /> <polygon | ||
| 45 | points="531.69,538.36 303.17,211.99 201.61,211.99 430.13,538.36 " | ||
| 46 | id="polygon4" /> </g> </svg> | ||
| @@ -646,7 +646,7 @@ | |||
| 646 | <input type="number" id="openai_max_tokens" name="openai_max_tokens" class="text_pole" min="1" max="65536"> | 646 | <input type="number" id="openai_max_tokens" name="openai_max_tokens" class="text_pole" min="1" max="65536"> |
| 647 | </div> | 647 | </div> |
| 648 | </div> | 648 | </div> |
| 649 | <div class="range-block" data-source="openai,custom"> | 649 | <div class="range-block" data-source="openai,custom,xai"> |
| 650 | <div class="range-block-title" data-i18n="Multiple swipes per generation"> | 650 | <div class="range-block-title" data-i18n="Multiple swipes per generation"> |
| 651 | Multiple swipes per generation | 651 | Multiple swipes per generation |
| 652 | </div> | 652 | </div> |
| @@ -685,7 +685,7 @@ | |||
| 685 | </span> | 685 | </span> |
| 686 | </div> | 686 | </div> |
| 687 | </div> | 687 | </div> |
| 688 | <div class="range-block" data-source="openai,claude,windowai,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt,deepseek"> | 688 | <div class="range-block" data-source="openai,claude,windowai,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt,deepseek,xai"> |
| 689 | <div class="range-block-title" data-i18n="Temperature"> | 689 | <div class="range-block-title" data-i18n="Temperature"> |
| 690 | Temperature | 690 | Temperature |
| 691 | </div> | 691 | </div> |
| @@ -698,7 +698,7 @@ | |||
| 698 | </div> | 698 | </div> |
| 699 | </div> | 699 | </div> |
| 700 | </div> | 700 | </div> |
| 701 | <div class="range-block" data-source="openai,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt,deepseek"> | 701 | <div class="range-block" data-source="openai,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt,deepseek,xai"> |
| 702 | <div class="range-block-title" data-i18n="Frequency Penalty"> | 702 | <div class="range-block-title" data-i18n="Frequency Penalty"> |
| 703 | Frequency Penalty | 703 | Frequency Penalty |
| 704 | </div> | 704 | </div> |
| @@ -711,7 +711,7 @@ | |||
| 711 | </div> | 711 | </div> |
| 712 | </div> | 712 | </div> |
| 713 | </div> | 713 | </div> |
| 714 | <div class="range-block" data-source="openai,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt,deepseek"> | 714 | <div class="range-block" data-source="openai,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt,deepseek,xai"> |
| 715 | <div class="range-block-title" data-i18n="Presence Penalty"> | 715 | <div class="range-block-title" data-i18n="Presence Penalty"> |
| 716 | Presence Penalty | 716 | Presence Penalty |
| 717 | </div> | 717 | </div> |
| @@ -737,7 +737,7 @@ | |||
| 737 | </div> | 737 | </div> |
| 738 | </div> | 738 | </div> |
| 739 | </div> | 739 | </div> |
| 740 | <div class="range-block" data-source="openai,claude,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt,deepseek"> | 740 | <div class="range-block" data-source="openai,claude,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt,deepseek,xai"> |
| 741 | <div class="range-block-title" data-i18n="Top P"> | 741 | <div class="range-block-title" data-i18n="Top P"> |
| 742 | Top P | 742 | Top P |
| 743 | </div> | 743 | </div> |
| @@ -974,7 +974,7 @@ | |||
| 974 | </div> | 974 | </div> |
| 975 | </div> | 975 | </div> |
| 976 | </div> | 976 | </div> |
| 977 | <div class="range-block" data-source="openai,openrouter,mistralai,custom,cohere,groq,nanogpt"> | 977 | <div class="range-block" data-source="openai,openrouter,mistralai,custom,cohere,groq,nanogpt,xai"> |
| 978 | <div class="range-block-title justifyLeft" data-i18n="Seed"> | 978 | <div class="range-block-title justifyLeft" data-i18n="Seed"> |
| 979 | Seed | 979 | Seed |
| 980 | </div> | 980 | </div> |
| @@ -1419,7 +1419,7 @@ | |||
| 1419 | </div> | 1419 | </div> |
| 1420 | </div> | 1420 | </div> |
| 1421 | 1421 | ||
| 1422 | <div data-tg-type="aphrodite, ooba, koboldcpp, tabby, llamacpp" id="dryBlock" class="wide100p"> | 1422 | <div data-tg-type="aphrodite, ooba, koboldcpp, tabby, llamacpp, dreamgen" id="dryBlock" class="wide100p"> |
| 1423 | <h4 class="wide100p textAlignCenter" title="DRY penalizes tokens that would extend the end of the input into a sequence that has previously occurred in the input. Set multiplier to 0 to disable." data-i18n="[title]DRY_Repetition_Penalty_desc"> | 1423 | <h4 class="wide100p textAlignCenter" title="DRY penalizes tokens that would extend the end of the input into a sequence that has previously occurred in the input. Set multiplier to 0 to disable." data-i18n="[title]DRY_Repetition_Penalty_desc"> |
| 1424 | <label data-i18n="DRY Repetition Penalty">DRY Repetition Penalty</label> | 1424 | <label data-i18n="DRY Repetition Penalty">DRY Repetition Penalty</label> |
| 1425 | <a href="https://github.com/oobabooga/text-generation-webui/pull/5677" target="_blank"> | 1425 | <a href="https://github.com/oobabooga/text-generation-webui/pull/5677" target="_blank"> |
| @@ -1574,7 +1574,7 @@ | |||
| 1574 | <div class="fa-solid fa-circle-info opacity50p " data-i18n="[title]Add the bos_token to the beginning of prompts. Disabling this can make the replies more creative" title="Add the bos_token to the beginning of prompts. Disabling this can make the replies more creative."></div> | 1574 | <div class="fa-solid fa-circle-info opacity50p " data-i18n="[title]Add the bos_token to the beginning of prompts. Disabling this can make the replies more creative" title="Add the bos_token to the beginning of prompts. Disabling this can make the replies more creative."></div> |
| 1575 | </label> | 1575 | </label> |
| 1576 | </label> | 1576 | </label> |
| 1577 | <label data-tg-type="ooba, llamacpp, tabby, koboldcpp" class="checkbox_label flexGrow flexShrink" for="ban_eos_token_textgenerationwebui"> | 1577 | <label data-tg-type="ooba, llamacpp, tabby, koboldcpp, dreamgen" class="checkbox_label flexGrow flexShrink" for="ban_eos_token_textgenerationwebui"> |
| 1578 | <input type="checkbox" id="ban_eos_token_textgenerationwebui" /> | 1578 | <input type="checkbox" id="ban_eos_token_textgenerationwebui" /> |
| 1579 | <label> | 1579 | <label> |
| 1580 | <small data-i18n="Ban EOS Token">Ban EOS Token</small> | 1580 | <small data-i18n="Ban EOS Token">Ban EOS Token</small> |
| @@ -1963,9 +1963,12 @@ | |||
| 1963 | <span data-i18n="Use search capabilities provided by the backend."> | 1963 | <span data-i18n="Use search capabilities provided by the backend."> |
| 1964 | Use search capabilities provided by the backend. | 1964 | Use search capabilities provided by the backend. |
| 1965 | </span> | 1965 | </span> |
| 1966 | <b data-source="openrouter" data-i18n="openrouter_web_search_fee"> | ||
| 1967 | Not free, adds a $0.02 fee to each prompt. | ||
| 1968 | </b> | ||
| 1966 | </div> | 1969 | </div> |
| 1967 | </div> | 1970 | </div> |
| 1968 | <div class="range-block" data-source="openai,cohere,mistralai,custom,claude,openrouter,groq,deepseek,makersuite,ai21"> | 1971 | <div class="range-block" data-source="openai,cohere,mistralai,custom,claude,openrouter,groq,deepseek,makersuite,ai21,xai"> |
| 1969 | <label for="openai_function_calling" class="checkbox_label flexWrap widthFreeExpand"> | 1972 | <label for="openai_function_calling" class="checkbox_label flexWrap widthFreeExpand"> |
| 1970 | <input id="openai_function_calling" type="checkbox" /> | 1973 | <input id="openai_function_calling" type="checkbox" /> |
| 1971 | <span data-i18n="Enable function calling">Enable function calling</span> | 1974 | <span data-i18n="Enable function calling">Enable function calling</span> |
| @@ -1975,7 +1978,7 @@ | |||
| 1975 | <span data-i18n="enable_functions_desc_3">Can be utilized by various extensions to provide additional functionality.</span> | 1978 | <span data-i18n="enable_functions_desc_3">Can be utilized by various extensions to provide additional functionality.</span> |
| 1976 | </div> | 1979 | </div> |
| 1977 | </div> | 1980 | </div> |
| 1978 | <div class="range-block" data-source="openai,openrouter,makersuite,claude,custom,01ai"> | 1981 | <div class="range-block" data-source="openai,openrouter,mistralai,makersuite,claude,custom,01ai,xai"> |
| 1979 | <label for="openai_image_inlining" class="checkbox_label flexWrap widthFreeExpand"> | 1982 | <label for="openai_image_inlining" class="checkbox_label flexWrap widthFreeExpand"> |
| 1980 | <input id="openai_image_inlining" type="checkbox" /> | 1983 | <input id="openai_image_inlining" type="checkbox" /> |
| 1981 | <span data-i18n="Send inline images">Send inline images</span> | 1984 | <span data-i18n="Send inline images">Send inline images</span> |
| @@ -1987,7 +1990,7 @@ | |||
| 1987 | <code><i class="fa-solid fa-wand-magic-sparkles"></i></code> | 1990 | <code><i class="fa-solid fa-wand-magic-sparkles"></i></code> |
| 1988 | <span data-i18n="image_inlining_hint_3">menu to attach an image file to the chat.</span> | 1991 | <span data-i18n="image_inlining_hint_3">menu to attach an image file to the chat.</span> |
| 1989 | </div> | 1992 | </div> |
| 1990 | <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom"> | 1993 | <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,xai"> |
| 1991 | <div class="flex-container oneline-dropdown"> | 1994 | <div class="flex-container oneline-dropdown"> |
| 1992 | <label for="openai_inline_image_quality" data-i18n="Inline Image Quality"> | 1995 | <label for="openai_inline_image_quality" data-i18n="Inline Image Quality"> |
| 1993 | Inline Image Quality | 1996 | Inline Image Quality |
| @@ -2022,7 +2025,7 @@ | |||
| 2022 | <input id="use_makersuite_sysprompt" type="checkbox" /> | 2025 | <input id="use_makersuite_sysprompt" type="checkbox" /> |
| 2023 | <span> | 2026 | <span> |
| 2024 | <span data-i18n="Use system prompt">Use system prompt</span> | 2027 | <span data-i18n="Use system prompt">Use system prompt</span> |
| 2025 | <i class="opacity50p fa-solid fa-circle-info" title="Gemini 1.5/2.0 Pro/Flash"></i> | 2028 | <i class="opacity50p fa-solid fa-circle-info" title="Gemini 1.5+, LearnLM"></i> |
| 2026 | </span> | 2029 | </span> |
| 2027 | </label> | 2030 | </label> |
| 2028 | <div class="toggle-description justifyLeft marginBot5"> | 2031 | <div class="toggle-description justifyLeft marginBot5"> |
| @@ -2031,7 +2034,7 @@ | |||
| 2031 | </span> | 2034 | </span> |
| 2032 | </div> | 2035 | </div> |
| 2033 | </div> | 2036 | </div> |
| 2034 | <div class="range-block" data-source="deepseek,openrouter,custom,claude"> | 2037 | <div class="range-block" data-source="deepseek,openrouter,custom,claude,xai"> |
| 2035 | <label for="openai_show_thoughts" class="checkbox_label widthFreeExpand"> | 2038 | <label for="openai_show_thoughts" class="checkbox_label widthFreeExpand"> |
| 2036 | <input id="openai_show_thoughts" type="checkbox" /> | 2039 | <input id="openai_show_thoughts" type="checkbox" /> |
| 2037 | <span> | 2040 | <span> |
| @@ -2045,16 +2048,20 @@ | |||
| 2045 | </span> | 2048 | </span> |
| 2046 | </div> | 2049 | </div> |
| 2047 | </div> | 2050 | </div> |
| 2048 | <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,claude"> | 2051 | <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,claude,xai,makersuite,openrouter"> |
| 2049 | <div class="flex-container oneline-dropdown" title="Constrains effort on reasoning for reasoning models. Currently supported values are low, medium, and high. 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."> | 2052 | <div class="flex-container oneline-dropdown" title="Constrains effort on reasoning for reasoning models. 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."> |
| 2050 | <label for="openai_reasoning_effort"> | 2053 | <label for="openai_reasoning_effort"> |
| 2051 | <span data-i18n="Reasoning Effort">Reasoning Effort</span> | 2054 | <span data-i18n="Reasoning Effort">Reasoning Effort</span> |
| 2052 | <i data-source="claude" class="opacity50p fa-solid fa-circle-info" title="Allocates a portion of the response length for thinking (low: 10%, medium: 25%, high: 50%), but minimum 1024 tokens."></i> | 2055 | <i data-source="openai,custom,xai,openrouter" class="opacity50p fa-solid fa-circle-info" title="OpenAI-style options: low, medium, high. Minimum and maximum are aliased to low and high. Auto does not send an effort level." data-i18n="[title]OpenAI-style options: low, medium, high. Minimum and maximum are aliased to low and high. Auto does not send an effort level."></i> |
| 2056 | <i data-source="claude,makersuite" class="opacity50p fa-solid fa-circle-info" title="Allocates a portion of the response length for thinking (low: 10%, medium: 25%, high: 50%). Other options are model-dependent." data-i18n="[title]Allocates a portion of the response length for thinking (low: 10%, medium: 25%, high: 50%). Other options are model-dependent."></i> | ||
| 2053 | </label> | 2057 | </label> |
| 2054 | <select id="openai_reasoning_effort"> | 2058 | <select id="openai_reasoning_effort"> |
| 2059 | <option data-i18n="openai_reasoning_effort_auto" value="auto">Auto</option> | ||
| 2060 | <option data-i18n="openai_reasoning_effort_minimum" value="min">Mininum</option> | ||
| 2055 | <option data-i18n="openai_reasoning_effort_low" value="low">Low</option> | 2061 | <option data-i18n="openai_reasoning_effort_low" value="low">Low</option> |
| 2056 | <option data-i18n="openai_reasoning_effort_medium" value="medium">Medium</option> | 2062 | <option data-i18n="openai_reasoning_effort_medium" value="medium">Medium</option> |
| 2057 | <option data-i18n="openai_reasoning_effort_high" value="high">High</option> | 2063 | <option data-i18n="openai_reasoning_effort_high" value="high">High</option> |
| 2064 | <option data-i18n="openai_reasoning_effort_maximum" value="max">Maximum</option> | ||
| 2058 | </select> | 2065 | </select> |
| 2059 | </div> | 2066 | </div> |
| 2060 | </div> | 2067 | </div> |
| @@ -2491,8 +2498,8 @@ | |||
| 2491 | <option value="search" data-i18n="Search" hidden>A-Z</option> | 2498 | <option value="search" data-i18n="Search" hidden>A-Z</option> |
| 2492 | <option value="asc">A-Z</option> | 2499 | <option value="asc">A-Z</option> |
| 2493 | <option value="desc">Z-A</option> | 2500 | <option value="desc">Z-A</option> |
| 2494 | <option value="date_asc">Date Asc</option> | 2501 | <option data-i18n="Date Asc" value="date_asc">Date Asc</option> |
| 2495 | <option value="date_desc">Date Desc</option> | 2502 | <option data-i18n="Date Desc" value="date_desc">Date Desc</option> |
| 2496 | </select> | 2503 | </select> |
| 2497 | <select id="featherless_category_selection" class="text_pole"> | 2504 | <select id="featherless_category_selection" class="text_pole"> |
| 2498 | <option value="" disabled selected data-i18n="category">category</option> | 2505 | <option value="" disabled selected data-i18n="category">category</option> |
| @@ -2502,7 +2509,7 @@ | |||
| 2502 | <option value="All" data-i18n="All">All</option> | 2509 | <option value="All" data-i18n="All">All</option> |
| 2503 | </select> | 2510 | </select> |
| 2504 | <select id="featherless_class_selection" class="text_pole"> | 2511 | <select id="featherless_class_selection" class="text_pole"> |
| 2505 | <option value="" selected data-i18n="class">All Classes</option> | 2512 | <option value="" selected data-i18n="All Classes">All Classes</option> |
| 2506 | </select> | 2513 | </select> |
| 2507 | <div id="featherless_model_pagination_container" class="flex1"></div> | 2514 | <div id="featherless_model_pagination_container" class="flex1"></div> |
| 2508 | <i id="featherless_model_grid_toggle" class="fa-solid fa-table-cells-large menu_button" data-i18n="[title]Toggle grid view" title="Toggle grid view"></i> | 2515 | <i id="featherless_model_grid_toggle" class="fa-solid fa-table-cells-large menu_button" data-i18n="[title]Toggle grid view" title="Toggle grid view"></i> |
| @@ -2615,8 +2622,8 @@ | |||
| 2615 | </div> | 2622 | </div> |
| 2616 | <div data-tg-type="ollama"> | 2623 | <div data-tg-type="ollama"> |
| 2617 | <div class="flex-container flexFlowColumn"> | 2624 | <div class="flex-container flexFlowColumn"> |
| 2618 | <a href="https://github.com/jmorganca/ollama" target="_blank"> | 2625 | <a href="https://github.com/ollama/ollama" target="_blank"> |
| 2619 | jmorganca/ollama | 2626 | ollama/ollama |
| 2620 | </a> | 2627 | </a> |
| 2621 | </div> | 2628 | </div> |
| 2622 | <div class="flex1"> | 2629 | <div class="flex1"> |
| @@ -2756,9 +2763,10 @@ | |||
| 2756 | <option value="perplexity">Perplexity</option> | 2763 | <option value="perplexity">Perplexity</option> |
| 2757 | <option value="scale">Scale</option> | 2764 | <option value="scale">Scale</option> |
| 2758 | <option value="windowai">Window AI</option> | 2765 | <option value="windowai">Window AI</option> |
| 2766 | <option value="xai">xAI (Grok)</option> | ||
| 2759 | </optgroup> | 2767 | </optgroup> |
| 2760 | </select> | 2768 | </select> |
| 2761 | <div class="inline-drawer wide100p" data-source="openai,claude,mistralai,makersuite,deepseek"> | 2769 | <div class="inline-drawer wide100p" data-source="openai,claude,mistralai,makersuite,deepseek,xai"> |
| 2762 | <div class="inline-drawer-toggle inline-drawer-header"> | 2770 | <div class="inline-drawer-toggle inline-drawer-header"> |
| 2763 | <b data-i18n="Reverse Proxy">Reverse Proxy</b> | 2771 | <b data-i18n="Reverse Proxy">Reverse Proxy</b> |
| 2764 | <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div> | 2772 | <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div> |
| @@ -2821,7 +2829,7 @@ | |||
| 2821 | </div> | 2829 | </div> |
| 2822 | </div> | 2830 | </div> |
| 2823 | </div> | 2831 | </div> |
| 2824 | <div id="ReverseProxyWarningMessage" data-source="openai,claude,mistralai,makersuite,deepseek"> | 2832 | <div id="ReverseProxyWarningMessage" data-source="openai,claude,mistralai,makersuite,deepseek,xai"> |
| 2825 | <div class="reverse_proxy_warning"> | 2833 | <div class="reverse_proxy_warning"> |
| 2826 | <b> | 2834 | <b> |
| 2827 | <div data-i18n="Using a proxy that you're not running yourself is a risk to your data privacy."> | 2835 | <div data-i18n="Using a proxy that you're not running yourself is a risk to your data privacy."> |
| @@ -2880,12 +2888,17 @@ | |||
| 2880 | </optgroup> | 2888 | </optgroup> |
| 2881 | <optgroup label="GPT-4o mini"> | 2889 | <optgroup label="GPT-4o mini"> |
| 2882 | <option value="gpt-4o-mini">gpt-4o-mini</option> | 2890 | <option value="gpt-4o-mini">gpt-4o-mini</option> |
| 2883 | <option value="gpt-4o-2024-11-20">gpt-4o-2024-11-20</option> | 2891 | <option value="gpt-4o-mini-2024-07-18">gpt-4o-mini-2024-07-18</option> |
| 2884 | <option value="gpt-4o-2024-08-06">gpt-4o-2024-08-06</option> | 2892 | </optgroup> |
| 2885 | <option value="gpt-4o-2024-05-13">gpt-4o-2024-05-13</option> | 2893 | <optgroup label="GPT-4.1"> |
| 2886 | <option value="chatgpt-4o-latest">chatgpt-4o-latest</option> | 2894 | <option value="gpt-4.1">gpt-4.1</option> |
| 2895 | <option value="gpt-4.1-2025-04-14">gpt-4.1-2025-04-14</option> | ||
| 2896 | <option value="gpt-4.1-mini">gpt-4.1-mini</option> | ||
| 2897 | <option value="gpt-4.1-mini-2025-04-14">gpt-4.1-mini-2025-04-14</option> | ||
| 2898 | <option value="gpt-4.1-nano">gpt-4.1-nano</option> | ||
| 2899 | <option value="gpt-4.1-nano-2025-04-14">gpt-4.1-nano-2025-04-14</option> | ||
| 2887 | </optgroup> | 2900 | </optgroup> |
| 2888 | <optgroup label="o1 and o1-mini"> | 2901 | <optgroup label="o1"> |
| 2889 | <option value="o1">o1</option> | 2902 | <option value="o1">o1</option> |
| 2890 | <option value="o1-2024-12-17">o1-2024-12-17</option> | 2903 | <option value="o1-2024-12-17">o1-2024-12-17</option> |
| 2891 | <option value="o1-mini">o1-mini</option> | 2904 | <option value="o1-mini">o1-mini</option> |
| @@ -2894,9 +2907,15 @@ | |||
| 2894 | <option value="o1-preview-2024-09-12">o1-preview-2024-09-12</option> | 2907 | <option value="o1-preview-2024-09-12">o1-preview-2024-09-12</option> |
| 2895 | </optgroup> | 2908 | </optgroup> |
| 2896 | <optgroup label="o3"> | 2909 | <optgroup label="o3"> |
| 2910 | <option value="o3">o3</option> | ||
| 2911 | <option value="o3-2025-04-16">o3-2025-04-16</option> | ||
| 2897 | <option value="o3-mini">o3-mini</option> | 2912 | <option value="o3-mini">o3-mini</option> |
| 2898 | <option value="o3-mini-2025-01-31">o3-mini-2025-01-31</option> | 2913 | <option value="o3-mini-2025-01-31">o3-mini-2025-01-31</option> |
| 2899 | </optgroup> | 2914 | </optgroup> |
| 2915 | <optgroup label="o4"> | ||
| 2916 | <option value="o4-mini">o4-mini</option> | ||
| 2917 | <option value="o4-mini-2025-04-16">o4-mini-2025-04-16</option> | ||
| 2918 | </optgroup> | ||
| 2900 | <optgroup label="GPT-4.5"> | 2919 | <optgroup label="GPT-4.5"> |
| 2901 | <option value="gpt-4.5-preview">gpt-4.5-preview</option> | 2920 | <option value="gpt-4.5-preview">gpt-4.5-preview</option> |
| 2902 | <option value="gpt-4.5-preview-2025-02-27">gpt-4.5-preview-2025-02-27</option> | 2921 | <option value="gpt-4.5-preview-2025-02-27">gpt-4.5-preview-2025-02-27</option> |
| @@ -3127,48 +3146,49 @@ | |||
| 3127 | <div> | 3146 | <div> |
| 3128 | <h4 data-i18n="Google Model">Google Model</h4> | 3147 | <h4 data-i18n="Google Model">Google Model</h4> |
| 3129 | <select id="model_google_select"> | 3148 | <select id="model_google_select"> |
| 3130 | <optgroup label="Primary"> | 3149 | <optgroup label="Gemini 2.5"> |
| 3131 | <option value="gemini-2.0-flash">Gemini 2.0 Flash</option> | 3150 | <option value="gemini-2.5-pro-preview-03-25">gemini-2.5-pro-preview-03-25</option> |
| 3132 | <option value="gemini-1.5-pro">Gemini 1.5 Pro</option> | 3151 | <option value="gemini-2.5-pro-exp-03-25">gemini-2.5-pro-exp-03-25</option> |
| 3133 | <option value="gemini-1.5-flash">Gemini 1.5 Flash</option> | 3152 | <option value="gemini-2.5-flash-preview-04-17">gemini-2.5-flash-preview-04-17</option> |
| 3134 | <option value="gemini-1.0-pro">Gemini 1.0 Pro (Deprecated)</option> | 3153 | </optgroup> |
| 3135 | <option value="gemini-pro">Gemini Pro (1.0) (Deprecated)</option> | 3154 | <optgroup label="Gemini 2.0"> |
| 3136 | <option value="gemini-ultra">Gemini Ultra (1.0)</option> | 3155 | <option value="gemini-2.0-pro-exp-02-05">gemini-2.0-pro-exp-02-05 → 2.5-pro-exp-03-25</option> |
| 3137 | <option value="gemini-1.0-ultra-latest">Gemini 1.0 Ultra</option> | 3156 | <option value="gemini-2.0-pro-exp">gemini-2.0-pro-exp → 2.5-pro-exp-03-25</option> |
| 3157 | <option value="gemini-exp-1206">gemini-exp-1206 → 2.5-pro-exp-03-25</option> | ||
| 3158 | <option value="gemini-2.0-flash-001">gemini-2.0-flash-001</option> | ||
| 3159 | <option value="gemini-2.0-flash-exp-image-generation">gemini-2.0-flash-exp-image-generation</option> | ||
| 3160 | <option value="gemini-2.0-flash-exp">gemini-2.0-flash-exp</option> | ||
| 3161 | <option value="gemini-2.0-flash">gemini-2.0-flash</option> | ||
| 3162 | <option value="gemini-2.0-flash-thinking-exp-01-21">gemini-2.0-flash-thinking-exp-01-21 → 2.5-flash-preview-04-17</option> | ||
| 3163 | <option value="gemini-2.0-flash-thinking-exp-1219">gemini-2.0-flash-thinking-exp-1219 → 2.5-flash-preview-04-17</option> | ||
| 3164 | <option value="gemini-2.0-flash-thinking-exp">gemini-2.0-flash-thinking-exp → 2.5-flash-preview-04-17</option> | ||
| 3165 | <option value="gemini-2.0-flash-lite-001">gemini-2.0-flash-lite-001</option> | ||
| 3166 | <option value="gemini-2.0-flash-lite-preview-02-05">gemini-2.0-flash-lite-preview-02-05</option> | ||
| 3167 | <option value="gemini-2.0-flash-lite-preview">gemini-2.0-flash-lite-preview</option> | ||
| 3168 | </optgroup> | ||
| 3169 | <optgroup label="Gemini 1.5"> | ||
| 3170 | <option value="gemini-1.5-pro-latest">gemini-1.5-pro-latest</option> | ||
| 3171 | <option value="gemini-1.5-pro-002">gemini-1.5-pro-002</option> | ||
| 3172 | <option value="gemini-1.5-pro-001">gemini-1.5-pro-001</option> | ||
| 3173 | <option value="gemini-1.5-pro">gemini-1.5-pro</option> | ||
| 3174 | <option value="gemini-1.5-flash-latest">gemini-1.5-flash-latest</option> | ||
| 3175 | <option value="gemini-1.5-flash-002">gemini-1.5-flash-002</option> | ||
| 3176 | <option value="gemini-1.5-flash-001">gemini-1.5-flash-001</option> | ||
| 3177 | <option value="gemini-1.5-flash">gemini-1.5-flash</option> | ||
| 3178 | <option value="gemini-1.5-flash-8b-001">gemini-1.5-flash-8b-001</option> | ||
| 3179 | <option value="gemini-1.5-flash-8b-exp-0924">gemini-1.5-flash-8b-exp-0924</option> | ||
| 3180 | <option value="gemini-1.5-flash-8b-exp-0827">gemini-1.5-flash-8b-exp-0827</option> | ||
| 3181 | <option value="gemini-1.5-flash-8b">gemini-1.5-flash-8b</option> | ||
| 3138 | </optgroup> | 3182 | </optgroup> |
| 3139 | <optgroup label="Gemma"> | 3183 | <optgroup label="Gemma"> |
| 3140 | <option value="gemma-3-27b-it">Gemma 3 27B</option> | 3184 | <option value="gemma-3-27b-it">gemma-3-27b-it</option> |
| 3185 | <option value="gemma-3-12b-it">gemma-3-12b-it</option> | ||
| 3186 | <option value="gemma-3-4b-it">gemma-3-4b-it</option> | ||
| 3187 | <option value="gemma-3-1b-it">gemma-3-1b-it</option> | ||
| 3141 | </optgroup> | 3188 | </optgroup> |
| 3142 | <optgroup label="Subversions"> | 3189 | <optgroup label="LearnLM"> |
| 3143 | <option value="gemini-2.5-pro-preview-03-25">Gemini 2.5 Pro Preview 2025-03-25</option> | 3190 | <option value="learnlm-2.0-flash-experimental">learnlm-2.0-flash-experimental</option> |
| 3144 | <option value="gemini-2.5-pro-exp-03-25">Gemini 2.5 Pro Experimental 2025-03-25</option> | 3191 | <option value="learnlm-1.5-pro-experimental">learnlm-1.5-pro-experimental</option> |
| 3145 | <option value="gemini-2.0-pro-exp">Gemini 2.0 Pro Experimental</option> | ||
| 3146 | <option value="gemini-2.0-pro-exp-02-05">Gemini 2.0 Pro Experimental 2025-02-05</option> | ||
| 3147 | <option value="gemini-2.0-flash-lite-preview">Gemini 2.0 Flash-Lite Preview</option> | ||
| 3148 | <option value="gemini-2.0-flash-lite-preview-02-05">Gemini 2.0 Flash-Lite Preview 2025-02-05</option> | ||
| 3149 | <option value="gemini-2.0-flash-001">Gemini 2.0 Flash [001]</option> | ||
| 3150 | <option value="gemini-2.0-flash-thinking-exp">Gemini 2.0 Flash Thinking Experimental</option> | ||
| 3151 | <option value="gemini-2.0-flash-thinking-exp-01-21">Gemini 2.0 Flash Thinking Experimental 2025-01-21</option> | ||
| 3152 | <option value="gemini-2.0-flash-thinking-exp-1219">Gemini 2.0 Flash Thinking Experimental 2024-12-19</option> | ||
| 3153 | <option value="gemini-2.0-flash-exp">Gemini 2.0 Flash Experimental</option> | ||
| 3154 | <option value="gemini-2.0-flash-exp-image-generation">Gemini 2.0 Flash (Image Generation) Experimental</option> | ||
| 3155 | <option value="gemini-exp-1114">Gemini Experimental 2024-11-14</option> | ||
| 3156 | <option value="gemini-exp-1121">Gemini Experimental 2024-11-21</option> | ||
| 3157 | <option value="gemini-exp-1206">Gemini Experimental 2024-12-06</option> | ||
| 3158 | <option value="gemini-1.5-pro-exp-0801">Gemini 1.5 Pro Experimental 2024-08-01</option> | ||
| 3159 | <option value="gemini-1.5-pro-exp-0827">Gemini 1.5 Pro Experimental 2024-08-27</option> | ||
| 3160 | <option value="gemini-1.5-pro-latest">Gemini 1.5 Pro [latest]</option> | ||
| 3161 | <option value="gemini-1.5-pro-001">Gemini 1.5 Pro [001]</option> | ||
| 3162 | <option value="gemini-1.5-pro-002">Gemini 1.5 Pro [002]</option> | ||
| 3163 | <option value="gemini-1.5-flash-8b">Gemini 1.5 Flash 8B</option> | ||
| 3164 | <option value="gemini-1.5-flash-exp-0827">Gemini 1.5 Flash Experimental 2024-08-27</option> | ||
| 3165 | <option value="gemini-1.5-flash-8b-exp-0827">Gemini 1.5 Flash 8B Experimental 2024-08-27</option> | ||
| 3166 | <option value="gemini-1.5-flash-8b-exp-0924">Gemini 1.5 Flash 8B Experimental 2024-09-24</option> | ||
| 3167 | <option value="gemini-1.5-flash-latest">Gemini 1.5 Flash [latest]</option> | ||
| 3168 | <option value="gemini-1.5-flash-001">Gemini 1.5 Flash [001]</option> | ||
| 3169 | <option value="gemini-1.5-flash-002">Gemini 1.5 Flash [002]</option> | ||
| 3170 | <option value="gemini-1.0-pro-latest">Gemini 1.0 Pro [latest] (Deprecated)</option> | ||
| 3171 | <option value="gemini-1.0-pro-001">Gemini 1.0 Pro (Tuning) [001] (Deprecated)</option> | ||
| 3172 | </optgroup> | 3192 | </optgroup> |
| 3173 | </select> | 3193 | </select> |
| 3174 | </div> | 3194 | </div> |
| @@ -3424,6 +3444,31 @@ | |||
| 3424 | <select id="model_01ai_select"> | 3444 | <select id="model_01ai_select"> |
| 3425 | </select> | 3445 | </select> |
| 3426 | </div> | 3446 | </div> |
| 3447 | <div id="xai_form" data-source="xai"> | ||
| 3448 | <h4> | ||
| 3449 | <a data-i18n="xAI API Key" href="https://console.x.ai/" target="_blank" rel="noopener noreferrer"> | ||
| 3450 | xAI API Key | ||
| 3451 | </a> | ||
| 3452 | </h4> | ||
| 3453 | <div class="flex-container"> | ||
| 3454 | <input id="api_key_xai" name="api_key_xai" class="text_pole flex1" value="" type="text" autocomplete="off"> | ||
| 3455 | <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> | ||
| 3456 | </div> | ||
| 3457 | <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."> | ||
| 3458 | For privacy reasons, your API key will be hidden after you reload the page. | ||
| 3459 | </div> | ||
| 3460 | <h4 data-i18n="xAI Model">xAI Model</h4> | ||
| 3461 | <select id="model_xai_select"> | ||
| 3462 | <option value="grok-3-beta">grok-3-beta</option> | ||
| 3463 | <option value="grok-3-fast-beta">grok-3-fast-beta</option> | ||
| 3464 | <option value="grok-3-mini-beta">grok-3-mini-beta</option> | ||
| 3465 | <option value="grok-3-mini-fast-beta">grok-3-mini-fast-beta</option> | ||
| 3466 | <option value="grok-2-vision-1212">grok-2-vision-1212</option> | ||
| 3467 | <option value="grok-2-1212">grok-2-1212</option> | ||
| 3468 | <option value="grok-vision-beta">grok-vision-beta</option> | ||
| 3469 | <option value="grok-beta">grok-beta</option> | ||
| 3470 | </select> | ||
| 3471 | </div> | ||
| 3427 | <div id="prompt_post_porcessing_form" data-source="custom,openrouter"> | 3472 | <div id="prompt_post_porcessing_form" data-source="custom,openrouter"> |
| 3428 | <h4 data-i18n="Prompt Post-Processing">Prompt Post-Processing</h4> | 3473 | <h4 data-i18n="Prompt Post-Processing">Prompt Post-Processing</h4> |
| 3429 | <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."> | 3474 | <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."> |
| @@ -3458,7 +3503,7 @@ | |||
| 3458 | <div class="drawer-toggle"> | 3503 | <div class="drawer-toggle"> |
| 3459 | <div class="drawer-icon fa-solid fa-font fa-fw closedIcon" title="AI Response Formatting" data-i18n="[title]AI Response Formatting"></div> | 3504 | <div class="drawer-icon fa-solid fa-font fa-fw closedIcon" title="AI Response Formatting" data-i18n="[title]AI Response Formatting"></div> |
| 3460 | </div> | 3505 | </div> |
| 3461 | <div id="AdvancedFormatting" class="drawer-content"> | 3506 | <div id="AdvancedFormatting" class="drawer-content closedDrawer"> |
| 3462 | <div class="flex-container alignItemsBaseline"> | 3507 | <div class="flex-container alignItemsBaseline"> |
| 3463 | <h3 class="margin0 flex1 flex-container alignItemsBaseline"> | 3508 | <h3 class="margin0 flex1 flex-container alignItemsBaseline"> |
| 3464 | <span data-i18n="Advanced Formatting"> | 3509 | <span data-i18n="Advanced Formatting"> |
| @@ -3577,11 +3622,6 @@ | |||
| 3577 | <small data-i18n="Names as Stop Strings">Names as Stop Strings</small> | 3622 | <small data-i18n="Names as Stop Strings">Names as Stop Strings</small> |
| 3578 | </label> | 3623 | </label> |
| 3579 | </div> | 3624 | </div> |
| 3580 | |||
| 3581 | <label class="checkbox_label" title="Includes Post-History Instructions at the end of the prompt, if defined in the character card AND ''Prefer Char. Instructions'' is enabled. THIS IS NOT RECOMMENDED FOR TEXT COMPLETION MODELS, CAN LEAD TO BAD OUTPUT." data-i18n="[title]context_allow_post_history_instructions"> | ||
| 3582 | <input id="context_allow_jailbreak" type="checkbox" /> | ||
| 3583 | <small data-i18n="Allow Post-History Instructions">Allow Post-History Instructions</small> | ||
| 3584 | </label> | ||
| 3585 | </div> | 3625 | </div> |
| 3586 | </div> | 3626 | </div> |
| 3587 | </div> | 3627 | </div> |
| @@ -3783,9 +3823,7 @@ | |||
| 3783 | </label> | 3823 | </label> |
| 3784 | </div> | 3824 | </div> |
| 3785 | </h4> | 3825 | </h4> |
| 3786 | <div id="SystemPromptBlock"> | 3826 | <div id="SystemPromptBlock" class="marginBot10"> |
| 3787 | |||
| 3788 | |||
| 3789 | <div class="flex-container" title="Select your current System Prompt" data-i18n="[title]Select your current System Prompt"> | 3827 | <div class="flex-container" title="Select your current System Prompt" data-i18n="[title]Select your current System Prompt"> |
| 3790 | <select id="sysprompt_select" data-preset-manager-for="sysprompt" class="flex1 text_pole"></select> | 3828 | <select id="sysprompt_select" data-preset-manager-for="sysprompt" class="flex1 text_pole"></select> |
| 3791 | <div class="flex-container margin0 justifyCenter gap3px"> | 3829 | <div class="flex-container margin0 justifyCenter gap3px"> |
| @@ -3807,10 +3845,14 @@ | |||
| 3807 | </label> | 3845 | </label> |
| 3808 | <textarea id="sysprompt_content" class="text_pole textarea_compact autoSetHeight"></textarea> | 3846 | <textarea id="sysprompt_content" class="text_pole textarea_compact autoSetHeight"></textarea> |
| 3809 | </div> | 3847 | </div> |
| 3810 | </div> | ||
| 3811 | 3848 | ||
| 3812 | <div> | 3849 | <div> |
| 3813 | | 3850 | <label for="sysprompt_post_history" class="flex-container"> |
| 3851 | <small data-i18n="Post-History Instructions">Post-History Instructions</small> | ||
| 3852 | <i class="editor_maximize fa-solid fa-maximize right_menu_button" data-for="sysprompt_post_history" title="Expand the editor" data-i18n="[title]Expand the editor"></i> | ||
| 3853 | </label> | ||
| 3854 | <textarea id="sysprompt_post_history" class="text_pole textarea_compact autoSetHeight"></textarea> | ||
| 3855 | </div> | ||
| 3814 | </div> | 3856 | </div> |
| 3815 | 3857 | ||
| 3816 | <div> | 3858 | <div> |
| @@ -5113,7 +5155,7 @@ | |||
| 5113 | <div id="persona_depth_position_settings" class="flex-container"> | 5155 | <div id="persona_depth_position_settings" class="flex-container"> |
| 5114 | <div class="flex1"> | 5156 | <div class="flex1"> |
| 5115 | <label for="persona_depth_value" data-i18n="Depth:">Depth:</label> | 5157 | <label for="persona_depth_value" data-i18n="Depth:">Depth:</label> |
| 5116 | <input id="persona_depth_value" class="text_pole" type="number" min="0" max="999" step="1"> | 5158 | <input id="persona_depth_value" class="text_pole" type="number" min="0" max="9999" step="1"> |
| 5117 | </div> | 5159 | </div> |
| 5118 | <div class="flex1"> | 5160 | <div class="flex1"> |
| 5119 | <label for="persona_depth_role" data-i18n="Role:">Role:</label> | 5161 | <label for="persona_depth_role" data-i18n="Role:">Role:</label> |
| @@ -5489,7 +5531,7 @@ | |||
| 5489 | <div class="inline-drawer-content"> | 5531 | <div class="inline-drawer-content"> |
| 5490 | <div id="currentGroupMembers" name="Current Group Members" class="flex-container flexFlowColumn overflowYAuto flex1"> | 5532 | <div id="currentGroupMembers" name="Current Group Members" class="flex-container flexFlowColumn overflowYAuto flex1"> |
| 5491 | <div id="rm_group_members_pagination" class="rm_group_members_pagination group_pagination"></div> | 5533 | <div id="rm_group_members_pagination" class="rm_group_members_pagination group_pagination"></div> |
| 5492 | <div id="rm_group_members" class="rm_group_members overflowYAuto flex-container"></div> | 5534 | <div id="rm_group_members" class="rm_group_members overflowYAuto flex-container" group_empty_text="Group is empty." data-i18n="[group_empty_text]Group is empty."></div> |
| 5493 | </div> | 5535 | </div> |
| 5494 | </div> | 5536 | </div> |
| 5495 | </div> | 5537 | </div> |
| @@ -5507,7 +5549,7 @@ | |||
| 5507 | <div class="tags rm_tag_filter"></div> | 5549 | <div class="tags rm_tag_filter"></div> |
| 5508 | </div> | 5550 | </div> |
| 5509 | <div id="rm_group_add_members_pagination" class="group_pagination"></div> | 5551 | <div id="rm_group_add_members_pagination" class="group_pagination"></div> |
| 5510 | <div id="rm_group_add_members" class="overflowYAuto flex-container"></div> | 5552 | <div id="rm_group_add_members" class="overflowYAuto flex-container" no_characters_text="No characters available" data-i18n="[no_characters_text]No characters available"></div> |
| 5511 | </div> | 5553 | </div> |
| 5512 | </div> | 5554 | </div> |
| 5513 | </div> | 5555 | </div> |
| @@ -5766,7 +5808,7 @@ | |||
| 5766 | @ Depth | 5808 | @ Depth |
| 5767 | </span> | 5809 | </span> |
| 5768 | </h4> | 5810 | </h4> |
| 5769 | <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" /> | 5811 | <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" /> |
| 5770 | <h4> | 5812 | <h4> |
| 5771 | <span data-i18n="Role"> | 5813 | <span data-i18n="Role"> |
| 5772 | Role | 5814 | Role |
| @@ -5927,7 +5969,7 @@ | |||
| 5927 | <div class="tag_view_color_picker" data-value="color"></div> | 5969 | <div class="tag_view_color_picker" data-value="color"></div> |
| 5928 | <div class="tag_view_color_picker" data-value="color2"></div> | 5970 | <div class="tag_view_color_picker" data-value="color2"></div> |
| 5929 | <div class="tag_view_name" contenteditable="true"></div> | 5971 | <div class="tag_view_name" contenteditable="true"></div> |
| 5930 | <div class="tag_view_counter"><span class="tag_view_counter_value"></span> entries</div> | 5972 | <div class="tag_view_counter"><span class="tag_view_counter_value"></span> <span data-i18n="tag_entries">entries</span></div> |
| 5931 | <div title="Delete tag" class="tag_delete fa-solid fa-trash-can right_menu_button" data-i18n="[title]Delete tag"></div> | 5973 | <div title="Delete tag" class="tag_delete fa-solid fa-trash-can right_menu_button" data-i18n="[title]Delete tag"></div> |
| 5932 | </div> | 5974 | </div> |
| 5933 | </div> | 5975 | </div> |
| @@ -5987,11 +6029,11 @@ | |||
| 5987 | </div> | 6029 | </div> |
| 5988 | <div class="world_entry_form_control wi-enter-footer-text flex-container flexNoGap"> | 6030 | <div class="world_entry_form_control wi-enter-footer-text flex-container flexNoGap"> |
| 5989 | <label for="depth" class="WIEntryHeaderTitleMobile" data-i18n="Depth:">Depth:</label> | 6031 | <label for="depth" class="WIEntryHeaderTitleMobile" data-i18n="Depth:">Depth:</label> |
| 5990 | <input title="Depth" class="text_pole wideMax100px margin0" type="number" name="depth" data-i18n="[title]Depth" placeholder="" min="0" max="999" /> | 6032 | <input title="Depth" class="text_pole wideMax100px margin0" type="number" name="depth" data-i18n="[title]Depth" placeholder="" min="0" max="9999" /> |
| 5991 | </div> | 6033 | </div> |
| 5992 | <div class="world_entry_form_control wi-enter-footer-text flex-container flexNoGap"> | 6034 | <div class="world_entry_form_control wi-enter-footer-text flex-container flexNoGap"> |
| 5993 | <label for="order" class="WIEntryHeaderTitleMobile" data-i18n="Order:">Order:</label> | 6035 | <label for="order" class="WIEntryHeaderTitleMobile" data-i18n="Order:">Order:</label> |
| 5994 | <input title="Order" data-i18n="[title]Order" class="text_pole wideMax100px margin0" type="number" name="order" placeholder="" min="0" max="999" /> | 6036 | <input title="Order" data-i18n="[title]Order" class="text_pole wideMax100px margin0" type="number" name="order" placeholder="" min="0" max="9999" /> |
| 5995 | </div> | 6037 | </div> |
| 5996 | <div class="world_entry_form_control wi-enter-footer-text flex-container flexNoGap probabilityContainer"> | 6038 | <div class="world_entry_form_control wi-enter-footer-text flex-container flexNoGap probabilityContainer"> |
| 5997 | <label for="order" class="WIEntryHeaderTitleMobile" data-i18n="Trigger %:">Trigger %:</label> | 6039 | <label for="order" class="WIEntryHeaderTitleMobile" data-i18n="Trigger %:">Trigger %:</label> |
| @@ -6247,6 +6289,54 @@ | |||
| 6247 | </label> | 6289 | </label> |
| 6248 | </div> | 6290 | </div> |
| 6249 | </div> | 6291 | </div> |
| 6292 | <div class="inline-drawer wide100p flexFlowColumn"> | ||
| 6293 | <div class="inline-drawer-toggle inline-drawer-header inline-drawer-header-pointer userSettingsInnerExpandable"> | ||
| 6294 | <strong data-i18n="Additional Matching Sources">Additional Matching Sources</strong> | ||
| 6295 | <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div> | ||
| 6296 | </div> | ||
| 6297 | <div class="inline-drawer-content flex-container flexFlowRow flexGap10 paddingBottom5px"> | ||
| 6298 | <small class="flex-container flex1 flexFlowColumn"> | ||
| 6299 | <label class="checkbox flex-container alignItemsCenter flexNoGap"> | ||
| 6300 | <input type="checkbox" name="matchCharacterDescription" /> | ||
| 6301 | <span data-i18n="Character Description"> | ||
| 6302 | Character Description | ||
| 6303 | </span> | ||
| 6304 | </label> | ||
| 6305 | <label class="checkbox flex-container alignItemsCenter flexNoGap"> | ||
| 6306 | <input type="checkbox" name="matchCharacterPersonality" /> | ||
| 6307 | <span data-i18n="Character Personality"> | ||
| 6308 | Character Personality | ||
| 6309 | </span> | ||
| 6310 | </label> | ||
| 6311 | <label class="checkbox flex-container alignItemsCenter flexNoGap"> | ||
| 6312 | <input type="checkbox" name="matchScenario" /> | ||
| 6313 | <span data-i18n="Scenario"> | ||
| 6314 | Scenario | ||
| 6315 | </span> | ||
| 6316 | </label> | ||
| 6317 | </small> | ||
| 6318 | <small class="flex-container flex1 flexFlowColumn"> | ||
| 6319 | <label class="checkbox flex-container alignItemsCenter flexNoGap"> | ||
| 6320 | <input type="checkbox" name="matchPersonaDescription" /> | ||
| 6321 | <span data-i18n="Persona Description"> | ||
| 6322 | Persona Description | ||
| 6323 | </span> | ||
| 6324 | </label> | ||
| 6325 | <label class="checkbox flex-container alignItemsCenter flexNoGap"> | ||
| 6326 | <input type="checkbox" name="matchCharacterDepthPrompt" /> | ||
| 6327 | <span data-i18n="Character's Note"> | ||
| 6328 | Character's Note | ||
| 6329 | </span> | ||
| 6330 | </label> | ||
| 6331 | <label class="checkbox flex-container alignItemsCenter flexNoGap"> | ||
| 6332 | <input type="checkbox" name="matchCreatorNotes" /> | ||
| 6333 | <span data-i18n="Creator's Notes"> | ||
| 6334 | Creator's Notes | ||
| 6335 | </span> | ||
| 6336 | </label> | ||
| 6337 | </small> | ||
| 6338 | </div> | ||
| 6339 | </div> | ||
| 6250 | </div> | 6340 | </div> |
| 6251 | </div> | 6341 | </div> |
| 6252 | </form> | 6342 | </form> |
| @@ -6353,7 +6443,7 @@ | |||
| 6353 | <span data-i18n="prompt_manager_depth">Depth</span> | 6443 | <span data-i18n="prompt_manager_depth">Depth</span> |
| 6354 | </label> | 6444 | </label> |
| 6355 | <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> | 6445 | <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> |
| 6356 | <input id="completion_prompt_manager_popup_entry_form_injection_depth" class="text_pole" type="number" name="injection_depth" min="0" max="999" value="4" /> | 6446 | <input id="completion_prompt_manager_popup_entry_form_injection_depth" class="text_pole" type="number" name="injection_depth" min="0" max="9999" value="4" /> |
| 6357 | </div> | 6447 | </div> |
| 6358 | </div> | 6448 | </div> |
| 6359 | <div class="completion_prompt_manager_popup_entry_form_control"> | 6449 | <div class="completion_prompt_manager_popup_entry_form_control"> |
| @@ -6614,7 +6704,7 @@ | |||
| 6614 | </div> | 6704 | </div> |
| 6615 | <div class="flex-container wide100pLess70px character_select_container height100p alignitemscenter"> | 6705 | <div class="flex-container wide100pLess70px character_select_container height100p alignitemscenter"> |
| 6616 | <div class="wide100p character_name_block"> | 6706 | <div class="wide100p character_name_block"> |
| 6617 | <span class="ch_name">Go back</span> | 6707 | <span class="ch_name" data-i18n="Go back">Go back</span> |
| 6618 | </div> | 6708 | </div> |
| 6619 | </div> | 6709 | </div> |
| 6620 | </div> | 6710 | </div> |
| @@ -6713,7 +6803,7 @@ | |||
| 6713 | <label class="checkbox_label alignItemsCenter" for="extension_floating_position_depth"> | 6803 | <label class="checkbox_label alignItemsCenter" for="extension_floating_position_depth"> |
| 6714 | <input type="radio" id="extension_floating_position_depth" name="extension_floating_position" value="1" /> | 6804 | <input type="radio" id="extension_floating_position_depth" name="extension_floating_position" value="1" /> |
| 6715 | <span data-i18n="In-chat @ Depth">In-chat @ Depth</span> | 6805 | <span data-i18n="In-chat @ Depth">In-chat @ Depth</span> |
| 6716 | <input id="extension_floating_depth" class="text_pole textarea_compact widthNatural" type="number" min="0" max="999" /> | 6806 | <input id="extension_floating_depth" class="text_pole textarea_compact widthNatural" type="number" min="0" max="9999" /> |
| 6717 | <span data-i18n="as">as</span> | 6807 | <span data-i18n="as">as</span> |
| 6718 | <select id="extension_floating_role" class="text_pole widthNatural"> | 6808 | <select id="extension_floating_role" class="text_pole widthNatural"> |
| 6719 | <option data-i18n="System" value="0">System</option> | 6809 | <option data-i18n="System" value="0">System</option> |
| @@ -6728,7 +6818,7 @@ | |||
| 6728 | <span data-i18n="Insertion Frequency">Insertion Frequency</span> | 6818 | <span data-i18n="Insertion Frequency">Insertion Frequency</span> |
| 6729 | <small data-i18n="(0 = Disable, 1 = Always)">(0 = Disable, 1 = Always)</small> | 6819 | <small data-i18n="(0 = Disable, 1 = Always)">(0 = Disable, 1 = Always)</small> |
| 6730 | </label> | 6820 | </label> |
| 6731 | <input id="extension_floating_interval" class="text_pole widthUnset" type="number" min="0" max="999" /> | 6821 | <input id="extension_floating_interval" class="text_pole widthUnset" type="number" min="0" max="9999" /> |
| 6732 | </div> | 6822 | </div> |
| 6733 | <br> | 6823 | <br> |
| 6734 | <span><span data-i18n="User inputs until next insertion:">User inputs until next insertion:</span> <span id="extension_floating_counter">(disabled)</span></span> | 6824 | <span><span data-i18n="User inputs until next insertion:">User inputs until next insertion:</span> <span id="extension_floating_counter">(disabled)</span></span> |
| @@ -6798,7 +6888,7 @@ | |||
| 6798 | <label class="checkbox_label alignItemsCenter" for="extension_default_position_depth"> | 6888 | <label class="checkbox_label alignItemsCenter" for="extension_default_position_depth"> |
| 6799 | <input type="radio" id="extension_default_position_depth" name="extension_default_position" value="1" /> | 6889 | <input type="radio" id="extension_default_position_depth" name="extension_default_position" value="1" /> |
| 6800 | <span data-i18n="In-chat @ Depth">In-chat @ Depth</span> | 6890 | <span data-i18n="In-chat @ Depth">In-chat @ Depth</span> |
| 6801 | <input id="extension_default_depth" class="text_pole textarea_compact widthNatural" type="number" min="0" max="999" /> | 6891 | <input id="extension_default_depth" class="text_pole textarea_compact widthNatural" type="number" min="0" max="9999" /> |
| 6802 | <span data-i18n="as">as</span> | 6892 | <span data-i18n="as">as</span> |
| 6803 | <select id="extension_default_role" class="text_pole widthNatural"> | 6893 | <select id="extension_default_role" class="text_pole widthNatural"> |
| 6804 | <option data-i18n="System" value="0">System</option> | 6894 | <option data-i18n="System" value="0">System</option> |
| @@ -6812,7 +6902,7 @@ | |||
| 6812 | <span data-i18n="Insertion Frequency">Insertion Frequency</span> | 6902 | <span data-i18n="Insertion Frequency">Insertion Frequency</span> |
| 6813 | <small data-i18n="(0 = Disable, 1 = Always)">(0 = Disable, 1 = Always)</small> | 6903 | <small data-i18n="(0 = Disable, 1 = Always)">(0 = Disable, 1 = Always)</small> |
| 6814 | </label> | 6904 | </label> |
| 6815 | <input id="extension_default_interval" class="text_pole widthUnset" type="number" min="0" max="999" /> | 6905 | <input id="extension_default_interval" class="text_pole widthUnset" type="number" min="0" max="9999" /> |
| 6816 | </div> | 6906 | </div> |
| 6817 | </div> | 6907 | </div> |
| 6818 | </div> | 6908 | </div> |
| @@ -411,7 +411,6 @@ | |||
| 411 | "Chat Start": "بداية الدردشة", | 411 | "Chat Start": "بداية الدردشة", |
| 412 | "Add Chat Start and Example Separator to a list of stopping strings.": "أضف بداية الدردشة وفاصل الأمثلة إلى قائمة سلاسل التوقف.", | 412 | "Add Chat Start and Example Separator to a list of stopping strings.": "أضف بداية الدردشة وفاصل الأمثلة إلى قائمة سلاسل التوقف.", |
| 413 | "Use as Stop Strings": "استخدم كسلاسل التوقف", | 413 | "Use as Stop Strings": "استخدم كسلاسل التوقف", |
| 414 | "context_allow_jailbreak": "يتضمن كسر الحماية في نهاية المطالبة، إذا تم تحديده في بطاقة الشخصية و''Prefer Char. تم تمكين الهروب من السجن.\nلا يُنصح بهذا بالنسبة لنماذج إكمال النص، فقد يؤدي إلى نتائج سيئة.", | ||
| 415 | "Allow Jailbreak": "السماح بالجيلبريك", | 414 | "Allow Jailbreak": "السماح بالجيلبريك", |
| 416 | "Context Order": "ترتيب السياق", | 415 | "Context Order": "ترتيب السياق", |
| 417 | "Summary": "ملخص", | 416 | "Summary": "ملخص", |
| @@ -411,7 +411,6 @@ | |||
| 411 | "Chat Start": "Chat-Start", | 411 | "Chat Start": "Chat-Start", |
| 412 | "Add Chat Start and Example Separator to a list of stopping strings.": "Fügen Sie einer Liste von Stoppzeichenfolgen „Chat-Start“ und „Beispieltrennzeichen“ hinzu.", | 412 | "Add Chat Start and Example Separator to a list of stopping strings.": "Fügen Sie einer Liste von Stoppzeichenfolgen „Chat-Start“ und „Beispieltrennzeichen“ hinzu.", |
| 413 | "Use as Stop Strings": "Verwende als Stoppzeichenfolgen", | 413 | "Use as Stop Strings": "Verwende als Stoppzeichenfolgen", |
| 414 | "context_allow_jailbreak": "Schließt Jailbreak am Ende der Eingabeaufforderung ein, wenn dies in der Charakterkarte definiert ist UND „Charakter-Jailbreak bevorzugen“ aktiviert ist.\nDIES WIRD FÜR TEXTVERVOLLSTÄNDIGUNGSMODELLE NICHT EMPFOHLEN, KANN ZU SCHLECHTEN AUSGABEN FÜHREN.", | ||
| 415 | "Allow Jailbreak": "Jailbreak zulassen", | 414 | "Allow Jailbreak": "Jailbreak zulassen", |
| 416 | "Context Order": "Kontextreihenfolge", | 415 | "Context Order": "Kontextreihenfolge", |
| 417 | "Summary": "Zusammenfassung", | 416 | "Summary": "Zusammenfassung", |
| @@ -411,7 +411,6 @@ | |||
| 411 | "Chat Start": "Inicio de chat", | 411 | "Chat Start": "Inicio de chat", |
| 412 | "Add Chat Start and Example Separator to a list of stopping strings.": "Agregue Inicio de chat y Separador de ejemplo a una lista de cadenas de parada.", | 412 | "Add Chat Start and Example Separator to a list of stopping strings.": "Agregue Inicio de chat y Separador de ejemplo a una lista de cadenas de parada.", |
| 413 | "Use as Stop Strings": "Usar como Cadenas de Parada", | 413 | "Use as Stop Strings": "Usar como Cadenas de Parada", |
| 414 | "context_allow_jailbreak": "Incluye Jailbreak al final del mensaje, si está definido en la tarjeta de personaje Y está habilitado \"Prefer Char. Jailbreak\".\nESTO NO SE RECOMIENDA PARA MODELOS DE COMPLETO DE TEXTO, PUEDE PRODUCIR UN RESULTADO INCORRECTO.", | ||
| 415 | "Allow Jailbreak": "Permitir Jailbreak", | 414 | "Allow Jailbreak": "Permitir Jailbreak", |
| 416 | "Context Order": "Orden de contexto", | 415 | "Context Order": "Orden de contexto", |
| 417 | "Summary": "Resumen", | 416 | "Summary": "Resumen", |
| @@ -1448,7 +1448,6 @@ | |||
| 1448 | "Add Character and User names to a list of stopping strings.": "Ajouter les noms de personnages et d'utilisateurs à une liste de chaînes d'arrêt.", | 1448 | "Add Character and User names to a list of stopping strings.": "Ajouter les noms de personnages et d'utilisateurs à une liste de chaînes d'arrêt.", |
| 1449 | "Names as Stop Strings": "Noms comme chaînes d'arrêt", | 1449 | "Names as Stop Strings": "Noms comme chaînes d'arrêt", |
| 1450 | "context_allow_post_history_instructions": "Inclut les instructions post-historiques à la fin du prompt, si elles sont définies dans la fiche de personnage ET si l'option 'Préférer les instructions de personnage' est activée.\nN'EST PAS RECOMMANDÉ POUR LES MODÈLES DE COMPLÉTION DE TEXTE, CAR IL PEUT ENTRAÎNER DE MAUVAIS RÉSULTATS.", | 1450 | "context_allow_post_history_instructions": "Inclut les instructions post-historiques à la fin du prompt, si elles sont définies dans la fiche de personnage ET si l'option 'Préférer les instructions de personnage' est activée.\nN'EST PAS RECOMMANDÉ POUR LES MODÈLES DE COMPLÉTION DE TEXTE, CAR IL PEUT ENTRAÎNER DE MAUVAIS RÉSULTATS.", |
| 1451 | "Allow Post-History Instructions": "Autoriser les instructions post-histoire", | ||
| 1452 | "Instruct Template": "Modèle d'instruction", | 1451 | "Instruct Template": "Modèle d'instruction", |
| 1453 | "instruct_derived": "Dériver des métadonnées du modèle, si possible.", | 1452 | "instruct_derived": "Dériver des métadonnées du modèle, si possible.", |
| 1454 | "instruct_enabled": "Activer le mode d'instruction", | 1453 | "instruct_enabled": "Activer le mode d'instruction", |
| @@ -411,7 +411,6 @@ | |||
| 411 | "Chat Start": "Chat Start", | 411 | "Chat Start": "Chat Start", |
| 412 | "Add Chat Start and Example Separator to a list of stopping strings.": "Bættu Chat Start og Example Separator við lista yfir stöðvunarstrengi.", | 412 | "Add Chat Start and Example Separator to a list of stopping strings.": "Bættu Chat Start og Example Separator við lista yfir stöðvunarstrengi.", |
| 413 | "Use as Stop Strings": "Nota sem Stoppa Strengir", | 413 | "Use as Stop Strings": "Nota sem Stoppa Strengir", |
| 414 | "context_allow_jailbreak": "Inniheldur Jailbreak í lok hvetjunnar, ef það er skilgreint á stafkortinu OG ''Velst Char. Jailbreak'' er virkt.\nÞETTA ER EKKI MÆLT FYRIR TEXTAÚRSLUNARGERÐ, GETUR leitt til lélegrar úttaks.", | ||
| 415 | "Allow Jailbreak": "Leyfa jailbreak", | 414 | "Allow Jailbreak": "Leyfa jailbreak", |
| 416 | "Context Order": "Samhengisröð", | 415 | "Context Order": "Samhengisröð", |
| 417 | "Summary": "Samantekt", | 416 | "Summary": "Samantekt", |
| @@ -411,7 +411,6 @@ | |||
| 411 | "Chat Start": "Inizio chat", | 411 | "Chat Start": "Inizio chat", |
| 412 | "Add Chat Start and Example Separator to a list of stopping strings.": "Aggiungi Inizio chat e Separatore di esempio a un elenco di stringhe di arresto.", | 412 | "Add Chat Start and Example Separator to a list of stopping strings.": "Aggiungi Inizio chat e Separatore di esempio a un elenco di stringhe di arresto.", |
| 413 | "Use as Stop Strings": "Usa come stringhe di arresto", | 413 | "Use as Stop Strings": "Usa come stringhe di arresto", |
| 414 | "context_allow_jailbreak": "Include il jailbreak alla fine del prompt, se definito nella carta personaggio E ''Preferisci Char. Il jailbreak'' è abilitato.\nQUESTO NON È CONSIGLIATO PER I MODELLI DI COMPLETAMENTO DEL TESTO, PUÒ PORTARE A UN RISULTATO CATTIVO.", | ||
| 415 | "Allow Jailbreak": "Consenti jailbreak", | 414 | "Allow Jailbreak": "Consenti jailbreak", |
| 416 | "Context Order": "Ordine del contesto", | 415 | "Context Order": "Ordine del contesto", |
| 417 | "Summary": "Riepilogo", | 416 | "Summary": "Riepilogo", |
| @@ -411,7 +411,6 @@ | |||
| 411 | "Chat Start": "チャット開始", | 411 | "Chat Start": "チャット開始", |
| 412 | "Add Chat Start and Example Separator to a list of stopping strings.": "停止文字列のリストにチャット開始と例の区切り文字を追加します。", | 412 | "Add Chat Start and Example Separator to a list of stopping strings.": "停止文字列のリストにチャット開始と例の区切り文字を追加します。", |
| 413 | "Use as Stop Strings": "ストップ文字列として使用", | 413 | "Use as Stop Strings": "ストップ文字列として使用", |
| 414 | "context_allow_jailbreak": "文字カードで定義されていて、「文字 Jailbreak を優先」が有効になっている場合は、プロンプトの最後に Jailbreak が含まれます。\nこれはテキスト補完モデルには推奨されません。出力が悪くなる可能性があります。", | ||
| 415 | "Allow Jailbreak": "脱獄を許可する", | 414 | "Allow Jailbreak": "脱獄を許可する", |
| 416 | "Context Order": "コンテキスト順序", | 415 | "Context Order": "コンテキスト順序", |
| 417 | "Summary": "まとめ", | 416 | "Summary": "まとめ", |
| @@ -421,7 +421,6 @@ | |||
| 421 | "Chat Start": "채팅 시작", | 421 | "Chat Start": "채팅 시작", |
| 422 | "Add Chat Start and Example Separator to a list of stopping strings.": "중지 문자열 목록에 채팅 시작 및 예제 구분 기호를 추가합니다.", | 422 | "Add Chat Start and Example Separator to a list of stopping strings.": "중지 문자열 목록에 채팅 시작 및 예제 구분 기호를 추가합니다.", |
| 423 | "Use as Stop Strings": "중지 문자열로 사용", | 423 | "Use as Stop Strings": "중지 문자열로 사용", |
| 424 | "context_allow_jailbreak": "캐릭터 카드에 정의되어 있고 ''Prefer Char. Jailbreak''가 활성화되어 있는 경우 프롬프트 끝에 Jailbreak를 포함합니다.\n이는 텍스트 완성 모델에 권장되지 않으며, 나쁜 출력으로 이어질 수 있습니다.", | ||
| 425 | "Allow Jailbreak": "탈옥 허용", | 424 | "Allow Jailbreak": "탈옥 허용", |
| 426 | "Context Order": "컨텍스트 순서", | 425 | "Context Order": "컨텍스트 순서", |
| 427 | "Summary": "요약", | 426 | "Summary": "요약", |
| @@ -1520,7 +1519,6 @@ | |||
| 1520 | "Always": "항상 추가함", | 1519 | "Always": "항상 추가함", |
| 1521 | "Separators as Stop Strings": "구분 기호를 정지 문자열로 사용하기", | 1520 | "Separators as Stop Strings": "구분 기호를 정지 문자열로 사용하기", |
| 1522 | "Names as Stop Strings": "캐릭터의 이름들을 정지 문자열로 사용하기", | 1521 | "Names as Stop Strings": "캐릭터의 이름들을 정지 문자열로 사용하기", |
| 1523 | "Allow Post-History Instructions": "Post-History 지침 허용", | ||
| 1524 | "Image Captioning": "이미지 캡셔닝", | 1522 | "Image Captioning": "이미지 캡셔닝", |
| 1525 | "Automatically caption images": "자동으로 이미지에 대한 설명 문장으로 나타내기", | 1523 | "Automatically caption images": "자동으로 이미지에 대한 설명 문장으로 나타내기", |
| 1526 | "Edit captions before saving": "저장하기 전에 이미지에 대한 설명 문장 편집하기", | 1524 | "Edit captions before saving": "저장하기 전에 이미지에 대한 설명 문장 편집하기", |
| @@ -411,7 +411,6 @@ | |||
| 411 | "Chat Start": "Chatstart", | 411 | "Chat Start": "Chatstart", |
| 412 | "Add Chat Start and Example Separator to a list of stopping strings.": "Voeg Chat Start en Voorbeeldscheidingsteken toe aan een lijst met stoptekenreeksen.", | 412 | "Add Chat Start and Example Separator to a list of stopping strings.": "Voeg Chat Start en Voorbeeldscheidingsteken toe aan een lijst met stoptekenreeksen.", |
| 413 | "Use as Stop Strings": "Gebruik als stopreeksen", | 413 | "Use as Stop Strings": "Gebruik als stopreeksen", |
| 414 | "context_allow_jailbreak": "Inclusief jailbreak aan het einde van de prompt, indien gedefinieerd in de karakterkaart EN ''Prefer Char. Jailbreak'' is ingeschakeld.\nDIT WORDT NIET AANBEVOLEN VOOR MODELLEN VOOR HET INVOEREN VAN TEKST. KAN TOT SLECHTE UITVOER LEIDEN.", | ||
| 415 | "Allow Jailbreak": "Jailbreak toestaan", | 414 | "Allow Jailbreak": "Jailbreak toestaan", |
| 416 | "Context Order": "Contextvolgorde", | 415 | "Context Order": "Contextvolgorde", |
| 417 | "Summary": "Samenvatting", | 416 | "Summary": "Samenvatting", |
| @@ -411,7 +411,6 @@ | |||
| 411 | "Chat Start": "Início do Chat", | 411 | "Chat Start": "Início do Chat", |
| 412 | "Add Chat Start and Example Separator to a list of stopping strings.": "Adicione o início do bate-papo e o separador de exemplo a uma lista de strings de parada.", | 412 | "Add Chat Start and Example Separator to a list of stopping strings.": "Adicione o início do bate-papo e o separador de exemplo a uma lista de strings de parada.", |
| 413 | "Use as Stop Strings": "Usar como Strings de Parada", | 413 | "Use as Stop Strings": "Usar como Strings de Parada", |
| 414 | "context_allow_jailbreak": "Inclui Jailbreak no final do prompt, se definido no cartão de personagem E ''Prefer Char. Jailbreak'' está habilitado.\nISTO NÃO É RECOMENDADO PARA MODELOS DE COMPLEMENTAÇÃO DE TEXTO, PODE LEVAR A UMA SAÍDA RUIM.", | ||
| 415 | "Allow Jailbreak": "Permitir jailbreak", | 414 | "Allow Jailbreak": "Permitir jailbreak", |
| 416 | "Context Order": "Ordem de Contexto", | 415 | "Context Order": "Ordem de Contexto", |
| 417 | "Summary": "Resumo", | 416 | "Summary": "Resumo", |
| @@ -52,7 +52,7 @@ | |||
| 52 | "Presence Penalty": "Штраф за присутствие", | 52 | "Presence Penalty": "Штраф за присутствие", |
| 53 | "Top A": "Top А", | 53 | "Top A": "Top А", |
| 54 | "Tail Free Sampling": "Tail Free Sampling", | 54 | "Tail Free Sampling": "Tail Free Sampling", |
| 55 | "Rep. Pen. Slope": "Rep. Pen. Slope", | 55 | "Rep. Pen. Slope": "Рост штрафа за повтор к концу промпта", |
| 56 | "Top K": "Top K", | 56 | "Top K": "Top K", |
| 57 | "Top P": "Top P", | 57 | "Top P": "Top P", |
| 58 | "Do Sample": "Включить сэмплинг", | 58 | "Do Sample": "Включить сэмплинг", |
| @@ -162,9 +162,9 @@ | |||
| 162 | "Story String": "Строка истории", | 162 | "Story String": "Строка истории", |
| 163 | "Example Separator": "Разделитель примеров сообщений", | 163 | "Example Separator": "Разделитель примеров сообщений", |
| 164 | "Chat Start": "Начало чата", | 164 | "Chat Start": "Начало чата", |
| 165 | "Activation Regex": "Regex для активации", | 165 | "Activation Regex": "Рег. выражение для активации", |
| 166 | "Instruct Mode": "Режим Instruct", | 166 | "Instruct Mode": "Режим Instruct", |
| 167 | "Wrap Sequences with Newline": "Отделять строки символом новой строки", | 167 | "Wrap Sequences with Newline": "Каждая строка из шаблона на новой строке", |
| 168 | "Include Names": "Добавлять имена", | 168 | "Include Names": "Добавлять имена", |
| 169 | "Force for Groups and Personas": "Также для групп и персон", | 169 | "Force for Groups and Personas": "Также для групп и персон", |
| 170 | "System Prompt": "Системный промпт", | 170 | "System Prompt": "Системный промпт", |
| @@ -299,7 +299,7 @@ | |||
| 299 | "AI Horde": "AI Horde", | 299 | "AI Horde": "AI Horde", |
| 300 | "NovelAI": "NovelAI", | 300 | "NovelAI": "NovelAI", |
| 301 | "OpenAI API key": "Ключ для API OpenAI", | 301 | "OpenAI API key": "Ключ для API OpenAI", |
| 302 | "Trim spaces": "Обрезать пробелы", | 302 | "Trim spaces": "Обрезать пробелы в начале и конце", |
| 303 | "Trim Incomplete Sentences": "Удалять неоконченные предложения", | 303 | "Trim Incomplete Sentences": "Удалять неоконченные предложения", |
| 304 | "Include Newline": "Добавлять новую строку", | 304 | "Include Newline": "Добавлять новую строку", |
| 305 | "Non-markdown strings": "Строки без разметки", | 305 | "Non-markdown strings": "Строки без разметки", |
| @@ -510,7 +510,7 @@ | |||
| 510 | "New preset": "Новый пресет", | 510 | "New preset": "Новый пресет", |
| 511 | "Delete preset": "Удалить пресет", | 511 | "Delete preset": "Удалить пресет", |
| 512 | "API Connections": "Соединения с API", | 512 | "API Connections": "Соединения с API", |
| 513 | "Can help with bad responses by queueing only the approved workers. May slowdown the response time.": "Может помочь с плохими ответами ставя в очередь только подтвержденных работников. Может замедлить время ответа.", | 513 | "Can help with bad responses by queueing only the approved workers. May slowdown the response time.": "Может помочь при плохих ответах, делая запросы только к доверенным рабочим машинам. Может замедлить время ответа.", |
| 514 | "Clear your API key": "Стереть ключ от API", | 514 | "Clear your API key": "Стереть ключ от API", |
| 515 | "Refresh models": "Обновить модели", | 515 | "Refresh models": "Обновить модели", |
| 516 | "Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "Получите свой OpenRouter API токен используя OAuth. У вас будет открыта вкладка openrouter.ai", | 516 | "Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "Получите свой OpenRouter API токен используя OAuth. У вас будет открыта вкладка openrouter.ai", |
| @@ -551,7 +551,7 @@ | |||
| 551 | "Token counts may be inaccurate and provided just for reference.": "Счетчик токенов может быть неточным, используйте как ориентир", | 551 | "Token counts may be inaccurate and provided just for reference.": "Счетчик токенов может быть неточным, используйте как ориентир", |
| 552 | "Click to select a new avatar for this character": "Нажмите чтобы выбрать новый аватар для этого персонажа", | 552 | "Click to select a new avatar for this character": "Нажмите чтобы выбрать новый аватар для этого персонажа", |
| 553 | "Example: [{{user}} is a 28-year-old Romanian cat girl.]": "Пример:\n [{{user}} is a 28-year-old Romanian cat girl.]", | 553 | "Example: [{{user}} is a 28-year-old Romanian cat girl.]": "Пример:\n [{{user}} is a 28-year-old Romanian cat girl.]", |
| 554 | "Toggle grid view": "Переключить вид сетки", | 554 | "Toggle grid view": "Сменить вид сетки", |
| 555 | "Add to Favorites": "Добавить в Избранное", | 555 | "Add to Favorites": "Добавить в Избранное", |
| 556 | "Advanced Definition": "Расширенное описание", | 556 | "Advanced Definition": "Расширенное описание", |
| 557 | "Character Lore": "Лор персонажа", | 557 | "Character Lore": "Лор персонажа", |
| @@ -624,7 +624,7 @@ | |||
| 624 | "UI Theme": "Тема UI", | 624 | "UI Theme": "Тема UI", |
| 625 | "This message is invisible for the AI": "Это сообщение невидимо для ИИ", | 625 | "This message is invisible for the AI": "Это сообщение невидимо для ИИ", |
| 626 | "Sampler Priority": "Приоритет сэмплеров", | 626 | "Sampler Priority": "Приоритет сэмплеров", |
| 627 | "Ooba only. Determines the order of samplers.": "Только oobabooga. Определяет порядок сэмплеров.", | 627 | "Ooba only. Determines the order of samplers.": "Только для oobabooga. Определяет порядок сэмплеров.", |
| 628 | "Load default order": "Загрузить стандартный порядок", | 628 | "Load default order": "Загрузить стандартный порядок", |
| 629 | "Max Tokens Second": "Макс. кол-во токенов в секунду", | 629 | "Max Tokens Second": "Макс. кол-во токенов в секунду", |
| 630 | "CFG": "CFG", | 630 | "CFG": "CFG", |
| @@ -695,7 +695,7 @@ | |||
| 695 | "Medium": "Средний", | 695 | "Medium": "Средний", |
| 696 | "Aggressive": "Агрессивный", | 696 | "Aggressive": "Агрессивный", |
| 697 | "Very aggressive": "Очень агрессивный", | 697 | "Very aggressive": "Очень агрессивный", |
| 698 | "Eta_Cutoff_desc": "Eta cutoff - основной параметр специальной техники сэмплинга под названием Eta Sampling. В единицах 1e-4; разумное значение - 3. Установите в 0, чтобы отключить. См. статью Truncation Sampling as Language Model Desmoothing от Хьюитт и др. (2022) для получения подробной информации.", | 698 | "Eta_Cutoff_desc": "Eta cutoff - основной параметр специальной техники сэмплинга под названием Eta Sampling.\nВ единицах 1e-4; разумное значение - 3.\nУстановите в 0, чтобы отключить.\nСм. статью Truncation Sampling as Language Model Desmoothing от Хьюитт и др. (2022) для получения подробной информации.", |
| 699 | "Learn how to contribute your idle GPU cycles to the Horde": "Узнайте, как использовать время простоя вашего GPU для помощи Horde", | 699 | "Learn how to contribute your idle GPU cycles to the Horde": "Узнайте, как использовать время простоя вашего GPU для помощи Horde", |
| 700 | "Use the appropriate tokenizer for Google models via their API. Slower prompt processing, but offers much more accurate token counting.": "Используйте соответствующий токенизатор для моделей Google через их API. Медленная обработка подсказок, но предлагает намного более точный подсчет токенов.", | 700 | "Use the appropriate tokenizer for Google models via their API. Slower prompt processing, but offers much more accurate token counting.": "Используйте соответствующий токенизатор для моделей Google через их API. Медленная обработка подсказок, но предлагает намного более точный подсчет токенов.", |
| 701 | "Load koboldcpp order": "Загрузить порядок из koboldcpp", | 701 | "Load koboldcpp order": "Загрузить порядок из koboldcpp", |
| @@ -964,7 +964,7 @@ | |||
| 964 | "char_import_3": "Персонаж с JanitorAI (прямая ссылка или UUID)", | 964 | "char_import_3": "Персонаж с JanitorAI (прямая ссылка или UUID)", |
| 965 | "char_import_4": "Персонаж с Pygmalion.chat (прямая ссылка или UUID)", | 965 | "char_import_4": "Персонаж с Pygmalion.chat (прямая ссылка или UUID)", |
| 966 | "char_import_5": "Персонаж с AICharacterCards.com (прямая ссылка или ID)", | 966 | "char_import_5": "Персонаж с AICharacterCards.com (прямая ссылка или ID)", |
| 967 | "char_import_6": "Прямая ссылка на PNG-файл (чтобы узнать список разрешённых хостов, загляните в", | 967 | "char_import_6": "Прямая ссылка на PNG-файл (список разрешённых хостов находится в", |
| 968 | "char_import_7": ")", | 968 | "char_import_7": ")", |
| 969 | "Grammar String": "Грамматика", | 969 | "Grammar String": "Грамматика", |
| 970 | "GBNF or EBNF, depends on the backend in use. If you're using this you should know which.": "GBNF или EBNF, зависит от бэкенда. Если вы это используете, то, скорее всего, сами знаете, какой именно.", | 970 | "GBNF or EBNF, depends on the backend in use. If you're using this you should know which.": "GBNF или EBNF, зависит от бэкенда. Если вы это используете, то, скорее всего, сами знаете, какой именно.", |
| @@ -1016,7 +1016,7 @@ | |||
| 1016 | "prompt_manager_relative": "Относительная", | 1016 | "prompt_manager_relative": "Относительная", |
| 1017 | "prompt_manager_depth": "Глубина", | 1017 | "prompt_manager_depth": "Глубина", |
| 1018 | "Injection depth. 0 = after the last message, 1 = before the last message, etc.": "Глубина вставки. 0 = после последнего сообщения, 1 = перед последним сообщением, и т.д.", | 1018 | "Injection depth. 0 = after the last message, 1 = before the last message, etc.": "Глубина вставки. 0 = после последнего сообщения, 1 = перед последним сообщением, и т.д.", |
| 1019 | "The prompt to be sent.": "Отправляемый ИИ промпт.", | 1019 | "The prompt to be sent.": "Текст промпта.", |
| 1020 | "prompt_manager_forbid_overrides": "Запретить перезапись", | 1020 | "prompt_manager_forbid_overrides": "Запретить перезапись", |
| 1021 | "This prompt cannot be overridden by character cards, even if overrides are preferred.": "Карточка персонажа не сможет перезаписать этот промпт, даже если настройки отдают приоритет именно ей.", | 1021 | "This prompt cannot be overridden by character cards, even if overrides are preferred.": "Карточка персонажа не сможет перезаписать этот промпт, даже если настройки отдают приоритет именно ей.", |
| 1022 | "image_inlining_hint_1": "Отправлять картинки как часть промпта, если позволяет модель (такой функционал поддерживают GPT-4V, Claude 3 или Llava 13B). Чтобы добавить в чат изображение, используйте на нужном сообщении действие", | 1022 | "image_inlining_hint_1": "Отправлять картинки как часть промпта, если позволяет модель (такой функционал поддерживают GPT-4V, Claude 3 или Llava 13B). Чтобы добавить в чат изображение, используйте на нужном сообщении действие", |
| @@ -1232,7 +1232,7 @@ | |||
| 1232 | "Top P & Min P": "Top P & Min P", | 1232 | "Top P & Min P": "Top P & Min P", |
| 1233 | "llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.", | 1233 | "llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.", |
| 1234 | "Helps the model to associate messages with characters.": "Помогает модели связывать сообщения с персонажами.", | 1234 | "Helps the model to associate messages with characters.": "Помогает модели связывать сообщения с персонажами.", |
| 1235 | "character_names_default": "Except for groups and past personas. Otherwise, make sure you provide names in the prompt.", | 1235 | "character_names_default": "Добавлять префиксы для групповых чатов и предыдущих персон. В остальных случаях указывайте имена в промпте иными способами.", |
| 1236 | "Completion": "Completion Object", | 1236 | "Completion": "Completion Object", |
| 1237 | "character_names_completion": "Только латинские буквы, цифры и знак подчёркивания. Работает не для всех бэкендов, в частности для Claude, MistralAI, Google.", | 1237 | "character_names_completion": "Только латинские буквы, цифры и знак подчёркивания. Работает не для всех бэкендов, в частности для Claude, MistralAI, Google.", |
| 1238 | "Use AI21 Tokenizer": "Использовать токенайзер AI21", | 1238 | "Use AI21 Tokenizer": "Использовать токенайзер AI21", |
| @@ -1257,7 +1257,6 @@ | |||
| 1257 | "Peek a password": "Посмотреть пароль", | 1257 | "Peek a password": "Посмотреть пароль", |
| 1258 | "Clear your cookie": "Clear your cookie", | 1258 | "Clear your cookie": "Clear your cookie", |
| 1259 | "Add Chat Start and Example Separator to a list of stopping strings.": "Использовать Начало чата и Разделитель примеров сообщений в качестве стоп-строк.", | 1259 | "Add Chat Start and Example Separator to a list of stopping strings.": "Использовать Начало чата и Разделитель примеров сообщений в качестве стоп-строк.", |
| 1260 | "context_allow_jailbreak": "Если в карточке есть джейлбрейк И ПРИ ЭТОМ включена опция \"Приоритет джейлбрейку из карточки персонажа\", то этот джейлбрейк добавляется в конец промпта.\nНЕ РЕКОМЕНДУЕТСЯ ДЛЯ МОДЕЛЕЙ TEXT COMPLETION, МОЖЕТ ПОРТИТЬ ВЫХОДНОЙ ТЕКСТ.", | ||
| 1261 | "Context Order": "Context Order", | 1260 | "Context Order": "Context Order", |
| 1262 | "Summary": "Summary", | 1261 | "Summary": "Summary", |
| 1263 | "Example Dialogues": "Примеры диалогов", | 1262 | "Example Dialogues": "Примеры диалогов", |
| @@ -1278,7 +1277,7 @@ | |||
| 1278 | "Will be inserted as a last prompt line when using system/neutral generation.": "Will be inserted as a last prompt line when using system/neutral generation.", | 1277 | "Will be inserted as a last prompt line when using system/neutral generation.": "Will be inserted as a last prompt line when using system/neutral generation.", |
| 1279 | "If a stop sequence is generated, everything past it will be removed from the output (inclusive).": "Если ИИ генерирует стоп-строку, то всё после неё будет вырезано из ответа (включая и саму стоп-строку).", | 1278 | "If a stop sequence is generated, everything past it will be removed from the output (inclusive).": "Если ИИ генерирует стоп-строку, то всё после неё будет вырезано из ответа (включая и саму стоп-строку).", |
| 1280 | "Will be inserted at the start of the chat history if it doesn't start with a User message.": "Вставляется в начале истории чата, если она начинается не с сообщения пользователя.", | 1279 | "Will be inserted at the start of the chat history if it doesn't start with a User message.": "Вставляется в начале истории чата, если она начинается не с сообщения пользователя.", |
| 1281 | "Global World Info/Lorebook activation settings": "Настройки активации глобального лорбука / Информации о мире", | 1280 | "Global World Info/Lorebook activation settings": "Глобальные настройки активации лорбука / Информации о мире", |
| 1282 | "Click to expand": "Щёлкните, чтобы развернуть", | 1281 | "Click to expand": "Щёлкните, чтобы развернуть", |
| 1283 | "Insertion Strategy": "Как инжектить", | 1282 | "Insertion Strategy": "Как инжектить", |
| 1284 | "Only the entries with the most number of key matches will be selected for Inclusion Group filtering": "Only the entries with the most number of key matches will be selected for Inclusion Group filtering", | 1283 | "Only the entries with the most number of key matches will be selected for Inclusion Group filtering": "Only the entries with the most number of key matches will be selected for Inclusion Group filtering", |
| @@ -1647,10 +1646,9 @@ | |||
| 1647 | "mui_reset": "Сброс", | 1646 | "mui_reset": "Сброс", |
| 1648 | "Quick 'Impersonate' button": "Быстрое перевоплощение", | 1647 | "Quick 'Impersonate' button": "Быстрое перевоплощение", |
| 1649 | "Show a button in the input area to ask the AI to impersonate your character for a single message": "Показать в поле ввода кнопку, по нажатии на которую ИИ сгенерирует одно сообщение от лица вашего персонажа.", | 1648 | "Show a button in the input area to ask the AI to impersonate your character for a single message": "Показать в поле ввода кнопку, по нажатии на которую ИИ сгенерирует одно сообщение от лица вашего персонажа.", |
| 1650 | "Separators as Stop Strings": "Разделители как стоп-строки", | 1649 | "Separators as Stop Strings": "Разделители в качестве стоп-строк", |
| 1651 | "Names as Stop Strings": "Имена как стоп-строки", | 1650 | "Names as Stop Strings": "Имена в качестве стоп-строк", |
| 1652 | "Add Character and User names to a list of stopping strings.": "Добавлять имена персонажа и пользователя в список стоп-строк.", | 1651 | "Add Character and User names to a list of stopping strings.": "Добавлять имена персонажа и пользователя в список стоп-строк.", |
| 1653 | "Allow Post-History Instructions": "Разрешить инструкции после истории", | ||
| 1654 | "context_allow_post_history_instructions": "Добавлять в конец промпта инструкции после истории. Работает только при наличии таких инструкций в карточке И при включенной опции ''Приоритет инструкциям из карточек''.\nНЕ РЕКОМЕНДУЕТСЯ ДЛЯ МОДЕЛЕЙ TEXT COMPLETION, МОЖЕТ ПОРТИТЬ ВЫХОДНОЙ ТЕКСТ.", | 1652 | "context_allow_post_history_instructions": "Добавлять в конец промпта инструкции после истории. Работает только при наличии таких инструкций в карточке И при включенной опции ''Приоритет инструкциям из карточек''.\nНЕ РЕКОМЕНДУЕТСЯ ДЛЯ МОДЕЛЕЙ TEXT COMPLETION, МОЖЕТ ПОРТИТЬ ВЫХОДНОЙ ТЕКСТ.", |
| 1655 | "First User Prefix": "Первый префикс пользователя", | 1653 | "First User Prefix": "Первый префикс пользователя", |
| 1656 | "Inserted before the first User's message.": "Вставляется перед первым сообщением пользователя.", | 1654 | "Inserted before the first User's message.": "Вставляется перед первым сообщением пользователя.", |
| @@ -1916,8 +1914,8 @@ | |||
| 1916 | "Cannot restore GUI preset": "Пресет для Gui восстановить нельзя", | 1914 | "Cannot restore GUI preset": "Пресет для Gui восстановить нельзя", |
| 1917 | "Default preset cannot be restored": "Невозможно восстановить пресет по умолчанию", | 1915 | "Default preset cannot be restored": "Невозможно восстановить пресет по умолчанию", |
| 1918 | "Default template cannot be restored": "Невозможно восстановить шаблон по умолчанию", | 1916 | "Default template cannot be restored": "Невозможно восстановить шаблон по умолчанию", |
| 1919 | "Resetting a <b>default preset</b> will restore the default settings": "Сброс <b>стандартного пресета</b> восстановит настройки по умолчанию.", | 1917 | "Resetting a <b>default preset</b> will restore the default settings.": "Сброс <b>комплектного пресета</b> восстановит настройки по умолчанию.", |
| 1920 | "Resetting a <b>default template</b> will restore the default settings.": "Сброс <b>стандартного шаблона</b> восстановит настройки по умолчанию.", | 1918 | "Resetting a <b>default template</b> will restore the default settings.": "Сброс <b>комплектного шаблона</b> восстановит настройки по умолчанию.", |
| 1921 | "Are you sure?": "Вы уверены?", | 1919 | "Are you sure?": "Вы уверены?", |
| 1922 | "Default preset restored": "Стандартный пресет восстановлен", | 1920 | "Default preset restored": "Стандартный пресет восстановлен", |
| 1923 | "Default template restored": "Стандартный шаблон восстановлен", | 1921 | "Default template restored": "Стандартный шаблон восстановлен", |
| @@ -2048,11 +2046,11 @@ | |||
| 2048 | "prompt_post_processing_merge": "Объединять идущие подряд сообщения с одной ролью", | 2046 | "prompt_post_processing_merge": "Объединять идущие подряд сообщения с одной ролью", |
| 2049 | "prompt_post_processing_semi": "Semi-strict (чередовать роли)", | 2047 | "prompt_post_processing_semi": "Semi-strict (чередовать роли)", |
| 2050 | "prompt_post_processing_strict": "Strict (чередовать роли, сначала пользователь)", | 2048 | "prompt_post_processing_strict": "Strict (чередовать роли, сначала пользователь)", |
| 2051 | "Select Horde models": "Выбрать модель из Horde", | 2049 | "Select Horde models": "Выберите модель из Horde", |
| 2052 | "Model ID (optional)": "Идентификатор модели (необязательно)", | 2050 | "Model ID (optional)": "Идентификатор модели (необязательно)", |
| 2053 | "Derive context size from backend": "Использовать бэкенд для определения размера контекста", | 2051 | "Derive context size from backend": "Использовать бэкенд для определения размера контекста", |
| 2054 | "Rename current preset": "Переименовать пресет", | 2052 | "Rename current preset": "Переименовать пресет", |
| 2055 | "No Worlds active. Click here to select.": "Нет активных миров. Нажмите, чтобы выбрать.", | 2053 | "No Worlds active. Click here to select.": "Активных миров нет, ЛКМ для выбора.", |
| 2056 | "Title/Memo": "Название", | 2054 | "Title/Memo": "Название", |
| 2057 | "Strategy": "Статус", | 2055 | "Strategy": "Статус", |
| 2058 | "Position": "Позиция", | 2056 | "Position": "Позиция", |
| @@ -2171,7 +2169,7 @@ | |||
| 2171 | "instruct_derived": "Считывать из метаданных модели (по возможности)", | 2169 | "instruct_derived": "Считывать из метаданных модели (по возможности)", |
| 2172 | "Confirm token parsing with": "Чтобы убедиться в правильности выделения токенов, используйте", | 2170 | "Confirm token parsing with": "Чтобы убедиться в правильности выделения токенов, используйте", |
| 2173 | "Reasoning Effort": "Рассуждения", | 2171 | "Reasoning Effort": "Рассуждения", |
| 2174 | "Constrains effort on reasoning for reasoning models.": "Регулирует объём внутренних рассуждений модели (reasoning), для моделей которые поддерживают эту возможность.\nНа данный момент поддерживаются три значения: Подробные, Обычные, Поверхностные.\nПри менее подробном рассуждении ответ получается быстрее, а также экономятся токены, уходящие на рассуждения.", | 2172 | "Constrains effort on reasoning for reasoning models.": "Регулирует объём внутренних рассуждений модели (reasoning), для моделей, которые поддерживают эту возможность.\nПри менее подробном рассуждении ответ получается быстрее, а также экономятся токены, уходящие на рассуждения.", |
| 2175 | "openai_reasoning_effort_low": "Поверхностные", | 2173 | "openai_reasoning_effort_low": "Поверхностные", |
| 2176 | "openai_reasoning_effort_medium": "Обычные", | 2174 | "openai_reasoning_effort_medium": "Обычные", |
| 2177 | "openai_reasoning_effort_high": "Подробные", | 2175 | "openai_reasoning_effort_high": "Подробные", |
| @@ -2276,8 +2274,8 @@ | |||
| 2276 | "Persona Name Not Set": "У персоны отсутствует имя", | 2274 | "Persona Name Not Set": "У персоны отсутствует имя", |
| 2277 | "You must bind a name to this persona before you can set a lorebook.": "Перед привязкой лорбука персоне необходимо присвоить имя.", | 2275 | "You must bind a name to this persona before you can set a lorebook.": "Перед привязкой лорбука персоне необходимо присвоить имя.", |
| 2278 | "Default Persona Removed": "Персона по умолчанию снята", | 2276 | "Default Persona Removed": "Персона по умолчанию снята", |
| 2279 | "Persona is locked to the current character": "Персона закреплена за этим персонажем", | 2277 | "Persona is locked to the current character": "Персона закреплена за текущим персонажем", |
| 2280 | "Persona is locked to the current chat": "Персона закреплена за этим чатом", | 2278 | "Persona is locked to the current chat": "Персона закреплена за текущим чатом", |
| 2281 | "characters": "перс.", | 2279 | "characters": "перс.", |
| 2282 | "character": "персонаж", | 2280 | "character": "персонаж", |
| 2283 | "in this group": "в группе", | 2281 | "in this group": "в группе", |
| @@ -2338,5 +2336,88 @@ | |||
| 2338 | "Reasoning already exists.": "Рассуждения уже присутствуют.", | 2336 | "Reasoning already exists.": "Рассуждения уже присутствуют.", |
| 2339 | "Edit Message": "Редактирование", | 2337 | "Edit Message": "Редактирование", |
| 2340 | "Status check bypassed": "Проверка статуса отключена", | 2338 | "Status check bypassed": "Проверка статуса отключена", |
| 2341 | "Valid": "Работает" | 2339 | "Valid": "Работает", |
| 2340 | "Use Group Scoring": "Использовать Group Scoring", | ||
| 2341 | "Only the entries with the most number of key matches will be selected for Inclusion Group filtering": "До групповых фильтров будут допущены только записи с наибольшим кол-вом совпадений", | ||
| 2342 | "Can be used to automatically activate Quick Replies": "Используется для автоматической активации быстрых ответов (Quick Replies)", | ||
| 2343 | "( None )": "(Отсутствует)", | ||
| 2344 | "Tie this entry to specific characters or characters with specific tags": "Привязать запись к опред. персонажам или персонажам с заданными тегами", | ||
| 2345 | "Move Entry to Another Lorebook": "Переместить запись в другой лорбук", | ||
| 2346 | "There are no other lorebooks to move to.": "Некуда перемещать: не найдено других лорбуков.", | ||
| 2347 | "Select Target Lorebook": "Выберите куда переместить", | ||
| 2348 | "Move '${0}' to:": "Переместить '${0}' в:", | ||
| 2349 | "Please select a target lorebook.": "Выберите лорбук, в который будет перемещена запись.", | ||
| 2350 | "Scan depth cannot be negative": "Глубина сканирования не может быть отрицательной", | ||
| 2351 | "Scan depth cannot exceed ${0}": "Глубина сканирования не может превышать ${0}", | ||
| 2352 | "Select your current Reasoning Template": "Выберите текущий Шаблон рассуждений", | ||
| 2353 | "Delete template": "Удалить шаблон", | ||
| 2354 | "Reasoning Template": "Шаблон рассуждений", | ||
| 2355 | "openai_reasoning_effort_auto": "Авто", | ||
| 2356 | "openai_reasoning_effort_minimum": "Минимальные", | ||
| 2357 | "openai_reasoning_effort_maximum": "Максимальные", | ||
| 2358 | "OpenAI-style options: low, medium, high. Minimum and maximum are aliased to low and high. Auto does not send an effort level.": "OpenAI принимает следующее: low (Поверхностные), medium (Обычные), high (Подробные). Minimum (Минимальные) - то же самое, что low. Maximum (Максимальные) - то же самое, что high. При выборе Auto (Авто) значение не отсылается вообще.", | ||
| 2359 | "Allocates a portion of the response length for thinking (low: 10%, medium: 25%, high: 50%). Other options are model-dependent.": "Резервирует часть ответа для рассуждений (Поверхностные: 10% ответа, Обычные: 25%, Подробные: 50%). Остальные значения зависят от конкретной модели.", | ||
| 2360 | "xAI Model": "Модель xAI", | ||
| 2361 | "xAI API Key": "Ключ от API xAI", | ||
| 2362 | "HuggingFace Token": "Токен HuggingFace", | ||
| 2363 | "Endpoint URL": "Адрес эндпоинта", | ||
| 2364 | "Example: https://****.endpoints.huggingface.cloud": "Пример: https://****.endpoints.huggingface.cloud", | ||
| 2365 | "Featherless Model Selection": "Выбор модели из Featherless", | ||
| 2366 | "category": "категория", | ||
| 2367 | "Top": "Топовые", | ||
| 2368 | "All Classes": "Все классы", | ||
| 2369 | "Date Asc": "Дата, возрастание", | ||
| 2370 | "Date Desc": "Дата, убывание", | ||
| 2371 | "Background Image": "Фоновое изображение", | ||
| 2372 | "Delete the background?": "Удалить фон?", | ||
| 2373 | "Tags_as_Folders_desc": "Чтобы тег отображался как папка, его нужно отметить таковым в меню управления тегами. Нажмите сюда, чтобы открыть его.", | ||
| 2374 | "tag_entries": "раз исп.", | ||
| 2375 | "Multiple personas are connected to this character.\nSelect a persona to use for this chat.": "К этому персонажу привязано несколько персон.\nВыберите персону, которую хотите использовать в этом чате.", | ||
| 2376 | "Select Persona": "Выберите персону", | ||
| 2377 | "Completion Object": "Как часть Completion Object", | ||
| 2378 | "Move ${0} to:": "Переместить '${0}' в:", | ||
| 2379 | "Chat Scenario Override": "Перезапись сценария чата", | ||
| 2380 | "Unique to this chat.": "Действует только в рамках текущего чата.", | ||
| 2381 | "All group members will use the following scenario text instead of what is specified in their character cards.": "Все участники группы будут использовать этот сценарий вместо того, который указан в карточке.", | ||
| 2382 | "Checkpoints inherit the scenario override from their parent, and can be changed individually after that.": "Чекпоинты наследуют сценарий родителя, после отделения его можно менять.", | ||
| 2383 | "Delete Tag": "Удалить тег", | ||
| 2384 | "Do you want to delete the tag": "Вы точно хотите удалить тег", | ||
| 2385 | "If you want to merge all references to this tag into another tag, select it below:": "Если хотите заменить ссылки на этот тег на какой-то другой, то выберите из списка:", | ||
| 2386 | "Open Folder (Show all characters even if not selected)": "Открытая папка (показать всех персонажей, включая невыбранных)", | ||
| 2387 | "Closed Folder (Hide all characters unless selected)": "Закрытая папка (скрыть всех персонажей, кроме выбранных)", | ||
| 2388 | "No Folder": "Не папка", | ||
| 2389 | "Show only favorites": "Показать только избранных персонажей", | ||
| 2390 | "Show only groups": "Показать только группы", | ||
| 2391 | "Show only folders": "Показать только папки", | ||
| 2392 | "Manage tags": "Панель управления тегами", | ||
| 2393 | "Show Tag List": "Показать список тегов", | ||
| 2394 | "Clear all filters": "Сбросить все фильтры", | ||
| 2395 | "There are no items to display.": "Отображать абсолютно нечего.", | ||
| 2396 | "Characters and groups hidden by filters or closed folders": "Персонажи и группы скрыты настройками фильтров либо закрытыми папками", | ||
| 2397 | "Otterly empty": "Всё что можно, всё выдрано", | ||
| 2398 | "Here be dragons": "Список настолько очистился, что в него вернулись драконы", | ||
| 2399 | "Kiwibunga": "Настолько пусто, что киви прилетела посидеть", | ||
| 2400 | "Pump-a-Rum": "Пу-пу-пу", | ||
| 2401 | "Croak it": "Только кваканье лягушек и стрёкот сверчков", | ||
| 2402 | "${0} character hidden.": "Персонажей скрыто: ${0}.", | ||
| 2403 | "${0} characters hidden.": "Персонажей скрыто: ${0}.", | ||
| 2404 | "/ page": "/ стр.", | ||
| 2405 | "Context Length": "Размер контекста", | ||
| 2406 | "Added On": "Добавлена", | ||
| 2407 | "Class": "Класс", | ||
| 2408 | "Bulk_edit_characters": "Массовое редактирование персонажей\n\nЛКМ, чтобы выделить либо отменить выделение персонажа\nShift+ЛКМ, чтобы массово выделить либо отменить выделение персонажей\nПКМ, чтобы выбрать действие", | ||
| 2409 | "Bulk select all characters": "Выбрать всех персонажей", | ||
| 2410 | "Duplicate": "Клонировать", | ||
| 2411 | "Next page": "След. страница", | ||
| 2412 | "Previous page": "Пред. страница", | ||
| 2413 | "Group: ${0}": "Группа: ${0}", | ||
| 2414 | "You deleted a character/chat and arrived back here for safety reasons! Pick another character!": "Вы удалили персонажа или чат, и мы из соображений безопасности перенесли вас на эту страницу! Выберите другого персонажа!", | ||
| 2415 | "Group is empty.": "Группа пуста.", | ||
| 2416 | "No characters available": "Персонажей нет", | ||
| 2417 | "Choose what to export": "Выберите, что экспортировать", | ||
| 2418 | "Text Completion Preset": "Пресет для режима Text Completion", | ||
| 2419 | "Update enabled": "Обновить включенные", | ||
| 2420 | "Could not connect to API": "Не удалось подключиться к API", | ||
| 2421 | "Connected to API": "Соединение с API установлено", | ||
| 2422 | "Go back": "Назад" | ||
| 2342 | } | 2423 | } |
| @@ -411,7 +411,6 @@ | |||
| 411 | "Chat Start": "Початок чату", | 411 | "Chat Start": "Початок чату", |
| 412 | "Add Chat Start and Example Separator to a list of stopping strings.": "Додайте початок чату та роздільник прикладів до списку рядків зупинки.", | 412 | "Add Chat Start and Example Separator to a list of stopping strings.": "Додайте початок чату та роздільник прикладів до списку рядків зупинки.", |
| 413 | "Use as Stop Strings": "Використовувати як рядки зупинки", | 413 | "Use as Stop Strings": "Використовувати як рядки зупинки", |
| 414 | "context_allow_jailbreak": "Включає втечу з в’язниці в кінці підказки, якщо визначено в картці символів ТА «Переважати символ. Втечу з в'язниці'' увімкнено.\nЦЕ НЕ РЕКОМЕНДУЄТЬСЯ ДЛЯ МОДЕЛЕЙ ЗАВЕРШЕННЯ ТЕКСТУ, МОЖЕ ПРИЗВЕСТИ ДО ПОГАНОГО РЕЗУЛЬТАТУ.", | ||
| 415 | "Allow Jailbreak": "Дозволити втечу з в'язниці", | 414 | "Allow Jailbreak": "Дозволити втечу з в'язниці", |
| 416 | "Context Order": "Порядок контексту", | 415 | "Context Order": "Порядок контексту", |
| 417 | "Summary": "Резюме", | 416 | "Summary": "Резюме", |
| @@ -411,7 +411,6 @@ | |||
| 411 | "Chat Start": "Bắt đầu Chat", | 411 | "Chat Start": "Bắt đầu Chat", |
| 412 | "Add Chat Start and Example Separator to a list of stopping strings.": "Thêm Bắt đầu trò chuyện và Dấu phân cách ví dụ vào danh sách các chuỗi dừng.", | 412 | "Add Chat Start and Example Separator to a list of stopping strings.": "Thêm Bắt đầu trò chuyện và Dấu phân cách ví dụ vào danh sách các chuỗi dừng.", |
| 413 | "Use as Stop Strings": "Sử dụng như chuỗi dừng", | 413 | "Use as Stop Strings": "Sử dụng như chuỗi dừng", |
| 414 | "context_allow_jailbreak": "Bao gồm Bẻ khóa ở cuối Prompt, nếu được xác định trong thẻ ký tự VÀ ''Thích Char. Bẻ khóa'' được bật.\nĐIỀU NÀY KHÔNG ĐƯỢC KHUYẾN NGHỊ CHO CÁC MÔ HÌNH HOÀN THÀNH VĂN BẢN, CÓ THỂ DẪN ĐẾN ĐẦU RA XẤU.", | ||
| 415 | "Allow Jailbreak": "Cho phép bẻ khóa", | 414 | "Allow Jailbreak": "Cho phép bẻ khóa", |
| 416 | "Context Order": "Thứ tự bối cảnh", | 415 | "Context Order": "Thứ tự bối cảnh", |
| 417 | "Summary": "Bản tóm tắt", | 416 | "Summary": "Bản tóm tắt", |
| @@ -504,7 +504,6 @@ | |||
| 504 | "Add Character and User names to a list of stopping strings.": "将角色和用户名添加到停止字符串列表中。", | 504 | "Add Character and User names to a list of stopping strings.": "将角色和用户名添加到停止字符串列表中。", |
| 505 | "Names as Stop Strings": "名称作为终止字符串", | 505 | "Names as Stop Strings": "名称作为终止字符串", |
| 506 | "context_allow_post_history_instructions": "如果在角色卡中定义并且启用了“首选角色卡说明”,则在提示末尾包含后历史说明。\n不建议在文本补全模型中使用此功能,否则会导致输出错误。", | 506 | "context_allow_post_history_instructions": "如果在角色卡中定义并且启用了“首选角色卡说明”,则在提示末尾包含后历史说明。\n不建议在文本补全模型中使用此功能,否则会导致输出错误。", |
| 507 | "Allow Post-History Instructions": "允许后历史说明", | ||
| 508 | "Instruct Template": "指导模板", | 507 | "Instruct Template": "指导模板", |
| 509 | "instruct_derived": "如果可能,从模型元数据中获取", | 508 | "instruct_derived": "如果可能,从模型元数据中获取", |
| 510 | "instruct_bind_to_context": "如果启用,上下文模板将根据所选的指导模板名称或偏好自动选择。", | 509 | "instruct_bind_to_context": "如果启用,上下文模板将根据所选的指导模板名称或偏好自动选择。", |
| @@ -1358,7 +1357,7 @@ | |||
| 1358 | "Image Captioning": "图像描述", | 1357 | "Image Captioning": "图像描述", |
| 1359 | "Source": "来源", | 1358 | "Source": "来源", |
| 1360 | "Local": "本地", | 1359 | "Local": "本地", |
| 1361 | "Multimodal (OpenAI / Anthropic / llama / Google)": "多模式(OpenAI / Anthropic / llama / Google)", | 1360 | "Multimodal (OpenAI / Anthropic / llama / Google)": "多模态(OpenAI / Anthropic / llama / Google)", |
| 1362 | "Extras": "更多", | 1361 | "Extras": "更多", |
| 1363 | "Horde": "Horde", | 1362 | "Horde": "Horde", |
| 1364 | "API": "API", | 1363 | "API": "API", |
| @@ -1588,8 +1587,8 @@ | |||
| 1588 | "sd_function_tool_txt": "Use function tool", | 1587 | "sd_function_tool_txt": "Use function tool", |
| 1589 | "sd_interactive_mode": "发送消息时自动生成图像,例如“给我发一张猫的照片”。", | 1588 | "sd_interactive_mode": "发送消息时自动生成图像,例如“给我发一张猫的照片”。", |
| 1590 | "sd_interactive_mode_txt": "交互模式", | 1589 | "sd_interactive_mode_txt": "交互模式", |
| 1591 | "sd_multimodal_captioning": "使用多模式字幕根据用户和角色的头像生成提示词。", | 1590 | "sd_multimodal_captioning": "使用多模态字幕根据用户和角色的头像生成提示词。", |
| 1592 | "sd_multimodal_captioning_txt": "使用多模式字幕来描绘肖像", | 1591 | "sd_multimodal_captioning_txt": "使用多模态字幕来描绘肖像", |
| 1593 | "sd_free_extend": "使用当前选择的 LLM 自动扩展自由模式主题提示(不是肖像或背景)。", | 1592 | "sd_free_extend": "使用当前选择的 LLM 自动扩展自由模式主题提示(不是肖像或背景)。", |
| 1594 | "sd_free_extend_txt": "延长自由模式提示", | 1593 | "sd_free_extend_txt": "延长自由模式提示", |
| 1595 | "sd_free_extend_small": "(交互/命令)", | 1594 | "sd_free_extend_small": "(交互/命令)", |
| @@ -2109,5 +2108,25 @@ | |||
| 2109 | "Title/Memo": "标题(备忘)", | 2108 | "Title/Memo": "标题(备忘)", |
| 2110 | "Strategy": "触发策略", | 2109 | "Strategy": "触发策略", |
| 2111 | "Position": "插入位置", | 2110 | "Position": "插入位置", |
| 2112 | "Trigger %": "触发概率%" | 2111 | "Trigger %": "触发概率%", |
| 2112 | "Generate Caption": "生成图片描述", | ||
| 2113 | "(DEPRECATED)": "(已弃用)", | ||
| 2114 | "[Currently loaded]": "[当前加载]", | ||
| 2115 | "Change Persona Image": "更改角色图片", | ||
| 2116 | "Delete Persona": "删除角色", | ||
| 2117 | "Duplicate Persona": "复制角色", | ||
| 2118 | "Enter a name for this persona:": "输入角色名", | ||
| 2119 | "Enable web search": "启用联网搜索", | ||
| 2120 | "Current Persona": "当前角色", | ||
| 2121 | "Global Settings": "全局设置", | ||
| 2122 | "Select a model": "选择模型", | ||
| 2123 | "Thinking...": "思考中", | ||
| 2124 | "Valid": "有效", | ||
| 2125 | "Rename Persona": "重命名角色", | ||
| 2126 | "Sort By: Name (Z-A)": "排序: 名称(Z-A)", | ||
| 2127 | "Sort By: Name (A-Z)": "排序: 名称(A-Z)", | ||
| 2128 | "Sort By: Date (Oldest First)": "排序: 日期(从最远到最新)", | ||
| 2129 | "Sort By: Date (Newest First)": "排序: 日期(从最新到最远)", | ||
| 2130 | "Set the reasoning block of a message. Returns the reasoning block content.": "设置消息的推理块。返回推理块内容。", | ||
| 2131 | "Select providers. No selection = all providers.": "选择服务商。未选择 = 所有服务商。" | ||
| 2113 | } | 2132 | } |
| @@ -412,7 +412,6 @@ | |||
| 412 | "Chat Start": "聊天開始符號", | 412 | "Chat Start": "聊天開始符號", |
| 413 | "Add Chat Start and Example Separator to a list of stopping strings.": "將聊天開始和範例分隔符號加入終止字串中。", | 413 | "Add Chat Start and Example Separator to a list of stopping strings.": "將聊天開始和範例分隔符號加入終止字串中。", |
| 414 | "Use as Stop Strings": "用作停止字串", | 414 | "Use as Stop Strings": "用作停止字串", |
| 415 | "context_allow_jailbreak": "如果在角色卡中定義了越獄,且啟用了「角色卡越獄優先」,則會在提示詞的結尾加入越獄內容。\n這不建議用於文字完成模型,因為可能導致不良的輸出結果。", | ||
| 416 | "Allow Jailbreak": "允許越獄", | 415 | "Allow Jailbreak": "允許越獄", |
| 417 | "Context Order": "上下文順序", | 416 | "Context Order": "上下文順序", |
| 418 | "Summary": "摘要", | 417 | "Summary": "摘要", |
| @@ -1555,7 +1554,6 @@ | |||
| 1555 | "All": "全部", | 1554 | "All": "全部", |
| 1556 | "Allow fallback models": "允許回退模型", | 1555 | "Allow fallback models": "允許回退模型", |
| 1557 | "Allow fallback providers": "允許回退供應商", | 1556 | "Allow fallback providers": "允許回退供應商", |
| 1558 | "Allow Post-History Instructions": "允許聊天歷史後指示", | ||
| 1559 | "Allow reverse proxy": "允許反向代理", | 1557 | "Allow reverse proxy": "允許反向代理", |
| 1560 | "Alternate Greeting #": "備選問候語 #", | 1558 | "Alternate Greeting #": "備選問候語 #", |
| 1561 | "alternate_greetings_hint_1": "點選", | 1559 | "alternate_greetings_hint_1": "點選", |
| @@ -50,6 +50,7 @@ import { | |||
| 50 | importWorldInfo, | 50 | importWorldInfo, |
| 51 | wi_anchor_position, | 51 | wi_anchor_position, |
| 52 | world_info_include_names, | 52 | world_info_include_names, |
| 53 | initWorldInfo, | ||
| 53 | } from './scripts/world-info.js'; | 54 | } from './scripts/world-info.js'; |
| 54 | 55 | ||
| 55 | import { | 56 | import { |
| @@ -142,6 +143,7 @@ import { | |||
| 142 | getHordeModels, | 143 | getHordeModels, |
| 143 | adjustHordeGenerationParams, | 144 | adjustHordeGenerationParams, |
| 144 | MIN_LENGTH, | 145 | MIN_LENGTH, |
| 146 | initHorde, | ||
| 145 | } from './scripts/horde.js'; | 147 | } from './scripts/horde.js'; |
| 146 | 148 | ||
| 147 | import { | 149 | import { |
| @@ -174,6 +176,9 @@ import { | |||
| 174 | saveBase64AsFile, | 176 | saveBase64AsFile, |
| 175 | uuidv4, | 177 | uuidv4, |
| 176 | equalsIgnoreCaseAndAccents, | 178 | equalsIgnoreCaseAndAccents, |
| 179 | localizePagination, | ||
| 180 | renderPaginationDropdown, | ||
| 181 | paginationDropdownChangeHandler, | ||
| 177 | } from './scripts/utils.js'; | 182 | } from './scripts/utils.js'; |
| 178 | import { debounce_timeout, IGNORE_SYMBOL } from './scripts/constants.js'; | 183 | import { debounce_timeout, IGNORE_SYMBOL } from './scripts/constants.js'; |
| 179 | 184 | ||
| @@ -651,7 +656,7 @@ export const extension_prompt_roles = { | |||
| 651 | ASSISTANT: 2, | 656 | ASSISTANT: 2, |
| 652 | }; | 657 | }; |
| 653 | 658 | ||
| 654 | export const MAX_INJECTION_DEPTH = 1000; | 659 | export const MAX_INJECTION_DEPTH = 10000; |
| 655 | 660 | ||
| 656 | const SAFETY_CHAT = [ | 661 | const SAFETY_CHAT = [ |
| 657 | { | 662 | { |
| @@ -992,6 +997,8 @@ async function firstLoadInit() { | |||
| 992 | initBackgrounds(); | 997 | initBackgrounds(); |
| 993 | initAuthorsNote(); | 998 | initAuthorsNote(); |
| 994 | await initPersonas(); | 999 | await initPersonas(); |
| 1000 | initWorldInfo(); | ||
| 1001 | initHorde(); | ||
| 995 | initRossMods(); | 1002 | initRossMods(); |
| 996 | initStats(); | 1003 | initStats(); |
| 997 | initCfg(); | 1004 | initCfg(); |
| @@ -1421,30 +1428,26 @@ function getBackBlock() { | |||
| 1421 | return template; | 1428 | return template; |
| 1422 | } | 1429 | } |
| 1423 | 1430 | ||
| 1424 | function getEmptyBlock() { | 1431 | async function getEmptyBlock() { |
| 1425 | const icons = ['fa-dragon', 'fa-otter', 'fa-kiwi-bird', 'fa-crow', 'fa-frog']; | 1432 | const icons = ['fa-dragon', 'fa-otter', 'fa-kiwi-bird', 'fa-crow', 'fa-frog']; |
| 1426 | const texts = ['Here be dragons', 'Otterly empty', 'Kiwibunga', 'Pump-a-Rum', 'Croak it']; | 1433 | const texts = [t`Here be dragons`, t`Otterly empty`, t`Kiwibunga`, t`Pump-a-Rum`, t`Croak it`]; |
| 1427 | const roll = new Date().getMinutes() % icons.length; | 1434 | const roll = new Date().getMinutes() % icons.length; |
| 1428 | const emptyBlock = ` | 1435 | const params = { |
| 1429 | <div class="text_block empty_block"> | 1436 | text: texts[roll], |
| 1430 | <i class="fa-solid ${icons[roll]} fa-4x"></i> | 1437 | icon: icons[roll], |
| 1431 | <h1>${texts[roll]}</h1> | 1438 | }; |
| 1432 | <p>There are no items to display.</p> | 1439 | const emptyBlock = await renderTemplateAsync('emptyBlock', params); |
| 1433 | </div>`; | ||
| 1434 | return $(emptyBlock); | 1440 | return $(emptyBlock); |
| 1435 | } | 1441 | } |
| 1436 | 1442 | ||
| 1437 | /** | 1443 | /** |
| 1438 | * @param {number} hidden Number of hidden characters | 1444 | * @param {number} hidden Number of hidden characters |
| 1439 | */ | 1445 | */ |
| 1440 | function getHiddenBlock(hidden) { | 1446 | async function getHiddenBlock(hidden) { |
| 1441 | const hiddenBlock = ` | 1447 | const params = { |
| 1442 | <div class="text_block hidden_block"> | 1448 | text: (hidden > 1 ? t`${hidden} characters hidden.` : t`${hidden} character hidden.`), |
| 1443 | <small> | 1449 | }; |
| 1444 | <p>${hidden} ${hidden > 1 ? 'characters' : 'character'} hidden.</p> | 1450 | const hiddenBlock = await renderTemplateAsync('hiddenBlock', params); |
| 1445 | <div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]Characters and groups hidden by filters or closed folders" title="Characters and groups hidden by filters or closed folders"></div> | ||
| 1446 | </small> | ||
| 1447 | </div>`; | ||
| 1448 | return $(hiddenBlock); | 1451 | return $(hiddenBlock); |
| 1449 | } | 1452 | } |
| 1450 | 1453 | ||
| @@ -1524,10 +1527,11 @@ export async function printCharacters(fullRefresh = false) { | |||
| 1524 | 1527 | ||
| 1525 | const entities = getEntitiesList({ doFilter: true }); | 1528 | const entities = getEntitiesList({ doFilter: true }); |
| 1526 | 1529 | ||
| 1530 | const pageSize = Number(accountStorage.getItem(storageKey)) || per_page_default; | ||
| 1531 | const sizeChangerOptions = [10, 25, 50, 100, 250, 500, 1000]; | ||
| 1527 | $('#rm_print_characters_pagination').pagination({ | 1532 | $('#rm_print_characters_pagination').pagination({ |
| 1528 | dataSource: entities, | 1533 | dataSource: entities, |
| 1529 | pageSize: Number(accountStorage.getItem(storageKey)) || per_page_default, | 1534 | pageSize, |
| 1530 | sizeChangerOptions: [10, 25, 50, 100, 250, 500, 1000], | ||
| 1531 | pageRange: 1, | 1535 | pageRange: 1, |
| 1532 | pageNumber: saveCharactersPage || 1, | 1536 | pageNumber: saveCharactersPage || 1, |
| 1533 | position: 'top', | 1537 | position: 'top', |
| @@ -1536,14 +1540,16 @@ export async function printCharacters(fullRefresh = false) { | |||
| 1536 | prevText: '<', | 1540 | prevText: '<', |
| 1537 | nextText: '>', | 1541 | nextText: '>', |
| 1538 | formatNavigator: PAGINATION_TEMPLATE, | 1542 | formatNavigator: PAGINATION_TEMPLATE, |
| 1543 | formatSizeChanger: renderPaginationDropdown(pageSize, sizeChangerOptions), | ||
| 1539 | showNavigator: true, | 1544 | showNavigator: true, |
| 1540 | callback: function (/** @type {Entity[]} */ data) { | 1545 | callback: async function (/** @type {Entity[]} */ data) { |
| 1541 | $(listId).empty(); | 1546 | $(listId).empty(); |
| 1542 | if (power_user.bogus_folders && isBogusFolderOpen()) { | 1547 | if (power_user.bogus_folders && isBogusFolderOpen()) { |
| 1543 | $(listId).append(getBackBlock()); | 1548 | $(listId).append(getBackBlock()); |
| 1544 | } | 1549 | } |
| 1545 | if (!data.length) { | 1550 | if (!data.length) { |
| 1546 | $(listId).append(getEmptyBlock()); | 1551 | const emptyBlock = await getEmptyBlock(); |
| 1552 | $(listId).append(emptyBlock); | ||
| 1547 | } | 1553 | } |
| 1548 | let displayCount = 0; | 1554 | let displayCount = 0; |
| 1549 | for (const i of data) { | 1555 | for (const i of data) { |
| @@ -1564,13 +1570,16 @@ export async function printCharacters(fullRefresh = false) { | |||
| 1564 | 1570 | ||
| 1565 | const hidden = (characters.length + groups.length) - displayCount; | 1571 | const hidden = (characters.length + groups.length) - displayCount; |
| 1566 | if (hidden > 0 && entitiesFilter.hasAnyFilter()) { | 1572 | if (hidden > 0 && entitiesFilter.hasAnyFilter()) { |
| 1567 | $(listId).append(getHiddenBlock(hidden)); | 1573 | const hiddenBlock = await getHiddenBlock(hidden); |
| 1574 | $(listId).append(hiddenBlock); | ||
| 1568 | } | 1575 | } |
| 1576 | localizePagination($('#rm_print_characters_pagination')); | ||
| 1569 | 1577 | ||
| 1570 | eventSource.emit(event_types.CHARACTER_PAGE_LOADED); | 1578 | eventSource.emit(event_types.CHARACTER_PAGE_LOADED); |
| 1571 | }, | 1579 | }, |
| 1572 | afterSizeSelectorChange: function (e) { | 1580 | afterSizeSelectorChange: function (e, size) { |
| 1573 | accountStorage.setItem(storageKey, e.target.value); | 1581 | accountStorage.setItem(storageKey, e.target.value); |
| 1582 | paginationDropdownChangeHandler(e, size); | ||
| 1574 | }, | 1583 | }, |
| 1575 | afterPaging: function (e) { | 1584 | afterPaging: function (e) { |
| 1576 | saveCharactersPage = e; | 1585 | saveCharactersPage = e; |
| @@ -2751,6 +2760,7 @@ export function substituteParams(content, _name1, _name2, _original, _group, _re | |||
| 2751 | environment.charVersion = fields.version || ''; | 2760 | environment.charVersion = fields.version || ''; |
| 2752 | environment.char_version = fields.version || ''; | 2761 | environment.char_version = fields.version || ''; |
| 2753 | environment.charDepthPrompt = fields.charDepthPrompt || ''; | 2762 | environment.charDepthPrompt = fields.charDepthPrompt || ''; |
| 2763 | environment.creatorNotes = fields.creatorNotes || ''; | ||
| 2754 | } | 2764 | } |
| 2755 | 2765 | ||
| 2756 | // Must be substituted last so that they're replaced inside {{description}} | 2766 | // Must be substituted last so that they're replaced inside {{description}} |
| @@ -3047,6 +3057,20 @@ export async function getExtensionPromptByName(moduleName) { | |||
| 3047 | } | 3057 | } |
| 3048 | 3058 | ||
| 3049 | /** | 3059 | /** |
| 3060 | * Gets the maximum depth of extension prompts. | ||
| 3061 | * @returns {number} Maximum depth of extension prompts | ||
| 3062 | */ | ||
| 3063 | export function getExtensionPromptMaxDepth() { | ||
| 3064 | return MAX_INJECTION_DEPTH; | ||
| 3065 | /* | ||
| 3066 | const prompts = Object.values(extension_prompts); | ||
| 3067 | const maxDepth = Math.max(...prompts.map(x => x.depth ?? 0)); | ||
| 3068 | // Clamp to 1 <= depth <= MAX_INJECTION_DEPTH | ||
| 3069 | return Math.max(Math.min(maxDepth, MAX_INJECTION_DEPTH), 1); | ||
| 3070 | */ | ||
| 3071 | } | ||
| 3072 | |||
| 3073 | /** | ||
| 3050 | * Returns the extension prompt for the given position, depth, and role. | 3074 | * Returns the extension prompt for the given position, depth, and role. |
| 3051 | * If multiple prompts are found, they are joined with a separator. | 3075 | * If multiple prompts are found, they are joined with a separator. |
| 3052 | * @param {number} [position] Position of the prompt | 3076 | * @param {number} [position] Position of the prompt |
| @@ -3115,6 +3139,7 @@ export function baseChatReplace(value, name1, name2) { | |||
| 3115 | * @property {string} jailbreak Jailbreak instructions | 3139 | * @property {string} jailbreak Jailbreak instructions |
| 3116 | * @property {string} version Character version | 3140 | * @property {string} version Character version |
| 3117 | * @property {string} charDepthPrompt Character depth note | 3141 | * @property {string} charDepthPrompt Character depth note |
| 3142 | * @property {string} creatorNotes Character creator notes | ||
| 3118 | * @returns {CharacterCardFields} Character card fields | 3143 | * @returns {CharacterCardFields} Character card fields |
| 3119 | */ | 3144 | */ |
| 3120 | export function getCharacterCardFields({ chid = null } = {}) { | 3145 | export function getCharacterCardFields({ chid = null } = {}) { |
| @@ -3130,6 +3155,7 @@ export function getCharacterCardFields({ chid = null } = {}) { | |||
| 3130 | jailbreak: '', | 3155 | jailbreak: '', |
| 3131 | version: '', | 3156 | version: '', |
| 3132 | charDepthPrompt: '', | 3157 | charDepthPrompt: '', |
| 3158 | creatorNotes: '', | ||
| 3133 | }; | 3159 | }; |
| 3134 | result.persona = baseChatReplace(power_user.persona_description?.trim(), name1, name2); | 3160 | result.persona = baseChatReplace(power_user.persona_description?.trim(), name1, name2); |
| 3135 | 3161 | ||
| @@ -3148,6 +3174,7 @@ export function getCharacterCardFields({ chid = null } = {}) { | |||
| 3148 | result.jailbreak = power_user.prefer_character_jailbreak ? baseChatReplace(character.data?.post_history_instructions?.trim(), name1, name2) : ''; | 3174 | result.jailbreak = power_user.prefer_character_jailbreak ? baseChatReplace(character.data?.post_history_instructions?.trim(), name1, name2) : ''; |
| 3149 | result.version = character.data?.character_version ?? ''; | 3175 | result.version = character.data?.character_version ?? ''; |
| 3150 | result.charDepthPrompt = baseChatReplace(character.data?.extensions?.depth_prompt?.prompt?.trim(), name1, name2); | 3176 | result.charDepthPrompt = baseChatReplace(character.data?.extensions?.depth_prompt?.prompt?.trim(), name1, name2); |
| 3177 | result.creatorNotes = baseChatReplace(character.data?.creator_notes?.trim(), name1, name2); | ||
| 3151 | 3178 | ||
| 3152 | if (selected_group) { | 3179 | if (selected_group) { |
| 3153 | const groupCards = getGroupCharacterCards(selected_group, Number(currentChid)); | 3180 | const groupCards = getGroupCharacterCards(selected_group, Number(currentChid)); |
| @@ -3975,11 +4002,14 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | |||
| 3975 | system, | 4002 | system, |
| 3976 | jailbreak, | 4003 | jailbreak, |
| 3977 | charDepthPrompt, | 4004 | charDepthPrompt, |
| 4005 | creatorNotes, | ||
| 3978 | } = getCharacterCardFields(); | 4006 | } = getCharacterCardFields(); |
| 3979 | 4007 | ||
| 3980 | if (main_api !== 'openai') { | 4008 | if (main_api !== 'openai') { |
| 3981 | if (power_user.sysprompt.enabled) { | 4009 | if (power_user.sysprompt.enabled) { |
| 3982 | system = power_user.prefer_character_prompt && system ? system : baseChatReplace(power_user.sysprompt.content, name1, name2); | 4010 | system = power_user.prefer_character_prompt && system |
| 4011 | ? substituteParams(system, name1, name2, (power_user.sysprompt.content ?? '')) | ||
| 4012 | : baseChatReplace(power_user.sysprompt.content, name1, name2); | ||
| 3983 | system = isInstruct ? formatInstructModeSystemPrompt(substituteParams(system, name1, name2, power_user.sysprompt.content)) : system; | 4013 | system = isInstruct ? formatInstructModeSystemPrompt(substituteParams(system, name1, name2, power_user.sysprompt.content)) : system; |
| 3984 | } else { | 4014 | } else { |
| 3985 | // Nullify if it's not enabled | 4015 | // Nullify if it's not enabled |
| @@ -4129,7 +4159,15 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | |||
| 4129 | // Make quiet prompt available for WIAN | 4159 | // Make quiet prompt available for WIAN |
| 4130 | setExtensionPrompt('QUIET_PROMPT', quiet_prompt || '', extension_prompt_types.IN_PROMPT, 0, true); | 4160 | setExtensionPrompt('QUIET_PROMPT', quiet_prompt || '', extension_prompt_types.IN_PROMPT, 0, true); |
| 4131 | const chatForWI = coreChat.map(x => world_info_include_names ? `${x.name}: ${x.mes}` : x.mes).reverse(); | 4161 | const chatForWI = coreChat.map(x => world_info_include_names ? `${x.name}: ${x.mes}` : x.mes).reverse(); |
| 4132 | const { worldInfoString, worldInfoBefore, worldInfoAfter, worldInfoExamples, worldInfoDepth } = await getWorldInfoPrompt(chatForWI, this_max_context, dryRun); | 4162 | const globalScanData = { |
| 4163 | personaDescription: persona, | ||
| 4164 | characterDescription: description, | ||
| 4165 | characterPersonality: personality, | ||
| 4166 | characterDepthPrompt: charDepthPrompt, | ||
| 4167 | scenario: scenario, | ||
| 4168 | creatorNotes: creatorNotes, | ||
| 4169 | }; | ||
| 4170 | const { worldInfoString, worldInfoBefore, worldInfoAfter, worldInfoExamples, worldInfoDepth } = await getWorldInfoPrompt(chatForWI, this_max_context, dryRun, globalScanData); | ||
| 4133 | setExtensionPrompt('QUIET_PROMPT', '', extension_prompt_types.IN_PROMPT, 0, true); | 4171 | setExtensionPrompt('QUIET_PROMPT', '', extension_prompt_types.IN_PROMPT, 0, true); |
| 4134 | 4172 | ||
| 4135 | // Add message example WI | 4173 | // Add message example WI |
| @@ -4178,11 +4216,13 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | |||
| 4178 | injectedIndices = await doChatInject(coreChat, isContinue); | 4216 | injectedIndices = await doChatInject(coreChat, isContinue); |
| 4179 | } | 4217 | } |
| 4180 | 4218 | ||
| 4181 | // Insert character jailbreak as the last user message (if exists, allowed, preferred, and not using Chat Completion) | 4219 | if (main_api !== 'openai' && power_user.sysprompt.enabled) { |
| 4182 | if (power_user.context.allow_jailbreak && power_user.prefer_character_jailbreak && main_api !== 'openai' && jailbreak) { | 4220 | jailbreak = power_user.prefer_character_jailbreak && jailbreak |
| 4183 | // Set "original" explicity to empty string since there's no original | 4221 | ? substituteParams(jailbreak, name1, name2, (power_user.sysprompt.post_history ?? '')) |
| 4184 | jailbreak = substituteParams(jailbreak, name1, name2, ''); | 4222 | : baseChatReplace(power_user.sysprompt.post_history, name1, name2); |
| 4185 | 4223 | ||
| 4224 | // Only inject the jb if there is one | ||
| 4225 | if (jailbreak) { | ||
| 4186 | // When continuing generation of previous output, last user message precedes the message to continue | 4226 | // When continuing generation of previous output, last user message precedes the message to continue |
| 4187 | if (isContinue) { | 4227 | if (isContinue) { |
| 4188 | coreChat.splice(coreChat.length - 1, 0, { mes: jailbreak, is_user: true }); | 4228 | coreChat.splice(coreChat.length - 1, 0, { mes: jailbreak, is_user: true }); |
| @@ -4191,6 +4231,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | |||
| 4191 | coreChat.push({ mes: jailbreak, is_user: true }); | 4231 | coreChat.push({ mes: jailbreak, is_user: true }); |
| 4192 | } | 4232 | } |
| 4193 | } | 4233 | } |
| 4234 | } | ||
| 4194 | 4235 | ||
| 4195 | let chat2 = []; | 4236 | let chat2 = []; |
| 4196 | let continue_mag = ''; | 4237 | let continue_mag = ''; |
| @@ -4221,12 +4262,20 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | |||
| 4221 | 4262 | ||
| 4222 | // Do not suffix the message for continuation | 4263 | // Do not suffix the message for continuation |
| 4223 | if (i === 0 && isContinue) { | 4264 | if (i === 0 && isContinue) { |
| 4265 | // Pick something that's very unlikely to be in a message | ||
| 4266 | const FORMAT_TOKEN = '\u0000\ufffc\u0000\ufffd'; | ||
| 4267 | |||
| 4224 | if (isInstruct) { | 4268 | if (isInstruct) { |
| 4269 | const originalMessage = String(coreChat[j].mes ?? ''); | ||
| 4270 | coreChat[j].mes = originalMessage.replaceAll(FORMAT_TOKEN, '') + FORMAT_TOKEN; | ||
| 4225 | // Reformat with the last output sequence (if any) | 4271 | // Reformat with the last output sequence (if any) |
| 4226 | chat2[i] = formatMessageHistoryItem(coreChat[j], isInstruct, force_output_sequence.LAST); | 4272 | chat2[i] = formatMessageHistoryItem(coreChat[j], isInstruct, force_output_sequence.LAST); |
| 4273 | coreChat[j].mes = originalMessage; | ||
| 4227 | } | 4274 | } |
| 4228 | 4275 | ||
| 4229 | chat2[i] = chat2[i].slice(0, chat2[i].lastIndexOf(coreChat[j].mes) + coreChat[j].mes.length); | 4276 | chat2[i] = chat2[i].includes(FORMAT_TOKEN) |
| 4277 | ? chat2[i].slice(0, chat2[i].lastIndexOf(FORMAT_TOKEN)) | ||
| 4278 | : chat2[i].slice(0, chat2[i].lastIndexOf(coreChat[j].mes) + coreChat[j].mes.length); | ||
| 4230 | continue_mag = coreChat[j].mes; | 4279 | continue_mag = coreChat[j].mes; |
| 4231 | } | 4280 | } |
| 4232 | 4281 | ||
| @@ -4840,6 +4889,8 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | |||
| 4840 | userPersona: (power_user.persona_description_position == persona_description_positions.IN_PROMPT ? (persona || '') : ''), | 4889 | userPersona: (power_user.persona_description_position == persona_description_positions.IN_PROMPT ? (persona || '') : ''), |
| 4841 | tokenizer: getFriendlyTokenizerName(main_api).tokenizerName || '', | 4890 | tokenizer: getFriendlyTokenizerName(main_api).tokenizerName || '', |
| 4842 | presetName: getPresetManager()?.getSelectedPresetName() || '', | 4891 | presetName: getPresetManager()?.getSelectedPresetName() || '', |
| 4892 | messagesCount: main_api !== 'openai' ? mesSend.length : oaiMessages.length, | ||
| 4893 | examplesCount: main_api !== 'openai' ? (pinExmString ? mesExamplesArray.length : count_exm_add) : oaiMessageExamples.length, | ||
| 4843 | }; | 4894 | }; |
| 4844 | 4895 | ||
| 4845 | //console.log(additionalPromptStuff); | 4896 | //console.log(additionalPromptStuff); |
| @@ -5099,7 +5150,8 @@ async function doChatInject(messages, isContinue) { | |||
| 5099 | let totalInsertedMessages = 0; | 5150 | let totalInsertedMessages = 0; |
| 5100 | messages.reverse(); | 5151 | messages.reverse(); |
| 5101 | 5152 | ||
| 5102 | for (let i = 0; i <= MAX_INJECTION_DEPTH; i++) { | 5153 | const maxDepth = getExtensionPromptMaxDepth(); |
| 5154 | for (let i = 0; i <= maxDepth; i++) { | ||
| 5103 | // Order of priority (most important go lower) | 5155 | // Order of priority (most important go lower) |
| 5104 | const roles = [extension_prompt_roles.SYSTEM, extension_prompt_roles.USER, extension_prompt_roles.ASSISTANT]; | 5156 | const roles = [extension_prompt_roles.SYSTEM, extension_prompt_roles.USER, extension_prompt_roles.ASSISTANT]; |
| 5105 | const names = { | 5157 | const names = { |
| @@ -5522,6 +5574,8 @@ export async function itemizedParams(itemizedPrompts, thisPromptSet, incomingMes | |||
| 5522 | modelUsed: chat[incomingMesId]?.extra?.model, | 5574 | modelUsed: chat[incomingMesId]?.extra?.model, |
| 5523 | apiUsed: chat[incomingMesId]?.extra?.api, | 5575 | apiUsed: chat[incomingMesId]?.extra?.api, |
| 5524 | presetName: itemizedPrompts[thisPromptSet].presetName || t`(Unknown)`, | 5576 | presetName: itemizedPrompts[thisPromptSet].presetName || t`(Unknown)`, |
| 5577 | messagesCount: String(itemizedPrompts[thisPromptSet].messagesCount ?? ''), | ||
| 5578 | examplesCount: String(itemizedPrompts[thisPromptSet].examplesCount ?? ''), | ||
| 5525 | }; | 5579 | }; |
| 5526 | 5580 | ||
| 5527 | const getFriendlyName = (value) => $(`#rm_api_block select option[value="${value}"]`).first().text() || value; | 5581 | const getFriendlyName = (value) => $(`#rm_api_block select option[value="${value}"]`).first().text() || value; |
| @@ -6940,15 +6994,23 @@ export async function saveChat({ chatName, withMetadata, mesId, force = false } | |||
| 6940 | throw new Error(result.statusText); | 6994 | throw new Error(result.statusText); |
| 6941 | } | 6995 | } |
| 6942 | 6996 | ||
| 6943 | const forceSaveConfirmed = await Popup.show.confirm( | 6997 | const popupResult = await Popup.show.input( |
| 6944 | t`ERROR: Chat integrity check failed.`, | 6998 | t`ERROR: Chat integrity check failed while saving the file.`, |
| 6945 | t`Continuing the operation may result in data loss. Would you like to overwrite the chat file anyway? Pressing "NO" will cancel the save operation.`, | 6999 | t`<p>After you click OK, the page will be reloaded to prevent data corruption.</p> |
| 6946 | { okButton: t`Yes, overwrite`, cancelButton: t`No, cancel` }, | 7000 | <p>To confirm an overwrite (and potentially <b>LOSE YOUR DATA</b>), enter <code>OVERWRITE</code> (in all caps) in the box below before clicking OK.</p>`, |
| 6947 | ) === POPUP_RESULT.AFFIRMATIVE; | 7001 | '', |
| 7002 | { okButton: 'OK', cancelButton: false }, | ||
| 7003 | ); | ||
| 6948 | 7004 | ||
| 6949 | if (forceSaveConfirmed) { | 7005 | const forceSaveConfirmed = popupResult === 'OVERWRITE'; |
| 6950 | await saveChat({ chatName, withMetadata, mesId, force: true }); | 7006 | |
| 7007 | if (!forceSaveConfirmed) { | ||
| 7008 | console.warn('Chat integrity check failed, and user did not confirm the overwrite. Reloading the page.'); | ||
| 7009 | window.location.reload(); | ||
| 7010 | return; | ||
| 6951 | } | 7011 | } |
| 7012 | |||
| 7013 | await saveChat({ chatName, withMetadata, mesId, force: true }); | ||
| 6952 | } catch (error) { | 7014 | } catch (error) { |
| 6953 | console.error(error); | 7015 | console.error(error); |
| 6954 | toastr.error(t`Check the server connection and reload the page to prevent data loss.`, t`Chat could not be saved`); | 7016 | toastr.error(t`Check the server connection and reload the page to prevent data loss.`, t`Chat could not be saved`); |
| @@ -8375,15 +8437,15 @@ export function callPopup(text, type, inputValue = '', { okButton, rows, wide, w | |||
| 8375 | function getOkButtonText() { | 8437 | function getOkButtonText() { |
| 8376 | if (['text', 'char_not_selected'].includes(popup_type)) { | 8438 | if (['text', 'char_not_selected'].includes(popup_type)) { |
| 8377 | $dialoguePopupCancel.css('display', 'none'); | 8439 | $dialoguePopupCancel.css('display', 'none'); |
| 8378 | return okButton ?? 'Ok'; | 8440 | return okButton ?? t`Ok`; |
| 8379 | } else if (['delete_extension'].includes(popup_type)) { | 8441 | } else if (['delete_extension'].includes(popup_type)) { |
| 8380 | return okButton ?? 'Ok'; | 8442 | return okButton ?? t`Ok`; |
| 8381 | } else if (['new_chat', 'confirm'].includes(popup_type)) { | 8443 | } else if (['new_chat', 'confirm'].includes(popup_type)) { |
| 8382 | return okButton ?? 'Yes'; | 8444 | return okButton ?? t`Yes`; |
| 8383 | } else if (['input'].includes(popup_type)) { | 8445 | } else if (['input'].includes(popup_type)) { |
| 8384 | return okButton ?? t`Save`; | 8446 | return okButton ?? t`Save`; |
| 8385 | } | 8447 | } |
| 8386 | return okButton ?? 'Delete'; | 8448 | return okButton ?? t`Delete`; |
| 8387 | } | 8449 | } |
| 8388 | 8450 | ||
| 8389 | dialogueCloseStop = true; | 8451 | dialogueCloseStop = true; |
| @@ -9089,7 +9151,7 @@ function formatSwipeCounter(current, total) { | |||
| 9089 | * @param {string} [params.source] The source of the swipe event. | 9151 | * @param {string} [params.source] The source of the swipe event. |
| 9090 | * @param {boolean} [params.repeated] Is the swipe event repeated. | 9152 | * @param {boolean} [params.repeated] Is the swipe event repeated. |
| 9091 | */ | 9153 | */ |
| 9092 | function swipe_left(_event, { source, repeated } = {}) { | 9154 | export function swipe_left(_event, { source, repeated } = {}) { |
| 9093 | if (chat.length - 1 === Number(this_edit_mes_id)) { | 9155 | if (chat.length - 1 === Number(this_edit_mes_id)) { |
| 9094 | closeMessageEditor(); | 9156 | closeMessageEditor(); |
| 9095 | } | 9157 | } |
| @@ -9237,7 +9299,7 @@ function swipe_left(_event, { source, repeated } = {}) { | |||
| 9237 | * @param {string} [params.source] The source of the swipe event. | 9299 | * @param {string} [params.source] The source of the swipe event. |
| 9238 | * @param {boolean} [params.repeated] Is the swipe event repeated. | 9300 | * @param {boolean} [params.repeated] Is the swipe event repeated. |
| 9239 | */ | 9301 | */ |
| 9240 | function swipe_right(_event, { source, repeated } = {}) { | 9302 | export function swipe_right(_event, { source, repeated } = {}) { |
| 9241 | if (chat.length - 1 === Number(this_edit_mes_id)) { | 9303 | if (chat.length - 1 === Number(this_edit_mes_id)) { |
| 9242 | closeMessageEditor(); | 9304 | closeMessageEditor(); |
| 9243 | } | 9305 | } |
| @@ -11832,8 +11894,8 @@ jQuery(async function () { | |||
| 11832 | return; | 11894 | return; |
| 11833 | } | 11895 | } |
| 11834 | const drawer = $(this).closest('.inline-drawer'); | 11896 | const drawer = $(this).closest('.inline-drawer'); |
| 11835 | const icon = drawer.find('.inline-drawer-icon'); | 11897 | const icon = drawer.find('>.inline-drawer-header .inline-drawer-icon'); |
| 11836 | const drawerContent = drawer.find('.inline-drawer-content'); | 11898 | const drawerContent = drawer.find('>.inline-drawer-content'); |
| 11837 | icon.toggleClass('down up'); | 11899 | icon.toggleClass('down up'); |
| 11838 | icon.toggleClass('fa-circle-chevron-down fa-circle-chevron-up'); | 11900 | icon.toggleClass('fa-circle-chevron-down fa-circle-chevron-up'); |
| 11839 | drawerContent.stop().slideToggle({ | 11901 | drawerContent.stop().slideToggle({ |
| @@ -409,6 +409,7 @@ function RA_autoconnect(PrevApi) { | |||
| 409 | || (secret_state[SECRET_KEYS.ZEROONEAI] && oai_settings.chat_completion_source == chat_completion_sources.ZEROONEAI) | 409 | || (secret_state[SECRET_KEYS.ZEROONEAI] && oai_settings.chat_completion_source == chat_completion_sources.ZEROONEAI) |
| 410 | || (secret_state[SECRET_KEYS.NANOGPT] && oai_settings.chat_completion_source == chat_completion_sources.NANOGPT) | 410 | || (secret_state[SECRET_KEYS.NANOGPT] && oai_settings.chat_completion_source == chat_completion_sources.NANOGPT) |
| 411 | || (secret_state[SECRET_KEYS.DEEPSEEK] && oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK) | 411 | || (secret_state[SECRET_KEYS.DEEPSEEK] && oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK) |
| 412 | || (secret_state[SECRET_KEYS.XAI] && oai_settings.chat_completion_source == chat_completion_sources.XAI) | ||
| 412 | || (isValidUrl(oai_settings.custom_url) && oai_settings.chat_completion_source == chat_completion_sources.CUSTOM) | 413 | || (isValidUrl(oai_settings.custom_url) && oai_settings.chat_completion_source == chat_completion_sources.CUSTOM) |
| 413 | ) { | 414 | ) { |
| 414 | $('#api_button_openai').trigger('click'); | 415 | $('#api_button_openai').trigger('click'); |
| @@ -1047,7 +1048,7 @@ export function initRossMods() { | |||
| 1047 | //Enter to send when send_textarea in focus | 1048 | //Enter to send when send_textarea in focus |
| 1048 | if (document.activeElement == hotkeyTargets['send_textarea']) { | 1049 | if (document.activeElement == hotkeyTargets['send_textarea']) { |
| 1049 | const sendOnEnter = shouldSendOnEnter(); | 1050 | const sendOnEnter = shouldSendOnEnter(); |
| 1050 | if (!event.shiftKey && !event.ctrlKey && !event.altKey && event.key == 'Enter' && sendOnEnter) { | 1051 | if (!event.isComposing && !event.shiftKey && !event.ctrlKey && !event.altKey && event.key == 'Enter' && sendOnEnter) { |
| 1051 | event.preventDefault(); | 1052 | event.preventDefault(); |
| 1052 | sendTextareaMessage(); | 1053 | sendTextareaMessage(); |
| 1053 | return; | 1054 | return; |
| @@ -1119,7 +1120,7 @@ export function initRossMods() { | |||
| 1119 | const result = await Popup.show.confirm('Regenerate Message', 'Are you sure you want to regenerate the latest message?', { | 1120 | const result = await Popup.show.confirm('Regenerate Message', 'Are you sure you want to regenerate the latest message?', { |
| 1120 | customInputs: [{ id: 'regenerateWithCtrlEnter', label: 'Don\'t ask again' }], | 1121 | customInputs: [{ id: 'regenerateWithCtrlEnter', label: 'Don\'t ask again' }], |
| 1121 | onClose: (popup) => { | 1122 | onClose: (popup) => { |
| 1122 | regenerateWithCtrlEnter = popup.inputResults.get('regenerateWithCtrlEnter') ?? false; | 1123 | regenerateWithCtrlEnter = Boolean(popup.inputResults.get('regenerateWithCtrlEnter') ?? false); |
| 1123 | }, | 1124 | }, |
| 1124 | }); | 1125 | }); |
| 1125 | if (!result) { | 1126 | if (!result) { |
| @@ -6,6 +6,7 @@ import { SlashCommand } from './slash-commands/SlashCommand.js'; | |||
| 6 | import { SlashCommandParser } from './slash-commands/SlashCommandParser.js'; | 6 | import { SlashCommandParser } from './slash-commands/SlashCommandParser.js'; |
| 7 | import { flashHighlight, stringFormat } from './utils.js'; | 7 | import { flashHighlight, stringFormat } from './utils.js'; |
| 8 | import { t } from './i18n.js'; | 8 | import { t } from './i18n.js'; |
| 9 | import { Popup } from './popup.js'; | ||
| 9 | 10 | ||
| 10 | const BG_METADATA_KEY = 'custom_background'; | 11 | const BG_METADATA_KEY = 'custom_background'; |
| 11 | const LIST_METADATA_KEY = 'chat_backgrounds'; | 12 | const LIST_METADATA_KEY = 'chat_backgrounds'; |
| @@ -291,7 +292,7 @@ async function onDeleteBackgroundClick(e) { | |||
| 291 | const bgToDelete = $(this).closest('.bg_example'); | 292 | const bgToDelete = $(this).closest('.bg_example'); |
| 292 | const url = bgToDelete.data('url'); | 293 | const url = bgToDelete.data('url'); |
| 293 | const isCustom = bgToDelete.attr('custom') === 'true'; | 294 | const isCustom = bgToDelete.attr('custom') === 'true'; |
| 294 | const confirm = await callPopup('<h3>Delete the background?</h3>', 'confirm'); | 295 | const confirm = await Popup.show.confirm(t`Delete the background?`, null); |
| 295 | const bg = bgToDelete.attr('bgfile'); | 296 | const bg = bgToDelete.attr('bgfile'); |
| 296 | 297 | ||
| 297 | if (confirm) { | 298 | if (confirm) { |
| @@ -33,6 +33,12 @@ | |||
| 33 | * @property {number} role - The specific function or purpose of the extension. | 33 | * @property {number} role - The specific function or purpose of the extension. |
| 34 | * @property {boolean} vectorized - Indicates if the extension is optimized for vectorized processing. | 34 | * @property {boolean} vectorized - Indicates if the extension is optimized for vectorized processing. |
| 35 | * @property {number} display_index - The order in which the extension should be displayed for user interfaces. | 35 | * @property {number} display_index - The order in which the extension should be displayed for user interfaces. |
| 36 | * @property {boolean} match_persona_description - Wether to match against the persona description. | ||
| 37 | * @property {boolean} match_character_description - Wether to match against the persona description. | ||
| 38 | * @property {boolean} match_character_personality - Wether to match against the character personality. | ||
| 39 | * @property {boolean} match_character_depth_prompt - Wether to match against the character depth prompt. | ||
| 40 | * @property {boolean} match_scenario - Wether to match against the character scenario. | ||
| 41 | * @property {boolean} match_creator_notes - Wether to match against the character creator notes. | ||
| 36 | */ | 42 | */ |
| 37 | 43 | ||
| 38 | /** | 44 | /** |
| @@ -74,6 +74,11 @@ const hash_derivations = { | |||
| 74 | 'b6835114b7303ddd78919a82e4d9f7d8c26ed0d7dfc36beeb12d524f6144eab1': | 74 | 'b6835114b7303ddd78919a82e4d9f7d8c26ed0d7dfc36beeb12d524f6144eab1': |
| 75 | 'DeepSeek-V2.5' | 75 | 'DeepSeek-V2.5' |
| 76 | , | 76 | , |
| 77 | |||
| 78 | // THUDM-GLM 4 | ||
| 79 | '854b703e44ca06bdb196cc471c728d15dbab61e744fe6cdce980086b61646ed1': | ||
| 80 | 'GLM-4' | ||
| 81 | , | ||
| 77 | }; | 82 | }; |
| 78 | 83 | ||
| 79 | const substr_derivations = { | 84 | const substr_derivations = { |
| @@ -43,10 +43,12 @@ import EventSourceStream from './sse-stream.js'; | |||
| 43 | * @property {boolean?} [stream=false] - Whether to stream the response | 43 | * @property {boolean?} [stream=false] - Whether to stream the response |
| 44 | * @property {ChatCompletionMessage[]} messages - Array of chat messages | 44 | * @property {ChatCompletionMessage[]} messages - Array of chat messages |
| 45 | * @property {string} [model] - Optional model name to use for completion | 45 | * @property {string} [model] - Optional model name to use for completion |
| 46 | * @property {string} chat_completion_source - Source provider for chat completion | 46 | * @property {string} chat_completion_source - Source provider |
| 47 | * @property {number} max_tokens - Maximum number of tokens to generate | 47 | * @property {number} max_tokens - Maximum number of tokens to generate |
| 48 | * @property {number} [temperature] - Optional temperature parameter for response randomness | 48 | * @property {number} [temperature] - Optional temperature parameter for response randomness |
| 49 | * @property {string} [custom_url] - Optional custom URL for chat completion | 49 | * @property {string} [custom_url] - Optional custom URL |
| 50 | * @property {string} [reverse_proxy] - Optional reverse proxy URL | ||
| 51 | * @property {string} [proxy_password] - Optional proxy password | ||
| 50 | */ | 52 | */ |
| 51 | 53 | ||
| 52 | /** @typedef {Record<string, any> & ChatCompletionPayloadBase} ChatCompletionPayload */ | 54 | /** @typedef {Record<string, any> & ChatCompletionPayloadBase} ChatCompletionPayload */ |
| @@ -80,7 +82,6 @@ export class TextCompletionService { | |||
| 80 | */ | 82 | */ |
| 81 | static createRequestData({ stream = false, prompt, max_tokens, model, api_type, api_server, temperature, min_p, ...props }) { | 83 | static createRequestData({ stream = false, prompt, max_tokens, model, api_type, api_server, temperature, min_p, ...props }) { |
| 82 | const payload = { | 84 | const payload = { |
| 83 | ...props, | ||
| 84 | stream, | 85 | stream, |
| 85 | prompt, | 86 | prompt, |
| 86 | max_tokens, | 87 | max_tokens, |
| @@ -90,6 +91,7 @@ export class TextCompletionService { | |||
| 90 | api_server: api_server ?? getTextGenServer(api_type), | 91 | api_server: api_server ?? getTextGenServer(api_type), |
| 91 | temperature, | 92 | temperature, |
| 92 | min_p, | 93 | min_p, |
| 94 | ...props, | ||
| 93 | }; | 95 | }; |
| 94 | 96 | ||
| 95 | // Remove undefined values to avoid API errors | 97 | // Remove undefined values to avoid API errors |
| @@ -387,9 +389,8 @@ export class ChatCompletionService { | |||
| 387 | * @param {ChatCompletionPayload} custom | 389 | * @param {ChatCompletionPayload} custom |
| 388 | * @returns {ChatCompletionPayload} | 390 | * @returns {ChatCompletionPayload} |
| 389 | */ | 391 | */ |
| 390 | static createRequestData({ stream = false, messages, model, chat_completion_source, max_tokens, temperature, custom_url, ...props }) { | 392 | static createRequestData({ stream = false, messages, model, chat_completion_source, max_tokens, temperature, custom_url, reverse_proxy, proxy_password, ...props }) { |
| 391 | const payload = { | 393 | const payload = { |
| 392 | ...props, | ||
| 393 | stream, | 394 | stream, |
| 394 | messages, | 395 | messages, |
| 395 | model, | 396 | model, |
| @@ -397,6 +398,11 @@ export class ChatCompletionService { | |||
| 397 | max_tokens, | 398 | max_tokens, |
| 398 | temperature, | 399 | temperature, |
| 399 | custom_url, | 400 | custom_url, |
| 401 | reverse_proxy, | ||
| 402 | proxy_password, | ||
| 403 | use_makersuite_sysprompt: true, | ||
| 404 | claude_use_sysprompt: true, | ||
| 405 | ...props, | ||
| 400 | }; | 406 | }; |
| 401 | 407 | ||
| 402 | // Remove undefined values to avoid API errors | 408 | // Remove undefined values to avoid API errors |
| @@ -661,6 +661,7 @@ function generateExtensionHtml(name, manifest, isActive, isDisabled, isExternal, | |||
| 661 | let deleteButton = isExternal ? `<button class="btn_delete menu_button" data-name="${externalId}" data-i18n="[title]Delete" title="Delete"><i class="fa-fw fa-solid fa-trash-can"></i></button>` : ''; | 661 | let deleteButton = isExternal ? `<button class="btn_delete menu_button" data-name="${externalId}" data-i18n="[title]Delete" title="Delete"><i class="fa-fw fa-solid fa-trash-can"></i></button>` : ''; |
| 662 | let updateButton = isExternal ? `<button class="btn_update menu_button displayNone" data-name="${externalId}" title="Update available"><i class="fa-solid fa-download fa-fw"></i></button>` : ''; | 662 | let updateButton = isExternal ? `<button class="btn_update menu_button displayNone" data-name="${externalId}" title="Update available"><i class="fa-solid fa-download fa-fw"></i></button>` : ''; |
| 663 | let moveButton = isExternal && isUserAdmin ? `<button class="btn_move menu_button" data-name="${externalId}" data-i18n="[title]Move" title="Move"><i class="fa-solid fa-folder-tree fa-fw"></i></button>` : ''; | 663 | let moveButton = isExternal && isUserAdmin ? `<button class="btn_move menu_button" data-name="${externalId}" data-i18n="[title]Move" title="Move"><i class="fa-solid fa-folder-tree fa-fw"></i></button>` : ''; |
| 664 | let branchButton = isExternal && isUserAdmin ? `<button class="btn_branch menu_button" data-name="${externalId}" data-i18n="[title]Switch branch" title="Switch branch"><i class="fa-solid fa-code-branch fa-fw"></i></button>` : ''; | ||
| 664 | let modulesInfo = ''; | 665 | let modulesInfo = ''; |
| 665 | 666 | ||
| 666 | if (isActive && Array.isArray(manifest.optional)) { | 667 | if (isActive && Array.isArray(manifest.optional)) { |
| @@ -701,6 +702,7 @@ function generateExtensionHtml(name, manifest, isActive, isDisabled, isExternal, | |||
| 701 | 702 | ||
| 702 | <div class="extension_actions flex-container alignItemsCenter"> | 703 | <div class="extension_actions flex-container alignItemsCenter"> |
| 703 | ${updateButton} | 704 | ${updateButton} |
| 705 | ${branchButton} | ||
| 704 | ${moveButton} | 706 | ${moveButton} |
| 705 | ${deleteButton} | 707 | ${deleteButton} |
| 706 | </div> | 708 | </div> |
| @@ -944,6 +946,44 @@ async function onDeleteClick() { | |||
| 944 | } | 946 | } |
| 945 | } | 947 | } |
| 946 | 948 | ||
| 949 | async function onBranchClick() { | ||
| 950 | const extensionName = $(this).data('name'); | ||
| 951 | const isCurrentUserAdmin = isAdmin(); | ||
| 952 | const isGlobal = getExtensionType(extensionName) === 'global'; | ||
| 953 | if (isGlobal && !isCurrentUserAdmin) { | ||
| 954 | toastr.error(t`You don't have permission to switch branch.`); | ||
| 955 | return; | ||
| 956 | } | ||
| 957 | |||
| 958 | let newBranch = ''; | ||
| 959 | |||
| 960 | const branches = await getExtensionBranches(extensionName, isGlobal); | ||
| 961 | const selectElement = document.createElement('select'); | ||
| 962 | selectElement.classList.add('text_pole', 'wide100p'); | ||
| 963 | selectElement.addEventListener('change', function () { | ||
| 964 | newBranch = this.value; | ||
| 965 | }); | ||
| 966 | for (const branch of branches) { | ||
| 967 | const option = document.createElement('option'); | ||
| 968 | option.value = branch.name; | ||
| 969 | option.textContent = `${branch.name} (${branch.commit}) [${branch.label}]`; | ||
| 970 | option.selected = branch.current; | ||
| 971 | selectElement.appendChild(option); | ||
| 972 | } | ||
| 973 | |||
| 974 | const popup = new Popup(selectElement, POPUP_TYPE.CONFIRM, '', { | ||
| 975 | okButton: t`Switch`, | ||
| 976 | cancelButton: t`Cancel`, | ||
| 977 | }); | ||
| 978 | const popupResult = await popup.show(); | ||
| 979 | |||
| 980 | if (!popupResult || !newBranch) { | ||
| 981 | return; | ||
| 982 | } | ||
| 983 | |||
| 984 | await switchExtensionBranch(extensionName, isGlobal, newBranch); | ||
| 985 | } | ||
| 986 | |||
| 947 | async function onMoveClick() { | 987 | async function onMoveClick() { |
| 948 | const extensionName = $(this).data('name'); | 988 | const extensionName = $(this).data('name'); |
| 949 | const isCurrentUserAdmin = isAdmin(); | 989 | const isCurrentUserAdmin = isAdmin(); |
| @@ -1056,12 +1096,82 @@ async function getExtensionVersion(extensionName, abortSignal) { | |||
| 1056 | } | 1096 | } |
| 1057 | 1097 | ||
| 1058 | /** | 1098 | /** |
| 1099 | * Gets the list of branches for a specific extension. | ||
| 1100 | * @param {string} extensionName The name of the extension | ||
| 1101 | * @param {boolean} isGlobal Whether the extension is global or not | ||
| 1102 | * @returns {Promise<ExtensionBranch[]>} List of branches for the extension | ||
| 1103 | * @typedef {object} ExtensionBranch | ||
| 1104 | * @property {string} name The name of the branch | ||
| 1105 | * @property {string} commit The commit hash of the branch | ||
| 1106 | * @property {boolean} current Whether this branch is the current one | ||
| 1107 | * @property {string} label The commit label of the branch | ||
| 1108 | */ | ||
| 1109 | async function getExtensionBranches(extensionName, isGlobal) { | ||
| 1110 | try { | ||
| 1111 | const response = await fetch('/api/extensions/branches', { | ||
| 1112 | method: 'POST', | ||
| 1113 | headers: getRequestHeaders(), | ||
| 1114 | body: JSON.stringify({ | ||
| 1115 | extensionName, | ||
| 1116 | global: isGlobal, | ||
| 1117 | }), | ||
| 1118 | }); | ||
| 1119 | |||
| 1120 | if (!response.ok) { | ||
| 1121 | const text = await response.text(); | ||
| 1122 | toastr.error(text || response.statusText, t`Extension branches fetch failed`); | ||
| 1123 | console.error('Extension branches fetch failed', response.status, response.statusText, text); | ||
| 1124 | return []; | ||
| 1125 | } | ||
| 1126 | |||
| 1127 | return await response.json(); | ||
| 1128 | } catch (error) { | ||
| 1129 | console.error('Error:', error); | ||
| 1130 | return []; | ||
| 1131 | } | ||
| 1132 | } | ||
| 1133 | |||
| 1134 | /** | ||
| 1135 | * Switches the branch of an extension. | ||
| 1136 | * @param {string} extensionName The name of the extension | ||
| 1137 | * @param {boolean} isGlobal If the extension is global | ||
| 1138 | * @param {string} branch Branch name to switch to | ||
| 1139 | * @returns {Promise<void>} | ||
| 1140 | */ | ||
| 1141 | async function switchExtensionBranch(extensionName, isGlobal, branch) { | ||
| 1142 | try { | ||
| 1143 | const response = await fetch('/api/extensions/switch', { | ||
| 1144 | method: 'POST', | ||
| 1145 | headers: getRequestHeaders(), | ||
| 1146 | body: JSON.stringify({ | ||
| 1147 | extensionName, | ||
| 1148 | branch, | ||
| 1149 | global: isGlobal, | ||
| 1150 | }), | ||
| 1151 | }); | ||
| 1152 | |||
| 1153 | if (!response.ok) { | ||
| 1154 | const text = await response.text(); | ||
| 1155 | toastr.error(text || response.statusText, t`Extension branch switch failed`); | ||
| 1156 | console.error('Extension branch switch failed', response.status, response.statusText, text); | ||
| 1157 | return; | ||
| 1158 | } | ||
| 1159 | |||
| 1160 | toastr.success(t`Extension ${extensionName} switched to ${branch}`); | ||
| 1161 | await loadExtensionSettings({}, false, false); | ||
| 1162 | void showExtensionsDetails(); | ||
| 1163 | } catch (error) { | ||
| 1164 | console.error('Error:', error); | ||
| 1165 | } | ||
| 1166 | } | ||
| 1167 | |||
| 1168 | /** | ||
| 1059 | * Installs a third-party extension via the API. | 1169 | * Installs a third-party extension via the API. |
| 1060 | * @param {string} url Extension repository URL | 1170 | * @param {string} url Extension repository URL |
| 1061 | * @param {boolean} global Is the extension global? | 1171 | * @param {boolean} global Is the extension global? |
| 1062 | * @returns {Promise<void>} | 1172 | * @returns {Promise<void>} |
| 1063 | */ | 1173 | */ |
| 1064 | export async function installExtension(url, global) { | 1174 | export async function installExtension(url, global, branch = '') { |
| 1065 | console.debug('Extension installation started', url); | 1175 | console.debug('Extension installation started', url); |
| 1066 | 1176 | ||
| 1067 | toastr.info(t`Please wait...`, t`Installing extension`); | 1177 | toastr.info(t`Please wait...`, t`Installing extension`); |
| @@ -1072,6 +1182,7 @@ export async function installExtension(url, global) { | |||
| 1072 | body: JSON.stringify({ | 1182 | body: JSON.stringify({ |
| 1073 | url, | 1183 | url, |
| 1074 | global, | 1184 | global, |
| 1185 | branch, | ||
| 1075 | }), | 1186 | }), |
| 1076 | }); | 1187 | }); |
| 1077 | 1188 | ||
| @@ -1406,9 +1517,17 @@ export async function openThirdPartyExtensionMenu(suggestUrl = '') { | |||
| 1406 | await popup.complete(POPUP_RESULT.AFFIRMATIVE); | 1517 | await popup.complete(POPUP_RESULT.AFFIRMATIVE); |
| 1407 | }, | 1518 | }, |
| 1408 | }; | 1519 | }; |
| 1520 | /** @type {import('./popup.js').CustomPopupInput} */ | ||
| 1521 | const branchNameInput = { | ||
| 1522 | id: 'extension_branch_name', | ||
| 1523 | label: t`Branch or tag name (optional)`, | ||
| 1524 | type: 'text', | ||
| 1525 | tooltip: 'e.g. main, dev, v1.0.0', | ||
| 1526 | }; | ||
| 1409 | 1527 | ||
| 1410 | const customButtons = isCurrentUserAdmin ? [installForAllButton] : []; | 1528 | const customButtons = isCurrentUserAdmin ? [installForAllButton] : []; |
| 1411 | const popup = new Popup(html, POPUP_TYPE.INPUT, suggestUrl ?? '', { okButton, customButtons }); | 1529 | const customInputs = [branchNameInput]; |
| 1530 | const popup = new Popup(html, POPUP_TYPE.INPUT, suggestUrl ?? '', { okButton, customButtons, customInputs }); | ||
| 1412 | const input = await popup.show(); | 1531 | const input = await popup.show(); |
| 1413 | 1532 | ||
| 1414 | if (!input) { | 1533 | if (!input) { |
| @@ -1417,7 +1536,8 @@ export async function openThirdPartyExtensionMenu(suggestUrl = '') { | |||
| 1417 | } | 1536 | } |
| 1418 | 1537 | ||
| 1419 | const url = String(input).trim(); | 1538 | const url = String(input).trim(); |
| 1420 | await installExtension(url, global); | 1539 | const branchName = String(popup.inputResults.get('extension_branch_name') ?? '').trim(); |
| 1540 | await installExtension(url, global, branchName); | ||
| 1421 | } | 1541 | } |
| 1422 | 1542 | ||
| 1423 | export async function initExtensions() { | 1543 | export async function initExtensions() { |
| @@ -1433,6 +1553,7 @@ export async function initExtensions() { | |||
| 1433 | $(document).on('click', '.extensions_info .extension_block .btn_update', onUpdateClick); | 1553 | $(document).on('click', '.extensions_info .extension_block .btn_update', onUpdateClick); |
| 1434 | $(document).on('click', '.extensions_info .extension_block .btn_delete', onDeleteClick); | 1554 | $(document).on('click', '.extensions_info .extension_block .btn_delete', onDeleteClick); |
| 1435 | $(document).on('click', '.extensions_info .extension_block .btn_move', onMoveClick); | 1555 | $(document).on('click', '.extensions_info .extension_block .btn_move', onMoveClick); |
| 1556 | $(document).on('click', '.extensions_info .extension_block .btn_branch', onBranchClick); | ||
| 1436 | 1557 | ||
| 1437 | /** | 1558 | /** |
| 1438 | * Handles the click event for the third-party extension import button. | 1559 | * Handles the click event for the third-party extension import button. |
| @@ -291,7 +291,7 @@ async function installAsset(url, assetType, filename) { | |||
| 291 | try { | 291 | try { |
| 292 | if (category === 'extension') { | 292 | if (category === 'extension') { |
| 293 | console.debug(DEBUG_PREFIX, 'Installing extension ', url); | 293 | console.debug(DEBUG_PREFIX, 'Installing extension ', url); |
| 294 | await installExtension(url); | 294 | await installExtension(url, false); |
| 295 | console.debug(DEBUG_PREFIX, 'Extension installed.'); | 295 | console.debug(DEBUG_PREFIX, 'Extension installed.'); |
| 296 | return; | 296 | return; |
| 297 | } | 297 | } |
| @@ -309,7 +309,7 @@ async function installAsset(url, assetType, filename) { | |||
| 309 | console.debug(DEBUG_PREFIX, 'Importing character ', filename); | 309 | console.debug(DEBUG_PREFIX, 'Importing character ', filename); |
| 310 | const blob = await result.blob(); | 310 | const blob = await result.blob(); |
| 311 | const file = new File([blob], filename, { type: blob.type }); | 311 | const file = new File([blob], filename, { type: blob.type }); |
| 312 | await processDroppedFiles([file], true); | 312 | await processDroppedFiles([file]); |
| 313 | console.debug(DEBUG_PREFIX, 'Character downloaded.'); | 313 | console.debug(DEBUG_PREFIX, 'Character downloaded.'); |
| 314 | } | 314 | } |
| 315 | } | 315 | } |
| @@ -39,7 +39,7 @@ To install a single 3rd party extension, use the "Install Extensions" | |||
| 39 | <span data-i18n="Characters">Characters</span> | 39 | <span data-i18n="Characters">Characters</span> |
| 40 | </div> | 40 | </div> |
| 41 | </div> | 41 | </div> |
| 42 | <div class="inline-drawer-content" id="assets_menu"> | 42 | <div id="assets_menu"> |
| 43 | </div> | 43 | </div> |
| 44 | </div> | 44 | </div> |
| 45 | </div> | 45 | </div> |
| @@ -428,6 +428,7 @@ jQuery(async function () { | |||
| 428 | 'zerooneai': SECRET_KEYS.ZEROONEAI, | 428 | 'zerooneai': SECRET_KEYS.ZEROONEAI, |
| 429 | 'groq': SECRET_KEYS.GROQ, | 429 | 'groq': SECRET_KEYS.GROQ, |
| 430 | 'cohere': SECRET_KEYS.COHERE, | 430 | 'cohere': SECRET_KEYS.COHERE, |
| 431 | 'xai': SECRET_KEYS.XAI, | ||
| 431 | }; | 432 | }; |
| 432 | 433 | ||
| 433 | if (chatCompletionApis[api] && secret_state[chatCompletionApis[api]]) { | 434 | if (chatCompletionApis[api] && secret_state[chatCompletionApis[api]]) { |
| @@ -31,6 +31,7 @@ | |||
| 31 | <option value="openrouter">OpenRouter</option> | 31 | <option value="openrouter">OpenRouter</option> |
| 32 | <option value="ooba" data-i18n="Text Generation WebUI (oobabooga)">Text Generation WebUI (oobabooga)</option> | 32 | <option value="ooba" data-i18n="Text Generation WebUI (oobabooga)">Text Generation WebUI (oobabooga)</option> |
| 33 | <option value="vllm">vLLM</option> | 33 | <option value="vllm">vLLM</option> |
| 34 | <option value="xai">xAI (Grok)</option> | ||
| 34 | </select> | 35 | </select> |
| 35 | </div> | 36 | </div> |
| 36 | <div class="flex1 flex-container flexFlowColumn flexNoGap"> | 37 | <div class="flex1 flex-container flexFlowColumn flexNoGap"> |
| @@ -46,13 +47,24 @@ | |||
| 46 | <option data-type="mistral" value="mistral-small-2503">mistral-small-2503</option> | 47 | <option data-type="mistral" value="mistral-small-2503">mistral-small-2503</option> |
| 47 | <option data-type="mistral" value="mistral-small-latest">mistral-small-latest</option> | 48 | <option data-type="mistral" value="mistral-small-latest">mistral-small-latest</option> |
| 48 | <option data-type="zerooneai" value="yi-vision">yi-vision</option> | 49 | <option data-type="zerooneai" value="yi-vision">yi-vision</option> |
| 50 | <option data-type="openai" value="gpt-4.1">gpt-4.1</option> | ||
| 51 | <option data-type="openai" value="gpt-4.1-2025-04-14">gpt-4.1-2025-04-14</option> | ||
| 52 | <option data-type="openai" value="gpt-4.1-mini">gpt-4.1-mini</option> | ||
| 53 | <option data-type="openai" value="gpt-4.1-mini-2025-04-14">gpt-4.1-mini-2025-04-14</option> | ||
| 54 | <option data-type="openai" value="gpt-4.1-nano">gpt-4.1-nano</option> | ||
| 55 | <option data-type="openai" value="gpt-4.1-nano-2025-04-14">gpt-4.1-nano-2025-04-14</option> | ||
| 49 | <option data-type="openai" value="gpt-4-vision-preview">gpt-4-vision-preview</option> | 56 | <option data-type="openai" value="gpt-4-vision-preview">gpt-4-vision-preview</option> |
| 50 | <option data-type="openai" value="gpt-4-turbo">gpt-4-turbo</option> | 57 | <option data-type="openai" value="gpt-4-turbo">gpt-4-turbo</option> |
| 51 | <option data-type="openai" value="gpt-4o">gpt-4o</option> | 58 | <option data-type="openai" value="gpt-4o">gpt-4o</option> |
| 52 | <option data-type="openai" value="gpt-4o-mini">gpt-4o-mini</option> | 59 | <option data-type="openai" value="gpt-4o-mini">gpt-4o-mini</option> |
| 60 | <option data-type="openai" value="gpt-4o-mini-2024-07-18">gpt-4o-mini-2024-07-18</option> | ||
| 53 | <option data-type="openai" value="chatgpt-4o-latest">chatgpt-4o-latest</option> | 61 | <option data-type="openai" value="chatgpt-4o-latest">chatgpt-4o-latest</option> |
| 54 | <option data-type="openai" value="o1">o1</option> | 62 | <option data-type="openai" value="o1">o1</option> |
| 55 | <option data-type="openai" value="o1-2024-12-17">o1-2024-12-17</option> | 63 | <option data-type="openai" value="o1-2024-12-17">o1-2024-12-17</option> |
| 64 | <option data-type="openai" value="o3">o3</option> | ||
| 65 | <option data-type="openai" value="o3-2025-04-16">o3-2025-04-16</option> | ||
| 66 | <option data-type="openai" value="o4-mini">o4-mini</option> | ||
| 67 | <option data-type="openai" value="o4-mini-2025-04-16">o4-mini-2025-04-16</option> | ||
| 56 | <option data-type="openai" value="gpt-4.5-preview">gpt-4.5-preview</option> | 68 | <option data-type="openai" value="gpt-4.5-preview">gpt-4.5-preview</option> |
| 57 | <option data-type="openai" value="gpt-4.5-preview-2025-02-27">gpt-4.5-preview-2025-02-27</option> | 69 | <option data-type="openai" value="gpt-4.5-preview-2025-02-27">gpt-4.5-preview-2025-02-27</option> |
| 58 | <option data-type="anthropic" value="claude-3-7-sonnet-latest">claude-3-7-sonnet-latest</option> | 70 | <option data-type="anthropic" value="claude-3-7-sonnet-latest">claude-3-7-sonnet-latest</option> |
| @@ -67,33 +79,33 @@ | |||
| 67 | <option data-type="anthropic" value="claude-3-haiku-20240307">claude-3-haiku-20240307</option> | 79 | <option data-type="anthropic" value="claude-3-haiku-20240307">claude-3-haiku-20240307</option> |
| 68 | <option data-type="google" value="gemini-2.5-pro-preview-03-25">gemini-2.5-pro-preview-03-25</option> | 80 | <option data-type="google" value="gemini-2.5-pro-preview-03-25">gemini-2.5-pro-preview-03-25</option> |
| 69 | <option data-type="google" value="gemini-2.5-pro-exp-03-25">gemini-2.5-pro-exp-03-25</option> | 81 | <option data-type="google" value="gemini-2.5-pro-exp-03-25">gemini-2.5-pro-exp-03-25</option> |
| 70 | <option data-type="google" value="gemini-2.0-pro-exp">gemini-2.0-pro-exp</option> | 82 | <option data-type="google" value="gemini-2.5-flash-preview-04-17">gemini-2.5-flash-preview-04-17</option> |
| 71 | <option data-type="google" value="gemini-2.0-pro-exp-02-05">gemini-2.0-pro-exp-02-05</option> | 83 | <option data-type="google" value="gemini-2.0-pro-exp-02-05">gemini-2.0-pro-exp-02-05 → 2.5-pro-exp-03-25</option> |
| 72 | <option data-type="google" value="gemini-2.0-flash-lite-preview">gemini-2.0-flash-lite-preview</option> | 84 | <option data-type="google" value="gemini-2.0-pro-exp">gemini-2.0-pro-exp → 2.5-pro-exp-03-25</option> |
| 73 | <option data-type="google" value="gemini-2.0-flash-lite-preview-02-05">gemini-2.0-flash-lite-preview-02-05</option> | 85 | <option data-type="google" value="gemini-exp-1206">gemini-exp-1206 → 2.5-pro-exp-03-25</option> |
| 74 | <option data-type="google" value="gemini-2.0-flash">gemini-2.0-flash</option> | ||
| 75 | <option data-type="google" value="gemini-2.0-flash-001">gemini-2.0-flash-001</option> | 86 | <option data-type="google" value="gemini-2.0-flash-001">gemini-2.0-flash-001</option> |
| 76 | <option data-type="google" value="gemini-2.0-flash-exp">gemini-2.0-flash-exp</option> | ||
| 77 | <option data-type="google" value="gemini-2.0-flash-exp-image-generation">gemini-2.0-flash-exp-image-generation</option> | 87 | <option data-type="google" value="gemini-2.0-flash-exp-image-generation">gemini-2.0-flash-exp-image-generation</option> |
| 78 | <option data-type="google" value="gemini-2.0-flash-thinking-exp">gemini-2.0-flash-thinking-exp</option> | 88 | <option data-type="google" value="gemini-2.0-flash-exp">gemini-2.0-flash-exp</option> |
| 79 | <option data-type="google" value="gemini-2.0-flash-thinking-exp-01-21">gemini-2.0-flash-thinking-exp-01-21</option> | 89 | <option data-type="google" value="gemini-2.0-flash">gemini-2.0-flash</option> |
| 80 | <option data-type="google" value="gemini-2.0-flash-thinking-exp-1219">gemini-2.0-flash-thinking-exp-1219</option> | 90 | <option data-type="google" value="gemini-2.0-flash-thinking-exp-01-21">gemini-2.0-flash-thinking-exp-01-21 → 2.5-flash-preview-04-17</option> |
| 81 | <option data-type="google" value="gemini-1.5-flash">gemini-1.5-flash</option> | 91 | <option data-type="google" value="gemini-2.0-flash-thinking-exp-1219">gemini-2.0-flash-thinking-exp-1219 → 2.5-flash-preview-04-17</option> |
| 92 | <option data-type="google" value="gemini-2.0-flash-thinking-exp">gemini-2.0-flash-thinking-exp → 2.5-flash-preview-04-17</option> | ||
| 93 | <option data-type="google" value="gemini-2.0-flash-lite-001">gemini-2.0-flash-lite-001</option> | ||
| 94 | <option data-type="google" value="gemini-2.0-flash-lite-preview-02-05">gemini-2.0-flash-lite-preview-02-05</option> | ||
| 95 | <option data-type="google" value="gemini-2.0-flash-lite-preview">gemini-2.0-flash-lite-preview</option> | ||
| 96 | <option data-type="google" value="gemini-1.5-pro-latest">gemini-1.5-pro-latest</option> | ||
| 97 | <option data-type="google" value="gemini-1.5-pro-002">gemini-1.5-pro-002</option> | ||
| 98 | <option data-type="google" value="gemini-1.5-pro-001">gemini-1.5-pro-001</option> | ||
| 99 | <option data-type="google" value="gemini-1.5-pro">gemini-1.5-pro</option> | ||
| 82 | <option data-type="google" value="gemini-1.5-flash-latest">gemini-1.5-flash-latest</option> | 100 | <option data-type="google" value="gemini-1.5-flash-latest">gemini-1.5-flash-latest</option> |
| 83 | <option data-type="google" value="gemini-1.5-flash-001">gemini-1.5-flash-001</option> | ||
| 84 | <option data-type="google" value="gemini-1.5-flash-002">gemini-1.5-flash-002</option> | 101 | <option data-type="google" value="gemini-1.5-flash-002">gemini-1.5-flash-002</option> |
| 85 | <option data-type="google" value="gemini-1.5-flash-exp-0827">gemini-1.5-flash-exp-0827</option> | 102 | <option data-type="google" value="gemini-1.5-flash-001">gemini-1.5-flash-001</option> |
| 86 | <option data-type="google" value="gemini-1.5-flash-8b-exp-0827">gemini-1.5-flash-8b-exp-0827</option> | 103 | <option data-type="google" value="gemini-1.5-flash">gemini-1.5-flash</option> |
| 104 | <option data-type="google" value="gemini-1.5-flash-8b-001">gemini-1.5-flash-8b-001</option> | ||
| 87 | <option data-type="google" value="gemini-1.5-flash-8b-exp-0924">gemini-1.5-flash-8b-exp-0924</option> | 105 | <option data-type="google" value="gemini-1.5-flash-8b-exp-0924">gemini-1.5-flash-8b-exp-0924</option> |
| 88 | <option data-type="google" value="gemini-exp-1114">gemini-exp-1114</option> | 106 | <option data-type="google" value="gemini-1.5-flash-8b-exp-0827">gemini-1.5-flash-8b-exp-0827</option> |
| 89 | <option data-type="google" value="gemini-exp-1121">gemini-exp-1121</option> | 107 | <option data-type="google" value="learnlm-2.0-flash-experimental">learnlm-2.0-flash-experimental</option> |
| 90 | <option data-type="google" value="gemini-exp-1206">gemini-exp-1206</option> | 108 | <option data-type="google" value="learnlm-1.5-pro-experimental">learnlm-1.5-pro-experimental</option> |
| 91 | <option data-type="google" value="gemini-1.5-pro">gemini-1.5-pro</option> | ||
| 92 | <option data-type="google" value="gemini-1.5-pro-latest">gemini-1.5-pro-latest</option> | ||
| 93 | <option data-type="google" value="gemini-1.5-pro-001">gemini-1.5-pro-001</option> | ||
| 94 | <option data-type="google" value="gemini-1.5-pro-002">gemini-1.5-pro-002</option> | ||
| 95 | <option data-type="google" value="gemini-1.5-pro-exp-0801">gemini-1.5-pro-exp-0801</option> | ||
| 96 | <option data-type="google" value="gemini-1.5-pro-exp-0827">gemini-1.5-pro-exp-0827</option> | ||
| 97 | <option data-type="groq" value="llama-3.2-11b-vision-preview">llama-3.2-11b-vision-preview</option> | 109 | <option data-type="groq" value="llama-3.2-11b-vision-preview">llama-3.2-11b-vision-preview</option> |
| 98 | <option data-type="groq" value="llama-3.2-90b-vision-preview">llama-3.2-90b-vision-preview</option> | 110 | <option data-type="groq" value="llama-3.2-90b-vision-preview">llama-3.2-90b-vision-preview</option> |
| 99 | <option data-type="groq" value="llava-v1.5-7b-4096-preview">llava-v1.5-7b-4096-preview</option> | 111 | <option data-type="groq" value="llava-v1.5-7b-4096-preview">llava-v1.5-7b-4096-preview</option> |
| @@ -134,6 +146,8 @@ | |||
| 134 | <option data-type="koboldcpp" value="koboldcpp_current" data-i18n="currently_loaded">[Currently loaded]</option> | 146 | <option data-type="koboldcpp" value="koboldcpp_current" data-i18n="currently_loaded">[Currently loaded]</option> |
| 135 | <option data-type="vllm" value="vllm_current" data-i18n="currently_selected">[Currently selected]</option> | 147 | <option data-type="vllm" value="vllm_current" data-i18n="currently_selected">[Currently selected]</option> |
| 136 | <option data-type="custom" value="custom_current" data-i18n="currently_selected">[Currently selected]</option> | 148 | <option data-type="custom" value="custom_current" data-i18n="currently_selected">[Currently selected]</option> |
| 149 | <option data-type="xai" value="grok-2-vision-1212">grok-2-vision-1212</option> | ||
| 150 | <option data-type="xai" value="grok-vision-beta">grok-vision-beta</option> | ||
| 137 | </select> | 151 | </select> |
| 138 | </div> | 152 | </div> |
| 139 | <div data-type="ollama"> | 153 | <div data-type="ollama"> |
| @@ -132,7 +132,7 @@ | |||
| 132 | </label> | 132 | </label> |
| 133 | <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."> | 133 | <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."> |
| 134 | <input type="radio" name="memory_position" value="1" /> | 134 | <input type="radio" name="memory_position" value="1" /> |
| 135 | <span data-i18n="In-chat @ Depth">In-chat @ Depth</span> <input id="memory_depth" class="text_pole widthUnset" type="number" min="0" max="999" /> | 135 | <span data-i18n="In-chat @ Depth">In-chat @ Depth</span> <input id="memory_depth" class="text_pole widthUnset" type="number" min="0" max="9999" /> |
| 136 | <span data-i18n="as">as</span> | 136 | <span data-i18n="as">as</span> |
| 137 | <select id="memory_role" class="text_pole widthNatural"> | 137 | <select id="memory_role" class="text_pole widthNatural"> |
| 138 | <option value="0" data-i18n="System">System</option> | 138 | <option value="0" data-i18n="System">System</option> |
| @@ -113,14 +113,14 @@ | |||
| 113 | <span data-i18n="Min Depth">Min Depth</span> | 113 | <span data-i18n="Min Depth">Min Depth</span> |
| 114 | <span class="fa-solid fa-circle-question note-link-span"></span> | 114 | <span class="fa-solid fa-circle-question note-link-span"></span> |
| 115 | </small> | 115 | </small> |
| 116 | <input name="min_depth" class="text_pole textarea_compact" type="number" min="-1" max="999" data-i18n="[placeholder]ext_regex_min_depth_placeholder" placeholder="Unlimited" /> | 116 | <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" /> |
| 117 | </div> | 117 | </div> |
| 118 | <div class="flex1 flex-container flexNoGap"> | 118 | <div class="flex1 flex-container flexNoGap"> |
| 119 | <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."> | 119 | <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."> |
| 120 | <span data-i18n="Max Depth">Max Depth</span> | 120 | <span data-i18n="Max Depth">Max Depth</span> |
| 121 | <span class="fa-solid fa-circle-question note-link-span"></span> | 121 | <span class="fa-solid fa-circle-question note-link-span"></span> |
| 122 | </small> | 122 | </small> |
| 123 | <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" /> | 123 | <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" /> |
| 124 | </div> | 124 | </div> |
| 125 | </div> | 125 | </div> |
| 126 | </div> | 126 | </div> |
| @@ -1,7 +1,7 @@ | |||
| 1 | import { CONNECT_API_MAP, getRequestHeaders } from '../../script.js'; | 1 | import { CONNECT_API_MAP, getRequestHeaders } from '../../script.js'; |
| 2 | import { extension_settings, openThirdPartyExtensionMenu } from '../extensions.js'; | 2 | import { extension_settings, openThirdPartyExtensionMenu } from '../extensions.js'; |
| 3 | import { t } from '../i18n.js'; | 3 | import { t } from '../i18n.js'; |
| 4 | import { oai_settings } from '../openai.js'; | 4 | import { oai_settings, proxies } from '../openai.js'; |
| 5 | import { SECRET_KEYS, secret_state } from '../secrets.js'; | 5 | import { SECRET_KEYS, secret_state } from '../secrets.js'; |
| 6 | import { textgen_types, textgenerationwebui_settings } from '../textgen-settings.js'; | 6 | import { textgen_types, textgenerationwebui_settings } from '../textgen-settings.js'; |
| 7 | import { getTokenCountAsync } from '../tokenizers.js'; | 7 | import { getTokenCountAsync } from '../tokenizers.js'; |
| @@ -153,6 +153,10 @@ function throwIfInvalidModel(useReverseProxy) { | |||
| 153 | throw new Error('Cohere API key is not set.'); | 153 | throw new Error('Cohere API key is not set.'); |
| 154 | } | 154 | } |
| 155 | 155 | ||
| 156 | if (extension_settings.caption.multimodal_api === 'xai' && !secret_state[SECRET_KEYS.XAI]) { | ||
| 157 | throw new Error('xAI API key is not set.'); | ||
| 158 | } | ||
| 159 | |||
| 156 | if (extension_settings.caption.multimodal_api === 'ollama' && !textgenerationwebui_settings.server_urls[textgen_types.OLLAMA]) { | 160 | if (extension_settings.caption.multimodal_api === 'ollama' && !textgenerationwebui_settings.server_urls[textgen_types.OLLAMA]) { |
| 157 | throw new Error('Ollama server URL is not set.'); | 161 | throw new Error('Ollama server URL is not set.'); |
| 158 | } | 162 | } |
| @@ -306,9 +310,10 @@ export class ConnectionManagerRequestService { | |||
| 306 | * @param {boolean?} [custom.includePreset=true] | 310 | * @param {boolean?} [custom.includePreset=true] |
| 307 | * @param {boolean?} [custom.includeInstruct=true] | 311 | * @param {boolean?} [custom.includeInstruct=true] |
| 308 | * @param {Partial<InstructSettings>?} [custom.instructSettings] Override instruct settings | 312 | * @param {Partial<InstructSettings>?} [custom.instructSettings] Override instruct settings |
| 313 | * @param {Record<string, any>} [overridePayload] - Override payload for the request | ||
| 309 | * @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 | 314 | * @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 |
| 310 | */ | 315 | */ |
| 311 | static async sendRequest(profileId, prompt, maxTokens, custom = this.defaultSendRequestParams) { | 316 | static async sendRequest(profileId, prompt, maxTokens, custom = this.defaultSendRequestParams, overridePayload = {}) { |
| 312 | const { stream, signal, extractData, includePreset, includeInstruct, instructSettings } = { ...this.defaultSendRequestParams, ...custom }; | 317 | const { stream, signal, extractData, includePreset, includeInstruct, instructSettings } = { ...this.defaultSendRequestParams, ...custom }; |
| 313 | 318 | ||
| 314 | const context = SillyTavern.getContext(); | 319 | const context = SillyTavern.getContext(); |
| @@ -326,6 +331,8 @@ export class ConnectionManagerRequestService { | |||
| 326 | throw new Error(`API type ${selectedApiMap.selected} does not support chat completions`); | 331 | throw new Error(`API type ${selectedApiMap.selected} does not support chat completions`); |
| 327 | } | 332 | } |
| 328 | 333 | ||
| 334 | const proxyPreset = proxies.find((p) => p.name === profile.proxy); | ||
| 335 | |||
| 329 | const messages = Array.isArray(prompt) ? prompt : [{ role: 'user', content: prompt }]; | 336 | const messages = Array.isArray(prompt) ? prompt : [{ role: 'user', content: prompt }]; |
| 330 | return await context.ChatCompletionService.processRequest({ | 337 | return await context.ChatCompletionService.processRequest({ |
| 331 | stream, | 338 | stream, |
| @@ -334,6 +341,9 @@ export class ConnectionManagerRequestService { | |||
| 334 | model: profile.model, | 341 | model: profile.model, |
| 335 | chat_completion_source: selectedApiMap.source, | 342 | chat_completion_source: selectedApiMap.source, |
| 336 | custom_url: profile['api-url'], | 343 | custom_url: profile['api-url'], |
| 344 | reverse_proxy: proxyPreset?.url, | ||
| 345 | proxy_password: proxyPreset?.password, | ||
| 346 | ...overridePayload, | ||
| 337 | }, { | 347 | }, { |
| 338 | presetName: includePreset ? profile.preset : undefined, | 348 | presetName: includePreset ? profile.preset : undefined, |
| 339 | }, extractData, signal); | 349 | }, extractData, signal); |
| @@ -350,6 +360,7 @@ export class ConnectionManagerRequestService { | |||
| 350 | model: profile.model, | 360 | model: profile.model, |
| 351 | api_type: selectedApiMap.type, | 361 | api_type: selectedApiMap.type, |
| 352 | api_server: profile['api-url'], | 362 | api_server: profile['api-url'], |
| 363 | ...overridePayload, | ||
| 353 | }, { | 364 | }, { |
| 354 | instructName: includeInstruct ? profile.instruct : undefined, | 365 | instructName: includeInstruct ? profile.instruct : undefined, |
| 355 | presetName: includePreset ? profile.preset : undefined, | 366 | presetName: includePreset ? profile.preset : undefined, |
| @@ -81,6 +81,7 @@ const sources = { | |||
| 81 | nanogpt: 'nanogpt', | 81 | nanogpt: 'nanogpt', |
| 82 | bfl: 'bfl', | 82 | bfl: 'bfl', |
| 83 | falai: 'falai', | 83 | falai: 'falai', |
| 84 | xai: 'xai', | ||
| 84 | }; | 85 | }; |
| 85 | 86 | ||
| 86 | const initiators = { | 87 | const initiators = { |
| @@ -1303,6 +1304,7 @@ async function onModelChange() { | |||
| 1303 | sources.nanogpt, | 1304 | sources.nanogpt, |
| 1304 | sources.bfl, | 1305 | sources.bfl, |
| 1305 | sources.falai, | 1306 | sources.falai, |
| 1307 | sources.xai, | ||
| 1306 | ]; | 1308 | ]; |
| 1307 | 1309 | ||
| 1308 | if (cloudSources.includes(extension_settings.sd.source)) { | 1310 | if (cloudSources.includes(extension_settings.sd.source)) { |
| @@ -1518,6 +1520,9 @@ async function loadSamplers() { | |||
| 1518 | case sources.bfl: | 1520 | case sources.bfl: |
| 1519 | samplers = ['N/A']; | 1521 | samplers = ['N/A']; |
| 1520 | break; | 1522 | break; |
| 1523 | case sources.xai: | ||
| 1524 | samplers = ['N/A']; | ||
| 1525 | break; | ||
| 1521 | } | 1526 | } |
| 1522 | 1527 | ||
| 1523 | for (const sampler of samplers) { | 1528 | for (const sampler of samplers) { |
| @@ -1708,6 +1713,9 @@ async function loadModels() { | |||
| 1708 | case sources.falai: | 1713 | case sources.falai: |
| 1709 | models = await loadFalaiModels(); | 1714 | models = await loadFalaiModels(); |
| 1710 | break; | 1715 | break; |
| 1716 | case sources.xai: | ||
| 1717 | models = await loadXAIModels(); | ||
| 1718 | break; | ||
| 1711 | } | 1719 | } |
| 1712 | 1720 | ||
| 1713 | for (const model of models) { | 1721 | for (const model of models) { |
| @@ -1760,6 +1768,12 @@ async function loadFalaiModels() { | |||
| 1760 | return []; | 1768 | return []; |
| 1761 | } | 1769 | } |
| 1762 | 1770 | ||
| 1771 | async function loadXAIModels() { | ||
| 1772 | return [ | ||
| 1773 | { value: 'grok-2-image-1212', text: 'grok-2-image-1212' }, | ||
| 1774 | ]; | ||
| 1775 | } | ||
| 1776 | |||
| 1763 | async function loadPollinationsModels() { | 1777 | async function loadPollinationsModels() { |
| 1764 | const result = await fetch('/api/sd/pollinations/models', { | 1778 | const result = await fetch('/api/sd/pollinations/models', { |
| 1765 | method: 'POST', | 1779 | method: 'POST', |
| @@ -2081,6 +2095,9 @@ async function loadSchedulers() { | |||
| 2081 | case sources.falai: | 2095 | case sources.falai: |
| 2082 | schedulers = ['N/A']; | 2096 | schedulers = ['N/A']; |
| 2083 | break; | 2097 | break; |
| 2098 | case sources.xai: | ||
| 2099 | schedulers = ['N/A']; | ||
| 2100 | break; | ||
| 2084 | } | 2101 | } |
| 2085 | 2102 | ||
| 2086 | for (const scheduler of schedulers) { | 2103 | for (const scheduler of schedulers) { |
| @@ -2166,6 +2183,12 @@ async function loadVaes() { | |||
| 2166 | case sources.bfl: | 2183 | case sources.bfl: |
| 2167 | vaes = ['N/A']; | 2184 | vaes = ['N/A']; |
| 2168 | break; | 2185 | break; |
| 2186 | case sources.falai: | ||
| 2187 | vaes = ['N/A']; | ||
| 2188 | break; | ||
| 2189 | case sources.xai: | ||
| 2190 | vaes = ['N/A']; | ||
| 2191 | break; | ||
| 2169 | } | 2192 | } |
| 2170 | 2193 | ||
| 2171 | for (const vae of vaes) { | 2194 | for (const vae of vaes) { |
| @@ -2735,6 +2758,9 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP | |||
| 2735 | case sources.falai: | 2758 | case sources.falai: |
| 2736 | result = await generateFalaiImage(prefixedPrompt, negativePrompt, signal); | 2759 | result = await generateFalaiImage(prefixedPrompt, negativePrompt, signal); |
| 2737 | break; | 2760 | break; |
| 2761 | case sources.xai: | ||
| 2762 | result = await generateXAIImage(prefixedPrompt, negativePrompt, signal); | ||
| 2763 | break; | ||
| 2738 | } | 2764 | } |
| 2739 | 2765 | ||
| 2740 | if (!result.data) { | 2766 | if (!result.data) { |
| @@ -3464,6 +3490,33 @@ async function generateBflImage(prompt, signal) { | |||
| 3464 | } | 3490 | } |
| 3465 | 3491 | ||
| 3466 | /** | 3492 | /** |
| 3493 | * Generates an image using the xAI API. | ||
| 3494 | * @param {string} prompt The main instruction used to guide the image generation. | ||
| 3495 | * @param {string} _negativePrompt Negative prompt is not used in this API | ||
| 3496 | * @param {AbortSignal} signal An AbortSignal object that can be used to cancel the request. | ||
| 3497 | * @returns {Promise<{format: string, data: string}>} A promise that resolves when the image generation and processing are complete. | ||
| 3498 | */ | ||
| 3499 | async function generateXAIImage(prompt, _negativePrompt, signal) { | ||
| 3500 | const result = await fetch('/api/sd/xai/generate', { | ||
| 3501 | method: 'POST', | ||
| 3502 | headers: getRequestHeaders(), | ||
| 3503 | signal: signal, | ||
| 3504 | body: JSON.stringify({ | ||
| 3505 | prompt: prompt, | ||
| 3506 | model: extension_settings.sd.model, | ||
| 3507 | }), | ||
| 3508 | }); | ||
| 3509 | |||
| 3510 | if (result.ok) { | ||
| 3511 | const data = await result.json(); | ||
| 3512 | return { format: 'jpg', data: data.image }; | ||
| 3513 | } else { | ||
| 3514 | const text = await result.text(); | ||
| 3515 | throw new Error(text); | ||
| 3516 | } | ||
| 3517 | } | ||
| 3518 | |||
| 3519 | /** | ||
| 3467 | * Generates an image using the FAL.AI API. | 3520 | * Generates an image using the FAL.AI API. |
| 3468 | * @param {string} prompt - The main instruction used to guide the image generation. | 3521 | * @param {string} prompt - The main instruction used to guide the image generation. |
| 3469 | * @param {string} negativePrompt - The negative prompt used to guide the image generation. | 3522 | * @param {string} negativePrompt - The negative prompt used to guide the image generation. |
| @@ -3782,6 +3835,8 @@ function isValidState() { | |||
| 3782 | return secret_state[SECRET_KEYS.BFL]; | 3835 | return secret_state[SECRET_KEYS.BFL]; |
| 3783 | case sources.falai: | 3836 | case sources.falai: |
| 3784 | return secret_state[SECRET_KEYS.FALAI]; | 3837 | return secret_state[SECRET_KEYS.FALAI]; |
| 3838 | case sources.xai: | ||
| 3839 | return secret_state[SECRET_KEYS.XAI]; | ||
| 3785 | } | 3840 | } |
| 3786 | } | 3841 | } |
| 3787 | 3842 | ||
| @@ -52,6 +52,7 @@ | |||
| 52 | <option value="auto">Stable Diffusion Web UI (AUTOMATIC1111)</option> | 52 | <option value="auto">Stable Diffusion Web UI (AUTOMATIC1111)</option> |
| 53 | <option value="horde">Stable Horde</option> | 53 | <option value="horde">Stable Horde</option> |
| 54 | <option value="togetherai">TogetherAI</option> | 54 | <option value="togetherai">TogetherAI</option> |
| 55 | <option value="xai">xAI (Grok)</option> | ||
| 55 | </select> | 56 | </select> |
| 56 | <div data-sd-source="auto"> | 57 | <div data-sd-source="auto"> |
| 57 | <label for="sd_auto_url">SD Web UI URL</label> | 58 | <label for="sd_auto_url">SD Web UI URL</label> |
| @@ -76,7 +76,7 @@ | |||
| 76 | <div id="tts_voicemap_block"> | 76 | <div id="tts_voicemap_block"> |
| 77 | </div> | 77 | </div> |
| 78 | <hr> | 78 | <hr> |
| 79 | <form id="tts_provider_settings" class="inline-drawer-content"> | 79 | <form id="tts_provider_settings"> |
| 80 | </form> | 80 | </form> |
| 81 | <div class="tts_buttons"> | 81 | <div class="tts_buttons"> |
| 82 | <input id="tts_voices" class="menu_button" type="submit" value="Available voices" /> | 82 | <input id="tts_voices" class="menu_button" type="submit" value="Available voices" /> |
| @@ -79,6 +79,10 @@ class SystemTtsProvider { | |||
| 79 | // Config // | 79 | // Config // |
| 80 | //########// | 80 | //########// |
| 81 | 81 | ||
| 82 | // Static constants for the simulated default voice | ||
| 83 | static BROWSER_DEFAULT_VOICE_ID = '__browser_default__'; | ||
| 84 | static BROWSER_DEFAULT_VOICE_NAME = 'System Default Voice'; | ||
| 85 | |||
| 82 | settings; | 86 | settings; |
| 83 | ready = false; | 87 | ready = false; |
| 84 | voices = []; | 88 | voices = []; |
| @@ -168,51 +172,97 @@ class SystemTtsProvider { | |||
| 168 | //#################// | 172 | //#################// |
| 169 | fetchTtsVoiceObjects() { | 173 | fetchTtsVoiceObjects() { |
| 170 | if (!('speechSynthesis' in window)) { | 174 | if (!('speechSynthesis' in window)) { |
| 171 | return []; | 175 | return Promise.resolve([]); |
| 172 | } | 176 | } |
| 173 | 177 | ||
| 174 | return new Promise((resolve) => { | 178 | return new Promise((resolve) => { |
| 175 | setTimeout(() => { | 179 | setTimeout(() => { |
| 176 | const voices = speechSynthesis | 180 | let voices = speechSynthesis.getVoices(); |
| 177 | .getVoices() | 181 | |
| 182 | if (voices.length === 0) { | ||
| 183 | // Edge compat: Provide default when voices empty | ||
| 184 | console.warn('SystemTTS: getVoices() returned empty list. Providing browser default option.'); | ||
| 185 | const defaultVoice = { | ||
| 186 | name: SystemTtsProvider.BROWSER_DEFAULT_VOICE_NAME, | ||
| 187 | voice_id: SystemTtsProvider.BROWSER_DEFAULT_VOICE_ID, | ||
| 188 | preview_url: false, | ||
| 189 | lang: navigator.language || 'en-US', | ||
| 190 | }; | ||
| 191 | resolve([defaultVoice]); | ||
| 192 | } else { | ||
| 193 | const mappedVoices = voices | ||
| 178 | .sort((a, b) => a.lang.localeCompare(b.lang) || a.name.localeCompare(b.name)) | 194 | .sort((a, b) => a.lang.localeCompare(b.lang) || a.name.localeCompare(b.name)) |
| 179 | .map(x => ({ name: x.name, voice_id: x.voiceURI, preview_url: false, lang: x.lang })); | 195 | .map(x => ({ name: x.name, voice_id: x.voiceURI, preview_url: false, lang: x.lang })); |
| 180 | 196 | resolve(mappedVoices); | |
| 181 | resolve(voices); | 197 | } |
| 182 | }, 1); | 198 | }, 50); |
| 183 | }); | 199 | }); |
| 184 | } | 200 | } |
| 185 | 201 | ||
| 186 | previewTtsVoice(voiceId) { | 202 | previewTtsVoice(voiceId) { |
| 187 | if (!('speechSynthesis' in window)) { | 203 | if (!('speechSynthesis' in window)) { |
| 188 | throw 'Speech synthesis API is not supported'; | 204 | throw new Error('Speech synthesis API is not supported'); |
| 189 | } | 205 | } |
| 190 | 206 | ||
| 191 | const voice = speechSynthesis.getVoices().find(x => x.voiceURI === voiceId); | 207 | let voice = null; |
| 208 | if (voiceId !== SystemTtsProvider.BROWSER_DEFAULT_VOICE_ID) { | ||
| 209 | const voices = speechSynthesis.getVoices(); | ||
| 210 | voice = voices.find(x => x.voiceURI === voiceId); | ||
| 192 | 211 | ||
| 193 | if (!voice) { | 212 | if (!voice && voices.length > 0) { |
| 194 | throw `TTS Voice id ${voiceId} not found`; | 213 | console.warn(`SystemTTS Preview: Voice ID "${voiceId}" not found among available voices. Using browser default.`); |
| 214 | } else if (!voice && voices.length === 0) { | ||
| 215 | console.warn('SystemTTS Preview: Voice list is empty. Using browser default.'); | ||
| 216 | } | ||
| 217 | } else { | ||
| 218 | console.log('SystemTTS Preview: Using browser default voice as requested.'); | ||
| 195 | } | 219 | } |
| 196 | 220 | ||
| 197 | speechSynthesis.cancel(); | 221 | speechSynthesis.cancel(); |
| 198 | const text = getPreviewString(voice.lang); | 222 | const langForPreview = voice ? voice.lang : (navigator.language || 'en-US'); |
| 223 | const text = getPreviewString(langForPreview); | ||
| 199 | const utterance = new SpeechSynthesisUtterance(text); | 224 | const utterance = new SpeechSynthesisUtterance(text); |
| 225 | |||
| 226 | if (voice) { | ||
| 200 | utterance.voice = voice; | 227 | utterance.voice = voice; |
| 228 | } | ||
| 229 | |||
| 201 | utterance.rate = this.settings.rate || 1; | 230 | utterance.rate = this.settings.rate || 1; |
| 202 | utterance.pitch = this.settings.pitch || 1; | 231 | utterance.pitch = this.settings.pitch || 1; |
| 232 | |||
| 233 | utterance.onerror = (event) => { | ||
| 234 | console.error(`SystemTTS Preview Error: ${event.error}`, event); | ||
| 235 | }; | ||
| 236 | |||
| 203 | speechSynthesis.speak(utterance); | 237 | speechSynthesis.speak(utterance); |
| 204 | } | 238 | } |
| 205 | 239 | ||
| 206 | async getVoice(voiceName) { | 240 | async getVoice(voiceName) { |
| 207 | if (!('speechSynthesis' in window)) { | 241 | if (!('speechSynthesis' in window)) { |
| 208 | return { voice_id: null }; | 242 | return { voice_id: null, name: 'API Not Supported' }; |
| 243 | } | ||
| 244 | |||
| 245 | if (voiceName === SystemTtsProvider.BROWSER_DEFAULT_VOICE_NAME) { | ||
| 246 | return { | ||
| 247 | voice_id: SystemTtsProvider.BROWSER_DEFAULT_VOICE_ID, | ||
| 248 | name: SystemTtsProvider.BROWSER_DEFAULT_VOICE_NAME, | ||
| 249 | }; | ||
| 209 | } | 250 | } |
| 210 | 251 | ||
| 211 | const voices = speechSynthesis.getVoices(); | 252 | const voices = speechSynthesis.getVoices(); |
| 253 | |||
| 254 | if (voices.length === 0) { | ||
| 255 | console.warn('SystemTTS: Empty voice list, using default fallback'); | ||
| 256 | return { | ||
| 257 | voice_id: SystemTtsProvider.BROWSER_DEFAULT_VOICE_ID, | ||
| 258 | name: SystemTtsProvider.BROWSER_DEFAULT_VOICE_NAME, | ||
| 259 | }; | ||
| 260 | } | ||
| 261 | |||
| 212 | const match = voices.find(x => x.name == voiceName); | 262 | const match = voices.find(x => x.name == voiceName); |
| 213 | 263 | ||
| 214 | if (!match) { | 264 | if (!match) { |
| 215 | throw `TTS Voice name ${voiceName} not found`; | 265 | throw new Error(`SystemTTS getVoice: TTS Voice name "${voiceName}" not found`); |
| 216 | } | 266 | } |
| 217 | 267 | ||
| 218 | return { voice_id: match.voiceURI, name: match.name }; | 268 | return { voice_id: match.voiceURI, name: match.name }; |
| @@ -237,7 +287,6 @@ class SystemTtsProvider { | |||
| 237 | speechUtteranceChunker(utterance, { | 287 | speechUtteranceChunker(utterance, { |
| 238 | chunkLength: 200, | 288 | chunkLength: 200, |
| 239 | }, function () { | 289 | }, function () { |
| 240 | //some code to execute when done | ||
| 241 | resolve(silence); | 290 | resolve(silence); |
| 242 | console.log('System TTS done'); | 291 | console.log('System TTS done'); |
| 243 | }); | 292 | }); |
| @@ -55,6 +55,8 @@ const getBatchSize = () => ['transformers', 'palm', 'ollama'].includes(settings. | |||
| 55 | const settings = { | 55 | const settings = { |
| 56 | // For both | 56 | // For both |
| 57 | source: 'transformers', | 57 | source: 'transformers', |
| 58 | alt_endpoint_url: '', | ||
| 59 | use_alt_endpoint: false, | ||
| 58 | include_wi: false, | 60 | include_wi: false, |
| 59 | togetherai_model: 'togethercomputer/m2-bert-80M-32k-retrieval', | 61 | togetherai_model: 'togethercomputer/m2-bert-80M-32k-retrieval', |
| 60 | openai_model: 'text-embedding-ada-002', | 62 | openai_model: 'text-embedding-ada-002', |
| @@ -109,6 +111,7 @@ const settings = { | |||
| 109 | const moduleWorker = new ModuleWorkerWrapper(synchronizeChat); | 111 | const moduleWorker = new ModuleWorkerWrapper(synchronizeChat); |
| 110 | const webllmProvider = new WebLlmVectorProvider(); | 112 | const webllmProvider = new WebLlmVectorProvider(); |
| 111 | const cachedSummaries = new Map(); | 113 | const cachedSummaries = new Map(); |
| 114 | const vectorApiRequiresUrl = ['llamacpp', 'vllm', 'ollama', 'koboldcpp']; | ||
| 112 | 115 | ||
| 113 | /** | 116 | /** |
| 114 | * Gets the Collection ID for a file embedded in the chat. | 117 | * Gets the Collection ID for a file embedded in the chat. |
| @@ -777,14 +780,14 @@ function getVectorsRequestBody(args = {}) { | |||
| 777 | break; | 780 | break; |
| 778 | case 'ollama': | 781 | case 'ollama': |
| 779 | body.model = extension_settings.vectors.ollama_model; | 782 | body.model = extension_settings.vectors.ollama_model; |
| 780 | body.apiUrl = textgenerationwebui_settings.server_urls[textgen_types.OLLAMA]; | 783 | body.apiUrl = settings.use_alt_endpoint ? settings.alt_endpoint_url : textgenerationwebui_settings.server_urls[textgen_types.OLLAMA]; |
| 781 | body.keep = !!extension_settings.vectors.ollama_keep; | 784 | body.keep = !!extension_settings.vectors.ollama_keep; |
| 782 | break; | 785 | break; |
| 783 | case 'llamacpp': | 786 | case 'llamacpp': |
| 784 | body.apiUrl = textgenerationwebui_settings.server_urls[textgen_types.LLAMACPP]; | 787 | body.apiUrl = settings.use_alt_endpoint ? settings.alt_endpoint_url : textgenerationwebui_settings.server_urls[textgen_types.LLAMACPP]; |
| 785 | break; | 788 | break; |
| 786 | case 'vllm': | 789 | case 'vllm': |
| 787 | body.apiUrl = textgenerationwebui_settings.server_urls[textgen_types.VLLM]; | 790 | body.apiUrl = settings.use_alt_endpoint ? settings.alt_endpoint_url : textgenerationwebui_settings.server_urls[textgen_types.VLLM]; |
| 788 | body.model = extension_settings.vectors.vllm_model; | 791 | body.model = extension_settings.vectors.vllm_model; |
| 789 | break; | 792 | break; |
| 790 | case 'webllm': | 793 | case 'webllm': |
| @@ -826,11 +829,12 @@ async function getAdditionalArgs(items) { | |||
| 826 | * @returns {Promise<number[]>} Saved hashes | 829 | * @returns {Promise<number[]>} Saved hashes |
| 827 | */ | 830 | */ |
| 828 | async function getSavedHashes(collectionId) { | 831 | async function getSavedHashes(collectionId) { |
| 832 | const args = await getAdditionalArgs([]); | ||
| 829 | const response = await fetch('/api/vector/list', { | 833 | const response = await fetch('/api/vector/list', { |
| 830 | method: 'POST', | 834 | method: 'POST', |
| 831 | headers: getRequestHeaders(), | 835 | headers: getRequestHeaders(), |
| 832 | body: JSON.stringify({ | 836 | body: JSON.stringify({ |
| 833 | ...getVectorsRequestBody(), | 837 | ...getVectorsRequestBody(args), |
| 834 | collectionId: collectionId, | 838 | collectionId: collectionId, |
| 835 | source: settings.source, | 839 | source: settings.source, |
| 836 | }), | 840 | }), |
| @@ -883,12 +887,19 @@ function throwIfSourceInvalid() { | |||
| 883 | throw new Error('Vectors: API key missing', { cause: 'api_key_missing' }); | 887 | throw new Error('Vectors: API key missing', { cause: 'api_key_missing' }); |
| 884 | } | 888 | } |
| 885 | 889 | ||
| 890 | if (vectorApiRequiresUrl.includes(settings.source) && settings.use_alt_endpoint) { | ||
| 891 | if (!settings.alt_endpoint_url) { | ||
| 892 | throw new Error('Vectors: API URL missing', { cause: 'api_url_missing' }); | ||
| 893 | } | ||
| 894 | } | ||
| 895 | else { | ||
| 886 | if (settings.source === 'ollama' && !textgenerationwebui_settings.server_urls[textgen_types.OLLAMA] || | 896 | if (settings.source === 'ollama' && !textgenerationwebui_settings.server_urls[textgen_types.OLLAMA] || |
| 887 | settings.source === 'vllm' && !textgenerationwebui_settings.server_urls[textgen_types.VLLM] || | 897 | settings.source === 'vllm' && !textgenerationwebui_settings.server_urls[textgen_types.VLLM] || |
| 888 | settings.source === 'koboldcpp' && !textgenerationwebui_settings.server_urls[textgen_types.KOBOLDCPP] || | 898 | settings.source === 'koboldcpp' && !textgenerationwebui_settings.server_urls[textgen_types.KOBOLDCPP] || |
| 889 | settings.source === 'llamacpp' && !textgenerationwebui_settings.server_urls[textgen_types.LLAMACPP]) { | 899 | settings.source === 'llamacpp' && !textgenerationwebui_settings.server_urls[textgen_types.LLAMACPP]) { |
| 890 | throw new Error('Vectors: API URL missing', { cause: 'api_url_missing' }); | 900 | throw new Error('Vectors: API URL missing', { cause: 'api_url_missing' }); |
| 891 | } | 901 | } |
| 902 | } | ||
| 892 | 903 | ||
| 893 | if (settings.source === 'ollama' && !settings.ollama_model || settings.source === 'vllm' && !settings.vllm_model) { | 904 | if (settings.source === 'ollama' && !settings.ollama_model || settings.source === 'vllm' && !settings.vllm_model) { |
| 894 | throw new Error('Vectors: API model missing', { cause: 'api_model_missing' }); | 905 | throw new Error('Vectors: API model missing', { cause: 'api_model_missing' }); |
| @@ -1087,6 +1098,7 @@ function toggleSettings() { | |||
| 1087 | $('#webllm_vectorsModel').toggle(settings.source === 'webllm'); | 1098 | $('#webllm_vectorsModel').toggle(settings.source === 'webllm'); |
| 1088 | $('#koboldcpp_vectorsModel').toggle(settings.source === 'koboldcpp'); | 1099 | $('#koboldcpp_vectorsModel').toggle(settings.source === 'koboldcpp'); |
| 1089 | $('#google_vectorsModel').toggle(settings.source === 'palm'); | 1100 | $('#google_vectorsModel').toggle(settings.source === 'palm'); |
| 1101 | $('#vector_altEndpointUrl').toggle(vectorApiRequiresUrl.includes(settings.source)); | ||
| 1090 | if (settings.source === 'webllm') { | 1102 | if (settings.source === 'webllm') { |
| 1091 | loadWebLlmModels(); | 1103 | loadWebLlmModels(); |
| 1092 | } | 1104 | } |
| @@ -1144,6 +1156,9 @@ function loadWebLlmModels() { | |||
| 1144 | * @returns {Promise<Record<string, number[]>>} Calculated embeddings | 1156 | * @returns {Promise<Record<string, number[]>>} Calculated embeddings |
| 1145 | */ | 1157 | */ |
| 1146 | async function createWebLlmEmbeddings(items) { | 1158 | async function createWebLlmEmbeddings(items) { |
| 1159 | if (items.length === 0) { | ||
| 1160 | return /** @type {Record<string, number[]>} */ ({}); | ||
| 1161 | } | ||
| 1147 | return executeWithWebLlmErrorHandling(async () => { | 1162 | return executeWithWebLlmErrorHandling(async () => { |
| 1148 | const embeddings = await webllmProvider.embedTexts(items, settings.webllm_model); | 1163 | const embeddings = await webllmProvider.embedTexts(items, settings.webllm_model); |
| 1149 | const result = /** @type {Record<string, number[]>} */ ({}); | 1164 | const result = /** @type {Record<string, number[]>} */ ({}); |
| @@ -1165,7 +1180,7 @@ async function createKoboldCppEmbeddings(items) { | |||
| 1165 | headers: getRequestHeaders(), | 1180 | headers: getRequestHeaders(), |
| 1166 | body: JSON.stringify({ | 1181 | body: JSON.stringify({ |
| 1167 | items: items, | 1182 | items: items, |
| 1168 | server: textgenerationwebui_settings.server_urls[textgen_types.KOBOLDCPP], | 1183 | server: settings.use_alt_endpoint ? settings.alt_endpoint_url : textgenerationwebui_settings.server_urls[textgen_types.KOBOLDCPP], |
| 1169 | }), | 1184 | }), |
| 1170 | }); | 1185 | }); |
| 1171 | 1186 | ||
| @@ -1467,6 +1482,16 @@ jQuery(async () => { | |||
| 1467 | saveSettingsDebounced(); | 1482 | saveSettingsDebounced(); |
| 1468 | toggleSettings(); | 1483 | toggleSettings(); |
| 1469 | }); | 1484 | }); |
| 1485 | $('#vector_altEndpointUrl_enabled').prop('checked', settings.use_alt_endpoint).on('input', () => { | ||
| 1486 | settings.use_alt_endpoint = $('#vector_altEndpointUrl_enabled').prop('checked'); | ||
| 1487 | Object.assign(extension_settings.vectors, settings); | ||
| 1488 | saveSettingsDebounced(); | ||
| 1489 | }); | ||
| 1490 | $('#vector_altEndpoint_address').val(settings.alt_endpoint_url).on('change', () => { | ||
| 1491 | settings.alt_endpoint_url = String($('#vector_altEndpoint_address').val()); | ||
| 1492 | Object.assign(extension_settings.vectors, settings); | ||
| 1493 | saveSettingsDebounced(); | ||
| 1494 | }); | ||
| 1470 | $('#api_key_nomicai').on('click', async () => { | 1495 | $('#api_key_nomicai').on('click', async () => { |
| 1471 | const popupText = 'NomicAI API Key:'; | 1496 | const popupText = 'NomicAI API Key:'; |
| 1472 | const key = await callGenericPopup(popupText, POPUP_TYPE.INPUT, '', { | 1497 | const key = await callGenericPopup(popupText, POPUP_TYPE.INPUT, '', { |
| @@ -25,6 +25,16 @@ | |||
| 25 | <option value="webllm" data-i18n="WebLLM Extension">WebLLM Extension</option> | 25 | <option value="webllm" data-i18n="WebLLM Extension">WebLLM Extension</option> |
| 26 | </select> | 26 | </select> |
| 27 | </div> | 27 | </div> |
| 28 | <div class="flex-container flexFlowColumn" id="vector_altEndpointUrl"> | ||
| 29 | <label class="checkbox_label" for="vector_altEndpointUrl_enabled" title="Enable secondary endpoint URL usage, instead of the main one."> | ||
| 30 | <input id="vector_altEndpointUrl_enabled" type="checkbox" class="checkbox"> | ||
| 31 | <span data-i18n="Use secondary URL">Use secondary URL</span> | ||
| 32 | </label> | ||
| 33 | <label for="vector_altEndpoint_address" data-i18n="Secondary Embedding endpoint URL"> | ||
| 34 | Secondary Embedding endpoint URL | ||
| 35 | </label> | ||
| 36 | <input id="vector_altEndpoint_address" class="text_pole" type="text" placeholder="e.g. http://localhost:5001" /> | ||
| 37 | </div> | ||
| 28 | <div class="flex-container flexFlowColumn" id="webllm_vectorsModel"> | 38 | <div class="flex-container flexFlowColumn" id="webllm_vectorsModel"> |
| 29 | <label for="vectors_webllm_model" data-i18n="Vectorization Model"> | 39 | <label for="vectors_webllm_model" data-i18n="Vectorization Model"> |
| 30 | Vectorization Model | 40 | Vectorization Model |
| @@ -87,6 +97,7 @@ | |||
| 87 | Vectorization Model | 97 | Vectorization Model |
| 88 | </label> | 98 | </label> |
| 89 | <select id="vectors_cohere_model" class="text_pole"> | 99 | <select id="vectors_cohere_model" class="text_pole"> |
| 100 | <option value="embed-v4.0">embed-v4.0</option> | ||
| 90 | <option value="embed-english-v3.0">embed-english-v3.0</option> | 101 | <option value="embed-english-v3.0">embed-english-v3.0</option> |
| 91 | <option value="embed-multilingual-v3.0">embed-multilingual-v3.0</option> | 102 | <option value="embed-multilingual-v3.0">embed-multilingual-v3.0</option> |
| 92 | <option value="embed-english-light-v3.0">embed-english-light-v3.0</option> | 103 | <option value="embed-english-light-v3.0">embed-english-light-v3.0</option> |
| @@ -310,7 +321,7 @@ | |||
| 310 | <label for="vectors_file_depth_db" title="How many messages before the current end of the chat." data-i18n="[title]How many messages before the current end of the chat."> | 321 | <label for="vectors_file_depth_db" title="How many messages before the current end of the chat." data-i18n="[title]How many messages before the current end of the chat."> |
| 311 | <input type="radio" name="vectors_file_position_db" value="1" /> | 322 | <input type="radio" name="vectors_file_position_db" value="1" /> |
| 312 | <span data-i18n="In-chat @ Depth">In-chat @ Depth</span> | 323 | <span data-i18n="In-chat @ Depth">In-chat @ Depth</span> |
| 313 | <input id="vectors_file_depth_db" class="text_pole widthUnset" type="number" min="0" max="999" /> | 324 | <input id="vectors_file_depth_db" class="text_pole widthUnset" type="number" min="0" max="9999" /> |
| 314 | <span>as</span> | 325 | <span>as</span> |
| 315 | <select id="vectors_file_depth_role_db" class="text_pole widthNatural"> | 326 | <select id="vectors_file_depth_role_db" class="text_pole widthNatural"> |
| 316 | <option value="0" data-i18n="System">System</option> | 327 | <option value="0" data-i18n="System">System</option> |
| @@ -362,7 +373,7 @@ | |||
| 362 | <label for="vectors_depth" title="How many messages before the current end of the chat." data-i18n="[title]How many messages before the current end of the chat."> | 373 | <label for="vectors_depth" title="How many messages before the current end of the chat." data-i18n="[title]How many messages before the current end of the chat."> |
| 363 | <input type="radio" name="vectors_position" value="1" /> | 374 | <input type="radio" name="vectors_position" value="1" /> |
| 364 | <span data-i18n="In-chat @ Depth">In-chat @ Depth </span> | 375 | <span data-i18n="In-chat @ Depth">In-chat @ Depth </span> |
| 365 | <input id="vectors_depth" class="text_pole widthUnset" type="number" min="0" max="999" /> | 376 | <input id="vectors_depth" class="text_pole widthUnset" type="number" min="0" max="9999" /> |
| 366 | </label> | 377 | </label> |
| 367 | </div> | 378 | </div> |
| 368 | <div class="flex-container"> | 379 | <div class="flex-container"> |
| @@ -13,6 +13,9 @@ import { | |||
| 13 | getBase64Async, | 13 | getBase64Async, |
| 14 | resetScrollHeight, | 14 | resetScrollHeight, |
| 15 | initScrollHeight, | 15 | initScrollHeight, |
| 16 | localizePagination, | ||
| 17 | renderPaginationDropdown, | ||
| 18 | paginationDropdownChangeHandler, | ||
| 16 | } from './utils.js'; | 19 | } from './utils.js'; |
| 17 | import { RA_CountCharTokens, humanizedDateTime, dragElement, favsToHotswap, getMessageTimeStamp } from './RossAscends-mods.js'; | 20 | import { RA_CountCharTokens, humanizedDateTime, dragElement, favsToHotswap, getMessageTimeStamp } from './RossAscends-mods.js'; |
| 18 | import { power_user, loadMovingUIState, sortEntitiesList } from './power-user.js'; | 21 | import { power_user, loadMovingUIState, sortEntitiesList } from './power-user.js'; |
| @@ -1374,6 +1377,8 @@ function getGroupCharacters({ doFilter, onlyMembers } = {}) { | |||
| 1374 | 1377 | ||
| 1375 | function printGroupCandidates() { | 1378 | function printGroupCandidates() { |
| 1376 | const storageKey = 'GroupCandidates_PerPage'; | 1379 | const storageKey = 'GroupCandidates_PerPage'; |
| 1380 | const pageSize = Number(accountStorage.getItem(storageKey)) || 5; | ||
| 1381 | const sizeChangerOptions = [5, 10, 25, 50, 100, 200, 500, 1000]; | ||
| 1377 | $('#rm_group_add_members_pagination').pagination({ | 1382 | $('#rm_group_add_members_pagination').pagination({ |
| 1378 | dataSource: getGroupCharacters({ doFilter: true, onlyMembers: false }), | 1383 | dataSource: getGroupCharacters({ doFilter: true, onlyMembers: false }), |
| 1379 | pageRange: 1, | 1384 | pageRange: 1, |
| @@ -1382,18 +1387,20 @@ function printGroupCandidates() { | |||
| 1382 | prevText: '<', | 1387 | prevText: '<', |
| 1383 | nextText: '>', | 1388 | nextText: '>', |
| 1384 | formatNavigator: PAGINATION_TEMPLATE, | 1389 | formatNavigator: PAGINATION_TEMPLATE, |
| 1390 | formatSizeChanger: renderPaginationDropdown(pageSize, sizeChangerOptions), | ||
| 1385 | showNavigator: true, | 1391 | showNavigator: true, |
| 1386 | showSizeChanger: true, | 1392 | showSizeChanger: true, |
| 1387 | pageSize: Number(accountStorage.getItem(storageKey)) || 5, | 1393 | pageSize, |
| 1388 | sizeChangerOptions: [5, 10, 25, 50, 100, 200, 500, 1000], | 1394 | afterSizeSelectorChange: function (e, size) { |
| 1389 | afterSizeSelectorChange: function (e) { | ||
| 1390 | accountStorage.setItem(storageKey, e.target.value); | 1395 | accountStorage.setItem(storageKey, e.target.value); |
| 1396 | paginationDropdownChangeHandler(e, size); | ||
| 1391 | }, | 1397 | }, |
| 1392 | callback: function (data) { | 1398 | callback: function (data) { |
| 1393 | $('#rm_group_add_members').empty(); | 1399 | $('#rm_group_add_members').empty(); |
| 1394 | for (const i of data) { | 1400 | for (const i of data) { |
| 1395 | $('#rm_group_add_members').append(getGroupCharacterBlock(i.item)); | 1401 | $('#rm_group_add_members').append(getGroupCharacterBlock(i.item)); |
| 1396 | } | 1402 | } |
| 1403 | localizePagination($('#rm_group_add_members_pagination')); | ||
| 1397 | }, | 1404 | }, |
| 1398 | }); | 1405 | }); |
| 1399 | } | 1406 | } |
| @@ -1401,6 +1408,9 @@ function printGroupCandidates() { | |||
| 1401 | function printGroupMembers() { | 1408 | function printGroupMembers() { |
| 1402 | const storageKey = 'GroupMembers_PerPage'; | 1409 | const storageKey = 'GroupMembers_PerPage'; |
| 1403 | $('.rm_group_members_pagination').each(function () { | 1410 | $('.rm_group_members_pagination').each(function () { |
| 1411 | let that = this; | ||
| 1412 | const pageSize = Number(accountStorage.getItem(storageKey)) || 5; | ||
| 1413 | const sizeChangerOptions = [5, 10, 25, 50, 100, 200, 500, 1000]; | ||
| 1404 | $(this).pagination({ | 1414 | $(this).pagination({ |
| 1405 | dataSource: getGroupCharacters({ doFilter: false, onlyMembers: true }), | 1415 | dataSource: getGroupCharacters({ doFilter: false, onlyMembers: true }), |
| 1406 | pageRange: 1, | 1416 | pageRange: 1, |
| @@ -1411,16 +1421,18 @@ function printGroupMembers() { | |||
| 1411 | formatNavigator: PAGINATION_TEMPLATE, | 1421 | formatNavigator: PAGINATION_TEMPLATE, |
| 1412 | showNavigator: true, | 1422 | showNavigator: true, |
| 1413 | showSizeChanger: true, | 1423 | showSizeChanger: true, |
| 1414 | pageSize: Number(accountStorage.getItem(storageKey)) || 5, | 1424 | formatSizeChanger: renderPaginationDropdown(pageSize, sizeChangerOptions), |
| 1415 | sizeChangerOptions: [5, 10, 25, 50, 100, 200, 500, 1000], | 1425 | pageSize, |
| 1416 | afterSizeSelectorChange: function (e) { | 1426 | afterSizeSelectorChange: function (e, size) { |
| 1417 | accountStorage.setItem(storageKey, e.target.value); | 1427 | accountStorage.setItem(storageKey, e.target.value); |
| 1428 | paginationDropdownChangeHandler(e, size); | ||
| 1418 | }, | 1429 | }, |
| 1419 | callback: function (data) { | 1430 | callback: function (data) { |
| 1420 | $('.rm_group_members').empty(); | 1431 | $('.rm_group_members').empty(); |
| 1421 | for (const i of data) { | 1432 | for (const i of data) { |
| 1422 | $('.rm_group_members').append(getGroupCharacterBlock(i.item)); | 1433 | $('.rm_group_members').append(getGroupCharacterBlock(i.item)); |
| 1423 | } | 1434 | } |
| 1435 | localizePagination($(that)); | ||
| 1424 | }, | 1436 | }, |
| 1425 | }); | 1437 | }); |
| 1426 | }); | 1438 | }); |
| @@ -1804,7 +1816,7 @@ async function createGroup() { | |||
| 1804 | const memberNames = characters.filter(x => members.includes(x.avatar)).map(x => x.name).join(', '); | 1816 | const memberNames = characters.filter(x => members.includes(x.avatar)).map(x => x.name).join(', '); |
| 1805 | 1817 | ||
| 1806 | if (!name) { | 1818 | if (!name) { |
| 1807 | name = `Group: ${memberNames}`; | 1819 | name = t`Group: ${memberNames}`; |
| 1808 | } | 1820 | } |
| 1809 | 1821 | ||
| 1810 | const avatar_url = $('#group_avatar_preview img').attr('src'); | 1822 | const avatar_url = $('#group_avatar_preview img').attr('src'); |
| @@ -394,7 +394,7 @@ function getHordeModelTemplate(option) { | |||
| 394 | `)); | 394 | `)); |
| 395 | } | 395 | } |
| 396 | 396 | ||
| 397 | jQuery(function () { | 397 | export function initHorde () { |
| 398 | $('#horde_model').on('mousedown change', async function (e) { | 398 | $('#horde_model').on('mousedown change', async function (e) { |
| 399 | console.log('Horde model change', e); | 399 | console.log('Horde model change', e); |
| 400 | horde_settings.models = $('#horde_model').val(); | 400 | horde_settings.models = $('#horde_model').val(); |
| @@ -441,7 +441,7 @@ jQuery(function () { | |||
| 441 | if (!isMobile()) { | 441 | if (!isMobile()) { |
| 442 | $('#horde_model').select2({ | 442 | $('#horde_model').select2({ |
| 443 | width: '100%', | 443 | width: '100%', |
| 444 | placeholder: 'Select Horde models', | 444 | placeholder: t`Select Horde models`, |
| 445 | allowClear: true, | 445 | allowClear: true, |
| 446 | closeOnSelect: false, | 446 | closeOnSelect: false, |
| 447 | templateSelection: function (data) { | 447 | templateSelection: function (data) { |
| @@ -451,5 +451,5 @@ jQuery(function () { | |||
| 451 | templateResult: getHordeModelTemplate, | 451 | templateResult: getHordeModelTemplate, |
| 452 | }); | 452 | }); |
| 453 | } | 453 | } |
| 454 | }); | 454 | } |
| 455 | 455 | ||
| @@ -208,16 +208,7 @@ export function autoSelectInstructPreset(modelId) { | |||
| 208 | 208 | ||
| 209 | // Select matching instruct preset | 209 | // Select matching instruct preset |
| 210 | let foundMatch = false; | 210 | let foundMatch = false; |
| 211 | for (const instruct_preset of instruct_presets) { | 211 | |
| 212 | // If instruct preset matches the context template | ||
| 213 | if (power_user.instruct.bind_to_context && instruct_preset.name === power_user.context.preset) { | ||
| 214 | foundMatch = true; | ||
| 215 | selectInstructPreset(instruct_preset.name, { isAuto: true }); | ||
| 216 | break; | ||
| 217 | } | ||
| 218 | } | ||
| 219 | // If no match was found, auto-select instruct preset | ||
| 220 | if (!foundMatch) { | ||
| 221 | for (const preset of instruct_presets) { | 212 | for (const preset of instruct_presets) { |
| 222 | // If activation regex is set, check if it matches the model id | 213 | // If activation regex is set, check if it matches the model id |
| 223 | if (preset.activation_regex) { | 214 | if (preset.activation_regex) { |
| @@ -227,8 +218,8 @@ export function autoSelectInstructPreset(modelId) { | |||
| 227 | // Stop on first match so it won't cycle back and forth between presets if multiple regexes match | 218 | // Stop on first match so it won't cycle back and forth between presets if multiple regexes match |
| 228 | if (regex instanceof RegExp && regex.test(modelId)) { | 219 | if (regex instanceof RegExp && regex.test(modelId)) { |
| 229 | selectInstructPreset(preset.name, { isAuto: true }); | 220 | selectInstructPreset(preset.name, { isAuto: true }); |
| 230 | 221 | foundMatch = true; | |
| 231 | return true; | 222 | break; |
| 232 | } | 223 | } |
| 233 | } catch { | 224 | } catch { |
| 234 | // If regex is invalid, ignore it | 225 | // If regex is invalid, ignore it |
| @@ -236,9 +227,20 @@ export function autoSelectInstructPreset(modelId) { | |||
| 236 | } | 227 | } |
| 237 | } | 228 | } |
| 238 | } | 229 | } |
| 230 | |||
| 231 | // If no match was found, auto-select instruct preset | ||
| 232 | if (!foundMatch && power_user.instruct.bind_to_context) { | ||
| 233 | for (const instruct_preset of instruct_presets) { | ||
| 234 | // If instruct preset matches the context template | ||
| 235 | if (instruct_preset.name === power_user.context.preset) { | ||
| 236 | selectInstructPreset(instruct_preset.name, { isAuto: true }); | ||
| 237 | foundMatch = true; | ||
| 238 | break; | ||
| 239 | } | ||
| 240 | } | ||
| 239 | } | 241 | } |
| 240 | 242 | ||
| 241 | return false; | 243 | return foundMatch; |
| 242 | } | 244 | } |
| 243 | 245 | ||
| 244 | /** | 246 | /** |
| @@ -15,12 +15,12 @@ import { | |||
| 15 | extension_prompt_types, | 15 | extension_prompt_types, |
| 16 | Generate, | 16 | Generate, |
| 17 | getExtensionPrompt, | 17 | getExtensionPrompt, |
| 18 | getExtensionPromptMaxDepth, | ||
| 18 | getNextMessageId, | 19 | getNextMessageId, |
| 19 | getRequestHeaders, | 20 | getRequestHeaders, |
| 20 | getStoppingStrings, | 21 | getStoppingStrings, |
| 21 | is_send_press, | 22 | is_send_press, |
| 22 | main_api, | 23 | main_api, |
| 23 | MAX_INJECTION_DEPTH, | ||
| 24 | name1, | 24 | name1, |
| 25 | name2, | 25 | name2, |
| 26 | replaceItemizedPromptText, | 26 | replaceItemizedPromptText, |
| @@ -184,6 +184,7 @@ export const chat_completion_sources = { | |||
| 184 | ZEROONEAI: '01ai', | 184 | ZEROONEAI: '01ai', |
| 185 | NANOGPT: 'nanogpt', | 185 | NANOGPT: 'nanogpt', |
| 186 | DEEPSEEK: 'deepseek', | 186 | DEEPSEEK: 'deepseek', |
| 187 | XAI: 'xai', | ||
| 187 | }; | 188 | }; |
| 188 | 189 | ||
| 189 | const character_names_behavior = { | 190 | const character_names_behavior = { |
| @@ -215,6 +216,15 @@ const openrouter_middleout_types = { | |||
| 215 | OFF: 'off', | 216 | OFF: 'off', |
| 216 | }; | 217 | }; |
| 217 | 218 | ||
| 219 | export const reasoning_effort_types = { | ||
| 220 | auto: 'auto', | ||
| 221 | low: 'low', | ||
| 222 | medium: 'medium', | ||
| 223 | high: 'high', | ||
| 224 | min: 'min', | ||
| 225 | max: 'max', | ||
| 226 | }; | ||
| 227 | |||
| 218 | const sensitiveFields = [ | 228 | const sensitiveFields = [ |
| 219 | 'reverse_proxy', | 229 | 'reverse_proxy', |
| 220 | 'proxy_password', | 230 | 'proxy_password', |
| @@ -257,6 +267,7 @@ export const settingsToUpdate = { | |||
| 257 | nanogpt_model: ['#model_nanogpt_select', 'nanogpt_model', false], | 267 | nanogpt_model: ['#model_nanogpt_select', 'nanogpt_model', false], |
| 258 | deepseek_model: ['#model_deepseek_select', 'deepseek_model', false], | 268 | deepseek_model: ['#model_deepseek_select', 'deepseek_model', false], |
| 259 | zerooneai_model: ['#model_01ai_select', 'zerooneai_model', false], | 269 | zerooneai_model: ['#model_01ai_select', 'zerooneai_model', false], |
| 270 | xai_model: ['#model_xai_select', 'xai_model', false], | ||
| 260 | custom_model: ['#custom_model_id', 'custom_model', false], | 271 | custom_model: ['#custom_model_id', 'custom_model', false], |
| 261 | custom_url: ['#custom_api_url_text', 'custom_url', false], | 272 | custom_url: ['#custom_api_url_text', 'custom_url', false], |
| 262 | custom_include_body: ['#custom_include_body', 'custom_include_body', false], | 273 | custom_include_body: ['#custom_include_body', 'custom_include_body', false], |
| @@ -345,6 +356,7 @@ const default_settings = { | |||
| 345 | nanogpt_model: 'gpt-4o-mini', | 356 | nanogpt_model: 'gpt-4o-mini', |
| 346 | zerooneai_model: 'yi-large', | 357 | zerooneai_model: 'yi-large', |
| 347 | deepseek_model: 'deepseek-chat', | 358 | deepseek_model: 'deepseek-chat', |
| 359 | xai_model: 'grok-3-beta', | ||
| 348 | custom_model: '', | 360 | custom_model: '', |
| 349 | custom_url: '', | 361 | custom_url: '', |
| 350 | custom_include_body: '', | 362 | custom_include_body: '', |
| @@ -379,7 +391,7 @@ const default_settings = { | |||
| 379 | continue_postfix: continue_postfix_types.SPACE, | 391 | continue_postfix: continue_postfix_types.SPACE, |
| 380 | custom_prompt_post_processing: custom_prompt_post_processing_types.NONE, | 392 | custom_prompt_post_processing: custom_prompt_post_processing_types.NONE, |
| 381 | show_thoughts: true, | 393 | show_thoughts: true, |
| 382 | reasoning_effort: 'medium', | 394 | reasoning_effort: reasoning_effort_types.auto, |
| 383 | enable_web_search: false, | 395 | enable_web_search: false, |
| 384 | request_images: false, | 396 | request_images: false, |
| 385 | seed: -1, | 397 | seed: -1, |
| @@ -425,6 +437,7 @@ const oai_settings = { | |||
| 425 | nanogpt_model: 'gpt-4o-mini', | 437 | nanogpt_model: 'gpt-4o-mini', |
| 426 | zerooneai_model: 'yi-large', | 438 | zerooneai_model: 'yi-large', |
| 427 | deepseek_model: 'deepseek-chat', | 439 | deepseek_model: 'deepseek-chat', |
| 440 | xai_model: 'grok-3-beta', | ||
| 428 | custom_model: '', | 441 | custom_model: '', |
| 429 | custom_url: '', | 442 | custom_url: '', |
| 430 | custom_include_body: '', | 443 | custom_include_body: '', |
| @@ -459,7 +472,7 @@ const oai_settings = { | |||
| 459 | continue_postfix: continue_postfix_types.SPACE, | 472 | continue_postfix: continue_postfix_types.SPACE, |
| 460 | custom_prompt_post_processing: custom_prompt_post_processing_types.NONE, | 473 | custom_prompt_post_processing: custom_prompt_post_processing_types.NONE, |
| 461 | show_thoughts: true, | 474 | show_thoughts: true, |
| 462 | reasoning_effort: 'medium', | 475 | reasoning_effort: reasoning_effort_types.auto, |
| 463 | enable_web_search: false, | 476 | enable_web_search: false, |
| 464 | request_images: false, | 477 | request_images: false, |
| 465 | seed: -1, | 478 | seed: -1, |
| @@ -738,7 +751,8 @@ async function populationInjectionPrompts(prompts, messages) { | |||
| 738 | 'assistant': extension_prompt_roles.ASSISTANT, | 751 | 'assistant': extension_prompt_roles.ASSISTANT, |
| 739 | }; | 752 | }; |
| 740 | 753 | ||
| 741 | for (let i = 0; i <= MAX_INJECTION_DEPTH; i++) { | 754 | const maxDepth = getExtensionPromptMaxDepth(); |
| 755 | for (let i = 0; i <= maxDepth; i++) { | ||
| 742 | // Get prompts for current depth | 756 | // Get prompts for current depth |
| 743 | const depthPrompts = prompts.filter(prompt => prompt.injection_depth === i && prompt.content); | 757 | const depthPrompts = prompts.filter(prompt => prompt.injection_depth === i && prompt.content); |
| 744 | 758 | ||
| @@ -1407,9 +1421,9 @@ export async function prepareOpenAIMessages({ | |||
| 1407 | await populateChatCompletion(prompts, chatCompletion, { bias, quietPrompt, quietImage, type, cyclePrompt, messages, messageExamples }); | 1421 | await populateChatCompletion(prompts, chatCompletion, { bias, quietPrompt, quietImage, type, cyclePrompt, messages, messageExamples }); |
| 1408 | } catch (error) { | 1422 | } catch (error) { |
| 1409 | if (error instanceof TokenBudgetExceededError) { | 1423 | if (error instanceof TokenBudgetExceededError) { |
| 1410 | toastr.error(t`An error occurred while counting tokens: Token budget exceeded.`); | 1424 | toastr.error(t`Mandatory prompts exceed the context size.`); |
| 1411 | chatCompletion.log('Token budget exceeded.'); | 1425 | chatCompletion.log('Mandatory prompts exceed the context size.'); |
| 1412 | promptManager.error = t`Not enough free tokens for mandatory prompts. Raise your token Limit or disable custom prompts.`; | 1426 | promptManager.error = t`Not enough free tokens for mandatory prompts. Raise your token limit or disable custom prompts.`; |
| 1413 | } else if (error instanceof InvalidCharacterNameError) { | 1427 | } else if (error instanceof InvalidCharacterNameError) { |
| 1414 | toastr.warning(t`An error occurred while counting tokens: Invalid character name`); | 1428 | toastr.warning(t`An error occurred while counting tokens: Invalid character name`); |
| 1415 | chatCompletion.log('Invalid character name'); | 1429 | chatCompletion.log('Invalid character name'); |
| @@ -1644,6 +1658,8 @@ export function getChatCompletionModel(source = null) { | |||
| 1644 | return oai_settings.nanogpt_model; | 1658 | return oai_settings.nanogpt_model; |
| 1645 | case chat_completion_sources.DEEPSEEK: | 1659 | case chat_completion_sources.DEEPSEEK: |
| 1646 | return oai_settings.deepseek_model; | 1660 | return oai_settings.deepseek_model; |
| 1661 | case chat_completion_sources.XAI: | ||
| 1662 | return oai_settings.xai_model; | ||
| 1647 | default: | 1663 | default: |
| 1648 | throw new Error(`Unknown chat completion source: ${activeSource}`); | 1664 | throw new Error(`Unknown chat completion source: ${activeSource}`); |
| 1649 | } | 1665 | } |
| @@ -1687,6 +1703,11 @@ function calculateOpenRouterCost() { | |||
| 1687 | } | 1703 | } |
| 1688 | } | 1704 | } |
| 1689 | 1705 | ||
| 1706 | if (oai_settings.enable_web_search) { | ||
| 1707 | const webSearchCost = (0.02).toFixed(2); | ||
| 1708 | cost = t`${cost} + $${webSearchCost}`; | ||
| 1709 | } | ||
| 1710 | |||
| 1690 | $('#openrouter_max_prompt_cost').text(cost); | 1711 | $('#openrouter_max_prompt_cost').text(cost); |
| 1691 | } | 1712 | } |
| 1692 | 1713 | ||
| @@ -1925,6 +1946,31 @@ async function sendAltScaleRequest(messages, logit_bias, signal, type) { | |||
| 1925 | return data.output; | 1946 | return data.output; |
| 1926 | } | 1947 | } |
| 1927 | 1948 | ||
| 1949 | function getReasoningEffort() { | ||
| 1950 | // These sources expect the effort as string. | ||
| 1951 | const reasoningEffortSources = [ | ||
| 1952 | chat_completion_sources.OPENAI, | ||
| 1953 | chat_completion_sources.CUSTOM, | ||
| 1954 | chat_completion_sources.XAI, | ||
| 1955 | chat_completion_sources.OPENROUTER, | ||
| 1956 | ]; | ||
| 1957 | |||
| 1958 | if (!reasoningEffortSources.includes(oai_settings.chat_completion_source)) { | ||
| 1959 | return oai_settings.reasoning_effort; | ||
| 1960 | } | ||
| 1961 | |||
| 1962 | switch (oai_settings.reasoning_effort) { | ||
| 1963 | case reasoning_effort_types.auto: | ||
| 1964 | return undefined; | ||
| 1965 | case reasoning_effort_types.min: | ||
| 1966 | return reasoning_effort_types.low; | ||
| 1967 | case reasoning_effort_types.max: | ||
| 1968 | return reasoning_effort_types.high; | ||
| 1969 | default: | ||
| 1970 | return oai_settings.reasoning_effort; | ||
| 1971 | } | ||
| 1972 | } | ||
| 1973 | |||
| 1928 | /** | 1974 | /** |
| 1929 | * Send a chat completion request to backend | 1975 | * Send a chat completion request to backend |
| 1930 | * @param {string} type (impersonate, quiet, continue, etc) | 1976 | * @param {string} type (impersonate, quiet, continue, etc) |
| @@ -1961,13 +2007,14 @@ async function sendOpenAIRequest(type, messages, signal) { | |||
| 1961 | const is01AI = oai_settings.chat_completion_source == chat_completion_sources.ZEROONEAI; | 2007 | const is01AI = oai_settings.chat_completion_source == chat_completion_sources.ZEROONEAI; |
| 1962 | const isNano = oai_settings.chat_completion_source == chat_completion_sources.NANOGPT; | 2008 | const isNano = oai_settings.chat_completion_source == chat_completion_sources.NANOGPT; |
| 1963 | const isDeepSeek = oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK; | 2009 | const isDeepSeek = oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK; |
| 2010 | const isXAI = oai_settings.chat_completion_source == chat_completion_sources.XAI; | ||
| 1964 | const isTextCompletion = isOAI && textCompletionModels.includes(oai_settings.openai_model); | 2011 | const isTextCompletion = isOAI && textCompletionModels.includes(oai_settings.openai_model); |
| 1965 | const isQuiet = type === 'quiet'; | 2012 | const isQuiet = type === 'quiet'; |
| 1966 | const isImpersonate = type === 'impersonate'; | 2013 | const isImpersonate = type === 'impersonate'; |
| 1967 | const isContinue = type === 'continue'; | 2014 | const isContinue = type === 'continue'; |
| 1968 | const stream = oai_settings.stream_openai && !isQuiet && !isScale && !(isOAI && ['o1-2024-12-17', 'o1'].includes(oai_settings.openai_model)); | 2015 | const stream = oai_settings.stream_openai && !isQuiet && !isScale && !(isOAI && ['o1-2024-12-17', 'o1'].includes(oai_settings.openai_model)); |
| 1969 | const useLogprobs = !!power_user.request_token_probabilities; | 2016 | const useLogprobs = !!power_user.request_token_probabilities; |
| 1970 | const canMultiSwipe = oai_settings.n > 1 && !isContinue && !isImpersonate && !isQuiet && (isOAI || isCustom); | 2017 | const canMultiSwipe = oai_settings.n > 1 && !isContinue && !isImpersonate && !isQuiet && (isOAI || isCustom || isXAI); |
| 1971 | 2018 | ||
| 1972 | // If we're using the window.ai extension, use that instead | 2019 | // If we're using the window.ai extension, use that instead |
| 1973 | // Doesn't support logit bias yet | 2020 | // Doesn't support logit bias yet |
| @@ -2010,7 +2057,7 @@ async function sendOpenAIRequest(type, messages, signal) { | |||
| 2010 | 'char_name': name2, | 2057 | 'char_name': name2, |
| 2011 | 'group_names': getGroupNames(), | 2058 | 'group_names': getGroupNames(), |
| 2012 | 'include_reasoning': Boolean(oai_settings.show_thoughts), | 2059 | 'include_reasoning': Boolean(oai_settings.show_thoughts), |
| 2013 | 'reasoning_effort': String(oai_settings.reasoning_effort), | 2060 | 'reasoning_effort': getReasoningEffort(), |
| 2014 | 'enable_web_search': Boolean(oai_settings.enable_web_search), | 2061 | 'enable_web_search': Boolean(oai_settings.enable_web_search), |
| 2015 | 'request_images': Boolean(oai_settings.request_images), | 2062 | 'request_images': Boolean(oai_settings.request_images), |
| 2016 | 'custom_prompt_post_processing': oai_settings.custom_prompt_post_processing, | 2063 | 'custom_prompt_post_processing': oai_settings.custom_prompt_post_processing, |
| @@ -2026,14 +2073,14 @@ async function sendOpenAIRequest(type, messages, signal) { | |||
| 2026 | } | 2073 | } |
| 2027 | 2074 | ||
| 2028 | // Proxy is only supported for Claude, OpenAI, Mistral, and Google MakerSuite | 2075 | // Proxy is only supported for Claude, OpenAI, Mistral, and Google MakerSuite |
| 2029 | if (oai_settings.reverse_proxy && [chat_completion_sources.CLAUDE, chat_completion_sources.OPENAI, chat_completion_sources.MISTRALAI, chat_completion_sources.MAKERSUITE, chat_completion_sources.DEEPSEEK].includes(oai_settings.chat_completion_source)) { | 2076 | if (oai_settings.reverse_proxy && [chat_completion_sources.CLAUDE, chat_completion_sources.OPENAI, chat_completion_sources.MISTRALAI, chat_completion_sources.MAKERSUITE, chat_completion_sources.DEEPSEEK, chat_completion_sources.XAI].includes(oai_settings.chat_completion_source)) { |
| 2030 | await validateReverseProxy(); | 2077 | await validateReverseProxy(); |
| 2031 | generate_data['reverse_proxy'] = oai_settings.reverse_proxy; | 2078 | generate_data['reverse_proxy'] = oai_settings.reverse_proxy; |
| 2032 | generate_data['proxy_password'] = oai_settings.proxy_password; | 2079 | generate_data['proxy_password'] = oai_settings.proxy_password; |
| 2033 | } | 2080 | } |
| 2034 | 2081 | ||
| 2035 | // Add logprobs request (currently OpenAI only, max 5 on their side) | 2082 | // Add logprobs request (currently OpenAI only, max 5 on their side) |
| 2036 | if (useLogprobs && (isOAI || isCustom || isDeepSeek)) { | 2083 | if (useLogprobs && (isOAI || isCustom || isDeepSeek || isXAI)) { |
| 2037 | generate_data['logprobs'] = 5; | 2084 | generate_data['logprobs'] = 5; |
| 2038 | } | 2085 | } |
| 2039 | 2086 | ||
| @@ -2152,29 +2199,42 @@ async function sendOpenAIRequest(type, messages, signal) { | |||
| 2152 | } | 2199 | } |
| 2153 | } | 2200 | } |
| 2154 | 2201 | ||
| 2155 | if ((isOAI || isOpenRouter || isMistral || isCustom || isCohere || isNano) && oai_settings.seed >= 0) { | 2202 | if (isXAI) { |
| 2156 | generate_data['seed'] = oai_settings.seed; | 2203 | if (generate_data.model.includes('grok-3-mini')) { |
| 2204 | delete generate_data.presence_penalty; | ||
| 2205 | delete generate_data.frequency_penalty; | ||
| 2206 | } | ||
| 2207 | if (generate_data.model.includes('grok-vision')) { | ||
| 2208 | delete generate_data.tools; | ||
| 2209 | delete generate_data.tool_choice; | ||
| 2210 | } | ||
| 2157 | } | 2211 | } |
| 2158 | 2212 | ||
| 2159 | if (isOAI && (oai_settings.openai_model.startsWith('o1') || oai_settings.openai_model.startsWith('o3'))) { | 2213 | if ((isOAI || isOpenRouter || isMistral || isCustom || isCohere || isNano || isXAI) && oai_settings.seed >= 0) { |
| 2160 | generate_data.messages.forEach((msg) => { | 2214 | generate_data['seed'] = oai_settings.seed; |
| 2161 | if (msg.role === 'system') { | ||
| 2162 | msg.role = 'user'; | ||
| 2163 | } | 2215 | } |
| 2164 | }); | 2216 | |
| 2217 | if (isOAI && /^(o1|o3|o4)/.test(oai_settings.openai_model)) { | ||
| 2165 | generate_data.max_completion_tokens = generate_data.max_tokens; | 2218 | generate_data.max_completion_tokens = generate_data.max_tokens; |
| 2166 | delete generate_data.max_tokens; | 2219 | delete generate_data.max_tokens; |
| 2167 | delete generate_data.logprobs; | 2220 | delete generate_data.logprobs; |
| 2168 | delete generate_data.top_logprobs; | 2221 | delete generate_data.top_logprobs; |
| 2169 | delete generate_data.n; | 2222 | delete generate_data.stop; |
| 2223 | delete generate_data.logit_bias; | ||
| 2170 | delete generate_data.temperature; | 2224 | delete generate_data.temperature; |
| 2171 | delete generate_data.top_p; | 2225 | delete generate_data.top_p; |
| 2172 | delete generate_data.frequency_penalty; | 2226 | delete generate_data.frequency_penalty; |
| 2173 | delete generate_data.presence_penalty; | 2227 | delete generate_data.presence_penalty; |
| 2228 | if (oai_settings.openai_model.startsWith('o1')) { | ||
| 2229 | generate_data.messages.forEach((msg) => { | ||
| 2230 | if (msg.role === 'system') { | ||
| 2231 | msg.role = 'user'; | ||
| 2232 | } | ||
| 2233 | }); | ||
| 2234 | delete generate_data.n; | ||
| 2174 | delete generate_data.tools; | 2235 | delete generate_data.tools; |
| 2175 | delete generate_data.tool_choice; | 2236 | delete generate_data.tool_choice; |
| 2176 | delete generate_data.stop; | 2237 | } |
| 2177 | delete generate_data.logit_bias; | ||
| 2178 | } | 2238 | } |
| 2179 | 2239 | ||
| 2180 | await eventSource.emit(event_types.CHAT_COMPLETION_SETTINGS_READY, generate_data); | 2240 | await eventSource.emit(event_types.CHAT_COMPLETION_SETTINGS_READY, generate_data); |
| @@ -2210,7 +2270,8 @@ async function sendOpenAIRequest(type, messages, signal) { | |||
| 2210 | 2270 | ||
| 2211 | if (Array.isArray(parsed?.choices) && parsed?.choices?.[0]?.index > 0) { | 2271 | if (Array.isArray(parsed?.choices) && parsed?.choices?.[0]?.index > 0) { |
| 2212 | const swipeIndex = parsed.choices[0].index - 1; | 2272 | const swipeIndex = parsed.choices[0].index - 1; |
| 2213 | swipes[swipeIndex] = (swipes[swipeIndex] || '') + getStreamingReply(parsed, state); | 2273 | // FIXME: state.reasoning should be an array to support multi-swipe |
| 2274 | swipes[swipeIndex] = (swipes[swipeIndex] || '') + getStreamingReply(parsed, state, { overrideShowThoughts: false }); | ||
| 2214 | } else { | 2275 | } else { |
| 2215 | text += getStreamingReply(parsed, state); | 2276 | text += getStreamingReply(parsed, state); |
| 2216 | } | 2277 | } |
| @@ -2278,6 +2339,11 @@ export function getStreamingReply(data, state, { chatCompletionSource = null, ov | |||
| 2278 | state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content || ''); | 2339 | state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content || ''); |
| 2279 | } | 2340 | } |
| 2280 | return data.choices?.[0]?.delta?.content || ''; | 2341 | return data.choices?.[0]?.delta?.content || ''; |
| 2342 | } else if (chat_completion_source === chat_completion_sources.XAI) { | ||
| 2343 | if (show_thoughts) { | ||
| 2344 | state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content || ''); | ||
| 2345 | } | ||
| 2346 | return data.choices?.[0]?.delta?.content || ''; | ||
| 2281 | } else if (chat_completion_source === chat_completion_sources.OPENROUTER) { | 2347 | } else if (chat_completion_source === chat_completion_sources.OPENROUTER) { |
| 2282 | if (show_thoughts) { | 2348 | if (show_thoughts) { |
| 2283 | state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning)?.[0]?.delta?.reasoning || ''); | 2349 | state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning)?.[0]?.delta?.reasoning || ''); |
| @@ -2310,6 +2376,7 @@ function parseChatCompletionLogprobs(data) { | |||
| 2310 | switch (oai_settings.chat_completion_source) { | 2376 | switch (oai_settings.chat_completion_source) { |
| 2311 | case chat_completion_sources.OPENAI: | 2377 | case chat_completion_sources.OPENAI: |
| 2312 | case chat_completion_sources.DEEPSEEK: | 2378 | case chat_completion_sources.DEEPSEEK: |
| 2379 | case chat_completion_sources.XAI: | ||
| 2313 | case chat_completion_sources.CUSTOM: | 2380 | case chat_completion_sources.CUSTOM: |
| 2314 | if (!data.choices?.length) { | 2381 | if (!data.choices?.length) { |
| 2315 | return null; | 2382 | return null; |
| @@ -3231,6 +3298,7 @@ function loadOpenAISettings(data, settings) { | |||
| 3231 | oai_settings.nanogpt_model = settings.nanogpt_model ?? default_settings.nanogpt_model; | 3298 | oai_settings.nanogpt_model = settings.nanogpt_model ?? default_settings.nanogpt_model; |
| 3232 | oai_settings.deepseek_model = settings.deepseek_model ?? default_settings.deepseek_model; | 3299 | oai_settings.deepseek_model = settings.deepseek_model ?? default_settings.deepseek_model; |
| 3233 | oai_settings.zerooneai_model = settings.zerooneai_model ?? default_settings.zerooneai_model; | 3300 | oai_settings.zerooneai_model = settings.zerooneai_model ?? default_settings.zerooneai_model; |
| 3301 | oai_settings.xai_model = settings.xai_model ?? default_settings.xai_model; | ||
| 3234 | oai_settings.custom_model = settings.custom_model ?? default_settings.custom_model; | 3302 | oai_settings.custom_model = settings.custom_model ?? default_settings.custom_model; |
| 3235 | oai_settings.custom_url = settings.custom_url ?? default_settings.custom_url; | 3303 | oai_settings.custom_url = settings.custom_url ?? default_settings.custom_url; |
| 3236 | oai_settings.custom_include_body = settings.custom_include_body ?? default_settings.custom_include_body; | 3304 | oai_settings.custom_include_body = settings.custom_include_body ?? default_settings.custom_include_body; |
| @@ -3316,6 +3384,8 @@ function loadOpenAISettings(data, settings) { | |||
| 3316 | $('#model_deepseek_select').val(oai_settings.deepseek_model); | 3384 | $('#model_deepseek_select').val(oai_settings.deepseek_model); |
| 3317 | $(`#model_deepseek_select option[value="${oai_settings.deepseek_model}"`).prop('selected', true); | 3385 | $(`#model_deepseek_select option[value="${oai_settings.deepseek_model}"`).prop('selected', true); |
| 3318 | $('#model_01ai_select').val(oai_settings.zerooneai_model); | 3386 | $('#model_01ai_select').val(oai_settings.zerooneai_model); |
| 3387 | $('#model_xai_select').val(oai_settings.xai_model); | ||
| 3388 | $(`#model_xai_select option[value="${oai_settings.xai_model}"`).attr('selected', true); | ||
| 3319 | $('#custom_model_id').val(oai_settings.custom_model); | 3389 | $('#custom_model_id').val(oai_settings.custom_model); |
| 3320 | $('#custom_api_url_text').val(oai_settings.custom_url); | 3390 | $('#custom_api_url_text').val(oai_settings.custom_url); |
| 3321 | $('#openai_max_context').val(oai_settings.openai_max_context); | 3391 | $('#openai_max_context').val(oai_settings.openai_max_context); |
| @@ -3512,7 +3582,7 @@ async function getStatusOpen() { | |||
| 3512 | chat_completion_source: oai_settings.chat_completion_source, | 3582 | chat_completion_source: oai_settings.chat_completion_source, |
| 3513 | }; | 3583 | }; |
| 3514 | 3584 | ||
| 3515 | if (oai_settings.reverse_proxy && [chat_completion_sources.CLAUDE, chat_completion_sources.OPENAI, chat_completion_sources.MISTRALAI, chat_completion_sources.MAKERSUITE, chat_completion_sources.DEEPSEEK].includes(oai_settings.chat_completion_source)) { | 3585 | if (oai_settings.reverse_proxy && [chat_completion_sources.CLAUDE, chat_completion_sources.OPENAI, chat_completion_sources.MISTRALAI, chat_completion_sources.MAKERSUITE, chat_completion_sources.DEEPSEEK, chat_completion_sources.XAI].includes(oai_settings.chat_completion_source)) { |
| 3516 | await validateReverseProxy(); | 3586 | await validateReverseProxy(); |
| 3517 | } | 3587 | } |
| 3518 | 3588 | ||
| @@ -3781,7 +3851,7 @@ function createLogitBiasListItem(entry) { | |||
| 3781 | } | 3851 | } |
| 3782 | 3852 | ||
| 3783 | async function createNewLogitBiasPreset() { | 3853 | async function createNewLogitBiasPreset() { |
| 3784 | const name = await callPopup('Preset name:', 'input'); | 3854 | const name = await Popup.show.input(t`Preset name:`, null); |
| 3785 | 3855 | ||
| 3786 | if (!name) { | 3856 | if (!name) { |
| 3787 | return; | 3857 | return; |
| @@ -4092,9 +4162,15 @@ function getMaxContextOpenAI(value) { | |||
| 4092 | if (oai_settings.max_context_unlocked) { | 4162 | if (oai_settings.max_context_unlocked) { |
| 4093 | return unlocked_max; | 4163 | return unlocked_max; |
| 4094 | } | 4164 | } |
| 4095 | else if (value.startsWith('o1') || value.startsWith('o3')) { | 4165 | else if (value.includes('gpt-4.1')) { |
| 4166 | return max_1mil; | ||
| 4167 | } | ||
| 4168 | else if (value.startsWith('o1')) { | ||
| 4096 | return max_128k; | 4169 | return max_128k; |
| 4097 | } | 4170 | } |
| 4171 | else if (value.startsWith('o4') || value.startsWith('o3')) { | ||
| 4172 | return max_200k; | ||
| 4173 | } | ||
| 4098 | else if (value.includes('chatgpt-4o-latest') || value.includes('gpt-4-turbo') || value.includes('gpt-4o') || value.includes('gpt-4-1106') || value.includes('gpt-4-0125') || value.includes('gpt-4-vision')) { | 4174 | else if (value.includes('chatgpt-4o-latest') || value.includes('gpt-4-turbo') || value.includes('gpt-4o') || value.includes('gpt-4-1106') || value.includes('gpt-4-0125') || value.includes('gpt-4-vision')) { |
| 4099 | return max_128k; | 4175 | return max_128k; |
| 4100 | } | 4176 | } |
| @@ -4166,6 +4242,80 @@ function getMaxContextWindowAI(value) { | |||
| 4166 | } | 4242 | } |
| 4167 | 4243 | ||
| 4168 | /** | 4244 | /** |
| 4245 | * Get the maximum context size for the Mistral model | ||
| 4246 | * @param {string} model Model identifier | ||
| 4247 | * @param {boolean} isUnlocked Whether context limits are unlocked | ||
| 4248 | * @returns {number} Maximum context size in tokens | ||
| 4249 | */ | ||
| 4250 | function getMistralMaxContext(model, isUnlocked) { | ||
| 4251 | if (isUnlocked) { | ||
| 4252 | return unlocked_max; | ||
| 4253 | } | ||
| 4254 | |||
| 4255 | if (Array.isArray(model_list) && model_list.length > 0) { | ||
| 4256 | const contextLength = model_list.find((record) => record.id === model)?.max_context_length; | ||
| 4257 | if (contextLength) { | ||
| 4258 | return contextLength; | ||
| 4259 | } | ||
| 4260 | } | ||
| 4261 | |||
| 4262 | const contextMap = { | ||
| 4263 | 'codestral-2411-rc5': 262144, | ||
| 4264 | 'codestral-2412': 262144, | ||
| 4265 | 'codestral-2501': 262144, | ||
| 4266 | 'codestral-latest': 262144, | ||
| 4267 | 'codestral-mamba-2407': 262144, | ||
| 4268 | 'codestral-mamba-latest': 262144, | ||
| 4269 | 'open-codestral-mamba': 262144, | ||
| 4270 | 'ministral-3b-2410': 131072, | ||
| 4271 | 'ministral-3b-latest': 131072, | ||
| 4272 | 'ministral-8b-2410': 131072, | ||
| 4273 | 'ministral-8b-latest': 131072, | ||
| 4274 | 'mistral-large-2407': 131072, | ||
| 4275 | 'mistral-large-2411': 131072, | ||
| 4276 | 'mistral-large-latest': 131072, | ||
| 4277 | 'mistral-large-pixtral-2411': 131072, | ||
| 4278 | 'mistral-tiny-2407': 131072, | ||
| 4279 | 'mistral-tiny-latest': 131072, | ||
| 4280 | 'open-mistral-nemo': 131072, | ||
| 4281 | 'open-mistral-nemo-2407': 131072, | ||
| 4282 | 'pixtral-12b': 131072, | ||
| 4283 | 'pixtral-12b-2409': 131072, | ||
| 4284 | 'pixtral-12b-latest': 131072, | ||
| 4285 | 'pixtral-large-2411': 131072, | ||
| 4286 | 'pixtral-large-latest': 131072, | ||
| 4287 | 'open-mixtral-8x22b': 65536, | ||
| 4288 | 'open-mixtral-8x22b-2404': 65536, | ||
| 4289 | 'codestral-2405': 32768, | ||
| 4290 | 'mistral-embed': 32768, | ||
| 4291 | 'mistral-large-2402': 32768, | ||
| 4292 | 'mistral-medium': 32768, | ||
| 4293 | 'mistral-medium-2312': 32768, | ||
| 4294 | 'mistral-medium-latest': 32768, | ||
| 4295 | 'mistral-moderation-2411': 32768, | ||
| 4296 | 'mistral-moderation-latest': 32768, | ||
| 4297 | 'mistral-ocr-2503': 32768, | ||
| 4298 | 'mistral-ocr-latest': 32768, | ||
| 4299 | 'mistral-saba-2502': 32768, | ||
| 4300 | 'mistral-saba-latest': 32768, | ||
| 4301 | 'mistral-small': 32768, | ||
| 4302 | 'mistral-small-2312': 32768, | ||
| 4303 | 'mistral-small-2402': 32768, | ||
| 4304 | 'mistral-small-2409': 32768, | ||
| 4305 | 'mistral-small-2501': 32768, | ||
| 4306 | 'mistral-small-2503': 32768, | ||
| 4307 | 'mistral-small-latest': 32768, | ||
| 4308 | 'mistral-tiny': 32768, | ||
| 4309 | 'mistral-tiny-2312': 32768, | ||
| 4310 | 'open-mistral-7b': 32768, | ||
| 4311 | 'open-mixtral-8x7b': 32768, | ||
| 4312 | }; | ||
| 4313 | |||
| 4314 | // Return context size if model found, otherwise default to 32k | ||
| 4315 | return Object.entries(contextMap).find(([key]) => model.includes(key))?.[1] || 32768; | ||
| 4316 | } | ||
| 4317 | |||
| 4318 | /** | ||
| 4169 | * Get the maximum context size for the Groq model | 4319 | * Get the maximum context size for the Groq model |
| 4170 | * @param {string} model Model identifier | 4320 | * @param {string} model Model identifier |
| 4171 | * @param {boolean} isUnlocked Whether context limits are unlocked | 4321 | * @param {boolean} isUnlocked Whether context limits are unlocked |
| @@ -4312,6 +4462,11 @@ async function onModelChange() { | |||
| 4312 | $('#custom_model_id').val(value).trigger('input'); | 4462 | $('#custom_model_id').val(value).trigger('input'); |
| 4313 | } | 4463 | } |
| 4314 | 4464 | ||
| 4465 | if ($(this).is('#model_xai_select')) { | ||
| 4466 | console.log('XAI model changed to', value); | ||
| 4467 | oai_settings.xai_model = value; | ||
| 4468 | } | ||
| 4469 | |||
| 4315 | if (oai_settings.chat_completion_source == chat_completion_sources.SCALE) { | 4470 | if (oai_settings.chat_completion_source == chat_completion_sources.SCALE) { |
| 4316 | if (oai_settings.max_context_unlocked) { | 4471 | if (oai_settings.max_context_unlocked) { |
| 4317 | $('#openai_max_context').attr('max', unlocked_max); | 4472 | $('#openai_max_context').attr('max', unlocked_max); |
| @@ -4326,20 +4481,16 @@ async function onModelChange() { | |||
| 4326 | if (oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE) { | 4481 | if (oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE) { |
| 4327 | if (oai_settings.max_context_unlocked) { | 4482 | if (oai_settings.max_context_unlocked) { |
| 4328 | $('#openai_max_context').attr('max', max_2mil); | 4483 | $('#openai_max_context').attr('max', max_2mil); |
| 4329 | } else if (value.includes('gemini-exp-1114') || value.includes('gemini-exp-1121') || value.includes('gemini-2.0-flash-thinking-exp-1219')) { | 4484 | } else if (value.includes('gemini-1.5-pro')) { |
| 4330 | $('#openai_max_context').attr('max', max_32k); | ||
| 4331 | } else if (value.includes('gemini-1.5-pro') || value.includes('gemini-exp-1206') || value.includes('gemini-2.0-pro')) { | ||
| 4332 | $('#openai_max_context').attr('max', max_2mil); | 4485 | $('#openai_max_context').attr('max', max_2mil); |
| 4333 | } else if (value.includes('gemini-1.5-flash') || value.includes('gemini-2.0-flash') || value.includes('gemini-2.5-pro-exp-03-25') || value.includes('gemini-2.5-pro-preview-03-25')) { | 4486 | } else if (value.includes('gemini-1.5-flash') || value.includes('gemini-2.0-flash') || value.includes('gemini-2.0-pro') || value.includes('gemini-exp') || value.includes('gemini-2.5-flash') || value.includes('gemini-2.5-pro') || value.includes('learnlm-2.0-flash')) { |
| 4334 | $('#openai_max_context').attr('max', max_1mil); | 4487 | $('#openai_max_context').attr('max', max_1mil); |
| 4335 | } else if (value.includes('gemini-1.0-pro') || value === 'gemini-pro') { | 4488 | } else if (value.includes('gemma-3-27b-it')) { |
| 4336 | $('#openai_max_context').attr('max', max_32k); | ||
| 4337 | } else if (value.includes('gemini-1.0-ultra') || value === 'gemini-ultra') { | ||
| 4338 | $('#openai_max_context').attr('max', max_32k); | ||
| 4339 | } else if (value.includes('gemma-3')) { | ||
| 4340 | $('#openai_max_context').attr('max', max_128k); | 4489 | $('#openai_max_context').attr('max', max_128k); |
| 4490 | } else if (value.includes('gemma-3') || value.includes('learnlm-1.5-pro-experimental')) { | ||
| 4491 | $('#openai_max_context').attr('max', max_32k); | ||
| 4341 | } else { | 4492 | } else { |
| 4342 | $('#openai_max_context').attr('max', max_4k); | 4493 | $('#openai_max_context').attr('max', max_32k); |
| 4343 | } | 4494 | } |
| 4344 | let makersuite_max_temp = (value.includes('vision') || value.includes('ultra') || value.includes('gemma')) ? 1.0 : 2.0; | 4495 | let makersuite_max_temp = (value.includes('vision') || value.includes('ultra') || value.includes('gemma')) ? 1.0 : 2.0; |
| 4345 | oai_settings.temp_openai = Math.min(makersuite_max_temp, oai_settings.temp_openai); | 4496 | oai_settings.temp_openai = Math.min(makersuite_max_temp, oai_settings.temp_openai); |
| @@ -4428,27 +4579,10 @@ async function onModelChange() { | |||
| 4428 | } | 4579 | } |
| 4429 | 4580 | ||
| 4430 | if (oai_settings.chat_completion_source === chat_completion_sources.MISTRALAI) { | 4581 | if (oai_settings.chat_completion_source === chat_completion_sources.MISTRALAI) { |
| 4431 | if (oai_settings.max_context_unlocked) { | 4582 | const maxContext = getMistralMaxContext(oai_settings.mistralai_model, oai_settings.max_context_unlocked); |
| 4432 | $('#openai_max_context').attr('max', unlocked_max); | 4583 | $('#openai_max_context').attr('max', maxContext); |
| 4433 | } else if (['codestral-latest', 'codestral-mamba-2407', 'codestral-2411-rc5', 'codestral-2412', 'codestral-2501'].includes(oai_settings.mistralai_model)) { | ||
| 4434 | $('#openai_max_context').attr('max', max_256k); | ||
| 4435 | } else if (['mistral-large-2407', 'mistral-large-2411', 'mistral-large-pixtral-2411', 'mistral-large-latest'].includes(oai_settings.mistralai_model)) { | ||
| 4436 | $('#openai_max_context').attr('max', max_128k); | ||
| 4437 | } else if (oai_settings.mistralai_model.includes('mistral-nemo')) { | ||
| 4438 | $('#openai_max_context').attr('max', max_128k); | ||
| 4439 | } else if (oai_settings.mistralai_model.includes('mixtral-8x22b')) { | ||
| 4440 | $('#openai_max_context').attr('max', max_64k); | ||
| 4441 | } else if (oai_settings.mistralai_model.includes('pixtral')) { | ||
| 4442 | $('#openai_max_context').attr('max', max_128k); | ||
| 4443 | } else if (oai_settings.mistralai_model.includes('ministral')) { | ||
| 4444 | $('#openai_max_context').attr('max', max_32k); | ||
| 4445 | } else { | ||
| 4446 | $('#openai_max_context').attr('max', max_32k); | ||
| 4447 | } | ||
| 4448 | oai_settings.openai_max_context = Math.min(oai_settings.openai_max_context, Number($('#openai_max_context').attr('max'))); | 4584 | oai_settings.openai_max_context = Math.min(oai_settings.openai_max_context, Number($('#openai_max_context').attr('max'))); |
| 4449 | $('#openai_max_context').val(oai_settings.openai_max_context).trigger('input'); | 4585 | $('#openai_max_context').val(oai_settings.openai_max_context).trigger('input'); |
| 4450 | |||
| 4451 | //mistral also caps temp at 1.0 | ||
| 4452 | oai_settings.temp_openai = Math.min(claude_max_temp, oai_settings.temp_openai); | 4586 | oai_settings.temp_openai = Math.min(claude_max_temp, oai_settings.temp_openai); |
| 4453 | $('#temp_openai').attr('max', claude_max_temp).val(oai_settings.temp_openai).trigger('input'); | 4587 | $('#temp_openai').attr('max', claude_max_temp).val(oai_settings.temp_openai).trigger('input'); |
| 4454 | } | 4588 | } |
| @@ -4584,6 +4718,22 @@ async function onModelChange() { | |||
| 4584 | $('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input'); | 4718 | $('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input'); |
| 4585 | } | 4719 | } |
| 4586 | 4720 | ||
| 4721 | if (oai_settings.chat_completion_source === chat_completion_sources.XAI) { | ||
| 4722 | if (oai_settings.max_context_unlocked) { | ||
| 4723 | $('#openai_max_context').attr('max', unlocked_max); | ||
| 4724 | } else if (oai_settings.xai_model.includes('grok-2-vision')) { | ||
| 4725 | $('#openai_max_context').attr('max', max_32k); | ||
| 4726 | } else if (oai_settings.xai_model.includes('grok-vision')) { | ||
| 4727 | $('#openai_max_context').attr('max', max_8k); | ||
| 4728 | } else { | ||
| 4729 | $('#openai_max_context').attr('max', max_128k); | ||
| 4730 | } | ||
| 4731 | |||
| 4732 | oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context); | ||
| 4733 | $('#openai_max_context').val(oai_settings.openai_max_context).trigger('input'); | ||
| 4734 | $('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input'); | ||
| 4735 | } | ||
| 4736 | |||
| 4587 | if (oai_settings.chat_completion_source === chat_completion_sources.COHERE) { | 4737 | if (oai_settings.chat_completion_source === chat_completion_sources.COHERE) { |
| 4588 | oai_settings.pres_pen_openai = Math.min(Math.max(0, oai_settings.pres_pen_openai), 1); | 4738 | oai_settings.pres_pen_openai = Math.min(Math.max(0, oai_settings.pres_pen_openai), 1); |
| 4589 | $('#pres_pen_openai').attr('max', 1).attr('min', 0).val(oai_settings.pres_pen_openai).trigger('input'); | 4739 | $('#pres_pen_openai').attr('max', 1).attr('min', 0).val(oai_settings.pres_pen_openai).trigger('input'); |
| @@ -4822,6 +4972,19 @@ async function onConnectButtonClick(e) { | |||
| 4822 | } | 4972 | } |
| 4823 | } | 4973 | } |
| 4824 | 4974 | ||
| 4975 | if (oai_settings.chat_completion_source === chat_completion_sources.XAI) { | ||
| 4976 | const api_key_xai = String($('#api_key_xai').val()).trim(); | ||
| 4977 | |||
| 4978 | if (api_key_xai.length) { | ||
| 4979 | await writeSecret(SECRET_KEYS.XAI, api_key_xai); | ||
| 4980 | } | ||
| 4981 | |||
| 4982 | if (!secret_state[SECRET_KEYS.XAI] && !oai_settings.reverse_proxy) { | ||
| 4983 | console.log('No secret key saved for XAI'); | ||
| 4984 | return; | ||
| 4985 | } | ||
| 4986 | } | ||
| 4987 | |||
| 4825 | startStatusLoading(); | 4988 | startStatusLoading(); |
| 4826 | saveSettingsDebounced(); | 4989 | saveSettingsDebounced(); |
| 4827 | await getStatusOpen(); | 4990 | await getStatusOpen(); |
| @@ -4878,6 +5041,9 @@ function toggleChatCompletionForms() { | |||
| 4878 | else if (oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK) { | 5041 | else if (oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK) { |
| 4879 | $('#model_deepseek_select').trigger('change'); | 5042 | $('#model_deepseek_select').trigger('change'); |
| 4880 | } | 5043 | } |
| 5044 | else if (oai_settings.chat_completion_source == chat_completion_sources.XAI) { | ||
| 5045 | $('#model_xai_select').trigger('change'); | ||
| 5046 | } | ||
| 4881 | $('[data-source]').each(function () { | 5047 | $('[data-source]').each(function () { |
| 4882 | const validSources = $(this).data('source').split(','); | 5048 | const validSources = $(this).data('source').split(','); |
| 4883 | $(this).toggle(validSources.includes(oai_settings.chat_completion_source)); | 5049 | $(this).toggle(validSources.includes(oai_settings.chat_completion_source)); |
| @@ -4962,63 +5128,43 @@ export function isImageInliningSupported() { | |||
| 4962 | 5128 | ||
| 4963 | // gultra just isn't being offered as multimodal, thanks google. | 5129 | // gultra just isn't being offered as multimodal, thanks google. |
| 4964 | const visionSupportedModels = [ | 5130 | const visionSupportedModels = [ |
| 4965 | 'gpt-4-vision', | 5131 | // OpenAI |
| 4966 | 'gemini-2.5-pro-exp-03-25', | 5132 | 'chatgpt-4o-latest', |
| 4967 | 'gemini-2.5-pro-preview-03-25', | ||
| 4968 | 'gemini-2.0-pro-exp', | ||
| 4969 | 'gemini-2.0-pro-exp-02-05', | ||
| 4970 | 'gemini-2.0-flash-lite-preview', | ||
| 4971 | 'gemini-2.0-flash-lite-preview-02-05', | ||
| 4972 | 'gemini-2.0-flash', | ||
| 4973 | 'gemini-2.0-flash-001', | ||
| 4974 | 'gemini-2.0-flash-thinking-exp-1219', | ||
| 4975 | 'gemini-2.0-flash-thinking-exp-01-21', | ||
| 4976 | 'gemini-2.0-flash-thinking-exp', | ||
| 4977 | 'gemini-2.0-flash-exp', | ||
| 4978 | 'gemini-2.0-flash-exp-image-generation', | ||
| 4979 | 'gemini-1.5-flash', | ||
| 4980 | 'gemini-1.5-flash-latest', | ||
| 4981 | 'gemini-1.5-flash-001', | ||
| 4982 | 'gemini-1.5-flash-002', | ||
| 4983 | 'gemini-1.5-flash-exp-0827', | ||
| 4984 | 'gemini-1.5-flash-8b', | ||
| 4985 | 'gemini-1.5-flash-8b-exp-0827', | ||
| 4986 | 'gemini-1.5-flash-8b-exp-0924', | ||
| 4987 | 'gemini-exp-1114', | ||
| 4988 | 'gemini-exp-1121', | ||
| 4989 | 'gemini-exp-1206', | ||
| 4990 | 'gemini-1.0-pro-vision-latest', | ||
| 4991 | 'gemini-1.5-pro', | ||
| 4992 | 'gemini-1.5-pro-latest', | ||
| 4993 | 'gemini-1.5-pro-001', | ||
| 4994 | 'gemini-1.5-pro-002', | ||
| 4995 | 'gemini-1.5-pro-exp-0801', | ||
| 4996 | 'gemini-1.5-pro-exp-0827', | ||
| 4997 | 'claude-3', | ||
| 4998 | 'claude-3-5', | ||
| 4999 | 'claude-3-7', | ||
| 5000 | 'gpt-4-turbo', | 5133 | 'gpt-4-turbo', |
| 5001 | 'gpt-4o', | 5134 | 'gpt-4-vision', |
| 5002 | 'gpt-4o-mini', | 5135 | 'gpt-4.1', |
| 5003 | 'gpt-4.5-preview', | 5136 | 'gpt-4.5-preview', |
| 5004 | 'gpt-4.5-preview-2025-02-27', | 5137 | 'gpt-4o', |
| 5005 | 'o1', | 5138 | 'o1', |
| 5006 | 'o1-2024-12-17', | 5139 | 'o3', |
| 5007 | 'chatgpt-4o-latest', | 5140 | 'o4-mini', |
| 5141 | // 01.AI (Yi) | ||
| 5008 | 'yi-vision', | 5142 | 'yi-vision', |
| 5009 | 'pixtral-latest', | 5143 | // Claude |
| 5010 | 'pixtral-12b-latest', | 5144 | 'claude-3', |
| 5011 | 'pixtral-12b', | 5145 | // Cohere |
| 5012 | 'pixtral-12b-2409', | 5146 | 'c4ai-aya-vision', |
| 5013 | 'pixtral-large-latest', | 5147 | // Google AI Studio |
| 5014 | 'pixtral-large-2411', | 5148 | 'gemini-1.5', |
| 5015 | 'c4ai-aya-vision-8b', | 5149 | 'gemini-2.0', |
| 5016 | 'c4ai-aya-vision-32b', | 5150 | 'gemini-2.5', |
| 5151 | 'gemini-exp-1206', | ||
| 5152 | 'learnlm', | ||
| 5153 | // MistralAI | ||
| 5154 | 'mistral-small-2503', | ||
| 5155 | 'mistral-small-latest', | ||
| 5156 | 'pixtral', | ||
| 5157 | // xAI (Grok) | ||
| 5158 | 'grok-2-vision', | ||
| 5159 | 'grok-vision', | ||
| 5017 | ]; | 5160 | ]; |
| 5018 | 5161 | ||
| 5019 | switch (oai_settings.chat_completion_source) { | 5162 | switch (oai_settings.chat_completion_source) { |
| 5020 | case chat_completion_sources.OPENAI: | 5163 | case chat_completion_sources.OPENAI: |
| 5021 | return visionSupportedModels.some(model => oai_settings.openai_model.includes(model) && !oai_settings.openai_model.includes('gpt-4-turbo-preview')); | 5164 | return visionSupportedModels.some(model => |
| 5165 | oai_settings.openai_model.includes(model) | ||
| 5166 | && ['gpt-4-turbo-preview', 'o1-mini', 'o3-mini'].some(x => !oai_settings.openai_model.includes(x)), | ||
| 5167 | ); | ||
| 5022 | case chat_completion_sources.MAKERSUITE: | 5168 | case chat_completion_sources.MAKERSUITE: |
| 5023 | return visionSupportedModels.some(model => oai_settings.google_model.includes(model)); | 5169 | return visionSupportedModels.some(model => oai_settings.google_model.includes(model)); |
| 5024 | case chat_completion_sources.CLAUDE: | 5170 | case chat_completion_sources.CLAUDE: |
| @@ -5033,6 +5179,8 @@ export function isImageInliningSupported() { | |||
| 5033 | return visionSupportedModels.some(model => oai_settings.mistralai_model.includes(model)); | 5179 | return visionSupportedModels.some(model => oai_settings.mistralai_model.includes(model)); |
| 5034 | case chat_completion_sources.COHERE: | 5180 | case chat_completion_sources.COHERE: |
| 5035 | return visionSupportedModels.some(model => oai_settings.cohere_model.includes(model)); | 5181 | return visionSupportedModels.some(model => oai_settings.cohere_model.includes(model)); |
| 5182 | case chat_completion_sources.XAI: | ||
| 5183 | return visionSupportedModels.some(model => oai_settings.xai_model.includes(model)); | ||
| 5036 | default: | 5184 | default: |
| 5037 | return false; | 5185 | return false; |
| 5038 | } | 5186 | } |
| @@ -5573,6 +5721,7 @@ export function initOpenAI() { | |||
| 5573 | 5721 | ||
| 5574 | $('#openai_enable_web_search').on('input', function () { | 5722 | $('#openai_enable_web_search').on('input', function () { |
| 5575 | oai_settings.enable_web_search = !!$(this).prop('checked'); | 5723 | oai_settings.enable_web_search = !!$(this).prop('checked'); |
| 5724 | calculateOpenRouterCost(); | ||
| 5576 | saveSettingsDebounced(); | 5725 | saveSettingsDebounced(); |
| 5577 | }); | 5726 | }); |
| 5578 | 5727 | ||
| @@ -5629,6 +5778,7 @@ export function initOpenAI() { | |||
| 5629 | $('#model_deepseek_select').on('change', onModelChange); | 5778 | $('#model_deepseek_select').on('change', onModelChange); |
| 5630 | $('#model_01ai_select').on('change', onModelChange); | 5779 | $('#model_01ai_select').on('change', onModelChange); |
| 5631 | $('#model_custom_select').on('change', onModelChange); | 5780 | $('#model_custom_select').on('change', onModelChange); |
| 5781 | $('#model_xai_select').on('change', onModelChange); | ||
| 5632 | $('#settings_preset_openai').on('change', onSettingsPresetChange); | 5782 | $('#settings_preset_openai').on('change', onSettingsPresetChange); |
| 5633 | $('#new_oai_preset').on('click', onNewPresetClick); | 5783 | $('#new_oai_preset').on('click', onNewPresetClick); |
| 5634 | $('#delete_oai_preset').on('click', onDeletePresetClick); | 5784 | $('#delete_oai_preset').on('click', onDeletePresetClick); |
| @@ -22,7 +22,7 @@ import { | |||
| 22 | } from '../script.js'; | 22 | } from '../script.js'; |
| 23 | import { persona_description_positions, power_user } from './power-user.js'; | 23 | import { persona_description_positions, power_user } from './power-user.js'; |
| 24 | import { getTokenCountAsync } from './tokenizers.js'; | 24 | import { getTokenCountAsync } from './tokenizers.js'; |
| 25 | import { PAGINATION_TEMPLATE, clearInfoBlock, debounce, delay, download, ensureImageFormatSupported, flashHighlight, getBase64Async, getCharIndex, isFalseBoolean, isTrueBoolean, onlyUnique, parseJsonFile, setInfoBlock } from './utils.js'; | 25 | import { PAGINATION_TEMPLATE, clearInfoBlock, debounce, delay, download, ensureImageFormatSupported, flashHighlight, getBase64Async, getCharIndex, isFalseBoolean, isTrueBoolean, onlyUnique, parseJsonFile, setInfoBlock, localizePagination, renderPaginationDropdown, paginationDropdownChangeHandler } from './utils.js'; |
| 26 | import { debounce_timeout } from './constants.js'; | 26 | import { debounce_timeout } from './constants.js'; |
| 27 | import { FILTER_TYPES, FilterHelper } from './filters.js'; | 27 | import { FILTER_TYPES, FilterHelper } from './filters.js'; |
| 28 | import { groups, selected_group } from './group-chats.js'; | 28 | import { groups, selected_group } from './group-chats.js'; |
| @@ -250,16 +250,18 @@ export async function getUserAvatars(doRender = true, openPageAt = '') { | |||
| 250 | const storageKey = 'Personas_PerPage'; | 250 | const storageKey = 'Personas_PerPage'; |
| 251 | const listId = '#user_avatar_block'; | 251 | const listId = '#user_avatar_block'; |
| 252 | const perPage = Number(accountStorage.getItem(storageKey)) || 5; | 252 | const perPage = Number(accountStorage.getItem(storageKey)) || 5; |
| 253 | const sizeChangerOptions = [5, 10, 25, 50, 100, 250, 500, 1000]; | ||
| 253 | 254 | ||
| 254 | $('#persona_pagination_container').pagination({ | 255 | $('#persona_pagination_container').pagination({ |
| 255 | dataSource: entities, | 256 | dataSource: entities, |
| 256 | pageSize: perPage, | 257 | pageSize: perPage, |
| 257 | sizeChangerOptions: [5, 10, 25, 50, 100, 250, 500, 1000], | 258 | sizeChangerOptions, |
| 258 | pageRange: 1, | 259 | pageRange: 1, |
| 259 | pageNumber: savePersonasPage || 1, | 260 | pageNumber: savePersonasPage || 1, |
| 260 | position: 'top', | 261 | position: 'top', |
| 261 | showPageNumbers: false, | 262 | showPageNumbers: false, |
| 262 | showSizeChanger: true, | 263 | showSizeChanger: true, |
| 264 | formatSizeChanger: renderPaginationDropdown(perPage, sizeChangerOptions), | ||
| 263 | prevText: '<', | 265 | prevText: '<', |
| 264 | nextText: '>', | 266 | nextText: '>', |
| 265 | formatNavigator: PAGINATION_TEMPLATE, | 267 | formatNavigator: PAGINATION_TEMPLATE, |
| @@ -270,9 +272,11 @@ export async function getUserAvatars(doRender = true, openPageAt = '') { | |||
| 270 | $(listId).append(getUserAvatarBlock(item)); | 272 | $(listId).append(getUserAvatarBlock(item)); |
| 271 | } | 273 | } |
| 272 | updatePersonaUIStates(); | 274 | updatePersonaUIStates(); |
| 275 | localizePagination($('#persona_pagination_container')); | ||
| 273 | }, | 276 | }, |
| 274 | afterSizeSelectorChange: function (e) { | 277 | afterSizeSelectorChange: function (e, size) { |
| 275 | accountStorage.setItem(storageKey, e.target.value); | 278 | accountStorage.setItem(storageKey, e.target.value); |
| 279 | paginationDropdownChangeHandler(e, size); | ||
| 276 | }, | 280 | }, |
| 277 | afterPaging: function (e) { | 281 | afterPaging: function (e) { |
| 278 | savePersonasPage = e; | 282 | savePersonasPage = e; |
| @@ -71,7 +71,8 @@ export const POPUP_RESULT = { | |||
| 71 | * @property {string} id - The id for the html element | 71 | * @property {string} id - The id for the html element |
| 72 | * @property {string} label - The label text for the input | 72 | * @property {string} label - The label text for the input |
| 73 | * @property {string?} [tooltip=null] - Optional tooltip icon displayed behind the label | 73 | * @property {string?} [tooltip=null] - Optional tooltip icon displayed behind the label |
| 74 | * @property {boolean?} [defaultState=false] - The default state when opening the popup (false if not set) | 74 | * @property {boolean|string|undefined} [defaultState=false] - The default state when opening the popup (false if not set) |
| 75 | * @property {string?} [type='checkbox'] - The type of the input (default is checkbox) | ||
| 75 | */ | 76 | */ |
| 76 | 77 | ||
| 77 | /** | 78 | /** |
| @@ -157,7 +158,7 @@ export class Popup { | |||
| 157 | 158 | ||
| 158 | /** @type {POPUP_RESULT|number} */ result; | 159 | /** @type {POPUP_RESULT|number} */ result; |
| 159 | /** @type {any} */ value; | 160 | /** @type {any} */ value; |
| 160 | /** @type {Map<string,boolean>?} */ inputResults; | 161 | /** @type {Map<string,string|boolean>?} */ inputResults; |
| 161 | /** @type {any} */ cropData; | 162 | /** @type {any} */ cropData; |
| 162 | 163 | ||
| 163 | /** @type {HTMLElement} */ lastFocus; | 164 | /** @type {HTMLElement} */ lastFocus; |
| @@ -260,13 +261,14 @@ export class Popup { | |||
| 260 | return; | 261 | return; |
| 261 | } | 262 | } |
| 262 | 263 | ||
| 264 | if (!input.type || input.type === 'checkbox') { | ||
| 263 | const label = document.createElement('label'); | 265 | const label = document.createElement('label'); |
| 264 | label.classList.add('checkbox_label', 'justifyCenter'); | 266 | label.classList.add('checkbox_label', 'justifyCenter'); |
| 265 | label.setAttribute('for', input.id); | 267 | label.setAttribute('for', input.id); |
| 266 | const inputElement = document.createElement('input'); | 268 | const inputElement = document.createElement('input'); |
| 267 | inputElement.type = 'checkbox'; | 269 | inputElement.type = 'checkbox'; |
| 268 | inputElement.id = input.id; | 270 | inputElement.id = input.id; |
| 269 | inputElement.checked = input.defaultState ?? false; | 271 | inputElement.checked = Boolean(input.defaultState ?? false); |
| 270 | label.appendChild(inputElement); | 272 | label.appendChild(inputElement); |
| 271 | const labelText = document.createElement('span'); | 273 | const labelText = document.createElement('span'); |
| 272 | labelText.innerText = input.label; | 274 | labelText.innerText = input.label; |
| @@ -282,6 +284,30 @@ export class Popup { | |||
| 282 | } | 284 | } |
| 283 | 285 | ||
| 284 | this.inputControls.appendChild(label); | 286 | this.inputControls.appendChild(label); |
| 287 | } else if (input.type === 'text') { | ||
| 288 | const label = document.createElement('label'); | ||
| 289 | label.classList.add('text_label', 'justifyCenter'); | ||
| 290 | label.setAttribute('for', input.id); | ||
| 291 | |||
| 292 | const inputElement = document.createElement('input'); | ||
| 293 | inputElement.classList.add('text_pole'); | ||
| 294 | inputElement.type = 'text'; | ||
| 295 | inputElement.id = input.id; | ||
| 296 | inputElement.value = String(input.defaultState ?? ''); | ||
| 297 | inputElement.placeholder = input.tooltip ?? ''; | ||
| 298 | |||
| 299 | const labelText = document.createElement('span'); | ||
| 300 | labelText.innerText = input.label; | ||
| 301 | labelText.dataset.i18n = input.label; | ||
| 302 | |||
| 303 | label.appendChild(labelText); | ||
| 304 | label.appendChild(inputElement); | ||
| 305 | |||
| 306 | this.inputControls.appendChild(label); | ||
| 307 | } else { | ||
| 308 | console.warn('Unknown custom input type. Only checkbox and text are supported.', input); | ||
| 309 | return; | ||
| 310 | } | ||
| 285 | }); | 311 | }); |
| 286 | 312 | ||
| 287 | // Set the default button class | 313 | // Set the default button class |
| @@ -529,7 +555,8 @@ export class Popup { | |||
| 529 | this.inputResults = new Map(this.customInputs.map(input => { | 555 | this.inputResults = new Map(this.customInputs.map(input => { |
| 530 | /** @type {HTMLInputElement} */ | 556 | /** @type {HTMLInputElement} */ |
| 531 | const inputControl = this.dlg.querySelector(`#${input.id}`); | 557 | const inputControl = this.dlg.querySelector(`#${input.id}`); |
| 532 | return [inputControl.id, inputControl.checked]; | 558 | const value = input.type === 'text' ? inputControl.value : inputControl.checked; |
| 559 | return [inputControl.id, value]; | ||
| 533 | })); | 560 | })); |
| 534 | } | 561 | } |
| 535 | 562 | ||
| @@ -619,7 +646,7 @@ export class Popup { | |||
| 619 | /** @readonly @type {Popup[]} Remember all popups */ | 646 | /** @readonly @type {Popup[]} Remember all popups */ |
| 620 | popups: [], | 647 | popups: [], |
| 621 | 648 | ||
| 622 | /** @type {{value: any, result: POPUP_RESULT|number?, inputResults: Map<string, boolean>?}?} Last popup result */ | 649 | /** @type {{value: any, result: POPUP_RESULT|number?, inputResults: Map<string, string|boolean>?}?} Last popup result */ |
| 623 | lastResult: null, | 650 | lastResult: null, |
| 624 | 651 | ||
| 625 | /** @returns {boolean} Checks if any modal popup dialog is open */ | 652 | /** @returns {boolean} Checks if any modal popup dialog is open */ |
| @@ -71,8 +71,8 @@ export { | |||
| 71 | 71 | ||
| 72 | export const MAX_CONTEXT_DEFAULT = 8192; | 72 | export const MAX_CONTEXT_DEFAULT = 8192; |
| 73 | export const MAX_RESPONSE_DEFAULT = 2048; | 73 | export const MAX_RESPONSE_DEFAULT = 2048; |
| 74 | const MAX_CONTEXT_UNLOCKED = 200 * 1024; | 74 | const MAX_CONTEXT_UNLOCKED = 512 * 1024; |
| 75 | const MAX_RESPONSE_UNLOCKED = 32 * 1024; | 75 | const MAX_RESPONSE_UNLOCKED = 64 * 1024; |
| 76 | const unlockedMaxContextStep = 512; | 76 | const unlockedMaxContextStep = 512; |
| 77 | const maxContextMin = 512; | 77 | const maxContextMin = 512; |
| 78 | const maxContextStep = 64; | 78 | const maxContextStep = 64; |
| @@ -244,7 +244,6 @@ let power_user = { | |||
| 244 | chat_start: defaultChatStart, | 244 | chat_start: defaultChatStart, |
| 245 | example_separator: defaultExampleSeparator, | 245 | example_separator: defaultExampleSeparator, |
| 246 | use_stop_strings: true, | 246 | use_stop_strings: true, |
| 247 | allow_jailbreak: false, | ||
| 248 | names_as_stop_strings: true, | 247 | names_as_stop_strings: true, |
| 249 | }, | 248 | }, |
| 250 | 249 | ||
| @@ -255,6 +254,7 @@ let power_user = { | |||
| 255 | enabled: true, | 254 | enabled: true, |
| 256 | name: 'Neutral - Chat', | 255 | name: 'Neutral - Chat', |
| 257 | content: 'Write {{char}}\'s next reply in a fictional chat between {{char}} and {{user}}.', | 256 | content: 'Write {{char}}\'s next reply in a fictional chat between {{char}} and {{user}}.', |
| 257 | post_history: '', | ||
| 258 | }, | 258 | }, |
| 259 | 259 | ||
| 260 | reasoning: { | 260 | reasoning: { |
| @@ -334,7 +334,6 @@ const contextControls = [ | |||
| 334 | { id: 'context_example_separator', property: 'example_separator', isCheckbox: false, isGlobalSetting: false }, | 334 | { id: 'context_example_separator', property: 'example_separator', isCheckbox: false, isGlobalSetting: false }, |
| 335 | { id: 'context_chat_start', property: 'chat_start', isCheckbox: false, isGlobalSetting: false }, | 335 | { id: 'context_chat_start', property: 'chat_start', isCheckbox: false, isGlobalSetting: false }, |
| 336 | { id: 'context_use_stop_strings', property: 'use_stop_strings', isCheckbox: true, isGlobalSetting: false, defaultValue: false }, | 336 | { id: 'context_use_stop_strings', property: 'use_stop_strings', isCheckbox: true, isGlobalSetting: false, defaultValue: false }, |
| 337 | { id: 'context_allow_jailbreak', property: 'allow_jailbreak', isCheckbox: true, isGlobalSetting: false, defaultValue: false }, | ||
| 338 | { id: 'context_names_as_stop_strings', property: 'names_as_stop_strings', isCheckbox: true, isGlobalSetting: false, defaultValue: true }, | 337 | { id: 'context_names_as_stop_strings', property: 'names_as_stop_strings', isCheckbox: true, isGlobalSetting: false, defaultValue: true }, |
| 339 | 338 | ||
| 340 | // Existing power user settings | 339 | // Existing power user settings |
| @@ -902,6 +902,12 @@ export async function initPresetManager() { | |||
| 902 | 902 | ||
| 903 | await presetManager.renamePreset(newName); | 903 | await presetManager.renamePreset(newName); |
| 904 | 904 | ||
| 905 | if (apiId === 'openai') { | ||
| 906 | // This is a horrible mess, but prevents the renamed preset from being corrupted. | ||
| 907 | $('#update_oai_preset').trigger('click'); | ||
| 908 | return; | ||
| 909 | } | ||
| 910 | |||
| 905 | const successToast = !presetManager.isAdvancedFormatting() ? t`Preset renamed` : t`Template renamed`; | 911 | const successToast = !presetManager.isAdvancedFormatting() ? t`Preset renamed` : t`Template renamed`; |
| 906 | toastr.success(successToast); | 912 | toastr.success(successToast); |
| 907 | }); | 913 | }); |
| @@ -109,6 +109,8 @@ export function extractReasoningFromData(data, { | |||
| 109 | switch (chatCompletionSource ?? oai_settings.chat_completion_source) { | 109 | switch (chatCompletionSource ?? oai_settings.chat_completion_source) { |
| 110 | case chat_completion_sources.DEEPSEEK: | 110 | case chat_completion_sources.DEEPSEEK: |
| 111 | return data?.choices?.[0]?.message?.reasoning_content ?? ''; | 111 | return data?.choices?.[0]?.message?.reasoning_content ?? ''; |
| 112 | case chat_completion_sources.XAI: | ||
| 113 | return data?.choices?.[0]?.message?.reasoning_content ?? ''; | ||
| 112 | case chat_completion_sources.OPENROUTER: | 114 | case chat_completion_sources.OPENROUTER: |
| 113 | return data?.choices?.[0]?.message?.reasoning ?? ''; | 115 | return data?.choices?.[0]?.message?.reasoning ?? ''; |
| 114 | case chat_completion_sources.MAKERSUITE: | 116 | case chat_completion_sources.MAKERSUITE: |
| @@ -42,6 +42,7 @@ export const SECRET_KEYS = { | |||
| 42 | DEEPSEEK: 'api_key_deepseek', | 42 | DEEPSEEK: 'api_key_deepseek', |
| 43 | SERPER: 'api_key_serper', | 43 | SERPER: 'api_key_serper', |
| 44 | FALAI: 'api_key_falai', | 44 | FALAI: 'api_key_falai', |
| 45 | XAI: 'api_key_xai', | ||
| 45 | }; | 46 | }; |
| 46 | 47 | ||
| 47 | const INPUT_MAP = { | 48 | const INPUT_MAP = { |
| @@ -76,6 +77,7 @@ const INPUT_MAP = { | |||
| 76 | [SECRET_KEYS.NANOGPT]: '#api_key_nanogpt', | 77 | [SECRET_KEYS.NANOGPT]: '#api_key_nanogpt', |
| 77 | [SECRET_KEYS.GENERIC]: '#api_key_generic', | 78 | [SECRET_KEYS.GENERIC]: '#api_key_generic', |
| 78 | [SECRET_KEYS.DEEPSEEK]: '#api_key_deepseek', | 79 | [SECRET_KEYS.DEEPSEEK]: '#api_key_deepseek', |
| 80 | [SECRET_KEYS.XAI]: '#api_key_xai', | ||
| 79 | }; | 81 | }; |
| 80 | 82 | ||
| 81 | async function clearSecret() { | 83 | async function clearSecret() { |
| @@ -1,4 +1,5 @@ | |||
| 1 | import { Fuse, DOMPurify } from '../lib.js'; | 1 | import { Fuse, DOMPurify } from '../lib.js'; |
| 2 | import { flashHighlight } from './utils.js'; | ||
| 2 | 3 | ||
| 3 | import { | 4 | import { |
| 4 | Generate, | 5 | Generate, |
| @@ -21,6 +22,7 @@ import { | |||
| 21 | extractMessageBias, | 22 | extractMessageBias, |
| 22 | generateQuietPrompt, | 23 | generateQuietPrompt, |
| 23 | generateRaw, | 24 | generateRaw, |
| 25 | getFirstDisplayedMessageId, | ||
| 24 | getThumbnailUrl, | 26 | getThumbnailUrl, |
| 25 | is_send_press, | 27 | is_send_press, |
| 26 | main_api, | 28 | main_api, |
| @@ -314,6 +316,11 @@ export function initDefaultSlashCommands() { | |||
| 314 | enumProvider: commonEnumProviders.messages({ allowIdAfter: true }), | 316 | enumProvider: commonEnumProviders.messages({ allowIdAfter: true }), |
| 315 | }), | 317 | }), |
| 316 | SlashCommandNamedArgument.fromProps({ | 318 | SlashCommandNamedArgument.fromProps({ |
| 319 | name: 'name', | ||
| 320 | description: 'Optional custom display name to use for this system narrator message.', | ||
| 321 | typeList: [ARGUMENT_TYPE.STRING], | ||
| 322 | }), | ||
| 323 | SlashCommandNamedArgument.fromProps({ | ||
| 317 | name: 'return', | 324 | name: 'return', |
| 318 | description: 'The way how you want the return value to be provided', | 325 | description: 'The way how you want the return value to be provided', |
| 319 | typeList: [ARGUMENT_TYPE.STRING], | 326 | typeList: [ARGUMENT_TYPE.STRING], |
| @@ -2077,8 +2084,9 @@ export function initDefaultSlashCommands() { | |||
| 2077 | name: 'replace', | 2084 | name: 'replace', |
| 2078 | aliases: ['re'], | 2085 | aliases: ['re'], |
| 2079 | callback: (async ({ mode = 'literal', pattern, replacer = '' }, text) => { | 2086 | callback: (async ({ mode = 'literal', pattern, replacer = '' }, text) => { |
| 2080 | if (pattern === '') | 2087 | if (!pattern) { |
| 2081 | throw new Error('Argument of \'pattern=\' cannot be empty'); | 2088 | throw new Error('Argument of \'pattern=\' cannot be empty'); |
| 2089 | } | ||
| 2082 | switch (mode) { | 2090 | switch (mode) { |
| 2083 | case 'literal': | 2091 | case 'literal': |
| 2084 | return text.replaceAll(pattern, replacer); | 2092 | return text.replaceAll(pattern, replacer); |
| @@ -2121,11 +2129,157 @@ export function initDefaultSlashCommands() { | |||
| 2121 | </div> | 2129 | </div> |
| 2122 | <div> | 2130 | <div> |
| 2123 | <strong>Example:</strong> | 2131 | <strong>Example:</strong> |
| 2124 | <pre>/let x Blue house and blue car || </pre> | 2132 | <pre><code class="language-stscript">/let x Blue house and blue car || </code></pre> |
| 2125 | <pre>/replace pattern="blue" {{var::x}} | /echo |/# Blue house and car ||</pre> | 2133 | <pre><code class="language-stscript">/replace pattern="blue" {{var::x}} | /echo |/# Blue house and car ||</code></pre> |
| 2126 | <pre>/replace pattern="blue" replacer="red" {{var::x}} | /echo |/# Blue house and red car ||</pre> | 2134 | <pre><code class="language-stscript">/replace pattern="blue" replacer="red" {{var::x}} | /echo |/# Blue house and red car ||</code></pre> |
| 2127 | <pre>/replace mode=regex pattern="/blue/i" replacer="red" {{var::x}} | /echo |/# red house and blue car ||</pre> | 2135 | <pre><code class="language-stscript">/replace mode=regex pattern="/blue/i" replacer="red" {{var::x}} | /echo |/# red house and blue car ||</code></pre> |
| 2128 | <pre>/replace mode=regex pattern="/blue/gi" replacer="red" {{var::x}} | /echo |/# red house and red car ||</pre> | 2136 | <pre><code class="language-stscript">/replace mode=regex pattern="/blue/gi" replacer="red" {{var::x}} | /echo |/# red house and red car ||</code></pre> |
| 2137 | </div> | ||
| 2138 | `, | ||
| 2139 | })); | ||
| 2140 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | ||
| 2141 | name: 'test', | ||
| 2142 | callback: (({ pattern }, text) => { | ||
| 2143 | if (!pattern) { | ||
| 2144 | throw new Error('Argument of \'pattern=\' cannot be empty'); | ||
| 2145 | } | ||
| 2146 | const re = regexFromString(pattern.toString()); | ||
| 2147 | if (!re) { | ||
| 2148 | throw new Error('The value of \'pattern\' argument is not a valid regular expression.'); | ||
| 2149 | } | ||
| 2150 | return JSON.stringify(re.test(text.toString())); | ||
| 2151 | }), | ||
| 2152 | returns: 'true | false', | ||
| 2153 | namedArgumentList: [ | ||
| 2154 | new SlashCommandNamedArgument( | ||
| 2155 | 'pattern', 'pattern to find', [ARGUMENT_TYPE.STRING], true, false, | ||
| 2156 | ), | ||
| 2157 | ], | ||
| 2158 | unnamedArgumentList: [ | ||
| 2159 | new SlashCommandArgument( | ||
| 2160 | 'text to test', [ARGUMENT_TYPE.STRING], true, false, | ||
| 2161 | ), | ||
| 2162 | ], | ||
| 2163 | helpString: ` | ||
| 2164 | <div> | ||
| 2165 | Tests text for a regular expression match. | ||
| 2166 | </div> | ||
| 2167 | <div> | ||
| 2168 | Returns <code>true</code> if the match is found, <code>false</code> otherwise. | ||
| 2169 | </div> | ||
| 2170 | <div> | ||
| 2171 | <strong>Example:</strong> | ||
| 2172 | <pre><code class="language-stscript">/let x Blue house and green car ||</code></pre> | ||
| 2173 | <pre><code class="language-stscript">/test pattern="green" {{var::x}} | /echo |/# true ||</code></pre> | ||
| 2174 | <pre><code class="language-stscript">/test pattern="blue" {{var::x}} | /echo |/# false ||</code></pre> | ||
| 2175 | <pre><code class="language-stscript">/test pattern="/blue/i" {{var::x}} | /echo |/# true ||</code></pre> | ||
| 2176 | </div> | ||
| 2177 | `, | ||
| 2178 | })); | ||
| 2179 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | ||
| 2180 | name: 'match', | ||
| 2181 | callback: (({ pattern }, text) => { | ||
| 2182 | if (!pattern) { | ||
| 2183 | throw new Error('Argument of \'pattern=\' cannot be empty'); | ||
| 2184 | } | ||
| 2185 | const re = regexFromString(pattern.toString()); | ||
| 2186 | if (!re) { | ||
| 2187 | throw new Error('The value of \'pattern\' argument is not a valid regular expression.'); | ||
| 2188 | } | ||
| 2189 | if (re.flags.includes('g')) { | ||
| 2190 | return JSON.stringify([...text.toString().matchAll(re)]); | ||
| 2191 | } else { | ||
| 2192 | const match = text.toString().match(re); | ||
| 2193 | return match ? JSON.stringify(match) : ''; | ||
| 2194 | } | ||
| 2195 | }), | ||
| 2196 | returns: 'group array for each match', | ||
| 2197 | namedArgumentList: [ | ||
| 2198 | new SlashCommandNamedArgument( | ||
| 2199 | 'pattern', 'pattern to find', [ARGUMENT_TYPE.STRING], true, false, | ||
| 2200 | ), | ||
| 2201 | ], | ||
| 2202 | unnamedArgumentList: [ | ||
| 2203 | new SlashCommandArgument( | ||
| 2204 | 'text to match against', [ARGUMENT_TYPE.STRING], true, false, | ||
| 2205 | ), | ||
| 2206 | ], | ||
| 2207 | helpString: ` | ||
| 2208 | <div> | ||
| 2209 | Retrieves regular expression matches in the given text | ||
| 2210 | </div> | ||
| 2211 | <div> | ||
| 2212 | Returns an array of groups (with the first group being the full match). If the regex contains the global flag (i.e. <code>/g</code>), | ||
| 2213 | multiple nested arrays are returned for each match. If the regex is global, returns <code>[]</code> if no matches are found, | ||
| 2214 | otherwise it returns an empty string. | ||
| 2215 | </div> | ||
| 2216 | <div> | ||
| 2217 | <strong>Example:</strong> | ||
| 2218 | <pre><code class="language-stscript">/let x color_green green lamp color_blue ||</code></pre> | ||
| 2219 | <pre><code class="language-stscript">/match pattern="green" {{var::x}} | /echo |/# [ "green" ] ||</code></pre> | ||
| 2220 | <pre><code class="language-stscript">/match pattern="color_(\\w+)" {{var::x}} | /echo |/# [ "color_green", "green" ] ||</code></pre> | ||
| 2221 | <pre><code class="language-stscript">/match pattern="/color_(\\w+)/g" {{var::x}} | /echo |/# [ [ "color_green", "green" ], [ "color_blue", "blue" ] ] ||</code></pre> | ||
| 2222 | <pre><code class="language-stscript">/match pattern="orange" {{var::x}} | /echo |/# ||</code></pre> | ||
| 2223 | <pre><code class="language-stscript">/match pattern="/orange/g" {{var::x}} | /echo |/# [] ||</code></pre> | ||
| 2224 | </div> | ||
| 2225 | `, | ||
| 2226 | })); | ||
| 2227 | |||
| 2228 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | ||
| 2229 | name: 'chat-jump', | ||
| 2230 | aliases: ['chat-scrollto', 'floor-teleport'], | ||
| 2231 | callback: async (_, index) => { | ||
| 2232 | const messageIndex = Number(index); | ||
| 2233 | |||
| 2234 | if (isNaN(messageIndex) || messageIndex < 0 || messageIndex >= chat.length) { | ||
| 2235 | toastr.warning(t`Invalid message index: ${index}. Please enter a number between 0 and ${chat.length}.`); | ||
| 2236 | console.warn(`WARN: Invalid message index provided for /chat-jump: ${index}. Max index: ${chat.length}`); | ||
| 2237 | return ''; | ||
| 2238 | } | ||
| 2239 | |||
| 2240 | // Load more messages if needed | ||
| 2241 | const firstDisplayedMessageId = getFirstDisplayedMessageId(); | ||
| 2242 | if (isFinite(firstDisplayedMessageId) && messageIndex < firstDisplayedMessageId) { | ||
| 2243 | const needToLoadCount = firstDisplayedMessageId - messageIndex; | ||
| 2244 | await showMoreMessages(needToLoadCount); | ||
| 2245 | await delay(1); | ||
| 2246 | } | ||
| 2247 | |||
| 2248 | const chatContainer = document.getElementById('chat'); | ||
| 2249 | const messageElement = document.querySelector(`#chat .mes[mesid="${messageIndex}"]`); | ||
| 2250 | |||
| 2251 | if (messageElement instanceof HTMLElement && chatContainer instanceof HTMLElement) { | ||
| 2252 | const elementRect = messageElement.getBoundingClientRect(); | ||
| 2253 | const containerRect = chatContainer.getBoundingClientRect(); | ||
| 2254 | |||
| 2255 | const scrollPosition = elementRect.top - containerRect.top + chatContainer.scrollTop; | ||
| 2256 | chatContainer.scrollTo({ | ||
| 2257 | top: scrollPosition, | ||
| 2258 | behavior: 'smooth', | ||
| 2259 | }); | ||
| 2260 | |||
| 2261 | flashHighlight($(messageElement), 2000); | ||
| 2262 | } else { | ||
| 2263 | toastr.warning(t`Could not find element for message ${messageIndex}. It might not be rendered yet or the index is invalid.`); | ||
| 2264 | console.warn(`WARN: Element not found for message index ${messageIndex} in /chat-jump.`); | ||
| 2265 | } | ||
| 2266 | |||
| 2267 | return ''; | ||
| 2268 | }, | ||
| 2269 | unnamedArgumentList: [ | ||
| 2270 | SlashCommandArgument.fromProps({ | ||
| 2271 | description: 'The message index (0-based) to scroll to.', | ||
| 2272 | typeList: [ARGUMENT_TYPE.NUMBER], | ||
| 2273 | isRequired: true, | ||
| 2274 | enumProvider: commonEnumProviders.messages(), | ||
| 2275 | }), | ||
| 2276 | ], | ||
| 2277 | helpString: ` | ||
| 2278 | <div> | ||
| 2279 | Scrolls the chat view to the specified message index. Index starts at 0. | ||
| 2280 | </div> | ||
| 2281 | <div> | ||
| 2282 | <strong>Example:</strong> <pre><code>/chat-jump 10</code></pre> Scrolls to the 11th message (id=10). | ||
| 2129 | </div> | 2283 | </div> |
| 2130 | `, | 2284 | `, |
| 2131 | })); | 2285 | })); |
| @@ -3702,7 +3856,7 @@ export async function sendMessageAs(args, text) { | |||
| 3702 | 3856 | ||
| 3703 | export async function sendNarratorMessage(args, text) { | 3857 | export async function sendNarratorMessage(args, text) { |
| 3704 | text = String(text ?? ''); | 3858 | text = String(text ?? ''); |
| 3705 | const name = chat_metadata[NARRATOR_NAME_KEY] || NARRATOR_NAME_DEFAULT; | 3859 | const name = args.name ?? (chat_metadata[NARRATOR_NAME_KEY] || NARRATOR_NAME_DEFAULT); |
| 3706 | // Messages that do nothing but set bias will be hidden from the context | 3860 | // Messages that do nothing but set bias will be hidden from the context |
| 3707 | const bias = extractMessageBias(text); | 3861 | const bias = extractMessageBias(text); |
| 3708 | const isSystem = bias && !removeMacros(text).length; | 3862 | const isSystem = bias && !removeMacros(text).length; |
| @@ -3942,6 +4096,7 @@ function getModelOptions(quiet) { | |||
| 3942 | { id: 'model_nanogpt_select', api: 'openai', type: chat_completion_sources.NANOGPT }, | 4096 | { id: 'model_nanogpt_select', api: 'openai', type: chat_completion_sources.NANOGPT }, |
| 3943 | { id: 'model_01ai_select', api: 'openai', type: chat_completion_sources.ZEROONEAI }, | 4097 | { id: 'model_01ai_select', api: 'openai', type: chat_completion_sources.ZEROONEAI }, |
| 3944 | { id: 'model_deepseek_select', api: 'openai', type: chat_completion_sources.DEEPSEEK }, | 4098 | { id: 'model_deepseek_select', api: 'openai', type: chat_completion_sources.DEEPSEEK }, |
| 4099 | { id: 'model_xai_select', api: 'openai', type: chat_completion_sources.XAI }, | ||
| 3945 | { id: 'model_novel_select', api: 'novel', type: null }, | 4100 | { id: 'model_novel_select', api: 'novel', type: null }, |
| 3946 | { id: 'horde_model', api: 'koboldhorde', type: null }, | 4101 | { id: 'horde_model', api: 'koboldhorde', type: null }, |
| 3947 | ]; | 4102 | ]; |
| @@ -50,6 +50,8 @@ import { | |||
| 50 | unshallowCharacter, | 50 | unshallowCharacter, |
| 51 | deleteLastMessage, | 51 | deleteLastMessage, |
| 52 | getCharacterCardFields, | 52 | getCharacterCardFields, |
| 53 | swipe_right, | ||
| 54 | swipe_left, | ||
| 53 | } from '../script.js'; | 55 | } from '../script.js'; |
| 54 | import { | 56 | import { |
| 55 | extension_settings, | 57 | extension_settings, |
| @@ -196,6 +198,7 @@ export function getContext() { | |||
| 196 | humanizedDateTime, | 198 | humanizedDateTime, |
| 197 | updateMessageBlock, | 199 | updateMessageBlock, |
| 198 | appendMediaToMessage, | 200 | appendMediaToMessage, |
| 201 | swipe: { left: swipe_left, right: swipe_right }, | ||
| 199 | variables: { | 202 | variables: { |
| 200 | local: { | 203 | local: { |
| 201 | get: getLocalVariable, | 204 | get: getLocalVariable, |
| @@ -17,6 +17,7 @@ export let system_prompts = []; | |||
| 17 | const $enabled = $('#sysprompt_enabled'); | 17 | const $enabled = $('#sysprompt_enabled'); |
| 18 | const $select = $('#sysprompt_select'); | 18 | const $select = $('#sysprompt_select'); |
| 19 | const $content = $('#sysprompt_content'); | 19 | const $content = $('#sysprompt_content'); |
| 20 | const $postHistory = $('#sysprompt_post_history'); | ||
| 20 | const $contentBlock = $('#SystemPromptBlock'); | 21 | const $contentBlock = $('#SystemPromptBlock'); |
| 21 | 22 | ||
| 22 | async function migrateSystemPromptFromInstructMode() { | 23 | async function migrateSystemPromptFromInstructMode() { |
| @@ -25,6 +26,7 @@ async function migrateSystemPromptFromInstructMode() { | |||
| 25 | delete power_user.instruct.system_prompt; | 26 | delete power_user.instruct.system_prompt; |
| 26 | power_user.sysprompt.enabled = power_user.instruct.enabled; | 27 | power_user.sysprompt.enabled = power_user.instruct.enabled; |
| 27 | power_user.sysprompt.content = prompt; | 28 | power_user.sysprompt.content = prompt; |
| 29 | power_user.sysprompt.post_history = ''; | ||
| 28 | 30 | ||
| 29 | const existingPromptName = system_prompts.find(x => x.content === prompt)?.name; | 31 | const existingPromptName = system_prompts.find(x => x.content === prompt)?.name; |
| 30 | 32 | ||
| @@ -59,7 +61,8 @@ export async function loadSystemPrompts(data) { | |||
| 59 | 61 | ||
| 60 | $enabled.prop('checked', power_user.sysprompt.enabled); | 62 | $enabled.prop('checked', power_user.sysprompt.enabled); |
| 61 | $select.val(power_user.sysprompt.name); | 63 | $select.val(power_user.sysprompt.name); |
| 62 | $content.val(power_user.sysprompt.content); | 64 | $content.val(power_user.sysprompt.content || ''); |
| 65 | $postHistory.val(power_user.sysprompt.post_history || ''); | ||
| 63 | if (!CSS.supports('field-sizing', 'content')) { | 66 | if (!CSS.supports('field-sizing', 'content')) { |
| 64 | await resetScrollHeight($content); | 67 | await resetScrollHeight($content); |
| 65 | } | 68 | } |
| @@ -165,13 +168,17 @@ export function initSystemPrompts() { | |||
| 165 | const name = String($(this).val()); | 168 | const name = String($(this).val()); |
| 166 | const prompt = system_prompts.find(p => p.name === name); | 169 | const prompt = system_prompts.find(p => p.name === name); |
| 167 | if (prompt) { | 170 | if (prompt) { |
| 168 | $content.val(prompt.content); | 171 | $content.val(prompt.content || ''); |
| 172 | $postHistory.val(prompt.post_history || ''); | ||
| 173 | |||
| 169 | if (!CSS.supports('field-sizing', 'content')) { | 174 | if (!CSS.supports('field-sizing', 'content')) { |
| 170 | await resetScrollHeight($content); | 175 | await resetScrollHeight($content); |
| 176 | await resetScrollHeight($postHistory); | ||
| 171 | } | 177 | } |
| 172 | 178 | ||
| 173 | power_user.sysprompt.name = name; | 179 | power_user.sysprompt.name = name; |
| 174 | power_user.sysprompt.content = prompt.content; | 180 | power_user.sysprompt.content = prompt.content || ''; |
| 181 | power_user.sysprompt.post_history = prompt.post_history || ''; | ||
| 175 | } | 182 | } |
| 176 | saveSettingsDebounced(); | 183 | saveSettingsDebounced(); |
| 177 | }); | 184 | }); |
| @@ -181,6 +188,11 @@ export function initSystemPrompts() { | |||
| 181 | saveSettingsDebounced(); | 188 | saveSettingsDebounced(); |
| 182 | }); | 189 | }); |
| 183 | 190 | ||
| 191 | $postHistory.on('input', function () { | ||
| 192 | power_user.sysprompt.post_history = String($(this).val()); | ||
| 193 | saveSettingsDebounced(); | ||
| 194 | }); | ||
| 195 | |||
| 184 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | 196 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| 185 | name: 'sysprompt', | 197 | name: 'sysprompt', |
| 186 | aliases: ['system-prompt'], | 198 | aliases: ['system-prompt'], |
| @@ -27,7 +27,7 @@ import { debounce_timeout } from './constants.js'; | |||
| 27 | import { INTERACTABLE_CONTROL_CLASS } from './keyboard.js'; | 27 | import { INTERACTABLE_CONTROL_CLASS } from './keyboard.js'; |
| 28 | import { commonEnumProviders } from './slash-commands/SlashCommandCommonEnumsProvider.js'; | 28 | import { commonEnumProviders } from './slash-commands/SlashCommandCommonEnumsProvider.js'; |
| 29 | import { renderTemplateAsync } from './templates.js'; | 29 | import { renderTemplateAsync } from './templates.js'; |
| 30 | import { t } from './i18n.js'; | 30 | import { t, translate } from './i18n.js'; |
| 31 | 31 | ||
| 32 | export { | 32 | export { |
| 33 | TAG_FOLDER_TYPES, | 33 | TAG_FOLDER_TYPES, |
| @@ -318,7 +318,7 @@ function getTagBlock(tag, entities, hidden = 0, isUseless = false) { | |||
| 318 | template.find('.avatar').css({ 'background-color': tag.color, 'color': tag.color2 }).attr('title', `[Folder] ${tag.name}`); | 318 | template.find('.avatar').css({ 'background-color': tag.color, 'color': tag.color2 }).attr('title', `[Folder] ${tag.name}`); |
| 319 | template.find('.ch_name').text(tag.name).attr('title', `[Folder] ${tag.name}`); | 319 | template.find('.ch_name').text(tag.name).attr('title', `[Folder] ${tag.name}`); |
| 320 | template.find('.bogus_folder_hidden_counter').text(hidden > 0 ? `${hidden} hidden` : ''); | 320 | template.find('.bogus_folder_hidden_counter').text(hidden > 0 ? `${hidden} hidden` : ''); |
| 321 | template.find('.bogus_folder_counter').text(`${count} ${count != 1 ? 'characters' : 'character'}`); | 321 | template.find('.bogus_folder_counter').text(`${count} ` + (count != 1 ? t`characters` : t`character`)); |
| 322 | template.find('.bogus_folder_icon').addClass(tagFolder.fa_icon); | 322 | template.find('.bogus_folder_icon').addClass(tagFolder.fa_icon); |
| 323 | if (isUseless) template.addClass('useless'); | 323 | if (isUseless) template.addClass('useless'); |
| 324 | 324 | ||
| @@ -1057,7 +1057,7 @@ function appendTagToList(listElement, tag, { removable = false, isFilter = false | |||
| 1057 | tagElement.attr('title', tag.title); | 1057 | tagElement.attr('title', tag.title); |
| 1058 | } | 1058 | } |
| 1059 | if (tag.icon) { | 1059 | if (tag.icon) { |
| 1060 | tagElement.find('.tag_name').text('').attr('title', `${tag.name} ${tag.title || ''}`.trim()).addClass(tag.icon); | 1060 | tagElement.find('.tag_name').text('').attr('title', `${translate(tag.name)} ${tag.title || ''}`.trim()).addClass(tag.icon); |
| 1061 | tagElement.addClass('actionable'); | 1061 | tagElement.addClass('actionable'); |
| 1062 | } | 1062 | } |
| 1063 | 1063 | ||
| @@ -1644,6 +1644,7 @@ function updateDrawTagFolder(element, tag) { | |||
| 1644 | 1644 | ||
| 1645 | // Draw/update css attributes for this class | 1645 | // Draw/update css attributes for this class |
| 1646 | folderElement.attr('title', tagFolder.tooltip); | 1646 | folderElement.attr('title', tagFolder.tooltip); |
| 1647 | folderElement.attr('data-i18n', '[title]' + tagFolder.tooltip); | ||
| 1647 | const indicator = folderElement.find('.tag_folder_indicator'); | 1648 | const indicator = folderElement.find('.tag_folder_indicator'); |
| 1648 | indicator.text(tagFolder.icon); | 1649 | indicator.text(tagFolder.icon); |
| 1649 | indicator.css('color', tagFolder.color); | 1650 | indicator.css('color', tagFolder.color); |
| @@ -1655,14 +1656,7 @@ async function onTagDeleteClick() { | |||
| 1655 | const tag = tags.find(x => x.id === id); | 1656 | const tag = tags.find(x => x.id === id); |
| 1656 | const otherTags = sortTags(tags.filter(x => x.id !== id).map(x => ({ id: x.id, name: x.name }))); | 1657 | const otherTags = sortTags(tags.filter(x => x.id !== id).map(x => ({ id: x.id, name: x.name }))); |
| 1657 | 1658 | ||
| 1658 | const popupContent = $(` | 1659 | const popupContent = $(await renderTemplateAsync('deleteTag', { otherTags })); |
| 1659 | <h3>Delete Tag</h3> | ||
| 1660 | <div>Do you want to delete the tag <div id="tag_to_delete" class="tags_inline inline-flex margin-r2"></div>?</div> | ||
| 1661 | <div class="m-t-2 marginBot5">If you want to merge all references to this tag into another tag, select it below:</div> | ||
| 1662 | <select id="merge_tag_select"> | ||
| 1663 | <option value="">--- None ---</option> | ||
| 1664 | ${otherTags.map(x => `<option value="${x.id}">${x.name}</option>`).join('')} | ||
| 1665 | </select>`); | ||
| 1666 | 1660 | ||
| 1667 | appendTagToList(popupContent.find('#tag_to_delete'), tag); | 1661 | appendTagToList(popupContent.find('#tag_to_delete'), tag); |
| 1668 | 1662 | ||
| @@ -0,0 +1,9 @@ | |||
| 1 | <h3 data-i18n="Delete Tag">Delete Tag</h3> | ||
| 2 | <div><span data-i18n="Do you want to delete the tag">Do you want to delete the tag</span> <div id="tag_to_delete" class="tags_inline inline-flex margin-r2"></div>?</div> | ||
| 3 | <div class="m-t-2 marginBot5" data-i18n="If you want to merge all references to this tag into another tag, select it below:">If you want to merge all references to this tag into another tag, select it below:</div> | ||
| 4 | <select id="merge_tag_select"> | ||
| 5 | <option value="">--- None ---</option> | ||
| 6 | {{#each otherTags}} | ||
| 7 | <option value="{{this.id}}">{{this.name}}</option> | ||
| 8 | {{/each}} | ||
| 9 | </select> | ||
| \ No newline at end of file | 9 | \ No newline at end of file | |
| @@ -0,0 +1,7 @@ | |||
| 1 | <div class="text_block empty_block"> | ||
| 2 | <i class="fa-solid {{icon}} fa-4x"></i> | ||
| 3 | <h1>{{text}}</h1> | ||
| 4 | <p data-i18n="There are no items to display."> | ||
| 5 | There are no items to display. | ||
| 6 | </p> | ||
| 7 | </div> | ||
| @@ -0,0 +1,6 @@ | |||
| 1 | <div class="text_block hidden_block"> | ||
| 2 | <small> | ||
| 3 | <p>{{text}}</p> | ||
| 4 | <div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]Characters and groups hidden by filters or closed folders" title="Characters and groups hidden by filters or closed folders"></div> | ||
| 5 | </small> | ||
| 6 | </div> | ||
| @@ -83,7 +83,10 @@ | |||
| 83 | <div class="tokenItemizingSubclass">{{scenarioTextTokens}}</div> | 83 | <div class="tokenItemizingSubclass">{{scenarioTextTokens}}</div> |
| 84 | </div> | 84 | </div> |
| 85 | <div class="flex-container "> | 85 | <div class="flex-container "> |
| 86 | <div class=" flex1 tokenItemizingSubclass">-- Examples:</div> | 86 | <div class=" flex1 tokenItemizingSubclass"> |
| 87 | <span>-- Examples:</span> | ||
| 88 | {{#if examplesCount}}<small>({{examplesCount}})</small>{{/if}} | ||
| 89 | </div> | ||
| 87 | <div class="tokenItemizingSubclass">{{examplesStringTokens}}</div> | 90 | <div class="tokenItemizingSubclass">{{examplesStringTokens}}</div> |
| 88 | </div> | 91 | </div> |
| 89 | <div class="flex-container "> | 92 | <div class="flex-container "> |
| @@ -96,7 +99,10 @@ | |||
| 96 | <div class="">{{worldInfoStringTokens}}</div> | 99 | <div class="">{{worldInfoStringTokens}}</div> |
| 97 | </div> | 100 | </div> |
| 98 | <div class="wide100p flex-container"> | 101 | <div class="wide100p flex-container"> |
| 99 | <div class="flex1" style="color: palegreen;"><span data-i18n="Chat History:">Chat History:</span></div> | 102 | <div class="flex1" style="color: palegreen;"> |
| 103 | <span data-i18n="Chat History:">Chat History:</span> | ||
| 104 | {{#if messagesCount}}<small>({{messagesCount}})</small>{{/if}} | ||
| 105 | </div> | ||
| 100 | <div class="">{{ActualChatHistoryTokens}}</div> | 106 | <div class="">{{ActualChatHistoryTokens}}</div> |
| 101 | </div> | 107 | </div> |
| 102 | <div class="wide100p flex-container flexNoGap flexFlowColumn"> | 108 | <div class="wide100p flex-container flexNoGap flexFlowColumn"> |
| @@ -51,7 +51,10 @@ | |||
| 51 | <div class="tokenItemizingSubclass">{{scenarioTextTokens}}</div> | 51 | <div class="tokenItemizingSubclass">{{scenarioTextTokens}}</div> |
| 52 | </div> | 52 | </div> |
| 53 | <div class="flex-container"> | 53 | <div class="flex-container"> |
| 54 | <div class=" flex1 tokenItemizingSubclass">-- Examples:</div> | 54 | <div class=" flex1 tokenItemizingSubclass"> |
| 55 | <span>-- Examples:</span> | ||
| 56 | {{#if examplesCount}}<small>({{examplesCount}})</small>{{/if}} | ||
| 57 | </div> | ||
| 55 | <div class="tokenItemizingSubclass"> {{examplesStringTokens}}</div> | 58 | <div class="tokenItemizingSubclass"> {{examplesStringTokens}}</div> |
| 56 | </div> | 59 | </div> |
| 57 | <div class="flex-container"> | 60 | <div class="flex-container"> |
| @@ -68,7 +71,10 @@ | |||
| 68 | <div class="">{{worldInfoStringTokens}}</div> | 71 | <div class="">{{worldInfoStringTokens}}</div> |
| 69 | </div> | 72 | </div> |
| 70 | <div class="wide100p flex-container"> | 73 | <div class="wide100p flex-container"> |
| 71 | <div class="flex1" style="color: palegreen;">Chat History:</div> | 74 | <div class="flex1" style="color: palegreen;"> |
| 75 | <span data-i18n="Chat History:">Chat History:</span> | ||
| 76 | {{#if messagesCount}}<small>({{messagesCount}})</small>{{/if}} | ||
| 77 | </div> | ||
| 72 | <div class=""> {{ActualChatHistoryTokens}}</div> | 78 | <div class=""> {{ActualChatHistoryTokens}}</div> |
| 73 | </div> | 79 | </div> |
| 74 | <div class="wide100p flex-container flexNoGap flexFlowColumn"> | 80 | <div class="wide100p flex-container flexNoGap flexFlowColumn"> |
| @@ -7,6 +7,7 @@ import { renderTemplateAsync } from './templates.js'; | |||
| 7 | import { POPUP_TYPE, callGenericPopup } from './popup.js'; | 7 | import { POPUP_TYPE, callGenericPopup } from './popup.js'; |
| 8 | import { t } from './i18n.js'; | 8 | import { t } from './i18n.js'; |
| 9 | import { accountStorage } from './util/AccountStorage.js'; | 9 | import { accountStorage } from './util/AccountStorage.js'; |
| 10 | import { localizePagination, PAGINATION_TEMPLATE } from './utils.js'; | ||
| 10 | 11 | ||
| 11 | let mancerModels = []; | 12 | let mancerModels = []; |
| 12 | let togetherModels = []; | 13 | let togetherModels = []; |
| @@ -41,12 +42,7 @@ const OPENROUTER_PROVIDERS = [ | |||
| 41 | 'Avian', | 42 | 'Avian', |
| 42 | 'Lambda', | 43 | 'Lambda', |
| 43 | 'Azure', | 44 | 'Azure', |
| 44 | 'Modal', | ||
| 45 | 'AnyScale', | ||
| 46 | 'Replicate', | ||
| 47 | 'Perplexity', | 45 | 'Perplexity', |
| 48 | 'Recursal', | ||
| 49 | 'OctoAI', | ||
| 50 | 'DeepSeek', | 46 | 'DeepSeek', |
| 51 | 'Infermatic', | 47 | 'Infermatic', |
| 52 | 'AI21', | 48 | 'AI21', |
| @@ -54,10 +50,12 @@ const OPENROUTER_PROVIDERS = [ | |||
| 54 | 'Inflection', | 50 | 'Inflection', |
| 55 | 'xAI', | 51 | 'xAI', |
| 56 | 'Cloudflare', | 52 | 'Cloudflare', |
| 57 | 'SF Compute', | ||
| 58 | 'Minimax', | 53 | 'Minimax', |
| 59 | 'Nineteen', | 54 | 'Nineteen', |
| 60 | 'Liquid', | 55 | 'Liquid', |
| 56 | 'GMICloud', | ||
| 57 | 'Stealth', | ||
| 58 | 'NCompass', | ||
| 61 | 'InferenceNet', | 59 | 'InferenceNet', |
| 62 | 'Friendli', | 60 | 'Friendli', |
| 63 | 'AionLabs', | 61 | 'AionLabs', |
| @@ -69,14 +67,16 @@ const OPENROUTER_PROVIDERS = [ | |||
| 69 | 'Targon', | 67 | 'Targon', |
| 70 | 'Ubicloud', | 68 | 'Ubicloud', |
| 71 | 'Parasail', | 69 | 'Parasail', |
| 72 | '01.AI', | 70 | 'Phala', |
| 73 | 'HuggingFace', | 71 | 'Cent-ML', |
| 72 | 'Venice', | ||
| 73 | 'OpenInference', | ||
| 74 | 'Atoma', | ||
| 75 | 'Enfer', | ||
| 74 | 'Mancer', | 76 | 'Mancer', |
| 75 | 'Mancer 2', | 77 | 'Mancer 2', |
| 76 | 'Hyperbolic', | 78 | 'Hyperbolic', |
| 77 | 'Hyperbolic 2', | 79 | 'Hyperbolic 2', |
| 78 | 'Lynn 2', | ||
| 79 | 'Lynn', | ||
| 80 | 'Reflection', | 80 | 'Reflection', |
| 81 | ]; | 81 | ]; |
| 82 | 82 | ||
| @@ -362,9 +362,7 @@ export async function loadFeatherlessModels(data) { | |||
| 362 | showSizeChanger: false, | 362 | showSizeChanger: false, |
| 363 | prevText: '<', | 363 | prevText: '<', |
| 364 | nextText: '>', | 364 | nextText: '>', |
| 365 | formatNavigator: function (currentPage, totalPage) { | 365 | formatNavigator: PAGINATION_TEMPLATE, |
| 366 | return (currentPage - 1) * perPage + 1 + ' - ' + currentPage * perPage + ' of ' + totalPage * perPage; | ||
| 367 | }, | ||
| 368 | showNavigator: true, | 366 | showNavigator: true, |
| 369 | callback: function (modelsOnPage, pagination) { | 367 | callback: function (modelsOnPage, pagination) { |
| 370 | modelCardBlock.innerHTML = ''; | 368 | modelCardBlock.innerHTML = ''; |
| @@ -386,15 +384,15 @@ export async function loadFeatherlessModels(data) { | |||
| 386 | 384 | ||
| 387 | const modelClassDiv = document.createElement('div'); | 385 | const modelClassDiv = document.createElement('div'); |
| 388 | modelClassDiv.classList.add('model-class'); | 386 | modelClassDiv.classList.add('model-class'); |
| 389 | modelClassDiv.textContent = `Class: ${model.model_class || 'N/A'}`; | 387 | modelClassDiv.textContent = t`Class` + `: ${model.model_class || 'N/A'}`; |
| 390 | 388 | ||
| 391 | const contextLengthDiv = document.createElement('div'); | 389 | const contextLengthDiv = document.createElement('div'); |
| 392 | contextLengthDiv.classList.add('model-context-length'); | 390 | contextLengthDiv.classList.add('model-context-length'); |
| 393 | contextLengthDiv.textContent = `Context Length: ${model.context_length}`; | 391 | contextLengthDiv.textContent = t`Context Length` + `: ${model.context_length}`; |
| 394 | 392 | ||
| 395 | const dateAddedDiv = document.createElement('div'); | 393 | const dateAddedDiv = document.createElement('div'); |
| 396 | dateAddedDiv.classList.add('model-date-added'); | 394 | dateAddedDiv.classList.add('model-date-added'); |
| 397 | dateAddedDiv.textContent = `Added On: ${new Date(model.created * 1000).toLocaleDateString()}`; | 395 | dateAddedDiv.textContent = t`Added On` + `: ${new Date(model.created * 1000).toLocaleDateString()}`; |
| 398 | 396 | ||
| 399 | detailsContainer.appendChild(modelClassDiv); | 397 | detailsContainer.appendChild(modelClassDiv); |
| 400 | detailsContainer.appendChild(contextLengthDiv); | 398 | detailsContainer.appendChild(contextLengthDiv); |
| @@ -418,6 +416,7 @@ export async function loadFeatherlessModels(data) { | |||
| 418 | 416 | ||
| 419 | // Update the current page value whenever the page changes | 417 | // Update the current page value whenever the page changes |
| 420 | featherlessCurrentPage = pagination.pageNumber; | 418 | featherlessCurrentPage = pagination.pageNumber; |
| 419 | localizePagination(paginationContainer); | ||
| 421 | }, | 420 | }, |
| 422 | afterSizeSelectorChange: function (e) { | 421 | afterSizeSelectorChange: function (e) { |
| 423 | const newPerPage = e.target.value; | 422 | const newPerPage = e.target.value; |
| @@ -923,6 +922,10 @@ export function getCurrentDreamGenModelTokenizer() { | |||
| 923 | return tokenizers.YI; | 922 | return tokenizers.YI; |
| 924 | } else if (model.id.startsWith('opus-v1-xl')) { | 923 | } else if (model.id.startsWith('opus-v1-xl')) { |
| 925 | return tokenizers.LLAMA; | 924 | return tokenizers.LLAMA; |
| 925 | } else if (model.id.startsWith('lucid-v1-medium')) { | ||
| 926 | return tokenizers.NEMO; | ||
| 927 | } else if (model.id.startsWith('lucid-v1-extra-large')) { | ||
| 928 | return tokenizers.LLAMA3; | ||
| 926 | } else { | 929 | } else { |
| 927 | return tokenizers.MISTRAL; | 930 | return tokenizers.MISTRAL; |
| 928 | } | 931 | } |
| @@ -1146,7 +1146,7 @@ function tryParseStreamingError(response, decoded) { | |||
| 1146 | // No JSON. Do nothing. | 1146 | // No JSON. Do nothing. |
| 1147 | } | 1147 | } |
| 1148 | 1148 | ||
| 1149 | const message = data?.error?.message || data?.message || data?.detail; | 1149 | const message = data?.error?.message || data?.error || data?.message || data?.detail; |
| 1150 | 1150 | ||
| 1151 | if (message) { | 1151 | if (message) { |
| 1152 | toastr.error(message, 'Text Completion API'); | 1152 | toastr.error(message, 'Text Completion API'); |
| @@ -586,6 +586,7 @@ export class ToolManager { | |||
| 586 | chat_completion_sources.DEEPSEEK, | 586 | chat_completion_sources.DEEPSEEK, |
| 587 | chat_completion_sources.MAKERSUITE, | 587 | chat_completion_sources.MAKERSUITE, |
| 588 | chat_completion_sources.AI21, | 588 | chat_completion_sources.AI21, |
| 589 | chat_completion_sources.XAI, | ||
| 589 | ]; | 590 | ]; |
| 590 | return supportedSources.includes(oai_settings.chat_completion_source); | 591 | return supportedSources.includes(oai_settings.chat_completion_source); |
| 591 | } | 592 | } |
| @@ -20,7 +20,46 @@ import { getCurrentLocale, t } from './i18n.js'; | |||
| 20 | * Pagination status string template. | 20 | * Pagination status string template. |
| 21 | * @type {string} | 21 | * @type {string} |
| 22 | */ | 22 | */ |
| 23 | export const PAGINATION_TEMPLATE = '<%= rangeStart %>-<%= rangeEnd %> of <%= totalNumber %>'; | 23 | export const PAGINATION_TEMPLATE = '<%= rangeStart %>-<%= rangeEnd %> .. <%= totalNumber %>'; |
| 24 | |||
| 25 | export const localizePagination = function(container) { | ||
| 26 | container.find('[title="Next page"]').attr('title', t`Next page`); | ||
| 27 | container.find('[title="Previous page"]').attr('title', t`Previous page`); | ||
| 28 | }; | ||
| 29 | |||
| 30 | /** | ||
| 31 | * Renders a dropdown for selecting page size in pagination. | ||
| 32 | * @param {number} pageSize Page size | ||
| 33 | * @param {number[]} sizeChangerOptions Array of page size options | ||
| 34 | * @returns {string} The rendered dropdown element as a string | ||
| 35 | */ | ||
| 36 | export const renderPaginationDropdown = function(pageSize, sizeChangerOptions) { | ||
| 37 | const sizeSelect = document.createElement('select'); | ||
| 38 | sizeSelect.classList.add('J-paginationjs-size-select'); | ||
| 39 | |||
| 40 | if (sizeChangerOptions.indexOf(pageSize) === -1) { | ||
| 41 | sizeChangerOptions.unshift(pageSize); | ||
| 42 | sizeChangerOptions.sort((a, b) => a - b); | ||
| 43 | } | ||
| 44 | |||
| 45 | for (let i = 0; i < sizeChangerOptions.length; i++) { | ||
| 46 | const option = document.createElement('option'); | ||
| 47 | option.value = `${sizeChangerOptions[i]}`; | ||
| 48 | option.textContent = `${sizeChangerOptions[i]} ${t`/ page`}`; | ||
| 49 | if (sizeChangerOptions[i] === pageSize) { | ||
| 50 | option.setAttribute('selected', 'selected'); | ||
| 51 | } | ||
| 52 | sizeSelect.appendChild(option); | ||
| 53 | } | ||
| 54 | |||
| 55 | return sizeSelect.outerHTML; | ||
| 56 | }; | ||
| 57 | |||
| 58 | export const paginationDropdownChangeHandler = function(event, size) { | ||
| 59 | let dropdown = $(event?.originalEvent?.currentTarget || event.delegateTarget).find('select'); | ||
| 60 | dropdown.find('[selected]').removeAttr('selected'); | ||
| 61 | dropdown.find(`[value=${size}]`).attr('selected', ''); | ||
| 62 | }; | ||
| 24 | 63 | ||
| 25 | /** | 64 | /** |
| 26 | * Navigation options for pagination. | 65 | * Navigation options for pagination. |
| @@ -1047,7 +1086,7 @@ export function getImageSizeFromDataURL(dataUrl) { | |||
| 1047 | 1086 | ||
| 1048 | /** | 1087 | /** |
| 1049 | * Gets the filename of the character avatar without extension | 1088 | * Gets the filename of the character avatar without extension |
| 1050 | * @param {number?} [chid=null] - Character ID. If not provided, uses the current character ID | 1089 | * @param {string|number?} [chid=null] - Character ID. If not provided, uses the current character ID |
| 1051 | * @param {object} [options={}] - Options arguments | 1090 | * @param {object} [options={}] - Options arguments |
| 1052 | * @param {string?} [options.manualAvatarKey=null] - Manually take the following avatar key, instead of using the chid to determine the name | 1091 | * @param {string?} [options.manualAvatarKey=null] - Manually take the following avatar key, instead of using the chid to determine the name |
| 1053 | * @returns {string?} The filename of the character avatar without extension, or null if the character ID is invalid | 1092 | * @returns {string?} The filename of the character avatar without extension, or null if the character ID is invalid |
| @@ -98,11 +98,28 @@ const KNOWN_DECORATORS = ['@@activate', '@@dont_activate']; | |||
| 98 | 98 | ||
| 99 | // Typedef area | 99 | // Typedef area |
| 100 | /** | 100 | /** |
| 101 | * @typedef {object} WIGlobalScanData The chat-independent data to be scanned. Each of | ||
| 102 | * these fields can be enabled for scanning per entry. | ||
| 103 | * @property {string} personaDescription User persona description | ||
| 104 | * @property {string} characterDescription Character description | ||
| 105 | * @property {string} characterPersonality Character personality | ||
| 106 | * @property {string} characterDepthPrompt Character depth prompt (sometimes referred to as character notes) | ||
| 107 | * @property {string} scenario Character defined scenario | ||
| 108 | * @property {string} creatorNotes Character creator notes | ||
| 109 | */ | ||
| 110 | |||
| 111 | /** | ||
| 101 | * @typedef {object} WIScanEntry The entry that triggered the scan | 112 | * @typedef {object} WIScanEntry The entry that triggered the scan |
| 102 | * @property {number} [scanDepth] The depth of the scan | 113 | * @property {number} [scanDepth] The depth of the scan |
| 103 | * @property {boolean} [caseSensitive] If the scan is case sensitive | 114 | * @property {boolean} [caseSensitive] If the scan is case sensitive |
| 104 | * @property {boolean} [matchWholeWords] If the scan should match whole words | 115 | * @property {boolean} [matchWholeWords] If the scan should match whole words |
| 105 | * @property {boolean} [useGroupScoring] If the scan should use group scoring | 116 | * @property {boolean} [useGroupScoring] If the scan should use group scoring |
| 117 | * @property {boolean} [matchPersonaDescription] If the scan should match against the persona description | ||
| 118 | * @property {boolean} [matchCharacterDescription] If the scan should match against the character description | ||
| 119 | * @property {boolean} [matchCharacterPersonality] If the scan should match against the character personality | ||
| 120 | * @property {boolean} [matchCharacterDepthPrompt] If the scan should match against the character depth prompt | ||
| 121 | * @property {boolean} [matchScenario] If the scan should match against the character scenario | ||
| 122 | * @property {boolean} [matchCreatorNotes] If the scan should match against the creator notes | ||
| 106 | * @property {number} [uid] The UID of the entry that triggered the scan | 123 | * @property {number} [uid] The UID of the entry that triggered the scan |
| 107 | * @property {string} [world] The world info book of origin of the entry | 124 | * @property {string} [world] The world info book of origin of the entry |
| 108 | * @property {string[]} [key] The primary keys to scan for | 125 | * @property {string[]} [key] The primary keys to scan for |
| @@ -139,6 +156,11 @@ class WorldInfoBuffer { | |||
| 139 | static externalActivations = new Map(); | 156 | static externalActivations = new Map(); |
| 140 | 157 | ||
| 141 | /** | 158 | /** |
| 159 | * @type {WIGlobalScanData} Chat independent data to be scanned, such as persona and character descriptions | ||
| 160 | */ | ||
| 161 | #globalScanData = null; | ||
| 162 | |||
| 163 | /** | ||
| 142 | * @type {string[]} Array of messages sorted by ascending depth | 164 | * @type {string[]} Array of messages sorted by ascending depth |
| 143 | */ | 165 | */ |
| 144 | #depthBuffer = []; | 166 | #depthBuffer = []; |
| @@ -166,9 +188,11 @@ class WorldInfoBuffer { | |||
| 166 | /** | 188 | /** |
| 167 | * Initialize the buffer with the given messages. | 189 | * Initialize the buffer with the given messages. |
| 168 | * @param {string[]} messages Array of messages to add to the buffer | 190 | * @param {string[]} messages Array of messages to add to the buffer |
| 191 | * @param {WIGlobalScanData} globalScanData Chat independent context to be scanned | ||
| 169 | */ | 192 | */ |
| 170 | constructor(messages) { | 193 | constructor(messages, globalScanData) { |
| 171 | this.#initDepthBuffer(messages); | 194 | this.#initDepthBuffer(messages); |
| 195 | this.#globalScanData = globalScanData; | ||
| 172 | } | 196 | } |
| 173 | 197 | ||
| 174 | /** | 198 | /** |
| @@ -225,6 +249,25 @@ class WorldInfoBuffer { | |||
| 225 | const JOINER = '\n' + MATCHER; | 249 | const JOINER = '\n' + MATCHER; |
| 226 | let result = MATCHER + this.#depthBuffer.slice(this.#startDepth, depth).join(JOINER); | 250 | let result = MATCHER + this.#depthBuffer.slice(this.#startDepth, depth).join(JOINER); |
| 227 | 251 | ||
| 252 | if (entry.matchPersonaDescription && this.#globalScanData.personaDescription) { | ||
| 253 | result += JOINER + this.#globalScanData.personaDescription; | ||
| 254 | } | ||
| 255 | if (entry.matchCharacterDescription && this.#globalScanData.characterDescription) { | ||
| 256 | result += JOINER + this.#globalScanData.characterDescription; | ||
| 257 | } | ||
| 258 | if (entry.matchCharacterPersonality && this.#globalScanData.characterPersonality) { | ||
| 259 | result += JOINER + this.#globalScanData.characterPersonality; | ||
| 260 | } | ||
| 261 | if (entry.matchCharacterDepthPrompt && this.#globalScanData.characterDepthPrompt) { | ||
| 262 | result += JOINER + this.#globalScanData.characterDepthPrompt; | ||
| 263 | } | ||
| 264 | if (entry.matchScenario && this.#globalScanData.scenario) { | ||
| 265 | result += JOINER + this.#globalScanData.scenario; | ||
| 266 | } | ||
| 267 | if (entry.matchCreatorNotes && this.#globalScanData.creatorNotes) { | ||
| 268 | result += JOINER + this.#globalScanData.creatorNotes; | ||
| 269 | } | ||
| 270 | |||
| 228 | if (this.#injectBuffer.length > 0) { | 271 | if (this.#injectBuffer.length > 0) { |
| 229 | result += JOINER + this.#injectBuffer.join(JOINER); | 272 | result += JOINER + this.#injectBuffer.join(JOINER); |
| 230 | } | 273 | } |
| @@ -756,6 +799,7 @@ export const worldInfoCache = new StructuredCloneMap({ cloneOnGet: true, cloneOn | |||
| 756 | * @param {string[]} chat - The chat messages to scan, in reverse order. | 799 | * @param {string[]} chat - The chat messages to scan, in reverse order. |
| 757 | * @param {number} maxContext - The maximum context size of the generation. | 800 | * @param {number} maxContext - The maximum context size of the generation. |
| 758 | * @param {boolean} isDryRun - If true, the function will not emit any events. | 801 | * @param {boolean} isDryRun - If true, the function will not emit any events. |
| 802 | * @param {WIGlobalScanData} globalScanData Chat independent context to be scanned | ||
| 759 | * @typedef {object} WIPromptResult | 803 | * @typedef {object} WIPromptResult |
| 760 | * @property {string} worldInfoString - Complete world info string | 804 | * @property {string} worldInfoString - Complete world info string |
| 761 | * @property {string} worldInfoBefore - World info that goes before the prompt | 805 | * @property {string} worldInfoBefore - World info that goes before the prompt |
| @@ -766,10 +810,10 @@ export const worldInfoCache = new StructuredCloneMap({ cloneOnGet: true, cloneOn | |||
| 766 | * @property {Array} anAfter - Array of entries after Author's Note | 810 | * @property {Array} anAfter - Array of entries after Author's Note |
| 767 | * @returns {Promise<WIPromptResult>} The world info string and depth. | 811 | * @returns {Promise<WIPromptResult>} The world info string and depth. |
| 768 | */ | 812 | */ |
| 769 | export async function getWorldInfoPrompt(chat, maxContext, isDryRun) { | 813 | export async function getWorldInfoPrompt(chat, maxContext, isDryRun, globalScanData) { |
| 770 | let worldInfoString = '', worldInfoBefore = '', worldInfoAfter = ''; | 814 | let worldInfoString = '', worldInfoBefore = '', worldInfoAfter = ''; |
| 771 | 815 | ||
| 772 | const activatedWorldInfo = await checkWorldInfo(chat, maxContext, isDryRun); | 816 | const activatedWorldInfo = await checkWorldInfo(chat, maxContext, isDryRun, globalScanData); |
| 773 | worldInfoBefore = activatedWorldInfo.worldInfoBefore; | 817 | worldInfoBefore = activatedWorldInfo.worldInfoBefore; |
| 774 | worldInfoAfter = activatedWorldInfo.worldInfoAfter; | 818 | worldInfoAfter = activatedWorldInfo.worldInfoAfter; |
| 775 | worldInfoString = worldInfoBefore + worldInfoAfter; | 819 | worldInfoString = worldInfoBefore + worldInfoAfter; |
| @@ -966,7 +1010,7 @@ function registerWorldInfoSlashCommands() { | |||
| 966 | /** | 1010 | /** |
| 967 | * Gets the name of the character-bound lorebook. | 1011 | * Gets the name of the character-bound lorebook. |
| 968 | * @param {import('./slash-commands/SlashCommand.js').NamedArguments} args Named arguments | 1012 | * @param {import('./slash-commands/SlashCommand.js').NamedArguments} args Named arguments |
| 969 | * @param {import('./slash-commands/SlashCommand.js').UnnamedArguments} name Character name | 1013 | * @param {string} name Character name |
| 970 | * @returns {string} The name of the character-bound lorebook, a JSON string of the character's lorebooks, or an empty string | 1014 | * @returns {string} The name of the character-bound lorebook, a JSON string of the character's lorebooks, or an empty string |
| 971 | */ | 1015 | */ |
| 972 | function getCharBookCallback({ type }, name) { | 1016 | function getCharBookCallback({ type }, name) { |
| @@ -1338,6 +1382,18 @@ function registerWorldInfoSlashCommands() { | |||
| 1338 | } | 1382 | } |
| 1339 | } | 1383 | } |
| 1340 | 1384 | ||
| 1385 | async function getGlobalBooksCallback() { | ||
| 1386 | if (!selected_world_info?.length) { | ||
| 1387 | return JSON.stringify([]); | ||
| 1388 | } | ||
| 1389 | |||
| 1390 | let entries = selected_world_info.slice(); | ||
| 1391 | |||
| 1392 | console.debug(`[WI] Selected global world info has ${entries.length} entries`, selected_world_info); | ||
| 1393 | |||
| 1394 | return JSON.stringify(entries); | ||
| 1395 | } | ||
| 1396 | |||
| 1341 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | 1397 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| 1342 | name: 'world', | 1398 | name: 'world', |
| 1343 | callback: onWorldInfoChange, | 1399 | callback: onWorldInfoChange, |
| @@ -1380,6 +1436,13 @@ function registerWorldInfoSlashCommands() { | |||
| 1380 | aliases: ['getchatlore', 'getchatwi'], | 1436 | aliases: ['getchatlore', 'getchatwi'], |
| 1381 | })); | 1437 | })); |
| 1382 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | 1438 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| 1439 | name: 'getglobalbooks', | ||
| 1440 | callback: getGlobalBooksCallback, | ||
| 1441 | returns: 'list of selected lorebook names', | ||
| 1442 | helpString: 'Get a list of names of the selected global lorebooks and pass it down the pipe.', | ||
| 1443 | aliases: ['getgloballore', 'getglobalwi'], | ||
| 1444 | })); | ||
| 1445 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | ||
| 1383 | name: 'getpersonabook', | 1446 | name: 'getpersonabook', |
| 1384 | callback: getPersonaBookCallback, | 1447 | callback: getPersonaBookCallback, |
| 1385 | returns: 'lorebook name', | 1448 | returns: 'lorebook name', |
| @@ -2172,6 +2235,12 @@ export const originalWIDataKeyMap = { | |||
| 2172 | 'matchWholeWords': 'extensions.match_whole_words', | 2235 | 'matchWholeWords': 'extensions.match_whole_words', |
| 2173 | 'useGroupScoring': 'extensions.use_group_scoring', | 2236 | 'useGroupScoring': 'extensions.use_group_scoring', |
| 2174 | 'caseSensitive': 'extensions.case_sensitive', | 2237 | 'caseSensitive': 'extensions.case_sensitive', |
| 2238 | 'matchPersonaDescription': 'extensions.match_persona_description', | ||
| 2239 | 'matchCharacterDescription': 'extensions.match_character_description', | ||
| 2240 | 'matchCharacterPersonality': 'extensions.match_character_personality', | ||
| 2241 | 'matchCharacterDepthPrompt': 'extensions.match_character_depth_prompt', | ||
| 2242 | 'matchScenario': 'extensions.match_scenario', | ||
| 2243 | 'matchCreatorNotes': 'extensions.match_creator_notes', | ||
| 2175 | 'scanDepth': 'extensions.scan_depth', | 2244 | 'scanDepth': 'extensions.scan_depth', |
| 2176 | 'automationId': 'extensions.automation_id', | 2245 | 'automationId': 'extensions.automation_id', |
| 2177 | 'vectorized': 'extensions.vectorized', | 2246 | 'vectorized': 'extensions.vectorized', |
| @@ -2589,7 +2658,7 @@ export async function getWorldEntry(name, data, entry) { | |||
| 2589 | if (!isMobile()) { | 2658 | if (!isMobile()) { |
| 2590 | $(characterFilter).select2({ | 2659 | $(characterFilter).select2({ |
| 2591 | width: '100%', | 2660 | width: '100%', |
| 2592 | placeholder: 'Tie this entry to specific characters or characters with specific tags', | 2661 | placeholder: t`Tie this entry to specific characters or characters with specific tags`, |
| 2593 | allowClear: true, | 2662 | allowClear: true, |
| 2594 | closeOnSelect: false, | 2663 | closeOnSelect: false, |
| 2595 | }); | 2664 | }); |
| @@ -3189,7 +3258,7 @@ export async function getWorldEntry(name, data, entry) { | |||
| 3189 | 3258 | ||
| 3190 | // Create wrapper div | 3259 | // Create wrapper div |
| 3191 | const wrapper = document.createElement('div'); | 3260 | const wrapper = document.createElement('div'); |
| 3192 | wrapper.textContent = t`Move "${sourceName}" to:`; | 3261 | wrapper.textContent = t`Move '${sourceName}' to:`; |
| 3193 | 3262 | ||
| 3194 | // Create container and append elements | 3263 | // Create container and append elements |
| 3195 | const container = document.createElement('div'); | 3264 | const container = document.createElement('div'); |
| @@ -3289,6 +3358,28 @@ export async function getWorldEntry(name, data, entry) { | |||
| 3289 | }); | 3358 | }); |
| 3290 | useGroupScoringSelect.val((entry.useGroupScoring === null || entry.useGroupScoring === undefined) ? 'null' : entry.useGroupScoring ? 'true' : 'false').trigger('input'); | 3359 | useGroupScoringSelect.val((entry.useGroupScoring === null || entry.useGroupScoring === undefined) ? 'null' : entry.useGroupScoring ? 'true' : 'false').trigger('input'); |
| 3291 | 3360 | ||
| 3361 | function handleMatchCheckbox(fieldName) { | ||
| 3362 | const key = originalWIDataKeyMap[fieldName]; | ||
| 3363 | const checkBoxElem = template.find(`input[type="checkbox"][name="${fieldName}"]`); | ||
| 3364 | checkBoxElem.data('uid', entry.uid); | ||
| 3365 | checkBoxElem.on('input', async function () { | ||
| 3366 | const uid = $(this).data('uid'); | ||
| 3367 | const value = $(this).prop('checked'); | ||
| 3368 | |||
| 3369 | data.entries[uid][fieldName] = value; | ||
| 3370 | setWIOriginalDataValue(data, uid, key, data.entries[uid][fieldName]); | ||
| 3371 | await saveWorldInfo(name, data); | ||
| 3372 | }); | ||
| 3373 | checkBoxElem.prop('checked', !!entry[fieldName]).trigger('input'); | ||
| 3374 | } | ||
| 3375 | |||
| 3376 | handleMatchCheckbox('matchPersonaDescription'); | ||
| 3377 | handleMatchCheckbox('matchCharacterDescription'); | ||
| 3378 | handleMatchCheckbox('matchCharacterPersonality'); | ||
| 3379 | handleMatchCheckbox('matchCharacterDepthPrompt'); | ||
| 3380 | handleMatchCheckbox('matchScenario'); | ||
| 3381 | handleMatchCheckbox('matchCreatorNotes'); | ||
| 3382 | |||
| 3292 | // automation id | 3383 | // automation id |
| 3293 | const automationIdInput = template.find('input[name="automationId"]'); | 3384 | const automationIdInput = template.find('input[name="automationId"]'); |
| 3294 | automationIdInput.data('uid', entry.uid); | 3385 | automationIdInput.data('uid', entry.uid); |
| @@ -3424,7 +3515,7 @@ function createEntryInputAutocomplete(input, callback, { allowMultiple = false } | |||
| 3424 | }); | 3515 | }); |
| 3425 | 3516 | ||
| 3426 | $(input).on('focus click', function () { | 3517 | $(input).on('focus click', function () { |
| 3427 | $(input).autocomplete('search', allowMultiple ? String($(input).val()).split(/,\s*/).pop() : $(input).val()); | 3518 | $(input).autocomplete('search', allowMultiple ? String($(input).val()).split(/,\s*/).pop() : String($(input).val())); |
| 3428 | }); | 3519 | }); |
| 3429 | } | 3520 | } |
| 3430 | 3521 | ||
| @@ -3495,6 +3586,12 @@ export const newWorldInfoEntryDefinition = { | |||
| 3495 | disable: { default: false, type: 'boolean' }, | 3586 | disable: { default: false, type: 'boolean' }, |
| 3496 | excludeRecursion: { default: false, type: 'boolean' }, | 3587 | excludeRecursion: { default: false, type: 'boolean' }, |
| 3497 | preventRecursion: { default: false, type: 'boolean' }, | 3588 | preventRecursion: { default: false, type: 'boolean' }, |
| 3589 | matchPersonaDescription: { default: false, type: 'boolean' }, | ||
| 3590 | matchCharacterDescription: { default: false, type: 'boolean' }, | ||
| 3591 | matchCharacterPersonality: { default: false, type: 'boolean' }, | ||
| 3592 | matchCharacterDepthPrompt: { default: false, type: 'boolean' }, | ||
| 3593 | matchScenario: { default: false, type: 'boolean' }, | ||
| 3594 | matchCreatorNotes: { default: false, type: 'boolean' }, | ||
| 3498 | delayUntilRecursion: { default: 0, type: 'number' }, | 3595 | delayUntilRecursion: { default: 0, type: 'number' }, |
| 3499 | probability: { default: 100, type: 'number' }, | 3596 | probability: { default: 100, type: 'number' }, |
| 3500 | useProbability: { default: true, type: 'boolean' }, | 3597 | useProbability: { default: true, type: 'boolean' }, |
| @@ -3959,6 +4056,7 @@ function parseDecorators(content) { | |||
| 3959 | * @param {string[]} chat The chat messages to scan, in reverse order. | 4056 | * @param {string[]} chat The chat messages to scan, in reverse order. |
| 3960 | * @param {number} maxContext The maximum context size of the generation. | 4057 | * @param {number} maxContext The maximum context size of the generation. |
| 3961 | * @param {boolean} isDryRun Whether to perform a dry run. | 4058 | * @param {boolean} isDryRun Whether to perform a dry run. |
| 4059 | * @param {WIGlobalScanData} globalScanData Chat independent context to be scanned | ||
| 3962 | * @typedef {object} WIActivated | 4060 | * @typedef {object} WIActivated |
| 3963 | * @property {string} worldInfoBefore The world info before the chat. | 4061 | * @property {string} worldInfoBefore The world info before the chat. |
| 3964 | * @property {string} worldInfoAfter The world info after the chat. | 4062 | * @property {string} worldInfoAfter The world info after the chat. |
| @@ -3969,9 +4067,9 @@ function parseDecorators(content) { | |||
| 3969 | * @property {Set<any>} allActivatedEntries All entries. | 4067 | * @property {Set<any>} allActivatedEntries All entries. |
| 3970 | * @returns {Promise<WIActivated>} The world info activated. | 4068 | * @returns {Promise<WIActivated>} The world info activated. |
| 3971 | */ | 4069 | */ |
| 3972 | export async function checkWorldInfo(chat, maxContext, isDryRun) { | 4070 | export async function checkWorldInfo(chat, maxContext, isDryRun, globalScanData) { |
| 3973 | const context = getContext(); | 4071 | const context = getContext(); |
| 3974 | const buffer = new WorldInfoBuffer(chat); | 4072 | const buffer = new WorldInfoBuffer(chat, globalScanData); |
| 3975 | 4073 | ||
| 3976 | console.debug(`[WI] --- START WI SCAN (on ${chat.length} messages)${isDryRun ? ' (DRY RUN)' : ''} ---`); | 4074 | console.debug(`[WI] --- START WI SCAN (on ${chat.length} messages)${isDryRun ? ' (DRY RUN)' : ''} ---`); |
| 3977 | 4075 | ||
| @@ -4829,6 +4927,12 @@ export function convertCharacterBook(characterBook) { | |||
| 4829 | sticky: entry.extensions?.sticky ?? null, | 4927 | sticky: entry.extensions?.sticky ?? null, |
| 4830 | cooldown: entry.extensions?.cooldown ?? null, | 4928 | cooldown: entry.extensions?.cooldown ?? null, |
| 4831 | delay: entry.extensions?.delay ?? null, | 4929 | delay: entry.extensions?.delay ?? null, |
| 4930 | matchPersonaDescription: entry.extensions?.match_persona_description ?? false, | ||
| 4931 | matchCharacterDescription: entry.extensions?.match_character_description ?? false, | ||
| 4932 | matchCharacterPersonality: entry.extensions?.match_character_personality ?? false, | ||
| 4933 | matchCharacterDepthPrompt: entry.extensions?.match_character_depth_prompt ?? false, | ||
| 4934 | matchScenario: entry.extensions?.match_scenario ?? false, | ||
| 4935 | matchCreatorNotes: entry.extensions?.match_creator_notes ?? false, | ||
| 4832 | extensions: entry.extensions ?? {}, | 4936 | extensions: entry.extensions ?? {}, |
| 4833 | }; | 4937 | }; |
| 4834 | }); | 4938 | }); |
| @@ -5099,7 +5203,7 @@ export function openWorldInfoEditor(worldName) { | |||
| 5099 | 5203 | ||
| 5100 | /** | 5204 | /** |
| 5101 | * Assigns a lorebook to the current chat. | 5205 | * Assigns a lorebook to the current chat. |
| 5102 | * @param {PointerEvent} event Pointer event | 5206 | * @param {JQuery.ClickEvent<Document, undefined, any, any>} event Pointer event |
| 5103 | * @returns {Promise<void>} | 5207 | * @returns {Promise<void>} |
| 5104 | */ | 5208 | */ |
| 5105 | export async function assignLorebookToChat(event) { | 5209 | export async function assignLorebookToChat(event) { |
| @@ -5138,11 +5242,106 @@ export async function assignLorebookToChat(event) { | |||
| 5138 | saveMetadata(); | 5242 | saveMetadata(); |
| 5139 | }); | 5243 | }); |
| 5140 | 5244 | ||
| 5141 | return callGenericPopup(template, POPUP_TYPE.TEXT); | 5245 | await callGenericPopup(template, POPUP_TYPE.TEXT); |
| 5246 | } | ||
| 5247 | |||
| 5248 | /** | ||
| 5249 | * Moves a World Info entry from a source lorebook to a target lorebook. | ||
| 5250 | * | ||
| 5251 | * @param {string} sourceName - The name of the source lorebook file. | ||
| 5252 | * @param {string} targetName - The name of the target lorebook file. | ||
| 5253 | * @param {string|number} uid - The UID of the entry to move from the source lorebook. | ||
| 5254 | * @returns {Promise<boolean>} True if the move was successful, false otherwise. | ||
| 5255 | */ | ||
| 5256 | export async function moveWorldInfoEntry(sourceName, targetName, uid) { | ||
| 5257 | if (sourceName === targetName) { | ||
| 5258 | return false; | ||
| 5259 | } | ||
| 5260 | |||
| 5261 | if (!world_names.includes(sourceName)) { | ||
| 5262 | toastr.error(t`Source lorebook '${sourceName}' not found.`); | ||
| 5263 | console.error(`[WI Move] Source lorebook '${sourceName}' does not exist.`); | ||
| 5264 | return false; | ||
| 5265 | } | ||
| 5266 | |||
| 5267 | if (!world_names.includes(targetName)) { | ||
| 5268 | toastr.error(t`Target lorebook '${targetName}' not found.`); | ||
| 5269 | console.error(`[WI Move] Target lorebook '${targetName}' does not exist.`); | ||
| 5270 | return false; | ||
| 5142 | } | 5271 | } |
| 5143 | 5272 | ||
| 5144 | jQuery(() => { | 5273 | const entryUidString = String(uid); |
| 5145 | 5274 | ||
| 5275 | try { | ||
| 5276 | const sourceData = await loadWorldInfo(sourceName); | ||
| 5277 | const targetData = await loadWorldInfo(targetName); | ||
| 5278 | |||
| 5279 | if (!sourceData || !sourceData.entries) { | ||
| 5280 | toastr.error(t`Failed to load data for source lorebook '${sourceName}'.`); | ||
| 5281 | console.error(`[WI Move] Could not load source data for '${sourceName}'.`); | ||
| 5282 | return false; | ||
| 5283 | } | ||
| 5284 | if (!targetData || !targetData.entries) { | ||
| 5285 | toastr.error(t`Failed to load data for target lorebook '${targetName}'.`); | ||
| 5286 | console.error(`[WI Move] Could not load target data for '${targetName}'.`); | ||
| 5287 | return false; | ||
| 5288 | } | ||
| 5289 | |||
| 5290 | if (!sourceData.entries[entryUidString]) { | ||
| 5291 | toastr.error(t`Entry not found in source lorebook '${sourceName}'.`); | ||
| 5292 | console.error(`[WI Move] Entry UID ${entryUidString} not found in '${sourceName}'.`); | ||
| 5293 | return false; | ||
| 5294 | } | ||
| 5295 | |||
| 5296 | const entryToMove = structuredClone(sourceData.entries[entryUidString]); | ||
| 5297 | |||
| 5298 | |||
| 5299 | const newUid = getFreeWorldEntryUid(targetData); | ||
| 5300 | if (newUid === null) { | ||
| 5301 | console.error(`[WI Move] Failed to get a free UID in '${targetName}'.`); | ||
| 5302 | return false; | ||
| 5303 | } | ||
| 5304 | |||
| 5305 | entryToMove.uid = newUid; | ||
| 5306 | // Place the entry at the end of the target lorebook | ||
| 5307 | const maxDisplayIndex = Object.values(targetData.entries).reduce((max, entry) => Math.max(max, entry.displayIndex ?? -1), -1); | ||
| 5308 | entryToMove.displayIndex = maxDisplayIndex + 1; | ||
| 5309 | |||
| 5310 | targetData.entries[newUid] = entryToMove; | ||
| 5311 | |||
| 5312 | delete sourceData.entries[entryUidString]; | ||
| 5313 | // Remove from originalData if it exists | ||
| 5314 | deleteWIOriginalDataValue(sourceData, entryUidString); | ||
| 5315 | // TODO: setWIOriginalDataValue | ||
| 5316 | console.debug(`[WI Move] Removed entry UID ${entryUidString} from source '${sourceName}'.`); | ||
| 5317 | |||
| 5318 | |||
| 5319 | await saveWorldInfo(targetName, targetData, true); | ||
| 5320 | console.debug(`[WI Move] Saved target lorebook '${targetName}'.`); | ||
| 5321 | await saveWorldInfo(sourceName, sourceData, true); | ||
| 5322 | console.debug(`[WI Move] Saved source lorebook '${sourceName}'.`); | ||
| 5323 | |||
| 5324 | |||
| 5325 | console.log(`[WI Move] ${entryToMove.comment} moved successfully to '${targetName}'.`); | ||
| 5326 | |||
| 5327 | // Check if the currently viewed book in the editor is the source or target and reload it | ||
| 5328 | const currentEditorBookIndex = Number($('#world_editor_select').val()); | ||
| 5329 | if (!isNaN(currentEditorBookIndex)) { | ||
| 5330 | const currentEditorBookName = world_names[currentEditorBookIndex]; | ||
| 5331 | if (currentEditorBookName === sourceName || currentEditorBookName === targetName) { | ||
| 5332 | reloadEditor(currentEditorBookName); | ||
| 5333 | } | ||
| 5334 | } | ||
| 5335 | |||
| 5336 | return true; | ||
| 5337 | } catch (error) { | ||
| 5338 | toastr.error(t`An unexpected error occurred while moving the entry: ${error.message}`); | ||
| 5339 | console.error('[WI Move] Unexpected error:', error); | ||
| 5340 | return false; | ||
| 5341 | } | ||
| 5342 | } | ||
| 5343 | |||
| 5344 | export function initWorldInfo() { | ||
| 5146 | $('#world_info').on('mousedown change', async function (e) { | 5345 | $('#world_info').on('mousedown change', async function (e) { |
| 5147 | // If there's no world names, don't do anything | 5346 | // If there's no world names, don't do anything |
| 5148 | if (world_names.length === 0) { | 5347 | if (world_names.length === 0) { |
| @@ -5329,7 +5528,7 @@ jQuery(() => { | |||
| 5329 | if (!isMobile()) { | 5528 | if (!isMobile()) { |
| 5330 | $('#world_info').select2({ | 5529 | $('#world_info').select2({ |
| 5331 | width: '100%', | 5530 | width: '100%', |
| 5332 | placeholder: 'No Worlds active. Click here to select.', | 5531 | placeholder: t`No Worlds active. Click here to select.`, |
| 5333 | allowClear: true, | 5532 | allowClear: true, |
| 5334 | closeOnSelect: false, | 5533 | closeOnSelect: false, |
| 5335 | }); | 5534 | }); |
| @@ -5354,100 +5553,4 @@ jQuery(() => { | |||
| 5354 | } | 5553 | } |
| 5355 | }); | 5554 | }); |
| 5356 | }); | 5555 | }); |
| 5357 | }); | ||
| 5358 | |||
| 5359 | /** | ||
| 5360 | * Moves a World Info entry from a source lorebook to a target lorebook. | ||
| 5361 | * | ||
| 5362 | * @param {string} sourceName - The name of the source lorebook file. | ||
| 5363 | * @param {string} targetName - The name of the target lorebook file. | ||
| 5364 | * @param {string|number} uid - The UID of the entry to move from the source lorebook. | ||
| 5365 | * @returns {Promise<boolean>} True if the move was successful, false otherwise. | ||
| 5366 | */ | ||
| 5367 | export async function moveWorldInfoEntry(sourceName, targetName, uid) { | ||
| 5368 | if (sourceName === targetName) { | ||
| 5369 | return false; | ||
| 5370 | } | ||
| 5371 | |||
| 5372 | if (!world_names.includes(sourceName)) { | ||
| 5373 | toastr.error(t`Source lorebook '${sourceName}' not found.`); | ||
| 5374 | console.error(`[WI Move] Source lorebook '${sourceName}' does not exist.`); | ||
| 5375 | return false; | ||
| 5376 | } | ||
| 5377 | |||
| 5378 | if (!world_names.includes(targetName)) { | ||
| 5379 | toastr.error(t`Target lorebook '${targetName}' not found.`); | ||
| 5380 | console.error(`[WI Move] Target lorebook '${targetName}' does not exist.`); | ||
| 5381 | return false; | ||
| 5382 | } | ||
| 5383 | |||
| 5384 | const entryUidString = String(uid); | ||
| 5385 | |||
| 5386 | try { | ||
| 5387 | const sourceData = await loadWorldInfo(sourceName); | ||
| 5388 | const targetData = await loadWorldInfo(targetName); | ||
| 5389 | |||
| 5390 | if (!sourceData || !sourceData.entries) { | ||
| 5391 | toastr.error(t`Failed to load data for source lorebook '${sourceName}'.`); | ||
| 5392 | console.error(`[WI Move] Could not load source data for '${sourceName}'.`); | ||
| 5393 | return false; | ||
| 5394 | } | ||
| 5395 | if (!targetData || !targetData.entries) { | ||
| 5396 | toastr.error(t`Failed to load data for target lorebook '${targetName}'.`); | ||
| 5397 | console.error(`[WI Move] Could not load target data for '${targetName}'.`); | ||
| 5398 | return false; | ||
| 5399 | } | ||
| 5400 | |||
| 5401 | if (!sourceData.entries[entryUidString]) { | ||
| 5402 | toastr.error(t`Entry not found in source lorebook '${sourceName}'.`); | ||
| 5403 | console.error(`[WI Move] Entry UID ${entryUidString} not found in '${sourceName}'.`); | ||
| 5404 | return false; | ||
| 5405 | } | ||
| 5406 | |||
| 5407 | const entryToMove = structuredClone(sourceData.entries[entryUidString]); | ||
| 5408 | |||
| 5409 | |||
| 5410 | const newUid = getFreeWorldEntryUid(targetData); | ||
| 5411 | if (newUid === null) { | ||
| 5412 | console.error(`[WI Move] Failed to get a free UID in '${targetName}'.`); | ||
| 5413 | return false; | ||
| 5414 | } | ||
| 5415 | |||
| 5416 | entryToMove.uid = newUid; | ||
| 5417 | // Place the entry at the end of the target lorebook | ||
| 5418 | const maxDisplayIndex = Object.values(targetData.entries).reduce((max, entry) => Math.max(max, entry.displayIndex ?? -1), -1); | ||
| 5419 | entryToMove.displayIndex = maxDisplayIndex + 1; | ||
| 5420 | |||
| 5421 | targetData.entries[newUid] = entryToMove; | ||
| 5422 | |||
| 5423 | delete sourceData.entries[entryUidString]; | ||
| 5424 | // Remove from originalData if it exists | ||
| 5425 | deleteWIOriginalDataValue(sourceData, entryUidString); | ||
| 5426 | // TODO: setWIOriginalDataValue | ||
| 5427 | console.debug(`[WI Move] Removed entry UID ${entryUidString} from source '${sourceName}'.`); | ||
| 5428 | |||
| 5429 | |||
| 5430 | await saveWorldInfo(targetName, targetData, true); | ||
| 5431 | console.debug(`[WI Move] Saved target lorebook '${targetName}'.`); | ||
| 5432 | await saveWorldInfo(sourceName, sourceData, true); | ||
| 5433 | console.debug(`[WI Move] Saved source lorebook '${sourceName}'.`); | ||
| 5434 | |||
| 5435 | |||
| 5436 | console.log(`[WI Move] ${entryToMove.comment} moved successfully to '${targetName}'.`); | ||
| 5437 | |||
| 5438 | // Check if the currently viewed book in the editor is the source or target and reload it | ||
| 5439 | const currentEditorBookIndex = Number($('#world_editor_select').val()); | ||
| 5440 | if (!isNaN(currentEditorBookIndex)) { | ||
| 5441 | const currentEditorBookName = world_names[currentEditorBookIndex]; | ||
| 5442 | if (currentEditorBookName === sourceName || currentEditorBookName === targetName) { | ||
| 5443 | reloadEditor(currentEditorBookName); | ||
| 5444 | } | ||
| 5445 | } | ||
| 5446 | |||
| 5447 | return true; | ||
| 5448 | } catch (error) { | ||
| 5449 | toastr.error(t`An unexpected error occurred while moving the entry: ${error.message}`); | ||
| 5450 | console.error('[WI Move] Unexpected error:', error); | ||
| 5451 | return false; | ||
| 5452 | } | ||
| 5453 | } | 5556 | } |
| @@ -157,11 +157,26 @@ body { | |||
| 157 | } | 157 | } |
| 158 | 158 | ||
| 159 | ::-webkit-scrollbar { | 159 | ::-webkit-scrollbar { |
| 160 | width: 10px; | 160 | width: 0.7rem; |
| 161 | height: 10px; | ||
| 162 | scrollbar-gutter: stable; | 161 | scrollbar-gutter: stable; |
| 163 | } | 162 | } |
| 164 | 163 | ||
| 164 | ::-webkit-scrollbar-track { | ||
| 165 | cursor: default; | ||
| 166 | } | ||
| 167 | |||
| 168 | ::-webkit-scrollbar-track:hover { | ||
| 169 | background-color: rgba(126, 126, 126, 0.2); /* Adaptive, but won't contrast with neutral-gray. */ | ||
| 170 | } | ||
| 171 | |||
| 172 | ::-webkit-scrollbar-thumb { | ||
| 173 | cursor: grab; | ||
| 174 | } | ||
| 175 | |||
| 176 | ::-webkit-scrollbar-thumb:active { | ||
| 177 | cursor: grabbing; | ||
| 178 | } | ||
| 179 | |||
| 165 | .scrollY { | 180 | .scrollY { |
| 166 | overflow-y: auto !important; | 181 | overflow-y: auto !important; |
| 167 | } | 182 | } |
| @@ -5091,7 +5106,7 @@ body:not(.sd) .mes_img_swipes { | |||
| 5091 | display: flex; | 5106 | display: flex; |
| 5092 | flex-direction: column; | 5107 | flex-direction: column; |
| 5093 | justify-content: center; | 5108 | justify-content: center; |
| 5094 | padding: 10px; | 5109 | padding: 0px; |
| 5095 | height: 100%; | 5110 | height: 100%; |
| 5096 | width: 100%; | 5111 | width: 100%; |
| 5097 | } | 5112 | } |
| @@ -5700,6 +5715,7 @@ body:not(.movingUI) .drawer-content.maximized { | |||
| 5700 | width: unset; | 5715 | width: unset; |
| 5701 | margin: 0; | 5716 | margin: 0; |
| 5702 | font-size: calc(var(--mainFontSize) * 0.85); | 5717 | font-size: calc(var(--mainFontSize) * 0.85); |
| 5718 | padding-right: 20px; | ||
| 5703 | } | 5719 | } |
| 5704 | 5720 | ||
| 5705 | .paginationjs-pages ul li a { | 5721 | .paginationjs-pages ul li a { |
| @@ -1,393 +1,15 @@ | |||
| 1 | #!/usr/bin/env node | 1 | #!/usr/bin/env node |
| 2 | |||
| 3 | // native node modules | ||
| 4 | import path from 'node:path'; | ||
| 5 | import util from 'node:util'; | ||
| 6 | import net from 'node:net'; | ||
| 7 | import dns from 'node:dns'; | ||
| 8 | import process from 'node:process'; | ||
| 9 | import { fileURLToPath } from 'node:url'; | ||
| 10 | |||
| 11 | import cors from 'cors'; | ||
| 12 | import { csrfSync } from 'csrf-sync'; | ||
| 13 | import express from 'express'; | ||
| 14 | import compression from 'compression'; | ||
| 15 | import cookieSession from 'cookie-session'; | ||
| 16 | import multer from 'multer'; | ||
| 17 | import responseTime from 'response-time'; | ||
| 18 | import helmet from 'helmet'; | ||
| 19 | import bodyParser from 'body-parser'; | ||
| 20 | import open from 'open'; | ||
| 21 | |||
| 22 | // local library imports | ||
| 23 | import './src/fetch-patch.js'; | ||
| 24 | import { serverEvents, EVENT_NAMES } from './src/server-events.js'; | ||
| 25 | import { CommandLineParser } from './src/command-line.js'; | 2 | import { CommandLineParser } from './src/command-line.js'; |
| 26 | import { loadPlugins } from './src/plugin-loader.js'; | 3 | import { serverDirectory } from './src/server-directory.js'; |
| 27 | import { | ||
| 28 | initUserStorage, | ||
| 29 | getCookieSecret, | ||
| 30 | getCookieSessionName, | ||
| 31 | ensurePublicDirectoriesExist, | ||
| 32 | getUserDirectoriesList, | ||
| 33 | migrateSystemPrompts, | ||
| 34 | migrateUserData, | ||
| 35 | requireLoginMiddleware, | ||
| 36 | setUserDataMiddleware, | ||
| 37 | shouldRedirectToLogin, | ||
| 38 | cleanUploads, | ||
| 39 | getSessionCookieAge, | ||
| 40 | verifySecuritySettings, | ||
| 41 | loginPageMiddleware, | ||
| 42 | } from './src/users.js'; | ||
| 43 | |||
| 44 | import getWebpackServeMiddleware from './src/middleware/webpack-serve.js'; | ||
| 45 | import basicAuthMiddleware from './src/middleware/basicAuth.js'; | ||
| 46 | import getWhitelistMiddleware from './src/middleware/whitelist.js'; | ||
| 47 | import accessLoggerMiddleware, { getAccessLogPath, migrateAccessLog } from './src/middleware/accessLogWriter.js'; | ||
| 48 | import multerMonkeyPatch from './src/middleware/multerMonkeyPatch.js'; | ||
| 49 | import initRequestProxy from './src/request-proxy.js'; | ||
| 50 | import getCacheBusterMiddleware from './src/middleware/cacheBuster.js'; | ||
| 51 | import corsProxyMiddleware from './src/middleware/corsProxy.js'; | ||
| 52 | import { | ||
| 53 | getVersion, | ||
| 54 | color, | ||
| 55 | removeColorFormatting, | ||
| 56 | getSeparator, | ||
| 57 | safeReadFileSync, | ||
| 58 | setupLogLevel, | ||
| 59 | setWindowTitle, | ||
| 60 | } from './src/util.js'; | ||
| 61 | import { UPLOADS_DIRECTORY } from './src/constants.js'; | ||
| 62 | import { ensureThumbnailCache } from './src/endpoints/thumbnails.js'; | ||
| 63 | |||
| 64 | // Routers | ||
| 65 | import { router as usersPublicRouter } from './src/endpoints/users-public.js'; | ||
| 66 | import { init as statsInit, onExit as statsOnExit } from './src/endpoints/stats.js'; | ||
| 67 | import { checkForNewContent } from './src/endpoints/content-manager.js'; | ||
| 68 | import { init as settingsInit } from './src/endpoints/settings.js'; | ||
| 69 | import { redirectDeprecatedEndpoints, ServerStartup, setupPrivateEndpoints } from './src/server-startup.js'; | ||
| 70 | import { diskCache } from './src/endpoints/characters.js'; | ||
| 71 | |||
| 72 | // Unrestrict console logs display limit | ||
| 73 | util.inspect.defaultOptions.maxArrayLength = null; | ||
| 74 | util.inspect.defaultOptions.maxStringLength = null; | ||
| 75 | util.inspect.defaultOptions.depth = 4; | ||
| 76 | |||
| 77 | // Set a working directory for the server | ||
| 78 | const serverDirectory = import.meta.dirname ?? path.dirname(fileURLToPath(import.meta.url)); | ||
| 79 | console.log(`Node version: ${process.version}. Running in ${process.env.NODE_ENV} environment. Server directory: ${serverDirectory}`); | ||
| 80 | process.chdir(serverDirectory); | ||
| 81 | |||
| 82 | // Work around a node v20.0.0, v20.1.0, and v20.2.0 bug. The issue was fixed in v20.3.0. | ||
| 83 | // https://github.com/nodejs/node/issues/47822#issuecomment-1564708870 | ||
| 84 | // Safe to remove once support for Node v20 is dropped. | ||
| 85 | if (process.versions && process.versions.node && process.versions.node.match(/20\.[0-2]\.0/)) { | ||
| 86 | // @ts-ignore | ||
| 87 | if (net.setDefaultAutoSelectFamily) net.setDefaultAutoSelectFamily(false); | ||
| 88 | } | ||
| 89 | 4 | ||
| 5 | // config.yaml will be set when parsing command line arguments | ||
| 90 | const cliArgs = new CommandLineParser().parse(process.argv); | 6 | const cliArgs = new CommandLineParser().parse(process.argv); |
| 91 | globalThis.DATA_ROOT = cliArgs.dataRoot; | 7 | globalThis.DATA_ROOT = cliArgs.dataRoot; |
| 92 | globalThis.COMMAND_LINE_ARGS = cliArgs; | 8 | globalThis.COMMAND_LINE_ARGS = cliArgs; |
| 9 | process.chdir(serverDirectory); | ||
| 93 | 10 | ||
| 94 | if (!cliArgs.enableIPv6 && !cliArgs.enableIPv4) { | ||
| 95 | console.error('error: You can\'t disable all internet protocols: at least IPv6 or IPv4 must be enabled.'); | ||
| 96 | process.exit(1); | ||
| 97 | } | ||
| 98 | |||
| 99 | try { | ||
| 100 | if (cliArgs.dnsPreferIPv6) { | ||
| 101 | dns.setDefaultResultOrder('ipv6first'); | ||
| 102 | console.log('Preferring IPv6 for DNS resolution'); | ||
| 103 | } else { | ||
| 104 | dns.setDefaultResultOrder('ipv4first'); | ||
| 105 | console.log('Preferring IPv4 for DNS resolution'); | ||
| 106 | } | ||
| 107 | } catch (error) { | ||
| 108 | console.warn('Failed to set DNS resolution order. Possibly unsupported in this Node version.'); | ||
| 109 | } | ||
| 110 | |||
| 111 | const app = express(); | ||
| 112 | app.use(helmet({ | ||
| 113 | contentSecurityPolicy: false, | ||
| 114 | })); | ||
| 115 | app.use(compression()); | ||
| 116 | app.use(responseTime()); | ||
| 117 | |||
| 118 | app.use(bodyParser.json({ limit: '200mb' })); | ||
| 119 | app.use(bodyParser.urlencoded({ extended: true, limit: '200mb' })); | ||
| 120 | |||
| 121 | // CORS Settings // | ||
| 122 | const CORS = cors({ | ||
| 123 | origin: 'null', | ||
| 124 | methods: ['OPTIONS'], | ||
| 125 | }); | ||
| 126 | |||
| 127 | app.use(CORS); | ||
| 128 | |||
| 129 | if (cliArgs.listen && cliArgs.basicAuthMode) { | ||
| 130 | app.use(basicAuthMiddleware); | ||
| 131 | } | ||
| 132 | |||
| 133 | if (cliArgs.whitelistMode) { | ||
| 134 | const whitelistMiddleware = await getWhitelistMiddleware(); | ||
| 135 | app.use(whitelistMiddleware); | ||
| 136 | } | ||
| 137 | |||
| 138 | if (cliArgs.listen) { | ||
| 139 | app.use(accessLoggerMiddleware()); | ||
| 140 | } | ||
| 141 | |||
| 142 | if (cliArgs.enableCorsProxy) { | ||
| 143 | app.use('/proxy/:url(*)', corsProxyMiddleware); | ||
| 144 | } else { | ||
| 145 | app.use('/proxy/:url(*)', async (_, res) => { | ||
| 146 | const message = 'CORS proxy is disabled. Enable it in config.yaml or use the --corsProxy flag.'; | ||
| 147 | console.log(message); | ||
| 148 | res.status(404).send(message); | ||
| 149 | }); | ||
| 150 | } | ||
| 151 | |||
| 152 | app.use(cookieSession({ | ||
| 153 | name: getCookieSessionName(), | ||
| 154 | sameSite: 'lax', | ||
| 155 | httpOnly: true, | ||
| 156 | maxAge: getSessionCookieAge(), | ||
| 157 | secret: getCookieSecret(globalThis.DATA_ROOT), | ||
| 158 | })); | ||
| 159 | |||
| 160 | app.use(setUserDataMiddleware); | ||
| 161 | |||
| 162 | // CSRF Protection // | ||
| 163 | if (!cliArgs.disableCsrf) { | ||
| 164 | const csrfSyncProtection = csrfSync({ | ||
| 165 | getTokenFromState: (req) => { | ||
| 166 | if (!req.session) { | ||
| 167 | console.error('(CSRF error) getTokenFromState: Session object not initialized'); | ||
| 168 | return; | ||
| 169 | } | ||
| 170 | return req.session.csrfToken; | ||
| 171 | }, | ||
| 172 | getTokenFromRequest: (req) => { | ||
| 173 | return req.headers['x-csrf-token']?.toString(); | ||
| 174 | }, | ||
| 175 | storeTokenInState: (req, token) => { | ||
| 176 | if (!req.session) { | ||
| 177 | console.error('(CSRF error) storeTokenInState: Session object not initialized'); | ||
| 178 | return; | ||
| 179 | } | ||
| 180 | req.session.csrfToken = token; | ||
| 181 | }, | ||
| 182 | size: 32, | ||
| 183 | }); | ||
| 184 | |||
| 185 | app.get('/csrf-token', (req, res) => { | ||
| 186 | res.json({ | ||
| 187 | 'token': csrfSyncProtection.generateToken(req), | ||
| 188 | }); | ||
| 189 | }); | ||
| 190 | |||
| 191 | // Customize the error message | ||
| 192 | csrfSyncProtection.invalidCsrfTokenError.message = color.red('Invalid CSRF token. Please refresh the page and try again.'); | ||
| 193 | csrfSyncProtection.invalidCsrfTokenError.stack = undefined; | ||
| 194 | |||
| 195 | app.use(csrfSyncProtection.csrfSynchronisedProtection); | ||
| 196 | } else { | ||
| 197 | console.warn('\nCSRF protection is disabled. This will make your server vulnerable to CSRF attacks.\n'); | ||
| 198 | app.get('/csrf-token', (req, res) => { | ||
| 199 | res.json({ | ||
| 200 | 'token': 'disabled', | ||
| 201 | }); | ||
| 202 | }); | ||
| 203 | } | ||
| 204 | |||
| 205 | // Static files | ||
| 206 | // Host index page | ||
| 207 | app.get('/', getCacheBusterMiddleware(), (request, response) => { | ||
| 208 | if (shouldRedirectToLogin(request)) { | ||
| 209 | const query = request.url.split('?')[1]; | ||
| 210 | const redirectUrl = query ? `/login?${query}` : '/login'; | ||
| 211 | return response.redirect(redirectUrl); | ||
| 212 | } | ||
| 213 | |||
| 214 | return response.sendFile('index.html', { root: path.join(process.cwd(), 'public') }); | ||
| 215 | }); | ||
| 216 | |||
| 217 | // Callback endpoint for OAuth PKCE flows (e.g. OpenRouter) | ||
| 218 | app.get('/callback/:source?', (request, response) => { | ||
| 219 | const source = request.params.source; | ||
| 220 | const query = request.url.split('?')[1]; | ||
| 221 | const searchParams = new URLSearchParams(); | ||
| 222 | source && searchParams.set('source', source); | ||
| 223 | query && searchParams.set('query', query); | ||
| 224 | const path = `/?${searchParams.toString()}`; | ||
| 225 | return response.redirect(307, path); | ||
| 226 | }); | ||
| 227 | |||
| 228 | // Host login page | ||
| 229 | app.get('/login', loginPageMiddleware); | ||
| 230 | |||
| 231 | // Host frontend assets | ||
| 232 | const webpackMiddleware = getWebpackServeMiddleware(); | ||
| 233 | app.use(webpackMiddleware); | ||
| 234 | app.use(express.static(process.cwd() + '/public', {})); | ||
| 235 | |||
| 236 | // Public API | ||
| 237 | app.use('/api/users', usersPublicRouter); | ||
| 238 | |||
| 239 | // Everything below this line requires authentication | ||
| 240 | app.use(requireLoginMiddleware); | ||
| 241 | app.get('/api/ping', (request, response) => { | ||
| 242 | if (request.query.extend && request.session) { | ||
| 243 | request.session.touch = Date.now(); | ||
| 244 | } | ||
| 245 | |||
| 246 | response.sendStatus(204); | ||
| 247 | }); | ||
| 248 | |||
| 249 | // File uploads | ||
| 250 | const uploadsPath = path.join(cliArgs.dataRoot, UPLOADS_DIRECTORY); | ||
| 251 | app.use(multer({ dest: uploadsPath, limits: { fieldSize: 10 * 1024 * 1024 } }).single('avatar')); | ||
| 252 | app.use(multerMonkeyPatch); | ||
| 253 | |||
| 254 | app.get('/version', async function (_, response) { | ||
| 255 | const data = await getVersion(); | ||
| 256 | response.send(data); | ||
| 257 | }); | ||
| 258 | |||
| 259 | redirectDeprecatedEndpoints(app); | ||
| 260 | setupPrivateEndpoints(app); | ||
| 261 | |||
| 262 | /** | ||
| 263 | * Tasks that need to be run before the server starts listening. | ||
| 264 | * @returns {Promise<void>} | ||
| 265 | */ | ||
| 266 | async function preSetupTasks() { | ||
| 267 | const version = await getVersion(); | ||
| 268 | |||
| 269 | // Print formatted header | ||
| 270 | console.log(); | ||
| 271 | console.log(`SillyTavern ${version.pkgVersion}`); | ||
| 272 | if (version.gitBranch) { | ||
| 273 | console.log(`Running '${version.gitBranch}' (${version.gitRevision}) - ${version.commitDate}`); | ||
| 274 | if (!version.isLatest && ['staging', 'release'].includes(version.gitBranch)) { | ||
| 275 | console.log('INFO: Currently not on the latest commit.'); | ||
| 276 | console.log(' Run \'git pull\' to update. If you have any merge conflicts, run \'git reset --hard\' and \'git pull\' to reset your branch.'); | ||
| 277 | } | ||
| 278 | } | ||
| 279 | console.log(); | ||
| 280 | |||
| 281 | const directories = await getUserDirectoriesList(); | ||
| 282 | await checkForNewContent(directories); | ||
| 283 | await ensureThumbnailCache(directories); | ||
| 284 | await diskCache.verify(directories); | ||
| 285 | cleanUploads(); | ||
| 286 | migrateAccessLog(); | ||
| 287 | |||
| 288 | await settingsInit(); | ||
| 289 | await statsInit(); | ||
| 290 | |||
| 291 | const pluginsDirectory = path.join(serverDirectory, 'plugins'); | ||
| 292 | const cleanupPlugins = await loadPlugins(app, pluginsDirectory); | ||
| 293 | const consoleTitle = process.title; | ||
| 294 | |||
| 295 | let isExiting = false; | ||
| 296 | const exitProcess = async () => { | ||
| 297 | if (isExiting) return; | ||
| 298 | isExiting = true; | ||
| 299 | await statsOnExit(); | ||
| 300 | if (typeof cleanupPlugins === 'function') { | ||
| 301 | await cleanupPlugins(); | ||
| 302 | } | ||
| 303 | diskCache.dispose(); | ||
| 304 | setWindowTitle(consoleTitle); | ||
| 305 | process.exit(); | ||
| 306 | }; | ||
| 307 | |||
| 308 | // Set up event listeners for a graceful shutdown | ||
| 309 | process.on('SIGINT', exitProcess); | ||
| 310 | process.on('SIGTERM', exitProcess); | ||
| 311 | process.on('uncaughtException', (err) => { | ||
| 312 | console.error('Uncaught exception:', err); | ||
| 313 | exitProcess(); | ||
| 314 | }); | ||
| 315 | |||
| 316 | // Add request proxy. | ||
| 317 | initRequestProxy({ enabled: cliArgs.requestProxyEnabled, url: cliArgs.requestProxyUrl, bypass: cliArgs.requestProxyBypass }); | ||
| 318 | |||
| 319 | // Wait for frontend libs to compile | ||
| 320 | await webpackMiddleware.runWebpackCompiler(); | ||
| 321 | } | ||
| 322 | |||
| 323 | /** | ||
| 324 | * Tasks that need to be run after the server starts listening. | ||
| 325 | * @param {import('./src/server-startup.js').ServerStartupResult} result The result of the server startup | ||
| 326 | * @returns {Promise<void>} | ||
| 327 | */ | ||
| 328 | async function postSetupTasks(result) { | ||
| 329 | const autorunHostname = await cliArgs.getAutorunHostname(result); | ||
| 330 | const autorunUrl = cliArgs.getAutorunUrl(autorunHostname); | ||
| 331 | |||
| 332 | if (cliArgs.autorun) { | ||
| 333 | try { | 11 | try { |
| 334 | console.log('Launching in a browser...'); | 12 | await import('./src/server-main.js'); |
| 335 | await open(autorunUrl.toString()); | ||
| 336 | } catch (error) { | 13 | } catch (error) { |
| 337 | console.error('Failed to launch the browser. Open the URL manually.'); | 14 | console.error('A critical error has occurred while starting the server:', error); |
| 338 | } | ||
| 339 | } | ||
| 340 | |||
| 341 | setWindowTitle('SillyTavern WebServer'); | ||
| 342 | |||
| 343 | let logListen = 'SillyTavern is listening on'; | ||
| 344 | |||
| 345 | if (result.useIPv6 && !result.v6Failed) { | ||
| 346 | logListen += color.green( | ||
| 347 | ' IPv6: ' + cliArgs.getIPv6ListenUrl().host, | ||
| 348 | ); | ||
| 349 | } | 15 | } |
| 350 | |||
| 351 | if (result.useIPv4 && !result.v4Failed) { | ||
| 352 | logListen += color.green( | ||
| 353 | ' IPv4: ' + cliArgs.getIPv4ListenUrl().host, | ||
| 354 | ); | ||
| 355 | } | ||
| 356 | |||
| 357 | const goToLog = 'Go to: ' + color.blue(autorunUrl) + ' to open SillyTavern'; | ||
| 358 | const plainGoToLog = removeColorFormatting(goToLog); | ||
| 359 | |||
| 360 | console.log(logListen); | ||
| 361 | if (cliArgs.listen) { | ||
| 362 | console.log(); | ||
| 363 | console.log('To limit connections to internal localhost only ([::1] or 127.0.0.1), change the setting in config.yaml to "listen: false".'); | ||
| 364 | console.log('Check the "access.log" file in the data directory to inspect incoming connections:', color.green(getAccessLogPath())); | ||
| 365 | } | ||
| 366 | console.log('\n' + getSeparator(plainGoToLog.length) + '\n'); | ||
| 367 | console.log(goToLog); | ||
| 368 | console.log('\n' + getSeparator(plainGoToLog.length) + '\n'); | ||
| 369 | |||
| 370 | setupLogLevel(); | ||
| 371 | serverEvents.emit(EVENT_NAMES.SERVER_STARTED, { url: autorunUrl }); | ||
| 372 | } | ||
| 373 | |||
| 374 | /** | ||
| 375 | * Registers a not-found error response if a not-found error page exists. Should only be called after all other middlewares have been registered. | ||
| 376 | */ | ||
| 377 | function apply404Middleware() { | ||
| 378 | const notFoundWebpage = safeReadFileSync('./public/error/url-not-found.html') ?? ''; | ||
| 379 | app.use((req, res) => { | ||
| 380 | res.status(404).send(notFoundWebpage); | ||
| 381 | }); | ||
| 382 | } | ||
| 383 | |||
| 384 | // User storage module needs to be initialized before starting the server | ||
| 385 | initUserStorage(globalThis.DATA_ROOT) | ||
| 386 | .then(ensurePublicDirectoriesExist) | ||
| 387 | .then(migrateUserData) | ||
| 388 | .then(migrateSystemPrompts) | ||
| 389 | .then(verifySecuritySettings) | ||
| 390 | .then(preSetupTasks) | ||
| 391 | .then(apply404Middleware) | ||
| 392 | .then(() => new ServerStartup(app, cliArgs).start()) | ||
| 393 | .then(postSetupTasks); | ||
| @@ -2,9 +2,11 @@ import yargs from 'yargs/yargs'; | |||
| 2 | import { hideBin } from 'yargs/helpers'; | 2 | import { hideBin } from 'yargs/helpers'; |
| 3 | import ipRegex from 'ip-regex'; | 3 | import ipRegex from 'ip-regex'; |
| 4 | import { canResolve, color, getConfigValue, stringToBool } from './util.js'; | 4 | import { canResolve, color, getConfigValue, stringToBool } from './util.js'; |
| 5 | import { initConfig } from './config-init.js'; | ||
| 5 | 6 | ||
| 6 | /** | 7 | /** |
| 7 | * @typedef {object} CommandLineArguments Parsed command line arguments | 8 | * @typedef {object} CommandLineArguments Parsed command line arguments |
| 9 | * @property {string} configPath Path to the config file | ||
| 8 | * @property {string} dataRoot Data root directory | 10 | * @property {string} dataRoot Data root directory |
| 9 | * @property {number} port Port number | 11 | * @property {number} port Port number |
| 10 | * @property {boolean} listen If SillyTavern is listening on all network interfaces | 12 | * @property {boolean} listen If SillyTavern is listening on all network interfaces |
| @@ -40,6 +42,7 @@ export class CommandLineParser { | |||
| 40 | constructor() { | 42 | constructor() { |
| 41 | /** @type {CommandLineArguments} */ | 43 | /** @type {CommandLineArguments} */ |
| 42 | this.default = Object.freeze({ | 44 | this.default = Object.freeze({ |
| 45 | configPath: './config.yaml', | ||
| 43 | dataRoot: './data', | 46 | dataRoot: './data', |
| 44 | port: 8000, | 47 | port: 8000, |
| 45 | listen: false, | 48 | listen: false, |
| @@ -88,6 +91,11 @@ export class CommandLineParser { | |||
| 88 | parse(args) { | 91 | parse(args) { |
| 89 | const cliArguments = yargs(hideBin(args)) | 92 | const cliArguments = yargs(hideBin(args)) |
| 90 | .usage('Usage: <your-start-script> [options]\nOptions that are not provided will be filled with config values.') | 93 | .usage('Usage: <your-start-script> [options]\nOptions that are not provided will be filled with config values.') |
| 94 | .option('configPath', { | ||
| 95 | type: 'string', | ||
| 96 | default: null, | ||
| 97 | describe: 'Path to the config file', | ||
| 98 | }) | ||
| 91 | .option('enableIPv6', { | 99 | .option('enableIPv6', { |
| 92 | type: 'string', | 100 | type: 'string', |
| 93 | default: null, | 101 | default: null, |
| @@ -177,8 +185,11 @@ export class CommandLineParser { | |||
| 177 | describe: 'Request proxy bypass list (space separated list of hosts)', | 185 | describe: 'Request proxy bypass list (space separated list of hosts)', |
| 178 | }).parseSync(); | 186 | }).parseSync(); |
| 179 | 187 | ||
| 188 | const configPath = cliArguments.configPath ?? this.default.configPath; | ||
| 189 | initConfig(configPath); | ||
| 180 | /** @type {CommandLineArguments} */ | 190 | /** @type {CommandLineArguments} */ |
| 181 | const result = { | 191 | const result = { |
| 192 | configPath: configPath, | ||
| 182 | dataRoot: cliArguments.dataRoot ?? getConfigValue('dataRoot', this.default.dataRoot), | 193 | dataRoot: cliArguments.dataRoot ?? getConfigValue('dataRoot', this.default.dataRoot), |
| 183 | port: cliArguments.port ?? getConfigValue('port', this.default.port, 'number'), | 194 | port: cliArguments.port ?? getConfigValue('port', this.default.port, 'number'), |
| 184 | listen: cliArguments.listen ?? getConfigValue('listen', this.default.listen, 'boolean'), | 195 | listen: cliArguments.listen ?? getConfigValue('listen', this.default.listen, 'boolean'), |
| @@ -0,0 +1,197 @@ | |||
| 1 | import fs from 'node:fs'; | ||
| 2 | import path from 'node:path'; | ||
| 3 | import yaml from 'yaml'; | ||
| 4 | import color from 'chalk'; | ||
| 5 | import _ from 'lodash'; | ||
| 6 | import { serverDirectory } from './server-directory.js'; | ||
| 7 | import { setConfigFilePath } from './util.js'; | ||
| 8 | |||
| 9 | const keyMigrationMap = [ | ||
| 10 | { | ||
| 11 | oldKey: 'disableThumbnails', | ||
| 12 | newKey: 'thumbnails.enabled', | ||
| 13 | migrate: (value) => !value, | ||
| 14 | }, | ||
| 15 | { | ||
| 16 | oldKey: 'thumbnailsQuality', | ||
| 17 | newKey: 'thumbnails.quality', | ||
| 18 | migrate: (value) => value, | ||
| 19 | }, | ||
| 20 | { | ||
| 21 | oldKey: 'avatarThumbnailsPng', | ||
| 22 | newKey: 'thumbnails.format', | ||
| 23 | migrate: (value) => (value ? 'png' : 'jpg'), | ||
| 24 | }, | ||
| 25 | { | ||
| 26 | oldKey: 'disableChatBackup', | ||
| 27 | newKey: 'backups.chat.enabled', | ||
| 28 | migrate: (value) => !value, | ||
| 29 | }, | ||
| 30 | { | ||
| 31 | oldKey: 'numberOfBackups', | ||
| 32 | newKey: 'backups.common.numberOfBackups', | ||
| 33 | migrate: (value) => value, | ||
| 34 | }, | ||
| 35 | { | ||
| 36 | oldKey: 'maxTotalChatBackups', | ||
| 37 | newKey: 'backups.chat.maxTotalBackups', | ||
| 38 | migrate: (value) => value, | ||
| 39 | }, | ||
| 40 | { | ||
| 41 | oldKey: 'chatBackupThrottleInterval', | ||
| 42 | newKey: 'backups.chat.throttleInterval', | ||
| 43 | migrate: (value) => value, | ||
| 44 | }, | ||
| 45 | { | ||
| 46 | oldKey: 'enableExtensions', | ||
| 47 | newKey: 'extensions.enabled', | ||
| 48 | migrate: (value) => value, | ||
| 49 | }, | ||
| 50 | { | ||
| 51 | oldKey: 'enableExtensionsAutoUpdate', | ||
| 52 | newKey: 'extensions.autoUpdate', | ||
| 53 | migrate: (value) => value, | ||
| 54 | }, | ||
| 55 | { | ||
| 56 | oldKey: 'extras.disableAutoDownload', | ||
| 57 | newKey: 'extensions.models.autoDownload', | ||
| 58 | migrate: (value) => !value, | ||
| 59 | }, | ||
| 60 | { | ||
| 61 | oldKey: 'extras.classificationModel', | ||
| 62 | newKey: 'extensions.models.classification', | ||
| 63 | migrate: (value) => value, | ||
| 64 | }, | ||
| 65 | { | ||
| 66 | oldKey: 'extras.captioningModel', | ||
| 67 | newKey: 'extensions.models.captioning', | ||
| 68 | migrate: (value) => value, | ||
| 69 | }, | ||
| 70 | { | ||
| 71 | oldKey: 'extras.embeddingModel', | ||
| 72 | newKey: 'extensions.models.embedding', | ||
| 73 | migrate: (value) => value, | ||
| 74 | }, | ||
| 75 | { | ||
| 76 | oldKey: 'extras.speechToTextModel', | ||
| 77 | newKey: 'extensions.models.speechToText', | ||
| 78 | migrate: (value) => value, | ||
| 79 | }, | ||
| 80 | { | ||
| 81 | oldKey: 'extras.textToSpeechModel', | ||
| 82 | newKey: 'extensions.models.textToSpeech', | ||
| 83 | migrate: (value) => value, | ||
| 84 | }, | ||
| 85 | { | ||
| 86 | oldKey: 'minLogLevel', | ||
| 87 | newKey: 'logging.minLogLevel', | ||
| 88 | migrate: (value) => value, | ||
| 89 | }, | ||
| 90 | { | ||
| 91 | oldKey: 'cardsCacheCapacity', | ||
| 92 | newKey: 'performance.memoryCacheCapacity', | ||
| 93 | migrate: (value) => `${value}mb`, | ||
| 94 | }, | ||
| 95 | { | ||
| 96 | oldKey: 'cookieSecret', | ||
| 97 | newKey: 'cookieSecret', | ||
| 98 | migrate: () => void 0, | ||
| 99 | remove: true, | ||
| 100 | }, | ||
| 101 | ]; | ||
| 102 | |||
| 103 | /** | ||
| 104 | * Gets all keys from an object recursively. | ||
| 105 | * @param {object} obj Object to get all keys from | ||
| 106 | * @param {string} prefix Prefix to prepend to all keys | ||
| 107 | * @returns {string[]} Array of all keys in the object | ||
| 108 | */ | ||
| 109 | function getAllKeys(obj, prefix = '') { | ||
| 110 | if (typeof obj !== 'object' || Array.isArray(obj) || obj === null) { | ||
| 111 | return []; | ||
| 112 | } | ||
| 113 | |||
| 114 | return _.flatMap(Object.keys(obj), key => { | ||
| 115 | const newPrefix = prefix ? `${prefix}.${key}` : key; | ||
| 116 | if (typeof obj[key] === 'object' && !Array.isArray(obj[key])) { | ||
| 117 | return getAllKeys(obj[key], newPrefix); | ||
| 118 | } else { | ||
| 119 | return [newPrefix]; | ||
| 120 | } | ||
| 121 | }); | ||
| 122 | } | ||
| 123 | |||
| 124 | /** | ||
| 125 | * Compares the current config.yaml with the default config.yaml and adds any missing values. | ||
| 126 | * @param {string} configPath Path to config.yaml | ||
| 127 | */ | ||
| 128 | export function addMissingConfigValues(configPath) { | ||
| 129 | try { | ||
| 130 | const defaultConfig = yaml.parse(fs.readFileSync(path.join(serverDirectory, './default/config.yaml'), 'utf8')); | ||
| 131 | let config = yaml.parse(fs.readFileSync(configPath, 'utf8')); | ||
| 132 | |||
| 133 | // Migrate old keys to new keys | ||
| 134 | const migratedKeys = []; | ||
| 135 | for (const { oldKey, newKey, migrate, remove } of keyMigrationMap) { | ||
| 136 | if (_.has(config, oldKey)) { | ||
| 137 | if (remove) { | ||
| 138 | _.unset(config, oldKey); | ||
| 139 | migratedKeys.push({ | ||
| 140 | oldKey, | ||
| 141 | newValue: void 0, | ||
| 142 | }); | ||
| 143 | continue; | ||
| 144 | } | ||
| 145 | |||
| 146 | const oldValue = _.get(config, oldKey); | ||
| 147 | const newValue = migrate(oldValue); | ||
| 148 | _.set(config, newKey, newValue); | ||
| 149 | _.unset(config, oldKey); | ||
| 150 | |||
| 151 | migratedKeys.push({ | ||
| 152 | oldKey, | ||
| 153 | newKey, | ||
| 154 | oldValue, | ||
| 155 | newValue, | ||
| 156 | }); | ||
| 157 | } | ||
| 158 | } | ||
| 159 | |||
| 160 | // Get all keys from the original config | ||
| 161 | const originalKeys = getAllKeys(config); | ||
| 162 | |||
| 163 | // Use lodash's defaultsDeep function to recursively apply default properties | ||
| 164 | config = _.defaultsDeep(config, defaultConfig); | ||
| 165 | |||
| 166 | // Get all keys from the updated config | ||
| 167 | const updatedKeys = getAllKeys(config); | ||
| 168 | |||
| 169 | // Find the keys that were added | ||
| 170 | const addedKeys = _.difference(updatedKeys, originalKeys); | ||
| 171 | |||
| 172 | if (addedKeys.length === 0 && migratedKeys.length === 0) { | ||
| 173 | return; | ||
| 174 | } | ||
| 175 | |||
| 176 | if (addedKeys.length > 0) { | ||
| 177 | console.log('Adding missing config values to config.yaml:', addedKeys); | ||
| 178 | } | ||
| 179 | |||
| 180 | if (migratedKeys.length > 0) { | ||
| 181 | console.log('Migrating config values in config.yaml:', migratedKeys); | ||
| 182 | } | ||
| 183 | |||
| 184 | fs.writeFileSync(configPath, yaml.stringify(config)); | ||
| 185 | } catch (error) { | ||
| 186 | console.error(color.red('FATAL: Could not add missing config values to config.yaml'), error); | ||
| 187 | } | ||
| 188 | } | ||
| 189 | |||
| 190 | /** | ||
| 191 | * Performs early initialization tasks before the server starts. | ||
| 192 | * @param {string} configPath Path to config.yaml | ||
| 193 | */ | ||
| 194 | export function initConfig(configPath) { | ||
| 195 | setConfigFilePath(configPath); | ||
| 196 | addMissingConfigValues(configPath); | ||
| 197 | } | ||
| @@ -156,7 +156,7 @@ export const GEMINI_SAFETY = [ | |||
| 156 | }, | 156 | }, |
| 157 | { | 157 | { |
| 158 | category: 'HARM_CATEGORY_CIVIC_INTEGRITY', | 158 | category: 'HARM_CATEGORY_CIVIC_INTEGRITY', |
| 159 | threshold: 'BLOCK_NONE', | 159 | threshold: 'OFF', |
| 160 | }, | 160 | }, |
| 161 | ]; | 161 | ]; |
| 162 | 162 | ||
| @@ -176,6 +176,7 @@ export const CHAT_COMPLETION_SOURCES = { | |||
| 176 | ZEROONEAI: '01ai', | 176 | ZEROONEAI: '01ai', |
| 177 | NANOGPT: 'nanogpt', | 177 | NANOGPT: 'nanogpt', |
| 178 | DEEPSEEK: 'deepseek', | 178 | DEEPSEEK: 'deepseek', |
| 179 | XAI: 'xai', | ||
| 179 | }; | 180 | }; |
| 180 | 181 | ||
| 181 | /** | 182 | /** |
| @@ -267,23 +268,6 @@ export const FEATHERLESS_KEYS = [ | |||
| 267 | 'guided_whitespace_pattern', | 268 | 'guided_whitespace_pattern', |
| 268 | ]; | 269 | ]; |
| 269 | 270 | ||
| 270 | // https://dreamgen.com/docs/api#openai-text | ||
| 271 | export const DREAMGEN_KEYS = [ | ||
| 272 | 'model', | ||
| 273 | 'prompt', | ||
| 274 | 'max_tokens', | ||
| 275 | 'temperature', | ||
| 276 | 'top_p', | ||
| 277 | 'top_k', | ||
| 278 | 'min_p', | ||
| 279 | 'repetition_penalty', | ||
| 280 | 'frequency_penalty', | ||
| 281 | 'presence_penalty', | ||
| 282 | 'stop', | ||
| 283 | 'stream', | ||
| 284 | 'minimum_message_content_tokens', | ||
| 285 | ]; | ||
| 286 | |||
| 287 | // https://docs.together.ai/reference/completions | 271 | // https://docs.together.ai/reference/completions |
| 288 | export const TOGETHERAI_KEYS = [ | 272 | export const TOGETHERAI_KEYS = [ |
| 289 | 'model', | 273 | 'model', |
| @@ -300,7 +284,7 @@ export const TOGETHERAI_KEYS = [ | |||
| 300 | 'stop', | 284 | 'stop', |
| 301 | ]; | 285 | ]; |
| 302 | 286 | ||
| 303 | // https://github.com/jmorganca/ollama/blob/main/docs/api.md#request-with-options | 287 | // https://github.com/ollama/ollama/blob/main/docs/api.md#request-with-options |
| 304 | export const OLLAMA_KEYS = [ | 288 | export const OLLAMA_KEYS = [ |
| 305 | 'num_predict', | 289 | 'num_predict', |
| 306 | 'num_ctx', | 290 | 'num_ctx', |
| @@ -235,14 +235,14 @@ router.post('/download', async (request, response) => { | |||
| 235 | const contentType = mime.lookup(temp_path) || 'application/octet-stream'; | 235 | const contentType = mime.lookup(temp_path) || 'application/octet-stream'; |
| 236 | response.setHeader('Content-Type', contentType); | 236 | response.setHeader('Content-Type', contentType); |
| 237 | response.send(fileContent); | 237 | response.send(fileContent); |
| 238 | fs.rmSync(temp_path); | 238 | fs.unlinkSync(temp_path); |
| 239 | return; | 239 | return; |
| 240 | } | 240 | } |
| 241 | 241 | ||
| 242 | // Move into asset place | 242 | // Move into asset place |
| 243 | console.info('Download finished, moving file from', temp_path, 'to', file_path); | 243 | console.info('Download finished, moving file from', temp_path, 'to', file_path); |
| 244 | fs.copyFileSync(temp_path, file_path); | 244 | fs.copyFileSync(temp_path, file_path); |
| 245 | fs.rmSync(temp_path); | 245 | fs.unlinkSync(temp_path); |
| 246 | response.sendStatus(200); | 246 | response.sendStatus(200); |
| 247 | } | 247 | } |
| 248 | catch (error) { | 248 | catch (error) { |
| @@ -28,7 +28,7 @@ router.post('/delete', getFileNameValidationFunction('avatar'), function (reques | |||
| 28 | const fileName = path.join(request.user.directories.avatars, sanitize(request.body.avatar)); | 28 | const fileName = path.join(request.user.directories.avatars, sanitize(request.body.avatar)); |
| 29 | 29 | ||
| 30 | if (fs.existsSync(fileName)) { | 30 | if (fs.existsSync(fileName)) { |
| 31 | fs.rmSync(fileName); | 31 | fs.unlinkSync(fileName); |
| 32 | return response.send({ result: 'ok' }); | 32 | return response.send({ result: 'ok' }); |
| 33 | } | 33 | } |
| 34 | 34 | ||
| @@ -53,7 +53,7 @@ router.post('/upload', async (request, response) => { | |||
| 53 | const filename = request.body.overwrite_name || `${Date.now()}.png`; | 53 | const filename = request.body.overwrite_name || `${Date.now()}.png`; |
| 54 | const pathToNewFile = path.join(request.user.directories.avatars, filename); | 54 | const pathToNewFile = path.join(request.user.directories.avatars, filename); |
| 55 | writeFileAtomicSync(pathToNewFile, image); | 55 | writeFileAtomicSync(pathToNewFile, image); |
| 56 | fs.rmSync(pathToUpload); | 56 | fs.unlinkSync(pathToUpload); |
| 57 | return response.send({ path: filename }); | 57 | return response.send({ path: filename }); |
| 58 | } catch (err) { | 58 | } catch (err) { |
| 59 | return response.status(400).send('Is not a valid image'); | 59 | return response.status(400).send('Is not a valid image'); |
| @@ -1,4 +1,5 @@ | |||
| 1 | import process from 'node:process'; | 1 | import process from 'node:process'; |
| 2 | import util from 'node:util'; | ||
| 2 | import express from 'express'; | 3 | import express from 'express'; |
| 3 | import fetch from 'node-fetch'; | 4 | import fetch from 'node-fetch'; |
| 4 | 5 | ||
| @@ -23,11 +24,13 @@ import { | |||
| 23 | convertCohereMessages, | 24 | convertCohereMessages, |
| 24 | convertMistralMessages, | 25 | convertMistralMessages, |
| 25 | convertAI21Messages, | 26 | convertAI21Messages, |
| 27 | convertXAIMessages, | ||
| 26 | mergeMessages, | 28 | mergeMessages, |
| 27 | cachingAtDepthForOpenRouterClaude, | 29 | cachingAtDepthForOpenRouterClaude, |
| 28 | cachingAtDepthForClaude, | 30 | cachingAtDepthForClaude, |
| 29 | getPromptNames, | 31 | getPromptNames, |
| 30 | calculateBudgetTokens, | 32 | calculateClaudeBudgetTokens, |
| 33 | calculateGoogleBudgetTokens, | ||
| 31 | } from '../../prompt-converters.js'; | 34 | } from '../../prompt-converters.js'; |
| 32 | 35 | ||
| 33 | import { readSecret, SECRET_KEYS } from '../secrets.js'; | 36 | import { readSecret, SECRET_KEYS } from '../secrets.js'; |
| @@ -53,6 +56,7 @@ const API_01AI = 'https://api.lingyiwanwu.com/v1'; | |||
| 53 | const API_AI21 = 'https://api.ai21.com/studio/v1'; | 56 | const API_AI21 = 'https://api.ai21.com/studio/v1'; |
| 54 | const API_NANOGPT = 'https://nano-gpt.com/api/v1'; | 57 | const API_NANOGPT = 'https://nano-gpt.com/api/v1'; |
| 55 | const API_DEEPSEEK = 'https://api.deepseek.com/beta'; | 58 | const API_DEEPSEEK = 'https://api.deepseek.com/beta'; |
| 59 | const API_XAI = 'https://api.x.ai/v1'; | ||
| 56 | 60 | ||
| 57 | /** | 61 | /** |
| 58 | * Applies a post-processing step to the generated messages. | 62 | * Applies a post-processing step to the generated messages. |
| @@ -200,7 +204,7 @@ async function sendClaudeRequest(request, response) { | |||
| 200 | // No prefill when thinking | 204 | // No prefill when thinking |
| 201 | voidPrefill = true; | 205 | voidPrefill = true; |
| 202 | const reasoningEffort = request.body.reasoning_effort; | 206 | const reasoningEffort = request.body.reasoning_effort; |
| 203 | const budgetTokens = calculateBudgetTokens(requestBody.max_tokens, reasoningEffort, requestBody.stream); | 207 | const budgetTokens = calculateClaudeBudgetTokens(requestBody.max_tokens, reasoningEffort, requestBody.stream); |
| 204 | const minThinkTokens = 1024; | 208 | const minThinkTokens = 1024; |
| 205 | if (requestBody.max_tokens <= minThinkTokens) { | 209 | if (requestBody.max_tokens <= minThinkTokens) { |
| 206 | const newValue = requestBody.max_tokens + minThinkTokens; | 210 | const newValue = requestBody.max_tokens + minThinkTokens; |
| @@ -248,7 +252,7 @@ async function sendClaudeRequest(request, response) { | |||
| 248 | if (!generateResponse.ok) { | 252 | if (!generateResponse.ok) { |
| 249 | const generateResponseText = await generateResponse.text(); | 253 | const generateResponseText = await generateResponse.text(); |
| 250 | console.warn(color.red(`Claude API returned error: ${generateResponse.status} ${generateResponse.statusText}\n${generateResponseText}\n${divider}`)); | 254 | console.warn(color.red(`Claude API returned error: ${generateResponse.status} ${generateResponse.statusText}\n${generateResponseText}\n${divider}`)); |
| 251 | return response.status(generateResponse.status).send({ error: true }); | 255 | return response.status(500).send({ error: true }); |
| 252 | } | 256 | } |
| 253 | 257 | ||
| 254 | /** @type {any} */ | 258 | /** @type {any} */ |
| @@ -338,8 +342,9 @@ async function sendMakerSuiteRequest(request, response) { | |||
| 338 | const stream = Boolean(request.body.stream); | 342 | const stream = Boolean(request.body.stream); |
| 339 | const enableWebSearch = Boolean(request.body.enable_web_search); | 343 | const enableWebSearch = Boolean(request.body.enable_web_search); |
| 340 | const requestImages = Boolean(request.body.request_images); | 344 | const requestImages = Boolean(request.body.request_images); |
| 341 | const isThinking = model.includes('thinking'); | 345 | const reasoningEffort = String(request.body.reasoning_effort); |
| 342 | const isGemma = model.includes('gemma'); | 346 | const isGemma = model.includes('gemma'); |
| 347 | const isLearnLM = model.includes('learnlm'); | ||
| 343 | 348 | ||
| 344 | const generationConfig = { | 349 | const generationConfig = { |
| 345 | stopSequences: request.body.stop, | 350 | stopSequences: request.body.stop, |
| @@ -353,47 +358,60 @@ async function sendMakerSuiteRequest(request, response) { | |||
| 353 | }; | 358 | }; |
| 354 | 359 | ||
| 355 | function getGeminiBody() { | 360 | function getGeminiBody() { |
| 361 | // #region UGLY MODEL LISTS AREA | ||
| 362 | const imageGenerationModels = [ | ||
| 363 | 'gemini-2.0-flash-exp', | ||
| 364 | 'gemini-2.0-flash-exp-image-generation', | ||
| 365 | ]; | ||
| 366 | |||
| 367 | // These models do not support setting the threshold to OFF at all. | ||
| 368 | const blockNoneModels = [ | ||
| 369 | 'gemini-1.5-pro-001', | ||
| 370 | 'gemini-1.5-flash-001', | ||
| 371 | 'gemini-1.5-flash-8b-exp-0827', | ||
| 372 | 'gemini-1.5-flash-8b-exp-0924', | ||
| 373 | ]; | ||
| 374 | |||
| 375 | const thinkingBudgetModels = [ | ||
| 376 | 'gemini-2.5-flash-preview-04-17', | ||
| 377 | ]; | ||
| 378 | |||
| 379 | const noSearchModels = [ | ||
| 380 | 'gemini-2.0-flash-lite', | ||
| 381 | 'gemini-2.0-flash-lite-001', | ||
| 382 | 'gemini-2.0-flash-lite-preview-02-05', | ||
| 383 | 'gemini-1.5-flash-8b-exp-0924', | ||
| 384 | 'gemini-1.5-flash-8b-exp-0827', | ||
| 385 | ]; | ||
| 386 | // #endregion | ||
| 387 | |||
| 356 | if (!Array.isArray(generationConfig.stopSequences) || !generationConfig.stopSequences.length) { | 388 | if (!Array.isArray(generationConfig.stopSequences) || !generationConfig.stopSequences.length) { |
| 357 | delete generationConfig.stopSequences; | 389 | delete generationConfig.stopSequences; |
| 358 | } | 390 | } |
| 359 | 391 | ||
| 360 | const useMultiModal = requestImages && ['gemini-2.0-flash-exp', 'gemini-2.0-flash-exp-image-generation'].includes(model); | 392 | const enableImageModality = requestImages && imageGenerationModels.includes(model); |
| 361 | if (useMultiModal) { | 393 | if (enableImageModality) { |
| 362 | generationConfig.responseModalities = ['text', 'image']; | 394 | generationConfig.responseModalities = ['text', 'image']; |
| 363 | } | 395 | } |
| 364 | 396 | ||
| 365 | const useSystemPrompt = !useMultiModal && ( | 397 | const useSystemPrompt = !enableImageModality && !isGemma && request.body.use_makersuite_sysprompt; |
| 366 | model.includes('gemini-2.5-pro') || | ||
| 367 | model.includes('gemini-2.0-pro') || | ||
| 368 | model.includes('gemini-2.0-flash') || | ||
| 369 | model.includes('gemini-2.0-flash-thinking-exp') || | ||
| 370 | model.includes('gemini-1.5-flash') || | ||
| 371 | model.includes('gemini-1.5-pro') || | ||
| 372 | model.startsWith('gemini-exp') | ||
| 373 | ) && request.body.use_makersuite_sysprompt; | ||
| 374 | 398 | ||
| 375 | const tools = []; | 399 | const tools = []; |
| 376 | const prompt = convertGooglePrompt(request.body.messages, model, useSystemPrompt, getPromptNames(request)); | 400 | const prompt = convertGooglePrompt(request.body.messages, model, useSystemPrompt, getPromptNames(request)); |
| 377 | let safetySettings = GEMINI_SAFETY; | 401 | let safetySettings = GEMINI_SAFETY; |
| 378 | 402 | ||
| 379 | // These models do not support setting the threshold to OFF at all. | 403 | if (blockNoneModels.includes(model)) { |
| 380 | if (['gemini-1.5-pro-001', 'gemini-1.5-flash-001', 'gemini-1.5-flash-8b-exp-0827', 'gemini-1.5-flash-8b-exp-0924', 'gemini-pro', 'gemini-1.0-pro', 'gemini-1.0-pro-001', 'gemma-3-27b-it'].includes(model)) { | ||
| 381 | safetySettings = GEMINI_SAFETY.map(setting => ({ ...setting, threshold: 'BLOCK_NONE' })); | 404 | safetySettings = GEMINI_SAFETY.map(setting => ({ ...setting, threshold: 'BLOCK_NONE' })); |
| 382 | } | 405 | } |
| 383 | // Interestingly, Gemini 2.0 Flash does support setting the threshold for HARM_CATEGORY_CIVIC_INTEGRITY to OFF. | ||
| 384 | else if (['gemini-2.0-flash', 'gemini-2.0-flash-001', 'gemini-2.0-flash-exp', 'gemini-2.0-flash-exp-image-generation'].includes(model)) { | ||
| 385 | safetySettings = GEMINI_SAFETY.map(setting => ({ ...setting, threshold: 'OFF' })); | ||
| 386 | } | ||
| 387 | // Most of the other models allow for setting the threshold of filters, except for HARM_CATEGORY_CIVIC_INTEGRITY, to OFF. | ||
| 388 | 406 | ||
| 389 | if (enableWebSearch && !useMultiModal && !isGemma) { | 407 | if (enableWebSearch && !enableImageModality && !isGemma && !isLearnLM && !noSearchModels.includes(model)) { |
| 390 | const searchTool = model.includes('1.5') || model.includes('1.0') | 408 | const searchTool = model.includes('1.5') |
| 391 | ? ({ google_search_retrieval: {} }) | 409 | ? ({ google_search_retrieval: {} }) |
| 392 | : ({ google_search: {} }); | 410 | : ({ google_search: {} }); |
| 393 | tools.push(searchTool); | 411 | tools.push(searchTool); |
| 394 | } | 412 | } |
| 395 | 413 | ||
| 396 | if (Array.isArray(request.body.tools) && request.body.tools.length > 0 && !useMultiModal && !isGemma) { | 414 | if (Array.isArray(request.body.tools) && request.body.tools.length > 0 && !enableImageModality && !isGemma) { |
| 397 | const functionDeclarations = []; | 415 | const functionDeclarations = []; |
| 398 | for (const tool of request.body.tools) { | 416 | for (const tool of request.body.tools) { |
| 399 | if (tool.type === 'function') { | 417 | if (tool.type === 'function') { |
| @@ -409,13 +427,21 @@ async function sendMakerSuiteRequest(request, response) { | |||
| 409 | tools.push({ function_declarations: functionDeclarations }); | 427 | tools.push({ function_declarations: functionDeclarations }); |
| 410 | } | 428 | } |
| 411 | 429 | ||
| 430 | if (thinkingBudgetModels.includes(model)) { | ||
| 431 | const thinkingBudget = calculateGoogleBudgetTokens(generationConfig.maxOutputTokens, reasoningEffort); | ||
| 432 | |||
| 433 | if (Number.isInteger(thinkingBudget)) { | ||
| 434 | generationConfig.thinkingConfig = { thinkingBudget: thinkingBudget }; | ||
| 435 | } | ||
| 436 | } | ||
| 437 | |||
| 412 | let body = { | 438 | let body = { |
| 413 | contents: prompt.contents, | 439 | contents: prompt.contents, |
| 414 | safetySettings: safetySettings, | 440 | safetySettings: safetySettings, |
| 415 | generationConfig: generationConfig, | 441 | generationConfig: generationConfig, |
| 416 | }; | 442 | }; |
| 417 | 443 | ||
| 418 | if (useSystemPrompt) { | 444 | if (useSystemPrompt && Array.isArray(prompt.system_instruction.parts) && prompt.system_instruction.parts.length) { |
| 419 | body.systemInstruction = prompt.system_instruction; | 445 | body.systemInstruction = prompt.system_instruction; |
| 420 | } | 446 | } |
| 421 | 447 | ||
| @@ -436,7 +462,7 @@ async function sendMakerSuiteRequest(request, response) { | |||
| 436 | controller.abort(); | 462 | controller.abort(); |
| 437 | }); | 463 | }); |
| 438 | 464 | ||
| 439 | const apiVersion = isThinking ? 'v1alpha' : 'v1beta'; | 465 | const apiVersion = getConfigValue('gemini.apiVersion', 'v1beta'); |
| 440 | const responseType = (stream ? 'streamGenerateContent' : 'generateContent'); | 466 | const responseType = (stream ? 'streamGenerateContent' : 'generateContent'); |
| 441 | 467 | ||
| 442 | const generateResponse = await fetch(`${apiUrl.toString().replace(/\/$/, '')}/${apiVersion}/models/${model}:${responseType}?key=${apiKey}${stream ? '&alt=sse' : ''}`, { | 468 | const generateResponse = await fetch(`${apiUrl.toString().replace(/\/$/, '')}/${apiVersion}/models/${model}:${responseType}?key=${apiKey}${stream ? '&alt=sse' : ''}`, { |
| @@ -447,7 +473,7 @@ async function sendMakerSuiteRequest(request, response) { | |||
| 447 | }, | 473 | }, |
| 448 | signal: controller.signal, | 474 | signal: controller.signal, |
| 449 | }); | 475 | }); |
| 450 | // have to do this because of their busted ass streaming endpoint | 476 | |
| 451 | if (stream) { | 477 | if (stream) { |
| 452 | try { | 478 | try { |
| 453 | // Pipe remote SSE stream to Express response | 479 | // Pipe remote SSE stream to Express response |
| @@ -461,7 +487,7 @@ async function sendMakerSuiteRequest(request, response) { | |||
| 461 | } else { | 487 | } else { |
| 462 | if (!generateResponse.ok) { | 488 | if (!generateResponse.ok) { |
| 463 | console.warn(`Google AI Studio API returned error: ${generateResponse.status} ${generateResponse.statusText} ${await generateResponse.text()}`); | 489 | console.warn(`Google AI Studio API returned error: ${generateResponse.status} ${generateResponse.statusText} ${await generateResponse.text()}`); |
| 464 | return response.status(generateResponse.status).send({ error: true }); | 490 | return response.status(500).send({ error: true }); |
| 465 | } | 491 | } |
| 466 | 492 | ||
| 467 | /** @type {any} */ | 493 | /** @type {any} */ |
| @@ -480,7 +506,7 @@ async function sendMakerSuiteRequest(request, response) { | |||
| 480 | const responseContent = candidates[0].content ?? candidates[0].output; | 506 | const responseContent = candidates[0].content ?? candidates[0].output; |
| 481 | const functionCall = (candidates?.[0]?.content?.parts ?? []).some(part => part.functionCall); | 507 | const functionCall = (candidates?.[0]?.content?.parts ?? []).some(part => part.functionCall); |
| 482 | const inlineData = (candidates?.[0]?.content?.parts ?? []).some(part => part.inlineData); | 508 | const inlineData = (candidates?.[0]?.content?.parts ?? []).some(part => part.inlineData); |
| 483 | console.warn('Google AI Studio response:', responseContent); | 509 | console.debug('Google AI Studio response:', util.inspect(generateResponseJson, { depth: 5, colors: true })); |
| 484 | 510 | ||
| 485 | const responseText = typeof responseContent === 'string' ? responseContent : responseContent?.parts?.filter(part => !part.thought)?.map(part => part.text)?.join('\n\n'); | 511 | const responseText = typeof responseContent === 'string' ? responseContent : responseContent?.parts?.filter(part => !part.thought)?.map(part => part.text)?.join('\n\n'); |
| 486 | if (!responseText && !functionCall && !inlineData) { | 512 | if (!responseText && !functionCall && !inlineData) { |
| @@ -828,6 +854,100 @@ async function sendDeepSeekRequest(request, response) { | |||
| 828 | } | 854 | } |
| 829 | } | 855 | } |
| 830 | 856 | ||
| 857 | /** | ||
| 858 | * Sends a request to XAI API. | ||
| 859 | * @param {express.Request} request Express request | ||
| 860 | * @param {express.Response} response Express response | ||
| 861 | */ | ||
| 862 | async function sendXaiRequest(request, response) { | ||
| 863 | const apiUrl = new URL(request.body.reverse_proxy || API_XAI).toString(); | ||
| 864 | const apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.XAI); | ||
| 865 | |||
| 866 | if (!apiKey && !request.body.reverse_proxy) { | ||
| 867 | console.warn('xAI API key is missing.'); | ||
| 868 | return response.status(400).send({ error: true }); | ||
| 869 | } | ||
| 870 | |||
| 871 | const controller = new AbortController(); | ||
| 872 | request.socket.removeAllListeners('close'); | ||
| 873 | request.socket.on('close', function () { | ||
| 874 | controller.abort(); | ||
| 875 | }); | ||
| 876 | |||
| 877 | try { | ||
| 878 | let bodyParams = {}; | ||
| 879 | |||
| 880 | if (request.body.logprobs > 0) { | ||
| 881 | bodyParams['top_logprobs'] = request.body.logprobs; | ||
| 882 | bodyParams['logprobs'] = true; | ||
| 883 | } | ||
| 884 | |||
| 885 | if (Array.isArray(request.body.tools) && request.body.tools.length > 0) { | ||
| 886 | bodyParams['tools'] = request.body.tools; | ||
| 887 | bodyParams['tool_choice'] = request.body.tool_choice; | ||
| 888 | } | ||
| 889 | |||
| 890 | if (Array.isArray(request.body.stop) && request.body.stop.length > 0) { | ||
| 891 | bodyParams['stop'] = request.body.stop; | ||
| 892 | } | ||
| 893 | |||
| 894 | if (request.body.reasoning_effort && ['grok-3-mini-beta', 'grok-3-mini-fast-beta'].includes(request.body.model)) { | ||
| 895 | bodyParams['reasoning_effort'] = request.body.reasoning_effort === 'high' ? 'high' : 'low'; | ||
| 896 | } | ||
| 897 | |||
| 898 | const processedMessages = request.body.messages = convertXAIMessages(request.body.messages, getPromptNames(request)); | ||
| 899 | |||
| 900 | const requestBody = { | ||
| 901 | 'messages': processedMessages, | ||
| 902 | 'model': request.body.model, | ||
| 903 | 'temperature': request.body.temperature, | ||
| 904 | 'max_tokens': request.body.max_tokens, | ||
| 905 | 'max_completion_tokens': request.body.max_completion_tokens, | ||
| 906 | 'stream': request.body.stream, | ||
| 907 | 'presence_penalty': request.body.presence_penalty, | ||
| 908 | 'frequency_penalty': request.body.frequency_penalty, | ||
| 909 | 'top_p': request.body.top_p, | ||
| 910 | 'seed': request.body.seed, | ||
| 911 | 'n': request.body.n, | ||
| 912 | ...bodyParams, | ||
| 913 | }; | ||
| 914 | |||
| 915 | const config = { | ||
| 916 | method: 'POST', | ||
| 917 | headers: { | ||
| 918 | 'Content-Type': 'application/json', | ||
| 919 | 'Authorization': 'Bearer ' + apiKey, | ||
| 920 | }, | ||
| 921 | body: JSON.stringify(requestBody), | ||
| 922 | signal: controller.signal, | ||
| 923 | }; | ||
| 924 | |||
| 925 | console.debug('xAI request:', requestBody); | ||
| 926 | |||
| 927 | const generateResponse = await fetch(apiUrl + '/chat/completions', config); | ||
| 928 | |||
| 929 | if (request.body.stream) { | ||
| 930 | forwardFetchResponse(generateResponse, response); | ||
| 931 | } else { | ||
| 932 | if (!generateResponse.ok) { | ||
| 933 | const errorText = await generateResponse.text(); | ||
| 934 | console.warn(`xAI API returned error: ${generateResponse.status} ${generateResponse.statusText} ${errorText}`); | ||
| 935 | const errorJson = tryParse(errorText) ?? { error: true }; | ||
| 936 | return response.status(500).send(errorJson); | ||
| 937 | } | ||
| 938 | const generateResponseJson = await generateResponse.json(); | ||
| 939 | console.debug('xAI response:', generateResponseJson); | ||
| 940 | return response.send(generateResponseJson); | ||
| 941 | } | ||
| 942 | } catch (error) { | ||
| 943 | console.error('Error communicating with xAI API: ', error); | ||
| 944 | if (!response.headersSent) { | ||
| 945 | response.send({ error: true }); | ||
| 946 | } else { | ||
| 947 | response.end(); | ||
| 948 | } | ||
| 949 | } | ||
| 950 | } | ||
| 831 | 951 | ||
| 832 | export const router = express.Router(); | 952 | export const router = express.Router(); |
| 833 | 953 | ||
| @@ -872,6 +992,10 @@ router.post('/status', async function (request, response_getstatus_openai) { | |||
| 872 | api_url = new URL(request.body.reverse_proxy || API_DEEPSEEK.replace('/beta', '')); | 992 | api_url = new URL(request.body.reverse_proxy || API_DEEPSEEK.replace('/beta', '')); |
| 873 | api_key_openai = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.DEEPSEEK); | 993 | api_key_openai = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.DEEPSEEK); |
| 874 | headers = {}; | 994 | headers = {}; |
| 995 | } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.XAI) { | ||
| 996 | api_url = new URL(request.body.reverse_proxy || API_XAI); | ||
| 997 | api_key_openai = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.XAI); | ||
| 998 | headers = {}; | ||
| 875 | } else { | 999 | } else { |
| 876 | console.warn('This chat completion source is not supported yet.'); | 1000 | console.warn('This chat completion source is not supported yet.'); |
| 877 | return response_getstatus_openai.status(400).send({ error: true }); | 1001 | return response_getstatus_openai.status(400).send({ error: true }); |
| @@ -1039,6 +1163,7 @@ router.post('/generate', function (request, response) { | |||
| 1039 | case CHAT_COMPLETION_SOURCES.MISTRALAI: return sendMistralAIRequest(request, response); | 1163 | case CHAT_COMPLETION_SOURCES.MISTRALAI: return sendMistralAIRequest(request, response); |
| 1040 | case CHAT_COMPLETION_SOURCES.COHERE: return sendCohereRequest(request, response); | 1164 | case CHAT_COMPLETION_SOURCES.COHERE: return sendCohereRequest(request, response); |
| 1041 | case CHAT_COMPLETION_SOURCES.DEEPSEEK: return sendDeepSeekRequest(request, response); | 1165 | case CHAT_COMPLETION_SOURCES.DEEPSEEK: return sendDeepSeekRequest(request, response); |
| 1166 | case CHAT_COMPLETION_SOURCES.XAI: return sendXaiRequest(request, response); | ||
| 1042 | } | 1167 | } |
| 1043 | 1168 | ||
| 1044 | let apiUrl; | 1169 | let apiUrl; |
| @@ -1108,6 +1233,10 @@ router.post('/generate', function (request, response) { | |||
| 1108 | bodyParams['route'] = 'fallback'; | 1233 | bodyParams['route'] = 'fallback'; |
| 1109 | } | 1234 | } |
| 1110 | 1235 | ||
| 1236 | if (request.body.reasoning_effort) { | ||
| 1237 | bodyParams['reasoning'] = { effort: request.body.reasoning_effort }; | ||
| 1238 | } | ||
| 1239 | |||
| 1111 | let cachingAtDepth = getConfigValue('claude.cachingAtDepth', -1, 'number'); | 1240 | let cachingAtDepth = getConfigValue('claude.cachingAtDepth', -1, 'number'); |
| 1112 | if (Number.isInteger(cachingAtDepth) && cachingAtDepth >= 0 && request.body.model?.startsWith('anthropic/claude-3')) { | 1241 | if (Number.isInteger(cachingAtDepth) && cachingAtDepth >= 0 && request.body.model?.startsWith('anthropic/claude-3')) { |
| 1113 | cachingAtDepthForOpenRouterClaude(request.body.messages, cachingAtDepth); | 1242 | cachingAtDepthForOpenRouterClaude(request.body.messages, cachingAtDepth); |
| @@ -1156,8 +1285,8 @@ router.post('/generate', function (request, response) { | |||
| 1156 | } | 1285 | } |
| 1157 | 1286 | ||
| 1158 | // A few of OpenAIs reasoning models support reasoning effort | 1287 | // A few of OpenAIs reasoning models support reasoning effort |
| 1159 | if ([CHAT_COMPLETION_SOURCES.CUSTOM, CHAT_COMPLETION_SOURCES.OPENAI].includes(request.body.chat_completion_source)) { | 1288 | if (request.body.reasoning_effort && [CHAT_COMPLETION_SOURCES.CUSTOM, CHAT_COMPLETION_SOURCES.OPENAI].includes(request.body.chat_completion_source)) { |
| 1160 | if (['o1', 'o3-mini', 'o3-mini-2025-01-31'].includes(request.body.model)) { | 1289 | if (['o1', 'o3-mini', 'o3-mini-2025-01-31', 'o4-mini', 'o4-mini-2025-04-16', 'o3', 'o3-2025-04-16'].includes(request.body.model)) { |
| 1161 | bodyParams['reasoning_effort'] = request.body.reasoning_effort; | 1290 | bodyParams['reasoning_effort'] = request.body.reasoning_effort; |
| 1162 | } | 1291 | } |
| 1163 | } | 1292 | } |
| @@ -204,7 +204,7 @@ router.post('/transcribe-audio', async function (request, response) { | |||
| 204 | console.debug('Transcribing audio with KoboldCpp', server); | 204 | console.debug('Transcribing audio with KoboldCpp', server); |
| 205 | 205 | ||
| 206 | const fileBase64 = fs.readFileSync(request.file.path).toString('base64'); | 206 | const fileBase64 = fs.readFileSync(request.file.path).toString('base64'); |
| 207 | fs.rmSync(request.file.path); | 207 | fs.unlinkSync(request.file.path); |
| 208 | 208 | ||
| 209 | const headers = {}; | 209 | const headers = {}; |
| 210 | setAdditionalHeadersByType(headers, TEXTGEN_TYPES.KOBOLDCPP, server, request.user.directories); | 210 | setAdditionalHeadersByType(headers, TEXTGEN_TYPES.KOBOLDCPP, server, request.user.directories); |
| @@ -10,7 +10,6 @@ import { | |||
| 10 | INFERMATICAI_KEYS, | 10 | INFERMATICAI_KEYS, |
| 11 | OPENROUTER_KEYS, | 11 | OPENROUTER_KEYS, |
| 12 | VLLM_KEYS, | 12 | VLLM_KEYS, |
| 13 | DREAMGEN_KEYS, | ||
| 14 | FEATHERLESS_KEYS, | 13 | FEATHERLESS_KEYS, |
| 15 | OPENAI_KEYS, | 14 | OPENAI_KEYS, |
| 16 | } from '../../constants.js'; | 15 | } from '../../constants.js'; |
| @@ -340,9 +339,6 @@ router.post('/generate', async function (request, response) { | |||
| 340 | } | 339 | } |
| 341 | 340 | ||
| 342 | if (request.body.api_type === TEXTGEN_TYPES.DREAMGEN) { | 341 | if (request.body.api_type === TEXTGEN_TYPES.DREAMGEN) { |
| 343 | request.body = _.pickBy(request.body, (_, key) => DREAMGEN_KEYS.includes(key)); | ||
| 344 | // NOTE: DreamGen sometimes get confused by the unusual formatting in the character cards. | ||
| 345 | request.body.stop?.push('### User', '## User'); | ||
| 346 | args.body = JSON.stringify(request.body); | 342 | args.body = JSON.stringify(request.body); |
| 347 | } | 343 | } |
| 348 | 344 | ||
| @@ -450,7 +446,7 @@ ollama.post('/download', async function (request, response) { | |||
| 450 | 446 | ||
| 451 | if (!fetchResponse.ok) { | 447 | if (!fetchResponse.ok) { |
| 452 | console.error('Download error:', fetchResponse.status, fetchResponse.statusText); | 448 | console.error('Download error:', fetchResponse.status, fetchResponse.statusText); |
| 453 | return response.status(fetchResponse.status).send({ error: true }); | 449 | return response.status(500).send({ error: true }); |
| 454 | } | 450 | } |
| 455 | 451 | ||
| 456 | console.debug('Ollama pull response:', await fetchResponse.json()); | 452 | console.debug('Ollama pull response:', await fetchResponse.json()); |
| @@ -659,14 +655,14 @@ tabby.post('/download', async function (request, response) { | |||
| 659 | } | 655 | } |
| 660 | } else { | 656 | } else { |
| 661 | console.error('API Permission error:', permissionResponse.status, permissionResponse.statusText); | 657 | console.error('API Permission error:', permissionResponse.status, permissionResponse.statusText); |
| 662 | return response.status(permissionResponse.status).send({ error: true }); | 658 | return response.status(500).send({ error: true }); |
| 663 | } | 659 | } |
| 664 | 660 | ||
| 665 | const fetchResponse = await fetch(`${baseUrl}/v1/download`, args); | 661 | const fetchResponse = await fetch(`${baseUrl}/v1/download`, args); |
| 666 | 662 | ||
| 667 | if (!fetchResponse.ok) { | 663 | if (!fetchResponse.ok) { |
| 668 | console.error('Download error:', fetchResponse.status, fetchResponse.statusText); | 664 | console.error('Download error:', fetchResponse.status, fetchResponse.statusText); |
| 669 | return response.status(fetchResponse.status).send({ error: true }); | 665 | return response.status(500).send({ error: true }); |
| 670 | } | 666 | } |
| 671 | 667 | ||
| 672 | return response.send({ ok: true }); | 668 | return response.send({ ok: true }); |
| @@ -30,7 +30,7 @@ router.post('/delete', getFileNameValidationFunction('bg'), function (request, r | |||
| 30 | return response.sendStatus(400); | 30 | return response.sendStatus(400); |
| 31 | } | 31 | } |
| 32 | 32 | ||
| 33 | fs.rmSync(fileName); | 33 | fs.unlinkSync(fileName); |
| 34 | invalidateThumbnail(request.user.directories, 'bg', request.body.bg); | 34 | invalidateThumbnail(request.user.directories, 'bg', request.body.bg); |
| 35 | return response.send('ok'); | 35 | return response.send('ok'); |
| 36 | }); | 36 | }); |
| @@ -52,7 +52,7 @@ router.post('/rename', function (request, response) { | |||
| 52 | } | 52 | } |
| 53 | 53 | ||
| 54 | fs.copyFileSync(oldFileName, newFileName); | 54 | fs.copyFileSync(oldFileName, newFileName); |
| 55 | fs.rmSync(oldFileName); | 55 | fs.unlinkSync(oldFileName); |
| 56 | invalidateThumbnail(request.user.directories, 'bg', request.body.old_bg); | 56 | invalidateThumbnail(request.user.directories, 'bg', request.body.old_bg); |
| 57 | return response.send('ok'); | 57 | return response.send('ok'); |
| 58 | }); | 58 | }); |
| @@ -65,7 +65,7 @@ router.post('/upload', function (request, response) { | |||
| 65 | 65 | ||
| 66 | try { | 66 | try { |
| 67 | fs.copyFileSync(img_path, path.join(request.user.directories.backgrounds, filename)); | 67 | fs.copyFileSync(img_path, path.join(request.user.directories.backgrounds, filename)); |
| 68 | fs.rmSync(img_path); | 68 | fs.unlinkSync(img_path); |
| 69 | invalidateThumbnail(request.user.directories, 'bg', filename); | 69 | invalidateThumbnail(request.user.directories, 'bg', filename); |
| 70 | response.send(filename); | 70 | response.send(filename); |
| 71 | } catch (err) { | 71 | } catch (err) { |
| @@ -702,6 +702,12 @@ function convertWorldInfoToCharacterBook(name, entries) { | |||
| 702 | sticky: entry.sticky ?? null, | 702 | sticky: entry.sticky ?? null, |
| 703 | cooldown: entry.cooldown ?? null, | 703 | cooldown: entry.cooldown ?? null, |
| 704 | delay: entry.delay ?? null, | 704 | delay: entry.delay ?? null, |
| 705 | match_persona_description: entry.matchPersonaDescription ?? false, | ||
| 706 | match_character_description: entry.matchCharacterDescription ?? false, | ||
| 707 | match_character_personality: entry.matchCharacterPersonality ?? false, | ||
| 708 | match_character_depth_prompt: entry.matchCharacterDepthPrompt ?? false, | ||
| 709 | match_scenario: entry.matchScenario ?? false, | ||
| 710 | match_creator_notes: entry.matchCreatorNotes ?? false, | ||
| 705 | }, | 711 | }, |
| 706 | }; | 712 | }; |
| 707 | 713 | ||
| @@ -720,7 +726,7 @@ function convertWorldInfoToCharacterBook(name, entries) { | |||
| 720 | */ | 726 | */ |
| 721 | async function importFromYaml(uploadPath, context, preservedFileName) { | 727 | async function importFromYaml(uploadPath, context, preservedFileName) { |
| 722 | const fileText = fs.readFileSync(uploadPath, 'utf8'); | 728 | const fileText = fs.readFileSync(uploadPath, 'utf8'); |
| 723 | fs.rmSync(uploadPath); | 729 | fs.unlinkSync(uploadPath); |
| 724 | const yamlData = yaml.parse(fileText); | 730 | const yamlData = yaml.parse(fileText); |
| 725 | console.info('Importing from YAML'); | 731 | console.info('Importing from YAML'); |
| 726 | yamlData.name = sanitize(yamlData.name); | 732 | yamlData.name = sanitize(yamlData.name); |
| @@ -754,7 +760,7 @@ async function importFromYaml(uploadPath, context, preservedFileName) { | |||
| 754 | */ | 760 | */ |
| 755 | async function importFromCharX(uploadPath, { request }, preservedFileName) { | 761 | async function importFromCharX(uploadPath, { request }, preservedFileName) { |
| 756 | const data = fs.readFileSync(uploadPath).buffer; | 762 | const data = fs.readFileSync(uploadPath).buffer; |
| 757 | fs.rmSync(uploadPath); | 763 | fs.unlinkSync(uploadPath); |
| 758 | console.info('Importing from CharX'); | 764 | console.info('Importing from CharX'); |
| 759 | const cardBuffer = await extractFileFromZipBuffer(data, 'card.json'); | 765 | const cardBuffer = await extractFileFromZipBuffer(data, 'card.json'); |
| 760 | 766 | ||
| @@ -995,7 +1001,7 @@ router.post('/rename', validateAvatarUrlMiddleware, async function (request, res | |||
| 995 | } | 1001 | } |
| 996 | 1002 | ||
| 997 | // Remove the old character file | 1003 | // Remove the old character file |
| 998 | fs.rmSync(oldAvatarPath); | 1004 | fs.unlinkSync(oldAvatarPath); |
| 999 | 1005 | ||
| 1000 | // Return new avatar name to ST | 1006 | // Return new avatar name to ST |
| 1001 | return response.send({ avatar: newAvatarName }); | 1007 | return response.send({ avatar: newAvatarName }); |
| @@ -1150,7 +1156,7 @@ router.post('/delete', validateAvatarUrlMiddleware, async function (request, res | |||
| 1150 | return response.sendStatus(400); | 1156 | return response.sendStatus(400); |
| 1151 | } | 1157 | } |
| 1152 | 1158 | ||
| 1153 | fs.rmSync(avatarPath); | 1159 | fs.unlinkSync(avatarPath); |
| 1154 | invalidateThumbnail(request.user.directories, 'avatar', request.body.avatar_url); | 1160 | invalidateThumbnail(request.user.directories, 'avatar', request.body.avatar_url); |
| 1155 | let dir_name = (request.body.avatar_url.replace('.png', '')); | 1161 | let dir_name = (request.body.avatar_url.replace('.png', '')); |
| 1156 | 1162 | ||
| @@ -433,7 +433,7 @@ router.post('/rename', validateAvatarUrlMiddleware, async function (request, res | |||
| 433 | } | 433 | } |
| 434 | 434 | ||
| 435 | fs.copyFileSync(pathToOriginalFile, pathToRenamedFile); | 435 | fs.copyFileSync(pathToOriginalFile, pathToRenamedFile); |
| 436 | fs.rmSync(pathToOriginalFile); | 436 | fs.unlinkSync(pathToOriginalFile); |
| 437 | console.info('Successfully renamed.'); | 437 | console.info('Successfully renamed.'); |
| 438 | return response.send({ ok: true, sanitizedFileName }); | 438 | return response.send({ ok: true, sanitizedFileName }); |
| 439 | }); | 439 | }); |
| @@ -449,7 +449,7 @@ router.post('/delete', validateAvatarUrlMiddleware, function (request, response) | |||
| 449 | return response.sendStatus(400); | 449 | return response.sendStatus(400); |
| 450 | } | 450 | } |
| 451 | 451 | ||
| 452 | fs.rmSync(filePath); | 452 | fs.unlinkSync(filePath); |
| 453 | console.info(`Deleted chat file: ${filePath}`); | 453 | console.info(`Deleted chat file: ${filePath}`); |
| 454 | return response.send('ok'); | 454 | return response.send('ok'); |
| 455 | }); | 455 | }); |
| @@ -665,7 +665,7 @@ router.post('/group/delete', (request, response) => { | |||
| 665 | const pathToFile = path.join(request.user.directories.groupChats, `${id}.jsonl`); | 665 | const pathToFile = path.join(request.user.directories.groupChats, `${id}.jsonl`); |
| 666 | 666 | ||
| 667 | if (fs.existsSync(pathToFile)) { | 667 | if (fs.existsSync(pathToFile)) { |
| 668 | fs.rmSync(pathToFile); | 668 | fs.unlinkSync(pathToFile); |
| 669 | return response.send({ ok: true }); | 669 | return response.send({ ok: true }); |
| 670 | } | 670 | } |
| 671 | 671 | ||
| @@ -1,6 +1,5 @@ | |||
| 1 | import fs from 'node:fs'; | 1 | import fs from 'node:fs'; |
| 2 | import path from 'node:path'; | 2 | import path from 'node:path'; |
| 3 | import process from 'node:process'; | ||
| 4 | import { Buffer } from 'node:buffer'; | 3 | import { Buffer } from 'node:buffer'; |
| 5 | 4 | ||
| 6 | import express from 'express'; | 5 | import express from 'express'; |
| @@ -8,11 +7,12 @@ import fetch from 'node-fetch'; | |||
| 8 | import sanitize from 'sanitize-filename'; | 7 | import sanitize from 'sanitize-filename'; |
| 9 | import { sync as writeFileAtomicSync } from 'write-file-atomic'; | 8 | import { sync as writeFileAtomicSync } from 'write-file-atomic'; |
| 10 | 9 | ||
| 11 | import { getConfigValue, color } from '../util.js'; | 10 | import { getConfigValue, color, setPermissionsSync } from '../util.js'; |
| 12 | import { write } from '../character-card-parser.js'; | 11 | import { write } from '../character-card-parser.js'; |
| 12 | import { serverDirectory } from '../server-directory.js'; | ||
| 13 | 13 | ||
| 14 | const contentDirectory = path.join(process.cwd(), 'default/content'); | 14 | const contentDirectory = path.join(serverDirectory, 'default/content'); |
| 15 | const scaffoldDirectory = path.join(process.cwd(), 'default/scaffold'); | 15 | const scaffoldDirectory = path.join(serverDirectory, 'default/scaffold'); |
| 16 | const contentIndexPath = path.join(contentDirectory, 'index.json'); | 16 | const contentIndexPath = path.join(contentDirectory, 'index.json'); |
| 17 | const scaffoldIndexPath = path.join(scaffoldDirectory, 'index.json'); | 17 | const scaffoldIndexPath = path.join(scaffoldDirectory, 'index.json'); |
| 18 | 18 | ||
| @@ -149,6 +149,7 @@ async function seedContentForUser(contentIndex, directories, forceCategories) { | |||
| 149 | } | 149 | } |
| 150 | 150 | ||
| 151 | fs.cpSync(contentPath, targetPath, { recursive: true, force: false }); | 151 | fs.cpSync(contentPath, targetPath, { recursive: true, force: false }); |
| 152 | setPermissionsSync(targetPath); | ||
| 152 | console.info(`Content file ${contentItem.filename} copied to ${contentTarget}`); | 153 | console.info(`Content file ${contentItem.filename} copied to ${contentTarget}`); |
| 153 | anyContentAdded = true; | 154 | anyContentAdded = true; |
| 154 | } | 155 | } |
| @@ -540,9 +541,21 @@ async function downloadGenericPng(url) { | |||
| 540 | 541 | ||
| 541 | if (result.ok) { | 542 | if (result.ok) { |
| 542 | const buffer = Buffer.from(await result.arrayBuffer()); | 543 | const buffer = Buffer.from(await result.arrayBuffer()); |
| 543 | const fileName = sanitize(result.url.split('?')[0].split('/').reverse()[0]); | 544 | let fileName = sanitize(result.url.split('?')[0].split('/').reverse()[0]); |
| 544 | const contentType = result.headers.get('content-type') || 'image/png'; //yoink it from AICC function lol | 545 | const contentType = result.headers.get('content-type') || 'image/png'; //yoink it from AICC function lol |
| 545 | 546 | ||
| 547 | // The `importCharacter()` function detects the MIME (content-type) of the file | ||
| 548 | // using its file extension. The problem is that not all third-party APIs serve | ||
| 549 | // their cards with a `.png` extension. To support more third-party sites, | ||
| 550 | // dynamically append the `.png` extension to the filename if it doesn't | ||
| 551 | // already have a file extension. | ||
| 552 | if (contentType === 'image/png') { | ||
| 553 | const ext = fileName.match(/\.(\w+)$/); // Same regex used by `importCharacter()` | ||
| 554 | if (!ext) { | ||
| 555 | fileName += '.png'; | ||
| 556 | } | ||
| 557 | } | ||
| 558 | |||
| 546 | return { | 559 | return { |
| 547 | buffer: buffer, | 560 | buffer: buffer, |
| 548 | fileName: fileName, | 561 | fileName: fileName, |
| @@ -694,10 +707,11 @@ router.post('/importURL', async (request, response) => { | |||
| 694 | type = 'character'; | 707 | type = 'character'; |
| 695 | result = await downloadRisuCharacter(uuid); | 708 | result = await downloadRisuCharacter(uuid); |
| 696 | } else if (isGeneric) { | 709 | } else if (isGeneric) { |
| 697 | console.info('Downloading from generic url.'); | 710 | console.info('Downloading from generic url:', url); |
| 698 | type = 'character'; | 711 | type = 'character'; |
| 699 | result = await downloadGenericPng(url); | 712 | result = await downloadGenericPng(url); |
| 700 | } else { | 713 | } else { |
| 714 | console.error(`Received an import for "${getHostFromUrl(url)}", but site is not whitelisted. This domain must be added to the config key "whitelistImportDomains" to allow import from this source.`); | ||
| 701 | return response.sendStatus(404); | 715 | return response.sendStatus(404); |
| 702 | } | 716 | } |
| 703 | 717 | ||
| @@ -30,17 +30,23 @@ async function getManifest(extensionPath) { | |||
| 30 | * @returns {Promise<Object>} - Returns the extension information as an object | 30 | * @returns {Promise<Object>} - Returns the extension information as an object |
| 31 | */ | 31 | */ |
| 32 | async function checkIfRepoIsUpToDate(extensionPath) { | 32 | async function checkIfRepoIsUpToDate(extensionPath) { |
| 33 | const git = simpleGit(); | 33 | const git = simpleGit({ baseDir: extensionPath }); |
| 34 | await git.cwd(extensionPath).fetch('origin'); | 34 | await git.fetch('origin'); |
| 35 | const currentBranch = await git.cwd(extensionPath).branch(); | 35 | const currentBranch = await git.branch(); |
| 36 | const currentCommitHash = await git.cwd(extensionPath).revparse(['HEAD']); | 36 | const currentCommitHash = await git.revparse(['HEAD']); |
| 37 | const log = await git.cwd(extensionPath).log({ | 37 | const log = await git.log({ |
| 38 | from: currentCommitHash, | 38 | from: currentCommitHash, |
| 39 | to: `origin/${currentBranch.current}`, | 39 | to: `origin/${currentBranch.current}`, |
| 40 | }); | 40 | }); |
| 41 | 41 | ||
| 42 | // Fetch remote repository information | 42 | // Fetch remote repository information |
| 43 | const remotes = await git.cwd(extensionPath).getRemotes(true); | 43 | const remotes = await git.getRemotes(true); |
| 44 | if (remotes.length === 0) { | ||
| 45 | return { | ||
| 46 | isUpToDate: true, | ||
| 47 | remoteUrl: '', | ||
| 48 | }; | ||
| 49 | } | ||
| 44 | 50 | ||
| 45 | return { | 51 | return { |
| 46 | isUpToDate: log.total === 0, | 52 | isUpToDate: log.total === 0, |
| @@ -76,7 +82,7 @@ router.post('/install', async (request, response) => { | |||
| 76 | fs.mkdirSync(PUBLIC_DIRECTORIES.globalExtensions); | 82 | fs.mkdirSync(PUBLIC_DIRECTORIES.globalExtensions); |
| 77 | } | 83 | } |
| 78 | 84 | ||
| 79 | const { url, global } = request.body; | 85 | const { url, global, branch } = request.body; |
| 80 | 86 | ||
| 81 | if (global && !request.user.profile.admin) { | 87 | if (global && !request.user.profile.admin) { |
| 82 | console.error(`User ${request.user.profile.handle} does not have permission to install global extensions.`); | 88 | console.error(`User ${request.user.profile.handle} does not have permission to install global extensions.`); |
| @@ -90,8 +96,12 @@ router.post('/install', async (request, response) => { | |||
| 90 | return response.status(409).send(`Directory already exists at ${extensionPath}`); | 96 | return response.status(409).send(`Directory already exists at ${extensionPath}`); |
| 91 | } | 97 | } |
| 92 | 98 | ||
| 93 | await git.clone(url, extensionPath, { '--depth': 1 }); | 99 | const cloneOptions = { '--depth': 1 }; |
| 94 | console.info(`Extension has been cloned at ${extensionPath}`); | 100 | if (branch) { |
| 101 | cloneOptions['--branch'] = branch; | ||
| 102 | } | ||
| 103 | await git.clone(url, extensionPath, cloneOptions); | ||
| 104 | console.info(`Extension has been cloned to ${extensionPath} from ${url} at ${branch || '(default)'} branch`); | ||
| 95 | 105 | ||
| 96 | const { version, author, display_name } = await getManifest(extensionPath); | 106 | const { version, author, display_name } = await getManifest(extensionPath); |
| 97 | 107 | ||
| @@ -114,7 +124,6 @@ router.post('/install', async (request, response) => { | |||
| 114 | * @returns {void} | 124 | * @returns {void} |
| 115 | */ | 125 | */ |
| 116 | router.post('/update', async (request, response) => { | 126 | router.post('/update', async (request, response) => { |
| 117 | const git = simpleGit(); | ||
| 118 | if (!request.body.extensionName) { | 127 | if (!request.body.extensionName) { |
| 119 | return response.status(400).send('Bad Request: extensionName is required in the request body.'); | 128 | return response.status(400).send('Bad Request: extensionName is required in the request body.'); |
| 120 | } | 129 | } |
| @@ -128,22 +137,23 @@ router.post('/update', async (request, response) => { | |||
| 128 | } | 137 | } |
| 129 | 138 | ||
| 130 | const basePath = global ? PUBLIC_DIRECTORIES.globalExtensions : request.user.directories.extensions; | 139 | const basePath = global ? PUBLIC_DIRECTORIES.globalExtensions : request.user.directories.extensions; |
| 131 | const extensionPath = path.join(basePath, extensionName); | 140 | const extensionPath = path.join(basePath, sanitize(extensionName)); |
| 132 | 141 | ||
| 133 | if (!fs.existsSync(extensionPath)) { | 142 | if (!fs.existsSync(extensionPath)) { |
| 134 | return response.status(404).send(`Directory does not exist at ${extensionPath}`); | 143 | return response.status(404).send(`Directory does not exist at ${extensionPath}`); |
| 135 | } | 144 | } |
| 136 | 145 | ||
| 137 | const { isUpToDate, remoteUrl } = await checkIfRepoIsUpToDate(extensionPath); | 146 | const { isUpToDate, remoteUrl } = await checkIfRepoIsUpToDate(extensionPath); |
| 138 | const currentBranch = await git.cwd(extensionPath).branch(); | 147 | const git = simpleGit({ baseDir: extensionPath }); |
| 148 | const currentBranch = await git.branch(); | ||
| 139 | if (!isUpToDate) { | 149 | if (!isUpToDate) { |
| 140 | await git.cwd(extensionPath).pull('origin', currentBranch.current); | 150 | await git.pull('origin', currentBranch.current); |
| 141 | console.info(`Extension has been updated at ${extensionPath}`); | 151 | console.info(`Extension has been updated at ${extensionPath}`); |
| 142 | } else { | 152 | } else { |
| 143 | console.info(`Extension is up to date at ${extensionPath}`); | 153 | console.info(`Extension is up to date at ${extensionPath}`); |
| 144 | } | 154 | } |
| 145 | await git.cwd(extensionPath).fetch('origin'); | 155 | await git.fetch('origin'); |
| 146 | const fullCommitHash = await git.cwd(extensionPath).revparse(['HEAD']); | 156 | const fullCommitHash = await git.revparse(['HEAD']); |
| 147 | const shortCommitHash = fullCommitHash.slice(0, 7); | 157 | const shortCommitHash = fullCommitHash.slice(0, 7); |
| 148 | 158 | ||
| 149 | return response.send({ shortCommitHash, extensionPath, isUpToDate, remoteUrl }); | 159 | return response.send({ shortCommitHash, extensionPath, isUpToDate, remoteUrl }); |
| @@ -154,6 +164,110 @@ router.post('/update', async (request, response) => { | |||
| 154 | } | 164 | } |
| 155 | }); | 165 | }); |
| 156 | 166 | ||
| 167 | router.post('/branches', async (request, response) => { | ||
| 168 | try { | ||
| 169 | const { extensionName, global } = request.body; | ||
| 170 | |||
| 171 | if (!extensionName) { | ||
| 172 | return response.status(400).send('Bad Request: extensionName is required in the request body.'); | ||
| 173 | } | ||
| 174 | |||
| 175 | if (global && !request.user.profile.admin) { | ||
| 176 | console.error(`User ${request.user.profile.handle} does not have permission to list branches of global extensions.`); | ||
| 177 | return response.status(403).send('Forbidden: No permission to list branches of global extensions.'); | ||
| 178 | } | ||
| 179 | |||
| 180 | const basePath = global ? PUBLIC_DIRECTORIES.globalExtensions : request.user.directories.extensions; | ||
| 181 | const extensionPath = path.join(basePath, sanitize(extensionName)); | ||
| 182 | |||
| 183 | if (!fs.existsSync(extensionPath)) { | ||
| 184 | return response.status(404).send(`Directory does not exist at ${extensionPath}`); | ||
| 185 | } | ||
| 186 | |||
| 187 | const git = simpleGit({ baseDir: extensionPath }); | ||
| 188 | // Unshallow the repository if it is shallow | ||
| 189 | const isShallow = await git.revparse(['--is-shallow-repository']) === 'true'; | ||
| 190 | if (isShallow) { | ||
| 191 | console.info(`Unshallowing the repository at ${extensionPath}`); | ||
| 192 | await git.fetch('origin', ['--unshallow']); | ||
| 193 | } | ||
| 194 | |||
| 195 | // Fetch all branches | ||
| 196 | await git.remote(['set-branches', 'origin', '*']); | ||
| 197 | await git.fetch('origin'); | ||
| 198 | const localBranches = await git.branchLocal(); | ||
| 199 | const remoteBranches = await git.branch(['-r', '--list', 'origin/*']); | ||
| 200 | const result = [ | ||
| 201 | ...Object.values(localBranches.branches), | ||
| 202 | ...Object.values(remoteBranches.branches), | ||
| 203 | ].map(b => ({ current: b.current, commit: b.commit, name: b.name, label: b.label })); | ||
| 204 | |||
| 205 | return response.send(result); | ||
| 206 | } catch (error) { | ||
| 207 | console.error('Getting branches failed', error); | ||
| 208 | return response.status(500).send('Internal Server Error. Check the server logs for more details.'); | ||
| 209 | } | ||
| 210 | }); | ||
| 211 | |||
| 212 | router.post('/switch', async (request, response) => { | ||
| 213 | try { | ||
| 214 | const { extensionName, branch, global } = request.body; | ||
| 215 | |||
| 216 | if (!extensionName || !branch) { | ||
| 217 | return response.status(400).send('Bad Request: extensionName and branch are required in the request body.'); | ||
| 218 | } | ||
| 219 | |||
| 220 | if (global && !request.user.profile.admin) { | ||
| 221 | console.error(`User ${request.user.profile.handle} does not have permission to switch branches of global extensions.`); | ||
| 222 | return response.status(403).send('Forbidden: No permission to switch branches of global extensions.'); | ||
| 223 | } | ||
| 224 | |||
| 225 | const basePath = global ? PUBLIC_DIRECTORIES.globalExtensions : request.user.directories.extensions; | ||
| 226 | const extensionPath = path.join(basePath, sanitize(extensionName)); | ||
| 227 | |||
| 228 | if (!fs.existsSync(extensionPath)) { | ||
| 229 | return response.status(404).send(`Directory does not exist at ${extensionPath}`); | ||
| 230 | } | ||
| 231 | |||
| 232 | const git = simpleGit({ baseDir: extensionPath }); | ||
| 233 | const branches = await git.branchLocal(); | ||
| 234 | |||
| 235 | if (String(branch).startsWith('origin/')) { | ||
| 236 | const localBranch = branch.replace('origin/', ''); | ||
| 237 | if (branches.all.includes(localBranch)) { | ||
| 238 | console.info(`Branch ${localBranch} already exists locally, checking it out`); | ||
| 239 | await git.checkout(localBranch); | ||
| 240 | return response.sendStatus(204); | ||
| 241 | } | ||
| 242 | |||
| 243 | console.info(`Branch ${localBranch} does not exist locally, creating it from ${branch}`); | ||
| 244 | await git.checkoutBranch(localBranch, branch); | ||
| 245 | return response.sendStatus(204); | ||
| 246 | } | ||
| 247 | |||
| 248 | if (!branches.all.includes(branch)) { | ||
| 249 | console.error(`Branch ${branch} does not exist locally`); | ||
| 250 | return response.status(404).send(`Branch ${branch} does not exist locally`); | ||
| 251 | } | ||
| 252 | |||
| 253 | // Check if the branch is already checked out | ||
| 254 | const currentBranch = await git.branch(); | ||
| 255 | if (currentBranch.current === branch) { | ||
| 256 | console.info(`Branch ${branch} is already checked out`); | ||
| 257 | return response.sendStatus(204); | ||
| 258 | } | ||
| 259 | |||
| 260 | // Checkout the branch | ||
| 261 | await git.checkout(branch); | ||
| 262 | console.info(`Checked out branch ${branch} at ${extensionPath}`); | ||
| 263 | |||
| 264 | return response.sendStatus(204); | ||
| 265 | } catch (error) { | ||
| 266 | console.error('Switching branches failed', error); | ||
| 267 | return response.status(500).send('Internal Server Error. Check the server logs for more details.'); | ||
| 268 | } | ||
| 269 | }); | ||
| 270 | |||
| 157 | router.post('/move', async (request, response) => { | 271 | router.post('/move', async (request, response) => { |
| 158 | try { | 272 | try { |
| 159 | const { extensionName, source, destination } = request.body; | 273 | const { extensionName, source, destination } = request.body; |
| @@ -194,7 +308,7 @@ router.post('/move', async (request, response) => { | |||
| 194 | return response.sendStatus(204); | 308 | return response.sendStatus(204); |
| 195 | } catch (error) { | 309 | } catch (error) { |
| 196 | console.error('Moving extension failed', error); | 310 | console.error('Moving extension failed', error); |
| 197 | return response.status(500).send('Internal Server Error. Try again later.'); | 311 | return response.status(500).send('Internal Server Error. Check the server logs for more details.'); |
| 198 | } | 312 | } |
| 199 | }); | 313 | }); |
| 200 | 314 | ||
| @@ -209,7 +323,6 @@ router.post('/move', async (request, response) => { | |||
| 209 | * @returns {void} | 323 | * @returns {void} |
| 210 | */ | 324 | */ |
| 211 | router.post('/version', async (request, response) => { | 325 | router.post('/version', async (request, response) => { |
| 212 | const git = simpleGit(); | ||
| 213 | if (!request.body.extensionName) { | 326 | if (!request.body.extensionName) { |
| 214 | return response.status(400).send('Bad Request: extensionName is required in the request body.'); | 327 | return response.status(400).send('Bad Request: extensionName is required in the request body.'); |
| 215 | } | 328 | } |
| @@ -223,19 +336,20 @@ router.post('/version', async (request, response) => { | |||
| 223 | return response.status(404).send(`Directory does not exist at ${extensionPath}`); | 336 | return response.status(404).send(`Directory does not exist at ${extensionPath}`); |
| 224 | } | 337 | } |
| 225 | 338 | ||
| 339 | const git = simpleGit({ baseDir: extensionPath }); | ||
| 226 | let currentCommitHash; | 340 | let currentCommitHash; |
| 227 | try { | 341 | try { |
| 228 | currentCommitHash = await git.cwd(extensionPath).revparse(['HEAD']); | 342 | currentCommitHash = await git.revparse(['HEAD']); |
| 229 | } catch (error) { | 343 | } catch (error) { |
| 230 | // it is not a git repo, or has no commits yet, or is a bare repo | 344 | // it is not a git repo, or has no commits yet, or is a bare repo |
| 231 | // not possible to update it, most likely can't get the branch name either | 345 | // not possible to update it, most likely can't get the branch name either |
| 232 | return response.send({ currentBranchName: '', currentCommitHash: '', isUpToDate: true, remoteUrl: '' }); | 346 | return response.send({ currentBranchName: '', currentCommitHash: '', isUpToDate: true, remoteUrl: '' }); |
| 233 | } | 347 | } |
| 234 | 348 | ||
| 235 | const currentBranch = await git.cwd(extensionPath).branch(); | 349 | const currentBranch = await git.branch(); |
| 236 | // get only the working branch | 350 | // get only the working branch |
| 237 | const currentBranchName = currentBranch.current; | 351 | const currentBranchName = currentBranch.current; |
| 238 | await git.cwd(extensionPath).fetch('origin'); | 352 | await git.fetch('origin'); |
| 239 | console.debug(extensionName, currentBranchName, currentCommitHash); | 353 | console.debug(extensionName, currentBranchName, currentCommitHash); |
| 240 | const { isUpToDate, remoteUrl } = await checkIfRepoIsUpToDate(extensionPath); | 354 | const { isUpToDate, remoteUrl } = await checkIfRepoIsUpToDate(extensionPath); |
| 241 | 355 | ||
| @@ -66,7 +66,7 @@ router.post('/delete', async (request, response) => { | |||
| 66 | return response.status(404).send('File not found'); | 66 | return response.status(404).send('File not found'); |
| 67 | } | 67 | } |
| 68 | 68 | ||
| 69 | fs.rmSync(pathToDelete); | 69 | fs.unlinkSync(pathToDelete); |
| 70 | console.info(`Deleted file: ${request.body.path} from ${request.user.profile.handle}`); | 70 | console.info(`Deleted file: ${request.body.path} from ${request.user.profile.handle}`); |
| 71 | return response.sendStatus(200); | 71 | return response.sendStatus(200); |
| 72 | } catch (error) { | 72 | } catch (error) { |
| @@ -45,7 +45,7 @@ router.post('/caption-image', async (request, response) => { | |||
| 45 | if (!result.ok) { | 45 | if (!result.ok) { |
| 46 | const error = await result.json(); | 46 | const error = await result.json(); |
| 47 | console.error(`Google AI Studio API returned error: ${result.status} ${result.statusText}`, error); | 47 | console.error(`Google AI Studio API returned error: ${result.status} ${result.statusText}`, error); |
| 48 | return response.status(result.status).send({ error: true }); | 48 | return response.status(500).send({ error: true }); |
| 49 | } | 49 | } |
| 50 | 50 | ||
| 51 | /** @type {any} */ | 51 | /** @type {any} */ |
| @@ -117,7 +117,7 @@ router.post('/delete', async (request, response) => { | |||
| 117 | const pathToFile = path.join(request.user.directories.groupChats, `${id}.jsonl`); | 117 | const pathToFile = path.join(request.user.directories.groupChats, `${id}.jsonl`); |
| 118 | 118 | ||
| 119 | if (fs.existsSync(pathToFile)) { | 119 | if (fs.existsSync(pathToFile)) { |
| 120 | fs.rmSync(pathToFile); | 120 | fs.unlinkSync(pathToFile); |
| 121 | } | 121 | } |
| 122 | } | 122 | } |
| 123 | } | 123 | } |
| @@ -126,7 +126,7 @@ router.post('/delete', async (request, response) => { | |||
| 126 | } | 126 | } |
| 127 | 127 | ||
| 128 | if (fs.existsSync(pathToGroup)) { | 128 | if (fs.existsSync(pathToGroup)) { |
| 129 | fs.rmSync(pathToGroup); | 129 | fs.unlinkSync(pathToGroup); |
| 130 | } | 130 | } |
| 131 | 131 | ||
| 132 | return response.send({ ok: true }); | 132 | return response.send({ ok: true }); |
| @@ -270,7 +270,7 @@ router.post('/generate', async function (req, res) { | |||
| 270 | // ignore | 270 | // ignore |
| 271 | } | 271 | } |
| 272 | 272 | ||
| 273 | return res.status(response.status).send({ error: { message } }); | 273 | return res.status(500).send({ error: { message } }); |
| 274 | } | 274 | } |
| 275 | 275 | ||
| 276 | /** @type {any} */ | 276 | /** @type {any} */ |
| @@ -65,6 +65,10 @@ router.post('/caption-image', async (request, response) => { | |||
| 65 | key = readSecret(request.user.directories, SECRET_KEYS.COHERE); | 65 | key = readSecret(request.user.directories, SECRET_KEYS.COHERE); |
| 66 | } | 66 | } |
| 67 | 67 | ||
| 68 | if (request.body.api === 'xai') { | ||
| 69 | key = readSecret(request.user.directories, SECRET_KEYS.XAI); | ||
| 70 | } | ||
| 71 | |||
| 68 | if (!key && !request.body.reverse_proxy && ['custom', 'ooba', 'koboldcpp', 'vllm'].includes(request.body.api) === false) { | 72 | if (!key && !request.body.reverse_proxy && ['custom', 'ooba', 'koboldcpp', 'vllm'].includes(request.body.api) === false) { |
| 69 | console.warn('No key found for API', request.body.api); | 73 | console.warn('No key found for API', request.body.api); |
| 70 | return response.sendStatus(400); | 74 | return response.sendStatus(400); |
| @@ -134,6 +138,10 @@ router.post('/caption-image', async (request, response) => { | |||
| 134 | apiUrl = 'https://api.cohere.ai/v2/chat'; | 138 | apiUrl = 'https://api.cohere.ai/v2/chat'; |
| 135 | } | 139 | } |
| 136 | 140 | ||
| 141 | if (request.body.api === 'xai') { | ||
| 142 | apiUrl = 'https://api.x.ai/v1/chat/completions'; | ||
| 143 | } | ||
| 144 | |||
| 137 | if (request.body.api === 'ooba') { | 145 | if (request.body.api === 'ooba') { |
| 138 | apiUrl = `${trimV1(request.body.server_url)}/v1/chat/completions`; | 146 | apiUrl = `${trimV1(request.body.server_url)}/v1/chat/completions`; |
| 139 | const imgMessage = body.messages.pop(); | 147 | const imgMessage = body.messages.pop(); |
| @@ -226,7 +234,7 @@ router.post('/transcribe-audio', async (request, response) => { | |||
| 226 | return response.status(500).send(text); | 234 | return response.status(500).send(text); |
| 227 | } | 235 | } |
| 228 | 236 | ||
| 229 | fs.rmSync(request.file.path); | 237 | fs.unlinkSync(request.file.path); |
| 230 | const data = await result.json(); | 238 | const data = await result.json(); |
| 231 | console.debug('OpenAI transcription response', data); | 239 | console.debug('OpenAI transcription response', data); |
| 232 | return response.json(data); | 240 | return response.json(data); |
| @@ -124,7 +124,7 @@ router.post('/delete-openai', function (request, response) { | |||
| 124 | const pathToFile = path.join(request.user.directories.openAI_Settings, `${name}.json`); | 124 | const pathToFile = path.join(request.user.directories.openAI_Settings, `${name}.json`); |
| 125 | 125 | ||
| 126 | if (fs.existsSync(pathToFile)) { | 126 | if (fs.existsSync(pathToFile)) { |
| 127 | fs.rmSync(pathToFile); | 127 | fs.unlinkSync(pathToFile); |
| 128 | return response.send({ ok: true }); | 128 | return response.send({ ok: true }); |
| 129 | } | 129 | } |
| 130 | 130 | ||
| @@ -52,6 +52,7 @@ export const SECRET_KEYS = { | |||
| 52 | GENERIC: 'api_key_generic', | 52 | GENERIC: 'api_key_generic', |
| 53 | DEEPSEEK: 'api_key_deepseek', | 53 | DEEPSEEK: 'api_key_deepseek', |
| 54 | SERPER: 'api_key_serper', | 54 | SERPER: 'api_key_serper', |
| 55 | XAI: 'api_key_xai', | ||
| 55 | }; | 56 | }; |
| 56 | 57 | ||
| 57 | // These are the keys that are safe to expose, even if allowKeysExposure is false | 58 | // These are the keys that are safe to expose, even if allowKeysExposure is false |
| @@ -165,7 +165,7 @@ router.post('/delete', async (request, response) => { | |||
| 165 | // Remove existing sprite with the same label | 165 | // Remove existing sprite with the same label |
| 166 | for (const file of files) { | 166 | for (const file of files) { |
| 167 | if (path.parse(file).name === spriteName) { | 167 | if (path.parse(file).name === spriteName) { |
| 168 | fs.rmSync(path.join(spritesPath, file)); | 168 | fs.unlinkSync(path.join(spritesPath, file)); |
| 169 | } | 169 | } |
| 170 | } | 170 | } |
| 171 | 171 | ||
| @@ -206,7 +206,7 @@ router.post('/upload-zip', async (request, response) => { | |||
| 206 | const existingFile = files.find(file => path.parse(file).name === path.parse(filename).name); | 206 | const existingFile = files.find(file => path.parse(file).name === path.parse(filename).name); |
| 207 | 207 | ||
| 208 | if (existingFile) { | 208 | if (existingFile) { |
| 209 | fs.rmSync(path.join(spritesPath, existingFile)); | 209 | fs.unlinkSync(path.join(spritesPath, existingFile)); |
| 210 | } | 210 | } |
| 211 | 211 | ||
| 212 | // Write sprite buffer to disk | 212 | // Write sprite buffer to disk |
| @@ -215,7 +215,7 @@ router.post('/upload-zip', async (request, response) => { | |||
| 215 | } | 215 | } |
| 216 | 216 | ||
| 217 | // Remove uploaded ZIP file | 217 | // Remove uploaded ZIP file |
| 218 | fs.rmSync(spritePackPath); | 218 | fs.unlinkSync(spritePackPath); |
| 219 | return response.send({ count: sprites.length }); | 219 | return response.send({ count: sprites.length }); |
| 220 | } catch (error) { | 220 | } catch (error) { |
| 221 | console.error(error); | 221 | console.error(error); |
| @@ -251,7 +251,7 @@ router.post('/upload', async (request, response) => { | |||
| 251 | // Remove existing sprite with the same label | 251 | // Remove existing sprite with the same label |
| 252 | for (const file of files) { | 252 | for (const file of files) { |
| 253 | if (path.parse(file).name === spriteName) { | 253 | if (path.parse(file).name === spriteName) { |
| 254 | fs.rmSync(path.join(spritesPath, file)); | 254 | fs.unlinkSync(path.join(spritesPath, file)); |
| 255 | } | 255 | } |
| 256 | } | 256 | } |
| 257 | 257 | ||
| @@ -261,7 +261,7 @@ router.post('/upload', async (request, response) => { | |||
| 261 | // Copy uploaded file to sprites folder | 261 | // Copy uploaded file to sprites folder |
| 262 | fs.cpSync(spritePath, pathToFile); | 262 | fs.cpSync(spritePath, pathToFile); |
| 263 | // Remove uploaded file | 263 | // Remove uploaded file |
| 264 | fs.rmSync(spritePath); | 264 | fs.unlinkSync(spritePath); |
| 265 | return response.sendStatus(200); | 265 | return response.sendStatus(200); |
| 266 | } catch (error) { | 266 | } catch (error) { |
| 267 | console.error(error); | 267 | console.error(error); |
| @@ -627,8 +627,8 @@ together.post('/models', async (request, response) => { | |||
| 627 | } | 627 | } |
| 628 | 628 | ||
| 629 | const models = data | 629 | const models = data |
| 630 | .filter(x => x.display_type === 'image') | 630 | .filter(x => x.type === 'image') |
| 631 | .map(x => ({ value: x.name, text: x.display_name })); | 631 | .map(x => ({ value: x.id, text: x.display_name })); |
| 632 | 632 | ||
| 633 | return response.send(models); | 633 | return response.send(models); |
| 634 | } catch (error) { | 634 | } catch (error) { |
| @@ -1166,7 +1166,8 @@ falai.post('/models', async (_request, response) => { | |||
| 1166 | const models = data | 1166 | const models = data |
| 1167 | .filter(x => !x.title.toLowerCase().includes('inpainting') && | 1167 | .filter(x => !x.title.toLowerCase().includes('inpainting') && |
| 1168 | !x.title.toLowerCase().includes('control') && | 1168 | !x.title.toLowerCase().includes('control') && |
| 1169 | !x.title.toLowerCase().includes('upscale')) | 1169 | !x.title.toLowerCase().includes('upscale') && |
| 1170 | !x.title.toLowerCase().includes('lora')) | ||
| 1170 | .sort((a, b) => a.title.localeCompare(b.title)) | 1171 | .sort((a, b) => a.title.localeCompare(b.title)) |
| 1171 | .map(x => ({ value: x.modelUrl.split('fal-ai/')[1], text: x.title })); | 1172 | .map(x => ({ value: x.modelUrl.split('fal-ai/')[1], text: x.title })); |
| 1172 | return response.send(models); | 1173 | return response.send(models); |
| @@ -1245,6 +1246,7 @@ falai.post('/generate', async (request, response) => { | |||
| 1245 | 'Authorization': `Key ${key}`, | 1246 | 'Authorization': `Key ${key}`, |
| 1246 | }, | 1247 | }, |
| 1247 | }); | 1248 | }); |
| 1249 | /** @type {any} */ | ||
| 1248 | const resultData = await resultFetch.json(); | 1250 | const resultData = await resultFetch.json(); |
| 1249 | 1251 | ||
| 1250 | if (resultData.detail !== null && resultData.detail !== undefined) { | 1252 | if (resultData.detail !== null && resultData.detail !== undefined) { |
| @@ -1270,6 +1272,56 @@ falai.post('/generate', async (request, response) => { | |||
| 1270 | } | 1272 | } |
| 1271 | }); | 1273 | }); |
| 1272 | 1274 | ||
| 1275 | const xai = express.Router(); | ||
| 1276 | |||
| 1277 | xai.post('/generate', async (request, response) => { | ||
| 1278 | try { | ||
| 1279 | const key = readSecret(request.user.directories, SECRET_KEYS.XAI); | ||
| 1280 | |||
| 1281 | if (!key) { | ||
| 1282 | console.warn('xAI key not found.'); | ||
| 1283 | return response.sendStatus(400); | ||
| 1284 | } | ||
| 1285 | |||
| 1286 | const requestBody = { | ||
| 1287 | prompt: request.body.prompt, | ||
| 1288 | model: request.body.model, | ||
| 1289 | response_format: 'b64_json', | ||
| 1290 | }; | ||
| 1291 | |||
| 1292 | console.debug('xAI request:', requestBody); | ||
| 1293 | |||
| 1294 | const result = await fetch('https://api.x.ai/v1/images/generations', { | ||
| 1295 | method: 'POST', | ||
| 1296 | body: JSON.stringify(requestBody), | ||
| 1297 | headers: { | ||
| 1298 | 'Content-Type': 'application/json', | ||
| 1299 | 'Authorization': `Bearer ${key}`, | ||
| 1300 | }, | ||
| 1301 | }); | ||
| 1302 | |||
| 1303 | if (!result.ok) { | ||
| 1304 | const text = await result.text(); | ||
| 1305 | console.warn('xAI returned an error.', text); | ||
| 1306 | return response.sendStatus(500); | ||
| 1307 | } | ||
| 1308 | |||
| 1309 | /** @type {any} */ | ||
| 1310 | const data = await result.json(); | ||
| 1311 | |||
| 1312 | const image = data?.data?.[0]?.b64_json; | ||
| 1313 | if (!image) { | ||
| 1314 | console.warn('xAI returned invalid data.'); | ||
| 1315 | return response.sendStatus(500); | ||
| 1316 | } | ||
| 1317 | |||
| 1318 | return response.send({ image }); | ||
| 1319 | } catch (error) { | ||
| 1320 | console.error('Error communicating with xAI', error); | ||
| 1321 | return response.sendStatus(500); | ||
| 1322 | } | ||
| 1323 | }); | ||
| 1324 | |||
| 1273 | router.use('/comfy', comfy); | 1325 | router.use('/comfy', comfy); |
| 1274 | router.use('/together', together); | 1326 | router.use('/together', together); |
| 1275 | router.use('/drawthings', drawthings); | 1327 | router.use('/drawthings', drawthings); |
| @@ -1279,3 +1331,4 @@ router.use('/huggingface', huggingface); | |||
| 1279 | router.use('/nanogpt', nanogpt); | 1331 | router.use('/nanogpt', nanogpt); |
| 1280 | router.use('/bfl', bfl); | 1332 | router.use('/bfl', bfl); |
| 1281 | router.use('/falai', falai); | 1333 | router.use('/falai', falai); |
| 1334 | router.use('/xai', xai); | ||
| @@ -29,7 +29,7 @@ router.post('/delete', function (request, response) { | |||
| 29 | console.error('Theme file not found:', filename); | 29 | console.error('Theme file not found:', filename); |
| 30 | return response.sendStatus(404); | 30 | return response.sendStatus(404); |
| 31 | } | 31 | } |
| 32 | fs.rmSync(filename); | 32 | fs.unlinkSync(filename); |
| 33 | return response.sendStatus(200); | 33 | return response.sendStatus(200); |
| 34 | } catch (error) { | 34 | } catch (error) { |
| 35 | console.error(error); | 35 | console.error(error); |
| @@ -75,7 +75,7 @@ export function invalidateThumbnail(directories, type, file) { | |||
| 75 | const pathToThumbnail = path.join(folder, file); | 75 | const pathToThumbnail = path.join(folder, file); |
| 76 | 76 | ||
| 77 | if (fs.existsSync(pathToThumbnail)) { | 77 | if (fs.existsSync(pathToThumbnail)) { |
| 78 | fs.rmSync(pathToThumbnail); | 78 | fs.unlinkSync(pathToThumbnail); |
| 79 | } | 79 | } |
| 80 | } | 80 | } |
| 81 | 81 | ||
| @@ -407,11 +407,15 @@ export function getTokenizerModel(requestModel) { | |||
| 407 | return 'o1'; | 407 | return 'o1'; |
| 408 | } | 408 | } |
| 409 | 409 | ||
| 410 | if (requestModel.includes('o3') || requestModel.includes('o4-mini')) { | ||
| 411 | return 'o1'; | ||
| 412 | } | ||
| 413 | |||
| 410 | if (requestModel.includes('gpt-4o') || requestModel.includes('chatgpt-4o-latest')) { | 414 | if (requestModel.includes('gpt-4o') || requestModel.includes('chatgpt-4o-latest')) { |
| 411 | return 'gpt-4o'; | 415 | return 'gpt-4o'; |
| 412 | } | 416 | } |
| 413 | 417 | ||
| 414 | if (requestModel.includes('gpt-4.5')) { | 418 | if (requestModel.includes('gpt-4.1') || requestModel.includes('gpt-4.5')) { |
| 415 | return 'gpt-4o'; | 419 | return 'gpt-4o'; |
| 416 | } | 420 | } |
| 417 | 421 | ||
| @@ -57,7 +57,7 @@ router.post('/delete', (request, response) => { | |||
| 57 | throw new Error(`World info file ${filename} doesn't exist.`); | 57 | throw new Error(`World info file ${filename} doesn't exist.`); |
| 58 | } | 58 | } |
| 59 | 59 | ||
| 60 | fs.rmSync(pathToWorldInfo); | 60 | fs.unlinkSync(pathToWorldInfo); |
| 61 | 61 | ||
| 62 | return response.sendStatus(200); | 62 | return response.sendStatus(200); |
| 63 | }); | 63 | }); |
| @@ -2,6 +2,7 @@ import fs from 'node:fs'; | |||
| 2 | import path from 'node:path'; | 2 | import path from 'node:path'; |
| 3 | import { fileURLToPath } from 'node:url'; | 3 | import { fileURLToPath } from 'node:url'; |
| 4 | import mime from 'mime-types'; | 4 | import mime from 'mime-types'; |
| 5 | import { serverDirectory } from './server-directory.js'; | ||
| 5 | 6 | ||
| 6 | const originalFetch = globalThis.fetch; | 7 | const originalFetch = globalThis.fetch; |
| 7 | 8 | ||
| @@ -67,10 +68,9 @@ globalThis.fetch = async (/** @type {string | URL | Request} */ request, /** @ty | |||
| 67 | } | 68 | } |
| 68 | const url = getRequestURL(request); | 69 | const url = getRequestURL(request); |
| 69 | const filePath = path.resolve(fileURLToPath(url)); | 70 | const filePath = path.resolve(fileURLToPath(url)); |
| 70 | const cwd = path.resolve(process.cwd()) + path.sep; | 71 | const isUnderServerDirectory = isPathUnderParent(serverDirectory, filePath); |
| 71 | const isUnderCwd = isPathUnderParent(cwd, filePath); | 72 | if (!isUnderServerDirectory) { |
| 72 | if (!isUnderCwd) { | 73 | throw new Error('Requested file path is outside of the server directory.'); |
| 73 | throw new Error('Requested file path is outside of the current working directory.'); | ||
| 74 | } | 74 | } |
| 75 | const parsedPath = path.parse(filePath); | 75 | const parsedPath = path.parse(filePath); |
| 76 | if (!ALLOWED_EXTENSIONS.includes(parsedPath.ext)) { | 76 | if (!ALLOWED_EXTENSIONS.includes(parsedPath.ext)) { |
| @@ -3,6 +3,15 @@ import { getConfigValue, tryParse } from './util.js'; | |||
| 3 | 3 | ||
| 4 | const PROMPT_PLACEHOLDER = getConfigValue('promptPlaceholder', 'Let\'s get started.'); | 4 | const PROMPT_PLACEHOLDER = getConfigValue('promptPlaceholder', 'Let\'s get started.'); |
| 5 | 5 | ||
| 6 | const REASONING_EFFORT = { | ||
| 7 | auto: 'auto', | ||
| 8 | low: 'low', | ||
| 9 | medium: 'medium', | ||
| 10 | high: 'high', | ||
| 11 | min: 'min', | ||
| 12 | max: 'max', | ||
| 13 | }; | ||
| 14 | |||
| 6 | /** | 15 | /** |
| 7 | * @typedef {object} PromptNames | 16 | * @typedef {object} PromptNames |
| 8 | * @property {string} charName Character name | 17 | * @property {string} charName Character name |
| @@ -342,59 +351,20 @@ export function convertCohereMessages(messages, names) { | |||
| 342 | } | 351 | } |
| 343 | }); | 352 | }); |
| 344 | 353 | ||
| 345 | // A prompt should end with a user/tool message | ||
| 346 | if (messages.length && !['user', 'tool'].includes(messages[messages.length - 1].role)) { | ||
| 347 | messages[messages.length - 1].role = 'user'; | ||
| 348 | } | ||
| 349 | |||
| 350 | return { chatHistory: messages }; | 354 | return { chatHistory: messages }; |
| 351 | } | 355 | } |
| 352 | 356 | ||
| 353 | /** | 357 | /** |
| 354 | * Convert a prompt from the ChatML objects to the format used by Google MakerSuite models. | 358 | * Convert a prompt from the ChatML objects to the format used by Google MakerSuite models. |
| 355 | * @param {object[]} messages Array of messages | 359 | * @param {object[]} messages Array of messages |
| 356 | * @param {string} model Model name | 360 | * @param {string} _model Model name |
| 357 | * @param {boolean} useSysPrompt Use system prompt | 361 | * @param {boolean} useSysPrompt Use system prompt |
| 358 | * @param {PromptNames} names Prompt names | 362 | * @param {PromptNames} names Prompt names |
| 359 | * @returns {{contents: *[], system_instruction: {parts: {text: string}}}} Prompt for Google MakerSuite models | 363 | * @returns {{contents: *[], system_instruction: {parts: {text: string}[]}}} Prompt for Google MakerSuite models |
| 360 | */ | 364 | */ |
| 361 | export function convertGooglePrompt(messages, model, useSysPrompt, names) { | 365 | export function convertGooglePrompt(messages, _model, useSysPrompt, names) { |
| 362 | const visionSupportedModels = [ | 366 | const sysPrompt = []; |
| 363 | 'gemini-2.5-pro-preview-03-25', | ||
| 364 | 'gemini-2.5-pro-exp-03-25', | ||
| 365 | 'gemini-2.0-pro-exp', | ||
| 366 | 'gemini-2.0-pro-exp-02-05', | ||
| 367 | 'gemini-2.0-flash-lite-preview', | ||
| 368 | 'gemini-2.0-flash-lite-preview-02-05', | ||
| 369 | 'gemini-2.0-flash', | ||
| 370 | 'gemini-2.0-flash-001', | ||
| 371 | 'gemini-2.0-flash-thinking-exp', | ||
| 372 | 'gemini-2.0-flash-thinking-exp-01-21', | ||
| 373 | 'gemini-2.0-flash-thinking-exp-1219', | ||
| 374 | 'gemini-2.0-flash-exp', | ||
| 375 | 'gemini-2.0-flash-exp-image-generation', | ||
| 376 | 'gemini-1.5-flash', | ||
| 377 | 'gemini-1.5-flash-latest', | ||
| 378 | 'gemini-1.5-flash-001', | ||
| 379 | 'gemini-1.5-flash-002', | ||
| 380 | 'gemini-1.5-flash-exp-0827', | ||
| 381 | 'gemini-1.5-flash-8b', | ||
| 382 | 'gemini-1.5-flash-8b-exp-0827', | ||
| 383 | 'gemini-1.5-flash-8b-exp-0924', | ||
| 384 | 'gemini-exp-1114', | ||
| 385 | 'gemini-exp-1121', | ||
| 386 | 'gemini-exp-1206', | ||
| 387 | 'gemini-1.5-pro', | ||
| 388 | 'gemini-1.5-pro-latest', | ||
| 389 | 'gemini-1.5-pro-001', | ||
| 390 | 'gemini-1.5-pro-002', | ||
| 391 | 'gemini-1.5-pro-exp-0801', | ||
| 392 | 'gemini-1.5-pro-exp-0827', | ||
| 393 | ]; | ||
| 394 | |||
| 395 | const isMultimodal = visionSupportedModels.includes(model); | ||
| 396 | 367 | ||
| 397 | let sys_prompt = ''; | ||
| 398 | if (useSysPrompt) { | 368 | if (useSysPrompt) { |
| 399 | while (messages.length > 1 && messages[0].role === 'system') { | 369 | while (messages.length > 1 && messages[0].role === 'system') { |
| 400 | // Append example names if not already done by the frontend (e.g. for group chats). | 370 | // Append example names if not already done by the frontend (e.g. for group chats). |
| @@ -408,12 +378,12 @@ export function convertGooglePrompt(messages, model, useSysPrompt, names) { | |||
| 408 | messages[0].content = `${names.charName}: ${messages[0].content}`; | 378 | messages[0].content = `${names.charName}: ${messages[0].content}`; |
| 409 | } | 379 | } |
| 410 | } | 380 | } |
| 411 | sys_prompt += `${messages[0].content}\n\n`; | 381 | sysPrompt.push(messages[0].content); |
| 412 | messages.shift(); | 382 | messages.shift(); |
| 413 | } | 383 | } |
| 414 | } | 384 | } |
| 415 | 385 | ||
| 416 | const system_instruction = { parts: [{ text: sys_prompt.trim() }]}; | 386 | const system_instruction = { parts: sysPrompt.map(text => ({ text })) }; |
| 417 | const toolNameMap = {}; | 387 | const toolNameMap = {}; |
| 418 | 388 | ||
| 419 | const contents = []; | 389 | const contents = []; |
| @@ -492,7 +462,7 @@ export function convertGooglePrompt(messages, model, useSysPrompt, names) { | |||
| 492 | 462 | ||
| 493 | toolNameMap[toolCall.id] = toolCall.function.name; | 463 | toolNameMap[toolCall.id] = toolCall.function.name; |
| 494 | }); | 464 | }); |
| 495 | } else if (part.type === 'image_url' && isMultimodal) { | 465 | } else if (part.type === 'image_url') { |
| 496 | const mimeType = part.image_url.url.split(';')[0].split(':')[1]; | 466 | const mimeType = part.image_url.url.split(';')[0].split(':')[1]; |
| 497 | const base64Data = part.image_url.url.split(',')[1]; | 467 | const base64Data = part.image_url.url.split(',')[1]; |
| 498 | parts.push({ | 468 | parts.push({ |
| @@ -508,7 +478,12 @@ export function convertGooglePrompt(messages, model, useSysPrompt, names) { | |||
| 508 | if (index > 0 && message.role === contents[contents.length - 1].role) { | 478 | if (index > 0 && message.role === contents[contents.length - 1].role) { |
| 509 | parts.forEach((part) => { | 479 | parts.forEach((part) => { |
| 510 | if (part.text) { | 480 | if (part.text) { |
| 511 | contents[contents.length - 1].parts[0].text += '\n\n' + part.text; | 481 | const textPart = contents[contents.length - 1].parts.find(p => typeof p.text === 'string'); |
| 482 | if (textPart) { | ||
| 483 | textPart.text += '\n\n' + part.text; | ||
| 484 | } else { | ||
| 485 | contents[contents.length - 1].parts.push(part); | ||
| 486 | } | ||
| 512 | } | 487 | } |
| 513 | if (part.inlineData || part.functionCall || part.functionResponse) { | 488 | if (part.inlineData || part.functionCall || part.functionResponse) { |
| 514 | contents[contents.length - 1].parts.push(part); | 489 | contents[contents.length - 1].parts.push(part); |
| @@ -680,6 +655,43 @@ export function convertMistralMessages(messages, names) { | |||
| 680 | } | 655 | } |
| 681 | 656 | ||
| 682 | /** | 657 | /** |
| 658 | * Convert a prompt from the messages objects to the format used by xAI. | ||
| 659 | * @param {object[]} messages Array of messages | ||
| 660 | * @param {PromptNames} names Prompt names | ||
| 661 | * @returns {object[]} Prompt for xAI | ||
| 662 | */ | ||
| 663 | export function convertXAIMessages(messages, names) { | ||
| 664 | if (!Array.isArray(messages)) { | ||
| 665 | return []; | ||
| 666 | } | ||
| 667 | |||
| 668 | messages.forEach(msg => { | ||
| 669 | if (!msg.name || msg.role === 'user') { | ||
| 670 | return; | ||
| 671 | } | ||
| 672 | |||
| 673 | const needsCharNamePrefix = [ | ||
| 674 | { role: 'assistant', condition: names.charName && !msg.content.startsWith(`${names.charName}: `) && !names.startsWithGroupName(msg.content) }, | ||
| 675 | { role: 'system', name: 'example_assistant', condition: names.charName && !msg.content.startsWith(`${names.charName}: `) && !names.startsWithGroupName(msg.content) }, | ||
| 676 | { role: 'system', name: 'example_user', condition: names.userName && !msg.content.startsWith(`${names.userName}: `) }, | ||
| 677 | ]; | ||
| 678 | |||
| 679 | const matchingRule = needsCharNamePrefix.find(rule => | ||
| 680 | msg.role === rule.role && (!rule.name || msg.name === rule.name) && rule.condition, | ||
| 681 | ); | ||
| 682 | |||
| 683 | if (matchingRule) { | ||
| 684 | const prefix = msg.role === 'system' && msg.name === 'example_user' ? names.userName : names.charName; | ||
| 685 | msg.content = `${prefix}: ${msg.content}`; | ||
| 686 | } | ||
| 687 | |||
| 688 | delete msg.name; | ||
| 689 | }); | ||
| 690 | |||
| 691 | return messages; | ||
| 692 | } | ||
| 693 | |||
| 694 | /** | ||
| 683 | * Merge messages with the same consecutive role, removing names if they exist. | 695 | * Merge messages with the same consecutive role, removing names if they exist. |
| 684 | * @param {any[]} messages Messages to merge | 696 | * @param {any[]} messages Messages to merge |
| 685 | * @param {PromptNames} names Prompt names | 697 | * @param {PromptNames} names Prompt names |
| @@ -901,25 +913,32 @@ export function cachingAtDepthForOpenRouterClaude(messages, cachingAtDepth) { | |||
| 901 | } | 913 | } |
| 902 | 914 | ||
| 903 | /** | 915 | /** |
| 904 | * Calculate the budget tokens for a given reasoning effort. | 916 | * Calculate the Claude budget tokens for a given reasoning effort. |
| 905 | * @param {number} maxTokens Maximum tokens | 917 | * @param {number} maxTokens Maximum tokens |
| 906 | * @param {string} reasoningEffort Reasoning effort | 918 | * @param {string} reasoningEffort Reasoning effort |
| 907 | * @param {boolean} stream If streaming is enabled | 919 | * @param {boolean} stream If streaming is enabled |
| 908 | * @returns {number} Budget tokens | 920 | * @returns {number} Budget tokens |
| 909 | */ | 921 | */ |
| 910 | export function calculateBudgetTokens(maxTokens, reasoningEffort, stream) { | 922 | export function calculateClaudeBudgetTokens(maxTokens, reasoningEffort, stream) { |
| 911 | let budgetTokens = 0; | 923 | let budgetTokens = 0; |
| 912 | 924 | ||
| 913 | switch (reasoningEffort) { | 925 | switch (reasoningEffort) { |
| 914 | case 'low': | 926 | case REASONING_EFFORT.min: |
| 927 | budgetTokens = 1024; | ||
| 928 | break; | ||
| 929 | case REASONING_EFFORT.low: | ||
| 915 | budgetTokens = Math.floor(maxTokens * 0.1); | 930 | budgetTokens = Math.floor(maxTokens * 0.1); |
| 916 | break; | 931 | break; |
| 917 | case 'medium': | 932 | case REASONING_EFFORT.auto: |
| 933 | case REASONING_EFFORT.medium: | ||
| 918 | budgetTokens = Math.floor(maxTokens * 0.25); | 934 | budgetTokens = Math.floor(maxTokens * 0.25); |
| 919 | break; | 935 | break; |
| 920 | case 'high': | 936 | case REASONING_EFFORT.high: |
| 921 | budgetTokens = Math.floor(maxTokens * 0.5); | 937 | budgetTokens = Math.floor(maxTokens * 0.5); |
| 922 | break; | 938 | break; |
| 939 | case REASONING_EFFORT.max: | ||
| 940 | budgetTokens = Math.floor(maxTokens * 0.95); | ||
| 941 | break; | ||
| 923 | } | 942 | } |
| 924 | 943 | ||
| 925 | budgetTokens = Math.max(budgetTokens, 1024); | 944 | budgetTokens = Math.max(budgetTokens, 1024); |
| @@ -930,3 +949,37 @@ export function calculateBudgetTokens(maxTokens, reasoningEffort, stream) { | |||
| 930 | 949 | ||
| 931 | return budgetTokens; | 950 | return budgetTokens; |
| 932 | } | 951 | } |
| 952 | |||
| 953 | /** | ||
| 954 | * Calculate the Google budget tokens for a given reasoning effort. | ||
| 955 | * @param {number} maxTokens Maximum tokens | ||
| 956 | * @param {string} reasoningEffort Reasoning effort | ||
| 957 | * @returns {number?} Budget tokens | ||
| 958 | */ | ||
| 959 | export function calculateGoogleBudgetTokens(maxTokens, reasoningEffort) { | ||
| 960 | let budgetTokens = 0; | ||
| 961 | |||
| 962 | switch (reasoningEffort) { | ||
| 963 | case REASONING_EFFORT.auto: | ||
| 964 | return null; | ||
| 965 | case REASONING_EFFORT.min: | ||
| 966 | budgetTokens = 0; | ||
| 967 | break; | ||
| 968 | case REASONING_EFFORT.low: | ||
| 969 | budgetTokens = Math.floor(maxTokens * 0.1); | ||
| 970 | break; | ||
| 971 | case REASONING_EFFORT.medium: | ||
| 972 | budgetTokens = Math.floor(maxTokens * 0.25); | ||
| 973 | break; | ||
| 974 | case REASONING_EFFORT.high: | ||
| 975 | budgetTokens = Math.floor(maxTokens * 0.5); | ||
| 976 | break; | ||
| 977 | case REASONING_EFFORT.max: | ||
| 978 | budgetTokens = maxTokens; | ||
| 979 | break; | ||
| 980 | } | ||
| 981 | |||
| 982 | budgetTokens = Math.min(budgetTokens, 24576); | ||
| 983 | |||
| 984 | return budgetTokens; | ||
| 985 | } | ||
| @@ -0,0 +1,3 @@ | |||
| 1 | import path from 'node:path'; | ||
| 2 | import { fileURLToPath } from 'node:url'; | ||
| 3 | export const serverDirectory = path.dirname(import.meta.dirname ?? path.dirname(fileURLToPath(import.meta.url))); | ||
| @@ -0,0 +1,386 @@ | |||
| 1 | // native node modules | ||
| 2 | import path from 'node:path'; | ||
| 3 | import util from 'node:util'; | ||
| 4 | import net from 'node:net'; | ||
| 5 | import dns from 'node:dns'; | ||
| 6 | import process from 'node:process'; | ||
| 7 | |||
| 8 | import cors from 'cors'; | ||
| 9 | import { csrfSync } from 'csrf-sync'; | ||
| 10 | import express from 'express'; | ||
| 11 | import compression from 'compression'; | ||
| 12 | import cookieSession from 'cookie-session'; | ||
| 13 | import multer from 'multer'; | ||
| 14 | import responseTime from 'response-time'; | ||
| 15 | import helmet from 'helmet'; | ||
| 16 | import bodyParser from 'body-parser'; | ||
| 17 | import open from 'open'; | ||
| 18 | |||
| 19 | // local library imports | ||
| 20 | import './fetch-patch.js'; | ||
| 21 | import { serverDirectory } from './server-directory.js'; | ||
| 22 | |||
| 23 | console.log(`Node version: ${process.version}. Running in ${process.env.NODE_ENV} environment. Server directory: ${serverDirectory}`); | ||
| 24 | |||
| 25 | // Work around a node v20.0.0, v20.1.0, and v20.2.0 bug. The issue was fixed in v20.3.0. | ||
| 26 | // https://github.com/nodejs/node/issues/47822#issuecomment-1564708870 | ||
| 27 | // Safe to remove once support for Node v20 is dropped. | ||
| 28 | if (process.versions && process.versions.node && process.versions.node.match(/20\.[0-2]\.0/)) { | ||
| 29 | // @ts-ignore | ||
| 30 | if (net.setDefaultAutoSelectFamily) net.setDefaultAutoSelectFamily(false); | ||
| 31 | } | ||
| 32 | |||
| 33 | import { serverEvents, EVENT_NAMES } from './server-events.js'; | ||
| 34 | import { loadPlugins } from './plugin-loader.js'; | ||
| 35 | import { | ||
| 36 | initUserStorage, | ||
| 37 | getCookieSecret, | ||
| 38 | getCookieSessionName, | ||
| 39 | ensurePublicDirectoriesExist, | ||
| 40 | getUserDirectoriesList, | ||
| 41 | migrateSystemPrompts, | ||
| 42 | migrateUserData, | ||
| 43 | requireLoginMiddleware, | ||
| 44 | setUserDataMiddleware, | ||
| 45 | shouldRedirectToLogin, | ||
| 46 | cleanUploads, | ||
| 47 | getSessionCookieAge, | ||
| 48 | verifySecuritySettings, | ||
| 49 | loginPageMiddleware, | ||
| 50 | } from './users.js'; | ||
| 51 | |||
| 52 | import getWebpackServeMiddleware from './middleware/webpack-serve.js'; | ||
| 53 | import basicAuthMiddleware from './middleware/basicAuth.js'; | ||
| 54 | import getWhitelistMiddleware from './middleware/whitelist.js'; | ||
| 55 | import accessLoggerMiddleware, { getAccessLogPath, migrateAccessLog } from './middleware/accessLogWriter.js'; | ||
| 56 | import multerMonkeyPatch from './middleware/multerMonkeyPatch.js'; | ||
| 57 | import initRequestProxy from './request-proxy.js'; | ||
| 58 | import getCacheBusterMiddleware from './middleware/cacheBuster.js'; | ||
| 59 | import corsProxyMiddleware from './middleware/corsProxy.js'; | ||
| 60 | import { | ||
| 61 | getVersion, | ||
| 62 | color, | ||
| 63 | removeColorFormatting, | ||
| 64 | getSeparator, | ||
| 65 | safeReadFileSync, | ||
| 66 | setupLogLevel, | ||
| 67 | setWindowTitle, | ||
| 68 | } from './util.js'; | ||
| 69 | import { UPLOADS_DIRECTORY } from './constants.js'; | ||
| 70 | import { ensureThumbnailCache } from './endpoints/thumbnails.js'; | ||
| 71 | |||
| 72 | // Routers | ||
| 73 | import { router as usersPublicRouter } from './endpoints/users-public.js'; | ||
| 74 | import { init as statsInit, onExit as statsOnExit } from './endpoints/stats.js'; | ||
| 75 | import { checkForNewContent } from './endpoints/content-manager.js'; | ||
| 76 | import { init as settingsInit } from './endpoints/settings.js'; | ||
| 77 | import { redirectDeprecatedEndpoints, ServerStartup, setupPrivateEndpoints } from './server-startup.js'; | ||
| 78 | import { diskCache } from './endpoints/characters.js'; | ||
| 79 | |||
| 80 | // Unrestrict console logs display limit | ||
| 81 | util.inspect.defaultOptions.maxArrayLength = null; | ||
| 82 | util.inspect.defaultOptions.maxStringLength = null; | ||
| 83 | util.inspect.defaultOptions.depth = 4; | ||
| 84 | |||
| 85 | const cliArgs = globalThis.COMMAND_LINE_ARGS; | ||
| 86 | |||
| 87 | if (!cliArgs.enableIPv6 && !cliArgs.enableIPv4) { | ||
| 88 | console.error('error: You can\'t disable all internet protocols: at least IPv6 or IPv4 must be enabled.'); | ||
| 89 | process.exit(1); | ||
| 90 | } | ||
| 91 | |||
| 92 | try { | ||
| 93 | if (cliArgs.dnsPreferIPv6) { | ||
| 94 | dns.setDefaultResultOrder('ipv6first'); | ||
| 95 | console.log('Preferring IPv6 for DNS resolution'); | ||
| 96 | } else { | ||
| 97 | dns.setDefaultResultOrder('ipv4first'); | ||
| 98 | console.log('Preferring IPv4 for DNS resolution'); | ||
| 99 | } | ||
| 100 | } catch (error) { | ||
| 101 | console.warn('Failed to set DNS resolution order. Possibly unsupported in this Node version.'); | ||
| 102 | } | ||
| 103 | |||
| 104 | const app = express(); | ||
| 105 | app.use(helmet({ | ||
| 106 | contentSecurityPolicy: false, | ||
| 107 | })); | ||
| 108 | app.use(compression()); | ||
| 109 | app.use(responseTime()); | ||
| 110 | |||
| 111 | app.use(bodyParser.json({ limit: '200mb' })); | ||
| 112 | app.use(bodyParser.urlencoded({ extended: true, limit: '200mb' })); | ||
| 113 | |||
| 114 | // CORS Settings // | ||
| 115 | const CORS = cors({ | ||
| 116 | origin: 'null', | ||
| 117 | methods: ['OPTIONS'], | ||
| 118 | }); | ||
| 119 | |||
| 120 | app.use(CORS); | ||
| 121 | |||
| 122 | if (cliArgs.listen && cliArgs.basicAuthMode) { | ||
| 123 | app.use(basicAuthMiddleware); | ||
| 124 | } | ||
| 125 | |||
| 126 | if (cliArgs.whitelistMode) { | ||
| 127 | const whitelistMiddleware = await getWhitelistMiddleware(); | ||
| 128 | app.use(whitelistMiddleware); | ||
| 129 | } | ||
| 130 | |||
| 131 | if (cliArgs.listen) { | ||
| 132 | app.use(accessLoggerMiddleware()); | ||
| 133 | } | ||
| 134 | |||
| 135 | if (cliArgs.enableCorsProxy) { | ||
| 136 | app.use('/proxy/:url(*)', corsProxyMiddleware); | ||
| 137 | } else { | ||
| 138 | app.use('/proxy/:url(*)', async (_, res) => { | ||
| 139 | const message = 'CORS proxy is disabled. Enable it in config.yaml or use the --corsProxy flag.'; | ||
| 140 | console.log(message); | ||
| 141 | res.status(404).send(message); | ||
| 142 | }); | ||
| 143 | } | ||
| 144 | |||
| 145 | app.use(cookieSession({ | ||
| 146 | name: getCookieSessionName(), | ||
| 147 | sameSite: 'lax', | ||
| 148 | httpOnly: true, | ||
| 149 | maxAge: getSessionCookieAge(), | ||
| 150 | secret: getCookieSecret(globalThis.DATA_ROOT), | ||
| 151 | })); | ||
| 152 | |||
| 153 | app.use(setUserDataMiddleware); | ||
| 154 | |||
| 155 | // CSRF Protection // | ||
| 156 | if (!cliArgs.disableCsrf) { | ||
| 157 | const csrfSyncProtection = csrfSync({ | ||
| 158 | getTokenFromState: (req) => { | ||
| 159 | if (!req.session) { | ||
| 160 | console.error('(CSRF error) getTokenFromState: Session object not initialized'); | ||
| 161 | return; | ||
| 162 | } | ||
| 163 | return req.session.csrfToken; | ||
| 164 | }, | ||
| 165 | getTokenFromRequest: (req) => { | ||
| 166 | return req.headers['x-csrf-token']?.toString(); | ||
| 167 | }, | ||
| 168 | storeTokenInState: (req, token) => { | ||
| 169 | if (!req.session) { | ||
| 170 | console.error('(CSRF error) storeTokenInState: Session object not initialized'); | ||
| 171 | return; | ||
| 172 | } | ||
| 173 | req.session.csrfToken = token; | ||
| 174 | }, | ||
| 175 | size: 32, | ||
| 176 | }); | ||
| 177 | |||
| 178 | app.get('/csrf-token', (req, res) => { | ||
| 179 | res.json({ | ||
| 180 | 'token': csrfSyncProtection.generateToken(req), | ||
| 181 | }); | ||
| 182 | }); | ||
| 183 | |||
| 184 | // Customize the error message | ||
| 185 | csrfSyncProtection.invalidCsrfTokenError.message = color.red('Invalid CSRF token. Please refresh the page and try again.'); | ||
| 186 | csrfSyncProtection.invalidCsrfTokenError.stack = undefined; | ||
| 187 | |||
| 188 | app.use(csrfSyncProtection.csrfSynchronisedProtection); | ||
| 189 | } else { | ||
| 190 | console.warn('\nCSRF protection is disabled. This will make your server vulnerable to CSRF attacks.\n'); | ||
| 191 | app.get('/csrf-token', (req, res) => { | ||
| 192 | res.json({ | ||
| 193 | 'token': 'disabled', | ||
| 194 | }); | ||
| 195 | }); | ||
| 196 | } | ||
| 197 | |||
| 198 | // Static files | ||
| 199 | // Host index page | ||
| 200 | app.get('/', getCacheBusterMiddleware(), (request, response) => { | ||
| 201 | if (shouldRedirectToLogin(request)) { | ||
| 202 | const query = request.url.split('?')[1]; | ||
| 203 | const redirectUrl = query ? `/login?${query}` : '/login'; | ||
| 204 | return response.redirect(redirectUrl); | ||
| 205 | } | ||
| 206 | |||
| 207 | return response.sendFile('index.html', { root: path.join(serverDirectory, 'public') }); | ||
| 208 | }); | ||
| 209 | |||
| 210 | // Callback endpoint for OAuth PKCE flows (e.g. OpenRouter) | ||
| 211 | app.get('/callback/:source?', (request, response) => { | ||
| 212 | const source = request.params.source; | ||
| 213 | const query = request.url.split('?')[1]; | ||
| 214 | const searchParams = new URLSearchParams(); | ||
| 215 | source && searchParams.set('source', source); | ||
| 216 | query && searchParams.set('query', query); | ||
| 217 | const path = `/?${searchParams.toString()}`; | ||
| 218 | return response.redirect(307, path); | ||
| 219 | }); | ||
| 220 | |||
| 221 | // Host login page | ||
| 222 | app.get('/login', loginPageMiddleware); | ||
| 223 | |||
| 224 | // Host frontend assets | ||
| 225 | const webpackMiddleware = getWebpackServeMiddleware(); | ||
| 226 | app.use(webpackMiddleware); | ||
| 227 | app.use(express.static(path.join(serverDirectory, 'public'), {})); | ||
| 228 | |||
| 229 | // Public API | ||
| 230 | app.use('/api/users', usersPublicRouter); | ||
| 231 | |||
| 232 | // Everything below this line requires authentication | ||
| 233 | app.use(requireLoginMiddleware); | ||
| 234 | app.get('/api/ping', (request, response) => { | ||
| 235 | if (request.query.extend && request.session) { | ||
| 236 | request.session.touch = Date.now(); | ||
| 237 | } | ||
| 238 | |||
| 239 | response.sendStatus(204); | ||
| 240 | }); | ||
| 241 | |||
| 242 | // File uploads | ||
| 243 | const uploadsPath = path.join(cliArgs.dataRoot, UPLOADS_DIRECTORY); | ||
| 244 | app.use(multer({ dest: uploadsPath, limits: { fieldSize: 10 * 1024 * 1024 } }).single('avatar')); | ||
| 245 | app.use(multerMonkeyPatch); | ||
| 246 | |||
| 247 | app.get('/version', async function (_, response) { | ||
| 248 | const data = await getVersion(); | ||
| 249 | response.send(data); | ||
| 250 | }); | ||
| 251 | |||
| 252 | redirectDeprecatedEndpoints(app); | ||
| 253 | setupPrivateEndpoints(app); | ||
| 254 | |||
| 255 | /** | ||
| 256 | * Tasks that need to be run before the server starts listening. | ||
| 257 | * @returns {Promise<void>} | ||
| 258 | */ | ||
| 259 | async function preSetupTasks() { | ||
| 260 | const version = await getVersion(); | ||
| 261 | |||
| 262 | // Print formatted header | ||
| 263 | console.log(); | ||
| 264 | console.log(`SillyTavern ${version.pkgVersion}`); | ||
| 265 | if (version.gitBranch) { | ||
| 266 | console.log(`Running '${version.gitBranch}' (${version.gitRevision}) - ${version.commitDate}`); | ||
| 267 | if (!version.isLatest && ['staging', 'release'].includes(version.gitBranch)) { | ||
| 268 | console.log('INFO: Currently not on the latest commit.'); | ||
| 269 | console.log(' Run \'git pull\' to update. If you have any merge conflicts, run \'git reset --hard\' and \'git pull\' to reset your branch.'); | ||
| 270 | } | ||
| 271 | } | ||
| 272 | console.log(); | ||
| 273 | |||
| 274 | const directories = await getUserDirectoriesList(); | ||
| 275 | await checkForNewContent(directories); | ||
| 276 | await ensureThumbnailCache(directories); | ||
| 277 | await diskCache.verify(directories); | ||
| 278 | cleanUploads(); | ||
| 279 | migrateAccessLog(); | ||
| 280 | |||
| 281 | await settingsInit(); | ||
| 282 | await statsInit(); | ||
| 283 | |||
| 284 | const pluginsDirectory = path.join(serverDirectory, 'plugins'); | ||
| 285 | const cleanupPlugins = await loadPlugins(app, pluginsDirectory); | ||
| 286 | const consoleTitle = process.title; | ||
| 287 | |||
| 288 | let isExiting = false; | ||
| 289 | const exitProcess = async () => { | ||
| 290 | if (isExiting) return; | ||
| 291 | isExiting = true; | ||
| 292 | await statsOnExit(); | ||
| 293 | if (typeof cleanupPlugins === 'function') { | ||
| 294 | await cleanupPlugins(); | ||
| 295 | } | ||
| 296 | diskCache.dispose(); | ||
| 297 | setWindowTitle(consoleTitle); | ||
| 298 | process.exit(); | ||
| 299 | }; | ||
| 300 | |||
| 301 | // Set up event listeners for a graceful shutdown | ||
| 302 | process.on('SIGINT', exitProcess); | ||
| 303 | process.on('SIGTERM', exitProcess); | ||
| 304 | process.on('uncaughtException', (err) => { | ||
| 305 | console.error('Uncaught exception:', err); | ||
| 306 | exitProcess(); | ||
| 307 | }); | ||
| 308 | |||
| 309 | // Add request proxy. | ||
| 310 | initRequestProxy({ enabled: cliArgs.requestProxyEnabled, url: cliArgs.requestProxyUrl, bypass: cliArgs.requestProxyBypass }); | ||
| 311 | |||
| 312 | // Wait for frontend libs to compile | ||
| 313 | await webpackMiddleware.runWebpackCompiler(); | ||
| 314 | } | ||
| 315 | |||
| 316 | /** | ||
| 317 | * Tasks that need to be run after the server starts listening. | ||
| 318 | * @param {import('./server-startup.js').ServerStartupResult} result The result of the server startup | ||
| 319 | * @returns {Promise<void>} | ||
| 320 | */ | ||
| 321 | async function postSetupTasks(result) { | ||
| 322 | const autorunHostname = await cliArgs.getAutorunHostname(result); | ||
| 323 | const autorunUrl = cliArgs.getAutorunUrl(autorunHostname); | ||
| 324 | |||
| 325 | if (cliArgs.autorun) { | ||
| 326 | try { | ||
| 327 | console.log('Launching in a browser...'); | ||
| 328 | await open(autorunUrl.toString()); | ||
| 329 | } catch (error) { | ||
| 330 | console.error('Failed to launch the browser. Open the URL manually.'); | ||
| 331 | } | ||
| 332 | } | ||
| 333 | |||
| 334 | setWindowTitle('SillyTavern WebServer'); | ||
| 335 | |||
| 336 | let logListen = 'SillyTavern is listening on'; | ||
| 337 | |||
| 338 | if (result.useIPv6 && !result.v6Failed) { | ||
| 339 | logListen += color.green( | ||
| 340 | ' IPv6: ' + cliArgs.getIPv6ListenUrl().host, | ||
| 341 | ); | ||
| 342 | } | ||
| 343 | |||
| 344 | if (result.useIPv4 && !result.v4Failed) { | ||
| 345 | logListen += color.green( | ||
| 346 | ' IPv4: ' + cliArgs.getIPv4ListenUrl().host, | ||
| 347 | ); | ||
| 348 | } | ||
| 349 | |||
| 350 | const goToLog = 'Go to: ' + color.blue(autorunUrl) + ' to open SillyTavern'; | ||
| 351 | const plainGoToLog = removeColorFormatting(goToLog); | ||
| 352 | |||
| 353 | console.log(logListen); | ||
| 354 | if (cliArgs.listen) { | ||
| 355 | console.log(); | ||
| 356 | console.log('To limit connections to internal localhost only ([::1] or 127.0.0.1), change the setting in config.yaml to "listen: false".'); | ||
| 357 | console.log('Check the "access.log" file in the data directory to inspect incoming connections:', color.green(getAccessLogPath())); | ||
| 358 | } | ||
| 359 | console.log('\n' + getSeparator(plainGoToLog.length) + '\n'); | ||
| 360 | console.log(goToLog); | ||
| 361 | console.log('\n' + getSeparator(plainGoToLog.length) + '\n'); | ||
| 362 | |||
| 363 | setupLogLevel(); | ||
| 364 | serverEvents.emit(EVENT_NAMES.SERVER_STARTED, { url: autorunUrl }); | ||
| 365 | } | ||
| 366 | |||
| 367 | /** | ||
| 368 | * Registers a not-found error response if a not-found error page exists. Should only be called after all other middlewares have been registered. | ||
| 369 | */ | ||
| 370 | function apply404Middleware() { | ||
| 371 | const notFoundWebpage = safeReadFileSync(path.join(serverDirectory, 'public/error/url-not-found.html')) ?? ''; | ||
| 372 | app.use((req, res) => { | ||
| 373 | res.status(404).send(notFoundWebpage); | ||
| 374 | }); | ||
| 375 | } | ||
| 376 | |||
| 377 | // User storage module needs to be initialized before starting the server | ||
| 378 | initUserStorage(globalThis.DATA_ROOT) | ||
| 379 | .then(ensurePublicDirectoriesExist) | ||
| 380 | .then(migrateUserData) | ||
| 381 | .then(migrateSystemPrompts) | ||
| 382 | .then(verifySecuritySettings) | ||
| 383 | .then(preSetupTasks) | ||
| 384 | .then(apply404Middleware) | ||
| 385 | .then(() => new ServerStartup(app, cliArgs).start()) | ||
| 386 | .then(postSetupTasks); | ||
| @@ -5,6 +5,7 @@ import { Buffer } from 'node:buffer'; | |||
| 5 | 5 | ||
| 6 | import { pipeline, env, RawImage } from 'sillytavern-transformers'; | 6 | import { pipeline, env, RawImage } from 'sillytavern-transformers'; |
| 7 | import { getConfigValue } from './util.js'; | 7 | import { getConfigValue } from './util.js'; |
| 8 | import { serverDirectory } from './server-directory.js'; | ||
| 8 | 9 | ||
| 9 | configureTransformers(); | 10 | configureTransformers(); |
| 10 | 11 | ||
| @@ -12,7 +13,7 @@ function configureTransformers() { | |||
| 12 | // Limit the number of threads to 1 to avoid issues on Android | 13 | // Limit the number of threads to 1 to avoid issues on Android |
| 13 | env.backends.onnx.wasm.numThreads = 1; | 14 | env.backends.onnx.wasm.numThreads = 1; |
| 14 | // Use WASM from a local folder to avoid CDN connections | 15 | // Use WASM from a local folder to avoid CDN connections |
| 15 | env.backends.onnx.wasm.wasmPaths = path.join(process.cwd(), 'dist') + path.sep; | 16 | env.backends.onnx.wasm.wasmPaths = path.join(serverDirectory, 'node_modules', 'sillytavern-transformers', 'dist') + path.sep; |
| 16 | } | 17 | } |
| 17 | 18 | ||
| 18 | const tasks = { | 19 | const tasks = { |
| @@ -18,6 +18,7 @@ import { USER_DIRECTORY_TEMPLATE, DEFAULT_USER, PUBLIC_DIRECTORIES, SETTINGS_FIL | |||
| 18 | import { getConfigValue, color, delay, generateTimestamp } from './util.js'; | 18 | import { getConfigValue, color, delay, generateTimestamp } from './util.js'; |
| 19 | import { readSecret, writeSecret } from './endpoints/secrets.js'; | 19 | import { readSecret, writeSecret } from './endpoints/secrets.js'; |
| 20 | import { getContentOfType } from './endpoints/content-manager.js'; | 20 | import { getContentOfType } from './endpoints/content-manager.js'; |
| 21 | import { serverDirectory } from './server-directory.js'; | ||
| 21 | 22 | ||
| 22 | export const KEY_PREFIX = 'user:'; | 23 | export const KEY_PREFIX = 'user:'; |
| 23 | const AVATAR_PREFIX = 'avatar:'; | 24 | const AVATAR_PREFIX = 'avatar:'; |
| @@ -905,7 +906,7 @@ export async function loginPageMiddleware(request, response) { | |||
| 905 | console.error('Error during auto-login:', error); | 906 | console.error('Error during auto-login:', error); |
| 906 | } | 907 | } |
| 907 | 908 | ||
| 908 | return response.sendFile('login.html', { root: path.join(process.cwd(), 'public') }); | 909 | return response.sendFile('login.html', { root: path.join(serverDirectory, 'public') }); |
| 909 | } | 910 | } |
| 910 | 911 | ||
| 911 | /** | 912 | /** |
| @@ -15,13 +15,15 @@ import yauzl from 'yauzl'; | |||
| 15 | import mime from 'mime-types'; | 15 | import mime from 'mime-types'; |
| 16 | import { default as simpleGit } from 'simple-git'; | 16 | import { default as simpleGit } from 'simple-git'; |
| 17 | import chalk from 'chalk'; | 17 | import chalk from 'chalk'; |
| 18 | import { LOG_LEVELS } from './constants.js'; | ||
| 19 | import bytes from 'bytes'; | 18 | import bytes from 'bytes'; |
| 19 | import { LOG_LEVELS } from './constants.js'; | ||
| 20 | import { serverDirectory } from './server-directory.js'; | ||
| 20 | 21 | ||
| 21 | /** | 22 | /** |
| 22 | * Parsed config object. | 23 | * Parsed config object. |
| 23 | */ | 24 | */ |
| 24 | let CACHED_CONFIG = null; | 25 | let CACHED_CONFIG = null; |
| 26 | let CONFIG_PATH = null; | ||
| 25 | 27 | ||
| 26 | /** | 28 | /** |
| 27 | * Converts a configuration key to an environment variable key. | 29 | * Converts a configuration key to an environment variable key. |
| @@ -32,22 +34,37 @@ let CACHED_CONFIG = null; | |||
| 32 | export const keyToEnv = (key) => 'SILLYTAVERN_' + String(key).toUpperCase().replace(/\./g, '_'); | 34 | export const keyToEnv = (key) => 'SILLYTAVERN_' + String(key).toUpperCase().replace(/\./g, '_'); |
| 33 | 35 | ||
| 34 | /** | 36 | /** |
| 37 | * Set the config file path. | ||
| 38 | * @param {string} configFilePath Path to the config file | ||
| 39 | */ | ||
| 40 | export function setConfigFilePath(configFilePath) { | ||
| 41 | if (CONFIG_PATH !== null) { | ||
| 42 | console.error(color.red('Config file path already set. Please restart the server to change the config file path.')); | ||
| 43 | } | ||
| 44 | CONFIG_PATH = path.resolve(configFilePath); | ||
| 45 | } | ||
| 46 | |||
| 47 | /** | ||
| 35 | * Returns the config object from the config.yaml file. | 48 | * Returns the config object from the config.yaml file. |
| 36 | * @returns {object} Config object | 49 | * @returns {object} Config object |
| 37 | */ | 50 | */ |
| 38 | export function getConfig() { | 51 | export function getConfig() { |
| 52 | if (CONFIG_PATH === null) { | ||
| 53 | console.trace(); | ||
| 54 | console.error(color.red('No config file path set. Please set the config file path using setConfigFilePath().')); | ||
| 55 | process.exit(1); | ||
| 56 | } | ||
| 39 | if (CACHED_CONFIG) { | 57 | if (CACHED_CONFIG) { |
| 40 | return CACHED_CONFIG; | 58 | return CACHED_CONFIG; |
| 41 | } | 59 | } |
| 42 | 60 | if (!fs.existsSync(CONFIG_PATH)) { | |
| 43 | if (!fs.existsSync('./config.yaml')) { | ||
| 44 | console.error(color.red('No config file found. Please create a config.yaml file. The default config file can be found in the /default folder.')); | 61 | console.error(color.red('No config file found. Please create a config.yaml file. The default config file can be found in the /default folder.')); |
| 45 | console.error(color.red('The program will now exit.')); | 62 | console.error(color.red('The program will now exit.')); |
| 46 | process.exit(1); | 63 | process.exit(1); |
| 47 | } | 64 | } |
| 48 | 65 | ||
| 49 | try { | 66 | try { |
| 50 | const config = yaml.parse(fs.readFileSync(path.join(process.cwd(), './config.yaml'), 'utf8')); | 67 | const config = yaml.parse(fs.readFileSync(CONFIG_PATH, 'utf8')); |
| 51 | CACHED_CONFIG = config; | 68 | CACHED_CONFIG = config; |
| 52 | return config; | 69 | return config; |
| 53 | } catch (error) { | 70 | } catch (error) { |
| @@ -121,20 +138,19 @@ export async function getVersion() { | |||
| 121 | 138 | ||
| 122 | try { | 139 | try { |
| 123 | const require = createRequire(import.meta.url); | 140 | const require = createRequire(import.meta.url); |
| 124 | const pkgJson = require(path.join(process.cwd(), './package.json')); | 141 | const pkgJson = require(path.join(serverDirectory, './package.json')); |
| 125 | pkgVersion = pkgJson.version; | 142 | pkgVersion = pkgJson.version; |
| 126 | if (commandExistsSync('git')) { | 143 | if (commandExistsSync('git')) { |
| 127 | const git = simpleGit(); | 144 | const git = simpleGit({ baseDir: serverDirectory }); |
| 128 | const cwd = process.cwd(); | 145 | gitRevision = await git.revparse(['--short', 'HEAD']); |
| 129 | gitRevision = await git.cwd(cwd).revparse(['--short', 'HEAD']); | 146 | gitBranch = await git.revparse(['--abbrev-ref', 'HEAD']); |
| 130 | gitBranch = await git.cwd(cwd).revparse(['--abbrev-ref', 'HEAD']); | 147 | commitDate = await git.show(['-s', '--format=%ci', gitRevision]); |
| 131 | commitDate = await git.cwd(cwd).show(['-s', '--format=%ci', gitRevision]); | ||
| 132 | 148 | ||
| 133 | const trackingBranch = await git.cwd(cwd).revparse(['--abbrev-ref', '@{u}']); | 149 | const trackingBranch = await git.revparse(['--abbrev-ref', '@{u}']); |
| 134 | 150 | ||
| 135 | // Might fail, but exception is caught. Just don't run anything relevant after in this block... | 151 | // Might fail, but exception is caught. Just don't run anything relevant after in this block... |
| 136 | const localLatest = await git.cwd(cwd).revparse(['HEAD']); | 152 | const localLatest = await git.revparse(['HEAD']); |
| 137 | const remoteLatest = await git.cwd(cwd).revparse([trackingBranch]); | 153 | const remoteLatest = await git.revparse([trackingBranch]); |
| 138 | isLatest = localLatest === remoteLatest; | 154 | isLatest = localLatest === remoteLatest; |
| 139 | } | 155 | } |
| 140 | } | 156 | } |
| @@ -419,7 +435,7 @@ export function removeOldBackups(directory, prefix, limit = null) { | |||
| 419 | break; | 435 | break; |
| 420 | } | 436 | } |
| 421 | 437 | ||
| 422 | fs.rmSync(oldest); | 438 | fs.unlinkSync(oldest); |
| 423 | } | 439 | } |
| 424 | } | 440 | } |
| 425 | } | 441 | } |
| @@ -1071,3 +1087,39 @@ export function mutateJsonString(jsonString, mutation) { | |||
| 1071 | return jsonString; | 1087 | return jsonString; |
| 1072 | } | 1088 | } |
| 1073 | } | 1089 | } |
| 1090 | |||
| 1091 | /** | ||
| 1092 | * Sets the permissions of a file or directory to be writable. | ||
| 1093 | * @param {string} targetPath Path to the file or directory | ||
| 1094 | */ | ||
| 1095 | export function setPermissionsSync(targetPath) { | ||
| 1096 | /** | ||
| 1097 | * Appends writable permission to the file mode. | ||
| 1098 | * @param {string} filePath Path to the file | ||
| 1099 | * @param {fs.Stats} stats File stats | ||
| 1100 | */ | ||
| 1101 | function appendWritablePermission(filePath, stats) { | ||
| 1102 | const currentMode = stats.mode; | ||
| 1103 | const newMode = currentMode | 0o200; | ||
| 1104 | if (newMode != currentMode) { | ||
| 1105 | fs.chmodSync(filePath, newMode); | ||
| 1106 | } | ||
| 1107 | } | ||
| 1108 | |||
| 1109 | try { | ||
| 1110 | const stats = fs.statSync(targetPath); | ||
| 1111 | |||
| 1112 | if (stats.isDirectory()) { | ||
| 1113 | appendWritablePermission(targetPath, stats); | ||
| 1114 | const files = fs.readdirSync(targetPath); | ||
| 1115 | |||
| 1116 | files.forEach((file) => { | ||
| 1117 | setPermissionsSync(path.join(targetPath, file)); | ||
| 1118 | }); | ||
| 1119 | } else { | ||
| 1120 | appendWritablePermission(targetPath, stats); | ||
| 1121 | } | ||
| 1122 | } catch (error) { | ||
| 1123 | console.error(`Error setting write permissions for ${targetPath}:`, error); | ||
| 1124 | } | ||
| 1125 | } | ||
| @@ -1038,20 +1038,19 @@ | |||
| 1038 | } | 1038 | } |
| 1039 | }, | 1039 | }, |
| 1040 | "node_modules/@puppeteer/browsers": { | 1040 | "node_modules/@puppeteer/browsers": { |
| 1041 | "version": "2.2.3", | 1041 | "version": "2.10.2", |
| 1042 | "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.2.3.tgz", | 1042 | "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.2.tgz", |
| 1043 | "integrity": "sha512-bJ0UBsk0ESOs6RFcLXOt99a3yTDcOKlzfjad+rhFwdaG1Lu/Wzq58GHYCDTlZ9z6mldf4g+NTb+TXEfe0PpnsQ==", | 1043 | "integrity": "sha512-i4Ez+s9oRWQbNjtI/3+jxr7OH508mjAKvza0ekPJem0ZtmsYHP3B5dq62+IaBHKaGCOuqJxXzvFLUhJvQ6jtsQ==", |
| 1044 | "license": "Apache-2.0", | 1044 | "license": "Apache-2.0", |
| 1045 | "peer": true, | 1045 | "peer": true, |
| 1046 | "dependencies": { | 1046 | "dependencies": { |
| 1047 | "debug": "4.3.4", | 1047 | "debug": "^4.4.0", |
| 1048 | "extract-zip": "2.0.1", | 1048 | "extract-zip": "^2.0.1", |
| 1049 | "progress": "2.0.3", | 1049 | "progress": "^2.0.3", |
| 1050 | "proxy-agent": "6.4.0", | 1050 | "proxy-agent": "^6.5.0", |
| 1051 | "semver": "7.6.0", | 1051 | "semver": "^7.7.1", |
| 1052 | "tar-fs": "3.0.5", | 1052 | "tar-fs": "^3.0.8", |
| 1053 | "unbzip2-stream": "1.4.3", | 1053 | "yargs": "^17.7.2" |
| 1054 | "yargs": "17.7.2" | ||
| 1055 | }, | 1054 | }, |
| 1056 | "bin": { | 1055 | "bin": { |
| 1057 | "browsers": "lib/cjs/main-cli.js" | 1056 | "browsers": "lib/cjs/main-cli.js" |
| @@ -1060,46 +1059,12 @@ | |||
| 1060 | "node": ">=18" | 1059 | "node": ">=18" |
| 1061 | } | 1060 | } |
| 1062 | }, | 1061 | }, |
| 1063 | "node_modules/@puppeteer/browsers/node_modules/debug": { | ||
| 1064 | "version": "4.3.4", | ||
| 1065 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", | ||
| 1066 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", | ||
| 1067 | "license": "MIT", | ||
| 1068 | "peer": true, | ||
| 1069 | "dependencies": { | ||
| 1070 | "ms": "2.1.2" | ||
| 1071 | }, | ||
| 1072 | "engines": { | ||
| 1073 | "node": ">=6.0" | ||
| 1074 | }, | ||
| 1075 | "peerDependenciesMeta": { | ||
| 1076 | "supports-color": { | ||
| 1077 | "optional": true | ||
| 1078 | } | ||
| 1079 | } | ||
| 1080 | }, | ||
| 1081 | "node_modules/@puppeteer/browsers/node_modules/lru-cache": { | ||
| 1082 | "version": "6.0.0", | ||
| 1083 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", | ||
| 1084 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", | ||
| 1085 | "license": "ISC", | ||
| 1086 | "peer": true, | ||
| 1087 | "dependencies": { | ||
| 1088 | "yallist": "^4.0.0" | ||
| 1089 | }, | ||
| 1090 | "engines": { | ||
| 1091 | "node": ">=10" | ||
| 1092 | } | ||
| 1093 | }, | ||
| 1094 | "node_modules/@puppeteer/browsers/node_modules/semver": { | 1062 | "node_modules/@puppeteer/browsers/node_modules/semver": { |
| 1095 | "version": "7.6.0", | 1063 | "version": "7.7.1", |
| 1096 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", | 1064 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", |
| 1097 | "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", | 1065 | "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", |
| 1098 | "license": "ISC", | 1066 | "license": "ISC", |
| 1099 | "peer": true, | 1067 | "peer": true, |
| 1100 | "dependencies": { | ||
| 1101 | "lru-cache": "^6.0.0" | ||
| 1102 | }, | ||
| 1103 | "bin": { | 1068 | "bin": { |
| 1104 | "semver": "bin/semver.js" | 1069 | "semver": "bin/semver.js" |
| 1105 | }, | 1070 | }, |
| @@ -1107,13 +1072,6 @@ | |||
| 1107 | "node": ">=10" | 1072 | "node": ">=10" |
| 1108 | } | 1073 | } |
| 1109 | }, | 1074 | }, |
| 1110 | "node_modules/@puppeteer/browsers/node_modules/yallist": { | ||
| 1111 | "version": "4.0.0", | ||
| 1112 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", | ||
| 1113 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", | ||
| 1114 | "license": "ISC", | ||
| 1115 | "peer": true | ||
| 1116 | }, | ||
| 1117 | "node_modules/@sideway/address": { | 1075 | "node_modules/@sideway/address": { |
| 1118 | "version": "4.1.5", | 1076 | "version": "4.1.5", |
| 1119 | "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", | 1077 | "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", |
| @@ -1452,14 +1410,11 @@ | |||
| 1452 | } | 1410 | } |
| 1453 | }, | 1411 | }, |
| 1454 | "node_modules/agent-base": { | 1412 | "node_modules/agent-base": { |
| 1455 | "version": "7.1.1", | 1413 | "version": "7.1.3", |
| 1456 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", | 1414 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", |
| 1457 | "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", | 1415 | "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", |
| 1458 | "license": "MIT", | 1416 | "license": "MIT", |
| 1459 | "peer": true, | 1417 | "peer": true, |
| 1460 | "dependencies": { | ||
| 1461 | "debug": "^4.3.4" | ||
| 1462 | }, | ||
| 1463 | "engines": { | 1418 | "engines": { |
| 1464 | "node": ">= 14" | 1419 | "node": ">= 14" |
| 1465 | } | 1420 | } |
| @@ -1581,9 +1536,9 @@ | |||
| 1581 | } | 1536 | } |
| 1582 | }, | 1537 | }, |
| 1583 | "node_modules/b4a": { | 1538 | "node_modules/b4a": { |
| 1584 | "version": "1.6.6", | 1539 | "version": "1.6.7", |
| 1585 | "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", | 1540 | "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", |
| 1586 | "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==", | 1541 | "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==", |
| 1587 | "license": "Apache-2.0", | 1542 | "license": "Apache-2.0", |
| 1588 | "peer": true | 1543 | "peer": true |
| 1589 | }, | 1544 | }, |
| @@ -1701,76 +1656,81 @@ | |||
| 1701 | "license": "MIT" | 1656 | "license": "MIT" |
| 1702 | }, | 1657 | }, |
| 1703 | "node_modules/bare-events": { | 1658 | "node_modules/bare-events": { |
| 1704 | "version": "2.4.2", | 1659 | "version": "2.5.4", |
| 1705 | "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.4.2.tgz", | 1660 | "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.4.tgz", |
| 1706 | "integrity": "sha512-qMKFd2qG/36aA4GwvKq8MxnPgCQAmBWmSyLWsJcbn8v03wvIPQ/hG1Ms8bPzndZxMDoHpxez5VOS+gC9Yi24/Q==", | 1661 | "integrity": "sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==", |
| 1707 | "license": "Apache-2.0", | 1662 | "license": "Apache-2.0", |
| 1708 | "optional": true, | 1663 | "optional": true, |
| 1709 | "peer": true | 1664 | "peer": true |
| 1710 | }, | 1665 | }, |
| 1711 | "node_modules/bare-fs": { | 1666 | "node_modules/bare-fs": { |
| 1712 | "version": "2.3.1", | 1667 | "version": "4.1.3", |
| 1713 | "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.1.tgz", | 1668 | "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.1.3.tgz", |
| 1714 | "integrity": "sha512-W/Hfxc/6VehXlsgFtbB5B4xFcsCl+pAh30cYhoFyXErf6oGrwjh8SwiPAdHgpmWonKuYpZgGywN0SXt7dgsADA==", | 1669 | "integrity": "sha512-OeEZYIg+2qepaWLyphaOXHAHKo3xkM8y3BeGAvHdMN8GNWvEAU1Yw6rYpGzu/wDDbKxgEjVeVDpgGhDzaeMpjg==", |
| 1715 | "license": "Apache-2.0", | 1670 | "license": "Apache-2.0", |
| 1716 | "optional": true, | 1671 | "optional": true, |
| 1717 | "peer": true, | 1672 | "peer": true, |
| 1718 | "dependencies": { | 1673 | "dependencies": { |
| 1719 | "bare-events": "^2.0.0", | 1674 | "bare-events": "^2.5.4", |
| 1720 | "bare-path": "^2.0.0", | 1675 | "bare-path": "^3.0.0", |
| 1721 | "bare-stream": "^2.0.0" | 1676 | "bare-stream": "^2.6.4" |
| 1677 | }, | ||
| 1678 | "engines": { | ||
| 1679 | "bare": ">=1.16.0" | ||
| 1680 | }, | ||
| 1681 | "peerDependencies": { | ||
| 1682 | "bare-buffer": "*" | ||
| 1683 | }, | ||
| 1684 | "peerDependenciesMeta": { | ||
| 1685 | "bare-buffer": { | ||
| 1686 | "optional": true | ||
| 1687 | } | ||
| 1722 | } | 1688 | } |
| 1723 | }, | 1689 | }, |
| 1724 | "node_modules/bare-os": { | 1690 | "node_modules/bare-os": { |
| 1725 | "version": "2.4.0", | 1691 | "version": "3.6.1", |
| 1726 | "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.4.0.tgz", | 1692 | "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.1.tgz", |
| 1727 | "integrity": "sha512-v8DTT08AS/G0F9xrhyLtepoo9EJBJ85FRSMbu1pQUlAf6A8T0tEEQGMVObWeqpjhSPXsE0VGlluFBJu2fdoTNg==", | 1693 | "integrity": "sha512-uaIjxokhFidJP+bmmvKSgiMzj2sV5GPHaZVAIktcxcpCyBFFWO+YlikVAdhmUo2vYFvFhOXIAlldqV29L8126g==", |
| 1728 | "license": "Apache-2.0", | 1694 | "license": "Apache-2.0", |
| 1729 | "optional": true, | 1695 | "optional": true, |
| 1730 | "peer": true | 1696 | "peer": true, |
| 1697 | "engines": { | ||
| 1698 | "bare": ">=1.14.0" | ||
| 1699 | } | ||
| 1731 | }, | 1700 | }, |
| 1732 | "node_modules/bare-path": { | 1701 | "node_modules/bare-path": { |
| 1733 | "version": "2.1.3", | 1702 | "version": "3.0.0", |
| 1734 | "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.3.tgz", | 1703 | "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", |
| 1735 | "integrity": "sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==", | 1704 | "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", |
| 1736 | "license": "Apache-2.0", | 1705 | "license": "Apache-2.0", |
| 1737 | "optional": true, | 1706 | "optional": true, |
| 1738 | "peer": true, | 1707 | "peer": true, |
| 1739 | "dependencies": { | 1708 | "dependencies": { |
| 1740 | "bare-os": "^2.1.0" | 1709 | "bare-os": "^3.0.1" |
| 1741 | } | 1710 | } |
| 1742 | }, | 1711 | }, |
| 1743 | "node_modules/bare-stream": { | 1712 | "node_modules/bare-stream": { |
| 1744 | "version": "2.1.3", | 1713 | "version": "2.6.5", |
| 1745 | "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.1.3.tgz", | 1714 | "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.6.5.tgz", |
| 1746 | "integrity": "sha512-tiDAH9H/kP+tvNO5sczyn9ZAA7utrSMobyDchsnyyXBuUe2FSQWbxhtuHB8jwpHYYevVo2UJpcmvvjrbHboUUQ==", | 1715 | "integrity": "sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA==", |
| 1747 | "license": "Apache-2.0", | 1716 | "license": "Apache-2.0", |
| 1748 | "optional": true, | 1717 | "optional": true, |
| 1749 | "peer": true, | 1718 | "peer": true, |
| 1750 | "dependencies": { | 1719 | "dependencies": { |
| 1751 | "streamx": "^2.18.0" | 1720 | "streamx": "^2.21.0" |
| 1752 | } | ||
| 1753 | }, | 1721 | }, |
| 1754 | "node_modules/base64-js": { | 1722 | "peerDependencies": { |
| 1755 | "version": "1.5.1", | 1723 | "bare-buffer": "*", |
| 1756 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", | 1724 | "bare-events": "*" |
| 1757 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", | ||
| 1758 | "funding": [ | ||
| 1759 | { | ||
| 1760 | "type": "github", | ||
| 1761 | "url": "https://github.com/sponsors/feross" | ||
| 1762 | }, | 1725 | }, |
| 1763 | { | 1726 | "peerDependenciesMeta": { |
| 1764 | "type": "patreon", | 1727 | "bare-buffer": { |
| 1765 | "url": "https://www.patreon.com/feross" | 1728 | "optional": true |
| 1766 | }, | 1729 | }, |
| 1767 | { | 1730 | "bare-events": { |
| 1768 | "type": "consulting", | 1731 | "optional": true |
| 1769 | "url": "https://feross.org/support" | 1732 | } |
| 1770 | } | 1733 | } |
| 1771 | ], | ||
| 1772 | "license": "MIT", | ||
| 1773 | "peer": true | ||
| 1774 | }, | 1734 | }, |
| 1775 | "node_modules/basic-ftp": { | 1735 | "node_modules/basic-ftp": { |
| 1776 | "version": "5.0.5", | 1736 | "version": "5.0.5", |
| @@ -1845,31 +1805,6 @@ | |||
| 1845 | "node-int64": "^0.4.0" | 1805 | "node-int64": "^0.4.0" |
| 1846 | } | 1806 | } |
| 1847 | }, | 1807 | }, |
| 1848 | "node_modules/buffer": { | ||
| 1849 | "version": "5.7.1", | ||
| 1850 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", | ||
| 1851 | "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", | ||
| 1852 | "funding": [ | ||
| 1853 | { | ||
| 1854 | "type": "github", | ||
| 1855 | "url": "https://github.com/sponsors/feross" | ||
| 1856 | }, | ||
| 1857 | { | ||
| 1858 | "type": "patreon", | ||
| 1859 | "url": "https://www.patreon.com/feross" | ||
| 1860 | }, | ||
| 1861 | { | ||
| 1862 | "type": "consulting", | ||
| 1863 | "url": "https://feross.org/support" | ||
| 1864 | } | ||
| 1865 | ], | ||
| 1866 | "license": "MIT", | ||
| 1867 | "peer": true, | ||
| 1868 | "dependencies": { | ||
| 1869 | "base64-js": "^1.3.1", | ||
| 1870 | "ieee754": "^1.1.13" | ||
| 1871 | } | ||
| 1872 | }, | ||
| 1873 | "node_modules/buffer-crc32": { | 1808 | "node_modules/buffer-crc32": { |
| 1874 | "version": "0.2.13", | 1809 | "version": "0.2.13", |
| 1875 | "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", | 1810 | "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", |
| @@ -1950,15 +1885,14 @@ | |||
| 1950 | } | 1885 | } |
| 1951 | }, | 1886 | }, |
| 1952 | "node_modules/chromium-bidi": { | 1887 | "node_modules/chromium-bidi": { |
| 1953 | "version": "0.5.24", | 1888 | "version": "4.1.1", |
| 1954 | "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.5.24.tgz", | 1889 | "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-4.1.1.tgz", |
| 1955 | "integrity": "sha512-5xQNN2SVBdZv4TxeMLaI+PelrnZsHDhn8h2JtyriLr+0qHcZS8BMuo93qN6J1VmtmrgYP+rmcLHcbpnA8QJh+w==", | 1890 | "integrity": "sha512-biR7t4vF3YluE6RlMSk9IWk+b9U+WWyzHp+N2pL9vRTk+UXHYRTVp7jTK58ZNzMLBgoLMHY4QyJMbeuw3eKxqg==", |
| 1956 | "license": "Apache-2.0", | 1891 | "license": "Apache-2.0", |
| 1957 | "peer": true, | 1892 | "peer": true, |
| 1958 | "dependencies": { | 1893 | "dependencies": { |
| 1959 | "mitt": "3.0.1", | 1894 | "mitt": "^3.0.1", |
| 1960 | "urlpattern-polyfill": "10.0.0", | 1895 | "zod": "^3.24.1" |
| 1961 | "zod": "3.23.8" | ||
| 1962 | }, | 1896 | }, |
| 1963 | "peerDependencies": { | 1897 | "peerDependencies": { |
| 1964 | "devtools-protocol": "*" | 1898 | "devtools-protocol": "*" |
| @@ -2169,12 +2103,12 @@ | |||
| 2169 | } | 2103 | } |
| 2170 | }, | 2104 | }, |
| 2171 | "node_modules/debug": { | 2105 | "node_modules/debug": { |
| 2172 | "version": "4.3.5", | 2106 | "version": "4.4.0", |
| 2173 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", | 2107 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", |
| 2174 | "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", | 2108 | "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", |
| 2175 | "license": "MIT", | 2109 | "license": "MIT", |
| 2176 | "dependencies": { | 2110 | "dependencies": { |
| 2177 | "ms": "2.1.2" | 2111 | "ms": "^2.1.3" |
| 2178 | }, | 2112 | }, |
| 2179 | "engines": { | 2113 | "engines": { |
| 2180 | "node": ">=6.0" | 2114 | "node": ">=6.0" |
| @@ -2248,9 +2182,9 @@ | |||
| 2248 | } | 2182 | } |
| 2249 | }, | 2183 | }, |
| 2250 | "node_modules/devtools-protocol": { | 2184 | "node_modules/devtools-protocol": { |
| 2251 | "version": "0.0.1299070", | 2185 | "version": "0.0.1425554", |
| 2252 | "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1299070.tgz", | 2186 | "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1425554.tgz", |
| 2253 | "integrity": "sha512-+qtL3eX50qsJ7c+qVyagqi7AWMoQCBGNfoyJZMwm/NSXVqLYbuitrWEEIzxfUmTNy7//Xe8yhMmQ+elj3uAqSg==", | 2187 | "integrity": "sha512-uRfxR6Nlzdzt0ihVIkV+sLztKgs7rgquY/Mhcv1YNCWDh5IZgl5mnn2aeEnW5stYTE0wwiF4RYVz8eMEpV1SEw==", |
| 2254 | "license": "BSD-3-Clause", | 2188 | "license": "BSD-3-Clause", |
| 2255 | "peer": true | 2189 | "peer": true |
| 2256 | }, | 2190 | }, |
| @@ -2987,21 +2921,6 @@ | |||
| 2987 | "node": ">=0.10.0" | 2921 | "node": ">=0.10.0" |
| 2988 | } | 2922 | } |
| 2989 | }, | 2923 | }, |
| 2990 | "node_modules/fs-extra": { | ||
| 2991 | "version": "11.2.0", | ||
| 2992 | "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", | ||
| 2993 | "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", | ||
| 2994 | "license": "MIT", | ||
| 2995 | "peer": true, | ||
| 2996 | "dependencies": { | ||
| 2997 | "graceful-fs": "^4.2.0", | ||
| 2998 | "jsonfile": "^6.0.1", | ||
| 2999 | "universalify": "^2.0.0" | ||
| 3000 | }, | ||
| 3001 | "engines": { | ||
| 3002 | "node": ">=14.14" | ||
| 3003 | } | ||
| 3004 | }, | ||
| 3005 | "node_modules/fs.realpath": { | 2924 | "node_modules/fs.realpath": { |
| 3006 | "version": "1.0.0", | 2925 | "version": "1.0.0", |
| 3007 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", | 2926 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", |
| @@ -3071,16 +2990,15 @@ | |||
| 3071 | } | 2990 | } |
| 3072 | }, | 2991 | }, |
| 3073 | "node_modules/get-uri": { | 2992 | "node_modules/get-uri": { |
| 3074 | "version": "6.0.3", | 2993 | "version": "6.0.4", |
| 3075 | "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.3.tgz", | 2994 | "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.4.tgz", |
| 3076 | "integrity": "sha512-BzUrJBS9EcUb4cFol8r4W3v1cPsSyajLSthNkz5BxbpDcHN5tIrM10E2eNvfnvBn3DaT3DUgx0OpsBKkaOpanw==", | 2995 | "integrity": "sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==", |
| 3077 | "license": "MIT", | 2996 | "license": "MIT", |
| 3078 | "peer": true, | 2997 | "peer": true, |
| 3079 | "dependencies": { | 2998 | "dependencies": { |
| 3080 | "basic-ftp": "^5.0.2", | 2999 | "basic-ftp": "^5.0.2", |
| 3081 | "data-uri-to-buffer": "^6.0.2", | 3000 | "data-uri-to-buffer": "^6.0.2", |
| 3082 | "debug": "^4.3.4", | 3001 | "debug": "^4.3.4" |
| 3083 | "fs-extra": "^11.2.0" | ||
| 3084 | }, | 3002 | }, |
| 3085 | "engines": { | 3003 | "engines": { |
| 3086 | "node": ">= 14" | 3004 | "node": ">= 14" |
| @@ -3254,13 +3172,13 @@ | |||
| 3254 | } | 3172 | } |
| 3255 | }, | 3173 | }, |
| 3256 | "node_modules/https-proxy-agent": { | 3174 | "node_modules/https-proxy-agent": { |
| 3257 | "version": "7.0.5", | 3175 | "version": "7.0.6", |
| 3258 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", | 3176 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", |
| 3259 | "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", | 3177 | "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", |
| 3260 | "license": "MIT", | 3178 | "license": "MIT", |
| 3261 | "peer": true, | 3179 | "peer": true, |
| 3262 | "dependencies": { | 3180 | "dependencies": { |
| 3263 | "agent-base": "^7.0.2", | 3181 | "agent-base": "^7.1.2", |
| 3264 | "debug": "4" | 3182 | "debug": "4" |
| 3265 | }, | 3183 | }, |
| 3266 | "engines": { | 3184 | "engines": { |
| @@ -3276,27 +3194,6 @@ | |||
| 3276 | "node": ">=10.17.0" | 3194 | "node": ">=10.17.0" |
| 3277 | } | 3195 | } |
| 3278 | }, | 3196 | }, |
| 3279 | "node_modules/ieee754": { | ||
| 3280 | "version": "1.2.1", | ||
| 3281 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", | ||
| 3282 | "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", | ||
| 3283 | "funding": [ | ||
| 3284 | { | ||
| 3285 | "type": "github", | ||
| 3286 | "url": "https://github.com/sponsors/feross" | ||
| 3287 | }, | ||
| 3288 | { | ||
| 3289 | "type": "patreon", | ||
| 3290 | "url": "https://www.patreon.com/feross" | ||
| 3291 | }, | ||
| 3292 | { | ||
| 3293 | "type": "consulting", | ||
| 3294 | "url": "https://feross.org/support" | ||
| 3295 | } | ||
| 3296 | ], | ||
| 3297 | "license": "BSD-3-Clause", | ||
| 3298 | "peer": true | ||
| 3299 | }, | ||
| 3300 | "node_modules/ignore": { | 3197 | "node_modules/ignore": { |
| 3301 | "version": "5.3.1", | 3198 | "version": "5.3.1", |
| 3302 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", | 3199 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", |
| @@ -4287,19 +4184,6 @@ | |||
| 4287 | "node": ">=6" | 4184 | "node": ">=6" |
| 4288 | } | 4185 | } |
| 4289 | }, | 4186 | }, |
| 4290 | "node_modules/jsonfile": { | ||
| 4291 | "version": "6.1.0", | ||
| 4292 | "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", | ||
| 4293 | "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", | ||
| 4294 | "license": "MIT", | ||
| 4295 | "peer": true, | ||
| 4296 | "dependencies": { | ||
| 4297 | "universalify": "^2.0.0" | ||
| 4298 | }, | ||
| 4299 | "optionalDependencies": { | ||
| 4300 | "graceful-fs": "^4.1.6" | ||
| 4301 | } | ||
| 4302 | }, | ||
| 4303 | "node_modules/keyv": { | 4187 | "node_modules/keyv": { |
| 4304 | "version": "4.5.4", | 4188 | "version": "4.5.4", |
| 4305 | "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", | 4189 | "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", |
| @@ -4501,9 +4385,9 @@ | |||
| 4501 | "peer": true | 4385 | "peer": true |
| 4502 | }, | 4386 | }, |
| 4503 | "node_modules/ms": { | 4387 | "node_modules/ms": { |
| 4504 | "version": "2.1.2", | 4388 | "version": "2.1.3", |
| 4505 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", | 4389 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", |
| 4506 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", | 4390 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", |
| 4507 | "license": "MIT" | 4391 | "license": "MIT" |
| 4508 | }, | 4392 | }, |
| 4509 | "node_modules/natural-compare": { | 4393 | "node_modules/natural-compare": { |
| @@ -4657,20 +4541,20 @@ | |||
| 4657 | } | 4541 | } |
| 4658 | }, | 4542 | }, |
| 4659 | "node_modules/pac-proxy-agent": { | 4543 | "node_modules/pac-proxy-agent": { |
| 4660 | "version": "7.0.2", | 4544 | "version": "7.2.0", |
| 4661 | "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.0.2.tgz", | 4545 | "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", |
| 4662 | "integrity": "sha512-BFi3vZnO9X5Qt6NRz7ZOaPja3ic0PhlsmCRYLOpN11+mWBCR6XJDqW5RF3j8jm4WGGQZtBA+bTfxYzeKW73eHg==", | 4546 | "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", |
| 4663 | "license": "MIT", | 4547 | "license": "MIT", |
| 4664 | "peer": true, | 4548 | "peer": true, |
| 4665 | "dependencies": { | 4549 | "dependencies": { |
| 4666 | "@tootallnate/quickjs-emscripten": "^0.23.0", | 4550 | "@tootallnate/quickjs-emscripten": "^0.23.0", |
| 4667 | "agent-base": "^7.0.2", | 4551 | "agent-base": "^7.1.2", |
| 4668 | "debug": "^4.3.4", | 4552 | "debug": "^4.3.4", |
| 4669 | "get-uri": "^6.0.1", | 4553 | "get-uri": "^6.0.1", |
| 4670 | "http-proxy-agent": "^7.0.0", | 4554 | "http-proxy-agent": "^7.0.0", |
| 4671 | "https-proxy-agent": "^7.0.5", | 4555 | "https-proxy-agent": "^7.0.6", |
| 4672 | "pac-resolver": "^7.0.1", | 4556 | "pac-resolver": "^7.0.1", |
| 4673 | "socks-proxy-agent": "^8.0.4" | 4557 | "socks-proxy-agent": "^8.0.5" |
| 4674 | }, | 4558 | }, |
| 4675 | "engines": { | 4559 | "engines": { |
| 4676 | "node": ">= 14" | 4560 | "node": ">= 14" |
| @@ -4876,20 +4760,20 @@ | |||
| 4876 | } | 4760 | } |
| 4877 | }, | 4761 | }, |
| 4878 | "node_modules/proxy-agent": { | 4762 | "node_modules/proxy-agent": { |
| 4879 | "version": "6.4.0", | 4763 | "version": "6.5.0", |
| 4880 | "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", | 4764 | "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", |
| 4881 | "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", | 4765 | "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", |
| 4882 | "license": "MIT", | 4766 | "license": "MIT", |
| 4883 | "peer": true, | 4767 | "peer": true, |
| 4884 | "dependencies": { | 4768 | "dependencies": { |
| 4885 | "agent-base": "^7.0.2", | 4769 | "agent-base": "^7.1.2", |
| 4886 | "debug": "^4.3.4", | 4770 | "debug": "^4.3.4", |
| 4887 | "http-proxy-agent": "^7.0.1", | 4771 | "http-proxy-agent": "^7.0.1", |
| 4888 | "https-proxy-agent": "^7.0.3", | 4772 | "https-proxy-agent": "^7.0.6", |
| 4889 | "lru-cache": "^7.14.1", | 4773 | "lru-cache": "^7.14.1", |
| 4890 | "pac-proxy-agent": "^7.0.1", | 4774 | "pac-proxy-agent": "^7.1.0", |
| 4891 | "proxy-from-env": "^1.1.0", | 4775 | "proxy-from-env": "^1.1.0", |
| 4892 | "socks-proxy-agent": "^8.0.2" | 4776 | "socks-proxy-agent": "^8.0.5" |
| 4893 | }, | 4777 | }, |
| 4894 | "engines": { | 4778 | "engines": { |
| 4895 | "node": ">= 14" | 4779 | "node": ">= 14" |
| @@ -4912,9 +4796,9 @@ | |||
| 4912 | "license": "MIT" | 4796 | "license": "MIT" |
| 4913 | }, | 4797 | }, |
| 4914 | "node_modules/pump": { | 4798 | "node_modules/pump": { |
| 4915 | "version": "3.0.0", | 4799 | "version": "3.0.2", |
| 4916 | "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", | 4800 | "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", |
| 4917 | "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", | 4801 | "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", |
| 4918 | "license": "MIT", | 4802 | "license": "MIT", |
| 4919 | "peer": true, | 4803 | "peer": true, |
| 4920 | "dependencies": { | 4804 | "dependencies": { |
| @@ -4932,37 +4816,40 @@ | |||
| 4932 | } | 4816 | } |
| 4933 | }, | 4817 | }, |
| 4934 | "node_modules/puppeteer": { | 4818 | "node_modules/puppeteer": { |
| 4935 | "version": "22.12.1", | 4819 | "version": "24.7.2", |
| 4936 | "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-22.12.1.tgz", | 4820 | "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.7.2.tgz", |
| 4937 | "integrity": "sha512-1GxY8dnEnHr1SLzdSDr0FCjM6JQfAh2E2I/EqzeF8a58DbGVk9oVjj4lFdqNoVbpgFSpAbz7VER9St7S1wDpNg==", | 4821 | "integrity": "sha512-ifYqoY6wGs0yZeFuFPn8BE9FhuveXkarF+eO18I2e/axdoCh4Qh1AE+qXdJBhdaeoPt6eRNTY4Dih29Jbq8wow==", |
| 4938 | "hasInstallScript": true, | 4822 | "hasInstallScript": true, |
| 4939 | "license": "Apache-2.0", | 4823 | "license": "Apache-2.0", |
| 4940 | "peer": true, | 4824 | "peer": true, |
| 4941 | "dependencies": { | 4825 | "dependencies": { |
| 4942 | "@puppeteer/browsers": "2.2.3", | 4826 | "@puppeteer/browsers": "2.10.2", |
| 4827 | "chromium-bidi": "4.1.1", | ||
| 4943 | "cosmiconfig": "^9.0.0", | 4828 | "cosmiconfig": "^9.0.0", |
| 4944 | "devtools-protocol": "0.0.1299070", | 4829 | "devtools-protocol": "0.0.1425554", |
| 4945 | "puppeteer-core": "22.12.1" | 4830 | "puppeteer-core": "24.7.2", |
| 4831 | "typed-query-selector": "^2.12.0" | ||
| 4946 | }, | 4832 | }, |
| 4947 | "bin": { | 4833 | "bin": { |
| 4948 | "puppeteer": "lib/esm/puppeteer/node/cli.js" | 4834 | "puppeteer": "lib/cjs/puppeteer/node/cli.js" |
| 4949 | }, | 4835 | }, |
| 4950 | "engines": { | 4836 | "engines": { |
| 4951 | "node": ">=18" | 4837 | "node": ">=18" |
| 4952 | } | 4838 | } |
| 4953 | }, | 4839 | }, |
| 4954 | "node_modules/puppeteer-core": { | 4840 | "node_modules/puppeteer-core": { |
| 4955 | "version": "22.12.1", | 4841 | "version": "24.7.2", |
| 4956 | "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.12.1.tgz", | 4842 | "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.7.2.tgz", |
| 4957 | "integrity": "sha512-XmqeDPVdC5/3nGJys1jbgeoZ02wP0WV1GBlPtr/ULRbGXJFuqgXMcKQ3eeNtFpBzGRbpeoCGWHge1ZWKWl0Exw==", | 4843 | "integrity": "sha512-P9pZyTmJqKODFCnkZgemCpoFA4LbAa8+NumHVQKyP5X9IgdNS1ZnAnIh1sMAwhF8/xEUGf7jt+qmNLlKieFw1Q==", |
| 4958 | "license": "Apache-2.0", | 4844 | "license": "Apache-2.0", |
| 4959 | "peer": true, | 4845 | "peer": true, |
| 4960 | "dependencies": { | 4846 | "dependencies": { |
| 4961 | "@puppeteer/browsers": "2.2.3", | 4847 | "@puppeteer/browsers": "2.10.2", |
| 4962 | "chromium-bidi": "0.5.24", | 4848 | "chromium-bidi": "4.1.1", |
| 4963 | "debug": "^4.3.5", | 4849 | "debug": "^4.4.0", |
| 4964 | "devtools-protocol": "0.0.1299070", | 4850 | "devtools-protocol": "0.0.1425554", |
| 4965 | "ws": "^8.17.1" | 4851 | "typed-query-selector": "^2.12.0", |
| 4852 | "ws": "^8.18.1" | ||
| 4966 | }, | 4853 | }, |
| 4967 | "engines": { | 4854 | "engines": { |
| 4968 | "node": ">=18" | 4855 | "node": ">=18" |
| @@ -5051,13 +4938,6 @@ | |||
| 5051 | ], | 4938 | ], |
| 5052 | "license": "MIT" | 4939 | "license": "MIT" |
| 5053 | }, | 4940 | }, |
| 5054 | "node_modules/queue-tick": { | ||
| 5055 | "version": "1.0.1", | ||
| 5056 | "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", | ||
| 5057 | "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", | ||
| 5058 | "license": "MIT", | ||
| 5059 | "peer": true | ||
| 5060 | }, | ||
| 5061 | "node_modules/react-is": { | 4941 | "node_modules/react-is": { |
| 5062 | "version": "18.3.1", | 4942 | "version": "18.3.1", |
| 5063 | "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", | 4943 | "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", |
| @@ -5254,9 +5134,9 @@ | |||
| 5254 | } | 5134 | } |
| 5255 | }, | 5135 | }, |
| 5256 | "node_modules/socks": { | 5136 | "node_modules/socks": { |
| 5257 | "version": "2.8.3", | 5137 | "version": "2.8.4", |
| 5258 | "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", | 5138 | "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.4.tgz", |
| 5259 | "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", | 5139 | "integrity": "sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==", |
| 5260 | "license": "MIT", | 5140 | "license": "MIT", |
| 5261 | "peer": true, | 5141 | "peer": true, |
| 5262 | "dependencies": { | 5142 | "dependencies": { |
| @@ -5269,13 +5149,13 @@ | |||
| 5269 | } | 5149 | } |
| 5270 | }, | 5150 | }, |
| 5271 | "node_modules/socks-proxy-agent": { | 5151 | "node_modules/socks-proxy-agent": { |
| 5272 | "version": "8.0.4", | 5152 | "version": "8.0.5", |
| 5273 | "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz", | 5153 | "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", |
| 5274 | "integrity": "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==", | 5154 | "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", |
| 5275 | "license": "MIT", | 5155 | "license": "MIT", |
| 5276 | "peer": true, | 5156 | "peer": true, |
| 5277 | "dependencies": { | 5157 | "dependencies": { |
| 5278 | "agent-base": "^7.1.1", | 5158 | "agent-base": "^7.1.2", |
| 5279 | "debug": "^4.3.4", | 5159 | "debug": "^4.3.4", |
| 5280 | "socks": "^2.8.3" | 5160 | "socks": "^2.8.3" |
| 5281 | }, | 5161 | }, |
| @@ -5345,14 +5225,13 @@ | |||
| 5345 | } | 5225 | } |
| 5346 | }, | 5226 | }, |
| 5347 | "node_modules/streamx": { | 5227 | "node_modules/streamx": { |
| 5348 | "version": "2.18.0", | 5228 | "version": "2.22.0", |
| 5349 | "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.18.0.tgz", | 5229 | "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.0.tgz", |
| 5350 | "integrity": "sha512-LLUC1TWdjVdn1weXGcSxyTR3T4+acB6tVGXT95y0nGbca4t4o/ng1wKAGTljm9VicuCVLvRlqFYXYy5GwgM7sQ==", | 5230 | "integrity": "sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw==", |
| 5351 | "license": "MIT", | 5231 | "license": "MIT", |
| 5352 | "peer": true, | 5232 | "peer": true, |
| 5353 | "dependencies": { | 5233 | "dependencies": { |
| 5354 | "fast-fifo": "^1.3.2", | 5234 | "fast-fifo": "^1.3.2", |
| 5355 | "queue-tick": "^1.0.1", | ||
| 5356 | "text-decoder": "^1.1.0" | 5235 | "text-decoder": "^1.1.0" |
| 5357 | }, | 5236 | }, |
| 5358 | "optionalDependencies": { | 5237 | "optionalDependencies": { |
| @@ -5453,9 +5332,9 @@ | |||
| 5453 | } | 5332 | } |
| 5454 | }, | 5333 | }, |
| 5455 | "node_modules/tar-fs": { | 5334 | "node_modules/tar-fs": { |
| 5456 | "version": "3.0.5", | 5335 | "version": "3.0.8", |
| 5457 | "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.5.tgz", | 5336 | "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.8.tgz", |
| 5458 | "integrity": "sha512-JOgGAmZyMgbqpLwct7ZV8VzkEB6pxXFBVErLtb+XCOqzc6w1xiWKI9GVd6bwk68EX7eJ4DWmfXVmq8K2ziZTGg==", | 5337 | "integrity": "sha512-ZoROL70jptorGAlgAYiLoBLItEKw/fUxg9BSYK/dF/GAGYFJOJJJMvjPAKDJraCXFwadD456FCuvLWgfhMsPwg==", |
| 5459 | "license": "MIT", | 5338 | "license": "MIT", |
| 5460 | "peer": true, | 5339 | "peer": true, |
| 5461 | "dependencies": { | 5340 | "dependencies": { |
| @@ -5463,8 +5342,8 @@ | |||
| 5463 | "tar-stream": "^3.1.5" | 5342 | "tar-stream": "^3.1.5" |
| 5464 | }, | 5343 | }, |
| 5465 | "optionalDependencies": { | 5344 | "optionalDependencies": { |
| 5466 | "bare-fs": "^2.1.1", | 5345 | "bare-fs": "^4.0.1", |
| 5467 | "bare-path": "^2.1.0" | 5346 | "bare-path": "^3.0.0" |
| 5468 | } | 5347 | } |
| 5469 | }, | 5348 | }, |
| 5470 | "node_modules/tar-stream": { | 5349 | "node_modules/tar-stream": { |
| @@ -5494,9 +5373,9 @@ | |||
| 5494 | } | 5373 | } |
| 5495 | }, | 5374 | }, |
| 5496 | "node_modules/text-decoder": { | 5375 | "node_modules/text-decoder": { |
| 5497 | "version": "1.1.1", | 5376 | "version": "1.2.3", |
| 5498 | "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.1.1.tgz", | 5377 | "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", |
| 5499 | "integrity": "sha512-8zll7REEv4GDD3x4/0pW+ppIxSNs7H1J10IKFZsuOMscumCdM2a+toDGLPA3T+1+fLBql4zbt5z83GEQGGV5VA==", | 5378 | "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", |
| 5500 | "license": "Apache-2.0", | 5379 | "license": "Apache-2.0", |
| 5501 | "peer": true, | 5380 | "peer": true, |
| 5502 | "dependencies": { | 5381 | "dependencies": { |
| @@ -5509,13 +5388,6 @@ | |||
| 5509 | "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", | 5388 | "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", |
| 5510 | "license": "MIT" | 5389 | "license": "MIT" |
| 5511 | }, | 5390 | }, |
| 5512 | "node_modules/through": { | ||
| 5513 | "version": "2.3.8", | ||
| 5514 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", | ||
| 5515 | "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", | ||
| 5516 | "license": "MIT", | ||
| 5517 | "peer": true | ||
| 5518 | }, | ||
| 5519 | "node_modules/tmpl": { | 5391 | "node_modules/tmpl": { |
| 5520 | "version": "1.0.5", | 5392 | "version": "1.0.5", |
| 5521 | "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", | 5393 | "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", |
| @@ -5594,6 +5466,13 @@ | |||
| 5594 | "url": "https://github.com/sponsors/sindresorhus" | 5466 | "url": "https://github.com/sponsors/sindresorhus" |
| 5595 | } | 5467 | } |
| 5596 | }, | 5468 | }, |
| 5469 | "node_modules/typed-query-selector": { | ||
| 5470 | "version": "2.12.0", | ||
| 5471 | "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", | ||
| 5472 | "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==", | ||
| 5473 | "license": "MIT", | ||
| 5474 | "peer": true | ||
| 5475 | }, | ||
| 5597 | "node_modules/typescript": { | 5476 | "node_modules/typescript": { |
| 5598 | "version": "5.5.3", | 5477 | "version": "5.5.3", |
| 5599 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz", | 5478 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz", |
| @@ -5608,33 +5487,12 @@ | |||
| 5608 | "node": ">=14.17" | 5487 | "node": ">=14.17" |
| 5609 | } | 5488 | } |
| 5610 | }, | 5489 | }, |
| 5611 | "node_modules/unbzip2-stream": { | ||
| 5612 | "version": "1.4.3", | ||
| 5613 | "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", | ||
| 5614 | "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", | ||
| 5615 | "license": "MIT", | ||
| 5616 | "peer": true, | ||
| 5617 | "dependencies": { | ||
| 5618 | "buffer": "^5.2.1", | ||
| 5619 | "through": "^2.3.8" | ||
| 5620 | } | ||
| 5621 | }, | ||
| 5622 | "node_modules/undici-types": { | 5490 | "node_modules/undici-types": { |
| 5623 | "version": "5.26.5", | 5491 | "version": "5.26.5", |
| 5624 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", | 5492 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", |
| 5625 | "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", | 5493 | "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", |
| 5626 | "license": "MIT" | 5494 | "license": "MIT" |
| 5627 | }, | 5495 | }, |
| 5628 | "node_modules/universalify": { | ||
| 5629 | "version": "2.0.1", | ||
| 5630 | "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", | ||
| 5631 | "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", | ||
| 5632 | "license": "MIT", | ||
| 5633 | "peer": true, | ||
| 5634 | "engines": { | ||
| 5635 | "node": ">= 10.0.0" | ||
| 5636 | } | ||
| 5637 | }, | ||
| 5638 | "node_modules/update-browserslist-db": { | 5496 | "node_modules/update-browserslist-db": { |
| 5639 | "version": "1.1.0", | 5497 | "version": "1.1.0", |
| 5640 | "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", | 5498 | "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", |
| @@ -5674,13 +5532,6 @@ | |||
| 5674 | "punycode": "^2.1.0" | 5532 | "punycode": "^2.1.0" |
| 5675 | } | 5533 | } |
| 5676 | }, | 5534 | }, |
| 5677 | "node_modules/urlpattern-polyfill": { | ||
| 5678 | "version": "10.0.0", | ||
| 5679 | "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", | ||
| 5680 | "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", | ||
| 5681 | "license": "MIT", | ||
| 5682 | "peer": true | ||
| 5683 | }, | ||
| 5684 | "node_modules/v8-to-istanbul": { | 5535 | "node_modules/v8-to-istanbul": { |
| 5685 | "version": "9.3.0", | 5536 | "version": "9.3.0", |
| 5686 | "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", | 5537 | "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", |
| @@ -5784,9 +5635,9 @@ | |||
| 5784 | } | 5635 | } |
| 5785 | }, | 5636 | }, |
| 5786 | "node_modules/ws": { | 5637 | "node_modules/ws": { |
| 5787 | "version": "8.18.0", | 5638 | "version": "8.18.1", |
| 5788 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", | 5639 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", |
| 5789 | "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", | 5640 | "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", |
| 5790 | "license": "MIT", | 5641 | "license": "MIT", |
| 5791 | "peer": true, | 5642 | "peer": true, |
| 5792 | "engines": { | 5643 | "engines": { |
| @@ -5871,9 +5722,9 @@ | |||
| 5871 | } | 5722 | } |
| 5872 | }, | 5723 | }, |
| 5873 | "node_modules/zod": { | 5724 | "node_modules/zod": { |
| 5874 | "version": "3.23.8", | 5725 | "version": "3.24.3", |
| 5875 | "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", | 5726 | "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.3.tgz", |
| 5876 | "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", | 5727 | "integrity": "sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==", |
| 5877 | "license": "MIT", | 5728 | "license": "MIT", |
| 5878 | "peer": true, | 5729 | "peer": true, |
| 5879 | "funding": { | 5730 | "funding": { |
| @@ -1,6 +1,7 @@ | |||
| 1 | import process from 'node:process'; | 1 | import process from 'node:process'; |
| 2 | import path from 'node:path'; | 2 | import path from 'node:path'; |
| 3 | import isDocker from 'is-docker'; | 3 | import isDocker from 'is-docker'; |
| 4 | import { serverDirectory } from './src/server-directory.js'; | ||
| 4 | 5 | ||
| 5 | /** | 6 | /** |
| 6 | * Get the Webpack configuration for the public/lib.js file. | 7 | * Get the Webpack configuration for the public/lib.js file. |
| @@ -40,7 +41,7 @@ export default function getPublicLibConfig(forceDist = false) { | |||
| 40 | 41 | ||
| 41 | return { | 42 | return { |
| 42 | mode: 'production', | 43 | mode: 'production', |
| 43 | entry: './public/lib.js', | 44 | entry: path.join(serverDirectory, 'public/lib.js'), |
| 44 | cache: { | 45 | cache: { |
| 45 | type: 'filesystem', | 46 | type: 'filesystem', |
| 46 | cacheDirectory: cacheDirectory, | 47 | cacheDirectory: cacheDirectory, |