Merge branch 'staging' of https://github.com/SillyTavern/SillyTavern into mancer-api

This commit is contained in:
50h100a
2023-08-02 23:46:03 -04:00
55 changed files with 2118 additions and 5040 deletions

View File

@ -291,7 +291,6 @@ SillyTavern 会将 API 密钥保存在目录中的 `secrets.json` 文件内。
* RossAscends' additions: AGPL v3 * RossAscends' additions: AGPL v3
* Portions of CncAnon's TavernAITurbo mod: Unknown license * Portions of CncAnon's TavernAITurbo mod: Unknown license
* kingbri's various commits and suggestions (https://github.com/bdashore3) * kingbri's various commits and suggestions (https://github.com/bdashore3)
* BlipRanger's miscellaneous UI & extension modifications (https://github.com/BlipRanger)
* Waifu mode inspired by the work of PepperTaco (https://github.com/peppertaco/Tavern/) * Waifu mode inspired by the work of PepperTaco (https://github.com/peppertaco/Tavern/)
* Thanks Pygmalion University for being awesome testers and suggesting cool features! * Thanks Pygmalion University for being awesome testers and suggesting cool features!
* Thanks oobabooga for compiling presets for TextGen * Thanks oobabooga for compiling presets for TextGen
@ -303,4 +302,4 @@ SillyTavern 会将 API 密钥保存在目录中的 `secrets.json` 文件内。
* Thanks paniphons for providing a FAQ document * Thanks paniphons for providing a FAQ document
* 10K Discord Users Celebratory Background by @kallmeflocc * 10K Discord Users Celebratory Background by @kallmeflocc
* Default content (characters and lore books) provided by @OtisAlejandro, @RossAscends and @kallmeflocc * Default content (characters and lore books) provided by @OtisAlejandro, @RossAscends and @kallmeflocc
* Korean translation by @doloroushyeonse * Korean translation by @doloroushyeonse

2
.github/readme.md vendored
View File

@ -293,7 +293,6 @@ GNU Affero General Public License for more details.**
* RossAscends' additions: AGPL v3 * RossAscends' additions: AGPL v3
* Portions of CncAnon's TavernAITurbo mod: Unknown license * Portions of CncAnon's TavernAITurbo mod: Unknown license
* kingbri's various commits and suggestions (<https://github.com/bdashore3>) * kingbri's various commits and suggestions (<https://github.com/bdashore3>)
* BlipRanger's miscellaneous UI & extension modifications (<https://github.com/BlipRanger>)
* Waifu mode inspired by the work of PepperTaco (<https://github.com/peppertaco/Tavern/>) * Waifu mode inspired by the work of PepperTaco (<https://github.com/peppertaco/Tavern/>)
* Thanks Pygmalion University for being awesome testers and suggesting cool features! * Thanks Pygmalion University for being awesome testers and suggesting cool features!
* Thanks oobabooga for compiling presets for TextGen * Thanks oobabooga for compiling presets for TextGen
@ -306,3 +305,4 @@ GNU Affero General Public License for more details.**
* 10K Discord Users Celebratory Background by @kallmeflocc * 10K Discord Users Celebratory Background by @kallmeflocc
* Default content (characters and lore books) provided by @OtisAlejandro, @RossAscends and @kallmeflocc * Default content (characters and lore books) provided by @OtisAlejandro, @RossAscends and @kallmeflocc
* Korean translation by @doloroushyeonse * Korean translation by @doloroushyeonse
* k_euler_a support for Horde by <https://github.com/Teashrock>

1
.gitignore vendored
View File

@ -26,4 +26,5 @@ secrets.json
/dist /dist
/backups/ /backups/
public/movingUI/ public/movingUI/
public/QuickReplies/
content.log content.log

View File

@ -14,6 +14,7 @@
"world_info_depth": 2, "world_info_depth": 2,
"world_info_budget": 25, "world_info_budget": 25,
"world_info_recursive": true, "world_info_recursive": true,
"world_info_overflow_alert": false,
"world_info_case_sensitive": false, "world_info_case_sensitive": false,
"world_info_match_whole_words": false, "world_info_match_whole_words": false,
"world_info_character_strategy": 1, "world_info_character_strategy": 1,
@ -27,6 +28,7 @@
"eta_cutoff": 0, "eta_cutoff": 0,
"typical_p": 1, "typical_p": 1,
"rep_pen": 1.1, "rep_pen": 1.1,
"rep_pen_range": 0,
"no_repeat_ngram_size": 0, "no_repeat_ngram_size": 0,
"penalty_alpha": 0, "penalty_alpha": 0,
"num_beams": 1, "num_beams": 1,
@ -91,7 +93,7 @@
"sort_order": "asc", "sort_order": "asc",
"sort_rule": null, "sort_rule": null,
"font_scale": 1, "font_scale": 1,
"blur_strength": 5, "blur_strength": 10,
"shadow_width": 2, "shadow_width": 2,
"main_text_color": "rgba(220, 220, 210, 1)", "main_text_color": "rgba(220, 220, 210, 1)",
"italics_text_color": "rgba(145, 145, 145, 1)", "italics_text_color": "rgba(145, 145, 145, 1)",
@ -144,7 +146,8 @@
"persona_descriptions": {}, "persona_descriptions": {},
"persona_description": "", "persona_description": "",
"persona_description_position": 0, "persona_description_position": 0,
"custom_stopping_strings": "" "custom_stopping_strings": "",
"fuzzy_search": false
}, },
"extension_settings": { "extension_settings": {
"apiUrl": "http://localhost:5100", "apiUrl": "http://localhost:5100",
@ -183,7 +186,14 @@
"promptInterval": 10, "promptInterval": 10,
"promptMinInterval": 1, "promptMinInterval": 1,
"promptMaxInterval": 100, "promptMaxInterval": 100,
"promptIntervalStep": 1 "promptIntervalStep": 1,
"template": "[Summary: {{summary}}]",
"position": 0,
"depth": 2,
"promptForceWords": 0,
"promptForceWordsStep": 100,
"promptMinForceWords": 0,
"promptMaxForceWords": 10000
}, },
"note": { "note": {
"default": "", "default": "",
@ -203,7 +213,8 @@
"ttsEnabled": false, "ttsEnabled": false,
"currentProvider": "System", "currentProvider": "System",
"auto_generation": true, "auto_generation": true,
"ElevenLabs": {} "ElevenLabs": {},
"System": {}
}, },
"sd": { "sd": {
"scale_min": 1, "scale_min": 1,
@ -228,7 +239,16 @@
"horde": true, "horde": true,
"horde_nsfw": false, "horde_nsfw": false,
"horde_karras": true, "horde_karras": true,
"refine_mode": false "refine_mode": false,
"prompts": {
"0": "[In the next response I want you to provide only a detailed comma-delimited list of keywords and phrases which describe {{char}}. The list must include all of the following items in this order: name, species and race, gender, age, clothing, occupation, physical features and appearances. Do not include descriptions of non-visual qualities such as personality, movements, scents, mental traits, or anything which could not be seen in a still photograph. Do not write in full sentences. Prefix your description with the phrase 'full body portrait,']",
"1": "[Pause your roleplay and provide a detailed description of {{user}}'s physical appearance from the perspective of {{char}} in the form of a comma-delimited list of keywords and phrases. The list must include all of the following items in this order: name, species and race, gender, age, clothing, occupation, physical features and appearances. Do not include descriptions of non-visual qualities such as personality, movements, scents, mental traits, or anything which could not be seen in a still photograph. Do not write in full sentences. Prefix your description with the phrase 'full body portrait,'. Ignore the rest of the story when crafting this description. Do not roleplay as {{char}} when writing this description, and do not attempt to continue the story.]",
"2": "[Pause your roleplay and provide a detailed description for all of the following: a brief recap of recent events in the story, {{char}}'s appearance, and {{char}}'s surroundings. Do not roleplay while writing this description.]",
"3": "[Pause your roleplay and provide ONLY the last chat message string back to me verbatim. Do not write anything after the string. Do not roleplay at all in your response. Do not continue the roleplay story.]",
"4": "[Pause your roleplay. Your next response must be formatted as a single comma-delimited list of concise keywords. The list will describe of the visual details included in the last chat message.\n\n Only mention characters by using pronouns ('he','his','she','her','it','its') or neutral nouns ('male', 'the man', 'female', 'the woman').\n\n Ignore non-visible things such as feelings, personality traits, thoughts, and spoken dialog.\n\n Add keywords in this precise order:\n a keyword to describe the location of the scene,\n a keyword to mention how many characters of each gender or type are present in the scene (minimum of two characters:\n {{user}} and {{char}}, example: '2 men ' or '1 man 1 woman ', '1 man 3 robots'),\n\n keywords to describe the relative physical positioning of the characters to each other (if a commonly known term for the positioning is known use it instead of describing the positioning in detail) + 'POV',\n\n a single keyword or phrase to describe the primary act taking place in the last chat message,\n\n keywords to describe {{char}}'s physical appearance and facial expression,\n keywords to describe {{char}}'s actions,\n keywords to describe {{user}}'s physical appearance and actions.\n\n If character actions involve direct physical interaction with another character, mention specifically which body parts interacting and how.\n\n A correctly formatted example response would be:\n '(location),(character list by gender),(primary action), (relative character position) POV, (character 1's description and actions), (character 2's description and actions)']",
"5": "[In the next response I want you to provide only a detailed comma-delimited list of keywords and phrases which describe {{char}}. The list must include all of the following items in this order: name, species and race, gender, age, facial features and expressions, occupation, hair and hair accessories (if any), what they are wearing on their upper body (if anything). Do not describe anything below their neck. Do not include descriptions of non-visual qualities such as personality, movements, scents, mental traits, or anything which could not be seen in a still photograph. Do not write in full sentences. Prefix your description with the phrase 'close up facial portrait,']"
},
"character_prompts": {}
}, },
"chromadb": {}, "chromadb": {},
"translate": { "translate": {
@ -237,7 +257,15 @@
"provider": "google", "provider": "google",
"auto_mode": "none" "auto_mode": "none"
}, },
"objective": {}, "objective": {
"customPrompts": {
"default": {
"createTask": "Pause your roleplay and generate a list of tasks to complete an objective. Your next response must be formatted as a numbered list of plain text entries. Do not include anything but the numbered list. The list must be prioritized in the order that tasks must be completed.\n\nThe objective that you must make a numbered task list for is: [{{objective}}].\nThe tasks created should take into account the character traits of {{char}}. These tasks may or may not involve {{user}} directly. Be sure to include the objective as the final task.\n\nGiven an example objective of 'Make me a four course dinner', here is an example output:\n1. Determine what the courses will be\n2. Find recipes for each course\n3. Go shopping for supplies with {{user}}\n4. Cook the food\n5. Get {{user}} to set the table\n6. Serve the food\n7. Enjoy eating the meal with {{user}}\n ",
"checkTaskCompleted": "Pause your roleplay. Determine if this task is completed: [{{task}}].\nTo do this, examine the most recent messages. Your response must only contain either true or false, nothing other words.\nExample output:\ntrue\n ",
"currentTask": "Your current task is [{{task}}]. Balance existing roleplay with completing this task."
}
}
},
"quickReply": { "quickReply": {
"quickReplyEnabled": false, "quickReplyEnabled": false,
"numberOfSlots": 5, "numberOfSlots": 5,
@ -273,6 +301,14 @@
"controls": [], "controls": [],
"fluctuation": 0.1, "fluctuation": 0.1,
"enabled": false "enabled": false
},
"speech_recognition": {
"currentProvider": "None",
"messageMode": "append",
"messageMappingText": "",
"messageMapping": [],
"messageMappingEnabled": false,
"None": {}
} }
}, },
"context_settings": { "context_settings": {
@ -296,33 +332,46 @@
"1345561466591" "1345561466591"
] ]
}, },
"temp_novel": 1.11, "nai_settings": {
"rep_pen_novel": 1.11, "temperature": 0.63,
"rep_pen_size_novel": 320, "repetition_penalty": 1.148125,
"model_novel": "euterpe-v2", "repetition_penalty_range": 2048,
"preset_settings_novel": "Classic-Euterpe", "repetition_penalty_slope": 0.09,
"streaming_novel": false, "repetition_penalty_frequency": 0,
"temp": 1, "repetition_penalty_presence": 0,
"rep_pen": 1.1, "tail_free_sampling": 0.975,
"rep_pen_range": 600, "top_k": 0,
"top_p": 0.95, "top_p": 0.975,
"top_a": 0, "top_a": 1,
"top_k": 0, "typical_p": 1,
"typical": 1, "min_length": 1,
"tfs": 1, "model_novel": "euterpe-v2",
"rep_pen_slope": 0, "preset_settings_novel": "Classic-Euterpe",
"single_line": false, "streaming_novel": false
"use_stop_sequence": false, },
"streaming_kobold": false, "kai_settings": {
"sampler_order": [ "temp": 1,
6, "rep_pen": 1.1,
0, "rep_pen_range": 600,
1, "top_p": 0.95,
2, "top_a": 0,
3, "top_k": 0,
4, "typical": 1,
5 "tfs": 1,
], "rep_pen_slope": 0,
"single_line": false,
"use_stop_sequence": false,
"streaming_kobold": false,
"sampler_order": [
6,
0,
1,
2,
3,
4,
5
]
},
"preset_settings_openai": "Default", "preset_settings_openai": "Default",
"temp_openai": "0.9", "temp_openai": "0.9",
"freq_pen_openai": 0.7, "freq_pen_openai": 0.7,
@ -374,5 +423,8 @@
"legacy_streaming": false, "legacy_streaming": false,
"chat_completion_source": "openai", "chat_completion_source": "openai",
"max_context_unlocked": false, "max_context_unlocked": false,
"api_url_scale": "" "api_url_scale": "",
"show_external_models": false,
"proxy_password": "",
"assistant_prefill": ""
} }

61
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "sillytavern", "name": "sillytavern",
"version": "1.9.2", "version": "1.9.4",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "sillytavern", "name": "sillytavern",
"version": "1.9.2", "version": "1.9.4",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"dependencies": { "dependencies": {
"@dqbd/tiktoken": "^1.0.2", "@dqbd/tiktoken": "^1.0.2",
@ -42,7 +42,6 @@
"sentencepiece-js": "^1.1.0", "sentencepiece-js": "^1.1.0",
"simple-git": "^3.19.1", "simple-git": "^3.19.1",
"uniqolor": "^1.1.0", "uniqolor": "^1.1.0",
"user-agents": "^1.0.1444",
"webp-converter": "2.3.2", "webp-converter": "2.3.2",
"ws": "^8.13.0", "ws": "^8.13.0",
"yargs": "^17.7.1", "yargs": "^17.7.1",
@ -1223,14 +1222,6 @@
"npm": "1.2.8000 || >= 1.4.16" "npm": "1.2.8000 || >= 1.4.16"
} }
}, },
"node_modules/detect-indent": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.0.0.tgz",
"integrity": "sha512-oSyFlqaTHCItVRGK5RmrmjB+CmaMOW7IaNA/kdxqhoa6d17j/5ce9O9eWXmV/KEdRwqpQA+Vqe8a8Bsybu4YnA==",
"engines": {
"node": ">=8"
}
},
"node_modules/detect-libc": { "node_modules/detect-libc": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz",
@ -1260,32 +1251,11 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/docopt": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/docopt/-/docopt-0.6.2.tgz",
"integrity": "sha512-NqTbaYeE4gA/wU1hdKFdU+AFahpDOpgGLzHP42k6H6DKExJd0A55KEVWYhL9FEmHmgeLvEU2vuKXDuU+4yToOw==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/dom-walk": { "node_modules/dom-walk": {
"version": "0.1.2", "version": "0.1.2",
"resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz",
"integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w=="
}, },
"node_modules/dot-json": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/dot-json/-/dot-json-1.3.0.tgz",
"integrity": "sha512-Pu11Prog/Yjf2lBICow82/DSV46n3a2XT1Rqt/CeuhkO1fuacF7xydYhI0SwQx2Ue0jCyLtQzgKPFEO6ewv+bQ==",
"dependencies": {
"detect-indent": "~6.0.0",
"docopt": "~0.6.2",
"underscore-keypath": "~0.0.22"
},
"bin": {
"dot-json": "bin/dot-json.js"
}
},
"node_modules/ee-first": { "node_modules/ee-first": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@ -2037,11 +2007,6 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
}, },
"node_modules/lodash.clonedeep": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ=="
},
"node_modules/lru-cache": { "node_modules/lru-cache": {
"version": "6.0.0", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
@ -3456,19 +3421,6 @@
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
}, },
"node_modules/underscore": {
"version": "1.13.6",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz",
"integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A=="
},
"node_modules/underscore-keypath": {
"version": "0.0.22",
"resolved": "https://registry.npmjs.org/underscore-keypath/-/underscore-keypath-0.0.22.tgz",
"integrity": "sha512-fU7aYj1J2LQd+jqdQ67AlCOZKK3Pl+VErS8fGYcgZG75XB9/bY+RLM+F2xEcKHhHNtLvqqFyXAoZQlLYfec3Xg==",
"dependencies": {
"underscore": "*"
}
},
"node_modules/uniqolor": { "node_modules/uniqolor": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/uniqolor/-/uniqolor-1.1.0.tgz", "resolved": "https://registry.npmjs.org/uniqolor/-/uniqolor-1.1.0.tgz",
@ -3491,15 +3443,6 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/user-agents": {
"version": "1.0.1444",
"resolved": "https://registry.npmjs.org/user-agents/-/user-agents-1.0.1444.tgz",
"integrity": "sha512-6WXJ0RZuUKgif1rW5FN02HnpoJ8EzH6COQoXCiVStZEVPz+YnAx3iA48etY3ZD4UwueYN9ALC7j4ayHvYEh7tA==",
"dependencies": {
"dot-json": "^1.3.0",
"lodash.clonedeep": "^4.5.0"
}
},
"node_modules/utf8-byte-length": { "node_modules/utf8-byte-length": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz", "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz",

View File

@ -33,7 +33,6 @@
"sentencepiece-js": "^1.1.0", "sentencepiece-js": "^1.1.0",
"simple-git": "^3.19.1", "simple-git": "^3.19.1",
"uniqolor": "^1.1.0", "uniqolor": "^1.1.0",
"user-agents": "^1.0.1444",
"webp-converter": "2.3.2", "webp-converter": "2.3.2",
"ws": "^8.13.0", "ws": "^8.13.0",
"yargs": "^17.7.1", "yargs": "^17.7.1",
@ -51,7 +50,7 @@
"type": "git", "type": "git",
"url": "https://github.com/SillyTavern/SillyTavern.git" "url": "https://github.com/SillyTavern/SillyTavern.git"
}, },
"version": "1.9.2", "version": "1.9.4",
"scripts": { "scripts": {
"start": "node server.js", "start": "node server.js",
"pkg": "pkg --compress Gzip --no-bytecode --public ." "pkg": "pkg --compress Gzip --no-bytecode --public ."

View File

@ -0,0 +1,24 @@
{
"genamt": 300,
"max_length": 4096,
"temp": 0,
"rep_pen": 1.1,
"rep_pen_range": 4096,
"streaming_kobold": true,
"top_p": 0,
"top_a": 0,
"top_k": 1,
"typical": 1,
"tfs": 1,
"rep_pen_slope": 0.2,
"single_line": false,
"sampler_order": [
6,
0,
1,
3,
4,
2,
5
]
}

View File

@ -0,0 +1,24 @@
{
"genamt": 300,
"max_length": 4096,
"temp": 0.72,
"rep_pen": 1.1,
"rep_pen_range": 4096,
"streaming_kobold": true,
"top_p": 0.73,
"top_a": 0,
"top_k": 0,
"typical": 1,
"tfs": 1,
"rep_pen_slope": 0.2,
"single_line": false,
"sampler_order": [
6,
0,
1,
3,
4,
2,
5
]
}

View File

@ -0,0 +1,24 @@
{
"genamt": 250,
"max_length": 4096,
"temp": 0.65,
"rep_pen": 1.18,
"rep_pen_range": 4096,
"streaming_kobold": true,
"top_p": 0.47,
"top_a": 0,
"top_k": 42,
"typical": 1,
"tfs": 1,
"rep_pen_slope": 0,
"single_line": false,
"sampler_order": [
6,
0,
1,
3,
4,
2,
5
]
}

View File

@ -0,0 +1,19 @@
{
"order": [5, 0, 1, 3],
"temperature": 1.23,
"max_length": 300,
"min_length": 1,
"top_k": 200,
"typical_p": 0.966,
"tail_free_sampling": 0.982,
"repetition_penalty": 1.74,
"repetition_penalty_range": 4000,
"repetition_penalty_frequency": 0,
"repetition_penalty_presence": 0.02,
"use_cache": false,
"return_full_text": false,
"prefix": "vanilla",
"cfg_scale": 1,
"phrase_rep_pen": "aggressive",
"max_context": 7800
}

View File

@ -0,0 +1,19 @@
{
"order": [6, 0, 1, 2, 3],
"temperature": 1,
"max_length": 300,
"min_length": 1,
"top_k": 25,
"top_p": 1,
"tail_free_sampling": 0.925,
"repetition_penalty": 1.6,
"repetition_penalty_frequency": 0.001,
"repetition_penalty_range": 0,
"repetition_penalty_presence": 0,
"use_cache": false,
"return_full_text": false,
"prefix": "vanilla",
"phrase_rep_pen": "medium",
"cfg_scale": 1.55,
"max_context": 7800
}

View File

@ -0,0 +1,20 @@
{
"order": [6, 2, 3, 1, 0],
"temperature": 1,
"max_length": 300,
"min_length": 1,
"top_k": 0,
"top_p": 0.96,
"tail_free_sampling": 0.96,
"repetition_penalty": 2,
"repetition_penalty_slope": 1,
"repetition_penalty_frequency": 0.02,
"repetition_penalty_range": 0,
"repetition_penalty_presence": 0.3,
"use_cache": false,
"return_full_text": false,
"prefix": "vanilla",
"phrase_rep_pen": "very_aggressive",
"cfg_scale": 1.3,
"max_context": 7800
}

View File

@ -1,20 +1,21 @@
{ {
"order": [2, 3, 0, 4, 1], "order": [2, 3, 0, 4, 1],
"temperature": 1.05, "temperature": 1.35,
"max_length": 300, "max_length": 300,
"min_length": 1, "min_length": 1,
"top_k": 12, "top_k": 12,
"top_p": 0.85, "top_p": 0.85,
"top_a": 0.1, "top_a": 0.1,
"typical_p": 1,
"tail_free_sampling": 0.915, "tail_free_sampling": 0.915,
"repetition_penalty": 2.8, "repetition_penalty": 2.8,
"repetition_penalty_range": 2048, "repetition_penalty_range": 2048,
"repetition_penalty_slope": 0.02, "repetition_penalty_slope": 0.02,
"repetition_penalty_frequency": 0.03, "repetition_penalty_frequency": 0.02,
"repetition_penalty_presence": 0.0, "repetition_penalty_presence": 0,
"use_cache": false, "use_cache": false,
"return_full_text": false, "return_full_text": false,
"prefix": "vanilla", "prefix": "vanilla",
"max_context": 8192 "cfg_scale": 1,
"phrase_rep_pen": "aggressive",
"max_context": 7800
} }

View File

@ -1,23 +0,0 @@
{
"order": [
1,
3,
4,
0,
2
],
"temperature": 1.05,
"max_length": 90,
"min_length": 1,
"tail_free_sampling": 0.989,
"repetition_penalty": 1.5,
"repetition_penalty_range": 8192,
"repetition_penalty_frequency": 0.03,
"repetition_penalty_presence": 0.005,
"repetition_penalty_slope": 0,
"top_a": 0.075,
"top_k": 79,
"top_p": 0.95,
"typical_p": 1,
"max_context": 8192
}

View File

@ -0,0 +1,21 @@
{
"order": [4, 0, 5, 3, 2],
"temperature": 1.09,
"max_length": 300,
"min_length": 1,
"top_p": 0.969,
"top_a": 0.09,
"typical_p": 0.99,
"tail_free_sampling": 0.969,
"repetition_penalty": 1.09,
"repetition_penalty_range": 8192,
"repetition_penalty_slope": 0.069,
"repetition_penalty_frequency": 0.006,
"repetition_penalty_presence": 0.009,
"use_cache": false,
"return_full_text": false,
"prefix": "vanilla",
"cfg_scale": 1,
"phrase_rep_pen": "very_light",
"max_context": 7800
}

View File

@ -16,5 +16,7 @@
"use_cache": false, "use_cache": false,
"return_full_text": false, "return_full_text": false,
"prefix": "vanilla", "prefix": "vanilla",
"max_context": 8192 "cfg_scale": 1,
"phrase_rep_pen": "very_light",
"max_context": 7800
} }

