commit
565327fe1e
|
@ -0,0 +1,90 @@
|
|||
name: Bug Report 🐛
|
||||
description: Report something that's not working the way it's (probably) intended to. PAY ATTENTION, Support requests for external programs (reverse proxies, 3rd party servers, other peoples' forks) will be refused!"
|
||||
title: '[BUG] <title>'
|
||||
labels: ['bug']
|
||||
body:
|
||||
- type: dropdown
|
||||
id: environment
|
||||
attributes:
|
||||
label: Environment
|
||||
description: Where are you running SillyTavern?
|
||||
options:
|
||||
- Self-Hosted (Bare Metal)
|
||||
- Self-Hosted (Docker)
|
||||
- Android (Termux)
|
||||
- Cloud Service (Static)
|
||||
- Other (Specify below)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: system
|
||||
attributes:
|
||||
label: System
|
||||
description: >-
|
||||
For deployment issues, specify your [distro or OS](https://whatsmyos.com/) and/ or Docker version.
|
||||
For client-side issues, include your [browser version](https://www.whatsmybrowser.org/)
|
||||
placeholder: e.g. Firefox 101, Manjaro Linux 21.3.0, Docker 20.10.16
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Version
|
||||
description: What version of SillyTavern are you running?
|
||||
placeholder: (check User Settings to see the version)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: desktop
|
||||
attributes:
|
||||
label: Desktop Information
|
||||
description: Please provide details about your desktop environment.
|
||||
placeholder: |
|
||||
- Node.js version (if applicable): [run `node --version` in cmd]
|
||||
- Generation API [e.g. KoboldAI, OpenAI]
|
||||
- Branch [staging, release]
|
||||
- Model [e.g. Pygmalion 6b, LLaMa 13b]
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: repro
|
||||
attributes:
|
||||
label: Describe the problem
|
||||
description: Please describe exactly what is not working, include the steps to reproduce, actual result and expected result
|
||||
placeholder: When doing ABC then DEF, I expect to see XYZ, but I actually see ZYX
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Additional info
|
||||
description: Logs? Screenshots? Yes, please.
|
||||
placeholder: If the issue happens during build-time, include terminal logs. For run-time errors, include browser logs which you can view in the Dev Tools (F12), under the Console tab. Take care to blank out any personal info.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: checkboxes
|
||||
id: idiot-check
|
||||
attributes:
|
||||
label: Please tick the boxes
|
||||
description: Before submitting, please ensure that
|
||||
options:
|
||||
- label: You have explained the issue clearly, and included all relevant info
|
||||
required: true
|
||||
- label: You've checked that this [issue hasn't already been raised](https://github.com/SillyTavern/SillyTavern/issues?q=is%3Aissue)
|
||||
required: true
|
||||
- label: You've checked the [docs](https://docs.sillytavern.app/) ![important](https://img.shields.io/badge/Important!-F6094E)
|
||||
required: true
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |-
|
||||
## Thanks 🙏
|
||||
Thank you for raising this ticket - in doing so you are helping to make SillyTavern better for everyone.
|
||||
validations:
|
||||
required: false
|
|
@ -1,45 +0,0 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: "Create a report to help us improve. PAY ATTENTION: Support requests for external programs (reverse proxies, 3rd party servers, other peoples' forks) will be refused!"
|
||||
title: "[BUG]"
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
> **Warning**. Complete **all** the fields below. Otherwise, your bug report will be **ignored**!
|
||||
|
||||
**Have you searched for similar [bugs](https://github.com/SillyTavern/SillyTavern/issues?q=)?**
|
||||
Yes/No
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Logs**
|
||||
|
||||
Providing the logs from the browser DevTools console (opened by pressing the F12 key) or SillyTavern command line window will be highly appreciated.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS/Device: [e.g. Windows 11]
|
||||
- Environment: [cloud, local]
|
||||
- Node.js version (if applicable): [run `node --version` in cmd]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Generation API [e.g. KoboldAI, OpenAI]
|
||||
- Branch [staging, release]
|
||||
- Model [e.g. Pygmalion 6b, LLaMa 13b]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
|
@ -0,0 +1,91 @@
|
|||
name: Feature Request ✨
|
||||
description: Suggest an idea for future development of this project
|
||||
title: '[FEATURE_REQUEST] <title>'
|
||||
labels: ['enhancement']
|
||||
|
||||
body:
|
||||
|
||||
# Field 1 - Did the user searched for similar requests
|
||||
- type: dropdown
|
||||
id: similarRequest
|
||||
attributes:
|
||||
label: Have you searched for similar [requests](https://github.com/SillyTavern/SillyTavern/issues?q=)?
|
||||
description:
|
||||
options:
|
||||
- 'No'
|
||||
- 'Yes'
|
||||
validations:
|
||||
required: false
|
||||
|
||||
# Field 2 - Is it bug-related
|
||||
- type: textarea
|
||||
id: issue
|
||||
attributes:
|
||||
label: Is your feature request related to a problem? If so, please describe.
|
||||
description:
|
||||
placeholder: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
validations:
|
||||
required: false
|
||||
|
||||
# Field 3 - Describe feature
|
||||
- type: textarea
|
||||
id: solution
|
||||
attributes:
|
||||
label: Describe the solution you'd like
|
||||
placeholder: An outline of how you would like this to be implemented, include as much details as possible
|
||||
validations:
|
||||
required: true
|
||||
|
||||
# Field 4 - Describe alternatives
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: Describe alternatives you've considered
|
||||
placeholder: A clear and concise description of any alternative solutions or features you've considered.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
# Field 5 - Additional context
|
||||
- type: textarea
|
||||
id: addcontext
|
||||
attributes:
|
||||
label: Additional context
|
||||
placeholder: Add any other context or screenshots about the feature request here.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
# Field 6 - Priority
|
||||
- type: dropdown
|
||||
id: priority
|
||||
attributes:
|
||||
label: Priority
|
||||
description: How urgent is the development of this feature
|
||||
options:
|
||||
- Low (Nice-to-have)
|
||||
- Medium (Would be very useful)
|
||||
- High (The app does not function without it)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
# Field 7 - Can the user implement
|
||||
- type: dropdown
|
||||
id: canImplement
|
||||
attributes:
|
||||
label: Is this something you would be keen to implement?
|
||||
description: Are you raising this ticket in order to get an issue number for your PR?
|
||||
options:
|
||||
- 'No'
|
||||
- 'Maybe'
|
||||
- 'Yes!'
|
||||
validations:
|
||||
required: false
|
||||
|
||||
# Final text
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |-
|
||||
## Thanks 🙏
|
||||
Thank you for your feature suggestion.
|
||||
Please note that there is no guarantee that your idea will be implemented.
|
||||
validations:
|
||||
required: false
|
|
@ -1,23 +0,0 @@
|
|||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: "[Feature Request] "
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Have you searched for similar [requests](https://github.com/SillyTavern/SillyTavern/issues?q=)?**
|
||||
Yes/No
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
|
@ -292,6 +292,7 @@ GNU Affero General Public License for more details.**
|
|||
* RossAscends' additions: AGPL v3
|
||||
* Portions of CncAnon's TavernAITurbo mod: Unknown license
|
||||
* kingbri's various commits and suggestions (<https://github.com/bdashore3>)
|
||||
* city_unit's extensions and various QoL features (<https://github.com/city-unit>)
|
||||
* StefanDanielSchwarz's various commits and bug reports (<https://github.com/StefanDanielSchwarz>)
|
||||
* Waifu mode inspired by the work of PepperTaco (<https://github.com/peppertaco/Tavern/>)
|
||||
* Thanks Pygmalion University for being awesome testers and suggesting cool features!
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
# This workflow will publish a docker image for every full release to the GitHub package repository
|
||||
|
||||
name: Create Docker Image on Release
|
||||
|
||||
on:
|
||||
release:
|
||||
# Only runs on full releases not pre releases
|
||||
types: [released]
|
||||
|
||||
env:
|
||||
# This should allow creation of docker images even in forked repositories
|
||||
# Image name may not contain uppercase characters, so we can not use the repository name
|
||||
# Creates a string like: ghcr.io/SillyTavern/sillytavern
|
||||
image_name: ghcr.io/${{ github.repository_owner }}/sillytavern
|
||||
|
||||
jobs:
|
||||
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Build docker image using dockerfile and tag it with branch name
|
||||
# Assumes branch name is the version number
|
||||
- name: Build the Docker image
|
||||
run: |
|
||||
docker build . --file Dockerfile --tag $image_name:${{ github.ref_name }}
|
||||
|
||||
# Login into package repository as the person who created the release
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# Assumes release is the latest and marks image as such
|
||||
- name: Docker Tag and Push
|
||||
run: |
|
||||
docker tag $image_name:${{ github.ref_name }} $image_name:latest
|
||||
docker push $image_name:${{ github.ref_name }}
|
||||
docker push $image_name:latest
|
|
@ -26,6 +26,7 @@ public/settings.json
|
|||
/thumbnails
|
||||
whitelist.txt
|
||||
.vscode
|
||||
.idea/
|
||||
secrets.json
|
||||
/dist
|
||||
/backups/
|
||||
|
@ -35,3 +36,5 @@ content.log
|
|||
cloudflared.exe
|
||||
public/assets/
|
||||
access.log
|
||||
/vectors/
|
||||
/cache/
|
||||
|
|
|
@ -13,7 +13,7 @@ ENTRYPOINT [ "tini", "--" ]
|
|||
WORKDIR ${APP_HOME}
|
||||
|
||||
# Install app dependencies
|
||||
COPY package*.json ./
|
||||
COPY package*.json post-install.js ./
|
||||
RUN \
|
||||
echo "*** Install npm packages ***" && \
|
||||
npm install && npm cache clean --force
|
||||
|
|
|
@ -9,13 +9,28 @@ const enableExtensions = true; //Enables support for TavernAI-extras project
|
|||
const listen = true; // If true, Can be access from other device or PC. otherwise can be access only from hosting machine.
|
||||
const allowKeysExposure = false; // If true, private API keys could be fetched to the frontend.
|
||||
const skipContentCheck = false; // If true, no new default content will be delivered to you.
|
||||
|
||||
const thumbnailsQuality = 95; // Quality of thumbnails. 0-100
|
||||
|
||||
// If true, Allows insecure settings for listen, whitelist, and authentication.
|
||||
// Change this setting only on "trusted networks". Do not change this value unless you are aware of the issues that can arise from changing this setting and configuring a insecure setting.
|
||||
const securityOverride = false;
|
||||
|
||||
// Additional settings for extra modules / extensions
|
||||
const extras = {
|
||||
// Disables auto-download of models from the HuggingFace Hub.
|
||||
// You will need to manually download the models and put them into the /cache folder.
|
||||
disableAutoDownload: false,
|
||||
// Text classification model for sentiment analysis. HuggingFace ID of a model in ONNX format.
|
||||
classificationModel: 'Cohee/distilbert-base-uncased-go-emotions-onnx',
|
||||
// Image captioning model. HuggingFace ID of a model in ONNX format.
|
||||
captioningModel: 'Xenova/vit-gpt2-image-captioning',
|
||||
// Feature extraction model. HuggingFace ID of a model in ONNX format.
|
||||
embeddingModel: 'Xenova/all-mpnet-base-v2',
|
||||
};
|
||||
|
||||
// Request overrides for additional headers
|
||||
// Format is an array of objects:
|
||||
// { hosts: [ "<url>" ], headers: { <header>: "<value>" } }
|
||||
const requestOverrides = [];
|
||||
|
||||
module.exports = {
|
||||
|
@ -32,4 +47,6 @@ module.exports = {
|
|||
securityOverride,
|
||||
skipContentCheck,
|
||||
requestOverrides,
|
||||
thumbnailsQuality,
|
||||
extras,
|
||||
};
|
||||
|
|
|
@ -75,9 +75,6 @@
|
|||
"always_force_name2": true,
|
||||
"user_prompt_bias": "",
|
||||
"show_user_prompt_bias": true,
|
||||
"multigen": false,
|
||||
"multigen_first_chunk": 50,
|
||||
"multigen_next_chunks": 30,
|
||||
"markdown_escape_strings": "",
|
||||
"fast_ui_mode": false,
|
||||
"avatar_style": 0,
|
||||
|
@ -167,7 +164,6 @@
|
|||
"custom_stopping_strings_macro": true,
|
||||
"fuzzy_search": true,
|
||||
"encode_tags": false,
|
||||
"lazy_load": 100,
|
||||
"ui_mode": 1
|
||||
},
|
||||
"extension_settings": {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
12
package.json
12
package.json
|
@ -14,8 +14,7 @@
|
|||
"gpt3-tokenizer": "^1.1.5",
|
||||
"ip-matching": "^2.1.2",
|
||||
"ipaddr.js": "^2.0.1",
|
||||
"jimp": "^0.22.7",
|
||||
"jquery": "^3.6.4",
|
||||
"jimp": "^0.22.10",
|
||||
"json5": "^2.2.3",
|
||||
"lodash": "^4.17.21",
|
||||
"mime-types": "^2.1.35",
|
||||
|
@ -27,8 +26,9 @@
|
|||
"png-chunks-extract": "^1.0.0",
|
||||
"response-time": "^2.3.2",
|
||||
"sanitize-filename": "^1.6.3",
|
||||
"sillytavern-transformers": "^2.7.3",
|
||||
"simple-git": "^3.19.1",
|
||||
"uniqolor": "^1.1.0",
|
||||
"vectra": "^0.2.2",
|
||||
"write-file-atomic": "^5.0.1",
|
||||
"ws": "^8.13.0",
|
||||
"yargs": "^17.7.1",
|
||||
|
@ -46,11 +46,12 @@
|
|||
"type": "git",
|
||||
"url": "https://github.com/SillyTavern/SillyTavern.git"
|
||||
},
|
||||
"version": "1.10.3",
|
||||
"version": "1.10.4",
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"start-multi": "node server.js --disableCsrf",
|
||||
"pkg": "pkg --compress Gzip --no-bytecode --public ."
|
||||
"pkg": "pkg --compress Gzip --no-bytecode --public .",
|
||||
"postinstall": "node post-install.js"
|
||||
},
|
||||
"bin": {
|
||||
"sillytavern": "./server.js"
|
||||
|
@ -75,6 +76,7 @@
|
|||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"jquery": "^3.6.4",
|
||||
"pkg": "^5.8.1",
|
||||
"pkg-fetch": "^3.5.2"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
/**
|
||||
* Scripts to be done before starting the server for the first time.
|
||||
*/
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
|
||||
/**
|
||||
* Creates the default config files if they don't exist yet.
|
||||
*/
|
||||
function createDefaultFiles() {
|
||||
const files = {
|
||||
settings: './public/settings.json',
|
||||
bg_load: './public/css/bg_load.css',
|
||||
config: './config.conf',
|
||||
};
|
||||
|
||||
for (const file of Object.values(files)) {
|
||||
try {
|
||||
if (!fs.existsSync(file)) {
|
||||
const defaultFilePath = path.join('./default', path.parse(file).base);
|
||||
fs.copyFileSync(defaultFilePath, file);
|
||||
console.log(`Created default file: ${file}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`FATAL: Could not write default file: ${file}`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the MD5 hash of the given data.
|
||||
* @param {Buffer} data Input data
|
||||
* @returns {string} MD5 hash of the input data
|
||||
*/
|
||||
function getMd5Hash(data) {
|
||||
return crypto
|
||||
.createHash('md5')
|
||||
.update(data)
|
||||
.digest('hex');
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the WASM binaries from the sillytavern-transformers package to the dist folder.
|
||||
*/
|
||||
function copyWasmFiles() {
|
||||
if (!fs.existsSync('./dist')) {
|
||||
fs.mkdirSync('./dist');
|
||||
}
|
||||
|
||||
const listDir = fs.readdirSync('./node_modules/sillytavern-transformers/dist');
|
||||
|
||||
for (const file of listDir) {
|
||||
if (file.endsWith('.wasm')) {
|
||||
const sourcePath = `./node_modules/sillytavern-transformers/dist/${file}`;
|
||||
const targetPath = `./dist/${file}`;
|
||||
|
||||
// Don't copy if the file already exists and is the same checksum
|
||||
if (fs.existsSync(targetPath)) {
|
||||
const sourceChecksum = getMd5Hash(fs.readFileSync(sourcePath));
|
||||
const targetChecksum = getMd5Hash(fs.readFileSync(targetPath));
|
||||
|
||||
if (sourceChecksum === targetChecksum) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
fs.copyFileSync(sourcePath, targetPath);
|
||||
console.log(`${file} successfully copied to ./dist/${file}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. Create default config files
|
||||
createDefaultFiles();
|
||||
// 2. Copy transformers WASM binaries from node_modules
|
||||
copyWasmFiles();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"temp": 1.06,
|
||||
"rep_pen": 1,
|
||||
"rep_pen_range": 0,
|
||||
"top_p": 1,
|
||||
"top_a": 0,
|
||||
"top_k": 0,
|
||||
"typical": 1,
|
||||
"tfs": 1,
|
||||
"rep_pen_slope": 0.9,
|
||||
"single_line": false,
|
||||
"sampler_order": [
|
||||
6,
|
||||
0,
|
||||
1,
|
||||
3,
|
||||
4,
|
||||
2,
|
||||
5
|
||||
],
|
||||
"mirostat": 2,
|
||||
"mirostat_tau": 9.61,
|
||||
"mirostat_eta": 1,
|
||||
"use_default_badwordsids": true
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"temp": 1.17,
|
||||
"rep_pen": 1,
|
||||
"rep_pen_range": 0,
|
||||
"top_p": 1,
|
||||
"top_a": 0,
|
||||
"top_k": 0,
|
||||
"typical": 1,
|
||||
"tfs": 1,
|
||||
"rep_pen_slope": 0.9,
|
||||
"single_line": false,
|
||||
"sampler_order": [
|
||||
6,
|
||||
0,
|
||||
1,
|
||||
3,
|
||||
4,
|
||||
2,
|
||||
5
|
||||
],
|
||||
"mirostat": 2,
|
||||
"mirostat_tau": 9.91,
|
||||
"mirostat_eta": 1,
|
||||
"use_default_badwordsids": true
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"temp": 1.17,
|
||||
"rep_pen": 1,
|
||||
"rep_pen_range": 0,
|
||||
"top_p": 1,
|
||||
"top_a": 0,
|
||||
"top_k": 0,
|
||||
"typical": 1,
|
||||
"tfs": 1,
|
||||
"rep_pen_slope": 0.9,
|
||||
"single_line": false,
|
||||
"sampler_order": [
|
||||
6,
|
||||
0,
|
||||
1,
|
||||
3,
|
||||
4,
|
||||
2,
|
||||
5
|
||||
],
|
||||
"mirostat": 2,
|
||||
"mirostat_tau": 9.62,
|
||||
"mirostat_eta": 1,
|
||||
"use_default_badwordsids": true
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"temp": 1.06,
|
||||
"top_p": 1,
|
||||
"top_k": 0,
|
||||
"typical_p": 1,
|
||||
"top_a": 0,
|
||||
"tfs": 1,
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1,
|
||||
"rep_pen_range": 0,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
"length_penalty": 1,
|
||||
"min_length": 0,
|
||||
"encoder_rep_pen": 1,
|
||||
"do_sample": true,
|
||||
"early_stopping": false,
|
||||
"mirostat_mode": 2,
|
||||
"mirostat_tau": 9.61,
|
||||
"mirostat_eta": 1,
|
||||
"rep_pen_size": 0
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"temp": 1.17,
|
||||
"top_p": 1,
|
||||
"top_k": 0,
|
||||
"typical_p": 1,
|
||||
"top_a": 0,
|
||||
"tfs": 1,
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1,
|
||||
"rep_pen_range": 0,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
"length_penalty": 1,
|
||||
"min_length": 0,
|
||||
"encoder_rep_pen": 1,
|
||||
"do_sample": true,
|
||||
"early_stopping": false,
|
||||
"mirostat_mode": 2,
|
||||
"mirostat_tau": 9.91,
|
||||
"mirostat_eta": 1,
|
||||
"rep_pen_size": 0
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"temp": 1.17,
|
||||
"top_p": 1,
|
||||
"top_k": 0,
|
||||
"typical_p": 1,
|
||||
"top_a": 0,
|
||||
"tfs": 1,
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1,
|
||||
"rep_pen_range": 0,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
"length_penalty": 1,
|
||||
"min_length": 0,
|
||||
"encoder_rep_pen": 1,
|
||||
"do_sample": true,
|
||||
"early_stopping": false,
|
||||
"mirostat_mode": 2,
|
||||
"mirostat_tau": 9.62,
|
||||
"mirostat_eta": 1,
|
||||
"rep_pen_size": 0
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"story_string": "{{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Circumstances and context of the dialogue: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}",
|
||||
"chat_start": "\nThen the roleplay chat between {{user}} and {{char}} begins.\n",
|
||||
"example_separator": "This is how {{char}} should talk",
|
||||
"name": "OldDefault"
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "Pygmalion",
|
||||
"story_string": "{{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{{char}}}'s Persona: {{description}}\n{{/if}}{{#if personality}}Personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}",
|
||||
"chat_start": "<START>",
|
||||
"example_separator": "<START>"
|
||||
"story_string": "{{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}",
|
||||
"chat_start": "",
|
||||
"example_separator": ""
|
||||
}
|
||||
|
|
|
@ -93,4 +93,118 @@ input.extension_missing[type="checkbox"] {
|
|||
.update-button {
|
||||
margin-right: 10px;
|
||||
display: inline-flex;
|
||||
}
|
||||
}
|
||||
|
||||
/* Fixes order of settings for extensions */
|
||||
#extensions_settings,
|
||||
#extensions_settings2 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/** LEFT COLUMN **/
|
||||
#extensions_settings>.expression_settings {
|
||||
order: 1;
|
||||
}
|
||||
|
||||
#extensions_settings>.background_settings {
|
||||
order: 2;
|
||||
}
|
||||
|
||||
#extensions_settings>.sd_settings {
|
||||
order: 3;
|
||||
}
|
||||
|
||||
#extensions_settings>#tts_settings {
|
||||
order: 4;
|
||||
}
|
||||
|
||||
#extensions_settings>#rvc_settings {
|
||||
order: 5;
|
||||
}
|
||||
|
||||
#extensions_settings>.objective-settings {
|
||||
order: 6;
|
||||
}
|
||||
|
||||
#extensions_settings>#speech_recognition_settings {
|
||||
order: 7;
|
||||
}
|
||||
|
||||
#extensions_settings>#audio_settings {
|
||||
order: 8;
|
||||
}
|
||||
|
||||
#extensions_settings>#assets_ui {
|
||||
order: 9;
|
||||
}
|
||||
|
||||
/** RIGHT COLUMN **/
|
||||
#extensions_settings2>.translation_settings {
|
||||
order: 1;
|
||||
}
|
||||
|
||||
#extensions_settings2>.caption_settings {
|
||||
order: 2;
|
||||
}
|
||||
|
||||
#extensions_settings2>.quickReplySettings {
|
||||
order: 3;
|
||||
}
|
||||
|
||||
#extensions_settings2>.idle-settings {
|
||||
order: 4;
|
||||
}
|
||||
|
||||
#extensions_settings2>#memory_settings {
|
||||
order: 5;
|
||||
}
|
||||
|
||||
#extensions_settings2>.hypebot_settings {
|
||||
order: 6;
|
||||
}
|
||||
|
||||
#extensions_settings2>.regex_settings {
|
||||
order: 7;
|
||||
}
|
||||
|
||||
#extensions_settings2>.vectors_settings {
|
||||
order: 8;
|
||||
}
|
||||
|
||||
#extensions_settings2>.chromadb_settings {
|
||||
order: 9;
|
||||
}
|
||||
|
||||
#extensions_settings2>.randomizer_settings {
|
||||
order: 10;
|
||||
}
|
||||
|
||||
/** WAND MENU **/
|
||||
#extensionsMenu>#ttsExtensionMenuItem {
|
||||
order: 1;
|
||||
}
|
||||
|
||||
#extensionsMenu>#sd_gen {
|
||||
order: 2;
|
||||
}
|
||||
|
||||
#extensionsMenu>#send_picture {
|
||||
order: 3;
|
||||
}
|
||||
|
||||
#extensionsMenu>#token_counter {
|
||||
order: 4;
|
||||
}
|
||||
|
||||
#extensionsMenu>#objective-task-manual-check-menu-item {
|
||||
order: 5;
|
||||
}
|
||||
|
||||
#extensionsMenu>#roll_dice {
|
||||
order: 6;
|
||||
}
|
||||
|
||||
#extensionsMenu>#translate_chat {
|
||||
order: 7;
|
||||
}
|
||||
|
|
|
@ -78,7 +78,7 @@
|
|||
position: fixed;
|
||||
left: 0;
|
||||
top: 5px;
|
||||
border: 1px solid var(--grey30);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
}
|
||||
|
||||
#select_chat_popup {
|
||||
|
@ -91,7 +91,6 @@
|
|||
#top-settings-holder,
|
||||
#top-bar {
|
||||
position: fixed;
|
||||
padding-top: 3px;
|
||||
width: 100vw;
|
||||
width: 100svw;
|
||||
}
|
||||
|
@ -114,14 +113,14 @@
|
|||
/* ,
|
||||
#world_popup */
|
||||
{
|
||||
max-height: calc(100vh - 36px);
|
||||
max-height: calc(100svh - 36px);
|
||||
/*max-height: calc(100vh - 36px);
|
||||
max-height: calc(100svh - 36px);*/
|
||||
width: 100% !important;
|
||||
margin: 0 auto;
|
||||
max-width: 100%;
|
||||
left: 0 !important;
|
||||
resize: none !important;
|
||||
top: 36px;
|
||||
top: var(--topBarBlockSize);
|
||||
}
|
||||
|
||||
.wi-settings {
|
||||
|
@ -135,15 +134,15 @@
|
|||
|
||||
#character_popup,
|
||||
#send_form {
|
||||
border: 1px solid var(--grey30);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
backdrop-filter: blur(calc(var(--SmartThemeBlurStrength) * 2));
|
||||
max-width: 100dvw;
|
||||
}
|
||||
|
||||
#chat {
|
||||
border-left: 1px solid var(--grey30);
|
||||
border-right: 1px solid var(--grey30);
|
||||
border-bottom: 1px solid var(--grey30);
|
||||
border-left: 1px solid var(--SmartThemeBorderColor);
|
||||
border-right: 1px solid var(--SmartThemeBorderColor);
|
||||
border-bottom: 1px solid var(--SmartThemeBorderColor);
|
||||
align-items: start;
|
||||
align-content: start;
|
||||
overflow-y: auto;
|
||||
|
@ -161,6 +160,7 @@
|
|||
}
|
||||
|
||||
#showRawPrompt,
|
||||
#copyPromptToClipboard,
|
||||
#groupCurrentMemberPopoutButton {
|
||||
display: none;
|
||||
}
|
||||
|
@ -175,11 +175,11 @@
|
|||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
overflow-y: hidden;
|
||||
border-left: 1px solid var(--grey30);
|
||||
border-right: 1px solid var(--grey30);
|
||||
border-bottom: 1px solid var(--grey30);
|
||||
border-left: 1px solid var(--SmartThemeBorderColor);
|
||||
border-right: 1px solid var(--SmartThemeBorderColor);
|
||||
border-bottom: 1px solid var(--SmartThemeBorderColor);
|
||||
border-radius: 0 0 20px 20px;
|
||||
top: 36px !important;
|
||||
top: var(--topBarBlockSize) !important;
|
||||
left: 0 !important;
|
||||
backdrop-filter: blur(calc(var(--SmartThemeBlurStrength) * 2));
|
||||
}
|
||||
|
@ -271,6 +271,7 @@
|
|||
}
|
||||
|
||||
@media screen and (min-width: 1001px) {
|
||||
|
||||
#PygOverrides,
|
||||
#ContextFormatting,
|
||||
#UI-Theme-Block,
|
||||
|
@ -417,4 +418,4 @@
|
|||
#horde_model {
|
||||
height: unset;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,7 +13,7 @@
|
|||
grid-column-end: 4;
|
||||
width: 100%;
|
||||
margin: 0.5em 0;
|
||||
background-image: linear-gradient(90deg, var(--transparent), var(--white30a), var(--transparent));
|
||||
background-image: linear-gradient(90deg, var(--transparent), var(--SmartThemeBorderColor), var(--transparent));
|
||||
min-height: 1px;
|
||||
}
|
||||
|
||||
|
@ -54,7 +54,7 @@
|
|||
#completion_prompt_manager #completion_prompt_manager_list li.completion_prompt_manager_prompt {
|
||||
align-items: center;
|
||||
padding: 0.5em;
|
||||
border: 1px solid var(--white30a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
}
|
||||
|
||||
#completion_prompt_manager #completion_prompt_manager_list li.completion_prompt_manager_prompt .prompt_manager_prompt_controls {
|
||||
|
@ -109,7 +109,7 @@
|
|||
#completion_prompt_manager_popup .completion_prompt_manager_prompt {
|
||||
margin: 1em 0;
|
||||
padding: 0.5em;
|
||||
border: 1px solid var(--white30a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
}
|
||||
|
||||
#completion_prompt_manager_popup .completion_prompt_manager_popup_header {
|
||||
|
@ -265,7 +265,7 @@
|
|||
top: var(--topBarBlockSize);
|
||||
box-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
|
||||
padding: 1em;
|
||||
border: 1px solid #333333;
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
flex-direction: column;
|
||||
z-index: 3010 !important;
|
||||
border-radius: 0 0 20px 20px;
|
||||
|
|
|
@ -14,7 +14,8 @@
|
|||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
color: rgb(188, 193, 200, 1);
|
||||
border: 1px solid #333;
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
padding: 6px;
|
||||
border-radius: 10px;
|
||||
|
@ -61,7 +62,8 @@
|
|||
#rm_group_add_members {
|
||||
margin-top: 0.25rem;
|
||||
margin-bottom: 0.5rem;
|
||||
border: 1px solid grey;
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
;
|
||||
border-radius: 10px;
|
||||
background-color: var(--black30a);
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
/* Customize the dropdown */
|
||||
.select2-dropdown {
|
||||
background-color: var(--SmartThemeBlurTintColor);
|
||||
border: 1px solid var(--white30a) !important;
|
||||
border: 1px solid var(--SmartThemeBorderColor) !important;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0 5px black;
|
||||
text-shadow: 0px 0px calc(var(--shadowWidth) * 1px) var(--SmartThemeShadowColor);
|
||||
|
@ -19,11 +19,24 @@
|
|||
color: var(--SmartThemeBodyColor);
|
||||
}
|
||||
|
||||
.select2-container .select2-search__field {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.select2-container .select2-selection--single .select2-selection__rendered {
|
||||
color: var(--SmartThemeBodyColor);
|
||||
line-height: revert;
|
||||
padding-left: unset;
|
||||
}
|
||||
|
||||
.select2-container .select2-results>.select2-results__options {
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
.select2-container .select2-selection--multiple .select2-selection__choice__remove {
|
||||
padding: revert;
|
||||
border-right: 1px solid var(--white30a);
|
||||
border-right: 1px solid var(--SmartThemeBorderColor);
|
||||
font-size: 1.1em;
|
||||
|
||||
}
|
||||
|
||||
.select2-container .select2-selection--multiple .select2-selection__choice__display {
|
||||
|
@ -34,7 +47,7 @@
|
|||
.select2-search__field {
|
||||
background-color: var(--black30a);
|
||||
color: var(--SmartThemeBodyColor);
|
||||
border: 1px solid var(--white30a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
border-radius: 7px;
|
||||
font-family: "Noto Sans", "Noto Color Emoji", sans-serif;
|
||||
padding: 3px 5px;
|
||||
|
@ -58,27 +71,30 @@
|
|||
background-color: var(--SmartThemeBodyColor);
|
||||
}
|
||||
|
||||
.select2-container .select2-selection--multiple {
|
||||
.select2-container .select2-selection--multiple,
|
||||
.select2-container .select2-selection--single {
|
||||
background-color: var(--black30a);
|
||||
color: var(--SmartThemeBodyColor);
|
||||
border: 1px solid var(--white30a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
border-radius: 7px;
|
||||
font-family: "Noto Sans", "Noto Color Emoji", sans-serif;
|
||||
padding: 3px 5px;
|
||||
}
|
||||
|
||||
.select2-container.select2-container--focus .select2-selection--multiple {
|
||||
border: 1px solid var(--white30a);
|
||||
.select2-container.select2-container--focus .select2-selection--multiple,
|
||||
.select2-container.select2-container--focus .select2-selection--single {
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
}
|
||||
|
||||
.select2-container .select2-selection--multiple .select2-selection__choice {
|
||||
.select2-container .select2-selection--multiple .select2-selection__choice,
|
||||
.select2-container .select2-selection--single .select2-selection__choice {
|
||||
border-radius: 5px;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
box-sizing: border-box;
|
||||
color: var(--SmartThemeBodyColor);
|
||||
background-color: var(--black30a);
|
||||
border-color: var(--white30a);
|
||||
border-color: var(--SmartThemeBorderColor);
|
||||
font-size: calc(var(--mainFontSize) - 5%);
|
||||
text-shadow: none !important;
|
||||
}
|
||||
|
@ -114,12 +130,13 @@
|
|||
margin-top: -7px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border: 1px solid var(--white30a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
background-color: var(--SmartThemeBlurTintColor);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.select2-container .select2-selection--multiple .select2-selection__choice__remove {
|
||||
.select2-container .select2-selection--multiple .select2-selection__choice__remove,
|
||||
.select2-container .select2-selection--single .select2-selection__choice__remove {
|
||||
color: var(--SmartThemeBodyColor);
|
||||
}
|
||||
|
||||
|
|
|
@ -262,6 +262,10 @@
|
|||
flex-flow: column;
|
||||
}
|
||||
|
||||
.flexFlowRow {
|
||||
flex-flow: row;
|
||||
}
|
||||
|
||||
.wideMinContent {
|
||||
width: min-content;
|
||||
}
|
||||
|
|
|
@ -109,6 +109,7 @@
|
|||
overflow: hidden;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
text-shadow: none !important;
|
||||
}
|
||||
|
||||
.tags_inline .tag {
|
||||
|
@ -128,6 +129,13 @@
|
|||
cursor: pointer;
|
||||
opacity: 0.6;
|
||||
filter: brightness(0.8);
|
||||
transition: opacity 200ms;
|
||||
}
|
||||
|
||||
.rm_tag_filter .tag:hover {
|
||||
|
||||
opacity: 1;
|
||||
filter: brightness(1);
|
||||
}
|
||||
|
||||
.tags_view,
|
||||
|
@ -163,4 +171,4 @@
|
|||
-1px 1px 0px black,
|
||||
1px -1px 0px black;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
|
@ -117,7 +117,7 @@ body.big-avatars .avatar img {
|
|||
height: 90px;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
border: 1px solid var(--black30a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
|
@ -191,7 +191,7 @@ body.bubblechat .mes {
|
|||
border-radius: 10px;
|
||||
background-color: var(--SmartThemeBotMesBlurTintColor);
|
||||
margin-bottom: 5px;
|
||||
border: 1px solid var(--white30a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
}
|
||||
|
||||
body.bubblechat .mes[is_user="true"] {
|
||||
|
@ -257,31 +257,16 @@ body.no-blur #bg_custom {
|
|||
|
||||
}
|
||||
|
||||
body:not(.bubblechat).no-blur #chat,
|
||||
body.no-blur #top-bar,
|
||||
body.no-blur #send_form {
|
||||
background-color: var(--SmartThemeBlurTintColor) !important;
|
||||
}
|
||||
|
||||
body.no-blur #options,
|
||||
body.no-blur .ui-widget-content,
|
||||
body.no-blur #floatingPrompt,
|
||||
body.no-blur #extensionsMenu,
|
||||
body.no-blur .list-group,
|
||||
body.no-blur #character_popup,
|
||||
body.no-blur #world_popup,
|
||||
body.no-blur #dialogue_popup,
|
||||
body.no-blur #select_chat_popup,
|
||||
body.no-blur .drawer-content,
|
||||
body.no-blur .select2-results__options {
|
||||
background-color: black !important;
|
||||
}
|
||||
|
||||
/* wAIfu mode*/
|
||||
|
||||
body.waifuMode #top-bar {
|
||||
border-radius: 0 0 20px 20px;
|
||||
border: 1px solid var(--grey30a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
}
|
||||
|
||||
body.waifuMode #sheld {
|
||||
|
@ -292,7 +277,7 @@ body.waifuMode #sheld {
|
|||
}
|
||||
|
||||
body.waifuMode #chat {
|
||||
border-top: 1px solid var(--grey30a);
|
||||
border-top: 1px solid var(--SmartThemeBorderColor);
|
||||
border-radius: 20px 20px 0 0;
|
||||
}
|
||||
|
||||
|
@ -353,6 +338,7 @@ body.movingUI #groupMemberListPopout {
|
|||
height: 120px;
|
||||
margin-top: 0;
|
||||
top: 50px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/*No Text Shadows Mode*/
|
||||
|
@ -360,3 +346,11 @@ body.movingUI #groupMemberListPopout {
|
|||
body.noShadows * {
|
||||
text-shadow: none !important;
|
||||
}
|
||||
|
||||
body.expandMessageActions .mes .mes_buttons .extraMesButtons {
|
||||
display: inherit !important;
|
||||
}
|
||||
|
||||
body.expandMessageActions .mes .mes_buttons .extraMesButtonsHint {
|
||||
display: none !important;
|
||||
}
|
|
@ -158,9 +158,6 @@
|
|||
"Disabled for all models": "对所有模型禁用",
|
||||
"Automatic (based on model name)": "自动(基于型号名称)",
|
||||
"Enabled for all models": "所有模型启用",
|
||||
"Multigen": "Multigen",
|
||||
"First chunk (tokens)": "第一个区块(Tokens)",
|
||||
"Next chunks (tokens)": "接下来的区块(Tokens)",
|
||||
"Anchors Order": "锚点顺序",
|
||||
"Character then Style": "字符然后样式",
|
||||
"Style then Character": "样式然后字符",
|
||||
|
@ -710,9 +707,6 @@
|
|||
"Disabled for all models": "すべてのモデルで無効",
|
||||
"Automatic (based on model name)": "自動(モデル名に基づく)",
|
||||
"Enabled for all models": "すべてのモデルで有効",
|
||||
"Multigen": "マルチジェン",
|
||||
"First chunk (tokens)": "最初のチャンク(トークン)",
|
||||
"Next chunks (tokens)": "次のチャンク(トークン)",
|
||||
"Anchors Order": "アンカーオーダー",
|
||||
"Character then Style": "キャラクター、次にスタイル",
|
||||
"Style then Character": "スタイル、次にキャラクター",
|
||||
|
@ -1264,9 +1258,6 @@
|
|||
"Disabled for all models": "모든 모델에 비활성화",
|
||||
"Automatic (based on model name)": "모델 서식 자동탐지",
|
||||
"Enabled for all models": "모든 모델에 활성화",
|
||||
"Multigen": "다수답변 생성",
|
||||
"First chunk (tokens)": "첫 말뭉치(토큰수)",
|
||||
"Next chunks (tokens)": "다음 말뭉치(토큰수)",
|
||||
"Anchors Order": "Anchors Order",
|
||||
"Character then Style": "캐릭터 다음 스타일",
|
||||
"Style then Character": "스타일 다음 캐릭터",
|
||||
|
@ -1873,9 +1864,6 @@
|
|||
"Disabled for all models": "Выключено для всех моделей",
|
||||
"Automatic (based on model name)": "Автоматически (выбор по названию модели)",
|
||||
"Enabled for all models": "Включить для всех моделей",
|
||||
"Multigen": "Мултиген",
|
||||
"First chunk (tokens)": "Первый отрезок (в токенах)",
|
||||
"Next chunks (tokens)": "Следующий отрезок (в токенах)",
|
||||
"Anchors Order": "Порядок Anchors",
|
||||
"Character then Style": "Персонаж после Стиля",
|
||||
"Style then Character": "Стиль после Персонажа",
|
||||
|
@ -1910,7 +1898,6 @@
|
|||
"Waifu Mode": "!!!РЕЖИМ ВАЙФУ!!!",
|
||||
"Message Timer": "Таймер сообщений",
|
||||
"Model Icon": "Показать значки модели",
|
||||
"Lazy Chat Loading": "Ленивая загрузка чата",
|
||||
"# of messages (0 = disabled)": "# сообщений (0 = отключено)",
|
||||
"Advanced Character Search": "Расширенный поиск персонажей",
|
||||
"Allow {{char}}: in bot messages": "Показывать {{char}}: в ответах",
|
||||
|
@ -2450,9 +2437,6 @@
|
|||
"Disabled for all models": "Disabilita per tutti i modelli",
|
||||
"Automatic (based on model name)": "Automatico (basato sul nome del modello)",
|
||||
"Enabled for all models": "Abilita per tutti i modelli",
|
||||
"Multigen": "Multigen",
|
||||
"First chunk (tokens)": "Primo pacchetto (in Token)",
|
||||
"Next chunks (tokens)": "Pacchetto successivo (in Token)",
|
||||
"Anchors Order": "Anchors Order",
|
||||
"Character then Style": "Prima il personaggio, successivamente lo stile",
|
||||
"Style then Character": "Prima lo stile, successivamente il personaggio",
|
||||
|
@ -2910,7 +2894,6 @@
|
|||
"Instruct": "Instruct",
|
||||
"Instruct Mode": "Modalità Instruct",
|
||||
"Last Sequence": "Ultima sequenza",
|
||||
"Lazy Chat Loading": "Caricamento svogliato della chat",
|
||||
"Least tokens": "Token minimi",
|
||||
"Light": "Leggero",
|
||||
"Load koboldcpp order": "Ripristina l'ordine di koboldcpp",
|
||||
|
@ -3128,9 +3111,6 @@
|
|||
"Disabled for all models": "Uitgeschakeld voor alle modellen",
|
||||
"Automatic (based on model name)": "Automatisch (op basis van modelnaam)",
|
||||
"Enabled for all models": "Ingeschakeld voor alle modellen",
|
||||
"Multigen": "Multigen",
|
||||
"First chunk (tokens)": "Eerste stuk (tokens)",
|
||||
"Next chunks (tokens)": "Volgende stukken (tokens)",
|
||||
"Anchors Order": "Ankersvolgorde",
|
||||
"Character then Style": "Personage dan Stijl",
|
||||
"Style then Character": "Stijl dan Personage",
|
||||
|
@ -3606,6 +3586,7 @@
|
|||
"Prompt that is used when the Jailbreak toggle is on": "Prompt que es utilizado cuando Jailbreak Prompt está activado",
|
||||
"Impersonation prompt": "Prompt \"Impersonar\"",
|
||||
"Prompt that is used for Impersonation function": "Prompt que es utilizado para la función \"Impersonar\"",
|
||||
"Restore default prompt":"Restaurar el prompt por defecto",
|
||||
"Logit Bias": "Logit Bias",
|
||||
"Helps to ban or reenforce the usage of certain words": "Ayuda a prohibir o alentar el uso de algunas palabras",
|
||||
"View / Edit bias preset": "Ver/Editar configuración de \"Logit Bias\"",
|
||||
|
@ -3635,7 +3616,7 @@
|
|||
"Follow": "Sigue",
|
||||
"these directions": "estas instrucciones",
|
||||
"to get your NovelAI API key.": "para conseguir tu NovelAI API key",
|
||||
"Enter it in the box below": "Introduce tu NovelAI API key en el siguiente campo",
|
||||
"Enter it in the box below": "Introduce tu clave API de OpenAI en el siguiente campo",
|
||||
"Novel AI Model": "Modelo IA de NovelAI",
|
||||
"No connection": "Desconectado",
|
||||
"oobabooga/text-generation-webui": "oobabooga/text-generation-webui",
|
||||
|
@ -3653,11 +3634,42 @@
|
|||
"Samplers Order": "Orden de Samplers",
|
||||
"Samplers will be applied in a top-down order. Use with caution.": "Los Samplers serán aplicados de orden superior a inferior. \nUsa con precaución",
|
||||
"Load koboldcpp order": "Cargar el orden de koboldcpp",
|
||||
"Repetition Penalty": "Repetition Penalty",
|
||||
"Epsilon Cutoff": "Epsilon Cutoff",
|
||||
"Eta Cutoff": "Eta Cutoff",
|
||||
"Rep. Pen. Range.": "Rep. Pen. Range.",
|
||||
"Rep. Pen. Freq.": "Rep. Pen. Freq.",
|
||||
"Rep. Pen. Presence": "Rep. Pen. Presence."
|
||||
"Unlocked Context Size": "Desbloquear Tamaño Del Contexto",
|
||||
"Unrestricted maximum value for the context slider":"Desbloquea el Tamaño máximo del contexto. Solo habilita esto si sabes lo que estás haciendo.",
|
||||
"Quick Edit": "Editor Rápido de Prompts",
|
||||
"Main": "Principal",
|
||||
"Assistant Prefill": "Prefijo del Asistente",
|
||||
"Start Claude's answer with...": "Inicia la respuesta de Claude con...",
|
||||
"Utility Prompts": "Indicaciones Útiles",
|
||||
"World Info Format Template": "Plantilla para formato de World Info",
|
||||
"NSFW avoidance prompt": "Prompt para evitar NSFW",
|
||||
"Prompt that is used when the NSFW toggle is O": "Prompt utilizado para evitar NSFW cuando \"Alentar NSFW\" está deshabilitado",
|
||||
"Wraps activated World Info entries before inserting into the prompt.": "Envuelve las entradas activadas de World Info antes de insertarlas en el prompt.",
|
||||
"New Chat": "Chat Nuevo",
|
||||
"Set at the beginning of the chat history to indicate that a new chat is about to start.": "Colocado al inicio del historial de chat para indicar que un nuevo chat va a comenzar.",
|
||||
"New Group Chat": "Nuevo Chat Grupal",
|
||||
"Set at the beginning of the chat history to indicate that a new group chat is about to start.":"Colocado al inicio del historial de chat para indicarle a la IA que un nuevo Chat Grupal va a comenzar",
|
||||
"New Example Chat": "Nuevo Ejemplo De Chat",
|
||||
"Add character names": "Incluír nombre del personaje",
|
||||
"Send names in the ChatML objects.": "Envía los mensajes al objeto ChatML. Ayuda a la IA a asociar mensajes con nombres en un chat grupal.",
|
||||
"Proxy Password": "Contraseña del Proxy",
|
||||
"Will be used as a password for the proxy instead of API key.": "Será utilizado como contraseña del proxy en vez de la clave API.",
|
||||
"Chat Completion Source": "Fuente de Chat",
|
||||
"Use Proxy password field instead. This input will be ignored.": "Utiliza el campo de Contraseña del Proxy. Lo que pongas aquí será ignorado.",
|
||||
"Show External models (provided by API)": "Mostrar modelos externos (Proveídos por la API)",
|
||||
"Connect": "Conectarse",
|
||||
"[title]Verifies your API connection by sending a short test message. Be aware that you'll be credited for it!": "Verifica que tu conexión con la API enviando un mensaje corto. ¡Ten en cuenta que se te cobrará por ello!",
|
||||
"Continue nudge": "Empujuón para continuar",
|
||||
"Replace empty message": "Reemplazar mensaje vacío",
|
||||
"Send this text instead of nothing when the text box is empty.": "Envía este mensaje en vez de nada cuando la barra de chat está vacía",
|
||||
"No connection...": "Sin conexión...",
|
||||
"Avoid sending sensitive information to the Horde.": "No envíes información personal a Horde.",
|
||||
"Review the Privacy statement": "Revisa el aviso de privacidad",
|
||||
"Learn how to contribute your idle GPU cycles to the Horde": "Aprende como contribuír a Horde con tu GPU.",
|
||||
"Trusted workers only": "Solo trabajadores de confianza",
|
||||
"API Key": "Clave API",
|
||||
"Get it here:": "Consíguela aquí:",
|
||||
"View my Kudos": "Ver mis Kudos",
|
||||
"Models": "Modelos IA"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
<script src="lib/toastr.min.js"></script>
|
||||
<script src="lib/fuse.js"></script>
|
||||
<script src="lib/select2.min.js"></script>
|
||||
<script src="lib/select2-search-placeholder.js"></script>
|
||||
<script src="lib/seedrandom.min.js"></script>
|
||||
<script src="lib/droll.js"></script>
|
||||
<script src="lib/localforage.min.js"></script>
|
||||
|
@ -72,6 +73,7 @@
|
|||
<script type="module" src="scripts/group-chats.js"></script>
|
||||
<script type="module" src="scripts/kai-settings.js"></script>
|
||||
<script type="module" src="scripts/textgen-settings.js"></script>
|
||||
<script type="module" src="scripts/mancer-settings.js"></script>
|
||||
<script type="module" src="scripts/bookmarks.js"></script>
|
||||
<script type="module" src="scripts/horde.js"></script>
|
||||
<script type="module" src="scripts/RossAscends-mods.js"></script>
|
||||
|
@ -83,6 +85,7 @@
|
|||
<script type="module" src="scripts/preset-manager.js"></script>
|
||||
<script type="module" src="scripts/filters.js"></script>
|
||||
<script type="module" src="scripts/personas.js"></script>
|
||||
<script type="module" src="scripts/server-history.js"></script>
|
||||
|
||||
<title>SillyTavern</title>
|
||||
</head>
|
||||
|
@ -246,7 +249,7 @@
|
|||
<input id="max_context_unlocked" type="checkbox" />
|
||||
<span data-i18n="unlocked">Unlocked</span>
|
||||
</label>
|
||||
<div id="max_context_unlocked_warning">
|
||||
<div id="max_context_unlocked_warning" class="toggle-description justifyLeft widthUnset">
|
||||
<span data-i18n="only select modls support context sizes greater than 2048 tokens. proceed only is you know you're doing">
|
||||
Only select models support context sizes greater than 2048 tokens.
|
||||
Increase only if you know what you're doing.
|
||||
|
@ -274,6 +277,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="range-block">
|
||||
<div class="range-block-title" data-i18n="temperature">
|
||||
Temperature
|
||||
|
@ -729,13 +733,13 @@
|
|||
</div>
|
||||
<div id="claude_assistant_prefill_block" data-source="claude" class="range-block">
|
||||
<span id="claude_assistant_prefill_text" data-i18n="Assistant Prefill">Assistant Prefill</span>
|
||||
<textarea id="claude_assistant_prefill" class="text_pole textarea_compact" name="assistant_prefill autoSetHeight" rows="3" maxlength="5000" placeholder="Start Claude's answer with..."></textarea>
|
||||
<textarea id="claude_assistant_prefill" class="text_pole textarea_compact" name="assistant_prefill autoSetHeight" rows="3" maxlength="5000" data-i18n="[placeholder]Start Claude's answer with..." placeholder="Start Claude's answer with..."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-newbie-hidden class="inline-drawer wide100p">
|
||||
<div class="inline-drawer-toggle inline-drawer-header">
|
||||
<b>Utility Prompts</b>
|
||||
<b data-i18n="Utility Prompts">Utility Prompts</b>
|
||||
<div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
|
@ -754,13 +758,13 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="range-block m-t-1">
|
||||
<div class="range-block-title openai_restorable">
|
||||
<div class="range-block-title openai_restorable" data-i18n="World Info Format Template">
|
||||
<span>World Info format template</span>
|
||||
<div id="wi_format_restore" title="Restore default format" class="right_menu_button">
|
||||
<div class="fa-solid fa-clock-rotate-left"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="toggle-description justifyLeft">
|
||||
<div class="toggle-description justifyLeft" data-i18n="Wraps activated World Info entries before inserting into the prompt.">
|
||||
Wraps activated World Info entries before inserting into the prompt. Use
|
||||
<tt>{0}</tt> to mark a place where the content is inserted.
|
||||
</div>
|
||||
|
@ -769,7 +773,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="range-block m-t-1">
|
||||
<div class="range-block-title openai_restorable">
|
||||
<div class="range-block-title openai_restorable" data-i18n="New Chat">
|
||||
<span>New Chat</span>
|
||||
<div id="newchat_prompt_restore" title="Restore new chat prompt" class="right_menu_button">
|
||||
<div class="fa-solid fa-clock-rotate-left"></div>
|
||||
|
@ -786,7 +790,7 @@
|
|||
</div>
|
||||
<div class="range-block m-t-1">
|
||||
<div class="range-block-title openai_restorable">
|
||||
<span>New Group Chat</span>
|
||||
<span data-i18n="New Group Chat">New Group Chat</span>
|
||||
<div id="newgroupchat_prompt_restore" title="Restore default prompt" data-i18n="[title]Restore new group chat prompt" class="right_menu_button">
|
||||
<div class="fa-solid fa-clock-rotate-left"></div>
|
||||
</div>
|
||||
|
@ -802,7 +806,7 @@
|
|||
</div>
|
||||
<div class="range-block m-t-1">
|
||||
<div class="range-block-title openai_restorable">
|
||||
<span>New Example Chat</span>
|
||||
<span data-i18n="New Example Chat">New Example Chat</span>
|
||||
<div id="newexamplechat_prompt_restore" title="Restore new example chat prompt" data-i18n="[title]Restore default prompt" class="right_menu_button">
|
||||
<div class="fa-solid fa-clock-rotate-left"></div>
|
||||
</div>
|
||||
|
@ -817,7 +821,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="range-block m-t-1">
|
||||
<div class="range-block-title openai_restorable">
|
||||
<div class="range-block-title openai_restorable" data-i18n="Continue nudge">
|
||||
<span>Continue nudge</span>
|
||||
<div id="continue_nudge_prompt_restore" title="Restore new chat prompt" class="right_menu_button">
|
||||
<div class="fa-solid fa-clock-rotate-left"></div>
|
||||
|
@ -833,7 +837,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="range-block m-t-1">
|
||||
<div class="range-block-title justifyLeft">
|
||||
<div class="range-block-title justifyLeft" data-i18n="Replace empty message">
|
||||
Replace empty message
|
||||
</div>
|
||||
<div class="toggle-description justifyLeft">
|
||||
|
@ -1058,6 +1062,18 @@
|
|||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div id="grammar_block">
|
||||
<h4 data-i18n="Grammar">Grammar</h4>
|
||||
<div class="range-block">
|
||||
<textarea id="grammar" rows="2" class="text_pole textarea_compact monospace"></textarea>
|
||||
<div class="toggle-description justifyLeft">
|
||||
<span data-i18n="Type in the desired custom grammar (GBNF).">
|
||||
Type in the desired custom grammar (<a href="https://github.com/ggerganov/llama.cpp/blob/master/grammars/README.md" target="_blank">GBNF</a>).
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="range-block flexFlowColumn">
|
||||
<div class="range-block-title">
|
||||
<span data-i18n="Samplers Order">Samplers Order</span>
|
||||
|
@ -1710,7 +1726,7 @@
|
|||
account for faster queue times</a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" href="https://github.com/db0/AI-Horde-Worker#readme" data-i18n="Learn how to contribute your idel GPU cycles to the Horde">Learn
|
||||
<a target="_blank" href="https://github.com/db0/AI-Horde-Worker#readme" data-i18n="Learn how to contribute your idle GPU cycles to the Horde">Learn
|
||||
how to contribute your idle GPU cycles to the Horde</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -1729,10 +1745,11 @@
|
|||
<input id="horde_trusted_workers_only" type="checkbox" />
|
||||
<span data-i18n="Trusted workers only">Trusted workers only</span>
|
||||
</label>
|
||||
<small id="adjustedHordeParams">Context: --, Response: --</small>
|
||||
|
||||
<h4 data-i18n="API key">API key</h4>
|
||||
<small>
|
||||
<span data-i18n="Get it here:">Get it here: </span> <a target="_blank" href="https://horde.koboldai.net/register" data-i18n="Register">Register</a> (<a id="horde_kudos" href="javascript:void(0);" data-i18n="View my Kudos">View my Kudos</a>)<br>
|
||||
<span data-i18n="Get it here:">Get it here: </span> <a target="_blank" href="https://horde.koboldai.net/register" data-i18n="Register">Register</a> (<a id="horde_kudos" href="javascript:void(0);" data-i18n="View my Kudos">View my Kudos</a>)<br>
|
||||
<span data-i18n="Enter">Enter </span> <span class="monospace">0000000000</span> <span data-i18n="to use anonymous mode.">to use anonymous mode. </span>
|
||||
</small>
|
||||
<!-- <div>
|
||||
|
@ -1766,8 +1783,8 @@
|
|||
<div id="kobold_api_block">
|
||||
<h4 data-i18n="API url">API url</h4>
|
||||
<small data-i18n="Example: http://127.0.0.1:5000/api ">Example: http://127.0.0.1:5000/api </small>
|
||||
<input id="api_url_text" name="api_url" class="text_pole" placeholder="http://127.0.0.1:5000/api" maxlength="500" value="" autocomplete="off">
|
||||
<div id="api_button" class="menu_button" type="submit" data-i18n="Connect">Connect</div>
|
||||
<input id="api_url_text" name="api_url" class="text_pole" placeholder="http://127.0.0.1:5000/api" maxlength="500" value="" autocomplete="off" data-server-history="kobold">
|
||||
<div id="api_button" class="menu_button" type="submit" data-i18n="Connect" data-server-connect="kobold">Connect</div>
|
||||
<div id="api_loading" class="api-load-icon fa-solid fa-hourglass fa-spin"></div>
|
||||
</div>
|
||||
<div id="online_status2">
|
||||
|
@ -1848,24 +1865,26 @@
|
|||
For privacy reasons, your API key will be hidden after you reload the page.
|
||||
</div>
|
||||
<div class="flex1">
|
||||
<h4 data-i18n="Mancer API url">Mancer API url</h4>
|
||||
<small data-i18n="Example: https://neuro.mancer.tech/webui/MODEL/api">Example: https://neuro.mancer.tech/webui/MODEL/api </small>
|
||||
<h4>Mancer Model</h4>
|
||||
<select id="mancer_model"></select>
|
||||
<h4 data-i18n="Mancer API url">Mancer API URL</h4>
|
||||
<small data-i18n="Example: https://neuro.mancer.tech/webui/MODEL/api">Example: https://neuro.mancer.tech/webui/MODEL/api</small>
|
||||
<input id="mancer_api_url_text" name="mancer_api_url" class="text_pole wide100p" maxlength="500" value="" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
<div id="tgwebui_api_subpanel" class="flex-container flexFlowColumn">
|
||||
<div class="flex1">
|
||||
<h4 data-i18n="Blocking API url">Blocking API url</h4>
|
||||
<h4 data-i18n="Blocking API url">Blocking API URL</h4>
|
||||
<small data-i18n="Example: http://127.0.0.1:5000/api ">Example: http://127.0.0.1:5000/api </small>
|
||||
<input id="textgenerationwebui_api_url_text" name="textgenerationwebui_api_url" class="text_pole wide100p" maxlength="500" value="" autocomplete="off">
|
||||
<input id="textgenerationwebui_api_url_text" name="textgenerationwebui_api_url" class="text_pole wide100p" maxlength="500" value="" autocomplete="off" data-server-history="ooba_blocking">
|
||||
</div>
|
||||
<div class="flex1">
|
||||
<h4 data-i18n="Streaming API url">Streaming API url</h4>
|
||||
<h4 data-i18n="Streaming API url">Streaming API URL</h4>
|
||||
<small data-i18n="Example: ws://127.0.0.1:5005/api/v1/stream">Example: ws://127.0.0.1:5005/api/v1/stream </small>
|
||||
<input id="streaming_url_textgenerationwebui" type="text" class="text_pole wide100p" maxlength="500" value="" autocomplete="off">
|
||||
<input id="streaming_url_textgenerationwebui" type="text" class="text_pole wide100p" maxlength="500" value="" autocomplete="off" data-server-history="ooba_streaming">
|
||||
</div>
|
||||
</div>
|
||||
<div id="api_button_textgenerationwebui" class="menu_button" type="submit" data-i18n="Connect">Connect</div>
|
||||
<div id="api_button_textgenerationwebui" class="menu_button" type="submit" data-i18n="Connect" data-server-connect="ooba_blocking,ooba_streaming">Connect</div>
|
||||
<div id="api_loading_textgenerationwebui" class="api-load-icon fa-solid fa-hourglass fa-spin"></div>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -1927,6 +1946,10 @@
|
|||
<option value="gpt-3.5-turbo-0613">gpt-3.5-turbo-0613</option>
|
||||
<option value="gpt-3.5-turbo-0301">gpt-3.5-turbo-0301</option>
|
||||
</optgroup>
|
||||
<optgroup label="GPT-3.5 Turbo Instruct">
|
||||
<option value="gpt-3.5-turbo-instruct">gpt-3.5-turbo-instruct</option>
|
||||
<option value="gpt-3.5-turbo-instruct-0914">gpt-3.5-turbo-instruct-0914</option>
|
||||
</optgroup>
|
||||
<optgroup label="GPT-4">
|
||||
<option value="gpt-4">gpt-4</option>
|
||||
<option value="gpt-4-0613">gpt-4-0613</option>
|
||||
|
@ -2469,25 +2492,27 @@
|
|||
</div>
|
||||
<div data-newbie-hidden>
|
||||
<h4>
|
||||
<span data-i18n="Multigen">Multigen</span>
|
||||
<a href="https://docs.sillytavern.app/usage/core-concepts/advancedformatting/#multigen" class="notes-link" target="_blank">
|
||||
<span class="note-link-span">?</span>
|
||||
</a>
|
||||
<span data-i18n="Auto-Continue">Auto-Continue</span>
|
||||
</h4>
|
||||
<label class="checkbox_label" for="multigen">
|
||||
<input id="multigen" type="checkbox" />
|
||||
<span data-i18n="Enabled">
|
||||
Enabled
|
||||
</span>
|
||||
</label>
|
||||
<div class="multigen_settings_block">
|
||||
<label for="multigen_1st_chunk">
|
||||
<small><span data-i18n="First chunk (tokens)">First chunk (tokens)</span></small>
|
||||
<input id="multigen_first_chunk" type="number" class="text_pole textarea_compact" min="1" max="512" />
|
||||
<div class="flex-container">
|
||||
<label class="checkbox_label" for="auto_continue_enabled">
|
||||
<input id="auto_continue_enabled" type="checkbox" />
|
||||
<span data-i18n="Enabled">
|
||||
Enabled
|
||||
</span>
|
||||
</label>
|
||||
<label for="multigen_next_chunk">
|
||||
<small><span data-i18n="Next chunks (tokens)">Next chunks (tokens)</span></small>
|
||||
<input id="multigen_next_chunks" type="number" class="text_pole textarea_compact" min="1" max="512" />
|
||||
<label class="checkbox_label" for="auto_continue_allow_chat_completions">
|
||||
<input id="auto_continue_allow_chat_completions" type="checkbox" />
|
||||
<span data-i18n="Allow for Chat Completion APIs">
|
||||
Allow for Chat Completion APIs
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="auto_continue_settings_block">
|
||||
<label for="auto_continue_target_length">
|
||||
<span data-i18n="Target length (tokens)">Target length (tokens)</span>
|
||||
<input id="auto_continue_target_length" type="number" class="text_pole textarea_compact" min="0" max="1024" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -2662,24 +2687,66 @@
|
|||
<div class="drawer-icon fa-solid fa-user-cog closedIcon" title="User Settings" data-i18n="[title]User Settings"></div>
|
||||
</div>
|
||||
<div id="user-settings-block" class="drawer-content closedDrawer">
|
||||
<div class="flex-container wide100p alignitemscenter spaceBetween">
|
||||
<h3><span data-i18n="User Settings">User Settings</span></h3>
|
||||
<div id="version_display"></div>
|
||||
</div>
|
||||
<div class="flex-container spaceEvenly">
|
||||
<div id="UI-Theme-Block" class="flex-container flexFlowColumn wide100p">
|
||||
<div id="color-picker-block" class="flex-container flexFlowColumn flexNoGap">
|
||||
<div id="UI-Mode-Block">
|
||||
<h4 data-i18n="UI Mode">
|
||||
UI Mode
|
||||
</h4>
|
||||
<select id="ui_mode_select" class="margin0 margin-r5">
|
||||
<div class="flex-container flexFlowColumn">
|
||||
<div name="userSettingsRowOne" class="flex-container flexFlowRow alignitemscenter spaceBetween">
|
||||
<div class="flex-container">
|
||||
<div class="flex-container flexnowrap alignitemscenter">
|
||||
<h3><span data-i18n="User Settings">User Settings</span></h3>
|
||||
<select id="ui_mode_select" class="margin0 widthNatural">
|
||||
<option value="0" data-i18n="Simple">Simple</option>
|
||||
<option value="1" data-i18n="Advanced">Advanced</option>
|
||||
</select>
|
||||
</div>
|
||||
<div data-newbie-hidden>
|
||||
<h4><span data-i18n="UI Colors">UI Colors</span></h4>
|
||||
</div>
|
||||
<div id="UI-language-block" class="flex-container alignitemscenter">
|
||||
<span data-i18n="UI Language">Language:</span>
|
||||
<select id="ui_language_select" class="widthNatural flex1 margin0">
|
||||
<option value="" data-i18n="Default">Default</option>
|
||||
<option value="en">en</option>
|
||||
</select>
|
||||
</div>
|
||||
<small id="version_display"></small>
|
||||
</div>
|
||||
<div name="UserSettingsRowTwo" class="flex-container flexFlowRow">
|
||||
|
||||
<textarea id="settingsSearch" class="textarea_compact wide100p" rows="1" placeholder="Search Settings"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div id="user-settings-block-content" class="flex-container spaceEvenly">
|
||||
<div name="UserSettingsFirstColumn" id="UI-Theme-Block" class="flex-container flexFlowColumn wide100p">
|
||||
<div id="UI-presets-block" class="flex-container flexFlowColumn">
|
||||
<h4>
|
||||
<span data-i18n="UI Theme Preset">Theme Preset</span>
|
||||
</h4>
|
||||
<div class="flex-container flexnowrap alignitemscenter">
|
||||
<select id="themes" class="margin0">
|
||||
</select>
|
||||
<div id="ui-preset-save-button" title="Save changes to a new theme file" data-i18n="[title]Save changes to a new theme file" class="menu_button margin0">
|
||||
<i class="fa-solid fa-save"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div name="themeElements" data-newbie-hidden class="flex-container flexFlowColumn flexNoGap">
|
||||
<h4><span data-i18n="UI Colors">Theme Settings</span></h4>
|
||||
<div name="AvatarAndChatDisplay" class="flex-container flexFlowColumn">
|
||||
<div class="flex-container">
|
||||
<span data-i18n="Avatar Style">Avatars:</span>
|
||||
<select id="avatar_style" class="widthNatural flex1 margin0">
|
||||
<option value="0" data-i18n="Circle">Circle</option>
|
||||
<option value="1" data-i18n="Rectangle">Rectangle</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex-container">
|
||||
<span data-i18n="Chat Style:">Chat Style:</span><br>
|
||||
<select id="chat_display" class="widthNatural flex1 margin0">
|
||||
<option value="0" data-i18n="Default">Flat</span>
|
||||
<option value="1" data-i18n="Bubbles">Bubbles</option>
|
||||
<option value="2" data-i18n="Document">Document</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div id="color-picker-block" class="flex-container flexFlowColumn flexNoGap">
|
||||
|
||||
<div class="flex-container">
|
||||
<toolcool-color-picker id="main-text-color-picker"></toolcool-color-picker>
|
||||
<span data-i18n="Main Text">Main Text</span>
|
||||
|
@ -2696,13 +2763,17 @@
|
|||
<toolcool-color-picker id="shadow-color-picker"></toolcool-color-picker>
|
||||
<span data-i18n="Shadow Color">Text Shadow</span>
|
||||
</div>
|
||||
<!-- <div class="flex-container">
|
||||
<toolcool-color-picker id="fastui-bg-color-picker"></toolcool-color-picker>
|
||||
<span data-i18n="FastUI BG">FastUI BG</span>
|
||||
</div> -->
|
||||
<div class="flex-container">
|
||||
<toolcool-color-picker id="chat-tint-color-picker"></toolcool-color-picker>
|
||||
<span data-i18n="Chat Background">Chat Background</span>
|
||||
</div>
|
||||
<div class="flex-container">
|
||||
<toolcool-color-picker id="blur-tint-color-picker"></toolcool-color-picker>
|
||||
<span data-i18n="Blur Tint">UI Background</span>
|
||||
<span data-i18n="UI Background">UI Background</span>
|
||||
</div>
|
||||
<div class="flex-container">
|
||||
<toolcool-color-picker id="border-color-picker"></toolcool-color-picker>
|
||||
<span data-i18n="UI Border">UI Border</span>
|
||||
</div>
|
||||
<div class="flex-container">
|
||||
<toolcool-color-picker id="user-mes-blur-tint-color-picker"></toolcool-color-picker>
|
||||
|
@ -2713,7 +2784,23 @@
|
|||
<span data-i18n="AI Message Blur Tint">AI Message</span>
|
||||
</div>
|
||||
</div>
|
||||
<div data-newbie-hidden id="font-blur-UIpresets-block" class="flex-container flexFlowColumn">
|
||||
|
||||
<div data-newbie-hidden name="FontBlurChatWidthBlock" class="flex-container flexFlowColumn flexNoGap">
|
||||
<div data-newbie-hidden class="range-block">
|
||||
<div class="range-block-title" data-i18n="Chat Width (PC)">
|
||||
Chat Width (PC)
|
||||
</div>
|
||||
<div class="range-block-range-and-counter">
|
||||
<div class="range-block-range">
|
||||
<input id="chat_width_slider" class="wide100p" type="range" min="25" max="100" step="1" value="50">
|
||||
</div>
|
||||
<div class="range-block-counter">
|
||||
<div contenteditable="true" data-for="chat_width_slider" id="chat_width_slider_counter">
|
||||
select
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="font-scale-block" class="range-block">
|
||||
<div class="range-block-title" data-i18n="Font Scale">
|
||||
Font Scale
|
||||
|
@ -2760,300 +2847,241 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="UI-language-block" class="flex-container flexFlowColumn">
|
||||
<h4>
|
||||
<span data-i18n="UI Language">UI Language</span>
|
||||
</h4>
|
||||
<div class="flex-container flexnowrap alignitemscenter">
|
||||
<select id="ui_language_select" class="margin0 margin-r5">
|
||||
<option value="" data-i18n="Browser default">
|
||||
Browser default
|
||||
</option>
|
||||
<option value="en">en</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div id="UI-presets-block" class="flex-container flexFlowColumn">
|
||||
<h4>
|
||||
<span data-i18n="UI Theme Preset">UI Theme Preset</span>
|
||||
</h4>
|
||||
<div class="flex-container flexnowrap alignitemscenter">
|
||||
<select id="themes" class="margin0 margin-r5">
|
||||
</select>
|
||||
<div id="ui-preset-save-button" title="Save changes to a new theme file" data-i18n="[title]Save changes to a new theme file" class="menu_button padding5 margin0">
|
||||
<i class="fa-solid fa-save"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-newbie-hidden id="MovingUI-presets-block" class="flex-container flexFlowColumn">
|
||||
<h4>
|
||||
<span data-i18n="MovingUI Preset">MovingUI Preset</span>
|
||||
</h4>
|
||||
<div class="flex-container flexnowrap alignitemscenter">
|
||||
<select id="movingUIPresets" class="margin0 margin-r5">
|
||||
</select>
|
||||
<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 padding5 margin0">
|
||||
<i class="fa-solid fa-save"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div id="movingUIreset" class="menu_button whitespacenowrap" data-i18n="Reset Panels">
|
||||
Reset MovingUI
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div id="UI-Customization" class="flex-container wide100p">
|
||||
<div class="ui-settings">
|
||||
<h4><span data-i18n="UI Customization">UI Customization</span></h4>
|
||||
<div data-newbie-hidden class="range-block">
|
||||
<div class="range-block-title" data-i18n="Chat Width (PC)">
|
||||
Chat Width (PC)
|
||||
</div>
|
||||
<div class="range-block-range-and-counter">
|
||||
<div class="range-block-range">
|
||||
<input id="chat_width_slider" class="wide100p" type="range" min="25" max="75" step="1" value="50">
|
||||
<div class="slider_hint">
|
||||
<span>25%</span>
|
||||
<span>50%</span>
|
||||
<span>75%</span>
|
||||
<div name="UserSettingsSecondColumn" id="UI-Customization" class="flex-container flexFlowColumn wide100p">
|
||||
<div name="themeToggles">
|
||||
<h4 data-i18n="Theme Toggles">Theme Toggles</h4>
|
||||
<label data-newbie-hidden for="fast_ui_mode" class="checkbox_label" title="removes blur from window backgrounds" data-i18n="[title]removes blur from window backgrounds">
|
||||
<input id="fast_ui_mode" type="checkbox" />
|
||||
<span data-i18n="No Blur Effect">No Blur Effect</span>
|
||||
</label>
|
||||
<label data-newbie-hidden for="noShadowsmode" class="checkbox_label">
|
||||
<input id="noShadowsmode" type="checkbox" />
|
||||
<span data-i18n="No Text Shadows">No Text Shadows</span>
|
||||
</label>
|
||||
<label for="waifuMode" class="checkbox_label">
|
||||
<input id="waifuMode" type="checkbox" />
|
||||
<span data-i18n="Waifu Mode">Visual Novel Mode</span>
|
||||
</label>
|
||||
|
||||
<label data-newbie-hidden for="expandMessageActions" class="checkbox_label">
|
||||
<input id="expandMessageActions" type="checkbox" />
|
||||
<span data-i18n="Auto-Expand Message Actions">Expand Message Actions</span>
|
||||
</label>
|
||||
|
||||
<label data-newbie-hidden for="messageTimerEnabled" class="checkbox_label">
|
||||
<input id="messageTimerEnabled" type="checkbox" />
|
||||
<span data-i18n="Message Timer">Message Timer</span>
|
||||
</label>
|
||||
|
||||
<label data-newbie-hidden for="messageTimestampsEnabled" class="checkbox_label">
|
||||
<input id="messageTimestampsEnabled" type="checkbox" />
|
||||
<span data-i18n="Chat Timestamps">Chat Timestamps</span>
|
||||
</label>
|
||||
|
||||
<label data-newbie-hidden for="messageModelIconEnabled" class="checkbox_label">
|
||||
<input id="messageModelIconEnabled" type="checkbox" />
|
||||
<span data-i18n="Model Icon">Model Icons</span>
|
||||
</label>
|
||||
|
||||
<label data-newbie-hidden for="mesIDDisplayEnabled" class="checkbox_label">
|
||||
<input id="mesIDDisplayEnabled" type="checkbox" />
|
||||
<span data-i18n="Message IDs">Message IDs</span>
|
||||
</label>
|
||||
|
||||
<label data-newbie-hidden for="messageTokensEnabled" class="checkbox_label">
|
||||
<input id="messageTokensEnabled" type="checkbox" />
|
||||
<span data-i18n="Show Message Token Count">Message Token Count</span>
|
||||
</label>
|
||||
<label data-newbie-hidden for="hotswapEnabled" class="checkbox_label">
|
||||
<input id="hotswapEnabled" type="checkbox" />
|
||||
<span data-i18n="Characters Hotswap">Characters Hotswap</span>
|
||||
</label>
|
||||
</div>
|
||||
<h4><span data-i18n="Miscellaneous">Miscellaneous</span></h4>
|
||||
<div>
|
||||
<label for="play_message_sound" class="checkbox_label">
|
||||
<input id="play_message_sound" type="checkbox" />
|
||||
<audio id="audio_message_sound" src="sounds/message.mp3" hidden></audio>
|
||||
<span>
|
||||
<span data-i18n="Message Sound">Message Sound</span>
|
||||
<a href="https://docs.sillytavern.app/usage/core-concepts/uicustomization/#message-sound" class="notes-link" target="_blank">
|
||||
<span class="note-link-span">?</span>
|
||||
</a>
|
||||
</span>
|
||||
</label>
|
||||
<label for="play_sound_unfocused" class="checkbox_label">
|
||||
<input id="play_sound_unfocused" type="checkbox" />
|
||||
<span data-i18n="Background Sound Only">Background Sound Only</span>
|
||||
</label>
|
||||
|
||||
<label data-newbie-hidden class="checkbox_label" for="relaxed_api_urls" title="Reduce the formatting requirements on API URLS">
|
||||
<input id="relaxed_api_urls" type="checkbox" />
|
||||
<span data-i18n="Relaxed API URLS">Relaxed API URLs</span>
|
||||
</label>
|
||||
<label data-newbie-hidden id="movingUIModeCheckBlock" for="movingUImode" class="checkbox_label">
|
||||
<input id="movingUImode" type="checkbox" />
|
||||
<span data-i18n="Movable UI Panels">MovingUI</span>
|
||||
</label>
|
||||
<div data-newbie-hidden id="MovingUI-presets-block" class="flex-container alignitemscenter">
|
||||
<div class="flex-container alignitemscenter">
|
||||
<span>MUI Preset:</span>
|
||||
<div class="flex-container flexnowrap">
|
||||
<select id="movingUIPresets" class="widthNatural flex1 margin0">
|
||||
</select>
|
||||
<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">
|
||||
<i class="fa-solid fa-save"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-newbie-hidden class="range-block">
|
||||
<div class="range-block-title">
|
||||
<span data-i18n="Lazy Chat Loading">Lazy Chat Loading</span><br>
|
||||
<small data-i18n="# of messages (0 = disabled)"># of messages (0 = disabled)</small>
|
||||
</div>
|
||||
<div class="range-block-range-and-counter">
|
||||
<div class="range-block-range">
|
||||
<input id="lazy_load" class="wide100p" type="range" min="0" max="100" step="10" value="0">
|
||||
<div class="slider_hint">
|
||||
<span>0</span>
|
||||
<span>50</span>
|
||||
<span>100</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="movingUIreset" class="menu_button whitespacenowrap" data-i18n="Reset Panels">
|
||||
Reset MovingUI
|
||||
</div>
|
||||
<div data-newbie-hidden id="CustomCSS-block" class="flex-container flexFlowColumn">
|
||||
<h4>
|
||||
<span data-i18n="Custom CSS">Custom CSS</span>
|
||||
</h4>
|
||||
<div class="flex-container flexnowrap alignitemscenter">
|
||||
<textarea id="customCSS" class="text_pole margin0 margin-r5 textarea_compact monospace"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span data-i18n="Avatar Style">Avatar Style:</span><br>
|
||||
<label>
|
||||
<input name="avatar_style" type="radio" value="0" />
|
||||
<span data-i18n="Circle">Circle</span>
|
||||
</div>
|
||||
</div>
|
||||
<div name="UserSettingsThirdColumn" id="power-user-options-block" class="flex-container wide100p">
|
||||
<div id="power-user-option-checkboxes">
|
||||
<div data-newbie-hidden name="CharacterHandlingToggles">
|
||||
<h4>Character Handling</h4>
|
||||
<label data-newbie-hidden class="checkbox_label" for="fuzzy_search_checkbox">
|
||||
<input id="fuzzy_search_checkbox" type="checkbox" />
|
||||
<span data-i18n="Advanced Character Search">Advanced Character Search</span>
|
||||
</label>
|
||||
<label>
|
||||
<input name="avatar_style" type="radio" value="1" />
|
||||
<span data-i18n="Rectangle">Rectangle</span>
|
||||
<label data-newbie-hidden for="prefer_character_prompt" title="If checked and the character card contains a prompt override (System Prompt), use that instead." data-i18n="[title]If checked and the character card contains a prompt override (System Prompt), use that instead." class="checkbox_label">
|
||||
<input id="prefer_character_prompt" type="checkbox" />
|
||||
<span data-i18n="Prefer Character Card Prompt">Prefer Char. Prompt</span>
|
||||
</label>
|
||||
<label data-newbie-hidden for="prefer_character_jailbreak" title="If checked and the character card contains a jailbreak override (Post History Instruction), use that instead." data-i18n="[title]If checked and the character card contains a jailbreak override (Post History Instruction), use that instead." class="checkbox_label">
|
||||
<input id="prefer_character_jailbreak" type="checkbox" />
|
||||
<span data-i18n="Prefer Character Card Jailbreak">Prefer Char. JB</span>
|
||||
</label>
|
||||
<label data-newbie-hidden class="checkbox_label" for="never_resize_avatars">
|
||||
<input id="never_resize_avatars" type="checkbox" />
|
||||
<span data-i18n="Never resize avatars">Never resize avatars</span>
|
||||
</label>
|
||||
<label data-newbie-hidden class="checkbox_label" for="show_card_avatar_urls">
|
||||
<input id="show_card_avatar_urls" type="checkbox" />
|
||||
<span data-i18n="Show avatar filenames">Show avatar filenames</span>
|
||||
</label>
|
||||
<label data-newbie-hidden class="checkbox_label" for="import_card_tags">
|
||||
<input id="import_card_tags" type="checkbox" />
|
||||
<span data-i18n="Import Card Tags">Import Card Tags</span>
|
||||
</label>
|
||||
<label data-newbie-hidden class="checkbox_label" for="spoiler_free_mode">
|
||||
<input id="spoiler_free_mode" type="checkbox" />
|
||||
<span data-i18n="Spoiler Free Mode">Spoiler Free Mode</span>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<span data-i18n="Chat Style:">Message Style:</span><br>
|
||||
<select id="chat_display">
|
||||
<option value="0" data-i18n="Default">Flat Chat</span>
|
||||
<option value="1" data-i18n="Bubbles">Bubble Chat</option>
|
||||
<option value="2" data-i18n="Document">Single Document</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="play_message_sound" class="checkbox_label">
|
||||
<input id="play_message_sound" type="checkbox" />
|
||||
<audio id="audio_message_sound" src="sounds/message.mp3" hidden></audio>
|
||||
<span>
|
||||
<span data-i18n="Message Sound">Message Sound</span>
|
||||
<a href="https://docs.sillytavern.app/usage/core-concepts/uicustomization/#message-sound" class="notes-link" target="_blank">
|
||||
<span class="note-link-span">?</span>
|
||||
</a>
|
||||
|
||||
<div name="ChatMessageHandlingToggles">
|
||||
<h4>Chat/Message Handling</h4>
|
||||
<div data-newbie-hidden class="flex-container alignitemscenter">
|
||||
<span data-i18n="Send on Enter">
|
||||
Enter to Send:
|
||||
</span>
|
||||
<select id="send_on_enter" class="widthNatural flex1 margin0">
|
||||
<option value="-1" data-i18n="Disabled">Disabled</option>
|
||||
<option value="0" data-i18n="Automatic (PC)">Automatic (PC)</option>
|
||||
<option value="1" data-i18n="Enabled">Enabled</option>
|
||||
</select>
|
||||
</div>
|
||||
<label data-newbie-hidden class="checkbox_label" for="continue_on_send">
|
||||
<input id="continue_on_send" type="checkbox" />
|
||||
<span data-i18n="Press Send to continue">
|
||||
"Send" to Continue
|
||||
</span>
|
||||
</label>
|
||||
<label for="play_sound_unfocused" class="checkbox_label">
|
||||
<input id="play_sound_unfocused" type="checkbox" />
|
||||
<span data-i18n="Background Sound Only">Background Sound Only</span>
|
||||
<label class="checkbox_label" for="quick_continue">
|
||||
<input id="quick_continue" type="checkbox" />
|
||||
<span data-i18n="Press Send to continue">
|
||||
Quick "Continue" button
|
||||
</span>
|
||||
</label>
|
||||
<label data-newbie-hidden for="fast_ui_mode" class="checkbox_label" title="removes blur and uses alternative background color for divs" data-i18n="[title]removes blur and uses alternative background color for divs">
|
||||
<input id="fast_ui_mode" type="checkbox" />
|
||||
<span data-i18n="No Blur Effect">No Blur Effect</span>
|
||||
<label data-newbie-hidden class="checkbox_label" for="swipes-checkbox">
|
||||
<input id="swipes-checkbox" type="checkbox" />
|
||||
<span data-i18n="Swipes">Swipes</span>
|
||||
</label>
|
||||
<label data-newbie-hidden for="noShadowsmode" class="checkbox_label">
|
||||
<input id="noShadowsmode" type="checkbox" />
|
||||
<span data-i18n="No Text Shadows">No Text Shadows</span>
|
||||
<label class="checkbox_label" for="auto-load-chat-checkbox">
|
||||
<input id="auto-load-chat-checkbox" type="checkbox" />
|
||||
<span data-i18n="Auto-load Last Chat">Auto-load Last Chat</span>
|
||||
</label>
|
||||
<label for="waifuMode" class="checkbox_label">
|
||||
<input id="waifuMode" type="checkbox" />
|
||||
<span data-i18n="Waifu Mode">Visual Novel Mode</span>
|
||||
</label>
|
||||
|
||||
<label for="messageTimerEnabled" class="checkbox_label">
|
||||
<input id="messageTimerEnabled" type="checkbox" />
|
||||
<span data-i18n="Message Timer">Message Timer</span>
|
||||
</label>
|
||||
|
||||
<label data-newbie-hidden for="messageTimestampsEnabled" class="checkbox_label">
|
||||
<input id="messageTimestampsEnabled" type="checkbox" />
|
||||
<span data-i18n="Chat Timestamps">Chat Timestamps</span>
|
||||
</label>
|
||||
|
||||
<label data-newbie-hidden for="messageModelIconEnabled" class="checkbox_label">
|
||||
<input id="messageModelIconEnabled" type="checkbox" />
|
||||
<span data-i18n="Model Icon">Show Model Icons</span>
|
||||
</label>
|
||||
|
||||
<label data-newbie-hidden for="mesIDDisplayEnabled" class="checkbox_label">
|
||||
<input id="mesIDDisplayEnabled" type="checkbox" />
|
||||
<span data-i18n="Message IDs">Show Message IDs</span>
|
||||
</label>
|
||||
|
||||
<label data-newbie-hidden for="messageTokensEnabled" class="checkbox_label">
|
||||
<input id="messageTokensEnabled" type="checkbox" />
|
||||
<span data-i18n="Show Message Token Count">Show Message Token Count</span>
|
||||
</label>
|
||||
|
||||
<label data-newbie-hidden for="auto_scroll_chat_to_bottom" class="checkbox_label">
|
||||
<input id="auto_scroll_chat_to_bottom" type="checkbox" />
|
||||
<span data-i18n="Auto-scroll Chat">Auto-scroll Chat</span>
|
||||
</label>
|
||||
|
||||
<label data-newbie-hidden for="hotswapEnabled" class="checkbox_label">
|
||||
<input id="hotswapEnabled" type="checkbox" />
|
||||
<span data-i18n="Characters Hotswap">Characters Hotswap</span>
|
||||
<label data-newbie-hidden class="checkbox_label" for="auto_save_msg_edits">
|
||||
<input id="auto_save_msg_edits" type="checkbox" />
|
||||
<span data-i18n="Auto-save Message Edits">Auto-save Message Edits</span>
|
||||
</label>
|
||||
<label data-newbie-hidden class="checkbox_label" for="confirm_message_delete">
|
||||
<input id="confirm_message_delete" type="checkbox" />
|
||||
<span data-i18n="Confirm message deletion">Confirm message deletion</span>
|
||||
</label>
|
||||
<label class="checkbox_label" for="auto_fix_generated_markdown">
|
||||
<input id="auto_fix_generated_markdown" type="checkbox" />
|
||||
<span data-i18n="Auto-fix Markdown">Auto-fix Markdown</span>
|
||||
</label>
|
||||
<label data-newbie-hidden class="checkbox_label" for="render_formulas">
|
||||
<input id="render_formulas" type="checkbox" />
|
||||
<span data-i18n="Render Formulas">Render Formulas</span>
|
||||
<a href="https://docs.sillytavern.app/usage/core-concepts/uicustomization/#formulas-rendering" class="notes-link" target="_blank">
|
||||
<span class="note-link-span">?</span>
|
||||
</a>
|
||||
</label>
|
||||
<label data-newbie-hidden class="checkbox_label" for="allow_name2_display">
|
||||
<input id="allow_name2_display" type="checkbox" />
|
||||
<span data-i18n="Allow {{char}}: in bot messages">Show {{char}}: in responses</span>
|
||||
</label>
|
||||
<label data-newbie-hidden class="checkbox_label" for="allow_name1_display">
|
||||
<input id="allow_name1_display" type="checkbox" />
|
||||
<span data-i18n="Allow {{user}}: in bot messages">Show {{user}}: in responses</span>
|
||||
</label>
|
||||
<label class="checkbox_label" for="encode_tags">
|
||||
<input id="encode_tags" type="checkbox" />
|
||||
<span data-i18n="Show tags in responses">Show <tags> in responses</span>
|
||||
</label>
|
||||
<label data-newbie-hidden class="checkbox_label" for="disable_group_trimming" title="Allow AI messages in groups to contain lines spoken by other group members.">
|
||||
<input id="disable_group_trimming" type="checkbox" />
|
||||
<span data-i18n="Relax message trim in Groups">Relax message trim in Groups</span>
|
||||
</label>
|
||||
<label data-newbie-hidden class="checkbox_label" for="console_log_prompts">
|
||||
<input id="console_log_prompts" type="checkbox" />
|
||||
<span data-i18n="Log prompts to console">Log prompts to console</span>
|
||||
</label>
|
||||
|
||||
<label data-newbie-hidden id="movingUIModeCheckBlock" for="movingUImode" class="checkbox_label">
|
||||
<input id="movingUImode" type="checkbox" />
|
||||
<span data-i18n="Movable UI Panels">Movable UI Panels</span>
|
||||
</label>
|
||||
|
||||
<div data-newbie-hidden class="flex-container flexFlowColumn">
|
||||
<h4 data-i18n="Send on Enter">
|
||||
Send on Enter
|
||||
</h4>
|
||||
<select id="send_on_enter">
|
||||
<option value="-1" data-i18n="Always disabled">Always disabled</option>
|
||||
<option value="0" data-i18n="Automatic (desktop)">Automatic (desktop)</option>
|
||||
<option value="1" data-i18n="Always enabled">Always enabled</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div id="power-user-options-block" class="flex-container wide100p">
|
||||
<div id="power-user-option-checkboxes">
|
||||
<h4 data-i18n="Power User Options">Power User Options</h4>
|
||||
<label data-newbie-hidden class="checkbox_label" for="swipes-checkbox">
|
||||
<input id="swipes-checkbox" type="checkbox" />
|
||||
<span data-i18n="Swipes">Swipes</span>
|
||||
</label>
|
||||
<label data-newbie-hidden class="checkbox_label" for="fuzzy_search_checkbox">
|
||||
<input id="fuzzy_search_checkbox" type="checkbox" />
|
||||
<span data-i18n="Advanced Character Search">Advanced Character Search</span>
|
||||
</label>
|
||||
<label data-newbie-hidden for="prefer_character_prompt" title="If checked and the character card contains a prompt override (System Prompt), use that instead." data-i18n="[title]If checked and the character card contains a prompt override (System Prompt), use that instead." class="checkbox_label">
|
||||
<input id="prefer_character_prompt" type="checkbox" />
|
||||
<span data-i18n="Prefer Character Card Prompt">Prefer Char. Prompt</span>
|
||||
</label>
|
||||
<label data-newbie-hidden for="prefer_character_jailbreak" title="If checked and the character card contains a jailbreak override (Post History Instruction), use that instead." data-i18n="[title]If checked and the character card contains a jailbreak override (Post History Instruction), use that instead." class="checkbox_label">
|
||||
<input id="prefer_character_jailbreak" type="checkbox" />
|
||||
<span data-i18n="Prefer Character Card Jailbreak">Prefer Char. JB</span>
|
||||
</label>
|
||||
<label data-newbie-hidden class="checkbox_label" for="continue_on_send">
|
||||
<input id="continue_on_send" type="checkbox" />
|
||||
<span data-i18n="Press Send to continue">
|
||||
Press "Send" to continue
|
||||
</span>
|
||||
</label>
|
||||
<label class="checkbox_label" for="quick_continue">
|
||||
<input id="quick_continue" type="checkbox" />
|
||||
<span data-i18n="Press Send to continue">
|
||||
Show quick "Continue" button
|
||||
</span>
|
||||
</label>
|
||||
<label class="checkbox_label" for="auto-load-chat-checkbox">
|
||||
<input id="auto-load-chat-checkbox" type="checkbox" />
|
||||
<span data-i18n="Auto-load Last Chat">Auto-load Last Chat</span>
|
||||
</label>
|
||||
<label data-newbie-hidden class="checkbox_label" for="auto_save_msg_edits">
|
||||
<input id="auto_save_msg_edits" type="checkbox" />
|
||||
<span data-i18n="Auto-save Message Edits">Auto-save Message Edits</span>
|
||||
</label>
|
||||
<label class="checkbox_label" for="auto_fix_generated_markdown">
|
||||
<input id="auto_fix_generated_markdown" type="checkbox" />
|
||||
<span data-i18n="Auto-fix Markdown">Auto-fix Markdown</span>
|
||||
</label>
|
||||
<label data-newbie-hidden class="checkbox_label" for="allow_name2_display">
|
||||
<input id="allow_name2_display" type="checkbox" />
|
||||
<span data-i18n="Allow {{char}}: in bot messages">Show {{char}}: in responses</span>
|
||||
</label>
|
||||
<label data-newbie-hidden class="checkbox_label" for="allow_name1_display">
|
||||
<input id="allow_name1_display" type="checkbox" />
|
||||
<span data-i18n="Allow {{user}}: in bot messages">Show {{user}}: in responses</span>
|
||||
</label>
|
||||
<label class="checkbox_label" for="encode_tags">
|
||||
<input id="encode_tags" type="checkbox" />
|
||||
<span data-i18n="Show tags in responses">Show <tags> in responses</span>
|
||||
</label>
|
||||
<label data-newbie-hidden class="checkbox_label" for="disable_group_trimming" title="Allow AI messages in groups to contain lines spoken by other group members.">
|
||||
<input id="disable_group_trimming" type="checkbox" />
|
||||
<span data-i18n="Show impersonated replies in groups">Show impersonated replies in groups</span>
|
||||
</label>
|
||||
<label data-newbie-hidden class="checkbox_label" for="console_log_prompts">
|
||||
<input id="console_log_prompts" type="checkbox" />
|
||||
<span data-i18n="Log prompts to console">Log prompts to console</span>
|
||||
</label>
|
||||
<label data-newbie-hidden class="checkbox_label" for="render_formulas">
|
||||
<input id="render_formulas" type="checkbox" />
|
||||
<span data-i18n="Render Formulas">Render Formulas</span>
|
||||
<a href="https://docs.sillytavern.app/usage/core-concepts/uicustomization/#formulas-rendering" class="notes-link" target="_blank">
|
||||
<span class="note-link-span">?</span>
|
||||
</a>
|
||||
</label>
|
||||
<label data-newbie-hidden class="checkbox_label" for="never_resize_avatars">
|
||||
<input id="never_resize_avatars" type="checkbox" />
|
||||
<span data-i18n="Never resize avatars">Never resize avatars</span>
|
||||
</label>
|
||||
<label data-newbie-hidden class="checkbox_label" for="show_card_avatar_urls">
|
||||
<input id="show_card_avatar_urls" type="checkbox" />
|
||||
<span data-i18n="Show avatar filenames">Show avatar filenames</span>
|
||||
</label>
|
||||
<label data-newbie-hidden class="checkbox_label" for="import_card_tags">
|
||||
<input id="import_card_tags" type="checkbox" />
|
||||
<span data-i18n="Import Card Tags">Import Card Tags</span>
|
||||
</label>
|
||||
<label data-newbie-hidden class="checkbox_label" for="confirm_message_delete">
|
||||
<input id="confirm_message_delete" type="checkbox" />
|
||||
<span data-i18n="Confirm message deletion">Confirm message deletion</span>
|
||||
</label>
|
||||
<label data-newbie-hidden class="checkbox_label" for="spoiler_free_mode">
|
||||
<input id="spoiler_free_mode" type="checkbox" />
|
||||
<span data-i18n="Spoiler Free Mode">Spoiler Free Mode</span>
|
||||
</label>
|
||||
<label data-newbie-hidden class="checkbox_label" for="relaxed_api_urls" title="Reduce the formatting requirements on API URLS">
|
||||
<input id="relaxed_api_urls" type="checkbox" />
|
||||
<span data-i18n="Relaxed API URLS">Relaxed API URLS</span>
|
||||
</label>
|
||||
|
||||
<div data-newbie-hidden class="inline-drawer wide100p flexFlowColumn">
|
||||
<div class="inline-drawer-toggle inline-drawer-header">
|
||||
<b><span data-i18n="Auto-swipe">Auto-swipe</span></b>
|
||||
<div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
<label class="checkbox_label" for="auto_swipe">
|
||||
<input id="auto_swipe" type="checkbox" />
|
||||
<span data-i18n="Enabled">Enabled</span>
|
||||
</label>
|
||||
<div data-i18n="Minimum generated message length">Minimum generated message length</div>
|
||||
<input id="auto_swipe_minimum_length" name="auto_swipe_minimum_length" type="number" min="0" step="1" value="0" class="text_pole">
|
||||
<div data-i18n="Blacklisted words">Blacklisted words</div>
|
||||
<div class="auto_swipe">
|
||||
<textarea id="auto_swipe_blacklist" name="auto_swipe_blacklist" data-i18n="[placeholder]words you dont want generated separated by comma ','" placeholder="words you dont want generated separated by comma ','" class="text_pole textarea_compact" maxlength="5000" value="" autocomplete="off" rows="3"></textarea>
|
||||
<div data-i18n="Blacklisted word count to swipe">Blacklisted word count to swipe</div>
|
||||
<input id="auto_swipe_blacklist_threshold" name="auto_swipe_blacklist_threshold" type="number" min="0" step="1" value="1" class="text_pole">
|
||||
<div data-newbie-hidden class="inline-drawer wide100p flexFlowColumn">
|
||||
<div class="inline-drawer-toggle inline-drawer-header">
|
||||
<b><span data-i18n="Auto-swipe">Auto-swipe</span></b>
|
||||
<div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
<label class="checkbox_label" for="auto_swipe">
|
||||
<input id="auto_swipe" type="checkbox" />
|
||||
<span data-i18n="Enabled">Enabled</span>
|
||||
</label>
|
||||
<div data-i18n="Minimum generated message length">Minimum generated message length</div>
|
||||
<input id="auto_swipe_minimum_length" name="auto_swipe_minimum_length" type="number" min="0" step="1" value="0" class="text_pole">
|
||||
<div data-i18n="Blacklisted words">Blacklisted words</div>
|
||||
<div class="auto_swipe">
|
||||
<textarea id="auto_swipe_blacklist" name="auto_swipe_blacklist" data-i18n="[placeholder]words you dont want generated separated by comma ','" placeholder="words you dont want generated separated by comma ','" class="text_pole textarea_compact" maxlength="5000" value="" autocomplete="off" rows="3"></textarea>
|
||||
<div data-i18n="Blacklisted word count to swipe">Blacklisted word count to swipe</div>
|
||||
<input id="auto_swipe_blacklist_threshold" name="auto_swipe_blacklist_threshold" type="number" min="0" step="1" value="1" class="text_pole">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div data-newbie-hidden class="flex-container">
|
||||
<div id="reload_chat" class="menu_button whitespacenowrap" data-i18n="Reload Chat">
|
||||
Reload Chat
|
||||
|
@ -3219,7 +3247,7 @@
|
|||
</div>
|
||||
<div class="right_menu_button fa-solid fa-list-ul" id="rm_button_characters" title="Select/Create Characters" data-i18n="[title]Select/Create Characters"></div>
|
||||
</div>
|
||||
<div name="HotSwapWrapper" class="alignitemscenter flex-container margin0auto">
|
||||
<div id="HotSwapWrapper" class="alignitemscenter flex-container margin0auto width100p">
|
||||
<div class="hotswap flex-container flex1"></div>
|
||||
</div>
|
||||
|
||||
|
@ -3232,9 +3260,16 @@
|
|||
<h2></h2>
|
||||
</div>
|
||||
<div id="result_info" class="flex-container" style="display: none;">
|
||||
<span id="result_info_text" title="Token counts may be inaccurate and provided just for reference." data-i18n="[title]Token counts may be inaccurate and provided just for reference.">
|
||||
<strong id="result_info_total_tokens">Calculating...</strong> Total Tokens
|
||||
</span>
|
||||
<div id="result_info_text" title="Token counts may be inaccurate and provided just for reference." data-i18n="[title]Token counts may be inaccurate and provided just for reference.">
|
||||
<div>
|
||||
<strong id="result_info_total_tokens" title="Total tokens">Calculating...</strong> <span data-i18n="Tokens">Tokens</span>
|
||||
</div>
|
||||
<div>
|
||||
<small title="Permanent tokens">
|
||||
(<span id="result_info_permanent_tokens"></span> <span data-i18n="Permanent">Permanent</span>)
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<a id="chartokenwarning" class="right_menu_button fa-solid fa-triangle-exclamation" href="https://docs.sillytavern.app/usage/core-concepts/characterdesign/#character-tokens" target="_blank" title="About Token 'Limits'"></a>
|
||||
<i title="Click for stats!" class="fa-solid fa-ranking-star right_menu_button rm_stats_button"></i>
|
||||
<i title="Toggle character info panel" id="hideCharPanelAvatarButton" class="fa-solid fa-eye right_menu_button"></i>
|
||||
|
@ -3250,7 +3285,7 @@
|
|||
<div id="name_div">
|
||||
<input id="character_name_pole" name="ch_name" class="text_pole" data-i18n="[placeholder]Name this character" placeholder="Name this character" maxlength="50" value="" autocomplete="off">
|
||||
<div class="extension_token_counter">
|
||||
Tokens: <span data-token-counter="character_name_pole">counting...</span>
|
||||
Tokens: <span data-token-counter="character_name_pole" data-token-permanent="true">counting...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -3332,7 +3367,7 @@
|
|||
|
||||
<textarea id="description_textarea" data-i18n="[placeholder]Describe your character's physical and mental traits here." placeholder="Describe your character's physical and mental traits here." class="marginBot5" name="description" placeholder=""></textarea>
|
||||
<div class="extension_token_counter">
|
||||
Tokens: <span data-token-counter="description_textarea">counting...</span>
|
||||
Tokens: <span data-token-counter="description_textarea" data-token-permanent="true">counting...</span>
|
||||
</div>
|
||||
|
||||
<div id="first_message_div" class="marginBot5 title_restorable">
|
||||
|
@ -3616,7 +3651,7 @@
|
|||
</h4>
|
||||
<textarea id="personality_textarea" name="personality" data-i18n="[placeholder](A brief description of the personality)" placeholder="(A brief description of the personality)" form="form_create" class="text_pole" autocomplete="off" rows="1" maxlength="20000"></textarea>
|
||||
<div class="extension_token_counter">
|
||||
Tokens: <span data-token-counter="personality_textarea">counting...</span>
|
||||
Tokens: <span data-token-counter="personality_textarea" data-token-permanent="true">counting...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -3629,7 +3664,7 @@
|
|||
</h4>
|
||||
<textarea id="scenario_pole" name="scenario" data-i18n="[placeholder](Circumstances and context of the interaction)" placeholder="(Circumstances and context of the interaction)" class="text_pole" maxlength="20000" value="" autocomplete="off" form="form_create" rows="1"></textarea>
|
||||
<div class="extension_token_counter">
|
||||
Tokens: <span data-token-counter="scenario_pole">counting...</span>
|
||||
Tokens: <span data-token-counter="scenario_pole" data-token-permanent="true">counting...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -3782,6 +3817,7 @@
|
|||
<div id="tag_view_template" class="template_element">
|
||||
<div class="tag_view_item">
|
||||
<div class="tagColorPickerHolder"></div>
|
||||
<div class="tagColorPicker2Holder"></div>
|
||||
<div class="tag_view_name" contenteditable="true"></div>
|
||||
<div class="tag_view_counter"><span class="tag_view_counter_value"></span> entries</div>
|
||||
<div title="Delete tag" class="tag_delete fa-solid fa-trash-can right_menu_button" data-i18n="[title]Delete tag"></div>
|
||||
|
@ -3931,6 +3967,7 @@
|
|||
<option value="1" data-i18n="After Char Defs">After Char Defs</option>
|
||||
<option value="2" data-i18n="Before AN">Before AN</option>
|
||||
<option value="3" data-i18n="After AN">After AN</option>
|
||||
<option value="4" data-i18n="at Depth">at Depth</option>
|
||||
</select>
|
||||
</small>
|
||||
</div>
|
||||
|
@ -3938,7 +3975,7 @@
|
|||
|
||||
<div class="world_entry_form_control wi-enter-footer-text flex-container flexNoGap ">
|
||||
<label for="order" data-i18n="Order:">Order:</label>
|
||||
<input class="text_pole wideMax100px margin0" type="number" name="order" placeholder="" />
|
||||
<input class="text_pole wideMax100px margin0" type="number" name="order" placeholder="" min="0" max="10000" />
|
||||
</div>
|
||||
|
||||
|
||||
|
@ -4094,7 +4131,8 @@
|
|||
<div title="Generate Image" class="sd_message_gen fa-solid fa-paintbrush" data-i18n="[title]Generate Image"></div>
|
||||
<div title="Narrate" class="mes_narrate fa-solid fa-bullhorn" data-i18n="[title]Narrate"></div>
|
||||
<div title="Prompt" class="mes_prompt fa-solid fa-square-poll-horizontal " data-i18n="[title]Prompt"></div>
|
||||
<div title="Create bookmark" class="mes_create_bookmark fa-regular fa-code-branch" data-i18n="[title]Create Bookmark"></div>
|
||||
<div title="Create bookmark" class="mes_create_bookmark fa-regular fa-solid fa-book-bookmark" data-i18n="[title]Create Bookmark"></div>
|
||||
<div title="Create branch" class="mes_create_branch fa-regular fa-code-branch" data-i18n="[title]Create Branch"></div>
|
||||
<div title="Copy" class="mes_copy fa-solid fa-copy " data-i18n="[title]Copy"></div>
|
||||
</div>
|
||||
<div title="Open bookmark chat" class="mes_bookmark fa-solid fa-bookmark" data-i18n="[title]Open bookmark chat"></div>
|
||||
|
@ -4272,6 +4310,10 @@
|
|||
</div>
|
||||
|
||||
<div class="floating_prompt_radio_group">
|
||||
<label>
|
||||
<input type="radio" name="extension_floating_position" value="2" />
|
||||
Before Main Prompt / Story String
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="extension_floating_position" value="0" />
|
||||
After Main Prompt / Story String
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "Pygmalion",
|
||||
"system_prompt": "Enter RP mode. You shall reply to {{user}} while staying in character. Your responses must be detailed, creative, immersive, and drive the scenario forward. You will follow {{char}}'s persona.",
|
||||
"input_sequence": "<|user|>",
|
||||
"output_sequence": "<|model|>",
|
||||
"first_output_sequence": "",
|
||||
"last_output_sequence": "",
|
||||
"system_sequence_prefix": "<|system|>",
|
||||
"system_sequence_suffix": "",
|
||||
"stop_sequence": "<|user|>",
|
||||
"separator_sequence": "",
|
||||
"wrap": false,
|
||||
"macro": true,
|
||||
"names": true,
|
||||
"names_force_groups": true,
|
||||
"activation_regex": ""
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
(function($) {
|
||||
|
||||
var Defaults = $.fn.select2.amd.require('select2/defaults');
|
||||
|
||||
$.extend(Defaults.defaults, {
|
||||
searchInputPlaceholder: '',
|
||||
searchInputCssClass: '',
|
||||
});
|
||||
|
||||
var SearchDropdown = $.fn.select2.amd.require('select2/dropdown/search');
|
||||
|
||||
var _renderSearchDropdown = SearchDropdown.prototype.render;
|
||||
|
||||
SearchDropdown.prototype.render = function(decorated) {
|
||||
|
||||
// invoke parent method
|
||||
var $rendered = _renderSearchDropdown.apply(this, Array.prototype.slice.apply(arguments));
|
||||
|
||||
this.$search.attr('placeholder', this.options.get('searchInputPlaceholder'));
|
||||
this.$search.addClass(this.options.get('searchInputCssClass'));
|
||||
|
||||
return $rendered;
|
||||
};
|
||||
|
||||
})(window.jQuery);
|
669
public/script.js
669
public/script.js
File diff suppressed because it is too large
Load Diff
|
@ -60,7 +60,7 @@ const registerPromptManagerMigration = () => {
|
|||
* Represents a prompt.
|
||||
*/
|
||||
class Prompt {
|
||||
identifier; role; content; name; system_prompt;
|
||||
identifier; role; content; name; system_prompt; position;
|
||||
|
||||
/**
|
||||
* Create a new Prompt instance.
|
||||
|
@ -71,13 +71,15 @@ class Prompt {
|
|||
* @param {string} param0.content - The content of the prompt.
|
||||
* @param {string} param0.name - The name of the prompt.
|
||||
* @param {boolean} param0.system_prompt - Indicates if the prompt is a system prompt.
|
||||
* @param {string} param0.position - The position of the prompt in the prompt list.
|
||||
*/
|
||||
constructor({ identifier, role, content, name, system_prompt } = {}) {
|
||||
constructor({ identifier, role, content, name, system_prompt, position } = {}) {
|
||||
this.identifier = identifier;
|
||||
this.role = role;
|
||||
this.content = content;
|
||||
this.name = name;
|
||||
this.system_prompt = system_prompt;
|
||||
this.position = position;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ import {
|
|||
SECRET_KEYS,
|
||||
secret_state,
|
||||
} from "./secrets.js";
|
||||
import { debounce, delay, getStringHash, waitUntilCondition } from "./utils.js";
|
||||
import { debounce, delay, getStringHash, isUrlOrAPIKey, waitUntilCondition } from "./utils.js";
|
||||
import { chat_completion_sources, oai_settings } from "./openai.js";
|
||||
import { getTokenCount } from "./tokenizers.js";
|
||||
|
||||
|
@ -210,10 +210,12 @@ $("#character_popup").on("input", function () { countTokensDebounced(); });
|
|||
//function:
|
||||
export function RA_CountCharTokens() {
|
||||
let total_tokens = 0;
|
||||
let permanent_tokens = 0;
|
||||
|
||||
$('[data-token-counter]').each(function () {
|
||||
const counter = $(this);
|
||||
const input = $(document.getElementById(counter.data('token-counter')));
|
||||
const isPermanent = counter.data('token-permanent') === true;
|
||||
const value = String(input.val());
|
||||
|
||||
if (input.length === 0) {
|
||||
|
@ -230,10 +232,12 @@ export function RA_CountCharTokens() {
|
|||
|
||||
if (input.data('last-value-hash') === valueHash) {
|
||||
total_tokens += Number(counter.text());
|
||||
permanent_tokens += isPermanent ? Number(counter.text()) : 0;
|
||||
} else {
|
||||
const tokens = getTokenCount(value);
|
||||
counter.text(tokens);
|
||||
total_tokens += tokens;
|
||||
permanent_tokens += isPermanent ? tokens : 0;
|
||||
input.data('last-value-hash', valueHash);
|
||||
}
|
||||
});
|
||||
|
@ -242,6 +246,7 @@ export function RA_CountCharTokens() {
|
|||
const tokenLimit = Math.max(((main_api !== 'openai' ? max_context : oai_settings.openai_max_context) / 2), 1024);
|
||||
const showWarning = (total_tokens > tokenLimit);
|
||||
$('#result_info_total_tokens').text(total_tokens);
|
||||
$('#result_info_permanent_tokens').text(permanent_tokens);
|
||||
$('#result_info_text').toggleClass('neutral_warning', showWarning);
|
||||
$('#chartokenwarning').toggle(showWarning);
|
||||
}
|
||||
|
@ -274,10 +279,16 @@ export async function favsToHotswap() {
|
|||
const entities = getEntitiesList({ doFilter: false });
|
||||
const container = $('#right-nav-panel .hotswap');
|
||||
const template = $('#hotswap_template .hotswapAvatar');
|
||||
container.empty();
|
||||
const maxCount = 6;
|
||||
const DEFAULT_COUNT = 6;
|
||||
const WIDTH_PER_ITEM = 60; // 50px + 5px gap + 5px padding
|
||||
const containerWidth = container.outerWidth();
|
||||
const maxCount = containerWidth > 0 ? Math.floor(containerWidth / WIDTH_PER_ITEM) : DEFAULT_COUNT;
|
||||
let count = 0;
|
||||
|
||||
const promises = [];
|
||||
const newContainer = container.clone();
|
||||
newContainer.empty();
|
||||
|
||||
for (const entity of entities) {
|
||||
if (count >= maxCount) {
|
||||
break;
|
||||
|
@ -310,23 +321,39 @@ export async function favsToHotswap() {
|
|||
}
|
||||
|
||||
if (isCharacter) {
|
||||
const avatarUrl = getThumbnailUrl('avatar', entity.item.avatar);
|
||||
$(slot).find('img').attr('src', avatarUrl);
|
||||
$(slot).attr('title', entity.item.avatar);
|
||||
const imgLoadPromise = new Promise((resolve) => {
|
||||
const avatarUrl = getThumbnailUrl('avatar', entity.item.avatar);
|
||||
$(slot).find('img').attr('src', avatarUrl).on('load', resolve);
|
||||
$(slot).attr('title', entity.item.avatar);
|
||||
});
|
||||
|
||||
// if the image doesn't load in 500ms, resolve the promise anyway
|
||||
promises.push(Promise.race([imgLoadPromise, delay(500)]));
|
||||
}
|
||||
|
||||
$(slot).css('cursor', 'pointer');
|
||||
container.append(slot);
|
||||
newContainer.append(slot);
|
||||
count++;
|
||||
}
|
||||
|
||||
// there are 6 slots in total,
|
||||
if (count < maxCount) { //if any are left over
|
||||
// don't fill leftover spaces with avatar placeholders
|
||||
// just evenly space the selected avatars instead
|
||||
/*
|
||||
if (count < maxCount) { //if any space is left over
|
||||
let leftOverSlots = maxCount - count;
|
||||
for (let i = 1; i <= leftOverSlots; i++) {
|
||||
container.append(template.clone());
|
||||
newContainer.append(template.clone());
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
await Promise.allSettled(promises);
|
||||
//helpful instruction message if no characters are favorited
|
||||
if (count === 0) { container.html(`<small><span><i class="fa-solid fa-star"></i> Favorite characters to add them to HotSwaps</span></small>`) }
|
||||
//otherwise replace with fav'd characters
|
||||
if (count > 0) {
|
||||
container.replaceWith(newContainer);
|
||||
}
|
||||
}
|
||||
|
||||
//changes input bar and send button display depending on connection status
|
||||
|
@ -403,15 +430,6 @@ function RA_autoconnect(PrevApi) {
|
|||
}
|
||||
}
|
||||
|
||||
function isUrlOrAPIKey(string) {
|
||||
try {
|
||||
new URL(string);
|
||||
return true;
|
||||
} catch (_) {
|
||||
// return pattern.test(string);
|
||||
}
|
||||
}
|
||||
|
||||
function OpenNavPanels() {
|
||||
const deviceInfo = getDeviceInfo();
|
||||
if (deviceInfo && deviceInfo.device.type === 'desktop') {
|
||||
|
@ -867,8 +885,12 @@ export function initRossMods() {
|
|||
|
||||
//this makes the chat input text area resize vertically to match the text size (limited by CSS at 50% window height)
|
||||
$('#send_textarea').on('input', function () {
|
||||
const chatBlock = $('#chat');
|
||||
const originalScrollBottom = chatBlock[0].scrollHeight - (chatBlock.scrollTop() + chatBlock.outerHeight());
|
||||
this.style.height = window.getComputedStyle(this).getPropertyValue('min-height');
|
||||
this.style.height = (this.scrollHeight) + 'px';
|
||||
const newScrollTop = chatBlock[0].scrollHeight - (chatBlock.outerHeight() + originalScrollBottom);
|
||||
chatBlock.scrollTop(newScrollTop);
|
||||
});
|
||||
|
||||
//Regenerate if user swipes on the last mesage in chat
|
||||
|
@ -906,10 +928,13 @@ export function initRossMods() {
|
|||
}
|
||||
|
||||
$(document).on('keydown', function (event) {
|
||||
processHotkeys(event);
|
||||
processHotkeys(event.originalEvent);
|
||||
});
|
||||
|
||||
//Additional hotkeys CTRL+ENTER and CTRL+UPARROW
|
||||
/**
|
||||
* @param {KeyboardEvent} event
|
||||
*/
|
||||
function processHotkeys(event) {
|
||||
//Enter to send when send_textarea in focus
|
||||
if ($(':focus').attr('id') === 'send_textarea') {
|
||||
|
@ -943,6 +968,14 @@ export function initRossMods() {
|
|||
}, 300);
|
||||
}
|
||||
|
||||
// Alt+Enter or AltGr+Enter to Continue
|
||||
if ((event.altKey || (event.altKey && event.ctrlKey)) && event.key == "Enter") {
|
||||
if (is_send_press == false) {
|
||||
console.debug("Continuing with Alt+Enter");
|
||||
$('#option_continue').trigger('click');
|
||||
}
|
||||
}
|
||||
|
||||
// Ctrl+Enter for Regeneration Last Response. If editing, accept the edits instead
|
||||
if (event.ctrlKey && event.key == "Enter") {
|
||||
const editMesDone = $(".mes_edit_done:visible");
|
||||
|
@ -958,14 +991,6 @@ export function initRossMods() {
|
|||
}
|
||||
}
|
||||
|
||||
// Alt+Enter to Continue
|
||||
if (event.altKey && event.key == "Enter") {
|
||||
if (is_send_press == false) {
|
||||
console.debug("Continuing with Alt+Enter");
|
||||
$('#option_continue').trigger('click');
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to check if nanogallery2's lightbox is active
|
||||
function isNanogallery2LightboxActive() {
|
||||
// Check if the body has the 'nGY2On' class, adjust this based on actual behavior
|
||||
|
@ -1097,13 +1122,14 @@ export function initRossMods() {
|
|||
$("#rightNavDrawerIcon").trigger('click');
|
||||
return
|
||||
}
|
||||
if ($(".draggable").is(":visible")) {
|
||||
// Remove the first matched element
|
||||
$('.draggable:first').remove();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ($(".draggable").is(":visible")) {
|
||||
// Remove the first matched element
|
||||
$('.draggable:first').remove();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (event.ctrlKey && /^[1-9]$/.test(event.key)) {
|
||||
|
|
|
@ -133,6 +133,39 @@ async function saveBookmarkMenu() {
|
|||
return createNewBookmark(chat.length - 1);
|
||||
}
|
||||
|
||||
export async function createBranch(mesId) {
|
||||
if (!chat.length) {
|
||||
toastr.warning('The chat is empty.', 'Branch creation failed');
|
||||
return;
|
||||
}
|
||||
|
||||
if (mesId < 0 || mesId >= chat.length) {
|
||||
toastr.warning('Invalid message ID.', 'Branch creation failed');
|
||||
return;
|
||||
}
|
||||
|
||||
const lastMes = chat[mesId];
|
||||
const mainChat = selected_group ? groups?.find(x => x.id == selected_group)?.chat_id : characters[this_chid].chat;
|
||||
const newMetadata = { main_chat: mainChat };
|
||||
let name = `Branch #${mesId} - ${humanizedDateTime()}`
|
||||
|
||||
if (selected_group) {
|
||||
await saveGroupBookmarkChat(selected_group, name, newMetadata, mesId);
|
||||
} else {
|
||||
await saveChat(name, newMetadata, mesId);
|
||||
}
|
||||
// append to branches list if it exists
|
||||
// otherwise create it
|
||||
if (typeof lastMes.extra !== 'object') {
|
||||
lastMes.extra = {};
|
||||
}
|
||||
if (typeof lastMes.extra['branches'] !== 'object') {
|
||||
lastMes.extra['branches'] = [];
|
||||
}
|
||||
lastMes.extra['branches'].push(name);
|
||||
return name;
|
||||
}
|
||||
|
||||
async function createNewBookmark(mesId) {
|
||||
if (!chat.length) {
|
||||
toastr.warning('The chat is empty.', 'Bookmark creation failed');
|
||||
|
@ -280,7 +313,6 @@ async function convertSoloToGroupChat() {
|
|||
message.name = character.name;
|
||||
message.original_avatar = character.avatar;
|
||||
message.force_avatar = getThumbnailUrl('avatar', character.avatar);
|
||||
message.is_name = true;
|
||||
|
||||
// Allow regens of a single message in group
|
||||
if (typeof message.extra !== 'object') {
|
||||
|
|
|
@ -134,7 +134,10 @@ const extension_settings = {
|
|||
caption: {
|
||||
refine_mode: false,
|
||||
},
|
||||
expressions: {},
|
||||
expressions: {
|
||||
/** @type {string[]} */
|
||||
custom: [],
|
||||
},
|
||||
dice: {},
|
||||
regex: [],
|
||||
tts: {},
|
||||
|
@ -153,6 +156,8 @@ const extension_settings = {
|
|||
},
|
||||
speech_recognition: {},
|
||||
rvc: {},
|
||||
hypebot: {},
|
||||
vectors: {},
|
||||
};
|
||||
|
||||
let modules = [];
|
||||
|
@ -200,7 +205,7 @@ async function doExtrasFetch(endpoint, args) {
|
|||
|
||||
async function discoverExtensions() {
|
||||
try {
|
||||
const response = await fetch('/discover_extensions');
|
||||
const response = await fetch('/api/extensions/discover');
|
||||
|
||||
if (response.ok) {
|
||||
const extensions = await response.json();
|
||||
|
@ -605,7 +610,7 @@ async function showExtensionsDetails() {
|
|||
async function onUpdateClick() {
|
||||
const extensionName = $(this).data('name');
|
||||
try {
|
||||
const response = await fetch('/update_extension', {
|
||||
const response = await fetch('/api/extensions/update', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({ extensionName })
|
||||
|
@ -625,7 +630,7 @@ async function onUpdateClick() {
|
|||
|
||||
/**
|
||||
* Handles the click event for the delete button of an extension.
|
||||
* This function makes a POST request to '/delete_extension' with the extension's name.
|
||||
* This function makes a POST request to '/api/extensions/delete' with the extension's name.
|
||||
* If the extension is deleted, it displays a success message.
|
||||
* Creates a popup for the user to confirm before delete.
|
||||
*/
|
||||
|
@ -635,7 +640,7 @@ async function onDeleteClick() {
|
|||
const confirmation = await callPopup(`Are you sure you want to delete ${extensionName}?`, 'delete_extension');
|
||||
if (confirmation) {
|
||||
try {
|
||||
const response = await fetch('/delete_extension', {
|
||||
const response = await fetch('/api/extensions/delete', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({ extensionName })
|
||||
|
@ -662,7 +667,7 @@ async function onDeleteClick() {
|
|||
*/
|
||||
async function getExtensionVersion(extensionName) {
|
||||
try {
|
||||
const response = await fetch('/get_extension_version', {
|
||||
const response = await fetch('/api/extensions/version', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({ extensionName })
|
||||
|
|
|
@ -150,7 +150,7 @@ async function installAsset(url, assetType, filename) {
|
|||
const category = assetType;
|
||||
try {
|
||||
const body = { url, category, filename };
|
||||
const result = await fetch('/asset_download', {
|
||||
const result = await fetch('/api/assets/download', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify(body),
|
||||
|
@ -171,7 +171,7 @@ async function deleteAsset(assetType, filename) {
|
|||
const category = assetType;
|
||||
try {
|
||||
const body = { category, filename };
|
||||
const result = await fetch('/asset_delete', {
|
||||
const result = await fetch('/api/assets/delete', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify(body),
|
||||
|
@ -194,7 +194,7 @@ async function deleteAsset(assetType, filename) {
|
|||
async function updateCurrentAssets() {
|
||||
console.debug(DEBUG_PREFIX, "Checking installed assets...")
|
||||
try {
|
||||
const result = await fetch(`/get_assets`, {
|
||||
const result = await fetch(`/api/assets/get`, {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
});
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
/*
|
||||
Ideas:
|
||||
- Clean design of new ui
|
||||
- change select text versus options for playing: audio
|
||||
- cross fading between bgm / start a different time
|
||||
- fading should appear before end when switching randomly
|
||||
- Background based ambient sounds
|
||||
- import option on background UI ?
|
||||
- Allow background music edition using background menu
|
||||
|
@ -59,6 +62,8 @@ const DEFAULT_EXPRESSIONS = [
|
|||
];
|
||||
const SPRITE_DOM_ID = "#expression-image";
|
||||
|
||||
let current_chat_id = null
|
||||
|
||||
let fallback_BGMS = null; // Initialized only once with module workers
|
||||
let ambients = null; // Initialized only once with module workers
|
||||
let characterMusics = {}; // Updated with module workers
|
||||
|
@ -69,16 +74,27 @@ let currentBackground = null;
|
|||
|
||||
let cooldownBGM = 0;
|
||||
|
||||
let bgmEnded = true;
|
||||
|
||||
//#############################//
|
||||
// Extension UI and Settings //
|
||||
//#############################//
|
||||
|
||||
const defaultSettings = {
|
||||
enabled: false,
|
||||
dynamic_bgm_enabled: false,
|
||||
//dynamic_ambient_enabled: false,
|
||||
|
||||
bgm_locked: true,
|
||||
bgm_muted: true,
|
||||
ambient_muted: true,
|
||||
bgm_volume: 50,
|
||||
bgm_selected: null,
|
||||
|
||||
ambient_locked: true,
|
||||
ambient_muted: true,
|
||||
ambient_volume: 50,
|
||||
ambient_selected: null,
|
||||
|
||||
bgm_cooldown: 30
|
||||
}
|
||||
|
||||
|
@ -90,6 +106,8 @@ function loadSettings() {
|
|||
Object.assign(extension_settings.audio, defaultSettings)
|
||||
}
|
||||
$("#audio_enabled").prop('checked', extension_settings.audio.enabled);
|
||||
$("#audio_dynamic_bgm_enabled").prop('checked', extension_settings.audio.dynamic_bgm_enabled);
|
||||
//$("#audio_dynamic_ambient_enabled").prop('checked', extension_settings.audio.dynamic_ambient_enabled);
|
||||
|
||||
$("#audio_bgm_volume").text(extension_settings.audio.bgm_volume);
|
||||
$("#audio_ambient_volume").text(extension_settings.audio.ambient_volume);
|
||||
|
@ -103,20 +121,55 @@ function loadSettings() {
|
|||
$("#audio_bgm_mute").addClass("redOverlayGlow");
|
||||
$("#audio_bgm").prop("muted", true);
|
||||
}
|
||||
else{
|
||||
else {
|
||||
$("#audio_bgm_mute_icon").addClass("fa-volume-high");
|
||||
$("#audio_bgm_mute_icon").removeClass("fa-volume-mute");
|
||||
$("#audio_bgm_mute").removeClass("redOverlayGlow");
|
||||
$("#audio_bgm").prop("muted", false);
|
||||
}
|
||||
|
||||
if (extension_settings.audio.bgm_locked) {
|
||||
//$("#audio_bgm_lock_icon").removeClass("fa-lock-open");
|
||||
//$("#audio_bgm_lock_icon").addClass("fa-lock");
|
||||
$("#audio_bgm").attr("loop", true);
|
||||
$("#audio_bgm_lock").addClass("redOverlayGlow");
|
||||
}
|
||||
else {
|
||||
//$("#audio_bgm_lock_icon").removeClass("fa-lock");
|
||||
//$("#audio_bgm_lock_icon").addClass("fa-lock-open");
|
||||
$("#audio_bgm").attr("loop", false);
|
||||
$("#audio_bgm_lock").removeClass("redOverlayGlow");
|
||||
}
|
||||
|
||||
/*
|
||||
if (extension_settings.audio.bgm_selected !== null) {
|
||||
$("#audio_bgm_select").append(new Option(extension_settings.audio.bgm_selected, extension_settings.audio.bgm_selected));
|
||||
$("#audio_bgm_select").val(extension_settings.audio.bgm_selected);
|
||||
}*/
|
||||
|
||||
if (extension_settings.audio.ambient_locked) {
|
||||
$("#audio_ambient_lock_icon").removeClass("fa-lock-open");
|
||||
$("#audio_ambient_lock_icon").addClass("fa-lock");
|
||||
$("#audio_ambient_lock").addClass("redOverlayGlow");
|
||||
}
|
||||
else {
|
||||
$("#audio_ambient_lock_icon").removeClass("fa-lock");
|
||||
$("#audio_ambient_lock_icon").addClass("fa-lock-open");
|
||||
}
|
||||
|
||||
/*
|
||||
if (extension_settings.audio.ambient_selected !== null) {
|
||||
$("#audio_ambient_select").append(new Option(extension_settings.audio.ambient_selected, extension_settings.audio.ambient_selected));
|
||||
$("#audio_ambient_select").val(extension_settings.audio.ambient_selected);
|
||||
}*/
|
||||
|
||||
if (extension_settings.audio.ambient_muted) {
|
||||
$("#audio_ambient_mute_icon").removeClass("fa-volume-high");
|
||||
$("#audio_ambient_mute_icon").addClass("fa-volume-mute");
|
||||
$("#audio_ambient_mute").addClass("redOverlayGlow");
|
||||
$("#audio_ambient").prop("muted", true);
|
||||
}
|
||||
else{
|
||||
else {
|
||||
$("#audio_ambient_mute_icon").addClass("fa-volume-high");
|
||||
$("#audio_ambient_mute_icon").removeClass("fa-volume-mute");
|
||||
$("#audio_ambient_mute").removeClass("redOverlayGlow");
|
||||
|
@ -125,7 +178,7 @@ function loadSettings() {
|
|||
|
||||
$("#audio_bgm_cooldown").val(extension_settings.audio.bgm_cooldown);
|
||||
|
||||
$("#audio_debug_div").hide(); // DBG
|
||||
$("#audio_debug_div").hide(); // DBG: comment to see debug mode
|
||||
}
|
||||
|
||||
async function onEnabledClick() {
|
||||
|
@ -142,6 +195,51 @@ async function onEnabledClick() {
|
|||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
async function onDynamicBGMEnabledClick() {
|
||||
extension_settings.audio.dynamic_bgm_enabled = $('#audio_dynamic_bgm_enabled').is(':checked');
|
||||
currentCharacterBGM = null;
|
||||
currentExpressionBGM = null;
|
||||
cooldownBGM = 0;
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
/*
|
||||
async function onDynamicAmbientEnabledClick() {
|
||||
extension_settings.audio.dynamic_ambient_enabled = $('#audio_dynamic_ambient_enabled').is(':checked');
|
||||
currentBackground = null;
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
*/
|
||||
async function onBGMLockClick() {
|
||||
extension_settings.audio.bgm_locked = !extension_settings.audio.bgm_locked;
|
||||
if (extension_settings.audio.bgm_locked) {
|
||||
extension_settings.audio.bgm_selected = $("#audio_bgm_select").val();
|
||||
$("#audio_bgm").attr("loop", true);
|
||||
}
|
||||
else {
|
||||
$("#audio_bgm").attr("loop", false);
|
||||
}
|
||||
//$("#audio_bgm_lock_icon").toggleClass("fa-lock");
|
||||
//$("#audio_bgm_lock_icon").toggleClass("fa-lock-open");
|
||||
$("#audio_bgm_lock").toggleClass("redOverlayGlow");
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
async function onBGMRandomClick() {
|
||||
var select = document.getElementById('audio_bgm_select');
|
||||
var items = select.getElementsByTagName('option');
|
||||
|
||||
if (items.length < 2)
|
||||
return;
|
||||
|
||||
var index;
|
||||
do {
|
||||
index = Math.floor(Math.random() * items.length);
|
||||
} while (index == select.selectedIndex);
|
||||
|
||||
select.selectedIndex = index;
|
||||
onBGMSelectChange();
|
||||
}
|
||||
|
||||
async function onBGMMuteClick() {
|
||||
extension_settings.audio.bgm_muted = !extension_settings.audio.bgm_muted;
|
||||
$("#audio_bgm_mute_icon").toggleClass("fa-volume-high");
|
||||
|
@ -151,6 +249,20 @@ async function onBGMMuteClick() {
|
|||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
async function onAmbientLockClick() {
|
||||
extension_settings.audio.ambient_locked = !extension_settings.audio.ambient_locked;
|
||||
if (extension_settings.audio.ambient_locked)
|
||||
extension_settings.audio.ambient_selected = $("#audio_ambient_select").val();
|
||||
else {
|
||||
extension_settings.audio.ambient_selected = null;
|
||||
currentBackground = null;
|
||||
}
|
||||
$("#audio_ambient_lock_icon").toggleClass("fa-lock");
|
||||
$("#audio_ambient_lock_icon").toggleClass("fa-lock-open");
|
||||
$("#audio_ambient_lock").toggleClass("redOverlayGlow");
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
async function onAmbientMuteClick() {
|
||||
extension_settings.audio.ambient_muted = !extension_settings.audio.ambient_muted;
|
||||
$("#audio_ambient_mute_icon").toggleClass("fa-volume-high");
|
||||
|
@ -176,6 +288,20 @@ async function onAmbientVolumeChange() {
|
|||
//console.debug(DEBUG_PREFIX,"UPDATED Ambient MAX TO",extension_settings.audio.ambient_volume);
|
||||
}
|
||||
|
||||
async function onBGMSelectChange() {
|
||||
extension_settings.audio.bgm_selected = $("#audio_bgm_select").val();
|
||||
updateBGM(true);
|
||||
saveSettingsDebounced();
|
||||
//console.debug(DEBUG_PREFIX,"UPDATED BGM MAX TO",extension_settings.audio.bgm_volume);
|
||||
}
|
||||
|
||||
async function onAmbientSelectChange() {
|
||||
extension_settings.audio.ambient_selected = $("#audio_ambient_select").val();
|
||||
updateAmbient(true);
|
||||
saveSettingsDebounced();
|
||||
//console.debug(DEBUG_PREFIX,"UPDATED BGM MAX TO",extension_settings.audio.bgm_volume);
|
||||
}
|
||||
|
||||
async function onBGMCooldownInput() {
|
||||
extension_settings.audio.bgm_cooldown = ~~($("#audio_bgm_cooldown").val());
|
||||
cooldownBGM = extension_settings.audio.bgm_cooldown * 1000;
|
||||
|
@ -191,7 +317,7 @@ async function getAssetsList(type) {
|
|||
console.debug(DEBUG_PREFIX, "getting assets of type", type);
|
||||
|
||||
try {
|
||||
const result = await fetch(`/get_assets`, {
|
||||
const result = await fetch(`/api/assets/get`, {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
});
|
||||
|
@ -209,7 +335,7 @@ async function getCharacterBgmList(name) {
|
|||
console.debug(DEBUG_PREFIX, "getting bgm list for", name);
|
||||
|
||||
try {
|
||||
const result = await fetch(`/get_character_assets_list?name=${encodeURIComponent(name)}&category=${CHARACTER_BGM_FOLDER}`, {
|
||||
const result = await fetch(`/api/assets/character?name=${encodeURIComponent(name)}&category=${CHARACTER_BGM_FOLDER}`, {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
});
|
||||
|
@ -222,11 +348,42 @@ async function getCharacterBgmList(name) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
//#############################//
|
||||
// Module Worker //
|
||||
//#############################//
|
||||
|
||||
function fillBGMSelect() {
|
||||
let found_last_selected_bgm = false;
|
||||
// Update bgm list in UI
|
||||
$("#audio_bgm_select")
|
||||
.find('option')
|
||||
.remove();
|
||||
|
||||
for (const file of fallback_BGMS) {
|
||||
$('#audio_bgm_select').append(new Option("asset: " + file.replace(/^.*[\\\/]/, '').replace(/\.[^/.]+$/, ""), file));
|
||||
if (file === extension_settings.audio.bgm_selected) {
|
||||
$('#audio_bgm_select').val(extension_settings.audio.bgm_selected);
|
||||
found_last_selected_bgm = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Update bgm list in UI
|
||||
for (const char in characterMusics)
|
||||
for (const e in characterMusics[char])
|
||||
for (const file of characterMusics[char][e]) {
|
||||
$('#audio_bgm_select').append(new Option(char + ": " + file.replace(/^.*[\\\/]/, '').replace(/\.[^/.]+$/, ""), file));
|
||||
if (file === extension_settings.audio.bgm_selected) {
|
||||
$('#audio_bgm_select').val(extension_settings.audio.bgm_selected);
|
||||
found_last_selected_bgm = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found_last_selected_bgm) {
|
||||
$('#audio_bgm_select').val($("#audio_bgm_select option:first").val());
|
||||
extension_settings.audio.bgm_selected = null;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
- Update ambient sound
|
||||
- Update character BGM
|
||||
|
@ -246,6 +403,8 @@ async function moduleWorker() {
|
|||
fallback_BGMS = await getAssetsList(ASSETS_BGM_FOLDER);
|
||||
fallback_BGMS = fallback_BGMS.filter((filename) => filename != ".placeholder")
|
||||
console.debug(DEBUG_PREFIX, "Detected assets:", fallback_BGMS);
|
||||
|
||||
fillBGMSelect();
|
||||
}
|
||||
|
||||
if (ambients == null) {
|
||||
|
@ -253,10 +412,33 @@ async function moduleWorker() {
|
|||
ambients = await getAssetsList(ASSETS_AMBIENT_FOLDER);
|
||||
ambients = ambients.filter((filename) => filename != ".placeholder")
|
||||
console.debug(DEBUG_PREFIX, "Detected assets:", ambients);
|
||||
|
||||
// Update bgm list in UI
|
||||
$("#audio_ambient_select")
|
||||
.find('option')
|
||||
.remove();
|
||||
|
||||
if (extension_settings.audio.ambient_selected !== null) {
|
||||
let ambient_label = extension_settings.audio.ambient_selected;
|
||||
if (ambient_label.includes("assets"))
|
||||
ambient_label = "asset: " + ambient_label.replace(/^.*[\\\/]/, '').replace(/\.[^/.]+$/, "");
|
||||
else {
|
||||
ambient_label = ambient_label.substring("/characters/".length);
|
||||
ambient_label = ambient_label.substring(0, ambient_label.indexOf("/")) + ": " + ambient_label.substring(ambient_label.indexOf("/") + "/bgm/".length);
|
||||
ambient_label = ambient_label.replace(/\.[^/.]+$/, "");
|
||||
}
|
||||
$('#audio_ambient_select').append(new Option(ambient_label, extension_settings.audio.ambient_selected));
|
||||
}
|
||||
|
||||
for (const file of ambients) {
|
||||
if (file !== extension_settings.audio.ambient_selected)
|
||||
$("#audio_ambient_select").append(new Option("asset: " + file.replace(/^.*[\\\/]/, '').replace(/\.[^/.]+$/, ""), file));
|
||||
}
|
||||
}
|
||||
|
||||
// 1) Update ambient audio
|
||||
// ---------------------------
|
||||
//if (extension_settings.audio.dynamic_ambient_enabled) {
|
||||
let newBackground = $("#bg1").css("background-image");
|
||||
const custom_background = getContext()["chatMetadata"]["custom_background"];
|
||||
|
||||
|
@ -275,6 +457,7 @@ async function moduleWorker() {
|
|||
updateAmbient();
|
||||
}
|
||||
}
|
||||
//}
|
||||
|
||||
const context = getContext();
|
||||
//console.debug(DEBUG_PREFIX,context);
|
||||
|
@ -288,6 +471,14 @@ async function moduleWorker() {
|
|||
// 1) Update BGM (single chat)
|
||||
// -----------------------------
|
||||
if (!chatIsGroup) {
|
||||
|
||||
// Reset bgm list on new chat
|
||||
if (context.chatId != current_chat_id) {
|
||||
current_chat_id = context.chatId;
|
||||
characterMusics = {};
|
||||
cooldownBGM = 0;
|
||||
}
|
||||
|
||||
newCharacter = context.name2;
|
||||
|
||||
//console.log(DEBUG_PREFIX,"SOLO CHAT MODE"); // DBG
|
||||
|
@ -307,7 +498,7 @@ async function moduleWorker() {
|
|||
if (currentCharacterBGM !== newCharacter) {
|
||||
currentCharacterBGM = newCharacter;
|
||||
try {
|
||||
await updateBGM();
|
||||
await updateBGM(false, true);
|
||||
cooldownBGM = extension_settings.audio.bgm_cooldown * 1000;
|
||||
}
|
||||
catch (error) {
|
||||
|
@ -329,9 +520,9 @@ async function moduleWorker() {
|
|||
}
|
||||
|
||||
try {
|
||||
currentExpressionBGM = newExpression;
|
||||
await updateBGM();
|
||||
cooldownBGM = extension_settings.audio.bgm_cooldown * 1000;
|
||||
currentExpressionBGM = newExpression;
|
||||
console.debug(DEBUG_PREFIX, "(SOLO) Updated current character expression to", currentExpressionBGM, "cooldown", cooldownBGM);
|
||||
}
|
||||
catch (error) {
|
||||
|
@ -346,6 +537,34 @@ async function moduleWorker() {
|
|||
|
||||
// 2) Update BGM (group chat)
|
||||
// -----------------------------
|
||||
|
||||
// Load current chat character bgms
|
||||
// Reset bgm list on new chat
|
||||
if (context.chatId != current_chat_id) {
|
||||
current_chat_id = context.chatId;
|
||||
characterMusics = {};
|
||||
cooldownBGM = 0;
|
||||
for (const message of context.chat) {
|
||||
if (characterMusics[message.name] === undefined)
|
||||
await loadCharacterBGM(message.name);
|
||||
}
|
||||
|
||||
try {
|
||||
newCharacter = context.chat[context.chat.length - 1].name;
|
||||
currentCharacterBGM = newCharacter;
|
||||
await updateBGM(false, true);
|
||||
cooldownBGM = extension_settings.audio.bgm_cooldown * 1000;
|
||||
currentCharacterBGM = newCharacter;
|
||||
currentExpressionBGM = FALLBACK_EXPRESSION;
|
||||
console.debug(DEBUG_PREFIX, "(GROUP) Updated current character BGM to", currentExpressionBGM, "cooldown", cooldownBGM);
|
||||
}
|
||||
catch (error) {
|
||||
console.debug(DEBUG_PREFIX, "Error while trying to update BGM group, will try again");
|
||||
currentCharacterBGM = null
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
newCharacter = context.chat[context.chat.length - 1].name;
|
||||
const userName = context.name1;
|
||||
|
||||
|
@ -353,17 +572,17 @@ async function moduleWorker() {
|
|||
|
||||
//console.log(DEBUG_PREFIX,"GROUP CHAT MODE"); // DBG
|
||||
|
||||
// 2.1) First time character appear
|
||||
// 2.1) New character appear
|
||||
if (characterMusics[newCharacter] === undefined) {
|
||||
await loadCharacterBGM(newCharacter);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2.2) Switched chat
|
||||
// 2.2) Switched char
|
||||
if (currentCharacterBGM !== newCharacter) {
|
||||
// Check cooldown
|
||||
if (cooldownBGM > 0) {
|
||||
//console.debug(DEBUG_PREFIX,"(GROUP) BGM switch on cooldown:",cooldownBGM);
|
||||
console.debug(DEBUG_PREFIX, "(GROUP) BGM switch on cooldown:", cooldownBGM);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -429,6 +648,8 @@ async function loadCharacterBGM(newCharacter) {
|
|||
characterMusics[newCharacter][e].push(i);
|
||||
}
|
||||
console.debug(DEBUG_PREFIX, "Updated BGM map of", newCharacter, "to", characterMusics[newCharacter]);
|
||||
|
||||
fillBGMSelect();
|
||||
}
|
||||
|
||||
function getNewExpression() {
|
||||
|
@ -458,24 +679,52 @@ function getNewExpression() {
|
|||
return newExpression;
|
||||
}
|
||||
|
||||
async function updateBGM() {
|
||||
let audio_files = characterMusics[currentCharacterBGM][currentExpressionBGM];// Try char expression BGM
|
||||
|
||||
if (audio_files === undefined || audio_files.length == 0) {
|
||||
console.debug(DEBUG_PREFIX, "No BGM for", currentCharacterBGM, currentExpressionBGM);
|
||||
audio_files = characterMusics[currentCharacterBGM][FALLBACK_EXPRESSION]; // Try char FALLBACK BGM
|
||||
if (audio_files === undefined || audio_files.length == 0) {
|
||||
console.debug(DEBUG_PREFIX, "No default BGM for", currentCharacterBGM, FALLBACK_EXPRESSION, "switch to ST BGM");
|
||||
audio_files = fallback_BGMS; // ST FALLBACK BGM
|
||||
|
||||
if (audio_files.length == 0) {
|
||||
console.debug(DEBUG_PREFIX, "No default BGM file found, bgm folder may be empty.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
async function updateBGM(isUserInput = false, newChat = false) {
|
||||
if (!isUserInput && !extension_settings.audio.dynamic_bgm_enabled && $("#audio_bgm").attr("src") != "" && !bgmEnded && !newChat) {
|
||||
console.debug(DEBUG_PREFIX, "BGM already playing and dynamic switch disabled, no update done");
|
||||
return;
|
||||
}
|
||||
|
||||
let audio_file_path = ""
|
||||
if (isUserInput || (extension_settings.audio.bgm_locked && extension_settings.audio.bgm_selected !== null)) {
|
||||
audio_file_path = extension_settings.audio.bgm_selected;
|
||||
|
||||
if (isUserInput)
|
||||
console.debug(DEBUG_PREFIX, "User selected BGM", audio_file_path);
|
||||
if (extension_settings.audio.bgm_locked)
|
||||
console.debug(DEBUG_PREFIX, "BGM locked keeping current audio", audio_file_path);
|
||||
}
|
||||
else {
|
||||
|
||||
let audio_files = null;
|
||||
|
||||
if (extension_settings.audio.dynamic_bgm_enabled) {
|
||||
extension_settings.audio.bgm_selected = null;
|
||||
saveSettingsDebounced();
|
||||
audio_files = characterMusics[currentCharacterBGM][currentExpressionBGM];// Try char expression BGM
|
||||
|
||||
if (audio_files === undefined || audio_files.length == 0) {
|
||||
console.debug(DEBUG_PREFIX, "No BGM for", currentCharacterBGM, currentExpressionBGM);
|
||||
audio_files = characterMusics[currentCharacterBGM][FALLBACK_EXPRESSION]; // Try char FALLBACK BGM
|
||||
if (audio_files === undefined || audio_files.length == 0) {
|
||||
console.debug(DEBUG_PREFIX, "No default BGM for", currentCharacterBGM, FALLBACK_EXPRESSION, "switch to ST BGM");
|
||||
audio_files = fallback_BGMS; // ST FALLBACK BGM
|
||||
|
||||
if (audio_files.length == 0) {
|
||||
console.debug(DEBUG_PREFIX, "No default BGM file found, bgm folder may be empty.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
audio_files = [];
|
||||
$("#audio_bgm_select option").each(function () { audio_files.push($(this).val()); });
|
||||
}
|
||||
|
||||
audio_file_path = audio_files[Math.floor(Math.random() * audio_files.length)];
|
||||
}
|
||||
|
||||
const audio_file_path = audio_files[Math.floor(Math.random() * audio_files.length)];
|
||||
console.log(DEBUG_PREFIX, "Updating BGM");
|
||||
console.log(DEBUG_PREFIX, "Checking file", audio_file_path);
|
||||
try {
|
||||
|
@ -485,20 +734,30 @@ async function updateBGM() {
|
|||
console.log(DEBUG_PREFIX, "File not found!")
|
||||
}
|
||||
else {
|
||||
console.log(DEBUG_PREFIX, "Switching BGM to", currentExpressionBGM)
|
||||
console.log(DEBUG_PREFIX, "Switching BGM to", currentExpressionBGM);
|
||||
$("#audio_bgm_select").val(audio_file_path);
|
||||
const audio = $("#audio_bgm");
|
||||
|
||||
if (audio.attr("src") == audio_file_path) {
|
||||
if (audio.attr("src") == audio_file_path && !bgmEnded) {
|
||||
console.log(DEBUG_PREFIX, "Already playing, ignored");
|
||||
return;
|
||||
}
|
||||
|
||||
audio.animate({ volume: 0.0 }, 2000, function () {
|
||||
let fade_time = 2000;
|
||||
bgmEnded = false;
|
||||
|
||||
if (isUserInput || extension_settings.audio.bgm_locked) {
|
||||
audio.attr("src", audio_file_path);
|
||||
audio[0].play();
|
||||
audio.volume = extension_settings.audio.bgm_volume * 0.01;
|
||||
audio.animate({ volume: extension_settings.audio.bgm_volume * 0.01 }, 2000);
|
||||
})
|
||||
}
|
||||
else {
|
||||
audio.animate({ volume: 0.0 }, fade_time, function () {
|
||||
audio.attr("src", audio_file_path);
|
||||
audio[0].play();
|
||||
audio.volume = extension_settings.audio.bgm_volume * 0.01;
|
||||
audio.animate({ volume: extension_settings.audio.bgm_volume * 0.01 }, fade_time);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
|
@ -506,18 +765,30 @@ async function updateBGM() {
|
|||
}
|
||||
}
|
||||
|
||||
async function updateAmbient() {
|
||||
async function updateAmbient(isUserInput = false) {
|
||||
let audio_file_path = null;
|
||||
for (const i of ambients) {
|
||||
console.debug(i)
|
||||
if (i.includes(currentBackground)) {
|
||||
audio_file_path = i;
|
||||
break;
|
||||
|
||||
if (isUserInput || extension_settings.audio.ambient_locked) {
|
||||
audio_file_path = extension_settings.audio.ambient_selected;
|
||||
|
||||
if (isUserInput)
|
||||
console.debug(DEBUG_PREFIX, "User selected Ambient", audio_file_path);
|
||||
if (extension_settings.audio.bgm_locked)
|
||||
console.debug(DEBUG_PREFIX, "Ambient locked keeping current audio", audio_file_path);
|
||||
}
|
||||
else {
|
||||
extension_settings.audio.ambient_selected = null;
|
||||
for (const i of ambients) {
|
||||
console.debug(i)
|
||||
if (i.includes(currentBackground)) {
|
||||
audio_file_path = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (audio_file_path === null) {
|
||||
console.debug(DEBUG_PREFIX, "No ambient file found for background", currentBackground);
|
||||
console.debug(DEBUG_PREFIX, "No bgm file found for background", currentBackground);
|
||||
const audio = $("#audio_ambient");
|
||||
audio.attr("src", "");
|
||||
audio[0].pause();
|
||||
|
@ -527,45 +798,87 @@ async function updateAmbient() {
|
|||
//const audio_file_path = AMBIENT_FOLDER+currentBackground+".mp3";
|
||||
console.log(DEBUG_PREFIX, "Updating ambient");
|
||||
console.log(DEBUG_PREFIX, "Checking file", audio_file_path);
|
||||
$("#audio_ambient_select").val(audio_file_path);
|
||||
|
||||
let fade_time = 2000;
|
||||
if (isUserInput)
|
||||
fade_time = 0;
|
||||
|
||||
const audio = $("#audio_ambient");
|
||||
audio.animate({ volume: 0.0 }, 2000, function () {
|
||||
|
||||
if (audio.attr("src") == audio_file_path) {
|
||||
console.log(DEBUG_PREFIX, "Already playing, ignored");
|
||||
return;
|
||||
}
|
||||
|
||||
audio.animate({ volume: 0.0 }, fade_time, function () {
|
||||
audio.attr("src", audio_file_path);
|
||||
audio[0].play();
|
||||
audio.volume = extension_settings.audio.ambient_volume * 0.01;
|
||||
audio.animate({ volume: extension_settings.audio.ambient_volume * 0.01 }, 2000);
|
||||
audio.animate({ volume: extension_settings.audio.ambient_volume * 0.01 }, fade_time);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles wheel events on volume sliders.
|
||||
* @param {WheelEvent} e Event
|
||||
*/
|
||||
function onVolumeSliderWheelEvent(e) {
|
||||
const slider = $(this);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const delta = e.deltaY / 20;
|
||||
const sliderVal = Number(slider.val());
|
||||
|
||||
let newVal = sliderVal - delta;
|
||||
if (newVal < 0) {
|
||||
newVal = 0;
|
||||
} else if (newVal > 100) {
|
||||
newVal = 100;
|
||||
}
|
||||
|
||||
slider.val(newVal).trigger('input');
|
||||
}
|
||||
|
||||
//#############################//
|
||||
// Extension load //
|
||||
//#############################//
|
||||
|
||||
// This function is called when the extension is loaded
|
||||
jQuery(async () => {
|
||||
// This is an example of loading HTML from a file
|
||||
const windowHtml = $(await $.get(`${extensionFolderPath}/window.html`));
|
||||
|
||||
$('#extensions_settings').append(windowHtml);
|
||||
loadSettings();
|
||||
|
||||
$("#audio_bgm").attr("loop", true);
|
||||
$("#audio_enabled").on("click", onEnabledClick);
|
||||
$("#audio_dynamic_bgm_enabled").on("click", onDynamicBGMEnabledClick);
|
||||
//$("#audio_dynamic_ambient_enabled").on("click", onDynamicAmbientEnabledClick);
|
||||
|
||||
//$("#audio_bgm").attr("loop", false);
|
||||
$("#audio_ambient").attr("loop", true);
|
||||
|
||||
$("#audio_bgm").hide();
|
||||
$("#audio_ambient").hide();
|
||||
$("#audio_bgm_lock").on("click", onBGMLockClick);
|
||||
$("#audio_bgm_mute").on("click", onBGMMuteClick);
|
||||
$("#audio_ambient_mute").on("click", onAmbientMuteClick);
|
||||
|
||||
$("#audio_enabled").on("click", onEnabledClick);
|
||||
$("#audio_bgm_volume_slider").on("input", onBGMVolumeChange);
|
||||
$("#audio_bgm_random").on("click", onBGMRandomClick);
|
||||
|
||||
$("#audio_ambient").hide();
|
||||
$("#audio_ambient_lock").on("click", onAmbientLockClick);
|
||||
$("#audio_ambient_mute").on("click", onAmbientMuteClick);
|
||||
$("#audio_ambient_volume_slider").on("input", onAmbientVolumeChange);
|
||||
|
||||
document.getElementById('audio_ambient_volume_slider').addEventListener('wheel', onVolumeSliderWheelEvent, { passive: false });
|
||||
document.getElementById('audio_bgm_volume_slider').addEventListener('wheel', onVolumeSliderWheelEvent, { passive: false });
|
||||
|
||||
$("#audio_bgm_cooldown").on("input", onBGMCooldownInput);
|
||||
|
||||
// Reset assets container, will be redected like if ST restarted
|
||||
$("#audio_refresh_assets").on("click", function(){
|
||||
console.debug(DEBUG_PREFIX,"Refreshing audio assets");
|
||||
$("#audio_refresh_assets").on("click", function () {
|
||||
console.debug(DEBUG_PREFIX, "Refreshing audio assets");
|
||||
current_chat_id = null
|
||||
fallback_BGMS = null;
|
||||
ambients = null;
|
||||
characterMusics = {};
|
||||
|
@ -574,6 +887,9 @@ jQuery(async () => {
|
|||
currentBackground = null;
|
||||
})
|
||||
|
||||
$("#audio_bgm_select").on("change", onBGMSelectChange);
|
||||
$("#audio_ambient_select").on("change", onAmbientSelectChange);
|
||||
|
||||
// DBG
|
||||
$("#audio_debug").on("click", function () {
|
||||
if ($("#audio_debug").is(':checked')) {
|
||||
|
@ -587,6 +903,14 @@ jQuery(async () => {
|
|||
});
|
||||
//
|
||||
|
||||
$("#audio_bgm").on("ended", function () {
|
||||
console.debug(DEBUG_PREFIX, "END OF BGM")
|
||||
if (!extension_settings.audio.bgm_locked) {
|
||||
bgmEnded = true;
|
||||
updateBGM();
|
||||
}
|
||||
});
|
||||
|
||||
const wrapper = new ModuleWorkerWrapper(moduleWorker);
|
||||
setInterval(wrapper.update.bind(wrapper), UPDATE_INTERVAL);
|
||||
moduleWorker();
|
||||
|
|
|
@ -1,15 +1,68 @@
|
|||
.mixer-div {
|
||||
.audio-ui-block {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.audio-mixer-div {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 5px;
|
||||
background-color: rgba(38, 38, 38, 0.5);
|
||||
border: 1px rgb(75, 75, 75) solid;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.audio-mixer-element {
|
||||
height: 60px;
|
||||
float: left;
|
||||
margin-right: 10px;
|
||||
label {
|
||||
text-align: top;
|
||||
}
|
||||
input {
|
||||
margin-top: 15px;
|
||||
}
|
||||
select {
|
||||
margin-top: 5px;
|
||||
padding: 5px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.audio-label {
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.audio-volume-div {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.audio-lock-button {
|
||||
padding: 1rem;
|
||||
width: 100%;
|
||||
height: 2em;
|
||||
}
|
||||
|
||||
.audio-random-button {
|
||||
padding: 1rem;
|
||||
width: 100%;
|
||||
height: 2em;
|
||||
}
|
||||
|
||||
.audio-mute-button {
|
||||
padding: 5px;
|
||||
width: 50px;
|
||||
height: 30px;
|
||||
padding: 1rem;
|
||||
width: 100%;
|
||||
height: 2em;
|
||||
}
|
||||
|
||||
.audio-slider {
|
||||
width: 100% !important;
|
||||
vertical-align: center;
|
||||
}
|
||||
|
||||
.audio-mute-button-muted {
|
||||
|
@ -19,4 +72,24 @@
|
|||
#audio_refresh_assets {
|
||||
width: 50px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.audio-mixer-mute {
|
||||
width: 10%;
|
||||
}
|
||||
|
||||
.audio-mixer-volume {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.audio-mixer-playlist {
|
||||
width: 45%;
|
||||
}
|
||||
|
||||
.audio-mixer-lock {
|
||||
width: 10%;
|
||||
}
|
||||
|
||||
.audio-mixer-random {
|
||||
width: 10%;
|
||||
}
|
|
@ -10,6 +10,20 @@
|
|||
<input type="checkbox" id="audio_enabled" name="audio_enabled">
|
||||
<small>Enabled</small>
|
||||
</label>
|
||||
<div id="audio_bgm_dynamic_enable_div">
|
||||
<label class="checkbox_label" for="audio_dynamic_bgm_enabled">
|
||||
<input type="checkbox" id="audio_dynamic_bgm_enabled" name="audio_dynamic_bgm_enabled">
|
||||
<small>Enable expression BGM switch (req. character expression)</small>
|
||||
</label>
|
||||
</div>
|
||||
<!--
|
||||
<div id="audio_ambient_dynamic_enable_div">
|
||||
<label class="checkbox_label" for="audio_dynamic_ambient_enabled">
|
||||
<input type="checkbox" id="audio_dynamic_ambient_enabled" name="audio_dynamic_ambient_enabled">
|
||||
<small>Enable ambient background switch (req. chat background)</small>
|
||||
</label>
|
||||
</div>
|
||||
-->
|
||||
<div id="audio_debug_div">
|
||||
<label class="checkbox_label" for="audio_debug">
|
||||
<input type="checkbox" id="audio_debug" name="audio_debug">
|
||||
|
@ -24,23 +38,63 @@
|
|||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<label for="audio_bgm_volume_slider">Music <span id="audio_bgm_volume"></span></label>
|
||||
<div class="mixer-div">
|
||||
<div id="audio_bgm_mute" class="menu_button audio-mute-button">
|
||||
<i class="fa-solid fa-volume-high fa-lg" id="audio_bgm_mute_icon"></i>
|
||||
<div class="audio-ui-block">
|
||||
<label for="audio_bgm_volume_slider">Music</label>
|
||||
<div class="audio-mixer-div">
|
||||
<div class="audio-mixer-element audio-mixer-mute">
|
||||
<label for="audio_bgm_lock" class="audio-label">Mute</label>
|
||||
<div id="audio_bgm_mute" class="menu_button audio-mute-button">
|
||||
<i class="fa-solid fa-volume-high fa-lg" id="audio_bgm_mute_icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="audio-mixer-element audio-mixer-volume">
|
||||
<label for="audio_bgm_lock" class="audio-label">Vol <span id="audio_bgm_volume"></span></label>
|
||||
<input type="range" class ="audio-slider" id ="audio_bgm_volume_slider" value = "0" maxlength ="100">
|
||||
</div>
|
||||
<div class="audio-mixer-element audio-mixer-playlist">
|
||||
<label for="audio_bgm_lock" class="audio-label">Playlist</label>
|
||||
<select id="audio_bgm_select">
|
||||
</select>
|
||||
</div>
|
||||
<div class="audio-mixer-element audio-mixer-lock">
|
||||
<label for="audio_bgm_lock" class="audio-label">Loop</label>
|
||||
<div id="audio_bgm_lock" class="menu_button audio-lock-button">
|
||||
<i class="fa-solid fa-repeat fa-lg" id="audio_bgm_lock_icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="audio-mixer-element audio-mixer-random">
|
||||
<label for="audio_bgm_random" class="audio-label">Roll</label>
|
||||
<div id="audio_bgm_random" class="menu_button audio-random-button">
|
||||
<i class="fa-solid fa-random fa-lg" id="audio_bgm_random_icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
<input type="range" class ="slider" id ="audio_bgm_volume_slider" value = "0" maxlength ="100">
|
||||
</div>
|
||||
<audio id="audio_bgm" controls src="">
|
||||
</div>
|
||||
<div>
|
||||
<label for="audio_ambient_volume_slider">Ambient <span id="audio_ambient_volume"></span></label>
|
||||
<div class="mixer-div">
|
||||
<div id="audio_ambient_mute" class="menu_button audio-mute-button">
|
||||
<i class="fa-solid fa-volume-high fa-lg" id="audio_ambient_mute_icon"></i>
|
||||
<label for="audio_ambient_volume_slider">Ambient</label>
|
||||
<div class="audio-mixer-div">
|
||||
<div class="audio-mixer-element audio-mixer-mute">
|
||||
<label for="audio_ambient_lock" class="audio-label">Mute</label>
|
||||
<div id="audio_ambient_mute" class="menu_button audio-mute-button">
|
||||
<i class="fa-solid fa-volume-high fa-lg" id="audio_ambient_mute_icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="audio-mixer-element audio-mixer-volume">
|
||||
<label for="audio_ambient_lock" class="audio-label">Vol <span id="audio_ambient_volume"></span></label>
|
||||
<input type="range" class ="audio-slider" id ="audio_ambient_volume_slider" value = "0" maxlength ="100">
|
||||
</div>
|
||||
<div class="audio-mixer-element audio-mixer-playlist">
|
||||
<label for="audio_ambient_lock" class="audio-label">Playlist</label>
|
||||
<select id="audio_ambient_select">
|
||||
</select>
|
||||
</div>
|
||||
<div class="audio-mixer-element">
|
||||
<label for="audio_ambient_lock audio-mixer-lock" class="audio-label">Lock</label>
|
||||
<div id="audio_ambient_lock" class="menu_button audio-lock-button">
|
||||
<i class="fa-solid fa-lock-open fa-lg" id="audio_ambient_lock_icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
<input type="range" class ="slider" id ="audio_ambient_volume_slider" value = "0" maxlength ="100">
|
||||
</div>
|
||||
<audio id="audio_ambient" controls src="">
|
||||
</div>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
background-attachment: fixed;
|
||||
background-size: cover;
|
||||
border-radius: 20px;
|
||||
border: 1px solid var(--black50a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
box-shadow: 0 0 7px var(--black50a);
|
||||
margin: 5px;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { getBase64Async } from "../../utils.js";
|
||||
import { getContext, getApiUrl, doExtrasFetch, extension_settings } from "../../extensions.js";
|
||||
import { callPopup, saveSettingsDebounced } from "../../../script.js";
|
||||
import { getBase64Async, saveBase64AsFile } from "../../utils.js";
|
||||
import { getContext, getApiUrl, doExtrasFetch, extension_settings, modules } from "../../extensions.js";
|
||||
import { callPopup, getRequestHeaders, saveSettingsDebounced } from "../../../script.js";
|
||||
import { getMessageTimeStamp } from "../../RossAscends-mods.js";
|
||||
export { MODULE_NAME };
|
||||
|
||||
|
@ -8,7 +8,8 @@ const MODULE_NAME = 'caption';
|
|||
const UPDATE_INTERVAL = 1000;
|
||||
|
||||
async function moduleWorker() {
|
||||
$('#send_picture').toggle(getContext().onlineStatus !== 'no_connection');
|
||||
const hasConnection = getContext().onlineStatus !== 'no_connection';
|
||||
$('#send_picture').toggle(hasConnection);
|
||||
}
|
||||
|
||||
async function setImageIcon() {
|
||||
|
@ -52,7 +53,6 @@ async function sendCaptionedMessage(caption, image) {
|
|||
const message = {
|
||||
name: context.name1,
|
||||
is_user: true,
|
||||
is_name: true,
|
||||
send_date: getMessageTimeStamp(),
|
||||
mes: messageText,
|
||||
extra: {
|
||||
|
@ -65,16 +65,21 @@ async function sendCaptionedMessage(caption, image) {
|
|||
await context.generate('caption');
|
||||
}
|
||||
|
||||
async function onSelectImage(e) {
|
||||
setSpinnerIcon();
|
||||
const file = e.target.files[0];
|
||||
async function doCaptionRequest(base64Img) {
|
||||
if (extension_settings.caption.local) {
|
||||
const apiResult = await fetch('/api/extra/caption', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({ image: base64Img })
|
||||
});
|
||||
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
if (!apiResult.ok) {
|
||||
throw new Error('Failed to caption image via local pipeline.');
|
||||
}
|
||||
|
||||
try {
|
||||
const base64Img = await getBase64Async(file);
|
||||
const data = await apiResult.json();
|
||||
return data;
|
||||
} else if (modules.includes('caption')) {
|
||||
const url = new URL(getApiUrl());
|
||||
url.pathname = '/api/caption';
|
||||
|
||||
|
@ -84,17 +89,42 @@ async function onSelectImage(e) {
|
|||
'Content-Type': 'application/json',
|
||||
'Bypass-Tunnel-Reminder': 'bypass',
|
||||
},
|
||||
body: JSON.stringify({ image: base64Img.split(',')[1] })
|
||||
body: JSON.stringify({ image: base64Img })
|
||||
});
|
||||
|
||||
if (apiResult.ok) {
|
||||
const data = await apiResult.json();
|
||||
const caption = data.caption;
|
||||
const imageToSave = data.thumbnail ? `data:image/jpeg;base64,${data.thumbnail}` : base64Img;
|
||||
await sendCaptionedMessage(caption, imageToSave);
|
||||
if (!apiResult.ok) {
|
||||
throw new Error('Failed to caption image via Extras.');
|
||||
}
|
||||
|
||||
const data = await apiResult.json();
|
||||
return data;
|
||||
} else {
|
||||
throw new Error('No captioning module is available.');
|
||||
}
|
||||
}
|
||||
|
||||
async function onSelectImage(e) {
|
||||
setSpinnerIcon();
|
||||
const file = e.target.files[0];
|
||||
|
||||
if (!file || !(file instanceof File)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const context = getContext();
|
||||
const fileData = await getBase64Async(file);
|
||||
const base64Format = fileData.split(',')[0].split(';')[0].split('/')[1];
|
||||
const base64Data = fileData.split(',')[1];
|
||||
const data = await doCaptionRequest(base64Data);
|
||||
const caption = data.caption;
|
||||
const imageToSave = data.thumbnail ? data.thumbnail : base64Data;
|
||||
const format = data.thumbnail ? 'jpeg' : base64Format;
|
||||
const imagePath = await saveBase64AsFile(imageToSave, context.name2, '', format);
|
||||
await sendCaptionedMessage(caption, imagePath);
|
||||
}
|
||||
catch (error) {
|
||||
toastr.error('Failed to caption image.');
|
||||
console.log(error);
|
||||
}
|
||||
finally {
|
||||
|
@ -113,12 +143,21 @@ jQuery(function () {
|
|||
const sendButton = $(`
|
||||
<div id="send_picture" class="list-group-item flex-container flexGap5">
|
||||
<div class="fa-solid fa-image extensionsMenuExtensionButton"></div>
|
||||
Send a picture
|
||||
Send a Picture
|
||||
</div>`);
|
||||
|
||||
$('#extensionsMenu').prepend(sendButton);
|
||||
$(sendButton).hide();
|
||||
$(sendButton).on('click', () => $('#img_file').trigger('click'));
|
||||
$(sendButton).on('click', () => {
|
||||
const hasCaptionModule = modules.includes('caption') || extension_settings.caption.local;
|
||||
|
||||
if (!hasCaptionModule) {
|
||||
toastr.error('No captioning module is available. Either enable the local captioning pipeline or connect to Extras.');
|
||||
return;
|
||||
}
|
||||
|
||||
$('#img_file').trigger('click');
|
||||
});
|
||||
}
|
||||
function addPictureSendForm() {
|
||||
const inputHtml = `<input id="img_file" type="file" accept="image/*">`;
|
||||
|
@ -131,13 +170,17 @@ jQuery(function () {
|
|||
}
|
||||
function addSettings() {
|
||||
const html = `
|
||||
<div class="background_settings">
|
||||
<div class="caption_settings">
|
||||
<div class="inline-drawer">
|
||||
<div class="inline-drawer-toggle inline-drawer-header">
|
||||
<b>Image Captioning</b>
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
<label class="checkbox_label" for="caption_local">
|
||||
<input id="caption_local" type="checkbox" class="checkbox">
|
||||
Use local captioning pipeline
|
||||
</label>
|
||||
<label class="checkbox_label" for="caption_refine_mode">
|
||||
<input id="caption_refine_mode" type="checkbox" class="checkbox">
|
||||
Edit captions before generation
|
||||
|
@ -155,6 +198,11 @@ jQuery(function () {
|
|||
setImageIcon();
|
||||
moduleWorker();
|
||||
$('#caption_refine_mode').prop('checked', !!(extension_settings.caption.refine_mode));
|
||||
$('#caption_local').prop('checked', !!(extension_settings.caption.local));
|
||||
$('#caption_refine_mode').on('input', onRefineModeInput);
|
||||
$('#caption_local').on('input', () => {
|
||||
extension_settings.caption.local = !!$('#caption_local').prop('checked');
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
setInterval(moduleWorker, UPDATE_INTERVAL);
|
||||
});
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"display_name": "Image Captioning",
|
||||
"loading_order": 4,
|
||||
"requires": [
|
||||
"requires": [],
|
||||
"optional": [
|
||||
"caption"
|
||||
],
|
||||
"optional": [],
|
||||
"js": "index.js",
|
||||
"css": "style.css",
|
||||
"author": "Cohee#1207",
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
#img_form {
|
||||
display: none;
|
||||
}
|
||||
#img_form {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
<h3>
|
||||
Enter a name for the custom expression:
|
||||
</h3>
|
||||
<h4>
|
||||
Requirements:
|
||||
</h4>
|
||||
<ol class="justifyLeft">
|
||||
<li>
|
||||
The name must be unique and not already in use by the default expression.
|
||||
</li>
|
||||
<li>
|
||||
The name must contain only letters, numbers, dashes and underscores. Don't include any file extensions.
|
||||
</li>
|
||||
</ol>
|
|
@ -2,11 +2,13 @@ import { callPopup, eventSource, event_types, getRequestHeaders, saveSettingsDeb
|
|||
import { dragElement, isMobile } from "../../RossAscends-mods.js";
|
||||
import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch, renderExtensionTemplate } from "../../extensions.js";
|
||||
import { loadMovingUIState, power_user } from "../../power-user.js";
|
||||
import { onlyUnique, debounce, getCharaFilename } from "../../utils.js";
|
||||
import { registerSlashCommand } from "../../slash-commands.js";
|
||||
import { onlyUnique, debounce, getCharaFilename, trimToEndSentence, trimToStartSentence } from "../../utils.js";
|
||||
export { MODULE_NAME };
|
||||
|
||||
const MODULE_NAME = 'expressions';
|
||||
const UPDATE_INTERVAL = 2000;
|
||||
const STREAMING_UPDATE_INTERVAL = 6000;
|
||||
const FALLBACK_EXPRESSION = 'joy';
|
||||
const DEFAULT_EXPRESSIONS = [
|
||||
"talkinghead",
|
||||
|
@ -45,6 +47,7 @@ let lastCharacter = undefined;
|
|||
let lastMessage = null;
|
||||
let spriteCache = {};
|
||||
let inApiCall = false;
|
||||
let lastServerResponseTime = 0;
|
||||
|
||||
function isVisualNovelMode() {
|
||||
return Boolean(!isMobile() && power_user.waifuMode && getContext().groupId);
|
||||
|
@ -125,15 +128,7 @@ async function visualNovelSetCharacterSprites(container, name, expression) {
|
|||
continue;
|
||||
}
|
||||
|
||||
let spriteFolderName = character.name;
|
||||
const avatarFileName = getSpriteFolderName({ original_avatar: character.avatar });
|
||||
const expressionOverride = extension_settings.expressionOverrides.find((e) =>
|
||||
e.name == avatarFileName
|
||||
);
|
||||
|
||||
if (expressionOverride && expressionOverride.path) {
|
||||
spriteFolderName = expressionOverride.path;
|
||||
}
|
||||
const spriteFolderName = getSpriteFolderName({ original_avatar: character.avatar }, character.name);
|
||||
|
||||
// download images if not downloaded yet
|
||||
if (spriteCache[spriteFolderName] === undefined) {
|
||||
|
@ -270,16 +265,7 @@ async function setLastMessageSprite(img, avatar, labels) {
|
|||
|
||||
if (lastMessage) {
|
||||
const text = lastMessage.mes || '';
|
||||
let spriteFolderName = lastMessage.name;
|
||||
const avatarFileName = getSpriteFolderName(lastMessage);
|
||||
const expressionOverride = extension_settings.expressionOverrides.find((e) =>
|
||||
e.name == avatarFileName
|
||||
);
|
||||
|
||||
if (expressionOverride && expressionOverride.path) {
|
||||
spriteFolderName = expressionOverride.path;
|
||||
}
|
||||
|
||||
const spriteFolderName = getSpriteFolderName(lastMessage, lastMessage.name);
|
||||
const sprites = spriteCache[spriteFolderName] || [];
|
||||
const label = await getExpressionLabel(text);
|
||||
const path = labels.includes(label) ? sprites.find(x => x.label === label)?.path : '';
|
||||
|
@ -365,7 +351,7 @@ async function setImage(img, path) {
|
|||
expressionClone.removeClass('default');
|
||||
expressionClone.off('error');
|
||||
expressionClone.on('error', function () {
|
||||
console.debug('Expression image error', sprite.path);
|
||||
console.debug('Expression image error', path);
|
||||
$(this).attr('src', '');
|
||||
$(this).off('error');
|
||||
resolve();
|
||||
|
@ -419,17 +405,7 @@ async function loadLiveChar() {
|
|||
return;
|
||||
}
|
||||
|
||||
const context = getContext();
|
||||
let spriteFolderName = context.name2;
|
||||
const message = getLastCharacterMessage();
|
||||
const avatarFileName = getSpriteFolderName(message);
|
||||
const expressionOverride = extension_settings.expressionOverrides.find((e) =>
|
||||
e.name == avatarFileName
|
||||
);
|
||||
|
||||
if (expressionOverride && expressionOverride.path) {
|
||||
spriteFolderName = expressionOverride.path;
|
||||
}
|
||||
const spriteFolderName = getSpriteFolderName();
|
||||
|
||||
const talkingheadPath = `/characters/${encodeURIComponent(spriteFolderName)}/talkinghead.png`;
|
||||
|
||||
|
@ -468,19 +444,19 @@ async function loadLiveChar() {
|
|||
function handleImageChange() {
|
||||
const imgElement = document.querySelector('img#expression-image.expression');
|
||||
|
||||
if (!imgElement) {
|
||||
if (!imgElement || !(imgElement instanceof HTMLImageElement)) {
|
||||
console.log("Cannot find addExpressionImage()");
|
||||
return;
|
||||
}
|
||||
|
||||
if (extension_settings.expressions.talkinghead) {
|
||||
if (extension_settings.expressions.talkinghead && !extension_settings.expressions.local) {
|
||||
// Method get IP of endpoint
|
||||
const talkingheadResultFeedSrc = `${getApiUrl()}/api/talkinghead/result_feed`;
|
||||
$('#expression-holder').css({ display: '' });
|
||||
if (imgElement.src !== talkingheadResultFeedSrc) {
|
||||
const expressionImageElement = document.querySelector('.expression_list_image');
|
||||
|
||||
if (expressionImageElement) {
|
||||
if (expressionImageElement && expressionImageElement instanceof HTMLImageElement) {
|
||||
doExtrasFetch(expressionImageElement.src, {
|
||||
method: 'HEAD',
|
||||
})
|
||||
|
@ -503,6 +479,14 @@ function handleImageChange() {
|
|||
async function moduleWorker() {
|
||||
const context = getContext();
|
||||
|
||||
// Hide and disable talkinghead while in local mode
|
||||
$('#image_type_block').toggle(!extension_settings.expressions.local);
|
||||
|
||||
if (extension_settings.expressions.local && extension_settings.expressions.talkinghead) {
|
||||
$('#image_type_toggle').prop('checked', false);
|
||||
setTalkingHeadState(false);
|
||||
}
|
||||
|
||||
// non-characters not supported
|
||||
if (!context.groupId && (context.characterId === undefined || context.characterId === 'invalid-safety-id')) {
|
||||
removeExpression();
|
||||
|
@ -516,12 +500,14 @@ async function moduleWorker() {
|
|||
|
||||
//clear expression
|
||||
let imgElement = document.getElementById('expression-image');
|
||||
imgElement.src = "";
|
||||
if (imgElement && imgElement instanceof HTMLImageElement) {
|
||||
imgElement.src = "";
|
||||
}
|
||||
|
||||
//set checkbox to global var
|
||||
$('#image_type_toggle').prop('checked', extension_settings.expressions.talkinghead);
|
||||
if (extension_settings.expressions.talkinghead) {
|
||||
settalkingheadState(extension_settings.expressions.talkinghead);
|
||||
setTalkingHeadState(extension_settings.expressions.talkinghead);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -545,15 +531,7 @@ async function moduleWorker() {
|
|||
}
|
||||
|
||||
const currentLastMessage = getLastCharacterMessage();
|
||||
let spriteFolderName = currentLastMessage.name;
|
||||
const avatarFileName = getSpriteFolderName(currentLastMessage);
|
||||
const expressionOverride = extension_settings.expressionOverrides.find((e) =>
|
||||
e.name == avatarFileName
|
||||
);
|
||||
|
||||
if (expressionOverride && expressionOverride.path) {
|
||||
spriteFolderName = expressionOverride.path;
|
||||
}
|
||||
let spriteFolderName = getSpriteFolderName(currentLastMessage, currentLastMessage.name);
|
||||
|
||||
// character has no expressions or it is not loaded
|
||||
if (Object.keys(spriteCache).length === 0) {
|
||||
|
@ -562,8 +540,9 @@ async function moduleWorker() {
|
|||
}
|
||||
|
||||
const offlineMode = $('.expression_settings .offline_mode');
|
||||
if (!modules.includes('classify')) {
|
||||
$('.expression_settings').show();
|
||||
if (!modules.includes('classify') && !extension_settings.expressions.local) {
|
||||
$('#open_chat_expressions').show();
|
||||
$('#no_chat_expressions').hide();
|
||||
offlineMode.css('display', 'block');
|
||||
lastCharacter = context.groupId || context.characterId;
|
||||
|
||||
|
@ -587,6 +566,11 @@ async function moduleWorker() {
|
|||
offlineMode.css('display', 'none');
|
||||
}
|
||||
|
||||
// Don't bother classifying if current char has no sprites and no default expressions are enabled
|
||||
if ((!Array.isArray(spriteCache[spriteFolderName]) || spriteCache[spriteFolderName].length === 0) && !extension_settings.expressions.showDefault) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check if last message changed
|
||||
if ((lastCharacter === context.characterId || lastCharacter === context.groupId)
|
||||
&& lastMessage === currentLastMessage.mes) {
|
||||
|
@ -595,9 +579,21 @@ async function moduleWorker() {
|
|||
|
||||
// API is busy
|
||||
if (inApiCall) {
|
||||
console.debug('Classification API is busy');
|
||||
return;
|
||||
}
|
||||
|
||||
// Throttle classification requests during streaming
|
||||
if (context.streamingProcessor && !context.streamingProcessor.isFinished) {
|
||||
const now = Date.now();
|
||||
const timeSinceLastServerResponse = now - lastServerResponseTime;
|
||||
|
||||
if (timeSinceLastServerResponse < STREAMING_UPDATE_INTERVAL) {
|
||||
console.log('Streaming in progress: throttling expression update. Next update at ' + new Date(lastServerResponseTime + STREAMING_UPDATE_INTERVAL));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
inApiCall = true;
|
||||
let expression = await getExpressionLabel(currentLastMessage.mes);
|
||||
|
@ -615,7 +611,6 @@ async function moduleWorker() {
|
|||
}
|
||||
|
||||
await sendExpressionCall(spriteFolderName, expression, force, vnMode);
|
||||
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error);
|
||||
|
@ -624,21 +619,12 @@ async function moduleWorker() {
|
|||
inApiCall = false;
|
||||
lastCharacter = context.groupId || context.characterId;
|
||||
lastMessage = currentLastMessage.mes;
|
||||
lastServerResponseTime = Date.now();
|
||||
}
|
||||
}
|
||||
|
||||
async function talkingheadcheck() {
|
||||
const context = getContext();
|
||||
let spriteFolderName = context.name2;
|
||||
const message = getLastCharacterMessage();
|
||||
const avatarFileName = getSpriteFolderName(message);
|
||||
const expressionOverride = extension_settings.expressionOverrides.find((e) =>
|
||||
e.name == avatarFileName
|
||||
);
|
||||
|
||||
if (expressionOverride && expressionOverride.path) {
|
||||
spriteFolderName = expressionOverride.path;
|
||||
}
|
||||
async function talkingHeadCheck() {
|
||||
let spriteFolderName = getSpriteFolderName();
|
||||
|
||||
try {
|
||||
await validateImages(spriteFolderName);
|
||||
|
@ -659,11 +645,29 @@ async function talkingheadcheck() {
|
|||
}
|
||||
}
|
||||
|
||||
function settalkingheadState(switch_var) {
|
||||
function getSpriteFolderName(characterMessage = null, characterName = null) {
|
||||
const context = getContext();
|
||||
let spriteFolderName = characterName ?? context.name2;
|
||||
const message = characterMessage ?? getLastCharacterMessage();
|
||||
const avatarFileName = getFolderNameByMessage(message);
|
||||
const expressionOverride = extension_settings.expressionOverrides.find(e => e.name == avatarFileName);
|
||||
|
||||
if (expressionOverride && expressionOverride.path) {
|
||||
spriteFolderName = expressionOverride.path;
|
||||
}
|
||||
|
||||
return spriteFolderName;
|
||||
}
|
||||
|
||||
function setTalkingHeadState(switch_var) {
|
||||
extension_settings.expressions.talkinghead = switch_var; // Store setting
|
||||
saveSettingsDebounced();
|
||||
|
||||
talkingheadcheck().then(result => {
|
||||
if (extension_settings.expressions.local) {
|
||||
return;
|
||||
}
|
||||
|
||||
talkingHeadCheck().then(result => {
|
||||
if (result) {
|
||||
//console.log("talkinghead exists!");
|
||||
|
||||
|
@ -672,7 +676,7 @@ function settalkingheadState(switch_var) {
|
|||
} else {
|
||||
unloadLiveChar();
|
||||
}
|
||||
handleImageChange(switch_var); // Change image as needed
|
||||
handleImageChange(); // Change image as needed
|
||||
|
||||
|
||||
} else {
|
||||
|
@ -681,7 +685,7 @@ function settalkingheadState(switch_var) {
|
|||
});
|
||||
}
|
||||
|
||||
function getSpriteFolderName(message) {
|
||||
function getFolderNameByMessage(message) {
|
||||
const context = getContext();
|
||||
let avatarPath = '';
|
||||
|
||||
|
@ -712,27 +716,122 @@ async function sendExpressionCall(name, expression, force, vnMode) {
|
|||
}
|
||||
}
|
||||
|
||||
async function setSpriteSetCommand(_, folder) {
|
||||
if (!folder) {
|
||||
console.log('Clearing sprite set');
|
||||
folder = '';
|
||||
}
|
||||
|
||||
if (folder.startsWith('/') || folder.startsWith('\\')) {
|
||||
folder = folder.slice(1);
|
||||
|
||||
const currentLastMessage = getLastCharacterMessage();
|
||||
folder = `${currentLastMessage.name}/${folder}`;
|
||||
}
|
||||
|
||||
$("#expression_override").val(folder.trim());
|
||||
onClickExpressionOverrideButton();
|
||||
removeExpression();
|
||||
moduleWorker();
|
||||
}
|
||||
|
||||
async function setSpriteSlashCommand(_, spriteId) {
|
||||
if (!spriteId) {
|
||||
console.log('No sprite id provided');
|
||||
return;
|
||||
}
|
||||
|
||||
spriteId = spriteId.trim().toLowerCase();
|
||||
|
||||
const currentLastMessage = getLastCharacterMessage();
|
||||
const spriteFolderName = getSpriteFolderName(currentLastMessage, currentLastMessage.name);
|
||||
await validateImages(spriteFolderName);
|
||||
|
||||
// Fuzzy search for sprite
|
||||
const fuse = new Fuse(spriteCache[spriteFolderName], { keys: ['label'] });
|
||||
const results = fuse.search(spriteId);
|
||||
const spriteItem = results[0]?.item;
|
||||
|
||||
if (!spriteItem) {
|
||||
console.log('No sprite found for search term ' + spriteId);
|
||||
return;
|
||||
}
|
||||
|
||||
const vnMode = isVisualNovelMode();
|
||||
await sendExpressionCall(spriteFolderName, spriteItem.label, true, vnMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the classification text to reduce the amount of text sent to the API.
|
||||
* Quotes and asterisks are to be removed. If the text is less than 300 characters, it is returned as is.
|
||||
* If the text is more than 300 characters, the first and last 150 characters are returned.
|
||||
* The result is trimmed to the end of sentence.
|
||||
* @param {string} text The text to process.
|
||||
* @returns {string}
|
||||
*/
|
||||
function sampleClassifyText(text) {
|
||||
if (!text) {
|
||||
return text;
|
||||
}
|
||||
|
||||
// Remove asterisks and quotes
|
||||
let result = text.replace(/[\*\"]/g, '');
|
||||
|
||||
const SAMPLE_THRESHOLD = 300;
|
||||
const HALF_SAMPLE_THRESHOLD = SAMPLE_THRESHOLD / 2;
|
||||
|
||||
if (text.length < SAMPLE_THRESHOLD) {
|
||||
result = trimToEndSentence(result);
|
||||
} else {
|
||||
result = trimToEndSentence(result.slice(0, HALF_SAMPLE_THRESHOLD)) + ' ' + trimToStartSentence(result.slice(-HALF_SAMPLE_THRESHOLD));
|
||||
}
|
||||
|
||||
return result.trim();
|
||||
}
|
||||
|
||||
async function getExpressionLabel(text) {
|
||||
// Return if text is undefined, saving a costly fetch request
|
||||
if (!modules.includes('classify') || !text) {
|
||||
if ((!modules.includes('classify') && !extension_settings.expressions.local) || !text) {
|
||||
return FALLBACK_EXPRESSION;
|
||||
}
|
||||
|
||||
const url = new URL(getApiUrl());
|
||||
url.pathname = '/api/classify';
|
||||
text = sampleClassifyText(text);
|
||||
|
||||
const apiResult = await doExtrasFetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Bypass-Tunnel-Reminder': 'bypass',
|
||||
},
|
||||
body: JSON.stringify({ text: text }),
|
||||
});
|
||||
try {
|
||||
if (extension_settings.expressions.local) {
|
||||
// Local transformers pipeline
|
||||
const apiResult = await fetch('/api/extra/classify', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({ text: text }),
|
||||
});
|
||||
|
||||
if (apiResult.ok) {
|
||||
const data = await apiResult.json();
|
||||
return data.classification[0].label;
|
||||
if (apiResult.ok) {
|
||||
const data = await apiResult.json();
|
||||
return data.classification[0].label;
|
||||
}
|
||||
} else {
|
||||
// Extras
|
||||
const url = new URL(getApiUrl());
|
||||
url.pathname = '/api/classify';
|
||||
|
||||
const apiResult = await doExtrasFetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Bypass-Tunnel-Reminder': 'bypass',
|
||||
},
|
||||
body: JSON.stringify({ text: text }),
|
||||
});
|
||||
|
||||
if (apiResult.ok) {
|
||||
const data = await apiResult.json();
|
||||
return data.classification[0].label;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return FALLBACK_EXPRESSION;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -756,7 +855,8 @@ function removeExpression() {
|
|||
$('img.expression').off('error');
|
||||
$('img.expression').prop('src', '');
|
||||
$('img.expression').removeClass('default');
|
||||
$('.expression_settings').hide();
|
||||
$('#open_chat_expressions').hide();
|
||||
$('#no_chat_expressions').show();
|
||||
}
|
||||
|
||||
async function validateImages(character, forceRedrawCached) {
|
||||
|
@ -782,9 +882,11 @@ async function validateImages(character, forceRedrawCached) {
|
|||
|
||||
function drawSpritesList(character, labels, sprites) {
|
||||
let validExpressions = [];
|
||||
$('.expression_settings').show();
|
||||
$('#no_chat_expressions').hide();
|
||||
$('#open_chat_expressions').show();
|
||||
$('#image_list').empty();
|
||||
$('#image_list').data('name', character);
|
||||
$('#image_list_header_name').text(character);
|
||||
|
||||
if (!Array.isArray(labels)) {
|
||||
return [];
|
||||
|
@ -792,27 +894,36 @@ function drawSpritesList(character, labels, sprites) {
|
|||
|
||||
labels.sort().forEach((item) => {
|
||||
const sprite = sprites.find(x => x.label == item);
|
||||
const isCustom = extension_settings.expressions.custom.includes(item);
|
||||
|
||||
if (sprite) {
|
||||
validExpressions.push(sprite);
|
||||
$('#image_list').append(getListItem(item, sprite.path, 'success'));
|
||||
$('#image_list').append(getListItem(item, sprite.path, 'success', isCustom));
|
||||
}
|
||||
else {
|
||||
$('#image_list').append(getListItem(item, '/img/No-Image-Placeholder.svg', 'failure'));
|
||||
$('#image_list').append(getListItem(item, '/img/No-Image-Placeholder.svg', 'failure', isCustom));
|
||||
}
|
||||
});
|
||||
return validExpressions;
|
||||
}
|
||||
|
||||
function getListItem(item, imageSrc, textClass) {
|
||||
return renderExtensionTemplate(MODULE_NAME, 'list-item', { item, imageSrc, textClass });
|
||||
/**
|
||||
* Renders a list item template for the expressions list.
|
||||
* @param {string} item Expression name
|
||||
* @param {string} imageSrc Path to image
|
||||
* @param {'success' | 'failure'} textClass 'success' or 'failure'
|
||||
* @param {boolean} isCustom If expression is added by user
|
||||
* @returns {string} Rendered list item template
|
||||
*/
|
||||
function getListItem(item, imageSrc, textClass, isCustom) {
|
||||
return renderExtensionTemplate(MODULE_NAME, 'list-item', { item, imageSrc, textClass, isCustom });
|
||||
}
|
||||
|
||||
async function getSpritesList(name) {
|
||||
console.debug('getting sprites list');
|
||||
|
||||
try {
|
||||
const result = await fetch(`/get_sprites?name=${encodeURIComponent(name)}`);
|
||||
const result = await fetch(`/api/sprites/get?name=${encodeURIComponent(name)}`);
|
||||
let sprites = result.ok ? (await result.json()) : [];
|
||||
return sprites;
|
||||
}
|
||||
|
@ -822,40 +933,84 @@ async function getSpritesList(name) {
|
|||
}
|
||||
}
|
||||
|
||||
async function getExpressionsList() {
|
||||
// get something for offline mode (default images)
|
||||
if (!modules.includes('classify')) {
|
||||
return DEFAULT_EXPRESSIONS;
|
||||
function renderCustomExpressions() {
|
||||
if (!Array.isArray(extension_settings.expressions.custom)) {
|
||||
extension_settings.expressions.custom = [];
|
||||
}
|
||||
|
||||
const customExpressions = extension_settings.expressions.custom.sort((a, b) => a.localeCompare(b));
|
||||
$('#expression_custom').empty();
|
||||
|
||||
for (const expression of customExpressions) {
|
||||
const option = document.createElement('option');
|
||||
option.value = expression;
|
||||
option.text = expression;
|
||||
$('#expression_custom').append(option);
|
||||
}
|
||||
|
||||
if (customExpressions.length === 0) {
|
||||
$('#expression_custom').append('<option value="" disabled selected>[ No custom expressions ]</option>');
|
||||
}
|
||||
}
|
||||
|
||||
async function getExpressionsList() {
|
||||
// Return cached list if available
|
||||
if (Array.isArray(expressionsList)) {
|
||||
return expressionsList;
|
||||
}
|
||||
|
||||
const url = new URL(getApiUrl());
|
||||
url.pathname = '/api/classify/labels';
|
||||
/**
|
||||
* Returns the list of expressions from the API or fallback in offline mode.
|
||||
* @returns {Promise<string[]>}
|
||||
*/
|
||||
async function resolveExpressionsList() {
|
||||
// get something for offline mode (default images)
|
||||
if (!modules.includes('classify') && !extension_settings.expressions.local) {
|
||||
return DEFAULT_EXPRESSIONS;
|
||||
}
|
||||
|
||||
try {
|
||||
const apiResult = await doExtrasFetch(url, {
|
||||
method: 'GET',
|
||||
headers: { 'Bypass-Tunnel-Reminder': 'bypass' },
|
||||
});
|
||||
try {
|
||||
if (extension_settings.expressions.local) {
|
||||
const apiResult = await fetch('/api/extra/classify/labels', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
});
|
||||
|
||||
if (apiResult.ok) {
|
||||
if (apiResult.ok) {
|
||||
const data = await apiResult.json();
|
||||
expressionsList = data.labels;
|
||||
return expressionsList;
|
||||
}
|
||||
} else {
|
||||
const url = new URL(getApiUrl());
|
||||
url.pathname = '/api/classify/labels';
|
||||
|
||||
const data = await apiResult.json();
|
||||
expressionsList = data.labels;
|
||||
return expressionsList;
|
||||
const apiResult = await doExtrasFetch(url, {
|
||||
method: 'GET',
|
||||
headers: { 'Bypass-Tunnel-Reminder': 'bypass' },
|
||||
});
|
||||
|
||||
if (apiResult.ok) {
|
||||
|
||||
const data = await apiResult.json();
|
||||
expressionsList = data.labels;
|
||||
return expressionsList;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error);
|
||||
return [];
|
||||
}
|
||||
|
||||
const result = await resolveExpressionsList();
|
||||
result.push(...extension_settings.expressions.custom);
|
||||
return result;
|
||||
}
|
||||
|
||||
async function setExpression(character, expression, force) {
|
||||
if (!extension_settings.expressions.talkinghead) {
|
||||
if (extension_settings.expressions.local || !extension_settings.expressions.talkinghead) {
|
||||
console.debug('entered setExpressions');
|
||||
await validateImages(character);
|
||||
const img = $('img.expression');
|
||||
|
@ -968,12 +1123,12 @@ async function setExpression(character, expression, force) {
|
|||
} else {
|
||||
|
||||
|
||||
talkingheadcheck().then(result => {
|
||||
talkingHeadCheck().then(result => {
|
||||
if (result) {
|
||||
// Find the <img> element with id="expression-image" and class="expression"
|
||||
const imgElement = document.querySelector('img#expression-image.expression');
|
||||
//console.log("searching");
|
||||
if (imgElement) {
|
||||
if (imgElement && imgElement instanceof HTMLImageElement) {
|
||||
//console.log("setting value");
|
||||
imgElement.src = getApiUrl() + '/api/talkinghead/result_feed';
|
||||
}
|
||||
|
@ -988,18 +1143,76 @@ async function setExpression(character, expression, force) {
|
|||
}
|
||||
|
||||
function onClickExpressionImage() {
|
||||
// online mode doesn't need force set
|
||||
if (modules.includes('classify')) {
|
||||
const expression = $(this).attr('id');
|
||||
setSpriteSlashCommand({}, expression);
|
||||
}
|
||||
|
||||
async function onClickExpressionAddCustom() {
|
||||
let expressionName = await callPopup(renderExtensionTemplate(MODULE_NAME, 'add-custom-expression'), 'input');
|
||||
|
||||
if (!expressionName) {
|
||||
console.debug('No custom expression name provided');
|
||||
return;
|
||||
}
|
||||
|
||||
const expression = $(this).attr('id');
|
||||
const name = getLastCharacterMessage().name;
|
||||
expressionName = expressionName.trim().toLowerCase();
|
||||
|
||||
if ($(this).find('.failure').length === 0) {
|
||||
setExpression(name, expression, true);
|
||||
// a-z, 0-9, dashes and underscores only
|
||||
if (!/^[a-z0-9-_]+$/.test(expressionName)) {
|
||||
toastr.info('Invalid custom expression name provided');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if expression name already exists in default expressions
|
||||
if (DEFAULT_EXPRESSIONS.includes(expressionName)) {
|
||||
toastr.info('Expression name already exists');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if expression name already exists in custom expressions
|
||||
if (extension_settings.expressions.custom.includes(expressionName)) {
|
||||
toastr.info('Custom expression already exists');
|
||||
return;
|
||||
}
|
||||
|
||||
// Add custom expression into settings
|
||||
extension_settings.expressions.custom.push(expressionName);
|
||||
renderCustomExpressions();
|
||||
saveSettingsDebounced();
|
||||
|
||||
// Force refresh sprites list
|
||||
expressionsList = null;
|
||||
spriteCache = {};
|
||||
moduleWorker();
|
||||
}
|
||||
|
||||
async function onClickExpressionRemoveCustom() {
|
||||
const selectedExpression = $('#expression_custom').val();
|
||||
|
||||
if (!selectedExpression) {
|
||||
console.debug('No custom expression selected');
|
||||
return;
|
||||
}
|
||||
|
||||
const confirmation = await callPopup(renderExtensionTemplate(MODULE_NAME, 'remove-custom-expression', { expression: selectedExpression }), 'confirm');
|
||||
|
||||
if (!confirmation) {
|
||||
console.debug('Custom expression removal cancelled');
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove custom expression from settings
|
||||
const index = extension_settings.expressions.custom.indexOf(selectedExpression);
|
||||
extension_settings.expressions.custom.splice(index, 1);
|
||||
renderCustomExpressions();
|
||||
saveSettingsDebounced();
|
||||
|
||||
// Force refresh sprites list
|
||||
expressionsList = null;
|
||||
spriteCache = {};
|
||||
moduleWorker();
|
||||
}
|
||||
|
||||
async function handleFileUpload(url, formData) {
|
||||
try {
|
||||
const data = await jQuery.ajax({
|
||||
|
@ -1042,7 +1255,7 @@ async function onClickExpressionUpload(event) {
|
|||
formData.append('label', id);
|
||||
formData.append('avatar', file);
|
||||
|
||||
await handleFileUpload('/upload_sprite', formData);
|
||||
await handleFileUpload('/api/sprites/upload', formData);
|
||||
|
||||
// Reset the input
|
||||
e.target.form.reset();
|
||||
|
@ -1057,7 +1270,7 @@ async function onClickExpressionUpload(event) {
|
|||
async function onClickExpressionOverrideButton() {
|
||||
const context = getContext();
|
||||
const currentLastMessage = getLastCharacterMessage();
|
||||
const avatarFileName = getSpriteFolderName(currentLastMessage);
|
||||
const avatarFileName = getFolderNameByMessage(currentLastMessage);
|
||||
|
||||
// If the avatar name couldn't be found, abort.
|
||||
if (!avatarFileName) {
|
||||
|
@ -1066,7 +1279,7 @@ async function onClickExpressionOverrideButton() {
|
|||
return;
|
||||
}
|
||||
|
||||
const overridePath = $("#expression_override").val();
|
||||
const overridePath = String($("#expression_override").val());
|
||||
const existingOverrideIndex = extension_settings.expressionOverrides.findIndex((e) =>
|
||||
e.name == avatarFileName
|
||||
);
|
||||
|
@ -1148,7 +1361,7 @@ async function onClickExpressionUploadPackButton() {
|
|||
formData.append('name', name);
|
||||
formData.append('avatar', file);
|
||||
|
||||
const { count } = await handleFileUpload('/upload_sprite_pack', formData);
|
||||
const { count } = await handleFileUpload('/api/sprites/upload-zip', formData);
|
||||
toastr.success(`Uploaded ${count} image(s) for ${name}`);
|
||||
|
||||
// Reset the input
|
||||
|
@ -1175,7 +1388,7 @@ async function onClickExpressionDelete(event) {
|
|||
const name = $('#image_list').data('name');
|
||||
|
||||
try {
|
||||
await fetch('/delete_sprite', {
|
||||
await fetch('/api/sprites/delete', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({ name, label: id }),
|
||||
|
@ -1191,7 +1404,7 @@ async function onClickExpressionDelete(event) {
|
|||
|
||||
function setExpressionOverrideHtml(forceClear = false) {
|
||||
const currentLastMessage = getLastCharacterMessage();
|
||||
const avatarFileName = getSpriteFolderName(currentLastMessage);
|
||||
const avatarFileName = getFolderNameByMessage(currentLastMessage);
|
||||
if (!avatarFileName) {
|
||||
return;
|
||||
}
|
||||
|
@ -1237,6 +1450,11 @@ function setExpressionOverrideHtml(forceClear = false) {
|
|||
$('#expressions_show_default').on('input', onExpressionsShowDefaultInput);
|
||||
$('#expression_upload_pack_button').on('click', onClickExpressionUploadPackButton);
|
||||
$('#expressions_show_default').prop('checked', extension_settings.expressions.showDefault).trigger('input');
|
||||
$('#expression_local').prop('checked', extension_settings.expressions.local).on('input', function () {
|
||||
extension_settings.expressions.local = !!$(this).prop('checked');
|
||||
moduleWorker();
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
$('#expression_override_cleanup_button').on('click', onClickExpressionOverrideRemoveAllButton);
|
||||
$(document).on('dragstart', '.expression', (e) => {
|
||||
e.preventDefault()
|
||||
|
@ -1246,11 +1464,18 @@ function setExpressionOverrideHtml(forceClear = false) {
|
|||
$(document).on('click', '.expression_list_upload', onClickExpressionUpload);
|
||||
$(document).on('click', '.expression_list_delete', onClickExpressionDelete);
|
||||
$(window).on("resize", updateVisualNovelModeDebounced);
|
||||
$('.expression_settings').hide();
|
||||
$("#open_chat_expressions").hide();
|
||||
|
||||
$('#image_type_toggle').on('click', function () {
|
||||
settalkingheadState(this.checked);
|
||||
if (this instanceof HTMLInputElement) {
|
||||
setTalkingHeadState(this.checked);
|
||||
}
|
||||
});
|
||||
|
||||
renderCustomExpressions();
|
||||
|
||||
$('#expression_custom_add').on('click', onClickExpressionAddCustom);
|
||||
$('#expression_custom_remove').on('click', onClickExpressionRemoveCustom);
|
||||
}
|
||||
|
||||
addExpressionImage();
|
||||
|
@ -1270,4 +1495,6 @@ function setExpressionOverrideHtml(forceClear = false) {
|
|||
});
|
||||
eventSource.on(event_types.MOVABLE_PANELS_RESET, updateVisualNovelModeDebounced);
|
||||
eventSource.on(event_types.GROUP_UPDATED, updateVisualNovelModeDebounced);
|
||||
registerSlashCommand('sprite', setSpriteSlashCommand, ['emote'], '<span class="monospace">spriteId</span> – force sets the sprite for the current character', true, true);
|
||||
registerSlashCommand('spriteoverride', setSpriteSetCommand, ['costume'], '<span class="monospace">folder</span> – sets an override sprite folder for the current character. If the name starts with a slash or a backslash, selects a sub-folder in the character-named folder. Empty value to reset to default.', true, true);
|
||||
})();
|
||||
|
|
|
@ -7,6 +7,11 @@
|
|||
<i class="fa-solid fa-trash"></i>
|
||||
</div>
|
||||
</div>
|
||||
<span class="expression_list_title {{textClass}}">{{item}}</span>
|
||||
<div class="expression_list_title {{textClass}}">
|
||||
<span>{{item}}</span>
|
||||
{{#if isCustom}}
|
||||
<small class="expression_list_custom">(custom)</small>
|
||||
{{/if}}
|
||||
</div>
|
||||
<img class="expression_list_image" src="{{imageSrc}}" />
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
<h3>
|
||||
Are you sure you want to remove the expression <tt>"{{expression}}"</tt>?
|
||||
</h3>
|
||||
<div>
|
||||
Uploaded images will not be deleted, but will no longer be used by the extension.
|
||||
</div>
|
||||
<br>
|
|
@ -6,35 +6,57 @@
|
|||
</div>
|
||||
|
||||
<div class="inline-drawer-content">
|
||||
<!-- Toggle button for aituber/static images -->
|
||||
<div class="toggle_button">
|
||||
<label class="switch">
|
||||
<input id="image_type_toggle" type="checkbox">
|
||||
<span class="slider round"></span>
|
||||
<label for="image_type_toggle">Image Type - talkinghead (extras)</label>
|
||||
</label>
|
||||
</div>
|
||||
<div class="offline_mode">
|
||||
<small>You are in offline mode. Click on the image below to set the expression.</small>
|
||||
</div>
|
||||
<div class="flex-container flexnowrap">
|
||||
<input id="expression_override" type="text" class="text_pole" placeholder="Override folder name" />
|
||||
<input id="expression_override_button" class="menu_button" type="submit" value="Submit" />
|
||||
</div>
|
||||
<div id="image_list"></div>
|
||||
<div class="expression_buttons flex-container spaceEvenly">
|
||||
<div id="expression_upload_pack_button" class="menu_button">
|
||||
<i class="fa-solid fa-file-zipper"></i>
|
||||
<span>Upload sprite pack (ZIP)</span>
|
||||
</div>
|
||||
<div id="expression_override_cleanup_button" class="menu_button">
|
||||
<i class="fa-solid fa-trash-can"></i>
|
||||
<span>Remove all image overrides</span>
|
||||
<label class="checkbox_label" for="expression_local" title="Use classification model without the Extras server.">
|
||||
<input id="expression_local" type="checkbox" />
|
||||
<span data-i18n="Local server classification">Local server classification</span>
|
||||
</label>
|
||||
<label class="checkbox_label" for="expressions_show_default">
|
||||
<input id="expressions_show_default" type="checkbox">
|
||||
<span>Show default images (emojis) if sprite missing</span>
|
||||
</label>
|
||||
<label id="image_type_block" class="checkbox_label" for="image_type_toggle">
|
||||
<input id="image_type_toggle" type="checkbox">
|
||||
<span>Image Type - talkinghead (extras)</span>
|
||||
</label>
|
||||
<div class="expression_custom_block m-b-1 m-t-1">
|
||||
<label for="expression_custom">Custom Expressions</label>
|
||||
<small>Can be set manually or with an <tt>/emote</tt> slash command.</small>
|
||||
<div class="flex-container">
|
||||
<select id="expression_custom" class="flex1 margin0"><select>
|
||||
<i id="expression_custom_add" class="menu_button fa-solid fa-plus margin0" title="Add"></i>
|
||||
<i id="expression_custom_remove" class="menu_button fa-solid fa-xmark margin0" title="Remove"></i>
|
||||
</div>
|
||||
</div>
|
||||
<p class="hint"><b>Hint:</b> <i>Create new folder in the <b>public/characters/</b> folder and name it as the name of the character.
|
||||
Put images with expressions there. File names should follow the pattern: <tt>[expression_label].[image_format]</tt></i></p>
|
||||
<label for="expressions_show_default"><input id="expressions_show_default" type="checkbox">Show default images (emojis) if missing</label>
|
||||
<div id="no_chat_expressions">
|
||||
Open a chat to see the character expressions.
|
||||
</div>
|
||||
<div id="open_chat_expressions">
|
||||
<div class="offline_mode">
|
||||
<small>You are in offline mode. Click on the image below to set the expression.</small>
|
||||
</div>
|
||||
<label for="expression_override">Sprite Folder Override</label>
|
||||
<small>Use a forward slash to specify a subfolder. Example: <tt>Bob/formal</tt></small>
|
||||
<div class="flex-container flexnowrap">
|
||||
<input id="expression_override" type="text" class="text_pole" placeholder="Override folder name" />
|
||||
<input id="expression_override_button" class="menu_button" type="submit" value="Submit" />
|
||||
</div>
|
||||
<h3 id="image_list_header">
|
||||
<strong>Sprite set:</strong> <span id="image_list_header_name"></span>
|
||||
</h3>
|
||||
<div id="image_list"></div>
|
||||
<div class="expression_buttons flex-container spaceEvenly">
|
||||
<div id="expression_upload_pack_button" class="menu_button">
|
||||
<i class="fa-solid fa-file-zipper"></i>
|
||||
<span>Upload sprite pack (ZIP)</span>
|
||||
</div>
|
||||
<div id="expression_override_cleanup_button" class="menu_button">
|
||||
<i class="fa-solid fa-trash-can"></i>
|
||||
<span>Remove all image overrides</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="hint"><b>Hint:</b> <i>Create new folder in the <b>public/characters/</b> folder and name it as the name of the character.
|
||||
Put images with expressions there. File names should follow the pattern: <tt>[expression_label].[image_format]</tt></i></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<form>
|
||||
|
|
|
@ -49,6 +49,18 @@
|
|||
overflow: hidden;
|
||||
resize: both;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#no_chat_expressions {
|
||||
text-align: center;
|
||||
margin: 10px 0;
|
||||
font-weight: bold;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
#image_list_header_name {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
img.expression {
|
||||
|
@ -112,6 +124,8 @@ img.expression.default {
|
|||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.expression_list_buttons {
|
||||
|
@ -180,4 +194,4 @@ img.expression.default {
|
|||
div.expression {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,8 @@ import {
|
|||
import { selected_group } from "../../group-chats.js";
|
||||
import { loadFileToDocument } from "../../utils.js";
|
||||
import { loadMovingUIState } from '../../power-user.js';
|
||||
import { dragElement } from '../../RossAscends-mods.js'
|
||||
import { dragElement } from '../../RossAscends-mods.js';
|
||||
import { registerSlashCommand } from "../../slash-commands.js";
|
||||
|
||||
const extensionName = "gallery";
|
||||
const extensionFolderPath = `scripts/extensions/${extensionName}/`;
|
||||
|
@ -367,6 +368,21 @@ function makeDragImg(id, url) {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes a given ID to ensure it can be used as an HTML ID.
|
||||
* This function replaces spaces and non-word characters with dashes.
|
||||
* It also removes any non-ASCII characters.
|
||||
* @param {string} id - The ID to be sanitized.
|
||||
* @returns {string} - The sanitized ID.
|
||||
*/
|
||||
function sanitizeHTMLId(id){
|
||||
// Replace spaces and non-word characters
|
||||
id = id.replace(/\s+/g, '-')
|
||||
.replace(/[^\x00-\x7F]/g, '-')
|
||||
.replace(/\W/g, '');
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a list of items (containing URLs) and creates a draggable box for the first item.
|
||||
|
@ -379,10 +395,17 @@ function makeDragImg(id, url) {
|
|||
*/
|
||||
function viewWithDragbox(items) {
|
||||
if (items && items.length > 0) {
|
||||
var url = items[0].responsiveURL(); // Get the URL of the clicked image/video
|
||||
const url = items[0].responsiveURL(); // Get the URL of the clicked image/video
|
||||
// ID should just be the last part of the URL, removing the extension
|
||||
var id = url.substring(url.lastIndexOf('/') + 1, url.lastIndexOf('.'));
|
||||
const id = sanitizeHTMLId(url.substring(url.lastIndexOf('/') + 1, url.lastIndexOf('.')));
|
||||
makeDragImg(id, url);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Registers a simple command for opening the char gallery.
|
||||
registerSlashCommand("show-gallery", showGalleryCommand, ["sg"], "Shows the gallery", true, true);
|
||||
|
||||
function showGalleryCommand(args) {
|
||||
showCharGallery();
|
||||
}
|
||||
|
|
|
@ -62,11 +62,11 @@ const generateDebounced = debounce(() => generateHypeBot(), 500);
|
|||
* @param {string} text Text to set
|
||||
*/
|
||||
function setHypeBotText(text) {
|
||||
const blockA = $('#chat');
|
||||
var originalScrollBottom = blockA[0].scrollHeight - (blockA.scrollTop() + blockA.outerHeight());
|
||||
const chatBlock = $('#chat');
|
||||
const originalScrollBottom = chatBlock[0].scrollHeight - (chatBlock.scrollTop() + chatBlock.outerHeight());
|
||||
hypeBotBar.html(DOMPurify.sanitize(text));
|
||||
var newScrollTop = blockA[0].scrollHeight - (blockA.outerHeight() + originalScrollBottom);
|
||||
blockA.scrollTop(newScrollTop);
|
||||
const newScrollTop = chatBlock[0].scrollHeight - (chatBlock.outerHeight() + originalScrollBottom);
|
||||
chatBlock.scrollTop(newScrollTop);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -159,7 +159,7 @@ async function generateHypeBot() {
|
|||
|
||||
abortController = new AbortController();
|
||||
|
||||
const response = await fetch('/generate_novelai', {
|
||||
const response = await fetch('/api/novelai/generate', {
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify(parameters),
|
||||
method: 'POST',
|
||||
|
@ -191,11 +191,13 @@ jQuery(() => {
|
|||
settings.enabled = $('#hypebot_enabled').prop('checked');
|
||||
hypeBotBar.toggle(settings.enabled);
|
||||
abortController?.abort();
|
||||
Object.assign(extension_settings.hypebot, settings);
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#hypebot_name').val(settings.name).on('input', () => {
|
||||
settings.name = String($('#hypebot_name').val());
|
||||
Object.assign(extension_settings.hypebot, settings);
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
<div class="idle-settings">
|
||||
<div class="inline-drawer">
|
||||
<div class="inline-drawer-toggle inline-drawer-header" title="Indicates the settings for the idle feature.">
|
||||
<b>Idle</b>
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
|
||||
<div class="inline-drawer-content">
|
||||
<div class="idle_block flex-container">
|
||||
<input id="idle_enabled" type="checkbox" title="Toggle to enable or disable the idle feature." />
|
||||
<label for="idle_enabled">Enabled</label>
|
||||
</div>
|
||||
<div class="idle_block flex-container">
|
||||
<input id="idle_repeats" class="text_pole widthUnset" type="number" min="0" max="100000" step="1" title="The number of times the idle action will be prompted." />
|
||||
<label for="idle_repeats">Idle Prompt Count</label>
|
||||
</div>
|
||||
<div class="idle_block flex-container" style="display: none;">
|
||||
<input id="idle_timer_min" class="text_pole widthUnset" type="number" min="0" max="600000" step="1" title="The minimum amount of time in seconds before the idle action is triggered." />
|
||||
<label for="idle_timer_min">Idle Timer Minimum (seconds)</label>
|
||||
</div>
|
||||
<div class="idle_block flex-container">
|
||||
<input id="idle_timer" class="text_pole widthUnset" type="number" min="0" max="600000" step="1" title="The amount of time in seconds before the idle action is triggered." />
|
||||
<label for="idle_timer">Idle Timer (seconds)</label>
|
||||
</div>
|
||||
<div class="idle_block flex-container">
|
||||
<label for="idle_prompts">Idle Prompts</label>
|
||||
<textarea id="idle_prompts" class="text_pole textarea_compact" rows="6" title="The prompts to be sent to initial the idle reply (newline seperated)."></textarea>
|
||||
</div>
|
||||
<div class="idle_block flex-container">
|
||||
<input id="idle_use_continuation" type="checkbox" title="Indicates whether the idle action will just use the 'Continue' function instead of a prompt." />
|
||||
<label for="idle_use_continuation">Use Continuation</label>
|
||||
</div>
|
||||
<div class="idle_block flex-container">
|
||||
<input id="idle_random_time" type="checkbox" title="Indicates if the idle time should be randomized between a min/max value." />
|
||||
<label for="idle_random_time">Randomize Time</label>
|
||||
</div>
|
||||
<div class="idle_block flex-container">
|
||||
<input id="idle_include_prompt" type="checkbox" title="Indicates if the idle prompting should be included in context. (Sends as user)" />
|
||||
<label for="idle_include_prompt">Include Idle Prompt</label>
|
||||
</div>
|
||||
<div class="idle_block flex-container">
|
||||
<label for="idle_sendAs">Send As</label>
|
||||
<select id="idle_sendAs" class="text_pole" title="Determines how the idle message prompting is sent; as a user, character, system, or raw message.">
|
||||
<option value="user">User</option>
|
||||
<option value="char">Character</option>
|
||||
<option value="sys">System</option>
|
||||
<option value="raw">Raw</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<hr class="sysHR" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,329 @@
|
|||
import {
|
||||
saveSettingsDebounced,
|
||||
substituteParams
|
||||
} from "../../../script.js";
|
||||
import { debounce } from "../../utils.js";
|
||||
import { promptQuietForLoudResponse, sendMessageAs, sendNarratorMessage } from "../../slash-commands.js";
|
||||
import { extension_settings, getContext, renderExtensionTemplate } from "../../extensions.js";
|
||||
import { registerSlashCommand } from "../../slash-commands.js";
|
||||
const extensionName = "idle";
|
||||
|
||||
let idleTimer = null;
|
||||
let repeatCount = 0;
|
||||
|
||||
let defaultSettings = {
|
||||
enabled: false,
|
||||
timer: 120,
|
||||
prompts: [
|
||||
"*stands silently, looking deep in thought*",
|
||||
"*pauses, eyes wandering over the surroundings*",
|
||||
"*hesitates, appearing lost for a moment*",
|
||||
"*takes a deep breath, collecting their thoughts*",
|
||||
"*gazes into the distance, seemingly distracted*",
|
||||
"*remains still, absorbing the ambiance*",
|
||||
"*lingers in silence, a contemplative look on their face*",
|
||||
"*stops, fingers brushing against an old memory*",
|
||||
"*seems to drift into a momentary daydream*",
|
||||
"*waits quietly, allowing the weight of the moment to settle*",
|
||||
],
|
||||
useContinuation: true,
|
||||
repeats: 2, // 0 = infinite
|
||||
sendAs: "user",
|
||||
randomTime: false,
|
||||
timeMin: 60,
|
||||
includePrompt: false,
|
||||
};
|
||||
|
||||
|
||||
//TODO: Can we make this a generic function?
|
||||
/**
|
||||
* Load the extension settings and set defaults if they don't exist.
|
||||
*/
|
||||
async function loadSettings() {
|
||||
if (!extension_settings.idle) {
|
||||
console.log("Creating extension_settings.idle");
|
||||
extension_settings.idle = {};
|
||||
}
|
||||
for (const [key, value] of Object.entries(defaultSettings)) {
|
||||
if (!extension_settings.idle.hasOwnProperty(key)) {
|
||||
console.log(`Setting default for: ${key}`);
|
||||
extension_settings.idle[key] = value;
|
||||
}
|
||||
}
|
||||
populateUIWithSettings();
|
||||
}
|
||||
|
||||
//TODO: Can we make this a generic function too?
|
||||
/**
|
||||
* Populate the UI components with values from the extension settings.
|
||||
*/
|
||||
function populateUIWithSettings() {
|
||||
$("#idle_timer").val(extension_settings.idle.timer).trigger("input");
|
||||
$("#idle_prompts").val(extension_settings.idle.prompts.join("\n")).trigger("input");
|
||||
$("#idle_use_continuation").prop("checked", extension_settings.idle.useContinuation).trigger("input");
|
||||
$("#idle_enabled").prop("checked", extension_settings.idle.enabled).trigger("input");
|
||||
$("#idle_repeats").val(extension_settings.idle.repeats).trigger("input");
|
||||
$("#idle_sendAs").val(extension_settings.idle.sendAs).trigger("input");
|
||||
$("#idle_random_time").prop("checked", extension_settings.idle.randomTime).trigger("input");
|
||||
$("#idle_timer_min").val(extension_settings.idle.timerMin).trigger("input");
|
||||
$("#idle_include_prompt").prop("checked", extension_settings.idle.includePrompt).trigger("input");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reset the idle timer based on the extension settings and context.
|
||||
*/
|
||||
function resetIdleTimer() {
|
||||
console.debug("Resetting idle timer");
|
||||
if (idleTimer) clearTimeout(idleTimer);
|
||||
let context = getContext();
|
||||
if (!context.characterId && !context.groupID) return;
|
||||
if (!extension_settings.idle.enabled) return;
|
||||
if (extension_settings.idle.randomTime) {
|
||||
// ensure these are ints
|
||||
let min = extension_settings.idle.timerMin;
|
||||
let max = extension_settings.idle.timer;
|
||||
min = parseInt(min);
|
||||
max = parseInt(max);
|
||||
let randomTime = (Math.random() * (max - min + 1)) + min;
|
||||
idleTimer = setTimeout(sendIdlePrompt, 1000 * randomTime);
|
||||
} else {
|
||||
idleTimer = setTimeout(sendIdlePrompt, 1000 * extension_settings.idle.timer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a random idle prompt to the AI based on the extension settings.
|
||||
* Checks conditions like if the extension is enabled and repeat conditions.
|
||||
*/
|
||||
async function sendIdlePrompt() {
|
||||
if (!extension_settings.idle.enabled) return;
|
||||
|
||||
// Check repeat conditions and waiting for a response
|
||||
if (repeatCount >= extension_settings.idle.repeats || $('#mes_stop').is(':visible')) {
|
||||
//console.debug("Not sending idle prompt due to repeat conditions or waiting for a response.");
|
||||
resetIdleTimer();
|
||||
return;
|
||||
}
|
||||
|
||||
const randomPrompt = extension_settings.idle.prompts[
|
||||
Math.floor(Math.random() * extension_settings.idle.prompts.length)
|
||||
];
|
||||
|
||||
sendPrompt(randomPrompt);
|
||||
repeatCount++;
|
||||
resetIdleTimer();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add our prompt to the chat and then send the chat to the backend.
|
||||
* @param {string} sendAs - The type of message to send. "user", "char", or "sys".
|
||||
* @param {string} prompt - The prompt text to send to the AI.
|
||||
*/
|
||||
function sendLoud(sendAs, prompt) {
|
||||
if (sendAs === "user") {
|
||||
prompt = substituteParams(prompt);
|
||||
|
||||
$("#send_textarea").val(prompt);
|
||||
|
||||
// Set the focus back to the textarea
|
||||
$("#send_textarea").focus();
|
||||
|
||||
$("#send_but").trigger('click');
|
||||
} else if (sendAs === "char") {
|
||||
sendMessageAs("", `${getContext().name2}\n${prompt}`);
|
||||
promptQuietForLoudResponse(sendAs, "");
|
||||
} else if (sendAs === "sys") {
|
||||
sendNarratorMessage("", prompt);
|
||||
promptQuietForLoudResponse(sendAs, "");
|
||||
}
|
||||
else {
|
||||
console.error(`Unknown sendAs value: ${sendAs}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the provided prompt to the AI. Determines method based on continuation setting.
|
||||
* @param {string} prompt - The prompt text to send to the AI.
|
||||
*/
|
||||
function sendPrompt(prompt) {
|
||||
clearTimeout(idleTimer);
|
||||
$("#send_textarea").off("input");
|
||||
|
||||
if (extension_settings.idle.useContinuation) {
|
||||
$('#option_continue').trigger('click');
|
||||
console.debug("Sending idle prompt with continuation");
|
||||
} else {
|
||||
console.debug("Sending idle prompt");
|
||||
console.log(extension_settings.idle);
|
||||
if (extension_settings.idle.includePrompt) {
|
||||
sendLoud(extension_settings.idle.sendAs, prompt);
|
||||
}
|
||||
else {
|
||||
promptQuietForLoudResponse(extension_settings.idle.sendAs, prompt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the settings HTML and append to the designated area.
|
||||
*/
|
||||
async function loadSettingsHTML() {
|
||||
const settingsHtml = renderExtensionTemplate(extensionName, "dropdown");
|
||||
$("#extensions_settings2").append(settingsHtml);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a specific setting based on user input.
|
||||
* @param {string} elementId - The HTML element ID tied to the setting.
|
||||
* @param {string} property - The property name in the settings object.
|
||||
* @param {boolean} [isCheckbox=false] - Whether the setting is a checkbox.
|
||||
*/
|
||||
function updateSetting(elementId, property, isCheckbox = false) {
|
||||
let value = $(`#${elementId}`).val();
|
||||
if (isCheckbox) {
|
||||
value = $(`#${elementId}`).prop('checked');
|
||||
}
|
||||
|
||||
if (property === "prompts") {
|
||||
value = value.split("\n");
|
||||
}
|
||||
|
||||
extension_settings.idle[property] = value;
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach an input listener to a UI component to update the corresponding setting.
|
||||
* @param {string} elementId - The HTML element ID tied to the setting.
|
||||
* @param {string} property - The property name in the settings object.
|
||||
* @param {boolean} [isCheckbox=false] - Whether the setting is a checkbox.
|
||||
*/
|
||||
function attachUpdateListener(elementId, property, isCheckbox = false) {
|
||||
$(`#${elementId}`).on('input', debounce(() => {
|
||||
updateSetting(elementId, property, isCheckbox);
|
||||
}, 250));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the enabling or disabling of the idle extension.
|
||||
* Adds or removes the idle listeners based on the checkbox's state.
|
||||
*/
|
||||
function handleIdleEnabled() {
|
||||
if (!extension_settings.idle.enabled) {
|
||||
clearTimeout(idleTimer);
|
||||
removeIdleListeners();
|
||||
} else {
|
||||
resetIdleTimer();
|
||||
attachIdleListeners();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Setup input listeners for the various settings and actions related to the idle extension.
|
||||
*/
|
||||
function setupListeners() {
|
||||
const settingsToWatch = [
|
||||
['idle_timer', 'timer'],
|
||||
['idle_prompts', 'prompts'],
|
||||
['idle_use_continuation', 'useContinuation', true],
|
||||
['idle_enabled', 'enabled', true],
|
||||
['idle_repeats', 'repeats'],
|
||||
['idle_sendAs', 'sendAs'],
|
||||
['idle_random_time', 'randomTime', true],
|
||||
['idle_timer_min', 'timerMin'],
|
||||
['idle_include_prompt', 'includePrompt', true]
|
||||
];
|
||||
settingsToWatch.forEach(setting => {
|
||||
attachUpdateListener(...setting);
|
||||
});
|
||||
|
||||
// Idleness listeners, could be made better
|
||||
$('#idle_enabled').on('input', debounce(handleIdleEnabled, 250));
|
||||
|
||||
// Add the idle listeners initially if the idle feature is enabled
|
||||
if (extension_settings.idle.enabled) {
|
||||
attachIdleListeners();
|
||||
}
|
||||
|
||||
//show/hide timer min parent div
|
||||
$('#idle_random_time').on('input', function () {
|
||||
if ($(this).prop('checked')) {
|
||||
$('#idle_timer_min').parent().show();
|
||||
} else {
|
||||
$('#idle_timer_min').parent().hide();
|
||||
}
|
||||
|
||||
$('#idle_timer').trigger('input');
|
||||
});
|
||||
|
||||
// if we're including the prompt, hide raw from the sendAs dropdown
|
||||
$('#idle_include_prompt').on('input', function () {
|
||||
if ($(this).prop('checked')) {
|
||||
$('#idle_sendAs option[value="raw"]').hide();
|
||||
} else {
|
||||
$('#idle_sendAs option[value="raw"]').show();
|
||||
}
|
||||
});
|
||||
|
||||
//make sure timer min is less than timer
|
||||
$('#idle_timer').on('input', function () {
|
||||
if ($('#idle_random_time').prop('checked')) {
|
||||
if ($(this).val() < $('#idle_timer_min').val()) {
|
||||
$('#idle_timer_min').val($(this).val());
|
||||
$('#idle_timer_min').trigger('input');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
const debouncedActivityHandler = debounce((event) => {
|
||||
// Check if the event target (or any of its parents) has the id "option_continue"
|
||||
if ($(event.target).closest('#option_continue').length) {
|
||||
return; // Do not proceed if the click was on (or inside) an element with id "option_continue"
|
||||
}
|
||||
|
||||
console.debug("Activity detected, resetting idle timer");
|
||||
resetIdleTimer();
|
||||
repeatCount = 0;
|
||||
}, 250);
|
||||
|
||||
function attachIdleListeners() {
|
||||
$(document).on("click keypress", debouncedActivityHandler);
|
||||
document.addEventListener('keydown', debouncedActivityHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove idle-specific listeners.
|
||||
*/
|
||||
function removeIdleListeners() {
|
||||
$(document).off("click keypress", debouncedActivityHandler);
|
||||
document.removeEventListener('keydown', debouncedActivityHandler);
|
||||
}
|
||||
|
||||
function toggleIdle() {
|
||||
extension_settings.idle.enabled = !extension_settings.idle.enabled;
|
||||
$('#idle_enabled').prop('checked', extension_settings.idle.enabled);
|
||||
$('#idle_enabled').trigger('input');
|
||||
toastr.info(`Idle mode ${extension_settings.idle.enabled ? "enabled" : "disabled"}.`);
|
||||
resetIdleTimer();
|
||||
}
|
||||
|
||||
|
||||
|
||||
jQuery(async () => {
|
||||
await loadSettingsHTML();
|
||||
loadSettings();
|
||||
setupListeners();
|
||||
if (extension_settings.idle.enabled) {
|
||||
resetIdleTimer();
|
||||
}
|
||||
// once the doc is ready, check if random time is checked and hide/show timer min
|
||||
if ($('#idle_random_time').prop('checked')) {
|
||||
$('#idle_timer_min').parent().show();
|
||||
}
|
||||
registerSlashCommand('idle', toggleIdle, [], ' – toggles idle mode', true, true);
|
||||
});
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"display_name": "Idle",
|
||||
"loading_order": 6,
|
||||
"requires": [],
|
||||
"optional": [
|
||||
],
|
||||
"js": "index.js",
|
||||
"css": "style.css",
|
||||
"author": "City-Unit",
|
||||
"version": "1.0.0",
|
||||
"homePage": "https://github.com/SillyTavern/SillyTavern"
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
.idle_block {
|
||||
align-items: center;
|
||||
}
|
|
@ -508,7 +508,6 @@ async function onSelectInjectFile(e) {
|
|||
meta: JSON.stringify({
|
||||
name: file.name,
|
||||
is_user: false,
|
||||
is_name: false,
|
||||
is_system: false,
|
||||
send_date: humanizedDateTime(),
|
||||
mes: m,
|
||||
|
@ -686,7 +685,6 @@ window.chromadb_interceptGeneration = async (chat, maxContext) => {
|
|||
const charname = context.name2;
|
||||
newChat.push(
|
||||
{
|
||||
is_name: false,
|
||||
is_user: false,
|
||||
mes: `[Use these past chat exchanges to inform ${charname}'s next response:`,
|
||||
name: "system",
|
||||
|
@ -696,7 +694,6 @@ window.chromadb_interceptGeneration = async (chat, maxContext) => {
|
|||
newChat.push(...queriedMessages.map(m => m.meta).filter(onlyUnique).map(JSON.parse));
|
||||
newChat.push(
|
||||
{
|
||||
is_name: false,
|
||||
is_user: false,
|
||||
mes: `]\n`,
|
||||
name: "system",
|
||||
|
@ -739,7 +736,7 @@ window.chromadb_interceptGeneration = async (chat, maxContext) => {
|
|||
// No memories? No prompt.
|
||||
const promptBlob = (tokenApprox == 0) ? "" : wrapperMsg.replace('{{memories}}', allMemoryBlob);
|
||||
console.debug("CHROMADB: prompt blob: %o", promptBlob);
|
||||
context.setExtensionPrompt(MODULE_NAME, promptBlob, extension_prompt_types.AFTER_SCENARIO);
|
||||
context.setExtensionPrompt(MODULE_NAME, promptBlob, extension_prompt_types.IN_PROMPT);
|
||||
}
|
||||
if (selectedStrategy === 'custom') {
|
||||
const context = getContext();
|
||||
|
@ -752,7 +749,6 @@ window.chromadb_interceptGeneration = async (chat, maxContext) => {
|
|||
|
||||
newChat.push(
|
||||
{
|
||||
is_name: false,
|
||||
is_user: false,
|
||||
mes: recallStart,
|
||||
name: "system",
|
||||
|
@ -762,7 +758,6 @@ window.chromadb_interceptGeneration = async (chat, maxContext) => {
|
|||
newChat.push(...queriedMessages.map(m => m.meta).filter(onlyUnique).map(JSON.parse));
|
||||
newChat.push(
|
||||
{
|
||||
is_name: false,
|
||||
is_user: false,
|
||||
mes: recallEnd + `\n`,
|
||||
name: "system",
|
||||
|
|
|
@ -2,6 +2,7 @@ import { getStringHash, debounce, waitUntilCondition, extractAllWords } from "..
|
|||
import { getContext, getApiUrl, extension_settings, doExtrasFetch, modules } from "../../extensions.js";
|
||||
import { eventSource, event_types, extension_prompt_types, generateQuietPrompt, is_send_press, saveSettingsDebounced, substituteParams } from "../../../script.js";
|
||||
import { is_group_generating, selected_group } from "../../group-chats.js";
|
||||
import { registerSlashCommand } from "../../slash-commands.js";
|
||||
export { MODULE_NAME };
|
||||
|
||||
const MODULE_NAME = '1_memory';
|
||||
|
@ -63,7 +64,7 @@ const defaultSettings = {
|
|||
source: summary_sources.extras,
|
||||
prompt: defaultPrompt,
|
||||
template: defaultTemplate,
|
||||
position: extension_prompt_types.AFTER_SCENARIO,
|
||||
position: extension_prompt_types.IN_PROMPT,
|
||||
depth: 2,
|
||||
promptWords: 200,
|
||||
promptMinWords: 25,
|
||||
|
@ -190,18 +191,21 @@ function onMemoryPromptInput() {
|
|||
function onMemoryTemplateInput() {
|
||||
const value = $(this).val();
|
||||
extension_settings.memory.template = value;
|
||||
reinsertMemory();
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onMemoryDepthInput() {
|
||||
const value = $(this).val();
|
||||
extension_settings.memory.depth = Number(value);
|
||||
reinsertMemory();
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onMemoryPositionChange(e) {
|
||||
const value = e.target.value;
|
||||
extension_settings.memory.position = value;
|
||||
reinsertMemory();
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
|
@ -392,7 +396,7 @@ async function summarizeChatMain(context, force) {
|
|||
return;
|
||||
}
|
||||
|
||||
const summary = await generateQuietPrompt(prompt);
|
||||
const summary = await generateQuietPrompt(prompt, false);
|
||||
const newContext = getContext();
|
||||
|
||||
// something changed during summarization request
|
||||
|
@ -517,6 +521,11 @@ function onMemoryContentInput() {
|
|||
setMemoryContext(value, true);
|
||||
}
|
||||
|
||||
function reinsertMemory() {
|
||||
const existingValue = $('#memory_contents').val();
|
||||
setMemoryContext(existingValue, false);
|
||||
}
|
||||
|
||||
function setMemoryContext(value, saveToMessage) {
|
||||
const context = getContext();
|
||||
context.setExtensionPrompt(MODULE_NAME, formatMemoryValue(value), extension_settings.memory.position, extension_settings.memory.depth);
|
||||
|
@ -565,6 +574,10 @@ jQuery(function () {
|
|||
</div>
|
||||
<label for="memory_position">Injection position:</label>
|
||||
<div class="radio_group">
|
||||
<label>
|
||||
<input type="radio" name="memory_position" value="2" />
|
||||
Before Main Prompt / Story String
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="memory_position" value="0" />
|
||||
After Main Prompt / Story String
|
||||
|
@ -637,4 +650,5 @@ jQuery(function () {
|
|||
eventSource.on(event_types.MESSAGE_EDITED, onChatEvent);
|
||||
eventSource.on(event_types.MESSAGE_SWIPED, onChatEvent);
|
||||
eventSource.on(event_types.CHAT_CHANGED, onChatEvent);
|
||||
registerSlashCommand('summarize', forceSummarizeChat, [], ' – forces the summarization of the current chat using the Main API', true, true);
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { saveSettingsDebounced, callPopup, getRequestHeaders } from "../../../script.js";
|
||||
import { saveSettingsDebounced, callPopup, getRequestHeaders, substituteParams } from "../../../script.js";
|
||||
import { getContext, extension_settings } from "../../extensions.js";
|
||||
import { initScrollHeight, resetScrollHeight } from "../../utils.js";
|
||||
import { executeSlashCommands, getSlashCommandsHelp, registerSlashCommand } from "../../slash-commands.js";
|
||||
|
@ -15,6 +15,8 @@ const defaultSettings = {
|
|||
quickReplyEnabled: false,
|
||||
numberOfSlots: 5,
|
||||
quickReplySlots: [],
|
||||
placeBeforePromptEnabled: false,
|
||||
quickActionEnabled: false,
|
||||
}
|
||||
|
||||
//method from worldinfo
|
||||
|
@ -75,6 +77,8 @@ async function loadSettings(type) {
|
|||
|
||||
$('#quickReplyEnabled').prop('checked', extension_settings.quickReply.quickReplyEnabled);
|
||||
$('#quickReplyNumberOfSlots').val(extension_settings.quickReply.numberOfSlots);
|
||||
$('#placeBeforePromptEnabled').prop('checked', extension_settings.quickReply.placeBeforePromptEnabled);
|
||||
$('#quickActionEnabled').prop('checked', extension_settings.quickReply.quickActionEnabled);
|
||||
}
|
||||
|
||||
function onQuickReplyInput(id) {
|
||||
|
@ -99,7 +103,19 @@ async function onQuickReplyEnabledInput() {
|
|||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
// New function to handle input on quickActionEnabled
|
||||
async function onQuickActionEnabledInput() {
|
||||
extension_settings.quickReply.quickActionEnabled = !!$(this).prop('checked');
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
async function onPlaceBeforePromptEnabledInput() {
|
||||
extension_settings.quickReply.placeBeforePromptEnabled = !!$(this).prop('checked');
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
async function sendQuickReply(index) {
|
||||
const existingText = $("#send_textarea").val();
|
||||
const prompt = extension_settings.quickReply.quickReplySlots[index]?.mes || '';
|
||||
|
||||
if (!prompt) {
|
||||
|
@ -107,10 +123,35 @@ async function sendQuickReply(index) {
|
|||
return;
|
||||
}
|
||||
|
||||
$("#send_textarea").val(prompt);
|
||||
$("#send_but").trigger('click');
|
||||
let newText;
|
||||
|
||||
if (existingText) {
|
||||
// If existing text, add space after prompt
|
||||
if (extension_settings.quickReply.placeBeforePromptEnabled) {
|
||||
newText = `${prompt} ${existingText} `;
|
||||
} else {
|
||||
newText = `${existingText} ${prompt} `;
|
||||
}
|
||||
} else {
|
||||
// If no existing text, add prompt only (with a trailing space)
|
||||
newText = prompt + ' ';
|
||||
}
|
||||
|
||||
newText = substituteParams(newText);
|
||||
|
||||
$("#send_textarea").val(newText);
|
||||
|
||||
// Set the focus back to the textarea
|
||||
$("#send_textarea").focus();
|
||||
|
||||
// Only trigger send button if quickActionEnabled is not checked or
|
||||
// the prompt starts with '/'
|
||||
if (!extension_settings.quickReply.quickActionEnabled || prompt.startsWith('/')) {
|
||||
$("#send_but").trigger('click');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function addQuickReplyBar() {
|
||||
$('#quickReplyBar').remove();
|
||||
let quickReplyButtonHtml = '';
|
||||
|
@ -309,6 +350,14 @@ jQuery(async () => {
|
|||
<input id="quickReplyEnabled" type="checkbox" />
|
||||
Enable Quick Replies
|
||||
</label>
|
||||
<label class="checkbox_label marginBot10 wide100p flexnowrap">
|
||||
<input id="quickActionEnabled" type="checkbox" />
|
||||
Disable Send / Insert In User Input
|
||||
</label>
|
||||
<label class="checkbox_label marginBot10 wide100p flexnowrap">
|
||||
<input id="placeBeforePromptEnabled" type="checkbox" />
|
||||
Place Quick-reply before the Prompt
|
||||
</label>
|
||||
<div class="flex-container flexnowrap wide100p">
|
||||
<select id="quickReplyPresets" name="quickreply-preset">
|
||||
</select>
|
||||
|
@ -330,7 +379,10 @@ jQuery(async () => {
|
|||
</div>`;
|
||||
|
||||
$('#extensions_settings2').append(settingsHtml);
|
||||
|
||||
|
||||
// Add event handler for quickActionEnabled
|
||||
$('#quickActionEnabled').on('input', onQuickActionEnabledInput);
|
||||
$('#placeBeforePromptEnabled').on('input', onPlaceBeforePromptEnabledInput);
|
||||
$('#quickReplyEnabled').on('input', onQuickReplyEnabledInput);
|
||||
$('#quickReplyNumberOfSlotsApply').on('click', onQuickReplyNumberOfSlotsInput);
|
||||
$("#quickReplyPresetSaveButton").on('click', saveQuickReplyPreset);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#quickReplyBar {
|
||||
outline: none;
|
||||
padding: 5px 0;
|
||||
border-bottom: 1px solid var(--black30a);
|
||||
border-bottom: 1px solid var(--SmartThemeBorderColor);
|
||||
margin: 0;
|
||||
transition: 0.3s;
|
||||
opacity: 0.7;
|
||||
|
@ -28,7 +28,7 @@
|
|||
#quickReplies div {
|
||||
color: var(--SmartThemeBodyColor);
|
||||
background-color: var(--black50a);
|
||||
border: 1px solid var(--white30a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
border-radius: 10px;
|
||||
padding: 3px 5px;
|
||||
width: min-content;
|
||||
|
@ -44,4 +44,4 @@
|
|||
opacity: 1;
|
||||
filter: brightness(1.2);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
|
@ -136,7 +136,7 @@ jQuery(() => {
|
|||
</div>
|
||||
</div>`;
|
||||
|
||||
$('#extensions_settings').append(html);
|
||||
$('#extensions_settings2').append(html);
|
||||
$('#ai_response_configuration .range-block-counter').each(addRandomizeButton);
|
||||
$('#randomizer_enabled').on('input', onRandomizerEnabled);
|
||||
$('#randomizer_enabled').prop('checked', extension_settings.randomizer.enabled).trigger('input');
|
||||
|
|
|
@ -12,9 +12,9 @@
|
|||
|
||||
.regex-script-label {
|
||||
align-items: center;
|
||||
border: 1px solid rgba(128, 128, 128, 0.5);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
border-radius: 10px;
|
||||
padding: 0 5px;
|
||||
margin-top: 1px;
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
export { MODULE_NAME };
|
||||
|
||||
const MODULE_NAME = 'settingsSearch';
|
||||
async function addSettingsSearchHTML() {
|
||||
|
||||
const html = `
|
||||
<div class="wide100p">
|
||||
<div class="justifyLeft">
|
||||
<textarea id="settingsSearch" class="wide100p textarea_compact" rows="1" placeholder="Search Settings"></textarea>
|
||||
</div>
|
||||
</div>`
|
||||
|
||||
$("#user-settings-block").prepend(html);
|
||||
}
|
||||
|
||||
async function searchSettings() {
|
||||
removeHighlighting(); // Remove previous highlights
|
||||
let searchString = $("#settingsSearch").val();
|
||||
let searchableText = $("#user-settings-block-content"); // Get the HTML block
|
||||
if (searchString.trim() !== "") {
|
||||
highlightMatchingElements(searchableText[0], searchString); // Highlight matching elements
|
||||
}
|
||||
}
|
||||
function isParentHeader(element) {
|
||||
return $(element).closest('h4, h3').length > 0;
|
||||
}
|
||||
function highlightMatchingElements(element, searchString) {
|
||||
$(element).contents().each(function () {
|
||||
const isTextNode = this.nodeType === Node.TEXT_NODE;
|
||||
const isElementNode = this.nodeType === Node.ELEMENT_NODE;
|
||||
|
||||
if (isTextNode && this.nodeValue.trim() !== "" && !isParentHeader(this)) {
|
||||
const parentElement = $(this).parent();
|
||||
const elementText = this.nodeValue;
|
||||
|
||||
if (elementText.toLowerCase().includes(searchString.toLowerCase())) {
|
||||
parentElement.addClass('highlighted'); // Add CSS class to highlight matched elements
|
||||
}
|
||||
} else if (isElementNode && !$(this).is("h4")) {
|
||||
highlightMatchingElements(this, searchString);
|
||||
}
|
||||
});
|
||||
}
|
||||
function removeHighlighting() {
|
||||
$(".highlighted").removeClass("highlighted"); // Remove CSS class from previously highlighted elements
|
||||
}
|
||||
jQuery(() => {
|
||||
//addSettingsSearchHTML();
|
||||
$('#settingsSearch').on('input change', searchSettings);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"display_name": "Settings Search",
|
||||
"loading_order": 15,
|
||||
"requires": [],
|
||||
"optional": [],
|
||||
"js": "index.js",
|
||||
"css": "style.css",
|
||||
"author": "RossAscends",
|
||||
"version": "1.0.0",
|
||||
"homePage": "https://github.com/SillyTavern/SillyTavern"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
.highlighted {
|
||||
color: black;
|
||||
background-color: yellow;
|
||||
text-shadow: none !important;
|
||||
}
|
|
@ -165,7 +165,6 @@ async function processTranscript(transcript) {
|
|||
const message = {
|
||||
name: context.name1,
|
||||
is_user: true,
|
||||
is_name: true,
|
||||
send_date: getMessageTimeStamp(),
|
||||
mes: messageText,
|
||||
};
|
||||
|
|
|
@ -622,7 +622,7 @@ async function loadSamplers() {
|
|||
}
|
||||
|
||||
async function loadHordeSamplers() {
|
||||
const result = await fetch('/horde_samplers', {
|
||||
const result = await fetch('/api/horde/sd-samplers', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
});
|
||||
|
@ -721,7 +721,7 @@ async function loadModels() {
|
|||
}
|
||||
|
||||
async function loadHordeModels() {
|
||||
const result = await fetch('/horde_models', {
|
||||
const result = await fetch('/api/horde/sd-models', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
});
|
||||
|
@ -1084,7 +1084,7 @@ async function generateExtrasImage(prompt) {
|
|||
* @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.
|
||||
*/
|
||||
async function generateHordeImage(prompt) {
|
||||
const result = await fetch('/horde_generateimage', {
|
||||
const result = await fetch('/api/horde/generate-image', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({
|
||||
|
@ -1246,7 +1246,6 @@ async function sendMessage(prompt, image) {
|
|||
name: context.groupId ? systemUserName : context.name2,
|
||||
is_user: false,
|
||||
is_system: true,
|
||||
is_name: true,
|
||||
send_date: getMessageTimeStamp(),
|
||||
mes: context.groupId ? p(messageText) : messageText,
|
||||
extra: {
|
||||
|
|
|
@ -135,8 +135,8 @@ const languageCodes = {
|
|||
'Zulu': 'zu',
|
||||
};
|
||||
|
||||
const KEY_REQUIRED = ['deepl','libre'];
|
||||
const LOCAL_URL = ['libre'];
|
||||
const KEY_REQUIRED = ['deepl', 'libre'];
|
||||
const LOCAL_URL = ['libre', 'oneringtranslator', 'deeplx'];
|
||||
|
||||
function showKeysButton() {
|
||||
const providerRequiresKey = KEY_REQUIRED.includes(extension_settings.translate.provider);
|
||||
|
@ -144,6 +144,7 @@ function showKeysButton() {
|
|||
$("#translate_key_button").toggle(providerRequiresKey);
|
||||
$("#translate_key_button").toggleClass('success', Boolean(secret_state[extension_settings.translate.provider]));
|
||||
$("#translate_url_button").toggle(providerOptionalUrl);
|
||||
$("#translate_url_button").toggleClass('success', Boolean(secret_state[extension_settings.translate.provider + "_url"]));
|
||||
}
|
||||
|
||||
function loadSettings() {
|
||||
|
@ -184,8 +185,33 @@ async function translateIncomingMessage(messageId) {
|
|||
updateMessageBlock(messageId, message);
|
||||
}
|
||||
|
||||
async function translateProviderOneRing(text, lang) {
|
||||
let from_lang = lang == extension_settings.translate.internal_language
|
||||
? extension_settings.translate.target_language
|
||||
: extension_settings.translate.internal_language;
|
||||
|
||||
const response = await fetch('/api/translate/onering', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({ text: text, from_lang: from_lang, to_lang: lang }),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const result = await response.text();
|
||||
return result;
|
||||
}
|
||||
|
||||
throw new Error(response.statusText);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates text using the LibreTranslate API
|
||||
* @param {string} text Text to translate
|
||||
* @param {string} lang Target language code
|
||||
* @returns {Promise<string>} Translated text
|
||||
*/
|
||||
async function translateProviderLibre(text, lang) {
|
||||
const response = await fetch('/libre_translate', {
|
||||
const response = await fetch('/api/translate/libre', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({ text: text, lang: lang }),
|
||||
|
@ -199,8 +225,14 @@ async function translateProviderLibre(text, lang) {
|
|||
throw new Error(response.statusText);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates text using the Google Translate API
|
||||
* @param {string} text Text to translate
|
||||
* @param {string} lang Target language code
|
||||
* @returns {Promise<string>} Translated text
|
||||
*/
|
||||
async function translateProviderGoogle(text, lang) {
|
||||
const response = await fetch('/google_translate', {
|
||||
const response = await fetch('/api/translate/google', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({ text: text, lang: lang }),
|
||||
|
@ -214,12 +246,18 @@ async function translateProviderGoogle(text, lang) {
|
|||
throw new Error(response.statusText);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates text using the DeepL API
|
||||
* @param {string} text Text to translate
|
||||
* @param {string} lang Target language code
|
||||
* @returns {Promise<string>} Translated text
|
||||
*/
|
||||
async function translateProviderDeepl(text, lang) {
|
||||
if (!secret_state.deepl) {
|
||||
throw new Error('No DeepL API key');
|
||||
}
|
||||
|
||||
const response = await fetch('/deepl_translate', {
|
||||
const response = await fetch('/api/translate/deepl', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({ text: text, lang: lang }),
|
||||
|
@ -233,6 +271,33 @@ async function translateProviderDeepl(text, lang) {
|
|||
throw new Error(response.statusText);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates text using the DeepLX API
|
||||
* @param {string} text Text to translate
|
||||
* @param {string} lang Target language code
|
||||
* @returns {Promise<string>} Translated text
|
||||
*/
|
||||
async function translateProviderDeepLX(text, lang) {
|
||||
const response = await fetch('/api/translate/deeplx', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({ text: text, lang: lang }),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const result = await response.text();
|
||||
return result;
|
||||
}
|
||||
|
||||
throw new Error(response.statusText);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates text using the selected translation provider
|
||||
* @param {string} text Text to translate
|
||||
* @param {string} lang Target language code
|
||||
* @returns {Promise<string>} Translated text
|
||||
*/
|
||||
async function translate(text, lang) {
|
||||
try {
|
||||
if (text == '') {
|
||||
|
@ -246,6 +311,10 @@ async function translate(text, lang) {
|
|||
return await translateProviderGoogle(text, lang);
|
||||
case 'deepl':
|
||||
return await translateProviderDeepl(text, lang);
|
||||
case 'deeplx':
|
||||
return await translateProviderDeepLX(text, lang);
|
||||
case 'oneringtranslator':
|
||||
return await translateProviderOneRing(text, lang);
|
||||
default:
|
||||
console.error('Unknown translation provider', extension_settings.translate.provider);
|
||||
return text;
|
||||
|
@ -391,6 +460,8 @@ jQuery(() => {
|
|||
<option value="libre">Libre</option>
|
||||
<option value="google">Google</option>
|
||||
<option value="deepl">DeepL</option>
|
||||
<option value="deeplx">DeepLX</option>
|
||||
<option value="oneringtranslator">OneRingTranslator</option>
|
||||
<select>
|
||||
<div id="translate_key_button" class="menu_button fa-solid fa-key margin0"></div>
|
||||
<div id="translate_url_button" class="menu_button fa-solid fa-link margin0"></div>
|
||||
|
@ -443,12 +514,17 @@ jQuery(() => {
|
|||
|
||||
await writeSecret(extension_settings.translate.provider, key);
|
||||
toastr.success('API Key saved');
|
||||
$("#translate_key_button").addClass('success');
|
||||
});
|
||||
$('#translate_url_button').on('click', async () => {
|
||||
const optionText = $('#translation_provider option:selected').text();
|
||||
const exampleURLs = {};
|
||||
exampleURLs['libre'] = 'http://127.0.0.1:5000/translate';
|
||||
const url = await callPopup(`<h3>${optionText} API URL</h3><i>Example: <tt>` + exampleURLs[extension_settings.translate.provider] + `</tt></i>`, 'input');
|
||||
const exampleURLs = {
|
||||
'libre': 'http://127.0.0.1:5000/translate',
|
||||
'oneringtranslator': 'http://127.0.0.1:4990/translate',
|
||||
'deeplx': 'http://127.0.0.1:1188/translate',
|
||||
};
|
||||
const popupText = `<h3>${optionText} API URL</h3><i>Example: <tt>${String(exampleURLs[extension_settings.translate.provider])}</tt></i>`;
|
||||
const url = await callPopup(popupText, 'input');
|
||||
|
||||
if (url == false) {
|
||||
return;
|
||||
|
@ -456,6 +532,7 @@ jQuery(() => {
|
|||
|
||||
await writeSecret(extension_settings.translate.provider + "_url", url);
|
||||
toastr.success('API URL saved');
|
||||
$("#translate_url_button").addClass('success');
|
||||
});
|
||||
|
||||
loadSettings();
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { callPopup, cancelTtsPlay, eventSource, event_types, isMultigenEnabled, is_send_press, saveSettingsDebounced } from '../../../script.js'
|
||||
import { callPopup, cancelTtsPlay, eventSource, event_types, saveSettingsDebounced } from '../../../script.js'
|
||||
import { ModuleWorkerWrapper, doExtrasFetch, extension_settings, getApiUrl, getContext, modules } from '../../extensions.js'
|
||||
import { escapeRegex, getStringHash } from '../../utils.js'
|
||||
import { EdgeTtsProvider } from './edge.js'
|
||||
|
@ -117,11 +117,6 @@ async function moduleWorker() {
|
|||
return
|
||||
}
|
||||
|
||||
// Multigen message is currently being generated
|
||||
if (is_send_press && isMultigenEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Chat changed
|
||||
if (
|
||||
context.chatId !== lastChatId
|
||||
|
|
|
@ -170,7 +170,7 @@ class NovelTtsProvider {
|
|||
|
||||
async fetchTtsGeneration(inputText, voiceId) {
|
||||
console.info(`Generating new TTS for voice_id ${voiceId}`)
|
||||
const response = await fetch(`/novel_tts`,
|
||||
const response = await fetch(`/api/novelai/generate-voice`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
|
|
|
@ -0,0 +1,439 @@
|
|||
import { eventSource, event_types, extension_prompt_types, getCurrentChatId, getRequestHeaders, is_send_press, saveSettingsDebounced, setExtensionPrompt, substituteParams } from "../../../script.js";
|
||||
import { ModuleWorkerWrapper, extension_settings, getContext, renderExtensionTemplate } from "../../extensions.js";
|
||||
import { collapseNewlines, power_user, ui_mode } from "../../power-user.js";
|
||||
import { debounce, getStringHash as calculateHash, waitUntilCondition, onlyUnique } from "../../utils.js";
|
||||
|
||||
const MODULE_NAME = 'vectors';
|
||||
|
||||
export const EXTENSION_PROMPT_TAG = '3_vectors';
|
||||
|
||||
const settings = {
|
||||
enabled: false,
|
||||
source: 'transformers',
|
||||
template: `Past events: {{text}}`,
|
||||
depth: 2,
|
||||
position: extension_prompt_types.IN_PROMPT,
|
||||
protect: 5,
|
||||
insert: 3,
|
||||
query: 2,
|
||||
};
|
||||
|
||||
const moduleWorker = new ModuleWorkerWrapper(synchronizeChat);
|
||||
|
||||
async function onVectorizeAllClick() {
|
||||
try {
|
||||
if (!settings.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const chatId = getCurrentChatId();
|
||||
|
||||
if (!chatId) {
|
||||
toastr.info('No chat selected', 'Vectorization aborted');
|
||||
return;
|
||||
}
|
||||
|
||||
const batchSize = 5;
|
||||
const elapsedLog = [];
|
||||
let finished = false;
|
||||
$('#vectorize_progress').show();
|
||||
$('#vectorize_progress_percent').text('0');
|
||||
$('#vectorize_progress_eta').text('...');
|
||||
|
||||
while (!finished) {
|
||||
if (is_send_press) {
|
||||
toastr.info('Message generation is in progress.', 'Vectorization aborted');
|
||||
throw new Error('Message generation is in progress.');
|
||||
}
|
||||
|
||||
const startTime = Date.now();
|
||||
const remaining = await synchronizeChat(batchSize);
|
||||
const elapsed = Date.now() - startTime;
|
||||
elapsedLog.push(elapsed);
|
||||
finished = remaining <= 0;
|
||||
|
||||
const total = getContext().chat.length;
|
||||
const processed = total - remaining;
|
||||
const processedPercent = Math.round((processed / total) * 100); // percentage of the work done
|
||||
const lastElapsed = elapsedLog.slice(-5); // last 5 elapsed times
|
||||
const averageElapsed = lastElapsed.reduce((a, b) => a + b, 0) / lastElapsed.length; // average time needed to process one item
|
||||
const pace = averageElapsed / batchSize; // time needed to process one item
|
||||
const remainingTime = Math.round(pace * remaining / 1000);
|
||||
|
||||
$('#vectorize_progress_percent').text(processedPercent);
|
||||
$('#vectorize_progress_eta').text(remainingTime);
|
||||
|
||||
if (chatId !== getCurrentChatId()) {
|
||||
throw new Error('Chat changed');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Vectors: Failed to vectorize all', error);
|
||||
} finally {
|
||||
$('#vectorize_progress').hide();
|
||||
}
|
||||
}
|
||||
|
||||
let syncBlocked = false;
|
||||
|
||||
async function synchronizeChat(batchSize = 5) {
|
||||
if (!settings.enabled) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
try {
|
||||
await waitUntilCondition(() => !syncBlocked && !is_send_press, 1000);
|
||||
} catch {
|
||||
console.log('Vectors: Synchronization blocked by another process');
|
||||
return -1;
|
||||
}
|
||||
|
||||
try {
|
||||
syncBlocked = true;
|
||||
const context = getContext();
|
||||
const chatId = getCurrentChatId();
|
||||
|
||||
if (!chatId || !Array.isArray(context.chat)) {
|
||||
console.debug('Vectors: No chat selected');
|
||||
return -1;
|
||||
}
|
||||
|
||||
const hashedMessages = context.chat.filter(x => !x.is_system).map(x => ({ text: String(x.mes), hash: getStringHash(x.mes) }));
|
||||
const hashesInCollection = await getSavedHashes(chatId);
|
||||
|
||||
const newVectorItems = hashedMessages.filter(x => !hashesInCollection.includes(x.hash));
|
||||
const deletedHashes = hashesInCollection.filter(x => !hashedMessages.some(y => y.hash === x));
|
||||
|
||||
if (newVectorItems.length > 0) {
|
||||
console.log(`Vectors: Found ${newVectorItems.length} new items. Processing ${batchSize}...`);
|
||||
await insertVectorItems(chatId, newVectorItems.slice(0, batchSize));
|
||||
}
|
||||
|
||||
if (deletedHashes.length > 0) {
|
||||
await deleteVectorItems(chatId, deletedHashes);
|
||||
console.log(`Vectors: Deleted ${deletedHashes.length} old hashes`);
|
||||
}
|
||||
|
||||
return newVectorItems.length - batchSize;
|
||||
} catch (error) {
|
||||
toastr.error('Check server console for more details', 'Vectorization failed');
|
||||
console.error('Vectors: Failed to synchronize chat', error);
|
||||
return -1;
|
||||
} finally {
|
||||
syncBlocked = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Cache object for storing hash values
|
||||
const hashCache = {};
|
||||
|
||||
/**
|
||||
* Gets the hash value for a given string
|
||||
* @param {string} str Input string
|
||||
* @returns {number} Hash value
|
||||
*/
|
||||
function getStringHash(str) {
|
||||
// Check if the hash is already in the cache
|
||||
if (hashCache.hasOwnProperty(str)) {
|
||||
return hashCache[str];
|
||||
}
|
||||
|
||||
// Calculate the hash value
|
||||
const hash = calculateHash(str);
|
||||
|
||||
// Store the hash in the cache
|
||||
hashCache[str] = hash;
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the most relevant messages from the chat and displays them in the extension prompt
|
||||
* @param {object[]} chat Array of chat messages
|
||||
*/
|
||||
async function rearrangeChat(chat) {
|
||||
try {
|
||||
// Clear the extension prompt
|
||||
setExtensionPrompt(EXTENSION_PROMPT_TAG, '', extension_prompt_types.IN_PROMPT, 0);
|
||||
|
||||
if (!settings.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const chatId = getCurrentChatId();
|
||||
|
||||
if (!chatId || !Array.isArray(chat)) {
|
||||
console.debug('Vectors: No chat selected');
|
||||
return;
|
||||
}
|
||||
|
||||
if (chat.length < settings.protect) {
|
||||
console.debug(`Vectors: Not enough messages to rearrange (less than ${settings.protect})`);
|
||||
return;
|
||||
}
|
||||
|
||||
const queryText = getQueryText(chat);
|
||||
|
||||
if (queryText.length === 0) {
|
||||
console.debug('Vectors: No text to query');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the most relevant messages, excluding the last few
|
||||
const queryHashes = (await queryCollection(chatId, queryText, settings.insert)).filter(onlyUnique);
|
||||
const queriedMessages = [];
|
||||
const insertedHashes = new Set();
|
||||
const retainMessages = chat.slice(-settings.protect);
|
||||
|
||||
for (const message of chat) {
|
||||
if (retainMessages.includes(message) || !message.mes) {
|
||||
continue;
|
||||
}
|
||||
const hash = getStringHash(message.mes);
|
||||
if (queryHashes.includes(hash) && !insertedHashes.has(hash)) {
|
||||
queriedMessages.push(message);
|
||||
insertedHashes.add(hash);
|
||||
}
|
||||
}
|
||||
|
||||
// Rearrange queried messages to match query order
|
||||
// Order is reversed because more relevant are at the lower indices
|
||||
queriedMessages.sort((a, b) => queryHashes.indexOf(getStringHash(b.mes)) - queryHashes.indexOf(getStringHash(a.mes)));
|
||||
|
||||
// Remove queried messages from the original chat array
|
||||
for (const message of chat) {
|
||||
if (queriedMessages.includes(message)) {
|
||||
chat.splice(chat.indexOf(message), 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (queriedMessages.length === 0) {
|
||||
console.debug('Vectors: No relevant messages found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Format queried messages into a single string
|
||||
const insertedText = getPromptText(queriedMessages);
|
||||
setExtensionPrompt(EXTENSION_PROMPT_TAG, insertedText, settings.position, settings.depth);
|
||||
} catch (error) {
|
||||
console.error('Vectors: Failed to rearrange chat', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any[]} queriedMessages
|
||||
* @returns {string}
|
||||
*/
|
||||
function getPromptText(queriedMessages) {
|
||||
const queriedText = queriedMessages.map(x => collapseNewlines(`${x.name}: ${x.mes}`).trim()).join('\n\n');
|
||||
console.log('Vectors: relevant past messages found.\n', queriedText);
|
||||
return substituteParams(settings.template.replace(/{{text}}/i, queriedText));
|
||||
}
|
||||
|
||||
window['vectors_rearrangeChat'] = rearrangeChat;
|
||||
|
||||
const onChatEvent = debounce(async () => await moduleWorker.update(), 500);
|
||||
|
||||
/**
|
||||
* Gets the text to query from the chat
|
||||
* @param {object[]} chat Chat messages
|
||||
* @returns {string} Text to query
|
||||
*/
|
||||
function getQueryText(chat) {
|
||||
let queryText = '';
|
||||
let i = 0;
|
||||
|
||||
for (const message of chat.slice().reverse()) {
|
||||
if (message.mes) {
|
||||
queryText += message.mes + '\n';
|
||||
i++;
|
||||
}
|
||||
|
||||
if (i === settings.query) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return collapseNewlines(queryText).trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the saved hashes for a collection
|
||||
* @param {string} collectionId
|
||||
* @returns {Promise<number[]>} Saved hashes
|
||||
*/
|
||||
async function getSavedHashes(collectionId) {
|
||||
const response = await fetch('/api/vector/list', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({
|
||||
collectionId: collectionId,
|
||||
source: settings.source,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to get saved hashes for collection ${collectionId}`);
|
||||
}
|
||||
|
||||
const hashes = await response.json();
|
||||
return hashes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts vector items into a collection
|
||||
* @param {string} collectionId - The collection to insert into
|
||||
* @param {{ hash: number, text: string }[]} items - The items to insert
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function insertVectorItems(collectionId, items) {
|
||||
const response = await fetch('/api/vector/insert', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({
|
||||
collectionId: collectionId,
|
||||
items: items,
|
||||
source: settings.source,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to insert vector items for collection ${collectionId}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes vector items from a collection
|
||||
* @param {string} collectionId - The collection to delete from
|
||||
* @param {number[]} hashes - The hashes of the items to delete
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function deleteVectorItems(collectionId, hashes) {
|
||||
const response = await fetch('/api/vector/delete', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({
|
||||
collectionId: collectionId,
|
||||
hashes: hashes,
|
||||
source: settings.source,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to delete vector items for collection ${collectionId}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} collectionId - The collection to query
|
||||
* @param {string} searchText - The text to query
|
||||
* @param {number} topK - The number of results to return
|
||||
* @returns {Promise<number[]>} - Hashes of the results
|
||||
*/
|
||||
async function queryCollection(collectionId, searchText, topK) {
|
||||
const response = await fetch('/api/vector/query', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({
|
||||
collectionId: collectionId,
|
||||
searchText: searchText,
|
||||
topK: topK,
|
||||
source: settings.source,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to query collection ${collectionId}`);
|
||||
}
|
||||
|
||||
const results = await response.json();
|
||||
return results;
|
||||
}
|
||||
|
||||
async function purgeVectorIndex(collectionId) {
|
||||
try {
|
||||
if (!settings.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await fetch('/api/vector/purge', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({
|
||||
collectionId: collectionId,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Could not delete vector index for collection ${collectionId}`);
|
||||
}
|
||||
|
||||
console.log(`Vectors: Purged vector index for collection ${collectionId}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Vectors: Failed to purge', error);
|
||||
}
|
||||
}
|
||||
|
||||
jQuery(async () => {
|
||||
if (!extension_settings.vectors) {
|
||||
extension_settings.vectors = settings;
|
||||
}
|
||||
|
||||
Object.assign(settings, extension_settings.vectors);
|
||||
// Migrate from TensorFlow to Transformers
|
||||
settings.source = settings.source !== 'local' ? settings.source : 'transformers';
|
||||
$('#extensions_settings2').append(renderExtensionTemplate(MODULE_NAME, 'settings'));
|
||||
$('#vectors_enabled').prop('checked', settings.enabled).on('input', () => {
|
||||
settings.enabled = $('#vectors_enabled').prop('checked');
|
||||
Object.assign(extension_settings.vectors, settings);
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
$('#vectors_source').val(settings.source).on('change', () => {
|
||||
settings.source = String($('#vectors_source').val());
|
||||
Object.assign(extension_settings.vectors, settings);
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
$('#vectors_template').val(settings.template).on('input', () => {
|
||||
settings.template = String($('#vectors_template').val());
|
||||
Object.assign(extension_settings.vectors, settings);
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
$('#vectors_depth').val(settings.depth).on('input', () => {
|
||||
settings.depth = Number($('#vectors_depth').val());
|
||||
Object.assign(extension_settings.vectors, settings);
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
$('#vectors_protect').val(settings.protect).on('input', () => {
|
||||
settings.protect = Number($('#vectors_protect').val());
|
||||
Object.assign(extension_settings.vectors, settings);
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
$('#vectors_insert').val(settings.insert).on('input', () => {
|
||||
settings.insert = Number($('#vectors_insert').val());
|
||||
Object.assign(extension_settings.vectors, settings);
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
$('#vectors_query').val(settings.query).on('input', () => {
|
||||
settings.query = Number($('#vectors_query').val());
|
||||
Object.assign(extension_settings.vectors, settings);
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
$(`input[name="vectors_position"][value="${settings.position}"]`).prop('checked', true);
|
||||
$('input[name="vectors_position"]').on('change', () => {
|
||||
settings.position = Number($('input[name="vectors_position"]:checked').val());
|
||||
Object.assign(extension_settings.vectors, settings);
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
$('#vectors_advanced_settings').toggleClass('displayNone', power_user.ui_mode === ui_mode.SIMPLE);
|
||||
|
||||
$('#vectors_vectorize_all').on('click', onVectorizeAllClick);
|
||||
|
||||
eventSource.on(event_types.MESSAGE_DELETED, onChatEvent);
|
||||
eventSource.on(event_types.MESSAGE_EDITED, onChatEvent);
|
||||
eventSource.on(event_types.MESSAGE_SENT, onChatEvent);
|
||||
eventSource.on(event_types.MESSAGE_RECEIVED, onChatEvent);
|
||||
eventSource.on(event_types.MESSAGE_SWIPED, onChatEvent);
|
||||
eventSource.on(event_types.CHAT_DELETED, purgeVectorIndex);
|
||||
eventSource.on(event_types.GROUP_CHAT_DELETED, purgeVectorIndex);
|
||||
});
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"display_name": "Vector Storage",
|
||||
"loading_order": 100,
|
||||
"requires": [],
|
||||
"optional": [],
|
||||
"generate_interceptor": "vectors_rearrangeChat",
|
||||
"js": "index.js",
|
||||
"css": "",
|
||||
"author": "Cohee#1207",
|
||||
"version": "1.0.0",
|
||||
"homePage": "https://github.com/SillyTavern/SillyTavern"
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
<div class="vectors_settings">
|
||||
<div class="inline-drawer">
|
||||
<div class="inline-drawer-toggle inline-drawer-header">
|
||||
<b>Vector Storage</b>
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
<label class="checkbox_label" for="vectors_enabled">
|
||||
<input id="vectors_enabled" type="checkbox" class="checkbox">
|
||||
Enabled
|
||||
</label>
|
||||
<label for="vectors_source">
|
||||
Vectorization Source
|
||||
</label>
|
||||
<select id="vectors_source" class="select">
|
||||
<option value="transformers">Local (Transformers)</option>
|
||||
<option value="openai">OpenAI</option>
|
||||
</select>
|
||||
<div id="vectors_advanced_settings" data-newbie-hidden>
|
||||
<label for="vectors_template">
|
||||
Insertion template:
|
||||
</label>
|
||||
<textarea id="vectors_template" class="text_pole textarea_compact autoSetHeight" rows="2" placeholder="Use {{text}} macro to specify the position of retrieved text."></textarea>
|
||||
<label for="vectors_position">Injection position:</label>
|
||||
<div class="radio_group">
|
||||
<label>
|
||||
<input type="radio" name="vectors_position" value="2" />
|
||||
Before Main Prompt / Story String
|
||||
</label>
|
||||
<!--Keep these as 0 and 1 to interface with the setExtensionPrompt function-->
|
||||
<label>
|
||||
<input type="radio" name="vectors_position" value="0" />
|
||||
After Main Prompt / Story String
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="vectors_position" value="1" />
|
||||
In-chat @ Depth <input id="vectors_depth" class="text_pole widthUnset" type="number" min="0" max="99" />
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex-container">
|
||||
<div class="flex1" title="Prevents last N messages from being placed out of order.">
|
||||
<label for="vectors_protect">
|
||||
<small>Retain#</small>
|
||||
</label>
|
||||
<input type="number" id="vectors_protect" class="text_pole widthUnset" min="1" max="99" />
|
||||
</div>
|
||||
<div class="flex1" title="How many last messages will be matched for relevance.">
|
||||
<label for="vectors_query">
|
||||
<small>Query#</small>
|
||||
</label>
|
||||
<input type="number" id="vectors_query" class="text_pole widthUnset" min="1" max="99" />
|
||||
</div>
|
||||
<div class="flex1" title="How many past messages to insert as memories.">
|
||||
<label for="vectors_insert">
|
||||
<small>Insert#</small>
|
||||
</label>
|
||||
<input type="number" id="vectors_insert" class="text_pole widthUnset" min="1" max="99" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<small>
|
||||
Old messages are vectorized gradually as you chat.
|
||||
To process all previous messages, click the button below.
|
||||
</small>
|
||||
<div id="vectors_vectorize_all" class="menu_button menu_button_icon">
|
||||
Vectorize All
|
||||
</div>
|
||||
<div id="vectorize_progress" style="display: none;">
|
||||
<small>
|
||||
Processed <span id="vectorize_progress_percent">0</span>% of messages.
|
||||
ETA: <span id="vectorize_progress_eta">...</span> seconds.
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -51,7 +51,6 @@ import {
|
|||
menu_type,
|
||||
select_selected_character,
|
||||
cancelTtsPlay,
|
||||
isMultigenEnabled,
|
||||
displayPastChats,
|
||||
sendMessageAsUser,
|
||||
getBiasStrings,
|
||||
|
@ -166,7 +165,7 @@ export async function getGroupChat(groupId) {
|
|||
for (let key of data) {
|
||||
chat.push(key);
|
||||
}
|
||||
printMessages();
|
||||
await printMessages();
|
||||
} else {
|
||||
sendSystemMessage(system_message_types.GROUP, '', { isSmallSys: true });
|
||||
if (group && Array.isArray(group.members)) {
|
||||
|
@ -206,7 +205,6 @@ function getFirstCharacterMessage(character) {
|
|||
mes["is_user"] = false;
|
||||
mes["is_system"] = false;
|
||||
mes["name"] = character.name;
|
||||
mes["is_name"] = true;
|
||||
mes["send_date"] = getMessageTimeStamp();
|
||||
mes["original_avatar"] = character.avatar;
|
||||
mes["extra"] = { "gen_id": Date.now() * Math.random() * 1000000 };
|
||||
|
@ -577,7 +575,7 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
|
|||
|
||||
await Generate(generateType, { automatic_trigger: by_auto_mode, ...(params || {}) });
|
||||
|
||||
if (type !== "swipe" && type !== "impersonate" && !isMultigenEnabled() && !isStreamingEnabled()) {
|
||||
if (type !== "swipe" && type !== "impersonate" && !isStreamingEnabled()) {
|
||||
// update indicator and scroll down
|
||||
typingIndicator
|
||||
.find(".typing_indicator_name")
|
||||
|
@ -593,7 +591,7 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
|
|||
}
|
||||
|
||||
// if not swipe - check if message generated already
|
||||
if (generateType === "group_chat" && !isMultigenEnabled() && chat.length == messagesBefore) {
|
||||
if (generateType === "group_chat" && chat.length == messagesBefore) {
|
||||
await delay(100);
|
||||
}
|
||||
// if swipe - see if message changed
|
||||
|
@ -606,13 +604,6 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
|
|||
break;
|
||||
}
|
||||
}
|
||||
else if (isMultigenEnabled()) {
|
||||
if (isGenerationDone) {
|
||||
break;
|
||||
} else {
|
||||
await delay(100);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (lastMessageText === chat[chat.length - 1].mes) {
|
||||
await delay(100);
|
||||
|
@ -631,13 +622,6 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
|
|||
break;
|
||||
}
|
||||
}
|
||||
else if (isMultigenEnabled()) {
|
||||
if (isGenerationDone) {
|
||||
break;
|
||||
} else {
|
||||
await delay(100);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!$("#send_textarea").val() || $("#send_textarea").val() == userInput) {
|
||||
await delay(100);
|
||||
|
@ -654,14 +638,6 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
|
|||
await delay(100);
|
||||
}
|
||||
}
|
||||
else if (isMultigenEnabled()) {
|
||||
if (isGenerationDone) {
|
||||
messagesBefore++;
|
||||
break;
|
||||
} else {
|
||||
await delay(100);
|
||||
}
|
||||
}
|
||||
else if (isStreamingEnabled()) {
|
||||
if (streamingProcessor && !streamingProcessor.isFinished) {
|
||||
await delay(100);
|
||||
|
@ -816,18 +792,26 @@ function activateNaturalOrder(members, input, lastMessage, allowSelfResponses, i
|
|||
}
|
||||
|
||||
async function deleteGroup(id) {
|
||||
const group = groups.find((x) => x.id === id);
|
||||
|
||||
const response = await fetch("/deletegroup", {
|
||||
method: "POST",
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({ id: id }),
|
||||
});
|
||||
|
||||
if (group && Array.isArray(group.chats)) {
|
||||
for (const chatId of group.chats) {
|
||||
await eventSource.emit(event_types.GROUP_CHAT_DELETED, chatId);
|
||||
}
|
||||
}
|
||||
|
||||
if (response.ok) {
|
||||
selected_group = null;
|
||||
delete tag_map[id];
|
||||
resetChatState();
|
||||
clearChat();
|
||||
printMessages();
|
||||
await printMessages();
|
||||
await getCharacters();
|
||||
|
||||
select_rm_info("group_delete", id);
|
||||
|
@ -1493,6 +1477,8 @@ export async function deleteGroupChat(groupId, chatId) {
|
|||
} else {
|
||||
await createNewGroupChat(groupId);
|
||||
}
|
||||
|
||||
await eventSource.emit(event_types.GROUP_CHAT_DELETED, chatId);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,8 @@ import {
|
|||
setGenerationProgress,
|
||||
CLIENT_VERSION,
|
||||
getRequestHeaders,
|
||||
max_context,
|
||||
amount_gen
|
||||
} from "../script.js";
|
||||
import { SECRET_KEYS, writeSecret } from "./secrets.js";
|
||||
import { delay } from "./utils.js";
|
||||
|
@ -29,7 +31,7 @@ let horde_settings = {
|
|||
trusted_workers_only: false,
|
||||
};
|
||||
|
||||
const MAX_RETRIES = 200;
|
||||
const MAX_RETRIES = 240;
|
||||
const CHECK_INTERVAL = 5000;
|
||||
const MIN_AMOUNT_GEN = 16;
|
||||
const getRequestArgs = () => ({
|
||||
|
@ -57,6 +59,7 @@ function validateHordeModel() {
|
|||
}
|
||||
|
||||
async function adjustHordeGenerationParams(max_context_length, max_length) {
|
||||
console.log(max_context_length, max_length)
|
||||
const workers = await getWorkers();
|
||||
let maxContextLength = max_context_length;
|
||||
let maxLength = max_length;
|
||||
|
@ -84,7 +87,8 @@ async function adjustHordeGenerationParams(max_context_length, max_length) {
|
|||
maxLength = Math.min(worker.max_length, maxLength);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(maxContextLength, maxLength)
|
||||
$("#adjustedHordeParams").text(`Context: ${maxContextLength}, Response: ${maxLength}`)
|
||||
return { maxContextLength, maxLength };
|
||||
}
|
||||
|
||||
|
@ -107,7 +111,7 @@ async function generateHorde(prompt, params, signal) {
|
|||
"models": horde_settings.models,
|
||||
};
|
||||
|
||||
const response = await fetch("/generate_horde", {
|
||||
const response = await fetch("/api/horde/generate-text", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...getRequestHeaders(),
|
||||
|
@ -149,6 +153,16 @@ async function generateHorde(prompt, params, signal) {
|
|||
const statusCheckJson = await statusCheckResponse.json();
|
||||
console.log(statusCheckJson);
|
||||
|
||||
if (statusCheckJson.faulted === true) {
|
||||
toastr.error('Horde request faulted. Please try again.');
|
||||
throw new Error(`Horde generation failed: Faulted`);
|
||||
}
|
||||
|
||||
if (statusCheckJson.is_possible === false) {
|
||||
toastr.error('There are no Horde workers that are able to generate text with your request. Please change the parameters or try again later.');
|
||||
throw new Error(`Horde generation failed: Unsatisfiable request`);
|
||||
}
|
||||
|
||||
if (statusCheckJson.done && Array.isArray(statusCheckJson.generations) && statusCheckJson.generations.length) {
|
||||
setGenerationProgress(100);
|
||||
const generatedText = statusCheckJson.generations[0].text;
|
||||
|
@ -184,11 +198,13 @@ async function getHordeModels() {
|
|||
$('#horde_model').empty();
|
||||
const response = await fetch('https://horde.koboldai.net/api/v2/status/models?type=text', getRequestArgs());
|
||||
models = await response.json();
|
||||
|
||||
models.sort((a, b) => {
|
||||
return b.performance - a.performance;
|
||||
});
|
||||
for (const model of models) {
|
||||
const option = document.createElement('option');
|
||||
option.value = model.name;
|
||||
option.innerText = `${model.name} (ETA: ${model.eta}s, Queue: ${model.queued}, Workers: ${model.count})`;
|
||||
option.innerText = `${model.name} (ETA: ${model.eta}s, Speed: ${model.performance}, Queue: ${model.queued}, Workers: ${model.count})`;
|
||||
option.selected = horde_settings.models.includes(model.name);
|
||||
$('#horde_model').append(option);
|
||||
}
|
||||
|
@ -210,7 +226,7 @@ function loadHordeSettings(settings) {
|
|||
}
|
||||
|
||||
async function showKudos() {
|
||||
const response = await fetch('/horde_userinfo', {
|
||||
const response = await fetch('/api/horde/user-info', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
});
|
||||
|
@ -238,15 +254,30 @@ jQuery(function () {
|
|||
|
||||
// Try select instruct preset
|
||||
autoSelectInstructPreset(horde_settings.models.join(' '));
|
||||
if (horde_settings.models.length) {
|
||||
adjustHordeGenerationParams(max_context, amount_gen)
|
||||
} else {
|
||||
$("#adjustedHordeParams").text(`Context: --, Response: --`)
|
||||
}
|
||||
});
|
||||
|
||||
$("#horde_auto_adjust_response_length").on("input", function () {
|
||||
horde_settings.auto_adjust_response_length = !!$(this).prop("checked");
|
||||
if (horde_settings.models.length) {
|
||||
adjustHordeGenerationParams(max_context, amount_gen)
|
||||
} else {
|
||||
$("#adjustedHordeParams").text(`Context: --, Response: --`)
|
||||
}
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$("#horde_auto_adjust_context_length").on("input", function () {
|
||||
horde_settings.auto_adjust_context_length = !!$(this).prop("checked");
|
||||
if (horde_settings.models.length) {
|
||||
adjustHordeGenerationParams(max_context, amount_gen);
|
||||
} else {
|
||||
$("#adjustedHordeParams").text(`Context: --, Response: --`)
|
||||
}
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
|
@ -256,7 +287,7 @@ jQuery(function () {
|
|||
})
|
||||
|
||||
$("#horde_api_key").on("input", async function () {
|
||||
const key = $(this).val().trim();
|
||||
const key = String($(this).val()).trim();
|
||||
await writeSecret(SECRET_KEYS.HORDE, key);
|
||||
});
|
||||
|
||||
|
@ -271,11 +302,11 @@ jQuery(function () {
|
|||
placeholder: 'Select Horde models',
|
||||
allowClear: true,
|
||||
closeOnSelect: false,
|
||||
templateSelection: function(data) {
|
||||
templateSelection: function (data) {
|
||||
// Customize the pillbox text by shortening the full text
|
||||
return data.id;
|
||||
},
|
||||
templateResult: function(data) {
|
||||
templateResult: function (data) {
|
||||
// Return the full text for the dropdown
|
||||
return data.text;
|
||||
},
|
||||
|
|
|
@ -26,6 +26,7 @@ export const kai_settings = {
|
|||
mirostat_tau: 5.0,
|
||||
mirostat_eta: 0.1,
|
||||
use_default_badwordsids: true,
|
||||
grammar: "",
|
||||
};
|
||||
|
||||
export const kai_flags = {
|
||||
|
@ -34,6 +35,7 @@ export const kai_flags = {
|
|||
can_use_streaming: false,
|
||||
can_use_default_badwordsids: false,
|
||||
can_use_mirostat: false,
|
||||
can_use_grammar: false,
|
||||
};
|
||||
|
||||
const defaultValues = Object.freeze(structuredClone(kai_settings));
|
||||
|
@ -43,6 +45,7 @@ const MIN_UNBAN_VERSION = '1.2.4';
|
|||
const MIN_STREAMING_KCPPVERSION = '1.30';
|
||||
const MIN_TOKENIZATION_KCPPVERSION = '1.41';
|
||||
const MIN_MIROSTAT_KCPPVERSION = '1.35';
|
||||
const MIN_GRAMMAR_KCPPVERSION = '1.44';
|
||||
const KOBOLDCPP_ORDER = [6, 0, 1, 3, 4, 2, 5];
|
||||
|
||||
export function formatKoboldUrl(value) {
|
||||
|
@ -88,6 +91,7 @@ export function loadKoboldSettings(preset) {
|
|||
|
||||
export function getKoboldGenerationData(finalPrompt, this_settings, this_amount_gen, this_max_context, isImpersonate, type) {
|
||||
const sampler_order = kai_settings.sampler_order || this_settings.sampler_order;
|
||||
|
||||
let generate_data = {
|
||||
prompt: finalPrompt,
|
||||
gui_settings: false,
|
||||
|
@ -115,10 +119,11 @@ export function getKoboldGenerationData(finalPrompt, this_settings, this_amount_
|
|||
stop_sequence: kai_flags.can_use_stop_sequence ? getStoppingStrings(isImpersonate) : undefined,
|
||||
streaming: kai_settings.streaming_kobold && kai_flags.can_use_streaming && type !== 'quiet',
|
||||
can_abort: kai_flags.can_use_streaming,
|
||||
mirostat: kai_flags.can_use_mirostat ? kai_settings.mirostat : undefined,
|
||||
mirostat: kai_flags.can_use_mirostat ? kai_settings.mirostat : undefined,
|
||||
mirostat_tau: kai_flags.can_use_mirostat ? kai_settings.mirostat_tau : undefined,
|
||||
mirostat_eta: kai_flags.can_use_mirostat ? kai_settings.mirostat_eta : undefined,
|
||||
use_default_badwordsids: kai_flags.can_use_default_badwordsids ? kai_settings.use_default_badwordsids : undefined,
|
||||
grammar: kai_flags.can_use_grammar ? kai_settings.grammar : undefined,
|
||||
};
|
||||
return generate_data;
|
||||
}
|
||||
|
@ -257,6 +262,13 @@ const sliders = [
|
|||
format: (val) => val,
|
||||
setValue: (val) => { kai_settings.mirostat_eta = Number(val); },
|
||||
},
|
||||
{
|
||||
name: "grammar",
|
||||
sliderId: "#grammar",
|
||||
counterId: "#grammar_counter_kobold",
|
||||
format: (val) => val,
|
||||
setValue: (val) => { kai_settings.grammar = val; },
|
||||
},
|
||||
];
|
||||
|
||||
export function setKoboldFlags(version, koboldVersion) {
|
||||
|
@ -265,6 +277,7 @@ export function setKoboldFlags(version, koboldVersion) {
|
|||
kai_flags.can_use_tokenization = canUseKoboldTokenization(koboldVersion);
|
||||
kai_flags.can_use_default_badwordsids = canUseDefaultBadwordIds(version);
|
||||
kai_flags.can_use_mirostat = canUseMirostat(koboldVersion);
|
||||
kai_flags.can_use_grammar = canUseGrammar(koboldVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -307,12 +320,28 @@ function canUseKoboldTokenization(koboldVersion) {
|
|||
} else return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the Kobold mirostat can be used with the given version.
|
||||
* @param {{result: string; version: string;}} koboldVersion KoboldAI version object.
|
||||
* @returns {boolean} True if the Kobold mirostat API can be used, false otherwise.
|
||||
*/
|
||||
function canUseMirostat(koboldVersion) {
|
||||
if (koboldVersion && koboldVersion.result == 'KoboldCpp') {
|
||||
return (koboldVersion.version || '0.0').localeCompare(MIN_MIROSTAT_KCPPVERSION, undefined, { numeric: true, sensitivity: 'base' }) > -1;
|
||||
} else return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the Kobold grammar can be used with the given version.
|
||||
* @param {{result: string; version:string;}} koboldVersion KoboldAI version object.
|
||||
* @returns {boolean} True if the Kobold grammar can be used, false otherwise.
|
||||
*/
|
||||
function canUseGrammar(koboldVersion) {
|
||||
if (koboldVersion && koboldVersion.result == 'KoboldCpp') {
|
||||
return (koboldVersion.version || '0.0').localeCompare(MIN_GRAMMAR_KCPPVERSION, undefined, { numeric: true, sensitivity: 'base' }) > -1;
|
||||
} else return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the sampler items by the given order.
|
||||
* @param {any[]} orderArray Sampler order array.
|
||||
|
@ -334,7 +363,7 @@ jQuery(function () {
|
|||
const value = $(this).val();
|
||||
const formattedValue = slider.format(value);
|
||||
slider.setValue(value);
|
||||
$(slider.counterId).html(formattedValue);
|
||||
$(slider.counterId).text(formattedValue);
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
import { api_server_textgenerationwebui, getRequestHeaders, setGenerationParamsFromPreset } from "../script.js";
|
||||
import { getDeviceInfo } from "./RossAscends-mods.js";
|
||||
|
||||
let models = [];
|
||||
|
||||
/**
|
||||
* @param {string} modelId
|
||||
*/
|
||||
export function getMancerModelURL(modelId) {
|
||||
return `https://neuro.mancer.tech/webui/${modelId}/api`;
|
||||
}
|
||||
|
||||
export async function loadMancerModels() {
|
||||
try {
|
||||
const response = await fetch('/api/mancer/models', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
models = data;
|
||||
|
||||
$('#mancer_model').empty();
|
||||
for (const model of data) {
|
||||
const option = document.createElement('option');
|
||||
option.value = model.id;
|
||||
option.text = model.name;
|
||||
option.selected = api_server_textgenerationwebui === getMancerModelURL(model.id);
|
||||
$('#mancer_model').append(option);
|
||||
}
|
||||
|
||||
} catch {
|
||||
console.warn('Failed to load Mancer models');
|
||||
}
|
||||
}
|
||||
|
||||
function onMancerModelSelect() {
|
||||
const modelId = String($('#mancer_model').val());
|
||||
const url = getMancerModelURL(modelId);
|
||||
$('#mancer_api_url_text').val(url);
|
||||
$('#api_button_textgenerationwebui').trigger('click');
|
||||
|
||||
const context = models.find(x => x.id === modelId)?.context;
|
||||
setGenerationParamsFromPreset({ max_length: context });
|
||||
}
|
||||
|
||||
function getMancerModelTemplate(option) {
|
||||
const model = models.find(x => x.id === option?.element?.value);
|
||||
|
||||
if (!option.id || !model) {
|
||||
return option.text;
|
||||
}
|
||||
|
||||
return $((`
|
||||
<div class="flex-container flexFlowColumn">
|
||||
<div><strong>${DOMPurify.sanitize(model.name)}</strong> | <span>${model.context} ctx</span></div>
|
||||
<small>${DOMPurify.sanitize(model.description)}</small>
|
||||
</div>
|
||||
`));
|
||||
}
|
||||
|
||||
jQuery(function () {
|
||||
$('#mancer_model').on('change', onMancerModelSelect);
|
||||
|
||||
const deviceInfo = getDeviceInfo();
|
||||
if (deviceInfo && deviceInfo.device.type === 'desktop') {
|
||||
$('#mancer_model').select2({
|
||||
placeholder: 'Select a model',
|
||||
searchInputPlaceholder: 'Search models...',
|
||||
searchInputCssClass: 'text_pole',
|
||||
width: '100%',
|
||||
templateResult: getMancerModelTemplate,
|
||||
});
|
||||
}
|
||||
});
|
|
@ -11,6 +11,7 @@ import { getTextTokens, tokenizers } from "./tokenizers.js";
|
|||
import {
|
||||
getSortableDelay,
|
||||
getStringHash,
|
||||
onlyUnique,
|
||||
uuidv4,
|
||||
} from "./utils.js";
|
||||
|
||||
|
@ -87,7 +88,7 @@ export function getNovelUnlimitedImageGeneration() {
|
|||
}
|
||||
|
||||
export async function loadNovelSubscriptionData() {
|
||||
const result = await fetch('/getstatus_novelai', {
|
||||
const result = await fetch('/api/novelai/status', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
});
|
||||
|
@ -402,7 +403,7 @@ function getBadWordPermutations(text) {
|
|||
// Ditto + leading space
|
||||
result.push(` ${text.toLowerCase()}`);
|
||||
|
||||
return result;
|
||||
return result.filter(onlyUnique);
|
||||
}
|
||||
|
||||
export function getNovelGenerationData(finalPrompt, this_settings, this_amount_gen, isImpersonate, cfgValues) {
|
||||
|
@ -679,7 +680,7 @@ function tryParseStreamingError(decoded) {
|
|||
export async function generateNovelWithStreaming(generate_data, signal) {
|
||||
generate_data.streaming = nai_settings.streaming_novel;
|
||||
|
||||
const response = await fetch('/generate_novelai', {
|
||||
const response = await fetch('/api/novelai/generate', {
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify(generate_data),
|
||||
method: 'POST',
|
||||
|
|
|
@ -123,6 +123,34 @@ const j2_max_pres = 5.0;
|
|||
const openrouter_website_model = 'OR_Website';
|
||||
const openai_max_stop_strings = 4;
|
||||
|
||||
const textCompletionModels = [
|
||||
"gpt-3.5-turbo-instruct",
|
||||
"gpt-3.5-turbo-instruct-0914",
|
||||
"text-davinci-003",
|
||||
"text-davinci-002",
|
||||
"text-davinci-001",
|
||||
"text-curie-001",
|
||||
"text-babbage-001",
|
||||
"text-ada-001",
|
||||
"code-davinci-002",
|
||||
"code-davinci-001",
|
||||
"code-cushman-002",
|
||||
"code-cushman-001",
|
||||
"text-davinci-edit-001",
|
||||
"code-davinci-edit-001",
|
||||
"text-embedding-ada-002",
|
||||
"text-similarity-davinci-001",
|
||||
"text-similarity-curie-001",
|
||||
"text-similarity-babbage-001",
|
||||
"text-similarity-ada-001",
|
||||
"text-search-davinci-doc-001",
|
||||
"text-search-curie-doc-001",
|
||||
"text-search-babbage-doc-001",
|
||||
"text-search-ada-doc-001",
|
||||
"code-search-babbage-code-001",
|
||||
"code-search-ada-code-001",
|
||||
];
|
||||
|
||||
let biasCache = undefined;
|
||||
let model_list = [];
|
||||
|
||||
|
@ -553,6 +581,22 @@ function populateDialogueExamples(prompts, chatCompletion) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} position - Prompt position in the extensions object.
|
||||
* @returns {string|false} - The prompt position for prompt collection.
|
||||
*/
|
||||
function getPromptPosition(position) {
|
||||
if (position == extension_prompt_types.BEFORE_PROMPT) {
|
||||
return 'start';
|
||||
}
|
||||
|
||||
if (position == extension_prompt_types.IN_PROMPT) {
|
||||
return 'end';
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate a chat conversation by adding prompts to the conversation and managing system and user prompts.
|
||||
*
|
||||
|
@ -617,17 +661,39 @@ function populateChatCompletion(prompts, chatCompletion, { bias, quietPrompt, ty
|
|||
if (bias && bias.trim().length) addToChatCompletion('bias');
|
||||
|
||||
// Tavern Extras - Summary
|
||||
if (prompts.has('summary')) chatCompletion.insert(Message.fromPrompt(prompts.get('summary')), 'main');
|
||||
if (prompts.has('summary')) {
|
||||
const summary = prompts.get('summary');
|
||||
|
||||
if (summary.position) {
|
||||
chatCompletion.insert(Message.fromPrompt(summary), 'main', summary.position);
|
||||
}
|
||||
}
|
||||
|
||||
// Authors Note
|
||||
if (prompts.has('authorsNote')) {
|
||||
const authorsNote = Message.fromPrompt(prompts.get('authorsNote'));
|
||||
const authorsNote = prompts.get('authorsNote') ;
|
||||
|
||||
// ToDo: Ideally this should not be retrieved here but already be referenced in some configuration object
|
||||
const afterScenario = document.querySelector('input[name="extension_floating_position"]').checked;
|
||||
if (authorsNote.position) {
|
||||
chatCompletion.insert(Message.fromPrompt(authorsNote), 'main', authorsNote.position);
|
||||
}
|
||||
}
|
||||
|
||||
// Add authors notes
|
||||
if (true === afterScenario) chatCompletion.insert(authorsNote, 'scenario');
|
||||
// Vectors Memory
|
||||
if (prompts.has('vectorsMemory')) {
|
||||
const vectorsMemory = prompts.get('vectorsMemory');
|
||||
|
||||
if (vectorsMemory.position) {
|
||||
chatCompletion.insert(Message.fromPrompt(vectorsMemory), 'main', vectorsMemory.position);
|
||||
}
|
||||
}
|
||||
|
||||
// Smart Context (ChromaDB)
|
||||
if (prompts.has('smartContext')) {
|
||||
const smartContext = prompts.get('smartContext');
|
||||
|
||||
if (smartContext.position) {
|
||||
chatCompletion.insert(Message.fromPrompt(smartContext), 'main', smartContext.position);
|
||||
}
|
||||
}
|
||||
|
||||
// Decide whether dialogue examples should always be added
|
||||
|
@ -686,7 +752,8 @@ function preparePromptsForChatCompletion({Scenario, charPersonality, name2, worl
|
|||
if (summary && summary.value) systemPrompts.push({
|
||||
role: 'system',
|
||||
content: summary.value,
|
||||
identifier: 'summary'
|
||||
identifier: 'summary',
|
||||
position: getPromptPosition(summary.position),
|
||||
});
|
||||
|
||||
// Authors Note
|
||||
|
@ -694,7 +761,26 @@ function preparePromptsForChatCompletion({Scenario, charPersonality, name2, worl
|
|||
if (authorsNote && authorsNote.value) systemPrompts.push({
|
||||
role: 'system',
|
||||
content: authorsNote.value,
|
||||
identifier: 'authorsNote'
|
||||
identifier: 'authorsNote',
|
||||
position: getPromptPosition(authorsNote.position),
|
||||
});
|
||||
|
||||
// Vectors Memory
|
||||
const vectorsMemory = extensionPrompts['3_vectors'];
|
||||
if (vectorsMemory && vectorsMemory.value) systemPrompts.push({
|
||||
role: 'system',
|
||||
content: vectorsMemory.value,
|
||||
identifier: 'vectorsMemory',
|
||||
position: getPromptPosition(vectorsMemory.position),
|
||||
});
|
||||
|
||||
// Smart Context (ChromaDB)
|
||||
const smartContext = extensionPrompts['chromadb'];
|
||||
if (smartContext && smartContext.value) systemPrompts.push({
|
||||
role: 'system',
|
||||
content: smartContext.value,
|
||||
identifier: 'smartContext',
|
||||
position: getPromptPosition(smartContext.position),
|
||||
});
|
||||
|
||||
// Persona Description
|
||||
|
@ -1096,7 +1182,7 @@ async function sendOpenAIRequest(type, openai_msgs_tosend, signal) {
|
|||
const isOpenRouter = oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER;
|
||||
const isScale = oai_settings.chat_completion_source == chat_completion_sources.SCALE;
|
||||
const isAI21 = oai_settings.chat_completion_source == chat_completion_sources.AI21;
|
||||
const isTextCompletion = oai_settings.chat_completion_source == chat_completion_sources.OPENAI && (oai_settings.openai_model.startsWith('text-') || oai_settings.openai_model.startsWith('code-'));
|
||||
const isTextCompletion = oai_settings.chat_completion_source == chat_completion_sources.OPENAI && textCompletionModels.includes(oai_settings.openai_model);
|
||||
const isQuiet = type === 'quiet';
|
||||
const stream = oai_settings.stream_openai && !isQuiet && !isScale && !isAI21;
|
||||
|
||||
|
@ -2100,7 +2186,7 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) {
|
|||
use_alt_scale: settings.use_alt_scale,
|
||||
};
|
||||
|
||||
const savePresetSettings = await fetch(`/savepreset_openai?name=${name}`, {
|
||||
const savePresetSettings = await fetch(`/api/presets/save-openai?name=${name}`, {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify(presetBody),
|
||||
|
@ -2260,7 +2346,7 @@ async function onPresetImportFileChange(e) {
|
|||
}
|
||||
}
|
||||
|
||||
const savePresetSettings = await fetch(`/savepreset_openai?name=${name}`, {
|
||||
const savePresetSettings = await fetch(`/api/presets/save-openai?name=${name}`, {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: importedFile,
|
||||
|
@ -2372,14 +2458,16 @@ async function onDeletePresetClick() {
|
|||
$('#settings_perset_openai').trigger('change');
|
||||
}
|
||||
|
||||
const response = await fetch('/deletepreset_openai', {
|
||||
const response = await fetch('/api/presets/delete-openai', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({ name: nameToDelete }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.warn('Preset was not deleted from server');
|
||||
toastr.warning('Preset was not deleted from server');
|
||||
} else {
|
||||
toastr.success('Preset deleted');
|
||||
}
|
||||
|
||||
saveSettingsDebounced();
|
||||
|
|
|
@ -16,7 +16,7 @@ import {
|
|||
setEditedMessageId,
|
||||
renderTemplate,
|
||||
} from "../script.js";
|
||||
import { isMobile, initMovingUI } from "./RossAscends-mods.js";
|
||||
import { isMobile, initMovingUI, favsToHotswap } from "./RossAscends-mods.js";
|
||||
import {
|
||||
groups,
|
||||
resetSelectedGroup,
|
||||
|
@ -30,7 +30,7 @@ import {
|
|||
import { registerSlashCommand } from "./slash-commands.js";
|
||||
import { tokenizers } from "./tokenizers.js";
|
||||
|
||||
import { countOccurrences, delay, isOdd, resetScrollHeight, sortMoments, timestampToMoment } from "./utils.js";
|
||||
import { countOccurrences, debounce, delay, isOdd, resetScrollHeight, sortMoments, timestampToMoment } from "./utils.js";
|
||||
|
||||
export {
|
||||
loadPowerUserSettings,
|
||||
|
@ -93,9 +93,11 @@ let power_user = {
|
|||
always_force_name2: false,
|
||||
user_prompt_bias: '',
|
||||
show_user_prompt_bias: true,
|
||||
multigen: false,
|
||||
multigen_first_chunk: 50,
|
||||
multigen_next_chunks: 30,
|
||||
auto_continue: {
|
||||
enabled: false,
|
||||
allow_chat_completions: false,
|
||||
target_length: 400,
|
||||
},
|
||||
markdown_escape_strings: '',
|
||||
|
||||
ui_mode: ui_mode.POWER,
|
||||
|
@ -121,9 +123,14 @@ let power_user = {
|
|||
italics_text_color: `${getComputedStyle(document.documentElement).getPropertyValue('--SmartThemeEmColor').trim()}`,
|
||||
quote_text_color: `${getComputedStyle(document.documentElement).getPropertyValue('--SmartThemeQuoteColor').trim()}`,
|
||||
blur_tint_color: `${getComputedStyle(document.documentElement).getPropertyValue('--SmartThemeBlurTintColor').trim()}`,
|
||||
chat_tint_color: `${getComputedStyle(document.documentElement).getPropertyValue('--SmartThemeChatTintColor').trim()}`,
|
||||
user_mes_blur_tint_color: `${getComputedStyle(document.documentElement).getPropertyValue('--SmartThemeUserMesBlurTintColor').trim()}`,
|
||||
bot_mes_blur_tint_color: `${getComputedStyle(document.documentElement).getPropertyValue('--SmartThemeBotMesBlurTintColor').trim()}`,
|
||||
shadow_color: `${getComputedStyle(document.documentElement).getPropertyValue('--SmartThemeShadowColor').trim()}`,
|
||||
border_color: `${getComputedStyle(document.documentElement).getPropertyValue('--SmartThemeBorderColor').trim()}`,
|
||||
|
||||
custom_css: '',
|
||||
|
||||
|
||||
waifuMode: false,
|
||||
movingUI: false,
|
||||
|
@ -144,7 +151,6 @@ let power_user = {
|
|||
render_formulas: false,
|
||||
allow_name1_display: false,
|
||||
allow_name2_display: false,
|
||||
//removeXML: false,
|
||||
hotswap_enabled: true,
|
||||
timer_enabled: true,
|
||||
timestamps_enabled: true,
|
||||
|
@ -152,6 +158,7 @@ let power_user = {
|
|||
mesIDDisplay_enabled: false,
|
||||
max_context_unlocked: false,
|
||||
message_token_count_enabled: false,
|
||||
expand_message_actions: false,
|
||||
prefer_character_prompt: true,
|
||||
prefer_character_jailbreak: true,
|
||||
quick_continue: false,
|
||||
|
@ -200,7 +207,7 @@ let power_user = {
|
|||
custom_stopping_strings_macro: true,
|
||||
fuzzy_search: false,
|
||||
encode_tags: false,
|
||||
lazy_load: 0,
|
||||
servers: [],
|
||||
};
|
||||
|
||||
let themes = [];
|
||||
|
@ -218,11 +225,15 @@ const storage_keys = {
|
|||
italics_text_color: "TavernAI_italics_text_color",
|
||||
quote_text_color: "TavernAI_quote_text_color",
|
||||
blur_tint_color: "TavernAI_blur_tint_color",
|
||||
chat_tint_color: "TavernAI_chat_tint_color",
|
||||
user_mes_blur_tint_color: "TavernAI_user_mes_blur_tint_color",
|
||||
bot_mes_blur_tint_color: "TavernAI_bot_mes_blur_tint_color",
|
||||
blur_strength: "TavernAI_blur_strength",
|
||||
shadow_color: "TavernAI_shadow_color",
|
||||
shadow_width: "TavernAI_shadow_width",
|
||||
border_color: "TavernAI_border_color",
|
||||
|
||||
custom_css: "TavernAI_custom_css",
|
||||
|
||||
waifuMode: "TavernAI_waifuMode",
|
||||
movingUI: "TavernAI_movingUI",
|
||||
|
@ -234,11 +245,14 @@ const storage_keys = {
|
|||
timestamp_model_icon: 'TimestampModelIcon',
|
||||
mesIDDisplay_enabled: 'mesIDDisplayEnabled',
|
||||
message_token_count_enabled: 'MessageTokenCountEnabled',
|
||||
expand_message_actions: 'ExpandMessageActions',
|
||||
};
|
||||
|
||||
let browser_has_focus = true;
|
||||
const debug_functions = [];
|
||||
|
||||
const setHotswapsDebounced = debounce(favsToHotswap, 500);
|
||||
|
||||
export function switchSimpleMode() {
|
||||
$('[data-newbie-hidden]').each(function () {
|
||||
$(this).toggleClass('displayNone', power_user.ui_mode === ui_mode.SIMPLE);
|
||||
|
@ -369,9 +383,21 @@ function switchTokenCount() {
|
|||
|
||||
function switchMesIDDisplay() {
|
||||
const value = localStorage.getItem(storage_keys.mesIDDisplay_enabled);
|
||||
let before = power_user.mesIDDisplay_enabled;
|
||||
power_user.mesIDDisplay_enabled = value === null ? true : value == "true";
|
||||
/* console.log(`
|
||||
localstorage value:${value},
|
||||
poweruser before:${before},
|
||||
poweruser after:${power_user.mesIDDisplay_enabled}`) */
|
||||
$("body").toggleClass("no-mesIDDisplay", !power_user.mesIDDisplay_enabled);
|
||||
$("#MesIDDisplayEnabled").prop("checked", power_user.mesIDDisplay_enabled);
|
||||
$("#mesIDDisplayEnabled").prop("checked", power_user.mesIDDisplay_enabled);
|
||||
}
|
||||
|
||||
function switchMessageActions() {
|
||||
const value = localStorage.getItem(storage_keys.expand_message_actions);
|
||||
power_user.expand_message_actions = value === null ? false : value == "true";
|
||||
$("body").toggleClass("expandMessageActions", power_user.expand_message_actions);
|
||||
$("#expandMessageActions").prop("checked", power_user.expand_message_actions);
|
||||
}
|
||||
|
||||
function switchUiMode() {
|
||||
|
@ -379,6 +405,10 @@ function switchUiMode() {
|
|||
power_user.fast_ui_mode = fastUi === null ? true : fastUi == "true";
|
||||
$("body").toggleClass("no-blur", power_user.fast_ui_mode);
|
||||
$("#fast_ui_mode").prop("checked", power_user.fast_ui_mode);
|
||||
if (power_user.fast_ui_mode) {
|
||||
$("#blur-strength-block").css('opacity', '0.2')
|
||||
$("#blur_strength").prop('disabled', true)
|
||||
} else { $("#blur-strength-block").css('opacity', '1') }
|
||||
}
|
||||
|
||||
function toggleWaifu() {
|
||||
|
@ -434,13 +464,18 @@ function noShadows() {
|
|||
power_user.noShadows = noShadows === null ? false : noShadows == "true";
|
||||
$("body").toggleClass("noShadows", power_user.noShadows);
|
||||
$("#noShadowsmode").prop("checked", power_user.noShadows);
|
||||
if (power_user.noShadows) {
|
||||
$("#shadow-width-block").css('opacity', '0.2')
|
||||
$("#shadow_width").prop('disabled', true)
|
||||
} else { $("#shadow-width-block").css('opacity', '1') }
|
||||
scrollChatToBottom();
|
||||
}
|
||||
|
||||
function applyAvatarStyle() {
|
||||
power_user.avatar_style = Number(localStorage.getItem(storage_keys.avatar_style) ?? avatar_styles.ROUND);
|
||||
$("body").toggleClass("big-avatars", power_user.avatar_style === avatar_styles.RECTANGULAR);
|
||||
$(`input[name="avatar_style"][value="${power_user.avatar_style}"]`).prop("checked", true);
|
||||
$("#avatar_style").val(power_user.avatar_style).prop("selected", true);
|
||||
//$(`input[name="avatar_style"][value="${power_user.avatar_style}"]`).prop("checked", true);
|
||||
|
||||
}
|
||||
|
||||
|
@ -450,9 +485,8 @@ function applyChatDisplay() {
|
|||
console.debug('applyChatDisplay: saw no chat display type defined')
|
||||
return
|
||||
}
|
||||
console.debug(`applyChatDisplay: applying ${power_user.chat_display}`)
|
||||
|
||||
$(`#chat_display option[value=${power_user.chat_display}]`).attr("selected", true)
|
||||
console.debug(`poweruser.chat_display ${power_user.chat_display}`)
|
||||
$("#chat_display").val(power_user.chat_display).prop("selected", true);
|
||||
|
||||
switch (power_user.chat_display) {
|
||||
case 0: {
|
||||
|
@ -476,11 +510,22 @@ function applyChatDisplay() {
|
|||
}
|
||||
}
|
||||
|
||||
function applyChatWidth() {
|
||||
function applyChatWidth(type) {
|
||||
power_user.chat_width = Number(localStorage.getItem(storage_keys.chat_width) ?? 50);
|
||||
let r = document.documentElement;
|
||||
r.style.setProperty('--sheldWidth', `${power_user.chat_width}vw`);
|
||||
$('#chat_width_slider').val(power_user.chat_width);
|
||||
|
||||
if (type === 'forced') {
|
||||
let r = document.documentElement;
|
||||
r.style.setProperty('--sheldWidth', `${power_user.chat_width}vw`);
|
||||
$('#chat_width_slider').val(power_user.chat_width);
|
||||
//document.documentElement.style.setProperty('--sheldWidth', power_user.chat_width);
|
||||
} else {
|
||||
//this is to prevent the slider from updating page in real time
|
||||
$("#chat_width_slider").off('mouseup touchend').on('mouseup touchend', () => {
|
||||
document.documentElement.style.setProperty('--sheldWidth', `${power_user.chat_width}vw`);
|
||||
})
|
||||
}
|
||||
|
||||
$('#chat_width_slider_counter').text(power_user.chat_width);
|
||||
}
|
||||
|
||||
async function applyThemeColor(type) {
|
||||
|
@ -499,6 +544,9 @@ async function applyThemeColor(type) {
|
|||
if (type === 'blurTint') {
|
||||
document.documentElement.style.setProperty('--SmartThemeBlurTintColor', power_user.blur_tint_color);
|
||||
}
|
||||
if (type === 'chatTint') {
|
||||
document.documentElement.style.setProperty('--SmartThemeChatTintColor', power_user.chat_tint_color);
|
||||
}
|
||||
if (type === 'userMesBlurTint') {
|
||||
document.documentElement.style.setProperty('--SmartThemeUserMesBlurTintColor', power_user.user_mes_blur_tint_color);
|
||||
}
|
||||
|
@ -508,6 +556,31 @@ async function applyThemeColor(type) {
|
|||
if (type === 'shadow') {
|
||||
document.documentElement.style.setProperty('--SmartThemeShadowColor', power_user.shadow_color);
|
||||
}
|
||||
if (type === 'border') {
|
||||
document.documentElement.style.setProperty('--SmartThemeBorderColor', power_user.border_color);
|
||||
}
|
||||
}
|
||||
|
||||
async function applyCustomCSS() {
|
||||
power_user.custom_css = String(localStorage.getItem(storage_keys.custom_css) ?? "");
|
||||
|
||||
if (power_user.custom_css.includes("@import")) {
|
||||
var removeImport = /@import[^;]+;/gm
|
||||
power_user.custom_css = power_user.custom_css.replace(removeImport, "");
|
||||
localStorage.setItem(storage_keys.custom_css, power_user.custom_css);
|
||||
toastr.warning('@import not allowed in Custom CSS. @import lines removed.')
|
||||
}
|
||||
|
||||
$("#customCSS").val(power_user.custom_css);
|
||||
var styleId = "custom-style";
|
||||
var style = document.getElementById(styleId);
|
||||
if (!style) {
|
||||
style = document.createElement("style");
|
||||
style.setAttribute("type", "text/css");
|
||||
style.setAttribute("id", styleId);
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
style.innerHTML = power_user.custom_css;
|
||||
}
|
||||
|
||||
async function applyBlurStrength() {
|
||||
|
@ -516,6 +589,7 @@ async function applyBlurStrength() {
|
|||
$("#blur_strength_counter").text(power_user.blur_strength);
|
||||
$("#blur_strength").val(power_user.blur_strength);
|
||||
|
||||
|
||||
}
|
||||
|
||||
async function applyShadowWidth() {
|
||||
|
@ -555,9 +629,11 @@ async function applyTheme(name) {
|
|||
{ key: 'italics_text_color', selector: '#italics-color-picker', type: 'italics' },
|
||||
{ key: 'quote_text_color', selector: '#quote-color-picker', type: 'quote' },
|
||||
{ key: 'blur_tint_color', selector: '#blur-tint-color-picker', type: 'blurTint' },
|
||||
{ key: 'chat_tint_color', selector: '#chat-tint-color-picker', type: 'chatTint' },
|
||||
{ key: 'user_mes_blur_tint_color', selector: '#user-mes-blur-tint-color-picker', type: 'userMesBlurTint' },
|
||||
{ key: 'bot_mes_blur_tint_color', selector: '#bot-mes-blur-tint-color-picker', type: 'botMesBlurTint' },
|
||||
{ key: 'shadow_color', selector: '#shadow-color-picker', type: 'shadow' },
|
||||
{ key: 'border_color', selector: '#border-color-picker', type: 'border' },
|
||||
{
|
||||
key: 'blur_strength',
|
||||
action: async () => {
|
||||
|
@ -565,6 +641,13 @@ async function applyTheme(name) {
|
|||
await applyBlurStrength();
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'custom_css',
|
||||
action: async () => {
|
||||
localStorage.setItem(storage_keys.custom_css, power_user.custom_css);
|
||||
await applyCustomCSS();
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'shadow_width',
|
||||
action: async () => {
|
||||
|
@ -623,48 +706,55 @@ async function applyTheme(name) {
|
|||
}
|
||||
|
||||
localStorage.setItem(storage_keys.chat_width, String(power_user.chat_width));
|
||||
applyChatWidth();
|
||||
applyChatWidth('forced');
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'timer_enabled',
|
||||
action: async () => {
|
||||
localStorage.setItem(storage_keys.timer_enabled, String(power_user.timer_enabled));
|
||||
localStorage.setItem(storage_keys.timer_enabled, Boolean(power_user.timer_enabled));
|
||||
switchTimer();
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'timestamps_enabled',
|
||||
action: async () => {
|
||||
localStorage.setItem(storage_keys.timestamps_enabled, String(power_user.timestamps_enabled));
|
||||
localStorage.setItem(storage_keys.timestamps_enabled, Boolean(power_user.timestamps_enabled));
|
||||
switchTimestamps();
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'timestamp_model_icon',
|
||||
action: async () => {
|
||||
localStorage.setItem(storage_keys.timestamp_model_icon, String(power_user.timestamp_model_icon));
|
||||
localStorage.setItem(storage_keys.timestamp_model_icon, Boolean(power_user.timestamp_model_icon));
|
||||
switchIcons();
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'message_token_count',
|
||||
key: 'message_token_count_enabled',
|
||||
action: async () => {
|
||||
localStorage.setItem(storage_keys.message_token_count_enabled, String(power_user.message_token_count_enabled));
|
||||
localStorage.setItem(storage_keys.message_token_count_enabled, Boolean(power_user.message_token_count_enabled));
|
||||
switchTokenCount();
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'mesIDDisplay_enabled',
|
||||
action: async () => {
|
||||
localStorage.setItem(storage_keys.mesIDDisplay_enabled, String(power_user.mesIDDisplay_enabled));
|
||||
localStorage.setItem(storage_keys.mesIDDisplay_enabled, Boolean(power_user.mesIDDisplay_enabled));
|
||||
switchMesIDDisplay();
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'expand_message_actions',
|
||||
action: async () => {
|
||||
localStorage.setItem(storage_keys.expand_message_actions, Boolean(power_user.expand_message_actions));
|
||||
switchMessageActions();
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'hotswap_enabled',
|
||||
action: async () => {
|
||||
localStorage.setItem(storage_keys.hotswap_enabled, String(power_user.hotswap_enabled));
|
||||
localStorage.setItem(storage_keys.hotswap_enabled, Boolean(power_user.hotswap_enabled));
|
||||
switchHotswap();
|
||||
}
|
||||
}
|
||||
|
@ -720,10 +810,11 @@ function showDebugMenu() {
|
|||
switchUiMode();
|
||||
applyFontScale('forced');
|
||||
applyThemeColor();
|
||||
applyChatWidth();
|
||||
applyChatWidth('forced');
|
||||
applyAvatarStyle();
|
||||
applyBlurStrength();
|
||||
applyShadowWidth();
|
||||
applyCustomCSS();
|
||||
switchMovingUI();
|
||||
noShadows();
|
||||
switchHotswap();
|
||||
|
@ -732,6 +823,7 @@ switchTimestamps();
|
|||
switchIcons();
|
||||
switchMesIDDisplay();
|
||||
switchTokenCount();
|
||||
switchMessageActions();
|
||||
|
||||
function loadPowerUserSettings(settings, data) {
|
||||
// Load from settings.json
|
||||
|
@ -760,6 +852,8 @@ function loadPowerUserSettings(settings, data) {
|
|||
const timer = localStorage.getItem(storage_keys.timer_enabled);
|
||||
const timestamps = localStorage.getItem(storage_keys.timestamps_enabled);
|
||||
const mesIDDisplay = localStorage.getItem(storage_keys.mesIDDisplay_enabled);
|
||||
const expandMessageActions = localStorage.getItem(storage_keys.expand_message_actions);
|
||||
console.log(expandMessageActions)
|
||||
power_user.fast_ui_mode = fastUi === null ? true : fastUi == "true";
|
||||
power_user.movingUI = movingUI === null ? false : movingUI == "true";
|
||||
power_user.noShadows = noShadows === null ? false : noShadows == "true";
|
||||
|
@ -767,6 +861,8 @@ function loadPowerUserSettings(settings, data) {
|
|||
power_user.timer_enabled = timer === null ? true : timer == "true";
|
||||
power_user.timestamps_enabled = timestamps === null ? true : timestamps == "true";
|
||||
power_user.mesIDDisplay_enabled = mesIDDisplay === null ? true : mesIDDisplay == "true";
|
||||
power_user.expand_message_actions = expandMessageActions === null ? true : expandMessageActions == "true";
|
||||
console.log(power_user.expand_message_actions)
|
||||
power_user.avatar_style = Number(localStorage.getItem(storage_keys.avatar_style) ?? avatar_styles.ROUND);
|
||||
//power_user.chat_display = Number(localStorage.getItem(storage_keys.chat_display) ?? chat_styles.DEFAULT);
|
||||
power_user.chat_width = Number(localStorage.getItem(storage_keys.chat_width) ?? 50);
|
||||
|
@ -803,7 +899,6 @@ function loadPowerUserSettings(settings, data) {
|
|||
$('#fuzzy_search_checkbox').prop("checked", power_user.fuzzy_search);
|
||||
$('#persona_show_notifications').prop("checked", power_user.persona_show_notifications);
|
||||
$('#encode_tags').prop("checked", power_user.encode_tags);
|
||||
$('#lazy_load').val(Number(power_user.lazy_load));
|
||||
|
||||
$("#console_log_prompts").prop("checked", power_user.console_log_prompts);
|
||||
$('#auto_fix_generated_markdown').prop("checked", power_user.auto_fix_generated_markdown);
|
||||
|
@ -828,9 +923,9 @@ function loadPowerUserSettings(settings, data) {
|
|||
$("#noShadowsmode").prop("checked", power_user.noShadows);
|
||||
$("#start_reply_with").val(power_user.user_prompt_bias);
|
||||
$("#chat-show-reply-prefix-checkbox").prop("checked", power_user.show_user_prompt_bias);
|
||||
$("#multigen").prop("checked", power_user.multigen);
|
||||
$("#multigen_first_chunk").val(power_user.multigen_first_chunk);
|
||||
$("#multigen_next_chunks").val(power_user.multigen_next_chunks);
|
||||
$("#auto_continue_enabled").prop("checked", power_user.auto_continue.enabled);
|
||||
$("#auto_continue_allow_chat_completions").prop("checked", power_user.auto_continue.allow_chat_completions);
|
||||
$("#auto_continue_target_length").val(power_user.auto_continue.target_length);
|
||||
$("#play_message_sound").prop("checked", power_user.play_message_sound);
|
||||
$("#play_sound_unfocused").prop("checked", power_user.play_sound_unfocused);
|
||||
$("#never_resize_avatars").prop("checked", power_user.never_resize_avatars);
|
||||
|
@ -863,11 +958,12 @@ function loadPowerUserSettings(settings, data) {
|
|||
$("#main-text-color-picker").attr('color', power_user.main_text_color);
|
||||
$("#italics-color-picker").attr('color', power_user.italics_text_color);
|
||||
$("#quote-color-picker").attr('color', power_user.quote_text_color);
|
||||
//$("#fastui-bg-color-picker").attr('color', power_user.fastui_bg_color);
|
||||
$("#blur-tint-color-picker").attr('color', power_user.blur_tint_color);
|
||||
$("#chat-tint-color-picker").attr('color', power_user.chat_tint_color);
|
||||
$("#user-mes-blur-tint-color-picker").attr('color', power_user.user_mes_blur_tint_color);
|
||||
$("#bot-mes-blur-tint-color-picker").attr('color', power_user.bot_mes_blur_tint_color);
|
||||
$("#shadow-color-picker").attr('color', power_user.shadow_color);
|
||||
$("#border-color-picker").attr('color', power_user.border_color);
|
||||
$("#ui_mode_select").val(power_user.ui_mode).find(`option[value="${power_user.ui_mode}"]`).attr('selected', true);
|
||||
|
||||
for (const theme of themes) {
|
||||
|
@ -1152,15 +1248,15 @@ const compareFunc = (first, second) => {
|
|||
return Math.random() > 0.5 ? 1 : -1;
|
||||
}
|
||||
|
||||
const a = first[power_user.sort_field];
|
||||
const b = second[power_user.sort_field];
|
||||
|
||||
if (power_user.sort_field === 'create_date') {
|
||||
return sortMoments(timestampToMoment(b), timestampToMoment(a));
|
||||
}
|
||||
|
||||
switch (power_user.sort_rule) {
|
||||
case 'boolean':
|
||||
const a = first[power_user.sort_field];
|
||||
const b = second[power_user.sort_field];
|
||||
|
||||
if (power_user.sort_field === 'create_date') {
|
||||
return sortMoments(timestampToMoment(a), timestampToMoment(b));
|
||||
}
|
||||
|
||||
if (a === true || a === 'true') return 1; // Prioritize 'true' or true
|
||||
if (b === true || b === 'true') return -1; // Prioritize 'true' or true
|
||||
if (a && !b) return -1; // Move truthy values to the end
|
||||
|
@ -1168,9 +1264,9 @@ const compareFunc = (first, second) => {
|
|||
if (a === b) return 0; // Sort equal values normally
|
||||
return a < b ? -1 : 1; // Sort non-boolean values normally
|
||||
default:
|
||||
return typeof first[power_user.sort_field] == "string"
|
||||
? first[power_user.sort_field].localeCompare(second[power_user.sort_field])
|
||||
: first[power_user.sort_field] - second[power_user.sort_field];
|
||||
return typeof a == "string"
|
||||
? a.localeCompare(b)
|
||||
: a - b;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1199,12 +1295,13 @@ async function saveTheme() {
|
|||
main_text_color: power_user.main_text_color,
|
||||
italics_text_color: power_user.italics_text_color,
|
||||
quote_text_color: power_user.quote_text_color,
|
||||
//fastui_bg_color: power_user.fastui_bg_color,
|
||||
blur_tint_color: power_user.blur_tint_color,
|
||||
chat_tint_color: power_user.chat_tint_color,
|
||||
user_mes_blur_tint_color: power_user.user_mes_blur_tint_color,
|
||||
bot_mes_blur_tint_color: power_user.bot_mes_blur_tint_color,
|
||||
shadow_color: power_user.shadow_color,
|
||||
shadow_width: power_user.shadow_width,
|
||||
border_color: power_user.border_color,
|
||||
font_scale: power_user.font_scale,
|
||||
fast_ui_mode: power_user.fast_ui_mode,
|
||||
waifuMode: power_user.waifuMode,
|
||||
|
@ -1215,8 +1312,14 @@ async function saveTheme() {
|
|||
timer_enabled: power_user.timer_enabled,
|
||||
timestamps_enabled: power_user.timestamps_enabled,
|
||||
timestamp_model_icon: power_user.timestamp_model_icon,
|
||||
|
||||
mesIDDisplay_enabled: power_user.mesIDDisplay_enabled,
|
||||
message_token_count_enabled: power_user.message_token_count_enabled,
|
||||
expand_message_actions: power_user.expand_message_actions,
|
||||
|
||||
hotswap_enabled: power_user.hotswap_enabled,
|
||||
custom_css: power_user.custom_css,
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
@ -1685,8 +1788,8 @@ export function getCustomStoppingStrings(limit = undefined) {
|
|||
return [];
|
||||
}
|
||||
|
||||
// Make sure all the elements are strings.
|
||||
strings = strings.filter((s) => typeof s === 'string');
|
||||
// Make sure all the elements are strings and non-empty.
|
||||
strings = strings.filter(s => typeof s === 'string' && s.length > 0);
|
||||
|
||||
// Substitute params if necessary
|
||||
if (power_user.custom_stopping_strings_macro) {
|
||||
|
@ -1722,6 +1825,7 @@ $(document).ready(() => {
|
|||
resetMovablePanels('resize');
|
||||
}
|
||||
// Adjust layout and styling here
|
||||
setHotswapsDebounced();
|
||||
});
|
||||
|
||||
// Settings that go to settings.json
|
||||
|
@ -1795,8 +1899,18 @@ $(document).ready(() => {
|
|||
saveSettingsDebounced();
|
||||
})
|
||||
|
||||
$("#multigen").change(function () {
|
||||
power_user.multigen = $(this).prop("checked");
|
||||
$("#auto_continue_enabled").on('change', function () {
|
||||
power_user.auto_continue.enabled = $(this).prop("checked");
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$("#auto_continue_allow_chat_completions").on('change', function () {
|
||||
power_user.auto_continue.allow_chat_completions = !!$(this).prop('checked');
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$("#auto_continue_target_length").on('input', function () {
|
||||
power_user.auto_continue.target_length = Number($(this).val());
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
|
@ -1805,40 +1919,52 @@ $(document).ready(() => {
|
|||
power_user.fast_ui_mode = $(this).prop("checked");
|
||||
localStorage.setItem(storage_keys.fast_ui_mode, power_user.fast_ui_mode);
|
||||
switchUiMode();
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$("#waifuMode").on('change', () => {
|
||||
power_user.waifuMode = $('#waifuMode').prop("checked");
|
||||
saveSettingsDebounced();
|
||||
switchWaifuMode();
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$("#customCSS").on('change', () => {
|
||||
power_user.custom_css = $('#customCSS').val();
|
||||
localStorage.setItem(storage_keys.custom_css, power_user.custom_css);
|
||||
saveSettingsDebounced();
|
||||
applyCustomCSS();
|
||||
});
|
||||
|
||||
$("#movingUImode").change(function () {
|
||||
power_user.movingUI = $(this).prop("checked");
|
||||
localStorage.setItem(storage_keys.movingUI, power_user.movingUI);
|
||||
switchMovingUI();
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$("#noShadowsmode").change(function () {
|
||||
power_user.noShadows = $(this).prop("checked");
|
||||
localStorage.setItem(storage_keys.noShadows, power_user.noShadows);
|
||||
noShadows();
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$("#movingUIreset").on('click', resetMovablePanels);
|
||||
|
||||
$(`input[name="avatar_style"]`).on('input', function (e) {
|
||||
power_user.avatar_style = Number(e.target.value);
|
||||
$("#avatar_style").on('change', function () {
|
||||
const value = $(this).find(':selected').val();
|
||||
power_user.avatar_style = Number(value);
|
||||
localStorage.setItem(storage_keys.avatar_style, power_user.avatar_style);
|
||||
applyAvatarStyle();
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$("#chat_display").on('change', function () {
|
||||
console.debug('###CHAT DISPLAY SELECTOR CHANGE###')
|
||||
const value = $(this).find(':selected').val();
|
||||
power_user.chat_display = Number(value);
|
||||
saveSettingsDebounced();
|
||||
localStorage.setItem(storage_keys.chat_display, power_user.chat_display);
|
||||
applyChatDisplay();
|
||||
saveSettingsDebounced();
|
||||
|
||||
});
|
||||
|
||||
|
@ -1846,6 +1972,7 @@ $(document).ready(() => {
|
|||
power_user.chat_width = Number(e.target.value);
|
||||
localStorage.setItem(storage_keys.chat_width, power_user.chat_width);
|
||||
applyChatWidth();
|
||||
setHotswapsDebounced();
|
||||
});
|
||||
|
||||
$(`input[name="font_scale"]`).on('input', async function (e) {
|
||||
|
@ -1853,6 +1980,7 @@ $(document).ready(() => {
|
|||
$("#font_scale_counter").text(power_user.font_scale);
|
||||
localStorage.setItem(storage_keys.font_scale, power_user.font_scale);
|
||||
await applyFontScale();
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$(`input[name="blur_strength"]`).on('input', async function (e) {
|
||||
|
@ -1860,6 +1988,7 @@ $(document).ready(() => {
|
|||
$("#blur_strength_counter").text(power_user.blur_strength);
|
||||
localStorage.setItem(storage_keys.blur_strength, power_user.blur_strength);
|
||||
await applyBlurStrength();
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$(`input[name="shadow_width"]`).on('input', async function (e) {
|
||||
|
@ -1867,6 +1996,7 @@ $(document).ready(() => {
|
|||
$("#shadow_width_counter").text(power_user.shadow_width);
|
||||
localStorage.setItem(storage_keys.shadow_width, power_user.shadow_width);
|
||||
await applyShadowWidth();
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$("#main-text-color-picker").on('change', (evt) => {
|
||||
|
@ -1893,6 +2023,13 @@ $(document).ready(() => {
|
|||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$("#chat-tint-color-picker").on('change', (evt) => {
|
||||
power_user.chat_tint_color = evt.detail.rgba;
|
||||
applyThemeColor('chatTint');
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
|
||||
$("#user-mes-blur-tint-color-picker").on('change', (evt) => {
|
||||
power_user.user_mes_blur_tint_color = evt.detail.rgba;
|
||||
applyThemeColor('userMesBlurTint');
|
||||
|
@ -1911,6 +2048,12 @@ $(document).ready(() => {
|
|||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$("#border-color-picker").on('change', (evt) => {
|
||||
power_user.border_color = evt.detail.rgba;
|
||||
applyThemeColor('border');
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$("#themes").on('change', function () {
|
||||
const themeSelected = String($(this).find(':selected').val());
|
||||
power_user.theme = themeSelected;
|
||||
|
@ -1964,16 +2107,6 @@ $(document).ready(() => {
|
|||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$("#multigen_first_chunk").on('input', function () {
|
||||
power_user.multigen_first_chunk = Number($(this).val());
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$("#multigen_next_chunks").on('input', function () {
|
||||
power_user.multigen_next_chunks = Number($(this).val());
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#auto_swipe').on('input', function () {
|
||||
power_user.auto_swipe = !!$(this).prop('checked');
|
||||
saveSettingsDebounced();
|
||||
|
@ -2080,42 +2213,49 @@ $(document).ready(() => {
|
|||
$("#messageTimerEnabled").on("input", function () {
|
||||
const value = !!$(this).prop('checked');
|
||||
power_user.timer_enabled = value;
|
||||
localStorage.setItem(storage_keys.timer_enabled, String(power_user.timer_enabled));
|
||||
localStorage.setItem(storage_keys.timer_enabled, Boolean(power_user.timer_enabled));
|
||||
switchTimer();
|
||||
});
|
||||
|
||||
$("#messageTimestampsEnabled").on("input", function () {
|
||||
const value = !!$(this).prop('checked');
|
||||
power_user.timestamps_enabled = value;
|
||||
localStorage.setItem(storage_keys.timestamps_enabled, String(power_user.timestamps_enabled));
|
||||
localStorage.setItem(storage_keys.timestamps_enabled, Boolean(power_user.timestamps_enabled));
|
||||
switchTimestamps();
|
||||
});
|
||||
|
||||
$("#messageModelIconEnabled").on("input", function () {
|
||||
const value = !!$(this).prop('checked');
|
||||
power_user.timestamp_model_icon = value;
|
||||
localStorage.setItem(storage_keys.timestamp_model_icon, String(power_user.timestamp_model_icon));
|
||||
localStorage.setItem(storage_keys.timestamp_model_icon, Boolean(power_user.timestamp_model_icon));
|
||||
switchIcons();
|
||||
});
|
||||
|
||||
$("#messageTokensEnabled").on("input", function () {
|
||||
const value = !!$(this).prop('checked');
|
||||
power_user.message_token_count_enabled = value;
|
||||
localStorage.setItem(storage_keys.message_token_count_enabled, String(power_user.message_token_count_enabled));
|
||||
localStorage.setItem(storage_keys.message_token_count_enabled, Boolean(power_user.message_token_count_enabled));
|
||||
switchTokenCount();
|
||||
});
|
||||
|
||||
$("#expandMessageActions").on("input", function () {
|
||||
const value = !!$(this).prop('checked');
|
||||
power_user.expand_message_actions = value;
|
||||
localStorage.setItem(storage_keys.expand_message_actions, Boolean(power_user.expand_message_actions));
|
||||
switchMessageActions();
|
||||
});
|
||||
|
||||
$("#mesIDDisplayEnabled").on("input", function () {
|
||||
const value = !!$(this).prop('checked');
|
||||
power_user.mesIDDisplay_enabled = value;
|
||||
localStorage.setItem(storage_keys.mesIDDisplay_enabled, String(power_user.mesIDDisplay_enabled));
|
||||
localStorage.setItem(storage_keys.mesIDDisplay_enabled, Boolean(power_user.mesIDDisplay_enabled));
|
||||
switchMesIDDisplay();
|
||||
});
|
||||
|
||||
$("#hotswapEnabled").on("input", function () {
|
||||
const value = !!$(this).prop('checked');
|
||||
power_user.hotswap_enabled = value;
|
||||
localStorage.setItem(storage_keys.hotswap_enabled, String(power_user.hotswap_enabled));
|
||||
localStorage.setItem(storage_keys.hotswap_enabled, Boolean(power_user.hotswap_enabled));
|
||||
switchHotswap();
|
||||
});
|
||||
|
||||
|
@ -2193,11 +2333,6 @@ $(document).ready(() => {
|
|||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#lazy_load').on('input', function () {
|
||||
power_user.lazy_load = Number($(this).val());
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#disable_group_trimming').on('input', function () {
|
||||
power_user.disable_group_trimming = !!$(this).prop('checked');
|
||||
saveSettingsDebounced();
|
||||
|
|
|
@ -133,7 +133,7 @@ class PresetManager {
|
|||
async savePreset(name, settings) {
|
||||
const preset = settings ?? this.getPresetSettings(name);
|
||||
|
||||
const res = await fetch(`/save_preset`, {
|
||||
const res = await fetch(`/api/presets/save`, {
|
||||
method: "POST",
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({ preset, name, apiId: this.apiId })
|
||||
|
@ -303,7 +303,7 @@ class PresetManager {
|
|||
$(this.select).trigger('change');
|
||||
}
|
||||
|
||||
const response = await fetch('/delete_preset', {
|
||||
const response = await fetch('/api/presets/delete', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({ name: nameToDelete, apiId: this.apiId }),
|
||||
|
|
|
@ -73,7 +73,7 @@ export let secret_state = {};
|
|||
|
||||
export async function writeSecret(key, value) {
|
||||
try {
|
||||
const response = await fetch('/writesecret', {
|
||||
const response = await fetch('/api/secrets/write', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({ key, value }),
|
||||
|
@ -94,7 +94,7 @@ export async function writeSecret(key, value) {
|
|||
|
||||
export async function readSecretState() {
|
||||
try {
|
||||
const response = await fetch('/readsecretstate', {
|
||||
const response = await fetch('/api/secrets/read', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
});
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
import { saveSettingsDebounced } from "../script.js";
|
||||
import { power_user } from "./power-user.js";
|
||||
import { isUrlOrAPIKey } from "./utils.js";
|
||||
|
||||
/**
|
||||
* @param {{ term: string; }} request
|
||||
* @param {function} resolve
|
||||
* @param {string} serverLabel
|
||||
*/
|
||||
function findServers(request, resolve, serverLabel) {
|
||||
if (!power_user.servers) {
|
||||
power_user.servers = [];
|
||||
}
|
||||
|
||||
const needle = request.term.toLowerCase();
|
||||
const result = power_user.servers.filter(x => x.label == serverLabel).sort((a, b) => b.lastConnection - a.lastConnection).map(x => x.url).slice(0, 5);
|
||||
const hasExactMatch = result.findIndex(x => x.toLowerCase() == needle) !== -1;
|
||||
|
||||
if (request.term && !hasExactMatch) {
|
||||
result.unshift(request.term);
|
||||
}
|
||||
|
||||
resolve(result);
|
||||
}
|
||||
|
||||
function selectServer(event, ui, serverLabel) {
|
||||
// unfocus the input
|
||||
$(event.target).val(ui.item.value).trigger('input').trigger('blur');
|
||||
|
||||
$('[data-server-connect]').each(function () {
|
||||
const serverLabels = String($(this).data('server-connect')).split(',');
|
||||
|
||||
if (serverLabels.includes(serverLabel)) {
|
||||
$(this).trigger('click');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function createServerAutocomplete() {
|
||||
const inputElement = $(this);
|
||||
const serverLabel = inputElement.data('server-history');
|
||||
|
||||
inputElement
|
||||
.autocomplete({
|
||||
source: (i, o) => findServers(i, o, serverLabel),
|
||||
select: (e, u) => selectServer(e, u, serverLabel),
|
||||
minLength: 0,
|
||||
})
|
||||
.focus(onInputFocus); // <== show tag list on click
|
||||
}
|
||||
|
||||
function onInputFocus() {
|
||||
$(this).autocomplete('search', $(this).val());
|
||||
}
|
||||
|
||||
function onServerConnectClick() {
|
||||
const serverLabels = String($(this).data('server-connect')).split(',');
|
||||
|
||||
serverLabels.forEach(serverLabel => {
|
||||
if (!power_user.servers) {
|
||||
power_user.servers = [];
|
||||
}
|
||||
|
||||
const value = String($(`[data-server-history="${serverLabel}"]`).val()).toLowerCase().trim();
|
||||
|
||||
// Don't save empty values or invalid URLs
|
||||
if (!value || !isUrlOrAPIKey(value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const server = power_user.servers.find(x => x.url === value && x.label === serverLabel);
|
||||
|
||||
if (!server) {
|
||||
power_user.servers.push({ label: serverLabel, url: value, lastConnection: Date.now() });
|
||||
} else {
|
||||
server.lastConnection = Date.now();
|
||||
}
|
||||
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
}
|
||||
|
||||
jQuery(function () {
|
||||
$('[data-server-history]').each(createServerAutocomplete);
|
||||
$(document).on('click', '[data-server-connect]', onServerConnectClick);
|
||||
});
|
|
@ -20,12 +20,14 @@ import {
|
|||
generateQuietPrompt,
|
||||
reloadCurrentChat,
|
||||
sendMessageAsUser,
|
||||
name1,
|
||||
} from "../script.js";
|
||||
import { getMessageTimeStamp } from "./RossAscends-mods.js";
|
||||
import { resetSelectedGroup } from "./group-chats.js";
|
||||
import { getRegexedString, regex_placement } from "./extensions/regex/engine.js";
|
||||
import { chat_styles, power_user } from "./power-user.js";
|
||||
import { autoSelectPersona } from "./personas.js";
|
||||
import { getContext } from "./extensions.js";
|
||||
export {
|
||||
executeSlashCommands,
|
||||
registerSlashCommand,
|
||||
|
@ -225,7 +227,7 @@ function continueChatCallback() {
|
|||
$('#option_continue').trigger('click', { fromSlashCommand: true });
|
||||
}
|
||||
|
||||
async function generateSystemMessage(_, prompt) {
|
||||
export async function generateSystemMessage(_, prompt) {
|
||||
$('#send_textarea').val('');
|
||||
|
||||
if (!prompt) {
|
||||
|
@ -289,7 +291,7 @@ async function setNarratorName(_, text) {
|
|||
await saveChatConditional();
|
||||
}
|
||||
|
||||
async function sendMessageAs(_, text) {
|
||||
export async function sendMessageAs(_, text) {
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
|
@ -325,7 +327,6 @@ async function sendMessageAs(_, text) {
|
|||
const message = {
|
||||
name: name,
|
||||
is_user: false,
|
||||
is_name: true,
|
||||
is_system: isSystem,
|
||||
send_date: getMessageTimeStamp(),
|
||||
mes: substituteParams(mesText),
|
||||
|
@ -344,7 +345,7 @@ async function sendMessageAs(_, text) {
|
|||
await saveChatConditional();
|
||||
}
|
||||
|
||||
async function sendNarratorMessage(_, text) {
|
||||
export async function sendNarratorMessage(_, text) {
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
|
@ -357,7 +358,6 @@ async function sendNarratorMessage(_, text) {
|
|||
const message = {
|
||||
name: name,
|
||||
is_user: false,
|
||||
is_name: false,
|
||||
is_system: isSystem,
|
||||
send_date: getMessageTimeStamp(),
|
||||
mes: substituteParams(text.trim()),
|
||||
|
@ -376,6 +376,45 @@ async function sendNarratorMessage(_, text) {
|
|||
await saveChatConditional();
|
||||
}
|
||||
|
||||
export async function promptQuietForLoudResponse(who, text) {
|
||||
|
||||
let character_id = getContext().characterId;
|
||||
if (who === 'sys') {
|
||||
text = "System: " + text;
|
||||
} else if (who === 'user') {
|
||||
text = name1 + ": " + text;
|
||||
} else if (who === 'char') {
|
||||
text = characters[character_id].name + ": " + text;
|
||||
} else if (who === 'raw') {
|
||||
text = text;
|
||||
}
|
||||
|
||||
//text = `${text}${power_user.instruct.enabled ? '' : '\n'}${(power_user.always_force_name2 && who != 'raw') ? characters[character_id].name + ":" : ""}`
|
||||
|
||||
let reply = await generateQuietPrompt(text, true);
|
||||
text = await getRegexedString(reply, regex_placement.SLASH_COMMAND);
|
||||
|
||||
const message = {
|
||||
name: characters[character_id].name,
|
||||
is_user: false,
|
||||
is_name: true,
|
||||
is_system: false,
|
||||
send_date: getMessageTimeStamp(),
|
||||
mes: substituteParams(text.trim()),
|
||||
extra: {
|
||||
type: system_message_types.COMMENT,
|
||||
gen_id: Date.now(),
|
||||
},
|
||||
};
|
||||
|
||||
chat.push(message);
|
||||
await eventSource.emit(event_types.MESSAGE_SENT, (chat.length - 1));
|
||||
addOneMessage(message);
|
||||
await eventSource.emit(event_types.USER_MESSAGE_RENDERED, (chat.length - 1));
|
||||
await saveChatConditional();
|
||||
|
||||
}
|
||||
|
||||
async function sendCommentMessage(_, text) {
|
||||
if (!text) {
|
||||
return;
|
||||
|
@ -384,7 +423,6 @@ async function sendCommentMessage(_, text) {
|
|||
const message = {
|
||||
name: COMMENT_NAME_DEFAULT,
|
||||
is_user: false,
|
||||
is_name: true,
|
||||
is_system: true,
|
||||
send_date: getMessageTimeStamp(),
|
||||
mes: substituteParams(text.trim()),
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// statsHelper.js
|
||||
import { getRequestHeaders, callPopup, characters, this_chid } from "../script.js";
|
||||
import { humanizeGenTime } from "./RossAscends-mods.js";
|
||||
import {registerDebugFunction,} from "./power-user.js";
|
||||
|
||||
let charStats = {};
|
||||
|
||||
|
@ -191,6 +192,32 @@ async function getStats() {
|
|||
charStats = await response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously recreates the stats file from chat files.
|
||||
*
|
||||
* Sends a POST request to the "/recreatestats" endpoint. If the request fails,
|
||||
* it displays an error notification and throws an error.
|
||||
*
|
||||
* @throws {Error} If the request to recreate stats is unsuccessful.
|
||||
*/
|
||||
async function recreateStats() {
|
||||
const response = await fetch("/recreatestats", {
|
||||
method: "POST",
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({}),
|
||||
cache: "no-cache",
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
toastr.error("Stats could not be loaded. Try reloading the page.");
|
||||
throw new Error("Error getting stats");
|
||||
}
|
||||
else {
|
||||
toastr.success("Stats file recreated successfully!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculates the generation time based on start and finish times.
|
||||
*
|
||||
|
@ -304,9 +331,22 @@ async function statMesProcess(line, type, characters, this_chid, oldMesssage) {
|
|||
}
|
||||
|
||||
jQuery(() => {
|
||||
$(".rm_stats_button").on('click', function () {
|
||||
characterStatsHandler(characters, this_chid);
|
||||
});
|
||||
})
|
||||
function init() {
|
||||
$(".rm_stats_button").on('click', function () {
|
||||
characterStatsHandler(characters, this_chid);
|
||||
});
|
||||
// Wait for debug functions to load, then add the refresh stats function
|
||||
registerDebugFunction('refreshStats', 'Refresh Stat File', 'Recreates the stats file based on existing chat files', recreateStats);
|
||||
}
|
||||
|
||||
// Check every 100ms if registerDebugFunction is defined (this is bad lmao)
|
||||
const interval = setInterval(() => {
|
||||
if (typeof registerDebugFunction !== 'undefined') {
|
||||
clearInterval(interval); // Clear the interval once the function is found
|
||||
init(); // Initialize your code
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
|
||||
|
||||
export { userStatsHandler, characterStatsHandler, getStats, statMesProcess, charStats };
|
||||
|
|
|
@ -192,7 +192,7 @@ function selectTag(event, ui, listSelector) {
|
|||
}
|
||||
|
||||
// unfocus and clear the input
|
||||
$(event.target).val("").trigger('blur');
|
||||
$(event.target).val("").trigger('input');
|
||||
|
||||
// add tag to the UI and internal map
|
||||
appendTagToList(listSelector, tag, { removable: true });
|
||||
|
@ -263,6 +263,7 @@ function createNewTag(tagName) {
|
|||
id: uuidv4(),
|
||||
name: tagName,
|
||||
color: '',
|
||||
color2: '',
|
||||
};
|
||||
tags.push(tag);
|
||||
return tag;
|
||||
|
@ -273,12 +274,12 @@ function appendTagToList(listElement, tag, { removable, selectable, action, isGe
|
|||
return;
|
||||
}
|
||||
|
||||
|
||||
let tagElement = $('#tag_template .tag').clone();
|
||||
tagElement.attr('id', tag.id);
|
||||
|
||||
tagElement.css('color', 'var(--SmartThemeBodyColor)');
|
||||
//tagElement.css('color', 'var(--SmartThemeBodyColor)');
|
||||
tagElement.css('background-color', tag.color);
|
||||
tagElement.css('color', tag.color2);
|
||||
|
||||
tagElement.find('.tag_name').text(tag.name);
|
||||
const removeButton = tagElement.find(".tag_remove");
|
||||
|
@ -462,22 +463,40 @@ function onViewTagsListClick() {
|
|||
template.find('.tag_view_counter_value').text(count);
|
||||
template.find('.tag_view_name').text(tag.name);
|
||||
template.find('.tag_view_name').addClass('tag');
|
||||
|
||||
template.find('.tag_view_name').css('background-color', tag.color);
|
||||
template.find('.tag_view_name').css('color', tag.color2);
|
||||
|
||||
const colorPickerId = tag.id + "-tag-color";
|
||||
const colorPicker2Id = tag.id + "-tag-color2";
|
||||
|
||||
template.find('.tagColorPickerHolder').html(
|
||||
`<toolcool-color-picker id="${colorPickerId}" color="${tag.color}" class="tag-color"></toolcool-color-picker>`
|
||||
);
|
||||
template.find('.tagColorPicker2Holder').html(
|
||||
`<toolcool-color-picker id="${colorPicker2Id}" color="${tag.color2}" class="tag-color2"></toolcool-color-picker>`
|
||||
);
|
||||
|
||||
template.find('.tag-color').attr('id', colorPickerId);
|
||||
template.find('.tag-color2').attr('id', colorPicker2Id);
|
||||
|
||||
list.appendChild(template.get(0));
|
||||
|
||||
setTimeout(function () {
|
||||
document.querySelector(`.tag-color[id="${colorPickerId}"`).addEventListener('change', (evt) => {
|
||||
onTagColorize(evt);
|
||||
});
|
||||
|
||||
}, 100);
|
||||
|
||||
setTimeout(function () {
|
||||
document.querySelector(`.tag-color2[id="${colorPicker2Id}"`).addEventListener('change', (evt) => {
|
||||
onTagColorize2(evt);
|
||||
});
|
||||
}, 100);
|
||||
|
||||
$(colorPickerId).color = tag.color;
|
||||
$(colorPicker2Id).color = tag.color2;
|
||||
|
||||
}
|
||||
callPopup(list.outerHTML, 'text');
|
||||
|
@ -520,6 +539,18 @@ function onTagColorize(evt) {
|
|||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onTagColorize2(evt) {
|
||||
console.debug(evt);
|
||||
const id = $(evt.target).closest('.tag_view_item').attr('id');
|
||||
const newColor = evt.detail.rgba;
|
||||
$(evt.target).parent().parent().find('.tag_view_name').css('color', newColor);
|
||||
$(`.tag[id="${id}"]`).css('color', newColor);
|
||||
const tag = tags.find(x => x.id === id);
|
||||
tag.color2 = newColor;
|
||||
console.debug(tag);
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onTagListHintClick() {
|
||||
console.log($(this));
|
||||
$(this).toggleClass('selected');
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<h3 class="flex-container justifyCenter alignitemscenter">
|
||||
Prompt Itemization
|
||||
<div id="showRawPrompt" class="fa-solid fa-square-poll-horizontal menu_button"></div>
|
||||
<div id="copyPromptToClipboard" class="fa-solid fa-copy menu_button"></div>
|
||||
</h3>
|
||||
Tokenizer: {{selectedTokenizer}}<br>
|
||||
API Used: {{this_main_api}}<br>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<h3 class="flex-container justifyCenter alignitemscenter">
|
||||
Prompt Itemization
|
||||
<div id="showRawPrompt" class="fa-solid fa-square-poll-horizontal menu_button"></div>
|
||||
<div id="copyPromptToClipboard" class="fa-solid fa-copy menu_button"></div>
|
||||
</h3>
|
||||
Tokenizer: {{selectedTokenizer}}<br>
|
||||
API Used: {{this_main_api}}<br>
|
||||
|
|
|
@ -101,13 +101,13 @@ function callTokenizer(type, str, padding) {
|
|||
case tokenizers.NONE:
|
||||
return guesstimate(str) + padding;
|
||||
case tokenizers.GPT2:
|
||||
return countTokensRemote('/tokenize_gpt2', str, padding);
|
||||
return countTokensRemote('/api/tokenize/gpt2', str, padding);
|
||||
case tokenizers.LLAMA:
|
||||
return countTokensRemote('/tokenize_llama', str, padding);
|
||||
return countTokensRemote('/api/tokenize/llama', str, padding);
|
||||
case tokenizers.NERD:
|
||||
return countTokensRemote('/tokenize_nerdstash', str, padding);
|
||||
return countTokensRemote('/api/tokenize/nerdstash', str, padding);
|
||||
case tokenizers.NERD2:
|
||||
return countTokensRemote('/tokenize_nerdstash_v2', str, padding);
|
||||
return countTokensRemote('/api/tokenize/nerdstash_v2', str, padding);
|
||||
case tokenizers.API:
|
||||
return countTokensRemote('/tokenize_via_api', str, padding);
|
||||
default:
|
||||
|
@ -264,7 +264,7 @@ export function countTokensOpenAI(messages, full = false) {
|
|||
jQuery.ajax({
|
||||
async: false,
|
||||
type: 'POST', //
|
||||
url: shouldTokenizeAI21 ? '/tokenize_ai21' : `/tokenize_openai?model=${model}`,
|
||||
url: shouldTokenizeAI21 ? '/api/tokenize/ai21' : `/api/tokenize/openai?model=${model}`,
|
||||
data: JSON.stringify([message]),
|
||||
dataType: "json",
|
||||
contentType: "application/json",
|
||||
|
@ -398,13 +398,13 @@ function decodeTextTokensRemote(endpoint, ids) {
|
|||
export function getTextTokens(tokenizerType, str) {
|
||||
switch (tokenizerType) {
|
||||
case tokenizers.GPT2:
|
||||
return getTextTokensRemote('/tokenize_gpt2', str);
|
||||
return getTextTokensRemote('/api/tokenize/gpt2', str);
|
||||
case tokenizers.LLAMA:
|
||||
return getTextTokensRemote('/tokenize_llama', str);
|
||||
return getTextTokensRemote('/api/tokenize/llama', str);
|
||||
case tokenizers.NERD:
|
||||
return getTextTokensRemote('/tokenize_nerdstash', str);
|
||||
return getTextTokensRemote('/api/tokenize/nerdstash', str);
|
||||
case tokenizers.NERD2:
|
||||
return getTextTokensRemote('/tokenize_nerdstash_v2', str);
|
||||
return getTextTokensRemote('/api/tokenize/nerdstash_v2', str);
|
||||
default:
|
||||
console.warn("Calling getTextTokens with unsupported tokenizer type", tokenizerType);
|
||||
return [];
|
||||
|
@ -413,19 +413,19 @@ export function getTextTokens(tokenizerType, str) {
|
|||
|
||||
/**
|
||||
* Decodes token ids to text using the remote server API.
|
||||
* @param {any} tokenizerType Tokenizer type.
|
||||
* @param {number} tokenizerType Tokenizer type.
|
||||
* @param {number[]} ids Array of token ids
|
||||
*/
|
||||
export function decodeTextTokens(tokenizerType, ids) {
|
||||
switch (tokenizerType) {
|
||||
case tokenizers.GPT2:
|
||||
return decodeTextTokensRemote('/decode_gpt2', ids);
|
||||
return decodeTextTokensRemote('/api/decode/gpt2', ids);
|
||||
case tokenizers.LLAMA:
|
||||
return decodeTextTokensRemote('/decode_llama', ids);
|
||||
return decodeTextTokensRemote('/api/decode/llama', ids);
|
||||
case tokenizers.NERD:
|
||||
return decodeTextTokensRemote('/decode_nerdstash', ids);
|
||||
return decodeTextTokensRemote('/api/decode/nerdstash', ids);
|
||||
case tokenizers.NERD2:
|
||||
return decodeTextTokensRemote('/decode_nerdstash_v2', ids);
|
||||
return decodeTextTokensRemote('/api/decode/nerdstash_v2', ids);
|
||||
default:
|
||||
console.warn("Calling decodeTextTokens with unsupported tokenizer type", tokenizerType);
|
||||
return '';
|
||||
|
|
|
@ -18,6 +18,15 @@ export function escapeHtml(str) {
|
|||
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
}
|
||||
|
||||
export function isUrlOrAPIKey(value) {
|
||||
try {
|
||||
new URL(value);
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a value is unique in an array.
|
||||
* @param {any} value Current value.
|
||||
|
@ -429,9 +438,9 @@ export function sortByCssOrder(a, b) {
|
|||
* @param {boolean} include_newline Whether to include a newline character in the trimmed string.
|
||||
* @returns {string} The trimmed string.
|
||||
* @example
|
||||
* end_trim_to_sentence('Hello, world! I am from'); // 'Hello, world!'
|
||||
* trimToEndSentence('Hello, world! I am from'); // 'Hello, world!'
|
||||
*/
|
||||
export function end_trim_to_sentence(input, include_newline = false) {
|
||||
export function trimToEndSentence(input, include_newline = false) {
|
||||
const punctuation = new Set(['.', '!', '?', '*', '"', ')', '}', '`', ']', '$', '。', '!', '?', '”', ')', '】', '】', '’', '」', '】']); // extend this as you see fit
|
||||
let last = -1;
|
||||
|
||||
|
@ -456,6 +465,26 @@ export function end_trim_to_sentence(input, include_newline = false) {
|
|||
return input.substring(0, last + 1).trimEnd();
|
||||
}
|
||||
|
||||
export function trimToStartSentence(input) {
|
||||
let p1 = input.indexOf(".");
|
||||
let p2 = input.indexOf("!");
|
||||
let p3 = input.indexOf("?");
|
||||
let p4 = input.indexOf("\n");
|
||||
let first = p1;
|
||||
let skip1 = false;
|
||||
if (p2 > 0 && p2 < first) { first = p2; }
|
||||
if (p3 > 0 && p3 < first) { first = p3; }
|
||||
if (p4 > 0 && p4 < first) { first = p4; skip1 = true; }
|
||||
if (first > 0) {
|
||||
if (skip1) {
|
||||
return input.substring(first + 1);
|
||||
} else {
|
||||
return input.substring(first + 2);
|
||||
}
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the number of occurrences of a character in a string.
|
||||
* @param {string} string The string to count occurrences in.
|
||||
|
@ -850,7 +879,7 @@ export async function saveBase64AsFile(base64Data, characterName, filename = "",
|
|||
|
||||
/**
|
||||
* Loads either a CSS or JS file and appends it to the appropriate document section.
|
||||
*
|
||||
*
|
||||
* @param {string} url - The URL of the file to be loaded.
|
||||
* @param {string} type - The type of file to load: "css" or "js".
|
||||
* @returns {Promise} - Resolves when the file has loaded, rejects if there's an error or invalid type.
|
||||
|
|
|
@ -71,6 +71,7 @@ const world_info_position = {
|
|||
after: 1,
|
||||
ANTop: 2,
|
||||
ANBottom: 3,
|
||||
atDepth: 4,
|
||||
|
||||
};
|
||||
|
||||
|
@ -268,10 +269,9 @@ function displayWorldEntries(name, data, navigation = navigation_option.none) {
|
|||
const storageKey = 'WI_PerPage';
|
||||
$("#world_info_pagination").pagination({
|
||||
dataSource: getDataArray,
|
||||
pageSize: 25,
|
||||
//pageSize: Number(localStorage.getItem(storageKey)) || 25,
|
||||
//sizeChangerOptions: [10, 25, 50, 100],
|
||||
//showSizeChanger: true,
|
||||
pageSize: Number(localStorage.getItem(storageKey)) || 25,
|
||||
sizeChangerOptions: [10, 25, 50, 100],
|
||||
showSizeChanger: true,
|
||||
pageRange: 1,
|
||||
pageNumber: startPage,
|
||||
position: 'top',
|
||||
|
@ -659,6 +659,9 @@ function getWorldEntry(name, data, entry) {
|
|||
const uid = $(this).data("uid");
|
||||
const value = Number($(this).val());
|
||||
data.entries[uid].position = !isNaN(value) ? value : 0;
|
||||
if (value === 4) {
|
||||
template.find('label[for="order"').text('Depth:')
|
||||
} else { template.find('label[for="order"').text('Order:') }
|
||||
// Spec v2 only supports before_char and after_char
|
||||
setOriginalDataValue(data, uid, "position", data.entries[uid].position == 0 ? 'before_char' : 'after_char');
|
||||
// Write the original value as extensions field
|
||||
|
@ -1155,6 +1158,13 @@ async function checkWorldInfo(chat, maxContext) {
|
|||
case world_info_position.ANBottom:
|
||||
ANBottomEntries.unshift(entry.content);
|
||||
break;
|
||||
case world_info_position.atDepth:
|
||||
//inserted one by one, unrelated to any array of items
|
||||
//must have a unique value for 'key' argument
|
||||
//uses the order input to specify depth
|
||||
var randomNumber = Math.floor(Math.random() * 99999) + 1;
|
||||
context.setExtensionPrompt(`customDepthWI-${entry.keywords}-${entry.uid}-${randomNumber}`, entry.content, 1, entry.order);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
148
public/style.css
148
public/style.css
|
@ -47,10 +47,12 @@
|
|||
--SmartThemeQuoteColor: rgb(225, 138, 36);
|
||||
/* --SmartThemeFastUIBGColor: rgba(0, 0, 0, 0.9); */
|
||||
--SmartThemeBlurTintColor: rgba(23, 23, 23, 1);
|
||||
--SmartThemeChatTintColor: rgba(23, 23, 23, 1);
|
||||
--SmartThemeUserMesBlurTintColor: rgba(0, 0, 0, 0.3);
|
||||
--SmartThemeBotMesBlurTintColor: rgba(60, 60, 60, 0.3);
|
||||
--SmartThemeBlurStrength: calc(var(--blurStrength) * 1px);
|
||||
--SmartThemeShadowColor: rgba(0, 0, 0, 0.5);
|
||||
--SmartThemeBorderColor: rgba(0, 0, 0, 0.5);
|
||||
|
||||
|
||||
--sheldWidth: 50vw;
|
||||
|
@ -195,7 +197,7 @@ table.responsiveTable {
|
|||
|
||||
.tokenGraph {
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--white30a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
max-height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
@ -335,7 +337,7 @@ code {
|
|||
font-family: Consolas, monospace;
|
||||
white-space: pre-wrap;
|
||||
/* word-wrap: break-word; */
|
||||
border: 1px solid var(--white30a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
border-radius: 5px;
|
||||
background-color: var(--black70a);
|
||||
padding: 0 3px;
|
||||
|
@ -346,7 +348,7 @@ code {
|
|||
|
||||
|
||||
hr {
|
||||
background-image: linear-gradient(90deg, var(--transparent), var(--white30a), var(--transparent));
|
||||
background-image: linear-gradient(90deg, var(--transparent), var(--SmartThemeBorderColor), var(--transparent));
|
||||
margin: 5px 0;
|
||||
height: 1px;
|
||||
min-height: 1px;
|
||||
|
@ -389,7 +391,7 @@ hr {
|
|||
display: inline-block;
|
||||
height: var(--bottomFormBlockSize);
|
||||
position: absolute;
|
||||
border-bottom: 1px solid var(--grey30a);
|
||||
border-bottom: 1px solid var(--SmartThemeBorderColor);
|
||||
box-shadow: 0 2px 20px 0 var(--black70a);
|
||||
backdrop-filter: blur(var(--SmartThemeBlurStrength));
|
||||
background-color: var(--SmartThemeBlurTintColor);
|
||||
|
@ -432,7 +434,7 @@ hr {
|
|||
top: 0px;
|
||||
opacity: 0.5;
|
||||
cursor: grab;
|
||||
border: 1px solid var(--white30a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
cursor: -moz-grab;
|
||||
cursor: -webkit-grab;
|
||||
display: none;
|
||||
|
@ -480,11 +482,11 @@ hr {
|
|||
overflow-y: scroll;
|
||||
display: flex;
|
||||
bottom: 10px;
|
||||
border-bottom: 1px solid var(--grey30a);
|
||||
border-left: 1px solid var(--grey30a);
|
||||
border-right: 1px solid var(--grey30a);
|
||||
border-bottom: 1px solid var(--SmartThemeBorderColor);
|
||||
border-left: 1px solid var(--SmartThemeBorderColor);
|
||||
border-right: 1px solid var(--SmartThemeBorderColor);
|
||||
backdrop-filter: blur(var(--SmartThemeBlurStrength));
|
||||
background-color: var(--SmartThemeBlurTintColor);
|
||||
background-color: var(--SmartThemeChatTintColor);
|
||||
-webkit-backdrop-filter: blur(var(--SmartThemeBlurStrength));
|
||||
text-shadow: 0px 0px calc(var(--shadowWidth) * 1px) var(--SmartThemeShadowColor);
|
||||
scrollbar-width: thin;
|
||||
|
@ -512,7 +514,7 @@ hr {
|
|||
align-items: center;
|
||||
width: 100%;
|
||||
margin: 0 auto 0 auto;
|
||||
border: 1px solid var(--grey30a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
border-radius: 0 0 10px 10px;
|
||||
background-color: var(--SmartThemeBlurTintColor);
|
||||
backdrop-filter: blur(var(--SmartThemeBlurStrength));
|
||||
|
@ -545,6 +547,7 @@ hr {
|
|||
cursor: pointer;
|
||||
transition: 0.3s;
|
||||
opacity: 0.7;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
@ -563,7 +566,7 @@ hr {
|
|||
order: 99998;
|
||||
}
|
||||
|
||||
.mes_stop {
|
||||
#send_but_sheld .mes_stop {
|
||||
display: none;
|
||||
order: 99997;
|
||||
}
|
||||
|
@ -608,7 +611,7 @@ hr {
|
|||
.list-group {
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
border: 1px solid var(--white30a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
border-radius: 10px;
|
||||
z-index: 2000;
|
||||
font-size: calc(var(--mainFontSize) * 1.1);
|
||||
|
@ -627,7 +630,7 @@ hr {
|
|||
.options-content hr {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border-top: 1px solid var(--white30a);
|
||||
border-top: 1px solid var(--SmartThemeBorderColor);
|
||||
background: none;
|
||||
}
|
||||
|
||||
|
@ -789,6 +792,10 @@ hr {
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
#HotSwapWrapper .hotswap {
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
.hotswapAvatar,
|
||||
.hotswapAvatar .avatar {
|
||||
width: 50px !important;
|
||||
|
@ -823,7 +830,7 @@ hr {
|
|||
height: 100%;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
border: 1px solid var(--black30a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
}
|
||||
|
||||
.hotswapAvatar .avatar {
|
||||
|
@ -842,7 +849,8 @@ hr {
|
|||
object-fit: cover;
|
||||
object-position: center center;
|
||||
border-radius: 50%;
|
||||
border: 1px solid var(--black30a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
/*--black30a*/
|
||||
box-shadow: 0 0 5px var(--black50a);
|
||||
}
|
||||
|
||||
|
@ -877,7 +885,7 @@ textarea {
|
|||
display: block;
|
||||
background-color: var(--black30a);
|
||||
outline: none;
|
||||
border: 1px solid var(--white30a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
border-radius: 7px;
|
||||
color: var(--SmartThemeBodyColor);
|
||||
font-size: var(--mainFontSize);
|
||||
|
@ -967,7 +975,7 @@ select {
|
|||
.text_pole {
|
||||
background-color: var(--black30a);
|
||||
color: var(--SmartThemeBodyColor);
|
||||
border: 1px solid var(--white30a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
border-radius: 7px;
|
||||
font-family: "Noto Sans", "Noto Color Emoji", sans-serif;
|
||||
padding: 3px 5px;
|
||||
|
@ -1172,7 +1180,7 @@ input[type="file"] {
|
|||
#rm_ch_create_block {
|
||||
display: none;
|
||||
overflow-y: auto;
|
||||
padding: 0 5px;
|
||||
padding: 5px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
@ -1189,7 +1197,7 @@ input[type="file"] {
|
|||
min-width: 100px;
|
||||
min-height: 100px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--white30a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
position: fixed;
|
||||
padding: 10px;
|
||||
padding-top: 25px;
|
||||
|
@ -1209,6 +1217,7 @@ input[type="file"] {
|
|||
.radio_group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
#extension_floating_counter {
|
||||
|
@ -1266,7 +1275,7 @@ select {
|
|||
|
||||
padding: 3px 2px;
|
||||
background-color: var(--black30a);
|
||||
border: 1px solid var(--white30a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
border-radius: 7px;
|
||||
margin-bottom: 5px;
|
||||
height: min-content;
|
||||
|
@ -1510,7 +1519,7 @@ input[type=search]:focus::-webkit-search-cancel-button {
|
|||
background-size: cover;
|
||||
background-position: center;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--black50a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
box-shadow: 0 0 7px var(--black50a);
|
||||
margin: 5px;
|
||||
cursor: pointer;
|
||||
|
@ -1668,7 +1677,7 @@ input[type=search]:focus::-webkit-search-cancel-button {
|
|||
border-radius: 5px;
|
||||
font-weight: 500;
|
||||
color: var(--SmartThemeQuoteColor);
|
||||
border: 1px solid var(--white30a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
transition: 0.2s;
|
||||
|
@ -1767,10 +1776,17 @@ grammarly-extension {
|
|||
#result_info {
|
||||
font-size: calc(var(--mainFontSize) * 0.9);
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
#result_info_text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
line-height: 1;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.rm_stats_button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
@ -1791,9 +1807,9 @@ grammarly-extension {
|
|||
transform: translateY(-50%);
|
||||
text-align: center;
|
||||
box-shadow: 0px 0px 14px var(--black70a);
|
||||
border: 1px solid var(--white30a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
padding: 4px;
|
||||
background-color: var(--black50a);
|
||||
background-color: var(--SmartThemeBlurTintColor);
|
||||
border-radius: 10px;
|
||||
max-height: 90vh;
|
||||
max-height: 90svh;
|
||||
|
@ -1878,7 +1894,7 @@ grammarly-extension {
|
|||
.menu_button {
|
||||
color: var(--SmartThemeBodyColor);
|
||||
background-color: var(--black50a);
|
||||
border: 1px solid var(--white30a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
border-radius: 7px;
|
||||
padding: 3px 5px;
|
||||
width: min-content;
|
||||
|
@ -1957,7 +1973,7 @@ grammarly-extension {
|
|||
border-radius: 5px;
|
||||
color: var(--SmartThemeBodyColor);
|
||||
background-color: var(--black30a);
|
||||
border: 1px solid var(--white30a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
cursor: grab;
|
||||
transition: background-color 200ms ease-in-out;
|
||||
position: relative;
|
||||
|
@ -1973,7 +1989,7 @@ grammarly-extension {
|
|||
}
|
||||
|
||||
.prompt_order>div:hover {
|
||||
background-color: var(--grey30a);
|
||||
background-color: var(--SmartThemeBorderColor);
|
||||
}
|
||||
|
||||
.prompt_order>div::after {
|
||||
|
@ -2190,7 +2206,8 @@ input[type='checkbox']:not(#nav-toggle):not(#rm_button_panel_pin):not(#lm_button
|
|||
|
||||
.range-block-counter {
|
||||
margin-left: 5px;
|
||||
margin-right: 15px;
|
||||
/*previously needed to avoid firefox scrollbar overlap, no longer necessary?*/
|
||||
/* margin-right: 15px; */
|
||||
font-size: calc(var(--mainFontSize) * 0.95);
|
||||
color: var(--SmartThemeBodyColor);
|
||||
flex: 1;
|
||||
|
@ -2211,7 +2228,7 @@ input[type='checkbox']:not(#nav-toggle):not(#rm_button_panel_pin):not(#lm_button
|
|||
display: block;
|
||||
cursor: text;
|
||||
background-color: var(--black30a);
|
||||
border: 1px solid var(--white30a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
border-radius: 5px;
|
||||
padding: 2px;
|
||||
flex: 1;
|
||||
|
@ -2235,6 +2252,7 @@ input[type="range"] {
|
|||
background-size: 70% 100%;
|
||||
background-repeat: no-repeat;
|
||||
box-shadow: inset 0 0 2px black;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
input[type="range"]::-webkit-slider-thumb {
|
||||
|
@ -2299,20 +2317,6 @@ input[type="range"]::-webkit-slider-thumb {
|
|||
opacity: 1;
|
||||
}
|
||||
|
||||
#shadow_tips_popup {
|
||||
display: none;
|
||||
opacity: 0.0;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
height: 100svh;
|
||||
z-index: 2055;
|
||||
background-color: var(--black70a);
|
||||
-webkit-backdrop-filter: blur(var(--SmartThemeBlurStrength));
|
||||
backdrop-filter: blur(var(--SmartThemeBlurStrength));
|
||||
}
|
||||
|
||||
|
||||
.mes_buttons,
|
||||
.extraMesButtons {
|
||||
/* height: 20px; */
|
||||
|
@ -2424,18 +2428,18 @@ input[type="range"]::-webkit-slider-thumb {
|
|||
#anchor_checkbox label,
|
||||
#power-user-option-checkboxes label,
|
||||
.checkbox_label,
|
||||
.multigen_settings_block {
|
||||
.auto_continue_settings_block {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
column-gap: 5px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.multigen_settings_block {
|
||||
.auto_continue_settings_block {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.multigen_settings_block label {
|
||||
.auto_continue_settings_block label {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -2477,7 +2481,7 @@ input[type="range"]::-webkit-slider-thumb {
|
|||
top: var(--topBarBlockSize);
|
||||
box-shadow: 0 0 20px var(--black70a);
|
||||
padding: 10px;
|
||||
border: 1px solid var(--black30a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
border-radius: 0 0 10px 10px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
@ -2577,7 +2581,7 @@ h5 {
|
|||
background-color: var(--SmartThemeBlurTintColor);
|
||||
border-radius: 10px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid var(--grey30);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
}
|
||||
|
||||
#export_div {
|
||||
|
@ -2622,7 +2626,7 @@ h5 {
|
|||
.select_chat_block {
|
||||
border-radius: 10px;
|
||||
margin-top: 10px;
|
||||
border: 1px solid var(--white30a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
padding: 10px;
|
||||
display: grid;
|
||||
grid-template-columns: min-content auto;
|
||||
|
@ -2730,7 +2734,7 @@ body .ui-front {
|
|||
|
||||
body .ui-widget-content {
|
||||
background-color: var(--SmartThemeBlurTintColor);
|
||||
border: 1px solid var(--white30a) !important;
|
||||
border: 1px solid var(--SmartThemeBorderColor) !important;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0 5px black;
|
||||
text-shadow: 0px 0px calc(var(--shadowWidth) * 1px) var(--SmartThemeShadowColor);
|
||||
|
@ -2909,7 +2913,7 @@ a {
|
|||
overflow: hidden;
|
||||
background-color: var(--SmartThemeBlurTintColor);
|
||||
backdrop-filter: blur(calc(var(--SmartThemeBlurStrength)*2));
|
||||
border: 1px solid var(--white30a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0 5px black;
|
||||
text-shadow: 0px 0px calc(var(--shadowWidth) * 1px) var(--SmartThemeShadowColor);
|
||||
|
@ -2937,9 +2941,9 @@ a {
|
|||
background-color: var(--SmartThemeBlurTintColor);
|
||||
-webkit-backdrop-filter: blur(calc(var(--SmartThemeBlurStrength)));
|
||||
z-index: 3000;
|
||||
border: 0;
|
||||
border-left: 1px solid var(--grey30a);
|
||||
border-bottom: 1px solid var(--grey30a);
|
||||
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
|
||||
box-shadow: none;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
|
@ -3029,12 +3033,13 @@ a {
|
|||
}
|
||||
|
||||
#extensions_settings .inline-drawer-toggle.inline-drawer-header,
|
||||
#extensions_settings2 .inline-drawer-toggle.inline-drawer-header {
|
||||
#extensions_settings2 .inline-drawer-toggle.inline-drawer-header,
|
||||
#user-settings-block h4 {
|
||||
background-image: linear-gradient(348deg, var(--white30a)2%, var(--grey30a)10%, var(--black70a)95%, var(--SmartThemeQuoteColor)100%);
|
||||
margin-bottom: 5px;
|
||||
border-radius: 10px;
|
||||
padding: 5px;
|
||||
border: 1px solid var(--grey30);
|
||||
padding: 2px 5px;
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
transition: all 250ms;
|
||||
}
|
||||
|
||||
|
@ -3146,7 +3151,7 @@ a {
|
|||
color: var(--SmartThemeBodyColor);
|
||||
border-radius: 10px;
|
||||
padding: 5px;
|
||||
border: 1px solid var(--grey30a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
box-shadow: 0 0 20px black;
|
||||
min-width: 450px;
|
||||
width: var(--sheldWidth);
|
||||
|
@ -3189,7 +3194,7 @@ a {
|
|||
border-radius: 10px;
|
||||
box-shadow: none;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--grey30a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
}
|
||||
|
||||
.scrollableInner {
|
||||
|
@ -3207,7 +3212,7 @@ a {
|
|||
}
|
||||
|
||||
.settingsSectionWrap {
|
||||
border: 1px solid var(--white30a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
border-radius: 10px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
@ -3371,7 +3376,7 @@ a {
|
|||
margin-top: 12px !important;
|
||||
border-radius: 5px;
|
||||
padding: 3px;
|
||||
border: 1px solid var(--white30a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
}
|
||||
|
||||
.neutral_warning {
|
||||
|
@ -3406,7 +3411,8 @@ a {
|
|||
min-height: 100px;
|
||||
max-height: 90vh;
|
||||
max-width: 90vh;
|
||||
width: calc((100vw - var(--sheldWidth)) /2);
|
||||
width: clamp(100px, 400px, calc((100vw - var(--sheldWidth)) /2));
|
||||
/* width: calc((100vw - var(--sheldWidth)) /2); */
|
||||
position: absolute;
|
||||
padding: 0;
|
||||
filter: drop-shadow(2px 2px 2px var(--grey7070a));
|
||||
|
@ -3423,7 +3429,7 @@ a {
|
|||
min-height: 100px;
|
||||
max-height: 90vh;
|
||||
max-width: 90vh;
|
||||
width: calc((100vw - var(--sheldWidth)) /2);
|
||||
width: clamp(100px, 400px, calc((100vw - var(--sheldWidth)) /2));
|
||||
position: absolute;
|
||||
padding: 0;
|
||||
filter: drop-shadow(2px 2px 2px var(--grey7070a));
|
||||
|
@ -3473,7 +3479,6 @@ a {
|
|||
}
|
||||
|
||||
.zoomed_avatar img {
|
||||
/* border: 1px solid var(--white50a); */
|
||||
border-radius: 20px;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
@ -3533,7 +3538,7 @@ a {
|
|||
padding: 0.05em 0.5em;
|
||||
text-decoration: none;
|
||||
color: var(--SmartThemeBodyColor);
|
||||
border: 1px solid var(--white30a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
border-radius: 5px;
|
||||
transition: opacity 0.2s;
|
||||
opacity: 0.8;
|
||||
|
@ -3572,6 +3577,15 @@ a {
|
|||
align-self: center;
|
||||
}
|
||||
|
||||
#show_more_messages {
|
||||
text-align: center;
|
||||
margin: 10px 0;
|
||||
font-weight: 500;
|
||||
text-decoration: underline;
|
||||
order: -1;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#select_chat_search {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
|
@ -3609,4 +3623,4 @@ a {
|
|||
height: 100vh;
|
||||
z-index: 9999;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,250 @@
|
|||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const sanitize = require('sanitize-filename');
|
||||
const fetch = require('node-fetch').default;
|
||||
const { finished } = require('stream/promises');
|
||||
const { DIRECTORIES, UNSAFE_EXTENSIONS } = require('./constants');
|
||||
|
||||
const VALID_CATEGORIES = ["bgm", "ambient"];
|
||||
|
||||
/**
|
||||
* Sanitizes the input filename for theasset.
|
||||
* @param {string} inputFilename Input filename
|
||||
* @returns {string} Normalized or empty path if invalid
|
||||
*/
|
||||
function checkAssetFileName(inputFilename) {
|
||||
// Sanitize filename
|
||||
if (inputFilename.indexOf('\0') !== -1) {
|
||||
console.debug("Bad request: poisong null bytes in filename.");
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!/^[a-zA-Z0-9_\-\.]+$/.test(inputFilename)) {
|
||||
console.debug("Bad request: illegal character in filename, only alphanumeric, '_', '-' are accepted.");
|
||||
return '';
|
||||
}
|
||||
|
||||
if (UNSAFE_EXTENSIONS.some(ext => inputFilename.toLowerCase().endsWith(ext))) {
|
||||
console.debug("Bad request: forbidden file extension.");
|
||||
return '';
|
||||
}
|
||||
|
||||
if (inputFilename.startsWith('.')) {
|
||||
console.debug("Bad request: filename cannot start with '.'");
|
||||
return '';
|
||||
}
|
||||
|
||||
return path.normalize(inputFilename).replace(/^(\.\.(\/|\\|$))+/, '');;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the endpoints for the asset management.
|
||||
* @param {import('express').Express} app Express app
|
||||
* @param {any} jsonParser JSON parser middleware
|
||||
*/
|
||||
function registerEndpoints(app, jsonParser) {
|
||||
/**
|
||||
* HTTP POST handler function to retrieve name of all files of a given folder path.
|
||||
*
|
||||
* @param {Object} request - HTTP Request object. Require folder path in query
|
||||
* @param {Object} response - HTTP Response object will contain a list of file path.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
app.post('/api/assets/get', jsonParser, async (_, response) => {
|
||||
const folderPath = path.join(DIRECTORIES.assets);
|
||||
let output = {}
|
||||
//console.info("Checking files into",folderPath);
|
||||
|
||||
try {
|
||||
if (fs.existsSync(folderPath) && fs.statSync(folderPath).isDirectory()) {
|
||||
const folders = fs.readdirSync(folderPath)
|
||||
.filter(filename => {
|
||||
return fs.statSync(path.join(folderPath, filename)).isDirectory();
|
||||
});
|
||||
|
||||
for (const folder of folders) {
|
||||
if (folder == "temp")
|
||||
continue;
|
||||
const files = fs.readdirSync(path.join(folderPath, folder))
|
||||
.filter(filename => {
|
||||
return filename != ".placeholder";
|
||||
});
|
||||
output[folder] = [];
|
||||
for (const file of files) {
|
||||
output[folder].push(path.join("assets", folder, file));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
finally {
|
||||
return response.send(output);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* HTTP POST handler function to download the requested asset.
|
||||
*
|
||||
* @param {Object} request - HTTP Request object, expects a url, a category and a filename.
|
||||
* @param {Object} response - HTTP Response only gives status.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
app.post('/api/assets/download', jsonParser, async (request, response) => {
|
||||
const url = request.body.url;
|
||||
const inputCategory = request.body.category;
|
||||
const inputFilename = sanitize(request.body.filename);
|
||||
|
||||
// Check category
|
||||
let category = null;
|
||||
for (let i of VALID_CATEGORIES)
|
||||
if (i == inputCategory)
|
||||
category = i;
|
||||
|
||||
if (category === null) {
|
||||
console.debug("Bad request: unsuported asset category.");
|
||||
return response.sendStatus(400);
|
||||
}
|
||||
|
||||
// Sanitize filename
|
||||
const safe_input = checkAssetFileName(inputFilename);
|
||||
if (safe_input == '')
|
||||
return response.sendStatus(400);
|
||||
|
||||
const temp_path = path.join(DIRECTORIES.assets, "temp", safe_input)
|
||||
const file_path = path.join(DIRECTORIES.assets, category, safe_input)
|
||||
console.debug("Request received to download", url, "to", file_path);
|
||||
|
||||
try {
|
||||
// Download to temp
|
||||
const res = await fetch(url);
|
||||
if (!res.ok || res.body === null) {
|
||||
throw new Error(`Unexpected response ${res.statusText}`);
|
||||
}
|
||||
const destination = path.resolve(temp_path);
|
||||
// Delete if previous download failed
|
||||
if (fs.existsSync(temp_path)) {
|
||||
fs.unlink(temp_path, (err) => {
|
||||
if (err) throw err;
|
||||
});
|
||||
}
|
||||
const fileStream = fs.createWriteStream(destination, { flags: 'wx' });
|
||||
await finished(res.body.pipe(fileStream));
|
||||
|
||||
// Move into asset place
|
||||
console.debug("Download finished, moving file from", temp_path, "to", file_path);
|
||||
fs.renameSync(temp_path, file_path);
|
||||
response.sendStatus(200);
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error);
|
||||
response.sendStatus(500);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* HTTP POST handler function to delete the requested asset.
|
||||
*
|
||||
* @param {Object} request - HTTP Request object, expects a category and a filename
|
||||
* @param {Object} response - HTTP Response only gives stats.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
app.post('/api/assets/delete', jsonParser, async (request, response) => {
|
||||
const inputCategory = request.body.category;
|
||||
const inputFilename = sanitize(request.body.filename);
|
||||
|
||||
// Check category
|
||||
let category = null;
|
||||
for (let i of VALID_CATEGORIES)
|
||||
if (i == inputCategory)
|
||||
category = i;
|
||||
|
||||
if (category === null) {
|
||||
console.debug("Bad request: unsuported asset category.");
|
||||
return response.sendStatus(400);
|
||||
}
|
||||
|
||||
// Sanitize filename
|
||||
const safe_input = checkAssetFileName(inputFilename);
|
||||
if (safe_input == '')
|
||||
return response.sendStatus(400);
|
||||
|
||||
const file_path = path.join(DIRECTORIES.assets, category, safe_input)
|
||||
console.debug("Request received to delete", category, file_path);
|
||||
|
||||
try {
|
||||
// Delete if previous download failed
|
||||
if (fs.existsSync(file_path)) {
|
||||
fs.unlink(file_path, (err) => {
|
||||
if (err) throw err;
|
||||
});
|
||||
console.debug("Asset deleted.");
|
||||
}
|
||||
else {
|
||||
console.debug("Asset not found.");
|
||||
response.sendStatus(400);
|
||||
}
|
||||
// Move into asset place
|
||||
response.sendStatus(200);
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error);
|
||||
response.sendStatus(500);
|
||||
}
|
||||
});
|
||||
|
||||
///////////////////////////////
|
||||
/**
|
||||
* HTTP POST handler function to retrieve a character background music list.
|
||||
*
|
||||
* @param {Object} request - HTTP Request object, expects a character name in the query.
|
||||
* @param {Object} response - HTTP Response object will contain a list of audio file path.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
app.post('/api/assets/character', jsonParser, async (request, response) => {
|
||||
if (request.query.name === undefined) return response.sendStatus(400);
|
||||
const name = sanitize(request.query.name.toString());
|
||||
const inputCategory = request.query.category;
|
||||
|
||||
// Check category
|
||||
let category = null
|
||||
for (let i of VALID_CATEGORIES)
|
||||
if (i == inputCategory)
|
||||
category = i
|
||||
|
||||
if (category === null) {
|
||||
console.debug("Bad request: unsuported asset category.");
|
||||
return response.sendStatus(400);
|
||||
}
|
||||
|
||||
const folderPath = path.join(DIRECTORIES.characters, name, category);
|
||||
|
||||
let output = [];
|
||||
try {
|
||||
if (fs.existsSync(folderPath) && fs.statSync(folderPath).isDirectory()) {
|
||||
const files = fs.readdirSync(folderPath)
|
||||
.filter(filename => {
|
||||
return filename != ".placeholder";
|
||||
});
|
||||
|
||||
for (let i of files)
|
||||
output.push(`/characters/${name}/${category}/${i}`);
|
||||
|
||||
}
|
||||
return response.send(output);
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
return response.sendStatus(500);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
registerEndpoints,
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue