mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-02-23 15:37:50 +01:00
commit
f246bc5ac6
@ -12,3 +12,4 @@ access.log
|
||||
/data
|
||||
/cache
|
||||
.DS_Store
|
||||
/public/scripts/extensions/third-party
|
||||
|
@ -91,6 +91,8 @@ module.exports = {
|
||||
'space-infix-ops': 'error',
|
||||
'no-unused-expressions': ['error', { allowShortCircuit: true, allowTernary: true }],
|
||||
'no-cond-assign': 'error',
|
||||
'no-unneeded-ternary': 'error',
|
||||
'no-irregular-whitespace': ['error', { skipStrings: true, skipTemplates: true }],
|
||||
|
||||
// These rules should eventually be enabled.
|
||||
'no-async-promise-executor': 'off',
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -50,3 +50,5 @@ public/css/user.css
|
||||
/default/scaffold
|
||||
public/scripts/extensions/third-party
|
||||
/certs
|
||||
.aider*
|
||||
.env
|
||||
|
@ -11,3 +11,4 @@ access.log
|
||||
.github
|
||||
.vscode
|
||||
.git
|
||||
/public/scripts/extensions/third-party
|
||||
|
@ -6,9 +6,6 @@ ARG APP_HOME=/home/node/app
|
||||
# Install system dependencies
|
||||
RUN apk add gcompat tini git
|
||||
|
||||
# Ensure proper handling of kernel signals
|
||||
ENTRYPOINT [ "tini", "--" ]
|
||||
|
||||
# Create app directory
|
||||
WORKDIR ${APP_HOME}
|
||||
|
||||
@ -42,4 +39,5 @@ RUN \
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
CMD [ "./docker-entrypoint.sh" ]
|
||||
# Ensure proper handling of kernel signals
|
||||
ENTRYPOINT ["tini", "--", "./docker-entrypoint.sh"]
|
||||
|
@ -98,6 +98,8 @@ skipContentCheck: false
|
||||
disableChatBackup: false
|
||||
# Number of backups to keep for each chat and settings file
|
||||
numberOfBackups: 50
|
||||
# Maximum number of chat backups to keep per user (starting from the most recent). Set to -1 to keep all backups.
|
||||
maxTotalChatBackups: -1
|
||||
# Interval in milliseconds to throttle chat backups per user
|
||||
chatBackupThrottleInterval: 10000
|
||||
# Allowed hosts for card downloads
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"input_sequence": "<|im_start|>[{{name}}]",
|
||||
"output_sequence": "<|im_start|>[{{name}}]",
|
||||
"input_sequence": "<|im_start|>{{name}}",
|
||||
"output_sequence": "<|im_start|>{{name}}",
|
||||
"last_output_sequence": "",
|
||||
"system_sequence": "<|im_start|>system",
|
||||
"stop_sequence": "<|im_end|>",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"input_sequence": "<|start_header_id|>[{{name}}]<|end_header_id|>\n\n",
|
||||
"output_sequence": "<|start_header_id|>[{{name}}]<|end_header_id|>\n\n",
|
||||
"input_sequence": "<|start_header_id|>{{name}}<|end_header_id|>\n\n",
|
||||
"output_sequence": "<|start_header_id|>{{name}}<|end_header_id|>\n\n",
|
||||
"last_output_sequence": "",
|
||||
"system_sequence": "<|start_header_id|>system<|end_header_id|>\n\n",
|
||||
"stop_sequence": "<|eot_id|>",
|
||||
|
@ -39,8 +39,8 @@
|
||||
"proxy_password": "",
|
||||
"max_context_unlocked": false,
|
||||
"wi_format": "{0}",
|
||||
"scenario_format": "[Circumstances and context of the dialogue: {{scenario}}]",
|
||||
"personality_format": "[{{char}}'s personality: {{personality}}]",
|
||||
"scenario_format": "{{scenario}}",
|
||||
"personality_format": "{{personality}}",
|
||||
"group_nudge_prompt": "[Write the next reply only as {{char}}.]",
|
||||
"stream_openai": true,
|
||||
"prompts": [
|
||||
|
@ -599,18 +599,22 @@
|
||||
"Default (none)": [],
|
||||
"Anti-bond": [
|
||||
{
|
||||
"id": "22154f79-dd98-41bc-8e34-87015d6a0eaf",
|
||||
"text": " bond",
|
||||
"value": -50
|
||||
},
|
||||
{
|
||||
"id": "8ad2d5c4-d8ef-49e4-bc5e-13e7f4690e0f",
|
||||
"text": " future",
|
||||
"value": -50
|
||||
},
|
||||
{
|
||||
"id": "52a4b280-0956-4940-ac52-4111f83e4046",
|
||||
"text": " bonding",
|
||||
"value": -50
|
||||
},
|
||||
{
|
||||
"id": "e63037c7-c9d1-4724-ab2d-7756008b433b",
|
||||
"text": " connection",
|
||||
"value": -25
|
||||
}
|
||||
|
@ -6,4 +6,4 @@ if [ ! -e "config/config.yaml" ]; then
|
||||
fi
|
||||
|
||||
# Start the server
|
||||
exec node server.js --listen
|
||||
exec node server.js --listen "$@"
|
||||
|
73
package-lock.json
generated
73
package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "sillytavern",
|
||||
"version": "1.12.9",
|
||||
"version": "1.12.10",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "sillytavern",
|
||||
"version": "1.12.9",
|
||||
"version": "1.12.10",
|
||||
"hasInstallScript": true,
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
@ -18,7 +18,7 @@
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@zeldafan0225/ai_horde": "^5.1.0",
|
||||
"archiver": "^7.0.1",
|
||||
"bing-translate-api": "^2.9.1",
|
||||
"bing-translate-api": "^4.0.2",
|
||||
"body-parser": "^1.20.2",
|
||||
"bowser": "^2.11.0",
|
||||
"command-exists": "^1.2.9",
|
||||
@ -63,7 +63,9 @@
|
||||
"showdown": "^2.1.0",
|
||||
"sillytavern-transformers": "2.14.6",
|
||||
"simple-git": "^3.19.1",
|
||||
"slidetoggle": "^4.0.0",
|
||||
"tiktoken": "^1.0.16",
|
||||
"url-join": "^5.0.0",
|
||||
"vectra": "^0.2.2",
|
||||
"wavefile": "^11.0.0",
|
||||
"webpack": "^5.95.0",
|
||||
@ -86,6 +88,9 @@
|
||||
"@types/deno": "^2.0.0",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/jquery": "^3.5.29",
|
||||
"@types/jquery-cropper": "^1.0.4",
|
||||
"@types/jquery.transit": "^0.9.33",
|
||||
"@types/jqueryui": "^1.12.23",
|
||||
"@types/lodash": "^4.17.10",
|
||||
"@types/mime-types": "^2.1.4",
|
||||
"@types/multer": "^1.4.12",
|
||||
@ -95,6 +100,7 @@
|
||||
"@types/png-chunks-encode": "^1.0.2",
|
||||
"@types/png-chunks-extract": "^1.0.2",
|
||||
"@types/response-time": "^2.3.8",
|
||||
"@types/select2": "^4.0.63",
|
||||
"@types/toastr": "^2.1.43",
|
||||
"@types/write-file-atomic": "^4.0.3",
|
||||
"@types/yargs": "^17.0.33",
|
||||
@ -1226,6 +1232,36 @@
|
||||
"@types/sizzle": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/jquery-cropper": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/jquery-cropper/-/jquery-cropper-1.0.4.tgz",
|
||||
"integrity": "sha512-YMyUoY+rhB8yc3xM1B/daNaSq5+q93rzvRx6HP8K9mmvXEviTH3/rldlYNCGd0TmE/kLlZYJsruYhu9wY350PA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/jquery": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/jquery.transit": {
|
||||
"version": "0.9.33",
|
||||
"resolved": "https://registry.npmjs.org/@types/jquery.transit/-/jquery.transit-0.9.33.tgz",
|
||||
"integrity": "sha512-gEDi1Lw7qfHFxtcnm2dg0F3Z5yG+84Sn0gDpGbd+u+r2RxsCcdQzfUmFKzHGBjWflZ9CXOZiAkenKOSvwLITrg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/jquery": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/jqueryui": {
|
||||
"version": "1.12.23",
|
||||
"resolved": "https://registry.npmjs.org/@types/jqueryui/-/jqueryui-1.12.23.tgz",
|
||||
"integrity": "sha512-pm1yVNVI29B9IGw41anCEzA5eR2r1pYc7flqD471ZT7B0yUXIY7YNe/zq7LGpihIGXNzWyG+Q4YQSzv2AF3fNA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/jquery": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/json-schema": {
|
||||
"version": "7.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
||||
@ -1377,6 +1413,16 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/select2": {
|
||||
"version": "4.0.63",
|
||||
"resolved": "https://registry.npmjs.org/@types/select2/-/select2-4.0.63.tgz",
|
||||
"integrity": "sha512-/DXUfPSj3iVTGlRYRYPCFKKSogAGP/j+Z0fIMXbBiBtmmZj0WH7vnfNuckafq9C43KnqPPQW2TI/Rj/vTSGnQQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/jquery": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/send": {
|
||||
"version": "0.17.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz",
|
||||
@ -2115,9 +2161,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/bing-translate-api": {
|
||||
"version": "2.9.1",
|
||||
"resolved": "https://registry.npmjs.org/bing-translate-api/-/bing-translate-api-2.9.1.tgz",
|
||||
"integrity": "sha512-DaYqa7iupfj+fj/KeaeZSp5FUY/ZR4sZ6jqoTP0RHkYOUfo7wwoxlhYDkh4VcvBBzuVORnBEgdXBVQrfM4kk7g==",
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/bing-translate-api/-/bing-translate-api-4.0.2.tgz",
|
||||
"integrity": "sha512-JJ8XUehnxzOhHU91oy86xEtp8OOMjVEjCZJX042fKxoO19NNvxJ5omeCcxQNFoPbDqVpBJwqiGVquL0oPdQm1Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"got": "^11.8.6"
|
||||
@ -6531,6 +6577,12 @@
|
||||
"integrity": "sha512-VZBmZP8WU3sMOZm1bdgTadsQbcscK0UM8oKxKVBs4XAhUo2Xxzm/OFMGBkPusxw9xL3Uy8LrzEqGqJhclsr0yA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/slidetoggle": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slidetoggle/-/slidetoggle-4.0.0.tgz",
|
||||
"integrity": "sha512-6qvrOS1dnDFEr41UEEFFRQE8nswaAFIYZAHer6dVlznRIjHyCISjNJoxIn5U5QlAbZfBBxTELQk4jS7miHto1A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/smart-buffer": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
|
||||
@ -7046,6 +7098,15 @@
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/url-join": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/url-join/-/url-join-5.0.0.tgz",
|
||||
"integrity": "sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/utf8-byte-length": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz",
|
||||
|
10
package.json
10
package.json
@ -8,7 +8,7 @@
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@zeldafan0225/ai_horde": "^5.1.0",
|
||||
"archiver": "^7.0.1",
|
||||
"bing-translate-api": "^2.9.1",
|
||||
"bing-translate-api": "^4.0.2",
|
||||
"body-parser": "^1.20.2",
|
||||
"bowser": "^2.11.0",
|
||||
"command-exists": "^1.2.9",
|
||||
@ -53,7 +53,9 @@
|
||||
"showdown": "^2.1.0",
|
||||
"sillytavern-transformers": "2.14.6",
|
||||
"simple-git": "^3.19.1",
|
||||
"slidetoggle": "^4.0.0",
|
||||
"tiktoken": "^1.0.16",
|
||||
"url-join": "^5.0.0",
|
||||
"vectra": "^0.2.2",
|
||||
"wavefile": "^11.0.0",
|
||||
"webpack": "^5.95.0",
|
||||
@ -84,7 +86,7 @@
|
||||
"type": "git",
|
||||
"url": "https://github.com/SillyTavern/SillyTavern.git"
|
||||
},
|
||||
"version": "1.12.9",
|
||||
"version": "1.12.10",
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"start:deno": "deno run --allow-run --allow-net --allow-read --allow-write --allow-sys --allow-env server.js",
|
||||
@ -114,6 +116,9 @@
|
||||
"@types/deno": "^2.0.0",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/jquery": "^3.5.29",
|
||||
"@types/jquery-cropper": "^1.0.4",
|
||||
"@types/jquery.transit": "^0.9.33",
|
||||
"@types/jqueryui": "^1.12.23",
|
||||
"@types/lodash": "^4.17.10",
|
||||
"@types/mime-types": "^2.1.4",
|
||||
"@types/multer": "^1.4.12",
|
||||
@ -123,6 +128,7 @@
|
||||
"@types/png-chunks-encode": "^1.0.2",
|
||||
"@types/png-chunks-extract": "^1.0.2",
|
||||
"@types/response-time": "^2.3.8",
|
||||
"@types/select2": "^4.0.63",
|
||||
"@types/toastr": "^2.1.43",
|
||||
"@types/write-file-atomic": "^4.0.3",
|
||||
"@types/yargs": "^17.0.33",
|
||||
|
@ -65,7 +65,7 @@ label[for="extensions_autoconnect"] {
|
||||
}
|
||||
|
||||
.extensions_info .extension_enabled {
|
||||
color: green;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.extensions_info .extension_disabled {
|
||||
@ -76,13 +76,44 @@ label[for="extensions_autoconnect"] {
|
||||
color: gray;
|
||||
}
|
||||
|
||||
input.extension_missing[type="checkbox"] {
|
||||
opacity: 0.5;
|
||||
.extensions_info .extension_modules {
|
||||
font-size: 0.8em;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
#extensions_list .disabled {
|
||||
text-decoration: line-through;
|
||||
color: lightgray;
|
||||
.extensions_info .extension_block {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
padding: 5px;
|
||||
margin-bottom: 5px;
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
border-radius: 10px;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.extensions_info .extension_name {
|
||||
font-size: 1.05em;
|
||||
}
|
||||
|
||||
.extensions_info .extension_version {
|
||||
opacity: 0.8;
|
||||
font-size: 0.8em;
|
||||
font-weight: normal;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.extensions_info .extension_block a {
|
||||
color: var(--SmartThemeBodyColor);
|
||||
}
|
||||
|
||||
.extensions_info .extension_name.update_available {
|
||||
color: limegreen;
|
||||
}
|
||||
|
||||
input.extension_missing[type="checkbox"] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.update-button {
|
||||
@ -105,3 +136,13 @@ input.extension_missing[type="checkbox"] {
|
||||
#extensionsMenu>div.extension_container:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.extensions_info .extension_text_block {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.extensions_info .extension_actions {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
@ -28,6 +28,11 @@
|
||||
color: var(--white50a);
|
||||
}
|
||||
|
||||
#completion_prompt_manager #completion_prompt_manager_list .completion_prompt_manager_prompt .completion_prompt_manager_prompt_name .fa-solid[data-role] {
|
||||
vertical-align: unset;
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
#completion_prompt_manager #completion_prompt_manager_list .completion_prompt_manager_prompt_invisible {
|
||||
display: none;
|
||||
}
|
||||
@ -260,7 +265,7 @@
|
||||
}
|
||||
|
||||
#completion_prompt_manager #completion_prompt_manager_list .completion_prompt_manager_prompt .completion_prompt_manager_prompt_name .fa-solid.prompt-manager-overridden {
|
||||
margin-left: 5px;
|
||||
margin-left: 3px;
|
||||
color: var(--SmartThemeQuoteColor);
|
||||
cursor: pointer;
|
||||
opacity: 0.8;
|
||||
|
408
public/global.d.ts
vendored
408
public/global.d.ts
vendored
@ -20,400 +20,24 @@ declare global {
|
||||
pagination(method: 'getCurrentPageNum'): number;
|
||||
pagination(method: string, options?: any): JQuery;
|
||||
pagination(options?: any): JQuery;
|
||||
transition(options?: any, complete?: function): JQuery;
|
||||
autocomplete(options?: any): JQuery;
|
||||
autocomplete(method: string, options?: any): JQuery;
|
||||
slider(options?: any): JQuery;
|
||||
slider(method: string, func: string, options?: any): JQuery;
|
||||
cropper(options?: any): JQuery;
|
||||
izoomify(options?: any): JQuery;
|
||||
}
|
||||
|
||||
//#region select2
|
||||
namespace Select2 {
|
||||
interface Options<Result = DataFormat | GroupedDataFormat, RemoteResult = any> {
|
||||
/**
|
||||
* Extends Select2 v4 plugin by adding an option to set a placeholder for the 'search' input field
|
||||
* [Custom Field]
|
||||
* @default ''
|
||||
*/
|
||||
searchInputPlaceholder?: string;
|
||||
|
||||
/**
|
||||
* Initializes or modifies a select2 instance with provided options
|
||||
*
|
||||
* @param options - Configuration options for the select2 instance
|
||||
* @returns The jQuery object for chaining
|
||||
*/
|
||||
select2(options?: Select2Options): JQuery;
|
||||
|
||||
/**
|
||||
* Retrieves data currently selected in the select2 instance
|
||||
*
|
||||
* @param field - A string specifying the 'data' method
|
||||
* @returns An array of selected items
|
||||
*/
|
||||
select2(field: 'data'): any[];
|
||||
|
||||
/**
|
||||
* Calls the specified select2 method
|
||||
*
|
||||
* @param method - The name of the select2 method to invoke
|
||||
* @returns The jQuery object for chaining
|
||||
*/
|
||||
select2(method: 'open' | 'close' | 'destroy' | 'focus' | 'val', value?: any): JQuery;
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region sortable
|
||||
|
||||
/**
|
||||
* Initializes or updates a sortable instance with the provided options
|
||||
*
|
||||
* @param options - Configuration options for the sortable instance
|
||||
* @returns The jQuery object for chaining
|
||||
*/
|
||||
sortable(options?: SortableOptions): JQuery;
|
||||
|
||||
/**
|
||||
* Calls a sortable method to perform actions on the instance
|
||||
*
|
||||
* @param method - The name of the sortable method to invoke
|
||||
* @returns The jQuery object for chaining
|
||||
*/
|
||||
sortable(method: 'destroy' | 'disable' | 'enable' | 'refresh' | 'toArray'): JQuery;
|
||||
|
||||
/**
|
||||
* Retrieves the sortable's instance object. If the element does not have an associated instance, undefined is returned.
|
||||
*
|
||||
* @returns The instance of the sortable object
|
||||
*/
|
||||
sortable(method: 'instance'): object;
|
||||
|
||||
/**
|
||||
* Retrieves the current option value for the specified option
|
||||
*
|
||||
* @param method - The string 'option' to retrieve an option value
|
||||
* @param optionName - The name of the option to retrieve
|
||||
* @returns The value of the specified option
|
||||
*/
|
||||
sortable(method: 'option', optionName: string): any;
|
||||
|
||||
/**
|
||||
* Sets the value of the specified option
|
||||
*
|
||||
* @param method - The string 'option' to set an option value
|
||||
* @param optionName - The name of the option to set
|
||||
* @param value - The value to assign to the option
|
||||
* @returns The jQuery object for chaining
|
||||
*/
|
||||
sortable(method: 'option', optionName: string, value: any): JQuery;
|
||||
|
||||
/**
|
||||
* Sets multiple options using an object
|
||||
*
|
||||
* @param method - The string 'option' to set options
|
||||
* @param options - An object containing multiple option key-value pairs
|
||||
* @returns The jQuery object for chaining
|
||||
*/
|
||||
sortable(method: 'option', options: SortableOptions): JQuery;
|
||||
|
||||
//#endregion
|
||||
/**
|
||||
* Extends select2 plugin by adding a custom css class for the 'search' input field
|
||||
* [Custom Field]
|
||||
* @default ''
|
||||
*/
|
||||
searchInputCssClass?: string;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//#region select2
|
||||
|
||||
/** Options for configuring a select2 instance */
|
||||
interface Select2Options {
|
||||
/**
|
||||
* Provides support for ajax data sources
|
||||
* @param params - Parameters including the search term
|
||||
* @param callback - A callback function to handle the results
|
||||
* @default null
|
||||
*/
|
||||
ajax?: {
|
||||
url: string;
|
||||
dataType?: string;
|
||||
delay?: number;
|
||||
data?: (params: any) => any;
|
||||
processResults?: (data: any, params: any) => any;
|
||||
} | { transport: (params, success, failure) => any };
|
||||
|
||||
/**
|
||||
* Provides support for clearable selections
|
||||
* @default false
|
||||
*/
|
||||
allowClear?: boolean;
|
||||
|
||||
/**
|
||||
* See Using Select2 with AMD or CommonJS loaders
|
||||
* @default './i18n/'
|
||||
*/
|
||||
amdLanguageBase?: string;
|
||||
|
||||
/**
|
||||
* Controls whether the dropdown is closed after a selection is made
|
||||
* @default true
|
||||
*/
|
||||
closeOnSelect?: boolean;
|
||||
|
||||
/**
|
||||
* Allows rendering dropdown options from an array
|
||||
* @default null
|
||||
*/
|
||||
data?: object[];
|
||||
|
||||
/**
|
||||
* Used to override the built-in DataAdapter
|
||||
* @default SelectAdapter
|
||||
*/
|
||||
dataAdapter?: SelectAdapter;
|
||||
|
||||
/**
|
||||
* Enable debugging messages in the browser console
|
||||
* @default false
|
||||
*/
|
||||
debug?: boolean;
|
||||
|
||||
/**
|
||||
* Sets the dir attribute on the selection and dropdown containers to indicate the direction of the text
|
||||
* @default 'ltr'
|
||||
*/
|
||||
dir?: string;
|
||||
|
||||
/**
|
||||
* When set to true, the select control will be disabled
|
||||
* @default false
|
||||
*/
|
||||
disabled?: boolean;
|
||||
|
||||
/**
|
||||
* Used to override the built-in DropdownAdapter
|
||||
* @default DropdownAdapter
|
||||
*/
|
||||
dropdownAdapter?: DropdownAdapter;
|
||||
|
||||
/**
|
||||
* @default false
|
||||
*/
|
||||
dropdownAutoWidth?: boolean;
|
||||
|
||||
/**
|
||||
* Adds additional CSS classes to the dropdown container. The helper :all: can be used to add all CSS classes present on the original <select> element.
|
||||
* @default ''
|
||||
*/
|
||||
dropdownCssClass?: string;
|
||||
|
||||
/**
|
||||
* Allows you to customize placement of the dropdown
|
||||
* @default $(document.body)
|
||||
*/
|
||||
dropdownParent?: JQuery | HTMLElement;
|
||||
|
||||
/**
|
||||
* Handles automatic escaping of content rendered by custom templates
|
||||
* @default Utils.escapeMarkup
|
||||
*/
|
||||
escapeMarkup?: function;
|
||||
|
||||
/**
|
||||
* Specify the language used for Select2 messages
|
||||
* @default EnglishTranslation
|
||||
*/
|
||||
language?: string | object;
|
||||
|
||||
/**
|
||||
* Handles custom search matching
|
||||
* @default null
|
||||
*/
|
||||
matcher?: (searchParams: object, data: object) => boolean;
|
||||
|
||||
/**
|
||||
* Maximum number of characters that may be provided for a search term
|
||||
* @default 0
|
||||
*/
|
||||
maximumInputLength?: number;
|
||||
|
||||
/**
|
||||
* The maximum number of items that may be selected in a multi-select control. If the value of this option is less than 1, the number of selected items will not be limited.
|
||||
* @default 0
|
||||
*/
|
||||
maximumSelectionLength?: number;
|
||||
|
||||
/**
|
||||
* Minimum number of characters required to start a search
|
||||
* @default 0
|
||||
*/
|
||||
minimumInputLength?: number;
|
||||
|
||||
/**
|
||||
* The minimum number of results required to display the search box
|
||||
* @default 0
|
||||
*/
|
||||
minimumResultsForSearch?: number;
|
||||
|
||||
/**
|
||||
* This option enables multi-select (pillbox) mode. Select2 will automatically map the value of the multiple HTML attribute to this option during initialization.
|
||||
* @default false
|
||||
*/
|
||||
multiple?: boolean;
|
||||
|
||||
/**
|
||||
* Specifies the placeholder for the control
|
||||
* @default null
|
||||
*/
|
||||
placeholder?: string;
|
||||
|
||||
/**
|
||||
* Used to override the built-in ResultsAdapter
|
||||
* @default ResultsAdapter
|
||||
*/
|
||||
resultsAdapter?: ResultsAdapter;
|
||||
|
||||
/**
|
||||
* Used to override the built-in SelectionAdapter
|
||||
* @default SingleSelection | MultipleSelection
|
||||
*/
|
||||
selectionAdapter?: SingleSelection | MultipleSelection;
|
||||
|
||||
/**
|
||||
* Adds additional CSS classes to the selection container. The helper :all: can be used to add all CSS classes present on the original <select> element
|
||||
* @default ''
|
||||
*/
|
||||
selectionCssClass?: string;
|
||||
|
||||
/**
|
||||
* Implements automatic selection when the dropdown is closed
|
||||
* @default false
|
||||
*/
|
||||
selectOnClose?: boolean;
|
||||
|
||||
sorter?: function;
|
||||
|
||||
/**
|
||||
* When set to `true`, allows the user to create new tags that aren't pre-populated
|
||||
* Used to enable free text responses
|
||||
* @default false
|
||||
*/
|
||||
tags?: boolean | object[];
|
||||
|
||||
/**
|
||||
* Customizes the way that search results are rendered
|
||||
* @param item - The item object to format
|
||||
* @returns The formatted representation
|
||||
* @default null
|
||||
*/
|
||||
templateResult?: (item: any) => JQuery | string;
|
||||
|
||||
/**
|
||||
* Customizes the way that selections are rendered
|
||||
* @param item - The selected item object to format
|
||||
* @returns The formatted representation
|
||||
* @default null
|
||||
*/
|
||||
templateSelection?: (item: any) => JQuery | string;
|
||||
|
||||
/**
|
||||
* Allows you to set the theme
|
||||
* @default 'default'
|
||||
*/
|
||||
theme?: string;
|
||||
|
||||
/**
|
||||
* A callback that handles automatic tokenization of free-text entry
|
||||
* @default null
|
||||
*/
|
||||
tokenizer?: (input: { _type: string, term: string }, selection: { options: object }, callback: (Select2Option) => any) => { term: string };
|
||||
|
||||
/**
|
||||
* The list of characters that should be used as token separators
|
||||
* @default null
|
||||
*/
|
||||
tokenSeparators?: string[];
|
||||
|
||||
/**
|
||||
* Supports customization of the container width
|
||||
* @default 'resolve'
|
||||
*/
|
||||
width?: string;
|
||||
|
||||
/**
|
||||
* If true, resolves issue for multiselects using closeOnSelect: false that caused the list of results to scroll to the first selection after each select/unselect
|
||||
* @default false
|
||||
*/
|
||||
scrollAfterSelect?: boolean;
|
||||
|
||||
/**
|
||||
* Extends Select2 v4 plugin by adding an option to set a placeholder for the 'search' input field
|
||||
* [Custom Field]
|
||||
* @default ''
|
||||
*/
|
||||
searchInputPlaceholder?: string;
|
||||
|
||||
/**
|
||||
* Extends select2 plugin by adding a custom css class for the 'searcH' input field
|
||||
* [Custom Field]
|
||||
* @default ''
|
||||
*/
|
||||
searchInputCssClass?: string;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region sortable
|
||||
|
||||
/** Options for configuring a sortable instance */
|
||||
interface SortableOptions {
|
||||
/**
|
||||
* When set, prevents the sortable items from being dragged unless clicked with a delay
|
||||
* @default 0
|
||||
*/
|
||||
delay?: number;
|
||||
|
||||
/**
|
||||
* Class name for elements to handle sorting. Elements with this class can be dragged to sort.
|
||||
* @default ''
|
||||
*/
|
||||
handle?: string;
|
||||
|
||||
/**
|
||||
* Whether to allow sorting between different connected lists
|
||||
* @default false
|
||||
*/
|
||||
connectWith?: string | boolean;
|
||||
|
||||
/**
|
||||
* Function called when sorting starts
|
||||
* @param event - The event object
|
||||
* @param ui - The UI object containing the helper and position information
|
||||
*/
|
||||
start?: (event: Event, ui: SortableUI) => void;
|
||||
|
||||
/**
|
||||
* Function called when sorting stops
|
||||
* @param event - The event object
|
||||
* @param ui - The UI object containing the helper and position information
|
||||
*/
|
||||
stop?: (event: Event, ui: SortableUI) => void;
|
||||
|
||||
/**
|
||||
* Function called when sorting updates
|
||||
* @param event - The event object
|
||||
* @param ui - The UI object containing the helper and position information
|
||||
*/
|
||||
update?: (event: Event, ui: SortableUI) => void;
|
||||
|
||||
/**
|
||||
* Specifies which items inside the element should be sortable
|
||||
* @default '> *'
|
||||
*/
|
||||
items?: string;
|
||||
}
|
||||
|
||||
/** UI object passed to sortable event handlers */
|
||||
interface SortableUI {
|
||||
/** jQuery object representing the helper element */
|
||||
helper: JQuery;
|
||||
/** The current position of the helper element */
|
||||
position: { top: number; left: number };
|
||||
/** Original position of the helper element */
|
||||
originalPosition: { top: number; left: number };
|
||||
/** jQuery object representing the item being sorted */
|
||||
item: JQuery;
|
||||
/** The placeholder element used during sorting */
|
||||
placeholder: JQuery;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
6
public/img/deepseek.svg
Normal file
6
public/img/deepseek.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="128px" height="128px" viewBox="0 0 128 128" version="1.1">
|
||||
<g id="surface1">
|
||||
<path d="M 126.65625 23.902344 C 125.300781 23.242188 124.714844 24.507812 123.925781 25.152344 C 123.652344 25.359375 123.425781 25.632812 123.195312 25.878906 C 121.210938 27.996094 118.894531 29.382812 115.871094 29.214844 C 111.449219 28.96875 107.675781 30.355469 104.335938 33.738281 C 103.625 29.566406 101.269531 27.082031 97.683594 25.484375 C 95.808594 24.652344 93.910156 23.824219 92.59375 22.015625 C 91.675781 20.730469 91.425781 19.296875 90.964844 17.886719 C 90.671875 17.035156 90.378906 16.164062 89.402344 16.019531 C 88.335938 15.855469 87.921875 16.746094 87.503906 17.492188 C 85.835938 20.542969 85.1875 23.902344 85.253906 27.308594 C 85.398438 34.964844 88.628906 41.066406 95.054688 45.402344 C 95.785156 45.898438 95.972656 46.398438 95.742188 47.125 C 95.308594 48.617188 94.785156 50.070312 94.324219 51.566406 C 94.03125 52.523438 93.59375 52.726562 92.570312 52.316406 C 89.109375 50.828125 85.96875 48.691406 83.3125 46.019531 C 78.742188 41.605469 74.613281 36.730469 69.460938 32.910156 C 68.269531 32.03125 67.042969 31.191406 65.785156 30.398438 C 60.535156 25.296875 66.480469 21.105469 67.855469 20.609375 C 69.296875 20.085938 68.351562 18.304688 63.703125 18.324219 C 59.050781 18.347656 54.792969 19.898438 49.371094 21.972656 C 48.566406 22.28125 47.734375 22.527344 46.890625 22.703125 C 41.820312 21.75 36.636719 21.566406 31.515625 22.160156 C 21.460938 23.28125 13.433594 28.039062 7.53125 36.148438 C 0.4375 45.898438 -1.230469 56.980469 0.8125 68.535156 C 2.960938 80.714844 9.179688 90.800781 18.730469 98.683594 C 28.640625 106.859375 40.046875 110.863281 53.066406 110.097656 C 60.96875 109.644531 69.777344 108.582031 79.703125 100.175781 C 82.207031 101.425781 84.832031 101.921875 89.195312 102.292969 C 92.554688 102.609375 95.785156 102.132812 98.289062 101.609375 C 102.207031 100.777344 101.9375 97.148438 100.523438 96.484375 C 89.03125 91.128906 91.550781 93.3125 89.253906 91.546875 C 95.097656 84.632812 103.898438 77.457031 107.34375 54.199219 C 107.609375 52.347656 107.382812 51.183594 107.34375 49.691406 C 107.324219 48.785156 107.53125 48.425781 108.570312 48.324219 C 111.457031 48.027344 114.253906 47.164062 116.8125 45.792969 C 124.257812 41.722656 127.265625 35.046875 127.972656 27.035156 C 128.078125 25.808594 127.953125 24.542969 126.65625 23.898438 Z M 61.765625 96 C 50.625 87.242188 45.222656 84.355469 42.992188 84.480469 C 40.902344 84.609375 41.28125 86.992188 41.738281 88.550781 C 42.21875 90.085938 42.84375 91.140625 43.71875 92.492188 C 44.324219 93.382812 44.742188 94.710938 43.113281 95.707031 C 39.523438 97.925781 33.289062 94.960938 32.996094 94.816406 C 25.738281 90.539062 19.664062 84.894531 15.390625 77.179688 C 11.265625 69.75 8.863281 61.78125 8.46875 53.273438 C 8.363281 51.214844 8.964844 50.492188 11.011719 50.117188 C 13.703125 49.601562 16.457031 49.53125 19.167969 49.910156 C 30.539062 51.574219 40.214844 56.65625 48.332031 64.703125 C 52.960938 69.289062 56.464844 74.769531 60.074219 80.121094 C 63.914062 85.808594 68.042969 91.226562 73.300781 95.664062 C 75.15625 97.222656 76.632812 98.40625 78.054688 99.273438 C 73.777344 99.753906 66.640625 99.863281 61.765625 96 Z M 67.097656 61.652344 C 67.097656 61.117188 67.359375 60.609375 67.800781 60.304688 C 68.246094 60 68.808594 59.929688 69.3125 60.121094 C 70.378906 62.097656 70.207031 62.515625 69.894531 62.824219 C 69.585938 63.132812 69.164062 63.300781 68.726562 63.296875 C 68.292969 63.296875 67.875 63.125 67.570312 62.816406 C 67.265625 62.507812 67.097656 62.085938 67.105469 61.652344 Z M 83.683594 70.164062 C 82.617188 70.597656 81.558594 70.96875 80.539062 71.019531 C 79.007812 71.070312 77.503906 70.59375 76.28125 69.664062 C 74.820312 68.4375 73.777344 67.753906 73.339844 65.621094 C 73.1875 64.578125 73.214844 63.519531 73.425781 62.484375 C 73.796875 60.742188 73.382812 59.621094 72.148438 58.609375 C 71.152344 57.777344 69.878906 57.546875 68.480469 57.546875 C 68 57.519531 67.535156 57.375 67.125 57.128906 C 66.539062 56.84375 66.058594 56.117188 66.515625 55.222656 C 66.667969 54.933594 67.371094 54.230469 67.542969 54.101562 C 69.441406 53.023438 71.632812 53.375 73.652344 54.1875 C 75.53125 54.953125 76.949219 56.363281 78.992188 58.355469 C 81.078125 60.761719 81.457031 61.429688 82.644531 63.230469 C 83.585938 64.644531 84.4375 66.097656 85.019531 67.753906 C 85.375 68.792969 84.917969 69.644531 83.683594 70.164062 Z M 83.683594 70.164062 "/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.6 KiB |
15
public/img/generic.svg
Normal file
15
public/img/generic.svg
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="616.86328"
|
||||
height="616.86328"
|
||||
viewBox="0 0 77.10791 77.10791"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<path
|
||||
id="circle1"
|
||||
d="M 38.553955,0 A 38.554,38.554 0 0 0 0,38.553955 38.554,38.554 0 0 0 38.553955,77.107911 38.554,38.554 0 0 0 77.107911,38.553955 38.554,38.554 0 0 0 38.553955,0 Z m -3.125976,13.545898 h 6.251953 v 19.593751 l 16.968751,-9.796875 3.125974,5.414306 -16.968506,9.796875 16.968506,9.796877 -3.125974,5.414305 -16.968751,-9.796874 V 63.56201 H 35.427979 V 43.968263 l -16.968506,9.796874 -3.125977,-5.414305 16.96875,-9.796877 -16.96875,-9.796875 3.125977,-5.414306 16.968506,9.796875 z" />
|
||||
</svg>
|
After Width: | Height: | Size: 803 B |
@ -651,6 +651,19 @@
|
||||
<input type="number" id="n_openai" name="n_openai" class="text_pole" min="1" value="1">
|
||||
</div>
|
||||
</div>
|
||||
<div data-source="openrouter" class="range-block">
|
||||
<div class="range-block-title" title="Allow compressing requests by removing messages from the middle of the prompt.">
|
||||
<span data-i18n="Middle-out Transform">Middle-out Transform</span>
|
||||
<a href="https://openrouter.ai/docs/transforms" target="_blank" rel="noreferrer noopener" class="note-link-span fa-solid fa-circle-question"></a>
|
||||
</div>
|
||||
<div class="wide100p">
|
||||
<select id="openrouter_middleout" class="text_pole">
|
||||
<option value="auto" data-i18n="Auto">Auto</option>
|
||||
<option value="on" data-i18n="Allow">Allow</option>
|
||||
<option value="off" data-i18n="Forbid">Forbid</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div data-source="openrouter">
|
||||
Max prompt cost: <span id="openrouter_max_prompt_cost">Unknown</span>
|
||||
</div>
|
||||
@ -669,7 +682,7 @@
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="openai,claude,windowai,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt">
|
||||
<div class="range-block" data-source="openai,claude,windowai,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt,deepseek">
|
||||
<div class="range-block-title" data-i18n="Temperature">
|
||||
Temperature
|
||||
</div>
|
||||
@ -682,7 +695,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="openai,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt">
|
||||
<div class="range-block" data-source="openai,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt,deepseek">
|
||||
<div class="range-block-title" data-i18n="Frequency Penalty">
|
||||
Frequency Penalty
|
||||
</div>
|
||||
@ -695,7 +708,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="openai,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt">
|
||||
<div class="range-block" data-source="openai,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt,deepseek">
|
||||
<div class="range-block-title" data-i18n="Presence Penalty">
|
||||
Presence Penalty
|
||||
</div>
|
||||
@ -721,7 +734,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="openai,claude,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt">
|
||||
<div class="range-block" data-source="openai,claude,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt,deepseek">
|
||||
<div class="range-block-title" data-i18n="Top P">
|
||||
Top P
|
||||
</div>
|
||||
@ -1235,7 +1248,8 @@
|
||||
<input class="neo-range-slider" type="range" id="temp_textgenerationwebui" name="volume" min="0.0" max="5.0" step="0.01" x-setting-id="temp">
|
||||
<input class="neo-range-input" type="number" min="0.0" max="5.0" step="0.01" data-for="temp_textgenerationwebui" id="temp_counter_textgenerationwebui">
|
||||
</div>
|
||||
<div class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
|
||||
<!-- Note: "except" mode = show for all BUT types in data-tg-type -->
|
||||
<div data-tg-type-mode="except" data-tg-type="generic" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
|
||||
<small>
|
||||
<span data-i18n="Top K">Top K</span>
|
||||
<div class="fa-solid fa-circle-info opacity50p" title="Top K sets a maximum amount of top tokens that can be chosen from. E.g Top K is 20, this means only the 20 highest ranking tokens will be kept (regardless of their probabilities being diverse or limited). Set to 0 (or -1, depending on your backend) to disable." data-i18n="[title]Top_K_desc"></div>
|
||||
@ -1251,7 +1265,7 @@
|
||||
<input class="neo-range-slider" type="range" id="top_p_textgenerationwebui" name="volume" min="0" max="1" step="0.01">
|
||||
<input class="neo-range-input" type="number" min="0" max="1" step="0.01" data-for="top_p_textgenerationwebui" id="top_p_counter_textgenerationwebui">
|
||||
</div>
|
||||
<div class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
|
||||
<div data-tg-type-mode="except" data-tg-type="generic" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
|
||||
<small>
|
||||
<span data-i18n="Typical P">Typical P</span>
|
||||
<div class="fa-solid fa-circle-info opacity50p" title="Typical P Sampling prioritizes tokens based on their deviation from the average entropy of the set. It maintains tokens whose cumulative probability is close to a predefined threshold (e.g., 0.5), emphasizing those with average information content. Set to 1.0 to disable." data-i18n="[title]Typical_P_desc"></div>
|
||||
@ -1259,7 +1273,7 @@
|
||||
<input class="neo-range-slider" type="range" id="typical_p_textgenerationwebui" name="volume" min="0" max="1" step="0.01">
|
||||
<input class="neo-range-input" type="number" min="0" max="1" step="0.01" data-for="typical_p_textgenerationwebui" id="typical_p_counter_textgenerationwebui">
|
||||
</div>
|
||||
<div class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
|
||||
<div data-tg-type-mode="except" data-tg-type="generic" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
|
||||
<small>
|
||||
<span data-i18n="Min P">Min P</span>
|
||||
<div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]Min_P_desc" title="Min P sets a base minimum probability. This is scaled according to the top token's probability. E.g If Top token is 80% probability, and Min P is 0.1, only tokens higher than 8% would be considered. Set to 0 to disable."></div>
|
||||
@ -1267,7 +1281,7 @@
|
||||
<input class="neo-range-slider" type="range" id="min_p_textgenerationwebui" name="volume" min="0" max="1" step="0.001">
|
||||
<input class="neo-range-input" type="number" min="0" max="1" step="0.001" data-for="min_p_textgenerationwebui" id="min_p_counter_textgenerationwebui">
|
||||
</div>
|
||||
<div class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
|
||||
<div data-tg-type-mode="except" data-tg-type="generic" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
|
||||
<small>
|
||||
<span data-i18n="Top A">Top A</span>
|
||||
<div class="fa-solid fa-circle-info opacity50p" title="Top A sets a threshold for token selection based on the square of the highest token probability. E.g if the Top-A value is 0.2 and the top token's probability is 50%, tokens with probabilities below 5% (0.2 * 0.5^2) are excluded. Set to 0 to disable." data-i18n="[title]Top_A_desc"></div>
|
||||
@ -1275,7 +1289,7 @@
|
||||
<input class="neo-range-slider" type="range" id="top_a_textgenerationwebui" name="volume" min="0" max="1" step="0.01">
|
||||
<input class="neo-range-input" type="number" min="0" max="1" step="0.01" data-for="top_a_textgenerationwebui" id="top_a_counter_textgenerationwebui">
|
||||
</div>
|
||||
<div class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
|
||||
<div data-tg-type-mode="except" data-tg-type="generic" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
|
||||
<small>
|
||||
<span data-i18n="TFS">TFS</span>
|
||||
<div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]Tail_Free_Sampling_desc" title="Tail-Free Sampling (TFS) searches for a tail of low-probability tokens in the distribution, by analyzing the rate of change in token probabilities using derivatives. It retains tokens up to a threshold (e.g., 0.3) based on the normalized second derivative. The closer to 0, the more discarded tokens. Set to 1.0 to disable."></div>
|
||||
@ -1307,7 +1321,7 @@
|
||||
<input class="neo-range-slider" type="range" id="eta_cutoff_textgenerationwebui" name="volume" min="0" max="20" step="0.01">
|
||||
<input class="neo-range-input" type="number" min="0" max="20" step="0.01" data-for="eta_cutoff_textgenerationwebui" id="eta_cutoff_counter_textgenerationwebui">
|
||||
</div>
|
||||
<div class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
|
||||
<div data-tg-type-mode="except" data-tg-type="generic" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
|
||||
<small data-i18n="rep.pen">Repetition Penalty</small>
|
||||
<input class="neo-range-slider" type="range" id="rep_pen_textgenerationwebui" name="volume" min="1" max="3" step="0.01">
|
||||
<input class="neo-range-input" type="number" min="1" max="3" step="0.01" data-for="rep_pen_textgenerationwebui" id="rep_pen_counter_textgenerationwebui">
|
||||
@ -1569,7 +1583,7 @@
|
||||
<small data-i18n="Ignore EOS Token">Ignore EOS Token</small>
|
||||
<div class="fa-solid fa-circle-info opacity50p " data-i18n="[title]Ignore the EOS Token even if it generates." title="Ignore the EOS Token even if it generates."></div>
|
||||
</label>
|
||||
<label class="checkbox_label flexGrow flexShrink" for="skip_special_tokens_textgenerationwebui">
|
||||
<label data-tg-type-mode="except" data-tg-type="generic" class="checkbox_label flexGrow flexShrink" for="skip_special_tokens_textgenerationwebui">
|
||||
<input type="checkbox" id="skip_special_tokens_textgenerationwebui" />
|
||||
<small data-i18n="Skip Special Tokens">Skip Special Tokens</small>
|
||||
</label>
|
||||
@ -1594,14 +1608,14 @@
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div data-tg-type="mancer, ooba, koboldcpp, vllm, aphrodite, llamacpp, ollama, infermaticai, huggingface" class="flex-container flexFlowColumn alignitemscenter flexBasis48p flexGrow flexShrink gap0">
|
||||
<div data-tg-type="mancer, ooba, koboldcpp, vllm, aphrodite, llamacpp, ollama, infermaticai, huggingface, generic" class="flex-container flexFlowColumn alignitemscenter flexBasis48p flexGrow flexShrink gap0">
|
||||
<label>
|
||||
<small data-i18n="Seed">Seed</small>
|
||||
<div class="fa-solid fa-circle-info opacity50p " data-i18n="[title]Seed_desc" title="A random seed to use for deterministic and reproducable outputs. Set to -1 to use a random seed."></div>
|
||||
</label>
|
||||
<input type="number" id="seed_textgenerationwebui" class="text_pole textAlignCenter" min="-1" value="-1" />
|
||||
</div>
|
||||
<div id="banned_tokens_block_ooba" class="wide100p">
|
||||
<div data-tg-type-mode="except" data-tg-type="generic" id="banned_tokens_block_ooba" class="wide100p">
|
||||
<hr class="width100p">
|
||||
<h4 class="range-block-title justifyCenter">
|
||||
<span data-i18n="Banned Tokens">Banned Tokens/Strings</span>
|
||||
@ -1735,6 +1749,7 @@
|
||||
<div data-name="tfs_z" draggable="true"><span>Tail Free Sampling</span><small></small></div>
|
||||
<div data-name="min_p" draggable="true"><span>Min P</span><small></small></div>
|
||||
<div data-name="xtc" draggable="true"><span>Exclude Top Choices</span><small></small></div>
|
||||
<div data-name="dry" draggable="true"><span>DRY</span><small></small></div>
|
||||
</div>
|
||||
<div id="llamacpp_samplers_default_order" class="menu_button menu_button_icon">
|
||||
<span data-i18n="Load default order">Load default order</span>
|
||||
@ -1774,6 +1789,35 @@
|
||||
<span data-i18n="Load default order">Load default order</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="sampler_priority_block_aphrodite" data-tg-type="aphrodite" class="range-block flexFlowColumn wide100p">
|
||||
<hr class="wide100p">
|
||||
<h4 class="range-block-title justifyCenter">
|
||||
<span data-i18n="Sampler Order">Sampler Order</span>
|
||||
<div class="margin5 fa-solid fa-circle-info opacity50p" title="Aphrodite only. Determines the order of samplers. Skew is always applied post-softmax, so it's not included here." data-i18n="[title]Aphrodite only. Determines the order of samplers. Skew is always applied post-softmax, so it's not included here."></div>
|
||||
</h4>
|
||||
<div class="toggle-description widthUnset" data-i18n="Aphrodite only. Determines the order of samplers.">
|
||||
Aphrodite only. Determines the order of samplers.
|
||||
</div>
|
||||
<div id="sampler_priority_container_aphrodite" class="prompt_order">
|
||||
<div data-name="dry" draggable="true"><span>DRY</span><small></small></div>
|
||||
<div data-name="penalties" draggable="true"><span>Penalties</span><small></small></div>
|
||||
<div data-name="no_repeat_ngram" draggable="true"><span>No Repeat Ngram</span><small></small></div>
|
||||
<div data-name="temperature" draggable="true"><span>Dynatemp & Temperature</span><small></small></div>
|
||||
<div data-name="top_nsigma" draggable="true"><span>Top Nsigma</span><small></small></div>
|
||||
<div data-name="top_p_top_k" draggable="true"><span>Top P & Top K</span><small></small></div>
|
||||
<div data-name="top_a" draggable="true"><span>Top A</span><small></small></div>
|
||||
<div data-name="min_p" draggable="true"><span>Min P</span><small></small></div>
|
||||
<div data-name="tfs" draggable="true"><span>Tail-Free Sampling</span><small></small></div>
|
||||
<div data-name="eta_cutoff" draggable="true"><span>Eta Cutoff</span><small></small></div>
|
||||
<div data-name="epsilon_cutoff" draggable="true"><span>Epsilon Cutoff</span><small></small></div>
|
||||
<div data-name="typical_p" draggable="true"><span>Typical P</span><small></small></div>
|
||||
<div data-name="quadratic" draggable="true"><span>Cubic and Quadratic Sampling</span><small></small></div>
|
||||
<div data-name="xtc" draggable="true"><span>XTC</span><small></small></div>
|
||||
</div>
|
||||
<div id="aphrodite_default_order" class="menu_button menu_button_icon">
|
||||
<span data-i18n="Load default order">Load default order</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- end of textgen settings-->
|
||||
<div id="openai_settings">
|
||||
@ -1921,8 +1965,8 @@
|
||||
<label for="use_makersuite_sysprompt" class="checkbox_label widthFreeExpand">
|
||||
<input id="use_makersuite_sysprompt" type="checkbox" />
|
||||
<span>
|
||||
<span data-i18n="Use system prompt">Use system prompt</span><br>
|
||||
<small data-i18n="(Gemini 1.5 Pro/Flash only)">(Gemini 1.5 Pro/Flash only)</small>
|
||||
<span data-i18n="Use system prompt">Use system prompt</span>
|
||||
<i class="opacity50p fa-solid fa-circle-info" title="Gemini 1.5/2.0 Pro/Flash"></i>
|
||||
</span>
|
||||
</label>
|
||||
<div class="toggle-description justifyLeft marginBot5">
|
||||
@ -1931,6 +1975,20 @@
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="makersuite">
|
||||
<label for="openai_show_thoughts" class="checkbox_label widthFreeExpand">
|
||||
<input id="openai_show_thoughts" type="checkbox" />
|
||||
<span>
|
||||
<span data-i18n="Show model thoughts">Show model thoughts</span>
|
||||
<i class="opacity50p fa-solid fa-circle-info" title="Gemini 2.0 Thinking"></i>
|
||||
</span>
|
||||
</label>
|
||||
<div class="toggle-description justifyLeft marginBot5">
|
||||
<span data-i18n="Display the model's internal thoughts in the response.">
|
||||
Display the model's internal thoughts in the response.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="claude">
|
||||
<div class="wide100p">
|
||||
<div class="flex-container alignItemsCenter">
|
||||
@ -2022,7 +2080,7 @@
|
||||
<option value="textgenerationwebui" data-i18n="Text Completion">Text Completion</option>
|
||||
<option value="openai" data-i18n="Chat Completion">Chat Completion</option>
|
||||
<option value="novel" data-i18n="NovelAI">NovelAI</option>
|
||||
<option value="koboldhorde" data-i18n="KoboldAI Horde">KoboldAI Horde</option>
|
||||
<option value="koboldhorde" data-i18n="AI Horde">AI Horde</option>
|
||||
<option value="kobold" data-i18n="KoboldAI">KoboldAI Classic</option>
|
||||
</select>
|
||||
</div>
|
||||
@ -2031,8 +2089,8 @@
|
||||
<div id="kobold_horde_block">
|
||||
<ul>
|
||||
<li>
|
||||
<a target="_blank" href="https://aihorde.net/" data-i18n="KoboldAI Horde Website">
|
||||
KoboldAI Horde Website
|
||||
<a target="_blank" href="https://aihorde.net/" data-i18n="AI Horde Website">
|
||||
AI Horde Website
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
@ -2158,10 +2216,10 @@
|
||||
<div>
|
||||
<h4 data-i18n="API Type">API Type</h4>
|
||||
<select id="textgen_type">
|
||||
<option value="ooba" data-i18n="Default (completions compatible)">Default [OpenAI /completions compatible: oobabooga, LM Studio, etc.]</option>
|
||||
<option value="aphrodite">Aphrodite</option>
|
||||
<option value="dreamgen">DreamGen</option>
|
||||
<option value="featherless">Featherless</option>
|
||||
<option value="generic" data-i18n="Generic (OpenAI-compatible) [LM Studio, LiteLLM, etc.]">Generic (OpenAI-compatible) [LM Studio, LiteLLM, etc.]</option>
|
||||
<option value="huggingface">HuggingFace (Inference Endpoint)</option>
|
||||
<option value="infermaticai">InfermaticAI</option>
|
||||
<option value="koboldcpp">KoboldCpp</option>
|
||||
@ -2170,6 +2228,7 @@
|
||||
<option value="ollama">Ollama</option>
|
||||
<option value="openrouter">OpenRouter</option>
|
||||
<option value="tabby">TabbyAPI</option>
|
||||
<option value="ooba">Text Generation WebUI (oobabooga)</option>
|
||||
<option value="togetherai">TogetherAI</option>
|
||||
<option value="vllm">vLLM</option>
|
||||
</select>
|
||||
@ -2292,6 +2351,24 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div data-tg-type="generic" class="flex-container flexFlowColumn">
|
||||
<h4 data-i18n="API key (optional)">API key (optional)</h4>
|
||||
<div class="flex-container">
|
||||
<input id="api_key_generic" name="api_key_generic" class="text_pole flex1 wide100p" type="text" autocomplete="off">
|
||||
<div title="Clear your API key" data-i18n="[title]Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_generic">
|
||||
</div>
|
||||
</div>
|
||||
<div data-for="api_key_generic" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">
|
||||
For privacy reasons, your API key will be hidden after you reload the page.
|
||||
</div>
|
||||
<div class="flex1">
|
||||
<h4 data-i18n="Server url">Server URL</h4>
|
||||
<small data-i18n="Example: 127.0.0.1:5000">Example: http://127.0.0.1:5000</small>
|
||||
<input id="generic_api_url_text" name="generic_api_url" class="text_pole wide100p" value="" autocomplete="off" data-server-history="generic">
|
||||
</div>
|
||||
<datalist id="generic_model_fill"></datalist>
|
||||
<input id="generic_model_textgenerationwebui" list="generic_model_fill" class="text_pole wide100p" placeholder="Model ID (optional)" data-i18n="[placeholder]Model ID (optional)" type="text">
|
||||
</div>
|
||||
<div data-tg-type="ooba" class="flex-container flexFlowColumn">
|
||||
<div class="flex-container flexFlowColumn">
|
||||
<a href="https://github.com/oobabooga/text-generation-webui" target="_blank">
|
||||
@ -2567,7 +2644,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-container flexFlowColumn marginTopBot5">
|
||||
<label data-tg-type="ooba" class="checkbox_label" for="bypass_status_check_textgenerationwebui">
|
||||
<label data-tg-type="ooba, generic" class="checkbox_label" for="bypass_status_check_textgenerationwebui">
|
||||
<input type="checkbox" id="bypass_status_check_textgenerationwebui" />
|
||||
<span data-i18n="Bypass status check">Bypass status check</span>
|
||||
</label>
|
||||
@ -2602,6 +2679,7 @@
|
||||
<option value="blockentropy">Block Entropy</option>
|
||||
<option value="claude">Claude</option>
|
||||
<option value="cohere">Cohere</option>
|
||||
<option value="deepseek">DeepSeek</option>
|
||||
<option value="groq">Groq</option>
|
||||
<option value="makersuite">Google AI Studio</option>
|
||||
<option value="mistralai">MistralAI</option>
|
||||
@ -2976,33 +3054,31 @@
|
||||
<optgroup label="Primary">
|
||||
<option value="gemini-1.5-pro">Gemini 1.5 Pro</option>
|
||||
<option value="gemini-1.5-flash">Gemini 1.5 Flash</option>
|
||||
<option value="gemini-1.0-pro">Gemini 1.0 Pro</option>
|
||||
<option value="gemini-pro">Gemini Pro (1.0)</option>
|
||||
<option value="gemini-pro-vision">Gemini Pro Vision (1.0)</option>
|
||||
<option value="gemini-1.0-pro">Gemini 1.0 Pro (Deprecated)</option>
|
||||
<option value="gemini-pro">Gemini Pro (1.0) (Deprecated)</option>
|
||||
<option value="gemini-ultra">Gemini Ultra (1.0)</option>
|
||||
<option value="gemini-1.0-ultra-latest">Gemini 1.0 Ultra</option>
|
||||
<option value="text-bison-001">PaLM 2 (Legacy)</option>
|
||||
<option value="chat-bison-001">PaLM 2 Chat (Legacy)</option>
|
||||
</optgroup>
|
||||
<optgroup label="Subversions">
|
||||
<option value="gemini-exp-1121">Gemini Experimental 2024-11-21</option>
|
||||
<option value="gemini-2.0-flash-thinking-exp-1219">Gemini 2.0 Flash Thinking Experimental</option>
|
||||
<option value="gemini-2.0-flash-exp">Gemini 2.0 Flash Experimental</option>
|
||||
<option value="gemini-exp-1114">Gemini Experimental 2024-11-14</option>
|
||||
<option value="gemini-exp-1121">Gemini Experimental 2024-11-21</option>
|
||||
<option value="gemini-exp-1206">Gemini Experimental 2024-12-06</option>
|
||||
<option value="gemini-1.5-pro-exp-0801">Gemini 1.5 Pro Experiment 2024-08-01</option>
|
||||
<option value="gemini-1.5-pro-exp-0827">Gemini 1.5 Pro Experiment 2024-08-27</option>
|
||||
<option value="gemini-1.5-pro-exp-0801">Gemini 1.5 Pro Experimental 2024-08-01</option>
|
||||
<option value="gemini-1.5-pro-exp-0827">Gemini 1.5 Pro Experimental 2024-08-27</option>
|
||||
<option value="gemini-1.5-pro-latest">Gemini 1.5 Pro [latest]</option>
|
||||
<option value="gemini-1.5-pro-001">Gemini 1.5 Pro [001]</option>
|
||||
<option value="gemini-1.5-pro-002">Gemini 1.5 Pro [002]</option>
|
||||
<option value="gemini-1.5-flash-8b">Gemini 1.5 Flash 8B</option>
|
||||
<option value="gemini-1.5-flash-exp-0827">Gemini 1.5 Flash Experiment 2024-08-27</option>
|
||||
<option value="gemini-1.5-flash-8b-exp-0827">Gemini 1.5 Flash 8B Experiment 2024-08-27</option>
|
||||
<option value="gemini-1.5-flash-8b-exp-0924">Gemini 1.5 Flash 8B Experiment 2024-09-24</option>
|
||||
<option value="gemini-1.5-flash-exp-0827">Gemini 1.5 Flash Experimental 2024-08-27</option>
|
||||
<option value="gemini-1.5-flash-8b-exp-0827">Gemini 1.5 Flash 8B Experimental 2024-08-27</option>
|
||||
<option value="gemini-1.5-flash-8b-exp-0924">Gemini 1.5 Flash 8B Experimental 2024-09-24</option>
|
||||
<option value="gemini-1.5-flash-latest">Gemini 1.5 Flash [latest]</option>
|
||||
<option value="gemini-1.5-flash-001">Gemini 1.5 Flash [001]</option>
|
||||
<option value="gemini-1.5-flash-002">Gemini 1.5 Flash [002]</option>
|
||||
<option value="gemini-1.0-pro-latest">Gemini 1.0 Pro [latest]</option>
|
||||
<option value="gemini-1.0-pro-001">Gemini 1.0 Pro (Tuning) [001]</option>
|
||||
<option value="gemini-1.0-pro-vision-latest">Gemini 1.0 Pro Vision [latest]</option>
|
||||
<option value="gemini-1.0-pro-latest">Gemini 1.0 Pro [latest] (Deprecated)</option>
|
||||
<option value="gemini-1.0-pro-001">Gemini 1.0 Pro (Tuning) [001] (Deprecated)</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
@ -3071,6 +3147,9 @@
|
||||
</div>
|
||||
<h4 data-i18n="Groq Model">Groq Model</h4>
|
||||
<select id="model_groq_select">
|
||||
<optgroup label="Llama 3.3">
|
||||
<option value="llama-3.3-70b-versatile">llama-3.3-70b-versatile</option>
|
||||
</optgroup>
|
||||
<optgroup label="Llama 3.2">
|
||||
<option value="llama-3.2-1b-preview">llama-3.2-1b-preview</option>
|
||||
<option value="llama-3.2-3b-preview">llama-3.2-3b-preview</option>
|
||||
@ -3114,6 +3193,23 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div id="deepseek_form" data-source="deepseek">
|
||||
<h4 data-i18n="DeepSeek API Key">DeepSeek API Key</h4>
|
||||
<div class="flex-container">
|
||||
<input id="api_key_deepseek" name="api_key_deepseek" class="text_pole flex1" value="" type="text" autocomplete="off">
|
||||
<div title="Clear your API key" data-i18n="[title]Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_deepseek"></div>
|
||||
</div>
|
||||
<div data-for="api_key_deepseek" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">
|
||||
For privacy reasons, your API key will be hidden after you reload the page.
|
||||
</div>
|
||||
<div>
|
||||
<h4 data-i18n="DeepSeek Model">DeepSeek Model</h4>
|
||||
<select id="model_deepseek_select">
|
||||
<option value="deepseek-chat">deepseek-chat</option>
|
||||
<option value="deepseek-coder">deepseek-coder</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div id="perplexity_form" data-source="perplexity">
|
||||
<h4 data-i18n="Perplexity API Key">Perplexity API Key</h4>
|
||||
<div class="flex-container">
|
||||
@ -3167,16 +3263,17 @@
|
||||
<h4 data-i18n="Cohere Model">Cohere Model</h4>
|
||||
<select id="model_cohere_select">
|
||||
<optgroup label="Stable">
|
||||
<option value="c4ai-aya-expanse-32b">c4ai-aya-expanse-32b</option>
|
||||
<option value="c4ai-aya-expanse-8b">c4ai-aya-expanse-8b</option>
|
||||
<option value="c4ai-aya-23-35b">c4ai-aya-23-35b</option>
|
||||
<option value="c4ai-aya-23-8b">c4ai-aya-23-8b</option>
|
||||
<option value="c4ai-aya-23">c4ai-aya-23</option>
|
||||
<option value="c4ai-aya-expanse-8b">c4ai-aya-expanse-8b</option>
|
||||
<option value="c4ai-aya-expanse-32b">c4ai-aya-expanse-32b</option>
|
||||
<option value="command-light">command-light</option>
|
||||
<option value="command">command</option>
|
||||
<option value="command-r">command-r</option>
|
||||
<option value="command-r-plus">command-r-plus</option>
|
||||
<option value="command-r-08-2024">command-r-08-2024</option>
|
||||
<option value="command-r-plus-08-2024">command-r-plus-08-2024</option>
|
||||
<option value="command-r7b-12-2024">command-r7b-12-2024</option>
|
||||
</optgroup>
|
||||
<optgroup label="Nightly">
|
||||
<option value="command-light-nightly">command-light-nightly</option>
|
||||
@ -3232,8 +3329,9 @@
|
||||
<h4 data-i18n="Prompt Post-Processing">Prompt Post-Processing</h4>
|
||||
<select id="custom_prompt_post_processing" class="text_pole" title="Applies additional processing to the prompt before sending it to the API." data-i18n="[title]Applies additional processing to the prompt before sending it to the API.">
|
||||
<option data-i18n="prompt_post_processing_none" value="">None</option>
|
||||
<option value="merge">Merge consecutive roles</option>
|
||||
<option value="strict">Strict (user first, alternating roles)</option>
|
||||
<option data-i18n="prompt_post_processing_merge" value="merge">Merge consecutive roles</option>
|
||||
<option data-i18n="prompt_post_processing_semi" value="semi">Semi-strict (alternating roles)</option>
|
||||
<option data-i18n="prompt_post_processing_strict" value="strict">Strict (user first, alternating roles)</option>
|
||||
</select>
|
||||
</form>
|
||||
<div id="01ai_form" data-source="01ai">
|
||||
@ -3683,6 +3781,7 @@
|
||||
<option value="17">Mistral Nemo</option>
|
||||
<option value="8">Yi</option>
|
||||
<option value="11">Claude 1/2</option>
|
||||
<option value="18">DeepSeek V3</option>
|
||||
<option value="6">API (WebUI / koboldcpp)</option>
|
||||
</select>
|
||||
</div>
|
||||
@ -4649,6 +4748,13 @@
|
||||
Background Image
|
||||
</h3>
|
||||
<input id="bg-filter" data-i18n="[placeholder]Filter" placeholder="Filter" class="text_pole flex1" type="search" />
|
||||
<select id="background_fitting" class="text_pole" data-i18n="[title]Background Fitting" title="Background Fitting">
|
||||
<option value="classic" data-i18n="Classic">Classic</option>
|
||||
<option value="cover" data-i18n="Cover">Cover</option>
|
||||
<option value="contain" data-i18n="Contain">Contain</option>
|
||||
<option value="stretch" data-i18n="Stretch">Stretch</option>
|
||||
<option value="center" data-i18n="Center">Center</option>
|
||||
</select>
|
||||
<div id="auto_background" class="menu_button menu_button_icon" data-i18n="[title]Automatically select a background based on the chat context" title="Automatically select a background based on the chat context.">
|
||||
<i class="fa-solid fa-wand-magic"></i>
|
||||
<span data-i18n="Auto-select">Auto-select</span>
|
||||
@ -4814,6 +4920,8 @@
|
||||
</div>
|
||||
<div id="sync_name_button" class="menu_button fa-solid fa-sync" title="Click to set user name for all messages" data-i18n="[title]Click to set user name for all messages">
|
||||
</div>
|
||||
<div id="persona_lore_button" class="menu_button fa-solid fa-globe" title="Persona Lore Alt+Click to open the lorebook" data-i18n="[title]Persona Lore Alt+Click to open the lorebook">
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4 data-i18n="Persona Description">Persona Description</h4>
|
||||
@ -4930,7 +5038,7 @@
|
||||
<input type="hidden" id="fav_checkbox" name="fav" />
|
||||
<div id="advanced_div" class="menu_button fa-solid fa-book " title="Advanced Definitions" data-i18n="[title]Advanced Definition"></div>
|
||||
<div id="world_button" class="menu_button fa-solid fa-globe" title="Character Lore Click to load Shift-click to open 'Link to World Info' popup" data-i18n="[title]world_button_title"></div>
|
||||
<div class="chat_lorebook_button menu_button fa-solid fa-passport" title="Chat Lore" data-i18n="[title]Chat Lore"></div>
|
||||
<div class="chat_lorebook_button menu_button fa-solid fa-passport" title="Chat Lore Alt+Click to open the lorebook" data-i18n="[title]Chat Lore Alt+Click to open the lorebook"></div>
|
||||
<div id="export_button" class="menu_button fa-solid fa-file-export " title="Export and Download" data-i18n="[title]Export and Download"></div>
|
||||
<!-- <div id="set_chat_scenario" class="menu_button fa-solid fa-scroll" title="Set a chat scenario override"></div> -->
|
||||
<!-- <div id="set_character_world" class="menu_button fa-solid fa-globe" title="Set a character World Info / Lorebook"></div> -->
|
||||
@ -5540,26 +5648,6 @@
|
||||
</div>
|
||||
</div>
|
||||
<!-- templates for JS to reuse when needed -->
|
||||
<div id="chat_world_template" class="template_element">
|
||||
<div class="chat_world range-block flexFlowColumn flex-container">
|
||||
<div class="range-block-title">
|
||||
<h4 data-i18n="Chat Lorebook"><!-- This data-i18n attribute is kept for backward compatibility, use the ones below when translating -->
|
||||
<span data-i18n="Chat Lorebook for">Chat Lorebook for</span> <span class="chat_name"></span>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="range-block-counter justifyLeft flex-container flexFlowColumn margin-bot-10px">
|
||||
<span data-i18n="chat_world_template_txt">
|
||||
A selected World Info will be bound to this chat. When generating an AI reply,
|
||||
it will be combined with the entries from global and character lorebooks.
|
||||
</span>
|
||||
</div>
|
||||
<div class="range-block-range wide100p">
|
||||
<select class="chat_world_info_selector wide100p">
|
||||
<option value="">--- None ---</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="character_world_template" class="template_element">
|
||||
<div class="character_world range-block flexFlowColumn flex-container">
|
||||
<div class="range-block-title">
|
||||
@ -5977,6 +6065,7 @@
|
||||
</div>
|
||||
<div id="openai_logit_bias_template" class="template_element">
|
||||
<div class="openai_logit_bias_form">
|
||||
<span class="drag-handle">☰</span>
|
||||
<input class="openai_logit_bias_text text_pole" data-i18n="[placeholder]Text or token ids" placeholder="Text or [token ids]" />
|
||||
<input class="openai_logit_bias_value text_pole" type="number" min="-100" value="0" max="100" />
|
||||
<i class="menu_button fa-solid fa-xmark openai_logit_bias_remove"></i>
|
||||
@ -5984,6 +6073,7 @@
|
||||
</div>
|
||||
<div id="logit_bias_template" class="template_element">
|
||||
<div class="logit_bias_form">
|
||||
<span class="drag-handle">☰</span>
|
||||
<input class="logit_bias_text text_pole" data-i18n="[placeholder]Type here..." placeholder="type here..." />
|
||||
<input class="logit_bias_value text_pole" type="number" min="-100" value="0" max="100" step="0.01" />
|
||||
<i class="menu_button fa-solid fa-xmark logit_bias_remove"></i>
|
||||
@ -6023,7 +6113,9 @@
|
||||
<label for="completion_prompt_manager_popup_entry_form_role">
|
||||
<span data-i18n="Role">Role</span>
|
||||
</label>
|
||||
<div class="text_muted" data-i18n="To whom this message will be attributed.">To whom this message will be attributed.</div>
|
||||
<div class="text_muted">
|
||||
<span data-i18n="To whom this message will be attributed.">To whom this message will be attributed.</span>
|
||||
</div>
|
||||
<select id="completion_prompt_manager_popup_entry_form_role" class="text_pole" name="role">
|
||||
<option data-i18n="System" value="system">System</option>
|
||||
<option data-i18n="User" value="user">User</option>
|
||||
|
@ -19,6 +19,7 @@ import seedrandom from 'seedrandom';
|
||||
import * as Popper from '@popperjs/core';
|
||||
import droll from 'droll';
|
||||
import morphdom from 'morphdom';
|
||||
import { toggle as slideToggle } from 'slidetoggle';
|
||||
|
||||
/**
|
||||
* Expose the libraries to the 'window' object.
|
||||
@ -94,6 +95,7 @@ export default {
|
||||
Popper,
|
||||
droll,
|
||||
morphdom,
|
||||
slideToggle,
|
||||
};
|
||||
|
||||
export {
|
||||
@ -115,4 +117,5 @@ export {
|
||||
Popper,
|
||||
droll,
|
||||
morphdom,
|
||||
slideToggle,
|
||||
};
|
||||
|
@ -95,13 +95,14 @@ EventEmitter.prototype.removeListener = function (event, listener) {
|
||||
};
|
||||
|
||||
EventEmitter.prototype.emit = async function (event) {
|
||||
let args = [].slice.call(arguments, 1);
|
||||
if (localStorage.getItem('eventTracing') === 'true') {
|
||||
console.trace('Event emitted: ' + event, args);
|
||||
} else {
|
||||
console.debug('Event emitted: ' + event);
|
||||
}
|
||||
|
||||
var i, listeners, length, args = [].slice.call(arguments, 1);
|
||||
let i, listeners, length;
|
||||
|
||||
if (typeof this.events[event] === 'object') {
|
||||
listeners = this.events[event].slice();
|
||||
@ -120,13 +121,14 @@ EventEmitter.prototype.emit = async function (event) {
|
||||
};
|
||||
|
||||
EventEmitter.prototype.emitAndWait = function (event) {
|
||||
let args = [].slice.call(arguments, 1);
|
||||
if (localStorage.getItem('eventTracing') === 'true') {
|
||||
console.trace('Event emitted: ' + event, args);
|
||||
} else {
|
||||
console.debug('Event emitted: ' + event);
|
||||
}
|
||||
|
||||
var i, listeners, length, args = [].slice.call(arguments, 1);
|
||||
let i, listeners, length;
|
||||
|
||||
if (typeof this.events[event] === 'object') {
|
||||
listeners = this.events[event].slice();
|
||||
|
@ -267,7 +267,7 @@
|
||||
"Text Completion": "اكتمال النص",
|
||||
"Chat Completion": "إكمال الدردشة",
|
||||
"NovelAI": "NovelAI",
|
||||
"KoboldAI Horde": "جماعة KoboldAI",
|
||||
"AI Horde": "AI Horde",
|
||||
"KoboldAI": "KoboldAI",
|
||||
"Avoid sending sensitive information to the Horde.": "تجنب إرسال معلومات حساسة إلى الجماعة.",
|
||||
"Review the Privacy statement": "مراجعة بيان الخصوصية",
|
||||
@ -1295,7 +1295,7 @@
|
||||
"sd_drawthings_auth_txt": "قم بتشغيل تطبيق DrawThings مع تمكين مفتاح HTTP API في واجهة المستخدم! يجب أن يكون الخادم قابلاً للوصول من الجهاز المضيف SillyTavern.",
|
||||
"sd_vlad_url": "مثال: {{vlad_url}}",
|
||||
"The server must be accessible from the SillyTavern host machine.": "يجب أن يكون الخادم قابلاً للوصول من الجهاز المضيف SillyTavern.",
|
||||
"Hint: Save an API key in Horde KoboldAI API settings to use it here.": "تلميح: احفظ مفتاح API في إعدادات Horde KoboldAI API لاستخدامه هنا.",
|
||||
"Hint: Save an API key in AI Horde API settings to use it here.": "تلميح: احفظ مفتاح API في إعدادات AI Horde API لاستخدامه هنا.",
|
||||
"Allow NSFW images from Horde": "السماح بصور NSFW من Horde",
|
||||
"Sanitize prompts (recommended)": "مطالبات التعقيم (مستحسن)",
|
||||
"Automatically adjust generation parameters to ensure free image generations.": "قم بضبط معلمات الإنشاء تلقائيًا لضمان إنشاء صور مجانية.",
|
||||
|
@ -267,7 +267,7 @@
|
||||
"Text Completion": "Textvervollständigung",
|
||||
"Chat Completion": "Chat-Abschluss",
|
||||
"NovelAI": "NovelAI",
|
||||
"KoboldAI Horde": "KoboldAI Horde",
|
||||
"AI Horde": "AI Horde",
|
||||
"KoboldAI": "KoboldAI",
|
||||
"Avoid sending sensitive information to the Horde.": "Vermeide das Senden sensibler Informationen an die Horde.",
|
||||
"Review the Privacy statement": "Überprüfe die Datenschutzerklärung",
|
||||
@ -1295,7 +1295,7 @@
|
||||
"sd_drawthings_auth_txt": "Führen Sie die DrawThings-App mit aktiviertem HTTP-API-Schalter in der Benutzeroberfläche aus! Der Server muss vom SillyTavern-Hostcomputer aus zugänglich sein.",
|
||||
"sd_vlad_url": "Beispiel: {{vlad_url}}",
|
||||
"The server must be accessible from the SillyTavern host machine.": "Der Server muss vom SillyTavern-Hostcomputer aus zugänglich sein.",
|
||||
"Hint: Save an API key in Horde KoboldAI API settings to use it here.": "Tipp: Speichern Sie einen API-Schlüssel in den Horde KoboldAI API-Einstellungen, um ihn hier zu verwenden.",
|
||||
"Hint: Save an API key in AI Horde API settings to use it here.": "Tipp: Speichern Sie einen API-Schlüssel in den AI Horde API-Einstellungen, um ihn hier zu verwenden.",
|
||||
"Allow NSFW images from Horde": "NSFW-Bilder von Horde zulassen",
|
||||
"Sanitize prompts (recommended)": "Eingabeaufforderungen bereinigen (empfohlen)",
|
||||
"Automatically adjust generation parameters to ensure free image generations.": "Passen Sie die Generierungsparameter automatisch an, um eine freie Bildgenerierung zu gewährleisten.",
|
||||
|
@ -267,7 +267,7 @@
|
||||
"Text Completion": "Completar texto",
|
||||
"Chat Completion": "Finalización del chat",
|
||||
"NovelAI": "NovelAI",
|
||||
"KoboldAI Horde": "Horde de KoboldAI",
|
||||
"AI Horde": "AI Horde",
|
||||
"KoboldAI": "KoboldAI",
|
||||
"Avoid sending sensitive information to the Horde.": "Evite enviar información sensible a Horde.",
|
||||
"Review the Privacy statement": "Revise la declaración de privacidad",
|
||||
@ -874,7 +874,7 @@
|
||||
"Bulk_edit_characters": "Editar personajes masivamente",
|
||||
"Bulk select all characters": "Seleccionar de forma masiva todos los personajes",
|
||||
"Bulk delete characters": "Eliminar personajes masivamente",
|
||||
"popup-button-save": "Ahorrar",
|
||||
"popup-button-save": "Guardar",
|
||||
"popup-button-yes": "Sí",
|
||||
"popup-button-no": "No",
|
||||
"popup-button-cancel": "Cancelar",
|
||||
@ -1019,7 +1019,7 @@
|
||||
"This prompt cannot be overridden by character cards, even if overrides are preferred.": "Este mensaje no puede ser anulado por tarjetas de personaje, incluso si se prefieren las anulaciones.",
|
||||
"prompt_manager_forbid_overrides": "Prohibir anulaciones",
|
||||
"reset": "reiniciar",
|
||||
"save": "ahorrar",
|
||||
"save": "guardar",
|
||||
"This message is invisible for the AI": "Este mensaje es invisible para la IA",
|
||||
"Message Actions": "Acciones de mensajes",
|
||||
"Translate message": "Traducir mensaje",
|
||||
@ -1295,7 +1295,7 @@
|
||||
"sd_drawthings_auth_txt": "¡Ejecute la aplicación DrawThings con el interruptor API HTTP habilitado en la interfaz de usuario! Se debe poder acceder al servidor desde la máquina host de SillyTavern.",
|
||||
"sd_vlad_url": "Ejemplo: {{vlad_url}}",
|
||||
"The server must be accessible from the SillyTavern host machine.": "Se debe poder acceder al servidor desde la máquina host de SillyTavern.",
|
||||
"Hint: Save an API key in Horde KoboldAI API settings to use it here.": "Sugerencia: guarde una clave API en la configuración de API de Horde KoboldAI para usarla aquí.",
|
||||
"Hint: Save an API key in AI Horde API settings to use it here.": "Sugerencia: guarde una clave API en la configuración de API de AI Horde para usarla aquí.",
|
||||
"Allow NSFW images from Horde": "Permitir imágenes NSFW de Horda",
|
||||
"Sanitize prompts (recommended)": "Indicaciones para desinfectar (recomendado)",
|
||||
"Automatically adjust generation parameters to ensure free image generations.": "Ajuste automáticamente los parámetros de generación para garantizar generaciones de imágenes gratuitas.",
|
||||
|
@ -267,7 +267,7 @@
|
||||
"Text Completion": "Achèvement du texte",
|
||||
"Chat Completion": "Achèvement du chat",
|
||||
"NovelAI": "NovelAI",
|
||||
"KoboldAI Horde": "Horde KoboldAI",
|
||||
"AI Horde": "AI Horde",
|
||||
"KoboldAI": "KoboldAI",
|
||||
"Avoid sending sensitive information to the Horde.": "Évitez d'envoyer des informations sensibles à la Horde.",
|
||||
"Review the Privacy statement": "Examiner la déclaration de confidentialité",
|
||||
@ -1295,7 +1295,7 @@
|
||||
"sd_drawthings_auth_txt": "exécutez l'application DrawThings avec le commutateur API HTTP activé dans l'interface utilisateur ! Le serveur doit être accessible depuis la machine hôte de SillyTavern.",
|
||||
"sd_vlad_url": "Exemple : {{vlad_url}}",
|
||||
"The server must be accessible from the SillyTavern host machine.": "Le serveur doit être accessible depuis la machine hôte de SillyTavern.",
|
||||
"Hint: Save an API key in Horde KoboldAI API settings to use it here.": "Astuce : enregistrez une clé API dans les paramètres de l'API Horde KoboldAI pour l'utiliser ici.",
|
||||
"Hint: Save an API key in AI Horde API settings to use it here.": "Astuce : enregistrez une clé API dans les paramètres de l'API AI Horde pour l'utiliser ici.",
|
||||
"Allow NSFW images from Horde": "Autoriser les images NSFW de la Horde",
|
||||
"Sanitize prompts (recommended)": "Désinfecter les invites (recommandé)",
|
||||
"Automatically adjust generation parameters to ensure free image generations.": "Ajustez automatiquement les paramètres de génération pour garantir des générations d’images gratuites.",
|
||||
|
@ -267,7 +267,7 @@
|
||||
"Text Completion": "Textaútfylling",
|
||||
"Chat Completion": "Spjalllokun",
|
||||
"NovelAI": "NovelAI",
|
||||
"KoboldAI Horde": "KoboldAI Hópur",
|
||||
"AI Horde": "AI Horde",
|
||||
"KoboldAI": "KoboldAI",
|
||||
"Avoid sending sensitive information to the Horde.": "Forðastu að senda viðkvæm gögn til Hórdans.",
|
||||
"Review the Privacy statement": "Farið yfir Persónuverndarskýrsluna",
|
||||
@ -1295,7 +1295,7 @@
|
||||
"sd_drawthings_auth_txt": "keyrðu DrawThings app með HTTP API rofi virkt í notendaviðmótinu! Miðlarinn verður að vera aðgengilegur frá SillyTavern hýsingarvélinni.",
|
||||
"sd_vlad_url": "Dæmi: {{vlad_url}}",
|
||||
"The server must be accessible from the SillyTavern host machine.": "Miðlarinn verður að vera aðgengilegur frá SillyTavern hýsingarvélinni.",
|
||||
"Hint: Save an API key in Horde KoboldAI API settings to use it here.": "Ábending: Vistaðu API lykil í Horde KoboldAI API stillingum til að nota hann hér.",
|
||||
"Hint: Save an API key in AI Horde API settings to use it here.": "Ábending: Vistaðu API lykil í AI Horde API stillingum til að nota hann hér.",
|
||||
"Allow NSFW images from Horde": "Leyfa NSFW myndir frá Horde",
|
||||
"Sanitize prompts (recommended)": "Hreinsunarleiðbeiningar (ráðlagt)",
|
||||
"Automatically adjust generation parameters to ensure free image generations.": "Stilltu kynslóðarbreytur sjálfkrafa til að tryggja ókeypis myndmyndun.",
|
||||
|
@ -267,7 +267,7 @@
|
||||
"Text Completion": "Completamento del testo",
|
||||
"Chat Completion": "Completamento della chat",
|
||||
"NovelAI": "NovelAI",
|
||||
"KoboldAI Horde": "Orda di KoboldAI",
|
||||
"AI Horde": "AI Horde",
|
||||
"KoboldAI": "KoboldAI",
|
||||
"Avoid sending sensitive information to the Horde.": "Evita di inviare informazioni sensibili all'Orda.",
|
||||
"Review the Privacy statement": "Revisione della dichiarazione sulla privacy",
|
||||
@ -1295,7 +1295,7 @@
|
||||
"sd_drawthings_auth_txt": "esegui l'app DrawThings con lo switch API HTTP abilitato nell'interfaccia utente! Il server deve essere accessibile dalla macchina host SillyTavern.",
|
||||
"sd_vlad_url": "Esempio: {{vlad_url}}",
|
||||
"The server must be accessible from the SillyTavern host machine.": "Il server deve essere accessibile dalla macchina host SillyTavern.",
|
||||
"Hint: Save an API key in Horde KoboldAI API settings to use it here.": "Suggerimento: salva una chiave API nelle impostazioni API Horde KoboldAI per usarla qui.",
|
||||
"Hint: Save an API key in AI Horde API settings to use it here.": "Suggerimento: salva una chiave API nelle impostazioni API AI Horde per usarla qui.",
|
||||
"Allow NSFW images from Horde": "Consenti immagini NSFW da Horde",
|
||||
"Sanitize prompts (recommended)": "Messaggi di disinfezione (consigliato)",
|
||||
"Automatically adjust generation parameters to ensure free image generations.": "Regola automaticamente i parametri di generazione per garantire generazioni di immagini gratuite.",
|
||||
|
@ -267,7 +267,7 @@
|
||||
"Text Completion": "テキスト補完",
|
||||
"Chat Completion": "チャット完了",
|
||||
"NovelAI": "NovelAI",
|
||||
"KoboldAI Horde": "KoboldAI Horde",
|
||||
"AI Horde": "AI Horde",
|
||||
"KoboldAI": "KoboldAI",
|
||||
"Avoid sending sensitive information to the Horde.": "Hordeに機密情報を送信しないでください。",
|
||||
"Review the Privacy statement": "プライバシー声明を確認する",
|
||||
@ -1274,6 +1274,8 @@
|
||||
"sd_Raw_Last_Message": "生の最後のメッセージ",
|
||||
"sd_Background": "背景",
|
||||
"Image Generation": "画像生成",
|
||||
"Stop Image Generation": "画像生成を停止",
|
||||
"Generate Caption": "画像説明を生成",
|
||||
"sd_refine_mode": "プロンプトを生成 API に送信する前に手動で編集できるようにする",
|
||||
"sd_refine_mode_txt": "生成前にプロンプトを編集する",
|
||||
"sd_interactive_mode": "「猫の写真を送ってください」のようなメッセージを送信するときに、画像を自動的に生成します。",
|
||||
@ -1295,7 +1297,7 @@
|
||||
"sd_drawthings_auth_txt": "UI で HTTP API スイッチを有効にして DrawThings アプリを実行します。サーバーは SillyTavern ホスト マシンからアクセスできる必要があります。",
|
||||
"sd_vlad_url": "例: {{vlad_url}}",
|
||||
"The server must be accessible from the SillyTavern host machine.": "サーバーは SillyTavern ホスト マシンからアクセスできる必要があります。",
|
||||
"Hint: Save an API key in Horde KoboldAI API settings to use it here.": "ヒント: ここで使用するには、Horde KoboldAI API 設定に API キーを保存してください。",
|
||||
"Hint: Save an API key in AI Horde API settings to use it here.": "ヒント: ここで使用するには、AI Horde API 設定に API キーを保存してください。",
|
||||
"Allow NSFW images from Horde": "HordeからのNSFW画像を許可する",
|
||||
"Sanitize prompts (recommended)": "サニタイズプロンプト(推奨)",
|
||||
"Automatically adjust generation parameters to ensure free image generations.": "生成パラメータを自動的に調整して、自由な画像生成を保証します。",
|
||||
@ -1439,5 +1441,8 @@
|
||||
"Still have questions?": "まだ質問がありますか?",
|
||||
"Join the SillyTavern Discord": "SillyTavernのDiscordに参加",
|
||||
"Post a GitHub issue": "GitHubの問題を投稿",
|
||||
"Contact the developers": "開発者に連絡"
|
||||
"Contact the developers": "開発者に連絡",
|
||||
"Stop Inspecting": "検査を停止",
|
||||
"Inspect Prompts": "プロンプトを検査",
|
||||
"Toggle prompt inspection": "プロンプト検査の切り替え"
|
||||
}
|
||||
|
@ -269,7 +269,7 @@
|
||||
"Text Completion": "Text Completion",
|
||||
"Chat Completion": "Chat Completion",
|
||||
"NovelAI": "NovelAI",
|
||||
"KoboldAI Horde": "KoboldAI Horde",
|
||||
"AI Horde": "AI Horde",
|
||||
"KoboldAI": "KoboldAI",
|
||||
"Avoid sending sensitive information to the Horde.": "민감한 정보를 Horde에 보내지 않도록 합니다.",
|
||||
"Review the Privacy statement": "개인 정보 보호 정책 검토",
|
||||
@ -1312,7 +1312,7 @@
|
||||
"sd_drawthings_auth_txt": "UI에서 HTTP API 스위치가 활성화된 상태에서 DrawThings 앱을 실행하세요! SillyTavern 호스트 시스템에서 서버에 액세스할 수 있어야 합니다.",
|
||||
"sd_vlad_url": "예: {{vlad_url}}",
|
||||
"The server must be accessible from the SillyTavern host machine.": "SillyTavern 호스트 시스템에서 서버에 액세스할 수 있어야 합니다.",
|
||||
"Hint: Save an API key in Horde KoboldAI API settings to use it here.": "힌트: 여기에서 사용하려면 Horde KoboldAI API 설정에 API 키를 저장하세요.",
|
||||
"Hint: Save an API key in AI Horde API settings to use it here.": "힌트: 여기에서 사용하려면 AI Horde API 설정에 API 키를 저장하세요.",
|
||||
"Allow NSFW images from Horde": "Horde의 NSFW 이미지 허용",
|
||||
"Sanitize prompts (recommended)": "프롬프트 삭제(권장)",
|
||||
"Automatically adjust generation parameters to ensure free image generations.": "무료 이미지 생성을 보장하기 위해 생성 매개변수를 자동으로 조정합니다.",
|
||||
@ -1623,4 +1623,4 @@
|
||||
"Master Import": "마스터 불러오기",
|
||||
"Master Export": "마스터 내보내기",
|
||||
"Chat Quick Reply Sets": "채팅 빠른 답장 세트들"
|
||||
}
|
||||
}
|
||||
|
@ -267,7 +267,7 @@
|
||||
"Text Completion": "Tekstvoltooiing",
|
||||
"Chat Completion": "Chat-voltooiing",
|
||||
"NovelAI": "NovelAI",
|
||||
"KoboldAI Horde": "KoboldAI Horde",
|
||||
"AI Horde": "AI Horde",
|
||||
"KoboldAI": "KoboldAI",
|
||||
"Avoid sending sensitive information to the Horde.": "Vermijd het verzenden van gevoelige informatie naar de Horde.",
|
||||
"Review the Privacy statement": "Bekijk de privacyverklaring",
|
||||
@ -1295,7 +1295,7 @@
|
||||
"sd_drawthings_auth_txt": "voer de DrawThings-app uit met HTTP API-switch ingeschakeld in de gebruikersinterface! De server moet toegankelijk zijn vanaf de SillyTavern-hostmachine.",
|
||||
"sd_vlad_url": "Voorbeeld: {{vlad_url}}",
|
||||
"The server must be accessible from the SillyTavern host machine.": "De server moet toegankelijk zijn vanaf de SillyTavern-hostmachine.",
|
||||
"Hint: Save an API key in Horde KoboldAI API settings to use it here.": "Tip: sla een API-sleutel op in de Horde KoboldAI API-instellingen om deze hier te gebruiken.",
|
||||
"Hint: Save an API key in AI Horde API settings to use it here.": "Tip: sla een API-sleutel op in de AI Horde API-instellingen om deze hier te gebruiken.",
|
||||
"Allow NSFW images from Horde": "Sta NSFW-afbeeldingen van Horde toe",
|
||||
"Sanitize prompts (recommended)": "Ontsmettingsmeldingen (aanbevolen)",
|
||||
"Automatically adjust generation parameters to ensure free image generations.": "Pas de generatieparameters automatisch aan om vrije beeldgeneraties te garanderen.",
|
||||
|
@ -267,7 +267,7 @@
|
||||
"Text Completion": "Conclusão de texto",
|
||||
"Chat Completion": "Conclusão do bate-papo",
|
||||
"NovelAI": "NovelAI",
|
||||
"KoboldAI Horde": "Horda KoboldAI",
|
||||
"AI Horde": "AI Horde",
|
||||
"KoboldAI": "KoboldAI",
|
||||
"Avoid sending sensitive information to the Horde.": "Evite enviar informações sensíveis para a Horda.",
|
||||
"Review the Privacy statement": "Reveja a declaração de privacidade",
|
||||
@ -1295,7 +1295,7 @@
|
||||
"sd_drawthings_auth_txt": "execute o aplicativo DrawThings com a opção HTTP API habilitada na IU! O servidor deve estar acessível a partir da máquina host SillyTavern.",
|
||||
"sd_vlad_url": "Exemplo: {{vlad_url}}",
|
||||
"The server must be accessible from the SillyTavern host machine.": "O servidor deve estar acessível a partir da máquina host SillyTavern.",
|
||||
"Hint: Save an API key in Horde KoboldAI API settings to use it here.": "Dica: salve uma chave de API nas configurações da API Horde KoboldAI para usá-la aqui.",
|
||||
"Hint: Save an API key in AI Horde API settings to use it here.": "Dica: salve uma chave de API nas configurações da API AI Horde para usá-la aqui.",
|
||||
"Allow NSFW images from Horde": "Permitir imagens NSFW da Horda",
|
||||
"Sanitize prompts (recommended)": "Solicitações de higienização (recomendado)",
|
||||
"Automatically adjust generation parameters to ensure free image generations.": "Ajuste automaticamente os parâmetros de geração para garantir gerações de imagens livres.",
|
||||
|
@ -299,7 +299,7 @@
|
||||
"Example: http://127.0.0.1:5000/api ": "Пример: http://127.0.0.1:5000/api",
|
||||
"No connection...": "Нет соединения...",
|
||||
"Get your NovelAI API Key": "Получите свой API-ключ для NovelAI",
|
||||
"KoboldAI Horde": "KoboldAI Horde",
|
||||
"AI Horde": "AI Horde",
|
||||
"NovelAI": "NovelAI",
|
||||
"OpenAI API key": "Ключ для API OpenAI",
|
||||
"Trim spaces": "Обрезать пробелы",
|
||||
@ -1461,7 +1461,7 @@
|
||||
"sd_drawthings_auth_txt": "run DrawThings app with HTTP API switch enabled in the UI! The server must be accessible from the SillyTavern host machine.",
|
||||
"sd_vlad_url": "Example: {{vlad_url}}",
|
||||
"The server must be accessible from the SillyTavern host machine.": "The server must be accessible from the SillyTavern host machine.",
|
||||
"Hint: Save an API key in Horde KoboldAI API settings to use it here.": "Hint: Save an API key in Horde KoboldAI API settings to use it here.",
|
||||
"Hint: Save an API key in AI Horde API settings to use it here.": "Hint: Save an API key in AI Horde API settings to use it here.",
|
||||
"Allow NSFW images from Horde": "Разрешить NSFW-картинки в Horde",
|
||||
"Sanitize prompts (recommended)": "Sanitize prompts (recommended)",
|
||||
"Automatically adjust generation parameters to ensure free image generations.": "Automatically adjust generation parameters to ensure free image generations.",
|
||||
|
@ -267,7 +267,7 @@
|
||||
"Text Completion": "Завершення тексту",
|
||||
"Chat Completion": "Завершення чату",
|
||||
"NovelAI": "NovelAI",
|
||||
"KoboldAI Horde": "KoboldAI Horde",
|
||||
"AI Horde": "AI Horde",
|
||||
"KoboldAI": "KoboldAI",
|
||||
"Avoid sending sensitive information to the Horde.": "Уникайте надсилання чутливої інформації в Horde.",
|
||||
"Review the Privacy statement": "Перегляньте заяву про конфіденційність",
|
||||
@ -1295,7 +1295,7 @@
|
||||
"sd_drawthings_auth_txt": "запустіть програму DrawThings із увімкненим перемикачем HTTP API в інтерфейсі користувача! Сервер має бути доступним із хост-машини SillyTavern.",
|
||||
"sd_vlad_url": "Приклад: {{vlad_url}}",
|
||||
"The server must be accessible from the SillyTavern host machine.": "Сервер має бути доступним із хост-машини SillyTavern.",
|
||||
"Hint: Save an API key in Horde KoboldAI API settings to use it here.": "Підказка: збережіть ключ API в налаштуваннях Horde KoboldAI API, щоб використовувати його тут.",
|
||||
"Hint: Save an API key in AI Horde API settings to use it here.": "Підказка: збережіть ключ API в налаштуваннях AI Horde API, щоб використовувати його тут.",
|
||||
"Allow NSFW images from Horde": "Дозволити зображення NSFW від Horde",
|
||||
"Sanitize prompts (recommended)": "Очистити підказки (рекомендовано)",
|
||||
"Automatically adjust generation parameters to ensure free image generations.": "Автоматично налаштовуйте параметри генерації, щоб забезпечити вільне створення зображень.",
|
||||
|
@ -267,7 +267,7 @@
|
||||
"Text Completion": "Text Completion",
|
||||
"Chat Completion": "Chat Completion",
|
||||
"NovelAI": "NovelAI",
|
||||
"KoboldAI Horde": "KoboldAI Horde",
|
||||
"AI Horde": "AI Horde",
|
||||
"KoboldAI": "KoboldAI",
|
||||
"Avoid sending sensitive information to the Horde.": "Tránh gửi thông tin nhạy cảm cho Horde.",
|
||||
"Review the Privacy statement": "Xem lại Chính sách bảo mật",
|
||||
@ -1295,7 +1295,7 @@
|
||||
"sd_drawthings_auth_txt": "chạy ứng dụng DrawThings với tính năng chuyển đổi API HTTP được bật trong giao diện người dùng! Máy chủ phải có thể truy cập được từ máy chủ SillyTavern.",
|
||||
"sd_vlad_url": "Ví dụ: {{vlad_url}}",
|
||||
"The server must be accessible from the SillyTavern host machine.": "Máy chủ phải có thể truy cập được từ máy chủ SillyTavern.",
|
||||
"Hint: Save an API key in Horde KoboldAI API settings to use it here.": "Gợi ý: Lưu khóa API trong cài đặt API Horde KoboldAI để sử dụng tại đây.",
|
||||
"Hint: Save an API key in AI Horde API settings to use it here.": "Gợi ý: Lưu khóa API trong cài đặt API AI Horde để sử dụng tại đây.",
|
||||
"Allow NSFW images from Horde": "Cho phép hình ảnh NSFW từ Horde",
|
||||
"Sanitize prompts (recommended)": "Nhắc nhở vệ sinh (khuyến nghị)",
|
||||
"Automatically adjust generation parameters to ensure free image generations.": "Tự động điều chỉnh các thông số tạo để đảm bảo tạo ra hình ảnh miễn phí.",
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
419
public/script.js
419
public/script.js
@ -10,6 +10,7 @@ import {
|
||||
SVGInject,
|
||||
Popper,
|
||||
initLibraryShims,
|
||||
slideToggle,
|
||||
default as libs,
|
||||
} from './lib.js';
|
||||
|
||||
@ -234,7 +235,7 @@ import {
|
||||
import { getBackgrounds, initBackgrounds, loadBackgroundSettings, background_settings } from './scripts/backgrounds.js';
|
||||
import { hideLoader, showLoader } from './scripts/loader.js';
|
||||
import { BulkEditOverlay, CharacterContextMenu } from './scripts/BulkEditOverlay.js';
|
||||
import { loadFeatherlessModels, loadMancerModels, loadOllamaModels, loadTogetherAIModels, loadInfermaticAIModels, loadOpenRouterModels, loadVllmModels, loadAphroditeModels, loadDreamGenModels, initTextGenModels, loadTabbyModels } from './scripts/textgen-models.js';
|
||||
import { loadFeatherlessModels, loadMancerModels, loadOllamaModels, loadTogetherAIModels, loadInfermaticAIModels, loadOpenRouterModels, loadVllmModels, loadAphroditeModels, loadDreamGenModels, initTextGenModels, loadTabbyModels, loadGenericModels } from './scripts/textgen-models.js';
|
||||
import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags, isExternalMediaAllowed, getCurrentEntityId, preserveNeutralChat, restoreNeutralChat } from './scripts/chats.js';
|
||||
import { getPresetManager, initPresetManager } from './scripts/preset-manager.js';
|
||||
import { evaluateMacros, getLastMessageId, initMacros } from './scripts/macros.js';
|
||||
@ -549,6 +550,7 @@ let optionsPopper = Popper.createPopper(document.getElementById('options_button'
|
||||
let exportPopper = Popper.createPopper(document.getElementById('export_button'), document.getElementById('export_format_popup'), {
|
||||
placement: 'left',
|
||||
});
|
||||
let isExportPopupOpen = false;
|
||||
|
||||
// Saved here for performance reasons
|
||||
const messageTemplate = $('#message_template .mes');
|
||||
@ -847,11 +849,9 @@ export let is_send_press = false; //Send generation
|
||||
|
||||
let this_del_mes = -1;
|
||||
|
||||
//message editing and chat scroll position persistence
|
||||
//message editing
|
||||
var this_edit_mes_chname = '';
|
||||
var this_edit_mes_id;
|
||||
var scroll_holder = 0;
|
||||
var is_use_scroll_holder = false;
|
||||
|
||||
//settings
|
||||
export let settings;
|
||||
@ -895,6 +895,13 @@ export function getRequestHeaders() {
|
||||
};
|
||||
}
|
||||
|
||||
export function getSlideToggleOptions() {
|
||||
return {
|
||||
miliseconds: animation_duration * 1.5,
|
||||
transitionFunction: animation_duration > 0 ? 'ease-in-out' : 'step-start',
|
||||
};
|
||||
}
|
||||
|
||||
$.ajaxPrefilter((options, originalOptions, xhr) => {
|
||||
xhr.setRequestHeader('X-CSRF-Token', token);
|
||||
});
|
||||
@ -1173,7 +1180,7 @@ async function getStatusTextgen() {
|
||||
return resultCheckStatus();
|
||||
}
|
||||
|
||||
if (textgen_settings.type == textgen_types.OOBA && textgen_settings.bypass_status_check) {
|
||||
if ([textgen_types.GENERIC, textgen_types.OOBA].includes(textgen_settings.type) && textgen_settings.bypass_status_check) {
|
||||
setOnlineStatus('Status check bypassed');
|
||||
return resultCheckStatus();
|
||||
}
|
||||
@ -1221,6 +1228,9 @@ async function getStatusTextgen() {
|
||||
} else if (textgen_settings.type === textgen_types.TABBY) {
|
||||
loadTabbyModels(data?.data);
|
||||
setOnlineStatus(textgen_settings.tabby_model || data?.result);
|
||||
} else if (textgen_settings.type === textgen_types.GENERIC) {
|
||||
loadGenericModels(data?.data);
|
||||
setOnlineStatus(textgen_settings.generic_model || data?.result || 'Connected');
|
||||
} else {
|
||||
setOnlineStatus(data?.result);
|
||||
}
|
||||
@ -2873,23 +2883,54 @@ function addPersonaDescriptionExtensionPrompt() {
|
||||
}
|
||||
}
|
||||
|
||||
function getAllExtensionPrompts() {
|
||||
const value = Object
|
||||
.values(extension_prompts)
|
||||
.filter(x => x.value)
|
||||
.map(x => x.value.trim())
|
||||
.join('\n');
|
||||
/**
|
||||
* Returns all extension prompts combined.
|
||||
* @returns {Promise<string>} Combined extension prompts
|
||||
*/
|
||||
async function getAllExtensionPrompts() {
|
||||
const values = [];
|
||||
|
||||
return value.length ? substituteParams(value) : '';
|
||||
for (const prompt of Object.values(extension_prompts)) {
|
||||
const value = prompt?.value?.trim();
|
||||
|
||||
if (!value) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const hasFilter = typeof prompt.filter === 'function';
|
||||
if (hasFilter && !await prompt.filter()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
values.push(value);
|
||||
}
|
||||
|
||||
return substituteParams(values.join('\n'));
|
||||
}
|
||||
|
||||
// Wrapper to fetch extension prompts by module name
|
||||
export function getExtensionPromptByName(moduleName) {
|
||||
if (moduleName) {
|
||||
return substituteParams(extension_prompts[moduleName]?.value);
|
||||
} else {
|
||||
return;
|
||||
/**
|
||||
* Wrapper to fetch extension prompts by module name
|
||||
* @param {string} moduleName Module name
|
||||
* @returns {Promise<string>} Extension prompt
|
||||
*/
|
||||
export async function getExtensionPromptByName(moduleName) {
|
||||
if (!moduleName) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const prompt = extension_prompts[moduleName];
|
||||
|
||||
if (!prompt) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const hasFilter = typeof prompt.filter === 'function';
|
||||
|
||||
if (hasFilter && !await prompt.filter()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return substituteParams(prompt.value);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2900,27 +2941,36 @@ export function getExtensionPromptByName(moduleName) {
|
||||
* @param {string} [separator] Separator for joining multiple prompts
|
||||
* @param {number} [role] Role of the prompt
|
||||
* @param {boolean} [wrap] Wrap start and end with a separator
|
||||
* @returns {string} Extension prompt
|
||||
* @returns {Promise<string>} Extension prompt
|
||||
*/
|
||||
export function getExtensionPrompt(position = extension_prompt_types.IN_PROMPT, depth = undefined, separator = '\n', role = undefined, wrap = true) {
|
||||
let extension_prompt = Object.keys(extension_prompts)
|
||||
export async function getExtensionPrompt(position = extension_prompt_types.IN_PROMPT, depth = undefined, separator = '\n', role = undefined, wrap = true) {
|
||||
const filterByFunction = async (prompt) => {
|
||||
const hasFilter = typeof prompt.filter === 'function';
|
||||
if (hasFilter && !await prompt.filter()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
const promptPromises = Object.keys(extension_prompts)
|
||||
.sort()
|
||||
.map((x) => extension_prompts[x])
|
||||
.filter(x => x.position == position && x.value)
|
||||
.filter(x => depth === undefined || x.depth === undefined || x.depth === depth)
|
||||
.filter(x => role === undefined || x.role === undefined || x.role === role)
|
||||
.map(x => x.value.trim())
|
||||
.join(separator);
|
||||
if (wrap && extension_prompt.length && !extension_prompt.startsWith(separator)) {
|
||||
extension_prompt = separator + extension_prompt;
|
||||
.filter(filterByFunction);
|
||||
const prompts = await Promise.all(promptPromises);
|
||||
|
||||
let values = prompts.map(x => x.value.trim()).join(separator);
|
||||
if (wrap && values.length && !values.startsWith(separator)) {
|
||||
values = separator + values;
|
||||
}
|
||||
if (wrap && extension_prompt.length && !extension_prompt.endsWith(separator)) {
|
||||
extension_prompt = extension_prompt + separator;
|
||||
if (wrap && values.length && !values.endsWith(separator)) {
|
||||
values = values + separator;
|
||||
}
|
||||
if (extension_prompt.length) {
|
||||
extension_prompt = substituteParams(extension_prompt);
|
||||
if (values.length) {
|
||||
values = substituteParams(values);
|
||||
}
|
||||
return extension_prompt;
|
||||
return values;
|
||||
}
|
||||
|
||||
export function baseChatReplace(value, name1, name2) {
|
||||
@ -3742,6 +3792,23 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
|
||||
}
|
||||
}
|
||||
|
||||
// Fetches the combined prompt for both negative and positive prompts
|
||||
const cfgGuidanceScale = getGuidanceScale();
|
||||
const useCfgPrompt = cfgGuidanceScale && cfgGuidanceScale.value !== 1;
|
||||
|
||||
// Adjust max context based on CFG prompt to prevent overfitting
|
||||
if (useCfgPrompt) {
|
||||
const negativePrompt = getCfgPrompt(cfgGuidanceScale, true, true)?.value || '';
|
||||
const positivePrompt = getCfgPrompt(cfgGuidanceScale, false, true)?.value || '';
|
||||
if (negativePrompt || positivePrompt) {
|
||||
const previousMaxContext = this_max_context;
|
||||
const [negativePromptTokenCount, positivePromptTokenCount] = await Promise.all([getTokenCountAsync(negativePrompt), getTokenCountAsync(positivePrompt)]);
|
||||
const decrement = Math.max(negativePromptTokenCount, positivePromptTokenCount);
|
||||
this_max_context -= decrement;
|
||||
console.log(`Max context reduced by ${decrement} tokens of CFG prompt (${previousMaxContext} -> ${this_max_context})`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Core/all messages: ${coreChat.length}/${chat.length}`);
|
||||
|
||||
// kingbri MARK: - Make sure the prompt bias isn't the same as the user bias
|
||||
@ -3834,7 +3901,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
|
||||
// Inject all Depth prompts. Chat Completion does it separately
|
||||
let injectedIndices = [];
|
||||
if (main_api !== 'openai') {
|
||||
injectedIndices = doChatInject(coreChat, isContinue);
|
||||
injectedIndices = await doChatInject(coreChat, isContinue);
|
||||
}
|
||||
|
||||
// Insert character jailbreak as the last user message (if exists, allowed, preferred, and not using Chat Completion)
|
||||
@ -3907,8 +3974,8 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
|
||||
}
|
||||
|
||||
// Call combined AN into Generate
|
||||
const beforeScenarioAnchor = getExtensionPrompt(extension_prompt_types.BEFORE_PROMPT).trimStart();
|
||||
const afterScenarioAnchor = getExtensionPrompt(extension_prompt_types.IN_PROMPT);
|
||||
const beforeScenarioAnchor = (await getExtensionPrompt(extension_prompt_types.BEFORE_PROMPT)).trimStart();
|
||||
const afterScenarioAnchor = await getExtensionPrompt(extension_prompt_types.IN_PROMPT);
|
||||
|
||||
const storyStringParams = {
|
||||
description: description,
|
||||
@ -4249,10 +4316,6 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
|
||||
setPromptString();
|
||||
}
|
||||
|
||||
// Fetches the combined prompt for both negative and positive prompts
|
||||
const cfgGuidanceScale = getGuidanceScale();
|
||||
const useCfgPrompt = cfgGuidanceScale && cfgGuidanceScale.value !== 1;
|
||||
|
||||
// For prompt bit itemization
|
||||
let mesSendString = '';
|
||||
|
||||
@ -4471,7 +4534,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
|
||||
...thisPromptBits[currentArrayEntry],
|
||||
rawPrompt: generate_data.prompt || generate_data.input,
|
||||
mesId: getNextMessageId(type),
|
||||
allAnchors: getAllExtensionPrompts(),
|
||||
allAnchors: await getAllExtensionPrompts(),
|
||||
chatInjects: injectedIndices?.map(index => arrMes[arrMes.length - index - 1])?.join('') || '',
|
||||
summarizeString: (extension_prompts['1_memory']?.value || ''),
|
||||
authorsNoteString: (extension_prompts['2_floating_prompt']?.value || ''),
|
||||
@ -4536,9 +4599,12 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
|
||||
const shouldDeleteMessage = type !== 'swipe' && ['', '...'].includes(lastMessage?.mes) && ['', '...'].includes(streamingProcessor?.result);
|
||||
hasToolCalls && shouldDeleteMessage && await deleteLastMessage();
|
||||
const invocationResult = await ToolManager.invokeFunctionTools(streamingProcessor.toolCalls);
|
||||
const shouldStopGeneration = (!invocationResult.invocations.length && shouldDeleteMessage) || invocationResult.stealthCalls.length;
|
||||
if (hasToolCalls) {
|
||||
if (!invocationResult.invocations.length && shouldDeleteMessage) {
|
||||
ToolManager.showToolCallError(invocationResult.errors);
|
||||
if (shouldStopGeneration) {
|
||||
if (Array.isArray(invocationResult.errors) && invocationResult.errors.length) {
|
||||
ToolManager.showToolCallError(invocationResult.errors);
|
||||
}
|
||||
unblockGeneration(type);
|
||||
generatedPromptCache = '';
|
||||
streamingProcessor = null;
|
||||
@ -4638,9 +4704,12 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
|
||||
const shouldDeleteMessage = type !== 'swipe' && ['', '...'].includes(getMessage);
|
||||
hasToolCalls && shouldDeleteMessage && await deleteLastMessage();
|
||||
const invocationResult = await ToolManager.invokeFunctionTools(data);
|
||||
const shouldStopGeneration = (!invocationResult.invocations.length && shouldDeleteMessage) || invocationResult.stealthCalls.length;
|
||||
if (hasToolCalls) {
|
||||
if (!invocationResult.invocations.length && shouldDeleteMessage) {
|
||||
ToolManager.showToolCallError(invocationResult.errors);
|
||||
if (shouldStopGeneration) {
|
||||
if (Array.isArray(invocationResult.errors) && invocationResult.errors.length) {
|
||||
ToolManager.showToolCallError(invocationResult.errors);
|
||||
}
|
||||
unblockGeneration(type);
|
||||
generatedPromptCache = '';
|
||||
return;
|
||||
@ -4740,9 +4809,9 @@ export function stopGeneration() {
|
||||
* Injects extension prompts into chat messages.
|
||||
* @param {object[]} messages Array of chat messages
|
||||
* @param {boolean} isContinue Whether the generation is a continuation. If true, the extension prompts of depth 0 are injected at position 1.
|
||||
* @returns {number[]} Array of indices where the extension prompts were injected
|
||||
* @returns {Promise<number[]>} Array of indices where the extension prompts were injected
|
||||
*/
|
||||
function doChatInject(messages, isContinue) {
|
||||
async function doChatInject(messages, isContinue) {
|
||||
const injectedIndices = [];
|
||||
let totalInsertedMessages = 0;
|
||||
messages.reverse();
|
||||
@ -4760,7 +4829,7 @@ function doChatInject(messages, isContinue) {
|
||||
const wrap = false;
|
||||
|
||||
for (const role of roles) {
|
||||
const extensionPrompt = String(getExtensionPrompt(extension_prompt_types.IN_CHAT, i, separator, role, wrap)).trimStart();
|
||||
const extensionPrompt = String(await getExtensionPrompt(extension_prompt_types.IN_CHAT, i, separator, role, wrap)).trimStart();
|
||||
const isNarrator = role === extension_prompt_roles.SYSTEM;
|
||||
const isUser = role === extension_prompt_roles.USER;
|
||||
const name = names[role];
|
||||
@ -7453,14 +7522,16 @@ function select_rm_characters() {
|
||||
* @param {number} depth Insertion depth. 0 represets the last message in context. Expected values up to MAX_INJECTION_DEPTH.
|
||||
* @param {number} role Extension prompt role. Defaults to SYSTEM.
|
||||
* @param {boolean} scan Should the prompt be included in the world info scan.
|
||||
* @param {(function(): Promise<boolean>|boolean)} filter Filter function to determine if the prompt should be injected.
|
||||
*/
|
||||
export function setExtensionPrompt(key, value, position, depth, scan = false, role = extension_prompt_roles.SYSTEM) {
|
||||
export function setExtensionPrompt(key, value, position, depth, scan = false, role = extension_prompt_roles.SYSTEM, filter = null) {
|
||||
extension_prompts[key] = {
|
||||
value: String(value),
|
||||
position: Number(position),
|
||||
depth: Number(depth),
|
||||
scan: !!scan,
|
||||
role: Number(role ?? extension_prompt_roles.SYSTEM),
|
||||
filter: filter,
|
||||
};
|
||||
}
|
||||
|
||||
@ -9158,40 +9229,48 @@ function doDrawerOpenClick() {
|
||||
* @returns {void}
|
||||
*/
|
||||
function doNavbarIconClick() {
|
||||
var icon = $(this).find('.drawer-icon');
|
||||
var drawer = $(this).parent().find('.drawer-content');
|
||||
const icon = $(this).find('.drawer-icon');
|
||||
const drawer = $(this).parent().find('.drawer-content');
|
||||
if (drawer.hasClass('resizing')) { return; }
|
||||
var drawerWasOpenAlready = $(this).parent().find('.drawer-content').hasClass('openDrawer');
|
||||
let targetDrawerID = $(this).parent().find('.drawer-content').attr('id');
|
||||
const drawerWasOpenAlready = $(this).parent().find('.drawer-content').hasClass('openDrawer');
|
||||
const targetDrawerID = $(this).parent().find('.drawer-content').attr('id');
|
||||
const pinnedDrawerClicked = drawer.hasClass('pinnedOpen');
|
||||
|
||||
if (!drawerWasOpenAlready) { //to open the drawer
|
||||
$('.openDrawer').not('.pinnedOpen').addClass('resizing').slideToggle(200, 'swing', async function () {
|
||||
await delay(50); $(this).closest('.drawer-content').removeClass('resizing');
|
||||
$('.openDrawer').not('.pinnedOpen').addClass('resizing').each((_, el) => {
|
||||
slideToggle(el, {
|
||||
...getSlideToggleOptions(),
|
||||
onAnimationEnd: function (el) {
|
||||
el.closest('.drawer-content').classList.remove('resizing');
|
||||
},
|
||||
});
|
||||
});
|
||||
$('.openIcon').toggleClass('closedIcon openIcon');
|
||||
$('.openIcon').not('.drawerPinnedOpen').toggleClass('closedIcon openIcon');
|
||||
$('.openDrawer').not('.pinnedOpen').toggleClass('closedDrawer openDrawer');
|
||||
icon.toggleClass('openIcon closedIcon');
|
||||
drawer.toggleClass('openDrawer closedDrawer');
|
||||
|
||||
//console.log(targetDrawerID);
|
||||
if (targetDrawerID === 'right-nav-panel') {
|
||||
$(this).closest('.drawer').find('.drawer-content').addClass('resizing').slideToggle({
|
||||
duration: 200,
|
||||
easing: 'swing',
|
||||
start: function () {
|
||||
jQuery(this).css('display', 'flex'); //flex needed to make charlist scroll
|
||||
},
|
||||
complete: async function () {
|
||||
favsToHotswap();
|
||||
await delay(50);
|
||||
$(this).closest('.drawer-content').removeClass('resizing');
|
||||
$('#rm_print_characters_block').trigger('scroll');
|
||||
},
|
||||
$(this).closest('.drawer').find('.drawer-content').addClass('resizing').each((_, el) => {
|
||||
slideToggle(el, {
|
||||
...getSlideToggleOptions(),
|
||||
elementDisplayStyle: 'flex',
|
||||
onAnimationEnd: function (el) {
|
||||
el.closest('.drawer-content').classList.remove('resizing');
|
||||
favsToHotswap();
|
||||
$('#rm_print_characters_block').trigger('scroll');
|
||||
},
|
||||
});
|
||||
});
|
||||
} else {
|
||||
$(this).closest('.drawer').find('.drawer-content').addClass('resizing').slideToggle(200, 'swing', async function () {
|
||||
await delay(50); $(this).closest('.drawer-content').removeClass('resizing');
|
||||
$(this).closest('.drawer').find('.drawer-content').addClass('resizing').each((_, el) => {
|
||||
slideToggle(el, {
|
||||
...getSlideToggleOptions(),
|
||||
onAnimationEnd: function (el) {
|
||||
el.closest('.drawer-content').classList.remove('resizing');
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -9207,13 +9286,23 @@ function doNavbarIconClick() {
|
||||
icon.toggleClass('closedIcon openIcon');
|
||||
|
||||
if (pinnedDrawerClicked) {
|
||||
$(drawer).addClass('resizing').slideToggle(200, 'swing', async function () {
|
||||
await delay(50); $(this).removeClass('resizing');
|
||||
$(drawer).addClass('resizing').each((_, el) => {
|
||||
slideToggle(el, {
|
||||
...getSlideToggleOptions(),
|
||||
onAnimationEnd: function (el) {
|
||||
el.classList.remove('resizing');
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
$('.openDrawer').not('.pinnedOpen').addClass('resizing').slideToggle(200, 'swing', async function () {
|
||||
await delay(50); $(this).closest('.drawer-content').removeClass('resizing');
|
||||
$('.openDrawer').not('.pinnedOpen').addClass('resizing').each((_, el) => {
|
||||
slideToggle(el, {
|
||||
...getSlideToggleOptions(),
|
||||
onAnimationEnd: function (el) {
|
||||
el.closest('.drawer-content').classList.remove('resizing');
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -9271,6 +9360,11 @@ function addDebugFunctions() {
|
||||
toastr.info('Event tracing is now ' + (localStorage.getItem('eventTracing') === 'true' ? 'enabled' : 'disabled'));
|
||||
});
|
||||
|
||||
registerDebugFunction('toggleRegenerateWarning', 'Toggle Ctrl+Enter regeneration confirmation', 'Toggle the warning when regenerating a message with a Ctrl+Enter hotkey.', () => {
|
||||
localStorage.setItem('RegenerateWithCtrlEnter', localStorage.getItem('RegenerateWithCtrlEnter') === 'true' ? 'false' : 'true');
|
||||
toastr.info('Regenerate warning is now ' + (localStorage.getItem('RegenerateWithCtrlEnter') === 'true' ? 'disabled' : 'enabled'));
|
||||
});
|
||||
|
||||
registerDebugFunction('copySetup', 'Copy ST setup to clipboard [WIP]', 'Useful data when reporting bugs', async () => {
|
||||
const getContextContents = getContext();
|
||||
const getSettingsContents = settings;
|
||||
@ -9649,25 +9743,29 @@ jQuery(async function () {
|
||||
chooseBogusFolder($(this), tagId);
|
||||
});
|
||||
|
||||
/**
|
||||
* Sets the scroll height of the edit textarea to fit the content.
|
||||
* @param {HTMLTextAreaElement} e Textarea element to auto-fit
|
||||
*/
|
||||
function autoFitEditTextArea(e) {
|
||||
scroll_holder = chatElement[0].scrollTop;
|
||||
e.style.height = '0px';
|
||||
const newHeight = e.scrollHeight + 4;
|
||||
e.style.height = `${newHeight}px`;
|
||||
is_use_scroll_holder = true;
|
||||
}
|
||||
const autoFitEditTextAreaDebounced = debounce(autoFitEditTextArea, debounce_timeout.short);
|
||||
document.addEventListener('input', e => {
|
||||
if (e.target instanceof HTMLTextAreaElement && e.target.classList.contains('edit_textarea')) {
|
||||
const scrollbarShown = e.target.clientWidth < e.target.offsetWidth && e.target.offsetHeight >= window.innerHeight * 0.75;
|
||||
const immediately = (e.target.scrollHeight > e.target.offsetHeight && !scrollbarShown) || e.target.value === '';
|
||||
immediately ? autoFitEditTextArea(e.target) : autoFitEditTextAreaDebounced(e.target);
|
||||
const cssAutofit = CSS.supports('field-sizing', 'content');
|
||||
if (!cssAutofit) {
|
||||
/**
|
||||
* Sets the scroll height of the edit textarea to fit the content.
|
||||
* @param {HTMLTextAreaElement} e Textarea element to auto-fit
|
||||
*/
|
||||
function autoFitEditTextArea(e) {
|
||||
const scrollTop = chatElement.scrollTop();
|
||||
e.style.height = '0px';
|
||||
const newHeight = e.scrollHeight + 4;
|
||||
e.style.height = `${newHeight}px`;
|
||||
chatElement.scrollTop(scrollTop);
|
||||
}
|
||||
});
|
||||
const autoFitEditTextAreaDebounced = debounce(autoFitEditTextArea, debounce_timeout.short);
|
||||
document.addEventListener('input', e => {
|
||||
if (e.target instanceof HTMLTextAreaElement && e.target.classList.contains('edit_textarea')) {
|
||||
const scrollbarShown = e.target.clientWidth < e.target.offsetWidth && e.target.offsetHeight >= window.innerHeight * 0.75;
|
||||
const immediately = (e.target.scrollHeight > e.target.offsetHeight && !scrollbarShown) || e.target.value === '';
|
||||
immediately ? autoFitEditTextArea(e.target) : autoFitEditTextAreaDebounced(e.target);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const chatElementScroll = document.getElementById('chat');
|
||||
const chatScrollHandler = function () {
|
||||
if (power_user.waifuMode) {
|
||||
@ -9689,12 +9787,6 @@ jQuery(async function () {
|
||||
};
|
||||
chatElementScroll.addEventListener('wheel', chatScrollHandler, { passive: true });
|
||||
chatElementScroll.addEventListener('touchmove', chatScrollHandler, { passive: true });
|
||||
chatElementScroll.addEventListener('scroll', function () {
|
||||
if (is_use_scroll_holder) {
|
||||
this.scrollTop = scroll_holder;
|
||||
is_use_scroll_holder = false;
|
||||
}
|
||||
}, { passive: true });
|
||||
|
||||
$(document).on('click', '.mes', function () {
|
||||
//when a 'delete message' parent div is clicked
|
||||
@ -10000,6 +10092,7 @@ jQuery(async function () {
|
||||
{ id: 'api_key_llamacpp', secret: SECRET_KEYS.LLAMACPP },
|
||||
{ id: 'api_key_featherless', secret: SECRET_KEYS.FEATHERLESS },
|
||||
{ id: 'api_key_huggingface', secret: SECRET_KEYS.HUGGINGFACE },
|
||||
{ id: 'api_key_generic', secret: SECRET_KEYS.GENERIC },
|
||||
];
|
||||
|
||||
for (const key of keys) {
|
||||
@ -10034,20 +10127,21 @@ jQuery(async function () {
|
||||
await getStatusNovel();
|
||||
});
|
||||
|
||||
var button = $('#options_button');
|
||||
var menu = $('#options');
|
||||
const button = $('#options_button');
|
||||
const menu = $('#options');
|
||||
let isOptionsMenuVisible = false;
|
||||
|
||||
function showMenu() {
|
||||
showBookmarksButtons();
|
||||
// menu.stop()
|
||||
menu.fadeIn(animation_duration);
|
||||
optionsPopper.update();
|
||||
isOptionsMenuVisible = true;
|
||||
}
|
||||
|
||||
function hideMenu() {
|
||||
// menu.stop();
|
||||
menu.fadeOut(animation_duration);
|
||||
optionsPopper.update();
|
||||
isOptionsMenuVisible = false;
|
||||
}
|
||||
|
||||
function isMouseOverButtonOrMenu() {
|
||||
@ -10055,26 +10149,15 @@ jQuery(async function () {
|
||||
}
|
||||
|
||||
button.on('click', function () {
|
||||
if (menu.is(':visible')) {
|
||||
if (isOptionsMenuVisible) {
|
||||
hideMenu();
|
||||
} else {
|
||||
showMenu();
|
||||
}
|
||||
});
|
||||
button.on('blur', function () {
|
||||
//delay to prevent menu hiding when mouse leaves button into menu
|
||||
setTimeout(() => {
|
||||
if (!isMouseOverButtonOrMenu()) { hideMenu(); }
|
||||
}, 100);
|
||||
});
|
||||
menu.on('blur', function () {
|
||||
//delay to prevent menu hide when mouseleaves menu into button
|
||||
setTimeout(() => {
|
||||
if (!isMouseOverButtonOrMenu()) { hideMenu(); }
|
||||
}, 100);
|
||||
});
|
||||
$(document).on('click', function () {
|
||||
if (!isMouseOverButtonOrMenu() && menu.is(':visible')) { hideMenu(); }
|
||||
if (!isOptionsMenuVisible) return;
|
||||
if (!isMouseOverButtonOrMenu()) { hideMenu(); }
|
||||
});
|
||||
|
||||
/* $('#set_chat_scenario').on('click', setScenarioOverride); */
|
||||
@ -10442,14 +10525,16 @@ jQuery(async function () {
|
||||
.closest('.mes_block')
|
||||
.find('.mes_text')
|
||||
.append(
|
||||
'<textarea id=\'curEditTextarea\' class=\'edit_textarea mdHotkeys\' style=\'max-width:auto;\'></textarea>',
|
||||
'<textarea id=\'curEditTextarea\' class=\'edit_textarea mdHotkeys\'></textarea>',
|
||||
);
|
||||
$('#curEditTextarea').val(text);
|
||||
let edit_textarea = $(this)
|
||||
.closest('.mes_block')
|
||||
.find('.edit_textarea');
|
||||
edit_textarea.height(0);
|
||||
edit_textarea.height(edit_textarea[0].scrollHeight);
|
||||
if (!cssAutofit) {
|
||||
edit_textarea.height(0);
|
||||
edit_textarea.height(edit_textarea[0].scrollHeight);
|
||||
}
|
||||
edit_textarea.focus();
|
||||
edit_textarea[0].setSelectionRange( //this sets the cursor at the end of the text
|
||||
String(edit_textarea.val()).length,
|
||||
@ -10470,22 +10555,28 @@ jQuery(async function () {
|
||||
});
|
||||
|
||||
$(document).on('click', '.extraMesButtonsHint', function (e) {
|
||||
const elmnt = e.target;
|
||||
$(elmnt).transition({
|
||||
const $hint = $(e.target);
|
||||
const $buttons = $hint.siblings('.extraMesButtons');
|
||||
|
||||
$hint.transition({
|
||||
opacity: 0,
|
||||
duration: animation_duration,
|
||||
easing: 'ease-in-out',
|
||||
easing: animation_easing,
|
||||
complete: function () {
|
||||
$hint.hide();
|
||||
$buttons
|
||||
.addClass('visible')
|
||||
.css({
|
||||
opacity: 0,
|
||||
display: 'flex',
|
||||
})
|
||||
.transition({
|
||||
opacity: 1,
|
||||
duration: animation_duration,
|
||||
easing: animation_easing,
|
||||
});
|
||||
},
|
||||
});
|
||||
setTimeout(function () {
|
||||
$(elmnt).hide();
|
||||
$(elmnt).siblings('.extraMesButtons').css('opcacity', '0');
|
||||
$(elmnt).siblings('.extraMesButtons').css('display', 'flex');
|
||||
$(elmnt).siblings('.extraMesButtons').transition({
|
||||
opacity: 1,
|
||||
duration: animation_duration,
|
||||
easing: 'ease-in-out',
|
||||
});
|
||||
}, animation_duration);
|
||||
});
|
||||
|
||||
$(document).on('click', function (e) {
|
||||
@ -10496,23 +10587,36 @@ jQuery(async function () {
|
||||
|
||||
// Check if the click was outside the relevant elements
|
||||
if (!$(e.target).closest('.extraMesButtons, .extraMesButtonsHint').length) {
|
||||
const $visibleButtons = $('.extraMesButtons.visible');
|
||||
|
||||
if (!$visibleButtons.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const $hiddenHints = $('.extraMesButtonsHint:hidden');
|
||||
|
||||
// Transition out the .extraMesButtons first
|
||||
$('.extraMesButtons:visible').transition({
|
||||
$visibleButtons.transition({
|
||||
opacity: 0,
|
||||
duration: animation_duration,
|
||||
easing: 'ease-in-out',
|
||||
easing: animation_easing,
|
||||
complete: function () {
|
||||
$(this).hide(); // Hide the .extraMesButtons after the transition
|
||||
// Hide the .extraMesButtons after the transition
|
||||
$(this)
|
||||
.hide()
|
||||
.removeClass('visible');
|
||||
|
||||
// Transition the .extraMesButtonsHint back in
|
||||
$('.extraMesButtonsHint:not(:visible)').show().transition({
|
||||
opacity: .3,
|
||||
duration: animation_duration,
|
||||
easing: 'ease-in-out',
|
||||
complete: function () {
|
||||
$(this).css('opacity', '');
|
||||
},
|
||||
});
|
||||
$hiddenHints
|
||||
.show()
|
||||
.transition({
|
||||
opacity: 0.3,
|
||||
duration: animation_duration,
|
||||
easing: animation_easing,
|
||||
complete: function () {
|
||||
$(this).css('opacity', '');
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
@ -10696,8 +10800,9 @@ jQuery(async function () {
|
||||
}
|
||||
});
|
||||
|
||||
$('#export_button').on('click', function (e) {
|
||||
$('#export_format_popup').toggle();
|
||||
$('#export_button').on('click', function () {
|
||||
isExportPopupOpen = !isExportPopupOpen;
|
||||
$('#export_format_popup').toggle(isExportPopupOpen);
|
||||
exportPopper.update();
|
||||
});
|
||||
|
||||
@ -10708,6 +10813,10 @@ jQuery(async function () {
|
||||
return;
|
||||
}
|
||||
|
||||
$('#export_format_popup').hide();
|
||||
isExportPopupOpen = false;
|
||||
exportPopper.update();
|
||||
|
||||
// Save before exporting
|
||||
await createOrEditCharacter();
|
||||
const body = { format, avatar_url: characters[this_chid].avatar };
|
||||
@ -10729,9 +10838,6 @@ jQuery(async function () {
|
||||
URL.revokeObjectURL(a.href);
|
||||
document.body.removeChild(a);
|
||||
}
|
||||
|
||||
|
||||
$('#export_format_popup').hide();
|
||||
});
|
||||
//**************************CHAT IMPORT EXPORT*************************//
|
||||
$('#chat_import_button').click(function () {
|
||||
@ -10803,15 +10909,18 @@ jQuery(async function () {
|
||||
});
|
||||
|
||||
$(document).on('click', '.drawer-opener', doDrawerOpenClick);
|
||||
|
||||
$('.drawer-toggle').on('click', doNavbarIconClick);
|
||||
|
||||
$('html').on('touchstart mousedown', function (e) {
|
||||
var clickTarget = $(e.target);
|
||||
|
||||
if ($('#export_format_popup').is(':visible')
|
||||
if (isExportPopupOpen
|
||||
&& clickTarget.closest('#export_button').length == 0
|
||||
&& clickTarget.closest('#export_format_popup').length == 0) {
|
||||
$('#export_format_popup').hide();
|
||||
isExportPopupOpen = false;
|
||||
exportPopper.update();
|
||||
}
|
||||
|
||||
const forbiddenTargets = [
|
||||
@ -10836,12 +10945,16 @@ jQuery(async function () {
|
||||
if ($('.openDrawer').length !== 0) {
|
||||
if (targetParentHasOpenDrawer === 0) {
|
||||
//console.log($('.openDrawer').not('.pinnedOpen').length);
|
||||
$('.openDrawer').not('.pinnedOpen').addClass('resizing').slideToggle(200, 'swing', function () {
|
||||
$(this).closest('.drawer-content').removeClass('resizing');
|
||||
$('.openDrawer').not('.pinnedOpen').addClass('resizing').each((_, el) => {
|
||||
slideToggle(el, {
|
||||
...getSlideToggleOptions(),
|
||||
onAnimationEnd: (el) => {
|
||||
el.closest('.drawer-content').classList.remove('resizing');
|
||||
},
|
||||
});
|
||||
});
|
||||
$('.openIcon').not('.drawerPinnedOpen').toggleClass('closedIcon openIcon');
|
||||
$('.openDrawer').not('.pinnedOpen').toggleClass('closedDrawer openDrawer');
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -11007,14 +11120,6 @@ jQuery(async function () {
|
||||
case 'renameCharButton':
|
||||
renameCharacter();
|
||||
break;
|
||||
/*case 'dupe_button':
|
||||
DupeChar();
|
||||
break;
|
||||
case 'export_button':
|
||||
$('#export_format_popup').toggle();
|
||||
exportPopper.update();
|
||||
break;
|
||||
*/
|
||||
case 'import_character_info':
|
||||
await importEmbeddedWorldInfo();
|
||||
saveCharacterDebounced();
|
||||
|
@ -1208,7 +1208,7 @@ class PromptManager {
|
||||
const forbidOverridesBlock = document.getElementById(this.configuration.prefix + 'prompt_manager_forbid_overrides_block');
|
||||
|
||||
nameField.value = prompt.name ?? '';
|
||||
roleField.value = prompt.role ?? 'system';
|
||||
roleField.value = prompt.role || 'system';
|
||||
promptField.value = prompt.content ?? '';
|
||||
promptField.disabled = prompt.marker ?? false;
|
||||
injectionPositionField.value = prompt.injection_position ?? INJECTION_POSITION.RELATIVE;
|
||||
@ -1518,7 +1518,7 @@ class PromptManager {
|
||||
let detachSpanHtml = '';
|
||||
if (this.isPromptDeletionAllowed(prompt)) {
|
||||
detachSpanHtml = `
|
||||
<span title="Remove" class="prompt-manager-detach-action caution fa-solid fa-chain-broken"></span>
|
||||
<span title="Remove" class="prompt-manager-detach-action caution fa-solid fa-chain-broken fa-xs"></span>
|
||||
`;
|
||||
} else {
|
||||
detachSpanHtml = '<span class="fa-solid"></span>';
|
||||
@ -1527,7 +1527,7 @@ class PromptManager {
|
||||
let editSpanHtml = '';
|
||||
if (this.isPromptEditAllowed(prompt)) {
|
||||
editSpanHtml = `
|
||||
<span title="edit" class="prompt-manager-edit-action fa-solid fa-pencil"></span>
|
||||
<span title="edit" class="prompt-manager-edit-action fa-solid fa-pencil fa-xs"></span>
|
||||
`;
|
||||
} else {
|
||||
editSpanHtml = '<span class="fa-solid"></span>';
|
||||
@ -1550,16 +1550,27 @@ class PromptManager {
|
||||
const isInjectionPrompt = prompt.injection_position === INJECTION_POSITION.ABSOLUTE;
|
||||
const isOverriddenPrompt = Array.isArray(this.overriddenPrompts) && this.overriddenPrompts.includes(prompt.identifier);
|
||||
const importantClass = isImportantPrompt ? `${prefix}prompt_manager_important` : '';
|
||||
const iconLookup = prompt.role === 'system' && (prompt.marker || prompt.system_prompt) ? '' : prompt.role;
|
||||
|
||||
//add role icons to the right of prompt name
|
||||
const promptRoles = {
|
||||
assistant: { roleIcon: 'fa-robot', roleTitle: 'Prompt will be sent as Assistant' },
|
||||
user: { roleIcon: 'fa-user', roleTitle: 'Prompt will be sent as User' },
|
||||
};
|
||||
const roleIcon = promptRoles[iconLookup]?.roleIcon || '';
|
||||
const roleTitle = promptRoles[iconLookup]?.roleTitle || '';
|
||||
|
||||
listItemHtml += `
|
||||
<li class="${prefix}prompt_manager_prompt ${draggableClass} ${enabledClass} ${markerClass} ${importantClass}" data-pm-identifier="${prompt.identifier}">
|
||||
<li class="${prefix}prompt_manager_prompt ${draggableClass} ${enabledClass} ${markerClass} ${importantClass}" data-pm-identifier="${escapeHtml(prompt.identifier)}">
|
||||
<span class="${prefix}prompt_manager_prompt_name" data-pm-name="${encodedName}">
|
||||
${isMarkerPrompt ? '<span class="fa-fw fa-solid fa-thumb-tack" title="Marker"></span>' : ''}
|
||||
${isSystemPrompt ? '<span class="fa-fw fa-solid fa-square-poll-horizontal" title="Global Prompt"></span>' : ''}
|
||||
${isImportantPrompt ? '<span class="fa-fw fa-solid fa-star" title="Important Prompt"></span>' : ''}
|
||||
${isUserPrompt ? '<span class="fa-fw fa-solid fa-user" title="User Prompt"></span>' : ''}
|
||||
${isUserPrompt ? '<span class="fa-fw fa-solid fa-asterisk" title="Preset Prompt"></span>' : ''}
|
||||
${isInjectionPrompt ? '<span class="fa-fw fa-solid fa-syringe" title="In-Chat Injection"></span>' : ''}
|
||||
${this.isPromptInspectionAllowed(prompt) ? `<a title="${encodedName}" class="prompt-manager-inspect-action">${encodedName}</a>` : `<span title="${encodedName}">${encodedName}</span>`}
|
||||
${isInjectionPrompt ? `<small class="prompt-manager-injection-depth">@ ${prompt.injection_depth}</small>` : ''}
|
||||
${roleIcon ? `<span data-role="${escapeHtml(prompt.role)}" class="fa-xs fa-solid ${roleIcon}" title="${roleTitle}"></span>` : ''}
|
||||
${isInjectionPrompt ? `<small class="prompt-manager-injection-depth">@ ${escapeHtml(prompt.injection_depth)}</small>` : ''}
|
||||
${isOverriddenPrompt ? '<small class="fa-solid fa-address-card prompt-manager-overridden" title="Pulled from a character card"></small>' : ''}
|
||||
</span>
|
||||
<span>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { DOMPurify, Bowser } from '../lib.js';
|
||||
import { DOMPurify, Bowser, slideToggle } from '../lib.js';
|
||||
|
||||
import {
|
||||
characters,
|
||||
@ -19,6 +19,7 @@ import {
|
||||
menu_type,
|
||||
substituteParams,
|
||||
sendTextareaMessage,
|
||||
getSlideToggleOptions,
|
||||
} from '../script.js';
|
||||
|
||||
import {
|
||||
@ -315,7 +316,7 @@ function RA_checkOnlineStatus() {
|
||||
if (online_status == 'no_connection') {
|
||||
const send_textarea = $('#send_textarea');
|
||||
send_textarea.attr('placeholder', send_textarea.attr('no_connection_text')); //Input bar placeholder tells users they are not connected
|
||||
//$('#send_form').addClass('no-connection'); //entire input form area is red when not connected
|
||||
$('#send_form').addClass('no-connection');
|
||||
$('#send_but').addClass('displayNone'); //send button is hidden when not connected;
|
||||
$('#mes_continue').addClass('displayNone'); //continue button is hidden when not connected;
|
||||
$('#mes_impersonate').addClass('displayNone'); //continue button is hidden when not connected;
|
||||
@ -326,7 +327,7 @@ function RA_checkOnlineStatus() {
|
||||
if (online_status !== undefined && online_status !== 'no_connection') {
|
||||
const send_textarea = $('#send_textarea');
|
||||
send_textarea.attr('placeholder', send_textarea.attr('connected_text')); //on connect, placeholder tells user to type message
|
||||
//$('#send_form').removeClass('no-connection');
|
||||
$('#send_form').removeClass('no-connection');
|
||||
$('#API-status-top').removeClass('fa-plug-circle-exclamation redOverlayGlow');
|
||||
$('#API-status-top').addClass('fa-plug');
|
||||
connection_made = true;
|
||||
@ -389,6 +390,7 @@ function RA_autoconnect(PrevApi) {
|
||||
|| (secret_state[SECRET_KEYS.ZEROONEAI] && oai_settings.chat_completion_source == chat_completion_sources.ZEROONEAI)
|
||||
|| (secret_state[SECRET_KEYS.BLOCKENTROPY] && oai_settings.chat_completion_source == chat_completion_sources.BLOCKENTROPY)
|
||||
|| (secret_state[SECRET_KEYS.NANOGPT] && oai_settings.chat_completion_source == chat_completion_sources.NANOGPT)
|
||||
|| (secret_state[SECRET_KEYS.DEEPSEEK] && oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK)
|
||||
|| (isValidUrl(oai_settings.custom_url) && oai_settings.chat_completion_source == chat_completion_sources.CUSTOM)
|
||||
) {
|
||||
$('#api_button_openai').trigger('click');
|
||||
@ -748,8 +750,8 @@ export function initRossMods() {
|
||||
$(RightNavDrawerIcon).removeClass('drawerPinnedOpen');
|
||||
|
||||
if ($(RightNavPanel).hasClass('openDrawer') && $('.openDrawer').length > 1) {
|
||||
$(RightNavPanel).slideToggle(200, 'swing');
|
||||
$(RightNavDrawerIcon).toggleClass('openIcon closedIcon');
|
||||
slideToggle(RightNavPanel, getSlideToggleOptions());
|
||||
$(RightNavDrawerIcon).toggleClass('closedIcon openIcon');
|
||||
$(RightNavPanel).toggleClass('openDrawer closedDrawer');
|
||||
}
|
||||
}
|
||||
@ -766,8 +768,8 @@ export function initRossMods() {
|
||||
$(LeftNavDrawerIcon).removeClass('drawerPinnedOpen');
|
||||
|
||||
if ($(LeftNavPanel).hasClass('openDrawer') && $('.openDrawer').length > 1) {
|
||||
$(LeftNavPanel).slideToggle(200, 'swing');
|
||||
$(LeftNavDrawerIcon).toggleClass('openIcon closedIcon');
|
||||
slideToggle(LeftNavPanel, getSlideToggleOptions());
|
||||
$(LeftNavDrawerIcon).toggleClass('closedIcon openIcon');
|
||||
$(LeftNavPanel).toggleClass('openDrawer closedDrawer');
|
||||
}
|
||||
}
|
||||
@ -786,8 +788,8 @@ export function initRossMods() {
|
||||
|
||||
if ($(WorldInfo).hasClass('openDrawer') && $('.openDrawer').length > 1) {
|
||||
console.debug('closing WI after lock removal');
|
||||
$(WorldInfo).slideToggle(200, 'swing');
|
||||
$(WIDrawerIcon).toggleClass('openIcon closedIcon');
|
||||
slideToggle(WorldInfo, getSlideToggleOptions());
|
||||
$(WIDrawerIcon).toggleClass('closedIcon openIcon');
|
||||
$(WorldInfo).toggleClass('openDrawer closedDrawer');
|
||||
}
|
||||
}
|
||||
@ -886,7 +888,40 @@ export function initRossMods() {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
const cssAutofit = CSS.supports('field-sizing', 'content');
|
||||
|
||||
if (cssAutofit) {
|
||||
let lastHeight = chatBlock.offsetHeight;
|
||||
const chatBlockResizeObserver = new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
if (entry.target !== chatBlock) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const threshold = 1;
|
||||
const newHeight = chatBlock.offsetHeight;
|
||||
const deltaHeight = newHeight - lastHeight;
|
||||
const isScrollAtBottom = Math.abs(chatBlock.scrollHeight - chatBlock.scrollTop - newHeight) <= threshold;
|
||||
|
||||
if (!isScrollAtBottom && Math.abs(deltaHeight) > threshold) {
|
||||
chatBlock.scrollTop -= deltaHeight;
|
||||
}
|
||||
lastHeight = newHeight;
|
||||
}
|
||||
});
|
||||
|
||||
chatBlockResizeObserver.observe(chatBlock);
|
||||
}
|
||||
|
||||
sendTextArea.addEventListener('input', () => {
|
||||
saveUserInputDebounced();
|
||||
|
||||
if (cssAutofit) {
|
||||
// Unset modifications made with a manual resize
|
||||
sendTextArea.style.height = 'auto';
|
||||
return;
|
||||
}
|
||||
|
||||
const hasContent = sendTextArea.value !== '';
|
||||
const fitsCurrentSize = sendTextArea.scrollHeight <= sendTextArea.offsetHeight;
|
||||
const isScrollbarShown = sendTextArea.clientWidth < sendTextArea.offsetWidth;
|
||||
@ -894,7 +929,6 @@ export function initRossMods() {
|
||||
const needsDebounce = hasContent && (fitsCurrentSize || (isScrollbarShown && isHalfScreenHeight));
|
||||
if (needsDebounce) autoFitSendTextAreaDebounced();
|
||||
else autoFitSendTextArea();
|
||||
saveUserInputDebounced();
|
||||
});
|
||||
|
||||
restoreUserInput();
|
||||
|
@ -440,7 +440,7 @@ async function onChatChanged() {
|
||||
const context = getContext();
|
||||
|
||||
// Disable the chara note if in a group
|
||||
$('#extension_floating_chara').prop('disabled', context.groupId ? true : false);
|
||||
$('#extension_floating_chara').prop('disabled', !!context.groupId);
|
||||
|
||||
const tokenCounter1 = chat_metadata[metadata_keys.prompt] ? await getTokenCountAsync(chat_metadata[metadata_keys.prompt]) : 0;
|
||||
$('#extension_floating_prompt_token_counter').text(tokenCounter1);
|
||||
|
@ -12,6 +12,7 @@ const LIST_METADATA_KEY = 'chat_backgrounds';
|
||||
export let background_settings = {
|
||||
name: '__transparent.png',
|
||||
url: generateUrlParameter('__transparent.png', false),
|
||||
fitting: 'classic',
|
||||
};
|
||||
|
||||
export function loadBackgroundSettings(settings) {
|
||||
@ -19,7 +20,12 @@ export function loadBackgroundSettings(settings) {
|
||||
if (!backgroundSettings || !backgroundSettings.name || !backgroundSettings.url) {
|
||||
backgroundSettings = background_settings;
|
||||
}
|
||||
if (!backgroundSettings.fitting) {
|
||||
backgroundSettings.fitting = 'classic';
|
||||
}
|
||||
setBackground(backgroundSettings.name, backgroundSettings.url);
|
||||
setFittingClass(backgroundSettings.fitting);
|
||||
$('#background_fitting').val(backgroundSettings.fitting);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -333,6 +339,14 @@ async function autoBackgroundCommand() {
|
||||
const bestMatch = fuse.search(reply, { limit: 1 });
|
||||
|
||||
if (bestMatch.length == 0) {
|
||||
for (const option of options) {
|
||||
if (String(reply).toLowerCase().includes(option.text.toLowerCase())) {
|
||||
console.debug('Fallback choosing background:', option);
|
||||
option.element.click();
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
toastr.warning('No match found. Please try again.');
|
||||
return '';
|
||||
}
|
||||
@ -462,6 +476,18 @@ function highlightNewBackground(bg) {
|
||||
flashHighlight(newBg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the fitting class for the background element
|
||||
* @param {string} fitting Fitting type
|
||||
*/
|
||||
function setFittingClass(fitting) {
|
||||
const backgrounds = $('#bg1, #bg_custom');
|
||||
backgrounds.toggleClass('cover', fitting === 'cover');
|
||||
backgrounds.toggleClass('contain', fitting === 'contain');
|
||||
backgrounds.toggleClass('stretch', fitting === 'stretch');
|
||||
backgrounds.toggleClass('center', fitting === 'center');
|
||||
}
|
||||
|
||||
function onBackgroundFilterInput() {
|
||||
const filterValue = String($(this).val()).toLowerCase();
|
||||
$('#bg_menu_content > div').each(function () {
|
||||
@ -502,4 +528,9 @@ export function initBackgrounds() {
|
||||
helpString: 'Automatically changes the background based on the chat context using the AI request prompt',
|
||||
}));
|
||||
|
||||
$('#background_fitting').on('input', function () {
|
||||
background_settings.fitting = String($(this).val());
|
||||
setFittingClass(background_settings.fitting);
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
}
|
||||
|
@ -451,8 +451,14 @@ function getCustomSeparator() {
|
||||
}
|
||||
}
|
||||
|
||||
// Gets the CFG prompt
|
||||
export function getCfgPrompt(guidanceScale, isNegative) {
|
||||
/**
|
||||
* Gets the CFG prompt based on the guidance scale.
|
||||
* @param {{type: number, value: number}} guidanceScale The CFG guidance scale
|
||||
* @param {boolean} isNegative Whether to get the negative prompt
|
||||
* @param {boolean} quiet Whether to suppress console output
|
||||
* @returns {{value: string, depth: number}} The CFG prompt and insertion depth
|
||||
*/
|
||||
export function getCfgPrompt(guidanceScale, isNegative, quiet = false) {
|
||||
let splitCfgPrompt = [];
|
||||
|
||||
const cfgPromptCombine = chat_metadata[metadataKeys.prompt_combine] ?? [];
|
||||
@ -484,7 +490,7 @@ export function getCfgPrompt(guidanceScale, isNegative) {
|
||||
const customSeparator = getCustomSeparator();
|
||||
const combinedCfgPrompt = splitCfgPrompt.filter((e) => e.length > 0).join(customSeparator);
|
||||
const insertionDepth = chat_metadata[metadataKeys.prompt_insertion_depth] ?? 1;
|
||||
console.log(`Setting CFG with guidance scale: ${guidanceScale.value}, negatives: ${combinedCfgPrompt}`);
|
||||
!quiet && console.log(`Setting CFG with guidance scale: ${guidanceScale.value}, negatives: ${combinedCfgPrompt}`);
|
||||
|
||||
return {
|
||||
value: combinedCfgPrompt,
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -393,7 +393,7 @@ jQuery(async function () {
|
||||
const sendButton = $(`
|
||||
<div id="send_picture" class="list-group-item flex-container flexGap5">
|
||||
<div class="fa-solid fa-image extensionsMenuExtensionButton"></div>
|
||||
Generate Caption
|
||||
<span data-i18n="Generate Caption">Generate Caption</span>
|
||||
</div>`);
|
||||
|
||||
$('#caption_wand_container').append(sendButton);
|
||||
|
@ -53,6 +53,8 @@
|
||||
<option data-type="anthropic" value="claude-3-opus-20240229">claude-3-opus-20240229</option>
|
||||
<option data-type="anthropic" value="claude-3-sonnet-20240229">claude-3-sonnet-20240229</option>
|
||||
<option data-type="anthropic" value="claude-3-haiku-20240307">claude-3-haiku-20240307</option>
|
||||
<option data-type="google" value="gemini-2.0-flash-exp">gemini-2.0-flash-exp</option>
|
||||
<option data-type="google" value="gemini-2.0-flash-thinking-exp-1219">gemini-2.0-flash-thinking-exp-1219</option>
|
||||
<option data-type="google" value="gemini-1.5-flash">gemini-1.5-flash</option>
|
||||
<option data-type="google" value="gemini-1.5-flash-latest">gemini-1.5-flash-latest</option>
|
||||
<option data-type="google" value="gemini-1.5-flash-001">gemini-1.5-flash-001</option>
|
||||
@ -69,7 +71,6 @@
|
||||
<option data-type="google" value="gemini-1.5-pro-002">gemini-1.5-pro-002</option>
|
||||
<option data-type="google" value="gemini-1.5-pro-exp-0801">gemini-1.5-pro-exp-0801</option>
|
||||
<option data-type="google" value="gemini-1.5-pro-exp-0827">gemini-1.5-pro-exp-0827</option>
|
||||
<option data-type="google" value="gemini-pro-vision">gemini-pro-vision</option>
|
||||
<option data-type="groq" value="llama-3.2-11b-vision-preview">llama-3.2-11b-vision-preview</option>
|
||||
<option data-type="groq" value="llama-3.2-90b-vision-preview">llama-3.2-90b-vision-preview</option>
|
||||
<option data-type="groq" value="llava-v1.5-7b-4096-preview">llava-v1.5-7b-4096-preview</option>
|
||||
|
@ -14,7 +14,7 @@ import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '
|
||||
import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashCommandEnumValue.js';
|
||||
import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||
import { slashCommandReturnHelper } from '../../slash-commands/SlashCommandReturnHelper.js';
|
||||
import { SlashCommandClosure } from '../../slash-commands/SlashCommandClosure.js';
|
||||
import { generateWebLlmChatPrompt, isWebLlmSupported } from '../shared.js';
|
||||
export { MODULE_NAME };
|
||||
|
||||
const MODULE_NAME = 'expressions';
|
||||
@ -59,6 +59,7 @@ const EXPRESSION_API = {
|
||||
local: 0,
|
||||
extras: 1,
|
||||
llm: 2,
|
||||
webllm: 3,
|
||||
};
|
||||
|
||||
let expressionsList = null;
|
||||
@ -697,6 +698,11 @@ async function moduleWorker() {
|
||||
return;
|
||||
}
|
||||
|
||||
// If using LLM api then check if streamingProcessor is finished to avoid sending multiple requests to the API
|
||||
if (extension_settings.expressions.api === EXPRESSION_API.llm && context.streamingProcessor && !context.streamingProcessor.isFinished) {
|
||||
return;
|
||||
}
|
||||
|
||||
// API is busy
|
||||
if (inApiCall) {
|
||||
console.debug('Classification API is busy');
|
||||
@ -847,7 +853,7 @@ function setTalkingHeadState(newState) {
|
||||
extension_settings.expressions.talkinghead = newState; // Store setting
|
||||
saveSettingsDebounced();
|
||||
|
||||
if (extension_settings.expressions.api == EXPRESSION_API.local || extension_settings.expressions.api == EXPRESSION_API.llm) {
|
||||
if ([EXPRESSION_API.local, EXPRESSION_API.llm, EXPRESSION_API.webllm].includes(extension_settings.expressions.api)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -979,6 +985,71 @@ async function setSpriteSlashCommand(_, spriteId) {
|
||||
return label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sprite folder name (including override) for a character.
|
||||
* @param {object} char Character object
|
||||
* @param {string} char.avatar Avatar filename with extension
|
||||
* @returns {string} Sprite folder name
|
||||
* @throws {Error} If character not found or avatar not set
|
||||
*/
|
||||
function spriteFolderNameFromCharacter(char) {
|
||||
const avatarFileName = char.avatar.replace(/\.[^/.]+$/, '');
|
||||
const expressionOverride = extension_settings.expressionOverrides.find(e => e.name === avatarFileName);
|
||||
return expressionOverride?.path ? expressionOverride.path : avatarFileName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Slash command callback for /uploadsprite
|
||||
*
|
||||
* label= is required
|
||||
* if name= is provided, it will be used as a findChar lookup
|
||||
* if name= is not provided, the last character's name will be used
|
||||
* if folder= is a full path, it will be used as the folder
|
||||
* if folder= is a partial path, it will be appended to the character's name
|
||||
* if folder= is not provided, the character's override folder will be used, if set
|
||||
*
|
||||
* @param {object} args
|
||||
* @param {string} args.name Character name or avatar key, passed through findChar
|
||||
* @param {string} args.label Expression label
|
||||
* @param {string} args.folder Sprite folder path, processed using backslash rules
|
||||
* @param {string} imageUrl Image URI to fetch and upload
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function uploadSpriteCommand({ name, label, folder }, imageUrl) {
|
||||
if (!imageUrl) throw new Error('Image URL is required');
|
||||
if (!label || typeof label !== 'string') throw new Error('Expression label is required');
|
||||
|
||||
label = label.replace(/[^a-z]/gi, '').toLowerCase().trim();
|
||||
if (!label) throw new Error('Expression label must contain at least one letter');
|
||||
|
||||
name = name || getLastCharacterMessage().original_avatar || getLastCharacterMessage().name;
|
||||
const char = findChar({ name });
|
||||
|
||||
if (!folder) {
|
||||
folder = spriteFolderNameFromCharacter(char);
|
||||
} else if (folder.startsWith('/') || folder.startsWith('\\')) {
|
||||
const subfolder = folder.slice(1);
|
||||
folder = `${char.name}/${subfolder}`;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(imageUrl);
|
||||
const blob = await response.blob();
|
||||
const file = new File([blob], 'image.png', { type: 'image/png' });
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('name', folder); // this is the folder or character name
|
||||
formData.append('label', label); // this is the expression label
|
||||
formData.append('avatar', file); // this is the image file
|
||||
|
||||
await handleFileUpload('/api/sprites/upload', formData);
|
||||
console.debug(`[${MODULE_NAME}] Upload of ${imageUrl} completed for ${name} with label ${label}`);
|
||||
} catch (error) {
|
||||
console.error(`[${MODULE_NAME}] Error uploading file:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the classification text to reduce the amount of text sent to the API.
|
||||
* Quotes and asterisks are to be removed. If the text is less than 300 characters, it is returned as is.
|
||||
@ -995,6 +1066,11 @@ function sampleClassifyText(text) {
|
||||
// Replace macros, remove asterisks and quotes
|
||||
let result = substituteParams(text).replace(/[*"]/g, '');
|
||||
|
||||
// If using LLM api there is no need to check length of characters
|
||||
if (extension_settings.expressions.api === EXPRESSION_API.llm) {
|
||||
return result.trim();
|
||||
}
|
||||
|
||||
const SAMPLE_THRESHOLD = 500;
|
||||
const HALF_SAMPLE_THRESHOLD = SAMPLE_THRESHOLD / 2;
|
||||
|
||||
@ -1047,11 +1123,39 @@ function parseLlmResponse(emotionResponse, labels) {
|
||||
console.debug(`fuzzy search found: ${result[0].item} as closest for the LLM response:`, emotionResponse);
|
||||
return result[0].item;
|
||||
}
|
||||
const lowerCaseResponse = String(emotionResponse || '').toLowerCase();
|
||||
for (const label of labels) {
|
||||
if (lowerCaseResponse.includes(label.toLowerCase())) {
|
||||
console.debug(`Found label ${label} in the LLM response:`, emotionResponse);
|
||||
return label;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Could not parse emotion response ' + emotionResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the JSON schema for the LLM API.
|
||||
* @param {string[]} emotions A list of emotions to search for.
|
||||
* @returns {object} The JSON schema for the LLM API.
|
||||
*/
|
||||
function getJsonSchema(emotions) {
|
||||
return {
|
||||
$schema: 'http://json-schema.org/draft-04/schema#',
|
||||
type: 'object',
|
||||
properties: {
|
||||
emotion: {
|
||||
type: 'string',
|
||||
enum: emotions,
|
||||
},
|
||||
},
|
||||
required: [
|
||||
'emotion',
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function onTextGenSettingsReady(args) {
|
||||
// Only call if inside an API call
|
||||
if (inApiCall && extension_settings.expressions.api === EXPRESSION_API.llm && isJsonSchemaSupported()) {
|
||||
@ -1061,19 +1165,7 @@ function onTextGenSettingsReady(args) {
|
||||
stop: [],
|
||||
stopping_strings: [],
|
||||
custom_token_bans: [],
|
||||
json_schema: {
|
||||
$schema: 'http://json-schema.org/draft-04/schema#',
|
||||
type: 'object',
|
||||
properties: {
|
||||
emotion: {
|
||||
type: 'string',
|
||||
enum: emotions,
|
||||
},
|
||||
},
|
||||
required: [
|
||||
'emotion',
|
||||
],
|
||||
},
|
||||
json_schema: getJsonSchema(emotions),
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1129,6 +1221,22 @@ export async function getExpressionLabel(text, expressionsApi = extension_settin
|
||||
const emotionResponse = await generateRaw(text, main_api, false, false, prompt);
|
||||
return parseLlmResponse(emotionResponse, expressionsList);
|
||||
}
|
||||
// Using WebLLM
|
||||
case EXPRESSION_API.webllm: {
|
||||
if (!isWebLlmSupported()) {
|
||||
console.warn('WebLLM is not supported. Using fallback expression');
|
||||
return getFallbackExpression();
|
||||
}
|
||||
|
||||
const expressionsList = await getExpressionsList();
|
||||
const prompt = substituteParamsExtended(customPrompt, { labels: expressionsList }) || await getLlmPrompt(expressionsList);
|
||||
const messages = [
|
||||
{ role: 'user', content: text + '\n\n' + prompt },
|
||||
];
|
||||
|
||||
const emotionResponse = await generateWebLlmChatPrompt(messages);
|
||||
return parseLlmResponse(emotionResponse, expressionsList);
|
||||
}
|
||||
// Extras
|
||||
default: {
|
||||
const url = new URL(getApiUrl());
|
||||
@ -1239,8 +1347,6 @@ async function drawSpritesList(character, labels, sprites) {
|
||||
* @returns {Promise<string>} Rendered list item template
|
||||
*/
|
||||
async function getListItem(item, imageSrc, textClass, isCustom) {
|
||||
const isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
|
||||
imageSrc = isFirefox ? `${imageSrc}?t=${Date.now()}` : imageSrc;
|
||||
return renderExtensionTemplateAsync(MODULE_NAME, 'list-item', { item, imageSrc, textClass, isCustom });
|
||||
}
|
||||
|
||||
@ -1593,7 +1699,7 @@ function onExpressionApiChanged() {
|
||||
const tempApi = this.value;
|
||||
if (tempApi) {
|
||||
extension_settings.expressions.api = Number(tempApi);
|
||||
$('.expression_llm_prompt_block').toggle(extension_settings.expressions.api === EXPRESSION_API.llm);
|
||||
$('.expression_llm_prompt_block').toggle([EXPRESSION_API.llm, EXPRESSION_API.webllm].includes(extension_settings.expressions.api));
|
||||
expressionsList = null;
|
||||
spriteCache = {};
|
||||
moduleWorker();
|
||||
@ -1930,7 +2036,7 @@ function migrateSettings() {
|
||||
|
||||
await renderAdditionalExpressionSettings();
|
||||
$('#expression_api').val(extension_settings.expressions.api ?? EXPRESSION_API.extras);
|
||||
$('.expression_llm_prompt_block').toggle(extension_settings.expressions.api === EXPRESSION_API.llm);
|
||||
$('.expression_llm_prompt_block').toggle([EXPRESSION_API.llm, EXPRESSION_API.webllm].includes(extension_settings.expressions.api));
|
||||
$('#expression_llm_prompt').val(extension_settings.expressions.llmPrompt ?? '');
|
||||
$('#expression_llm_prompt').on('input', function () {
|
||||
extension_settings.expressions.llmPrompt = $(this).val();
|
||||
@ -2173,4 +2279,43 @@ function migrateSettings() {
|
||||
</div>
|
||||
`,
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'uploadsprite',
|
||||
callback: async (args, url) => {
|
||||
await uploadSpriteCommand(args, url);
|
||||
return '';
|
||||
},
|
||||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'URL of the image to upload',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
}),
|
||||
],
|
||||
namedArgumentList: [
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'name',
|
||||
description: 'Character name or avatar key (default is current character)',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: false,
|
||||
acceptsMultiple: false,
|
||||
}),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'label',
|
||||
description: 'Sprite label/expression name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
enumProvider: localEnumProviders.expressions,
|
||||
isRequired: true,
|
||||
acceptsMultiple: false,
|
||||
}),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'folder',
|
||||
description: 'Override folder to upload into',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: false,
|
||||
acceptsMultiple: false,
|
||||
}),
|
||||
],
|
||||
helpString: '<div>Upload a sprite from a URL.</div><div>Example:</div><pre><code>/uploadsprite name=Seraphina label=joy /user/images/Seraphina/Seraphina_2024-12-22@12h37m57s.png</code></pre>',
|
||||
}));
|
||||
})();
|
||||
|
@ -24,7 +24,8 @@
|
||||
<select id="expression_api" class="flex1 margin0">
|
||||
<option value="0" data-i18n="Local">Local</option>
|
||||
<option value="1" data-i18n="Extras">Extras</option>
|
||||
<option value="2" data-i18n="LLM">LLM</option>
|
||||
<option value="2" data-i18n="Main API">Main API</option>
|
||||
<option value="3" data-i18n="WebLLM Extension">WebLLM Extension</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="expression_llm_prompt_block m-b-1 m-t-1">
|
||||
|
@ -1,8 +1,8 @@
|
||||
<div id="sd_gen" class="list-group-item flex-container flexGap5">
|
||||
<div class="fa-solid fa-paintbrush extensionsMenuExtensionButton" title="Trigger Stable Diffusion" data-i18n="[title]Trigger Stable Diffusion"></div>
|
||||
<span>Generate Image</span>
|
||||
<span data-i18n="Generate Image">Generate Image</span>
|
||||
</div>
|
||||
<div id="sd_stop_gen" class="list-group-item flex-container flexGap5">
|
||||
<div class="fa-solid fa-circle-stop extensionsMenuExtensionButton" title="Abort current image generation task" data-i18n="[title]Abort current image generation task"></div>
|
||||
<span>Stop Image Generation</span>
|
||||
<span data-i18n="Stop Image Generation">Stop Image Generation</span>
|
||||
</div>
|
||||
|
@ -218,7 +218,7 @@ const defaultSettings = {
|
||||
// CFG Scale
|
||||
scale_min: 1,
|
||||
scale_max: 30,
|
||||
scale_step: 0.5,
|
||||
scale_step: 0.1,
|
||||
scale: 7,
|
||||
|
||||
// Sampler steps
|
||||
@ -319,6 +319,7 @@ const defaultSettings = {
|
||||
wand_visible: false,
|
||||
command_visible: false,
|
||||
interactive_visible: false,
|
||||
tool_visible: false,
|
||||
|
||||
// Stability AI settings
|
||||
stability_style_preset: 'anime',
|
||||
@ -488,6 +489,7 @@ async function loadSettings() {
|
||||
$('#sd_wand_visible').prop('checked', extension_settings.sd.wand_visible);
|
||||
$('#sd_command_visible').prop('checked', extension_settings.sd.command_visible);
|
||||
$('#sd_interactive_visible').prop('checked', extension_settings.sd.interactive_visible);
|
||||
$('#sd_tool_visible').prop('checked', extension_settings.sd.tool_visible);
|
||||
$('#sd_stability_style_preset').val(extension_settings.sd.stability_style_preset);
|
||||
$('#sd_huggingface_model_id').val(extension_settings.sd.huggingface_model_id);
|
||||
$('#sd_function_tool').prop('checked', extension_settings.sd.function_tool);
|
||||
@ -844,6 +846,11 @@ function onInteractiveVisibleInput() {
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onToolVisibleInput() {
|
||||
extension_settings.sd.tool_visible = !!$('#sd_tool_visible').prop('checked');
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onClipSkipInput() {
|
||||
extension_settings.sd.clip_skip = Number($('#sd_clip_skip').val());
|
||||
$('#sd_clip_skip_value').val(extension_settings.sd.clip_skip);
|
||||
@ -1104,7 +1111,8 @@ function onHrSecondPassStepsInput() {
|
||||
}
|
||||
|
||||
function onComfyUrlInput() {
|
||||
extension_settings.sd.comfy_url = $('#sd_comfy_url').val();
|
||||
// Remove trailing slashes
|
||||
extension_settings.sd.comfy_url = String($('#sd_comfy_url').val());
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
@ -1605,17 +1613,12 @@ async function loadVladSamplers() {
|
||||
}
|
||||
|
||||
async function loadNovelSamplers() {
|
||||
if (!secret_state[SECRET_KEYS.NOVEL]) {
|
||||
console.debug('NovelAI API key is not set.');
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
'k_euler_ancestral',
|
||||
'k_euler',
|
||||
'k_dpmpp_2m',
|
||||
'k_dpmpp_sde',
|
||||
'k_dpmpp_2s_ancestral',
|
||||
'k_euler',
|
||||
'k_euler_ancestral',
|
||||
'k_dpm_fast',
|
||||
'ddim',
|
||||
];
|
||||
@ -1971,12 +1974,11 @@ async function loadVladModels() {
|
||||
}
|
||||
|
||||
async function loadNovelModels() {
|
||||
if (!secret_state[SECRET_KEYS.NOVEL]) {
|
||||
console.debug('NovelAI API key is not set.');
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
value: 'nai-diffusion-4-curated-preview',
|
||||
text: 'NAI Diffusion Anime V4 (Curated Preview)',
|
||||
},
|
||||
{
|
||||
value: 'nai-diffusion-3',
|
||||
text: 'NAI Diffusion Anime V3',
|
||||
@ -1985,22 +1987,10 @@ async function loadNovelModels() {
|
||||
value: 'nai-diffusion-2',
|
||||
text: 'NAI Diffusion Anime V2',
|
||||
},
|
||||
{
|
||||
value: 'nai-diffusion',
|
||||
text: 'NAI Diffusion Anime V1 (Full)',
|
||||
},
|
||||
{
|
||||
value: 'safe-diffusion',
|
||||
text: 'NAI Diffusion Anime V1 (Curated)',
|
||||
},
|
||||
{
|
||||
value: 'nai-diffusion-furry-3',
|
||||
text: 'NAI Diffusion Furry V3',
|
||||
},
|
||||
{
|
||||
value: 'nai-diffusion-furry',
|
||||
text: 'NAI Diffusion Furry',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@ -2041,7 +2031,7 @@ async function loadSchedulers() {
|
||||
schedulers = await getAutoRemoteSchedulers();
|
||||
break;
|
||||
case sources.novel:
|
||||
schedulers = ['N/A'];
|
||||
schedulers = ['karras', 'native', 'exponential', 'polyexponential'];
|
||||
break;
|
||||
case sources.vlad:
|
||||
schedulers = ['N/A'];
|
||||
@ -3041,12 +3031,14 @@ async function generateAutoImage(prompt, negativePrompt, signal) {
|
||||
enable_hr: !!extension_settings.sd.enable_hr,
|
||||
hr_upscaler: extension_settings.sd.hr_upscaler,
|
||||
hr_scale: extension_settings.sd.hr_scale,
|
||||
hr_additional_modules: [],
|
||||
denoising_strength: extension_settings.sd.denoising_strength,
|
||||
hr_second_pass_steps: extension_settings.sd.hr_second_pass_steps,
|
||||
seed: extension_settings.sd.seed >= 0 ? extension_settings.sd.seed : undefined,
|
||||
override_settings: {
|
||||
CLIP_stop_at_last_layers: extension_settings.sd.clip_skip,
|
||||
sd_vae: isValidVae ? extension_settings.sd.vae : undefined,
|
||||
forge_additional_modules: isValidVae ? [extension_settings.sd.vae] : undefined, // For SD Forge
|
||||
},
|
||||
override_settings_restore_afterwards: true,
|
||||
clip_skip: extension_settings.sd.clip_skip, // For SD.Next
|
||||
@ -3150,6 +3142,7 @@ async function generateNovelImage(prompt, negativePrompt, signal) {
|
||||
prompt: prompt,
|
||||
model: extension_settings.sd.model,
|
||||
sampler: extension_settings.sd.sampler,
|
||||
scheduler: extension_settings.sd.scheduler,
|
||||
steps: steps,
|
||||
scale: extension_settings.sd.scale,
|
||||
width: width,
|
||||
@ -3177,13 +3170,18 @@ async function generateNovelImage(prompt, negativePrompt, signal) {
|
||||
* @returns {{steps: number, width: number, height: number, sm: boolean, sm_dyn: boolean}} - A tuple of parameters for NovelAI API.
|
||||
*/
|
||||
function getNovelParams() {
|
||||
let steps = extension_settings.sd.steps;
|
||||
let steps = Math.min(extension_settings.sd.steps, 50);
|
||||
let width = extension_settings.sd.width;
|
||||
let height = extension_settings.sd.height;
|
||||
let sm = extension_settings.sd.novel_sm;
|
||||
let sm_dyn = extension_settings.sd.novel_sm_dyn;
|
||||
|
||||
if (extension_settings.sd.sampler === 'ddim') {
|
||||
// If a source was never changed after the scheduler setting was added, we need to set it to 'karras' for compatibility.
|
||||
if (!extension_settings.sd.scheduler || extension_settings.sd.scheduler === 'normal') {
|
||||
extension_settings.sd.scheduler = 'karras';
|
||||
}
|
||||
|
||||
if (extension_settings.sd.sampler === 'ddim' || extension_settings.sd.model === 'nai-diffusion-4-curated-preview') {
|
||||
sm = false;
|
||||
sm_dyn = false;
|
||||
}
|
||||
@ -3314,7 +3312,6 @@ async function generateComfyImage(prompt, negativePrompt, signal) {
|
||||
'scale',
|
||||
'width',
|
||||
'height',
|
||||
'clip_skip',
|
||||
];
|
||||
|
||||
const workflowResponse = await fetch('/api/sd/comfy/workflow', {
|
||||
@ -3337,6 +3334,9 @@ async function generateComfyImage(prompt, negativePrompt, signal) {
|
||||
const denoising_strength = extension_settings.sd.denoising_strength === undefined ? 1.0 : extension_settings.sd.denoising_strength;
|
||||
workflow = workflow.replaceAll('"%denoise%"', JSON.stringify(denoising_strength));
|
||||
|
||||
const clip_skip = isNaN(extension_settings.sd.clip_skip) ? -1 : -extension_settings.sd.clip_skip;
|
||||
workflow = workflow.replaceAll('"%clip_skip%"', JSON.stringify(clip_skip));
|
||||
|
||||
placeholders.forEach(ph => {
|
||||
workflow = workflow.replaceAll(`"%${ph}%"`, JSON.stringify(extension_settings.sd[ph]));
|
||||
});
|
||||
@ -3670,6 +3670,8 @@ function getVisibilityByInitiator(initiator) {
|
||||
return !!extension_settings.sd.wand_visible;
|
||||
case initiators.command:
|
||||
return !!extension_settings.sd.command_visible;
|
||||
case initiators.tool:
|
||||
return !!extension_settings.sd.tool_visible;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -4417,6 +4419,7 @@ jQuery(async () => {
|
||||
$('#sd_wand_visible').on('input', onWandVisibleInput);
|
||||
$('#sd_command_visible').on('input', onCommandVisibleInput);
|
||||
$('#sd_interactive_visible').on('input', onInteractiveVisibleInput);
|
||||
$('#sd_tool_visible').on('input', onToolVisibleInput);
|
||||
$('#sd_swap_dimensions').on('click', onSwapDimensionsClick);
|
||||
$('#sd_stability_key').on('click', onStabilityKeyClick);
|
||||
$('#sd_stability_style_preset').on('change', onStabilityStylePresetChange);
|
||||
|
@ -109,7 +109,7 @@
|
||||
<i data-i18n="The server must be accessible from the SillyTavern host machine.">The server must be accessible from the SillyTavern host machine.</i>
|
||||
</div>
|
||||
<div data-sd-source="horde">
|
||||
<i data-i18n="Hint: Save an API key in Horde KoboldAI API settings to use it here.">Hint: Save an API key in Horde KoboldAI API settings to use it here.</i>
|
||||
<i data-i18n="Hint: Save an API key in AI Horde API settings to use it here.">Hint: Save an API key in AI Horde API settings to use it here.</i>
|
||||
<label for="sd_horde_nsfw" class="checkbox_label">
|
||||
<input id="sd_horde_nsfw" type="checkbox" />
|
||||
<span data-i18n="Allow NSFW images from Horde">
|
||||
@ -274,7 +274,7 @@
|
||||
<select id="sd_sampler"></select>
|
||||
</div>
|
||||
|
||||
<div class="flex1" data-sd-source="comfy,auto">
|
||||
<div class="flex1" data-sd-source="comfy,auto,novel">
|
||||
<label for="sd_scheduler" data-i18n="Scheduler">Scheduler</label>
|
||||
<select id="sd_scheduler"></select>
|
||||
</div>
|
||||
@ -459,25 +459,32 @@
|
||||
<div class="flex-container flexFlowColumn marginTopBot5 flexGap10">
|
||||
<label for="sd_wand_visible" class="checkbox_label">
|
||||
<span class="flex1 flex-container alignItemsCenter">
|
||||
<i class="fa-solid fa-wand-magic-sparkles"></i>
|
||||
<i class="fa-solid fa-wand-magic-sparkles fa-fw"></i>
|
||||
<span data-i18n="Extensions Menu">Extensions Menu</span>
|
||||
</span>
|
||||
<input id="sd_wand_visible" type="checkbox" />
|
||||
</label>
|
||||
<label for="sd_command_visible" class="checkbox_label">
|
||||
<span class="flex1 flex-container alignItemsCenter">
|
||||
<i class="fa-solid fa-terminal"></i>
|
||||
<i class="fa-solid fa-terminal fa-fw"></i>
|
||||
<span data-i18n="Slash Command">Slash Command</span>
|
||||
</span>
|
||||
<input id="sd_command_visible" type="checkbox" />
|
||||
</label>
|
||||
<label for="sd_interactive_visible" class="checkbox_label">
|
||||
<span class="flex1 flex-container alignItemsCenter">
|
||||
<i class="fa-solid fa-message"></i>
|
||||
<i class="fa-solid fa-message fa-fw"></i>
|
||||
<span data-i18n="Interactive Mode">Interactive Mode</span>
|
||||
</span>
|
||||
<input id="sd_interactive_visible" type="checkbox" />
|
||||
</label>
|
||||
<label for="sd_tool_visible" class="checkbox_label">
|
||||
<span class="flex1 flex-container alignItemsCenter">
|
||||
<i class="fa-solid fa-wrench fa-fw"></i>
|
||||
<span data-i18n="Function Tool">Function Tool</span>
|
||||
</span>
|
||||
<input id="sd_tool_visible" type="checkbox" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
0
public/scripts/extensions/third-party/.gitkeep
vendored
Normal file
0
public/scripts/extensions/third-party/.gitkeep
vendored
Normal file
@ -14,16 +14,20 @@
|
||||
</select>
|
||||
<label data-i18n="ext_translate_mode_provider" for="translation_provider">Provider</label>
|
||||
<div class="flex-container gap5px flexnowrap marginBot5">
|
||||
<select id="translation_provider" name="provider" class="margin0">
|
||||
<option value="libre">Libre</option>
|
||||
<select id="translation_provider" name="provider" class="margin0 text_pole flex2">
|
||||
<option value="libre">LibreTranslate</option>
|
||||
<option value="google">Google</option>
|
||||
<option value="lingva">Lingva</option>
|
||||
<option value="deepl">DeepL</option>
|
||||
<option value="deepl">DeepL API</option>
|
||||
<option value="deeplx">DeepLX</option>
|
||||
<option value="bing">Bing</option>
|
||||
<option value="oneringtranslator">OneRingTranslator</option>
|
||||
<option value="yandex">Yandex</option>
|
||||
<select>
|
||||
<select id="deepl_api_endpoint" class="margin0 text_pole flex1" title="DeepL API Endpoint">
|
||||
<option value="free">Free</option>
|
||||
<option value="pro">Pro</option>
|
||||
</select>
|
||||
<div id="translate_key_button" class="menu_button fa-solid fa-key margin0"></div>
|
||||
<div id="translate_url_button" class="menu_button fa-solid fa-link margin0"></div>
|
||||
</div>
|
||||
@ -35,4 +39,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -14,6 +14,8 @@ import { POPUP_RESULT, POPUP_TYPE, callGenericPopup } from '../../popup.js';
|
||||
import { findSecret, secret_state, writeSecret } from '../../secrets.js';
|
||||
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
|
||||
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
|
||||
import { enumIcons } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||
import { enumTypes, SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js';
|
||||
import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
|
||||
import { splitRecursive } from '../../utils.js';
|
||||
|
||||
@ -32,6 +34,7 @@ const defaultSettings = {
|
||||
internal_language: 'en',
|
||||
provider: 'google',
|
||||
auto_mode: autoModeOptions.NONE,
|
||||
deepl_endpoint: 'free',
|
||||
};
|
||||
|
||||
const languageCodes = {
|
||||
@ -106,7 +109,8 @@ const languageCodes = {
|
||||
'Pashto': 'ps',
|
||||
'Persian': 'fa',
|
||||
'Polish': 'pl',
|
||||
'Portuguese (Portugal, Brazil)': 'pt',
|
||||
'Portuguese (Portugal)': 'pt-PT',
|
||||
'Portuguese (Brazil)': 'pt-BR',
|
||||
'Punjabi': 'pa',
|
||||
'Romanian': 'ro',
|
||||
'Russian': 'ru',
|
||||
@ -151,6 +155,7 @@ function showKeysButton() {
|
||||
$('#translate_key_button').toggleClass('success', Boolean(secret_state[extension_settings.translate.provider]));
|
||||
$('#translate_url_button').toggle(providerOptionalUrl);
|
||||
$('#translate_url_button').toggleClass('success', Boolean(secret_state[extension_settings.translate.provider + '_url']));
|
||||
$('#deepl_api_endpoint').toggle(extension_settings.translate.provider === 'deepl');
|
||||
}
|
||||
|
||||
function loadSettings() {
|
||||
@ -160,9 +165,10 @@ function loadSettings() {
|
||||
}
|
||||
}
|
||||
|
||||
$(`#translation_provider option[value="${extension_settings.translate.provider}"]`).attr('selected', true);
|
||||
$(`#translation_target_language option[value="${extension_settings.translate.target_language}"]`).attr('selected', true);
|
||||
$(`#translation_auto_mode option[value="${extension_settings.translate.auto_mode}"]`).attr('selected', true);
|
||||
$(`#translation_provider option[value="${extension_settings.translate.provider}"]`).attr('selected', 'true');
|
||||
$(`#translation_target_language option[value="${extension_settings.translate.target_language}"]`).attr('selected', 'true');
|
||||
$(`#translation_auto_mode option[value="${extension_settings.translate.auto_mode}"]`).attr('selected', 'true');
|
||||
$('#deepl_api_endpoint').val(extension_settings.translate.deepl_endpoint).toggle(extension_settings.translate.provider === 'deepl');
|
||||
showKeysButton();
|
||||
}
|
||||
|
||||
@ -284,10 +290,11 @@ async function translateProviderDeepl(text, lang) {
|
||||
throw new Error('No DeepL API key');
|
||||
}
|
||||
|
||||
const endpoint = extension_settings.translate.deepl_endpoint || 'free';
|
||||
const response = await fetch('/api/translate/deepl', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({ text: text, lang: lang }),
|
||||
body: JSON.stringify({ text: text, lang: lang, endpoint: endpoint }),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
@ -394,9 +401,10 @@ async function chunkedTranslate(text, lang, translateFn, chunkSize = 5000) {
|
||||
* Translates text using the selected translation provider
|
||||
* @param {string} text Text to translate
|
||||
* @param {string} lang Target language code
|
||||
* @param {string} provider Translation provider to use
|
||||
* @returns {Promise<string>} Translated text
|
||||
*/
|
||||
async function translate(text, lang) {
|
||||
async function translate(text, lang, provider = null) {
|
||||
try {
|
||||
if (text == '') {
|
||||
return '';
|
||||
@ -406,13 +414,17 @@ async function translate(text, lang) {
|
||||
lang = extension_settings.translate.target_language;
|
||||
}
|
||||
|
||||
if (!provider) {
|
||||
provider = extension_settings.translate.provider;
|
||||
}
|
||||
|
||||
// split text by embedded images links
|
||||
const chunks = text.split(/!\[.*?]\([^)]*\)/);
|
||||
const links = [...text.matchAll(/!\[.*?]\([^)]*\)/g)];
|
||||
|
||||
let result = '';
|
||||
for (let i = 0; i < chunks.length; i++) {
|
||||
result += await translateInner(chunks[i], lang);
|
||||
result += await translateInner(chunks[i], lang, provider);
|
||||
if (i < links.length) result += links[i][0];
|
||||
}
|
||||
|
||||
@ -423,11 +435,21 @@ async function translate(text, lang) {
|
||||
}
|
||||
}
|
||||
|
||||
async function translateInner(text, lang) {
|
||||
/**
|
||||
* Common translation function that handles the translation logic
|
||||
* @param {string} text Text to translate
|
||||
* @param {string} lang Target language code
|
||||
* @param {string} provider Translation provider to use
|
||||
* @returns {Promise<string>} Translated text
|
||||
*/
|
||||
async function translateInner(text, lang, provider) {
|
||||
if (text == '') {
|
||||
return '';
|
||||
}
|
||||
switch (extension_settings.translate.provider) {
|
||||
if (!provider) {
|
||||
provider = extension_settings.translate.provider;
|
||||
}
|
||||
switch (provider) {
|
||||
case 'libre':
|
||||
return await translateProviderLibre(text, lang);
|
||||
case 'google':
|
||||
@ -445,7 +467,7 @@ async function translateInner(text, lang) {
|
||||
case 'yandex':
|
||||
return await translateProviderYandex(text, lang);
|
||||
default:
|
||||
console.error('Unknown translation provider', extension_settings.translate.provider);
|
||||
console.error('Unknown translation provider', provider);
|
||||
return text;
|
||||
}
|
||||
}
|
||||
@ -600,18 +622,34 @@ jQuery(async () => {
|
||||
}
|
||||
|
||||
$('#translation_auto_mode').on('change', (event) => {
|
||||
if (!(event.target instanceof HTMLSelectElement)) {
|
||||
return;
|
||||
}
|
||||
extension_settings.translate.auto_mode = event.target.value;
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
$('#translation_provider').on('change', (event) => {
|
||||
if (!(event.target instanceof HTMLSelectElement)) {
|
||||
return;
|
||||
}
|
||||
extension_settings.translate.provider = event.target.value;
|
||||
showKeysButton();
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
$('#translation_target_language').on('change', (event) => {
|
||||
if (!(event.target instanceof HTMLSelectElement)) {
|
||||
return;
|
||||
}
|
||||
extension_settings.translate.target_language = event.target.value;
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
$('#deepl_api_endpoint').on('change', (event) => {
|
||||
if (!(event.target instanceof HTMLSelectElement)) {
|
||||
return;
|
||||
}
|
||||
extension_settings.translate.deepl_endpoint = event.target.value;
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
$(document).on('click', '.mes_translate', onMessageTranslateClick);
|
||||
$('#translate_key_button').on('click', async () => {
|
||||
const optionText = $('#translation_provider option:selected').text();
|
||||
@ -687,6 +725,14 @@ jQuery(async () => {
|
||||
helpString: 'Translate text to a target language. If target language is not provided, the value from the extension settings will be used.',
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument('target', 'The target language code to translate to', ARGUMENT_TYPE.STRING, false, false, '', Object.values(languageCodes)),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'provider',
|
||||
description: 'The translation provider to use. If not provided, the value from the extension settings will be used.',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: false,
|
||||
acceptsMultiple: false,
|
||||
enumProvider: () => Array.from(document.getElementById('translation_provider').querySelectorAll('option')).map((option) => new SlashCommandEnumValue(option.value, option.text, enumTypes.name, enumIcons.server)),
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument('The text to translate', ARGUMENT_TYPE.STRING, true, false, ''),
|
||||
@ -695,7 +741,8 @@ jQuery(async () => {
|
||||
const target = args?.target && Object.values(languageCodes).includes(String(args.target))
|
||||
? String(args.target)
|
||||
: extension_settings.translate.target_language;
|
||||
return await translate(String(value), target);
|
||||
const provider = args?.provider || extension_settings.translate.provider;
|
||||
return await translate(String(value), target, provider);
|
||||
},
|
||||
returns: ARGUMENT_TYPE.STRING,
|
||||
}));
|
||||
|
@ -448,6 +448,5 @@ export class FilterHelper {
|
||||
for (const cache of Object.values(this.fuzzySearchCaches)) {
|
||||
cache.resultMap.clear();
|
||||
}
|
||||
console.log('All fuzzy search caches cleared');
|
||||
}
|
||||
}
|
||||
|
@ -275,6 +275,20 @@ export function getGroupMembers(groupId = selected_group) {
|
||||
return group?.members.map(member => characters.find(x => x.avatar === member)) ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the member names of a group. If the group is not selected, an empty array is returned.
|
||||
* @returns {string[]} An array of character names representing the members of the group.
|
||||
*/
|
||||
export function getGroupNames() {
|
||||
if (!selected_group) {
|
||||
return [];
|
||||
}
|
||||
const groupMembers = groups.find(x => x.id == selected_group)?.members;
|
||||
return Array.isArray(groupMembers)
|
||||
? groupMembers.map(x => characters.find(y => y.avatar === x)?.name).filter(x => x)
|
||||
: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the character ID for a group member.
|
||||
* @param {string} arg 0-based member index or character name
|
||||
@ -423,14 +437,20 @@ export function getGroupCharacterCards(groupId, characterId) {
|
||||
* @param {string} value Value to replace
|
||||
* @param {string} characterName Name of the character
|
||||
* @param {string} fieldName Name of the field
|
||||
* @param {function(string): string} [preprocess] Preprocess function
|
||||
* @returns {string} Prepared text
|
||||
* */
|
||||
function replaceAndPrepareForJoin(value, characterName, fieldName) {
|
||||
function replaceAndPrepareForJoin(value, characterName, fieldName, preprocess = null) {
|
||||
value = value.trim();
|
||||
if (!value) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Run preprocess function
|
||||
if (typeof preprocess === 'function') {
|
||||
value = preprocess(value);
|
||||
}
|
||||
|
||||
// Prepare and replace prefixes
|
||||
const prefix = customBaseChatReplace(group.generation_mode_join_prefix, fieldName, characterName);
|
||||
const suffix = customBaseChatReplace(group.generation_mode_join_suffix, fieldName, characterName);
|
||||
@ -465,7 +485,7 @@ export function getGroupCharacterCards(groupId, characterId) {
|
||||
descriptions.push(replaceAndPrepareForJoin(character.description, character.name, 'Description'));
|
||||
personalities.push(replaceAndPrepareForJoin(character.personality, character.name, 'Personality'));
|
||||
scenarios.push(replaceAndPrepareForJoin(character.scenario, character.name, 'Scenario'));
|
||||
mesExamplesArray.push(replaceAndPrepareForJoin(character.mes_example, character.name, 'Example Messages'));
|
||||
mesExamplesArray.push(replaceAndPrepareForJoin(character.mes_example, character.name, 'Example Messages', (x) => !x.startsWith('<START>') ? `<START>\n${x}` : x));
|
||||
}
|
||||
|
||||
const description = descriptions.filter(x => x.length).join('\n');
|
||||
|
@ -431,7 +431,8 @@ export function formatInstructModeExamples(mesExamplesArray, name1, name2) {
|
||||
return mesExamplesArray.map(x => x.replace(/<START>\n/i, blockHeading));
|
||||
}
|
||||
|
||||
const includeNames = power_user.instruct.names_behavior === names_behavior_types.ALWAYS || (!!selected_group && power_user.instruct.names_behavior === names_behavior_types.FORCE);
|
||||
const includeNames = power_user.instruct.names_behavior === names_behavior_types.ALWAYS;
|
||||
const includeGroupNames = selected_group && [names_behavior_types.ALWAYS, names_behavior_types.FORCE].includes(power_user.instruct.names_behavior);
|
||||
|
||||
let inputPrefix = power_user.instruct.input_sequence || '';
|
||||
let outputPrefix = power_user.instruct.output_sequence || '';
|
||||
@ -463,7 +464,7 @@ export function formatInstructModeExamples(mesExamplesArray, name1, name2) {
|
||||
|
||||
for (const item of mesExamplesArray) {
|
||||
const cleanedItem = item.replace(/<START>/i, '{Example Dialogue:}').replace(/\r/gm, '');
|
||||
const blockExamples = parseExampleIntoIndividual(cleanedItem);
|
||||
const blockExamples = parseExampleIntoIndividual(cleanedItem, includeGroupNames);
|
||||
|
||||
if (blockExamples.length === 0) {
|
||||
continue;
|
||||
@ -474,8 +475,9 @@ export function formatInstructModeExamples(mesExamplesArray, name1, name2) {
|
||||
}
|
||||
|
||||
for (const example of blockExamples) {
|
||||
// If force group/persona names is set, we should override the include names for the user placeholder
|
||||
const includeThisName = includeNames || (power_user.instruct.names_behavior === names_behavior_types.FORCE && example.name == 'example_user');
|
||||
// If group names were included, we don't want to add any additional prefix as it already was applied.
|
||||
// Otherwise, if force group/persona names is set, we should override the include names for the user placeholder
|
||||
const includeThisName = !includeGroupNames && (includeNames || (power_user.instruct.names_behavior === names_behavior_types.FORCE && example.name == 'example_user'));
|
||||
|
||||
const prefix = example.name == 'example_user' ? inputPrefix : outputPrefix;
|
||||
const suffix = example.name == 'example_user' ? inputSuffix : outputSuffix;
|
||||
@ -489,7 +491,6 @@ export function formatInstructModeExamples(mesExamplesArray, name1, name2) {
|
||||
if (formattedExamples.length === 0) {
|
||||
return mesExamplesArray.map(x => x.replace(/<START>\n/i, blockHeading));
|
||||
}
|
||||
|
||||
return formattedExamples;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { saveSettingsDebounced } from '../script.js';
|
||||
import { getTextTokens } from './tokenizers.js';
|
||||
import { uuidv4 } from './utils.js';
|
||||
import { getSortableDelay, uuidv4 } from './utils.js';
|
||||
|
||||
export const BIAS_CACHE = new Map();
|
||||
|
||||
@ -16,7 +16,8 @@ export function displayLogitBias(logitBias, containerSelector) {
|
||||
return;
|
||||
}
|
||||
|
||||
$(containerSelector).find('.logit_bias_list').empty();
|
||||
const list = $(containerSelector).find('.logit_bias_list');
|
||||
list.empty();
|
||||
|
||||
for (const entry of logitBias) {
|
||||
if (entry) {
|
||||
@ -24,6 +25,27 @@ export function displayLogitBias(logitBias, containerSelector) {
|
||||
}
|
||||
}
|
||||
|
||||
// Check if a sortable instance exists
|
||||
if (list.sortable('instance') !== undefined) {
|
||||
// Destroy the instance
|
||||
list.sortable('destroy');
|
||||
}
|
||||
|
||||
// Make the list sortable
|
||||
list.sortable({
|
||||
delay: getSortableDelay(),
|
||||
handle: '.drag-handle',
|
||||
stop: function () {
|
||||
const order = [];
|
||||
list.children().each(function () {
|
||||
order.unshift($(this).data('id'));
|
||||
});
|
||||
logitBias.sort((a, b) => order.indexOf(a.id) - order.indexOf(b.id));
|
||||
console.log('Logit bias reordered:', logitBias);
|
||||
saveSettingsDebounced();
|
||||
},
|
||||
});
|
||||
|
||||
BIAS_CACHE.delete(containerSelector);
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ import { timestampToMoment, isDigitsOnly, getStringHash, escapeRegex, uuidv4 } f
|
||||
import { textgenerationwebui_banned_in_macros } from './textgen-settings.js';
|
||||
import { getInstructMacros } from './instruct-mode.js';
|
||||
import { getVariableMacros } from './variables.js';
|
||||
import { isMobile } from './RossAscends-mods.js';
|
||||
|
||||
/**
|
||||
* @typedef Macro
|
||||
@ -516,7 +517,11 @@ export function evaluateMacros(content, env, postProcessFn) {
|
||||
break;
|
||||
}
|
||||
|
||||
content = content.replace(macro.regex, (...args) => postProcessFn(macro.replace(...args)));
|
||||
try {
|
||||
content = content.replace(macro.regex, (...args) => postProcessFn(macro.replace(...args)));
|
||||
} catch (e) {
|
||||
console.warn(`Macro content can't be replaced: ${macro.regex} in ${content}`, e);
|
||||
}
|
||||
}
|
||||
|
||||
return content;
|
||||
@ -538,5 +543,6 @@ export function initMacros() {
|
||||
});
|
||||
}
|
||||
|
||||
MacrosParser.registerMacro('isMobile', () => String(isMobile()));
|
||||
initLastGenerationType();
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ import {
|
||||
system_message_types,
|
||||
this_chid,
|
||||
} from '../script.js';
|
||||
import { selected_group } from './group-chats.js';
|
||||
import { getGroupNames, selected_group } from './group-chats.js';
|
||||
|
||||
import {
|
||||
chatCompletionDefaultPrompts,
|
||||
@ -60,6 +60,7 @@ import {
|
||||
parseJsonFile,
|
||||
resetScrollHeight,
|
||||
stringFormat,
|
||||
uuidv4,
|
||||
} from './utils.js';
|
||||
import { countTokensOpenAIAsync, getTokenizerModel } from './tokenizers.js';
|
||||
import { isMobile } from './RossAscends-mods.js';
|
||||
@ -101,16 +102,16 @@ const default_new_group_chat_prompt = '[Start a new group chat. Group members: {
|
||||
const default_new_example_chat_prompt = '[Example Chat]';
|
||||
const default_continue_nudge_prompt = '[Continue the following message. Do not include ANY parts of the original message. Use capitalization and punctuation as if your reply is a part of the original message: {{lastChatMessage}}]';
|
||||
const default_bias = 'Default (none)';
|
||||
const default_personality_format = '[{{char}}\'s personality: {{personality}}]';
|
||||
const default_scenario_format = '[Circumstances and context of the dialogue: {{scenario}}]';
|
||||
const default_personality_format = '{{personality}}';
|
||||
const default_scenario_format = '{{scenario}}';
|
||||
const default_group_nudge_prompt = '[Write the next reply only as {{char}}.]';
|
||||
const default_bias_presets = {
|
||||
[default_bias]: [],
|
||||
'Anti-bond': [
|
||||
{ text: ' bond', value: -50 },
|
||||
{ text: ' future', value: -50 },
|
||||
{ text: ' bonding', value: -50 },
|
||||
{ text: ' connection', value: -25 },
|
||||
{ id: '22154f79-dd98-41bc-8e34-87015d6a0eaf', text: ' bond', value: -50 },
|
||||
{ id: '8ad2d5c4-d8ef-49e4-bc5e-13e7f4690e0f', text: ' future', value: -50 },
|
||||
{ id: '52a4b280-0956-4940-ac52-4111f83e4046', text: ' bonding', value: -50 },
|
||||
{ id: 'e63037c7-c9d1-4724-ab2d-7756008b433b', text: ' connection', value: -25 },
|
||||
],
|
||||
};
|
||||
|
||||
@ -182,6 +183,7 @@ export const chat_completion_sources = {
|
||||
ZEROONEAI: '01ai',
|
||||
BLOCKENTROPY: 'blockentropy',
|
||||
NANOGPT: 'nanogpt',
|
||||
DEEPSEEK: 'deepseek',
|
||||
};
|
||||
|
||||
const character_names_behavior = {
|
||||
@ -203,9 +205,16 @@ const custom_prompt_post_processing_types = {
|
||||
/** @deprecated Use MERGE instead. */
|
||||
CLAUDE: 'claude',
|
||||
MERGE: 'merge',
|
||||
SEMI: 'semi',
|
||||
STRICT: 'strict',
|
||||
};
|
||||
|
||||
const openrouter_middleout_types = {
|
||||
AUTO: 'auto',
|
||||
ON: 'on',
|
||||
OFF: 'off',
|
||||
};
|
||||
|
||||
const sensitiveFields = [
|
||||
'reverse_proxy',
|
||||
'proxy_password',
|
||||
@ -254,6 +263,7 @@ const default_settings = {
|
||||
nanogpt_model: 'gpt-4o-mini',
|
||||
zerooneai_model: 'yi-large',
|
||||
blockentropy_model: 'be-70b-base-llama3.1',
|
||||
deepseek_model: 'deepseek-chat',
|
||||
custom_model: '',
|
||||
custom_url: '',
|
||||
custom_include_body: '',
|
||||
@ -266,6 +276,7 @@ const default_settings = {
|
||||
openrouter_sort_models: 'alphabetically',
|
||||
openrouter_providers: [],
|
||||
openrouter_allow_fallbacks: true,
|
||||
openrouter_middleout: openrouter_middleout_types.ON,
|
||||
jailbreak_system: false,
|
||||
reverse_proxy: '',
|
||||
chat_completion_source: chat_completion_sources.OPENAI,
|
||||
@ -287,6 +298,7 @@ const default_settings = {
|
||||
names_behavior: character_names_behavior.DEFAULT,
|
||||
continue_postfix: continue_postfix_types.SPACE,
|
||||
custom_prompt_post_processing: custom_prompt_post_processing_types.NONE,
|
||||
show_thoughts: false,
|
||||
seed: -1,
|
||||
n: 1,
|
||||
};
|
||||
@ -330,6 +342,7 @@ const oai_settings = {
|
||||
nanogpt_model: 'gpt-4o-mini',
|
||||
zerooneai_model: 'yi-large',
|
||||
blockentropy_model: 'be-70b-base-llama3.1',
|
||||
deepseek_model: 'deepseek-chat',
|
||||
custom_model: '',
|
||||
custom_url: '',
|
||||
custom_include_body: '',
|
||||
@ -342,6 +355,7 @@ const oai_settings = {
|
||||
openrouter_sort_models: 'alphabetically',
|
||||
openrouter_providers: [],
|
||||
openrouter_allow_fallbacks: true,
|
||||
openrouter_middleout: openrouter_middleout_types.ON,
|
||||
jailbreak_system: false,
|
||||
reverse_proxy: '',
|
||||
chat_completion_source: chat_completion_sources.OPENAI,
|
||||
@ -363,6 +377,7 @@ const oai_settings = {
|
||||
names_behavior: character_names_behavior.DEFAULT,
|
||||
continue_postfix: continue_postfix_types.SPACE,
|
||||
custom_prompt_post_processing: custom_prompt_post_processing_types.NONE,
|
||||
show_thoughts: false,
|
||||
seed: -1,
|
||||
n: 1,
|
||||
};
|
||||
@ -543,11 +558,15 @@ function setupChatCompletionPromptManager(openAiSettings) {
|
||||
* @returns {Message[]} Array of message objects
|
||||
*/
|
||||
export function parseExampleIntoIndividual(messageExampleString, appendNamesForGroup = true) {
|
||||
const groupBotNames = getGroupNames().map(name => `${name}:`);
|
||||
|
||||
let result = []; // array of msgs
|
||||
let tmp = messageExampleString.split('\n');
|
||||
let cur_msg_lines = [];
|
||||
let in_user = false;
|
||||
let in_bot = false;
|
||||
let botName = name2;
|
||||
|
||||
// DRY my cock and balls :)
|
||||
function add_msg(name, role, system_name) {
|
||||
// join different newlines (we split them by \n and join by \n)
|
||||
@ -571,10 +590,14 @@ export function parseExampleIntoIndividual(messageExampleString, appendNamesForG
|
||||
in_user = true;
|
||||
// we were in the bot mode previously, add the message
|
||||
if (in_bot) {
|
||||
add_msg(name2, 'system', 'example_assistant');
|
||||
add_msg(botName, 'system', 'example_assistant');
|
||||
}
|
||||
in_bot = false;
|
||||
} else if (cur_str.startsWith(name2 + ':')) {
|
||||
} else if (cur_str.startsWith(name2 + ':') || groupBotNames.some(n => cur_str.startsWith(n))) {
|
||||
if (!cur_str.startsWith(name2 + ':') && groupBotNames.length) {
|
||||
botName = cur_str.split(':')[0];
|
||||
}
|
||||
|
||||
in_bot = true;
|
||||
// we were in the user mode previously, add the message
|
||||
if (in_user) {
|
||||
@ -589,7 +612,7 @@ export function parseExampleIntoIndividual(messageExampleString, appendNamesForG
|
||||
if (in_user) {
|
||||
add_msg(name1, 'system', 'example_user');
|
||||
} else if (in_bot) {
|
||||
add_msg(name2, 'system', 'example_assistant');
|
||||
add_msg(botName, 'system', 'example_assistant');
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -611,8 +634,9 @@ function formatWorldInfo(value) {
|
||||
*
|
||||
* @param {Prompt[]} prompts - Array containing injection prompts.
|
||||
* @param {Object[]} messages - Array containing all messages.
|
||||
* @returns {Promise<Object[]>} - Array containing all messages with injections.
|
||||
*/
|
||||
function populationInjectionPrompts(prompts, messages) {
|
||||
async function populationInjectionPrompts(prompts, messages) {
|
||||
let totalInsertedMessages = 0;
|
||||
|
||||
const roleTypes = {
|
||||
@ -635,7 +659,7 @@ function populationInjectionPrompts(prompts, messages) {
|
||||
// Get prompts for current role
|
||||
const rolePrompts = depthPrompts.filter(prompt => prompt.role === role).map(x => x.content).join(separator);
|
||||
// Get extension prompt
|
||||
const extensionPrompt = getExtensionPrompt(extension_prompt_types.IN_CHAT, i, separator, roleTypes[role], wrap);
|
||||
const extensionPrompt = await getExtensionPrompt(extension_prompt_types.IN_CHAT, i, separator, roleTypes[role], wrap);
|
||||
|
||||
const jointPrompt = [rolePrompts, extensionPrompt].filter(x => x).map(x => x.trim()).join(separator);
|
||||
|
||||
@ -749,7 +773,9 @@ async function populateChatHistory(messages, prompts, chatCompletion, type = nul
|
||||
if (type === 'continue' && oai_settings.continue_prefill && chatPrompt === firstNonInjected) {
|
||||
// in case we are using continue_prefill and the latest message is an assistant message, we want to prepend the users assistant prefill on the message
|
||||
if (chatPrompt.role === 'assistant') {
|
||||
const messageContent = [substituteParams(oai_settings.assistant_prefill), chatMessage.content].filter(x => x).join('\n\n');
|
||||
const supportsAssistantPrefill = oai_settings.chat_completion_source === chat_completion_sources.CLAUDE;
|
||||
const assistantPrefill = supportsAssistantPrefill ? substituteParams(oai_settings.assistant_prefill) : '';
|
||||
const messageContent = [assistantPrefill, chatMessage.content].filter(x => x).join('\n\n');
|
||||
const continueMessage = await Message.createAsync(chatMessage.role, messageContent, chatMessage.identifier);
|
||||
const collection = new MessageCollection('continuePrefill', continueMessage);
|
||||
chatCompletion.add(collection, -1);
|
||||
@ -1020,7 +1046,7 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm
|
||||
}
|
||||
|
||||
// Add in-chat injections
|
||||
messages = populationInjectionPrompts(absolutePrompts, messages);
|
||||
messages = await populationInjectionPrompts(absolutePrompts, messages);
|
||||
|
||||
// Decide whether dialogue examples should always be added
|
||||
if (power_user.pin_examples) {
|
||||
@ -1051,9 +1077,9 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm
|
||||
* @param {string} options.systemPromptOverride
|
||||
* @param {string} options.jailbreakPromptOverride
|
||||
* @param {string} options.personaDescription
|
||||
* @returns {Object} prompts - The prepared and merged system and user-defined prompts.
|
||||
* @returns {Promise<Object>} prompts - The prepared and merged system and user-defined prompts.
|
||||
*/
|
||||
function preparePromptsForChatCompletion({ Scenario, charPersonality, name2, worldInfoBefore, worldInfoAfter, charDescription, quietPrompt, bias, extensionPrompts, systemPromptOverride, jailbreakPromptOverride, personaDescription }) {
|
||||
async function preparePromptsForChatCompletion({ Scenario, charPersonality, name2, worldInfoBefore, worldInfoAfter, charDescription, quietPrompt, bias, extensionPrompts, systemPromptOverride, jailbreakPromptOverride, personaDescription }) {
|
||||
const scenarioText = Scenario && oai_settings.scenario_format ? substituteParams(oai_settings.scenario_format) : '';
|
||||
const charPersonalityText = charPersonality && oai_settings.personality_format ? substituteParams(oai_settings.personality_format) : '';
|
||||
const groupNudge = substituteParams(oai_settings.group_nudge_prompt);
|
||||
@ -1142,6 +1168,9 @@ function preparePromptsForChatCompletion({ Scenario, charPersonality, name2, wor
|
||||
if (!extensionPrompts[key].value) continue;
|
||||
if (![extension_prompt_types.BEFORE_PROMPT, extension_prompt_types.IN_PROMPT].includes(prompt.position)) continue;
|
||||
|
||||
const hasFilter = typeof prompt.filter === 'function';
|
||||
if (hasFilter && !await prompt.filter()) continue;
|
||||
|
||||
systemPrompts.push({
|
||||
identifier: key.replace(/\W/g, '_'),
|
||||
position: getPromptPosition(prompt.position),
|
||||
@ -1178,7 +1207,8 @@ function preparePromptsForChatCompletion({ Scenario, charPersonality, name2, wor
|
||||
|
||||
// Apply character-specific main prompt
|
||||
const systemPrompt = prompts.get('main') ?? null;
|
||||
if (systemPromptOverride && systemPrompt && systemPrompt.forbid_overrides !== true) {
|
||||
const isSystemPromptDisabled = promptManager.isPromptDisabledForActiveCharacter('main');
|
||||
if (systemPromptOverride && systemPrompt && systemPrompt.forbid_overrides !== true && !isSystemPromptDisabled) {
|
||||
const mainOriginalContent = systemPrompt.content;
|
||||
systemPrompt.content = systemPromptOverride;
|
||||
const mainReplacement = promptManager.preparePrompt(systemPrompt, mainOriginalContent);
|
||||
@ -1187,7 +1217,8 @@ function preparePromptsForChatCompletion({ Scenario, charPersonality, name2, wor
|
||||
|
||||
// Apply character-specific jailbreak
|
||||
const jailbreakPrompt = prompts.get('jailbreak') ?? null;
|
||||
if (jailbreakPromptOverride && jailbreakPrompt && jailbreakPrompt.forbid_overrides !== true) {
|
||||
const isJailbreakPromptDisabled = promptManager.isPromptDisabledForActiveCharacter('jailbreak');
|
||||
if (jailbreakPromptOverride && jailbreakPrompt && jailbreakPrompt.forbid_overrides !== true && !isJailbreakPromptDisabled) {
|
||||
const jbOriginalContent = jailbreakPrompt.content;
|
||||
jailbreakPrompt.content = jailbreakPromptOverride;
|
||||
const jbReplacement = promptManager.preparePrompt(jailbreakPrompt, jbOriginalContent);
|
||||
@ -1252,7 +1283,7 @@ export async function prepareOpenAIMessages({
|
||||
|
||||
try {
|
||||
// Merge markers and ordered user prompts with system prompts
|
||||
const prompts = preparePromptsForChatCompletion({
|
||||
const prompts = await preparePromptsForChatCompletion({
|
||||
Scenario,
|
||||
charPersonality,
|
||||
name2,
|
||||
@ -1498,6 +1529,8 @@ function getChatCompletionModel() {
|
||||
return oai_settings.blockentropy_model;
|
||||
case chat_completion_sources.NANOGPT:
|
||||
return oai_settings.nanogpt_model;
|
||||
case chat_completion_sources.DEEPSEEK:
|
||||
return oai_settings.deepseek_model;
|
||||
default:
|
||||
throw new Error(`Unknown chat completion source: ${oai_settings.chat_completion_source}`);
|
||||
}
|
||||
@ -1673,6 +1706,24 @@ function saveModelList(data) {
|
||||
|
||||
$('#model_nanogpt_select').val(oai_settings.nanogpt_model).trigger('change');
|
||||
}
|
||||
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK) {
|
||||
$('#model_deepseek_select').empty();
|
||||
model_list.forEach((model) => {
|
||||
$('#model_deepseek_select').append(
|
||||
$('<option>', {
|
||||
value: model.id,
|
||||
text: model.id,
|
||||
}));
|
||||
});
|
||||
|
||||
const selectedModel = model_list.find(model => model.id === oai_settings.deepseek_model);
|
||||
if (model_list.length > 0 && (!selectedModel || !oai_settings.deepseek_model)) {
|
||||
oai_settings.deepseek_model = model_list[0].id;
|
||||
}
|
||||
|
||||
$('#model_deepseek_select').val(oai_settings.deepseek_model).trigger('change');
|
||||
}
|
||||
}
|
||||
|
||||
function appendOpenRouterOptions(model_list, groupModels = false, sort = false) {
|
||||
@ -1813,6 +1864,7 @@ async function sendOpenAIRequest(type, messages, signal) {
|
||||
const isGroq = oai_settings.chat_completion_source == chat_completion_sources.GROQ;
|
||||
const is01AI = oai_settings.chat_completion_source == chat_completion_sources.ZEROONEAI;
|
||||
const isNano = oai_settings.chat_completion_source == chat_completion_sources.NANOGPT;
|
||||
const isDeepSeek = oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK;
|
||||
const isTextCompletion = isOAI && textCompletionModels.includes(oai_settings.openai_model);
|
||||
const isQuiet = type === 'quiet';
|
||||
const isImpersonate = type === 'impersonate';
|
||||
@ -1860,6 +1912,8 @@ async function sendOpenAIRequest(type, messages, signal) {
|
||||
'n': canMultiSwipe ? oai_settings.n : undefined,
|
||||
'user_name': name1,
|
||||
'char_name': name2,
|
||||
'group_names': getGroupNames(),
|
||||
'show_thoughts': Boolean(oai_settings.show_thoughts),
|
||||
};
|
||||
|
||||
// Empty array will produce a validation error
|
||||
@ -1875,7 +1929,7 @@ async function sendOpenAIRequest(type, messages, signal) {
|
||||
}
|
||||
|
||||
// Add logprobs request (currently OpenAI only, max 5 on their side)
|
||||
if (useLogprobs && (isOAI || isCustom)) {
|
||||
if (useLogprobs && (isOAI || isCustom || isDeepSeek)) {
|
||||
generate_data['logprobs'] = 5;
|
||||
}
|
||||
|
||||
@ -1904,6 +1958,7 @@ async function sendOpenAIRequest(type, messages, signal) {
|
||||
generate_data['use_fallback'] = oai_settings.openrouter_use_fallback;
|
||||
generate_data['provider'] = oai_settings.openrouter_providers;
|
||||
generate_data['allow_fallbacks'] = oai_settings.openrouter_allow_fallbacks;
|
||||
generate_data['middleout'] = oai_settings.openrouter_middleout;
|
||||
|
||||
if (isTextCompletion) {
|
||||
generate_data['stop'] = getStoppingStrings(isImpersonate, isContinue);
|
||||
@ -1972,6 +2027,11 @@ async function sendOpenAIRequest(type, messages, signal) {
|
||||
delete generate_data.stop;
|
||||
}
|
||||
|
||||
// https://api-docs.deepseek.com/api/create-chat-completion
|
||||
if (isDeepSeek) {
|
||||
generate_data.top_p = generate_data.top_p || Number.EPSILON;
|
||||
}
|
||||
|
||||
if ((isOAI || isOpenRouter || isMistral || isCustom || isCohere || isNano) && oai_settings.seed >= 0) {
|
||||
generate_data['seed'] = oai_settings.seed;
|
||||
}
|
||||
@ -2073,7 +2133,7 @@ function getStreamingReply(data) {
|
||||
if (oai_settings.chat_completion_source === chat_completion_sources.CLAUDE) {
|
||||
return data?.delta?.text || '';
|
||||
} else if (oai_settings.chat_completion_source === chat_completion_sources.MAKERSUITE) {
|
||||
return data?.candidates?.[0]?.content?.parts?.[0]?.text || '';
|
||||
return data?.candidates?.[0]?.content?.parts?.filter(x => oai_settings.show_thoughts || !x.thought)?.map(x => x.text)?.filter(x => x)?.join('\n\n') || '';
|
||||
} else if (oai_settings.chat_completion_source === chat_completion_sources.COHERE) {
|
||||
return data?.delta?.message?.content?.text || data?.delta?.message?.tool_plan || '';
|
||||
} else {
|
||||
@ -2085,7 +2145,7 @@ function getStreamingReply(data) {
|
||||
* parseChatCompletionLogprobs converts the response data returned from a chat
|
||||
* completions-like source into an array of TokenLogprobs found in the response.
|
||||
* @param {Object} data - response data from a chat completions-like source
|
||||
* @returns {import('logprobs.js').TokenLogprobs[] | null} converted logprobs
|
||||
* @returns {import('./logprobs.js').TokenLogprobs[] | null} converted logprobs
|
||||
*/
|
||||
function parseChatCompletionLogprobs(data) {
|
||||
if (!data) {
|
||||
@ -2094,6 +2154,7 @@ function parseChatCompletionLogprobs(data) {
|
||||
|
||||
switch (oai_settings.chat_completion_source) {
|
||||
case chat_completion_sources.OPENAI:
|
||||
case chat_completion_sources.DEEPSEEK:
|
||||
case chat_completion_sources.CUSTOM:
|
||||
if (!data.choices?.length) {
|
||||
return null;
|
||||
@ -2114,7 +2175,7 @@ function parseChatCompletionLogprobs(data) {
|
||||
* completion API and converts into the structure used by the Token Probabilities
|
||||
* view.
|
||||
* @param {{content: { token: string, logprob: number, top_logprobs: { token: string, logprob: number }[] }[]}} logprobs
|
||||
* @returns {import('logprobs.js').TokenLogprobs[] | null} converted logprobs
|
||||
* @returns {import('./logprobs.js').TokenLogprobs[] | null} converted logprobs
|
||||
*/
|
||||
function parseOpenAIChatLogprobs(logprobs) {
|
||||
const { content } = logprobs ?? {};
|
||||
@ -2142,7 +2203,7 @@ function parseOpenAIChatLogprobs(logprobs) {
|
||||
* completion API and converts into the structure used by the Token Probabilities
|
||||
* view.
|
||||
* @param {{tokens: string[], token_logprobs: number[], top_logprobs: { token: string, logprob: number }[][]}} logprobs
|
||||
* @returns {import('logprobs.js').TokenLogprobs[] | null} converted logprobs
|
||||
* @returns {import('./logprobs.js').TokenLogprobs[] | null} converted logprobs
|
||||
*/
|
||||
function parseOpenAITextLogprobs(logprobs) {
|
||||
const { tokens, token_logprobs, top_logprobs } = logprobs ?? {};
|
||||
@ -3006,12 +3067,14 @@ function loadOpenAISettings(data, settings) {
|
||||
oai_settings.openrouter_sort_models = settings.openrouter_sort_models ?? default_settings.openrouter_sort_models;
|
||||
oai_settings.openrouter_use_fallback = settings.openrouter_use_fallback ?? default_settings.openrouter_use_fallback;
|
||||
oai_settings.openrouter_allow_fallbacks = settings.openrouter_allow_fallbacks ?? default_settings.openrouter_allow_fallbacks;
|
||||
oai_settings.openrouter_middleout = settings.openrouter_middleout ?? default_settings.openrouter_middleout;
|
||||
oai_settings.ai21_model = settings.ai21_model ?? default_settings.ai21_model;
|
||||
oai_settings.mistralai_model = settings.mistralai_model ?? default_settings.mistralai_model;
|
||||
oai_settings.cohere_model = settings.cohere_model ?? default_settings.cohere_model;
|
||||
oai_settings.perplexity_model = settings.perplexity_model ?? default_settings.perplexity_model;
|
||||
oai_settings.groq_model = settings.groq_model ?? default_settings.groq_model;
|
||||
oai_settings.nanogpt_model = settings.nanogpt_model ?? default_settings.nanogpt_model;
|
||||
oai_settings.deepseek_model = settings.deepseek_model ?? default_settings.deepseek_model;
|
||||
oai_settings.blockentropy_model = settings.blockentropy_model ?? default_settings.blockentropy_model;
|
||||
oai_settings.zerooneai_model = settings.zerooneai_model ?? default_settings.zerooneai_model;
|
||||
oai_settings.custom_model = settings.custom_model ?? default_settings.custom_model;
|
||||
@ -3030,6 +3093,7 @@ function loadOpenAISettings(data, settings) {
|
||||
oai_settings.image_inlining = settings.image_inlining ?? default_settings.image_inlining;
|
||||
oai_settings.inline_image_quality = settings.inline_image_quality ?? default_settings.inline_image_quality;
|
||||
oai_settings.bypass_status_check = settings.bypass_status_check ?? default_settings.bypass_status_check;
|
||||
oai_settings.show_thoughts = settings.show_thoughts ?? default_settings.show_thoughts;
|
||||
oai_settings.seed = settings.seed ?? default_settings.seed;
|
||||
oai_settings.n = settings.n ?? default_settings.n;
|
||||
|
||||
@ -3092,6 +3156,8 @@ function loadOpenAISettings(data, settings) {
|
||||
$(`#model_groq_select option[value="${oai_settings.groq_model}"`).attr('selected', true);
|
||||
$('#model_nanogpt_select').val(oai_settings.nanogpt_model);
|
||||
$(`#model_nanogpt_select option[value="${oai_settings.nanogpt_model}"`).attr('selected', true);
|
||||
$('#model_deepseek_select').val(oai_settings.deepseek_model);
|
||||
$(`#model_deepseek_select option[value="${oai_settings.deepseek_model}"`).prop('selected', true);
|
||||
$('#model_01ai_select').val(oai_settings.zerooneai_model);
|
||||
$('#model_blockentropy_select').val(oai_settings.blockentropy_model);
|
||||
$('#custom_model_id').val(oai_settings.custom_model);
|
||||
@ -3114,6 +3180,7 @@ function loadOpenAISettings(data, settings) {
|
||||
$('#openrouter_group_models').prop('checked', oai_settings.openrouter_group_models);
|
||||
$('#openrouter_allow_fallbacks').prop('checked', oai_settings.openrouter_allow_fallbacks);
|
||||
$('#openrouter_providers_chat').val(oai_settings.openrouter_providers).trigger('change');
|
||||
$('#openrouter_middleout').val(oai_settings.openrouter_middleout);
|
||||
$('#squash_system_messages').prop('checked', oai_settings.squash_system_messages);
|
||||
$('#continue_prefill').prop('checked', oai_settings.continue_prefill);
|
||||
$('#openai_function_calling').prop('checked', oai_settings.function_calling);
|
||||
@ -3154,6 +3221,7 @@ function loadOpenAISettings(data, settings) {
|
||||
$('#repetition_penalty_counter_openai').val(Number(oai_settings.repetition_penalty_openai));
|
||||
$('#seed_openai').val(oai_settings.seed);
|
||||
$('#n_openai').val(oai_settings.n);
|
||||
$('#openai_show_thoughts').prop('checked', oai_settings.show_thoughts);
|
||||
|
||||
if (settings.reverse_proxy !== undefined) oai_settings.reverse_proxy = settings.reverse_proxy;
|
||||
$('#openai_reverse_proxy').val(oai_settings.reverse_proxy);
|
||||
@ -3162,6 +3230,14 @@ function loadOpenAISettings(data, settings) {
|
||||
|
||||
$('#openai_logit_bias_preset').empty();
|
||||
for (const preset of Object.keys(oai_settings.bias_presets)) {
|
||||
// Backfill missing IDs
|
||||
if (Array.isArray(oai_settings.bias_presets[preset])) {
|
||||
oai_settings.bias_presets[preset].forEach((bias) => {
|
||||
if (bias && !bias.id) {
|
||||
bias.id = uuidv4();
|
||||
}
|
||||
});
|
||||
}
|
||||
const option = document.createElement('option');
|
||||
option.innerText = preset;
|
||||
option.value = preset;
|
||||
@ -3270,7 +3346,7 @@ async function getStatusOpen() {
|
||||
chat_completion_source: oai_settings.chat_completion_source,
|
||||
};
|
||||
|
||||
if (oai_settings.reverse_proxy && (oai_settings.chat_completion_source === chat_completion_sources.OPENAI || oai_settings.chat_completion_source === chat_completion_sources.CLAUDE)) {
|
||||
if (oai_settings.reverse_proxy && [chat_completion_sources.CLAUDE, chat_completion_sources.OPENAI, chat_completion_sources.MISTRALAI, chat_completion_sources.MAKERSUITE].includes(oai_settings.chat_completion_source)) {
|
||||
await validateReverseProxy();
|
||||
}
|
||||
|
||||
@ -3346,6 +3422,7 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) {
|
||||
openrouter_sort_models: settings.openrouter_sort_models,
|
||||
openrouter_providers: settings.openrouter_providers,
|
||||
openrouter_allow_fallbacks: settings.openrouter_allow_fallbacks,
|
||||
openrouter_middleout: settings.openrouter_middleout,
|
||||
ai21_model: settings.ai21_model,
|
||||
mistralai_model: settings.mistralai_model,
|
||||
cohere_model: settings.cohere_model,
|
||||
@ -3405,6 +3482,7 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) {
|
||||
continue_prefill: settings.continue_prefill,
|
||||
continue_postfix: settings.continue_postfix,
|
||||
function_calling: settings.function_calling,
|
||||
show_thoughts: settings.show_thoughts,
|
||||
seed: settings.seed,
|
||||
n: settings.n,
|
||||
};
|
||||
@ -3450,7 +3528,8 @@ function onLogitBiasPresetChange() {
|
||||
}
|
||||
|
||||
oai_settings.bias_preset_selected = value;
|
||||
$('.openai_logit_bias_list').empty();
|
||||
const list = $('.openai_logit_bias_list');
|
||||
list.empty();
|
||||
|
||||
for (const entry of preset) {
|
||||
if (entry) {
|
||||
@ -3458,12 +3537,33 @@ function onLogitBiasPresetChange() {
|
||||
}
|
||||
}
|
||||
|
||||
// Check if a sortable instance exists
|
||||
if (list.sortable('instance') !== undefined) {
|
||||
// Destroy the instance
|
||||
list.sortable('destroy');
|
||||
}
|
||||
|
||||
// Make the list sortable
|
||||
list.sortable({
|
||||
delay: getSortableDelay(),
|
||||
handle: '.drag-handle',
|
||||
stop: function () {
|
||||
const order = [];
|
||||
list.children().each(function () {
|
||||
order.unshift($(this).data('id'));
|
||||
});
|
||||
preset.sort((a, b) => order.indexOf(a.id) - order.indexOf(b.id));
|
||||
console.log('Logit bias reordered:', preset);
|
||||
saveSettingsDebounced();
|
||||
},
|
||||
});
|
||||
|
||||
biasCache = undefined;
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function createNewLogitBiasEntry() {
|
||||
const entry = { text: '', value: 0 };
|
||||
const entry = { id: uuidv4(), text: '', value: 0 };
|
||||
oai_settings.bias_presets[oai_settings.bias_preset_selected].push(entry);
|
||||
biasCache = undefined;
|
||||
createLogitBiasListItem(entry);
|
||||
@ -3471,11 +3571,14 @@ function createNewLogitBiasEntry() {
|
||||
}
|
||||
|
||||
function createLogitBiasListItem(entry) {
|
||||
const id = oai_settings.bias_presets[oai_settings.bias_preset_selected].indexOf(entry);
|
||||
if (!entry.id) {
|
||||
entry.id = uuidv4();
|
||||
}
|
||||
const id = entry.id;
|
||||
const template = $('#openai_logit_bias_template .openai_logit_bias_form').clone();
|
||||
template.data('id', id);
|
||||
template.find('.openai_logit_bias_text').val(entry.text).on('input', function () {
|
||||
oai_settings.bias_presets[oai_settings.bias_preset_selected][id].text = String($(this).val());
|
||||
entry.text = String($(this).val());
|
||||
biasCache = undefined;
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
@ -3494,13 +3597,17 @@ function createLogitBiasListItem(entry) {
|
||||
value = max;
|
||||
}
|
||||
|
||||
oai_settings.bias_presets[oai_settings.bias_preset_selected][id].value = value;
|
||||
entry.value = value;
|
||||
biasCache = undefined;
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
template.find('.openai_logit_bias_remove').on('click', function () {
|
||||
$(this).closest('.openai_logit_bias_form').remove();
|
||||
oai_settings.bias_presets[oai_settings.bias_preset_selected].splice(id, 1);
|
||||
const preset = oai_settings.bias_presets[oai_settings.bias_preset_selected];
|
||||
const index = preset.findIndex(item => item.id === id);
|
||||
if (index >= 0) {
|
||||
preset.splice(index, 1);
|
||||
}
|
||||
onLogitBiasPresetChange();
|
||||
});
|
||||
$('.openai_logit_bias_list').prepend(template);
|
||||
@ -3680,6 +3787,9 @@ async function onLogitBiasPresetImportFileChange(e) {
|
||||
if (typeof entry == 'object' && entry !== null) {
|
||||
if (Object.hasOwn(entry, 'text') &&
|
||||
Object.hasOwn(entry, 'value')) {
|
||||
if (!entry.id) {
|
||||
entry.id = uuidv4();
|
||||
}
|
||||
validEntries.push(entry);
|
||||
}
|
||||
}
|
||||
@ -3780,12 +3890,14 @@ function onSettingsPresetChange() {
|
||||
openrouter_sort_models: ['#openrouter_sort_models', 'openrouter_sort_models', false],
|
||||
openrouter_providers: ['#openrouter_providers_chat', 'openrouter_providers', false],
|
||||
openrouter_allow_fallbacks: ['#openrouter_allow_fallbacks', 'openrouter_allow_fallbacks', true],
|
||||
openrouter_middleout: ['#openrouter_middleout', 'openrouter_middleout', false],
|
||||
ai21_model: ['#model_ai21_select', 'ai21_model', false],
|
||||
mistralai_model: ['#model_mistralai_select', 'mistralai_model', false],
|
||||
cohere_model: ['#model_cohere_select', 'cohere_model', false],
|
||||
perplexity_model: ['#model_perplexity_select', 'perplexity_model', false],
|
||||
groq_model: ['#model_groq_select', 'groq_model', false],
|
||||
nanogpt_model: ['#model_nanogpt_select', 'nanogpt_model', false],
|
||||
deepseek_model: ['#model_deepseek_select', 'deepseek_model', false],
|
||||
zerooneai_model: ['#model_01ai_select', 'zerooneai_model', false],
|
||||
blockentropy_model: ['#model_blockentropy_select', 'blockentropy_model', false],
|
||||
custom_model: ['#custom_model_id', 'custom_model', false],
|
||||
@ -3828,6 +3940,7 @@ function onSettingsPresetChange() {
|
||||
continue_prefill: ['#continue_prefill', 'continue_prefill', true],
|
||||
continue_postfix: ['#continue_postfix', 'continue_postfix', false],
|
||||
function_calling: ['#openai_function_calling', 'function_calling', true],
|
||||
show_thoughts: ['#openai_show_thoughts', 'show_thoughts', true],
|
||||
seed: ['#seed_openai', 'seed', false],
|
||||
n: ['#n_openai', 'n', false],
|
||||
};
|
||||
@ -4044,6 +4157,16 @@ async function onModelChange() {
|
||||
oai_settings.nanogpt_model = value;
|
||||
}
|
||||
|
||||
if ($(this).is('#model_deepseek_select')) {
|
||||
if (!value) {
|
||||
console.debug('Null DeepSeek model selected. Ignoring.');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('DeepSeek model changed to', value);
|
||||
oai_settings.deepseek_model = value;
|
||||
}
|
||||
|
||||
if (value && $(this).is('#model_01ai_select')) {
|
||||
console.log('01.AI model changed to', value);
|
||||
oai_settings.zerooneai_model = value;
|
||||
@ -4075,19 +4198,14 @@ async function onModelChange() {
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE) {
|
||||
if (oai_settings.max_context_unlocked) {
|
||||
$('#openai_max_context').attr('max', max_2mil);
|
||||
} else if (value.includes('gemini-exp-1114') || value.includes('gemini-exp-1121')) {
|
||||
} else if (value.includes('gemini-exp-1114') || value.includes('gemini-exp-1121') || value.includes('gemini-2.0-flash-thinking-exp-1219')) {
|
||||
$('#openai_max_context').attr('max', max_32k);
|
||||
} else if (value.includes('gemini-1.5-pro') || value.includes('gemini-exp-1206')) {
|
||||
$('#openai_max_context').attr('max', max_2mil);
|
||||
} else if (value.includes('gemini-1.5-flash')) {
|
||||
} else if (value.includes('gemini-1.5-flash') || value.includes('gemini-2.0-flash-exp')) {
|
||||
$('#openai_max_context').attr('max', max_1mil);
|
||||
} else if (value.includes('gemini-1.0-pro-vision') || value === 'gemini-pro-vision') {
|
||||
$('#openai_max_context').attr('max', max_16k);
|
||||
} else if (value.includes('gemini-1.0-pro') || value === 'gemini-pro') {
|
||||
$('#openai_max_context').attr('max', max_32k);
|
||||
} else if (value === 'text-bison-001') {
|
||||
$('#openai_max_context').attr('max', max_8k);
|
||||
// The ultra endpoints are possibly dead:
|
||||
} else if (value.includes('gemini-1.0-ultra') || value === 'gemini-ultra') {
|
||||
$('#openai_max_context').attr('max', max_32k);
|
||||
} else {
|
||||
@ -4209,16 +4327,13 @@ async function onModelChange() {
|
||||
if (oai_settings.max_context_unlocked) {
|
||||
$('#openai_max_context').attr('max', unlocked_max);
|
||||
}
|
||||
else if (['command-light', 'command'].includes(oai_settings.cohere_model)) {
|
||||
else if (['command-light-nightly', 'command-light', 'command'].includes(oai_settings.cohere_model)) {
|
||||
$('#openai_max_context').attr('max', max_4k);
|
||||
}
|
||||
else if (['command-light-nightly', 'command-nightly'].includes(oai_settings.cohere_model)) {
|
||||
$('#openai_max_context').attr('max', max_8k);
|
||||
}
|
||||
else if (oai_settings.cohere_model.includes('command-r') || ['c4ai-aya-expanse-32b'].includes(oai_settings.cohere_model)) {
|
||||
else if (oai_settings.cohere_model.includes('command-r') || ['c4ai-aya-23', 'c4ai-aya-expanse-32b', 'command-nightly'].includes(oai_settings.cohere_model)) {
|
||||
$('#openai_max_context').attr('max', max_128k);
|
||||
}
|
||||
else if (['c4ai-aya-23', 'c4ai-aya-expanse-8b'].includes(oai_settings.cohere_model)) {
|
||||
else if (['c4ai-aya-23-8b', 'c4ai-aya-expanse-8b'].includes(oai_settings.cohere_model)) {
|
||||
$('#openai_max_context').attr('max', max_8k);
|
||||
}
|
||||
else {
|
||||
@ -4269,7 +4384,7 @@ async function onModelChange() {
|
||||
else if (oai_settings.groq_model.includes('llama-3.2') && oai_settings.groq_model.includes('-preview')) {
|
||||
$('#openai_max_context').attr('max', max_8k);
|
||||
}
|
||||
else if (oai_settings.groq_model.includes('llama-3.2') || oai_settings.groq_model.includes('llama-3.1')) {
|
||||
else if (oai_settings.groq_model.includes('llama-3.3') || oai_settings.groq_model.includes('llama-3.2') || oai_settings.groq_model.includes('llama-3.1')) {
|
||||
$('#openai_max_context').attr('max', max_128k);
|
||||
}
|
||||
else if (oai_settings.groq_model.includes('llama3-groq')) {
|
||||
@ -4368,6 +4483,22 @@ async function onModelChange() {
|
||||
$('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input');
|
||||
}
|
||||
|
||||
if (oai_settings.chat_completion_source === chat_completion_sources.DEEPSEEK) {
|
||||
if (oai_settings.max_context_unlocked) {
|
||||
$('#openai_max_context').attr('max', unlocked_max);
|
||||
} else if (oai_settings.deepseek_model == 'deepseek-chat') {
|
||||
$('#openai_max_context').attr('max', max_64k);
|
||||
} else if (oai_settings.deepseek_model == 'deepseek-coder') {
|
||||
$('#openai_max_context').attr('max', max_16k);
|
||||
} else {
|
||||
$('#openai_max_context').attr('max', max_64k);
|
||||
}
|
||||
|
||||
oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context);
|
||||
$('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
|
||||
$('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input');
|
||||
}
|
||||
|
||||
if (oai_settings.chat_completion_source === chat_completion_sources.COHERE) {
|
||||
oai_settings.pres_pen_openai = Math.min(Math.max(0, oai_settings.pres_pen_openai), 1);
|
||||
$('#pres_pen_openai').attr('max', 1).attr('min', 0).val(oai_settings.pres_pen_openai).trigger('input');
|
||||
@ -4585,6 +4716,19 @@ async function onConnectButtonClick(e) {
|
||||
}
|
||||
}
|
||||
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK) {
|
||||
const api_key_deepseek = String($('#api_key_deepseek').val()).trim();
|
||||
|
||||
if (api_key_deepseek.length) {
|
||||
await writeSecret(SECRET_KEYS.DEEPSEEK, api_key_deepseek);
|
||||
}
|
||||
|
||||
if (!secret_state[SECRET_KEYS.DEEPSEEK]) {
|
||||
console.log('No secret key saved for DeepSeek');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.ZEROONEAI) {
|
||||
const api_key_01ai = String($('#api_key_01ai').val()).trim();
|
||||
|
||||
@ -4666,6 +4810,9 @@ function toggleChatCompletionForms() {
|
||||
else if (oai_settings.chat_completion_source == chat_completion_sources.BLOCKENTROPY) {
|
||||
$('#model_blockentropy_select').trigger('change');
|
||||
}
|
||||
else if (oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK) {
|
||||
$('#model_deepseek_select').trigger('change');
|
||||
}
|
||||
$('[data-source]').each(function () {
|
||||
const validSources = $(this).data('source').split(',');
|
||||
$(this).toggle(validSources.includes(oai_settings.chat_completion_source));
|
||||
@ -4751,6 +4898,8 @@ export function isImageInliningSupported() {
|
||||
// gultra just isn't being offered as multimodal, thanks google.
|
||||
const visionSupportedModels = [
|
||||
'gpt-4-vision',
|
||||
'gemini-2.0-flash-thinking-exp-1219',
|
||||
'gemini-2.0-flash-exp',
|
||||
'gemini-1.5-flash',
|
||||
'gemini-1.5-flash-latest',
|
||||
'gemini-1.5-flash-001',
|
||||
@ -4769,7 +4918,6 @@ export function isImageInliningSupported() {
|
||||
'gemini-1.5-pro-002',
|
||||
'gemini-1.5-pro-exp-0801',
|
||||
'gemini-1.5-pro-exp-0827',
|
||||
'gemini-pro-vision',
|
||||
'claude-3',
|
||||
'claude-3-5',
|
||||
'gpt-4-turbo',
|
||||
@ -5213,6 +5361,11 @@ export function initOpenAI() {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#openrouter_middleout').on('input', function () {
|
||||
oai_settings.openrouter_middleout = String($(this).val());
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#squash_system_messages').on('input', function () {
|
||||
oai_settings.squash_system_messages = !!$(this).prop('checked');
|
||||
saveSettingsDebounced();
|
||||
@ -5323,6 +5476,11 @@ export function initOpenAI() {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#openai_show_thoughts').on('input', function () {
|
||||
oai_settings.show_thoughts = !!$(this).prop('checked');
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
if (!CSS.supports('field-sizing', 'content')) {
|
||||
$(document).on('input', '#openai_settings .autoSetHeight', function () {
|
||||
resetScrollHeight($(this));
|
||||
@ -5368,6 +5526,7 @@ export function initOpenAI() {
|
||||
$('#model_perplexity_select').on('change', onModelChange);
|
||||
$('#model_groq_select').on('change', onModelChange);
|
||||
$('#model_nanogpt_select').on('change', onModelChange);
|
||||
$('#model_deepseek_select').on('change', onModelChange);
|
||||
$('#model_01ai_select').on('change', onModelChange);
|
||||
$('#model_blockentropy_select').on('change', onModelChange);
|
||||
$('#model_custom_select').on('change', onModelChange);
|
||||
|
@ -21,8 +21,10 @@ import { PAGINATION_TEMPLATE, debounce, delay, download, ensureImageFormatSuppor
|
||||
import { debounce_timeout } from './constants.js';
|
||||
import { FILTER_TYPES, FilterHelper } from './filters.js';
|
||||
import { selected_group } from './group-chats.js';
|
||||
import { POPUP_RESULT, POPUP_TYPE, Popup } from './popup.js';
|
||||
import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
|
||||
import { t } from './i18n.js';
|
||||
import { openWorldInfoEditor, world_names } from './world-info.js';
|
||||
import { renderTemplateAsync } from './templates.js';
|
||||
|
||||
let savePersonasPage = 0;
|
||||
const GRID_STORAGE_KEY = 'Personas_GridView';
|
||||
@ -375,6 +377,7 @@ export function initPersona(avatarId, personaName, personaDescription) {
|
||||
position: persona_description_positions.IN_PROMPT,
|
||||
depth: DEFAULT_DEPTH,
|
||||
role: DEFAULT_ROLE,
|
||||
lorebook: '',
|
||||
};
|
||||
|
||||
saveSettingsDebounced();
|
||||
@ -418,6 +421,7 @@ export async function convertCharacterToPersona(characterId = null) {
|
||||
position: persona_description_positions.IN_PROMPT,
|
||||
depth: DEFAULT_DEPTH,
|
||||
role: DEFAULT_ROLE,
|
||||
lorebook: '',
|
||||
};
|
||||
|
||||
// If the user is currently using this persona, update the description
|
||||
@ -461,6 +465,7 @@ export function setPersonaDescription() {
|
||||
.val(power_user.persona_description_role)
|
||||
.find(`option[value="${power_user.persona_description_role}"]`)
|
||||
.prop('selected', String(true));
|
||||
$('#persona_lore_button').toggleClass('world_set', !!power_user.persona_description_lorebook);
|
||||
countPersonaDescriptionTokens();
|
||||
}
|
||||
|
||||
@ -490,6 +495,7 @@ async function updatePersonaNameIfExists(avatarId, newName) {
|
||||
position: persona_description_positions.IN_PROMPT,
|
||||
depth: DEFAULT_DEPTH,
|
||||
role: DEFAULT_ROLE,
|
||||
lorebook: '',
|
||||
};
|
||||
console.log(`Created persona name for ${avatarId} as ${newName}`);
|
||||
}
|
||||
@ -535,6 +541,7 @@ async function bindUserNameToPersona(e) {
|
||||
position: isCurrentPersona ? power_user.persona_description_position : persona_description_positions.IN_PROMPT,
|
||||
depth: isCurrentPersona ? power_user.persona_description_depth : DEFAULT_DEPTH,
|
||||
role: isCurrentPersona ? power_user.persona_description_role : DEFAULT_ROLE,
|
||||
lorebook: isCurrentPersona ? power_user.persona_description_lorebook : '',
|
||||
};
|
||||
}
|
||||
|
||||
@ -579,12 +586,20 @@ function selectCurrentPersona() {
|
||||
power_user.persona_description_position = descriptor.position ?? persona_description_positions.IN_PROMPT;
|
||||
power_user.persona_description_depth = descriptor.depth ?? DEFAULT_DEPTH;
|
||||
power_user.persona_description_role = descriptor.role ?? DEFAULT_ROLE;
|
||||
power_user.persona_description_lorebook = descriptor.lorebook ?? '';
|
||||
} else {
|
||||
power_user.persona_description = '';
|
||||
power_user.persona_description_position = persona_description_positions.IN_PROMPT;
|
||||
power_user.persona_description_depth = DEFAULT_DEPTH;
|
||||
power_user.persona_description_role = DEFAULT_ROLE;
|
||||
power_user.persona_descriptions[user_avatar] = { description: '', position: persona_description_positions.IN_PROMPT, depth: DEFAULT_DEPTH, role: DEFAULT_ROLE };
|
||||
power_user.persona_description_lorebook = '';
|
||||
power_user.persona_descriptions[user_avatar] = {
|
||||
description: '',
|
||||
position: persona_description_positions.IN_PROMPT,
|
||||
depth: DEFAULT_DEPTH,
|
||||
role: DEFAULT_ROLE,
|
||||
lorebook: '',
|
||||
};
|
||||
}
|
||||
|
||||
setPersonaDescription();
|
||||
@ -652,6 +667,7 @@ async function lockPersona() {
|
||||
position: persona_description_positions.IN_PROMPT,
|
||||
depth: DEFAULT_DEPTH,
|
||||
role: DEFAULT_ROLE,
|
||||
lorebook: '',
|
||||
};
|
||||
}
|
||||
|
||||
@ -731,6 +747,7 @@ function onPersonaDescriptionInput() {
|
||||
position: Number($('#persona_description_position').find(':selected').val()),
|
||||
depth: Number($('#persona_depth_value').val()),
|
||||
role: Number($('#persona_depth_role').find(':selected').val()),
|
||||
lorebook: '',
|
||||
};
|
||||
power_user.persona_descriptions[user_avatar] = object;
|
||||
}
|
||||
@ -766,6 +783,52 @@ function onPersonaDescriptionDepthRoleInput() {
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a popup to set the lorebook for the current persona.
|
||||
* @param {PointerEvent} event Click event
|
||||
*/
|
||||
async function onPersonaLoreButtonClick(event) {
|
||||
const personaName = power_user.personas[user_avatar];
|
||||
const selectedLorebook = power_user.persona_description_lorebook;
|
||||
|
||||
if (!personaName) {
|
||||
toastr.warning(t`You must bind a name to this persona before you can set a lorebook.`, t`Persona name not set`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.altKey && selectedLorebook) {
|
||||
openWorldInfoEditor(selectedLorebook);
|
||||
return;
|
||||
}
|
||||
|
||||
const template = $(await renderTemplateAsync('personaLorebook'));
|
||||
|
||||
const worldSelect = template.find('select');
|
||||
template.find('.persona_name').text(personaName);
|
||||
|
||||
for (const worldName of world_names) {
|
||||
const option = document.createElement('option');
|
||||
option.value = worldName;
|
||||
option.innerText = worldName;
|
||||
option.selected = selectedLorebook === worldName;
|
||||
worldSelect.append(option);
|
||||
}
|
||||
|
||||
worldSelect.on('change', function () {
|
||||
power_user.persona_description_lorebook = String($(this).val());
|
||||
|
||||
if (power_user.personas[user_avatar]) {
|
||||
const object = getOrCreatePersonaDescriptor();
|
||||
object.lorebook = power_user.persona_description_lorebook;
|
||||
}
|
||||
|
||||
$('#persona_lore_button').toggleClass('world_set', !!power_user.persona_description_lorebook);
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
await callGenericPopup(template, POPUP_TYPE.TEXT);
|
||||
}
|
||||
|
||||
function onPersonaDescriptionPositionInput() {
|
||||
power_user.persona_description_position = Number(
|
||||
$('#persona_description_position').find(':selected').val(),
|
||||
@ -789,6 +852,7 @@ function getOrCreatePersonaDescriptor() {
|
||||
position: power_user.persona_description_position,
|
||||
depth: power_user.persona_description_depth,
|
||||
role: power_user.persona_description_role,
|
||||
lorebook: power_user.persona_description_lorebook,
|
||||
};
|
||||
power_user.persona_descriptions[user_avatar] = object;
|
||||
}
|
||||
@ -1038,6 +1102,7 @@ async function duplicatePersona(avatarId) {
|
||||
position: descriptor?.position ?? persona_description_positions.IN_PROMPT,
|
||||
depth: descriptor?.depth ?? DEFAULT_DEPTH,
|
||||
role: descriptor?.role ?? DEFAULT_ROLE,
|
||||
lorebook: descriptor?.lorebook ?? '',
|
||||
};
|
||||
|
||||
await uploadUserAvatar(getUserAvatar(avatarId), newAvatarId);
|
||||
@ -1055,6 +1120,7 @@ export function initPersonas() {
|
||||
$('#persona_description_position').on('input', onPersonaDescriptionPositionInput);
|
||||
$('#persona_depth_value').on('input', onPersonaDescriptionDepthValueInput);
|
||||
$('#persona_depth_role').on('input', onPersonaDescriptionDepthRoleInput);
|
||||
$('#persona_lore_button').on('click', onPersonaLoreButtonClick);
|
||||
$('#personas_backup').on('click', onBackupPersonas);
|
||||
$('#personas_restore').on('click', () => $('#personas_restore_input').trigger('click'));
|
||||
$('#personas_restore_input').on('change', onPersonasRestoreInput);
|
||||
|
@ -75,7 +75,7 @@ const showPopupHelper = {
|
||||
* Asynchronously displays an input popup with the given header and text, and returns the user's input.
|
||||
*
|
||||
* @param {string?} header - The header text for the popup.
|
||||
* @param {string?} text - The main text for the popup.
|
||||
* @param {string?} [text] - The main text for the popup.
|
||||
* @param {string} [defaultValue=''] - The default value for the input field.
|
||||
* @param {PopupOptions} [popupOptions={}] - Options for the popup.
|
||||
* @return {Promise<string?>} A Promise that resolves with the user's input.
|
||||
@ -94,7 +94,7 @@ const showPopupHelper = {
|
||||
* Asynchronously displays a confirmation popup with the given header and text, returning the clicked result button value.
|
||||
*
|
||||
* @param {string?} header - The header text for the popup.
|
||||
* @param {string?} text - The main text for the popup.
|
||||
* @param {string?} [text] - The main text for the popup.
|
||||
* @param {PopupOptions} [popupOptions={}] - Options for the popup.
|
||||
* @return {Promise<POPUP_RESULT?>} A Promise that resolves with the result of the user's interaction.
|
||||
*/
|
||||
|
@ -261,6 +261,7 @@ let power_user = {
|
||||
persona_description_position: persona_description_positions.IN_PROMPT,
|
||||
persona_description_role: 0,
|
||||
persona_description_depth: 2,
|
||||
persona_description_lorebook: '',
|
||||
persona_show_notifications: true,
|
||||
persona_sort_order: 'asc',
|
||||
|
||||
@ -1756,7 +1757,7 @@ async function loadContextSettings() {
|
||||
} else {
|
||||
$element.val(power_user.context[control.property]);
|
||||
}
|
||||
console.log(`Setting ${$element.prop('id')} to ${power_user.context[control.property]}`);
|
||||
console.debug(`Setting ${$element.prop('id')} to ${power_user.context[control.property]}`);
|
||||
|
||||
// If the setting already exists, no need to duplicate it
|
||||
// TODO: Maybe check the power_user object for the setting instead of a flag?
|
||||
@ -1767,7 +1768,7 @@ async function loadContextSettings() {
|
||||
} else {
|
||||
power_user.context[control.property] = value;
|
||||
}
|
||||
console.log(`Setting ${$element.prop('id')} to ${value}`);
|
||||
console.debug(`Setting ${$element.prop('id')} to ${value}`);
|
||||
if (!CSS.supports('field-sizing', 'content') && $(this).is('textarea')) {
|
||||
await resetScrollHeight($(this));
|
||||
}
|
||||
|
@ -585,6 +585,7 @@ class PresetManager {
|
||||
'openrouter_allow_fallbacks',
|
||||
'tabby_model',
|
||||
'derived',
|
||||
'generic_model',
|
||||
];
|
||||
const settings = Object.assign({}, getSettingsByApiId(this.apiId));
|
||||
|
||||
|
@ -129,6 +129,10 @@ function setSamplerListListeners() {
|
||||
relatedDOMElement = $('#sampler_priority_block_ooba');
|
||||
}
|
||||
|
||||
if (samplerName === 'samplers_priorities') { //this is for aphrodite's sampler priority
|
||||
relatedDOMElement = $('#sampler_priority_block_aphrodite');
|
||||
}
|
||||
|
||||
if (samplerName === 'penalty_alpha') { //contrastive search only has one sampler, does it need its own block?
|
||||
relatedDOMElement = $('#contrastiveSearchBlock');
|
||||
}
|
||||
@ -237,6 +241,11 @@ async function listSamplers(main_api, arrayOnly = false) {
|
||||
displayname = 'Ooba Sampler Priority Block';
|
||||
}
|
||||
|
||||
if (sampler === 'samplers_priorities') { //this is for aphrodite's sampler priority
|
||||
targetDOMelement = $('#sampler_priority_block_aphrodite');
|
||||
displayname = 'Aphrodite Sampler Priority Block';
|
||||
}
|
||||
|
||||
if (sampler === 'penalty_alpha') { //contrastive search only has one sampler, does it need its own block?
|
||||
targetDOMelement = $('#contrastiveSearchBlock');
|
||||
displayname = 'Contrast Search Block';
|
||||
@ -373,6 +382,10 @@ export async function validateDisabledSamplers(redraw = false) {
|
||||
relatedDOMElement = $('#sampler_priority_block_ooba');
|
||||
}
|
||||
|
||||
if (sampler === 'samplers_priorities') { //this is for aphrodite's sampler priority
|
||||
relatedDOMElement = $('#sampler_priority_block_aphrodite');
|
||||
}
|
||||
|
||||
if (sampler === 'dry_multiplier') {
|
||||
relatedDOMElement = $('#dryBlock');
|
||||
targetDisplayType = 'block';
|
||||
|
@ -38,6 +38,8 @@ export const SECRET_KEYS = {
|
||||
NANOGPT: 'api_key_nanogpt',
|
||||
TAVILY: 'api_key_tavily',
|
||||
BFL: 'api_key_bfl',
|
||||
GENERIC: 'api_key_generic',
|
||||
DEEPSEEK: 'api_key_deepseek',
|
||||
};
|
||||
|
||||
const INPUT_MAP = {
|
||||
@ -71,6 +73,8 @@ const INPUT_MAP = {
|
||||
[SECRET_KEYS.HUGGINGFACE]: '#api_key_huggingface',
|
||||
[SECRET_KEYS.BLOCKENTROPY]: '#api_key_blockentropy',
|
||||
[SECRET_KEYS.NANOGPT]: '#api_key_nanogpt',
|
||||
[SECRET_KEYS.GENERIC]: '#api_key_generic',
|
||||
[SECRET_KEYS.DEEPSEEK]: '#api_key_deepseek',
|
||||
};
|
||||
|
||||
async function clearSecret() {
|
||||
|
@ -47,7 +47,7 @@ import {
|
||||
} from '../script.js';
|
||||
import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
|
||||
import { SlashCommandParserError } from './slash-commands/SlashCommandParserError.js';
|
||||
import { getMessageTimeStamp } from './RossAscends-mods.js';
|
||||
import { getMessageTimeStamp, isMobile } from './RossAscends-mods.js';
|
||||
import { hideChatMessageRange } from './chats.js';
|
||||
import { getContext, saveMetadataDebounced } from './extensions.js';
|
||||
import { getRegexedString, regex_placement } from './extensions/regex/engine.js';
|
||||
@ -84,6 +84,25 @@ export const parser = new SlashCommandParser();
|
||||
const registerSlashCommand = SlashCommandParser.addCommand.bind(SlashCommandParser);
|
||||
const getSlashCommandsHelp = parser.getHelpString.bind(parser);
|
||||
|
||||
/**
|
||||
* Converts a SlashCommandClosure to a filter function that returns a boolean.
|
||||
* @param {SlashCommandClosure} closure
|
||||
* @returns {() => Promise<boolean>}
|
||||
*/
|
||||
function closureToFilter(closure) {
|
||||
return async () => {
|
||||
try {
|
||||
const localClosure = closure.getCopy();
|
||||
localClosure.onProgress = () => { };
|
||||
const result = await localClosure.execute();
|
||||
return isTrueBoolean(result.pipe);
|
||||
} catch (e) {
|
||||
console.error('Error executing filter closure', e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function initDefaultSlashCommands() {
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: '?',
|
||||
@ -1611,6 +1630,13 @@ export function initDefaultSlashCommands() {
|
||||
new SlashCommandNamedArgument(
|
||||
'ephemeral', 'remove injection after generation', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false',
|
||||
),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'filter',
|
||||
description: 'if a filter is defined, an injection will only be performed if the closure returns true',
|
||||
typeList: [ARGUMENT_TYPE.CLOSURE],
|
||||
isRequired: false,
|
||||
acceptsMultiple: false,
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
@ -1892,6 +1918,52 @@ export function initDefaultSlashCommands() {
|
||||
],
|
||||
helpString: 'Converts the provided string to lowercase.',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'substr',
|
||||
aliases: ['substring'],
|
||||
callback: (arg, text) => typeof text === 'string' ? text.slice(...[Number(arg.start), arg.end && Number(arg.end)]) : '',
|
||||
returns: 'substring',
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument(
|
||||
'start', 'start index', [ARGUMENT_TYPE.NUMBER], false, false,
|
||||
),
|
||||
new SlashCommandNamedArgument(
|
||||
'end', 'end index', [ARGUMENT_TYPE.NUMBER], false, false,
|
||||
),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'string', [ARGUMENT_TYPE.STRING], true, false,
|
||||
),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
Extracts text from the provided string.
|
||||
</div>
|
||||
<div>
|
||||
If <code>start</code> is omitted, it's treated as 0.<br />
|
||||
If <code>start</code> < 0, the index is counted from the end of the string.<br />
|
||||
If <code>start</code> >= the string's length, an empty string is returned.<br />
|
||||
If <code>end</code> is omitted, or if <code>end</code> >= the string's length, extracts to the end of the string.<br />
|
||||
If <code>end</code> < 0, the index is counted from the end of the string.<br />
|
||||
If <code>end</code> <= <code>start</code> after normalizing negative values, an empty string is returned.
|
||||
</div>
|
||||
<div>
|
||||
<strong>Example:</strong>
|
||||
<pre>/let x The morning is upon us. || </pre>
|
||||
<pre>/substr start=-3 {{var::x}} | /echo |/# us. ||</pre>
|
||||
<pre>/substr start=-3 end=-1 {{var::x}} | /echo |/# us ||</pre>
|
||||
<pre>/substr end=-1 {{var::x}} | /echo |/# The morning is upon us ||</pre>
|
||||
<pre>/substr start=4 end=-1 {{var::x}} | /echo |/# morning is upon us ||</pre>
|
||||
</div>
|
||||
`,
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'is-mobile',
|
||||
callback: () => String(isMobile()),
|
||||
returns: ARGUMENT_TYPE.BOOLEAN,
|
||||
helpString: 'Returns true if the current device is a mobile device, false otherwise. Equivalent to <code>{{isMobile}}</code> macro.',
|
||||
}));
|
||||
|
||||
registerVariableCommands();
|
||||
}
|
||||
@ -1901,6 +1973,11 @@ const NARRATOR_NAME_DEFAULT = 'System';
|
||||
export const COMMENT_NAME_DEFAULT = 'Note';
|
||||
const SCRIPT_PROMPT_KEY = 'script_inject_';
|
||||
|
||||
/**
|
||||
* Adds a new script injection to the chat.
|
||||
* @param {import('./slash-commands/SlashCommand.js').NamedArguments} args Named arguments
|
||||
* @param {import('./slash-commands/SlashCommand.js').UnnamedArguments} value Unnamed argument
|
||||
*/
|
||||
function injectCallback(args, value) {
|
||||
const positions = {
|
||||
'before': extension_prompt_types.BEFORE_PROMPT,
|
||||
@ -1914,8 +1991,8 @@ function injectCallback(args, value) {
|
||||
'assistant': extension_prompt_roles.ASSISTANT,
|
||||
};
|
||||
|
||||
const id = args?.id;
|
||||
const ephemeral = isTrueBoolean(args?.ephemeral);
|
||||
const id = String(args?.id);
|
||||
const ephemeral = isTrueBoolean(String(args?.ephemeral));
|
||||
|
||||
if (!id) {
|
||||
console.warn('WARN: No ID provided for /inject command');
|
||||
@ -1931,9 +2008,15 @@ function injectCallback(args, value) {
|
||||
const depth = isNaN(depthValue) ? defaultDepth : depthValue;
|
||||
const roleValue = typeof args?.role === 'string' ? args.role.toLowerCase().trim() : Number(args?.role ?? extension_prompt_roles.SYSTEM);
|
||||
const role = roles[roleValue] ?? roles[extension_prompt_roles.SYSTEM];
|
||||
const scan = isTrueBoolean(args?.scan);
|
||||
const scan = isTrueBoolean(String(args?.scan));
|
||||
const filter = args?.filter instanceof SlashCommandClosure ? args.filter.rawText : null;
|
||||
const filterFunction = args?.filter instanceof SlashCommandClosure ? closureToFilter(args.filter) : null;
|
||||
value = value || '';
|
||||
|
||||
if (args?.filter && !String(filter ?? '').trim()) {
|
||||
throw new Error('Failed to parse the filter argument. Make sure it is a valid non-empty closure.');
|
||||
}
|
||||
|
||||
const prefixedId = `${SCRIPT_PROMPT_KEY}${id}`;
|
||||
|
||||
if (!chat_metadata.script_injects) {
|
||||
@ -1941,13 +2024,13 @@ function injectCallback(args, value) {
|
||||
}
|
||||
|
||||
if (value) {
|
||||
const inject = { value, position, depth, scan, role };
|
||||
const inject = { value, position, depth, scan, role, filter };
|
||||
chat_metadata.script_injects[id] = inject;
|
||||
} else {
|
||||
delete chat_metadata.script_injects[id];
|
||||
}
|
||||
|
||||
setExtensionPrompt(prefixedId, value, position, depth, scan, role);
|
||||
setExtensionPrompt(prefixedId, String(value), position, depth, scan, role, filterFunction);
|
||||
saveMetadataDebounced();
|
||||
|
||||
if (ephemeral) {
|
||||
@ -1958,7 +2041,7 @@ function injectCallback(args, value) {
|
||||
}
|
||||
console.log('Removing ephemeral script injection', id);
|
||||
delete chat_metadata.script_injects[id];
|
||||
setExtensionPrompt(prefixedId, '', position, depth, scan, role);
|
||||
setExtensionPrompt(prefixedId, '', position, depth, scan, role, filterFunction);
|
||||
saveMetadataDebounced();
|
||||
deleted = true;
|
||||
};
|
||||
@ -2053,9 +2136,28 @@ export function processChatSlashCommands() {
|
||||
}
|
||||
|
||||
for (const [id, inject] of Object.entries(context.chatMetadata.script_injects)) {
|
||||
/**
|
||||
* Rehydrates a filter closure from a string.
|
||||
* @returns {SlashCommandClosure | null}
|
||||
*/
|
||||
function reviveFilterClosure() {
|
||||
if (!inject.filter) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return new SlashCommandParser().parse(inject.filter, true);
|
||||
} catch (error) {
|
||||
console.warn('Failed to revive filter closure for script injection', id, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const prefixedId = `${SCRIPT_PROMPT_KEY}${id}`;
|
||||
const filterClosure = reviveFilterClosure();
|
||||
const filter = filterClosure ? closureToFilter(filterClosure) : null;
|
||||
console.log('Adding script injection', id);
|
||||
setExtensionPrompt(prefixedId, inject.value, inject.position, inject.depth, inject.scan, inject.role);
|
||||
setExtensionPrompt(prefixedId, inject.value, inject.position, inject.depth, inject.scan, inject.role, filter);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3687,6 +3789,7 @@ function setBackgroundCallback(_, bg) {
|
||||
function getModelOptions(quiet) {
|
||||
const nullResult = { control: null, options: null };
|
||||
const modelSelectMap = [
|
||||
{ id: 'generic_model_textgenerationwebui', api: 'textgenerationwebui', type: textgen_types.GENERIC },
|
||||
{ id: 'custom_model_textgenerationwebui', api: 'textgenerationwebui', type: textgen_types.OOBA },
|
||||
{ id: 'model_togetherai_select', api: 'textgenerationwebui', type: textgen_types.TOGETHERAI },
|
||||
{ id: 'openrouter_model', api: 'textgenerationwebui', type: textgen_types.OPENROUTER },
|
||||
@ -3712,6 +3815,7 @@ function getModelOptions(quiet) {
|
||||
{ id: 'model_nanogpt_select', api: 'openai', type: chat_completion_sources.NANOGPT },
|
||||
{ id: 'model_01ai_select', api: 'openai', type: chat_completion_sources.ZEROONEAI },
|
||||
{ id: 'model_blockentropy_select', api: 'openai', type: chat_completion_sources.BLOCKENTROPY },
|
||||
{ id: 'model_deepseek_select', api: 'openai', type: chat_completion_sources.DEEPSEEK },
|
||||
{ id: 'model_novel_select', api: 'novel', type: null },
|
||||
{ id: 'horde_model', api: 'koboldhorde', type: null },
|
||||
];
|
||||
|
@ -144,9 +144,14 @@ async function* parseStreamData(json) {
|
||||
for (let j = 0; j < json.candidates[i].content.parts.length; j++) {
|
||||
if (typeof json.candidates[i].content.parts[j].text === 'string') {
|
||||
for (let k = 0; k < json.candidates[i].content.parts[j].text.length; k++) {
|
||||
const str = json.candidates[i].content.parts[j].text[k];
|
||||
const moreThanOnePart = json.candidates[i].content.parts.length > 1;
|
||||
const isNotLastPart = j !== json.candidates[i].content.parts.length - 1;
|
||||
const isLastSymbol = k === json.candidates[i].content.parts[j].text.length - 1;
|
||||
const addNewline = moreThanOnePart && isNotLastPart && isLastSymbol;
|
||||
const str = json.candidates[i].content.parts[j].text[k] + (addNewline ? '\n\n' : '');
|
||||
const candidateClone = structuredClone(json.candidates[0]);
|
||||
candidateClone.content.parts[j].text = str;
|
||||
candidateClone.content.parts = [candidateClone.content.parts[j]];
|
||||
const candidates = [candidateClone];
|
||||
yield {
|
||||
data: { ...json, candidates },
|
||||
|
@ -63,7 +63,7 @@ import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '
|
||||
import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
|
||||
import { tag_map, tags } from './tags.js';
|
||||
import { textgenerationwebui_settings } from './textgen-settings.js';
|
||||
import { getTokenCount, getTokenCountAsync, getTokenizerModel } from './tokenizers.js';
|
||||
import { tokenizers, getTextTokens, getTokenCount, getTokenCountAsync, getTokenizerModel } from './tokenizers.js';
|
||||
import { ToolManager } from './tool-calling.js';
|
||||
import { timestampToMoment } from './utils.js';
|
||||
|
||||
@ -95,6 +95,8 @@ export function getContext() {
|
||||
sendStreamingRequest,
|
||||
sendGenerationRequest,
|
||||
stopGeneration,
|
||||
tokenizers,
|
||||
getTextTokens,
|
||||
/** @deprecated Use getTokenCountAsync instead */
|
||||
getTokenCount,
|
||||
getTokenCountAsync,
|
||||
|
@ -1,5 +1,5 @@
|
||||
<div class="flex-container">
|
||||
<h3>
|
||||
<h3 data-i18n="Enter a new display name:">
|
||||
Enter a new display name:
|
||||
</h3>
|
||||
</div>
|
||||
|
18
public/scripts/templates/chatLorebook.html
Normal file
18
public/scripts/templates/chatLorebook.html
Normal file
@ -0,0 +1,18 @@
|
||||
<div class="chat_world range-block flexFlowColumn flex-container">
|
||||
<div class="range-block-title">
|
||||
<h4 data-i18n="Chat Lorebook"><!-- This data-i18n attribute is kept for backward compatibility, use the ones below when translating -->
|
||||
<span data-i18n="Chat Lorebook for">Chat Lorebook for</span> <span class="chat_name"></span>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="range-block-counter justifyLeft flex-container flexFlowColumn margin-bot-10px">
|
||||
<span data-i18n="chat_world_template_txt">
|
||||
A selected World Info will be bound to this chat. When generating an AI reply,
|
||||
it will be combined with the entries from global and character lorebooks.
|
||||
</span>
|
||||
</div>
|
||||
<div class="range-block-range wide100p">
|
||||
<select class="chat_world_info_selector wide100p">
|
||||
<option value="">--- None ---</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
@ -1,8 +1,8 @@
|
||||
<div>
|
||||
<span class="margin-right-10px">Enter Checkpoint Name:</span><small>(Leave empty to auto-generate)</small>
|
||||
<span class="margin-right-10px" data-i18n="Enter Checkpoint Name:">Enter Checkpoint Name:</span><small data-i18n="(Leave empty to auto-generate)">(Leave empty to auto-generate)</small>
|
||||
</div>
|
||||
{{#if isReplace}}
|
||||
<div class="m-t-1">
|
||||
<small>The currently existing checkpoint will be unlinked and replaced with the new checkpoint, but can still be found in the Chat Management.</small>
|
||||
<small data-i18n="The currently existing checkpoint will be unlinked and replaced with the new checkpoint, but can still be found in the Chat Management.">The currently existing checkpoint will be unlinked and replaced with the new checkpoint, but can still be found in the Chat Management.</small>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
@ -1,6 +1,6 @@
|
||||
<div>
|
||||
<h3 data-i18n="Debug Menu">Debug Menu</h3>
|
||||
<div data-i18n="Debug Warning">
|
||||
<div data-i18n="Functions in this category are for advanced users only. Don't click anything if you're not sure about the consequences.">
|
||||
Functions in this category are for advanced users only. Don't click anything if you're not sure about the consequences.
|
||||
</div>
|
||||
<table id="debug_table" class="responsiveTable">
|
||||
|
@ -1,7 +1,10 @@
|
||||
<h3>Enter the Git URL of the extension to install</h3>
|
||||
<h3 data-i18n="Enter the Git URL of the extension to install">Enter the Git URL of the extension to install</h3>
|
||||
<br>
|
||||
<p><b>Disclaimer:</b> Please be aware that using external extensions can have unintended side effects and may pose
|
||||
security risks. Always make sure you trust the source before importing an extension. We are not responsible for any
|
||||
damage caused by third-party extensions.</p>
|
||||
<p>
|
||||
<b data-i18n="Disclaimer:">Disclaimer:</b>
|
||||
<span data-i18n="Please be aware that using external extensions can have unintended side effects and may pose security risks. Always make sure you trust the source before importing an extension. We are not responsible for any damage caused by third-party extensions.">
|
||||
Please be aware that using external extensions can have unintended side effects and may pose security risks. Always make sure you trust the source before importing an extension. We are not responsible for any damage caused by third-party extensions.
|
||||
</span>
|
||||
</p>
|
||||
<br>
|
||||
<p>Example: <tt> https://github.com/author/extension-name </tt></p>
|
||||
<p><span data-i18n="Example:">Example:</span> <tt> https://github.com/author/extension-name </tt></p>
|
||||
|
@ -1,21 +1,21 @@
|
||||
<h3 class="flex-container justifyCenter alignitemscenter">
|
||||
Prompt Itemization
|
||||
<span data-i18n="Prompt Itemization">Prompt Itemization</span>
|
||||
<div id="showRawPrompt" class="fa-solid fa-square-poll-horizontal menu_button" title="Show Raw Prompt" data-i18n="[title]Show Raw Prompt"></div>
|
||||
<div id="copyPromptToClipboard" class="fa-solid fa-copy menu_button" title="Copy Prompt" data-i18n="[title]Copy Prompt"></div>
|
||||
<div id="diffPrevPrompt" class="fa-solid fa-code-compare menu_button" title="Show Prompt Differences" data-i18n="[title]Show Prompt Differences"></div>
|
||||
</h3>
|
||||
<div>
|
||||
<div>
|
||||
API/Model: {{mainApiFriendlyName}} {{#if apiUsed}}({{apiUsed}}){{/if}} {{#if modelUsed}}– {{modelUsed}}{{/if}}
|
||||
<span data-i18n="API/Model:">API/Model:</span> {{mainApiFriendlyName}} {{#if apiUsed}}({{apiUsed}}){{/if}} {{#if modelUsed}}– {{modelUsed}}{{/if}}
|
||||
</div>
|
||||
<div>
|
||||
<small>Preset: {{presetName}}</small>
|
||||
<small><span data-i18n="Preset:">Preset:</span> {{presetName}}</small>
|
||||
<span>|</span>
|
||||
<small>Tokenizer: {{selectedTokenizer}}</small>
|
||||
<small><span data-i18n="Tokenizer:">Tokenizer:</span> {{selectedTokenizer}}</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span class="tokenItemizingSubclass">
|
||||
<span class="tokenItemizingSubclass" data-i18n="Only the white numbers really matter. All numbers are estimates. Grey color items may not have been included in the context due to certain prompt format settings.">
|
||||
Only the white numbers really matter. All numbers are estimates.
|
||||
Grey color items may not have been included in the context due to certain prompt format settings.
|
||||
</span>
|
||||
@ -37,12 +37,12 @@
|
||||
<div class="flex-container wide50p">
|
||||
<div class="wide100p flex-container flexNoGap flexFlowColumn">
|
||||
<div class="flex-container wide100p">
|
||||
<div class="flex1" style="color: grey;">System Info:</div>
|
||||
<div class=""> {{oaiSystemTokens}}</div>
|
||||
<div class="flex1" style="color: grey;"><span data-i18n="System Info:">System Info:</span></div>
|
||||
<div class="">{{oaiSystemTokens}}</div>
|
||||
</div>
|
||||
<div class="flex-container ">
|
||||
<div class=" flex1 tokenItemizingSubclass">-- Chat Start: </div>
|
||||
<div class="tokenItemizingSubclass"> {{oaiStartTokens}}</div>
|
||||
<div class="tokenItemizingSubclass">{{oaiStartTokens}}</div>
|
||||
</div>
|
||||
<div class="flex-container ">
|
||||
<div class=" flex1 tokenItemizingSubclass">-- Main: </div>
|
||||
@ -67,8 +67,8 @@
|
||||
</div>
|
||||
<div class="wide100p flex-container flexNoGap flexFlowColumn">
|
||||
<div class="flex-container wide100p">
|
||||
<div class="flex1" style="color: indianred;">Prompt Tokens:</div>
|
||||
<div class=""> {{oaiPromptTokens}}</div>
|
||||
<div class="flex1" style="color: indianred;"><span data-i18n="Prompt Tokens:">Prompt Tokens:</span></div>
|
||||
<div class="">{{oaiPromptTokens}}</div>
|
||||
</div>
|
||||
<div class="flex-container ">
|
||||
<div class=" flex1 tokenItemizingSubclass">-- Description: </div>
|
||||
@ -76,7 +76,7 @@
|
||||
</div>
|
||||
<div class="flex-container ">
|
||||
<div class=" flex1 tokenItemizingSubclass">-- Personality:</div>
|
||||
<div class="tokenItemizingSubclass"> {{charPersonalityTokens}}</div>
|
||||
<div class="tokenItemizingSubclass">{{charPersonalityTokens}}</div>
|
||||
</div>
|
||||
<div class="flex-container ">
|
||||
<div class=" flex1 tokenItemizingSubclass">-- Scenario: </div>
|
||||
@ -84,24 +84,24 @@
|
||||
</div>
|
||||
<div class="flex-container ">
|
||||
<div class=" flex1 tokenItemizingSubclass">-- Examples:</div>
|
||||
<div class="tokenItemizingSubclass"> {{examplesStringTokens}}</div>
|
||||
<div class="tokenItemizingSubclass">{{examplesStringTokens}}</div>
|
||||
</div>
|
||||
<div class="flex-container ">
|
||||
<div class=" flex1 tokenItemizingSubclass">-- User Persona:</div>
|
||||
<div class="tokenItemizingSubclass"> {{userPersonaStringTokens}}</div>
|
||||
<div class="tokenItemizingSubclass">{{userPersonaStringTokens}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wide100p flex-container">
|
||||
<div class="flex1" style="color: gold;">World Info:</div>
|
||||
<div class="flex1" style="color: gold;"><span data-i18n="World Info:">World Info:</span></div>
|
||||
<div class="">{{worldInfoStringTokens}}</div>
|
||||
</div>
|
||||
<div class="wide100p flex-container">
|
||||
<div class="flex1" style="color: palegreen;">Chat History:</div>
|
||||
<div class=""> {{ActualChatHistoryTokens}}</div>
|
||||
<div class="flex1" style="color: palegreen;"><span data-i18n="Chat History:">Chat History:</span></div>
|
||||
<div class="">{{ActualChatHistoryTokens}}</div>
|
||||
</div>
|
||||
<div class="wide100p flex-container flexNoGap flexFlowColumn">
|
||||
<div class="wide100p flex-container">
|
||||
<div class="flex1" style="color: cornflowerblue;">Extensions:</div>
|
||||
<div class="flex1" style="color: cornflowerblue;"><span data-i18n="Extensions:">Extensions:</span></div>
|
||||
<div class="">{{allAnchorsTokens}}</div>
|
||||
</div>
|
||||
<div class="flex-container ">
|
||||
@ -110,23 +110,23 @@
|
||||
</div>
|
||||
<div class="flex-container ">
|
||||
<div class=" flex1 tokenItemizingSubclass">-- Author's Note:</div>
|
||||
<div class="tokenItemizingSubclass"> {{authorsNoteStringTokens}}</div>
|
||||
<div class="tokenItemizingSubclass">{{authorsNoteStringTokens}}</div>
|
||||
</div>
|
||||
<div class="flex-container ">
|
||||
<div class=" flex1 tokenItemizingSubclass">-- Smart Context:</div>
|
||||
<div class="tokenItemizingSubclass"> {{smartContextStringTokens}}</div>
|
||||
<div class="tokenItemizingSubclass">{{smartContextStringTokens}}</div>
|
||||
</div>
|
||||
<div class="flex-container ">
|
||||
<div class=" flex1 tokenItemizingSubclass">-- Vector Storage (Chats):</div>
|
||||
<div class="tokenItemizingSubclass"> {{chatVectorsStringTokens}}</div>
|
||||
<div class="tokenItemizingSubclass">{{chatVectorsStringTokens}}</div>
|
||||
</div>
|
||||
<div class="flex-container ">
|
||||
<div class=" flex1 tokenItemizingSubclass">-- Vector Storage (Data Bank):</div>
|
||||
<div class="tokenItemizingSubclass"> {{dataBankVectorsStringTokens}}</div>
|
||||
<div class="tokenItemizingSubclass">{{dataBankVectorsStringTokens}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wide100p flex-container">
|
||||
<div class="flex1" style="color: mediumpurple;">{{}} Bias:</div>
|
||||
<div class="flex1" style="color: mediumpurple;"><span>{{}}</span> <span data-i18n="Bias:">Bias:</span></div>
|
||||
<div class="">{{oaiBiasTokens}}</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -135,11 +135,11 @@
|
||||
<hr>
|
||||
<div class="wide100p flex-container flexFlowColumns">
|
||||
<div class="flex-container wide100p">
|
||||
<div class="flex1">Total Tokens in Prompt:</div>
|
||||
<div class=""> {{finalPromptTokens}}</div>
|
||||
<div class="flex1"><span data-i18n="Total Tokens in Prompt:">Total Tokens in Prompt:</span></div>
|
||||
<div class="">{{finalPromptTokens}}</div>
|
||||
</div>
|
||||
<div class="flex-container wide100p">
|
||||
<div class="flex1">Max Context (Context Size - Response Length):</div>
|
||||
<div class="flex1"><span data-i18n="Max Context">Max Context</span> <small data-i18n="(Context Size - Response Length)">(Context Size - Response Length)</small><span data-i18n=":">:</span></div>
|
||||
<div class="">{{thisPrompt_max_context}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -48,6 +48,7 @@
|
||||
<li><tt>{{random::(arg1)::(arg2)}}</tt> – <span data-i18n="help_macros_38">alternative syntax for random that allows to use commas in the list items.</span></li>
|
||||
<li><tt>{{pick::(args)}}</tt> – <span data-i18n="help_macros_39">picks a random item from the list. Works the same as {{random}}, with the same possible syntax options, but the pick will stay consistent for this chat once picked and won't be re-rolled on consecutive messages and prompt processing.</span></li>
|
||||
<li><tt>{{banned "text here"}}</tt> – <span data-i18n="help_macros_40">dynamically add text in the quotes to banned words sequences, if Text Generation WebUI backend used. Do nothing for others backends. Can be used anywhere (Character description, WI, AN, etc.) Quotes around the text are important.</span></li>
|
||||
<li><tt>{{isMobile}}</tt> – <span data-i18n="help_macros_isMobile">"true" if currently running in a mobile environment, "false" otherwise</span></li>
|
||||
</ul>
|
||||
<div data-i18n="Instruct Mode and Context Template Macros:">
|
||||
Instruct Mode and Context Template Macros:
|
||||
|
@ -1,4 +1,4 @@
|
||||
<h3>
|
||||
<h3 data-i18n="Choose what to export">
|
||||
Choose what to export
|
||||
</h3>
|
||||
<div class="flex-container flexFlowColumn justifyLeft">
|
||||
|
@ -1,4 +1,4 @@
|
||||
<h3>
|
||||
<h3 data-i18n="Choose what to import">
|
||||
Choose what to import
|
||||
</h3>
|
||||
<div class="flex-container flexFlowColumn justifyLeft">
|
||||
|
18
public/scripts/templates/personaLorebook.html
Normal file
18
public/scripts/templates/personaLorebook.html
Normal file
@ -0,0 +1,18 @@
|
||||
<div class="persona_world range-block flexFlowColumn flex-container">
|
||||
<div class="range-block-title">
|
||||
<h4>
|
||||
<span data-i18n="Persona Lorebook for">Persona Lorebook for</span> <span class="persona_name"></span>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="range-block-counter justifyLeft flex-container flexFlowColumn margin-bot-10px">
|
||||
<span data-i18n="persona_world_template_txt">
|
||||
A selected World Info will be bound to this persona. When generating an AI reply,
|
||||
it will be combined with the entries from global, character and chat lorebooks.
|
||||
</span>
|
||||
</div>
|
||||
<div class="range-block-range wide100p">
|
||||
<select class="persona_world_info_selector wide100p">
|
||||
<option value="">--- None ---</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
@ -6,7 +6,7 @@
|
||||
Don't forget to save a snapshot of your settings before proceeding.
|
||||
</div>
|
||||
<hr>
|
||||
<div>
|
||||
<div data-i18n="Enter your password below to confirm:">
|
||||
Enter your password below to confirm:
|
||||
</div>
|
||||
<input id="resetSettingsPassword" name="password" type="password" class="text_pole" placeholder="Password">
|
||||
|
@ -5,10 +5,10 @@
|
||||
class="menu_button fa-solid fa-trash-can remove_scenario_override"></div>
|
||||
</div>
|
||||
<div class="range-block-counter justifyLeft flex-container flexFlowColumn">
|
||||
<strong>Unique to this chat.</strong>
|
||||
<span data-group="true">All group members will use the following scenario text instead of what is specified in their character cards.</span>
|
||||
<span data-character="true">The following scenario text will be used instead of the value set in the character card.</span>
|
||||
Checkpoints inherit the scenario override from their parent, and can be changed individually after that.
|
||||
<strong data-i18n="Unique to this chat.">Unique to this chat.</strong>
|
||||
<span data-group="true" data-i18n="All group members will use the following scenario text instead of what is specified in their character cards.">All group members will use the following scenario text instead of what is specified in their character cards.</span>
|
||||
<span data-character="true" data-i18n="The following scenario text will be used instead of the value set in the character card.">The following scenario text will be used instead of the value set in the character card.</span>
|
||||
<span data-i18n="Checkpoints inherit the scenario override from their parent, and can be changed individually after that.">Checkpoints inherit the scenario override from their parent, and can be changed individually after that.</span>
|
||||
</div>
|
||||
<div class="range-block-range wide100p">
|
||||
<textarea class="wide100p chat_scenario" class="text_pole" rows="15" data-i18n="[placeholder]Type here..."
|
||||
|
@ -20,14 +20,14 @@
|
||||
<small>
|
||||
<span data-i18n="Drag handle to reorder. Click name to rename. Click color to change display.">Drag handle to reorder. Click name to rename. Click color to change display.</span><br>
|
||||
{{#if bogus_folders}}<span data-i18n="Click on the folder icon to use this tag as a folder.">Click on the folder icon to use this tag as a folder.</span><br>{{/if}}
|
||||
<label class="checkbox flex-container alignitemscenter flexNoGap m-t-1" for="auto_sort_tags">
|
||||
<label class="checkbox_label flex-container alignItemsCenter m-t-1" for="auto_sort_tags">
|
||||
<input type="checkbox" id="auto_sort_tags" name="auto_sort_tags" {{#if auto_sort_tags}} checked{{/if}} />
|
||||
<span data-i18n="Use alphabetical sorting">
|
||||
Use alphabetical sorting
|
||||
</span>
|
||||
<div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]tags_sorting_desc"
|
||||
title="If enabled, tags will automatically be sorted alphabetically on creation or rename.\nIf disabled, new tags will be appended at the end.\n\nIf a tag is manually reordered by dragging, automatic sorting will be disabled.">
|
||||
</div>
|
||||
<div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]tags_sorting_desc"
|
||||
title="If enabled, tags will automatically be sorted alphabetically on creation or rename. If disabled, new tags will be appended at the end. If a tag is manually reordered by dragging, automatic sorting will be disabled.">
|
||||
</div>
|
||||
</label>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,3 +1,4 @@
|
||||
<div>
|
||||
Are you sure you want to delete the theme "{{themeName}}"?
|
||||
<div data-i18n="Are you sure you want to delete the theme?">Are you sure you want to delete the theme?</div>
|
||||
<b>"{{themeName}}"<b>
|
||||
</div>
|
||||
|
@ -1,12 +1,13 @@
|
||||
<form action="javascript:void(0);" class="flex-container flexFlowColumn">
|
||||
<h3 class="neutral_warning">
|
||||
<h3 class="neutral_warning" data-i18n="This will delete all your settings and data. There will be no undo button. Make sure you have a backup before proceeding.">
|
||||
This will delete all your settings and data. There will be no undo button.
|
||||
Make sure you have a backup before proceeding.
|
||||
</h3>
|
||||
<hr>
|
||||
<div>
|
||||
<div data-i18n="Account reset code has been posted to the server console.">
|
||||
Account reset code has been posted to the server console.
|
||||
</div>
|
||||
<br>
|
||||
<div class="currentPasswordBlock">
|
||||
<label data-i18n="Current Password:" for="user">Current Password:</label>
|
||||
<input type="password" name="password" class="text_pole" placeholder="[ No password ]" autocomplete="current-password">
|
||||
|
@ -1 +1 @@
|
||||
<div id="extensionsMenuButton" style="display: none;" class="fa-solid fa-magic-wand-sparkles interactable" title="Extensions" /></div>
|
||||
<div id="extensionsMenuButton" style="display: none;" class="fa-solid fa-magic-wand-sparkles interactable" title="Extensions" data-i18n="[title]Extensions" /></div>
|
||||
|
@ -25,6 +25,7 @@ const OPENROUTER_PROVIDERS = [
|
||||
'Anthropic',
|
||||
'Google',
|
||||
'Google AI Studio',
|
||||
'Amazon Bedrock',
|
||||
'Groq',
|
||||
'SambaNova',
|
||||
'Cohere',
|
||||
@ -50,6 +51,8 @@ const OPENROUTER_PROVIDERS = [
|
||||
'Featherless',
|
||||
'Inflection',
|
||||
'xAI',
|
||||
'Cloudflare',
|
||||
'SF Compute',
|
||||
'01.AI',
|
||||
'HuggingFace',
|
||||
'Mancer',
|
||||
@ -160,6 +163,24 @@ export async function loadInfermaticAIModels(data) {
|
||||
}
|
||||
}
|
||||
|
||||
export function loadGenericModels(data) {
|
||||
if (!Array.isArray(data)) {
|
||||
console.error('Invalid Generic models data', data);
|
||||
return;
|
||||
}
|
||||
|
||||
data.sort((a, b) => a.id.localeCompare(b.id));
|
||||
const dataList = $('#generic_model_fill');
|
||||
dataList.empty();
|
||||
|
||||
for (const model of data) {
|
||||
const option = document.createElement('option');
|
||||
option.value = model.id;
|
||||
option.text = model.id;
|
||||
dataList.append(option);
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadDreamGenModels(data) {
|
||||
if (!Array.isArray(data)) {
|
||||
console.error('Invalid DreamGen models data', data);
|
||||
|
@ -16,7 +16,7 @@ import { power_user, registerDebugFunction } from './power-user.js';
|
||||
import { getEventSourceStream } from './sse-stream.js';
|
||||
import { getCurrentDreamGenModelTokenizer, getCurrentOpenRouterModelTokenizer } from './textgen-models.js';
|
||||
import { ENCODE_TOKENIZERS, TEXTGEN_TOKENIZERS, getTextTokens, tokenizers } from './tokenizers.js';
|
||||
import { getSortableDelay, onlyUnique } from './utils.js';
|
||||
import { getSortableDelay, onlyUnique, arraysEqual } from './utils.js';
|
||||
|
||||
export const textgen_types = {
|
||||
OOBA: 'ooba',
|
||||
@ -33,9 +33,11 @@ export const textgen_types = {
|
||||
OPENROUTER: 'openrouter',
|
||||
FEATHERLESS: 'featherless',
|
||||
HUGGINGFACE: 'huggingface',
|
||||
GENERIC: 'generic',
|
||||
};
|
||||
|
||||
const {
|
||||
GENERIC,
|
||||
MANCER,
|
||||
VLLM,
|
||||
APHRODITE,
|
||||
@ -53,6 +55,7 @@ const {
|
||||
} = textgen_types;
|
||||
|
||||
const LLAMACPP_DEFAULT_ORDER = [
|
||||
'dry',
|
||||
'top_k',
|
||||
'tfs_z',
|
||||
'typical_p',
|
||||
@ -82,6 +85,22 @@ const OOBA_DEFAULT_ORDER = [
|
||||
'encoder_repetition_penalty',
|
||||
'no_repeat_ngram',
|
||||
];
|
||||
const APHRODITE_DEFAULT_ORDER = [
|
||||
'dry',
|
||||
'penalties',
|
||||
'no_repeat_ngram',
|
||||
'temperature',
|
||||
'top_nsigma',
|
||||
'top_p_top_k',
|
||||
'top_a',
|
||||
'min_p',
|
||||
'tfs',
|
||||
'eta_cutoff',
|
||||
'epsilon_cutoff',
|
||||
'typical_p',
|
||||
'quadratic',
|
||||
'xtc',
|
||||
];
|
||||
const BIAS_KEY = '#textgenerationwebui_api-settings';
|
||||
|
||||
// Maybe let it be configurable in the future?
|
||||
@ -104,6 +123,7 @@ export const SERVER_INPUTS = {
|
||||
[textgen_types.LLAMACPP]: '#llamacpp_api_url_text',
|
||||
[textgen_types.OLLAMA]: '#ollama_api_url_text',
|
||||
[textgen_types.HUGGINGFACE]: '#huggingface_api_url_text',
|
||||
[textgen_types.GENERIC]: '#generic_api_url_text',
|
||||
};
|
||||
|
||||
const KOBOLDCPP_ORDER = [6, 0, 1, 3, 4, 2, 5];
|
||||
@ -163,6 +183,7 @@ const settings = {
|
||||
banned_tokens: '',
|
||||
sampler_priority: OOBA_DEFAULT_ORDER,
|
||||
samplers: LLAMACPP_DEFAULT_ORDER,
|
||||
samplers_priorities: APHRODITE_DEFAULT_ORDER,
|
||||
ignore_eos_token: false,
|
||||
spaces_between_special_tokens: true,
|
||||
speculative_ngram: false,
|
||||
@ -188,6 +209,7 @@ const settings = {
|
||||
xtc_probability: 0,
|
||||
nsigma: 0.0,
|
||||
featherless_model: '',
|
||||
generic_model: '',
|
||||
};
|
||||
|
||||
export {
|
||||
@ -256,6 +278,7 @@ export const setting_names = [
|
||||
'sampler_order',
|
||||
'sampler_priority',
|
||||
'samplers',
|
||||
'samplers_priorities',
|
||||
'n',
|
||||
'logit_bias',
|
||||
'custom_model',
|
||||
@ -264,6 +287,7 @@ export const setting_names = [
|
||||
'xtc_threshold',
|
||||
'xtc_probability',
|
||||
'nsigma',
|
||||
'generic_model',
|
||||
];
|
||||
|
||||
const DYNATEMP_BLOCK = document.getElementById('dynatemp_block_ooba');
|
||||
@ -553,6 +577,20 @@ function sortOobaItemsByOrder(orderArray) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the Aphrodite sampler items by the given order.
|
||||
* @param {string[]} orderArray Sampler order array.
|
||||
*/
|
||||
function sortAphroditeItemsByOrder(orderArray) {
|
||||
console.debug('Preset samplers order: ', orderArray);
|
||||
const $container = $('#sampler_priority_container_aphrodite');
|
||||
|
||||
orderArray.forEach((name) => {
|
||||
const $item = $container.find(`[data-name="${name}"]`).detach();
|
||||
$container.append($item);
|
||||
});
|
||||
}
|
||||
|
||||
jQuery(function () {
|
||||
$('#koboldcpp_order').sortable({
|
||||
delay: getSortableDelay(),
|
||||
@ -606,6 +644,19 @@ jQuery(function () {
|
||||
},
|
||||
});
|
||||
|
||||
$('#sampler_priority_container_aphrodite').sortable({
|
||||
delay: getSortableDelay(),
|
||||
stop: function () {
|
||||
const order = [];
|
||||
$('#sampler_priority_container_aphrodite').children().each(function () {
|
||||
order.push($(this).data('name'));
|
||||
});
|
||||
settings.samplers_priorities = order;
|
||||
console.log('Samplers reordered:', settings.samplers_priorities);
|
||||
saveSettingsDebounced();
|
||||
},
|
||||
});
|
||||
|
||||
$('#tabby_json_schema').on('input', function () {
|
||||
const json_schema_string = String($(this).val());
|
||||
|
||||
@ -624,6 +675,13 @@ jQuery(function () {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#aphrodite_default_order').on('click', function () {
|
||||
sortAphroditeItemsByOrder(APHRODITE_DEFAULT_ORDER);
|
||||
settings.samplers_priorities = APHRODITE_DEFAULT_ORDER;
|
||||
console.log('Default samplers order loaded:', settings.samplers_priorities);
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#textgen_type').on('change', function () {
|
||||
const type = String($(this).val());
|
||||
settings.type = type;
|
||||
@ -781,7 +839,14 @@ jQuery(function () {
|
||||
|
||||
function showTypeSpecificControls(type) {
|
||||
$('[data-tg-type]').each(function () {
|
||||
const mode = String($(this).attr('data-tg-type-mode') ?? '').toLowerCase().trim();
|
||||
const tgTypes = $(this).attr('data-tg-type').split(',').map(x => x.trim());
|
||||
|
||||
if (mode === 'except') {
|
||||
$(this)[tgTypes.includes(type) ? 'hide' : 'show']();
|
||||
return;
|
||||
}
|
||||
|
||||
for (const tgType of tgTypes) {
|
||||
if (tgType === type || tgType == 'all') {
|
||||
$(this).show();
|
||||
@ -832,6 +897,14 @@ function setSettingByName(setting, value, trigger) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ('samplers_priorities' === setting) {
|
||||
value = Array.isArray(value) ? value : APHRODITE_DEFAULT_ORDER;
|
||||
insertMissingArrayItems(APHRODITE_DEFAULT_ORDER, value);
|
||||
sortAphroditeItemsByOrder(value);
|
||||
settings.samplers_priorities = value;
|
||||
return;
|
||||
}
|
||||
|
||||
if ('samplers' === setting) {
|
||||
value = Array.isArray(value) ? value : LLAMACPP_DEFAULT_ORDER;
|
||||
insertMissingArrayItems(LLAMACPP_DEFAULT_ORDER, value);
|
||||
@ -966,12 +1039,30 @@ export function parseTextgenLogprobs(token, logprobs) {
|
||||
return { token, topLogprobs: candidates };
|
||||
}
|
||||
case LLAMACPP: {
|
||||
/** @type {Record<string, number>[]} */
|
||||
if (!logprobs?.length) {
|
||||
return null;
|
||||
}
|
||||
const candidates = logprobs[0].probs.map(x => [x.tok_str, x.prob]);
|
||||
return { token, topLogprobs: candidates };
|
||||
|
||||
// 3 cases:
|
||||
// 1. Before commit 6c5bc06, "probs" key with "tok_str"/"prob", and probs are [0, 1] so use them directly.
|
||||
// 2. After commit 6c5bc06 but before commit 89d604f broke logprobs (they all return the first token's logprobs)
|
||||
// We don't know the llama.cpp version so we can't do much about this.
|
||||
// 3. After commit 89d604f uses OpenAI-compatible format with "completion_probabilities" and "token"/"logprob" keys.
|
||||
// Note that it is also the *actual* logprob (negative number), so we need to convert to [0, 1].
|
||||
if (logprobs?.[0]?.probs) {
|
||||
const candidates = logprobs?.[0]?.probs?.map(x => [x.tok_str, x.prob]);
|
||||
if (!candidates) {
|
||||
return null;
|
||||
}
|
||||
return { token, topLogprobs: candidates };
|
||||
} else if (logprobs?.[0].top_logprobs) {
|
||||
const candidates = logprobs?.[0]?.top_logprobs?.map(x => [x.token, Math.exp(x.logprob)]);
|
||||
if (!candidates) {
|
||||
return null;
|
||||
}
|
||||
return { token, topLogprobs: candidates };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
@ -1040,6 +1131,11 @@ export function getTextGenModel() {
|
||||
return settings.custom_model;
|
||||
}
|
||||
break;
|
||||
case GENERIC:
|
||||
if (settings.generic_model) {
|
||||
return settings.generic_model;
|
||||
}
|
||||
break;
|
||||
case MANCER:
|
||||
return settings.mancer_model;
|
||||
case TOGETHERAI:
|
||||
@ -1256,6 +1352,11 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate,
|
||||
'nsigma': settings.nsigma,
|
||||
'custom_token_bans': toIntArray(banned_tokens),
|
||||
'no_repeat_ngram_size': settings.no_repeat_ngram_size,
|
||||
'sampler_priority': settings.type === APHRODITE && !arraysEqual(
|
||||
settings.samplers_priorities,
|
||||
APHRODITE_DEFAULT_ORDER)
|
||||
? settings.samplers_priorities
|
||||
: undefined,
|
||||
};
|
||||
|
||||
if (settings.type === OPENROUTER) {
|
||||
|
@ -31,6 +31,7 @@ export const tokenizers = {
|
||||
QWEN2: 15,
|
||||
COMMAND_R: 16,
|
||||
NEMO: 17,
|
||||
DEEPSEEK: 18,
|
||||
BEST_MATCH: 99,
|
||||
};
|
||||
|
||||
@ -45,6 +46,7 @@ export const ENCODE_TOKENIZERS = [
|
||||
tokenizers.QWEN2,
|
||||
tokenizers.COMMAND_R,
|
||||
tokenizers.NEMO,
|
||||
tokenizers.DEEPSEEK,
|
||||
// uncomment when NovelAI releases Kayra and Clio weights, lol
|
||||
//tokenizers.NERD,
|
||||
//tokenizers.NERD2,
|
||||
@ -132,6 +134,11 @@ const TOKENIZER_URLS = {
|
||||
decode: '/api/tokenizers/nemo/decode',
|
||||
count: '/api/tokenizers/nemo/encode',
|
||||
},
|
||||
[tokenizers.DEEPSEEK]: {
|
||||
encode: '/api/tokenizers/deepseek/encode',
|
||||
decode: '/api/tokenizers/deepseek/decode',
|
||||
count: '/api/tokenizers/deepseek/encode',
|
||||
},
|
||||
[tokenizers.API_TEXTGENERATIONWEBUI]: {
|
||||
encode: '/api/tokenizers/remote/textgenerationwebui/encode',
|
||||
count: '/api/tokenizers/remote/textgenerationwebui/encode',
|
||||
@ -536,7 +543,6 @@ export function getTokenizerModel() {
|
||||
return oai_settings.openai_model;
|
||||
}
|
||||
|
||||
const turbo0301Tokenizer = 'gpt-3.5-turbo-0301';
|
||||
const turboTokenizer = 'gpt-3.5-turbo';
|
||||
const gpt4Tokenizer = 'gpt-4';
|
||||
const gpt4oTokenizer = 'gpt-4o';
|
||||
@ -551,20 +557,22 @@ export function getTokenizerModel() {
|
||||
const qwen2Tokenizer = 'qwen2';
|
||||
const commandRTokenizer = 'command-r';
|
||||
const nemoTokenizer = 'nemo';
|
||||
const deepseekTokenizer = 'deepseek';
|
||||
|
||||
// Assuming no one would use it for different models.. right?
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.SCALE) {
|
||||
return gpt4Tokenizer;
|
||||
}
|
||||
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK) {
|
||||
return deepseekTokenizer;
|
||||
}
|
||||
|
||||
// Select correct tokenizer for WindowAI proxies
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI && oai_settings.windowai_model) {
|
||||
if (oai_settings.windowai_model.includes('gpt-4')) {
|
||||
return gpt4Tokenizer;
|
||||
}
|
||||
else if (oai_settings.windowai_model.includes('gpt-3.5-turbo-0301')) {
|
||||
return turbo0301Tokenizer;
|
||||
}
|
||||
else if (oai_settings.windowai_model.includes('gpt-3.5-turbo')) {
|
||||
return turboTokenizer;
|
||||
}
|
||||
@ -610,9 +618,6 @@ export function getTokenizerModel() {
|
||||
else if (oai_settings.openrouter_model.includes('gpt-4')) {
|
||||
return gpt4Tokenizer;
|
||||
}
|
||||
else if (oai_settings.openrouter_model.includes('gpt-3.5-turbo-0301')) {
|
||||
return turbo0301Tokenizer;
|
||||
}
|
||||
else if (oai_settings.openrouter_model.includes('gpt-3.5-turbo')) {
|
||||
return turboTokenizer;
|
||||
}
|
||||
@ -625,6 +630,9 @@ export function getTokenizerModel() {
|
||||
else if (oai_settings.openrouter_model.includes('jamba')) {
|
||||
return jambaTokenizer;
|
||||
}
|
||||
else if (oai_settings.openrouter_model.includes('deepseek')) {
|
||||
return deepseekTokenizer;
|
||||
}
|
||||
}
|
||||
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.COHERE) {
|
||||
|
@ -25,6 +25,7 @@ import { isTrueBoolean } from './utils.js';
|
||||
* @typedef {object} ToolInvocationResult
|
||||
* @property {ToolInvocation[]} invocations Successful tool invocations
|
||||
* @property {Error[]} errors Errors that occurred during tool invocation
|
||||
* @property {string[]} stealthCalls Names of stealth tools that were invoked
|
||||
*/
|
||||
|
||||
/**
|
||||
@ -36,6 +37,7 @@ import { isTrueBoolean } from './utils.js';
|
||||
* @property {function} action - The action to perform when the tool is invoked.
|
||||
* @property {function} [formatMessage] - A function to format the tool call message.
|
||||
* @property {function} [shouldRegister] - A function to determine if the tool should be registered.
|
||||
* @property {boolean} [stealth] - A tool call result will not be shown in the chat. No follow-up generation will be performed.
|
||||
*/
|
||||
|
||||
/**
|
||||
@ -147,6 +149,12 @@ class ToolDefinition {
|
||||
*/
|
||||
#shouldRegister;
|
||||
|
||||
/**
|
||||
* A tool call result will not be shown in the chat. No follow-up generation will be performed.
|
||||
* @type {boolean}
|
||||
*/
|
||||
#stealth;
|
||||
|
||||
/**
|
||||
* Creates a new ToolDefinition.
|
||||
* @param {string} name A unique name for the tool.
|
||||
@ -156,8 +164,9 @@ class ToolDefinition {
|
||||
* @param {function} action A function that will be called when the tool is executed.
|
||||
* @param {function} formatMessage A function that will be called to format the tool call toast.
|
||||
* @param {function} shouldRegister A function that will be called to determine if the tool should be registered.
|
||||
* @param {boolean} stealth A tool call result will not be shown in the chat. No follow-up generation will be performed.
|
||||
*/
|
||||
constructor(name, displayName, description, parameters, action, formatMessage, shouldRegister) {
|
||||
constructor(name, displayName, description, parameters, action, formatMessage, shouldRegister, stealth) {
|
||||
this.#name = name;
|
||||
this.#displayName = displayName;
|
||||
this.#description = description;
|
||||
@ -165,6 +174,7 @@ class ToolDefinition {
|
||||
this.#action = action;
|
||||
this.#formatMessage = formatMessage;
|
||||
this.#shouldRegister = shouldRegister;
|
||||
this.#stealth = stealth;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -214,6 +224,10 @@ class ToolDefinition {
|
||||
get displayName() {
|
||||
return this.#displayName;
|
||||
}
|
||||
|
||||
get stealth() {
|
||||
return this.#stealth;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -246,7 +260,7 @@ export class ToolManager {
|
||||
* Registers a new tool with the tool registry.
|
||||
* @param {ToolRegistration} tool The tool to register.
|
||||
*/
|
||||
static registerFunctionTool({ name, displayName, description, parameters, action, formatMessage, shouldRegister }) {
|
||||
static registerFunctionTool({ name, displayName, description, parameters, action, formatMessage, shouldRegister, stealth }) {
|
||||
// Convert WIP arguments
|
||||
if (typeof arguments[0] !== 'object') {
|
||||
[name, description, parameters, action] = arguments;
|
||||
@ -256,7 +270,16 @@ export class ToolManager {
|
||||
console.warn(`[ToolManager] A tool with the name "${name}" has already been registered. The definition will be overwritten.`);
|
||||
}
|
||||
|
||||
const definition = new ToolDefinition(name, displayName, description, parameters, action, formatMessage, shouldRegister);
|
||||
const definition = new ToolDefinition(
|
||||
name,
|
||||
displayName,
|
||||
description,
|
||||
parameters,
|
||||
action,
|
||||
formatMessage,
|
||||
shouldRegister,
|
||||
stealth,
|
||||
);
|
||||
this.#tools.set(name, definition);
|
||||
console.log('[ToolManager] Registered function tool:', definition);
|
||||
}
|
||||
@ -302,6 +325,20 @@ export class ToolManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a tool is a stealth tool.
|
||||
* @param {string} name The name of the tool to check.
|
||||
* @returns {boolean} Whether the tool is a stealth tool.
|
||||
*/
|
||||
static isStealthTool(name) {
|
||||
if (!this.#tools.has(name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const tool = this.#tools.get(name);
|
||||
return !!tool.stealth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a message for a tool call by name.
|
||||
* @param {string} name The name of the tool to format the message for.
|
||||
@ -608,6 +645,7 @@ export class ToolManager {
|
||||
const result = {
|
||||
invocations: [],
|
||||
errors: [],
|
||||
stealthCalls: [],
|
||||
};
|
||||
const toolCalls = ToolManager.#getToolCallsFromData(data);
|
||||
|
||||
@ -625,7 +663,7 @@ export class ToolManager {
|
||||
const parameters = toolCall.function.arguments;
|
||||
const name = toolCall.function.name;
|
||||
const displayName = ToolManager.getDisplayName(name);
|
||||
|
||||
const isStealth = ToolManager.isStealthTool(name);
|
||||
const message = await ToolManager.formatToolCallMessage(name, parameters);
|
||||
const toast = message && toastr.info(message, 'Tool Calling', { timeOut: 0 });
|
||||
const toolResult = await ToolManager.invokeFunctionTool(name, parameters);
|
||||
@ -638,6 +676,12 @@ export class ToolManager {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Don't save stealth tool invocations
|
||||
if (isStealth) {
|
||||
result.stealthCalls.push(name);
|
||||
continue;
|
||||
}
|
||||
|
||||
const invocation = {
|
||||
id,
|
||||
displayName,
|
||||
@ -860,6 +904,14 @@ export class ToolManager {
|
||||
isRequired: false,
|
||||
acceptsMultiple: false,
|
||||
}),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'stealth',
|
||||
description: 'If true, a tool call result will not be shown in the chat and no follow-up generation will be performed.',
|
||||
typeList: [ARGUMENT_TYPE.BOOLEAN],
|
||||
isRequired: false,
|
||||
acceptsMultiple: false,
|
||||
defaultValue: String(false),
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({
|
||||
@ -891,7 +943,7 @@ export class ToolManager {
|
||||
};
|
||||
}
|
||||
|
||||
const { name, displayName, description, parameters, formatMessage, shouldRegister } = args;
|
||||
const { name, displayName, description, parameters, formatMessage, shouldRegister, stealth } = args;
|
||||
|
||||
if (!(action instanceof SlashCommandClosure)) {
|
||||
throw new Error('The unnamed argument must be a closure.');
|
||||
@ -927,6 +979,7 @@ export class ToolManager {
|
||||
action: actionFunc,
|
||||
formatMessage: formatMessageFunc,
|
||||
shouldRegister: shouldRegisterFunc,
|
||||
stealth: stealth && isTrueBoolean(String(stealth)),
|
||||
});
|
||||
|
||||
return '';
|
||||
|
@ -31,7 +31,11 @@ export async function setUserControls(isEnabled) {
|
||||
* Check if the current user is an admin.
|
||||
* @returns {boolean} True if the current user is an admin
|
||||
*/
|
||||
function isAdmin() {
|
||||
export function isAdmin() {
|
||||
if (!accountsEnabled) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!currentUser) {
|
||||
return false;
|
||||
}
|
||||
|
@ -665,9 +665,10 @@ export function trimToEndSentence(input) {
|
||||
const characters = Array.from(input);
|
||||
for (let i = characters.length - 1; i >= 0; i--) {
|
||||
const char = characters[i];
|
||||
const emoji = isEmoji(char);
|
||||
|
||||
if (punctuation.has(char) || isEmoji(char)) {
|
||||
if (i > 0 && /[\s\n]/.test(characters[i - 1])) {
|
||||
if (punctuation.has(char) || emoji) {
|
||||
if (!emoji && i > 0 && /[\s\n]/.test(characters[i - 1])) {
|
||||
last = i - 1;
|
||||
} else {
|
||||
last = i;
|
||||
@ -2112,7 +2113,7 @@ export async function showFontAwesomePicker(customList = null) {
|
||||
* @param {string[]?} [options.filteredByTags=null] - Tags to filter characters by
|
||||
* @param {boolean} [options.preferCurrentChar=true] - Whether to prefer the current character(s)
|
||||
* @param {boolean} [options.quiet=false] - Whether to suppress warnings
|
||||
* @returns {any?} - The found character or null if not found
|
||||
* @returns {import('./char-data.js').v1CharData?} - The found character or null if not found
|
||||
*/
|
||||
export function findChar({ name = null, allowAvatar = true, insensitive = true, filteredByTags = null, preferCurrentChar = true, quiet = false } = {}) {
|
||||
const matches = (char) => !name || (allowAvatar && char.avatar === name) || (insensitive ? equalsIgnoreCaseAndAccents(char.name, name) : char.name === name);
|
||||
@ -2173,3 +2174,20 @@ export function getCharIndex(char) {
|
||||
if (index === -1) throw new Error(`Character not found: ${char.avatar}`);
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two arrays for equality
|
||||
* @param {any[]} a - The first array
|
||||
* @param {any[]} b - The second array
|
||||
* @returns {boolean} True if the arrays are equal, false otherwise
|
||||
*/
|
||||
export function arraysEqual(a, b) {
|
||||
if (a === b) return true;
|
||||
if (a == null || b == null) return false;
|
||||
if (a.length !== b.length) return false;
|
||||
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
if (a[i] !== b[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -46,6 +46,10 @@ function getLocalVariable(name, args = {}) {
|
||||
}
|
||||
|
||||
function setLocalVariable(name, value, args = {}) {
|
||||
if (!name) {
|
||||
throw new Error('Variable name cannot be empty or undefined.');
|
||||
}
|
||||
|
||||
if (!chat_metadata.variables) {
|
||||
chat_metadata.variables = {};
|
||||
}
|
||||
@ -99,6 +103,10 @@ function getGlobalVariable(name, args = {}) {
|
||||
}
|
||||
|
||||
function setGlobalVariable(name, value, args = {}) {
|
||||
if (!name) {
|
||||
throw new Error('Variable name cannot be empty or undefined.');
|
||||
}
|
||||
|
||||
if (args.index !== undefined) {
|
||||
try {
|
||||
let globalVariable = JSON.parse(extension_settings.variables.global[name] ?? 'null');
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Fuse } from '../lib.js';
|
||||
|
||||
import { saveSettings, callPopup, substituteParams, getRequestHeaders, chat_metadata, this_chid, characters, saveCharacterDebounced, menu_type, eventSource, event_types, getExtensionPromptByName, saveMetadata, getCurrentChatId, extension_prompt_roles } from '../script.js';
|
||||
import { download, debounce, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, getCharaFilename, getSortableDelay, escapeRegex, PAGINATION_TEMPLATE, navigation_option, waitUntilCondition, isTrueBoolean, setValueByPath, flashHighlight, select2ModifyOptions, getSelect2OptionId, dynamicSelect2DataViaAjax, highlightRegex, select2ChoiceClickSubscribe, isFalseBoolean, getSanitizedFilename, checkOverwriteExistingData, getStringHash, parseStringArray, cancelDebounce } from './utils.js';
|
||||
import { download, debounce, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, getCharaFilename, getSortableDelay, escapeRegex, PAGINATION_TEMPLATE, navigation_option, waitUntilCondition, isTrueBoolean, setValueByPath, flashHighlight, select2ModifyOptions, getSelect2OptionId, dynamicSelect2DataViaAjax, highlightRegex, select2ChoiceClickSubscribe, isFalseBoolean, getSanitizedFilename, checkOverwriteExistingData, getStringHash, parseStringArray, cancelDebounce, findChar, onlyUnique } from './utils.js';
|
||||
import { extension_settings, getContext } from './extensions.js';
|
||||
import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from './authors-note.js';
|
||||
import { isMobile } from './RossAscends-mods.js';
|
||||
@ -930,7 +930,50 @@ function registerWorldInfoSlashCommands() {
|
||||
return entries;
|
||||
}
|
||||
|
||||
async function getChatBookCallback() {
|
||||
/**
|
||||
* Gets the name of the persona-bound lorebook.
|
||||
* @returns {string} The name of the persona-bound lorebook
|
||||
*/
|
||||
function getPersonaBookCallback() {
|
||||
return power_user.persona_description_lorebook || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the character-bound lorebook.
|
||||
* @param {import('./slash-commands/SlashCommand.js').NamedArguments} args Named arguments
|
||||
* @param {import('./slash-commands/SlashCommand.js').UnnamedArguments} name Character name
|
||||
* @returns {string} The name of the character-bound lorebook, a JSON string of the character's lorebooks, or an empty string
|
||||
*/
|
||||
function getCharBookCallback({ type }, name) {
|
||||
const context = getContext();
|
||||
if (context.groupId && !name) throw new Error('This command is not available in groups without providing a character name');
|
||||
type = String(type ?? '').trim().toLowerCase() || 'primary';
|
||||
name = String(name ?? '') || context.characters[context.characterId]?.avatar || null;
|
||||
const character = findChar({ name });
|
||||
if (!character) {
|
||||
toastr.error('Character not found.');
|
||||
return '';
|
||||
}
|
||||
const books = [];
|
||||
if (type === 'all' || type === 'primary') {
|
||||
books.push(character.data?.extensions?.world);
|
||||
}
|
||||
if (type === 'all' || type === 'additional') {
|
||||
const fileName = getCharaFilename(context.characters.indexOf(character));
|
||||
const extraCharLore = world_info.charLore?.find((e) => e.name === fileName);
|
||||
if (extraCharLore && Array.isArray(extraCharLore.extraBooks)) {
|
||||
books.push(...extraCharLore.extraBooks);
|
||||
}
|
||||
}
|
||||
return type === 'primary' ? (books[0] ?? '') : JSON.stringify(books.filter(onlyUnique).filter(Boolean));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the chat-bound lorebook. Creates a new one if it doesn't exist.
|
||||
* @param {import('./slash-commands/SlashCommand.js').NamedArguments} args Named arguments
|
||||
* @returns {Promise<string>} The name of the chat-bound lorebook
|
||||
*/
|
||||
async function getChatBookCallback(args) {
|
||||
const chatId = getCurrentChatId();
|
||||
|
||||
if (!chatId) {
|
||||
@ -942,8 +985,19 @@ function registerWorldInfoSlashCommands() {
|
||||
return chat_metadata[METADATA_KEY];
|
||||
}
|
||||
|
||||
// Replace non-alphanumeric characters with underscores, cut to 64 characters
|
||||
const name = `Chat Book ${getCurrentChatId()}`.replace(/[^a-z0-9]/gi, '_').replace(/_{2,}/g, '_').substring(0, 64);
|
||||
const name = (() => {
|
||||
// Use the provided name if it's not in use
|
||||
if (typeof args.name === 'string') {
|
||||
const name = String(args.name);
|
||||
if (world_names.includes(name)) {
|
||||
throw new Error('This World Info file name is already in use');
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
// Replace non-alphanumeric characters with underscores, cut to 64 characters
|
||||
return `Chat Book ${getCurrentChatId()}`.replace(/[^a-z0-9]/gi, '_').replace(/_{2,}/g, '_').substring(0, 64);
|
||||
})();
|
||||
await createNewWorldInfo(name);
|
||||
|
||||
chat_metadata[METADATA_KEY] = name;
|
||||
@ -1289,8 +1343,48 @@ function registerWorldInfoSlashCommands() {
|
||||
callback: getChatBookCallback,
|
||||
returns: 'lorebook name',
|
||||
helpString: 'Get a name of the chat-bound lorebook or create a new one if was unbound, and pass it down the pipe.',
|
||||
namedArgumentList: [
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'name',
|
||||
description: 'lorebook name if creating a new one, will be auto-generated otherwise',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: false,
|
||||
acceptsMultiple: false,
|
||||
}),
|
||||
],
|
||||
aliases: ['getchatlore', 'getchatwi'],
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'getpersonabook',
|
||||
callback: getPersonaBookCallback,
|
||||
returns: 'lorebook name',
|
||||
helpString: 'Get a name of the current persona-bound lorebook and pass it down the pipe. Returns empty string if persona lorebook is not set.',
|
||||
aliases: ['getpersonalore', 'getpersonawi'],
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'getcharbook',
|
||||
callback: getCharBookCallback,
|
||||
returns: 'lorebook name or a list of lorebook names',
|
||||
namedArgumentList: [
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'type',
|
||||
description: 'type of the lorebook to get, returns a list for "all" and "additional"',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
enumList: ['primary', 'additional', 'all'],
|
||||
defaultValue: 'primary',
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'Character name - or unique character identifier (avatar key). If not provided, the current character is used.',
|
||||
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING],
|
||||
isRequired: false,
|
||||
enumProvider: commonEnumProviders.characters('character'),
|
||||
}),
|
||||
],
|
||||
helpString: 'Get a name of the character-bound lorebook and pass it down the pipe. Returns empty string if character lorebook is not set. Does not work in group chats without providing a character avatar name.',
|
||||
aliases: ['getcharlore', 'getcharwi'],
|
||||
}));
|
||||
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'findentry',
|
||||
@ -3548,6 +3642,11 @@ async function getCharacterLore() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (power_user.persona_description_lorebook === worldName) {
|
||||
console.debug(`[WI] Character ${name}'s world ${worldName} is already activated in persona lore! Skipping...`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const data = await loadWorldInfo(worldName);
|
||||
const newEntries = data ? Object.keys(data.entries).map((x) => data.entries[x]).map(({ uid, ...rest }) => ({ uid, world: worldName, ...rest })) : [];
|
||||
entries = entries.concat(newEntries);
|
||||
@ -3598,11 +3697,45 @@ async function getChatLore() {
|
||||
return entries;
|
||||
}
|
||||
|
||||
async function getPersonaLore() {
|
||||
const chatWorld = chat_metadata[METADATA_KEY];
|
||||
const personaWorld = power_user.persona_description_lorebook;
|
||||
|
||||
if (!personaWorld) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (chatWorld === personaWorld) {
|
||||
console.debug(`[WI] Persona world ${personaWorld} is already activated in chat world! Skipping...`);
|
||||
return [];
|
||||
}
|
||||
|
||||
if (selected_world_info.includes(personaWorld)) {
|
||||
console.debug(`[WI] Persona world ${personaWorld} is already activated in global world info! Skipping...`);
|
||||
return [];
|
||||
}
|
||||
|
||||
const data = await loadWorldInfo(personaWorld);
|
||||
const entries = data ? Object.keys(data.entries).map((x) => data.entries[x]).map(({ uid, ...rest }) => ({ uid, world: personaWorld, ...rest })) : [];
|
||||
|
||||
console.debug(`[WI] Persona lore has ${entries.length} entries`, [personaWorld]);
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
export async function getSortedEntries() {
|
||||
try {
|
||||
const globalLore = await getGlobalLore();
|
||||
const characterLore = await getCharacterLore();
|
||||
const chatLore = await getChatLore();
|
||||
const [
|
||||
globalLore,
|
||||
characterLore,
|
||||
chatLore,
|
||||
personaLore,
|
||||
] = await Promise.all([
|
||||
getGlobalLore(),
|
||||
getCharacterLore(),
|
||||
getChatLore(),
|
||||
getPersonaLore(),
|
||||
]);
|
||||
|
||||
let entries;
|
||||
|
||||
@ -3622,8 +3755,8 @@ export async function getSortedEntries() {
|
||||
break;
|
||||
}
|
||||
|
||||
// Chat lore always goes first
|
||||
entries = [...chatLore.sort(sortFn), ...entries];
|
||||
// Chat lore always goes first, then persona lore, then the rest
|
||||
entries = [...chatLore.sort(sortFn), ...personaLore.sort(sortFn), ...entries];
|
||||
|
||||
// Calculate hash and parse decorators. Split maps to preserve old hashes.
|
||||
entries = entries.map((entry) => {
|
||||
@ -3721,7 +3854,7 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
|
||||
// Put this code here since otherwise, the chat reference is modified
|
||||
for (const key of Object.keys(context.extensionPrompts)) {
|
||||
if (context.extensionPrompts[key]?.scan) {
|
||||
const prompt = getExtensionPromptByName(key);
|
||||
const prompt = await getExtensionPromptByName(key);
|
||||
if (prompt) {
|
||||
buffer.addInject(prompt);
|
||||
}
|
||||
@ -4548,7 +4681,7 @@ function convertCharacterBook(characterBook) {
|
||||
preventRecursion: entry.extensions?.prevent_recursion ?? false,
|
||||
delayUntilRecursion: entry.extensions?.delay_until_recursion ?? false,
|
||||
disable: !entry.enabled,
|
||||
addMemo: entry.comment ? true : false,
|
||||
addMemo: !!entry.comment,
|
||||
displayIndex: entry.extensions?.display_index ?? index,
|
||||
probability: entry.extensions?.probability ?? 100,
|
||||
useProbability: entry.extensions?.useProbability ?? true,
|
||||
@ -4816,9 +4949,33 @@ export async function importWorldInfo(file) {
|
||||
});
|
||||
}
|
||||
|
||||
export function assignLorebookToChat() {
|
||||
/**
|
||||
* Forces the world info editor to open on a specific world.
|
||||
* @param {string} worldName The name of the world to open
|
||||
*/
|
||||
export function openWorldInfoEditor(worldName) {
|
||||
console.log(`Opening lorebook for ${worldName}`);
|
||||
if (!$('#WorldInfo').is(':visible')) {
|
||||
$('#WIDrawerIcon').trigger('click');
|
||||
}
|
||||
const index = world_names.indexOf(worldName);
|
||||
$('#world_editor_select').val(index).trigger('change');
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns a lorebook to the current chat.
|
||||
* @param {PointerEvent} event Pointer event
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function assignLorebookToChat(event) {
|
||||
const selectedName = chat_metadata[METADATA_KEY];
|
||||
const template = $('#chat_world_template .chat_world').clone();
|
||||
|
||||
if (selectedName && event.altKey) {
|
||||
openWorldInfoEditor(selectedName);
|
||||
return;
|
||||
}
|
||||
|
||||
const template = $(await renderTemplateAsync('chatLorebook'));
|
||||
|
||||
const worldSelect = template.find('select');
|
||||
const chatName = template.find('.chat_name');
|
||||
@ -4846,7 +5003,7 @@ export function assignLorebookToChat() {
|
||||
saveMetadata();
|
||||
});
|
||||
|
||||
callPopup(template, 'text');
|
||||
return callGenericPopup(template, POPUP_TYPE.TEXT);
|
||||
}
|
||||
|
||||
jQuery(() => {
|
||||
@ -4997,11 +5154,7 @@ jQuery(() => {
|
||||
const worldName = characters[chid]?.data?.extensions?.world;
|
||||
const hasEmbed = checkEmbeddedWorld(chid);
|
||||
if (worldName && world_names.includes(worldName) && !event.shiftKey) {
|
||||
if (!$('#WorldInfo').is(':visible')) {
|
||||
$('#WIDrawerIcon').trigger('click');
|
||||
}
|
||||
const index = world_names.indexOf(worldName);
|
||||
$('#world_editor_select').val(index).trigger('change');
|
||||
openWorldInfoEditor(worldName);
|
||||
} else if (hasEmbed && !event.shiftKey) {
|
||||
await importEmbeddedWorldInfo();
|
||||
saveCharacterDebounced();
|
||||
|
@ -92,8 +92,6 @@
|
||||
/* base variable for shadow width slider calculations */
|
||||
--shadowWidth: 2;
|
||||
|
||||
color-scheme: only light;
|
||||
|
||||
/* Send form variables */
|
||||
--bottomFormBlockPadding: calc(var(--mainFontSize) / 2.5);
|
||||
--bottomFormIconSize: calc(var(--mainFontSize) * 1.9);
|
||||
@ -141,7 +139,7 @@ body {
|
||||
height: 100dvh;
|
||||
/*defaults as 100%, then reassigned via JS as pixels, will work on PC and Android*/
|
||||
/*height: calc(var(--doc-height) - 1px);*/
|
||||
background-color: var(--greyCAIbg);
|
||||
background-color: var(--SmartThemeBlurTintColor);
|
||||
background-repeat: no-repeat;
|
||||
background-attachment: fixed;
|
||||
background-size: cover;
|
||||
@ -149,6 +147,7 @@ body {
|
||||
font-size: var(--mainFontSize);
|
||||
color: var(--SmartThemeBodyColor);
|
||||
overflow: hidden;
|
||||
color-scheme: only light;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
@ -519,6 +518,40 @@ hr {
|
||||
transition: background-image 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
/* Background fitting options */
|
||||
#background_fitting {
|
||||
max-width: 6em;
|
||||
}
|
||||
|
||||
/* Fill/Cover - scales to fill width while maintaining aspect ratio */
|
||||
#bg1.cover,
|
||||
#bg_custom.cover {
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
/* Fit/Contain - shows entire image maintaining aspect ratio */
|
||||
#bg1.contain,
|
||||
#bg_custom.contain {
|
||||
background-size: contain;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
/* Stretch - stretches to fill entire space */
|
||||
#bg1.stretch,
|
||||
#bg_custom.stretch {
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
/* Center - centers without scaling */
|
||||
#bg1.center,
|
||||
#bg_custom.center {
|
||||
background-size: auto;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
body.reduced-motion #bg1,
|
||||
body.reduced-motion #bg_custom {
|
||||
transition: none;
|
||||
@ -1264,6 +1297,7 @@ button {
|
||||
text-shadow: 0px 0px calc(var(--shadowWidth) * 1px) var(--SmartThemeShadowColor);
|
||||
flex: 1;
|
||||
order: 3;
|
||||
field-sizing: content;
|
||||
|
||||
--progColor: rgb(146, 190, 252);
|
||||
--progFlashColor: rgb(215, 136, 114);
|
||||
@ -2788,6 +2822,10 @@ select option:not(:checked) {
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
#persona-management-block .menu_button {
|
||||
filter: grayscale(0.5);
|
||||
}
|
||||
|
||||
input[type=search]::-webkit-search-cancel-button {
|
||||
-webkit-appearance: none;
|
||||
height: 1em;
|
||||
@ -2870,6 +2908,7 @@ input[type=search]:focus::-webkit-search-cancel-button {
|
||||
|
||||
.mes_block .ch_name {
|
||||
max-width: 100%;
|
||||
min-height: 22px;
|
||||
}
|
||||
|
||||
/*applies to both groups and solos chars in the char list*/
|
||||
@ -3631,7 +3670,9 @@ body #toast-container {
|
||||
}
|
||||
|
||||
body #toast-container>div {
|
||||
opacity: 0.95;
|
||||
opacity: 1;
|
||||
filter: unset;
|
||||
-ms-filter: unset;
|
||||
}
|
||||
|
||||
#dialogue_del_mes {
|
||||
@ -4039,7 +4080,7 @@ input[type="range"]::-webkit-slider-thumb {
|
||||
.mes_button,
|
||||
.extraMesButtons>div {
|
||||
cursor: pointer;
|
||||
transition: 0.3s ease-in-out;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
filter: drop-shadow(0px 0px 2px black);
|
||||
opacity: 0.3;
|
||||
padding: 1px 3px;
|
||||
@ -4106,6 +4147,7 @@ input[type="range"]::-webkit-slider-thumb {
|
||||
line-height: calc(var(--mainFontSize) + .25rem);
|
||||
max-height: 75vh;
|
||||
max-height: 75dvh;
|
||||
field-sizing: content;
|
||||
}
|
||||
|
||||
#anchor_order {
|
||||
@ -5172,7 +5214,7 @@ body:not(.movingUI) .drawer-content.maximized {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0.8;
|
||||
min-height: 3rem;
|
||||
min-height: 2.5em;
|
||||
}
|
||||
|
||||
.openai_restorable,
|
||||
|
@ -172,6 +172,19 @@ function getHuggingFaceHeaders(directories) {
|
||||
}) : {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the headers for the Generic text completion API.
|
||||
* @param {import('./users.js').UserDirectoryList} directories
|
||||
* @returns {object} Headers for the request
|
||||
*/
|
||||
function getGenericHeaders(directories) {
|
||||
const apiKey = readSecret(directories, SECRET_KEYS.GENERIC);
|
||||
|
||||
return apiKey ? ({
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
}) : {};
|
||||
}
|
||||
|
||||
export function getOverrideHeaders(urlHost) {
|
||||
const requestOverrides = getConfigValue('requestOverrides', []);
|
||||
const overrideHeaders = requestOverrides?.find((e) => e.hosts?.includes(urlHost))?.headers;
|
||||
@ -214,6 +227,7 @@ export function setAdditionalHeadersByType(requestHeaders, type, server, directo
|
||||
[TEXTGEN_TYPES.LLAMACPP]: getLlamaCppHeaders,
|
||||
[TEXTGEN_TYPES.FEATHERLESS]: getFeatherlessHeaders,
|
||||
[TEXTGEN_TYPES.HUGGINGFACE]: getHuggingFaceHeaders,
|
||||
[TEXTGEN_TYPES.GENERIC]: getGenericHeaders,
|
||||
};
|
||||
|
||||
const getHeaders = headerGetters[type];
|
||||
|
@ -3,6 +3,7 @@ export const PUBLIC_DIRECTORIES = {
|
||||
backups: 'backups/',
|
||||
sounds: 'public/sounds',
|
||||
extensions: 'public/scripts/extensions',
|
||||
globalExtensions: 'public/scripts/extensions/third-party',
|
||||
};
|
||||
|
||||
export const SETTINGS_FILE = 'settings.json';
|
||||
@ -158,33 +159,6 @@ export const GEMINI_SAFETY = [
|
||||
},
|
||||
];
|
||||
|
||||
export const BISON_SAFETY = [
|
||||
{
|
||||
category: 'HARM_CATEGORY_DEROGATORY',
|
||||
threshold: 'BLOCK_NONE',
|
||||
},
|
||||
{
|
||||
category: 'HARM_CATEGORY_TOXICITY',
|
||||
threshold: 'BLOCK_NONE',
|
||||
},
|
||||
{
|
||||
category: 'HARM_CATEGORY_VIOLENCE',
|
||||
threshold: 'BLOCK_NONE',
|
||||
},
|
||||
{
|
||||
category: 'HARM_CATEGORY_SEXUAL',
|
||||
threshold: 'BLOCK_NONE',
|
||||
},
|
||||
{
|
||||
category: 'HARM_CATEGORY_MEDICAL',
|
||||
threshold: 'BLOCK_NONE',
|
||||
},
|
||||
{
|
||||
category: 'HARM_CATEGORY_DANGEROUS',
|
||||
threshold: 'BLOCK_NONE',
|
||||
},
|
||||
];
|
||||
|
||||
export const CHAT_COMPLETION_SOURCES = {
|
||||
OPENAI: 'openai',
|
||||
WINDOWAI: 'windowai',
|
||||
@ -201,6 +175,7 @@ export const CHAT_COMPLETION_SOURCES = {
|
||||
ZEROONEAI: '01ai',
|
||||
BLOCKENTROPY: 'blockentropy',
|
||||
NANOGPT: 'nanogpt',
|
||||
DEEPSEEK: 'deepseek',
|
||||
};
|
||||
|
||||
/**
|
||||
@ -224,6 +199,7 @@ export const TEXTGEN_TYPES = {
|
||||
OPENROUTER: 'openrouter',
|
||||
FEATHERLESS: 'featherless',
|
||||
HUGGINGFACE: 'huggingface',
|
||||
GENERIC: 'generic',
|
||||
};
|
||||
|
||||
export const INFERMATICAI_KEYS = [
|
||||
@ -345,6 +321,24 @@ export const OLLAMA_KEYS = [
|
||||
'min_p',
|
||||
];
|
||||
|
||||
// https://platform.openai.com/docs/api-reference/completions
|
||||
export const OPENAI_KEYS = [
|
||||
'model',
|
||||
'prompt',
|
||||
'stream',
|
||||
'temperature',
|
||||
'top_p',
|
||||
'frequency_penalty',
|
||||
'presence_penalty',
|
||||
'stop',
|
||||
'seed',
|
||||
'logit_bias',
|
||||
'logprobs',
|
||||
'max_tokens',
|
||||
'n',
|
||||
'best_of',
|
||||
];
|
||||
|
||||
export const AVATAR_WIDTH = 512;
|
||||
export const AVATAR_HEIGHT = 768;
|
||||
|
||||
|
@ -6,7 +6,6 @@ import { jsonParser } from '../../express-common.js';
|
||||
import {
|
||||
CHAT_COMPLETION_SOURCES,
|
||||
GEMINI_SAFETY,
|
||||
BISON_SAFETY,
|
||||
OPENROUTER_HEADERS,
|
||||
} from '../../constants.js';
|
||||
import {
|
||||
@ -28,6 +27,7 @@ import {
|
||||
mergeMessages,
|
||||
cachingAtDepthForOpenRouterClaude,
|
||||
cachingAtDepthForClaude,
|
||||
getPromptNames,
|
||||
} from '../../prompt-converters.js';
|
||||
|
||||
import { readSecret, SECRET_KEYS } from '../secrets.js';
|
||||
@ -51,27 +51,47 @@ const API_01AI = 'https://api.01.ai/v1';
|
||||
const API_BLOCKENTROPY = 'https://api.blockentropy.ai/v1';
|
||||
const API_AI21 = 'https://api.ai21.com/studio/v1';
|
||||
const API_NANOGPT = 'https://nano-gpt.com/api/v1';
|
||||
const API_DEEPSEEK = 'https://api.deepseek.com/beta';
|
||||
|
||||
/**
|
||||
* Applies a post-processing step to the generated messages.
|
||||
* @param {object[]} messages Messages to post-process
|
||||
* @param {string} type Prompt conversion type
|
||||
* @param {string} charName Character name
|
||||
* @param {string} userName User name
|
||||
* @param {import('../../prompt-converters.js').PromptNames} names Prompt names
|
||||
* @returns
|
||||
*/
|
||||
function postProcessPrompt(messages, type, charName, userName) {
|
||||
function postProcessPrompt(messages, type, names) {
|
||||
switch (type) {
|
||||
case 'merge':
|
||||
case 'claude':
|
||||
return mergeMessages(messages, charName, userName, false);
|
||||
return mergeMessages(messages, names, false, false);
|
||||
case 'semi':
|
||||
return mergeMessages(messages, names, true, false);
|
||||
case 'strict':
|
||||
return mergeMessages(messages, charName, userName, true);
|
||||
return mergeMessages(messages, names, true, true);
|
||||
case 'deepseek':
|
||||
return (x => x.length && (x[x.length - 1].role !== 'assistant' || (x[x.length - 1].prefix = true)) ? x : x)(mergeMessages(messages, names, true, false));
|
||||
default:
|
||||
return messages;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets OpenRouter transforms based on the request.
|
||||
* @param {import('express').Request} request Express request
|
||||
* @returns {string[] | undefined} OpenRouter transforms
|
||||
*/
|
||||
function getOpenRouterTransforms(request) {
|
||||
switch (request.body.middleout) {
|
||||
case 'on':
|
||||
return ['middle-out'];
|
||||
case 'off':
|
||||
return [];
|
||||
case 'auto':
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a request to Claude API.
|
||||
* @param {express.Request} request Express request
|
||||
@ -102,7 +122,7 @@ async function sendClaudeRequest(request, response) {
|
||||
const additionalHeaders = {};
|
||||
const useTools = request.body.model.startsWith('claude-3') && Array.isArray(request.body.tools) && request.body.tools.length > 0;
|
||||
const useSystemPrompt = (request.body.model.startsWith('claude-2') || request.body.model.startsWith('claude-3')) && request.body.claude_use_sysprompt;
|
||||
const convertedPrompt = convertClaudeMessages(request.body.messages, request.body.assistant_prefill, useSystemPrompt, useTools, request.body.char_name, request.body.user_name);
|
||||
const convertedPrompt = convertClaudeMessages(request.body.messages, request.body.assistant_prefill, useSystemPrompt, useTools, getPromptNames(request));
|
||||
// Add custom stop sequences
|
||||
const stopSequences = [];
|
||||
if (Array.isArray(request.body.stop)) {
|
||||
@ -262,9 +282,8 @@ async function sendMakerSuiteRequest(request, response) {
|
||||
}
|
||||
|
||||
const model = String(request.body.model);
|
||||
const isGemini = model.includes('gemini');
|
||||
const isText = model.includes('text');
|
||||
const stream = Boolean(request.body.stream) && isGemini;
|
||||
const stream = Boolean(request.body.stream);
|
||||
const showThoughts = Boolean(request.body.show_thoughts);
|
||||
|
||||
const generationConfig = {
|
||||
stopSequences: request.body.stop,
|
||||
@ -280,8 +299,15 @@ async function sendMakerSuiteRequest(request, response) {
|
||||
delete generationConfig.stopSequences;
|
||||
}
|
||||
|
||||
const should_use_system_prompt = (model.includes('gemini-1.5-flash') || model.includes('gemini-1.5-pro') || model.startsWith('gemini-exp')) && request.body.use_makersuite_sysprompt;
|
||||
const prompt = convertGooglePrompt(request.body.messages, model, should_use_system_prompt, request.body.char_name, request.body.user_name);
|
||||
const should_use_system_prompt = (
|
||||
model.includes('gemini-2.0-flash-thinking-exp') ||
|
||||
model.includes('gemini-2.0-flash-exp') ||
|
||||
model.includes('gemini-1.5-flash') ||
|
||||
model.includes('gemini-1.5-pro') ||
|
||||
model.startsWith('gemini-exp')
|
||||
) && request.body.use_makersuite_sysprompt;
|
||||
|
||||
const prompt = convertGooglePrompt(request.body.messages, model, should_use_system_prompt, getPromptNames(request));
|
||||
let body = {
|
||||
contents: prompt.contents,
|
||||
safetySettings: GEMINI_SAFETY,
|
||||
@ -295,39 +321,7 @@ async function sendMakerSuiteRequest(request, response) {
|
||||
return body;
|
||||
}
|
||||
|
||||
function getBisonBody() {
|
||||
const prompt = isText
|
||||
? ({ text: convertTextCompletionPrompt(request.body.messages) })
|
||||
: ({ messages: convertGooglePrompt(request.body.messages, model).contents });
|
||||
|
||||
/** @type {any} Shut the lint up */
|
||||
const bisonBody = {
|
||||
...generationConfig,
|
||||
safetySettings: BISON_SAFETY,
|
||||
candidate_count: 1, // lewgacy spelling
|
||||
prompt: prompt,
|
||||
};
|
||||
|
||||
if (!isText) {
|
||||
delete bisonBody.stopSequences;
|
||||
delete bisonBody.maxOutputTokens;
|
||||
delete bisonBody.safetySettings;
|
||||
|
||||
if (Array.isArray(prompt.messages)) {
|
||||
for (const msg of prompt.messages) {
|
||||
msg.author = msg.role;
|
||||
msg.content = msg.parts[0].text;
|
||||
delete msg.parts;
|
||||
delete msg.role;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delete bisonBody.candidateCount;
|
||||
return bisonBody;
|
||||
}
|
||||
|
||||
const body = isGemini ? getGeminiBody() : getBisonBody();
|
||||
const body = getGeminiBody();
|
||||
console.log('Google AI Studio request:', body);
|
||||
|
||||
try {
|
||||
@ -337,10 +331,9 @@ async function sendMakerSuiteRequest(request, response) {
|
||||
controller.abort();
|
||||
});
|
||||
|
||||
const apiVersion = isGemini ? 'v1beta' : 'v1beta2';
|
||||
const responseType = isGemini
|
||||
? (stream ? 'streamGenerateContent' : 'generateContent')
|
||||
: (isText ? 'generateText' : 'generateMessage');
|
||||
const isThinking = model.includes('thinking');
|
||||
const apiVersion = isThinking ? 'v1alpha' : 'v1beta';
|
||||
const responseType = (stream ? 'streamGenerateContent' : 'generateContent');
|
||||
|
||||
const generateResponse = await fetch(`${apiUrl.toString().replace(/\/$/, '')}/${apiVersion}/models/${model}:${responseType}?key=${apiKey}${stream ? '&alt=sse' : ''}`, {
|
||||
body: JSON.stringify(body),
|
||||
@ -381,15 +374,19 @@ async function sendMakerSuiteRequest(request, response) {
|
||||
}
|
||||
|
||||
const responseContent = candidates[0].content ?? candidates[0].output;
|
||||
const responseText = typeof responseContent === 'string' ? responseContent : responseContent?.parts?.[0]?.text;
|
||||
console.log('Google AI Studio response:', responseContent);
|
||||
|
||||
if (Array.isArray(responseContent?.parts) && isThinking && !showThoughts) {
|
||||
responseContent.parts = responseContent.parts.filter(part => !part.thought);
|
||||
}
|
||||
|
||||
const responseText = typeof responseContent === 'string' ? responseContent : responseContent?.parts?.map(part => part.text)?.join('\n\n');
|
||||
if (!responseText) {
|
||||
let message = 'Google AI Studio Candidate text empty';
|
||||
console.log(message, generateResponseJson);
|
||||
return response.send({ error: { message } });
|
||||
}
|
||||
|
||||
console.log('Google AI Studio response:', responseText);
|
||||
|
||||
// Wrap it back to OAI format
|
||||
const reply = { choices: [{ 'message': { 'content': responseText } }] };
|
||||
return response.send(reply);
|
||||
@ -415,7 +412,7 @@ async function sendAI21Request(request, response) {
|
||||
request.socket.on('close', function () {
|
||||
controller.abort();
|
||||
});
|
||||
const convertedPrompt = convertAI21Messages(request.body.messages, request.body.char_name, request.body.user_name);
|
||||
const convertedPrompt = convertAI21Messages(request.body.messages, getPromptNames(request));
|
||||
const body = {
|
||||
messages: convertedPrompt,
|
||||
model: request.body.model,
|
||||
@ -478,7 +475,7 @@ async function sendMistralAIRequest(request, response) {
|
||||
}
|
||||
|
||||
try {
|
||||
const messages = convertMistralMessages(request.body.messages, request.body.char_name, request.body.user_name);
|
||||
const messages = convertMistralMessages(request.body.messages, getPromptNames(request));
|
||||
const controller = new AbortController();
|
||||
request.socket.removeAllListeners('close');
|
||||
request.socket.on('close', function () {
|
||||
@ -559,7 +556,7 @@ async function sendCohereRequest(request, response) {
|
||||
}
|
||||
|
||||
try {
|
||||
const convertedHistory = convertCohereMessages(request.body.messages, request.body.char_name, request.body.user_name);
|
||||
const convertedHistory = convertCohereMessages(request.body.messages, getPromptNames(request));
|
||||
const tools = [];
|
||||
|
||||
if (Array.isArray(request.body.tools) && request.body.tools.length > 0) {
|
||||
@ -676,13 +673,17 @@ router.post('/status', jsonParser, async function (request, response_getstatus_o
|
||||
api_url = API_NANOGPT;
|
||||
api_key_openai = readSecret(request.user.directories, SECRET_KEYS.NANOGPT);
|
||||
headers = {};
|
||||
} else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.DEEPSEEK) {
|
||||
api_url = API_DEEPSEEK.replace('/beta', '');
|
||||
api_key_openai = readSecret(request.user.directories, SECRET_KEYS.DEEPSEEK);
|
||||
headers = {};
|
||||
} else {
|
||||
console.log('This chat completion source is not supported yet.');
|
||||
return response_getstatus_openai.status(400).send({ error: true });
|
||||
}
|
||||
|
||||
if (!api_key_openai && !request.body.reverse_proxy && request.body.chat_completion_source !== CHAT_COMPLETION_SOURCES.CUSTOM) {
|
||||
console.log('OpenAI API key is missing.');
|
||||
console.log('Chat Completion API key is missing.');
|
||||
return response_getstatus_openai.status(400).send({ error: true });
|
||||
}
|
||||
|
||||
@ -726,14 +727,14 @@ router.post('/status', jsonParser, async function (request, response_getstatus_o
|
||||
|
||||
if (Array.isArray(models)) {
|
||||
const modelIds = models.filter(x => x && typeof x === 'object').map(x => x.id).sort();
|
||||
console.log('Available OpenAI models:', modelIds);
|
||||
console.log('Available models:', modelIds);
|
||||
} else {
|
||||
console.log('OpenAI endpoint did not return a list of models.');
|
||||
console.log('Chat Completion endpoint did not return a list of models.');
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log('OpenAI status check failed. Either Access Token is incorrect or API endpoint is down.');
|
||||
console.log('Chat Completion status check failed. Either Access Token is incorrect or API endpoint is down.');
|
||||
response_getstatus_openai.send({ error: true, can_bypass: true, data: { data: [] } });
|
||||
}
|
||||
} catch (e) {
|
||||
@ -865,7 +866,9 @@ router.post('/generate', jsonParser, function (request, response) {
|
||||
apiKey = readSecret(request.user.directories, SECRET_KEYS.OPENROUTER);
|
||||
// OpenRouter needs to pass the Referer and X-Title: https://openrouter.ai/docs#requests
|
||||
headers = { ...OPENROUTER_HEADERS };
|
||||
bodyParams = { 'transforms': ['middle-out'] };
|
||||
bodyParams = {
|
||||
'transforms': getOpenRouterTransforms(request),
|
||||
};
|
||||
|
||||
if (request.body.min_p !== undefined) {
|
||||
bodyParams['min_p'] = request.body.min_p;
|
||||
@ -917,15 +920,14 @@ router.post('/generate', jsonParser, function (request, response) {
|
||||
request.body.messages = postProcessPrompt(
|
||||
request.body.messages,
|
||||
request.body.custom_prompt_post_processing,
|
||||
request.body.char_name,
|
||||
request.body.user_name);
|
||||
getPromptNames(request));
|
||||
}
|
||||
} else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.PERPLEXITY) {
|
||||
apiUrl = API_PERPLEXITY;
|
||||
apiKey = readSecret(request.user.directories, SECRET_KEYS.PERPLEXITY);
|
||||
headers = {};
|
||||
bodyParams = {};
|
||||
request.body.messages = postProcessPrompt(request.body.messages, 'strict', request.body.char_name, request.body.user_name);
|
||||
request.body.messages = postProcessPrompt(request.body.messages, 'strict', getPromptNames(request));
|
||||
} else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.GROQ) {
|
||||
apiUrl = API_GROQ;
|
||||
apiKey = readSecret(request.user.directories, SECRET_KEYS.GROQ);
|
||||
@ -946,6 +948,18 @@ router.post('/generate', jsonParser, function (request, response) {
|
||||
apiKey = readSecret(request.user.directories, SECRET_KEYS.BLOCKENTROPY);
|
||||
headers = {};
|
||||
bodyParams = {};
|
||||
} else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.DEEPSEEK) {
|
||||
apiUrl = API_DEEPSEEK;
|
||||
apiKey = readSecret(request.user.directories, SECRET_KEYS.DEEPSEEK);
|
||||
headers = {};
|
||||
bodyParams = {};
|
||||
|
||||
if (request.body.logprobs > 0) {
|
||||
bodyParams['top_logprobs'] = request.body.logprobs;
|
||||
bodyParams['logprobs'] = true;
|
||||
}
|
||||
|
||||
request.body.messages = postProcessPrompt(request.body.messages, 'deepseek', getPromptNames(request));
|
||||
} else {
|
||||
console.log('This chat completion source is not supported yet.');
|
||||
return response.status(400).send({ error: true });
|
||||
|
@ -13,6 +13,7 @@ import {
|
||||
VLLM_KEYS,
|
||||
DREAMGEN_KEYS,
|
||||
FEATHERLESS_KEYS,
|
||||
OPENAI_KEYS,
|
||||
} from '../../constants.js';
|
||||
import { forwardFetchResponse, trimV1, getConfigValue } from '../../util.js';
|
||||
import { setAdditionalHeaders } from '../../additional-headers.js';
|
||||
@ -113,8 +114,8 @@ router.post('/status', jsonParser, async function (request, response) {
|
||||
let url = baseUrl;
|
||||
let result = '';
|
||||
|
||||
|
||||
switch (apiType) {
|
||||
case TEXTGEN_TYPES.GENERIC:
|
||||
case TEXTGEN_TYPES.OOBA:
|
||||
case TEXTGEN_TYPES.VLLM:
|
||||
case TEXTGEN_TYPES.APHRODITE:
|
||||
@ -287,6 +288,7 @@ router.post('/generate', jsonParser, async function (request, response) {
|
||||
let url = trimV1(baseUrl);
|
||||
|
||||
switch (request.body.api_type) {
|
||||
case TEXTGEN_TYPES.GENERIC:
|
||||
case TEXTGEN_TYPES.VLLM:
|
||||
case TEXTGEN_TYPES.FEATHERLESS:
|
||||
case TEXTGEN_TYPES.APHRODITE:
|
||||
@ -347,6 +349,12 @@ router.post('/generate', jsonParser, async function (request, response) {
|
||||
args.body = JSON.stringify(request.body);
|
||||
}
|
||||
|
||||
if (request.body.api_type === TEXTGEN_TYPES.GENERIC) {
|
||||
request.body = _.pickBy(request.body, (_, key) => OPENAI_KEYS.includes(key));
|
||||
if (Array.isArray(request.body.stop)) { request.body.stop = request.body.stop.slice(0, 4); }
|
||||
args.body = JSON.stringify(request.body);
|
||||
}
|
||||
|
||||
if (request.body.api_type === TEXTGEN_TYPES.OPENROUTER) {
|
||||
if (Array.isArray(request.body.provider) && request.body.provider.length > 0) {
|
||||
request.body.provider = {
|
||||
|
@ -158,7 +158,8 @@ async function tryReadImage(imgPath, crop) {
|
||||
return image;
|
||||
}
|
||||
// If it's an unsupported type of image (APNG) - just read the file as buffer
|
||||
catch {
|
||||
catch (error) {
|
||||
console.log(`Failed to read image: ${imgPath}`, error);
|
||||
return fs.readFileSync(imgPath);
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,18 @@ import { sync as writeFileAtomicSync } from 'write-file-atomic';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { jsonParser, urlencodedParser } from '../express-common.js';
|
||||
import { getConfigValue, humanizedISO8601DateTime, tryParse, generateTimestamp, removeOldBackups } from '../util.js';
|
||||
import {
|
||||
getConfigValue,
|
||||
humanizedISO8601DateTime,
|
||||
tryParse,
|
||||
generateTimestamp,
|
||||
removeOldBackups,
|
||||
formatBytes,
|
||||
} from '../util.js';
|
||||
|
||||
const isBackupDisabled = getConfigValue('disableChatBackup', false);
|
||||
const maxTotalChatBackups = Number(getConfigValue('maxTotalChatBackups', -1));
|
||||
const throttleInterval = getConfigValue('chatBackupThrottleInterval', 10_000);
|
||||
|
||||
/**
|
||||
* Saves a chat to the backups directory.
|
||||
@ -19,7 +30,6 @@ import { getConfigValue, humanizedISO8601DateTime, tryParse, generateTimestamp,
|
||||
*/
|
||||
function backupChat(directory, name, chat) {
|
||||
try {
|
||||
const isBackupDisabled = getConfigValue('disableChatBackup', false);
|
||||
|
||||
if (isBackupDisabled) {
|
||||
return;
|
||||
@ -32,11 +42,20 @@ function backupChat(directory, name, chat) {
|
||||
writeFileAtomicSync(backupFile, chat, 'utf-8');
|
||||
|
||||
removeOldBackups(directory, `chat_${name}_`);
|
||||
|
||||
if (isNaN(maxTotalChatBackups) || maxTotalChatBackups < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
removeOldBackups(directory, 'chat_', maxTotalChatBackups);
|
||||
} catch (err) {
|
||||
console.log(`Could not backup chat for ${name}`, err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {Map<string, import('lodash').DebouncedFunc<function(string, string, string): void>>}
|
||||
*/
|
||||
const backupFunctions = new Map();
|
||||
|
||||
/**
|
||||
@ -45,24 +64,10 @@ const backupFunctions = new Map();
|
||||
* @returns {function(string, string, string): void} Backup function
|
||||
*/
|
||||
function getBackupFunction(handle) {
|
||||
const throttleInterval = getConfigValue('chatBackupThrottleInterval', 10_000);
|
||||
if (!backupFunctions.has(handle)) {
|
||||
backupFunctions.set(handle, _.throttle(backupChat, throttleInterval, { leading: true, trailing: true }));
|
||||
}
|
||||
return backupFunctions.get(handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a byte size into a human-readable string with units
|
||||
* @param {number} bytes - The size in bytes to format
|
||||
* @returns {string} The formatted string (e.g., "1.5 MB")
|
||||
*/
|
||||
function formatBytes(bytes) {
|
||||
if (bytes === 0) return '0 B';
|
||||
const k = 1024;
|
||||
const sizes = ['B', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
return backupFunctions.get(handle) || (() => {});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -664,12 +669,12 @@ router.post('/search', jsonParser, function (request, response) {
|
||||
.map(line => { try { return JSON.parse(line); } catch (_) { return null; } })
|
||||
.filter(x => x && typeof x.mes === 'string');
|
||||
|
||||
if (messages.length === 0) {
|
||||
if (query && messages.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const lastMessage = messages[messages.length - 1];
|
||||
const lastMesDate = lastMessage?.send_date || new Date().toISOString();
|
||||
const lastMesDate = lastMessage?.send_date || Math.round(fs.statSync(chatFile.path).mtimeMs);
|
||||
|
||||
// If no search query, just return metadata
|
||||
if (!query) {
|
||||
@ -702,7 +707,7 @@ router.post('/search', jsonParser, function (request, response) {
|
||||
}
|
||||
|
||||
// Sort by last message date descending
|
||||
results.sort((a, b) => new Date(b.last_mes) - new Date(a.last_mes));
|
||||
results.sort((a, b) => new Date(b.last_mes).getTime() - new Date(a.last_mes).getTime());
|
||||
return response.send(results);
|
||||
|
||||
} catch (error) {
|
||||
|
@ -73,8 +73,19 @@ router.post('/install', jsonParser, async (request, response) => {
|
||||
fs.mkdirSync(path.join(request.user.directories.extensions));
|
||||
}
|
||||
|
||||
const url = request.body.url;
|
||||
const extensionPath = path.join(request.user.directories.extensions, path.basename(url, '.git'));
|
||||
if (!fs.existsSync(PUBLIC_DIRECTORIES.globalExtensions)) {
|
||||
fs.mkdirSync(PUBLIC_DIRECTORIES.globalExtensions);
|
||||
}
|
||||
|
||||
const { url, global } = request.body;
|
||||
|
||||
if (global && !request.user.profile.admin) {
|
||||
console.warn(`User ${request.user.profile.handle} does not have permission to install global extensions.`);
|
||||
return response.status(403).send('Forbidden: No permission to install global extensions.');
|
||||
}
|
||||
|
||||
const basePath = global ? PUBLIC_DIRECTORIES.globalExtensions : request.user.directories.extensions;
|
||||
const extensionPath = path.join(basePath, sanitize(path.basename(url, '.git')));
|
||||
|
||||
if (fs.existsSync(extensionPath)) {
|
||||
return response.status(409).send(`Directory already exists at ${extensionPath}`);
|
||||
@ -83,10 +94,8 @@ router.post('/install', jsonParser, async (request, response) => {
|
||||
await git.clone(url, extensionPath, { '--depth': 1 });
|
||||
console.log(`Extension has been cloned at ${extensionPath}`);
|
||||
|
||||
|
||||
const { version, author, display_name } = await getManifest(extensionPath);
|
||||
|
||||
|
||||
return response.send({ version, author, display_name, extensionPath });
|
||||
} catch (error) {
|
||||
console.log('Importing custom content failed', error);
|
||||
@ -112,8 +121,15 @@ router.post('/update', jsonParser, async (request, response) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const extensionName = request.body.extensionName;
|
||||
const extensionPath = path.join(request.user.directories.extensions, extensionName);
|
||||
const { extensionName, global } = request.body;
|
||||
|
||||
if (global && !request.user.profile.admin) {
|
||||
console.warn(`User ${request.user.profile.handle} does not have permission to update global extensions.`);
|
||||
return response.status(403).send('Forbidden: No permission to update global extensions.');
|
||||
}
|
||||
|
||||
const basePath = global ? PUBLIC_DIRECTORIES.globalExtensions : request.user.directories.extensions;
|
||||
const extensionPath = path.join(basePath, extensionName);
|
||||
|
||||
if (!fs.existsSync(extensionPath)) {
|
||||
return response.status(404).send(`Directory does not exist at ${extensionPath}`);
|
||||
@ -122,7 +138,6 @@ router.post('/update', jsonParser, async (request, response) => {
|
||||
const { isUpToDate, remoteUrl } = await checkIfRepoIsUpToDate(extensionPath);
|
||||
const currentBranch = await git.cwd(extensionPath).branch();
|
||||
if (!isUpToDate) {
|
||||
|
||||
await git.cwd(extensionPath).pull('origin', currentBranch.current);
|
||||
console.log(`Extension has been updated at ${extensionPath}`);
|
||||
} else {
|
||||
@ -140,6 +155,50 @@ router.post('/update', jsonParser, async (request, response) => {
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/move', jsonParser, async (request, response) => {
|
||||
try {
|
||||
const { extensionName, source, destination } = request.body;
|
||||
|
||||
if (!extensionName || !source || !destination) {
|
||||
return response.status(400).send('Bad Request. Not all required parameters are provided.');
|
||||
}
|
||||
|
||||
if (!request.user.profile.admin) {
|
||||
console.warn(`User ${request.user.profile.handle} does not have permission to move extensions.`);
|
||||
return response.status(403).send('Forbidden: No permission to move extensions.');
|
||||
}
|
||||
|
||||
const sourceDirectory = source === 'global' ? PUBLIC_DIRECTORIES.globalExtensions : request.user.directories.extensions;
|
||||
const destinationDirectory = destination === 'global' ? PUBLIC_DIRECTORIES.globalExtensions : request.user.directories.extensions;
|
||||
const sourcePath = path.join(sourceDirectory, sanitize(extensionName));
|
||||
const destinationPath = path.join(destinationDirectory, sanitize(extensionName));
|
||||
|
||||
if (!fs.existsSync(sourcePath) || !fs.statSync(sourcePath).isDirectory()) {
|
||||
console.error(`Source directory does not exist at ${sourcePath}`);
|
||||
return response.status(404).send('Source directory does not exist.');
|
||||
}
|
||||
|
||||
if (fs.existsSync(destinationPath)) {
|
||||
console.error(`Destination directory already exists at ${destinationPath}`);
|
||||
return response.status(409).send('Destination directory already exists.');
|
||||
}
|
||||
|
||||
if (source === destination) {
|
||||
console.error('Source and destination directories are the same');
|
||||
return response.status(409).send('Source and destination directories are the same.');
|
||||
}
|
||||
|
||||
fs.cpSync(sourcePath, destinationPath, { recursive: true, force: true });
|
||||
fs.rmSync(sourcePath, { recursive: true, force: true });
|
||||
console.log(`Extension has been moved from ${sourcePath} to ${destinationPath}`);
|
||||
|
||||
return response.sendStatus(204);
|
||||
} catch (error) {
|
||||
console.log('Moving extension failed', error);
|
||||
return response.status(500).send('Internal Server Error. Try again later.');
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* HTTP POST handler function to get the current git commit hash and branch name for a given extension.
|
||||
* It checks whether the repository is up-to-date with the remote, and returns the status along with
|
||||
@ -157,19 +216,28 @@ router.post('/version', jsonParser, async (request, response) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const extensionName = request.body.extensionName;
|
||||
const extensionPath = path.join(request.user.directories.extensions, extensionName);
|
||||
const { extensionName, global } = request.body;
|
||||
const basePath = global ? PUBLIC_DIRECTORIES.globalExtensions : request.user.directories.extensions;
|
||||
const extensionPath = path.join(basePath, sanitize(extensionName));
|
||||
|
||||
if (!fs.existsSync(extensionPath)) {
|
||||
return response.status(404).send(`Directory does not exist at ${extensionPath}`);
|
||||
}
|
||||
|
||||
let currentCommitHash;
|
||||
try {
|
||||
currentCommitHash = await git.cwd(extensionPath).revparse(['HEAD']);
|
||||
} catch (error) {
|
||||
// it is not a git repo, or has no commits yet, or is a bare repo
|
||||
// not possible to update it, most likely can't get the branch name either
|
||||
return response.send({ currentBranchName: null, currentCommitHash, isUpToDate: true, remoteUrl: null });
|
||||
}
|
||||
|
||||
const currentBranch = await git.cwd(extensionPath).branch();
|
||||
// get only the working branch
|
||||
const currentBranchName = currentBranch.current;
|
||||
await git.cwd(extensionPath).fetch('origin');
|
||||
const currentCommitHash = await git.cwd(extensionPath).revparse(['HEAD']);
|
||||
console.log(currentBranch, currentCommitHash);
|
||||
console.log(extensionName, currentBranchName, currentCommitHash);
|
||||
const { isUpToDate, remoteUrl } = await checkIfRepoIsUpToDate(extensionPath);
|
||||
|
||||
return response.send({ currentBranchName, currentCommitHash, isUpToDate, remoteUrl });
|
||||
@ -193,11 +261,16 @@ router.post('/delete', jsonParser, async (request, response) => {
|
||||
return response.status(400).send('Bad Request: extensionName is required in the request body.');
|
||||
}
|
||||
|
||||
// Sanitize the extension name to prevent directory traversal
|
||||
const extensionName = sanitize(request.body.extensionName);
|
||||
|
||||
try {
|
||||
const extensionPath = path.join(request.user.directories.extensions, extensionName);
|
||||
const { extensionName, global } = request.body;
|
||||
|
||||
if (global && !request.user.profile.admin) {
|
||||
console.warn(`User ${request.user.profile.handle} does not have permission to delete global extensions.`);
|
||||
return response.status(403).send('Forbidden: No permission to delete global extensions.');
|
||||
}
|
||||
|
||||
const basePath = global ? PUBLIC_DIRECTORIES.globalExtensions : request.user.directories.extensions;
|
||||
const extensionPath = path.join(basePath, sanitize(extensionName));
|
||||
|
||||
if (!fs.existsSync(extensionPath)) {
|
||||
return response.status(404).send(`Directory does not exist at ${extensionPath}`);
|
||||
@ -219,26 +292,38 @@ router.post('/delete', jsonParser, async (request, response) => {
|
||||
* If the folder is called third-party, search for subfolders instead
|
||||
*/
|
||||
router.get('/discover', jsonParser, function (request, response) {
|
||||
// get all folders in the extensions folder, except third-party
|
||||
const extensions = fs
|
||||
.readdirSync(PUBLIC_DIRECTORIES.extensions)
|
||||
.filter(f => fs.statSync(path.join(PUBLIC_DIRECTORIES.extensions, f)).isDirectory())
|
||||
.filter(f => f !== 'third-party');
|
||||
|
||||
// get all folders in the third-party folder, if it exists
|
||||
|
||||
if (!fs.existsSync(path.join(request.user.directories.extensions))) {
|
||||
return response.send(extensions);
|
||||
fs.mkdirSync(path.join(request.user.directories.extensions));
|
||||
}
|
||||
|
||||
const thirdPartyExtensions = fs
|
||||
if (!fs.existsSync(PUBLIC_DIRECTORIES.globalExtensions)) {
|
||||
fs.mkdirSync(PUBLIC_DIRECTORIES.globalExtensions);
|
||||
}
|
||||
|
||||
// Get all folders in system extensions folder, excluding third-party
|
||||
const builtInExtensions = fs
|
||||
.readdirSync(PUBLIC_DIRECTORIES.extensions)
|
||||
.filter(f => fs.statSync(path.join(PUBLIC_DIRECTORIES.extensions, f)).isDirectory())
|
||||
.filter(f => f !== 'third-party')
|
||||
.map(f => ({ type: 'system', name: f }));
|
||||
|
||||
// Get all folders in local extensions folder
|
||||
const userExtensions = fs
|
||||
.readdirSync(path.join(request.user.directories.extensions))
|
||||
.filter(f => fs.statSync(path.join(request.user.directories.extensions, f)).isDirectory());
|
||||
.filter(f => fs.statSync(path.join(request.user.directories.extensions, f)).isDirectory())
|
||||
.map(f => ({ type: 'local', name: `third-party/${f}` }));
|
||||
|
||||
// add the third-party extensions to the extensions array
|
||||
extensions.push(...thirdPartyExtensions.map(f => `third-party/${f}`));
|
||||
console.log(extensions);
|
||||
// Get all folders in global extensions folder
|
||||
// In case of a conflict, the extension will be loaded from the user folder
|
||||
const globalExtensions = fs
|
||||
.readdirSync(PUBLIC_DIRECTORIES.globalExtensions)
|
||||
.filter(f => fs.statSync(path.join(PUBLIC_DIRECTORIES.globalExtensions, f)).isDirectory())
|
||||
.map(f => ({ type: 'global', name: `third-party/${f}` }))
|
||||
.filter(f => !userExtensions.some(e => e.name === f.name));
|
||||
|
||||
// Combine all extensions
|
||||
const allExtensions = [...builtInExtensions, ...userExtensions, ...globalExtensions];
|
||||
console.log('Extensions available for', request.user.profile.handle, allExtensions);
|
||||
|
||||
return response.send(extensions);
|
||||
return response.send(allExtensions);
|
||||
});
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user