Merge branch 'parser-followup-2' of https://github.com/LenAnderson/SillyTavern into parser-followup-2

83ba31ba063caf2dd8e1d2d5f1f952e220f38e5c

LenAnderson <Anderson.Len@outlook.com>

25 files changed, +176 -211Ignore whitespace
public/css/tags.css+6 -6
@@ -209,20 +209,20 @@
209209 opacity: 1;
210210}
211211
212212.tag_as_folder.right_menu_button {
213213 filter: brightness(75%) saturate(0.6);
214214}
215215
216216.tag_as_folder.right_menu_button:hover,
217217.tag_as_folder.right_menu_button.flash {
218218 filter: brightness(150%) saturate(0.6) !important;
219219}
220220
221221.tag_as_folder.right_menu_button.no_folder {
222222 filter: brightness(25%) saturate(0.25);
223223}
224224
225225.tag_as_folder.right_menu_button .tag_folder_indicator {
226226 position: absolute;
227227 top: calc(var(--mainFontSize) * -0.5);
228228 right: calc(var(--mainFontSize) * -0.5);
public/index.html+6 -2
@@ -1544,7 +1544,7 @@
15441544 <h4 class="wide100p textAlignCenter">
15451545 <label>
15461546 <span data-i18n="Grammar String">Grammar String</span>
15471547 <div class="margin5 fa-solid fa-circle-info opacity50p " data-i18n="[title]GNBFGBNF or ENBFEBNF, depends on the backend in use. If you're using this you should know which." title="GNBFGBNF or ENBFEBNF, depends on the backend in use. If you're using this you should know which."></div>
15481548 <a href="https://github.com/ggerganov/llama.cpp/blob/master/grammars/README.md" target="_blank">
15491549 <small>
15501550 <div class="fa-solid fa-up-right-from-square note-link-span"></div>
@@ -3069,7 +3069,11 @@
30693069 </div>
30703070 <label class="checkbox_label" title="Add Chat Start and Example Separator to a list of stopping strings." data-i18n="[title]Add Chat Start and Example Separator to a list of stopping strings.">
30713071 <input id="context_use_stop_strings" type="checkbox" />
30723072 <small data-i18n="UseSeparators as Stop Strings">UseSeparators as Stop Strings</small>
3073+ </label>
3074+ <label class="checkbox_label" title="Add Character and User names to a list of stopping strings." data-i18n="[title]Add Character and User names to a list of stopping strings.">
3075+ <input id="context_names_as_stop_strings" type="checkbox" />
3076+ <small data-i18n="Names as Stop Strings">Names as Stop Strings</small>
30733077 </label>
30743078 <label class="checkbox_label" title="Includes Post-History Instructions at the end of the prompt, if defined in the character card AND ''Prefer Char. Instructions'' is enabled.&#10;THIS IS NOT RECOMMENDED FOR TEXT COMPLETION MODELS, CAN LEAD TO BAD OUTPUT." data-i18n="[title]context_allow_post_history_instructions">
30753079 <input id="context_allow_jailbreak" type="checkbox" />
public/locales/ar-sa.json+1 -1
@@ -207,7 +207,7 @@
207207 "JSON Schema": "مخطط جيسون",
208208 "Type in the desired JSON schema": "اكتب مخطط JSON المطلوب",
209209 "Grammar String": "سلسلة القواعد",
210210 "GNBFGBNF or ENBFEBNF, depends on the backend in use. If you're using this you should know which.": "يعتمد GNBFGBNF أو ENBFEBNF على الواجهة الخلفية المستخدمة. إذا كنت تستخدم هذا يجب أن تعرف أي.",
211211 "Top P & Min P": "أعلى ع وأدنى ص",
212212 "Load default order": "تحميل الترتيب الافتراضي",
213213 "llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "llama.cpp فقط. تحديد ترتيب أخذ العينات. إذا لم يكن وضع Mirostat 0، فسيتم تجاهل ترتيب أخذ العينات.",
public/locales/de-de.json+1 -1
@@ -207,7 +207,7 @@
207207 "JSON Schema": "JSON-Schema",
208208 "Type in the desired JSON schema": "Geben Sie das gewünschte JSON-Schema ein",
209209 "Grammar String": "Grammatikzeichenfolge",
210210 "GNBFGBNF or ENBFEBNF, depends on the backend in use. If you're using this you should know which.": "GNBFGBNF oder ENBFEBNF, hängt vom verwendeten Backend ab. Wenn Sie dieses verwenden, sollten Sie wissen, welches.",
211211 "Top P & Min P": "Top P und Min P",
212212 "Load default order": "Standardreihenfolge laden",
213213 "llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "Nur llama.cpp. Bestimmt die Reihenfolge der Sampler. Wenn der Mirostat-Modus nicht 0 ist, wird die Sampler-Reihenfolge ignoriert.",
public/locales/es-es.json+1 -1
@@ -207,7 +207,7 @@
207207 "JSON Schema": "Esquema JSON",
208208 "Type in the desired JSON schema": "Escriba el esquema JSON deseado",
209209 "Grammar String": "Cadena de gramática",
210210 "GNBFGBNF or ENBFEBNF, depends on the backend in use. If you're using this you should know which.": "GNBFGBNF o ENBFEBNF, depende del backend en uso. Si estás usando esto, debes saber cuál.",
211211 "Top P & Min P": "P superior y P mínima",
212212 "Load default order": "Cargar orden predeterminado",
213213 "llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "llama.cpp únicamente. Determina el orden de los muestreadores. Si el modo Mirostat no es 0, se ignora el orden de las muestras.",
public/locales/fr-fr.json+1 -1
@@ -207,7 +207,7 @@
207207 "JSON Schema": "Schéma JSON",
208208 "Type in the desired JSON schema": "Tapez le schéma JSON souhaité",
209209 "Grammar String": "Chaîne de grammaire",
210210 "GNBFGBNF or ENBFEBNF, depends on the backend in use. If you're using this you should know which.": "GNBFGBNF ou ENBFEBNF dépend du backend utilisé. Si vous l'utilisez, vous devez savoir lequel.",
211211 "Top P & Min P": "P supérieur et P minimal",
212212 "Load default order": "Charger l'ordre par défaut",
213213 "llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "lama.cpp uniquement. Détermine l’ordre des échantillonneurs. Si le mode Mirostat n'est pas 0, l'ordre de l'échantillonneur est ignoré.",
public/locales/is-is.json+1 -1
@@ -207,7 +207,7 @@
207207 "JSON Schema": "JSON kerfi",
208208 "Type in the desired JSON schema": "Sláðu inn æskilegt JSON skema",
209209 "Grammar String": "Málfræðistrengur",
210210 "GNBFGBNF or ENBFEBNF, depends on the backend in use. If you're using this you should know which.": "GNBFGBNF eða ENBFEBNF, fer eftir bakendanum sem er í notkun. Ef þú ert að nota þetta ættir þú að vita hvaða.",
211211 "Top P & Min P": "Efstu P & Min P",
212212 "Load default order": "Hlaða sjálfgefna röð",
213213 "llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "llama.cpp eingöngu. Ákveður röð sýnataka. Ef Mirostat hamur er ekki 0, er röð sýnatöku hunsuð.",
public/locales/it-it.json+1 -1
@@ -207,7 +207,7 @@
207207 "JSON Schema": "Schema JSON",
208208 "Type in the desired JSON schema": "Digita lo schema JSON desiderato",
209209 "Grammar String": "Stringa grammaticale",
210210 "GNBFGBNF or ENBFEBNF, depends on the backend in use. If you're using this you should know which.": "GNBFGBNF o ENBFEBNF, dipende dal backend in uso. Se stai usando questo dovresti sapere quale.",
211211 "Top P & Min P": "P massimo e P minimo",
212212 "Load default order": "Carica ordine predefinito",
213213 "llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "Solo lama.cpp. Determina l'ordine dei campionatori. Se la modalità Mirostat non è 0, l'ordine del campionatore viene ignorato.",
public/locales/ja-jp.json+1 -1
@@ -207,7 +207,7 @@
207207 "JSON Schema": "JSONスキーマ",
208208 "Type in the desired JSON schema": "希望するJSONスキーマを入力します",
209209 "Grammar String": "文法文字列",
210210 "GNBFGBNF or ENBFEBNF, depends on the backend in use. If you're using this you should know which.": "GNBFGBNF または ENBFEBNF は、使用するバックエンドによって異なります。これを使用する場合は、どちらであるかを知っておく必要があります。",
211211 "Top P & Min P": "トップPと最小P",
212212 "Load default order": "デフォルトの順序を読み込む",
213213 "llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "llama.cpp のみ。サンプラーの順序を決定します。Mirostat モードが 0 でない場合、サンプラーの順序は無視されます。",
public/locales/ko-kr.json+1 -1
@@ -207,7 +207,7 @@
207207 "JSON Schema": "JSON 스키마",
208208 "Type in the desired JSON schema": "원하는 JSON 스키마를 입력하세요.",
209209 "Grammar String": "문법 문자열",
210210 "GNBFGBNF or ENBFEBNF, depends on the backend in use. If you're using this you should know which.": "GNBFGBNF 또는 ENBF는EBNF는 사용 중인 백엔드에 따라 다릅니다. 이것을 사용한다면 어느 것이 무엇인지 알아야합니다.",
211211 "Top P & Min P": "상위 P 및 최소 P",
212212 "Load default order": "기본 순서로 로드",
213213 "llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "llama.cpp만 가능합니다. 샘플러의 순서를 결정합니다. Mirostat 모드가 0이 아닌 경우 샘플러 순서는 무시됩니다.",
public/locales/nl-nl.json+1 -1
@@ -207,7 +207,7 @@
207207 "JSON Schema": "JSON-schema",
208208 "Type in the desired JSON schema": "Typ het gewenste JSON-schema",
209209 "Grammar String": "Grammaticareeks",
210210 "GNBFGBNF or ENBFEBNF, depends on the backend in use. If you're using this you should know which.": "GNBFGBNF of ENBFEBNF, hangt af van de gebruikte backend. Als u dit gebruikt, moet u weten welke.",
211211 "Top P & Min P": "Top P & Min P",
212212 "Load default order": "Standaardvolgorde laden",
213213 "llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "alleen lama.cpp. Bepaalt de volgorde van de samplers. Als de Mirostat-modus niet 0 is, wordt de samplervolgorde genegeerd.",
public/locales/pt-pt.json+1 -1
@@ -207,7 +207,7 @@
207207 "JSON Schema": "Esquema JSON",
208208 "Type in the desired JSON schema": "Digite o esquema JSON desejado",
209209 "Grammar String": "Cadeia de Gramática",
210210 "GNBFGBNF or ENBFEBNF, depends on the backend in use. If you're using this you should know which.": "GNBFGBNF ou ENBFEBNF, depende do backend em uso. Se você estiver usando isso, você deve saber qual.",
211211 "Top P & Min P": "P superior e P mínimo",
212212 "Load default order": "Carregar ordem padrão",
213213 "llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "apenas lhama.cpp. Determina a ordem dos amostradores. Se o modo Mirostat não for 0, a ordem do amostrador será ignorada.",
public/locales/ru-ru.json+1 -1
@@ -978,7 +978,7 @@
978978 "char_import_6": "Прямая ссылка на PNG-файл (чтобы узнать список разрешённых хостов, загляните в",
979979 "char_import_7": ")",
980980 "Grammar String": "Грамматика",
981981 "GNBFGBNF or ENBFEBNF, depends on the backend in use. If you're using this you should know which.": "GNBFGBNF или ENBFEBNF, зависит от бэкенда. Если вы это используете, то, скорее всего, сами знаете, какой именно.",
982982 "Account": "Аккаунт",
983983 "Hi,": "Привет,",
984984 "To enable multi-account features, restart the SillyTavern server with": "Чтобы активировать систему аккаунтов, перезапустите SillyTavern, выставив",
public/locales/uk-ua.json+1 -1
@@ -207,7 +207,7 @@
207207 "JSON Schema": "Схема JSON",
208208 "Type in the desired JSON schema": "Введіть потрібну схему JSON",
209209 "Grammar String": "Граматичний рядок",
210210 "GNBFGBNF or ENBFEBNF, depends on the backend in use. If you're using this you should know which.": "GNBFGBNF або ENBFEBNF, залежить від серверної частини, яка використовується. Якщо ви використовуєте це, ви повинні знати, який.",
211211 "Top P & Min P": "Верхній P & Min P",
212212 "Load default order": "Завантажити типовий порядок",
213213 "llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "лише llama.cpp. Визначає порядок пробовідбірників. Якщо режим Mirostat не 0, порядок вибірки ігнорується.",
public/locales/vi-vn.json+1 -1
@@ -207,7 +207,7 @@
207207 "JSON Schema": "Lược đồ JSON",
208208 "Type in the desired JSON schema": "Nhập lược đồ JSON mong muốn",
209209 "Grammar String": "Chuỗi ngữ pháp",
210210 "GNBFGBNF or ENBFEBNF, depends on the backend in use. If you're using this you should know which.": "GNBFGBNF hoặc ENBFEBNF, tùy thuộc vào backend đang sử dụng. Nếu bạn đang sử dụng cái này, bạn nên biết cái nào.",
211211 "Top P & Min P": "P & P tối thiểu hàng đầu",
212212 "Load default order": "Tải thứ tự mặc định",
213213 "llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "chỉ llama.cpp. Xác định thứ tự lấy mẫu. Nếu chế độ Mirostat khác 0, thứ tự lấy mẫu sẽ bị bỏ qua.",
public/locales/zh-cn.json+4 -2
@@ -208,7 +208,7 @@
208208 "JSON Schema": "JSON 结构",
209209 "Type in the desired JSON schema": "输入所需的 JSON 结构",
210210 "Grammar String": "语法字符串",
211211 "GNBFGBNF or ENBFEBNF, depends on the backend in use. If you're using this you should know which.": "GNBFGBNFENBFEBNF,取决于使用的后端。如果您使用这个,您应该知道该用哪一个。",
212212 "Top P & Min P": "Top P 和 Min P",
213213 "Load default order": "加载默认顺序",
214214 "llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "仅限 llama.cpp。确定采样器的顺序。如果 Mirostat 模式不为 0,则忽略采样器顺序。",
@@ -443,7 +443,9 @@
443443 "Example Separator": "示例分隔符",
444444 "Chat Start": "聊天开始",
445445 "Add Chat Start and Example Separator to a list of stopping strings.": "将聊天开始和示例分隔符添加到停止字符串列表中。",
446446 "UseSeparators as Stop Strings": "用作停止字符串分隔符作为终止字符串",
447+ "Add Character and User names to a list of stopping strings.": "将角色和用户名添加到停止字符串列表中。",
448+ "Names as Stop Strings": "名称作为终止字符串",
447449 "context_allow_post_history_instructions": "如果在角色卡中定义并且启用了“首选角色卡说明”,则在提示末尾包含后历史说明。\n不建议在文本补全模型中使用此功能,否则会导致输出错误。",
448450 "Allow Post-History Instructions": "允许后历史说明",
449451 "Context Order": "上下文顺序",
public/locales/zh-tw.json+1 -1
@@ -208,7 +208,7 @@
208208 "JSON Schema": "JSON 結構",
209209 "Type in the desired JSON schema": "輸入所需的 JSON 結構",
210210 "Grammar String": "語法字串",
211211 "GNBFGBNF or ENBFEBNF, depends on the backend in use. If you're using this you should know which.": "GNBFGBNFENBFEBNF,取決於所使用的後端。如果您使用此功能,應該知道是哪一種",
212212 "Top P & Min P": "Top P 和 Min P",
213213 "Load default order": "載入預設順序",
214214 "llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "僅適用於 llama.cpp。決定取樣器的順序。如果 Mirostat 模式不為 0,則忽略取樣器順序。",
public/script.js+60 -146
@@ -227,7 +227,7 @@ import { BulkEditOverlay, CharacterContextMenu } from './scripts/BulkEditOverlay
227227import { loadFeatherlessModels, loadMancerModels, loadOllamaModels, loadTogetherAIModels, loadInfermaticAIModels, loadOpenRouterModels, loadVllmModels, loadAphroditeModels, loadDreamGenModels } from './scripts/textgen-models.js';
228228import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags, isExternalMediaAllowed, getCurrentEntityId } from './scripts/chats.js';
229229import { initPresetManager } from './scripts/preset-manager.js';
230230import { MacrosParser, evaluateMacros, getLastMessageId } from './scripts/macros.js';
231231import { currentUser, setUserControls } from './scripts/user.js';
232232import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup, fixToastrForDialogs } from './scripts/popup.js';
233233import { renderTemplate, renderTemplateAsync } from './scripts/templates.js';
@@ -439,6 +439,7 @@ export const event_types = {
439439 GROUP_CHAT_CREATED: 'group_chat_created',
440440 GENERATE_BEFORE_COMBINE_PROMPTS: 'generate_before_combine_prompts',
441441 GENERATE_AFTER_COMBINE_PROMPTS: 'generate_after_combine_prompts',
442+ GENERATE_AFTER_DATA: 'generate_after_data',
442443 GROUP_MEMBER_DRAFTED: 'group_member_drafted',
443444 WORLD_INFO_ACTIVATED: 'world_info_activated',
444445 TEXT_COMPLETION_SETTINGS_READY: 'text_completion_settings_ready',
@@ -1720,16 +1721,24 @@ export async function replaceCurrentChat() {
17201721}
17211722
17221723export function showMoreMessages() {
17231724 letconst messageIdfirstDisplayedMesId = Number($('#chat').children('.mes').first().attr('mesid'));
1725+ let messageId = Number(firstDisplayedMesId);
17241726 let count = power_user.chat_truncation || Number.MAX_SAFE_INTEGER;
17251727
1728+ // If there are no messages displayed, or the message somehow has no mesid, we default to one higher than last message id,
1729+ // so the first "new" message being shown will be the last available message
1730+ if (isNaN(messageId)) {
1731+ messageId = getLastMessageId() + 1;
1732+ }
1733+
17261734 console.debug('Inserting messages before', messageId, 'count', count, 'chat length', chat.length);
17271735 const prevHeight = $('#chat').prop('scrollHeight');
17281736
17291737 while (messageId > 0 && count > 0) {
1738+ let newMessageId = messageId - 1;
1739+ addOneMessage(chat[newMessageId], { insertBefore: messageId >= chat.length ? null : messageId, scroll: false, forceId: newMessageId });
17301740 count--;
17311741 messageId--;
1732- addOneMessage(chat[messageId], { insertBefore: messageId + 1, scroll: false, forceId: messageId });
17331742 }
17341743
17351744 if (messageId == 0) {
@@ -2470,26 +2479,30 @@ export function substituteParams(content, _name1, _name2, _original, _group, _re
24702479 * @returns {string[]} Array of stopping strings
24712480 */
24722481export function getStoppingStrings(isImpersonate, isContinue) {
24732482 const charStringresult = `\n${name2}:`[];
2474- const userString = `\n${name1}:`;
2475- const result = isImpersonate ? [charString] : [userString];
24762483
2477- result.push(userString);
2484+ if (power_user.context.names_as_stop_strings) {
2485+ const charString = `\n${name2}:`;
2486+ const userString = `\n${name1}:`;
2487+ result.push(isImpersonate ? charString : userString);
24782488
2479- if (isContinue && Array.isArray(chat) && chat[chat.length - 1]?.is_user) {
2489+ result.push(userString);
2480- result.push(charString);
2481- }
24822490
2483- // Add other group members as the stopping strings
2491+ if (isContinue && Array.isArray(chat) && chat[chat.length - 1]?.is_user) {
2484- if (selected_group) {
2492+ result.push(charString);
2485- const group = groups.find(x => x.id === selected_group);
2493+ }
2494+
2495+ // Add other group members as the stopping strings
2496+ if (selected_group) {
2497+ const group = groups.find(x => x.id === selected_group);
24862498
24872499 if (group && Array.isArray(group.members)) {
24882500 const names = group.members
24892501 .map(x => characters.find(y => y.avatar == x))
24902502 .filter(x => x && x.name && x.name !== name2)
24912503 .map(x => `\n${x.name}:`);
24922504 result.push(...names);
2505+ }
24932506 }
24942507 }
24952508
@@ -4211,6 +4224,8 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
42114224 }
42124225 }
42134226
4227+ await eventSource.emit(event_types.GENERATE_AFTER_DATA, generate_data);
4228+
42144229 if (dryRun) {
42154230 generatedPromptCache = '';
42164231 return Promise.resolve();
@@ -5074,7 +5089,7 @@ function setInContextMessages(lastmsg, type) {
50745089 * @param {object} data Generation data
50755090 * @returns {Promise<object>} Response data from the API
50765091 */
50775092export async function sendGenerationRequest(type, data) {
50785093 if (main_api === 'openai') {
50795094 return await sendOpenAIRequest(type, data.prompt, abortController.signal);
50805095 }
@@ -5106,7 +5121,7 @@ async function sendGenerationRequest(type, data) {
51065121 * @param {object} data Generation data
51075122 * @returns {Promise<any>} Streaming generator
51085123 */
51095124export async function sendStreamingRequest(type, data) {
51105125 if (abortController?.signal?.aborted) {
51115126 throw new Error('Generation was aborted.');
51125127 }
@@ -7917,6 +7932,8 @@ window['SillyTavern'].getContext = function () {
79177932 eventTypes: event_types,
79187933 addOneMessage: addOneMessage,
79197934 generate: Generate,
7935+ sendStreamingRequest: sendStreamingRequest,
7936+ sendGenerationRequest: sendGenerationRequest,
79207937 stopGeneration: stopGeneration,
79217938 getTokenCount: getTokenCount,
79227939 extensionPrompts: extension_prompts,
@@ -8310,6 +8327,7 @@ const swipe_right = () => {
83108327};
83118328
83128329const CONNECT_API_MAP = {
8330+ // Default APIs not contined inside text gen / chat gen
83138331 'kobold': {
83148332 selected: 'kobold',
83158333 button: '#api_button',
@@ -8321,152 +8339,48 @@ const CONNECT_API_MAP = {
83218339 selected: 'novel',
83228340 button: '#api_button_novel',
83238341 },
8324- 'ooba': {
8342+ // KoboldCpp alias
8325- selected: 'textgenerationwebui',
8326- button: '#api_button_textgenerationwebui',
8327- type: textgen_types.OOBA,
8328- },
8329- 'tabby': {
8330- selected: 'textgenerationwebui',
8331- button: '#api_button_textgenerationwebui',
8332- type: textgen_types.TABBY,
8333- },
8334- 'llamacpp': {
8335- selected: 'textgenerationwebui',
8336- button: '#api_button_textgenerationwebui',
8337- type: textgen_types.LLAMACPP,
8338- },
8339- 'ollama': {
8340- selected: 'textgenerationwebui',
8341- button: '#api_button_textgenerationwebui',
8342- type: textgen_types.OLLAMA,
8343- },
8344- 'mancer': {
8345- selected: 'textgenerationwebui',
8346- button: '#api_button_textgenerationwebui',
8347- type: textgen_types.MANCER,
8348- },
8349- 'vllm': {
8350- selected: 'textgenerationwebui',
8351- button: '#api_button_textgenerationwebui',
8352- type: textgen_types.VLLM,
8353- },
8354- 'aphrodite': {
8355- selected: 'textgenerationwebui',
8356- button: '#api_button_textgenerationwebui',
8357- type: textgen_types.APHRODITE,
8358- },
8359- 'koboldcpp': {
8360- selected: 'textgenerationwebui',
8361- button: '#api_button_textgenerationwebui',
8362- type: textgen_types.KOBOLDCPP,
8363- },
83648343 'kcpp': {
83658344 selected: 'textgenerationwebui',
83668345 button: '#api_button_textgenerationwebui',
83678346 type: textgen_types.KOBOLDCPP,
83688347 },
8369- 'togetherai': {
8348+ // OpenAI alias
8370- selected: 'textgenerationwebui',
8371- button: '#api_button_textgenerationwebui',
8372- type: textgen_types.TOGETHERAI,
8373- },
8374- 'openai': {
8375- selected: 'openai',
8376- button: '#api_button_openai',
8377- source: chat_completion_sources.OPENAI,
8378- },
83798349 'oai': {
83808350 selected: 'openai',
83818351 button: '#api_button_openai',
83828352 source: chat_completion_sources.OPENAI,
83838353 },
8384- 'claude': {
8354+ // OpenRouter special naming, to differentiate between chat comp and text comp
8385- selected: 'openai',
8386- button: '#api_button_openai',
8387- source: chat_completion_sources.CLAUDE,
8388- },
8389- 'windowai': {
8390- selected: 'openai',
8391- button: '#api_button_openai',
8392- source: chat_completion_sources.WINDOWAI,
8393- },
83948355 'openrouter': {
83958356 selected: 'openai',
83968357 button: '#api_button_openai',
83978358 source: chat_completion_sources.OPENROUTER,
83988359 },
8399- 'scale': {
8400- selected: 'openai',
8401- button: '#api_button_openai',
8402- source: chat_completion_sources.SCALE,
8403- },
8404- 'ai21': {
8405- selected: 'openai',
8406- button: '#api_button_openai',
8407- source: chat_completion_sources.AI21,
8408- },
8409- 'makersuite': {
8410- selected: 'openai',
8411- button: '#api_button_openai',
8412- source: chat_completion_sources.MAKERSUITE,
8413- },
8414- 'mistralai': {
8415- selected: 'openai',
8416- button: '#api_button_openai',
8417- source: chat_completion_sources.MISTRALAI,
8418- },
8419- 'custom': {
8420- selected: 'openai',
8421- button: '#api_button_openai',
8422- source: chat_completion_sources.CUSTOM,
8423- },
8424- 'cohere': {
8425- selected: 'openai',
8426- button: '#api_button_openai',
8427- source: chat_completion_sources.COHERE,
8428- },
8429- 'perplexity': {
8430- selected: 'openai',
8431- button: '#api_button_openai',
8432- source: chat_completion_sources.PERPLEXITY,
8433- },
8434- 'groq': {
8435- selected: 'openai',
8436- button: '#api_button_openai',
8437- source: chat_completion_sources.GROQ,
8438- },
8439- '01ai': {
8440- selected: 'openai',
8441- button: '#api_button_openai',
8442- source: chat_completion_sources.ZEROONEAI,
8443- },
8444- 'infermaticai': {
8445- selected: 'textgenerationwebui',
8446- button: '#api_button_textgenerationwebui',
8447- type: textgen_types.INFERMATICAI,
8448- },
8449- 'dreamgen': {
8450- selected: 'textgenerationwebui',
8451- button: '#api_button_textgenerationwebui',
8452- type: textgen_types.DREAMGEN,
8453- },
84548360 'openrouter-text': {
84558361 selected: 'textgenerationwebui',
84568362 button: '#api_button_textgenerationwebui',
84578363 type: textgen_types.OPENROUTER,
84588364 },
8459- 'featherless': {
8365+};
8460- selected: 'textgenerationwebui',
8366+
8461- button: '#api_button_textgenerationwebui',
8367+// Fill connections map from textgen_types and chat_completion_sources
8462- type: textgen_types.FEATHERLESS,
8368+for (const textGenType of Object.values(textgen_types)) {
8463- },
8369+ if (CONNECT_API_MAP[textGenType]) continue;
8464- 'huggingface': {
8370+ CONNECT_API_MAP[textGenType] = {
84658371 selected: 'textgenerationwebui',
84668372 button: '#api_button_textgenerationwebui',
84678373 type: textgen_types.HUGGINGFACEtextGenType,
84688374 },;
84698375};
8376+for (const chatCompletionSource of Object.values(chat_completion_sources)) {
8377+ if (CONNECT_API_MAP[chatCompletionSource]) continue;
8378+ CONNECT_API_MAP[chatCompletionSource] = {
8379+ selected: 'openai',
8380+ button: '#api_button_openai',
8381+ source: chatCompletionSource,
8382+ };
8383+}
84708384
84718385async function selectContextCallback(_, name) {
84728386 if (!name) {
@@ -8599,7 +8513,7 @@ export async function processDroppedFiles(files, data = new Map()) {
85998513
86008514 for (const file of files) {
86018515 const extension = file.name.split('.').pop().toLowerCase();
86028516 if (allowedMimeTypes.includessome(x => file.type.startsWith(x)) || allowedExtensions.includes(extension)) {
86038517 const preservedName = data instanceof Map && data.get(file);
86048518 await importCharacter(file, preservedName);
86058519 } else {
public/scripts/popup.js+21 -6
@@ -101,6 +101,21 @@ const showPopupHelper = {
101101 if (typeof result === 'string' || typeof result === 'boolean') throw new Error(`Invalid popup result. CONFIRM popups only support numbers, or null. Result: ${result}`);
102102 return result;
103103 },
104+ /**
105+ * Asynchronously displays a text popup with the given header and text, returning the clicked result button value.
106+ *
107+ * @param {string?} header - The header text for the popup.
108+ * @param {string?} text - The main text for the popup.
109+ * @param {PopupOptions} [popupOptions={}] - Options for the popup.
110+ * @return {Promise<POPUP_RESULT>} A Promise that resolves with the result of the user's interaction.
111+ */
112+ text: async (header, text, popupOptions = {}) => {
113+ const content = PopupUtils.BuildTextWithHeader(header, text);
114+ const popup = new Popup(content, POPUP_TYPE.TEXT, null, popupOptions);
115+ const result = await popup.show();
116+ if (typeof result === 'string' || typeof result === 'boolean') throw new Error(`Invalid popup result. TEXT popups only support numbers, or null. Result: ${result}`);
117+ return result;
118+ },
104119};
105120
106121export class Popup {
@@ -511,14 +526,14 @@ export class Popup {
511526
512527 return this.#promise;
513528 }
514529 async completeAffirmative() {
515530 return await this.complete(POPUP_RESULT.AFFIRMATIVE);
516531 }
517532 async completeNegative() {
518533 return await this.complete(POPUP_RESULT.NEGATIVE);
519534 }
520535 async completeCancelled() {
521536 return await this.complete(POPUP_RESULT.CANCELLED);
522537 }
523538
524539 /**
public/scripts/power-user.js+2 -0
@@ -242,6 +242,7 @@ let power_user = {
242242 example_separator: defaultExampleSeparator,
243243 use_stop_strings: true,
244244 allow_jailbreak: false,
245+ names_as_stop_strings: true,
245246 },
246247
247248 personas: {},
@@ -348,6 +349,7 @@ const contextControls = [
348349 { id: 'context_chat_start', property: 'chat_start', isCheckbox: false, isGlobalSetting: false },
349350 { id: 'context_use_stop_strings', property: 'use_stop_strings', isCheckbox: true, isGlobalSetting: false, defaultValue: false },
350351 { id: 'context_allow_jailbreak', property: 'allow_jailbreak', isCheckbox: true, isGlobalSetting: false, defaultValue: false },
352+ { id: 'context_names_as_stop_strings', property: 'names_as_stop_strings', isCheckbox: true, isGlobalSetting: false, defaultValue: true },
351353
352354 // Existing power user settings
353355 { id: 'always-force-name2-checkbox', property: 'always_force_name2', isCheckbox: true, isGlobalSetting: true, defaultValue: true },
public/scripts/samplerSelect.js+2 -2
@@ -158,12 +158,12 @@ async function showSamplerSelectPopup() {
158158 <div class="flex-container justifyCenter">
159159 <h3>Sampler Select</h3>
160160 <div class="flex-container alignItemsBaseline">
161161 <div id="resetSelectedSamplers" class="menu_button menu_button_icon tag_view_create" title="Reset custom sampler selection">
162162 <i class="fa-solid fa-recycle"></i>
163163 </div>
164164 </div>
165165 <!--<div class="flex-container alignItemsBaseline">
166166 <div class="menu_button menu_button_icon tag_view_create" title="Create a new sampler">
167167 <i class="fa-solid fa-plus"></i>
168168 <span data-i18n="Create">Create</span>
169169 </div>
public/scripts/tags.js+50 -32
@@ -21,7 +21,7 @@ import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
2121import { SlashCommand } from './slash-commands/SlashCommand.js';
2222import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
2323import { isMobile } from './RossAscends-mods.js';
2424import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
2525import { debounce_timeout } from './constants.js';
2626import { INTERACTABLE_CONTROL_CLASS } from './keyboard.js';
2727import { commonEnumProviders } from './slash-commands/SlashCommandCommonEnumsProvider.js';
@@ -1367,27 +1367,8 @@ function makeTagListDraggable(tagContainer) {
13671367 const id = $(tagElement).attr('id');
13681368 const tag = tags.find(x => x.id === id);
13691369
1370- // Fix the defined colors, because if there is no color set, they seem to get automatically set to black
1371- // based on the color picker after drag&drop, even if there was no color chosen. We just set them back.
1372- const color = $(tagElement).find('.tagColorPickerHolder .tag-color').attr('color');
1373- const color2 = $(tagElement).find('.tagColorPicker2Holder .tag-color2').attr('color');
1374- if (color === '' || color === undefined) {
1375- tag.color = '';
1376- fixColor('background-color', tag.color);
1377- }
1378- if (color2 === '' || color2 === undefined) {
1379- tag.color2 = '';
1380- fixColor('color', tag.color2);
1381- }
1382-
13831370 // Update the sort order
13841371 tag.sort_order = i;
1385-
1386- function fixColor(property, color) {
1387- $(tagElement).find('.tag_view_name').css(property, color);
1388- $(`.tag[id="${id}"]`).css(property, color);
1389- $(`.bogus_folder_select[tagid="${id}"] .avatar`).css(property, color);
1390- }
13911372 });
13921373
13931374 // If tags were dragged manually, we have to disable auto sorting
@@ -1455,18 +1436,28 @@ async function onTagRestoreFileSelect(e) {
14551436 const data = await parseJsonFile(file);
14561437
14571438 if (!data) {
14581439 toastr.warning('Empty file data', 'Tag restoreRestore');
14591440 console.log('Tag restore: File data empty.');
14601441 return;
14611442 }
14621443
14631444 if (!data.tags || !data.tag_map || !Array.isArray(data.tags) || typeof data.tag_map !== 'object') {
14641445 toastr.warning('Invalid file format', 'Tag restoreRestore');
14651446 console.log('Tag restore: Invalid file format.');
14661447 return;
14671448 }
14681449
1450+ // Prompt user if they want to overwrite existing tags
1451+ let overwrite = false;
1452+ if (tags.length > 0) {
1453+ const result = await Popup.show.confirm('Tag Restore', 'You have existing tags. If the backup contains any of those tags, do you want the backup to overwrite their settings (Name, color, folder state, etc)?',
1454+ { okButton: 'Overwrite', cancelButton: 'Keep Existing' });
1455+ overwrite = result === POPUP_RESULT.AFFIRMATIVE;
1456+ }
1457+
14691458 const warnings = [];
1459+ /** @type {Map<string, string>} Map import tag ids with existing ids on overwrite */
1460+ const idToActualTagIdMap = new Map();
14701461
14711462 // Import tags
14721463 for (const tag of data.tags) {
@@ -1475,11 +1466,29 @@ async function onTagRestoreFileSelect(e) {
14751466 continue;
14761467 }
14771468
1478- if (tags.find(x => x.id === tag.id)) {
1469+ // Check against both existing id (direct match) and tag with the same name, which is not allowed.
1479- warnings.push(`Tag with id ${tag.id} already exists.`);
1470+ let existingTag = tags.find(x => x.id === tag.id);
1471+ if (existingTag && !overwrite) {
1472+ warnings.push(`Tag '${tag.name}' with id ${tag.id} already exists.`);
1473+ continue;
1474+ }
1475+ existingTag = getTag(tag.name);
1476+ if (existingTag && !overwrite) {
1477+ warnings.push(`Tag with name '${tag.name}' already exists.`);
1478+ // Remember the tag id, so we can still import the tag map entries for this
1479+ idToActualTagIdMap.set(tag.id, existingTag.id);
14801480 continue;
14811481 }
14821482
1483+ if (existingTag) {
1484+ // On overwrite, we remove and re-add the tag
1485+ removeFromArray(tags, existingTag);
1486+ // And remember the ID if it was different, so we can update the tag map accordingly
1487+ if (existingTag.id !== tag.id) {
1488+ idToActualTagIdMap.set(existingTag.id, tag.id);
1489+ }
1490+ }
1491+
14831492 tags.push(tag);
14841493 }
14851494
@@ -1497,30 +1506,39 @@ async function onTagRestoreFileSelect(e) {
14971506 const groupExists = groups.some(x => String(x.id) === String(key));
14981507
14991508 if (!characterExists && !groupExists) {
15001509 warnings.push(`Tag map key ${key} does not exist as character or group.`);
15011510 continue;
15021511 }
15031512
15041513 // Get existing tag ids for this key or empty array.
15051514 const existingTagIds = tag_map[key] || [];
1506- // Merge existing and new tag ids. Remove duplicates.
1515+
1507- tag_map[key] = existingTagIds.concat(tagIds).filter(onlyUnique);
1516+ // Merge existing and new tag ids. Replace the ones mapped to a new id. Remove duplicates.
1517+ const combinedTags = existingTagIds.concat(tagIds)
1518+ .map(tagId => (idToActualTagIdMap.has(tagId)) ? idToActualTagIdMap.get(tagId) : tagId)
1519+ .filter(onlyUnique);
1520+
15081521 // Verify that all tags exist. Remove tags that don't exist.
15091522 tag_map[key] = tag_map[key]combinedTags.filter(xtagId => tags.some(y => String(y.id) === String(xtagId)));
15101523 }
15111524
15121525 if (warnings.length) {
15131526 toastr.successwarning('Tags restored with warnings. Check console or click on this message for details.');, 'Tag Restore', {
1527+ timeOut: toastr.options.timeOut * 2, // Display double the time
1528+ onclick: () => Popup.show.text('Tag Restore Warnings', `<samp class="justifyLeft">${DOMPurify.sanitize(warnings.join('\n'))}<samp>`, { allowVerticalScrolling: true }),
1529+ });
15141530 console.warn(`TAG RESTORE REPORT\n====================\n${warnings.join('\n')}`);
15151531 } else {
15161532 toastr.success('Tags restored successfully.', 'Tag Restore');
15171533 }
15181534
15191535 $('#tag_view_restore_input').val('');
15201536 printCharactersDebounced();
15211537 saveSettingsDebounced();
15221538
1523- await onViewTagsListClick();
1539+ // Reprint the tag management popup, without having it to be opened again
1540+ const tagContainer = $('#tag_view_list .tag_view_list_tags');
1541+ printViewTagList(tagContainer);
15241542}
15251543
15261544function onBackupRestoreClick() {
@@ -1577,7 +1595,7 @@ function appendViewTagToList(list, tag, everything) {
15771595
15781596 const primaryColorPicker = $('<toolcool-color-picker></toolcool-color-picker>')
15791597 .addClass('tag-color')
15801598 .attr({ id: colorPickerId, color: tag.color || 'rgba(0, 0, 0, 0.35)', 'data-default-color': 'rgba(0, 0, 0, 0.35)' });
15811599
15821600 const secondaryColorPicker = $('<toolcool-color-picker></toolcool-color-picker>')
15831601 .addClass('tag-color2')
public/scripts/world-info.js+1 -1
@@ -3584,7 +3584,7 @@ export async function getSortedEntries() {
35843584
35853585 // Parse decorators
35863586 entries = entries.map((entry) => {
35873587 const [decorators, content] = parseDecorators(entry.content || '');
35883588 return { ...entry, decorators, content };
35893589 });
35903590
public/style.css+7 -0
@@ -470,6 +470,13 @@ kbd {
470470 line-height: 1;
471471}
472472
473+samp {
474+ display: block;
475+ font-family: var(--monoFontFamily);
476+ white-space: pre-wrap;
477+ text-align: start;
478+ justify-content: left;
479+}
473480
474481hr {
475482 background-image: linear-gradient(90deg, var(--transparent), var(--SmartThemeBodyColor), var(--transparent));
src/constants.js+3 -0
@@ -312,6 +312,9 @@ const TOGETHERAI_KEYS = [
312312 'top_p',
313313 'top_k',
314314 'repetition_penalty',
315+ 'min_p',
316+ 'presence_penalty',
317+ 'frequency_penalty',
315318 'stream',
316319 'stop',
317320];