mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-02-23 07:27:41 +01:00
commit
2428c3979f
@ -55,6 +55,7 @@ module.exports = {
|
|||||||
isProbablyReaderable: 'readonly',
|
isProbablyReaderable: 'readonly',
|
||||||
ePub: 'readonly',
|
ePub: 'readonly',
|
||||||
diff_match_patch: 'readonly',
|
diff_match_patch: 'readonly',
|
||||||
|
SillyTavern: 'readonly',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -4,8 +4,22 @@ dataRoot: ./data
|
|||||||
# -- SERVER CONFIGURATION --
|
# -- SERVER CONFIGURATION --
|
||||||
# Listen for incoming connections
|
# Listen for incoming connections
|
||||||
listen: false
|
listen: false
|
||||||
|
# Enables IPv6 and/or IPv4 protocols. Need to have at least one enabled!
|
||||||
|
protocol:
|
||||||
|
ipv4: true
|
||||||
|
ipv6: false
|
||||||
|
# Prefers IPv6 for DNS. Enable this on ISPs that don't have issues with IPv6
|
||||||
|
dnsPreferIPv6: false
|
||||||
|
# The hostname that autorun opens.
|
||||||
|
# - Use "auto" to let the server decide
|
||||||
|
# - Use options like 'localhost', 'st.example.com'
|
||||||
|
autorunHostname: "auto"
|
||||||
# Server port
|
# Server port
|
||||||
port: 8000
|
port: 8000
|
||||||
|
# Overrides the port for autorun in browser.
|
||||||
|
# - Use -1 to use the server port.
|
||||||
|
# - Specify a port to override the default.
|
||||||
|
autorunPortOverride: -1
|
||||||
# -- SECURITY CONFIGURATION --
|
# -- SECURITY CONFIGURATION --
|
||||||
# Toggle whitelist mode
|
# Toggle whitelist mode
|
||||||
whitelistMode: true
|
whitelistMode: true
|
||||||
@ -13,6 +27,7 @@ whitelistMode: true
|
|||||||
enableForwardedWhitelist: true
|
enableForwardedWhitelist: true
|
||||||
# Whitelist of allowed IP addresses
|
# Whitelist of allowed IP addresses
|
||||||
whitelist:
|
whitelist:
|
||||||
|
- ::1
|
||||||
- 127.0.0.1
|
- 127.0.0.1
|
||||||
# Toggle basic authentication for endpoints
|
# Toggle basic authentication for endpoints
|
||||||
basicAuthMode: false
|
basicAuthMode: false
|
||||||
@ -26,6 +41,11 @@ enableCorsProxy: false
|
|||||||
enableUserAccounts: false
|
enableUserAccounts: false
|
||||||
# Enable discreet login mode: hides user list on the login screen
|
# Enable discreet login mode: hides user list on the login screen
|
||||||
enableDiscreetLogin: false
|
enableDiscreetLogin: false
|
||||||
|
# User session timeout *in seconds* (defaults to 24 hours).
|
||||||
|
## Set to a positive number to expire session after a certain time of inactivity
|
||||||
|
## Set to 0 to expire session when the browser is closed
|
||||||
|
## Set to a negative number to disable session expiration
|
||||||
|
sessionTimeout: 86400
|
||||||
# Used to sign session cookies. Will be auto-generated if not set
|
# Used to sign session cookies. Will be auto-generated if not set
|
||||||
cookieSecret: ''
|
cookieSecret: ''
|
||||||
# Disable CSRF protection - NOT RECOMMENDED
|
# Disable CSRF protection - NOT RECOMMENDED
|
||||||
@ -35,6 +55,9 @@ securityOverride: false
|
|||||||
# -- ADVANCED CONFIGURATION --
|
# -- ADVANCED CONFIGURATION --
|
||||||
# Open the browser automatically
|
# Open the browser automatically
|
||||||
autorun: true
|
autorun: true
|
||||||
|
# Avoids using 'localhost' for autorun in auto mode.
|
||||||
|
# use if you don't have 'localhost' in your hosts file
|
||||||
|
avoidLocalhost: false
|
||||||
# Disable thumbnail generation
|
# Disable thumbnail generation
|
||||||
disableThumbnails: false
|
disableThumbnails: false
|
||||||
# Thumbnail quality (0-100)
|
# Thumbnail quality (0-100)
|
||||||
@ -98,10 +121,21 @@ mistral:
|
|||||||
# Enables prefilling of the reply with the last assistant message in the prompt
|
# Enables prefilling of the reply with the last assistant message in the prompt
|
||||||
# CAUTION: The prefix is echoed into the completion. You may want to use regex to trim it out.
|
# CAUTION: The prefix is echoed into the completion. You may want to use regex to trim it out.
|
||||||
enablePrefix: false
|
enablePrefix: false
|
||||||
|
# -- OLLAMA API CONFIGURATION --
|
||||||
|
ollama:
|
||||||
|
# Controls how long the model will stay loaded into memory following the request
|
||||||
|
# * -1: Keep the model loaded indefinitely
|
||||||
|
# * 0: Unload the model immediately after the request
|
||||||
|
# * N (any positive number): Keep the model loaded for N seconds after the request.
|
||||||
|
keepAlive: -1
|
||||||
|
# -- ANTHROPIC CLAUDE API CONFIGURATION --
|
||||||
|
claude:
|
||||||
|
# Enables caching of the system prompt (if supported).
|
||||||
|
# https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching
|
||||||
|
# -- IMPORTANT! --
|
||||||
|
# Use only when the prompt before the chat history is static and doesn't change between requests
|
||||||
|
# (e.g {{random}} macro or lorebooks not as in-chat injections).
|
||||||
|
# Otherwise, you'll just waste money on cache misses.
|
||||||
|
enableSystemPromptCache: false
|
||||||
# -- SERVER PLUGIN CONFIGURATION --
|
# -- SERVER PLUGIN CONFIGURATION --
|
||||||
enableServerPlugins: false
|
enableServerPlugins: false
|
||||||
# User session timeout *in seconds* (defaults to 24 hours).
|
|
||||||
## Set to a positive number to expire session after a certain time of inactivity
|
|
||||||
## Set to 0 to expire session when the browser is closed
|
|
||||||
## Set to a negative number to disable session expiration
|
|
||||||
sessionTimeout: 86400
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"chat_completion_source": "openai",
|
"chat_completion_source": "openai",
|
||||||
"openai_model": "gpt-3.5-turbo",
|
"openai_model": "gpt-4-turbo",
|
||||||
"claude_model": "claude-instant-v1",
|
"claude_model": "claude-3-5-sonnet-20240620",
|
||||||
"windowai_model": "",
|
"windowai_model": "",
|
||||||
"openrouter_model": "OR_Website",
|
"openrouter_model": "OR_Website",
|
||||||
"openrouter_use_fallback": false,
|
"openrouter_use_fallback": false,
|
||||||
@ -9,7 +9,7 @@
|
|||||||
"openrouter_group_models": false,
|
"openrouter_group_models": false,
|
||||||
"openrouter_sort_models": "alphabetically",
|
"openrouter_sort_models": "alphabetically",
|
||||||
"ai21_model": "j2-ultra",
|
"ai21_model": "j2-ultra",
|
||||||
"mistralai_model": "mistral-medium-latest",
|
"mistralai_model": "mistral-large-latest",
|
||||||
"custom_model": "",
|
"custom_model": "",
|
||||||
"custom_url": "",
|
"custom_url": "",
|
||||||
"custom_include_body": "",
|
"custom_include_body": "",
|
||||||
@ -22,7 +22,7 @@
|
|||||||
"count_penalty": 0,
|
"count_penalty": 0,
|
||||||
"top_p": 1,
|
"top_p": 1,
|
||||||
"top_k": 0,
|
"top_k": 0,
|
||||||
"top_a": 1,
|
"top_a": 0,
|
||||||
"min_p": 0,
|
"min_p": 0,
|
||||||
"repetition_penalty": 1,
|
"repetition_penalty": 1,
|
||||||
"openai_max_context": 4095,
|
"openai_max_context": 4095,
|
||||||
|
@ -610,9 +610,9 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"wi_format": "[Details of the fictional world the RP is set in:\n{0}]\n",
|
"wi_format": "{0}",
|
||||||
"openai_model": "gpt-3.5-turbo",
|
"openai_model": "gpt-4-turbo",
|
||||||
"claude_model": "claude-instant-v1",
|
"claude_model": "claude-3-5-sonnet-20240620",
|
||||||
"ai21_model": "j2-ultra",
|
"ai21_model": "j2-ultra",
|
||||||
"windowai_model": "",
|
"windowai_model": "",
|
||||||
"openrouter_model": "OR_Website",
|
"openrouter_model": "OR_Website",
|
||||||
|
5
index.d.ts
vendored
5
index.d.ts
vendored
@ -9,6 +9,11 @@ declare global {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The root directory for user data.
|
||||||
|
*/
|
||||||
|
var DATA_ROOT: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module 'express-session' {
|
declare module 'express-session' {
|
||||||
|
81
package-lock.json
generated
81
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "sillytavern",
|
"name": "sillytavern",
|
||||||
"version": "1.12.4",
|
"version": "1.12.5",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "sillytavern",
|
"name": "sillytavern",
|
||||||
"version": "1.12.4",
|
"version": "1.12.5",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -27,6 +27,7 @@
|
|||||||
"google-translate-api-browser": "^3.0.1",
|
"google-translate-api-browser": "^3.0.1",
|
||||||
"he": "^1.2.0",
|
"he": "^1.2.0",
|
||||||
"helmet": "^7.1.0",
|
"helmet": "^7.1.0",
|
||||||
|
"iconv-lite": "^0.6.3",
|
||||||
"ip-matching": "^2.1.2",
|
"ip-matching": "^2.1.2",
|
||||||
"ipaddr.js": "^2.0.1",
|
"ipaddr.js": "^2.0.1",
|
||||||
"jimp": "^0.22.10",
|
"jimp": "^0.22.10",
|
||||||
@ -42,7 +43,7 @@
|
|||||||
"rate-limiter-flexible": "^5.0.0",
|
"rate-limiter-flexible": "^5.0.0",
|
||||||
"response-time": "^2.3.2",
|
"response-time": "^2.3.2",
|
||||||
"sanitize-filename": "^1.6.3",
|
"sanitize-filename": "^1.6.3",
|
||||||
"sillytavern-transformers": "^2.14.6",
|
"sillytavern-transformers": "2.14.6",
|
||||||
"simple-git": "^3.19.1",
|
"simple-git": "^3.19.1",
|
||||||
"tiktoken": "^1.0.15",
|
"tiktoken": "^1.0.15",
|
||||||
"vectra": "^0.2.2",
|
"vectra": "^0.2.2",
|
||||||
@ -58,7 +59,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jquery": "^3.5.29",
|
"@types/jquery": "^3.5.29",
|
||||||
"eslint": "^8.55.0",
|
"eslint": "^8.57.0",
|
||||||
"jquery": "^3.6.4"
|
"jquery": "^3.6.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -166,9 +167,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/js": {
|
"node_modules/@eslint/js": {
|
||||||
"version": "8.55.0",
|
"version": "8.57.0",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.55.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz",
|
||||||
"integrity": "sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA==",
|
"integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -185,14 +186,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@humanwhocodes/config-array": {
|
"node_modules/@humanwhocodes/config-array": {
|
||||||
"version": "0.11.13",
|
"version": "0.11.14",
|
||||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz",
|
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
|
||||||
"integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==",
|
"integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==",
|
||||||
|
"deprecated": "Use @eslint/config-array instead",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@humanwhocodes/object-schema": "^2.0.1",
|
"@humanwhocodes/object-schema": "^2.0.2",
|
||||||
"debug": "^4.1.1",
|
"debug": "^4.3.1",
|
||||||
"minimatch": "^3.0.5"
|
"minimatch": "^3.0.5"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -200,9 +202,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@humanwhocodes/config-array/node_modules/debug": {
|
"node_modules/@humanwhocodes/config-array/node_modules/debug": {
|
||||||
"version": "4.3.4",
|
"version": "4.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
|
||||||
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
"integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -239,9 +241,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@humanwhocodes/object-schema": {
|
"node_modules/@humanwhocodes/object-schema": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
|
||||||
"integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==",
|
"integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
|
||||||
|
"deprecated": "Use @eslint/object-schema instead",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-3-Clause"
|
"license": "BSD-3-Clause"
|
||||||
},
|
},
|
||||||
@ -1490,6 +1493,18 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/body-parser/node_modules/iconv-lite": {
|
||||||
|
"version": "0.4.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||||
|
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"safer-buffer": ">= 2.1.2 < 3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/boolbase": {
|
"node_modules/boolbase": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
||||||
@ -2455,17 +2470,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint": {
|
"node_modules/eslint": {
|
||||||
"version": "8.55.0",
|
"version": "8.57.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.55.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz",
|
||||||
"integrity": "sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA==",
|
"integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.2.0",
|
"@eslint-community/eslint-utils": "^4.2.0",
|
||||||
"@eslint-community/regexpp": "^4.6.1",
|
"@eslint-community/regexpp": "^4.6.1",
|
||||||
"@eslint/eslintrc": "^2.1.4",
|
"@eslint/eslintrc": "^2.1.4",
|
||||||
"@eslint/js": "8.55.0",
|
"@eslint/js": "8.57.0",
|
||||||
"@humanwhocodes/config-array": "^0.11.13",
|
"@humanwhocodes/config-array": "^0.11.14",
|
||||||
"@humanwhocodes/module-importer": "^1.0.1",
|
"@humanwhocodes/module-importer": "^1.0.1",
|
||||||
"@nodelib/fs.walk": "^1.2.8",
|
"@nodelib/fs.walk": "^1.2.8",
|
||||||
"@ungap/structured-clone": "^1.2.0",
|
"@ungap/structured-clone": "^1.2.0",
|
||||||
@ -3280,12 +3295,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/iconv-lite": {
|
"node_modules/iconv-lite": {
|
||||||
"version": "0.4.24",
|
"version": "0.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"safer-buffer": ">= 2.1.2 < 3"
|
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@ -4616,6 +4631,18 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/raw-body/node_modules/iconv-lite": {
|
||||||
|
"version": "0.4.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||||
|
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"safer-buffer": ">= 2.1.2 < 3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/readable-stream": {
|
"node_modules/readable-stream": {
|
||||||
"version": "2.3.8",
|
"version": "2.3.8",
|
||||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
"google-translate-api-browser": "^3.0.1",
|
"google-translate-api-browser": "^3.0.1",
|
||||||
"he": "^1.2.0",
|
"he": "^1.2.0",
|
||||||
"helmet": "^7.1.0",
|
"helmet": "^7.1.0",
|
||||||
|
"iconv-lite": "^0.6.3",
|
||||||
"ip-matching": "^2.1.2",
|
"ip-matching": "^2.1.2",
|
||||||
"ipaddr.js": "^2.0.1",
|
"ipaddr.js": "^2.0.1",
|
||||||
"jimp": "^0.22.10",
|
"jimp": "^0.22.10",
|
||||||
@ -32,7 +33,7 @@
|
|||||||
"rate-limiter-flexible": "^5.0.0",
|
"rate-limiter-flexible": "^5.0.0",
|
||||||
"response-time": "^2.3.2",
|
"response-time": "^2.3.2",
|
||||||
"sanitize-filename": "^1.6.3",
|
"sanitize-filename": "^1.6.3",
|
||||||
"sillytavern-transformers": "^2.14.6",
|
"sillytavern-transformers": "2.14.6",
|
||||||
"simple-git": "^3.19.1",
|
"simple-git": "^3.19.1",
|
||||||
"tiktoken": "^1.0.15",
|
"tiktoken": "^1.0.15",
|
||||||
"vectra": "^0.2.2",
|
"vectra": "^0.2.2",
|
||||||
@ -70,7 +71,7 @@
|
|||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/SillyTavern/SillyTavern.git"
|
"url": "https://github.com/SillyTavern/SillyTavern.git"
|
||||||
},
|
},
|
||||||
"version": "1.12.4",
|
"version": "1.12.5",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node server.js",
|
"start": "node server.js",
|
||||||
"start:no-csrf": "node server.js --disableCsrf",
|
"start:no-csrf": "node server.js --disableCsrf",
|
||||||
@ -90,7 +91,7 @@
|
|||||||
"main": "server.js",
|
"main": "server.js",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jquery": "^3.5.29",
|
"@types/jquery": "^3.5.29",
|
||||||
"eslint": "^8.55.0",
|
"eslint": "^8.57.0",
|
||||||
"jquery": "^3.6.4"
|
"jquery": "^3.6.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,6 +99,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#bulk_tag_shadow_popup #bulk_tag_popup #dialogue_popup_controls .menu_button {
|
#bulk_tag_shadow_popup #bulk_tag_popup #dialogue_popup_controls .menu_button {
|
||||||
width: 100px;
|
width: unset;
|
||||||
padding: 0.25em;
|
padding: 0.25em;
|
||||||
}
|
}
|
||||||
|
@ -120,6 +120,14 @@
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.world_entry .inline-drawer-header {
|
||||||
|
cursor: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
.world_entry .killSwitch {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.world_entry_form_control input[type=button] {
|
.world_entry_form_control input[type=button] {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
@ -173,6 +181,10 @@
|
|||||||
width: 7em;
|
width: 7em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.world_entry .killSwitch.fa-toggle-on {
|
||||||
|
color: var(--SmartThemeQuoteColor);
|
||||||
|
}
|
||||||
|
|
||||||
.wi-card-entry {
|
.wi-card-entry {
|
||||||
border: 1px solid;
|
border: 1px solid;
|
||||||
border-color: var(--SmartThemeBorderColor);
|
border-color: var(--SmartThemeBorderColor);
|
||||||
|
5
public/global.d.ts
vendored
5
public/global.d.ts
vendored
@ -14,6 +14,11 @@ declare var isProbablyReaderable;
|
|||||||
declare var ePub;
|
declare var ePub;
|
||||||
declare var ai;
|
declare var ai;
|
||||||
|
|
||||||
|
declare var SillyTavern: {
|
||||||
|
getContext(): any;
|
||||||
|
llm: any;
|
||||||
|
};
|
||||||
|
|
||||||
// Jquery plugins
|
// Jquery plugins
|
||||||
interface JQuery {
|
interface JQuery {
|
||||||
nanogallery2(options?: any): JQuery;
|
nanogallery2(options?: any): JQuery;
|
||||||
|
3
public/img/blockentropy.svg
Normal file
3
public/img/blockentropy.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg id="Layer_2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 236.38 282.41">
|
||||||
|
<path d="M126.55,0v54.44l-79.87,33.76v93.95l27.53-12.94,43.08,31.09.04-.05v.09l55.21-31.44.13-.08v-80.06l-55.34,24.92v80.2l-42.55-30.7h-.02s0-81.16,0-81.16l57.02-24.11V9.23l93.54,56.12v22.51l-24.34,11.53,1.84,90.56-88.45,51.47-.13.08v34.46L5.23,198.97v-65.56H0v66.92c0,.85.41,1.64,1.11,2.14l113.13,79.91v.05l.04-.02h0s0,0,0,0l121.97-73.54.13-.08v-126.13l-5.84,2.76v-22.94h-.3l.11-.18L126.55,0Z" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 509 B |
149
public/img/step-into.svg
Normal file
149
public/img/step-into.svg
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="48"
|
||||||
|
height="48"
|
||||||
|
viewBox="0 0 48 48"
|
||||||
|
version="1.1"
|
||||||
|
id="svg2120"
|
||||||
|
xml:space="preserve"
|
||||||
|
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||||
|
sodipodi:docname="step-into.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||||
|
id="namedview2122"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="11.313708"
|
||||||
|
inkscape:cx="58.910834"
|
||||||
|
inkscape:cy="25.323262"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="992"
|
||||||
|
inkscape:window-x="-8"
|
||||||
|
inkscape:window-y="-8"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="g2714" /><defs
|
||||||
|
id="defs2117"><inkscape:path-effect
|
||||||
|
effect="spiro"
|
||||||
|
id="path-effect2144"
|
||||||
|
is_visible="true"
|
||||||
|
lpeversion="1" /><inkscape:path-effect
|
||||||
|
effect="bspline"
|
||||||
|
id="path-effect2138"
|
||||||
|
is_visible="true"
|
||||||
|
lpeversion="1"
|
||||||
|
weight="33.333333"
|
||||||
|
steps="2"
|
||||||
|
helper_size="0"
|
||||||
|
apply_no_weight="true"
|
||||||
|
apply_with_weight="true"
|
||||||
|
only_selected="false" /></defs><g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer4"
|
||||||
|
inkscape:label="img"
|
||||||
|
style="display:none"><image
|
||||||
|
width="305.68866"
|
||||||
|
height="70.374367"
|
||||||
|
preserveAspectRatio="none"
|
||||||
|
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARYAAABACAYAAADf7VgRAAAABHNCSVQICAgIfAhkiAAABdxJREFU
|
||||||
|
eJzt3T9vo0gYBvB3by+SsZRiKCI5SCkyJS6dzny3+26UpoSSFJFMOaTyUETaK3zDAQYbzGBgeH7S
|
||||||
|
SutN1kKj8cM7fxj/2u/3fwgAQKO/xr4AADAPggUAtEOwAIB2CBYA0A7BAgDaIVgAQDsECwBoh2AB
|
||||||
|
AO0QLACgHYIFALRDsACAdn+PfQFw5nkeWZZFRERSSvJ9f+Qrmg7XdSnLMorjeOxLgZYQLDBpruuS
|
||||||
|
4zj5a4TLPGAoBJNVDRXOOXHOR7wiaAvBApNUDRUF4TIPCBaYnKZQURAu04dggUm5FSoKwmXaECww
|
||||||
|
GW1DRUG4TNfDV4VUx1mtViSEICKiLMtIStn4f4qdJ8syOh6Pw14kPFxdqAghyLKs0jI8EeWvif7v
|
||||||
|
G1gtmpbBg8V1XbJtm4jKHYKoHBhCCIrjuDZkqr+HYDFLU6gEQUCe55X+3ff90p4fIoTLFA0WLI7j
|
||||||
|
kOu6rX/ftm2ybZuEEBRF0dUKBsxxLVSaIFymT/scC2OMdrtdp1Apsm2bPM8jzvlFhQPmybKs9PpW
|
||||||
|
qCi+71/cfKrvZQrXdYkxNvZldKK1YmGM0cfHR+3PhBAkpaTv728iIjqdTmTbNq1WK7Jtu3aYxBij
|
||||||
|
KIp0XuLoXNelz8/PzhWZZVnkuq5x1ZyqMDjnrUNFKVYuURQZOUTe7XZ5NR+GIaVpOvYltaItWCzL
|
||||||
|
qg0VNXdS1yDFf2OM0Xa7LQWMbdt3Vz5TVOwkQRC0DgjLsvK5BlPD5d5Jed/3yXEcI0OlOj+53W5n
|
||||||
|
Ey5ahkLFjq9IKelwOFAQBK0aIk1T8n3/ooOohp27aifZ7XathnrVtjUtbJU+wWBiqBDRRWWrwqXv
|
||||||
|
sKi40jYULRVLtaP3eTo3iiLKssy4/QlJkpSGfCpcrlUudYFNhAnKpZBSUhAEpZvQvZWLGkoXb9RS
|
||||||
|
SkqSZJD+1Lti4ZxfVBVhGPZ6zziOjfvwpGlKYRhe3IGaKpemUDkcDrMohUEPFS59KhfOOXmed/E5
|
||||||
|
tSwr/5nuCqZ3xfL6+lp63bXjq0naJVDhUpxLqgsXhAoU9alcGGM3q3/VB3WeAdSrYqmO1aSUnTs+
|
||||||
|
Yyyf0Kz+MVFT5XILQmXZ7q1cmlZpqyzL6vQ4xS29KpZqtZIkSa+LWYq6yuUahIo56irRLqr95Vrl
|
||||||
|
0nUksNlstE2EawsWKaVx8yJDahsuCBWzDLEa0xQu6/W68/vo0itYdIzJumyIMs2tcEGoQFt1/We1
|
||||||
|
WvV+j3vhzNuRNYULQsVMfTc2Nn346/qLEKLTtg2d+4FGOzbhdDrlz3aYtIv0HtVwQaiYq0+V33W1
|
||||||
|
UJ0U0LYS0fms1cOCpelQHjzNfKbCRf0dzqSUed9Ych+5ZwuClJLCMGy1MqQevdHl136//6Pt3Rqo
|
||||||
|
Z2SuMfUhMoC++u5runXSngognTe0wSuWup25dd7f3/MnoAHgTMdmyTiOKUmSh27pHzRY1Jbhtr/7
|
||||||
|
+vqKJWuA/+jcga022FWP+RzKpFaFlrK1vw5jjNbrdX4W8K1zgJdiye1S9xR734n9R7XdoMFS3Zl7
|
||||||
|
i6nb+K+pOxyLcz7IuHdO0C7necfi8GVOq4W/397e/hnqzdfrNb28vLT+fSklfX19DXU5k8M5p+12
|
||||||
|
W/uzp6enfGl+Lp1JF7TL2c/PD6VpSs/Pz7ML00G/V0h9vUdbSylxidrPPy3t7F+0S5maG5lTqBAN
|
||||||
|
HCzFPQhtLGnitsspcCaeGNcE7WKGwb8Jse3ZrkKI2aVyH13mk+oOGzcV2sUMgweLmmy7Fi5dT2ef
|
||||||
|
O3wY6qFdzPGQ5WZ1ULY6LU4dEHU8HilJkkVVKvda0vxTF2iXaXroPpYlzaFco+ae2t6hl/LhQbuY
|
||||||
|
Y/ChENTrctrekk7mQ7uYAcEykjiOW91xl3YyH9rFDAiWEd1aMRNC9P4qlTlCu8zfQ45NgOscx6HN
|
||||||
|
ZlOa1M6ybPF3ZLTLfCFYAEA7DIUAQDsECwBoh2ABAO0QLACgHYIFALRDsACAdggWANAOwQIA2iFY
|
||||||
|
AEA7BAsAaIdgAQDt/gUoDXNStc/rMQAAAABJRU5ErkJggg==
|
||||||
|
"
|
||||||
|
id="image2132"
|
||||||
|
x="-82"
|
||||||
|
y="-11.9" /></g><g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer5"
|
||||||
|
inkscape:label="dot" /><g
|
||||||
|
inkscape:label="over"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
style="display:none"><ellipse
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none"
|
||||||
|
id="path2637"
|
||||||
|
cx="24"
|
||||||
|
cy="33"
|
||||||
|
rx="4.3600898"
|
||||||
|
ry="4.3600893" /><path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 9,24 C 9.7590836,20.626295 11.695032,17.529367 14.395314,15.369142 17.095595,13.208917 20.541953,12 24,12 c 3.458047,0 6.904405,1.208917 9.604686,3.369142 C 36.304968,17.529367 38.240916,20.626295 39,24"
|
||||||
|
id="path2142"
|
||||||
|
inkscape:path-effect="#path-effect2144"
|
||||||
|
inkscape:original-d="m 9,24 c 4.959859,-3.824406 10.021901,-8.173595 15,-12 4.978099,-3.8264055 10.285024,8.398237 15,12"
|
||||||
|
sodipodi:nodetypes="csc" /><path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 26,22 H 39 V 9"
|
||||||
|
id="path2626"
|
||||||
|
sodipodi:nodetypes="ccc" /></g><g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer6"
|
||||||
|
inkscape:label="into"
|
||||||
|
style="display:inline"><ellipse
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none"
|
||||||
|
id="path2637-3"
|
||||||
|
cx="24"
|
||||||
|
cy="38.5"
|
||||||
|
rx="4.3600898"
|
||||||
|
ry="4.3600893" /><path
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 24,2 V 24"
|
||||||
|
id="path2668"
|
||||||
|
sodipodi:nodetypes="cc" /><path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 14.807612,16.994936 24,26.187324 33.192388,16.994936"
|
||||||
|
id="path2626-8"
|
||||||
|
sodipodi:nodetypes="ccc" /></g><g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="g2714"
|
||||||
|
inkscape:label="out"
|
||||||
|
style="display:none"><ellipse
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none"
|
||||||
|
id="ellipse2708"
|
||||||
|
cx="24"
|
||||||
|
cy="38.5"
|
||||||
|
rx="4.3600898"
|
||||||
|
ry="4.3600893" /><path
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 24,29.722858 V 7.7228579"
|
||||||
|
id="path2710"
|
||||||
|
sodipodi:nodetypes="cc" /><path
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 33.192388,14.727922 24,5.5355339 14.807612,14.727922"
|
||||||
|
id="path2712"
|
||||||
|
sodipodi:nodetypes="ccc" /></g></svg>
|
After Width: | Height: | Size: 6.3 KiB |
149
public/img/step-out.svg
Normal file
149
public/img/step-out.svg
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="48"
|
||||||
|
height="48"
|
||||||
|
viewBox="0 0 48 48"
|
||||||
|
version="1.1"
|
||||||
|
id="svg2120"
|
||||||
|
xml:space="preserve"
|
||||||
|
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||||
|
sodipodi:docname="step-out.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||||
|
id="namedview2122"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="11.313708"
|
||||||
|
inkscape:cx="58.910834"
|
||||||
|
inkscape:cy="25.323262"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="992"
|
||||||
|
inkscape:window-x="-8"
|
||||||
|
inkscape:window-y="-8"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="g2714" /><defs
|
||||||
|
id="defs2117"><inkscape:path-effect
|
||||||
|
effect="spiro"
|
||||||
|
id="path-effect2144"
|
||||||
|
is_visible="true"
|
||||||
|
lpeversion="1" /><inkscape:path-effect
|
||||||
|
effect="bspline"
|
||||||
|
id="path-effect2138"
|
||||||
|
is_visible="true"
|
||||||
|
lpeversion="1"
|
||||||
|
weight="33.333333"
|
||||||
|
steps="2"
|
||||||
|
helper_size="0"
|
||||||
|
apply_no_weight="true"
|
||||||
|
apply_with_weight="true"
|
||||||
|
only_selected="false" /></defs><g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer4"
|
||||||
|
inkscape:label="img"
|
||||||
|
style="display:none"><image
|
||||||
|
width="305.68866"
|
||||||
|
height="70.374367"
|
||||||
|
preserveAspectRatio="none"
|
||||||
|
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARYAAABACAYAAADf7VgRAAAABHNCSVQICAgIfAhkiAAABdxJREFU
|
||||||
|
eJzt3T9vo0gYBvB3by+SsZRiKCI5SCkyJS6dzny3+26UpoSSFJFMOaTyUETaK3zDAQYbzGBgeH7S
|
||||||
|
SutN1kKj8cM7fxj/2u/3fwgAQKO/xr4AADAPggUAtEOwAIB2CBYA0A7BAgDaIVgAQDsECwBoh2AB
|
||||||
|
AO0QLACgHYIFALRDsACAdn+PfQFw5nkeWZZFRERSSvJ9f+Qrmg7XdSnLMorjeOxLgZYQLDBpruuS
|
||||||
|
4zj5a4TLPGAoBJNVDRXOOXHOR7wiaAvBApNUDRUF4TIPCBaYnKZQURAu04dggUm5FSoKwmXaECww
|
||||||
|
GW1DRUG4TNfDV4VUx1mtViSEICKiLMtIStn4f4qdJ8syOh6Pw14kPFxdqAghyLKs0jI8EeWvif7v
|
||||||
|
G1gtmpbBg8V1XbJtm4jKHYKoHBhCCIrjuDZkqr+HYDFLU6gEQUCe55X+3ff90p4fIoTLFA0WLI7j
|
||||||
|
kOu6rX/ftm2ybZuEEBRF0dUKBsxxLVSaIFymT/scC2OMdrtdp1Apsm2bPM8jzvlFhQPmybKs9PpW
|
||||||
|
qCi+71/cfKrvZQrXdYkxNvZldKK1YmGM0cfHR+3PhBAkpaTv728iIjqdTmTbNq1WK7Jtu3aYxBij
|
||||||
|
KIp0XuLoXNelz8/PzhWZZVnkuq5x1ZyqMDjnrUNFKVYuURQZOUTe7XZ5NR+GIaVpOvYltaItWCzL
|
||||||
|
qg0VNXdS1yDFf2OM0Xa7LQWMbdt3Vz5TVOwkQRC0DgjLsvK5BlPD5d5Jed/3yXEcI0OlOj+53W5n
|
||||||
|
Ey5ahkLFjq9IKelwOFAQBK0aIk1T8n3/ooOohp27aifZ7XathnrVtjUtbJU+wWBiqBDRRWWrwqXv
|
||||||
|
sKi40jYULRVLtaP3eTo3iiLKssy4/QlJkpSGfCpcrlUudYFNhAnKpZBSUhAEpZvQvZWLGkoXb9RS
|
||||||
|
SkqSZJD+1Lti4ZxfVBVhGPZ6zziOjfvwpGlKYRhe3IGaKpemUDkcDrMohUEPFS59KhfOOXmed/E5
|
||||||
|
tSwr/5nuCqZ3xfL6+lp63bXjq0naJVDhUpxLqgsXhAoU9alcGGM3q3/VB3WeAdSrYqmO1aSUnTs+
|
||||||
|
Yyyf0Kz+MVFT5XILQmXZ7q1cmlZpqyzL6vQ4xS29KpZqtZIkSa+LWYq6yuUahIo56irRLqr95Vrl
|
||||||
|
0nUksNlstE2EawsWKaVx8yJDahsuCBWzDLEa0xQu6/W68/vo0itYdIzJumyIMs2tcEGoQFt1/We1
|
||||||
|
WvV+j3vhzNuRNYULQsVMfTc2Nn346/qLEKLTtg2d+4FGOzbhdDrlz3aYtIv0HtVwQaiYq0+V33W1
|
||||||
|
UJ0U0LYS0fms1cOCpelQHjzNfKbCRf0dzqSUed9Ych+5ZwuClJLCMGy1MqQevdHl136//6Pt3Rqo
|
||||||
|
Z2SuMfUhMoC++u5runXSngognTe0wSuWup25dd7f3/MnoAHgTMdmyTiOKUmSh27pHzRY1Jbhtr/7
|
||||||
|
+vqKJWuA/+jcga022FWP+RzKpFaFlrK1vw5jjNbrdX4W8K1zgJdiye1S9xR734n9R7XdoMFS3Zl7
|
||||||
|
i6nb+K+pOxyLcz7IuHdO0C7necfi8GVOq4W/397e/hnqzdfrNb28vLT+fSklfX19DXU5k8M5p+12
|
||||||
|
W/uzp6enfGl+Lp1JF7TL2c/PD6VpSs/Pz7ML00G/V0h9vUdbSylxidrPPy3t7F+0S5maG5lTqBAN
|
||||||
|
HCzFPQhtLGnitsspcCaeGNcE7WKGwb8Jse3ZrkKI2aVyH13mk+oOGzcV2sUMgweLmmy7Fi5dT2ef
|
||||||
|
O3wY6qFdzPGQ5WZ1ULY6LU4dEHU8HilJkkVVKvda0vxTF2iXaXroPpYlzaFco+ae2t6hl/LhQbuY
|
||||||
|
Y/ChENTrctrekk7mQ7uYAcEykjiOW91xl3YyH9rFDAiWEd1aMRNC9P4qlTlCu8zfQ45NgOscx6HN
|
||||||
|
ZlOa1M6ybPF3ZLTLfCFYAEA7DIUAQDsECwBoh2ABAO0QLACgHYIFALRDsACAdggWANAOwQIA2iFY
|
||||||
|
AEA7BAsAaIdgAQDt/gUoDXNStc/rMQAAAABJRU5ErkJggg==
|
||||||
|
"
|
||||||
|
id="image2132"
|
||||||
|
x="-82"
|
||||||
|
y="-11.9" /></g><g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer5"
|
||||||
|
inkscape:label="dot" /><g
|
||||||
|
inkscape:label="over"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
style="display:none"><ellipse
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none"
|
||||||
|
id="path2637"
|
||||||
|
cx="24"
|
||||||
|
cy="33"
|
||||||
|
rx="4.3600898"
|
||||||
|
ry="4.3600893" /><path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 9,24 C 9.7590836,20.626295 11.695032,17.529367 14.395314,15.369142 17.095595,13.208917 20.541953,12 24,12 c 3.458047,0 6.904405,1.208917 9.604686,3.369142 C 36.304968,17.529367 38.240916,20.626295 39,24"
|
||||||
|
id="path2142"
|
||||||
|
inkscape:path-effect="#path-effect2144"
|
||||||
|
inkscape:original-d="m 9,24 c 4.959859,-3.824406 10.021901,-8.173595 15,-12 4.978099,-3.8264055 10.285024,8.398237 15,12"
|
||||||
|
sodipodi:nodetypes="csc" /><path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 26,22 H 39 V 9"
|
||||||
|
id="path2626"
|
||||||
|
sodipodi:nodetypes="ccc" /></g><g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer6"
|
||||||
|
inkscape:label="into"
|
||||||
|
style="display:none"><ellipse
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none"
|
||||||
|
id="path2637-3"
|
||||||
|
cx="24"
|
||||||
|
cy="38.5"
|
||||||
|
rx="4.3600898"
|
||||||
|
ry="4.3600893" /><path
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 24,2 V 24"
|
||||||
|
id="path2668"
|
||||||
|
sodipodi:nodetypes="cc" /><path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 14.807612,16.994936 24,26.187324 33.192388,16.994936"
|
||||||
|
id="path2626-8"
|
||||||
|
sodipodi:nodetypes="ccc" /></g><g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="g2714"
|
||||||
|
inkscape:label="out"
|
||||||
|
style="display:inline"><ellipse
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none"
|
||||||
|
id="ellipse2708"
|
||||||
|
cx="24"
|
||||||
|
cy="38.5"
|
||||||
|
rx="4.3600898"
|
||||||
|
ry="4.3600893" /><path
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 24,29.722858 V 7.7228579"
|
||||||
|
id="path2710"
|
||||||
|
sodipodi:nodetypes="cc" /><path
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 33.192388,14.727922 24,5.5355339 14.807612,14.727922"
|
||||||
|
id="path2712"
|
||||||
|
sodipodi:nodetypes="ccc" /></g></svg>
|
After Width: | Height: | Size: 6.3 KiB |
149
public/img/step-over.svg
Normal file
149
public/img/step-over.svg
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="48"
|
||||||
|
height="48"
|
||||||
|
viewBox="0 0 48 48"
|
||||||
|
version="1.1"
|
||||||
|
id="svg2120"
|
||||||
|
xml:space="preserve"
|
||||||
|
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||||
|
sodipodi:docname="step-over.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||||
|
id="namedview2122"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="11.313708"
|
||||||
|
inkscape:cx="58.910834"
|
||||||
|
inkscape:cy="25.323262"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="992"
|
||||||
|
inkscape:window-x="-8"
|
||||||
|
inkscape:window-y="-8"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="g2714" /><defs
|
||||||
|
id="defs2117"><inkscape:path-effect
|
||||||
|
effect="spiro"
|
||||||
|
id="path-effect2144"
|
||||||
|
is_visible="true"
|
||||||
|
lpeversion="1" /><inkscape:path-effect
|
||||||
|
effect="bspline"
|
||||||
|
id="path-effect2138"
|
||||||
|
is_visible="true"
|
||||||
|
lpeversion="1"
|
||||||
|
weight="33.333333"
|
||||||
|
steps="2"
|
||||||
|
helper_size="0"
|
||||||
|
apply_no_weight="true"
|
||||||
|
apply_with_weight="true"
|
||||||
|
only_selected="false" /></defs><g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer4"
|
||||||
|
inkscape:label="img"
|
||||||
|
style="display:none"><image
|
||||||
|
width="305.68866"
|
||||||
|
height="70.374367"
|
||||||
|
preserveAspectRatio="none"
|
||||||
|
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARYAAABACAYAAADf7VgRAAAABHNCSVQICAgIfAhkiAAABdxJREFU
|
||||||
|
eJzt3T9vo0gYBvB3by+SsZRiKCI5SCkyJS6dzny3+26UpoSSFJFMOaTyUETaK3zDAQYbzGBgeH7S
|
||||||
|
SutN1kKj8cM7fxj/2u/3fwgAQKO/xr4AADAPggUAtEOwAIB2CBYA0A7BAgDaIVgAQDsECwBoh2AB
|
||||||
|
AO0QLACgHYIFALRDsACAdn+PfQFw5nkeWZZFRERSSvJ9f+Qrmg7XdSnLMorjeOxLgZYQLDBpruuS
|
||||||
|
4zj5a4TLPGAoBJNVDRXOOXHOR7wiaAvBApNUDRUF4TIPCBaYnKZQURAu04dggUm5FSoKwmXaECww
|
||||||
|
GW1DRUG4TNfDV4VUx1mtViSEICKiLMtIStn4f4qdJ8syOh6Pw14kPFxdqAghyLKs0jI8EeWvif7v
|
||||||
|
G1gtmpbBg8V1XbJtm4jKHYKoHBhCCIrjuDZkqr+HYDFLU6gEQUCe55X+3ff90p4fIoTLFA0WLI7j
|
||||||
|
kOu6rX/ftm2ybZuEEBRF0dUKBsxxLVSaIFymT/scC2OMdrtdp1Apsm2bPM8jzvlFhQPmybKs9PpW
|
||||||
|
qCi+71/cfKrvZQrXdYkxNvZldKK1YmGM0cfHR+3PhBAkpaTv728iIjqdTmTbNq1WK7Jtu3aYxBij
|
||||||
|
KIp0XuLoXNelz8/PzhWZZVnkuq5x1ZyqMDjnrUNFKVYuURQZOUTe7XZ5NR+GIaVpOvYltaItWCzL
|
||||||
|
qg0VNXdS1yDFf2OM0Xa7LQWMbdt3Vz5TVOwkQRC0DgjLsvK5BlPD5d5Jed/3yXEcI0OlOj+53W5n
|
||||||
|
Ey5ahkLFjq9IKelwOFAQBK0aIk1T8n3/ooOohp27aifZ7XathnrVtjUtbJU+wWBiqBDRRWWrwqXv
|
||||||
|
sKi40jYULRVLtaP3eTo3iiLKssy4/QlJkpSGfCpcrlUudYFNhAnKpZBSUhAEpZvQvZWLGkoXb9RS
|
||||||
|
SkqSZJD+1Lti4ZxfVBVhGPZ6zziOjfvwpGlKYRhe3IGaKpemUDkcDrMohUEPFS59KhfOOXmed/E5
|
||||||
|
tSwr/5nuCqZ3xfL6+lp63bXjq0naJVDhUpxLqgsXhAoU9alcGGM3q3/VB3WeAdSrYqmO1aSUnTs+
|
||||||
|
Yyyf0Kz+MVFT5XILQmXZ7q1cmlZpqyzL6vQ4xS29KpZqtZIkSa+LWYq6yuUahIo56irRLqr95Vrl
|
||||||
|
0nUksNlstE2EawsWKaVx8yJDahsuCBWzDLEa0xQu6/W68/vo0itYdIzJumyIMs2tcEGoQFt1/We1
|
||||||
|
WvV+j3vhzNuRNYULQsVMfTc2Nn346/qLEKLTtg2d+4FGOzbhdDrlz3aYtIv0HtVwQaiYq0+V33W1
|
||||||
|
UJ0U0LYS0fms1cOCpelQHjzNfKbCRf0dzqSUed9Ych+5ZwuClJLCMGy1MqQevdHl136//6Pt3Rqo
|
||||||
|
Z2SuMfUhMoC++u5runXSngognTe0wSuWup25dd7f3/MnoAHgTMdmyTiOKUmSh27pHzRY1Jbhtr/7
|
||||||
|
+vqKJWuA/+jcga022FWP+RzKpFaFlrK1vw5jjNbrdX4W8K1zgJdiye1S9xR734n9R7XdoMFS3Zl7
|
||||||
|
i6nb+K+pOxyLcz7IuHdO0C7necfi8GVOq4W/397e/hnqzdfrNb28vLT+fSklfX19DXU5k8M5p+12
|
||||||
|
W/uzp6enfGl+Lp1JF7TL2c/PD6VpSs/Pz7ML00G/V0h9vUdbSylxidrPPy3t7F+0S5maG5lTqBAN
|
||||||
|
HCzFPQhtLGnitsspcCaeGNcE7WKGwb8Jse3ZrkKI2aVyH13mk+oOGzcV2sUMgweLmmy7Fi5dT2ef
|
||||||
|
O3wY6qFdzPGQ5WZ1ULY6LU4dEHU8HilJkkVVKvda0vxTF2iXaXroPpYlzaFco+ae2t6hl/LhQbuY
|
||||||
|
Y/ChENTrctrekk7mQ7uYAcEykjiOW91xl3YyH9rFDAiWEd1aMRNC9P4qlTlCu8zfQ45NgOscx6HN
|
||||||
|
ZlOa1M6ybPF3ZLTLfCFYAEA7DIUAQDsECwBoh2ABAO0QLACgHYIFALRDsACAdggWANAOwQIA2iFY
|
||||||
|
AEA7BAsAaIdgAQDt/gUoDXNStc/rMQAAAABJRU5ErkJggg==
|
||||||
|
"
|
||||||
|
id="image2132"
|
||||||
|
x="-82"
|
||||||
|
y="-11.9" /></g><g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer5"
|
||||||
|
inkscape:label="dot" /><g
|
||||||
|
inkscape:label="over"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
style="display:inline"><ellipse
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none"
|
||||||
|
id="path2637"
|
||||||
|
cx="24"
|
||||||
|
cy="33"
|
||||||
|
rx="4.3600898"
|
||||||
|
ry="4.3600893" /><path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 9,24 C 9.7590836,20.626295 11.695032,17.529367 14.395314,15.369142 17.095595,13.208917 20.541953,12 24,12 c 3.458047,0 6.904405,1.208917 9.604686,3.369142 C 36.304968,17.529367 38.240916,20.626295 39,24"
|
||||||
|
id="path2142"
|
||||||
|
inkscape:path-effect="#path-effect2144"
|
||||||
|
inkscape:original-d="m 9,24 c 4.959859,-3.824406 10.021901,-8.173595 15,-12 4.978099,-3.8264055 10.285024,8.398237 15,12"
|
||||||
|
sodipodi:nodetypes="csc" /><path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 26,22 H 39 V 9"
|
||||||
|
id="path2626"
|
||||||
|
sodipodi:nodetypes="ccc" /></g><g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer6"
|
||||||
|
inkscape:label="into"
|
||||||
|
style="display:none"><ellipse
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none"
|
||||||
|
id="path2637-3"
|
||||||
|
cx="24"
|
||||||
|
cy="38.5"
|
||||||
|
rx="4.3600898"
|
||||||
|
ry="4.3600893" /><path
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 24,2 V 24"
|
||||||
|
id="path2668"
|
||||||
|
sodipodi:nodetypes="cc" /><path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 14.807612,16.994936 24,26.187324 33.192388,16.994936"
|
||||||
|
id="path2626-8"
|
||||||
|
sodipodi:nodetypes="ccc" /></g><g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="g2714"
|
||||||
|
inkscape:label="out"
|
||||||
|
style="display:none"><ellipse
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none"
|
||||||
|
id="ellipse2708"
|
||||||
|
cx="24"
|
||||||
|
cy="38.5"
|
||||||
|
rx="4.3600898"
|
||||||
|
ry="4.3600893" /><path
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 24,29.722858 V 7.7228579"
|
||||||
|
id="path2710"
|
||||||
|
sodipodi:nodetypes="cc" /><path
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 33.192388,14.727922 24,5.5355339 14.807612,14.727922"
|
||||||
|
id="path2712"
|
||||||
|
sodipodi:nodetypes="ccc" /></g></svg>
|
After Width: | Height: | Size: 6.3 KiB |
218
public/img/step-resume.svg
Normal file
218
public/img/step-resume.svg
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="48"
|
||||||
|
height="48"
|
||||||
|
viewBox="0 0 48 48"
|
||||||
|
version="1.1"
|
||||||
|
id="svg2120"
|
||||||
|
xml:space="preserve"
|
||||||
|
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||||
|
sodipodi:docname="step-resume.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||||
|
id="namedview2122"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="11.315"
|
||||||
|
inkscape:cx="4.7282369"
|
||||||
|
inkscape:cy="24.259832"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="992"
|
||||||
|
inkscape:window-x="-8"
|
||||||
|
inkscape:window-y="-8"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer8" /><defs
|
||||||
|
id="defs2117"><inkscape:path-effect
|
||||||
|
effect="spiro"
|
||||||
|
id="path-effect2144"
|
||||||
|
is_visible="true"
|
||||||
|
lpeversion="1" /><inkscape:path-effect
|
||||||
|
effect="bspline"
|
||||||
|
id="path-effect2138"
|
||||||
|
is_visible="true"
|
||||||
|
lpeversion="1"
|
||||||
|
weight="33.333333"
|
||||||
|
steps="2"
|
||||||
|
helper_size="0"
|
||||||
|
apply_no_weight="true"
|
||||||
|
apply_with_weight="true"
|
||||||
|
only_selected="false" /></defs><g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer4"
|
||||||
|
inkscape:label="img"
|
||||||
|
style="display:none"><image
|
||||||
|
width="305.68866"
|
||||||
|
height="70.374367"
|
||||||
|
preserveAspectRatio="none"
|
||||||
|
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARYAAABACAYAAADf7VgRAAAABHNCSVQICAgIfAhkiAAABdxJREFU
|
||||||
|
eJzt3T9vo0gYBvB3by+SsZRiKCI5SCkyJS6dzny3+26UpoSSFJFMOaTyUETaK3zDAQYbzGBgeH7S
|
||||||
|
SutN1kKj8cM7fxj/2u/3fwgAQKO/xr4AADAPggUAtEOwAIB2CBYA0A7BAgDaIVgAQDsECwBoh2AB
|
||||||
|
AO0QLACgHYIFALRDsACAdn+PfQFw5nkeWZZFRERSSvJ9f+Qrmg7XdSnLMorjeOxLgZYQLDBpruuS
|
||||||
|
4zj5a4TLPGAoBJNVDRXOOXHOR7wiaAvBApNUDRUF4TIPCBaYnKZQURAu04dggUm5FSoKwmXaECww
|
||||||
|
GW1DRUG4TNfDV4VUx1mtViSEICKiLMtIStn4f4qdJ8syOh6Pw14kPFxdqAghyLKs0jI8EeWvif7v
|
||||||
|
G1gtmpbBg8V1XbJtm4jKHYKoHBhCCIrjuDZkqr+HYDFLU6gEQUCe55X+3ff90p4fIoTLFA0WLI7j
|
||||||
|
kOu6rX/ftm2ybZuEEBRF0dUKBsxxLVSaIFymT/scC2OMdrtdp1Apsm2bPM8jzvlFhQPmybKs9PpW
|
||||||
|
qCi+71/cfKrvZQrXdYkxNvZldKK1YmGM0cfHR+3PhBAkpaTv728iIjqdTmTbNq1WK7Jtu3aYxBij
|
||||||
|
KIp0XuLoXNelz8/PzhWZZVnkuq5x1ZyqMDjnrUNFKVYuURQZOUTe7XZ5NR+GIaVpOvYltaItWCzL
|
||||||
|
qg0VNXdS1yDFf2OM0Xa7LQWMbdt3Vz5TVOwkQRC0DgjLsvK5BlPD5d5Jed/3yXEcI0OlOj+53W5n
|
||||||
|
Ey5ahkLFjq9IKelwOFAQBK0aIk1T8n3/ooOohp27aifZ7XathnrVtjUtbJU+wWBiqBDRRWWrwqXv
|
||||||
|
sKi40jYULRVLtaP3eTo3iiLKssy4/QlJkpSGfCpcrlUudYFNhAnKpZBSUhAEpZvQvZWLGkoXb9RS
|
||||||
|
SkqSZJD+1Lti4ZxfVBVhGPZ6zziOjfvwpGlKYRhe3IGaKpemUDkcDrMohUEPFS59KhfOOXmed/E5
|
||||||
|
tSwr/5nuCqZ3xfL6+lp63bXjq0naJVDhUpxLqgsXhAoU9alcGGM3q3/VB3WeAdSrYqmO1aSUnTs+
|
||||||
|
Yyyf0Kz+MVFT5XILQmXZ7q1cmlZpqyzL6vQ4xS29KpZqtZIkSa+LWYq6yuUahIo56irRLqr95Vrl
|
||||||
|
0nUksNlstE2EawsWKaVx8yJDahsuCBWzDLEa0xQu6/W68/vo0itYdIzJumyIMs2tcEGoQFt1/We1
|
||||||
|
WvV+j3vhzNuRNYULQsVMfTc2Nn346/qLEKLTtg2d+4FGOzbhdDrlz3aYtIv0HtVwQaiYq0+V33W1
|
||||||
|
UJ0U0LYS0fms1cOCpelQHjzNfKbCRf0dzqSUed9Ych+5ZwuClJLCMGy1MqQevdHl136//6Pt3Rqo
|
||||||
|
Z2SuMfUhMoC++u5runXSngognTe0wSuWup25dd7f3/MnoAHgTMdmyTiOKUmSh27pHzRY1Jbhtr/7
|
||||||
|
+vqKJWuA/+jcga022FWP+RzKpFaFlrK1vw5jjNbrdX4W8K1zgJdiye1S9xR734n9R7XdoMFS3Zl7
|
||||||
|
i6nb+K+pOxyLcz7IuHdO0C7necfi8GVOq4W/397e/hnqzdfrNb28vLT+fSklfX19DXU5k8M5p+12
|
||||||
|
W/uzp6enfGl+Lp1JF7TL2c/PD6VpSs/Pz7ML00G/V0h9vUdbSylxidrPPy3t7F+0S5maG5lTqBAN
|
||||||
|
HCzFPQhtLGnitsspcCaeGNcE7WKGwb8Jse3ZrkKI2aVyH13mk+oOGzcV2sUMgweLmmy7Fi5dT2ef
|
||||||
|
O3wY6qFdzPGQ5WZ1ULY6LU4dEHU8HilJkkVVKvda0vxTF2iXaXroPpYlzaFco+ae2t6hl/LhQbuY
|
||||||
|
Y/ChENTrctrekk7mQ7uYAcEykjiOW91xl3YyH9rFDAiWEd1aMRNC9P4qlTlCu8zfQ45NgOscx6HN
|
||||||
|
ZlOa1M6ybPF3ZLTLfCFYAEA7DIUAQDsECwBoh2ABAO0QLACgHYIFALRDsACAdggWANAOwQIA2iFY
|
||||||
|
AEA7BAsAaIdgAQDt/gUoDXNStc/rMQAAAABJRU5ErkJggg==
|
||||||
|
"
|
||||||
|
id="image2132"
|
||||||
|
x="-82"
|
||||||
|
y="-11.9" /><image
|
||||||
|
width="460"
|
||||||
|
height="71"
|
||||||
|
preserveAspectRatio="none"
|
||||||
|
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAcwAAABHCAYAAACK23cpAAAABHNCSVQICAgIfAhkiAAACuZJREFU
|
||||||
|
eJzt3T9o3FgeB/Cv77aZBHInYZPD+JLF4iDxDl44ZNiAvVfNhC2vCEqxWQJpJn3MwlRXTeP0drNF
|
||||||
|
NtfIKa5cYnVxkQMLDoztbU6GgBkwDiPOEHuOK/aK7FM0Gs3M01gzkp6+HzBknPFEkSV93z/9NLO6
|
||||||
|
uvoLiIiIaKjfZL0BRERERcDAJCIiksDAJCIiksDAJCIiksDAJCIiksDAJCIiksDAJCIiksDAJCIi
|
||||||
|
ksDAJCIiksDAJCIiksDAJCIiksDAJCIikvBZ1htAlIZmswlN0wAAvu+j1WplvEX5YVkWOp0OHMfJ
|
||||||
|
elMoBYuLi8Gfj4+PM9yS8mFgEinMsiyYphm8ZmgSjY9DskSKioZlvV5HrVbLcIuoyEzTDEZxyoqB
|
||||||
|
SaSgaFgKDE0aR6PRCI6pMocmA5NIMYPCUmBoUhKNRgOGYQD4eOyUOTQZmEQKGRWWAkOTZPm+3/O6
|
||||||
|
zKHJwCRShGxYCgxNkmHbNlzX7fleWUNzqqtk5xaWMbuwHLx+d+Tg4vw0eH3txs2e19RPXBA1TQuW
|
||||||
|
lHc6nb5WYFj4ouj7ft/BT8UXF5ae50HX9Z7bbQD0XOTq9ToArp6l4WzbBoC+RWQA4Lru0OuPSqYa
|
||||||
|
mLMLy1i69yh4/f5kvycgv3nyI47evuwL0jKzLCuYPxjWmvM8D47jxIanOLDF+xiYahkUlltbW2g2
|
||||||
|
mz3fb7VaPfesAgxNksPQzOGQ7NK9R/jLgw3MhXqiZWSaJjY2NoJhj1FDH4ZhBCvZyjZMUmbDwnKQ
|
||||||
|
VqsV26ji8CyNUvbh2VwWLrh24ya+frCBs5N9uK+fl6q3aRgGarVa0Ksc5+ebzSZ2dnZK0+ors06n
|
||||||
|
0/N6VFgKcT1NVY8Vy7Lgui48z8t6Uwpr1HVpUE9T0zQ8ffp0rDAVo2F5GhHLXQ8zbG5hGd88+RG3
|
||||||
|
l2q4duNm1pszcaKXGHdQioPHtm3Yto2tra2hoViv15XsbY77f9I0DY1GQ7n94TgOdnZ2AMiHpRDu
|
||||||
|
acb1HFTQaDRgmmbP1AYlI86dUfsvrqfp+z42NzfHaowZhgHLsnI18pHLHmaUef+Z8r1NcVBGibnJ
|
||||||
|
uNZx+Hvi4AofrOJ7qhAnrWEYiU5CTdOCuTzLsmDbtlK9Kcdxxl7M1Wq1YJqmkmEZnf8Xv3v2NJMJ
|
||||||
|
r4GQfW+4IS9Cc9yeZp7m2H9769atv03rH5tbWMbcH78MXkcX94QXBEVdv3ETf/rzXwEAF+en+N9/
|
||||||
|
P0xuQ6csfEEXfN/HixcvgovhKL7vY3d3F7quY35+Pvi+rut97yvixdGyLFSrVQBApVJBtVrFwcEB
|
||||||
|
ut0uAGBtbQ2VSgUA0O12sbu7C6B/34r9U8R9MEy73Y79/qD9IvOzRddut1GtVoP/f6VSgWEYaLfb
|
||||||
|
V2owaZqGSqUSHHvTNu1h9MePH/e89jwPvu/3fIWvM4Zh4PLyEr7vB/uo2+3i4OAA8/PzfT876Cv6
|
||||||
|
mb7vZ36s5raHeXF+ig/np32Lf5buPcLnX9Thvn6Os5P9jLYuXdFe4FWetmHbNjqdTqJWYRG4rgvD
|
||||||
|
MIKLhZgbGdbTjGuIAPloqdLkxfVsxu1pip8LD0v6vo+9vb1SHU+Dhv3j6hYD/T3NJFMG0Tl2y7KC
|
||||||
|
sM5KbucwP5yf4s2r9dhhWLEo6OsHG4Wf24ybSBfLt8cVntdShed5fUOpwxYUDArLra0tDsmVSNwc
|
||||||
|
Wlz4DVOr1dBsNvver2ka6vV634W9jCaxejauMTzusG5acjske3F+indHDv5zdox//+sfuP67P+D3
|
||||||
|
c70HrBimFUO0RRymffjwYTBkBCS/oNdqteDADH/put43HAsUd0gWQDAkYxhGzzBbtVrtOYkqlQrW
|
||||||
|
1tb6fr6MYSkzJKs6MRw4zvCszDoAcQxOa99Oe0g2PFo17PpxeHjYNyUUNzwra9DvLTodM0257WFG
|
||||||
|
ua+f482r9dhFP+b9ZzDvPytcbzN6f6Xv+4kv6GIRTNyXigb1NEcpY1jSJ+P2NOMW4sXRNC1RWUJV
|
||||||
|
pd3THPZ7y6Knmds5zDhnJ/v46YfvcPerb/H5F/WegBS3oBSpUlD0BNvb28toS4pFhKbsScOwVEfc
|
||||||
|
MHsS0eNl2Jxm0kanqquNk0q7IpCYK15ZWQl+f4ZhBPt7mnOahelhhv38z78PXPRTpEpBKysrwZ99
|
||||||
|
3y/V4oGriutpxmFYqkWMyoz7Negz43qaSXswcVMgZZV2T9NxnL4Ohfi8aSpUDzPsQwF6kKOMuxI2
|
||||||
|
LMmqM9WM6mkyLEnWoIVjV/2MMptETzNq2o2UQgbm3a++HXjP5rsjB+7r51PeIsrKoNBkWKrpqsNv
|
||||||
|
g0It7ngRTwOSxeHYfmmFZtziK3HuT1OhAnNuYRl37z2KHW49O9nHz29fKnNv5iDiwPN9P6gjqlLV
|
||||||
|
mnFEQ5Nhqa6rjMokvdVIPPlHtucYretLH101NEXJ0LCkZSDTUpjANO8/w+2l+JqC7uvneHek9vyf
|
||||||
|
uH0kSnYuT3Xh1ibD8pNOp8OGFca7L9f3fdi2LbVSVpSwpHjjhmZcydCkBRDSlPvAvL1Ug3n/Wezf
|
||||||
|
qV5fVhhW+Fg8nUTV4tlJMCj7lXmOW7hKEQvP87CzszO0chYX7MlJGppiMVaYaMRkJbeBef3Xaj5x
|
||||||
|
w68X56dKlcYbRvZRX/V6PfOyUUR5k0bFJ8dx4LouS+OlIGloRhf1ZF08P7eBee3GzdhCBGVa1CNK
|
||||||
|
b8m+1zRNnrxEv0qzPKIYBhTzmWVqmIbncXVdly7mkITMU07ysDYht4EZVZZFPVdhGEZpA1MUZtc0
|
||||||
|
DcfHx8GCjbIr836JK2l31YtuWfZdWKfT6SleP6nbZ4aFpmEYmYclUJDALMOinjhJb8pVtRzeMHEr
|
||||||
|
6IBPcx15OMmywP2CYOW0OC/y0EMpItu2p1b0fFBo5mV9Rm6LrwMfe5VvXq3j/QR6lbOzs7i4uEj9
|
||||||
|
c9Ok63rwDEgZ4pmYZVGr1QYWxq5UKkGDI+n9dEXH/fJRt9uF53mYn5/H9va2MmE57eLr3W4X7XYb
|
||||||
|
mqZhZmam52ERk3CVgu2Tlsse5qQX9czOzuL777/H+vr6RD4/LUlP8DLdByY7v1uv16debzJL3C+9
|
||||||
|
srwFQSWe5020wSHzPM08yF0t2XdHDn764buJhuWTJ08m8tlpE08el1Wm+ctRj1wa971Fx/1CRTSJ
|
||||||
|
52lOwlR7mO9P9nH09mXwOloP9s2r9Yku6hFhOTs7O7F/I23RJ8YPMukWYN4kma8VC1/y1FKdFO4X
|
||||||
|
Kqq0a89OwlQD8+xkf2ggMiz7iUUawx5llVWZqKzkqcWZJ9wvVHR5D81czmGmrahhKXieh1arFRQx
|
||||||
|
0HUdmqbBdV24rluqnuW4sj7R8or7hfImz6GpfGAWPSzDHMcp1TzlIGJuV7ZHVZZQ4H4hVeQ1NHO3
|
||||||
|
6CdNKoUl9Yo+TDat9xYd9wupIo8LgZQNTIal2hzHkWpllq0wNvcLqWRQaGZVpEXJwGRYlsPm5ubQ
|
||||||
|
cMjiAbN5wP1CKomGpud5mVX+mWqln2lIEpZsYRdbt9vF7u4ufN9HpVIJqpC4rouDgwNsb2+Xcp6O
|
||||||
|
+0Vt0670kweHh4fQdR2Xl5eZ3hEws7q6+ktm/3rKkvYs817ph4iI8kOZVbKi3B0REdEkKNXDJCIi
|
||||||
|
mhQlF/0QERGljYFJREQkgYFJREQkgYFJREQkgYFJREQkgYFJREQkgYFJREQkgYFJREQkgYFJREQk
|
||||||
|
4bPFxcWst4GIiCj32MMkIiKSMHPnzh3WkiUiIhqBPUwiIiIJDEwiIiIJDEwiIiIJ/weH8+Ed/xCz
|
||||||
|
fAAAAABJRU5ErkJggg==
|
||||||
|
"
|
||||||
|
id="image2845"
|
||||||
|
x="-15.489594"
|
||||||
|
y="-8.2945251" /></g><g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer5"
|
||||||
|
inkscape:label="dot" /><g
|
||||||
|
inkscape:label="over"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
style="display:none"><ellipse
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none"
|
||||||
|
id="path2637"
|
||||||
|
cx="24"
|
||||||
|
cy="33"
|
||||||
|
rx="4.3600898"
|
||||||
|
ry="4.3600893" /><path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 9,24 C 9.7590836,20.626295 11.695032,17.529367 14.395314,15.369142 17.095595,13.208917 20.541953,12 24,12 c 3.458047,0 6.904405,1.208917 9.604686,3.369142 C 36.304968,17.529367 38.240916,20.626295 39,24"
|
||||||
|
id="path2142"
|
||||||
|
inkscape:path-effect="#path-effect2144"
|
||||||
|
inkscape:original-d="m 9,24 c 4.959859,-3.824406 10.021901,-8.173595 15,-12 4.978099,-3.8264055 10.285024,8.398237 15,12"
|
||||||
|
sodipodi:nodetypes="csc" /><path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 26,22 H 39 V 9"
|
||||||
|
id="path2626"
|
||||||
|
sodipodi:nodetypes="ccc" /></g><g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer6"
|
||||||
|
inkscape:label="into"
|
||||||
|
style="display:none"><ellipse
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none"
|
||||||
|
id="path2637-3"
|
||||||
|
cx="24"
|
||||||
|
cy="38.5"
|
||||||
|
rx="4.3600898"
|
||||||
|
ry="4.3600893" /><path
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 24,2 V 24"
|
||||||
|
id="path2668"
|
||||||
|
sodipodi:nodetypes="cc" /><path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 14.807612,16.994936 24,26.187324 33.192388,16.994936"
|
||||||
|
id="path2626-8"
|
||||||
|
sodipodi:nodetypes="ccc" /></g><g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="g2714"
|
||||||
|
inkscape:label="out"
|
||||||
|
style="display:none"><ellipse
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none"
|
||||||
|
id="ellipse2708"
|
||||||
|
cx="24"
|
||||||
|
cy="38.5"
|
||||||
|
rx="4.3600898"
|
||||||
|
ry="4.3600893" /><path
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 24,29.722858 V 7.7228579"
|
||||||
|
id="path2710"
|
||||||
|
sodipodi:nodetypes="cc" /><path
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 33.192388,14.727922 24,5.5355339 14.807612,14.727922"
|
||||||
|
id="path2712"
|
||||||
|
sodipodi:nodetypes="ccc" /></g><g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer8"
|
||||||
|
inkscape:label="resume"><path
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 13,12 V 38"
|
||||||
|
id="path2850"
|
||||||
|
sodipodi:nodetypes="cc" /><path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 21,16 V 34 L 36.5,25 Z"
|
||||||
|
id="path2852"
|
||||||
|
sodipodi:nodetypes="cccc" /></g></svg>
|
After Width: | Height: | Size: 11 KiB |
@ -383,7 +383,7 @@
|
|||||||
Max Response Length (tokens)
|
Max Response Length (tokens)
|
||||||
</div>
|
</div>
|
||||||
<div class="wide100p">
|
<div class="wide100p">
|
||||||
<input type="number" id="openai_max_tokens" name="openai_max_tokens" class="text_pole" min="50" max="8000">
|
<input type="number" id="openai_max_tokens" name="openai_max_tokens" class="text_pole" min="1" max="16384">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="range-block" data-source="openai,custom">
|
<div class="range-block" data-source="openai,custom">
|
||||||
@ -1273,7 +1273,7 @@
|
|||||||
<input class="neo-range-slider" type="range" id="max_tokens_second_textgenerationwebui" name="volume" min="0" max="20" step="1" />
|
<input class="neo-range-slider" type="range" id="max_tokens_second_textgenerationwebui" name="volume" min="0" max="20" step="1" />
|
||||||
<input class="neo-range-input" type="number" min="0" max="20" step="1" data-for="max_tokens_second_textgenerationwebui" id="max_tokens_second_counter_textgenerationwebui">
|
<input class="neo-range-input" type="number" min="0" max="20" step="1" data-for="max_tokens_second_textgenerationwebui" id="max_tokens_second_counter_textgenerationwebui">
|
||||||
</div>
|
</div>
|
||||||
<div data-newbie-hidden data-tg-type="mancer, ooba, koboldcpp, aphrodite, tabby" name="smoothingBlock" class="wide100p">
|
<div data-newbie-hidden data-tg-type="mancer, ooba, koboldcpp, aphrodite, tabby" id="smoothingBlock" name="smoothingBlock" class="wide100p">
|
||||||
<h4 class="wide100p textAlignCenter">
|
<h4 class="wide100p textAlignCenter">
|
||||||
<label data-i18n="Smooth Sampling">Smooth Sampling</label>
|
<label data-i18n="Smooth Sampling">Smooth Sampling</label>
|
||||||
<div class=" fa-solid fa-circle-info opacity50p " data-i18n="[title]Smooth_Sampling_desc" title="Allows you to use quadratic/cubic transformations to adjust the distribution. Lower Smoothing Factor values will be more creative, usually between 0.2-0.3 is the sweetspot (assuming the curve = 1). Higher Smoothing Curve values will make the curve steeper, which will punish low probability choices more aggressively. 1.0 curve is equivalent to only using Smoothing Factor."></div>
|
<div class=" fa-solid fa-circle-info opacity50p " data-i18n="[title]Smooth_Sampling_desc" title="Allows you to use quadratic/cubic transformations to adjust the distribution. Lower Smoothing Factor values will be more creative, usually between 0.2-0.3 is the sweetspot (assuming the curve = 1). Higher Smoothing Curve values will make the curve steeper, which will punish low probability choices more aggressively. 1.0 curve is equivalent to only using Smoothing Factor."></div>
|
||||||
@ -1358,7 +1358,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div data-newbie-hidden id="mirostat_block_ooba" class="wide100p">
|
<div data-newbie-hidden data-tg-type="ooba,aphrodite,infermaticai,koboldcpp,llamacpp,mancer,ollama,tabby" id="mirostat_block_ooba" class="wide100p">
|
||||||
<h4 class="wide100p textAlignCenter">
|
<h4 class="wide100p textAlignCenter">
|
||||||
<label data-i18n="Mirostat (mode=1 is only for llama.cpp)">Mirostat</label>
|
<label data-i18n="Mirostat (mode=1 is only for llama.cpp)">Mirostat</label>
|
||||||
<div class=" fa-solid fa-circle-info opacity50p " data-i18n="[title]Mirostat_desc" title="Mirostat is a thermostat for output perplexity. Mirostat matches the output perplexity to that of the input, thus avoiding the repetition trap (where, as the autoregressive inference produces text, the perplexity of the output tends toward zero) and the confusion trap (where the perplexity diverges). For details, see the paper Mirostat: A Neural Text Decoding Algorithm that Directly Controls Perplexity by Basu et al. (2020). Mode chooses the Mirostat version. 0=disable, 1=Mirostat 1.0 (llama.cpp only), 2=Mirostat 2.0."></div>
|
<div class=" fa-solid fa-circle-info opacity50p " data-i18n="[title]Mirostat_desc" title="Mirostat is a thermostat for output perplexity. Mirostat matches the output perplexity to that of the input, thus avoiding the repetition trap (where, as the autoregressive inference produces text, the perplexity of the output tends toward zero) and the confusion trap (where the perplexity diverges). For details, see the paper Mirostat: A Neural Text Decoding Algorithm that Directly Controls Perplexity by Basu et al. (2020). Mode chooses the Mirostat version. 0=disable, 1=Mirostat 1.0 (llama.cpp only), 2=Mirostat 2.0."></div>
|
||||||
@ -1387,7 +1387,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div data-newbie-hidden data-tg-type="ooba, vllm" name="beamSearchBlock" class="wide100p">
|
<div data-newbie-hidden data-tg-type="ooba, vllm" id="beamSearchBlock" name="beamSearchBlock" class="wide100p">
|
||||||
<h4 class="wide100p textAlignCenter">
|
<h4 class="wide100p textAlignCenter">
|
||||||
<label>
|
<label>
|
||||||
<span data-i18n="Beam search">Beam Search</span>
|
<span data-i18n="Beam search">Beam Search</span>
|
||||||
@ -1413,7 +1413,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div data-tg-type="ooba" data-newbie-hidden name="contrastiveSearchBlock" class="alignitemscenter flex-container flexFlowColumn flexBasis48p flexGrow flexShrink gap0">
|
<div data-tg-type="ooba" data-newbie-hidden id="contrastiveSearchBlock" name="contrastiveSearchBlock" class="alignitemscenter flex-container flexFlowColumn flexBasis48p flexGrow flexShrink gap0">
|
||||||
<h4 class="textAlignCenter" data-i18n="Contrastive search">Contrastive Search
|
<h4 class="textAlignCenter" data-i18n="Contrastive search">Contrastive Search
|
||||||
<div class=" fa-solid fa-circle-info opacity50p " data-i18n="Contrastive_search_txt" title="A sampler that encourages diversity while maintaining coherence, by exploiting the isotropicity of the representation space of most LLMs. For details, see the paper A Contrastive Framework for Neural Text Generation by Su et al. (2022)."></div>
|
<div class=" fa-solid fa-circle-info opacity50p " data-i18n="Contrastive_search_txt" title="A sampler that encourages diversity while maintaining coherence, by exploiting the isotropicity of the representation space of most LLMs. For details, see the paper A Contrastive Framework for Neural Text Generation by Su et al. (2022)."></div>
|
||||||
</h4>
|
</h4>
|
||||||
@ -1544,7 +1544,7 @@
|
|||||||
<h4 class="wide100p textAlignCenter">
|
<h4 class="wide100p textAlignCenter">
|
||||||
<label>
|
<label>
|
||||||
<span data-i18n="Grammar String">Grammar String</span>
|
<span data-i18n="Grammar String">Grammar String</span>
|
||||||
<div class="margin5 fa-solid fa-circle-info opacity50p " data-i18n="[title]GNBF or ENBF, depends on the backend in use. If you're using this you should know which." title="GNBF or ENBF, depends on the backend in use. If you're using this you should know which."></div>
|
<div class="margin5 fa-solid fa-circle-info opacity50p " data-i18n="[title]GBNF or EBNF, depends on the backend in use. If you're using this you should know which." title="GBNF or EBNF, depends on the backend in use. If you're using this you should know which."></div>
|
||||||
<a href="https://github.com/ggerganov/llama.cpp/blob/master/grammars/README.md" target="_blank">
|
<a href="https://github.com/ggerganov/llama.cpp/blob/master/grammars/README.md" target="_blank">
|
||||||
<small>
|
<small>
|
||||||
<div class="fa-solid fa-up-right-from-square note-link-span"></div>
|
<div class="fa-solid fa-up-right-from-square note-link-span"></div>
|
||||||
@ -1554,13 +1554,13 @@
|
|||||||
</h4>
|
</h4>
|
||||||
<textarea id="grammar_string_textgenerationwebui" rows="4" class="text_pole textarea_compact monospace" data-i18n="[placeholder]Type in the desired custom grammar" placeholder="Type in the desired custom grammar"></textarea>
|
<textarea id="grammar_string_textgenerationwebui" rows="4" class="text_pole textarea_compact monospace" data-i18n="[placeholder]Type in the desired custom grammar" placeholder="Type in the desired custom grammar"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div id="sampler_order_block" data-newbie-hidden data-tg-type="koboldcpp" class="range-block flexFlowColumn wide100p">
|
<div id="sampler_order_block_kcpp" data-newbie-hidden data-tg-type="koboldcpp" class="range-block flexFlowColumn wide100p">
|
||||||
<hr class="wide100p">
|
<hr class="wide100p">
|
||||||
<div class="range-block-title">
|
<div class="range-block-title">
|
||||||
<span data-i18n="Samplers Order">Samplers Order</span>
|
<span data-i18n="Samplers Order">Samplers Order</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="toggle-description widthUnset" data-i18n="Samplers will be applied in a top-down order. Use with caution.">
|
<div class="toggle-description widthUnset" data-i18n="Samplers will be applied in a top-down order. Use with caution.">
|
||||||
Samplers will be applied in a top-down order.
|
kcpp only. Samplers will be applied in a top-down order.
|
||||||
Use with caution.
|
Use with caution.
|
||||||
</div>
|
</div>
|
||||||
<div id="koboldcpp_order" class="prompt_order">
|
<div id="koboldcpp_order" class="prompt_order">
|
||||||
@ -1597,10 +1597,10 @@
|
|||||||
<span data-i18n="Load default order">Load default order</span>
|
<span data-i18n="Load default order">Load default order</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div data-newbie-hidden data-tg-type="llamacpp" class="range-block flexFlowColumn wide100p">
|
<div id="sampler_order_block_lcpp" data-newbie-hidden data-tg-type="llamacpp" class="range-block flexFlowColumn wide100p">
|
||||||
<hr class="wide100p">
|
<hr class="wide100p">
|
||||||
<h4 class="range-block-title justifyCenter">
|
<h4 class="range-block-title justifyCenter">
|
||||||
<span data-i18n="Samplers Order">Samplers Order</span>
|
<span data-i18n="Sampler Order">Sampler Order</span>
|
||||||
<div class="margin5 fa-solid fa-circle-info opacity50p" data-i18n="[title]llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored." title="llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored."></div>
|
<div class="margin5 fa-solid fa-circle-info opacity50p" data-i18n="[title]llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored." title="llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored."></div>
|
||||||
</h4>
|
</h4>
|
||||||
<div class="toggle-description widthUnset" data-i18n="llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.">
|
<div class="toggle-description widthUnset" data-i18n="llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.">
|
||||||
@ -1618,7 +1618,7 @@
|
|||||||
<span data-i18n="Load default order">Load default order</span>
|
<span data-i18n="Load default order">Load default order</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div data-newbie-hidden data-tg-type="ooba" class="range-block flexFlowColumn wide100p">
|
<div id="sampler_priority_block_ooba" data-newbie-hidden data-tg-type="ooba" class="range-block flexFlowColumn wide100p">
|
||||||
<hr class="wide100p">
|
<hr class="wide100p">
|
||||||
<h4 class="range-block-title justifyCenter">
|
<h4 class="range-block-title justifyCenter">
|
||||||
<span data-i18n="Sampler Priority">Sampler Priority</span>
|
<span data-i18n="Sampler Priority">Sampler Priority</span>
|
||||||
@ -1770,13 +1770,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="range-block" data-source="openai,openrouter,makersuite,claude,custom">
|
<div class="range-block" data-source="openai,openrouter,makersuite,claude,custom,01ai">
|
||||||
<label for="openai_image_inlining" class="checkbox_label flexWrap widthFreeExpand">
|
<label for="openai_image_inlining" class="checkbox_label flexWrap widthFreeExpand">
|
||||||
<input id="openai_image_inlining" type="checkbox" />
|
<input id="openai_image_inlining" type="checkbox" />
|
||||||
<span data-i18n="Send inline images">Send inline images</span>
|
<span data-i18n="Send inline images">Send inline images</span>
|
||||||
<div id="image_inlining_hint" class="flexBasis100p toggle-description justifyLeft">
|
<div id="image_inlining_hint" class="flexBasis100p toggle-description justifyLeft">
|
||||||
<span data-i18n="image_inlining_hint_1">Sends images in prompts if the model supports it (e.g. GPT-4V, Claude 3 or Llava 13B).
|
<span data-i18n="image_inlining_hint_1">Sends images in prompts if the model supports it (e.g. GPT-4V, Claude 3 or Llava 13B).
|
||||||
Use the</span> <code><i class="fa-solid fa-paperclip"></i></code> <span data-i18n="image_inlining_hint_2">action on any message or the</span>
|
Use the</span> <code><i class="fa-solid fa-paperclip"></i></code> <span data-i18n="image_inlining_hint_2">action on any message or the</span>
|
||||||
<code><i class="fa-solid fa-wand-magic-sparkles"></i></code> <span data-i18n="image_inlining_hint_3">menu to attach an image file to the chat.</span>
|
<code><i class="fa-solid fa-wand-magic-sparkles"></i></code> <span data-i18n="image_inlining_hint_3">menu to attach an image file to the chat.</span>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
@ -1823,10 +1823,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<div data-newbie-hidden class="range-block" data-source="claude">
|
<div data-newbie-hidden class="range-block" data-source="claude">
|
||||||
<div class="wide100p">
|
<div class="wide100p">
|
||||||
<span id="claude_assistant_prefill_text" data-i18n="Assistant Prefill">Assistant Prefill</span>
|
<div class="flex-container alignItemsCenter">
|
||||||
<textarea id="claude_assistant_prefill" class="text_pole textarea_compact autoSetHeight" name="assistant_prefill" rows="3" maxlength="10000" data-i18n="[placeholder]Start Claude's answer with..." placeholder="Start Claude's answer with..."></textarea>
|
<span id="claude_assistant_prefill_text" data-i18n="Assistant Prefill">Assistant Prefill</span>
|
||||||
<span id="claude_assistant_impersonation_text" data-i18n="Assistant Impersonation Prefill">Assistant Impersonation Prefill</span>
|
<i class="editor_maximize fa-solid fa-maximize right_menu_button" data-for="claude_assistant_prefill" title="Expand the editor" data-i18n="[title]Expand the editor"></i>
|
||||||
<textarea id="claude_assistant_impersonation" class="text_pole textarea_compact autoSetHeight" name="assistant_impersonation" rows="3" maxlength="10000" data-i18n="[placeholder]Start Claude's answer with..." placeholder="Start Claude's answer with..."></textarea>
|
</div>
|
||||||
|
<textarea id="claude_assistant_prefill" class="text_pole textarea_compact" name="assistant_prefill" rows="6" maxlength="100000" data-i18n="[placeholder]Start Claude's answer with..." placeholder="Start Claude's answer with..."></textarea>
|
||||||
|
<div class="flex-container alignItemsCenter">
|
||||||
|
<span id="claude_assistant_impersonation_text" data-i18n="Assistant Impersonation Prefill">Assistant Impersonation Prefill</span>
|
||||||
|
<i class="editor_maximize fa-solid fa-maximize right_menu_button" data-for="claude_assistant_impersonation" title="Expand the editor" data-i18n="[title]Expand the editor"></i>
|
||||||
|
</div>
|
||||||
|
<textarea id="claude_assistant_impersonation" class="text_pole textarea_compact" name="assistant_impersonation" rows="6" maxlength="100000" data-i18n="[placeholder]Start Claude's answer with..." placeholder="Start Claude's answer with..."></textarea>
|
||||||
</div>
|
</div>
|
||||||
<label for="claude_use_sysprompt" class="checkbox_label widthFreeExpand">
|
<label for="claude_use_sysprompt" class="checkbox_label widthFreeExpand">
|
||||||
<input id="claude_use_sysprompt" type="checkbox" />
|
<input id="claude_use_sysprompt" type="checkbox" />
|
||||||
@ -2427,10 +2433,11 @@
|
|||||||
<optgroup>
|
<optgroup>
|
||||||
<option value="01ai">01.AI (Yi)</option>
|
<option value="01ai">01.AI (Yi)</option>
|
||||||
<option value="ai21">AI21</option>
|
<option value="ai21">AI21</option>
|
||||||
|
<option value="blockentropy">Block Entropy</option>
|
||||||
<option value="claude">Claude</option>
|
<option value="claude">Claude</option>
|
||||||
<option value="cohere">Cohere</option>
|
<option value="cohere">Cohere</option>
|
||||||
<option value="groq">Groq</option>
|
<option value="groq">Groq</option>
|
||||||
<option value="makersuite">Google MakerSuite</option>
|
<option value="makersuite">Google AI Studio</option>
|
||||||
<option value="mistralai">MistralAI</option>
|
<option value="mistralai">MistralAI</option>
|
||||||
<option value="openrouter">OpenRouter</option>
|
<option value="openrouter">OpenRouter</option>
|
||||||
<option value="perplexity">Perplexity</option>
|
<option value="perplexity">Perplexity</option>
|
||||||
@ -2570,7 +2577,9 @@
|
|||||||
</optgroup>
|
</optgroup>
|
||||||
<optgroup label="GPT-4o">
|
<optgroup label="GPT-4o">
|
||||||
<option value="gpt-4o">gpt-4o</option>
|
<option value="gpt-4o">gpt-4o</option>
|
||||||
|
<option value="gpt-4o-2024-08-06">gpt-4o-2024-08-06</option>
|
||||||
<option value="gpt-4o-2024-05-13">gpt-4o-2024-05-13</option>
|
<option value="gpt-4o-2024-05-13">gpt-4o-2024-05-13</option>
|
||||||
|
<option value="chatgpt-4o-latest">chatgpt-4o-latest</option>
|
||||||
</optgroup>
|
</optgroup>
|
||||||
<optgroup label="gpt-4o-mini">
|
<optgroup label="gpt-4o-mini">
|
||||||
<option value="gpt-4o-mini">gpt-4o-mini</option>
|
<option value="gpt-4o-mini">gpt-4o-mini</option>
|
||||||
@ -2791,7 +2800,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<form id="makersuite_form" data-source="makersuite" action="javascript:void(null);" method="post" enctype="multipart/form-data">
|
<form id="makersuite_form" data-source="makersuite" action="javascript:void(null);" method="post" enctype="multipart/form-data">
|
||||||
<h4 data-i18n="MakerSuite API Key">MakerSuite API Key</h4>
|
<h4 data-i18n="Google AI Studio API Key">Google AI Studio API Key</h4>
|
||||||
<div class="flex-container">
|
<div class="flex-container">
|
||||||
<input id="api_key_makersuite" name="api_key_makersuite" class="text_pole flex1" maxlength="500" value="" type="text" autocomplete="off">
|
<input id="api_key_makersuite" name="api_key_makersuite" class="text_pole flex1" maxlength="500" value="" type="text" autocomplete="off">
|
||||||
<div title="Clear your API key" data-i18n="[title]Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_makersuite"></div>
|
<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_makersuite"></div>
|
||||||
@ -2802,21 +2811,26 @@
|
|||||||
<div>
|
<div>
|
||||||
<h4 data-i18n="Google Model">Google Model</h4>
|
<h4 data-i18n="Google Model">Google Model</h4>
|
||||||
<select id="model_google_select">
|
<select id="model_google_select">
|
||||||
<optgroup label="Latest">
|
<optgroup label="Primary">
|
||||||
<!-- Doesn't work without "latest". Maybe my key is scuffed? -->
|
<option value="gemini-1.5-pro">Gemini 1.5 Pro</option>
|
||||||
<option value="gemini-1.5-flash-latest">Gemini 1.5 Flash</option>
|
<option value="gemini-1.5-flash">Gemini 1.5 Flash</option>
|
||||||
<!-- Points to 1.0, no default 1.5 endpoint -->
|
<option value="gemini-1.0-pro">Gemini 1.0 Pro</option>
|
||||||
<option value="gemini-pro">Gemini Pro</option>
|
<option value="gemini-pro">Gemini Pro (1.0)</option>
|
||||||
<option value="gemini-pro-vision">Gemini Pro Vision</option>
|
<option value="gemini-pro-vision">Gemini Pro Vision (1.0)</option>
|
||||||
<option value="gemini-ultra">Gemini Ultra</option>
|
<option value="gemini-ultra">Gemini Ultra (1.0)</option>
|
||||||
<option value="text-bison-001">Bison Text</option>
|
|
||||||
<option value="chat-bison-001">Bison Chat</option>
|
|
||||||
</optgroup>
|
|
||||||
<optgroup label="Sub-versions">
|
|
||||||
<option value="gemini-1.5-pro-latest">Gemini 1.5 Pro</option>
|
|
||||||
<option value="gemini-1.0-pro-latest">Gemini 1.0 Pro</option>
|
|
||||||
<option value="gemini-1.0-pro-vision-latest">Gemini 1.0 Pro Vision</option>
|
|
||||||
<option value="gemini-1.0-ultra-latest">Gemini 1.0 Ultra</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-1.5-pro-exp-0801">Gemini 1.5 Pro Experiment 2024-08-01</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-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.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>
|
||||||
</optgroup>
|
</optgroup>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@ -2894,7 +2908,20 @@
|
|||||||
</div>
|
</div>
|
||||||
<h4 data-i18n="Perplexity Model">Perplexity Model</h4>
|
<h4 data-i18n="Perplexity Model">Perplexity Model</h4>
|
||||||
<select id="model_perplexity_select">
|
<select id="model_perplexity_select">
|
||||||
<optgroup label="Perplexity Models">
|
<optgroup label="Perplexity Sonar Models">
|
||||||
|
<option value="llama-3.1-sonar-small-128k-online">llama-3.1-sonar-small-128k-online</option>
|
||||||
|
<option value="llama-3.1-sonar-large-128k-online">llama-3.1-sonar-large-128k-online</option>
|
||||||
|
<option value="llama-3.1-sonar-huge-128k-online">llama-3.1-sonar-huge-128k-online</option>
|
||||||
|
</optgroup>
|
||||||
|
<optgroup label="Perplexity Chat Models">
|
||||||
|
<option value="llama-3.1-sonar-small-128k-chat">llama-3.1-sonar-small-128k-chat</option>
|
||||||
|
<option value="llama-3.1-sonar-large-128k-chat">llama-3.1-sonar-large-128k-chat</option>
|
||||||
|
</optgroup>
|
||||||
|
<optgroup label="Open-Source Models">
|
||||||
|
<option value="llama-3.1-8b-instruct">llama-3.1-8b-instruct</option>
|
||||||
|
<option value="llama-3.1-70b-instruct">llama-3.1-70b-instruct</option>
|
||||||
|
</optgroup>
|
||||||
|
<optgroup label="Deprecated Models">
|
||||||
<option value="llama-3-sonar-small-32k-chat">llama-3-sonar-small-32k-chat</option>
|
<option value="llama-3-sonar-small-32k-chat">llama-3-sonar-small-32k-chat</option>
|
||||||
<option value="llama-3-sonar-small-32k-online">llama-3-sonar-small-32k-online</option>
|
<option value="llama-3-sonar-small-32k-online">llama-3-sonar-small-32k-online</option>
|
||||||
<option value="llama-3-sonar-large-32k-chat">llama-3-sonar-large-32k-chat</option>
|
<option value="llama-3-sonar-large-32k-chat">llama-3-sonar-large-32k-chat</option>
|
||||||
@ -2903,8 +2930,6 @@
|
|||||||
<option value="sonar-small-online">sonar-small-online</option>
|
<option value="sonar-small-online">sonar-small-online</option>
|
||||||
<option value="sonar-medium-chat">sonar-medium-chat</option>
|
<option value="sonar-medium-chat">sonar-medium-chat</option>
|
||||||
<option value="sonar-medium-online">sonar-medium-online</option>
|
<option value="sonar-medium-online">sonar-medium-online</option>
|
||||||
</optgroup>
|
|
||||||
<optgroup label="Open-Source Models">
|
|
||||||
<option value="llama-3-8b-instruct">llama-3-8b-instruct</option>
|
<option value="llama-3-8b-instruct">llama-3-8b-instruct</option>
|
||||||
<option value="llama-3-70b-instruct">llama-3-70b-instruct</option>
|
<option value="llama-3-70b-instruct">llama-3-70b-instruct</option>
|
||||||
<option value="mistral-7b-instruct">mistral-7b-instruct (v0.2)</option>
|
<option value="mistral-7b-instruct">mistral-7b-instruct (v0.2)</option>
|
||||||
@ -2938,6 +2963,20 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<form id="blockentropy_form" data-source="blockentropy">
|
||||||
|
<h4 data-i18n="Block Entropy API Key">Block Entropy API Key</h4>
|
||||||
|
<div class="flex-container">
|
||||||
|
<input id="api_key_blockentropy" name="api_key_blockentropy" class="text_pole flex1" maxlength="500" value="" type="text" autocomplete="off">
|
||||||
|
<div title="Clear your API key" data-i18n="[title]Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_blockentropy"></div>
|
||||||
|
</div>
|
||||||
|
<div data-for="api_key_blockentropy" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">
|
||||||
|
For privacy reasons, your API key will be hidden after you reload the page.
|
||||||
|
</div>
|
||||||
|
<h4 data-i18n="Select a Model">Select a Model</h4>
|
||||||
|
<div class="flex-container">
|
||||||
|
<select id="model_blockentropy_select" class="text_pole"></select>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
<form id="custom_form" data-source="custom">
|
<form id="custom_form" data-source="custom">
|
||||||
<h4 data-i18n="Custom Endpoint (Base URL)">Custom Endpoint (Base URL)</h4>
|
<h4 data-i18n="Custom Endpoint (Base URL)">Custom Endpoint (Base URL)</h4>
|
||||||
<div class="flex-container">
|
<div class="flex-container">
|
||||||
@ -3069,7 +3108,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<label class="checkbox_label" title="Add Chat Start and Example Separator to a list of stopping strings." data-i18n="[title]Add Chat Start and Example Separator to a list of stopping strings.">
|
<label class="checkbox_label" title="Add Chat Start and Example Separator to a list of stopping strings." data-i18n="[title]Add Chat Start and Example Separator to a list of stopping strings.">
|
||||||
<input id="context_use_stop_strings" type="checkbox" />
|
<input id="context_use_stop_strings" type="checkbox" />
|
||||||
<small data-i18n="Use as Stop Strings">Use as Stop Strings</small>
|
<small data-i18n="Separators as Stop Strings">Separators as Stop Strings</small>
|
||||||
|
</label>
|
||||||
|
<label class="checkbox_label" title="Add Character and User names to a list of stopping strings." data-i18n="[title]Add Character and User names to a list of stopping strings.">
|
||||||
|
<input id="context_names_as_stop_strings" type="checkbox" />
|
||||||
|
<small data-i18n="Names as Stop Strings">Names as Stop Strings</small>
|
||||||
</label>
|
</label>
|
||||||
<label class="checkbox_label" title="Includes Post-History Instructions at the end of the prompt, if defined in the character card AND ''Prefer Char. Instructions'' is enabled. THIS IS NOT RECOMMENDED FOR TEXT COMPLETION MODELS, CAN LEAD TO BAD OUTPUT." data-i18n="[title]context_allow_post_history_instructions">
|
<label class="checkbox_label" title="Includes Post-History Instructions at the end of the prompt, if defined in the character card AND ''Prefer Char. Instructions'' is enabled. THIS IS NOT RECOMMENDED FOR TEXT COMPLETION MODELS, CAN LEAD TO BAD OUTPUT." data-i18n="[title]context_allow_post_history_instructions">
|
||||||
<input id="context_allow_jailbreak" type="checkbox" />
|
<input id="context_allow_jailbreak" type="checkbox" />
|
||||||
@ -3406,6 +3449,7 @@
|
|||||||
<!-- Option #2 was a legacy GPT-2/3 tokenizer -->
|
<!-- Option #2 was a legacy GPT-2/3 tokenizer -->
|
||||||
<option value="3">Llama 1/2</option>
|
<option value="3">Llama 1/2</option>
|
||||||
<option value="12">Llama 3</option>
|
<option value="12">Llama 3</option>
|
||||||
|
<option value="13">Gemma / Gemini</option>
|
||||||
<option value="4">NerdStash (NovelAI Clio)</option>
|
<option value="4">NerdStash (NovelAI Clio)</option>
|
||||||
<option value="5">NerdStash v2 (NovelAI Kayra)</option>
|
<option value="5">NerdStash v2 (NovelAI Kayra)</option>
|
||||||
<option value="7">Mistral</option>
|
<option value="7">Mistral</option>
|
||||||
@ -3578,6 +3622,7 @@
|
|||||||
<div class="alignitemscenter flex-container flexFlowColumn flexGrow flexShrink gap0 flexBasis48p" title="Scan chronologically until reached min entries or token budget." data-i18n="[title]Scan chronologically until reached min entries or token budget.">
|
<div class="alignitemscenter flex-container flexFlowColumn flexGrow flexShrink gap0 flexBasis48p" title="Scan chronologically until reached min entries or token budget." data-i18n="[title]Scan chronologically until reached min entries or token budget.">
|
||||||
<small>
|
<small>
|
||||||
<span data-i18n="Min Activations">Min Activations</span>
|
<span data-i18n="Min Activations">Min Activations</span>
|
||||||
|
<div class="fa-solid fa-triangle-exclamation opacity50p" data-i18n="[title](disabled when max recursion steps are used)" title="(disabled when max recursion steps are used)"></div>
|
||||||
</small>
|
</small>
|
||||||
<input class="neo-range-slider" type="range" id="world_info_min_activations" name="world_info_min_activations" min="0" max="100" step="1">
|
<input class="neo-range-slider" type="range" id="world_info_min_activations" name="world_info_min_activations" min="0" max="100" step="1">
|
||||||
<input class="neo-range-input" type="number" min="0" max="100" step="1" data-for="world_info_min_activations" id="world_info_min_activations_counter">
|
<input class="neo-range-input" type="number" min="0" max="100" step="1" data-for="world_info_min_activations" id="world_info_min_activations_counter">
|
||||||
@ -3591,6 +3636,14 @@
|
|||||||
<input class="neo-range-slider" type="range" id="world_info_min_activations_depth_max" name="volume" min="0" max="100" step="1">
|
<input class="neo-range-slider" type="range" id="world_info_min_activations_depth_max" name="volume" min="0" max="100" step="1">
|
||||||
<input class="neo-range-input" type="number" min="0" max="100" step="1" data-for="world_info_min_activations_depth_max" id="world_info_min_activations_depth_max_counter">
|
<input class="neo-range-input" type="number" min="0" max="100" step="1" data-for="world_info_min_activations_depth_max" id="world_info_min_activations_depth_max_counter">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="alignitemscenter flex-container flexFlowColumn flexGrow flexShrink gap0 flexBasis48p" title="Cap the number of entry activation recursions" data-i18n="[title]Cap the number of entry activation recursions">
|
||||||
|
<small>
|
||||||
|
<span data-i18n="Max Recursion Steps">Max Recursion Steps</span>
|
||||||
|
<div class="fa-solid fa-triangle-exclamation opacity50p" data-i18n="[title]0 = unlimited, 1 = scans once and doesn't recurse, 2 = scans once and recurses once, etc\n(disabled when min activations are used)" title="0 = unlimited, 1 = scans once and doesn't recurse, 2 = scans once and recurses once, etc (disabled when min activations are used)"></div>
|
||||||
|
</small>
|
||||||
|
<input class="neo-range-slider" type="range" id="world_info_max_recursion_steps" name="world_info_max_recursion_steps" min="0" max="10" step="1">
|
||||||
|
<input class="neo-range-input" type="number" min="0" max="10" step="1" data-for="world_info_max_recursion_steps" id="world_info_max_recursion_steps_counter">
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="alignitemscenter flex-container flexFlowColumn flexGrow flexShrink flexBasis48p">
|
<div class="alignitemscenter flex-container flexFlowColumn flexGrow flexShrink flexBasis48p">
|
||||||
<small data-i18n="Insertion Strategy">
|
<small data-i18n="Insertion Strategy">
|
||||||
@ -4054,25 +4107,31 @@
|
|||||||
<input id="world_import_dialog" type="checkbox" />
|
<input id="world_import_dialog" type="checkbox" />
|
||||||
<small data-i18n="Lorebook Import Dialog">Lorebook Import Dialog</small>
|
<small data-i18n="Lorebook Import Dialog">Lorebook Import Dialog</small>
|
||||||
</label>
|
</label>
|
||||||
|
<label data-newbie-hidden class="checkbox_label" for="enable_auto_select_input" title="Enable auto-select of input text in some text fields when clicking/selecting them. Applies to popup input textboxes, and possible other custom input fields." data-i18n="[title]Enable auto-select of input text in some text fields when clicking/selecting them. Applies to popup input textboxes, and possible other custom input fields.">
|
||||||
|
<input id="enable_auto_select_input" type="checkbox" />
|
||||||
|
<small data-i18n="Auto-select Input Text">Auto-select Input Text</small>
|
||||||
|
</label>
|
||||||
<label class="checkbox_label" for="restore_user_input" title="Restore unsaved user input on page refresh." data-i18n="[title]Restore unsaved user input on page refresh">
|
<label class="checkbox_label" for="restore_user_input" title="Restore unsaved user input on page refresh." data-i18n="[title]Restore unsaved user input on page refresh">
|
||||||
<input id="restore_user_input" type="checkbox" />
|
<input id="restore_user_input" type="checkbox" />
|
||||||
<small data-i18n="Restore User Input">Restore User Input</small>
|
<small data-i18n="Restore User Input">Restore User Input</small>
|
||||||
</label>
|
</label>
|
||||||
<label data-newbie-hidden id="movingUIModeCheckBlock" for="movingUImode" class="checkbox_label" title="Allow repositioning certain UI elements by dragging them. PC only, no effect on mobile." data-i18n="[title]Allow repositioning certain UI elements by dragging them. PC only, no effect on mobile">
|
<div class="flex-container alignItemsCenter">
|
||||||
<input id="movingUImode" type="checkbox" />
|
<label data-newbie-hidden id="movingUIModeCheckBlock" for="movingUImode" class="checkbox_label" title="Allow repositioning certain UI elements by dragging them. PC only, no effect on mobile." data-i18n="[title]Allow repositioning certain UI elements by dragging them. PC only, no effect on mobile">
|
||||||
<small data-i18n="Movable UI Panels">MovingUI <i class="fa-solid fa-desktop"></i></small>
|
<input id="movingUImode" type="checkbox" />
|
||||||
</label>
|
<small data-i18n="Movable UI Panels">MovingUI <i class="fa-solid fa-desktop"></i></small>
|
||||||
|
</label>
|
||||||
|
<div data-newbie-hidden id="movingUIreset" title="Reset MovingUI panel sizes/locations." class="menu_button margin0" data-i18n="[title]Reset MovingUI panel sizes/locations."><i class=" fa-solid fa-recycle margin-r5"></i> Reset</div>
|
||||||
|
</div>
|
||||||
<div data-newbie-hidden id="MovingUI-presets-block" class="flex-container alignitemscenter">
|
<div data-newbie-hidden id="MovingUI-presets-block" class="flex-container alignitemscenter">
|
||||||
<div class="flex-container alignItemsFlexEnd">
|
<div class="flex-container alignItemsFlexEnd">
|
||||||
<label for="movingUIPresets" title="MovingUI preset. Predefined/saved draggable positions." data-i18n="[title]MovingUI preset. Predefined/saved draggable positions">
|
<label for="movingUIPresets" title="MovingUI preset. Predefined/saved draggable positions." data-i18n="[title]MovingUI preset. Predefined/saved draggable positions">
|
||||||
<small data-i18n="MUI Preset">MUI Preset:</small>
|
<small data-i18n="MUI Preset">MovingUI Preset:</small>
|
||||||
<div class="flex-container flexnowrap">
|
<div class="flex-container flexnowrap">
|
||||||
<select id="movingUIPresets" class="widthNatural flex1 margin0">
|
<select id="movingUIPresets" class="widthNatural flex1 margin0">
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
<div id="movingui-preset-save-button" title="Save changes to a new MovingUI preset file." data-i18n="[title]Save movingUI changes to a new file" class="menu_button margin0 fa-solid fa-save"></div>
|
<div id="movingui-preset-save-button" title="Save changes to a new MovingUI preset file." data-i18n="[title]Save movingUI changes to a new file" class="menu_button margin0 fa-solid fa-save"></div>
|
||||||
<div data-newbie-hidden id="movingUIreset" title="Reset MovingUI panel sizes/locations." class="menu_button fa-solid fa-recycle margin0" data-i18n="[title]Reset MovingUI panel sizes/locations."></div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div data-newbie-hidden id="CustomCSS-block" class="flex-container flexFlowColumn">
|
<div data-newbie-hidden id="CustomCSS-block" class="flex-container flexFlowColumn">
|
||||||
@ -4144,6 +4203,12 @@
|
|||||||
Quick "Continue" button
|
Quick "Continue" button
|
||||||
</small>
|
</small>
|
||||||
</label>
|
</label>
|
||||||
|
<label class="checkbox_label" for="quick_impersonate" title="Show a button in the input area to ask the AI to impersonate your character for a single message." data-i18n="[title]Show a button in the input area to ask the AI to impersonate your character for a single message">
|
||||||
|
<input id="quick_impersonate" type="checkbox" />
|
||||||
|
<small data-i18n="Quick 'Impersonate' button">
|
||||||
|
Quick "Impersonate" button
|
||||||
|
</small>
|
||||||
|
</label>
|
||||||
<div class="checkbox-container flex-container">
|
<div class="checkbox-container flex-container">
|
||||||
<label data-newbie-hidden class="checkbox_label" for="swipes-checkbox" title="Show arrow buttons on the last in-chat message to generate alternative AI responses. Both PC and mobile." data-i18n="[title]Show arrow buttons on the last in-chat message to generate alternative AI responses. Both PC and mobile">
|
<label data-newbie-hidden class="checkbox_label" for="swipes-checkbox" title="Show arrow buttons on the last in-chat message to generate alternative AI responses. Both PC and mobile." data-i18n="[title]Show arrow buttons on the last in-chat message to generate alternative AI responses. Both PC and mobile">
|
||||||
<input id="swipes-checkbox" type="checkbox" />
|
<input id="swipes-checkbox" type="checkbox" />
|
||||||
@ -4264,6 +4329,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div title="Determines which keys select an item from the AutoComplete suggestions">
|
||||||
|
<label data-i18n="Keyboard">
|
||||||
|
<small>Keyboard:</small>
|
||||||
|
</label>
|
||||||
|
<select id="stscript_autocomplete_select">
|
||||||
|
<option value="3" data-i18n="Select with Tab or Enter">Select with Tab or Enter</option>
|
||||||
|
<option value="1" data-i18n="Select with Tab">Select with Tab</option>
|
||||||
|
<option value="2" data-i18n="Select with Enter">Select with Enter</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
<div class="flex-container flexFlowColumn gap0" title="Sets the font size of the autocomplete." data-i18n="[title]Sets the font size of the autocomplete.">
|
<div class="flex-container flexFlowColumn gap0" title="Sets the font size of the autocomplete." data-i18n="[title]Sets the font size of the autocomplete.">
|
||||||
<label for="stscript_autocomplete_font_scale"><small>Font Scale</small></label>
|
<label for="stscript_autocomplete_font_scale"><small>Font Scale</small></label>
|
||||||
<input class="neo-range-slider" type="range" id="stscript_autocomplete_font_scale" min="0.5" max="2" step="0.01">
|
<input class="neo-range-slider" type="range" id="stscript_autocomplete_font_scale" min="0.5" max="2" step="0.01">
|
||||||
@ -4605,7 +4680,7 @@
|
|||||||
<div id="favorite_button" class="menu_button fa-solid fa-star" title="Add to Favorites" data-i18n="[title]Add to Favorites"></div>
|
<div id="favorite_button" class="menu_button fa-solid fa-star" title="Add to Favorites" data-i18n="[title]Add to Favorites"></div>
|
||||||
<input type="hidden" id="fav_checkbox" name="fav" />
|
<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="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" data-i18n="[title]Character Lore"></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" data-i18n="[title]Chat Lore"></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="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_chat_scenario" class="menu_button fa-solid fa-scroll" title="Set a chat scenario override"></div> -->
|
||||||
@ -4923,7 +4998,7 @@
|
|||||||
<div class="popup-crop-wrap">
|
<div class="popup-crop-wrap">
|
||||||
<img class="popup-crop-image" src="">
|
<img class="popup-crop-image" src="">
|
||||||
</div>
|
</div>
|
||||||
<textarea class="popup-input text_pole result-control" rows="1" data-result="1" data-result-event="submit"></textarea>
|
<textarea class="popup-input text_pole result-control auto-select" rows="1" data-result="1" data-result-event="submit"></textarea>
|
||||||
<div class="popup-inputs"></div>
|
<div class="popup-inputs"></div>
|
||||||
<div class="popup-controls">
|
<div class="popup-controls">
|
||||||
<div class="popup-button-ok menu_button result-control" data-result="1" data-i18n="Delete">Delete</div>
|
<div class="popup-button-ok menu_button result-control" data-result="1" data-i18n="Delete">Delete</div>
|
||||||
@ -5262,21 +5337,22 @@
|
|||||||
<div class="world_entry">
|
<div class="world_entry">
|
||||||
<form class="world_entry_form wi-card-entry">
|
<form class="world_entry_form wi-card-entry">
|
||||||
<div class="inline-drawer wide100p">
|
<div class="inline-drawer wide100p">
|
||||||
<div class="inline-drawer-toggle inline-drawer-header gap5px padding0">
|
<div class="inline-drawer-header gap5px padding0">
|
||||||
<span class="drag-handle">☰</span>
|
<span class="drag-handle">☰</span>
|
||||||
<div class="gap5px world_entry_thin_controls wide100p alignitemscenter">
|
<div class="gap5px world_entry_thin_controls wide100p alignitemscenter">
|
||||||
<div class="fa-fw fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
|
<div class="inline-drawer-toggle fa-fw fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
|
||||||
|
<div class="fa-solid fa-toggle-on killSwitch" name="entryKillSwitch" title="Toggle entry's active state."></div>
|
||||||
<div class="flex-container alignitemscenter wide100p">
|
<div class="flex-container alignitemscenter wide100p">
|
||||||
|
|
||||||
<div class="WIEntryTitleAndStatus flex-container flex1 alignitemscenter">
|
<div class="WIEntryTitleAndStatus flex-container flex1 alignitemscenter">
|
||||||
|
|
||||||
<div class="flex-container flex1">
|
<div class="flex-container flex1">
|
||||||
<textarea class="text_pole" rows="1" name="comment" maxlength="5000" data-i18n="[placeholder]Entry Title/Memo" placeholder="Entry Title/Memo"></textarea>
|
<textarea class="text_pole" rows="1" name="comment" maxlength="5000" data-i18n="[placeholder]Entry Title/Memo" placeholder="Entry Title/Memo"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<!-- <span class="world_entry_form_position_value"></span> -->
|
|
||||||
<select data-i18n="[title]WI Entry Status:🔵 Constant🟢 Normal🔗 Vectorized❌ Disabled" title="WI Entry Status: 🔵 Constant 🟢 Normal 🔗 Vectorized ❌ Disabled" name="entryStateSelector" class="text_pole widthNatural margin0">
|
<select data-i18n="[title]WI Entry Status:🔵 Constant🟢 Normal🔗 Vectorized❌ Disabled" title="WI Entry Status: 🔵 Constant 🟢 Normal 🔗 Vectorized ❌ Disabled" name="entryStateSelector" class="text_pole widthNatural margin0">
|
||||||
<option value="constant" title="Constant" data-i18n="[title]WI_Entry_Status_Constant">🔵</option>
|
<option value="constant" title="Constant" data-i18n="[title]WI_Entry_Status_Constant">🔵</option>
|
||||||
<option value="normal" title="Normal" data-i18n="[title]WI_Entry_Status_Normal">🟢</option>
|
<option value="normal" title="Normal" data-i18n="[title]WI_Entry_Status_Normal">🟢</option>
|
||||||
<option value="vectorized" title="Vectorized" data-i18n="[title]WI_Entry_Status_Vectorized">🔗</option>
|
<option value="vectorized" title="Vectorized" data-i18n="[title]WI_Entry_Status_Vectorized">🔗</option>
|
||||||
<option value="disabled" title="Disabled" data-i18n="[title]WI_Entry_Status_Disabled">❌</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="WIEnteryHeaderControls flex-container">
|
<div class="WIEnteryHeaderControls flex-container">
|
||||||
@ -5525,13 +5601,16 @@
|
|||||||
<div class="flex-container wide100p flexGap10">
|
<div class="flex-container wide100p flexGap10">
|
||||||
<div class="flex4 flex-container flexFlowColumn flexNoGap">
|
<div class="flex4 flex-container flexFlowColumn flexNoGap">
|
||||||
<div class="flex-container justifySpaceBetween">
|
<div class="flex-container justifySpaceBetween">
|
||||||
<small for="characterFilter" data-i18n="Filter to Character(s)">
|
<small for="characterFilter" data-i18n="Filter to Characters or Tags">
|
||||||
Filter to Character(s)
|
Filter to Characters or Tags
|
||||||
</small>
|
</small>
|
||||||
<label class="checkbox_label flexNoGap margin-r5" for="character_exclusion">
|
<label class="checkbox_label flexNoGap margin-r5" for="character_exclusion">
|
||||||
<input type="checkbox" name="character_exclusion" />
|
<input type="checkbox" name="character_exclusion" />
|
||||||
<span>
|
<span>
|
||||||
<small data-i18n="Character Exclusion">Character Exclusion</small>
|
<small title="Switch the Character/Tags filter around to exclude the listed characters and tags from matching for this entry" data-i18n="[title]Switch the Character/Tags filter around to exclude the listed characters and tags from matching for this entry">
|
||||||
|
<span data-i18n="Exclude">Exclude</span>
|
||||||
|
<div class="fa-solid fa-circle-info opacity50p"></div>
|
||||||
|
</small>
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@ -5749,6 +5828,11 @@
|
|||||||
<div title="Caption" class="right_menu_button fa-lg fa-solid fa-envelope-open-text mes_img_caption" data-i18n="[title]Caption"></div>
|
<div title="Caption" class="right_menu_button fa-lg fa-solid fa-envelope-open-text mes_img_caption" data-i18n="[title]Caption"></div>
|
||||||
<div title="Delete" class="right_menu_button fa-lg fa-solid fa-trash-can mes_img_delete" data-i18n="[title]Delete"></div>
|
<div title="Delete" class="right_menu_button fa-lg fa-solid fa-trash-can mes_img_delete" data-i18n="[title]Delete"></div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mes_img_swipes">
|
||||||
|
<div title="Swipe left" class="right_menu_button fa-lg fa-solid fa-chevron-left mes_img_swipe_left" data-i18n="[title]Swipe left"></div>
|
||||||
|
<div class="mes_img_swipe_counter">1/1</div>
|
||||||
|
<div title="Swipe right" class="right_menu_button fa-lg fa-solid fa-chevron-right mes_img_swipe_right" data-i18n="[title]Swipe right"></div>
|
||||||
|
</div>
|
||||||
<img class="mes_img" src="" />
|
<img class="mes_img" src="" />
|
||||||
</div>
|
</div>
|
||||||
<div class="mes_bias"></div>
|
<div class="mes_bias"></div>
|
||||||
@ -5811,6 +5895,7 @@
|
|||||||
Enable simple UI mode
|
Enable simple UI mode
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
|
<div class="expander"></div>
|
||||||
<div class="textAlignCenter">
|
<div class="textAlignCenter">
|
||||||
<h3 data-i18n="Looking for AI characters?">
|
<h3 data-i18n="Looking for AI characters?">
|
||||||
Looking for AI characters?
|
Looking for AI characters?
|
||||||
@ -5829,6 +5914,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="expander"></div>
|
||||||
<h3 data-i18n="Your Persona">
|
<h3 data-i18n="Your Persona">
|
||||||
Your Persona
|
Your Persona
|
||||||
</h3>
|
</h3>
|
||||||
@ -6342,6 +6428,7 @@
|
|||||||
<div id="mes_stop" title="Abort request" class="mes_stop" data-i18n="[title]Abort request">
|
<div id="mes_stop" title="Abort request" class="mes_stop" data-i18n="[title]Abort request">
|
||||||
<i class="fa-solid fa-circle-stop"></i>
|
<i class="fa-solid fa-circle-stop"></i>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="mes_impersonate" class="fa-solid fa-user-secret interactable displayNone" title="Ask AI to write your message for you" data-i18n="[title]Ask AI to write your message for you" tabindex="0"></div>
|
||||||
<div id="mes_continue" class="fa-fw fa-solid fa-arrow-right interactable displayNone" title="Continue the last message" data-i18n="[title]Continue the last message"></div>
|
<div id="mes_continue" class="fa-fw fa-solid fa-arrow-right interactable displayNone" title="Continue the last message" data-i18n="[title]Continue the last message"></div>
|
||||||
<div id="send_but" class="fa-solid fa-paper-plane interactable displayNone" title="Send a message" data-i18n="[title]Send a message"></div>
|
<div id="send_but" class="fa-solid fa-paper-plane interactable displayNone" title="Send a message" data-i18n="[title]Send a message"></div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -207,7 +207,7 @@
|
|||||||
"JSON Schema": "مخطط جيسون",
|
"JSON Schema": "مخطط جيسون",
|
||||||
"Type in the desired JSON schema": "اكتب مخطط JSON المطلوب",
|
"Type in the desired JSON schema": "اكتب مخطط JSON المطلوب",
|
||||||
"Grammar String": "سلسلة القواعد",
|
"Grammar String": "سلسلة القواعد",
|
||||||
"GNBF or ENBF, depends on the backend in use. If you're using this you should know which.": "يعتمد GNBF أو ENBF على الواجهة الخلفية المستخدمة. إذا كنت تستخدم هذا يجب أن تعرف أي.",
|
"GBNF or EBNF, depends on the backend in use. If you're using this you should know which.": "يعتمد GBNF أو EBNF على الواجهة الخلفية المستخدمة. إذا كنت تستخدم هذا يجب أن تعرف أي.",
|
||||||
"Top P & Min P": "أعلى ع وأدنى ص",
|
"Top P & Min P": "أعلى ع وأدنى ص",
|
||||||
"Load default order": "تحميل الترتيب الافتراضي",
|
"Load default order": "تحميل الترتيب الافتراضي",
|
||||||
"llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "llama.cpp فقط. تحديد ترتيب أخذ العينات. إذا لم يكن وضع Mirostat 0، فسيتم تجاهل ترتيب أخذ العينات.",
|
"llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "llama.cpp فقط. تحديد ترتيب أخذ العينات. إذا لم يكن وضع Mirostat 0، فسيتم تجاهل ترتيب أخذ العينات.",
|
||||||
@ -390,7 +390,7 @@
|
|||||||
"Alt Method": "طريقة بديلة",
|
"Alt Method": "طريقة بديلة",
|
||||||
"AI21 API Key": "مفتاح API لـ AI21",
|
"AI21 API Key": "مفتاح API لـ AI21",
|
||||||
"AI21 Model": "نموذج AI21",
|
"AI21 Model": "نموذج AI21",
|
||||||
"MakerSuite API Key": "مفتاح واجهة برمجة تطبيقات MakerSuite",
|
"Google AI Studio API Key": "مفتاح واجهة برمجة تطبيقات Google AI Studio",
|
||||||
"Google Model": "نموذج جوجل",
|
"Google Model": "نموذج جوجل",
|
||||||
"MistralAI API Key": "مفتاح واجهة برمجة التطبيقات MistralAI",
|
"MistralAI API Key": "مفتاح واجهة برمجة التطبيقات MistralAI",
|
||||||
"MistralAI Model": "نموذج ميسترال آي آي",
|
"MistralAI Model": "نموذج ميسترال آي آي",
|
||||||
|
@ -207,7 +207,7 @@
|
|||||||
"JSON Schema": "JSON-Schema",
|
"JSON Schema": "JSON-Schema",
|
||||||
"Type in the desired JSON schema": "Geben Sie das gewünschte JSON-Schema ein",
|
"Type in the desired JSON schema": "Geben Sie das gewünschte JSON-Schema ein",
|
||||||
"Grammar String": "Grammatikzeichenfolge",
|
"Grammar String": "Grammatikzeichenfolge",
|
||||||
"GNBF or ENBF, depends on the backend in use. If you're using this you should know which.": "GNBF oder ENBF, hängt vom verwendeten Backend ab. Wenn Sie dieses verwenden, sollten Sie wissen, welches.",
|
"GBNF or EBNF, depends on the backend in use. If you're using this you should know which.": "GBNF oder EBNF, hängt vom verwendeten Backend ab. Wenn Sie dieses verwenden, sollten Sie wissen, welches.",
|
||||||
"Top P & Min P": "Top P und Min P",
|
"Top P & Min P": "Top P und Min P",
|
||||||
"Load default order": "Standardreihenfolge laden",
|
"Load default order": "Standardreihenfolge laden",
|
||||||
"llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "Nur llama.cpp. Bestimmt die Reihenfolge der Sampler. Wenn der Mirostat-Modus nicht 0 ist, wird die Sampler-Reihenfolge ignoriert.",
|
"llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "Nur llama.cpp. Bestimmt die Reihenfolge der Sampler. Wenn der Mirostat-Modus nicht 0 ist, wird die Sampler-Reihenfolge ignoriert.",
|
||||||
@ -390,7 +390,7 @@
|
|||||||
"Alt Method": "Alternative Methode",
|
"Alt Method": "Alternative Methode",
|
||||||
"AI21 API Key": "AI21 API-Schlüssel",
|
"AI21 API Key": "AI21 API-Schlüssel",
|
||||||
"AI21 Model": "AI21-Modell",
|
"AI21 Model": "AI21-Modell",
|
||||||
"MakerSuite API Key": "MakerSuite API-Schlüssel",
|
"Google AI Studio API Key": "Google AI Studio API-Schlüssel",
|
||||||
"Google Model": "Google-Modell",
|
"Google Model": "Google-Modell",
|
||||||
"MistralAI API Key": "MistralAI API-Schlüssel",
|
"MistralAI API Key": "MistralAI API-Schlüssel",
|
||||||
"MistralAI Model": "MistralAI-Modell",
|
"MistralAI Model": "MistralAI-Modell",
|
||||||
|
@ -207,7 +207,7 @@
|
|||||||
"JSON Schema": "Esquema JSON",
|
"JSON Schema": "Esquema JSON",
|
||||||
"Type in the desired JSON schema": "Escriba el esquema JSON deseado",
|
"Type in the desired JSON schema": "Escriba el esquema JSON deseado",
|
||||||
"Grammar String": "Cadena de gramática",
|
"Grammar String": "Cadena de gramática",
|
||||||
"GNBF or ENBF, depends on the backend in use. If you're using this you should know which.": "GNBF o ENBF, depende del backend en uso. Si estás usando esto, debes saber cuál.",
|
"GBNF or EBNF, depends on the backend in use. If you're using this you should know which.": "GBNF o EBNF, depende del backend en uso. Si estás usando esto, debes saber cuál.",
|
||||||
"Top P & Min P": "P superior y P mínima",
|
"Top P & Min P": "P superior y P mínima",
|
||||||
"Load default order": "Cargar orden predeterminado",
|
"Load default order": "Cargar orden predeterminado",
|
||||||
"llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "llama.cpp únicamente. Determina el orden de los muestreadores. Si el modo Mirostat no es 0, se ignora el orden de las muestras.",
|
"llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "llama.cpp únicamente. Determina el orden de los muestreadores. Si el modo Mirostat no es 0, se ignora el orden de las muestras.",
|
||||||
@ -390,7 +390,7 @@
|
|||||||
"Alt Method": "Método alternativo",
|
"Alt Method": "Método alternativo",
|
||||||
"AI21 API Key": "Clave API de AI21",
|
"AI21 API Key": "Clave API de AI21",
|
||||||
"AI21 Model": "Modelo de AI21",
|
"AI21 Model": "Modelo de AI21",
|
||||||
"MakerSuite API Key": "Clave API de MakerSuite",
|
"Google AI Studio API Key": "Clave API de Google AI Studio",
|
||||||
"Google Model": "Modelo de Google",
|
"Google Model": "Modelo de Google",
|
||||||
"MistralAI API Key": "Clave API de MistralAI",
|
"MistralAI API Key": "Clave API de MistralAI",
|
||||||
"MistralAI Model": "Modelo MistralAI",
|
"MistralAI Model": "Modelo MistralAI",
|
||||||
|
@ -207,7 +207,7 @@
|
|||||||
"JSON Schema": "Schéma JSON",
|
"JSON Schema": "Schéma JSON",
|
||||||
"Type in the desired JSON schema": "Tapez le schéma JSON souhaité",
|
"Type in the desired JSON schema": "Tapez le schéma JSON souhaité",
|
||||||
"Grammar String": "Chaîne de grammaire",
|
"Grammar String": "Chaîne de grammaire",
|
||||||
"GNBF or ENBF, depends on the backend in use. If you're using this you should know which.": "GNBF ou ENBF dépend du backend utilisé. Si vous l'utilisez, vous devez savoir lequel.",
|
"GBNF or EBNF, depends on the backend in use. If you're using this you should know which.": "GBNF ou EBNF dépend du backend utilisé. Si vous l'utilisez, vous devez savoir lequel.",
|
||||||
"Top P & Min P": "P supérieur et P minimal",
|
"Top P & Min P": "P supérieur et P minimal",
|
||||||
"Load default order": "Charger l'ordre par défaut",
|
"Load default order": "Charger l'ordre par défaut",
|
||||||
"llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "lama.cpp uniquement. Détermine l’ordre des échantillonneurs. Si le mode Mirostat n'est pas 0, l'ordre de l'échantillonneur est ignoré.",
|
"llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "lama.cpp uniquement. Détermine l’ordre des échantillonneurs. Si le mode Mirostat n'est pas 0, l'ordre de l'échantillonneur est ignoré.",
|
||||||
@ -390,7 +390,7 @@
|
|||||||
"Alt Method": "Méthode alternative",
|
"Alt Method": "Méthode alternative",
|
||||||
"AI21 API Key": "Clé API AI21",
|
"AI21 API Key": "Clé API AI21",
|
||||||
"AI21 Model": "Modèle AI21",
|
"AI21 Model": "Modèle AI21",
|
||||||
"MakerSuite API Key": "Clé API MakerSuite",
|
"Google AI Studio API Key": "Clé API Google AI Studio",
|
||||||
"Google Model": "Modèle Google",
|
"Google Model": "Modèle Google",
|
||||||
"MistralAI API Key": "Clé API MistralAI",
|
"MistralAI API Key": "Clé API MistralAI",
|
||||||
"MistralAI Model": "Modèle MistralAI",
|
"MistralAI Model": "Modèle MistralAI",
|
||||||
|
@ -207,7 +207,7 @@
|
|||||||
"JSON Schema": "JSON kerfi",
|
"JSON Schema": "JSON kerfi",
|
||||||
"Type in the desired JSON schema": "Sláðu inn æskilegt JSON skema",
|
"Type in the desired JSON schema": "Sláðu inn æskilegt JSON skema",
|
||||||
"Grammar String": "Málfræðistrengur",
|
"Grammar String": "Málfræðistrengur",
|
||||||
"GNBF or ENBF, depends on the backend in use. If you're using this you should know which.": "GNBF eða ENBF, fer eftir bakendanum sem er í notkun. Ef þú ert að nota þetta ættir þú að vita hvaða.",
|
"GBNF or EBNF, depends on the backend in use. If you're using this you should know which.": "GBNF eða EBNF, fer eftir bakendanum sem er í notkun. Ef þú ert að nota þetta ættir þú að vita hvaða.",
|
||||||
"Top P & Min P": "Efstu P & Min P",
|
"Top P & Min P": "Efstu P & Min P",
|
||||||
"Load default order": "Hlaða sjálfgefna röð",
|
"Load default order": "Hlaða sjálfgefna röð",
|
||||||
"llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "llama.cpp eingöngu. Ákveður röð sýnataka. Ef Mirostat hamur er ekki 0, er röð sýnatöku hunsuð.",
|
"llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "llama.cpp eingöngu. Ákveður röð sýnataka. Ef Mirostat hamur er ekki 0, er röð sýnatöku hunsuð.",
|
||||||
@ -390,7 +390,7 @@
|
|||||||
"Alt Method": "Aðferð Bakmenn",
|
"Alt Method": "Aðferð Bakmenn",
|
||||||
"AI21 API Key": "Lykill API fyrir AI21",
|
"AI21 API Key": "Lykill API fyrir AI21",
|
||||||
"AI21 Model": "AI21 Módel",
|
"AI21 Model": "AI21 Módel",
|
||||||
"MakerSuite API Key": "MakerSuite API lykill",
|
"Google AI Studio API Key": "Google AI Studio API lykill",
|
||||||
"Google Model": "Google líkan",
|
"Google Model": "Google líkan",
|
||||||
"MistralAI API Key": "MistralAI API lykill",
|
"MistralAI API Key": "MistralAI API lykill",
|
||||||
"MistralAI Model": "MistralAI líkan",
|
"MistralAI Model": "MistralAI líkan",
|
||||||
|
@ -207,7 +207,7 @@
|
|||||||
"JSON Schema": "Schema JSON",
|
"JSON Schema": "Schema JSON",
|
||||||
"Type in the desired JSON schema": "Digita lo schema JSON desiderato",
|
"Type in the desired JSON schema": "Digita lo schema JSON desiderato",
|
||||||
"Grammar String": "Stringa grammaticale",
|
"Grammar String": "Stringa grammaticale",
|
||||||
"GNBF or ENBF, depends on the backend in use. If you're using this you should know which.": "GNBF o ENBF, dipende dal backend in uso. Se stai usando questo dovresti sapere quale.",
|
"GBNF or EBNF, depends on the backend in use. If you're using this you should know which.": "GBNF o EBNF, dipende dal backend in uso. Se stai usando questo dovresti sapere quale.",
|
||||||
"Top P & Min P": "P massimo e P minimo",
|
"Top P & Min P": "P massimo e P minimo",
|
||||||
"Load default order": "Carica ordine predefinito",
|
"Load default order": "Carica ordine predefinito",
|
||||||
"llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "Solo lama.cpp. Determina l'ordine dei campionatori. Se la modalità Mirostat non è 0, l'ordine del campionatore viene ignorato.",
|
"llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "Solo lama.cpp. Determina l'ordine dei campionatori. Se la modalità Mirostat non è 0, l'ordine del campionatore viene ignorato.",
|
||||||
@ -390,7 +390,7 @@
|
|||||||
"Alt Method": "Metodo alternativo",
|
"Alt Method": "Metodo alternativo",
|
||||||
"AI21 API Key": "Chiave API di AI21",
|
"AI21 API Key": "Chiave API di AI21",
|
||||||
"AI21 Model": "Modello AI21",
|
"AI21 Model": "Modello AI21",
|
||||||
"MakerSuite API Key": "Chiave API MakerSuite",
|
"Google AI Studio API Key": "Chiave API Google AI Studio",
|
||||||
"Google Model": "Modello Google",
|
"Google Model": "Modello Google",
|
||||||
"MistralAI API Key": "Chiave API MistralAI",
|
"MistralAI API Key": "Chiave API MistralAI",
|
||||||
"MistralAI Model": "Modello MistralAI",
|
"MistralAI Model": "Modello MistralAI",
|
||||||
|
@ -207,7 +207,7 @@
|
|||||||
"JSON Schema": "JSONスキーマ",
|
"JSON Schema": "JSONスキーマ",
|
||||||
"Type in the desired JSON schema": "希望するJSONスキーマを入力します",
|
"Type in the desired JSON schema": "希望するJSONスキーマを入力します",
|
||||||
"Grammar String": "文法文字列",
|
"Grammar String": "文法文字列",
|
||||||
"GNBF or ENBF, depends on the backend in use. If you're using this you should know which.": "GNBF または ENBF は、使用するバックエンドによって異なります。これを使用する場合は、どちらであるかを知っておく必要があります。",
|
"GBNF or EBNF, depends on the backend in use. If you're using this you should know which.": "GBNF または EBNF は、使用するバックエンドによって異なります。これを使用する場合は、どちらであるかを知っておく必要があります。",
|
||||||
"Top P & Min P": "トップPと最小P",
|
"Top P & Min P": "トップPと最小P",
|
||||||
"Load default order": "デフォルトの順序を読み込む",
|
"Load default order": "デフォルトの順序を読み込む",
|
||||||
"llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "llama.cpp のみ。サンプラーの順序を決定します。Mirostat モードが 0 でない場合、サンプラーの順序は無視されます。",
|
"llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "llama.cpp のみ。サンプラーの順序を決定します。Mirostat モードが 0 でない場合、サンプラーの順序は無視されます。",
|
||||||
@ -390,7 +390,7 @@
|
|||||||
"Alt Method": "代替手法",
|
"Alt Method": "代替手法",
|
||||||
"AI21 API Key": "AI21のAPIキー",
|
"AI21 API Key": "AI21のAPIキー",
|
||||||
"AI21 Model": "AI21モデル",
|
"AI21 Model": "AI21モデル",
|
||||||
"MakerSuite API Key": "MakerSuite APIキー",
|
"Google AI Studio API Key": "Google AI Studio APIキー",
|
||||||
"Google Model": "Google モデル",
|
"Google Model": "Google モデル",
|
||||||
"MistralAI API Key": "MistralAI API キー",
|
"MistralAI API Key": "MistralAI API キー",
|
||||||
"MistralAI Model": "MistralAI モデル",
|
"MistralAI Model": "MistralAI モデル",
|
||||||
|
@ -207,7 +207,7 @@
|
|||||||
"JSON Schema": "JSON 스키마",
|
"JSON Schema": "JSON 스키마",
|
||||||
"Type in the desired JSON schema": "원하는 JSON 스키마를 입력하세요.",
|
"Type in the desired JSON schema": "원하는 JSON 스키마를 입력하세요.",
|
||||||
"Grammar String": "문법 문자열",
|
"Grammar String": "문법 문자열",
|
||||||
"GNBF or ENBF, depends on the backend in use. If you're using this you should know which.": "GNBF 또는 ENBF는 사용 중인 백엔드에 따라 다릅니다. 이것을 사용한다면 어느 것이 무엇인지 알아야합니다.",
|
"GBNF or EBNF, depends on the backend in use. If you're using this you should know which.": "GBNF 또는 EBNF는 사용 중인 백엔드에 따라 다릅니다. 이것을 사용한다면 어느 것이 무엇인지 알아야합니다.",
|
||||||
"Top P & Min P": "상위 P 및 최소 P",
|
"Top P & Min P": "상위 P 및 최소 P",
|
||||||
"Load default order": "기본 순서로 로드",
|
"Load default order": "기본 순서로 로드",
|
||||||
"llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "llama.cpp만 가능합니다. 샘플러의 순서를 결정합니다. Mirostat 모드가 0이 아닌 경우 샘플러 순서는 무시됩니다.",
|
"llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "llama.cpp만 가능합니다. 샘플러의 순서를 결정합니다. Mirostat 모드가 0이 아닌 경우 샘플러 순서는 무시됩니다.",
|
||||||
@ -390,7 +390,7 @@
|
|||||||
"Alt Method": "대체 방법",
|
"Alt Method": "대체 방법",
|
||||||
"AI21 API Key": "AI21 API 키",
|
"AI21 API Key": "AI21 API 키",
|
||||||
"AI21 Model": "AI21 모델",
|
"AI21 Model": "AI21 모델",
|
||||||
"MakerSuite API Key": "MakerSuite API 키",
|
"Google AI Studio API Key": "Google AI Studio API 키",
|
||||||
"Google Model": "구글 모델",
|
"Google Model": "구글 모델",
|
||||||
"MistralAI API Key": "MistralAI API 키",
|
"MistralAI API Key": "MistralAI API 키",
|
||||||
"MistralAI Model": "MistralAI 모델",
|
"MistralAI Model": "MistralAI 모델",
|
||||||
|
@ -207,7 +207,7 @@
|
|||||||
"JSON Schema": "JSON-schema",
|
"JSON Schema": "JSON-schema",
|
||||||
"Type in the desired JSON schema": "Typ het gewenste JSON-schema",
|
"Type in the desired JSON schema": "Typ het gewenste JSON-schema",
|
||||||
"Grammar String": "Grammaticareeks",
|
"Grammar String": "Grammaticareeks",
|
||||||
"GNBF or ENBF, depends on the backend in use. If you're using this you should know which.": "GNBF of ENBF, hangt af van de gebruikte backend. Als u dit gebruikt, moet u weten welke.",
|
"GBNF or EBNF, depends on the backend in use. If you're using this you should know which.": "GBNF of EBNF, hangt af van de gebruikte backend. Als u dit gebruikt, moet u weten welke.",
|
||||||
"Top P & Min P": "Top P & Min P",
|
"Top P & Min P": "Top P & Min P",
|
||||||
"Load default order": "Standaardvolgorde laden",
|
"Load default order": "Standaardvolgorde laden",
|
||||||
"llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "alleen lama.cpp. Bepaalt de volgorde van de samplers. Als de Mirostat-modus niet 0 is, wordt de samplervolgorde genegeerd.",
|
"llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "alleen lama.cpp. Bepaalt de volgorde van de samplers. Als de Mirostat-modus niet 0 is, wordt de samplervolgorde genegeerd.",
|
||||||
|
@ -207,7 +207,7 @@
|
|||||||
"JSON Schema": "Esquema JSON",
|
"JSON Schema": "Esquema JSON",
|
||||||
"Type in the desired JSON schema": "Digite o esquema JSON desejado",
|
"Type in the desired JSON schema": "Digite o esquema JSON desejado",
|
||||||
"Grammar String": "Cadeia de Gramática",
|
"Grammar String": "Cadeia de Gramática",
|
||||||
"GNBF or ENBF, depends on the backend in use. If you're using this you should know which.": "GNBF ou ENBF, depende do backend em uso. Se você estiver usando isso, você deve saber qual.",
|
"GBNF or EBNF, depends on the backend in use. If you're using this you should know which.": "GBNF ou EBNF, depende do backend em uso. Se você estiver usando isso, você deve saber qual.",
|
||||||
"Top P & Min P": "P superior e P mínimo",
|
"Top P & Min P": "P superior e P mínimo",
|
||||||
"Load default order": "Carregar ordem padrão",
|
"Load default order": "Carregar ordem padrão",
|
||||||
"llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "apenas lhama.cpp. Determina a ordem dos amostradores. Se o modo Mirostat não for 0, a ordem do amostrador será ignorada.",
|
"llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "apenas lhama.cpp. Determina a ordem dos amostradores. Se o modo Mirostat não for 0, a ordem do amostrador será ignorada.",
|
||||||
@ -390,7 +390,7 @@
|
|||||||
"Alt Method": "Método Alternativo",
|
"Alt Method": "Método Alternativo",
|
||||||
"AI21 API Key": "Chave da API AI21",
|
"AI21 API Key": "Chave da API AI21",
|
||||||
"AI21 Model": "Modelo AI21",
|
"AI21 Model": "Modelo AI21",
|
||||||
"MakerSuite API Key": "Chave API MakerSuite",
|
"Google AI Studio API Key": "Chave API Google AI Studio",
|
||||||
"Google Model": "Modelo Google",
|
"Google Model": "Modelo Google",
|
||||||
"MistralAI API Key": "Chave de API MistralAI",
|
"MistralAI API Key": "Chave de API MistralAI",
|
||||||
"MistralAI Model": "Modelo MistralAI",
|
"MistralAI Model": "Modelo MistralAI",
|
||||||
|
@ -722,7 +722,7 @@
|
|||||||
"Proxy Server URL": "Адрес прокси-сервера",
|
"Proxy Server URL": "Адрес прокси-сервера",
|
||||||
"MistralAI Model": "Модель MistralAI",
|
"MistralAI Model": "Модель MistralAI",
|
||||||
"MistralAI API Key": "Ключ от API MistralAI",
|
"MistralAI API Key": "Ключ от API MistralAI",
|
||||||
"MakerSuite API Key": "Ключ от API MakerSuite",
|
"Google AI Studio API Key": "Ключ от API Google AI Studio",
|
||||||
"Google Model": "Модель Google",
|
"Google Model": "Модель Google",
|
||||||
"Cohere API Key": "Ключ от API Cohere",
|
"Cohere API Key": "Ключ от API Cohere",
|
||||||
"Cohere Model": "Модель Cohere",
|
"Cohere Model": "Модель Cohere",
|
||||||
@ -978,7 +978,7 @@
|
|||||||
"char_import_6": "Прямая ссылка на PNG-файл (чтобы узнать список разрешённых хостов, загляните в",
|
"char_import_6": "Прямая ссылка на PNG-файл (чтобы узнать список разрешённых хостов, загляните в",
|
||||||
"char_import_7": ")",
|
"char_import_7": ")",
|
||||||
"Grammar String": "Грамматика",
|
"Grammar String": "Грамматика",
|
||||||
"GNBF or ENBF, depends on the backend in use. If you're using this you should know which.": "GNBF или ENBF, зависит от бэкенда. Если вы это используете, то, скорее всего, сами знаете, какой именно.",
|
"GBNF or EBNF, depends on the backend in use. If you're using this you should know which.": "GBNF или EBNF, зависит от бэкенда. Если вы это используете, то, скорее всего, сами знаете, какой именно.",
|
||||||
"Account": "Аккаунт",
|
"Account": "Аккаунт",
|
||||||
"Hi,": "Привет,",
|
"Hi,": "Привет,",
|
||||||
"To enable multi-account features, restart the SillyTavern server with": "Чтобы активировать систему аккаунтов, перезапустите SillyTavern, выставив",
|
"To enable multi-account features, restart the SillyTavern server with": "Чтобы активировать систему аккаунтов, перезапустите SillyTavern, выставив",
|
||||||
|
@ -207,7 +207,7 @@
|
|||||||
"JSON Schema": "Схема JSON",
|
"JSON Schema": "Схема JSON",
|
||||||
"Type in the desired JSON schema": "Введіть потрібну схему JSON",
|
"Type in the desired JSON schema": "Введіть потрібну схему JSON",
|
||||||
"Grammar String": "Граматичний рядок",
|
"Grammar String": "Граматичний рядок",
|
||||||
"GNBF or ENBF, depends on the backend in use. If you're using this you should know which.": "GNBF або ENBF, залежить від серверної частини, яка використовується. Якщо ви використовуєте це, ви повинні знати, який.",
|
"GBNF or EBNF, depends on the backend in use. If you're using this you should know which.": "GBNF або EBNF, залежить від серверної частини, яка використовується. Якщо ви використовуєте це, ви повинні знати, який.",
|
||||||
"Top P & Min P": "Верхній P & Min P",
|
"Top P & Min P": "Верхній P & Min P",
|
||||||
"Load default order": "Завантажити типовий порядок",
|
"Load default order": "Завантажити типовий порядок",
|
||||||
"llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "лише llama.cpp. Визначає порядок пробовідбірників. Якщо режим Mirostat не 0, порядок вибірки ігнорується.",
|
"llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "лише llama.cpp. Визначає порядок пробовідбірників. Якщо режим Mirostat не 0, порядок вибірки ігнорується.",
|
||||||
@ -390,7 +390,7 @@
|
|||||||
"Alt Method": "Альтернативний метод",
|
"Alt Method": "Альтернативний метод",
|
||||||
"AI21 API Key": "Ключ API для AI21",
|
"AI21 API Key": "Ключ API для AI21",
|
||||||
"AI21 Model": "Модель AI21",
|
"AI21 Model": "Модель AI21",
|
||||||
"MakerSuite API Key": "Ключ API MakerSuite",
|
"Google AI Studio API Key": "Ключ API Google AI Studio",
|
||||||
"Google Model": "Модель Google",
|
"Google Model": "Модель Google",
|
||||||
"MistralAI API Key": "Ключ API MistralAI",
|
"MistralAI API Key": "Ключ API MistralAI",
|
||||||
"MistralAI Model": "Модель MistralAI",
|
"MistralAI Model": "Модель MistralAI",
|
||||||
|
@ -207,7 +207,7 @@
|
|||||||
"JSON Schema": "Lược đồ JSON",
|
"JSON Schema": "Lược đồ JSON",
|
||||||
"Type in the desired JSON schema": "Nhập lược đồ JSON mong muốn",
|
"Type in the desired JSON schema": "Nhập lược đồ JSON mong muốn",
|
||||||
"Grammar String": "Chuỗi ngữ pháp",
|
"Grammar String": "Chuỗi ngữ pháp",
|
||||||
"GNBF or ENBF, depends on the backend in use. If you're using this you should know which.": "GNBF hoặc ENBF, tùy thuộc vào backend đang sử dụng. Nếu bạn đang sử dụng cái này, bạn nên biết cái nào.",
|
"GBNF or EBNF, depends on the backend in use. If you're using this you should know which.": "GBNF hoặc EBNF, tùy thuộc vào backend đang sử dụng. Nếu bạn đang sử dụng cái này, bạn nên biết cái nào.",
|
||||||
"Top P & Min P": "P & P tối thiểu hàng đầu",
|
"Top P & Min P": "P & P tối thiểu hàng đầu",
|
||||||
"Load default order": "Tải thứ tự mặc định",
|
"Load default order": "Tải thứ tự mặc định",
|
||||||
"llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "chỉ llama.cpp. Xác định thứ tự lấy mẫu. Nếu chế độ Mirostat khác 0, thứ tự lấy mẫu sẽ bị bỏ qua.",
|
"llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "chỉ llama.cpp. Xác định thứ tự lấy mẫu. Nếu chế độ Mirostat khác 0, thứ tự lấy mẫu sẽ bị bỏ qua.",
|
||||||
@ -390,7 +390,7 @@
|
|||||||
"Alt Method": "Phương pháp thay thế",
|
"Alt Method": "Phương pháp thay thế",
|
||||||
"AI21 API Key": "Khóa API của AI21",
|
"AI21 API Key": "Khóa API của AI21",
|
||||||
"AI21 Model": "Mô hình AI21",
|
"AI21 Model": "Mô hình AI21",
|
||||||
"MakerSuite API Key": "Khóa API MakerSuite",
|
"Google AI Studio API Key": "Khóa API Google AI Studio",
|
||||||
"Google Model": "Mô hình Google",
|
"Google Model": "Mô hình Google",
|
||||||
"MistralAI API Key": "Khóa API MistralAI",
|
"MistralAI API Key": "Khóa API MistralAI",
|
||||||
"MistralAI Model": "Mô hình MistralAI",
|
"MistralAI Model": "Mô hình MistralAI",
|
||||||
|
@ -208,9 +208,10 @@
|
|||||||
"JSON Schema": "JSON 结构",
|
"JSON Schema": "JSON 结构",
|
||||||
"Type in the desired JSON schema": "输入所需的 JSON 结构",
|
"Type in the desired JSON schema": "输入所需的 JSON 结构",
|
||||||
"Grammar String": "语法字符串",
|
"Grammar String": "语法字符串",
|
||||||
"GNBF or ENBF, depends on the backend in use. If you're using this you should know which.": "GNBF 或 ENBF,取决于使用的后端。如果您使用这个,您应该知道该用哪一个。",
|
"GBNF or EBNF, depends on the backend in use. If you're using this you should know which.": "GBNF 或 EBNF,取决于使用的后端。如果您使用这个,您应该知道该用哪一个。",
|
||||||
"Top P & Min P": "Top P 和 Min P",
|
"Top P & Min P": "Top P 和 Min P",
|
||||||
"Load default order": "加载默认顺序",
|
"Load default order": "加载默认顺序",
|
||||||
|
"Sampler Order": "取样器顺序",
|
||||||
"llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "仅限 llama.cpp。确定采样器的顺序。如果 Mirostat 模式不为 0,则忽略采样器顺序。",
|
"llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "仅限 llama.cpp。确定采样器的顺序。如果 Mirostat 模式不为 0,则忽略采样器顺序。",
|
||||||
"Sampler Priority": "采样器优先级",
|
"Sampler Priority": "采样器优先级",
|
||||||
"Ooba only. Determines the order of samplers.": "确定采样器的顺序(仅适用于Ooba)",
|
"Ooba only. Determines the order of samplers.": "确定采样器的顺序(仅适用于Ooba)",
|
||||||
@ -405,7 +406,7 @@
|
|||||||
"Alt Method": "备用方法",
|
"Alt Method": "备用方法",
|
||||||
"AI21 API Key": "AI21 API 密钥",
|
"AI21 API Key": "AI21 API 密钥",
|
||||||
"AI21 Model": "AI21 模型",
|
"AI21 Model": "AI21 模型",
|
||||||
"MakerSuite API Key": "MakerSuite API 密钥",
|
"Google AI Studio API Key": "Google AI Studio API 密钥",
|
||||||
"Google Model": "Google 模型",
|
"Google Model": "Google 模型",
|
||||||
"MistralAI API Key": "MistralAI API 密钥",
|
"MistralAI API Key": "MistralAI API 密钥",
|
||||||
"MistralAI Model": "MistralAI 模型",
|
"MistralAI Model": "MistralAI 模型",
|
||||||
@ -443,7 +444,9 @@
|
|||||||
"Example Separator": "示例分隔符",
|
"Example Separator": "示例分隔符",
|
||||||
"Chat Start": "聊天开始",
|
"Chat Start": "聊天开始",
|
||||||
"Add Chat Start and Example Separator to a list of stopping strings.": "将聊天开始和示例分隔符添加到停止字符串列表中。",
|
"Add Chat Start and Example Separator to a list of stopping strings.": "将聊天开始和示例分隔符添加到停止字符串列表中。",
|
||||||
"Use as Stop Strings": "用作停止字符串",
|
"Separators as Stop Strings": "分隔符作为终止字符串",
|
||||||
|
"Add Character and User names to a list of stopping strings.": "将角色和用户名添加到停止字符串列表中。",
|
||||||
|
"Names as Stop Strings": "名称作为终止字符串",
|
||||||
"context_allow_post_history_instructions": "如果在角色卡中定义并且启用了“首选角色卡说明”,则在提示末尾包含后历史说明。\n不建议在文本补全模型中使用此功能,否则会导致输出错误。",
|
"context_allow_post_history_instructions": "如果在角色卡中定义并且启用了“首选角色卡说明”,则在提示末尾包含后历史说明。\n不建议在文本补全模型中使用此功能,否则会导致输出错误。",
|
||||||
"Allow Post-History Instructions": "允许后历史说明",
|
"Allow Post-History Instructions": "允许后历史说明",
|
||||||
"Context Order": "上下文顺序",
|
"Context Order": "上下文顺序",
|
||||||
@ -704,10 +707,10 @@
|
|||||||
"Restore User Input": "恢复用户输入",
|
"Restore User Input": "恢复用户输入",
|
||||||
"Allow repositioning certain UI elements by dragging them. PC only, no effect on mobile": "允许通过拖动重新定位某些UI元素。仅适用于PC,对移动设备无影响",
|
"Allow repositioning certain UI elements by dragging them. PC only, no effect on mobile": "允许通过拖动重新定位某些UI元素。仅适用于PC,对移动设备无影响",
|
||||||
"Movable UI Panels": "可移动 UI 面板",
|
"Movable UI Panels": "可移动 UI 面板",
|
||||||
|
"Reset MovingUI panel sizes/locations.": "重置 MovingUI 面板大小/位置。",
|
||||||
"MovingUI preset. Predefined/saved draggable positions": "可移动UI预设。预定义/保存的可拖动位置",
|
"MovingUI preset. Predefined/saved draggable positions": "可移动UI预设。预定义/保存的可拖动位置",
|
||||||
"MUI Preset": "可移动 UI 预设",
|
"MUI Preset": "可移动 UI 预设",
|
||||||
"Save movingUI changes to a new file": "将可移动UI更改保存到新文件中",
|
"Save movingUI changes to a new file": "将可移动UI更改保存到新文件中",
|
||||||
"Reset MovingUI panel sizes/locations.": "重置 MovingUI 面板大小/位置。",
|
|
||||||
"Apply a custom CSS style to all of the ST GUI": "将自定义CSS样式应用于所有ST GUI",
|
"Apply a custom CSS style to all of the ST GUI": "将自定义CSS样式应用于所有ST GUI",
|
||||||
"Custom CSS": "自定义 CSS",
|
"Custom CSS": "自定义 CSS",
|
||||||
"Expand the editor": "展开编辑器",
|
"Expand the editor": "展开编辑器",
|
||||||
@ -727,6 +730,8 @@
|
|||||||
"Press Send to continue": "按发送键以继续",
|
"Press Send to continue": "按发送键以继续",
|
||||||
"Show a button in the input area to ask the AI to continue (extend) its last message": "在输入区域中显示一个按钮,要求AI继续(延长)其上一条消息",
|
"Show a button in the input area to ask the AI to continue (extend) its last message": "在输入区域中显示一个按钮,要求AI继续(延长)其上一条消息",
|
||||||
"Quick 'Continue' button": "快速“继续”按钮",
|
"Quick 'Continue' button": "快速“继续”按钮",
|
||||||
|
"Show a button in the input area to ask the AI to impersonate your character for a single message": "在输入区域中显示一个按钮,让 AI 模仿你的角色发送一条消息。",
|
||||||
|
"Quick 'Impersonate' button": "快速“模仿”按钮",
|
||||||
"Show arrow buttons on the last in-chat message to generate alternative AI responses. Both PC and mobile": "在聊天窗口的最后一条信息上显示箭头按钮,以生成AI的其他回复选项。适用于电脑和手机端。",
|
"Show arrow buttons on the last in-chat message to generate alternative AI responses. Both PC and mobile": "在聊天窗口的最后一条信息上显示箭头按钮,以生成AI的其他回复选项。适用于电脑和手机端。",
|
||||||
"Swipes": "刷新回复按钮",
|
"Swipes": "刷新回复按钮",
|
||||||
"Allow using swiping gestures on the last in-chat message to trigger swipe generation. Mobile only, no effect on PC": "允许在最后一条聊天消息上使用滑动手势触发滑动生成。仅适用于移动设备,对PC无影响",
|
"Allow using swiping gestures on the last in-chat message to trigger swipe generation. Mobile only, no effect on PC": "允许在最后一条聊天消息上使用滑动手势触发滑动生成。仅适用于移动设备,对PC无影响",
|
||||||
@ -772,6 +777,10 @@
|
|||||||
"Autocomplete Style": "风格",
|
"Autocomplete Style": "风格",
|
||||||
"Follow Theme": "关注主题",
|
"Follow Theme": "关注主题",
|
||||||
"Dark": "黑暗的",
|
"Dark": "黑暗的",
|
||||||
|
"Keyboard": "键盘:",
|
||||||
|
"Select with Tab or Enter": "使用 Tab 或 Enter 选择",
|
||||||
|
"Select with Tab": "使用 Tab 选择",
|
||||||
|
"Select with Enter": "按 Enter 键选择",
|
||||||
"Sets the font size of the autocomplete.": "设置自动完成的字体大小。",
|
"Sets the font size of the autocomplete.": "设置自动完成的字体大小。",
|
||||||
"Sets the width of the autocomplete.": "设置自动完成的宽度。",
|
"Sets the width of the autocomplete.": "设置自动完成的宽度。",
|
||||||
"Autocomplete Width": "宽度",
|
"Autocomplete Width": "宽度",
|
||||||
@ -1176,6 +1185,7 @@
|
|||||||
"Pause script execution": "暂停执行脚本",
|
"Pause script execution": "暂停执行脚本",
|
||||||
"Abort script execution": "中止执行脚本",
|
"Abort script execution": "中止执行脚本",
|
||||||
"Abort request": "中止请求",
|
"Abort request": "中止请求",
|
||||||
|
"Ask AI to write your message for you": "让AI为您撰写消息",
|
||||||
"Continue the last message": "继续上一条消息",
|
"Continue the last message": "继续上一条消息",
|
||||||
"Send a message": "发送消息",
|
"Send a message": "发送消息",
|
||||||
"Close chat": "关闭聊天",
|
"Close chat": "关闭聊天",
|
||||||
@ -1187,7 +1197,6 @@
|
|||||||
"Manage chat files": "管理聊天文件",
|
"Manage chat files": "管理聊天文件",
|
||||||
"Delete messages": "删除消息",
|
"Delete messages": "删除消息",
|
||||||
"Regenerate": "重新生成",
|
"Regenerate": "重新生成",
|
||||||
"Ask AI to write your message for you": "请求AI为您撰写消息",
|
|
||||||
"Impersonate": "AI 帮答",
|
"Impersonate": "AI 帮答",
|
||||||
"Continue": "继续",
|
"Continue": "继续",
|
||||||
"Bind user name to that avatar": "将用户名称绑定到该头像",
|
"Bind user name to that avatar": "将用户名称绑定到该头像",
|
||||||
@ -1341,6 +1350,7 @@
|
|||||||
"How many messages before the current end of the chat.": "当前聊天结束前还有多少条消息。",
|
"How many messages before the current end of the chat.": "当前聊天结束前还有多少条消息。",
|
||||||
"Labels and Message": "标签和信息",
|
"Labels and Message": "标签和信息",
|
||||||
"Label": "标签",
|
"Label": "标签",
|
||||||
|
"(label of the button, if no icon is chosen) ": "(如果没有选择图标,则为按钮的标签)",
|
||||||
"Title": "标题",
|
"Title": "标题",
|
||||||
"(tooltip, leave empty to show message or /command)": "(工具提示,留空以显示消息或/命令)",
|
"(tooltip, leave empty to show message or /command)": "(工具提示,留空以显示消息或/命令)",
|
||||||
"Message / Command:": "消息/命令:",
|
"Message / Command:": "消息/命令:",
|
||||||
@ -1371,6 +1381,8 @@
|
|||||||
"Inject user input automatically": "自动注入用户输入",
|
"Inject user input automatically": "自动注入用户输入",
|
||||||
"(if disabled, use ": "(如果禁用,使用",
|
"(if disabled, use ": "(如果禁用,使用",
|
||||||
"macro for manual injection)": "宏用于手动注入)",
|
"macro for manual injection)": "宏用于手动注入)",
|
||||||
|
"Color": "颜色",
|
||||||
|
"Only apply color as accent": "仅应用颜色作为强调",
|
||||||
"ext_regex_title": "正则",
|
"ext_regex_title": "正则",
|
||||||
"ext_regex_new_global_script_desc": "新的全局正则表达式脚本",
|
"ext_regex_new_global_script_desc": "新的全局正则表达式脚本",
|
||||||
"ext_regex_new_global_script": "新建全局正则",
|
"ext_regex_new_global_script": "新建全局正则",
|
||||||
@ -1419,6 +1431,7 @@
|
|||||||
"ext_regex_export_script": "导出脚本",
|
"ext_regex_export_script": "导出脚本",
|
||||||
"ext_regex_delete_script": "删除脚本",
|
"ext_regex_delete_script": "删除脚本",
|
||||||
"Trigger Stable Diffusion": "触发Stable Diffusion",
|
"Trigger Stable Diffusion": "触发Stable Diffusion",
|
||||||
|
"Abort current image generation task": "中止当前图像生成",
|
||||||
"sd_Yourself": "你自己",
|
"sd_Yourself": "你自己",
|
||||||
"sd_Your_Face": "你的脸",
|
"sd_Your_Face": "你的脸",
|
||||||
"sd_Me": "我",
|
"sd_Me": "我",
|
||||||
@ -1572,6 +1585,10 @@
|
|||||||
"Only used when Main API is selected.": "仅在选择主 API 时使用。",
|
"Only used when Main API is selected.": "仅在选择主 API 时使用。",
|
||||||
"Old messages are vectorized gradually as you chat. To process all previous messages, click the button below.": "随着您聊天,旧消息会逐渐矢量化。\n要处理所有以前的消息,请单击下面的按钮。",
|
"Old messages are vectorized gradually as you chat. To process all previous messages, click the button below.": "随着您聊天,旧消息会逐渐矢量化。\n要处理所有以前的消息,请单击下面的按钮。",
|
||||||
"View Stats": "查看统计数据",
|
"View Stats": "查看统计数据",
|
||||||
|
"Title/Memo": "标题/备忘录",
|
||||||
|
"Status": "状态",
|
||||||
|
"Position": "位置",
|
||||||
|
"Trigger %": "触发率 %",
|
||||||
"Manager Users": "管理用户",
|
"Manager Users": "管理用户",
|
||||||
"New User": "新用户",
|
"New User": "新用户",
|
||||||
"Status:": "地位:",
|
"Status:": "地位:",
|
||||||
|
@ -208,7 +208,7 @@
|
|||||||
"JSON Schema": "JSON 結構",
|
"JSON Schema": "JSON 結構",
|
||||||
"Type in the desired JSON schema": "輸入所需的 JSON 結構",
|
"Type in the desired JSON schema": "輸入所需的 JSON 結構",
|
||||||
"Grammar String": "語法字串",
|
"Grammar String": "語法字串",
|
||||||
"GNBF or ENBF, depends on the backend in use. If you're using this you should know which.": "GNBF 或 ENBF,取決於所使用的後端。如果您使用此功能,應該知道是哪一種",
|
"GBNF or EBNF, depends on the backend in use. If you're using this you should know which.": "GBNF 或 EBNF,取決於所使用的後端。如果您使用此功能,應該知道是哪一種",
|
||||||
"Top P & Min P": "Top P 和 Min P",
|
"Top P & Min P": "Top P 和 Min P",
|
||||||
"Load default order": "載入預設順序",
|
"Load default order": "載入預設順序",
|
||||||
"llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "僅適用於 llama.cpp。決定取樣器的順序。如果 Mirostat 模式不為 0,則忽略取樣器順序。",
|
"llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "僅適用於 llama.cpp。決定取樣器的順序。如果 Mirostat 模式不為 0,則忽略取樣器順序。",
|
||||||
@ -391,7 +391,7 @@
|
|||||||
"Alt Method": "替代方法",
|
"Alt Method": "替代方法",
|
||||||
"AI21 API Key": "AI21 API 金鑰",
|
"AI21 API Key": "AI21 API 金鑰",
|
||||||
"AI21 Model": "AI21 模型",
|
"AI21 Model": "AI21 模型",
|
||||||
"MakerSuite API Key": "MakerSuite API 金鑰",
|
"Google AI Studio API Key": "Google AI Studio API 金鑰",
|
||||||
"Google Model": "Google 模型",
|
"Google Model": "Google 模型",
|
||||||
"MistralAI API Key": "MistralAI API 金鑰",
|
"MistralAI API Key": "MistralAI API 金鑰",
|
||||||
"MistralAI Model": "MistralAI 模型",
|
"MistralAI Model": "MistralAI 模型",
|
||||||
|
305
public/script.js
305
public/script.js
@ -156,6 +156,7 @@ import {
|
|||||||
ensureImageFormatSupported,
|
ensureImageFormatSupported,
|
||||||
flashHighlight,
|
flashHighlight,
|
||||||
isTrueBoolean,
|
isTrueBoolean,
|
||||||
|
toggleDrawer,
|
||||||
} from './scripts/utils.js';
|
} from './scripts/utils.js';
|
||||||
import { debounce_timeout } from './scripts/constants.js';
|
import { debounce_timeout } from './scripts/constants.js';
|
||||||
|
|
||||||
@ -224,10 +225,10 @@ import {
|
|||||||
import { getBackgrounds, initBackgrounds, loadBackgroundSettings, background_settings } from './scripts/backgrounds.js';
|
import { getBackgrounds, initBackgrounds, loadBackgroundSettings, background_settings } from './scripts/backgrounds.js';
|
||||||
import { hideLoader, showLoader } from './scripts/loader.js';
|
import { hideLoader, showLoader } from './scripts/loader.js';
|
||||||
import { BulkEditOverlay, CharacterContextMenu } from './scripts/BulkEditOverlay.js';
|
import { BulkEditOverlay, CharacterContextMenu } from './scripts/BulkEditOverlay.js';
|
||||||
import { loadFeatherlessModels, loadMancerModels, loadOllamaModels, loadTogetherAIModels, loadInfermaticAIModels, loadOpenRouterModels, loadVllmModels, loadAphroditeModels, loadDreamGenModels } from './scripts/textgen-models.js';
|
import { loadFeatherlessModels, loadMancerModels, loadOllamaModels, loadTogetherAIModels, loadInfermaticAIModels, loadOpenRouterModels, loadVllmModels, loadAphroditeModels, loadDreamGenModels, initTextGenModels } from './scripts/textgen-models.js';
|
||||||
import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags, isExternalMediaAllowed, getCurrentEntityId } from './scripts/chats.js';
|
import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags, isExternalMediaAllowed, getCurrentEntityId } from './scripts/chats.js';
|
||||||
import { initPresetManager } from './scripts/preset-manager.js';
|
import { initPresetManager } from './scripts/preset-manager.js';
|
||||||
import { MacrosParser, evaluateMacros } from './scripts/macros.js';
|
import { MacrosParser, evaluateMacros, getLastMessageId } from './scripts/macros.js';
|
||||||
import { currentUser, setUserControls } from './scripts/user.js';
|
import { currentUser, setUserControls } from './scripts/user.js';
|
||||||
import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup, fixToastrForDialogs } from './scripts/popup.js';
|
import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup, fixToastrForDialogs } from './scripts/popup.js';
|
||||||
import { renderTemplate, renderTemplateAsync } from './scripts/templates.js';
|
import { renderTemplate, renderTemplateAsync } from './scripts/templates.js';
|
||||||
@ -241,7 +242,7 @@ import { DragAndDropHandler } from './scripts/dragdrop.js';
|
|||||||
import { INTERACTABLE_CONTROL_CLASS, initKeyboard } from './scripts/keyboard.js';
|
import { INTERACTABLE_CONTROL_CLASS, initKeyboard } from './scripts/keyboard.js';
|
||||||
import { initDynamicStyles } from './scripts/dynamic-styles.js';
|
import { initDynamicStyles } from './scripts/dynamic-styles.js';
|
||||||
import { SlashCommandEnumValue, enumTypes } from './scripts/slash-commands/SlashCommandEnumValue.js';
|
import { SlashCommandEnumValue, enumTypes } from './scripts/slash-commands/SlashCommandEnumValue.js';
|
||||||
import { enumIcons } from './scripts/slash-commands/SlashCommandCommonEnumsProvider.js';
|
import { commonEnumProviders, enumIcons } from './scripts/slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||||
|
|
||||||
//exporting functions and vars for mods
|
//exporting functions and vars for mods
|
||||||
export {
|
export {
|
||||||
@ -424,6 +425,8 @@ export const event_types = {
|
|||||||
CHATCOMPLETION_MODEL_CHANGED: 'chatcompletion_model_changed',
|
CHATCOMPLETION_MODEL_CHANGED: 'chatcompletion_model_changed',
|
||||||
OAI_PRESET_CHANGED_BEFORE: 'oai_preset_changed_before',
|
OAI_PRESET_CHANGED_BEFORE: 'oai_preset_changed_before',
|
||||||
OAI_PRESET_CHANGED_AFTER: 'oai_preset_changed_after',
|
OAI_PRESET_CHANGED_AFTER: 'oai_preset_changed_after',
|
||||||
|
OAI_PRESET_EXPORT_READY: 'oai_preset_export_ready',
|
||||||
|
OAI_PRESET_IMPORT_READY: 'oai_preset_import_ready',
|
||||||
WORLDINFO_SETTINGS_UPDATED: 'worldinfo_settings_updated',
|
WORLDINFO_SETTINGS_UPDATED: 'worldinfo_settings_updated',
|
||||||
WORLDINFO_UPDATED: 'worldinfo_updated',
|
WORLDINFO_UPDATED: 'worldinfo_updated',
|
||||||
CHARACTER_EDITED: 'character_edited',
|
CHARACTER_EDITED: 'character_edited',
|
||||||
@ -439,6 +442,7 @@ export const event_types = {
|
|||||||
GROUP_CHAT_CREATED: 'group_chat_created',
|
GROUP_CHAT_CREATED: 'group_chat_created',
|
||||||
GENERATE_BEFORE_COMBINE_PROMPTS: 'generate_before_combine_prompts',
|
GENERATE_BEFORE_COMBINE_PROMPTS: 'generate_before_combine_prompts',
|
||||||
GENERATE_AFTER_COMBINE_PROMPTS: 'generate_after_combine_prompts',
|
GENERATE_AFTER_COMBINE_PROMPTS: 'generate_after_combine_prompts',
|
||||||
|
GENERATE_AFTER_DATA: 'generate_after_data',
|
||||||
GROUP_MEMBER_DRAFTED: 'group_member_drafted',
|
GROUP_MEMBER_DRAFTED: 'group_member_drafted',
|
||||||
WORLD_INFO_ACTIVATED: 'world_info_activated',
|
WORLD_INFO_ACTIVATED: 'world_info_activated',
|
||||||
TEXT_COMPLETION_SETTINGS_READY: 'text_completion_settings_ready',
|
TEXT_COMPLETION_SETTINGS_READY: 'text_completion_settings_ready',
|
||||||
@ -455,6 +459,7 @@ export const event_types = {
|
|||||||
LLM_FUNCTION_TOOL_REGISTER: 'llm_function_tool_register',
|
LLM_FUNCTION_TOOL_REGISTER: 'llm_function_tool_register',
|
||||||
LLM_FUNCTION_TOOL_CALL: 'llm_function_tool_call',
|
LLM_FUNCTION_TOOL_CALL: 'llm_function_tool_call',
|
||||||
ONLINE_STATUS_CHANGED: 'online_status_changed',
|
ONLINE_STATUS_CHANGED: 'online_status_changed',
|
||||||
|
IMAGE_SWIPED: 'image_swiped',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const eventSource = new EventEmitter();
|
export const eventSource = new EventEmitter();
|
||||||
@ -910,6 +915,7 @@ async function firstLoadInit() {
|
|||||||
await readSecretState();
|
await readSecretState();
|
||||||
initLocales();
|
initLocales();
|
||||||
initDefaultSlashCommands();
|
initDefaultSlashCommands();
|
||||||
|
initTextGenModels();
|
||||||
await getSystemMessages();
|
await getSystemMessages();
|
||||||
sendSystemMessage(system_message_types.WELCOME);
|
sendSystemMessage(system_message_types.WELCOME);
|
||||||
await getSettings();
|
await getSettings();
|
||||||
@ -1720,16 +1726,24 @@ export async function replaceCurrentChat() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function showMoreMessages() {
|
export function showMoreMessages() {
|
||||||
let messageId = Number($('#chat').children('.mes').first().attr('mesid'));
|
const firstDisplayedMesId = $('#chat').children('.mes').first().attr('mesid');
|
||||||
|
let messageId = Number(firstDisplayedMesId);
|
||||||
let count = power_user.chat_truncation || Number.MAX_SAFE_INTEGER;
|
let count = power_user.chat_truncation || Number.MAX_SAFE_INTEGER;
|
||||||
|
|
||||||
|
// If there are no messages displayed, or the message somehow has no mesid, we default to one higher than last message id,
|
||||||
|
// so the first "new" message being shown will be the last available message
|
||||||
|
if (isNaN(messageId)) {
|
||||||
|
messageId = getLastMessageId() + 1;
|
||||||
|
}
|
||||||
|
|
||||||
console.debug('Inserting messages before', messageId, 'count', count, 'chat length', chat.length);
|
console.debug('Inserting messages before', messageId, 'count', count, 'chat length', chat.length);
|
||||||
const prevHeight = $('#chat').prop('scrollHeight');
|
const prevHeight = $('#chat').prop('scrollHeight');
|
||||||
|
|
||||||
while (messageId > 0 && count > 0) {
|
while (messageId > 0 && count > 0) {
|
||||||
|
let newMessageId = messageId - 1;
|
||||||
|
addOneMessage(chat[newMessageId], { insertBefore: messageId >= chat.length ? null : messageId, scroll: false, forceId: newMessageId });
|
||||||
count--;
|
count--;
|
||||||
messageId--;
|
messageId--;
|
||||||
addOneMessage(chat[messageId], { insertBefore: messageId + 1, scroll: false, forceId: messageId });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (messageId == 0) {
|
if (messageId == 0) {
|
||||||
@ -1865,7 +1879,12 @@ export function messageFormatting(mes, ch_name, isSystem, isUser, messageId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Number(messageId) === 0 && !isSystem && !isUser) {
|
if (Number(messageId) === 0 && !isSystem && !isUser) {
|
||||||
|
const mesBeforeReplace = mes;
|
||||||
|
const chatMessage = chat[messageId];
|
||||||
mes = substituteParams(mes, undefined, ch_name);
|
mes = substituteParams(mes, undefined, ch_name);
|
||||||
|
if (chatMessage && chatMessage.mes === mesBeforeReplace && chatMessage.extra?.display_text !== mesBeforeReplace) {
|
||||||
|
chatMessage.mes = mes;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mesForShowdownParse = mes;
|
mesForShowdownParse = mes;
|
||||||
@ -2099,6 +2118,7 @@ export function updateMessageBlock(messageId, message) {
|
|||||||
export function appendMediaToMessage(mes, messageElement, adjustScroll = true) {
|
export function appendMediaToMessage(mes, messageElement, adjustScroll = true) {
|
||||||
// Add image to message
|
// Add image to message
|
||||||
if (mes.extra?.image) {
|
if (mes.extra?.image) {
|
||||||
|
const container = messageElement.find('.mes_img_container');
|
||||||
const chatHeight = $('#chat').prop('scrollHeight');
|
const chatHeight = $('#chat').prop('scrollHeight');
|
||||||
const image = messageElement.find('.mes_img');
|
const image = messageElement.find('.mes_img');
|
||||||
const text = messageElement.find('.mes_text');
|
const text = messageElement.find('.mes_text');
|
||||||
@ -2114,9 +2134,27 @@ export function appendMediaToMessage(mes, messageElement, adjustScroll = true) {
|
|||||||
});
|
});
|
||||||
image.attr('src', mes.extra?.image);
|
image.attr('src', mes.extra?.image);
|
||||||
image.attr('title', mes.extra?.title || mes.title || '');
|
image.attr('title', mes.extra?.title || mes.title || '');
|
||||||
messageElement.find('.mes_img_container').addClass('img_extra');
|
container.addClass('img_extra');
|
||||||
image.toggleClass('img_inline', isInline);
|
image.toggleClass('img_inline', isInline);
|
||||||
text.toggleClass('displayNone', !isInline);
|
text.toggleClass('displayNone', !isInline);
|
||||||
|
|
||||||
|
const imageSwipes = mes.extra.image_swipes;
|
||||||
|
if (Array.isArray(imageSwipes) && imageSwipes.length > 0) {
|
||||||
|
container.addClass('img_swipes');
|
||||||
|
const counter = container.find('.mes_img_swipe_counter');
|
||||||
|
const currentImage = imageSwipes.indexOf(mes.extra.image) + 1;
|
||||||
|
counter.text(`${currentImage}/${imageSwipes.length}`);
|
||||||
|
|
||||||
|
const swipeLeft = container.find('.mes_img_swipe_left');
|
||||||
|
swipeLeft.off('click').on('click', function () {
|
||||||
|
eventSource.emit(event_types.IMAGE_SWIPED, { message: mes, element: messageElement, direction: 'left' });
|
||||||
|
});
|
||||||
|
|
||||||
|
const swipeRight = container.find('.mes_img_swipe_right');
|
||||||
|
swipeRight.off('click').on('click', function () {
|
||||||
|
eventSource.emit(event_types.IMAGE_SWIPED, { message: mes, element: messageElement, direction: 'right' });
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add file to message
|
// Add file to message
|
||||||
@ -2470,26 +2508,30 @@ export function substituteParams(content, _name1, _name2, _original, _group, _re
|
|||||||
* @returns {string[]} Array of stopping strings
|
* @returns {string[]} Array of stopping strings
|
||||||
*/
|
*/
|
||||||
export function getStoppingStrings(isImpersonate, isContinue) {
|
export function getStoppingStrings(isImpersonate, isContinue) {
|
||||||
const charString = `\n${name2}:`;
|
const result = [];
|
||||||
const userString = `\n${name1}:`;
|
|
||||||
const result = isImpersonate ? [charString] : [userString];
|
|
||||||
|
|
||||||
result.push(userString);
|
if (power_user.context.names_as_stop_strings) {
|
||||||
|
const charString = `\n${name2}:`;
|
||||||
|
const userString = `\n${name1}:`;
|
||||||
|
result.push(isImpersonate ? charString : userString);
|
||||||
|
|
||||||
if (isContinue && Array.isArray(chat) && chat[chat.length - 1]?.is_user) {
|
result.push(userString);
|
||||||
result.push(charString);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add other group members as the stopping strings
|
if (isContinue && Array.isArray(chat) && chat[chat.length - 1]?.is_user) {
|
||||||
if (selected_group) {
|
result.push(charString);
|
||||||
const group = groups.find(x => x.id === selected_group);
|
}
|
||||||
|
|
||||||
if (group && Array.isArray(group.members)) {
|
// Add group members as stopping strings if generating for a specific group member or user. (Allow slash commands to work around name stopping string restrictions)
|
||||||
const names = group.members
|
if (selected_group && (name2 || isImpersonate)) {
|
||||||
.map(x => characters.find(y => y.avatar == x))
|
const group = groups.find(x => x.id === selected_group);
|
||||||
.filter(x => x && x.name && x.name !== name2)
|
|
||||||
.map(x => `\n${x.name}:`);
|
if (group && Array.isArray(group.members)) {
|
||||||
result.push(...names);
|
const names = group.members
|
||||||
|
.map(x => characters.find(y => y.avatar == x))
|
||||||
|
.filter(x => x && x.name && x.name !== name2)
|
||||||
|
.map(x => `\n${x.name}:`);
|
||||||
|
result.push(...names);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2802,7 +2844,14 @@ function hideStopButton() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class StreamingProcessor {
|
class StreamingProcessor {
|
||||||
constructor(type, force_name2, timeStarted, messageAlreadyGenerated) {
|
/**
|
||||||
|
* Creates a new streaming processor.
|
||||||
|
* @param {string} type Generation type
|
||||||
|
* @param {boolean} forceName2 If true, force the use of name2
|
||||||
|
* @param {Date} timeStarted Date when generation was started
|
||||||
|
* @param {string} continueMessage Previous message if the type is 'continue'
|
||||||
|
*/
|
||||||
|
constructor(type, forceName2, timeStarted, continueMessage) {
|
||||||
this.result = '';
|
this.result = '';
|
||||||
this.messageId = -1;
|
this.messageId = -1;
|
||||||
this.messageDom = null;
|
this.messageDom = null;
|
||||||
@ -2812,14 +2861,14 @@ class StreamingProcessor {
|
|||||||
/** @type {HTMLTextAreaElement} */
|
/** @type {HTMLTextAreaElement} */
|
||||||
this.sendTextarea = document.querySelector('#send_textarea');
|
this.sendTextarea = document.querySelector('#send_textarea');
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.force_name2 = force_name2;
|
this.force_name2 = forceName2;
|
||||||
this.isStopped = false;
|
this.isStopped = false;
|
||||||
this.isFinished = false;
|
this.isFinished = false;
|
||||||
this.generator = this.nullStreamingGeneration;
|
this.generator = this.nullStreamingGeneration;
|
||||||
this.abortController = new AbortController();
|
this.abortController = new AbortController();
|
||||||
this.firstMessageText = '...';
|
this.firstMessageText = '...';
|
||||||
this.timeStarted = timeStarted;
|
this.timeStarted = timeStarted;
|
||||||
this.messageAlreadyGenerated = messageAlreadyGenerated;
|
this.continueMessage = type === 'continue' ? continueMessage : '';
|
||||||
this.swipes = [];
|
this.swipes = [];
|
||||||
/** @type {import('./scripts/logprobs.js').TokenLogprobs[]} */
|
/** @type {import('./scripts/logprobs.js').TokenLogprobs[]} */
|
||||||
this.messageLogprobs = [];
|
this.messageLogprobs = [];
|
||||||
@ -2972,8 +3021,7 @@ class StreamingProcessor {
|
|||||||
await eventSource.emit(event_types.IMPERSONATE_READY, text);
|
await eventSource.emit(event_types.IMPERSONATE_READY, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
const continueMsg = this.type === 'continue' ? this.messageAlreadyGenerated : undefined;
|
saveLogprobsForActiveMessage(this.messageLogprobs.filter(Boolean), this.continueMessage);
|
||||||
saveLogprobsForActiveMessage(this.messageLogprobs.filter(Boolean), continueMsg);
|
|
||||||
await saveChatConditional();
|
await saveChatConditional();
|
||||||
unblockGeneration();
|
unblockGeneration();
|
||||||
generatedPromptCache = '';
|
generatedPromptCache = '';
|
||||||
@ -3069,7 +3117,7 @@ class StreamingProcessor {
|
|||||||
if (logprobs) {
|
if (logprobs) {
|
||||||
this.messageLogprobs.push(...(Array.isArray(logprobs) ? logprobs : [logprobs]));
|
this.messageLogprobs.push(...(Array.isArray(logprobs) ? logprobs : [logprobs]));
|
||||||
}
|
}
|
||||||
await sw.tick(() => this.onProgressStreaming(this.messageId, this.messageAlreadyGenerated + text));
|
await sw.tick(() => this.onProgressStreaming(this.messageId, this.continueMessage + text));
|
||||||
}
|
}
|
||||||
const seconds = (timestamps[timestamps.length - 1] - timestamps[0]) / 1000;
|
const seconds = (timestamps[timestamps.length - 1] - timestamps[0]) / 1000;
|
||||||
console.warn(`Stream stats: ${timestamps.length} tokens, ${seconds.toFixed(2)} seconds, rate: ${Number(timestamps.length / seconds).toFixed(2)} TPS`);
|
console.warn(`Stream stats: ${timestamps.length} tokens, ${seconds.toFixed(2)} seconds, rate: ${Number(timestamps.length / seconds).toFixed(2)} TPS`);
|
||||||
@ -3262,8 +3310,6 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
|
|||||||
const isInstruct = power_user.instruct.enabled && main_api !== 'openai';
|
const isInstruct = power_user.instruct.enabled && main_api !== 'openai';
|
||||||
const isImpersonate = type == 'impersonate';
|
const isImpersonate = type == 'impersonate';
|
||||||
|
|
||||||
let message_already_generated = isImpersonate ? `${name1}: ` : `${name2}: `;
|
|
||||||
|
|
||||||
if (!(dryRun || type == 'regenerate' || type == 'swipe' || type == 'quiet')) {
|
if (!(dryRun || type == 'regenerate' || type == 'swipe' || type == 'quiet')) {
|
||||||
const interruptedByCommand = await processCommands(String($('#send_textarea').val()));
|
const interruptedByCommand = await processCommands(String($('#send_textarea').val()));
|
||||||
|
|
||||||
@ -3702,7 +3748,6 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
|
|||||||
let oaiMessageExamples = [];
|
let oaiMessageExamples = [];
|
||||||
|
|
||||||
if (main_api === 'openai') {
|
if (main_api === 'openai') {
|
||||||
message_already_generated = '';
|
|
||||||
oaiMessages = setOpenAIMessages(coreChat);
|
oaiMessages = setOpenAIMessages(coreChat);
|
||||||
oaiMessageExamples = setOpenAIMessageExamples(mesExamplesArray);
|
oaiMessageExamples = setOpenAIMessageExamples(mesExamplesArray);
|
||||||
}
|
}
|
||||||
@ -3845,7 +3890,6 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
|
|||||||
cyclePrompt += oai_settings.continue_postfix;
|
cyclePrompt += oai_settings.continue_postfix;
|
||||||
continue_mag += oai_settings.continue_postfix;
|
continue_mag += oai_settings.continue_postfix;
|
||||||
}
|
}
|
||||||
message_already_generated = continue_mag;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const originalType = type;
|
const originalType = type;
|
||||||
@ -3930,7 +3974,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
|
|||||||
|
|
||||||
// Get instruct mode line
|
// Get instruct mode line
|
||||||
if (isInstruct && !isContinue) {
|
if (isInstruct && !isContinue) {
|
||||||
const name = (quiet_prompt && !quietToLoud) ? (quietName ?? 'System') : (isImpersonate ? name1 : name2);
|
const name = (quiet_prompt && !quietToLoud && !isImpersonate) ? (quietName ?? 'System') : (isImpersonate ? name1 : name2);
|
||||||
const isQuiet = quiet_prompt && type == 'quiet';
|
const isQuiet = quiet_prompt && type == 'quiet';
|
||||||
lastMesString += formatInstructModePrompt(name, isImpersonate, promptBias, name1, name2, isQuiet, quietToLoud);
|
lastMesString += formatInstructModePrompt(name, isImpersonate, promptBias, name1, name2, isQuiet, quietToLoud);
|
||||||
}
|
}
|
||||||
@ -4211,6 +4255,8 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await eventSource.emit(event_types.GENERATE_AFTER_DATA, generate_data);
|
||||||
|
|
||||||
if (dryRun) {
|
if (dryRun) {
|
||||||
generatedPromptCache = '';
|
generatedPromptCache = '';
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
@ -4270,7 +4316,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
|
|||||||
console.debug(`pushed prompt bits to itemizedPrompts array. Length is now: ${itemizedPrompts.length}`);
|
console.debug(`pushed prompt bits to itemizedPrompts array. Length is now: ${itemizedPrompts.length}`);
|
||||||
|
|
||||||
if (isStreamingEnabled() && type !== 'quiet') {
|
if (isStreamingEnabled() && type !== 'quiet') {
|
||||||
streamingProcessor = new StreamingProcessor(type, force_name2, generation_started, message_already_generated);
|
streamingProcessor = new StreamingProcessor(type, force_name2, generation_started, continue_mag);
|
||||||
if (isContinue) {
|
if (isContinue) {
|
||||||
// Save reply does add cycle text to the prompt, so it's not needed here
|
// Save reply does add cycle text to the prompt, so it's not needed here
|
||||||
streamingProcessor.firstMessageText = '';
|
streamingProcessor.firstMessageText = '';
|
||||||
@ -5074,7 +5120,7 @@ function setInContextMessages(lastmsg, type) {
|
|||||||
* @param {object} data Generation data
|
* @param {object} data Generation data
|
||||||
* @returns {Promise<object>} Response data from the API
|
* @returns {Promise<object>} Response data from the API
|
||||||
*/
|
*/
|
||||||
async function sendGenerationRequest(type, data) {
|
export async function sendGenerationRequest(type, data) {
|
||||||
if (main_api === 'openai') {
|
if (main_api === 'openai') {
|
||||||
return await sendOpenAIRequest(type, data.prompt, abortController.signal);
|
return await sendOpenAIRequest(type, data.prompt, abortController.signal);
|
||||||
}
|
}
|
||||||
@ -5106,7 +5152,7 @@ async function sendGenerationRequest(type, data) {
|
|||||||
* @param {object} data Generation data
|
* @param {object} data Generation data
|
||||||
* @returns {Promise<any>} Streaming generator
|
* @returns {Promise<any>} Streaming generator
|
||||||
*/
|
*/
|
||||||
async function sendStreamingRequest(type, data) {
|
export async function sendStreamingRequest(type, data) {
|
||||||
if (abortController?.signal?.aborted) {
|
if (abortController?.signal?.aborted) {
|
||||||
throw new Error('Generation was aborted.');
|
throw new Error('Generation was aborted.');
|
||||||
}
|
}
|
||||||
@ -5387,9 +5433,11 @@ export function cleanUpMessage(getMessage, isImpersonate, isContinue, displayInc
|
|||||||
getMessage = fixMarkdown(getMessage, false);
|
getMessage = fixMarkdown(getMessage, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
const nameToTrim2 = isImpersonate ? name1 : name2;
|
const nameToTrim2 = isImpersonate
|
||||||
|
? (!power_user.allow_name1_display ? name1 : '')
|
||||||
|
: (!power_user.allow_name2_display ? name2 : '');
|
||||||
|
|
||||||
if (getMessage.startsWith(nameToTrim2 + ':')) {
|
if (nameToTrim2 && getMessage.startsWith(nameToTrim2 + ':')) {
|
||||||
getMessage = getMessage.replace(nameToTrim2 + ':', '');
|
getMessage = getMessage.replace(nameToTrim2 + ':', '');
|
||||||
getMessage = getMessage.trimStart();
|
getMessage = getMessage.trimStart();
|
||||||
}
|
}
|
||||||
@ -5610,6 +5658,7 @@ export function activateSendButtons() {
|
|||||||
is_send_press = false;
|
is_send_press = false;
|
||||||
$('#send_but').removeClass('displayNone');
|
$('#send_but').removeClass('displayNone');
|
||||||
$('#mes_continue').removeClass('displayNone');
|
$('#mes_continue').removeClass('displayNone');
|
||||||
|
$('#mes_impersonate').removeClass('displayNone');
|
||||||
$('.mes_buttons:last').show();
|
$('.mes_buttons:last').show();
|
||||||
hideStopButton();
|
hideStopButton();
|
||||||
}
|
}
|
||||||
@ -5617,6 +5666,7 @@ export function activateSendButtons() {
|
|||||||
export function deactivateSendButtons() {
|
export function deactivateSendButtons() {
|
||||||
$('#send_but').addClass('displayNone');
|
$('#send_but').addClass('displayNone');
|
||||||
$('#mes_continue').addClass('displayNone');
|
$('#mes_continue').addClass('displayNone');
|
||||||
|
$('#mes_impersonate').addClass('displayNone');
|
||||||
showStopButton();
|
showStopButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -6407,7 +6457,7 @@ export async function getSettings() {
|
|||||||
loadHordeSettings(settings);
|
loadHordeSettings(settings);
|
||||||
|
|
||||||
// Load power user settings
|
// Load power user settings
|
||||||
loadPowerUserSettings(settings, data);
|
await loadPowerUserSettings(settings, data);
|
||||||
|
|
||||||
// Load character tags
|
// Load character tags
|
||||||
loadTagsSettings(settings);
|
loadTagsSettings(settings);
|
||||||
@ -7917,6 +7967,8 @@ window['SillyTavern'].getContext = function () {
|
|||||||
eventTypes: event_types,
|
eventTypes: event_types,
|
||||||
addOneMessage: addOneMessage,
|
addOneMessage: addOneMessage,
|
||||||
generate: Generate,
|
generate: Generate,
|
||||||
|
sendStreamingRequest: sendStreamingRequest,
|
||||||
|
sendGenerationRequest: sendGenerationRequest,
|
||||||
stopGeneration: stopGeneration,
|
stopGeneration: stopGeneration,
|
||||||
getTokenCount: getTokenCount,
|
getTokenCount: getTokenCount,
|
||||||
extensionPrompts: extension_prompts,
|
extensionPrompts: extension_prompts,
|
||||||
@ -8334,6 +8386,12 @@ const CONNECT_API_MAP = {
|
|||||||
button: '#api_button_openai',
|
button: '#api_button_openai',
|
||||||
source: chat_completion_sources.OPENAI,
|
source: chat_completion_sources.OPENAI,
|
||||||
},
|
},
|
||||||
|
// Google alias
|
||||||
|
'google': {
|
||||||
|
selected: 'openai',
|
||||||
|
button: '#api_button_openai',
|
||||||
|
source: chat_completion_sources.MAKERSUITE,
|
||||||
|
},
|
||||||
// OpenRouter special naming, to differentiate between chat comp and text comp
|
// OpenRouter special naming, to differentiate between chat comp and text comp
|
||||||
'openrouter': {
|
'openrouter': {
|
||||||
selected: 'openai',
|
selected: 'openai',
|
||||||
@ -8347,6 +8405,9 @@ const CONNECT_API_MAP = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Collect all unique API names in an array
|
||||||
|
export const UNIQUE_APIS = [...new Set(Object.values(CONNECT_API_MAP).map(x => x.selected))];
|
||||||
|
|
||||||
// Fill connections map from textgen_types and chat_completion_sources
|
// Fill connections map from textgen_types and chat_completion_sources
|
||||||
for (const textGenType of Object.values(textgen_types)) {
|
for (const textGenType of Object.values(textgen_types)) {
|
||||||
if (CONNECT_API_MAP[textGenType]) continue;
|
if (CONNECT_API_MAP[textGenType]) continue;
|
||||||
@ -8416,7 +8477,7 @@ async function disableInstructCallback() {
|
|||||||
/**
|
/**
|
||||||
* @param {string} text API name
|
* @param {string} text API name
|
||||||
*/
|
*/
|
||||||
async function connectAPISlash(_, text) {
|
async function connectAPISlash(args, text) {
|
||||||
if (!text.trim()) {
|
if (!text.trim()) {
|
||||||
for (const [key, config] of Object.entries(CONNECT_API_MAP)) {
|
for (const [key, config] of Object.entries(CONNECT_API_MAP)) {
|
||||||
if (config.selected !== main_api) continue;
|
if (config.selected !== main_api) continue;
|
||||||
@ -8439,12 +8500,15 @@ async function connectAPISlash(_, text) {
|
|||||||
|
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.error('FIXME: The current API is not in the API map');
|
||||||
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
const apiConfig = CONNECT_API_MAP[text.toLowerCase()];
|
const apiConfig = CONNECT_API_MAP[text.toLowerCase()];
|
||||||
if (!apiConfig) {
|
if (!apiConfig) {
|
||||||
toastr.error(`Error: ${text} is not a valid API`);
|
toastr.error(`Error: ${text} is not a valid API`);
|
||||||
return;
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
$(`#main_api option[value='${apiConfig.selected || text}']`).prop('selected', true);
|
$(`#main_api option[value='${apiConfig.selected || text}']`).prop('selected', true);
|
||||||
@ -8464,14 +8528,18 @@ async function connectAPISlash(_, text) {
|
|||||||
$(apiConfig.button).trigger('click');
|
$(apiConfig.button).trigger('click');
|
||||||
}
|
}
|
||||||
|
|
||||||
toastr.info(`API set to ${text}, trying to connect..`);
|
const quiet = isTrueBoolean(args?.quiet);
|
||||||
|
const toast = quiet ? jQuery() : toastr.info(`API set to ${text}, trying to connect..`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await waitUntilCondition(() => online_status !== 'no_connection', 10000, 100);
|
await waitUntilCondition(() => online_status !== 'no_connection', 10000, 100);
|
||||||
console.log('Connection successful');
|
console.log('Connection successful');
|
||||||
} catch {
|
} catch {
|
||||||
console.log('Could not connect after 5 seconds, skipping.');
|
console.log('Could not connect after 10 seconds, skipping.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toastr.clear(toast);
|
||||||
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -8496,7 +8564,7 @@ export async function processDroppedFiles(files, data = new Map()) {
|
|||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
const extension = file.name.split('.').pop().toLowerCase();
|
const extension = file.name.split('.').pop().toLowerCase();
|
||||||
if (allowedMimeTypes.includes(file.type) || allowedExtensions.includes(extension)) {
|
if (allowedMimeTypes.some(x => file.type.startsWith(x)) || allowedExtensions.includes(extension)) {
|
||||||
const preservedName = data instanceof Map && data.get(file);
|
const preservedName = data instanceof Map && data.get(file);
|
||||||
await importCharacter(file, preservedName);
|
await importCharacter(file, preservedName);
|
||||||
} else {
|
} else {
|
||||||
@ -8921,9 +8989,6 @@ jQuery(async function () {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect all unique API names in an array
|
|
||||||
const uniqueAPIs = [...new Set(Object.values(CONNECT_API_MAP).map(x => x.selected))];
|
|
||||||
|
|
||||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||||
name: 'dupe',
|
name: 'dupe',
|
||||||
callback: duplicateCharacter,
|
callback: duplicateCharacter,
|
||||||
@ -8932,13 +8997,22 @@ jQuery(async function () {
|
|||||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||||
name: 'api',
|
name: 'api',
|
||||||
callback: connectAPISlash,
|
callback: connectAPISlash,
|
||||||
|
returns: 'the current API',
|
||||||
|
namedArgumentList: [
|
||||||
|
SlashCommandNamedArgument.fromProps({
|
||||||
|
name: 'quiet',
|
||||||
|
description: 'Suppress the toast message on connection',
|
||||||
|
typeList: [ARGUMENT_TYPE.BOOLEAN],
|
||||||
|
defaultValue: 'false',
|
||||||
|
enumList: commonEnumProviders.boolean('trueFalse')(),
|
||||||
|
}),
|
||||||
|
],
|
||||||
unnamedArgumentList: [
|
unnamedArgumentList: [
|
||||||
SlashCommandArgument.fromProps({
|
SlashCommandArgument.fromProps({
|
||||||
description: 'API to connect to',
|
description: 'API to connect to',
|
||||||
typeList: [ARGUMENT_TYPE.STRING],
|
typeList: [ARGUMENT_TYPE.STRING],
|
||||||
isRequired: false,
|
|
||||||
enumList: Object.entries(CONNECT_API_MAP).map(([api, { selected }]) =>
|
enumList: Object.entries(CONNECT_API_MAP).map(([api, { selected }]) =>
|
||||||
new SlashCommandEnumValue(api, selected, enumTypes.getBasedOnIndex(uniqueAPIs.findIndex(x => x === selected)),
|
new SlashCommandEnumValue(api, selected, enumTypes.getBasedOnIndex(UNIQUE_APIS.findIndex(x => x === selected)),
|
||||||
selected[0].toUpperCase() ?? enumIcons.default)),
|
selected[0].toUpperCase() ?? enumIcons.default)),
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
@ -9095,14 +9169,14 @@ jQuery(async function () {
|
|||||||
$('#send_textarea').on('focusin focus click', () => {
|
$('#send_textarea').on('focusin focus click', () => {
|
||||||
S_TAPreviouslyFocused = true;
|
S_TAPreviouslyFocused = true;
|
||||||
});
|
});
|
||||||
$('#send_but, #option_regenerate, #option_continue, #mes_continue').on('click', () => {
|
$('#send_but, #option_regenerate, #option_continue, #mes_continue, #mes_impersonate').on('click', () => {
|
||||||
if (S_TAPreviouslyFocused) {
|
if (S_TAPreviouslyFocused) {
|
||||||
$('#send_textarea').focus();
|
$('#send_textarea').focus();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$(document).click(event => {
|
$(document).click(event => {
|
||||||
if ($(':focus').attr('id') !== 'send_textarea') {
|
if ($(':focus').attr('id') !== 'send_textarea') {
|
||||||
var validIDs = ['options_button', 'send_but', 'mes_continue', 'send_textarea', 'option_regenerate', 'option_continue'];
|
var validIDs = ['options_button', 'send_but', 'mes_impersonate', 'mes_continue', 'send_textarea', 'option_regenerate', 'option_continue'];
|
||||||
if (!validIDs.includes($(event.target).attr('id'))) {
|
if (!validIDs.includes($(event.target).attr('id'))) {
|
||||||
S_TAPreviouslyFocused = false;
|
S_TAPreviouslyFocused = false;
|
||||||
}
|
}
|
||||||
@ -9138,6 +9212,9 @@ jQuery(async function () {
|
|||||||
debouncedCharacterSearch(searchQuery);
|
debouncedCharacterSearch(searchQuery);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('#mes_impersonate').on('click', function () {
|
||||||
|
$('#option_impersonate').trigger('click');
|
||||||
|
});
|
||||||
|
|
||||||
$('#mes_continue').on('click', function () {
|
$('#mes_continue').on('click', function () {
|
||||||
$('#option_continue').trigger('click');
|
$('#option_continue').trigger('click');
|
||||||
@ -10435,8 +10512,9 @@ jQuery(async function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set the height of "autoSetHeight" textareas within the drawer to their scroll height
|
// Set the height of "autoSetHeight" textareas within the drawer to their scroll height
|
||||||
$(this).closest('.drawer').find('.drawer-content textarea.autoSetHeight').each(function () {
|
$(this).closest('.drawer').find('.drawer-content textarea.autoSetHeight').each(async function () {
|
||||||
resetScrollHeight($(this));
|
await resetScrollHeight($(this));
|
||||||
|
return;
|
||||||
});
|
});
|
||||||
|
|
||||||
} else if (drawerWasOpenAlready) { //to close manually
|
} else if (drawerWasOpenAlready) { //to close manually
|
||||||
@ -10509,8 +10587,9 @@ jQuery(async function () {
|
|||||||
$(this).closest('.inline-drawer').find('.inline-drawer-content').stop().slideToggle();
|
$(this).closest('.inline-drawer').find('.inline-drawer-content').stop().slideToggle();
|
||||||
|
|
||||||
// Set the height of "autoSetHeight" textareas within the inline-drawer to their scroll height
|
// Set the height of "autoSetHeight" textareas within the inline-drawer to their scroll height
|
||||||
$(this).closest('.inline-drawer').find('.inline-drawer-content textarea.autoSetHeight').each(function () {
|
$(this).closest('.inline-drawer').find('.inline-drawer-content textarea.autoSetHeight').each(async function () {
|
||||||
resetScrollHeight($(this));
|
await resetScrollHeight($(this));
|
||||||
|
return;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -10591,15 +10670,31 @@ jQuery(async function () {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$(document).on('click', '#OpenAllWIEntries', function () {
|
document.addEventListener('click', function (e) {
|
||||||
$('#world_popup_entries_list').children().find('.down').click();
|
if (!(e.target instanceof HTMLElement)) return;
|
||||||
});
|
if (e.target.matches('#OpenAllWIEntries')) {
|
||||||
$(document).on('click', '#CloseAllWIEntries', function () {
|
document.querySelectorAll('#world_popup_entries_list .inline-drawer').forEach((/** @type {HTMLElement} */ drawer) => {
|
||||||
$('#world_popup_entries_list').children().find('.up').click();
|
toggleDrawer(drawer, true);
|
||||||
|
});
|
||||||
|
} else if (e.target.matches('#CloseAllWIEntries')) {
|
||||||
|
document.querySelectorAll('#world_popup_entries_list .inline-drawer').forEach((/** @type {HTMLElement} */ drawer) => {
|
||||||
|
toggleDrawer(drawer, false);
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$(document).on('click', '.open_alternate_greetings', openAlternateGreetings);
|
$(document).on('click', '.open_alternate_greetings', openAlternateGreetings);
|
||||||
/* $('#set_character_world').on('click', openCharacterWorldPopup); */
|
/* $('#set_character_world').on('click', openCharacterWorldPopup); */
|
||||||
|
|
||||||
|
$(document).on('focus', 'input.auto-select, textarea.auto-select', function () {
|
||||||
|
if (!power_user.enable_auto_select_input) return;
|
||||||
|
const control = $(this)[0];
|
||||||
|
if (control instanceof HTMLInputElement || control instanceof HTMLTextAreaElement) {
|
||||||
|
control.select();
|
||||||
|
console.debug('Auto-selecting content of input control', control);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
$(document).keyup(function (e) {
|
$(document).keyup(function (e) {
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
const isEditVisible = $('#curEditTextarea').is(':visible');
|
const isEditVisible = $('#curEditTextarea').is(':visible');
|
||||||
@ -10683,7 +10778,7 @@ jQuery(async function () {
|
|||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
case 'import_tags': {
|
case 'import_tags': {
|
||||||
await importTags(characters[this_chid], { forceShow: true });
|
await importTags(characters[this_chid], { importSetting: tag_import_setting.ASK });
|
||||||
} break;
|
} break;
|
||||||
/*case 'delete_button':
|
/*case 'delete_button':
|
||||||
popup_type = "del_ch";
|
popup_type = "del_ch";
|
||||||
@ -10712,62 +10807,66 @@ jQuery(async function () {
|
|||||||
var isManualInput = false;
|
var isManualInput = false;
|
||||||
var valueBeforeManualInput;
|
var valueBeforeManualInput;
|
||||||
|
|
||||||
$('.range-block-counter input, .neo-range-input').on('click', function () {
|
$(document).on('input', '.range-block-counter input, .neo-range-input', function () {
|
||||||
valueBeforeManualInput = $(this).val();
|
valueBeforeManualInput = $(this).val();
|
||||||
console.log(valueBeforeManualInput);
|
console.log(valueBeforeManualInput);
|
||||||
})
|
});
|
||||||
.on('change', function (e) {
|
|
||||||
e.target.focus();
|
$(document).on('change', '.range-block-counter input, .neo-range-input', function (e) {
|
||||||
e.target.dispatchEvent(new Event('keyup'));
|
e.target.focus();
|
||||||
})
|
e.target.dispatchEvent(new KeyboardEvent('keyup', { bubbles: true }));
|
||||||
.on('keydown', function (e) {
|
});
|
||||||
const masterSelector = '#' + $(this).data('for');
|
|
||||||
const masterElement = $(masterSelector);
|
$(document).on('keydown', '.range-block-counter input, .neo-range-input', function (e) {
|
||||||
if (e.key === 'Enter') {
|
const masterSelector = '#' + $(this).data('for');
|
||||||
let manualInput = Number($(this).val());
|
const masterElement = $(masterSelector);
|
||||||
if (isManualInput) {
|
if (e.key === 'Enter') {
|
||||||
//disallow manual inputs outside acceptable range
|
|
||||||
if (manualInput >= Number($(this).attr('min')) && manualInput <= Number($(this).attr('max'))) {
|
|
||||||
//if value is ok, assign to slider and update handle text and position
|
|
||||||
//newSlider.val(manualInput)
|
|
||||||
//handleSlideEvent.call(newSlider, null, { value: parseFloat(manualInput) }, 'manual');
|
|
||||||
valueBeforeManualInput = manualInput;
|
|
||||||
$(masterElement).val($(this).val()).trigger('input', { forced: true });
|
|
||||||
} else {
|
|
||||||
//if value not ok, warn and reset to last known valid value
|
|
||||||
toastr.warning(`Invalid value. Must be between ${$(this).attr('min')} and ${$(this).attr('max')}`);
|
|
||||||
console.log(valueBeforeManualInput);
|
|
||||||
//newSlider.val(valueBeforeManualInput)
|
|
||||||
$(this).val(valueBeforeManualInput);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.on('keyup', function () {
|
|
||||||
valueBeforeManualInput = $(this).val();
|
|
||||||
console.log(valueBeforeManualInput);
|
|
||||||
isManualInput = true;
|
|
||||||
})
|
|
||||||
//trigger slider changes when user clicks away
|
|
||||||
.on('mouseup blur', function () {
|
|
||||||
const masterSelector = '#' + $(this).data('for');
|
|
||||||
const masterElement = $(masterSelector);
|
|
||||||
let manualInput = Number($(this).val());
|
let manualInput = Number($(this).val());
|
||||||
if (isManualInput) {
|
if (isManualInput) {
|
||||||
//if value is between correct range for the slider
|
//disallow manual inputs outside acceptable range
|
||||||
if (manualInput >= Number($(this).attr('min')) && manualInput <= Number($(this).attr('max'))) {
|
if (manualInput >= Number($(this).attr('min')) && manualInput <= Number($(this).attr('max'))) {
|
||||||
|
//if value is ok, assign to slider and update handle text and position
|
||||||
|
//newSlider.val(manualInput)
|
||||||
|
//handleSlideEvent.call(newSlider, null, { value: parseFloat(manualInput) }, 'manual');
|
||||||
valueBeforeManualInput = manualInput;
|
valueBeforeManualInput = manualInput;
|
||||||
//set the slider value to input value
|
|
||||||
$(masterElement).val($(this).val()).trigger('input', { forced: true });
|
$(masterElement).val($(this).val()).trigger('input', { forced: true });
|
||||||
} else {
|
} else {
|
||||||
//if value not ok, warn and reset to last known valid value
|
//if value not ok, warn and reset to last known valid value
|
||||||
toastr.warning(`Invalid value. Must be between ${$(this).attr('min')} and ${$(this).attr('max')}`);
|
toastr.warning(`Invalid value. Must be between ${$(this).attr('min')} and ${$(this).attr('max')}`);
|
||||||
console.log(valueBeforeManualInput);
|
console.log(valueBeforeManualInput);
|
||||||
|
//newSlider.val(valueBeforeManualInput)
|
||||||
$(this).val(valueBeforeManualInput);
|
$(this).val(valueBeforeManualInput);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
isManualInput = false;
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$(document).on('keyup', '.range-block-counter input, .neo-range-input', function () {
|
||||||
|
valueBeforeManualInput = $(this).val();
|
||||||
|
console.log(valueBeforeManualInput);
|
||||||
|
isManualInput = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
//trigger slider changes when user clicks away
|
||||||
|
$(document).on('mouseup blur', '.range-block-counter input, .neo-range-input', function () {
|
||||||
|
const masterSelector = '#' + $(this).data('for');
|
||||||
|
const masterElement = $(masterSelector);
|
||||||
|
let manualInput = Number($(this).val());
|
||||||
|
if (isManualInput) {
|
||||||
|
//if value is between correct range for the slider
|
||||||
|
if (manualInput >= Number($(this).attr('min')) && manualInput <= Number($(this).attr('max'))) {
|
||||||
|
valueBeforeManualInput = manualInput;
|
||||||
|
//set the slider value to input value
|
||||||
|
$(masterElement).val($(this).val()).trigger('input', { forced: true });
|
||||||
|
} else {
|
||||||
|
//if value not ok, warn and reset to last known valid value
|
||||||
|
toastr.warning(`Invalid value. Must be between ${$(this).attr('min')} and ${$(this).attr('max')}`);
|
||||||
|
console.log(valueBeforeManualInput);
|
||||||
|
$(this).val(valueBeforeManualInput);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isManualInput = false;
|
||||||
|
});
|
||||||
|
|
||||||
$('.user_stats_button').on('click', function () {
|
$('.user_stats_button').on('click', function () {
|
||||||
userStatsHandler();
|
userStatsHandler();
|
||||||
|
@ -18,7 +18,7 @@ import {
|
|||||||
import { favsToHotswap } from './RossAscends-mods.js';
|
import { favsToHotswap } from './RossAscends-mods.js';
|
||||||
import { hideLoader, showLoader } from './loader.js';
|
import { hideLoader, showLoader } from './loader.js';
|
||||||
import { convertCharacterToPersona } from './personas.js';
|
import { convertCharacterToPersona } from './personas.js';
|
||||||
import { createTagInput, getTagKeyForEntity, getTagsList, printTagList, tag_map, compareTagsForSort, removeTagFromMap } from './tags.js';
|
import { createTagInput, getTagKeyForEntity, getTagsList, printTagList, tag_map, compareTagsForSort, removeTagFromMap, importTags, tag_import_setting } from './tags.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Static object representing the actions of the
|
* Static object representing the actions of the
|
||||||
@ -197,10 +197,10 @@ class BulkTagPopupHandler {
|
|||||||
#getHtml = () => {
|
#getHtml = () => {
|
||||||
const characterData = JSON.stringify({ characterIds: this.characterIds });
|
const characterData = JSON.stringify({ characterIds: this.characterIds });
|
||||||
return `<div id="bulk_tag_shadow_popup">
|
return `<div id="bulk_tag_shadow_popup">
|
||||||
<div id="bulk_tag_popup">
|
<div id="bulk_tag_popup" class="wider_dialogue_popup">
|
||||||
<div id="bulk_tag_popup_holder">
|
<div id="bulk_tag_popup_holder">
|
||||||
<h3 class="marginBot5">Modify tags of ${this.characterIds.length} characters</h3>
|
<h3 class="marginBot5">Modify tags of ${this.characterIds.length} characters</h3>
|
||||||
<small class="bulk_tags_desc m-b-1">Add or remove the mutual tags of all selected characters.</small>
|
<small class="bulk_tags_desc m-b-1">Add or remove the mutual tags of all selected characters. Import all or existing tags for all selected characters.</small>
|
||||||
<div id="bulk_tags_avatars_block" class="avatars_inline avatars_inline_small tags tags_inline"></div>
|
<div id="bulk_tags_avatars_block" class="avatars_inline avatars_inline_small tags tags_inline"></div>
|
||||||
<br>
|
<br>
|
||||||
<div id="bulk_tags_div" class="marginBot5" data-characters='${characterData}'>
|
<div id="bulk_tags_div" class="marginBot5" data-characters='${characterData}'>
|
||||||
@ -219,6 +219,12 @@ class BulkTagPopupHandler {
|
|||||||
<i class="fa-solid fa-trash-can margin-right-10px"></i>
|
<i class="fa-solid fa-trash-can margin-right-10px"></i>
|
||||||
Mutual
|
Mutual
|
||||||
</div>
|
</div>
|
||||||
|
<div id="bulk_tag_popup_import_all_tags" class="menu_button" title="Import all tags from selected characters" data-i18n="[title]Import all tags from selected characters">
|
||||||
|
Import All
|
||||||
|
</div>
|
||||||
|
<div id="bulk_tag_popup_import_existing_tags" class="menu_button" title="Import existing tags from selected characters" data-i18n="[title]Import existing tags from selected characters">
|
||||||
|
Import Existing
|
||||||
|
</div>
|
||||||
<div id="bulk_tag_popup_cancel" class="menu_button" data-i18n="Cancel">Close</div>
|
<div id="bulk_tag_popup_cancel" class="menu_button" data-i18n="Cancel">Close</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -254,6 +260,30 @@ class BulkTagPopupHandler {
|
|||||||
document.querySelector('#bulk_tag_popup_reset').addEventListener('click', this.resetTags.bind(this));
|
document.querySelector('#bulk_tag_popup_reset').addEventListener('click', this.resetTags.bind(this));
|
||||||
document.querySelector('#bulk_tag_popup_remove_mutual').addEventListener('click', this.removeMutual.bind(this));
|
document.querySelector('#bulk_tag_popup_remove_mutual').addEventListener('click', this.removeMutual.bind(this));
|
||||||
document.querySelector('#bulk_tag_popup_cancel').addEventListener('click', this.hide.bind(this));
|
document.querySelector('#bulk_tag_popup_cancel').addEventListener('click', this.hide.bind(this));
|
||||||
|
document.querySelector('#bulk_tag_popup_import_all_tags').addEventListener('click', this.importAllTags.bind(this));
|
||||||
|
document.querySelector('#bulk_tag_popup_import_existing_tags').addEventListener('click', this.importExistingTags.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import existing tags for all selected characters
|
||||||
|
*/
|
||||||
|
async importExistingTags() {
|
||||||
|
for (const characterId of this.characterIds) {
|
||||||
|
await importTags(characters[characterId], { importSetting: tag_import_setting.ONLY_EXISTING });
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#bulkTagList').empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import all tags for all selected characters
|
||||||
|
*/
|
||||||
|
async importAllTags() {
|
||||||
|
for (const characterId of this.characterIds) {
|
||||||
|
await importTags(characters[characterId], { importSetting: tag_import_setting.ALL });
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#bulkTagList').empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -570,7 +600,7 @@ class BulkEditOverlay {
|
|||||||
this.container.removeEventListener('mouseup', cancelHold);
|
this.container.removeEventListener('mouseup', cancelHold);
|
||||||
this.container.removeEventListener('touchend', cancelHold);
|
this.container.removeEventListener('touchend', cancelHold);
|
||||||
},
|
},
|
||||||
BulkEditOverlay.longPressDelay);
|
BulkEditOverlay.longPressDelay);
|
||||||
};
|
};
|
||||||
|
|
||||||
handleLongPressEnd = (event) => {
|
handleLongPressEnd = (event) => {
|
||||||
|
@ -311,6 +311,7 @@ function RA_checkOnlineStatus() {
|
|||||||
$('#send_form').addClass('no-connection'); //entire input form area is red when not connected
|
$('#send_form').addClass('no-connection'); //entire input form area is red when not connected
|
||||||
$('#send_but').addClass('displayNone'); //send button is hidden when not connected;
|
$('#send_but').addClass('displayNone'); //send button is hidden when not connected;
|
||||||
$('#mes_continue').addClass('displayNone'); //continue 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;
|
||||||
$('#API-status-top').removeClass('fa-plug');
|
$('#API-status-top').removeClass('fa-plug');
|
||||||
$('#API-status-top').addClass('fa-plug-circle-exclamation redOverlayGlow');
|
$('#API-status-top').addClass('fa-plug-circle-exclamation redOverlayGlow');
|
||||||
connection_made = false;
|
connection_made = false;
|
||||||
@ -327,6 +328,7 @@ function RA_checkOnlineStatus() {
|
|||||||
if (!is_send_press && !(selected_group && is_group_generating)) {
|
if (!is_send_press && !(selected_group && is_group_generating)) {
|
||||||
$('#send_but').removeClass('displayNone'); //on connect, send button shows
|
$('#send_but').removeClass('displayNone'); //on connect, send button shows
|
||||||
$('#mes_continue').removeClass('displayNone'); //continue button is shown when connected
|
$('#mes_continue').removeClass('displayNone'); //continue button is shown when connected
|
||||||
|
$('#mes_impersonate').removeClass('displayNone'); //continue button is shown when connected
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -378,6 +380,7 @@ function RA_autoconnect(PrevApi) {
|
|||||||
|| (secret_state[SECRET_KEYS.PERPLEXITY] && oai_settings.chat_completion_source == chat_completion_sources.PERPLEXITY)
|
|| (secret_state[SECRET_KEYS.PERPLEXITY] && oai_settings.chat_completion_source == chat_completion_sources.PERPLEXITY)
|
||||||
|| (secret_state[SECRET_KEYS.GROQ] && oai_settings.chat_completion_source == chat_completion_sources.GROQ)
|
|| (secret_state[SECRET_KEYS.GROQ] && oai_settings.chat_completion_source == chat_completion_sources.GROQ)
|
||||||
|| (secret_state[SECRET_KEYS.ZEROONEAI] && oai_settings.chat_completion_source == chat_completion_sources.ZEROONEAI)
|
|| (secret_state[SECRET_KEYS.ZEROONEAI] && oai_settings.chat_completion_source == chat_completion_sources.ZEROONEAI)
|
||||||
|
|| (secret_state[SECRET_KEYS.BLOCKENTROPY] && oai_settings.chat_completion_source == chat_completion_sources.BLOCKENTROPY)
|
||||||
|| (isValidUrl(oai_settings.custom_url) && oai_settings.chat_completion_source == chat_completion_sources.CUSTOM)
|
|| (isValidUrl(oai_settings.custom_url) && oai_settings.chat_completion_source == chat_completion_sources.CUSTOM)
|
||||||
) {
|
) {
|
||||||
$('#api_button_openai').trigger('click');
|
$('#api_button_openai').trigger('click');
|
||||||
@ -951,6 +954,11 @@ export function initRossMods() {
|
|||||||
* @param {KeyboardEvent} event
|
* @param {KeyboardEvent} event
|
||||||
*/
|
*/
|
||||||
async function processHotkeys(event) {
|
async function processHotkeys(event) {
|
||||||
|
// Default hotkeys and shortcuts shouldn't work if any popup is currently open
|
||||||
|
if (Popup.util.isPopupOpen()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
//Enter to send when send_textarea in focus
|
//Enter to send when send_textarea in focus
|
||||||
if (document.activeElement == hotkeyTargets['send_textarea']) {
|
if (document.activeElement == hotkeyTargets['send_textarea']) {
|
||||||
const sendOnEnter = shouldSendOnEnter();
|
const sendOnEnter = shouldSendOnEnter();
|
||||||
@ -1104,10 +1112,6 @@ export function initRossMods() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (event.key == 'Escape') { //closes various panels
|
if (event.key == 'Escape') { //closes various panels
|
||||||
// Do not close panels if we are currently inside a popup
|
|
||||||
if (Popup.util.isPopupOpen())
|
|
||||||
return;
|
|
||||||
|
|
||||||
//dont override Escape hotkey functions from script.js
|
//dont override Escape hotkey functions from script.js
|
||||||
//"close edit box" and "cancel stream generation".
|
//"close edit box" and "cancel stream generation".
|
||||||
if ($('#curEditTextarea').is(':visible') || $('#mes_stop').is(':visible')) {
|
if ($('#curEditTextarea').is(':visible') || $('#mes_stop').is(':visible')) {
|
||||||
|
@ -16,8 +16,15 @@ export const AUTOCOMPLETE_WIDTH = {
|
|||||||
'FULL': 2,
|
'FULL': 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**@readonly*/
|
||||||
|
/**@enum {Number}*/
|
||||||
|
export const AUTOCOMPLETE_SELECT_KEY = {
|
||||||
|
'TAB': 1, // 2^0
|
||||||
|
'ENTER': 2, // 2^1
|
||||||
|
};
|
||||||
|
|
||||||
export class AutoComplete {
|
export class AutoComplete {
|
||||||
/**@type {HTMLTextAreaElement}*/ textarea;
|
/**@type {HTMLTextAreaElement|HTMLInputElement}*/ textarea;
|
||||||
/**@type {boolean}*/ isFloating = false;
|
/**@type {boolean}*/ isFloating = false;
|
||||||
/**@type {()=>boolean}*/ checkIfActivate;
|
/**@type {()=>boolean}*/ checkIfActivate;
|
||||||
/**@type {(text:string, index:number) => Promise<AutoCompleteNameResult>}*/ getNameAt;
|
/**@type {(text:string, index:number) => Promise<AutoCompleteNameResult>}*/ getNameAt;
|
||||||
@ -56,6 +63,8 @@ export class AutoComplete {
|
|||||||
/**@type {function}*/ updateDetailsPositionDebounced;
|
/**@type {function}*/ updateDetailsPositionDebounced;
|
||||||
/**@type {function}*/ updateFloatingPositionDebounced;
|
/**@type {function}*/ updateFloatingPositionDebounced;
|
||||||
|
|
||||||
|
/**@type {(item:AutoCompleteOption)=>any}*/ onSelect;
|
||||||
|
|
||||||
get matchType() {
|
get matchType() {
|
||||||
return power_user.stscript.matching ?? 'fuzzy';
|
return power_user.stscript.matching ?? 'fuzzy';
|
||||||
}
|
}
|
||||||
@ -68,7 +77,7 @@ export class AutoComplete {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {HTMLTextAreaElement} textarea The textarea to receive autocomplete.
|
* @param {HTMLTextAreaElement|HTMLInputElement} textarea The textarea to receive autocomplete.
|
||||||
* @param {() => boolean} checkIfActivate Function should return true only if under the current conditions, autocomplete should display (e.g., for slash commands: autoComplete.text[0] == '/')
|
* @param {() => boolean} checkIfActivate Function should return true only if under the current conditions, autocomplete should display (e.g., for slash commands: autoComplete.text[0] == '/')
|
||||||
* @param {(text: string, index: number) => Promise<AutoCompleteNameResult>} getNameAt Function should return (unfiltered, matching against input is done in AutoComplete) information about name options at index in text.
|
* @param {(text: string, index: number) => Promise<AutoCompleteNameResult>} getNameAt Function should return (unfiltered, matching against input is done in AutoComplete) information about name options at index in text.
|
||||||
* @param {boolean} isFloating Whether autocomplete should float at the keyboard cursor.
|
* @param {boolean} isFloating Whether autocomplete should float at the keyboard cursor.
|
||||||
@ -102,10 +111,15 @@ export class AutoComplete {
|
|||||||
this.updateDetailsPositionDebounced = debounce(this.updateDetailsPosition.bind(this), 10);
|
this.updateDetailsPositionDebounced = debounce(this.updateDetailsPosition.bind(this), 10);
|
||||||
this.updateFloatingPositionDebounced = debounce(this.updateFloatingPosition.bind(this), 10);
|
this.updateFloatingPositionDebounced = debounce(this.updateFloatingPosition.bind(this), 10);
|
||||||
|
|
||||||
textarea.addEventListener('input', ()=>this.text != this.textarea.value && this.show(true, this.wasForced));
|
textarea.addEventListener('input', ()=>{
|
||||||
|
this.selectionStart = this.textarea.selectionStart;
|
||||||
|
if (this.text != this.textarea.value) this.show(true, this.wasForced);
|
||||||
|
});
|
||||||
textarea.addEventListener('keydown', (evt)=>this.handleKeyDown(evt));
|
textarea.addEventListener('keydown', (evt)=>this.handleKeyDown(evt));
|
||||||
textarea.addEventListener('click', ()=>this.isActive ? this.show() : null);
|
textarea.addEventListener('click', ()=>{
|
||||||
textarea.addEventListener('selectionchange', ()=>this.show());
|
this.selectionStart = this.textarea.selectionStart;
|
||||||
|
if (this.isActive) this.show();
|
||||||
|
});
|
||||||
textarea.addEventListener('blur', ()=>this.hide());
|
textarea.addEventListener('blur', ()=>this.hide());
|
||||||
if (isFloating) {
|
if (isFloating) {
|
||||||
textarea.addEventListener('scroll', ()=>this.updateFloatingPositionDebounced());
|
textarea.addEventListener('scroll', ()=>this.updateFloatingPositionDebounced());
|
||||||
@ -189,6 +203,11 @@ export class AutoComplete {
|
|||||||
* @returns The option.
|
* @returns The option.
|
||||||
*/
|
*/
|
||||||
fuzzyScore(option) {
|
fuzzyScore(option) {
|
||||||
|
// might have been matched by the options matchProvider function instead
|
||||||
|
if (!this.fuzzyRegex.test(option.name)) {
|
||||||
|
option.score = new AutoCompleteFuzzyScore(Number.MAX_SAFE_INTEGER, -1);
|
||||||
|
return option;
|
||||||
|
}
|
||||||
const parts = this.fuzzyRegex.exec(option.name).slice(1, -1);
|
const parts = this.fuzzyRegex.exec(option.name).slice(1, -1);
|
||||||
let start = null;
|
let start = null;
|
||||||
let consecutive = [];
|
let consecutive = [];
|
||||||
@ -339,7 +358,7 @@ export class AutoComplete {
|
|||||||
|
|
||||||
this.result = this.effectiveParserResult.optionList
|
this.result = this.effectiveParserResult.optionList
|
||||||
// filter the list of options by the partial name according to the matching type
|
// filter the list of options by the partial name according to the matching type
|
||||||
.filter(it => this.isReplaceable || it.name == '' ? matchers[this.matchType](it.name) : it.name.toLowerCase() == this.name)
|
.filter(it => this.isReplaceable || it.name == '' ? (it.matchProvider ? it.matchProvider(this.name) : matchers[this.matchType](it.name)) : it.name.toLowerCase() == this.name)
|
||||||
// remove aliases
|
// remove aliases
|
||||||
.filter((it,idx,list) => list.findIndex(opt=>opt.value == it.value) == idx);
|
.filter((it,idx,list) => list.findIndex(opt=>opt.value == it.value) == idx);
|
||||||
|
|
||||||
@ -357,10 +376,11 @@ export class AutoComplete {
|
|||||||
// build element
|
// build element
|
||||||
option.dom = this.makeItem(option);
|
option.dom = this.makeItem(option);
|
||||||
// update replacer and add quotes if necessary
|
// update replacer and add quotes if necessary
|
||||||
|
const optionName = option.valueProvider ? option.valueProvider(this.name) : option.name;
|
||||||
if (this.effectiveParserResult.canBeQuoted) {
|
if (this.effectiveParserResult.canBeQuoted) {
|
||||||
option.replacer = option.name.includes(' ') || this.startQuote || this.endQuote ? `"${option.name}"` : `${option.name}`;
|
option.replacer = optionName.includes(' ') || this.startQuote || this.endQuote ? `"${optionName.replace(/"/g, '\\"')}"` : `${optionName}`;
|
||||||
} else {
|
} else {
|
||||||
option.replacer = option.name;
|
option.replacer = optionName;
|
||||||
}
|
}
|
||||||
// calculate fuzzy score if matching is fuzzy
|
// calculate fuzzy score if matching is fuzzy
|
||||||
if (this.matchType == 'fuzzy') this.fuzzyScore(option);
|
if (this.matchType == 'fuzzy') this.fuzzyScore(option);
|
||||||
@ -399,7 +419,7 @@ export class AutoComplete {
|
|||||||
,
|
,
|
||||||
);
|
);
|
||||||
this.result.push(option);
|
this.result.push(option);
|
||||||
} else if (this.result.length == 1 && this.effectiveParserResult && this.result[0].name == this.effectiveParserResult.name) {
|
} else if (this.result.length == 1 && this.effectiveParserResult && this.effectiveParserResult != this.secondaryParserResult && this.result[0].name == this.effectiveParserResult.name) {
|
||||||
// only one result that is exactly the current value? just show hint, no autocomplete
|
// only one result that is exactly the current value? just show hint, no autocomplete
|
||||||
this.isReplaceable = false;
|
this.isReplaceable = false;
|
||||||
this.isShowingDetails = false;
|
this.isShowingDetails = false;
|
||||||
@ -439,11 +459,14 @@ export class AutoComplete {
|
|||||||
} else {
|
} else {
|
||||||
item.dom.classList.remove('selected');
|
item.dom.classList.remove('selected');
|
||||||
}
|
}
|
||||||
|
if (!item.isSelectable) {
|
||||||
|
item.dom.classList.add('not-selectable');
|
||||||
|
}
|
||||||
frag.append(item.dom);
|
frag.append(item.dom);
|
||||||
}
|
}
|
||||||
this.dom.append(frag);
|
this.dom.append(frag);
|
||||||
this.updatePosition();
|
this.updatePosition();
|
||||||
getTopmostModalLayer().append(this.domWrap);
|
this.getLayer().append(this.domWrap);
|
||||||
} else {
|
} else {
|
||||||
this.domWrap.remove();
|
this.domWrap.remove();
|
||||||
}
|
}
|
||||||
@ -458,10 +481,17 @@ export class AutoComplete {
|
|||||||
if (!this.isShowingDetails && this.isReplaceable) return this.detailsWrap.remove();
|
if (!this.isShowingDetails && this.isReplaceable) return this.detailsWrap.remove();
|
||||||
this.detailsDom.innerHTML = '';
|
this.detailsDom.innerHTML = '';
|
||||||
this.detailsDom.append(this.selectedItem?.renderDetails() ?? 'NO ITEM');
|
this.detailsDom.append(this.selectedItem?.renderDetails() ?? 'NO ITEM');
|
||||||
getTopmostModalLayer().append(this.detailsWrap);
|
this.getLayer().append(this.detailsWrap);
|
||||||
this.updateDetailsPositionDebounced();
|
this.updateDetailsPositionDebounced();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {HTMLElement} closest ancestor dialog or body
|
||||||
|
*/
|
||||||
|
getLayer() {
|
||||||
|
return this.textarea.closest('dialog, body');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -474,7 +504,7 @@ export class AutoComplete {
|
|||||||
const rect = {};
|
const rect = {};
|
||||||
rect[AUTOCOMPLETE_WIDTH.INPUT] = this.textarea.getBoundingClientRect();
|
rect[AUTOCOMPLETE_WIDTH.INPUT] = this.textarea.getBoundingClientRect();
|
||||||
rect[AUTOCOMPLETE_WIDTH.CHAT] = document.querySelector('#sheld').getBoundingClientRect();
|
rect[AUTOCOMPLETE_WIDTH.CHAT] = document.querySelector('#sheld').getBoundingClientRect();
|
||||||
rect[AUTOCOMPLETE_WIDTH.FULL] = getTopmostModalLayer().getBoundingClientRect();
|
rect[AUTOCOMPLETE_WIDTH.FULL] = this.getLayer().getBoundingClientRect();
|
||||||
this.domWrap.style.setProperty('--bottom', `${window.innerHeight - rect[AUTOCOMPLETE_WIDTH.INPUT].top}px`);
|
this.domWrap.style.setProperty('--bottom', `${window.innerHeight - rect[AUTOCOMPLETE_WIDTH.INPUT].top}px`);
|
||||||
this.dom.style.setProperty('--bottom', `${window.innerHeight - rect[AUTOCOMPLETE_WIDTH.INPUT].top}px`);
|
this.dom.style.setProperty('--bottom', `${window.innerHeight - rect[AUTOCOMPLETE_WIDTH.INPUT].top}px`);
|
||||||
this.domWrap.style.bottom = `${window.innerHeight - rect[AUTOCOMPLETE_WIDTH.INPUT].top}px`;
|
this.domWrap.style.bottom = `${window.innerHeight - rect[AUTOCOMPLETE_WIDTH.INPUT].top}px`;
|
||||||
@ -501,7 +531,7 @@ export class AutoComplete {
|
|||||||
const rect = {};
|
const rect = {};
|
||||||
rect[AUTOCOMPLETE_WIDTH.INPUT] = this.textarea.getBoundingClientRect();
|
rect[AUTOCOMPLETE_WIDTH.INPUT] = this.textarea.getBoundingClientRect();
|
||||||
rect[AUTOCOMPLETE_WIDTH.CHAT] = document.querySelector('#sheld').getBoundingClientRect();
|
rect[AUTOCOMPLETE_WIDTH.CHAT] = document.querySelector('#sheld').getBoundingClientRect();
|
||||||
rect[AUTOCOMPLETE_WIDTH.FULL] = getTopmostModalLayer().getBoundingClientRect();
|
rect[AUTOCOMPLETE_WIDTH.FULL] = this.getLayer().getBoundingClientRect();
|
||||||
if (this.isReplaceable) {
|
if (this.isReplaceable) {
|
||||||
this.detailsWrap.classList.remove('full');
|
this.detailsWrap.classList.remove('full');
|
||||||
const selRect = this.selectedItem.dom.children[0].getBoundingClientRect();
|
const selRect = this.selectedItem.dom.children[0].getBoundingClientRect();
|
||||||
@ -527,32 +557,34 @@ export class AutoComplete {
|
|||||||
updateFloatingPosition() {
|
updateFloatingPosition() {
|
||||||
const location = this.getCursorPosition();
|
const location = this.getCursorPosition();
|
||||||
const rect = this.textarea.getBoundingClientRect();
|
const rect = this.textarea.getBoundingClientRect();
|
||||||
|
const layerRect = this.getLayer().getBoundingClientRect();
|
||||||
// cursor is out of view -> hide
|
// cursor is out of view -> hide
|
||||||
if (location.bottom < rect.top || location.top > rect.bottom || location.left < rect.left || location.left > rect.right) {
|
if (location.bottom < rect.top || location.top > rect.bottom || location.left < rect.left || location.left > rect.right) {
|
||||||
return this.hide();
|
return this.hide();
|
||||||
}
|
}
|
||||||
const left = Math.max(rect.left, location.left);
|
const left = Math.max(rect.left, location.left) - layerRect.left;
|
||||||
this.domWrap.style.setProperty('--targetOffset', `${left}`);
|
this.domWrap.style.setProperty('--targetOffset', `${left}`);
|
||||||
if (location.top <= window.innerHeight / 2) {
|
if (location.top <= window.innerHeight / 2) {
|
||||||
// if cursor is in lower half of window, show list above line
|
// if cursor is in lower half of window, show list above line
|
||||||
this.domWrap.style.top = `${location.bottom}px`;
|
this.domWrap.style.top = `${location.bottom - layerRect.top}px`;
|
||||||
this.domWrap.style.bottom = 'auto';
|
this.domWrap.style.bottom = 'auto';
|
||||||
this.domWrap.style.maxHeight = `calc(${location.bottom}px - 1vh)`;
|
this.domWrap.style.maxHeight = `calc(${location.bottom - layerRect.top}px - ${this.textarea.closest('dialog') ? '0' : '1vh'})`;
|
||||||
} else {
|
} else {
|
||||||
// if cursor is in upper half of window, show list below line
|
// if cursor is in upper half of window, show list below line
|
||||||
this.domWrap.style.top = 'auto';
|
this.domWrap.style.top = 'auto';
|
||||||
this.domWrap.style.bottom = `calc(100vh - ${location.top}px)`;
|
this.domWrap.style.bottom = `calc(${layerRect.height}px - ${location.top - layerRect.top}px)`;
|
||||||
this.domWrap.style.maxHeight = `calc(${location.top}px - 1vh)`;
|
this.domWrap.style.maxHeight = `calc(${location.top - layerRect.top}px - ${this.textarea.closest('dialog') ? '0' : '1vh'})`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateFloatingDetailsPosition(location = null) {
|
updateFloatingDetailsPosition(location = null) {
|
||||||
if (!location) location = this.getCursorPosition();
|
if (!location) location = this.getCursorPosition();
|
||||||
const rect = this.textarea.getBoundingClientRect();
|
const rect = this.textarea.getBoundingClientRect();
|
||||||
|
const layerRect = this.getLayer().getBoundingClientRect();
|
||||||
if (location.bottom < rect.top || location.top > rect.bottom || location.left < rect.left || location.left > rect.right) {
|
if (location.bottom < rect.top || location.top > rect.bottom || location.left < rect.left || location.left > rect.right) {
|
||||||
return this.hide();
|
return this.hide();
|
||||||
}
|
}
|
||||||
const left = Math.max(rect.left, location.left);
|
const left = Math.max(rect.left, location.left) - layerRect.left;
|
||||||
this.detailsWrap.style.setProperty('--targetOffset', `${left}`);
|
this.detailsWrap.style.setProperty('--targetOffset', `${left}`);
|
||||||
if (this.isReplaceable) {
|
if (this.isReplaceable) {
|
||||||
this.detailsWrap.classList.remove('full');
|
this.detailsWrap.classList.remove('full');
|
||||||
@ -572,14 +604,14 @@ export class AutoComplete {
|
|||||||
}
|
}
|
||||||
if (location.top <= window.innerHeight / 2) {
|
if (location.top <= window.innerHeight / 2) {
|
||||||
// if cursor is in lower half of window, show list above line
|
// if cursor is in lower half of window, show list above line
|
||||||
this.detailsWrap.style.top = `${location.bottom}px`;
|
this.detailsWrap.style.top = `${location.bottom - layerRect.top}px`;
|
||||||
this.detailsWrap.style.bottom = 'auto';
|
this.detailsWrap.style.bottom = 'auto';
|
||||||
this.detailsWrap.style.maxHeight = `calc(${location.bottom}px - 1vh)`;
|
this.detailsWrap.style.maxHeight = `calc(${location.bottom - layerRect.top}px - ${this.textarea.closest('dialog') ? '0' : '1vh'})`;
|
||||||
} else {
|
} else {
|
||||||
// if cursor is in upper half of window, show list below line
|
// if cursor is in upper half of window, show list below line
|
||||||
this.detailsWrap.style.top = 'auto';
|
this.detailsWrap.style.top = 'auto';
|
||||||
this.detailsWrap.style.bottom = `calc(100vh - ${location.top}px)`;
|
this.detailsWrap.style.bottom = `calc(${layerRect.height}px - ${location.top - layerRect.top}px)`;
|
||||||
this.detailsWrap.style.maxHeight = `calc(${location.top}px - 1vh)`;
|
this.detailsWrap.style.maxHeight = `calc(${location.top - layerRect.top}px - ${this.textarea.closest('dialog') ? '0' : '1vh'})`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -597,7 +629,7 @@ export class AutoComplete {
|
|||||||
}
|
}
|
||||||
this.clone.style.position = 'fixed';
|
this.clone.style.position = 'fixed';
|
||||||
this.clone.style.visibility = 'hidden';
|
this.clone.style.visibility = 'hidden';
|
||||||
getTopmostModalLayer().append(this.clone);
|
document.body.append(this.clone);
|
||||||
const mo = new MutationObserver(muts=>{
|
const mo = new MutationObserver(muts=>{
|
||||||
if (muts.find(it=>Array.from(it.removedNodes).includes(this.textarea))) {
|
if (muts.find(it=>Array.from(it.removedNodes).includes(this.textarea))) {
|
||||||
this.clone.remove();
|
this.clone.remove();
|
||||||
@ -656,6 +688,7 @@ export class AutoComplete {
|
|||||||
}
|
}
|
||||||
this.wasForced = false;
|
this.wasForced = false;
|
||||||
this.textarea.dispatchEvent(new Event('input', { bubbles:true }));
|
this.textarea.dispatchEvent(new Event('input', { bubbles:true }));
|
||||||
|
this.onSelect?.(this.selectedItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -708,8 +741,10 @@ export class AutoComplete {
|
|||||||
}
|
}
|
||||||
case 'Enter': {
|
case 'Enter': {
|
||||||
// pick the selected item to autocomplete
|
// pick the selected item to autocomplete
|
||||||
|
if ((power_user.stscript.autocomplete.select & AUTOCOMPLETE_SELECT_KEY.ENTER) != AUTOCOMPLETE_SELECT_KEY.ENTER) break;
|
||||||
if (evt.ctrlKey || evt.altKey || evt.shiftKey || this.selectedItem.value == '') break;
|
if (evt.ctrlKey || evt.altKey || evt.shiftKey || this.selectedItem.value == '') break;
|
||||||
if (this.selectedItem.name == this.name) break;
|
if (this.selectedItem.name == this.name) break;
|
||||||
|
if (!this.selectedItem.isSelectable) break;
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
evt.stopImmediatePropagation();
|
evt.stopImmediatePropagation();
|
||||||
this.select();
|
this.select();
|
||||||
@ -717,9 +752,11 @@ export class AutoComplete {
|
|||||||
}
|
}
|
||||||
case 'Tab': {
|
case 'Tab': {
|
||||||
// pick the selected item to autocomplete
|
// pick the selected item to autocomplete
|
||||||
|
if ((power_user.stscript.autocomplete.select & AUTOCOMPLETE_SELECT_KEY.TAB) != AUTOCOMPLETE_SELECT_KEY.TAB) break;
|
||||||
if (evt.ctrlKey || evt.altKey || evt.shiftKey || this.selectedItem.value == '') break;
|
if (evt.ctrlKey || evt.altKey || evt.shiftKey || this.selectedItem.value == '') break;
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
evt.stopImmediatePropagation();
|
evt.stopImmediatePropagation();
|
||||||
|
if (!this.selectedItem.isSelectable) break;
|
||||||
this.select();
|
this.select();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -772,30 +809,16 @@ export class AutoComplete {
|
|||||||
// ignore keydown on modifier keys
|
// ignore keydown on modifier keys
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
switch (evt.key) {
|
// await keyup to see if cursor position or text has changed
|
||||||
case 'ArrowUp':
|
const oldText = this.textarea.value;
|
||||||
case 'ArrowDown':
|
await new Promise(resolve=>{
|
||||||
case 'ArrowRight':
|
window.addEventListener('keyup', resolve, { once:true });
|
||||||
case 'ArrowLeft': {
|
});
|
||||||
if (this.isActive) {
|
if (this.selectionStart != this.textarea.selectionStart) {
|
||||||
// keyboard navigation, wait for keyup to complete cursor move
|
this.selectionStart = this.textarea.selectionStart;
|
||||||
const oldText = this.textarea.value;
|
this.show(this.isReplaceable || oldText != this.textarea.value);
|
||||||
await new Promise(resolve=>{
|
} else if (this.isActive) {
|
||||||
window.addEventListener('keyup', resolve, { once:true });
|
this.text != this.textarea.value && this.show(this.isReplaceable);
|
||||||
});
|
|
||||||
if (this.selectionStart != this.textarea.selectionStart) {
|
|
||||||
this.selectionStart = this.textarea.selectionStart;
|
|
||||||
this.show(this.isReplaceable || oldText != this.textarea.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
if (this.isActive) {
|
|
||||||
this.text != this.textarea.value && this.show(this.isReplaceable);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,36 +1,9 @@
|
|||||||
import { SlashCommandNamedArgumentAutoCompleteOption } from '../slash-commands/SlashCommandNamedArgumentAutoCompleteOption.js';
|
import { AutoCompleteNameResultBase } from './AutoCompleteNameResultBase.js';
|
||||||
import { AutoCompleteOption } from './AutoCompleteOption.js';
|
import { AutoCompleteSecondaryNameResult } from './AutoCompleteSecondaryNameResult.js';
|
||||||
// import { AutoCompleteSecondaryNameResult } from './AutoCompleteSecondaryNameResult.js';
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export class AutoCompleteNameResult {
|
export class AutoCompleteNameResult extends AutoCompleteNameResultBase {
|
||||||
/**@type {string} */ name;
|
|
||||||
/**@type {number} */ start;
|
|
||||||
/**@type {AutoCompleteOption[]} */ optionList = [];
|
|
||||||
/**@type {boolean} */ canBeQuoted = false;
|
|
||||||
/**@type {()=>string} */ makeNoMatchText = ()=>`No matches found for "${this.name}"`;
|
|
||||||
/**@type {()=>string} */ makeNoOptionsText = ()=>'No options';
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} name Name (potentially partial) of the name at the requested index.
|
|
||||||
* @param {number} start Index where the name starts.
|
|
||||||
* @param {AutoCompleteOption[]} optionList A list of autocomplete options found in the current scope.
|
|
||||||
* @param {boolean} canBeQuoted Whether the name can be inside quotes.
|
|
||||||
* @param {()=>string} makeNoMatchText Function that returns text to show when no matches where found.
|
|
||||||
* @param {()=>string} makeNoOptionsText Function that returns text to show when no options are available to match against.
|
|
||||||
*/
|
|
||||||
constructor(name, start, optionList = [], canBeQuoted = false, makeNoMatchText = null, makeNoOptionsText = null) {
|
|
||||||
this.name = name;
|
|
||||||
this.start = start;
|
|
||||||
this.optionList = optionList;
|
|
||||||
this.canBeQuoted = canBeQuoted;
|
|
||||||
this.noMatchText = makeNoMatchText ?? this.makeNoMatchText;
|
|
||||||
this.noOptionstext = makeNoOptionsText ?? this.makeNoOptionsText;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {string} text The whole text
|
* @param {string} text The whole text
|
||||||
|
31
public/scripts/autocomplete/AutoCompleteNameResultBase.js
Normal file
31
public/scripts/autocomplete/AutoCompleteNameResultBase.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { SlashCommandNamedArgumentAutoCompleteOption } from '../slash-commands/SlashCommandNamedArgumentAutoCompleteOption.js';
|
||||||
|
import { AutoCompleteOption } from './AutoCompleteOption.js';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export class AutoCompleteNameResultBase {
|
||||||
|
/**@type {string} */ name;
|
||||||
|
/**@type {number} */ start;
|
||||||
|
/**@type {AutoCompleteOption[]} */ optionList = [];
|
||||||
|
/**@type {boolean} */ canBeQuoted = false;
|
||||||
|
/**@type {()=>string} */ makeNoMatchText = ()=>`No matches found for "${this.name}"`;
|
||||||
|
/**@type {()=>string} */ makeNoOptionsText = ()=>'No options';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} name Name (potentially partial) of the name at the requested index.
|
||||||
|
* @param {number} start Index where the name starts.
|
||||||
|
* @param {AutoCompleteOption[]} optionList A list of autocomplete options found in the current scope.
|
||||||
|
* @param {boolean} canBeQuoted Whether the name can be inside quotes.
|
||||||
|
* @param {()=>string} makeNoMatchText Function that returns text to show when no matches where found.
|
||||||
|
* @param {()=>string} makeNoOptionsText Function that returns text to show when no options are available to match against.
|
||||||
|
*/
|
||||||
|
constructor(name, start, optionList = [], canBeQuoted = false, makeNoMatchText = null, makeNoOptionsText = null) {
|
||||||
|
this.name = name;
|
||||||
|
this.start = start;
|
||||||
|
this.optionList = optionList;
|
||||||
|
this.canBeQuoted = canBeQuoted;
|
||||||
|
this.noMatchText = makeNoMatchText ?? this.makeNoMatchText;
|
||||||
|
this.noOptionstext = makeNoOptionsText ?? this.makeNoOptionsText;
|
||||||
|
}
|
||||||
|
}
|
@ -11,6 +11,9 @@ export class AutoCompleteOption {
|
|||||||
/**@type {AutoCompleteFuzzyScore}*/ score;
|
/**@type {AutoCompleteFuzzyScore}*/ score;
|
||||||
/**@type {string}*/ replacer;
|
/**@type {string}*/ replacer;
|
||||||
/**@type {HTMLElement}*/ dom;
|
/**@type {HTMLElement}*/ dom;
|
||||||
|
/**@type {(input:string)=>boolean}*/ matchProvider;
|
||||||
|
/**@type {(input:string)=>string}*/ valueProvider;
|
||||||
|
/**@type {boolean}*/ makeSelectable = false;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -21,14 +24,21 @@ export class AutoCompleteOption {
|
|||||||
return this.name;
|
return this.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isSelectable() {
|
||||||
|
return this.makeSelectable || !this.valueProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} name
|
* @param {string} name
|
||||||
*/
|
*/
|
||||||
constructor(name, typeIcon = ' ', type = '') {
|
constructor(name, typeIcon = ' ', type = '', matchProvider = null, valueProvider = null, makeSelectable = false) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.typeIcon = typeIcon;
|
this.typeIcon = typeIcon;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
|
this.matchProvider = matchProvider;
|
||||||
|
this.valueProvider = valueProvider;
|
||||||
|
this.makeSelectable = makeSelectable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { AutoCompleteNameResult } from './AutoCompleteNameResult.js';
|
import { AutoCompleteNameResultBase } from './AutoCompleteNameResultBase.js';
|
||||||
|
|
||||||
export class AutoCompleteSecondaryNameResult extends AutoCompleteNameResult {
|
export class AutoCompleteSecondaryNameResult extends AutoCompleteNameResultBase {
|
||||||
/**@type {boolean}*/ isRequired = false;
|
/**@type {boolean}*/ isRequired = false;
|
||||||
/**@type {boolean}*/ forceMatch = true;
|
/**@type {boolean}*/ forceMatch = true;
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ const defaultUrl = 'http://localhost:5100';
|
|||||||
let saveMetadataTimeout = null;
|
let saveMetadataTimeout = null;
|
||||||
|
|
||||||
let requiresReload = false;
|
let requiresReload = false;
|
||||||
|
let stateChanged = false;
|
||||||
|
|
||||||
export function saveMetadataDebounced() {
|
export function saveMetadataDebounced() {
|
||||||
const context = getContext();
|
const context = getContext();
|
||||||
@ -238,6 +239,7 @@ function onEnableExtensionClick() {
|
|||||||
|
|
||||||
async function enableExtension(name, reload = true) {
|
async function enableExtension(name, reload = true) {
|
||||||
extension_settings.disabledExtensions = extension_settings.disabledExtensions.filter(x => x !== name);
|
extension_settings.disabledExtensions = extension_settings.disabledExtensions.filter(x => x !== name);
|
||||||
|
stateChanged = true;
|
||||||
await saveSettings();
|
await saveSettings();
|
||||||
if (reload) {
|
if (reload) {
|
||||||
location.reload();
|
location.reload();
|
||||||
@ -248,6 +250,7 @@ async function enableExtension(name, reload = true) {
|
|||||||
|
|
||||||
async function disableExtension(name, reload = true) {
|
async function disableExtension(name, reload = true) {
|
||||||
extension_settings.disabledExtensions.push(name);
|
extension_settings.disabledExtensions.push(name);
|
||||||
|
stateChanged = true;
|
||||||
await saveSettings();
|
await saveSettings();
|
||||||
if (reload) {
|
if (reload) {
|
||||||
location.reload();
|
location.reload();
|
||||||
@ -657,7 +660,29 @@ async function showExtensionsDetails() {
|
|||||||
await oldPopup.complete(POPUP_RESULT.CANCELLED);
|
await oldPopup.complete(POPUP_RESULT.CANCELLED);
|
||||||
}
|
}
|
||||||
|
|
||||||
const popup = new Popup(html, POPUP_TYPE.TEXT, '', { okButton: 'Close', wide: true, large: true, customButtons: [updateAllButton], allowVerticalScrolling: true });
|
let waitingForSave = false;
|
||||||
|
|
||||||
|
const popup = new Popup(html, POPUP_TYPE.TEXT, '', {
|
||||||
|
okButton: 'Close',
|
||||||
|
wide: true,
|
||||||
|
large: true,
|
||||||
|
customButtons: [updateAllButton],
|
||||||
|
allowVerticalScrolling: true,
|
||||||
|
onClosing: async () => {
|
||||||
|
if (waitingForSave) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (stateChanged) {
|
||||||
|
waitingForSave = true;
|
||||||
|
const toast = toastr.info('The page will be reloaded shortly...', 'Extensions state changed');
|
||||||
|
await saveSettings();
|
||||||
|
toastr.clear(toast);
|
||||||
|
waitingForSave = false;
|
||||||
|
requiresReload = true;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
});
|
||||||
popupPromise = popup.show();
|
popupPromise = popup.show();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toastr.error('Error loading extensions. See browser console for details.');
|
toastr.error('Error loading extensions. See browser console for details.');
|
||||||
@ -989,6 +1014,28 @@ export async function writeExtensionField(characterId, key, value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prompts the user to enter the Git URL of the extension to import.
|
||||||
|
* After obtaining the Git URL, makes a POST request to '/api/extensions/install' to import the extension.
|
||||||
|
* If the extension is imported successfully, a success message is displayed.
|
||||||
|
* If the extension import fails, an error message is displayed and the error is logged to the console.
|
||||||
|
* After successfully importing the extension, the extension settings are reloaded and a 'EXTENSION_SETTINGS_LOADED' event is emitted.
|
||||||
|
* @param {string} [suggestUrl] Suggested URL to install
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
export async function openThirdPartyExtensionMenu(suggestUrl = '') {
|
||||||
|
const html = await renderTemplateAsync('installExtension');
|
||||||
|
const input = await callGenericPopup(html, POPUP_TYPE.INPUT, suggestUrl ?? '');
|
||||||
|
|
||||||
|
if (!input) {
|
||||||
|
console.debug('Extension install cancelled');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = String(input).trim();
|
||||||
|
await installExtension(url);
|
||||||
|
}
|
||||||
|
|
||||||
jQuery(async function () {
|
jQuery(async function () {
|
||||||
await addExtensionsButtonAndMenu();
|
await addExtensionsButtonAndMenu();
|
||||||
$('#extensionsMenuButton').css('display', 'flex');
|
$('#extensionsMenuButton').css('display', 'flex');
|
||||||
@ -1004,28 +1051,8 @@ jQuery(async function () {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the click event for the third-party extension import button.
|
* Handles the click event for the third-party extension import button.
|
||||||
* Prompts the user to enter the Git URL of the extension to import.
|
|
||||||
* After obtaining the Git URL, makes a POST request to '/api/extensions/install' to import the extension.
|
|
||||||
* If the extension is imported successfully, a success message is displayed.
|
|
||||||
* If the extension import fails, an error message is displayed and the error is logged to the console.
|
|
||||||
* After successfully importing the extension, the extension settings are reloaded and a 'EXTENSION_SETTINGS_LOADED' event is emitted.
|
|
||||||
*
|
*
|
||||||
* @listens #third_party_extension_button#click - The click event of the '#third_party_extension_button' element.
|
* @listens #third_party_extension_button#click - The click event of the '#third_party_extension_button' element.
|
||||||
*/
|
*/
|
||||||
$('#third_party_extension_button').on('click', async () => {
|
$('#third_party_extension_button').on('click', () => openThirdPartyExtensionMenu());
|
||||||
const html = `<h3>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>
|
|
||||||
<br>
|
|
||||||
<p>Example: <tt> https://github.com/author/extension-name </tt></p>`;
|
|
||||||
const input = await callGenericPopup(html, POPUP_TYPE.INPUT, '');
|
|
||||||
|
|
||||||
if (!input) {
|
|
||||||
console.debug('Extension install cancelled');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const url = String(input).trim();
|
|
||||||
await installExtension(url);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@ -8,13 +8,12 @@ import { textgen_types, textgenerationwebui_settings } from '../../textgen-setti
|
|||||||
import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
|
import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
|
||||||
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
|
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
|
||||||
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
|
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
|
||||||
import { SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js';
|
|
||||||
import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
|
import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||||
export { MODULE_NAME };
|
export { MODULE_NAME };
|
||||||
|
|
||||||
const MODULE_NAME = 'caption';
|
const MODULE_NAME = 'caption';
|
||||||
|
|
||||||
const PROMPT_DEFAULT = 'What’s in this image?';
|
const PROMPT_DEFAULT = 'What\'s in this image?';
|
||||||
const TEMPLATE_DEFAULT = '[{{user}} sends {{char}} a picture that contains: {{caption}}]';
|
const TEMPLATE_DEFAULT = '[{{user}} sends {{char}} a picture that contains: {{caption}}]';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -170,7 +169,11 @@ async function sendCaptionedMessage(caption, image) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
context.chat.push(message);
|
context.chat.push(message);
|
||||||
|
const messageId = context.chat.length - 1;
|
||||||
|
await eventSource.emit(event_types.MESSAGE_SENT, messageId);
|
||||||
context.addOneMessage(message);
|
context.addOneMessage(message);
|
||||||
|
await eventSource.emit(event_types.USER_MESSAGE_RENDERED, messageId);
|
||||||
|
await context.saveChat();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -334,7 +337,7 @@ async function getCaptionForFile(file, prompt, quiet) {
|
|||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
const errorMessage = error.message || 'Unknown error';
|
const errorMessage = error.message || 'Unknown error';
|
||||||
toastr.error(errorMessage, "Failed to caption image.");
|
toastr.error(errorMessage, 'Failed to caption image.');
|
||||||
console.error(error);
|
console.error(error);
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@ -399,6 +402,7 @@ jQuery(async function () {
|
|||||||
(modules.includes('caption') && extension_settings.caption.source === 'extras') ||
|
(modules.includes('caption') && extension_settings.caption.source === 'extras') ||
|
||||||
(extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'openai' && (secret_state[SECRET_KEYS.OPENAI] || extension_settings.caption.allow_reverse_proxy)) ||
|
(extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'openai' && (secret_state[SECRET_KEYS.OPENAI] || extension_settings.caption.allow_reverse_proxy)) ||
|
||||||
(extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'openrouter' && secret_state[SECRET_KEYS.OPENROUTER]) ||
|
(extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'openrouter' && secret_state[SECRET_KEYS.OPENROUTER]) ||
|
||||||
|
(extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'zerooneai' && secret_state[SECRET_KEYS.ZEROONEAI]) ||
|
||||||
(extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'google' && (secret_state[SECRET_KEYS.MAKERSUITE] || extension_settings.caption.allow_reverse_proxy)) ||
|
(extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'google' && (secret_state[SECRET_KEYS.MAKERSUITE] || extension_settings.caption.allow_reverse_proxy)) ||
|
||||||
(extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'anthropic' && (secret_state[SECRET_KEYS.CLAUDE] || extension_settings.caption.allow_reverse_proxy)) ||
|
(extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'anthropic' && (secret_state[SECRET_KEYS.CLAUDE] || extension_settings.caption.allow_reverse_proxy)) ||
|
||||||
(extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'ollama' && textgenerationwebui_settings.server_urls[textgen_types.OLLAMA]) ||
|
(extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'ollama' && textgenerationwebui_settings.server_urls[textgen_types.OLLAMA]) ||
|
||||||
|
@ -17,9 +17,10 @@
|
|||||||
<div class="flex1 flex-container flexFlowColumn flexNoGap">
|
<div class="flex1 flex-container flexFlowColumn flexNoGap">
|
||||||
<label for="caption_multimodal_api" data-i18n="API">API</label>
|
<label for="caption_multimodal_api" data-i18n="API">API</label>
|
||||||
<select id="caption_multimodal_api" class="flex1 text_pole">
|
<select id="caption_multimodal_api" class="flex1 text_pole">
|
||||||
|
<option value="zerooneai">01.AI (Yi)</option>
|
||||||
<option value="anthropic">Anthropic</option>
|
<option value="anthropic">Anthropic</option>
|
||||||
<option value="custom" data-i18n="Custom (OpenAI-compatible)">Custom (OpenAI-compatible)</option>
|
<option value="custom" data-i18n="Custom (OpenAI-compatible)">Custom (OpenAI-compatible)</option>
|
||||||
<option value="google">Google MakerSuite</option>
|
<option value="google">Google AI Studio</option>
|
||||||
<option value="koboldcpp">KoboldCpp</option>
|
<option value="koboldcpp">KoboldCpp</option>
|
||||||
<option value="llamacpp">llama.cpp</option>
|
<option value="llamacpp">llama.cpp</option>
|
||||||
<option value="ollama">Ollama</option>
|
<option value="ollama">Ollama</option>
|
||||||
@ -32,16 +33,20 @@
|
|||||||
<div class="flex1 flex-container flexFlowColumn flexNoGap">
|
<div class="flex1 flex-container flexFlowColumn flexNoGap">
|
||||||
<label for="caption_multimodal_model" data-i18n="Model">Model</label>
|
<label for="caption_multimodal_model" data-i18n="Model">Model</label>
|
||||||
<select id="caption_multimodal_model" class="flex1 text_pole">
|
<select id="caption_multimodal_model" class="flex1 text_pole">
|
||||||
|
<option data-type="zerooneai" value="yi-vision">yi-vision</option>
|
||||||
<option data-type="openai" value="gpt-4-vision-preview">gpt-4-vision-preview</option>
|
<option data-type="openai" value="gpt-4-vision-preview">gpt-4-vision-preview</option>
|
||||||
<option data-type="openai" value="gpt-4-turbo">gpt-4-turbo</option>
|
<option data-type="openai" value="gpt-4-turbo">gpt-4-turbo</option>
|
||||||
<option data-type="openai" value="gpt-4o">gpt-4o</option>
|
<option data-type="openai" value="gpt-4o">gpt-4o</option>
|
||||||
<option data-type="openai" value="gpt-4o-mini">gpt-4o-mini</option>
|
<option data-type="openai" value="gpt-4o-mini">gpt-4o-mini</option>
|
||||||
|
<option data-type="openai" value="chatgpt-4o-latest">chatgpt-4o-latest</option>
|
||||||
<option data-type="anthropic" value="claude-3-5-sonnet-20240620">claude-3-5-sonnet-20240620</option>
|
<option data-type="anthropic" value="claude-3-5-sonnet-20240620">claude-3-5-sonnet-20240620</option>
|
||||||
<option data-type="anthropic" value="claude-3-opus-20240229">claude-3-opus-20240229</option>
|
<option data-type="anthropic" value="claude-3-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-sonnet-20240229">claude-3-sonnet-20240229</option>
|
||||||
<option data-type="anthropic" value="claude-3-haiku-20240307">claude-3-haiku-20240307</option>
|
<option data-type="anthropic" value="claude-3-haiku-20240307">claude-3-haiku-20240307</option>
|
||||||
<option data-type="google" value="gemini-pro-vision">gemini-pro-vision</option>
|
<option data-type="google" value="gemini-pro-vision">gemini-pro-vision</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-latest">gemini-1.5-flash-latest</option>
|
||||||
|
<option data-type="google" value="gemini-1.5-pro-latest">gemini-1.5-pro-latest</option>
|
||||||
|
<option data-type="google" value="gemini-1.5-pro-exp-0801">gemini-1.5-pro-exp-0801</option>
|
||||||
<option data-type="openrouter" value="openai/gpt-4-vision-preview">openai/gpt-4-vision-preview</option>
|
<option data-type="openrouter" value="openai/gpt-4-vision-preview">openai/gpt-4-vision-preview</option>
|
||||||
<option data-type="openrouter" value="openai/gpt-4o">openai/gpt-4o</option>
|
<option data-type="openrouter" value="openai/gpt-4o">openai/gpt-4o</option>
|
||||||
<option data-type="openrouter" value="openai/gpt-4-turbo">openai/gpt-4-turbo</option>
|
<option data-type="openrouter" value="openai/gpt-4-turbo">openai/gpt-4-turbo</option>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { callPopup, eventSource, event_types, generateQuietPrompt, getRequestHeaders, online_status, saveSettingsDebounced, substituteParams, substituteParamsExtended, system_message_types } from '../../../script.js';
|
import { callPopup, eventSource, event_types, generateRaw, getRequestHeaders, main_api, online_status, saveSettingsDebounced, substituteParams, substituteParamsExtended, system_message_types } from '../../../script.js';
|
||||||
import { dragElement, isMobile } from '../../RossAscends-mods.js';
|
import { dragElement, isMobile } from '../../RossAscends-mods.js';
|
||||||
import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch, renderExtensionTemplateAsync } from '../../extensions.js';
|
import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch, renderExtensionTemplateAsync } from '../../extensions.js';
|
||||||
import { loadMovingUIState, power_user } from '../../power-user.js';
|
import { loadMovingUIState, power_user } from '../../power-user.js';
|
||||||
@ -1156,7 +1156,7 @@ async function getExpressionLabel(text) {
|
|||||||
|
|
||||||
functionResult = args?.arguments;
|
functionResult = args?.arguments;
|
||||||
});
|
});
|
||||||
const emotionResponse = await generateQuietPrompt(prompt, false, false);
|
const emotionResponse = await generateRaw(text, main_api, false, false, prompt);
|
||||||
return parseLlmResponse(functionResult || emotionResponse, expressionsList);
|
return parseLlmResponse(functionResult || emotionResponse, expressionsList);
|
||||||
}
|
}
|
||||||
// Extras
|
// Extras
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { getStringHash, debounce, waitUntilCondition, extractAllWords } from '../../utils.js';
|
import { getStringHash, debounce, waitUntilCondition, extractAllWords, isTrueBoolean } from '../../utils.js';
|
||||||
import { getContext, getApiUrl, extension_settings, doExtrasFetch, modules, renderExtensionTemplateAsync } from '../../extensions.js';
|
import { getContext, getApiUrl, extension_settings, doExtrasFetch, modules, renderExtensionTemplateAsync } from '../../extensions.js';
|
||||||
import {
|
import {
|
||||||
activateSendButtons,
|
activateSendButtons,
|
||||||
@ -25,6 +25,8 @@ import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
|
|||||||
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
|
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
|
||||||
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
|
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
|
||||||
import { MacrosParser } from '../../macros.js';
|
import { MacrosParser } from '../../macros.js';
|
||||||
|
import { countWebLlmTokens, generateWebLlmChatPrompt, getWebLlmContextSize, isWebLlmSupported } from '../shared.js';
|
||||||
|
import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||||
export { MODULE_NAME };
|
export { MODULE_NAME };
|
||||||
|
|
||||||
const MODULE_NAME = '1_memory';
|
const MODULE_NAME = '1_memory';
|
||||||
@ -36,6 +38,41 @@ let lastMessageHash = null;
|
|||||||
let lastMessageId = null;
|
let lastMessageId = null;
|
||||||
let inApiCall = false;
|
let inApiCall = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count the number of tokens in the provided text.
|
||||||
|
* @param {string} text Text to count tokens for
|
||||||
|
* @param {number} padding Number of additional tokens to add to the count
|
||||||
|
* @returns {Promise<number>} Number of tokens in the text
|
||||||
|
*/
|
||||||
|
async function countSourceTokens(text, padding = 0) {
|
||||||
|
if (extension_settings.memory.source === summary_sources.webllm) {
|
||||||
|
const count = await countWebLlmTokens(text);
|
||||||
|
return count + padding;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extension_settings.memory.source === summary_sources.extras) {
|
||||||
|
const count = getTextTokens(tokenizers.GPT2, text).length;
|
||||||
|
return count + padding;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await getTokenCountAsync(text, padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getSourceContextSize() {
|
||||||
|
const overrideLength = extension_settings.memory.overrideResponseLength;
|
||||||
|
|
||||||
|
if (extension_settings.memory.source === summary_sources.webllm) {
|
||||||
|
const maxContext = await getWebLlmContextSize();
|
||||||
|
return overrideLength > 0 ? (maxContext - overrideLength) : Math.round(maxContext * 0.75);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extension_settings.source === summary_sources.extras) {
|
||||||
|
return 1024 - 64;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getMaxContextSize(overrideLength);
|
||||||
|
}
|
||||||
|
|
||||||
const formatMemoryValue = function (value) {
|
const formatMemoryValue = function (value) {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return '';
|
return '';
|
||||||
@ -55,6 +92,7 @@ const saveChatDebounced = debounce(() => getContext().saveChat(), debounce_timeo
|
|||||||
const summary_sources = {
|
const summary_sources = {
|
||||||
'extras': 'extras',
|
'extras': 'extras',
|
||||||
'main': 'main',
|
'main': 'main',
|
||||||
|
'webllm': 'webllm',
|
||||||
};
|
};
|
||||||
|
|
||||||
const prompt_builders = {
|
const prompt_builders = {
|
||||||
@ -130,12 +168,12 @@ function loadSettings() {
|
|||||||
|
|
||||||
async function onPromptForceWordsAutoClick() {
|
async function onPromptForceWordsAutoClick() {
|
||||||
const context = getContext();
|
const context = getContext();
|
||||||
const maxPromptLength = getMaxContextSize(extension_settings.memory.overrideResponseLength);
|
const maxPromptLength = await getSourceContextSize();
|
||||||
const chat = context.chat;
|
const chat = context.chat;
|
||||||
const allMessages = chat.filter(m => !m.is_system && m.mes).map(m => m.mes);
|
const allMessages = chat.filter(m => !m.is_system && m.mes).map(m => m.mes);
|
||||||
const messagesWordCount = allMessages.map(m => extractAllWords(m)).flat().length;
|
const messagesWordCount = allMessages.map(m => extractAllWords(m)).flat().length;
|
||||||
const averageMessageWordCount = messagesWordCount / allMessages.length;
|
const averageMessageWordCount = messagesWordCount / allMessages.length;
|
||||||
const tokensPerWord = await getTokenCountAsync(allMessages.join('\n')) / messagesWordCount;
|
const tokensPerWord = await countSourceTokens(allMessages.join('\n')) / messagesWordCount;
|
||||||
const wordsPerToken = 1 / tokensPerWord;
|
const wordsPerToken = 1 / tokensPerWord;
|
||||||
const maxPromptLengthWords = Math.round(maxPromptLength * wordsPerToken);
|
const maxPromptLengthWords = Math.round(maxPromptLength * wordsPerToken);
|
||||||
// How many words should pass so that messages will start be dropped out of context;
|
// How many words should pass so that messages will start be dropped out of context;
|
||||||
@ -168,15 +206,15 @@ async function onPromptForceWordsAutoClick() {
|
|||||||
|
|
||||||
async function onPromptIntervalAutoClick() {
|
async function onPromptIntervalAutoClick() {
|
||||||
const context = getContext();
|
const context = getContext();
|
||||||
const maxPromptLength = getMaxContextSize(extension_settings.memory.overrideResponseLength);
|
const maxPromptLength = await getSourceContextSize();
|
||||||
const chat = context.chat;
|
const chat = context.chat;
|
||||||
const allMessages = chat.filter(m => !m.is_system && m.mes).map(m => m.mes);
|
const allMessages = chat.filter(m => !m.is_system && m.mes).map(m => m.mes);
|
||||||
const messagesWordCount = allMessages.map(m => extractAllWords(m)).flat().length;
|
const messagesWordCount = allMessages.map(m => extractAllWords(m)).flat().length;
|
||||||
const messagesTokenCount = await getTokenCountAsync(allMessages.join('\n'));
|
const messagesTokenCount = await countSourceTokens(allMessages.join('\n'));
|
||||||
const tokensPerWord = messagesTokenCount / messagesWordCount;
|
const tokensPerWord = messagesTokenCount / messagesWordCount;
|
||||||
const averageMessageTokenCount = messagesTokenCount / allMessages.length;
|
const averageMessageTokenCount = messagesTokenCount / allMessages.length;
|
||||||
const targetSummaryTokens = Math.round(extension_settings.memory.promptWords * tokensPerWord);
|
const targetSummaryTokens = Math.round(extension_settings.memory.promptWords * tokensPerWord);
|
||||||
const promptTokens = await getTokenCountAsync(extension_settings.memory.prompt);
|
const promptTokens = await countSourceTokens(extension_settings.memory.prompt);
|
||||||
const promptAllowance = maxPromptLength - promptTokens - targetSummaryTokens;
|
const promptAllowance = maxPromptLength - promptTokens - targetSummaryTokens;
|
||||||
const maxMessagesPerSummary = extension_settings.memory.maxMessagesPerRequest || 0;
|
const maxMessagesPerSummary = extension_settings.memory.maxMessagesPerRequest || 0;
|
||||||
const averageMessagesPerPrompt = Math.floor(promptAllowance / averageMessageTokenCount);
|
const averageMessagesPerPrompt = Math.floor(promptAllowance / averageMessageTokenCount);
|
||||||
@ -213,8 +251,8 @@ function onSummarySourceChange(event) {
|
|||||||
|
|
||||||
function switchSourceControls(value) {
|
function switchSourceControls(value) {
|
||||||
$('#memory_settings [data-summary-source]').each((_, element) => {
|
$('#memory_settings [data-summary-source]').each((_, element) => {
|
||||||
const source = $(element).data('summary-source');
|
const source = element.dataset.summarySource.split(',').map(s => s.trim());
|
||||||
$(element).toggle(source === value);
|
$(element).toggle(source.includes(value));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -353,10 +391,13 @@ function getIndexOfLatestChatSummary(chat) {
|
|||||||
|
|
||||||
async function onChatEvent() {
|
async function onChatEvent() {
|
||||||
// Module not enabled
|
// Module not enabled
|
||||||
if (extension_settings.memory.source === summary_sources.extras) {
|
if (extension_settings.memory.source === summary_sources.extras && !modules.includes('summarize')) {
|
||||||
if (!modules.includes('summarize')) {
|
return;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
// WebLLM is not supported
|
||||||
|
if (extension_settings.memory.source === summary_sources.webllm && !isWebLlmSupported()) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const context = getContext();
|
const context = getContext();
|
||||||
@ -416,7 +457,12 @@ async function onChatEvent() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function forceSummarizeChat() {
|
/**
|
||||||
|
* Forces a summary generation for the current chat.
|
||||||
|
* @param {boolean} quiet If an informational toast should be displayed
|
||||||
|
* @returns {Promise<string>} Summarized text
|
||||||
|
*/
|
||||||
|
async function forceSummarizeChat(quiet) {
|
||||||
if (extension_settings.memory.source === summary_sources.extras) {
|
if (extension_settings.memory.source === summary_sources.extras) {
|
||||||
toastr.warning('Force summarization is not supported for Extras API');
|
toastr.warning('Force summarization is not supported for Extras API');
|
||||||
return;
|
return;
|
||||||
@ -431,8 +477,12 @@ async function forceSummarizeChat() {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
toastr.info('Summarizing chat...', 'Please wait');
|
const toast = quiet ? jQuery() : toastr.info('Summarizing chat...', 'Please wait', { timeOut: 0, extendedTimeOut: 0 });
|
||||||
const value = await summarizeChatMain(context, true, skipWIAN);
|
const value = extension_settings.memory.source === summary_sources.main
|
||||||
|
? await summarizeChatMain(context, true, skipWIAN)
|
||||||
|
: await summarizeChatWebLLM(context, true);
|
||||||
|
|
||||||
|
toastr.clear(toast);
|
||||||
|
|
||||||
if (!value) {
|
if (!value) {
|
||||||
toastr.warning('Failed to summarize chat');
|
toastr.warning('Failed to summarize chat');
|
||||||
@ -450,9 +500,10 @@ async function forceSummarizeChat() {
|
|||||||
async function summarizeCallback(args, text) {
|
async function summarizeCallback(args, text) {
|
||||||
text = text.trim();
|
text = text.trim();
|
||||||
|
|
||||||
// Using forceSummarizeChat to summarize the current chat
|
// Summarize the current chat if no text provided
|
||||||
if (!text) {
|
if (!text) {
|
||||||
return await forceSummarizeChat();
|
const quiet = isTrueBoolean(args.quiet);
|
||||||
|
return await forceSummarizeChat(quiet);
|
||||||
}
|
}
|
||||||
|
|
||||||
const source = args.source || extension_settings.memory.source;
|
const source = args.source || extension_settings.memory.source;
|
||||||
@ -464,6 +515,11 @@ async function summarizeCallback(args, text) {
|
|||||||
return await callExtrasSummarizeAPI(text);
|
return await callExtrasSummarizeAPI(text);
|
||||||
case summary_sources.main:
|
case summary_sources.main:
|
||||||
return await generateRaw(text, '', false, false, prompt, extension_settings.memory.overrideResponseLength);
|
return await generateRaw(text, '', false, false, prompt, extension_settings.memory.overrideResponseLength);
|
||||||
|
case summary_sources.webllm: {
|
||||||
|
const messages = [{ role: 'system', content: prompt }, { role: 'user', content: text }].filter(m => m.content);
|
||||||
|
const params = extension_settings.memory.overrideResponseLength > 0 ? { max_tokens: extension_settings.memory.overrideResponseLength } : {};
|
||||||
|
return await generateWebLlmChatPrompt(messages, params);
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
toastr.warning('Invalid summarization source specified');
|
toastr.warning('Invalid summarization source specified');
|
||||||
return '';
|
return '';
|
||||||
@ -484,16 +540,25 @@ async function summarizeChat(context) {
|
|||||||
case summary_sources.main:
|
case summary_sources.main:
|
||||||
await summarizeChatMain(context, false, skipWIAN);
|
await summarizeChatMain(context, false, skipWIAN);
|
||||||
break;
|
break;
|
||||||
|
case summary_sources.webllm:
|
||||||
|
await summarizeChatWebLLM(context, false);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function summarizeChatMain(context, force, skipWIAN) {
|
/**
|
||||||
|
* Check if the chat should be summarized based on the current conditions.
|
||||||
|
* Return summary prompt if it should be summarized.
|
||||||
|
* @param {any} context ST context
|
||||||
|
* @param {boolean} force Summarize the chat regardless of the conditions
|
||||||
|
* @returns {Promise<string>} Summary prompt or empty string
|
||||||
|
*/
|
||||||
|
async function getSummaryPromptForNow(context, force) {
|
||||||
if (extension_settings.memory.promptInterval === 0 && !force) {
|
if (extension_settings.memory.promptInterval === 0 && !force) {
|
||||||
console.debug('Prompt interval is set to 0, skipping summarization');
|
console.debug('Prompt interval is set to 0, skipping summarization');
|
||||||
return;
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -505,17 +570,17 @@ async function summarizeChatMain(context, force, skipWIAN) {
|
|||||||
waitUntilCondition(() => is_send_press === false, 30000, 100);
|
waitUntilCondition(() => is_send_press === false, 30000, 100);
|
||||||
} catch {
|
} catch {
|
||||||
console.debug('Timeout waiting for is_send_press');
|
console.debug('Timeout waiting for is_send_press');
|
||||||
return;
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!context.chat.length) {
|
if (!context.chat.length) {
|
||||||
console.debug('No messages in chat to summarize');
|
console.debug('No messages in chat to summarize');
|
||||||
return;
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context.chat.length < extension_settings.memory.promptInterval && !force) {
|
if (context.chat.length < extension_settings.memory.promptInterval && !force) {
|
||||||
console.debug(`Not enough messages in chat to summarize (chat: ${context.chat.length}, interval: ${extension_settings.memory.promptInterval})`);
|
console.debug(`Not enough messages in chat to summarize (chat: ${context.chat.length}, interval: ${extension_settings.memory.promptInterval})`);
|
||||||
return;
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
let messagesSinceLastSummary = 0;
|
let messagesSinceLastSummary = 0;
|
||||||
@ -539,7 +604,7 @@ async function summarizeChatMain(context, force, skipWIAN) {
|
|||||||
|
|
||||||
if (!conditionSatisfied && !force) {
|
if (!conditionSatisfied && !force) {
|
||||||
console.debug(`Summary conditions not satisfied (messages: ${messagesSinceLastSummary}, interval: ${extension_settings.memory.promptInterval}, words: ${wordsSinceLastSummary}, force words: ${extension_settings.memory.promptForceWords})`);
|
console.debug(`Summary conditions not satisfied (messages: ${messagesSinceLastSummary}, interval: ${extension_settings.memory.promptInterval}, words: ${wordsSinceLastSummary}, force words: ${extension_settings.memory.promptForceWords})`);
|
||||||
return;
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Summarizing chat, messages since last summary: ' + messagesSinceLastSummary, 'words since last summary: ' + wordsSinceLastSummary);
|
console.log('Summarizing chat, messages since last summary: ' + messagesSinceLastSummary, 'words since last summary: ' + wordsSinceLastSummary);
|
||||||
@ -547,6 +612,63 @@ async function summarizeChatMain(context, force, skipWIAN) {
|
|||||||
|
|
||||||
if (!prompt) {
|
if (!prompt) {
|
||||||
console.debug('Summarization prompt is empty. Skipping summarization.');
|
console.debug('Summarization prompt is empty. Skipping summarization.');
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return prompt;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function summarizeChatWebLLM(context, force) {
|
||||||
|
if (!isWebLlmSupported()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const prompt = await getSummaryPromptForNow(context, force);
|
||||||
|
|
||||||
|
if (!prompt) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { rawPrompt, lastUsedIndex } = await getRawSummaryPrompt(context, prompt);
|
||||||
|
|
||||||
|
if (lastUsedIndex === null || lastUsedIndex === -1) {
|
||||||
|
if (force) {
|
||||||
|
toastr.info('To try again, remove the latest summary.', 'No messages found to summarize');
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const messages = [
|
||||||
|
{ role: 'system', content: prompt },
|
||||||
|
{ role: 'user', content: rawPrompt },
|
||||||
|
];
|
||||||
|
|
||||||
|
const params = {};
|
||||||
|
|
||||||
|
if (extension_settings.memory.overrideResponseLength > 0) {
|
||||||
|
params.max_tokens = extension_settings.memory.overrideResponseLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
const summary = await generateWebLlmChatPrompt(messages, params);
|
||||||
|
const newContext = getContext();
|
||||||
|
|
||||||
|
// something changed during summarization request
|
||||||
|
if (newContext.groupId !== context.groupId ||
|
||||||
|
newContext.chatId !== context.chatId ||
|
||||||
|
(!newContext.groupId && (newContext.characterId !== context.characterId))) {
|
||||||
|
console.log('Context changed, summary discarded');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setMemoryContext(summary, true, lastUsedIndex);
|
||||||
|
return summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function summarizeChatMain(context, force, skipWIAN) {
|
||||||
|
const prompt = await getSummaryPromptForNow(context, force);
|
||||||
|
|
||||||
|
if (!prompt) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -634,7 +756,7 @@ async function getRawSummaryPrompt(context, prompt) {
|
|||||||
chat.pop(); // We always exclude the last message from the buffer
|
chat.pop(); // We always exclude the last message from the buffer
|
||||||
const chatBuffer = [];
|
const chatBuffer = [];
|
||||||
const PADDING = 64;
|
const PADDING = 64;
|
||||||
const PROMPT_SIZE = getMaxContextSize(extension_settings.memory.overrideResponseLength);
|
const PROMPT_SIZE = await getSourceContextSize();
|
||||||
let latestUsedMessage = null;
|
let latestUsedMessage = null;
|
||||||
|
|
||||||
for (let index = latestSummaryIndex + 1; index < chat.length; index++) {
|
for (let index = latestSummaryIndex + 1; index < chat.length; index++) {
|
||||||
@ -651,7 +773,7 @@ async function getRawSummaryPrompt(context, prompt) {
|
|||||||
const entry = `${message.name}:\n${message.mes}`;
|
const entry = `${message.name}:\n${message.mes}`;
|
||||||
chatBuffer.push(entry);
|
chatBuffer.push(entry);
|
||||||
|
|
||||||
const tokens = await getTokenCountAsync(getMemoryString(true), PADDING);
|
const tokens = await countSourceTokens(getMemoryString(true), PADDING);
|
||||||
|
|
||||||
if (tokens > PROMPT_SIZE) {
|
if (tokens > PROMPT_SIZE) {
|
||||||
chatBuffer.pop();
|
chatBuffer.pop();
|
||||||
@ -680,7 +802,7 @@ async function summarizeChatExtras(context) {
|
|||||||
const reversedChat = chat.slice().reverse();
|
const reversedChat = chat.slice().reverse();
|
||||||
reversedChat.shift();
|
reversedChat.shift();
|
||||||
const memoryBuffer = [];
|
const memoryBuffer = [];
|
||||||
const CONTEXT_SIZE = 1024 - 64;
|
const CONTEXT_SIZE = await getSourceContextSize();
|
||||||
|
|
||||||
for (const message of reversedChat) {
|
for (const message of reversedChat) {
|
||||||
// we reached the point of latest memory
|
// we reached the point of latest memory
|
||||||
@ -698,14 +820,14 @@ async function summarizeChatExtras(context) {
|
|||||||
memoryBuffer.push(entry);
|
memoryBuffer.push(entry);
|
||||||
|
|
||||||
// check if token limit was reached
|
// check if token limit was reached
|
||||||
const tokens = getTextTokens(tokenizers.GPT2, getMemoryString()).length;
|
const tokens = await countSourceTokens(getMemoryString());
|
||||||
if (tokens >= CONTEXT_SIZE) {
|
if (tokens >= CONTEXT_SIZE) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const resultingString = getMemoryString();
|
const resultingString = getMemoryString();
|
||||||
const resultingTokens = getTextTokens(tokenizers.GPT2, resultingString).length;
|
const resultingTokens = await countSourceTokens(resultingString);
|
||||||
|
|
||||||
if (!resultingString || resultingTokens < CONTEXT_SIZE) {
|
if (!resultingString || resultingTokens < CONTEXT_SIZE) {
|
||||||
console.debug('Not enough context to summarize');
|
console.debug('Not enough context to summarize');
|
||||||
@ -890,7 +1012,7 @@ function setupListeners() {
|
|||||||
$('#memory_prompt_words').off('click').on('input', onMemoryPromptWordsInput);
|
$('#memory_prompt_words').off('click').on('input', onMemoryPromptWordsInput);
|
||||||
$('#memory_prompt_interval').off('click').on('input', onMemoryPromptIntervalInput);
|
$('#memory_prompt_interval').off('click').on('input', onMemoryPromptIntervalInput);
|
||||||
$('#memory_prompt').off('click').on('input', onMemoryPromptInput);
|
$('#memory_prompt').off('click').on('input', onMemoryPromptInput);
|
||||||
$('#memory_force_summarize').off('click').on('click', forceSummarizeChat);
|
$('#memory_force_summarize').off('click').on('click', () => forceSummarizeChat(false));
|
||||||
$('#memory_template').off('click').on('input', onMemoryTemplateInput);
|
$('#memory_template').off('click').on('input', onMemoryTemplateInput);
|
||||||
$('#memory_depth').off('click').on('input', onMemoryDepthInput);
|
$('#memory_depth').off('click').on('input', onMemoryDepthInput);
|
||||||
$('#memory_role').off('click').on('input', onMemoryRoleInput);
|
$('#memory_role').off('click').on('input', onMemoryRoleInput);
|
||||||
@ -933,13 +1055,20 @@ jQuery(async function () {
|
|||||||
name: 'summarize',
|
name: 'summarize',
|
||||||
callback: summarizeCallback,
|
callback: summarizeCallback,
|
||||||
namedArgumentList: [
|
namedArgumentList: [
|
||||||
new SlashCommandNamedArgument('source', 'API to use for summarization', [ARGUMENT_TYPE.STRING], false, false, '', ['main', 'extras']),
|
new SlashCommandNamedArgument('source', 'API to use for summarization', [ARGUMENT_TYPE.STRING], false, false, '', Object.values(summary_sources)),
|
||||||
SlashCommandNamedArgument.fromProps({
|
SlashCommandNamedArgument.fromProps({
|
||||||
name: 'prompt',
|
name: 'prompt',
|
||||||
description: 'prompt to use for summarization',
|
description: 'prompt to use for summarization',
|
||||||
typeList: [ARGUMENT_TYPE.STRING],
|
typeList: [ARGUMENT_TYPE.STRING],
|
||||||
defaultValue: '',
|
defaultValue: '',
|
||||||
}),
|
}),
|
||||||
|
SlashCommandNamedArgument.fromProps({
|
||||||
|
name: 'quiet',
|
||||||
|
description: 'suppress the toast message when summarizing the chat',
|
||||||
|
typeList: [ARGUMENT_TYPE.BOOLEAN],
|
||||||
|
defaultValue: 'false',
|
||||||
|
enumList: commonEnumProviders.boolean('trueFalse')(),
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
unnamedArgumentList: [
|
unnamedArgumentList: [
|
||||||
new SlashCommandArgument('text to summarize', [ARGUMENT_TYPE.STRING], false, false, ''),
|
new SlashCommandArgument('text to summarize', [ARGUMENT_TYPE.STRING], false, false, ''),
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
<select id="summary_source">
|
<select id="summary_source">
|
||||||
<option value="main" data-i18n="ext_sum_main_api">Main API</option>
|
<option value="main" data-i18n="ext_sum_main_api">Main API</option>
|
||||||
<option value="extras">Extras API</option>
|
<option value="extras">Extras API</option>
|
||||||
|
<option value="webllm" data-i18n="ext_sum_webllm">WebLLM Extension</option>
|
||||||
</select><br>
|
</select><br>
|
||||||
|
|
||||||
<div class="flex-container justifyspacebetween alignitemscenter">
|
<div class="flex-container justifyspacebetween alignitemscenter">
|
||||||
@ -24,7 +25,7 @@
|
|||||||
|
|
||||||
<textarea id="memory_contents" class="text_pole textarea_compact" rows="6" data-i18n="[placeholder]ext_sum_memory_placeholder" placeholder="Summary will be generated here..."></textarea>
|
<textarea id="memory_contents" class="text_pole textarea_compact" rows="6" data-i18n="[placeholder]ext_sum_memory_placeholder" placeholder="Summary will be generated here..."></textarea>
|
||||||
<div class="memory_contents_controls">
|
<div class="memory_contents_controls">
|
||||||
<div id="memory_force_summarize" data-summary-source="main" class="menu_button menu_button_icon" title="Trigger a summary update right now." data-i18n="[title]ext_sum_force_tip">
|
<div id="memory_force_summarize" data-summary-source="main,webllm" class="menu_button menu_button_icon" title="Trigger a summary update right now." data-i18n="[title]ext_sum_force_tip">
|
||||||
<i class="fa-solid fa-database"></i>
|
<i class="fa-solid fa-database"></i>
|
||||||
<span data-i18n="ext_sum_force_text">Summarize now</span>
|
<span data-i18n="ext_sum_force_text">Summarize now</span>
|
||||||
</div>
|
</div>
|
||||||
@ -58,7 +59,7 @@
|
|||||||
<span data-i18n="ext_sum_prompt_builder_3">Classic, blocking</span>
|
<span data-i18n="ext_sum_prompt_builder_3">Classic, blocking</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div data-summary-source="main">
|
<div data-summary-source="main,webllm">
|
||||||
<label for="memory_prompt" class="title_restorable">
|
<label for="memory_prompt" class="title_restorable">
|
||||||
<span data-i18n="Summary Prompt">Summary Prompt</span>
|
<span data-i18n="Summary Prompt">Summary Prompt</span>
|
||||||
<div id="memory_prompt_restore" data-i18n="[title]ext_sum_restore_default_prompt_tip" title="Restore default prompt" class="right_menu_button">
|
<div id="memory_prompt_restore" data-i18n="[title]ext_sum_restore_default_prompt_tip" title="Restore default prompt" class="right_menu_button">
|
||||||
@ -74,7 +75,7 @@
|
|||||||
</label>
|
</label>
|
||||||
<input id="memory_override_response_length" type="range" value="{{defaultSettings.overrideResponseLength}}" min="{{defaultSettings.overrideResponseLengthMin}}" max="{{defaultSettings.overrideResponseLengthMax}}" step="{{defaultSettings.overrideResponseLengthStep}}" />
|
<input id="memory_override_response_length" type="range" value="{{defaultSettings.overrideResponseLength}}" min="{{defaultSettings.overrideResponseLengthMin}}" max="{{defaultSettings.overrideResponseLengthMax}}" step="{{defaultSettings.overrideResponseLengthStep}}" />
|
||||||
<label for="memory_max_messages_per_request">
|
<label for="memory_max_messages_per_request">
|
||||||
<span data-i18n="ext_sum_raw_max_msg">[Raw] Max messages per request</span> (<span id="memory_max_messages_per_request_value"></span>)
|
<span data-i18n="ext_sum_raw_max_msg">[Raw/WebLLM] Max messages per request</span> (<span id="memory_max_messages_per_request_value"></span>)
|
||||||
<small class="memory_disabled_hint" data-i18n="ext_sum_0_unlimited">0 = unlimited</small>
|
<small class="memory_disabled_hint" data-i18n="ext_sum_0_unlimited">0 = unlimited</small>
|
||||||
</label>
|
</label>
|
||||||
<input id="memory_max_messages_per_request" type="range" value="{{defaultSettings.maxMessagesPerRequest}}" min="{{defaultSettings.maxMessagesPerRequestMin}}" max="{{defaultSettings.maxMessagesPerRequestMax}}" step="{{defaultSettings.maxMessagesPerRequestStep}}" />
|
<input id="memory_max_messages_per_request" type="range" value="{{defaultSettings.maxMessagesPerRequest}}" min="{{defaultSettings.maxMessagesPerRequestMin}}" max="{{defaultSettings.maxMessagesPerRequestMax}}" step="{{defaultSettings.maxMessagesPerRequestStep}}" />
|
||||||
|
@ -23,10 +23,18 @@ export class QuickReplyApi {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {QuickReply} qr
|
||||||
|
* @returns {QuickReplySet}
|
||||||
|
*/
|
||||||
|
getSetByQr(qr) {
|
||||||
|
return QuickReplySet.list.find(it=>it.qrList.includes(qr));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds and returns an existing Quick Reply Set by its name.
|
* Finds and returns an existing Quick Reply Set by its name.
|
||||||
*
|
*
|
||||||
* @param {String} name name of the quick reply set
|
* @param {string} name name of the quick reply set
|
||||||
* @returns the quick reply set, or undefined if not found
|
* @returns the quick reply set, or undefined if not found
|
||||||
*/
|
*/
|
||||||
getSetByName(name) {
|
getSetByName(name) {
|
||||||
@ -36,13 +44,14 @@ export class QuickReplyApi {
|
|||||||
/**
|
/**
|
||||||
* Finds and returns an existing Quick Reply by its set's name and its label.
|
* Finds and returns an existing Quick Reply by its set's name and its label.
|
||||||
*
|
*
|
||||||
* @param {String} setName name of the quick reply set
|
* @param {string} setName name of the quick reply set
|
||||||
* @param {String} label label of the quick reply
|
* @param {string|number} label label or numeric ID of the quick reply
|
||||||
* @returns the quick reply, or undefined if not found
|
* @returns the quick reply, or undefined if not found
|
||||||
*/
|
*/
|
||||||
getQrByLabel(setName, label) {
|
getQrByLabel(setName, label) {
|
||||||
const set = this.getSetByName(setName);
|
const set = this.getSetByName(setName);
|
||||||
if (!set) return;
|
if (!set) return;
|
||||||
|
if (Number.isInteger(label)) return set.qrList.find(it=>it.id == label);
|
||||||
return set.qrList.find(it=>it.label == label);
|
return set.qrList.find(it=>it.label == label);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,24 +79,25 @@ export class QuickReplyApi {
|
|||||||
/**
|
/**
|
||||||
* Executes an existing quick reply.
|
* Executes an existing quick reply.
|
||||||
*
|
*
|
||||||
* @param {String} setName name of the existing quick reply set
|
* @param {string} setName name of the existing quick reply set
|
||||||
* @param {String} label label of the existing quick reply (text on the button)
|
* @param {string|number} label label of the existing quick reply (text on the button) or its numeric ID
|
||||||
* @param {Object} [args] optional arguments
|
* @param {object} [args] optional arguments
|
||||||
|
* @param {import('../../../slash-commands.js').ExecuteSlashCommandsOptions} [options] optional execution options
|
||||||
*/
|
*/
|
||||||
async executeQuickReply(setName, label, args = {}) {
|
async executeQuickReply(setName, label, args = {}, options = {}) {
|
||||||
const qr = this.getQrByLabel(setName, label);
|
const qr = this.getQrByLabel(setName, label);
|
||||||
if (!qr) {
|
if (!qr) {
|
||||||
throw new Error(`No quick reply with label "${label}" in set "${setName}" found.`);
|
throw new Error(`No quick reply with label "${label}" in set "${setName}" found.`);
|
||||||
}
|
}
|
||||||
return await qr.execute(args);
|
return await qr.execute(args, false, false, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds or removes a quick reply set to the list of globally active quick reply sets.
|
* Adds or removes a quick reply set to the list of globally active quick reply sets.
|
||||||
*
|
*
|
||||||
* @param {String} name the name of the set
|
* @param {string} name the name of the set
|
||||||
* @param {Boolean} isVisible whether to show the set's buttons or not
|
* @param {boolean} isVisible whether to show the set's buttons or not
|
||||||
*/
|
*/
|
||||||
toggleGlobalSet(name, isVisible = true) {
|
toggleGlobalSet(name, isVisible = true) {
|
||||||
const set = this.getSetByName(name);
|
const set = this.getSetByName(name);
|
||||||
@ -104,8 +114,8 @@ export class QuickReplyApi {
|
|||||||
/**
|
/**
|
||||||
* Adds a quick reply set to the list of globally active quick reply sets.
|
* Adds a quick reply set to the list of globally active quick reply sets.
|
||||||
*
|
*
|
||||||
* @param {String} name the name of the set
|
* @param {string} name the name of the set
|
||||||
* @param {Boolean} isVisible whether to show the set's buttons or not
|
* @param {boolean} isVisible whether to show the set's buttons or not
|
||||||
*/
|
*/
|
||||||
addGlobalSet(name, isVisible = true) {
|
addGlobalSet(name, isVisible = true) {
|
||||||
const set = this.getSetByName(name);
|
const set = this.getSetByName(name);
|
||||||
@ -118,7 +128,7 @@ export class QuickReplyApi {
|
|||||||
/**
|
/**
|
||||||
* Removes a quick reply set from the list of globally active quick reply sets.
|
* Removes a quick reply set from the list of globally active quick reply sets.
|
||||||
*
|
*
|
||||||
* @param {String} name the name of the set
|
* @param {string} name the name of the set
|
||||||
*/
|
*/
|
||||||
removeGlobalSet(name) {
|
removeGlobalSet(name) {
|
||||||
const set = this.getSetByName(name);
|
const set = this.getSetByName(name);
|
||||||
@ -132,8 +142,8 @@ export class QuickReplyApi {
|
|||||||
/**
|
/**
|
||||||
* Adds or removes a quick reply set to the list of the current chat's active quick reply sets.
|
* Adds or removes a quick reply set to the list of the current chat's active quick reply sets.
|
||||||
*
|
*
|
||||||
* @param {String} name the name of the set
|
* @param {string} name the name of the set
|
||||||
* @param {Boolean} isVisible whether to show the set's buttons or not
|
* @param {boolean} isVisible whether to show the set's buttons or not
|
||||||
*/
|
*/
|
||||||
toggleChatSet(name, isVisible = true) {
|
toggleChatSet(name, isVisible = true) {
|
||||||
if (!this.settings.chatConfig) return;
|
if (!this.settings.chatConfig) return;
|
||||||
@ -151,8 +161,8 @@ export class QuickReplyApi {
|
|||||||
/**
|
/**
|
||||||
* Adds a quick reply set to the list of the current chat's active quick reply sets.
|
* Adds a quick reply set to the list of the current chat's active quick reply sets.
|
||||||
*
|
*
|
||||||
* @param {String} name the name of the set
|
* @param {string} name the name of the set
|
||||||
* @param {Boolean} isVisible whether to show the set's buttons or not
|
* @param {boolean} isVisible whether to show the set's buttons or not
|
||||||
*/
|
*/
|
||||||
addChatSet(name, isVisible = true) {
|
addChatSet(name, isVisible = true) {
|
||||||
if (!this.settings.chatConfig) return;
|
if (!this.settings.chatConfig) return;
|
||||||
@ -166,7 +176,7 @@ export class QuickReplyApi {
|
|||||||
/**
|
/**
|
||||||
* Removes a quick reply set from the list of the current chat's active quick reply sets.
|
* Removes a quick reply set from the list of the current chat's active quick reply sets.
|
||||||
*
|
*
|
||||||
* @param {String} name the name of the set
|
* @param {string} name the name of the set
|
||||||
*/
|
*/
|
||||||
removeChatSet(name) {
|
removeChatSet(name) {
|
||||||
if (!this.settings.chatConfig) return;
|
if (!this.settings.chatConfig) return;
|
||||||
@ -181,21 +191,26 @@ export class QuickReplyApi {
|
|||||||
/**
|
/**
|
||||||
* Creates a new quick reply in an existing quick reply set.
|
* Creates a new quick reply in an existing quick reply set.
|
||||||
*
|
*
|
||||||
* @param {String} setName name of the quick reply set to insert the new quick reply into
|
* @param {string} setName name of the quick reply set to insert the new quick reply into
|
||||||
* @param {String} label label for the new quick reply (text on the button)
|
* @param {string} label label for the new quick reply (text on the button)
|
||||||
* @param {Object} [props]
|
* @param {object} [props]
|
||||||
* @param {String} [props.message] the message to be sent or slash command to be executed by the new quick reply
|
* @param {string} [props.icon] the icon to show on the QR button
|
||||||
* @param {String} [props.title] the title / tooltip to be shown on the quick reply button
|
* @param {boolean} [props.showLabel] whether to show the label even when an icon is assigned
|
||||||
* @param {Boolean} [props.isHidden] whether to hide or show the button
|
* @param {string} [props.message] the message to be sent or slash command to be executed by the new quick reply
|
||||||
* @param {Boolean} [props.executeOnStartup] whether to execute the quick reply when SillyTavern starts
|
* @param {string} [props.title] the title / tooltip to be shown on the quick reply button
|
||||||
* @param {Boolean} [props.executeOnUser] whether to execute the quick reply after a user has sent a message
|
* @param {boolean} [props.isHidden] whether to hide or show the button
|
||||||
* @param {Boolean} [props.executeOnAi] whether to execute the quick reply after the AI has sent a message
|
* @param {boolean} [props.executeOnStartup] whether to execute the quick reply when SillyTavern starts
|
||||||
* @param {Boolean} [props.executeOnChatChange] whether to execute the quick reply when a new chat is loaded
|
* @param {boolean} [props.executeOnUser] whether to execute the quick reply after a user has sent a message
|
||||||
* @param {Boolean} [props.executeOnGroupMemberDraft] whether to execute the quick reply when a group member is selected
|
* @param {boolean} [props.executeOnAi] whether to execute the quick reply after the AI has sent a message
|
||||||
* @param {String} [props.automationId] when not empty, the quick reply will be executed when the WI with the given automation ID is activated
|
* @param {boolean} [props.executeOnChatChange] whether to execute the quick reply when a new chat is loaded
|
||||||
|
* @param {boolean} [props.executeOnGroupMemberDraft] whether to execute the quick reply when a group member is selected
|
||||||
|
* @param {boolean} [props.executeOnNewChat] whether to execute the quick reply when a new chat is created
|
||||||
|
* @param {string} [props.automationId] when not empty, the quick reply will be executed when the WI with the given automation ID is activated
|
||||||
* @returns {QuickReply} the new quick reply
|
* @returns {QuickReply} the new quick reply
|
||||||
*/
|
*/
|
||||||
createQuickReply(setName, label, {
|
createQuickReply(setName, label, {
|
||||||
|
icon,
|
||||||
|
showLabel,
|
||||||
message,
|
message,
|
||||||
title,
|
title,
|
||||||
isHidden,
|
isHidden,
|
||||||
@ -204,6 +219,7 @@ export class QuickReplyApi {
|
|||||||
executeOnAi,
|
executeOnAi,
|
||||||
executeOnChatChange,
|
executeOnChatChange,
|
||||||
executeOnGroupMemberDraft,
|
executeOnGroupMemberDraft,
|
||||||
|
executeOnNewChat,
|
||||||
automationId,
|
automationId,
|
||||||
} = {}) {
|
} = {}) {
|
||||||
const set = this.getSetByName(setName);
|
const set = this.getSetByName(setName);
|
||||||
@ -212,6 +228,8 @@ export class QuickReplyApi {
|
|||||||
}
|
}
|
||||||
const qr = set.addQuickReply();
|
const qr = set.addQuickReply();
|
||||||
qr.label = label ?? '';
|
qr.label = label ?? '';
|
||||||
|
qr.icon = icon ?? '';
|
||||||
|
qr.showLabel = showLabel ?? false;
|
||||||
qr.message = message ?? '';
|
qr.message = message ?? '';
|
||||||
qr.title = title ?? '';
|
qr.title = title ?? '';
|
||||||
qr.isHidden = isHidden ?? false;
|
qr.isHidden = isHidden ?? false;
|
||||||
@ -220,6 +238,7 @@ export class QuickReplyApi {
|
|||||||
qr.executeOnAi = executeOnAi ?? false;
|
qr.executeOnAi = executeOnAi ?? false;
|
||||||
qr.executeOnChatChange = executeOnChatChange ?? false;
|
qr.executeOnChatChange = executeOnChatChange ?? false;
|
||||||
qr.executeOnGroupMemberDraft = executeOnGroupMemberDraft ?? false;
|
qr.executeOnGroupMemberDraft = executeOnGroupMemberDraft ?? false;
|
||||||
|
qr.executeOnNewChat = executeOnNewChat ?? false;
|
||||||
qr.automationId = automationId ?? '';
|
qr.automationId = automationId ?? '';
|
||||||
qr.onUpdate();
|
qr.onUpdate();
|
||||||
return qr;
|
return qr;
|
||||||
@ -228,22 +247,27 @@ export class QuickReplyApi {
|
|||||||
/**
|
/**
|
||||||
* Updates an existing quick reply.
|
* Updates an existing quick reply.
|
||||||
*
|
*
|
||||||
* @param {String} setName name of the existing quick reply set
|
* @param {string} setName name of the existing quick reply set
|
||||||
* @param {String} label label of the existing quick reply (text on the button)
|
* @param {string|number} label label of the existing quick reply (text on the button) or its numeric ID
|
||||||
* @param {Object} [props]
|
* @param {object} [props]
|
||||||
* @param {String} [props.newLabel] new label for quick reply (text on the button)
|
* @param {string} [props.icon] the icon to show on the QR button
|
||||||
* @param {String} [props.message] the message to be sent or slash command to be executed by the quick reply
|
* @param {boolean} [props.showLabel] whether to show the label even when an icon is assigned
|
||||||
* @param {String} [props.title] the title / tooltip to be shown on the quick reply button
|
* @param {string} [props.newLabel] new label for quick reply (text on the button)
|
||||||
* @param {Boolean} [props.isHidden] whether to hide or show the button
|
* @param {string} [props.message] the message to be sent or slash command to be executed by the quick reply
|
||||||
* @param {Boolean} [props.executeOnStartup] whether to execute the quick reply when SillyTavern starts
|
* @param {string} [props.title] the title / tooltip to be shown on the quick reply button
|
||||||
* @param {Boolean} [props.executeOnUser] whether to execute the quick reply after a user has sent a message
|
* @param {boolean} [props.isHidden] whether to hide or show the button
|
||||||
* @param {Boolean} [props.executeOnAi] whether to execute the quick reply after the AI has sent a message
|
* @param {boolean} [props.executeOnStartup] whether to execute the quick reply when SillyTavern starts
|
||||||
* @param {Boolean} [props.executeOnChatChange] whether to execute the quick reply when a new chat is loaded
|
* @param {boolean} [props.executeOnUser] whether to execute the quick reply after a user has sent a message
|
||||||
* @param {Boolean} [props.executeOnGroupMemberDraft] whether to execute the quick reply when a group member is selected
|
* @param {boolean} [props.executeOnAi] whether to execute the quick reply after the AI has sent a message
|
||||||
* @param {String} [props.automationId] when not empty, the quick reply will be executed when the WI with the given automation ID is activated
|
* @param {boolean} [props.executeOnChatChange] whether to execute the quick reply when a new chat is loaded
|
||||||
|
* @param {boolean} [props.executeOnGroupMemberDraft] whether to execute the quick reply when a group member is selected
|
||||||
|
* @param {boolean} [props.executeOnNewChat] whether to execute the quick reply when a new chat is created
|
||||||
|
* @param {string} [props.automationId] when not empty, the quick reply will be executed when the WI with the given automation ID is activated
|
||||||
* @returns {QuickReply} the altered quick reply
|
* @returns {QuickReply} the altered quick reply
|
||||||
*/
|
*/
|
||||||
updateQuickReply(setName, label, {
|
updateQuickReply(setName, label, {
|
||||||
|
icon,
|
||||||
|
showLabel,
|
||||||
newLabel,
|
newLabel,
|
||||||
message,
|
message,
|
||||||
title,
|
title,
|
||||||
@ -253,12 +277,15 @@ export class QuickReplyApi {
|
|||||||
executeOnAi,
|
executeOnAi,
|
||||||
executeOnChatChange,
|
executeOnChatChange,
|
||||||
executeOnGroupMemberDraft,
|
executeOnGroupMemberDraft,
|
||||||
|
executeOnNewChat,
|
||||||
automationId,
|
automationId,
|
||||||
} = {}) {
|
} = {}) {
|
||||||
const qr = this.getQrByLabel(setName, label);
|
const qr = this.getQrByLabel(setName, label);
|
||||||
if (!qr) {
|
if (!qr) {
|
||||||
throw new Error(`No quick reply with label "${label}" in set "${setName}" found.`);
|
throw new Error(`No quick reply with label "${label}" in set "${setName}" found.`);
|
||||||
}
|
}
|
||||||
|
qr.updateIcon(icon ?? qr.icon);
|
||||||
|
qr.updateShowLabel(showLabel ?? qr.showLabel);
|
||||||
qr.updateLabel(newLabel ?? qr.label);
|
qr.updateLabel(newLabel ?? qr.label);
|
||||||
qr.updateMessage(message ?? qr.message);
|
qr.updateMessage(message ?? qr.message);
|
||||||
qr.updateTitle(title ?? qr.title);
|
qr.updateTitle(title ?? qr.title);
|
||||||
@ -268,6 +295,7 @@ export class QuickReplyApi {
|
|||||||
qr.executeOnAi = executeOnAi ?? qr.executeOnAi;
|
qr.executeOnAi = executeOnAi ?? qr.executeOnAi;
|
||||||
qr.executeOnChatChange = executeOnChatChange ?? qr.executeOnChatChange;
|
qr.executeOnChatChange = executeOnChatChange ?? qr.executeOnChatChange;
|
||||||
qr.executeOnGroupMemberDraft = executeOnGroupMemberDraft ?? qr.executeOnGroupMemberDraft;
|
qr.executeOnGroupMemberDraft = executeOnGroupMemberDraft ?? qr.executeOnGroupMemberDraft;
|
||||||
|
qr.executeOnNewChat = executeOnNewChat ?? qr.executeOnNewChat;
|
||||||
qr.automationId = automationId ?? qr.automationId;
|
qr.automationId = automationId ?? qr.automationId;
|
||||||
qr.onUpdate();
|
qr.onUpdate();
|
||||||
return qr;
|
return qr;
|
||||||
@ -276,8 +304,8 @@ export class QuickReplyApi {
|
|||||||
/**
|
/**
|
||||||
* Deletes an existing quick reply.
|
* Deletes an existing quick reply.
|
||||||
*
|
*
|
||||||
* @param {String} setName name of the existing quick reply set
|
* @param {string} setName name of the existing quick reply set
|
||||||
* @param {String} label label of the existing quick reply (text on the button)
|
* @param {string|number} label label of the existing quick reply (text on the button) or its numeric ID
|
||||||
*/
|
*/
|
||||||
deleteQuickReply(setName, label) {
|
deleteQuickReply(setName, label) {
|
||||||
const qr = this.getQrByLabel(setName, label);
|
const qr = this.getQrByLabel(setName, label);
|
||||||
@ -291,10 +319,10 @@ export class QuickReplyApi {
|
|||||||
/**
|
/**
|
||||||
* Adds an existing quick reply set as a context menu to an existing quick reply.
|
* Adds an existing quick reply set as a context menu to an existing quick reply.
|
||||||
*
|
*
|
||||||
* @param {String} setName name of the existing quick reply set containing the quick reply
|
* @param {string} setName name of the existing quick reply set containing the quick reply
|
||||||
* @param {String} label label of the existing quick reply
|
* @param {string|number} label label of the existing quick reply or its numeric ID
|
||||||
* @param {String} contextSetName name of the existing quick reply set to be used as a context menu
|
* @param {string} contextSetName name of the existing quick reply set to be used as a context menu
|
||||||
* @param {Boolean} isChained whether or not to chain the context menu quick replies
|
* @param {boolean} isChained whether or not to chain the context menu quick replies
|
||||||
*/
|
*/
|
||||||
createContextItem(setName, label, contextSetName, isChained = false) {
|
createContextItem(setName, label, contextSetName, isChained = false) {
|
||||||
const qr = this.getQrByLabel(setName, label);
|
const qr = this.getQrByLabel(setName, label);
|
||||||
@ -314,9 +342,9 @@ export class QuickReplyApi {
|
|||||||
/**
|
/**
|
||||||
* Removes a quick reply set from a quick reply's context menu.
|
* Removes a quick reply set from a quick reply's context menu.
|
||||||
*
|
*
|
||||||
* @param {String} setName name of the existing quick reply set containing the quick reply
|
* @param {string} setName name of the existing quick reply set containing the quick reply
|
||||||
* @param {String} label label of the existing quick reply
|
* @param {string|number} label label of the existing quick reply or its numeric ID
|
||||||
* @param {String} contextSetName name of the existing quick reply set to be used as a context menu
|
* @param {string} contextSetName name of the existing quick reply set to be used as a context menu
|
||||||
*/
|
*/
|
||||||
deleteContextItem(setName, label, contextSetName) {
|
deleteContextItem(setName, label, contextSetName) {
|
||||||
const qr = this.getQrByLabel(setName, label);
|
const qr = this.getQrByLabel(setName, label);
|
||||||
@ -333,8 +361,8 @@ export class QuickReplyApi {
|
|||||||
/**
|
/**
|
||||||
* Removes all entries from a quick reply's context menu.
|
* Removes all entries from a quick reply's context menu.
|
||||||
*
|
*
|
||||||
* @param {String} setName name of the existing quick reply set containing the quick reply
|
* @param {string} setName name of the existing quick reply set containing the quick reply
|
||||||
* @param {String} label label of the existing quick reply
|
* @param {string|number} label label of the existing quick reply or its numeric ID
|
||||||
*/
|
*/
|
||||||
clearContextMenu(setName, label) {
|
clearContextMenu(setName, label) {
|
||||||
const qr = this.getQrByLabel(setName, label);
|
const qr = this.getQrByLabel(setName, label);
|
||||||
@ -348,11 +376,11 @@ export class QuickReplyApi {
|
|||||||
/**
|
/**
|
||||||
* Create a new quick reply set.
|
* Create a new quick reply set.
|
||||||
*
|
*
|
||||||
* @param {String} name name of the new quick reply set
|
* @param {string} name name of the new quick reply set
|
||||||
* @param {Object} [props]
|
* @param {object} [props]
|
||||||
* @param {Boolean} [props.disableSend] whether or not to send the quick replies or put the message or slash command into the char input box
|
* @param {boolean} [props.disableSend] whether or not to send the quick replies or put the message or slash command into the char input box
|
||||||
* @param {Boolean} [props.placeBeforeInput] whether or not to place the quick reply contents before the existing user input
|
* @param {boolean} [props.placeBeforeInput] whether or not to place the quick reply contents before the existing user input
|
||||||
* @param {Boolean} [props.injectInput] whether or not to automatically inject the user input at the end of the quick reply
|
* @param {boolean} [props.injectInput] whether or not to automatically inject the user input at the end of the quick reply
|
||||||
* @returns {Promise<QuickReplySet>} the new quick reply set
|
* @returns {Promise<QuickReplySet>} the new quick reply set
|
||||||
*/
|
*/
|
||||||
async createSet(name, {
|
async createSet(name, {
|
||||||
@ -384,11 +412,11 @@ export class QuickReplyApi {
|
|||||||
/**
|
/**
|
||||||
* Update an existing quick reply set.
|
* Update an existing quick reply set.
|
||||||
*
|
*
|
||||||
* @param {String} name name of the existing quick reply set
|
* @param {string} name name of the existing quick reply set
|
||||||
* @param {Object} [props]
|
* @param {object} [props]
|
||||||
* @param {Boolean} [props.disableSend] whether or not to send the quick replies or put the message or slash command into the char input box
|
* @param {boolean} [props.disableSend] whether or not to send the quick replies or put the message or slash command into the char input box
|
||||||
* @param {Boolean} [props.placeBeforeInput] whether or not to place the quick reply contents before the existing user input
|
* @param {boolean} [props.placeBeforeInput] whether or not to place the quick reply contents before the existing user input
|
||||||
* @param {Boolean} [props.injectInput] whether or not to automatically inject the user input at the end of the quick reply
|
* @param {boolean} [props.injectInput] whether or not to automatically inject the user input at the end of the quick reply
|
||||||
* @returns {Promise<QuickReplySet>} the altered quick reply set
|
* @returns {Promise<QuickReplySet>} the altered quick reply set
|
||||||
*/
|
*/
|
||||||
async updateSet(name, {
|
async updateSet(name, {
|
||||||
@ -411,7 +439,7 @@ export class QuickReplyApi {
|
|||||||
/**
|
/**
|
||||||
* Delete an existing quick reply set.
|
* Delete an existing quick reply set.
|
||||||
*
|
*
|
||||||
* @param {String} name name of the existing quick reply set
|
* @param {string} name name of the existing quick reply set
|
||||||
*/
|
*/
|
||||||
async deleteSet(name) {
|
async deleteSet(name) {
|
||||||
const set = this.getSetByName(name);
|
const set = this.getSetByName(name);
|
||||||
@ -451,7 +479,7 @@ export class QuickReplyApi {
|
|||||||
/**
|
/**
|
||||||
* Gets a list of all quick replies in the quick reply set.
|
* Gets a list of all quick replies in the quick reply set.
|
||||||
*
|
*
|
||||||
* @param {String} setName name of the existing quick reply set
|
* @param {string} setName name of the existing quick reply set
|
||||||
* @returns array with the labels of this set's quick replies
|
* @returns array with the labels of this set's quick replies
|
||||||
*/
|
*/
|
||||||
listQuickReplies(setName) {
|
listQuickReplies(setName) {
|
||||||
|
@ -2,10 +2,23 @@
|
|||||||
<div id="qr--main">
|
<div id="qr--main">
|
||||||
<h3 data-i18n="Labels and Message">Labels and Message</h3>
|
<h3 data-i18n="Labels and Message">Labels and Message</h3>
|
||||||
<div class="qr--labels">
|
<div class="qr--labels">
|
||||||
<label>
|
<label class="qr--fit">
|
||||||
<span class="qr--labelText" data-i18n="Label">Label</span>
|
<span class="qr--labelText" data-i18n="Label">Icon</span>
|
||||||
<input type="text" class="text_pole" id="qr--modal-label">
|
<small class="qr--labelHint"> </small>
|
||||||
|
<div class="menu_button fa-fw" id="qr--modal-icon" title="Click to change icon"></div>
|
||||||
</label>
|
</label>
|
||||||
|
<div class="label">
|
||||||
|
<span class="qr--labelText" data-i18n="Label">Label</span>
|
||||||
|
<small class="qr--labelHint" data-i18n="(label of the button, if no icon is chosen) ">(label of the button, if no icon is chosen)</small>
|
||||||
|
<div class="qr--inputGroup">
|
||||||
|
<label class="checkbox_label" title="Show label even if an icon is assigned">
|
||||||
|
<input type="checkbox" id="qr--modal-showLabel">
|
||||||
|
Show
|
||||||
|
</label>
|
||||||
|
<input type="text" class="text_pole" id="qr--modal-label">
|
||||||
|
<div class="menu_button fa-fw fa-solid fa-chevron-down" id="qr--modal-switcher" title="Switch to another QR"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<label>
|
<label>
|
||||||
<span class="qr--labelText" data-i18n="Title">Title</span>
|
<span class="qr--labelText" data-i18n="Title">Title</span>
|
||||||
<small class="qr--labelHint" data-i18n="(tooltip, leave empty to show message or /command)">(tooltip, leave empty to show message or /command)</small>
|
<small class="qr--labelHint" data-i18n="(tooltip, leave empty to show message or /command)">(tooltip, leave empty to show message or /command)</small>
|
||||||
@ -33,6 +46,8 @@
|
|||||||
<input type="checkbox" id="qr--modal-syntax">
|
<input type="checkbox" id="qr--modal-syntax">
|
||||||
<span>Syntax highlight</span>
|
<span>Syntax highlight</span>
|
||||||
</label>
|
</label>
|
||||||
|
<small>Ctrl+Alt+Click (or F9) to set / remove breakpoints</small>
|
||||||
|
<small>Ctrl+<span id="qr--modal-commentKey"></span> to toggle block comments</small>
|
||||||
</div>
|
</div>
|
||||||
<div id="qr--modal-messageHolder">
|
<div id="qr--modal-messageHolder">
|
||||||
<pre id="qr--modal-messageSyntax"><code id="qr--modal-messageSyntaxInner" class="hljs language-stscript"></code></pre>
|
<pre id="qr--modal-messageSyntax"><code id="qr--modal-messageSyntaxInner" class="hljs language-stscript"></code></pre>
|
||||||
@ -43,6 +58,10 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div id="qr--resizeHandle"></div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div id="qr--qrOptions">
|
<div id="qr--qrOptions">
|
||||||
<h3 data-i18n="Context Menu">Context Menu</h3>
|
<h3 data-i18n="Context Menu">Context Menu</h3>
|
||||||
<div id="qr--ctxEditor">
|
<div id="qr--ctxEditor">
|
||||||
@ -64,7 +83,7 @@
|
|||||||
|
|
||||||
|
|
||||||
<h3 data-i18n="Auto-Execute">Auto-Execute</h3>
|
<h3 data-i18n="Auto-Execute">Auto-Execute</h3>
|
||||||
<div class="flex-container flexFlowColumn">
|
<div id="qr--autoExec" class="flex-container flexFlowColumn">
|
||||||
<label class="checkbox_label" title="Prevent this quick reply from triggering other auto-executed quick replies while auto-executing (i.e., prevent recursive auto-execution)">
|
<label class="checkbox_label" title="Prevent this quick reply from triggering other auto-executed quick replies while auto-executing (i.e., prevent recursive auto-execution)">
|
||||||
<input type="checkbox" id="qr--preventAutoExecute" >
|
<input type="checkbox" id="qr--preventAutoExecute" >
|
||||||
<span><i class="fa-solid fa-fw fa-plane-slash"></i><span data-i18n="Don't trigger auto-execute">Don't trigger auto-execute</span></span>
|
<span><i class="fa-solid fa-fw fa-plane-slash"></i><span data-i18n="Don't trigger auto-execute">Don't trigger auto-execute</span></span>
|
||||||
@ -89,6 +108,10 @@
|
|||||||
<input type="checkbox" id="qr--executeOnChatChange" >
|
<input type="checkbox" id="qr--executeOnChatChange" >
|
||||||
<span><i class="fa-solid fa-fw fa-message"></i><span data-i18n="Execute on chat change">Execute on chat change</span></span>
|
<span><i class="fa-solid fa-fw fa-message"></i><span data-i18n="Execute on chat change">Execute on chat change</span></span>
|
||||||
</label>
|
</label>
|
||||||
|
<label class="checkbox_label">
|
||||||
|
<input type="checkbox" id="qr--executeOnNewChat">
|
||||||
|
<span><i class="fa-solid fa-fw fa-comments"></i><span data-i18n="Execute on new chat">Execute on new chat</span></span>
|
||||||
|
</label>
|
||||||
<label class="checkbox_label">
|
<label class="checkbox_label">
|
||||||
<input type="checkbox" id="qr--executeOnGroupMemberDraft">
|
<input type="checkbox" id="qr--executeOnGroupMemberDraft">
|
||||||
<span><i class="fa-solid fa-fw fa-people-group"></i><span data-i18n="Execute on group member draft">Execute on group member draft</span></span>
|
<span><i class="fa-solid fa-fw fa-people-group"></i><span data-i18n="Execute on group member draft">Execute on group member draft</span></span>
|
||||||
@ -117,11 +140,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="qr--modal-executeProgress"></div>
|
<div id="qr--modal-executeProgress"></div>
|
||||||
<label class="checkbox_label">
|
|
||||||
<input type="checkbox" id="qr--modal-executeHide">
|
|
||||||
<span title="Hide editor while executing"> Hide editor while executing</span>
|
|
||||||
</label>
|
|
||||||
<div id="qr--modal-executeErrors"></div>
|
<div id="qr--modal-executeErrors"></div>
|
||||||
<div id="qr--modal-executeResult"></div>
|
<div id="qr--modal-executeResult"></div>
|
||||||
|
|
||||||
|
<div id="qr--modal-debugButtons">
|
||||||
|
<div title="Resume" id="qr--modal-resume" class="qr--modal-debugButton menu_button"></div>
|
||||||
|
<div title="Step Over" id="qr--modal-step" class="qr--modal-debugButton menu_button"></div>
|
||||||
|
<div title="Step Into" id="qr--modal-stepInto" class="qr--modal-debugButton menu_button"></div>
|
||||||
|
<div title="Step Out" id="qr--modal-stepOut" class="qr--modal-debugButton menu_button"></div>
|
||||||
|
<div title="Minimize" id="qr--modal-minimize" class="qr--modal-debugButton menu_button fa-solid fa-minimize"></div>
|
||||||
|
<div title="Maximize" id="qr--modal-maximize" class="qr--modal-debugButton menu_button fa-solid fa-maximize"></div>
|
||||||
|
</div>
|
||||||
|
<textarea rows="1" id="qr--modal-send_textarea" placeholder="Chat input for use with {{input}}" title="Chat input for use with {{input}}"></textarea>
|
||||||
|
<div id="qr--modal-debugState"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -11,6 +11,9 @@
|
|||||||
<label class="flex-container">
|
<label class="flex-container">
|
||||||
<input type="checkbox" id="qr--isCombined"><span data-i18n="Combine Quick Replies">Combine Quick Replies</span>
|
<input type="checkbox" id="qr--isCombined"><span data-i18n="Combine Quick Replies">Combine Quick Replies</span>
|
||||||
</label>
|
</label>
|
||||||
|
<label class="flex-container">
|
||||||
|
<input type="checkbox" id="qr--showPopoutButton"><span data-i18n="Show Popout Button">Show Popout Button</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
@ -60,10 +63,20 @@
|
|||||||
<label class="flex-container" id="qr--injectInputContainer">
|
<label class="flex-container" id="qr--injectInputContainer">
|
||||||
<input type="checkbox" id="qr--injectInput"> <span><span data-i18n="Inject user input automatically">Inject user input automatically</span> <small><span data-i18n="(if disabled, use ">(if disabled, use</span><code>{{input}}</code> <span data-i18n="macro for manual injection)">macro for manual injection)</span></small></span>
|
<input type="checkbox" id="qr--injectInput"> <span><span data-i18n="Inject user input automatically">Inject user input automatically</span> <small><span data-i18n="(if disabled, use ">(if disabled, use</span><code>{{input}}</code> <span data-i18n="macro for manual injection)">macro for manual injection)</span></small></span>
|
||||||
</label>
|
</label>
|
||||||
|
<div class="flex-container alignItemsCenter">
|
||||||
|
<toolcool-color-picker id="qr--color"></toolcool-color-picker>
|
||||||
|
<div class="menu_button" id="qr--colorClear">Clear</div>
|
||||||
|
<span data-i18n="Color">Color</span>
|
||||||
|
</div>
|
||||||
|
<label class="flex-container" id="qr--onlyBorderColorContainer">
|
||||||
|
<input type="checkbox" id="qr--onlyBorderColor"> <span data-i18n="Only apply color as accent">Only apply color as accent</span>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div id="qr--set-qrList" class="qr--qrList"></div>
|
<div id="qr--set-qrList" class="qr--qrList"></div>
|
||||||
<div class="qr--set-qrListActions">
|
<div class="qr--set-qrListActions">
|
||||||
<div class="qr--add menu_button menu_button_icon fa-solid fa-plus" id="qr--set-add" title="Add quick reply"></div>
|
<div class="qr--add menu_button menu_button_icon fa-solid fa-plus" id="qr--set-add" title="Add quick reply"></div>
|
||||||
|
<div class="qr--paste menu_button menu_button_icon fa-solid fa-paste" id="qr--set-paste" title="Paste quick reply from clipboard"></div>
|
||||||
|
<div class="qr--import menu_button menu_button_icon fa-solid fa-file-import" id="qr--set-importQr" title="Import quick reply from file"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -105,6 +105,7 @@ const loadSets = async () => {
|
|||||||
qr.executeOnAi = slot.autoExecute_botMessage ?? false;
|
qr.executeOnAi = slot.autoExecute_botMessage ?? false;
|
||||||
qr.executeOnChatChange = slot.autoExecute_chatLoad ?? false;
|
qr.executeOnChatChange = slot.autoExecute_chatLoad ?? false;
|
||||||
qr.executeOnGroupMemberDraft = slot.autoExecute_groupMemberDraft ?? false;
|
qr.executeOnGroupMemberDraft = slot.autoExecute_groupMemberDraft ?? false;
|
||||||
|
qr.executeOnNewChat = slot.autoExecute_newChat ?? false;
|
||||||
qr.automationId = slot.automationId ?? '';
|
qr.automationId = slot.automationId ?? '';
|
||||||
qr.contextList = (slot.contextMenu ?? []).map(it=>({
|
qr.contextList = (slot.contextMenu ?? []).map(it=>({
|
||||||
set: it.preset,
|
set: it.preset,
|
||||||
@ -176,7 +177,7 @@ const init = async () => {
|
|||||||
buttons.show();
|
buttons.show();
|
||||||
settings.onSave = ()=>buttons.refresh();
|
settings.onSave = ()=>buttons.refresh();
|
||||||
|
|
||||||
window['executeQuickReplyByName'] = async(name, args = {}) => {
|
window['executeQuickReplyByName'] = async(name, args = {}, options = {}) => {
|
||||||
let qr = [...settings.config.setList, ...(settings.chatConfig?.setList ?? [])]
|
let qr = [...settings.config.setList, ...(settings.chatConfig?.setList ?? [])]
|
||||||
.map(it=>it.set.qrList)
|
.map(it=>it.set.qrList)
|
||||||
.flat()
|
.flat()
|
||||||
@ -191,7 +192,7 @@ const init = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (qr && qr.onExecute) {
|
if (qr && qr.onExecute) {
|
||||||
return await qr.execute(args, false, true);
|
return await qr.execute(args, false, true, options);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`No Quick Reply found for "${name}".`);
|
throw new Error(`No Quick Reply found for "${name}".`);
|
||||||
}
|
}
|
||||||
@ -260,3 +261,8 @@ const onWIActivation = async (entries) => {
|
|||||||
await autoExec.handleWIActivation(entries);
|
await autoExec.handleWIActivation(entries);
|
||||||
};
|
};
|
||||||
eventSource.on(event_types.WORLD_INFO_ACTIVATED, (...args) => executeIfReadyElseQueue(onWIActivation, args));
|
eventSource.on(event_types.WORLD_INFO_ACTIVATED, (...args) => executeIfReadyElseQueue(onWIActivation, args));
|
||||||
|
|
||||||
|
const onNewChat = async () => {
|
||||||
|
await autoExec.handleNewChat();
|
||||||
|
};
|
||||||
|
eventSource.on(event_types.CHAT_CREATED, (...args) => executeIfReadyElseQueue(onNewChat, args));
|
||||||
|
769
public/scripts/extensions/quick-reply/lib/morphdom-esm.js
Normal file
769
public/scripts/extensions/quick-reply/lib/morphdom-esm.js
Normal file
@ -0,0 +1,769 @@
|
|||||||
|
var DOCUMENT_FRAGMENT_NODE = 11;
|
||||||
|
|
||||||
|
function morphAttrs(fromNode, toNode) {
|
||||||
|
var toNodeAttrs = toNode.attributes;
|
||||||
|
var attr;
|
||||||
|
var attrName;
|
||||||
|
var attrNamespaceURI;
|
||||||
|
var attrValue;
|
||||||
|
var fromValue;
|
||||||
|
|
||||||
|
// document-fragments dont have attributes so lets not do anything
|
||||||
|
if (toNode.nodeType === DOCUMENT_FRAGMENT_NODE || fromNode.nodeType === DOCUMENT_FRAGMENT_NODE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update attributes on original DOM element
|
||||||
|
for (var i = toNodeAttrs.length - 1; i >= 0; i--) {
|
||||||
|
attr = toNodeAttrs[i];
|
||||||
|
attrName = attr.name;
|
||||||
|
attrNamespaceURI = attr.namespaceURI;
|
||||||
|
attrValue = attr.value;
|
||||||
|
|
||||||
|
if (attrNamespaceURI) {
|
||||||
|
attrName = attr.localName || attrName;
|
||||||
|
fromValue = fromNode.getAttributeNS(attrNamespaceURI, attrName);
|
||||||
|
|
||||||
|
if (fromValue !== attrValue) {
|
||||||
|
if (attr.prefix === 'xmlns'){
|
||||||
|
attrName = attr.name; // It's not allowed to set an attribute with the XMLNS namespace without specifying the `xmlns` prefix
|
||||||
|
}
|
||||||
|
fromNode.setAttributeNS(attrNamespaceURI, attrName, attrValue);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fromValue = fromNode.getAttribute(attrName);
|
||||||
|
|
||||||
|
if (fromValue !== attrValue) {
|
||||||
|
fromNode.setAttribute(attrName, attrValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove any extra attributes found on the original DOM element that
|
||||||
|
// weren't found on the target element.
|
||||||
|
var fromNodeAttrs = fromNode.attributes;
|
||||||
|
|
||||||
|
for (var d = fromNodeAttrs.length - 1; d >= 0; d--) {
|
||||||
|
attr = fromNodeAttrs[d];
|
||||||
|
attrName = attr.name;
|
||||||
|
attrNamespaceURI = attr.namespaceURI;
|
||||||
|
|
||||||
|
if (attrNamespaceURI) {
|
||||||
|
attrName = attr.localName || attrName;
|
||||||
|
|
||||||
|
if (!toNode.hasAttributeNS(attrNamespaceURI, attrName)) {
|
||||||
|
fromNode.removeAttributeNS(attrNamespaceURI, attrName);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!toNode.hasAttribute(attrName)) {
|
||||||
|
fromNode.removeAttribute(attrName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var range; // Create a range object for efficently rendering strings to elements.
|
||||||
|
var NS_XHTML = 'http://www.w3.org/1999/xhtml';
|
||||||
|
|
||||||
|
var doc = typeof document === 'undefined' ? undefined : document;
|
||||||
|
var HAS_TEMPLATE_SUPPORT = !!doc && 'content' in doc.createElement('template');
|
||||||
|
var HAS_RANGE_SUPPORT = !!doc && doc.createRange && 'createContextualFragment' in doc.createRange();
|
||||||
|
|
||||||
|
function createFragmentFromTemplate(str) {
|
||||||
|
var template = doc.createElement('template');
|
||||||
|
template.innerHTML = str;
|
||||||
|
return template.content.childNodes[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
function createFragmentFromRange(str) {
|
||||||
|
if (!range) {
|
||||||
|
range = doc.createRange();
|
||||||
|
range.selectNode(doc.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
var fragment = range.createContextualFragment(str);
|
||||||
|
return fragment.childNodes[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
function createFragmentFromWrap(str) {
|
||||||
|
var fragment = doc.createElement('body');
|
||||||
|
fragment.innerHTML = str;
|
||||||
|
return fragment.childNodes[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is about the same
|
||||||
|
* var html = new DOMParser().parseFromString(str, 'text/html');
|
||||||
|
* return html.body.firstChild;
|
||||||
|
*
|
||||||
|
* @method toElement
|
||||||
|
* @param {String} str
|
||||||
|
*/
|
||||||
|
function toElement(str) {
|
||||||
|
str = str.trim();
|
||||||
|
if (HAS_TEMPLATE_SUPPORT) {
|
||||||
|
// avoid restrictions on content for things like `<tr><th>Hi</th></tr>` which
|
||||||
|
// createContextualFragment doesn't support
|
||||||
|
// <template> support not available in IE
|
||||||
|
return createFragmentFromTemplate(str);
|
||||||
|
} else if (HAS_RANGE_SUPPORT) {
|
||||||
|
return createFragmentFromRange(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
return createFragmentFromWrap(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if two node's names are the same.
|
||||||
|
*
|
||||||
|
* NOTE: We don't bother checking `namespaceURI` because you will never find two HTML elements with the same
|
||||||
|
* nodeName and different namespace URIs.
|
||||||
|
*
|
||||||
|
* @param {Element} a
|
||||||
|
* @param {Element} b The target element
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
function compareNodeNames(fromEl, toEl) {
|
||||||
|
var fromNodeName = fromEl.nodeName;
|
||||||
|
var toNodeName = toEl.nodeName;
|
||||||
|
var fromCodeStart, toCodeStart;
|
||||||
|
|
||||||
|
if (fromNodeName === toNodeName) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fromCodeStart = fromNodeName.charCodeAt(0);
|
||||||
|
toCodeStart = toNodeName.charCodeAt(0);
|
||||||
|
|
||||||
|
// If the target element is a virtual DOM node or SVG node then we may
|
||||||
|
// need to normalize the tag name before comparing. Normal HTML elements that are
|
||||||
|
// in the "http://www.w3.org/1999/xhtml"
|
||||||
|
// are converted to upper case
|
||||||
|
if (fromCodeStart <= 90 && toCodeStart >= 97) { // from is upper and to is lower
|
||||||
|
return fromNodeName === toNodeName.toUpperCase();
|
||||||
|
} else if (toCodeStart <= 90 && fromCodeStart >= 97) { // to is upper and from is lower
|
||||||
|
return toNodeName === fromNodeName.toUpperCase();
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an element, optionally with a known namespace URI.
|
||||||
|
*
|
||||||
|
* @param {string} name the element name, e.g. 'div' or 'svg'
|
||||||
|
* @param {string} [namespaceURI] the element's namespace URI, i.e. the value of
|
||||||
|
* its `xmlns` attribute or its inferred namespace.
|
||||||
|
*
|
||||||
|
* @return {Element}
|
||||||
|
*/
|
||||||
|
function createElementNS(name, namespaceURI) {
|
||||||
|
return !namespaceURI || namespaceURI === NS_XHTML ?
|
||||||
|
doc.createElement(name) :
|
||||||
|
doc.createElementNS(namespaceURI, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copies the children of one DOM element to another DOM element
|
||||||
|
*/
|
||||||
|
function moveChildren(fromEl, toEl) {
|
||||||
|
var curChild = fromEl.firstChild;
|
||||||
|
while (curChild) {
|
||||||
|
var nextChild = curChild.nextSibling;
|
||||||
|
toEl.appendChild(curChild);
|
||||||
|
curChild = nextChild;
|
||||||
|
}
|
||||||
|
return toEl;
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncBooleanAttrProp(fromEl, toEl, name) {
|
||||||
|
if (fromEl[name] !== toEl[name]) {
|
||||||
|
fromEl[name] = toEl[name];
|
||||||
|
if (fromEl[name]) {
|
||||||
|
fromEl.setAttribute(name, '');
|
||||||
|
} else {
|
||||||
|
fromEl.removeAttribute(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var specialElHandlers = {
|
||||||
|
OPTION: function(fromEl, toEl) {
|
||||||
|
var parentNode = fromEl.parentNode;
|
||||||
|
if (parentNode) {
|
||||||
|
var parentName = parentNode.nodeName.toUpperCase();
|
||||||
|
if (parentName === 'OPTGROUP') {
|
||||||
|
parentNode = parentNode.parentNode;
|
||||||
|
parentName = parentNode && parentNode.nodeName.toUpperCase();
|
||||||
|
}
|
||||||
|
if (parentName === 'SELECT' && !parentNode.hasAttribute('multiple')) {
|
||||||
|
if (fromEl.hasAttribute('selected') && !toEl.selected) {
|
||||||
|
// Workaround for MS Edge bug where the 'selected' attribute can only be
|
||||||
|
// removed if set to a non-empty value:
|
||||||
|
// https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/12087679/
|
||||||
|
fromEl.setAttribute('selected', 'selected');
|
||||||
|
fromEl.removeAttribute('selected');
|
||||||
|
}
|
||||||
|
// We have to reset select element's selectedIndex to -1, otherwise setting
|
||||||
|
// fromEl.selected using the syncBooleanAttrProp below has no effect.
|
||||||
|
// The correct selectedIndex will be set in the SELECT special handler below.
|
||||||
|
parentNode.selectedIndex = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
syncBooleanAttrProp(fromEl, toEl, 'selected');
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* The "value" attribute is special for the <input> element since it sets
|
||||||
|
* the initial value. Changing the "value" attribute without changing the
|
||||||
|
* "value" property will have no effect since it is only used to the set the
|
||||||
|
* initial value. Similar for the "checked" attribute, and "disabled".
|
||||||
|
*/
|
||||||
|
INPUT: function(fromEl, toEl) {
|
||||||
|
syncBooleanAttrProp(fromEl, toEl, 'checked');
|
||||||
|
syncBooleanAttrProp(fromEl, toEl, 'disabled');
|
||||||
|
|
||||||
|
if (fromEl.value !== toEl.value) {
|
||||||
|
fromEl.value = toEl.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!toEl.hasAttribute('value')) {
|
||||||
|
fromEl.removeAttribute('value');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
TEXTAREA: function(fromEl, toEl) {
|
||||||
|
var newValue = toEl.value;
|
||||||
|
if (fromEl.value !== newValue) {
|
||||||
|
fromEl.value = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var firstChild = fromEl.firstChild;
|
||||||
|
if (firstChild) {
|
||||||
|
// Needed for IE. Apparently IE sets the placeholder as the
|
||||||
|
// node value and vise versa. This ignores an empty update.
|
||||||
|
var oldValue = firstChild.nodeValue;
|
||||||
|
|
||||||
|
if (oldValue == newValue || (!newValue && oldValue == fromEl.placeholder)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
firstChild.nodeValue = newValue;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
SELECT: function(fromEl, toEl) {
|
||||||
|
if (!toEl.hasAttribute('multiple')) {
|
||||||
|
var selectedIndex = -1;
|
||||||
|
var i = 0;
|
||||||
|
// We have to loop through children of fromEl, not toEl since nodes can be moved
|
||||||
|
// from toEl to fromEl directly when morphing.
|
||||||
|
// At the time this special handler is invoked, all children have already been morphed
|
||||||
|
// and appended to / removed from fromEl, so using fromEl here is safe and correct.
|
||||||
|
var curChild = fromEl.firstChild;
|
||||||
|
var optgroup;
|
||||||
|
var nodeName;
|
||||||
|
while(curChild) {
|
||||||
|
nodeName = curChild.nodeName && curChild.nodeName.toUpperCase();
|
||||||
|
if (nodeName === 'OPTGROUP') {
|
||||||
|
optgroup = curChild;
|
||||||
|
curChild = optgroup.firstChild;
|
||||||
|
} else {
|
||||||
|
if (nodeName === 'OPTION') {
|
||||||
|
if (curChild.hasAttribute('selected')) {
|
||||||
|
selectedIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
curChild = curChild.nextSibling;
|
||||||
|
if (!curChild && optgroup) {
|
||||||
|
curChild = optgroup.nextSibling;
|
||||||
|
optgroup = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fromEl.selectedIndex = selectedIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var ELEMENT_NODE = 1;
|
||||||
|
var DOCUMENT_FRAGMENT_NODE$1 = 11;
|
||||||
|
var TEXT_NODE = 3;
|
||||||
|
var COMMENT_NODE = 8;
|
||||||
|
|
||||||
|
function noop() {}
|
||||||
|
|
||||||
|
function defaultGetNodeKey(node) {
|
||||||
|
if (node) {
|
||||||
|
return (node.getAttribute && node.getAttribute('id')) || node.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function morphdomFactory(morphAttrs) {
|
||||||
|
|
||||||
|
return function morphdom(fromNode, toNode, options) {
|
||||||
|
if (!options) {
|
||||||
|
options = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof toNode === 'string') {
|
||||||
|
if (fromNode.nodeName === '#document' || fromNode.nodeName === 'HTML' || fromNode.nodeName === 'BODY') {
|
||||||
|
var toNodeHtml = toNode;
|
||||||
|
toNode = doc.createElement('html');
|
||||||
|
toNode.innerHTML = toNodeHtml;
|
||||||
|
} else {
|
||||||
|
toNode = toElement(toNode);
|
||||||
|
}
|
||||||
|
} else if (toNode.nodeType === DOCUMENT_FRAGMENT_NODE$1) {
|
||||||
|
toNode = toNode.firstElementChild;
|
||||||
|
}
|
||||||
|
|
||||||
|
var getNodeKey = options.getNodeKey || defaultGetNodeKey;
|
||||||
|
var onBeforeNodeAdded = options.onBeforeNodeAdded || noop;
|
||||||
|
var onNodeAdded = options.onNodeAdded || noop;
|
||||||
|
var onBeforeElUpdated = options.onBeforeElUpdated || noop;
|
||||||
|
var onElUpdated = options.onElUpdated || noop;
|
||||||
|
var onBeforeNodeDiscarded = options.onBeforeNodeDiscarded || noop;
|
||||||
|
var onNodeDiscarded = options.onNodeDiscarded || noop;
|
||||||
|
var onBeforeElChildrenUpdated = options.onBeforeElChildrenUpdated || noop;
|
||||||
|
var skipFromChildren = options.skipFromChildren || noop;
|
||||||
|
var addChild = options.addChild || function(parent, child){ return parent.appendChild(child); };
|
||||||
|
var childrenOnly = options.childrenOnly === true;
|
||||||
|
|
||||||
|
// This object is used as a lookup to quickly find all keyed elements in the original DOM tree.
|
||||||
|
var fromNodesLookup = Object.create(null);
|
||||||
|
var keyedRemovalList = [];
|
||||||
|
|
||||||
|
function addKeyedRemoval(key) {
|
||||||
|
keyedRemovalList.push(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
function walkDiscardedChildNodes(node, skipKeyedNodes) {
|
||||||
|
if (node.nodeType === ELEMENT_NODE) {
|
||||||
|
var curChild = node.firstChild;
|
||||||
|
while (curChild) {
|
||||||
|
|
||||||
|
var key = undefined;
|
||||||
|
|
||||||
|
if (skipKeyedNodes && (key = getNodeKey(curChild))) {
|
||||||
|
// If we are skipping keyed nodes then we add the key
|
||||||
|
// to a list so that it can be handled at the very end.
|
||||||
|
addKeyedRemoval(key);
|
||||||
|
} else {
|
||||||
|
// Only report the node as discarded if it is not keyed. We do this because
|
||||||
|
// at the end we loop through all keyed elements that were unmatched
|
||||||
|
// and then discard them in one final pass.
|
||||||
|
onNodeDiscarded(curChild);
|
||||||
|
if (curChild.firstChild) {
|
||||||
|
walkDiscardedChildNodes(curChild, skipKeyedNodes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
curChild = curChild.nextSibling;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a DOM node out of the original DOM
|
||||||
|
*
|
||||||
|
* @param {Node} node The node to remove
|
||||||
|
* @param {Node} parentNode The nodes parent
|
||||||
|
* @param {Boolean} skipKeyedNodes If true then elements with keys will be skipped and not discarded.
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
function removeNode(node, parentNode, skipKeyedNodes) {
|
||||||
|
if (onBeforeNodeDiscarded(node) === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parentNode) {
|
||||||
|
parentNode.removeChild(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
onNodeDiscarded(node);
|
||||||
|
walkDiscardedChildNodes(node, skipKeyedNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// // TreeWalker implementation is no faster, but keeping this around in case this changes in the future
|
||||||
|
// function indexTree(root) {
|
||||||
|
// var treeWalker = document.createTreeWalker(
|
||||||
|
// root,
|
||||||
|
// NodeFilter.SHOW_ELEMENT);
|
||||||
|
//
|
||||||
|
// var el;
|
||||||
|
// while((el = treeWalker.nextNode())) {
|
||||||
|
// var key = getNodeKey(el);
|
||||||
|
// if (key) {
|
||||||
|
// fromNodesLookup[key] = el;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // NodeIterator implementation is no faster, but keeping this around in case this changes in the future
|
||||||
|
//
|
||||||
|
// function indexTree(node) {
|
||||||
|
// var nodeIterator = document.createNodeIterator(node, NodeFilter.SHOW_ELEMENT);
|
||||||
|
// var el;
|
||||||
|
// while((el = nodeIterator.nextNode())) {
|
||||||
|
// var key = getNodeKey(el);
|
||||||
|
// if (key) {
|
||||||
|
// fromNodesLookup[key] = el;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
function indexTree(node) {
|
||||||
|
if (node.nodeType === ELEMENT_NODE || node.nodeType === DOCUMENT_FRAGMENT_NODE$1) {
|
||||||
|
var curChild = node.firstChild;
|
||||||
|
while (curChild) {
|
||||||
|
var key = getNodeKey(curChild);
|
||||||
|
if (key) {
|
||||||
|
fromNodesLookup[key] = curChild;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk recursively
|
||||||
|
indexTree(curChild);
|
||||||
|
|
||||||
|
curChild = curChild.nextSibling;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
indexTree(fromNode);
|
||||||
|
|
||||||
|
function handleNodeAdded(el) {
|
||||||
|
onNodeAdded(el);
|
||||||
|
|
||||||
|
var curChild = el.firstChild;
|
||||||
|
while (curChild) {
|
||||||
|
var nextSibling = curChild.nextSibling;
|
||||||
|
|
||||||
|
var key = getNodeKey(curChild);
|
||||||
|
if (key) {
|
||||||
|
var unmatchedFromEl = fromNodesLookup[key];
|
||||||
|
// if we find a duplicate #id node in cache, replace `el` with cache value
|
||||||
|
// and morph it to the child node.
|
||||||
|
if (unmatchedFromEl && compareNodeNames(curChild, unmatchedFromEl)) {
|
||||||
|
curChild.parentNode.replaceChild(unmatchedFromEl, curChild);
|
||||||
|
morphEl(unmatchedFromEl, curChild);
|
||||||
|
} else {
|
||||||
|
handleNodeAdded(curChild);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// recursively call for curChild and it's children to see if we find something in
|
||||||
|
// fromNodesLookup
|
||||||
|
handleNodeAdded(curChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
curChild = nextSibling;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanupFromEl(fromEl, curFromNodeChild, curFromNodeKey) {
|
||||||
|
// We have processed all of the "to nodes". If curFromNodeChild is
|
||||||
|
// non-null then we still have some from nodes left over that need
|
||||||
|
// to be removed
|
||||||
|
while (curFromNodeChild) {
|
||||||
|
var fromNextSibling = curFromNodeChild.nextSibling;
|
||||||
|
if ((curFromNodeKey = getNodeKey(curFromNodeChild))) {
|
||||||
|
// Since the node is keyed it might be matched up later so we defer
|
||||||
|
// the actual removal to later
|
||||||
|
addKeyedRemoval(curFromNodeKey);
|
||||||
|
} else {
|
||||||
|
// NOTE: we skip nested keyed nodes from being removed since there is
|
||||||
|
// still a chance they will be matched up later
|
||||||
|
removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */);
|
||||||
|
}
|
||||||
|
curFromNodeChild = fromNextSibling;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function morphEl(fromEl, toEl, childrenOnly) {
|
||||||
|
var toElKey = getNodeKey(toEl);
|
||||||
|
|
||||||
|
if (toElKey) {
|
||||||
|
// If an element with an ID is being morphed then it will be in the final
|
||||||
|
// DOM so clear it out of the saved elements collection
|
||||||
|
delete fromNodesLookup[toElKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!childrenOnly) {
|
||||||
|
// optional
|
||||||
|
var beforeUpdateResult = onBeforeElUpdated(fromEl, toEl);
|
||||||
|
if (beforeUpdateResult === false) {
|
||||||
|
return;
|
||||||
|
} else if (beforeUpdateResult instanceof HTMLElement) {
|
||||||
|
fromEl = beforeUpdateResult;
|
||||||
|
// reindex the new fromEl in case it's not in the same
|
||||||
|
// tree as the original fromEl
|
||||||
|
// (Phoenix LiveView sometimes returns a cloned tree,
|
||||||
|
// but keyed lookups would still point to the original tree)
|
||||||
|
indexTree(fromEl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// update attributes on original DOM element first
|
||||||
|
morphAttrs(fromEl, toEl);
|
||||||
|
// optional
|
||||||
|
onElUpdated(fromEl);
|
||||||
|
|
||||||
|
if (onBeforeElChildrenUpdated(fromEl, toEl) === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fromEl.nodeName !== 'TEXTAREA') {
|
||||||
|
morphChildren(fromEl, toEl);
|
||||||
|
} else {
|
||||||
|
specialElHandlers.TEXTAREA(fromEl, toEl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function morphChildren(fromEl, toEl) {
|
||||||
|
var skipFrom = skipFromChildren(fromEl, toEl);
|
||||||
|
var curToNodeChild = toEl.firstChild;
|
||||||
|
var curFromNodeChild = fromEl.firstChild;
|
||||||
|
var curToNodeKey;
|
||||||
|
var curFromNodeKey;
|
||||||
|
|
||||||
|
var fromNextSibling;
|
||||||
|
var toNextSibling;
|
||||||
|
var matchingFromEl;
|
||||||
|
|
||||||
|
// walk the children
|
||||||
|
outer: while (curToNodeChild) {
|
||||||
|
toNextSibling = curToNodeChild.nextSibling;
|
||||||
|
curToNodeKey = getNodeKey(curToNodeChild);
|
||||||
|
|
||||||
|
// walk the fromNode children all the way through
|
||||||
|
while (!skipFrom && curFromNodeChild) {
|
||||||
|
fromNextSibling = curFromNodeChild.nextSibling;
|
||||||
|
|
||||||
|
if (curToNodeChild.isSameNode && curToNodeChild.isSameNode(curFromNodeChild)) {
|
||||||
|
curToNodeChild = toNextSibling;
|
||||||
|
curFromNodeChild = fromNextSibling;
|
||||||
|
continue outer;
|
||||||
|
}
|
||||||
|
|
||||||
|
curFromNodeKey = getNodeKey(curFromNodeChild);
|
||||||
|
|
||||||
|
var curFromNodeType = curFromNodeChild.nodeType;
|
||||||
|
|
||||||
|
// this means if the curFromNodeChild doesnt have a match with the curToNodeChild
|
||||||
|
var isCompatible = undefined;
|
||||||
|
|
||||||
|
if (curFromNodeType === curToNodeChild.nodeType) {
|
||||||
|
if (curFromNodeType === ELEMENT_NODE) {
|
||||||
|
// Both nodes being compared are Element nodes
|
||||||
|
|
||||||
|
if (curToNodeKey) {
|
||||||
|
// The target node has a key so we want to match it up with the correct element
|
||||||
|
// in the original DOM tree
|
||||||
|
if (curToNodeKey !== curFromNodeKey) {
|
||||||
|
// The current element in the original DOM tree does not have a matching key so
|
||||||
|
// let's check our lookup to see if there is a matching element in the original
|
||||||
|
// DOM tree
|
||||||
|
if ((matchingFromEl = fromNodesLookup[curToNodeKey])) {
|
||||||
|
if (fromNextSibling === matchingFromEl) {
|
||||||
|
// Special case for single element removals. To avoid removing the original
|
||||||
|
// DOM node out of the tree (since that can break CSS transitions, etc.),
|
||||||
|
// we will instead discard the current node and wait until the next
|
||||||
|
// iteration to properly match up the keyed target element with its matching
|
||||||
|
// element in the original tree
|
||||||
|
isCompatible = false;
|
||||||
|
} else {
|
||||||
|
// We found a matching keyed element somewhere in the original DOM tree.
|
||||||
|
// Let's move the original DOM node into the current position and morph
|
||||||
|
// it.
|
||||||
|
|
||||||
|
// NOTE: We use insertBefore instead of replaceChild because we want to go through
|
||||||
|
// the `removeNode()` function for the node that is being discarded so that
|
||||||
|
// all lifecycle hooks are correctly invoked
|
||||||
|
fromEl.insertBefore(matchingFromEl, curFromNodeChild);
|
||||||
|
|
||||||
|
// fromNextSibling = curFromNodeChild.nextSibling;
|
||||||
|
|
||||||
|
if (curFromNodeKey) {
|
||||||
|
// Since the node is keyed it might be matched up later so we defer
|
||||||
|
// the actual removal to later
|
||||||
|
addKeyedRemoval(curFromNodeKey);
|
||||||
|
} else {
|
||||||
|
// NOTE: we skip nested keyed nodes from being removed since there is
|
||||||
|
// still a chance they will be matched up later
|
||||||
|
removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */);
|
||||||
|
}
|
||||||
|
|
||||||
|
curFromNodeChild = matchingFromEl;
|
||||||
|
curFromNodeKey = getNodeKey(curFromNodeChild);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// The nodes are not compatible since the "to" node has a key and there
|
||||||
|
// is no matching keyed node in the source tree
|
||||||
|
isCompatible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (curFromNodeKey) {
|
||||||
|
// The original has a key
|
||||||
|
isCompatible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
isCompatible = isCompatible !== false && compareNodeNames(curFromNodeChild, curToNodeChild);
|
||||||
|
if (isCompatible) {
|
||||||
|
// We found compatible DOM elements so transform
|
||||||
|
// the current "from" node to match the current
|
||||||
|
// target DOM node.
|
||||||
|
// MORPH
|
||||||
|
morphEl(curFromNodeChild, curToNodeChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (curFromNodeType === TEXT_NODE || curFromNodeType == COMMENT_NODE) {
|
||||||
|
// Both nodes being compared are Text or Comment nodes
|
||||||
|
isCompatible = true;
|
||||||
|
// Simply update nodeValue on the original node to
|
||||||
|
// change the text value
|
||||||
|
if (curFromNodeChild.nodeValue !== curToNodeChild.nodeValue) {
|
||||||
|
curFromNodeChild.nodeValue = curToNodeChild.nodeValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCompatible) {
|
||||||
|
// Advance both the "to" child and the "from" child since we found a match
|
||||||
|
// Nothing else to do as we already recursively called morphChildren above
|
||||||
|
curToNodeChild = toNextSibling;
|
||||||
|
curFromNodeChild = fromNextSibling;
|
||||||
|
continue outer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No compatible match so remove the old node from the DOM and continue trying to find a
|
||||||
|
// match in the original DOM. However, we only do this if the from node is not keyed
|
||||||
|
// since it is possible that a keyed node might match up with a node somewhere else in the
|
||||||
|
// target tree and we don't want to discard it just yet since it still might find a
|
||||||
|
// home in the final DOM tree. After everything is done we will remove any keyed nodes
|
||||||
|
// that didn't find a home
|
||||||
|
if (curFromNodeKey) {
|
||||||
|
// Since the node is keyed it might be matched up later so we defer
|
||||||
|
// the actual removal to later
|
||||||
|
addKeyedRemoval(curFromNodeKey);
|
||||||
|
} else {
|
||||||
|
// NOTE: we skip nested keyed nodes from being removed since there is
|
||||||
|
// still a chance they will be matched up later
|
||||||
|
removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */);
|
||||||
|
}
|
||||||
|
|
||||||
|
curFromNodeChild = fromNextSibling;
|
||||||
|
} // END: while(curFromNodeChild) {}
|
||||||
|
|
||||||
|
// If we got this far then we did not find a candidate match for
|
||||||
|
// our "to node" and we exhausted all of the children "from"
|
||||||
|
// nodes. Therefore, we will just append the current "to" node
|
||||||
|
// to the end
|
||||||
|
if (curToNodeKey && (matchingFromEl = fromNodesLookup[curToNodeKey]) && compareNodeNames(matchingFromEl, curToNodeChild)) {
|
||||||
|
// MORPH
|
||||||
|
if(!skipFrom){ addChild(fromEl, matchingFromEl); }
|
||||||
|
morphEl(matchingFromEl, curToNodeChild);
|
||||||
|
} else {
|
||||||
|
var onBeforeNodeAddedResult = onBeforeNodeAdded(curToNodeChild);
|
||||||
|
if (onBeforeNodeAddedResult !== false) {
|
||||||
|
if (onBeforeNodeAddedResult) {
|
||||||
|
curToNodeChild = onBeforeNodeAddedResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curToNodeChild.actualize) {
|
||||||
|
curToNodeChild = curToNodeChild.actualize(fromEl.ownerDocument || doc);
|
||||||
|
}
|
||||||
|
addChild(fromEl, curToNodeChild);
|
||||||
|
handleNodeAdded(curToNodeChild);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
curToNodeChild = toNextSibling;
|
||||||
|
curFromNodeChild = fromNextSibling;
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanupFromEl(fromEl, curFromNodeChild, curFromNodeKey);
|
||||||
|
|
||||||
|
var specialElHandler = specialElHandlers[fromEl.nodeName];
|
||||||
|
if (specialElHandler) {
|
||||||
|
specialElHandler(fromEl, toEl);
|
||||||
|
}
|
||||||
|
} // END: morphChildren(...)
|
||||||
|
|
||||||
|
var morphedNode = fromNode;
|
||||||
|
var morphedNodeType = morphedNode.nodeType;
|
||||||
|
var toNodeType = toNode.nodeType;
|
||||||
|
|
||||||
|
if (!childrenOnly) {
|
||||||
|
// Handle the case where we are given two DOM nodes that are not
|
||||||
|
// compatible (e.g. <div> --> <span> or <div> --> TEXT)
|
||||||
|
if (morphedNodeType === ELEMENT_NODE) {
|
||||||
|
if (toNodeType === ELEMENT_NODE) {
|
||||||
|
if (!compareNodeNames(fromNode, toNode)) {
|
||||||
|
onNodeDiscarded(fromNode);
|
||||||
|
morphedNode = moveChildren(fromNode, createElementNS(toNode.nodeName, toNode.namespaceURI));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Going from an element node to a text node
|
||||||
|
morphedNode = toNode;
|
||||||
|
}
|
||||||
|
} else if (morphedNodeType === TEXT_NODE || morphedNodeType === COMMENT_NODE) { // Text or comment node
|
||||||
|
if (toNodeType === morphedNodeType) {
|
||||||
|
if (morphedNode.nodeValue !== toNode.nodeValue) {
|
||||||
|
morphedNode.nodeValue = toNode.nodeValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return morphedNode;
|
||||||
|
} else {
|
||||||
|
// Text node to something else
|
||||||
|
morphedNode = toNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (morphedNode === toNode) {
|
||||||
|
// The "to node" was not compatible with the "from node" so we had to
|
||||||
|
// toss out the "from node" and use the "to node"
|
||||||
|
onNodeDiscarded(fromNode);
|
||||||
|
} else {
|
||||||
|
if (toNode.isSameNode && toNode.isSameNode(morphedNode)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
morphEl(morphedNode, toNode, childrenOnly);
|
||||||
|
|
||||||
|
// We now need to loop over any keyed nodes that might need to be
|
||||||
|
// removed. We only do the removal if we know that the keyed node
|
||||||
|
// never found a match. When a keyed node is matched up we remove
|
||||||
|
// it out of fromNodesLookup and we use fromNodesLookup to determine
|
||||||
|
// if a keyed node has been matched up or not
|
||||||
|
if (keyedRemovalList) {
|
||||||
|
for (var i=0, len=keyedRemovalList.length; i<len; i++) {
|
||||||
|
var elToRemove = fromNodesLookup[keyedRemovalList[i]];
|
||||||
|
if (elToRemove) {
|
||||||
|
removeNode(elToRemove, elToRemove.parentNode, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!childrenOnly && morphedNode !== fromNode && fromNode.parentNode) {
|
||||||
|
if (morphedNode.actualize) {
|
||||||
|
morphedNode = morphedNode.actualize(fromNode.ownerDocument || doc);
|
||||||
|
}
|
||||||
|
// If we had to swap out the from node with a new node because the old
|
||||||
|
// node was not compatible with the target node then we need to
|
||||||
|
// replace the old DOM node in the original DOM tree. This is only
|
||||||
|
// possible if the original DOM node was part of a DOM tree which
|
||||||
|
// we know is the case if it has a parent node.
|
||||||
|
fromNode.parentNode.replaceChild(morphedNode, fromNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return morphedNode;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var morphdom = morphdomFactory(morphAttrs);
|
||||||
|
|
||||||
|
export default morphdom;
|
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) Patrick Steele-Idem <pnidem@gmail.com> (psteeleidem.com)
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
@ -83,6 +83,15 @@ export class AutoExecuteHandler {
|
|||||||
await this.performAutoExecute(qrList);
|
await this.performAutoExecute(qrList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async handleNewChat() {
|
||||||
|
if (!this.checkExecute()) return;
|
||||||
|
const qrList = [
|
||||||
|
...this.settings.config.setList.map(link=>link.set.qrList.filter(qr=>qr.executeOnNewChat)).flat(),
|
||||||
|
...(this.settings.chatConfig?.setList?.map(link=>link.set.qrList.filter(qr=>qr.executeOnNewChat))?.flat() ?? []),
|
||||||
|
];
|
||||||
|
await this.performAutoExecute(qrList);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {any[]} entries Set of activated entries
|
* @param {any[]} entries Set of activated entries
|
||||||
*/
|
*/
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -60,7 +60,12 @@ export class QuickReplyConfig {
|
|||||||
/**@type {HTMLElement}*/
|
/**@type {HTMLElement}*/
|
||||||
this.setListDom = root.querySelector('.qr--setList');
|
this.setListDom = root.querySelector('.qr--setList');
|
||||||
root.querySelector('.qr--setListAdd').addEventListener('click', ()=>{
|
root.querySelector('.qr--setListAdd').addEventListener('click', ()=>{
|
||||||
this.addSet(QuickReplySet.list[0]);
|
const newSet = QuickReplySet.list.find(qr=>!this.setList.find(qrl=>qrl.set == qr));
|
||||||
|
if (newSet) {
|
||||||
|
this.addSet(newSet);
|
||||||
|
} else {
|
||||||
|
toastr.warning('All existing QR Sets have already been added.');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
this.updateSetListDom();
|
this.updateSetListDom();
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import { getRequestHeaders, substituteParams } from '../../../../script.js';
|
import { getRequestHeaders, substituteParams } from '../../../../script.js';
|
||||||
|
import { Popup, POPUP_RESULT, POPUP_TYPE } from '../../../popup.js';
|
||||||
import { executeSlashCommands, executeSlashCommandsOnChatInput, executeSlashCommandsWithOptions } from '../../../slash-commands.js';
|
import { executeSlashCommands, executeSlashCommandsOnChatInput, executeSlashCommandsWithOptions } from '../../../slash-commands.js';
|
||||||
|
import { SlashCommandParser } from '../../../slash-commands/SlashCommandParser.js';
|
||||||
import { SlashCommandScope } from '../../../slash-commands/SlashCommandScope.js';
|
import { SlashCommandScope } from '../../../slash-commands/SlashCommandScope.js';
|
||||||
import { debounceAsync, warn } from '../index.js';
|
import { debounceAsync, log, warn } from '../index.js';
|
||||||
import { QuickReply } from './QuickReply.js';
|
import { QuickReply } from './QuickReply.js';
|
||||||
|
|
||||||
export class QuickReplySet {
|
export class QuickReplySet {
|
||||||
@ -16,7 +18,7 @@ export class QuickReplySet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {String} name - name of the QuickReplySet
|
* @param {string} name - name of the QuickReplySet
|
||||||
*/
|
*/
|
||||||
static get(name) {
|
static get(name) {
|
||||||
return this.list.find(it=>it.name == name);
|
return this.list.find(it=>it.name == name);
|
||||||
@ -25,17 +27,19 @@ export class QuickReplySet {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**@type {String}*/ name;
|
/**@type {string}*/ name;
|
||||||
/**@type {Boolean}*/ disableSend = false;
|
/**@type {boolean}*/ disableSend = false;
|
||||||
/**@type {Boolean}*/ placeBeforeInput = false;
|
/**@type {boolean}*/ placeBeforeInput = false;
|
||||||
/**@type {Boolean}*/ injectInput = false;
|
/**@type {boolean}*/ injectInput = false;
|
||||||
|
/**@type {string}*/ color = 'transparent';
|
||||||
|
/**@type {boolean}*/ onlyBorderColor = false;
|
||||||
/**@type {QuickReply[]}*/ qrList = [];
|
/**@type {QuickReply[]}*/ qrList = [];
|
||||||
|
|
||||||
/**@type {Number}*/ idIndex = 0;
|
/**@type {number}*/ idIndex = 0;
|
||||||
|
|
||||||
/**@type {Boolean}*/ isDeleted = false;
|
/**@type {boolean}*/ isDeleted = false;
|
||||||
|
|
||||||
/**@type {Function}*/ save;
|
/**@type {function}*/ save;
|
||||||
|
|
||||||
/**@type {HTMLElement}*/ dom;
|
/**@type {HTMLElement}*/ dom;
|
||||||
/**@type {HTMLElement}*/ settingsDom;
|
/**@type {HTMLElement}*/ settingsDom;
|
||||||
@ -64,6 +68,7 @@ export class QuickReplySet {
|
|||||||
const root = document.createElement('div'); {
|
const root = document.createElement('div'); {
|
||||||
this.dom = root;
|
this.dom = root;
|
||||||
root.classList.add('qr--buttons');
|
root.classList.add('qr--buttons');
|
||||||
|
this.updateColor();
|
||||||
this.qrList.filter(qr=>!qr.isHidden).forEach(qr=>{
|
this.qrList.filter(qr=>!qr.isHidden).forEach(qr=>{
|
||||||
root.append(qr.render());
|
root.append(qr.render());
|
||||||
});
|
});
|
||||||
@ -78,6 +83,22 @@ export class QuickReplySet {
|
|||||||
this.dom.append(qr.render());
|
this.dom.append(qr.render());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
updateColor() {
|
||||||
|
if (!this.dom) return;
|
||||||
|
if (this.color && this.color != 'transparent') {
|
||||||
|
this.dom.style.setProperty('--qr--color', this.color);
|
||||||
|
this.dom.classList.add('qr--color');
|
||||||
|
if (this.onlyBorderColor) {
|
||||||
|
this.dom.classList.add('qr--borderColor');
|
||||||
|
} else {
|
||||||
|
this.dom.classList.remove('qr--borderColor');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.dom.style.setProperty('--qr--color', 'transparent');
|
||||||
|
this.dom.classList.remove('qr--color');
|
||||||
|
this.dom.classList.remove('qr--borderColor');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -93,6 +114,11 @@ export class QuickReplySet {
|
|||||||
}
|
}
|
||||||
return this.settingsDom;
|
return this.settingsDom;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {QuickReply} qr
|
||||||
|
* @param {number} idx
|
||||||
|
*/
|
||||||
renderSettingsItem(qr, idx) {
|
renderSettingsItem(qr, idx) {
|
||||||
this.settingsDom.append(qr.renderSettings(idx));
|
this.settingsDom.append(qr.renderSettings(idx));
|
||||||
}
|
}
|
||||||
@ -100,6 +126,18 @@ export class QuickReplySet {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {QuickReply} qr
|
||||||
|
*/
|
||||||
|
async debug(qr) {
|
||||||
|
const parser = new SlashCommandParser();
|
||||||
|
const closure = parser.parse(qr.message, true, [], qr.abortController, qr.debugController);
|
||||||
|
closure.source = `${this.name}.${qr.label}`;
|
||||||
|
closure.onProgress = (done, total) => qr.updateEditorProgress(done, total);
|
||||||
|
closure.scope.setMacro('arg::*', '');
|
||||||
|
return (await closure.execute())?.pipe;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {QuickReply} qr The QR to execute.
|
* @param {QuickReply} qr The QR to execute.
|
||||||
@ -109,6 +147,7 @@ export class QuickReplySet {
|
|||||||
* @param {boolean} [options.isEditor] (false) whether the execution is triggered by the QR editor
|
* @param {boolean} [options.isEditor] (false) whether the execution is triggered by the QR editor
|
||||||
* @param {boolean} [options.isRun] (false) whether the execution is triggered by /run or /: (window.executeQuickReplyByName)
|
* @param {boolean} [options.isRun] (false) whether the execution is triggered by /run or /: (window.executeQuickReplyByName)
|
||||||
* @param {SlashCommandScope} [options.scope] (null) scope to be used when running the command
|
* @param {SlashCommandScope} [options.scope] (null) scope to be used when running the command
|
||||||
|
* @param {import('../../../slash-commands.js').ExecuteSlashCommandsOptions} [options.executionOptions] ({}) further execution options
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
async executeWithOptions(qr, options = {}) {
|
async executeWithOptions(qr, options = {}) {
|
||||||
@ -118,7 +157,9 @@ export class QuickReplySet {
|
|||||||
isEditor:false,
|
isEditor:false,
|
||||||
isRun:false,
|
isRun:false,
|
||||||
scope:null,
|
scope:null,
|
||||||
|
executionOptions:{},
|
||||||
}, options);
|
}, options);
|
||||||
|
const execOptions = options.executionOptions;
|
||||||
/**@type {HTMLTextAreaElement}*/
|
/**@type {HTMLTextAreaElement}*/
|
||||||
const ta = document.querySelector('#send_textarea');
|
const ta = document.querySelector('#send_textarea');
|
||||||
const finalMessage = options.message ?? qr.message;
|
const finalMessage = options.message ?? qr.message;
|
||||||
@ -136,21 +177,24 @@ export class QuickReplySet {
|
|||||||
if (input[0] == '/' && !this.disableSend) {
|
if (input[0] == '/' && !this.disableSend) {
|
||||||
let result;
|
let result;
|
||||||
if (options.isAutoExecute || options.isRun) {
|
if (options.isAutoExecute || options.isRun) {
|
||||||
result = await executeSlashCommandsWithOptions(input, {
|
result = await executeSlashCommandsWithOptions(input, Object.assign(execOptions, {
|
||||||
handleParserErrors: true,
|
handleParserErrors: true,
|
||||||
scope: options.scope,
|
scope: options.scope,
|
||||||
});
|
source: `${this.name}.${qr.label}`,
|
||||||
|
}));
|
||||||
} else if (options.isEditor) {
|
} else if (options.isEditor) {
|
||||||
result = await executeSlashCommandsWithOptions(input, {
|
result = await executeSlashCommandsWithOptions(input, Object.assign(execOptions, {
|
||||||
handleParserErrors: false,
|
handleParserErrors: false,
|
||||||
scope: options.scope,
|
scope: options.scope,
|
||||||
abortController: qr.abortController,
|
abortController: qr.abortController,
|
||||||
|
source: `${this.name}.${qr.label}`,
|
||||||
onProgress: (done, total) => qr.updateEditorProgress(done, total),
|
onProgress: (done, total) => qr.updateEditorProgress(done, total),
|
||||||
});
|
}));
|
||||||
} else {
|
} else {
|
||||||
result = await executeSlashCommandsOnChatInput(input, {
|
result = await executeSlashCommandsOnChatInput(input, Object.assign(execOptions, {
|
||||||
scope: options.scope,
|
scope: options.scope,
|
||||||
});
|
source: `${this.name}.${qr.label}`,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
return typeof result === 'object' ? result?.pipe : '';
|
return typeof result === 'object' ? result?.pipe : '';
|
||||||
}
|
}
|
||||||
@ -165,7 +209,7 @@ export class QuickReplySet {
|
|||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @param {QuickReply} qr
|
* @param {QuickReply} qr
|
||||||
* @param {String} [message] - optional altered message to be used
|
* @param {string} [message] - optional altered message to be used
|
||||||
* @param {SlashCommandScope} [scope] - optional scope to be used when running the command
|
* @param {SlashCommandScope} [scope] - optional scope to be used when running the command
|
||||||
*/
|
*/
|
||||||
async execute(qr, message = null, isAutoExecute = false, scope = null) {
|
async execute(qr, message = null, isAutoExecute = false, scope = null) {
|
||||||
@ -179,10 +223,11 @@ export class QuickReplySet {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
addQuickReply() {
|
addQuickReply(data = {}) {
|
||||||
const id = Math.max(this.idIndex, this.qrList.reduce((max,qr)=>Math.max(max,qr.id),0)) + 1;
|
const id = Math.max(this.idIndex, this.qrList.reduce((max,qr)=>Math.max(max,qr.id),0)) + 1;
|
||||||
|
data.id =
|
||||||
this.idIndex = id + 1;
|
this.idIndex = id + 1;
|
||||||
const qr = QuickReply.from({ id });
|
const qr = QuickReply.from(data);
|
||||||
this.qrList.push(qr);
|
this.qrList.push(qr);
|
||||||
this.hookQuickReply(qr);
|
this.hookQuickReply(qr);
|
||||||
if (this.settingsDom) {
|
if (this.settingsDom) {
|
||||||
@ -194,11 +239,131 @@ export class QuickReplySet {
|
|||||||
this.save();
|
this.save();
|
||||||
return qr;
|
return qr;
|
||||||
}
|
}
|
||||||
|
addQuickReplyFromText(qrJson) {
|
||||||
|
let data;
|
||||||
|
if (qrJson) {
|
||||||
|
try {
|
||||||
|
data = JSON.parse(qrJson ?? '{}');
|
||||||
|
delete data.id;
|
||||||
|
} catch {
|
||||||
|
// not JSON data
|
||||||
|
}
|
||||||
|
if (data) {
|
||||||
|
// JSON data
|
||||||
|
if (data.label === undefined || data.message === undefined) {
|
||||||
|
// not a QR
|
||||||
|
toastr.error('Not a QR.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// no JSON, use plaintext as QR message
|
||||||
|
data = { message: qrJson };
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
data = {};
|
||||||
|
}
|
||||||
|
const newQr = this.addQuickReply(data);
|
||||||
|
return newQr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {QuickReply} qr
|
||||||
|
*/
|
||||||
hookQuickReply(qr) {
|
hookQuickReply(qr) {
|
||||||
|
qr.onDebug = ()=>this.debug(qr);
|
||||||
qr.onExecute = (_, options)=>this.executeWithOptions(qr, options);
|
qr.onExecute = (_, options)=>this.executeWithOptions(qr, options);
|
||||||
qr.onDelete = ()=>this.removeQuickReply(qr);
|
qr.onDelete = ()=>this.removeQuickReply(qr);
|
||||||
qr.onUpdate = ()=>this.save();
|
qr.onUpdate = ()=>this.save();
|
||||||
|
qr.onInsertBefore = (qrJson)=>{
|
||||||
|
this.addQuickReplyFromText(qrJson);
|
||||||
|
const newQr = this.qrList.pop();
|
||||||
|
this.qrList.splice(this.qrList.indexOf(qr), 0, newQr);
|
||||||
|
if (qr.settingsDom) {
|
||||||
|
qr.settingsDom.insertAdjacentElement('beforebegin', newQr.settingsDom);
|
||||||
|
}
|
||||||
|
this.save();
|
||||||
|
};
|
||||||
|
qr.onTransfer = async()=>{
|
||||||
|
/**@type {HTMLSelectElement} */
|
||||||
|
let sel;
|
||||||
|
let isCopy = false;
|
||||||
|
const dom = document.createElement('div'); {
|
||||||
|
dom.classList.add('qr--transferModal');
|
||||||
|
const title = document.createElement('h3'); {
|
||||||
|
title.textContent = 'Transfer Quick Reply';
|
||||||
|
dom.append(title);
|
||||||
|
}
|
||||||
|
const subTitle = document.createElement('h4'); {
|
||||||
|
const entryName = qr.label;
|
||||||
|
const bookName = this.name;
|
||||||
|
subTitle.textContent = `${bookName}: ${entryName}`;
|
||||||
|
dom.append(subTitle);
|
||||||
|
}
|
||||||
|
sel = document.createElement('select'); {
|
||||||
|
sel.classList.add('qr--transferSelect');
|
||||||
|
sel.setAttribute('autofocus', '1');
|
||||||
|
const noOpt = document.createElement('option'); {
|
||||||
|
noOpt.value = '';
|
||||||
|
noOpt.textContent = '-- Select QR Set --';
|
||||||
|
sel.append(noOpt);
|
||||||
|
}
|
||||||
|
for (const qrs of QuickReplySet.list) {
|
||||||
|
const opt = document.createElement('option'); {
|
||||||
|
opt.value = qrs.name;
|
||||||
|
opt.textContent = qrs.name;
|
||||||
|
sel.append(opt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sel.addEventListener('keyup', (evt)=>{
|
||||||
|
if (evt.key == 'Shift') {
|
||||||
|
(dlg.dom ?? dlg.dlg).classList.remove('qr--isCopy');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
sel.addEventListener('keydown', (evt)=>{
|
||||||
|
if (evt.key == 'Shift') {
|
||||||
|
(dlg.dom ?? dlg.dlg).classList.add('qr--isCopy');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!evt.ctrlKey && !evt.altKey && evt.key == 'Enter') {
|
||||||
|
evt.preventDefault();
|
||||||
|
if (evt.shiftKey) isCopy = true;
|
||||||
|
dlg.completeAffirmative();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dom.append(sel);
|
||||||
|
}
|
||||||
|
const hintP = document.createElement('p'); {
|
||||||
|
const hint = document.createElement('small'); {
|
||||||
|
hint.textContent = 'Type or arrows to select QR Set. Enter to transfer. Shift+Enter to copy.';
|
||||||
|
hintP.append(hint);
|
||||||
|
}
|
||||||
|
dom.append(hintP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const dlg = new Popup(dom, POPUP_TYPE.CONFIRM, null, { okButton:'Transfer', cancelButton:'Cancel' });
|
||||||
|
const copyBtn = document.createElement('div'); {
|
||||||
|
copyBtn.classList.add('qr--copy');
|
||||||
|
copyBtn.classList.add('menu_button');
|
||||||
|
copyBtn.textContent = 'Copy';
|
||||||
|
copyBtn.addEventListener('click', ()=>{
|
||||||
|
isCopy = true;
|
||||||
|
dlg.completeAffirmative();
|
||||||
|
});
|
||||||
|
(dlg.ok ?? dlg.okButton).insertAdjacentElement('afterend', copyBtn);
|
||||||
|
}
|
||||||
|
const prom = dlg.show();
|
||||||
|
sel.focus();
|
||||||
|
await prom;
|
||||||
|
if (dlg.result == POPUP_RESULT.AFFIRMATIVE) {
|
||||||
|
const qrs = QuickReplySet.list.find(it=>it.name == sel.value);
|
||||||
|
qrs.addQuickReply(qr.toJSON());
|
||||||
|
if (!isCopy) {
|
||||||
|
qr.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
removeQuickReply(qr) {
|
removeQuickReply(qr) {
|
||||||
@ -214,6 +379,8 @@ export class QuickReplySet {
|
|||||||
disableSend: this.disableSend,
|
disableSend: this.disableSend,
|
||||||
placeBeforeInput: this.placeBeforeInput,
|
placeBeforeInput: this.placeBeforeInput,
|
||||||
injectInput: this.injectInput,
|
injectInput: this.injectInput,
|
||||||
|
color: this.color,
|
||||||
|
onlyBorderColor: this.onlyBorderColor,
|
||||||
qrList: this.qrList,
|
qrList: this.qrList,
|
||||||
idIndex: this.idIndex,
|
idIndex: this.idIndex,
|
||||||
};
|
};
|
||||||
@ -245,8 +412,12 @@ export class QuickReplySet {
|
|||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
this.unrender();
|
this.unrender();
|
||||||
const idx = QuickReplySet.list.indexOf(this);
|
const idx = QuickReplySet.list.indexOf(this);
|
||||||
QuickReplySet.list.splice(idx, 1);
|
if (idx > -1) {
|
||||||
this.isDeleted = true;
|
QuickReplySet.list.splice(idx, 1);
|
||||||
|
this.isDeleted = true;
|
||||||
|
} else {
|
||||||
|
warn(`Deleted Quick Reply Set was not found in the list of sets: ${this.name}`);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
warn(`Failed to delete Quick Reply Set: ${this.name}`);
|
warn(`Failed to delete Quick Reply Set: ${this.name}`);
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ export class QuickReplySetLink {
|
|||||||
this.set = QuickReplySet.get(set.value);
|
this.set = QuickReplySet.get(set.value);
|
||||||
this.update();
|
this.update();
|
||||||
});
|
});
|
||||||
QuickReplySet.list.forEach(qrs=>{
|
QuickReplySet.list.toSorted((a,b)=>a.name.toLowerCase().localeCompare(b.name.toLowerCase())).forEach(qrs=>{
|
||||||
const opt = document.createElement('option'); {
|
const opt = document.createElement('option'); {
|
||||||
opt.value = qrs.name;
|
opt.value = qrs.name;
|
||||||
opt.textContent = qrs.name;
|
opt.textContent = qrs.name;
|
||||||
|
@ -16,6 +16,7 @@ export class QuickReplySettings {
|
|||||||
/**@type {Boolean}*/ isEnabled = false;
|
/**@type {Boolean}*/ isEnabled = false;
|
||||||
/**@type {Boolean}*/ isCombined = false;
|
/**@type {Boolean}*/ isCombined = false;
|
||||||
/**@type {Boolean}*/ isPopout = false;
|
/**@type {Boolean}*/ isPopout = false;
|
||||||
|
/**@type {Boolean}*/ showPopoutButton = true;
|
||||||
/**@type {QuickReplyConfig}*/ config;
|
/**@type {QuickReplyConfig}*/ config;
|
||||||
/**@type {QuickReplyConfig}*/ _chatConfig;
|
/**@type {QuickReplyConfig}*/ _chatConfig;
|
||||||
get chatConfig() {
|
get chatConfig() {
|
||||||
@ -79,6 +80,7 @@ export class QuickReplySettings {
|
|||||||
isEnabled: this.isEnabled,
|
isEnabled: this.isEnabled,
|
||||||
isCombined: this.isCombined,
|
isCombined: this.isCombined,
|
||||||
isPopout: this.isPopout,
|
isPopout: this.isPopout,
|
||||||
|
showPopoutButton: this.showPopoutButton,
|
||||||
config: this.config,
|
config: this.config,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
import { SlashCommand } from '../../../slash-commands/SlashCommand.js';
|
import { SlashCommand } from '../../../slash-commands/SlashCommand.js';
|
||||||
|
import { SlashCommandAbortController } from '../../../slash-commands/SlashCommandAbortController.js';
|
||||||
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../../slash-commands/SlashCommandArgument.js';
|
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../../slash-commands/SlashCommandArgument.js';
|
||||||
|
import { SlashCommandClosure } from '../../../slash-commands/SlashCommandClosure.js';
|
||||||
import { enumIcons } from '../../../slash-commands/SlashCommandCommonEnumsProvider.js';
|
import { enumIcons } from '../../../slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||||
|
import { SlashCommandDebugController } from '../../../slash-commands/SlashCommandDebugController.js';
|
||||||
import { SlashCommandEnumValue, enumTypes } from '../../../slash-commands/SlashCommandEnumValue.js';
|
import { SlashCommandEnumValue, enumTypes } from '../../../slash-commands/SlashCommandEnumValue.js';
|
||||||
import { SlashCommandParser } from '../../../slash-commands/SlashCommandParser.js';
|
import { SlashCommandParser } from '../../../slash-commands/SlashCommandParser.js';
|
||||||
|
import { SlashCommandScope } from '../../../slash-commands/SlashCommandScope.js';
|
||||||
import { isTrueBoolean } from '../../../utils.js';
|
import { isTrueBoolean } from '../../../utils.js';
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
import { QuickReplyApi } from '../api/QuickReplyApi.js';
|
import { QuickReplyApi } from '../api/QuickReplyApi.js';
|
||||||
@ -47,6 +51,13 @@ export class SlashCommandHandler {
|
|||||||
return new SlashCommandEnumValue(qr.label, message, enumTypes.enum, enumIcons.qr);
|
return new SlashCommandEnumValue(qr.label, message, enumTypes.enum, enumIcons.qr);
|
||||||
}) ?? [],
|
}) ?? [],
|
||||||
|
|
||||||
|
/** All QRs inside a set, utilizing the "set" named argument, returns the QR's ID */
|
||||||
|
qrIds: (executor) => QuickReplySet.get(String(executor.namedArgumentList.find(x => x.name == 'set')?.value))?.qrList.map(qr => {
|
||||||
|
const icons = getExecutionIcons(qr);
|
||||||
|
const message = `${qr.automationId ? `[${qr.automationId}]` : ''}${icons ? `[auto: ${icons}]` : ''} ${qr.title || qr.message}`.trim();
|
||||||
|
return new SlashCommandEnumValue(qr.label, message, enumTypes.enum, enumIcons.qr, null, ()=>qr.id.toString(), true);
|
||||||
|
}) ?? [],
|
||||||
|
|
||||||
/** All QRs as a set.name string, to be able to execute, for example via the /run command */
|
/** All QRs as a set.name string, to be able to execute, for example via the /run command */
|
||||||
qrExecutables: () => {
|
qrExecutables: () => {
|
||||||
const globalSetList = this.api.settings.config.setList;
|
const globalSetList = this.api.settings.config.setList;
|
||||||
@ -63,7 +74,7 @@ export class SlashCommandHandler {
|
|||||||
...otherQrs.map(x => new SlashCommandEnumValue(`${x.set.name}.${x.qr.label}`, `${x.qr.title || x.qr.message}`, enumTypes.qr, enumIcons.qr)),
|
...otherQrs.map(x => new SlashCommandEnumValue(`${x.set.name}.${x.qr.label}`, `${x.qr.title || x.qr.message}`, enumTypes.qr, enumIcons.qr)),
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
window['qrEnumProviderExecutables'] = localEnumProviders.qrExecutables;
|
window['qrEnumProviderExecutables'] = localEnumProviders.qrExecutables;
|
||||||
|
|
||||||
@ -234,8 +245,20 @@ export class SlashCommandHandler {
|
|||||||
name: 'label',
|
name: 'label',
|
||||||
description: 'text on the button, e.g., label=MyButton',
|
description: 'text on the button, e.g., label=MyButton',
|
||||||
typeList: [ARGUMENT_TYPE.STRING],
|
typeList: [ARGUMENT_TYPE.STRING],
|
||||||
isRequired: true,
|
isRequired: false,
|
||||||
enumProvider: localEnumProviders.qrLabels,
|
enumProvider: localEnumProviders.qrEntries,
|
||||||
|
}),
|
||||||
|
SlashCommandNamedArgument.fromProps({
|
||||||
|
name: 'icon',
|
||||||
|
description: 'icon to show on the button, e.g., icon=fa-pencil',
|
||||||
|
typeList: [ARGUMENT_TYPE.STRING],
|
||||||
|
isRequired: false,
|
||||||
|
}),
|
||||||
|
SlashCommandNamedArgument.fromProps({
|
||||||
|
name: 'showlabel',
|
||||||
|
description: 'whether to show the label even when an icon is assigned, e.g., icon=fa-pencil showlabel=true',
|
||||||
|
typeList: [ARGUMENT_TYPE.BOOLEAN],
|
||||||
|
isRequired: false,
|
||||||
}),
|
}),
|
||||||
new SlashCommandNamedArgument('hidden', 'whether the button should be hidden, e.g., hidden=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'),
|
new SlashCommandNamedArgument('hidden', 'whether the button should be hidden, e.g., hidden=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'),
|
||||||
new SlashCommandNamedArgument('startup', 'auto execute on app startup, e.g., startup=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'),
|
new SlashCommandNamedArgument('startup', 'auto execute on app startup, e.g., startup=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'),
|
||||||
@ -247,6 +270,13 @@ export class SlashCommandHandler {
|
|||||||
];
|
];
|
||||||
const qrUpdateArgs = [
|
const qrUpdateArgs = [
|
||||||
new SlashCommandNamedArgument('newlabel', 'new text for the button', [ARGUMENT_TYPE.STRING], false),
|
new SlashCommandNamedArgument('newlabel', 'new text for the button', [ARGUMENT_TYPE.STRING], false),
|
||||||
|
SlashCommandNamedArgument.fromProps({
|
||||||
|
name: 'id',
|
||||||
|
description: 'numeric ID of the QR, e.g., id=42',
|
||||||
|
typeList: [ARGUMENT_TYPE.NUMBER],
|
||||||
|
isRequired: false,
|
||||||
|
enumProvider: localEnumProviders.qrIds,
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-create',
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-create',
|
||||||
@ -272,13 +302,61 @@ export class SlashCommandHandler {
|
|||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
}));
|
}));
|
||||||
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-get',
|
||||||
|
callback: (args, _) => {
|
||||||
|
return this.getQuickReply(args);
|
||||||
|
},
|
||||||
|
namedArgumentList: [
|
||||||
|
SlashCommandNamedArgument.fromProps({
|
||||||
|
name: 'set',
|
||||||
|
description: 'name of the QR set, e.g., set=PresetName1',
|
||||||
|
typeList: [ARGUMENT_TYPE.STRING],
|
||||||
|
isRequired: true,
|
||||||
|
enumProvider: localEnumProviders.qrSets,
|
||||||
|
}),
|
||||||
|
SlashCommandNamedArgument.fromProps({
|
||||||
|
name: 'label',
|
||||||
|
description: 'text on the button, e.g., label=MyButton',
|
||||||
|
typeList: [ARGUMENT_TYPE.STRING],
|
||||||
|
isRequired: false,
|
||||||
|
enumProvider: localEnumProviders.qrEntries,
|
||||||
|
}),
|
||||||
|
SlashCommandNamedArgument.fromProps({
|
||||||
|
name: 'id',
|
||||||
|
description: 'numeric ID of the QR, e.g., id=42',
|
||||||
|
typeList: [ARGUMENT_TYPE.NUMBER],
|
||||||
|
isRequired: false,
|
||||||
|
enumProvider: localEnumProviders.qrIds,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
returns: 'a dictionary with all the QR\'s properties',
|
||||||
|
helpString: `
|
||||||
|
<div>Get a Quick Reply's properties.</div>
|
||||||
|
<div>
|
||||||
|
<strong>Examples:</strong>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<pre><code>/qr-get set=MyPreset label=MyButton | /echo</code></pre>
|
||||||
|
<pre><code>/qr-get set=MyPreset id=42 | /echo</code></pre>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
}));
|
||||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-update',
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-update',
|
||||||
callback: (args, message) => {
|
callback: (args, message) => {
|
||||||
this.updateQuickReply(args, message);
|
this.updateQuickReply(args, message);
|
||||||
return '';
|
return '';
|
||||||
},
|
},
|
||||||
returns: 'updated quick reply',
|
returns: 'updated quick reply',
|
||||||
namedArgumentList: [...qrUpdateArgs, ...qrArgs],
|
namedArgumentList: [...qrUpdateArgs, ...qrArgs.map(it=>{
|
||||||
|
if (it.name == 'label') {
|
||||||
|
const clone = SlashCommandNamedArgument.fromProps(it);
|
||||||
|
clone.isRequired = false;
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
return it;
|
||||||
|
})],
|
||||||
unnamedArgumentList: [
|
unnamedArgumentList: [
|
||||||
new SlashCommandArgument('command', [ARGUMENT_TYPE.STRING]),
|
new SlashCommandArgument('command', [ARGUMENT_TYPE.STRING]),
|
||||||
],
|
],
|
||||||
@ -315,6 +393,12 @@ export class SlashCommandHandler {
|
|||||||
typeList: [ARGUMENT_TYPE.STRING],
|
typeList: [ARGUMENT_TYPE.STRING],
|
||||||
enumProvider: localEnumProviders.qrEntries,
|
enumProvider: localEnumProviders.qrEntries,
|
||||||
}),
|
}),
|
||||||
|
SlashCommandNamedArgument.fromProps({
|
||||||
|
name: 'id',
|
||||||
|
description: 'numeric ID of the QR, e.g., id=42',
|
||||||
|
typeList: [ARGUMENT_TYPE.NUMBER],
|
||||||
|
enumProvider: localEnumProviders.qrIds,
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
unnamedArgumentList: [
|
unnamedArgumentList: [
|
||||||
SlashCommandArgument.fromProps({
|
SlashCommandArgument.fromProps({
|
||||||
@ -344,6 +428,12 @@ export class SlashCommandHandler {
|
|||||||
typeList: [ARGUMENT_TYPE.STRING],
|
typeList: [ARGUMENT_TYPE.STRING],
|
||||||
enumProvider: localEnumProviders.qrEntries,
|
enumProvider: localEnumProviders.qrEntries,
|
||||||
}),
|
}),
|
||||||
|
SlashCommandNamedArgument.fromProps({
|
||||||
|
name: 'id',
|
||||||
|
description: 'numeric ID of the QR, e.g., id=42',
|
||||||
|
typeList: [ARGUMENT_TYPE.NUMBER],
|
||||||
|
enumProvider: localEnumProviders.qrIds,
|
||||||
|
}),
|
||||||
new SlashCommandNamedArgument(
|
new SlashCommandNamedArgument(
|
||||||
'chain', 'boolean', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false',
|
'chain', 'boolean', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false',
|
||||||
),
|
),
|
||||||
@ -389,6 +479,12 @@ export class SlashCommandHandler {
|
|||||||
typeList: [ARGUMENT_TYPE.STRING],
|
typeList: [ARGUMENT_TYPE.STRING],
|
||||||
enumProvider: localEnumProviders.qrEntries,
|
enumProvider: localEnumProviders.qrEntries,
|
||||||
}),
|
}),
|
||||||
|
SlashCommandNamedArgument.fromProps({
|
||||||
|
name: 'id',
|
||||||
|
description: 'numeric ID of the QR, e.g., id=42',
|
||||||
|
typeList: [ARGUMENT_TYPE.NUMBER],
|
||||||
|
enumProvider: localEnumProviders.qrIds,
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
unnamedArgumentList: [
|
unnamedArgumentList: [
|
||||||
SlashCommandArgument.fromProps({
|
SlashCommandArgument.fromProps({
|
||||||
@ -425,6 +521,12 @@ export class SlashCommandHandler {
|
|||||||
isRequired: true,
|
isRequired: true,
|
||||||
enumProvider: localEnumProviders.qrSets,
|
enumProvider: localEnumProviders.qrSets,
|
||||||
}),
|
}),
|
||||||
|
SlashCommandNamedArgument.fromProps({
|
||||||
|
name: 'id',
|
||||||
|
description: 'numeric ID of the QR, e.g., id=42',
|
||||||
|
typeList: [ARGUMENT_TYPE.NUMBER],
|
||||||
|
enumProvider: localEnumProviders.qrIds,
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
unnamedArgumentList: [
|
unnamedArgumentList: [
|
||||||
SlashCommandArgument.fromProps({
|
SlashCommandArgument.fromProps({
|
||||||
@ -454,8 +556,8 @@ export class SlashCommandHandler {
|
|||||||
new SlashCommandNamedArgument('inject', 'inject user input automatically (if disabled use {{input}})', [ARGUMENT_TYPE.BOOLEAN], false),
|
new SlashCommandNamedArgument('inject', 'inject user input automatically (if disabled use {{input}})', [ARGUMENT_TYPE.BOOLEAN], false),
|
||||||
];
|
];
|
||||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-create',
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-create',
|
||||||
callback: (args, name) => {
|
callback: async (args, name) => {
|
||||||
this.createSet(name, args);
|
await this.createSet(name, args);
|
||||||
return '';
|
return '';
|
||||||
},
|
},
|
||||||
aliases: ['qr-presetadd'],
|
aliases: ['qr-presetadd'],
|
||||||
@ -485,8 +587,8 @@ export class SlashCommandHandler {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-update',
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-update',
|
||||||
callback: (args, name) => {
|
callback: async (args, name) => {
|
||||||
this.updateSet(name, args);
|
await this.updateSet(name, args);
|
||||||
return '';
|
return '';
|
||||||
},
|
},
|
||||||
aliases: ['qr-presetupdate'],
|
aliases: ['qr-presetupdate'],
|
||||||
@ -510,8 +612,8 @@ export class SlashCommandHandler {
|
|||||||
`,
|
`,
|
||||||
}));
|
}));
|
||||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-delete',
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-delete',
|
||||||
callback: (_, name) => {
|
callback: async (_, name) => {
|
||||||
this.deleteSet(name);
|
await this.deleteSet(name);
|
||||||
return '';
|
return '';
|
||||||
},
|
},
|
||||||
aliases: ['qr-presetdelete'],
|
aliases: ['qr-presetdelete'],
|
||||||
@ -533,6 +635,134 @@ export class SlashCommandHandler {
|
|||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-arg',
|
||||||
|
callback: ({ _scope }, [key, value]) => {
|
||||||
|
_scope.setMacro(`arg::${key}`, value, key.includes('*'));
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
unnamedArgumentList: [
|
||||||
|
SlashCommandArgument.fromProps({ description: 'argument name',
|
||||||
|
typeList: ARGUMENT_TYPE.STRING,
|
||||||
|
isRequired: true,
|
||||||
|
}),
|
||||||
|
SlashCommandArgument.fromProps({ description: 'argument value',
|
||||||
|
typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.BOOLEAN, ARGUMENT_TYPE.LIST, ARGUMENT_TYPE.DICTIONARY],
|
||||||
|
isRequired: true,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
splitUnnamedArgument: true,
|
||||||
|
splitUnnamedArgumentCount: 2,
|
||||||
|
helpString: `
|
||||||
|
<div>
|
||||||
|
Set a fallback value for a Quick Reply argument.
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Example:</strong>
|
||||||
|
<pre><code>/qr-arg x foo |\n/echo {{arg::x}}</code></pre>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
}));
|
||||||
|
|
||||||
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'import',
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {{_scope:SlashCommandScope, _abortController:SlashCommandAbortController, _debugController:SlashCommandDebugController, from:string}} args
|
||||||
|
* @param {string} value
|
||||||
|
*/
|
||||||
|
callback: (args, value) => {
|
||||||
|
if (!args.from) throw new Error('/import requires from= to be set.');
|
||||||
|
if (!value) throw new Error('/import requires the unnamed argument to be set.');
|
||||||
|
let qr = [...this.api.listGlobalSets(), ...this.api.listChatSets()]
|
||||||
|
.map(it=>this.api.getSetByName(it)?.qrList ?? [])
|
||||||
|
.flat()
|
||||||
|
.find(it=>it.label == args.from)
|
||||||
|
;
|
||||||
|
if (!qr) {
|
||||||
|
let [setName, ...qrNameParts] = args.from.split('.');
|
||||||
|
let qrName = qrNameParts.join('.');
|
||||||
|
let qrs = QuickReplySet.get(setName);
|
||||||
|
if (qrs) {
|
||||||
|
qr = qrs.qrList.find(it=>it.label == qrName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (qr) {
|
||||||
|
const parser = new SlashCommandParser();
|
||||||
|
const closure = parser.parse(qr.message, true, [], args._abortController, args._debugController);
|
||||||
|
if (args._debugController) {
|
||||||
|
closure.source = args.from;
|
||||||
|
}
|
||||||
|
const testCandidates = (executor)=>{
|
||||||
|
return (
|
||||||
|
executor.namedArgumentList.find(arg=>arg.name == 'key')
|
||||||
|
&& executor.unnamedArgumentList.length > 0
|
||||||
|
&& executor.unnamedArgumentList[0].value instanceof SlashCommandClosure
|
||||||
|
) || (
|
||||||
|
!executor.namedArgumentList.find(arg=>arg.name == 'key')
|
||||||
|
&& executor.unnamedArgumentList.length > 1
|
||||||
|
&& executor.unnamedArgumentList[1].value instanceof SlashCommandClosure
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const candidates = closure.executorList
|
||||||
|
.filter(executor=>['let', 'var'].includes(executor.command.name))
|
||||||
|
.filter(testCandidates)
|
||||||
|
.map(executor=>({
|
||||||
|
key: executor.namedArgumentList.find(arg=>arg.name == 'key')?.value ?? executor.unnamedArgumentList[0].value,
|
||||||
|
value: executor.unnamedArgumentList[executor.namedArgumentList.find(arg=>arg.name == 'key') ? 0 : 1].value,
|
||||||
|
}))
|
||||||
|
;
|
||||||
|
for (let i = 0; i < value.length; i++) {
|
||||||
|
const srcName = value[i];
|
||||||
|
let dstName = srcName;
|
||||||
|
if (i + 2 < value.length && value[i + 1] == 'as') {
|
||||||
|
dstName = value[i + 2];
|
||||||
|
i += 2;
|
||||||
|
}
|
||||||
|
const pick = candidates.find(it=>it.key == srcName);
|
||||||
|
if (!pick) throw new Error(`No scoped closure named "${srcName}" found in "${args.from}"`);
|
||||||
|
if (args._scope.existsVariableInScope(dstName)) {
|
||||||
|
args._scope.setVariable(dstName, pick.value);
|
||||||
|
} else {
|
||||||
|
args._scope.letVariable(dstName, pick.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error(`No Quick Reply found for "${name}".`);
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
namedArgumentList: [
|
||||||
|
SlashCommandNamedArgument.fromProps({ name: 'from',
|
||||||
|
description: 'Quick Reply to import from (QRSet.QRLabel)',
|
||||||
|
typeList: ARGUMENT_TYPE.STRING,
|
||||||
|
isRequired: true,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
unnamedArgumentList: [
|
||||||
|
SlashCommandArgument.fromProps({ description: 'what to import (x or x as y)',
|
||||||
|
acceptsMultiple: true,
|
||||||
|
typeList: ARGUMENT_TYPE.STRING,
|
||||||
|
isRequired: true,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
splitUnnamedArgument: true,
|
||||||
|
helpString: `
|
||||||
|
<div>
|
||||||
|
Import one or more closures from another Quick Reply.
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Only imports closures that are directly assigned a scoped variable via <code>/let</code> or <code>/var</code>.
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Examples:</strong>
|
||||||
|
<ul>
|
||||||
|
<li><pre><code>/import from=LibraryQrSet.FooBar foo |\n/:foo</code></pre></li>
|
||||||
|
<li><pre><code>/import from=LibraryQrSet.FooBar\n\tfoo\n\tbar\n|\n/:foo |\n/:bar</code></pre></li>
|
||||||
|
<li><pre><code>/import from=LibraryQrSet.FooBar\n\tfoo as x\n\tbar as y\n|\n/:x |\n/:y</code></pre></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -618,6 +848,8 @@ export class SlashCommandHandler {
|
|||||||
args.set ?? '',
|
args.set ?? '',
|
||||||
args.label ?? '',
|
args.label ?? '',
|
||||||
{
|
{
|
||||||
|
icon: args.icon,
|
||||||
|
showLabel: args.showlabel === undefined ? undefined : isTrueBoolean(args.showlabel),
|
||||||
message: message ?? '',
|
message: message ?? '',
|
||||||
title: args.title,
|
title: args.title,
|
||||||
isHidden: isTrueBoolean(args.hidden),
|
isHidden: isTrueBoolean(args.hidden),
|
||||||
@ -633,12 +865,21 @@ export class SlashCommandHandler {
|
|||||||
toastr.error(ex.message);
|
toastr.error(ex.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
getQuickReply(args) {
|
||||||
|
try {
|
||||||
|
return JSON.stringify(this.api.getQrByLabel(args.set, args.id !== undefined ? Number(args.id) : args.label));
|
||||||
|
} catch (ex) {
|
||||||
|
toastr.error(ex.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
updateQuickReply(args, message) {
|
updateQuickReply(args, message) {
|
||||||
try {
|
try {
|
||||||
this.api.updateQuickReply(
|
this.api.updateQuickReply(
|
||||||
args.set ?? '',
|
args.set ?? '',
|
||||||
args.label ?? '',
|
args.id !== undefined ? Number(args.id) : (args.label ?? ''),
|
||||||
{
|
{
|
||||||
|
icon: args.icon,
|
||||||
|
showLabel: args.showlabel === undefined ? undefined : isTrueBoolean(args.showlabel),
|
||||||
newLabel: args.newlabel,
|
newLabel: args.newlabel,
|
||||||
message: (message ?? '').trim().length > 0 ? message : undefined,
|
message: (message ?? '').trim().length > 0 ? message : undefined,
|
||||||
title: args.title,
|
title: args.title,
|
||||||
@ -657,7 +898,7 @@ export class SlashCommandHandler {
|
|||||||
}
|
}
|
||||||
deleteQuickReply(args, label) {
|
deleteQuickReply(args, label) {
|
||||||
try {
|
try {
|
||||||
this.api.deleteQuickReply(args.set, args.label ?? label);
|
this.api.deleteQuickReply(args.set, args.id !== undefined ? Number(args.id) : (args.label ?? label));
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
toastr.error(ex.message);
|
toastr.error(ex.message);
|
||||||
}
|
}
|
||||||
@ -692,9 +933,9 @@ export class SlashCommandHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
createSet(name, args) {
|
async createSet(name, args) {
|
||||||
try {
|
try {
|
||||||
this.api.createSet(
|
await this.api.createSet(
|
||||||
args.name ?? name ?? '',
|
args.name ?? name ?? '',
|
||||||
{
|
{
|
||||||
disableSend: isTrueBoolean(args.nosend),
|
disableSend: isTrueBoolean(args.nosend),
|
||||||
@ -706,9 +947,9 @@ export class SlashCommandHandler {
|
|||||||
toastr.error(ex.message);
|
toastr.error(ex.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateSet(name, args) {
|
async updateSet(name, args) {
|
||||||
try {
|
try {
|
||||||
this.api.updateSet(
|
await this.api.updateSet(
|
||||||
args.name ?? name ?? '',
|
args.name ?? name ?? '',
|
||||||
{
|
{
|
||||||
disableSend: args.nosend !== undefined ? isTrueBoolean(args.nosend) : undefined,
|
disableSend: args.nosend !== undefined ? isTrueBoolean(args.nosend) : undefined,
|
||||||
@ -720,9 +961,9 @@ export class SlashCommandHandler {
|
|||||||
toastr.error(ex.message);
|
toastr.error(ex.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
deleteSet(name) {
|
async deleteSet(name) {
|
||||||
try {
|
try {
|
||||||
this.api.deleteSet(name ?? '');
|
await this.api.deleteSet(name ?? '');
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
toastr.error(ex.message);
|
toastr.error(ex.message);
|
||||||
}
|
}
|
||||||
|
@ -69,17 +69,20 @@ export class ButtonUi {
|
|||||||
root.id = 'qr--bar';
|
root.id = 'qr--bar';
|
||||||
root.classList.add('flex-container');
|
root.classList.add('flex-container');
|
||||||
root.classList.add('flexGap5');
|
root.classList.add('flexGap5');
|
||||||
const popout = document.createElement('div'); {
|
if (this.settings.showPopoutButton) {
|
||||||
popout.id = 'qr--popoutTrigger';
|
root.classList.add('popoutVisible');
|
||||||
popout.classList.add('menu_button');
|
const popout = document.createElement('div'); {
|
||||||
popout.classList.add('fa-solid');
|
popout.id = 'qr--popoutTrigger';
|
||||||
popout.classList.add('fa-window-restore');
|
popout.classList.add('menu_button');
|
||||||
popout.addEventListener('click', ()=>{
|
popout.classList.add('fa-solid');
|
||||||
this.settings.isPopout = true;
|
popout.classList.add('fa-window-restore');
|
||||||
this.refresh();
|
popout.addEventListener('click', ()=>{
|
||||||
this.settings.save();
|
this.settings.isPopout = true;
|
||||||
});
|
this.refresh();
|
||||||
root.append(popout);
|
this.settings.save();
|
||||||
|
});
|
||||||
|
root.append(popout);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (this.settings.isCombined) {
|
if (this.settings.isCombined) {
|
||||||
const buttons = document.createElement('div'); {
|
const buttons = document.createElement('div'); {
|
||||||
|
@ -14,6 +14,7 @@ export class SettingsUi {
|
|||||||
|
|
||||||
/**@type {HTMLInputElement}*/ isEnabled;
|
/**@type {HTMLInputElement}*/ isEnabled;
|
||||||
/**@type {HTMLInputElement}*/ isCombined;
|
/**@type {HTMLInputElement}*/ isCombined;
|
||||||
|
/**@type {HTMLInputElement}*/ showPopoutButton;
|
||||||
|
|
||||||
/**@type {HTMLElement}*/ globalSetList;
|
/**@type {HTMLElement}*/ globalSetList;
|
||||||
|
|
||||||
@ -23,6 +24,8 @@ export class SettingsUi {
|
|||||||
/**@type {HTMLInputElement}*/ disableSend;
|
/**@type {HTMLInputElement}*/ disableSend;
|
||||||
/**@type {HTMLInputElement}*/ placeBeforeInput;
|
/**@type {HTMLInputElement}*/ placeBeforeInput;
|
||||||
/**@type {HTMLInputElement}*/ injectInput;
|
/**@type {HTMLInputElement}*/ injectInput;
|
||||||
|
/**@type {HTMLInputElement}*/ color;
|
||||||
|
/**@type {HTMLInputElement}*/ onlyBorderColor;
|
||||||
/**@type {HTMLSelectElement}*/ currentSet;
|
/**@type {HTMLSelectElement}*/ currentSet;
|
||||||
|
|
||||||
|
|
||||||
@ -77,6 +80,10 @@ export class SettingsUi {
|
|||||||
this.isCombined = this.dom.querySelector('#qr--isCombined');
|
this.isCombined = this.dom.querySelector('#qr--isCombined');
|
||||||
this.isCombined.checked = this.settings.isCombined;
|
this.isCombined.checked = this.settings.isCombined;
|
||||||
this.isCombined.addEventListener('click', ()=>this.onIsCombined());
|
this.isCombined.addEventListener('click', ()=>this.onIsCombined());
|
||||||
|
|
||||||
|
this.showPopoutButton = this.dom.querySelector('#qr--showPopoutButton');
|
||||||
|
this.showPopoutButton.checked = this.settings.showPopoutButton;
|
||||||
|
this.showPopoutButton.addEventListener('click', ()=>this.onShowPopoutButton());
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareGlobalSetList() {
|
prepareGlobalSetList() {
|
||||||
@ -117,10 +124,29 @@ export class SettingsUi {
|
|||||||
this.dom.querySelector('#qr--set-add').addEventListener('click', async()=>{
|
this.dom.querySelector('#qr--set-add').addEventListener('click', async()=>{
|
||||||
this.currentQrSet.addQuickReply();
|
this.currentQrSet.addQuickReply();
|
||||||
});
|
});
|
||||||
|
this.dom.querySelector('#qr--set-paste').addEventListener('click', async()=>{
|
||||||
|
const text = await navigator.clipboard.readText();
|
||||||
|
this.currentQrSet.addQuickReplyFromText(text);
|
||||||
|
});
|
||||||
|
this.dom.querySelector('#qr--set-importQr').addEventListener('click', async()=>{
|
||||||
|
const inp = document.createElement('input'); {
|
||||||
|
inp.type = 'file';
|
||||||
|
inp.accept = '.json';
|
||||||
|
inp.addEventListener('change', async()=>{
|
||||||
|
if (inp.files.length > 0) {
|
||||||
|
for (const file of inp.files) {
|
||||||
|
const text = await file.text();
|
||||||
|
this.currentQrSet.addQuickReply(JSON.parse(text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
inp.click();
|
||||||
|
}
|
||||||
|
});
|
||||||
this.qrList = this.dom.querySelector('#qr--set-qrList');
|
this.qrList = this.dom.querySelector('#qr--set-qrList');
|
||||||
this.currentSet = this.dom.querySelector('#qr--set');
|
this.currentSet = this.dom.querySelector('#qr--set');
|
||||||
this.currentSet.addEventListener('change', ()=>this.onQrSetChange());
|
this.currentSet.addEventListener('change', ()=>this.onQrSetChange());
|
||||||
QuickReplySet.list.forEach(qrs=>{
|
QuickReplySet.list.toSorted((a,b)=>a.name.toLowerCase().localeCompare(b.name.toLowerCase())).forEach(qrs=>{
|
||||||
const opt = document.createElement('option'); {
|
const opt = document.createElement('option'); {
|
||||||
opt.value = qrs.name;
|
opt.value = qrs.name;
|
||||||
opt.textContent = qrs.name;
|
opt.textContent = qrs.name;
|
||||||
@ -145,6 +171,34 @@ export class SettingsUi {
|
|||||||
qrs.injectInput = this.injectInput.checked;
|
qrs.injectInput = this.injectInput.checked;
|
||||||
qrs.save();
|
qrs.save();
|
||||||
});
|
});
|
||||||
|
let initialColorChange = true;
|
||||||
|
this.color = this.dom.querySelector('#qr--color');
|
||||||
|
this.color.color = this.currentQrSet?.color ?? 'transparent';
|
||||||
|
this.color.addEventListener('change', (evt)=>{
|
||||||
|
if (!this.dom.closest('body')) return;
|
||||||
|
const qrs = this.currentQrSet;
|
||||||
|
if (initialColorChange) {
|
||||||
|
initialColorChange = false;
|
||||||
|
this.color.color = qrs.color;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
qrs.color = evt.detail.rgb;
|
||||||
|
qrs.save();
|
||||||
|
this.currentQrSet.updateColor();
|
||||||
|
});
|
||||||
|
this.dom.querySelector('#qr--colorClear').addEventListener('click', (evt)=>{
|
||||||
|
const qrs = this.currentQrSet;
|
||||||
|
this.color.color = 'transparent';
|
||||||
|
qrs.save();
|
||||||
|
this.currentQrSet.updateColor();
|
||||||
|
});
|
||||||
|
this.onlyBorderColor = this.dom.querySelector('#qr--onlyBorderColor');
|
||||||
|
this.onlyBorderColor.addEventListener('click', ()=>{
|
||||||
|
const qrs = this.currentQrSet;
|
||||||
|
qrs.onlyBorderColor = this.onlyBorderColor.checked;
|
||||||
|
qrs.save();
|
||||||
|
this.currentQrSet.updateColor();
|
||||||
|
});
|
||||||
this.onQrSetChange();
|
this.onQrSetChange();
|
||||||
}
|
}
|
||||||
onQrSetChange() {
|
onQrSetChange() {
|
||||||
@ -152,6 +206,8 @@ export class SettingsUi {
|
|||||||
this.disableSend.checked = this.currentQrSet.disableSend;
|
this.disableSend.checked = this.currentQrSet.disableSend;
|
||||||
this.placeBeforeInput.checked = this.currentQrSet.placeBeforeInput;
|
this.placeBeforeInput.checked = this.currentQrSet.placeBeforeInput;
|
||||||
this.injectInput.checked = this.currentQrSet.injectInput;
|
this.injectInput.checked = this.currentQrSet.injectInput;
|
||||||
|
this.color.color = this.currentQrSet.color ?? 'transparent';
|
||||||
|
this.onlyBorderColor.checked = this.currentQrSet.onlyBorderColor;
|
||||||
this.qrList.innerHTML = '';
|
this.qrList.innerHTML = '';
|
||||||
const qrsDom = this.currentQrSet.renderSettings();
|
const qrsDom = this.currentQrSet.renderSettings();
|
||||||
this.qrList.append(qrsDom);
|
this.qrList.append(qrsDom);
|
||||||
@ -184,6 +240,11 @@ export class SettingsUi {
|
|||||||
this.settings.save();
|
this.settings.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async onShowPopoutButton() {
|
||||||
|
this.settings.showPopoutButton = this.showPopoutButton.checked;
|
||||||
|
this.settings.save();
|
||||||
|
}
|
||||||
|
|
||||||
async onGlobalSetListSort() {
|
async onGlobalSetListSort() {
|
||||||
this.settings.config.setList = Array.from(this.globalSetList.children).map((it,idx)=>{
|
this.settings.config.setList = Array.from(this.globalSetList.children).map((it,idx)=>{
|
||||||
const set = this.settings.config.setList[Number(it.getAttribute('data-order'))];
|
const set = this.settings.config.setList[Number(it.getAttribute('data-order'))];
|
||||||
@ -265,7 +326,7 @@ export class SettingsUi {
|
|||||||
const qrs = new QuickReplySet();
|
const qrs = new QuickReplySet();
|
||||||
qrs.name = name;
|
qrs.name = name;
|
||||||
qrs.addQuickReply();
|
qrs.addQuickReply();
|
||||||
const idx = QuickReplySet.list.findIndex(it=>it.name.localeCompare(name) == 1);
|
const idx = QuickReplySet.list.findIndex(it=>it.name.toLowerCase().localeCompare(name.toLowerCase()) == 1);
|
||||||
if (idx > -1) {
|
if (idx > -1) {
|
||||||
QuickReplySet.list.splice(idx, 0, qrs);
|
QuickReplySet.list.splice(idx, 0, qrs);
|
||||||
} else {
|
} else {
|
||||||
@ -321,7 +382,7 @@ export class SettingsUi {
|
|||||||
this.prepareChatSetList();
|
this.prepareChatSetList();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const idx = QuickReplySet.list.findIndex(it=>it.name.localeCompare(qrs.name) == 1);
|
const idx = QuickReplySet.list.findIndex(it=>it.name.toLowerCase().localeCompare(qrs.name.toLowerCase()) == 1);
|
||||||
if (idx > -1) {
|
if (idx > -1) {
|
||||||
QuickReplySet.list.splice(idx, 0, qrs);
|
QuickReplySet.list.splice(idx, 0, qrs);
|
||||||
} else {
|
} else {
|
||||||
|
@ -33,11 +33,14 @@ export class ContextMenu {
|
|||||||
*/
|
*/
|
||||||
build(qr, chainedMessage = null, hierarchy = [], labelHierarchy = []) {
|
build(qr, chainedMessage = null, hierarchy = [], labelHierarchy = []) {
|
||||||
const tree = {
|
const tree = {
|
||||||
|
icon: qr.icon,
|
||||||
|
showLabel: qr.showLabel,
|
||||||
label: qr.label,
|
label: qr.label,
|
||||||
message: (chainedMessage && qr.message ? `${chainedMessage} | ` : '') + qr.message,
|
message: (chainedMessage && qr.message ? `${chainedMessage} | ` : '') + qr.message,
|
||||||
children: [],
|
children: [],
|
||||||
};
|
};
|
||||||
qr.contextList.forEach((cl) => {
|
qr.contextList.forEach((cl) => {
|
||||||
|
if (!cl.set) return;
|
||||||
if (!hierarchy.includes(cl.set)) {
|
if (!hierarchy.includes(cl.set)) {
|
||||||
const nextHierarchy = [...hierarchy, cl.set];
|
const nextHierarchy = [...hierarchy, cl.set];
|
||||||
const nextLabelHierarchy = [...labelHierarchy, tree.label];
|
const nextLabelHierarchy = [...labelHierarchy, tree.label];
|
||||||
@ -45,6 +48,8 @@ export class ContextMenu {
|
|||||||
cl.set.qrList.forEach(subQr => {
|
cl.set.qrList.forEach(subQr => {
|
||||||
const subTree = this.build(subQr, cl.isChained ? tree.message : null, nextHierarchy, nextLabelHierarchy);
|
const subTree = this.build(subQr, cl.isChained ? tree.message : null, nextHierarchy, nextLabelHierarchy);
|
||||||
tree.children.push(new MenuItem(
|
tree.children.push(new MenuItem(
|
||||||
|
subTree.icon,
|
||||||
|
subTree.showLabel,
|
||||||
subTree.label,
|
subTree.label,
|
||||||
subTree.message,
|
subTree.message,
|
||||||
(evt) => {
|
(evt) => {
|
||||||
|
@ -2,7 +2,7 @@ import { MenuItem } from './MenuItem.js';
|
|||||||
|
|
||||||
export class MenuHeader extends MenuItem {
|
export class MenuHeader extends MenuItem {
|
||||||
constructor(/**@type {String}*/label) {
|
constructor(/**@type {String}*/label) {
|
||||||
super(label, null, null);
|
super(null, null, label, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,21 +1,34 @@
|
|||||||
import { SubMenu } from './SubMenu.js';
|
import { SubMenu } from './SubMenu.js';
|
||||||
|
|
||||||
export class MenuItem {
|
export class MenuItem {
|
||||||
/**@type {String}*/ label;
|
/**@type {string}*/ icon;
|
||||||
/**@type {Object}*/ value;
|
/**@type {boolean}*/ showLabel;
|
||||||
/**@type {Function}*/ callback;
|
/**@type {string}*/ label;
|
||||||
|
/**@type {object}*/ value;
|
||||||
|
/**@type {function}*/ callback;
|
||||||
/**@type {MenuItem[]}*/ childList = [];
|
/**@type {MenuItem[]}*/ childList = [];
|
||||||
/**@type {SubMenu}*/ subMenu;
|
/**@type {SubMenu}*/ subMenu;
|
||||||
/**@type {Boolean}*/ isForceExpanded = false;
|
/**@type {boolean}*/ isForceExpanded = false;
|
||||||
|
|
||||||
/**@type {HTMLElement}*/ root;
|
/**@type {HTMLElement}*/ root;
|
||||||
|
|
||||||
/**@type {Function}*/ onExpand;
|
/**@type {function}*/ onExpand;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
constructor(/**@type {String}*/label, /**@type {Object}*/value, /**@type {function}*/callback, /**@type {MenuItem[]}*/children = []) {
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} icon
|
||||||
|
* @param {boolean} showLabel
|
||||||
|
* @param {string} label
|
||||||
|
* @param {object} value
|
||||||
|
* @param {function} callback
|
||||||
|
* @param {MenuItem[]} children
|
||||||
|
*/
|
||||||
|
constructor(icon, showLabel, label, value, callback, children = []) {
|
||||||
|
this.icon = icon;
|
||||||
|
this.showLabel = showLabel;
|
||||||
this.label = label;
|
this.label = label;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
@ -33,7 +46,21 @@ export class MenuItem {
|
|||||||
if (this.callback) {
|
if (this.callback) {
|
||||||
item.addEventListener('click', (evt) => this.callback(evt, this));
|
item.addEventListener('click', (evt) => this.callback(evt, this));
|
||||||
}
|
}
|
||||||
item.append(this.label);
|
const icon = document.createElement('div'); {
|
||||||
|
this.domIcon = icon;
|
||||||
|
icon.classList.add('qr--button-icon');
|
||||||
|
icon.classList.add('fa-solid');
|
||||||
|
if (!this.icon) icon.classList.add('qr--hidden');
|
||||||
|
else icon.classList.add(this.icon);
|
||||||
|
item.append(icon);
|
||||||
|
}
|
||||||
|
const lbl = document.createElement('div'); {
|
||||||
|
this.domLabel = lbl;
|
||||||
|
lbl.classList.add('qr--button-label');
|
||||||
|
if (this.icon && !this.showLabel) lbl.classList.add('qr--hidden');
|
||||||
|
lbl.textContent = this.label;
|
||||||
|
item.append(lbl);
|
||||||
|
}
|
||||||
if (this.childList.length > 0) {
|
if (this.childList.length > 0) {
|
||||||
item.classList.add('ctx-has-children');
|
item.classList.add('ctx-has-children');
|
||||||
const sub = new SubMenu(this.childList);
|
const sub = new SubMenu(this.childList);
|
||||||
|
@ -1,3 +1,20 @@
|
|||||||
|
@keyframes qr--success {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
color: var(--SmartThemeBodyColor);
|
||||||
|
}
|
||||||
|
25%,
|
||||||
|
75% {
|
||||||
|
color: #51a351;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.qr--success {
|
||||||
|
animation-name: qr--success;
|
||||||
|
animation-duration: 3s;
|
||||||
|
animation-timing-function: linear;
|
||||||
|
animation-delay: 0s;
|
||||||
|
animation-iteration-count: 1;
|
||||||
|
}
|
||||||
#qr--bar {
|
#qr--bar {
|
||||||
outline: none;
|
outline: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -10,7 +27,6 @@
|
|||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
order: 1;
|
order: 1;
|
||||||
padding-right: 2.5em;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
#qr--bar > #qr--popoutTrigger {
|
#qr--bar > #qr--popoutTrigger {
|
||||||
@ -18,6 +34,9 @@
|
|||||||
right: 0.25em;
|
right: 0.25em;
|
||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
|
#qr--bar.popoutVisible {
|
||||||
|
padding-right: 2.5em;
|
||||||
|
}
|
||||||
#qr--popout {
|
#qr--popout {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -41,6 +60,7 @@
|
|||||||
}
|
}
|
||||||
#qr--bar > .qr--buttons,
|
#qr--bar > .qr--buttons,
|
||||||
#qr--popout > .qr--body > .qr--buttons {
|
#qr--popout > .qr--body > .qr--buttons {
|
||||||
|
--qr--color: transparent;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -49,10 +69,44 @@
|
|||||||
gap: 5px;
|
gap: 5px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
#qr--bar > .qr--buttons.qr--color,
|
||||||
|
#qr--popout > .qr--body > .qr--buttons.qr--color {
|
||||||
|
background-color: var(--qr--color);
|
||||||
|
}
|
||||||
|
#qr--bar > .qr--buttons.qr--borderColor,
|
||||||
|
#qr--popout > .qr--body > .qr--buttons.qr--borderColor {
|
||||||
|
background-color: transparent;
|
||||||
|
border-left: 5px solid var(--qr--color);
|
||||||
|
border-right: 5px solid var(--qr--color);
|
||||||
|
}
|
||||||
|
#qr--bar > .qr--buttons:has(.qr--buttons.qr--color),
|
||||||
|
#qr--popout > .qr--body > .qr--buttons:has(.qr--buttons.qr--color) {
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
#qr--bar > .qr--buttons > .qr--buttons,
|
#qr--bar > .qr--buttons > .qr--buttons,
|
||||||
#qr--popout > .qr--body > .qr--buttons > .qr--buttons {
|
#qr--popout > .qr--body > .qr--buttons > .qr--buttons {
|
||||||
display: contents;
|
display: contents;
|
||||||
}
|
}
|
||||||
|
#qr--bar > .qr--buttons > .qr--buttons.qr--color .qr--button:before,
|
||||||
|
#qr--popout > .qr--body > .qr--buttons > .qr--buttons.qr--color .qr--button:before {
|
||||||
|
content: '';
|
||||||
|
background-color: var(--qr--color);
|
||||||
|
position: absolute;
|
||||||
|
inset: -5px;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
#qr--bar > .qr--buttons > .qr--buttons.qr--color.qr--borderColor .qr--button:before,
|
||||||
|
#qr--popout > .qr--body > .qr--buttons > .qr--buttons.qr--color.qr--borderColor .qr--button:before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#qr--bar > .qr--buttons > .qr--buttons.qr--color.qr--borderColor:before,
|
||||||
|
#qr--popout > .qr--body > .qr--buttons > .qr--buttons.qr--color.qr--borderColor:before,
|
||||||
|
#qr--bar > .qr--buttons > .qr--buttons.qr--color.qr--borderColor:after,
|
||||||
|
#qr--popout > .qr--body > .qr--buttons > .qr--buttons.qr--color.qr--borderColor:after {
|
||||||
|
content: '';
|
||||||
|
width: 5px;
|
||||||
|
background-color: var(--qr--color);
|
||||||
|
}
|
||||||
#qr--bar > .qr--buttons .qr--button,
|
#qr--bar > .qr--buttons .qr--button,
|
||||||
#qr--popout > .qr--body > .qr--buttons .qr--button {
|
#qr--popout > .qr--body > .qr--buttons .qr--button {
|
||||||
color: var(--SmartThemeBodyColor);
|
color: var(--SmartThemeBodyColor);
|
||||||
@ -66,11 +120,19 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
#qr--bar > .qr--buttons .qr--button:hover,
|
#qr--bar > .qr--buttons .qr--button:hover,
|
||||||
#qr--popout > .qr--body > .qr--buttons .qr--button:hover {
|
#qr--popout > .qr--body > .qr--buttons .qr--button:hover {
|
||||||
opacity: 1;
|
background-color: #4d4d4d;
|
||||||
filter: brightness(1.2);
|
}
|
||||||
|
#qr--bar > .qr--buttons .qr--button .qr--hidden,
|
||||||
|
#qr--popout > .qr--body > .qr--buttons .qr--button .qr--hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#qr--bar > .qr--buttons .qr--button .qr--button-icon,
|
||||||
|
#qr--popout > .qr--body > .qr--buttons .qr--button .qr--button-icon {
|
||||||
|
margin: 0 0.5em;
|
||||||
}
|
}
|
||||||
#qr--bar > .qr--buttons .qr--button > .qr--button-expander,
|
#qr--bar > .qr--buttons .qr--button > .qr--button-expander,
|
||||||
#qr--popout > .qr--body > .qr--buttons .qr--button > .qr--button-expander {
|
#qr--popout > .qr--body > .qr--buttons .qr--button > .qr--button-expander {
|
||||||
@ -170,36 +232,80 @@
|
|||||||
#qr--settings #qr--set-qrList .qr--set-qrListContents {
|
#qr--settings #qr--set-qrList .qr--set-qrListContents {
|
||||||
padding: 0 0.5em;
|
padding: 0 0.5em;
|
||||||
}
|
}
|
||||||
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item {
|
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--set-itemAdder {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
opacity: 0;
|
||||||
|
transition: 100ms;
|
||||||
|
margin: -2px 0 -11px 0;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--set-itemAdder .qr--actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.25em;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--set-itemAdder .qr--actions .qr--action {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--set-itemAdder:before,
|
||||||
|
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--set-itemAdder:after {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
border: 1px solid;
|
||||||
|
margin: 0 1em;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--set-itemAdder:hover,
|
||||||
|
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--set-itemAdder:focus-within {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
padding: 0.25em 0;
|
padding: 0.25em 0;
|
||||||
}
|
}
|
||||||
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item > :nth-child(1) {
|
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content > :nth-child(2) {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item > :nth-child(2) {
|
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content > :nth-child(2) {
|
||||||
flex: 1 1 25%;
|
flex: 1 1 25%;
|
||||||
}
|
}
|
||||||
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item > :nth-child(3) {
|
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content > :nth-child(3) {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item > :nth-child(4) {
|
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content > :nth-child(4) {
|
||||||
flex: 1 1 75%;
|
flex: 1 1 75%;
|
||||||
}
|
}
|
||||||
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item > :nth-child(5) {
|
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content > :nth-child(5) {
|
||||||
flex: 0 0 auto;
|
flex: 0 1 auto;
|
||||||
|
display: flex;
|
||||||
|
gap: 0.25em;
|
||||||
|
justify-content: flex-end;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item > .drag-handle {
|
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content > .drag-handle {
|
||||||
padding: 0.75em;
|
padding: 0.75em;
|
||||||
}
|
}
|
||||||
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--set-itemLabel,
|
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content .qr--set-itemLabelContainer {
|
||||||
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--action {
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5em;
|
||||||
|
}
|
||||||
|
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content .qr--set-itemLabelContainer .qr--set-itemIcon:not(.fa-solid) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content .qr--set-itemLabelContainer .qr--set-itemLabel {
|
||||||
|
min-width: 24px;
|
||||||
|
}
|
||||||
|
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content .qr--set-itemLabel,
|
||||||
|
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content .qr--action {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--set-itemMessage {
|
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content .qr--set-itemMessage {
|
||||||
font-size: smaller;
|
font-size: smaller;
|
||||||
}
|
}
|
||||||
#qr--settings .qr--set-qrListActions {
|
#qr--settings .qr--set-qrListActions {
|
||||||
@ -212,6 +318,7 @@
|
|||||||
#qr--qrOptions {
|
#qr--qrOptions {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
padding-right: 1px;
|
||||||
}
|
}
|
||||||
#qr--qrOptions > #qr--ctxEditor .qr--ctxItem {
|
#qr--qrOptions > #qr--ctxEditor .qr--ctxItem {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -219,6 +326,12 @@
|
|||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
}
|
}
|
||||||
|
#qr--qrOptions > #qr--autoExec .checkbox_label {
|
||||||
|
text-wrap: nowrap;
|
||||||
|
}
|
||||||
|
#qr--qrOptions > #qr--autoExec .checkbox_label .fa-fw {
|
||||||
|
margin-right: 2px;
|
||||||
|
}
|
||||||
@media screen and (max-width: 750px) {
|
@media screen and (max-width: 750px) {
|
||||||
body .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor {
|
body .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -238,6 +351,72 @@
|
|||||||
.popup:has(#qr--modalEditor) {
|
.popup:has(#qr--modalEditor) {
|
||||||
aspect-ratio: unset;
|
aspect-ratio: unset;
|
||||||
}
|
}
|
||||||
|
.popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) {
|
||||||
|
min-width: unset;
|
||||||
|
min-height: unset;
|
||||||
|
height: auto !important;
|
||||||
|
width: min-content !important;
|
||||||
|
position: absolute;
|
||||||
|
right: 1em;
|
||||||
|
top: 1em;
|
||||||
|
left: unset;
|
||||||
|
bottom: unset;
|
||||||
|
margin: unset;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized)::backdrop {
|
||||||
|
backdrop-filter: unset;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-body {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
height: min-content;
|
||||||
|
width: min-content;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-content {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-content > #qr--modalEditor {
|
||||||
|
max-height: 50vh;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-content > #qr--modalEditor > #qr--main,
|
||||||
|
.popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-content > #qr--modalEditor > #qr--resizeHandle,
|
||||||
|
.popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-content > #qr--modalEditor > #qr--qrOptions > h3,
|
||||||
|
.popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-content > #qr--modalEditor > #qr--qrOptions > #qr--modal-executeButtons,
|
||||||
|
.popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-content > #qr--modalEditor > #qr--qrOptions > #qr--modal-executeProgress {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-content > #qr--modalEditor #qr--qrOptions {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-content > #qr--modalEditor #qr--modal-debugButtons .qr--modal-debugButton#qr--modal-maximize {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-content > #qr--modalEditor #qr--modal-debugButtons .qr--modal-debugButton#qr--modal-minimize {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-content > #qr--modalEditor #qr--modal-debugState {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor):has(.qr--isExecuting) .popup-controls {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor):has(.qr--isExecuting) .qr--highlight {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 50000;
|
||||||
|
pointer-events: none;
|
||||||
|
background-color: rgba(47, 150, 180, 0.5);
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor):has(.qr--isExecuting) .qr--highlight.qr--unresolved {
|
||||||
|
background-color: rgba(255, 255, 0, 0.5);
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor):has(.qr--isExecuting) .qr--highlight-secondary {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 50000;
|
||||||
|
pointer-events: none;
|
||||||
|
border: 3px solid red;
|
||||||
|
}
|
||||||
.popup:has(#qr--modalEditor) .popup-content {
|
.popup:has(#qr--modalEditor) .popup-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -249,6 +428,67 @@
|
|||||||
gap: 1em;
|
gap: 1em;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--main > h3:first-child,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--main > .qr--labels,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--qrOptions > h3:first-child,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--qrOptions > #qr--ctxEditor,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--qrOptions > .qr--ctxEditorActions,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--qrOptions > .qr--ctxEditorActions + h3,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--qrOptions > .qr--ctxEditorActions + h3 + div {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder > #qr--modal-message {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--modal-debugButtons {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--modal-debugButtons .menu_button:not(#qr--modal-minimize, #qr--modal-maximize) {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.5;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: 200ms;
|
||||||
|
border-color: transparent;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting.qr--isPaused #qr--modal-debugButtons .menu_button:not(#qr--modal-minimize, #qr--modal-maximize) {
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting.qr--isPaused #qr--modal-debugButtons .menu_button:not(#qr--modal-minimize, #qr--modal-maximize)#qr--modal-resume {
|
||||||
|
animation-name: qr--debugPulse;
|
||||||
|
animation-duration: 1500ms;
|
||||||
|
animation-timing-function: ease-in-out;
|
||||||
|
animation-delay: 0s;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting.qr--isPaused #qr--modal-debugButtons .menu_button:not(#qr--modal-minimize, #qr--modal-maximize)#qr--modal-resume {
|
||||||
|
border-color: #51a351;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting.qr--isPaused #qr--modal-debugButtons .menu_button:not(#qr--modal-minimize, #qr--modal-maximize)#qr--modal-step {
|
||||||
|
border-color: var(--SmartThemeQuoteColor);
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting.qr--isPaused #qr--modal-debugButtons .menu_button:not(#qr--modal-minimize, #qr--modal-maximize)#qr--modal-stepInto {
|
||||||
|
border-color: var(--SmartThemeQuoteColor);
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting.qr--isPaused #qr--modal-debugButtons .menu_button:not(#qr--modal-minimize, #qr--modal-maximize)#qr--modal-stepOut {
|
||||||
|
border-color: var(--SmartThemeQuoteColor);
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--resizeHandle {
|
||||||
|
width: 6px;
|
||||||
|
background-color: var(--SmartThemeBorderColor);
|
||||||
|
border: 2px solid var(--SmartThemeBlurTintColor);
|
||||||
|
transition: border-color 200ms, background-color 200ms;
|
||||||
|
cursor: w-resize;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--resizeHandle:hover {
|
||||||
|
background-color: var(--SmartThemeQuoteColor);
|
||||||
|
border-color: var(--SmartThemeQuoteColor);
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--qrOptions {
|
||||||
|
width: var(--width, auto);
|
||||||
|
}
|
||||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main {
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -260,20 +500,114 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
|
padding: 1px;
|
||||||
}
|
}
|
||||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label {
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label {
|
||||||
flex: 1 1 1px;
|
flex: 1 1 1px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label > .qr--labelText {
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label.qr--fit,
|
||||||
flex: 1 1 auto;
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label.qr--fit {
|
||||||
}
|
|
||||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label > .qr--labelHint {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
}
|
|
||||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label > input {
|
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--inputGroup,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--inputGroup {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 0.5em;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--inputGroup input,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--inputGroup input {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--labelText,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--labelText {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--labelHint,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--labelHint {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label input,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label input {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList {
|
||||||
|
background-color: var(--stcdx--bgColor);
|
||||||
|
border: 1px solid var(--SmartThemeBorderColor);
|
||||||
|
backdrop-filter: blur(var(--SmartThemeBlurStrength));
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: smaller;
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
overflow: auto;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.5em;
|
||||||
|
max-height: 50vh;
|
||||||
|
list-style: none;
|
||||||
|
z-index: 40000;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--modal-switcherItem,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList .qr--modal-switcherItem {
|
||||||
|
display: flex;
|
||||||
|
gap: 1em;
|
||||||
|
text-align: left;
|
||||||
|
opacity: 0.75;
|
||||||
|
transition: 200ms;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--modal-switcherItem:hover,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList .qr--modal-switcherItem:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--modal-switcherItem.qr--current,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList .qr--modal-switcherItem.qr--current {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--modal-switcherItem.qr--current .qr--label,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList .qr--modal-switcherItem.qr--current .qr--label,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--modal-switcherItem.qr--current .qr--id,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList .qr--modal-switcherItem.qr--current .qr--id {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--label,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList .qr--label {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--label .menu_button,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList .qr--label .menu_button {
|
||||||
|
display: inline-block;
|
||||||
|
height: min-content;
|
||||||
|
width: min-content;
|
||||||
|
margin: 0 0.5em 0 0;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--id,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList .qr--id {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--id:before,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList .qr--id:before {
|
||||||
|
content: "[";
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--id:after,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList .qr--id:after {
|
||||||
|
content: "]";
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--message,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList .qr--message {
|
||||||
|
height: 1lh;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer {
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
@ -283,8 +617,9 @@
|
|||||||
}
|
}
|
||||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings {
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: 1em;
|
column-gap: 1em;
|
||||||
color: var(--grey70);
|
color: var(--grey70);
|
||||||
font-size: smaller;
|
font-size: smaller;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
@ -308,6 +643,11 @@
|
|||||||
background-color: var(--ac-style-color-background);
|
background-color: var(--ac-style-color-background);
|
||||||
color: var(--ac-style-color-text);
|
color: var(--ac-style-color-text);
|
||||||
}
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder.qr--noSyntax > #qr--modal-message::-webkit-scrollbar,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder.qr--noSyntax > #qr--modal-message::-webkit-scrollbar-thumb {
|
||||||
|
visibility: visible;
|
||||||
|
cursor: unset;
|
||||||
|
}
|
||||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder.qr--noSyntax > #qr--modal-message::selection {
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder.qr--noSyntax > #qr--modal-message::selection {
|
||||||
color: unset;
|
color: unset;
|
||||||
background-color: rgba(108 171 251 / 0.25);
|
background-color: rgba(108 171 251 / 0.25);
|
||||||
@ -357,11 +697,15 @@
|
|||||||
font-family: var(--monoFontFamily);
|
font-family: var(--monoFontFamily);
|
||||||
padding: 0.75em;
|
padding: 0.75em;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
border: none;
|
|
||||||
resize: none;
|
resize: none;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
border: 1px solid var(--SmartThemeBorderColor);
|
border: 1px solid var(--SmartThemeBorderColor);
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-icon {
|
||||||
|
height: 100%;
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
}
|
}
|
||||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons {
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -410,6 +754,46 @@
|
|||||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons #qr--modal-stop {
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons #qr--modal-stop {
|
||||||
border-color: #d78872;
|
border-color: #d78872;
|
||||||
}
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugButtons {
|
||||||
|
display: none;
|
||||||
|
gap: 1em;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugButtons .qr--modal-debugButton {
|
||||||
|
aspect-ratio: 1.25 / 1;
|
||||||
|
width: 2.25em;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugButtons .qr--modal-debugButton:not(.fa-solid) {
|
||||||
|
border-width: 1px;
|
||||||
|
border-style: solid;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugButtons .qr--modal-debugButton:not(.fa-solid):after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 3px;
|
||||||
|
background-color: var(--SmartThemeBodyColor);
|
||||||
|
mask-size: contain;
|
||||||
|
mask-position: center;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugButtons .qr--modal-debugButton#qr--modal-resume:after {
|
||||||
|
mask-image: url('/img/step-resume.svg');
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugButtons .qr--modal-debugButton#qr--modal-step:after {
|
||||||
|
mask-image: url('/img/step-over.svg');
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugButtons .qr--modal-debugButton#qr--modal-stepInto:after {
|
||||||
|
mask-image: url('/img/step-into.svg');
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugButtons .qr--modal-debugButton#qr--modal-stepOut:after {
|
||||||
|
mask-image: url('/img/step-out.svg');
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugButtons .qr--modal-debugButton#qr--modal-maximize {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-send_textarea {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeProgress {
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeProgress {
|
||||||
--prog: 0;
|
--prog: 0;
|
||||||
--progColor: #92befc;
|
--progColor: #92befc;
|
||||||
@ -417,6 +801,7 @@
|
|||||||
--progSuccessColor: #51a351;
|
--progSuccessColor: #51a351;
|
||||||
--progErrorColor: #bd362f;
|
--progErrorColor: #bd362f;
|
||||||
--progAbortedColor: #d78872;
|
--progAbortedColor: #d78872;
|
||||||
|
flex: 0 0 auto;
|
||||||
height: 0.5em;
|
height: 0.5em;
|
||||||
background-color: var(--black50a);
|
background-color: var(--black50a);
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -469,6 +854,7 @@
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
min-width: 100%;
|
min-width: 100%;
|
||||||
width: 0;
|
width: 0;
|
||||||
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeResult.qr--hasResult {
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeResult.qr--hasResult {
|
||||||
display: block;
|
display: block;
|
||||||
@ -476,6 +862,150 @@
|
|||||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeResult:before {
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeResult:before {
|
||||||
content: 'Result: ';
|
content: 'Result: ';
|
||||||
}
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState {
|
||||||
|
display: none;
|
||||||
|
text-align: left;
|
||||||
|
font-size: smaller;
|
||||||
|
font-family: var(--monoFontFamily);
|
||||||
|
color: white;
|
||||||
|
padding: 0.5em 0;
|
||||||
|
overflow: auto;
|
||||||
|
min-width: 100%;
|
||||||
|
width: 0;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState.qr--active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 0fr 1fr 1fr;
|
||||||
|
column-gap: 0em;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--title {
|
||||||
|
grid-column: 1 / 4;
|
||||||
|
font-weight: bold;
|
||||||
|
font-family: var(--mainFontFamily);
|
||||||
|
background-color: var(--black50a);
|
||||||
|
padding: 0.25em;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var:nth-child(2n + 1) .qr--key,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro:nth-child(2n + 1) .qr--key,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe:nth-child(2n + 1) .qr--key,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var:nth-child(2n + 1) .qr--val,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro:nth-child(2n + 1) .qr--val,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe:nth-child(2n + 1) .qr--val {
|
||||||
|
background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.25);
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var:nth-child(2n + 1) .qr--val:nth-child(2n),
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro:nth-child(2n + 1) .qr--val:nth-child(2n),
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe:nth-child(2n + 1) .qr--val:nth-child(2n) {
|
||||||
|
background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.125);
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var:nth-child(2n + 1) .qr--val:hover,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro:nth-child(2n + 1) .qr--val:hover,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe:nth-child(2n + 1) .qr--val:hover {
|
||||||
|
background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.5);
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var:nth-child(2n) .qr--val:nth-child(2n),
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro:nth-child(2n) .qr--val:nth-child(2n),
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe:nth-child(2n) .qr--val:nth-child(2n) {
|
||||||
|
background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.0625);
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var:nth-child(2n) .qr--val:hover,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro:nth-child(2n) .qr--val:hover,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe:nth-child(2n) .qr--val:hover {
|
||||||
|
background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.5);
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var.qr--isHidden .qr--key,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro.qr--isHidden .qr--key,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe.qr--isHidden .qr--key,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var.qr--isHidden .qr--val,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro.qr--isHidden .qr--val,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe.qr--isHidden .qr--val {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var .qr--val,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro .qr--val,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe .qr--val {
|
||||||
|
grid-column: 2 / 4;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var .qr--val.qr--singleCol,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro .qr--val.qr--singleCol,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe .qr--val.qr--singleCol {
|
||||||
|
grid-column: unset;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var .qr--val.qr--simple:before,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro .qr--val.qr--simple:before,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe .qr--val.qr--simple:before,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var .qr--val.qr--simple:after,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro .qr--val.qr--simple:after,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe .qr--val.qr--simple:after {
|
||||||
|
content: '"';
|
||||||
|
color: var(--SmartThemeQuoteColor);
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var .qr--val.qr--unresolved:after,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro .qr--val.qr--unresolved:after,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe .qr--val.qr--unresolved:after {
|
||||||
|
content: '-UNRESOLVED-';
|
||||||
|
font-style: italic;
|
||||||
|
color: var(--SmartThemeQuoteColor);
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--key {
|
||||||
|
margin-left: 0.5em;
|
||||||
|
padding-right: 1em;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--key:after {
|
||||||
|
content: ": ";
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe > .qr--key:before,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro > .qr--key:before {
|
||||||
|
content: "{{";
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe > .qr--key:after,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro > .qr--key:after {
|
||||||
|
content: "}}: ";
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--scope {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--scope .qr--pipe .qr--key,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--scope .qr--pipe .qr--val {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--stack {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 0fr;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--stack .qr--title {
|
||||||
|
grid-column: 1 / 3;
|
||||||
|
font-weight: bold;
|
||||||
|
font-family: var(--mainFontFamily);
|
||||||
|
background-color: var(--black50a);
|
||||||
|
padding: 0.25em;
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--stack .qr--item {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--stack .qr--item:nth-child(2n + 1) .qr--name,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--stack .qr--item:nth-child(2n + 1) .qr--source {
|
||||||
|
background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.25);
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--stack .qr--item .qr--name {
|
||||||
|
margin-left: 0.5em;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--stack .qr--item .qr--source {
|
||||||
|
opacity: 0.5;
|
||||||
|
text-align: right;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
@keyframes qr--progressPulse {
|
@keyframes qr--progressPulse {
|
||||||
0%,
|
0%,
|
||||||
100% {
|
100% {
|
||||||
@ -485,9 +1015,63 @@
|
|||||||
background-color: var(--progFlashColor);
|
background-color: var(--progFlashColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@keyframes qr--debugPulse {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
border-color: #51a351;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
border-color: #92befc;
|
||||||
|
}
|
||||||
|
}
|
||||||
.popup.qr--hide {
|
.popup.qr--hide {
|
||||||
opacity: 0 !important;
|
opacity: 0 !important;
|
||||||
}
|
}
|
||||||
.popup.qr--hide::backdrop {
|
.popup.qr--hide::backdrop {
|
||||||
opacity: 0 !important;
|
opacity: 0 !important;
|
||||||
}
|
}
|
||||||
|
.popup.qr--hide::backdrop {
|
||||||
|
opacity: 0 !important;
|
||||||
|
}
|
||||||
|
.popup:has(.qr--transferModal) .popup-button-ok {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
white-space: pre;
|
||||||
|
font-weight: normal;
|
||||||
|
box-shadow: 0 0 0;
|
||||||
|
transition: 200ms;
|
||||||
|
}
|
||||||
|
.popup:has(.qr--transferModal) .popup-button-ok:after {
|
||||||
|
content: 'Transfer';
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.popup:has(.qr--transferModal) .qr--copy {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
white-space: pre;
|
||||||
|
font-weight: normal;
|
||||||
|
box-shadow: 0 0 0;
|
||||||
|
transition: 200ms;
|
||||||
|
}
|
||||||
|
.popup:has(.qr--transferModal) .qr--copy:after {
|
||||||
|
content: 'Copy';
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.popup:has(.qr--transferModal):has(.qr--transferSelect:focus) .popup-button-ok {
|
||||||
|
font-weight: bold;
|
||||||
|
box-shadow: 0 0 10px;
|
||||||
|
}
|
||||||
|
.popup:has(.qr--transferModal):has(.qr--transferSelect:focus).qr--isCopy .popup-button-ok {
|
||||||
|
font-weight: normal;
|
||||||
|
box-shadow: 0 0 0;
|
||||||
|
}
|
||||||
|
.popup:has(.qr--transferModal):has(.qr--transferSelect:focus).qr--isCopy .qr--copy {
|
||||||
|
font-weight: bold;
|
||||||
|
box-shadow: 0 0 10px;
|
||||||
|
}
|
||||||
|
@ -1,3 +1,18 @@
|
|||||||
|
@keyframes qr--success {
|
||||||
|
0%, 100% {
|
||||||
|
color: var(--SmartThemeBodyColor);
|
||||||
|
}
|
||||||
|
25%, 75% {
|
||||||
|
color: rgb(81, 163, 81);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.qr--success {
|
||||||
|
animation-name: qr--success;
|
||||||
|
animation-duration: 3s;
|
||||||
|
animation-timing-function: linear;
|
||||||
|
animation-delay: 0s;
|
||||||
|
animation-iteration-count: 1;
|
||||||
|
}
|
||||||
#qr--bar {
|
#qr--bar {
|
||||||
outline: none;
|
outline: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -10,7 +25,6 @@
|
|||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
order: 1;
|
order: 1;
|
||||||
padding-right: 2.5em;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
>#qr--popoutTrigger {
|
>#qr--popoutTrigger {
|
||||||
@ -19,6 +33,9 @@
|
|||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#qr--bar.popoutVisible {
|
||||||
|
padding-right: 2.5em;
|
||||||
|
}
|
||||||
|
|
||||||
#qr--popout {
|
#qr--popout {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -50,6 +67,18 @@
|
|||||||
#qr--bar,
|
#qr--bar,
|
||||||
#qr--popout>.qr--body {
|
#qr--popout>.qr--body {
|
||||||
>.qr--buttons {
|
>.qr--buttons {
|
||||||
|
--qr--color: transparent;
|
||||||
|
&.qr--color {
|
||||||
|
background-color: var(--qr--color);
|
||||||
|
}
|
||||||
|
&.qr--borderColor {
|
||||||
|
background-color: transparent;
|
||||||
|
border-left: 5px solid var(--qr--color);
|
||||||
|
border-right: 5px solid var(--qr--color);
|
||||||
|
}
|
||||||
|
&:has(.qr--buttons.qr--color) {
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -60,6 +89,25 @@
|
|||||||
|
|
||||||
>.qr--buttons {
|
>.qr--buttons {
|
||||||
display: contents;
|
display: contents;
|
||||||
|
&.qr--color {
|
||||||
|
.qr--button:before {
|
||||||
|
content: '';
|
||||||
|
background-color: var(--qr--color);
|
||||||
|
position: absolute;
|
||||||
|
inset: -5px;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
&.qr--borderColor {
|
||||||
|
.qr--button:before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
&:before, &:after {
|
||||||
|
content: '';
|
||||||
|
width: 5px;
|
||||||
|
background-color: var(--qr--color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.qr--button {
|
.qr--button {
|
||||||
@ -75,10 +123,17 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: 1;
|
background-color: rgb(30% 30% 30%);
|
||||||
filter: brightness(1.2);
|
}
|
||||||
|
|
||||||
|
.qr--hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.qr--button-icon {
|
||||||
|
margin: 0 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
>.qr--button-expander {
|
>.qr--button-expander {
|
||||||
@ -211,14 +266,41 @@
|
|||||||
.qr--set-qrListContents> {
|
.qr--set-qrListContents> {
|
||||||
padding: 0 0.5em;
|
padding: 0 0.5em;
|
||||||
|
|
||||||
>.qr--set-item {
|
>.qr--set-item .qr--set-itemAdder {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
opacity: 0;
|
||||||
|
transition: 100ms;
|
||||||
|
margin: -2px 0 -11px 0;
|
||||||
|
position: relative;
|
||||||
|
.qr--actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.25em;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
.qr--action {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:before, &:after {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
border: 1px solid;
|
||||||
|
margin: 0 1em;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
&:hover, &:focus-within {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>.qr--set-item .qr--content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
padding: 0.25em 0;
|
padding: 0.25em 0;
|
||||||
|
|
||||||
> :nth-child(1) {
|
> :nth-child(2) {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,13 +317,29 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
> :nth-child(5) {
|
> :nth-child(5) {
|
||||||
flex: 0 0 auto;
|
flex: 0 1 auto;
|
||||||
|
display: flex;
|
||||||
|
gap: 0.25em;
|
||||||
|
justify-content: flex-end;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
>.drag-handle {
|
>.drag-handle {
|
||||||
padding: 0.75em;
|
padding: 0.75em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.qr--set-itemLabelContainer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5em;
|
||||||
|
.qr--set-itemIcon:not(.fa-solid) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.qr--set-itemLabel {
|
||||||
|
min-width: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.qr--set-itemLabel,
|
.qr--set-itemLabel,
|
||||||
.qr--action {
|
.qr--action {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -251,6 +349,8 @@
|
|||||||
font-size: smaller;
|
font-size: smaller;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -270,6 +370,7 @@
|
|||||||
#qr--qrOptions {
|
#qr--qrOptions {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
padding-right: 1px;
|
||||||
|
|
||||||
>#qr--ctxEditor {
|
>#qr--ctxEditor {
|
||||||
.qr--ctxItem {
|
.qr--ctxItem {
|
||||||
@ -279,6 +380,15 @@
|
|||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
>#qr--autoExec {
|
||||||
|
.checkbox_label {
|
||||||
|
text-wrap: nowrap;
|
||||||
|
|
||||||
|
.fa-fw {
|
||||||
|
margin-right: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -306,6 +416,78 @@
|
|||||||
.popup:has(#qr--modalEditor) {
|
.popup:has(#qr--modalEditor) {
|
||||||
aspect-ratio: unset;
|
aspect-ratio: unset;
|
||||||
|
|
||||||
|
&:has(.qr--isExecuting.qr--minimized) {
|
||||||
|
min-width: unset;
|
||||||
|
min-height: unset;
|
||||||
|
height: auto !important;
|
||||||
|
width: min-content !important;
|
||||||
|
position: absolute;
|
||||||
|
right: 1em;
|
||||||
|
top: 1em;
|
||||||
|
left: unset;
|
||||||
|
bottom: unset;
|
||||||
|
margin: unset;
|
||||||
|
padding: 0;
|
||||||
|
&::backdrop {
|
||||||
|
backdrop-filter: unset;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
.popup-body {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
height: min-content;
|
||||||
|
width: min-content;
|
||||||
|
}
|
||||||
|
.popup-content {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
margin-top: 0;
|
||||||
|
|
||||||
|
> #qr--modalEditor {
|
||||||
|
max-height: 50vh;
|
||||||
|
> #qr--main,
|
||||||
|
> #qr--resizeHandle,
|
||||||
|
> #qr--qrOptions > h3,
|
||||||
|
> #qr--qrOptions > #qr--modal-executeButtons,
|
||||||
|
> #qr--qrOptions > #qr--modal-executeProgress
|
||||||
|
{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#qr--qrOptions {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
#qr--modal-debugButtons .qr--modal-debugButton#qr--modal-maximize {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
#qr--modal-debugButtons .qr--modal-debugButton#qr--modal-minimize {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#qr--modal-debugState {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:has(.qr--isExecuting) {
|
||||||
|
.popup-controls {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qr--highlight {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 50000;
|
||||||
|
pointer-events: none;
|
||||||
|
background-color: rgb(47 150 180 / 0.5);
|
||||||
|
&.qr--unresolved {
|
||||||
|
background-color: rgb(255 255 0 / 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.qr--highlight-secondary {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 50000;
|
||||||
|
pointer-events: none;
|
||||||
|
border: 3px solid red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.popup-content {
|
.popup-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -317,140 +499,262 @@
|
|||||||
gap: 1em;
|
gap: 1em;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
>#qr--main {
|
&.qr--isExecuting {
|
||||||
flex: 1 1 auto;
|
#qr--main > h3:first-child,
|
||||||
display: flex;
|
#qr--main > .qr--labels,
|
||||||
flex-direction: column;
|
#qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings,
|
||||||
overflow: hidden;
|
#qr--qrOptions > h3:first-child,
|
||||||
|
#qr--qrOptions > #qr--ctxEditor,
|
||||||
>.qr--labels {
|
#qr--qrOptions > .qr--ctxEditorActions,
|
||||||
flex: 0 0 auto;
|
#qr--qrOptions > .qr--ctxEditorActions + h3,
|
||||||
display: flex;
|
#qr--qrOptions > .qr--ctxEditorActions + h3 + div
|
||||||
flex-direction: row;
|
{
|
||||||
gap: 0.5em;
|
display: none;
|
||||||
|
}
|
||||||
>label {
|
#qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder > #qr--modal-message {
|
||||||
flex: 1 1 1px;
|
visibility: hidden;
|
||||||
display: flex;
|
}
|
||||||
flex-direction: column;
|
#qr--modal-debugButtons {
|
||||||
|
display: flex;
|
||||||
>.qr--labelText {
|
.menu_button:not(#qr--modal-minimize, #qr--modal-maximize) {
|
||||||
flex: 1 1 auto;
|
cursor: not-allowed;
|
||||||
|
opacity: 0.5;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: 200ms;
|
||||||
|
border-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.qr--isPaused #qr--modal-debugButtons {
|
||||||
|
.menu_button:not(#qr--modal-minimize, #qr--modal-maximize) {
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: all;
|
||||||
|
&#qr--modal-resume {
|
||||||
|
animation-name: qr--debugPulse;
|
||||||
|
animation-duration: 1500ms;
|
||||||
|
animation-timing-function: ease-in-out;
|
||||||
|
animation-delay: 0s;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
}
|
}
|
||||||
|
&#qr--modal-resume {
|
||||||
>.qr--labelHint {
|
border-color: rgb(81, 163, 81);
|
||||||
flex: 1 1 auto;
|
|
||||||
}
|
}
|
||||||
|
&#qr--modal-step {
|
||||||
>input {
|
border-color: var(--SmartThemeQuoteColor);
|
||||||
flex: 0 0 auto;
|
}
|
||||||
|
&#qr--modal-stepInto {
|
||||||
|
border-color: var(--SmartThemeQuoteColor);
|
||||||
|
}
|
||||||
|
&#qr--modal-stepOut {
|
||||||
|
border-color: var(--SmartThemeQuoteColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#qr--resizeHandle {
|
||||||
|
width: 6px;
|
||||||
|
background-color: var(--SmartThemeBorderColor);
|
||||||
|
border: 2px solid var(--SmartThemeBlurTintColor);
|
||||||
|
transition: border-color 200ms, background-color 200ms;
|
||||||
|
cursor: w-resize;
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--SmartThemeQuoteColor);
|
||||||
|
border-color: var(--SmartThemeQuoteColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#qr--qrOptions {
|
||||||
|
width: var(--width, auto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
>.qr--modal-messageContainer {
|
> #qr--main {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
> .qr--labels {
|
||||||
>.qr--modal-editorSettings {
|
flex: 0 0 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: 1em;
|
gap: 0.5em;
|
||||||
color: var(--grey70);
|
padding: 1px;
|
||||||
font-size: smaller;
|
> label, > .label {
|
||||||
align-items: baseline;
|
flex: 1 1 1px;
|
||||||
|
display: flex;
|
||||||
>.checkbox_label {
|
flex-direction: column;
|
||||||
white-space: nowrap;
|
position: relative;
|
||||||
|
&.qr--fit {
|
||||||
>input {
|
flex: 0 0 auto;
|
||||||
font-size: inherit;
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.qr--inputGroup {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 0.5em;
|
||||||
|
input {
|
||||||
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
.qr--labelText {
|
||||||
|
flex: 1 1 auto;
|
||||||
>#qr--modal-messageHolder {
|
}
|
||||||
flex: 1 1 auto;
|
.qr--labelHint {
|
||||||
display: grid;
|
flex: 1 1 auto;
|
||||||
text-align: left;
|
}
|
||||||
overflow: hidden;
|
input {
|
||||||
|
flex: 0 0 auto;
|
||||||
&.qr--noSyntax {
|
}
|
||||||
>#qr--modal-messageSyntax {
|
.qr--modal-switcherList {
|
||||||
display: none;
|
background-color: var(--stcdx--bgColor);
|
||||||
}
|
border: 1px solid var(--SmartThemeBorderColor);
|
||||||
|
backdrop-filter: blur(var(--SmartThemeBlurStrength));
|
||||||
>#qr--modal-message {
|
border-radius: 10px;
|
||||||
background-color: var(--ac-style-color-background);
|
font-size: smaller;
|
||||||
color: var(--ac-style-color-text);
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
&::selection {
|
left: 0;
|
||||||
color: unset;
|
right: 0;
|
||||||
background-color: rgba(108 171 251 / 0.25);
|
overflow: auto;
|
||||||
|
margin: 0;
|
||||||
@supports (color: rgb(from white r g b / 0.25)) {
|
padding: 0.5em;
|
||||||
background-color: rgb(from var(--ac-style-color-matchedText) r g b / 0.25);
|
max-height: 50vh;
|
||||||
|
list-style: none;
|
||||||
|
z-index: 40000;
|
||||||
|
max-width: 100%;
|
||||||
|
.qr--modal-switcherItem {
|
||||||
|
display: flex;
|
||||||
|
gap: 1em;
|
||||||
|
text-align: left;
|
||||||
|
opacity: 0.75;
|
||||||
|
transition: 200ms;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
&.qr--current {
|
||||||
|
opacity: 1;
|
||||||
|
.qr--label, .qr--id {
|
||||||
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
.qr--label {
|
||||||
|
white-space: nowrap;
|
||||||
>#qr--modal-messageSyntax {
|
.menu_button {
|
||||||
grid-column: 1;
|
display: inline-block;
|
||||||
grid-row: 1;
|
height: min-content;
|
||||||
padding: 0;
|
width: min-content;
|
||||||
margin: 0;
|
margin: 0 0.5em 0 0;
|
||||||
border: none;
|
|
||||||
overflow: hidden;
|
|
||||||
min-width: 100%;
|
|
||||||
width: 0;
|
|
||||||
|
|
||||||
>#qr--modal-messageSyntaxInner {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
>#qr--modal-message {
|
|
||||||
background-color: transparent;
|
|
||||||
color: transparent;
|
|
||||||
grid-column: 1;
|
|
||||||
grid-row: 1;
|
|
||||||
caret-color: var(--ac-style-color-text);
|
|
||||||
overflow: auto;
|
|
||||||
|
|
||||||
&::-webkit-scrollbar,
|
|
||||||
&::-webkit-scrollbar-thumb {
|
|
||||||
visibility: hidden;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::selection {
|
|
||||||
color: transparent;
|
|
||||||
background-color: rgba(108 171 251 / 0.25);
|
|
||||||
|
|
||||||
@supports (color: rgb(from white r g b / 0.25)) {
|
|
||||||
background-color: rgb(from var(--ac-style-color-matchedText) r g b / 0.25);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.qr--id {
|
||||||
|
opacity: 0.5;
|
||||||
|
&:before { content: "["; }
|
||||||
|
&:after { content: "]"; }
|
||||||
|
}
|
||||||
|
.qr--message {
|
||||||
|
height: 1lh;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
#qr--modal-message,
|
}
|
||||||
#qr--modal-messageSyntaxInner {
|
> .qr--modal-messageContainer {
|
||||||
font-family: var(--monoFontFamily);
|
flex: 1 1 auto;
|
||||||
padding: 0.75em;
|
display: flex;
|
||||||
margin: 0;
|
flex-direction: column;
|
||||||
border: none;
|
overflow: hidden;
|
||||||
resize: none;
|
> .qr--modal-editorSettings {
|
||||||
line-height: 1.2;
|
display: flex;
|
||||||
border: 1px solid var(--SmartThemeBorderColor);
|
flex-wrap: wrap;
|
||||||
border-radius: 5px;
|
flex-direction: row;
|
||||||
}
|
column-gap: 1em;
|
||||||
}
|
color: var(--grey70);
|
||||||
}
|
font-size: smaller;
|
||||||
|
align-items: baseline;
|
||||||
|
> .checkbox_label {
|
||||||
|
white-space: nowrap;
|
||||||
|
> input {
|
||||||
|
font-size: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> #qr--modal-messageHolder {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
display: grid;
|
||||||
|
text-align: left;
|
||||||
|
overflow: hidden;
|
||||||
|
&.qr--noSyntax {
|
||||||
|
> #qr--modal-messageSyntax {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
> #qr--modal-message {
|
||||||
|
background-color: var(--ac-style-color-background);
|
||||||
|
color: var(--ac-style-color-text);
|
||||||
|
&::-webkit-scrollbar, &::-webkit-scrollbar-thumb {
|
||||||
|
visibility: visible;
|
||||||
|
cursor: unset;
|
||||||
|
}
|
||||||
|
&::selection {
|
||||||
|
color: unset;
|
||||||
|
background-color: rgba(108 171 251 / 0.25);
|
||||||
|
@supports (color: rgb(from white r g b / 0.25)) {
|
||||||
|
background-color: rgb(from var(--ac-style-color-matchedText) r g b / 0.25);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> #qr--modal-messageSyntax {
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 1;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
border: none;
|
||||||
|
overflow: hidden;
|
||||||
|
min-width: 100%;
|
||||||
|
width: 0;
|
||||||
|
> #qr--modal-messageSyntaxInner {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> #qr--modal-message {
|
||||||
|
background-color: transparent;
|
||||||
|
color: transparent;
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 1;
|
||||||
|
caret-color: var(--ac-style-color-text);
|
||||||
|
overflow: auto;
|
||||||
|
&::-webkit-scrollbar, &::-webkit-scrollbar-thumb {
|
||||||
|
visibility: hidden;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
&::selection {
|
||||||
|
color: transparent;
|
||||||
|
background-color: rgba(108 171 251 / 0.25);
|
||||||
|
@supports (color: rgb(from white r g b / 0.25)) {
|
||||||
|
background-color: rgb(from var(--ac-style-color-matchedText) r g b / 0.25);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#qr--modal-message, #qr--modal-messageSyntaxInner {
|
||||||
|
font-family: var(--monoFontFamily);
|
||||||
|
padding: 0.75em;
|
||||||
|
margin: 0;
|
||||||
|
resize: none;
|
||||||
|
line-height: 1.2;
|
||||||
|
border: 1px solid var(--SmartThemeBorderColor);
|
||||||
|
border-radius: 5px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#qr--modal-icon {
|
||||||
|
height: 100%;
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#qr--modal-executeButtons {
|
#qr--modal-executeButtons {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 1em;
|
gap: 1em;
|
||||||
@ -510,6 +814,47 @@
|
|||||||
border-color: rgb(215, 136, 114);
|
border-color: rgb(215, 136, 114);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#qr--modal-debugButtons {
|
||||||
|
display: none;
|
||||||
|
gap: 1em;
|
||||||
|
.qr--modal-debugButton {
|
||||||
|
aspect-ratio: 1.25 / 1;
|
||||||
|
width: 2.25em;
|
||||||
|
position: relative;
|
||||||
|
&:not(.fa-solid) {
|
||||||
|
border-width: 1px;
|
||||||
|
border-style: solid;
|
||||||
|
&:after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 3px;
|
||||||
|
background-color: var(--SmartThemeBodyColor);
|
||||||
|
mask-size: contain;
|
||||||
|
mask-position: center;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&#qr--modal-resume:after {
|
||||||
|
mask-image: url('/img/step-resume.svg');
|
||||||
|
}
|
||||||
|
&#qr--modal-step:after {
|
||||||
|
mask-image: url('/img/step-over.svg');
|
||||||
|
}
|
||||||
|
&#qr--modal-stepInto:after {
|
||||||
|
mask-image: url('/img/step-into.svg');
|
||||||
|
}
|
||||||
|
&#qr--modal-stepOut:after {
|
||||||
|
mask-image: url('/img/step-out.svg');
|
||||||
|
}
|
||||||
|
&#qr--modal-maximize {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#qr--modal-send_textarea {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
#qr--modal-executeProgress {
|
#qr--modal-executeProgress {
|
||||||
--prog: 0;
|
--prog: 0;
|
||||||
@ -518,6 +863,7 @@
|
|||||||
--progSuccessColor: rgb(81, 163, 81);
|
--progSuccessColor: rgb(81, 163, 81);
|
||||||
--progErrorColor: rgb(189, 54, 47);
|
--progErrorColor: rgb(189, 54, 47);
|
||||||
--progAbortedColor: rgb(215, 136, 114);
|
--progAbortedColor: rgb(215, 136, 114);
|
||||||
|
flex: 0 0 auto;
|
||||||
height: 0.5em;
|
height: 0.5em;
|
||||||
background-color: var(--black50a);
|
background-color: var(--black50a);
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -588,6 +934,135 @@
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
min-width: 100%;
|
min-width: 100%;
|
||||||
width: 0;
|
width: 0;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
#qr--modal-debugState {
|
||||||
|
display: none;
|
||||||
|
&.qr--active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
text-align: left;
|
||||||
|
font-size: smaller;
|
||||||
|
font-family: var(--monoFontFamily);
|
||||||
|
// background-color: rgb(146, 190, 252);
|
||||||
|
color: white;
|
||||||
|
padding: 0.5em 0;
|
||||||
|
overflow: auto;
|
||||||
|
min-width: 100%;
|
||||||
|
width: 0;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
|
||||||
|
.qr--scope {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 0fr 1fr 1fr;
|
||||||
|
column-gap: 0em;
|
||||||
|
.qr--title {
|
||||||
|
grid-column: 1 / 4;
|
||||||
|
font-weight: bold;
|
||||||
|
font-family: var(--mainFontFamily);
|
||||||
|
background-color: var(--black50a);
|
||||||
|
padding: 0.25em;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
|
.qr--var, .qr--macro, .qr--pipe {
|
||||||
|
display: contents;
|
||||||
|
&:nth-child(2n + 1) {
|
||||||
|
.qr--key, .qr--val {
|
||||||
|
background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.25);
|
||||||
|
}
|
||||||
|
.qr--val {
|
||||||
|
&:nth-child(2n) {
|
||||||
|
background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.125);
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:nth-child(2n) {
|
||||||
|
.qr--val {
|
||||||
|
&:nth-child(2n) {
|
||||||
|
background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.0625);
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.qr--isHidden {
|
||||||
|
.qr--key, .qr--val {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.qr--val {
|
||||||
|
grid-column: 2 / 4;
|
||||||
|
&.qr--singleCol {
|
||||||
|
grid-column: unset;
|
||||||
|
}
|
||||||
|
&.qr--simple {
|
||||||
|
&:before, &:after {
|
||||||
|
content: '"';
|
||||||
|
color: var(--SmartThemeQuoteColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.qr--unresolved {
|
||||||
|
&:after {
|
||||||
|
content: '-UNRESOLVED-';
|
||||||
|
font-style: italic;
|
||||||
|
color: var(--SmartThemeQuoteColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.qr--key {
|
||||||
|
margin-left: 0.5em;
|
||||||
|
padding-right: 1em;
|
||||||
|
&:after { content: ": "; }
|
||||||
|
}
|
||||||
|
.qr--pipe, .qr--macro {
|
||||||
|
> .qr--key {
|
||||||
|
&:before { content: "{{"; }
|
||||||
|
&:after { content: "}}: "; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.qr--scope {
|
||||||
|
display: contents;
|
||||||
|
.qr--pipe {
|
||||||
|
.qr--key, .qr--val {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.qr--stack {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 0fr;
|
||||||
|
.qr--title {
|
||||||
|
grid-column: 1 / 3;
|
||||||
|
font-weight: bold;
|
||||||
|
font-family: var(--mainFontFamily);
|
||||||
|
background-color: var(--black50a);
|
||||||
|
padding: 0.25em;
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
.qr--item {
|
||||||
|
display: contents;
|
||||||
|
&:nth-child(2n + 1) {
|
||||||
|
.qr--name, .qr--source {
|
||||||
|
background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.25);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.qr--name {
|
||||||
|
margin-left: 0.5em;
|
||||||
|
}
|
||||||
|
.qr--source {
|
||||||
|
opacity: 0.5;
|
||||||
|
text-align: right;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -605,10 +1080,75 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes qr--debugPulse {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
border-color: rgb(81, 163, 81);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
border-color: rgb(146, 190, 252);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.popup.qr--hide {
|
.popup.qr--hide {
|
||||||
opacity: 0 !important;
|
opacity: 0 !important;
|
||||||
|
&::backdrop {
|
||||||
|
opacity: 0 !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.popup.qr--hide::backdrop {
|
.popup.qr--hide::backdrop {
|
||||||
opacity: 0 !important;
|
opacity: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.popup:has(.qr--transferModal) {
|
||||||
|
.popup-button-ok {
|
||||||
|
&:after {
|
||||||
|
content: 'Transfer';
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
white-space: pre;
|
||||||
|
font-weight: normal;
|
||||||
|
box-shadow: 0 0 0;
|
||||||
|
transition: 200ms;
|
||||||
|
}
|
||||||
|
.qr--copy {
|
||||||
|
&:after {
|
||||||
|
content: 'Copy';
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
white-space: pre;
|
||||||
|
font-weight: normal;
|
||||||
|
box-shadow: 0 0 0;
|
||||||
|
transition: 200ms;
|
||||||
|
}
|
||||||
|
&:has(.qr--transferSelect:focus) {
|
||||||
|
.popup-button-ok {
|
||||||
|
font-weight: bold;
|
||||||
|
box-shadow: 0 0 10px;
|
||||||
|
}
|
||||||
|
&.qr--isCopy {
|
||||||
|
.popup-button-ok {
|
||||||
|
font-weight: normal;
|
||||||
|
box-shadow: 0 0 0;
|
||||||
|
}
|
||||||
|
.qr--copy {
|
||||||
|
font-weight: bold;
|
||||||
|
box-shadow: 0 0 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -54,12 +54,7 @@
|
|||||||
<small data-i18n="Replace With">Replace With</small>
|
<small data-i18n="Replace With">Replace With</small>
|
||||||
</label>
|
</label>
|
||||||
<div>
|
<div>
|
||||||
<textarea
|
<textarea class="regex_replace_string text_pole wide100p textarea_compact" data-i18n="[placeholder]ext_regex_replace_string_placeholder" placeholder="Use {{match}} to include the matched text from the Find Regex or $1, $2, etc. for capture groups." rows="2"></textarea>
|
||||||
class="regex_replace_string text_pole wide100p textarea_compact"
|
|
||||||
data-i18n="[placeholder]ext_regex_replace_string_placeholder"
|
|
||||||
placeholder="Use {{match}} to include the matched text from the Find Regex or $1, $2, etc. for capture groups."
|
|
||||||
rows="2"
|
|
||||||
></textarea>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex1">
|
<div class="flex1">
|
||||||
@ -67,11 +62,7 @@
|
|||||||
<small data-i18n="Trim Out">Trim Out</small>
|
<small data-i18n="Trim Out">Trim Out</small>
|
||||||
</label>
|
</label>
|
||||||
<div>
|
<div>
|
||||||
<textarea
|
<textarea class="regex_trim_strings text_pole wide100p textarea_compact" data-i18n="[placeholder]ext_regex_trim_placeholder" placeholder="Globally trims any unwanted parts from a regex match before replacement. Separate each element by an enter." rows="3"></textarea>
|
||||||
class="regex_trim_strings text_pole wide100p textarea_compact" data-i18n="[placeholder]ext_regex_trim_placeholder"
|
|
||||||
placeholder="Globally trims any unwanted parts from a regex match before replacement. Separate each element by an enter."
|
|
||||||
rows="3"
|
|
||||||
></textarea>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -126,17 +117,6 @@
|
|||||||
<input type="checkbox" name="disabled" />
|
<input type="checkbox" name="disabled" />
|
||||||
<span data-i18n="Disabled">Disabled</span>
|
<span data-i18n="Disabled">Disabled</span>
|
||||||
</label>
|
</label>
|
||||||
<label class="checkbox flex-container" title="Chat history won't change, only the message rendered in the UI.">
|
|
||||||
<input type="checkbox" name="only_format_display" />
|
|
||||||
<span data-i18n="Only Format Display">Only Format Display</span>
|
|
||||||
</label>
|
|
||||||
<label class="checkbox flex-container" data-i18n="[title]ext_regex_only_format_prompt_desc" title="Chat history won't change, only the prompt as the request is sent (on generation).">
|
|
||||||
<input type="checkbox" name="only_format_prompt"/>
|
|
||||||
<span>
|
|
||||||
<span data-i18n="Only Format Prompt (?)">Only Format Prompt</span>
|
|
||||||
<span class="fa-solid fa-circle-question note-link-span"></span>
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<label class="checkbox flex-container">
|
<label class="checkbox flex-container">
|
||||||
<input type="checkbox" name="run_on_edit" />
|
<input type="checkbox" name="run_on_edit" />
|
||||||
<span data-i18n="Run On Edit">Run On Edit</span>
|
<span data-i18n="Run On Edit">Run On Edit</span>
|
||||||
@ -148,6 +128,19 @@
|
|||||||
<span class="fa-solid fa-circle-question note-link-span"></span>
|
<span class="fa-solid fa-circle-question note-link-span"></span>
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
|
<span>
|
||||||
|
<small data-i18n="ext_regex_other_options" data-i18n="Ephemerality">Ephemerality</small>
|
||||||
|
<span class="fa-solid fa-circle-question note-link-span" title="By default, regex scripts alter the chat file directly and irreversibly. Enabling either (or both) of the options below will prevent chat file alteration, while still altering the specified item(s)."></span>
|
||||||
|
</span>
|
||||||
|
<label class="checkbox flex-container" title="Chat history file contents won't change, but regex will be applied to the messages displayed in the Chat UI.">
|
||||||
|
<input type="checkbox" name="only_format_display" />
|
||||||
|
<span data-i18n="Only Format Display">Alter Chat Display</span>
|
||||||
|
</label>
|
||||||
|
<label class="checkbox flex-container" data-i18n="[title]ext_regex_only_format_prompt_desc" title="Chat history file contents won't change, but regex will be applied to the outgoing prompt before it is sent to the LLM.">
|
||||||
|
<input type="checkbox" name="only_format_prompt" />
|
||||||
|
<span data-i18n="Only Format Prompt (?)">Alter Outgoing Prompt</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { getRequestHeaders } from '../../script.js';
|
import { getRequestHeaders } from '../../script.js';
|
||||||
import { extension_settings } from '../extensions.js';
|
import { extension_settings, openThirdPartyExtensionMenu } from '../extensions.js';
|
||||||
import { oai_settings } from '../openai.js';
|
import { oai_settings } from '../openai.js';
|
||||||
import { SECRET_KEYS, secret_state } from '../secrets.js';
|
import { SECRET_KEYS, secret_state } from '../secrets.js';
|
||||||
import { textgen_types, textgenerationwebui_settings } from '../textgen-settings.js';
|
import { textgen_types, textgenerationwebui_settings } from '../textgen-settings.js';
|
||||||
@ -136,8 +136,12 @@ function throwIfInvalidModel(useReverseProxy) {
|
|||||||
throw new Error('Anthropic (Claude) API key is not set.');
|
throw new Error('Anthropic (Claude) API key is not set.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (extension_settings.caption.multimodal_api === 'zerooneai' && !secret_state[SECRET_KEYS.ZEROONEAI]) {
|
||||||
|
throw new Error('01.AI API key is not set.');
|
||||||
|
}
|
||||||
|
|
||||||
if (extension_settings.caption.multimodal_api === 'google' && !secret_state[SECRET_KEYS.MAKERSUITE] && !useReverseProxy) {
|
if (extension_settings.caption.multimodal_api === 'google' && !secret_state[SECRET_KEYS.MAKERSUITE] && !useReverseProxy) {
|
||||||
throw new Error('MakerSuite API key is not set.');
|
throw new Error('Google AI Studio API key is not set.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (extension_settings.caption.multimodal_api === 'ollama' && !textgenerationwebui_settings.server_urls[textgen_types.OLLAMA]) {
|
if (extension_settings.caption.multimodal_api === 'ollama' && !textgenerationwebui_settings.server_urls[textgen_types.OLLAMA]) {
|
||||||
@ -172,3 +176,86 @@ function throwIfInvalidModel(useReverseProxy) {
|
|||||||
throw new Error('Custom API URL is not set.');
|
throw new Error('Custom API URL is not set.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the WebLLM extension is installed and supported.
|
||||||
|
* @returns {boolean} Whether the extension is installed and supported
|
||||||
|
*/
|
||||||
|
export function isWebLlmSupported() {
|
||||||
|
if (!('gpu' in navigator)) {
|
||||||
|
const warningKey = 'webllm_browser_warning_shown';
|
||||||
|
if (!sessionStorage.getItem(warningKey)) {
|
||||||
|
toastr.error('Your browser does not support the WebGPU API. Please use a different browser.', 'WebLLM', {
|
||||||
|
preventDuplicates: true,
|
||||||
|
timeOut: 0,
|
||||||
|
extendedTimeOut: 0,
|
||||||
|
});
|
||||||
|
sessionStorage.setItem(warningKey, '1');
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!('llm' in SillyTavern)) {
|
||||||
|
const warningKey = 'webllm_extension_warning_shown';
|
||||||
|
if (!sessionStorage.getItem(warningKey)) {
|
||||||
|
toastr.error('WebLLM extension is not installed. Click here to install it.', 'WebLLM', {
|
||||||
|
timeOut: 0,
|
||||||
|
extendedTimeOut: 0,
|
||||||
|
preventDuplicates: true,
|
||||||
|
onclick: () => openThirdPartyExtensionMenu('https://github.com/SillyTavern/Extension-WebLLM'),
|
||||||
|
});
|
||||||
|
sessionStorage.setItem(warningKey, '1');
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates text in response to a chat prompt using WebLLM.
|
||||||
|
* @param {any[]} messages Messages to use for generating
|
||||||
|
* @param {object} params Additional parameters
|
||||||
|
* @returns {Promise<string>} Generated response
|
||||||
|
*/
|
||||||
|
export async function generateWebLlmChatPrompt(messages, params = {}) {
|
||||||
|
if (!isWebLlmSupported()) {
|
||||||
|
throw new Error('WebLLM extension is not installed.');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.debug('WebLLM chat completion request:', messages, params);
|
||||||
|
const engine = SillyTavern.llm;
|
||||||
|
const response = await engine.generateChatPrompt(messages, params);
|
||||||
|
console.debug('WebLLM chat completion response:', response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counts the number of tokens in the provided text using WebLLM's default model.
|
||||||
|
* @param {string} text Text to count tokens in
|
||||||
|
* @returns {Promise<number>} Number of tokens in the text
|
||||||
|
*/
|
||||||
|
export async function countWebLlmTokens(text) {
|
||||||
|
if (!isWebLlmSupported()) {
|
||||||
|
throw new Error('WebLLM extension is not installed.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const engine = SillyTavern.llm;
|
||||||
|
const response = await engine.countTokens(text);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the size of the context in the WebLLM's default model.
|
||||||
|
* @returns {Promise<number>} Size of the context in the WebLLM model
|
||||||
|
*/
|
||||||
|
export async function getWebLlmContextSize() {
|
||||||
|
if (!isWebLlmSupported()) {
|
||||||
|
throw new Error('WebLLM extension is not installed.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const engine = SillyTavern.llm;
|
||||||
|
await engine.loadModel();
|
||||||
|
const model = await engine.getCurrentModelInfo();
|
||||||
|
return model?.context_size;
|
||||||
|
}
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
<div id="sd_gen" class="list-group-item flex-container flexGap5">
|
<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>
|
<div class="fa-solid fa-paintbrush extensionsMenuExtensionButton" title="Trigger Stable Diffusion" data-i18n="[title]Trigger Stable Diffusion"></div>
|
||||||
Generate Image
|
<span>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>
|
||||||
</div>
|
</div>
|
||||||
|
@ -30,13 +30,14 @@ import { SlashCommand } from '../../slash-commands/SlashCommand.js';
|
|||||||
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
|
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
|
||||||
import { debounce_timeout } from '../../constants.js';
|
import { debounce_timeout } from '../../constants.js';
|
||||||
import { SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js';
|
import { SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js';
|
||||||
import { POPUP_TYPE, Popup, callGenericPopup } from '../../popup.js';
|
import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from '../../popup.js';
|
||||||
export { MODULE_NAME };
|
export { MODULE_NAME };
|
||||||
|
|
||||||
const MODULE_NAME = 'sd';
|
const MODULE_NAME = 'sd';
|
||||||
const UPDATE_INTERVAL = 1000;
|
const UPDATE_INTERVAL = 1000;
|
||||||
// This is a 1x1 transparent PNG
|
// This is a 1x1 transparent PNG
|
||||||
const PNG_PIXEL = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';
|
const PNG_PIXEL = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';
|
||||||
|
const CUSTOM_STOP_EVENT = 'sd_stop_generation';
|
||||||
|
|
||||||
const sources = {
|
const sources = {
|
||||||
extras: 'extras',
|
extras: 'extras',
|
||||||
@ -50,6 +51,8 @@ const sources = {
|
|||||||
drawthings: 'drawthings',
|
drawthings: 'drawthings',
|
||||||
pollinations: 'pollinations',
|
pollinations: 'pollinations',
|
||||||
stability: 'stability',
|
stability: 'stability',
|
||||||
|
blockentropy: 'blockentropy',
|
||||||
|
huggingface: 'huggingface',
|
||||||
};
|
};
|
||||||
|
|
||||||
const initiators = {
|
const initiators = {
|
||||||
@ -57,6 +60,7 @@ const initiators = {
|
|||||||
action: 'action',
|
action: 'action',
|
||||||
interactive: 'interactive',
|
interactive: 'interactive',
|
||||||
wand: 'wand',
|
wand: 'wand',
|
||||||
|
swipe: 'swipe',
|
||||||
};
|
};
|
||||||
|
|
||||||
const generationMode = {
|
const generationMode = {
|
||||||
@ -451,6 +455,7 @@ async function loadSettings() {
|
|||||||
$('#sd_command_visible').prop('checked', extension_settings.sd.command_visible);
|
$('#sd_command_visible').prop('checked', extension_settings.sd.command_visible);
|
||||||
$('#sd_interactive_visible').prop('checked', extension_settings.sd.interactive_visible);
|
$('#sd_interactive_visible').prop('checked', extension_settings.sd.interactive_visible);
|
||||||
$('#sd_stability_style_preset').val(extension_settings.sd.stability_style_preset);
|
$('#sd_stability_style_preset').val(extension_settings.sd.stability_style_preset);
|
||||||
|
$('#sd_huggingface_model_id').val(extension_settings.sd.huggingface_model_id);
|
||||||
|
|
||||||
for (const style of extension_settings.sd.styles) {
|
for (const style of extension_settings.sd.styles) {
|
||||||
const option = document.createElement('option');
|
const option = document.createElement('option');
|
||||||
@ -718,29 +723,29 @@ function onChatChanged() {
|
|||||||
adjustElementScrollHeight();
|
adjustElementScrollHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
function adjustElementScrollHeight() {
|
async function adjustElementScrollHeight() {
|
||||||
if (!$('.sd_settings').is(':visible')) {
|
if (!$('.sd_settings').is(':visible')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
resetScrollHeight($('#sd_prompt_prefix'));
|
await resetScrollHeight($('#sd_prompt_prefix'));
|
||||||
resetScrollHeight($('#sd_negative_prompt'));
|
await resetScrollHeight($('#sd_negative_prompt'));
|
||||||
resetScrollHeight($('#sd_character_prompt'));
|
await resetScrollHeight($('#sd_character_prompt'));
|
||||||
resetScrollHeight($('#sd_character_negative_prompt'));
|
await resetScrollHeight($('#sd_character_negative_prompt'));
|
||||||
}
|
}
|
||||||
|
|
||||||
function onCharacterPromptInput() {
|
async function onCharacterPromptInput() {
|
||||||
const key = getCharaFilename(this_chid);
|
const key = getCharaFilename(this_chid);
|
||||||
extension_settings.sd.character_prompts[key] = $('#sd_character_prompt').val();
|
extension_settings.sd.character_prompts[key] = $('#sd_character_prompt').val();
|
||||||
resetScrollHeight($(this));
|
await resetScrollHeight($(this));
|
||||||
saveSettingsDebounced();
|
saveSettingsDebounced();
|
||||||
writePromptFieldsDebounced(this_chid);
|
writePromptFieldsDebounced(this_chid);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onCharacterNegativePromptInput() {
|
async function onCharacterNegativePromptInput() {
|
||||||
const key = getCharaFilename(this_chid);
|
const key = getCharaFilename(this_chid);
|
||||||
extension_settings.sd.character_negative_prompts[key] = $('#sd_character_negative_prompt').val();
|
extension_settings.sd.character_negative_prompts[key] = $('#sd_character_negative_prompt').val();
|
||||||
resetScrollHeight($(this));
|
await resetScrollHeight($(this));
|
||||||
saveSettingsDebounced();
|
saveSettingsDebounced();
|
||||||
writePromptFieldsDebounced(this_chid);
|
writePromptFieldsDebounced(this_chid);
|
||||||
}
|
}
|
||||||
@ -849,15 +854,15 @@ function onStepsInput() {
|
|||||||
saveSettingsDebounced();
|
saveSettingsDebounced();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onPromptPrefixInput() {
|
async function onPromptPrefixInput() {
|
||||||
extension_settings.sd.prompt_prefix = $('#sd_prompt_prefix').val();
|
extension_settings.sd.prompt_prefix = $('#sd_prompt_prefix').val();
|
||||||
resetScrollHeight($(this));
|
await resetScrollHeight($(this));
|
||||||
saveSettingsDebounced();
|
saveSettingsDebounced();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onNegativePromptInput() {
|
async function onNegativePromptInput() {
|
||||||
extension_settings.sd.negative_prompt = $('#sd_negative_prompt').val();
|
extension_settings.sd.negative_prompt = $('#sd_negative_prompt').val();
|
||||||
resetScrollHeight($(this));
|
await resetScrollHeight($(this));
|
||||||
saveSettingsDebounced();
|
saveSettingsDebounced();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1088,6 +1093,11 @@ function onComfyUrlInput() {
|
|||||||
saveSettingsDebounced();
|
saveSettingsDebounced();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onHFModelInput() {
|
||||||
|
extension_settings.sd.huggingface_model_id = $('#sd_huggingface_model_id').val();
|
||||||
|
saveSettingsDebounced();
|
||||||
|
}
|
||||||
|
|
||||||
function onComfyWorkflowChange() {
|
function onComfyWorkflowChange() {
|
||||||
extension_settings.sd.comfy_workflow = $('#sd_comfy_workflow').find(':selected').val();
|
extension_settings.sd.comfy_workflow = $('#sd_comfy_workflow').find(':selected').val();
|
||||||
saveSettingsDebounced();
|
saveSettingsDebounced();
|
||||||
@ -1095,7 +1105,18 @@ function onComfyWorkflowChange() {
|
|||||||
|
|
||||||
async function onStabilityKeyClick() {
|
async function onStabilityKeyClick() {
|
||||||
const popupText = 'Stability AI API Key:';
|
const popupText = 'Stability AI API Key:';
|
||||||
const key = await callGenericPopup(popupText, POPUP_TYPE.INPUT);
|
const key = await callGenericPopup(popupText, POPUP_TYPE.INPUT, '', {
|
||||||
|
customButtons: [{
|
||||||
|
text: 'Remove Key',
|
||||||
|
appendAtEnd: true,
|
||||||
|
result: POPUP_RESULT.NEGATIVE,
|
||||||
|
action: async () => {
|
||||||
|
await writeSecret(SECRET_KEYS.STABILITY, '');
|
||||||
|
toastr.success('API Key removed');
|
||||||
|
await loadSettingOptions();
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
|
||||||
if (!key) {
|
if (!key) {
|
||||||
return;
|
return;
|
||||||
@ -1221,7 +1242,16 @@ async function onModelChange() {
|
|||||||
extension_settings.sd.model = $('#sd_model').find(':selected').val();
|
extension_settings.sd.model = $('#sd_model').find(':selected').val();
|
||||||
saveSettingsDebounced();
|
saveSettingsDebounced();
|
||||||
|
|
||||||
const cloudSources = [sources.horde, sources.novel, sources.openai, sources.togetherai, sources.pollinations, sources.stability];
|
const cloudSources = [
|
||||||
|
sources.horde,
|
||||||
|
sources.novel,
|
||||||
|
sources.openai,
|
||||||
|
sources.togetherai,
|
||||||
|
sources.pollinations,
|
||||||
|
sources.stability,
|
||||||
|
sources.blockentropy,
|
||||||
|
sources.huggingface,
|
||||||
|
];
|
||||||
|
|
||||||
if (cloudSources.includes(extension_settings.sd.source)) {
|
if (cloudSources.includes(extension_settings.sd.source)) {
|
||||||
return;
|
return;
|
||||||
@ -1433,6 +1463,12 @@ async function loadSamplers() {
|
|||||||
case sources.stability:
|
case sources.stability:
|
||||||
samplers = ['N/A'];
|
samplers = ['N/A'];
|
||||||
break;
|
break;
|
||||||
|
case sources.blockentropy:
|
||||||
|
samplers = ['N/A'];
|
||||||
|
break;
|
||||||
|
case sources.huggingface:
|
||||||
|
samplers = ['N/A'];
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const sampler of samplers) {
|
for (const sampler of samplers) {
|
||||||
@ -1619,6 +1655,12 @@ async function loadModels() {
|
|||||||
case sources.stability:
|
case sources.stability:
|
||||||
models = await loadStabilityModels();
|
models = await loadStabilityModels();
|
||||||
break;
|
break;
|
||||||
|
case sources.blockentropy:
|
||||||
|
models = await loadBlockEntropyModels();
|
||||||
|
break;
|
||||||
|
case sources.huggingface:
|
||||||
|
models = [{ value: '', text: '<Enter Model ID above>' }];
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const model of models) {
|
for (const model of models) {
|
||||||
@ -1648,49 +1690,13 @@ async function loadStabilityModels() {
|
|||||||
async function loadPollinationsModels() {
|
async function loadPollinationsModels() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
value: 'pixart',
|
value: 'flux',
|
||||||
text: 'PixArt-αlpha',
|
text: 'FLUX.1 [schnell]',
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'playground',
|
|
||||||
text: 'Playground v2',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'dalle3xl',
|
|
||||||
text: 'DALL•E 3 XL',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'formulaxl',
|
|
||||||
text: 'FormulaXL',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'dreamshaper',
|
|
||||||
text: 'DreamShaper',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'deliberate',
|
|
||||||
text: 'Deliberate',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'dpo',
|
|
||||||
text: 'SDXL-DPO',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'swizz8',
|
|
||||||
text: 'Swizz8',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'juggernaut',
|
|
||||||
text: 'Juggernaut',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'turbo',
|
value: 'turbo',
|
||||||
text: 'SDXL Turbo',
|
text: 'SDXL Turbo',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
value: 'realvis',
|
|
||||||
text: 'Realistic Vision',
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1713,6 +1719,26 @@ async function loadTogetherAIModels() {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadBlockEntropyModels() {
|
||||||
|
if (!secret_state[SECRET_KEYS.BLOCKENTROPY]) {
|
||||||
|
console.debug('Block Entropy API key is not set.');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await fetch('/api/sd/blockentropy/models', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: getRequestHeaders(),
|
||||||
|
});
|
||||||
|
console.log(result);
|
||||||
|
if (result.ok) {
|
||||||
|
const data = await result.json();
|
||||||
|
console.log(data);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
async function loadHordeModels() {
|
async function loadHordeModels() {
|
||||||
const result = await fetch('/api/horde/sd-models', {
|
const result = await fetch('/api/horde/sd-models', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -1979,6 +2005,12 @@ async function loadSchedulers() {
|
|||||||
case sources.stability:
|
case sources.stability:
|
||||||
schedulers = ['N/A'];
|
schedulers = ['N/A'];
|
||||||
break;
|
break;
|
||||||
|
case sources.blockentropy:
|
||||||
|
schedulers = ['N/A'];
|
||||||
|
break;
|
||||||
|
case sources.huggingface:
|
||||||
|
schedulers = ['N/A'];
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const scheduler of schedulers) {
|
for (const scheduler of schedulers) {
|
||||||
@ -2055,6 +2087,12 @@ async function loadVaes() {
|
|||||||
case sources.stability:
|
case sources.stability:
|
||||||
vaes = ['N/A'];
|
vaes = ['N/A'];
|
||||||
break;
|
break;
|
||||||
|
case sources.blockentropy:
|
||||||
|
vaes = ['N/A'];
|
||||||
|
break;
|
||||||
|
case sources.huggingface:
|
||||||
|
vaes = ['N/A'];
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const vae of vaes) {
|
for (const vae of vaes) {
|
||||||
@ -2266,9 +2304,9 @@ async function generatePicture(initiator, args, trigger, message, callback) {
|
|||||||
const quietPrompt = getQuietPrompt(generationType, trigger);
|
const quietPrompt = getQuietPrompt(generationType, trigger);
|
||||||
const context = getContext();
|
const context = getContext();
|
||||||
|
|
||||||
// if context.characterId is not null, then we get context.characters[context.characterId].avatar, else we get groupId and context.groups[groupId].id
|
const characterName = context.groupId
|
||||||
// sadly, groups is not an array, but is a dict with keys being index numbers, so we have to filter it
|
? context.groups[Object.keys(context.groups).filter(x => context.groups[x].id === context.groupId)[0]]?.id?.toString()
|
||||||
const characterName = context.characterId ? context.characters[context.characterId].name : context.groups[Object.keys(context.groups).filter(x => context.groups[x].id === context.groupId)[0]]?.id?.toString();
|
: context.characters[context.characterId]?.name;
|
||||||
|
|
||||||
if (generationType == generationMode.BACKGROUND) {
|
if (generationType == generationMode.BACKGROUND) {
|
||||||
const callbackOriginal = callback;
|
const callbackOriginal = callback;
|
||||||
@ -2290,6 +2328,7 @@ async function generatePicture(initiator, args, trigger, message, callback) {
|
|||||||
|
|
||||||
const dimensions = setTypeSpecificDimensions(generationType);
|
const dimensions = setTypeSpecificDimensions(generationType);
|
||||||
const abortController = new AbortController();
|
const abortController = new AbortController();
|
||||||
|
const stopButton = document.getElementById('sd_stop_gen');
|
||||||
let negativePromptPrefix = args?.negative || '';
|
let negativePromptPrefix = args?.negative || '';
|
||||||
let imagePath = '';
|
let imagePath = '';
|
||||||
|
|
||||||
@ -2300,9 +2339,8 @@ async function generatePicture(initiator, args, trigger, message, callback) {
|
|||||||
const prompt = await getPrompt(generationType, message, trigger, quietPrompt, combineNegatives);
|
const prompt = await getPrompt(generationType, message, trigger, quietPrompt, combineNegatives);
|
||||||
console.log('Processed image prompt:', prompt);
|
console.log('Processed image prompt:', prompt);
|
||||||
|
|
||||||
eventSource.once(event_types.GENERATION_STOPPED, stopListener);
|
$(stopButton).show();
|
||||||
context.deactivateSendButtons();
|
eventSource.once(CUSTOM_STOP_EVENT, stopListener);
|
||||||
hideSwipeButtons();
|
|
||||||
|
|
||||||
if (typeof args?._abortController?.addEventListener === 'function') {
|
if (typeof args?._abortController?.addEventListener === 'function') {
|
||||||
args._abortController.addEventListener('abort', stopListener);
|
args._abortController.addEventListener('abort', stopListener);
|
||||||
@ -2311,13 +2349,13 @@ async function generatePicture(initiator, args, trigger, message, callback) {
|
|||||||
imagePath = await sendGenerationRequest(generationType, prompt, negativePromptPrefix, characterName, callback, initiator, abortController.signal);
|
imagePath = await sendGenerationRequest(generationType, prompt, negativePromptPrefix, characterName, callback, initiator, abortController.signal);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.trace(err);
|
console.trace(err);
|
||||||
throw new Error('SD prompt text generation failed.');
|
toastr.error('SD prompt text generation failed. Reason: ' + err, 'Image Generation');
|
||||||
|
throw new Error('SD prompt text generation failed. Reason: ' + err);
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
|
$(stopButton).hide();
|
||||||
restoreOriginalDimensions(dimensions);
|
restoreOriginalDimensions(dimensions);
|
||||||
eventSource.removeListener(event_types.GENERATION_STOPPED, stopListener);
|
eventSource.removeListener(CUSTOM_STOP_EVENT, stopListener);
|
||||||
context.activateSendButtons();
|
|
||||||
showSwipeButtons();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return imagePath;
|
return imagePath;
|
||||||
@ -2583,6 +2621,12 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP
|
|||||||
case sources.stability:
|
case sources.stability:
|
||||||
result = await generateStabilityImage(prefixedPrompt, negativePrompt, signal);
|
result = await generateStabilityImage(prefixedPrompt, negativePrompt, signal);
|
||||||
break;
|
break;
|
||||||
|
case sources.blockentropy:
|
||||||
|
result = await generateBlockEntropyImage(prefixedPrompt, negativePrompt, signal);
|
||||||
|
break;
|
||||||
|
case sources.huggingface:
|
||||||
|
result = await generateHuggingFaceImage(prefixedPrompt, signal);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!result.data) {
|
if (!result.data) {
|
||||||
@ -2638,6 +2682,40 @@ async function generateTogetherAIImage(prompt, negativePrompt, signal) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function generateBlockEntropyImage(prompt, negativePrompt, signal) {
|
||||||
|
const result = await fetch('/api/sd/blockentropy/generate', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: getRequestHeaders(),
|
||||||
|
signal: signal,
|
||||||
|
body: JSON.stringify({
|
||||||
|
prompt: prompt,
|
||||||
|
negative_prompt: negativePrompt,
|
||||||
|
model: extension_settings.sd.model,
|
||||||
|
steps: extension_settings.sd.steps,
|
||||||
|
width: extension_settings.sd.width,
|
||||||
|
height: extension_settings.sd.height,
|
||||||
|
seed: extension_settings.sd.seed >= 0 ? extension_settings.sd.seed : undefined,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.ok) {
|
||||||
|
const data = await result.json();
|
||||||
|
|
||||||
|
// Default format is 'jpg'
|
||||||
|
let format = 'jpg';
|
||||||
|
|
||||||
|
// Check if a format is specified in the result
|
||||||
|
if (data.format) {
|
||||||
|
format = data.format.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
return { format: format, data: data.images[0] };
|
||||||
|
} else {
|
||||||
|
const text = await result.text();
|
||||||
|
throw new Error(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates an image using the Pollinations API.
|
* Generates an image using the Pollinations API.
|
||||||
* @param {string} prompt - The main instruction used to guide the image generation.
|
* @param {string} prompt - The main instruction used to guide the image generation.
|
||||||
@ -3182,6 +3260,34 @@ async function generateComfyImage(prompt, negativePrompt, signal) {
|
|||||||
return { format: 'png', data: await promptResult.text() };
|
return { format: 'png', data: await promptResult.text() };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates an image in Hugging Face Inference API using the provided prompt and configuration settings (model selected).
|
||||||
|
* @param {string} prompt - The main instruction used to guide the image generation.
|
||||||
|
* @param {AbortSignal} signal - An AbortSignal object that can be used to cancel the request.
|
||||||
|
* @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.
|
||||||
|
*/
|
||||||
|
async function generateHuggingFaceImage(prompt, signal) {
|
||||||
|
const result = await fetch('/api/sd/huggingface/generate', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: getRequestHeaders(),
|
||||||
|
signal: signal,
|
||||||
|
body: JSON.stringify({
|
||||||
|
model: extension_settings.sd.huggingface_model_id,
|
||||||
|
prompt: prompt,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.ok) {
|
||||||
|
const data = await result.json();
|
||||||
|
return { format: 'jpg', data: data.image };
|
||||||
|
} else {
|
||||||
|
const text = await result.text();
|
||||||
|
throw new Error(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async function onComfyOpenWorkflowEditorClick() {
|
async function onComfyOpenWorkflowEditorClick() {
|
||||||
let workflow = await (await fetch('/api/sd/comfy/workflow', {
|
let workflow = await (await fetch('/api/sd/comfy/workflow', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -3347,11 +3453,15 @@ async function sendMessage(prompt, image, generationType, additionalNegativePref
|
|||||||
generationType: generationType,
|
generationType: generationType,
|
||||||
negative: additionalNegativePrefix,
|
negative: additionalNegativePrefix,
|
||||||
inline_image: false,
|
inline_image: false,
|
||||||
|
image_swipes: [image],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
context.chat.push(message);
|
context.chat.push(message);
|
||||||
|
const messageId = context.chat.length - 1;
|
||||||
|
await eventSource.emit(event_types.MESSAGE_RECEIVED, messageId);
|
||||||
context.addOneMessage(message);
|
context.addOneMessage(message);
|
||||||
context.saveChat();
|
await eventSource.emit(event_types.CHARACTER_MESSAGE_RENDERED, messageId);
|
||||||
|
await context.saveChat();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -3395,7 +3505,7 @@ async function addSDGenButtons() {
|
|||||||
$(document).on('click touchend', function (e) {
|
$(document).on('click touchend', function (e) {
|
||||||
const target = $(e.target);
|
const target = $(e.target);
|
||||||
if (target.is(dropdown) || target.closest(dropdown).length) return;
|
if (target.is(dropdown) || target.closest(dropdown).length) return;
|
||||||
if (target.is(button) && !dropdown.is(':visible') && $('#send_but').is(':visible')) {
|
if ((target.is(button) || target.closest(button).length) && !dropdown.is(':visible')) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
dropdown.fadeIn(animation_duration);
|
dropdown.fadeIn(animation_duration);
|
||||||
@ -3425,6 +3535,10 @@ async function addSDGenButtons() {
|
|||||||
generatePicture(initiators.wand, {}, param);
|
generatePicture(initiators.wand, {}, param);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const stopGenButton = $('#sd_stop_gen');
|
||||||
|
stopGenButton.hide();
|
||||||
|
stopGenButton.on('click', () => eventSource.emit(CUSTOM_STOP_EVENT));
|
||||||
}
|
}
|
||||||
|
|
||||||
function isValidState() {
|
function isValidState() {
|
||||||
@ -3451,6 +3565,10 @@ function isValidState() {
|
|||||||
return true;
|
return true;
|
||||||
case sources.stability:
|
case sources.stability:
|
||||||
return secret_state[SECRET_KEYS.STABILITY];
|
return secret_state[SECRET_KEYS.STABILITY];
|
||||||
|
case sources.blockentropy:
|
||||||
|
return secret_state[SECRET_KEYS.BLOCKENTROPY];
|
||||||
|
case sources.huggingface:
|
||||||
|
return secret_state[SECRET_KEYS.HUGGINGFACE];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3480,7 +3598,9 @@ async function sdMessageButton(e) {
|
|||||||
const $mes = $icon.closest('.mes');
|
const $mes = $icon.closest('.mes');
|
||||||
const message_id = $mes.attr('mesid');
|
const message_id = $mes.attr('mesid');
|
||||||
const message = context.chat[message_id];
|
const message = context.chat[message_id];
|
||||||
const characterFileName = context.characterId ? context.characters[context.characterId].name : context.groups[Object.keys(context.groups).filter(x => context.groups[x].id === context.groupId)[0]]?.id?.toString();
|
const characterFileName = context.groupId
|
||||||
|
? context.groups[Object.keys(context.groups).filter(x => context.groups[x].id === context.groupId)[0]]?.id?.toString()
|
||||||
|
: context.characters[context.characterId]?.name;
|
||||||
const messageText = message?.mes;
|
const messageText = message?.mes;
|
||||||
const hasSavedImage = message?.extra?.image && message?.extra?.title;
|
const hasSavedImage = message?.extra?.image && message?.extra?.title;
|
||||||
const hasSavedNegative = message?.extra?.negative;
|
const hasSavedNegative = message?.extra?.negative;
|
||||||
@ -3524,10 +3644,23 @@ async function sdMessageButton(e) {
|
|||||||
|
|
||||||
function saveGeneratedImage(prompt, image, generationType, negative) {
|
function saveGeneratedImage(prompt, image, generationType, negative) {
|
||||||
// Some message sources may not create the extra object
|
// Some message sources may not create the extra object
|
||||||
if (typeof message.extra !== 'object') {
|
if (typeof message.extra !== 'object' || message.extra === null) {
|
||||||
message.extra = {};
|
message.extra = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add image to the swipe list if it's not already there
|
||||||
|
if (!Array.isArray(message.extra.image_swipes)) {
|
||||||
|
message.extra.image_swipes = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const swipes = message.extra.image_swipes;
|
||||||
|
|
||||||
|
if (message.extra.image && !swipes.includes(message.extra.image)) {
|
||||||
|
swipes.push(message.extra.image);
|
||||||
|
}
|
||||||
|
|
||||||
|
swipes.push(image);
|
||||||
|
|
||||||
// If already contains an image and it's not inline - leave it as is
|
// If already contains an image and it's not inline - leave it as is
|
||||||
message.extra.inline_image = message.extra.image && !message.extra.inline_image ? false : true;
|
message.extra.inline_image = message.extra.image && !message.extra.inline_image ? false : true;
|
||||||
message.extra.image = image;
|
message.extra.image = image;
|
||||||
@ -3566,6 +3699,99 @@ async function writePromptFields(characterId) {
|
|||||||
await writeExtensionField(characterId, 'sd_character_prompt', promptObject);
|
await writeExtensionField(characterId, 'sd_character_prompt', promptObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switches an image to the next or previous one in the swipe list.
|
||||||
|
* @param {object} args Event arguments
|
||||||
|
* @param {any} args.message Message object
|
||||||
|
* @param {JQuery<HTMLElement>} args.element Message element
|
||||||
|
* @param {string} args.direction Swipe direction
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async function onImageSwiped({ message, element, direction }) {
|
||||||
|
const context = getContext();
|
||||||
|
const animationClass = 'fa-fade';
|
||||||
|
const messageImg = element.find('.mes_img');
|
||||||
|
|
||||||
|
// Current image is already animating
|
||||||
|
if (messageImg.hasClass(animationClass)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const swipes = message?.extra?.image_swipes;
|
||||||
|
|
||||||
|
if (!Array.isArray(swipes)) {
|
||||||
|
console.warn('No image swipes found in the message');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentIndex = swipes.indexOf(message.extra.image);
|
||||||
|
|
||||||
|
if (currentIndex === -1) {
|
||||||
|
console.warn('Current image not found in the swipes');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Switch to previous image or wrap around if at the beginning
|
||||||
|
if (direction === 'left') {
|
||||||
|
const newIndex = currentIndex === 0 ? swipes.length - 1 : currentIndex - 1;
|
||||||
|
message.extra.image = swipes[newIndex];
|
||||||
|
|
||||||
|
// Update the image in the message
|
||||||
|
appendMediaToMessage(message, element, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Switch to next image or generate a new one if at the end
|
||||||
|
if (direction === 'right') {
|
||||||
|
const newIndex = currentIndex === swipes.length - 1 ? swipes.length : currentIndex + 1;
|
||||||
|
|
||||||
|
if (newIndex === swipes.length) {
|
||||||
|
const abortController = new AbortController();
|
||||||
|
const swipeControls = element.find('.mes_img_swipes');
|
||||||
|
const stopButton = document.getElementById('sd_stop_gen');
|
||||||
|
const stopListener = () => abortController.abort('Aborted by user');
|
||||||
|
const generationType = message?.extra?.generationType ?? generationMode.FREE;
|
||||||
|
const dimensions = setTypeSpecificDimensions(generationType);
|
||||||
|
const originalSeed = extension_settings.sd.seed;
|
||||||
|
extension_settings.sd.seed = Math.round(Math.random() * Number.MAX_SAFE_INTEGER);
|
||||||
|
let imagePath = '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
$(stopButton).show();
|
||||||
|
eventSource.once(CUSTOM_STOP_EVENT, stopListener);
|
||||||
|
const callback = () => { };
|
||||||
|
const hasNegative = message.extra.negative;
|
||||||
|
const prompt = await refinePrompt(message.extra.title, false, false);
|
||||||
|
const negativePromptPrefix = hasNegative ? await refinePrompt(message.extra.negative, false, true) : '';
|
||||||
|
const characterName = context.groupId
|
||||||
|
? context.groups[Object.keys(context.groups).filter(x => context.groups[x].id === context.groupId)[0]]?.id?.toString()
|
||||||
|
: context.characters[context.characterId]?.name;
|
||||||
|
|
||||||
|
messageImg.addClass(animationClass);
|
||||||
|
swipeControls.hide();
|
||||||
|
imagePath = await sendGenerationRequest(generationType, prompt, negativePromptPrefix, characterName, callback, initiators.swipe, abortController.signal);
|
||||||
|
} finally {
|
||||||
|
$(stopButton).hide();
|
||||||
|
messageImg.removeClass(animationClass);
|
||||||
|
swipeControls.show();
|
||||||
|
eventSource.removeListener(CUSTOM_STOP_EVENT, stopListener);
|
||||||
|
restoreOriginalDimensions(dimensions);
|
||||||
|
extension_settings.sd.seed = originalSeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!imagePath) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
swipes.push(imagePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
message.extra.image = swipes[newIndex];
|
||||||
|
appendMediaToMessage(message, element, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
await context.saveChat();
|
||||||
|
}
|
||||||
|
|
||||||
jQuery(async () => {
|
jQuery(async () => {
|
||||||
await addSDGenButtons();
|
await addSDGenButtons();
|
||||||
|
|
||||||
@ -3683,6 +3909,7 @@ jQuery(async () => {
|
|||||||
$('#sd_swap_dimensions').on('click', onSwapDimensionsClick);
|
$('#sd_swap_dimensions').on('click', onSwapDimensionsClick);
|
||||||
$('#sd_stability_key').on('click', onStabilityKeyClick);
|
$('#sd_stability_key').on('click', onStabilityKeyClick);
|
||||||
$('#sd_stability_style_preset').on('change', onStabilityStylePresetChange);
|
$('#sd_stability_style_preset').on('change', onStabilityStylePresetChange);
|
||||||
|
$('#sd_huggingface_model_id').on('input', onHFModelInput);
|
||||||
|
|
||||||
$('.sd_settings .inline-drawer-toggle').on('click', function () {
|
$('.sd_settings .inline-drawer-toggle').on('click', function () {
|
||||||
initScrollHeight($('#sd_prompt_prefix'));
|
initScrollHeight($('#sd_prompt_prefix'));
|
||||||
@ -3704,6 +3931,8 @@ jQuery(async () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
eventSource.on(event_types.IMAGE_SWIPED, onImageSwiped);
|
||||||
|
|
||||||
eventSource.on(event_types.CHAT_CHANGED, onChatChanged);
|
eventSource.on(event_types.CHAT_CHANGED, onChatChanged);
|
||||||
|
|
||||||
await loadSettings();
|
await loadSettings();
|
||||||
|
@ -29,7 +29,8 @@
|
|||||||
</label>
|
</label>
|
||||||
<label for="sd_expand" class="checkbox_label" data-i18n="[title]sd_expand" title="Automatically extend prompts using text generation model">
|
<label for="sd_expand" class="checkbox_label" data-i18n="[title]sd_expand" title="Automatically extend prompts using text generation model">
|
||||||
<input id="sd_expand" type="checkbox" />
|
<input id="sd_expand" type="checkbox" />
|
||||||
<span data-i18n="sd_expand_txt">Auto-enhance prompts</span>
|
<span data-i18n="sd_expand_txt">Auto-extend prompts</span>
|
||||||
|
<span class="right_menu_button fa-solid fa-triangle-exclamation" data-i18n="[title]sd_expand_warning" title="May produce unexpected results. Manual prompt editing is recommended."></span>
|
||||||
</label>
|
</label>
|
||||||
<label for="sd_snap" class="checkbox_label" data-i18n="[title]sd_snap" title="Snap generation requests with a forced aspect ratio (portraits, backgrounds) to the nearest known resolution, while trying to preserve the absolute pixel counts (recommended for SDXL).">
|
<label for="sd_snap" class="checkbox_label" data-i18n="[title]sd_snap" title="Snap generation requests with a forced aspect ratio (portraits, backgrounds) to the nearest known resolution, while trying to preserve the absolute pixel counts (recommended for SDXL).">
|
||||||
<input id="sd_snap" type="checkbox" />
|
<input id="sd_snap" type="checkbox" />
|
||||||
@ -37,9 +38,11 @@
|
|||||||
</label>
|
</label>
|
||||||
<label for="sd_source" data-i18n="Source">Source</label>
|
<label for="sd_source" data-i18n="Source">Source</label>
|
||||||
<select id="sd_source">
|
<select id="sd_source">
|
||||||
|
<option value="blockentropy">Block Entropy</option>
|
||||||
<option value="comfy">ComfyUI</option>
|
<option value="comfy">ComfyUI</option>
|
||||||
<option value="drawthings">DrawThings HTTP API</option>
|
<option value="drawthings">DrawThings HTTP API</option>
|
||||||
<option value="extras">Extras API (local / remote)</option>
|
<option value="extras">Extras API (local / remote)</option>
|
||||||
|
<option value="huggingface">HuggingFace Inference API (serverless)</option>
|
||||||
<option value="novel">NovelAI Diffusion</option>
|
<option value="novel">NovelAI Diffusion</option>
|
||||||
<option value="openai">OpenAI (DALL-E)</option>
|
<option value="openai">OpenAI (DALL-E)</option>
|
||||||
<option value="pollinations">Pollinations</option>
|
<option value="pollinations">Pollinations</option>
|
||||||
@ -81,6 +84,11 @@
|
|||||||
<!-- (Original Text)<b>Important:</b> run DrawThings app with HTTP API switch enabled in the UI! The server must be accessible from the SillyTavern host machine. -->
|
<!-- (Original Text)<b>Important:</b> run DrawThings app with HTTP API switch enabled in the UI! The server must be accessible from the SillyTavern host machine. -->
|
||||||
<i><b data-i18n="Important:">Important:</b></i><i data-i18n="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.</i>
|
<i><b data-i18n="Important:">Important:</b></i><i data-i18n="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.</i>
|
||||||
</div>
|
</div>
|
||||||
|
<div data-sd-source="huggingface">
|
||||||
|
<i>Hint: Save an API key in the Hugging Face (Text Completion) API settings to use it here.</i>
|
||||||
|
<label for="sd_huggingface_model_id" data-i18n="Model ID">Model ID</label>
|
||||||
|
<input id="sd_huggingface_model_id" type="text" class="text_pole" data-i18n="[placeholder]e.g. black-forest-labs/FLUX.1-dev" placeholder="e.g. black-forest-labs/FLUX.1-dev" value="" />
|
||||||
|
</div>
|
||||||
<div data-sd-source="vlad">
|
<div data-sd-source="vlad">
|
||||||
<label for="sd_vlad_url">SD.Next API URL</label>
|
<label for="sd_vlad_url">SD.Next API URL</label>
|
||||||
<div class="flex-container flexnowrap">
|
<div class="flex-container flexnowrap">
|
||||||
@ -378,7 +386,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div data-sd-source="novel,togetherai,pollinations,comfy,drawthings,vlad,auto,horde,extras,stability" class="marginTop5">
|
<div data-sd-source="novel,togetherai,pollinations,comfy,drawthings,vlad,auto,horde,extras,stability,blockentropy" class="marginTop5">
|
||||||
<label for="sd_seed">
|
<label for="sd_seed">
|
||||||
<span data-i18n="Seed">Seed</span>
|
<span data-i18n="Seed">Seed</span>
|
||||||
<small data-i18n="(-1 for random)">(-1 for random)</small>
|
<small data-i18n="(-1 for random)">(-1 for random)</small>
|
||||||
|
@ -59,8 +59,8 @@ async function doTokenCounter() {
|
|||||||
$('#tokenized_chunks_display').text('—');
|
$('#tokenized_chunks_display').text('—');
|
||||||
}
|
}
|
||||||
|
|
||||||
resetScrollHeight($('#token_counter_textarea'));
|
await resetScrollHeight($('#token_counter_textarea'));
|
||||||
resetScrollHeight($('#token_counter_ids'));
|
await resetScrollHeight($('#token_counter_ids'));
|
||||||
}, debounce_timeout.relaxed);
|
}, debounce_timeout.relaxed);
|
||||||
dialog.find('#token_counter_textarea').on('input', () => countDebounced());
|
dialog.find('#token_counter_textarea').on('input', () => countDebounced());
|
||||||
|
|
||||||
@ -134,7 +134,8 @@ jQuery(() => {
|
|||||||
</div>`;
|
</div>`;
|
||||||
$('#token_counter_wand_container').append(buttonHtml);
|
$('#token_counter_wand_container').append(buttonHtml);
|
||||||
$('#token_counter').on('click', doTokenCounter);
|
$('#token_counter').on('click', doTokenCounter);
|
||||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'count',
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||||
|
name: 'count',
|
||||||
callback: async () => String(await doCount()),
|
callback: async () => String(await doCount()),
|
||||||
returns: 'number of tokens',
|
returns: 'number of tokens',
|
||||||
helpString: 'Counts the number of tokens in the current chat.',
|
helpString: 'Counts the number of tokens in the current chat.',
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<div id="translate_chat" class="list-group-item flex-container flexGap5">
|
<div id="translate_chat" class="list-group-item flex-container flexGap5">
|
||||||
<div class="fa-solid fa-language extensionsMenuExtensionButton" /></div>
|
<div class="fa-solid fa-language extensionsMenuExtensionButton"></div>
|
||||||
<span data-i18n="ext_translate_btn_chat">Translate Chat</span>
|
<span data-i18n="ext_translate_btn_chat">Translate Chat</span>
|
||||||
</div>
|
</div>
|
||||||
<div id="translate_input_message" class="list-group-item flex-container flexGap5">
|
<div id="translate_input_message" class="list-group-item flex-container flexGap5">
|
||||||
<div class="fa-solid fa-keyboard extensionsMenuExtensionButton" /></div>
|
<div class="fa-solid fa-keyboard extensionsMenuExtensionButton"></div>
|
||||||
<span data-i18n="ext_translate_btn_input">Translate Input</span>
|
<span data-i18n="ext_translate_btn_input">Translate Input</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
updateMessageBlock,
|
updateMessageBlock,
|
||||||
} from '../../../script.js';
|
} from '../../../script.js';
|
||||||
import { extension_settings, getContext, renderExtensionTemplateAsync } from '../../extensions.js';
|
import { extension_settings, getContext, renderExtensionTemplateAsync } from '../../extensions.js';
|
||||||
import { POPUP_TYPE, callGenericPopup } from '../../popup.js';
|
import { POPUP_RESULT, POPUP_TYPE, callGenericPopup } from '../../popup.js';
|
||||||
import { findSecret, secret_state, writeSecret } from '../../secrets.js';
|
import { findSecret, secret_state, writeSecret } from '../../secrets.js';
|
||||||
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
|
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
|
||||||
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
|
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
|
||||||
@ -621,7 +621,18 @@ jQuery(async () => {
|
|||||||
const secretKey = extension_settings.translate.provider + '_url';
|
const secretKey = extension_settings.translate.provider + '_url';
|
||||||
const savedUrl = secret_state[secretKey] ? await findSecret(secretKey) : '';
|
const savedUrl = secret_state[secretKey] ? await findSecret(secretKey) : '';
|
||||||
|
|
||||||
const url = await callGenericPopup(popupText, POPUP_TYPE.INPUT, savedUrl);
|
const url = await callGenericPopup(popupText, POPUP_TYPE.INPUT, savedUrl,{
|
||||||
|
customButtons: [{
|
||||||
|
text: 'Remove URL',
|
||||||
|
appendAtEnd: true,
|
||||||
|
result: POPUP_RESULT.NEGATIVE,
|
||||||
|
action: async () => {
|
||||||
|
await writeSecret(secretKey, '');
|
||||||
|
toastr.success('API URL removed');
|
||||||
|
$('#translate_url_button').toggleClass('success', !!secret_state[secretKey]);
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
|
||||||
if (url == false || url == '') {
|
if (url == false || url == '') {
|
||||||
return;
|
return;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { getRequestHeaders } from '../../../script.js';
|
import { getRequestHeaders } from '../../../script.js';
|
||||||
import { POPUP_TYPE, callGenericPopup } from '../../popup.js';
|
import { POPUP_RESULT, POPUP_TYPE, callGenericPopup } from '../../popup.js';
|
||||||
import { SECRET_KEYS, findSecret, secret_state, writeSecret } from '../../secrets.js';
|
import { SECRET_KEYS, findSecret, secret_state, writeSecret } from '../../secrets.js';
|
||||||
import { getPreviewString, saveTtsProviderSettings } from './index.js';
|
import { getPreviewString, saveTtsProviderSettings } from './index.js';
|
||||||
export { AzureTtsProvider };
|
export { AzureTtsProvider };
|
||||||
@ -70,7 +70,19 @@ class AzureTtsProvider {
|
|||||||
const popupText = 'Azure TTS API Key';
|
const popupText = 'Azure TTS API Key';
|
||||||
const savedKey = secret_state[SECRET_KEYS.AZURE_TTS] ? await findSecret(SECRET_KEYS.AZURE_TTS) : '';
|
const savedKey = secret_state[SECRET_KEYS.AZURE_TTS] ? await findSecret(SECRET_KEYS.AZURE_TTS) : '';
|
||||||
|
|
||||||
const key = await callGenericPopup(popupText, POPUP_TYPE.INPUT, savedKey);
|
const key = await callGenericPopup(popupText, POPUP_TYPE.INPUT, savedKey, {
|
||||||
|
customButtons: [{
|
||||||
|
text: 'Remove Key',
|
||||||
|
appendAtEnd: true,
|
||||||
|
result: POPUP_RESULT.NEGATIVE,
|
||||||
|
action: async () => {
|
||||||
|
await writeSecret(SECRET_KEYS.AZURE_TTS, '');
|
||||||
|
$('#azure_tts_key').toggleClass('success', secret_state[SECRET_KEYS.AZURE_TTS]);
|
||||||
|
toastr.success('API Key removed');
|
||||||
|
await this.onRefreshClick();
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
|
||||||
if (key == false || key == '') {
|
if (key == false || key == '') {
|
||||||
return;
|
return;
|
||||||
|
@ -9,6 +9,7 @@ import { SystemTtsProvider } from './system.js';
|
|||||||
import { NovelTtsProvider } from './novel.js';
|
import { NovelTtsProvider } from './novel.js';
|
||||||
import { power_user } from '../../power-user.js';
|
import { power_user } from '../../power-user.js';
|
||||||
import { OpenAITtsProvider } from './openai.js';
|
import { OpenAITtsProvider } from './openai.js';
|
||||||
|
import { OpenAICompatibleTtsProvider } from './openai-compatible.js';
|
||||||
import { XTTSTtsProvider } from './xtts.js';
|
import { XTTSTtsProvider } from './xtts.js';
|
||||||
import { VITSTtsProvider } from './vits.js';
|
import { VITSTtsProvider } from './vits.js';
|
||||||
import { GSVITtsProvider } from './gsvi.js';
|
import { GSVITtsProvider } from './gsvi.js';
|
||||||
@ -82,20 +83,21 @@ export function getPreviewString(lang) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ttsProviders = {
|
const ttsProviders = {
|
||||||
ElevenLabs: ElevenLabsTtsProvider,
|
AllTalk: AllTalkTtsProvider,
|
||||||
Silero: SileroTtsProvider,
|
Azure: AzureTtsProvider,
|
||||||
XTTSv2: XTTSTtsProvider,
|
|
||||||
VITS: VITSTtsProvider,
|
|
||||||
GSVI: GSVITtsProvider,
|
|
||||||
SBVits2: SBVits2TtsProvider,
|
|
||||||
System: SystemTtsProvider,
|
|
||||||
Coqui: CoquiTtsProvider,
|
Coqui: CoquiTtsProvider,
|
||||||
Edge: EdgeTtsProvider,
|
Edge: EdgeTtsProvider,
|
||||||
|
ElevenLabs: ElevenLabsTtsProvider,
|
||||||
|
GSVI: GSVITtsProvider,
|
||||||
Novel: NovelTtsProvider,
|
Novel: NovelTtsProvider,
|
||||||
OpenAI: OpenAITtsProvider,
|
OpenAI: OpenAITtsProvider,
|
||||||
AllTalk: AllTalkTtsProvider,
|
'OpenAI Compatible': OpenAICompatibleTtsProvider,
|
||||||
|
SBVits2: SBVits2TtsProvider,
|
||||||
|
Silero: SileroTtsProvider,
|
||||||
SpeechT5: SpeechT5TtsProvider,
|
SpeechT5: SpeechT5TtsProvider,
|
||||||
Azure: AzureTtsProvider,
|
System: SystemTtsProvider,
|
||||||
|
VITS: VITSTtsProvider,
|
||||||
|
XTTSv2: XTTSTtsProvider,
|
||||||
};
|
};
|
||||||
let ttsProvider;
|
let ttsProvider;
|
||||||
let ttsProviderName;
|
let ttsProviderName;
|
||||||
@ -753,6 +755,11 @@ async function onMessageEvent(messageId, lastCharIndex) {
|
|||||||
const message = structuredClone(context.chat[messageId]);
|
const message = structuredClone(context.chat[messageId]);
|
||||||
const hashNew = getStringHash(message?.mes ?? '');
|
const hashNew = getStringHash(message?.mes ?? '');
|
||||||
|
|
||||||
|
// Ignore prompt-hidden messages
|
||||||
|
if (message.is_system) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// if no new messages, or same message, or same message hash, do nothing
|
// if no new messages, or same message, or same message hash, do nothing
|
||||||
if (hashNew === lastMessageHash) {
|
if (hashNew === lastMessageHash) {
|
||||||
return;
|
return;
|
||||||
|
193
public/scripts/extensions/tts/openai-compatible.js
Normal file
193
public/scripts/extensions/tts/openai-compatible.js
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
import { getRequestHeaders } from '../../../script.js';
|
||||||
|
import { callGenericPopup, POPUP_RESULT, POPUP_TYPE } from '../../popup.js';
|
||||||
|
import { findSecret, SECRET_KEYS, secret_state, writeSecret } from '../../secrets.js';
|
||||||
|
import { getPreviewString, saveTtsProviderSettings } from './index.js';
|
||||||
|
|
||||||
|
export { OpenAICompatibleTtsProvider };
|
||||||
|
|
||||||
|
class OpenAICompatibleTtsProvider {
|
||||||
|
settings;
|
||||||
|
voices = [];
|
||||||
|
separator = ' . ';
|
||||||
|
|
||||||
|
audioElement = document.createElement('audio');
|
||||||
|
|
||||||
|
defaultSettings = {
|
||||||
|
voiceMap: {},
|
||||||
|
model: 'tts-1',
|
||||||
|
speed: 1,
|
||||||
|
available_voices: ['alloy', 'echo', 'fable', 'onyx', 'nova', 'shimmer'],
|
||||||
|
provider_endpoint: 'http://127.0.0.1:8000/v1/audio/speech',
|
||||||
|
};
|
||||||
|
|
||||||
|
get settingsHtml() {
|
||||||
|
let html = `
|
||||||
|
<label for="openai_compatible_tts_endpoint">Provider Endpoint:</label>
|
||||||
|
<div class="flex-container alignItemsCenter">
|
||||||
|
<div class="flex1">
|
||||||
|
<input id="openai_compatible_tts_endpoint" type="text" class="text_pole" maxlength="250" value="${this.defaultSettings.provider_endpoint}"/>
|
||||||
|
</div>
|
||||||
|
<div id="openai_compatible_tts_key" class="menu_button menu_button_icon">
|
||||||
|
<i class="fa-solid fa-key"></i>
|
||||||
|
<span>API Key</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<label for="openai_compatible_model">Model:</label>
|
||||||
|
<input id="openai_compatible_model" type="text" class="text_pole" maxlength="250" value="${this.defaultSettings.model}"/>
|
||||||
|
<label for="openai_compatible_tts_voices">Available Voices (comma separated):</label>
|
||||||
|
<input id="openai_compatible_tts_voices" type="text" class="text_pole" maxlength="250" value="${this.defaultSettings.available_voices.join()}"/>
|
||||||
|
<label for="openai_compatible_tts_speed">Speed: <span id="openai_compatible_tts_speed_output"></span></label>
|
||||||
|
<input type="range" id="openai_compatible_tts_speed" value="1" min="0.25" max="4" step="0.05">`;
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadSettings(settings) {
|
||||||
|
// Populate Provider UI given input settings
|
||||||
|
if (Object.keys(settings).length == 0) {
|
||||||
|
console.info('Using default TTS Provider settings');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only accept keys defined in defaultSettings
|
||||||
|
this.settings = this.defaultSettings;
|
||||||
|
|
||||||
|
for (const key in settings) {
|
||||||
|
if (key in this.settings) {
|
||||||
|
this.settings[key] = settings[key];
|
||||||
|
} else {
|
||||||
|
throw `Invalid setting passed to TTS Provider: ${key}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#openai_compatible_tts_endpoint').val(this.settings.provider_endpoint);
|
||||||
|
$('#openai_compatible_tts_endpoint').on('input', () => { this.onSettingsChange(); });
|
||||||
|
|
||||||
|
$('#openai_compatible_model').val(this.defaultSettings.model);
|
||||||
|
$('#openai_compatible_model').on('input', () => { this.onSettingsChange(); });
|
||||||
|
|
||||||
|
$('#openai_compatible_tts_voices').val(this.settings.available_voices.join());
|
||||||
|
$('#openai_compatible_tts_voices').on('input', () => { this.onSettingsChange(); });
|
||||||
|
|
||||||
|
$('#openai_compatible_tts_speed').val(this.settings.speed);
|
||||||
|
$('#openai_compatible_tts_speed').on('input', () => {
|
||||||
|
this.onSettingsChange();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#openai_compatible_tts_speed_output').text(this.settings.speed);
|
||||||
|
|
||||||
|
$('#openai_compatible_tts_key').toggleClass('success', secret_state[SECRET_KEYS.CUSTOM_OPENAI_TTS]);
|
||||||
|
$('#openai_compatible_tts_key').on('click', async () => {
|
||||||
|
const popupText = 'OpenAI-compatible TTS API Key';
|
||||||
|
const savedKey = secret_state[SECRET_KEYS.CUSTOM_OPENAI_TTS] ? await findSecret(SECRET_KEYS.CUSTOM_OPENAI_TTS) : '';
|
||||||
|
|
||||||
|
const key = await callGenericPopup(popupText, POPUP_TYPE.INPUT, savedKey, {
|
||||||
|
customButtons: [{
|
||||||
|
text: 'Remove Key',
|
||||||
|
appendAtEnd: true,
|
||||||
|
result: POPUP_RESULT.NEGATIVE,
|
||||||
|
action: async () => {
|
||||||
|
await writeSecret(SECRET_KEYS.CUSTOM_OPENAI_TTS, '');
|
||||||
|
$('#openai_compatible_tts_key').toggleClass('success', secret_state[SECRET_KEYS.CUSTOM_OPENAI_TTS]);
|
||||||
|
toastr.success('API Key removed');
|
||||||
|
await this.onRefreshClick();
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (key == false || key == '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await writeSecret(SECRET_KEYS.CUSTOM_OPENAI_TTS, String(key));
|
||||||
|
|
||||||
|
toastr.success('API Key saved');
|
||||||
|
$('#openai_compatible_tts_key').toggleClass('success', secret_state[SECRET_KEYS.CUSTOM_OPENAI_TTS]);
|
||||||
|
await this.onRefreshClick();
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.checkReady();
|
||||||
|
|
||||||
|
console.debug('OpenAI Compatible TTS: Settings loaded');
|
||||||
|
}
|
||||||
|
|
||||||
|
onSettingsChange() {
|
||||||
|
// Update dynamically
|
||||||
|
this.settings.provider_endpoint = String($('#openai_compatible_tts_endpoint').val());
|
||||||
|
this.settings.model = String($('#openai_compatible_model').val());
|
||||||
|
this.settings.available_voices = String($('#openai_compatible_tts_voices').val()).split(',');
|
||||||
|
this.settings.speed = Number($('#openai_compatible_tts_speed').val());
|
||||||
|
$('#openai_compatible_tts_speed_output').text(this.settings.speed);
|
||||||
|
saveTtsProviderSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
async checkReady() {
|
||||||
|
await this.fetchTtsVoiceObjects();
|
||||||
|
}
|
||||||
|
|
||||||
|
async onRefreshClick() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getVoice(voiceName) {
|
||||||
|
if (this.voices.length == 0) {
|
||||||
|
this.voices = await this.fetchTtsVoiceObjects();
|
||||||
|
}
|
||||||
|
const match = this.voices.filter(
|
||||||
|
oaicVoice => oaicVoice.name == voiceName,
|
||||||
|
)[0];
|
||||||
|
if (!match) {
|
||||||
|
throw `TTS Voice name ${voiceName} not found`;
|
||||||
|
}
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
|
async generateTts(text, voiceId) {
|
||||||
|
const response = await this.fetchTtsGeneration(text, voiceId);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchTtsVoiceObjects() {
|
||||||
|
return this.settings.available_voices.map(v => {
|
||||||
|
return { name: v, voice_id: v, lang: 'en-US' };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async previewTtsVoice(voiceId) {
|
||||||
|
this.audioElement.pause();
|
||||||
|
this.audioElement.currentTime = 0;
|
||||||
|
|
||||||
|
const text = getPreviewString('en-US');
|
||||||
|
const response = await this.fetchTtsGeneration(text, voiceId);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const audio = await response.blob();
|
||||||
|
const url = URL.createObjectURL(audio);
|
||||||
|
this.audioElement.src = url;
|
||||||
|
this.audioElement.play();
|
||||||
|
this.audioElement.onended = () => URL.revokeObjectURL(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchTtsGeneration(inputText, voiceId) {
|
||||||
|
console.info(`Generating new TTS for voice_id ${voiceId}`);
|
||||||
|
const response = await fetch('/api/openai/custom/generate-voice', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: getRequestHeaders(),
|
||||||
|
body: JSON.stringify({
|
||||||
|
provider_endpoint: this.settings.provider_endpoint,
|
||||||
|
model: this.settings.model,
|
||||||
|
input: inputText,
|
||||||
|
voice: voiceId,
|
||||||
|
response_format: 'mp3',
|
||||||
|
speed: this.settings.speed,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
toastr.error(response.statusText, 'TTS Generation Failed');
|
||||||
|
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
@ -124,7 +124,7 @@ class SystemTtsProvider {
|
|||||||
if (hasEnabledVoice) {
|
if (hasEnabledVoice) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const utterance = new SpeechSynthesisUtterance('hi');
|
const utterance = new SpeechSynthesisUtterance(' . ');
|
||||||
utterance.volume = 0;
|
utterance.volume = 0;
|
||||||
speechSynthesis.speak(utterance);
|
speechSynthesis.speak(utterance);
|
||||||
hasEnabledVoice = true;
|
hasEnabledVoice = true;
|
||||||
|
@ -30,6 +30,13 @@ import { textgen_types, textgenerationwebui_settings } from '../../textgen-setti
|
|||||||
import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
|
import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
|
||||||
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
|
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
|
||||||
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
|
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
|
||||||
|
import { callGenericPopup, POPUP_RESULT, POPUP_TYPE } from '../../popup.js';
|
||||||
|
import { generateWebLlmChatPrompt, isWebLlmSupported } from '../shared.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object} HashedMessage
|
||||||
|
* @property {string} text - The hashed message text
|
||||||
|
*/
|
||||||
|
|
||||||
const MODULE_NAME = 'vectors';
|
const MODULE_NAME = 'vectors';
|
||||||
|
|
||||||
@ -191,6 +198,11 @@ function splitByChunks(items) {
|
|||||||
return chunkedItems;
|
return chunkedItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Summarizes messages using the Extras API method.
|
||||||
|
* @param {HashedMessage[]} hashedMessages Array of hashed messages
|
||||||
|
* @returns {Promise<HashedMessage[]>} Summarized messages
|
||||||
|
*/
|
||||||
async function summarizeExtra(hashedMessages) {
|
async function summarizeExtra(hashedMessages) {
|
||||||
for (const element of hashedMessages) {
|
for (const element of hashedMessages) {
|
||||||
try {
|
try {
|
||||||
@ -222,6 +234,11 @@ async function summarizeExtra(hashedMessages) {
|
|||||||
return hashedMessages;
|
return hashedMessages;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Summarizes messages using the main API method.
|
||||||
|
* @param {HashedMessage[]} hashedMessages Array of hashed messages
|
||||||
|
* @returns {Promise<HashedMessage[]>} Summarized messages
|
||||||
|
*/
|
||||||
async function summarizeMain(hashedMessages) {
|
async function summarizeMain(hashedMessages) {
|
||||||
for (const element of hashedMessages) {
|
for (const element of hashedMessages) {
|
||||||
element.text = await generateRaw(element.text, '', false, false, settings.summary_prompt);
|
element.text = await generateRaw(element.text, '', false, false, settings.summary_prompt);
|
||||||
@ -230,12 +247,39 @@ async function summarizeMain(hashedMessages) {
|
|||||||
return hashedMessages;
|
return hashedMessages;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Summarizes messages using WebLLM.
|
||||||
|
* @param {HashedMessage[]} hashedMessages Array of hashed messages
|
||||||
|
* @returns {Promise<HashedMessage[]>} Summarized messages
|
||||||
|
*/
|
||||||
|
async function summarizeWebLLM(hashedMessages) {
|
||||||
|
if (!isWebLlmSupported()) {
|
||||||
|
console.warn('Vectors: WebLLM is not supported');
|
||||||
|
return hashedMessages;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const element of hashedMessages) {
|
||||||
|
const messages = [{ role:'system', content: settings.summary_prompt }, { role:'user', content: element.text }];
|
||||||
|
element.text = await generateWebLlmChatPrompt(messages);
|
||||||
|
}
|
||||||
|
|
||||||
|
return hashedMessages;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Summarizes messages using the chosen method.
|
||||||
|
* @param {HashedMessage[]} hashedMessages Array of hashed messages
|
||||||
|
* @param {string} endpoint Type of endpoint to use
|
||||||
|
* @returns {Promise<HashedMessage[]>} Summarized messages
|
||||||
|
*/
|
||||||
async function summarize(hashedMessages, endpoint = 'main') {
|
async function summarize(hashedMessages, endpoint = 'main') {
|
||||||
switch (endpoint) {
|
switch (endpoint) {
|
||||||
case 'main':
|
case 'main':
|
||||||
return await summarizeMain(hashedMessages);
|
return await summarizeMain(hashedMessages);
|
||||||
case 'extras':
|
case 'extras':
|
||||||
return await summarizeExtra(hashedMessages);
|
return await summarizeExtra(hashedMessages);
|
||||||
|
case 'webllm':
|
||||||
|
return await summarizeWebLLM(hashedMessages);
|
||||||
default:
|
default:
|
||||||
console.error('Unsupported endpoint', endpoint);
|
console.error('Unsupported endpoint', endpoint);
|
||||||
}
|
}
|
||||||
@ -357,7 +401,7 @@ async function processFiles(chat) {
|
|||||||
const dataBankCollectionIds = await ingestDataBankAttachments();
|
const dataBankCollectionIds = await ingestDataBankAttachments();
|
||||||
|
|
||||||
if (dataBankCollectionIds.length) {
|
if (dataBankCollectionIds.length) {
|
||||||
const queryText = await getQueryText(chat);
|
const queryText = await getQueryText(chat, 'file');
|
||||||
await injectDataBankChunks(queryText, dataBankCollectionIds);
|
await injectDataBankChunks(queryText, dataBankCollectionIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -391,7 +435,7 @@ async function processFiles(chat) {
|
|||||||
await vectorizeFile(fileText, fileName, collectionId, settings.chunk_size, settings.overlap_percent);
|
await vectorizeFile(fileText, fileName, collectionId, settings.chunk_size, settings.overlap_percent);
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryText = await getQueryText(chat);
|
const queryText = await getQueryText(chat, 'file');
|
||||||
const fileChunks = await retrieveFileChunks(queryText, collectionId);
|
const fileChunks = await retrieveFileChunks(queryText, collectionId);
|
||||||
|
|
||||||
message.mes = `${fileChunks}\n\n${message.mes}`;
|
message.mes = `${fileChunks}\n\n${message.mes}`;
|
||||||
@ -552,7 +596,7 @@ async function rearrangeChat(chat) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryText = await getQueryText(chat);
|
const queryText = await getQueryText(chat, 'chat');
|
||||||
|
|
||||||
if (queryText.length === 0) {
|
if (queryText.length === 0) {
|
||||||
console.debug('Vectors: No text to query');
|
console.debug('Vectors: No text to query');
|
||||||
@ -639,15 +683,16 @@ const onChatEvent = debounce(async () => await moduleWorker.update(), debounce_t
|
|||||||
/**
|
/**
|
||||||
* Gets the text to query from the chat
|
* Gets the text to query from the chat
|
||||||
* @param {object[]} chat Chat messages
|
* @param {object[]} chat Chat messages
|
||||||
|
* @param {'file'|'chat'|'world-info'} initiator Initiator of the query
|
||||||
* @returns {Promise<string>} Text to query
|
* @returns {Promise<string>} Text to query
|
||||||
*/
|
*/
|
||||||
async function getQueryText(chat) {
|
async function getQueryText(chat, initiator) {
|
||||||
let queryText = '';
|
let queryText = '';
|
||||||
let i = 0;
|
let i = 0;
|
||||||
|
|
||||||
let hashedMessages = chat.map(x => ({ text: String(substituteParams(x.mes)) }));
|
let hashedMessages = chat.map(x => ({ text: String(substituteParams(x.mes)) }));
|
||||||
|
|
||||||
if (settings.summarize && settings.summarize_sent) {
|
if (initiator === 'chat' && settings.enabled_chats && settings.summarize && settings.summarize_sent) {
|
||||||
hashedMessages = await summarize(hashedMessages, settings.summary_source);
|
hashedMessages = await summarize(hashedMessages, settings.summary_source);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1235,7 +1280,7 @@ async function activateWorldInfo(chat) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Perform a multi-query
|
// Perform a multi-query
|
||||||
const queryText = await getQueryText(chat);
|
const queryText = await getQueryText(chat, 'world-info');
|
||||||
|
|
||||||
if (queryText.length === 0) {
|
if (queryText.length === 0) {
|
||||||
console.debug('Vectors: No text to query for WI');
|
console.debug('Vectors: No text to query for WI');
|
||||||
@ -1299,11 +1344,30 @@ jQuery(async () => {
|
|||||||
saveSettingsDebounced();
|
saveSettingsDebounced();
|
||||||
toggleSettings();
|
toggleSettings();
|
||||||
});
|
});
|
||||||
$('#api_key_nomicai').on('change', () => {
|
$('#api_key_nomicai').on('click', async () => {
|
||||||
const nomicKey = String($('#api_key_nomicai').val()).trim();
|
const popupText = 'NomicAI API Key:';
|
||||||
if (nomicKey.length) {
|
const key = await callGenericPopup(popupText, POPUP_TYPE.INPUT, '', {
|
||||||
writeSecret(SECRET_KEYS.NOMICAI, nomicKey);
|
customButtons: [{
|
||||||
|
text: 'Remove Key',
|
||||||
|
appendAtEnd: true,
|
||||||
|
result: POPUP_RESULT.NEGATIVE,
|
||||||
|
action: async () => {
|
||||||
|
await writeSecret(SECRET_KEYS.NOMICAI, '');
|
||||||
|
toastr.success('API Key removed');
|
||||||
|
$('#api_key_nomicai').toggleClass('success', !!secret_state[SECRET_KEYS.NOMICAI]);
|
||||||
|
saveSettingsDebounced();
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!key) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await writeSecret(SECRET_KEYS.NOMICAI, String(key));
|
||||||
|
$('#api_key_nomicai').toggleClass('success', !!secret_state[SECRET_KEYS.NOMICAI]);
|
||||||
|
|
||||||
|
toastr.success('API Key saved');
|
||||||
saveSettingsDebounced();
|
saveSettingsDebounced();
|
||||||
});
|
});
|
||||||
$('#vectors_togetherai_model').val(settings.togetherai_model).on('change', () => {
|
$('#vectors_togetherai_model').val(settings.togetherai_model).on('change', () => {
|
||||||
@ -1531,9 +1595,7 @@ jQuery(async () => {
|
|||||||
$('#dialogue_popup_input').val(presetModel);
|
$('#dialogue_popup_input').val(presetModel);
|
||||||
});
|
});
|
||||||
|
|
||||||
const validSecret = !!secret_state[SECRET_KEYS.NOMICAI];
|
$('#api_key_nomicai').toggleClass('success', !!secret_state[SECRET_KEYS.NOMICAI]);
|
||||||
const placeholder = validSecret ? '✔️ Key saved' : '❌ Missing key';
|
|
||||||
$('#api_key_nomicai').attr('placeholder', placeholder);
|
|
||||||
|
|
||||||
toggleSettings();
|
toggleSettings();
|
||||||
eventSource.on(event_types.MESSAGE_DELETED, onChatEvent);
|
eventSource.on(event_types.MESSAGE_DELETED, onChatEvent);
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
<select id="vectors_source" class="text_pole">
|
<select id="vectors_source" class="text_pole">
|
||||||
<option value="cohere">Cohere</option>
|
<option value="cohere">Cohere</option>
|
||||||
<option value="extras">Extras</option>
|
<option value="extras">Extras</option>
|
||||||
<option value="palm">Google MakerSuite</option>
|
<option value="palm">Google AI Studio</option>
|
||||||
<option value="llamacpp">llama.cpp</option>
|
<option value="llamacpp">llama.cpp</option>
|
||||||
<option value="transformers" data-i18n="Local (Transformers)">Local (Transformers)</option>
|
<option value="transformers" data-i18n="Local (Transformers)">Local (Transformers)</option>
|
||||||
<option value="mistral">MistralAI</option>
|
<option value="mistral">MistralAI</option>
|
||||||
@ -103,17 +103,13 @@
|
|||||||
</span>
|
</span>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
<div class="flex-container flexFlowColumn" id="nomicai_apiKey">
|
<div class="flex-container alignItemsCenter" id="nomicai_apiKey">
|
||||||
<label for="api_key_nomicai">
|
<label for="api_key_nomicai" class="flex1">
|
||||||
<span data-i18n="NomicAI API Key">NomicAI API Key</span>
|
<span data-i18n="NomicAI API Key">NomicAI API Key</span>
|
||||||
</label>
|
</label>
|
||||||
<div class="flex-container">
|
<div id="api_key_nomicai" class="menu_button menu_button_icon">
|
||||||
<input id="api_key_nomicai" name="api_key_nomicai" class="text_pole flex1 wide100p" maxlength="500" size="35" type="text" autocomplete="off">
|
<i class="fa-solid fa-key"></i>
|
||||||
<div title="Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_nomicai">
|
<span data-i18n="Click to set">Click to set</span>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div data-for="api_key_nomicai" 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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -378,10 +374,11 @@
|
|||||||
<select id="vectors_summary_source" class="text_pole">
|
<select id="vectors_summary_source" class="text_pole">
|
||||||
<option value="main" data-i18n="Main API">Main API</option>
|
<option value="main" data-i18n="Main API">Main API</option>
|
||||||
<option value="extras" data-i18n="Extras API">Extras API</option>
|
<option value="extras" data-i18n="Extras API">Extras API</option>
|
||||||
|
<option value="webllm" data-i18n="WebLLM Extension">WebLLM Extension</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<label for="vectors_summary_prompt" title="Summary Prompt:">Summary Prompt:</label>
|
<label for="vectors_summary_prompt" title="Summary Prompt:">Summary Prompt:</label>
|
||||||
<small data-i18n="Only used when Main API is selected.">Only used when Main API is selected.</small>
|
<small data-i18n="Only used when Main API or WebLLM Extension is selected.">Only used when Main API or WebLLM Extension is selected.</small>
|
||||||
<textarea id="vectors_summary_prompt" class="text_pole textarea_compact" rows="6" placeholder="This prompt will be sent to AI to request the summary generation."></textarea>
|
<textarea id="vectors_summary_prompt" class="text_pole textarea_compact" rows="6" placeholder="This prompt will be sent to AI to request the summary generation."></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -94,11 +94,11 @@ export function loadInstructMode(data) {
|
|||||||
$element.val(power_user.instruct[control.property]);
|
$element.val(power_user.instruct[control.property]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$element.on('input', function () {
|
$element.on('input', async function () {
|
||||||
power_user.instruct[control.property] = control.isCheckbox ? !!$(this).prop('checked') : $(this).val();
|
power_user.instruct[control.property] = control.isCheckbox ? !!$(this).prop('checked') : $(this).val();
|
||||||
saveSettingsDebounced();
|
saveSettingsDebounced();
|
||||||
if (!control.isCheckbox) {
|
if (!control.isCheckbox) {
|
||||||
resetScrollHeight($element);
|
await resetScrollHeight($element);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -120,6 +120,7 @@ const default_bias_presets = {
|
|||||||
const max_2k = 2047;
|
const max_2k = 2047;
|
||||||
const max_4k = 4095;
|
const max_4k = 4095;
|
||||||
const max_8k = 8191;
|
const max_8k = 8191;
|
||||||
|
const max_12k = 12287;
|
||||||
const max_16k = 16383;
|
const max_16k = 16383;
|
||||||
const max_32k = 32767;
|
const max_32k = 32767;
|
||||||
const max_64k = 65535;
|
const max_64k = 65535;
|
||||||
@ -127,6 +128,7 @@ const max_128k = 128 * 1000;
|
|||||||
const max_200k = 200 * 1000;
|
const max_200k = 200 * 1000;
|
||||||
const max_256k = 256 * 1000;
|
const max_256k = 256 * 1000;
|
||||||
const max_1mil = 1000 * 1000;
|
const max_1mil = 1000 * 1000;
|
||||||
|
const max_2mil = 2000 * 1000;
|
||||||
const scale_max = 8191;
|
const scale_max = 8191;
|
||||||
const claude_max = 9000; // We have a proper tokenizer, so theoretically could be larger (up to 9k)
|
const claude_max = 9000; // We have a proper tokenizer, so theoretically could be larger (up to 9k)
|
||||||
const claude_100k_max = 99000;
|
const claude_100k_max = 99000;
|
||||||
@ -185,6 +187,7 @@ export const chat_completion_sources = {
|
|||||||
PERPLEXITY: 'perplexity',
|
PERPLEXITY: 'perplexity',
|
||||||
GROQ: 'groq',
|
GROQ: 'groq',
|
||||||
ZEROONEAI: '01ai',
|
ZEROONEAI: '01ai',
|
||||||
|
BLOCKENTROPY: 'blockentropy',
|
||||||
};
|
};
|
||||||
|
|
||||||
const character_names_behavior = {
|
const character_names_behavior = {
|
||||||
@ -237,7 +240,7 @@ const default_settings = {
|
|||||||
top_p_openai: 1.0,
|
top_p_openai: 1.0,
|
||||||
top_k_openai: 0,
|
top_k_openai: 0,
|
||||||
min_p_openai: 0,
|
min_p_openai: 0,
|
||||||
top_a_openai: 1,
|
top_a_openai: 0,
|
||||||
repetition_penalty_openai: 1,
|
repetition_penalty_openai: 1,
|
||||||
stream_openai: false,
|
stream_openai: false,
|
||||||
websearch_cohere: false,
|
websearch_cohere: false,
|
||||||
@ -258,15 +261,16 @@ const default_settings = {
|
|||||||
group_nudge_prompt: default_group_nudge_prompt,
|
group_nudge_prompt: default_group_nudge_prompt,
|
||||||
scenario_format: default_scenario_format,
|
scenario_format: default_scenario_format,
|
||||||
personality_format: default_personality_format,
|
personality_format: default_personality_format,
|
||||||
openai_model: 'gpt-3.5-turbo',
|
openai_model: 'gpt-4-turbo',
|
||||||
claude_model: 'claude-2.1',
|
claude_model: 'claude-3-5-sonnet-20240620',
|
||||||
google_model: 'gemini-pro',
|
google_model: 'gemini-1.5-pro',
|
||||||
ai21_model: 'j2-ultra',
|
ai21_model: 'j2-ultra',
|
||||||
mistralai_model: 'mistral-medium-latest',
|
mistralai_model: 'mistral-large-latest',
|
||||||
cohere_model: 'command-r',
|
cohere_model: 'command-r-plus',
|
||||||
perplexity_model: 'llama-3-70b-instruct',
|
perplexity_model: 'llama-3.1-70b-instruct',
|
||||||
groq_model: 'llama3-70b-8192',
|
groq_model: 'llama-3.1-70b-versatile',
|
||||||
zerooneai_model: 'yi-large',
|
zerooneai_model: 'yi-large',
|
||||||
|
blockentropy_model: 'be-70b-base-llama3.1',
|
||||||
custom_model: '',
|
custom_model: '',
|
||||||
custom_url: '',
|
custom_url: '',
|
||||||
custom_include_body: '',
|
custom_include_body: '',
|
||||||
@ -317,7 +321,7 @@ const oai_settings = {
|
|||||||
top_p_openai: 1.0,
|
top_p_openai: 1.0,
|
||||||
top_k_openai: 0,
|
top_k_openai: 0,
|
||||||
min_p_openai: 0,
|
min_p_openai: 0,
|
||||||
top_a_openai: 1,
|
top_a_openai: 0,
|
||||||
repetition_penalty_openai: 1,
|
repetition_penalty_openai: 1,
|
||||||
stream_openai: false,
|
stream_openai: false,
|
||||||
websearch_cohere: false,
|
websearch_cohere: false,
|
||||||
@ -338,15 +342,16 @@ const oai_settings = {
|
|||||||
group_nudge_prompt: default_group_nudge_prompt,
|
group_nudge_prompt: default_group_nudge_prompt,
|
||||||
scenario_format: default_scenario_format,
|
scenario_format: default_scenario_format,
|
||||||
personality_format: default_personality_format,
|
personality_format: default_personality_format,
|
||||||
openai_model: 'gpt-3.5-turbo',
|
openai_model: 'gpt-4-turbo',
|
||||||
claude_model: 'claude-2.1',
|
claude_model: 'claude-3-5-sonnet-20240620',
|
||||||
google_model: 'gemini-pro',
|
google_model: 'gemini-1.5-pro',
|
||||||
ai21_model: 'j2-ultra',
|
ai21_model: 'j2-ultra',
|
||||||
mistralai_model: 'mistral-medium-latest',
|
mistralai_model: 'mistral-large-latest',
|
||||||
cohere_model: 'command-r',
|
cohere_model: 'command-r-plus',
|
||||||
perplexity_model: 'llama-3-70b-instruct',
|
perplexity_model: 'llama-3.1-70b-instruct',
|
||||||
groq_model: 'llama3-70b-8192',
|
groq_model: 'llama-3.1-70b-versatile',
|
||||||
zerooneai_model: 'yi-large',
|
zerooneai_model: 'yi-large',
|
||||||
|
blockentropy_model: 'be-70b-base-llama3.1',
|
||||||
custom_model: '',
|
custom_model: '',
|
||||||
custom_url: '',
|
custom_url: '',
|
||||||
custom_include_body: '',
|
custom_include_body: '',
|
||||||
@ -803,7 +808,8 @@ async function populateChatHistory(messages, prompts, chatCompletion, type = nul
|
|||||||
|
|
||||||
// Reserve budget for group nudge
|
// Reserve budget for group nudge
|
||||||
let groupNudgeMessage = null;
|
let groupNudgeMessage = null;
|
||||||
if (selected_group) {
|
const noGroupNudgeTypes = ['impersonate'];
|
||||||
|
if (selected_group && prompts.has('groupNudge') && !noGroupNudgeTypes.includes(type)) {
|
||||||
groupNudgeMessage = Message.fromPrompt(prompts.get('groupNudge'));
|
groupNudgeMessage = Message.fromPrompt(prompts.get('groupNudge'));
|
||||||
chatCompletion.reserveBudget(groupNudgeMessage);
|
chatCompletion.reserveBudget(groupNudgeMessage);
|
||||||
}
|
}
|
||||||
@ -1541,6 +1547,8 @@ function getChatCompletionModel() {
|
|||||||
return oai_settings.groq_model;
|
return oai_settings.groq_model;
|
||||||
case chat_completion_sources.ZEROONEAI:
|
case chat_completion_sources.ZEROONEAI:
|
||||||
return oai_settings.zerooneai_model;
|
return oai_settings.zerooneai_model;
|
||||||
|
case chat_completion_sources.BLOCKENTROPY:
|
||||||
|
return oai_settings.blockentropy_model;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unknown chat completion source: ${oai_settings.chat_completion_source}`);
|
throw new Error(`Unknown chat completion source: ${oai_settings.chat_completion_source}`);
|
||||||
}
|
}
|
||||||
@ -1654,6 +1662,23 @@ function saveModelList(data) {
|
|||||||
|
|
||||||
$('#model_01ai_select').val(oai_settings.zerooneai_model).trigger('change');
|
$('#model_01ai_select').val(oai_settings.zerooneai_model).trigger('change');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (oai_settings.chat_completion_source == chat_completion_sources.BLOCKENTROPY) {
|
||||||
|
$('#model_blockentropy_select').empty();
|
||||||
|
model_list.forEach((model) => {
|
||||||
|
$('#model_blockentropy_select').append(
|
||||||
|
$('<option>', {
|
||||||
|
value: model.id,
|
||||||
|
text: model.id,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!oai_settings.blockentropy_model && model_list.length > 0) {
|
||||||
|
oai_settings.blockentropy_model = model_list[0].id;
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#model_blockentropy_select').val(oai_settings.blockentropy_model).trigger('change');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function appendOpenRouterOptions(model_list, groupModels = false, sort = false) {
|
function appendOpenRouterOptions(model_list, groupModels = false, sort = false) {
|
||||||
@ -3014,6 +3039,7 @@ function loadOpenAISettings(data, settings) {
|
|||||||
oai_settings.cohere_model = settings.cohere_model ?? default_settings.cohere_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.perplexity_model = settings.perplexity_model ?? default_settings.perplexity_model;
|
||||||
oai_settings.groq_model = settings.groq_model ?? default_settings.groq_model;
|
oai_settings.groq_model = settings.groq_model ?? default_settings.groq_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.zerooneai_model = settings.zerooneai_model ?? default_settings.zerooneai_model;
|
||||||
oai_settings.custom_model = settings.custom_model ?? default_settings.custom_model;
|
oai_settings.custom_model = settings.custom_model ?? default_settings.custom_model;
|
||||||
oai_settings.custom_url = settings.custom_url ?? default_settings.custom_url;
|
oai_settings.custom_url = settings.custom_url ?? default_settings.custom_url;
|
||||||
@ -3047,6 +3073,7 @@ function loadOpenAISettings(data, settings) {
|
|||||||
oai_settings.names_behavior = settings.names_behavior ?? default_settings.names_behavior;
|
oai_settings.names_behavior = settings.names_behavior ?? default_settings.names_behavior;
|
||||||
oai_settings.continue_postfix = settings.continue_postfix ?? default_settings.continue_postfix;
|
oai_settings.continue_postfix = settings.continue_postfix ?? default_settings.continue_postfix;
|
||||||
oai_settings.function_calling = settings.function_calling ?? default_settings.function_calling;
|
oai_settings.function_calling = settings.function_calling ?? default_settings.function_calling;
|
||||||
|
oai_settings.openrouter_providers = settings.openrouter_providers ?? default_settings.openrouter_providers;
|
||||||
|
|
||||||
// Migrate from old settings
|
// Migrate from old settings
|
||||||
if (settings.names_in_completion === true) {
|
if (settings.names_in_completion === true) {
|
||||||
@ -3092,6 +3119,7 @@ function loadOpenAISettings(data, settings) {
|
|||||||
$('#model_groq_select').val(oai_settings.groq_model);
|
$('#model_groq_select').val(oai_settings.groq_model);
|
||||||
$(`#model_groq_select option[value="${oai_settings.groq_model}"`).attr('selected', true);
|
$(`#model_groq_select option[value="${oai_settings.groq_model}"`).attr('selected', true);
|
||||||
$('#model_01ai_select').val(oai_settings.zerooneai_model);
|
$('#model_01ai_select').val(oai_settings.zerooneai_model);
|
||||||
|
$('#model_blockentropy_select').val(oai_settings.blockentropy_model);
|
||||||
$('#custom_model_id').val(oai_settings.custom_model);
|
$('#custom_model_id').val(oai_settings.custom_model);
|
||||||
$('#custom_api_url_text').val(oai_settings.custom_url);
|
$('#custom_api_url_text').val(oai_settings.custom_url);
|
||||||
$('#openai_max_context').val(oai_settings.openai_max_context);
|
$('#openai_max_context').val(oai_settings.openai_max_context);
|
||||||
@ -3353,6 +3381,7 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) {
|
|||||||
perplexity_model: settings.perplexity_model,
|
perplexity_model: settings.perplexity_model,
|
||||||
groq_model: settings.groq_model,
|
groq_model: settings.groq_model,
|
||||||
zerooneai_model: settings.zerooneai_model,
|
zerooneai_model: settings.zerooneai_model,
|
||||||
|
blockentropy_model: settings.blockentropy_model,
|
||||||
custom_model: settings.custom_model,
|
custom_model: settings.custom_model,
|
||||||
custom_url: settings.custom_url,
|
custom_url: settings.custom_url,
|
||||||
custom_include_body: settings.custom_include_body,
|
custom_include_body: settings.custom_include_body,
|
||||||
@ -3595,6 +3624,8 @@ async function onPresetImportFileChange(e) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await eventSource.emit(event_types.OAI_PRESET_IMPORT_READY, { data: presetBody, presetName: name });
|
||||||
|
|
||||||
const savePresetSettings = await fetch(`/api/presets/save-openai?name=${name}`, {
|
const savePresetSettings = await fetch(`/api/presets/save-openai?name=${name}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: getRequestHeaders(),
|
headers: getRequestHeaders(),
|
||||||
@ -3650,6 +3681,7 @@ async function onExportPresetClick() {
|
|||||||
sensitiveFields.forEach(field => delete preset[field]);
|
sensitiveFields.forEach(field => delete preset[field]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await eventSource.emit(event_types.OAI_PRESET_EXPORT_READY, preset);
|
||||||
const presetJsonString = JSON.stringify(preset, null, 4);
|
const presetJsonString = JSON.stringify(preset, null, 4);
|
||||||
const presetFileName = `${oai_settings.preset_settings_openai}.json`;
|
const presetFileName = `${oai_settings.preset_settings_openai}.json`;
|
||||||
download(presetJsonString, presetFileName, 'application/json');
|
download(presetJsonString, presetFileName, 'application/json');
|
||||||
@ -3790,6 +3822,7 @@ function onSettingsPresetChange() {
|
|||||||
perplexity_model: ['#model_perplexity_select', 'perplexity_model', false],
|
perplexity_model: ['#model_perplexity_select', 'perplexity_model', false],
|
||||||
groq_model: ['#model_groq_select', 'groq_model', false],
|
groq_model: ['#model_groq_select', 'groq_model', false],
|
||||||
zerooneai_model: ['#model_01ai_select', 'zerooneai_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],
|
custom_model: ['#custom_model_id', 'custom_model', false],
|
||||||
custom_url: ['#custom_api_url_text', 'custom_url', false],
|
custom_url: ['#custom_api_url_text', 'custom_url', false],
|
||||||
custom_include_body: ['#custom_include_body', 'custom_include_body', false],
|
custom_include_body: ['#custom_include_body', 'custom_include_body', false],
|
||||||
@ -3888,7 +3921,7 @@ function getMaxContextOpenAI(value) {
|
|||||||
if (oai_settings.max_context_unlocked) {
|
if (oai_settings.max_context_unlocked) {
|
||||||
return unlocked_max;
|
return unlocked_max;
|
||||||
}
|
}
|
||||||
else if (value.includes('gpt-4-turbo') || value.includes('gpt-4o') || value.includes('gpt-4-1106') || value.includes('gpt-4-0125') || value.includes('gpt-4-vision')) {
|
else if (value.includes('chatgpt-4o-latest') || value.includes('gpt-4-turbo') || value.includes('gpt-4o') || value.includes('gpt-4-1106') || value.includes('gpt-4-0125') || value.includes('gpt-4-vision')) {
|
||||||
return max_128k;
|
return max_128k;
|
||||||
}
|
}
|
||||||
else if (value.includes('gpt-3.5-turbo-1106')) {
|
else if (value.includes('gpt-3.5-turbo-1106')) {
|
||||||
@ -4037,6 +4070,12 @@ async function onModelChange() {
|
|||||||
oai_settings.zerooneai_model = value;
|
oai_settings.zerooneai_model = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (value && $(this).is('#model_blockentropy_select')) {
|
||||||
|
console.log('Block Entropy model changed to', value);
|
||||||
|
oai_settings.blockentropy_model = value;
|
||||||
|
$('#blockentropy_model_id').val(value).trigger('input');
|
||||||
|
}
|
||||||
|
|
||||||
if (value && $(this).is('#model_custom_select')) {
|
if (value && $(this).is('#model_custom_select')) {
|
||||||
console.log('Custom model changed to', value);
|
console.log('Custom model changed to', value);
|
||||||
oai_settings.custom_model = value;
|
oai_settings.custom_model = value;
|
||||||
@ -4056,18 +4095,26 @@ async function onModelChange() {
|
|||||||
|
|
||||||
if (oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE) {
|
if (oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE) {
|
||||||
if (oai_settings.max_context_unlocked) {
|
if (oai_settings.max_context_unlocked) {
|
||||||
|
$('#openai_max_context').attr('max', max_2mil);
|
||||||
|
} else if (value.includes('gemini-1.5-pro')) {
|
||||||
|
$('#openai_max_context').attr('max', max_2mil);
|
||||||
|
} else if (value.includes('gemini-1.5-flash')) {
|
||||||
$('#openai_max_context').attr('max', max_1mil);
|
$('#openai_max_context').attr('max', max_1mil);
|
||||||
} else if (value === 'gemini-1.5-pro-latest' || value.includes('gemini-1.5-flash')) {
|
} else if (value.includes('gemini-1.0-pro-vision') || value === 'gemini-pro-vision') {
|
||||||
$('#openai_max_context').attr('max', max_1mil);
|
|
||||||
} else if (value === 'gemini-ultra' || value === 'gemini-1.0-pro-latest' || value === 'gemini-pro' || value === 'gemini-1.0-ultra-latest') {
|
|
||||||
$('#openai_max_context').attr('max', max_32k);
|
|
||||||
} else if (value === 'gemini-1.0-pro-vision-latest' || value === 'gemini-pro-vision') {
|
|
||||||
$('#openai_max_context').attr('max', max_16k);
|
$('#openai_max_context').attr('max', max_16k);
|
||||||
} else {
|
} 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);
|
$('#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 {
|
||||||
|
$('#openai_max_context').attr('max', max_4k);
|
||||||
}
|
}
|
||||||
oai_settings.temp_openai = Math.min(claude_max_temp, oai_settings.temp_openai);
|
let makersuite_max_temp = (value.includes('vision') || value.includes('ultra')) ? 1.0 : 2.0;
|
||||||
$('#temp_openai').attr('max', claude_max_temp).val(oai_settings.temp_openai).trigger('input');
|
oai_settings.temp_openai = Math.min(makersuite_max_temp, oai_settings.temp_openai);
|
||||||
|
$('#temp_openai').attr('max', makersuite_max_temp).val(oai_settings.temp_openai).trigger('input');
|
||||||
oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context);
|
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');
|
$('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
|
||||||
}
|
}
|
||||||
@ -4201,6 +4248,11 @@ async function onModelChange() {
|
|||||||
if (oai_settings.max_context_unlocked) {
|
if (oai_settings.max_context_unlocked) {
|
||||||
$('#openai_max_context').attr('max', unlocked_max);
|
$('#openai_max_context').attr('max', unlocked_max);
|
||||||
}
|
}
|
||||||
|
else if (oai_settings.perplexity_model.includes('llama-3.1')) {
|
||||||
|
const isOnline = oai_settings.perplexity_model.includes('online');
|
||||||
|
const contextSize = isOnline ? 128 * 1024 - 4000 : 128 * 1024;
|
||||||
|
$('#openai_max_context').attr('max', contextSize);
|
||||||
|
}
|
||||||
else if (['llama-3-sonar-small-32k-chat', 'llama-3-sonar-large-32k-chat'].includes(oai_settings.perplexity_model)) {
|
else if (['llama-3-sonar-small-32k-chat', 'llama-3-sonar-large-32k-chat'].includes(oai_settings.perplexity_model)) {
|
||||||
$('#openai_max_context').attr('max', max_32k);
|
$('#openai_max_context').attr('max', max_32k);
|
||||||
}
|
}
|
||||||
@ -4292,9 +4344,42 @@ async function onModelChange() {
|
|||||||
if (oai_settings.chat_completion_source === chat_completion_sources.ZEROONEAI) {
|
if (oai_settings.chat_completion_source === chat_completion_sources.ZEROONEAI) {
|
||||||
if (oai_settings.max_context_unlocked) {
|
if (oai_settings.max_context_unlocked) {
|
||||||
$('#openai_max_context').attr('max', unlocked_max);
|
$('#openai_max_context').attr('max', unlocked_max);
|
||||||
} else {
|
}
|
||||||
|
else if (['yi-large'].includes(oai_settings.zerooneai_model)) {
|
||||||
|
$('#openai_max_context').attr('max', max_32k);
|
||||||
|
}
|
||||||
|
else if (['yi-vision'].includes(oai_settings.zerooneai_model)) {
|
||||||
$('#openai_max_context').attr('max', max_16k);
|
$('#openai_max_context').attr('max', max_16k);
|
||||||
}
|
}
|
||||||
|
else if (['yi-large-turbo'].includes(oai_settings.zerooneai_model)) {
|
||||||
|
$('#openai_max_context').attr('max', max_4k);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$('#openai_max_context').attr('max', max_16k);
|
||||||
|
}
|
||||||
|
|
||||||
|
oai_settings.openai_max_context = Math.min(oai_settings.openai_max_context, Number($('#openai_max_context').attr('max')));
|
||||||
|
$('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
|
||||||
|
|
||||||
|
oai_settings.temp_openai = Math.min(oai_max_temp, oai_settings.temp_openai);
|
||||||
|
$('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input');
|
||||||
|
}
|
||||||
|
if (oai_settings.chat_completion_source === chat_completion_sources.BLOCKENTROPY) {
|
||||||
|
if (oai_settings.max_context_unlocked) {
|
||||||
|
$('#openai_max_context').attr('max', unlocked_max);
|
||||||
|
}
|
||||||
|
else if (oai_settings.blockentropy_model.includes('llama3.1')) {
|
||||||
|
$('#openai_max_context').attr('max', max_16k);
|
||||||
|
}
|
||||||
|
else if (oai_settings.blockentropy_model.includes('72b')) {
|
||||||
|
$('#openai_max_context').attr('max', max_16k);
|
||||||
|
}
|
||||||
|
else if (oai_settings.blockentropy_model.includes('120b')) {
|
||||||
|
$('#openai_max_context').attr('max', max_12k);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$('#openai_max_context').attr('max', max_8k);
|
||||||
|
}
|
||||||
|
|
||||||
oai_settings.openai_max_context = Math.min(oai_settings.openai_max_context, Number($('#openai_max_context').attr('max')));
|
oai_settings.openai_max_context = Math.min(oai_settings.openai_max_context, Number($('#openai_max_context').attr('max')));
|
||||||
$('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
|
$('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
|
||||||
@ -4388,7 +4473,7 @@ async function onConnectButtonClick(e) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!secret_state[SECRET_KEYS.MAKERSUITE] && !oai_settings.reverse_proxy) {
|
if (!secret_state[SECRET_KEYS.MAKERSUITE] && !oai_settings.reverse_proxy) {
|
||||||
console.log('No secret key saved for MakerSuite');
|
console.log('No secret key saved for Google AI Studio');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4509,6 +4594,18 @@ async function onConnectButtonClick(e) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (oai_settings.chat_completion_source == chat_completion_sources.BLOCKENTROPY) {
|
||||||
|
const api_key_blockentropy = String($('#api_key_blockentropy').val()).trim();
|
||||||
|
|
||||||
|
if (api_key_blockentropy.length) {
|
||||||
|
await writeSecret(SECRET_KEYS.BLOCKENTROPY, api_key_blockentropy);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!secret_state[SECRET_KEYS.BLOCKENTROPY]) {
|
||||||
|
console.log('No secret key saved for Block Entropy');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
startStatusLoading();
|
startStatusLoading();
|
||||||
saveSettingsDebounced();
|
saveSettingsDebounced();
|
||||||
@ -4560,6 +4657,9 @@ function toggleChatCompletionForms() {
|
|||||||
else if (oai_settings.chat_completion_source == chat_completion_sources.CUSTOM) {
|
else if (oai_settings.chat_completion_source == chat_completion_sources.CUSTOM) {
|
||||||
$('#model_custom_select').trigger('change');
|
$('#model_custom_select').trigger('change');
|
||||||
}
|
}
|
||||||
|
else if (oai_settings.chat_completion_source == chat_completion_sources.BLOCKENTROPY) {
|
||||||
|
$('#model_blockentropy_select').trigger('change');
|
||||||
|
}
|
||||||
$('[data-source]').each(function () {
|
$('[data-source]').each(function () {
|
||||||
const validSources = $(this).data('source').split(',');
|
const validSources = $(this).data('source').split(',');
|
||||||
$(this).toggle(validSources.includes(oai_settings.chat_completion_source));
|
$(this).toggle(validSources.includes(oai_settings.chat_completion_source));
|
||||||
@ -4649,16 +4749,22 @@ export function isImageInliningSupported() {
|
|||||||
// gultra just isn't being offered as multimodal, thanks google.
|
// gultra just isn't being offered as multimodal, thanks google.
|
||||||
const visionSupportedModels = [
|
const visionSupportedModels = [
|
||||||
'gpt-4-vision',
|
'gpt-4-vision',
|
||||||
'gemini-1.5-flash-latest',
|
|
||||||
'gemini-1.5-flash',
|
'gemini-1.5-flash',
|
||||||
|
'gemini-1.5-flash-latest',
|
||||||
|
'gemini-1.5-flash-001',
|
||||||
'gemini-1.0-pro-vision-latest',
|
'gemini-1.0-pro-vision-latest',
|
||||||
|
'gemini-1.5-pro',
|
||||||
'gemini-1.5-pro-latest',
|
'gemini-1.5-pro-latest',
|
||||||
|
'gemini-1.5-pro-001',
|
||||||
|
'gemini-1.5-pro-exp-0801',
|
||||||
'gemini-pro-vision',
|
'gemini-pro-vision',
|
||||||
'claude-3',
|
'claude-3',
|
||||||
'claude-3-5',
|
'claude-3-5',
|
||||||
'gpt-4-turbo',
|
'gpt-4-turbo',
|
||||||
'gpt-4o',
|
'gpt-4o',
|
||||||
'gpt-4o-mini',
|
'gpt-4o-mini',
|
||||||
|
'chatgpt-4o-latest',
|
||||||
|
'yi-vision',
|
||||||
];
|
];
|
||||||
|
|
||||||
switch (oai_settings.chat_completion_source) {
|
switch (oai_settings.chat_completion_source) {
|
||||||
@ -4672,6 +4778,8 @@ export function isImageInliningSupported() {
|
|||||||
return !oai_settings.openrouter_force_instruct;
|
return !oai_settings.openrouter_force_instruct;
|
||||||
case chat_completion_sources.CUSTOM:
|
case chat_completion_sources.CUSTOM:
|
||||||
return true;
|
return true;
|
||||||
|
case chat_completion_sources.ZEROONEAI:
|
||||||
|
return visionSupportedModels.some(model => oai_settings.zerooneai_model.includes(model));
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -5282,6 +5390,7 @@ $(document).ready(async function () {
|
|||||||
$('#model_perplexity_select').on('change', onModelChange);
|
$('#model_perplexity_select').on('change', onModelChange);
|
||||||
$('#model_groq_select').on('change', onModelChange);
|
$('#model_groq_select').on('change', onModelChange);
|
||||||
$('#model_01ai_select').on('change', onModelChange);
|
$('#model_01ai_select').on('change', onModelChange);
|
||||||
|
$('#model_blockentropy_select').on('change', onModelChange);
|
||||||
$('#model_custom_select').on('change', onModelChange);
|
$('#model_custom_select').on('change', onModelChange);
|
||||||
$('#settings_preset_openai').on('change', onSettingsPresetChange);
|
$('#settings_preset_openai').on('change', onSettingsPresetChange);
|
||||||
$('#new_oai_preset').on('click', onNewPresetClick);
|
$('#new_oai_preset').on('click', onNewPresetClick);
|
||||||
|
@ -40,8 +40,8 @@ export const POPUP_RESULT = {
|
|||||||
* @property {POPUP_RESULT|number?} [defaultResult=POPUP_RESULT.AFFIRMATIVE] - The default result of this popup when Enter is pressed. Can be changed from `POPUP_RESULT.AFFIRMATIVE`.
|
* @property {POPUP_RESULT|number?} [defaultResult=POPUP_RESULT.AFFIRMATIVE] - The default result of this popup when Enter is pressed. Can be changed from `POPUP_RESULT.AFFIRMATIVE`.
|
||||||
* @property {CustomPopupButton[]|string[]?} [customButtons=null] - Custom buttons to add to the popup. If only strings are provided, the buttons will be added with default options, and their result will be in order from `2` onward.
|
* @property {CustomPopupButton[]|string[]?} [customButtons=null] - Custom buttons to add to the popup. If only strings are provided, the buttons will be added with default options, and their result will be in order from `2` onward.
|
||||||
* @property {CustomPopupInput[]?} [customInputs=null] - Custom inputs to add to the popup. The display below the content and the input box, one by one.
|
* @property {CustomPopupInput[]?} [customInputs=null] - Custom inputs to add to the popup. The display below the content and the input box, one by one.
|
||||||
* @property {(popup: Popup) => boolean?} [onClosing=null] - Handler called before the popup closes, return `false` to cancel the close
|
* @property {(popup: Popup) => Promise<boolean?>|boolean?} [onClosing=null] - Handler called before the popup closes, return `false` to cancel the close
|
||||||
* @property {(popup: Popup) => void?} [onClose=null] - Handler called after the popup closes, but before the DOM is cleaned up
|
* @property {(popup: Popup) => Promise<void?>|void?} [onClose=null] - Handler called after the popup closes, but before the DOM is cleaned up
|
||||||
* @property {number?} [cropAspect=null] - Aspect ratio for the crop popup
|
* @property {number?} [cropAspect=null] - Aspect ratio for the crop popup
|
||||||
* @property {string?} [cropImage=null] - Image URL to display in the crop popup
|
* @property {string?} [cropImage=null] - Image URL to display in the crop popup
|
||||||
*/
|
*/
|
||||||
@ -101,6 +101,21 @@ const showPopupHelper = {
|
|||||||
if (typeof result === 'string' || typeof result === 'boolean') throw new Error(`Invalid popup result. CONFIRM popups only support numbers, or null. Result: ${result}`);
|
if (typeof result === 'string' || typeof result === 'boolean') throw new Error(`Invalid popup result. CONFIRM popups only support numbers, or null. Result: ${result}`);
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Asynchronously displays a text 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 {PopupOptions} [popupOptions={}] - Options for the popup.
|
||||||
|
* @return {Promise<POPUP_RESULT>} A Promise that resolves with the result of the user's interaction.
|
||||||
|
*/
|
||||||
|
text: async (header, text, popupOptions = {}) => {
|
||||||
|
const content = PopupUtils.BuildTextWithHeader(header, text);
|
||||||
|
const popup = new Popup(content, POPUP_TYPE.TEXT, null, popupOptions);
|
||||||
|
const result = await popup.show();
|
||||||
|
if (typeof result === 'string' || typeof result === 'boolean') throw new Error(`Invalid popup result. TEXT popups only support numbers, or null. Result: ${result}`);
|
||||||
|
return result;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Popup {
|
export class Popup {
|
||||||
@ -123,8 +138,8 @@ export class Popup {
|
|||||||
/** @readonly @type {CustomPopupButton[]|string[]?} */ customButtons;
|
/** @readonly @type {CustomPopupButton[]|string[]?} */ customButtons;
|
||||||
/** @readonly @type {CustomPopupInput[]} */ customInputs;
|
/** @readonly @type {CustomPopupInput[]} */ customInputs;
|
||||||
|
|
||||||
/** @type {(popup: Popup) => boolean?} */ onClosing;
|
/** @type {(popup: Popup) => Promise<boolean?>|boolean?} */ onClosing;
|
||||||
/** @type {(popup: Popup) => void?} */ onClose;
|
/** @type {(popup: Popup) => Promise<void?>|void?} */ onClose;
|
||||||
|
|
||||||
/** @type {POPUP_RESULT|number} */ result;
|
/** @type {POPUP_RESULT|number} */ result;
|
||||||
/** @type {any} */ value;
|
/** @type {any} */ value;
|
||||||
@ -494,7 +509,7 @@ export class Popup {
|
|||||||
this.result = result;
|
this.result = result;
|
||||||
|
|
||||||
if (this.onClosing) {
|
if (this.onClosing) {
|
||||||
const shouldClose = this.onClosing(this);
|
const shouldClose = await this.onClosing(this);
|
||||||
if (!shouldClose) {
|
if (!shouldClose) {
|
||||||
this.#isClosingPrevented = true;
|
this.#isClosingPrevented = true;
|
||||||
// Set values back if we cancel out of closing the popup
|
// Set values back if we cancel out of closing the popup
|
||||||
@ -511,6 +526,15 @@ export class Popup {
|
|||||||
|
|
||||||
return this.#promise;
|
return this.#promise;
|
||||||
}
|
}
|
||||||
|
async completeAffirmative() {
|
||||||
|
return await this.complete(POPUP_RESULT.AFFIRMATIVE);
|
||||||
|
}
|
||||||
|
async completeNegative() {
|
||||||
|
return await this.complete(POPUP_RESULT.NEGATIVE);
|
||||||
|
}
|
||||||
|
async completeCancelled() {
|
||||||
|
return await this.complete(POPUP_RESULT.CANCELLED);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hides the popup, using the internal resolver to return the value to the original show promise
|
* Hides the popup, using the internal resolver to return the value to the original show promise
|
||||||
@ -523,13 +547,13 @@ export class Popup {
|
|||||||
fixToastrForDialogs();
|
fixToastrForDialogs();
|
||||||
|
|
||||||
// After the dialog is actually completely closed, remove it from the DOM
|
// After the dialog is actually completely closed, remove it from the DOM
|
||||||
runAfterAnimation(this.dlg, () => {
|
runAfterAnimation(this.dlg, async () => {
|
||||||
// Call the close on the dialog
|
// Call the close on the dialog
|
||||||
this.dlg.close();
|
this.dlg.close();
|
||||||
|
|
||||||
// Run a possible custom handler right before DOM removal
|
// Run a possible custom handler right before DOM removal
|
||||||
if (this.onClose) {
|
if (this.onClose) {
|
||||||
this.onClose(this);
|
await this.onClose(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove it from the dom
|
// Remove it from the dom
|
||||||
@ -572,7 +596,7 @@ export class Popup {
|
|||||||
|
|
||||||
/** @returns {boolean} Checks if any modal popup dialog is open */
|
/** @returns {boolean} Checks if any modal popup dialog is open */
|
||||||
isPopupOpen() {
|
isPopupOpen() {
|
||||||
return Popup.util.popups.length > 0;
|
return Popup.util.popups.filter(x => x.dlg.hasAttribute('open')).length > 0;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -45,7 +45,7 @@ import { FILTER_TYPES } from './filters.js';
|
|||||||
import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js';
|
import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js';
|
||||||
import { SlashCommand } from './slash-commands/SlashCommand.js';
|
import { SlashCommand } from './slash-commands/SlashCommand.js';
|
||||||
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
|
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
|
||||||
import { AUTOCOMPLETE_WIDTH } from './autocomplete/AutoComplete.js';
|
import { AUTOCOMPLETE_SELECT_KEY, AUTOCOMPLETE_WIDTH } from './autocomplete/AutoComplete.js';
|
||||||
import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
|
import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
|
||||||
import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
|
import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||||
import { POPUP_TYPE, callGenericPopup } from './popup.js';
|
import { POPUP_TYPE, callGenericPopup } from './popup.js';
|
||||||
@ -197,10 +197,12 @@ let power_user = {
|
|||||||
prefer_character_prompt: true,
|
prefer_character_prompt: true,
|
||||||
prefer_character_jailbreak: true,
|
prefer_character_jailbreak: true,
|
||||||
quick_continue: false,
|
quick_continue: false,
|
||||||
|
quick_impersonate: false,
|
||||||
continue_on_send: false,
|
continue_on_send: false,
|
||||||
trim_spaces: true,
|
trim_spaces: true,
|
||||||
relaxed_api_urls: false,
|
relaxed_api_urls: false,
|
||||||
world_import_dialog: true,
|
world_import_dialog: true,
|
||||||
|
enable_auto_select_input: false,
|
||||||
tag_import_setting: tag_import_setting.ASK,
|
tag_import_setting: tag_import_setting.ASK,
|
||||||
disable_group_trimming: false,
|
disable_group_trimming: false,
|
||||||
single_line: false,
|
single_line: false,
|
||||||
@ -242,6 +244,7 @@ let power_user = {
|
|||||||
example_separator: defaultExampleSeparator,
|
example_separator: defaultExampleSeparator,
|
||||||
use_stop_strings: true,
|
use_stop_strings: true,
|
||||||
allow_jailbreak: false,
|
allow_jailbreak: false,
|
||||||
|
names_as_stop_strings: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
personas: {},
|
personas: {},
|
||||||
@ -276,6 +279,7 @@ let power_user = {
|
|||||||
left: AUTOCOMPLETE_WIDTH.CHAT,
|
left: AUTOCOMPLETE_WIDTH.CHAT,
|
||||||
right: AUTOCOMPLETE_WIDTH.CHAT,
|
right: AUTOCOMPLETE_WIDTH.CHAT,
|
||||||
},
|
},
|
||||||
|
select: AUTOCOMPLETE_SELECT_KEY.TAB + AUTOCOMPLETE_SELECT_KEY.ENTER,
|
||||||
},
|
},
|
||||||
parser: {
|
parser: {
|
||||||
/**@type {Object.<PARSER_FLAG,boolean>} */
|
/**@type {Object.<PARSER_FLAG,boolean>} */
|
||||||
@ -347,6 +351,7 @@ const contextControls = [
|
|||||||
{ id: 'context_chat_start', property: 'chat_start', isCheckbox: false, isGlobalSetting: false },
|
{ id: 'context_chat_start', property: 'chat_start', isCheckbox: false, isGlobalSetting: false },
|
||||||
{ id: 'context_use_stop_strings', property: 'use_stop_strings', isCheckbox: true, isGlobalSetting: false, defaultValue: false },
|
{ id: 'context_use_stop_strings', property: 'use_stop_strings', isCheckbox: true, isGlobalSetting: false, defaultValue: false },
|
||||||
{ id: 'context_allow_jailbreak', property: 'allow_jailbreak', isCheckbox: true, isGlobalSetting: false, defaultValue: false },
|
{ id: 'context_allow_jailbreak', property: 'allow_jailbreak', isCheckbox: true, isGlobalSetting: false, defaultValue: false },
|
||||||
|
{ id: 'context_names_as_stop_strings', property: 'names_as_stop_strings', isCheckbox: true, isGlobalSetting: false, defaultValue: true },
|
||||||
|
|
||||||
// Existing power user settings
|
// Existing power user settings
|
||||||
{ id: 'always-force-name2-checkbox', property: 'always_force_name2', isCheckbox: true, isGlobalSetting: true, defaultValue: true },
|
{ id: 'always-force-name2-checkbox', property: 'always_force_name2', isCheckbox: true, isGlobalSetting: true, defaultValue: true },
|
||||||
@ -1026,6 +1031,12 @@ function switchMovingUI() {
|
|||||||
if (power_user.movingUIState) {
|
if (power_user.movingUIState) {
|
||||||
loadMovingUIState();
|
loadMovingUIState();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (Object.keys(power_user.movingUIState).length !== 0) {
|
||||||
|
power_user.movingUIState = {};
|
||||||
|
resetMovablePanels();
|
||||||
|
saveSettingsDebounced();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1470,7 +1481,7 @@ function getExampleMessagesBehavior() {
|
|||||||
return 'normal';
|
return 'normal';
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadPowerUserSettings(settings, data) {
|
async function loadPowerUserSettings(settings, data) {
|
||||||
const defaultStscript = JSON.parse(JSON.stringify(power_user.stscript));
|
const defaultStscript = JSON.parse(JSON.stringify(power_user.stscript));
|
||||||
// Load from settings.json
|
// Load from settings.json
|
||||||
if (settings.power_user !== undefined) {
|
if (settings.power_user !== undefined) {
|
||||||
@ -1492,6 +1503,9 @@ function loadPowerUserSettings(settings, data) {
|
|||||||
if (power_user.stscript.autocomplete.style === undefined) {
|
if (power_user.stscript.autocomplete.style === undefined) {
|
||||||
power_user.stscript.autocomplete.style = power_user.stscript.autocomplete_style || defaultStscript.autocomplete.style;
|
power_user.stscript.autocomplete.style = power_user.stscript.autocomplete_style || defaultStscript.autocomplete.style;
|
||||||
}
|
}
|
||||||
|
if (power_user.stscript.autocomplete.select === undefined) {
|
||||||
|
power_user.stscript.autocomplete.select = defaultStscript.autocomplete.select;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (power_user.stscript.parser === undefined) {
|
if (power_user.stscript.parser === undefined) {
|
||||||
power_user.stscript.parser = defaultStscript.parser;
|
power_user.stscript.parser = defaultStscript.parser;
|
||||||
@ -1583,10 +1597,13 @@ function loadPowerUserSettings(settings, data) {
|
|||||||
$('#single_line').prop('checked', power_user.single_line);
|
$('#single_line').prop('checked', power_user.single_line);
|
||||||
$('#relaxed_api_urls').prop('checked', power_user.relaxed_api_urls);
|
$('#relaxed_api_urls').prop('checked', power_user.relaxed_api_urls);
|
||||||
$('#world_import_dialog').prop('checked', power_user.world_import_dialog);
|
$('#world_import_dialog').prop('checked', power_user.world_import_dialog);
|
||||||
|
$('#enable_auto_select_input').prop('checked', power_user.enable_auto_select_input);
|
||||||
$('#trim_spaces').prop('checked', power_user.trim_spaces);
|
$('#trim_spaces').prop('checked', power_user.trim_spaces);
|
||||||
$('#continue_on_send').prop('checked', power_user.continue_on_send);
|
$('#continue_on_send').prop('checked', power_user.continue_on_send);
|
||||||
$('#quick_continue').prop('checked', power_user.quick_continue);
|
$('#quick_continue').prop('checked', power_user.quick_continue);
|
||||||
|
$('#quick_impersonate').prop('checked', power_user.quick_continue);
|
||||||
$('#mes_continue').css('display', power_user.quick_continue ? '' : 'none');
|
$('#mes_continue').css('display', power_user.quick_continue ? '' : 'none');
|
||||||
|
$('#mes_impersonate').css('display', power_user.quick_impersonate ? '' : 'none');
|
||||||
$('#gestures-checkbox').prop('checked', power_user.gestures);
|
$('#gestures-checkbox').prop('checked', power_user.gestures);
|
||||||
$('#auto_swipe').prop('checked', power_user.auto_swipe);
|
$('#auto_swipe').prop('checked', power_user.auto_swipe);
|
||||||
$('#auto_swipe_minimum_length').val(power_user.auto_swipe_minimum_length);
|
$('#auto_swipe_minimum_length').val(power_user.auto_swipe_minimum_length);
|
||||||
@ -1656,6 +1673,7 @@ function loadPowerUserSettings(settings, data) {
|
|||||||
$('#stscript_matching').val(power_user.stscript.matching ?? 'fuzzy');
|
$('#stscript_matching').val(power_user.stscript.matching ?? 'fuzzy');
|
||||||
$('#stscript_autocomplete_style').val(power_user.stscript.autocomplete.style ?? 'theme');
|
$('#stscript_autocomplete_style').val(power_user.stscript.autocomplete.style ?? 'theme');
|
||||||
document.body.setAttribute('data-stscript-style', power_user.stscript.autocomplete.style);
|
document.body.setAttribute('data-stscript-style', power_user.stscript.autocomplete.style);
|
||||||
|
$('#stscript_autocomplete_select').val(power_user.stscript.autocomplete.select ?? (AUTOCOMPLETE_SELECT_KEY.TAB + AUTOCOMPLETE_SELECT_KEY.ENTER));
|
||||||
$('#stscript_parser_flag_strict_escaping').prop('checked', power_user.stscript.parser.flags[PARSER_FLAG.STRICT_ESCAPING] ?? false);
|
$('#stscript_parser_flag_strict_escaping').prop('checked', power_user.stscript.parser.flags[PARSER_FLAG.STRICT_ESCAPING] ?? false);
|
||||||
$('#stscript_parser_flag_replace_getvar').prop('checked', power_user.stscript.parser.flags[PARSER_FLAG.REPLACE_GETVAR] ?? false);
|
$('#stscript_parser_flag_replace_getvar').prop('checked', power_user.stscript.parser.flags[PARSER_FLAG.REPLACE_GETVAR] ?? false);
|
||||||
$('#stscript_autocomplete_font_scale').val(power_user.stscript.autocomplete.font.scale ?? defaultStscript.autocomplete.font.scale);
|
$('#stscript_autocomplete_font_scale').val(power_user.stscript.autocomplete.font.scale ?? defaultStscript.autocomplete.font.scale);
|
||||||
@ -1724,7 +1742,7 @@ function loadPowerUserSettings(settings, data) {
|
|||||||
switchCompactInputArea();
|
switchCompactInputArea();
|
||||||
reloadMarkdownProcessor(power_user.render_formulas);
|
reloadMarkdownProcessor(power_user.render_formulas);
|
||||||
loadInstructMode(data);
|
loadInstructMode(data);
|
||||||
loadContextSettings();
|
await loadContextSettings();
|
||||||
loadMaxContextUnlocked();
|
loadMaxContextUnlocked();
|
||||||
switchWaifuMode();
|
switchWaifuMode();
|
||||||
switchSpoilerMode();
|
switchSpoilerMode();
|
||||||
@ -1856,7 +1874,7 @@ function getContextSettings() {
|
|||||||
|
|
||||||
// TODO: Maybe add a refresh button to reset settings to preset
|
// TODO: Maybe add a refresh button to reset settings to preset
|
||||||
// TODO: Add "global state" if a preset doesn't set the power_user checkboxes
|
// TODO: Add "global state" if a preset doesn't set the power_user checkboxes
|
||||||
function loadContextSettings() {
|
async function loadContextSettings() {
|
||||||
contextControls.forEach(control => {
|
contextControls.forEach(control => {
|
||||||
const $element = $(`#${control.id}`);
|
const $element = $(`#${control.id}`);
|
||||||
|
|
||||||
@ -1876,7 +1894,7 @@ function loadContextSettings() {
|
|||||||
|
|
||||||
// If the setting already exists, no need to duplicate it
|
// If the setting already exists, no need to duplicate it
|
||||||
// TODO: Maybe check the power_user object for the setting instead of a flag?
|
// TODO: Maybe check the power_user object for the setting instead of a flag?
|
||||||
$element.on('input', function () {
|
$element.on('input', async function () {
|
||||||
const value = control.isCheckbox ? !!$(this).prop('checked') : $(this).val();
|
const value = control.isCheckbox ? !!$(this).prop('checked') : $(this).val();
|
||||||
if (control.isGlobalSetting) {
|
if (control.isGlobalSetting) {
|
||||||
power_user[control.property] = value;
|
power_user[control.property] = value;
|
||||||
@ -1886,7 +1904,7 @@ function loadContextSettings() {
|
|||||||
|
|
||||||
saveSettingsDebounced();
|
saveSettingsDebounced();
|
||||||
if (!control.isCheckbox) {
|
if (!control.isCheckbox) {
|
||||||
resetScrollHeight($element);
|
await resetScrollHeight($element);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -3294,8 +3312,8 @@ $(document).ready(() => {
|
|||||||
saveSettingsDebounced();
|
saveSettingsDebounced();
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#customCSS').on('change', () => {
|
$('#customCSS').on('input', () => {
|
||||||
power_user.custom_css = $('#customCSS').val();
|
power_user.custom_css = String($('#customCSS').val());
|
||||||
localStorage.setItem(storage_keys.custom_css, power_user.custom_css);
|
localStorage.setItem(storage_keys.custom_css, power_user.custom_css);
|
||||||
saveSettingsDebounced();
|
saveSettingsDebounced();
|
||||||
applyCustomCSS();
|
applyCustomCSS();
|
||||||
@ -3732,6 +3750,13 @@ $(document).ready(() => {
|
|||||||
saveSettingsDebounced();
|
saveSettingsDebounced();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('#quick_impersonate').on('input', function () {
|
||||||
|
const value = !!$(this).prop('checked');
|
||||||
|
power_user.quick_impersonate = value;
|
||||||
|
$('#mes_impersonate').css('display', value ? '' : 'none');
|
||||||
|
saveSettingsDebounced();
|
||||||
|
});
|
||||||
|
|
||||||
$('#trim_spaces').on('input', function () {
|
$('#trim_spaces').on('input', function () {
|
||||||
const value = !!$(this).prop('checked');
|
const value = !!$(this).prop('checked');
|
||||||
power_user.trim_spaces = value;
|
power_user.trim_spaces = value;
|
||||||
@ -3750,6 +3775,12 @@ $(document).ready(() => {
|
|||||||
saveSettingsDebounced();
|
saveSettingsDebounced();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('#enable_auto_select_input').on('input', function () {
|
||||||
|
const value = !!$(this).prop('checked');
|
||||||
|
power_user.enable_auto_select_input = value;
|
||||||
|
saveSettingsDebounced();
|
||||||
|
});
|
||||||
|
|
||||||
$('#spoiler_free_mode').on('input', function () {
|
$('#spoiler_free_mode').on('input', function () {
|
||||||
power_user.spoiler_free_mode = !!$(this).prop('checked');
|
power_user.spoiler_free_mode = !!$(this).prop('checked');
|
||||||
switchSpoilerMode();
|
switchSpoilerMode();
|
||||||
@ -3846,6 +3877,12 @@ $(document).ready(() => {
|
|||||||
saveSettingsDebounced();
|
saveSettingsDebounced();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('#stscript_autocomplete_select').on('change', function () {
|
||||||
|
const value = $(this).find(':selected').val();
|
||||||
|
power_user.stscript.autocomplete.select = parseInt(String(value));
|
||||||
|
saveSettingsDebounced();
|
||||||
|
});
|
||||||
|
|
||||||
$('#stscript_autocomplete_font_scale').on('input', function () {
|
$('#stscript_autocomplete_font_scale').on('input', function () {
|
||||||
const value = $(this).val();
|
const value = $(this).val();
|
||||||
$('#stscript_autocomplete_font_scale_counter').val(value);
|
$('#stscript_autocomplete_font_scale_counter').val(value);
|
||||||
@ -4014,6 +4051,7 @@ $(document).ready(() => {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
helpString: 'Enter message deletion mode, and auto-deletes last N messages if numeric argument is provided.',
|
helpString: 'Enter message deletion mode, and auto-deletes last N messages if numeric argument is provided.',
|
||||||
|
returns: 'The text of the deleted messages.',
|
||||||
}));
|
}));
|
||||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||||
name: 'cut',
|
name: 'cut',
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
main_api,
|
main_api,
|
||||||
saveSettingsDebounced,
|
saveSettingsDebounced,
|
||||||
novelai_setting_names,
|
|
||||||
callPopup,
|
callPopup,
|
||||||
settings,
|
|
||||||
} from '../script.js';
|
} from '../script.js';
|
||||||
import { power_user } from './power-user.js';
|
import { power_user } from './power-user.js';
|
||||||
//import { BIAS_CACHE, displayLogitBias, getLogitBiasListResult } from './logit-bias.js';
|
//import { BIAS_CACHE, displayLogitBias, getLogitBiasListResult } from './logit-bias.js';
|
||||||
@ -20,132 +18,6 @@ const forcedOffColoring = 'filter: sepia(1) hue-rotate(308deg) contrast(0.7) sat
|
|||||||
|
|
||||||
let userDisabledSamplers, userShownSamplers;
|
let userDisabledSamplers, userShownSamplers;
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
for reference purposes:
|
|
||||||
|
|
||||||
//NAI
|
|
||||||
const nai_settings = {
|
|
||||||
temperature: 1.5,
|
|
||||||
repetition_penalty: 2.25,
|
|
||||||
repetition_penalty_range: 2048,
|
|
||||||
repetition_penalty_slope: 0.09,
|
|
||||||
repetition_penalty_frequency: 0,
|
|
||||||
repetition_penalty_presence: 0.005,
|
|
||||||
tail_free_sampling: 0.975,
|
|
||||||
top_k: 10,
|
|
||||||
top_p: 0.75,
|
|
||||||
top_a: 0.08,
|
|
||||||
typical_p: 0.975,
|
|
||||||
min_length: 1,
|
|
||||||
model_novel: 'clio-v1',
|
|
||||||
preset_settings_novel: 'Talker-Chat-Clio',
|
|
||||||
streaming_novel: false,
|
|
||||||
preamble: default_preamble,
|
|
||||||
prefix: '',
|
|
||||||
cfg_uc: '',
|
|
||||||
banned_tokens: '',
|
|
||||||
order: default_order,
|
|
||||||
logit_bias: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
// TG Types
|
|
||||||
export const textgen_types = {
|
|
||||||
OOBA: 'ooba',
|
|
||||||
MANCER: 'mancer',
|
|
||||||
VLLM: 'vllm',
|
|
||||||
APHRODITE: 'aphrodite',
|
|
||||||
TABBY: 'tabby',
|
|
||||||
KOBOLDCPP: 'koboldcpp',
|
|
||||||
TOGETHERAI: 'togetherai',
|
|
||||||
LLAMACPP: 'llamacpp',
|
|
||||||
OLLAMA: 'ollama',
|
|
||||||
INFERMATICAI: 'infermaticai',
|
|
||||||
DREAMGEN: 'dreamgen',
|
|
||||||
OPENROUTER: 'openrouter',
|
|
||||||
};
|
|
||||||
|
|
||||||
//KAI and TextGen
|
|
||||||
const setting_names = [
|
|
||||||
'temp',
|
|
||||||
'temperature_last',
|
|
||||||
'rep_pen',
|
|
||||||
'rep_pen_range',
|
|
||||||
'no_repeat_ngram_size',
|
|
||||||
'top_k',
|
|
||||||
'top_p',
|
|
||||||
'top_a',
|
|
||||||
'tfs',
|
|
||||||
'epsilon_cutoff',
|
|
||||||
'eta_cutoff',
|
|
||||||
'typical_p',
|
|
||||||
'min_p',
|
|
||||||
'penalty_alpha',
|
|
||||||
'num_beams',
|
|
||||||
'length_penalty',
|
|
||||||
'min_length',
|
|
||||||
'dynatemp',
|
|
||||||
'min_temp',
|
|
||||||
'max_temp',
|
|
||||||
'dynatemp_exponent',
|
|
||||||
'smoothing_factor',
|
|
||||||
'smoothing_curve',
|
|
||||||
'max_tokens_second',
|
|
||||||
'encoder_rep_pen',
|
|
||||||
'freq_pen',
|
|
||||||
'presence_pen',
|
|
||||||
'do_sample',
|
|
||||||
'early_stopping',
|
|
||||||
'seed',
|
|
||||||
'add_bos_token',
|
|
||||||
'ban_eos_token',
|
|
||||||
'skip_special_tokens',
|
|
||||||
'streaming',
|
|
||||||
'mirostat_mode',
|
|
||||||
'mirostat_tau',
|
|
||||||
'mirostat_eta',
|
|
||||||
'guidance_scale',
|
|
||||||
'negative_prompt',
|
|
||||||
'grammar_string',
|
|
||||||
'json_schema',
|
|
||||||
'banned_tokens',
|
|
||||||
'legacy_api',
|
|
||||||
//'n_aphrodite',
|
|
||||||
//'best_of_aphrodite',
|
|
||||||
'ignore_eos_token',
|
|
||||||
'spaces_between_special_tokens',
|
|
||||||
//'logits_processors_aphrodite',
|
|
||||||
//'log_probs_aphrodite',
|
|
||||||
//'prompt_log_probs_aphrodite'
|
|
||||||
'sampler_order',
|
|
||||||
'sampler_priority',
|
|
||||||
'samplers',
|
|
||||||
'n',
|
|
||||||
'logit_bias',
|
|
||||||
'custom_model',
|
|
||||||
'bypass_status_check',
|
|
||||||
];
|
|
||||||
|
|
||||||
//OAI settings
|
|
||||||
|
|
||||||
const default_settings = {
|
|
||||||
preset_settings_openai: 'Default',
|
|
||||||
temp_openai: 1.0,
|
|
||||||
freq_pen_openai: 0,
|
|
||||||
pres_pen_openai: 0,
|
|
||||||
count_pen: 0.0,
|
|
||||||
top_p_openai: 1.0,
|
|
||||||
top_k_openai: 0,
|
|
||||||
min_p_openai: 0,
|
|
||||||
top_a_openai: 1,
|
|
||||||
repetition_penalty_openai: 1,
|
|
||||||
stream_openai: false,
|
|
||||||
//...
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Goal 1: show popup with all samplers for active API
|
// Goal 1: show popup with all samplers for active API
|
||||||
async function showSamplerSelectPopup() {
|
async function showSamplerSelectPopup() {
|
||||||
const popup = $('#dialogue_popup');
|
const popup = $('#dialogue_popup');
|
||||||
@ -158,12 +30,12 @@ async function showSamplerSelectPopup() {
|
|||||||
<div class="flex-container justifyCenter">
|
<div class="flex-container justifyCenter">
|
||||||
<h3>Sampler Select</h3>
|
<h3>Sampler Select</h3>
|
||||||
<div class="flex-container alignItemsBaseline">
|
<div class="flex-container alignItemsBaseline">
|
||||||
<div id="resetSelectedSamplers" class="menu_button menu_button_icon tag_view_create" title="Reset custom sampler selection">
|
<div id="resetSelectedSamplers" class="menu_button menu_button_icon" title="Reset custom sampler selection">
|
||||||
<i class="fa-solid fa-recycle"></i>
|
<i class="fa-solid fa-recycle"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!--<div class="flex-container alignItemsBaseline">
|
<!--<div class="flex-container alignItemsBaseline">
|
||||||
<div class="menu_button menu_button_icon tag_view_create" title="Create a new sampler">
|
<div class="menu_button menu_button_icon" title="Create a new sampler">
|
||||||
<i class="fa-solid fa-plus"></i>
|
<i class="fa-solid fa-plus"></i>
|
||||||
<span data-i18n="Create">Create</span>
|
<span data-i18n="Create">Create</span>
|
||||||
</div>
|
</div>
|
||||||
@ -190,6 +62,15 @@ async function showSamplerSelectPopup() {
|
|||||||
power_user.selectSamplers.forceHidden = [];
|
power_user.selectSamplers.forceHidden = [];
|
||||||
await validateDisabledSamplers(true);
|
await validateDisabledSamplers(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('#textgen_type').on('change', async function () {
|
||||||
|
console.log('changed TG Type, resetting custom samplers'); //unfortunate, but necessary unless we save custom samplers for each TGTytpe
|
||||||
|
userDisabledSamplers = [];
|
||||||
|
userShownSamplers = [];
|
||||||
|
power_user.selectSamplers.forceShown = [];
|
||||||
|
power_user.selectSamplers.forceHidden = [];
|
||||||
|
await validateDisabledSamplers();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function setSamplerListListeners() {
|
function setSamplerListListeners() {
|
||||||
@ -221,6 +102,11 @@ function setSamplerListListeners() {
|
|||||||
targetDisplayType = 'block';
|
targetDisplayType = 'block';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (samplerName === 'dry_multiplier') {
|
||||||
|
relatedDOMElement = $('#dryBlock');
|
||||||
|
targetDisplayType = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
if (samplerName === 'dynatemp') {
|
if (samplerName === 'dynatemp') {
|
||||||
relatedDOMElement = $('#dynatemp_block_ooba');
|
relatedDOMElement = $('#dynatemp_block_ooba');
|
||||||
targetDisplayType = 'block';
|
targetDisplayType = 'block';
|
||||||
@ -231,9 +117,30 @@ function setSamplerListListeners() {
|
|||||||
targetDisplayType = 'block';
|
targetDisplayType = 'block';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (samplerName === 'sampler_order') {
|
if (samplerName === 'sampler_order') { //this is for kcpp sampler order
|
||||||
relatedDOMElement = $('#sampler_order_block');
|
relatedDOMElement = $('#sampler_order_block_kcpp');
|
||||||
targetDisplayType = 'flex';
|
}
|
||||||
|
|
||||||
|
if (samplerName === 'samplers') { //this is for lcpp sampler order
|
||||||
|
relatedDOMElement = $('#sampler_order_block_lcpp');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (samplerName === 'sampler_priority') { //this is for ooba's sampler priority
|
||||||
|
relatedDOMElement = $('#sampler_priority_block_ooba');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (samplerName === 'penalty_alpha') { //contrastive search only has one sampler, does it need its own block?
|
||||||
|
relatedDOMElement = $('#contrastiveSearchBlock');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (samplerName === 'num_beams') { // num_beams is the killswitch for Beam Search
|
||||||
|
relatedDOMElement = $('#beamSearchBlock');
|
||||||
|
targetDisplayType = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (samplerName === 'smoothing_factor') { // num_beams is the killswitch for Beam Search
|
||||||
|
relatedDOMElement = $('#smoothingBlock');
|
||||||
|
targetDisplayType = 'block';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the current state of the custom data attribute
|
// Get the current state of the custom data attribute
|
||||||
@ -301,7 +208,7 @@ async function listSamplers(main_api, arrayOnly = false) {
|
|||||||
let availableSamplers;
|
let availableSamplers;
|
||||||
if (main_api === 'textgenerationwebui') {
|
if (main_api === 'textgenerationwebui') {
|
||||||
availableSamplers = TGsamplerNames;
|
availableSamplers = TGsamplerNames;
|
||||||
const valuesToRemove = new Set(['streaming', 'seed', 'bypass_status_check', 'custom_model', 'legacy_api', 'samplers']);
|
const valuesToRemove = new Set(['streaming', 'bypass_status_check', 'custom_model', 'legacy_api']);
|
||||||
availableSamplers = availableSamplers.filter(sampler => !valuesToRemove.has(sampler));
|
availableSamplers = availableSamplers.filter(sampler => !valuesToRemove.has(sampler));
|
||||||
availableSamplers.sort();
|
availableSamplers.sort();
|
||||||
}
|
}
|
||||||
@ -312,8 +219,70 @@ async function listSamplers(main_api, arrayOnly = false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const samplersListHTML = availableSamplers.reduce((html, sampler) => {
|
const samplersListHTML = availableSamplers.reduce((html, sampler) => {
|
||||||
let customColor;
|
let customColor, displayname;
|
||||||
const targetDOMelement = $(`#${sampler}_${main_api}`);
|
let targetDOMelement = $(`#${sampler}_${main_api}`);
|
||||||
|
|
||||||
|
if (sampler === 'sampler_order') { //this is for kcpp sampler order
|
||||||
|
targetDOMelement = $('#sampler_order_block_kcpp');
|
||||||
|
displayname = 'KCPP Sampler Order Block';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sampler === 'samplers') { //this is for lcpp sampler order
|
||||||
|
targetDOMelement = $('#sampler_order_block_lcpp');
|
||||||
|
displayname = 'LCPP Sampler Order Block';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sampler === 'sampler_priority') { //this is for ooba's sampler priority
|
||||||
|
targetDOMelement = $('#sampler_priority_block_ooba');
|
||||||
|
displayname = 'Ooba 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';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sampler === 'num_beams') { // num_beams is the killswitch for Beam Search
|
||||||
|
targetDOMelement = $('#beamSearchBlock');
|
||||||
|
displayname = 'Beam Search Block';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sampler === 'smoothing_factor') { // num_beams is the killswitch for Beam Search
|
||||||
|
targetDOMelement = $('#smoothingBlock');
|
||||||
|
displayname = 'Smoothing Block';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sampler === 'dry_multiplier') {
|
||||||
|
targetDOMelement = $('#dryBlock');
|
||||||
|
displayname = 'DRY Rep Pen Block';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sampler === 'dynatemp') {
|
||||||
|
targetDOMelement = $('#dynatemp_block_ooba');
|
||||||
|
displayname = 'DynaTemp Block';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sampler === 'json_schema') {
|
||||||
|
targetDOMelement = $('#json_schema_block');
|
||||||
|
displayname = 'JSON Schema Block';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sampler === 'grammar_string') {
|
||||||
|
targetDOMelement = $('#grammar_block_ooba');
|
||||||
|
displayname = 'Grammar Block';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sampler === 'guidance_scale') {
|
||||||
|
targetDOMelement = $('#cfg_block_ooba');
|
||||||
|
displayname = 'CFG Block';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sampler === 'mirostat_mode') {
|
||||||
|
targetDOMelement = $('#mirostat_block_ooba');
|
||||||
|
displayname = 'Mirostat Block';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const isInForceHiddenArray = userDisabledSamplers.includes(sampler);
|
const isInForceHiddenArray = userDisabledSamplers.includes(sampler);
|
||||||
const isInForceShownArray = userShownSamplers.includes(sampler);
|
const isInForceShownArray = userShownSamplers.includes(sampler);
|
||||||
@ -335,11 +304,12 @@ async function listSamplers(main_api, arrayOnly = false) {
|
|||||||
}
|
}
|
||||||
else { return isVisibleInDOM; }
|
else { return isVisibleInDOM; }
|
||||||
};
|
};
|
||||||
console.log(sampler, isInDefaultState(), isInForceHiddenArray, shouldBeChecked());
|
console.log(sampler, targetDOMelement.prop('id'), isInDefaultState(), isInForceShownArray, isInForceHiddenArray, shouldBeChecked());
|
||||||
|
if (displayname === undefined) { displayname = sampler; }
|
||||||
return html + `
|
return html + `
|
||||||
<div class="sampler_view_list_item wide50p flex-container">
|
<div class="sampler_view_list_item wide50p flex-container">
|
||||||
<input type="checkbox" name="${sampler}_checkbox" ${shouldBeChecked() ? 'checked' : ''}>
|
<input type="checkbox" name="${sampler}_checkbox" ${shouldBeChecked() ? 'checked' : ''}>
|
||||||
<small class="sampler_name" style="${customColor}">${sampler}</small>
|
<small class="sampler_name" style="${customColor}">${displayname}</small>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}, '');
|
}, '');
|
||||||
@ -391,8 +361,33 @@ export async function validateDisabledSamplers(redraw = false) {
|
|||||||
targetDisplayType = 'block';
|
targetDisplayType = 'block';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sampler === 'sampler_order') {
|
if (sampler === 'sampler_order') { //this is for kcpp sampler order
|
||||||
relatedDOMElement = $('#sampler_order_block');
|
relatedDOMElement = $('#sampler_order_block_kcpp');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sampler === 'samplers') { //this is for lcpp sampler order
|
||||||
|
relatedDOMElement = $('#sampler_order_block_lcpp');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sampler === 'sampler_priority') { //this is for ooba's sampler priority
|
||||||
|
relatedDOMElement = $('#sampler_priority_block_ooba');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sampler === 'dry_multiplier') {
|
||||||
|
relatedDOMElement = $('#dryBlock');
|
||||||
|
targetDisplayType = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sampler === 'penalty_alpha') { //contrastive search only has one sampler, does it need its own block?
|
||||||
|
relatedDOMElement = $('#contrastiveSearchBlock');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sampler === 'num_beams') { // num_beams is the killswitch for Beam Search
|
||||||
|
relatedDOMElement = $('#beamSearchBlock');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sampler === 'smoothing_factor') { // num_beams is the killswitch for Beam Search
|
||||||
|
relatedDOMElement = $('#smoothingBlock');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (power_user?.selectSamplers?.forceHidden.includes(sampler)) {
|
if (power_user?.selectSamplers?.forceHidden.includes(sampler)) {
|
||||||
@ -418,6 +413,7 @@ export async function validateDisabledSamplers(redraw = false) {
|
|||||||
setSamplerListListeners();
|
setSamplerListListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await saveSettingsDebounced();
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,8 @@ export const SECRET_KEYS = {
|
|||||||
ZEROONEAI: 'api_key_01ai',
|
ZEROONEAI: 'api_key_01ai',
|
||||||
HUGGINGFACE: 'api_key_huggingface',
|
HUGGINGFACE: 'api_key_huggingface',
|
||||||
STABILITY: 'api_key_stability',
|
STABILITY: 'api_key_stability',
|
||||||
|
BLOCKENTROPY: 'api_key_blockentropy',
|
||||||
|
CUSTOM_OPENAI_TTS: 'api_key_custom_openai_tts',
|
||||||
};
|
};
|
||||||
|
|
||||||
const INPUT_MAP = {
|
const INPUT_MAP = {
|
||||||
@ -63,6 +65,7 @@ const INPUT_MAP = {
|
|||||||
[SECRET_KEYS.FEATHERLESS]: '#api_key_featherless',
|
[SECRET_KEYS.FEATHERLESS]: '#api_key_featherless',
|
||||||
[SECRET_KEYS.ZEROONEAI]: '#api_key_01ai',
|
[SECRET_KEYS.ZEROONEAI]: '#api_key_01ai',
|
||||||
[SECRET_KEYS.HUGGINGFACE]: '#api_key_huggingface',
|
[SECRET_KEYS.HUGGINGFACE]: '#api_key_huggingface',
|
||||||
|
[SECRET_KEYS.BLOCKENTROPY]: '#api_key_blockentropy',
|
||||||
};
|
};
|
||||||
|
|
||||||
async function clearSecret() {
|
async function clearSecret() {
|
||||||
@ -125,7 +128,7 @@ export async function writeSecret(key, value) {
|
|||||||
const text = await response.text();
|
const text = await response.text();
|
||||||
|
|
||||||
if (text == 'ok') {
|
if (text == 'ok') {
|
||||||
secret_state[key] = true;
|
secret_state[key] = !!value;
|
||||||
updateSecretDisplay();
|
updateSecretDisplay();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,9 +7,19 @@ export const markdownUnderscoreExt = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return [{
|
return [{
|
||||||
type: 'lang',
|
type: 'output',
|
||||||
regex: new RegExp('\\b(?<!_)_(?!_)(.*?)(?<!_)_(?!_)\\b', 'g'),
|
regex: new RegExp('(<code>[\\s\\S]*?<\\/code>)|\\b(?<!_)_(?!_)(.*?)(?<!_)_(?!_)\\b', 'g'),
|
||||||
replace: '<em>$1</em>',
|
replace: function(match, codeContent, italicContent) {
|
||||||
|
if (codeContent) {
|
||||||
|
// If it's inside <code> tags, return unchanged
|
||||||
|
return match;
|
||||||
|
} else if (italicContent) {
|
||||||
|
// If it's an italic group, apply the replacement
|
||||||
|
return '<em>' + italicContent + '</em>';
|
||||||
|
}
|
||||||
|
// If none of the conditions are met, return the original match
|
||||||
|
return match;
|
||||||
|
},
|
||||||
}];
|
}];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error in Showdown-underscore extension:', e);
|
console.error('Error in Showdown-underscore extension:', e);
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
Generate,
|
Generate,
|
||||||
|
UNIQUE_APIS,
|
||||||
activateSendButtons,
|
activateSendButtons,
|
||||||
addOneMessage,
|
addOneMessage,
|
||||||
|
api_server,
|
||||||
callPopup,
|
callPopup,
|
||||||
characters,
|
characters,
|
||||||
chat,
|
chat,
|
||||||
@ -49,9 +51,9 @@ import { findGroupMemberId, groups, is_group_generating, openGroupById, resetSel
|
|||||||
import { chat_completion_sources, oai_settings, setupChatCompletionPromptManager } from './openai.js';
|
import { chat_completion_sources, oai_settings, setupChatCompletionPromptManager } from './openai.js';
|
||||||
import { autoSelectPersona, retriggerFirstMessageOnEmptyChat, setPersonaLockState, togglePersonaLock, user_avatar } from './personas.js';
|
import { autoSelectPersona, retriggerFirstMessageOnEmptyChat, setPersonaLockState, togglePersonaLock, user_avatar } from './personas.js';
|
||||||
import { addEphemeralStoppingString, chat_styles, flushEphemeralStoppingStrings, power_user } from './power-user.js';
|
import { addEphemeralStoppingString, chat_styles, flushEphemeralStoppingStrings, power_user } from './power-user.js';
|
||||||
import { textgen_types, textgenerationwebui_settings } from './textgen-settings.js';
|
import { SERVER_INPUTS, textgen_types, textgenerationwebui_settings } from './textgen-settings.js';
|
||||||
import { decodeTextTokens, getFriendlyTokenizerName, getTextTokens, getTokenCountAsync } from './tokenizers.js';
|
import { decodeTextTokens, getAvailableTokenizers, getFriendlyTokenizerName, getTextTokens, getTokenCountAsync, selectTokenizer } from './tokenizers.js';
|
||||||
import { debounce, delay, isFalseBoolean, isTrueBoolean, stringToRange, trimToEndSentence, trimToStartSentence, waitUntilCondition } from './utils.js';
|
import { debounce, delay, isFalseBoolean, isTrueBoolean, showFontAwesomePicker, stringToRange, trimToEndSentence, trimToStartSentence, waitUntilCondition } from './utils.js';
|
||||||
import { registerVariableCommands, resolveVariable } from './variables.js';
|
import { registerVariableCommands, resolveVariable } from './variables.js';
|
||||||
import { background_settings } from './backgrounds.js';
|
import { background_settings } from './backgrounds.js';
|
||||||
import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
|
import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
|
||||||
@ -64,6 +66,9 @@ import { SlashCommandNamedArgumentAssignment } from './slash-commands/SlashComma
|
|||||||
import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
|
import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
|
||||||
import { POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
|
import { POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
|
||||||
import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
|
import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||||
|
import { SlashCommandDebugController } from './slash-commands/SlashCommandDebugController.js';
|
||||||
|
import { SlashCommandBreakController } from './slash-commands/SlashCommandBreakController.js';
|
||||||
|
import { SlashCommandExecutionError } from './slash-commands/SlashCommandExecutionError.js';
|
||||||
export {
|
export {
|
||||||
executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, registerSlashCommand,
|
executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, registerSlashCommand,
|
||||||
};
|
};
|
||||||
@ -714,6 +719,7 @@ export function initDefaultSlashCommands() {
|
|||||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||||
name: 'delswipe',
|
name: 'delswipe',
|
||||||
callback: deleteSwipeCallback,
|
callback: deleteSwipeCallback,
|
||||||
|
returns: 'the new, currently selected swipe id',
|
||||||
aliases: ['swipedel'],
|
aliases: ['swipedel'],
|
||||||
unnamedArgumentList: [
|
unnamedArgumentList: [
|
||||||
SlashCommandArgument.fromProps({
|
SlashCommandArgument.fromProps({
|
||||||
@ -909,13 +915,28 @@ export function initDefaultSlashCommands() {
|
|||||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||||
name: 'addswipe',
|
name: 'addswipe',
|
||||||
callback: addSwipeCallback,
|
callback: addSwipeCallback,
|
||||||
|
returns: 'the new swipe id',
|
||||||
aliases: ['swipeadd'],
|
aliases: ['swipeadd'],
|
||||||
|
namedArgumentList: [
|
||||||
|
SlashCommandNamedArgument.fromProps({
|
||||||
|
name: 'switch',
|
||||||
|
description: 'switch to the new swipe',
|
||||||
|
typeList: [ARGUMENT_TYPE.BOOLEAN],
|
||||||
|
enumList: commonEnumProviders.boolean()(),
|
||||||
|
}),
|
||||||
|
],
|
||||||
unnamedArgumentList: [
|
unnamedArgumentList: [
|
||||||
new SlashCommandArgument(
|
new SlashCommandArgument(
|
||||||
'text', [ARGUMENT_TYPE.STRING], true,
|
'text', [ARGUMENT_TYPE.STRING], true,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
helpString: 'Adds a swipe to the last chat message.',
|
helpString: `
|
||||||
|
<div>
|
||||||
|
Adds a swipe to the last chat message.
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Use switch=true to switch to directly switch to the new swipe.
|
||||||
|
</div>`,
|
||||||
}));
|
}));
|
||||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||||
name: 'stop',
|
name: 'stop',
|
||||||
@ -1120,10 +1141,10 @@ export function initDefaultSlashCommands() {
|
|||||||
unnamedArgumentList: [
|
unnamedArgumentList: [
|
||||||
SlashCommandArgument.fromProps({
|
SlashCommandArgument.fromProps({
|
||||||
description: 'scoped variable or qr label',
|
description: 'scoped variable or qr label',
|
||||||
typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING],
|
typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.CLOSURE],
|
||||||
isRequired: true,
|
isRequired: true,
|
||||||
enumProvider: () => [
|
enumProvider: (executor, scope) => [
|
||||||
...commonEnumProviders.variables('scope')(),
|
...commonEnumProviders.variables('scope')(executor, scope),
|
||||||
...(typeof window['qrEnumProviderExecutables'] === 'function') ? window['qrEnumProviderExecutables']() : [],
|
...(typeof window['qrEnumProviderExecutables'] === 'function') ? window['qrEnumProviderExecutables']() : [],
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
@ -1477,6 +1498,88 @@ export function initDefaultSlashCommands() {
|
|||||||
],
|
],
|
||||||
helpString: 'Sets the specified prompt manager entry/entries on or off.',
|
helpString: 'Sets the specified prompt manager entry/entries on or off.',
|
||||||
}));
|
}));
|
||||||
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||||
|
name: 'pick-icon',
|
||||||
|
callback: async () => ((await showFontAwesomePicker()) ?? false).toString(),
|
||||||
|
returns: 'The chosen icon name or false if cancelled.',
|
||||||
|
helpString: `
|
||||||
|
<div>Opens a popup with all the available Font Awesome icons and returns the selected icon's name.</div>
|
||||||
|
<div>
|
||||||
|
<strong>Example:</strong>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<pre><code>/pick-icon |\n/if left={{pipe}} rule=eq right=false\n\telse={: /echo chosen icon: "{{pipe}}" :}\n\t{: /echo cancelled icon selection :}\n|</code></pre>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
}));
|
||||||
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||||
|
name: 'api-url',
|
||||||
|
callback: setApiUrlCallback,
|
||||||
|
returns: 'the current API url',
|
||||||
|
aliases: ['server'],
|
||||||
|
namedArgumentList: [
|
||||||
|
SlashCommandNamedArgument.fromProps({
|
||||||
|
name: 'api',
|
||||||
|
description: 'API to set/get the URL for - if not provided, current API is used',
|
||||||
|
typeList: [ARGUMENT_TYPE.STRING],
|
||||||
|
enumList: [
|
||||||
|
new SlashCommandEnumValue('custom', 'custom OpenAI-compatible', enumTypes.getBasedOnIndex(UNIQUE_APIS.findIndex(x => x === 'openai')), 'O'),
|
||||||
|
new SlashCommandEnumValue('kobold', 'KoboldAI Classic', enumTypes.getBasedOnIndex(UNIQUE_APIS.findIndex(x => x === 'kobold')), 'K'),
|
||||||
|
...Object.values(textgen_types).map(api => new SlashCommandEnumValue(api, null, enumTypes.getBasedOnIndex(UNIQUE_APIS.findIndex(x => x === 'textgenerationwebui')), 'T')),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
SlashCommandNamedArgument.fromProps({
|
||||||
|
name: 'connect',
|
||||||
|
description: 'Whether to auto-connect to the API after setting the URL',
|
||||||
|
typeList: [ARGUMENT_TYPE.BOOLEAN],
|
||||||
|
defaultValue: 'true',
|
||||||
|
enumList: commonEnumProviders.boolean('trueFalse')(),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
unnamedArgumentList: [
|
||||||
|
SlashCommandArgument.fromProps({
|
||||||
|
description: 'API url to connect to',
|
||||||
|
typeList: [ARGUMENT_TYPE.STRING],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
helpString: `
|
||||||
|
<div>
|
||||||
|
Set the API url / server url for the currently selected API, including the port. If no argument is provided, it will return the current API url.
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
If a manual API is provided to <b>set</b> the URL, make sure to set <code>connect=false</code>, as auto-connect only works for the currently selected API,
|
||||||
|
or consider switching to it with <code>/api</code> first.
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
This slash command works for most of the Text Completion sources, KoboldAI Classic, and also Custom OpenAI compatible for the Chat Completion sources. If unsure which APIs are supported,
|
||||||
|
check the auto-completion of the optional <code>api</code> argument of this command.
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
}));
|
||||||
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||||
|
name: 'tokenizer',
|
||||||
|
callback: selectTokenizerCallback,
|
||||||
|
returns: 'current tokenizer',
|
||||||
|
unnamedArgumentList: [
|
||||||
|
SlashCommandArgument.fromProps({
|
||||||
|
description: 'tokenizer name',
|
||||||
|
typeList: [ARGUMENT_TYPE.STRING],
|
||||||
|
enumList: getAvailableTokenizers().map(tokenizer =>
|
||||||
|
new SlashCommandEnumValue(tokenizer.tokenizerKey, tokenizer.tokenizerName, enumTypes.enum, enumIcons.default)),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
helpString: `
|
||||||
|
<div>
|
||||||
|
Selects tokenizer by name. Gets the current tokenizer if no name is provided.
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Available tokenizers:</strong>
|
||||||
|
<pre><code>${getAvailableTokenizers().map(t => t.tokenizerKey).join(', ')}</code></pre>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
}));
|
||||||
|
|
||||||
registerVariableCommands();
|
registerVariableCommands();
|
||||||
}
|
}
|
||||||
@ -1754,7 +1857,7 @@ async function popupCallback(args, value) {
|
|||||||
return String(value);
|
return String(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMessagesCallback(args, value) {
|
async function getMessagesCallback(args, value) {
|
||||||
const includeNames = !isFalseBoolean(args?.names);
|
const includeNames = !isFalseBoolean(args?.names);
|
||||||
const includeHidden = isTrueBoolean(args?.hidden);
|
const includeHidden = isTrueBoolean(args?.hidden);
|
||||||
const role = args?.role;
|
const role = args?.role;
|
||||||
@ -1787,33 +1890,34 @@ function getMessagesCallback(args, value) {
|
|||||||
throw new Error(`Invalid role provided. Expected one of: system, assistant, user. Got: ${role}`);
|
throw new Error(`Invalid role provided. Expected one of: system, assistant, user. Got: ${role}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const messages = [];
|
const processMessage = async (mesId) => {
|
||||||
|
const msg = chat[mesId];
|
||||||
for (let messageId = range.start; messageId <= range.end; messageId++) {
|
if (!msg) {
|
||||||
const message = chat[messageId];
|
console.warn(`WARN: No message found with ID ${mesId}`);
|
||||||
if (!message) {
|
return null;
|
||||||
console.warn(`WARN: No message found with ID ${messageId}`);
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (role && !filterByRole(message)) {
|
if (role && !filterByRole(msg)) {
|
||||||
console.debug(`/messages: Skipping message with ID ${messageId} due to role filter`);
|
console.debug(`/messages: Skipping message with ID ${mesId} due to role filter`);
|
||||||
continue;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!includeHidden && message.is_system) {
|
if (!includeHidden && msg.is_system) {
|
||||||
console.debug(`/messages: Skipping hidden message with ID ${messageId}`);
|
console.debug(`/messages: Skipping hidden message with ID ${mesId}`);
|
||||||
continue;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (includeNames) {
|
return includeNames ? `${msg.name}: ${msg.mes}` : msg.mes;
|
||||||
messages.push(`${message.name}: ${message.mes}`);
|
};
|
||||||
} else {
|
|
||||||
messages.push(message.mes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return messages.join('\n\n');
|
const messagePromises = [];
|
||||||
|
|
||||||
|
for (let rInd = range.start; rInd <= range.end; ++rInd)
|
||||||
|
messagePromises.push(processMessage(rInd));
|
||||||
|
|
||||||
|
const messages = await Promise.all(messagePromises);
|
||||||
|
|
||||||
|
return messages.filter(m => m !== null).join('\n\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function runCallback(args, name) {
|
async function runCallback(args, name) {
|
||||||
@ -1821,6 +1925,11 @@ async function runCallback(args, name) {
|
|||||||
throw new Error('No name provided for /run command');
|
throw new Error('No name provided for /run command');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (name instanceof SlashCommandClosure) {
|
||||||
|
name.breakController = new SlashCommandBreakController();
|
||||||
|
return (await name.execute())?.pipe;
|
||||||
|
}
|
||||||
|
|
||||||
/**@type {SlashCommandScope} */
|
/**@type {SlashCommandScope} */
|
||||||
const scope = args._scope;
|
const scope = args._scope;
|
||||||
if (scope.existsVariable(name)) {
|
if (scope.existsVariable(name)) {
|
||||||
@ -1829,6 +1938,11 @@ async function runCallback(args, name) {
|
|||||||
throw new Error(`"${name}" is not callable.`);
|
throw new Error(`"${name}" is not callable.`);
|
||||||
}
|
}
|
||||||
closure.scope.parent = scope;
|
closure.scope.parent = scope;
|
||||||
|
closure.breakController = new SlashCommandBreakController();
|
||||||
|
if (args._debugController && !closure.debugController) {
|
||||||
|
closure.debugController = args._debugController;
|
||||||
|
}
|
||||||
|
while (closure.providedArgumentList.pop());
|
||||||
closure.argumentList.forEach(arg => {
|
closure.argumentList.forEach(arg => {
|
||||||
if (Object.keys(args).includes(arg.name)) {
|
if (Object.keys(args).includes(arg.name)) {
|
||||||
const providedArg = new SlashCommandNamedArgumentAssignment();
|
const providedArg = new SlashCommandNamedArgumentAssignment();
|
||||||
@ -1847,9 +1961,14 @@ async function runCallback(args, name) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
name = name.trim();
|
name = name.trim();
|
||||||
return await window['executeQuickReplyByName'](name, args);
|
/**@type {ExecuteSlashCommandsOptions} */
|
||||||
|
const options = {
|
||||||
|
abortController: args._abortController,
|
||||||
|
debugController: args._debugController,
|
||||||
|
};
|
||||||
|
return await window['executeQuickReplyByName'](name, args, options);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`Error running Quick Reply "${name}": ${error.message}`, 'Error');
|
throw new Error(`Error running Quick Reply "${name}": ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2028,12 +2147,13 @@ async function generateRawCallback(args, value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for the /gen command
|
||||||
|
* @param {object} args Named arguments
|
||||||
|
* @param {string} value Unnamed argument
|
||||||
|
* @returns {Promise<string>} The generated text
|
||||||
|
*/
|
||||||
async function generateCallback(args, value) {
|
async function generateCallback(args, value) {
|
||||||
if (!value) {
|
|
||||||
console.warn('WARN: No argument provided for /gen command');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prevent generate recursion
|
// Prevent generate recursion
|
||||||
$('#send_textarea').val('')[0].dispatchEvent(new Event('input', { bubbles: true }));
|
$('#send_textarea').val('')[0].dispatchEvent(new Event('input', { bubbles: true }));
|
||||||
const lock = isTrueBoolean(args?.lock);
|
const lock = isTrueBoolean(args?.lock);
|
||||||
@ -2121,8 +2241,11 @@ async function echoCallback(args, value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
async function addSwipeCallback(_, arg) {
|
* @param {{switch?: string}} args - named arguments
|
||||||
|
* @param {string} value - The swipe text to add (unnamed argument)
|
||||||
|
*/
|
||||||
|
async function addSwipeCallback(args, value) {
|
||||||
const lastMessage = chat[chat.length - 1];
|
const lastMessage = chat[chat.length - 1];
|
||||||
|
|
||||||
if (!lastMessage) {
|
if (!lastMessage) {
|
||||||
@ -2130,7 +2253,7 @@ async function addSwipeCallback(_, arg) {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!arg) {
|
if (!value) {
|
||||||
console.warn('WARN: No argument provided for /addswipe command');
|
console.warn('WARN: No argument provided for /addswipe command');
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@ -2159,23 +2282,30 @@ async function addSwipeCallback(_, arg) {
|
|||||||
lastMessage.swipe_info = lastMessage.swipes.map(() => ({}));
|
lastMessage.swipe_info = lastMessage.swipes.map(() => ({}));
|
||||||
}
|
}
|
||||||
|
|
||||||
lastMessage.swipes.push(arg);
|
lastMessage.swipes.push(value);
|
||||||
lastMessage.swipe_info.push({
|
lastMessage.swipe_info.push({
|
||||||
send_date: getMessageTimeStamp(),
|
send_date: getMessageTimeStamp(),
|
||||||
gen_started: null,
|
gen_started: null,
|
||||||
gen_finished: null,
|
gen_finished: null,
|
||||||
extra: {
|
extra: {
|
||||||
bias: extractMessageBias(arg),
|
bias: extractMessageBias(value),
|
||||||
gen_id: Date.now(),
|
gen_id: Date.now(),
|
||||||
api: 'manual',
|
api: 'manual',
|
||||||
model: 'slash command',
|
model: 'slash command',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const newSwipeId = lastMessage.swipes.length - 1;
|
||||||
|
|
||||||
|
if (isTrueBoolean(args.switch)) {
|
||||||
|
lastMessage.swipe_id = newSwipeId;
|
||||||
|
lastMessage.mes = lastMessage.swipes[newSwipeId];
|
||||||
|
}
|
||||||
|
|
||||||
await saveChatConditional();
|
await saveChatConditional();
|
||||||
await reloadCurrentChat();
|
await reloadCurrentChat();
|
||||||
|
|
||||||
return '';
|
return String(newSwipeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteSwipeCallback(_, arg) {
|
async function deleteSwipeCallback(_, arg) {
|
||||||
@ -2211,7 +2341,7 @@ async function deleteSwipeCallback(_, arg) {
|
|||||||
await saveChatConditional();
|
await saveChatConditional();
|
||||||
await reloadCurrentChat();
|
await reloadCurrentChat();
|
||||||
|
|
||||||
return '';
|
return String(newSwipeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function askCharacter(args, text) {
|
async function askCharacter(args, text) {
|
||||||
@ -3190,6 +3320,7 @@ function getModelOptions() {
|
|||||||
{ id: 'model_perplexity_select', api: 'openai', type: chat_completion_sources.PERPLEXITY },
|
{ id: 'model_perplexity_select', api: 'openai', type: chat_completion_sources.PERPLEXITY },
|
||||||
{ id: 'model_groq_select', api: 'openai', type: chat_completion_sources.GROQ },
|
{ id: 'model_groq_select', api: 'openai', type: chat_completion_sources.GROQ },
|
||||||
{ id: 'model_01ai_select', api: 'openai', type: chat_completion_sources.ZEROONEAI },
|
{ id: 'model_01ai_select', api: 'openai', type: chat_completion_sources.ZEROONEAI },
|
||||||
|
{ id: 'model_blockentropy_select', api: 'openai', type: chat_completion_sources.BLOCKENTROPY },
|
||||||
{ id: 'model_novel_select', api: 'novel', type: null },
|
{ id: 'model_novel_select', api: 'novel', type: null },
|
||||||
{ id: 'horde_model', api: 'koboldhorde', type: null },
|
{ id: 'horde_model', api: 'koboldhorde', type: null },
|
||||||
];
|
];
|
||||||
@ -3358,6 +3489,123 @@ function setPromptEntryCallback(args, targetState) {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the API URL and triggers the text generation web UI button click.
|
||||||
|
*
|
||||||
|
* @param {object} args - named args
|
||||||
|
* @param {string?} [args.api=null] - the API name to set/get the URL for
|
||||||
|
* @param {string?} [args.connect=true] - whether to connect to the API after setting
|
||||||
|
* @param {string} url - the API URL to set
|
||||||
|
* @returns {Promise<string>}
|
||||||
|
*/
|
||||||
|
async function setApiUrlCallback({ api = null, connect = 'true' }, url) {
|
||||||
|
const autoConnect = isTrueBoolean(connect);
|
||||||
|
|
||||||
|
// Special handling for Chat Completion Custom OpenAI compatible, that one can also support API url handling
|
||||||
|
const isCurrentlyCustomOpenai = main_api === 'openai' && oai_settings.chat_completion_source === chat_completion_sources.CUSTOM;
|
||||||
|
if (api === chat_completion_sources.CUSTOM || (!api && isCurrentlyCustomOpenai)) {
|
||||||
|
if (!url) {
|
||||||
|
return oai_settings.custom_url ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isCurrentlyCustomOpenai && autoConnect) {
|
||||||
|
toastr.warning('Custom OpenAI API is not the currently selected API, so we cannot do an auto-connect. Consider switching to it via /api beforehand.');
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#custom_api_url_text').val(url).trigger('input');
|
||||||
|
|
||||||
|
if (autoConnect) {
|
||||||
|
$('#api_button_openai').trigger('click');
|
||||||
|
}
|
||||||
|
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special handling for Kobold Classic API
|
||||||
|
const isCurrentlyKoboldClassic = main_api === 'kobold';
|
||||||
|
if (api === 'kobold' || (!api && isCurrentlyKoboldClassic)) {
|
||||||
|
if (!url) {
|
||||||
|
return api_server ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isCurrentlyKoboldClassic && autoConnect) {
|
||||||
|
toastr.warning('Kobold Classic API is not the currently selected API, so we cannot do an auto-connect. Consider switching to it via /api beforehand.');
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#api_url_text').val(url).trigger('input');
|
||||||
|
// trigger blur debounced, so we hide the autocomplete menu
|
||||||
|
setTimeout(() => $('#api_url_text').trigger('blur'), 1);
|
||||||
|
|
||||||
|
if (autoConnect) {
|
||||||
|
$('#api_button').trigger('click');
|
||||||
|
}
|
||||||
|
|
||||||
|
return api_server ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do some checks and get the api type we are targeting with this command
|
||||||
|
if (api && !Object.values(textgen_types).includes(api)) {
|
||||||
|
toastr.warning(`API '${api}' is not a valid text_gen API.`);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if (!api && !Object.values(textgen_types).includes(textgenerationwebui_settings.type)) {
|
||||||
|
toastr.warning(`API '${textgenerationwebui_settings.type}' is not a valid text_gen API.`);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if (api && url && autoConnect && api !== textgenerationwebui_settings.type) {
|
||||||
|
toastr.warning(`API '${api}' is not the currently selected API, so we cannot do an auto-connect. Consider switching to it via /api beforehand.`);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const type = api || textgenerationwebui_settings.type;
|
||||||
|
|
||||||
|
const inputSelector = SERVER_INPUTS[type];
|
||||||
|
if (!inputSelector) {
|
||||||
|
toastr.warning(`API '${type}' does not have a server url input.`);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no url was provided, return the current one
|
||||||
|
if (!url) {
|
||||||
|
return textgenerationwebui_settings.server_urls[type] ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// else, we want to actually set the url
|
||||||
|
$(inputSelector).val(url).trigger('input');
|
||||||
|
// trigger blur debounced, so we hide the autocomplete menu
|
||||||
|
setTimeout(() => $(inputSelector).trigger('blur'), 1);
|
||||||
|
|
||||||
|
// Trigger the auto connect via connect button, if requested
|
||||||
|
if (autoConnect) {
|
||||||
|
$('#api_button_textgenerationwebui').trigger('click');
|
||||||
|
}
|
||||||
|
|
||||||
|
// We still re-acquire the value, as it might have been modified by the validation on connect
|
||||||
|
return textgenerationwebui_settings.server_urls[type] ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function selectTokenizerCallback(_, name) {
|
||||||
|
if (!name) {
|
||||||
|
return getAvailableTokenizers().find(tokenizer => tokenizer.tokenizerId === power_user.tokenizer)?.tokenizerKey ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokenizers = getAvailableTokenizers();
|
||||||
|
const fuse = new Fuse(tokenizers, { keys: ['tokenizerKey', 'tokenizerName'] });
|
||||||
|
const result = fuse.search(name);
|
||||||
|
|
||||||
|
if (result.length === 0) {
|
||||||
|
toastr.warning(`Tokenizer "${name}" not found`);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {import('./tokenizers.js').Tokenizer} */
|
||||||
|
const foundTokenizer = result[0].item;
|
||||||
|
selectTokenizer(foundTokenizer.tokenizerId);
|
||||||
|
|
||||||
|
return foundTokenizer.tokenizerKey;
|
||||||
|
}
|
||||||
|
|
||||||
export let isExecutingCommandsFromChatInput = false;
|
export let isExecutingCommandsFromChatInput = false;
|
||||||
export let commandsFromChatInputAbortController;
|
export let commandsFromChatInputAbortController;
|
||||||
|
|
||||||
@ -3428,7 +3676,9 @@ const clearCommandProgressDebounced = debounce(clearCommandProgress);
|
|||||||
* @prop {boolean} [handleExecutionErrors] (false) Whether to handle execution errors (show toast on error) or throw
|
* @prop {boolean} [handleExecutionErrors] (false) Whether to handle execution errors (show toast on error) or throw
|
||||||
* @prop {{[id:PARSER_FLAG]:boolean}} [parserFlags] (null) Parser flags to apply
|
* @prop {{[id:PARSER_FLAG]:boolean}} [parserFlags] (null) Parser flags to apply
|
||||||
* @prop {SlashCommandAbortController} [abortController] (null) Controller used to abort or pause command execution
|
* @prop {SlashCommandAbortController} [abortController] (null) Controller used to abort or pause command execution
|
||||||
|
* @prop {SlashCommandDebugController} [debugController] (null) Controller used to control debug execution
|
||||||
* @prop {(done:number, total:number)=>void} [onProgress] (null) Callback to handle progress events
|
* @prop {(done:number, total:number)=>void} [onProgress] (null) Callback to handle progress events
|
||||||
|
* @prop {string} [source] (null) String indicating where the code come from (e.g., QR name)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -3436,6 +3686,7 @@ const clearCommandProgressDebounced = debounce(clearCommandProgress);
|
|||||||
* @prop {SlashCommandScope} [scope] (null) The scope to be used when executing the commands.
|
* @prop {SlashCommandScope} [scope] (null) The scope to be used when executing the commands.
|
||||||
* @prop {{[id:PARSER_FLAG]:boolean}} [parserFlags] (null) Parser flags to apply
|
* @prop {{[id:PARSER_FLAG]:boolean}} [parserFlags] (null) Parser flags to apply
|
||||||
* @prop {boolean} [clearChatInput] (false) Whether to clear the chat input textarea
|
* @prop {boolean} [clearChatInput] (false) Whether to clear the chat input textarea
|
||||||
|
* @prop {string} [source] (null) String indicating where the code come from (e.g., QR name)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -3451,6 +3702,7 @@ export async function executeSlashCommandsOnChatInput(text, options = {}) {
|
|||||||
scope: null,
|
scope: null,
|
||||||
parserFlags: null,
|
parserFlags: null,
|
||||||
clearChatInput: false,
|
clearChatInput: false,
|
||||||
|
source: null,
|
||||||
}, options);
|
}, options);
|
||||||
|
|
||||||
isExecutingCommandsFromChatInput = true;
|
isExecutingCommandsFromChatInput = true;
|
||||||
@ -3473,13 +3725,21 @@ export async function executeSlashCommandsOnChatInput(text, options = {}) {
|
|||||||
|
|
||||||
/**@type {SlashCommandClosureResult} */
|
/**@type {SlashCommandClosureResult} */
|
||||||
let result = null;
|
let result = null;
|
||||||
|
let currentProgress = 0;
|
||||||
try {
|
try {
|
||||||
commandsFromChatInputAbortController = new SlashCommandAbortController();
|
commandsFromChatInputAbortController = new SlashCommandAbortController();
|
||||||
result = await executeSlashCommandsWithOptions(text, {
|
result = await executeSlashCommandsWithOptions(text, {
|
||||||
abortController: commandsFromChatInputAbortController,
|
abortController: commandsFromChatInputAbortController,
|
||||||
onProgress: (done, total) => ta.style.setProperty('--prog', `${done / total * 100}%`),
|
onProgress: (done, total) => {
|
||||||
|
const newProgress = done / total;
|
||||||
|
if (newProgress > currentProgress) {
|
||||||
|
currentProgress = newProgress;
|
||||||
|
ta.style.setProperty('--prog', `${newProgress * 100}%`);
|
||||||
|
}
|
||||||
|
},
|
||||||
parserFlags: options.parserFlags,
|
parserFlags: options.parserFlags,
|
||||||
scope: options.scope,
|
scope: options.scope,
|
||||||
|
source: options.source,
|
||||||
});
|
});
|
||||||
if (commandsFromChatInputAbortController.signal.aborted) {
|
if (commandsFromChatInputAbortController.signal.aborted) {
|
||||||
document.querySelector('#form_sheld').classList.add('script_aborted');
|
document.querySelector('#form_sheld').classList.add('script_aborted');
|
||||||
@ -3492,7 +3752,23 @@ export async function executeSlashCommandsOnChatInput(text, options = {}) {
|
|||||||
result.isError = true;
|
result.isError = true;
|
||||||
result.errorMessage = e.message || 'An unknown error occurred';
|
result.errorMessage = e.message || 'An unknown error occurred';
|
||||||
if (e.cause !== 'abort') {
|
if (e.cause !== 'abort') {
|
||||||
toastr.error(result.errorMessage);
|
if (e instanceof SlashCommandExecutionError) {
|
||||||
|
/**@type {SlashCommandExecutionError}*/
|
||||||
|
const ex = e;
|
||||||
|
const toast = `
|
||||||
|
<div>${ex.message}</div>
|
||||||
|
<div>Line: ${ex.line} Column: ${ex.column}</div>
|
||||||
|
<pre style="text-align:left;">${ex.hint}</pre>
|
||||||
|
`;
|
||||||
|
const clickHint = '<p>Click to see details</p>';
|
||||||
|
toastr.error(
|
||||||
|
`${toast}${clickHint}`,
|
||||||
|
'SlashCommandExecutionError',
|
||||||
|
{ escapeHtml: false, timeOut: 10000, onclick: () => callPopup(toast, 'text') },
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
toastr.error(result.errorMessage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
delay(1000).then(() => clearCommandProgressDebounced());
|
delay(1000).then(() => clearCommandProgressDebounced());
|
||||||
@ -3520,7 +3796,9 @@ async function executeSlashCommandsWithOptions(text, options = {}) {
|
|||||||
handleExecutionErrors: false,
|
handleExecutionErrors: false,
|
||||||
parserFlags: null,
|
parserFlags: null,
|
||||||
abortController: null,
|
abortController: null,
|
||||||
|
debugController: null,
|
||||||
onProgress: null,
|
onProgress: null,
|
||||||
|
source: null,
|
||||||
}, options);
|
}, options);
|
||||||
|
|
||||||
let closure;
|
let closure;
|
||||||
@ -3528,6 +3806,8 @@ async function executeSlashCommandsWithOptions(text, options = {}) {
|
|||||||
closure = parser.parse(text, true, options.parserFlags, options.abortController ?? new SlashCommandAbortController());
|
closure = parser.parse(text, true, options.parserFlags, options.abortController ?? new SlashCommandAbortController());
|
||||||
closure.scope.parent = options.scope;
|
closure.scope.parent = options.scope;
|
||||||
closure.onProgress = options.onProgress;
|
closure.onProgress = options.onProgress;
|
||||||
|
closure.debugController = options.debugController;
|
||||||
|
closure.source = options.source;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (options.handleParserErrors && e instanceof SlashCommandParserError) {
|
if (options.handleParserErrors && e instanceof SlashCommandParserError) {
|
||||||
/**@type {SlashCommandParserError}*/
|
/**@type {SlashCommandParserError}*/
|
||||||
@ -3559,7 +3839,23 @@ async function executeSlashCommandsWithOptions(text, options = {}) {
|
|||||||
return result;
|
return result;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (options.handleExecutionErrors) {
|
if (options.handleExecutionErrors) {
|
||||||
toastr.error(e.message);
|
if (e instanceof SlashCommandExecutionError) {
|
||||||
|
/**@type {SlashCommandExecutionError}*/
|
||||||
|
const ex = e;
|
||||||
|
const toast = `
|
||||||
|
<div>${ex.message}</div>
|
||||||
|
<div>Line: ${ex.line} Column: ${ex.column}</div>
|
||||||
|
<pre style="text-align:left;">${ex.hint}</pre>
|
||||||
|
`;
|
||||||
|
const clickHint = '<p>Click to see details</p>';
|
||||||
|
toastr.error(
|
||||||
|
`${toast}${clickHint}`,
|
||||||
|
'SlashCommandExecutionError',
|
||||||
|
{ escapeHtml: false, timeOut: 10000, onclick: () => callPopup(toast, 'text') },
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
toastr.error(e.message);
|
||||||
|
}
|
||||||
const result = new SlashCommandClosureResult();
|
const result = new SlashCommandClosureResult();
|
||||||
result.isError = true;
|
result.isError = true;
|
||||||
result.errorMessage = e.message;
|
result.errorMessage = e.message;
|
||||||
@ -3596,6 +3892,7 @@ async function executeSlashCommands(text, handleParserErrors = true, scope = nul
|
|||||||
*
|
*
|
||||||
* @param {HTMLTextAreaElement} textarea The textarea to receive autocomplete
|
* @param {HTMLTextAreaElement} textarea The textarea to receive autocomplete
|
||||||
* @param {Boolean} isFloating Whether to show the auto complete as a floating window (e.g., large QR editor)
|
* @param {Boolean} isFloating Whether to show the auto complete as a floating window (e.g., large QR editor)
|
||||||
|
* @returns {Promise<AutoComplete>}
|
||||||
*/
|
*/
|
||||||
export async function setSlashCommandAutoComplete(textarea, isFloating = false) {
|
export async function setSlashCommandAutoComplete(textarea, isFloating = false) {
|
||||||
function canUseNegativeLookbehind() {
|
function canUseNegativeLookbehind() {
|
||||||
@ -3619,6 +3916,7 @@ export async function setSlashCommandAutoComplete(textarea, isFloating = false)
|
|||||||
async (text, index) => await parser.getNameAt(text, index),
|
async (text, index) => await parser.getNameAt(text, index),
|
||||||
isFloating,
|
isFloating,
|
||||||
);
|
);
|
||||||
|
return ac;
|
||||||
}
|
}
|
||||||
/**@type {HTMLTextAreaElement} */
|
/**@type {HTMLTextAreaElement} */
|
||||||
const sendTextarea = document.querySelector('#send_textarea');
|
const sendTextarea = document.querySelector('#send_textarea');
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { SlashCommandAbortController } from './SlashCommandAbortController.js';
|
import { SlashCommandAbortController } from './SlashCommandAbortController.js';
|
||||||
import { SlashCommandArgument, SlashCommandNamedArgument } from './SlashCommandArgument.js';
|
import { SlashCommandArgument, SlashCommandNamedArgument } from './SlashCommandArgument.js';
|
||||||
import { SlashCommandClosure } from './SlashCommandClosure.js';
|
import { SlashCommandClosure } from './SlashCommandClosure.js';
|
||||||
|
import { SlashCommandDebugController } from './SlashCommandDebugController.js';
|
||||||
import { PARSER_FLAG } from './SlashCommandParser.js';
|
import { PARSER_FLAG } from './SlashCommandParser.js';
|
||||||
import { SlashCommandScope } from './SlashCommandScope.js';
|
import { SlashCommandScope } from './SlashCommandScope.js';
|
||||||
|
|
||||||
@ -12,6 +13,7 @@ import { SlashCommandScope } from './SlashCommandScope.js';
|
|||||||
* _scope:SlashCommandScope,
|
* _scope:SlashCommandScope,
|
||||||
* _parserFlags:{[id:PARSER_FLAG]:boolean},
|
* _parserFlags:{[id:PARSER_FLAG]:boolean},
|
||||||
* _abortController:SlashCommandAbortController,
|
* _abortController:SlashCommandAbortController,
|
||||||
|
* _debugController:SlashCommandDebugController,
|
||||||
* _hasUnnamedArgument:boolean,
|
* _hasUnnamedArgument:boolean,
|
||||||
* [id:string]:string|SlashCommandClosure,
|
* [id:string]:string|SlashCommandClosure,
|
||||||
* }} NamedArguments
|
* }} NamedArguments
|
||||||
@ -36,6 +38,7 @@ export class SlashCommand {
|
|||||||
* @param {(namedArguments:NamedArguments|NamedArgumentsCapture, unnamedArguments:string|SlashCommandClosure|(string|SlashCommandClosure)[])=>string|SlashCommandClosure|Promise<string|SlashCommandClosure>} [props.callback]
|
* @param {(namedArguments:NamedArguments|NamedArgumentsCapture, unnamedArguments:string|SlashCommandClosure|(string|SlashCommandClosure)[])=>string|SlashCommandClosure|Promise<string|SlashCommandClosure>} [props.callback]
|
||||||
* @param {string} [props.helpString]
|
* @param {string} [props.helpString]
|
||||||
* @param {boolean} [props.splitUnnamedArgument]
|
* @param {boolean} [props.splitUnnamedArgument]
|
||||||
|
* @param {Number} [props.splitUnnamedArgumentCount]
|
||||||
* @param {string[]} [props.aliases]
|
* @param {string[]} [props.aliases]
|
||||||
* @param {string} [props.returns]
|
* @param {string} [props.returns]
|
||||||
* @param {SlashCommandNamedArgument[]} [props.namedArgumentList]
|
* @param {SlashCommandNamedArgument[]} [props.namedArgumentList]
|
||||||
@ -50,9 +53,10 @@ export class SlashCommand {
|
|||||||
|
|
||||||
|
|
||||||
/**@type {string}*/ name;
|
/**@type {string}*/ name;
|
||||||
/**@type {(namedArguments:{_pipe:string|SlashCommandClosure, _scope:SlashCommandScope, _abortController:SlashCommandAbortController, [id:string]:string|SlashCommandClosure}, unnamedArguments:string|SlashCommandClosure|(string|SlashCommandClosure)[])=>string|SlashCommandClosure|Promise<string|SlashCommandClosure>}*/ callback;
|
/**@type {(namedArguments:{_scope:SlashCommandScope, _abortController:SlashCommandAbortController, [id:string]:string|SlashCommandClosure}, unnamedArguments:string|SlashCommandClosure|(string|SlashCommandClosure)[])=>string|SlashCommandClosure|Promise<string|SlashCommandClosure>}*/ callback;
|
||||||
/**@type {string}*/ helpString;
|
/**@type {string}*/ helpString;
|
||||||
/**@type {boolean}*/ splitUnnamedArgument = false;
|
/**@type {boolean}*/ splitUnnamedArgument = false;
|
||||||
|
/**@type {Number}*/ splitUnnamedArgumentCount;
|
||||||
/**@type {string[]}*/ aliases = [];
|
/**@type {string[]}*/ aliases = [];
|
||||||
/**@type {string}*/ returns;
|
/**@type {string}*/ returns;
|
||||||
/**@type {SlashCommandNamedArgument[]}*/ namedArgumentList = [];
|
/**@type {SlashCommandNamedArgument[]}*/ namedArgumentList = [];
|
||||||
@ -61,6 +65,10 @@ export class SlashCommand {
|
|||||||
/**@type {Object.<string, HTMLElement>}*/ helpCache = {};
|
/**@type {Object.<string, HTMLElement>}*/ helpCache = {};
|
||||||
/**@type {Object.<string, DocumentFragment>}*/ helpDetailsCache = {};
|
/**@type {Object.<string, DocumentFragment>}*/ helpDetailsCache = {};
|
||||||
|
|
||||||
|
/**@type {boolean}*/ isExtension = false;
|
||||||
|
/**@type {boolean}*/ isThirdParty = false;
|
||||||
|
/**@type {string}*/ source;
|
||||||
|
|
||||||
renderHelpItem(key = null) {
|
renderHelpItem(key = null) {
|
||||||
key = key ?? this.name;
|
key = key ?? this.name;
|
||||||
if (!this.helpCache[key]) {
|
if (!this.helpCache[key]) {
|
||||||
@ -225,12 +233,35 @@ export class SlashCommand {
|
|||||||
const aliasList = [cmd.name, ...(cmd.aliases ?? [])].filter(it=>it != key);
|
const aliasList = [cmd.name, ...(cmd.aliases ?? [])].filter(it=>it != key);
|
||||||
const specs = document.createElement('div'); {
|
const specs = document.createElement('div'); {
|
||||||
specs.classList.add('specs');
|
specs.classList.add('specs');
|
||||||
const name = document.createElement('div'); {
|
const head = document.createElement('div'); {
|
||||||
name.classList.add('name');
|
head.classList.add('head');
|
||||||
name.classList.add('monospace');
|
const name = document.createElement('div'); {
|
||||||
name.title = 'command name';
|
name.classList.add('name');
|
||||||
name.textContent = `/${key}`;
|
name.classList.add('monospace');
|
||||||
specs.append(name);
|
name.title = 'command name';
|
||||||
|
name.textContent = `/${key}`;
|
||||||
|
head.append(name);
|
||||||
|
}
|
||||||
|
const src = document.createElement('div'); {
|
||||||
|
src.classList.add('source');
|
||||||
|
src.classList.add('fa-solid');
|
||||||
|
if (this.isExtension) {
|
||||||
|
src.classList.add('isExtension');
|
||||||
|
src.classList.add('fa-cubes');
|
||||||
|
if (this.isThirdParty) src.classList.add('isThirdParty');
|
||||||
|
else src.classList.add('isCore');
|
||||||
|
} else {
|
||||||
|
src.classList.add('isCore');
|
||||||
|
src.classList.add('fa-star-of-life');
|
||||||
|
}
|
||||||
|
src.title = [
|
||||||
|
this.isExtension ? 'Extension' : 'Core',
|
||||||
|
this.isThirdParty ? 'Third Party' : (this.isExtension ? 'Core' : null),
|
||||||
|
this.source,
|
||||||
|
].filter(it=>it).join('\n');
|
||||||
|
head.append(src);
|
||||||
|
}
|
||||||
|
specs.append(head);
|
||||||
}
|
}
|
||||||
const body = document.createElement('div'); {
|
const body = document.createElement('div'); {
|
||||||
body.classList.add('body');
|
body.classList.add('body');
|
||||||
@ -303,40 +334,52 @@ export class SlashCommand {
|
|||||||
for (const arg of unnamedArguments) {
|
for (const arg of unnamedArguments) {
|
||||||
const listItem = document.createElement('li'); {
|
const listItem = document.createElement('li'); {
|
||||||
listItem.classList.add('argumentItem');
|
listItem.classList.add('argumentItem');
|
||||||
const argItem = document.createElement('div'); {
|
const argSpec = document.createElement('div'); {
|
||||||
argItem.classList.add('argument');
|
argSpec.classList.add('argumentSpec');
|
||||||
argItem.classList.add('unnamedArgument');
|
const argItem = document.createElement('div'); {
|
||||||
argItem.title = `${arg.isRequired ? '' : 'optional '}unnamed argument`;
|
argItem.classList.add('argument');
|
||||||
if (!arg.isRequired || (arg.defaultValue ?? false)) argItem.classList.add('optional');
|
argItem.classList.add('unnamedArgument');
|
||||||
if (arg.acceptsMultiple) argItem.classList.add('multiple');
|
argItem.title = `${arg.isRequired ? '' : 'optional '}unnamed argument`;
|
||||||
if (arg.enumList.length > 0) {
|
if (!arg.isRequired || (arg.defaultValue ?? false)) argItem.classList.add('optional');
|
||||||
const enums = document.createElement('span'); {
|
if (arg.acceptsMultiple) argItem.classList.add('multiple');
|
||||||
enums.classList.add('argument-enums');
|
if (arg.enumList.length > 0) {
|
||||||
enums.title = `${argItem.title} - accepted values`;
|
const enums = document.createElement('span'); {
|
||||||
for (const e of arg.enumList) {
|
enums.classList.add('argument-enums');
|
||||||
const enumItem = document.createElement('span'); {
|
enums.title = `${argItem.title} - accepted values`;
|
||||||
enumItem.classList.add('argument-enum');
|
for (const e of arg.enumList) {
|
||||||
enumItem.textContent = e.value;
|
const enumItem = document.createElement('span'); {
|
||||||
enums.append(enumItem);
|
enumItem.classList.add('argument-enum');
|
||||||
|
enumItem.textContent = e.value;
|
||||||
|
enums.append(enumItem);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
argItem.append(enums);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const types = document.createElement('span'); {
|
||||||
|
types.classList.add('argument-types');
|
||||||
|
types.title = `${argItem.title} - accepted types`;
|
||||||
|
for (const t of arg.typeList) {
|
||||||
|
const type = document.createElement('span'); {
|
||||||
|
type.classList.add('argument-type');
|
||||||
|
type.textContent = t;
|
||||||
|
types.append(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
argItem.append(types);
|
||||||
}
|
}
|
||||||
argItem.append(enums);
|
|
||||||
}
|
}
|
||||||
} else {
|
argSpec.append(argItem);
|
||||||
const types = document.createElement('span'); {
|
}
|
||||||
types.classList.add('argument-types');
|
if (arg.defaultValue !== null) {
|
||||||
types.title = `${argItem.title} - accepted types`;
|
const argDefault = document.createElement('div'); {
|
||||||
for (const t of arg.typeList) {
|
argDefault.classList.add('argument-default');
|
||||||
const type = document.createElement('span'); {
|
argDefault.title = 'default value';
|
||||||
type.classList.add('argument-type');
|
argDefault.textContent = arg.defaultValue.toString();
|
||||||
type.textContent = t;
|
argSpec.append(argDefault);
|
||||||
types.append(type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
argItem.append(types);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
listItem.append(argItem);
|
listItem.append(argSpec);
|
||||||
}
|
}
|
||||||
const desc = document.createElement('div'); {
|
const desc = document.createElement('div'); {
|
||||||
desc.classList.add('argument-description');
|
desc.classList.add('argument-description');
|
||||||
|
@ -2,6 +2,9 @@ import { SlashCommandClosure } from './SlashCommandClosure.js';
|
|||||||
import { commonEnumProviders } from './SlashCommandCommonEnumsProvider.js';
|
import { commonEnumProviders } from './SlashCommandCommonEnumsProvider.js';
|
||||||
import { SlashCommandEnumValue } from './SlashCommandEnumValue.js';
|
import { SlashCommandEnumValue } from './SlashCommandEnumValue.js';
|
||||||
import { SlashCommandExecutor } from './SlashCommandExecutor.js';
|
import { SlashCommandExecutor } from './SlashCommandExecutor.js';
|
||||||
|
import { SlashCommandScope } from './SlashCommandScope.js';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**@readonly*/
|
/**@readonly*/
|
||||||
/**@enum {string}*/
|
/**@enum {string}*/
|
||||||
@ -27,7 +30,7 @@ export class SlashCommandArgument {
|
|||||||
* @param {boolean} [props.acceptsMultiple=false] default: false - whether argument accepts multiple values
|
* @param {boolean} [props.acceptsMultiple=false] default: false - whether argument accepts multiple values
|
||||||
* @param {string|SlashCommandClosure} [props.defaultValue=null] default value if no value is provided
|
* @param {string|SlashCommandClosure} [props.defaultValue=null] default value if no value is provided
|
||||||
* @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} [props.enumList=[]] list of accepted values
|
* @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} [props.enumList=[]] list of accepted values
|
||||||
* @param {(executor:SlashCommandExecutor)=>SlashCommandEnumValue[]} [props.enumProvider=null] function that returns auto complete options
|
* @param {(executor:SlashCommandExecutor, scope:SlashCommandScope)=>SlashCommandEnumValue[]} [props.enumProvider=null] function that returns auto complete options
|
||||||
* @param {boolean} [props.forceEnum=false] default: false - whether the input must match one of the enum values
|
* @param {boolean} [props.forceEnum=false] default: false - whether the input must match one of the enum values
|
||||||
*/
|
*/
|
||||||
static fromProps(props) {
|
static fromProps(props) {
|
||||||
@ -49,7 +52,7 @@ export class SlashCommandArgument {
|
|||||||
/**@type {boolean}*/ acceptsMultiple = false;
|
/**@type {boolean}*/ acceptsMultiple = false;
|
||||||
/**@type {string|SlashCommandClosure}*/ defaultValue;
|
/**@type {string|SlashCommandClosure}*/ defaultValue;
|
||||||
/**@type {SlashCommandEnumValue[]}*/ enumList = [];
|
/**@type {SlashCommandEnumValue[]}*/ enumList = [];
|
||||||
/**@type {(executor:SlashCommandExecutor)=>SlashCommandEnumValue[]}*/ enumProvider = null;
|
/**@type {(executor:SlashCommandExecutor, scope:SlashCommandScope)=>SlashCommandEnumValue[]}*/ enumProvider = null;
|
||||||
/**@type {boolean}*/ forceEnum = false;
|
/**@type {boolean}*/ forceEnum = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -57,7 +60,7 @@ export class SlashCommandArgument {
|
|||||||
* @param {ARGUMENT_TYPE|ARGUMENT_TYPE[]} types
|
* @param {ARGUMENT_TYPE|ARGUMENT_TYPE[]} types
|
||||||
* @param {string|SlashCommandClosure} defaultValue
|
* @param {string|SlashCommandClosure} defaultValue
|
||||||
* @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} enums
|
* @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} enums
|
||||||
* @param {(executor:SlashCommandExecutor)=>SlashCommandEnumValue[]} enumProvider function that returns auto complete options
|
* @param {(executor:SlashCommandExecutor, scope:SlashCommandScope)=>SlashCommandEnumValue[]} enumProvider function that returns auto complete options
|
||||||
*/
|
*/
|
||||||
constructor(description, types, isRequired = false, acceptsMultiple = false, defaultValue = null, enums = [], enumProvider = null, forceEnum = false) {
|
constructor(description, types, isRequired = false, acceptsMultiple = false, defaultValue = null, enums = [], enumProvider = null, forceEnum = false) {
|
||||||
this.description = description;
|
this.description = description;
|
||||||
@ -89,7 +92,7 @@ export class SlashCommandNamedArgument extends SlashCommandArgument {
|
|||||||
* @param {boolean} [props.acceptsMultiple=false] default: false - whether argument accepts multiple values
|
* @param {boolean} [props.acceptsMultiple=false] default: false - whether argument accepts multiple values
|
||||||
* @param {string|SlashCommandClosure} [props.defaultValue=null] default value if no value is provided
|
* @param {string|SlashCommandClosure} [props.defaultValue=null] default value if no value is provided
|
||||||
* @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} [props.enumList=[]] list of accepted values
|
* @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} [props.enumList=[]] list of accepted values
|
||||||
* @param {(executor:SlashCommandExecutor)=>SlashCommandEnumValue[]} [props.enumProvider=null] function that returns auto complete options
|
* @param {(executor:SlashCommandExecutor, scope:SlashCommandScope)=>SlashCommandEnumValue[]} [props.enumProvider=null] function that returns auto complete options
|
||||||
* @param {boolean} [props.forceEnum=false] default: false - whether the input must match one of the enum values
|
* @param {boolean} [props.forceEnum=false] default: false - whether the input must match one of the enum values
|
||||||
*/
|
*/
|
||||||
static fromProps(props) {
|
static fromProps(props) {
|
||||||
@ -119,7 +122,7 @@ export class SlashCommandNamedArgument extends SlashCommandArgument {
|
|||||||
* @param {string|SlashCommandClosure} [defaultValue=null]
|
* @param {string|SlashCommandClosure} [defaultValue=null]
|
||||||
* @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} [enums=[]]
|
* @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} [enums=[]]
|
||||||
* @param {string[]} [aliases=[]]
|
* @param {string[]} [aliases=[]]
|
||||||
* @param {(executor:SlashCommandExecutor)=>SlashCommandEnumValue[]} [enumProvider=null] function that returns auto complete options
|
* @param {(executor:SlashCommandExecutor, scope:SlashCommandScope)=>SlashCommandEnumValue[]} [enumProvider=null] function that returns auto complete options
|
||||||
* @param {boolean} [forceEnum=false]
|
* @param {boolean} [forceEnum=false]
|
||||||
*/
|
*/
|
||||||
constructor(name, description, types, isRequired = false, acceptsMultiple = false, defaultValue = null, enums = [], aliases = [], enumProvider = null, forceEnum = false) {
|
constructor(name, description, types, isRequired = false, acceptsMultiple = false, defaultValue = null, enums = [], aliases = [], enumProvider = null, forceEnum = false) {
|
||||||
|
@ -8,15 +8,18 @@ import { SlashCommandCommandAutoCompleteOption } from './SlashCommandCommandAuto
|
|||||||
import { SlashCommandEnumAutoCompleteOption } from './SlashCommandEnumAutoCompleteOption.js';
|
import { SlashCommandEnumAutoCompleteOption } from './SlashCommandEnumAutoCompleteOption.js';
|
||||||
import { SlashCommandExecutor } from './SlashCommandExecutor.js';
|
import { SlashCommandExecutor } from './SlashCommandExecutor.js';
|
||||||
import { SlashCommandNamedArgumentAutoCompleteOption } from './SlashCommandNamedArgumentAutoCompleteOption.js';
|
import { SlashCommandNamedArgumentAutoCompleteOption } from './SlashCommandNamedArgumentAutoCompleteOption.js';
|
||||||
|
import { SlashCommandScope } from './SlashCommandScope.js';
|
||||||
|
|
||||||
export class SlashCommandAutoCompleteNameResult extends AutoCompleteNameResult {
|
export class SlashCommandAutoCompleteNameResult extends AutoCompleteNameResult {
|
||||||
/**@type {SlashCommandExecutor}*/ executor;
|
/**@type {SlashCommandExecutor}*/ executor;
|
||||||
|
/**@type {SlashCommandScope}*/ scope;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {SlashCommandExecutor} executor
|
* @param {SlashCommandExecutor} executor
|
||||||
|
* @param {SlashCommandScope} scope
|
||||||
* @param {Object.<string,SlashCommand>} commands
|
* @param {Object.<string,SlashCommand>} commands
|
||||||
*/
|
*/
|
||||||
constructor(executor, commands) {
|
constructor(executor, scope, commands) {
|
||||||
super(
|
super(
|
||||||
executor.name,
|
executor.name,
|
||||||
executor.start,
|
executor.start,
|
||||||
@ -29,6 +32,7 @@ export class SlashCommandAutoCompleteNameResult extends AutoCompleteNameResult {
|
|||||||
()=>'No slash commands found!',
|
()=>'No slash commands found!',
|
||||||
);
|
);
|
||||||
this.executor = executor;
|
this.executor = executor;
|
||||||
|
this.scope = scope;
|
||||||
}
|
}
|
||||||
|
|
||||||
getSecondaryNameAt(text, index, isSelect) {
|
getSecondaryNameAt(text, index, isSelect) {
|
||||||
@ -86,8 +90,8 @@ export class SlashCommandAutoCompleteNameResult extends AutoCompleteNameResult {
|
|||||||
}
|
}
|
||||||
} else if (unamedArgLength > 0 && index >= this.executor.startUnnamedArgs && index <= this.executor.endUnnamedArgs) {
|
} else if (unamedArgLength > 0 && index >= this.executor.startUnnamedArgs && index <= this.executor.endUnnamedArgs) {
|
||||||
// cursor is somewhere within the unnamed arguments
|
// cursor is somewhere within the unnamed arguments
|
||||||
//TODO if index is in first array item and that is a string, treat it as an unfinished named arg
|
// if index is in first array item and that is a string, treat it as an unfinished named arg
|
||||||
if (typeof this.executor.unnamedArgumentList[0].value == 'string') {
|
if (typeof this.executor.unnamedArgumentList[0]?.value == 'string') {
|
||||||
if (index <= this.executor.startUnnamedArgs + this.executor.unnamedArgumentList[0].value.length) {
|
if (index <= this.executor.startUnnamedArgs + this.executor.unnamedArgumentList[0].value.length) {
|
||||||
name = this.executor.unnamedArgumentList[0].value.slice(0, index - this.executor.startUnnamedArgs);
|
name = this.executor.unnamedArgumentList[0].value.slice(0, index - this.executor.startUnnamedArgs);
|
||||||
start = this.executor.startUnnamedArgs;
|
start = this.executor.startUnnamedArgs;
|
||||||
@ -103,7 +107,7 @@ export class SlashCommandAutoCompleteNameResult extends AutoCompleteNameResult {
|
|||||||
|
|
||||||
if (name.includes('=') && cmdArg) {
|
if (name.includes('=') && cmdArg) {
|
||||||
// if cursor is already behind "=" check for enums
|
// if cursor is already behind "=" check for enums
|
||||||
const enumList = cmdArg?.enumProvider?.(this.executor) ?? cmdArg?.enumList;
|
const enumList = cmdArg?.enumProvider?.(this.executor, this.scope) ?? cmdArg?.enumList;
|
||||||
if (cmdArg && enumList?.length) {
|
if (cmdArg && enumList?.length) {
|
||||||
if (isSelect && enumList.find(it=>it.value == value) && argAssign && argAssign.end == index) {
|
if (isSelect && enumList.find(it=>it.value == value) && argAssign && argAssign.end == index) {
|
||||||
return null;
|
return null;
|
||||||
@ -111,7 +115,7 @@ export class SlashCommandAutoCompleteNameResult extends AutoCompleteNameResult {
|
|||||||
const result = new AutoCompleteSecondaryNameResult(
|
const result = new AutoCompleteSecondaryNameResult(
|
||||||
value,
|
value,
|
||||||
start + name.length,
|
start + name.length,
|
||||||
enumList.map(it=>new SlashCommandEnumAutoCompleteOption(this.executor.command, it)),
|
enumList.map(it=>SlashCommandEnumAutoCompleteOption.from(this.executor.command, it)),
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
result.isRequired = true;
|
result.isRequired = true;
|
||||||
@ -150,7 +154,10 @@ export class SlashCommandAutoCompleteNameResult extends AutoCompleteNameResult {
|
|||||||
if (idx > -1) {
|
if (idx > -1) {
|
||||||
argAssign = this.executor.unnamedArgumentList[idx];
|
argAssign = this.executor.unnamedArgumentList[idx];
|
||||||
cmdArg = this.executor.command.unnamedArgumentList[idx];
|
cmdArg = this.executor.command.unnamedArgumentList[idx];
|
||||||
const enumList = cmdArg?.enumProvider?.(this.executor) ?? cmdArg?.enumList;
|
if (cmdArg === undefined && this.executor.command.unnamedArgumentList.slice(-1)[0]?.acceptsMultiple) {
|
||||||
|
cmdArg = this.executor.command.unnamedArgumentList.slice(-1)[0];
|
||||||
|
}
|
||||||
|
const enumList = cmdArg?.enumProvider?.(this.executor, this.scope) ?? cmdArg?.enumList;
|
||||||
if (cmdArg && enumList.length > 0) {
|
if (cmdArg && enumList.length > 0) {
|
||||||
value = argAssign.value.toString().slice(0, index - argAssign.start);
|
value = argAssign.value.toString().slice(0, index - argAssign.start);
|
||||||
start = argAssign.start;
|
start = argAssign.start;
|
||||||
@ -161,23 +168,26 @@ export class SlashCommandAutoCompleteNameResult extends AutoCompleteNameResult {
|
|||||||
value = '';
|
value = '';
|
||||||
start = index;
|
start = index;
|
||||||
cmdArg = notProvidedArguments[0];
|
cmdArg = notProvidedArguments[0];
|
||||||
|
if (cmdArg === undefined && this.executor.command.unnamedArgumentList.slice(-1)[0]?.acceptsMultiple) {
|
||||||
|
cmdArg = this.executor.command.unnamedArgumentList.slice(-1)[0];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const enumList = cmdArg?.enumProvider?.(this.executor) ?? cmdArg?.enumList;
|
const enumList = cmdArg?.enumProvider?.(this.executor, this.scope) ?? cmdArg?.enumList;
|
||||||
if (cmdArg == null || enumList.length == 0) return null;
|
if (cmdArg == null || enumList.length == 0) return null;
|
||||||
|
|
||||||
const result = new AutoCompleteSecondaryNameResult(
|
const result = new AutoCompleteSecondaryNameResult(
|
||||||
value,
|
value,
|
||||||
start,
|
start,
|
||||||
enumList.map(it=>new SlashCommandEnumAutoCompleteOption(this.executor.command, it)),
|
enumList.map(it=>SlashCommandEnumAutoCompleteOption.from(this.executor.command, it)),
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
const isCompleteValue = enumList.find(it=>it.value == value);
|
const isCompleteValue = enumList.find(it=>it.value == value);
|
||||||
const isSelectedValue = isSelect && isCompleteValue;
|
const isSelectedValue = isSelect && isCompleteValue;
|
||||||
result.isRequired = cmdArg.isRequired && !isSelectedValue && !isCompleteValue;
|
result.isRequired = cmdArg.isRequired && !isSelectedValue;
|
||||||
result.forceMatch = cmdArg.forceEnum;
|
result.forceMatch = cmdArg.forceEnum;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
7
public/scripts/slash-commands/SlashCommandBreak.js
Normal file
7
public/scripts/slash-commands/SlashCommandBreak.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { SlashCommandExecutor } from './SlashCommandExecutor.js';
|
||||||
|
|
||||||
|
export class SlashCommandBreak extends SlashCommandExecutor {
|
||||||
|
get value() {
|
||||||
|
return this.unnamedArgumentList[0]?.value;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
export class SlashCommandBreakController {
|
||||||
|
/**@type {boolean} */ isBreak = false;
|
||||||
|
|
||||||
|
break() {
|
||||||
|
this.isBreak = true;
|
||||||
|
}
|
||||||
|
}
|
3
public/scripts/slash-commands/SlashCommandBreakPoint.js
Normal file
3
public/scripts/slash-commands/SlashCommandBreakPoint.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { SlashCommandExecutor } from './SlashCommandExecutor.js';
|
||||||
|
|
||||||
|
export class SlashCommandBreakPoint extends SlashCommandExecutor {}
|
@ -1,9 +1,13 @@
|
|||||||
import { substituteParams } from '../../script.js';
|
import { substituteParams } from '../../script.js';
|
||||||
import { delay, escapeRegex } from '../utils.js';
|
import { delay, escapeRegex, uuidv4 } from '../utils.js';
|
||||||
import { SlashCommand } from './SlashCommand.js';
|
import { SlashCommand } from './SlashCommand.js';
|
||||||
import { SlashCommandAbortController } from './SlashCommandAbortController.js';
|
import { SlashCommandAbortController } from './SlashCommandAbortController.js';
|
||||||
import { SlashCommandClosureExecutor } from './SlashCommandClosureExecutor.js';
|
import { SlashCommandBreak } from './SlashCommandBreak.js';
|
||||||
|
import { SlashCommandBreakController } from './SlashCommandBreakController.js';
|
||||||
|
import { SlashCommandBreakPoint } from './SlashCommandBreakPoint.js';
|
||||||
import { SlashCommandClosureResult } from './SlashCommandClosureResult.js';
|
import { SlashCommandClosureResult } from './SlashCommandClosureResult.js';
|
||||||
|
import { SlashCommandDebugController } from './SlashCommandDebugController.js';
|
||||||
|
import { SlashCommandExecutionError } from './SlashCommandExecutionError.js';
|
||||||
import { SlashCommandExecutor } from './SlashCommandExecutor.js';
|
import { SlashCommandExecutor } from './SlashCommandExecutor.js';
|
||||||
import { SlashCommandNamedArgumentAssignment } from './SlashCommandNamedArgumentAssignment.js';
|
import { SlashCommandNamedArgumentAssignment } from './SlashCommandNamedArgumentAssignment.js';
|
||||||
import { SlashCommandScope } from './SlashCommandScope.js';
|
import { SlashCommandScope } from './SlashCommandScope.js';
|
||||||
@ -17,8 +21,20 @@ export class SlashCommandClosure {
|
|||||||
/**@type {SlashCommandNamedArgumentAssignment[]}*/ providedArgumentList = [];
|
/**@type {SlashCommandNamedArgumentAssignment[]}*/ providedArgumentList = [];
|
||||||
/**@type {SlashCommandExecutor[]}*/ executorList = [];
|
/**@type {SlashCommandExecutor[]}*/ executorList = [];
|
||||||
/**@type {SlashCommandAbortController}*/ abortController;
|
/**@type {SlashCommandAbortController}*/ abortController;
|
||||||
|
/**@type {SlashCommandBreakController}*/ breakController;
|
||||||
|
/**@type {SlashCommandDebugController}*/ debugController;
|
||||||
/**@type {(done:number, total:number)=>void}*/ onProgress;
|
/**@type {(done:number, total:number)=>void}*/ onProgress;
|
||||||
/**@type {string}*/ rawText;
|
/**@type {string}*/ rawText;
|
||||||
|
/**@type {string}*/ fullText;
|
||||||
|
/**@type {string}*/ parserContext;
|
||||||
|
/**@type {string}*/ #source = uuidv4();
|
||||||
|
get source() { return this.#source; }
|
||||||
|
set source(value) {
|
||||||
|
this.#source = value;
|
||||||
|
for (const executor of this.executorList) {
|
||||||
|
executor.source = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**@type {number}*/
|
/**@type {number}*/
|
||||||
get commandCount() {
|
get commandCount() {
|
||||||
@ -30,7 +46,7 @@ export class SlashCommandClosure {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toString() {
|
toString() {
|
||||||
return '[Closure]';
|
return `[Closure]${this.executeNow ? '()' : ''}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -43,16 +59,37 @@ export class SlashCommandClosure {
|
|||||||
let isList = false;
|
let isList = false;
|
||||||
let listValues = [];
|
let listValues = [];
|
||||||
scope = scope ?? this.scope;
|
scope = scope ?? this.scope;
|
||||||
const macros = scope.macroList.map(it=>escapeRegex(it.key)).join('|');
|
const escapeMacro = (it, isAnchored = false)=>{
|
||||||
const re = new RegExp(`({{pipe}})|(?:{{var::([^\\s]+?)(?:::((?!}}).+))?}})|(?:{{(${macros})}})`);
|
const regexText = escapeRegex(it.key.replace(/\*/g, '~~~WILDCARD~~~'))
|
||||||
|
.replaceAll('~~~WILDCARD~~~', '(?:(?:(?!(?:::|}})).)*)')
|
||||||
|
;
|
||||||
|
if (isAnchored) {
|
||||||
|
return `^${regexText}$`;
|
||||||
|
}
|
||||||
|
return regexText;
|
||||||
|
};
|
||||||
|
const macroList = scope.macroList.toSorted((a,b)=>{
|
||||||
|
if (a.key.includes('*') && !b.key.includes('*')) return 1;
|
||||||
|
if (!a.key.includes('*') && b.key.includes('*')) return -1;
|
||||||
|
if (a.key.includes('*') && b.key.includes('*')) return b.key.indexOf('*') - a.key.indexOf('*');
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
const macros = macroList.map(it=>escapeMacro(it)).join('|');
|
||||||
|
const re = new RegExp(`(?<pipe>{{pipe}})|(?:{{var::(?<var>[^\\s]+?)(?:::(?<varIndex>(?!}}).+))?}})|(?:{{(?<macro>${macros})}})`);
|
||||||
let done = '';
|
let done = '';
|
||||||
let remaining = text;
|
let remaining = text;
|
||||||
while (re.test(remaining)) {
|
while (re.test(remaining)) {
|
||||||
const match = re.exec(remaining);
|
const match = re.exec(remaining);
|
||||||
const before = substituteParams(remaining.slice(0, match.index));
|
const before = substituteParams(remaining.slice(0, match.index));
|
||||||
const after = remaining.slice(match.index + match[0].length);
|
const after = remaining.slice(match.index + match[0].length);
|
||||||
const replacer = match[1] ? scope.pipe : match[2] ? scope.getVariable(match[2], match[3]) : scope.macroList.find(it=>it.key == match[4])?.value;
|
const replacer = match.groups.pipe ? scope.pipe : match.groups.var ? scope.getVariable(match.groups.var, match.groups.index) : macroList.find(it=>it.key == match.groups.macro || new RegExp(escapeMacro(it, true)).test(match.groups.macro))?.value;
|
||||||
if (replacer instanceof SlashCommandClosure) {
|
if (replacer instanceof SlashCommandClosure) {
|
||||||
|
replacer.abortController = this.abortController;
|
||||||
|
replacer.breakController = this.breakController;
|
||||||
|
replacer.scope.parent = this.scope;
|
||||||
|
if (this.debugController && !replacer.debugController) {
|
||||||
|
replacer.debugController = this.debugController;
|
||||||
|
}
|
||||||
isList = true;
|
isList = true;
|
||||||
if (match.index > 0) {
|
if (match.index > 0) {
|
||||||
listValues.push(before);
|
listValues.push(before);
|
||||||
@ -87,6 +124,12 @@ export class SlashCommandClosure {
|
|||||||
closure.providedArgumentList = this.providedArgumentList;
|
closure.providedArgumentList = this.providedArgumentList;
|
||||||
closure.executorList = this.executorList;
|
closure.executorList = this.executorList;
|
||||||
closure.abortController = this.abortController;
|
closure.abortController = this.abortController;
|
||||||
|
closure.breakController = this.breakController;
|
||||||
|
closure.debugController = this.debugController;
|
||||||
|
closure.rawText = this.rawText;
|
||||||
|
closure.fullText = this.fullText;
|
||||||
|
closure.parserContext = this.parserContext;
|
||||||
|
closure.source = this.source;
|
||||||
closure.onProgress = this.onProgress;
|
closure.onProgress = this.onProgress;
|
||||||
return closure;
|
return closure;
|
||||||
}
|
}
|
||||||
@ -96,11 +139,22 @@ export class SlashCommandClosure {
|
|||||||
* @returns {Promise<SlashCommandClosureResult>}
|
* @returns {Promise<SlashCommandClosureResult>}
|
||||||
*/
|
*/
|
||||||
async execute() {
|
async execute() {
|
||||||
|
// execute a copy of the closure to no taint it and its scope with the effects of its execution
|
||||||
|
// as this would affect the closure being called a second time (e.g., loop, multiple /run calls)
|
||||||
const closure = this.getCopy();
|
const closure = this.getCopy();
|
||||||
return await closure.executeDirect();
|
const gen = closure.executeDirect();
|
||||||
|
let step;
|
||||||
|
while (!step?.done) {
|
||||||
|
step = await gen.next(this.debugController?.testStepping(this) ?? false);
|
||||||
|
if (!(step.value instanceof SlashCommandClosureResult) && this.debugController) {
|
||||||
|
this.debugController.isStepping = await this.debugController.awaitBreakPoint(step.value.closure, step.value.executor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return step.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
async executeDirect() {
|
async * executeDirect() {
|
||||||
|
this.debugController?.down(this);
|
||||||
// closure arguments
|
// closure arguments
|
||||||
for (const arg of this.argumentList) {
|
for (const arg of this.argumentList) {
|
||||||
let v = arg.value;
|
let v = arg.value;
|
||||||
@ -108,6 +162,7 @@ export class SlashCommandClosure {
|
|||||||
/**@type {SlashCommandClosure}*/
|
/**@type {SlashCommandClosure}*/
|
||||||
const closure = v;
|
const closure = v;
|
||||||
closure.scope.parent = this.scope;
|
closure.scope.parent = this.scope;
|
||||||
|
closure.breakController = this.breakController;
|
||||||
if (closure.executeNow) {
|
if (closure.executeNow) {
|
||||||
v = (await closure.execute())?.pipe;
|
v = (await closure.execute())?.pipe;
|
||||||
} else {
|
} else {
|
||||||
@ -131,6 +186,7 @@ export class SlashCommandClosure {
|
|||||||
/**@type {SlashCommandClosure}*/
|
/**@type {SlashCommandClosure}*/
|
||||||
const closure = v;
|
const closure = v;
|
||||||
closure.scope.parent = this.scope;
|
closure.scope.parent = this.scope;
|
||||||
|
closure.breakController = this.breakController;
|
||||||
if (closure.executeNow) {
|
if (closure.executeNow) {
|
||||||
v = (await closure.execute())?.pipe;
|
v = (await closure.execute())?.pipe;
|
||||||
} else {
|
} else {
|
||||||
@ -149,106 +205,145 @@ export class SlashCommandClosure {
|
|||||||
this.scope.setVariable(arg.name, v);
|
this.scope.setVariable(arg.name, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
let done = 0;
|
|
||||||
if (this.executorList.length == 0) {
|
if (this.executorList.length == 0) {
|
||||||
this.scope.pipe = '';
|
this.scope.pipe = '';
|
||||||
}
|
}
|
||||||
|
const stepper = this.executeStep();
|
||||||
|
let step;
|
||||||
|
while (!step?.done && !this.breakController?.isBreak) {
|
||||||
|
// get executor before execution
|
||||||
|
step = await stepper.next();
|
||||||
|
if (step.value instanceof SlashCommandBreakPoint) {
|
||||||
|
console.log('encountered SlashCommandBreakPoint');
|
||||||
|
if (this.debugController) {
|
||||||
|
// resolve args
|
||||||
|
step = await stepper.next();
|
||||||
|
// "execute" breakpoint
|
||||||
|
step = await stepper.next();
|
||||||
|
// get next executor
|
||||||
|
step = await stepper.next();
|
||||||
|
// breakpoint has to yield before arguments are resolved if one of the
|
||||||
|
// arguments is an immediate closure, otherwise you cannot step into the
|
||||||
|
// immediate closure
|
||||||
|
const hasImmediateClosureInNamedArgs = /**@type {SlashCommandExecutor}*/(step.value)?.namedArgumentList?.find(it=>it.value instanceof SlashCommandClosure && it.value.executeNow);
|
||||||
|
const hasImmediateClosureInUnnamedArgs = /**@type {SlashCommandExecutor}*/(step.value)?.unnamedArgumentList?.find(it=>it.value instanceof SlashCommandClosure && it.value.executeNow);
|
||||||
|
if (hasImmediateClosureInNamedArgs || hasImmediateClosureInUnnamedArgs) {
|
||||||
|
this.debugController.isStepping = yield { closure:this, executor:step.value };
|
||||||
|
} else {
|
||||||
|
this.debugController.isStepping = true;
|
||||||
|
this.debugController.stepStack[this.debugController.stepStack.length - 1] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (!step.done && this.debugController?.testStepping(this)) {
|
||||||
|
this.debugController.isSteppingInto = false;
|
||||||
|
// if stepping, have to yield before arguments are resolved if one of the arguments
|
||||||
|
// is an immediate closure, otherwise you cannot step into the immediate closure
|
||||||
|
const hasImmediateClosureInNamedArgs = /**@type {SlashCommandExecutor}*/(step.value)?.namedArgumentList?.find(it=>it.value instanceof SlashCommandClosure && it.value.executeNow);
|
||||||
|
const hasImmediateClosureInUnnamedArgs = /**@type {SlashCommandExecutor}*/(step.value)?.unnamedArgumentList?.find(it=>it.value instanceof SlashCommandClosure && it.value.executeNow);
|
||||||
|
if (hasImmediateClosureInNamedArgs || hasImmediateClosureInUnnamedArgs) {
|
||||||
|
this.debugController.isStepping = yield { closure:this, executor:step.value };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// resolve args
|
||||||
|
step = await stepper.next();
|
||||||
|
if (step.value instanceof SlashCommandBreak) {
|
||||||
|
console.log('encountered SlashCommandBreak');
|
||||||
|
if (this.breakController) {
|
||||||
|
this.breakController?.break();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (!step.done && this.debugController?.testStepping(this)) {
|
||||||
|
this.debugController.isSteppingInto = false;
|
||||||
|
this.debugController.isStepping = yield { closure:this, executor:step.value };
|
||||||
|
}
|
||||||
|
// execute executor
|
||||||
|
step = await stepper.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// if execution has returned a closure result, return that (should only happen on abort)
|
||||||
|
if (step.value instanceof SlashCommandClosureResult) {
|
||||||
|
this.debugController?.up();
|
||||||
|
return step.value;
|
||||||
|
}
|
||||||
|
/**@type {SlashCommandClosureResult} */
|
||||||
|
const result = Object.assign(new SlashCommandClosureResult(), { pipe: this.scope.pipe, isBreak: this.breakController?.isBreak ?? false });
|
||||||
|
this.debugController?.up();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Generator that steps through the executor list.
|
||||||
|
* Every executor is split into three steps:
|
||||||
|
* - before arguments are resolved
|
||||||
|
* - after arguments are resolved
|
||||||
|
* - after execution
|
||||||
|
*/
|
||||||
|
async * executeStep() {
|
||||||
|
let done = 0;
|
||||||
|
let isFirst = true;
|
||||||
for (const executor of this.executorList) {
|
for (const executor of this.executorList) {
|
||||||
this.onProgress?.(done, this.commandCount);
|
this.onProgress?.(done, this.commandCount);
|
||||||
if (executor instanceof SlashCommandClosureExecutor) {
|
if (this.debugController) {
|
||||||
const closure = this.scope.getVariable(executor.name);
|
this.debugController.setExecutor(executor);
|
||||||
if (!closure || !(closure instanceof SlashCommandClosure)) throw new Error(`${executor.name} is not a closure.`);
|
this.debugController.namedArguments = undefined;
|
||||||
closure.scope.parent = this.scope;
|
this.debugController.unnamedArguments = undefined;
|
||||||
closure.providedArgumentList = executor.providedArgumentList;
|
}
|
||||||
const result = await closure.execute();
|
// yield before doing anything with this executor, the debugger might want to do
|
||||||
this.scope.pipe = result.pipe;
|
// something with it (e.g., breakpoint, immediate closures that need resolving
|
||||||
|
// or stepping into)
|
||||||
|
yield executor;
|
||||||
|
/**@type {import('./SlashCommand.js').NamedArguments} */
|
||||||
|
// @ts-ignore
|
||||||
|
let args = {
|
||||||
|
_scope: this.scope,
|
||||||
|
_parserFlags: executor.parserFlags,
|
||||||
|
_abortController: this.abortController,
|
||||||
|
_debugController: this.debugController,
|
||||||
|
_hasUnnamedArgument: executor.unnamedArgumentList.length > 0,
|
||||||
|
};
|
||||||
|
if (executor instanceof SlashCommandBreakPoint) {
|
||||||
|
// nothing to do for breakpoints, just raise counter and yield for "before exec"
|
||||||
|
done++;
|
||||||
|
yield executor;
|
||||||
|
isFirst = false;
|
||||||
|
} else if (executor instanceof SlashCommandBreak) {
|
||||||
|
// /break need to resolve the unnamed arg and put it into pipe, then yield
|
||||||
|
// for "before exec"
|
||||||
|
const value = await this.substituteUnnamedArgument(executor, isFirst, args);
|
||||||
|
done += this.executorList.length - this.executorList.indexOf(executor);
|
||||||
|
this.scope.pipe = value ?? this.scope.pipe;
|
||||||
|
yield executor;
|
||||||
|
isFirst = false;
|
||||||
} else {
|
} else {
|
||||||
/**@type {import('./SlashCommand.js').NamedArguments} */
|
// regular commands do all the argument resolving logic...
|
||||||
let args = {
|
await this.substituteNamedArguments(executor, args);
|
||||||
_scope: this.scope,
|
let value = await this.substituteUnnamedArgument(executor, isFirst, args);
|
||||||
_parserFlags: executor.parserFlags,
|
|
||||||
_abortController: this.abortController,
|
|
||||||
_hasUnnamedArgument: executor.unnamedArgumentList.length > 0,
|
|
||||||
};
|
|
||||||
let value;
|
|
||||||
// substitute named arguments
|
|
||||||
for (const arg of executor.namedArgumentList) {
|
|
||||||
if (arg.value instanceof SlashCommandClosure) {
|
|
||||||
/**@type {SlashCommandClosure}*/
|
|
||||||
const closure = arg.value;
|
|
||||||
closure.scope.parent = this.scope;
|
|
||||||
if (closure.executeNow) {
|
|
||||||
args[arg.name] = (await closure.execute())?.pipe;
|
|
||||||
} else {
|
|
||||||
args[arg.name] = closure;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
args[arg.name] = this.substituteParams(arg.value);
|
|
||||||
}
|
|
||||||
// unescape named argument
|
|
||||||
if (typeof args[arg.name] == 'string') {
|
|
||||||
args[arg.name] = args[arg.name]
|
|
||||||
?.replace(/\\\{/g, '{')
|
|
||||||
?.replace(/\\\}/g, '}')
|
|
||||||
;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// substitute unnamed argument
|
|
||||||
if (executor.unnamedArgumentList.length == 0) {
|
|
||||||
if (executor.injectPipe) {
|
|
||||||
value = this.scope.pipe;
|
|
||||||
args._hasUnnamedArgument = this.scope.pipe !== null && this.scope.pipe !== undefined;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
value = [];
|
|
||||||
for (let i = 0; i < executor.unnamedArgumentList.length; i++) {
|
|
||||||
let v = executor.unnamedArgumentList[i].value;
|
|
||||||
if (v instanceof SlashCommandClosure) {
|
|
||||||
/**@type {SlashCommandClosure}*/
|
|
||||||
const closure = v;
|
|
||||||
closure.scope.parent = this.scope;
|
|
||||||
if (closure.executeNow) {
|
|
||||||
v = (await closure.execute())?.pipe;
|
|
||||||
} else {
|
|
||||||
v = closure;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
v = this.substituteParams(v);
|
|
||||||
}
|
|
||||||
value[i] = v;
|
|
||||||
}
|
|
||||||
if (!executor.command.splitUnnamedArgument) {
|
|
||||||
if (value.length == 1) {
|
|
||||||
value = value[0];
|
|
||||||
} else if (!value.find(it=>it instanceof SlashCommandClosure)) {
|
|
||||||
value = value.join('');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// unescape unnamed argument
|
|
||||||
if (typeof value == 'string') {
|
|
||||||
value = value
|
|
||||||
?.replace(/\\\{/g, '{')
|
|
||||||
?.replace(/\\\}/g, '}')
|
|
||||||
;
|
|
||||||
} else if (Array.isArray(value)) {
|
|
||||||
value = value.map(v=>{
|
|
||||||
if (typeof v == 'string') {
|
|
||||||
return v
|
|
||||||
?.replace(/\\\{/g, '{')
|
|
||||||
?.replace(/\\\}/g, '}');
|
|
||||||
}
|
|
||||||
return v;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let abortResult = await this.testAbortController();
|
let abortResult = await this.testAbortController();
|
||||||
if (abortResult) {
|
if (abortResult) {
|
||||||
return abortResult;
|
return abortResult;
|
||||||
}
|
}
|
||||||
|
if (this.debugController) {
|
||||||
|
this.debugController.namedArguments = args;
|
||||||
|
this.debugController.unnamedArguments = value ?? '';
|
||||||
|
}
|
||||||
|
// then yield for "before exec"
|
||||||
|
yield executor;
|
||||||
|
// followed by command execution
|
||||||
executor.onProgress = (subDone, subTotal)=>this.onProgress?.(done + subDone, this.commandCount);
|
executor.onProgress = (subDone, subTotal)=>this.onProgress?.(done + subDone, this.commandCount);
|
||||||
this.scope.pipe = await executor.command.callback(args, value ?? '');
|
const isStepping = this.debugController?.testStepping(this);
|
||||||
|
if (this.debugController) {
|
||||||
|
this.debugController.isStepping = false || this.debugController.isSteppingInto;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this.scope.pipe = await executor.command.callback(args, value ?? '');
|
||||||
|
} catch (ex) {
|
||||||
|
throw new SlashCommandExecutionError(ex, ex.message, executor.name, executor.start, executor.end, this.fullText.slice(executor.start, executor.end), this.fullText);
|
||||||
|
}
|
||||||
|
if (this.debugController) {
|
||||||
|
this.debugController.namedArguments = undefined;
|
||||||
|
this.debugController.unnamedArguments = undefined;
|
||||||
|
this.debugController.isStepping = isStepping;
|
||||||
|
}
|
||||||
this.#lintPipe(executor.command);
|
this.#lintPipe(executor.command);
|
||||||
done += executor.commandCount;
|
done += executor.commandCount;
|
||||||
this.onProgress?.(done, this.commandCount);
|
this.onProgress?.(done, this.commandCount);
|
||||||
@ -257,10 +352,10 @@ export class SlashCommandClosure {
|
|||||||
return abortResult;
|
return abortResult;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// finally, yield for "after exec"
|
||||||
|
yield executor;
|
||||||
|
isFirst = false;
|
||||||
}
|
}
|
||||||
/**@type {SlashCommandClosureResult} */
|
|
||||||
const result = Object.assign(new SlashCommandClosureResult(), { pipe: this.scope.pipe });
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async testPaused() {
|
async testPaused() {
|
||||||
@ -279,14 +374,113 @@ export class SlashCommandClosure {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {SlashCommandExecutor} executor
|
||||||
|
* @param {import('./SlashCommand.js').NamedArguments} args
|
||||||
|
*/
|
||||||
|
async substituteNamedArguments(executor, args) {
|
||||||
|
// substitute named arguments
|
||||||
|
for (const arg of executor.namedArgumentList) {
|
||||||
|
if (arg.value instanceof SlashCommandClosure) {
|
||||||
|
/**@type {SlashCommandClosure}*/
|
||||||
|
const closure = arg.value;
|
||||||
|
closure.scope.parent = this.scope;
|
||||||
|
closure.breakController = this.breakController;
|
||||||
|
if (this.debugController && !closure.debugController) {
|
||||||
|
closure.debugController = this.debugController;
|
||||||
|
}
|
||||||
|
if (closure.executeNow) {
|
||||||
|
args[arg.name] = (await closure.execute())?.pipe;
|
||||||
|
} else {
|
||||||
|
args[arg.name] = closure;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
args[arg.name] = this.substituteParams(arg.value);
|
||||||
|
}
|
||||||
|
// unescape named argument
|
||||||
|
if (typeof args[arg.name] == 'string') {
|
||||||
|
args[arg.name] = args[arg.name]
|
||||||
|
?.replace(/\\\{/g, '{')
|
||||||
|
?.replace(/\\\}/g, '}')
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {SlashCommandExecutor} executor
|
||||||
|
* @param {boolean} isFirst
|
||||||
|
* @param {import('./SlashCommand.js').NamedArguments} args
|
||||||
|
* @returns {Promise<string|SlashCommandClosure|(string|SlashCommandClosure)[]>}
|
||||||
|
*/
|
||||||
|
async substituteUnnamedArgument(executor, isFirst, args) {
|
||||||
|
let value;
|
||||||
|
// substitute unnamed argument
|
||||||
|
if (executor.unnamedArgumentList.length == 0) {
|
||||||
|
if (!isFirst && executor.injectPipe) {
|
||||||
|
value = this.scope.pipe;
|
||||||
|
args._hasUnnamedArgument = this.scope.pipe !== null && this.scope.pipe !== undefined;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
value = [];
|
||||||
|
for (let i = 0; i < executor.unnamedArgumentList.length; i++) {
|
||||||
|
let v = executor.unnamedArgumentList[i].value;
|
||||||
|
if (v instanceof SlashCommandClosure) {
|
||||||
|
/**@type {SlashCommandClosure}*/
|
||||||
|
const closure = v;
|
||||||
|
closure.scope.parent = this.scope;
|
||||||
|
closure.breakController = this.breakController;
|
||||||
|
if (this.debugController && !closure.debugController) {
|
||||||
|
closure.debugController = this.debugController;
|
||||||
|
}
|
||||||
|
if (closure.executeNow) {
|
||||||
|
v = (await closure.execute())?.pipe;
|
||||||
|
} else {
|
||||||
|
v = closure;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
v = this.substituteParams(v);
|
||||||
|
}
|
||||||
|
value[i] = v;
|
||||||
|
}
|
||||||
|
if (!executor.command.splitUnnamedArgument) {
|
||||||
|
if (value.length == 1) {
|
||||||
|
value = value[0];
|
||||||
|
} else if (!value.find(it=>it instanceof SlashCommandClosure)) {
|
||||||
|
value = value.join('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// unescape unnamed argument
|
||||||
|
if (typeof value == 'string') {
|
||||||
|
value = value
|
||||||
|
?.replace(/\\\{/g, '{')
|
||||||
|
?.replace(/\\\}/g, '}')
|
||||||
|
;
|
||||||
|
} else if (Array.isArray(value)) {
|
||||||
|
value = value.map(v=>{
|
||||||
|
if (typeof v == 'string') {
|
||||||
|
return v
|
||||||
|
?.replace(/\\\{/g, '{')
|
||||||
|
?.replace(/\\\}/g, '}');
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Auto-fixes the pipe if it is not a valid result for STscript.
|
* Auto-fixes the pipe if it is not a valid result for STscript.
|
||||||
* @param {SlashCommand} command Command being executed
|
* @param {SlashCommand} command Command being executed
|
||||||
*/
|
*/
|
||||||
#lintPipe(command) {
|
#lintPipe(command) {
|
||||||
if (this.scope.pipe === undefined || this.scope.pipe === null) {
|
if (this.scope.pipe === undefined || this.scope.pipe === null) {
|
||||||
console.warn(`${command.name} returned undefined or null. Auto-fixing to empty string.`);
|
console.warn(`/${command.name} returned undefined or null. Auto-fixing to empty string.`);
|
||||||
this.scope.pipe = '';
|
this.scope.pipe = '';
|
||||||
|
} else if (!(typeof this.scope.pipe == 'string' || this.scope.pipe instanceof SlashCommandClosure)) {
|
||||||
|
console.warn(`/${command.name} returned illegal type (${typeof this.scope.pipe} - ${this.scope.pipe.constructor?.name ?? ''}). Auto-fixing to stringified JSON.`);
|
||||||
|
this.scope.pipe = JSON.stringify(this.scope.pipe) ?? '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
import { SlashCommandNamedArgumentAssignment } from './SlashCommandNamedArgumentAssignment.js';
|
|
||||||
|
|
||||||
export class SlashCommandClosureExecutor {
|
|
||||||
/**@type {String}*/ name = '';
|
|
||||||
// @ts-ignore
|
|
||||||
/**@type {SlashCommandNamedArgumentAssignment[]}*/ providedArgumentList = [];
|
|
||||||
}
|
|
@ -1,6 +1,7 @@
|
|||||||
export class SlashCommandClosureResult {
|
export class SlashCommandClosureResult {
|
||||||
/**@type {boolean}*/ interrupt = false;
|
/**@type {boolean}*/ interrupt = false;
|
||||||
/**@type {string}*/ pipe;
|
/**@type {string}*/ pipe;
|
||||||
|
/**@type {boolean}*/ isBreak = false;
|
||||||
/**@type {boolean}*/ isAborted = false;
|
/**@type {boolean}*/ isAborted = false;
|
||||||
/**@type {boolean}*/ isQuietlyAborted = false;
|
/**@type {boolean}*/ isQuietlyAborted = false;
|
||||||
/**@type {string}*/ abortReason;
|
/**@type {string}*/ abortReason;
|
||||||
|
@ -6,6 +6,7 @@ import { searchCharByName, getTagsList, tags } from '../tags.js';
|
|||||||
import { world_names } from '../world-info.js';
|
import { world_names } from '../world-info.js';
|
||||||
import { SlashCommandClosure } from './SlashCommandClosure.js';
|
import { SlashCommandClosure } from './SlashCommandClosure.js';
|
||||||
import { SlashCommandEnumValue, enumTypes } from './SlashCommandEnumValue.js';
|
import { SlashCommandEnumValue, enumTypes } from './SlashCommandEnumValue.js';
|
||||||
|
import { SlashCommandScope } from "./SlashCommandScope.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A collection of regularly used enum icons
|
* A collection of regularly used enum icons
|
||||||
@ -134,16 +135,16 @@ export const commonEnumProviders = {
|
|||||||
* Can be filtered by `type` to only show global or local variables
|
* Can be filtered by `type` to only show global or local variables
|
||||||
*
|
*
|
||||||
* @param {...('global'|'local'|'scope'|'all')} type - The type of variables to include in the array. Can be 'all', 'global', or 'local'.
|
* @param {...('global'|'local'|'scope'|'all')} type - The type of variables to include in the array. Can be 'all', 'global', or 'local'.
|
||||||
* @returns {() => SlashCommandEnumValue[]}
|
* @returns {(executor:SlashCommandExecutor, scope:SlashCommandScope) => SlashCommandEnumValue[]}
|
||||||
*/
|
*/
|
||||||
variables: (...type) => () => {
|
variables: (...type) => (executor, scope) => {
|
||||||
const types = type.flat();
|
const types = type.flat();
|
||||||
const isAll = types.includes('all');
|
const isAll = types.includes('all');
|
||||||
return [
|
return [
|
||||||
...isAll || types.includes('global') ? Object.keys(extension_settings.variables.global ?? []).map(name => new SlashCommandEnumValue(name, null, enumTypes.macro, enumIcons.globalVariable)) : [],
|
...isAll || types.includes('scope') ? scope.allVariableNames.map(name => new SlashCommandEnumValue(name, null, enumTypes.variable, enumIcons.scopeVariable)) : [],
|
||||||
...isAll || types.includes('local') ? Object.keys(chat_metadata.variables ?? []).map(name => new SlashCommandEnumValue(name, null, enumTypes.name, enumIcons.localVariable)) : [],
|
...isAll || types.includes('local') ? Object.keys(chat_metadata.variables ?? []).map(name => new SlashCommandEnumValue(name, null, enumTypes.name, enumIcons.localVariable)) : [],
|
||||||
...isAll || types.includes('scope') ? [].map(name => new SlashCommandEnumValue(name, null, enumTypes.variable, enumIcons.scopeVariable)) : [], // TODO: Add scoped variables here, Lenny
|
...isAll || types.includes('global') ? Object.keys(extension_settings.variables.global ?? []).map(name => new SlashCommandEnumValue(name, null, enumTypes.macro, enumIcons.globalVariable)) : [],
|
||||||
];
|
].filter((item, idx, list)=>idx == list.findIndex(it=>it.value == item.value));
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
83
public/scripts/slash-commands/SlashCommandDebugController.js
Normal file
83
public/scripts/slash-commands/SlashCommandDebugController.js
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import { SlashCommandClosure } from './SlashCommandClosure.js';
|
||||||
|
import { SlashCommandExecutor } from './SlashCommandExecutor.js';
|
||||||
|
|
||||||
|
export class SlashCommandDebugController {
|
||||||
|
/**@type {SlashCommandClosure[]} */ stack = [];
|
||||||
|
/**@type {SlashCommandExecutor[]} */ cmdStack = [];
|
||||||
|
/**@type {boolean[]} */ stepStack = [];
|
||||||
|
/**@type {boolean} */ isStepping = false;
|
||||||
|
/**@type {boolean} */ isSteppingInto = false;
|
||||||
|
/**@type {boolean} */ isSteppingOut = false;
|
||||||
|
|
||||||
|
/**@type {object} */ namedArguments;
|
||||||
|
/**@type {string|SlashCommandClosure|(string|SlashCommandClosure)[]} */ unnamedArguments;
|
||||||
|
|
||||||
|
/**@type {Promise<boolean>} */ continuePromise;
|
||||||
|
/**@type {(boolean)=>void} */ continueResolver;
|
||||||
|
|
||||||
|
/**@type {(closure:SlashCommandClosure, executor:SlashCommandExecutor)=>Promise<boolean>} */ onBreakPoint;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
testStepping(closure) {
|
||||||
|
return this.stepStack[this.stack.indexOf(closure)];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
down(closure) {
|
||||||
|
this.stack.push(closure);
|
||||||
|
if (this.stepStack.length < this.stack.length) {
|
||||||
|
this.stepStack.push(this.isSteppingInto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
up() {
|
||||||
|
this.stack.pop();
|
||||||
|
while (this.cmdStack.length > this.stack.length) this.cmdStack.pop();
|
||||||
|
this.stepStack.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
setExecutor(executor) {
|
||||||
|
this.cmdStack[this.stack.length - 1] = executor;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
resume() {
|
||||||
|
this.continueResolver?.(false);
|
||||||
|
this.continuePromise = null;
|
||||||
|
this.stepStack.forEach((_,idx)=>this.stepStack[idx] = false);
|
||||||
|
}
|
||||||
|
step() {
|
||||||
|
this.stepStack.forEach((_,idx)=>this.stepStack[idx] = true);
|
||||||
|
this.continueResolver?.(true);
|
||||||
|
this.continuePromise = null;
|
||||||
|
}
|
||||||
|
stepInto() {
|
||||||
|
this.isSteppingInto = true;
|
||||||
|
this.stepStack.forEach((_,idx)=>this.stepStack[idx] = true);
|
||||||
|
this.continueResolver?.(true);
|
||||||
|
this.continuePromise = null;
|
||||||
|
}
|
||||||
|
stepOut() {
|
||||||
|
this.isSteppingOut = true;
|
||||||
|
this.stepStack[this.stepStack.length - 1] = false;
|
||||||
|
this.continueResolver?.(false);
|
||||||
|
this.continuePromise = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async awaitContinue() {
|
||||||
|
this.continuePromise ??= new Promise(resolve=>{
|
||||||
|
this.continueResolver = resolve;
|
||||||
|
});
|
||||||
|
this.isStepping = await this.continuePromise;
|
||||||
|
return this.isStepping;
|
||||||
|
}
|
||||||
|
|
||||||
|
async awaitBreakPoint(closure, executor) {
|
||||||
|
this.isStepping = await this.onBreakPoint(closure, executor);
|
||||||
|
return this.isStepping;
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,17 @@ import { SlashCommand } from './SlashCommand.js';
|
|||||||
import { SlashCommandEnumValue } from './SlashCommandEnumValue.js';
|
import { SlashCommandEnumValue } from './SlashCommandEnumValue.js';
|
||||||
|
|
||||||
export class SlashCommandEnumAutoCompleteOption extends AutoCompleteOption {
|
export class SlashCommandEnumAutoCompleteOption extends AutoCompleteOption {
|
||||||
|
/**
|
||||||
|
* @param {SlashCommand} cmd
|
||||||
|
* @param {SlashCommandEnumValue} enumValue
|
||||||
|
* @returns {SlashCommandEnumAutoCompleteOption}
|
||||||
|
*/
|
||||||
|
static from(cmd, enumValue) {
|
||||||
|
const mapped = this.valueToOptionMap.find(it=>enumValue instanceof it.value)?.option ?? this;
|
||||||
|
return new mapped(cmd, enumValue);
|
||||||
|
}
|
||||||
|
/**@type {{value:(typeof SlashCommandEnumValue), option:(typeof SlashCommandEnumAutoCompleteOption)}[]} */
|
||||||
|
static valueToOptionMap = [];
|
||||||
/**@type {SlashCommand}*/ cmd;
|
/**@type {SlashCommand}*/ cmd;
|
||||||
/**@type {SlashCommandEnumValue}*/ enumValue;
|
/**@type {SlashCommandEnumValue}*/ enumValue;
|
||||||
|
|
||||||
@ -13,7 +24,7 @@ export class SlashCommandEnumAutoCompleteOption extends AutoCompleteOption {
|
|||||||
* @param {SlashCommandEnumValue} enumValue
|
* @param {SlashCommandEnumValue} enumValue
|
||||||
*/
|
*/
|
||||||
constructor(cmd, enumValue) {
|
constructor(cmd, enumValue) {
|
||||||
super(enumValue.value, enumValue.typeIcon, enumValue.type);
|
super(enumValue.value, enumValue.typeIcon, enumValue.type, enumValue.matchProvider, enumValue.valueProvider, enumValue.makeSelectable);
|
||||||
this.cmd = cmd;
|
this.cmd = cmd;
|
||||||
this.enumValue = enumValue;
|
this.enumValue = enumValue;
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
import { SlashCommandExecutor } from './SlashCommandExecutor.js';
|
||||||
|
import { SlashCommandScope } from './SlashCommandScope.js';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {'enum' | 'command' | 'namedArgument' | 'variable' | 'qr' | 'macro' | 'number' | 'name'} EnumType
|
* @typedef {'enum' | 'command' | 'namedArgument' | 'variable' | 'qr' | 'macro' | 'number' | 'name'} EnumType
|
||||||
@ -37,14 +40,17 @@ export const enumTypes = {
|
|||||||
getBasedOnIndex(index) {
|
getBasedOnIndex(index) {
|
||||||
const keys = Object.keys(this);
|
const keys = Object.keys(this);
|
||||||
return this[keys[(index ?? 0) % keys.length]];
|
return this[keys[(index ?? 0) % keys.length]];
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
export class SlashCommandEnumValue {
|
export class SlashCommandEnumValue {
|
||||||
/**@type {string}*/ value;
|
/**@type {string}*/ value;
|
||||||
/**@type {string}*/ description;
|
/**@type {string}*/ description;
|
||||||
/**@type {EnumType}*/ type = 'enum';
|
/**@type {EnumType}*/ type = 'enum';
|
||||||
/**@type {string}*/ typeIcon = '◊';
|
/**@type {string}*/ typeIcon = '◊';
|
||||||
|
/**@type {(input:string)=>boolean}*/ matchProvider;
|
||||||
|
/**@type {(input:string)=>string}*/ valueProvider;
|
||||||
|
/**@type {boolean}*/ makeSelectable = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A constructor for creating a SlashCommandEnumValue instance.
|
* A constructor for creating a SlashCommandEnumValue instance.
|
||||||
@ -52,13 +58,19 @@ export class SlashCommandEnumValue {
|
|||||||
* @param {string} value - The value
|
* @param {string} value - The value
|
||||||
* @param {string?} description - Optional description, displayed in a second line
|
* @param {string?} description - Optional description, displayed in a second line
|
||||||
* @param {EnumType?} type - type of the enum (defining its color)
|
* @param {EnumType?} type - type of the enum (defining its color)
|
||||||
* @param {string} typeIcon - The icon to display (Can be pulled from `enumIcons` for common ones)
|
* @param {string?} typeIcon - The icon to display (Can be pulled from `enumIcons` for common ones)
|
||||||
|
* @param {(input:string)=>boolean?} matchProvider - A custom function to match autocomplete input instead of startsWith/includes/fuzzy. Should only be used for generic options like "any number" or "any string". "input" is the part of the text that is getting auto completed.
|
||||||
|
* @param {(input:string)=>string?} valueProvider - A function returning a value to be used in autocomplete instead of the enum value. "input" is the part of the text that is getting auto completed. By default, values with a valueProvider will not be selectable in the autocomplete (with tab/enter).
|
||||||
|
* @param {boolean?} makeSelectable - Set to true to make the value selectable (through tab/enter) even though a valueProvider exists.
|
||||||
*/
|
*/
|
||||||
constructor(value, description = null, type = 'enum', typeIcon = '◊') {
|
constructor(value, description = null, type = 'enum', typeIcon = '◊', matchProvider = null, valueProvider = null, makeSelectable = false) {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
this.type = type ?? 'enum';
|
this.type = type ?? 'enum';
|
||||||
this.typeIcon = typeIcon;
|
this.typeIcon = typeIcon;
|
||||||
|
this.matchProvider = matchProvider;
|
||||||
|
this.valueProvider = valueProvider;
|
||||||
|
this.makeSelectable = makeSelectable;
|
||||||
}
|
}
|
||||||
|
|
||||||
toString() {
|
toString() {
|
||||||
|
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