View File

@ -0,0 +1,20 @@
{
"order": [0, 1, 2, 3],
"temperature": 1,
"max_length": 300,
"min_length": 1,
"top_k": 25,
"top_p": 1,
"tail_free_sampling": 0.925,
"repetition_penalty": 1.9,
"repetition_penalty_range": 768,
"repetition_penalty_slope": 1,
"repetition_penalty_frequency": 0.0025,
"repetition_penalty_presence": 0.001,
"use_cache": false,
"return_full_text": false,
"prefix": "vanilla",
"cfg_scale": 1,
"phrase_rep_pen": "off",
"max_context": 7800
}

View File

@ -0,0 +1,19 @@
{
"order": [6, 1, 0, 5, 3],
"temperature": 1.25,
"max_length": 300,
"min_length": 1,
"top_k": 70,
"typical_p": 0.9,
"tail_free_sampling": 0.925,
"repetition_penalty": 2,
"repetition_penalty_range": 1632,
"repetition_penalty_frequency": 0,
"repetition_penalty_presence": 0,
"use_cache": false,
"return_full_text": false,
"prefix": "vanilla",
"phrase_rep_pen": "aggressive",
"cfg_scale": 1.825,
"max_context": 7800
}

View File

@ -16,5 +16,7 @@
"use_cache": false, "use_cache": false,
"return_full_text": false, "return_full_text": false,
"prefix": "vanilla", "prefix": "vanilla",
"max_context": 8192 "cfg_scale": 1,
"phrase_rep_pen": "very_light",
"max_context": 7800
} }

View File

@ -16,5 +16,7 @@
"use_cache": false, "use_cache": false,
"return_full_text": false, "return_full_text": false,
"prefix": "vanilla", "prefix": "vanilla",
"max_context": 8192 "cfg_scale": 1,
"phrase_rep_pen": "very_light",
"max_context": 7800
} }

View File

@ -0,0 +1,22 @@
{
"order": [6, 0, 4, 1, 2, 5, 3],
"temperature": 1.31,
"max_length": 300,
"min_length": 1,
"top_k": 25,
"top_p": 0.97,
"top_a": 0.18,
"typical_p": 0.98,
"tail_free_sampling": 1,
"repetition_penalty": 1.55,
"repetition_penalty_frequency": 0.00075,
"repetition_penalty_presence": 0.00085,
"repetition_penalty_range": 8192,
"repetition_penalty_slope": 1.8,
"use_cache": false,
"return_full_text": false,
"prefix": "vanilla",
"phrase_rep_pen": "medium",
"cfg_scale": 1.35,
"max_context": 7800
}

View File

@ -0,0 +1,17 @@
{
"order": [3, 0, 5],
"temperature": 2.5,
"max_length": 300,
"min_length": 1,
"typical_p": 0.966,
"tail_free_sampling": 0.933,
"repetition_penalty": 1,
"repetition_penalty_range": 2048,
"repetition_penalty_frequency": 0,
"repetition_penalty_presence": 0,
"use_cache": false,
"return_full_text": false,
"prefix": "vanilla",
"phrase_rep_pen": "aggressive",
"max_context": 7800
}

View File

@ -1,20 +1,22 @@
{ {
"order": [1, 3, 4, 0, 2], "order": [1, 5, 0, 2, 3, 4],
"temperature": 1.05, "temperature": 1.5,
"max_length": 40, "max_length": 300,
"min_length": 1, "min_length": 1,
"top_k": 79, "top_k": 10,
"top_p": 0.95, "top_p": 0.75,
"top_a": 0.075, "top_a": 0.08,
"typical_p": 1, "typical_p": 0.975,
"tail_free_sampling": 0.989, "tail_free_sampling": 0.967,
"repetition_penalty": 1.5, "repetition_penalty": 2.25,
"repetition_penalty_range": 8192, "repetition_penalty_range": 8192,
"repetition_penalty_slope": 3.33, "repetition_penalty_slope": 0.09,
"repetition_penalty_frequency": 0.03, "repetition_penalty_frequency": 0,
"repetition_penalty_presence": 0.005, "repetition_penalty_presence": 0.005,
"use_cache": false, "use_cache": false,
"return_full_text": false, "return_full_text": false,
"prefix": "vanilla", "prefix": "vanilla",
"max_context": 8192 "cfg_scale": 1,
"phrase_rep_pen": "very_light",
"max_context": 7800
} }

View File

@ -0,0 +1,18 @@
{
"order": [6, 0, 5],
"temperature": 0.895,
"max_length": 300,
"min_length": 1,
"typical_p": 0.9,
"repetition_penalty": 2,
"repetition_penalty_slope": 3.2,
"repetition_penalty_frequency": 0,
"repetition_penalty_presence": 0,
"repetition_penalty_range": 4048,
"use_cache": false,
"return_full_text": false,
"prefix": "vanilla",
"phrase_rep_pen": "aggressive",
"cfg_scale": 1.3,
"max_context": 7800
}

View File

@ -16,5 +16,7 @@
"use_cache": false, "use_cache": false,
"return_full_text": false, "return_full_text": false,
"prefix": "vanilla", "prefix": "vanilla",
"max_context": 8192 "cfg_scale": 1,
"phrase_rep_pen": "very_light",
"max_context": 7800
} }

View File

@ -0,0 +1,19 @@
{
"order": [6, 1, 0, 5, 3, 2],
"temperature": 1.5,
"max_length": 300,
"min_length": 1,
"top_k": 70,
"top_p": 0.95,
"typical_p": 0.95,
"tail_free_sampling": 0.95,
"repetition_penalty": 1.6,
"repetition_penalty_range": 2016,
"repetition_penalty_frequency": 0,
"repetition_penalty_presence": 0,
"use_cache": false,
"return_full_text": false,
"prefix": "vanilla",
"phrase_rep_pen": "very_aggressive",
"max_context": 7800
}

View File

@ -0,0 +1,23 @@
{
"name": "Default",
"quickReplyEnabled": true,
"quickReplySlots": [
{
"mes": "/?",
"label": "HELP",
"enabled": true
},
{
"mes": "/newchat",
"label": "New Chat",
"enabled": true
},
{
"mes": "/bgcol",
"label": "Match UI to Background",
"enabled": true
}
],
"numberOfSlots": 3,
"selectedPreset": "Default"
}

View File

@ -3,7 +3,7 @@
"top_p": 0.9, "top_p": 0.9,
"top_k": 20, "top_k": 20,
"typical_p": 1, "typical_p": 1,
"top_a": 0.75, "top_a": 0,
"tfs": 1, "tfs": 1,
"epsilon_cutoff": 0, "epsilon_cutoff": 0,
"eta_cutoff": 0, "eta_cutoff": 0,

View File

@ -253,7 +253,7 @@
</div> </div>
<div class="range-block-range-and-counter"> <div class="range-block-range-and-counter">
<div class="range-block-range"> <div class="range-block-range">
<input type="range" id="temp" name="volume" min="0.1" max="2.0" step="0.01"> <input type="range" id="temp" name="volume" min="0.0" max="2.0" step="0.01">
</div> </div>
<div class="range-block-counter"> <div class="range-block-counter">
<div contenteditable="true" data-for="temp" id="temp_counter"> <div contenteditable="true" data-for="temp" id="temp_counter">
@ -867,6 +867,36 @@
</div> </div>
</div> </div>
</div> </div>
<div class="range-block">
<div class="range-block-title" data-i18n="CFG Scale">
CFG Scale
</div>
<div class="range-block-range-and-counter">
<div class="range-block-range">
<input type="range" id="cfg_scale_novel" name="volume" min="1" max="3" step="0.05">
</div>
<div class="range-block-counter">
<div contenteditable="true" data-for="cfg_scale_novel" id="cfg_scale_counter_novel">
select
</div>
</div>
</div>
</div>
<div class="range-block">
<div class="range-block-title" data-i18n="Phrase Repetition Penalty">
Phrase Repetition Penalty
</div>
<div class="range-block-range-and-counter">
<div class="range-block-range">
<input type="range" id="phrase_rep_pen_novel" name="volume" min="0" max="5" step="1">
</div>
<div class="range-block-counter">
<div contenteditable="true" data-for="phrase_rep_pen_novel" id="phrase_rep_pen_counter_novel">
select
</div>
</div>
</div>
</div>
<div class="range-block"> <div class="range-block">
<div class="range-block-title" data-i18n="Min Length"> <div class="range-block-title" data-i18n="Min Length">
Min Length Min Length
@ -1289,6 +1319,14 @@
<textarea id="jailbreak_prompt_textarea" class="text_pole textarea_compact" name="jailbreak_prompt" rows="6" placeholder=""></textarea> <textarea id="jailbreak_prompt_textarea" class="text_pole textarea_compact" name="jailbreak_prompt" rows="6" placeholder=""></textarea>
</div> </div>
</div> </div>
<div class="range-block" data-source="claude">
<div class="range-block-title" data-i18n="Assistant Prefill">
Assistant Prefill
</div>
<div class="wide100p">
<input type="text" id="claude_assistant_prefill" class="text_pole" placeholder="Start Claude's answer with...">
</div>
</div>
<div class="inline-drawer wide100p"> <div class="inline-drawer wide100p">
<div class="inline-drawer-toggle inline-drawer-header margin-bot-10px"> <div class="inline-drawer-toggle inline-drawer-header margin-bot-10px">
@ -1805,7 +1843,7 @@
Custom Chat Separator Custom Chat Separator
</h4> </h4>
<div> <div>
<input id="custom_chat_separator" class="text_pole textarea_compact" type="text" placeholder="&lt;START&gt;" maxlength="100" /> <textarea id="custom_chat_separator" class="text_pole textarea_compact" type="text" placeholder="&lt;START&gt;" maxlength="500" rows="1"></textarea>
</div> </div>
</div> </div>
<div> <div>
@ -1858,7 +1896,7 @@
<span data-i18n="Input Sequence">Input Sequence</span> <span data-i18n="Input Sequence">Input Sequence</span>
</label> </label>
<div> <div>
<input id="instruct_input_sequence" class="text_pole textarea_compact" type="text" maxlength="100" /> <textarea id="instruct_input_sequence" class="text_pole textarea_compact" type="text" maxlength="500" rows="1"></textarea>
</div> </div>
</div> </div>
<div class="flex1"> <div class="flex1">
@ -1866,7 +1904,7 @@
<span data-i18n="Output Sequence">Output Sequence</span> <span data-i18n="Output Sequence">Output Sequence</span>
</label> </label>
<div> <div>
<input id="instruct_output_sequence" class="text_pole wide100p textarea_compact" type="text" maxlength="100" /> <textarea id="instruct_output_sequence" class="text_pole wide100p textarea_compact" type="text" maxlength="500" rows="1"></textarea>
</div> </div>
</div> </div>
</div> </div>
@ -1876,7 +1914,7 @@
<small data-i18n="System Sequence">System Sequence</small> <small data-i18n="System Sequence">System Sequence</small>
</label> </label>
<div> <div>
<input id="instruct_system_sequence" class="text_pole textarea_compact" type="text" maxlength="100" /> <textarea id="instruct_system_sequence" class="text_pole textarea_compact" type="text" maxlength="500" rows="1"></textarea>
</div> </div>
</div> </div>
<div class="flex1"> <div class="flex1">
@ -1884,7 +1922,7 @@
<small data-i18n="Stop Sequence">Stop Sequence</small> <small data-i18n="Stop Sequence">Stop Sequence</small>
</label> </label>
<div> <div>
<input id="instruct_stop_sequence" class="text_pole wide100p textarea_compact" type="text" maxlength="100" /> <textarea id="instruct_stop_sequence" class="text_pole wide100p textarea_compact" type="text" maxlength="500" rows="1"></textarea>
</div> </div>
</div> </div>
<div class="flex1"> <div class="flex1">
@ -1892,7 +1930,7 @@
<small data-i18n="Separator">Separator</small> <small data-i18n="Separator">Separator</small>
</label> </label>
<div> <div>
<input id="instruct_separator_sequence" class="text_pole wide100p textarea_compact" type="text" maxlength="100" /> <textarea id="instruct_separator_sequence" class="text_pole wide100p textarea_compact" type="text" maxlength="500" rows="1"></textarea>
</div> </div>
</div> </div>
</div> </div>
@ -2132,6 +2170,14 @@
Match whole words Match whole words
</small> </small>
</label> </label>
<label title="Alert if your world info is greater than the allocated budget."
data-i18n="[title]Alert if your world info is greater than the allocated budget."
class="checkbox_label">
<input id="world_info_overflow_alert" type="checkbox" />
<small data-i18n="Alert On Overflow">
Alert On Overflow
</small>
</label>
</div> </div>
</div> </div>
@ -2606,14 +2652,14 @@
</div> </div>
</div> </div>
<div class="flex1"> <div class="flex1">
<h4 data-i18n="Your Avatar" class="title_restorable"> <h4 class="title_restorable">
<span>Your Persona</span> <span data-i18n="Your Persona">Your Persona</span>
<button class="menu_button menu_button_icon user_stats_button" title="Click for stats!"> <button class="menu_button menu_button_icon user_stats_button" title="Click for stats!">
<i class="fa-solid fa-circle-info"></i>Usage Stats <i class="fa-solid fa-circle-info"></i><span data-i18n="Usage Stats">Usage Stats</span>
</button> </button>
<div id="create_dummy_persona" class="menu_button menu_button_icon" title="Create a dummy persona" data-i18n="[title]Create a dummy persona"> <div id="create_dummy_persona" class="menu_button menu_button_icon" title="Create a dummy persona" data-i18n="[title]Create a dummy persona">
<i class="fa-solid fa-person-circle-question fa-fw"></i> <i class="fa-solid fa-person-circle-question fa-fw"></i>
<span>Blank</span> <span data-i18n="Blank">Blank</span>
</div> </div>
</h4> </h4>
<div id="user_avatar_block"> <div id="user_avatar_block">
@ -3761,6 +3807,14 @@
<!-- popups live outside sheld to avoid blur conflicts --> <!-- popups live outside sheld to avoid blur conflicts -->
<div id="options" class="font-family-reset" style="display: none;"> <div id="options" class="font-family-reset" style="display: none;">
<div class="options-content"> <div class="options-content">
<a id="option_close_chat">
<i class="fa-lg fa-solid fa-times"></i>
<span data-i18n="Close chat">Close chat</span>
</a>
<a id="option_settings">
<i class="fa-lg fa-solid fa-cog"></i>
<span data-i18n="Toggle Panels">Toggle Panels</span>
</a>
<a id="option_toggle_AN"> <a id="option_toggle_AN">
<i class="fa-lg fa-solid fa-note-sticky"></i> <i class="fa-lg fa-solid fa-note-sticky"></i>
<span data-i18n="Author's Note">Author's Note</span> <span data-i18n="Author's Note">Author's Note</span>

View File

@ -0,0 +1,13 @@
{
"enabled": true,
"input_sequence": "### Instruction:",
"macro": true,
"name": "Roleplay",
"names": false,
"output_sequence": "### Response (2 paragraphs, engaging, natural, authentic, descriptive, creative):",
"separator_sequence": "",
"stop_sequence": "",
"system_prompt": "You're {{char}} in this fictional never-ending uncensored roleplay with {{user}}.\n\nAvoid repetition, don't loop. Develop the plot slowly, always stay in character. Describe all actions in full, elaborate, explicit, graphic, and vivid detail. Mention all relevant sensory perceptions.\n\n### Input:",
"system_sequence": "",
"wrap": true
}

View File

