Merge branch 'staging' into enable-autoselect-inputs
| @@ -55,6 +55,7 @@ module.exports = { | |||
| 55 | isProbablyReaderable: 'readonly', | 55 | isProbablyReaderable: 'readonly', |
| 56 | ePub: 'readonly', | 56 | ePub: 'readonly', |
| 57 | diff_match_patch: 'readonly', | 57 | diff_match_patch: 'readonly', |
| 58 | SillyTavern: 'readonly', | ||
| 58 | }, | 59 | }, |
| 59 | }, | 60 | }, |
| 60 | ], | 61 | ], |
| @@ -16,21 +16,21 @@ jobs: | |||
| 16 | uses: actions/stale@v4 | 16 | uses: actions/stale@v4 |
| 17 | with: | 17 | with: |
| 18 | repo-token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} | 18 | repo-token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} |
| 19 | days-before-stale: 360 | 19 | days-before-stale: 183 |
| 20 | days-before-close: 5 | 20 | days-before-close: 7 |
| 21 | operations-per-run: 30 | 21 | operations-per-run: 30 |
| 22 | remove-stale-when-updated: true | 22 | remove-stale-when-updated: true |
| 23 | enable-statistics: true | 23 | enable-statistics: true |
| 24 | stale-issue-message: > | 24 | stale-issue-message: > |
| 25 | This issue has gone 3 months without an update. To keep the ticket open, please indicate that it is still relevant in a comment below. | 25 | This issue has gone 6 months without an update. To keep the ticket open, please indicate that it is still relevant in a comment below. |
| 26 | Otherwise it will be closed in 5 working days. | 26 | Otherwise it will be closed in 7 days. |
| 27 | stale-pr-message: > | 27 | stale-pr-message: > |
| 28 | This PR is stale because it has been open 6 weeks with no activity. Either remove the stale label or comment below with a short update, | 28 | This PR is stale because it has been open 6 months with no activity. Either remove the stale label or comment below with a short update, |
| 29 | otherwise this PR will be closed in 5 days. | 29 | otherwise this PR will be closed in 7 days. |
| 30 | close-issue-message: > | 30 | close-issue-message: > |
| 31 | This issue was automatically closed because it has been stalled for over 1 year with no activity. | 31 | This issue was automatically closed because it has been stalled for over 6 months with no activity. |
| 32 | close-pr-message: > | 32 | close-pr-message: > |
| 33 | This pull request was automatically closed because it has been stalled for over 1 year with no activity. | 33 | This pull request was automatically closed because it has been stalled for over 6 months with no activity. |
| 34 | stale-issue-label: '⚰️ Stale' | 34 | stale-issue-label: '⚰️ Stale' |
| 35 | close-issue-label: '🕸️ Inactive' | 35 | close-issue-label: '🕸️ Inactive' |
| 36 | stale-pr-label: '⚰️ Stale' | 36 | stale-pr-label: '⚰️ Stale' |
| @@ -44,8 +44,8 @@ jobs: | |||
| 44 | uses: actions/stale@v4 | 44 | uses: actions/stale@v4 |
| 45 | with: | 45 | with: |
| 46 | repo-token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} | 46 | repo-token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} |
| 47 | days-before-stale: 5 | 47 | days-before-stale: 7 |
| 48 | days-before-close: 3 | 48 | days-before-close: 7 |
| 49 | operations-per-run: 30 | 49 | operations-per-run: 30 |
| 50 | remove-stale-when-updated: true | 50 | remove-stale-when-updated: true |
| 51 | stale-issue-message: > | 51 | stale-issue-message: > |
| @@ -65,7 +65,7 @@ jobs: | |||
| 65 | with: | 65 | with: |
| 66 | repo-token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} | 66 | repo-token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} |
| 67 | days-before-stale: 7 | 67 | days-before-stale: 7 |
| 68 | days-before-close: 365 | 68 | days-before-close: 183 |
| 69 | operations-per-run: 30 | 69 | operations-per-run: 30 |
| 70 | remove-stale-when-updated: true | 70 | remove-stale-when-updated: true |
| 71 | stale-issue-message: Hey SillyTavern, - Don't forget to respond! | 71 | stale-issue-message: Hey SillyTavern, - Don't forget to respond! |
| @@ -79,4 +79,4 @@ jobs: | |||
| 79 | close-issue-label: '🕸️ Inactive' | 79 | close-issue-label: '🕸️ Inactive' |
| 80 | close-pr-label: '🕸️ Inactive' | 80 | close-pr-label: '🕸️ Inactive' |
| 81 | exempt-issue-labels: '📌 Keep Open' | 81 | exempt-issue-labels: '📌 Keep Open' |
| 82 | exempt-pr-labels: '📌 Keep Open' | ||
| 82 | \ No newline at end of file | \ No newline at end of file | |
| 82 | exempt-pr-labels: '📌 Keep Open' | ||
| @@ -29,4 +29,13 @@ | |||
| 29 | - Updating GitHub Actions. | 29 | - Updating GitHub Actions. |
| 30 | - Hotfixing a critical bug. | 30 | - Hotfixing a critical bug. |
| 31 | 4. Project maintainers will test and can change your code before merging. | 31 | 4. Project maintainers will test and can change your code before merging. |
| 32 | 5. Mind the license. Your contributions will be licensed under the GNU Affero General Public License. If you don't know what that implies, consult your lawyer. | 32 | 5. Write at least somewhat meaningful PR descriptions. There's no "right" way to do it, but the following may help with outlining a general structure: |
| 33 | - What is the reason for a change? | ||
| 34 | - What did you do to achieve this? | ||
| 35 | - How would a reviewer test the change? | ||
| 36 | 6. Mind the license. Your contributions will be licensed under the GNU Affero General Public License. If you don't know what that implies, consult your lawyer. | ||
| 37 | |||
| 38 | ## Further reading | ||
| 39 | |||
| 40 | 1. [How to write UI extensions](https://docs.sillytavern.app/for-contributors/writing-extensions/) | ||
| 41 | 2. [How to write server plugins](https://docs.sillytavern.app/for-contributors/server-plugins) | ||
| @@ -4,8 +4,22 @@ dataRoot: ./data | |||
| 4 | # -- SERVER CONFIGURATION -- | 4 | # -- SERVER CONFIGURATION -- |
| 5 | # Listen for incoming connections | 5 | # Listen for incoming connections |
| 6 | listen: false | 6 | listen: false |
| 7 | # Enables IPv6 and/or IPv4 protocols. Need to have at least one enabled! | ||
| 8 | protocol: | ||
| 9 | ipv4: true | ||
| 10 | ipv6: false | ||
| 11 | # Prefers IPv6 for DNS. Enable this on ISPs that don't have issues with IPv6 | ||
| 12 | dnsPreferIPv6: false | ||
| 13 | # The hostname that autorun opens. | ||
| 14 | # - Use "auto" to let the server decide | ||
| 15 | # - Use options like 'localhost', 'st.example.com' | ||
| 16 | autorunHostname: "auto" | ||
| 7 | # Server port | 17 | # Server port |
| 8 | port: 8000 | 18 | port: 8000 |
| 19 | # Overrides the port for autorun in browser. | ||
| 20 | # - Use -1 to use the server port. | ||
| 21 | # - Specify a port to override the default. | ||
| 22 | autorunPortOverride: -1 | ||
| 9 | # -- SECURITY CONFIGURATION -- | 23 | # -- SECURITY CONFIGURATION -- |
| 10 | # Toggle whitelist mode | 24 | # Toggle whitelist mode |
| 11 | whitelistMode: true | 25 | whitelistMode: true |
| @@ -13,6 +27,7 @@ whitelistMode: true | |||
| 13 | enableForwardedWhitelist: true | 27 | enableForwardedWhitelist: true |
| 14 | # Whitelist of allowed IP addresses | 28 | # Whitelist of allowed IP addresses |
| 15 | whitelist: | 29 | whitelist: |
| 30 | - ::1 | ||
| 16 | - 127.0.0.1 | 31 | - 127.0.0.1 |
| 17 | # Toggle basic authentication for endpoints | 32 | # Toggle basic authentication for endpoints |
| 18 | basicAuthMode: false | 33 | basicAuthMode: false |
| @@ -26,6 +41,11 @@ enableCorsProxy: false | |||
| 26 | enableUserAccounts: false | 41 | enableUserAccounts: false |
| 27 | # Enable discreet login mode: hides user list on the login screen | 42 | # Enable discreet login mode: hides user list on the login screen |
| 28 | enableDiscreetLogin: false | 43 | enableDiscreetLogin: false |
| 44 | # User session timeout *in seconds* (defaults to 24 hours). | ||
| 45 | ## Set to a positive number to expire session after a certain time of inactivity | ||
| 46 | ## Set to 0 to expire session when the browser is closed | ||
| 47 | ## Set to a negative number to disable session expiration | ||
| 48 | sessionTimeout: 86400 | ||
| 29 | # Used to sign session cookies. Will be auto-generated if not set | 49 | # Used to sign session cookies. Will be auto-generated if not set |
| 30 | cookieSecret: '' | 50 | cookieSecret: '' |
| 31 | # Disable CSRF protection - NOT RECOMMENDED | 51 | # Disable CSRF protection - NOT RECOMMENDED |
| @@ -35,6 +55,9 @@ securityOverride: false | |||
| 35 | # -- ADVANCED CONFIGURATION -- | 55 | # -- ADVANCED CONFIGURATION -- |
| 36 | # Open the browser automatically | 56 | # Open the browser automatically |
| 37 | autorun: true | 57 | autorun: true |
| 58 | # Avoids using 'localhost' for autorun in auto mode. | ||
| 59 | # use if you don't have 'localhost' in your hosts file | ||
| 60 | avoidLocalhost: false | ||
| 38 | # Disable thumbnail generation | 61 | # Disable thumbnail generation |
| 39 | disableThumbnails: false | 62 | disableThumbnails: false |
| 40 | # Thumbnail quality (0-100) | 63 | # Thumbnail quality (0-100) |
| @@ -93,10 +116,26 @@ openai: | |||
| 93 | deepl: | 116 | deepl: |
| 94 | # Available options: default, more, less, prefer_more, prefer_less | 117 | # Available options: default, more, less, prefer_more, prefer_less |
| 95 | formality: default | 118 | formality: default |
| 119 | # -- MISTRAL API CONFIGURATION -- | ||
| 120 | mistral: | ||
| 121 | # Enables prefilling of the reply with the last assistant message in the prompt | ||
| 122 | # CAUTION: The prefix is echoed into the completion. You may want to use regex to trim it out. | ||
| 123 | enablePrefix: false | ||
| 124 | # -- OLLAMA API CONFIGURATION -- | ||
| 125 | ollama: | ||
| 126 | # Controls how long the model will stay loaded into memory following the request | ||
| 127 | # * -1: Keep the model loaded indefinitely | ||
| 128 | # * 0: Unload the model immediately after the request | ||
| 129 | # * 5m: Keep the model loaded for 5 minutes after the request. Accepts duration strings (e.g. 5h30m40s) | ||
| 130 | keepAlive: -1 | ||
| 131 | # -- ANTHROPIC CLAUDE API CONFIGURATION -- | ||
| 132 | claude: | ||
| 133 | # Enables caching of the system prompt (if supported). | ||
| 134 | # https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching | ||
| 135 | # -- IMPORTANT! -- | ||
| 136 | # Use only when the prompt before the chat history is static and doesn't change between requests | ||
| 137 | # (e.g {{random}} macro or lorebooks not as in-chat injections). | ||
| 138 | # Otherwise, you'll just waste money on cache misses. | ||
| 139 | enableSystemPromptCache: false | ||
| 96 | # -- SERVER PLUGIN CONFIGURATION -- | 140 | # -- SERVER PLUGIN CONFIGURATION -- |
| 97 | enableServerPlugins: false | 141 | enableServerPlugins: false |
| 98 | # User session timeout *in seconds* (defaults to 24 hours). | ||
| 99 | ## Set to a positive number to expire session after a certain time of inactivity | ||
| 100 | ## Set to 0 to expire session when the browser is closed | ||
| 101 | ## Set to a negative number to disable session expiration | ||
| 102 | sessionTimeout: 86400 | ||
| @@ -1,7 +1,7 @@ | |||
| 1 | { | 1 | { |
| 2 | "chat_completion_source": "openai", | 2 | "chat_completion_source": "openai", |
| 3 | "openai_model": "gpt-3.5-turbo", | 3 | "openai_model": "gpt-4-turbo", |
| 4 | "claude_model": "claude-instant-v1", | 4 | "claude_model": "claude-3-5-sonnet-20240620", |
| 5 | "windowai_model": "", | 5 | "windowai_model": "", |
| 6 | "openrouter_model": "OR_Website", | 6 | "openrouter_model": "OR_Website", |
| 7 | "openrouter_use_fallback": false, | 7 | "openrouter_use_fallback": false, |
| @@ -9,7 +9,7 @@ | |||
| 9 | "openrouter_group_models": false, | 9 | "openrouter_group_models": false, |
| 10 | "openrouter_sort_models": "alphabetically", | 10 | "openrouter_sort_models": "alphabetically", |
| 11 | "ai21_model": "j2-ultra", | 11 | "ai21_model": "j2-ultra", |
| 12 | "mistralai_model": "mistral-medium-latest", | 12 | "mistralai_model": "mistral-large-latest", |
| 13 | "custom_model": "", | 13 | "custom_model": "", |
| 14 | "custom_url": "", | 14 | "custom_url": "", |
| 15 | "custom_include_body": "", | 15 | "custom_include_body": "", |
| @@ -22,7 +22,7 @@ | |||
| 22 | "count_penalty": 0, | 22 | "count_penalty": 0, |
| 23 | "top_p": 1, | 23 | "top_p": 1, |
| 24 | "top_k": 0, | 24 | "top_k": 0, |
| 25 | "top_a": 1, | 25 | "top_a": 0, |
| 26 | "min_p": 0, | 26 | "min_p": 0, |
| 27 | "repetition_penalty": 1, | 27 | "repetition_penalty": 1, |
| 28 | "openai_max_context": 4095, | 28 | "openai_max_context": 4095, |
| @@ -610,9 +610,9 @@ | |||
| 610 | } | 610 | } |
| 611 | ] | 611 | ] |
| 612 | }, | 612 | }, |
| 613 | "wi_format": "[Details of the fictional world the RP is set in:\n{0}]\n", | 613 | "wi_format": "{0}", |
| 614 | "openai_model": "gpt-3.5-turbo", | 614 | "openai_model": "gpt-4-turbo", |
| 615 | "claude_model": "claude-instant-v1", | 615 | "claude_model": "claude-3-5-sonnet-20240620", |
| 616 | "ai21_model": "j2-ultra", | 616 | "ai21_model": "j2-ultra", |
| 617 | "windowai_model": "", | 617 | "windowai_model": "", |
| 618 | "openrouter_model": "OR_Website", | 618 | "openrouter_model": "OR_Website", |
| @@ -9,6 +9,11 @@ declare global { | |||
| 9 | }; | 9 | }; |
| 10 | } | 10 | } |
| 11 | } | 11 | } |
| 12 | |||
| 13 | /** | ||
| 14 | * The root directory for user data. | ||
| 15 | */ | ||
| 16 | var DATA_ROOT: string; | ||
| 12 | } | 17 | } |
| 13 | 18 | ||
| 14 | declare module 'express-session' { | 19 | declare module 'express-session' { |
| @@ -1,12 +1,12 @@ | |||
| 1 | { | 1 | { |
| 2 | "name": "sillytavern", | 2 | "name": "sillytavern", |
| 3 | "version": "1.12.3", | 3 | "version": "1.12.4", |
| 4 | "lockfileVersion": 3, | 4 | "lockfileVersion": 3, |
| 5 | "requires": true, | 5 | "requires": true, |
| 6 | "packages": { | 6 | "packages": { |
| 7 | "": { | 7 | "": { |
| 8 | "name": "sillytavern", | 8 | "name": "sillytavern", |
| 9 | "version": "1.12.3", | 9 | "version": "1.12.4", |
| 10 | "hasInstallScript": true, | 10 | "hasInstallScript": true, |
| 11 | "license": "AGPL-3.0", | 11 | "license": "AGPL-3.0", |
| 12 | "dependencies": { | 12 | "dependencies": { |
| @@ -42,7 +42,7 @@ | |||
| 42 | "rate-limiter-flexible": "^5.0.0", | 42 | "rate-limiter-flexible": "^5.0.0", |
| 43 | "response-time": "^2.3.2", | 43 | "response-time": "^2.3.2", |
| 44 | "sanitize-filename": "^1.6.3", | 44 | "sanitize-filename": "^1.6.3", |
| 45 | "sillytavern-transformers": "^2.14.6", | 45 | "sillytavern-transformers": "2.14.6", |
| 46 | "simple-git": "^3.19.1", | 46 | "simple-git": "^3.19.1", |
| 47 | "tiktoken": "^1.0.15", | 47 | "tiktoken": "^1.0.15", |
| 48 | "vectra": "^0.2.2", | 48 | "vectra": "^0.2.2", |
| @@ -58,7 +58,7 @@ | |||
| 58 | }, | 58 | }, |
| 59 | "devDependencies": { | 59 | "devDependencies": { |
| 60 | "@types/jquery": "^3.5.29", | 60 | "@types/jquery": "^3.5.29", |
| 61 | "eslint": "^8.55.0", | 61 | "eslint": "^8.57.0", |
| 62 | "jquery": "^3.6.4" | 62 | "jquery": "^3.6.4" |
| 63 | }, | 63 | }, |
| 64 | "engines": { | 64 | "engines": { |
| @@ -166,9 +166,9 @@ | |||
| 166 | "license": "MIT" | 166 | "license": "MIT" |
| 167 | }, | 167 | }, |
| 168 | "node_modules/@eslint/js": { | 168 | "node_modules/@eslint/js": { |
| 169 | "version": "8.55.0", | 169 | "version": "8.57.0", |
| 170 | "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.55.0.tgz", | 170 | "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", |
| 171 | "integrity": "sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA==", | 171 | "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", |
| 172 | "dev": true, | 172 | "dev": true, |
| 173 | "license": "MIT", | 173 | "license": "MIT", |
| 174 | "engines": { | 174 | "engines": { |
| @@ -185,14 +185,15 @@ | |||
| 185 | } | 185 | } |
| 186 | }, | 186 | }, |
| 187 | "node_modules/@humanwhocodes/config-array": { | 187 | "node_modules/@humanwhocodes/config-array": { |
| 188 | "version": "0.11.13", | 188 | "version": "0.11.14", |
| 189 | "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", | 189 | "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", |
| 190 | "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", | 190 | "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", |
| 191 | "deprecated": "Use @eslint/config-array instead", | ||
| 191 | "dev": true, | 192 | "dev": true, |
| 192 | "license": "Apache-2.0", | 193 | "license": "Apache-2.0", |
| 193 | "dependencies": { | 194 | "dependencies": { |
| 194 | "@humanwhocodes/object-schema": "^2.0.1", | 195 | "@humanwhocodes/object-schema": "^2.0.2", |
| 195 | "debug": "^4.1.1", | 196 | "debug": "^4.3.1", |
| 196 | "minimatch": "^3.0.5" | 197 | "minimatch": "^3.0.5" |
| 197 | }, | 198 | }, |
| 198 | "engines": { | 199 | "engines": { |
| @@ -200,9 +201,9 @@ | |||
| 200 | } | 201 | } |
| 201 | }, | 202 | }, |
| 202 | "node_modules/@humanwhocodes/config-array/node_modules/debug": { | 203 | "node_modules/@humanwhocodes/config-array/node_modules/debug": { |
| 203 | "version": "4.3.4", | 204 | "version": "4.3.6", |
| 204 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", | 205 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", |
| 205 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", | 206 | "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", |
| 206 | "dev": true, | 207 | "dev": true, |
| 207 | "license": "MIT", | 208 | "license": "MIT", |
| 208 | "dependencies": { | 209 | "dependencies": { |
| @@ -239,9 +240,10 @@ | |||
| 239 | } | 240 | } |
| 240 | }, | 241 | }, |
| 241 | "node_modules/@humanwhocodes/object-schema": { | 242 | "node_modules/@humanwhocodes/object-schema": { |
| 242 | "version": "2.0.1", | 243 | "version": "2.0.3", |
| 243 | "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", | 244 | "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", |
| 244 | "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", | 245 | "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", |
| 246 | "deprecated": "Use @eslint/object-schema instead", | ||
| 245 | "dev": true, | 247 | "dev": true, |
| 246 | "license": "BSD-3-Clause" | 248 | "license": "BSD-3-Clause" |
| 247 | }, | 249 | }, |
| @@ -1391,12 +1393,11 @@ | |||
| 1391 | "license": "MIT" | 1393 | "license": "MIT" |
| 1392 | }, | 1394 | }, |
| 1393 | "node_modules/axios": { | 1395 | "node_modules/axios": { |
| 1394 | "version": "1.6.1", | 1396 | "version": "1.7.4", |
| 1395 | "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.1.tgz", | 1397 | "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz", |
| 1396 | "integrity": "sha512-vfBmhDpKafglh0EldBEbVuoe7DyAavGSLWhuSm5ZSEKQnHhBf0xAAwybbNH1IkrJNGnS/VG4I5yxig1pCEXE4g==", | 1398 | "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==", |
| 1397 | "license": "MIT", | ||
| 1398 | "dependencies": { | 1399 | "dependencies": { |
| 1399 | "follow-redirects": "^1.15.0", | 1400 | "follow-redirects": "^1.15.6", |
| 1400 | "form-data": "^4.0.0", | 1401 | "form-data": "^4.0.0", |
| 1401 | "proxy-from-env": "^1.1.0" | 1402 | "proxy-from-env": "^1.1.0" |
| 1402 | } | 1403 | } |
| @@ -2456,17 +2457,17 @@ | |||
| 2456 | } | 2457 | } |
| 2457 | }, | 2458 | }, |
| 2458 | "node_modules/eslint": { | 2459 | "node_modules/eslint": { |
| 2459 | "version": "8.55.0", | 2460 | "version": "8.57.0", |
| 2460 | "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.55.0.tgz", | 2461 | "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", |
| 2461 | "integrity": "sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA==", | 2462 | "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", |
| 2462 | "dev": true, | 2463 | "dev": true, |
| 2463 | "license": "MIT", | 2464 | "license": "MIT", |
| 2464 | "dependencies": { | 2465 | "dependencies": { |
| 2465 | "@eslint-community/eslint-utils": "^4.2.0", | 2466 | "@eslint-community/eslint-utils": "^4.2.0", |
| 2466 | "@eslint-community/regexpp": "^4.6.1", | 2467 | "@eslint-community/regexpp": "^4.6.1", |
| 2467 | "@eslint/eslintrc": "^2.1.4", | 2468 | "@eslint/eslintrc": "^2.1.4", |
| 2468 | "@eslint/js": "8.55.0", | 2469 | "@eslint/js": "8.57.0", |
| 2469 | "@humanwhocodes/config-array": "^0.11.13", | 2470 | "@humanwhocodes/config-array": "^0.11.14", |
| 2470 | "@humanwhocodes/module-importer": "^1.0.1", | 2471 | "@humanwhocodes/module-importer": "^1.0.1", |
| 2471 | "@nodelib/fs.walk": "^1.2.8", | 2472 | "@nodelib/fs.walk": "^1.2.8", |
| 2472 | "@ungap/structured-clone": "^1.2.0", | 2473 | "@ungap/structured-clone": "^1.2.0", |
| @@ -32,7 +32,7 @@ | |||
| 32 | "rate-limiter-flexible": "^5.0.0", | 32 | "rate-limiter-flexible": "^5.0.0", |
| 33 | "response-time": "^2.3.2", | 33 | "response-time": "^2.3.2", |
| 34 | "sanitize-filename": "^1.6.3", | 34 | "sanitize-filename": "^1.6.3", |
| 35 | "sillytavern-transformers": "^2.14.6", | 35 | "sillytavern-transformers": "2.14.6", |
| 36 | "simple-git": "^3.19.1", | 36 | "simple-git": "^3.19.1", |
| 37 | "tiktoken": "^1.0.15", | 37 | "tiktoken": "^1.0.15", |
| 38 | "vectra": "^0.2.2", | 38 | "vectra": "^0.2.2", |
| @@ -70,7 +70,7 @@ | |||
| 70 | "type": "git", | 70 | "type": "git", |
| 71 | "url": "https://github.com/SillyTavern/SillyTavern.git" | 71 | "url": "https://github.com/SillyTavern/SillyTavern.git" |
| 72 | }, | 72 | }, |
| 73 | "version": "1.12.3", | 73 | "version": "1.12.4", |
| 74 | "scripts": { | 74 | "scripts": { |
| 75 | "start": "node server.js", | 75 | "start": "node server.js", |
| 76 | "start:no-csrf": "node server.js --disableCsrf", | 76 | "start:no-csrf": "node server.js --disableCsrf", |
| @@ -90,7 +90,7 @@ | |||
| 90 | "main": "server.js", | 90 | "main": "server.js", |
| 91 | "devDependencies": { | 91 | "devDependencies": { |
| 92 | "@types/jquery": "^3.5.29", | 92 | "@types/jquery": "^3.5.29", |
| 93 | "eslint": "^8.55.0", | 93 | "eslint": "^8.57.0", |
| 94 | "jquery": "^3.6.4" | 94 | "jquery": "^3.6.4" |
| 95 | } | 95 | } |
| 96 | } | 96 | } |
| @@ -89,7 +89,7 @@ | |||
| 89 | position: absolute; | 89 | position: absolute; |
| 90 | width: 100%; | 90 | width: 100%; |
| 91 | height: 100vh; | 91 | height: 100vh; |
| 92 | height: 100svh; | 92 | height: 100dvh; |
| 93 | z-index: 9998; | 93 | z-index: 9998; |
| 94 | top: 0; | 94 | top: 0; |
| 95 | } | 95 | } |
| @@ -99,6 +99,6 @@ | |||
| 99 | } | 99 | } |
| 100 | 100 | ||
| 101 | #bulk_tag_shadow_popup #bulk_tag_popup #dialogue_popup_controls .menu_button { | 101 | #bulk_tag_shadow_popup #bulk_tag_popup #dialogue_popup_controls .menu_button { |
| 102 | width: 100px; | 102 | width: unset; |
| 103 | padding: 0.25em; | 103 | padding: 0.25em; |
| 104 | } | 104 | } |
| @@ -7,7 +7,8 @@ | |||
| 7 | background-color: green; | 7 | background-color: green; |
| 8 | } | 8 | } |
| 9 | 9 | ||
| 10 | .extensions_block input[type="checkbox"] { | 10 | .extensions_block input[type="checkbox"], |
| 11 | .extensions_block input[type="radio"] { | ||
| 11 | margin-left: 10px; | 12 | margin-left: 10px; |
| 12 | margin-right: 10px; | 13 | margin-right: 10px; |
| 13 | } | 14 | } |
| @@ -7,8 +7,8 @@ | |||
| 7 | z-index: 999999; | 7 | z-index: 999999; |
| 8 | width: 100vw; | 8 | width: 100vw; |
| 9 | height: 100vh; | 9 | height: 100vh; |
| 10 | width: 100svw; | 10 | width: 100dvw; |
| 11 | height: 100svh; | 11 | height: 100dvh; |
| 12 | background-color: var(--SmartThemeBlurTintColor); | 12 | background-color: var(--SmartThemeBlurTintColor); |
| 13 | color: var(--SmartThemeBodyColor); | 13 | color: var(--SmartThemeBodyColor); |
| 14 | /*for some reason the full screen blur does not work on iOS*/ | 14 | /*for some reason the full screen blur does not work on iOS*/ |
| @@ -1,7 +1,7 @@ | |||
| 1 | #logprobsViewer { | 1 | #logprobsViewer { |
| 2 | overflow-y: auto; | 2 | overflow-y: auto; |
| 3 | max-width: 90svw; | 3 | max-width: 90dvw; |
| 4 | max-height: 90svh; | 4 | max-height: 90dvh; |
| 5 | min-width: 100px; | 5 | min-width: 100px; |
| 6 | min-height: 50px; | 6 | min-height: 50px; |
| 7 | border-radius: 10px; | 7 | border-radius: 10px; |
| @@ -16,7 +16,7 @@ | |||
| 16 | top: 0; | 16 | top: 0; |
| 17 | margin: 0; | 17 | margin: 0; |
| 18 | right: unset; | 18 | right: unset; |
| 19 | width: calc(((100svw - var(--sheldWidth)) / 2) - 1px); | 19 | width: calc(((100dvw - var(--sheldWidth)) / 2) - 1px); |
| 20 | } | 20 | } |
| 21 | 21 | ||
| 22 | .logprobs_panel_header { | 22 | .logprobs_panel_header { |
| @@ -1,6 +1,8 @@ | |||
| 1 | /*will apply to anything 1000px or less. this catches ipads, horizontal phones, and vertical phones)*/ | 1 | /*will apply to anything 1000px or less. this catches ipads, horizontal phones, and vertical phones)*/ |
| 2 | @media screen and (max-width: 1000px) { | 2 | @media screen and (max-width: 1000px) { |
| 3 | #send_form.compact #leftSendForm, #send_form.compact #rightSendForm { | 3 | |
| 4 | #send_form.compact #leftSendForm, | ||
| 5 | #send_form.compact #rightSendForm { | ||
| 4 | flex-wrap: nowrap; | 6 | flex-wrap: nowrap; |
| 5 | width: unset; | 7 | width: unset; |
| 6 | } | 8 | } |
| @@ -34,9 +36,9 @@ | |||
| 34 | right: 0; | 36 | right: 0; |
| 35 | width: fit-content; | 37 | width: fit-content; |
| 36 | max-height: calc(60vh - 60px); | 38 | max-height: calc(60vh - 60px); |
| 37 | max-height: calc(60svh - 60px); | 39 | max-height: calc(60dvh - 60px); |
| 38 | max-width: 90vw; | 40 | max-width: 90vw; |
| 39 | max-width: 90svw; | 41 | max-width: 90dvw; |
| 40 | left: 50%; | 42 | left: 50%; |
| 41 | top: 50%; | 43 | top: 50%; |
| 42 | transform: translateX(-50%) translateY(-50%); | 44 | transform: translateX(-50%) translateY(-50%); |
| @@ -102,7 +104,7 @@ | |||
| 102 | min-width: unset; | 104 | min-width: unset; |
| 103 | width: 100%; | 105 | width: 100%; |
| 104 | max-height: calc(100vh - 45px); | 106 | max-height: calc(100vh - 45px); |
| 105 | max-height: calc(100svh - 45px); | 107 | max-height: calc(100dvh - 45px); |
| 106 | position: fixed; | 108 | position: fixed; |
| 107 | left: 0; | 109 | left: 0; |
| 108 | top: 5px; | 110 | top: 5px; |
| @@ -130,15 +132,15 @@ | |||
| 130 | #top-bar { | 132 | #top-bar { |
| 131 | position: fixed; | 133 | position: fixed; |
| 132 | width: 100vw; | 134 | width: 100vw; |
| 133 | width: 100svw; | 135 | width: 100dvw; |
| 134 | } | 136 | } |
| 135 | 137 | ||
| 136 | #bg1, | 138 | #bg1, |
| 137 | #bg_custom { | 139 | #bg_custom { |
| 138 | height: 100vh !important; | 140 | height: 100vh !important; |
| 139 | height: 100svh !important; | 141 | height: 100dvh !important; |
| 140 | width: 100vw !important; | 142 | width: 100vw !important; |
| 141 | width: 100svw !important; | 143 | width: 100dvw !important; |
| 142 | background-position: center; | 144 | background-position: center; |
| 143 | 145 | ||
| 144 | } | 146 | } |
| @@ -146,13 +148,7 @@ | |||
| 146 | 148 | ||
| 147 | #sheld, | 149 | #sheld, |
| 148 | #character_popup, | 150 | #character_popup, |
| 149 | .drawer-content | 151 | .drawer-content { |
| 150 | |||
| 151 | /* , | ||
| 152 | #world_popup */ | ||
| 153 | { | ||
| 154 | /*max-height: calc(100vh - 36px); | ||
| 155 | max-height: calc(100svh - 36px);*/ | ||
| 156 | width: 100% !important; | 152 | width: 100% !important; |
| 157 | margin: 0 auto; | 153 | margin: 0 auto; |
| 158 | max-width: 100%; | 154 | max-width: 100%; |
| @@ -223,10 +219,10 @@ | |||
| 223 | #floatingPrompt, | 219 | #floatingPrompt, |
| 224 | #cfgConfig, | 220 | #cfgConfig, |
| 225 | #logprobsViewer, | 221 | #logprobsViewer, |
| 226 | #movingDivs > div { | 222 | #movingDivs>div { |
| 227 | /* 100vh are fallback units for browsers that don't support svh */ | 223 | /* 100vh are fallback units for browsers that don't support dvh */ |
| 228 | height: calc(100vh - 45px); | 224 | height: calc(100vh - 45px); |
| 229 | height: calc(100svh - 45px); | 225 | height: calc(100dvh - 45px); |
| 230 | min-width: 100% !important; | 226 | min-width: 100% !important; |
| 231 | width: 100% !important; | 227 | width: 100% !important; |
| 232 | max-width: 100% !important; | 228 | max-width: 100% !important; |
| @@ -249,7 +245,7 @@ | |||
| 249 | #floatingPrompt, | 245 | #floatingPrompt, |
| 250 | #cfgConfig, | 246 | #cfgConfig, |
| 251 | #logprobsViewer, | 247 | #logprobsViewer, |
| 252 | #movingDivs > div { | 248 | #movingDivs>div { |
| 253 | height: min-content; | 249 | height: min-content; |
| 254 | } | 250 | } |
| 255 | 251 | ||
| @@ -286,9 +282,9 @@ | |||
| 286 | 282 | ||
| 287 | body.waifuMode #sheld { | 283 | body.waifuMode #sheld { |
| 288 | height: 40vh; | 284 | height: 40vh; |
| 289 | height: 40svh; | 285 | height: 40dvh; |
| 290 | top: 60vh; | 286 | top: 60vh; |
| 291 | top: 60svh; | 287 | top: 60dvh; |
| 292 | bottom: 0 !important; | 288 | bottom: 0 !important; |
| 293 | } | 289 | } |
| 294 | 290 | ||
| @@ -325,16 +321,16 @@ | |||
| 325 | body.waifuMode .zoomed_avatar { | 321 | body.waifuMode .zoomed_avatar { |
| 326 | width: fit-content; | 322 | width: fit-content; |
| 327 | max-height: calc(60vh - 60px); | 323 | max-height: calc(60vh - 60px); |
| 328 | max-height: calc(60svh - 60px); | 324 | max-height: calc(60dvh - 60px); |
| 329 | max-width: 90vw; | 325 | max-width: 90vw; |
| 330 | max-width: 90svw; | 326 | max-width: 90dvw; |
| 331 | } | 327 | } |
| 332 | 328 | ||
| 333 | .scrollableInner { | 329 | .scrollableInner { |
| 334 | overflow-y: auto; | 330 | overflow-y: auto; |
| 335 | overflow-x: hidden; | 331 | overflow-x: hidden; |
| 336 | max-height: calc(100vh - 90px); | 332 | max-height: calc(100vh - 90px); |
| 337 | max-height: calc(100svh - 90px); | 333 | max-height: calc(100dvh - 90px); |
| 338 | } | 334 | } |
| 339 | 335 | ||
| 340 | .horde_multiple_hint { | 336 | .horde_multiple_hint { |
| @@ -370,9 +366,9 @@ | |||
| 370 | 366 | ||
| 371 | body:not(.waifuMode) .zoomed_avatar { | 367 | body:not(.waifuMode) .zoomed_avatar { |
| 372 | max-height: calc(60vh - 60px); | 368 | max-height: calc(60vh - 60px); |
| 373 | max-height: calc(60svh - 60px); | 369 | max-height: calc(60dvh - 60px); |
| 374 | max-width: 90vw; | 370 | max-width: 90vw; |
| 375 | max-width: 90svw; | 371 | max-width: 90dvw; |
| 376 | left: 50%; | 372 | left: 50%; |
| 377 | top: 50%; | 373 | top: 50%; |
| 378 | transform: translateX(-50%) translateY(-50%); | 374 | transform: translateX(-50%) translateY(-50%); |
| @@ -453,9 +449,9 @@ | |||
| 453 | min-height: unset; | 449 | min-height: unset; |
| 454 | max-height: unset; | 450 | max-height: unset; |
| 455 | width: 100vw; | 451 | width: 100vw; |
| 456 | width: 100svw; | 452 | width: 100dvw; |
| 457 | height: calc(100vh - 36px); | 453 | height: calc(100vh - 36px); |
| 458 | height: calc(100svh - 36px); | 454 | height: calc(100dvh - 36px); |
| 459 | padding-right: max(env(safe-area-inset-right), 0px); | 455 | padding-right: max(env(safe-area-inset-right), 0px); |
| 460 | padding-left: max(env(safe-area-inset-left), 0px); | 456 | padding-left: max(env(safe-area-inset-left), 0px); |
| 461 | padding-bottom: 0; | 457 | padding-bottom: 0; |
| @@ -485,10 +481,10 @@ | |||
| 485 | top: 0; | 481 | top: 0; |
| 486 | margin: 0 auto; | 482 | margin: 0 auto; |
| 487 | height: calc(100vh - 70px); | 483 | height: calc(100vh - 70px); |
| 488 | height: calc(100svh - 70px); | 484 | height: calc(100dvh - 70px); |
| 489 | width: calc(100% - 5px); | 485 | width: calc(100% - 5px); |
| 490 | max-height: calc(100vh - 70px); | 486 | max-height: calc(100vh - 70px); |
| 491 | max-height: calc(100svh - 70px); | 487 | max-height: calc(100dvh - 70px); |
| 492 | max-width: calc(100% - 5px); | 488 | max-width: calc(100% - 5px); |
| 493 | 489 | ||
| 494 | } | 490 | } |
| @@ -7,5 +7,5 @@ body.safari .popup.large_dialogue_popup .popup-body { | |||
| 7 | body.safari .popup .popup-body { | 7 | body.safari .popup .popup-body { |
| 8 | height: fit-content; | 8 | height: fit-content; |
| 9 | max-height: 90vh; | 9 | max-height: 90vh; |
| 10 | max-height: 90svh; | 10 | max-height: 90dvh; |
| 11 | } | 11 | } |
| @@ -16,8 +16,8 @@ dialog { | |||
| 16 | display: flex; | 16 | display: flex; |
| 17 | flex-direction: column; | 17 | flex-direction: column; |
| 18 | 18 | ||
| 19 | max-height: calc(100svh - 2em); | 19 | max-height: calc(100dvh - 2em); |
| 20 | max-width: calc(100svw - 2em); | 20 | max-width: calc(100dvw - 2em); |
| 21 | min-height: fit-content; | 21 | min-height: fit-content; |
| 22 | 22 | ||
| 23 | /* Overflow visible so elements (like toasts) can appear outside of the dialog. '.popup-body' is hiding overflow for the real content. */ | 23 | /* Overflow visible so elements (like toasts) can appear outside of the dialog. '.popup-body' is hiding overflow for the real content. */ |
| @@ -103,7 +103,7 @@ body.no-blur .popup[open]::backdrop { | |||
| 103 | 103 | ||
| 104 | .popup #toast-container { | 104 | .popup #toast-container { |
| 105 | /* Fix toastr in dialogs by actually placing it at the top of the screen via transform */ | 105 | /* Fix toastr in dialogs by actually placing it at the top of the screen via transform */ |
| 106 | height: 100svh; | 106 | height: 100dvh; |
| 107 | top: calc(50% + var(--topBarBlockSize)); | 107 | top: calc(50% + var(--topBarBlockSize)); |
| 108 | left: 50%; | 108 | left: 50%; |
| 109 | transform: translate(-50%, -50%); | 109 | transform: translate(-50%, -50%); |
| @@ -115,7 +115,7 @@ body.no-blur .popup[open]::backdrop { | |||
| 115 | .popup-crop-wrap { | 115 | .popup-crop-wrap { |
| 116 | margin: 10px auto; | 116 | margin: 10px auto; |
| 117 | max-height: 75vh; | 117 | max-height: 75vh; |
| 118 | max-height: 75svh; | 118 | max-height: 75dvh; |
| 119 | max-width: 100%; | 119 | max-width: 100%; |
| 120 | } | 120 | } |
| 121 | 121 | ||
| @@ -209,20 +209,20 @@ | |||
| 209 | opacity: 1; | 209 | opacity: 1; |
| 210 | } | 210 | } |
| 211 | 211 | ||
| 212 | .tag_as_folder { | 212 | .tag_as_folder.right_menu_button { |
| 213 | filter: brightness(75%) saturate(0.6); | 213 | filter: brightness(75%) saturate(0.6); |
| 214 | } | 214 | } |
| 215 | 215 | ||
| 216 | .tag_as_folder:hover, | 216 | .tag_as_folder.right_menu_button:hover, |
| 217 | .tag_as_folder.flash { | 217 | .tag_as_folder.right_menu_button.flash { |
| 218 | filter: brightness(150%) saturate(0.6) !important; | 218 | filter: brightness(150%) saturate(0.6); |
| 219 | } | 219 | } |
| 220 | 220 | ||
| 221 | .tag_as_folder.no_folder { | 221 | .tag_as_folder.right_menu_button.no_folder { |
| 222 | filter: brightness(25%) saturate(0.25); | 222 | filter: brightness(25%) saturate(0.25); |
| 223 | } | 223 | } |
| 224 | 224 | ||
| 225 | .tag_as_folder .tag_folder_indicator { | 225 | .tag_as_folder.right_menu_button .tag_folder_indicator { |
| 226 | position: absolute; | 226 | position: absolute; |
| 227 | top: calc(var(--mainFontSize) * -0.5); | 227 | top: calc(var(--mainFontSize) * -0.5); |
| 228 | right: calc(var(--mainFontSize) * -0.5); | 228 | right: calc(var(--mainFontSize) * -0.5); |
| @@ -28,6 +28,10 @@ body.hideChatAvatars .mesAvatarWrapper .avatar { | |||
| 28 | display: none !important; | 28 | display: none !important; |
| 29 | } | 29 | } |
| 30 | 30 | ||
| 31 | body.hideChatAvatars .last_mes { | ||
| 32 | padding-bottom: 40px !important; | ||
| 33 | } | ||
| 34 | |||
| 31 | body.square-avatars .avatar, | 35 | body.square-avatars .avatar, |
| 32 | body.square-avatars .avatar img { | 36 | body.square-avatars .avatar img { |
| 33 | border-radius: var(--avatar-base-border-radius) !important; | 37 | border-radius: var(--avatar-base-border-radius) !important; |
| @@ -360,7 +364,7 @@ body.waifuMode #top-bar { | |||
| 360 | 364 | ||
| 361 | body.waifuMode #sheld { | 365 | body.waifuMode #sheld { |
| 362 | height: 40vh; | 366 | height: 40vh; |
| 363 | height: 40svh; | 367 | height: 40dvh; |
| 364 | top: calc(100% - 40vh); | 368 | top: calc(100% - 40vh); |
| 365 | bottom: 0; | 369 | bottom: 0; |
| 366 | } | 370 | } |
| @@ -450,4 +454,4 @@ body.expandMessageActions .mes .mes_buttons .extraMesButtonsHint { | |||
| 450 | 454 | ||
| 451 | #smooth_streaming:checked~#smooth_streaming_speed_control { | 455 | #smooth_streaming:checked~#smooth_streaming_speed_control { |
| 452 | display: block; | 456 | display: block; |
| 453 | } | 457 | } |
| \ No newline at end of file | 457 | \ No newline at end of file | |
| @@ -120,6 +120,14 @@ | |||
| 120 | flex-wrap: wrap; | 120 | flex-wrap: wrap; |
| 121 | } | 121 | } |
| 122 | 122 | ||
| 123 | .world_entry .inline-drawer-header { | ||
| 124 | cursor: initial; | ||
| 125 | } | ||
| 126 | |||
| 127 | .world_entry .killSwitch { | ||
| 128 | cursor: pointer; | ||
| 129 | } | ||
| 130 | |||
| 123 | .world_entry_form_control input[type=button] { | 131 | .world_entry_form_control input[type=button] { |
| 124 | cursor: pointer; | 132 | cursor: pointer; |
| 125 | } | 133 | } |
| @@ -173,6 +181,10 @@ | |||
| 173 | width: 7em; | 181 | width: 7em; |
| 174 | } | 182 | } |
| 175 | 183 | ||
| 184 | .world_entry .killSwitch.fa-toggle-on { | ||
| 185 | color: var(--SmartThemeQuoteColor); | ||
| 186 | } | ||
| 187 | |||
| 176 | .wi-card-entry { | 188 | .wi-card-entry { |
| 177 | border: 1px solid; | 189 | border: 1px solid; |
| 178 | border-color: var(--SmartThemeBorderColor); | 190 | border-color: var(--SmartThemeBorderColor); |
| @@ -14,8 +14,15 @@ declare var isProbablyReaderable; | |||
| 14 | declare var ePub; | 14 | declare var ePub; |
| 15 | declare var ai; | 15 | declare var ai; |
| 16 | 16 | ||
| 17 | declare var SillyTavern: { | ||
| 18 | getContext(): any; | ||
| 19 | llm: any; | ||
| 20 | }; | ||
| 21 | |||
| 17 | // Jquery plugins | 22 | // Jquery plugins |
| 18 | interface JQuery { | 23 | interface JQuery { |
| 24 | nanogallery2(options?: any): JQuery; | ||
| 25 | nanogallery2(method: string, options?: any): JQuery; | ||
| 19 | pagination(method: 'getCurrentPageNum'): number; | 26 | pagination(method: 'getCurrentPageNum'): number; |
| 20 | pagination(method: string, options?: any): JQuery; | 27 | pagination(method: string, options?: any): JQuery; |
| 21 | pagination(options?: any): JQuery; | 28 | pagination(options?: any): JQuery; |
| @@ -0,0 +1,3 @@ | |||
| 1 | <svg id="Layer_2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 236.38 282.41"> | ||
| 2 | <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" /> | ||
| 3 | </svg> | ||
| @@ -0,0 +1,149 @@ | |||
| 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||
| 2 | <!-- Created with Inkscape (http://www.inkscape.org/) --> | ||
| 3 | |||
| 4 | <svg | ||
| 5 | width="48" | ||
| 6 | height="48" | ||
| 7 | viewBox="0 0 48 48" | ||
| 8 | version="1.1" | ||
| 9 | id="svg2120" | ||
| 10 | xml:space="preserve" | ||
| 11 | inkscape:version="1.2.2 (732a01da63, 2022-12-09)" | ||
| 12 | sodipodi:docname="step-into.svg" | ||
| 13 | xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | ||
| 14 | xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | ||
| 15 | xmlns:xlink="http://www.w3.org/1999/xlink" | ||
| 16 | xmlns="http://www.w3.org/2000/svg" | ||
| 17 | xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview | ||
| 18 | id="namedview2122" | ||
| 19 | pagecolor="#ffffff" | ||
| 20 | bordercolor="#000000" | ||
| 21 | borderopacity="0.25" | ||
| 22 | inkscape:showpageshadow="2" | ||
| 23 | inkscape:pageopacity="0.0" | ||
| 24 | inkscape:pagecheckerboard="0" | ||
| 25 | inkscape:deskcolor="#d1d1d1" | ||
| 26 | inkscape:document-units="px" | ||
| 27 | showgrid="false" | ||
| 28 | inkscape:zoom="11.313708" | ||
| 29 | inkscape:cx="58.910834" | ||
| 30 | inkscape:cy="25.323262" | ||
| 31 | inkscape:window-width="1920" | ||
| 32 | inkscape:window-height="992" | ||
| 33 | inkscape:window-x="-8" | ||
| 34 | inkscape:window-y="-8" | ||
| 35 | inkscape:window-maximized="1" | ||
| 36 | inkscape:current-layer="g2714" /><defs | ||
| 37 | id="defs2117"><inkscape:path-effect | ||
| 38 | effect="spiro" | ||
| 39 | id="path-effect2144" | ||
| 40 | is_visible="true" | ||
| 41 | lpeversion="1" /><inkscape:path-effect | ||
| 42 | effect="bspline" | ||
| 43 | id="path-effect2138" | ||
| 44 | is_visible="true" | ||
| 45 | lpeversion="1" | ||
| 46 | weight="33.333333" | ||
| 47 | steps="2" | ||
| 48 | helper_size="0" | ||
| 49 | apply_no_weight="true" | ||
| 50 | apply_with_weight="true" | ||
| 51 | only_selected="false" /></defs><g | ||
| 52 | inkscape:groupmode="layer" | ||
| 53 | id="layer4" | ||
| 54 | inkscape:label="img" | ||
| 55 | style="display:none"><image | ||
| 56 | width="305.68866" | ||
| 57 | height="70.374367" | ||
| 58 | preserveAspectRatio="none" | ||
| 59 | xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARYAAABACAYAAADf7VgRAAAABHNCSVQICAgIfAhkiAAABdxJREFU | ||
| 60 | eJzt3T9vo0gYBvB3by+SsZRiKCI5SCkyJS6dzny3+26UpoSSFJFMOaTyUETaK3zDAQYbzGBgeH7S | ||
| 61 | SutN1kKj8cM7fxj/2u/3fwgAQKO/xr4AADAPggUAtEOwAIB2CBYA0A7BAgDaIVgAQDsECwBoh2AB | ||
| 62 | AO0QLACgHYIFALRDsACAdn+PfQFw5nkeWZZFRERSSvJ9f+Qrmg7XdSnLMorjeOxLgZYQLDBpruuS | ||
| 63 | 4zj5a4TLPGAoBJNVDRXOOXHOR7wiaAvBApNUDRUF4TIPCBaYnKZQURAu04dggUm5FSoKwmXaECww | ||
| 64 | GW1DRUG4TNfDV4VUx1mtViSEICKiLMtIStn4f4qdJ8syOh6Pw14kPFxdqAghyLKs0jI8EeWvif7v | ||
| 65 | G1gtmpbBg8V1XbJtm4jKHYKoHBhCCIrjuDZkqr+HYDFLU6gEQUCe55X+3ff90p4fIoTLFA0WLI7j | ||
| 66 | kOu6rX/ftm2ybZuEEBRF0dUKBsxxLVSaIFymT/scC2OMdrtdp1Apsm2bPM8jzvlFhQPmybKs9PpW | ||
| 67 | qCi+71/cfKrvZQrXdYkxNvZldKK1YmGM0cfHR+3PhBAkpaTv728iIjqdTmTbNq1WK7Jtu3aYxBij | ||
| 68 | KIp0XuLoXNelz8/PzhWZZVnkuq5x1ZyqMDjnrUNFKVYuURQZOUTe7XZ5NR+GIaVpOvYltaItWCzL | ||
| 69 | qg0VNXdS1yDFf2OM0Xa7LQWMbdt3Vz5TVOwkQRC0DgjLsvK5BlPD5d5Jed/3yXEcI0OlOj+53W5n | ||
| 70 | Ey5ahkLFjq9IKelwOFAQBK0aIk1T8n3/ooOohp27aifZ7XathnrVtjUtbJU+wWBiqBDRRWWrwqXv | ||
| 71 | sKi40jYULRVLtaP3eTo3iiLKssy4/QlJkpSGfCpcrlUudYFNhAnKpZBSUhAEpZvQvZWLGkoXb9RS | ||
| 72 | SkqSZJD+1Lti4ZxfVBVhGPZ6zziOjfvwpGlKYRhe3IGaKpemUDkcDrMohUEPFS59KhfOOXmed/E5 | ||
| 73 | tSwr/5nuCqZ3xfL6+lp63bXjq0naJVDhUpxLqgsXhAoU9alcGGM3q3/VB3WeAdSrYqmO1aSUnTs+ | ||
| 74 | Yyyf0Kz+MVFT5XILQmXZ7q1cmlZpqyzL6vQ4xS29KpZqtZIkSa+LWYq6yuUahIo56irRLqr95Vrl | ||
| 75 | 0nUksNlstE2EawsWKaVx8yJDahsuCBWzDLEa0xQu6/W68/vo0itYdIzJumyIMs2tcEGoQFt1/We1 | ||
| 76 | WvV+j3vhzNuRNYULQsVMfTc2Nn346/qLEKLTtg2d+4FGOzbhdDrlz3aYtIv0HtVwQaiYq0+V33W1 | ||
| 77 | UJ0U0LYS0fms1cOCpelQHjzNfKbCRf0dzqSUed9Ych+5ZwuClJLCMGy1MqQevdHl136//6Pt3Rqo | ||
| 78 | Z2SuMfUhMoC++u5runXSngognTe0wSuWup25dd7f3/MnoAHgTMdmyTiOKUmSh27pHzRY1Jbhtr/7 | ||
| 79 | +vqKJWuA/+jcga022FWP+RzKpFaFlrK1vw5jjNbrdX4W8K1zgJdiye1S9xR734n9R7XdoMFS3Zl7 | ||
| 80 | i6nb+K+pOxyLcz7IuHdO0C7necfi8GVOq4W/397e/hnqzdfrNb28vLT+fSklfX19DXU5k8M5p+12 | ||
| 81 | W/uzp6enfGl+Lp1JF7TL2c/PD6VpSs/Pz7ML00G/V0h9vUdbSylxidrPPy3t7F+0S5maG5lTqBAN | ||
| 82 | HCzFPQhtLGnitsspcCaeGNcE7WKGwb8Jse3ZrkKI2aVyH13mk+oOGzcV2sUMgweLmmy7Fi5dT2ef | ||
| 83 | O3wY6qFdzPGQ5WZ1ULY6LU4dEHU8HilJkkVVKvda0vxTF2iXaXroPpYlzaFco+ae2t6hl/LhQbuY | ||
| 84 | Y/ChENTrctrekk7mQ7uYAcEykjiOW91xl3YyH9rFDAiWEd1aMRNC9P4qlTlCu8zfQ45NgOscx6HN | ||
| 85 | ZlOa1M6ybPF3ZLTLfCFYAEA7DIUAQDsECwBoh2ABAO0QLACgHYIFALRDsACAdggWANAOwQIA2iFY | ||
| 86 | AEA7BAsAaIdgAQDt/gUoDXNStc/rMQAAAABJRU5ErkJggg== | ||
| 87 | " | ||
| 88 | id="image2132" | ||
| 89 | x="-82" | ||
| 90 | y="-11.9" /></g><g | ||
| 91 | inkscape:groupmode="layer" | ||
| 92 | id="layer5" | ||
| 93 | inkscape:label="dot" /><g | ||
| 94 | inkscape:label="over" | ||
| 95 | inkscape:groupmode="layer" | ||
| 96 | id="layer1" | ||
| 97 | style="display:none"><ellipse | ||
| 98 | style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none" | ||
| 99 | id="path2637" | ||
| 100 | cx="24" | ||
| 101 | cy="33" | ||
| 102 | rx="4.3600898" | ||
| 103 | ry="4.3600893" /><path | ||
| 104 | style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 105 | 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" | ||
| 106 | id="path2142" | ||
| 107 | inkscape:path-effect="#path-effect2144" | ||
| 108 | 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" | ||
| 109 | sodipodi:nodetypes="csc" /><path | ||
| 110 | style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 111 | d="M 26,22 H 39 V 9" | ||
| 112 | id="path2626" | ||
| 113 | sodipodi:nodetypes="ccc" /></g><g | ||
| 114 | inkscape:groupmode="layer" | ||
| 115 | id="layer6" | ||
| 116 | inkscape:label="into" | ||
| 117 | style="display:inline"><ellipse | ||
| 118 | style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none" | ||
| 119 | id="path2637-3" | ||
| 120 | cx="24" | ||
| 121 | cy="38.5" | ||
| 122 | rx="4.3600898" | ||
| 123 | ry="4.3600893" /><path | ||
| 124 | style="fill:#000000;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 125 | d="M 24,2 V 24" | ||
| 126 | id="path2668" | ||
| 127 | sodipodi:nodetypes="cc" /><path | ||
| 128 | style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 129 | d="M 14.807612,16.994936 24,26.187324 33.192388,16.994936" | ||
| 130 | id="path2626-8" | ||
| 131 | sodipodi:nodetypes="ccc" /></g><g | ||
| 132 | inkscape:groupmode="layer" | ||
| 133 | id="g2714" | ||
| 134 | inkscape:label="out" | ||
| 135 | style="display:none"><ellipse | ||
| 136 | style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none" | ||
| 137 | id="ellipse2708" | ||
| 138 | cx="24" | ||
| 139 | cy="38.5" | ||
| 140 | rx="4.3600898" | ||
| 141 | ry="4.3600893" /><path | ||
| 142 | style="fill:#000000;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 143 | d="M 24,29.722858 V 7.7228579" | ||
| 144 | id="path2710" | ||
| 145 | sodipodi:nodetypes="cc" /><path | ||
| 146 | style="display:inline;fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 147 | d="M 33.192388,14.727922 24,5.5355339 14.807612,14.727922" | ||
| 148 | id="path2712" | ||
| 149 | sodipodi:nodetypes="ccc" /></g></svg> | ||
| @@ -0,0 +1,149 @@ | |||
| 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||
| 2 | <!-- Created with Inkscape (http://www.inkscape.org/) --> | ||
| 3 | |||
| 4 | <svg | ||
| 5 | width="48" | ||
| 6 | height="48" | ||
| 7 | viewBox="0 0 48 48" | ||
| 8 | version="1.1" | ||
| 9 | id="svg2120" | ||
| 10 | xml:space="preserve" | ||
| 11 | inkscape:version="1.2.2 (732a01da63, 2022-12-09)" | ||
| 12 | sodipodi:docname="step-out.svg" | ||
| 13 | xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | ||
| 14 | xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | ||
| 15 | xmlns:xlink="http://www.w3.org/1999/xlink" | ||
| 16 | xmlns="http://www.w3.org/2000/svg" | ||
| 17 | xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview | ||
| 18 | id="namedview2122" | ||
| 19 | pagecolor="#ffffff" | ||
| 20 | bordercolor="#000000" | ||
| 21 | borderopacity="0.25" | ||
| 22 | inkscape:showpageshadow="2" | ||
| 23 | inkscape:pageopacity="0.0" | ||
| 24 | inkscape:pagecheckerboard="0" | ||
| 25 | inkscape:deskcolor="#d1d1d1" | ||
| 26 | inkscape:document-units="px" | ||
| 27 | showgrid="false" | ||
| 28 | inkscape:zoom="11.313708" | ||
| 29 | inkscape:cx="58.910834" | ||
| 30 | inkscape:cy="25.323262" | ||
| 31 | inkscape:window-width="1920" | ||
| 32 | inkscape:window-height="992" | ||
| 33 | inkscape:window-x="-8" | ||
| 34 | inkscape:window-y="-8" | ||
| 35 | inkscape:window-maximized="1" | ||
| 36 | inkscape:current-layer="g2714" /><defs | ||
| 37 | id="defs2117"><inkscape:path-effect | ||
| 38 | effect="spiro" | ||
| 39 | id="path-effect2144" | ||
| 40 | is_visible="true" | ||
| 41 | lpeversion="1" /><inkscape:path-effect | ||
| 42 | effect="bspline" | ||
| 43 | id="path-effect2138" | ||
| 44 | is_visible="true" | ||
| 45 | lpeversion="1" | ||
| 46 | weight="33.333333" | ||
| 47 | steps="2" | ||
| 48 | helper_size="0" | ||
| 49 | apply_no_weight="true" | ||
| 50 | apply_with_weight="true" | ||
| 51 | only_selected="false" /></defs><g | ||
| 52 | inkscape:groupmode="layer" | ||
| 53 | id="layer4" | ||
| 54 | inkscape:label="img" | ||
| 55 | style="display:none"><image | ||
| 56 | width="305.68866" | ||
| 57 | height="70.374367" | ||
| 58 | preserveAspectRatio="none" | ||
| 59 | xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARYAAABACAYAAADf7VgRAAAABHNCSVQICAgIfAhkiAAABdxJREFU | ||
| 60 | eJzt3T9vo0gYBvB3by+SsZRiKCI5SCkyJS6dzny3+26UpoSSFJFMOaTyUETaK3zDAQYbzGBgeH7S | ||
| 61 | SutN1kKj8cM7fxj/2u/3fwgAQKO/xr4AADAPggUAtEOwAIB2CBYA0A7BAgDaIVgAQDsECwBoh2AB | ||
| 62 | AO0QLACgHYIFALRDsACAdn+PfQFw5nkeWZZFRERSSvJ9f+Qrmg7XdSnLMorjeOxLgZYQLDBpruuS | ||
| 63 | 4zj5a4TLPGAoBJNVDRXOOXHOR7wiaAvBApNUDRUF4TIPCBaYnKZQURAu04dggUm5FSoKwmXaECww | ||
| 64 | GW1DRUG4TNfDV4VUx1mtViSEICKiLMtIStn4f4qdJ8syOh6Pw14kPFxdqAghyLKs0jI8EeWvif7v | ||
| 65 | G1gtmpbBg8V1XbJtm4jKHYKoHBhCCIrjuDZkqr+HYDFLU6gEQUCe55X+3ff90p4fIoTLFA0WLI7j | ||
| 66 | kOu6rX/ftm2ybZuEEBRF0dUKBsxxLVSaIFymT/scC2OMdrtdp1Apsm2bPM8jzvlFhQPmybKs9PpW | ||
| 67 | qCi+71/cfKrvZQrXdYkxNvZldKK1YmGM0cfHR+3PhBAkpaTv728iIjqdTmTbNq1WK7Jtu3aYxBij | ||
| 68 | KIp0XuLoXNelz8/PzhWZZVnkuq5x1ZyqMDjnrUNFKVYuURQZOUTe7XZ5NR+GIaVpOvYltaItWCzL | ||
| 69 | qg0VNXdS1yDFf2OM0Xa7LQWMbdt3Vz5TVOwkQRC0DgjLsvK5BlPD5d5Jed/3yXEcI0OlOj+53W5n | ||
| 70 | Ey5ahkLFjq9IKelwOFAQBK0aIk1T8n3/ooOohp27aifZ7XathnrVtjUtbJU+wWBiqBDRRWWrwqXv | ||
| 71 | sKi40jYULRVLtaP3eTo3iiLKssy4/QlJkpSGfCpcrlUudYFNhAnKpZBSUhAEpZvQvZWLGkoXb9RS | ||
| 72 | SkqSZJD+1Lti4ZxfVBVhGPZ6zziOjfvwpGlKYRhe3IGaKpemUDkcDrMohUEPFS59KhfOOXmed/E5 | ||
| 73 | tSwr/5nuCqZ3xfL6+lp63bXjq0naJVDhUpxLqgsXhAoU9alcGGM3q3/VB3WeAdSrYqmO1aSUnTs+ | ||
| 74 | Yyyf0Kz+MVFT5XILQmXZ7q1cmlZpqyzL6vQ4xS29KpZqtZIkSa+LWYq6yuUahIo56irRLqr95Vrl | ||
| 75 | 0nUksNlstE2EawsWKaVx8yJDahsuCBWzDLEa0xQu6/W68/vo0itYdIzJumyIMs2tcEGoQFt1/We1 | ||
| 76 | WvV+j3vhzNuRNYULQsVMfTc2Nn346/qLEKLTtg2d+4FGOzbhdDrlz3aYtIv0HtVwQaiYq0+V33W1 | ||
| 77 | UJ0U0LYS0fms1cOCpelQHjzNfKbCRf0dzqSUed9Ych+5ZwuClJLCMGy1MqQevdHl136//6Pt3Rqo | ||
| 78 | Z2SuMfUhMoC++u5runXSngognTe0wSuWup25dd7f3/MnoAHgTMdmyTiOKUmSh27pHzRY1Jbhtr/7 | ||
| 79 | +vqKJWuA/+jcga022FWP+RzKpFaFlrK1vw5jjNbrdX4W8K1zgJdiye1S9xR734n9R7XdoMFS3Zl7 | ||
| 80 | i6nb+K+pOxyLcz7IuHdO0C7necfi8GVOq4W/397e/hnqzdfrNb28vLT+fSklfX19DXU5k8M5p+12 | ||
| 81 | W/uzp6enfGl+Lp1JF7TL2c/PD6VpSs/Pz7ML00G/V0h9vUdbSylxidrPPy3t7F+0S5maG5lTqBAN | ||
| 82 | HCzFPQhtLGnitsspcCaeGNcE7WKGwb8Jse3ZrkKI2aVyH13mk+oOGzcV2sUMgweLmmy7Fi5dT2ef | ||
| 83 | O3wY6qFdzPGQ5WZ1ULY6LU4dEHU8HilJkkVVKvda0vxTF2iXaXroPpYlzaFco+ae2t6hl/LhQbuY | ||
| 84 | Y/ChENTrctrekk7mQ7uYAcEykjiOW91xl3YyH9rFDAiWEd1aMRNC9P4qlTlCu8zfQ45NgOscx6HN | ||
| 85 | ZlOa1M6ybPF3ZLTLfCFYAEA7DIUAQDsECwBoh2ABAO0QLACgHYIFALRDsACAdggWANAOwQIA2iFY | ||
| 86 | AEA7BAsAaIdgAQDt/gUoDXNStc/rMQAAAABJRU5ErkJggg== | ||
| 87 | " | ||
| 88 | id="image2132" | ||
| 89 | x="-82" | ||
| 90 | y="-11.9" /></g><g | ||
| 91 | inkscape:groupmode="layer" | ||
| 92 | id="layer5" | ||
| 93 | inkscape:label="dot" /><g | ||
| 94 | inkscape:label="over" | ||
| 95 | inkscape:groupmode="layer" | ||
| 96 | id="layer1" | ||
| 97 | style="display:none"><ellipse | ||
| 98 | style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none" | ||
| 99 | id="path2637" | ||
| 100 | cx="24" | ||
| 101 | cy="33" | ||
| 102 | rx="4.3600898" | ||
| 103 | ry="4.3600893" /><path | ||
| 104 | style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 105 | 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" | ||
| 106 | id="path2142" | ||
| 107 | inkscape:path-effect="#path-effect2144" | ||
| 108 | 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" | ||
| 109 | sodipodi:nodetypes="csc" /><path | ||
| 110 | style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 111 | d="M 26,22 H 39 V 9" | ||
| 112 | id="path2626" | ||
| 113 | sodipodi:nodetypes="ccc" /></g><g | ||
| 114 | inkscape:groupmode="layer" | ||
| 115 | id="layer6" | ||
| 116 | inkscape:label="into" | ||
| 117 | style="display:none"><ellipse | ||
| 118 | style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none" | ||
| 119 | id="path2637-3" | ||
| 120 | cx="24" | ||
| 121 | cy="38.5" | ||
| 122 | rx="4.3600898" | ||
| 123 | ry="4.3600893" /><path | ||
| 124 | style="fill:#000000;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 125 | d="M 24,2 V 24" | ||
| 126 | id="path2668" | ||
| 127 | sodipodi:nodetypes="cc" /><path | ||
| 128 | style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 129 | d="M 14.807612,16.994936 24,26.187324 33.192388,16.994936" | ||
| 130 | id="path2626-8" | ||
| 131 | sodipodi:nodetypes="ccc" /></g><g | ||
| 132 | inkscape:groupmode="layer" | ||
| 133 | id="g2714" | ||
| 134 | inkscape:label="out" | ||
| 135 | style="display:inline"><ellipse | ||
| 136 | style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none" | ||
| 137 | id="ellipse2708" | ||
| 138 | cx="24" | ||
| 139 | cy="38.5" | ||
| 140 | rx="4.3600898" | ||
| 141 | ry="4.3600893" /><path | ||
| 142 | style="fill:#000000;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 143 | d="M 24,29.722858 V 7.7228579" | ||
| 144 | id="path2710" | ||
| 145 | sodipodi:nodetypes="cc" /><path | ||
| 146 | style="display:inline;fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 147 | d="M 33.192388,14.727922 24,5.5355339 14.807612,14.727922" | ||
| 148 | id="path2712" | ||
| 149 | sodipodi:nodetypes="ccc" /></g></svg> | ||
| @@ -0,0 +1,149 @@ | |||
| 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||
| 2 | <!-- Created with Inkscape (http://www.inkscape.org/) --> | ||
| 3 | |||
| 4 | <svg | ||
| 5 | width="48" | ||
| 6 | height="48" | ||
| 7 | viewBox="0 0 48 48" | ||
| 8 | version="1.1" | ||
| 9 | id="svg2120" | ||
| 10 | xml:space="preserve" | ||
| 11 | inkscape:version="1.2.2 (732a01da63, 2022-12-09)" | ||
| 12 | sodipodi:docname="step-over.svg" | ||
| 13 | xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | ||
| 14 | xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | ||
| 15 | xmlns:xlink="http://www.w3.org/1999/xlink" | ||
| 16 | xmlns="http://www.w3.org/2000/svg" | ||
| 17 | xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview | ||
| 18 | id="namedview2122" | ||
| 19 | pagecolor="#ffffff" | ||
| 20 | bordercolor="#000000" | ||
| 21 | borderopacity="0.25" | ||
| 22 | inkscape:showpageshadow="2" | ||
| 23 | inkscape:pageopacity="0.0" | ||
| 24 | inkscape:pagecheckerboard="0" | ||
| 25 | inkscape:deskcolor="#d1d1d1" | ||
| 26 | inkscape:document-units="px" | ||
| 27 | showgrid="false" | ||
| 28 | inkscape:zoom="11.313708" | ||
| 29 | inkscape:cx="58.910834" | ||
| 30 | inkscape:cy="25.323262" | ||
| 31 | inkscape:window-width="1920" | ||
| 32 | inkscape:window-height="992" | ||
| 33 | inkscape:window-x="-8" | ||
| 34 | inkscape:window-y="-8" | ||
| 35 | inkscape:window-maximized="1" | ||
| 36 | inkscape:current-layer="g2714" /><defs | ||
| 37 | id="defs2117"><inkscape:path-effect | ||
| 38 | effect="spiro" | ||
| 39 | id="path-effect2144" | ||
| 40 | is_visible="true" | ||
| 41 | lpeversion="1" /><inkscape:path-effect | ||
| 42 | effect="bspline" | ||
| 43 | id="path-effect2138" | ||
| 44 | is_visible="true" | ||
| 45 | lpeversion="1" | ||
| 46 | weight="33.333333" | ||
| 47 | steps="2" | ||
| 48 | helper_size="0" | ||
| 49 | apply_no_weight="true" | ||
| 50 | apply_with_weight="true" | ||
| 51 | only_selected="false" /></defs><g | ||
| 52 | inkscape:groupmode="layer" | ||
| 53 | id="layer4" | ||
| 54 | inkscape:label="img" | ||
| 55 | style="display:none"><image | ||
| 56 | width="305.68866" | ||
| 57 | height="70.374367" | ||
| 58 | preserveAspectRatio="none" | ||
| 59 | xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARYAAABACAYAAADf7VgRAAAABHNCSVQICAgIfAhkiAAABdxJREFU | ||
| 60 | eJzt3T9vo0gYBvB3by+SsZRiKCI5SCkyJS6dzny3+26UpoSSFJFMOaTyUETaK3zDAQYbzGBgeH7S | ||
| 61 | SutN1kKj8cM7fxj/2u/3fwgAQKO/xr4AADAPggUAtEOwAIB2CBYA0A7BAgDaIVgAQDsECwBoh2AB | ||
| 62 | AO0QLACgHYIFALRDsACAdn+PfQFw5nkeWZZFRERSSvJ9f+Qrmg7XdSnLMorjeOxLgZYQLDBpruuS | ||
| 63 | 4zj5a4TLPGAoBJNVDRXOOXHOR7wiaAvBApNUDRUF4TIPCBaYnKZQURAu04dggUm5FSoKwmXaECww | ||
| 64 | GW1DRUG4TNfDV4VUx1mtViSEICKiLMtIStn4f4qdJ8syOh6Pw14kPFxdqAghyLKs0jI8EeWvif7v | ||
| 65 | G1gtmpbBg8V1XbJtm4jKHYKoHBhCCIrjuDZkqr+HYDFLU6gEQUCe55X+3ff90p4fIoTLFA0WLI7j | ||
| 66 | kOu6rX/ftm2ybZuEEBRF0dUKBsxxLVSaIFymT/scC2OMdrtdp1Apsm2bPM8jzvlFhQPmybKs9PpW | ||
| 67 | qCi+71/cfKrvZQrXdYkxNvZldKK1YmGM0cfHR+3PhBAkpaTv728iIjqdTmTbNq1WK7Jtu3aYxBij | ||
| 68 | KIp0XuLoXNelz8/PzhWZZVnkuq5x1ZyqMDjnrUNFKVYuURQZOUTe7XZ5NR+GIaVpOvYltaItWCzL | ||
| 69 | qg0VNXdS1yDFf2OM0Xa7LQWMbdt3Vz5TVOwkQRC0DgjLsvK5BlPD5d5Jed/3yXEcI0OlOj+53W5n | ||
| 70 | Ey5ahkLFjq9IKelwOFAQBK0aIk1T8n3/ooOohp27aifZ7XathnrVtjUtbJU+wWBiqBDRRWWrwqXv | ||
| 71 | sKi40jYULRVLtaP3eTo3iiLKssy4/QlJkpSGfCpcrlUudYFNhAnKpZBSUhAEpZvQvZWLGkoXb9RS | ||
| 72 | SkqSZJD+1Lti4ZxfVBVhGPZ6zziOjfvwpGlKYRhe3IGaKpemUDkcDrMohUEPFS59KhfOOXmed/E5 | ||
| 73 | tSwr/5nuCqZ3xfL6+lp63bXjq0naJVDhUpxLqgsXhAoU9alcGGM3q3/VB3WeAdSrYqmO1aSUnTs+ | ||
| 74 | Yyyf0Kz+MVFT5XILQmXZ7q1cmlZpqyzL6vQ4xS29KpZqtZIkSa+LWYq6yuUahIo56irRLqr95Vrl | ||
| 75 | 0nUksNlstE2EawsWKaVx8yJDahsuCBWzDLEa0xQu6/W68/vo0itYdIzJumyIMs2tcEGoQFt1/We1 | ||
| 76 | WvV+j3vhzNuRNYULQsVMfTc2Nn346/qLEKLTtg2d+4FGOzbhdDrlz3aYtIv0HtVwQaiYq0+V33W1 | ||
| 77 | UJ0U0LYS0fms1cOCpelQHjzNfKbCRf0dzqSUed9Ych+5ZwuClJLCMGy1MqQevdHl136//6Pt3Rqo | ||
| 78 | Z2SuMfUhMoC++u5runXSngognTe0wSuWup25dd7f3/MnoAHgTMdmyTiOKUmSh27pHzRY1Jbhtr/7 | ||
| 79 | +vqKJWuA/+jcga022FWP+RzKpFaFlrK1vw5jjNbrdX4W8K1zgJdiye1S9xR734n9R7XdoMFS3Zl7 | ||
| 80 | i6nb+K+pOxyLcz7IuHdO0C7necfi8GVOq4W/397e/hnqzdfrNb28vLT+fSklfX19DXU5k8M5p+12 | ||
| 81 | W/uzp6enfGl+Lp1JF7TL2c/PD6VpSs/Pz7ML00G/V0h9vUdbSylxidrPPy3t7F+0S5maG5lTqBAN | ||
| 82 | HCzFPQhtLGnitsspcCaeGNcE7WKGwb8Jse3ZrkKI2aVyH13mk+oOGzcV2sUMgweLmmy7Fi5dT2ef | ||
| 83 | O3wY6qFdzPGQ5WZ1ULY6LU4dEHU8HilJkkVVKvda0vxTF2iXaXroPpYlzaFco+ae2t6hl/LhQbuY | ||
| 84 | Y/ChENTrctrekk7mQ7uYAcEykjiOW91xl3YyH9rFDAiWEd1aMRNC9P4qlTlCu8zfQ45NgOscx6HN | ||
| 85 | ZlOa1M6ybPF3ZLTLfCFYAEA7DIUAQDsECwBoh2ABAO0QLACgHYIFALRDsACAdggWANAOwQIA2iFY | ||
| 86 | AEA7BAsAaIdgAQDt/gUoDXNStc/rMQAAAABJRU5ErkJggg== | ||
| 87 | " | ||
| 88 | id="image2132" | ||
| 89 | x="-82" | ||
| 90 | y="-11.9" /></g><g | ||
| 91 | inkscape:groupmode="layer" | ||
| 92 | id="layer5" | ||
| 93 | inkscape:label="dot" /><g | ||
| 94 | inkscape:label="over" | ||
| 95 | inkscape:groupmode="layer" | ||
| 96 | id="layer1" | ||
| 97 | style="display:inline"><ellipse | ||
| 98 | style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none" | ||
| 99 | id="path2637" | ||
| 100 | cx="24" | ||
| 101 | cy="33" | ||
| 102 | rx="4.3600898" | ||
| 103 | ry="4.3600893" /><path | ||
| 104 | style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 105 | 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" | ||
| 106 | id="path2142" | ||
| 107 | inkscape:path-effect="#path-effect2144" | ||
| 108 | 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" | ||
| 109 | sodipodi:nodetypes="csc" /><path | ||
| 110 | style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 111 | d="M 26,22 H 39 V 9" | ||
| 112 | id="path2626" | ||
| 113 | sodipodi:nodetypes="ccc" /></g><g | ||
| 114 | inkscape:groupmode="layer" | ||
| 115 | id="layer6" | ||
| 116 | inkscape:label="into" | ||
| 117 | style="display:none"><ellipse | ||
| 118 | style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none" | ||
| 119 | id="path2637-3" | ||
| 120 | cx="24" | ||
| 121 | cy="38.5" | ||
| 122 | rx="4.3600898" | ||
| 123 | ry="4.3600893" /><path | ||
| 124 | style="fill:#000000;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 125 | d="M 24,2 V 24" | ||
| 126 | id="path2668" | ||
| 127 | sodipodi:nodetypes="cc" /><path | ||
| 128 | style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 129 | d="M 14.807612,16.994936 24,26.187324 33.192388,16.994936" | ||
| 130 | id="path2626-8" | ||
| 131 | sodipodi:nodetypes="ccc" /></g><g | ||
| 132 | inkscape:groupmode="layer" | ||
| 133 | id="g2714" | ||
| 134 | inkscape:label="out" | ||
| 135 | style="display:none"><ellipse | ||
| 136 | style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none" | ||
| 137 | id="ellipse2708" | ||
| 138 | cx="24" | ||
| 139 | cy="38.5" | ||
| 140 | rx="4.3600898" | ||
| 141 | ry="4.3600893" /><path | ||
| 142 | style="fill:#000000;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 143 | d="M 24,29.722858 V 7.7228579" | ||
| 144 | id="path2710" | ||
| 145 | sodipodi:nodetypes="cc" /><path | ||
| 146 | style="display:inline;fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 147 | d="M 33.192388,14.727922 24,5.5355339 14.807612,14.727922" | ||
| 148 | id="path2712" | ||
| 149 | sodipodi:nodetypes="ccc" /></g></svg> | ||
| @@ -0,0 +1,218 @@ | |||
| 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||
| 2 | <!-- Created with Inkscape (http://www.inkscape.org/) --> | ||
| 3 | |||
| 4 | <svg | ||
| 5 | width="48" | ||
| 6 | height="48" | ||
| 7 | viewBox="0 0 48 48" | ||
| 8 | version="1.1" | ||
| 9 | id="svg2120" | ||
| 10 | xml:space="preserve" | ||
| 11 | inkscape:version="1.2.2 (732a01da63, 2022-12-09)" | ||
| 12 | sodipodi:docname="step-resume.svg" | ||
| 13 | xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | ||
| 14 | xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | ||
| 15 | xmlns:xlink="http://www.w3.org/1999/xlink" | ||
| 16 | xmlns="http://www.w3.org/2000/svg" | ||
| 17 | xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview | ||
| 18 | id="namedview2122" | ||
| 19 | pagecolor="#ffffff" | ||
| 20 | bordercolor="#000000" | ||
| 21 | borderopacity="0.25" | ||
| 22 | inkscape:showpageshadow="2" | ||
| 23 | inkscape:pageopacity="0.0" | ||
| 24 | inkscape:pagecheckerboard="0" | ||
| 25 | inkscape:deskcolor="#d1d1d1" | ||
| 26 | inkscape:document-units="px" | ||
| 27 | showgrid="false" | ||
| 28 | inkscape:zoom="11.315" | ||
| 29 | inkscape:cx="4.7282369" | ||
| 30 | inkscape:cy="24.259832" | ||
| 31 | inkscape:window-width="1920" | ||
| 32 | inkscape:window-height="992" | ||
| 33 | inkscape:window-x="-8" | ||
| 34 | inkscape:window-y="-8" | ||
| 35 | inkscape:window-maximized="1" | ||
| 36 | inkscape:current-layer="layer8" /><defs | ||
| 37 | id="defs2117"><inkscape:path-effect | ||
| 38 | effect="spiro" | ||
| 39 | id="path-effect2144" | ||
| 40 | is_visible="true" | ||
| 41 | lpeversion="1" /><inkscape:path-effect | ||
| 42 | effect="bspline" | ||
| 43 | id="path-effect2138" | ||
| 44 | is_visible="true" | ||
| 45 | lpeversion="1" | ||
| 46 | weight="33.333333" | ||
| 47 | steps="2" | ||
| 48 | helper_size="0" | ||
| 49 | apply_no_weight="true" | ||
| 50 | apply_with_weight="true" | ||
| 51 | only_selected="false" /></defs><g | ||
| 52 | inkscape:groupmode="layer" | ||
| 53 | id="layer4" | ||
| 54 | inkscape:label="img" | ||
| 55 | style="display:none"><image | ||
| 56 | width="305.68866" | ||
| 57 | height="70.374367" | ||
| 58 | preserveAspectRatio="none" | ||
| 59 | xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARYAAABACAYAAADf7VgRAAAABHNCSVQICAgIfAhkiAAABdxJREFU | ||
| 60 | eJzt3T9vo0gYBvB3by+SsZRiKCI5SCkyJS6dzny3+26UpoSSFJFMOaTyUETaK3zDAQYbzGBgeH7S | ||
| 61 | SutN1kKj8cM7fxj/2u/3fwgAQKO/xr4AADAPggUAtEOwAIB2CBYA0A7BAgDaIVgAQDsECwBoh2AB | ||
| 62 | AO0QLACgHYIFALRDsACAdn+PfQFw5nkeWZZFRERSSvJ9f+Qrmg7XdSnLMorjeOxLgZYQLDBpruuS | ||
| 63 | 4zj5a4TLPGAoBJNVDRXOOXHOR7wiaAvBApNUDRUF4TIPCBaYnKZQURAu04dggUm5FSoKwmXaECww | ||
| 64 | GW1DRUG4TNfDV4VUx1mtViSEICKiLMtIStn4f4qdJ8syOh6Pw14kPFxdqAghyLKs0jI8EeWvif7v | ||
| 65 | G1gtmpbBg8V1XbJtm4jKHYKoHBhCCIrjuDZkqr+HYDFLU6gEQUCe55X+3ff90p4fIoTLFA0WLI7j | ||
| 66 | kOu6rX/ftm2ybZuEEBRF0dUKBsxxLVSaIFymT/scC2OMdrtdp1Apsm2bPM8jzvlFhQPmybKs9PpW | ||
| 67 | qCi+71/cfKrvZQrXdYkxNvZldKK1YmGM0cfHR+3PhBAkpaTv728iIjqdTmTbNq1WK7Jtu3aYxBij | ||
| 68 | KIp0XuLoXNelz8/PzhWZZVnkuq5x1ZyqMDjnrUNFKVYuURQZOUTe7XZ5NR+GIaVpOvYltaItWCzL | ||
| 69 | qg0VNXdS1yDFf2OM0Xa7LQWMbdt3Vz5TVOwkQRC0DgjLsvK5BlPD5d5Jed/3yXEcI0OlOj+53W5n | ||
| 70 | Ey5ahkLFjq9IKelwOFAQBK0aIk1T8n3/ooOohp27aifZ7XathnrVtjUtbJU+wWBiqBDRRWWrwqXv | ||
| 71 | sKi40jYULRVLtaP3eTo3iiLKssy4/QlJkpSGfCpcrlUudYFNhAnKpZBSUhAEpZvQvZWLGkoXb9RS | ||
| 72 | SkqSZJD+1Lti4ZxfVBVhGPZ6zziOjfvwpGlKYRhe3IGaKpemUDkcDrMohUEPFS59KhfOOXmed/E5 | ||
| 73 | tSwr/5nuCqZ3xfL6+lp63bXjq0naJVDhUpxLqgsXhAoU9alcGGM3q3/VB3WeAdSrYqmO1aSUnTs+ | ||
| 74 | Yyyf0Kz+MVFT5XILQmXZ7q1cmlZpqyzL6vQ4xS29KpZqtZIkSa+LWYq6yuUahIo56irRLqr95Vrl | ||
| 75 | 0nUksNlstE2EawsWKaVx8yJDahsuCBWzDLEa0xQu6/W68/vo0itYdIzJumyIMs2tcEGoQFt1/We1 | ||
| 76 | WvV+j3vhzNuRNYULQsVMfTc2Nn346/qLEKLTtg2d+4FGOzbhdDrlz3aYtIv0HtVwQaiYq0+V33W1 | ||
| 77 | UJ0U0LYS0fms1cOCpelQHjzNfKbCRf0dzqSUed9Ych+5ZwuClJLCMGy1MqQevdHl136//6Pt3Rqo | ||
| 78 | Z2SuMfUhMoC++u5runXSngognTe0wSuWup25dd7f3/MnoAHgTMdmyTiOKUmSh27pHzRY1Jbhtr/7 | ||
| 79 | +vqKJWuA/+jcga022FWP+RzKpFaFlrK1vw5jjNbrdX4W8K1zgJdiye1S9xR734n9R7XdoMFS3Zl7 | ||
| 80 | i6nb+K+pOxyLcz7IuHdO0C7necfi8GVOq4W/397e/hnqzdfrNb28vLT+fSklfX19DXU5k8M5p+12 | ||
| 81 | W/uzp6enfGl+Lp1JF7TL2c/PD6VpSs/Pz7ML00G/V0h9vUdbSylxidrPPy3t7F+0S5maG5lTqBAN | ||
| 82 | HCzFPQhtLGnitsspcCaeGNcE7WKGwb8Jse3ZrkKI2aVyH13mk+oOGzcV2sUMgweLmmy7Fi5dT2ef | ||
| 83 | O3wY6qFdzPGQ5WZ1ULY6LU4dEHU8HilJkkVVKvda0vxTF2iXaXroPpYlzaFco+ae2t6hl/LhQbuY | ||
| 84 | Y/ChENTrctrekk7mQ7uYAcEykjiOW91xl3YyH9rFDAiWEd1aMRNC9P4qlTlCu8zfQ45NgOscx6HN | ||
| 85 | ZlOa1M6ybPF3ZLTLfCFYAEA7DIUAQDsECwBoh2ABAO0QLACgHYIFALRDsACAdggWANAOwQIA2iFY | ||
| 86 | AEA7BAsAaIdgAQDt/gUoDXNStc/rMQAAAABJRU5ErkJggg== | ||
| 87 | " | ||
| 88 | id="image2132" | ||
| 89 | x="-82" | ||
| 90 | y="-11.9" /><image | ||
| 91 | width="460" | ||
| 92 | height="71" | ||
| 93 | preserveAspectRatio="none" | ||
| 94 | xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAcwAAABHCAYAAACK23cpAAAABHNCSVQICAgIfAhkiAAACuZJREFU | ||
| 95 | eJzt3T9o3FgeB/Cv77aZBHInYZPD+JLF4iDxDl44ZNiAvVfNhC2vCEqxWQJpJn3MwlRXTeP0drNF | ||
| 96 | NtfIKa5cYnVxkQMLDoztbU6GgBkwDiPOEHuOK/aK7FM0Gs3M01gzkp6+HzBknPFEkSV93z/9NLO6 | ||
| 97 | uvoLiIiIaKjfZL0BRERERcDAJCIiksDAJCIiksDAJCIiksDAJCIiksDAJCIiksDAJCIiksDAJCIi | ||
| 98 | ksDAJCIiksDAJCIiksDAJCIiksDAJCIikvBZ1htAlIZmswlN0wAAvu+j1WplvEX5YVkWOp0OHMfJ | ||
| 99 | elMoBYuLi8Gfj4+PM9yS8mFgEinMsiyYphm8ZmgSjY9DskSKioZlvV5HrVbLcIuoyEzTDEZxyoqB | ||
| 100 | SaSgaFgKDE0aR6PRCI6pMocmA5NIMYPCUmBoUhKNRgOGYQD4eOyUOTQZmEQKGRWWAkOTZPm+3/O6 | ||
| 101 | zKHJwCRShGxYCgxNkmHbNlzX7fleWUNzqqtk5xaWMbuwHLx+d+Tg4vw0eH3txs2e19RPXBA1TQuW | ||
| 102 | lHc6nb5WYFj4ouj7ft/BT8UXF5ae50HX9Z7bbQD0XOTq9ToArp6l4WzbBoC+RWQA4Lru0OuPSqYa | ||
| 103 | mLMLy1i69yh4/f5kvycgv3nyI47evuwL0jKzLCuYPxjWmvM8D47jxIanOLDF+xiYahkUlltbW2g2 | ||
| 104 | mz3fb7VaPfesAgxNksPQzOGQ7NK9R/jLgw3MhXqiZWSaJjY2NoJhj1FDH4ZhBCvZyjZMUmbDwnKQ | ||
| 105 | VqsV26ji8CyNUvbh2VwWLrh24ya+frCBs5N9uK+fl6q3aRgGarVa0Ksc5+ebzSZ2dnZK0+ors06n | ||
| 106 | 0/N6VFgKcT1NVY8Vy7Lgui48z8t6Uwpr1HVpUE9T0zQ8ffp0rDAVo2F5GhHLXQ8zbG5hGd88+RG3 | ||
| 107 | l2q4duNm1pszcaKXGHdQioPHtm3Yto2tra2hoViv15XsbY77f9I0DY1GQ7n94TgOdnZ2AMiHpRDu | ||
| 108 | acb1HFTQaDRgmmbP1AYlI86dUfsvrqfp+z42NzfHaowZhgHLsnI18pHLHmaUef+Z8r1NcVBGibnJ | ||
| 109 | uNZx+Hvi4AofrOJ7qhAnrWEYiU5CTdOCuTzLsmDbtlK9Kcdxxl7M1Wq1YJqmkmEZnf8Xv3v2NJMJ | ||
| 110 | r4GQfW+4IS9Cc9yeZp7m2H9769atv03rH5tbWMbcH78MXkcX94QXBEVdv3ETf/rzXwEAF+en+N9/ | ||
| 111 | P0xuQ6csfEEXfN/HixcvgovhKL7vY3d3F7quY35+Pvi+rut97yvixdGyLFSrVQBApVJBtVrFwcEB | ||
| 112 | ut0uAGBtbQ2VSgUA0O12sbu7C6B/34r9U8R9MEy73Y79/qD9IvOzRddut1GtVoP/f6VSgWEYaLfb | ||
| 113 | V2owaZqGSqUSHHvTNu1h9MePH/e89jwPvu/3fIWvM4Zh4PLyEr7vB/uo2+3i4OAA8/PzfT876Cv6 | ||
| 114 | mb7vZ36s5raHeXF+ig/np32Lf5buPcLnX9Thvn6Os5P9jLYuXdFe4FWetmHbNjqdTqJWYRG4rgvD | ||
| 115 | MIKLhZgbGdbTjGuIAPloqdLkxfVsxu1pip8LD0v6vo+9vb1SHU+Dhv3j6hYD/T3NJFMG0Tl2y7KC | ||
| 116 | sM5KbucwP5yf4s2r9dhhWLEo6OsHG4Wf24ybSBfLt8cVntdShed5fUOpwxYUDArLra0tDsmVSNwc | ||
| 117 | Wlz4DVOr1dBsNvver2ka6vV634W9jCaxejauMTzusG5acjske3F+indHDv5zdox//+sfuP67P+D3 | ||
| 118 | c70HrBimFUO0RRymffjwYTBkBCS/oNdqteDADH/put43HAsUd0gWQDAkYxhGzzBbtVrtOYkqlQrW | ||
| 119 | 1tb6fr6MYSkzJKs6MRw4zvCszDoAcQxOa99Oe0g2PFo17PpxeHjYNyUUNzwra9DvLTodM0257WFG | ||
| 120 | ua+f482r9dhFP+b9ZzDvPytcbzN6f6Xv+4kv6GIRTNyXigb1NEcpY1jSJ+P2NOMW4sXRNC1RWUJV | ||
| 121 | pd3THPZ7y6Knmds5zDhnJ/v46YfvcPerb/H5F/WegBS3oBSpUlD0BNvb28toS4pFhKbsScOwVEfc | ||
| 122 | MHsS0eNl2Jxm0kanqquNk0q7IpCYK15ZWQl+f4ZhBPt7mnOahelhhv38z78PXPRTpEpBKysrwZ99 | ||
| 123 | 3y/V4oGriutpxmFYqkWMyoz7Negz43qaSXswcVMgZZV2T9NxnL4Ohfi8aSpUDzPsQwF6kKOMuxI2 | ||
| 124 | LMmqM9WM6mkyLEnWoIVjV/2MMptETzNq2o2UQgbm3a++HXjP5rsjB+7r51PeIsrKoNBkWKrpqsNv | ||
| 125 | g0It7ngRTwOSxeHYfmmFZtziK3HuT1OhAnNuYRl37z2KHW49O9nHz29fKnNv5iDiwPN9P6gjqlLV | ||
| 126 | mnFEQ5Nhqa6rjMokvdVIPPlHtucYretLH101NEXJ0LCkZSDTUpjANO8/w+2l+JqC7uvneHek9vyf | ||
| 127 | uH0kSnYuT3Xh1ibD8pNOp8OGFca7L9f3fdi2LbVSVpSwpHjjhmZcydCkBRDSlPvAvL1Ug3n/Wezf | ||
| 128 | qV5fVhhW+Fg8nUTV4tlJMCj7lXmOW7hKEQvP87CzszO0chYX7MlJGppiMVaYaMRkJbeBef3Xaj5x | ||
| 129 | w68X56dKlcYbRvZRX/V6PfOyUUR5k0bFJ8dx4LouS+OlIGloRhf1ZF08P7eBee3GzdhCBGVa1CNK | ||
| 130 | b8m+1zRNnrxEv0qzPKIYBhTzmWVqmIbncXVdly7mkITMU07ysDYht4EZVZZFPVdhGEZpA1MUZtc0 | ||
| 131 | DcfHx8GCjbIr836JK2l31YtuWfZdWKfT6SleP6nbZ4aFpmEYmYclUJDALMOinjhJb8pVtRzeMHEr | ||
| 132 | 6IBPcx15OMmywP2CYOW0OC/y0EMpItu2p1b0fFBo5mV9Rm6LrwMfe5VvXq3j/QR6lbOzs7i4uEj9 | ||
| 133 | c9Ok63rwDEgZ4pmYZVGr1QYWxq5UKkGDI+n9dEXH/fJRt9uF53mYn5/H9va2MmE57eLr3W4X7XYb | ||
| 134 | mqZhZmam52ERk3CVgu2Tlsse5qQX9czOzuL777/H+vr6RD4/LUlP8DLdByY7v1uv16debzJL3C+9 | ||
| 135 | srwFQSWe5020wSHzPM08yF0t2XdHDn764buJhuWTJ08m8tlpE08el1Wm+ctRj1wa971Fx/1CRTSJ | ||
| 136 | 52lOwlR7mO9P9nH09mXwOloP9s2r9Yku6hFhOTs7O7F/I23RJ8YPMukWYN4kma8VC1/y1FKdFO4X | ||
| 137 | Kqq0a89OwlQD8+xkf2ggMiz7iUUawx5llVWZqKzkqcWZJ9wvVHR5D81czmGmrahhKXieh1arFRQx | ||
| 138 | 0HUdmqbBdV24rluqnuW4sj7R8or7hfImz6GpfGAWPSzDHMcp1TzlIGJuV7ZHVZZQ4H4hVeQ1NHO3 | ||
| 139 | 6CdNKoUl9Yo+TDat9xYd9wupIo8LgZQNTIal2hzHkWpllq0wNvcLqWRQaGZVpEXJwGRYlsPm5ubQ | ||
| 140 | cMjiAbN5wP1CKomGpud5mVX+mWqln2lIEpZsYRdbt9vF7u4ufN9HpVIJqpC4rouDgwNsb2+Xcp6O | ||
| 141 | +0Vt0670kweHh4fQdR2Xl5eZ3hEws7q6+ktm/3rKkvYs817ph4iI8kOZVbKi3B0REdEkKNXDJCIi | ||
| 142 | mhQlF/0QERGljYFJREQkgYFJREQkgYFJREQkgYFJREQkgYFJREQkgYFJREQkgYFJREQkgYFJREQk | ||
| 143 | 4bPFxcWst4GIiCj32MMkIiKSMHPnzh3WkiUiIhqBPUwiIiIJDEwiIiIJDEwiIiIJ/weH8+Ed/xCz | ||
| 144 | fAAAAABJRU5ErkJggg== | ||
| 145 | " | ||
| 146 | id="image2845" | ||
| 147 | x="-15.489594" | ||
| 148 | y="-8.2945251" /></g><g | ||
| 149 | inkscape:groupmode="layer" | ||
| 150 | id="layer5" | ||
| 151 | inkscape:label="dot" /><g | ||
| 152 | inkscape:label="over" | ||
| 153 | inkscape:groupmode="layer" | ||
| 154 | id="layer1" | ||
| 155 | style="display:none"><ellipse | ||
| 156 | style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none" | ||
| 157 | id="path2637" | ||
| 158 | cx="24" | ||
| 159 | cy="33" | ||
| 160 | rx="4.3600898" | ||
| 161 | ry="4.3600893" /><path | ||
| 162 | style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 163 | 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" | ||
| 164 | id="path2142" | ||
| 165 | inkscape:path-effect="#path-effect2144" | ||
| 166 | 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" | ||
| 167 | sodipodi:nodetypes="csc" /><path | ||
| 168 | style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 169 | d="M 26,22 H 39 V 9" | ||
| 170 | id="path2626" | ||
| 171 | sodipodi:nodetypes="ccc" /></g><g | ||
| 172 | inkscape:groupmode="layer" | ||
| 173 | id="layer6" | ||
| 174 | inkscape:label="into" | ||
| 175 | style="display:none"><ellipse | ||
| 176 | style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none" | ||
| 177 | id="path2637-3" | ||
| 178 | cx="24" | ||
| 179 | cy="38.5" | ||
| 180 | rx="4.3600898" | ||
| 181 | ry="4.3600893" /><path | ||
| 182 | style="fill:#000000;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 183 | d="M 24,2 V 24" | ||
| 184 | id="path2668" | ||
| 185 | sodipodi:nodetypes="cc" /><path | ||
| 186 | style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 187 | d="M 14.807612,16.994936 24,26.187324 33.192388,16.994936" | ||
| 188 | id="path2626-8" | ||
| 189 | sodipodi:nodetypes="ccc" /></g><g | ||
| 190 | inkscape:groupmode="layer" | ||
| 191 | id="g2714" | ||
| 192 | inkscape:label="out" | ||
| 193 | style="display:none"><ellipse | ||
| 194 | style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none" | ||
| 195 | id="ellipse2708" | ||
| 196 | cx="24" | ||
| 197 | cy="38.5" | ||
| 198 | rx="4.3600898" | ||
| 199 | ry="4.3600893" /><path | ||
| 200 | style="fill:#000000;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 201 | d="M 24,29.722858 V 7.7228579" | ||
| 202 | id="path2710" | ||
| 203 | sodipodi:nodetypes="cc" /><path | ||
| 204 | style="display:inline;fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 205 | d="M 33.192388,14.727922 24,5.5355339 14.807612,14.727922" | ||
| 206 | id="path2712" | ||
| 207 | sodipodi:nodetypes="ccc" /></g><g | ||
| 208 | inkscape:groupmode="layer" | ||
| 209 | id="layer8" | ||
| 210 | inkscape:label="resume"><path | ||
| 211 | style="fill:#000000;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 212 | d="M 13,12 V 38" | ||
| 213 | id="path2850" | ||
| 214 | sodipodi:nodetypes="cc" /><path | ||
| 215 | style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 216 | d="M 21,16 V 34 L 36.5,25 Z" | ||
| 217 | id="path2852" | ||
| 218 | sodipodi:nodetypes="cccc" /></g></svg> | ||
| @@ -383,7 +383,7 @@ | |||
| 383 | Max Response Length (tokens) | 383 | Max Response Length (tokens) |
| 384 | </div> | 384 | </div> |
| 385 | <div class="wide100p"> | 385 | <div class="wide100p"> |
| 386 | <input type="number" id="openai_max_tokens" name="openai_max_tokens" class="text_pole" min="50" max="8000"> | 386 | <input type="number" id="openai_max_tokens" name="openai_max_tokens" class="text_pole" min="1" max="16384"> |
| 387 | </div> | 387 | </div> |
| 388 | </div> | 388 | </div> |
| 389 | <div class="range-block" data-source="openai,custom"> | 389 | <div class="range-block" data-source="openai,custom"> |
| @@ -1273,7 +1273,7 @@ | |||
| 1273 | <input class="neo-range-slider" type="range" id="max_tokens_second_textgenerationwebui" name="volume" min="0" max="20" step="1" /> | 1273 | <input class="neo-range-slider" type="range" id="max_tokens_second_textgenerationwebui" name="volume" min="0" max="20" step="1" /> |
| 1274 | <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"> | 1274 | <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"> |
| 1275 | </div> | 1275 | </div> |
| 1276 | <div data-newbie-hidden data-tg-type="mancer, ooba, koboldcpp, aphrodite, tabby" name="smoothingBlock" class="wide100p"> | 1276 | <div data-newbie-hidden data-tg-type="mancer, ooba, koboldcpp, aphrodite, tabby" id="smoothingBlock" name="smoothingBlock" class="wide100p"> |
| 1277 | <h4 class="wide100p textAlignCenter"> | 1277 | <h4 class="wide100p textAlignCenter"> |
| 1278 | <label data-i18n="Smooth Sampling">Smooth Sampling</label> | 1278 | <label data-i18n="Smooth Sampling">Smooth Sampling</label> |
| 1279 | <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> | 1279 | <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 @@ | |||
| 1358 | </div> | 1358 | </div> |
| 1359 | </div> | 1359 | </div> |
| 1360 | </div> | 1360 | </div> |
| 1361 | <div data-newbie-hidden id="mirostat_block_ooba" class="wide100p"> | 1361 | <div data-newbie-hidden data-tg-type="ooba,aphrodite,infermaticai,koboldcpp,llamacpp,mancer,ollama,tabby" id="mirostat_block_ooba" class="wide100p"> |
| 1362 | <h4 class="wide100p textAlignCenter"> | 1362 | <h4 class="wide100p textAlignCenter"> |
| 1363 | <label data-i18n="Mirostat (mode=1 is only for llama.cpp)">Mirostat</label> | 1363 | <label data-i18n="Mirostat (mode=1 is only for llama.cpp)">Mirostat</label> |
| 1364 | <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> | 1364 | <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 @@ | |||
| 1387 | </div> | 1387 | </div> |
| 1388 | </div> | 1388 | </div> |
| 1389 | </div> | 1389 | </div> |
| 1390 | <div data-newbie-hidden data-tg-type="ooba, vllm" name="beamSearchBlock" class="wide100p"> | 1390 | <div data-newbie-hidden data-tg-type="ooba, vllm" id="beamSearchBlock" name="beamSearchBlock" class="wide100p"> |
| 1391 | <h4 class="wide100p textAlignCenter"> | 1391 | <h4 class="wide100p textAlignCenter"> |
| 1392 | <label> | 1392 | <label> |
| 1393 | <span data-i18n="Beam search">Beam Search</span> | 1393 | <span data-i18n="Beam search">Beam Search</span> |
| @@ -1413,7 +1413,7 @@ | |||
| 1413 | </div> | 1413 | </div> |
| 1414 | </div> | 1414 | </div> |
| 1415 | </div> | 1415 | </div> |
| 1416 | <div data-tg-type="ooba" data-newbie-hidden name="contrastiveSearchBlock" class="alignitemscenter flex-container flexFlowColumn flexBasis48p flexGrow flexShrink gap0"> | 1416 | <div data-tg-type="ooba" data-newbie-hidden id="contrastiveSearchBlock" name="contrastiveSearchBlock" class="alignitemscenter flex-container flexFlowColumn flexBasis48p flexGrow flexShrink gap0"> |
| 1417 | <h4 class="textAlignCenter" data-i18n="Contrastive search">Contrastive Search | 1417 | <h4 class="textAlignCenter" data-i18n="Contrastive search">Contrastive Search |
| 1418 | <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> | 1418 | <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> |
| 1419 | </h4> | 1419 | </h4> |
| @@ -1544,7 +1544,7 @@ | |||
| 1544 | <h4 class="wide100p textAlignCenter"> | 1544 | <h4 class="wide100p textAlignCenter"> |
| 1545 | <label> | 1545 | <label> |
| 1546 | <span data-i18n="Grammar String">Grammar String</span> | 1546 | <span data-i18n="Grammar String">Grammar String</span> |
| 1547 | <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> | 1547 | <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> |
| 1548 | <a href="https://github.com/ggerganov/llama.cpp/blob/master/grammars/README.md" target="_blank"> | 1548 | <a href="https://github.com/ggerganov/llama.cpp/blob/master/grammars/README.md" target="_blank"> |
| 1549 | <small> | 1549 | <small> |
| 1550 | <div class="fa-solid fa-up-right-from-square note-link-span"></div> | 1550 | <div class="fa-solid fa-up-right-from-square note-link-span"></div> |
| @@ -1554,13 +1554,13 @@ | |||
| 1554 | </h4> | 1554 | </h4> |
| 1555 | <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> | 1555 | <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> |
| 1556 | </div> | 1556 | </div> |
| 1557 | <div id="sampler_order_block" data-newbie-hidden data-tg-type="koboldcpp" class="range-block flexFlowColumn wide100p"> | 1557 | <div id="sampler_order_block_kcpp" data-newbie-hidden data-tg-type="koboldcpp" class="range-block flexFlowColumn wide100p"> |
| 1558 | <hr class="wide100p"> | 1558 | <hr class="wide100p"> |
| 1559 | <div class="range-block-title"> | 1559 | <div class="range-block-title"> |
| 1560 | <span data-i18n="Samplers Order">Samplers Order</span> | 1560 | <span data-i18n="Samplers Order">Samplers Order</span> |
| 1561 | </div> | 1561 | </div> |
| 1562 | <div class="toggle-description widthUnset" data-i18n="Samplers will be applied in a top-down order. Use with caution."> | 1562 | <div class="toggle-description widthUnset" data-i18n="Samplers will be applied in a top-down order. Use with caution."> |
| 1563 | Samplers will be applied in a top-down order. | 1563 | kcpp only. Samplers will be applied in a top-down order. |
| 1564 | Use with caution. | 1564 | Use with caution. |
| 1565 | </div> | 1565 | </div> |
| 1566 | <div id="koboldcpp_order" class="prompt_order"> | 1566 | <div id="koboldcpp_order" class="prompt_order"> |
| @@ -1597,10 +1597,10 @@ | |||
| 1597 | <span data-i18n="Load default order">Load default order</span> | 1597 | <span data-i18n="Load default order">Load default order</span> |
| 1598 | </div> | 1598 | </div> |
| 1599 | </div> | 1599 | </div> |
| 1600 | <div data-newbie-hidden data-tg-type="llamacpp" class="range-block flexFlowColumn wide100p"> | 1600 | <div id="sampler_order_block_lcpp" data-newbie-hidden data-tg-type="llamacpp" class="range-block flexFlowColumn wide100p"> |
| 1601 | <hr class="wide100p"> | 1601 | <hr class="wide100p"> |
| 1602 | <h4 class="range-block-title justifyCenter"> | 1602 | <h4 class="range-block-title justifyCenter"> |
| 1603 | <span data-i18n="Samplers Order">Samplers Order</span> | 1603 | <span data-i18n="Sampler Order">Sampler Order</span> |
| 1604 | <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> | 1604 | <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> |
| 1605 | </h4> | 1605 | </h4> |
| 1606 | <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."> | 1606 | <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 @@ | |||
| 1618 | <span data-i18n="Load default order">Load default order</span> | 1618 | <span data-i18n="Load default order">Load default order</span> |
| 1619 | </div> | 1619 | </div> |
| 1620 | </div> | 1620 | </div> |
| 1621 | <div data-newbie-hidden data-tg-type="ooba" class="range-block flexFlowColumn wide100p"> | 1621 | <div id="sampler_priority_block_ooba" data-newbie-hidden data-tg-type="ooba" class="range-block flexFlowColumn wide100p"> |
| 1622 | <hr class="wide100p"> | 1622 | <hr class="wide100p"> |
| 1623 | <h4 class="range-block-title justifyCenter"> | 1623 | <h4 class="range-block-title justifyCenter"> |
| 1624 | <span data-i18n="Sampler Priority">Sampler Priority</span> | 1624 | <span data-i18n="Sampler Priority">Sampler Priority</span> |
| @@ -1660,16 +1660,24 @@ | |||
| 1660 | </div> | 1660 | </div> |
| 1661 | <div class="inline-drawer-content"> | 1661 | <div class="inline-drawer-content"> |
| 1662 | <label class="checkbox_label flexWrap alignItemsCenter" for="character_names_none"> | 1662 | <label class="checkbox_label flexWrap alignItemsCenter" for="character_names_none"> |
| 1663 | <input type="radio" id="character_names_none" name="character_names" value="0"> | 1663 | <input type="radio" id="character_names_none" name="character_names" value="-1"> |
| 1664 | <span data-i18n="None">None</span> | 1664 | <span data-i18n="None">None</span> |
| 1665 | <i class="right_menu_button fa-solid fa-circle-exclamation" title="Except for groups and past personas. Otherwise, make sure you provide names in the prompt." data-i18n="[title]character_names_none"></i> | 1665 | <i class="right_menu_button fa-solid fa-circle-exclamation" title="Never add character name prefixes. May behave poorly in groups, choose with caution." data-i18n="[title]character_names_none"></i> |
| 1666 | <small class="flexBasis100p" data-i18n="Don't add character names."> | 1666 | <small class="flexBasis100p" data-i18n="Never add character names."> |
| 1667 | Don't add character names. | 1667 | Never add character names. |
| 1668 | </small> | ||
| 1669 | </label> | ||
| 1670 | <label class="checkbox_label flexWrap alignItemsCenter" for="character_names_default"> | ||
| 1671 | <input type="radio" id="character_names_default" name="character_names" value="0"> | ||
| 1672 | <span data-i18n="Default">Default</span> | ||
| 1673 | <i class="right_menu_button fa-solid fa-circle-exclamation" title="Add prefixes for groups and past personas. Otherwise, make sure you provide names in the prompt." data-i18n="[title]character_names_default"></i> | ||
| 1674 | <small class="flexBasis100p" data-i18n="Don't add character names unless necessary."> | ||
| 1675 | Don't add character names unless necessary. | ||
| 1668 | </small> | 1676 | </small> |
| 1669 | </label> | 1677 | </label> |
| 1670 | <label class="checkbox_label flexWrap alignItemsCenter" for="character_names_completion"> | 1678 | <label class="checkbox_label flexWrap alignItemsCenter" for="character_names_completion"> |
| 1671 | <input type="radio" id="character_names_completion" name="character_names" value="1"> | 1679 | <input type="radio" id="character_names_completion" name="character_names" value="1"> |
| 1672 | <span data-i18n="Completion">Completion Object</span> | 1680 | <span data-i18n="Completion Object">Completion Object</span> |
| 1673 | <i class="right_menu_button fa-solid fa-circle-exclamation" title="Restrictions apply: only Latin alphanumerics and underscores. Doesn't work for all sources, notably: Claude, MistralAI, Google." data-i18n="[title]character_names_completion"></i> | 1681 | <i class="right_menu_button fa-solid fa-circle-exclamation" title="Restrictions apply: only Latin alphanumerics and underscores. Doesn't work for all sources, notably: Claude, MistralAI, Google." data-i18n="[title]character_names_completion"></i> |
| 1674 | <small class="flexBasis100p" data-i18n="Add character names to completion objects."> | 1682 | <small class="flexBasis100p" data-i18n="Add character names to completion objects."> |
| 1675 | Add character names to completion objects. | 1683 | Add character names to completion objects. |
| @@ -1762,13 +1770,13 @@ | |||
| 1762 | </div> | 1770 | </div> |
| 1763 | </label> | 1771 | </label> |
| 1764 | </div> | 1772 | </div> |
| 1765 | <div class="range-block" data-source="openai,openrouter,makersuite,claude,custom"> | 1773 | <div class="range-block" data-source="openai,openrouter,makersuite,claude,custom,01ai"> |
| 1766 | <label for="openai_image_inlining" class="checkbox_label flexWrap widthFreeExpand"> | 1774 | <label for="openai_image_inlining" class="checkbox_label flexWrap widthFreeExpand"> |
| 1767 | <input id="openai_image_inlining" type="checkbox" /> | 1775 | <input id="openai_image_inlining" type="checkbox" /> |
| 1768 | <span data-i18n="Send inline images">Send inline images</span> | 1776 | <span data-i18n="Send inline images">Send inline images</span> |
| 1769 | <div id="image_inlining_hint" class="flexBasis100p toggle-description justifyLeft"> | 1777 | <div id="image_inlining_hint" class="flexBasis100p toggle-description justifyLeft"> |
| 1770 | <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). | 1778 | <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). |
| 1771 | 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> | 1779 | 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> |
| 1772 | <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> | 1780 | <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> |
| 1773 | </div> | 1781 | </div> |
| 1774 | </label> | 1782 | </label> |
| @@ -1815,10 +1823,16 @@ | |||
| 1815 | </div> | 1823 | </div> |
| 1816 | <div data-newbie-hidden class="range-block" data-source="claude"> | 1824 | <div data-newbie-hidden class="range-block" data-source="claude"> |
| 1817 | <div class="wide100p"> | 1825 | <div class="wide100p"> |
| 1818 | <span id="claude_assistant_prefill_text" data-i18n="Assistant Prefill">Assistant Prefill</span> | 1826 | <div class="flex-container alignItemsCenter"> |
| 1819 | <textarea id="claude_assistant_prefill" class="text_pole textarea_compact" name="assistant_prefill autoSetHeight" rows="3" maxlength="10000" data-i18n="[placeholder]Start Claude's answer with..." placeholder="Start Claude's answer with..."></textarea> | 1827 | <span id="claude_assistant_prefill_text" data-i18n="Assistant Prefill">Assistant Prefill</span> |
| 1820 | <span id="claude_assistant_impersonation_text" data-i18n="Assistant Impersonation Prefill">Assistant Impersonation Prefill</span> | 1828 | <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> |
| 1821 | <textarea id="claude_assistant_impersonation" class="text_pole textarea_compact" name="assistant_impersonation autoSetHeight" rows="3" maxlength="10000" data-i18n="[placeholder]Start Claude's answer with..." placeholder="Start Claude's answer with..."></textarea> | 1829 | </div> |
| 1830 | <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> | ||
| 1831 | <div class="flex-container alignItemsCenter"> | ||
| 1832 | <span id="claude_assistant_impersonation_text" data-i18n="Assistant Impersonation Prefill">Assistant Impersonation Prefill</span> | ||
| 1833 | <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> | ||
| 1834 | </div> | ||
| 1835 | <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> | ||
| 1822 | </div> | 1836 | </div> |
| 1823 | <label for="claude_use_sysprompt" class="checkbox_label widthFreeExpand"> | 1837 | <label for="claude_use_sysprompt" class="checkbox_label widthFreeExpand"> |
| 1824 | <input id="claude_use_sysprompt" type="checkbox" /> | 1838 | <input id="claude_use_sysprompt" type="checkbox" /> |
| @@ -2419,6 +2433,7 @@ | |||
| 2419 | <optgroup> | 2433 | <optgroup> |
| 2420 | <option value="01ai">01.AI (Yi)</option> | 2434 | <option value="01ai">01.AI (Yi)</option> |
| 2421 | <option value="ai21">AI21</option> | 2435 | <option value="ai21">AI21</option> |
| 2436 | <option value="blockentropy">Block Entropy</option> | ||
| 2422 | <option value="claude">Claude</option> | 2437 | <option value="claude">Claude</option> |
| 2423 | <option value="cohere">Cohere</option> | 2438 | <option value="cohere">Cohere</option> |
| 2424 | <option value="groq">Groq</option> | 2439 | <option value="groq">Groq</option> |
| @@ -2562,7 +2577,9 @@ | |||
| 2562 | </optgroup> | 2577 | </optgroup> |
| 2563 | <optgroup label="GPT-4o"> | 2578 | <optgroup label="GPT-4o"> |
| 2564 | <option value="gpt-4o">gpt-4o</option> | 2579 | <option value="gpt-4o">gpt-4o</option> |
| 2580 | <option value="gpt-4o-2024-08-06">gpt-4o-2024-08-06</option> | ||
| 2565 | <option value="gpt-4o-2024-05-13">gpt-4o-2024-05-13</option> | 2581 | <option value="gpt-4o-2024-05-13">gpt-4o-2024-05-13</option> |
| 2582 | <option value="chatgpt-4o-latest">chatgpt-4o-latest</option> | ||
| 2566 | </optgroup> | 2583 | </optgroup> |
| 2567 | <optgroup label="gpt-4o-mini"> | 2584 | <optgroup label="gpt-4o-mini"> |
| 2568 | <option value="gpt-4o-mini">gpt-4o-mini</option> | 2585 | <option value="gpt-4o-mini">gpt-4o-mini</option> |
| @@ -2794,21 +2811,26 @@ | |||
| 2794 | <div> | 2811 | <div> |
| 2795 | <h4 data-i18n="Google Model">Google Model</h4> | 2812 | <h4 data-i18n="Google Model">Google Model</h4> |
| 2796 | <select id="model_google_select"> | 2813 | <select id="model_google_select"> |
| 2797 | <optgroup label="Latest"> | 2814 | <optgroup label="Primary"> |
| 2798 | <!-- Doesn't work without "latest". Maybe my key is scuffed? --> | 2815 | <option value="gemini-1.5-pro">Gemini 1.5 Pro</option> |
| 2799 | <option value="gemini-1.5-flash-latest">Gemini 1.5 Flash</option> | 2816 | <option value="gemini-1.5-flash">Gemini 1.5 Flash</option> |
| 2800 | <!-- Points to 1.0, no default 1.5 endpoint --> | 2817 | <option value="gemini-1.0-pro">Gemini 1.0 Pro</option> |
| 2801 | <option value="gemini-pro">Gemini Pro</option> | 2818 | <option value="gemini-pro">Gemini Pro (1.0)</option> |
| 2802 | <option value="gemini-pro-vision">Gemini Pro Vision</option> | 2819 | <option value="gemini-pro-vision">Gemini Pro Vision (1.0)</option> |
| 2803 | <option value="gemini-ultra">Gemini Ultra</option> | 2820 | <option value="gemini-ultra">Gemini Ultra (1.0)</option> |
| 2804 | <option value="text-bison-001">Bison Text</option> | ||
| 2805 | <option value="chat-bison-001">Bison Chat</option> | ||
| 2806 | </optgroup> | ||
| 2807 | <optgroup label="Sub-versions"> | ||
| 2808 | <option value="gemini-1.5-pro-latest">Gemini 1.5 Pro</option> | ||
| 2809 | <option value="gemini-1.0-pro-latest">Gemini 1.0 Pro</option> | ||
| 2810 | <option value="gemini-1.0-pro-vision-latest">Gemini 1.0 Pro Vision</option> | ||
| 2811 | <option value="gemini-1.0-ultra-latest">Gemini 1.0 Ultra</option> | 2821 | <option value="gemini-1.0-ultra-latest">Gemini 1.0 Ultra</option> |
| 2822 | <option value="text-bison-001">PaLM 2 (Legacy)</option> | ||
| 2823 | <option value="chat-bison-001">PaLM 2 Chat (Legacy)</option> | ||
| 2824 | </optgroup> | ||
| 2825 | <optgroup label="Subversions"> | ||
| 2826 | <option value="gemini-1.5-pro-exp-0801">Gemini 1.5 Pro Experiment 2024-08-01</option> | ||
| 2827 | <option value="gemini-1.5-pro-latest">Gemini 1.5 Pro [latest]</option> | ||
| 2828 | <option value="gemini-1.5-pro-001">Gemini 1.5 Pro [001]</option> | ||
| 2829 | <option value="gemini-1.5-flash-latest">Gemini 1.5 Flash [latest]</option> | ||
| 2830 | <option value="gemini-1.5-flash-001">Gemini 1.5 Flash [001]</option> | ||
| 2831 | <option value="gemini-1.0-pro-latest">Gemini 1.0 Pro [latest]</option> | ||
| 2832 | <option value="gemini-1.0-pro-001">Gemini 1.0 Pro (Tuning) [001]</option> | ||
| 2833 | <option value="gemini-1.0-pro-vision-latest">Gemini 1.0 Pro Vision [latest]</option> | ||
| 2812 | </optgroup> | 2834 | </optgroup> |
| 2813 | </select> | 2835 | </select> |
| 2814 | </div> | 2836 | </div> |
| @@ -2845,6 +2867,7 @@ | |||
| 2845 | <option value="mistral-small-2402">mistral-small-2402</option> | 2867 | <option value="mistral-small-2402">mistral-small-2402</option> |
| 2846 | <option value="mistral-medium-2312">mistral-medium-2312</option> | 2868 | <option value="mistral-medium-2312">mistral-medium-2312</option> |
| 2847 | <option value="mistral-large-2402">mistral-large-2402</option> | 2869 | <option value="mistral-large-2402">mistral-large-2402</option> |
| 2870 | <option value="mistral-large-2407">mistral-large-2407</option> | ||
| 2848 | <option value="codestral-2405">codestral-2405</option> | 2871 | <option value="codestral-2405">codestral-2405</option> |
| 2849 | <option value="codestral-mamba-2407">codestral-mamba-2407</option> | 2872 | <option value="codestral-mamba-2407">codestral-mamba-2407</option> |
| 2850 | </optgroup> | 2873 | </optgroup> |
| @@ -2885,7 +2908,20 @@ | |||
| 2885 | </div> | 2908 | </div> |
| 2886 | <h4 data-i18n="Perplexity Model">Perplexity Model</h4> | 2909 | <h4 data-i18n="Perplexity Model">Perplexity Model</h4> |
| 2887 | <select id="model_perplexity_select"> | 2910 | <select id="model_perplexity_select"> |
| 2888 | <optgroup label="Perplexity Models"> | 2911 | <optgroup label="Perplexity Sonar Models"> |
| 2912 | <option value="llama-3.1-sonar-small-128k-online">llama-3.1-sonar-small-128k-online</option> | ||
| 2913 | <option value="llama-3.1-sonar-large-128k-online">llama-3.1-sonar-large-128k-online</option> | ||
| 2914 | <option value="llama-3.1-sonar-huge-128k-online">llama-3.1-sonar-huge-128k-online</option> | ||
| 2915 | </optgroup> | ||
| 2916 | <optgroup label="Perplexity Chat Models"> | ||
| 2917 | <option value="llama-3.1-sonar-small-128k-chat">llama-3.1-sonar-small-128k-chat</option> | ||
| 2918 | <option value="llama-3.1-sonar-large-128k-chat">llama-3.1-sonar-large-128k-chat</option> | ||
| 2919 | </optgroup> | ||
| 2920 | <optgroup label="Open-Source Models"> | ||
| 2921 | <option value="llama-3.1-8b-instruct">llama-3.1-8b-instruct</option> | ||
| 2922 | <option value="llama-3.1-70b-instruct">llama-3.1-70b-instruct</option> | ||
| 2923 | </optgroup> | ||
| 2924 | <optgroup label="Deprecated Models"> | ||
| 2889 | <option value="llama-3-sonar-small-32k-chat">llama-3-sonar-small-32k-chat</option> | 2925 | <option value="llama-3-sonar-small-32k-chat">llama-3-sonar-small-32k-chat</option> |
| 2890 | <option value="llama-3-sonar-small-32k-online">llama-3-sonar-small-32k-online</option> | 2926 | <option value="llama-3-sonar-small-32k-online">llama-3-sonar-small-32k-online</option> |
| 2891 | <option value="llama-3-sonar-large-32k-chat">llama-3-sonar-large-32k-chat</option> | 2927 | <option value="llama-3-sonar-large-32k-chat">llama-3-sonar-large-32k-chat</option> |
| @@ -2894,8 +2930,6 @@ | |||
| 2894 | <option value="sonar-small-online">sonar-small-online</option> | 2930 | <option value="sonar-small-online">sonar-small-online</option> |
| 2895 | <option value="sonar-medium-chat">sonar-medium-chat</option> | 2931 | <option value="sonar-medium-chat">sonar-medium-chat</option> |
| 2896 | <option value="sonar-medium-online">sonar-medium-online</option> | 2932 | <option value="sonar-medium-online">sonar-medium-online</option> |
| 2897 | </optgroup> | ||
| 2898 | <optgroup label="Open-Source Models"> | ||
| 2899 | <option value="llama-3-8b-instruct">llama-3-8b-instruct</option> | 2933 | <option value="llama-3-8b-instruct">llama-3-8b-instruct</option> |
| 2900 | <option value="llama-3-70b-instruct">llama-3-70b-instruct</option> | 2934 | <option value="llama-3-70b-instruct">llama-3-70b-instruct</option> |
| 2901 | <option value="mistral-7b-instruct">mistral-7b-instruct (v0.2)</option> | 2935 | <option value="mistral-7b-instruct">mistral-7b-instruct (v0.2)</option> |
| @@ -2929,6 +2963,20 @@ | |||
| 2929 | </select> | 2963 | </select> |
| 2930 | </div> | 2964 | </div> |
| 2931 | </form> | 2965 | </form> |
| 2966 | <form id="blockentropy_form" data-source="blockentropy"> | ||
| 2967 | <h4 data-i18n="Block Entropy API Key">Block Entropy API Key</h4> | ||
| 2968 | <div class="flex-container"> | ||
| 2969 | <input id="api_key_blockentropy" name="api_key_blockentropy" class="text_pole flex1" maxlength="500" value="" type="text" autocomplete="off"> | ||
| 2970 | <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> | ||
| 2971 | </div> | ||
| 2972 | <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."> | ||
| 2973 | For privacy reasons, your API key will be hidden after you reload the page. | ||
| 2974 | </div> | ||
| 2975 | <h4 data-i18n="Select a Model">Select a Model</h4> | ||
| 2976 | <div class="flex-container"> | ||
| 2977 | <select id="model_blockentropy_select" class="text_pole"></select> | ||
| 2978 | </div> | ||
| 2979 | </form> | ||
| 2932 | <form id="custom_form" data-source="custom"> | 2980 | <form id="custom_form" data-source="custom"> |
| 2933 | <h4 data-i18n="Custom Endpoint (Base URL)">Custom Endpoint (Base URL)</h4> | 2981 | <h4 data-i18n="Custom Endpoint (Base URL)">Custom Endpoint (Base URL)</h4> |
| 2934 | <div class="flex-container"> | 2982 | <div class="flex-container"> |
| @@ -3060,7 +3108,11 @@ | |||
| 3060 | </div> | 3108 | </div> |
| 3061 | <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."> | 3109 | <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."> |
| 3062 | <input id="context_use_stop_strings" type="checkbox" /> | 3110 | <input id="context_use_stop_strings" type="checkbox" /> |
| 3063 | <small data-i18n="Use as Stop Strings">Use as Stop Strings</small> | 3111 | <small data-i18n="Separators as Stop Strings">Separators as Stop Strings</small> |
| 3112 | </label> | ||
| 3113 | <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."> | ||
| 3114 | <input id="context_names_as_stop_strings" type="checkbox" /> | ||
| 3115 | <small data-i18n="Names as Stop Strings">Names as Stop Strings</small> | ||
| 3064 | </label> | 3116 | </label> |
| 3065 | <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"> | 3117 | <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"> |
| 3066 | <input id="context_allow_jailbreak" type="checkbox" /> | 3118 | <input id="context_allow_jailbreak" type="checkbox" /> |
| @@ -3288,6 +3340,24 @@ | |||
| 3288 | </div> | 3340 | </div> |
| 3289 | </div> | 3341 | </div> |
| 3290 | <div class="flex-container"> | 3342 | <div class="flex-container"> |
| 3343 | <div class="flex1" title="Inserted before the first User's message." data-i18n="[title]Inserted before the first User's message."> | ||
| 3344 | <label for="instruct_first_input_sequence"> | ||
| 3345 | <small data-i18n="First User Prefix">First User Prefix</small> | ||
| 3346 | </label> | ||
| 3347 | <div> | ||
| 3348 | <textarea id="instruct_first_input_sequence" class="text_pole textarea_compact autoSetHeight" maxlength="2000" placeholder="—" rows="1"></textarea> | ||
| 3349 | </div> | ||
| 3350 | </div> | ||
| 3351 | <div class="flex1" title="Inserted before the last User's message." data-i18n="[title]instruct_last_input_sequence"> | ||
| 3352 | <label for="instruct_last_input_sequence"> | ||
| 3353 | <small data-i18n="Last User Prefix">Last User Prefix</small> | ||
| 3354 | </label> | ||
| 3355 | <div> | ||
| 3356 | <textarea id="instruct_last_input_sequence" class="text_pole wide100p textarea_compact autoSetHeight" maxlength="2000" placeholder="—" rows="1"></textarea> | ||
| 3357 | </div> | ||
| 3358 | </div> | ||
| 3359 | </div> | ||
| 3360 | <div class="flex-container"> | ||
| 3291 | <div class="flex1" title="Will be inserted as a last prompt line when using system/neutral generation." data-i18n="[title]Will be inserted as a last prompt line when using system/neutral generation."> | 3361 | <div class="flex1" title="Will be inserted as a last prompt line when using system/neutral generation." data-i18n="[title]Will be inserted as a last prompt line when using system/neutral generation."> |
| 3292 | <label for="instruct_last_system_sequence"> | 3362 | <label for="instruct_last_system_sequence"> |
| 3293 | <small data-i18n="System Instruction Prefix">System Instruction Prefix</small> | 3363 | <small data-i18n="System Instruction Prefix">System Instruction Prefix</small> |
| @@ -3379,6 +3449,7 @@ | |||
| 3379 | <!-- Option #2 was a legacy GPT-2/3 tokenizer --> | 3449 | <!-- Option #2 was a legacy GPT-2/3 tokenizer --> |
| 3380 | <option value="3">Llama 1/2</option> | 3450 | <option value="3">Llama 1/2</option> |
| 3381 | <option value="12">Llama 3</option> | 3451 | <option value="12">Llama 3</option> |
| 3452 | <option value="13">Gemma / Gemini</option> | ||
| 3382 | <option value="4">NerdStash (NovelAI Clio)</option> | 3453 | <option value="4">NerdStash (NovelAI Clio)</option> |
| 3383 | <option value="5">NerdStash v2 (NovelAI Kayra)</option> | 3454 | <option value="5">NerdStash v2 (NovelAI Kayra)</option> |
| 3384 | <option value="7">Mistral</option> | 3455 | <option value="7">Mistral</option> |
| @@ -3635,8 +3706,8 @@ | |||
| 3635 | <div id="OpenAllWIEntries" class="menu_button fa-solid fa-expand" title="Open all Entries" data-i18n="[title]Open all Entries"></div> | 3706 | <div id="OpenAllWIEntries" class="menu_button fa-solid fa-expand" title="Open all Entries" data-i18n="[title]Open all Entries"></div> |
| 3636 | <div id="CloseAllWIEntries" class="menu_button fa-solid fa-compress" title="Close all Entries" data-i18n="[title]Close all Entries"></div> | 3707 | <div id="CloseAllWIEntries" class="menu_button fa-solid fa-compress" title="Close all Entries" data-i18n="[title]Close all Entries"></div> |
| 3637 | <div id="world_popup_new" class="menu_button fa-solid fa-plus" title="New Entry" data-i18n="[title]New Entry"></div> | 3708 | <div id="world_popup_new" class="menu_button fa-solid fa-plus" title="New Entry" data-i18n="[title]New Entry"></div> |
| 3638 | <div id="world_backfill_memos" class="menu_button fa-solid fa-notes-medical" title="Fill empty Memo/Titles with Keywords" data-i18n="[title]Fill empty Memo/Titles with Keywords"></div><div id="world_apply_custom_sorting" class="menu_button fa-solid fa-solid fa-arrow-down-9-1" | 3709 | <div id="world_backfill_memos" class="menu_button fa-solid fa-notes-medical" title="Fill empty Memo/Titles with Keywords" data-i18n="[title]Fill empty Memo/Titles with Keywords"></div> |
| 3639 | title="Apply custom sorting as Order" data-i18n="[title]Apply custom sorting as Order"></div> | 3710 | <div id="world_apply_current_sorting" class="menu_button fa-solid fa-solid fa-arrow-down-9-1" title="Apply current sorting as Order" data-i18n="[title]Apply current sorting as Order"></div> |
| 3640 | <div id="world_import_button" class="menu_button fa-solid fa-file-import" title="Import World Info" data-i18n="[title]Import World Info"></div> | 3711 | <div id="world_import_button" class="menu_button fa-solid fa-file-import" title="Import World Info" data-i18n="[title]Import World Info"></div> |
| 3641 | <div id="world_popup_export" class="menu_button fa-solid fa-file-export" title="Export World Info" data-i18n="[title]Export World Info"></div> | 3712 | <div id="world_popup_export" class="menu_button fa-solid fa-file-export" title="Export World Info" data-i18n="[title]Export World Info"></div> |
| 3642 | <div id="world_duplicate" class="menu_button fa-solid fa-paste" title="Duplicate World Info" data-i18n="[title]Duplicate World Info"></div> | 3713 | <div id="world_duplicate" class="menu_button fa-solid fa-paste" title="Duplicate World Info" data-i18n="[title]Duplicate World Info"></div> |
| @@ -3999,7 +4070,7 @@ | |||
| 3999 | <input type="range" id="smooth_streaming_speed" name="smooth_streaming_speed" min="0" max="100" step="10" value="50"> | 4070 | <input type="range" id="smooth_streaming_speed" name="smooth_streaming_speed" min="0" max="100" step="10" value="50"> |
| 4000 | <div class="slider_hint"> | 4071 | <div class="slider_hint"> |
| 4001 | <span data-i18n="Slow">Slow</span> | 4072 | <span data-i18n="Slow">Slow</span> |
| 4002 | <span data-i18n=""></span> | 4073 | <span></span> |
| 4003 | <span data-i18n="Fast">Fast</span> | 4074 | <span data-i18n="Fast">Fast</span> |
| 4004 | </div> | 4075 | </div> |
| 4005 | </div> | 4076 | </div> |
| @@ -4031,21 +4102,23 @@ | |||
| 4031 | <input id="restore_user_input" type="checkbox" /> | 4102 | <input id="restore_user_input" type="checkbox" /> |
| 4032 | <small data-i18n="Restore User Input">Restore User Input</small> | 4103 | <small data-i18n="Restore User Input">Restore User Input</small> |
| 4033 | </label> | 4104 | </label> |
| 4034 | <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"> | 4105 | <div class="flex-container alignItemsCenter"> |
| 4035 | <input id="movingUImode" type="checkbox" /> | 4106 | <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"> |
| 4036 | <small data-i18n="Movable UI Panels">MovingUI <i class="fa-solid fa-desktop"></i></small> | 4107 | <input id="movingUImode" type="checkbox" /> |
| 4037 | </label> | 4108 | <small data-i18n="Movable UI Panels">MovingUI <i class="fa-solid fa-desktop"></i></small> |
| 4109 | </label> | ||
| 4110 | <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> | ||
| 4111 | </div> | ||
| 4038 | <div data-newbie-hidden id="MovingUI-presets-block" class="flex-container alignitemscenter"> | 4112 | <div data-newbie-hidden id="MovingUI-presets-block" class="flex-container alignitemscenter"> |
| 4039 | <div class="flex-container alignItemsFlexEnd"> | 4113 | <div class="flex-container alignItemsFlexEnd"> |
| 4040 | <label for="movingUIPresets" title="MovingUI preset. Predefined/saved draggable positions." data-i18n="[title]MovingUI preset. Predefined/saved draggable positions"> | 4114 | <label for="movingUIPresets" title="MovingUI preset. Predefined/saved draggable positions." data-i18n="[title]MovingUI preset. Predefined/saved draggable positions"> |
| 4041 | <small data-i18n="MUI Preset">MUI Preset:</small> | 4115 | <small data-i18n="MUI Preset">MovingUI Preset:</small> |
| 4042 | <div class="flex-container flexnowrap"> | 4116 | <div class="flex-container flexnowrap"> |
| 4043 | <select id="movingUIPresets" class="widthNatural flex1 margin0"> | 4117 | <select id="movingUIPresets" class="widthNatural flex1 margin0"> |
| 4044 | </select> | 4118 | </select> |
| 4045 | </div> | 4119 | </div> |
| 4046 | </label> | 4120 | </label> |
| 4047 | <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> | 4121 | <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> |
| 4048 | <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> | ||
| 4049 | </div> | 4122 | </div> |
| 4050 | </div> | 4123 | </div> |
| 4051 | <div data-newbie-hidden id="CustomCSS-block" class="flex-container flexFlowColumn"> | 4124 | <div data-newbie-hidden id="CustomCSS-block" class="flex-container flexFlowColumn"> |
| @@ -4117,6 +4190,12 @@ | |||
| 4117 | Quick "Continue" button | 4190 | Quick "Continue" button |
| 4118 | </small> | 4191 | </small> |
| 4119 | </label> | 4192 | </label> |
| 4193 | <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"> | ||
| 4194 | <input id="quick_impersonate" type="checkbox" /> | ||
| 4195 | <small data-i18n="Quick 'Impersonate' button"> | ||
| 4196 | Quick "Impersonate" button | ||
| 4197 | </small> | ||
| 4198 | </label> | ||
| 4120 | <div class="checkbox-container flex-container"> | 4199 | <div class="checkbox-container flex-container"> |
| 4121 | <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"> | 4200 | <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"> |
| 4122 | <input id="swipes-checkbox" type="checkbox" /> | 4201 | <input id="swipes-checkbox" type="checkbox" /> |
| @@ -4237,6 +4316,16 @@ | |||
| 4237 | </div> | 4316 | </div> |
| 4238 | </div> | 4317 | </div> |
| 4239 | </div> | 4318 | </div> |
| 4319 | <div title="Determines which keys select an item from the AutoComplete suggestions"> | ||
| 4320 | <label data-i18n="Keyboard"> | ||
| 4321 | <small>Keyboard:</small> | ||
| 4322 | </label> | ||
| 4323 | <select id="stscript_autocomplete_select"> | ||
| 4324 | <option value="3" data-i18n="Select with Tab or Enter">Select with Tab or Enter</option> | ||
| 4325 | <option value="1" data-i18n="Select with Tab">Select with Tab</option> | ||
| 4326 | <option value="2" data-i18n="Select with Enter">Select with Enter</option> | ||
| 4327 | </select> | ||
| 4328 | </div> | ||
| 4240 | <div class="flex-container flexFlowColumn gap0" title="Sets the font size of the autocomplete." data-i18n="[title]Sets the font size of the autocomplete."> | 4329 | <div class="flex-container flexFlowColumn gap0" title="Sets the font size of the autocomplete." data-i18n="[title]Sets the font size of the autocomplete."> |
| 4241 | <label for="stscript_autocomplete_font_scale"><small>Font Scale</small></label> | 4330 | <label for="stscript_autocomplete_font_scale"><small>Font Scale</small></label> |
| 4242 | <input class="neo-range-slider" type="range" id="stscript_autocomplete_font_scale" min="0.5" max="2" step="0.01"> | 4331 | <input class="neo-range-slider" type="range" id="stscript_autocomplete_font_scale" min="0.5" max="2" step="0.01"> |
| @@ -4275,7 +4364,7 @@ | |||
| 4275 | <span class="fa-solid fa-circle-question note-link-span"></span> | 4364 | <span class="fa-solid fa-circle-question note-link-span"></span> |
| 4276 | </a> | 4365 | </a> |
| 4277 | </label> | 4366 | </label> |
| 4278 | <label class="checkbox_label" title="Prevents {{getvar::}} {{getglobalvar::}} macros from having literal macro-like values auto-evaluated.
e.g. "{{newline}}" remains as literal string "{{newline}}"

(This is done by internally replacing {{getvar::}} {{getglobalvar::}} macros with scoped variables.)" data-i18n="[title]Prevents {{getvar::}} {{getglobalvar::}} macros from having literal macro-like values auto-evaluated.
e.g. "{{newline}}" remains as literal string "{{newline}}"

(This is done by internally replacing {{getvar::}} {{getglobalvar::}} macros with scoped variables.)"> | 4367 | <label class="checkbox_label" title="Prevents {{getvar::}} {{getglobalvar::}} macros from having literal macro-like values auto-evaluated.
e.g. "{{newline}}" remains as literal string "{{newline}}"

(This is done by internally replacing {{getvar::}} {{getglobalvar::}} macros with scoped variables.)" data-i18n="[title]stscript_parser_flag_replace_getvar_label"> |
| 4279 | <input id="stscript_parser_flag_replace_getvar" type="checkbox" /> | 4368 | <input id="stscript_parser_flag_replace_getvar" type="checkbox" /> |
| 4280 | <span data-i18n="REPLACE_GETVAR"><small>REPLACE_GETVAR</small></span> | 4369 | <span data-i18n="REPLACE_GETVAR"><small>REPLACE_GETVAR</small></span> |
| 4281 | <a href="https://docs.sillytavern.app/usage/st-script/#replace-variable-macros" target="_blank" class="notes-link"> | 4370 | <a href="https://docs.sillytavern.app/usage/st-script/#replace-variable-macros" target="_blank" class="notes-link"> |
| @@ -4578,7 +4667,7 @@ | |||
| 4578 | <div id="favorite_button" class="menu_button fa-solid fa-star" title="Add to Favorites" data-i18n="[title]Add to Favorites"></div> | 4667 | <div id="favorite_button" class="menu_button fa-solid fa-star" title="Add to Favorites" data-i18n="[title]Add to Favorites"></div> |
| 4579 | <input type="hidden" id="fav_checkbox" name="fav" /> | 4668 | <input type="hidden" id="fav_checkbox" name="fav" /> |
| 4580 | <div id="advanced_div" class="menu_button fa-solid fa-book " title="Advanced Definitions" data-i18n="[title]Advanced Definition"></div> | 4669 | <div id="advanced_div" class="menu_button fa-solid fa-book " title="Advanced Definitions" data-i18n="[title]Advanced Definition"></div> |
| 4581 | <div id="world_button" class="menu_button fa-solid fa-globe" title="Character Lore" data-i18n="[title]Character Lore"></div> | 4670 | <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> |
| 4582 | <div class="chat_lorebook_button menu_button fa-solid fa-passport" title="Chat Lore" data-i18n="[title]Chat Lore"></div> | 4671 | <div class="chat_lorebook_button menu_button fa-solid fa-passport" title="Chat Lore" data-i18n="[title]Chat Lore"></div> |
| 4583 | <div id="export_button" class="menu_button fa-solid fa-file-export " title="Export and Download" data-i18n="[title]Export and Download"></div> | 4672 | <div id="export_button" class="menu_button fa-solid fa-file-export " title="Export and Download" data-i18n="[title]Export and Download"></div> |
| 4584 | <!-- <div id="set_chat_scenario" class="menu_button fa-solid fa-scroll" title="Set a chat scenario override"></div> --> | 4673 | <!-- <div id="set_chat_scenario" class="menu_button fa-solid fa-scroll" title="Set a chat scenario override"></div> --> |
| @@ -5235,21 +5324,22 @@ | |||
| 5235 | <div class="world_entry"> | 5324 | <div class="world_entry"> |
| 5236 | <form class="world_entry_form wi-card-entry"> | 5325 | <form class="world_entry_form wi-card-entry"> |
| 5237 | <div class="inline-drawer wide100p"> | 5326 | <div class="inline-drawer wide100p"> |
| 5238 | <div class="inline-drawer-toggle inline-drawer-header gap5px padding0"> | 5327 | <div class="inline-drawer-header gap5px padding0"> |
| 5239 | <span class="drag-handle">☰</span> | 5328 | <span class="drag-handle">☰</span> |
| 5240 | <div class="gap5px world_entry_thin_controls wide100p alignitemscenter"> | 5329 | <div class="gap5px world_entry_thin_controls wide100p alignitemscenter"> |
| 5241 | <div class="fa-fw fa-solid fa-circle-chevron-down inline-drawer-icon down"></div> | 5330 | <div class="inline-drawer-toggle fa-fw fa-solid fa-circle-chevron-down inline-drawer-icon down"></div> |
| 5331 | <div class="fa-solid fa-toggle-on killSwitch" name="entryKillSwitch" title="Toggle entry's active state."></div> | ||
| 5242 | <div class="flex-container alignitemscenter wide100p"> | 5332 | <div class="flex-container alignitemscenter wide100p"> |
| 5333 | |||
| 5243 | <div class="WIEntryTitleAndStatus flex-container flex1 alignitemscenter"> | 5334 | <div class="WIEntryTitleAndStatus flex-container flex1 alignitemscenter"> |
| 5335 | |||
| 5244 | <div class="flex-container flex1"> | 5336 | <div class="flex-container flex1"> |
| 5245 | <textarea class="text_pole" rows="1" name="comment" maxlength="5000" data-i18n="[placeholder]Entry Title/Memo" placeholder="Entry Title/Memo"></textarea> | 5337 | <textarea class="text_pole" rows="1" name="comment" maxlength="5000" data-i18n="[placeholder]Entry Title/Memo" placeholder="Entry Title/Memo"></textarea> |
| 5246 | </div> | 5338 | </div> |
| 5247 | <!-- <span class="world_entry_form_position_value"></span> --> | ||
| 5248 | <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"> | 5339 | <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"> |
| 5249 | <option value="constant" title="Constant" data-i18n="[title]WI_Entry_Status_Constant">🔵</option> | 5340 | <option value="constant" title="Constant" data-i18n="[title]WI_Entry_Status_Constant">🔵</option> |
| 5250 | <option value="normal" title="Normal" data-i18n="[title]WI_Entry_Status_Normal">🟢</option> | 5341 | <option value="normal" title="Normal" data-i18n="[title]WI_Entry_Status_Normal">🟢</option> |
| 5251 | <option value="vectorized" title="Vectorized" data-i18n="[title]WI_Entry_Status_Vectorized">🔗</option> | 5342 | <option value="vectorized" title="Vectorized" data-i18n="[title]WI_Entry_Status_Vectorized">🔗</option> |
| 5252 | <option value="disabled" title="Disabled" data-i18n="[title]WI_Entry_Status_Disabled">❌</option> | ||
| 5253 | </select> | 5343 | </select> |
| 5254 | </div> | 5344 | </div> |
| 5255 | <div class="WIEnteryHeaderControls flex-container"> | 5345 | <div class="WIEnteryHeaderControls flex-container"> |
| @@ -5722,6 +5812,11 @@ | |||
| 5722 | <div title="Caption" class="right_menu_button fa-lg fa-solid fa-envelope-open-text mes_img_caption" data-i18n="[title]Caption"></div> | 5812 | <div title="Caption" class="right_menu_button fa-lg fa-solid fa-envelope-open-text mes_img_caption" data-i18n="[title]Caption"></div> |
| 5723 | <div title="Delete" class="right_menu_button fa-lg fa-solid fa-trash-can mes_img_delete" data-i18n="[title]Delete"></div> | 5813 | <div title="Delete" class="right_menu_button fa-lg fa-solid fa-trash-can mes_img_delete" data-i18n="[title]Delete"></div> |
| 5724 | </div> | 5814 | </div> |
| 5815 | <div class="mes_img_swipes"> | ||
| 5816 | <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> | ||
| 5817 | <div class="mes_img_swipe_counter">1/1</div> | ||
| 5818 | <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> | ||
| 5819 | </div> | ||
| 5725 | <img class="mes_img" src="" /> | 5820 | <img class="mes_img" src="" /> |
| 5726 | </div> | 5821 | </div> |
| 5727 | <div class="mes_bias"></div> | 5822 | <div class="mes_bias"></div> |
| @@ -5784,6 +5879,7 @@ | |||
| 5784 | Enable simple UI mode | 5879 | Enable simple UI mode |
| 5785 | </span> | 5880 | </span> |
| 5786 | </label> | 5881 | </label> |
| 5882 | <div class="expander"></div> | ||
| 5787 | <div class="textAlignCenter"> | 5883 | <div class="textAlignCenter"> |
| 5788 | <h3 data-i18n="Looking for AI characters?"> | 5884 | <h3 data-i18n="Looking for AI characters?"> |
| 5789 | Looking for AI characters? | 5885 | Looking for AI characters? |
| @@ -5802,6 +5898,7 @@ | |||
| 5802 | </span> | 5898 | </span> |
| 5803 | </span> | 5899 | </span> |
| 5804 | </div> | 5900 | </div> |
| 5901 | <div class="expander"></div> | ||
| 5805 | <h3 data-i18n="Your Persona"> | 5902 | <h3 data-i18n="Your Persona"> |
| 5806 | Your Persona | 5903 | Your Persona |
| 5807 | </h3> | 5904 | </h3> |
| @@ -6315,6 +6412,7 @@ | |||
| 6315 | <div id="mes_stop" title="Abort request" class="mes_stop" data-i18n="[title]Abort request"> | 6412 | <div id="mes_stop" title="Abort request" class="mes_stop" data-i18n="[title]Abort request"> |
| 6316 | <i class="fa-solid fa-circle-stop"></i> | 6413 | <i class="fa-solid fa-circle-stop"></i> |
| 6317 | </div> | 6414 | </div> |
| 6415 | <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> | ||
| 6318 | <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> | 6416 | <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> |
| 6319 | <div id="send_but" class="fa-solid fa-paper-plane interactable displayNone" title="Send a message" data-i18n="[title]Send a message"></div> | 6417 | <div id="send_but" class="fa-solid fa-paper-plane interactable displayNone" title="Send a message" data-i18n="[title]Send a message"></div> |
| 6320 | </div> | 6418 | </div> |
| @@ -6431,6 +6529,9 @@ | |||
| 6431 | <button class="menu_button set_default_persona" title="Select this as default persona for the new chats." data-i18n="[title]Select this as default persona for the new chats."> | 6529 | <button class="menu_button set_default_persona" title="Select this as default persona for the new chats." data-i18n="[title]Select this as default persona for the new chats."> |
| 6432 | <i class="fa-fw fa-solid fa-crown"></i> | 6530 | <i class="fa-fw fa-solid fa-crown"></i> |
| 6433 | </button> | 6531 | </button> |
| 6532 | <button class="menu_button duplicate_persona" title="Duplicate persona" data-i18n="[title]Duplicate persona"> | ||
| 6533 | <i class="fa-fw fa-solid fa-clone"></i> | ||
| 6534 | </button> | ||
| 6434 | <button class="menu_button delete_avatar" title="Delete persona" data-i18n="[title]Delete persona"> | 6535 | <button class="menu_button delete_avatar" title="Delete persona" data-i18n="[title]Delete persona"> |
| 6435 | <i class="fa-fw fa-solid fa-trash-alt"></i> | 6536 | <i class="fa-fw fa-solid fa-trash-alt"></i> |
| 6436 | </button> | 6537 | </button> |
| @@ -207,7 +207,7 @@ | |||
| 207 | "JSON Schema": "مخطط جيسون", | 207 | "JSON Schema": "مخطط جيسون", |
| 208 | "Type in the desired JSON schema": "اكتب مخطط JSON المطلوب", | 208 | "Type in the desired JSON schema": "اكتب مخطط JSON المطلوب", |
| 209 | "Grammar String": "سلسلة القواعد", | 209 | "Grammar String": "سلسلة القواعد", |
| 210 | "GNBF or ENBF, depends on the backend in use. If you're using this you should know which.": "يعتمد GNBF أو ENBF على الواجهة الخلفية المستخدمة. إذا كنت تستخدم هذا يجب أن تعرف أي.", | 210 | "GBNF or EBNF, depends on the backend in use. If you're using this you should know which.": "يعتمد GBNF أو EBNF على الواجهة الخلفية المستخدمة. إذا كنت تستخدم هذا يجب أن تعرف أي.", |
| 211 | "Top P & Min P": "أعلى ع وأدنى ص", | 211 | "Top P & Min P": "أعلى ع وأدنى ص", |
| 212 | "Load default order": "تحميل الترتيب الافتراضي", | 212 | "Load default order": "تحميل الترتيب الافتراضي", |
| 213 | "llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "llama.cpp فقط. تحديد ترتيب أخذ العينات. إذا لم يكن وضع Mirostat 0، فسيتم تجاهل ترتيب أخذ العينات.", | 213 | "llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "llama.cpp فقط. تحديد ترتيب أخذ العينات. إذا لم يكن وضع Mirostat 0، فسيتم تجاهل ترتيب أخذ العينات.", |
| @@ -216,7 +216,7 @@ | |||
| 216 | "Character Names Behavior": "سلوك أسماء الشخصيات", | 216 | "Character Names Behavior": "سلوك أسماء الشخصيات", |
| 217 | "Helps the model to associate messages with characters.": "يساعد النموذج على ربط الرسائل بالأحرف.", | 217 | "Helps the model to associate messages with characters.": "يساعد النموذج على ربط الرسائل بالأحرف.", |
| 218 | "None": "لا شيء", | 218 | "None": "لا شيء", |
| 219 | "character_names_none": "باستثناء المجموعات والشخصيات السابقة. بخلاف ذلك، تأكد من تقديم الأسماء في المطالبة.", | 219 | "character_names_default": "باستثناء المجموعات والشخصيات السابقة. بخلاف ذلك، تأكد من تقديم الأسماء في المطالبة.", |
| 220 | "Don't add character names.": "لا تضيف أسماء الشخصيات.", | 220 | "Don't add character names.": "لا تضيف أسماء الشخصيات.", |
| 221 | "Completion": "كائن الإكمال", | 221 | "Completion": "كائن الإكمال", |
| 222 | "character_names_completion": "تنطبق القيود: فقط الحروف الأبجدية اللاتينية والأرقام والشرطات السفلية. لا يعمل مع جميع المصادر، ولا سيما: Claude وMistralAI وGoogle.", | 222 | "character_names_completion": "تنطبق القيود: فقط الحروف الأبجدية اللاتينية والأرقام والشرطات السفلية. لا يعمل مع جميع المصادر، ولا سيما: Claude وMistralAI وGoogle.", |
| @@ -207,7 +207,7 @@ | |||
| 207 | "JSON Schema": "JSON-Schema", | 207 | "JSON Schema": "JSON-Schema", |
| 208 | "Type in the desired JSON schema": "Geben Sie das gewünschte JSON-Schema ein", | 208 | "Type in the desired JSON schema": "Geben Sie das gewünschte JSON-Schema ein", |
| 209 | "Grammar String": "Grammatikzeichenfolge", | 209 | "Grammar String": "Grammatikzeichenfolge", |
| 210 | "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.", | 210 | "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.", |
| 211 | "Top P & Min P": "Top P und Min P", | 211 | "Top P & Min P": "Top P und Min P", |
| 212 | "Load default order": "Standardreihenfolge laden", | 212 | "Load default order": "Standardreihenfolge laden", |
| 213 | "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.", | 213 | "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.", |
| @@ -216,7 +216,7 @@ | |||
| 216 | "Character Names Behavior": "Charakternamen Verhalten", | 216 | "Character Names Behavior": "Charakternamen Verhalten", |
| 217 | "Helps the model to associate messages with characters.": "Hilft dem Modell, Nachrichten mit Zeichen zu verknüpfen.", | 217 | "Helps the model to associate messages with characters.": "Hilft dem Modell, Nachrichten mit Zeichen zu verknüpfen.", |
| 218 | "None": "Keins", | 218 | "None": "Keins", |
| 219 | "character_names_none": "Außer für Gruppen und frühere Personas. Andernfalls stellen Sie sicher, dass Sie in der Eingabeaufforderung Namen angeben.", | 219 | "character_names_default": "Außer für Gruppen und frühere Personas. Andernfalls stellen Sie sicher, dass Sie in der Eingabeaufforderung Namen angeben.", |
| 220 | "Don't add character names.": "Fügen Sie keine Charakternamen hinzu.", | 220 | "Don't add character names.": "Fügen Sie keine Charakternamen hinzu.", |
| 221 | "Completion": "Vervollständigungsobjekt", | 221 | "Completion": "Vervollständigungsobjekt", |
| 222 | "character_names_completion": "Es gelten Einschränkungen: nur lateinische alphanumerische Zeichen und Unterstriche. Funktioniert nicht für alle Quellen, insbesondere: Claude, MistralAI, Google.", | 222 | "character_names_completion": "Es gelten Einschränkungen: nur lateinische alphanumerische Zeichen und Unterstriche. Funktioniert nicht für alle Quellen, insbesondere: Claude, MistralAI, Google.", |
| @@ -207,7 +207,7 @@ | |||
| 207 | "JSON Schema": "Esquema JSON", | 207 | "JSON Schema": "Esquema JSON", |
| 208 | "Type in the desired JSON schema": "Escriba el esquema JSON deseado", | 208 | "Type in the desired JSON schema": "Escriba el esquema JSON deseado", |
| 209 | "Grammar String": "Cadena de gramática", | 209 | "Grammar String": "Cadena de gramática", |
| 210 | "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.", | 210 | "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.", |
| 211 | "Top P & Min P": "P superior y P mínima", | 211 | "Top P & Min P": "P superior y P mínima", |
| 212 | "Load default order": "Cargar orden predeterminado", | 212 | "Load default order": "Cargar orden predeterminado", |
| 213 | "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.", | 213 | "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.", |
| @@ -216,7 +216,7 @@ | |||
| 216 | "Character Names Behavior": "Comportamiento de los nombres de personajes", | 216 | "Character Names Behavior": "Comportamiento de los nombres de personajes", |
| 217 | "Helps the model to associate messages with characters.": "Ayuda al modelo a asociar mensajes con personajes.", | 217 | "Helps the model to associate messages with characters.": "Ayuda al modelo a asociar mensajes con personajes.", |
| 218 | "None": "Ninguno", | 218 | "None": "Ninguno", |
| 219 | "character_names_none": "Excepto grupos y personas pasadas. De lo contrario, asegúrese de proporcionar nombres en el mensaje.", | 219 | "character_names_default": "Excepto grupos y personas pasadas. De lo contrario, asegúrese de proporcionar nombres en el mensaje.", |
| 220 | "Don't add character names.": "No agregues nombres de personajes.", | 220 | "Don't add character names.": "No agregues nombres de personajes.", |
| 221 | "Completion": "Objeto de finalización", | 221 | "Completion": "Objeto de finalización", |
| 222 | "character_names_completion": "Aplican restricciones: solo caracteres alfanuméricos latinos y guiones bajos. No funciona para todas las fuentes, en particular: Claude, MistralAI, Google.", | 222 | "character_names_completion": "Aplican restricciones: solo caracteres alfanuméricos latinos y guiones bajos. No funciona para todas las fuentes, en particular: Claude, MistralAI, Google.", |
| @@ -207,7 +207,7 @@ | |||
| 207 | "JSON Schema": "Schéma JSON", | 207 | "JSON Schema": "Schéma JSON", |
| 208 | "Type in the desired JSON schema": "Tapez le schéma JSON souhaité", | 208 | "Type in the desired JSON schema": "Tapez le schéma JSON souhaité", |
| 209 | "Grammar String": "Chaîne de grammaire", | 209 | "Grammar String": "Chaîne de grammaire", |
| 210 | "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.", | 210 | "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.", |
| 211 | "Top P & Min P": "P supérieur et P minimal", | 211 | "Top P & Min P": "P supérieur et P minimal", |
| 212 | "Load default order": "Charger l'ordre par défaut", | 212 | "Load default order": "Charger l'ordre par défaut", |
| 213 | "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é.", | 213 | "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é.", |
| @@ -216,7 +216,7 @@ | |||
| 216 | "Character Names Behavior": "Comportement des noms de personnages", | 216 | "Character Names Behavior": "Comportement des noms de personnages", |
| 217 | "Helps the model to associate messages with characters.": "Aide le modèle à associer des messages à des personnages.", | 217 | "Helps the model to associate messages with characters.": "Aide le modèle à associer des messages à des personnages.", |
| 218 | "None": "Aucun", | 218 | "None": "Aucun", |
| 219 | "character_names_none": "Sauf pour les groupes et les personnages passés. Sinon, assurez-vous de fournir des noms dans l'invite.", | 219 | "character_names_default": "Sauf pour les groupes et les personnages passés. Sinon, assurez-vous de fournir des noms dans l'invite.", |
| 220 | "Don't add character names.": "N'ajoutez pas de noms de personnages.", | 220 | "Don't add character names.": "N'ajoutez pas de noms de personnages.", |
| 221 | "Completion": "Objet d'achèvement", | 221 | "Completion": "Objet d'achèvement", |
| 222 | "character_names_completion": "Des restrictions s'appliquent : uniquement les caractères alphanumériques latins et les traits de soulignement. Ne fonctionne pas pour toutes les sources, notamment : Claude, MistralAI, Google.", | 222 | "character_names_completion": "Des restrictions s'appliquent : uniquement les caractères alphanumériques latins et les traits de soulignement. Ne fonctionne pas pour toutes les sources, notamment : Claude, MistralAI, Google.", |
| @@ -207,7 +207,7 @@ | |||
| 207 | "JSON Schema": "JSON kerfi", | 207 | "JSON Schema": "JSON kerfi", |
| 208 | "Type in the desired JSON schema": "Sláðu inn æskilegt JSON skema", | 208 | "Type in the desired JSON schema": "Sláðu inn æskilegt JSON skema", |
| 209 | "Grammar String": "Málfræðistrengur", | 209 | "Grammar String": "Málfræðistrengur", |
| 210 | "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.", | 210 | "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.", |
| 211 | "Top P & Min P": "Efstu P & Min P", | 211 | "Top P & Min P": "Efstu P & Min P", |
| 212 | "Load default order": "Hlaða sjálfgefna röð", | 212 | "Load default order": "Hlaða sjálfgefna röð", |
| 213 | "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ð.", | 213 | "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ð.", |
| @@ -216,7 +216,7 @@ | |||
| 216 | "Character Names Behavior": "Hegðun persónunafna", | 216 | "Character Names Behavior": "Hegðun persónunafna", |
| 217 | "Helps the model to associate messages with characters.": "Hjálpar líkaninu að tengja skilaboð við stafi.", | 217 | "Helps the model to associate messages with characters.": "Hjálpar líkaninu að tengja skilaboð við stafi.", |
| 218 | "None": "Enginn", | 218 | "None": "Enginn", |
| 219 | "character_names_none": "Nema hópar og fyrri persónur. Annars, vertu viss um að gefa upp nöfn í hvetjunni.", | 219 | "character_names_default": "Nema hópar og fyrri persónur. Annars, vertu viss um að gefa upp nöfn í hvetjunni.", |
| 220 | "Don't add character names.": "Ekki bæta við persónunöfnum.", | 220 | "Don't add character names.": "Ekki bæta við persónunöfnum.", |
| 221 | "Completion": "Lokunarhlutur", | 221 | "Completion": "Lokunarhlutur", |
| 222 | "character_names_completion": "Takmarkanir gilda: aðeins latneskar tölustafir og undirstrik. Virkar ekki fyrir allar heimildir, sérstaklega: Claude, MistralAI, Google.", | 222 | "character_names_completion": "Takmarkanir gilda: aðeins latneskar tölustafir og undirstrik. Virkar ekki fyrir allar heimildir, sérstaklega: Claude, MistralAI, Google.", |
| @@ -207,7 +207,7 @@ | |||
| 207 | "JSON Schema": "Schema JSON", | 207 | "JSON Schema": "Schema JSON", |
| 208 | "Type in the desired JSON schema": "Digita lo schema JSON desiderato", | 208 | "Type in the desired JSON schema": "Digita lo schema JSON desiderato", |
| 209 | "Grammar String": "Stringa grammaticale", | 209 | "Grammar String": "Stringa grammaticale", |
| 210 | "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.", | 210 | "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.", |
| 211 | "Top P & Min P": "P massimo e P minimo", | 211 | "Top P & Min P": "P massimo e P minimo", |
| 212 | "Load default order": "Carica ordine predefinito", | 212 | "Load default order": "Carica ordine predefinito", |
| 213 | "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.", | 213 | "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.", |
| @@ -216,7 +216,7 @@ | |||
| 216 | "Character Names Behavior": "Comportamento dei nomi dei personaggi", | 216 | "Character Names Behavior": "Comportamento dei nomi dei personaggi", |
| 217 | "Helps the model to associate messages with characters.": "Aiuta il modello ad associare i messaggi ai personaggi.", | 217 | "Helps the model to associate messages with characters.": "Aiuta il modello ad associare i messaggi ai personaggi.", |
| 218 | "None": "Nessuno", | 218 | "None": "Nessuno", |
| 219 | "character_names_none": "Fatta eccezione per i gruppi e i personaggi passati. Altrimenti, assicurati di fornire i nomi nel prompt.", | 219 | "character_names_default": "Fatta eccezione per i gruppi e i personaggi passati. Altrimenti, assicurati di fornire i nomi nel prompt.", |
| 220 | "Don't add character names.": "Non aggiungere nomi di personaggi.", | 220 | "Don't add character names.": "Non aggiungere nomi di personaggi.", |
| 221 | "Completion": "Oggetto di completamento", | 221 | "Completion": "Oggetto di completamento", |
| 222 | "character_names_completion": "Si applicano restrizioni: solo caratteri alfanumerici latini e trattini bassi. Non funziona con tutte le fonti, in particolare: Claude, MistralAI, Google.", | 222 | "character_names_completion": "Si applicano restrizioni: solo caratteri alfanumerici latini e trattini bassi. Non funziona con tutte le fonti, in particolare: Claude, MistralAI, Google.", |
| @@ -207,7 +207,7 @@ | |||
| 207 | "JSON Schema": "JSONスキーマ", | 207 | "JSON Schema": "JSONスキーマ", |
| 208 | "Type in the desired JSON schema": "希望するJSONスキーマを入力します", | 208 | "Type in the desired JSON schema": "希望するJSONスキーマを入力します", |
| 209 | "Grammar String": "文法文字列", | 209 | "Grammar String": "文法文字列", |
| 210 | "GNBF or ENBF, depends on the backend in use. If you're using this you should know which.": "GNBF または ENBF は、使用するバックエンドによって異なります。これを使用する場合は、どちらであるかを知っておく必要があります。", | 210 | "GBNF or EBNF, depends on the backend in use. If you're using this you should know which.": "GBNF または EBNF は、使用するバックエンドによって異なります。これを使用する場合は、どちらであるかを知っておく必要があります。", |
| 211 | "Top P & Min P": "トップPと最小P", | 211 | "Top P & Min P": "トップPと最小P", |
| 212 | "Load default order": "デフォルトの順序を読み込む", | 212 | "Load default order": "デフォルトの順序を読み込む", |
| 213 | "llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "llama.cpp のみ。サンプラーの順序を決定します。Mirostat モードが 0 でない場合、サンプラーの順序は無視されます。", | 213 | "llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "llama.cpp のみ。サンプラーの順序を決定します。Mirostat モードが 0 でない場合、サンプラーの順序は無視されます。", |
| @@ -216,7 +216,7 @@ | |||
| 216 | "Character Names Behavior": "キャラクター名の動作", | 216 | "Character Names Behavior": "キャラクター名の動作", |
| 217 | "Helps the model to associate messages with characters.": "モデルがメッセージをキャラクターに関連付けるのに役立ちます。", | 217 | "Helps the model to associate messages with characters.": "モデルがメッセージをキャラクターに関連付けるのに役立ちます。", |
| 218 | "None": "なし", | 218 | "None": "なし", |
| 219 | "character_names_none": "グループと過去のペルソナを除きます。それ以外の場合は、プロンプトに名前を必ず入力してください。", | 219 | "character_names_default": "グループと過去のペルソナを除きます。それ以外の場合は、プロンプトに名前を必ず入力してください。", |
| 220 | "Don't add character names.": "キャラクター名を追加しないでください。", | 220 | "Don't add character names.": "キャラクター名を追加しないでください。", |
| 221 | "Completion": "完了オブジェクト", | 221 | "Completion": "完了オブジェクト", |
| 222 | "character_names_completion": "制限事項: ラテン英数字とアンダースコアのみ。すべてのソースで機能するわけではありません。特に、Claude、MistralAI、Google では機能しません。", | 222 | "character_names_completion": "制限事項: ラテン英数字とアンダースコアのみ。すべてのソースで機能するわけではありません。特に、Claude、MistralAI、Google では機能しません。", |
| @@ -207,7 +207,7 @@ | |||
| 207 | "JSON Schema": "JSON 스키마", | 207 | "JSON Schema": "JSON 스키마", |
| 208 | "Type in the desired JSON schema": "원하는 JSON 스키마를 입력하세요.", | 208 | "Type in the desired JSON schema": "원하는 JSON 스키마를 입력하세요.", |
| 209 | "Grammar String": "문법 문자열", | 209 | "Grammar String": "문법 문자열", |
| 210 | "GNBF or ENBF, depends on the backend in use. If you're using this you should know which.": "GNBF 또는 ENBF는 사용 중인 백엔드에 따라 다릅니다. 이것을 사용한다면 어느 것이 무엇인지 알아야합니다.", | 210 | "GBNF or EBNF, depends on the backend in use. If you're using this you should know which.": "GBNF 또는 EBNF는 사용 중인 백엔드에 따라 다릅니다. 이것을 사용한다면 어느 것이 무엇인지 알아야합니다.", |
| 211 | "Top P & Min P": "상위 P 및 최소 P", | 211 | "Top P & Min P": "상위 P 및 최소 P", |
| 212 | "Load default order": "기본 순서로 로드", | 212 | "Load default order": "기본 순서로 로드", |
| 213 | "llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "llama.cpp만 가능합니다. 샘플러의 순서를 결정합니다. Mirostat 모드가 0이 아닌 경우 샘플러 순서는 무시됩니다.", | 213 | "llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "llama.cpp만 가능합니다. 샘플러의 순서를 결정합니다. Mirostat 모드가 0이 아닌 경우 샘플러 순서는 무시됩니다.", |
| @@ -216,7 +216,7 @@ | |||
| 216 | "Character Names Behavior": "캐릭터 이름 행동", | 216 | "Character Names Behavior": "캐릭터 이름 행동", |
| 217 | "Helps the model to associate messages with characters.": "모델이 메시지를 캐릭터와 연관시키는 데 도움이 됩니다.", | 217 | "Helps the model to associate messages with characters.": "모델이 메시지를 캐릭터와 연관시키는 데 도움이 됩니다.", |
| 218 | "None": "없음", | 218 | "None": "없음", |
| 219 | "character_names_none": "그룹 및 과거 페르소나는 제외됩니다. 그렇지 않으면 프롬프트에 이름을 제공해야 합니다.", | 219 | "character_names_default": "그룹 및 과거 페르소나는 제외됩니다. 그렇지 않으면 프롬프트에 이름을 제공해야 합니다.", |
| 220 | "Don't add character names.": "캐릭터 이름을 추가하지 마세요.", | 220 | "Don't add character names.": "캐릭터 이름을 추가하지 마세요.", |
| 221 | "Completion": "완료 객체", | 221 | "Completion": "완료 객체", |
| 222 | "character_names_completion": "제한 사항이 적용됩니다. 라틴 영숫자 및 밑줄만 사용할 수 있습니다. 모든 소스, 특히 Claude, MistralAI, Google에서 작동하지 않습니다.", | 222 | "character_names_completion": "제한 사항이 적용됩니다. 라틴 영숫자 및 밑줄만 사용할 수 있습니다. 모든 소스, 특히 Claude, MistralAI, Google에서 작동하지 않습니다.", |
| @@ -207,7 +207,7 @@ | |||
| 207 | "JSON Schema": "JSON-schema", | 207 | "JSON Schema": "JSON-schema", |
| 208 | "Type in the desired JSON schema": "Typ het gewenste JSON-schema", | 208 | "Type in the desired JSON schema": "Typ het gewenste JSON-schema", |
| 209 | "Grammar String": "Grammaticareeks", | 209 | "Grammar String": "Grammaticareeks", |
| 210 | "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.", | 210 | "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.", |
| 211 | "Top P & Min P": "Top P & Min P", | 211 | "Top P & Min P": "Top P & Min P", |
| 212 | "Load default order": "Standaardvolgorde laden", | 212 | "Load default order": "Standaardvolgorde laden", |
| 213 | "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.", | 213 | "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.", |
| @@ -216,7 +216,7 @@ | |||
| 216 | "Character Names Behavior": "Karakternamen Gedrag", | 216 | "Character Names Behavior": "Karakternamen Gedrag", |
| 217 | "Helps the model to associate messages with characters.": "Helpt het model berichten aan karakters te koppelen.", | 217 | "Helps the model to associate messages with characters.": "Helpt het model berichten aan karakters te koppelen.", |
| 218 | "None": "Geen", | 218 | "None": "Geen", |
| 219 | "character_names_none": "Behalve voor groepen en vroegere persona's. Zorg er anders voor dat u namen opgeeft in de prompt.", | 219 | "character_names_default": "Behalve voor groepen en vroegere persona's. Zorg er anders voor dat u namen opgeeft in de prompt.", |
| 220 | "Don't add character names.": "Voeg geen namen van personages toe.", | 220 | "Don't add character names.": "Voeg geen namen van personages toe.", |
| 221 | "Completion": "Voltooiingsobject", | 221 | "Completion": "Voltooiingsobject", |
| 222 | "character_names_completion": "Er zijn beperkingen van toepassing: alleen Latijnse alfanumerieke tekens en onderstrepingstekens. Werkt niet voor alle bronnen, met name: Claude, MistralAI, Google.", | 222 | "character_names_completion": "Er zijn beperkingen van toepassing: alleen Latijnse alfanumerieke tekens en onderstrepingstekens. Werkt niet voor alle bronnen, met name: Claude, MistralAI, Google.", |
| @@ -207,7 +207,7 @@ | |||
| 207 | "JSON Schema": "Esquema JSON", | 207 | "JSON Schema": "Esquema JSON", |
| 208 | "Type in the desired JSON schema": "Digite o esquema JSON desejado", | 208 | "Type in the desired JSON schema": "Digite o esquema JSON desejado", |
| 209 | "Grammar String": "Cadeia de Gramática", | 209 | "Grammar String": "Cadeia de Gramática", |
| 210 | "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.", | 210 | "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.", |
| 211 | "Top P & Min P": "P superior e P mínimo", | 211 | "Top P & Min P": "P superior e P mínimo", |
| 212 | "Load default order": "Carregar ordem padrão", | 212 | "Load default order": "Carregar ordem padrão", |
| 213 | "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.", | 213 | "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.", |
| @@ -216,7 +216,7 @@ | |||
| 216 | "Character Names Behavior": "Comportamento dos nomes dos personagens", | 216 | "Character Names Behavior": "Comportamento dos nomes dos personagens", |
| 217 | "Helps the model to associate messages with characters.": "Ajuda o modelo a associar mensagens a personagens.", | 217 | "Helps the model to associate messages with characters.": "Ajuda o modelo a associar mensagens a personagens.", |
| 218 | "None": "Nenhum", | 218 | "None": "Nenhum", |
| 219 | "character_names_none": "Exceto para grupos e personas passadas. Caso contrário, certifique-se de fornecer nomes no prompt.", | 219 | "character_names_default": "Exceto para grupos e personas passadas. Caso contrário, certifique-se de fornecer nomes no prompt.", |
| 220 | "Don't add character names.": "Não adicione nomes de personagens.", | 220 | "Don't add character names.": "Não adicione nomes de personagens.", |
| 221 | "Completion": "Objeto de conclusão", | 221 | "Completion": "Objeto de conclusão", |
| 222 | "character_names_completion": "Aplicam-se restrições: apenas alfanuméricos latinos e sublinhados. Não funciona para todas as fontes, nomeadamente: Claude, MistralAI, Google.", | 222 | "character_names_completion": "Aplicam-se restrições: apenas alfanuméricos latinos e sublinhados. Não funciona para todas as fontes, nomeadamente: Claude, MistralAI, Google.", |
| @@ -978,7 +978,7 @@ | |||
| 978 | "char_import_6": "Прямая ссылка на PNG-файл (чтобы узнать список разрешённых хостов, загляните в", | 978 | "char_import_6": "Прямая ссылка на PNG-файл (чтобы узнать список разрешённых хостов, загляните в", |
| 979 | "char_import_7": ")", | 979 | "char_import_7": ")", |
| 980 | "Grammar String": "Грамматика", | 980 | "Grammar String": "Грамматика", |
| 981 | "GNBF or ENBF, depends on the backend in use. If you're using this you should know which.": "GNBF или ENBF, зависит от бэкенда. Если вы это используете, то, скорее всего, сами знаете, какой именно.", | 981 | "GBNF or EBNF, depends on the backend in use. If you're using this you should know which.": "GBNF или EBNF, зависит от бэкенда. Если вы это используете, то, скорее всего, сами знаете, какой именно.", |
| 982 | "Account": "Аккаунт", | 982 | "Account": "Аккаунт", |
| 983 | "Hi,": "Привет,", | 983 | "Hi,": "Привет,", |
| 984 | "To enable multi-account features, restart the SillyTavern server with": "Чтобы активировать систему аккаунтов, перезапустите SillyTavern, выставив", | 984 | "To enable multi-account features, restart the SillyTavern server with": "Чтобы активировать систему аккаунтов, перезапустите SillyTavern, выставив", |
| @@ -1246,7 +1246,7 @@ | |||
| 1246 | "Top P & Min P": "Top P & Min P", | 1246 | "Top P & Min P": "Top P & Min P", |
| 1247 | "llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.", | 1247 | "llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.", |
| 1248 | "Helps the model to associate messages with characters.": "Помогает модели связывать сообщения с персонажами.", | 1248 | "Helps the model to associate messages with characters.": "Помогает модели связывать сообщения с персонажами.", |
| 1249 | "character_names_none": "Except for groups and past personas. Otherwise, make sure you provide names in the prompt.", | 1249 | "character_names_default": "Except for groups and past personas. Otherwise, make sure you provide names in the prompt.", |
| 1250 | "Completion": "Completion Object", | 1250 | "Completion": "Completion Object", |
| 1251 | "character_names_completion": "Только латинские буквы, цифры и знак подчёркивания. Работает не для всех бэкендов, в частности для Claude, MistralAI, Google.", | 1251 | "character_names_completion": "Только латинские буквы, цифры и знак подчёркивания. Работает не для всех бэкендов, в частности для Claude, MistralAI, Google.", |
| 1252 | "Use AI21 Tokenizer": "Использовать токенайзер AI21", | 1252 | "Use AI21 Tokenizer": "Использовать токенайзер AI21", |
| @@ -1662,4 +1662,4 @@ | |||
| 1662 | "ext_regex_sts_desc": "Сообщения, отправленные с помощью команд STscript", | 1662 | "ext_regex_sts_desc": "Сообщения, отправленные с помощью команд STscript", |
| 1663 | "ext_regex_wi_desc": "Содержимое лорбуков и миров. Для работы требует включения флажка \"Только промпт\"!", | 1663 | "ext_regex_wi_desc": "Содержимое лорбуков и миров. Для работы требует включения флажка \"Только промпт\"!", |
| 1664 | "ext_regex_only_format_display_desc": "История чата не изменится, замена будет осуществляться только в отображаемом сообщении (в UI)" | 1664 | "ext_regex_only_format_display_desc": "История чата не изменится, замена будет осуществляться только в отображаемом сообщении (в UI)" |
| 1665 | } | ||
| 1665 | \ No newline at end of file | \ No newline at end of file | |
| 1665 | } | ||
| @@ -207,7 +207,7 @@ | |||
| 207 | "JSON Schema": "Схема JSON", | 207 | "JSON Schema": "Схема JSON", |
| 208 | "Type in the desired JSON schema": "Введіть потрібну схему JSON", | 208 | "Type in the desired JSON schema": "Введіть потрібну схему JSON", |
| 209 | "Grammar String": "Граматичний рядок", | 209 | "Grammar String": "Граматичний рядок", |
| 210 | "GNBF or ENBF, depends on the backend in use. If you're using this you should know which.": "GNBF або ENBF, залежить від серверної частини, яка використовується. Якщо ви використовуєте це, ви повинні знати, який.", | 210 | "GBNF or EBNF, depends on the backend in use. If you're using this you should know which.": "GBNF або EBNF, залежить від серверної частини, яка використовується. Якщо ви використовуєте це, ви повинні знати, який.", |
| 211 | "Top P & Min P": "Верхній P & Min P", | 211 | "Top P & Min P": "Верхній P & Min P", |
| 212 | "Load default order": "Завантажити типовий порядок", | 212 | "Load default order": "Завантажити типовий порядок", |
| 213 | "llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "лише llama.cpp. Визначає порядок пробовідбірників. Якщо режим Mirostat не 0, порядок вибірки ігнорується.", | 213 | "llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "лише llama.cpp. Визначає порядок пробовідбірників. Якщо режим Mirostat не 0, порядок вибірки ігнорується.", |
| @@ -216,7 +216,7 @@ | |||
| 216 | "Character Names Behavior": "Поведінка імен персонажів", | 216 | "Character Names Behavior": "Поведінка імен персонажів", |
| 217 | "Helps the model to associate messages with characters.": "Допомагає моделі пов’язувати повідомлення з символами.", | 217 | "Helps the model to associate messages with characters.": "Допомагає моделі пов’язувати повідомлення з символами.", |
| 218 | "None": "Немає", | 218 | "None": "Немає", |
| 219 | "character_names_none": "За винятком груп і минулих персонажів. В іншому випадку переконайтеся, що ви вказали імена в підказці.", | 219 | "character_names_default": "За винятком груп і минулих персонажів. В іншому випадку переконайтеся, що ви вказали імена в підказці.", |
| 220 | "Don't add character names.": "Не додавайте імена персонажів.", | 220 | "Don't add character names.": "Не додавайте імена персонажів.", |
| 221 | "Completion": "Об'єкт завершення", | 221 | "Completion": "Об'єкт завершення", |
| 222 | "character_names_completion": "Застосовуються обмеження: лише латинські букви та цифри підкреслення. Працює не для всіх джерел, зокрема: Claude, MistralAI, Google.", | 222 | "character_names_completion": "Застосовуються обмеження: лише латинські букви та цифри підкреслення. Працює не для всіх джерел, зокрема: Claude, MistralAI, Google.", |
| @@ -207,7 +207,7 @@ | |||
| 207 | "JSON Schema": "Lược đồ JSON", | 207 | "JSON Schema": "Lược đồ JSON", |
| 208 | "Type in the desired JSON schema": "Nhập lược đồ JSON mong muốn", | 208 | "Type in the desired JSON schema": "Nhập lược đồ JSON mong muốn", |
| 209 | "Grammar String": "Chuỗi ngữ pháp", | 209 | "Grammar String": "Chuỗi ngữ pháp", |
| 210 | "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.", | 210 | "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.", |
| 211 | "Top P & Min P": "P & P tối thiểu hàng đầu", | 211 | "Top P & Min P": "P & P tối thiểu hàng đầu", |
| 212 | "Load default order": "Tải thứ tự mặc định", | 212 | "Load default order": "Tải thứ tự mặc định", |
| 213 | "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.", | 213 | "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.", |
| @@ -216,7 +216,7 @@ | |||
| 216 | "Character Names Behavior": "Tên nhân vật Hành vi", | 216 | "Character Names Behavior": "Tên nhân vật Hành vi", |
| 217 | "Helps the model to associate messages with characters.": "Giúp mô hình liên kết tin nhắn với các ký tự.", | 217 | "Helps the model to associate messages with characters.": "Giúp mô hình liên kết tin nhắn với các ký tự.", |
| 218 | "None": "Không", | 218 | "None": "Không", |
| 219 | "character_names_none": "Ngoại trừ các nhóm và cá tính trong quá khứ. Nếu không, hãy đảm bảo bạn cung cấp tên trong lời nhắc.", | 219 | "character_names_default": "Ngoại trừ các nhóm và cá tính trong quá khứ. Nếu không, hãy đảm bảo bạn cung cấp tên trong lời nhắc.", |
| 220 | "Don't add character names.": "Không thêm tên nhân vật.", | 220 | "Don't add character names.": "Không thêm tên nhân vật.", |
| 221 | "Completion": "Đối tượng hoàn thành", | 221 | "Completion": "Đối tượng hoàn thành", |
| 222 | "character_names_completion": "Áp dụng hạn chế: chỉ chữ và số Latinh và dấu gạch dưới. Không hoạt động với tất cả các nguồn, đặc biệt là: Claude, MistralAI, Google.", | 222 | "character_names_completion": "Áp dụng hạn chế: chỉ chữ và số Latinh và dấu gạch dưới. Không hoạt động với tất cả các nguồn, đặc biệt là: Claude, MistralAI, Google.", |
| @@ -69,8 +69,8 @@ | |||
| 69 | "Top A": "Top A", | 69 | "Top A": "Top A", |
| 70 | "Quick Prompts Edit": "快速提示词编辑", | 70 | "Quick Prompts Edit": "快速提示词编辑", |
| 71 | "Main": "主要", | 71 | "Main": "主要", |
| 72 | "NSFW": "NSFW", | 72 | "Auxiliary": "辅助的", |
| 73 | "Jailbreak": "越狱", | 73 | "Post-History Instructions": "后续历史指令", |
| 74 | "Utility Prompts": "实用提示词", | 74 | "Utility Prompts": "实用提示词", |
| 75 | "Impersonation prompt": "AI帮答提示词", | 75 | "Impersonation prompt": "AI帮答提示词", |
| 76 | "Restore default prompt": "恢复默认提示词", | 76 | "Restore default prompt": "恢复默认提示词", |
| @@ -208,19 +208,21 @@ | |||
| 208 | "JSON Schema": "JSON 结构", | 208 | "JSON Schema": "JSON 结构", |
| 209 | "Type in the desired JSON schema": "输入所需的 JSON 结构", | 209 | "Type in the desired JSON schema": "输入所需的 JSON 结构", |
| 210 | "Grammar String": "语法字符串", | 210 | "Grammar String": "语法字符串", |
| 211 | "GNBF or ENBF, depends on the backend in use. If you're using this you should know which.": "GNBF 或 ENBF,取决于使用的后端。如果您使用这个,您应该知道该用哪一个。", | 211 | "GBNF or EBNF, depends on the backend in use. If you're using this you should know which.": "GBNF 或 EBNF,取决于使用的后端。如果您使用这个,您应该知道该用哪一个。", |
| 212 | "Top P & Min P": "Top P 和 Min P", | 212 | "Top P & Min P": "Top P 和 Min P", |
| 213 | "Load default order": "加载默认顺序", | 213 | "Load default order": "加载默认顺序", |
| 214 | "Sampler Order": "取样器顺序", | ||
| 214 | "llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "仅限 llama.cpp。确定采样器的顺序。如果 Mirostat 模式不为 0,则忽略采样器顺序。", | 215 | "llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "仅限 llama.cpp。确定采样器的顺序。如果 Mirostat 模式不为 0,则忽略采样器顺序。", |
| 215 | "Sampler Priority": "采样器优先级", | 216 | "Sampler Priority": "采样器优先级", |
| 216 | "Ooba only. Determines the order of samplers.": "确定采样器的顺序(仅适用于Ooba)", | 217 | "Ooba only. Determines the order of samplers.": "确定采样器的顺序(仅适用于Ooba)", |
| 217 | "Character Names Behavior": "角色名称行为", | 218 | "Character Names Behavior": "角色名称行为", |
| 218 | "Helps the model to associate messages with characters.": "有助于模型将消息与角色关联起来。", | 219 | "Helps the model to associate messages with characters.": "有助于模型将消息与角色关联起来。", |
| 219 | "None": "无", | 220 | "None": "无", |
| 220 | "tag_import_none": "无", | 221 | "character_names_none": "不添加角色名称前缀。在群聊中可能导致错误行为,谨慎勾选。", |
| 221 | "character_names_none": "群聊和过去的角色除外。否则,请确保在提示词中提供了姓名。", | 222 | "Never add character names.": "不添加角色名称。", |
| 222 | "Don't add character names.": "不添加角色名称。", | 223 | "character_names_default": "群聊和过去的角色除外。否则,请确保在提示词中提供了姓名。", |
| 223 | "Completion": "补全对象", | 224 | "Don't add character names unless necessary.": "如非必要,否则不添加角色名称。", |
| 225 | "Completion Object": "补全对象", | ||
| 224 | "character_names_completion": "适用限制:仅限拉丁字母数字和下划线。不适用于所有补全源,尤其是:Claude、MistralAI、Google。", | 226 | "character_names_completion": "适用限制:仅限拉丁字母数字和下划线。不适用于所有补全源,尤其是:Claude、MistralAI、Google。", |
| 225 | "Add character names to completion objects.": "在补全对象中添加角色名称。", | 227 | "Add character names to completion objects.": "在补全对象中添加角色名称。", |
| 226 | "Message Content": "消息内容", | 228 | "Message Content": "消息内容", |
| @@ -318,6 +320,7 @@ | |||
| 318 | "View Remaining Credits": "查看剩余额度", | 320 | "View Remaining Credits": "查看剩余额度", |
| 319 | "OpenRouter Model": "OpenRouter 模型", | 321 | "OpenRouter Model": "OpenRouter 模型", |
| 320 | "Model Providers": "模型提供者", | 322 | "Model Providers": "模型提供者", |
| 323 | "Allow fallback providers": "允许后备提供者", | ||
| 321 | "InfermaticAI API Key": "InfermaticAI API 密钥", | 324 | "InfermaticAI API Key": "InfermaticAI API 密钥", |
| 322 | "InfermaticAI Model": "InfermaticAI 模型", | 325 | "InfermaticAI Model": "InfermaticAI 模型", |
| 323 | "DreamGen API key": "DreamGen API 密钥", | 326 | "DreamGen API key": "DreamGen API 密钥", |
| @@ -346,6 +349,7 @@ | |||
| 346 | "Ollama Model": "Ollama 模型", | 349 | "Ollama Model": "Ollama 模型", |
| 347 | "Download": "下载", | 350 | "Download": "下载", |
| 348 | "Tabby API key": "Tabby API 密钥", | 351 | "Tabby API key": "Tabby API 密钥", |
| 352 | "Tabby Model": "Tabby 模型", | ||
| 349 | "koboldcpp API key (optional)": "koboldcpp API 密钥(可选)", | 353 | "koboldcpp API key (optional)": "koboldcpp API 密钥(可选)", |
| 350 | "Example: 127.0.0.1:5001": "示例:127.0.0.1:5001", | 354 | "Example: 127.0.0.1:5001": "示例:127.0.0.1:5001", |
| 351 | "Authorize": "授权", | 355 | "Authorize": "授权", |
| @@ -363,13 +367,14 @@ | |||
| 363 | "This will show up as your saved preset.": "这将显示为您保存的预设。", | 367 | "This will show up as your saved preset.": "这将显示为您保存的预设。", |
| 364 | "Proxy Server URL": "代理服务器 URL", | 368 | "Proxy Server URL": "代理服务器 URL", |
| 365 | "Alternative server URL (leave empty to use the default value).": "备用服务器 URL(留空以使用默认值)。", | 369 | "Alternative server URL (leave empty to use the default value).": "备用服务器 URL(留空以使用默认值)。", |
| 366 | "Remove your real OAI API Key from the API panel BEFORE typing anything into this box": "在键入任何内容之前,从 API 面板中删除您的真实 OAI API 密钥", | ||
| 367 | "We cannot provide support for problems encountered while using an unofficial OpenAI proxy": "我们无法为使用非官方 OpenAI 代理时遇到的问题提供支持", | ||
| 368 | "Doesn't work? Try adding": "不起作用?尝试在最后添加", | 370 | "Doesn't work? Try adding": "不起作用?尝试在最后添加", |
| 369 | "at the end!": "!", | 371 | "at the end!": "!", |
| 370 | "Proxy Password": "代理密码", | 372 | "Proxy Password": "代理密码", |
| 371 | "Will be used as a password for the proxy instead of API key.": "将用作代理的密码,而不是 API 密钥。", | 373 | "Will be used as a password for the proxy instead of API key.": "将用作代理的密码,而不是 API 密钥。", |
| 372 | "Peek a password": "查看密码", | 374 | "Peek a password": "查看密码", |
| 375 | "Using a proxy that you're not running yourself is a risk to your data privacy.": "使用您自己未运行的代理会对您的数据隐私造成风险。", | ||
| 376 | "ANY support requests will be REFUSED if you are using a proxy.": "如果您使用代理,任何支持请求都将被拒绝。", | ||
| 377 | "Do not proceed if you do not agree to this!": "如果您不同意,请不要继续!", | ||
| 373 | "OpenAI API key": "OpenAI API 密钥", | 378 | "OpenAI API key": "OpenAI API 密钥", |
| 374 | "View API Usage Metrics": "查看API使用情况", | 379 | "View API Usage Metrics": "查看API使用情况", |
| 375 | "Follow": "跟随", | 380 | "Follow": "跟随", |
| @@ -384,14 +389,14 @@ | |||
| 384 | "Slack and Poe cookies will not work here, do not bother trying.": "Slack和Poe的cookie在这里不起作用,请不要尝试。", | 389 | "Slack and Poe cookies will not work here, do not bother trying.": "Slack和Poe的cookie在这里不起作用,请不要尝试。", |
| 385 | "Claude Model": "Claude 模型", | 390 | "Claude Model": "Claude 模型", |
| 386 | "Window AI Model": "Window AI 模型", | 391 | "Window AI Model": "Window AI 模型", |
| 392 | "Allow fallback routes Description": "如果所选模型无法响应您的请求,则自动选择备用模型。", | ||
| 393 | "Allow fallback models": "允许后备模型", | ||
| 387 | "Model Order": "OpenRouter 模型顺序", | 394 | "Model Order": "OpenRouter 模型顺序", |
| 388 | "Alphabetically": "按字母顺序", | 395 | "Alphabetically": "按字母顺序", |
| 389 | "Price": "价格(最便宜)", | 396 | "Price": "价格(最便宜)", |
| 390 | "Context Size": "上下文大小", | 397 | "Context Size": "上下文大小", |
| 391 | "Group by vendors": "按供应商分组", | 398 | "Group by vendors": "按供应商分组", |
| 392 | "Group by vendors Description": "将 OpenAI 模型放在一组,将 Anthropic 模型放在另一组,等等。可以与排序结合。", | 399 | "Group by vendors Description": "将 OpenAI 模型放在一组,将 Anthropic 模型放在另一组,等等。可以与排序结合。", |
| 393 | "Allow fallback routes": "允许后备方案", | ||
| 394 | "Allow fallback routes Description": "如果所选模型无法响应您的请求,则自动选择备用模型。", | ||
| 395 | "openrouter_force_instruct": "此选项已过时,将来会被删除。要使用指令格式,请改用文本完成 API 下的 OpenRouter。", | 400 | "openrouter_force_instruct": "此选项已过时,将来会被删除。要使用指令格式,请改用文本完成 API 下的 OpenRouter。", |
| 396 | "LEGACY": "旧版", | 401 | "LEGACY": "旧版", |
| 397 | "Force Instruct Mode formatting": "强制指令模式格式化", | 402 | "Force Instruct Mode formatting": "强制指令模式格式化", |
| @@ -439,9 +444,11 @@ | |||
| 439 | "Example Separator": "示例分隔符", | 444 | "Example Separator": "示例分隔符", |
| 440 | "Chat Start": "聊天开始", | 445 | "Chat Start": "聊天开始", |
| 441 | "Add Chat Start and Example Separator to a list of stopping strings.": "将聊天开始和示例分隔符添加到停止字符串列表中。", | 446 | "Add Chat Start and Example Separator to a list of stopping strings.": "将聊天开始和示例分隔符添加到停止字符串列表中。", |
| 442 | "Use as Stop Strings": "用作停止字符串", | 447 | "Separators as Stop Strings": "分隔符作为终止字符串", |
| 443 | "context_allow_jailbreak": "如果在角色卡中定义并且启用了“首选角色越狱”,则在提示词末尾包含越狱。\n不建议在文本完成模型中使用此功能,否则会导致输出错误。", | 448 | "Add Character and User names to a list of stopping strings.": "将角色和用户名添加到停止字符串列表中。", |
| 444 | "Allow Jailbreak": "允许越狱", | 449 | "Names as Stop Strings": "名称作为终止字符串", |
| 450 | "context_allow_post_history_instructions": "如果在角色卡中定义并且启用了“首选角色卡说明”,则在提示末尾包含后历史说明。\n不建议在文本补全模型中使用此功能,否则会导致输出错误。", | ||
| 451 | "Allow Post-History Instructions": "允许后历史说明", | ||
| 445 | "Context Order": "上下文顺序", | 452 | "Context Order": "上下文顺序", |
| 446 | "Summary": "总结", | 453 | "Summary": "总结", |
| 447 | "Author's Note": "作者注释", | 454 | "Author's Note": "作者注释", |
| @@ -487,6 +494,10 @@ | |||
| 487 | "First Assistant Prefix": "第一个助理前缀", | 494 | "First Assistant Prefix": "第一个助理前缀", |
| 488 | "instruct_last_output_sequence": "插入到最后一条助手消息之前或作为生成 AI 回复时的最后一行提示词(中立/系统角色除外)。", | 495 | "instruct_last_output_sequence": "插入到最后一条助手消息之前或作为生成 AI 回复时的最后一行提示词(中立/系统角色除外)。", |
| 489 | "Last Assistant Prefix": "最后一个助理前缀", | 496 | "Last Assistant Prefix": "最后一个助理前缀", |
| 497 | "Inserted before the first User's message.": "插入在第一个用户的消息之前。", | ||
| 498 | "First User Prefix": "第一个用户前缀", | ||
| 499 | "instruct_last_input_sequence": "插入到最后一条用户消息之前。", | ||
| 500 | "Last User Prefix": "上次用户前缀", | ||
| 490 | "Will be inserted as a last prompt line when using system/neutral generation.": "当使用系统/中性生成时将作为最后的一行提示词插入。", | 501 | "Will be inserted as a last prompt line when using system/neutral generation.": "当使用系统/中性生成时将作为最后的一行提示词插入。", |
| 491 | "System Instruction Prefix": "系统指令前缀", | 502 | "System Instruction Prefix": "系统指令前缀", |
| 492 | "If a stop sequence is generated, everything past it will be removed from the output (inclusive).": "如果生成了停止序列,则该序列之后的所有内容都将从输出中删除(包括在内)。", | 503 | "If a stop sequence is generated, everything past it will be removed from the output (inclusive).": "如果生成了停止序列,则该序列之后的所有内容都将从输出中删除(包括在内)。", |
| @@ -534,6 +545,7 @@ | |||
| 534 | "Sorted Evenly": "均匀排序", | 545 | "Sorted Evenly": "均匀排序", |
| 535 | "Character Lore First": "角色世界书优先", | 546 | "Character Lore First": "角色世界书优先", |
| 536 | "Global Lore First": "全局世界书优先", | 547 | "Global Lore First": "全局世界书优先", |
| 548 | "Include names with each message into the context for scanning": "将每条消息的名称纳入上下文中以供扫描", | ||
| 537 | "Entries can activate other entries by mentioning their keywords": "条目可以通过提及它们的关键字来激活其他条目", | 549 | "Entries can activate other entries by mentioning their keywords": "条目可以通过提及它们的关键字来激活其他条目", |
| 538 | "Recursive Scan": "递归扫描", | 550 | "Recursive Scan": "递归扫描", |
| 539 | "Lookup for the entry keys in the context will respect the case": "在上下文中查找条目键将保持大小写敏感", | 551 | "Lookup for the entry keys in the context will respect the case": "在上下文中查找条目键将保持大小写敏感", |
| @@ -552,6 +564,7 @@ | |||
| 552 | "Close all Entries": "关闭所有条目", | 564 | "Close all Entries": "关闭所有条目", |
| 553 | "New Entry": "新条目", | 565 | "New Entry": "新条目", |
| 554 | "Fill empty Memo/Titles with Keywords": "使用关键字填充空的备忘录/标题", | 566 | "Fill empty Memo/Titles with Keywords": "使用关键字填充空的备忘录/标题", |
| 567 | "Apply current sorting as Order": "应用当前排序作为顺序", | ||
| 555 | "Import World Info": "导入世界书", | 568 | "Import World Info": "导入世界书", |
| 556 | "Export World Info": "导出世界书", | 569 | "Export World Info": "导出世界书", |
| 557 | "Duplicate World Info": "复制世界书", | 570 | "Duplicate World Info": "复制世界书", |
| @@ -659,14 +672,15 @@ | |||
| 659 | "Defines on importing cards which action should be chosen for importing its listed tags. 'Ask' will always display the dialog.": "定义在导入卡片时应选择哪种操作来导入其列出的标签。“询问”将始终显示对话框。", | 672 | "Defines on importing cards which action should be chosen for importing its listed tags. 'Ask' will always display the dialog.": "定义在导入卡片时应选择哪种操作来导入其列出的标签。“询问”将始终显示对话框。", |
| 660 | "Import Card Tags": "导入卡片标签", | 673 | "Import Card Tags": "导入卡片标签", |
| 661 | "Ask": "询问", | 674 | "Ask": "询问", |
| 675 | "tag_import_none": "无", | ||
| 662 | "tag_import_all": "全部", | 676 | "tag_import_all": "全部", |
| 663 | "Existing": "现存的", | 677 | "Existing": "现存的", |
| 664 | "Use fuzzy matching, and search characters in the list by all data fields, not just by a name substring": "使用模糊匹配,在列表中通过所有数据字段搜索角色,而不仅仅是名称子字符串", | 678 | "Use fuzzy matching, and search characters in the list by all data fields, not just by a name substring": "使用模糊匹配,在列表中通过所有数据字段搜索角色,而不仅仅是名称子字符串", |
| 665 | "Advanced Character Search": "高级角色搜索", | 679 | "Advanced Character Search": "高级角色搜索", |
| 666 | "If checked and the character card contains a prompt override (System Prompt), use that instead": "如果角色卡包含提示词,则使用它替代系统提示词", | 680 | "If checked and the character card contains a prompt override (System Prompt), use that instead": "如果角色卡包含提示词,则使用它替代系统提示词", |
| 667 | "Prefer Character Card Prompt": "角色卡提示词优先", | 681 | "Prefer Character Card Prompt": "角色卡提示词优先", |
| 668 | "If checked and the character card contains a jailbreak override (Post History Instruction), use that instead": "如果角色卡包含越狱(后置历史记录指令),则使用它替代系统越狱", | 682 | "If checked and the character card contains a Post-History Instructions override, use that instead": "如果选中并且角色卡包含后历史指令覆盖,则使用它。", |
| 669 | "Prefer Character Card Jailbreak": "角色卡越狱优先", | 683 | "Prefer Character Card Instructions": "首选角色卡说明", |
| 670 | "Avoid cropping and resizing imported character images. When off, crop/resize to 512x768": "避免裁剪和调整导入的角色图像的大小。关闭时,裁剪/调整大小为 512x768。", | 684 | "Avoid cropping and resizing imported character images. When off, crop/resize to 512x768": "避免裁剪和调整导入的角色图像的大小。关闭时,裁剪/调整大小为 512x768。", |
| 671 | "Never resize avatars": "永不调整头像大小", | 685 | "Never resize avatars": "永不调整头像大小", |
| 672 | "Show actual file names on the disk, in the characters list display only": "在角色列表显示中,显示磁盘上实际的文件名。", | 686 | "Show actual file names on the disk, in the characters list display only": "在角色列表显示中,显示磁盘上实际的文件名。", |
| @@ -693,10 +707,10 @@ | |||
| 693 | "Restore User Input": "恢复用户输入", | 707 | "Restore User Input": "恢复用户输入", |
| 694 | "Allow repositioning certain UI elements by dragging them. PC only, no effect on mobile": "允许通过拖动重新定位某些UI元素。仅适用于PC,对移动设备无影响", | 708 | "Allow repositioning certain UI elements by dragging them. PC only, no effect on mobile": "允许通过拖动重新定位某些UI元素。仅适用于PC,对移动设备无影响", |
| 695 | "Movable UI Panels": "可移动 UI 面板", | 709 | "Movable UI Panels": "可移动 UI 面板", |
| 710 | "Reset MovingUI panel sizes/locations.": "重置 MovingUI 面板大小/位置。", | ||
| 696 | "MovingUI preset. Predefined/saved draggable positions": "可移动UI预设。预定义/保存的可拖动位置", | 711 | "MovingUI preset. Predefined/saved draggable positions": "可移动UI预设。预定义/保存的可拖动位置", |
| 697 | "MUI Preset": "可移动 UI 预设", | 712 | "MUI Preset": "可移动 UI 预设", |
| 698 | "Save movingUI changes to a new file": "将可移动UI更改保存到新文件中", | 713 | "Save movingUI changes to a new file": "将可移动UI更改保存到新文件中", |
| 699 | "Reset MovingUI panel sizes/locations.": "重置 MovingUI 面板大小/位置。", | ||
| 700 | "Apply a custom CSS style to all of the ST GUI": "将自定义CSS样式应用于所有ST GUI", | 714 | "Apply a custom CSS style to all of the ST GUI": "将自定义CSS样式应用于所有ST GUI", |
| 701 | "Custom CSS": "自定义 CSS", | 715 | "Custom CSS": "自定义 CSS", |
| 702 | "Expand the editor": "展开编辑器", | 716 | "Expand the editor": "展开编辑器", |
| @@ -716,6 +730,8 @@ | |||
| 716 | "Press Send to continue": "按发送键以继续", | 730 | "Press Send to continue": "按发送键以继续", |
| 717 | "Show a button in the input area to ask the AI to continue (extend) its last message": "在输入区域中显示一个按钮,要求AI继续(延长)其上一条消息", | 731 | "Show a button in the input area to ask the AI to continue (extend) its last message": "在输入区域中显示一个按钮,要求AI继续(延长)其上一条消息", |
| 718 | "Quick 'Continue' button": "快速“继续”按钮", | 732 | "Quick 'Continue' button": "快速“继续”按钮", |
| 733 | "Show a button in the input area to ask the AI to impersonate your character for a single message": "在输入区域中显示一个按钮,让 AI 模仿你的角色发送一条消息。", | ||
| 734 | "Quick 'Impersonate' button": "快速“模仿”按钮", | ||
| 719 | "Show arrow buttons on the last in-chat message to generate alternative AI responses. Both PC and mobile": "在聊天窗口的最后一条信息上显示箭头按钮,以生成AI的其他回复选项。适用于电脑和手机端。", | 735 | "Show arrow buttons on the last in-chat message to generate alternative AI responses. Both PC and mobile": "在聊天窗口的最后一条信息上显示箭头按钮,以生成AI的其他回复选项。适用于电脑和手机端。", |
| 720 | "Swipes": "刷新回复按钮", | 736 | "Swipes": "刷新回复按钮", |
| 721 | "Allow using swiping gestures on the last in-chat message to trigger swipe generation. Mobile only, no effect on PC": "允许在最后一条聊天消息上使用滑动手势触发滑动生成。仅适用于移动设备,对PC无影响", | 737 | "Allow using swiping gestures on the last in-chat message to trigger swipe generation. Mobile only, no effect on PC": "允许在最后一条聊天消息上使用滑动手势触发滑动生成。仅适用于移动设备,对PC无影响", |
| @@ -739,6 +755,8 @@ | |||
| 739 | "Log prompts to console": "将提示词记录到控制台", | 755 | "Log prompts to console": "将提示词记录到控制台", |
| 740 | "Requests logprobs from the API for the Token Probabilities feature": "从API请求对数概率数据,用于实现词符概率功能。", | 756 | "Requests logprobs from the API for the Token Probabilities feature": "从API请求对数概率数据,用于实现词符概率功能。", |
| 741 | "Request token probabilities": "请求词符概率", | 757 | "Request token probabilities": "请求词符概率", |
| 758 | "In group chat, highlight the character(s) that are currently queued to generate responses and the order in which they will respond.": "在群聊中,突出显示当前排队等待生成响应的角色以及他们响应的顺序。", | ||
| 759 | "Show group chat queue": "显示群聊队列", | ||
| 742 | "Automatically reject and re-generate AI message based on configurable criteria": "根据可配置的条件自动拒绝并重新生成AI消息", | 760 | "Automatically reject and re-generate AI message based on configurable criteria": "根据可配置的条件自动拒绝并重新生成AI消息", |
| 743 | "Auto-swipe": "自动滑动", | 761 | "Auto-swipe": "自动滑动", |
| 744 | "Enable the auto-swipe function. Settings in this section only have an effect when auto-swipe is enabled": "启用自动滑动功能。仅当启用自动滑动时,本节中的设置才会生效", | 762 | "Enable the auto-swipe function. Settings in this section only have an effect when auto-swipe is enabled": "启用自动滑动功能。仅当启用自动滑动时,本节中的设置才会生效", |
| @@ -759,6 +777,10 @@ | |||
| 759 | "Autocomplete Style": "风格", | 777 | "Autocomplete Style": "风格", |
| 760 | "Follow Theme": "关注主题", | 778 | "Follow Theme": "关注主题", |
| 761 | "Dark": "黑暗的", | 779 | "Dark": "黑暗的", |
| 780 | "Keyboard": "键盘:", | ||
| 781 | "Select with Tab or Enter": "使用 Tab 或 Enter 选择", | ||
| 782 | "Select with Tab": "使用 Tab 选择", | ||
| 783 | "Select with Enter": "按 Enter 键选择", | ||
| 762 | "Sets the font size of the autocomplete.": "设置自动完成的字体大小。", | 784 | "Sets the font size of the autocomplete.": "设置自动完成的字体大小。", |
| 763 | "Sets the width of the autocomplete.": "设置自动完成的宽度。", | 785 | "Sets the width of the autocomplete.": "设置自动完成的宽度。", |
| 764 | "Autocomplete Width": "宽度", | 786 | "Autocomplete Width": "宽度", |
| @@ -770,7 +792,7 @@ | |||
| 770 | "Parser Flags": "解析器标志", | 792 | "Parser Flags": "解析器标志", |
| 771 | "Switch to stricter escaping, allowing all delimiting characters to be escaped with a backslash, and backslashes to be escaped as well.": "切换到更严格的转义,允许所有分隔字符用反斜杠转义,并且反斜杠也可以转义。", | 793 | "Switch to stricter escaping, allowing all delimiting characters to be escaped with a backslash, and backslashes to be escaped as well.": "切换到更严格的转义,允许所有分隔字符用反斜杠转义,并且反斜杠也可以转义。", |
| 772 | "STRICT_ESCAPING": "严格转义", | 794 | "STRICT_ESCAPING": "严格转义", |
| 773 | "Replace all {{getvar::}} and {{getglobalvar::}} macros with scoped variables to avoid double macro substitution.": "用范围变量替换所有 {{getvar::}} 和 {{getglobalvar::}} 宏,以避免双重宏替换。", | 795 | "stscript_parser_flag_replace_getvar_label": "防止 {{getvar::}} {{getglobalvar::}} 宏具有自动评估的文字宏类值。\n例如,“{{newline}}”保留为文字字符串“{{newline}}”\n\n(这是通过在内部用范围变量替换 {{getvar::}} {{getglobalvar::}} 宏来实现的。)", |
| 774 | "REPLACE_GETVAR": "替换GETVAR", | 796 | "REPLACE_GETVAR": "替换GETVAR", |
| 775 | "Change Background Image": "更改背景图片", | 797 | "Change Background Image": "更改背景图片", |
| 776 | "Filter": "搜索", | 798 | "Filter": "搜索", |
| @@ -921,7 +943,7 @@ | |||
| 921 | "Insert {{original}} into either box to include the respective default prompt from system settings.": "将{{original}}插入到任一框中,以包含系统设置中的相应默认提示词。", | 943 | "Insert {{original}} into either box to include the respective default prompt from system settings.": "将{{original}}插入到任一框中,以包含系统设置中的相应默认提示词。", |
| 922 | "Main Prompt": "主要提示词", | 944 | "Main Prompt": "主要提示词", |
| 923 | "Any contents here will replace the default Main Prompt used for this character. (v2 spec: system_prompt)": "此处的任何内容都将替换用于此角色的默认主提示词。(v2规范:system_prompt)", | 945 | "Any contents here will replace the default Main Prompt used for this character. (v2 spec: system_prompt)": "此处的任何内容都将替换用于此角色的默认主提示词。(v2规范:system_prompt)", |
| 924 | "Any contents here will replace the default Jailbreak Prompt used for this character. (v2 spec: post_history_instructions)": "此处的任何内容都将替换用于此角色的默认越狱提示词。(v2规范:post_history_instructions)", | 946 | "Any contents here will replace the default Post-History Instructions used for this character. (v2 spec: post_history_instructions)": "此处的任何内容都将替换此角色使用的默认后历史说明。\n(v2 规范:post_history_instructions)", |
| 925 | "Creator's Metadata (Not sent with the AI prompt)": "创作者的元数据(不与AI提示词一起发送)", | 947 | "Creator's Metadata (Not sent with the AI prompt)": "创作者的元数据(不与AI提示词一起发送)", |
| 926 | "Creator's Metadata": "创作者的元数据", | 948 | "Creator's Metadata": "创作者的元数据", |
| 927 | "(Not sent with the AI Prompt)": "(不随 AI 提示词发送)", | 949 | "(Not sent with the AI Prompt)": "(不随 AI 提示词发送)", |
| @@ -957,9 +979,6 @@ | |||
| 957 | "Lock": "加锁", | 979 | "Lock": "加锁", |
| 958 | "Unlock": "解锁", | 980 | "Unlock": "解锁", |
| 959 | "Delete background": "删除背景", | 981 | "Delete background": "删除背景", |
| 960 | "Chat Scenario Override": "聊天场景覆盖", | ||
| 961 | "Remove": "移除", | ||
| 962 | "Type here...": "在此处输入...", | ||
| 963 | "Chat Lorebook": "聊天知识书", | 982 | "Chat Lorebook": "聊天知识书", |
| 964 | "Chat Lorebook for": "聊天知识书", | 983 | "Chat Lorebook for": "聊天知识书", |
| 965 | "chat_world_template_txt": "选定的世界信息将绑定到此聊天。生成 AI 回复时,\n它将与全球和角色传说书中的条目相结合。", | 984 | "chat_world_template_txt": "选定的世界信息将绑定到此聊天。生成 AI 回复时,\n它将与全球和角色传说书中的条目相结合。", |
| @@ -1047,6 +1066,7 @@ | |||
| 1047 | "Use Probability": "使用概率", | 1066 | "Use Probability": "使用概率", |
| 1048 | "Add Memo": "添加备忘录", | 1067 | "Add Memo": "添加备忘录", |
| 1049 | "Text or token ids": "文本或 [token ID]", | 1068 | "Text or token ids": "文本或 [token ID]", |
| 1069 | "Type here...": "在此处输入...", | ||
| 1050 | "close": "关闭", | 1070 | "close": "关闭", |
| 1051 | "prompt_manager_edit": "编辑", | 1071 | "prompt_manager_edit": "编辑", |
| 1052 | "prompt_manager_name": "姓名", | 1072 | "prompt_manager_name": "姓名", |
| @@ -1054,8 +1074,9 @@ | |||
| 1054 | "To whom this message will be attributed.": "此消息应归于谁。", | 1074 | "To whom this message will be attributed.": "此消息应归于谁。", |
| 1055 | "AI Assistant": "AI助手", | 1075 | "AI Assistant": "AI助手", |
| 1056 | "prompt_manager_position": "位置", | 1076 | "prompt_manager_position": "位置", |
| 1057 | "Injection position. Next to other prompts (relative) or in-chat (absolute).": "注入位置。其他提示词旁边(相对)或在聊天中(绝对)。", | 1077 | "Injection position. Relative (to other prompts in prompt manager) or In-chat @ Depth.": "注入位置。相对(相对于提示管理器中的其他提示)或在聊天中@深度。", |
| 1058 | "prompt_manager_relative": "相对", | 1078 | "prompt_manager_relative": "相对", |
| 1079 | "prompt_manager_in_chat": "聊天中", | ||
| 1059 | "prompt_manager_depth": "深度", | 1080 | "prompt_manager_depth": "深度", |
| 1060 | "Injection depth. 0 = after the last message, 1 = before the last message, etc.": "注入深度。0 = 在最后一条消息之后,1 = 在最后一条消息之前,等等。", | 1081 | "Injection depth. 0 = after the last message, 1 = before the last message, etc.": "注入深度。0 = 在最后一条消息之后,1 = 在最后一条消息之前,等等。", |
| 1061 | "Prompt": "提示词", | 1082 | "Prompt": "提示词", |
| @@ -1164,6 +1185,7 @@ | |||
| 1164 | "Pause script execution": "暂停执行脚本", | 1185 | "Pause script execution": "暂停执行脚本", |
| 1165 | "Abort script execution": "中止执行脚本", | 1186 | "Abort script execution": "中止执行脚本", |
| 1166 | "Abort request": "中止请求", | 1187 | "Abort request": "中止请求", |
| 1188 | "Ask AI to write your message for you": "让AI为您撰写消息", | ||
| 1167 | "Continue the last message": "继续上一条消息", | 1189 | "Continue the last message": "继续上一条消息", |
| 1168 | "Send a message": "发送消息", | 1190 | "Send a message": "发送消息", |
| 1169 | "Close chat": "关闭聊天", | 1191 | "Close chat": "关闭聊天", |
| @@ -1175,19 +1197,24 @@ | |||
| 1175 | "Manage chat files": "管理聊天文件", | 1197 | "Manage chat files": "管理聊天文件", |
| 1176 | "Delete messages": "删除消息", | 1198 | "Delete messages": "删除消息", |
| 1177 | "Regenerate": "重新生成", | 1199 | "Regenerate": "重新生成", |
| 1178 | "Ask AI to write your message for you": "请求AI为您撰写消息", | ||
| 1179 | "Impersonate": "AI 帮答", | 1200 | "Impersonate": "AI 帮答", |
| 1180 | "Continue": "继续", | 1201 | "Continue": "继续", |
| 1181 | "Bind user name to that avatar": "将用户名称绑定到该头像", | 1202 | "Bind user name to that avatar": "将用户名称绑定到该头像", |
| 1182 | "Change persona image": "更改用户角色头像", | 1203 | "Change persona image": "更改用户角色头像", |
| 1183 | "Select this as default persona for the new chats.": "选择此项作为新聊天的默认用户角色。", | 1204 | "Select this as default persona for the new chats.": "选择此项作为新聊天的默认用户角色。", |
| 1205 | "Duplicate persona": "复制用户角色", | ||
| 1184 | "Delete persona": "删除用户角色", | 1206 | "Delete persona": "删除用户角色", |
| 1185 | "These characters are the winners of character design contests and have outstandable quality.": "这些角色都是角色设计大赛的获奖者,品质非常出色。", | 1207 | "These characters are the winners of character design contests and have outstandable quality.": "这些角色都是角色设计大赛的获奖者,品质非常出色。", |
| 1186 | "Contest Winners": "比赛获胜者", | 1208 | "Contest Winners": "比赛获胜者", |
| 1187 | "These characters are the finalists of character design contests and have remarkable quality.": "这些角色都是角色设计大赛的入围作品,品质十分出色。", | 1209 | "These characters are the finalists of character design contests and have remarkable quality.": "这些角色都是角色设计大赛的入围作品,品质十分出色。", |
| 1188 | "Featured Characters": "特色角色", | 1210 | "Featured Characters": "特色角色", |
| 1189 | "Download Extensions & Assets": "下载扩展和资源菜单", | 1211 | "Download Extensions & Assets": "下载扩展和资源菜单", |
| 1212 | "Load a custom asset list or select": "加载自定义资产列表或选择", | ||
| 1213 | "to install 3rd party extensions.": "安装第三方扩展。", | ||
| 1190 | "Assets URL": "资产网址", | 1214 | "Assets URL": "资产网址", |
| 1215 | "load_asset_list_desc": "根据资产列表文件加载扩展和资产列表。\n\n此字段中的默认资产 URL 指向官方第一方扩展和资产列表。\n如果您有自定义资产列表,可以在此处插入。\n\n要安装单个第三方扩展,请使用右上角的“安装扩展”按钮。", | ||
| 1216 | "Load an asset list": "加载资产列表", | ||
| 1217 | "Load Asset List": "加载资产列表", | ||
| 1191 | "Characters": "人物", | 1218 | "Characters": "人物", |
| 1192 | "Attach a File": "附加文件", | 1219 | "Attach a File": "附加文件", |
| 1193 | "Enter a URL or the ID of a Fandom wiki page to scrape:": "输入要抓取的 Fandom wiki 页面的 URL 或 ID:", | 1220 | "Enter a URL or the ID of a Fandom wiki page to scrape:": "输入要抓取的 Fandom wiki 页面的 URL 或 ID:", |
| @@ -1271,6 +1298,7 @@ | |||
| 1271 | "Put images with expressions there. File names should follow the pattern:": "将带有表情的图像放在那里。文件名应遵循以下模式:", | 1298 | "Put images with expressions there. File names should follow the pattern:": "将带有表情的图像放在那里。文件名应遵循以下模式:", |
| 1272 | "expression_label_pattern": "[表达式标签].[图像格式]", | 1299 | "expression_label_pattern": "[表达式标签].[图像格式]", |
| 1273 | "Sprite set:": "表情集:", | 1300 | "Sprite set:": "表情集:", |
| 1301 | "Show Gallery": "展示图库", | ||
| 1274 | "ext_sum_title": "总结", | 1302 | "ext_sum_title": "总结", |
| 1275 | "ext_sum_with": "总结如下:", | 1303 | "ext_sum_with": "总结如下:", |
| 1276 | "ext_sum_main_api": "主要 API", | 1304 | "ext_sum_main_api": "主要 API", |
| @@ -1315,9 +1343,14 @@ | |||
| 1315 | "ext_sum_injection_template": "插入模板", | 1343 | "ext_sum_injection_template": "插入模板", |
| 1316 | "ext_sum_memory_template_placeholder": "{{summary}} 将解析当前摘要内容。", | 1344 | "ext_sum_memory_template_placeholder": "{{summary}} 将解析当前摘要内容。", |
| 1317 | "ext_sum_injection_position": "插入位置", | 1345 | "ext_sum_injection_position": "插入位置", |
| 1346 | "ext_sum_include_wi_scan_desc": "在 WI 扫描中包括最新摘要。", | ||
| 1347 | "ext_sum_include_wi_scan": "纳入世界信息扫描", | ||
| 1348 | "None (not injected)": "无(未注入)", | ||
| 1349 | "ext_sum_injection_position_none": "摘要不会被注入到提示中。您仍然可以通过 {{summary}} 宏访问它。", | ||
| 1318 | "How many messages before the current end of the chat.": "当前聊天结束前还有多少条消息。", | 1350 | "How many messages before the current end of the chat.": "当前聊天结束前还有多少条消息。", |
| 1319 | "Labels and Message": "标签和信息", | 1351 | "Labels and Message": "标签和信息", |
| 1320 | "Label": "标签", | 1352 | "Label": "标签", |
| 1353 | "(label of the button, if no icon is chosen) ": "(如果没有选择图标,则为按钮的标签)", | ||
| 1321 | "Title": "标题", | 1354 | "Title": "标题", |
| 1322 | "(tooltip, leave empty to show message or /command)": "(工具提示,留空以显示消息或/命令)", | 1355 | "(tooltip, leave empty to show message or /command)": "(工具提示,留空以显示消息或/命令)", |
| 1323 | "Message / Command:": "消息/命令:", | 1356 | "Message / Command:": "消息/命令:", |
| @@ -1348,6 +1381,8 @@ | |||
| 1348 | "Inject user input automatically": "自动注入用户输入", | 1381 | "Inject user input automatically": "自动注入用户输入", |
| 1349 | "(if disabled, use ": "(如果禁用,使用", | 1382 | "(if disabled, use ": "(如果禁用,使用", |
| 1350 | "macro for manual injection)": "宏用于手动注入)", | 1383 | "macro for manual injection)": "宏用于手动注入)", |
| 1384 | "Color": "颜色", | ||
| 1385 | "Only apply color as accent": "仅应用颜色作为强调", | ||
| 1351 | "ext_regex_title": "正则", | 1386 | "ext_regex_title": "正则", |
| 1352 | "ext_regex_new_global_script_desc": "新的全局正则表达式脚本", | 1387 | "ext_regex_new_global_script_desc": "新的全局正则表达式脚本", |
| 1353 | "ext_regex_new_global_script": "新建全局正则", | 1388 | "ext_regex_new_global_script": "新建全局正则", |
| @@ -1361,7 +1396,7 @@ | |||
| 1361 | "ext_regex_scoped_scripts_desc": "只影响当前角色,保存在角色卡片中", | 1396 | "ext_regex_scoped_scripts_desc": "只影响当前角色,保存在角色卡片中", |
| 1362 | "Regex Editor": "正则表达式编辑器", | 1397 | "Regex Editor": "正则表达式编辑器", |
| 1363 | "Test Mode": "测试模式", | 1398 | "Test Mode": "测试模式", |
| 1364 | "ext_regex_desc": "Regex 是一款使用正则表达式查找/替换字符串的工具。如果您想了解更多信息,请点击标题旁边的 ?。", | 1399 | "ext_regex_desc": "正则是一款使用正则表达式查找/替换字符串的工具。如果您想了解更多信息,请点击标题旁边的 ?。", |
| 1365 | "Input": "输入", | 1400 | "Input": "输入", |
| 1366 | "ext_regex_test_input_placeholder": "在此输入...", | 1401 | "ext_regex_test_input_placeholder": "在此输入...", |
| 1367 | "Output": "输出", | 1402 | "Output": "输出", |
| @@ -1396,6 +1431,7 @@ | |||
| 1396 | "ext_regex_export_script": "导出脚本", | 1431 | "ext_regex_export_script": "导出脚本", |
| 1397 | "ext_regex_delete_script": "删除脚本", | 1432 | "ext_regex_delete_script": "删除脚本", |
| 1398 | "Trigger Stable Diffusion": "触发Stable Diffusion", | 1433 | "Trigger Stable Diffusion": "触发Stable Diffusion", |
| 1434 | "Abort current image generation task": "中止当前图像生成", | ||
| 1399 | "sd_Yourself": "你自己", | 1435 | "sd_Yourself": "你自己", |
| 1400 | "sd_Your_Face": "你的脸", | 1436 | "sd_Your_Face": "你的脸", |
| 1401 | "sd_Me": "我", | 1437 | "sd_Me": "我", |
| @@ -1445,6 +1481,10 @@ | |||
| 1445 | "Delete workflow": "删除工作流", | 1481 | "Delete workflow": "删除工作流", |
| 1446 | "Enhance": "提高", | 1482 | "Enhance": "提高", |
| 1447 | "Refine": "优化", | 1483 | "Refine": "优化", |
| 1484 | "API Key": "API 密钥", | ||
| 1485 | "Click to set": "点击设置", | ||
| 1486 | "You can find your API key in the Stability AI dashboard.": "您可以在 Stability AI 仪表板中找到您的 API 密钥。", | ||
| 1487 | "Style Preset": "风格预设", | ||
| 1448 | "Sampling method": "采样方法", | 1488 | "Sampling method": "采样方法", |
| 1449 | "Scheduler": "调度器", | 1489 | "Scheduler": "调度器", |
| 1450 | "Resolution": "分辨率", | 1490 | "Resolution": "分辨率", |
| @@ -1545,6 +1585,10 @@ | |||
| 1545 | "Only used when Main API is selected.": "仅在选择主 API 时使用。", | 1585 | "Only used when Main API is selected.": "仅在选择主 API 时使用。", |
| 1546 | "Old messages are vectorized gradually as you chat. To process all previous messages, click the button below.": "随着您聊天,旧消息会逐渐矢量化。\n要处理所有以前的消息,请单击下面的按钮。", | 1586 | "Old messages are vectorized gradually as you chat. To process all previous messages, click the button below.": "随着您聊天,旧消息会逐渐矢量化。\n要处理所有以前的消息,请单击下面的按钮。", |
| 1547 | "View Stats": "查看统计数据", | 1587 | "View Stats": "查看统计数据", |
| 1588 | "Title/Memo": "标题/备忘录", | ||
| 1589 | "Status": "状态", | ||
| 1590 | "Position": "位置", | ||
| 1591 | "Trigger %": "触发率 %", | ||
| 1548 | "Manager Users": "管理用户", | 1592 | "Manager Users": "管理用户", |
| 1549 | "New User": "新用户", | 1593 | "New User": "新用户", |
| 1550 | "Status:": "地位:", | 1594 | "Status:": "地位:", |
| @@ -1564,12 +1608,10 @@ | |||
| 1564 | "New Tags": "新标签", | 1608 | "New Tags": "新标签", |
| 1565 | "Folder Tags": "文件夹标签", | 1609 | "Folder Tags": "文件夹标签", |
| 1566 | "The following tags will be auto-imported based on the currently selected folders": "根据当前选定的文件夹将自动导入以下标签", | 1610 | "The following tags will be auto-imported based on the currently selected folders": "根据当前选定的文件夹将自动导入以下标签", |
| 1567 | "Remember my choice": "记住我的选择", | ||
| 1568 | "Remember the chosen import option If anything besides 'Cancel' is selected, this dialog will not show up anymore. To change this, go to the settings and modify \"Tag Import Option\". If the \"Import\" option is chosen, the global setting will stay on \"Ask\".": "记住所选的导入选项\n如果选择了“取消”以外的任何选项,此对话框将不再显示。\n要更改此设置,请转到设置并修改“标签导入选项”。\n\n如果选择了“导入”选项,则全局设置将保留为“询问”。", | ||
| 1569 | "Import None": "不导入", | 1611 | "Import None": "不导入", |
| 1570 | "Import All": "全部导入", | 1612 | "Import All": "全部导入", |
| 1571 | "Import Existing": "导入现有", | 1613 | "Import Existing": "导入现有", |
| 1572 | "Import tags button": "导入", | 1614 | "Import": "导入", |
| 1573 | "Include Body Parameters": "包括主体参数", | 1615 | "Include Body Parameters": "包括主体参数", |
| 1574 | "custom_include_body_desc": "聊天完成请求主体中要包含的参数(YAML 对象)\n\n示例:\n- top_k:20\n- repetition_penalty:1.1", | 1616 | "custom_include_body_desc": "聊天完成请求主体中要包含的参数(YAML 对象)\n\n示例:\n- top_k:20\n- repetition_penalty:1.1", |
| 1575 | "Exclude Body Parameters": "排除主体参数", | 1617 | "Exclude Body Parameters": "排除主体参数", |
| @@ -1671,6 +1713,9 @@ | |||
| 1671 | "char_import_8": "RisuRealm 角色(直链)", | 1713 | "char_import_8": "RisuRealm 角色(直链)", |
| 1672 | "Supports importing multiple characters.": "支持导入多个角色。", | 1714 | "Supports importing multiple characters.": "支持导入多个角色。", |
| 1673 | "Write each URL or ID into a new line.": "将每个 URL 或 ID 写入新行。", | 1715 | "Write each URL or ID into a new line.": "将每个 URL 或 ID 写入新行。", |
| 1716 | "Show Raw Prompt": "显示原始提示", | ||
| 1717 | "Copy Prompt": "复制提示", | ||
| 1718 | "Show Prompt Differences": "显示提示差异", | ||
| 1674 | "System-wide Replacement Macros (in order of evaluation):": "系统范围的替换宏(按评估顺序):", | 1719 | "System-wide Replacement Macros (in order of evaluation):": "系统范围的替换宏(按评估顺序):", |
| 1675 | "help_macros_1": "仅适用于斜线命令批处理。替换为上一个命令的返回结果。", | 1720 | "help_macros_1": "仅适用于斜线命令批处理。替换为上一个命令的返回结果。", |
| 1676 | "help_macros_2": "仅插入一个换行符。", | 1721 | "help_macros_2": "仅插入一个换行符。", |
| @@ -1687,6 +1732,7 @@ | |||
| 1687 | "help_macros_13": "角色对话示例", | 1732 | "help_macros_13": "角色对话示例", |
| 1688 | "help_macros_14": "未格式化的对话示例", | 1733 | "help_macros_14": "未格式化的对话示例", |
| 1689 | "(only for Story String)": "(仅适用于故事字符串)", | 1734 | "(only for Story String)": "(仅适用于故事字符串)", |
| 1735 | "help_macros_summary": "“Summarize”扩展生成的最新聊天摘要(如果有)。", | ||
| 1690 | "help_macros_15": "您当前的 Persona 用户名", | 1736 | "help_macros_15": "您当前的 Persona 用户名", |
| 1691 | "help_macros_16": "角色的名字", | 1737 | "help_macros_16": "角色的名字", |
| 1692 | "help_macros_17": "角色的版本号", | 1738 | "help_macros_17": "角色的版本号", |
| @@ -1700,6 +1746,7 @@ | |||
| 1700 | "help_macros_22": "上下文中包含的第一条消息的 ID。要求在当前会话中至少运行一次生成。", | 1746 | "help_macros_22": "上下文中包含的第一条消息的 ID。要求在当前会话中至少运行一次生成。", |
| 1701 | "help_macros_23": "最后一条聊天消息中当前滑动的 ID(以 1 为基数)。如果最后一条消息是用户或提示隐藏的,则为空字符串。", | 1747 | "help_macros_23": "最后一条聊天消息中当前滑动的 ID(以 1 为基数)。如果最后一条消息是用户或提示隐藏的,则为空字符串。", |
| 1702 | "help_macros_24": "最后一条聊天消息中的滑动次数。如果最后一条消息是用户隐藏或提示隐藏的,则为空字符串。", | 1748 | "help_macros_24": "最后一条聊天消息中的滑动次数。如果最后一条消息是用户隐藏或提示隐藏的,则为空字符串。", |
| 1749 | "help_macros_reverse": "反转宏的内容。", | ||
| 1703 | "help_macros_25": "您可以在此处留言,宏将被替换为空白内容。AI 看不到。", | 1750 | "help_macros_25": "您可以在此处留言,宏将被替换为空白内容。AI 看不到。", |
| 1704 | "help_macros_26": "当前时间", | 1751 | "help_macros_26": "当前时间", |
| 1705 | "help_macros_27": "当前日期", | 1752 | "help_macros_27": "当前日期", |
| @@ -1738,6 +1785,8 @@ | |||
| 1738 | "help_macros_56": "指示系统指令前缀", | 1785 | "help_macros_56": "指示系统指令前缀", |
| 1739 | "help_macros_57": "指示第一个用户消息填充器", | 1786 | "help_macros_57": "指示第一个用户消息填充器", |
| 1740 | "help_macros_58": "指示停止顺序", | 1787 | "help_macros_58": "指示停止顺序", |
| 1788 | "help_macros_first_user": "指示用户第一个输入序列", | ||
| 1789 | "help_macros_last_user": "指示用户最后输入序列", | ||
| 1741 | "Chat variables Macros:": "聊天变量宏:", | 1790 | "Chat variables Macros:": "聊天变量宏:", |
| 1742 | "Local variables = unique to the current chat": "局部变量 = 当前聊天所独有", | 1791 | "Local variables = unique to the current chat": "局部变量 = 当前聊天所独有", |
| 1743 | "Global variables = works in any chat for any character": "全局变量 = 适用于任何角色的任何聊天", | 1792 | "Global variables = works in any chat for any character": "全局变量 = 适用于任何角色的任何聊天", |
| @@ -1769,10 +1818,22 @@ | |||
| 1769 | "prompt_manager_tokens": "词符", | 1818 | "prompt_manager_tokens": "词符", |
| 1770 | "Are you sure you want to reset your settings to factory defaults?": "您确定要将设置重置为出厂默认设置吗?", | 1819 | "Are you sure you want to reset your settings to factory defaults?": "您确定要将设置重置为出厂默认设置吗?", |
| 1771 | "Don't forget to save a snapshot of your settings before proceeding.": "在继续之前,不要忘记保存您的设置快照。", | 1820 | "Don't forget to save a snapshot of your settings before proceeding.": "在继续之前,不要忘记保存您的设置快照。", |
| 1821 | "Chat Scenario Override": "聊天场景覆盖", | ||
| 1822 | "Remove": "移除", | ||
| 1772 | "Settings Snapshots": "设置快照", | 1823 | "Settings Snapshots": "设置快照", |
| 1773 | "Record a snapshot of your current settings.": "记录当前设置的快照。", | 1824 | "Record a snapshot of your current settings.": "记录当前设置的快照。", |
| 1774 | "Make a Snapshot": "制作快照", | 1825 | "Make a Snapshot": "制作快照", |
| 1775 | "Restore this snapshot": "恢复此快照", | 1826 | "Restore this snapshot": "恢复此快照", |
| 1827 | "Download Model": "下载模型", | ||
| 1828 | "Downloader Options": "下载器选项", | ||
| 1829 | "Extra parameters for downloading/HuggingFace API": "下载/HuggingFace API 的额外参数。如果不确定,请将其留空。", | ||
| 1830 | "Revision": "修订", | ||
| 1831 | "Folder Name": "输出文件夹名称", | ||
| 1832 | "HF Token": "HF代币", | ||
| 1833 | "Include Patterns": "包含模式", | ||
| 1834 | "Glob patterns of files to include in the download.": "要包含在下载中的文件的全局模式。每个模式用换行符分隔。", | ||
| 1835 | "Exclude Patterns": "排除模式", | ||
| 1836 | "Glob patterns of files to exclude in the download.": "下载中要排除的文件的 Glob 模式。每个模式用换行符分隔。", | ||
| 1776 | "Hi,": "嘿,", | 1837 | "Hi,": "嘿,", |
| 1777 | "To enable multi-account features, restart the SillyTavern server with": "要启用多帐户功能,请使用以下命令重新启动 SillyTavern 服务器", | 1838 | "To enable multi-account features, restart the SillyTavern server with": "要启用多帐户功能,请使用以下命令重新启动 SillyTavern 服务器", |
| 1778 | "set to true in the config.yaml file.": "在 config.yaml 文件中设置为 true。", | 1839 | "set to true in the config.yaml file.": "在 config.yaml 文件中设置为 true。", |
| @@ -208,7 +208,7 @@ | |||
| 208 | "JSON Schema": "JSON 結構", | 208 | "JSON Schema": "JSON 結構", |
| 209 | "Type in the desired JSON schema": "輸入所需的 JSON 結構", | 209 | "Type in the desired JSON schema": "輸入所需的 JSON 結構", |
| 210 | "Grammar String": "語法字串", | 210 | "Grammar String": "語法字串", |
| 211 | "GNBF or ENBF, depends on the backend in use. If you're using this you should know which.": "GNBF 或 ENBF,取決於所使用的後端。如果您使用此功能,應該知道是哪一種", | 211 | "GBNF or EBNF, depends on the backend in use. If you're using this you should know which.": "GBNF 或 EBNF,取決於所使用的後端。如果您使用此功能,應該知道是哪一種", |
| 212 | "Top P & Min P": "Top P 和 Min P", | 212 | "Top P & Min P": "Top P 和 Min P", |
| 213 | "Load default order": "載入預設順序", | 213 | "Load default order": "載入預設順序", |
| 214 | "llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "僅適用於 llama.cpp。決定取樣器的順序。如果 Mirostat 模式不為 0,則忽略取樣器順序。", | 214 | "llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "僅適用於 llama.cpp。決定取樣器的順序。如果 Mirostat 模式不為 0,則忽略取樣器順序。", |
| @@ -217,7 +217,7 @@ | |||
| 217 | "Character Names Behavior": "角色人物名稱行為", | 217 | "Character Names Behavior": "角色人物名稱行為", |
| 218 | "Helps the model to associate messages with characters.": "幫助模型將訊息與角色人物關聯起來。", | 218 | "Helps the model to associate messages with characters.": "幫助模型將訊息與角色人物關聯起來。", |
| 219 | "None": "無", | 219 | "None": "無", |
| 220 | "character_names_none": "除了團體和過去的玩家角色人物外。否則,請確保在提示中提供名字。", | 220 | "character_names_default": "除了團體和過去的玩家角色人物外。否則,請確保在提示中提供名字。", |
| 221 | "Don't add character names.": "不要新增角色人物名稱", | 221 | "Don't add character names.": "不要新增角色人物名稱", |
| 222 | "Completion": "補充", | 222 | "Completion": "補充", |
| 223 | "character_names_completion": "字元限制:僅限拉丁字母數字和底線。不適用於所有來源,特別是:Claude、MistralAI、Google。", | 223 | "character_names_completion": "字元限制:僅限拉丁字母數字和底線。不適用於所有來源,特別是:Claude、MistralAI、Google。", |
| @@ -156,6 +156,7 @@ import { | |||
| 156 | ensureImageFormatSupported, | 156 | ensureImageFormatSupported, |
| 157 | flashHighlight, | 157 | flashHighlight, |
| 158 | isTrueBoolean, | 158 | isTrueBoolean, |
| 159 | toggleDrawer, | ||
| 159 | } from './scripts/utils.js'; | 160 | } from './scripts/utils.js'; |
| 160 | import { debounce_timeout } from './scripts/constants.js'; | 161 | import { debounce_timeout } from './scripts/constants.js'; |
| 161 | 162 | ||
| @@ -224,10 +225,10 @@ import { | |||
| 224 | import { getBackgrounds, initBackgrounds, loadBackgroundSettings, background_settings } from './scripts/backgrounds.js'; | 225 | import { getBackgrounds, initBackgrounds, loadBackgroundSettings, background_settings } from './scripts/backgrounds.js'; |
| 225 | import { hideLoader, showLoader } from './scripts/loader.js'; | 226 | import { hideLoader, showLoader } from './scripts/loader.js'; |
| 226 | import { BulkEditOverlay, CharacterContextMenu } from './scripts/BulkEditOverlay.js'; | 227 | import { BulkEditOverlay, CharacterContextMenu } from './scripts/BulkEditOverlay.js'; |
| 227 | import { loadFeatherlessModels, loadMancerModels, loadOllamaModels, loadTogetherAIModels, loadInfermaticAIModels, loadOpenRouterModels, loadVllmModels, loadAphroditeModels, loadDreamGenModels } from './scripts/textgen-models.js'; | 228 | import { loadFeatherlessModels, loadMancerModels, loadOllamaModels, loadTogetherAIModels, loadInfermaticAIModels, loadOpenRouterModels, loadVllmModels, loadAphroditeModels, loadDreamGenModels, initTextGenModels } from './scripts/textgen-models.js'; |
| 228 | import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags, isExternalMediaAllowed, getCurrentEntityId } from './scripts/chats.js'; | 229 | import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags, isExternalMediaAllowed, getCurrentEntityId } from './scripts/chats.js'; |
| 229 | import { initPresetManager } from './scripts/preset-manager.js'; | 230 | import { initPresetManager } from './scripts/preset-manager.js'; |
| 230 | import { MacrosParser, evaluateMacros } from './scripts/macros.js'; | 231 | import { MacrosParser, evaluateMacros, getLastMessageId } from './scripts/macros.js'; |
| 231 | import { currentUser, setUserControls } from './scripts/user.js'; | 232 | import { currentUser, setUserControls } from './scripts/user.js'; |
| 232 | import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup, fixToastrForDialogs } from './scripts/popup.js'; | 233 | import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup, fixToastrForDialogs } from './scripts/popup.js'; |
| 233 | import { renderTemplate, renderTemplateAsync } from './scripts/templates.js'; | 234 | import { renderTemplate, renderTemplateAsync } from './scripts/templates.js'; |
| @@ -424,6 +425,8 @@ export const event_types = { | |||
| 424 | CHATCOMPLETION_MODEL_CHANGED: 'chatcompletion_model_changed', | 425 | CHATCOMPLETION_MODEL_CHANGED: 'chatcompletion_model_changed', |
| 425 | OAI_PRESET_CHANGED_BEFORE: 'oai_preset_changed_before', | 426 | OAI_PRESET_CHANGED_BEFORE: 'oai_preset_changed_before', |
| 426 | OAI_PRESET_CHANGED_AFTER: 'oai_preset_changed_after', | 427 | OAI_PRESET_CHANGED_AFTER: 'oai_preset_changed_after', |
| 428 | OAI_PRESET_EXPORT_READY: 'oai_preset_export_ready', | ||
| 429 | OAI_PRESET_IMPORT_READY: 'oai_preset_import_ready', | ||
| 427 | WORLDINFO_SETTINGS_UPDATED: 'worldinfo_settings_updated', | 430 | WORLDINFO_SETTINGS_UPDATED: 'worldinfo_settings_updated', |
| 428 | WORLDINFO_UPDATED: 'worldinfo_updated', | 431 | WORLDINFO_UPDATED: 'worldinfo_updated', |
| 429 | CHARACTER_EDITED: 'character_edited', | 432 | CHARACTER_EDITED: 'character_edited', |
| @@ -439,6 +442,7 @@ export const event_types = { | |||
| 439 | GROUP_CHAT_CREATED: 'group_chat_created', | 442 | GROUP_CHAT_CREATED: 'group_chat_created', |
| 440 | GENERATE_BEFORE_COMBINE_PROMPTS: 'generate_before_combine_prompts', | 443 | GENERATE_BEFORE_COMBINE_PROMPTS: 'generate_before_combine_prompts', |
| 441 | GENERATE_AFTER_COMBINE_PROMPTS: 'generate_after_combine_prompts', | 444 | GENERATE_AFTER_COMBINE_PROMPTS: 'generate_after_combine_prompts', |
| 445 | GENERATE_AFTER_DATA: 'generate_after_data', | ||
| 442 | GROUP_MEMBER_DRAFTED: 'group_member_drafted', | 446 | GROUP_MEMBER_DRAFTED: 'group_member_drafted', |
| 443 | WORLD_INFO_ACTIVATED: 'world_info_activated', | 447 | WORLD_INFO_ACTIVATED: 'world_info_activated', |
| 444 | TEXT_COMPLETION_SETTINGS_READY: 'text_completion_settings_ready', | 448 | TEXT_COMPLETION_SETTINGS_READY: 'text_completion_settings_ready', |
| @@ -455,6 +459,7 @@ export const event_types = { | |||
| 455 | LLM_FUNCTION_TOOL_REGISTER: 'llm_function_tool_register', | 459 | LLM_FUNCTION_TOOL_REGISTER: 'llm_function_tool_register', |
| 456 | LLM_FUNCTION_TOOL_CALL: 'llm_function_tool_call', | 460 | LLM_FUNCTION_TOOL_CALL: 'llm_function_tool_call', |
| 457 | ONLINE_STATUS_CHANGED: 'online_status_changed', | 461 | ONLINE_STATUS_CHANGED: 'online_status_changed', |
| 462 | IMAGE_SWIPED: 'image_swiped', | ||
| 458 | }; | 463 | }; |
| 459 | 464 | ||
| 460 | export const eventSource = new EventEmitter(); | 465 | export const eventSource = new EventEmitter(); |
| @@ -570,6 +575,7 @@ export const system_message_types = { | |||
| 570 | * @enum {number} Extension prompt types | 575 | * @enum {number} Extension prompt types |
| 571 | */ | 576 | */ |
| 572 | export const extension_prompt_types = { | 577 | export const extension_prompt_types = { |
| 578 | NONE: -1, | ||
| 573 | IN_PROMPT: 0, | 579 | IN_PROMPT: 0, |
| 574 | IN_CHAT: 1, | 580 | IN_CHAT: 1, |
| 575 | BEFORE_PROMPT: 2, | 581 | BEFORE_PROMPT: 2, |
| @@ -909,6 +915,7 @@ async function firstLoadInit() { | |||
| 909 | await readSecretState(); | 915 | await readSecretState(); |
| 910 | initLocales(); | 916 | initLocales(); |
| 911 | initDefaultSlashCommands(); | 917 | initDefaultSlashCommands(); |
| 918 | initTextGenModels(); | ||
| 912 | await getSystemMessages(); | 919 | await getSystemMessages(); |
| 913 | sendSystemMessage(system_message_types.WELCOME); | 920 | sendSystemMessage(system_message_types.WELCOME); |
| 914 | await getSettings(); | 921 | await getSettings(); |
| @@ -929,10 +936,17 @@ async function firstLoadInit() { | |||
| 929 | initCfg(); | 936 | initCfg(); |
| 930 | initLogprobs(); | 937 | initLogprobs(); |
| 931 | doDailyExtensionUpdatesCheck(); | 938 | doDailyExtensionUpdatesCheck(); |
| 932 | hideLoader(); | 939 | await hideLoader(); |
| 940 | await fixViewport(); | ||
| 933 | await eventSource.emit(event_types.APP_READY); | 941 | await eventSource.emit(event_types.APP_READY); |
| 934 | } | 942 | } |
| 935 | 943 | ||
| 944 | async function fixViewport() { | ||
| 945 | document.body.style.position = 'absolute'; | ||
| 946 | await delay(1); | ||
| 947 | document.body.style.position = ''; | ||
| 948 | } | ||
| 949 | |||
| 936 | function cancelStatusCheck() { | 950 | function cancelStatusCheck() { |
| 937 | abortStatusCheck?.abort(); | 951 | abortStatusCheck?.abort(); |
| 938 | abortStatusCheck = new AbortController(); | 952 | abortStatusCheck = new AbortController(); |
| @@ -1712,16 +1726,24 @@ export async function replaceCurrentChat() { | |||
| 1712 | } | 1726 | } |
| 1713 | 1727 | ||
| 1714 | export function showMoreMessages() { | 1728 | export function showMoreMessages() { |
| 1715 | let messageId = Number($('#chat').children('.mes').first().attr('mesid')); | 1729 | const firstDisplayedMesId = $('#chat').children('.mes').first().attr('mesid'); |
| 1730 | let messageId = Number(firstDisplayedMesId); | ||
| 1716 | let count = power_user.chat_truncation || Number.MAX_SAFE_INTEGER; | 1731 | let count = power_user.chat_truncation || Number.MAX_SAFE_INTEGER; |
| 1717 | 1732 | ||
| 1733 | // If there are no messages displayed, or the message somehow has no mesid, we default to one higher than last message id, | ||
| 1734 | // so the first "new" message being shown will be the last available message | ||
| 1735 | if (isNaN(messageId)) { | ||
| 1736 | messageId = getLastMessageId() + 1; | ||
| 1737 | } | ||
| 1738 | |||
| 1718 | console.debug('Inserting messages before', messageId, 'count', count, 'chat length', chat.length); | 1739 | console.debug('Inserting messages before', messageId, 'count', count, 'chat length', chat.length); |
| 1719 | const prevHeight = $('#chat').prop('scrollHeight'); | 1740 | const prevHeight = $('#chat').prop('scrollHeight'); |
| 1720 | 1741 | ||
| 1721 | while (messageId > 0 && count > 0) { | 1742 | while (messageId > 0 && count > 0) { |
| 1743 | let newMessageId = messageId - 1; | ||
| 1744 | addOneMessage(chat[newMessageId], { insertBefore: messageId >= chat.length ? null : messageId, scroll: false, forceId: newMessageId }); | ||
| 1722 | count--; | 1745 | count--; |
| 1723 | messageId--; | 1746 | messageId--; |
| 1724 | addOneMessage(chat[messageId], { insertBefore: messageId + 1, scroll: false, forceId: messageId }); | ||
| 1725 | } | 1747 | } |
| 1726 | 1748 | ||
| 1727 | if (messageId == 0) { | 1749 | if (messageId == 0) { |
| @@ -2091,6 +2113,7 @@ export function updateMessageBlock(messageId, message) { | |||
| 2091 | export function appendMediaToMessage(mes, messageElement, adjustScroll = true) { | 2113 | export function appendMediaToMessage(mes, messageElement, adjustScroll = true) { |
| 2092 | // Add image to message | 2114 | // Add image to message |
| 2093 | if (mes.extra?.image) { | 2115 | if (mes.extra?.image) { |
| 2116 | const container = messageElement.find('.mes_img_container'); | ||
| 2094 | const chatHeight = $('#chat').prop('scrollHeight'); | 2117 | const chatHeight = $('#chat').prop('scrollHeight'); |
| 2095 | const image = messageElement.find('.mes_img'); | 2118 | const image = messageElement.find('.mes_img'); |
| 2096 | const text = messageElement.find('.mes_text'); | 2119 | const text = messageElement.find('.mes_text'); |
| @@ -2106,9 +2129,27 @@ export function appendMediaToMessage(mes, messageElement, adjustScroll = true) { | |||
| 2106 | }); | 2129 | }); |
| 2107 | image.attr('src', mes.extra?.image); | 2130 | image.attr('src', mes.extra?.image); |
| 2108 | image.attr('title', mes.extra?.title || mes.title || ''); | 2131 | image.attr('title', mes.extra?.title || mes.title || ''); |
| 2109 | messageElement.find('.mes_img_container').addClass('img_extra'); | 2132 | container.addClass('img_extra'); |
| 2110 | image.toggleClass('img_inline', isInline); | 2133 | image.toggleClass('img_inline', isInline); |
| 2111 | text.toggleClass('displayNone', !isInline); | 2134 | text.toggleClass('displayNone', !isInline); |
| 2135 | |||
| 2136 | const imageSwipes = mes.extra.image_swipes; | ||
| 2137 | if (Array.isArray(imageSwipes) && imageSwipes.length > 0) { | ||
| 2138 | container.addClass('img_swipes'); | ||
| 2139 | const counter = container.find('.mes_img_swipe_counter'); | ||
| 2140 | const currentImage = imageSwipes.indexOf(mes.extra.image) + 1; | ||
| 2141 | counter.text(`${currentImage}/${imageSwipes.length}`); | ||
| 2142 | |||
| 2143 | const swipeLeft = container.find('.mes_img_swipe_left'); | ||
| 2144 | swipeLeft.off('click').on('click', function () { | ||
| 2145 | eventSource.emit(event_types.IMAGE_SWIPED, { message: mes, element: messageElement, direction: 'left' }); | ||
| 2146 | }); | ||
| 2147 | |||
| 2148 | const swipeRight = container.find('.mes_img_swipe_right'); | ||
| 2149 | swipeRight.off('click').on('click', function () { | ||
| 2150 | eventSource.emit(event_types.IMAGE_SWIPED, { message: mes, element: messageElement, direction: 'right' }); | ||
| 2151 | }); | ||
| 2152 | } | ||
| 2112 | } | 2153 | } |
| 2113 | 2154 | ||
| 2114 | // Add file to message | 2155 | // Add file to message |
| @@ -2462,26 +2503,30 @@ export function substituteParams(content, _name1, _name2, _original, _group, _re | |||
| 2462 | * @returns {string[]} Array of stopping strings | 2503 | * @returns {string[]} Array of stopping strings |
| 2463 | */ | 2504 | */ |
| 2464 | export function getStoppingStrings(isImpersonate, isContinue) { | 2505 | export function getStoppingStrings(isImpersonate, isContinue) { |
| 2465 | const charString = `\n${name2}:`; | 2506 | const result = []; |
| 2466 | const userString = `\n${name1}:`; | ||
| 2467 | const result = isImpersonate ? [charString] : [userString]; | ||
| 2468 | 2507 | ||
| 2469 | result.push(userString); | 2508 | if (power_user.context.names_as_stop_strings) { |
| 2509 | const charString = `\n${name2}:`; | ||
| 2510 | const userString = `\n${name1}:`; | ||
| 2511 | result.push(isImpersonate ? charString : userString); | ||
| 2470 | 2512 | ||
| 2471 | if (isContinue && Array.isArray(chat) && chat[chat.length - 1]?.is_user) { | 2513 | result.push(userString); |
| 2472 | result.push(charString); | ||
| 2473 | } | ||
| 2474 | 2514 | ||
| 2475 | // Add other group members as the stopping strings | 2515 | if (isContinue && Array.isArray(chat) && chat[chat.length - 1]?.is_user) { |
| 2476 | if (selected_group) { | 2516 | result.push(charString); |
| 2477 | const group = groups.find(x => x.id === selected_group); | 2517 | } |
| 2518 | |||
| 2519 | // 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) | ||
| 2520 | if (selected_group && (name2 || isImpersonate)) { | ||
| 2521 | const group = groups.find(x => x.id === selected_group); | ||
| 2478 | 2522 | ||
| 2479 | if (group && Array.isArray(group.members)) { | 2523 | if (group && Array.isArray(group.members)) { |
| 2480 | const names = group.members | 2524 | const names = group.members |
| 2481 | .map(x => characters.find(y => y.avatar == x)) | 2525 | .map(x => characters.find(y => y.avatar == x)) |
| 2482 | .filter(x => x && x.name && x.name !== name2) | 2526 | .filter(x => x && x.name && x.name !== name2) |
| 2483 | .map(x => `\n${x.name}:`); | 2527 | .map(x => `\n${x.name}:`); |
| 2484 | result.push(...names); | 2528 | result.push(...names); |
| 2529 | } | ||
| 2485 | } | 2530 | } |
| 2486 | } | 2531 | } |
| 2487 | 2532 | ||
| @@ -2800,6 +2845,8 @@ class StreamingProcessor { | |||
| 2800 | this.messageDom = null; | 2845 | this.messageDom = null; |
| 2801 | this.messageTextDom = null; | 2846 | this.messageTextDom = null; |
| 2802 | this.messageTimerDom = null; | 2847 | this.messageTimerDom = null; |
| 2848 | this.messageTokenCounterDom = null; | ||
| 2849 | /** @type {HTMLTextAreaElement} */ | ||
| 2803 | this.sendTextarea = document.querySelector('#send_textarea'); | 2850 | this.sendTextarea = document.querySelector('#send_textarea'); |
| 2804 | this.type = type; | 2851 | this.type = type; |
| 2805 | this.force_name2 = force_name2; | 2852 | this.force_name2 = force_name2; |
| @@ -2815,6 +2862,15 @@ class StreamingProcessor { | |||
| 2815 | this.messageLogprobs = []; | 2862 | this.messageLogprobs = []; |
| 2816 | } | 2863 | } |
| 2817 | 2864 | ||
| 2865 | #checkDomElements(messageId) { | ||
| 2866 | if (this.messageDom === null || this.messageTextDom === null) { | ||
| 2867 | this.messageDom = document.querySelector(`#chat .mes[mesid="${messageId}"]`); | ||
| 2868 | this.messageTextDom = this.messageDom?.querySelector('.mes_text'); | ||
| 2869 | this.messageTimerDom = this.messageDom?.querySelector('.mes_timer'); | ||
| 2870 | this.messageTokenCounterDom = this.messageDom?.querySelector('.tokenCounterDisplay'); | ||
| 2871 | } | ||
| 2872 | } | ||
| 2873 | |||
| 2818 | showMessageButtons(messageId) { | 2874 | showMessageButtons(messageId) { |
| 2819 | if (messageId == -1) { | 2875 | if (messageId == -1) { |
| 2820 | return; | 2876 | return; |
| @@ -2843,9 +2899,7 @@ class StreamingProcessor { | |||
| 2843 | else { | 2899 | else { |
| 2844 | await saveReply(this.type, text, true); | 2900 | await saveReply(this.type, text, true); |
| 2845 | messageId = chat.length - 1; | 2901 | messageId = chat.length - 1; |
| 2846 | this.messageDom = document.querySelector(`#chat .mes[mesid="${messageId}"]`); | 2902 | this.#checkDomElements(messageId); |
| 2847 | this.messageTextDom = this.messageDom.querySelector('.mes_text'); | ||
| 2848 | this.messageTimerDom = this.messageDom.querySelector('.mes_timer'); | ||
| 2849 | this.showMessageButtons(messageId); | 2903 | this.showMessageButtons(messageId); |
| 2850 | } | 2904 | } |
| 2851 | 2905 | ||
| @@ -2881,9 +2935,10 @@ class StreamingProcessor { | |||
| 2881 | this.sendTextarea.dispatchEvent(new Event('input', { bubbles: true })); | 2935 | this.sendTextarea.dispatchEvent(new Event('input', { bubbles: true })); |
| 2882 | } | 2936 | } |
| 2883 | else { | 2937 | else { |
| 2884 | let currentTime = new Date(); | 2938 | this.#checkDomElements(messageId); |
| 2939 | const currentTime = new Date(); | ||
| 2885 | // Don't waste time calculating token count for streaming | 2940 | // Don't waste time calculating token count for streaming |
| 2886 | let currentTokenCount = isFinal && power_user.message_token_count_enabled ? getTokenCount(processedText, 0) : 0; | 2941 | const currentTokenCount = isFinal && power_user.message_token_count_enabled ? getTokenCount(processedText, 0) : 0; |
| 2887 | const timePassed = formatGenerationTimer(this.timeStarted, currentTime, currentTokenCount); | 2942 | const timePassed = formatGenerationTimer(this.timeStarted, currentTime, currentTokenCount); |
| 2888 | chat[messageId]['mes'] = processedText; | 2943 | chat[messageId]['mes'] = processedText; |
| 2889 | chat[messageId]['gen_started'] = this.timeStarted; | 2944 | chat[messageId]['gen_started'] = this.timeStarted; |
| @@ -2895,8 +2950,9 @@ class StreamingProcessor { | |||
| 2895 | } | 2950 | } |
| 2896 | 2951 | ||
| 2897 | chat[messageId]['extra']['token_count'] = currentTokenCount; | 2952 | chat[messageId]['extra']['token_count'] = currentTokenCount; |
| 2898 | const tokenCounter = $(`#chat .mes[mesid="${messageId}"] .tokenCounterDisplay`); | 2953 | if (this.messageTokenCounterDom instanceof HTMLElement) { |
| 2899 | tokenCounter.text(`${currentTokenCount}t`); | 2954 | this.messageTokenCounterDom.textContent = `${currentTokenCount}t`; |
| 2955 | } | ||
| 2900 | } | 2956 | } |
| 2901 | 2957 | ||
| 2902 | if ((this.type == 'swipe' || this.type === 'continue') && Array.isArray(chat[messageId]['swipes'])) { | 2958 | if ((this.type == 'swipe' || this.type === 'continue') && Array.isArray(chat[messageId]['swipes'])) { |
| @@ -2904,16 +2960,20 @@ class StreamingProcessor { | |||
| 2904 | chat[messageId]['swipe_info'][chat[messageId]['swipe_id']] = { 'send_date': chat[messageId]['send_date'], 'gen_started': chat[messageId]['gen_started'], 'gen_finished': chat[messageId]['gen_finished'], 'extra': JSON.parse(JSON.stringify(chat[messageId]['extra'])) }; | 2960 | chat[messageId]['swipe_info'][chat[messageId]['swipe_id']] = { 'send_date': chat[messageId]['send_date'], 'gen_started': chat[messageId]['gen_started'], 'gen_finished': chat[messageId]['gen_finished'], 'extra': JSON.parse(JSON.stringify(chat[messageId]['extra'])) }; |
| 2905 | } | 2961 | } |
| 2906 | 2962 | ||
| 2907 | let formattedText = messageFormatting( | 2963 | const formattedText = messageFormatting( |
| 2908 | processedText, | 2964 | processedText, |
| 2909 | chat[messageId].name, | 2965 | chat[messageId].name, |
| 2910 | chat[messageId].is_system, | 2966 | chat[messageId].is_system, |
| 2911 | chat[messageId].is_user, | 2967 | chat[messageId].is_user, |
| 2912 | messageId, | 2968 | messageId, |
| 2913 | ); | 2969 | ); |
| 2914 | this.messageTextDom.innerHTML = formattedText; | 2970 | if (this.messageTextDom instanceof HTMLElement) { |
| 2915 | this.messageTimerDom.textContent = timePassed.timerValue; | 2971 | this.messageTextDom.innerHTML = formattedText; |
| 2916 | this.messageTimerDom.title = timePassed.timerTitle; | 2972 | } |
| 2973 | if (this.messageTimerDom instanceof HTMLElement) { | ||
| 2974 | this.messageTimerDom.textContent = timePassed.timerValue; | ||
| 2975 | this.messageTimerDom.title = timePassed.timerTitle; | ||
| 2976 | } | ||
| 2917 | this.setFirstSwipe(messageId); | 2977 | this.setFirstSwipe(messageId); |
| 2918 | } | 2978 | } |
| 2919 | 2979 | ||
| @@ -3200,6 +3260,23 @@ function restoreResponseLength(api, responseLength) { | |||
| 3200 | } | 3260 | } |
| 3201 | 3261 | ||
| 3202 | /** | 3262 | /** |
| 3263 | * Removes last message from the chat DOM. | ||
| 3264 | * @returns {Promise<void>} Resolves when the message is removed. | ||
| 3265 | */ | ||
| 3266 | function removeLastMessage() { | ||
| 3267 | return new Promise((resolve) => { | ||
| 3268 | const lastMes = $('#chat').children('.mes').last(); | ||
| 3269 | if (lastMes.length === 0) { | ||
| 3270 | return resolve(); | ||
| 3271 | } | ||
| 3272 | lastMes.hide(animation_duration, function () { | ||
| 3273 | $(this).remove(); | ||
| 3274 | resolve(); | ||
| 3275 | }); | ||
| 3276 | }); | ||
| 3277 | } | ||
| 3278 | |||
| 3279 | /** | ||
| 3203 | * Runs a generation using the current chat context. | 3280 | * Runs a generation using the current chat context. |
| 3204 | * @param {string} type Generation type | 3281 | * @param {string} type Generation type |
| 3205 | * @param {GenerateOptions} options Generation options | 3282 | * @param {GenerateOptions} options Generation options |
| @@ -3331,9 +3408,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | |||
| 3331 | } | 3408 | } |
| 3332 | else if (type !== 'quiet' && type !== 'swipe' && !isImpersonate && !dryRun && chat.length) { | 3409 | else if (type !== 'quiet' && type !== 'swipe' && !isImpersonate && !dryRun && chat.length) { |
| 3333 | chat.length = chat.length - 1; | 3410 | chat.length = chat.length - 1; |
| 3334 | $('#chat').children().last().hide(250, function () { | 3411 | await removeLastMessage(); |
| 3335 | $(this).remove(); | ||
| 3336 | }); | ||
| 3337 | await eventSource.emit(event_types.MESSAGE_DELETED, chat.length); | 3412 | await eventSource.emit(event_types.MESSAGE_DELETED, chat.length); |
| 3338 | } | 3413 | } |
| 3339 | } | 3414 | } |
| @@ -3581,6 +3656,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | |||
| 3581 | let chat2 = []; | 3656 | let chat2 = []; |
| 3582 | let continue_mag = ''; | 3657 | let continue_mag = ''; |
| 3583 | const userMessageIndices = []; | 3658 | const userMessageIndices = []; |
| 3659 | const lastUserMessageIndex = coreChat.findLastIndex(x => x.is_user); | ||
| 3584 | 3660 | ||
| 3585 | for (let i = coreChat.length - 1, j = 0; i >= 0; i--, j++) { | 3661 | for (let i = coreChat.length - 1, j = 0; i >= 0; i--, j++) { |
| 3586 | if (main_api == 'openai') { | 3662 | if (main_api == 'openai') { |
| @@ -3599,6 +3675,11 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | |||
| 3599 | chat2[i] = formatMessageHistoryItem(coreChat[j], isInstruct, force_output_sequence.FIRST); | 3675 | chat2[i] = formatMessageHistoryItem(coreChat[j], isInstruct, force_output_sequence.FIRST); |
| 3600 | } | 3676 | } |
| 3601 | 3677 | ||
| 3678 | if (lastUserMessageIndex >= 0 && j === lastUserMessageIndex && isInstruct) { | ||
| 3679 | // Reformat with the last input sequence (if any) | ||
| 3680 | chat2[i] = formatMessageHistoryItem(coreChat[j], isInstruct, force_output_sequence.LAST); | ||
| 3681 | } | ||
| 3682 | |||
| 3602 | // Do not suffix the message for continuation | 3683 | // Do not suffix the message for continuation |
| 3603 | if (i === 0 && isContinue) { | 3684 | if (i === 0 && isContinue) { |
| 3604 | if (isInstruct) { | 3685 | if (isInstruct) { |
| @@ -3624,7 +3705,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | |||
| 3624 | mes: power_user.instruct.user_alignment_message, | 3705 | mes: power_user.instruct.user_alignment_message, |
| 3625 | is_user: true, | 3706 | is_user: true, |
| 3626 | }; | 3707 | }; |
| 3627 | userAlignmentMessage = formatMessageHistoryItem(alignmentMessage, isInstruct, false); | 3708 | userAlignmentMessage = formatMessageHistoryItem(alignmentMessage, isInstruct, force_output_sequence.FIRST); |
| 3628 | } | 3709 | } |
| 3629 | 3710 | ||
| 3630 | // Call combined AN into Generate | 3711 | // Call combined AN into Generate |
| @@ -3886,7 +3967,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | |||
| 3886 | 3967 | ||
| 3887 | // Get instruct mode line | 3968 | // Get instruct mode line |
| 3888 | if (isInstruct && !isContinue) { | 3969 | if (isInstruct && !isContinue) { |
| 3889 | const name = (quiet_prompt && !quietToLoud) ? (quietName ?? 'System') : (isImpersonate ? name1 : name2); | 3970 | const name = (quiet_prompt && !quietToLoud && !isImpersonate) ? (quietName ?? 'System') : (isImpersonate ? name1 : name2); |
| 3890 | const isQuiet = quiet_prompt && type == 'quiet'; | 3971 | const isQuiet = quiet_prompt && type == 'quiet'; |
| 3891 | lastMesString += formatInstructModePrompt(name, isImpersonate, promptBias, name1, name2, isQuiet, quietToLoud); | 3972 | lastMesString += formatInstructModePrompt(name, isImpersonate, promptBias, name1, name2, isQuiet, quietToLoud); |
| 3892 | } | 3973 | } |
| @@ -4167,6 +4248,8 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | |||
| 4167 | } | 4248 | } |
| 4168 | } | 4249 | } |
| 4169 | 4250 | ||
| 4251 | await eventSource.emit(event_types.GENERATE_AFTER_DATA, generate_data); | ||
| 4252 | |||
| 4170 | if (dryRun) { | 4253 | if (dryRun) { |
| 4171 | generatedPromptCache = ''; | 4254 | generatedPromptCache = ''; |
| 4172 | return Promise.resolve(); | 4255 | return Promise.resolve(); |
| @@ -4698,6 +4781,7 @@ export async function sendMessageAsUser(messageText, messageBias, insertAt = nul | |||
| 4698 | await eventSource.emit(event_types.MESSAGE_SENT, chat_id); | 4781 | await eventSource.emit(event_types.MESSAGE_SENT, chat_id); |
| 4699 | addOneMessage(message); | 4782 | addOneMessage(message); |
| 4700 | await eventSource.emit(event_types.USER_MESSAGE_RENDERED, chat_id); | 4783 | await eventSource.emit(event_types.USER_MESSAGE_RENDERED, chat_id); |
| 4784 | await saveChatConditional(); | ||
| 4701 | } | 4785 | } |
| 4702 | } | 4786 | } |
| 4703 | 4787 | ||
| @@ -5029,7 +5113,7 @@ function setInContextMessages(lastmsg, type) { | |||
| 5029 | * @param {object} data Generation data | 5113 | * @param {object} data Generation data |
| 5030 | * @returns {Promise<object>} Response data from the API | 5114 | * @returns {Promise<object>} Response data from the API |
| 5031 | */ | 5115 | */ |
| 5032 | async function sendGenerationRequest(type, data) { | 5116 | export async function sendGenerationRequest(type, data) { |
| 5033 | if (main_api === 'openai') { | 5117 | if (main_api === 'openai') { |
| 5034 | return await sendOpenAIRequest(type, data.prompt, abortController.signal); | 5118 | return await sendOpenAIRequest(type, data.prompt, abortController.signal); |
| 5035 | } | 5119 | } |
| @@ -5061,7 +5145,7 @@ async function sendGenerationRequest(type, data) { | |||
| 5061 | * @param {object} data Generation data | 5145 | * @param {object} data Generation data |
| 5062 | * @returns {Promise<any>} Streaming generator | 5146 | * @returns {Promise<any>} Streaming generator |
| 5063 | */ | 5147 | */ |
| 5064 | async function sendStreamingRequest(type, data) { | 5148 | export async function sendStreamingRequest(type, data) { |
| 5065 | if (abortController?.signal?.aborted) { | 5149 | if (abortController?.signal?.aborted) { |
| 5066 | throw new Error('Generation was aborted.'); | 5150 | throw new Error('Generation was aborted.'); |
| 5067 | } | 5151 | } |
| @@ -5342,9 +5426,11 @@ export function cleanUpMessage(getMessage, isImpersonate, isContinue, displayInc | |||
| 5342 | getMessage = fixMarkdown(getMessage, false); | 5426 | getMessage = fixMarkdown(getMessage, false); |
| 5343 | } | 5427 | } |
| 5344 | 5428 | ||
| 5345 | const nameToTrim2 = isImpersonate ? name1 : name2; | 5429 | const nameToTrim2 = isImpersonate |
| 5430 | ? (!power_user.allow_name1_display ? name1 : '') | ||
| 5431 | : (!power_user.allow_name2_display ? name2 : ''); | ||
| 5346 | 5432 | ||
| 5347 | if (getMessage.startsWith(nameToTrim2 + ':')) { | 5433 | if (nameToTrim2 && getMessage.startsWith(nameToTrim2 + ':')) { |
| 5348 | getMessage = getMessage.replace(nameToTrim2 + ':', ''); | 5434 | getMessage = getMessage.replace(nameToTrim2 + ':', ''); |
| 5349 | getMessage = getMessage.trimStart(); | 5435 | getMessage = getMessage.trimStart(); |
| 5350 | } | 5436 | } |
| @@ -5565,6 +5651,7 @@ export function activateSendButtons() { | |||
| 5565 | is_send_press = false; | 5651 | is_send_press = false; |
| 5566 | $('#send_but').removeClass('displayNone'); | 5652 | $('#send_but').removeClass('displayNone'); |
| 5567 | $('#mes_continue').removeClass('displayNone'); | 5653 | $('#mes_continue').removeClass('displayNone'); |
| 5654 | $('#mes_impersonate').removeClass('displayNone'); | ||
| 5568 | $('.mes_buttons:last').show(); | 5655 | $('.mes_buttons:last').show(); |
| 5569 | hideStopButton(); | 5656 | hideStopButton(); |
| 5570 | } | 5657 | } |
| @@ -5572,6 +5659,7 @@ export function activateSendButtons() { | |||
| 5572 | export function deactivateSendButtons() { | 5659 | export function deactivateSendButtons() { |
| 5573 | $('#send_but').addClass('displayNone'); | 5660 | $('#send_but').addClass('displayNone'); |
| 5574 | $('#mes_continue').addClass('displayNone'); | 5661 | $('#mes_continue').addClass('displayNone'); |
| 5662 | $('#mes_impersonate').addClass('displayNone'); | ||
| 5575 | showStopButton(); | 5663 | showStopButton(); |
| 5576 | } | 5664 | } |
| 5577 | 5665 | ||
| @@ -6022,9 +6110,10 @@ async function getChatResult() { | |||
| 6022 | const message = getFirstMessage(); | 6110 | const message = getFirstMessage(); |
| 6023 | if (message.mes) { | 6111 | if (message.mes) { |
| 6024 | chat.push(message); | 6112 | chat.push(message); |
| 6025 | await saveChatConditional(); | ||
| 6026 | freshChat = true; | 6113 | freshChat = true; |
| 6027 | } | 6114 | } |
| 6115 | // Make sure the chat appears on the server | ||
| 6116 | await saveChatConditional(); | ||
| 6028 | } | 6117 | } |
| 6029 | await loadItemizedPrompts(getCurrentChatId()); | 6118 | await loadItemizedPrompts(getCurrentChatId()); |
| 6030 | await printMessages(); | 6119 | await printMessages(); |
| @@ -6076,7 +6165,7 @@ export async function openCharacterChat(file_name) { | |||
| 6076 | chat_metadata = {}; | 6165 | chat_metadata = {}; |
| 6077 | await getChat(); | 6166 | await getChat(); |
| 6078 | $('#selected_chat_pole').val(file_name); | 6167 | $('#selected_chat_pole').val(file_name); |
| 6079 | await createOrEditCharacter(); | 6168 | await createOrEditCharacter(new CustomEvent('newChat')); |
| 6080 | } | 6169 | } |
| 6081 | 6170 | ||
| 6082 | ////////// OPTIMZED MAIN API CHANGE FUNCTION //////////// | 6171 | ////////// OPTIMZED MAIN API CHANGE FUNCTION //////////// |
| @@ -6361,7 +6450,7 @@ export async function getSettings() { | |||
| 6361 | loadHordeSettings(settings); | 6450 | loadHordeSettings(settings); |
| 6362 | 6451 | ||
| 6363 | // Load power user settings | 6452 | // Load power user settings |
| 6364 | loadPowerUserSettings(settings, data); | 6453 | await loadPowerUserSettings(settings, data); |
| 6365 | 6454 | ||
| 6366 | // Load character tags | 6455 | // Load character tags |
| 6367 | loadTagsSettings(settings); | 6456 | loadTagsSettings(settings); |
| @@ -6778,6 +6867,11 @@ export async function displayPastChats() { | |||
| 6778 | const fileName = chat['file_name']; | 6867 | const fileName = chat['file_name']; |
| 6779 | const chatContent = rawChats[fileName]; | 6868 | const chatContent = rawChats[fileName]; |
| 6780 | 6869 | ||
| 6870 | // Make sure empty chats are displayed when there is no search query | ||
| 6871 | if (Array.isArray(chatContent) && !chatContent.length && !searchQuery) { | ||
| 6872 | return true; | ||
| 6873 | } | ||
| 6874 | |||
| 6781 | // // Uncomment this to return to old behavior (classical full-substring search). | 6875 | // // Uncomment this to return to old behavior (classical full-substring search). |
| 6782 | // return chatContent && Object.values(chatContent).some(message => message?.mes?.toLowerCase()?.includes(searchQuery.toLowerCase())); | 6876 | // return chatContent && Object.values(chatContent).some(message => message?.mes?.toLowerCase()?.includes(searchQuery.toLowerCase())); |
| 6783 | 6877 | ||
| @@ -7866,6 +7960,8 @@ window['SillyTavern'].getContext = function () { | |||
| 7866 | eventTypes: event_types, | 7960 | eventTypes: event_types, |
| 7867 | addOneMessage: addOneMessage, | 7961 | addOneMessage: addOneMessage, |
| 7868 | generate: Generate, | 7962 | generate: Generate, |
| 7963 | sendStreamingRequest: sendStreamingRequest, | ||
| 7964 | sendGenerationRequest: sendGenerationRequest, | ||
| 7869 | stopGeneration: stopGeneration, | 7965 | stopGeneration: stopGeneration, |
| 7870 | getTokenCount: getTokenCount, | 7966 | getTokenCount: getTokenCount, |
| 7871 | extensionPrompts: extension_prompts, | 7967 | extensionPrompts: extension_prompts, |
| @@ -8259,6 +8355,7 @@ const swipe_right = () => { | |||
| 8259 | }; | 8355 | }; |
| 8260 | 8356 | ||
| 8261 | const CONNECT_API_MAP = { | 8357 | const CONNECT_API_MAP = { |
| 8358 | // Default APIs not contined inside text gen / chat gen | ||
| 8262 | 'kobold': { | 8359 | 'kobold': { |
| 8263 | selected: 'kobold', | 8360 | selected: 'kobold', |
| 8264 | button: '#api_button', | 8361 | button: '#api_button', |
| @@ -8270,147 +8367,48 @@ const CONNECT_API_MAP = { | |||
| 8270 | selected: 'novel', | 8367 | selected: 'novel', |
| 8271 | button: '#api_button_novel', | 8368 | button: '#api_button_novel', |
| 8272 | }, | 8369 | }, |
| 8273 | 'ooba': { | 8370 | // KoboldCpp alias |
| 8274 | selected: 'textgenerationwebui', | ||
| 8275 | button: '#api_button_textgenerationwebui', | ||
| 8276 | type: textgen_types.OOBA, | ||
| 8277 | }, | ||
| 8278 | 'tabby': { | ||
| 8279 | selected: 'textgenerationwebui', | ||
| 8280 | button: '#api_button_textgenerationwebui', | ||
| 8281 | type: textgen_types.TABBY, | ||
| 8282 | }, | ||
| 8283 | 'llamacpp': { | ||
| 8284 | selected: 'textgenerationwebui', | ||
| 8285 | button: '#api_button_textgenerationwebui', | ||
| 8286 | type: textgen_types.LLAMACPP, | ||
| 8287 | }, | ||
| 8288 | 'ollama': { | ||
| 8289 | selected: 'textgenerationwebui', | ||
| 8290 | button: '#api_button_textgenerationwebui', | ||
| 8291 | type: textgen_types.OLLAMA, | ||
| 8292 | }, | ||
| 8293 | 'mancer': { | ||
| 8294 | selected: 'textgenerationwebui', | ||
| 8295 | button: '#api_button_textgenerationwebui', | ||
| 8296 | type: textgen_types.MANCER, | ||
| 8297 | }, | ||
| 8298 | 'vllm': { | ||
| 8299 | selected: 'textgenerationwebui', | ||
| 8300 | button: '#api_button_textgenerationwebui', | ||
| 8301 | type: textgen_types.VLLM, | ||
| 8302 | }, | ||
| 8303 | 'aphrodite': { | ||
| 8304 | selected: 'textgenerationwebui', | ||
| 8305 | button: '#api_button_textgenerationwebui', | ||
| 8306 | type: textgen_types.APHRODITE, | ||
| 8307 | }, | ||
| 8308 | 'koboldcpp': { | ||
| 8309 | selected: 'textgenerationwebui', | ||
| 8310 | button: '#api_button_textgenerationwebui', | ||
| 8311 | type: textgen_types.KOBOLDCPP, | ||
| 8312 | }, | ||
| 8313 | 'kcpp': { | 8371 | 'kcpp': { |
| 8314 | selected: 'textgenerationwebui', | 8372 | selected: 'textgenerationwebui', |
| 8315 | button: '#api_button_textgenerationwebui', | 8373 | button: '#api_button_textgenerationwebui', |
| 8316 | type: textgen_types.KOBOLDCPP, | 8374 | type: textgen_types.KOBOLDCPP, |
| 8317 | }, | 8375 | }, |
| 8318 | 'togetherai': { | 8376 | // OpenAI alias |
| 8319 | selected: 'textgenerationwebui', | ||
| 8320 | button: '#api_button_textgenerationwebui', | ||
| 8321 | type: textgen_types.TOGETHERAI, | ||
| 8322 | }, | ||
| 8323 | 'openai': { | ||
| 8324 | selected: 'openai', | ||
| 8325 | button: '#api_button_openai', | ||
| 8326 | source: chat_completion_sources.OPENAI, | ||
| 8327 | }, | ||
| 8328 | 'oai': { | 8377 | 'oai': { |
| 8329 | selected: 'openai', | 8378 | selected: 'openai', |
| 8330 | button: '#api_button_openai', | 8379 | button: '#api_button_openai', |
| 8331 | source: chat_completion_sources.OPENAI, | 8380 | source: chat_completion_sources.OPENAI, |
| 8332 | }, | 8381 | }, |
| 8333 | 'claude': { | 8382 | // OpenRouter special naming, to differentiate between chat comp and text comp |
| 8334 | selected: 'openai', | ||
| 8335 | button: '#api_button_openai', | ||
| 8336 | source: chat_completion_sources.CLAUDE, | ||
| 8337 | }, | ||
| 8338 | 'windowai': { | ||
| 8339 | selected: 'openai', | ||
| 8340 | button: '#api_button_openai', | ||
| 8341 | source: chat_completion_sources.WINDOWAI, | ||
| 8342 | }, | ||
| 8343 | 'openrouter': { | 8383 | 'openrouter': { |
| 8344 | selected: 'openai', | 8384 | selected: 'openai', |
| 8345 | button: '#api_button_openai', | 8385 | button: '#api_button_openai', |
| 8346 | source: chat_completion_sources.OPENROUTER, | 8386 | source: chat_completion_sources.OPENROUTER, |
| 8347 | }, | 8387 | }, |
| 8348 | 'scale': { | ||
| 8349 | selected: 'openai', | ||
| 8350 | button: '#api_button_openai', | ||
| 8351 | source: chat_completion_sources.SCALE, | ||
| 8352 | }, | ||
| 8353 | 'ai21': { | ||
| 8354 | selected: 'openai', | ||
| 8355 | button: '#api_button_openai', | ||
| 8356 | source: chat_completion_sources.AI21, | ||
| 8357 | }, | ||
| 8358 | 'makersuite': { | ||
| 8359 | selected: 'openai', | ||
| 8360 | button: '#api_button_openai', | ||
| 8361 | source: chat_completion_sources.MAKERSUITE, | ||
| 8362 | }, | ||
| 8363 | 'mistralai': { | ||
| 8364 | selected: 'openai', | ||
| 8365 | button: '#api_button_openai', | ||
| 8366 | source: chat_completion_sources.MISTRALAI, | ||
| 8367 | }, | ||
| 8368 | 'custom': { | ||
| 8369 | selected: 'openai', | ||
| 8370 | button: '#api_button_openai', | ||
| 8371 | source: chat_completion_sources.CUSTOM, | ||
| 8372 | }, | ||
| 8373 | 'cohere': { | ||
| 8374 | selected: 'openai', | ||
| 8375 | button: '#api_button_openai', | ||
| 8376 | source: chat_completion_sources.COHERE, | ||
| 8377 | }, | ||
| 8378 | 'perplexity': { | ||
| 8379 | selected: 'openai', | ||
| 8380 | button: '#api_button_openai', | ||
| 8381 | source: chat_completion_sources.PERPLEXITY, | ||
| 8382 | }, | ||
| 8383 | 'groq': { | ||
| 8384 | selected: 'openai', | ||
| 8385 | button: '#api_button_openai', | ||
| 8386 | source: chat_completion_sources.GROQ, | ||
| 8387 | }, | ||
| 8388 | '01ai': { | ||
| 8389 | selected: 'openai', | ||
| 8390 | button: '#api_button_openai', | ||
| 8391 | source: chat_completion_sources.ZEROONEAI, | ||
| 8392 | }, | ||
| 8393 | 'infermaticai': { | ||
| 8394 | selected: 'textgenerationwebui', | ||
| 8395 | button: '#api_button_textgenerationwebui', | ||
| 8396 | type: textgen_types.INFERMATICAI, | ||
| 8397 | }, | ||
| 8398 | 'dreamgen': { | ||
| 8399 | selected: 'textgenerationwebui', | ||
| 8400 | button: '#api_button_textgenerationwebui', | ||
| 8401 | type: textgen_types.DREAMGEN, | ||
| 8402 | }, | ||
| 8403 | 'openrouter-text': { | 8388 | 'openrouter-text': { |
| 8404 | selected: 'textgenerationwebui', | 8389 | selected: 'textgenerationwebui', |
| 8405 | button: '#api_button_textgenerationwebui', | 8390 | button: '#api_button_textgenerationwebui', |
| 8406 | type: textgen_types.OPENROUTER, | 8391 | type: textgen_types.OPENROUTER, |
| 8407 | }, | 8392 | }, |
| 8408 | 'huggingface': { | 8393 | }; |
| 8394 | |||
| 8395 | // Fill connections map from textgen_types and chat_completion_sources | ||
| 8396 | for (const textGenType of Object.values(textgen_types)) { | ||
| 8397 | if (CONNECT_API_MAP[textGenType]) continue; | ||
| 8398 | CONNECT_API_MAP[textGenType] = { | ||
| 8409 | selected: 'textgenerationwebui', | 8399 | selected: 'textgenerationwebui', |
| 8410 | button: '#api_button_textgenerationwebui', | 8400 | button: '#api_button_textgenerationwebui', |
| 8411 | type: textgen_types.HUGGINGFACE, | 8401 | type: textGenType, |
| 8412 | }, | 8402 | }; |
| 8413 | }; | 8403 | } |
| 8404 | for (const chatCompletionSource of Object.values(chat_completion_sources)) { | ||
| 8405 | if (CONNECT_API_MAP[chatCompletionSource]) continue; | ||
| 8406 | CONNECT_API_MAP[chatCompletionSource] = { | ||
| 8407 | selected: 'openai', | ||
| 8408 | button: '#api_button_openai', | ||
| 8409 | source: chatCompletionSource, | ||
| 8410 | }; | ||
| 8411 | } | ||
| 8414 | 8412 | ||
| 8415 | async function selectContextCallback(_, name) { | 8413 | async function selectContextCallback(_, name) { |
| 8416 | if (!name) { | 8414 | if (!name) { |
| @@ -8543,7 +8541,7 @@ export async function processDroppedFiles(files, data = new Map()) { | |||
| 8543 | 8541 | ||
| 8544 | for (const file of files) { | 8542 | for (const file of files) { |
| 8545 | const extension = file.name.split('.').pop().toLowerCase(); | 8543 | const extension = file.name.split('.').pop().toLowerCase(); |
| 8546 | if (allowedMimeTypes.includes(file.type) || allowedExtensions.includes(extension)) { | 8544 | if (allowedMimeTypes.some(x => file.type.startsWith(x)) || allowedExtensions.includes(extension)) { |
| 8547 | const preservedName = data instanceof Map && data.get(file); | 8545 | const preservedName = data instanceof Map && data.get(file); |
| 8548 | await importCharacter(file, preservedName); | 8546 | await importCharacter(file, preservedName); |
| 8549 | } else { | 8547 | } else { |
| @@ -9142,14 +9140,14 @@ jQuery(async function () { | |||
| 9142 | $('#send_textarea').on('focusin focus click', () => { | 9140 | $('#send_textarea').on('focusin focus click', () => { |
| 9143 | S_TAPreviouslyFocused = true; | 9141 | S_TAPreviouslyFocused = true; |
| 9144 | }); | 9142 | }); |
| 9145 | $('#send_but, #option_regenerate, #option_continue, #mes_continue').on('click', () => { | 9143 | $('#send_but, #option_regenerate, #option_continue, #mes_continue, #mes_impersonate').on('click', () => { |
| 9146 | if (S_TAPreviouslyFocused) { | 9144 | if (S_TAPreviouslyFocused) { |
| 9147 | $('#send_textarea').focus(); | 9145 | $('#send_textarea').focus(); |
| 9148 | } | 9146 | } |
| 9149 | }); | 9147 | }); |
| 9150 | $(document).click(event => { | 9148 | $(document).click(event => { |
| 9151 | if ($(':focus').attr('id') !== 'send_textarea') { | 9149 | if ($(':focus').attr('id') !== 'send_textarea') { |
| 9152 | var validIDs = ['options_button', 'send_but', 'mes_continue', 'send_textarea', 'option_regenerate', 'option_continue']; | 9150 | var validIDs = ['options_button', 'send_but', 'mes_impersonate', 'mes_continue', 'send_textarea', 'option_regenerate', 'option_continue']; |
| 9153 | if (!validIDs.includes($(event.target).attr('id'))) { | 9151 | if (!validIDs.includes($(event.target).attr('id'))) { |
| 9154 | S_TAPreviouslyFocused = false; | 9152 | S_TAPreviouslyFocused = false; |
| 9155 | } | 9153 | } |
| @@ -9185,6 +9183,9 @@ jQuery(async function () { | |||
| 9185 | debouncedCharacterSearch(searchQuery); | 9183 | debouncedCharacterSearch(searchQuery); |
| 9186 | }); | 9184 | }); |
| 9187 | 9185 | ||
| 9186 | $('#mes_impersonate').on('click', function () { | ||
| 9187 | $('#option_impersonate').trigger('click'); | ||
| 9188 | }); | ||
| 9188 | 9189 | ||
| 9189 | $('#mes_continue').on('click', function () { | 9190 | $('#mes_continue').on('click', function () { |
| 9190 | $('#option_continue').trigger('click'); | 9191 | $('#option_continue').trigger('click'); |
| @@ -9806,8 +9807,8 @@ jQuery(async function () { | |||
| 9806 | hideMenu(); | 9807 | hideMenu(); |
| 9807 | }); | 9808 | }); |
| 9808 | 9809 | ||
| 9809 | $('#newChatFromManageScreenButton').on('click', function () { | 9810 | $('#newChatFromManageScreenButton').on('click', async function () { |
| 9810 | doNewChat({ deleteCurrentChat: false }); | 9811 | await doNewChat({ deleteCurrentChat: false }); |
| 9811 | $('#select_chat_cross').trigger('click'); | 9812 | $('#select_chat_cross').trigger('click'); |
| 9812 | }); | 9813 | }); |
| 9813 | 9814 | ||
| @@ -10482,8 +10483,9 @@ jQuery(async function () { | |||
| 10482 | } | 10483 | } |
| 10483 | 10484 | ||
| 10484 | // Set the height of "autoSetHeight" textareas within the drawer to their scroll height | 10485 | // Set the height of "autoSetHeight" textareas within the drawer to their scroll height |
| 10485 | $(this).closest('.drawer').find('.drawer-content textarea.autoSetHeight').each(function () { | 10486 | $(this).closest('.drawer').find('.drawer-content textarea.autoSetHeight').each(async function () { |
| 10486 | resetScrollHeight($(this)); | 10487 | await resetScrollHeight($(this)); |
| 10488 | return; | ||
| 10487 | }); | 10489 | }); |
| 10488 | 10490 | ||
| 10489 | } else if (drawerWasOpenAlready) { //to close manually | 10491 | } else if (drawerWasOpenAlready) { //to close manually |
| @@ -10556,8 +10558,9 @@ jQuery(async function () { | |||
| 10556 | $(this).closest('.inline-drawer').find('.inline-drawer-content').stop().slideToggle(); | 10558 | $(this).closest('.inline-drawer').find('.inline-drawer-content').stop().slideToggle(); |
| 10557 | 10559 | ||
| 10558 | // Set the height of "autoSetHeight" textareas within the inline-drawer to their scroll height | 10560 | // Set the height of "autoSetHeight" textareas within the inline-drawer to their scroll height |
| 10559 | $(this).closest('.inline-drawer').find('.inline-drawer-content textarea.autoSetHeight').each(function () { | 10561 | $(this).closest('.inline-drawer').find('.inline-drawer-content textarea.autoSetHeight').each(async function () { |
| 10560 | resetScrollHeight($(this)); | 10562 | await resetScrollHeight($(this)); |
| 10563 | return; | ||
| 10561 | }); | 10564 | }); |
| 10562 | }); | 10565 | }); |
| 10563 | 10566 | ||
| @@ -10638,12 +10641,19 @@ jQuery(async function () { | |||
| 10638 | } | 10641 | } |
| 10639 | }); | 10642 | }); |
| 10640 | 10643 | ||
| 10641 | $(document).on('click', '#OpenAllWIEntries', function () { | 10644 | document.addEventListener('click', function (e) { |
| 10642 | $('#world_popup_entries_list').children().find('.down').click(); | 10645 | if (!(e.target instanceof HTMLElement)) return; |
| 10643 | }); | 10646 | if (e.target.matches('#OpenAllWIEntries')) { |
| 10644 | $(document).on('click', '#CloseAllWIEntries', function () { | 10647 | document.querySelectorAll('#world_popup_entries_list .inline-drawer').forEach((/** @type {HTMLElement} */ drawer) => { |
| 10645 | $('#world_popup_entries_list').children().find('.up').click(); | 10648 | toggleDrawer(drawer, true); |
| 10649 | }); | ||
| 10650 | } else if (e.target.matches('#CloseAllWIEntries')) { | ||
| 10651 | document.querySelectorAll('#world_popup_entries_list .inline-drawer').forEach((/** @type {HTMLElement} */ drawer) => { | ||
| 10652 | toggleDrawer(drawer, false); | ||
| 10653 | }); | ||
| 10654 | } | ||
| 10646 | }); | 10655 | }); |
| 10656 | |||
| 10647 | $(document).on('click', '.open_alternate_greetings', openAlternateGreetings); | 10657 | $(document).on('click', '.open_alternate_greetings', openAlternateGreetings); |
| 10648 | /* $('#set_character_world').on('click', openCharacterWorldPopup); */ | 10658 | /* $('#set_character_world').on('click', openCharacterWorldPopup); */ |
| 10649 | 10659 | ||
| @@ -10738,7 +10748,7 @@ jQuery(async function () { | |||
| 10738 | } | 10748 | } |
| 10739 | } break; | 10749 | } break; |
| 10740 | case 'import_tags': { | 10750 | case 'import_tags': { |
| 10741 | await importTags(characters[this_chid], { forceShow: true }); | 10751 | await importTags(characters[this_chid], { importSetting: tag_import_setting.ASK }); |
| 10742 | } break; | 10752 | } break; |
| 10743 | /*case 'delete_button': | 10753 | /*case 'delete_button': |
| 10744 | popup_type = "del_ch"; | 10754 | popup_type = "del_ch"; |
| @@ -10767,62 +10777,66 @@ jQuery(async function () { | |||
| 10767 | var isManualInput = false; | 10777 | var isManualInput = false; |
| 10768 | var valueBeforeManualInput; | 10778 | var valueBeforeManualInput; |
| 10769 | 10779 | ||
| 10770 | $('.range-block-counter input, .neo-range-input').on('click', function () { | 10780 | $(document).on('input', '.range-block-counter input, .neo-range-input', function () { |
| 10771 | valueBeforeManualInput = $(this).val(); | 10781 | valueBeforeManualInput = $(this).val(); |
| 10772 | console.log(valueBeforeManualInput); | 10782 | console.log(valueBeforeManualInput); |
| 10773 | }) | 10783 | }); |
| 10774 | .on('change', function (e) { | 10784 | |
| 10775 | e.target.focus(); | 10785 | $(document).on('change', '.range-block-counter input, .neo-range-input', function (e) { |
| 10776 | e.target.dispatchEvent(new Event('keyup')); | 10786 | e.target.focus(); |
| 10777 | }) | 10787 | e.target.dispatchEvent(new KeyboardEvent('keyup', { bubbles: true })); |
| 10778 | .on('keydown', function (e) { | 10788 | }); |
| 10779 | const masterSelector = '#' + $(this).data('for'); | 10789 | |
| 10780 | const masterElement = $(masterSelector); | 10790 | $(document).on('keydown', '.range-block-counter input, .neo-range-input', function (e) { |
| 10781 | if (e.key === 'Enter') { | 10791 | const masterSelector = '#' + $(this).data('for'); |
| 10782 | let manualInput = Number($(this).val()); | 10792 | const masterElement = $(masterSelector); |
| 10783 | if (isManualInput) { | 10793 | if (e.key === 'Enter') { |
| 10784 | //disallow manual inputs outside acceptable range | ||
| 10785 | if (manualInput >= Number($(this).attr('min')) && manualInput <= Number($(this).attr('max'))) { | ||
| 10786 | //if value is ok, assign to slider and update handle text and position | ||
| 10787 | //newSlider.val(manualInput) | ||
| 10788 | //handleSlideEvent.call(newSlider, null, { value: parseFloat(manualInput) }, 'manual'); | ||
| 10789 | valueBeforeManualInput = manualInput; | ||
| 10790 | $(masterElement).val($(this).val()).trigger('input'); | ||
| 10791 | } else { | ||
| 10792 | //if value not ok, warn and reset to last known valid value | ||
| 10793 | toastr.warning(`Invalid value. Must be between ${$(this).attr('min')} and ${$(this).attr('max')}`); | ||
| 10794 | console.log(valueBeforeManualInput); | ||
| 10795 | //newSlider.val(valueBeforeManualInput) | ||
| 10796 | $(this).val(valueBeforeManualInput); | ||
| 10797 | } | ||
| 10798 | } | ||
| 10799 | } | ||
| 10800 | }) | ||
| 10801 | .on('keyup', function () { | ||
| 10802 | valueBeforeManualInput = $(this).val(); | ||
| 10803 | console.log(valueBeforeManualInput); | ||
| 10804 | isManualInput = true; | ||
| 10805 | }) | ||
| 10806 | //trigger slider changes when user clicks away | ||
| 10807 | .on('mouseup blur', function () { | ||
| 10808 | const masterSelector = '#' + $(this).data('for'); | ||
| 10809 | const masterElement = $(masterSelector); | ||
| 10810 | let manualInput = Number($(this).val()); | 10794 | let manualInput = Number($(this).val()); |
| 10811 | if (isManualInput) { | 10795 | if (isManualInput) { |
| 10812 | //if value is between correct range for the slider | 10796 | //disallow manual inputs outside acceptable range |
| 10813 | if (manualInput >= Number($(this).attr('min')) && manualInput <= Number($(this).attr('max'))) { | 10797 | if (manualInput >= Number($(this).attr('min')) && manualInput <= Number($(this).attr('max'))) { |
| 10798 | //if value is ok, assign to slider and update handle text and position | ||
| 10799 | //newSlider.val(manualInput) | ||
| 10800 | //handleSlideEvent.call(newSlider, null, { value: parseFloat(manualInput) }, 'manual'); | ||
| 10814 | valueBeforeManualInput = manualInput; | 10801 | valueBeforeManualInput = manualInput; |
| 10815 | //set the slider value to input value | 10802 | $(masterElement).val($(this).val()).trigger('input', { forced: true }); |
| 10816 | $(masterElement).val($(this).val()).trigger('input'); | ||
| 10817 | } else { | 10803 | } else { |
| 10818 | //if value not ok, warn and reset to last known valid value | 10804 | //if value not ok, warn and reset to last known valid value |
| 10819 | toastr.warning(`Invalid value. Must be between ${$(this).attr('min')} and ${$(this).attr('max')}`); | 10805 | toastr.warning(`Invalid value. Must be between ${$(this).attr('min')} and ${$(this).attr('max')}`); |
| 10820 | console.log(valueBeforeManualInput); | 10806 | console.log(valueBeforeManualInput); |
| 10807 | //newSlider.val(valueBeforeManualInput) | ||
| 10821 | $(this).val(valueBeforeManualInput); | 10808 | $(this).val(valueBeforeManualInput); |
| 10822 | } | 10809 | } |
| 10823 | } | 10810 | } |
| 10824 | isManualInput = false; | 10811 | } |
| 10825 | }); | 10812 | }); |
| 10813 | |||
| 10814 | $(document).on('keyup', '.range-block-counter input, .neo-range-input', function () { | ||
| 10815 | valueBeforeManualInput = $(this).val(); | ||
| 10816 | console.log(valueBeforeManualInput); | ||
| 10817 | isManualInput = true; | ||
| 10818 | }); | ||
| 10819 | |||
| 10820 | //trigger slider changes when user clicks away | ||
| 10821 | $(document).on('mouseup blur', '.range-block-counter input, .neo-range-input', function () { | ||
| 10822 | const masterSelector = '#' + $(this).data('for'); | ||
| 10823 | const masterElement = $(masterSelector); | ||
| 10824 | let manualInput = Number($(this).val()); | ||
| 10825 | if (isManualInput) { | ||
| 10826 | //if value is between correct range for the slider | ||
| 10827 | if (manualInput >= Number($(this).attr('min')) && manualInput <= Number($(this).attr('max'))) { | ||
| 10828 | valueBeforeManualInput = manualInput; | ||
| 10829 | //set the slider value to input value | ||
| 10830 | $(masterElement).val($(this).val()).trigger('input', { forced: true }); | ||
| 10831 | } else { | ||
| 10832 | //if value not ok, warn and reset to last known valid value | ||
| 10833 | toastr.warning(`Invalid value. Must be between ${$(this).attr('min')} and ${$(this).attr('max')}`); | ||
| 10834 | console.log(valueBeforeManualInput); | ||
| 10835 | $(this).val(valueBeforeManualInput); | ||
| 10836 | } | ||
| 10837 | } | ||
| 10838 | isManualInput = false; | ||
| 10839 | }); | ||
| 10826 | 10840 | ||
| 10827 | $('.user_stats_button').on('click', function () { | 10841 | $('.user_stats_button').on('click', function () { |
| 10828 | userStatsHandler(); | 10842 | userStatsHandler(); |
| @@ -18,7 +18,7 @@ import { | |||
| 18 | import { favsToHotswap } from './RossAscends-mods.js'; | 18 | import { favsToHotswap } from './RossAscends-mods.js'; |
| 19 | import { hideLoader, showLoader } from './loader.js'; | 19 | import { hideLoader, showLoader } from './loader.js'; |
| 20 | import { convertCharacterToPersona } from './personas.js'; | 20 | import { convertCharacterToPersona } from './personas.js'; |
| 21 | import { createTagInput, getTagKeyForEntity, getTagsList, printTagList, tag_map, compareTagsForSort, removeTagFromMap } from './tags.js'; | 21 | import { createTagInput, getTagKeyForEntity, getTagsList, printTagList, tag_map, compareTagsForSort, removeTagFromMap, importTags, tag_import_setting } from './tags.js'; |
| 22 | 22 | ||
| 23 | /** | 23 | /** |
| 24 | * Static object representing the actions of the | 24 | * Static object representing the actions of the |
| @@ -197,10 +197,10 @@ class BulkTagPopupHandler { | |||
| 197 | #getHtml = () => { | 197 | #getHtml = () => { |
| 198 | const characterData = JSON.stringify({ characterIds: this.characterIds }); | 198 | const characterData = JSON.stringify({ characterIds: this.characterIds }); |
| 199 | return `<div id="bulk_tag_shadow_popup"> | 199 | return `<div id="bulk_tag_shadow_popup"> |
| 200 | <div id="bulk_tag_popup"> | 200 | <div id="bulk_tag_popup" class="wider_dialogue_popup"> |
| 201 | <div id="bulk_tag_popup_holder"> | 201 | <div id="bulk_tag_popup_holder"> |
| 202 | <h3 class="marginBot5">Modify tags of ${this.characterIds.length} characters</h3> | 202 | <h3 class="marginBot5">Modify tags of ${this.characterIds.length} characters</h3> |
| 203 | <small class="bulk_tags_desc m-b-1">Add or remove the mutual tags of all selected characters.</small> | 203 | <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> |
| 204 | <div id="bulk_tags_avatars_block" class="avatars_inline avatars_inline_small tags tags_inline"></div> | 204 | <div id="bulk_tags_avatars_block" class="avatars_inline avatars_inline_small tags tags_inline"></div> |
| 205 | <br> | 205 | <br> |
| 206 | <div id="bulk_tags_div" class="marginBot5" data-characters='${characterData}'> | 206 | <div id="bulk_tags_div" class="marginBot5" data-characters='${characterData}'> |
| @@ -219,6 +219,12 @@ class BulkTagPopupHandler { | |||
| 219 | <i class="fa-solid fa-trash-can margin-right-10px"></i> | 219 | <i class="fa-solid fa-trash-can margin-right-10px"></i> |
| 220 | Mutual | 220 | Mutual |
| 221 | </div> | 221 | </div> |
| 222 | <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"> | ||
| 223 | Import All | ||
| 224 | </div> | ||
| 225 | <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"> | ||
| 226 | Import Existing | ||
| 227 | </div> | ||
| 222 | <div id="bulk_tag_popup_cancel" class="menu_button" data-i18n="Cancel">Close</div> | 228 | <div id="bulk_tag_popup_cancel" class="menu_button" data-i18n="Cancel">Close</div> |
| 223 | </div> | 229 | </div> |
| 224 | </div> | 230 | </div> |
| @@ -254,6 +260,30 @@ class BulkTagPopupHandler { | |||
| 254 | document.querySelector('#bulk_tag_popup_reset').addEventListener('click', this.resetTags.bind(this)); | 260 | document.querySelector('#bulk_tag_popup_reset').addEventListener('click', this.resetTags.bind(this)); |
| 255 | document.querySelector('#bulk_tag_popup_remove_mutual').addEventListener('click', this.removeMutual.bind(this)); | 261 | document.querySelector('#bulk_tag_popup_remove_mutual').addEventListener('click', this.removeMutual.bind(this)); |
| 256 | document.querySelector('#bulk_tag_popup_cancel').addEventListener('click', this.hide.bind(this)); | 262 | document.querySelector('#bulk_tag_popup_cancel').addEventListener('click', this.hide.bind(this)); |
| 263 | document.querySelector('#bulk_tag_popup_import_all_tags').addEventListener('click', this.importAllTags.bind(this)); | ||
| 264 | document.querySelector('#bulk_tag_popup_import_existing_tags').addEventListener('click', this.importExistingTags.bind(this)); | ||
| 265 | } | ||
| 266 | |||
| 267 | /** | ||
| 268 | * Import existing tags for all selected characters | ||
| 269 | */ | ||
| 270 | async importExistingTags() { | ||
| 271 | for (const characterId of this.characterIds) { | ||
| 272 | await importTags(characters[characterId], { importSetting: tag_import_setting.ONLY_EXISTING }); | ||
| 273 | } | ||
| 274 | |||
| 275 | $('#bulkTagList').empty(); | ||
| 276 | } | ||
| 277 | |||
| 278 | /** | ||
| 279 | * Import all tags for all selected characters | ||
| 280 | */ | ||
| 281 | async importAllTags() { | ||
| 282 | for (const characterId of this.characterIds) { | ||
| 283 | await importTags(characters[characterId], { importSetting: tag_import_setting.ALL }); | ||
| 284 | } | ||
| 285 | |||
| 286 | $('#bulkTagList').empty(); | ||
| 257 | } | 287 | } |
| 258 | 288 | ||
| 259 | /** | 289 | /** |
| @@ -570,7 +600,7 @@ class BulkEditOverlay { | |||
| 570 | this.container.removeEventListener('mouseup', cancelHold); | 600 | this.container.removeEventListener('mouseup', cancelHold); |
| 571 | this.container.removeEventListener('touchend', cancelHold); | 601 | this.container.removeEventListener('touchend', cancelHold); |
| 572 | }, | 602 | }, |
| 573 | BulkEditOverlay.longPressDelay); | 603 | BulkEditOverlay.longPressDelay); |
| 574 | }; | 604 | }; |
| 575 | 605 | ||
| 576 | handleLongPressEnd = (event) => { | 606 | handleLongPressEnd = (event) => { |
| @@ -157,18 +157,15 @@ export function shouldSendOnEnter() { | |||
| 157 | //Does not break old characters/chats, as the code just uses whatever timestamp exists in the chat. | 157 | //Does not break old characters/chats, as the code just uses whatever timestamp exists in the chat. |
| 158 | //New chats made with characters will use this new formatting. | 158 | //New chats made with characters will use this new formatting. |
| 159 | export function humanizedDateTime() { | 159 | export function humanizedDateTime() { |
| 160 | let baseDate = new Date(Date.now()); | 160 | const now = new Date(Date.now()); |
| 161 | let humanYear = baseDate.getFullYear(); | 161 | const dt = { |
| 162 | let humanMonth = baseDate.getMonth() + 1; | 162 | year: now.getFullYear(), month: now.getMonth() + 1, day: now.getDate(), |
| 163 | let humanDate = baseDate.getDate(); | 163 | hour: now.getHours(), minute: now.getMinutes(), second: now.getSeconds(), |
| 164 | let humanHour = (baseDate.getHours() < 10 ? '0' : '') + baseDate.getHours(); | 164 | }; |
| 165 | let humanMinute = | 165 | for (const key in dt) { |
| 166 | (baseDate.getMinutes() < 10 ? '0' : '') + baseDate.getMinutes(); | 166 | dt[key] = dt[key].toString().padStart(2, '0'); |
| 167 | let humanSecond = | 167 | } |
| 168 | (baseDate.getSeconds() < 10 ? '0' : '') + baseDate.getSeconds(); | 168 | return `${dt.year}-${dt.month}-${dt.day}@${dt.hour}h${dt.minute}m${dt.second}s`; |
| 169 | let HumanizedDateTime = | ||
| 170 | humanYear + '-' + humanMonth + '-' + humanDate + '@' + humanHour + 'h' + humanMinute + 'm' + humanSecond + 's'; | ||
| 171 | return HumanizedDateTime; | ||
| 172 | } | 169 | } |
| 173 | 170 | ||
| 174 | //this is a common format version to display a timestamp on each chat message | 171 | //this is a common format version to display a timestamp on each chat message |
| @@ -314,6 +311,7 @@ function RA_checkOnlineStatus() { | |||
| 314 | $('#send_form').addClass('no-connection'); //entire input form area is red when not connected | 311 | $('#send_form').addClass('no-connection'); //entire input form area is red when not connected |
| 315 | $('#send_but').addClass('displayNone'); //send button is hidden when not connected; | 312 | $('#send_but').addClass('displayNone'); //send button is hidden when not connected; |
| 316 | $('#mes_continue').addClass('displayNone'); //continue button is hidden when not connected; | 313 | $('#mes_continue').addClass('displayNone'); //continue button is hidden when not connected; |
| 314 | $('#mes_impersonate').addClass('displayNone'); //continue button is hidden when not connected; | ||
| 317 | $('#API-status-top').removeClass('fa-plug'); | 315 | $('#API-status-top').removeClass('fa-plug'); |
| 318 | $('#API-status-top').addClass('fa-plug-circle-exclamation redOverlayGlow'); | 316 | $('#API-status-top').addClass('fa-plug-circle-exclamation redOverlayGlow'); |
| 319 | connection_made = false; | 317 | connection_made = false; |
| @@ -330,6 +328,7 @@ function RA_checkOnlineStatus() { | |||
| 330 | if (!is_send_press && !(selected_group && is_group_generating)) { | 328 | if (!is_send_press && !(selected_group && is_group_generating)) { |
| 331 | $('#send_but').removeClass('displayNone'); //on connect, send button shows | 329 | $('#send_but').removeClass('displayNone'); //on connect, send button shows |
| 332 | $('#mes_continue').removeClass('displayNone'); //continue button is shown when connected | 330 | $('#mes_continue').removeClass('displayNone'); //continue button is shown when connected |
| 331 | $('#mes_impersonate').removeClass('displayNone'); //continue button is shown when connected | ||
| 333 | } | 332 | } |
| 334 | } | 333 | } |
| 335 | } | 334 | } |
| @@ -381,6 +380,7 @@ function RA_autoconnect(PrevApi) { | |||
| 381 | || (secret_state[SECRET_KEYS.PERPLEXITY] && oai_settings.chat_completion_source == chat_completion_sources.PERPLEXITY) | 380 | || (secret_state[SECRET_KEYS.PERPLEXITY] && oai_settings.chat_completion_source == chat_completion_sources.PERPLEXITY) |
| 382 | || (secret_state[SECRET_KEYS.GROQ] && oai_settings.chat_completion_source == chat_completion_sources.GROQ) | 381 | || (secret_state[SECRET_KEYS.GROQ] && oai_settings.chat_completion_source == chat_completion_sources.GROQ) |
| 383 | || (secret_state[SECRET_KEYS.ZEROONEAI] && oai_settings.chat_completion_source == chat_completion_sources.ZEROONEAI) | 382 | || (secret_state[SECRET_KEYS.ZEROONEAI] && oai_settings.chat_completion_source == chat_completion_sources.ZEROONEAI) |
| 383 | || (secret_state[SECRET_KEYS.BLOCKENTROPY] && oai_settings.chat_completion_source == chat_completion_sources.BLOCKENTROPY) | ||
| 384 | || (isValidUrl(oai_settings.custom_url) && oai_settings.chat_completion_source == chat_completion_sources.CUSTOM) | 384 | || (isValidUrl(oai_settings.custom_url) && oai_settings.chat_completion_source == chat_completion_sources.CUSTOM) |
| 385 | ) { | 385 | ) { |
| 386 | $('#api_button_openai').trigger('click'); | 386 | $('#api_button_openai').trigger('click'); |
| @@ -16,8 +16,15 @@ export const AUTOCOMPLETE_WIDTH = { | |||
| 16 | 'FULL': 2, | 16 | 'FULL': 2, |
| 17 | }; | 17 | }; |
| 18 | 18 | ||
| 19 | /**@readonly*/ | ||
| 20 | /**@enum {Number}*/ | ||
| 21 | export const AUTOCOMPLETE_SELECT_KEY = { | ||
| 22 | 'TAB': 1, // 2^0 | ||
| 23 | 'ENTER': 2, // 2^1 | ||
| 24 | }; | ||
| 25 | |||
| 19 | export class AutoComplete { | 26 | export class AutoComplete { |
| 20 | /**@type {HTMLTextAreaElement}*/ textarea; | 27 | /**@type {HTMLTextAreaElement|HTMLInputElement}*/ textarea; |
| 21 | /**@type {boolean}*/ isFloating = false; | 28 | /**@type {boolean}*/ isFloating = false; |
| 22 | /**@type {()=>boolean}*/ checkIfActivate; | 29 | /**@type {()=>boolean}*/ checkIfActivate; |
| 23 | /**@type {(text:string, index:number) => Promise<AutoCompleteNameResult>}*/ getNameAt; | 30 | /**@type {(text:string, index:number) => Promise<AutoCompleteNameResult>}*/ getNameAt; |
| @@ -56,6 +63,8 @@ export class AutoComplete { | |||
| 56 | /**@type {function}*/ updateDetailsPositionDebounced; | 63 | /**@type {function}*/ updateDetailsPositionDebounced; |
| 57 | /**@type {function}*/ updateFloatingPositionDebounced; | 64 | /**@type {function}*/ updateFloatingPositionDebounced; |
| 58 | 65 | ||
| 66 | /**@type {(item:AutoCompleteOption)=>any}*/ onSelect; | ||
| 67 | |||
| 59 | get matchType() { | 68 | get matchType() { |
| 60 | return power_user.stscript.matching ?? 'fuzzy'; | 69 | return power_user.stscript.matching ?? 'fuzzy'; |
| 61 | } | 70 | } |
| @@ -68,7 +77,7 @@ export class AutoComplete { | |||
| 68 | 77 | ||
| 69 | 78 | ||
| 70 | /** | 79 | /** |
| 71 | * @param {HTMLTextAreaElement} textarea The textarea to receive autocomplete. | 80 | * @param {HTMLTextAreaElement|HTMLInputElement} textarea The textarea to receive autocomplete. |
| 72 | * @param {() => boolean} checkIfActivate Function should return true only if under the current conditions, autocomplete should display (e.g., for slash commands: autoComplete.text[0] == '/') | 81 | * @param {() => boolean} checkIfActivate Function should return true only if under the current conditions, autocomplete should display (e.g., for slash commands: autoComplete.text[0] == '/') |
| 73 | * @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. | 82 | * @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. |
| 74 | * @param {boolean} isFloating Whether autocomplete should float at the keyboard cursor. | 83 | * @param {boolean} isFloating Whether autocomplete should float at the keyboard cursor. |
| @@ -102,10 +111,15 @@ export class AutoComplete { | |||
| 102 | this.updateDetailsPositionDebounced = debounce(this.updateDetailsPosition.bind(this), 10); | 111 | this.updateDetailsPositionDebounced = debounce(this.updateDetailsPosition.bind(this), 10); |
| 103 | this.updateFloatingPositionDebounced = debounce(this.updateFloatingPosition.bind(this), 10); | 112 | this.updateFloatingPositionDebounced = debounce(this.updateFloatingPosition.bind(this), 10); |
| 104 | 113 | ||
| 105 | textarea.addEventListener('input', ()=>this.text != this.textarea.value && this.show(true, this.wasForced)); | 114 | textarea.addEventListener('input', ()=>{ |
| 115 | this.selectionStart = this.textarea.selectionStart; | ||
| 116 | if (this.text != this.textarea.value) this.show(true, this.wasForced); | ||
| 117 | }); | ||
| 106 | textarea.addEventListener('keydown', (evt)=>this.handleKeyDown(evt)); | 118 | textarea.addEventListener('keydown', (evt)=>this.handleKeyDown(evt)); |
| 107 | textarea.addEventListener('click', ()=>this.isActive ? this.show() : null); | 119 | textarea.addEventListener('click', ()=>{ |
| 108 | textarea.addEventListener('selectionchange', ()=>this.show()); | 120 | this.selectionStart = this.textarea.selectionStart; |
| 121 | if (this.isActive) this.show(); | ||
| 122 | }); | ||
| 109 | textarea.addEventListener('blur', ()=>this.hide()); | 123 | textarea.addEventListener('blur', ()=>this.hide()); |
| 110 | if (isFloating) { | 124 | if (isFloating) { |
| 111 | textarea.addEventListener('scroll', ()=>this.updateFloatingPositionDebounced()); | 125 | textarea.addEventListener('scroll', ()=>this.updateFloatingPositionDebounced()); |
| @@ -189,6 +203,11 @@ export class AutoComplete { | |||
| 189 | * @returns The option. | 203 | * @returns The option. |
| 190 | */ | 204 | */ |
| 191 | fuzzyScore(option) { | 205 | fuzzyScore(option) { |
| 206 | // might have been matched by the options matchProvider function instead | ||
| 207 | if (!this.fuzzyRegex.test(option.name)) { | ||
| 208 | option.score = new AutoCompleteFuzzyScore(Number.MAX_SAFE_INTEGER, -1); | ||
| 209 | return option; | ||
| 210 | } | ||
| 192 | const parts = this.fuzzyRegex.exec(option.name).slice(1, -1); | 211 | const parts = this.fuzzyRegex.exec(option.name).slice(1, -1); |
| 193 | let start = null; | 212 | let start = null; |
| 194 | let consecutive = []; | 213 | let consecutive = []; |
| @@ -339,7 +358,7 @@ export class AutoComplete { | |||
| 339 | 358 | ||
| 340 | this.result = this.effectiveParserResult.optionList | 359 | this.result = this.effectiveParserResult.optionList |
| 341 | // filter the list of options by the partial name according to the matching type | 360 | // filter the list of options by the partial name according to the matching type |
| 342 | .filter(it => this.isReplaceable || it.name == '' ? matchers[this.matchType](it.name) : it.name.toLowerCase() == this.name) | 361 | .filter(it => this.isReplaceable || it.name == '' ? (it.matchProvider ? it.matchProvider(this.name) : matchers[this.matchType](it.name)) : it.name.toLowerCase() == this.name) |
| 343 | // remove aliases | 362 | // remove aliases |
| 344 | .filter((it,idx,list) => list.findIndex(opt=>opt.value == it.value) == idx); | 363 | .filter((it,idx,list) => list.findIndex(opt=>opt.value == it.value) == idx); |
| 345 | 364 | ||
| @@ -357,10 +376,11 @@ export class AutoComplete { | |||
| 357 | // build element | 376 | // build element |
| 358 | option.dom = this.makeItem(option); | 377 | option.dom = this.makeItem(option); |
| 359 | // update replacer and add quotes if necessary | 378 | // update replacer and add quotes if necessary |
| 379 | const optionName = option.valueProvider ? option.valueProvider(this.name) : option.name; | ||
| 360 | if (this.effectiveParserResult.canBeQuoted) { | 380 | if (this.effectiveParserResult.canBeQuoted) { |
| 361 | option.replacer = option.name.includes(' ') || this.startQuote || this.endQuote ? `"${option.name}"` : `${option.name}`; | 381 | option.replacer = optionName.includes(' ') || this.startQuote || this.endQuote ? `"${optionName.replace(/"/g, '\\"')}"` : `${optionName}`; |
| 362 | } else { | 382 | } else { |
| 363 | option.replacer = option.name; | 383 | option.replacer = optionName; |
| 364 | } | 384 | } |
| 365 | // calculate fuzzy score if matching is fuzzy | 385 | // calculate fuzzy score if matching is fuzzy |
| 366 | if (this.matchType == 'fuzzy') this.fuzzyScore(option); | 386 | if (this.matchType == 'fuzzy') this.fuzzyScore(option); |
| @@ -399,7 +419,7 @@ export class AutoComplete { | |||
| 399 | , | 419 | , |
| 400 | ); | 420 | ); |
| 401 | this.result.push(option); | 421 | this.result.push(option); |
| 402 | } else if (this.result.length == 1 && this.effectiveParserResult && this.result[0].name == this.effectiveParserResult.name) { | 422 | } else if (this.result.length == 1 && this.effectiveParserResult && this.effectiveParserResult != this.secondaryParserResult && this.result[0].name == this.effectiveParserResult.name) { |
| 403 | // only one result that is exactly the current value? just show hint, no autocomplete | 423 | // only one result that is exactly the current value? just show hint, no autocomplete |
| 404 | this.isReplaceable = false; | 424 | this.isReplaceable = false; |
| 405 | this.isShowingDetails = false; | 425 | this.isShowingDetails = false; |
| @@ -439,11 +459,14 @@ export class AutoComplete { | |||
| 439 | } else { | 459 | } else { |
| 440 | item.dom.classList.remove('selected'); | 460 | item.dom.classList.remove('selected'); |
| 441 | } | 461 | } |
| 462 | if (!item.isSelectable) { | ||
| 463 | item.dom.classList.add('not-selectable'); | ||
| 464 | } | ||
| 442 | frag.append(item.dom); | 465 | frag.append(item.dom); |
| 443 | } | 466 | } |
| 444 | this.dom.append(frag); | 467 | this.dom.append(frag); |
| 445 | this.updatePosition(); | 468 | this.updatePosition(); |
| 446 | getTopmostModalLayer().append(this.domWrap); | 469 | this.getLayer().append(this.domWrap); |
| 447 | } else { | 470 | } else { |
| 448 | this.domWrap.remove(); | 471 | this.domWrap.remove(); |
| 449 | } | 472 | } |
| @@ -458,10 +481,17 @@ export class AutoComplete { | |||
| 458 | if (!this.isShowingDetails && this.isReplaceable) return this.detailsWrap.remove(); | 481 | if (!this.isShowingDetails && this.isReplaceable) return this.detailsWrap.remove(); |
| 459 | this.detailsDom.innerHTML = ''; | 482 | this.detailsDom.innerHTML = ''; |
| 460 | this.detailsDom.append(this.selectedItem?.renderDetails() ?? 'NO ITEM'); | 483 | this.detailsDom.append(this.selectedItem?.renderDetails() ?? 'NO ITEM'); |
| 461 | getTopmostModalLayer().append(this.detailsWrap); | 484 | this.getLayer().append(this.detailsWrap); |
| 462 | this.updateDetailsPositionDebounced(); | 485 | this.updateDetailsPositionDebounced(); |
| 463 | } | 486 | } |
| 464 | 487 | ||
| 488 | /** | ||
| 489 | * @returns {HTMLElement} closest ancestor dialog or body | ||
| 490 | */ | ||
| 491 | getLayer() { | ||
| 492 | return this.textarea.closest('dialog, body'); | ||
| 493 | } | ||
| 494 | |||
| 465 | 495 | ||
| 466 | 496 | ||
| 467 | /** | 497 | /** |
| @@ -474,7 +504,7 @@ export class AutoComplete { | |||
| 474 | const rect = {}; | 504 | const rect = {}; |
| 475 | rect[AUTOCOMPLETE_WIDTH.INPUT] = this.textarea.getBoundingClientRect(); | 505 | rect[AUTOCOMPLETE_WIDTH.INPUT] = this.textarea.getBoundingClientRect(); |
| 476 | rect[AUTOCOMPLETE_WIDTH.CHAT] = document.querySelector('#sheld').getBoundingClientRect(); | 506 | rect[AUTOCOMPLETE_WIDTH.CHAT] = document.querySelector('#sheld').getBoundingClientRect(); |
| 477 | rect[AUTOCOMPLETE_WIDTH.FULL] = getTopmostModalLayer().getBoundingClientRect(); | 507 | rect[AUTOCOMPLETE_WIDTH.FULL] = this.getLayer().getBoundingClientRect(); |
| 478 | this.domWrap.style.setProperty('--bottom', `${window.innerHeight - rect[AUTOCOMPLETE_WIDTH.INPUT].top}px`); | 508 | this.domWrap.style.setProperty('--bottom', `${window.innerHeight - rect[AUTOCOMPLETE_WIDTH.INPUT].top}px`); |
| 479 | this.dom.style.setProperty('--bottom', `${window.innerHeight - rect[AUTOCOMPLETE_WIDTH.INPUT].top}px`); | 509 | this.dom.style.setProperty('--bottom', `${window.innerHeight - rect[AUTOCOMPLETE_WIDTH.INPUT].top}px`); |
| 480 | this.domWrap.style.bottom = `${window.innerHeight - rect[AUTOCOMPLETE_WIDTH.INPUT].top}px`; | 510 | this.domWrap.style.bottom = `${window.innerHeight - rect[AUTOCOMPLETE_WIDTH.INPUT].top}px`; |
| @@ -501,7 +531,7 @@ export class AutoComplete { | |||
| 501 | const rect = {}; | 531 | const rect = {}; |
| 502 | rect[AUTOCOMPLETE_WIDTH.INPUT] = this.textarea.getBoundingClientRect(); | 532 | rect[AUTOCOMPLETE_WIDTH.INPUT] = this.textarea.getBoundingClientRect(); |
| 503 | rect[AUTOCOMPLETE_WIDTH.CHAT] = document.querySelector('#sheld').getBoundingClientRect(); | 533 | rect[AUTOCOMPLETE_WIDTH.CHAT] = document.querySelector('#sheld').getBoundingClientRect(); |
| 504 | rect[AUTOCOMPLETE_WIDTH.FULL] = getTopmostModalLayer().getBoundingClientRect(); | 534 | rect[AUTOCOMPLETE_WIDTH.FULL] = this.getLayer().getBoundingClientRect(); |
| 505 | if (this.isReplaceable) { | 535 | if (this.isReplaceable) { |
| 506 | this.detailsWrap.classList.remove('full'); | 536 | this.detailsWrap.classList.remove('full'); |
| 507 | const selRect = this.selectedItem.dom.children[0].getBoundingClientRect(); | 537 | const selRect = this.selectedItem.dom.children[0].getBoundingClientRect(); |
| @@ -527,32 +557,34 @@ export class AutoComplete { | |||
| 527 | updateFloatingPosition() { | 557 | updateFloatingPosition() { |
| 528 | const location = this.getCursorPosition(); | 558 | const location = this.getCursorPosition(); |
| 529 | const rect = this.textarea.getBoundingClientRect(); | 559 | const rect = this.textarea.getBoundingClientRect(); |
| 560 | const layerRect = this.getLayer().getBoundingClientRect(); | ||
| 530 | // cursor is out of view -> hide | 561 | // cursor is out of view -> hide |
| 531 | if (location.bottom < rect.top || location.top > rect.bottom || location.left < rect.left || location.left > rect.right) { | 562 | if (location.bottom < rect.top || location.top > rect.bottom || location.left < rect.left || location.left > rect.right) { |
| 532 | return this.hide(); | 563 | return this.hide(); |
| 533 | } | 564 | } |
| 534 | const left = Math.max(rect.left, location.left); | 565 | const left = Math.max(rect.left, location.left) - layerRect.left; |
| 535 | this.domWrap.style.setProperty('--targetOffset', `${left}`); | 566 | this.domWrap.style.setProperty('--targetOffset', `${left}`); |
| 536 | if (location.top <= window.innerHeight / 2) { | 567 | if (location.top <= window.innerHeight / 2) { |
| 537 | // if cursor is in lower half of window, show list above line | 568 | // if cursor is in lower half of window, show list above line |
| 538 | this.domWrap.style.top = `${location.bottom}px`; | 569 | this.domWrap.style.top = `${location.bottom - layerRect.top}px`; |
| 539 | this.domWrap.style.bottom = 'auto'; | 570 | this.domWrap.style.bottom = 'auto'; |
| 540 | this.domWrap.style.maxHeight = `calc(${location.bottom}px - 1vh)`; | 571 | this.domWrap.style.maxHeight = `calc(${location.bottom - layerRect.top}px - ${this.textarea.closest('dialog') ? '0' : '1vh'})`; |
| 541 | } else { | 572 | } else { |
| 542 | // if cursor is in upper half of window, show list below line | 573 | // if cursor is in upper half of window, show list below line |
| 543 | this.domWrap.style.top = 'auto'; | 574 | this.domWrap.style.top = 'auto'; |
| 544 | this.domWrap.style.bottom = `calc(100vh - ${location.top}px)`; | 575 | this.domWrap.style.bottom = `calc(${layerRect.height}px - ${location.top - layerRect.top}px)`; |
| 545 | this.domWrap.style.maxHeight = `calc(${location.top}px - 1vh)`; | 576 | this.domWrap.style.maxHeight = `calc(${location.top - layerRect.top}px - ${this.textarea.closest('dialog') ? '0' : '1vh'})`; |
| 546 | } | 577 | } |
| 547 | } | 578 | } |
| 548 | 579 | ||
| 549 | updateFloatingDetailsPosition(location = null) { | 580 | updateFloatingDetailsPosition(location = null) { |
| 550 | if (!location) location = this.getCursorPosition(); | 581 | if (!location) location = this.getCursorPosition(); |
| 551 | const rect = this.textarea.getBoundingClientRect(); | 582 | const rect = this.textarea.getBoundingClientRect(); |
| 583 | const layerRect = this.getLayer().getBoundingClientRect(); | ||
| 552 | if (location.bottom < rect.top || location.top > rect.bottom || location.left < rect.left || location.left > rect.right) { | 584 | if (location.bottom < rect.top || location.top > rect.bottom || location.left < rect.left || location.left > rect.right) { |
| 553 | return this.hide(); | 585 | return this.hide(); |
| 554 | } | 586 | } |
| 555 | const left = Math.max(rect.left, location.left); | 587 | const left = Math.max(rect.left, location.left) - layerRect.left; |
| 556 | this.detailsWrap.style.setProperty('--targetOffset', `${left}`); | 588 | this.detailsWrap.style.setProperty('--targetOffset', `${left}`); |
| 557 | if (this.isReplaceable) { | 589 | if (this.isReplaceable) { |
| 558 | this.detailsWrap.classList.remove('full'); | 590 | this.detailsWrap.classList.remove('full'); |
| @@ -572,14 +604,14 @@ export class AutoComplete { | |||
| 572 | } | 604 | } |
| 573 | if (location.top <= window.innerHeight / 2) { | 605 | if (location.top <= window.innerHeight / 2) { |
| 574 | // if cursor is in lower half of window, show list above line | 606 | // if cursor is in lower half of window, show list above line |
| 575 | this.detailsWrap.style.top = `${location.bottom}px`; | 607 | this.detailsWrap.style.top = `${location.bottom - layerRect.top}px`; |
| 576 | this.detailsWrap.style.bottom = 'auto'; | 608 | this.detailsWrap.style.bottom = 'auto'; |
| 577 | this.detailsWrap.style.maxHeight = `calc(${location.bottom}px - 1vh)`; | 609 | this.detailsWrap.style.maxHeight = `calc(${location.bottom - layerRect.top}px - ${this.textarea.closest('dialog') ? '0' : '1vh'})`; |
| 578 | } else { | 610 | } else { |
| 579 | // if cursor is in upper half of window, show list below line | 611 | // if cursor is in upper half of window, show list below line |
| 580 | this.detailsWrap.style.top = 'auto'; | 612 | this.detailsWrap.style.top = 'auto'; |
| 581 | this.detailsWrap.style.bottom = `calc(100vh - ${location.top}px)`; | 613 | this.detailsWrap.style.bottom = `calc(${layerRect.height}px - ${location.top - layerRect.top}px)`; |
| 582 | this.detailsWrap.style.maxHeight = `calc(${location.top}px - 1vh)`; | 614 | this.detailsWrap.style.maxHeight = `calc(${location.top - layerRect.top}px - ${this.textarea.closest('dialog') ? '0' : '1vh'})`; |
| 583 | } | 615 | } |
| 584 | } | 616 | } |
| 585 | 617 | ||
| @@ -597,7 +629,7 @@ export class AutoComplete { | |||
| 597 | } | 629 | } |
| 598 | this.clone.style.position = 'fixed'; | 630 | this.clone.style.position = 'fixed'; |
| 599 | this.clone.style.visibility = 'hidden'; | 631 | this.clone.style.visibility = 'hidden'; |
| 600 | getTopmostModalLayer().append(this.clone); | 632 | document.body.append(this.clone); |
| 601 | const mo = new MutationObserver(muts=>{ | 633 | const mo = new MutationObserver(muts=>{ |
| 602 | if (muts.find(it=>Array.from(it.removedNodes).includes(this.textarea))) { | 634 | if (muts.find(it=>Array.from(it.removedNodes).includes(this.textarea))) { |
| 603 | this.clone.remove(); | 635 | this.clone.remove(); |
| @@ -656,6 +688,7 @@ export class AutoComplete { | |||
| 656 | } | 688 | } |
| 657 | this.wasForced = false; | 689 | this.wasForced = false; |
| 658 | this.textarea.dispatchEvent(new Event('input', { bubbles:true })); | 690 | this.textarea.dispatchEvent(new Event('input', { bubbles:true })); |
| 691 | this.onSelect?.(this.selectedItem); | ||
| 659 | } | 692 | } |
| 660 | 693 | ||
| 661 | 694 | ||
| @@ -708,8 +741,10 @@ export class AutoComplete { | |||
| 708 | } | 741 | } |
| 709 | case 'Enter': { | 742 | case 'Enter': { |
| 710 | // pick the selected item to autocomplete | 743 | // pick the selected item to autocomplete |
| 744 | if ((power_user.stscript.autocomplete.select & AUTOCOMPLETE_SELECT_KEY.ENTER) != AUTOCOMPLETE_SELECT_KEY.ENTER) break; | ||
| 711 | if (evt.ctrlKey || evt.altKey || evt.shiftKey || this.selectedItem.value == '') break; | 745 | if (evt.ctrlKey || evt.altKey || evt.shiftKey || this.selectedItem.value == '') break; |
| 712 | if (this.selectedItem.name == this.name) break; | 746 | if (this.selectedItem.name == this.name) break; |
| 747 | if (!this.selectedItem.isSelectable) break; | ||
| 713 | evt.preventDefault(); | 748 | evt.preventDefault(); |
| 714 | evt.stopImmediatePropagation(); | 749 | evt.stopImmediatePropagation(); |
| 715 | this.select(); | 750 | this.select(); |
| @@ -717,9 +752,11 @@ export class AutoComplete { | |||
| 717 | } | 752 | } |
| 718 | case 'Tab': { | 753 | case 'Tab': { |
| 719 | // pick the selected item to autocomplete | 754 | // pick the selected item to autocomplete |
| 755 | if ((power_user.stscript.autocomplete.select & AUTOCOMPLETE_SELECT_KEY.TAB) != AUTOCOMPLETE_SELECT_KEY.TAB) break; | ||
| 720 | if (evt.ctrlKey || evt.altKey || evt.shiftKey || this.selectedItem.value == '') break; | 756 | if (evt.ctrlKey || evt.altKey || evt.shiftKey || this.selectedItem.value == '') break; |
| 721 | evt.preventDefault(); | 757 | evt.preventDefault(); |
| 722 | evt.stopImmediatePropagation(); | 758 | evt.stopImmediatePropagation(); |
| 759 | if (!this.selectedItem.isSelectable) break; | ||
| 723 | this.select(); | 760 | this.select(); |
| 724 | return; | 761 | return; |
| 725 | } | 762 | } |
| @@ -772,30 +809,16 @@ export class AutoComplete { | |||
| 772 | // ignore keydown on modifier keys | 809 | // ignore keydown on modifier keys |
| 773 | return; | 810 | return; |
| 774 | } | 811 | } |
| 775 | switch (evt.key) { | 812 | // await keyup to see if cursor position or text has changed |
| 776 | case 'ArrowUp': | 813 | const oldText = this.textarea.value; |
| 777 | case 'ArrowDown': | 814 | await new Promise(resolve=>{ |
| 778 | case 'ArrowRight': | 815 | window.addEventListener('keyup', resolve, { once:true }); |
| 779 | case 'ArrowLeft': { | 816 | }); |
| 780 | if (this.isActive) { | 817 | if (this.selectionStart != this.textarea.selectionStart) { |
| 781 | // keyboard navigation, wait for keyup to complete cursor move | 818 | this.selectionStart = this.textarea.selectionStart; |
| 782 | const oldText = this.textarea.value; | 819 | this.show(this.isReplaceable || oldText != this.textarea.value); |
| 783 | await new Promise(resolve=>{ | 820 | } else if (this.isActive) { |
| 784 | window.addEventListener('keyup', resolve, { once:true }); | 821 | this.text != this.textarea.value && this.show(this.isReplaceable); |
| 785 | }); | ||
| 786 | if (this.selectionStart != this.textarea.selectionStart) { | ||
| 787 | this.selectionStart = this.textarea.selectionStart; | ||
| 788 | this.show(this.isReplaceable || oldText != this.textarea.value); | ||
| 789 | } | ||
| 790 | } | ||
| 791 | break; | ||
| 792 | } | ||
| 793 | default: { | ||
| 794 | if (this.isActive) { | ||
| 795 | this.text != this.textarea.value && this.show(this.isReplaceable); | ||
| 796 | } | ||
| 797 | break; | ||
| 798 | } | ||
| 799 | } | 822 | } |
| 800 | } | 823 | } |
| 801 | } | 824 | } |
| @@ -1,36 +1,9 @@ | |||
| 1 | import { SlashCommandNamedArgumentAutoCompleteOption } from '../slash-commands/SlashCommandNamedArgumentAutoCompleteOption.js'; | 1 | import { AutoCompleteNameResultBase } from './AutoCompleteNameResultBase.js'; |
| 2 | import { AutoCompleteOption } from './AutoCompleteOption.js'; | 2 | import { AutoCompleteSecondaryNameResult } from './AutoCompleteSecondaryNameResult.js'; |
| 3 | // import { AutoCompleteSecondaryNameResult } from './AutoCompleteSecondaryNameResult.js'; | ||
| 4 | 3 | ||
| 5 | 4 | ||
| 6 | 5 | ||
| 7 | export class AutoCompleteNameResult { | 6 | export class AutoCompleteNameResult extends AutoCompleteNameResultBase { |
| 8 | /**@type {string} */ name; | ||
| 9 | /**@type {number} */ start; | ||
| 10 | /**@type {AutoCompleteOption[]} */ optionList = []; | ||
| 11 | /**@type {boolean} */ canBeQuoted = false; | ||
| 12 | /**@type {()=>string} */ makeNoMatchText = ()=>`No matches found for "${this.name}"`; | ||
| 13 | /**@type {()=>string} */ makeNoOptionsText = ()=>'No options'; | ||
| 14 | |||
| 15 | |||
| 16 | /** | ||
| 17 | * @param {string} name Name (potentially partial) of the name at the requested index. | ||
| 18 | * @param {number} start Index where the name starts. | ||
| 19 | * @param {AutoCompleteOption[]} optionList A list of autocomplete options found in the current scope. | ||
| 20 | * @param {boolean} canBeQuoted Whether the name can be inside quotes. | ||
| 21 | * @param {()=>string} makeNoMatchText Function that returns text to show when no matches where found. | ||
| 22 | * @param {()=>string} makeNoOptionsText Function that returns text to show when no options are available to match against. | ||
| 23 | */ | ||
| 24 | constructor(name, start, optionList = [], canBeQuoted = false, makeNoMatchText = null, makeNoOptionsText = null) { | ||
| 25 | this.name = name; | ||
| 26 | this.start = start; | ||
| 27 | this.optionList = optionList; | ||
| 28 | this.canBeQuoted = canBeQuoted; | ||
| 29 | this.noMatchText = makeNoMatchText ?? this.makeNoMatchText; | ||
| 30 | this.noOptionstext = makeNoOptionsText ?? this.makeNoOptionsText; | ||
| 31 | } | ||
| 32 | |||
| 33 | |||
| 34 | /** | 7 | /** |
| 35 | * | 8 | * |
| 36 | * @param {string} text The whole text | 9 | * @param {string} text The whole text |
| @@ -0,0 +1,31 @@ | |||
| 1 | import { SlashCommandNamedArgumentAutoCompleteOption } from '../slash-commands/SlashCommandNamedArgumentAutoCompleteOption.js'; | ||
| 2 | import { AutoCompleteOption } from './AutoCompleteOption.js'; | ||
| 3 | |||
| 4 | |||
| 5 | |||
| 6 | export class AutoCompleteNameResultBase { | ||
| 7 | /**@type {string} */ name; | ||
| 8 | /**@type {number} */ start; | ||
| 9 | /**@type {AutoCompleteOption[]} */ optionList = []; | ||
| 10 | /**@type {boolean} */ canBeQuoted = false; | ||
| 11 | /**@type {()=>string} */ makeNoMatchText = ()=>`No matches found for "${this.name}"`; | ||
| 12 | /**@type {()=>string} */ makeNoOptionsText = ()=>'No options'; | ||
| 13 | |||
| 14 | |||
| 15 | /** | ||
| 16 | * @param {string} name Name (potentially partial) of the name at the requested index. | ||
| 17 | * @param {number} start Index where the name starts. | ||
| 18 | * @param {AutoCompleteOption[]} optionList A list of autocomplete options found in the current scope. | ||
| 19 | * @param {boolean} canBeQuoted Whether the name can be inside quotes. | ||
| 20 | * @param {()=>string} makeNoMatchText Function that returns text to show when no matches where found. | ||
| 21 | * @param {()=>string} makeNoOptionsText Function that returns text to show when no options are available to match against. | ||
| 22 | */ | ||
| 23 | constructor(name, start, optionList = [], canBeQuoted = false, makeNoMatchText = null, makeNoOptionsText = null) { | ||
| 24 | this.name = name; | ||
| 25 | this.start = start; | ||
| 26 | this.optionList = optionList; | ||
| 27 | this.canBeQuoted = canBeQuoted; | ||
| 28 | this.noMatchText = makeNoMatchText ?? this.makeNoMatchText; | ||
| 29 | this.noOptionstext = makeNoOptionsText ?? this.makeNoOptionsText; | ||
| 30 | } | ||
| 31 | } | ||
| @@ -11,6 +11,9 @@ export class AutoCompleteOption { | |||
| 11 | /**@type {AutoCompleteFuzzyScore}*/ score; | 11 | /**@type {AutoCompleteFuzzyScore}*/ score; |
| 12 | /**@type {string}*/ replacer; | 12 | /**@type {string}*/ replacer; |
| 13 | /**@type {HTMLElement}*/ dom; | 13 | /**@type {HTMLElement}*/ dom; |
| 14 | /**@type {(input:string)=>boolean}*/ matchProvider; | ||
| 15 | /**@type {(input:string)=>string}*/ valueProvider; | ||
| 16 | /**@type {boolean}*/ makeSelectable = false; | ||
| 14 | 17 | ||
| 15 | 18 | ||
| 16 | /** | 19 | /** |
| @@ -21,14 +24,21 @@ export class AutoCompleteOption { | |||
| 21 | return this.name; | 24 | return this.name; |
| 22 | } | 25 | } |
| 23 | 26 | ||
| 27 | get isSelectable() { | ||
| 28 | return this.makeSelectable || !this.valueProvider; | ||
| 29 | } | ||
| 30 | |||
| 24 | 31 | ||
| 25 | /** | 32 | /** |
| 26 | * @param {string} name | 33 | * @param {string} name |
| 27 | */ | 34 | */ |
| 28 | constructor(name, typeIcon = ' ', type = '') { | 35 | constructor(name, typeIcon = ' ', type = '', matchProvider = null, valueProvider = null, makeSelectable = false) { |
| 29 | this.name = name; | 36 | this.name = name; |
| 30 | this.typeIcon = typeIcon; | 37 | this.typeIcon = typeIcon; |
| 31 | this.type = type; | 38 | this.type = type; |
| 39 | this.matchProvider = matchProvider; | ||
| 40 | this.valueProvider = valueProvider; | ||
| 41 | this.makeSelectable = makeSelectable; | ||
| 32 | } | 42 | } |
| 33 | 43 | ||
| 34 | 44 | ||
| @@ -1,6 +1,6 @@ | |||
| 1 | import { AutoCompleteNameResult } from './AutoCompleteNameResult.js'; | 1 | import { AutoCompleteNameResultBase } from './AutoCompleteNameResultBase.js'; |
| 2 | 2 | ||
| 3 | export class AutoCompleteSecondaryNameResult extends AutoCompleteNameResult { | 3 | export class AutoCompleteSecondaryNameResult extends AutoCompleteNameResultBase { |
| 4 | /**@type {boolean}*/ isRequired = false; | 4 | /**@type {boolean}*/ isRequired = false; |
| 5 | /**@type {boolean}*/ forceMatch = true; | 5 | /**@type {boolean}*/ forceMatch = true; |
| 6 | } | 6 | } |
| @@ -14,7 +14,7 @@ import { | |||
| 14 | saveChatConditional, | 14 | saveChatConditional, |
| 15 | saveItemizedPrompts, | 15 | saveItemizedPrompts, |
| 16 | } from '../script.js'; | 16 | } from '../script.js'; |
| 17 | import { humanizedDateTime } from './RossAscends-mods.js'; | 17 | import { humanizedDateTime, getMessageTimeStamp } from './RossAscends-mods.js'; |
| 18 | import { | 18 | import { |
| 19 | getGroupPastChats, | 19 | getGroupPastChats, |
| 20 | group_activation_strategy, | 20 | group_activation_strategy, |
| @@ -297,7 +297,7 @@ async function convertSoloToGroupChat() { | |||
| 297 | if (groupChat.length === 0) { | 297 | if (groupChat.length === 0) { |
| 298 | const newMessage = { | 298 | const newMessage = { |
| 299 | ...system_messages[system_message_types.GROUP], | 299 | ...system_messages[system_message_types.GROUP], |
| 300 | send_date: humanizedDateTime(), | 300 | send_date: getMessageTimeStamp(), |
| 301 | extra: { type: system_message_types.GROUP }, | 301 | extra: { type: system_message_types.GROUP }, |
| 302 | }; | 302 | }; |
| 303 | groupChat.push(newMessage); | 303 | groupChat.push(newMessage); |
| @@ -989,6 +989,28 @@ export async function writeExtensionField(characterId, key, value) { | |||
| 989 | } | 989 | } |
| 990 | } | 990 | } |
| 991 | 991 | ||
| 992 | /** | ||
| 993 | * Prompts the user to enter the Git URL of the extension to import. | ||
| 994 | * After obtaining the Git URL, makes a POST request to '/api/extensions/install' to import the extension. | ||
| 995 | * If the extension is imported successfully, a success message is displayed. | ||
| 996 | * If the extension import fails, an error message is displayed and the error is logged to the console. | ||
| 997 | * After successfully importing the extension, the extension settings are reloaded and a 'EXTENSION_SETTINGS_LOADED' event is emitted. | ||
| 998 | * @param {string} [suggestUrl] Suggested URL to install | ||
| 999 | * @returns {Promise<void>} | ||
| 1000 | */ | ||
| 1001 | export async function openThirdPartyExtensionMenu(suggestUrl = '') { | ||
| 1002 | const html = await renderTemplateAsync('installExtension'); | ||
| 1003 | const input = await callGenericPopup(html, POPUP_TYPE.INPUT, suggestUrl ?? ''); | ||
| 1004 | |||
| 1005 | if (!input) { | ||
| 1006 | console.debug('Extension install cancelled'); | ||
| 1007 | return; | ||
| 1008 | } | ||
| 1009 | |||
| 1010 | const url = String(input).trim(); | ||
| 1011 | await installExtension(url); | ||
| 1012 | } | ||
| 1013 | |||
| 992 | jQuery(async function () { | 1014 | jQuery(async function () { |
| 993 | await addExtensionsButtonAndMenu(); | 1015 | await addExtensionsButtonAndMenu(); |
| 994 | $('#extensionsMenuButton').css('display', 'flex'); | 1016 | $('#extensionsMenuButton').css('display', 'flex'); |
| @@ -1004,28 +1026,8 @@ jQuery(async function () { | |||
| 1004 | 1026 | ||
| 1005 | /** | 1027 | /** |
| 1006 | * Handles the click event for the third-party extension import button. | 1028 | * Handles the click event for the third-party extension import button. |
| 1007 | * Prompts the user to enter the Git URL of the extension to import. | ||
| 1008 | * After obtaining the Git URL, makes a POST request to '/api/extensions/install' to import the extension. | ||
| 1009 | * If the extension is imported successfully, a success message is displayed. | ||
| 1010 | * If the extension import fails, an error message is displayed and the error is logged to the console. | ||
| 1011 | * After successfully importing the extension, the extension settings are reloaded and a 'EXTENSION_SETTINGS_LOADED' event is emitted. | ||
| 1012 | * | 1029 | * |
| 1013 | * @listens #third_party_extension_button#click - The click event of the '#third_party_extension_button' element. | 1030 | * @listens #third_party_extension_button#click - The click event of the '#third_party_extension_button' element. |
| 1014 | */ | 1031 | */ |
| 1015 | $('#third_party_extension_button').on('click', async () => { | 1032 | $('#third_party_extension_button').on('click', () => openThirdPartyExtensionMenu()); |
| 1016 | const html = `<h3>Enter the Git URL of the extension to install</h3> | ||
| 1017 | <br> | ||
| 1018 | <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> | ||
| 1019 | <br> | ||
| 1020 | <p>Example: <tt> https://github.com/author/extension-name </tt></p>`; | ||
| 1021 | const input = await callGenericPopup(html, POPUP_TYPE.INPUT, ''); | ||
| 1022 | |||
| 1023 | if (!input) { | ||
| 1024 | console.debug('Extension install cancelled'); | ||
| 1025 | return; | ||
| 1026 | } | ||
| 1027 | |||
| 1028 | const url = String(input).trim(); | ||
| 1029 | await installExtension(url); | ||
| 1030 | }); | ||
| 1031 | }); | 1033 | }); |
| @@ -8,13 +8,12 @@ import { textgen_types, textgenerationwebui_settings } from '../../textgen-setti | |||
| 8 | import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js'; | 8 | import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js'; |
| 9 | import { SlashCommand } from '../../slash-commands/SlashCommand.js'; | 9 | import { SlashCommand } from '../../slash-commands/SlashCommand.js'; |
| 10 | import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js'; | 10 | import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js'; |
| 11 | import { SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js'; | ||
| 12 | import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js'; | 11 | import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js'; |
| 13 | export { MODULE_NAME }; | 12 | export { MODULE_NAME }; |
| 14 | 13 | ||
| 15 | const MODULE_NAME = 'caption'; | 14 | const MODULE_NAME = 'caption'; |
| 16 | 15 | ||
| 17 | const PROMPT_DEFAULT = 'What’s in this image?'; | 16 | const PROMPT_DEFAULT = 'What\'s in this image?'; |
| 18 | const TEMPLATE_DEFAULT = '[{{user}} sends {{char}} a picture that contains: {{caption}}]'; | 17 | const TEMPLATE_DEFAULT = '[{{user}} sends {{char}} a picture that contains: {{caption}}]'; |
| 19 | 18 | ||
| 20 | /** | 19 | /** |
| @@ -170,7 +169,11 @@ async function sendCaptionedMessage(caption, image) { | |||
| 170 | }, | 169 | }, |
| 171 | }; | 170 | }; |
| 172 | context.chat.push(message); | 171 | context.chat.push(message); |
| 172 | const messageId = context.chat.length - 1; | ||
| 173 | await eventSource.emit(event_types.MESSAGE_SENT, messageId); | ||
| 173 | context.addOneMessage(message); | 174 | context.addOneMessage(message); |
| 175 | await eventSource.emit(event_types.USER_MESSAGE_RENDERED, messageId); | ||
| 176 | await context.saveChat(); | ||
| 174 | } | 177 | } |
| 175 | 178 | ||
| 176 | /** | 179 | /** |
| @@ -334,7 +337,7 @@ async function getCaptionForFile(file, prompt, quiet) { | |||
| 334 | } | 337 | } |
| 335 | catch (error) { | 338 | catch (error) { |
| 336 | const errorMessage = error.message || 'Unknown error'; | 339 | const errorMessage = error.message || 'Unknown error'; |
| 337 | toastr.error(errorMessage, "Failed to caption image."); | 340 | toastr.error(errorMessage, 'Failed to caption image.'); |
| 338 | console.error(error); | 341 | console.error(error); |
| 339 | return ''; | 342 | return ''; |
| 340 | } | 343 | } |
| @@ -399,6 +402,7 @@ jQuery(async function () { | |||
| 399 | (modules.includes('caption') && extension_settings.caption.source === 'extras') || | 402 | (modules.includes('caption') && extension_settings.caption.source === 'extras') || |
| 400 | (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'openai' && (secret_state[SECRET_KEYS.OPENAI] || extension_settings.caption.allow_reverse_proxy)) || | 403 | (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'openai' && (secret_state[SECRET_KEYS.OPENAI] || extension_settings.caption.allow_reverse_proxy)) || |
| 401 | (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'openrouter' && secret_state[SECRET_KEYS.OPENROUTER]) || | 404 | (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'openrouter' && secret_state[SECRET_KEYS.OPENROUTER]) || |
| 405 | (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'zerooneai' && secret_state[SECRET_KEYS.ZEROONEAI]) || | ||
| 402 | (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'google' && (secret_state[SECRET_KEYS.MAKERSUITE] || extension_settings.caption.allow_reverse_proxy)) || | 406 | (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'google' && (secret_state[SECRET_KEYS.MAKERSUITE] || extension_settings.caption.allow_reverse_proxy)) || |
| 403 | (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'anthropic' && (secret_state[SECRET_KEYS.CLAUDE] || extension_settings.caption.allow_reverse_proxy)) || | 407 | (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'anthropic' && (secret_state[SECRET_KEYS.CLAUDE] || extension_settings.caption.allow_reverse_proxy)) || |
| 404 | (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'ollama' && textgenerationwebui_settings.server_urls[textgen_types.OLLAMA]) || | 408 | (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'ollama' && textgenerationwebui_settings.server_urls[textgen_types.OLLAMA]) || |
| @@ -17,6 +17,7 @@ | |||
| 17 | <div class="flex1 flex-container flexFlowColumn flexNoGap"> | 17 | <div class="flex1 flex-container flexFlowColumn flexNoGap"> |
| 18 | <label for="caption_multimodal_api" data-i18n="API">API</label> | 18 | <label for="caption_multimodal_api" data-i18n="API">API</label> |
| 19 | <select id="caption_multimodal_api" class="flex1 text_pole"> | 19 | <select id="caption_multimodal_api" class="flex1 text_pole"> |
| 20 | <option value="zerooneai">01.AI (Yi)</option> | ||
| 20 | <option value="anthropic">Anthropic</option> | 21 | <option value="anthropic">Anthropic</option> |
| 21 | <option value="custom" data-i18n="Custom (OpenAI-compatible)">Custom (OpenAI-compatible)</option> | 22 | <option value="custom" data-i18n="Custom (OpenAI-compatible)">Custom (OpenAI-compatible)</option> |
| 22 | <option value="google">Google MakerSuite</option> | 23 | <option value="google">Google MakerSuite</option> |
| @@ -32,16 +33,20 @@ | |||
| 32 | <div class="flex1 flex-container flexFlowColumn flexNoGap"> | 33 | <div class="flex1 flex-container flexFlowColumn flexNoGap"> |
| 33 | <label for="caption_multimodal_model" data-i18n="Model">Model</label> | 34 | <label for="caption_multimodal_model" data-i18n="Model">Model</label> |
| 34 | <select id="caption_multimodal_model" class="flex1 text_pole"> | 35 | <select id="caption_multimodal_model" class="flex1 text_pole"> |
| 36 | <option data-type="zerooneai" value="yi-vision">yi-vision</option> | ||
| 35 | <option data-type="openai" value="gpt-4-vision-preview">gpt-4-vision-preview</option> | 37 | <option data-type="openai" value="gpt-4-vision-preview">gpt-4-vision-preview</option> |
| 36 | <option data-type="openai" value="gpt-4-turbo">gpt-4-turbo</option> | 38 | <option data-type="openai" value="gpt-4-turbo">gpt-4-turbo</option> |
| 37 | <option data-type="openai" value="gpt-4o">gpt-4o</option> | 39 | <option data-type="openai" value="gpt-4o">gpt-4o</option> |
| 38 | <option data-type="openai" value="gpt-4o-mini">gpt-4o-mini</option> | 40 | <option data-type="openai" value="gpt-4o-mini">gpt-4o-mini</option> |
| 41 | <option data-type="openai" value="chatgpt-4o-latest">chatgpt-4o-latest</option> | ||
| 39 | <option data-type="anthropic" value="claude-3-5-sonnet-20240620">claude-3-5-sonnet-20240620</option> | 42 | <option data-type="anthropic" value="claude-3-5-sonnet-20240620">claude-3-5-sonnet-20240620</option> |
| 40 | <option data-type="anthropic" value="claude-3-opus-20240229">claude-3-opus-20240229</option> | 43 | <option data-type="anthropic" value="claude-3-opus-20240229">claude-3-opus-20240229</option> |
| 41 | <option data-type="anthropic" value="claude-3-sonnet-20240229">claude-3-sonnet-20240229</option> | 44 | <option data-type="anthropic" value="claude-3-sonnet-20240229">claude-3-sonnet-20240229</option> |
| 42 | <option data-type="anthropic" value="claude-3-haiku-20240307">claude-3-haiku-20240307</option> | 45 | <option data-type="anthropic" value="claude-3-haiku-20240307">claude-3-haiku-20240307</option> |
| 43 | <option data-type="google" value="gemini-pro-vision">gemini-pro-vision</option> | 46 | <option data-type="google" value="gemini-pro-vision">gemini-pro-vision</option> |
| 44 | <option data-type="google" value="gemini-1.5-flash-latest">gemini-1.5-flash-latest</option> | 47 | <option data-type="google" value="gemini-1.5-flash-latest">gemini-1.5-flash-latest</option> |
| 48 | <option data-type="google" value="gemini-1.5-pro-latest">gemini-1.5-pro-latest</option> | ||
| 49 | <option data-type="google" value="gemini-1.5-pro-exp-0801">gemini-1.5-pro-exp-0801</option> | ||
| 45 | <option data-type="openrouter" value="openai/gpt-4-vision-preview">openai/gpt-4-vision-preview</option> | 50 | <option data-type="openrouter" value="openai/gpt-4-vision-preview">openai/gpt-4-vision-preview</option> |
| 46 | <option data-type="openrouter" value="openai/gpt-4o">openai/gpt-4o</option> | 51 | <option data-type="openrouter" value="openai/gpt-4o">openai/gpt-4o</option> |
| 47 | <option data-type="openrouter" value="openai/gpt-4-turbo">openai/gpt-4-turbo</option> | 52 | <option data-type="openrouter" value="openai/gpt-4-turbo">openai/gpt-4-turbo</option> |
| @@ -1,4 +1,4 @@ | |||
| 1 | import { callPopup, eventSource, event_types, generateQuietPrompt, getRequestHeaders, online_status, saveSettingsDebounced, substituteParams, substituteParamsExtended, system_message_types } from '../../../script.js'; | 1 | import { callPopup, eventSource, event_types, generateRaw, getRequestHeaders, main_api, online_status, saveSettingsDebounced, substituteParams, substituteParamsExtended, system_message_types } from '../../../script.js'; |
| 2 | import { dragElement, isMobile } from '../../RossAscends-mods.js'; | 2 | import { dragElement, isMobile } from '../../RossAscends-mods.js'; |
| 3 | import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch, renderExtensionTemplateAsync } from '../../extensions.js'; | 3 | import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch, renderExtensionTemplateAsync } from '../../extensions.js'; |
| 4 | import { loadMovingUIState, power_user } from '../../power-user.js'; | 4 | import { loadMovingUIState, power_user } from '../../power-user.js'; |
| @@ -1156,7 +1156,7 @@ async function getExpressionLabel(text) { | |||
| 1156 | 1156 | ||
| 1157 | functionResult = args?.arguments; | 1157 | functionResult = args?.arguments; |
| 1158 | }); | 1158 | }); |
| 1159 | const emotionResponse = await generateQuietPrompt(prompt, false, false); | 1159 | const emotionResponse = await generateRaw(text, main_api, false, false, prompt); |
| 1160 | return parseLlmResponse(functionResult || emotionResponse, expressionsList); | 1160 | return parseLlmResponse(functionResult || emotionResponse, expressionsList); |
| 1161 | } | 1161 | } |
| 1162 | // Extras | 1162 | // Extras |
| @@ -1387,7 +1387,8 @@ async function getExpressionsList() { | |||
| 1387 | } | 1387 | } |
| 1388 | 1388 | ||
| 1389 | // If there was no specific list, or an error, just return the default expressions | 1389 | // If there was no specific list, or an error, just return the default expressions |
| 1390 | return DEFAULT_EXPRESSIONS; | 1390 | expressionsList = DEFAULT_EXPRESSIONS.filter(e => e !== 'talkinghead').slice(); |
| 1391 | return expressionsList; | ||
| 1391 | } | 1392 | } |
| 1392 | 1393 | ||
| 1393 | const result = await resolveExpressionsList(); | 1394 | const result = await resolveExpressionsList(); |
| @@ -1618,11 +1619,13 @@ async function onClickExpressionRemoveCustom() { | |||
| 1618 | moduleWorker(); | 1619 | moduleWorker(); |
| 1619 | } | 1620 | } |
| 1620 | 1621 | ||
| 1621 | function onExperesionApiChanged() { | 1622 | function onExpressionApiChanged() { |
| 1622 | const tempApi = this.value; | 1623 | const tempApi = this.value; |
| 1623 | if (tempApi) { | 1624 | if (tempApi) { |
| 1624 | extension_settings.expressions.api = Number(tempApi); | 1625 | extension_settings.expressions.api = Number(tempApi); |
| 1625 | $('.expression_llm_prompt_block').toggle(extension_settings.expressions.api === EXPRESSION_API.llm); | 1626 | $('.expression_llm_prompt_block').toggle(extension_settings.expressions.api === EXPRESSION_API.llm); |
| 1627 | expressionsList = null; | ||
| 1628 | spriteCache = {}; | ||
| 1626 | moduleWorker(); | 1629 | moduleWorker(); |
| 1627 | saveSettingsDebounced(); | 1630 | saveSettingsDebounced(); |
| 1628 | } | 1631 | } |
| @@ -1972,7 +1975,7 @@ function migrateSettings() { | |||
| 1972 | $('#expression_custom_add').on('click', onClickExpressionAddCustom); | 1975 | $('#expression_custom_add').on('click', onClickExpressionAddCustom); |
| 1973 | $('#expression_custom_remove').on('click', onClickExpressionRemoveCustom); | 1976 | $('#expression_custom_remove').on('click', onClickExpressionRemoveCustom); |
| 1974 | $('#expression_fallback').on('change', onExpressionFallbackChanged); | 1977 | $('#expression_fallback').on('change', onExpressionFallbackChanged); |
| 1975 | $('#expression_api').on('change', onExperesionApiChanged); | 1978 | $('#expression_api').on('change', onExpressionApiChanged); |
| 1976 | } | 1979 | } |
| 1977 | 1980 | ||
| 1978 | // Pause Talkinghead to save resources when the ST tab is not visible or the window is minimized. | 1981 | // Pause Talkinghead to save resources when the ST tab is not visible or the window is minimized. |
| @@ -0,0 +1,2 @@ | |||
| 1 | <!-- I18n data for tools used to auto generate translations --> | ||
| 2 | <div data-i18n="Show Gallery">Show Gallery</div> | ||
| @@ -3,6 +3,7 @@ import { | |||
| 3 | this_chid, | 3 | this_chid, |
| 4 | characters, | 4 | characters, |
| 5 | getRequestHeaders, | 5 | getRequestHeaders, |
| 6 | event_types, | ||
| 6 | } from '../../../script.js'; | 7 | } from '../../../script.js'; |
| 7 | import { groups, selected_group } from '../../group-chats.js'; | 8 | import { groups, selected_group } from '../../group-chats.js'; |
| 8 | import { loadFileToDocument, delay } from '../../utils.js'; | 9 | import { loadFileToDocument, delay } from '../../utils.js'; |
| @@ -13,6 +14,7 @@ import { SlashCommand } from '../../slash-commands/SlashCommand.js'; | |||
| 13 | import { ARGUMENT_TYPE, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js'; | 14 | import { ARGUMENT_TYPE, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js'; |
| 14 | import { DragAndDropHandler } from '../../dragdrop.js'; | 15 | import { DragAndDropHandler } from '../../dragdrop.js'; |
| 15 | import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js'; | 16 | import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js'; |
| 17 | import { translate } from '../../i18n.js'; | ||
| 16 | 18 | ||
| 17 | const extensionName = 'gallery'; | 19 | const extensionName = 'gallery'; |
| 18 | const extensionFolderPath = `scripts/extensions/${extensionName}/`; | 20 | const extensionFolderPath = `scripts/extensions/${extensionName}/`; |
| @@ -24,6 +26,27 @@ let paginationVisiblePages = 10; | |||
| 24 | let paginationMaxLinesPerPage = 2; | 26 | let paginationMaxLinesPerPage = 2; |
| 25 | let galleryMaxRows = 3; | 27 | let galleryMaxRows = 3; |
| 26 | 28 | ||
| 29 | $('body').on('click', '.dragClose', function () { | ||
| 30 | const relatedId = $(this).data('related-id'); // Get the ID of the related draggable | ||
| 31 | $(`body > .draggable[id="${relatedId}"]`).remove(); // Remove the associated draggable | ||
| 32 | }); | ||
| 33 | |||
| 34 | const CUSTOM_GALLERY_REMOVED_EVENT = 'galleryRemoved'; | ||
| 35 | |||
| 36 | const mutationObserver = new MutationObserver((mutations) => { | ||
| 37 | mutations.forEach((mutation) => { | ||
| 38 | mutation.removedNodes.forEach((node) => { | ||
| 39 | if (node instanceof HTMLElement && node.tagName === 'DIV' && node.id === 'gallery') { | ||
| 40 | eventSource.emit(CUSTOM_GALLERY_REMOVED_EVENT); | ||
| 41 | } | ||
| 42 | }); | ||
| 43 | }); | ||
| 44 | }); | ||
| 45 | |||
| 46 | mutationObserver.observe(document.body, { | ||
| 47 | childList: true, | ||
| 48 | subtree: false, | ||
| 49 | }); | ||
| 27 | 50 | ||
| 28 | /** | 51 | /** |
| 29 | * Retrieves a list of gallery items based on a given URL. This function calls an API endpoint | 52 | * Retrieves a list of gallery items based on a given URL. This function calls an API endpoint |
| @@ -58,7 +81,9 @@ async function getGalleryItems(url) { | |||
| 58 | * @returns {Promise<void>} - Promise representing the completion of the gallery initialization. | 81 | * @returns {Promise<void>} - Promise representing the completion of the gallery initialization. |
| 59 | */ | 82 | */ |
| 60 | async function initGallery(items, url) { | 83 | async function initGallery(items, url) { |
| 84 | const nonce = `nonce-${Math.random().toString(36).substring(2, 15)}`; | ||
| 61 | const gallery = $('#dragGallery'); | 85 | const gallery = $('#dragGallery'); |
| 86 | gallery.addClass(nonce); | ||
| 62 | gallery.nanogallery2({ | 87 | gallery.nanogallery2({ |
| 63 | 'items': items, | 88 | 'items': items, |
| 64 | thumbnailWidth: 'auto', | 89 | thumbnailWidth: 'auto', |
| @@ -81,16 +106,26 @@ async function initGallery(items, url) { | |||
| 81 | fnThumbnailOpen: viewWithDragbox, | 106 | fnThumbnailOpen: viewWithDragbox, |
| 82 | }); | 107 | }); |
| 83 | 108 | ||
| 109 | const dragDropHandler = new DragAndDropHandler(`#dragGallery.${nonce}`, async (files, event) => { | ||
| 110 | let file = files[0]; | ||
| 111 | uploadFile(file, url); // Added url parameter to know where to upload | ||
| 112 | }); | ||
| 84 | 113 | ||
| 85 | eventSource.on('resizeUI', function (elmntName) { | 114 | const resizeHandler = function () { |
| 86 | gallery.nanogallery2('resize'); | 115 | gallery.nanogallery2('resize'); |
| 87 | }); | 116 | }; |
| 88 | 117 | ||
| 89 | const dragDropHandler = new DragAndDropHandler('#dragGallery', async (files, event) => { | 118 | eventSource.on('resizeUI', resizeHandler); |
| 90 | let file = files[0]; | 119 | |
| 91 | uploadFile(file, url); // Added url parameter to know where to upload | 120 | eventSource.once(event_types.CHAT_CHANGED, function () { |
| 121 | gallery.closest('#gallery').remove(); | ||
| 92 | }); | 122 | }); |
| 93 | 123 | ||
| 124 | eventSource.once(CUSTOM_GALLERY_REMOVED_EVENT, function () { | ||
| 125 | gallery.nanogallery2('destroy'); | ||
| 126 | dragDropHandler.destroy(); | ||
| 127 | eventSource.removeListener('resizeUI', resizeHandler); | ||
| 128 | }); | ||
| 94 | 129 | ||
| 95 | // Set dropzone height to be the same as the parent | 130 | // Set dropzone height to be the same as the parent |
| 96 | gallery.css('height', gallery.parent().css('height')); | 131 | gallery.css('height', gallery.parent().css('height')); |
| @@ -139,16 +174,10 @@ async function showCharGallery() { | |||
| 139 | 174 | ||
| 140 | const items = await getGalleryItems(url); | 175 | const items = await getGalleryItems(url); |
| 141 | // if there already is a gallery, destroy it and place this one in its place | 176 | // if there already is a gallery, destroy it and place this one in its place |
| 142 | if ($('#dragGallery').length) { | 177 | $('#dragGallery').closest('#gallery').remove(); |
| 143 | $('#dragGallery').nanogallery2('destroy'); | 178 | makeMovable(); |
| 144 | initGallery(items, url); | 179 | await delay(100); |
| 145 | } else { | 180 | await initGallery(items, url); |
| 146 | makeMovable(); | ||
| 147 | setTimeout(async () => { | ||
| 148 | await initGallery(items, url); | ||
| 149 | }, 100); | ||
| 150 | } | ||
| 151 | |||
| 152 | } catch (err) { | 181 | } catch (err) { |
| 153 | console.trace(); | 182 | console.trace(); |
| 154 | console.error(err); | 183 | console.error(err); |
| @@ -201,11 +230,11 @@ async function uploadFile(file, url) { | |||
| 201 | toastr.success('File uploaded successfully. Saved at: ' + result.path); | 230 | toastr.success('File uploaded successfully. Saved at: ' + result.path); |
| 202 | 231 | ||
| 203 | // Refresh the gallery | 232 | // Refresh the gallery |
| 204 | $('#dragGallery').nanogallery2('destroy'); // Destroy old gallery | ||
| 205 | const newItems = await getGalleryItems(url); // Fetch the latest items | 233 | const newItems = await getGalleryItems(url); // Fetch the latest items |
| 206 | initGallery(newItems, url); // Reinitialize the gallery with new items and pass 'url' | 234 | $('#dragGallery').closest('#gallery').remove(); // Destroy old gallery |
| 207 | 235 | makeMovable(); | |
| 208 | 236 | await delay(100); | |
| 237 | await initGallery(newItems, url); // Reinitialize the gallery with new items and pass 'url' | ||
| 209 | } catch (error) { | 238 | } catch (error) { |
| 210 | console.error('There was an issue uploading the file:', error); | 239 | console.error('There was an issue uploading the file:', error); |
| 211 | 240 | ||
| @@ -228,7 +257,7 @@ $(document).ready(function () { | |||
| 228 | $('#char-management-dropdown').append( | 257 | $('#char-management-dropdown').append( |
| 229 | $('<option>', { | 258 | $('<option>', { |
| 230 | id: 'show_char_gallery', | 259 | id: 'show_char_gallery', |
| 231 | text: 'Show Gallery', | 260 | text: translate('Show Gallery'), |
| 232 | }), | 261 | }), |
| 233 | ); | 262 | ); |
| 234 | }); | 263 | }); |
| @@ -272,11 +301,6 @@ function makeMovable(id = 'gallery') { | |||
| 272 | e.preventDefault(); | 301 | e.preventDefault(); |
| 273 | return false; | 302 | return false; |
| 274 | }); | 303 | }); |
| 275 | |||
| 276 | $('body').on('click', '.dragClose', function () { | ||
| 277 | const relatedId = $(this).data('related-id'); // Get the ID of the related draggable | ||
| 278 | $(`#${relatedId}`).remove(); // Remove the associated draggable | ||
| 279 | }); | ||
| 280 | } | 304 | } |
| 281 | 305 | ||
| 282 | /** | 306 | /** |
| @@ -357,11 +381,6 @@ function makeDragImg(id, url) { | |||
| 357 | } else { | 381 | } else { |
| 358 | console.error('Failed to append the template content or retrieve the appended content.'); | 382 | console.error('Failed to append the template content or retrieve the appended content.'); |
| 359 | } | 383 | } |
| 360 | |||
| 361 | $('body').on('click', '.dragClose', function () { | ||
| 362 | const relatedId = $(this).data('related-id'); // Get the ID of the related draggable | ||
| 363 | $(`#${relatedId}`).remove(); // Remove the associated draggable | ||
| 364 | }); | ||
| 365 | } | 384 | } |
| 366 | 385 | ||
| 367 | /** | 386 | /** |
| @@ -400,7 +419,8 @@ function viewWithDragbox(items) { | |||
| 400 | 419 | ||
| 401 | 420 | ||
| 402 | // Registers a simple command for opening the char gallery. | 421 | // Registers a simple command for opening the char gallery. |
| 403 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'show-gallery', | 422 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| 423 | name: 'show-gallery', | ||
| 404 | aliases: ['sg'], | 424 | aliases: ['sg'], |
| 405 | callback: () => { | 425 | callback: () => { |
| 406 | showCharGallery(); | 426 | showCharGallery(); |
| @@ -408,7 +428,8 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'show-gallery | |||
| 408 | }, | 428 | }, |
| 409 | helpString: 'Shows the gallery.', | 429 | helpString: 'Shows the gallery.', |
| 410 | })); | 430 | })); |
| 411 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'list-gallery', | 431 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| 432 | name: 'list-gallery', | ||
| 412 | aliases: ['lg'], | 433 | aliases: ['lg'], |
| 413 | callback: listGalleryCommand, | 434 | callback: listGalleryCommand, |
| 414 | returns: 'list of images', | 435 | returns: 'list of images', |
| @@ -431,14 +452,14 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'list-gallery | |||
| 431 | 452 | ||
| 432 | async function listGalleryCommand(args) { | 453 | async function listGalleryCommand(args) { |
| 433 | try { | 454 | try { |
| 434 | let url = args.char ?? (args.group ? groups.find(it=>it.name == args.group)?.id : null) ?? (selected_group || this_chid); | 455 | let url = args.char ?? (args.group ? groups.find(it => it.name == args.group)?.id : null) ?? (selected_group || this_chid); |
| 435 | if (!args.char && !args.group && !selected_group && this_chid) { | 456 | if (!args.char && !args.group && !selected_group && this_chid) { |
| 436 | const char = characters[this_chid]; | 457 | const char = characters[this_chid]; |
| 437 | url = char.avatar.replace('.png', ''); | 458 | url = char.avatar.replace('.png', ''); |
| 438 | } | 459 | } |
| 439 | 460 | ||
| 440 | const items = await getGalleryItems(url); | 461 | const items = await getGalleryItems(url); |
| 441 | return JSON.stringify(items.map(it=>it.src)); | 462 | return JSON.stringify(items.map(it => it.src)); |
| 442 | 463 | ||
| 443 | } catch (err) { | 464 | } catch (err) { |
| 444 | console.trace(); | 465 | console.trace(); |
| @@ -5,7 +5,7 @@ | |||
| 5 | "optional": [ | 5 | "optional": [ |
| 6 | ], | 6 | ], |
| 7 | "js": "index.js", | 7 | "js": "index.js", |
| 8 | "css": "", | 8 | "css": "style.css", |
| 9 | "author": "City-Unit", | 9 | "author": "City-Unit", |
| 10 | "version": "1.5.0", | 10 | "version": "1.5.0", |
| 11 | "homePage": "https://github.com/SillyTavern/SillyTavern" | 11 | "homePage": "https://github.com/SillyTavern/SillyTavern" |
| @@ -0,0 +1,5 @@ | |||
| 1 | .nGY2 .nGY2GalleryBottom { | ||
| 2 | display: flex; | ||
| 3 | align-items: center; | ||
| 4 | justify-content: center; | ||
| 5 | } | ||
| @@ -14,6 +14,7 @@ import { | |||
| 14 | substituteParamsExtended, | 14 | substituteParamsExtended, |
| 15 | generateRaw, | 15 | generateRaw, |
| 16 | getMaxContextSize, | 16 | getMaxContextSize, |
| 17 | setExtensionPrompt, | ||
| 17 | } from '../../../script.js'; | 18 | } from '../../../script.js'; |
| 18 | import { is_group_generating, selected_group } from '../../group-chats.js'; | 19 | import { is_group_generating, selected_group } from '../../group-chats.js'; |
| 19 | import { loadMovingUIState } from '../../power-user.js'; | 20 | import { loadMovingUIState } from '../../power-user.js'; |
| @@ -24,6 +25,7 @@ import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js'; | |||
| 24 | import { SlashCommand } from '../../slash-commands/SlashCommand.js'; | 25 | import { SlashCommand } from '../../slash-commands/SlashCommand.js'; |
| 25 | import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js'; | 26 | import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js'; |
| 26 | import { MacrosParser } from '../../macros.js'; | 27 | import { MacrosParser } from '../../macros.js'; |
| 28 | import { countWebLlmTokens, generateWebLlmChatPrompt, getWebLlmContextSize, isWebLlmSupported } from '../shared.js'; | ||
| 27 | export { MODULE_NAME }; | 29 | export { MODULE_NAME }; |
| 28 | 30 | ||
| 29 | const MODULE_NAME = '1_memory'; | 31 | const MODULE_NAME = '1_memory'; |
| @@ -35,6 +37,41 @@ let lastMessageHash = null; | |||
| 35 | let lastMessageId = null; | 37 | let lastMessageId = null; |
| 36 | let inApiCall = false; | 38 | let inApiCall = false; |
| 37 | 39 | ||
| 40 | /** | ||
| 41 | * Count the number of tokens in the provided text. | ||
| 42 | * @param {string} text Text to count tokens for | ||
| 43 | * @param {number} padding Number of additional tokens to add to the count | ||
| 44 | * @returns {Promise<number>} Number of tokens in the text | ||
| 45 | */ | ||
| 46 | async function countSourceTokens(text, padding = 0) { | ||
| 47 | if (extension_settings.memory.source === summary_sources.webllm) { | ||
| 48 | const count = await countWebLlmTokens(text); | ||
| 49 | return count + padding; | ||
| 50 | } | ||
| 51 | |||
| 52 | if (extension_settings.memory.source === summary_sources.extras) { | ||
| 53 | const count = getTextTokens(tokenizers.GPT2, text).length; | ||
| 54 | return count + padding; | ||
| 55 | } | ||
| 56 | |||
| 57 | return await getTokenCountAsync(text, padding); | ||
| 58 | } | ||
| 59 | |||
| 60 | async function getSourceContextSize() { | ||
| 61 | const overrideLength = extension_settings.memory.overrideResponseLength; | ||
| 62 | |||
| 63 | if (extension_settings.memory.source === summary_sources.webllm) { | ||
| 64 | const maxContext = await getWebLlmContextSize(); | ||
| 65 | return overrideLength > 0 ? (maxContext - overrideLength) : Math.round(maxContext * 0.75); | ||
| 66 | } | ||
| 67 | |||
| 68 | if (extension_settings.source === summary_sources.extras) { | ||
| 69 | return 1024 - 64; | ||
| 70 | } | ||
| 71 | |||
| 72 | return getMaxContextSize(overrideLength); | ||
| 73 | } | ||
| 74 | |||
| 38 | const formatMemoryValue = function (value) { | 75 | const formatMemoryValue = function (value) { |
| 39 | if (!value) { | 76 | if (!value) { |
| 40 | return ''; | 77 | return ''; |
| @@ -54,6 +91,7 @@ const saveChatDebounced = debounce(() => getContext().saveChat(), debounce_timeo | |||
| 54 | const summary_sources = { | 91 | const summary_sources = { |
| 55 | 'extras': 'extras', | 92 | 'extras': 'extras', |
| 56 | 'main': 'main', | 93 | 'main': 'main', |
| 94 | 'webllm': 'webllm', | ||
| 57 | }; | 95 | }; |
| 58 | 96 | ||
| 59 | const prompt_builders = { | 97 | const prompt_builders = { |
| @@ -73,6 +111,7 @@ const defaultSettings = { | |||
| 73 | template: defaultTemplate, | 111 | template: defaultTemplate, |
| 74 | position: extension_prompt_types.IN_PROMPT, | 112 | position: extension_prompt_types.IN_PROMPT, |
| 75 | role: extension_prompt_roles.SYSTEM, | 113 | role: extension_prompt_roles.SYSTEM, |
| 114 | scan: false, | ||
| 76 | depth: 2, | 115 | depth: 2, |
| 77 | promptWords: 200, | 116 | promptWords: 200, |
| 78 | promptMinWords: 25, | 117 | promptMinWords: 25, |
| @@ -122,17 +161,18 @@ function loadSettings() { | |||
| 122 | $(`input[name="memory_prompt_builder"][value="${extension_settings.memory.prompt_builder}"]`).prop('checked', true).trigger('input'); | 161 | $(`input[name="memory_prompt_builder"][value="${extension_settings.memory.prompt_builder}"]`).prop('checked', true).trigger('input'); |
| 123 | $('#memory_override_response_length').val(extension_settings.memory.overrideResponseLength).trigger('input'); | 162 | $('#memory_override_response_length').val(extension_settings.memory.overrideResponseLength).trigger('input'); |
| 124 | $('#memory_max_messages_per_request').val(extension_settings.memory.maxMessagesPerRequest).trigger('input'); | 163 | $('#memory_max_messages_per_request').val(extension_settings.memory.maxMessagesPerRequest).trigger('input'); |
| 164 | $('#memory_include_wi_scan').prop('checked', extension_settings.memory.scan).trigger('input'); | ||
| 125 | switchSourceControls(extension_settings.memory.source); | 165 | switchSourceControls(extension_settings.memory.source); |
| 126 | } | 166 | } |
| 127 | 167 | ||
| 128 | async function onPromptForceWordsAutoClick() { | 168 | async function onPromptForceWordsAutoClick() { |
| 129 | const context = getContext(); | 169 | const context = getContext(); |
| 130 | const maxPromptLength = getMaxContextSize(extension_settings.memory.overrideResponseLength); | 170 | const maxPromptLength = await getSourceContextSize(); |
| 131 | const chat = context.chat; | 171 | const chat = context.chat; |
| 132 | const allMessages = chat.filter(m => !m.is_system && m.mes).map(m => m.mes); | 172 | const allMessages = chat.filter(m => !m.is_system && m.mes).map(m => m.mes); |
| 133 | const messagesWordCount = allMessages.map(m => extractAllWords(m)).flat().length; | 173 | const messagesWordCount = allMessages.map(m => extractAllWords(m)).flat().length; |
| 134 | const averageMessageWordCount = messagesWordCount / allMessages.length; | 174 | const averageMessageWordCount = messagesWordCount / allMessages.length; |
| 135 | const tokensPerWord = await getTokenCountAsync(allMessages.join('\n')) / messagesWordCount; | 175 | const tokensPerWord = await countSourceTokens(allMessages.join('\n')) / messagesWordCount; |
| 136 | const wordsPerToken = 1 / tokensPerWord; | 176 | const wordsPerToken = 1 / tokensPerWord; |
| 137 | const maxPromptLengthWords = Math.round(maxPromptLength * wordsPerToken); | 177 | const maxPromptLengthWords = Math.round(maxPromptLength * wordsPerToken); |
| 138 | // How many words should pass so that messages will start be dropped out of context; | 178 | // How many words should pass so that messages will start be dropped out of context; |
| @@ -165,15 +205,15 @@ async function onPromptForceWordsAutoClick() { | |||
| 165 | 205 | ||
| 166 | async function onPromptIntervalAutoClick() { | 206 | async function onPromptIntervalAutoClick() { |
| 167 | const context = getContext(); | 207 | const context = getContext(); |
| 168 | const maxPromptLength = getMaxContextSize(extension_settings.memory.overrideResponseLength); | 208 | const maxPromptLength = await getSourceContextSize(); |
| 169 | const chat = context.chat; | 209 | const chat = context.chat; |
| 170 | const allMessages = chat.filter(m => !m.is_system && m.mes).map(m => m.mes); | 210 | const allMessages = chat.filter(m => !m.is_system && m.mes).map(m => m.mes); |
| 171 | const messagesWordCount = allMessages.map(m => extractAllWords(m)).flat().length; | 211 | const messagesWordCount = allMessages.map(m => extractAllWords(m)).flat().length; |
| 172 | const messagesTokenCount = await getTokenCountAsync(allMessages.join('\n')); | 212 | const messagesTokenCount = await countSourceTokens(allMessages.join('\n')); |
| 173 | const tokensPerWord = messagesTokenCount / messagesWordCount; | 213 | const tokensPerWord = messagesTokenCount / messagesWordCount; |
| 174 | const averageMessageTokenCount = messagesTokenCount / allMessages.length; | 214 | const averageMessageTokenCount = messagesTokenCount / allMessages.length; |
| 175 | const targetSummaryTokens = Math.round(extension_settings.memory.promptWords * tokensPerWord); | 215 | const targetSummaryTokens = Math.round(extension_settings.memory.promptWords * tokensPerWord); |
| 176 | const promptTokens = await getTokenCountAsync(extension_settings.memory.prompt); | 216 | const promptTokens = await countSourceTokens(extension_settings.memory.prompt); |
| 177 | const promptAllowance = maxPromptLength - promptTokens - targetSummaryTokens; | 217 | const promptAllowance = maxPromptLength - promptTokens - targetSummaryTokens; |
| 178 | const maxMessagesPerSummary = extension_settings.memory.maxMessagesPerRequest || 0; | 218 | const maxMessagesPerSummary = extension_settings.memory.maxMessagesPerRequest || 0; |
| 179 | const averageMessagesPerPrompt = Math.floor(promptAllowance / averageMessageTokenCount); | 219 | const averageMessagesPerPrompt = Math.floor(promptAllowance / averageMessageTokenCount); |
| @@ -210,8 +250,8 @@ function onSummarySourceChange(event) { | |||
| 210 | 250 | ||
| 211 | function switchSourceControls(value) { | 251 | function switchSourceControls(value) { |
| 212 | $('#memory_settings [data-summary-source]').each((_, element) => { | 252 | $('#memory_settings [data-summary-source]').each((_, element) => { |
| 213 | const source = $(element).data('summary-source'); | 253 | const source = element.dataset.summarySource.split(',').map(s => s.trim()); |
| 214 | $(element).toggle(source === value); | 254 | $(element).toggle(source.includes(value)); |
| 215 | }); | 255 | }); |
| 216 | } | 256 | } |
| 217 | 257 | ||
| @@ -279,6 +319,13 @@ function onMemoryPositionChange(e) { | |||
| 279 | saveSettingsDebounced(); | 319 | saveSettingsDebounced(); |
| 280 | } | 320 | } |
| 281 | 321 | ||
| 322 | function onMemoryIncludeWIScanInput() { | ||
| 323 | const value = !!$(this).prop('checked'); | ||
| 324 | extension_settings.memory.scan = value; | ||
| 325 | reinsertMemory(); | ||
| 326 | saveSettingsDebounced(); | ||
| 327 | } | ||
| 328 | |||
| 282 | function onMemoryPromptWordsForceInput() { | 329 | function onMemoryPromptWordsForceInput() { |
| 283 | const value = $(this).val(); | 330 | const value = $(this).val(); |
| 284 | extension_settings.memory.promptForceWords = Number(value); | 331 | extension_settings.memory.promptForceWords = Number(value); |
| @@ -343,10 +390,13 @@ function getIndexOfLatestChatSummary(chat) { | |||
| 343 | 390 | ||
| 344 | async function onChatEvent() { | 391 | async function onChatEvent() { |
| 345 | // Module not enabled | 392 | // Module not enabled |
| 346 | if (extension_settings.memory.source === summary_sources.extras) { | 393 | if (extension_settings.memory.source === summary_sources.extras && !modules.includes('summarize')) { |
| 347 | if (!modules.includes('summarize')) { | 394 | return; |
| 348 | return; | 395 | } |
| 349 | } | 396 | |
| 397 | // WebLLM is not supported | ||
| 398 | if (extension_settings.memory.source === summary_sources.webllm && !isWebLlmSupported()) { | ||
| 399 | return; | ||
| 350 | } | 400 | } |
| 351 | 401 | ||
| 352 | const context = getContext(); | 402 | const context = getContext(); |
| @@ -421,8 +471,12 @@ async function forceSummarizeChat() { | |||
| 421 | return ''; | 471 | return ''; |
| 422 | } | 472 | } |
| 423 | 473 | ||
| 424 | toastr.info('Summarizing chat...', 'Please wait'); | 474 | const toast = toastr.info('Summarizing chat...', 'Please wait', { timeOut: 0, extendedTimeOut: 0 }); |
| 425 | const value = await summarizeChatMain(context, true, skipWIAN); | 475 | const value = extension_settings.memory.source === summary_sources.main |
| 476 | ? await summarizeChatMain(context, true, skipWIAN) | ||
| 477 | : await summarizeChatWebLLM(context, true); | ||
| 478 | |||
| 479 | toastr.clear(toast); | ||
| 426 | 480 | ||
| 427 | if (!value) { | 481 | if (!value) { |
| 428 | toastr.warning('Failed to summarize chat'); | 482 | toastr.warning('Failed to summarize chat'); |
| @@ -454,6 +508,11 @@ async function summarizeCallback(args, text) { | |||
| 454 | return await callExtrasSummarizeAPI(text); | 508 | return await callExtrasSummarizeAPI(text); |
| 455 | case summary_sources.main: | 509 | case summary_sources.main: |
| 456 | return await generateRaw(text, '', false, false, prompt, extension_settings.memory.overrideResponseLength); | 510 | return await generateRaw(text, '', false, false, prompt, extension_settings.memory.overrideResponseLength); |
| 511 | case summary_sources.webllm: { | ||
| 512 | const messages = [{ role: 'system', content: prompt }, { role: 'user', content: text }].filter(m => m.content); | ||
| 513 | const params = extension_settings.memory.overrideResponseLength > 0 ? { max_tokens: extension_settings.memory.overrideResponseLength } : {}; | ||
| 514 | return await generateWebLlmChatPrompt(messages, params); | ||
| 515 | } | ||
| 457 | default: | 516 | default: |
| 458 | toastr.warning('Invalid summarization source specified'); | 517 | toastr.warning('Invalid summarization source specified'); |
| 459 | return ''; | 518 | return ''; |
| @@ -474,16 +533,25 @@ async function summarizeChat(context) { | |||
| 474 | case summary_sources.main: | 533 | case summary_sources.main: |
| 475 | await summarizeChatMain(context, false, skipWIAN); | 534 | await summarizeChatMain(context, false, skipWIAN); |
| 476 | break; | 535 | break; |
| 536 | case summary_sources.webllm: | ||
| 537 | await summarizeChatWebLLM(context, false); | ||
| 538 | break; | ||
| 477 | default: | 539 | default: |
| 478 | break; | 540 | break; |
| 479 | } | 541 | } |
| 480 | } | 542 | } |
| 481 | 543 | ||
| 482 | async function summarizeChatMain(context, force, skipWIAN) { | 544 | /** |
| 483 | 545 | * Check if the chat should be summarized based on the current conditions. | |
| 546 | * Return summary prompt if it should be summarized. | ||
| 547 | * @param {any} context ST context | ||
| 548 | * @param {boolean} force Summarize the chat regardless of the conditions | ||
| 549 | * @returns {Promise<string>} Summary prompt or empty string | ||
| 550 | */ | ||
| 551 | async function getSummaryPromptForNow(context, force) { | ||
| 484 | if (extension_settings.memory.promptInterval === 0 && !force) { | 552 | if (extension_settings.memory.promptInterval === 0 && !force) { |
| 485 | console.debug('Prompt interval is set to 0, skipping summarization'); | 553 | console.debug('Prompt interval is set to 0, skipping summarization'); |
| 486 | return; | 554 | return ''; |
| 487 | } | 555 | } |
| 488 | 556 | ||
| 489 | try { | 557 | try { |
| @@ -495,17 +563,17 @@ async function summarizeChatMain(context, force, skipWIAN) { | |||
| 495 | waitUntilCondition(() => is_send_press === false, 30000, 100); | 563 | waitUntilCondition(() => is_send_press === false, 30000, 100); |
| 496 | } catch { | 564 | } catch { |
| 497 | console.debug('Timeout waiting for is_send_press'); | 565 | console.debug('Timeout waiting for is_send_press'); |
| 498 | return; | 566 | return ''; |
| 499 | } | 567 | } |
| 500 | 568 | ||
| 501 | if (!context.chat.length) { | 569 | if (!context.chat.length) { |
| 502 | console.debug('No messages in chat to summarize'); | 570 | console.debug('No messages in chat to summarize'); |
| 503 | return; | 571 | return ''; |
| 504 | } | 572 | } |
| 505 | 573 | ||
| 506 | if (context.chat.length < extension_settings.memory.promptInterval && !force) { | 574 | if (context.chat.length < extension_settings.memory.promptInterval && !force) { |
| 507 | console.debug(`Not enough messages in chat to summarize (chat: ${context.chat.length}, interval: ${extension_settings.memory.promptInterval})`); | 575 | console.debug(`Not enough messages in chat to summarize (chat: ${context.chat.length}, interval: ${extension_settings.memory.promptInterval})`); |
| 508 | return; | 576 | return ''; |
| 509 | } | 577 | } |
| 510 | 578 | ||
| 511 | let messagesSinceLastSummary = 0; | 579 | let messagesSinceLastSummary = 0; |
| @@ -529,7 +597,7 @@ async function summarizeChatMain(context, force, skipWIAN) { | |||
| 529 | 597 | ||
| 530 | if (!conditionSatisfied && !force) { | 598 | if (!conditionSatisfied && !force) { |
| 531 | console.debug(`Summary conditions not satisfied (messages: ${messagesSinceLastSummary}, interval: ${extension_settings.memory.promptInterval}, words: ${wordsSinceLastSummary}, force words: ${extension_settings.memory.promptForceWords})`); | 599 | console.debug(`Summary conditions not satisfied (messages: ${messagesSinceLastSummary}, interval: ${extension_settings.memory.promptInterval}, words: ${wordsSinceLastSummary}, force words: ${extension_settings.memory.promptForceWords})`); |
| 532 | return; | 600 | return ''; |
| 533 | } | 601 | } |
| 534 | 602 | ||
| 535 | console.log('Summarizing chat, messages since last summary: ' + messagesSinceLastSummary, 'words since last summary: ' + wordsSinceLastSummary); | 603 | console.log('Summarizing chat, messages since last summary: ' + messagesSinceLastSummary, 'words since last summary: ' + wordsSinceLastSummary); |
| @@ -537,6 +605,63 @@ async function summarizeChatMain(context, force, skipWIAN) { | |||
| 537 | 605 | ||
| 538 | if (!prompt) { | 606 | if (!prompt) { |
| 539 | console.debug('Summarization prompt is empty. Skipping summarization.'); | 607 | console.debug('Summarization prompt is empty. Skipping summarization.'); |
| 608 | return ''; | ||
| 609 | } | ||
| 610 | |||
| 611 | return prompt; | ||
| 612 | } | ||
| 613 | |||
| 614 | async function summarizeChatWebLLM(context, force) { | ||
| 615 | if (!isWebLlmSupported()) { | ||
| 616 | return; | ||
| 617 | } | ||
| 618 | |||
| 619 | const prompt = await getSummaryPromptForNow(context, force); | ||
| 620 | |||
| 621 | if (!prompt) { | ||
| 622 | return; | ||
| 623 | } | ||
| 624 | |||
| 625 | const { rawPrompt, lastUsedIndex } = await getRawSummaryPrompt(context, prompt); | ||
| 626 | |||
| 627 | if (lastUsedIndex === null || lastUsedIndex === -1) { | ||
| 628 | if (force) { | ||
| 629 | toastr.info('To try again, remove the latest summary.', 'No messages found to summarize'); | ||
| 630 | } | ||
| 631 | |||
| 632 | return null; | ||
| 633 | } | ||
| 634 | |||
| 635 | const messages = [ | ||
| 636 | { role: 'system', content: prompt }, | ||
| 637 | { role: 'user', content: rawPrompt }, | ||
| 638 | ]; | ||
| 639 | |||
| 640 | const params = {}; | ||
| 641 | |||
| 642 | if (extension_settings.memory.overrideResponseLength > 0) { | ||
| 643 | params.max_tokens = extension_settings.memory.overrideResponseLength; | ||
| 644 | } | ||
| 645 | |||
| 646 | const summary = await generateWebLlmChatPrompt(messages, params); | ||
| 647 | const newContext = getContext(); | ||
| 648 | |||
| 649 | // something changed during summarization request | ||
| 650 | if (newContext.groupId !== context.groupId || | ||
| 651 | newContext.chatId !== context.chatId || | ||
| 652 | (!newContext.groupId && (newContext.characterId !== context.characterId))) { | ||
| 653 | console.log('Context changed, summary discarded'); | ||
| 654 | return; | ||
| 655 | } | ||
| 656 | |||
| 657 | setMemoryContext(summary, true, lastUsedIndex); | ||
| 658 | return summary; | ||
| 659 | } | ||
| 660 | |||
| 661 | async function summarizeChatMain(context, force, skipWIAN) { | ||
| 662 | const prompt = await getSummaryPromptForNow(context, force); | ||
| 663 | |||
| 664 | if (!prompt) { | ||
| 540 | return; | 665 | return; |
| 541 | } | 666 | } |
| 542 | 667 | ||
| @@ -624,7 +749,7 @@ async function getRawSummaryPrompt(context, prompt) { | |||
| 624 | chat.pop(); // We always exclude the last message from the buffer | 749 | chat.pop(); // We always exclude the last message from the buffer |
| 625 | const chatBuffer = []; | 750 | const chatBuffer = []; |
| 626 | const PADDING = 64; | 751 | const PADDING = 64; |
| 627 | const PROMPT_SIZE = getMaxContextSize(extension_settings.memory.overrideResponseLength); | 752 | const PROMPT_SIZE = await getSourceContextSize(); |
| 628 | let latestUsedMessage = null; | 753 | let latestUsedMessage = null; |
| 629 | 754 | ||
| 630 | for (let index = latestSummaryIndex + 1; index < chat.length; index++) { | 755 | for (let index = latestSummaryIndex + 1; index < chat.length; index++) { |
| @@ -641,7 +766,7 @@ async function getRawSummaryPrompt(context, prompt) { | |||
| 641 | const entry = `${message.name}:\n${message.mes}`; | 766 | const entry = `${message.name}:\n${message.mes}`; |
| 642 | chatBuffer.push(entry); | 767 | chatBuffer.push(entry); |
| 643 | 768 | ||
| 644 | const tokens = await getTokenCountAsync(getMemoryString(true), PADDING); | 769 | const tokens = await countSourceTokens(getMemoryString(true), PADDING); |
| 645 | 770 | ||
| 646 | if (tokens > PROMPT_SIZE) { | 771 | if (tokens > PROMPT_SIZE) { |
| 647 | chatBuffer.pop(); | 772 | chatBuffer.pop(); |
| @@ -670,7 +795,7 @@ async function summarizeChatExtras(context) { | |||
| 670 | const reversedChat = chat.slice().reverse(); | 795 | const reversedChat = chat.slice().reverse(); |
| 671 | reversedChat.shift(); | 796 | reversedChat.shift(); |
| 672 | const memoryBuffer = []; | 797 | const memoryBuffer = []; |
| 673 | const CONTEXT_SIZE = 1024 - 64; | 798 | const CONTEXT_SIZE = await getSourceContextSize(); |
| 674 | 799 | ||
| 675 | for (const message of reversedChat) { | 800 | for (const message of reversedChat) { |
| 676 | // we reached the point of latest memory | 801 | // we reached the point of latest memory |
| @@ -688,14 +813,14 @@ async function summarizeChatExtras(context) { | |||
| 688 | memoryBuffer.push(entry); | 813 | memoryBuffer.push(entry); |
| 689 | 814 | ||
| 690 | // check if token limit was reached | 815 | // check if token limit was reached |
| 691 | const tokens = getTextTokens(tokenizers.GPT2, getMemoryString()).length; | 816 | const tokens = await countSourceTokens(getMemoryString()); |
| 692 | if (tokens >= CONTEXT_SIZE) { | 817 | if (tokens >= CONTEXT_SIZE) { |
| 693 | break; | 818 | break; |
| 694 | } | 819 | } |
| 695 | } | 820 | } |
| 696 | 821 | ||
| 697 | const resultingString = getMemoryString(); | 822 | const resultingString = getMemoryString(); |
| 698 | const resultingTokens = getTextTokens(tokenizers.GPT2, resultingString).length; | 823 | const resultingTokens = await countSourceTokens(resultingString); |
| 699 | 824 | ||
| 700 | if (!resultingString || resultingTokens < CONTEXT_SIZE) { | 825 | if (!resultingString || resultingTokens < CONTEXT_SIZE) { |
| 701 | console.debug('Not enough context to summarize'); | 826 | console.debug('Not enough context to summarize'); |
| @@ -800,8 +925,7 @@ function reinsertMemory() { | |||
| 800 | * @param {number|null} index Index of the chat message to save the summary to. If null, the pre-last message is used. | 925 | * @param {number|null} index Index of the chat message to save the summary to. If null, the pre-last message is used. |
| 801 | */ | 926 | */ |
| 802 | function setMemoryContext(value, saveToMessage, index = null) { | 927 | function setMemoryContext(value, saveToMessage, index = null) { |
| 803 | const context = getContext(); | 928 | setExtensionPrompt(MODULE_NAME, formatMemoryValue(value), extension_settings.memory.position, extension_settings.memory.depth, extension_settings.memory.scan, extension_settings.memory.role); |
| 804 | context.setExtensionPrompt(MODULE_NAME, formatMemoryValue(value), extension_settings.memory.position, extension_settings.memory.depth, false, extension_settings.memory.role); | ||
| 805 | $('#memory_contents').val(value); | 929 | $('#memory_contents').val(value); |
| 806 | 930 | ||
| 807 | const summaryLog = value | 931 | const summaryLog = value |
| @@ -809,6 +933,7 @@ function setMemoryContext(value, saveToMessage, index = null) { | |||
| 809 | : 'Summary has no content'; | 933 | : 'Summary has no content'; |
| 810 | console.debug(summaryLog); | 934 | console.debug(summaryLog); |
| 811 | 935 | ||
| 936 | const context = getContext(); | ||
| 812 | if (saveToMessage && context.chat.length) { | 937 | if (saveToMessage && context.chat.length) { |
| 813 | const idx = index ?? context.chat.length - 2; | 938 | const idx = index ?? context.chat.length - 2; |
| 814 | const mes = context.chat[idx < 0 ? 0 : idx]; | 939 | const mes = context.chat[idx < 0 ? 0 : idx]; |
| @@ -894,6 +1019,7 @@ function setupListeners() { | |||
| 894 | $('#memory_prompt_words_auto').off('click').on('click', onPromptForceWordsAutoClick); | 1019 | $('#memory_prompt_words_auto').off('click').on('click', onPromptForceWordsAutoClick); |
| 895 | $('#memory_override_response_length').off('click').on('input', onOverrideResponseLengthInput); | 1020 | $('#memory_override_response_length').off('click').on('input', onOverrideResponseLengthInput); |
| 896 | $('#memory_max_messages_per_request').off('click').on('input', onMaxMessagesPerRequestInput); | 1021 | $('#memory_max_messages_per_request').off('click').on('input', onMaxMessagesPerRequestInput); |
| 1022 | $('#memory_include_wi_scan').off('input').on('input', onMemoryIncludeWIScanInput); | ||
| 897 | $('#summarySettingsBlockToggle').off('click').on('click', function () { | 1023 | $('#summarySettingsBlockToggle').off('click').on('click', function () { |
| 898 | console.log('saw settings button click'); | 1024 | console.log('saw settings button click'); |
| 899 | $('#summarySettingsBlock').slideToggle(200, 'swing'); //toggleClass("hidden"); | 1025 | $('#summarySettingsBlock').slideToggle(200, 'swing'); //toggleClass("hidden"); |
| @@ -922,7 +1048,7 @@ jQuery(async function () { | |||
| 922 | name: 'summarize', | 1048 | name: 'summarize', |
| 923 | callback: summarizeCallback, | 1049 | callback: summarizeCallback, |
| 924 | namedArgumentList: [ | 1050 | namedArgumentList: [ |
| 925 | new SlashCommandNamedArgument('source', 'API to use for summarization', [ARGUMENT_TYPE.STRING], false, false, '', ['main', 'extras']), | 1051 | new SlashCommandNamedArgument('source', 'API to use for summarization', [ARGUMENT_TYPE.STRING], false, false, '', Object.values(summary_sources)), |
| 926 | SlashCommandNamedArgument.fromProps({ | 1052 | SlashCommandNamedArgument.fromProps({ |
| 927 | name: 'prompt', | 1053 | name: 'prompt', |
| 928 | description: 'prompt to use for summarization', | 1054 | description: 'prompt to use for summarization', |
| @@ -13,6 +13,7 @@ | |||
| 13 | <select id="summary_source"> | 13 | <select id="summary_source"> |
| 14 | <option value="main" data-i18n="ext_sum_main_api">Main API</option> | 14 | <option value="main" data-i18n="ext_sum_main_api">Main API</option> |
| 15 | <option value="extras">Extras API</option> | 15 | <option value="extras">Extras API</option> |
| 16 | <option value="webllm" data-i18n="ext_sum_webllm">WebLLM Extension</option> | ||
| 16 | </select><br> | 17 | </select><br> |
| 17 | 18 | ||
| 18 | <div class="flex-container justifyspacebetween alignitemscenter"> | 19 | <div class="flex-container justifyspacebetween alignitemscenter"> |
| @@ -24,7 +25,7 @@ | |||
| 24 | 25 | ||
| 25 | <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> | 26 | <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> |
| 26 | <div class="memory_contents_controls"> | 27 | <div class="memory_contents_controls"> |
| 27 | <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"> | 28 | <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"> |
| 28 | <i class="fa-solid fa-database"></i> | 29 | <i class="fa-solid fa-database"></i> |
| 29 | <span data-i18n="ext_sum_force_text">Summarize now</span> | 30 | <span data-i18n="ext_sum_force_text">Summarize now</span> |
| 30 | </div> | 31 | </div> |
| @@ -58,7 +59,7 @@ | |||
| 58 | <span data-i18n="ext_sum_prompt_builder_3">Classic, blocking</span> | 59 | <span data-i18n="ext_sum_prompt_builder_3">Classic, blocking</span> |
| 59 | </label> | 60 | </label> |
| 60 | </div> | 61 | </div> |
| 61 | <div data-summary-source="main"> | 62 | <div data-summary-source="main,webllm"> |
| 62 | <label for="memory_prompt" class="title_restorable"> | 63 | <label for="memory_prompt" class="title_restorable"> |
| 63 | <span data-i18n="Summary Prompt">Summary Prompt</span> | 64 | <span data-i18n="Summary Prompt">Summary Prompt</span> |
| 64 | <div id="memory_prompt_restore" data-i18n="[title]ext_sum_restore_default_prompt_tip" title="Restore default prompt" class="right_menu_button"> | 65 | <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 @@ | |||
| 74 | </label> | 75 | </label> |
| 75 | <input id="memory_override_response_length" type="range" value="{{defaultSettings.overrideResponseLength}}" min="{{defaultSettings.overrideResponseLengthMin}}" max="{{defaultSettings.overrideResponseLengthMax}}" step="{{defaultSettings.overrideResponseLengthStep}}" /> | 76 | <input id="memory_override_response_length" type="range" value="{{defaultSettings.overrideResponseLength}}" min="{{defaultSettings.overrideResponseLengthMin}}" max="{{defaultSettings.overrideResponseLengthMax}}" step="{{defaultSettings.overrideResponseLengthStep}}" /> |
| 76 | <label for="memory_max_messages_per_request"> | 77 | <label for="memory_max_messages_per_request"> |
| 77 | <span data-i18n="ext_sum_raw_max_msg">[Raw] Max messages per request</span> (<span id="memory_max_messages_per_request_value"></span>) | 78 | <span data-i18n="ext_sum_raw_max_msg">[Raw/WebLLM] Max messages per request</span> (<span id="memory_max_messages_per_request_value"></span>) |
| 78 | <small class="memory_disabled_hint" data-i18n="ext_sum_0_unlimited">0 = unlimited</small> | 79 | <small class="memory_disabled_hint" data-i18n="ext_sum_0_unlimited">0 = unlimited</small> |
| 79 | </label> | 80 | </label> |
| 80 | <input id="memory_max_messages_per_request" type="range" value="{{defaultSettings.maxMessagesPerRequest}}" min="{{defaultSettings.maxMessagesPerRequestMin}}" max="{{defaultSettings.maxMessagesPerRequestMax}}" step="{{defaultSettings.maxMessagesPerRequestStep}}" /> | 81 | <input id="memory_max_messages_per_request" type="range" value="{{defaultSettings.maxMessagesPerRequest}}" min="{{defaultSettings.maxMessagesPerRequestMin}}" max="{{defaultSettings.maxMessagesPerRequestMax}}" step="{{defaultSettings.maxMessagesPerRequestStep}}" /> |
| @@ -109,8 +110,17 @@ | |||
| 109 | <textarea id="memory_template" class="text_pole textarea_compact" rows="2" data-i18n="[placeholder]ext_sum_memory_template_placeholder" placeholder="{{summary}} will resolve to the current summary contents."></textarea> | 110 | <textarea id="memory_template" class="text_pole textarea_compact" rows="2" data-i18n="[placeholder]ext_sum_memory_template_placeholder" placeholder="{{summary}} will resolve to the current summary contents."></textarea> |
| 110 | </div> | 111 | </div> |
| 111 | <label for="memory_position" data-i18n="ext_sum_injection_position">Injection Position</label> | 112 | <label for="memory_position" data-i18n="ext_sum_injection_position">Injection Position</label> |
| 113 | <label class="checkbox_label" for="memory_include_wi_scan" data-i18n="[title]ext_sum_include_wi_scan_desc" title="Include the latest summary in the WI scan."> | ||
| 114 | <input id="memory_include_wi_scan" type="checkbox" /> | ||
| 115 | <span data-i18n="ext_sum_include_wi_scan">Include in World Info Scanning</span> | ||
| 116 | </label> | ||
| 112 | <div class="radio_group"> | 117 | <div class="radio_group"> |
| 113 | <label> | 118 | <label> |
| 119 | <input type="radio" name="memory_position" value="-1" /> | ||
| 120 | <span data-i18n="None (not injected)">None (not injected)</span> | ||
| 121 | <i class="fa-solid fa-info-circle" title="The summary will not be injected into the prompt. You can still access it via the {{summary}} macro." data-i18n="[title]ext_sum_injection_position_none"></i> | ||
| 122 | </label> | ||
| 123 | <label> | ||
| 114 | <input type="radio" name="memory_position" value="2" /> | 124 | <input type="radio" name="memory_position" value="2" /> |
| 115 | <span data-i18n="Before Main Prompt / Story String">Before Main Prompt / Story String</span> | 125 | <span data-i18n="Before Main Prompt / Story String">Before Main Prompt / Story String</span> |
| 116 | </label> | 126 | </label> |
| @@ -24,9 +24,17 @@ export class QuickReplyApi { | |||
| 24 | 24 | ||
| 25 | 25 | ||
| 26 | /** | 26 | /** |
| 27 | * @param {QuickReply} qr | ||
| 28 | * @returns {QuickReplySet} | ||
| 29 | */ | ||
| 30 | getSetByQr(qr) { | ||
| 31 | return QuickReplySet.list.find(it=>it.qrList.includes(qr)); | ||
| 32 | } | ||
| 33 | |||
| 34 | /** | ||
| 27 | * Finds and returns an existing Quick Reply Set by its name. | 35 | * Finds and returns an existing Quick Reply Set by its name. |
| 28 | * | 36 | * |
| 29 | * @param {String} name name of the quick reply set | 37 | * @param {string} name name of the quick reply set |
| 30 | * @returns the quick reply set, or undefined if not found | 38 | * @returns the quick reply set, or undefined if not found |
| 31 | */ | 39 | */ |
| 32 | getSetByName(name) { | 40 | getSetByName(name) { |
| @@ -36,13 +44,14 @@ export class QuickReplyApi { | |||
| 36 | /** | 44 | /** |
| 37 | * Finds and returns an existing Quick Reply by its set's name and its label. | 45 | * Finds and returns an existing Quick Reply by its set's name and its label. |
| 38 | * | 46 | * |
| 39 | * @param {String} setName name of the quick reply set | 47 | * @param {string} setName name of the quick reply set |
| 40 | * @param {String} label label of the quick reply | 48 | * @param {string|number} label label or numeric ID of the quick reply |
| 41 | * @returns the quick reply, or undefined if not found | 49 | * @returns the quick reply, or undefined if not found |
| 42 | */ | 50 | */ |
| 43 | getQrByLabel(setName, label) { | 51 | getQrByLabel(setName, label) { |
| 44 | const set = this.getSetByName(setName); | 52 | const set = this.getSetByName(setName); |
| 45 | if (!set) return; | 53 | if (!set) return; |
| 54 | if (Number.isInteger(label)) return set.qrList.find(it=>it.id == label); | ||
| 46 | return set.qrList.find(it=>it.label == label); | 55 | return set.qrList.find(it=>it.label == label); |
| 47 | } | 56 | } |
| 48 | 57 | ||
| @@ -70,24 +79,25 @@ export class QuickReplyApi { | |||
| 70 | /** | 79 | /** |
| 71 | * Executes an existing quick reply. | 80 | * Executes an existing quick reply. |
| 72 | * | 81 | * |
| 73 | * @param {String} setName name of the existing quick reply set | 82 | * @param {string} setName name of the existing quick reply set |
| 74 | * @param {String} label label of the existing quick reply (text on the button) | 83 | * @param {string|number} label label of the existing quick reply (text on the button) or its numeric ID |
| 75 | * @param {Object} [args] optional arguments | 84 | * @param {object} [args] optional arguments |
| 85 | * @param {import('../../../slash-commands.js').ExecuteSlashCommandsOptions} [options] optional execution options | ||
| 76 | */ | 86 | */ |
| 77 | async executeQuickReply(setName, label, args = {}) { | 87 | async executeQuickReply(setName, label, args = {}, options = {}) { |
| 78 | const qr = this.getQrByLabel(setName, label); | 88 | const qr = this.getQrByLabel(setName, label); |
| 79 | if (!qr) { | 89 | if (!qr) { |
| 80 | throw new Error(`No quick reply with label "${label}" in set "${setName}" found.`); | 90 | throw new Error(`No quick reply with label "${label}" in set "${setName}" found.`); |
| 81 | } | 91 | } |
| 82 | return await qr.execute(args); | 92 | return await qr.execute(args, false, false, options); |
| 83 | } | 93 | } |
| 84 | 94 | ||
| 85 | 95 | ||
| 86 | /** | 96 | /** |
| 87 | * Adds or removes a quick reply set to the list of globally active quick reply sets. | 97 | * Adds or removes a quick reply set to the list of globally active quick reply sets. |
| 88 | * | 98 | * |
| 89 | * @param {String} name the name of the set | 99 | * @param {string} name the name of the set |
| 90 | * @param {Boolean} isVisible whether to show the set's buttons or not | 100 | * @param {boolean} isVisible whether to show the set's buttons or not |
| 91 | */ | 101 | */ |
| 92 | toggleGlobalSet(name, isVisible = true) { | 102 | toggleGlobalSet(name, isVisible = true) { |
| 93 | const set = this.getSetByName(name); | 103 | const set = this.getSetByName(name); |
| @@ -104,8 +114,8 @@ export class QuickReplyApi { | |||
| 104 | /** | 114 | /** |
| 105 | * Adds a quick reply set to the list of globally active quick reply sets. | 115 | * Adds a quick reply set to the list of globally active quick reply sets. |
| 106 | * | 116 | * |
| 107 | * @param {String} name the name of the set | 117 | * @param {string} name the name of the set |
| 108 | * @param {Boolean} isVisible whether to show the set's buttons or not | 118 | * @param {boolean} isVisible whether to show the set's buttons or not |
| 109 | */ | 119 | */ |
| 110 | addGlobalSet(name, isVisible = true) { | 120 | addGlobalSet(name, isVisible = true) { |
| 111 | const set = this.getSetByName(name); | 121 | const set = this.getSetByName(name); |
| @@ -118,7 +128,7 @@ export class QuickReplyApi { | |||
| 118 | /** | 128 | /** |
| 119 | * Removes a quick reply set from the list of globally active quick reply sets. | 129 | * Removes a quick reply set from the list of globally active quick reply sets. |
| 120 | * | 130 | * |
| 121 | * @param {String} name the name of the set | 131 | * @param {string} name the name of the set |
| 122 | */ | 132 | */ |
| 123 | removeGlobalSet(name) { | 133 | removeGlobalSet(name) { |
| 124 | const set = this.getSetByName(name); | 134 | const set = this.getSetByName(name); |
| @@ -132,8 +142,8 @@ export class QuickReplyApi { | |||
| 132 | /** | 142 | /** |
| 133 | * Adds or removes a quick reply set to the list of the current chat's active quick reply sets. | 143 | * Adds or removes a quick reply set to the list of the current chat's active quick reply sets. |
| 134 | * | 144 | * |
| 135 | * @param {String} name the name of the set | 145 | * @param {string} name the name of the set |
| 136 | * @param {Boolean} isVisible whether to show the set's buttons or not | 146 | * @param {boolean} isVisible whether to show the set's buttons or not |
| 137 | */ | 147 | */ |
| 138 | toggleChatSet(name, isVisible = true) { | 148 | toggleChatSet(name, isVisible = true) { |
| 139 | if (!this.settings.chatConfig) return; | 149 | if (!this.settings.chatConfig) return; |
| @@ -151,8 +161,8 @@ export class QuickReplyApi { | |||
| 151 | /** | 161 | /** |
| 152 | * Adds a quick reply set to the list of the current chat's active quick reply sets. | 162 | * Adds a quick reply set to the list of the current chat's active quick reply sets. |
| 153 | * | 163 | * |
| 154 | * @param {String} name the name of the set | 164 | * @param {string} name the name of the set |
| 155 | * @param {Boolean} isVisible whether to show the set's buttons or not | 165 | * @param {boolean} isVisible whether to show the set's buttons or not |
| 156 | */ | 166 | */ |
| 157 | addChatSet(name, isVisible = true) { | 167 | addChatSet(name, isVisible = true) { |
| 158 | if (!this.settings.chatConfig) return; | 168 | if (!this.settings.chatConfig) return; |
| @@ -166,7 +176,7 @@ export class QuickReplyApi { | |||
| 166 | /** | 176 | /** |
| 167 | * Removes a quick reply set from the list of the current chat's active quick reply sets. | 177 | * Removes a quick reply set from the list of the current chat's active quick reply sets. |
| 168 | * | 178 | * |
| 169 | * @param {String} name the name of the set | 179 | * @param {string} name the name of the set |
| 170 | */ | 180 | */ |
| 171 | removeChatSet(name) { | 181 | removeChatSet(name) { |
| 172 | if (!this.settings.chatConfig) return; | 182 | if (!this.settings.chatConfig) return; |
| @@ -181,21 +191,26 @@ export class QuickReplyApi { | |||
| 181 | /** | 191 | /** |
| 182 | * Creates a new quick reply in an existing quick reply set. | 192 | * Creates a new quick reply in an existing quick reply set. |
| 183 | * | 193 | * |
| 184 | * @param {String} setName name of the quick reply set to insert the new quick reply into | 194 | * @param {string} setName name of the quick reply set to insert the new quick reply into |
| 185 | * @param {String} label label for the new quick reply (text on the button) | 195 | * @param {string} label label for the new quick reply (text on the button) |
| 186 | * @param {Object} [props] | 196 | * @param {object} [props] |
| 187 | * @param {String} [props.message] the message to be sent or slash command to be executed by the new quick reply | 197 | * @param {string} [props.icon] the icon to show on the QR button |
| 188 | * @param {String} [props.title] the title / tooltip to be shown on the quick reply button | 198 | * @param {boolean} [props.showLabel] whether to show the label even when an icon is assigned |
| 189 | * @param {Boolean} [props.isHidden] whether to hide or show the button | 199 | * @param {string} [props.message] the message to be sent or slash command to be executed by the new quick reply |
| 190 | * @param {Boolean} [props.executeOnStartup] whether to execute the quick reply when SillyTavern starts | 200 | * @param {string} [props.title] the title / tooltip to be shown on the quick reply button |
| 191 | * @param {Boolean} [props.executeOnUser] whether to execute the quick reply after a user has sent a message | 201 | * @param {boolean} [props.isHidden] whether to hide or show the button |
| 192 | * @param {Boolean} [props.executeOnAi] whether to execute the quick reply after the AI has sent a message | 202 | * @param {boolean} [props.executeOnStartup] whether to execute the quick reply when SillyTavern starts |
| 193 | * @param {Boolean} [props.executeOnChatChange] whether to execute the quick reply when a new chat is loaded | 203 | * @param {boolean} [props.executeOnUser] whether to execute the quick reply after a user has sent a message |
| 194 | * @param {Boolean} [props.executeOnGroupMemberDraft] whether to execute the quick reply when a group member is selected | 204 | * @param {boolean} [props.executeOnAi] whether to execute the quick reply after the AI has sent a message |
| 195 | * @param {String} [props.automationId] when not empty, the quick reply will be executed when the WI with the given automation ID is activated | 205 | * @param {boolean} [props.executeOnChatChange] whether to execute the quick reply when a new chat is loaded |
| 206 | * @param {boolean} [props.executeOnGroupMemberDraft] whether to execute the quick reply when a group member is selected | ||
| 207 | * @param {boolean} [props.executeOnNewChat] whether to execute the quick reply when a new chat is created | ||
| 208 | * @param {string} [props.automationId] when not empty, the quick reply will be executed when the WI with the given automation ID is activated | ||
| 196 | * @returns {QuickReply} the new quick reply | 209 | * @returns {QuickReply} the new quick reply |
| 197 | */ | 210 | */ |
| 198 | createQuickReply(setName, label, { | 211 | createQuickReply(setName, label, { |
| 212 | icon, | ||
| 213 | showLabel, | ||
| 199 | message, | 214 | message, |
| 200 | title, | 215 | title, |
| 201 | isHidden, | 216 | isHidden, |
| @@ -204,6 +219,7 @@ export class QuickReplyApi { | |||
| 204 | executeOnAi, | 219 | executeOnAi, |
| 205 | executeOnChatChange, | 220 | executeOnChatChange, |
| 206 | executeOnGroupMemberDraft, | 221 | executeOnGroupMemberDraft, |
| 222 | executeOnNewChat, | ||
| 207 | automationId, | 223 | automationId, |
| 208 | } = {}) { | 224 | } = {}) { |
| 209 | const set = this.getSetByName(setName); | 225 | const set = this.getSetByName(setName); |
| @@ -212,6 +228,8 @@ export class QuickReplyApi { | |||
| 212 | } | 228 | } |
| 213 | const qr = set.addQuickReply(); | 229 | const qr = set.addQuickReply(); |
| 214 | qr.label = label ?? ''; | 230 | qr.label = label ?? ''; |
| 231 | qr.icon = icon ?? ''; | ||
| 232 | qr.showLabel = showLabel ?? false; | ||
| 215 | qr.message = message ?? ''; | 233 | qr.message = message ?? ''; |
| 216 | qr.title = title ?? ''; | 234 | qr.title = title ?? ''; |
| 217 | qr.isHidden = isHidden ?? false; | 235 | qr.isHidden = isHidden ?? false; |
| @@ -220,6 +238,7 @@ export class QuickReplyApi { | |||
| 220 | qr.executeOnAi = executeOnAi ?? false; | 238 | qr.executeOnAi = executeOnAi ?? false; |
| 221 | qr.executeOnChatChange = executeOnChatChange ?? false; | 239 | qr.executeOnChatChange = executeOnChatChange ?? false; |
| 222 | qr.executeOnGroupMemberDraft = executeOnGroupMemberDraft ?? false; | 240 | qr.executeOnGroupMemberDraft = executeOnGroupMemberDraft ?? false; |
| 241 | qr.executeOnNewChat = executeOnNewChat ?? false; | ||
| 223 | qr.automationId = automationId ?? ''; | 242 | qr.automationId = automationId ?? ''; |
| 224 | qr.onUpdate(); | 243 | qr.onUpdate(); |
| 225 | return qr; | 244 | return qr; |
| @@ -228,22 +247,27 @@ export class QuickReplyApi { | |||
| 228 | /** | 247 | /** |
| 229 | * Updates an existing quick reply. | 248 | * Updates an existing quick reply. |
| 230 | * | 249 | * |
| 231 | * @param {String} setName name of the existing quick reply set | 250 | * @param {string} setName name of the existing quick reply set |
| 232 | * @param {String} label label of the existing quick reply (text on the button) | 251 | * @param {string|number} label label of the existing quick reply (text on the button) or its numeric ID |
| 233 | * @param {Object} [props] | 252 | * @param {object} [props] |
| 234 | * @param {String} [props.newLabel] new label for quick reply (text on the button) | 253 | * @param {string} [props.icon] the icon to show on the QR button |
| 235 | * @param {String} [props.message] the message to be sent or slash command to be executed by the quick reply | 254 | * @param {boolean} [props.showLabel] whether to show the label even when an icon is assigned |
| 236 | * @param {String} [props.title] the title / tooltip to be shown on the quick reply button | 255 | * @param {string} [props.newLabel] new label for quick reply (text on the button) |
| 237 | * @param {Boolean} [props.isHidden] whether to hide or show the button | 256 | * @param {string} [props.message] the message to be sent or slash command to be executed by the quick reply |
| 238 | * @param {Boolean} [props.executeOnStartup] whether to execute the quick reply when SillyTavern starts | 257 | * @param {string} [props.title] the title / tooltip to be shown on the quick reply button |
| 239 | * @param {Boolean} [props.executeOnUser] whether to execute the quick reply after a user has sent a message | 258 | * @param {boolean} [props.isHidden] whether to hide or show the button |
| 240 | * @param {Boolean} [props.executeOnAi] whether to execute the quick reply after the AI has sent a message | 259 | * @param {boolean} [props.executeOnStartup] whether to execute the quick reply when SillyTavern starts |
| 241 | * @param {Boolean} [props.executeOnChatChange] whether to execute the quick reply when a new chat is loaded | 260 | * @param {boolean} [props.executeOnUser] whether to execute the quick reply after a user has sent a message |
| 242 | * @param {Boolean} [props.executeOnGroupMemberDraft] whether to execute the quick reply when a group member is selected | 261 | * @param {boolean} [props.executeOnAi] whether to execute the quick reply after the AI has sent a message |
| 243 | * @param {String} [props.automationId] when not empty, the quick reply will be executed when the WI with the given automation ID is activated | 262 | * @param {boolean} [props.executeOnChatChange] whether to execute the quick reply when a new chat is loaded |
| 263 | * @param {boolean} [props.executeOnGroupMemberDraft] whether to execute the quick reply when a group member is selected | ||
| 264 | * @param {boolean} [props.executeOnNewChat] whether to execute the quick reply when a new chat is created | ||
| 265 | * @param {string} [props.automationId] when not empty, the quick reply will be executed when the WI with the given automation ID is activated | ||
| 244 | * @returns {QuickReply} the altered quick reply | 266 | * @returns {QuickReply} the altered quick reply |
| 245 | */ | 267 | */ |
| 246 | updateQuickReply(setName, label, { | 268 | updateQuickReply(setName, label, { |
| 269 | icon, | ||
| 270 | showLabel, | ||
| 247 | newLabel, | 271 | newLabel, |
| 248 | message, | 272 | message, |
| 249 | title, | 273 | title, |
| @@ -253,12 +277,15 @@ export class QuickReplyApi { | |||
| 253 | executeOnAi, | 277 | executeOnAi, |
| 254 | executeOnChatChange, | 278 | executeOnChatChange, |
| 255 | executeOnGroupMemberDraft, | 279 | executeOnGroupMemberDraft, |
| 280 | executeOnNewChat, | ||
| 256 | automationId, | 281 | automationId, |
| 257 | } = {}) { | 282 | } = {}) { |
| 258 | const qr = this.getQrByLabel(setName, label); | 283 | const qr = this.getQrByLabel(setName, label); |
| 259 | if (!qr) { | 284 | if (!qr) { |
| 260 | throw new Error(`No quick reply with label "${label}" in set "${setName}" found.`); | 285 | throw new Error(`No quick reply with label "${label}" in set "${setName}" found.`); |
| 261 | } | 286 | } |
| 287 | qr.updateIcon(icon ?? qr.icon); | ||
| 288 | qr.updateShowLabel(showLabel ?? qr.showLabel); | ||
| 262 | qr.updateLabel(newLabel ?? qr.label); | 289 | qr.updateLabel(newLabel ?? qr.label); |
| 263 | qr.updateMessage(message ?? qr.message); | 290 | qr.updateMessage(message ?? qr.message); |
| 264 | qr.updateTitle(title ?? qr.title); | 291 | qr.updateTitle(title ?? qr.title); |
| @@ -268,6 +295,7 @@ export class QuickReplyApi { | |||
| 268 | qr.executeOnAi = executeOnAi ?? qr.executeOnAi; | 295 | qr.executeOnAi = executeOnAi ?? qr.executeOnAi; |
| 269 | qr.executeOnChatChange = executeOnChatChange ?? qr.executeOnChatChange; | 296 | qr.executeOnChatChange = executeOnChatChange ?? qr.executeOnChatChange; |
| 270 | qr.executeOnGroupMemberDraft = executeOnGroupMemberDraft ?? qr.executeOnGroupMemberDraft; | 297 | qr.executeOnGroupMemberDraft = executeOnGroupMemberDraft ?? qr.executeOnGroupMemberDraft; |
| 298 | qr.executeOnNewChat = executeOnNewChat ?? qr.executeOnNewChat; | ||
| 271 | qr.automationId = automationId ?? qr.automationId; | 299 | qr.automationId = automationId ?? qr.automationId; |
| 272 | qr.onUpdate(); | 300 | qr.onUpdate(); |
| 273 | return qr; | 301 | return qr; |
| @@ -276,8 +304,8 @@ export class QuickReplyApi { | |||
| 276 | /** | 304 | /** |
| 277 | * Deletes an existing quick reply. | 305 | * Deletes an existing quick reply. |
| 278 | * | 306 | * |
| 279 | * @param {String} setName name of the existing quick reply set | 307 | * @param {string} setName name of the existing quick reply set |
| 280 | * @param {String} label label of the existing quick reply (text on the button) | 308 | * @param {string|number} label label of the existing quick reply (text on the button) or its numeric ID |
| 281 | */ | 309 | */ |
| 282 | deleteQuickReply(setName, label) { | 310 | deleteQuickReply(setName, label) { |
| 283 | const qr = this.getQrByLabel(setName, label); | 311 | const qr = this.getQrByLabel(setName, label); |
| @@ -291,10 +319,10 @@ export class QuickReplyApi { | |||
| 291 | /** | 319 | /** |
| 292 | * Adds an existing quick reply set as a context menu to an existing quick reply. | 320 | * Adds an existing quick reply set as a context menu to an existing quick reply. |
| 293 | * | 321 | * |
| 294 | * @param {String} setName name of the existing quick reply set containing the quick reply | 322 | * @param {string} setName name of the existing quick reply set containing the quick reply |
| 295 | * @param {String} label label of the existing quick reply | 323 | * @param {string|number} label label of the existing quick reply or its numeric ID |
| 296 | * @param {String} contextSetName name of the existing quick reply set to be used as a context menu | 324 | * @param {string} contextSetName name of the existing quick reply set to be used as a context menu |
| 297 | * @param {Boolean} isChained whether or not to chain the context menu quick replies | 325 | * @param {boolean} isChained whether or not to chain the context menu quick replies |
| 298 | */ | 326 | */ |
| 299 | createContextItem(setName, label, contextSetName, isChained = false) { | 327 | createContextItem(setName, label, contextSetName, isChained = false) { |
| 300 | const qr = this.getQrByLabel(setName, label); | 328 | const qr = this.getQrByLabel(setName, label); |
| @@ -314,9 +342,9 @@ export class QuickReplyApi { | |||
| 314 | /** | 342 | /** |
| 315 | * Removes a quick reply set from a quick reply's context menu. | 343 | * Removes a quick reply set from a quick reply's context menu. |
| 316 | * | 344 | * |
| 317 | * @param {String} setName name of the existing quick reply set containing the quick reply | 345 | * @param {string} setName name of the existing quick reply set containing the quick reply |
| 318 | * @param {String} label label of the existing quick reply | 346 | * @param {string|number} label label of the existing quick reply or its numeric ID |
| 319 | * @param {String} contextSetName name of the existing quick reply set to be used as a context menu | 347 | * @param {string} contextSetName name of the existing quick reply set to be used as a context menu |
| 320 | */ | 348 | */ |
| 321 | deleteContextItem(setName, label, contextSetName) { | 349 | deleteContextItem(setName, label, contextSetName) { |
| 322 | const qr = this.getQrByLabel(setName, label); | 350 | const qr = this.getQrByLabel(setName, label); |
| @@ -333,8 +361,8 @@ export class QuickReplyApi { | |||
| 333 | /** | 361 | /** |
| 334 | * Removes all entries from a quick reply's context menu. | 362 | * Removes all entries from a quick reply's context menu. |
| 335 | * | 363 | * |
| 336 | * @param {String} setName name of the existing quick reply set containing the quick reply | 364 | * @param {string} setName name of the existing quick reply set containing the quick reply |
| 337 | * @param {String} label label of the existing quick reply | 365 | * @param {string|number} label label of the existing quick reply or its numeric ID |
| 338 | */ | 366 | */ |
| 339 | clearContextMenu(setName, label) { | 367 | clearContextMenu(setName, label) { |
| 340 | const qr = this.getQrByLabel(setName, label); | 368 | const qr = this.getQrByLabel(setName, label); |
| @@ -348,11 +376,11 @@ export class QuickReplyApi { | |||
| 348 | /** | 376 | /** |
| 349 | * Create a new quick reply set. | 377 | * Create a new quick reply set. |
| 350 | * | 378 | * |
| 351 | * @param {String} name name of the new quick reply set | 379 | * @param {string} name name of the new quick reply set |
| 352 | * @param {Object} [props] | 380 | * @param {object} [props] |
| 353 | * @param {Boolean} [props.disableSend] whether or not to send the quick replies or put the message or slash command into the char input box | 381 | * @param {boolean} [props.disableSend] whether or not to send the quick replies or put the message or slash command into the char input box |
| 354 | * @param {Boolean} [props.placeBeforeInput] whether or not to place the quick reply contents before the existing user input | 382 | * @param {boolean} [props.placeBeforeInput] whether or not to place the quick reply contents before the existing user input |
| 355 | * @param {Boolean} [props.injectInput] whether or not to automatically inject the user input at the end of the quick reply | 383 | * @param {boolean} [props.injectInput] whether or not to automatically inject the user input at the end of the quick reply |
| 356 | * @returns {Promise<QuickReplySet>} the new quick reply set | 384 | * @returns {Promise<QuickReplySet>} the new quick reply set |
| 357 | */ | 385 | */ |
| 358 | async createSet(name, { | 386 | async createSet(name, { |
| @@ -384,11 +412,11 @@ export class QuickReplyApi { | |||
| 384 | /** | 412 | /** |
| 385 | * Update an existing quick reply set. | 413 | * Update an existing quick reply set. |
| 386 | * | 414 | * |
| 387 | * @param {String} name name of the existing quick reply set | 415 | * @param {string} name name of the existing quick reply set |
| 388 | * @param {Object} [props] | 416 | * @param {object} [props] |
| 389 | * @param {Boolean} [props.disableSend] whether or not to send the quick replies or put the message or slash command into the char input box | 417 | * @param {boolean} [props.disableSend] whether or not to send the quick replies or put the message or slash command into the char input box |
| 390 | * @param {Boolean} [props.placeBeforeInput] whether or not to place the quick reply contents before the existing user input | 418 | * @param {boolean} [props.placeBeforeInput] whether or not to place the quick reply contents before the existing user input |
| 391 | * @param {Boolean} [props.injectInput] whether or not to automatically inject the user input at the end of the quick reply | 419 | * @param {boolean} [props.injectInput] whether or not to automatically inject the user input at the end of the quick reply |
| 392 | * @returns {Promise<QuickReplySet>} the altered quick reply set | 420 | * @returns {Promise<QuickReplySet>} the altered quick reply set |
| 393 | */ | 421 | */ |
| 394 | async updateSet(name, { | 422 | async updateSet(name, { |
| @@ -411,7 +439,7 @@ export class QuickReplyApi { | |||
| 411 | /** | 439 | /** |
| 412 | * Delete an existing quick reply set. | 440 | * Delete an existing quick reply set. |
| 413 | * | 441 | * |
| 414 | * @param {String} name name of the existing quick reply set | 442 | * @param {string} name name of the existing quick reply set |
| 415 | */ | 443 | */ |
| 416 | async deleteSet(name) { | 444 | async deleteSet(name) { |
| 417 | const set = this.getSetByName(name); | 445 | const set = this.getSetByName(name); |
| @@ -451,7 +479,7 @@ export class QuickReplyApi { | |||
| 451 | /** | 479 | /** |
| 452 | * Gets a list of all quick replies in the quick reply set. | 480 | * Gets a list of all quick replies in the quick reply set. |
| 453 | * | 481 | * |
| 454 | * @param {String} setName name of the existing quick reply set | 482 | * @param {string} setName name of the existing quick reply set |
| 455 | * @returns array with the labels of this set's quick replies | 483 | * @returns array with the labels of this set's quick replies |
| 456 | */ | 484 | */ |
| 457 | listQuickReplies(setName) { | 485 | listQuickReplies(setName) { |
| @@ -2,10 +2,23 @@ | |||
| 2 | <div id="qr--main"> | 2 | <div id="qr--main"> |
| 3 | <h3 data-i18n="Labels and Message">Labels and Message</h3> | 3 | <h3 data-i18n="Labels and Message">Labels and Message</h3> |
| 4 | <div class="qr--labels"> | 4 | <div class="qr--labels"> |
| 5 | <label> | 5 | <label class="qr--fit"> |
| 6 | <span class="qr--labelText" data-i18n="Label">Label</span> | 6 | <span class="qr--labelText" data-i18n="Label">Icon</span> |
| 7 | <input type="text" class="text_pole" id="qr--modal-label"> | 7 | <small class="qr--labelHint"> </small> |
| 8 | <div class="menu_button fa-fw" id="qr--modal-icon" title="Click to change icon"></div> | ||
| 8 | </label> | 9 | </label> |
| 10 | <div class="label"> | ||
| 11 | <span class="qr--labelText" data-i18n="Label">Label</span> | ||
| 12 | <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> | ||
| 13 | <div class="qr--inputGroup"> | ||
| 14 | <label class="checkbox_label" title="Show label even if an icon is assigned"> | ||
| 15 | <input type="checkbox" id="qr--modal-showLabel"> | ||
| 16 | Show | ||
| 17 | </label> | ||
| 18 | <input type="text" class="text_pole" id="qr--modal-label"> | ||
| 19 | <div class="menu_button fa-fw fa-solid fa-chevron-down" id="qr--modal-switcher" title="Switch to another QR"></div> | ||
| 20 | </div> | ||
| 21 | </div> | ||
| 9 | <label> | 22 | <label> |
| 10 | <span class="qr--labelText" data-i18n="Title">Title</span> | 23 | <span class="qr--labelText" data-i18n="Title">Title</span> |
| 11 | <small class="qr--labelHint" data-i18n="(tooltip, leave empty to show message or /command)">(tooltip, leave empty to show message or /command)</small> | 24 | <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 @@ | |||
| 33 | <input type="checkbox" id="qr--modal-syntax"> | 46 | <input type="checkbox" id="qr--modal-syntax"> |
| 34 | <span>Syntax highlight</span> | 47 | <span>Syntax highlight</span> |
| 35 | </label> | 48 | </label> |
| 49 | <small>Ctrl+Alt+Click (or F9) to set / remove breakpoints</small> | ||
| 50 | <small>Ctrl+<span id="qr--modal-commentKey"></span> to toggle block comments</small> | ||
| 36 | </div> | 51 | </div> |
| 37 | <div id="qr--modal-messageHolder"> | 52 | <div id="qr--modal-messageHolder"> |
| 38 | <pre id="qr--modal-messageSyntax"><code id="qr--modal-messageSyntaxInner" class="hljs language-stscript"></code></pre> | 53 | <pre id="qr--modal-messageSyntax"><code id="qr--modal-messageSyntaxInner" class="hljs language-stscript"></code></pre> |
| @@ -43,6 +58,10 @@ | |||
| 43 | 58 | ||
| 44 | 59 | ||
| 45 | 60 | ||
| 61 | <div id="qr--resizeHandle"></div> | ||
| 62 | |||
| 63 | |||
| 64 | |||
| 46 | <div id="qr--qrOptions"> | 65 | <div id="qr--qrOptions"> |
| 47 | <h3 data-i18n="Context Menu">Context Menu</h3> | 66 | <h3 data-i18n="Context Menu">Context Menu</h3> |
| 48 | <div id="qr--ctxEditor"> | 67 | <div id="qr--ctxEditor"> |
| @@ -64,7 +83,7 @@ | |||
| 64 | 83 | ||
| 65 | 84 | ||
| 66 | <h3 data-i18n="Auto-Execute">Auto-Execute</h3> | 85 | <h3 data-i18n="Auto-Execute">Auto-Execute</h3> |
| 67 | <div class="flex-container flexFlowColumn"> | 86 | <div id="qr--autoExec" class="flex-container flexFlowColumn"> |
| 68 | <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)"> | 87 | <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)"> |
| 69 | <input type="checkbox" id="qr--preventAutoExecute" > | 88 | <input type="checkbox" id="qr--preventAutoExecute" > |
| 70 | <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 | <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> |
| @@ -90,6 +109,10 @@ | |||
| 90 | <span><i class="fa-solid fa-fw fa-message"></i><span data-i18n="Execute on chat change">Execute on chat change</span></span> | 109 | <span><i class="fa-solid fa-fw fa-message"></i><span data-i18n="Execute on chat change">Execute on chat change</span></span> |
| 91 | </label> | 110 | </label> |
| 92 | <label class="checkbox_label"> | 111 | <label class="checkbox_label"> |
| 112 | <input type="checkbox" id="qr--executeOnNewChat"> | ||
| 113 | <span><i class="fa-solid fa-fw fa-comments"></i><span data-i18n="Execute on new chat">Execute on new chat</span></span> | ||
| 114 | </label> | ||
| 115 | <label class="checkbox_label"> | ||
| 93 | <input type="checkbox" id="qr--executeOnGroupMemberDraft"> | 116 | <input type="checkbox" id="qr--executeOnGroupMemberDraft"> |
| 94 | <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 | <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> |
| 95 | </label> | 118 | </label> |
| @@ -117,11 +140,18 @@ | |||
| 117 | </div> | 140 | </div> |
| 118 | </div> | 141 | </div> |
| 119 | <div id="qr--modal-executeProgress"></div> | 142 | <div id="qr--modal-executeProgress"></div> |
| 120 | <label class="checkbox_label"> | ||
| 121 | <input type="checkbox" id="qr--modal-executeHide"> | ||
| 122 | <span title="Hide editor while executing"> Hide editor while executing</span> | ||
| 123 | </label> | ||
| 124 | <div id="qr--modal-executeErrors"></div> | 143 | <div id="qr--modal-executeErrors"></div> |
| 125 | <div id="qr--modal-executeResult"></div> | 144 | <div id="qr--modal-executeResult"></div> |
| 145 | |||
| 146 | <div id="qr--modal-debugButtons"> | ||
| 147 | <div title="Resume" id="qr--modal-resume" class="qr--modal-debugButton menu_button"></div> | ||
| 148 | <div title="Step Over" id="qr--modal-step" class="qr--modal-debugButton menu_button"></div> | ||
| 149 | <div title="Step Into" id="qr--modal-stepInto" class="qr--modal-debugButton menu_button"></div> | ||
| 150 | <div title="Step Out" id="qr--modal-stepOut" class="qr--modal-debugButton menu_button"></div> | ||
| 151 | <div title="Minimize" id="qr--modal-minimize" class="qr--modal-debugButton menu_button fa-solid fa-minimize"></div> | ||
| 152 | <div title="Maximize" id="qr--modal-maximize" class="qr--modal-debugButton menu_button fa-solid fa-maximize"></div> | ||
| 153 | </div> | ||
| 154 | <textarea rows="1" id="qr--modal-send_textarea" placeholder="Chat input for use with {{input}}" title="Chat input for use with {{input}}"></textarea> | ||
| 155 | <div id="qr--modal-debugState"></div> | ||
| 126 | </div> | 156 | </div> |
| 127 | </div> | 157 | </div> |
| @@ -60,10 +60,20 @@ | |||
| 60 | <label class="flex-container" id="qr--injectInputContainer"> | 60 | <label class="flex-container" id="qr--injectInputContainer"> |
| 61 | <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> | 61 | <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> |
| 62 | </label> | 62 | </label> |
| 63 | <div class="flex-container alignItemsCenter"> | ||
| 64 | <toolcool-color-picker id="qr--color"></toolcool-color-picker> | ||
| 65 | <div class="menu_button" id="qr--colorClear">Clear</div> | ||
| 66 | <span data-i18n="Color">Color</span> | ||
| 67 | </div> | ||
| 68 | <label class="flex-container" id="qr--onlyBorderColorContainer"> | ||
| 69 | <input type="checkbox" id="qr--onlyBorderColor"> <span data-i18n="Only apply color as accent">Only apply color as accent</span> | ||
| 70 | </label> | ||
| 63 | </div> | 71 | </div> |
| 64 | <div id="qr--set-qrList" class="qr--qrList"></div> | 72 | <div id="qr--set-qrList" class="qr--qrList"></div> |
| 65 | <div class="qr--set-qrListActions"> | 73 | <div class="qr--set-qrListActions"> |
| 66 | <div class="qr--add menu_button menu_button_icon fa-solid fa-plus" id="qr--set-add" title="Add quick reply"></div> | 74 | <div class="qr--add menu_button menu_button_icon fa-solid fa-plus" id="qr--set-add" title="Add quick reply"></div> |
| 75 | <div class="qr--paste menu_button menu_button_icon fa-solid fa-paste" id="qr--set-paste" title="Paste quick reply from clipboard"></div> | ||
| 76 | <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> | ||
| 67 | </div> | 77 | </div> |
| 68 | </div> | 78 | </div> |
| 69 | </div> | 79 | </div> |
| @@ -105,6 +105,7 @@ const loadSets = async () => { | |||
| 105 | qr.executeOnAi = slot.autoExecute_botMessage ?? false; | 105 | qr.executeOnAi = slot.autoExecute_botMessage ?? false; |
| 106 | qr.executeOnChatChange = slot.autoExecute_chatLoad ?? false; | 106 | qr.executeOnChatChange = slot.autoExecute_chatLoad ?? false; |
| 107 | qr.executeOnGroupMemberDraft = slot.autoExecute_groupMemberDraft ?? false; | 107 | qr.executeOnGroupMemberDraft = slot.autoExecute_groupMemberDraft ?? false; |
| 108 | qr.executeOnNewChat = slot.autoExecute_newChat ?? false; | ||
| 108 | qr.automationId = slot.automationId ?? ''; | 109 | qr.automationId = slot.automationId ?? ''; |
| 109 | qr.contextList = (slot.contextMenu ?? []).map(it=>({ | 110 | qr.contextList = (slot.contextMenu ?? []).map(it=>({ |
| 110 | set: it.preset, | 111 | set: it.preset, |
| @@ -176,7 +177,7 @@ const init = async () => { | |||
| 176 | buttons.show(); | 177 | buttons.show(); |
| 177 | settings.onSave = ()=>buttons.refresh(); | 178 | settings.onSave = ()=>buttons.refresh(); |
| 178 | 179 | ||
| 179 | window['executeQuickReplyByName'] = async(name, args = {}) => { | 180 | window['executeQuickReplyByName'] = async(name, args = {}, options = {}) => { |
| 180 | let qr = [...settings.config.setList, ...(settings.chatConfig?.setList ?? [])] | 181 | let qr = [...settings.config.setList, ...(settings.chatConfig?.setList ?? [])] |
| 181 | .map(it=>it.set.qrList) | 182 | .map(it=>it.set.qrList) |
| 182 | .flat() | 183 | .flat() |
| @@ -191,7 +192,7 @@ const init = async () => { | |||
| 191 | } | 192 | } |
| 192 | } | 193 | } |
| 193 | if (qr && qr.onExecute) { | 194 | if (qr && qr.onExecute) { |
| 194 | return await qr.execute(args, false, true); | 195 | return await qr.execute(args, false, true, options); |
| 195 | } else { | 196 | } else { |
| 196 | throw new Error(`No Quick Reply found for "${name}".`); | 197 | throw new Error(`No Quick Reply found for "${name}".`); |
| 197 | } | 198 | } |
| @@ -260,3 +261,8 @@ const onWIActivation = async (entries) => { | |||
| 260 | await autoExec.handleWIActivation(entries); | 261 | await autoExec.handleWIActivation(entries); |
| 261 | }; | 262 | }; |
| 262 | eventSource.on(event_types.WORLD_INFO_ACTIVATED, (...args) => executeIfReadyElseQueue(onWIActivation, args)); | 263 | eventSource.on(event_types.WORLD_INFO_ACTIVATED, (...args) => executeIfReadyElseQueue(onWIActivation, args)); |
| 264 | |||
| 265 | const onNewChat = async () => { | ||
| 266 | await autoExec.handleNewChat(); | ||
| 267 | }; | ||
| 268 | eventSource.on(event_types.CHAT_CREATED, (...args) => executeIfReadyElseQueue(onNewChat, args)); | ||
| @@ -0,0 +1,769 @@ | |||
| 1 | var DOCUMENT_FRAGMENT_NODE = 11; | ||
| 2 | |||
| 3 | function morphAttrs(fromNode, toNode) { | ||
| 4 | var toNodeAttrs = toNode.attributes; | ||
| 5 | var attr; | ||
| 6 | var attrName; | ||
| 7 | var attrNamespaceURI; | ||
| 8 | var attrValue; | ||
| 9 | var fromValue; | ||
| 10 | |||
| 11 | // document-fragments dont have attributes so lets not do anything | ||
| 12 | if (toNode.nodeType === DOCUMENT_FRAGMENT_NODE || fromNode.nodeType === DOCUMENT_FRAGMENT_NODE) { | ||
| 13 | return; | ||
| 14 | } | ||
| 15 | |||
| 16 | // update attributes on original DOM element | ||
| 17 | for (var i = toNodeAttrs.length - 1; i >= 0; i--) { | ||
| 18 | attr = toNodeAttrs[i]; | ||
| 19 | attrName = attr.name; | ||
| 20 | attrNamespaceURI = attr.namespaceURI; | ||
| 21 | attrValue = attr.value; | ||
| 22 | |||
| 23 | if (attrNamespaceURI) { | ||
| 24 | attrName = attr.localName || attrName; | ||
| 25 | fromValue = fromNode.getAttributeNS(attrNamespaceURI, attrName); | ||
| 26 | |||
| 27 | if (fromValue !== attrValue) { | ||
| 28 | if (attr.prefix === 'xmlns'){ | ||
| 29 | attrName = attr.name; // It's not allowed to set an attribute with the XMLNS namespace without specifying the `xmlns` prefix | ||
| 30 | } | ||
| 31 | fromNode.setAttributeNS(attrNamespaceURI, attrName, attrValue); | ||
| 32 | } | ||
| 33 | } else { | ||
| 34 | fromValue = fromNode.getAttribute(attrName); | ||
| 35 | |||
| 36 | if (fromValue !== attrValue) { | ||
| 37 | fromNode.setAttribute(attrName, attrValue); | ||
| 38 | } | ||
| 39 | } | ||
| 40 | } | ||
| 41 | |||
| 42 | // Remove any extra attributes found on the original DOM element that | ||
| 43 | // weren't found on the target element. | ||
| 44 | var fromNodeAttrs = fromNode.attributes; | ||
| 45 | |||
| 46 | for (var d = fromNodeAttrs.length - 1; d >= 0; d--) { | ||
| 47 | attr = fromNodeAttrs[d]; | ||
| 48 | attrName = attr.name; | ||
| 49 | attrNamespaceURI = attr.namespaceURI; | ||
| 50 | |||
| 51 | if (attrNamespaceURI) { | ||
| 52 | attrName = attr.localName || attrName; | ||
| 53 | |||
| 54 | if (!toNode.hasAttributeNS(attrNamespaceURI, attrName)) { | ||
| 55 | fromNode.removeAttributeNS(attrNamespaceURI, attrName); | ||
| 56 | } | ||
| 57 | } else { | ||
| 58 | if (!toNode.hasAttribute(attrName)) { | ||
| 59 | fromNode.removeAttribute(attrName); | ||
| 60 | } | ||
| 61 | } | ||
| 62 | } | ||
| 63 | } | ||
| 64 | |||
| 65 | var range; // Create a range object for efficently rendering strings to elements. | ||
| 66 | var NS_XHTML = 'http://www.w3.org/1999/xhtml'; | ||
| 67 | |||
| 68 | var doc = typeof document === 'undefined' ? undefined : document; | ||
| 69 | var HAS_TEMPLATE_SUPPORT = !!doc && 'content' in doc.createElement('template'); | ||
| 70 | var HAS_RANGE_SUPPORT = !!doc && doc.createRange && 'createContextualFragment' in doc.createRange(); | ||
| 71 | |||
| 72 | function createFragmentFromTemplate(str) { | ||
| 73 | var template = doc.createElement('template'); | ||
| 74 | template.innerHTML = str; | ||
| 75 | return template.content.childNodes[0]; | ||
| 76 | } | ||
| 77 | |||
| 78 | function createFragmentFromRange(str) { | ||
| 79 | if (!range) { | ||
| 80 | range = doc.createRange(); | ||
| 81 | range.selectNode(doc.body); | ||
| 82 | } | ||
| 83 | |||
| 84 | var fragment = range.createContextualFragment(str); | ||
| 85 | return fragment.childNodes[0]; | ||
| 86 | } | ||
| 87 | |||
| 88 | function createFragmentFromWrap(str) { | ||
| 89 | var fragment = doc.createElement('body'); | ||
| 90 | fragment.innerHTML = str; | ||
| 91 | return fragment.childNodes[0]; | ||
| 92 | } | ||
| 93 | |||
| 94 | /** | ||
| 95 | * This is about the same | ||
| 96 | * var html = new DOMParser().parseFromString(str, 'text/html'); | ||
| 97 | * return html.body.firstChild; | ||
| 98 | * | ||
| 99 | * @method toElement | ||
| 100 | * @param {String} str | ||
| 101 | */ | ||
| 102 | function toElement(str) { | ||
| 103 | str = str.trim(); | ||
| 104 | if (HAS_TEMPLATE_SUPPORT) { | ||
| 105 | // avoid restrictions on content for things like `<tr><th>Hi</th></tr>` which | ||
| 106 | // createContextualFragment doesn't support | ||
| 107 | // <template> support not available in IE | ||
| 108 | return createFragmentFromTemplate(str); | ||
| 109 | } else if (HAS_RANGE_SUPPORT) { | ||
| 110 | return createFragmentFromRange(str); | ||
| 111 | } | ||
| 112 | |||
| 113 | return createFragmentFromWrap(str); | ||
| 114 | } | ||
| 115 | |||
| 116 | /** | ||
| 117 | * Returns true if two node's names are the same. | ||
| 118 | * | ||
| 119 | * NOTE: We don't bother checking `namespaceURI` because you will never find two HTML elements with the same | ||
| 120 | * nodeName and different namespace URIs. | ||
| 121 | * | ||
| 122 | * @param {Element} a | ||
| 123 | * @param {Element} b The target element | ||
| 124 | * @return {boolean} | ||
| 125 | */ | ||
| 126 | function compareNodeNames(fromEl, toEl) { | ||
| 127 | var fromNodeName = fromEl.nodeName; | ||
| 128 | var toNodeName = toEl.nodeName; | ||
| 129 | var fromCodeStart, toCodeStart; | ||
| 130 | |||
| 131 | if (fromNodeName === toNodeName) { | ||
| 132 | return true; | ||
| 133 | } | ||
| 134 | |||
| 135 | fromCodeStart = fromNodeName.charCodeAt(0); | ||
| 136 | toCodeStart = toNodeName.charCodeAt(0); | ||
| 137 | |||
| 138 | // If the target element is a virtual DOM node or SVG node then we may | ||
| 139 | // need to normalize the tag name before comparing. Normal HTML elements that are | ||
| 140 | // in the "http://www.w3.org/1999/xhtml" | ||
| 141 | // are converted to upper case | ||
| 142 | if (fromCodeStart <= 90 && toCodeStart >= 97) { // from is upper and to is lower | ||
| 143 | return fromNodeName === toNodeName.toUpperCase(); | ||
| 144 | } else if (toCodeStart <= 90 && fromCodeStart >= 97) { // to is upper and from is lower | ||
| 145 | return toNodeName === fromNodeName.toUpperCase(); | ||
| 146 | } else { | ||
| 147 | return false; | ||
| 148 | } | ||
| 149 | } | ||
| 150 | |||
| 151 | /** | ||
| 152 | * Create an element, optionally with a known namespace URI. | ||
| 153 | * | ||
| 154 | * @param {string} name the element name, e.g. 'div' or 'svg' | ||
| 155 | * @param {string} [namespaceURI] the element's namespace URI, i.e. the value of | ||
| 156 | * its `xmlns` attribute or its inferred namespace. | ||
| 157 | * | ||
| 158 | * @return {Element} | ||
| 159 | */ | ||
| 160 | function createElementNS(name, namespaceURI) { | ||
| 161 | return !namespaceURI || namespaceURI === NS_XHTML ? | ||
| 162 | doc.createElement(name) : | ||
| 163 | doc.createElementNS(namespaceURI, name); | ||
| 164 | } | ||
| 165 | |||
| 166 | /** | ||
| 167 | * Copies the children of one DOM element to another DOM element | ||
| 168 | */ | ||
| 169 | function moveChildren(fromEl, toEl) { | ||
| 170 | var curChild = fromEl.firstChild; | ||
| 171 | while (curChild) { | ||
| 172 | var nextChild = curChild.nextSibling; | ||
| 173 | toEl.appendChild(curChild); | ||
| 174 | curChild = nextChild; | ||
| 175 | } | ||
| 176 | return toEl; | ||
| 177 | } | ||
| 178 | |||
| 179 | function syncBooleanAttrProp(fromEl, toEl, name) { | ||
| 180 | if (fromEl[name] !== toEl[name]) { | ||
| 181 | fromEl[name] = toEl[name]; | ||
| 182 | if (fromEl[name]) { | ||
| 183 | fromEl.setAttribute(name, ''); | ||
| 184 | } else { | ||
| 185 | fromEl.removeAttribute(name); | ||
| 186 | } | ||
| 187 | } | ||
| 188 | } | ||
| 189 | |||
| 190 | var specialElHandlers = { | ||
| 191 | OPTION: function(fromEl, toEl) { | ||
| 192 | var parentNode = fromEl.parentNode; | ||
| 193 | if (parentNode) { | ||
| 194 | var parentName = parentNode.nodeName.toUpperCase(); | ||
| 195 | if (parentName === 'OPTGROUP') { | ||
| 196 | parentNode = parentNode.parentNode; | ||
| 197 | parentName = parentNode && parentNode.nodeName.toUpperCase(); | ||
| 198 | } | ||
| 199 | if (parentName === 'SELECT' && !parentNode.hasAttribute('multiple')) { | ||
| 200 | if (fromEl.hasAttribute('selected') && !toEl.selected) { | ||
| 201 | // Workaround for MS Edge bug where the 'selected' attribute can only be | ||
| 202 | // removed if set to a non-empty value: | ||
| 203 | // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/12087679/ | ||
| 204 | fromEl.setAttribute('selected', 'selected'); | ||
| 205 | fromEl.removeAttribute('selected'); | ||
| 206 | } | ||
| 207 | // We have to reset select element's selectedIndex to -1, otherwise setting | ||
| 208 | // fromEl.selected using the syncBooleanAttrProp below has no effect. | ||
| 209 | // The correct selectedIndex will be set in the SELECT special handler below. | ||
| 210 | parentNode.selectedIndex = -1; | ||
| 211 | } | ||
| 212 | } | ||
| 213 | syncBooleanAttrProp(fromEl, toEl, 'selected'); | ||
| 214 | }, | ||
| 215 | /** | ||
| 216 | * The "value" attribute is special for the <input> element since it sets | ||
| 217 | * the initial value. Changing the "value" attribute without changing the | ||
| 218 | * "value" property will have no effect since it is only used to the set the | ||
| 219 | * initial value. Similar for the "checked" attribute, and "disabled". | ||
| 220 | */ | ||
| 221 | INPUT: function(fromEl, toEl) { | ||
| 222 | syncBooleanAttrProp(fromEl, toEl, 'checked'); | ||
| 223 | syncBooleanAttrProp(fromEl, toEl, 'disabled'); | ||
| 224 | |||
| 225 | if (fromEl.value !== toEl.value) { | ||
| 226 | fromEl.value = toEl.value; | ||
| 227 | } | ||
| 228 | |||
| 229 | if (!toEl.hasAttribute('value')) { | ||
| 230 | fromEl.removeAttribute('value'); | ||
| 231 | } | ||
| 232 | }, | ||
| 233 | |||
| 234 | TEXTAREA: function(fromEl, toEl) { | ||
| 235 | var newValue = toEl.value; | ||
| 236 | if (fromEl.value !== newValue) { | ||
| 237 | fromEl.value = newValue; | ||
| 238 | } | ||
| 239 | |||
| 240 | var firstChild = fromEl.firstChild; | ||
| 241 | if (firstChild) { | ||
| 242 | // Needed for IE. Apparently IE sets the placeholder as the | ||
| 243 | // node value and vise versa. This ignores an empty update. | ||
| 244 | var oldValue = firstChild.nodeValue; | ||
| 245 | |||
| 246 | if (oldValue == newValue || (!newValue && oldValue == fromEl.placeholder)) { | ||
| 247 | return; | ||
| 248 | } | ||
| 249 | |||
| 250 | firstChild.nodeValue = newValue; | ||
| 251 | } | ||
| 252 | }, | ||
| 253 | SELECT: function(fromEl, toEl) { | ||
| 254 | if (!toEl.hasAttribute('multiple')) { | ||
| 255 | var selectedIndex = -1; | ||
| 256 | var i = 0; | ||
| 257 | // We have to loop through children of fromEl, not toEl since nodes can be moved | ||
| 258 | // from toEl to fromEl directly when morphing. | ||
| 259 | // At the time this special handler is invoked, all children have already been morphed | ||
| 260 | // and appended to / removed from fromEl, so using fromEl here is safe and correct. | ||
| 261 | var curChild = fromEl.firstChild; | ||
| 262 | var optgroup; | ||
| 263 | var nodeName; | ||
| 264 | while(curChild) { | ||
| 265 | nodeName = curChild.nodeName && curChild.nodeName.toUpperCase(); | ||
| 266 | if (nodeName === 'OPTGROUP') { | ||
| 267 | optgroup = curChild; | ||
| 268 | curChild = optgroup.firstChild; | ||
| 269 | } else { | ||
| 270 | if (nodeName === 'OPTION') { | ||
| 271 | if (curChild.hasAttribute('selected')) { | ||
| 272 | selectedIndex = i; | ||
| 273 | break; | ||
| 274 | } | ||
| 275 | i++; | ||
| 276 | } | ||
| 277 | curChild = curChild.nextSibling; | ||
| 278 | if (!curChild && optgroup) { | ||
| 279 | curChild = optgroup.nextSibling; | ||
| 280 | optgroup = null; | ||
| 281 | } | ||
| 282 | } | ||
| 283 | } | ||
| 284 | |||
| 285 | fromEl.selectedIndex = selectedIndex; | ||
| 286 | } | ||
| 287 | } | ||
| 288 | }; | ||
| 289 | |||
| 290 | var ELEMENT_NODE = 1; | ||
| 291 | var DOCUMENT_FRAGMENT_NODE$1 = 11; | ||
| 292 | var TEXT_NODE = 3; | ||
| 293 | var COMMENT_NODE = 8; | ||
| 294 | |||
| 295 | function noop() {} | ||
| 296 | |||
| 297 | function defaultGetNodeKey(node) { | ||
| 298 | if (node) { | ||
| 299 | return (node.getAttribute && node.getAttribute('id')) || node.id; | ||
| 300 | } | ||
| 301 | } | ||
| 302 | |||
| 303 | function morphdomFactory(morphAttrs) { | ||
| 304 | |||
| 305 | return function morphdom(fromNode, toNode, options) { | ||
| 306 | if (!options) { | ||
| 307 | options = {}; | ||
| 308 | } | ||
| 309 | |||
| 310 | if (typeof toNode === 'string') { | ||
| 311 | if (fromNode.nodeName === '#document' || fromNode.nodeName === 'HTML' || fromNode.nodeName === 'BODY') { | ||
| 312 | var toNodeHtml = toNode; | ||
| 313 | toNode = doc.createElement('html'); | ||
| 314 | toNode.innerHTML = toNodeHtml; | ||
| 315 | } else { | ||
| 316 | toNode = toElement(toNode); | ||
| 317 | } | ||
| 318 | } else if (toNode.nodeType === DOCUMENT_FRAGMENT_NODE$1) { | ||
| 319 | toNode = toNode.firstElementChild; | ||
| 320 | } | ||
| 321 | |||
| 322 | var getNodeKey = options.getNodeKey || defaultGetNodeKey; | ||
| 323 | var onBeforeNodeAdded = options.onBeforeNodeAdded || noop; | ||
| 324 | var onNodeAdded = options.onNodeAdded || noop; | ||
| 325 | var onBeforeElUpdated = options.onBeforeElUpdated || noop; | ||
| 326 | var onElUpdated = options.onElUpdated || noop; | ||
| 327 | var onBeforeNodeDiscarded = options.onBeforeNodeDiscarded || noop; | ||
| 328 | var onNodeDiscarded = options.onNodeDiscarded || noop; | ||
| 329 | var onBeforeElChildrenUpdated = options.onBeforeElChildrenUpdated || noop; | ||
| 330 | var skipFromChildren = options.skipFromChildren || noop; | ||
| 331 | var addChild = options.addChild || function(parent, child){ return parent.appendChild(child); }; | ||
| 332 | var childrenOnly = options.childrenOnly === true; | ||
| 333 | |||
| 334 | // This object is used as a lookup to quickly find all keyed elements in the original DOM tree. | ||
| 335 | var fromNodesLookup = Object.create(null); | ||
| 336 | var keyedRemovalList = []; | ||
| 337 | |||
| 338 | function addKeyedRemoval(key) { | ||
| 339 | keyedRemovalList.push(key); | ||
| 340 | } | ||
| 341 | |||
| 342 | function walkDiscardedChildNodes(node, skipKeyedNodes) { | ||
| 343 | if (node.nodeType === ELEMENT_NODE) { | ||
| 344 | var curChild = node.firstChild; | ||
| 345 | while (curChild) { | ||
| 346 | |||
| 347 | var key = undefined; | ||
| 348 | |||
| 349 | if (skipKeyedNodes && (key = getNodeKey(curChild))) { | ||
| 350 | // If we are skipping keyed nodes then we add the key | ||
| 351 | // to a list so that it can be handled at the very end. | ||
| 352 | addKeyedRemoval(key); | ||
| 353 | } else { | ||
| 354 | // Only report the node as discarded if it is not keyed. We do this because | ||
| 355 | // at the end we loop through all keyed elements that were unmatched | ||
| 356 | // and then discard them in one final pass. | ||
| 357 | onNodeDiscarded(curChild); | ||
| 358 | if (curChild.firstChild) { | ||
| 359 | walkDiscardedChildNodes(curChild, skipKeyedNodes); | ||
| 360 | } | ||
| 361 | } | ||
| 362 | |||
| 363 | curChild = curChild.nextSibling; | ||
| 364 | } | ||
| 365 | } | ||
| 366 | } | ||
| 367 | |||
| 368 | /** | ||
| 369 | * Removes a DOM node out of the original DOM | ||
| 370 | * | ||
| 371 | * @param {Node} node The node to remove | ||
| 372 | * @param {Node} parentNode The nodes parent | ||
| 373 | * @param {Boolean} skipKeyedNodes If true then elements with keys will be skipped and not discarded. | ||
| 374 | * @return {undefined} | ||
| 375 | */ | ||
| 376 | function removeNode(node, parentNode, skipKeyedNodes) { | ||
| 377 | if (onBeforeNodeDiscarded(node) === false) { | ||
| 378 | return; | ||
| 379 | } | ||
| 380 | |||
| 381 | if (parentNode) { | ||
| 382 | parentNode.removeChild(node); | ||
| 383 | } | ||
| 384 | |||
| 385 | onNodeDiscarded(node); | ||
| 386 | walkDiscardedChildNodes(node, skipKeyedNodes); | ||
| 387 | } | ||
| 388 | |||
| 389 | // // TreeWalker implementation is no faster, but keeping this around in case this changes in the future | ||
| 390 | // function indexTree(root) { | ||
| 391 | // var treeWalker = document.createTreeWalker( | ||
| 392 | // root, | ||
| 393 | // NodeFilter.SHOW_ELEMENT); | ||
| 394 | // | ||
| 395 | // var el; | ||
| 396 | // while((el = treeWalker.nextNode())) { | ||
| 397 | // var key = getNodeKey(el); | ||
| 398 | // if (key) { | ||
| 399 | // fromNodesLookup[key] = el; | ||
| 400 | // } | ||
| 401 | // } | ||
| 402 | // } | ||
| 403 | |||
| 404 | // // NodeIterator implementation is no faster, but keeping this around in case this changes in the future | ||
| 405 | // | ||
| 406 | // function indexTree(node) { | ||
| 407 | // var nodeIterator = document.createNodeIterator(node, NodeFilter.SHOW_ELEMENT); | ||
| 408 | // var el; | ||
| 409 | // while((el = nodeIterator.nextNode())) { | ||
| 410 | // var key = getNodeKey(el); | ||
| 411 | // if (key) { | ||
| 412 | // fromNodesLookup[key] = el; | ||
| 413 | // } | ||
| 414 | // } | ||
| 415 | // } | ||
| 416 | |||
| 417 | function indexTree(node) { | ||
| 418 | if (node.nodeType === ELEMENT_NODE || node.nodeType === DOCUMENT_FRAGMENT_NODE$1) { | ||
| 419 | var curChild = node.firstChild; | ||
| 420 | while (curChild) { | ||
| 421 | var key = getNodeKey(curChild); | ||
| 422 | if (key) { | ||
| 423 | fromNodesLookup[key] = curChild; | ||
| 424 | } | ||
| 425 | |||
| 426 | // Walk recursively | ||
| 427 | indexTree(curChild); | ||
| 428 | |||
| 429 | curChild = curChild.nextSibling; | ||
| 430 | } | ||
| 431 | } | ||
| 432 | } | ||
| 433 | |||
| 434 | indexTree(fromNode); | ||
| 435 | |||
| 436 | function handleNodeAdded(el) { | ||
| 437 | onNodeAdded(el); | ||
| 438 | |||
| 439 | var curChild = el.firstChild; | ||
| 440 | while (curChild) { | ||
| 441 | var nextSibling = curChild.nextSibling; | ||
| 442 | |||
| 443 | var key = getNodeKey(curChild); | ||
| 444 | if (key) { | ||
| 445 | var unmatchedFromEl = fromNodesLookup[key]; | ||
| 446 | // if we find a duplicate #id node in cache, replace `el` with cache value | ||
| 447 | // and morph it to the child node. | ||
| 448 | if (unmatchedFromEl && compareNodeNames(curChild, unmatchedFromEl)) { | ||
| 449 | curChild.parentNode.replaceChild(unmatchedFromEl, curChild); | ||
| 450 | morphEl(unmatchedFromEl, curChild); | ||
| 451 | } else { | ||
| 452 | handleNodeAdded(curChild); | ||
| 453 | } | ||
| 454 | } else { | ||
| 455 | // recursively call for curChild and it's children to see if we find something in | ||
| 456 | // fromNodesLookup | ||
| 457 | handleNodeAdded(curChild); | ||
| 458 | } | ||
| 459 | |||
| 460 | curChild = nextSibling; | ||
| 461 | } | ||
| 462 | } | ||
| 463 | |||
| 464 | function cleanupFromEl(fromEl, curFromNodeChild, curFromNodeKey) { | ||
| 465 | // We have processed all of the "to nodes". If curFromNodeChild is | ||
| 466 | // non-null then we still have some from nodes left over that need | ||
| 467 | // to be removed | ||
| 468 | while (curFromNodeChild) { | ||
| 469 | var fromNextSibling = curFromNodeChild.nextSibling; | ||
| 470 | if ((curFromNodeKey = getNodeKey(curFromNodeChild))) { | ||
| 471 | // Since the node is keyed it might be matched up later so we defer | ||
| 472 | // the actual removal to later | ||
| 473 | addKeyedRemoval(curFromNodeKey); | ||
| 474 | } else { | ||
| 475 | // NOTE: we skip nested keyed nodes from being removed since there is | ||
| 476 | // still a chance they will be matched up later | ||
| 477 | removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */); | ||
| 478 | } | ||
| 479 | curFromNodeChild = fromNextSibling; | ||
| 480 | } | ||
| 481 | } | ||
| 482 | |||
| 483 | function morphEl(fromEl, toEl, childrenOnly) { | ||
| 484 | var toElKey = getNodeKey(toEl); | ||
| 485 | |||
| 486 | if (toElKey) { | ||
| 487 | // If an element with an ID is being morphed then it will be in the final | ||
| 488 | // DOM so clear it out of the saved elements collection | ||
| 489 | delete fromNodesLookup[toElKey]; | ||
| 490 | } | ||
| 491 | |||
| 492 | if (!childrenOnly) { | ||
| 493 | // optional | ||
| 494 | var beforeUpdateResult = onBeforeElUpdated(fromEl, toEl); | ||
| 495 | if (beforeUpdateResult === false) { | ||
| 496 | return; | ||
| 497 | } else if (beforeUpdateResult instanceof HTMLElement) { | ||
| 498 | fromEl = beforeUpdateResult; | ||
| 499 | // reindex the new fromEl in case it's not in the same | ||
| 500 | // tree as the original fromEl | ||
| 501 | // (Phoenix LiveView sometimes returns a cloned tree, | ||
| 502 | // but keyed lookups would still point to the original tree) | ||
| 503 | indexTree(fromEl); | ||
| 504 | } | ||
| 505 | |||
| 506 | // update attributes on original DOM element first | ||
| 507 | morphAttrs(fromEl, toEl); | ||
| 508 | // optional | ||
| 509 | onElUpdated(fromEl); | ||
| 510 | |||
| 511 | if (onBeforeElChildrenUpdated(fromEl, toEl) === false) { | ||
| 512 | return; | ||
| 513 | } | ||
| 514 | } | ||
| 515 | |||
| 516 | if (fromEl.nodeName !== 'TEXTAREA') { | ||
| 517 | morphChildren(fromEl, toEl); | ||
| 518 | } else { | ||
| 519 | specialElHandlers.TEXTAREA(fromEl, toEl); | ||
| 520 | } | ||
| 521 | } | ||
| 522 | |||
| 523 | function morphChildren(fromEl, toEl) { | ||
| 524 | var skipFrom = skipFromChildren(fromEl, toEl); | ||
| 525 | var curToNodeChild = toEl.firstChild; | ||
| 526 | var curFromNodeChild = fromEl.firstChild; | ||
| 527 | var curToNodeKey; | ||
| 528 | var curFromNodeKey; | ||
| 529 | |||
| 530 | var fromNextSibling; | ||
| 531 | var toNextSibling; | ||
| 532 | var matchingFromEl; | ||
| 533 | |||
| 534 | // walk the children | ||
| 535 | outer: while (curToNodeChild) { | ||
| 536 | toNextSibling = curToNodeChild.nextSibling; | ||
| 537 | curToNodeKey = getNodeKey(curToNodeChild); | ||
| 538 | |||
| 539 | // walk the fromNode children all the way through | ||
| 540 | while (!skipFrom && curFromNodeChild) { | ||
| 541 | fromNextSibling = curFromNodeChild.nextSibling; | ||
| 542 | |||
| 543 | if (curToNodeChild.isSameNode && curToNodeChild.isSameNode(curFromNodeChild)) { | ||
| 544 | curToNodeChild = toNextSibling; | ||
| 545 | curFromNodeChild = fromNextSibling; | ||
| 546 | continue outer; | ||
| 547 | } | ||
| 548 | |||
| 549 | curFromNodeKey = getNodeKey(curFromNodeChild); | ||
| 550 | |||
| 551 | var curFromNodeType = curFromNodeChild.nodeType; | ||
| 552 | |||
| 553 | // this means if the curFromNodeChild doesnt have a match with the curToNodeChild | ||
| 554 | var isCompatible = undefined; | ||
| 555 | |||
| 556 | if (curFromNodeType === curToNodeChild.nodeType) { | ||
| 557 | if (curFromNodeType === ELEMENT_NODE) { | ||
| 558 | // Both nodes being compared are Element nodes | ||
| 559 | |||
| 560 | if (curToNodeKey) { | ||
| 561 | // The target node has a key so we want to match it up with the correct element | ||
| 562 | // in the original DOM tree | ||
| 563 | if (curToNodeKey !== curFromNodeKey) { | ||
| 564 | // The current element in the original DOM tree does not have a matching key so | ||
| 565 | // let's check our lookup to see if there is a matching element in the original | ||
| 566 | // DOM tree | ||
| 567 | if ((matchingFromEl = fromNodesLookup[curToNodeKey])) { | ||
| 568 | if (fromNextSibling === matchingFromEl) { | ||
| 569 | // Special case for single element removals. To avoid removing the original | ||
| 570 | // DOM node out of the tree (since that can break CSS transitions, etc.), | ||
| 571 | // we will instead discard the current node and wait until the next | ||
| 572 | // iteration to properly match up the keyed target element with its matching | ||
| 573 | // element in the original tree | ||
| 574 | isCompatible = false; | ||
| 575 | } else { | ||
| 576 | // We found a matching keyed element somewhere in the original DOM tree. | ||
| 577 | // Let's move the original DOM node into the current position and morph | ||
| 578 | // it. | ||
| 579 | |||
| 580 | // NOTE: We use insertBefore instead of replaceChild because we want to go through | ||
| 581 | // the `removeNode()` function for the node that is being discarded so that | ||
| 582 | // all lifecycle hooks are correctly invoked | ||
| 583 | fromEl.insertBefore(matchingFromEl, curFromNodeChild); | ||
| 584 | |||
| 585 | // fromNextSibling = curFromNodeChild.nextSibling; | ||
| 586 | |||
| 587 | if (curFromNodeKey) { | ||
| 588 | // Since the node is keyed it might be matched up later so we defer | ||
| 589 | // the actual removal to later | ||
| 590 | addKeyedRemoval(curFromNodeKey); | ||
| 591 | } else { | ||
| 592 | // NOTE: we skip nested keyed nodes from being removed since there is | ||
| 593 | // still a chance they will be matched up later | ||
| 594 | removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */); | ||
| 595 | } | ||
| 596 | |||
| 597 | curFromNodeChild = matchingFromEl; | ||
| 598 | curFromNodeKey = getNodeKey(curFromNodeChild); | ||
| 599 | } | ||
| 600 | } else { | ||
| 601 | // The nodes are not compatible since the "to" node has a key and there | ||
| 602 | // is no matching keyed node in the source tree | ||
| 603 | isCompatible = false; | ||
| 604 | } | ||
| 605 | } | ||
| 606 | } else if (curFromNodeKey) { | ||
| 607 | // The original has a key | ||
| 608 | isCompatible = false; | ||
| 609 | } | ||
| 610 | |||
| 611 | isCompatible = isCompatible !== false && compareNodeNames(curFromNodeChild, curToNodeChild); | ||
| 612 | if (isCompatible) { | ||
| 613 | // We found compatible DOM elements so transform | ||
| 614 | // the current "from" node to match the current | ||
| 615 | // target DOM node. | ||
| 616 | // MORPH | ||
| 617 | morphEl(curFromNodeChild, curToNodeChild); | ||
| 618 | } | ||
| 619 | |||
| 620 | } else if (curFromNodeType === TEXT_NODE || curFromNodeType == COMMENT_NODE) { | ||
| 621 | // Both nodes being compared are Text or Comment nodes | ||
| 622 | isCompatible = true; | ||
| 623 | // Simply update nodeValue on the original node to | ||
| 624 | // change the text value | ||
| 625 | if (curFromNodeChild.nodeValue !== curToNodeChild.nodeValue) { | ||
| 626 | curFromNodeChild.nodeValue = curToNodeChild.nodeValue; | ||
| 627 | } | ||
| 628 | |||
| 629 | } | ||
| 630 | } | ||
| 631 | |||
| 632 | if (isCompatible) { | ||
| 633 | // Advance both the "to" child and the "from" child since we found a match | ||
| 634 | // Nothing else to do as we already recursively called morphChildren above | ||
| 635 | curToNodeChild = toNextSibling; | ||
| 636 | curFromNodeChild = fromNextSibling; | ||
| 637 | continue outer; | ||
| 638 | } | ||
| 639 | |||
| 640 | // No compatible match so remove the old node from the DOM and continue trying to find a | ||
| 641 | // match in the original DOM. However, we only do this if the from node is not keyed | ||
| 642 | // since it is possible that a keyed node might match up with a node somewhere else in the | ||
| 643 | // target tree and we don't want to discard it just yet since it still might find a | ||
| 644 | // home in the final DOM tree. After everything is done we will remove any keyed nodes | ||
| 645 | // that didn't find a home | ||
| 646 | if (curFromNodeKey) { | ||
| 647 | // Since the node is keyed it might be matched up later so we defer | ||
| 648 | // the actual removal to later | ||
| 649 | addKeyedRemoval(curFromNodeKey); | ||
| 650 | } else { | ||
| 651 | // NOTE: we skip nested keyed nodes from being removed since there is | ||
| 652 | // still a chance they will be matched up later | ||
| 653 | removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */); | ||
| 654 | } | ||
| 655 | |||
| 656 | curFromNodeChild = fromNextSibling; | ||
| 657 | } // END: while(curFromNodeChild) {} | ||
| 658 | |||
| 659 | // If we got this far then we did not find a candidate match for | ||
| 660 | // our "to node" and we exhausted all of the children "from" | ||
| 661 | // nodes. Therefore, we will just append the current "to" node | ||
| 662 | // to the end | ||
| 663 | if (curToNodeKey && (matchingFromEl = fromNodesLookup[curToNodeKey]) && compareNodeNames(matchingFromEl, curToNodeChild)) { | ||
| 664 | // MORPH | ||
| 665 | if(!skipFrom){ addChild(fromEl, matchingFromEl); } | ||
| 666 | morphEl(matchingFromEl, curToNodeChild); | ||
| 667 | } else { | ||
| 668 | var onBeforeNodeAddedResult = onBeforeNodeAdded(curToNodeChild); | ||
| 669 | if (onBeforeNodeAddedResult !== false) { | ||
| 670 | if (onBeforeNodeAddedResult) { | ||
| 671 | curToNodeChild = onBeforeNodeAddedResult; | ||
| 672 | } | ||
| 673 | |||
| 674 | if (curToNodeChild.actualize) { | ||
| 675 | curToNodeChild = curToNodeChild.actualize(fromEl.ownerDocument || doc); | ||
| 676 | } | ||
| 677 | addChild(fromEl, curToNodeChild); | ||
| 678 | handleNodeAdded(curToNodeChild); | ||
| 679 | } | ||
| 680 | } | ||
| 681 | |||
| 682 | curToNodeChild = toNextSibling; | ||
| 683 | curFromNodeChild = fromNextSibling; | ||
| 684 | } | ||
| 685 | |||
| 686 | cleanupFromEl(fromEl, curFromNodeChild, curFromNodeKey); | ||
| 687 | |||
| 688 | var specialElHandler = specialElHandlers[fromEl.nodeName]; | ||
| 689 | if (specialElHandler) { | ||
| 690 | specialElHandler(fromEl, toEl); | ||
| 691 | } | ||
| 692 | } // END: morphChildren(...) | ||
| 693 | |||
| 694 | var morphedNode = fromNode; | ||
| 695 | var morphedNodeType = morphedNode.nodeType; | ||
| 696 | var toNodeType = toNode.nodeType; | ||
| 697 | |||
| 698 | if (!childrenOnly) { | ||
| 699 | // Handle the case where we are given two DOM nodes that are not | ||
| 700 | // compatible (e.g. <div> --> <span> or <div> --> TEXT) | ||
| 701 | if (morphedNodeType === ELEMENT_NODE) { | ||
| 702 | if (toNodeType === ELEMENT_NODE) { | ||
| 703 | if (!compareNodeNames(fromNode, toNode)) { | ||
| 704 | onNodeDiscarded(fromNode); | ||
| 705 | morphedNode = moveChildren(fromNode, createElementNS(toNode.nodeName, toNode.namespaceURI)); | ||
| 706 | } | ||
| 707 | } else { | ||
| 708 | // Going from an element node to a text node | ||
| 709 | morphedNode = toNode; | ||
| 710 | } | ||
| 711 | } else if (morphedNodeType === TEXT_NODE || morphedNodeType === COMMENT_NODE) { // Text or comment node | ||
| 712 | if (toNodeType === morphedNodeType) { | ||
| 713 | if (morphedNode.nodeValue !== toNode.nodeValue) { | ||
| 714 | morphedNode.nodeValue = toNode.nodeValue; | ||
| 715 | } | ||
| 716 | |||
| 717 | return morphedNode; | ||
| 718 | } else { | ||
| 719 | // Text node to something else | ||
| 720 | morphedNode = toNode; | ||
| 721 | } | ||
| 722 | } | ||
| 723 | } | ||
| 724 | |||
| 725 | if (morphedNode === toNode) { | ||
| 726 | // The "to node" was not compatible with the "from node" so we had to | ||
| 727 | // toss out the "from node" and use the "to node" | ||
| 728 | onNodeDiscarded(fromNode); | ||
| 729 | } else { | ||
| 730 | if (toNode.isSameNode && toNode.isSameNode(morphedNode)) { | ||
| 731 | return; | ||
| 732 | } | ||
| 733 | |||
| 734 | morphEl(morphedNode, toNode, childrenOnly); | ||
| 735 | |||
| 736 | // We now need to loop over any keyed nodes that might need to be | ||
| 737 | // removed. We only do the removal if we know that the keyed node | ||
| 738 | // never found a match. When a keyed node is matched up we remove | ||
| 739 | // it out of fromNodesLookup and we use fromNodesLookup to determine | ||
| 740 | // if a keyed node has been matched up or not | ||
| 741 | if (keyedRemovalList) { | ||
| 742 | for (var i=0, len=keyedRemovalList.length; i<len; i++) { | ||
| 743 | var elToRemove = fromNodesLookup[keyedRemovalList[i]]; | ||
| 744 | if (elToRemove) { | ||
| 745 | removeNode(elToRemove, elToRemove.parentNode, false); | ||
| 746 | } | ||
| 747 | } | ||
| 748 | } | ||
| 749 | } | ||
| 750 | |||
| 751 | if (!childrenOnly && morphedNode !== fromNode && fromNode.parentNode) { | ||
| 752 | if (morphedNode.actualize) { | ||
| 753 | morphedNode = morphedNode.actualize(fromNode.ownerDocument || doc); | ||
| 754 | } | ||
| 755 | // If we had to swap out the from node with a new node because the old | ||
| 756 | // node was not compatible with the target node then we need to | ||
| 757 | // replace the old DOM node in the original DOM tree. This is only | ||
| 758 | // possible if the original DOM node was part of a DOM tree which | ||
| 759 | // we know is the case if it has a parent node. | ||
| 760 | fromNode.parentNode.replaceChild(morphedNode, fromNode); | ||
| 761 | } | ||
| 762 | |||
| 763 | return morphedNode; | ||
| 764 | }; | ||
| 765 | } | ||
| 766 | |||
| 767 | var morphdom = morphdomFactory(morphAttrs); | ||
| 768 | |||
| 769 | export default morphdom; | ||
| @@ -0,0 +1,21 @@ | |||
| 1 | The MIT License (MIT) | ||
| 2 | |||
| 3 | Copyright (c) Patrick Steele-Idem <pnidem@gmail.com> (psteeleidem.com) | ||
| 4 | |||
| 5 | Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| 6 | of this software and associated documentation files (the "Software"), to deal | ||
| 7 | in the Software without restriction, including without limitation the rights | ||
| 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| 9 | copies of the Software, and to permit persons to whom the Software is | ||
| 10 | furnished to do so, subject to the following conditions: | ||
| 11 | |||
| 12 | The above copyright notice and this permission notice shall be included in | ||
| 13 | all copies or substantial portions of the Software. | ||
| 14 | |||
| 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
| 21 | THE SOFTWARE. | ||
| \ No newline at end of file | 21 | \ No newline at end of file | |
| @@ -83,6 +83,15 @@ export class AutoExecuteHandler { | |||
| 83 | await this.performAutoExecute(qrList); | 83 | await this.performAutoExecute(qrList); |
| 84 | } | 84 | } |
| 85 | 85 | ||
| 86 | async handleNewChat() { | ||
| 87 | if (!this.checkExecute()) return; | ||
| 88 | const qrList = [ | ||
| 89 | ...this.settings.config.setList.map(link=>link.set.qrList.filter(qr=>qr.executeOnNewChat)).flat(), | ||
| 90 | ...(this.settings.chatConfig?.setList?.map(link=>link.set.qrList.filter(qr=>qr.executeOnNewChat))?.flat() ?? []), | ||
| 91 | ]; | ||
| 92 | await this.performAutoExecute(qrList); | ||
| 93 | } | ||
| 94 | |||
| 86 | /** | 95 | /** |
| 87 | * @param {any[]} entries Set of activated entries | 96 | * @param {any[]} entries Set of activated entries |
| 88 | */ | 97 | */ |
| @@ -1,10 +1,17 @@ | |||
| 1 | import { POPUP_TYPE, Popup } from '../../../popup.js'; | 1 | import { POPUP_RESULT, POPUP_TYPE, Popup } from '../../../popup.js'; |
| 2 | import { setSlashCommandAutoComplete } from '../../../slash-commands.js'; | 2 | import { setSlashCommandAutoComplete } from '../../../slash-commands.js'; |
| 3 | import { SlashCommandAbortController } from '../../../slash-commands/SlashCommandAbortController.js'; | 3 | import { SlashCommandAbortController } from '../../../slash-commands/SlashCommandAbortController.js'; |
| 4 | import { SlashCommandBreakPoint } from '../../../slash-commands/SlashCommandBreakPoint.js'; | ||
| 5 | import { SlashCommandClosure } from '../../../slash-commands/SlashCommandClosure.js'; | ||
| 6 | import { SlashCommandClosureResult } from '../../../slash-commands/SlashCommandClosureResult.js'; | ||
| 7 | import { SlashCommandDebugController } from '../../../slash-commands/SlashCommandDebugController.js'; | ||
| 8 | import { SlashCommandExecutor } from '../../../slash-commands/SlashCommandExecutor.js'; | ||
| 9 | import { SlashCommandParser } from '../../../slash-commands/SlashCommandParser.js'; | ||
| 4 | import { SlashCommandParserError } from '../../../slash-commands/SlashCommandParserError.js'; | 10 | import { SlashCommandParserError } from '../../../slash-commands/SlashCommandParserError.js'; |
| 5 | import { SlashCommandScope } from '../../../slash-commands/SlashCommandScope.js'; | 11 | import { SlashCommandScope } from '../../../slash-commands/SlashCommandScope.js'; |
| 6 | import { debounce, getSortableDelay } from '../../../utils.js'; | 12 | import { debounce, delay, getSortableDelay, showFontAwesomePicker } from '../../../utils.js'; |
| 7 | import { log, warn } from '../index.js'; | 13 | import { log, quickReplyApi, warn } from '../index.js'; |
| 14 | import morphdom from '../lib/morphdom-esm.js'; | ||
| 8 | import { QuickReplyContextLink } from './QuickReplyContextLink.js'; | 15 | import { QuickReplyContextLink } from './QuickReplyContextLink.js'; |
| 9 | import { QuickReplySet } from './QuickReplySet.js'; | 16 | import { QuickReplySet } from './QuickReplySet.js'; |
| 10 | import { ContextMenu } from './ui/ctx/ContextMenu.js'; | 17 | import { ContextMenu } from './ui/ctx/ContextMenu.js'; |
| @@ -21,48 +28,62 @@ export class QuickReply { | |||
| 21 | 28 | ||
| 22 | 29 | ||
| 23 | 30 | ||
| 24 | /**@type {Number}*/ id; | 31 | /**@type {number}*/ id; |
| 25 | /**@type {String}*/ label = ''; | 32 | /**@type {string}*/ icon; |
| 26 | /**@type {String}*/ title = ''; | 33 | /**@type {string}*/ label = ''; |
| 27 | /**@type {String}*/ message = ''; | 34 | /**@type {boolean}*/ showLabel = false; |
| 35 | /**@type {string}*/ title = ''; | ||
| 36 | /**@type {string}*/ message = ''; | ||
| 28 | 37 | ||
| 29 | /**@type {QuickReplyContextLink[]}*/ contextList; | 38 | /**@type {QuickReplyContextLink[]}*/ contextList; |
| 30 | 39 | ||
| 31 | /**@type {Boolean}*/ preventAutoExecute = true; | 40 | /**@type {boolean}*/ preventAutoExecute = true; |
| 32 | /**@type {Boolean}*/ isHidden = false; | 41 | /**@type {boolean}*/ isHidden = false; |
| 33 | /**@type {Boolean}*/ executeOnStartup = false; | 42 | /**@type {boolean}*/ executeOnStartup = false; |
| 34 | /**@type {Boolean}*/ executeOnUser = false; | 43 | /**@type {boolean}*/ executeOnUser = false; |
| 35 | /**@type {Boolean}*/ executeOnAi = false; | 44 | /**@type {boolean}*/ executeOnAi = false; |
| 36 | /**@type {Boolean}*/ executeOnChatChange = false; | 45 | /**@type {boolean}*/ executeOnChatChange = false; |
| 37 | /**@type {Boolean}*/ executeOnGroupMemberDraft = false; | 46 | /**@type {boolean}*/ executeOnGroupMemberDraft = false; |
| 38 | /**@type {String}*/ automationId = ''; | 47 | /**@type {boolean}*/ executeOnNewChat = false; |
| 48 | /**@type {string}*/ automationId = ''; | ||
| 39 | 49 | ||
| 40 | /**@type {Function}*/ onExecute; | 50 | /**@type {function}*/ onExecute; |
| 41 | /**@type {Function}*/ onDelete; | 51 | /**@type {(qr:QuickReply)=>AsyncGenerator<SlashCommandClosureResult|{closure:SlashCommandClosure, executor:SlashCommandExecutor|SlashCommandClosureResult}, SlashCommandClosureResult, boolean>}*/ onDebug; |
| 42 | /**@type {Function}*/ onUpdate; | 52 | /**@type {function}*/ onDelete; |
| 53 | /**@type {function}*/ onUpdate; | ||
| 54 | /**@type {function}*/ onInsertBefore; | ||
| 55 | /**@type {function}*/ onTransfer; | ||
| 43 | 56 | ||
| 44 | 57 | ||
| 45 | /**@type {HTMLElement}*/ dom; | 58 | /**@type {HTMLElement}*/ dom; |
| 59 | /**@type {HTMLElement}*/ domIcon; | ||
| 46 | /**@type {HTMLElement}*/ domLabel; | 60 | /**@type {HTMLElement}*/ domLabel; |
| 47 | /**@type {HTMLElement}*/ settingsDom; | 61 | /**@type {HTMLElement}*/ settingsDom; |
| 62 | /**@type {HTMLElement}*/ settingsDomIcon; | ||
| 48 | /**@type {HTMLInputElement}*/ settingsDomLabel; | 63 | /**@type {HTMLInputElement}*/ settingsDomLabel; |
| 49 | /**@type {HTMLTextAreaElement}*/ settingsDomMessage; | 64 | /**@type {HTMLTextAreaElement}*/ settingsDomMessage; |
| 50 | 65 | ||
| 51 | /**@type {Popup}*/ editorPopup; | 66 | /**@type {Popup}*/ editorPopup; |
| 67 | /**@type {HTMLElement}*/ editorDom; | ||
| 52 | 68 | ||
| 69 | /**@type {HTMLTextAreaElement}*/ editorMessage; | ||
| 70 | /**@type {HTMLTextAreaElement}*/ editorMessageLabel; | ||
| 71 | /**@type {HTMLElement}*/ editorSyntax; | ||
| 53 | /**@type {HTMLElement}*/ editorExecuteBtn; | 72 | /**@type {HTMLElement}*/ editorExecuteBtn; |
| 54 | /**@type {HTMLElement}*/ editorExecuteBtnPause; | 73 | /**@type {HTMLElement}*/ editorExecuteBtnPause; |
| 55 | /**@type {HTMLElement}*/ editorExecuteBtnStop; | 74 | /**@type {HTMLElement}*/ editorExecuteBtnStop; |
| 56 | /**@type {HTMLElement}*/ editorExecuteProgress; | 75 | /**@type {HTMLElement}*/ editorExecuteProgress; |
| 57 | /**@type {HTMLElement}*/ editorExecuteErrors; | 76 | /**@type {HTMLElement}*/ editorExecuteErrors; |
| 58 | /**@type {HTMLElement}*/ editorExecuteResult; | 77 | /**@type {HTMLElement}*/ editorExecuteResult; |
| 59 | /**@type {HTMLInputElement}*/ editorExecuteHide; | 78 | /**@type {HTMLElement}*/ editorDebugState; |
| 60 | /**@type {Promise}*/ editorExecutePromise; | 79 | /**@type {Promise}*/ editorExecutePromise; |
| 80 | /**@type {boolean}*/ isExecuting; | ||
| 61 | /**@type {SlashCommandAbortController}*/ abortController; | 81 | /**@type {SlashCommandAbortController}*/ abortController; |
| 82 | /**@type {SlashCommandDebugController}*/ debugController; | ||
| 62 | 83 | ||
| 63 | 84 | ||
| 64 | get hasContext() { | 85 | get hasContext() { |
| 65 | return this.contextList && this.contextList.length > 0; | 86 | return this.contextList && this.contextList.filter(it => it.set).length > 0; |
| 66 | } | 87 | } |
| 67 | 88 | ||
| 68 | 89 | ||
| @@ -75,6 +96,14 @@ export class QuickReply { | |||
| 75 | updateRender() { | 96 | updateRender() { |
| 76 | if (!this.dom) return; | 97 | if (!this.dom) return; |
| 77 | this.dom.title = this.title || this.message; | 98 | this.dom.title = this.title || this.message; |
| 99 | if (this.icon) { | ||
| 100 | this.domIcon.classList.remove('qr--hidden'); | ||
| 101 | if (this.showLabel) this.domLabel.classList.remove('qr--hidden'); | ||
| 102 | else this.domLabel.classList.add('qr--hidden'); | ||
| 103 | } else { | ||
| 104 | this.domIcon.classList.add('qr--hidden'); | ||
| 105 | this.domLabel.classList.remove('qr--hidden'); | ||
| 106 | } | ||
| 78 | this.domLabel.textContent = this.label; | 107 | this.domLabel.textContent = this.label; |
| 79 | this.dom.classList[this.hasContext ? 'add' : 'remove']('qr--hasCtx'); | 108 | this.dom.classList[this.hasContext ? 'add' : 'remove']('qr--hasCtx'); |
| 80 | } | 109 | } |
| @@ -105,9 +134,18 @@ export class QuickReply { | |||
| 105 | } | 134 | } |
| 106 | this.execute(); | 135 | this.execute(); |
| 107 | }); | 136 | }); |
| 137 | const icon = document.createElement('div'); { | ||
| 138 | this.domIcon = icon; | ||
| 139 | icon.classList.add('qr--button-icon'); | ||
| 140 | icon.classList.add('fa-solid'); | ||
| 141 | if (!this.icon) icon.classList.add('qr--hidden'); | ||
| 142 | else icon.classList.add(this.icon); | ||
| 143 | root.append(icon); | ||
| 144 | } | ||
| 108 | const lbl = document.createElement('div'); { | 145 | const lbl = document.createElement('div'); { |
| 109 | this.domLabel = lbl; | 146 | this.domLabel = lbl; |
| 110 | lbl.classList.add('qr--button-label'); | 147 | lbl.classList.add('qr--button-label'); |
| 148 | if (this.icon && !this.showLabel) lbl.classList.add('qr--hidden'); | ||
| 111 | lbl.textContent = this.label; | 149 | lbl.textContent = this.label; |
| 112 | root.append(lbl); | 150 | root.append(lbl); |
| 113 | } | 151 | } |
| @@ -138,36 +176,115 @@ export class QuickReply { | |||
| 138 | item.classList.add('qr--set-item'); | 176 | item.classList.add('qr--set-item'); |
| 139 | item.setAttribute('data-order', String(idx)); | 177 | item.setAttribute('data-order', String(idx)); |
| 140 | item.setAttribute('data-id', String(this.id)); | 178 | item.setAttribute('data-id', String(this.id)); |
| 141 | const drag = document.createElement('div'); { | 179 | const adder = document.createElement('div'); { |
| 142 | drag.classList.add('drag-handle'); | 180 | adder.classList.add('qr--set-itemAdder'); |
| 143 | drag.classList.add('ui-sortable-handle'); | 181 | const actions = document.createElement('div'); { |
| 144 | drag.textContent = '☰'; | 182 | actions.classList.add('qr--actions'); |
| 145 | item.append(drag); | 183 | const addNew = document.createElement('div'); { |
| 146 | } | 184 | addNew.classList.add('qr--action'); |
| 147 | const lblContainer = document.createElement('div'); { | 185 | addNew.classList.add('qr--add'); |
| 148 | lblContainer.classList.add('qr--set-itemLabelContainer'); | 186 | addNew.classList.add('menu_button'); |
| 149 | const lbl = document.createElement('input'); { | 187 | addNew.classList.add('menu_button_icon'); |
| 150 | this.settingsDomLabel = lbl; | 188 | addNew.classList.add('fa-solid'); |
| 151 | lbl.classList.add('qr--set-itemLabel'); | 189 | addNew.classList.add('fa-plus'); |
| 152 | lbl.classList.add('text_pole'); | 190 | addNew.title = 'Add quick reply'; |
| 153 | lbl.value = this.label; | 191 | addNew.addEventListener('click', ()=>this.onInsertBefore()); |
| 154 | lbl.addEventListener('input', ()=>this.updateLabel(lbl.value)); | 192 | actions.append(addNew); |
| 155 | lblContainer.append(lbl); | 193 | } |
| 194 | const paste = document.createElement('div'); { | ||
| 195 | paste.classList.add('qr--action'); | ||
| 196 | paste.classList.add('qr--paste'); | ||
| 197 | paste.classList.add('menu_button'); | ||
| 198 | paste.classList.add('menu_button_icon'); | ||
| 199 | paste.classList.add('fa-solid'); | ||
| 200 | paste.classList.add('fa-paste'); | ||
| 201 | paste.title = 'Add quick reply from clipboard'; | ||
| 202 | paste.addEventListener('click', async()=>{ | ||
| 203 | const text = await navigator.clipboard.readText(); | ||
| 204 | this.onInsertBefore(text); | ||
| 205 | }); | ||
| 206 | actions.append(paste); | ||
| 207 | } | ||
| 208 | const importFile = document.createElement('div'); { | ||
| 209 | importFile.classList.add('qr--action'); | ||
| 210 | importFile.classList.add('qr--importFile'); | ||
| 211 | importFile.classList.add('menu_button'); | ||
| 212 | importFile.classList.add('menu_button_icon'); | ||
| 213 | importFile.classList.add('fa-solid'); | ||
| 214 | importFile.classList.add('fa-file-import'); | ||
| 215 | importFile.title = 'Add quick reply from JSON file'; | ||
| 216 | importFile.addEventListener('click', async()=>{ | ||
| 217 | const inp = document.createElement('input'); { | ||
| 218 | inp.type = 'file'; | ||
| 219 | inp.accept = '.json'; | ||
| 220 | inp.addEventListener('change', async()=>{ | ||
| 221 | if (inp.files.length > 0) { | ||
| 222 | for (const file of inp.files) { | ||
| 223 | const text = await file.text(); | ||
| 224 | this.onInsertBefore(text); | ||
| 225 | } | ||
| 226 | } | ||
| 227 | }); | ||
| 228 | inp.click(); | ||
| 229 | } | ||
| 230 | }); | ||
| 231 | actions.append(importFile); | ||
| 232 | } | ||
| 233 | adder.append(actions); | ||
| 234 | } | ||
| 235 | item.append(adder); | ||
| 236 | } | ||
| 237 | const itemContent = document.createElement('div'); { | ||
| 238 | itemContent.classList.add('qr--content'); | ||
| 239 | const drag = document.createElement('div'); { | ||
| 240 | drag.classList.add('drag-handle'); | ||
| 241 | drag.classList.add('ui-sortable-handle'); | ||
| 242 | drag.textContent = '☰'; | ||
| 243 | itemContent.append(drag); | ||
| 244 | } | ||
| 245 | const lblContainer = document.createElement('div'); { | ||
| 246 | lblContainer.classList.add('qr--set-itemLabelContainer'); | ||
| 247 | const icon = document.createElement('div'); { | ||
| 248 | this.settingsDomIcon = icon; | ||
| 249 | icon.title = 'Click to change icon'; | ||
| 250 | icon.classList.add('qr--set-itemIcon'); | ||
| 251 | icon.classList.add('menu_button'); | ||
| 252 | icon.classList.add('fa-fw'); | ||
| 253 | if (this.icon) { | ||
| 254 | icon.classList.add('fa-solid'); | ||
| 255 | icon.classList.add(this.icon); | ||
| 256 | } | ||
| 257 | icon.addEventListener('click', async()=>{ | ||
| 258 | let value = await showFontAwesomePicker(); | ||
| 259 | this.updateIcon(value); | ||
| 260 | }); | ||
| 261 | lblContainer.append(icon); | ||
| 262 | } | ||
| 263 | const lbl = document.createElement('input'); { | ||
| 264 | this.settingsDomLabel = lbl; | ||
| 265 | lbl.classList.add('qr--set-itemLabel'); | ||
| 266 | lbl.classList.add('text_pole'); | ||
| 267 | lbl.value = this.label; | ||
| 268 | lbl.addEventListener('input', ()=>this.updateLabel(lbl.value)); | ||
| 269 | lblContainer.append(lbl); | ||
| 270 | } | ||
| 271 | itemContent.append(lblContainer); | ||
| 156 | } | 272 | } |
| 157 | item.append(lblContainer); | 273 | item.append(itemContent); |
| 158 | } | 274 | } |
| 159 | const optContainer = document.createElement('div'); { | 275 | const optContainer = document.createElement('div'); { |
| 160 | optContainer.classList.add('qr--set-optionsContainer'); | 276 | optContainer.classList.add('qr--set-optionsContainer'); |
| 161 | const opt = document.createElement('div'); { | 277 | const opt = document.createElement('div'); { |
| 162 | opt.classList.add('qr--action'); | 278 | opt.classList.add('qr--action'); |
| 163 | opt.classList.add('menu_button'); | 279 | opt.classList.add('menu_button'); |
| 280 | opt.classList.add('fa-fw'); | ||
| 164 | opt.classList.add('fa-solid'); | 281 | opt.classList.add('fa-solid'); |
| 165 | opt.textContent = '⁝'; | 282 | opt.textContent = '⁝'; |
| 166 | opt.title = 'Additional options:\n - large editor\n - context menu\n - auto-execution\n - tooltip'; | 283 | opt.title = 'Additional options:\n - large editor\n - context menu\n - auto-execution\n - tooltip'; |
| 167 | opt.addEventListener('click', ()=>this.showEditor()); | 284 | opt.addEventListener('click', ()=>this.showEditor()); |
| 168 | optContainer.append(opt); | 285 | optContainer.append(opt); |
| 169 | } | 286 | } |
| 170 | item.append(optContainer); | 287 | itemContent.append(optContainer); |
| 171 | } | 288 | } |
| 172 | const mes = document.createElement('textarea'); { | 289 | const mes = document.createElement('textarea'); { |
| 173 | this.settingsDomMessage = mes; | 290 | this.settingsDomMessage = mes; |
| @@ -176,22 +293,89 @@ export class QuickReply { | |||
| 176 | mes.value = this.message; | 293 | mes.value = this.message; |
| 177 | //HACK need to use jQuery to catch the triggered event from the expanded editor | 294 | //HACK need to use jQuery to catch the triggered event from the expanded editor |
| 178 | $(mes).on('input', ()=>this.updateMessage(mes.value)); | 295 | $(mes).on('input', ()=>this.updateMessage(mes.value)); |
| 179 | item.append(mes); | 296 | itemContent.append(mes); |
| 180 | } | 297 | } |
| 181 | const actions = document.createElement('div'); { | 298 | const actions = document.createElement('div'); { |
| 182 | actions.classList.add('qr--actions'); | 299 | actions.classList.add('qr--actions'); |
| 300 | const move = document.createElement('div'); { | ||
| 301 | move.classList.add('qr--action'); | ||
| 302 | move.classList.add('menu_button'); | ||
| 303 | move.classList.add('fa-fw'); | ||
| 304 | move.classList.add('fa-solid'); | ||
| 305 | move.classList.add('fa-truck-arrow-right'); | ||
| 306 | move.title = 'Move quick reply to other set'; | ||
| 307 | move.addEventListener('click', ()=>this.onTransfer(this)); | ||
| 308 | actions.append(move); | ||
| 309 | } | ||
| 310 | const copy = document.createElement('div'); { | ||
| 311 | copy.classList.add('qr--action'); | ||
| 312 | copy.classList.add('menu_button'); | ||
| 313 | copy.classList.add('fa-fw'); | ||
| 314 | copy.classList.add('fa-solid'); | ||
| 315 | copy.classList.add('fa-copy'); | ||
| 316 | copy.title = 'Copy quick reply to clipboard'; | ||
| 317 | copy.addEventListener('click', async()=>{ | ||
| 318 | await navigator.clipboard.writeText(JSON.stringify(this)); | ||
| 319 | copy.classList.add('qr--success'); | ||
| 320 | await delay(3010); | ||
| 321 | copy.classList.remove('qr--success'); | ||
| 322 | }); | ||
| 323 | actions.append(copy); | ||
| 324 | } | ||
| 325 | const cut = document.createElement('div'); { | ||
| 326 | cut.classList.add('qr--action'); | ||
| 327 | cut.classList.add('menu_button'); | ||
| 328 | cut.classList.add('fa-fw'); | ||
| 329 | cut.classList.add('fa-solid'); | ||
| 330 | cut.classList.add('fa-cut'); | ||
| 331 | cut.title = 'Cut quick reply to clipboard (copy and remove)'; | ||
| 332 | cut.addEventListener('click', async()=>{ | ||
| 333 | await navigator.clipboard.writeText(JSON.stringify(this)); | ||
| 334 | this.delete(); | ||
| 335 | }); | ||
| 336 | actions.append(cut); | ||
| 337 | } | ||
| 338 | const exp = document.createElement('div'); { | ||
| 339 | exp.classList.add('qr--action'); | ||
| 340 | exp.classList.add('menu_button'); | ||
| 341 | exp.classList.add('fa-fw'); | ||
| 342 | exp.classList.add('fa-solid'); | ||
| 343 | exp.classList.add('fa-file-export'); | ||
| 344 | exp.title = 'Export quick reply as file'; | ||
| 345 | exp.addEventListener('click', ()=>{ | ||
| 346 | const blob = new Blob([JSON.stringify(this)], { type:'text' }); | ||
| 347 | const url = URL.createObjectURL(blob); | ||
| 348 | const a = document.createElement('a'); { | ||
| 349 | a.href = url; | ||
| 350 | a.download = `${this.label}.qr.json`; | ||
| 351 | a.click(); | ||
| 352 | } | ||
| 353 | }); | ||
| 354 | actions.append(exp); | ||
| 355 | } | ||
| 183 | const del = document.createElement('div'); { | 356 | const del = document.createElement('div'); { |
| 184 | del.classList.add('qr--action'); | 357 | del.classList.add('qr--action'); |
| 185 | del.classList.add('menu_button'); | 358 | del.classList.add('menu_button'); |
| 186 | del.classList.add('menu_button_icon'); | 359 | del.classList.add('fa-fw'); |
| 187 | del.classList.add('fa-solid'); | 360 | del.classList.add('fa-solid'); |
| 188 | del.classList.add('fa-trash-can'); | 361 | del.classList.add('fa-trash-can'); |
| 189 | del.classList.add('redWarningBG'); | 362 | del.classList.add('redWarningBG'); |
| 190 | del.title = 'Remove quick reply'; | 363 | del.title = 'Remove Quick Reply\n---\nShit+Click to skip confirmation'; |
| 191 | del.addEventListener('click', ()=>this.delete()); | 364 | del.addEventListener('click', async(evt)=>{ |
| 365 | if (!evt.shiftKey) { | ||
| 366 | const result = await Popup.show.confirm( | ||
| 367 | 'Remove Quick Reply', | ||
| 368 | 'Are you sure you want to remove this Quick Reply?', | ||
| 369 | ); | ||
| 370 | if (result != POPUP_RESULT.AFFIRMATIVE) { | ||
| 371 | return; | ||
| 372 | } | ||
| 373 | } | ||
| 374 | this.delete(); | ||
| 375 | }); | ||
| 192 | actions.append(del); | 376 | actions.append(del); |
| 193 | } | 377 | } |
| 194 | item.append(actions); | 378 | itemContent.append(actions); |
| 195 | } | 379 | } |
| 196 | } | 380 | } |
| 197 | } | 381 | } |
| @@ -208,22 +392,156 @@ export class QuickReply { | |||
| 208 | /**@type {HTMLElement} */ | 392 | /**@type {HTMLElement} */ |
| 209 | // @ts-ignore | 393 | // @ts-ignore |
| 210 | const dom = this.template.cloneNode(true); | 394 | const dom = this.template.cloneNode(true); |
| 395 | this.editorDom = dom; | ||
| 211 | this.editorPopup = new Popup(dom, POPUP_TYPE.TEXT, undefined, { okButton: 'OK', wide: true, large: true, rows: 1 }); | 396 | this.editorPopup = new Popup(dom, POPUP_TYPE.TEXT, undefined, { okButton: 'OK', wide: true, large: true, rows: 1 }); |
| 212 | const popupResult = this.editorPopup.show(); | 397 | const popupResult = this.editorPopup.show(); |
| 213 | 398 | ||
| 214 | // basics | 399 | // basics |
| 400 | /**@type {HTMLElement}*/ | ||
| 401 | const icon = dom.querySelector('#qr--modal-icon'); | ||
| 402 | if (this.icon) { | ||
| 403 | icon.classList.add('fa-solid'); | ||
| 404 | icon.classList.add(this.icon); | ||
| 405 | } | ||
| 406 | else { | ||
| 407 | icon.textContent = '…'; | ||
| 408 | } | ||
| 409 | icon.addEventListener('click', async()=>{ | ||
| 410 | let value = await showFontAwesomePicker(); | ||
| 411 | if (value === null) return; | ||
| 412 | if (this.icon) icon.classList.remove(this.icon); | ||
| 413 | if (value == '') { | ||
| 414 | icon.classList.remove('fa-solid'); | ||
| 415 | icon.textContent = '…'; | ||
| 416 | } else { | ||
| 417 | icon.textContent = ''; | ||
| 418 | icon.classList.add('fa-solid'); | ||
| 419 | icon.classList.add(value); | ||
| 420 | } | ||
| 421 | this.updateIcon(value); | ||
| 422 | }); | ||
| 423 | /**@type {HTMLInputElement}*/ | ||
| 424 | const showLabel = dom.querySelector('#qr--modal-showLabel'); | ||
| 425 | showLabel.checked = this.showLabel; | ||
| 426 | showLabel.addEventListener('click', ()=>{ | ||
| 427 | this.updateShowLabel(showLabel.checked); | ||
| 428 | }); | ||
| 215 | /**@type {HTMLInputElement}*/ | 429 | /**@type {HTMLInputElement}*/ |
| 216 | const label = dom.querySelector('#qr--modal-label'); | 430 | const label = dom.querySelector('#qr--modal-label'); |
| 217 | label.value = this.label; | 431 | label.value = this.label; |
| 218 | label.addEventListener('input', ()=>{ | 432 | label.addEventListener('input', ()=>{ |
| 219 | this.updateLabel(label.value); | 433 | this.updateLabel(label.value); |
| 220 | }); | 434 | }); |
| 435 | let switcherList; | ||
| 436 | dom.querySelector('#qr--modal-switcher').addEventListener('click', (evt)=>{ | ||
| 437 | if (switcherList) { | ||
| 438 | switcherList.remove(); | ||
| 439 | switcherList = null; | ||
| 440 | return; | ||
| 441 | } | ||
| 442 | const list = document.createElement('ul'); { | ||
| 443 | switcherList = list; | ||
| 444 | list.classList.add('qr--modal-switcherList'); | ||
| 445 | const makeList = (qrs)=>{ | ||
| 446 | const setItem = document.createElement('li'); { | ||
| 447 | setItem.classList.add('qr--modal-switcherItem'); | ||
| 448 | setItem.addEventListener('click', ()=>{ | ||
| 449 | list.innerHTML = ''; | ||
| 450 | for (const qrs of quickReplyApi.listSets()) { | ||
| 451 | const item = document.createElement('li'); { | ||
| 452 | item.classList.add('qr--modal-switcherItem'); | ||
| 453 | item.addEventListener('click', ()=>{ | ||
| 454 | list.innerHTML = ''; | ||
| 455 | makeList(quickReplyApi.getSetByName(qrs)); | ||
| 456 | }); | ||
| 457 | const lbl = document.createElement('div'); { | ||
| 458 | lbl.classList.add('qr--label'); | ||
| 459 | lbl.textContent = qrs; | ||
| 460 | item.append(lbl); | ||
| 461 | } | ||
| 462 | list.append(item); | ||
| 463 | } | ||
| 464 | } | ||
| 465 | }); | ||
| 466 | const lbl = document.createElement('div'); { | ||
| 467 | lbl.classList.add('qr--label'); | ||
| 468 | const icon = document.createElement('i'); { | ||
| 469 | icon.classList.add('fa-solid'); | ||
| 470 | icon.classList.add('fa-arrow-alt-circle-right'); | ||
| 471 | icon.classList.add('menu_button'); | ||
| 472 | lbl.append(icon); | ||
| 473 | } | ||
| 474 | const text = document.createElement('span'); { | ||
| 475 | text.textContent = 'Switch QR Sets...'; | ||
| 476 | lbl.append(text); | ||
| 477 | } | ||
| 478 | setItem.append(lbl); | ||
| 479 | } | ||
| 480 | list.append(setItem); | ||
| 481 | } | ||
| 482 | const addItem = document.createElement('li'); { | ||
| 483 | addItem.classList.add('qr--modal-switcherItem'); | ||
| 484 | addItem.addEventListener('click', ()=>{ | ||
| 485 | const qr = quickReplyApi.getSetByQr(this).addQuickReply(); | ||
| 486 | this.editorPopup.completeAffirmative(); | ||
| 487 | qr.showEditor(); | ||
| 488 | }); | ||
| 489 | const lbl = document.createElement('div'); { | ||
| 490 | lbl.classList.add('qr--label'); | ||
| 491 | const icon = document.createElement('i'); { | ||
| 492 | icon.classList.add('fa-solid'); | ||
| 493 | icon.classList.add('fa-plus'); | ||
| 494 | icon.classList.add('menu_button'); | ||
| 495 | lbl.append(icon); | ||
| 496 | } | ||
| 497 | const text = document.createElement('span'); { | ||
| 498 | text.textContent = 'Add QR'; | ||
| 499 | lbl.append(text); | ||
| 500 | } | ||
| 501 | addItem.append(lbl); | ||
| 502 | } | ||
| 503 | list.append(addItem); | ||
| 504 | } | ||
| 505 | for (const qr of qrs.qrList.toSorted((a,b)=>a.label.toLowerCase().localeCompare(b.label.toLowerCase()))) { | ||
| 506 | const item = document.createElement('li'); { | ||
| 507 | item.classList.add('qr--modal-switcherItem'); | ||
| 508 | if (qr == this) item.classList.add('qr--current'); | ||
| 509 | else item.addEventListener('click', ()=>{ | ||
| 510 | this.editorPopup.completeAffirmative(); | ||
| 511 | qr.showEditor(); | ||
| 512 | }); | ||
| 513 | const lbl = document.createElement('div'); { | ||
| 514 | lbl.classList.add('qr--label'); | ||
| 515 | lbl.textContent = qr.label; | ||
| 516 | item.append(lbl); | ||
| 517 | } | ||
| 518 | const id = document.createElement('div'); { | ||
| 519 | id.classList.add('qr--id'); | ||
| 520 | id.textContent = qr.id.toString(); | ||
| 521 | item.append(id); | ||
| 522 | } | ||
| 523 | const mes = document.createElement('div'); { | ||
| 524 | mes.classList.add('qr--message'); | ||
| 525 | mes.textContent = qr.message; | ||
| 526 | item.append(mes); | ||
| 527 | } | ||
| 528 | list.append(item); | ||
| 529 | } | ||
| 530 | } | ||
| 531 | }; | ||
| 532 | makeList(quickReplyApi.getSetByQr(this)); | ||
| 533 | } | ||
| 534 | label.parentElement.append(list); | ||
| 535 | }); | ||
| 221 | /**@type {HTMLInputElement}*/ | 536 | /**@type {HTMLInputElement}*/ |
| 222 | const title = dom.querySelector('#qr--modal-title'); | 537 | const title = dom.querySelector('#qr--modal-title'); |
| 223 | title.value = this.title; | 538 | title.value = this.title; |
| 224 | title.addEventListener('input', () => { | 539 | title.addEventListener('input', () => { |
| 225 | this.updateTitle(title.value); | 540 | this.updateTitle(title.value); |
| 226 | }); | 541 | }); |
| 542 | /**@type {HTMLElement}*/ | ||
| 543 | const messageSyntaxInner = dom.querySelector('#qr--modal-messageSyntaxInner'); | ||
| 544 | this.editorSyntax = messageSyntaxInner; | ||
| 227 | /**@type {HTMLInputElement}*/ | 545 | /**@type {HTMLInputElement}*/ |
| 228 | const wrap = dom.querySelector('#qr--modal-wrap'); | 546 | const wrap = dom.querySelector('#qr--modal-wrap'); |
| 229 | wrap.checked = JSON.parse(localStorage.getItem('qr--wrap') ?? 'false'); | 547 | wrap.checked = JSON.parse(localStorage.getItem('qr--wrap') ?? 'false'); |
| @@ -235,9 +553,15 @@ export class QuickReply { | |||
| 235 | if (wrap.checked) { | 553 | if (wrap.checked) { |
| 236 | message.style.whiteSpace = 'pre-wrap'; | 554 | message.style.whiteSpace = 'pre-wrap'; |
| 237 | messageSyntaxInner.style.whiteSpace = 'pre-wrap'; | 555 | messageSyntaxInner.style.whiteSpace = 'pre-wrap'; |
| 556 | if (this.clone) { | ||
| 557 | this.clone.style.whiteSpace = 'pre-wrap'; | ||
| 558 | } | ||
| 238 | } else { | 559 | } else { |
| 239 | message.style.whiteSpace = 'pre'; | 560 | message.style.whiteSpace = 'pre'; |
| 240 | messageSyntaxInner.style.whiteSpace = 'pre'; | 561 | messageSyntaxInner.style.whiteSpace = 'pre'; |
| 562 | if (this.clone) { | ||
| 563 | this.clone.style.whiteSpace = 'pre'; | ||
| 564 | } | ||
| 241 | } | 565 | } |
| 242 | updateScrollDebounced(); | 566 | updateScrollDebounced(); |
| 243 | }; | 567 | }; |
| @@ -261,11 +585,8 @@ export class QuickReply { | |||
| 261 | }); | 585 | }); |
| 262 | }; | 586 | }; |
| 263 | const updateScrollDebounced = updateScroll; | 587 | const updateScrollDebounced = updateScroll; |
| 264 | const updateSyntax = ()=>{ | ||
| 265 | messageSyntaxInner.innerHTML = hljs.highlight(`${message.value}${message.value.slice(-1) == '\n' ? ' ' : ''}`, { language:'stscript', ignoreIllegals:true })?.value; | ||
| 266 | }; | ||
| 267 | const updateSyntaxEnabled = ()=>{ | 588 | const updateSyntaxEnabled = ()=>{ |
| 268 | if (JSON.parse(localStorage.getItem('qr--syntax'))) { | 589 | if (syntax.checked) { |
| 269 | dom.querySelector('#qr--modal-messageHolder').classList.remove('qr--noSyntax'); | 590 | dom.querySelector('#qr--modal-messageHolder').classList.remove('qr--noSyntax'); |
| 270 | } else { | 591 | } else { |
| 271 | dom.querySelector('#qr--modal-messageHolder').classList.add('qr--noSyntax'); | 592 | dom.querySelector('#qr--modal-messageHolder').classList.add('qr--noSyntax'); |
| @@ -296,48 +617,108 @@ export class QuickReply { | |||
| 296 | localStorage.setItem('qr--syntax', JSON.stringify(syntax.checked)); | 617 | localStorage.setItem('qr--syntax', JSON.stringify(syntax.checked)); |
| 297 | updateSyntaxEnabled(); | 618 | updateSyntaxEnabled(); |
| 298 | }); | 619 | }); |
| 620 | if (navigator.keyboard) { | ||
| 621 | navigator.keyboard.getLayoutMap().then(it=>dom.querySelector('#qr--modal-commentKey').textContent = it.get('Backslash')); | ||
| 622 | } else { | ||
| 623 | dom.querySelector('#qr--modal-commentKey').closest('small').remove(); | ||
| 624 | } | ||
| 625 | this.editorMessageLabel = dom.querySelector('label[for="qr--modal-message"]'); | ||
| 299 | /**@type {HTMLTextAreaElement}*/ | 626 | /**@type {HTMLTextAreaElement}*/ |
| 300 | const message = dom.querySelector('#qr--modal-message'); | 627 | const message = dom.querySelector('#qr--modal-message'); |
| 628 | this.editorMessage = message; | ||
| 301 | message.value = this.message; | 629 | message.value = this.message; |
| 630 | const updateMessageDebounced = debounce((value)=>this.updateMessage(value), 10); | ||
| 302 | message.addEventListener('input', () => { | 631 | message.addEventListener('input', () => { |
| 303 | updateSyntax(); | 632 | updateMessageDebounced(message.value); |
| 304 | this.updateMessage(message.value); | ||
| 305 | updateScrollDebounced(); | 633 | updateScrollDebounced(); |
| 306 | }); | 634 | }, { passive:true }); |
| 307 | setSlashCommandAutoComplete(message, true); | 635 | const getLineStart = ()=>{ |
| 308 | //TODO move tab support for textarea into its own helper(?) and use for both this and .editor_maximize | 636 | const start = message.selectionStart; |
| 637 | const end = message.selectionEnd; | ||
| 638 | let lineStart; | ||
| 639 | if (start == 0 || message.value[start - 1] == '\n') { | ||
| 640 | // cursor is already at beginning of line | ||
| 641 | // -> keep start | ||
| 642 | lineStart = start; | ||
| 643 | } else { | ||
| 644 | // cursor is at end of line or somewhere in the line | ||
| 645 | // -> find last newline before cursor and start after that | ||
| 646 | lineStart = message.value.lastIndexOf('\n', start - 1) + 1; | ||
| 647 | } | ||
| 648 | return lineStart; | ||
| 649 | }; | ||
| 309 | message.addEventListener('keydown', async(evt) => { | 650 | message.addEventListener('keydown', async(evt) => { |
| 651 | if (this.isExecuting) return; | ||
| 310 | if (evt.key == 'Tab' && !evt.shiftKey && !evt.ctrlKey && !evt.altKey) { | 652 | if (evt.key == 'Tab' && !evt.shiftKey && !evt.ctrlKey && !evt.altKey) { |
| 653 | // increase indent | ||
| 311 | evt.preventDefault(); | 654 | evt.preventDefault(); |
| 312 | const start = message.selectionStart; | 655 | const start = message.selectionStart; |
| 313 | const end = message.selectionEnd; | 656 | const end = message.selectionEnd; |
| 314 | if (end - start > 0 && message.value.substring(start, end).includes('\n')) { | 657 | if (end - start > 0 && message.value.substring(start, end).includes('\n')) { |
| 315 | const lineStart = message.value.lastIndexOf('\n', start); | 658 | evt.stopImmediatePropagation(); |
| 316 | const count = message.value.substring(lineStart, end).split('\n').length - 1; | 659 | evt.stopPropagation(); |
| 317 | message.value = `${message.value.substring(0, lineStart)}${message.value.substring(lineStart, end).replace(/\n/g, '\n\t')}${message.value.substring(end)}`; | 660 | const lineStart = getLineStart(); |
| 318 | message.selectionStart = start + 1; | 661 | message.selectionStart = lineStart; |
| 319 | message.selectionEnd = end + count; | 662 | const affectedLines = message.value.substring(lineStart, end).split('\n'); |
| 320 | updateSyntax(); | 663 | // document.execCommand is deprecated (and potentially buggy in some browsers) but the only way to retain undo-history |
| 321 | } else { | 664 | document.execCommand('insertText', false, `\t${affectedLines.join('\n\t')}`); |
| 322 | message.value = `${message.value.substring(0, start)}\t${message.value.substring(end)}`; | ||
| 323 | message.selectionStart = start + 1; | 665 | message.selectionStart = start + 1; |
| 324 | message.selectionEnd = end + 1; | 666 | message.selectionEnd = end + affectedLines.length; |
| 325 | updateSyntax(); | 667 | message.dispatchEvent(new Event('input', { bubbles:true })); |
| 668 | } else if (!(ac.isReplaceable && ac.isActive)) { | ||
| 669 | evt.stopImmediatePropagation(); | ||
| 670 | evt.stopPropagation(); | ||
| 671 | // document.execCommand is deprecated (and potentially buggy in some browsers) but the only way to retain undo-history | ||
| 672 | document.execCommand('insertText', false, '\t'); | ||
| 673 | message.dispatchEvent(new Event('input', { bubbles:true })); | ||
| 326 | } | 674 | } |
| 327 | } else if (evt.key == 'Tab' && evt.shiftKey && !evt.ctrlKey && !evt.altKey) { | 675 | } else if (evt.key == 'Tab' && evt.shiftKey && !evt.ctrlKey && !evt.altKey) { |
| 676 | // decrease indent | ||
| 328 | evt.preventDefault(); | 677 | evt.preventDefault(); |
| 678 | evt.stopImmediatePropagation(); | ||
| 679 | evt.stopPropagation(); | ||
| 329 | const start = message.selectionStart; | 680 | const start = message.selectionStart; |
| 330 | const end = message.selectionEnd; | 681 | const end = message.selectionEnd; |
| 331 | const lineStart = message.value.lastIndexOf('\n', start); | 682 | const lineStart = getLineStart(); |
| 332 | const count = message.value.substring(lineStart, end).split('\n\t').length - 1; | 683 | message.selectionStart = lineStart; |
| 333 | message.value = `${message.value.substring(0, lineStart)}${message.value.substring(lineStart, end).replace(/\n\t/g, '\n')}${message.value.substring(end)}`; | 684 | const affectedLines = message.value.substring(lineStart, end).split('\n'); |
| 334 | message.selectionStart = start - 1; | 685 | const newText = affectedLines.map(it=>it.replace(/^\t/, '')).join('\n'); |
| 335 | message.selectionEnd = end - count; | 686 | const delta = affectedLines.join('\n').length - newText.length; |
| 336 | updateSyntax(); | 687 | // document.execCommand is deprecated (and potentially buggy in some browsers) but the only way to retain undo-history |
| 688 | if (delta > 0) { | ||
| 689 | if (newText == '') { | ||
| 690 | document.execCommand('delete', false); | ||
| 691 | } else { | ||
| 692 | document.execCommand('insertText', false, newText); | ||
| 693 | } | ||
| 694 | message.selectionStart = start - (affectedLines[0].startsWith('\t') ? 1 : 0); | ||
| 695 | message.selectionEnd = end - delta; | ||
| 696 | message.dispatchEvent(new Event('input', { bubbles:true })); | ||
| 697 | } else { | ||
| 698 | message.selectionStart = start; | ||
| 699 | } | ||
| 700 | } else if (evt.key == 'Enter' && !evt.ctrlKey && !evt.shiftKey && !evt.altKey && !(ac.isReplaceable && ac.isActive)) { | ||
| 701 | // new line, keep indent | ||
| 702 | const start = message.selectionStart; | ||
| 703 | const end = message.selectionEnd; | ||
| 704 | let lineStart = getLineStart(); | ||
| 705 | const indent = /^([^\S\n]*)/.exec(message.value.slice(lineStart))[1] ?? ''; | ||
| 706 | if (indent.length) { | ||
| 707 | evt.stopImmediatePropagation(); | ||
| 708 | evt.stopPropagation(); | ||
| 709 | evt.preventDefault(); | ||
| 710 | // document.execCommand is deprecated (and potentially buggy in some browsers) but the only way to retain undo-history | ||
| 711 | document.execCommand('insertText', false, `\n${indent}`); | ||
| 712 | message.selectionStart = start + 1 + indent.length; | ||
| 713 | message.selectionEnd = message.selectionStart; | ||
| 714 | message.dispatchEvent(new Event('input', { bubbles:true })); | ||
| 715 | } | ||
| 337 | } else if (evt.key == 'Enter' && evt.ctrlKey && !evt.shiftKey && !evt.altKey) { | 716 | } else if (evt.key == 'Enter' && evt.ctrlKey && !evt.shiftKey && !evt.altKey) { |
| 338 | evt.stopPropagation(); | ||
| 339 | evt.preventDefault(); | ||
| 340 | if (executeShortcut.checked) { | 717 | if (executeShortcut.checked) { |
| 718 | // execute QR | ||
| 719 | evt.stopImmediatePropagation(); | ||
| 720 | evt.stopPropagation(); | ||
| 721 | evt.preventDefault(); | ||
| 341 | const selectionStart = message.selectionStart; | 722 | const selectionStart = message.selectionStart; |
| 342 | const selectionEnd = message.selectionEnd; | 723 | const selectionEnd = message.selectionEnd; |
| 343 | message.blur(); | 724 | message.blur(); |
| @@ -348,17 +729,175 @@ export class QuickReply { | |||
| 348 | message.selectionEnd = selectionEnd; | 729 | message.selectionEnd = selectionEnd; |
| 349 | } | 730 | } |
| 350 | } | 731 | } |
| 732 | } else if (evt.key == 'F9' && !evt.ctrlKey && !evt.shiftKey && !evt.altKey) { | ||
| 733 | // toggle breakpoint | ||
| 734 | evt.stopImmediatePropagation(); | ||
| 735 | evt.stopPropagation(); | ||
| 736 | evt.preventDefault(); | ||
| 737 | preBreakPointStart = message.selectionStart; | ||
| 738 | preBreakPointEnd = message.selectionEnd; | ||
| 739 | toggleBreakpoint(); | ||
| 740 | } else if (evt.code == 'Backslash' && evt.ctrlKey && !evt.shiftKey && !evt.altKey) { | ||
| 741 | // toggle block comment | ||
| 742 | // (evt.code will use the same physical key on the keyboard across different keyboard layouts) | ||
| 743 | evt.stopImmediatePropagation(); | ||
| 744 | evt.stopPropagation(); | ||
| 745 | evt.preventDefault(); | ||
| 746 | // check if we are inside a comment -> uncomment | ||
| 747 | const parser = new SlashCommandParser(); | ||
| 748 | parser.parse(message.value, false); | ||
| 749 | const start = message.selectionStart; | ||
| 750 | const end = message.selectionEnd; | ||
| 751 | const comment = parser.commandIndex.findLast(it=>it.name == '*' && (it.start <= start && it.end >= start || it.start <= end && it.end >= end)); | ||
| 752 | if (comment) { | ||
| 753 | // uncomment | ||
| 754 | let content = message.value.slice(comment.start + 1, comment.end - 1); | ||
| 755 | let len = content.length; | ||
| 756 | content = content.replace(/^ /, ''); | ||
| 757 | const offsetStart = len - content.length; | ||
| 758 | len = content.length; | ||
| 759 | content = content.replace(/ $/, ''); | ||
| 760 | const offsetEnd = len - content.length; | ||
| 761 | message.selectionStart = comment.start - 1; | ||
| 762 | message.selectionEnd = comment.end + 1; | ||
| 763 | // document.execCommand is deprecated (and potentially buggy in some browsers) but the only way to retain undo-history | ||
| 764 | document.execCommand('insertText', false, content); | ||
| 765 | message.selectionStart = start - (start >= comment.start ? 2 + offsetStart : 0); | ||
| 766 | message.selectionEnd = end - 2 - offsetStart - (end >= comment.end ? 2 + offsetEnd : 0); | ||
| 767 | } else { | ||
| 768 | // comment | ||
| 769 | const lineStart = getLineStart(); | ||
| 770 | const lineEnd = message.value.indexOf('\n', end); | ||
| 771 | message.selectionStart = lineStart; | ||
| 772 | message.selectionEnd = lineEnd; | ||
| 773 | // document.execCommand is deprecated (and potentially buggy in some browsers) but the only way to retain undo-history | ||
| 774 | document.execCommand('insertText', false, `/* ${message.value.slice(lineStart, lineEnd)} *|`); | ||
| 775 | message.selectionStart = start + 3; | ||
| 776 | message.selectionEnd = end + 3; | ||
| 777 | } | ||
| 778 | message.dispatchEvent(new Event('input', { bubbles:true })); | ||
| 351 | } | 779 | } |
| 352 | }); | 780 | }); |
| 781 | const ac = await setSlashCommandAutoComplete(message, true); | ||
| 353 | message.addEventListener('wheel', (evt)=>{ | 782 | message.addEventListener('wheel', (evt)=>{ |
| 354 | updateScrollDebounced(evt); | 783 | updateScrollDebounced(evt); |
| 355 | }); | 784 | }); |
| 356 | message.addEventListener('scroll', (evt)=>{ | 785 | message.addEventListener('scroll', (evt)=>{ |
| 357 | updateScrollDebounced(); | 786 | updateScrollDebounced(); |
| 358 | }); | 787 | }); |
| 788 | let preBreakPointStart; | ||
| 789 | let preBreakPointEnd; | ||
| 790 | /** | ||
| 791 | * @param {SlashCommandBreakPoint} bp | ||
| 792 | */ | ||
| 793 | const removeBreakpoint = (bp)=>{ | ||
| 794 | // start at -1 because "/" is not included in start-end | ||
| 795 | let start = bp.start - 1; | ||
| 796 | // step left until forward slash "/" | ||
| 797 | while (message.value[start] != '/') start--; | ||
| 798 | // step left while whitespace (except newline) before start | ||
| 799 | while (/[^\S\n]/.test(message.value[start - 1])) start--; | ||
| 800 | // if newline before indent, include the newline for removal | ||
| 801 | if (message.value[start - 1] == '\n') start--; | ||
| 802 | let end = bp.end; | ||
| 803 | // step right while whitespace | ||
| 804 | while (/\s/.test(message.value[end])) end++; | ||
| 805 | // if pipe after whitepace, include pipe for removal | ||
| 806 | if (message.value[end] == '|') end++; | ||
| 807 | message.selectionStart = start; | ||
| 808 | message.selectionEnd = end; | ||
| 809 | // document.execCommand is deprecated (and potentially buggy in some browsers) but the only way to retain undo-history | ||
| 810 | document.execCommand('insertText', false, ''); | ||
| 811 | message.dispatchEvent(new Event('input', { bubbles:true })); | ||
| 812 | let postStart = preBreakPointStart; | ||
| 813 | let postEnd = preBreakPointEnd; | ||
| 814 | // set caret back to where it was | ||
| 815 | if (preBreakPointStart <= start) { | ||
| 816 | // selection start was before breakpoint: do nothing | ||
| 817 | } else if (preBreakPointStart > start && preBreakPointEnd < end) { | ||
| 818 | // selection start was inside breakpoint: move to index before breakpoint | ||
| 819 | postStart = start; | ||
| 820 | } else if (preBreakPointStart >= end) { | ||
| 821 | // selection start was behind breakpoint: move back by length of removed string | ||
| 822 | postStart = preBreakPointStart - (end - start); | ||
| 823 | } | ||
| 824 | if (preBreakPointEnd <= start) { | ||
| 825 | // do nothing | ||
| 826 | } else if (preBreakPointEnd > start && preBreakPointEnd < end) { | ||
| 827 | // selection end was inside breakpoint: move to index before breakpoint | ||
| 828 | postEnd = start; | ||
| 829 | } else if (preBreakPointEnd >= end) { | ||
| 830 | // selection end was behind breakpoint: move back by length of removed string | ||
| 831 | postEnd = preBreakPointEnd - (end - start); | ||
| 832 | } | ||
| 833 | return { start:postStart, end:postEnd }; | ||
| 834 | }; | ||
| 835 | /** | ||
| 836 | * @param {SlashCommandExecutor} cmd | ||
| 837 | */ | ||
| 838 | const addBreakpoint = (cmd)=>{ | ||
| 839 | // start at -1 because "/" is not included in start-end | ||
| 840 | let start = cmd.start - 1; | ||
| 841 | let indent = ''; | ||
| 842 | // step left until forward slash "/" | ||
| 843 | while (message.value[start] != '/') start--; | ||
| 844 | // step left while whitespace (except newline) before start, collect the whitespace to help build indentation | ||
| 845 | while (/[^\S\n]/.test(message.value[start - 1])) { | ||
| 846 | start--; | ||
| 847 | indent += message.value[start]; | ||
| 848 | } | ||
| 849 | // if newline before indent, include the newline | ||
| 850 | if (message.value[start - 1] == '\n') { | ||
| 851 | start--; | ||
| 852 | indent = `\n${indent}`; | ||
| 853 | } | ||
| 854 | const breakpointText = `${indent}/breakpoint |`; | ||
| 855 | message.selectionStart = start; | ||
| 856 | message.selectionEnd = start; | ||
| 857 | // document.execCommand is deprecated (and potentially buggy in some browsers) but the only way to retain undo-history | ||
| 858 | document.execCommand('insertText', false, breakpointText); | ||
| 859 | message.dispatchEvent(new Event('input', { bubbles:true })); | ||
| 860 | return breakpointText.length; | ||
| 861 | }; | ||
| 862 | const toggleBreakpoint = ()=>{ | ||
| 863 | const idx = message.selectionStart; | ||
| 864 | let postStart = preBreakPointStart; | ||
| 865 | let postEnd = preBreakPointEnd; | ||
| 866 | const parser = new SlashCommandParser(); | ||
| 867 | parser.parse(message.value, false); | ||
| 868 | const cmdIdx = parser.commandIndex.findLastIndex(it=>it.start <= idx); | ||
| 869 | if (cmdIdx > -1) { | ||
| 870 | const cmd = parser.commandIndex[cmdIdx]; | ||
| 871 | if (cmd instanceof SlashCommandBreakPoint) { | ||
| 872 | const bp = cmd; | ||
| 873 | const { start, end } = removeBreakpoint(bp); | ||
| 874 | postStart = start; | ||
| 875 | postEnd = end; | ||
| 876 | } else if (parser.commandIndex[cmdIdx - 1] instanceof SlashCommandBreakPoint) { | ||
| 877 | const bp = parser.commandIndex[cmdIdx - 1]; | ||
| 878 | const { start, end } = removeBreakpoint(bp); | ||
| 879 | postStart = start; | ||
| 880 | postEnd = end; | ||
| 881 | } else { | ||
| 882 | const len = addBreakpoint(cmd); | ||
| 883 | postStart += len; | ||
| 884 | postEnd += len; | ||
| 885 | } | ||
| 886 | message.selectionStart = postStart; | ||
| 887 | message.selectionEnd = postEnd; | ||
| 888 | } | ||
| 889 | }; | ||
| 890 | message.addEventListener('pointerdown', (evt)=>{ | ||
| 891 | if (!evt.ctrlKey || !evt.altKey) return; | ||
| 892 | preBreakPointStart = message.selectionStart; | ||
| 893 | preBreakPointEnd = message.selectionEnd; | ||
| 894 | }); | ||
| 895 | message.addEventListener('pointerup', async(evt)=>{ | ||
| 896 | if (!evt.ctrlKey || !evt.altKey || message.selectionStart != message.selectionEnd) return; | ||
| 897 | toggleBreakpoint(); | ||
| 898 | }); | ||
| 359 | /** @type {any} */ | 899 | /** @type {any} */ |
| 360 | const resizeListener = debounce((evt) => { | 900 | const resizeListener = debounce((evt) => { |
| 361 | updateSyntax(); | ||
| 362 | updateScrollDebounced(evt); | 901 | updateScrollDebounced(evt); |
| 363 | if (document.activeElement == message) { | 902 | if (document.activeElement == message) { |
| 364 | message.blur(); | 903 | message.blur(); |
| @@ -367,10 +906,43 @@ export class QuickReply { | |||
| 367 | }); | 906 | }); |
| 368 | window.addEventListener('resize', resizeListener); | 907 | window.addEventListener('resize', resizeListener); |
| 369 | updateSyntaxEnabled(); | 908 | updateSyntaxEnabled(); |
| 909 | const updateSyntax = ()=>{ | ||
| 910 | if (messageSyntaxInner && syntax.checked) { | ||
| 911 | morphdom( | ||
| 912 | messageSyntaxInner, | ||
| 913 | `<div>${hljs.highlight(`${message.value}${message.value.slice(-1) == '\n' ? ' ' : ''}`, { language:'stscript', ignoreIllegals:true })?.value}</div>`, | ||
| 914 | { childrenOnly: true }, | ||
| 915 | ); | ||
| 916 | updateScrollDebounced(); | ||
| 917 | } | ||
| 918 | }; | ||
| 919 | let lastSyntaxUpdate = 0; | ||
| 920 | const fpsTime = 1000 / 30; | ||
| 921 | let lastMessageValue = null; | ||
| 922 | let wasSyntax = null; | ||
| 923 | const updateSyntaxLoop = ()=>{ | ||
| 924 | const now = Date.now(); | ||
| 925 | // fps limit | ||
| 926 | if (now - lastSyntaxUpdate < fpsTime) return requestAnimationFrame(updateSyntaxLoop); | ||
| 927 | // elements don't exist (yet?) | ||
| 928 | if (!messageSyntaxInner || !message) return requestAnimationFrame(updateSyntaxLoop); | ||
| 929 | // elements no longer part of the document | ||
| 930 | if (!messageSyntaxInner.closest('body')) return; | ||
| 931 | // debugger is running | ||
| 932 | if (this.isExecuting) { | ||
| 933 | lastMessageValue = null; | ||
| 934 | return requestAnimationFrame(updateSyntaxLoop); | ||
| 935 | } | ||
| 936 | // value hasn't changed | ||
| 937 | if (wasSyntax == syntax.checked && lastMessageValue == message.value) return requestAnimationFrame(updateSyntaxLoop); | ||
| 938 | wasSyntax = syntax.checked; | ||
| 939 | lastSyntaxUpdate = now; | ||
| 940 | lastMessageValue = message.value; | ||
| 941 | updateSyntax(); | ||
| 942 | requestAnimationFrame(updateSyntaxLoop); | ||
| 943 | }; | ||
| 944 | requestAnimationFrame(()=>updateSyntaxLoop()); | ||
| 370 | message.style.setProperty('text-shadow', 'none', 'important'); | 945 | message.style.setProperty('text-shadow', 'none', 'important'); |
| 371 | /**@type {HTMLElement}*/ | ||
| 372 | const messageSyntaxInner = dom.querySelector('#qr--modal-messageSyntaxInner'); | ||
| 373 | updateSyntax(); | ||
| 374 | updateWrap(); | 946 | updateWrap(); |
| 375 | updateTabSize(); | 947 | updateTabSize(); |
| 376 | 948 | ||
| @@ -379,7 +951,7 @@ export class QuickReply { | |||
| 379 | const tpl = dom.querySelector('#qr--ctxItem'); | 951 | const tpl = dom.querySelector('#qr--ctxItem'); |
| 380 | const linkList = dom.querySelector('#qr--ctxEditor'); | 952 | const linkList = dom.querySelector('#qr--ctxEditor'); |
| 381 | const fillQrSetSelect = (/**@type {HTMLSelectElement}*/select, /**@type {QuickReplyContextLink}*/ link) => { | 953 | const fillQrSetSelect = (/**@type {HTMLSelectElement}*/select, /**@type {QuickReplyContextLink}*/ link) => { |
| 382 | [{ name: 'Select a QR set' }, ...QuickReplySet.list].forEach(qrs => { | 954 | [{ name: 'Select a QR set' }, ...QuickReplySet.list.toSorted((a,b)=>a.name.toLowerCase().localeCompare(b.name.toLowerCase()))].forEach(qrs => { |
| 383 | const opt = document.createElement('option'); { | 955 | const opt = document.createElement('option'); { |
| 384 | opt.value = qrs.name; | 956 | opt.value = qrs.name; |
| 385 | opt.textContent = qrs.name; | 957 | opt.textContent = qrs.name; |
| @@ -388,7 +960,7 @@ export class QuickReply { | |||
| 388 | } | 960 | } |
| 389 | }); | 961 | }); |
| 390 | }; | 962 | }; |
| 391 | const addCtxItem = (/**@type {QuickReplyContextLink}*/link, /**@type {Number}*/idx) => { | 963 | const addCtxItem = (/**@type {QuickReplyContextLink}*/link, /**@type {number}*/idx) => { |
| 392 | /**@type {HTMLElement} */ | 964 | /**@type {HTMLElement} */ |
| 393 | // @ts-ignore | 965 | // @ts-ignore |
| 394 | const itemDom = tpl.content.querySelector('.qr--ctxItem').cloneNode(true); { | 966 | const itemDom = tpl.content.querySelector('.qr--ctxItem').cloneNode(true); { |
| @@ -490,6 +1062,13 @@ export class QuickReply { | |||
| 490 | this.updateContext(); | 1062 | this.updateContext(); |
| 491 | }); | 1063 | }); |
| 492 | /**@type {HTMLInputElement}*/ | 1064 | /**@type {HTMLInputElement}*/ |
| 1065 | const executeOnNewChat = dom.querySelector('#qr--executeOnNewChat'); | ||
| 1066 | executeOnNewChat.checked = this.executeOnNewChat; | ||
| 1067 | executeOnNewChat.addEventListener('click', ()=>{ | ||
| 1068 | this.executeOnNewChat = executeOnNewChat.checked; | ||
| 1069 | this.updateContext(); | ||
| 1070 | }); | ||
| 1071 | /**@type {HTMLInputElement}*/ | ||
| 493 | const automationId = dom.querySelector('#qr--automationId'); | 1072 | const automationId = dom.querySelector('#qr--automationId'); |
| 494 | automationId.value = this.automationId; | 1073 | automationId.value = this.automationId; |
| 495 | automationId.addEventListener('input', () => { | 1074 | automationId.addEventListener('input', () => { |
| @@ -506,9 +1085,9 @@ export class QuickReply { | |||
| 506 | /**@type {HTMLElement}*/ | 1085 | /**@type {HTMLElement}*/ |
| 507 | const executeResult = dom.querySelector('#qr--modal-executeResult'); | 1086 | const executeResult = dom.querySelector('#qr--modal-executeResult'); |
| 508 | this.editorExecuteResult = executeResult; | 1087 | this.editorExecuteResult = executeResult; |
| 509 | /**@type {HTMLInputElement}*/ | 1088 | /**@type {HTMLElement}*/ |
| 510 | const executeHide = dom.querySelector('#qr--modal-executeHide'); | 1089 | const debugState = dom.querySelector('#qr--modal-debugState'); |
| 511 | this.editorExecuteHide = executeHide; | 1090 | this.editorDebugState = debugState; |
| 512 | /**@type {HTMLElement}*/ | 1091 | /**@type {HTMLElement}*/ |
| 513 | const executeBtn = dom.querySelector('#qr--modal-execute'); | 1092 | const executeBtn = dom.querySelector('#qr--modal-execute'); |
| 514 | this.editorExecuteBtn = executeBtn; | 1093 | this.editorExecuteBtn = executeBtn; |
| @@ -536,6 +1115,76 @@ export class QuickReply { | |||
| 536 | this.abortController?.abort('Stop button clicked'); | 1115 | this.abortController?.abort('Stop button clicked'); |
| 537 | }); | 1116 | }); |
| 538 | 1117 | ||
| 1118 | /**@type {HTMLTextAreaElement} */ | ||
| 1119 | const inputOg = document.querySelector('#send_textarea'); | ||
| 1120 | const inputMirror = dom.querySelector('#qr--modal-send_textarea'); | ||
| 1121 | inputMirror.value = inputOg.value; | ||
| 1122 | const inputOgMo = new MutationObserver(muts=>{ | ||
| 1123 | if (muts.find(it=>[...it.removedNodes].includes(inputMirror) || [...it.removedNodes].find(n=>n.contains(inputMirror)))) { | ||
| 1124 | inputOg.removeEventListener('input', inputOgListener); | ||
| 1125 | } | ||
| 1126 | }); | ||
| 1127 | inputOgMo.observe(document.body, { childList:true }); | ||
| 1128 | const inputOgListener = ()=>{ | ||
| 1129 | inputMirror.value = inputOg.value; | ||
| 1130 | }; | ||
| 1131 | inputOg.addEventListener('input', inputOgListener); | ||
| 1132 | inputMirror.addEventListener('input', ()=>{ | ||
| 1133 | inputOg.value = inputMirror.value; | ||
| 1134 | }); | ||
| 1135 | |||
| 1136 | /**@type {HTMLElement}*/ | ||
| 1137 | const resumeBtn = dom.querySelector('#qr--modal-resume'); | ||
| 1138 | resumeBtn.addEventListener('click', ()=>{ | ||
| 1139 | this.debugController?.resume(); | ||
| 1140 | }); | ||
| 1141 | /**@type {HTMLElement}*/ | ||
| 1142 | const stepBtn = dom.querySelector('#qr--modal-step'); | ||
| 1143 | stepBtn.addEventListener('click', ()=>{ | ||
| 1144 | this.debugController?.step(); | ||
| 1145 | }); | ||
| 1146 | /**@type {HTMLElement}*/ | ||
| 1147 | const stepIntoBtn = dom.querySelector('#qr--modal-stepInto'); | ||
| 1148 | stepIntoBtn.addEventListener('click', ()=>{ | ||
| 1149 | this.debugController?.stepInto(); | ||
| 1150 | }); | ||
| 1151 | /**@type {HTMLElement}*/ | ||
| 1152 | const stepOutBtn = dom.querySelector('#qr--modal-stepOut'); | ||
| 1153 | stepOutBtn.addEventListener('click', ()=>{ | ||
| 1154 | this.debugController?.stepOut(); | ||
| 1155 | }); | ||
| 1156 | /**@type {HTMLElement}*/ | ||
| 1157 | const minimizeBtn = dom.querySelector('#qr--modal-minimize'); | ||
| 1158 | minimizeBtn.addEventListener('click', ()=>{ | ||
| 1159 | this.editorDom.classList.add('qr--minimized'); | ||
| 1160 | }); | ||
| 1161 | const maximizeBtn = dom.querySelector('#qr--modal-maximize'); | ||
| 1162 | maximizeBtn.addEventListener('click', ()=>{ | ||
| 1163 | this.editorDom.classList.remove('qr--minimized'); | ||
| 1164 | }); | ||
| 1165 | /**@type {boolean}*/ | ||
| 1166 | let isResizing = false; | ||
| 1167 | let resizeStart; | ||
| 1168 | let wStart; | ||
| 1169 | /**@type {HTMLElement}*/ | ||
| 1170 | const resizeHandle = dom.querySelector('#qr--resizeHandle'); | ||
| 1171 | resizeHandle.addEventListener('pointerdown', (evt)=>{ | ||
| 1172 | if (isResizing) return; | ||
| 1173 | isResizing = true; | ||
| 1174 | evt.preventDefault(); | ||
| 1175 | resizeStart = evt.x; | ||
| 1176 | wStart = dom.querySelector('#qr--qrOptions').offsetWidth; | ||
| 1177 | const dragListener = debounce((evt)=>{ | ||
| 1178 | const w = wStart + resizeStart - evt.x; | ||
| 1179 | dom.querySelector('#qr--qrOptions').style.setProperty('--width', `${w}px`); | ||
| 1180 | }, 5); | ||
| 1181 | window.addEventListener('pointerup', ()=>{ | ||
| 1182 | window.removeEventListener('pointermove', dragListener); | ||
| 1183 | isResizing = false; | ||
| 1184 | }, { once:true }); | ||
| 1185 | window.addEventListener('pointermove', dragListener); | ||
| 1186 | }); | ||
| 1187 | |||
| 539 | await popupResult; | 1188 | await popupResult; |
| 540 | 1189 | ||
| 541 | window.removeEventListener('resize', resizeListener); | 1190 | window.removeEventListener('resize', resizeListener); |
| @@ -544,8 +1193,62 @@ export class QuickReply { | |||
| 544 | } | 1193 | } |
| 545 | } | 1194 | } |
| 546 | 1195 | ||
| 1196 | getEditorPosition(start, end, message = null) { | ||
| 1197 | const inputRect = this.editorMessage.getBoundingClientRect(); | ||
| 1198 | const style = window.getComputedStyle(this.editorMessage); | ||
| 1199 | if (!this.clone) { | ||
| 1200 | this.clone = document.createElement('div'); | ||
| 1201 | for (const key of style) { | ||
| 1202 | this.clone.style[key] = style[key]; | ||
| 1203 | } | ||
| 1204 | this.clone.style.position = 'fixed'; | ||
| 1205 | this.clone.style.visibility = 'hidden'; | ||
| 1206 | const mo = new MutationObserver(muts=>{ | ||
| 1207 | if (muts.find(it=>[...it.removedNodes].includes(this.editorMessage) || [...it.removedNodes].find(n=>n.contains(this.editorMessage)))) { | ||
| 1208 | this.clone?.remove(); | ||
| 1209 | this.clone = null; | ||
| 1210 | } | ||
| 1211 | }); | ||
| 1212 | mo.observe(document.body, { childList:true }); | ||
| 1213 | } | ||
| 1214 | document.body.append(this.clone); | ||
| 1215 | this.clone.style.width = `${inputRect.width}px`; | ||
| 1216 | this.clone.style.height = `${inputRect.height}px`; | ||
| 1217 | this.clone.style.left = `${inputRect.left}px`; | ||
| 1218 | this.clone.style.top = `${inputRect.top}px`; | ||
| 1219 | this.clone.style.whiteSpace = style.whiteSpace; | ||
| 1220 | this.clone.style.tabSize = style.tabSize; | ||
| 1221 | const text = message ?? this.editorMessage.value; | ||
| 1222 | const before = text.slice(0, start); | ||
| 1223 | this.clone.textContent = before; | ||
| 1224 | const locator = document.createElement('span'); | ||
| 1225 | locator.textContent = text.slice(start, end); | ||
| 1226 | this.clone.append(locator); | ||
| 1227 | this.clone.append(text.slice(end)); | ||
| 1228 | this.clone.scrollTop = this.editorSyntax.scrollTop; | ||
| 1229 | this.clone.scrollLeft = this.editorSyntax.scrollLeft; | ||
| 1230 | const locatorRect = locator.getBoundingClientRect(); | ||
| 1231 | const bodyRect = document.body.getBoundingClientRect(); | ||
| 1232 | const location = { | ||
| 1233 | left: locatorRect.left - bodyRect.left, | ||
| 1234 | right: locatorRect.right - bodyRect.left, | ||
| 1235 | top: locatorRect.top - bodyRect.top, | ||
| 1236 | bottom: locatorRect.bottom - bodyRect.top, | ||
| 1237 | }; | ||
| 1238 | // this.clone.remove(); | ||
| 1239 | return location; | ||
| 1240 | } | ||
| 547 | async executeFromEditor() { | 1241 | async executeFromEditor() { |
| 548 | if (this.editorExecutePromise) return; | 1242 | if (this.isExecuting) return; |
| 1243 | this.editorPopup.onClosing = ()=>false; | ||
| 1244 | const uuidCheck = /^[0-9a-z]{8}(-[0-9a-z]{4}){3}-[0-9a-z]{12}$/; | ||
| 1245 | const oText = this.message; | ||
| 1246 | this.isExecuting = true; | ||
| 1247 | this.editorDom.classList.add('qr--isExecuting'); | ||
| 1248 | const noSyntax = this.editorDom.querySelector('#qr--modal-messageHolder').classList.contains('qr--noSyntax'); | ||
| 1249 | if (noSyntax) { | ||
| 1250 | this.editorDom.querySelector('#qr--modal-messageHolder').classList.remove('qr--noSyntax'); | ||
| 1251 | } | ||
| 549 | this.editorExecuteBtn.classList.add('qr--busy'); | 1252 | this.editorExecuteBtn.classList.add('qr--busy'); |
| 550 | this.editorExecuteProgress.style.setProperty('--prog', '0'); | 1253 | this.editorExecuteProgress.style.setProperty('--prog', '0'); |
| 551 | this.editorExecuteErrors.classList.remove('qr--hasErrors'); | 1254 | this.editorExecuteErrors.classList.remove('qr--hasErrors'); |
| @@ -556,12 +1259,451 @@ export class QuickReply { | |||
| 556 | this.editorExecuteProgress.classList.remove('qr--aborted'); | 1259 | this.editorExecuteProgress.classList.remove('qr--aborted'); |
| 557 | this.editorExecuteErrors.innerHTML = ''; | 1260 | this.editorExecuteErrors.innerHTML = ''; |
| 558 | this.editorExecuteResult.innerHTML = ''; | 1261 | this.editorExecuteResult.innerHTML = ''; |
| 559 | if (this.editorExecuteHide.checked) { | 1262 | const syntax = this.editorDom.querySelector('#qr--modal-messageSyntaxInner'); |
| 560 | this.editorPopup.dlg.classList.add('qr--hide'); | 1263 | const updateScroll = (evt) => { |
| 561 | } | 1264 | let left = syntax.scrollLeft; |
| 1265 | let top = syntax.scrollTop; | ||
| 1266 | if (evt) { | ||
| 1267 | evt.preventDefault(); | ||
| 1268 | left = syntax.scrollLeft + evt.deltaX; | ||
| 1269 | top = syntax.scrollTop + evt.deltaY; | ||
| 1270 | syntax.scrollTo({ | ||
| 1271 | behavior: 'instant', | ||
| 1272 | left, | ||
| 1273 | top, | ||
| 1274 | }); | ||
| 1275 | } | ||
| 1276 | this.editorMessage.scrollTo({ | ||
| 1277 | behavior: 'instant', | ||
| 1278 | left, | ||
| 1279 | top, | ||
| 1280 | }); | ||
| 1281 | }; | ||
| 1282 | const updateScrollDebounced = updateScroll; | ||
| 1283 | syntax.addEventListener('wheel', (evt)=>{ | ||
| 1284 | updateScrollDebounced(evt); | ||
| 1285 | }); | ||
| 1286 | syntax.addEventListener('scroll', (evt)=>{ | ||
| 1287 | updateScrollDebounced(); | ||
| 1288 | }); | ||
| 562 | try { | 1289 | try { |
| 563 | this.editorExecutePromise = this.execute({}, true); | 1290 | this.abortController = new SlashCommandAbortController(); |
| 564 | const result = await this.editorExecutePromise; | 1291 | this.debugController = new SlashCommandDebugController(); |
| 1292 | this.debugController.onBreakPoint = async(closure, executor)=>{ | ||
| 1293 | this.editorDom.classList.add('qr--isPaused'); | ||
| 1294 | syntax.innerHTML = hljs.highlight(`${closure.fullText}${closure.fullText.slice(-1) == '\n' ? ' ' : ''}`, { language:'stscript', ignoreIllegals:true })?.value; | ||
| 1295 | this.editorMessageLabel.innerHTML = ''; | ||
| 1296 | if (uuidCheck.test(closure.source)) { | ||
| 1297 | const p0 = document.createElement('span'); { | ||
| 1298 | p0.textContent = 'anonymous: '; | ||
| 1299 | this.editorMessageLabel.append(p0); | ||
| 1300 | } | ||
| 1301 | const p1 = document.createElement('strong'); { | ||
| 1302 | p1.textContent = executor.source.slice(0,5); | ||
| 1303 | this.editorMessageLabel.append(p1); | ||
| 1304 | } | ||
| 1305 | const p2 = document.createElement('span'); { | ||
| 1306 | p2.textContent = executor.source.slice(5, -5); | ||
| 1307 | this.editorMessageLabel.append(p2); | ||
| 1308 | } | ||
| 1309 | const p3 = document.createElement('strong'); { | ||
| 1310 | p3.textContent = executor.source.slice(-5); | ||
| 1311 | this.editorMessageLabel.append(p3); | ||
| 1312 | } | ||
| 1313 | } else { | ||
| 1314 | this.editorMessageLabel.textContent = executor.source; | ||
| 1315 | } | ||
| 1316 | const source = closure.source; | ||
| 1317 | this.editorDebugState.innerHTML = ''; | ||
| 1318 | let ci = -1; | ||
| 1319 | const varNames = []; | ||
| 1320 | const macroNames = []; | ||
| 1321 | /** | ||
| 1322 | * @param {SlashCommandScope} scope | ||
| 1323 | */ | ||
| 1324 | const buildVars = (scope, isCurrent = false)=>{ | ||
| 1325 | if (!isCurrent) { | ||
| 1326 | ci--; | ||
| 1327 | } | ||
| 1328 | const c = this.debugController.stack.slice(ci)[0]; | ||
| 1329 | const wrap = document.createElement('div'); { | ||
| 1330 | wrap.classList.add('qr--scope'); | ||
| 1331 | if (isCurrent) { | ||
| 1332 | const executor = this.debugController.cmdStack.slice(-1)[0]; | ||
| 1333 | { // named args | ||
| 1334 | const namedTitle = document.createElement('div'); { | ||
| 1335 | namedTitle.classList.add('qr--title'); | ||
| 1336 | namedTitle.textContent = `Named Args - /${executor.name}`; | ||
| 1337 | if (executor.command.name == 'run') { | ||
| 1338 | namedTitle.textContent += `${(executor.name == ':' ? '' : ' ')}${executor.unnamedArgumentList[0]?.value}`; | ||
| 1339 | } | ||
| 1340 | wrap.append(namedTitle); | ||
| 1341 | } | ||
| 1342 | const keys = new Set([...Object.keys(this.debugController.namedArguments ?? {}), ...(executor.namedArgumentList ?? []).map(it=>it.name)]); | ||
| 1343 | for (const key of keys) { | ||
| 1344 | if (key[0] == '_') continue; | ||
| 1345 | const item = document.createElement('div'); { | ||
| 1346 | item.classList.add('qr--var'); | ||
| 1347 | const k = document.createElement('div'); { | ||
| 1348 | k.classList.add('qr--key'); | ||
| 1349 | k.textContent = key; | ||
| 1350 | item.append(k); | ||
| 1351 | } | ||
| 1352 | const vUnresolved = document.createElement('div'); { | ||
| 1353 | vUnresolved.classList.add('qr--val'); | ||
| 1354 | vUnresolved.classList.add('qr--singleCol'); | ||
| 1355 | const val = executor.namedArgumentList.find(it=>it.name == key)?.value; | ||
| 1356 | if (val instanceof SlashCommandClosure) { | ||
| 1357 | vUnresolved.classList.add('qr--closure'); | ||
| 1358 | vUnresolved.title = val.rawText; | ||
| 1359 | vUnresolved.textContent = val.toString(); | ||
| 1360 | } else if (val === undefined) { | ||
| 1361 | vUnresolved.classList.add('qr--undefined'); | ||
| 1362 | vUnresolved.textContent = 'undefined'; | ||
| 1363 | } else { | ||
| 1364 | let jsonVal; | ||
| 1365 | try { jsonVal = JSON.parse(val); } catch { /* empty */ } | ||
| 1366 | if (jsonVal && typeof jsonVal == 'object') { | ||
| 1367 | vUnresolved.textContent = JSON.stringify(jsonVal, null, 2); | ||
| 1368 | } else { | ||
| 1369 | vUnresolved.textContent = val; | ||
| 1370 | vUnresolved.classList.add('qr--simple'); | ||
| 1371 | } | ||
| 1372 | } | ||
| 1373 | item.append(vUnresolved); | ||
| 1374 | } | ||
| 1375 | const vResolved = document.createElement('div'); { | ||
| 1376 | vResolved.classList.add('qr--val'); | ||
| 1377 | vResolved.classList.add('qr--singleCol'); | ||
| 1378 | if (this.debugController.namedArguments === undefined) { | ||
| 1379 | vResolved.classList.add('qr--unresolved'); | ||
| 1380 | } else { | ||
| 1381 | const val = this.debugController.namedArguments?.[key]; | ||
| 1382 | if (val instanceof SlashCommandClosure) { | ||
| 1383 | vResolved.classList.add('qr--closure'); | ||
| 1384 | vResolved.title = val.rawText; | ||
| 1385 | vResolved.textContent = val.toString(); | ||
| 1386 | } else if (val === undefined) { | ||
| 1387 | vResolved.classList.add('qr--undefined'); | ||
| 1388 | vResolved.textContent = 'undefined'; | ||
| 1389 | } else { | ||
| 1390 | let jsonVal; | ||
| 1391 | try { jsonVal = JSON.parse(val); } catch { /* empty */ } | ||
| 1392 | if (jsonVal && typeof jsonVal == 'object') { | ||
| 1393 | vResolved.textContent = JSON.stringify(jsonVal, null, 2); | ||
| 1394 | } else { | ||
| 1395 | vResolved.textContent = val; | ||
| 1396 | vResolved.classList.add('qr--simple'); | ||
| 1397 | } | ||
| 1398 | } | ||
| 1399 | } | ||
| 1400 | item.append(vResolved); | ||
| 1401 | } | ||
| 1402 | wrap.append(item); | ||
| 1403 | } | ||
| 1404 | } | ||
| 1405 | } | ||
| 1406 | { // unnamed args | ||
| 1407 | const unnamedTitle = document.createElement('div'); { | ||
| 1408 | unnamedTitle.classList.add('qr--title'); | ||
| 1409 | unnamedTitle.textContent = `Unnamed Args - /${executor.name}`; | ||
| 1410 | if (executor.command.name == 'run') { | ||
| 1411 | unnamedTitle.textContent += `${(executor.name == ':' ? '' : ' ')}${executor.unnamedArgumentList[0]?.value}`; | ||
| 1412 | } | ||
| 1413 | wrap.append(unnamedTitle); | ||
| 1414 | } | ||
| 1415 | let i = 0; | ||
| 1416 | let unnamed = this.debugController.unnamedArguments ?? []; | ||
| 1417 | if (!Array.isArray(unnamed)) unnamed = [unnamed]; | ||
| 1418 | while (unnamed.length < executor.unnamedArgumentList?.length ?? 0) unnamed.push(undefined); | ||
| 1419 | unnamed = unnamed.map((it,idx)=>[executor.unnamedArgumentList?.[idx], it]); | ||
| 1420 | for (const arg of unnamed) { | ||
| 1421 | i++; | ||
| 1422 | const item = document.createElement('div'); { | ||
| 1423 | item.classList.add('qr--var'); | ||
| 1424 | const k = document.createElement('div'); { | ||
| 1425 | k.classList.add('qr--key'); | ||
| 1426 | k.textContent = i.toString(); | ||
| 1427 | item.append(k); | ||
| 1428 | } | ||
| 1429 | const vUnresolved = document.createElement('div'); { | ||
| 1430 | vUnresolved.classList.add('qr--val'); | ||
| 1431 | vUnresolved.classList.add('qr--singleCol'); | ||
| 1432 | const val = arg[0]?.value; | ||
| 1433 | if (val instanceof SlashCommandClosure) { | ||
| 1434 | vUnresolved.classList.add('qr--closure'); | ||
| 1435 | vUnresolved.title = val.rawText; | ||
| 1436 | vUnresolved.textContent = val.toString(); | ||
| 1437 | } else if (val === undefined) { | ||
| 1438 | vUnresolved.classList.add('qr--undefined'); | ||
| 1439 | vUnresolved.textContent = 'undefined'; | ||
| 1440 | } else { | ||
| 1441 | let jsonVal; | ||
| 1442 | try { jsonVal = JSON.parse(val); } catch { /* empty */ } | ||
| 1443 | if (jsonVal && typeof jsonVal == 'object') { | ||
| 1444 | vUnresolved.textContent = JSON.stringify(jsonVal, null, 2); | ||
| 1445 | } else { | ||
| 1446 | vUnresolved.textContent = val; | ||
| 1447 | vUnresolved.classList.add('qr--simple'); | ||
| 1448 | } | ||
| 1449 | } | ||
| 1450 | item.append(vUnresolved); | ||
| 1451 | } | ||
| 1452 | const vResolved = document.createElement('div'); { | ||
| 1453 | vResolved.classList.add('qr--val'); | ||
| 1454 | vResolved.classList.add('qr--singleCol'); | ||
| 1455 | if (this.debugController.unnamedArguments === undefined) { | ||
| 1456 | vResolved.classList.add('qr--unresolved'); | ||
| 1457 | } else if ((Array.isArray(this.debugController.unnamedArguments) ? this.debugController.unnamedArguments : [this.debugController.unnamedArguments]).length < i) { | ||
| 1458 | // do nothing | ||
| 1459 | } else { | ||
| 1460 | const val = arg[1]; | ||
| 1461 | if (val instanceof SlashCommandClosure) { | ||
| 1462 | vResolved.classList.add('qr--closure'); | ||
| 1463 | vResolved.title = val.rawText; | ||
| 1464 | vResolved.textContent = val.toString(); | ||
| 1465 | } else if (val === undefined) { | ||
| 1466 | vResolved.classList.add('qr--undefined'); | ||
| 1467 | vResolved.textContent = 'undefined'; | ||
| 1468 | } else { | ||
| 1469 | let jsonVal; | ||
| 1470 | try { jsonVal = JSON.parse(val); } catch { /* empty */ } | ||
| 1471 | if (jsonVal && typeof jsonVal == 'object') { | ||
| 1472 | vResolved.textContent = JSON.stringify(jsonVal, null, 2); | ||
| 1473 | } else { | ||
| 1474 | vResolved.textContent = val; | ||
| 1475 | vResolved.classList.add('qr--simple'); | ||
| 1476 | } | ||
| 1477 | } | ||
| 1478 | } | ||
| 1479 | item.append(vResolved); | ||
| 1480 | } | ||
| 1481 | wrap.append(item); | ||
| 1482 | } | ||
| 1483 | } | ||
| 1484 | } | ||
| 1485 | } | ||
| 1486 | // current scope | ||
| 1487 | const title = document.createElement('div'); { | ||
| 1488 | title.classList.add('qr--title'); | ||
| 1489 | title.textContent = isCurrent ? 'Current Scope' : 'Parent Scope'; | ||
| 1490 | if (c.source == source) { | ||
| 1491 | let hi; | ||
| 1492 | title.addEventListener('pointerenter', ()=>{ | ||
| 1493 | const loc = this.getEditorPosition(Math.max(0, c.executorList[0].start - 1), c.executorList.slice(-1)[0].end, c.fullText); | ||
| 1494 | const layer = syntax.getBoundingClientRect(); | ||
| 1495 | hi = document.createElement('div'); | ||
| 1496 | hi.classList.add('qr--highlight-secondary'); | ||
| 1497 | hi.style.left = `${loc.left - layer.left}px`; | ||
| 1498 | hi.style.width = `${loc.right - loc.left}px`; | ||
| 1499 | hi.style.top = `${loc.top - layer.top + syntax.scrollTop}px`; | ||
| 1500 | hi.style.height = `${loc.bottom - loc.top}px`; | ||
| 1501 | syntax.append(hi); | ||
| 1502 | }); | ||
| 1503 | title.addEventListener('pointerleave', ()=>hi?.remove()); | ||
| 1504 | } | ||
| 1505 | wrap.append(title); | ||
| 1506 | } | ||
| 1507 | for (const key of Object.keys(scope.variables)) { | ||
| 1508 | const isHidden = varNames.includes(key); | ||
| 1509 | if (!isHidden) varNames.push(key); | ||
| 1510 | const item = document.createElement('div'); { | ||
| 1511 | item.classList.add('qr--var'); | ||
| 1512 | if (isHidden) item.classList.add('qr--isHidden'); | ||
| 1513 | const k = document.createElement('div'); { | ||
| 1514 | k.classList.add('qr--key'); | ||
| 1515 | k.textContent = key; | ||
| 1516 | item.append(k); | ||
| 1517 | } | ||
| 1518 | const v = document.createElement('div'); { | ||
| 1519 | v.classList.add('qr--val'); | ||
| 1520 | const val = scope.variables[key]; | ||
| 1521 | if (val instanceof SlashCommandClosure) { | ||
| 1522 | v.classList.add('qr--closure'); | ||
| 1523 | v.title = val.rawText; | ||
| 1524 | v.textContent = val.toString(); | ||
| 1525 | } else if (val === undefined) { | ||
| 1526 | v.classList.add('qr--undefined'); | ||
| 1527 | v.textContent = 'undefined'; | ||
| 1528 | } else { | ||
| 1529 | let jsonVal; | ||
| 1530 | try { jsonVal = JSON.parse(val); } catch { /* empty */ } | ||
| 1531 | if (jsonVal && typeof jsonVal == 'object') { | ||
| 1532 | v.textContent = JSON.stringify(jsonVal, null, 2); | ||
| 1533 | } else { | ||
| 1534 | v.textContent = val; | ||
| 1535 | v.classList.add('qr--simple'); | ||
| 1536 | } | ||
| 1537 | } | ||
| 1538 | item.append(v); | ||
| 1539 | } | ||
| 1540 | wrap.append(item); | ||
| 1541 | } | ||
| 1542 | } | ||
| 1543 | for (const key of Object.keys(scope.macros)) { | ||
| 1544 | const isHidden = macroNames.includes(key); | ||
| 1545 | if (!isHidden) macroNames.push(key); | ||
| 1546 | const item = document.createElement('div'); { | ||
| 1547 | item.classList.add('qr--macro'); | ||
| 1548 | if (isHidden) item.classList.add('qr--isHidden'); | ||
| 1549 | const k = document.createElement('div'); { | ||
| 1550 | k.classList.add('qr--key'); | ||
| 1551 | k.textContent = key; | ||
| 1552 | item.append(k); | ||
| 1553 | } | ||
| 1554 | const v = document.createElement('div'); { | ||
| 1555 | v.classList.add('qr--val'); | ||
| 1556 | const val = scope.macros[key]; | ||
| 1557 | if (val instanceof SlashCommandClosure) { | ||
| 1558 | v.classList.add('qr--closure'); | ||
| 1559 | v.title = val.rawText; | ||
| 1560 | v.textContent = val.toString(); | ||
| 1561 | } else if (val === undefined) { | ||
| 1562 | v.classList.add('qr--undefined'); | ||
| 1563 | v.textContent = 'undefined'; | ||
| 1564 | } else { | ||
| 1565 | let jsonVal; | ||
| 1566 | try { jsonVal = JSON.parse(val); } catch { /* empty */ } | ||
| 1567 | if (jsonVal && typeof jsonVal == 'object') { | ||
| 1568 | v.textContent = JSON.stringify(jsonVal, null, 2); | ||
| 1569 | } else { | ||
| 1570 | v.textContent = val; | ||
| 1571 | v.classList.add('qr--simple'); | ||
| 1572 | } | ||
| 1573 | } | ||
| 1574 | item.append(v); | ||
| 1575 | } | ||
| 1576 | wrap.append(item); | ||
| 1577 | } | ||
| 1578 | } | ||
| 1579 | const pipeItem = document.createElement('div'); { | ||
| 1580 | pipeItem.classList.add('qr--pipe'); | ||
| 1581 | const k = document.createElement('div'); { | ||
| 1582 | k.classList.add('qr--key'); | ||
| 1583 | k.textContent = 'pipe'; | ||
| 1584 | pipeItem.append(k); | ||
| 1585 | } | ||
| 1586 | const v = document.createElement('div'); { | ||
| 1587 | v.classList.add('qr--val'); | ||
| 1588 | const val = scope.pipe; | ||
| 1589 | if (val instanceof SlashCommandClosure) { | ||
| 1590 | v.classList.add('qr--closure'); | ||
| 1591 | v.title = val.rawText; | ||
| 1592 | v.textContent = val.toString(); | ||
| 1593 | } else if (val === undefined) { | ||
| 1594 | v.classList.add('qr--undefined'); | ||
| 1595 | v.textContent = 'undefined'; | ||
| 1596 | } else { | ||
| 1597 | let jsonVal; | ||
| 1598 | try { jsonVal = JSON.parse(val); } catch { /* empty */ } | ||
| 1599 | if (jsonVal && typeof jsonVal == 'object') { | ||
| 1600 | v.textContent = JSON.stringify(jsonVal, null, 2); | ||
| 1601 | } else { | ||
| 1602 | v.textContent = val; | ||
| 1603 | v.classList.add('qr--simple'); | ||
| 1604 | } | ||
| 1605 | } | ||
| 1606 | pipeItem.append(v); | ||
| 1607 | } | ||
| 1608 | wrap.append(pipeItem); | ||
| 1609 | } | ||
| 1610 | if (scope.parent) { | ||
| 1611 | wrap.append(buildVars(scope.parent)); | ||
| 1612 | } | ||
| 1613 | } | ||
| 1614 | return wrap; | ||
| 1615 | }; | ||
| 1616 | const buildStack = ()=>{ | ||
| 1617 | const wrap = document.createElement('div'); { | ||
| 1618 | wrap.classList.add('qr--stack'); | ||
| 1619 | const title = document.createElement('div'); { | ||
| 1620 | title.classList.add('qr--title'); | ||
| 1621 | title.textContent = 'Call Stack'; | ||
| 1622 | wrap.append(title); | ||
| 1623 | } | ||
| 1624 | let ei = -1; | ||
| 1625 | for (const executor of this.debugController.cmdStack.toReversed()) { | ||
| 1626 | ei++; | ||
| 1627 | const c = this.debugController.stack.toReversed()[ei]; | ||
| 1628 | const item = document.createElement('div'); { | ||
| 1629 | item.classList.add('qr--item'); | ||
| 1630 | if (executor.source == source) { | ||
| 1631 | let hi; | ||
| 1632 | item.addEventListener('pointerenter', ()=>{ | ||
| 1633 | const loc = this.getEditorPosition(Math.max(0, executor.start - 1), executor.end, c.fullText); | ||
| 1634 | const layer = syntax.getBoundingClientRect(); | ||
| 1635 | hi = document.createElement('div'); | ||
| 1636 | hi.classList.add('qr--highlight-secondary'); | ||
| 1637 | hi.style.left = `${loc.left - layer.left}px`; | ||
| 1638 | hi.style.width = `${loc.right - loc.left}px`; | ||
| 1639 | hi.style.top = `${loc.top - layer.top + syntax.scrollTop}px`; | ||
| 1640 | hi.style.height = `${loc.bottom - loc.top}px`; | ||
| 1641 | syntax.append(hi); | ||
| 1642 | }); | ||
| 1643 | item.addEventListener('pointerleave', ()=>hi?.remove()); | ||
| 1644 | } | ||
| 1645 | const cmd = document.createElement('div'); { | ||
| 1646 | cmd.classList.add('qr--cmd'); | ||
| 1647 | cmd.textContent = `/${executor.name}`; | ||
| 1648 | if (executor.command.name == 'run') { | ||
| 1649 | cmd.textContent += `${(executor.name == ':' ? '' : ' ')}${executor.unnamedArgumentList[0]?.value}`; | ||
| 1650 | } | ||
| 1651 | item.append(cmd); | ||
| 1652 | } | ||
| 1653 | const src = document.createElement('div'); { | ||
| 1654 | src.classList.add('qr--source'); | ||
| 1655 | const line = closure.fullText.slice(0, executor.start).split('\n').length; | ||
| 1656 | if (uuidCheck.test(executor.source)) { | ||
| 1657 | const p1 = document.createElement('span'); { | ||
| 1658 | p1.classList.add('qr--fixed'); | ||
| 1659 | p1.textContent = executor.source.slice(0,5); | ||
| 1660 | src.append(p1); | ||
| 1661 | } | ||
| 1662 | const p2 = document.createElement('span'); { | ||
| 1663 | p2.classList.add('qr--truncated'); | ||
| 1664 | p2.textContent = '…'; | ||
| 1665 | src.append(p2); | ||
| 1666 | } | ||
| 1667 | const p3 = document.createElement('span'); { | ||
| 1668 | p3.classList.add('qr--fixed'); | ||
| 1669 | p3.textContent = `${executor.source.slice(-5)}:${line}`; | ||
| 1670 | src.append(p3); | ||
| 1671 | } | ||
| 1672 | src.title = `anonymous: ${executor.source}`; | ||
| 1673 | } else { | ||
| 1674 | src.textContent = `${executor.source}:${line}`; | ||
| 1675 | } | ||
| 1676 | item.append(src); | ||
| 1677 | } | ||
| 1678 | wrap.append(item); | ||
| 1679 | } | ||
| 1680 | } | ||
| 1681 | } | ||
| 1682 | return wrap; | ||
| 1683 | }; | ||
| 1684 | this.editorDebugState.append(buildVars(closure.scope, true)); | ||
| 1685 | this.editorDebugState.append(buildStack()); | ||
| 1686 | this.editorDebugState.classList.add('qr--active'); | ||
| 1687 | const loc = this.getEditorPosition(Math.max(0, executor.start - 1), executor.end, closure.fullText); | ||
| 1688 | const layer = syntax.getBoundingClientRect(); | ||
| 1689 | const hi = document.createElement('div'); | ||
| 1690 | hi.classList.add('qr--highlight'); | ||
| 1691 | if (this.debugController.namedArguments === undefined) { | ||
| 1692 | hi.classList.add('qr--unresolved'); | ||
| 1693 | } | ||
| 1694 | hi.style.left = `${loc.left - layer.left}px`; | ||
| 1695 | hi.style.width = `${loc.right - loc.left}px`; | ||
| 1696 | hi.style.top = `${loc.top - layer.top + syntax.scrollTop}px`; | ||
| 1697 | hi.style.height = `${loc.bottom - loc.top}px`; | ||
| 1698 | syntax.append(hi); | ||
| 1699 | const isStepping = await this.debugController.awaitContinue(); | ||
| 1700 | hi.remove(); | ||
| 1701 | this.editorDebugState.textContent = ''; | ||
| 1702 | this.editorDebugState.classList.remove('qr--active'); | ||
| 1703 | this.editorDom.classList.remove('qr--isPaused'); | ||
| 1704 | return isStepping; | ||
| 1705 | }; | ||
| 1706 | const result = await this.onDebug(this); | ||
| 565 | if (this.abortController?.signal?.aborted) { | 1707 | if (this.abortController?.signal?.aborted) { |
| 566 | this.editorExecuteProgress.classList.add('qr--aborted'); | 1708 | this.editorExecuteProgress.classList.add('qr--aborted'); |
| 567 | } else { | 1709 | } else { |
| @@ -586,9 +1728,18 @@ export class QuickReply { | |||
| 586 | `; | 1728 | `; |
| 587 | } | 1729 | } |
| 588 | } | 1730 | } |
| 1731 | if (noSyntax) { | ||
| 1732 | this.editorDom.querySelector('#qr--modal-messageHolder').classList.add('qr--noSyntax'); | ||
| 1733 | } | ||
| 1734 | this.editorMessageLabel.innerHTML = ''; | ||
| 1735 | this.editorMessageLabel.textContent = 'Message / Command: '; | ||
| 1736 | this.editorMessage.value = oText; | ||
| 1737 | this.editorMessage.dispatchEvent(new Event('input', { bubbles:true })); | ||
| 589 | this.editorExecutePromise = null; | 1738 | this.editorExecutePromise = null; |
| 590 | this.editorExecuteBtn.classList.remove('qr--busy'); | 1739 | this.editorExecuteBtn.classList.remove('qr--busy'); |
| 591 | this.editorPopup.dlg.classList.remove('qr--hide'); | 1740 | this.editorDom.classList.remove('qr--isExecuting'); |
| 1741 | this.isExecuting = false; | ||
| 1742 | this.editorPopup.onClosing = null; | ||
| 592 | } | 1743 | } |
| 593 | 1744 | ||
| 594 | updateEditorProgress(done, total) { | 1745 | updateEditorProgress(done, total) { |
| @@ -623,6 +1774,47 @@ export class QuickReply { | |||
| 623 | /** | 1774 | /** |
| 624 | * @param {string} value | 1775 | * @param {string} value |
| 625 | */ | 1776 | */ |
| 1777 | updateIcon(value) { | ||
| 1778 | if (this.onUpdate) { | ||
| 1779 | if (value === null) return; | ||
| 1780 | if (this.settingsDomIcon) { | ||
| 1781 | if (this.icon != value) { | ||
| 1782 | if (value == '') { | ||
| 1783 | if (this.icon) { | ||
| 1784 | this.settingsDomIcon.classList.remove(this.icon); | ||
| 1785 | } | ||
| 1786 | this.settingsDomIcon.textContent = '…'; | ||
| 1787 | this.settingsDomIcon.classList.remove('fa-solid'); | ||
| 1788 | } else { | ||
| 1789 | if (this.icon) { | ||
| 1790 | this.settingsDomIcon.classList.remove(this.icon); | ||
| 1791 | } else { | ||
| 1792 | this.settingsDomIcon.classList.add('fa-solid'); | ||
| 1793 | } | ||
| 1794 | this.settingsDomIcon.classList.add(value); | ||
| 1795 | } | ||
| 1796 | } | ||
| 1797 | } | ||
| 1798 | this.icon = value; | ||
| 1799 | this.updateRender(); | ||
| 1800 | this.onUpdate(this); | ||
| 1801 | } | ||
| 1802 | } | ||
| 1803 | |||
| 1804 | /** | ||
| 1805 | * @param {boolean} value | ||
| 1806 | */ | ||
| 1807 | updateShowLabel(value) { | ||
| 1808 | if (this.onUpdate) { | ||
| 1809 | this.showLabel = value; | ||
| 1810 | this.updateRender(); | ||
| 1811 | this.onUpdate(this); | ||
| 1812 | } | ||
| 1813 | } | ||
| 1814 | |||
| 1815 | /** | ||
| 1816 | * @param {string} value | ||
| 1817 | */ | ||
| 626 | updateLabel(value) { | 1818 | updateLabel(value) { |
| 627 | if (this.onUpdate) { | 1819 | if (this.onUpdate) { |
| 628 | if (this.settingsDomLabel && this.settingsDomLabel.value != value) { | 1820 | if (this.settingsDomLabel && this.settingsDomLabel.value != value) { |
| @@ -670,21 +1862,25 @@ export class QuickReply { | |||
| 670 | } | 1862 | } |
| 671 | 1863 | ||
| 672 | 1864 | ||
| 673 | async execute(args = {}, isEditor = false, isRun = false) { | 1865 | async execute(args = {}, isEditor = false, isRun = false, options = {}) { |
| 674 | if (this.message?.length > 0 && this.onExecute) { | 1866 | if (this.message?.length > 0 && this.onExecute) { |
| 675 | const scope = new SlashCommandScope(); | 1867 | const scope = new SlashCommandScope(); |
| 676 | for (const key of Object.keys(args)) { | 1868 | for (const key of Object.keys(args)) { |
| 1869 | if (key[0] == '_') continue; | ||
| 1870 | if (key == 'isAutoExecute') continue; | ||
| 677 | scope.setMacro(`arg::${key}`, args[key]); | 1871 | scope.setMacro(`arg::${key}`, args[key]); |
| 678 | } | 1872 | } |
| 1873 | scope.setMacro('arg::*', ''); | ||
| 679 | if (isEditor) { | 1874 | if (isEditor) { |
| 680 | this.abortController = new SlashCommandAbortController(); | 1875 | this.abortController = new SlashCommandAbortController(); |
| 681 | } | 1876 | } |
| 682 | return await this.onExecute(this, { | 1877 | return await this.onExecute(this, { |
| 683 | message:this.message, | 1878 | message: this.message, |
| 684 | isAutoExecute: args.isAutoExecute ?? false, | 1879 | isAutoExecute: args.isAutoExecute ?? false, |
| 685 | isEditor, | 1880 | isEditor, |
| 686 | isRun, | 1881 | isRun, |
| 687 | scope, | 1882 | scope, |
| 1883 | executionOptions: options, | ||
| 688 | }); | 1884 | }); |
| 689 | } | 1885 | } |
| 690 | } | 1886 | } |
| @@ -695,6 +1891,8 @@ export class QuickReply { | |||
| 695 | toJSON() { | 1891 | toJSON() { |
| 696 | return { | 1892 | return { |
| 697 | id: this.id, | 1893 | id: this.id, |
| 1894 | icon: this.icon, | ||
| 1895 | showLabel: this.showLabel, | ||
| 698 | label: this.label, | 1896 | label: this.label, |
| 699 | title: this.title, | 1897 | title: this.title, |
| 700 | message: this.message, | 1898 | message: this.message, |
| @@ -60,7 +60,12 @@ export class QuickReplyConfig { | |||
| 60 | /**@type {HTMLElement}*/ | 60 | /**@type {HTMLElement}*/ |
| 61 | this.setListDom = root.querySelector('.qr--setList'); | 61 | this.setListDom = root.querySelector('.qr--setList'); |
| 62 | root.querySelector('.qr--setListAdd').addEventListener('click', ()=>{ | 62 | root.querySelector('.qr--setListAdd').addEventListener('click', ()=>{ |
| 63 | this.addSet(QuickReplySet.list[0]); | 63 | const newSet = QuickReplySet.list.find(qr=>!this.setList.find(qrl=>qrl.set == qr)); |
| 64 | if (newSet) { | ||
| 65 | this.addSet(newSet); | ||
| 66 | } else { | ||
| 67 | toastr.warning('All existing QR Sets have already been added.'); | ||
| 68 | } | ||
| 64 | }); | 69 | }); |
| 65 | this.updateSetListDom(); | 70 | this.updateSetListDom(); |
| 66 | } | 71 | } |
| @@ -1,7 +1,9 @@ | |||
| 1 | import { getRequestHeaders, substituteParams } from '../../../../script.js'; | 1 | import { getRequestHeaders, substituteParams } from '../../../../script.js'; |
| 2 | import { Popup, POPUP_RESULT, POPUP_TYPE } from '../../../popup.js'; | ||
| 2 | import { executeSlashCommands, executeSlashCommandsOnChatInput, executeSlashCommandsWithOptions } from '../../../slash-commands.js'; | 3 | import { executeSlashCommands, executeSlashCommandsOnChatInput, executeSlashCommandsWithOptions } from '../../../slash-commands.js'; |
| 4 | import { SlashCommandParser } from '../../../slash-commands/SlashCommandParser.js'; | ||
| 3 | import { SlashCommandScope } from '../../../slash-commands/SlashCommandScope.js'; | 5 | import { SlashCommandScope } from '../../../slash-commands/SlashCommandScope.js'; |
| 4 | import { debounceAsync, warn } from '../index.js'; | 6 | import { debounceAsync, log, warn } from '../index.js'; |
| 5 | import { QuickReply } from './QuickReply.js'; | 7 | import { QuickReply } from './QuickReply.js'; |
| 6 | 8 | ||
| 7 | export class QuickReplySet { | 9 | export class QuickReplySet { |
| @@ -16,7 +18,7 @@ export class QuickReplySet { | |||
| 16 | } | 18 | } |
| 17 | 19 | ||
| 18 | /** | 20 | /** |
| 19 | * @param {String} name - name of the QuickReplySet | 21 | * @param {string} name - name of the QuickReplySet |
| 20 | */ | 22 | */ |
| 21 | static get(name) { | 23 | static get(name) { |
| 22 | return this.list.find(it=>it.name == name); | 24 | return this.list.find(it=>it.name == name); |
| @@ -25,17 +27,19 @@ export class QuickReplySet { | |||
| 25 | 27 | ||
| 26 | 28 | ||
| 27 | 29 | ||
| 28 | /**@type {String}*/ name; | 30 | /**@type {string}*/ name; |
| 29 | /**@type {Boolean}*/ disableSend = false; | 31 | /**@type {boolean}*/ disableSend = false; |
| 30 | /**@type {Boolean}*/ placeBeforeInput = false; | 32 | /**@type {boolean}*/ placeBeforeInput = false; |
| 31 | /**@type {Boolean}*/ injectInput = false; | 33 | /**@type {boolean}*/ injectInput = false; |
| 34 | /**@type {string}*/ color = 'transparent'; | ||
| 35 | /**@type {boolean}*/ onlyBorderColor = false; | ||
| 32 | /**@type {QuickReply[]}*/ qrList = []; | 36 | /**@type {QuickReply[]}*/ qrList = []; |
| 33 | 37 | ||
| 34 | /**@type {Number}*/ idIndex = 0; | 38 | /**@type {number}*/ idIndex = 0; |
| 35 | 39 | ||
| 36 | /**@type {Boolean}*/ isDeleted = false; | 40 | /**@type {boolean}*/ isDeleted = false; |
| 37 | 41 | ||
| 38 | /**@type {Function}*/ save; | 42 | /**@type {function}*/ save; |
| 39 | 43 | ||
| 40 | /**@type {HTMLElement}*/ dom; | 44 | /**@type {HTMLElement}*/ dom; |
| 41 | /**@type {HTMLElement}*/ settingsDom; | 45 | /**@type {HTMLElement}*/ settingsDom; |
| @@ -64,6 +68,7 @@ export class QuickReplySet { | |||
| 64 | const root = document.createElement('div'); { | 68 | const root = document.createElement('div'); { |
| 65 | this.dom = root; | 69 | this.dom = root; |
| 66 | root.classList.add('qr--buttons'); | 70 | root.classList.add('qr--buttons'); |
| 71 | this.updateColor(); | ||
| 67 | this.qrList.filter(qr=>!qr.isHidden).forEach(qr=>{ | 72 | this.qrList.filter(qr=>!qr.isHidden).forEach(qr=>{ |
| 68 | root.append(qr.render()); | 73 | root.append(qr.render()); |
| 69 | }); | 74 | }); |
| @@ -78,6 +83,22 @@ export class QuickReplySet { | |||
| 78 | this.dom.append(qr.render()); | 83 | this.dom.append(qr.render()); |
| 79 | }); | 84 | }); |
| 80 | } | 85 | } |
| 86 | updateColor() { | ||
| 87 | if (!this.dom) return; | ||
| 88 | if (this.color && this.color != 'transparent') { | ||
| 89 | this.dom.style.setProperty('--qr--color', this.color); | ||
| 90 | this.dom.classList.add('qr--color'); | ||
| 91 | if (this.onlyBorderColor) { | ||
| 92 | this.dom.classList.add('qr--borderColor'); | ||
| 93 | } else { | ||
| 94 | this.dom.classList.remove('qr--borderColor'); | ||
| 95 | } | ||
| 96 | } else { | ||
| 97 | this.dom.style.setProperty('--qr--color', 'transparent'); | ||
| 98 | this.dom.classList.remove('qr--color'); | ||
| 99 | this.dom.classList.remove('qr--borderColor'); | ||
| 100 | } | ||
| 101 | } | ||
| 81 | 102 | ||
| 82 | 103 | ||
| 83 | 104 | ||
| @@ -93,6 +114,11 @@ export class QuickReplySet { | |||
| 93 | } | 114 | } |
| 94 | return this.settingsDom; | 115 | return this.settingsDom; |
| 95 | } | 116 | } |
| 117 | /** | ||
| 118 | * | ||
| 119 | * @param {QuickReply} qr | ||
| 120 | * @param {number} idx | ||
| 121 | */ | ||
| 96 | renderSettingsItem(qr, idx) { | 122 | renderSettingsItem(qr, idx) { |
| 97 | this.settingsDom.append(qr.renderSettings(idx)); | 123 | this.settingsDom.append(qr.renderSettings(idx)); |
| 98 | } | 124 | } |
| @@ -102,6 +128,18 @@ export class QuickReplySet { | |||
| 102 | 128 | ||
| 103 | /** | 129 | /** |
| 104 | * | 130 | * |
| 131 | * @param {QuickReply} qr | ||
| 132 | */ | ||
| 133 | async debug(qr) { | ||
| 134 | const parser = new SlashCommandParser(); | ||
| 135 | const closure = parser.parse(qr.message, true, [], qr.abortController, qr.debugController); | ||
| 136 | closure.source = `${this.name}.${qr.label}`; | ||
| 137 | closure.onProgress = (done, total) => qr.updateEditorProgress(done, total); | ||
| 138 | closure.scope.setMacro('arg::*', ''); | ||
| 139 | return (await closure.execute())?.pipe; | ||
| 140 | } | ||
| 141 | /** | ||
| 142 | * | ||
| 105 | * @param {QuickReply} qr The QR to execute. | 143 | * @param {QuickReply} qr The QR to execute. |
| 106 | * @param {object} options | 144 | * @param {object} options |
| 107 | * @param {string} [options.message] (null) altered message to be used | 145 | * @param {string} [options.message] (null) altered message to be used |
| @@ -109,6 +147,7 @@ export class QuickReplySet { | |||
| 109 | * @param {boolean} [options.isEditor] (false) whether the execution is triggered by the QR editor | 147 | * @param {boolean} [options.isEditor] (false) whether the execution is triggered by the QR editor |
| 110 | * @param {boolean} [options.isRun] (false) whether the execution is triggered by /run or /: (window.executeQuickReplyByName) | 148 | * @param {boolean} [options.isRun] (false) whether the execution is triggered by /run or /: (window.executeQuickReplyByName) |
| 111 | * @param {SlashCommandScope} [options.scope] (null) scope to be used when running the command | 149 | * @param {SlashCommandScope} [options.scope] (null) scope to be used when running the command |
| 150 | * @param {import('../../../slash-commands.js').ExecuteSlashCommandsOptions} [options.executionOptions] ({}) further execution options | ||
| 112 | * @returns | 151 | * @returns |
| 113 | */ | 152 | */ |
| 114 | async executeWithOptions(qr, options = {}) { | 153 | async executeWithOptions(qr, options = {}) { |
| @@ -118,7 +157,9 @@ export class QuickReplySet { | |||
| 118 | isEditor:false, | 157 | isEditor:false, |
| 119 | isRun:false, | 158 | isRun:false, |
| 120 | scope:null, | 159 | scope:null, |
| 160 | executionOptions:{}, | ||
| 121 | }, options); | 161 | }, options); |
| 162 | const execOptions = options.executionOptions; | ||
| 122 | /**@type {HTMLTextAreaElement}*/ | 163 | /**@type {HTMLTextAreaElement}*/ |
| 123 | const ta = document.querySelector('#send_textarea'); | 164 | const ta = document.querySelector('#send_textarea'); |
| 124 | const finalMessage = options.message ?? qr.message; | 165 | const finalMessage = options.message ?? qr.message; |
| @@ -136,21 +177,24 @@ export class QuickReplySet { | |||
| 136 | if (input[0] == '/' && !this.disableSend) { | 177 | if (input[0] == '/' && !this.disableSend) { |
| 137 | let result; | 178 | let result; |
| 138 | if (options.isAutoExecute || options.isRun) { | 179 | if (options.isAutoExecute || options.isRun) { |
| 139 | result = await executeSlashCommandsWithOptions(input, { | 180 | result = await executeSlashCommandsWithOptions(input, Object.assign(execOptions, { |
| 140 | handleParserErrors: true, | 181 | handleParserErrors: true, |
| 141 | scope: options.scope, | 182 | scope: options.scope, |
| 142 | }); | 183 | source: `${this.name}.${qr.label}`, |
| 184 | })); | ||
| 143 | } else if (options.isEditor) { | 185 | } else if (options.isEditor) { |
| 144 | result = await executeSlashCommandsWithOptions(input, { | 186 | result = await executeSlashCommandsWithOptions(input, Object.assign(execOptions, { |
| 145 | handleParserErrors: false, | 187 | handleParserErrors: false, |
| 146 | scope: options.scope, | 188 | scope: options.scope, |
| 147 | abortController: qr.abortController, | 189 | abortController: qr.abortController, |
| 190 | source: `${this.name}.${qr.label}`, | ||
| 148 | onProgress: (done, total) => qr.updateEditorProgress(done, total), | 191 | onProgress: (done, total) => qr.updateEditorProgress(done, total), |
| 149 | }); | 192 | })); |
| 150 | } else { | 193 | } else { |
| 151 | result = await executeSlashCommandsOnChatInput(input, { | 194 | result = await executeSlashCommandsOnChatInput(input, Object.assign(execOptions, { |
| 152 | scope: options.scope, | 195 | scope: options.scope, |
| 153 | }); | 196 | source: `${this.name}.${qr.label}`, |
| 197 | })); | ||
| 154 | } | 198 | } |
| 155 | return typeof result === 'object' ? result?.pipe : ''; | 199 | return typeof result === 'object' ? result?.pipe : ''; |
| 156 | } | 200 | } |
| @@ -165,7 +209,7 @@ export class QuickReplySet { | |||
| 165 | } | 209 | } |
| 166 | /** | 210 | /** |
| 167 | * @param {QuickReply} qr | 211 | * @param {QuickReply} qr |
| 168 | * @param {String} [message] - optional altered message to be used | 212 | * @param {string} [message] - optional altered message to be used |
| 169 | * @param {SlashCommandScope} [scope] - optional scope to be used when running the command | 213 | * @param {SlashCommandScope} [scope] - optional scope to be used when running the command |
| 170 | */ | 214 | */ |
| 171 | async execute(qr, message = null, isAutoExecute = false, scope = null) { | 215 | async execute(qr, message = null, isAutoExecute = false, scope = null) { |
| @@ -179,10 +223,11 @@ export class QuickReplySet { | |||
| 179 | 223 | ||
| 180 | 224 | ||
| 181 | 225 | ||
| 182 | addQuickReply() { | 226 | addQuickReply(data = {}) { |
| 183 | const id = Math.max(this.idIndex, this.qrList.reduce((max,qr)=>Math.max(max,qr.id),0)) + 1; | 227 | const id = Math.max(this.idIndex, this.qrList.reduce((max,qr)=>Math.max(max,qr.id),0)) + 1; |
| 228 | data.id = | ||
| 184 | this.idIndex = id + 1; | 229 | this.idIndex = id + 1; |
| 185 | const qr = QuickReply.from({ id }); | 230 | const qr = QuickReply.from(data); |
| 186 | this.qrList.push(qr); | 231 | this.qrList.push(qr); |
| 187 | this.hookQuickReply(qr); | 232 | this.hookQuickReply(qr); |
| 188 | if (this.settingsDom) { | 233 | if (this.settingsDom) { |
| @@ -194,11 +239,131 @@ export class QuickReplySet { | |||
| 194 | this.save(); | 239 | this.save(); |
| 195 | return qr; | 240 | return qr; |
| 196 | } | 241 | } |
| 242 | addQuickReplyFromText(qrJson) { | ||
| 243 | let data; | ||
| 244 | if (qrJson) { | ||
| 245 | try { | ||
| 246 | data = JSON.parse(qrJson ?? '{}'); | ||
| 247 | delete data.id; | ||
| 248 | } catch { | ||
| 249 | // not JSON data | ||
| 250 | } | ||
| 251 | if (data) { | ||
| 252 | // JSON data | ||
| 253 | if (data.label === undefined || data.message === undefined) { | ||
| 254 | // not a QR | ||
| 255 | toastr.error('Not a QR.'); | ||
| 256 | return; | ||
| 257 | } | ||
| 258 | } else { | ||
| 259 | // no JSON, use plaintext as QR message | ||
| 260 | data = { message: qrJson }; | ||
| 261 | } | ||
| 262 | } else { | ||
| 263 | data = {}; | ||
| 264 | } | ||
| 265 | const newQr = this.addQuickReply(data); | ||
| 266 | return newQr; | ||
| 267 | } | ||
| 197 | 268 | ||
| 269 | /** | ||
| 270 | * | ||
| 271 | * @param {QuickReply} qr | ||
| 272 | */ | ||
| 198 | hookQuickReply(qr) { | 273 | hookQuickReply(qr) { |
| 274 | qr.onDebug = ()=>this.debug(qr); | ||
| 199 | qr.onExecute = (_, options)=>this.executeWithOptions(qr, options); | 275 | qr.onExecute = (_, options)=>this.executeWithOptions(qr, options); |
| 200 | qr.onDelete = ()=>this.removeQuickReply(qr); | 276 | qr.onDelete = ()=>this.removeQuickReply(qr); |
| 201 | qr.onUpdate = ()=>this.save(); | 277 | qr.onUpdate = ()=>this.save(); |
| 278 | qr.onInsertBefore = (qrJson)=>{ | ||
| 279 | this.addQuickReplyFromText(qrJson); | ||
| 280 | const newQr = this.qrList.pop(); | ||
| 281 | this.qrList.splice(this.qrList.indexOf(qr), 0, newQr); | ||
| 282 | if (qr.settingsDom) { | ||
| 283 | qr.settingsDom.insertAdjacentElement('beforebegin', newQr.settingsDom); | ||
| 284 | } | ||
| 285 | this.save(); | ||
| 286 | }; | ||
| 287 | qr.onTransfer = async()=>{ | ||
| 288 | /**@type {HTMLSelectElement} */ | ||
| 289 | let sel; | ||
| 290 | let isCopy = false; | ||
| 291 | const dom = document.createElement('div'); { | ||
| 292 | dom.classList.add('qr--transferModal'); | ||
| 293 | const title = document.createElement('h3'); { | ||
| 294 | title.textContent = 'Transfer Quick Reply'; | ||
| 295 | dom.append(title); | ||
| 296 | } | ||
| 297 | const subTitle = document.createElement('h4'); { | ||
| 298 | const entryName = qr.label; | ||
| 299 | const bookName = this.name; | ||
| 300 | subTitle.textContent = `${bookName}: ${entryName}`; | ||
| 301 | dom.append(subTitle); | ||
| 302 | } | ||
| 303 | sel = document.createElement('select'); { | ||
| 304 | sel.classList.add('qr--transferSelect'); | ||
| 305 | sel.setAttribute('autofocus', '1'); | ||
| 306 | const noOpt = document.createElement('option'); { | ||
| 307 | noOpt.value = ''; | ||
| 308 | noOpt.textContent = '-- Select QR Set --'; | ||
| 309 | sel.append(noOpt); | ||
| 310 | } | ||
| 311 | for (const qrs of QuickReplySet.list) { | ||
| 312 | const opt = document.createElement('option'); { | ||
| 313 | opt.value = qrs.name; | ||
| 314 | opt.textContent = qrs.name; | ||
| 315 | sel.append(opt); | ||
| 316 | } | ||
| 317 | } | ||
| 318 | sel.addEventListener('keyup', (evt)=>{ | ||
| 319 | if (evt.key == 'Shift') { | ||
| 320 | (dlg.dom ?? dlg.dlg).classList.remove('qr--isCopy'); | ||
| 321 | return; | ||
| 322 | } | ||
| 323 | }); | ||
| 324 | sel.addEventListener('keydown', (evt)=>{ | ||
| 325 | if (evt.key == 'Shift') { | ||
| 326 | (dlg.dom ?? dlg.dlg).classList.add('qr--isCopy'); | ||
| 327 | return; | ||
| 328 | } | ||
| 329 | if (!evt.ctrlKey && !evt.altKey && evt.key == 'Enter') { | ||
| 330 | evt.preventDefault(); | ||
| 331 | if (evt.shiftKey) isCopy = true; | ||
| 332 | dlg.completeAffirmative(); | ||
| 333 | } | ||
| 334 | }); | ||
| 335 | dom.append(sel); | ||
| 336 | } | ||
| 337 | const hintP = document.createElement('p'); { | ||
| 338 | const hint = document.createElement('small'); { | ||
| 339 | hint.textContent = 'Type or arrows to select QR Set. Enter to transfer. Shift+Enter to copy.'; | ||
| 340 | hintP.append(hint); | ||
| 341 | } | ||
| 342 | dom.append(hintP); | ||
| 343 | } | ||
| 344 | } | ||
| 345 | const dlg = new Popup(dom, POPUP_TYPE.CONFIRM, null, { okButton:'Transfer', cancelButton:'Cancel' }); | ||
| 346 | const copyBtn = document.createElement('div'); { | ||
| 347 | copyBtn.classList.add('qr--copy'); | ||
| 348 | copyBtn.classList.add('menu_button'); | ||
| 349 | copyBtn.textContent = 'Copy'; | ||
| 350 | copyBtn.addEventListener('click', ()=>{ | ||
| 351 | isCopy = true; | ||
| 352 | dlg.completeAffirmative(); | ||
| 353 | }); | ||
| 354 | (dlg.ok ?? dlg.okButton).insertAdjacentElement('afterend', copyBtn); | ||
| 355 | } | ||
| 356 | const prom = dlg.show(); | ||
| 357 | sel.focus(); | ||
| 358 | await prom; | ||
| 359 | if (dlg.result == POPUP_RESULT.AFFIRMATIVE) { | ||
| 360 | const qrs = QuickReplySet.list.find(it=>it.name == sel.value); | ||
| 361 | qrs.addQuickReply(qr.toJSON()); | ||
| 362 | if (!isCopy) { | ||
| 363 | qr.delete(); | ||
| 364 | } | ||
| 365 | } | ||
| 366 | }; | ||
| 202 | } | 367 | } |
| 203 | 368 | ||
| 204 | removeQuickReply(qr) { | 369 | removeQuickReply(qr) { |
| @@ -214,6 +379,8 @@ export class QuickReplySet { | |||
| 214 | disableSend: this.disableSend, | 379 | disableSend: this.disableSend, |
| 215 | placeBeforeInput: this.placeBeforeInput, | 380 | placeBeforeInput: this.placeBeforeInput, |
| 216 | injectInput: this.injectInput, | 381 | injectInput: this.injectInput, |
| 382 | color: this.color, | ||
| 383 | onlyBorderColor: this.onlyBorderColor, | ||
| 217 | qrList: this.qrList, | 384 | qrList: this.qrList, |
| 218 | idIndex: this.idIndex, | 385 | idIndex: this.idIndex, |
| 219 | }; | 386 | }; |
| @@ -245,8 +412,12 @@ export class QuickReplySet { | |||
| 245 | if (response.ok) { | 412 | if (response.ok) { |
| 246 | this.unrender(); | 413 | this.unrender(); |
| 247 | const idx = QuickReplySet.list.indexOf(this); | 414 | const idx = QuickReplySet.list.indexOf(this); |
| 248 | QuickReplySet.list.splice(idx, 1); | 415 | if (idx > -1) { |
| 249 | this.isDeleted = true; | 416 | QuickReplySet.list.splice(idx, 1); |
| 417 | this.isDeleted = true; | ||
| 418 | } else { | ||
| 419 | warn(`Deleted Quick Reply Set was not found in the list of sets: ${this.name}`); | ||
| 420 | } | ||
| 250 | } else { | 421 | } else { |
| 251 | warn(`Failed to delete Quick Reply Set: ${this.name}`); | 422 | warn(`Failed to delete Quick Reply Set: ${this.name}`); |
| 252 | } | 423 | } |
| @@ -45,7 +45,7 @@ export class QuickReplySetLink { | |||
| 45 | this.set = QuickReplySet.get(set.value); | 45 | this.set = QuickReplySet.get(set.value); |
| 46 | this.update(); | 46 | this.update(); |
| 47 | }); | 47 | }); |
| 48 | QuickReplySet.list.forEach(qrs=>{ | 48 | QuickReplySet.list.toSorted((a,b)=>a.name.toLowerCase().localeCompare(b.name.toLowerCase())).forEach(qrs=>{ |
| 49 | const opt = document.createElement('option'); { | 49 | const opt = document.createElement('option'); { |
| 50 | opt.value = qrs.name; | 50 | opt.value = qrs.name; |
| 51 | opt.textContent = qrs.name; | 51 | opt.textContent = qrs.name; |
| @@ -1,8 +1,12 @@ | |||
| 1 | import { SlashCommand } from '../../../slash-commands/SlashCommand.js'; | 1 | import { SlashCommand } from '../../../slash-commands/SlashCommand.js'; |
| 2 | import { SlashCommandAbortController } from '../../../slash-commands/SlashCommandAbortController.js'; | ||
| 2 | import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../../slash-commands/SlashCommandArgument.js'; | 3 | import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../../slash-commands/SlashCommandArgument.js'; |
| 4 | import { SlashCommandClosure } from '../../../slash-commands/SlashCommandClosure.js'; | ||
| 3 | import { enumIcons } from '../../../slash-commands/SlashCommandCommonEnumsProvider.js'; | 5 | import { enumIcons } from '../../../slash-commands/SlashCommandCommonEnumsProvider.js'; |
| 6 | import { SlashCommandDebugController } from '../../../slash-commands/SlashCommandDebugController.js'; | ||
| 4 | import { SlashCommandEnumValue, enumTypes } from '../../../slash-commands/SlashCommandEnumValue.js'; | 7 | import { SlashCommandEnumValue, enumTypes } from '../../../slash-commands/SlashCommandEnumValue.js'; |
| 5 | import { SlashCommandParser } from '../../../slash-commands/SlashCommandParser.js'; | 8 | import { SlashCommandParser } from '../../../slash-commands/SlashCommandParser.js'; |
| 9 | import { SlashCommandScope } from '../../../slash-commands/SlashCommandScope.js'; | ||
| 6 | import { isTrueBoolean } from '../../../utils.js'; | 10 | import { isTrueBoolean } from '../../../utils.js'; |
| 7 | // eslint-disable-next-line no-unused-vars | 11 | // eslint-disable-next-line no-unused-vars |
| 8 | import { QuickReplyApi } from '../api/QuickReplyApi.js'; | 12 | import { QuickReplyApi } from '../api/QuickReplyApi.js'; |
| @@ -47,6 +51,13 @@ export class SlashCommandHandler { | |||
| 47 | return new SlashCommandEnumValue(qr.label, message, enumTypes.enum, enumIcons.qr); | 51 | return new SlashCommandEnumValue(qr.label, message, enumTypes.enum, enumIcons.qr); |
| 48 | }) ?? [], | 52 | }) ?? [], |
| 49 | 53 | ||
| 54 | /** All QRs inside a set, utilizing the "set" named argument, returns the QR's ID */ | ||
| 55 | qrIds: (executor) => QuickReplySet.get(String(executor.namedArgumentList.find(x => x.name == 'set')?.value))?.qrList.map(qr => { | ||
| 56 | const icons = getExecutionIcons(qr); | ||
| 57 | const message = `${qr.automationId ? `[${qr.automationId}]` : ''}${icons ? `[auto: ${icons}]` : ''} ${qr.title || qr.message}`.trim(); | ||
| 58 | return new SlashCommandEnumValue(qr.label, message, enumTypes.enum, enumIcons.qr, null, ()=>qr.id.toString(), true); | ||
| 59 | }) ?? [], | ||
| 60 | |||
| 50 | /** All QRs as a set.name string, to be able to execute, for example via the /run command */ | 61 | /** All QRs as a set.name string, to be able to execute, for example via the /run command */ |
| 51 | qrExecutables: () => { | 62 | qrExecutables: () => { |
| 52 | const globalSetList = this.api.settings.config.setList; | 63 | const globalSetList = this.api.settings.config.setList; |
| @@ -63,7 +74,7 @@ export class SlashCommandHandler { | |||
| 63 | ...otherQrs.map(x => new SlashCommandEnumValue(`${x.set.name}.${x.qr.label}`, `${x.qr.title || x.qr.message}`, enumTypes.qr, enumIcons.qr)), | 74 | ...otherQrs.map(x => new SlashCommandEnumValue(`${x.set.name}.${x.qr.label}`, `${x.qr.title || x.qr.message}`, enumTypes.qr, enumIcons.qr)), |
| 64 | ]; | 75 | ]; |
| 65 | }, | 76 | }, |
| 66 | } | 77 | }; |
| 67 | 78 | ||
| 68 | window['qrEnumProviderExecutables'] = localEnumProviders.qrExecutables; | 79 | window['qrEnumProviderExecutables'] = localEnumProviders.qrExecutables; |
| 69 | 80 | ||
| @@ -234,8 +245,20 @@ export class SlashCommandHandler { | |||
| 234 | name: 'label', | 245 | name: 'label', |
| 235 | description: 'text on the button, e.g., label=MyButton', | 246 | description: 'text on the button, e.g., label=MyButton', |
| 236 | typeList: [ARGUMENT_TYPE.STRING], | 247 | typeList: [ARGUMENT_TYPE.STRING], |
| 237 | isRequired: true, | 248 | isRequired: false, |
| 238 | enumProvider: localEnumProviders.qrLabels, | 249 | enumProvider: localEnumProviders.qrEntries, |
| 250 | }), | ||
| 251 | SlashCommandNamedArgument.fromProps({ | ||
| 252 | name: 'icon', | ||
| 253 | description: 'icon to show on the button, e.g., icon=fa-pencil', | ||
| 254 | typeList: [ARGUMENT_TYPE.STRING], | ||
| 255 | isRequired: false, | ||
| 256 | }), | ||
| 257 | SlashCommandNamedArgument.fromProps({ | ||
| 258 | name: 'showlabel', | ||
| 259 | description: 'whether to show the label even when an icon is assigned, e.g., icon=fa-pencil showlabel=true', | ||
| 260 | typeList: [ARGUMENT_TYPE.BOOLEAN], | ||
| 261 | isRequired: false, | ||
| 239 | }), | 262 | }), |
| 240 | new SlashCommandNamedArgument('hidden', 'whether the button should be hidden, e.g., hidden=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'), | 263 | new SlashCommandNamedArgument('hidden', 'whether the button should be hidden, e.g., hidden=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'), |
| 241 | new SlashCommandNamedArgument('startup', 'auto execute on app startup, e.g., startup=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'), | 264 | 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 { | |||
| 247 | ]; | 270 | ]; |
| 248 | const qrUpdateArgs = [ | 271 | const qrUpdateArgs = [ |
| 249 | new SlashCommandNamedArgument('newlabel', 'new text for the button', [ARGUMENT_TYPE.STRING], false), | 272 | new SlashCommandNamedArgument('newlabel', 'new text for the button', [ARGUMENT_TYPE.STRING], false), |
| 273 | SlashCommandNamedArgument.fromProps({ | ||
| 274 | name: 'id', | ||
| 275 | description: 'numeric ID of the QR, e.g., id=42', | ||
| 276 | typeList: [ARGUMENT_TYPE.NUMBER], | ||
| 277 | isRequired: false, | ||
| 278 | enumProvider: localEnumProviders.qrIds, | ||
| 279 | }), | ||
| 250 | ]; | 280 | ]; |
| 251 | 281 | ||
| 252 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-create', | 282 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-create', |
| @@ -272,13 +302,61 @@ export class SlashCommandHandler { | |||
| 272 | </div> | 302 | </div> |
| 273 | `, | 303 | `, |
| 274 | })); | 304 | })); |
| 305 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-get', | ||
| 306 | callback: (args, _) => { | ||
| 307 | return this.getQuickReply(args); | ||
| 308 | }, | ||
| 309 | namedArgumentList: [ | ||
| 310 | SlashCommandNamedArgument.fromProps({ | ||
| 311 | name: 'set', | ||
| 312 | description: 'name of the QR set, e.g., set=PresetName1', | ||
| 313 | typeList: [ARGUMENT_TYPE.STRING], | ||
| 314 | isRequired: true, | ||
| 315 | enumProvider: localEnumProviders.qrSets, | ||
| 316 | }), | ||
| 317 | SlashCommandNamedArgument.fromProps({ | ||
| 318 | name: 'label', | ||
| 319 | description: 'text on the button, e.g., label=MyButton', | ||
| 320 | typeList: [ARGUMENT_TYPE.STRING], | ||
| 321 | isRequired: false, | ||
| 322 | enumProvider: localEnumProviders.qrEntries, | ||
| 323 | }), | ||
| 324 | SlashCommandNamedArgument.fromProps({ | ||
| 325 | name: 'id', | ||
| 326 | description: 'numeric ID of the QR, e.g., id=42', | ||
| 327 | typeList: [ARGUMENT_TYPE.NUMBER], | ||
| 328 | isRequired: false, | ||
| 329 | enumProvider: localEnumProviders.qrIds, | ||
| 330 | }), | ||
| 331 | ], | ||
| 332 | returns: 'a dictionary with all the QR\'s properties', | ||
| 333 | helpString: ` | ||
| 334 | <div>Get a Quick Reply's properties.</div> | ||
| 335 | <div> | ||
| 336 | <strong>Examples:</strong> | ||
| 337 | <ul> | ||
| 338 | <li> | ||
| 339 | <pre><code>/qr-get set=MyPreset label=MyButton | /echo</code></pre> | ||
| 340 | <pre><code>/qr-get set=MyPreset id=42 | /echo</code></pre> | ||
| 341 | </li> | ||
| 342 | </ul> | ||
| 343 | </div> | ||
| 344 | `, | ||
| 345 | })); | ||
| 275 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-update', | 346 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-update', |
| 276 | callback: (args, message) => { | 347 | callback: (args, message) => { |
| 277 | this.updateQuickReply(args, message); | 348 | this.updateQuickReply(args, message); |
| 278 | return ''; | 349 | return ''; |
| 279 | }, | 350 | }, |
| 280 | returns: 'updated quick reply', | 351 | returns: 'updated quick reply', |
| 281 | namedArgumentList: [...qrUpdateArgs, ...qrArgs], | 352 | namedArgumentList: [...qrUpdateArgs, ...qrArgs.map(it=>{ |
| 353 | if (it.name == 'label') { | ||
| 354 | const clone = SlashCommandNamedArgument.fromProps(it); | ||
| 355 | clone.isRequired = false; | ||
| 356 | return clone; | ||
| 357 | } | ||
| 358 | return it; | ||
| 359 | })], | ||
| 282 | unnamedArgumentList: [ | 360 | unnamedArgumentList: [ |
| 283 | new SlashCommandArgument('command', [ARGUMENT_TYPE.STRING]), | 361 | new SlashCommandArgument('command', [ARGUMENT_TYPE.STRING]), |
| 284 | ], | 362 | ], |
| @@ -315,6 +393,12 @@ export class SlashCommandHandler { | |||
| 315 | typeList: [ARGUMENT_TYPE.STRING], | 393 | typeList: [ARGUMENT_TYPE.STRING], |
| 316 | enumProvider: localEnumProviders.qrEntries, | 394 | enumProvider: localEnumProviders.qrEntries, |
| 317 | }), | 395 | }), |
| 396 | SlashCommandNamedArgument.fromProps({ | ||
| 397 | name: 'id', | ||
| 398 | description: 'numeric ID of the QR, e.g., id=42', | ||
| 399 | typeList: [ARGUMENT_TYPE.NUMBER], | ||
| 400 | enumProvider: localEnumProviders.qrIds, | ||
| 401 | }), | ||
| 318 | ], | 402 | ], |
| 319 | unnamedArgumentList: [ | 403 | unnamedArgumentList: [ |
| 320 | SlashCommandArgument.fromProps({ | 404 | SlashCommandArgument.fromProps({ |
| @@ -344,6 +428,12 @@ export class SlashCommandHandler { | |||
| 344 | typeList: [ARGUMENT_TYPE.STRING], | 428 | typeList: [ARGUMENT_TYPE.STRING], |
| 345 | enumProvider: localEnumProviders.qrEntries, | 429 | enumProvider: localEnumProviders.qrEntries, |
| 346 | }), | 430 | }), |
| 431 | SlashCommandNamedArgument.fromProps({ | ||
| 432 | name: 'id', | ||
| 433 | description: 'numeric ID of the QR, e.g., id=42', | ||
| 434 | typeList: [ARGUMENT_TYPE.NUMBER], | ||
| 435 | enumProvider: localEnumProviders.qrIds, | ||
| 436 | }), | ||
| 347 | new SlashCommandNamedArgument( | 437 | new SlashCommandNamedArgument( |
| 348 | 'chain', 'boolean', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false', | 438 | 'chain', 'boolean', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false', |
| 349 | ), | 439 | ), |
| @@ -389,6 +479,12 @@ export class SlashCommandHandler { | |||
| 389 | typeList: [ARGUMENT_TYPE.STRING], | 479 | typeList: [ARGUMENT_TYPE.STRING], |
| 390 | enumProvider: localEnumProviders.qrEntries, | 480 | enumProvider: localEnumProviders.qrEntries, |
| 391 | }), | 481 | }), |
| 482 | SlashCommandNamedArgument.fromProps({ | ||
| 483 | name: 'id', | ||
| 484 | description: 'numeric ID of the QR, e.g., id=42', | ||
| 485 | typeList: [ARGUMENT_TYPE.NUMBER], | ||
| 486 | enumProvider: localEnumProviders.qrIds, | ||
| 487 | }), | ||
| 392 | ], | 488 | ], |
| 393 | unnamedArgumentList: [ | 489 | unnamedArgumentList: [ |
| 394 | SlashCommandArgument.fromProps({ | 490 | SlashCommandArgument.fromProps({ |
| @@ -425,6 +521,12 @@ export class SlashCommandHandler { | |||
| 425 | isRequired: true, | 521 | isRequired: true, |
| 426 | enumProvider: localEnumProviders.qrSets, | 522 | enumProvider: localEnumProviders.qrSets, |
| 427 | }), | 523 | }), |
| 524 | SlashCommandNamedArgument.fromProps({ | ||
| 525 | name: 'id', | ||
| 526 | description: 'numeric ID of the QR, e.g., id=42', | ||
| 527 | typeList: [ARGUMENT_TYPE.NUMBER], | ||
| 528 | enumProvider: localEnumProviders.qrIds, | ||
| 529 | }), | ||
| 428 | ], | 530 | ], |
| 429 | unnamedArgumentList: [ | 531 | unnamedArgumentList: [ |
| 430 | SlashCommandArgument.fromProps({ | 532 | SlashCommandArgument.fromProps({ |
| @@ -454,8 +556,8 @@ export class SlashCommandHandler { | |||
| 454 | new SlashCommandNamedArgument('inject', 'inject user input automatically (if disabled use {{input}})', [ARGUMENT_TYPE.BOOLEAN], false), | 556 | new SlashCommandNamedArgument('inject', 'inject user input automatically (if disabled use {{input}})', [ARGUMENT_TYPE.BOOLEAN], false), |
| 455 | ]; | 557 | ]; |
| 456 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-create', | 558 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-create', |
| 457 | callback: (args, name) => { | 559 | callback: async (args, name) => { |
| 458 | this.createSet(name, args); | 560 | await this.createSet(name, args); |
| 459 | return ''; | 561 | return ''; |
| 460 | }, | 562 | }, |
| 461 | aliases: ['qr-presetadd'], | 563 | aliases: ['qr-presetadd'], |
| @@ -485,8 +587,8 @@ export class SlashCommandHandler { | |||
| 485 | })); | 587 | })); |
| 486 | 588 | ||
| 487 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-update', | 589 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-update', |
| 488 | callback: (args, name) => { | 590 | callback: async (args, name) => { |
| 489 | this.updateSet(name, args); | 591 | await this.updateSet(name, args); |
| 490 | return ''; | 592 | return ''; |
| 491 | }, | 593 | }, |
| 492 | aliases: ['qr-presetupdate'], | 594 | aliases: ['qr-presetupdate'], |
| @@ -510,8 +612,8 @@ export class SlashCommandHandler { | |||
| 510 | `, | 612 | `, |
| 511 | })); | 613 | })); |
| 512 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-delete', | 614 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-delete', |
| 513 | callback: (_, name) => { | 615 | callback: async (_, name) => { |
| 514 | this.deleteSet(name); | 616 | await this.deleteSet(name); |
| 515 | return ''; | 617 | return ''; |
| 516 | }, | 618 | }, |
| 517 | aliases: ['qr-presetdelete'], | 619 | aliases: ['qr-presetdelete'], |
| @@ -533,6 +635,134 @@ export class SlashCommandHandler { | |||
| 533 | </div> | 635 | </div> |
| 534 | `, | 636 | `, |
| 535 | })); | 637 | })); |
| 638 | |||
| 639 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-arg', | ||
| 640 | callback: ({ _scope }, [key, value]) => { | ||
| 641 | _scope.setMacro(`arg::${key}`, value, key.includes('*')); | ||
| 642 | return ''; | ||
| 643 | }, | ||
| 644 | unnamedArgumentList: [ | ||
| 645 | SlashCommandArgument.fromProps({ description: 'argument name', | ||
| 646 | typeList: ARGUMENT_TYPE.STRING, | ||
| 647 | isRequired: true, | ||
| 648 | }), | ||
| 649 | SlashCommandArgument.fromProps({ description: 'argument value', | ||
| 650 | typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.BOOLEAN, ARGUMENT_TYPE.LIST, ARGUMENT_TYPE.DICTIONARY], | ||
| 651 | isRequired: true, | ||
| 652 | }), | ||
| 653 | ], | ||
| 654 | splitUnnamedArgument: true, | ||
| 655 | splitUnnamedArgumentCount: 2, | ||
| 656 | helpString: ` | ||
| 657 | <div> | ||
| 658 | Set a fallback value for a Quick Reply argument. | ||
| 659 | </div> | ||
| 660 | <div> | ||
| 661 | <strong>Example:</strong> | ||
| 662 | <pre><code>/qr-arg x foo |\n/echo {{arg::x}}</code></pre> | ||
| 663 | </div> | ||
| 664 | `, | ||
| 665 | })); | ||
| 666 | |||
| 667 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'import', | ||
| 668 | /** | ||
| 669 | * | ||
| 670 | * @param {{_scope:SlashCommandScope, _abortController:SlashCommandAbortController, _debugController:SlashCommandDebugController, from:string}} args | ||
| 671 | * @param {string} value | ||
| 672 | */ | ||
| 673 | callback: (args, value) => { | ||
| 674 | if (!args.from) throw new Error('/import requires from= to be set.'); | ||
| 675 | if (!value) throw new Error('/import requires the unnamed argument to be set.'); | ||
| 676 | let qr = [...this.api.listGlobalSets(), ...this.api.listChatSets()] | ||
| 677 | .map(it=>this.api.getSetByName(it)?.qrList ?? []) | ||
| 678 | .flat() | ||
| 679 | .find(it=>it.label == args.from) | ||
| 680 | ; | ||
| 681 | if (!qr) { | ||
| 682 | let [setName, ...qrNameParts] = args.from.split('.'); | ||
| 683 | let qrName = qrNameParts.join('.'); | ||
| 684 | let qrs = QuickReplySet.get(setName); | ||
| 685 | if (qrs) { | ||
| 686 | qr = qrs.qrList.find(it=>it.label == qrName); | ||
| 687 | } | ||
| 688 | } | ||
| 689 | if (qr) { | ||
| 690 | const parser = new SlashCommandParser(); | ||
| 691 | const closure = parser.parse(qr.message, true, [], args._abortController, args._debugController); | ||
| 692 | if (args._debugController) { | ||
| 693 | closure.source = args.from; | ||
| 694 | } | ||
| 695 | const testCandidates = (executor)=>{ | ||
| 696 | return ( | ||
| 697 | executor.namedArgumentList.find(arg=>arg.name == 'key') | ||
| 698 | && executor.unnamedArgumentList.length > 0 | ||
| 699 | && executor.unnamedArgumentList[0].value instanceof SlashCommandClosure | ||
| 700 | ) || ( | ||
| 701 | !executor.namedArgumentList.find(arg=>arg.name == 'key') | ||
| 702 | && executor.unnamedArgumentList.length > 1 | ||
| 703 | && executor.unnamedArgumentList[1].value instanceof SlashCommandClosure | ||
| 704 | ); | ||
| 705 | }; | ||
| 706 | const candidates = closure.executorList | ||
| 707 | .filter(executor=>['let', 'var'].includes(executor.command.name)) | ||
| 708 | .filter(testCandidates) | ||
| 709 | .map(executor=>({ | ||
| 710 | key: executor.namedArgumentList.find(arg=>arg.name == 'key')?.value ?? executor.unnamedArgumentList[0].value, | ||
| 711 | value: executor.unnamedArgumentList[executor.namedArgumentList.find(arg=>arg.name == 'key') ? 0 : 1].value, | ||
| 712 | })) | ||
| 713 | ; | ||
| 714 | for (let i = 0; i < value.length; i++) { | ||
| 715 | const srcName = value[i]; | ||
| 716 | let dstName = srcName; | ||
| 717 | if (i + 2 < value.length && value[i + 1] == 'as') { | ||
| 718 | dstName = value[i + 2]; | ||
| 719 | i += 2; | ||
| 720 | } | ||
| 721 | const pick = candidates.find(it=>it.key == srcName); | ||
| 722 | if (!pick) throw new Error(`No scoped closure named "${srcName}" found in "${args.from}"`); | ||
| 723 | if (args._scope.existsVariableInScope(dstName)) { | ||
| 724 | args._scope.setVariable(dstName, pick.value); | ||
| 725 | } else { | ||
| 726 | args._scope.letVariable(dstName, pick.value); | ||
| 727 | } | ||
| 728 | } | ||
| 729 | } else { | ||
| 730 | throw new Error(`No Quick Reply found for "${name}".`); | ||
| 731 | } | ||
| 732 | return ''; | ||
| 733 | }, | ||
| 734 | namedArgumentList: [ | ||
| 735 | SlashCommandNamedArgument.fromProps({ name: 'from', | ||
| 736 | description: 'Quick Reply to import from (QRSet.QRLabel)', | ||
| 737 | typeList: ARGUMENT_TYPE.STRING, | ||
| 738 | isRequired: true, | ||
| 739 | }), | ||
| 740 | ], | ||
| 741 | unnamedArgumentList: [ | ||
| 742 | SlashCommandArgument.fromProps({ description: 'what to import (x or x as y)', | ||
| 743 | acceptsMultiple: true, | ||
| 744 | typeList: ARGUMENT_TYPE.STRING, | ||
| 745 | isRequired: true, | ||
| 746 | }), | ||
| 747 | ], | ||
| 748 | splitUnnamedArgument: true, | ||
| 749 | helpString: ` | ||
| 750 | <div> | ||
| 751 | Import one or more closures from another Quick Reply. | ||
| 752 | </div> | ||
| 753 | <div> | ||
| 754 | Only imports closures that are directly assigned a scoped variable via <code>/let</code> or <code>/var</code>. | ||
| 755 | </div> | ||
| 756 | <div> | ||
| 757 | <strong>Examples:</strong> | ||
| 758 | <ul> | ||
| 759 | <li><pre><code>/import from=LibraryQrSet.FooBar foo |\n/:foo</code></pre></li> | ||
| 760 | <li><pre><code>/import from=LibraryQrSet.FooBar\n\tfoo\n\tbar\n|\n/:foo |\n/:bar</code></pre></li> | ||
| 761 | <li><pre><code>/import from=LibraryQrSet.FooBar\n\tfoo as x\n\tbar as y\n|\n/:x |\n/:y</code></pre></li> | ||
| 762 | </ul> | ||
| 763 | </div> | ||
| 764 | `, | ||
| 765 | })); | ||
| 536 | } | 766 | } |
| 537 | 767 | ||
| 538 | 768 | ||
| @@ -618,6 +848,8 @@ export class SlashCommandHandler { | |||
| 618 | args.set ?? '', | 848 | args.set ?? '', |
| 619 | args.label ?? '', | 849 | args.label ?? '', |
| 620 | { | 850 | { |
| 851 | icon: args.icon, | ||
| 852 | showLabel: args.showlabel === undefined ? undefined : isTrueBoolean(args.showlabel), | ||
| 621 | message: message ?? '', | 853 | message: message ?? '', |
| 622 | title: args.title, | 854 | title: args.title, |
| 623 | isHidden: isTrueBoolean(args.hidden), | 855 | isHidden: isTrueBoolean(args.hidden), |
| @@ -633,12 +865,21 @@ export class SlashCommandHandler { | |||
| 633 | toastr.error(ex.message); | 865 | toastr.error(ex.message); |
| 634 | } | 866 | } |
| 635 | } | 867 | } |
| 868 | getQuickReply(args) { | ||
| 869 | try { | ||
| 870 | return JSON.stringify(this.api.getQrByLabel(args.set, args.id !== undefined ? Number(args.id) : args.label)); | ||
| 871 | } catch (ex) { | ||
| 872 | toastr.error(ex.message); | ||
| 873 | } | ||
| 874 | } | ||
| 636 | updateQuickReply(args, message) { | 875 | updateQuickReply(args, message) { |
| 637 | try { | 876 | try { |
| 638 | this.api.updateQuickReply( | 877 | this.api.updateQuickReply( |
| 639 | args.set ?? '', | 878 | args.set ?? '', |
| 640 | args.label ?? '', | 879 | args.id !== undefined ? Number(args.id) : (args.label ?? ''), |
| 641 | { | 880 | { |
| 881 | icon: args.icon, | ||
| 882 | showLabel: args.showlabel === undefined ? undefined : isTrueBoolean(args.showlabel), | ||
| 642 | newLabel: args.newlabel, | 883 | newLabel: args.newlabel, |
| 643 | message: (message ?? '').trim().length > 0 ? message : undefined, | 884 | message: (message ?? '').trim().length > 0 ? message : undefined, |
| 644 | title: args.title, | 885 | title: args.title, |
| @@ -657,7 +898,7 @@ export class SlashCommandHandler { | |||
| 657 | } | 898 | } |
| 658 | deleteQuickReply(args, label) { | 899 | deleteQuickReply(args, label) { |
| 659 | try { | 900 | try { |
| 660 | this.api.deleteQuickReply(args.set, args.label ?? label); | 901 | this.api.deleteQuickReply(args.set, args.id !== undefined ? Number(args.id) : (args.label ?? label)); |
| 661 | } catch (ex) { | 902 | } catch (ex) { |
| 662 | toastr.error(ex.message); | 903 | toastr.error(ex.message); |
| 663 | } | 904 | } |
| @@ -692,9 +933,9 @@ export class SlashCommandHandler { | |||
| 692 | } | 933 | } |
| 693 | 934 | ||
| 694 | 935 | ||
| 695 | createSet(name, args) { | 936 | async createSet(name, args) { |
| 696 | try { | 937 | try { |
| 697 | this.api.createSet( | 938 | await this.api.createSet( |
| 698 | args.name ?? name ?? '', | 939 | args.name ?? name ?? '', |
| 699 | { | 940 | { |
| 700 | disableSend: isTrueBoolean(args.nosend), | 941 | disableSend: isTrueBoolean(args.nosend), |
| @@ -706,9 +947,9 @@ export class SlashCommandHandler { | |||
| 706 | toastr.error(ex.message); | 947 | toastr.error(ex.message); |
| 707 | } | 948 | } |
| 708 | } | 949 | } |
| 709 | updateSet(name, args) { | 950 | async updateSet(name, args) { |
| 710 | try { | 951 | try { |
| 711 | this.api.updateSet( | 952 | await this.api.updateSet( |
| 712 | args.name ?? name ?? '', | 953 | args.name ?? name ?? '', |
| 713 | { | 954 | { |
| 714 | disableSend: args.nosend !== undefined ? isTrueBoolean(args.nosend) : undefined, | 955 | disableSend: args.nosend !== undefined ? isTrueBoolean(args.nosend) : undefined, |
| @@ -720,9 +961,9 @@ export class SlashCommandHandler { | |||
| 720 | toastr.error(ex.message); | 961 | toastr.error(ex.message); |
| 721 | } | 962 | } |
| 722 | } | 963 | } |
| 723 | deleteSet(name) { | 964 | async deleteSet(name) { |
| 724 | try { | 965 | try { |
| 725 | this.api.deleteSet(name ?? ''); | 966 | await this.api.deleteSet(name ?? ''); |
| 726 | } catch (ex) { | 967 | } catch (ex) { |
| 727 | toastr.error(ex.message); | 968 | toastr.error(ex.message); |
| 728 | } | 969 | } |
| @@ -23,6 +23,8 @@ export class SettingsUi { | |||
| 23 | /**@type {HTMLInputElement}*/ disableSend; | 23 | /**@type {HTMLInputElement}*/ disableSend; |
| 24 | /**@type {HTMLInputElement}*/ placeBeforeInput; | 24 | /**@type {HTMLInputElement}*/ placeBeforeInput; |
| 25 | /**@type {HTMLInputElement}*/ injectInput; | 25 | /**@type {HTMLInputElement}*/ injectInput; |
| 26 | /**@type {HTMLInputElement}*/ color; | ||
| 27 | /**@type {HTMLInputElement}*/ onlyBorderColor; | ||
| 26 | /**@type {HTMLSelectElement}*/ currentSet; | 28 | /**@type {HTMLSelectElement}*/ currentSet; |
| 27 | 29 | ||
| 28 | 30 | ||
| @@ -117,10 +119,29 @@ export class SettingsUi { | |||
| 117 | this.dom.querySelector('#qr--set-add').addEventListener('click', async()=>{ | 119 | this.dom.querySelector('#qr--set-add').addEventListener('click', async()=>{ |
| 118 | this.currentQrSet.addQuickReply(); | 120 | this.currentQrSet.addQuickReply(); |
| 119 | }); | 121 | }); |
| 122 | this.dom.querySelector('#qr--set-paste').addEventListener('click', async()=>{ | ||
| 123 | const text = await navigator.clipboard.readText(); | ||
| 124 | this.currentQrSet.addQuickReplyFromText(text); | ||
| 125 | }); | ||
| 126 | this.dom.querySelector('#qr--set-importQr').addEventListener('click', async()=>{ | ||
| 127 | const inp = document.createElement('input'); { | ||
| 128 | inp.type = 'file'; | ||
| 129 | inp.accept = '.json'; | ||
| 130 | inp.addEventListener('change', async()=>{ | ||
| 131 | if (inp.files.length > 0) { | ||
| 132 | for (const file of inp.files) { | ||
| 133 | const text = await file.text(); | ||
| 134 | this.currentQrSet.addQuickReply(JSON.parse(text)); | ||
| 135 | } | ||
| 136 | } | ||
| 137 | }); | ||
| 138 | inp.click(); | ||
| 139 | } | ||
| 140 | }); | ||
| 120 | this.qrList = this.dom.querySelector('#qr--set-qrList'); | 141 | this.qrList = this.dom.querySelector('#qr--set-qrList'); |
| 121 | this.currentSet = this.dom.querySelector('#qr--set'); | 142 | this.currentSet = this.dom.querySelector('#qr--set'); |
| 122 | this.currentSet.addEventListener('change', ()=>this.onQrSetChange()); | 143 | this.currentSet.addEventListener('change', ()=>this.onQrSetChange()); |
| 123 | QuickReplySet.list.forEach(qrs=>{ | 144 | QuickReplySet.list.toSorted((a,b)=>a.name.toLowerCase().localeCompare(b.name.toLowerCase())).forEach(qrs=>{ |
| 124 | const opt = document.createElement('option'); { | 145 | const opt = document.createElement('option'); { |
| 125 | opt.value = qrs.name; | 146 | opt.value = qrs.name; |
| 126 | opt.textContent = qrs.name; | 147 | opt.textContent = qrs.name; |
| @@ -145,6 +166,34 @@ export class SettingsUi { | |||
| 145 | qrs.injectInput = this.injectInput.checked; | 166 | qrs.injectInput = this.injectInput.checked; |
| 146 | qrs.save(); | 167 | qrs.save(); |
| 147 | }); | 168 | }); |
| 169 | let initialColorChange = true; | ||
| 170 | this.color = this.dom.querySelector('#qr--color'); | ||
| 171 | this.color.color = this.currentQrSet?.color ?? 'transparent'; | ||
| 172 | this.color.addEventListener('change', (evt)=>{ | ||
| 173 | if (!this.dom.closest('body')) return; | ||
| 174 | const qrs = this.currentQrSet; | ||
| 175 | if (initialColorChange) { | ||
| 176 | initialColorChange = false; | ||
| 177 | this.color.color = qrs.color; | ||
| 178 | return; | ||
| 179 | } | ||
| 180 | qrs.color = evt.detail.rgb; | ||
| 181 | qrs.save(); | ||
| 182 | this.currentQrSet.updateColor(); | ||
| 183 | }); | ||
| 184 | this.dom.querySelector('#qr--colorClear').addEventListener('click', (evt)=>{ | ||
| 185 | const qrs = this.currentQrSet; | ||
| 186 | this.color.color = 'transparent'; | ||
| 187 | qrs.save(); | ||
| 188 | this.currentQrSet.updateColor(); | ||
| 189 | }); | ||
| 190 | this.onlyBorderColor = this.dom.querySelector('#qr--onlyBorderColor'); | ||
| 191 | this.onlyBorderColor.addEventListener('click', ()=>{ | ||
| 192 | const qrs = this.currentQrSet; | ||
| 193 | qrs.onlyBorderColor = this.onlyBorderColor.checked; | ||
| 194 | qrs.save(); | ||
| 195 | this.currentQrSet.updateColor(); | ||
| 196 | }); | ||
| 148 | this.onQrSetChange(); | 197 | this.onQrSetChange(); |
| 149 | } | 198 | } |
| 150 | onQrSetChange() { | 199 | onQrSetChange() { |
| @@ -152,6 +201,8 @@ export class SettingsUi { | |||
| 152 | this.disableSend.checked = this.currentQrSet.disableSend; | 201 | this.disableSend.checked = this.currentQrSet.disableSend; |
| 153 | this.placeBeforeInput.checked = this.currentQrSet.placeBeforeInput; | 202 | this.placeBeforeInput.checked = this.currentQrSet.placeBeforeInput; |
| 154 | this.injectInput.checked = this.currentQrSet.injectInput; | 203 | this.injectInput.checked = this.currentQrSet.injectInput; |
| 204 | this.color.color = this.currentQrSet.color ?? 'transparent'; | ||
| 205 | this.onlyBorderColor.checked = this.currentQrSet.onlyBorderColor; | ||
| 155 | this.qrList.innerHTML = ''; | 206 | this.qrList.innerHTML = ''; |
| 156 | const qrsDom = this.currentQrSet.renderSettings(); | 207 | const qrsDom = this.currentQrSet.renderSettings(); |
| 157 | this.qrList.append(qrsDom); | 208 | this.qrList.append(qrsDom); |
| @@ -265,7 +316,7 @@ export class SettingsUi { | |||
| 265 | const qrs = new QuickReplySet(); | 316 | const qrs = new QuickReplySet(); |
| 266 | qrs.name = name; | 317 | qrs.name = name; |
| 267 | qrs.addQuickReply(); | 318 | qrs.addQuickReply(); |
| 268 | const idx = QuickReplySet.list.findIndex(it=>it.name.localeCompare(name) == 1); | 319 | const idx = QuickReplySet.list.findIndex(it=>it.name.toLowerCase().localeCompare(name.toLowerCase()) == 1); |
| 269 | if (idx > -1) { | 320 | if (idx > -1) { |
| 270 | QuickReplySet.list.splice(idx, 0, qrs); | 321 | QuickReplySet.list.splice(idx, 0, qrs); |
| 271 | } else { | 322 | } else { |
| @@ -321,7 +372,7 @@ export class SettingsUi { | |||
| 321 | this.prepareChatSetList(); | 372 | this.prepareChatSetList(); |
| 322 | } | 373 | } |
| 323 | } else { | 374 | } else { |
| 324 | const idx = QuickReplySet.list.findIndex(it=>it.name.localeCompare(qrs.name) == 1); | 375 | const idx = QuickReplySet.list.findIndex(it=>it.name.toLowerCase().localeCompare(qrs.name.toLowerCase()) == 1); |
| 325 | if (idx > -1) { | 376 | if (idx > -1) { |
| 326 | QuickReplySet.list.splice(idx, 0, qrs); | 377 | QuickReplySet.list.splice(idx, 0, qrs); |
| 327 | } else { | 378 | } else { |
| @@ -33,11 +33,14 @@ export class ContextMenu { | |||
| 33 | */ | 33 | */ |
| 34 | build(qr, chainedMessage = null, hierarchy = [], labelHierarchy = []) { | 34 | build(qr, chainedMessage = null, hierarchy = [], labelHierarchy = []) { |
| 35 | const tree = { | 35 | const tree = { |
| 36 | icon: qr.icon, | ||
| 37 | showLabel: qr.showLabel, | ||
| 36 | label: qr.label, | 38 | label: qr.label, |
| 37 | message: (chainedMessage && qr.message ? `${chainedMessage} | ` : '') + qr.message, | 39 | message: (chainedMessage && qr.message ? `${chainedMessage} | ` : '') + qr.message, |
| 38 | children: [], | 40 | children: [], |
| 39 | }; | 41 | }; |
| 40 | qr.contextList.forEach((cl) => { | 42 | qr.contextList.forEach((cl) => { |
| 43 | if (!cl.set) return; | ||
| 41 | if (!hierarchy.includes(cl.set)) { | 44 | if (!hierarchy.includes(cl.set)) { |
| 42 | const nextHierarchy = [...hierarchy, cl.set]; | 45 | const nextHierarchy = [...hierarchy, cl.set]; |
| 43 | const nextLabelHierarchy = [...labelHierarchy, tree.label]; | 46 | const nextLabelHierarchy = [...labelHierarchy, tree.label]; |
| @@ -45,6 +48,8 @@ export class ContextMenu { | |||
| 45 | cl.set.qrList.forEach(subQr => { | 48 | cl.set.qrList.forEach(subQr => { |
| 46 | const subTree = this.build(subQr, cl.isChained ? tree.message : null, nextHierarchy, nextLabelHierarchy); | 49 | const subTree = this.build(subQr, cl.isChained ? tree.message : null, nextHierarchy, nextLabelHierarchy); |
| 47 | tree.children.push(new MenuItem( | 50 | tree.children.push(new MenuItem( |
| 51 | subTree.icon, | ||
| 52 | subTree.showLabel, | ||
| 48 | subTree.label, | 53 | subTree.label, |
| 49 | subTree.message, | 54 | subTree.message, |
| 50 | (evt) => { | 55 | (evt) => { |
| @@ -2,7 +2,7 @@ import { MenuItem } from './MenuItem.js'; | |||
| 2 | 2 | ||
| 3 | export class MenuHeader extends MenuItem { | 3 | export class MenuHeader extends MenuItem { |
| 4 | constructor(/**@type {String}*/label) { | 4 | constructor(/**@type {String}*/label) { |
| 5 | super(label, null, null); | 5 | super(null, null, label, null, null); |
| 6 | } | 6 | } |
| 7 | 7 | ||
| 8 | 8 | ||
| @@ -1,21 +1,34 @@ | |||
| 1 | import { SubMenu } from './SubMenu.js'; | 1 | import { SubMenu } from './SubMenu.js'; |
| 2 | 2 | ||
| 3 | export class MenuItem { | 3 | export class MenuItem { |
| 4 | /**@type {String}*/ label; | 4 | /**@type {string}*/ icon; |
| 5 | /**@type {Object}*/ value; | 5 | /**@type {boolean}*/ showLabel; |
| 6 | /**@type {Function}*/ callback; | 6 | /**@type {string}*/ label; |
| 7 | /**@type {object}*/ value; | ||
| 8 | /**@type {function}*/ callback; | ||
| 7 | /**@type {MenuItem[]}*/ childList = []; | 9 | /**@type {MenuItem[]}*/ childList = []; |
| 8 | /**@type {SubMenu}*/ subMenu; | 10 | /**@type {SubMenu}*/ subMenu; |
| 9 | /**@type {Boolean}*/ isForceExpanded = false; | 11 | /**@type {boolean}*/ isForceExpanded = false; |
| 10 | 12 | ||
| 11 | /**@type {HTMLElement}*/ root; | 13 | /**@type {HTMLElement}*/ root; |
| 12 | 14 | ||
| 13 | /**@type {Function}*/ onExpand; | 15 | /**@type {function}*/ onExpand; |
| 14 | 16 | ||
| 15 | 17 | ||
| 16 | 18 | ||
| 17 | 19 | ||
| 18 | constructor(/**@type {String}*/label, /**@type {Object}*/value, /**@type {function}*/callback, /**@type {MenuItem[]}*/children = []) { | 20 | /** |
| 21 | * | ||
| 22 | * @param {string} icon | ||
| 23 | * @param {boolean} showLabel | ||
| 24 | * @param {string} label | ||
| 25 | * @param {object} value | ||
| 26 | * @param {function} callback | ||
| 27 | * @param {MenuItem[]} children | ||
| 28 | */ | ||
| 29 | constructor(icon, showLabel, label, value, callback, children = []) { | ||
| 30 | this.icon = icon; | ||
| 31 | this.showLabel = showLabel; | ||
| 19 | this.label = label; | 32 | this.label = label; |
| 20 | this.value = value; | 33 | this.value = value; |
| 21 | this.callback = callback; | 34 | this.callback = callback; |
| @@ -33,7 +46,21 @@ export class MenuItem { | |||
| 33 | if (this.callback) { | 46 | if (this.callback) { |
| 34 | item.addEventListener('click', (evt) => this.callback(evt, this)); | 47 | item.addEventListener('click', (evt) => this.callback(evt, this)); |
| 35 | } | 48 | } |
| 36 | item.append(this.label); | 49 | const icon = document.createElement('div'); { |
| 50 | this.domIcon = icon; | ||
| 51 | icon.classList.add('qr--button-icon'); | ||
| 52 | icon.classList.add('fa-solid'); | ||
| 53 | if (!this.icon) icon.classList.add('qr--hidden'); | ||
| 54 | else icon.classList.add(this.icon); | ||
| 55 | item.append(icon); | ||
| 56 | } | ||
| 57 | const lbl = document.createElement('div'); { | ||
| 58 | this.domLabel = lbl; | ||
| 59 | lbl.classList.add('qr--button-label'); | ||
| 60 | if (this.icon && !this.showLabel) lbl.classList.add('qr--hidden'); | ||
| 61 | lbl.textContent = this.label; | ||
| 62 | item.append(lbl); | ||
| 63 | } | ||
| 37 | if (this.childList.length > 0) { | 64 | if (this.childList.length > 0) { |
| 38 | item.classList.add('ctx-has-children'); | 65 | item.classList.add('ctx-has-children'); |
| 39 | const sub = new SubMenu(this.childList); | 66 | const sub = new SubMenu(this.childList); |
| @@ -1,3 +1,20 @@ | |||
| 1 | @keyframes qr--success { | ||
| 2 | 0%, | ||
| 3 | 100% { | ||
| 4 | color: var(--SmartThemeBodyColor); | ||
| 5 | } | ||
| 6 | 25%, | ||
| 7 | 75% { | ||
| 8 | color: #51a351; | ||
| 9 | } | ||
| 10 | } | ||
| 11 | .qr--success { | ||
| 12 | animation-name: qr--success; | ||
| 13 | animation-duration: 3s; | ||
| 14 | animation-timing-function: linear; | ||
| 15 | animation-delay: 0s; | ||
| 16 | animation-iteration-count: 1; | ||
| 17 | } | ||
| 1 | #qr--bar { | 18 | #qr--bar { |
| 2 | outline: none; | 19 | outline: none; |
| 3 | margin: 0; | 20 | margin: 0; |
| @@ -41,6 +58,7 @@ | |||
| 41 | } | 58 | } |
| 42 | #qr--bar > .qr--buttons, | 59 | #qr--bar > .qr--buttons, |
| 43 | #qr--popout > .qr--body > .qr--buttons { | 60 | #qr--popout > .qr--body > .qr--buttons { |
| 61 | --qr--color: transparent; | ||
| 44 | margin: 0; | 62 | margin: 0; |
| 45 | padding: 0; | 63 | padding: 0; |
| 46 | display: flex; | 64 | display: flex; |
| @@ -49,10 +67,44 @@ | |||
| 49 | gap: 5px; | 67 | gap: 5px; |
| 50 | width: 100%; | 68 | width: 100%; |
| 51 | } | 69 | } |
| 70 | #qr--bar > .qr--buttons.qr--color, | ||
| 71 | #qr--popout > .qr--body > .qr--buttons.qr--color { | ||
| 72 | background-color: var(--qr--color); | ||
| 73 | } | ||
| 74 | #qr--bar > .qr--buttons.qr--borderColor, | ||
| 75 | #qr--popout > .qr--body > .qr--buttons.qr--borderColor { | ||
| 76 | background-color: transparent; | ||
| 77 | border-left: 5px solid var(--qr--color); | ||
| 78 | border-right: 5px solid var(--qr--color); | ||
| 79 | } | ||
| 80 | #qr--bar > .qr--buttons:has(.qr--buttons.qr--color), | ||
| 81 | #qr--popout > .qr--body > .qr--buttons:has(.qr--buttons.qr--color) { | ||
| 82 | margin: 5px; | ||
| 83 | } | ||
| 52 | #qr--bar > .qr--buttons > .qr--buttons, | 84 | #qr--bar > .qr--buttons > .qr--buttons, |
| 53 | #qr--popout > .qr--body > .qr--buttons > .qr--buttons { | 85 | #qr--popout > .qr--body > .qr--buttons > .qr--buttons { |
| 54 | display: contents; | 86 | display: contents; |
| 55 | } | 87 | } |
| 88 | #qr--bar > .qr--buttons > .qr--buttons.qr--color .qr--button:before, | ||
| 89 | #qr--popout > .qr--body > .qr--buttons > .qr--buttons.qr--color .qr--button:before { | ||
| 90 | content: ''; | ||
| 91 | background-color: var(--qr--color); | ||
| 92 | position: absolute; | ||
| 93 | inset: -5px; | ||
| 94 | z-index: -1; | ||
| 95 | } | ||
| 96 | #qr--bar > .qr--buttons > .qr--buttons.qr--color.qr--borderColor .qr--button:before, | ||
| 97 | #qr--popout > .qr--body > .qr--buttons > .qr--buttons.qr--color.qr--borderColor .qr--button:before { | ||
| 98 | display: none; | ||
| 99 | } | ||
| 100 | #qr--bar > .qr--buttons > .qr--buttons.qr--color.qr--borderColor:before, | ||
| 101 | #qr--popout > .qr--body > .qr--buttons > .qr--buttons.qr--color.qr--borderColor:before, | ||
| 102 | #qr--bar > .qr--buttons > .qr--buttons.qr--color.qr--borderColor:after, | ||
| 103 | #qr--popout > .qr--body > .qr--buttons > .qr--buttons.qr--color.qr--borderColor:after { | ||
| 104 | content: ''; | ||
| 105 | width: 5px; | ||
| 106 | background-color: var(--qr--color); | ||
| 107 | } | ||
| 56 | #qr--bar > .qr--buttons .qr--button, | 108 | #qr--bar > .qr--buttons .qr--button, |
| 57 | #qr--popout > .qr--body > .qr--buttons .qr--button { | 109 | #qr--popout > .qr--body > .qr--buttons .qr--button { |
| 58 | color: var(--SmartThemeBodyColor); | 110 | color: var(--SmartThemeBodyColor); |
| @@ -66,11 +118,19 @@ | |||
| 66 | align-items: center; | 118 | align-items: center; |
| 67 | justify-content: center; | 119 | justify-content: center; |
| 68 | text-align: center; | 120 | text-align: center; |
| 121 | position: relative; | ||
| 69 | } | 122 | } |
| 70 | #qr--bar > .qr--buttons .qr--button:hover, | 123 | #qr--bar > .qr--buttons .qr--button:hover, |
| 71 | #qr--popout > .qr--body > .qr--buttons .qr--button:hover { | 124 | #qr--popout > .qr--body > .qr--buttons .qr--button:hover { |
| 72 | opacity: 1; | 125 | background-color: #4d4d4d; |
| 73 | filter: brightness(1.2); | 126 | } |
| 127 | #qr--bar > .qr--buttons .qr--button .qr--hidden, | ||
| 128 | #qr--popout > .qr--body > .qr--buttons .qr--button .qr--hidden { | ||
| 129 | display: none; | ||
| 130 | } | ||
| 131 | #qr--bar > .qr--buttons .qr--button .qr--button-icon, | ||
| 132 | #qr--popout > .qr--body > .qr--buttons .qr--button .qr--button-icon { | ||
| 133 | margin: 0 0.5em; | ||
| 74 | } | 134 | } |
| 75 | #qr--bar > .qr--buttons .qr--button > .qr--button-expander, | 135 | #qr--bar > .qr--buttons .qr--button > .qr--button-expander, |
| 76 | #qr--popout > .qr--body > .qr--buttons .qr--button > .qr--button-expander { | 136 | #qr--popout > .qr--body > .qr--buttons .qr--button > .qr--button-expander { |
| @@ -170,36 +230,80 @@ | |||
| 170 | #qr--settings #qr--set-qrList .qr--set-qrListContents { | 230 | #qr--settings #qr--set-qrList .qr--set-qrListContents { |
| 171 | padding: 0 0.5em; | 231 | padding: 0 0.5em; |
| 172 | } | 232 | } |
| 173 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item { | 233 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--set-itemAdder { |
| 234 | display: flex; | ||
| 235 | align-items: center; | ||
| 236 | opacity: 0; | ||
| 237 | transition: 100ms; | ||
| 238 | margin: -2px 0 -11px 0; | ||
| 239 | position: relative; | ||
| 240 | } | ||
| 241 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--set-itemAdder .qr--actions { | ||
| 242 | display: flex; | ||
| 243 | gap: 0.25em; | ||
| 244 | flex: 0 0 auto; | ||
| 245 | } | ||
| 246 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--set-itemAdder .qr--actions .qr--action { | ||
| 247 | margin: 0; | ||
| 248 | } | ||
| 249 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--set-itemAdder:before, | ||
| 250 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--set-itemAdder:after { | ||
| 251 | content: ""; | ||
| 252 | display: block; | ||
| 253 | flex: 1 1 auto; | ||
| 254 | border: 1px solid; | ||
| 255 | margin: 0 1em; | ||
| 256 | height: 0; | ||
| 257 | } | ||
| 258 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--set-itemAdder:hover, | ||
| 259 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--set-itemAdder:focus-within { | ||
| 260 | opacity: 1; | ||
| 261 | } | ||
| 262 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content { | ||
| 174 | display: flex; | 263 | display: flex; |
| 175 | flex-direction: row; | 264 | flex-direction: row; |
| 176 | gap: 0.5em; | 265 | gap: 0.5em; |
| 177 | align-items: baseline; | 266 | align-items: baseline; |
| 178 | padding: 0.25em 0; | 267 | padding: 0.25em 0; |
| 179 | } | 268 | } |
| 180 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item > :nth-child(1) { | 269 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content > :nth-child(2) { |
| 181 | flex: 0 0 auto; | 270 | flex: 0 0 auto; |
| 182 | } | 271 | } |
| 183 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item > :nth-child(2) { | 272 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content > :nth-child(2) { |
| 184 | flex: 1 1 25%; | 273 | flex: 1 1 25%; |
| 185 | } | 274 | } |
| 186 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item > :nth-child(3) { | 275 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content > :nth-child(3) { |
| 187 | flex: 0 0 auto; | 276 | flex: 0 0 auto; |
| 188 | } | 277 | } |
| 189 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item > :nth-child(4) { | 278 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content > :nth-child(4) { |
| 190 | flex: 1 1 75%; | 279 | flex: 1 1 75%; |
| 191 | } | 280 | } |
| 192 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item > :nth-child(5) { | 281 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content > :nth-child(5) { |
| 193 | flex: 0 0 auto; | 282 | flex: 0 1 auto; |
| 283 | display: flex; | ||
| 284 | gap: 0.25em; | ||
| 285 | justify-content: flex-end; | ||
| 286 | flex-wrap: wrap; | ||
| 194 | } | 287 | } |
| 195 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item > .drag-handle { | 288 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content > .drag-handle { |
| 196 | padding: 0.75em; | 289 | padding: 0.75em; |
| 197 | } | 290 | } |
| 198 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--set-itemLabel, | 291 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content .qr--set-itemLabelContainer { |
| 199 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--action { | 292 | display: flex; |
| 293 | align-items: center; | ||
| 294 | gap: 0.5em; | ||
| 295 | } | ||
| 296 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content .qr--set-itemLabelContainer .qr--set-itemIcon:not(.fa-solid) { | ||
| 297 | display: none; | ||
| 298 | } | ||
| 299 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content .qr--set-itemLabelContainer .qr--set-itemLabel { | ||
| 300 | min-width: 24px; | ||
| 301 | } | ||
| 302 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content .qr--set-itemLabel, | ||
| 303 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content .qr--action { | ||
| 200 | margin: 0; | 304 | margin: 0; |
| 201 | } | 305 | } |
| 202 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--set-itemMessage { | 306 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content .qr--set-itemMessage { |
| 203 | font-size: smaller; | 307 | font-size: smaller; |
| 204 | } | 308 | } |
| 205 | #qr--settings .qr--set-qrListActions { | 309 | #qr--settings .qr--set-qrListActions { |
| @@ -212,6 +316,7 @@ | |||
| 212 | #qr--qrOptions { | 316 | #qr--qrOptions { |
| 213 | display: flex; | 317 | display: flex; |
| 214 | flex-direction: column; | 318 | flex-direction: column; |
| 319 | padding-right: 1px; | ||
| 215 | } | 320 | } |
| 216 | #qr--qrOptions > #qr--ctxEditor .qr--ctxItem { | 321 | #qr--qrOptions > #qr--ctxEditor .qr--ctxItem { |
| 217 | display: flex; | 322 | display: flex; |
| @@ -219,6 +324,12 @@ | |||
| 219 | gap: 0.5em; | 324 | gap: 0.5em; |
| 220 | align-items: baseline; | 325 | align-items: baseline; |
| 221 | } | 326 | } |
| 327 | #qr--qrOptions > #qr--autoExec .checkbox_label { | ||
| 328 | text-wrap: nowrap; | ||
| 329 | } | ||
| 330 | #qr--qrOptions > #qr--autoExec .checkbox_label .fa-fw { | ||
| 331 | margin-right: 2px; | ||
| 332 | } | ||
| 222 | @media screen and (max-width: 750px) { | 333 | @media screen and (max-width: 750px) { |
| 223 | body .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor { | 334 | body .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor { |
| 224 | flex-direction: column; | 335 | flex-direction: column; |
| @@ -231,13 +342,79 @@ | |||
| 231 | flex-direction: column; | 342 | flex-direction: column; |
| 232 | } | 343 | } |
| 233 | body .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder { | 344 | body .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder { |
| 234 | min-height: 50svh; | 345 | min-height: 50dvh; |
| 235 | height: 50svh; | 346 | height: 50dvh; |
| 236 | } | 347 | } |
| 237 | } | 348 | } |
| 238 | .popup:has(#qr--modalEditor) { | 349 | .popup:has(#qr--modalEditor) { |
| 239 | aspect-ratio: unset; | 350 | aspect-ratio: unset; |
| 240 | } | 351 | } |
| 352 | .popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) { | ||
| 353 | min-width: unset; | ||
| 354 | min-height: unset; | ||
| 355 | height: auto !important; | ||
| 356 | width: min-content !important; | ||
| 357 | position: absolute; | ||
| 358 | right: 1em; | ||
| 359 | top: 1em; | ||
| 360 | left: unset; | ||
| 361 | bottom: unset; | ||
| 362 | margin: unset; | ||
| 363 | padding: 0; | ||
| 364 | } | ||
| 365 | .popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized)::backdrop { | ||
| 366 | backdrop-filter: unset; | ||
| 367 | background-color: transparent; | ||
| 368 | } | ||
| 369 | .popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-body { | ||
| 370 | flex: 0 0 auto; | ||
| 371 | height: min-content; | ||
| 372 | width: min-content; | ||
| 373 | } | ||
| 374 | .popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-content { | ||
| 375 | flex: 0 0 auto; | ||
| 376 | margin-top: 0; | ||
| 377 | } | ||
| 378 | .popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-content > #qr--modalEditor { | ||
| 379 | max-height: 50vh; | ||
| 380 | } | ||
| 381 | .popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-content > #qr--modalEditor > #qr--main, | ||
| 382 | .popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-content > #qr--modalEditor > #qr--resizeHandle, | ||
| 383 | .popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-content > #qr--modalEditor > #qr--qrOptions > h3, | ||
| 384 | .popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-content > #qr--modalEditor > #qr--qrOptions > #qr--modal-executeButtons, | ||
| 385 | .popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-content > #qr--modalEditor > #qr--qrOptions > #qr--modal-executeProgress { | ||
| 386 | display: none; | ||
| 387 | } | ||
| 388 | .popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-content > #qr--modalEditor #qr--qrOptions { | ||
| 389 | width: auto; | ||
| 390 | } | ||
| 391 | .popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-content > #qr--modalEditor #qr--modal-debugButtons .qr--modal-debugButton#qr--modal-maximize { | ||
| 392 | display: flex; | ||
| 393 | } | ||
| 394 | .popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-content > #qr--modalEditor #qr--modal-debugButtons .qr--modal-debugButton#qr--modal-minimize { | ||
| 395 | display: none; | ||
| 396 | } | ||
| 397 | .popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-content > #qr--modalEditor #qr--modal-debugState { | ||
| 398 | padding-top: 0; | ||
| 399 | } | ||
| 400 | .popup:has(#qr--modalEditor):has(.qr--isExecuting) .popup-controls { | ||
| 401 | display: none; | ||
| 402 | } | ||
| 403 | .popup:has(#qr--modalEditor):has(.qr--isExecuting) .qr--highlight { | ||
| 404 | position: absolute; | ||
| 405 | z-index: 50000; | ||
| 406 | pointer-events: none; | ||
| 407 | background-color: rgba(47, 150, 180, 0.5); | ||
| 408 | } | ||
| 409 | .popup:has(#qr--modalEditor):has(.qr--isExecuting) .qr--highlight.qr--unresolved { | ||
| 410 | background-color: rgba(255, 255, 0, 0.5); | ||
| 411 | } | ||
| 412 | .popup:has(#qr--modalEditor):has(.qr--isExecuting) .qr--highlight-secondary { | ||
| 413 | position: absolute; | ||
| 414 | z-index: 50000; | ||
| 415 | pointer-events: none; | ||
| 416 | border: 3px solid red; | ||
| 417 | } | ||
| 241 | .popup:has(#qr--modalEditor) .popup-content { | 418 | .popup:has(#qr--modalEditor) .popup-content { |
| 242 | display: flex; | 419 | display: flex; |
| 243 | flex-direction: column; | 420 | flex-direction: column; |
| @@ -249,6 +426,67 @@ | |||
| 249 | gap: 1em; | 426 | gap: 1em; |
| 250 | overflow: hidden; | 427 | overflow: hidden; |
| 251 | } | 428 | } |
| 429 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--main > h3:first-child, | ||
| 430 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--main > .qr--labels, | ||
| 431 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings, | ||
| 432 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--qrOptions > h3:first-child, | ||
| 433 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--qrOptions > #qr--ctxEditor, | ||
| 434 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--qrOptions > .qr--ctxEditorActions, | ||
| 435 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--qrOptions > .qr--ctxEditorActions + h3, | ||
| 436 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--qrOptions > .qr--ctxEditorActions + h3 + div { | ||
| 437 | display: none; | ||
| 438 | } | ||
| 439 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder > #qr--modal-message { | ||
| 440 | visibility: hidden; | ||
| 441 | } | ||
| 442 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--modal-debugButtons { | ||
| 443 | display: flex; | ||
| 444 | } | ||
| 445 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--modal-debugButtons .menu_button:not(#qr--modal-minimize, #qr--modal-maximize) { | ||
| 446 | cursor: not-allowed; | ||
| 447 | opacity: 0.5; | ||
| 448 | pointer-events: none; | ||
| 449 | transition: 200ms; | ||
| 450 | border-color: transparent; | ||
| 451 | } | ||
| 452 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting.qr--isPaused #qr--modal-debugButtons .menu_button:not(#qr--modal-minimize, #qr--modal-maximize) { | ||
| 453 | cursor: pointer; | ||
| 454 | opacity: 1; | ||
| 455 | pointer-events: all; | ||
| 456 | } | ||
| 457 | .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 { | ||
| 458 | animation-name: qr--debugPulse; | ||
| 459 | animation-duration: 1500ms; | ||
| 460 | animation-timing-function: ease-in-out; | ||
| 461 | animation-delay: 0s; | ||
| 462 | animation-iteration-count: infinite; | ||
| 463 | } | ||
| 464 | .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 { | ||
| 465 | border-color: #51a351; | ||
| 466 | } | ||
| 467 | .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 { | ||
| 468 | border-color: var(--SmartThemeQuoteColor); | ||
| 469 | } | ||
| 470 | .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 { | ||
| 471 | border-color: var(--SmartThemeQuoteColor); | ||
| 472 | } | ||
| 473 | .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 { | ||
| 474 | border-color: var(--SmartThemeQuoteColor); | ||
| 475 | } | ||
| 476 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--resizeHandle { | ||
| 477 | width: 6px; | ||
| 478 | background-color: var(--SmartThemeBorderColor); | ||
| 479 | border: 2px solid var(--SmartThemeBlurTintColor); | ||
| 480 | transition: border-color 200ms, background-color 200ms; | ||
| 481 | cursor: w-resize; | ||
| 482 | } | ||
| 483 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--resizeHandle:hover { | ||
| 484 | background-color: var(--SmartThemeQuoteColor); | ||
| 485 | border-color: var(--SmartThemeQuoteColor); | ||
| 486 | } | ||
| 487 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--qrOptions { | ||
| 488 | width: var(--width, auto); | ||
| 489 | } | ||
| 252 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main { | 490 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main { |
| 253 | flex: 1 1 auto; | 491 | flex: 1 1 auto; |
| 254 | display: flex; | 492 | display: flex; |
| @@ -260,21 +498,115 @@ | |||
| 260 | display: flex; | 498 | display: flex; |
| 261 | flex-direction: row; | 499 | flex-direction: row; |
| 262 | gap: 0.5em; | 500 | gap: 0.5em; |
| 501 | padding: 1px; | ||
| 263 | } | 502 | } |
| 264 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label { | 503 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label, |
| 504 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label { | ||
| 265 | flex: 1 1 1px; | 505 | flex: 1 1 1px; |
| 266 | display: flex; | 506 | display: flex; |
| 267 | flex-direction: column; | 507 | flex-direction: column; |
| 508 | position: relative; | ||
| 509 | } | ||
| 510 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label.qr--fit, | ||
| 511 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label.qr--fit { | ||
| 512 | flex: 0 0 auto; | ||
| 513 | justify-content: center; | ||
| 514 | } | ||
| 515 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--inputGroup, | ||
| 516 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--inputGroup { | ||
| 517 | display: flex; | ||
| 518 | align-items: baseline; | ||
| 519 | gap: 0.5em; | ||
| 520 | } | ||
| 521 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--inputGroup input, | ||
| 522 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--inputGroup input { | ||
| 523 | flex: 1 1 auto; | ||
| 268 | } | 524 | } |
| 269 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label > .qr--labelText { | 525 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--labelText, |
| 526 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--labelText { | ||
| 270 | flex: 1 1 auto; | 527 | flex: 1 1 auto; |
| 271 | } | 528 | } |
| 272 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label > .qr--labelHint { | 529 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--labelHint, |
| 530 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--labelHint { | ||
| 273 | flex: 1 1 auto; | 531 | flex: 1 1 auto; |
| 274 | } | 532 | } |
| 275 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label > input { | 533 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label input, |
| 534 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label input { | ||
| 276 | flex: 0 0 auto; | 535 | flex: 0 0 auto; |
| 277 | } | 536 | } |
| 537 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList, | ||
| 538 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList { | ||
| 539 | background-color: var(--stcdx--bgColor); | ||
| 540 | border: 1px solid var(--SmartThemeBorderColor); | ||
| 541 | backdrop-filter: blur(var(--SmartThemeBlurStrength)); | ||
| 542 | border-radius: 10px; | ||
| 543 | font-size: smaller; | ||
| 544 | position: absolute; | ||
| 545 | top: 100%; | ||
| 546 | left: 0; | ||
| 547 | right: 0; | ||
| 548 | overflow: auto; | ||
| 549 | margin: 0; | ||
| 550 | padding: 0.5em; | ||
| 551 | max-height: 50vh; | ||
| 552 | list-style: none; | ||
| 553 | z-index: 40000; | ||
| 554 | max-width: 100%; | ||
| 555 | } | ||
| 556 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--modal-switcherItem, | ||
| 557 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList .qr--modal-switcherItem { | ||
| 558 | display: flex; | ||
| 559 | gap: 1em; | ||
| 560 | text-align: left; | ||
| 561 | opacity: 0.75; | ||
| 562 | transition: 200ms; | ||
| 563 | cursor: pointer; | ||
| 564 | } | ||
| 565 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--modal-switcherItem:hover, | ||
| 566 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList .qr--modal-switcherItem:hover { | ||
| 567 | opacity: 1; | ||
| 568 | } | ||
| 569 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--modal-switcherItem.qr--current, | ||
| 570 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList .qr--modal-switcherItem.qr--current { | ||
| 571 | opacity: 1; | ||
| 572 | } | ||
| 573 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--modal-switcherItem.qr--current .qr--label, | ||
| 574 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList .qr--modal-switcherItem.qr--current .qr--label, | ||
| 575 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--modal-switcherItem.qr--current .qr--id, | ||
| 576 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList .qr--modal-switcherItem.qr--current .qr--id { | ||
| 577 | font-weight: bold; | ||
| 578 | } | ||
| 579 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--label, | ||
| 580 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList .qr--label { | ||
| 581 | white-space: nowrap; | ||
| 582 | } | ||
| 583 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--label .menu_button, | ||
| 584 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList .qr--label .menu_button { | ||
| 585 | display: inline-block; | ||
| 586 | height: min-content; | ||
| 587 | width: min-content; | ||
| 588 | margin: 0 0.5em 0 0; | ||
| 589 | } | ||
| 590 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--id, | ||
| 591 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList .qr--id { | ||
| 592 | opacity: 0.5; | ||
| 593 | } | ||
| 594 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--id:before, | ||
| 595 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList .qr--id:before { | ||
| 596 | content: "["; | ||
| 597 | } | ||
| 598 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--id:after, | ||
| 599 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList .qr--id:after { | ||
| 600 | content: "]"; | ||
| 601 | } | ||
| 602 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--message, | ||
| 603 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList .qr--message { | ||
| 604 | height: 1lh; | ||
| 605 | overflow: hidden; | ||
| 606 | text-overflow: ellipsis; | ||
| 607 | white-space: nowrap; | ||
| 608 | opacity: 0.5; | ||
| 609 | } | ||
| 278 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer { | 610 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer { |
| 279 | flex: 1 1 auto; | 611 | flex: 1 1 auto; |
| 280 | display: flex; | 612 | display: flex; |
| @@ -283,8 +615,9 @@ | |||
| 283 | } | 615 | } |
| 284 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings { | 616 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings { |
| 285 | display: flex; | 617 | display: flex; |
| 618 | flex-wrap: wrap; | ||
| 286 | flex-direction: row; | 619 | flex-direction: row; |
| 287 | gap: 1em; | 620 | column-gap: 1em; |
| 288 | color: var(--grey70); | 621 | color: var(--grey70); |
| 289 | font-size: smaller; | 622 | font-size: smaller; |
| 290 | align-items: baseline; | 623 | align-items: baseline; |
| @@ -308,6 +641,11 @@ | |||
| 308 | background-color: var(--ac-style-color-background); | 641 | background-color: var(--ac-style-color-background); |
| 309 | color: var(--ac-style-color-text); | 642 | color: var(--ac-style-color-text); |
| 310 | } | 643 | } |
| 644 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder.qr--noSyntax > #qr--modal-message::-webkit-scrollbar, | ||
| 645 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder.qr--noSyntax > #qr--modal-message::-webkit-scrollbar-thumb { | ||
| 646 | visibility: visible; | ||
| 647 | cursor: unset; | ||
| 648 | } | ||
| 311 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder.qr--noSyntax > #qr--modal-message::selection { | 649 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder.qr--noSyntax > #qr--modal-message::selection { |
| 312 | color: unset; | 650 | color: unset; |
| 313 | background-color: rgba(108 171 251 / 0.25); | 651 | background-color: rgba(108 171 251 / 0.25); |
| @@ -357,11 +695,15 @@ | |||
| 357 | font-family: var(--monoFontFamily); | 695 | font-family: var(--monoFontFamily); |
| 358 | padding: 0.75em; | 696 | padding: 0.75em; |
| 359 | margin: 0; | 697 | margin: 0; |
| 360 | border: none; | ||
| 361 | resize: none; | 698 | resize: none; |
| 362 | line-height: 1.2; | 699 | line-height: 1.2; |
| 363 | border: 1px solid var(--SmartThemeBorderColor); | 700 | border: 1px solid var(--SmartThemeBorderColor); |
| 364 | border-radius: 5px; | 701 | border-radius: 5px; |
| 702 | position: relative; | ||
| 703 | } | ||
| 704 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-icon { | ||
| 705 | height: 100%; | ||
| 706 | aspect-ratio: 1 / 1; | ||
| 365 | } | 707 | } |
| 366 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons { | 708 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons { |
| 367 | display: flex; | 709 | display: flex; |
| @@ -410,6 +752,46 @@ | |||
| 410 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons #qr--modal-stop { | 752 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons #qr--modal-stop { |
| 411 | border-color: #d78872; | 753 | border-color: #d78872; |
| 412 | } | 754 | } |
| 755 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugButtons { | ||
| 756 | display: none; | ||
| 757 | gap: 1em; | ||
| 758 | } | ||
| 759 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugButtons .qr--modal-debugButton { | ||
| 760 | aspect-ratio: 1.25 / 1; | ||
| 761 | width: 2.25em; | ||
| 762 | position: relative; | ||
| 763 | } | ||
| 764 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugButtons .qr--modal-debugButton:not(.fa-solid) { | ||
| 765 | border-width: 1px; | ||
| 766 | border-style: solid; | ||
| 767 | } | ||
| 768 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugButtons .qr--modal-debugButton:not(.fa-solid):after { | ||
| 769 | content: ''; | ||
| 770 | position: absolute; | ||
| 771 | inset: 3px; | ||
| 772 | background-color: var(--SmartThemeBodyColor); | ||
| 773 | mask-size: contain; | ||
| 774 | mask-position: center; | ||
| 775 | mask-repeat: no-repeat; | ||
| 776 | } | ||
| 777 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugButtons .qr--modal-debugButton#qr--modal-resume:after { | ||
| 778 | mask-image: url('/img/step-resume.svg'); | ||
| 779 | } | ||
| 780 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugButtons .qr--modal-debugButton#qr--modal-step:after { | ||
| 781 | mask-image: url('/img/step-over.svg'); | ||
| 782 | } | ||
| 783 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugButtons .qr--modal-debugButton#qr--modal-stepInto:after { | ||
| 784 | mask-image: url('/img/step-into.svg'); | ||
| 785 | } | ||
| 786 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugButtons .qr--modal-debugButton#qr--modal-stepOut:after { | ||
| 787 | mask-image: url('/img/step-out.svg'); | ||
| 788 | } | ||
| 789 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugButtons .qr--modal-debugButton#qr--modal-maximize { | ||
| 790 | display: none; | ||
| 791 | } | ||
| 792 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-send_textarea { | ||
| 793 | flex: 0 0 auto; | ||
| 794 | } | ||
| 413 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeProgress { | 795 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeProgress { |
| 414 | --prog: 0; | 796 | --prog: 0; |
| 415 | --progColor: #92befc; | 797 | --progColor: #92befc; |
| @@ -417,6 +799,7 @@ | |||
| 417 | --progSuccessColor: #51a351; | 799 | --progSuccessColor: #51a351; |
| 418 | --progErrorColor: #bd362f; | 800 | --progErrorColor: #bd362f; |
| 419 | --progAbortedColor: #d78872; | 801 | --progAbortedColor: #d78872; |
| 802 | flex: 0 0 auto; | ||
| 420 | height: 0.5em; | 803 | height: 0.5em; |
| 421 | background-color: var(--black50a); | 804 | background-color: var(--black50a); |
| 422 | position: relative; | 805 | position: relative; |
| @@ -469,6 +852,7 @@ | |||
| 469 | overflow: auto; | 852 | overflow: auto; |
| 470 | min-width: 100%; | 853 | min-width: 100%; |
| 471 | width: 0; | 854 | width: 0; |
| 855 | white-space: pre-wrap; | ||
| 472 | } | 856 | } |
| 473 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeResult.qr--hasResult { | 857 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeResult.qr--hasResult { |
| 474 | display: block; | 858 | display: block; |
| @@ -476,6 +860,150 @@ | |||
| 476 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeResult:before { | 860 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeResult:before { |
| 477 | content: 'Result: '; | 861 | content: 'Result: '; |
| 478 | } | 862 | } |
| 863 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState { | ||
| 864 | display: none; | ||
| 865 | text-align: left; | ||
| 866 | font-size: smaller; | ||
| 867 | font-family: var(--monoFontFamily); | ||
| 868 | color: white; | ||
| 869 | padding: 0.5em 0; | ||
| 870 | overflow: auto; | ||
| 871 | min-width: 100%; | ||
| 872 | width: 0; | ||
| 873 | white-space: pre-wrap; | ||
| 874 | } | ||
| 875 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState.qr--active { | ||
| 876 | display: block; | ||
| 877 | } | ||
| 878 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope { | ||
| 879 | display: grid; | ||
| 880 | grid-template-columns: 0fr 1fr 1fr; | ||
| 881 | column-gap: 0em; | ||
| 882 | } | ||
| 883 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--title { | ||
| 884 | grid-column: 1 / 4; | ||
| 885 | font-weight: bold; | ||
| 886 | font-family: var(--mainFontFamily); | ||
| 887 | background-color: var(--black50a); | ||
| 888 | padding: 0.25em; | ||
| 889 | margin-top: 0.5em; | ||
| 890 | } | ||
| 891 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var, | ||
| 892 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro, | ||
| 893 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe { | ||
| 894 | display: contents; | ||
| 895 | } | ||
| 896 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var:nth-child(2n + 1) .qr--key, | ||
| 897 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro:nth-child(2n + 1) .qr--key, | ||
| 898 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe:nth-child(2n + 1) .qr--key, | ||
| 899 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var:nth-child(2n + 1) .qr--val, | ||
| 900 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro:nth-child(2n + 1) .qr--val, | ||
| 901 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe:nth-child(2n + 1) .qr--val { | ||
| 902 | background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.25); | ||
| 903 | } | ||
| 904 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var:nth-child(2n + 1) .qr--val:nth-child(2n), | ||
| 905 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro:nth-child(2n + 1) .qr--val:nth-child(2n), | ||
| 906 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe:nth-child(2n + 1) .qr--val:nth-child(2n) { | ||
| 907 | background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.125); | ||
| 908 | } | ||
| 909 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var:nth-child(2n + 1) .qr--val:hover, | ||
| 910 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro:nth-child(2n + 1) .qr--val:hover, | ||
| 911 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe:nth-child(2n + 1) .qr--val:hover { | ||
| 912 | background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.5); | ||
| 913 | } | ||
| 914 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var:nth-child(2n) .qr--val:nth-child(2n), | ||
| 915 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro:nth-child(2n) .qr--val:nth-child(2n), | ||
| 916 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe:nth-child(2n) .qr--val:nth-child(2n) { | ||
| 917 | background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.0625); | ||
| 918 | } | ||
| 919 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var:nth-child(2n) .qr--val:hover, | ||
| 920 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro:nth-child(2n) .qr--val:hover, | ||
| 921 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe:nth-child(2n) .qr--val:hover { | ||
| 922 | background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.5); | ||
| 923 | } | ||
| 924 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var.qr--isHidden .qr--key, | ||
| 925 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro.qr--isHidden .qr--key, | ||
| 926 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe.qr--isHidden .qr--key, | ||
| 927 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var.qr--isHidden .qr--val, | ||
| 928 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro.qr--isHidden .qr--val, | ||
| 929 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe.qr--isHidden .qr--val { | ||
| 930 | opacity: 0.5; | ||
| 931 | } | ||
| 932 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var .qr--val, | ||
| 933 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro .qr--val, | ||
| 934 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe .qr--val { | ||
| 935 | grid-column: 2 / 4; | ||
| 936 | } | ||
| 937 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var .qr--val.qr--singleCol, | ||
| 938 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro .qr--val.qr--singleCol, | ||
| 939 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe .qr--val.qr--singleCol { | ||
| 940 | grid-column: unset; | ||
| 941 | } | ||
| 942 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var .qr--val.qr--simple:before, | ||
| 943 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro .qr--val.qr--simple:before, | ||
| 944 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe .qr--val.qr--simple:before, | ||
| 945 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var .qr--val.qr--simple:after, | ||
| 946 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro .qr--val.qr--simple:after, | ||
| 947 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe .qr--val.qr--simple:after { | ||
| 948 | content: '"'; | ||
| 949 | color: var(--SmartThemeQuoteColor); | ||
| 950 | } | ||
| 951 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var .qr--val.qr--unresolved:after, | ||
| 952 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro .qr--val.qr--unresolved:after, | ||
| 953 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe .qr--val.qr--unresolved:after { | ||
| 954 | content: '-UNRESOLVED-'; | ||
| 955 | font-style: italic; | ||
| 956 | color: var(--SmartThemeQuoteColor); | ||
| 957 | } | ||
| 958 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--key { | ||
| 959 | margin-left: 0.5em; | ||
| 960 | padding-right: 1em; | ||
| 961 | } | ||
| 962 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--key:after { | ||
| 963 | content: ": "; | ||
| 964 | } | ||
| 965 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe > .qr--key:before, | ||
| 966 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro > .qr--key:before { | ||
| 967 | content: "{{"; | ||
| 968 | } | ||
| 969 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe > .qr--key:after, | ||
| 970 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro > .qr--key:after { | ||
| 971 | content: "}}: "; | ||
| 972 | } | ||
| 973 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--scope { | ||
| 974 | display: contents; | ||
| 975 | } | ||
| 976 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--scope .qr--pipe .qr--key, | ||
| 977 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--scope .qr--pipe .qr--val { | ||
| 978 | opacity: 0.5; | ||
| 979 | } | ||
| 980 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--stack { | ||
| 981 | display: grid; | ||
| 982 | grid-template-columns: 1fr 0fr; | ||
| 983 | } | ||
| 984 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--stack .qr--title { | ||
| 985 | grid-column: 1 / 3; | ||
| 986 | font-weight: bold; | ||
| 987 | font-family: var(--mainFontFamily); | ||
| 988 | background-color: var(--black50a); | ||
| 989 | padding: 0.25em; | ||
| 990 | margin-top: 1em; | ||
| 991 | } | ||
| 992 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--stack .qr--item { | ||
| 993 | display: contents; | ||
| 994 | } | ||
| 995 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--stack .qr--item:nth-child(2n + 1) .qr--name, | ||
| 996 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--stack .qr--item:nth-child(2n + 1) .qr--source { | ||
| 997 | background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.25); | ||
| 998 | } | ||
| 999 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--stack .qr--item .qr--name { | ||
| 1000 | margin-left: 0.5em; | ||
| 1001 | } | ||
| 1002 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--stack .qr--item .qr--source { | ||
| 1003 | opacity: 0.5; | ||
| 1004 | text-align: right; | ||
| 1005 | white-space: nowrap; | ||
| 1006 | } | ||
| 479 | @keyframes qr--progressPulse { | 1007 | @keyframes qr--progressPulse { |
| 480 | 0%, | 1008 | 0%, |
| 481 | 100% { | 1009 | 100% { |
| @@ -485,9 +1013,63 @@ | |||
| 485 | background-color: var(--progFlashColor); | 1013 | background-color: var(--progFlashColor); |
| 486 | } | 1014 | } |
| 487 | } | 1015 | } |
| 1016 | @keyframes qr--debugPulse { | ||
| 1017 | 0%, | ||
| 1018 | 100% { | ||
| 1019 | border-color: #51a351; | ||
| 1020 | } | ||
| 1021 | 50% { | ||
| 1022 | border-color: #92befc; | ||
| 1023 | } | ||
| 1024 | } | ||
| 488 | .popup.qr--hide { | 1025 | .popup.qr--hide { |
| 489 | opacity: 0 !important; | 1026 | opacity: 0 !important; |
| 490 | } | 1027 | } |
| 491 | .popup.qr--hide::backdrop { | 1028 | .popup.qr--hide::backdrop { |
| 492 | opacity: 0 !important; | 1029 | opacity: 0 !important; |
| 493 | } | 1030 | } |
| 1031 | .popup.qr--hide::backdrop { | ||
| 1032 | opacity: 0 !important; | ||
| 1033 | } | ||
| 1034 | .popup:has(.qr--transferModal) .popup-button-ok { | ||
| 1035 | display: flex; | ||
| 1036 | align-items: center; | ||
| 1037 | flex-direction: column; | ||
| 1038 | white-space: pre; | ||
| 1039 | font-weight: normal; | ||
| 1040 | box-shadow: 0 0 0; | ||
| 1041 | transition: 200ms; | ||
| 1042 | } | ||
| 1043 | .popup:has(.qr--transferModal) .popup-button-ok:after { | ||
| 1044 | content: 'Transfer'; | ||
| 1045 | height: 0; | ||
| 1046 | overflow: hidden; | ||
| 1047 | font-weight: bold; | ||
| 1048 | } | ||
| 1049 | .popup:has(.qr--transferModal) .qr--copy { | ||
| 1050 | display: flex; | ||
| 1051 | align-items: center; | ||
| 1052 | flex-direction: column; | ||
| 1053 | white-space: pre; | ||
| 1054 | font-weight: normal; | ||
| 1055 | box-shadow: 0 0 0; | ||
| 1056 | transition: 200ms; | ||
| 1057 | } | ||
| 1058 | .popup:has(.qr--transferModal) .qr--copy:after { | ||
| 1059 | content: 'Copy'; | ||
| 1060 | height: 0; | ||
| 1061 | overflow: hidden; | ||
| 1062 | font-weight: bold; | ||
| 1063 | } | ||
| 1064 | .popup:has(.qr--transferModal):has(.qr--transferSelect:focus) .popup-button-ok { | ||
| 1065 | font-weight: bold; | ||
| 1066 | box-shadow: 0 0 10px; | ||
| 1067 | } | ||
| 1068 | .popup:has(.qr--transferModal):has(.qr--transferSelect:focus).qr--isCopy .popup-button-ok { | ||
| 1069 | font-weight: normal; | ||
| 1070 | box-shadow: 0 0 0; | ||
| 1071 | } | ||
| 1072 | .popup:has(.qr--transferModal):has(.qr--transferSelect:focus).qr--isCopy .qr--copy { | ||
| 1073 | font-weight: bold; | ||
| 1074 | box-shadow: 0 0 10px; | ||
| 1075 | } | ||
| @@ -1,3 +1,18 @@ | |||
| 1 | @keyframes qr--success { | ||
| 2 | 0%, 100% { | ||
| 3 | color: var(--SmartThemeBodyColor); | ||
| 4 | } | ||
| 5 | 25%, 75% { | ||
| 6 | color: rgb(81, 163, 81); | ||
| 7 | } | ||
| 8 | } | ||
| 9 | &.qr--success { | ||
| 10 | animation-name: qr--success; | ||
| 11 | animation-duration: 3s; | ||
| 12 | animation-timing-function: linear; | ||
| 13 | animation-delay: 0s; | ||
| 14 | animation-iteration-count: 1; | ||
| 15 | } | ||
| 1 | #qr--bar { | 16 | #qr--bar { |
| 2 | outline: none; | 17 | outline: none; |
| 3 | margin: 0; | 18 | margin: 0; |
| @@ -50,6 +65,18 @@ | |||
| 50 | #qr--bar, | 65 | #qr--bar, |
| 51 | #qr--popout>.qr--body { | 66 | #qr--popout>.qr--body { |
| 52 | >.qr--buttons { | 67 | >.qr--buttons { |
| 68 | --qr--color: transparent; | ||
| 69 | &.qr--color { | ||
| 70 | background-color: var(--qr--color); | ||
| 71 | } | ||
| 72 | &.qr--borderColor { | ||
| 73 | background-color: transparent; | ||
| 74 | border-left: 5px solid var(--qr--color); | ||
| 75 | border-right: 5px solid var(--qr--color); | ||
| 76 | } | ||
| 77 | &:has(.qr--buttons.qr--color) { | ||
| 78 | margin: 5px; | ||
| 79 | } | ||
| 53 | margin: 0; | 80 | margin: 0; |
| 54 | padding: 0; | 81 | padding: 0; |
| 55 | display: flex; | 82 | display: flex; |
| @@ -60,6 +87,25 @@ | |||
| 60 | 87 | ||
| 61 | >.qr--buttons { | 88 | >.qr--buttons { |
| 62 | display: contents; | 89 | display: contents; |
| 90 | &.qr--color { | ||
| 91 | .qr--button:before { | ||
| 92 | content: ''; | ||
| 93 | background-color: var(--qr--color); | ||
| 94 | position: absolute; | ||
| 95 | inset: -5px; | ||
| 96 | z-index: -1; | ||
| 97 | } | ||
| 98 | &.qr--borderColor { | ||
| 99 | .qr--button:before { | ||
| 100 | display: none; | ||
| 101 | } | ||
| 102 | &:before, &:after { | ||
| 103 | content: ''; | ||
| 104 | width: 5px; | ||
| 105 | background-color: var(--qr--color); | ||
| 106 | } | ||
| 107 | } | ||
| 108 | } | ||
| 63 | } | 109 | } |
| 64 | 110 | ||
| 65 | .qr--button { | 111 | .qr--button { |
| @@ -75,10 +121,17 @@ | |||
| 75 | align-items: center; | 121 | align-items: center; |
| 76 | justify-content: center; | 122 | justify-content: center; |
| 77 | text-align: center; | 123 | text-align: center; |
| 124 | position: relative; | ||
| 78 | 125 | ||
| 79 | &:hover { | 126 | &:hover { |
| 80 | opacity: 1; | 127 | background-color: rgb(30% 30% 30%); |
| 81 | filter: brightness(1.2); | 128 | } |
| 129 | |||
| 130 | .qr--hidden { | ||
| 131 | display: none; | ||
| 132 | } | ||
| 133 | .qr--button-icon { | ||
| 134 | margin: 0 0.5em; | ||
| 82 | } | 135 | } |
| 83 | 136 | ||
| 84 | >.qr--button-expander { | 137 | >.qr--button-expander { |
| @@ -211,14 +264,41 @@ | |||
| 211 | .qr--set-qrListContents> { | 264 | .qr--set-qrListContents> { |
| 212 | padding: 0 0.5em; | 265 | padding: 0 0.5em; |
| 213 | 266 | ||
| 214 | >.qr--set-item { | 267 | >.qr--set-item .qr--set-itemAdder { |
| 268 | display: flex; | ||
| 269 | align-items: center; | ||
| 270 | opacity: 0; | ||
| 271 | transition: 100ms; | ||
| 272 | margin: -2px 0 -11px 0; | ||
| 273 | position: relative; | ||
| 274 | .qr--actions { | ||
| 275 | display: flex; | ||
| 276 | gap: 0.25em; | ||
| 277 | flex: 0 0 auto; | ||
| 278 | .qr--action { | ||
| 279 | margin: 0; | ||
| 280 | } | ||
| 281 | } | ||
| 282 | &:before, &:after { | ||
| 283 | content: ""; | ||
| 284 | display: block; | ||
| 285 | flex: 1 1 auto; | ||
| 286 | border: 1px solid; | ||
| 287 | margin: 0 1em; | ||
| 288 | height: 0; | ||
| 289 | } | ||
| 290 | &:hover, &:focus-within { | ||
| 291 | opacity: 1; | ||
| 292 | } | ||
| 293 | } | ||
| 294 | >.qr--set-item .qr--content { | ||
| 215 | display: flex; | 295 | display: flex; |
| 216 | flex-direction: row; | 296 | flex-direction: row; |
| 217 | gap: 0.5em; | 297 | gap: 0.5em; |
| 218 | align-items: baseline; | 298 | align-items: baseline; |
| 219 | padding: 0.25em 0; | 299 | padding: 0.25em 0; |
| 220 | 300 | ||
| 221 | > :nth-child(1) { | 301 | > :nth-child(2) { |
| 222 | flex: 0 0 auto; | 302 | flex: 0 0 auto; |
| 223 | } | 303 | } |
| 224 | 304 | ||
| @@ -235,13 +315,29 @@ | |||
| 235 | } | 315 | } |
| 236 | 316 | ||
| 237 | > :nth-child(5) { | 317 | > :nth-child(5) { |
| 238 | flex: 0 0 auto; | 318 | flex: 0 1 auto; |
| 319 | display: flex; | ||
| 320 | gap: 0.25em; | ||
| 321 | justify-content: flex-end; | ||
| 322 | flex-wrap: wrap; | ||
| 239 | } | 323 | } |
| 240 | 324 | ||
| 241 | >.drag-handle { | 325 | >.drag-handle { |
| 242 | padding: 0.75em; | 326 | padding: 0.75em; |
| 243 | } | 327 | } |
| 244 | 328 | ||
| 329 | .qr--set-itemLabelContainer { | ||
| 330 | display: flex; | ||
| 331 | align-items: center; | ||
| 332 | gap: 0.5em; | ||
| 333 | .qr--set-itemIcon:not(.fa-solid) { | ||
| 334 | display: none; | ||
| 335 | } | ||
| 336 | .qr--set-itemLabel { | ||
| 337 | min-width: 24px; | ||
| 338 | } | ||
| 339 | } | ||
| 340 | |||
| 245 | .qr--set-itemLabel, | 341 | .qr--set-itemLabel, |
| 246 | .qr--action { | 342 | .qr--action { |
| 247 | margin: 0; | 343 | margin: 0; |
| @@ -251,6 +347,8 @@ | |||
| 251 | font-size: smaller; | 347 | font-size: smaller; |
| 252 | } | 348 | } |
| 253 | } | 349 | } |
| 350 | |||
| 351 | |||
| 254 | } | 352 | } |
| 255 | } | 353 | } |
| 256 | 354 | ||
| @@ -270,6 +368,7 @@ | |||
| 270 | #qr--qrOptions { | 368 | #qr--qrOptions { |
| 271 | display: flex; | 369 | display: flex; |
| 272 | flex-direction: column; | 370 | flex-direction: column; |
| 371 | padding-right: 1px; | ||
| 273 | 372 | ||
| 274 | >#qr--ctxEditor { | 373 | >#qr--ctxEditor { |
| 275 | .qr--ctxItem { | 374 | .qr--ctxItem { |
| @@ -279,6 +378,15 @@ | |||
| 279 | align-items: baseline; | 378 | align-items: baseline; |
| 280 | } | 379 | } |
| 281 | } | 380 | } |
| 381 | >#qr--autoExec { | ||
| 382 | .checkbox_label { | ||
| 383 | text-wrap: nowrap; | ||
| 384 | |||
| 385 | .fa-fw { | ||
| 386 | margin-right: 2px; | ||
| 387 | } | ||
| 388 | } | ||
| 389 | } | ||
| 282 | } | 390 | } |
| 283 | 391 | ||
| 284 | 392 | ||
| @@ -297,8 +405,8 @@ | |||
| 297 | } | 405 | } |
| 298 | 406 | ||
| 299 | >#qr--main>.qr--modal-messageContainer>#qr--modal-messageHolder { | 407 | >#qr--main>.qr--modal-messageContainer>#qr--modal-messageHolder { |
| 300 | min-height: 50svh; | 408 | min-height: 50dvh; |
| 301 | height: 50svh; | 409 | height: 50dvh; |
| 302 | } | 410 | } |
| 303 | } | 411 | } |
| 304 | } | 412 | } |
| @@ -306,6 +414,78 @@ | |||
| 306 | .popup:has(#qr--modalEditor) { | 414 | .popup:has(#qr--modalEditor) { |
| 307 | aspect-ratio: unset; | 415 | aspect-ratio: unset; |
| 308 | 416 | ||
| 417 | &:has(.qr--isExecuting.qr--minimized) { | ||
| 418 | min-width: unset; | ||
| 419 | min-height: unset; | ||
| 420 | height: auto !important; | ||
| 421 | width: min-content !important; | ||
| 422 | position: absolute; | ||
| 423 | right: 1em; | ||
| 424 | top: 1em; | ||
| 425 | left: unset; | ||
| 426 | bottom: unset; | ||
| 427 | margin: unset; | ||
| 428 | padding: 0; | ||
| 429 | &::backdrop { | ||
| 430 | backdrop-filter: unset; | ||
| 431 | background-color: transparent; | ||
| 432 | } | ||
| 433 | .popup-body { | ||
| 434 | flex: 0 0 auto; | ||
| 435 | height: min-content; | ||
| 436 | width: min-content; | ||
| 437 | } | ||
| 438 | .popup-content { | ||
| 439 | flex: 0 0 auto; | ||
| 440 | margin-top: 0; | ||
| 441 | |||
| 442 | > #qr--modalEditor { | ||
| 443 | max-height: 50vh; | ||
| 444 | > #qr--main, | ||
| 445 | > #qr--resizeHandle, | ||
| 446 | > #qr--qrOptions > h3, | ||
| 447 | > #qr--qrOptions > #qr--modal-executeButtons, | ||
| 448 | > #qr--qrOptions > #qr--modal-executeProgress | ||
| 449 | { | ||
| 450 | display: none; | ||
| 451 | } | ||
| 452 | #qr--qrOptions { | ||
| 453 | width: auto; | ||
| 454 | } | ||
| 455 | #qr--modal-debugButtons .qr--modal-debugButton#qr--modal-maximize { | ||
| 456 | display: flex; | ||
| 457 | } | ||
| 458 | #qr--modal-debugButtons .qr--modal-debugButton#qr--modal-minimize { | ||
| 459 | display: none; | ||
| 460 | } | ||
| 461 | #qr--modal-debugState { | ||
| 462 | padding-top: 0; | ||
| 463 | } | ||
| 464 | } | ||
| 465 | } | ||
| 466 | } | ||
| 467 | &:has(.qr--isExecuting) { | ||
| 468 | .popup-controls { | ||
| 469 | display: none; | ||
| 470 | } | ||
| 471 | |||
| 472 | .qr--highlight { | ||
| 473 | position: absolute; | ||
| 474 | z-index: 50000; | ||
| 475 | pointer-events: none; | ||
| 476 | background-color: rgb(47 150 180 / 0.5); | ||
| 477 | &.qr--unresolved { | ||
| 478 | background-color: rgb(255 255 0 / 0.5); | ||
| 479 | } | ||
| 480 | } | ||
| 481 | .qr--highlight-secondary { | ||
| 482 | position: absolute; | ||
| 483 | z-index: 50000; | ||
| 484 | pointer-events: none; | ||
| 485 | border: 3px solid red; | ||
| 486 | } | ||
| 487 | } | ||
| 488 | |||
| 309 | .popup-content { | 489 | .popup-content { |
| 310 | display: flex; | 490 | display: flex; |
| 311 | flex-direction: column; | 491 | flex-direction: column; |
| @@ -317,140 +497,262 @@ | |||
| 317 | gap: 1em; | 497 | gap: 1em; |
| 318 | overflow: hidden; | 498 | overflow: hidden; |
| 319 | 499 | ||
| 320 | >#qr--main { | 500 | &.qr--isExecuting { |
| 321 | flex: 1 1 auto; | 501 | #qr--main > h3:first-child, |
| 322 | display: flex; | 502 | #qr--main > .qr--labels, |
| 323 | flex-direction: column; | 503 | #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings, |
| 324 | overflow: hidden; | 504 | #qr--qrOptions > h3:first-child, |
| 325 | 505 | #qr--qrOptions > #qr--ctxEditor, | |
| 326 | >.qr--labels { | 506 | #qr--qrOptions > .qr--ctxEditorActions, |
| 327 | flex: 0 0 auto; | 507 | #qr--qrOptions > .qr--ctxEditorActions + h3, |
| 328 | display: flex; | 508 | #qr--qrOptions > .qr--ctxEditorActions + h3 + div |
| 329 | flex-direction: row; | 509 | { |
| 330 | gap: 0.5em; | 510 | display: none; |
| 331 | 511 | } | |
| 332 | >label { | 512 | #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder > #qr--modal-message { |
| 333 | flex: 1 1 1px; | 513 | visibility: hidden; |
| 334 | display: flex; | 514 | } |
| 335 | flex-direction: column; | 515 | #qr--modal-debugButtons { |
| 336 | 516 | display: flex; | |
| 337 | >.qr--labelText { | 517 | .menu_button:not(#qr--modal-minimize, #qr--modal-maximize) { |
| 338 | flex: 1 1 auto; | 518 | cursor: not-allowed; |
| 519 | opacity: 0.5; | ||
| 520 | pointer-events: none; | ||
| 521 | transition: 200ms; | ||
| 522 | border-color: transparent; | ||
| 523 | } | ||
| 524 | } | ||
| 525 | &.qr--isPaused #qr--modal-debugButtons { | ||
| 526 | .menu_button:not(#qr--modal-minimize, #qr--modal-maximize) { | ||
| 527 | cursor: pointer; | ||
| 528 | opacity: 1; | ||
| 529 | pointer-events: all; | ||
| 530 | &#qr--modal-resume { | ||
| 531 | animation-name: qr--debugPulse; | ||
| 532 | animation-duration: 1500ms; | ||
| 533 | animation-timing-function: ease-in-out; | ||
| 534 | animation-delay: 0s; | ||
| 535 | animation-iteration-count: infinite; | ||
| 339 | } | 536 | } |
| 340 | 537 | &#qr--modal-resume { | |
| 341 | >.qr--labelHint { | 538 | border-color: rgb(81, 163, 81); |
| 342 | flex: 1 1 auto; | ||
| 343 | } | 539 | } |
| 344 | 540 | &#qr--modal-step { | |
| 345 | >input { | 541 | border-color: var(--SmartThemeQuoteColor); |
| 346 | flex: 0 0 auto; | 542 | } |
| 543 | &#qr--modal-stepInto { | ||
| 544 | border-color: var(--SmartThemeQuoteColor); | ||
| 545 | } | ||
| 546 | &#qr--modal-stepOut { | ||
| 547 | border-color: var(--SmartThemeQuoteColor); | ||
| 347 | } | 548 | } |
| 348 | } | 549 | } |
| 349 | } | 550 | } |
| 350 | 551 | #qr--resizeHandle { | |
| 351 | >.qr--modal-messageContainer { | 552 | width: 6px; |
| 352 | flex: 1 1 auto; | 553 | background-color: var(--SmartThemeBorderColor); |
| 353 | display: flex; | 554 | border: 2px solid var(--SmartThemeBlurTintColor); |
| 354 | flex-direction: column; | 555 | transition: border-color 200ms, background-color 200ms; |
| 355 | overflow: hidden; | 556 | cursor: w-resize; |
| 356 | 557 | &:hover { | |
| 357 | >.qr--modal-editorSettings { | 558 | background-color: var(--SmartThemeQuoteColor); |
| 358 | display: flex; | 559 | border-color: var(--SmartThemeQuoteColor); |
| 359 | flex-direction: row; | 560 | } |
| 360 | gap: 1em; | 561 | } |
| 361 | color: var(--grey70); | 562 | #qr--qrOptions { |
| 362 | font-size: smaller; | 563 | width: var(--width, auto); |
| 363 | align-items: baseline; | 564 | } |
| 364 | 565 | } | |
| 365 | >.checkbox_label { | 566 | |
| 366 | white-space: nowrap; | 567 | > #qr--main { |
| 367 | 568 | flex: 1 1 auto; | |
| 368 | >input { | 569 | display: flex; |
| 369 | font-size: inherit; | 570 | flex-direction: column; |
| 370 | } | 571 | overflow: hidden; |
| 572 | > .qr--labels { | ||
| 573 | flex: 0 0 auto; | ||
| 574 | display: flex; | ||
| 575 | flex-direction: row; | ||
| 576 | gap: 0.5em; | ||
| 577 | padding: 1px; | ||
| 578 | > label, > .label { | ||
| 579 | flex: 1 1 1px; | ||
| 580 | display: flex; | ||
| 581 | flex-direction: column; | ||
| 582 | position: relative; | ||
| 583 | &.qr--fit { | ||
| 584 | flex: 0 0 auto; | ||
| 585 | justify-content: center; | ||
| 371 | } | 586 | } |
| 372 | } | 587 | .qr--inputGroup { |
| 373 | 588 | display: flex; | |
| 374 | >#qr--modal-messageHolder { | 589 | align-items: baseline; |
| 375 | flex: 1 1 auto; | 590 | gap: 0.5em; |
| 376 | display: grid; | 591 | input { |
| 377 | text-align: left; | 592 | flex: 1 1 auto; |
| 378 | overflow: hidden; | ||
| 379 | |||
| 380 | &.qr--noSyntax { | ||
| 381 | >#qr--modal-messageSyntax { | ||
| 382 | display: none; | ||
| 383 | } | 593 | } |
| 384 | 594 | } | |
| 385 | >#qr--modal-message { | 595 | .qr--labelText { |
| 386 | background-color: var(--ac-style-color-background); | 596 | flex: 1 1 auto; |
| 387 | color: var(--ac-style-color-text); | 597 | } |
| 388 | 598 | .qr--labelHint { | |
| 389 | &::selection { | 599 | flex: 1 1 auto; |
| 390 | color: unset; | 600 | } |
| 391 | background-color: rgba(108 171 251 / 0.25); | 601 | input { |
| 392 | 602 | flex: 0 0 auto; | |
| 393 | @supports (color: rgb(from white r g b / 0.25)) { | 603 | } |
| 394 | background-color: rgb(from var(--ac-style-color-matchedText) r g b / 0.25); | 604 | .qr--modal-switcherList { |
| 605 | background-color: var(--stcdx--bgColor); | ||
| 606 | border: 1px solid var(--SmartThemeBorderColor); | ||
| 607 | backdrop-filter: blur(var(--SmartThemeBlurStrength)); | ||
| 608 | border-radius: 10px; | ||
| 609 | font-size: smaller; | ||
| 610 | position: absolute; | ||
| 611 | top: 100%; | ||
| 612 | left: 0; | ||
| 613 | right: 0; | ||
| 614 | overflow: auto; | ||
| 615 | margin: 0; | ||
| 616 | padding: 0.5em; | ||
| 617 | max-height: 50vh; | ||
| 618 | list-style: none; | ||
| 619 | z-index: 40000; | ||
| 620 | max-width: 100%; | ||
| 621 | .qr--modal-switcherItem { | ||
| 622 | display: flex; | ||
| 623 | gap: 1em; | ||
| 624 | text-align: left; | ||
| 625 | opacity: 0.75; | ||
| 626 | transition: 200ms; | ||
| 627 | cursor: pointer; | ||
| 628 | &:hover { | ||
| 629 | opacity: 1; | ||
| 630 | } | ||
| 631 | &.qr--current { | ||
| 632 | opacity: 1; | ||
| 633 | .qr--label, .qr--id { | ||
| 634 | font-weight: bold; | ||
| 395 | } | 635 | } |
| 396 | } | 636 | } |
| 397 | } | 637 | } |
| 398 | } | 638 | .qr--label { |
| 399 | 639 | white-space: nowrap; | |
| 400 | >#qr--modal-messageSyntax { | 640 | .menu_button { |
| 401 | grid-column: 1; | 641 | display: inline-block; |
| 402 | grid-row: 1; | 642 | height: min-content; |
| 403 | padding: 0; | 643 | width: min-content; |
| 404 | margin: 0; | 644 | margin: 0 0.5em 0 0; |
| 405 | border: none; | 645 | } |
| 406 | overflow: hidden; | ||
| 407 | min-width: 100%; | ||
| 408 | width: 0; | ||
| 409 | |||
| 410 | >#qr--modal-messageSyntaxInner { | ||
| 411 | height: 100%; | ||
| 412 | } | 646 | } |
| 413 | } | 647 | .qr--id { |
| 414 | 648 | opacity: 0.5; | |
| 415 | >#qr--modal-message { | 649 | &:before { content: "["; } |
| 416 | background-color: transparent; | 650 | &:after { content: "]"; } |
| 417 | color: transparent; | ||
| 418 | grid-column: 1; | ||
| 419 | grid-row: 1; | ||
| 420 | caret-color: var(--ac-style-color-text); | ||
| 421 | overflow: auto; | ||
| 422 | |||
| 423 | &::-webkit-scrollbar, | ||
| 424 | &::-webkit-scrollbar-thumb { | ||
| 425 | visibility: hidden; | ||
| 426 | cursor: default; | ||
| 427 | } | 651 | } |
| 428 | 652 | .qr--message { | |
| 429 | &::selection { | 653 | height: 1lh; |
| 430 | color: transparent; | 654 | overflow: hidden; |
| 431 | background-color: rgba(108 171 251 / 0.25); | 655 | text-overflow: ellipsis; |
| 432 | 656 | white-space: nowrap; | |
| 433 | @supports (color: rgb(from white r g b / 0.25)) { | 657 | opacity: 0.5; |
| 434 | background-color: rgb(from var(--ac-style-color-matchedText) r g b / 0.25); | ||
| 435 | } | ||
| 436 | } | 658 | } |
| 437 | } | 659 | } |
| 438 | 660 | } | |
| 439 | #qr--modal-message, | 661 | } |
| 440 | #qr--modal-messageSyntaxInner { | 662 | > .qr--modal-messageContainer { |
| 441 | font-family: var(--monoFontFamily); | 663 | flex: 1 1 auto; |
| 442 | padding: 0.75em; | 664 | display: flex; |
| 443 | margin: 0; | 665 | flex-direction: column; |
| 444 | border: none; | 666 | overflow: hidden; |
| 445 | resize: none; | 667 | > .qr--modal-editorSettings { |
| 446 | line-height: 1.2; | 668 | display: flex; |
| 447 | border: 1px solid var(--SmartThemeBorderColor); | 669 | flex-wrap: wrap; |
| 448 | border-radius: 5px; | 670 | flex-direction: row; |
| 449 | } | 671 | column-gap: 1em; |
| 450 | } | 672 | color: var(--grey70); |
| 451 | } | 673 | font-size: smaller; |
| 674 | align-items: baseline; | ||
| 675 | > .checkbox_label { | ||
| 676 | white-space: nowrap; | ||
| 677 | > input { | ||
| 678 | font-size: inherit; | ||
| 679 | } | ||
| 680 | } | ||
| 681 | } | ||
| 682 | > #qr--modal-messageHolder { | ||
| 683 | flex: 1 1 auto; | ||
| 684 | display: grid; | ||
| 685 | text-align: left; | ||
| 686 | overflow: hidden; | ||
| 687 | &.qr--noSyntax { | ||
| 688 | > #qr--modal-messageSyntax { | ||
| 689 | display: none; | ||
| 690 | } | ||
| 691 | > #qr--modal-message { | ||
| 692 | background-color: var(--ac-style-color-background); | ||
| 693 | color: var(--ac-style-color-text); | ||
| 694 | &::-webkit-scrollbar, &::-webkit-scrollbar-thumb { | ||
| 695 | visibility: visible; | ||
| 696 | cursor: unset; | ||
| 697 | } | ||
| 698 | &::selection { | ||
| 699 | color: unset; | ||
| 700 | background-color: rgba(108 171 251 / 0.25); | ||
| 701 | @supports (color: rgb(from white r g b / 0.25)) { | ||
| 702 | background-color: rgb(from var(--ac-style-color-matchedText) r g b / 0.25); | ||
| 703 | } | ||
| 704 | } | ||
| 705 | } | ||
| 706 | } | ||
| 707 | > #qr--modal-messageSyntax { | ||
| 708 | grid-column: 1; | ||
| 709 | grid-row: 1; | ||
| 710 | padding: 0; | ||
| 711 | margin: 0; | ||
| 712 | border: none; | ||
| 713 | overflow: hidden; | ||
| 714 | min-width: 100%; | ||
| 715 | width: 0; | ||
| 716 | > #qr--modal-messageSyntaxInner { | ||
| 717 | height: 100%; | ||
| 718 | } | ||
| 719 | } | ||
| 720 | > #qr--modal-message { | ||
| 721 | background-color: transparent; | ||
| 722 | color: transparent; | ||
| 723 | grid-column: 1; | ||
| 724 | grid-row: 1; | ||
| 725 | caret-color: var(--ac-style-color-text); | ||
| 726 | overflow: auto; | ||
| 727 | &::-webkit-scrollbar, &::-webkit-scrollbar-thumb { | ||
| 728 | visibility: hidden; | ||
| 729 | cursor: default; | ||
| 730 | } | ||
| 731 | &::selection { | ||
| 732 | color: transparent; | ||
| 733 | background-color: rgba(108 171 251 / 0.25); | ||
| 734 | @supports (color: rgb(from white r g b / 0.25)) { | ||
| 735 | background-color: rgb(from var(--ac-style-color-matchedText) r g b / 0.25); | ||
| 736 | } | ||
| 737 | } | ||
| 738 | } | ||
| 739 | #qr--modal-message, #qr--modal-messageSyntaxInner { | ||
| 740 | font-family: var(--monoFontFamily); | ||
| 741 | padding: 0.75em; | ||
| 742 | margin: 0; | ||
| 743 | resize: none; | ||
| 744 | line-height: 1.2; | ||
| 745 | border: 1px solid var(--SmartThemeBorderColor); | ||
| 746 | border-radius: 5px; | ||
| 747 | position: relative; | ||
| 748 | } | ||
| 749 | } | ||
| 750 | } | ||
| 751 | } | ||
| 752 | #qr--modal-icon { | ||
| 753 | height: 100%; | ||
| 754 | aspect-ratio: 1 / 1; | ||
| 452 | } | 755 | } |
| 453 | |||
| 454 | #qr--modal-executeButtons { | 756 | #qr--modal-executeButtons { |
| 455 | display: flex; | 757 | display: flex; |
| 456 | gap: 1em; | 758 | gap: 1em; |
| @@ -510,6 +812,47 @@ | |||
| 510 | border-color: rgb(215, 136, 114); | 812 | border-color: rgb(215, 136, 114); |
| 511 | } | 813 | } |
| 512 | } | 814 | } |
| 815 | #qr--modal-debugButtons { | ||
| 816 | display: none; | ||
| 817 | gap: 1em; | ||
| 818 | .qr--modal-debugButton { | ||
| 819 | aspect-ratio: 1.25 / 1; | ||
| 820 | width: 2.25em; | ||
| 821 | position: relative; | ||
| 822 | &:not(.fa-solid) { | ||
| 823 | border-width: 1px; | ||
| 824 | border-style: solid; | ||
| 825 | &:after { | ||
| 826 | content: ''; | ||
| 827 | position: absolute; | ||
| 828 | inset: 3px; | ||
| 829 | background-color: var(--SmartThemeBodyColor); | ||
| 830 | mask-size: contain; | ||
| 831 | mask-position: center; | ||
| 832 | mask-repeat: no-repeat; | ||
| 833 | } | ||
| 834 | } | ||
| 835 | &#qr--modal-resume:after { | ||
| 836 | mask-image: url('/img/step-resume.svg'); | ||
| 837 | } | ||
| 838 | &#qr--modal-step:after { | ||
| 839 | mask-image: url('/img/step-over.svg'); | ||
| 840 | } | ||
| 841 | &#qr--modal-stepInto:after { | ||
| 842 | mask-image: url('/img/step-into.svg'); | ||
| 843 | } | ||
| 844 | &#qr--modal-stepOut:after { | ||
| 845 | mask-image: url('/img/step-out.svg'); | ||
| 846 | } | ||
| 847 | &#qr--modal-maximize { | ||
| 848 | display: none; | ||
| 849 | } | ||
| 850 | } | ||
| 851 | } | ||
| 852 | |||
| 853 | #qr--modal-send_textarea { | ||
| 854 | flex: 0 0 auto; | ||
| 855 | } | ||
| 513 | 856 | ||
| 514 | #qr--modal-executeProgress { | 857 | #qr--modal-executeProgress { |
| 515 | --prog: 0; | 858 | --prog: 0; |
| @@ -518,6 +861,7 @@ | |||
| 518 | --progSuccessColor: rgb(81, 163, 81); | 861 | --progSuccessColor: rgb(81, 163, 81); |
| 519 | --progErrorColor: rgb(189, 54, 47); | 862 | --progErrorColor: rgb(189, 54, 47); |
| 520 | --progAbortedColor: rgb(215, 136, 114); | 863 | --progAbortedColor: rgb(215, 136, 114); |
| 864 | flex: 0 0 auto; | ||
| 521 | height: 0.5em; | 865 | height: 0.5em; |
| 522 | background-color: var(--black50a); | 866 | background-color: var(--black50a); |
| 523 | position: relative; | 867 | position: relative; |
| @@ -588,6 +932,135 @@ | |||
| 588 | overflow: auto; | 932 | overflow: auto; |
| 589 | min-width: 100%; | 933 | min-width: 100%; |
| 590 | width: 0; | 934 | width: 0; |
| 935 | white-space: pre-wrap; | ||
| 936 | } | ||
| 937 | #qr--modal-debugState { | ||
| 938 | display: none; | ||
| 939 | &.qr--active { | ||
| 940 | display: block; | ||
| 941 | } | ||
| 942 | text-align: left; | ||
| 943 | font-size: smaller; | ||
| 944 | font-family: var(--monoFontFamily); | ||
| 945 | // background-color: rgb(146, 190, 252); | ||
| 946 | color: white; | ||
| 947 | padding: 0.5em 0; | ||
| 948 | overflow: auto; | ||
| 949 | min-width: 100%; | ||
| 950 | width: 0; | ||
| 951 | white-space: pre-wrap; | ||
| 952 | |||
| 953 | .qr--scope { | ||
| 954 | display: grid; | ||
| 955 | grid-template-columns: 0fr 1fr 1fr; | ||
| 956 | column-gap: 0em; | ||
| 957 | .qr--title { | ||
| 958 | grid-column: 1 / 4; | ||
| 959 | font-weight: bold; | ||
| 960 | font-family: var(--mainFontFamily); | ||
| 961 | background-color: var(--black50a); | ||
| 962 | padding: 0.25em; | ||
| 963 | margin-top: 0.5em; | ||
| 964 | } | ||
| 965 | .qr--var, .qr--macro, .qr--pipe { | ||
| 966 | display: contents; | ||
| 967 | &:nth-child(2n + 1) { | ||
| 968 | .qr--key, .qr--val { | ||
| 969 | background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.25); | ||
| 970 | } | ||
| 971 | .qr--val { | ||
| 972 | &:nth-child(2n) { | ||
| 973 | background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.125); | ||
| 974 | } | ||
| 975 | &:hover { | ||
| 976 | background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.5); | ||
| 977 | } | ||
| 978 | } | ||
| 979 | } | ||
| 980 | &:nth-child(2n) { | ||
| 981 | .qr--val { | ||
| 982 | &:nth-child(2n) { | ||
| 983 | background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.0625); | ||
| 984 | } | ||
| 985 | &:hover { | ||
| 986 | background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.5); | ||
| 987 | } | ||
| 988 | } | ||
| 989 | } | ||
| 990 | &.qr--isHidden { | ||
| 991 | .qr--key, .qr--val { | ||
| 992 | opacity: 0.5; | ||
| 993 | } | ||
| 994 | } | ||
| 995 | .qr--val { | ||
| 996 | grid-column: 2 / 4; | ||
| 997 | &.qr--singleCol { | ||
| 998 | grid-column: unset; | ||
| 999 | } | ||
| 1000 | &.qr--simple { | ||
| 1001 | &:before, &:after { | ||
| 1002 | content: '"'; | ||
| 1003 | color: var(--SmartThemeQuoteColor); | ||
| 1004 | } | ||
| 1005 | } | ||
| 1006 | &.qr--unresolved { | ||
| 1007 | &:after { | ||
| 1008 | content: '-UNRESOLVED-'; | ||
| 1009 | font-style: italic; | ||
| 1010 | color: var(--SmartThemeQuoteColor); | ||
| 1011 | } | ||
| 1012 | } | ||
| 1013 | } | ||
| 1014 | } | ||
| 1015 | .qr--key { | ||
| 1016 | margin-left: 0.5em; | ||
| 1017 | padding-right: 1em; | ||
| 1018 | &:after { content: ": "; } | ||
| 1019 | } | ||
| 1020 | .qr--pipe, .qr--macro { | ||
| 1021 | > .qr--key { | ||
| 1022 | &:before { content: "{{"; } | ||
| 1023 | &:after { content: "}}: "; } | ||
| 1024 | } | ||
| 1025 | } | ||
| 1026 | .qr--scope { | ||
| 1027 | display: contents; | ||
| 1028 | .qr--pipe { | ||
| 1029 | .qr--key, .qr--val { | ||
| 1030 | opacity: 0.5; | ||
| 1031 | } | ||
| 1032 | } | ||
| 1033 | } | ||
| 1034 | } | ||
| 1035 | |||
| 1036 | .qr--stack { | ||
| 1037 | display: grid; | ||
| 1038 | grid-template-columns: 1fr 0fr; | ||
| 1039 | .qr--title { | ||
| 1040 | grid-column: 1 / 3; | ||
| 1041 | font-weight: bold; | ||
| 1042 | font-family: var(--mainFontFamily); | ||
| 1043 | background-color: var(--black50a); | ||
| 1044 | padding: 0.25em; | ||
| 1045 | margin-top: 1em; | ||
| 1046 | } | ||
| 1047 | .qr--item { | ||
| 1048 | display: contents; | ||
| 1049 | &:nth-child(2n + 1) { | ||
| 1050 | .qr--name, .qr--source { | ||
| 1051 | background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.25); | ||
| 1052 | } | ||
| 1053 | } | ||
| 1054 | .qr--name { | ||
| 1055 | margin-left: 0.5em; | ||
| 1056 | } | ||
| 1057 | .qr--source { | ||
| 1058 | opacity: 0.5; | ||
| 1059 | text-align: right; | ||
| 1060 | white-space: nowrap; | ||
| 1061 | } | ||
| 1062 | } | ||
| 1063 | } | ||
| 591 | } | 1064 | } |
| 592 | } | 1065 | } |
| 593 | } | 1066 | } |
| @@ -605,10 +1078,75 @@ | |||
| 605 | } | 1078 | } |
| 606 | } | 1079 | } |
| 607 | 1080 | ||
| 1081 | @keyframes qr--debugPulse { | ||
| 1082 | 0%, | ||
| 1083 | 100% { | ||
| 1084 | border-color: rgb(81, 163, 81); | ||
| 1085 | } | ||
| 1086 | |||
| 1087 | 50% { | ||
| 1088 | border-color: rgb(146, 190, 252); | ||
| 1089 | } | ||
| 1090 | } | ||
| 1091 | |||
| 608 | .popup.qr--hide { | 1092 | .popup.qr--hide { |
| 609 | opacity: 0 !important; | 1093 | opacity: 0 !important; |
| 1094 | &::backdrop { | ||
| 1095 | opacity: 0 !important; | ||
| 1096 | } | ||
| 610 | } | 1097 | } |
| 611 | 1098 | ||
| 612 | .popup.qr--hide::backdrop { | 1099 | .popup.qr--hide::backdrop { |
| 613 | opacity: 0 !important; | 1100 | opacity: 0 !important; |
| 614 | } | 1101 | } |
| 1102 | |||
| 1103 | |||
| 1104 | |||
| 1105 | .popup:has(.qr--transferModal) { | ||
| 1106 | .popup-button-ok { | ||
| 1107 | &:after { | ||
| 1108 | content: 'Transfer'; | ||
| 1109 | height: 0; | ||
Binary file
Diff truncated