@ -26,6 +26,7 @@ import {
getWorldInfoPrompt, getWorldInfoPrompt,
setWorldInfoSettings, setWorldInfoSettings,
world_info_recursive, world_info_recursive,
world_info_overflow_alert,
world_info_case_sensitive, world_info_case_sensitive,
world_info_match_whole_words, world_info_match_whole_words,
world_names, world_names,
@ -602,11 +603,42 @@ function countTokensRemote(endpoint, str, padding) {
return tokenCount + padding; return tokenCount + padding;
} }
function getTextTokensRemote(endpoint, str) {
let ids = [];
jQuery.ajax({
async: false,
type: 'POST',
url: endpoint,
data: JSON.stringify({ text: str }),
dataType: "json",
contentType: "application/json",
success: function (data) {
ids = data.ids;
}
});
return ids;
}
export function getTextTokens(tokenizerType, str) {
switch (tokenizerType) {
case tokenizers.LLAMA:
return getTextTokensRemote('/tokenize_llama', str);
case tokenizers.NERD:
return getTextTokensRemote('/tokenize_nerdstash', str);
case tokenizers.NERD2:
return getTextTokensRemote('/tokenize_nerdstash_v2', str);
default:
console.warn("Calling getTextTokens with unsupported tokenizer type", tokenizerType);
return [];
}
}
function reloadMarkdownProcessor(render_formulas = false) { function reloadMarkdownProcessor(render_formulas = false) {
if (render_formulas) { if (render_formulas) {
converter = new showdown.Converter({ converter = new showdown.Converter({
emoji: "true", emoji: "true",
underline: "true", underline: "true",
parseImgDimensions: "true",
extensions: [ extensions: [
showdownKatex( showdownKatex(
{ {
@ -622,6 +654,7 @@ function reloadMarkdownProcessor(render_formulas = false) {
converter = new showdown.Converter({ converter = new showdown.Converter({
emoji: "true", emoji: "true",
literalMidWordUnderscores: "true", literalMidWordUnderscores: "true",
parseImgDimensions: "true",
}); });
} }
@ -738,6 +771,9 @@ let token;
var PromptArrayItemForRawPromptDisplay; var PromptArrayItemForRawPromptDisplay;
export let active_character = ""
export let active_group = ""
export function getRequestHeaders() { export function getRequestHeaders() {
return { return {
"Content-Type": "application/json", "Content-Type": "application/json",
@ -787,6 +823,14 @@ function checkOnlineStatus() {
} }
} }
export function setActiveCharacter(character) {
active_character = character;
}
export function setActiveGroup(group) {
active_group = group;
}
async function getStatus() { async function getStatus() {
if (is_get_status) { if (is_get_status) {
if (main_api == "koboldhorde") { if (main_api == "koboldhorde") {
@ -1813,7 +1857,7 @@ function getPersonaDescription(storyString) {
case persona_description_positions.BEFORE_CHAR: case persona_description_positions.BEFORE_CHAR:
return `${substituteParams(power_user.persona_description)}\n${storyString}`; return `${substituteParams(power_user.persona_description)}\n${storyString}`;
case persona_description_positions.AFTER_CHAR: case persona_description_positions.AFTER_CHAR:
return `${storyString}\n${substituteParams(power_user.persona_description)}`; return `${storyString}${substituteParams(power_user.persona_description)}\n`;
default: default:
if (shouldWIAddPrompt) { if (shouldWIAddPrompt) {
const originalAN = extension_prompts[NOTE_MODULE_NAME].value const originalAN = extension_prompts[NOTE_MODULE_NAME].value
@ -2476,7 +2520,7 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
} }
if (i === arrMes.length - 1 && !item.trim().startsWith(name1 + ":")) { if (i === arrMes.length - 1 && !item.trim().startsWith(name1 + ":")) {
if (textareaText == "") { //if (textareaText == "") {
// Cohee: I think this was added to allow the model to continue // Cohee: I think this was added to allow the model to continue
// where it left off by removing the trailing newline at the end // where it left off by removing the trailing newline at the end
// that was added by chat2 generator. This causes problems with // that was added by chat2 generator. This causes problems with
@ -2484,7 +2528,7 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
// removing a newline ONLY at the end of the string if it exists. // removing a newline ONLY at the end of the string if it exists.
item = item.replace(/\n?$/, ''); item = item.replace(/\n?$/, '');
//item = item.substr(0, item.length - 1); //item = item.substr(0, item.length - 1);
} //}
} }
if (is_pygmalion && !isInstruct) { if (is_pygmalion && !isInstruct) {
if (item.trim().startsWith(name1)) { if (item.trim().startsWith(name1)) {
@ -2677,7 +2721,7 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
} }
else if (main_api == 'novel') { else if (main_api == 'novel') {
const this_settings = novelai_settings[novelai_setting_names[nai_settings.preset_settings_novel]]; const this_settings = novelai_settings[novelai_setting_names[nai_settings.preset_settings_novel]];
generate_data = getNovelGenerationData(finalPromt, this_settings, this_amount_gen); generate_data = getNovelGenerationData(finalPromt, this_settings, this_amount_gen, isImpersonate);
} }
else if (main_api == 'openai') { else if (main_api == 'openai') {
let [prompt, counts] = await prepareOpenAIMessages({ let [prompt, counts] = await prepareOpenAIMessages({
@ -2779,7 +2823,8 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
}); });
if (!response.ok) { if (!response.ok) {
throw new Error(response.status); const error = await response.json();
throw error;
} }
const data = await response.json(); const data = await response.json();
@ -2957,6 +3002,10 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
}; };
function onError(exception) { function onError(exception) {
if (typeof exception?.error?.message === 'string') {
toastr.error(exception.error.message, 'Error', { timeOut: 10000, extendedTimeOut: 20000 });
}
reject(exception); reject(exception);
$("#send_textarea").removeAttr('disabled'); $("#send_textarea").removeAttr('disabled');
is_send_press = false; is_send_press = false;
@ -5019,6 +5068,10 @@ async function getSettings(type) {
highlightSelectedAvatar(); highlightSelectedAvatar();
setPersonaDescription(); setPersonaDescription();
//Load the active character and group
active_character = settings.active_character;
active_group = settings.active_group;
//Load the API server URL from settings //Load the API server URL from settings
api_server = settings.api_server; api_server = settings.api_server;
$("#api_url_text").val(api_server); $("#api_url_text").val(api_server);
@ -5061,6 +5114,8 @@ async function saveSettings(type) {
data: JSON.stringify({ data: JSON.stringify({
firstRun: firstRun, firstRun: firstRun,
username: name1, username: name1,
active_character: active_character,
active_group: active_group,
api_server: api_server, api_server: api_server,
api_server_textgenerationwebui: api_server_textgenerationwebui, api_server_textgenerationwebui: api_server_textgenerationwebui,
api_use_mancer_webui: api_use_mancer_webui, api_use_mancer_webui: api_use_mancer_webui,
@ -5073,6 +5128,7 @@ async function saveSettings(type) {
world_info_depth: world_info_depth, world_info_depth: world_info_depth,
world_info_budget: world_info_budget, world_info_budget: world_info_budget,
world_info_recursive: world_info_recursive, world_info_recursive: world_info_recursive,
world_info_overflow_alert: world_info_overflow_alert,
world_info_case_sensitive: world_info_case_sensitive, world_info_case_sensitive: world_info_case_sensitive,
world_info_match_whole_words: world_info_match_whole_words, world_info_match_whole_words: world_info_match_whole_words,
world_info_character_strategy: world_info_character_strategy, world_info_character_strategy: world_info_character_strategy,
@ -6825,6 +6881,11 @@ function importCharacter(file) {
contentType: false, contentType: false,
processData: false, processData: false,
success: async function (data) { success: async function (data) {
if (data.error) {
toastr.error('The file is likely invalid or corrupted.', 'Could not import character');
return;
}
if (data.file_name !== undefined) { if (data.file_name !== undefined) {
$('#character_search_bar').val('').trigger('input'); $('#character_search_bar').val('').trigger('input');
$("#rm_info_block").transition({ opacity: 0, duration: 0 }); $("#rm_info_block").transition({ opacity: 0, duration: 0 });
@ -7757,6 +7818,51 @@ $(document).ready(function () {
else if (id == "option_delete_mes") { else if (id == "option_delete_mes") {
setTimeout(openMessageDelete, animation_duration); setTimeout(openMessageDelete, animation_duration);
} }
else if (id == "option_close_chat") {
if (is_send_press == false) {
clearChat();
chat.length = 0;
resetSelectedGroup();
setCharacterId(undefined);
setCharacterName('');
setActiveCharacter(null);
setActiveGroup(null);
this_edit_mes_id = undefined;
chat_metadata = {};
selected_button = "characters";
$("#rm_button_selected_ch").children("h2").text('');
select_rm_characters();
sendSystemMessage(system_message_types.WELCOME);
} else {
toastr.info("Please stop the message generation first.");
}
}
else if (id === "option_settings") {
//var checkBox = document.getElementById("waifuMode");
var topBar = document.getElementById("top-bar");
var topSettingsHolder = document.getElementById("top-settings-holder");
var divchat = document.getElementById("chat");
//if (checkBox.checked) {
if (topBar.style.display === "none") {
topBar.style.display = ""; // or "inline-block" if that's the original display value
topSettingsHolder.style.display = ""; // or "inline-block" if that's the original display value
divchat.style.borderRadius = "";
divchat.style.backgroundColor = "";
} else {
divchat.style.borderRadius = "10px"; // Adjust the value to control the roundness of the corners
divchat.style.backgroundColor = ""; // Set the background color to your preference
topBar.style.display = "none";
topSettingsHolder.style.display = "none";
}
//}
}
hideMenu(); hideMenu();
}); });

View File

@ -13,6 +13,10 @@ import {
menu_type, menu_type,
max_context, max_context,
saveSettingsDebounced, saveSettingsDebounced,
active_group,
active_character,
setActiveGroup,
setActiveCharacter,
} from "../script.js"; } from "../script.js";
import { import {
@ -239,7 +243,6 @@ $("#rm_ch_create_block").on("input", function () { countTokensDebounced(); });
$("#character_popup").on("input", function () { countTokensDebounced(); }); $("#character_popup").on("input", function () { countTokensDebounced(); });
//function: //function:
export function RA_CountCharTokens() { export function RA_CountCharTokens() {
$("#result_info").html("");
//console.log('RA_TC -- starting with this_chid = ' + this_chid); //console.log('RA_TC -- starting with this_chid = ' + this_chid);
if (menu_type === "create") { //if new char if (menu_type === "create") { //if new char
function saveFormVariables() { function saveFormVariables() {
@ -331,11 +334,21 @@ export function RA_CountCharTokens() {
characterStatsHandler(characters, this_chid); characterStatsHandler(characters, this_chid);
}); });
} }
//Auto Load Last Charcter -- (fires when active_character is defined and auto_load_chat is true) /**
* Auto load chat with the last active character or group.
* Fires when active_character is defined and auto_load_chat is true.
* The function first tries to find a character with a specific ID from the global settings.
* If it doesn't exist, it tries to find a group with a specific grid from the global settings.
* If the character list hadn't been loaded yet, it calls itself again after 100ms delay.
* The character or group is selected (clicked) if it is found.
*/
async function RA_autoloadchat() { async function RA_autoloadchat() {
if (document.getElementById('CharID0') !== null) { if (document.getElementById('CharID0') !== null) {
var charToAutoLoad = document.getElementById('CharID' + LoadLocal('ActiveChar')); // active character is the name, we should look it up in the character list and get the id
let groupToAutoLoad = document.querySelector(`.group_select[grid="${LoadLocal('ActiveGroup')}"]`); let active_character_id = Object.keys(characters).find(key => characters[key].avatar === active_character);
var charToAutoLoad = document.getElementById('CharID' + active_character_id);
let groupToAutoLoad = document.querySelector(`.group_select[grid="${active_group}"]`);
if (charToAutoLoad != null) { if (charToAutoLoad != null) {
$(charToAutoLoad).click(); $(charToAutoLoad).click();
} }
@ -343,7 +356,7 @@ async function RA_autoloadchat() {
$(groupToAutoLoad).click(); $(groupToAutoLoad).click();
} }
// if the charcter list hadn't been loaded yet, try again. // if the character list hadn't been loaded yet, try again.
} else { setTimeout(RA_autoloadchat, 100); } } else { setTimeout(RA_autoloadchat, 100); }
} }
@ -904,16 +917,22 @@ $("document").ready(function () {
$("#rm_button_characters").click(function () { SaveLocal('SelectedNavTab', 'rm_button_characters'); }); $("#rm_button_characters").click(function () { SaveLocal('SelectedNavTab', 'rm_button_characters'); });
// when a char is selected from the list, save them as the auto-load character for next page load // when a char is selected from the list, save them as the auto-load character for next page load
// when a char is selected from the list, save their name as the auto-load character for next page load
$(document).on("click", ".character_select", function () { $(document).on("click", ".character_select", function () {
SaveLocal('ActiveChar', $(this).attr('chid')); setActiveCharacter($(this).find('.avatar').attr('title'));
SaveLocal('ActiveGroup', null); setActiveGroup(null);
saveSettingsDebounced();
}); });
$(document).on("click", ".group_select", function () { $(document).on("click", ".group_select", function () {
SaveLocal('ActiveChar', null); setActiveCharacter(null);
SaveLocal('ActiveGroup', $(this).data('id')); setActiveGroup($(this).data('id'));
saveSettingsDebounced();
}); });
//this makes the chat input text area resize vertically to match the text size (limited by CSS at 50% window height) //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 () { $('#send_textarea').on('input', function () {
this.style.height = '40px'; this.style.height = '40px';
@ -1029,11 +1048,10 @@ $("document").ready(function () {
} }
if (event.ctrlKey && event.key == "ArrowUp") { //edits last USER message if chatbar is empty and focused if (event.ctrlKey && event.key == "ArrowUp") { //edits last USER message if chatbar is empty and focused
console.debug('got ctrl+uparrow input');
if ( if (
$("#send_textarea").val() === '' && $("#send_textarea").val() === '' &&
chatbarInFocus === true && chatbarInFocus === true &&
$(".swipe_right:last").css('display') === 'flex' && ($(".swipe_right:last").css('display') === 'flex' || $('.last_mes').attr('is_system') === 'true') &&
$("#character_popup").css("display") === "none" && $("#character_popup").css("display") === "none" &&
$("#shadow_select_chat_popup").css("display") === "none" $("#shadow_select_chat_popup").css("display") === "none"
) { ) {
@ -1041,7 +1059,7 @@ $("document").ready(function () {
const lastIsUserMes = isUserMesList[isUserMesList.length - 1]; const lastIsUserMes = isUserMesList[isUserMesList.length - 1];
const editMes = lastIsUserMes.querySelector('.mes_block .mes_edit'); const editMes = lastIsUserMes.querySelector('.mes_block .mes_edit');
if (editMes !== null) { if (editMes !== null) {
$(editMes).click(); $(editMes).trigger('click');
} }
} }
} }
@ -1062,5 +1080,59 @@ $("document").ready(function () {
} }
} }
} }
if (event.key == "Escape") { //closes various panels
if ($("#curEditTextarea").is(":visible")) {
return
}
if ($("#dialogue_popup").is(":visible")) {
if ($("#dialogue_popup_cancel").is(":visible")) {
$("#dialogue_popup_cancel").trigger('click');
return
} else {
$("#dialogue_popup_ok").trigger('click')
return
}
}
if ($("#select_chat_popup").is(":visible")) {
$("#select_chat_cross").trigger('click');
return
}
if ($("#character_popup").is(":visible")) {
$("#character_cross").trigger('click');
return
}
if ($(".drawer-content")
.not('#WorldInfo')
.not('#left-nav-panel')
.not('#right-nav-panel')
.is(":visible")) {
let visibleDrawerContent = $(".drawer-content:visible")
.not('#WorldInfo')
.not('#left-nav-panel')
.not('#right-nav-panel')
$(visibleDrawerContent).parent().find('.drawer-icon').trigger('click');
return
}
if ($("#floatingPrompt").is(":visible")) {
$("#ANClose").trigger('click');
return
}
if ($("#WorldInfo").is(":visible")) {
$("#WIDrawerIcon").trigger('click');
return
}
if ($("#left-nav-panel").is(":visible")) {
$("#leftNavDrawerIcon").trigger('click');
return
}
if ($("#right-nav-panel").is(":visible")) {
$("#rightNavDrawerIcon").trigger('click');
return
}
}
} }
}); });

View File

@ -9,6 +9,7 @@ const MODULE_NAME = 'expressions';
const UPDATE_INTERVAL = 2000; const UPDATE_INTERVAL = 2000;
const FALLBACK_EXPRESSION = 'joy'; const FALLBACK_EXPRESSION = 'joy';
const DEFAULT_EXPRESSIONS = [ const DEFAULT_EXPRESSIONS = [
"live2d",
"admiration", "admiration",
"amusement", "amusement",
"anger", "anger",
@ -44,6 +45,9 @@ let lastCharacter = undefined;
let lastMessage = null; let lastMessage = null;
let spriteCache = {}; let spriteCache = {};
let inApiCall = false; let inApiCall = false;
let live2d_var = false;
let previousSrc = null;
function isVisualNovelMode() { function isVisualNovelMode() {
return Boolean(!isMobile() && power_user.waifuMode && getContext().groupId); return Boolean(!isMobile() && power_user.waifuMode && getContext().groupId);
@ -392,6 +396,94 @@ function onExpressionsShowDefaultInput() {
} }
} }
async function loadLiveChar() {
if (!modules.includes('live2d')) {
console.debug('live2d module is disabled');
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 live2dPath = `/characters/${encodeURIComponent(spriteFolderName)}/live2d.png`;
try {
const spriteResponse = await fetch(live2dPath);
if (!spriteResponse.ok) {
throw new Error(spriteResponse.statusText);
}
const spriteBlob = await spriteResponse.blob();
const spriteFile = new File([spriteBlob], 'live2d.png', { type: 'image/png' });
const formData = new FormData();
formData.append('file', spriteFile);
const url = new URL(getApiUrl());
url.pathname = '/api/live2d/load';
const loadResponse = await doExtrasFetch(url, {
method: 'POST',
body: formData,
});
if (!loadResponse.ok) {
throw new Error(loadResponse.statusText);
}
const loadResponseText = await loadResponse.text();
console.log(`Load live2d response: ${loadResponseText}`);
} catch (error) {
console.error(`Error loading live2d image: ${live2dPath} - ${error}`);
}
}
function handleImageChange(isChecked) {
const imgElement = document.querySelector('img#expression-image.expression');
if (!imgElement) {
console.log("Cannot find addExpressionImage()");
return;
}
if (isChecked) {
// Method get IP of endpoint
if (imgElement.src !== getApiUrl() + '/api/live2d/result_feed') {
const expressionListItemElement = document.querySelector('#live2d');
const expressionImageElement = expressionListItemElement.querySelector('.expression_list_image');
const newSrc = expressionImageElement.src;
doExtrasFetch(newSrc, {
method: 'HEAD',
})
.then(response => {
if (response.ok) {
imgElement.src = getApiUrl() + '/api/live2d/result_feed';
}
})
.catch(error => {
console.error(error); // Log the error if necessary
});
} else if (previousSrc) {
imgElement.src = previousSrc; // Revert the src to its previous value
}
} else if (previousSrc !== null) {
imgElement.src = previousSrc; // Revert the src to its previous value
}
live2d_var = isChecked;
}
async function moduleWorker() { async function moduleWorker() {
const context = getContext(); const context = getContext();
@ -405,6 +497,23 @@ async function moduleWorker() {
if (context.groupId !== lastCharacter && context.characterId !== lastCharacter) { if (context.groupId !== lastCharacter && context.characterId !== lastCharacter) {
removeExpression(); removeExpression();
spriteCache = {}; spriteCache = {};
previousSrc = null;
//uncheck live image
let checkbox = document.getElementById('image_type_toggle');
if (checkbox.checked) {
checkbox.click();
}
//clear expression
let imgElement = document.getElementById('expression-image');
imgElement.src = "";
//Load new char
if (live2d_var) {
loadLiveChar();
}
} }
const vnMode = isVisualNovelMode(); const vnMode = isVisualNovelMode();
@ -654,7 +763,6 @@ async function getSpritesList(name) {
try { try {
const result = await fetch(`/get_sprites?name=${encodeURIComponent(name)}`); const result = await fetch(`/get_sprites?name=${encodeURIComponent(name)}`);
let sprites = result.ok ? (await result.json()) : []; let sprites = result.ok ? (await result.json()) : [];
return sprites; return sprites;
} }
@ -697,114 +805,126 @@ async function getExpressionsList() {
} }
async function setExpression(character, expression, force) { async function setExpression(character, expression, force) {
console.debug('entered setExpressions'); if (live2d_var == false) {
await validateImages(character);
const img = $('img.expression');
const prevExpressionSrc = img.attr('src');
const expressionClone = img.clone()
const sprite = (spriteCache[character] && spriteCache[character].find(x => x.label === expression)); console.debug('entered setExpressions');
console.debug('checking for expression images to show..'); await validateImages(character);
if (sprite) { const img = $('img.expression');
console.debug('setting expression from character images folder'); const prevExpressionSrc = img.attr('src');
const expressionClone = img.clone()
if (force && isVisualNovelMode()) { const sprite = (spriteCache[character] && spriteCache[character].find(x => x.label === expression));
const context = getContext(); console.debug('checking for expression images to show..');
const group = context.groups.find(x => x.id === context.groupId); if (sprite) {
console.debug('setting expression from character images folder');
for (const member of group.members) { if (force && isVisualNovelMode()) {
const groupMember = context.characters.find(x => x.avatar === member); const context = getContext();
const group = context.groups.find(x => x.id === context.groupId);
if (!groupMember) { for (const member of group.members) {
continue; const groupMember = context.characters.find(x => x.avatar === member);
if (!groupMember) {
continue;
}
if (groupMember.name == character) {
await setImage($(`.expression-holder[data-avatar="${member}"] img`), sprite.path);
return;
}
} }
}
//only swap expressions when necessary
if (prevExpressionSrc !== sprite.path
&& !img.hasClass('expression-animating')) {
//clone expression
expressionClone.addClass('expression-clone')
//make invisible and remove id to prevent double ids
//must be made invisible to start because they share the same Z-index
expressionClone.attr('id', '').css({ opacity: 0 });
//add new sprite path to clone src
expressionClone.attr('src', sprite.path);
//add invisible clone to html
expressionClone.appendTo($("#expression-holder"))
if (groupMember.name == character) { const duration = 200;
await setImage($(`.expression-holder[data-avatar="${member}"] img`), sprite.path);
return; //add animation flags to both images
//to prevent multiple expression changes happening simultaneously
img.addClass('expression-animating');
// Set the parent container's min width and height before running the transition
const imgWidth = img.width();
const imgHeight = img.height();
const expressionHolder = img.parent();
expressionHolder.css('min-width', imgWidth > 100 ? imgWidth : 100);
expressionHolder.css('min-height', imgHeight > 100 ? imgHeight : 100);
//position absolute prevent the original from jumping around during transition
img.css('position', 'absolute');
expressionClone.addClass('expression-animating');
//fade the clone in
expressionClone.css({
opacity: 0
}).animate({
opacity: 1
}, duration)
//when finshed fading in clone, fade out the original
.promise().done(function () {
img.animate({
opacity: 0
}, duration);
//remove old expression
img.remove();
//replace ID so it becomes the new 'original' expression for next change
expressionClone.attr('id', 'expression-image');
expressionClone.removeClass('expression-animating');
// Reset the expression holder min height and width
expressionHolder.css('min-width', 100);
expressionHolder.css('min-height', 100);
});
expressionClone.removeClass('expression-clone');
expressionClone.removeClass('default');
expressionClone.off('error');
expressionClone.on('error', function () {
console.debug('Expression image error', sprite.path);
$(this).attr('src', '');
$(this).off('error');
if (force && extension_settings.expressions.showDefault) {
setDefault();
}
});
} else {
if (extension_settings.expressions.showDefault) {
setDefault();
} }
} }
} }
//only swap expressions when necessary
if (prevExpressionSrc !== sprite.path
&& !img.hasClass('expression-animating')) {
//clone expression
expressionClone.addClass('expression-clone')
//make invisible and remove id to prevent double ids
//must be made invisible to start because they share the same Z-index
expressionClone.attr('id', '').css({ opacity: 0 });
//add new sprite path to clone src
expressionClone.attr('src', sprite.path);
//add invisible clone to html
expressionClone.appendTo($("#expression-holder"))
const duration = 200; function setDefault() {
console.debug('setting default');
//add animation flags to both images const defImgUrl = `/img/default-expressions/${expression}.png`;
//to prevent multiple expression changes happening simultaneously //console.log(defImgUrl);
img.addClass('expression-animating'); img.attr('src', defImgUrl);
img.addClass('default');
// Set the parent container's min width and height before running the transition
const imgWidth = img.width();
const imgHeight = img.height();
const expressionHolder = img.parent();
expressionHolder.css('min-width', imgWidth > 100 ? imgWidth : 100);
expressionHolder.css('min-height', imgHeight > 100 ? imgHeight : 100);
//position absolute prevent the original from jumping around during transition
img.css('position', 'absolute');
expressionClone.addClass('expression-animating');
//fade the clone in
expressionClone.css({
opacity: 0
}).animate({
opacity: 1
}, duration)
//when finshed fading in clone, fade out the original
.promise().done(function () {
img.animate({
opacity: 0
}, duration);
//remove old expression
img.remove();
//replace ID so it becomes the new 'original' expression for next change
expressionClone.attr('id', 'expression-image');
expressionClone.removeClass('expression-animating');
// Reset the expression holder min height and width
expressionHolder.css('min-width', 100);
expressionHolder.css('min-height', 100);
});
expressionClone.removeClass('expression-clone');
expressionClone.removeClass('default');
expressionClone.off('error');
expressionClone.on('error', function () {
console.debug('Expression image error', sprite.path);
$(this).attr('src', '');
$(this).off('error');
if (force && extension_settings.expressions.showDefault) {
setDefault();
}
});
} }
document.getElementById("expression-holder").style.display = '';
} else { if (live2d_var == true) {
if (extension_settings.expressions.showDefault) { // Find the <img> element with id="expression-image" and class="expression"
setDefault(); const imgElement = document.querySelector('img#expression-image.expression');
//console.log("searching");
if (imgElement) {
console.log("setting value");
imgElement.src = getApiUrl() + '/api/live2d/result_feed';
}
} }
} }
function setDefault() {
console.debug('setting default');
const defImgUrl = `/img/default-expressions/${expression}.png`;
console.log(defImgUrl);
img.attr('src', defImgUrl);
img.addClass('default');
}
document.getElementById("expression-holder").style.display = '';
} }
function onClickExpressionImage() { function onClickExpressionImage() {
@ -1052,7 +1172,6 @@ function setExpressionOverrideHtml(forceClear = false) {
$('body').append(element); $('body').append(element);
} }
function addSettings() { function addSettings() {
const html = ` const html = `
<div class="expression_settings"> <div class="expression_settings">
<div class="inline-drawer"> <div class="inline-drawer">
@ -1060,8 +1179,16 @@ function setExpressionOverrideHtml(forceClear = false) {
<b>Character Expressions</b> <b>Character Expressions</b>
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div> <div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
</div> </div>
<div class="inline-drawer-content"> <div class="inline-drawer-content">
<div class="offline_mode"> <!-- 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 - Live2d (extras)</label>
</div>
<div class="offline_mode">
<small>You are in offline mode. Click on the image below to set the expression.</small> <small>You are in offline mode. Click on the image below to set the expression.</small>
</div> </div>
<div class="flex-container flexnowrap"> <div class="flex-container flexnowrap">
@ -1090,6 +1217,7 @@ function setExpressionOverrideHtml(forceClear = false) {
</form> </form>
</div> </div>
`; `;
$('#extensions_settings').append(html); $('#extensions_settings').append(html);
$('#expression_override_button').on('click', onClickExpressionOverrideButton); $('#expression_override_button').on('click', onClickExpressionOverrideButton);
$('#expressions_show_default').on('input', onExpressionsShowDefaultInput); $('#expressions_show_default').on('input', onExpressionsShowDefaultInput);
@ -1105,6 +1233,15 @@ function setExpressionOverrideHtml(forceClear = false) {
$(document).on('click', '.expression_list_delete', onClickExpressionDelete); $(document).on('click', '.expression_list_delete', onClickExpressionDelete);
$(window).on("resize", updateVisualNovelModeDebounced); $(window).on("resize", updateVisualNovelModeDebounced);
$('.expression_settings').hide(); $('.expression_settings').hide();
$('#image_type_toggle').on('change', function () {
const isChecked = this.checked;
if (isChecked) {
loadLiveChar();
}
handleImageChange(isChecked);
});
} }
addExpressionImage(); addExpressionImage();
@ -1116,6 +1253,7 @@ function setExpressionOverrideHtml(forceClear = false) {
moduleWorker(); moduleWorker();
dragElement($("#expression-holder")) dragElement($("#expression-holder"))
eventSource.on(event_types.CHAT_CHANGED, () => { eventSource.on(event_types.CHAT_CHANGED, () => {
//console.log("checked: " + live2d_var);
setExpressionOverrideHtml(); setExpressionOverrideHtml();
if (isVisualNovelMode()) { if (isVisualNovelMode()) {

View File

@ -544,6 +544,30 @@ async function onSelectInjectFile(e) {
} }
} }
// Gets the length of character description in the current context
function getCharacterDataLength() {
const context = getContext();
const character = context.characters[context.characterId];
if (typeof character?.data !== 'object') {
return 0;
}
let characterDataLength = 0;
for (const [key, value] of Object.entries(character.data)) {
if (typeof value !== 'string') {
continue;
}
if (['description', 'personality', 'scenario'].includes(key)) {
characterDataLength += character.data[key].length;
}
}
return characterDataLength;
}
/* /*
* Automatically adjusts the extension settings for the optimal number of messages to keep and query based * Automatically adjusts the extension settings for the optimal number of messages to keep and query based
* on the chat history and a specified maximum context length. * on the chat history and a specified maximum context length.
@ -558,6 +582,10 @@ function doAutoAdjust(chat, maxContext) {
return; return;
} }
// Adjust max context for character defs length
maxContext = Math.floor(maxContext - (getCharacterDataLength() / CHARACTERS_PER_TOKEN_RATIO));
console.debug('CHROMADB: Max context adjusted for character defs: %o', maxContext);
console.debug('CHROMADB: Mean message length (characters): %o', meanMessageLength); console.debug('CHROMADB: Mean message length (characters): %o', meanMessageLength);
// Convert to number of "tokens" // Convert to number of "tokens"
const meanMessageLengthTokens = Math.ceil(meanMessageLength / CHARACTERS_PER_TOKEN_RATIO); const meanMessageLengthTokens = Math.ceil(meanMessageLength / CHARACTERS_PER_TOKEN_RATIO);

View File

@ -1,4 +1,4 @@
import { getStringHash, debounce, waitUntilCondition } from "../../utils.js"; import { getStringHash, debounce, waitUntilCondition, extractAllWords } from "../../utils.js";
import { getContext, getApiUrl, extension_settings, doExtrasFetch, modules } from "../../extensions.js"; 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 { eventSource, event_types, extension_prompt_types, generateQuietPrompt, is_send_press, saveSettingsDebounced, substituteParams } from "../../../script.js";
export { MODULE_NAME }; export { MODULE_NAME };
@ -12,7 +12,21 @@ let lastMessageHash = null;
let lastMessageId = null; let lastMessageId = null;
let inApiCall = false; let inApiCall = false;
const formatMemoryValue = (value) => value ? `Summary: ${value.trim()}` : ''; const formatMemoryValue = function (value) {
if (!value) {
return '';
}
value = value.trim();
if (extension_settings.memory.template) {
let result = extension_settings.memory.template.replace(/{{summary}}/i, value);
return substituteParams(result);
} else {
return `Summary: ${value}`;
}
}
const saveChatDebounced = debounce(() => getContext().saveChat(), 2000); const saveChatDebounced = debounce(() => getContext().saveChat(), 2000);
const summary_sources = { const summary_sources = {
@ -21,6 +35,7 @@ const summary_sources = {
}; };
const defaultPrompt = '[Pause your roleplay. Summarize the most important facts and events that have happened in the chat so far. If a summary already exists in your memory, use that as a base and expand with new facts. Limit the summary to {{words}} words or less. Your response should include nothing but the summary.]'; const defaultPrompt = '[Pause your roleplay. Summarize the most important facts and events that have happened in the chat so far. If a summary already exists in your memory, use that as a base and expand with new facts. Limit the summary to {{words}} words or less. Your response should include nothing but the summary.]';
const defaultTemplate = '[Summary: {{summary}}]';
const defaultSettings = { const defaultSettings = {
minLongMemory: 16, minLongMemory: 16,
@ -46,6 +61,9 @@ const defaultSettings = {
memoryFrozen: false, memoryFrozen: false,
source: summary_sources.extras, source: summary_sources.extras,
prompt: defaultPrompt, prompt: defaultPrompt,
template: defaultTemplate,
position: extension_prompt_types.AFTER_SCENARIO,
depth: 2,
promptWords: 200, promptWords: 200,
promptMinWords: 25, promptMinWords: 25,
promptMaxWords: 1000, promptMaxWords: 1000,
@ -54,6 +72,10 @@ const defaultSettings = {
promptMinInterval: 1, promptMinInterval: 1,
promptMaxInterval: 100, promptMaxInterval: 100,
promptIntervalStep: 1, promptIntervalStep: 1,
promptForceWords: 0,
promptForceWordsStep: 100,
promptMinForceWords: 0,
promptMaxForceWords: 10000,
}; };
function loadSettings() { function loadSettings() {
@ -61,20 +83,10 @@ function loadSettings() {
Object.assign(extension_settings.memory, defaultSettings); Object.assign(extension_settings.memory, defaultSettings);
} }
if (extension_settings.memory.source === undefined) { for (const key of Object.keys(defaultSettings)) {
extension_settings.memory.source = defaultSettings.source; if (extension_settings.memory[key] === undefined) {
} extension_settings.memory[key] = defaultSettings[key];
}
if (extension_settings.memory.prompt === undefined) {
extension_settings.memory.prompt = defaultSettings.prompt;
}
if (extension_settings.memory.promptWords === undefined) {
extension_settings.memory.promptWords = defaultSettings.promptWords;
}
if (extension_settings.memory.promptInterval === undefined) {
extension_settings.memory.promptInterval = defaultSettings.promptInterval;
} }
$('#summary_source').val(extension_settings.memory.source).trigger('change'); $('#summary_source').val(extension_settings.memory.source).trigger('change');
@ -87,6 +99,10 @@ function loadSettings() {
$('#memory_prompt').val(extension_settings.memory.prompt).trigger('input'); $('#memory_prompt').val(extension_settings.memory.prompt).trigger('input');
$('#memory_prompt_words').val(extension_settings.memory.promptWords).trigger('input'); $('#memory_prompt_words').val(extension_settings.memory.promptWords).trigger('input');
$('#memory_prompt_interval').val(extension_settings.memory.promptInterval).trigger('input'); $('#memory_prompt_interval').val(extension_settings.memory.promptInterval).trigger('input');
$('#memory_template').val(extension_settings.memory.template).trigger('input');
$('#memory_depth').val(extension_settings.memory.depth).trigger('input');
$(`input[name="memory_position"][value="${extension_settings.memory.position}"]`).prop('checked', true).trigger('input');
$('#memory_prompt_words_force').val(extension_settings.memory.promptForceWords).trigger('input');
} }
function onSummarySourceChange(event) { function onSummarySourceChange(event) {
@ -170,6 +186,31 @@ function onMemoryPromptInput() {
saveSettingsDebounced(); saveSettingsDebounced();
} }
function onMemoryTemplateInput() {
const value = $(this).val();
extension_settings.memory.template = value;
saveSettingsDebounced();
}
function onMemoryDepthInput() {
const value = $(this).val();
extension_settings.memory.depth = Number(value);
saveSettingsDebounced();
}
function onMemoryPositionChange(e) {
const value = e.target.value;
extension_settings.memory.position = value;
saveSettingsDebounced();
}
function onMemoryPromptWordsForceInput() {
const value = $(this).val();
extension_settings.memory.promptForceWords = Number(value);
$('#memory_prompt_words_force_value').text(extension_settings.memory.promptForceWords);
saveSettingsDebounced();
}
function saveLastValues() { function saveLastValues() {
const context = getContext(); const context = getContext();
lastGroupId = context.groupId; lastGroupId = context.groupId;
@ -293,7 +334,7 @@ async function summarizeChat(context) {
async function summarizeChatMain(context, force) { async function summarizeChatMain(context, force) {
try { try {
// Wait for the send button to be released // Wait for the send button to be released
waitUntilCondition(() => is_send_press === false, 10000, 100); waitUntilCondition(() => is_send_press === false, 30000, 100);
} catch { } catch {
console.debug('Timeout waiting for is_send_press'); console.debug('Timeout waiting for is_send_press');
return; return;
@ -310,19 +351,30 @@ async function summarizeChatMain(context, force) {
} }
let messagesSinceLastSummary = 0; let messagesSinceLastSummary = 0;
let wordsSinceLastSummary = 0;
let conditionSatisfied = false;
for (let i = context.chat.length - 1; i >= 0; i--) { for (let i = context.chat.length - 1; i >= 0; i--) {
if (context.chat[i].extra && context.chat[i].extra.memory) { if (context.chat[i].extra && context.chat[i].extra.memory) {
break; break;
} }
messagesSinceLastSummary++; messagesSinceLastSummary++;
wordsSinceLastSummary += extractAllWords(context.chat[i].mes).length;
} }
if (messagesSinceLastSummary < extension_settings.memory.promptInterval && !force) { if (messagesSinceLastSummary >= extension_settings.memory.promptInterval) {
console.debug(`Not enough messages since last summary (messages: ${messagesSinceLastSummary}, interval: ${extension_settings.memory.promptInterval}`); conditionSatisfied = true;
}
if (extension_settings.memory.promptForceWords && wordsSinceLastSummary >= extension_settings.memory.promptForceWords) {
conditionSatisfied = true;
}
if (!conditionSatisfied && !force) {
console.debug(`Summary conditions not satisfied (messages: ${messagesSinceLastSummary}, interval: ${extension_settings.memory.promptInterval}, words: ${wordsSinceLastSummary}, force words: ${extension_settings.memory.promptForceWords})`);
return; return;
} }
console.log('Summarizing chat, messages since last summary: ' + messagesSinceLastSummary); console.log('Summarizing chat, messages since last summary: ' + messagesSinceLastSummary, 'words since last summary: ' + wordsSinceLastSummary);
const prompt = substituteParams(extension_settings.memory.prompt) const prompt = substituteParams(extension_settings.memory.prompt)
.replace(/{{words}}/gi, extension_settings.memory.promptWords); .replace(/{{words}}/gi, extension_settings.memory.promptWords);
@ -458,9 +510,11 @@ function onMemoryContentInput() {
function setMemoryContext(value, saveToMessage) { function setMemoryContext(value, saveToMessage) {
const context = getContext(); const context = getContext();
context.setExtensionPrompt(MODULE_NAME, formatMemoryValue(value), extension_prompt_types.AFTER_SCENARIO); context.setExtensionPrompt(MODULE_NAME, formatMemoryValue(value), extension_settings.memory.position, extension_settings.memory.depth);
$('#memory_contents').val(value); $('#memory_contents').val(value);
console.log('Memory set to: ' + value); console.log('Summary set to: ' + value);
console.debug('Position: ' + extension_settings.memory.position);
console.debug('Depth: ' + extension_settings.memory.depth);
if (saveToMessage && context.chat.length) { if (saveToMessage && context.chat.length) {
const idx = context.chat.length - 2; const idx = context.chat.length - 2;
@ -496,6 +550,21 @@ jQuery(function () {
<input id="memory_restore" class="menu_button" type="button" value="Restore previous state" /> <input id="memory_restore" class="menu_button" type="button" value="Restore previous state" />
<label for="memory_frozen"><input id="memory_frozen" type="checkbox" />Pause summarization</label> <label for="memory_frozen"><input id="memory_frozen" type="checkbox" />Pause summarization</label>
</div> </div>
<div class="memory_template">
<label for="memory_template">Injection template:</label>
<textarea id="memory_template" class="text_pole textarea_compact" rows="1" placeholder="Use {{summary}} macro to specify the position of summarized text."></textarea>
</div>
<label for="memory_position">Injection position:</label>
<div class="radio_group">
<label>
<input type="radio" name="memory_position" value="0" />
After scenario
</label>
<label>
<input type="radio" name="memory_position" value="1" />
In-chat @ Depth <input id="memory_depth" class="text_pole widthUnset" type="number" min="0" max="99" />
</label>
</div>
<div data-source="main" class="memory_contents_controls"> <div data-source="main" class="memory_contents_controls">
</div> </div>
<div data-source="main"> <div data-source="main">
@ -511,6 +580,9 @@ jQuery(function () {
<input id="memory_prompt_words" type="range" value="${defaultSettings.promptWords}" min="${defaultSettings.promptMinWords}" max="${defaultSettings.promptMaxWords}" step="${defaultSettings.promptWordsStep}" /> <input id="memory_prompt_words" type="range" value="${defaultSettings.promptWords}" min="${defaultSettings.promptMinWords}" max="${defaultSettings.promptMaxWords}" step="${defaultSettings.promptWordsStep}" />
<label for="memory_prompt_interval">Update interval (<span id="memory_prompt_interval_value"></span> messages)</label> <label for="memory_prompt_interval">Update interval (<span id="memory_prompt_interval_value"></span> messages)</label>
<input id="memory_prompt_interval" type="range" value="${defaultSettings.promptInterval}" min="${defaultSettings.promptMinInterval}" max="${defaultSettings.promptMaxInterval}" step="${defaultSettings.promptIntervalStep}" /> <input id="memory_prompt_interval" type="range" value="${defaultSettings.promptInterval}" min="${defaultSettings.promptMinInterval}" max="${defaultSettings.promptMaxInterval}" step="${defaultSettings.promptIntervalStep}" />
<label for="memory_prompt_words_force">Force update after (<span id="memory_prompt_words_force_value"></span> words)</label>
<small>Set to 0 to disable</small>
<input id="memory_prompt_words_force" type="range" value="${defaultSettings.promptForceWords}" min="${defaultSettings.promptMinForceWords}" max="${defaultSettings.promptMaxForceWords}" step="${defaultSettings.promptForceWordsStep}" />
</div> </div>
<div data-source="extras"> <div data-source="extras">
<label for="memory_short_length">Chat to Summarize buffer length (<span id="memory_short_length_tokens"></span> tokens)</label> <label for="memory_short_length">Chat to Summarize buffer length (<span id="memory_short_length_tokens"></span> tokens)</label>
@ -542,6 +614,10 @@ jQuery(function () {
$('#memory_prompt_interval').on('input', onMemoryPromptIntervalInput); $('#memory_prompt_interval').on('input', onMemoryPromptIntervalInput);
$('#memory_prompt').on('input', onMemoryPromptInput); $('#memory_prompt').on('input', onMemoryPromptInput);
$('#memory_force_summarize').on('click', forceSummarizeChat); $('#memory_force_summarize').on('click', forceSummarizeChat);
$('#memory_template').on('input', onMemoryTemplateInput);
$('#memory_depth').on('input', onMemoryDepthInput);
$('input[name="memory_position"]').on('change', onMemoryPositionChange);
$('#memory_prompt_words_force').on('input', onMemoryPromptWordsForceInput);
} }
addExtensionControls(); addExtensionControls();

View File

@ -1,34 +1,26 @@
#memory_settings { #memory_settings {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
#memory_settings textarea { #memory_settings textarea {
font-size: calc(var(--mainFontSize) * 0.9); font-size: calc(var(--mainFontSize) * 0.9);
line-height: 1.2; line-height: 1.2;
} }
#memory_settings input[type="range"] { label[for="memory_frozen"] {
margin-bottom: 20px; display: flex;
} align-items: center;
margin: 0 !important;
#memory_settings label { }
margin-bottom: 10px;
} label[for="memory_frozen"] input {
margin-right: 10px;
label[for="memory_frozen"] { }
display: flex;
align-items: center; .memory_contents_controls {
margin: 0 !important; display: flex;
} flex-direction: row;
align-items: center;
label[for="memory_frozen"] input { justify-content: space-between;
margin-right: 10px; }
}
.memory_contents_controls {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
}

View File

@ -1,4 +1,4 @@
import { chat_metadata, callPopup, saveSettingsDebounced } from "../../../script.js"; import { chat_metadata, callPopup, saveSettingsDebounced, is_send_press } from "../../../script.js";
import { getContext, extension_settings, saveMetadataDebounced } from "../../extensions.js"; import { getContext, extension_settings, saveMetadataDebounced } from "../../extensions.js";
import { import {
substituteParams, substituteParams,
@ -7,15 +7,19 @@ import {
generateQuietPrompt, generateQuietPrompt,
} from "../../../script.js"; } from "../../../script.js";
import { registerSlashCommand } from "../../slash-commands.js"; import { registerSlashCommand } from "../../slash-commands.js";
import { waitUntilCondition } from "../../utils.js";
import { is_group_generating, selected_group } from "../../group-chats.js";
const MODULE_NAME = "Objective" const MODULE_NAME = "Objective"
let globalObjective = "" let taskTree = null
let globalTasks = [] let globalTasks = []
let currentChatId = "" let currentChatId = ""
let currentObjective = null
let currentTask = null let currentTask = null
let checkCounter = 0 let checkCounter = 0
let lastMessageWasSwipe = false
const defaultPrompts = { const defaultPrompts = {
@ -47,57 +51,59 @@ let objectivePrompts = defaultPrompts
//# Task Management #// //# Task Management #//
//###############################// //###############################//
// Accepts optional index. Defaults to adding to end of list.
function addTask(description, index = null) {
index = index != null ? index: index = globalTasks.length
globalTasks.splice(index, 0, new ObjectiveTask(
{description: description}
))
saveState()
}
// Return the task and index or throw an error // Return the task and index or throw an error
function getTaskById(taskId){ function getTaskById(taskId){
if (taskId == null) { if (taskId == null) {
throw `Null task id` throw `Null task id`
} }
const index = globalTasks.findIndex((task) => task.id === taskId); return getTaskByIdRecurse(taskId, taskTree)
if (index !== -1) {
return { task: globalTasks[index], index: index };
} else {
throw `Cannot find task with ${taskId}`
}
} }
function deleteTask(taskId){ function getTaskByIdRecurse(taskId, task) {
const { task, index } = getTaskById(taskId) if (task.id == taskId){
return task
}
for (const childTask of task.children) {
const foundTask = getTaskByIdRecurse(taskId, childTask);
if (foundTask != null) {
return foundTask;
}
}
return null;
}
globalTasks.splice(index, 1) function substituteParamsPrompts(content) {
setCurrentTask() content = content.replace(/{{objective}}/gi, currentObjective.description)
updateUiTaskList() content = content.replace(/{{task}}/gi, currentTask.description)
if (currentTask.parent){
content = content.replace(/{{parent}}/gi, currentTask.parent.description)
}
content = substituteParams(content)
return content
} }
// Call Quiet Generate to create task list using character context, then convert to tasks. Should not be called much. // Call Quiet Generate to create task list using character context, then convert to tasks. Should not be called much.
async function generateTasks() { async function generateTasks() {
const prompt = substituteParams(objectivePrompts.createTask.replace(/{{objective}}/gi, globalObjective));
const prompt = substituteParamsPrompts(objectivePrompts.createTask);
console.log(`Generating tasks for objective with prompt`) console.log(`Generating tasks for objective with prompt`)
toastr.info('Generating tasks for objective', 'Please wait...'); toastr.info('Generating tasks for objective', 'Please wait...');
const taskResponse = await generateQuietPrompt(prompt) const taskResponse = await generateQuietPrompt(prompt)
// Clear all existing global tasks when generating // Clear all existing objective tasks when generating
globalTasks = [] currentObjective.children = []
const numberedListPattern = /^\d+\./ const numberedListPattern = /^\d+\./
// Create tasks from generated task list // Create tasks from generated task list
for (const task of taskResponse.split('\n').map(x => x.trim())) { for (const task of taskResponse.split('\n').map(x => x.trim())) {
if (task.match(numberedListPattern) != null) { if (task.match(numberedListPattern) != null) {
addTask(task.replace(numberedListPattern,"").trim()) currentObjective.addTask(task.replace(numberedListPattern,"").trim())
} }
} }
updateUiTaskList() updateUiTaskList();
console.info(`Response for Objective: '${globalObjective}' was \n'${taskResponse}', \nwhich created tasks \n${JSON.stringify(globalTasks.map(v => {return v.toSaveState()}), null, 2)} `) setCurrentTask();
toastr.success(`Generated ${globalTasks.length} tasks`, 'Done!'); console.info(`Response for Objective: '${taskTree.description}' was \n'${taskResponse}', \nwhich created tasks \n${JSON.stringify(globalTasks.map(v => {return v.toSaveState()}), null, 2)} `)
toastr.success(`Generated ${taskTree.length} tasks`, 'Done!');
} }
// Call Quiet Generate to check if a task is completed // Call Quiet Generate to check if a task is completed
@ -106,14 +112,28 @@ async function checkTaskCompleted() {
if (jQuery.isEmptyObject(currentTask)) { if (jQuery.isEmptyObject(currentTask)) {
return return
} }
checkCounter = $('#objective-check-frequency').val()
const prompt = substituteParams(objectivePrompts.checkTaskCompleted.replace(/{{task}}/gi, currentTask.description)); try {
// Wait for group to finish generating
if (selected_group) {
await waitUntilCondition(() => is_group_generating === false, 1000, 10);
}
// Another extension might be doing something with the chat, so wait for it to finish
await waitUntilCondition(() => is_send_press === false, 30000, 10);
} catch {
console.debug("Failed to wait for group to finish generating")
return;
}
checkCounter = $('#objective-check-frequency').val()
toastr.info("Checking for task completion.")
const prompt = substituteParamsPrompts(objectivePrompts.checkTaskCompleted);
const taskResponse = (await generateQuietPrompt(prompt)).toLowerCase() const taskResponse = (await generateQuietPrompt(prompt)).toLowerCase()
// Check response if task complete // Check response if task complete
if (taskResponse.includes("true")) { if (taskResponse.includes("true")) {
console.info(`Character determined task '${JSON.stringify(currentTask.toSaveState())} is completed.`) console.info(`Character determined task '${currentTask.description} is completed.`)
currentTask.completeTask() currentTask.completeTask()
} else if (!(taskResponse.includes("false"))) { } else if (!(taskResponse.includes("false"))) {
console.warn(`checkTaskCompleted response did not contain true or false. taskResponse: ${taskResponse}`) console.warn(`checkTaskCompleted response did not contain true or false. taskResponse: ${taskResponse}`)
@ -122,29 +142,57 @@ async function checkTaskCompleted() {
} }
} }
function getNextIncompleteTaskRecurse(task){
if (task.completed === false // Return task if incomplete
&& task.children.length === 0 // Ensure task has no children, it's subtasks will determine completeness
&& task.parentId !== "" // Must have parent id. Only root task will be missing this and we dont want that
){
return task
}
for (const childTask of task.children) {
if (childTask.completed === true){ // Don't recurse into completed tasks
continue
}
const foundTask = getNextIncompleteTaskRecurse(childTask);
if (foundTask != null) {
return foundTask;
}
}
return null;
}
// Set a task in extensionPrompt context. Defaults to first incomplete // Set a task in extensionPrompt context. Defaults to first incomplete
function setCurrentTask(taskId = null) { function setCurrentTask(taskId = null) {
const context = getContext(); const context = getContext();
// TODO: Should probably null this rather than set empty object
currentTask = {}; currentTask = {};
// Set current task to either the next incomplete task, or the index // Find the task, either next incomplete, or by provided taskId
if (taskId === null) { if (taskId === null) {
currentTask = globalTasks.find(task => !task.completed) || {}; currentTask = getNextIncompleteTaskRecurse(taskTree) || {};
} else { } else {
const { _, index } = getTaskById(taskId) currentTask = getTaskById(taskId);
currentTask = globalTasks[index];
} }
// Get the task description and add to extension prompt // Don't just check for a current task, check if it has data
const description = currentTask.description || null; const description = currentTask.description || null;
// Now update the extension prompt
if (description) { if (description) {
const extensionPromptText = objectivePrompts.currentTask.replace(/{{task}}/gi, description); const extensionPromptText = substituteParamsPrompts(objectivePrompts.currentTask);
$('.objective-task').css({'border-color':'','border-width':''}) // Clear highlights
currentTask.descriptionSpan.css({'border-color':'yellow','border-width':'2px'}); // Highlight current task // Remove highlights
$('.objective-task').css({'border-color':'','border-width':''})
// Highlight current task
let highlightTask = currentTask
while (highlightTask.parentId !== ""){
if (highlightTask.descriptionSpan){
highlightTask.descriptionSpan.css({'border-color':'yellow','border-width':'2px'});
}
const parent = getTaskById(highlightTask.parentId)
highlightTask = parent
}
// Update the extension prompt
context.setExtensionPrompt(MODULE_NAME, extensionPromptText, 1, $('#objective-chat-depth').val()); context.setExtensionPrompt(MODULE_NAME, extensionPromptText, 1, $('#objective-chat-depth').val());
console.info(`Current task in context.extensionPrompts.Objective is ${JSON.stringify(context.extensionPrompts.Objective)}`); console.info(`Current task in context.extensionPrompts.Objective is ${JSON.stringify(context.extensionPrompts.Objective)}`);
} else { } else {
@ -155,22 +203,26 @@ function setCurrentTask(taskId = null) {
saveState(); saveState();
} }
let taskIdCounter = 0 function getHighestTaskIdRecurse(task) {
function getNextTaskId(){ let nextId = task.id;
// Make sure id does not exist
while (globalTasks.find(task => task.id == taskIdCounter) != undefined) { for (const childTask of task.children) {
taskIdCounter += 1 const childId = getHighestTaskIdRecurse(childTask);
if (childId > nextId) {
nextId = childId;
}
} }
const nextId = taskIdCounter return nextId;
console.log(`TaskID assigned: ${nextId}`)
taskIdCounter += 1
return nextId
} }
//###############################//
//# Task Class #//
//###############################//
class ObjectiveTask { class ObjectiveTask {
id id
description description
completed completed
parent parentId
children children
// UI Elements // UI Elements
@ -180,25 +232,67 @@ class ObjectiveTask {
deleteTaskButton deleteTaskButton
addTaskButton addTaskButton
constructor ({id=undefined, description, completed=false, parent=null}) { constructor ({id=undefined, description, completed=false, parentId=""}) {
this.description = description this.description = description
this.parent = parent this.parentId = parentId
this.children = [] this.children = []
this.completed = completed this.completed = completed
// Generate a new ID if none specified // Generate a new ID if none specified
if (id==undefined){ if (id==undefined){
this.id = getNextTaskId() this.id = getHighestTaskIdRecurse(taskTree) + 1
} else { } else {
this.id=id this.id=id
} }
} }
// Accepts optional index. Defaults to adding to end of list.
addTask(description, index = null) {
index = index != null ? index: index = this.children.length
this.children.splice(index, 0, new ObjectiveTask(
{description: description, parentId: this.id}
))
saveState()
}
getIndex(){
if (this.parentId !== null) {
const parent = getTaskById(this.parentId)
const index = parent.children.findIndex(task => task.id === this.id)
if (index === -1){
throw `getIndex failed: Task '${this.description}' not found in parent task '${parent.description}'`
}
return index
} else {
throw `getIndex failed: Task '${this.description}' has no parent`
}
}
// Used to set parent to complete when all child tasks are completed
checkParentComplete() {
let all_completed = true;
if (this.parentId !== ""){
const parent = getTaskById(this.parentId);
for (const child of parent.children){
if (!child.completed){
all_completed = false;
break;
}
}
if (all_completed){
parent.completed = true;
console.info(`Parent task '${parent.description}' completed after all child tasks complated.`)
} else {
parent.completed = false;
}
}
}
// Complete the current task, setting next task to next incomplete task // Complete the current task, setting next task to next incomplete task
completeTask() { completeTask() {
this.completed = true this.completed = true
console.info(`Task successfully completed: ${JSON.stringify(this.description)}`) console.info(`Task successfully completed: ${JSON.stringify(this.description)}`)
this.checkParentComplete()
setCurrentTask() setCurrentTask()
updateUiTaskList() updateUiTaskList()
} }
@ -211,6 +305,7 @@ class ObjectiveTask {
<span class="text_pole objective-task" style="display: block" id="objective-task-description-${this.id}" contenteditable>${this.description}</span> <span class="text_pole objective-task" style="display: block" id="objective-task-description-${this.id}" contenteditable>${this.description}</span>
<div id="objective-task-delete-${this.id}" class="objective-task-button fa-solid fa-xmark fa-2x" title="Delete Task"></div> <div id="objective-task-delete-${this.id}" class="objective-task-button fa-solid fa-xmark fa-2x" title="Delete Task"></div>
<div id="objective-task-add-${this.id}" class="objective-task-button fa-solid fa-plus fa-2x" title="Add Task"></div> <div id="objective-task-add-${this.id}" class="objective-task-button fa-solid fa-plus fa-2x" title="Add Task"></div>
<div id="objective-task-add-branch-${this.id}" class="objective-task-button fa-solid fa-code-fork fa-2x" title="Branch Task"></div>
</div><br> </div><br>
`; `;
@ -221,6 +316,15 @@ class ObjectiveTask {
this.descriptionSpan = $(`#objective-task-description-${this.id}`); this.descriptionSpan = $(`#objective-task-description-${this.id}`);
this.addButton = $(`#objective-task-add-${this.id}`); this.addButton = $(`#objective-task-add-${this.id}`);
this.deleteButton = $(`#objective-task-delete-${this.id}`); this.deleteButton = $(`#objective-task-delete-${this.id}`);
this.taskHtml = $(`#objective-task-label-${this.id}`);
this.branchButton = $(`#objective-task-add-branch-${this.id}`)
// Handle sub-task forking style
if (this.children.length > 0){
this.branchButton.css({'color':'#33cc33'})
} else {
this.branchButton.css({'color':''})
}
// Add event listeners and set properties // Add event listeners and set properties
$(`#objective-task-complete-${this.id}`).prop('checked', this.completed); $(`#objective-task-complete-${this.id}`).prop('checked', this.completed);
@ -229,49 +333,197 @@ class ObjectiveTask {
$(`#objective-task-description-${this.id}`).on('focusout', () => (this.onDescriptionFocusout())); $(`#objective-task-description-${this.id}`).on('focusout', () => (this.onDescriptionFocusout()));
$(`#objective-task-delete-${this.id}`).on('click', () => (this.onDeleteClick())); $(`#objective-task-delete-${this.id}`).on('click', () => (this.onDeleteClick()));
$(`#objective-task-add-${this.id}`).on('click', () => (this.onAddClick())); $(`#objective-task-add-${this.id}`).on('click', () => (this.onAddClick()));
this.branchButton.on('click', () => (this.onBranchClick()))
}
onBranchClick() {
currentObjective = this
updateUiTaskList();
setCurrentTask();
} }
onCompleteClick(){ onCompleteClick(){
this.completed = this.completedCheckbox.prop('checked') this.completed = this.completedCheckbox.prop('checked')
this.checkParentComplete()
setCurrentTask(); setCurrentTask();
} }
onDescriptionUpdate(){ onDescriptionUpdate(){
this.description = this.descriptionSpan.text(); this.description = this.descriptionSpan.text();
} }
onDescriptionFocusout(){ onDescriptionFocusout(){
setCurrentTask(); setCurrentTask();
} }
onDeleteClick(){ onDeleteClick(){
deleteTask(this.id); const index = this.getIndex()
const parent = getTaskById(this.parentId)
parent.children.splice(index, 1)
updateUiTaskList()
setCurrentTask()
} }
onAddClick(){ onAddClick(){
const {_, index} = getTaskById(this.id) const index = this.getIndex()
addTask("", index + 1); const parent = getTaskById(this.parentId)
setCurrentTask(); parent.addTask("", index + 1);
updateUiTaskList(); updateUiTaskList();
setCurrentTask();
} }
toSaveState() { toSaveStateRecurse() {
let children = []
if (this.children.length > 0){
for (const child of this.children){
children.push(child.toSaveStateRecurse())
}
}
return { return {
"id":this.id, "id":this.id,
"description":this.description, "description":this.description,
"completed":this.completed, "completed":this.completed,
"parent": this.parent, "parentId": this.parentId,
"children": children,
} }
} }
} }
//###############################//
//# Custom Prompts #//
//###############################//
function onEditPromptClick() {
let popupText = ''
popupText += `
<div class="objective_prompt_modal">
<small>Edit prompts used by Objective for this session. You can use {{objective}} or {{task}} plus any other standard template variables. Save template to persist changes.</small>
<br>
<div>
<label for="objective-prompt-generate">Generation Prompt</label>
<textarea id="objective-prompt-generate" type="text" class="text_pole textarea_compact" rows="8"></textarea>
<label for="objective-prompt-check">Completion Check Prompt</label>
<textarea id="objective-prompt-check" type="text" class="text_pole textarea_compact" rows="8"></textarea>
<label for="objective-prompt-extension-prompt">Injected Prompt</label>
<textarea id="objective-prompt-extension-prompt" type="text" class="text_pole textarea_compact" rows="8"></textarea>
</div>
<div class="objective_prompt_block">
<label for="objective-custom-prompt-select">Custom Prompt Select</label>
<select id="objective-custom-prompt-select"><select>
</div>
<div class="objective_prompt_block">
<input id="objective-custom-prompt-new" class="menu_button" type="submit" value="New Prompt" />
<input id="objective-custom-prompt-save" class="menu_button" type="submit" value="Save Prompt" />
<input id="objective-custom-prompt-delete" class="menu_button" type="submit" value="Delete Prompt" />
</div>
</div>`
callPopup(popupText, 'text')
populateCustomPrompts()
// Set current values
$('#objective-prompt-generate').val(objectivePrompts.createTask)
$('#objective-prompt-check').val(objectivePrompts.checkTaskCompleted)
$('#objective-prompt-extension-prompt').val(objectivePrompts.currentTask)
// Handle value updates
$('#objective-prompt-generate').on('input', () => {
objectivePrompts.createTask = $('#objective-prompt-generate').val()
})
$('#objective-prompt-check').on('input', () => {
objectivePrompts.checkTaskCompleted = $('#objective-prompt-check').val()
})
$('#objective-prompt-extension-prompt').on('input', () => {
objectivePrompts.currentTask = $('#objective-prompt-extension-prompt').val()
})
// Handle new
$('#objective-custom-prompt-new').on('click', () => {
newCustomPrompt()
})
// Handle save
$('#objective-custom-prompt-save').on('click', () => {
saveCustomPrompt()
})
// Handle delete
$('#objective-custom-prompt-delete').on('click', () => {
deleteCustomPrompt()
})
// Handle load
$('#objective-custom-prompt-select').on('change', loadCustomPrompt)
}
async function newCustomPrompt() {
const customPromptName = await callPopup('<h3>Custom Prompt name:</h3>', 'input');
if (customPromptName == "") {
toastr.warning("Please set custom prompt name to save.")
return
}
if (customPromptName == "default"){
toastr.error("Cannot save over default prompt")
return
}
extension_settings.objective.customPrompts[customPromptName] = {}
Object.assign(extension_settings.objective.customPrompts[customPromptName], objectivePrompts)
saveSettingsDebounced()
populateCustomPrompts()
}
function saveCustomPrompt() {
const customPromptName = $("#objective-custom-prompt-select").find(':selected').val()
if (customPromptName == "default"){
toastr.error("Cannot save over default prompt")
return
}
Object.assign(extension_settings.objective.customPrompts[customPromptName], objectivePrompts)
saveSettingsDebounced()
populateCustomPrompts()
}
function deleteCustomPrompt(){
const customPromptName = $("#objective-custom-prompt-select").find(':selected').val()
if (customPromptName == "default"){
toastr.error("Cannot delete default prompt")
return
}
delete extension_settings.objective.customPrompts[customPromptName]
saveSettingsDebounced()
populateCustomPrompts()
loadCustomPrompt()
}
function loadCustomPrompt(){
const optionSelected = $("#objective-custom-prompt-select").find(':selected').val()
Object.assign(objectivePrompts, extension_settings.objective.customPrompts[optionSelected])
$('#objective-prompt-generate').val(objectivePrompts.createTask)
$('#objective-prompt-check').val(objectivePrompts.checkTaskCompleted)
$('#objective-prompt-extension-prompt').val(objectivePrompts.currentTask)
}
function populateCustomPrompts(){
// Populate saved prompts
$('#objective-custom-prompt-select').empty()
for (const customPromptName in extension_settings.objective.customPrompts){
const option = document.createElement('option');
option.innerText = customPromptName;
option.value = customPromptName;
option.selected = customPromptName
$('#objective-custom-prompt-select').append(option)
}
}
//###############################// //###############################//
//# UI AND Settings #// //# UI AND Settings #//
//###############################// //###############################//
const defaultSettings = { const defaultSettings = {
objective: "", currentObjectiveId: null,
tasks: [], taskTree: null,
chatDepth: 2, chatDepth: 2,
checkFrequency: 3, checkFrequency: 3,
hideTasks: false, hideTasks: false,
@ -280,6 +532,7 @@ const defaultSettings = {
// Convenient single call. Not much at the moment. // Convenient single call. Not much at the moment.
function resetState() { function resetState() {
lastMessageWasSwipe = false
loadSettings(); loadSettings();
} }
@ -291,12 +544,9 @@ function saveState() {
currentChatId = context.chatId currentChatId = context.chatId
} }
// Convert globalTasks for saving
const tasks = globalTasks.map(task => {return task.toSaveState()})
chat_metadata['objective'] = { chat_metadata['objective'] = {
objective: globalObjective, currentObjectiveId: currentObjective.id,
tasks: tasks, taskTree: taskTree.toSaveStateRecurse(),
checkFrequency: $('#objective-check-frequency').val(), checkFrequency: $('#objective-check-frequency').val(),
chatDepth: $('#objective-chat-depth').val(), chatDepth: $('#objective-chat-depth').val(),
hideTasks: $('#objective-hide-tasks').prop('checked'), hideTasks: $('#objective-hide-tasks').prop('checked'),
@ -309,9 +559,9 @@ function saveState() {
// Dump core state // Dump core state
function debugObjectiveExtension() { function debugObjectiveExtension() {
console.log(JSON.stringify({ console.log(JSON.stringify({
"currentTask": currentTask.description, "currentTask": currentTask,
"globalObjective": globalObjective, "currentObjective": currentObjective,
"globalTasks": globalTasks.map(v => {return v.toSaveState()}), "taskTree": taskTree.toSaveStateRecurse(),
"chat_metadata": chat_metadata['objective'], "chat_metadata": chat_metadata['objective'],
"extension_settings": extension_settings['objective'], "extension_settings": extension_settings['objective'],
"prompts": objectivePrompts "prompts": objectivePrompts
@ -324,9 +574,20 @@ window.debugObjectiveExtension = debugObjectiveExtension
// Populate UI task list // Populate UI task list
function updateUiTaskList() { function updateUiTaskList() {
$('#objective-tasks').empty() $('#objective-tasks').empty()
// Show tasks if there are any
if (globalTasks.length > 0){ // Show button to navigate back to parent objective if parent exists
for (const task of globalTasks) { if (currentObjective){
if (currentObjective.parentId !== "") {
$('#objective-parent').show()
} else {
$('#objective-parent').hide()
}
}
$('#objective-text').val(currentObjective.description)
if (currentObjective.children.length > 0){
// Show tasks if there are any to show
for (const task of currentObjective.children) {
task.addUiElement() task.addUiElement()
} }
} else { } else {
@ -335,17 +596,21 @@ function updateUiTaskList() {
<input id="objective-task-add-first" type="button" class="menu_button" value="Add Task"> <input id="objective-task-add-first" type="button" class="menu_button" value="Add Task">
`) `)
$("#objective-task-add-first").on('click', () => { $("#objective-task-add-first").on('click', () => {
addTask("") currentObjective.addTask("")
setCurrentTask() setCurrentTask()
updateUiTaskList() updateUiTaskList()
}) })
} }
} }
function onParentClick() {
currentObjective = getTaskById(currentObjective.parentId)
updateUiTaskList()
setCurrentTask()
}
// Trigger creation of new tasks with given objective. // Trigger creation of new tasks with given objective.
async function onGenerateObjectiveClick() { async function onGenerateObjectiveClick() {
globalObjective = $('#objective-text').val()
await generateTasks() await generateTasks()
saveState() saveState()
} }
@ -356,6 +621,13 @@ function onChatDepthInput() {
setCurrentTask() // Ensure extension prompt is updated setCurrentTask() // Ensure extension prompt is updated
} }
function onObjectiveTextFocusOut(){
if (currentObjective){
currentObjective.description = $('#objective-text').val()
saveState()
}
}
// Update how often we check for task completion // Update how often we check for task completion
function onCheckFrequencyInput() { function onCheckFrequencyInput() {
checkCounter = $("#objective-check-frequency").val() checkCounter = $("#objective-check-frequency").val()
@ -368,114 +640,28 @@ function onHideTasksInput() {
saveState() saveState()
} }
function onEditPromptClick() { function loadTaskChildrenRecurse(savedTask) {
let popupText = '' let tempTaskTree = new ObjectiveTask({
popupText += ` id: savedTask.id,
<div class="objective_prompt_modal"> description: savedTask.description,
<div> completed: savedTask.completed,
<label for="objective-prompt-generate">Generation Prompt</label> parentId: savedTask.parentId,
<textarea id="objective-prompt-generate" type="text" class="text_pole textarea_compact" rows="8"></textarea>
<label for="objective-prompt-check">Completion Check Prompt</label>
<textarea id="objective-prompt-check" type="text" class="text_pole textarea_compact" rows="8"></textarea>
<label for="objective-prompt-extension-prompt">Injected Prompt</label>
<textarea id="objective-prompt-extension-prompt" type="text" class="text_pole textarea_compact" rows="8"></textarea>
</div>
<div class="objective_prompt_block">
<input id="objective-custom-prompt-name" style="flex-grow:2" type="text" class="flex1 heightFitContent text_pole widthNatural" maxlength="250" placeholder="Custom Prompt Name">
<input id="objective-custom-prompt-save" style="flex-grow:1" class="menu_button" type="submit" value="Save Prompt" />
</div>
<div class="objective_prompt_block">
<label for="objective-prompt-load">Load Prompt</label>
<select id="objective-prompt-load"><select>
<input id="objective-custom-prompt-delete" class="menu_button" type="submit" value="Delete Prompt" />
</div>
</div>`
callPopup(popupText, 'text')
populateCustomPrompts()
// Set current values
$('#objective-prompt-generate').val(objectivePrompts.createTask)
$('#objective-prompt-check').val(objectivePrompts.checkTaskCompleted)
$('#objective-prompt-extension-prompt').val(objectivePrompts.currentTask)
// Handle value updates
$('#objective-prompt-generate').on('input', () => {
objectivePrompts.createTask = $('#objective-prompt-generate').val()
}) })
$('#objective-prompt-check').on('input', () => { for (const task of savedTask.children){
objectivePrompts.checkTaskCompleted = $('#objective-prompt-check').val() const childTask = loadTaskChildrenRecurse(task)
}) tempTaskTree.children.push(childTask)
$('#objective-prompt-extension-prompt').on('input', () => {
objectivePrompts.currentTask = $('#objective-prompt-extension-prompt').val()
})
// Handle save
$('#objective-custom-prompt-save').on('click', () => {
addCustomPrompt($('#objective-custom-prompt-name').val(), objectivePrompts)
})
// Handle delete
$('#objective-custom-prompt-delete').on('click', () => {
const optionSelected = $("#objective-prompt-load").find(':selected').val()
deleteCustomPrompt(optionSelected)
})
// Handle load
$('#objective-prompt-load').on('change', loadCustomPrompt)
}
function addCustomPrompt(customPromptName, customPrompts) {
if (customPromptName == "") {
toastr.warning("Please set custom prompt name to save.")
return
}
if (customPromptName == "default"){
toastr.error("Cannot save over default prompt")
return
}
extension_settings.objective.customPrompts[customPromptName] = {}
Object.assign(extension_settings.objective.customPrompts[customPromptName], customPrompts)
saveSettingsDebounced()
populateCustomPrompts()
}
function deleteCustomPrompt(customPromptName){
if (customPromptName == "default"){
toastr.error("Cannot delete default prompt")
return
}
delete extension_settings.objective.customPrompts[customPromptName]
saveSettingsDebounced()
populateCustomPrompts()
loadCustomPrompt()
}
function loadCustomPrompt(){
const optionSelected = $("#objective-prompt-load").find(':selected').val()
console.log(optionSelected)
objectivePrompts = extension_settings.objective.customPrompts[optionSelected]
$('#objective-prompt-generate').val(objectivePrompts.createTask)
$('#objective-prompt-check').val(objectivePrompts.checkTaskCompleted)
$('#objective-prompt-extension-prompt').val(objectivePrompts.currentTask)
}
function populateCustomPrompts(){
// Populate saved prompts
$('#objective-prompt-load').empty()
for (const customPromptName in extension_settings.objective.customPrompts){
const option = document.createElement('option');
option.innerText = customPromptName;
option.value = customPromptName;
option.selected = customPromptName
$('#objective-prompt-load').append(option)
} }
return tempTaskTree
} }
function loadSettings() { function loadSettings() {
// Load/Init settings for chatId // Load/Init settings for chatId
currentChatId = getContext().chatId currentChatId = getContext().chatId
// Reset Objectives and Tasks in memory
taskTree = null;
currentObjective = null;
// Init extension settings // Init extension settings
if (Object.keys(extension_settings.objective).length === 0) { if (Object.keys(extension_settings.objective).length === 0) {
Object.assign(extension_settings.objective, { 'customPrompts': {'default':defaultPrompts}}) Object.assign(extension_settings.objective, { 'customPrompts': {'default':defaultPrompts}})
@ -488,6 +674,7 @@ function loadSettings() {
// Migrate existing settings // Migrate existing settings
if (currentChatId in extension_settings.objective) { if (currentChatId in extension_settings.objective) {
// TODO: Remove this soon
chat_metadata['objective'] = extension_settings.objective[currentChatId]; chat_metadata['objective'] = extension_settings.objective[currentChatId];
delete extension_settings.objective[currentChatId]; delete extension_settings.objective[currentChatId];
} }
@ -496,21 +683,47 @@ function loadSettings() {
Object.assign(chat_metadata, { objective: defaultSettings }); Object.assign(chat_metadata, { objective: defaultSettings });
} }
// Update globals // Migrate legacy flat objective to new objectiveTree and currentObjective
globalObjective = chat_metadata['objective'].objective if ('objective' in chat_metadata.objective) {
globalTasks = chat_metadata['objective'].tasks.map(task => {
return new ObjectiveTask({ // Create root objective from legacy objective
id: task.id, taskTree = new ObjectiveTask({id:0, description: chat_metadata.objective.objective});
description: task.description, currentObjective = taskTree;
completed: task.completed,
parent: task.parent, // Populate root objective tree from legacy tasks
}) if ('tasks' in chat_metadata.objective) {
}); let idIncrement = 0;
taskTree.children = chat_metadata.objective.tasks.map(task => {
idIncrement += 1;
return new ObjectiveTask({
id: idIncrement,
description: task.description,
completed: task.completed,
parentId: taskTree.id,
})
});
}
saveState();
delete chat_metadata.objective.objective;
delete chat_metadata.objective.tasks;
} else {
// Load Objectives and Tasks (Normal path)
if (chat_metadata.objective.taskTree){
taskTree = loadTaskChildrenRecurse(chat_metadata.objective.taskTree)
}
}
// Make sure there's a root task
if (!taskTree) {
taskTree = new ObjectiveTask({id:0,description:$('#objective-text').val()})
}
currentObjective = taskTree
checkCounter = chat_metadata['objective'].checkFrequency checkCounter = chat_metadata['objective'].checkFrequency
// Update UI elements // Update UI elements
$('#objective-counter').text(checkCounter) $('#objective-counter').text(checkCounter)
$("#objective-text").text(globalObjective) $("#objective-text").text(taskTree.description)
updateUiTaskList() updateUiTaskList()
$('#objective-chat-depth').val(chat_metadata['objective'].chatDepth) $('#objective-chat-depth').val(chat_metadata['objective'].chatDepth)
$('#objective-check-frequency').val(chat_metadata['objective'].checkFrequency) $('#objective-check-frequency').val(chat_metadata['objective'].checkFrequency)
@ -533,38 +746,44 @@ jQuery(() => {
<div class="objective-settings"> <div class="objective-settings">
<div class="inline-drawer"> <div class="inline-drawer">
<div class="inline-drawer-toggle inline-drawer-header"> <div class="inline-drawer-toggle inline-drawer-header">
<b>Objective</b> <b>Objective</b>
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div> <div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
</div>
<div class="inline-drawer-content">
<label for="objective-text"><small>Enter an objective and generate tasks. The AI will attempt to complete tasks autonomously</small></label>
<textarea id="objective-text" type="text" class="text_pole textarea_compact" rows="4"></textarea>
<div class="objective_block flex-container">
<input id="objective-generate" class="menu_button" type="submit" value="Auto-Generate Tasks" />
<label class="checkbox_label"><input id="objective-hide-tasks" type="checkbox"> Hide Tasks</label>
</div> </div>
<div class="inline-drawer-content">
<div id="objective-tasks"> </div> <label for="objective-text"><small>Enter an objective and generate tasks. The AI will attempt to complete tasks autonomously</small></label>
<div class="objective_block margin-bot-10px"> <textarea id="objective-text" type="text" class="text_pole textarea_compact" rows="4"></textarea>
<div class="objective_block objective_block_control flex1 flexFlowColumn"> <div class="objective_block flex-container">
<label for="objective-chat-depth">Position in Chat</label> <input id="objective-generate" class="menu_button" type="submit" value="Auto-Generate Tasks" />
<input id="objective-chat-depth" class="text_pole widthUnset" type="number" min="0" max="99" /> <label class="checkbox_label"><input id="objective-hide-tasks" type="checkbox"> Hide Tasks</label>
</div> </div>
<br> <div id="objective-parent" class="objective_block flex-container">
<div class="objective_block objective_block_control flex1"> <i class="objective-task-button fa-solid fa-circle-left fa-2x" title="Go to Parent"></i>
<small>Go to parent task</small>
<label for="objective-check-frequency">Task Check Frequency</label>
<input id="objective-check-frequency" class="text_pole widthUnset" type="number" min="0" max="99" />
<small>(0 = disabled)</small>
</div> </div>
<div id="objective-tasks"> </div>
<div class="objective_block margin-bot-10px">
<div class="objective_block objective_block_control flex1 flexFlowColumn">
<label for="objective-chat-depth">Position in Chat</label>
<input id="objective-chat-depth" class="text_pole widthUnset" type="number" min="0" max="99" />
</div>
<br>
<div class="objective_block objective_block_control flex1">
<label for="objective-check-frequency">Task Check Frequency</label>
<input id="objective-check-frequency" class="text_pole widthUnset" type="number" min="0" max="99" />
<small>(0 = disabled)</small>
</div>
</div>
<span> Messages until next AI task completion check <span id="objective-counter">0</span></span>
<div class="objective_block flex-container">
<input id="objective_prompt_edit" class="menu_button" type="submit" value="Edit Prompts" />
</div>
<hr class="sysHR">
</div> </div>
<span> Messages until next AI task completion check <span id="objective-counter">0</span></span>
<div class="objective_block flex-container">
<input id="objective_prompt_edit" class="menu_button" type="submit" value="Edit Prompts" />
</div>
<hr class="sysHR">
</div> </div>
</div>`; </div>
`;
addManualTaskCheckUi() addManualTaskCheckUi()
$('#extensions_settings').append(settingsHtml); $('#extensions_settings').append(settingsHtml);
@ -573,14 +792,20 @@ jQuery(() => {
$("#objective-check-frequency").on('input', onCheckFrequencyInput) $("#objective-check-frequency").on('input', onCheckFrequencyInput)
$('#objective-hide-tasks').on('click', onHideTasksInput) $('#objective-hide-tasks').on('click', onHideTasksInput)
$('#objective_prompt_edit').on('click', onEditPromptClick) $('#objective_prompt_edit').on('click', onEditPromptClick)
$('#objective-parent').hide()
$('#objective-parent').on('click',onParentClick)
$('#objective-text').on('focusout',onObjectiveTextFocusOut)
loadSettings() loadSettings()
eventSource.on(event_types.CHAT_CHANGED, () => { eventSource.on(event_types.CHAT_CHANGED, () => {
resetState() resetState()
}); });
eventSource.on(event_types.MESSAGE_SWIPED, () => {
lastMessageWasSwipe = true
})
eventSource.on(event_types.MESSAGE_RECEIVED, () => { eventSource.on(event_types.MESSAGE_RECEIVED, () => {
if (currentChatId == undefined) { if (currentChatId == undefined || jQuery.isEmptyObject(currentTask) || lastMessageWasSwipe) {
lastMessageWasSwipe = false
return return
} }
if ($("#objective-check-frequency").val() > 0) { if ($("#objective-check-frequency").val() > 0) {

View File

@ -1,19 +1,48 @@
import { saveSettingsDebounced } from "../../../script.js"; import { saveSettingsDebounced, callPopup, getRequestHeaders } from "../../../script.js";
import { getContext, extension_settings } from "../../extensions.js"; import { getContext, extension_settings } from "../../extensions.js";
import { initScrollHeight, resetScrollHeight } from "../../utils.js"; import { initScrollHeight, resetScrollHeight } from "../../utils.js";
export { MODULE_NAME }; export { MODULE_NAME };
const MODULE_NAME = 'quick-reply'; const MODULE_NAME = 'quick-reply';
const UPDATE_INTERVAL = 1000; const UPDATE_INTERVAL = 1000;
let presets = [];
let selected_preset = '';
const defaultSettings = { const defaultSettings = {
quickReplyEnabled: false, quickReplyEnabled: true,
numberOfSlots: 5, numberOfSlots: 5,
quickReplySlots: [], quickReplySlots: [],
} }
async function loadSettings() { //method from worldinfo
async function updateQuickReplyPresetList() {
var result = await fetch("/getsettings", {
method: "POST",
headers: getRequestHeaders(),
body: JSON.stringify({}),
});
if (result.ok) {
var data = await result.json();
presets = data.quickReplyPresets?.length ? data.quickReplyPresets : [];
console.log(presets)
$("#quickReplyPresets").find('option[value!=""]').remove();
if (presets !== undefined) {
presets.forEach((item, i) => {
$("#quickReplyPresets").append(`<option value='${item.name}'${selected_preset.includes(item.name) ? ' selected' : ''}>${item.name}</option>`);
});
}
}
}
async function loadSettings(type) {
if (type === 'init') {
await updateQuickReplyPresetList()
}
if (Object.keys(extension_settings.quickReply).length === 0) { if (Object.keys(extension_settings.quickReply).length === 0) {
Object.assign(extension_settings.quickReply, defaultSettings); Object.assign(extension_settings.quickReply, defaultSettings);
} }
@ -111,6 +140,51 @@ async function moduleWorker() {
if (extension_settings.quickReply.quickReplyEnabled === true) { if (extension_settings.quickReply.quickReplyEnabled === true) {
$('#quickReplyBar').toggle(getContext().onlineStatus !== 'no_connection'); $('#quickReplyBar').toggle(getContext().onlineStatus !== 'no_connection');
} }
if (extension_settings.quickReply.selectedPreset) {
selected_preset = extension_settings.quickReply.selectedPreset;
}
}
async function saveQuickReplyPreset() {
const name = await callPopup('Enter a name for the Quick Reply Preset:', 'input');
if (!name) {
return;
}
const quickReplyPreset = {
name: name,
quickReplyEnabled: extension_settings.quickReply.quickReplyEnabled,
quickReplySlots: extension_settings.quickReply.quickReplySlots,
numberOfSlots: extension_settings.quickReply.numberOfSlots,
selectedPreset: name
}
const response = await fetch('/savequickreply', {
method: 'POST',
headers: getRequestHeaders(),
body: JSON.stringify(quickReplyPreset)
});
if (response.ok) {
const quickReplyPresetIndex = presets.findIndex(x => x.name == name);
if (quickReplyPresetIndex == -1) {
presets.push(quickReplyPreset);
const option = document.createElement('option');
option.selected = true;
option.value = name;
option.innerText = name;
$('#quickReplyPresets').append(option);
}
else {
presets[quickReplyPresetIndex] = quickReplyPreset;
$(`#quickReplyPresets option[value="${name}"]`).attr('selected', true);
}
saveSettingsDebounced();
} else {
toastr.warning('Failed to save Quick Reply Preset.')
}
} }
async function onQuickReplyNumberOfSlotsInput() { async function onQuickReplyNumberOfSlotsInput() {
@ -178,6 +252,27 @@ function generateQuickReplyElements() {
}); });
} }
async function applyQuickReplyPreset(name) {
const quickReplyPreset = presets.find(x => x.name == name);
if (!quickReplyPreset) {
console.log(`error, QR preset '${name}' not found`)
return;
}
extension_settings.quickReply = quickReplyPreset;
extension_settings.quickReply.selectedPreset = name;
saveSettingsDebounced()
loadSettings('init')
addQuickReplyBar();
moduleWorker();
$(`#quickReplyPresets option[value="${name}"]`).attr('selected', true);
console.debug('QR Preset applied: ' + name);
//loadMovingUIState()
}
jQuery(async () => { jQuery(async () => {
moduleWorker(); moduleWorker();
@ -190,11 +285,18 @@ jQuery(async () => {
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div> <div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
</div> </div>
<div class="inline-drawer-content"> <div class="inline-drawer-content">
<label class="checkbox_label marginBot10"> <div class="flex-container ">
<input id="quickReplyEnabled" type="checkbox" /> <label class="checkbox_label marginBot10 wide100p flexnowrap">
Enable Quick Replies <input id="quickReplyEnabled" type="checkbox" />
</label> Enable Quick Replies
<label for="quickReplyNumberOfSlots">Number of slots:</label> </label>
<div class="flex-container flexnowrap wide100p">
<select id="quickReplyPresets" name="quickreply-preset">
</select>
<i id="quickReplyPresetSaveButton" class="fa-solid fa-save"></i>
</div>
<label for="quickReplyNumberOfSlots">Number of slots:</label>
</div>
<div class="flex-container flexGap5 flexnowrap"> <div class="flex-container flexGap5 flexnowrap">
<input id="quickReplyNumberOfSlots" class="text_pole" type="number" min="1" max="100" value="" /> <input id="quickReplyNumberOfSlots" class="text_pole" type="number" min="1" max="100" value="" />
<div class="menu_button menu_button_icon" id="quickReplyNumberOfSlotsApply"> <div class="menu_button menu_button_icon" id="quickReplyNumberOfSlotsApply">
@ -212,8 +314,17 @@ jQuery(async () => {
$('#quickReplyEnabled').on('input', onQuickReplyEnabledInput); $('#quickReplyEnabled').on('input', onQuickReplyEnabledInput);
$('#quickReplyNumberOfSlotsApply').on('click', onQuickReplyNumberOfSlotsInput); $('#quickReplyNumberOfSlotsApply').on('click', onQuickReplyNumberOfSlotsInput);
$("#quickReplyPresetSaveButton").on('click', saveQuickReplyPreset);
await loadSettings(); $("#quickReplyPresets").on('change', async function () {
const quickReplyPresetSelected = $(this).find(':selected').val();
extension_settings.quickReplyPreset = quickReplyPresetSelected;
applyQuickReplyPreset(quickReplyPresetSelected);
saveSettingsDebounced();
});
await loadSettings('init');
addQuickReplyBar(); addQuickReplyBar();
}); });

View File

@ -8,16 +8,21 @@ import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper
import { VoskSttProvider } from './vosk.js' import { VoskSttProvider } from './vosk.js'
import { WhisperSttProvider } from './whisper.js' import { WhisperSttProvider } from './whisper.js'
import { BrowserSttProvider } from './browser.js' import { BrowserSttProvider } from './browser.js'
import { StreamingSttProvider } from './streaming.js'
export { MODULE_NAME }; export { MODULE_NAME };
const MODULE_NAME = 'Speech Recognition'; const MODULE_NAME = 'Speech Recognition';
const DEBUG_PREFIX = "<Speech Recognition module> " const DEBUG_PREFIX = "<Speech Recognition module> "
const UPDATE_INTERVAL = 100;
let inApiCall = false;
let sttProviders = { let sttProviders = {
None: null, None: null,
Browser: BrowserSttProvider, Browser: BrowserSttProvider,
Whisper: WhisperSttProvider, Whisper: WhisperSttProvider,
Vosk: VoskSttProvider, Vosk: VoskSttProvider,
Streaming: StreamingSttProvider,
} }
let sttProvider = null let sttProvider = null
@ -27,6 +32,82 @@ let audioRecording = false
const constraints = { audio: { sampleSize: 16, channelCount: 1, sampleRate: 16000 } }; const constraints = { audio: { sampleSize: 16, channelCount: 1, sampleRate: 16000 } };
let audioChunks = []; let audioChunks = [];
async function moduleWorker() {
if (sttProviderName != "Streaming") {
return;
}
// API is busy
if (inApiCall) {
return;
}
try {
inApiCall = true;
const userMessageOriginal = await sttProvider.getUserMessage();
let userMessageFormatted = userMessageOriginal.trim();
if (userMessageFormatted.length > 0)
{
console.debug(DEBUG_PREFIX+"recorded transcript: \""+userMessageFormatted+"\"");
let userMessageLower = userMessageFormatted.toLowerCase();
// remove punctuation
let userMessageRaw = userMessageLower.replace(/[^\w\s\']|_/g, "").replace(/\s+/g, " ");
console.debug(DEBUG_PREFIX+"raw transcript:",userMessageRaw);
// Detect trigger words
let messageStart = -1;
if (extension_settings.speech_recognition.Streaming.triggerWordsEnabled) {
for (const triggerWord of extension_settings.speech_recognition.Streaming.triggerWords) {
const triggerPos = userMessageRaw.indexOf(triggerWord.toLowerCase());
// Trigger word not found or not starting message and just a substring
if (triggerPos == -1){ // | (triggerPos > 0 & userMessageFormatted[triggerPos-1] != " ")) {
console.debug(DEBUG_PREFIX+"trigger word not found: ", triggerWord);
}
else {
console.debug(DEBUG_PREFIX+"Found trigger word: ", triggerWord, " at index ", triggerPos);
if (triggerPos < messageStart | messageStart == -1) { // & (triggerPos + triggerWord.length) < userMessageFormatted.length)) {
messageStart = triggerPos; // + triggerWord.length + 1;
}
}
}
} else {
messageStart = 0;
}
if (messageStart == -1) {
console.debug(DEBUG_PREFIX+"message ignored, no trigger word preceding a message. Voice transcript: \""+ userMessageOriginal +"\"");
if (extension_settings.speech_recognition.Streaming.debug) {
toastr.info(
"No trigger word preceding a message. Voice transcript: \""+ userMessageOriginal +"\"",
DEBUG_PREFIX+"message ignored.",
{ timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true },
);
}
}
else{
userMessageFormatted = userMessageFormatted.substring(messageStart);
processTranscript(userMessageFormatted);
}
}
else
{
console.debug(DEBUG_PREFIX+"Received empty transcript, ignored");
}
}
catch (error) {
console.debug(error);
}
finally {
inApiCall = false;
}
}
async function processTranscript(transcript) { async function processTranscript(transcript) {
try { try {
const transcriptOriginal = transcript; const transcriptOriginal = transcript;
@ -198,13 +279,21 @@ function loadSttProvider(provider) {
if (sttProviderName == "Browser") { if (sttProviderName == "Browser") {
sttProvider.processTranscriptFunction = processTranscript; sttProvider.processTranscriptFunction = processTranscript;
sttProvider.loadSettings(extension_settings.speech_recognition[sttProviderName]); sttProvider.loadSettings(extension_settings.speech_recognition[sttProviderName]);
}
else {
sttProvider.loadSettings(extension_settings.speech_recognition[sttProviderName]);
loadNavigatorAudioRecording();
$("#microphone_button").show(); $("#microphone_button").show();
} }
if (sttProviderName == "Vosk" | sttProviderName == "Whisper") {
sttProvider.loadSettings(extension_settings.speech_recognition[sttProviderName]);
loadNavigatorAudioRecording();
$("#microphone_button").show();
}
if (sttProviderName == "Streaming") {
sttProvider.loadSettings(extension_settings.speech_recognition[sttProviderName]);
$("#microphone_button").off('click');
$("#microphone_button").hide();
}
} }
function onSttProviderChange() { function onSttProviderChange() {
@ -231,7 +320,7 @@ const defaultSettings = {
messageMode: "append", messageMode: "append",
messageMappingText: "", messageMappingText: "",
messageMapping: [], messageMapping: [],
messageMappingEnabled: false messageMappingEnabled: false,
} }
function loadSettings() { function loadSettings() {
@ -344,8 +433,7 @@ $(document).ready(function () {
addExtensionControls(); // No init dependencies addExtensionControls(); // No init dependencies
loadSettings(); // Depends on Extension Controls and loadTtsProvider loadSettings(); // Depends on Extension Controls and loadTtsProvider
loadSttProvider(extension_settings.speech_recognition.currentProvider); // No dependencies loadSttProvider(extension_settings.speech_recognition.currentProvider); // No dependencies
const wrapper = new ModuleWorkerWrapper(moduleWorker);
//const wrapper = new ModuleWorkerWrapper(moduleWorker); setInterval(wrapper.update.bind(wrapper), UPDATE_INTERVAL); // Init depends on all the things
//setInterval(wrapper.update.bind(wrapper), UPDATE_INTERVAL); // Init depends on all the things moduleWorker();
//moduleWorker();
}) })

View File

@ -0,0 +1,102 @@
import { getApiUrl, doExtrasFetch, modules } from "../../extensions.js";
export { StreamingSttProvider }
const DEBUG_PREFIX = "<Speech Recognition module (streaming)> "
class StreamingSttProvider {
//########//
// Config //
//########//
settings
defaultSettings = {
triggerWordsText: "",
triggerWords : [],
triggerWordsEnabled : false,
debug : false,
}
get settingsHtml() {
let html = '\
<div id="speech_recognition_streaming_trigger_words_div">\
<span>Trigger words</span>\
<textarea id="speech_recognition_streaming_trigger_words" class="text_pole textarea_compact" type="text" rows="4" placeholder="Enter comma separated words that triggers new message, example:\nhey, hey aqua, record, listen"></textarea>\
<label class="checkbox_label" for="speech_recognition_streaming_trigger_words_enabled">\
<input type="checkbox" id="speech_recognition_streaming_trigger_words_enabled" name="speech_recognition_trigger_words_enabled">\
<small>Enable trigger words</small>\
</label>\
<label class="checkbox_label" for="speech_recognition_streaming_debug">\
<input type="checkbox" id="speech_recognition_streaming_debug" name="speech_recognition_streaming_debug">\
<small>Enable debug pop ups</small>\
</label>\
</div>\
'
return html
}
onSettingsChange() {
this.settings.triggerWordsText = $('#speech_recognition_streaming_trigger_words').val();
let array = $('#speech_recognition_streaming_trigger_words').val().split(",");
array = array.map(element => {return element.trim().toLowerCase();});
array = array.filter((str) => str !== '');
this.settings.triggerWords = array;
this.settings.triggerWordsEnabled = $("#speech_recognition_streaming_trigger_words_enabled").is(':checked');
this.settings.debug = $("#speech_recognition_streaming_debug").is(':checked');
console.debug(DEBUG_PREFIX+" Updated settings: ", this.settings);
this.loadSettings(this.settings);
}
loadSettings(settings) {
// Populate Provider UI given input settings
if (Object.keys(settings).length == 0) {
console.debug(DEBUG_PREFIX+"Using default Whisper STT extension settings")
}
// Only accept keys defined in defaultSettings
this.settings = this.defaultSettings
for (const key in settings){
if (key in this.settings){
this.settings[key] = settings[key]
} else {
throw `Invalid setting passed to STT extension: ${key}`
}
}
$("#speech_recognition_streaming_trigger_words").val(this.settings.triggerWordsText);
$("#speech_recognition_streaming_trigger_words_enabled").prop('checked',this.settings.triggerWordsEnabled);
$("#speech_recognition_streaming_debug").prop('checked',this.settings.debug);
console.debug(DEBUG_PREFIX+"streaming STT settings loaded")
}
async getUserMessage() {
// Return if module is not loaded
if (!modules.includes('streaming-stt')) {
console.debug(DEBUG_PREFIX+"Module streaming-stt must be activated in Sillytavern Extras for streaming user voice.")
return "";
}
const url = new URL(getApiUrl());
url.pathname = '/api/speech-recognition/streaming/record-and-transcript';
const apiResult = await doExtrasFetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Bypass-Tunnel-Reminder': 'bypass',
},
body: JSON.stringify({ text: "" }),
});
if (!apiResult.ok) {
toastr.error(apiResult.statusText, DEBUG_PREFIX+'STT Generation Failed (streaming)', { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
throw new Error(`HTTP ${apiResult.status}: ${await apiResult.text()}`);
}
const data = await apiResult.json();
return data.transcript;
}
}

View File

@ -1,5 +1,5 @@
import { callPopup, cancelTtsPlay, eventSource, event_types, isMultigenEnabled, is_send_press, saveSettingsDebounced } from '../../../script.js' import { callPopup, cancelTtsPlay, eventSource, event_types, isMultigenEnabled, is_send_press, saveSettingsDebounced } from '../../../script.js'
import { ModuleWorkerWrapper, extension_settings, getContext } from '../../extensions.js' import { ModuleWorkerWrapper, doExtrasFetch, extension_settings, getApiUrl, getContext } from '../../extensions.js'
import { escapeRegex, getStringHash } from '../../utils.js' import { escapeRegex, getStringHash } from '../../utils.js'
import { EdgeTtsProvider } from './edge.js' import { EdgeTtsProvider } from './edge.js'
import { ElevenLabsTtsProvider } from './elevenlabs.js' import { ElevenLabsTtsProvider } from './elevenlabs.js'
@ -7,14 +7,13 @@ import { SileroTtsProvider } from './silerotts.js'
import { CoquiTtsProvider } from './coquitts.js' import { CoquiTtsProvider } from './coquitts.js'
import { SystemTtsProvider } from './system.js' import { SystemTtsProvider } from './system.js'
import { NovelTtsProvider } from './novel.js' import { NovelTtsProvider } from './novel.js'
import { isMobile } from '../../RossAscends-mods.js'
import { power_user } from '../../power-user.js' import { power_user } from '../../power-user.js'
const UPDATE_INTERVAL = 1000 const UPDATE_INTERVAL = 1000
let voiceMap = {} // {charName:voiceid, charName2:voiceid2} let voiceMap = {} // {charName:voiceid, charName2:voiceid2}
let audioControl let audioControl
let storedvalue = false;
let lastCharacterId = null let lastCharacterId = null
let lastGroupId = null let lastGroupId = null
let lastChatId = null let lastChatId = null
@ -164,6 +163,20 @@ async function moduleWorker() {
ttsJobQueue.push(message) ttsJobQueue.push(message)
} }
function talkingAnimation(switchValue) {
const apiUrl = getApiUrl();
const animationType = switchValue ? "start" : "stop";
if (switchValue !== storedvalue) {
try {
console.log(animationType + " Talking Animation");
doExtrasFetch(`${apiUrl}/api/live2d/${animationType}_talking`);
storedvalue = switchValue; // Update the storedvalue to the current switchValue
} catch (error) {
// Handle the error here or simply ignore it to prevent logging
}
}
}
function resetTtsPlayback() { function resetTtsPlayback() {
// Stop system TTS utterance // Stop system TTS utterance
@ -291,8 +304,10 @@ function updateUiAudioPlayState() {
// Give user feedback that TTS is active by setting the stop icon if processing or playing // Give user feedback that TTS is active by setting the stop icon if processing or playing
if (!audioElement.paused || isTtsProcessing()) { if (!audioElement.paused || isTtsProcessing()) {
img = 'fa-solid fa-stop-circle extensionsMenuExtensionButton' img = 'fa-solid fa-stop-circle extensionsMenuExtensionButton'
talkingAnimation(true)
} else { } else {
img = 'fa-solid fa-circle-play extensionsMenuExtensionButton' img = 'fa-solid fa-circle-play extensionsMenuExtensionButton'
talkingAnimation(false)
} }
$('#tts_media_control').attr('class', img); $('#tts_media_control').attr('class', img);
} else { } else {
@ -354,6 +369,7 @@ async function processAudioJobQueue() {
audioQueueProcessorReady = false audioQueueProcessorReady = false
currentAudioJob = audioJobQueue.pop() currentAudioJob = audioJobQueue.pop()
playAudioData(currentAudioJob) playAudioData(currentAudioJob)
talkingAnimation(true)
} catch (error) { } catch (error) {
console.error(error) console.error(error)
audioQueueProcessorReady = true audioQueueProcessorReady = true

View File

@ -5,6 +5,7 @@ import {
delay, delay,
isDataURL, isDataURL,
createThumbnail, createThumbnail,
extractAllWords,
} from './utils.js'; } from './utils.js';
import { RA_CountCharTokens, humanizedDateTime, dragElement } from "./RossAscends-mods.js"; import { RA_CountCharTokens, humanizedDateTime, dragElement } from "./RossAscends-mods.js";
import { sortCharactersList, sortGroupMembers, loadMovingUIState } from './power-user.js'; import { sortCharactersList, sortGroupMembers, loadMovingUIState } from './power-user.js';
@ -782,19 +783,6 @@ function activateNaturalOrder(members, input, lastMessage, allowSelfResponses, i
return memberIds; return memberIds;
} }
function extractAllWords(value) {
const words = [];
if (!value) {
return words;
}
const matches = value.matchAll(/\b\w+\b/gim);
for (let match of matches) {
words.push(match[0].toLowerCase());
}
return words;
}
async function deleteGroup(id) { async function deleteGroup(id) {

View File

@ -1,7 +1,10 @@
import { import {
getRequestHeaders, getRequestHeaders,
saveSettingsDebounced, saveSettingsDebounced,
getStoppingStrings,
getTextTokens
} from "../script.js"; } from "../script.js";
import { tokenizers } from "./power-user.js";
export { export {
nai_settings, nai_settings,
@ -62,6 +65,8 @@ function loadNovelPreset(preset) {
nai_settings.top_a = preset.top_a; nai_settings.top_a = preset.top_a;
nai_settings.typical_p = preset.typical_p; nai_settings.typical_p = preset.typical_p;
nai_settings.min_length = preset.min_length; nai_settings.min_length = preset.min_length;
nai_settings.cfg_scale = preset.cfg_scale;
nai_settings.phrase_rep_pen = preset.phrase_rep_pen;
loadNovelSettingsUi(nai_settings); loadNovelSettingsUi(nai_settings);
} }
@ -84,14 +89,42 @@ function loadNovelSettings(settings) {
nai_settings.top_a = settings.top_a; nai_settings.top_a = settings.top_a;
nai_settings.typical_p = settings.typical_p; nai_settings.typical_p = settings.typical_p;
nai_settings.min_length = settings.min_length; nai_settings.min_length = settings.min_length;
nai_settings.phrase_rep_pen = settings.phrase_rep_pen;
nai_settings.cfg_scale = settings.cfg_scale;
nai_settings.streaming_novel = !!settings.streaming_novel; nai_settings.streaming_novel = !!settings.streaming_novel;
loadNovelSettingsUi(nai_settings); loadNovelSettingsUi(nai_settings);
}
// reload the preset to migrate any new settings const phraseRepPenStrings = [
for (const key of Object.keys(nai_settings)) { null,
if (typeof nai_settings[key] === 'number' && Number.isNaN(nai_settings[key])) { "very_light",
$("#settings_perset_novel").trigger("change"); "light",
} "medium",
"aggressive",
"very_aggressive"
]
function getPhraseRepPenString(phraseRepPenCounter) {
if (phraseRepPenCounter < 1 || phraseRepPenCounter > 5) {
return null;
} else {
return phraseRepPenStrings[phraseRepPenCounter];
}
}
function getPhraseRepPenCounter(phraseRepPenString) {
if (phraseRepPenString === phraseRepPenStrings[1]) {
return 1;
} else if (phraseRepPenString === phraseRepPenStrings[2]) {
return 2;
} else if (phraseRepPenString === phraseRepPenStrings[3]) {
return 3;
} else if (phraseRepPenString === phraseRepPenStrings[4]) {
return 4;
} else if (phraseRepPenString === phraseRepPenStrings[5]) {
return 5;
} else {
return 0;
} }
} }
@ -118,6 +151,10 @@ function loadNovelSettingsUi(ui_settings) {
$("#top_a_counter_novel").text(Number(ui_settings.top_a).toFixed(2)); $("#top_a_counter_novel").text(Number(ui_settings.top_a).toFixed(2));
$("#typical_p_novel").val(ui_settings.typical_p); $("#typical_p_novel").val(ui_settings.typical_p);
$("#typical_p_counter_novel").text(Number(ui_settings.typical_p).toFixed(2)); $("#typical_p_counter_novel").text(Number(ui_settings.typical_p).toFixed(2));
$("#cfg_scale_novel").val(ui_settings.cfg_scale);
$("#cfg_scale_counter_novel").text(Number(ui_settings.cfg_scale).toFixed(2));
$("#phrase_rep_pen_novel").val(getPhraseRepPenCounter(ui_settings.phrase_rep_pen));
$("#phrase_rep_pen_counter_novel").text(getPhraseRepPenCounter(ui_settings.phrase_rep_pen));
$("#min_length_novel").val(ui_settings.min_length); $("#min_length_novel").val(ui_settings.min_length);
$("#min_length_counter_novel").text(Number(ui_settings.min_length).toFixed(0)); $("#min_length_counter_novel").text(Number(ui_settings.min_length).toFixed(0));
@ -191,6 +228,18 @@ const sliders = [
format: (val) => Number(val).toFixed(2), format: (val) => Number(val).toFixed(2),
setValue: (val) => { nai_settings.typical_p = Number(val).toFixed(2); }, setValue: (val) => { nai_settings.typical_p = Number(val).toFixed(2); },
}, },
{
sliderId: "#cfg_scale_novel",
counterId: "#cfg_scale_counter_novel",
format: (val) => `${val}`,
setValue: (val) => { nai_settings.cfg_scale = Number(val).toFixed(2); },
},
{
sliderId: "#phrase_rep_pen_novel",
counterId: "#phrase_rep_pen_counter_novel",
format: (val) => `${val}`,
setValue: (val) => { nai_settings.phrase_rep_pen = getPhraseRepPenString(Number(val).toFixed(0)); },
},
{ {
sliderId: "#min_length_novel", sliderId: "#min_length_novel",
counterId: "#min_length_counter_novel", counterId: "#min_length_counter_novel",
@ -199,7 +248,17 @@ const sliders = [
}, },
]; ];
export function getNovelGenerationData(finalPromt, this_settings, this_amount_gen) { export function getNovelGenerationData(finalPromt, this_settings, this_amount_gen, isImpersonate) {
const clio = nai_settings.model_novel.includes('clio');
const kayra = nai_settings.model_novel.includes('kayra');
const isNewModel = clio || kayra;
const tokenizerType = kayra ? tokenizers.NERD2 : (clio ? tokenizers.NERD : tokenizers.NONE);
const stopSequences = (tokenizerType !== tokenizers.NONE)
? getStoppingStrings(isImpersonate, false)
.map(t => getTextTokens(tokenizerType, t))
: undefined;
return { return {
"input": finalPromt, "input": finalPromt,
"model": nai_settings.model_novel, "model": nai_settings.model_novel,
@ -217,13 +276,17 @@ export function getNovelGenerationData(finalPromt, this_settings, this_amount_ge
"top_p": parseFloat(nai_settings.top_p), "top_p": parseFloat(nai_settings.top_p),
"top_k": parseInt(nai_settings.top_k), "top_k": parseInt(nai_settings.top_k),
"typical_p": parseFloat(nai_settings.typical_p), "typical_p": parseFloat(nai_settings.typical_p),
"cfg_scale": parseFloat(nai_settings.cfg_scale),
"cfg_uc": "",
"phrase_rep_pen": nai_settings.phrase_rep_pen,
//"stop_sequences": {{187}}, //"stop_sequences": {{187}},
"stop_sequences": stopSequences,
//bad_words_ids = {{50256}, {0}, {1}}; //bad_words_ids = {{50256}, {0}, {1}};
"generate_until_sentence": true, "generate_until_sentence": true,
"use_cache": false, "use_cache": false,
"use_string": true, "use_string": true,
"return_full_text": false, "return_full_text": false,
"prefix": "vanilla", "prefix": isNewModel ? "special_instruct" : "vanilla",
"order": this_settings.order, "order": this_settings.order,
"streaming": nai_settings.streaming_novel, "streaming": nai_settings.streaming_novel,
}; };

View File

@ -143,6 +143,7 @@ const default_settings = {
api_url_scale: '', api_url_scale: '',
show_external_models: false, show_external_models: false,
proxy_password: '', proxy_password: '',
assistant_prefill: '',
}; };
const oai_settings = { const oai_settings = {
@ -180,6 +181,7 @@ const oai_settings = {
api_url_scale: '', api_url_scale: '',
show_external_models: false, show_external_models: false,
proxy_password: '', proxy_password: '',
assistant_prefill: '',
}; };
let openai_setting_names; let openai_setting_names;
@ -775,6 +777,7 @@ async function sendOpenAIRequest(type, openai_msgs_tosend, signal) {
if (isClaude) { if (isClaude) {
generate_data['use_claude'] = true; generate_data['use_claude'] = true;
generate_data['top_k'] = parseFloat(oai_settings.top_k_openai); generate_data['top_k'] = parseFloat(oai_settings.top_k_openai);
generate_data['assistant_prefill'] = substituteParams(oai_settings.assistant_prefill);
} }
if (isOpenRouter) { if (isOpenRouter) {
@ -1109,6 +1112,7 @@ function loadOpenAISettings(data, settings) {
oai_settings.api_url_scale = settings.api_url_scale ?? default_settings.api_url_scale; oai_settings.api_url_scale = settings.api_url_scale ?? default_settings.api_url_scale;
oai_settings.show_external_models = settings.show_external_models ?? default_settings.show_external_models; oai_settings.show_external_models = settings.show_external_models ?? default_settings.show_external_models;
oai_settings.proxy_password = settings.proxy_password ?? default_settings.proxy_password; oai_settings.proxy_password = settings.proxy_password ?? default_settings.proxy_password;
oai_settings.assistant_prefill = settings.assistant_prefill ?? default_settings.assistant_prefill;
if (settings.nsfw_toggle !== undefined) oai_settings.nsfw_toggle = !!settings.nsfw_toggle; if (settings.nsfw_toggle !== undefined) oai_settings.nsfw_toggle = !!settings.nsfw_toggle;
if (settings.keep_example_dialogue !== undefined) oai_settings.keep_example_dialogue = !!settings.keep_example_dialogue; if (settings.keep_example_dialogue !== undefined) oai_settings.keep_example_dialogue = !!settings.keep_example_dialogue;
@ -1121,6 +1125,7 @@ function loadOpenAISettings(data, settings) {
$('#stream_toggle').prop('checked', oai_settings.stream_openai); $('#stream_toggle').prop('checked', oai_settings.stream_openai);
$('#api_url_scale').val(oai_settings.api_url_scale); $('#api_url_scale').val(oai_settings.api_url_scale);
$('#openai_proxy_password').val(oai_settings.proxy_password); $('#openai_proxy_password').val(oai_settings.proxy_password);
$('#claude_assistant_prefill').val(oai_settings.assistant_prefill);
$('#model_openai_select').val(oai_settings.openai_model); $('#model_openai_select').val(oai_settings.openai_model);
$(`#model_openai_select option[value="${oai_settings.openai_model}"`).attr('selected', true); $(`#model_openai_select option[value="${oai_settings.openai_model}"`).attr('selected', true);
@ -1323,6 +1328,7 @@ async function saveOpenAIPreset(name, settings) {
stream_openai: settings.stream_openai, stream_openai: settings.stream_openai,
api_url_scale: settings.api_url_scale, api_url_scale: settings.api_url_scale,
show_external_models: settings.show_external_models, show_external_models: settings.show_external_models,
assistant_prefill: settings.assistant_prefill,
}; };
const savePresetSettings = await fetch(`/savepreset_openai?name=${name}`, { const savePresetSettings = await fetch(`/savepreset_openai?name=${name}`, {
@ -1656,6 +1662,7 @@ function onSettingsPresetChange() {
api_url_scale: ['#api_url_scale', 'api_url_scale', false], api_url_scale: ['#api_url_scale', 'api_url_scale', false],
show_external_models: ['#openai_show_external_models', 'show_external_models', true], show_external_models: ['#openai_show_external_models', 'show_external_models', true],
proxy_password: ['#openai_proxy_password', 'proxy_password', false], proxy_password: ['#openai_proxy_password', 'proxy_password', false],
assistant_prefill: ['#claude_assistant_prefill', 'assistant_prefill', false],
}; };
for (const [key, [selector, setting, isCheckbox]] of Object.entries(settingsToUpdate)) { for (const [key, [selector, setting, isCheckbox]] of Object.entries(settingsToUpdate)) {
@ -2206,6 +2213,11 @@ $(document).ready(function () {
saveSettingsDebounced(); saveSettingsDebounced();
}); });
$('#claude_assistant_prefill').on('input', function () {
oai_settings.assistant_prefill = $(this).val();
saveSettingsDebounced();
});
$("#api_button_openai").on("click", onConnectButtonClick); $("#api_button_openai").on("click", onConnectButtonClick);
$("#openai_reverse_proxy").on("input", onReverseProxyInput); $("#openai_reverse_proxy").on("input", onReverseProxyInput);
$("#model_openai_select").on("change", onModelChange); $("#model_openai_select").on("change", onModelChange);

View File

@ -256,7 +256,7 @@ function fixMarkdown(text) {
// i.e. "^example * text* * harder problem *\n" -> "^example *text* *harder problem*\n" // i.e. "^example * text* * harder problem *\n" -> "^example *text* *harder problem*\n"
// Find pairs of formatting characters and capture the text in between them // Find pairs of formatting characters and capture the text in between them
const format = /(\*|_|~){1,2}([\s\S]*?)\1{1,2}/gm; const format = /([\*_]{1,2})([\s\S]*?)\1/gm;
let matches = []; let matches = [];
let match; let match;
while ((match = format.exec(text)) !== null) { while ((match = format.exec(text)) !== null) {
@ -267,7 +267,7 @@ function fixMarkdown(text) {
let newText = text; let newText = text;
for (let i = matches.length - 1; i >= 0; i--) { for (let i = matches.length - 1; i >= 0; i--) {
let matchText = matches[i][0]; let matchText = matches[i][0];
let replacementText = matchText.replace(/(\*|_|~)(\s+)|(\s+)(\*|_|~)/g, '$1$4'); let replacementText = matchText.replace(/(\*|_)([\t \u00a0\u1680\u2000-\u200a\u202f\u205f\u3000\ufeff]+)|([\t \u00a0\u1680\u2000-\u200a\u202f\u205f\u3000\ufeff]+)(\*|_)/g, '$1$4');
newText = newText.slice(0, matches[i].index) + replacementText + newText.slice(matches[i].index + matchText.length); newText = newText.slice(0, matches[i].index) + replacementText + newText.slice(matches[i].index + matchText.length);
} }
@ -1224,12 +1224,6 @@ async function doMesCut(_, text) {
return return
} }
//reject attempts to delete firstmes
if (text === '0') {
toastr.error('Cannot delete the First Message')
return
}
let mesIDToCut = Number(text).toFixed(0) let mesIDToCut = Number(text).toFixed(0)
let mesToCut = $("#chat").find(`.mes[mesid=${mesIDToCut}]`) let mesToCut = $("#chat").find(`.mes[mesid=${mesIDToCut}]`)

View File

@ -499,7 +499,7 @@ function onViewTagsListClick() {
$(list).append('<h3>Tags</h3><i>Click on the tag name to edit it.</i><br>'); $(list).append('<h3>Tags</h3><i>Click on the tag name to edit it.</i><br>');
$(list).append('<i>Click on color box to assign new color.</i><br><br>'); $(list).append('<i>Click on color box to assign new color.</i><br><br>');
for (const tag of tags) { for (const tag of tags.slice().sort((a, b) => a?.name?.localeCompare(b?.name))) {
const count = everything.filter(x => x == tag.id).length; const count = everything.filter(x => x == tag.id).length;
const template = $('#tag_view_template .tag_view_item').clone(); const template = $('#tag_view_template .tag_view_item').clone();
template.attr('id', tag.id); template.attr('id', tag.id);

View File

@ -1,154 +0,0 @@
import cloneDeep from 'lodash.clonedeep';
import userAgents from './user-agents.json';
// Normalizes the total weight to 1 and constructs a cumulative distribution.
const makeCumulativeWeightIndexPairs = (weightIndexPairs) => {
const totalWeight = weightIndexPairs.reduce((sum, [weight]) => sum + weight, 0);
let sum = 0;
return weightIndexPairs.map(([weight, index]) => {
sum += weight / totalWeight;
return [sum, index];
});
};
// Precompute these so that we can quickly generate unfiltered user agents.
const defaultWeightIndexPairs = userAgents.map(({ weight }, index) => [weight, index]);
const defaultCumulativeWeightIndexPairs = makeCumulativeWeightIndexPairs(defaultWeightIndexPairs);
// Turn the various filter formats into a single filter function that acts on raw user agents.
const constructFilter = (filters, accessor = parentObject => parentObject) => {
let childFilters;
if (typeof filters === 'function') {
childFilters = [filters];
} else if (filters instanceof RegExp) {
childFilters = [
value => (
typeof value === 'object' && value && value.userAgent
? filters.test(value.userAgent)
: filters.test(value)
),
];
} else if (filters instanceof Array) {
childFilters = filters.map(childFilter => constructFilter(childFilter));
} else if (typeof filters === 'object') {
childFilters = Object.entries(filters).map(([key, valueFilter]) => (
constructFilter(valueFilter, parentObject => parentObject[key])
));
} else {
childFilters = [
value => (
typeof value === 'object' && value && value.userAgent
? filters === value.userAgent
: filters === value
),
];
}
return (parentObject) => {
try {
const value = accessor(parentObject);
return childFilters.every(childFilter => childFilter(value));
} catch (error) {
// This happens when a user-agent lacks a nested property.
return false;
}
};
};
// Construct normalized cumulative weight index pairs given the filters.
const constructCumulativeWeightIndexPairsFromFilters = (filters) => {
if (!filters) {
return defaultCumulativeWeightIndexPairs;
}
const filter = constructFilter(filters);
const weightIndexPairs = [];
userAgents.forEach((rawUserAgent, index) => {
if (filter(rawUserAgent)) {
weightIndexPairs.push([rawUserAgent.weight, index]);
}
});
return makeCumulativeWeightIndexPairs(weightIndexPairs);
};
const setCumulativeWeightIndexPairs = (userAgent, cumulativeWeightIndexPairs) => {
Object.defineProperty(userAgent, 'cumulativeWeightIndexPairs', {
configurable: true,
enumerable: false,
writable: false,
value: cumulativeWeightIndexPairs,
});
};
export default class UserAgent extends Function {
constructor(filters) {
super();
setCumulativeWeightIndexPairs(this, constructCumulativeWeightIndexPairsFromFilters(filters));
if (this.cumulativeWeightIndexPairs.length === 0) {
throw new Error('No user agents matched your filters.');
}
this.randomize();
return new Proxy(this, {
apply: () => this.random(),
get: (target, property, receiver) => {
const dataCandidate = target.data && typeof property === 'string'
&& Object.prototype.hasOwnProperty.call(target.data, property)
&& Object.prototype.propertyIsEnumerable.call(target.data, property);
if (dataCandidate) {
const value = target.data[property];
if (value !== undefined) {
return value;
}
}
return Reflect.get(target, property, receiver);
},
});
}
static random = (filters) => {
try {
return new UserAgent(filters);
} catch (error) {
return null;
}
};
//
// Standard Object Methods
//
[Symbol.toPrimitive] = () => (
this.data.userAgent
);
toString = () => (
this.data.userAgent
);
random = () => {
const userAgent = new UserAgent();
setCumulativeWeightIndexPairs(userAgent, this.cumulativeWeightIndexPairs);
userAgent.randomize();
return userAgent;
};
randomize = () => {
// Find a random raw random user agent.
const randomNumber = Math.random();
const [, index] = this.cumulativeWeightIndexPairs
.find(([cumulativeWeight]) => cumulativeWeight > randomNumber);
const rawUserAgent = userAgents[index];
this.data = cloneDeep(rawUserAgent);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -470,6 +470,20 @@ export function getCharaFilename(chid) {
} }
} }
export function extractAllWords(value) {
const words = [];
if (!value) {
return words;
}
const matches = value.matchAll(/\b\w+\b/gim);
for (let match of matches) {
words.push(match[0].toLowerCase());
}
return words;
}
export function escapeRegex(string) { export function escapeRegex(string) {
return string.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&'); return string.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&');
} }

View File

@ -10,6 +10,7 @@ export {
world_info_budget, world_info_budget,
world_info_depth, world_info_depth,
world_info_recursive, world_info_recursive,
world_info_overflow_alert,
world_info_case_sensitive, world_info_case_sensitive,
world_info_match_whole_words, world_info_match_whole_words,
world_info_character_strategy, world_info_character_strategy,
@ -32,6 +33,7 @@ let world_names;
let world_info_depth = 2; let world_info_depth = 2;
let world_info_budget = 25; let world_info_budget = 25;
let world_info_recursive = false; let world_info_recursive = false;
let world_info_overflow_alert = false;
let world_info_case_sensitive = false; let world_info_case_sensitive = false;
let world_info_match_whole_words = false; let world_info_match_whole_words = false;
let world_info_character_strategy = world_info_insertion_strategy.character_first; let world_info_character_strategy = world_info_insertion_strategy.character_first;
@ -70,6 +72,8 @@ function setWorldInfoSettings(settings, data) {
world_info_budget = Number(settings.world_info_budget); world_info_budget = Number(settings.world_info_budget);
if (settings.world_info_recursive !== undefined) if (settings.world_info_recursive !== undefined)
world_info_recursive = Boolean(settings.world_info_recursive); world_info_recursive = Boolean(settings.world_info_recursive);
if (settings.world_info_overflow_alert !== undefined)
world_info_overflow_alert = Boolean(settings.world_info_overflow_alert);
if (settings.world_info_case_sensitive !== undefined) if (settings.world_info_case_sensitive !== undefined)
world_info_case_sensitive = Boolean(settings.world_info_case_sensitive); world_info_case_sensitive = Boolean(settings.world_info_case_sensitive);
if (settings.world_info_match_whole_words !== undefined) if (settings.world_info_match_whole_words !== undefined)
@ -102,6 +106,7 @@ function setWorldInfoSettings(settings, data) {
$("#world_info_budget").val(world_info_budget); $("#world_info_budget").val(world_info_budget);
$("#world_info_recursive").prop('checked', world_info_recursive); $("#world_info_recursive").prop('checked', world_info_recursive);
$("#world_info_overflow_alert").prop('checked', world_info_overflow_alert);
$("#world_info_case_sensitive").prop('checked', world_info_case_sensitive); $("#world_info_case_sensitive").prop('checked', world_info_case_sensitive);
$("#world_info_match_whole_words").prop('checked', world_info_match_whole_words); $("#world_info_match_whole_words").prop('checked', world_info_match_whole_words);
@ -1020,6 +1025,10 @@ async function checkWorldInfo(chat, maxContext) {
if (textToScanTokens + getTokenCount(newContent) >= budget) { if (textToScanTokens + getTokenCount(newContent) >= budget) {
console.debug(`WI budget reached, stopping`); console.debug(`WI budget reached, stopping`);
if (world_info_overflow_alert) {
console.log("Alerting");
toastr.warning(`World info budget reached after ${count} entries.`, 'World Info');
}
needsToScan = false; needsToScan = false;
break; break;
} }
@ -1501,6 +1510,11 @@ jQuery(() => {
saveSettingsDebounced(); saveSettingsDebounced();
}); });
$('#world_info_overflow_alert').on('change', function () {
world_info_overflow_alert = $(this).val();
saveSettingsDebounced();
});
$('#world_button').on('click', async function () { $('#world_button').on('click', async function () {
const chid = $('#set_character_world').data('chid'); const chid = $('#set_character_world').data('chid');

View File

@ -609,7 +609,7 @@ hr {
display: flex; display: flex;
column-gap: 10px; column-gap: 10px;
cursor: pointer; cursor: pointer;
align-items: baseline;
} }
#extensionsMenu>div, #extensionsMenu>div,
@ -1293,7 +1293,7 @@ body.charListGrid #rm_print_characters_block .tags_inline {
} }
.floating_prompt_radio_group { .floating_prompt_radio_group, .radio_group {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }

View File

@ -186,13 +186,19 @@ async function loadSentencepieceTokenizer(modelPath) {
async function countSentencepieceTokens(spp, text) { async function countSentencepieceTokens(spp, text) {
// Fallback to strlen estimation // Fallback to strlen estimation
if (!spp) { if (!spp) {
return Math.ceil(text.length / CHARS_PER_TOKEN); return {
ids: [],
count: Math.ceil(text.length / CHARS_PER_TOKEN)
};
} }
let cleaned = cleanText(text); let cleaned = text; // cleanText(text); <-- cleaning text can result in an incorrect tokenization
let ids = spp.encodeIds(cleaned); let ids = spp.encodeIds(cleaned);
return ids.length; return {
ids,
count: ids.length
};
} }
async function loadClaudeTokenizer(modelPath) { async function loadClaudeTokenizer(modelPath) {
@ -298,6 +304,7 @@ const directories = {
instruct: 'public/instruct', instruct: 'public/instruct',
context: 'public/context', context: 'public/context',
backups: 'backups/', backups: 'backups/',
quickreplies: 'public/QuickReplies'
}; };
// CSRF Protection // // CSRF Protection //
@ -513,8 +520,16 @@ app.post("/generate", jsonParser, async function (request, response_generate = r
return response.body.pipe(response_generate); return response.body.pipe(response_generate);
} else { } else {
if (!response.ok) { if (!response.ok) {
console.log(`Kobold returned error: ${response.status} ${response.statusText} ${await response.text()}`); const errorText = await response.text();
return response.status(response.status).send({ error: true }); console.log(`Kobold returned error: ${response.status} ${response.statusText} ${errorText}`);
try {
const errorJson = JSON.parse(errorText);
const message = errorJson?.detail?.msg || errorText;
return response_generate.status(400).send({ error: { message } });
} catch {
return response_generate.status(400).send({ error: { message: errorText } });
}
} }
const data = await response.json(); const data = await response.json();
@ -1617,6 +1632,8 @@ app.post('/getsettings', jsonParser, (request, response) => {
const themes = readAndParseFromDirectory(directories.themes); const themes = readAndParseFromDirectory(directories.themes);
const movingUIPresets = readAndParseFromDirectory(directories.movingUI); const movingUIPresets = readAndParseFromDirectory(directories.movingUI);
const quickReplyPresets = readAndParseFromDirectory(directories.quickreplies);
const instruct = readAndParseFromDirectory(directories.instruct); const instruct = readAndParseFromDirectory(directories.instruct);
const context = readAndParseFromDirectory(directories.context); const context = readAndParseFromDirectory(directories.context);
@ -1633,6 +1650,7 @@ app.post('/getsettings', jsonParser, (request, response) => {
textgenerationwebui_preset_names, textgenerationwebui_preset_names,
themes, themes,
movingUIPresets, movingUIPresets,
quickReplyPresets,
instruct, instruct,
context, context,
enable_extensions: enableExtensions, enable_extensions: enableExtensions,
@ -1689,6 +1707,17 @@ app.post('/savemovingui', jsonParser, (request, response) => {
return response.sendStatus(200); return response.sendStatus(200);
}); });
app.post('/savequickreply', jsonParser, (request, response) => {
if (!request.body || !request.body.name) {
return response.sendStatus(400);
}
const filename = path.join(directories.quickreplies, sanitize(request.body.name) + '.json');
fs.writeFileSync(filename, JSON.stringify(request.body, null, 4), 'utf8');
return response.sendStatus(200);
});
function convertWorldInfoToCharacterBook(name, entries) { function convertWorldInfoToCharacterBook(name, entries) {
const result = { entries: [], name }; const result = { entries: [], name };
@ -1801,9 +1830,9 @@ app.post("/generate_novelai", jsonParser, async function (request, response_gene
controller.abort(); controller.abort();
}); });
console.log(request.body); const novelai = require('./src/novelai');
const bw = require('./src/bad-words'); const isNewModel = (request.body.model.includes('clio') || request.body.model.includes('kayra'));
const bad_words_ids = request.body.model.includes('clio') ? bw.clioBadWordsId : bw.badWordIds; const isKrake = request.body.model.includes('krake');
const data = { const data = {
"input": request.body.input, "input": request.body.input,
"model": request.body.model, "model": request.body.model,
@ -1818,12 +1847,18 @@ app.post("/generate_novelai", jsonParser, async function (request, response_gene
"repetition_penalty_slope": request.body.repetition_penalty_slope, "repetition_penalty_slope": request.body.repetition_penalty_slope,
"repetition_penalty_frequency": request.body.repetition_penalty_frequency, "repetition_penalty_frequency": request.body.repetition_penalty_frequency,
"repetition_penalty_presence": request.body.repetition_penalty_presence, "repetition_penalty_presence": request.body.repetition_penalty_presence,
"repetition_penalty_whitelist": isNewModel ? novelai.repPenaltyAllowList : null,
"top_a": request.body.top_a, "top_a": request.body.top_a,
"top_p": request.body.top_p, "top_p": request.body.top_p,
"top_k": request.body.top_k, "top_k": request.body.top_k,
"typical_p": request.body.typical_p, "typical_p": request.body.typical_p,
"cfg_scale": request.body.cfg_scale,
"cfg_uc": request.body.cfg_uc,
"phrase_rep_pen": request.body.phrase_rep_pen,
"stop_sequences": request.body.stop_sequences,
//"stop_sequences": {{187}}, //"stop_sequences": {{187}},
"bad_words_ids": bad_words_ids, "bad_words_ids": isNewModel ? novelai.badWordsList : (isKrake ? novelai.krakeBadWordsList : novelai.euterpeBadWordsList),
"logit_bias_exp": isNewModel ? novelai.logitBiasExp : null,
//generate_until_sentence = true; //generate_until_sentence = true;
"use_cache": request.body.use_cache, "use_cache": request.body.use_cache,
"use_string": true, "use_string": true,
@ -1832,6 +1867,8 @@ app.post("/generate_novelai", jsonParser, async function (request, response_gene
"order": request.body.order "order": request.body.order
} }
}; };
const util = require('util');
console.log(util.inspect(data, { depth: 4 }))
const args = { const args = {
body: JSON.stringify(data), body: JSON.stringify(data),
@ -3091,7 +3128,12 @@ async function sendClaudeRequest(request, response) {
controller.abort(); controller.abort();
}); });
const requestPrompt = convertClaudePrompt(request.body.messages, true, true); let requestPrompt = convertClaudePrompt(request.body.messages, true, true);
if (request.body.assistant_prefill) {
requestPrompt += request.body.assistant_prefill;
}
console.log('Claude request:', requestPrompt); console.log('Claude request:', requestPrompt);
const generateResponse = await fetch(api_url + '/complete', { const generateResponse = await fetch(api_url + '/complete', {
@ -3164,16 +3206,19 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op
let api_url; let api_url;
let api_key_openai; let api_key_openai;
let headers; let headers;
let bodyParams;
if (!request.body.use_openrouter) { if (!request.body.use_openrouter) {
api_url = new URL(request.body.reverse_proxy || api_openai).toString(); api_url = new URL(request.body.reverse_proxy || api_openai).toString();
api_key_openai = request.body.reverse_proxy ? request.body.proxy_password : readSecret(SECRET_KEYS.OPENAI); api_key_openai = request.body.reverse_proxy ? request.body.proxy_password : readSecret(SECRET_KEYS.OPENAI);
headers = {}; headers = {};
bodyParams = {};
} else { } else {
api_url = 'https://openrouter.ai/api/v1'; api_url = 'https://openrouter.ai/api/v1';
api_key_openai = readSecret(SECRET_KEYS.OPENROUTER); api_key_openai = readSecret(SECRET_KEYS.OPENROUTER);
// OpenRouter needs to pass the referer: https://openrouter.ai/docs // OpenRouter needs to pass the referer: https://openrouter.ai/docs
headers = { 'HTTP-Referer': request.headers.referer }; headers = { 'HTTP-Referer': request.headers.referer };
bodyParams = { 'transforms': ["middle-out"] };
} }
if (!api_key_openai && !request.body.reverse_proxy) { if (!api_key_openai && !request.body.reverse_proxy) {
@ -3210,7 +3255,8 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op
"top_p": request.body.top_p, "top_p": request.body.top_p,
"top_k": request.body.top_k, "top_k": request.body.top_k,
"stop": request.body.stop, "stop": request.body.stop,
"logit_bias": request.body.logit_bias "logit_bias": request.body.logit_bias,
...bodyParams,
}, },
signal: controller.signal, signal: controller.signal,
}; };
@ -3267,11 +3313,15 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op
switch (error?.response?.status) { switch (error?.response?.status) {
case 402: case 402:
message = 'Credit limit reached'; message = error?.response?.data?.error?.message || 'Credit limit reached';
console.log(message); console.log(message);
break; break;
case 403: case 403:
message = 'API key disabled or exhausted'; message = error?.response?.data?.error?.message || 'API key disabled or exhausted';
console.log(message);
break;
case 451:
message = error?.response?.data?.error?.message || 'Unavailable for legal reasons';
console.log(message); console.log(message);
break; break;
} }
@ -3401,8 +3451,8 @@ function createTokenizationHandler(getTokenizerFn) {
const text = request.body.text || ''; const text = request.body.text || '';
const tokenizer = getTokenizerFn(); const tokenizer = getTokenizerFn();
const count = await countSentencepieceTokens(tokenizer, text); const { ids, count } = await countSentencepieceTokens(tokenizer, text);
return response.send({ count }); return response.send({ ids, count });
}; };
} }

View File

@ -1,326 +0,0 @@
const badWordIds = [
[60],
[62],
[544],
[683],
[696],
[880],
[905],
[1008],
[1019],
[1084],
[1092],
[1181],
[1184],
[1254],
[1447],
[1570],
[1656],
[2194],
[2470],
[2479],
[2498],
[2947],
[3138],
[3291],
[3455],
[3725],
[3851],
[3891],
[3921],
[3951],
[4207],
[4299],
[4622],
[4681],
[5013],
[5032],
[5180],
[5218],
[5290],
[5413],
[5456],
[5709],
[5749],
[5774],
[6038],
[6257],
[6334],
[6660],
[6904],
[7082],
[7086],
[7254],
[7444],
[7748],
[8001],
[8088],
[8168],
[8562],
[8605],
[8795],
[8850],
[9014],
[9102],
[9259],
[9318],
[9336],
[9502],
[9686],
[9793],
[9855],
[9899],
[9955],
[10148],
[10174],
[10943],
[11326],
[11337],
[11661],
[12004],
[12084],
[12159],
[12520],
[12977],
[13380],
[13488],
[13663],
[13811],
[13976],
[14412],
[14598],
[14767],
[15640],
[15707],
[15775],
[15830],
[16079],
[16354],
[16369],
[16445],
[16595],
[16614],
[16731],
[16943],
[17278],
[17281],
[17548],
[17555],
[17981],
[18022],
[18095],
[18297],
[18413],
[18736],
[18772],
[18990],
[19181],
[20095],
[20197],
[20481],
[20629],
[20871],
[20879],
[20924],
[20977],
[21375],
[21382],
[21391],
[21687],
[21810],
[21828],
[21938],
[22367],
[22372],
[22734],
[23405],
[23505],
[23734],
[23741],
[23781],
[24237],
[24254],
[24345],
[24430],
[25416],
[25896],
[26119],
[26635],
[26842],
[26991],
[26997],
[27075],
[27114],
[27468],
[27501],
[27618],
[27655],
[27720],
[27829],
[28052],
[28118],
[28231],
[28532],
[28571],
[28591],
[28653],
[29013],
[29547],
[29650],
[29925],
[30522],
[30537],
[30996],
[31011],
[31053],
[31096],
[31148],
[31258],
[31350],
[31379],
[31422],
[31789],
[31830],
[32214],
[32666],
[32871],
[33094],
[33376],
[33440],
[33805],
[34368],
[34398],
[34417],
[34418],
[34419],
[34476],
[34494],
[34607],
[34758],
[34761],
[34904],
[34993],
[35117],
[35138],
[35237],
[35487],
[35830],
[35869],
[36033],
[36134],
[36320],
[36399],
[36487],
[36586],
[36676],
[36692],
[36786],
[37077],
[37594],
[37596],
[37786],
[37982],
[38475],
[38791],
[39083],
[39258],
[39487],
[39822],
[40116],
[40125],
[41000],
[41018],
[41256],
[41305],
[41361],
[41447],
[41449],
[41512],
[41604],
[42041],
[42274],
[42368],
[42696],
[42767],
[42804],
[42854],
[42944],
[42989],
[43134],
[43144],
[43189],
[43521],
[43782],
[44082],
[44162],
[44270],
[44308],
[44479],
[44524],
[44965],
[45114],
[45301],
[45382],
[45443],
[45472],
[45488],
[45507],
[45564],
[45662],
[46265],
[46267],
[46275],
[46295],
[46462],
[46468],
[46576],
[46694],
[47093],
[47384],
[47389],
[47446],
[47552],
[47686],
[47744],
[47916],
[48064],
[48167],
[48392],
[48471],
[48664],
[48701],
[49021],
[49193],
[49236],
[49550],
[49694],
[49806],
[49824],
[50001],
[50256],
[0],
[1],
]
const clioBadWordsId = [
[3],
[49356],
[1431],
[31715],
[34387],
[20765],
[30702],
[10691],
[49333],
[1266],
[19438],
[43145],
[26523],
[41471],
[2936],
]
module.exports = {
badWordIds,
clioBadWordsId,
};

View File

@ -61,6 +61,11 @@ const parse = async (cardUrl, format) => {
return PNGtext.decode(chunk.data); return PNGtext.decode(chunk.data);
}); });
if (textChunks.length === 0) {
console.error('PNG metadata does not contain any character data.');
throw new Error('No PNG metadata.');
}
return Buffer.from(textChunks[0].text, 'base64').toString('utf8'); return Buffer.from(textChunks[0].text, 'base64').toString('utf8');
default: default:
break; break;

75
src/novelai.js Normal file
View File

@ -0,0 +1,75 @@
// Ban bracket generation, plus defaults
const euterpeBadWordsList = [
[8162], [17202], [8162], [17202], [8162], [17202], [8162], [17202], [8162], [17202], [46256, 224], [2343, 223, 224],
[46256, 224], [2343, 223, 224], [46256, 224], [2343, 223, 224], [46256, 224], [2343, 223, 224], [46256, 224],
[2343, 223, 224], [58], [60], [90], [92], [685], [1391], [1782], [2361], [3693], [4083], [4357], [4895], [5512],
[5974], [7131], [8183], [8351], [8762], [8964], [8973], [9063], [11208], [11709], [11907], [11919], [12878], [12962],
[13018], [13412], [14631], [14692], [14980], [15090], [15437], [16151], [16410], [16589], [17241], [17414], [17635],
[17816], [17912], [18083], [18161], [18477], [19629], [19779], [19953], [20520], [20598], [20662], [20740], [21476],
[21737], [22133], [22241], [22345], [22935], [23330], [23785], [23834], [23884], [25295], [25597], [25719], [25787],
[25915], [26076], [26358], [26398], [26894], [26933], [27007], [27422], [28013], [29164], [29225], [29342], [29565],
[29795], [30072], [30109], [30138], [30866], [31161], [31478], [32092], [32239], [32509], [33116], [33250], [33761],
[34171], [34758], [34949], [35944], [36338], [36463], [36563], [36786], [36796], [36937], [37250], [37913], [37981],
[38165], [38362], [38381], [38430], [38892], [39850], [39893], [41832], [41888], [42535], [42669], [42785], [42924],
[43839], [44438], [44587], [44926], [45144], [45297], [46110], [46570], [46581], [46956], [47175], [47182], [47527],
[47715], [48600], [48683], [48688], [48874], [48999], [49074], [49082], [49146], [49946], [10221], [4841], [1427],
[2602, 834], [29343], [37405], [35780], [2602], [50256],
]
// Ban bracket generation, plus defaults
const krakeBadWordsList = [
[9264], [14244], [9264], [14244], [9264], [14244], [9264], [14244], [9264], [14244], [25086, 213], [27344, 213],
[25086, 213], [27344, 213], [25086, 213], [27344, 213], [25086, 213], [27344, 213], [25086, 213], [27344, 213], [60],
[62], [544], [683], [696], [880], [905], [1008], [1019], [1084], [1092], [1181], [1184], [1254], [1447], [1570], [1656],
[2194], [2470], [2479], [2498], [2947], [3138], [3291], [3455], [3725], [3851], [3891], [3921], [3951], [4207], [4299],
[4622], [4681], [5013], [5032], [5180], [5218], [5290], [5413], [5456], [5709], [5749], [5774], [6038], [6257], [6334],
[6660], [6904], [7082], [7086], [7254], [7444], [7748], [8001], [8088], [8168], [8562], [8605], [8795], [8850], [9014],
[9102], [9259], [9318], [9336], [9502], [9686], [9793], [9855], [9899], [9955], [10148], [10174], [10943], [11326],
[11337], [11661], [12004], [12084], [12159], [12520], [12977], [13380], [13488], [13663], [13811], [13976], [14412],
[14598], [14767], [15640], [15707], [15775], [15830], [16079], [16354], [16369], [16445], [16595], [16614], [16731],
[16943], [17278], [17281], [17548], [17555], [17981], [18022], [18095], [18297], [18413], [18736], [18772], [18990],
[19181], [20095], [20197], [20481], [20629], [20871], [20879], [20924], [20977], [21375], [21382], [21391], [21687],
[21810], [21828], [21938], [22367], [22372], [22734], [23405], [23505], [23734], [23741], [23781], [24237], [24254],
[24345], [24430], [25416], [25896], [26119], [26635], [26842], [26991], [26997], [27075], [27114], [27468], [27501],
[27618], [27655], [27720], [27829], [28052], [28118], [28231], [28532], [28571], [28591], [28653], [29013], [29547],
[29650], [29925], [30522], [30537], [30996], [31011], [31053], [31096], [31148], [31258], [31350], [31379], [31422],
[31789], [31830], [32214], [32666], [32871], [33094], [33376], [33440], [33805], [34368], [34398], [34417], [34418],
[34419], [34476], [34494], [34607], [34758], [34761], [34904], [34993], [35117], [35138], [35237], [35487], [35830],
[35869], [36033], [36134], [36320], [36399], [36487], [36586], [36676], [36692], [36786], [37077], [37594], [37596],
[37786], [37982], [38475], [38791], [39083], [39258], [39487], [39822], [40116], [40125], [41000], [41018], [41256],
[41305], [41361], [41447], [41449], [41512], [41604], [42041], [42274], [42368], [42696], [42767], [42804], [42854],
[42944], [42989], [43134], [43144], [43189], [43521], [43782], [44082], [44162], [44270], [44308], [44479], [44524],
[44965], [45114], [45301], [45382], [45443], [45472], [45488], [45507], [45564], [45662], [46265], [46267], [46275],
[46295], [46462], [46468], [46576], [46694], [47093], [47384], [47389], [47446], [47552], [47686], [47744], [47916],
[48064], [48167], [48392], [48471], [48664], [48701], [49021], [49193], [49236], [49550], [49694], [49806], [49824],
[50001], [50256], [0], [1]
]
// Ban bracket generation, plus defaults
const badWordsList = [
[23], [49209, 23], [23], [49209, 23], [23], [49209, 23], [23], [49209, 23], [23], [49209, 23], [21], [49209, 21],
[21], [49209, 21], [21], [49209, 21], [21], [49209, 21], [21], [49209, 21], [3], [49356], [1431], [31715], [34387],
[20765], [30702], [10691], [49333], [1266], [26523], [41471], [2936], [85, 85], [49332], [7286], [1115]
]
// Used for phrase repetition penalty
const repPenaltyAllowList = [
[49256, 49264, 49231, 49230, 49287, 85, 49255, 49399, 49262, 336, 333, 432, 363, 468, 492, 745, 401, 426, 623, 794,
1096, 2919, 2072, 7379, 1259, 2110, 620, 526, 487, 16562, 603, 805, 761, 2681, 942, 8917, 653, 3513, 506, 5301,
562, 5010, 614, 10942, 539, 2976, 462, 5189, 567, 2032, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 588,
803, 1040, 49209, 4, 5, 6, 7, 8, 9, 10, 11, 12]
]
// Ban the dinkus and asterism
const logitBiasExp = [
{ "sequence": [23], "bias": -0.08, "ensure_sequence_finish": false, "generate_once": false },
{ "sequence": [21], "bias": -0.08, "ensure_sequence_finish": false, "generate_once": false }
]
module.exports = {
euterpeBadWordsList,
krakeBadWordsList,
badWordsList,
repPenaltyAllowList,
logitBiasExp
};