Merge branch 'staging' into parser-post-stuff

This commit is contained in:
LenAnderson
2024-05-18 14:51:06 -04:00
37 changed files with 1704 additions and 160 deletions

1
.gitignore vendored
View File

@ -47,3 +47,4 @@ access.log
public/css/user.css public/css/user.css
/plugins/ /plugins/
/data /data
/default/scaffold

View File

@ -231,6 +231,7 @@
"api_url_scale": "", "api_url_scale": "",
"show_external_models": false, "show_external_models": false,
"assistant_prefill": "", "assistant_prefill": "",
"assistant_impersonation": "",
"human_sysprompt_message": "Let's get started. Please generate your response based on the information and instructions provided above.", "human_sysprompt_message": "Let's get started. Please generate your response based on the information and instructions provided above.",
"use_ai21_tokenizer": false, "use_ai21_tokenizer": false,
"use_google_tokenizer": false, "use_google_tokenizer": false,

View File

@ -624,6 +624,7 @@
"show_external_models": false, "show_external_models": false,
"proxy_password": "", "proxy_password": "",
"assistant_prefill": "", "assistant_prefill": "",
"assistant_impersonation": "",
"use_ai21_tokenizer": false "use_ai21_tokenizer": false
} }
} }

View File

@ -0,0 +1,26 @@
# Content Scaffolding
Content files in this folder will be copied for all users (old and new) on the server startup.
1. You **must** create an `index.json` file in `/default/scaffold` for it to work. The syntax is the same as for default content.
2. All file paths should be relative to `/default/scaffold`, the use of subdirectories is allowed.
3. Scaffolded files are copied first, so they override any of the default files (presets/settings/etc.) that have the same file name.
## Example
```json
[
{
"filename": "themes/Midnight.json",
"type": "theme"
},
{
"filename": "backgrounds/city.png",
"type": "background"
},
{
"filename": "characters/Charlie.png",
"type": "character"
}
]
```

View File

@ -103,7 +103,8 @@
} }
#bulkTagsList, #bulkTagsList,
#tagList .tag { #tagList .tag,
#groupTagList .tag {
opacity: 0.6; opacity: 0.6;
} }

View File

@ -116,7 +116,7 @@
</h4> </h4>
<div class="flex-container flexNoGap"> <div class="flex-container flexNoGap">
<select id="settings_preset_novel" class="flex1 text_pole" data-preset-manager-for="novel"> <select id="settings_preset_novel" class="flex1 text_pole" data-preset-manager-for="novel">
<option value="gui" data-i18n="default">Default</option> <option value="gui" data-i18n="Default">Default</option>
</select> </select>
<div class="flex-container marginLeft5 "> <div class="flex-container marginLeft5 ">
<input type="file" hidden data-preset-manager-file="novel" accept=".json, .settings"> <input type="file" hidden data-preset-manager-file="novel" accept=".json, .settings">
@ -134,7 +134,7 @@
<h4 class="margin0"><span data-i18n="openaipresets">Chat Completion Presets</span></h4> <h4 class="margin0"><span data-i18n="openaipresets">Chat Completion Presets</span></h4>
<div class="flex-container flexNoGap"> <div class="flex-container flexNoGap">
<select id="settings_preset_openai" class="flex1 text_pole" data-preset-manager-for="openai"> <select id="settings_preset_openai" class="flex1 text_pole" data-preset-manager-for="openai">
<option value="gui" data-i18n="default">Default</option> <option value="gui" data-i18n="Default">Default</option>
</select> </select>
<div class="flex-container marginLeft5 "> <div class="flex-container marginLeft5 ">
<input id="openai_preset_import_file" type="file" accept=".json,.settings" hidden /> <input id="openai_preset_import_file" type="file" accept=".json,.settings" hidden />
@ -246,7 +246,7 @@
</div> </div>
</div> </div>
<div class="range-block"> <div class="range-block">
<div class="range-block-title" data-i18n="temperature"> <div class="range-block-title" data-i18n="Temperature">
Temperature Temperature
</div> </div>
<div class="range-block-range-and-counter"> <div class="range-block-range-and-counter">
@ -1268,13 +1268,12 @@
</div> </div>
</div> </div>
<div data-newbie-hidden data-tg-type="ooba, mancer, koboldcpp, tabby, llamacpp, aphrodite" name="dynaTempBlock" class="wide100p"> <div data-newbie-hidden data-tg-type="ooba, mancer, koboldcpp, tabby, llamacpp, aphrodite" name="dynaTempBlock" class="wide100p">
<h4 class="wide100p textAlignCenter" data-i18n="DynaTemp"> <h4 class="wide100p textAlignCenter">
<div class="flex-container alignitemscenter" style="justify-content: center;"> <div class="flex-container alignitemscenter justifyCenter">
<div class="checkbox_label" for="dynatemp_textgenerationwebui"> <div class="checkbox_label" for="dynatemp_textgenerationwebui">
<input type="checkbox" id="dynatemp_textgenerationwebui" /> <input type="checkbox" id="dynatemp_textgenerationwebui" />
<small data-i18n="dynatemp"></small>
</div> </div>
<span style="text-align: center;" data-i18n="Dynamic Temperature">Dynamic Temperature</span> <span class="textAlignCenter" data-i18n="Dynamic Temperature">Dynamic Temperature</span>
<div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]Scale Temperature dynamically per token, based on the variation of probabilities" title="Scale Temperature dynamically per token, based on the variation of probabilities."></div> <div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]Scale Temperature dynamically per token, based on the variation of probabilities" title="Scale Temperature dynamically per token, based on the variation of probabilities."></div>
</div> </div>
</h4> </h4>
@ -1459,7 +1458,7 @@
</div> </div>
</div> </div>
</div> </div>
<div data-newbie-hidden id="json_schema_block" data-tg-type="tabby" class="wide100p"> <div data-newbie-hidden id="json_schema_block" data-tg-type="tabby, llamacpp" class="wide100p">
<hr class="wide100p"> <hr class="wide100p">
<h4 class="wide100p textAlignCenter"><span data-i18n="JSON Schema">JSON Schema</span> <h4 class="wide100p textAlignCenter"><span data-i18n="JSON Schema">JSON Schema</span>
<a href="https://json-schema.org/learn/getting-started-step-by-step" target="_blank"> <a href="https://json-schema.org/learn/getting-started-step-by-step" target="_blank">
@ -1734,6 +1733,8 @@
<div class="wide100p"> <div class="wide100p">
<span id="claude_assistant_prefill_text" data-i18n="Assistant Prefill">Assistant Prefill</span> <span id="claude_assistant_prefill_text" data-i18n="Assistant Prefill">Assistant Prefill</span>
<textarea id="claude_assistant_prefill" class="text_pole textarea_compact" name="assistant_prefill autoSetHeight" rows="3" maxlength="10000" data-i18n="[placeholder]Start Claude's answer with..." placeholder="Start Claude's answer with..."></textarea> <textarea id="claude_assistant_prefill" class="text_pole textarea_compact" name="assistant_prefill autoSetHeight" rows="3" maxlength="10000" data-i18n="[placeholder]Start Claude's answer with..." placeholder="Start Claude's answer with..."></textarea>
<span id="claude_assistant_impersonation_text" data-i18n="Assistant Impersonation Prefill">Assistant Impersonation Prefill</span>
<textarea id="claude_assistant_impersonation" class="text_pole textarea_compact" name="assistant_impersonation autoSetHeight" rows="3" maxlength="10000" data-i18n="[placeholder]Start Claude's answer with..." placeholder="Start Claude's answer with..."></textarea>
</div> </div>
<label for="claude_use_sysprompt" class="checkbox_label widthFreeExpand"> <label for="claude_use_sysprompt" class="checkbox_label widthFreeExpand">
<input id="claude_use_sysprompt" type="checkbox" /> <input id="claude_use_sysprompt" type="checkbox" />
@ -3443,8 +3444,8 @@
</label> </label>
<label title="If the entry key consists of only one word, it would not be matched as part of other words" data-i18n="[title]If the entry key consists of only one word, it would not be matched as part of other words" class="checkbox_label flex1"> <label title="If the entry key consists of only one word, it would not be matched as part of other words" data-i18n="[title]If the entry key consists of only one word, it would not be matched as part of other words" class="checkbox_label flex1">
<input id="world_info_match_whole_words" type="checkbox" /> <input id="world_info_match_whole_words" type="checkbox" />
<small data-i18n="Match whole words" class="whitespacenowrap flex1"> <small data-i18n="Match Whole Words" class="whitespacenowrap flex1">
Match whole words Match Whole Words
</small> </small>
</label> </label>
<label title="Only the entries with the most number of key matches will be selected for Inclusion Group filtering" data-i18n="[title]Only the entries with the most number of key matches will be selected for Inclusion Group filtering" class="checkbox_label flex1"> <label title="Only the entries with the most number of key matches will be selected for Inclusion Group filtering" data-i18n="[title]Only the entries with the most number of key matches will be selected for Inclusion Group filtering" class="checkbox_label flex1">
@ -3597,7 +3598,7 @@
<div class="flex-container"> <div class="flex-container">
<span data-i18n="Chat Style:">Chat Style:</span><br> <span data-i18n="Chat Style:">Chat Style:</span><br>
<select id="chat_display" class="widthNatural flex1 margin0"> <select id="chat_display" class="widthNatural flex1 margin0">
<option value="0" data-i18n="Default">Flat</span> <option value="0" data-i18n="Flat">Flat</span>
<option value="1" data-i18n="Bubbles">Bubbles</option> <option value="1" data-i18n="Bubbles">Bubbles</option>
<option value="2" data-i18n="Document">Document</option> <option value="2" data-i18n="Document">Document</option>
</select> </select>
@ -4082,7 +4083,7 @@
<small class="fa-solid fa-circle-question note-link-small"></small> <small class="fa-solid fa-circle-question note-link-small"></small>
</a> </a>
</label> </label>
<label class="checkbox_label" for="forbid_external_media" title="Disalow embedded media from other domains in chat messages." data-i18n="[title]Disalow embedded media from other domains in chat messages"> <label class="checkbox_label" for="forbid_external_media" title="Disallow embedded media from other domains in chat messages." data-i18n="[title]Disallow embedded media from other domains in chat messages">
<input id="forbid_external_media" type="checkbox" /> <input id="forbid_external_media" type="checkbox" />
<small data-i18n="Forbid External Media">Forbid External Media</small> <small data-i18n="Forbid External Media">Forbid External Media</small>
</label> </label>

View File

@ -3,7 +3,6 @@
"kobldpresets": "الإعدادات المسبقة لـ Kobold", "kobldpresets": "الإعدادات المسبقة لـ Kobold",
"guikoboldaisettings": "إعدادات واجهة KoboldAI", "guikoboldaisettings": "إعدادات واجهة KoboldAI",
"novelaipreserts": "الإعدادات المسبقة لـ NovelAI", "novelaipreserts": "الإعدادات المسبقة لـ NovelAI",
"default": "افتراضي",
"openaipresets": "الإعدادات المسبقة لـ OpenAI", "openaipresets": "الإعدادات المسبقة لـ OpenAI",
"text gen webio(ooba) presets": "الإعدادات المسبقة لـ WebUI(ooba)", "text gen webio(ooba) presets": "الإعدادات المسبقة لـ WebUI(ooba)",
"response legth(tokens)": "طول الاستجابة (بعدد الاحرف او الرموز)", "response legth(tokens)": "طول الاستجابة (بعدد الاحرف او الرموز)",
@ -495,7 +494,6 @@
"Global Lore First": "سرد العالم أولاً", "Global Lore First": "سرد العالم أولاً",
"Recursive Scan": "فحص متكرر", "Recursive Scan": "فحص متكرر",
"Case Sensitive": "حساس لحالة الأحرف", "Case Sensitive": "حساس لحالة الأحرف",
"Match whole words": "تطابق الكلمات الكاملة",
"Alert On Overflow": "تنبيه عند التجاوز", "Alert On Overflow": "تنبيه عند التجاوز",
"World/Lore Editor": "محرر العالم/السرد", "World/Lore Editor": "محرر العالم/السرد",
"--- None ---": "--- لا شيء ---", "--- None ---": "--- لا شيء ---",

View File

@ -3,7 +3,6 @@
"kobldpresets": "Kobold-Einstellungen von vorher", "kobldpresets": "Kobold-Einstellungen von vorher",
"guikoboldaisettings": "KoboldAI-Einstellungen für das Menü", "guikoboldaisettings": "KoboldAI-Einstellungen für das Menü",
"novelaipreserts": "NovelAI-Einstellungen von früher", "novelaipreserts": "NovelAI-Einstellungen von früher",
"default": "Normal",
"openaipresets": "OpenAI-Einstellungen von vorher", "openaipresets": "OpenAI-Einstellungen von vorher",
"text gen webio(ooba) presets": "WebUI(ooba)-Einstellungen für Texterstellung", "text gen webio(ooba) presets": "WebUI(ooba)-Einstellungen für Texterstellung",
"response legth(tokens)": "Länge der Antwort (Tokens)", "response legth(tokens)": "Länge der Antwort (Tokens)",
@ -494,7 +493,6 @@
"Global Lore First": "Globale Lore zuerst", "Global Lore First": "Globale Lore zuerst",
"Recursive Scan": "Rekursive Suche", "Recursive Scan": "Rekursive Suche",
"Case Sensitive": "Groß-/Kleinschreibung beachten", "Case Sensitive": "Groß-/Kleinschreibung beachten",
"Match whole words": "Ganze Wörter abgleichen",
"Alert On Overflow": "Warnung bei Überlauf", "Alert On Overflow": "Warnung bei Überlauf",
"World/Lore Editor": "Welt-/Lore-Editor", "World/Lore Editor": "Welt-/Lore-Editor",
"--- None ---": "--- Keine ---", "--- None ---": "--- Keine ---",

View File

@ -3,7 +3,6 @@
"kobldpresets": "Preajustes de Kobold", "kobldpresets": "Preajustes de Kobold",
"guikoboldaisettings": "Ajustes de interfaz de KoboldAI", "guikoboldaisettings": "Ajustes de interfaz de KoboldAI",
"novelaipreserts": "Preajustes de NovelAI", "novelaipreserts": "Preajustes de NovelAI",
"default": "Predeterminado",
"openaipresets": "Preajustes de OpenAI", "openaipresets": "Preajustes de OpenAI",
"text gen webio(ooba) presets": "Preajustes de Text Gen WebUI(ooba)", "text gen webio(ooba) presets": "Preajustes de Text Gen WebUI(ooba)",
"response legth(tokens)": "Longitud de respuesta (tokens)", "response legth(tokens)": "Longitud de respuesta (tokens)",
@ -494,7 +493,6 @@
"Global Lore First": "Historia Global Primero", "Global Lore First": "Historia Global Primero",
"Recursive Scan": "Escaneo Recursiva", "Recursive Scan": "Escaneo Recursiva",
"Case Sensitive": "Sensible a mayúsculas y minúsculas", "Case Sensitive": "Sensible a mayúsculas y minúsculas",
"Match whole words": "Coincidir palabras completas",
"Alert On Overflow": "Alerta en Desbordamiento", "Alert On Overflow": "Alerta en Desbordamiento",
"World/Lore Editor": "Editor de Mundo/Historia", "World/Lore Editor": "Editor de Mundo/Historia",
"--- None ---": "--- Ninguno ---", "--- None ---": "--- Ninguno ---",
@ -891,6 +889,7 @@
"Chat API": " API de chat", "Chat API": " API de chat",
"and pick a character": "y elige un personaje", "and pick a character": "y elige un personaje",
"in the chat bar": "en la barra de chat", "in the chat bar": "en la barra de chat",
"You can browse a list of bundled characters in the Download Extensions & Assets menu within": "Puedes explorar una lista de personajes incluidos en el menú de Download Extensions & Assets dentro de ",
"Confused or lost?": "¿Confundido o perdido?", "Confused or lost?": "¿Confundido o perdido?",
"click these icons!": "¡Haz clic en estos iconos!", "click these icons!": "¡Haz clic en estos iconos!",
"SillyTavern Documentation Site": "Sitio de documentación de SillyTavern", "SillyTavern Documentation Site": "Sitio de documentación de SillyTavern",
@ -914,7 +913,4 @@
"Use the appropriate tokenizer for Google models via their API. Slower prompt processing, but offers much more accurate token counting.": "Usa el tokenizador apropiado para los modelos de Google a través de su API. Procesamiento de indicaciones más lento, pero ofrece un recuento de tokens mucho más preciso.", "Use the appropriate tokenizer for Google models via their API. Slower prompt processing, but offers much more accurate token counting.": "Usa el tokenizador apropiado para los modelos de Google a través de su API. Procesamiento de indicaciones más lento, pero ofrece un recuento de tokens mucho más preciso.",
"Load koboldcpp order": "Cargar orden de koboldcpp", "Load koboldcpp order": "Cargar orden de koboldcpp",
"Use Google Tokenizer": "Usar Tokenizador de Google" "Use Google Tokenizer": "Usar Tokenizador de Google"
} }

View File

@ -3,7 +3,6 @@
"kobldpresets": "Préréglages de Kobold", "kobldpresets": "Préréglages de Kobold",
"guikoboldaisettings": "Paramètres de l'interface utilisateur de KoboldAI", "guikoboldaisettings": "Paramètres de l'interface utilisateur de KoboldAI",
"novelaipreserts": "Préréglages de NovelAI", "novelaipreserts": "Préréglages de NovelAI",
"default": "Par défaut",
"openaipresets": "Préréglages d'OpenAI", "openaipresets": "Préréglages d'OpenAI",
"text gen webio(ooba) presets": "Préréglages de WebUI(ooba)", "text gen webio(ooba) presets": "Préréglages de WebUI(ooba)",
"response legth(tokens)": "Longueur de la réponse (en tokens)", "response legth(tokens)": "Longueur de la réponse (en tokens)",
@ -494,7 +493,6 @@
"Global Lore First": "Lore global d'abord", "Global Lore First": "Lore global d'abord",
"Recursive Scan": "Analyse récursive", "Recursive Scan": "Analyse récursive",
"Case Sensitive": "Sensible à la casse", "Case Sensitive": "Sensible à la casse",
"Match whole words": "Correspondre aux mots entiers",
"Alert On Overflow": "Alerte en cas de dépassement", "Alert On Overflow": "Alerte en cas de dépassement",
"World/Lore Editor": "Éditeur de monde/lore", "World/Lore Editor": "Éditeur de monde/lore",
"--- None ---": "--- Aucun ---", "--- None ---": "--- Aucun ---",

View File

@ -3,7 +3,6 @@
"kobldpresets": "Fyrir stillingar Kobold", "kobldpresets": "Fyrir stillingar Kobold",
"guikoboldaisettings": "Stillingar fyrir KoboldAI viðmót", "guikoboldaisettings": "Stillingar fyrir KoboldAI viðmót",
"novelaipreserts": "Fyrir stillingar NovelAI", "novelaipreserts": "Fyrir stillingar NovelAI",
"default": "Sjálfgefið",
"openaipresets": "Fyrir stillingar OpenAI", "openaipresets": "Fyrir stillingar OpenAI",
"text gen webio(ooba) presets": "Fyrir stillingar WebUI(ooba) textagerðar", "text gen webio(ooba) presets": "Fyrir stillingar WebUI(ooba) textagerðar",
"response legth(tokens)": "Lengd svars (í táknum eða stöfum)", "response legth(tokens)": "Lengd svars (í táknum eða stöfum)",
@ -495,7 +494,6 @@
"Global Lore First": "Fyrst heimsfræði", "Global Lore First": "Fyrst heimsfræði",
"Recursive Scan": "Endurkvæm skoðun", "Recursive Scan": "Endurkvæm skoðun",
"Case Sensitive": "Skilgreiningarfræðilegt", "Case Sensitive": "Skilgreiningarfræðilegt",
"Match whole words": "Nákvæm samræmi",
"Alert On Overflow": "Viðvörun um flæði", "Alert On Overflow": "Viðvörun um flæði",
"World/Lore Editor": "Heims-/fræðiritari", "World/Lore Editor": "Heims-/fræðiritari",
"--- None ---": "--- Engin ---", "--- None ---": "--- Engin ---",

View File

@ -3,7 +3,6 @@
"kobldpresets": "Preimpostazioni Kobold", "kobldpresets": "Preimpostazioni Kobold",
"guikoboldaisettings": "Impostazioni dell'interfaccia KoboldAI", "guikoboldaisettings": "Impostazioni dell'interfaccia KoboldAI",
"novelaipreserts": "Preimpostazioni NovelAI", "novelaipreserts": "Preimpostazioni NovelAI",
"default": "Predefinito",
"openaipresets": "Preimpostazioni OpenAI", "openaipresets": "Preimpostazioni OpenAI",
"text gen webio(ooba) presets": "Preimpostazioni WebUI(ooba) per la generazione di testo", "text gen webio(ooba) presets": "Preimpostazioni WebUI(ooba) per la generazione di testo",
"response legth(tokens)": "Lunghezza della risposta (token)", "response legth(tokens)": "Lunghezza della risposta (token)",
@ -495,7 +494,6 @@
"Global Lore First": "Lore Globale Prima", "Global Lore First": "Lore Globale Prima",
"Recursive Scan": "Scansione Ricorsiva", "Recursive Scan": "Scansione Ricorsiva",
"Case Sensitive": "Sensibile alle Maiuscole", "Case Sensitive": "Sensibile alle Maiuscole",
"Match whole words": "Corrispondi a parole intere",
"Alert On Overflow": "Avviso Su Overflow", "Alert On Overflow": "Avviso Su Overflow",
"World/Lore Editor": "Editor di Mondo/Lore", "World/Lore Editor": "Editor di Mondo/Lore",
"--- None ---": "--- Nessuno ---", "--- None ---": "--- Nessuno ---",

View File

@ -3,7 +3,6 @@
"kobldpresets": "Koboldのプリセット", "kobldpresets": "Koboldのプリセット",
"guikoboldaisettings": "KoboldAIのGUI設定", "guikoboldaisettings": "KoboldAIのGUI設定",
"novelaipreserts": "NovelAIのプリセット", "novelaipreserts": "NovelAIのプリセット",
"default": "デフォルト",
"openaipresets": "OpenAIのプリセット", "openaipresets": "OpenAIのプリセット",
"text gen webio(ooba) presets": "WebUI(ooba)のプリセット", "text gen webio(ooba) presets": "WebUI(ooba)のプリセット",
"response legth(tokens)": "応答の長さ(トークン数)", "response legth(tokens)": "応答の長さ(トークン数)",
@ -494,7 +493,6 @@
"Global Lore First": "グローバルロアを最初に表示", "Global Lore First": "グローバルロアを最初に表示",
"Recursive Scan": "再帰的スキャン", "Recursive Scan": "再帰的スキャン",
"Case Sensitive": "大文字と小文字を区別する", "Case Sensitive": "大文字と小文字を区別する",
"Match whole words": "完全な単語の一致",
"Alert On Overflow": "オーバーフロー時に警告", "Alert On Overflow": "オーバーフロー時に警告",
"World/Lore Editor": "ワールド/ロアの編集", "World/Lore Editor": "ワールド/ロアの編集",
"--- None ---": "--- なし ---", "--- None ---": "--- なし ---",

View File

@ -3,7 +3,6 @@
"kobldpresets": "코볼드 사전 설정", "kobldpresets": "코볼드 사전 설정",
"guikoboldaisettings": "KoboldAI 인터페이스 설정", "guikoboldaisettings": "KoboldAI 인터페이스 설정",
"novelaipreserts": "NovelAI 사전 설정", "novelaipreserts": "NovelAI 사전 설정",
"default": "기본값",
"openaipresets": "OpenAI 사전 설정", "openaipresets": "OpenAI 사전 설정",
"text gen webio(ooba) presets": "텍스트 생성 WebUI(ooba) 사전 설정", "text gen webio(ooba) presets": "텍스트 생성 WebUI(ooba) 사전 설정",
"response legth(tokens)": "응답 길이 (토큰)", "response legth(tokens)": "응답 길이 (토큰)",
@ -495,7 +494,6 @@
"Global Lore First": "글로벌 로어 우선", "Global Lore First": "글로벌 로어 우선",
"Recursive Scan": "재귀 스캔", "Recursive Scan": "재귀 스캔",
"Case Sensitive": "대소문자 구분", "Case Sensitive": "대소문자 구분",
"Match whole words": "전체 단어 일치",
"Alert On Overflow": "오버플로우 알림", "Alert On Overflow": "오버플로우 알림",
"World/Lore Editor": "월드/로어 편집기", "World/Lore Editor": "월드/로어 편집기",
"--- None ---": "--- 없음 ---", "--- None ---": "--- 없음 ---",

View File

@ -1,6 +1,7 @@
[ [
{ "lang": "ar-sa", "display": "عربي (Arabic)" }, { "lang": "ar-sa", "display": "عربي (Arabic)" },
{ "lang": "zh-cn", "display": "简体中文 (Chinese) (Simplified)" }, { "lang": "zh-cn", "display": "简体中文 (Chinese) (Simplified)" },
{ "lang": "zh-tw", "display": "繁體中文 (Chinese) (Taiwan)" },
{ "lang": "nl-nl", "display": "Nederlands (Dutch)" }, { "lang": "nl-nl", "display": "Nederlands (Dutch)" },
{ "lang": "de-de", "display": "Deutsch (German)" }, { "lang": "de-de", "display": "Deutsch (German)" },
{ "lang": "fr-fr", "display": "Français (French)" }, { "lang": "fr-fr", "display": "Français (French)" },

View File

@ -3,7 +3,6 @@
"kobldpresets": "Kobold voorinstellingen", "kobldpresets": "Kobold voorinstellingen",
"guikoboldaisettings": "KoboldAI-interface-instellingen", "guikoboldaisettings": "KoboldAI-interface-instellingen",
"novelaipreserts": "NovelAI-voorinstellingen", "novelaipreserts": "NovelAI-voorinstellingen",
"default": "Standaard",
"openaipresets": "OpenAI-voorinstellingen", "openaipresets": "OpenAI-voorinstellingen",
"text gen webio(ooba) presets": "WebUI(ooba)-voorinstellingen voor tekstgeneratie", "text gen webio(ooba) presets": "WebUI(ooba)-voorinstellingen voor tekstgeneratie",
"response legth(tokens)": "Reactielengte (tokens)", "response legth(tokens)": "Reactielengte (tokens)",
@ -495,7 +494,6 @@
"Global Lore First": "Globale Lore Eerst", "Global Lore First": "Globale Lore Eerst",
"Recursive Scan": "Recursieve Scan", "Recursive Scan": "Recursieve Scan",
"Case Sensitive": "Hoofdlettergevoelig", "Case Sensitive": "Hoofdlettergevoelig",
"Match whole words": "Hele woorden matchen",
"Alert On Overflow": "Waarschuwing bij overloop", "Alert On Overflow": "Waarschuwing bij overloop",
"World/Lore Editor": "Wereld/Lore Editor", "World/Lore Editor": "Wereld/Lore Editor",
"--- None ---": "--- Geen ---", "--- None ---": "--- Geen ---",

View File

@ -3,7 +3,6 @@
"kobldpresets": "Configurações predefinidas do Kobold", "kobldpresets": "Configurações predefinidas do Kobold",
"guikoboldaisettings": "Configurações da interface do KoboldAI", "guikoboldaisettings": "Configurações da interface do KoboldAI",
"novelaipreserts": "Configurações predefinidas do NovelAI", "novelaipreserts": "Configurações predefinidas do NovelAI",
"default": "Padrão",
"openaipresets": "Configurações predefinidas do OpenAI", "openaipresets": "Configurações predefinidas do OpenAI",
"text gen webio(ooba) presets": "Configurações predefinidas do WebUI(ooba) para geração de texto", "text gen webio(ooba) presets": "Configurações predefinidas do WebUI(ooba) para geração de texto",
"response legth(tokens)": "Comprimento da resposta (tokens)", "response legth(tokens)": "Comprimento da resposta (tokens)",
@ -493,7 +492,6 @@
"Global Lore First": "Lore Global Primeiro", "Global Lore First": "Lore Global Primeiro",
"Recursive Scan": "Verificação Recursiva", "Recursive Scan": "Verificação Recursiva",
"Case Sensitive": "Sensível a Maiúsculas", "Case Sensitive": "Sensível a Maiúsculas",
"Match whole words": "Corresponder palavras inteiras",
"Alert On Overflow": "Alerta em Overflow", "Alert On Overflow": "Alerta em Overflow",
"World/Lore Editor": "Editor de Mundo/Lore", "World/Lore Editor": "Editor de Mundo/Lore",
"--- None ---": "--- Nenhum ---", "--- None ---": "--- Nenhum ---",

View File

@ -3,7 +3,6 @@
"kobldpresets": "Пресеты для Kobold", "kobldpresets": "Пресеты для Kobold",
"guikoboldaisettings": "Настройки из интерфейса KoboldAI", "guikoboldaisettings": "Настройки из интерфейса KoboldAI",
"novelaipreserts": "Пресеты для NovelAI", "novelaipreserts": "Пресеты для NovelAI",
"default": "По умолчанию",
"openaipresets": "Пресеты для OpenAI", "openaipresets": "Пресеты для OpenAI",
"text gen webio(ooba) presets": "Пресеты для WebUI(ooba)", "text gen webio(ooba) presets": "Пресеты для WebUI(ooba)",
"response legth(tokens)": "Ответ (в токенах)", "response legth(tokens)": "Ответ (в токенах)",
@ -276,7 +275,7 @@
"World Info": "Информация о мире", "World Info": "Информация о мире",
"Scan Depth": "Глубина сканирования", "Scan Depth": "Глубина сканирования",
"Case-Sensitive": "С учетом регистра", "Case-Sensitive": "С учетом регистра",
"Match Whole Words": "Только целые слова", "Match Whole Words": "Только полное совпадение",
"Use global setting": "Использовать глобальную настройку", "Use global setting": "Использовать глобальную настройку",
"Yes": "Да", "Yes": "Да",
"No": "Нет", "No": "Нет",
@ -495,7 +494,6 @@
"Global Lore First": "Сначала глобальный лор", "Global Lore First": "Сначала глобальный лор",
"Recursive Scan": "Рекурсивное сканирование", "Recursive Scan": "Рекурсивное сканирование",
"Case Sensitive": "Учитывать регистр", "Case Sensitive": "Учитывать регистр",
"Match whole words": "Только полное совпадение",
"Alert On Overflow": "Оповещение о переполнении", "Alert On Overflow": "Оповещение о переполнении",
"World/Lore Editor": "Редактировать мир или лор", "World/Lore Editor": "Редактировать мир или лор",
"--- None ---": "--- Отсутствует ---", "--- None ---": "--- Отсутствует ---",

View File

@ -3,7 +3,6 @@
"kobldpresets": "Налаштування Kobold", "kobldpresets": "Налаштування Kobold",
"guikoboldaisettings": "З інтерфейсу KoboldAI", "guikoboldaisettings": "З інтерфейсу KoboldAI",
"novelaipreserts": "Налаштування NovelAI", "novelaipreserts": "Налаштування NovelAI",
"default": "За замовчуванням",
"openaipresets": "Налаштування OpenAI", "openaipresets": "Налаштування OpenAI",
"text gen webio(ooba) presets": "Налаштування Text Completion", "text gen webio(ooba) presets": "Налаштування Text Completion",
"response legth(tokens)": "Відповідь (токени)", "response legth(tokens)": "Відповідь (токени)",
@ -495,7 +494,6 @@
"Global Lore First": "Глобальна інформація першою", "Global Lore First": "Глобальна інформація першою",
"Recursive Scan": "Рекурсивне сканування", "Recursive Scan": "Рекурсивне сканування",
"Case Sensitive": "Чутливість до регістру", "Case Sensitive": "Чутливість до регістру",
"Match whole words": "Відповідність цілим словам",
"Alert On Overflow": "Сповіщення при переповненні", "Alert On Overflow": "Сповіщення при переповненні",
"World/Lore Editor": "Редактор світу/книги", "World/Lore Editor": "Редактор світу/книги",
"--- None ---": "--- Нічого ---", "--- None ---": "--- Нічого ---",

View File

@ -3,7 +3,6 @@
"kobldpresets": "Cài đặt trước Kobold", "kobldpresets": "Cài đặt trước Kobold",
"guikoboldaisettings": "Cài đặt giao diện KoboldAI", "guikoboldaisettings": "Cài đặt giao diện KoboldAI",
"novelaipreserts": "Cài đặt trước NovelAI", "novelaipreserts": "Cài đặt trước NovelAI",
"default": "Mặc định",
"openaipresets": "Cài đặt trước OpenAI", "openaipresets": "Cài đặt trước OpenAI",
"text gen webio(ooba) presets": "Cài đặt trước WebUI(ooba) của máy tạo văn bản", "text gen webio(ooba) presets": "Cài đặt trước WebUI(ooba) của máy tạo văn bản",
"response legth(tokens)": "Độ dài phản hồi (trong các token)", "response legth(tokens)": "Độ dài phản hồi (trong các token)",
@ -495,7 +494,6 @@
"Global Lore First": "Sử liệu toàn cầu đầu tiên", "Global Lore First": "Sử liệu toàn cầu đầu tiên",
"Recursive Scan": "Quét đệ quy", "Recursive Scan": "Quét đệ quy",
"Case Sensitive": "Phân biệt chữ hoa chữ thường", "Case Sensitive": "Phân biệt chữ hoa chữ thường",
"Match whole words": "Khớp toàn bộ từ",
"Alert On Overflow": "Cảnh báo khi tràn", "Alert On Overflow": "Cảnh báo khi tràn",
"World/Lore Editor": "Trình soạn thảo Thế giới/Sử liệu", "World/Lore Editor": "Trình soạn thảo Thế giới/Sử liệu",
"--- None ---": "--- Không ---", "--- None ---": "--- Không ---",

View File

@ -3,7 +3,6 @@
"kobldpresets": "Kobold 预设", "kobldpresets": "Kobold 预设",
"guikoboldaisettings": "KoboldAI 用户界面设置", "guikoboldaisettings": "KoboldAI 用户界面设置",
"novelaipreserts": "NovelAI 预设", "novelaipreserts": "NovelAI 预设",
"default": "默认",
"openaipresets": "对话补全预设", "openaipresets": "对话补全预设",
"text gen webio(ooba) presets": "WebUI(ooba) 预设", "text gen webio(ooba) presets": "WebUI(ooba) 预设",
"response legth(tokens)": "响应长度(以词符数计)", "response legth(tokens)": "响应长度(以词符数计)",
@ -495,7 +494,6 @@
"Global Lore First": "全局世界书优先", "Global Lore First": "全局世界书优先",
"Recursive Scan": "递归扫描", "Recursive Scan": "递归扫描",
"Case Sensitive": "区分大小写", "Case Sensitive": "区分大小写",
"Match whole words": "完整匹配单词",
"Alert On Overflow": "溢出警报", "Alert On Overflow": "溢出警报",
"World/Lore Editor": "世界书编辑器", "World/Lore Editor": "世界书编辑器",
"--- None ---": "--- 无 ---", "--- None ---": "--- 无 ---",

1192
public/locales/zh-tw.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -8636,11 +8636,6 @@ jQuery(async function () {
callback: doCloseChat, callback: doCloseChat,
helpString: 'Closes the current chat.', helpString: 'Closes the current chat.',
})); }));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'closechat',
callback: doCloseChat,
helpString: 'Closes the current chat.',
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'panels', SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'panels',
callback: doTogglePanels, callback: doTogglePanels,
aliases: ['togglepanels'], aliases: ['togglepanels'],

View File

@ -4,7 +4,7 @@
Enter a URL or the ID of a Fandom wiki page to scrape: Enter a URL or the ID of a Fandom wiki page to scrape:
</label> </label>
<small> <small>
<span data-i18n=Examples:">Examples:</span> <span data-i18n="Examples:">Examples:</span>
<code>https://harrypotter.fandom.com/</code> <code>https://harrypotter.fandom.com/</code>
<span data-i18n="or">or</span> <span data-i18n="or">or</span>
<code>harrypotter</code> <code>harrypotter</code>

View File

@ -7,7 +7,7 @@
Don't include the page name! Don't include the page name!
</i> </i>
<small> <small>
<span data-i18n=Examples:">Examples:</span> <span data-i18n="Examples:">Examples:</span>
<code>https://streetcat.wiki/index.php</code> <code>https://streetcat.wiki/index.php</code>
<span data-i18n="or">or</span> <span data-i18n="or">or</span>
<code>https://tcrf.net</code> <code>https://tcrf.net</code>

View File

@ -1020,12 +1020,12 @@ function parseLlmResponse(emotionResponse, labels) {
const parsedEmotion = JSON.parse(emotionResponse); const parsedEmotion = JSON.parse(emotionResponse);
return parsedEmotion?.emotion ?? fallbackExpression; return parsedEmotion?.emotion ?? fallbackExpression;
} catch { } catch {
const fuse = new Fuse([emotionResponse]); const fuse = new Fuse(labels, { includeScore: true });
for (const label of labels) { console.debug('Using fuzzy search in labels:', labels);
const result = fuse.search(label); const result = fuse.search(emotionResponse);
if (result.length > 0) { if (result.length > 0) {
return label; console.debug(`fuzzy search found: ${result[0].item} as closest for the LLM response:`, emotionResponse);
} return result[0].item;
} }
} }

View File

@ -342,6 +342,16 @@ export class QuickReply {
message.addEventListener('scroll', (evt)=>{ message.addEventListener('scroll', (evt)=>{
updateScrollDebounced(); updateScrollDebounced();
}); });
/** @type {any} */
const resizeListener = debounce((evt) => {
updateSyntax();
updateScrollDebounced(evt);
if (document.activeElement == message) {
message.blur();
message.focus();
}
});
window.addEventListener('resize', resizeListener);
message.style.color = 'transparent'; message.style.color = 'transparent';
message.style.background = 'transparent'; message.style.background = 'transparent';
message.style.setProperty('text-shadow', 'none', 'important'); message.style.setProperty('text-shadow', 'none', 'important');
@ -514,6 +524,8 @@ export class QuickReply {
}); });
await popupResult; await popupResult;
window.removeEventListener('resize', resizeListener);
} else { } else {
warn('failed to fetch qrEditor template'); warn('failed to fetch qrEditor template');
} }

View File

@ -266,6 +266,7 @@ const default_settings = {
show_external_models: false, show_external_models: false,
proxy_password: '', proxy_password: '',
assistant_prefill: '', assistant_prefill: '',
assistant_impersonation: '',
human_sysprompt_message: default_claude_human_sysprompt_message, human_sysprompt_message: default_claude_human_sysprompt_message,
use_ai21_tokenizer: false, use_ai21_tokenizer: false,
use_google_tokenizer: false, use_google_tokenizer: false,
@ -342,6 +343,7 @@ const oai_settings = {
show_external_models: false, show_external_models: false,
proxy_password: '', proxy_password: '',
assistant_prefill: '', assistant_prefill: '',
assistant_impersonation: '',
human_sysprompt_message: default_claude_human_sysprompt_message, human_sysprompt_message: default_claude_human_sysprompt_message,
use_ai21_tokenizer: false, use_ai21_tokenizer: false,
use_google_tokenizer: false, use_google_tokenizer: false,
@ -1767,7 +1769,7 @@ async function sendOpenAIRequest(type, messages, signal) {
generate_data['human_sysprompt_message'] = substituteParams(oai_settings.human_sysprompt_message); generate_data['human_sysprompt_message'] = substituteParams(oai_settings.human_sysprompt_message);
// Don't add a prefill on quiet gens (summarization) // Don't add a prefill on quiet gens (summarization)
if (!isQuiet) { if (!isQuiet) {
generate_data['assistant_prefill'] = substituteParams(oai_settings.assistant_prefill); generate_data['assistant_prefill'] = isImpersonate ? substituteParams(oai_settings.assistant_impersonation) : substituteParams(oai_settings.assistant_prefill);
} }
} }
@ -2760,6 +2762,7 @@ function loadOpenAISettings(data, settings) {
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; oai_settings.assistant_prefill = settings.assistant_prefill ?? default_settings.assistant_prefill;
oai_settings.assistant_impersonation = settings.assistant_impersonation ?? default_settings.assistant_impersonation;
oai_settings.human_sysprompt_message = settings.human_sysprompt_message ?? default_settings.human_sysprompt_message; oai_settings.human_sysprompt_message = settings.human_sysprompt_message ?? default_settings.human_sysprompt_message;
oai_settings.image_inlining = settings.image_inlining ?? default_settings.image_inlining; oai_settings.image_inlining = settings.image_inlining ?? default_settings.image_inlining;
oai_settings.inline_image_quality = settings.inline_image_quality ?? default_settings.inline_image_quality; oai_settings.inline_image_quality = settings.inline_image_quality ?? default_settings.inline_image_quality;
@ -2796,6 +2799,7 @@ function loadOpenAISettings(data, settings) {
$('#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); $('#claude_assistant_prefill').val(oai_settings.assistant_prefill);
$('#claude_assistant_impersonation').val(oai_settings.assistant_impersonation);
$('#claude_human_sysprompt_textarea').val(oai_settings.human_sysprompt_message); $('#claude_human_sysprompt_textarea').val(oai_settings.human_sysprompt_message);
$('#openai_image_inlining').prop('checked', oai_settings.image_inlining); $('#openai_image_inlining').prop('checked', oai_settings.image_inlining);
$('#openai_bypass_status_check').prop('checked', oai_settings.bypass_status_check); $('#openai_bypass_status_check').prop('checked', oai_settings.bypass_status_check);
@ -3115,6 +3119,7 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) {
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, assistant_prefill: settings.assistant_prefill,
assistant_impersonation: settings.assistant_impersonation,
human_sysprompt_message: settings.human_sysprompt_message, human_sysprompt_message: settings.human_sysprompt_message,
use_ai21_tokenizer: settings.use_ai21_tokenizer, use_ai21_tokenizer: settings.use_ai21_tokenizer,
use_google_tokenizer: settings.use_google_tokenizer, use_google_tokenizer: settings.use_google_tokenizer,
@ -3501,6 +3506,7 @@ function onSettingsPresetChange() {
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], assistant_prefill: ['#claude_assistant_prefill', 'assistant_prefill', false],
assistant_impersonation: ['#claude_assistant_impersonation', 'assistant_impersonation', false],
human_sysprompt_message: ['#claude_human_sysprompt_textarea', 'human_sysprompt_message', false], human_sysprompt_message: ['#claude_human_sysprompt_textarea', 'human_sysprompt_message', false],
use_ai21_tokenizer: ['#use_ai21_tokenizer', 'use_ai21_tokenizer', true], use_ai21_tokenizer: ['#use_ai21_tokenizer', 'use_ai21_tokenizer', true],
use_google_tokenizer: ['#use_google_tokenizer', 'use_google_tokenizer', true], use_google_tokenizer: ['#use_google_tokenizer', 'use_google_tokenizer', true],
@ -3526,6 +3532,11 @@ function onSettingsPresetChange() {
preset.names_behavior = character_names_behavior.COMPLETION; preset.names_behavior = character_names_behavior.COMPLETION;
} }
// Claude: Assistant Impersonation Prefill = Inherit from Assistant Prefill
if (preset.assistant_prefill !== undefined && preset.assistant_impersonation === undefined) {
preset.assistant_impersonation = preset.assistant_prefill;
}
const updateInput = (selector, value) => $(selector).val(value).trigger('input'); const updateInput = (selector, value) => $(selector).val(value).trigger('input');
const updateCheckbox = (selector, value) => $(selector).prop('checked', value).trigger('input'); const updateCheckbox = (selector, value) => $(selector).prop('checked', value).trigger('input');
@ -4721,6 +4732,11 @@ $(document).ready(async function () {
saveSettingsDebounced(); saveSettingsDebounced();
}); });
$('#claude_assistant_impersonation').on('input', function () {
oai_settings.assistant_impersonation = String($(this).val());
saveSettingsDebounced();
});
$('#claude_human_sysprompt_textarea').on('input', function () { $('#claude_human_sysprompt_textarea').on('input', function () {
oai_settings.human_sysprompt_message = String($('#claude_human_sysprompt_textarea').val()); oai_settings.human_sysprompt_message = String($('#claude_human_sysprompt_textarea').val());
saveSettingsDebounced(); saveSettingsDebounced();

View File

@ -114,7 +114,9 @@ class PresetManager {
* @returns {any} Preset value * @returns {any} Preset value
*/ */
findPreset(name) { findPreset(name) {
return $(this.select).find(`option:contains(${name})`).val(); return $(this.select).find('option').filter(function() {
return $(this).text() === name;
}).val();
} }
/** /**

View File

@ -447,8 +447,9 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'unhide',
], ],
helpString: 'Unhides a message from the prompt.', helpString: 'Unhides a message from the prompt.',
})); }));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'disable', SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'member-disable',
callback: disableGroupMemberCallback, callback: disableGroupMemberCallback,
aliases: ['disable', 'disablemember', 'memberdisable'],
unnamedArgumentList: [ unnamedArgumentList: [
new SlashCommandArgument( new SlashCommandArgument(
'member index or name', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], true, 'member index or name', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], true,
@ -456,7 +457,8 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'disable',
], ],
helpString: 'Disables a group member from being drafted for replies.', helpString: 'Disables a group member from being drafted for replies.',
})); }));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'enable', SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'member-enable',
aliases: ['enable', 'enablemember', 'memberenable'],
callback: enableGroupMemberCallback, callback: enableGroupMemberCallback,
unnamedArgumentList: [ unnamedArgumentList: [
new SlashCommandArgument( new SlashCommandArgument(
@ -465,9 +467,9 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'enable',
], ],
helpString: 'Enables a group member to be drafted for replies.', helpString: 'Enables a group member to be drafted for replies.',
})); }));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'memberadd', SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'member-add',
callback: addGroupMemberCallback, callback: addGroupMemberCallback,
aliases: ['addmember'], aliases: ['addmember', 'memberadd'],
unnamedArgumentList: [ unnamedArgumentList: [
new SlashCommandArgument( new SlashCommandArgument(
'character name', [ARGUMENT_TYPE.STRING], true, 'character name', [ARGUMENT_TYPE.STRING], true,
@ -481,15 +483,15 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'memberadd',
<strong>Example:</strong> <strong>Example:</strong>
<ul> <ul>
<li> <li>
<pre><code>/memberadd John Doe</code></pre> <pre><code>/member-add John Doe</code></pre>
</li> </li>
</ul> </ul>
</div> </div>
`, `,
})); }));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'memberremove', SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'member-remove',
callback: removeGroupMemberCallback, callback: removeGroupMemberCallback,
aliases: ['removemember'], aliases: ['removemember', 'memberremove'],
unnamedArgumentList: [ unnamedArgumentList: [
new SlashCommandArgument( new SlashCommandArgument(
'member index or name', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], true, 'member index or name', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], true,
@ -503,16 +505,16 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'memberremove
<strong>Example:</strong> <strong>Example:</strong>
<ul> <ul>
<li> <li>
<pre><code>/memberremove 2</code></pre> <pre><code>/member-remove 2</code></pre>
<pre><code>/memberremove John Doe</code></pre> <pre><code>/member-remove John Doe</code></pre>
</li> </li>
</ul> </ul>
</div> </div>
`, `,
})); }));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'memberup', SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'member-up',
callback: moveGroupMemberUpCallback, callback: moveGroupMemberUpCallback,
aliases: ['upmember'], aliases: ['upmember', 'memberup'],
unnamedArgumentList: [ unnamedArgumentList: [
new SlashCommandArgument( new SlashCommandArgument(
'member index or name', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], true, 'member index or name', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], true,
@ -520,9 +522,9 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'memberup',
], ],
helpString: 'Moves a group member up in the group chat list.', helpString: 'Moves a group member up in the group chat list.',
})); }));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'memberdown', SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'member-down',
callback: moveGroupMemberDownCallback, callback: moveGroupMemberDownCallback,
aliases: ['downmember'], aliases: ['downmember', 'memberdown'],
unnamedArgumentList: [ unnamedArgumentList: [
new SlashCommandArgument( new SlashCommandArgument(
'member index or name', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], true, 'member index or name', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], true,
@ -2766,10 +2768,12 @@ export async function executeSlashCommandsOnChatInput(text, options = {}) {
} }
} catch (e) { } catch (e) {
document.querySelector('#form_sheld').classList.add('script_error'); document.querySelector('#form_sheld').classList.add('script_error');
toastr.error(e.message);
result = new SlashCommandClosureResult(); result = new SlashCommandClosureResult();
result.isError = true; result.isError = true;
result.errorMessage = e.message; result.errorMessage = e.message;
if (e.cause !== 'abort') {
toastr.error(e.message);
}
} finally { } finally {
delay(1000).then(()=>clearCommandProgressDebounced()); delay(1000).then(()=>clearCommandProgressDebounced());

View File

@ -58,6 +58,9 @@ export class SlashCommandAutoCompleteNameResult extends AutoCompleteNameResult {
return new RegExp('=(.*)'); return new RegExp('=(.*)');
} }
} }
if (!Array.isArray(this.executor.command?.namedArgumentList)) {
return null;
}
const notProvidedNamedArguments = this.executor.command.namedArgumentList.filter(arg=>!this.executor.namedArgumentList.find(it=>it.name == arg.name)); const notProvidedNamedArguments = this.executor.command.namedArgumentList.filter(arg=>!this.executor.namedArgumentList.find(it=>it.name == arg.name));
let name; let name;
let value; let value;
@ -130,6 +133,9 @@ export class SlashCommandAutoCompleteNameResult extends AutoCompleteNameResult {
} }
getUnnamedArgumentAt(text, index, isSelect) { getUnnamedArgumentAt(text, index, isSelect) {
if (!Array.isArray(this.executor.command?.unnamedArgumentList)) {
return null;
}
const lastArgIsBlank = this.executor.unnamedArgumentList.slice(-1)[0]?.value == ''; const lastArgIsBlank = this.executor.unnamedArgumentList.slice(-1)[0]?.value == '';
const notProvidedArguments = this.executor.command.unnamedArgumentList.slice(this.executor.unnamedArgumentList.length - (lastArgIsBlank ? 1 : 0)); const notProvidedArguments = this.executor.command.unnamedArgumentList.slice(this.executor.unnamedArgumentList.length - (lastArgIsBlank ? 1 : 0));
let value; let value;

View File

@ -14,10 +14,12 @@ import {
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
import { FILTER_TYPES, FILTER_STATES, DEFAULT_FILTER_STATE, isFilterState, FilterHelper } from './filters.js'; import { FILTER_TYPES, FILTER_STATES, DEFAULT_FILTER_STATE, isFilterState, FilterHelper } from './filters.js';
import { groupCandidatesFilter, groups, selected_group } from './group-chats.js'; import { groupCandidatesFilter, groups, select_group_chats, selected_group } from './group-chats.js';
import { download, onlyUnique, parseJsonFile, uuidv4, getSortableDelay, flashHighlight } from './utils.js'; import { download, onlyUnique, parseJsonFile, uuidv4, getSortableDelay, flashHighlight } from './utils.js';
import { power_user } from './power-user.js'; import { power_user } from './power-user.js';
import { debounce_timeout } from './constants.js'; import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
import { SlashCommand } from './slash-commands/SlashCommand.js';
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
export { export {
TAG_FOLDER_TYPES, TAG_FOLDER_TYPES,
@ -357,7 +359,7 @@ function createTagMapFromList(listElement, key) {
* If you have an entity, you can get it's key easily via `getTagKeyForEntity(entity)`. * If you have an entity, you can get it's key easily via `getTagKeyForEntity(entity)`.
* *
* @param {string} key - The key for which to get tags via the tag map * @param {string} key - The key for which to get tags via the tag map
* @param {boolean} [sort=true] - * @param {boolean} [sort=true] - Whether the tag list should be sorted
* @returns {Tag[]} A list of tags * @returns {Tag[]} A list of tags
*/ */
function getTagsList(key, sort = true) { function getTagsList(key, sort = true) {
@ -463,35 +465,122 @@ export function getTagKeyForEntityElement(element) {
return undefined; return undefined;
} }
/**
* Adds a tag to a given entity
* @param {Tag} tag - The tag to add
* @param {string|string[]} entityId - The entity to add this tag to. Has to be the entity key (e.g. `addTagToEntity`). (Also allows multiple entities to be passed in)
* @param {object} [options={}] - Optional arguments
* @param {JQuery<HTMLElement>|string?} [options.tagListSelector=null] - An optional selector if a specific list should be updated with the new tag too (for example because the add was triggered for that function)
* @param {PrintTagListOptions} [options.tagListOptions] - Optional parameters for printing the tag list. Can be set to be consistent with the expected behavior of tags in the list that was defined before.
* @returns {boolean} Whether at least one tag was added
*/
export function addTagToEntity(tag, entityId, { tagListSelector = null, tagListOptions = {} } = {}) {
let result = false;
// Add tags to the map
if (Array.isArray(entityId)) {
entityId.forEach((id) => result = addTagToMap(tag.id, id) || result);
} else {
result = addTagToMap(tag.id, entityId);
}
// Save and redraw
printCharactersDebounced();
saveSettingsDebounced();
// We should manually add the selected tag to the print tag function, so we cover places where the tag list did not automatically include it
tagListOptions.addTag = tag;
// add tag to the UI and internal map - we reprint so sorting and new markup is done correctly
if (tagListSelector) printTagList(tagListSelector, tagListOptions);
const inlineSelector = getInlineListSelector();
if (inlineSelector) {
printTagList($(inlineSelector), tagListOptions);
}
return result;
}
/**
* Removes a tag from a given entity
* @param {Tag} tag - The tag to remove
* @param {string|string[]} entityId - The entity to remove this tag from. Has to be the entity key (e.g. `addTagToEntity`). (Also allows multiple entities to be passed in)
* @param {object} [options={}] - Optional arguments
* @param {JQuery<HTMLElement>|string?} [options.tagListSelector=null] - An optional selector if a specific list should be updated with the tag removed too (for example because the add was triggered for that function)
* @param {JQuery<HTMLElement>?} [options.tagElement=null] - Optionally a direct html element of the tag to be removed, so it can be removed from the UI
* @returns {boolean} Whether at least one tag was removed
*/
export function removeTagFromEntity(tag, entityId, { tagListSelector = null, tagElement = null } = {}) {
let result = false;
// Remove tag from the map
if (Array.isArray(entityId)) {
entityId.forEach((id) => result = removeTagFromMap(tag.id, id) || result);
} else {
result = removeTagFromMap(tag.id, entityId);
}
// Save and redraw
printCharactersDebounced();
saveSettingsDebounced();
// We don't reprint the lists, we can just remove the html elements from them.
if (tagListSelector) {
const $selector = (typeof tagListSelector === 'string') ? $(tagListSelector) : tagListSelector;
$selector.find(`.tag[id="${tag.id}"]`).remove();
}
if (tagElement) tagElement.remove();
$(`${getInlineListSelector()} .tag[id="${tag.id}"]`).remove();
return result;
}
/**
* Adds a tag from a given character. If no character is provided, adds it from the currently active one.
* @param {string} tagId - The id of the tag
* @param {string} characterId - The id/key of the character or group
* @returns {boolean} Whether the tag was added or not
*/
function addTagToMap(tagId, characterId = null) { function addTagToMap(tagId, characterId = null) {
const key = characterId !== null && characterId !== undefined ? getTagKeyForEntity(characterId) : getTagKey(); const key = characterId !== null && characterId !== undefined ? getTagKeyForEntity(characterId) : getTagKey();
if (!key) { if (!key) {
return; return false;
} }
if (!Array.isArray(tag_map[key])) { if (!Array.isArray(tag_map[key])) {
tag_map[key] = [tagId]; tag_map[key] = [tagId];
return true;
} }
else { else {
if (tag_map[key].includes(tagId))
return false;
tag_map[key].push(tagId); tag_map[key].push(tagId);
tag_map[key] = tag_map[key].filter(onlyUnique); tag_map[key] = tag_map[key].filter(onlyUnique);
return true;
} }
} }
/**
* Removes a tag from a given character. If no character is provided, removes it from the currently active one.
* @param {string} tagId - The id of the tag
* @param {string} characterId - The id/key of the character or group
* @returns {boolean} Whether the tag was removed or not
*/
function removeTagFromMap(tagId, characterId = null) { function removeTagFromMap(tagId, characterId = null) {
const key = characterId !== null && characterId !== undefined ? getTagKeyForEntity(characterId) : getTagKey(); const key = characterId !== null && characterId !== undefined ? getTagKeyForEntity(characterId) : getTagKey();
if (!key) { if (!key) {
return; return false;
} }
if (!Array.isArray(tag_map[key])) { if (!Array.isArray(tag_map[key])) {
tag_map[key] = []; tag_map[key] = [];
return false;
} }
else { else {
const indexOf = tag_map[key].indexOf(tagId); const indexOf = tag_map[key].indexOf(tagId);
tag_map[key].splice(indexOf, 1); tag_map[key].splice(indexOf, 1);
return indexOf !== -1;
} }
} }
@ -535,24 +624,7 @@ function selectTag(event, ui, listSelector, { tagListOptions = {} } = {}) {
const characterData = event.target.closest('#bulk_tags_div')?.dataset.characters; const characterData = event.target.closest('#bulk_tags_div')?.dataset.characters;
const characterIds = characterData ? JSON.parse(characterData).characterIds : null; const characterIds = characterData ? JSON.parse(characterData).characterIds : null;
if (characterIds) { addTagToEntity(tag, characterIds, { tagListSelector: listSelector, tagListOptions: tagListOptions });
characterIds.forEach((characterId) => addTagToMap(tag.id, characterId));
} else {
addTagToMap(tag.id);
}
printCharactersDebounced();
saveSettingsDebounced();
// We should manually add the selected tag to the print tag function, so we cover places where the tag list did not automatically include it
tagListOptions.addTag = tag;
// add tag to the UI and internal map - we reprint so sorting and new markup is done correctly
printTagList(listSelector, tagListOptions);
const inlineSelector = getInlineListSelector();
if (inlineSelector) {
printTagList($(inlineSelector), tagListOptions);
}
// need to return false to keep the input clear // need to return false to keep the input clear
return false; return false;
@ -635,6 +707,7 @@ function createNewTag(tagName) {
create_date: Date.now(), create_date: Date.now(),
}; };
tags.push(tag); tags.push(tag);
console.debug('Created new tag', tag.name, 'with id', tag.id);
return tag; return tag;
} }
@ -932,8 +1005,8 @@ function updateTagFilterIndicator() {
function onTagRemoveClick(event) { function onTagRemoveClick(event) {
event.stopPropagation(); event.stopPropagation();
const tag = $(this).closest('.tag'); const tagElement = $(this).closest('.tag');
const tagId = tag.attr('id'); const tagId = tagElement.attr('id');
// Check if we are inside the drilldown. If so, we call remove on the bogus folder // Check if we are inside the drilldown. If so, we call remove on the bogus folder
if ($(this).closest('.rm_tag_bogus_drilldown').length > 0) { if ($(this).closest('.rm_tag_bogus_drilldown').length > 0) {
@ -942,24 +1015,13 @@ function onTagRemoveClick(event) {
return; return;
} }
const tag = tags.find(t => t.id === tagId);
// Optional, check for multiple character ids being present. // Optional, check for multiple character ids being present.
const characterData = event.target.closest('#bulk_tags_div')?.dataset.characters; const characterData = event.target.closest('#bulk_tags_div')?.dataset.characters;
const characterIds = characterData ? JSON.parse(characterData).characterIds : null; const characterIds = characterData ? JSON.parse(characterData).characterIds : null;
tag.remove(); removeTagFromEntity(tag, characterIds, { tagElement: tagElement });
if (characterIds) {
characterIds.forEach((characterId) => removeTagFromMap(tagId, characterId));
} else {
removeTagFromMap(tagId);
}
$(`${getInlineListSelector()} .tag[id="${tagId}"]`).remove();
printCharactersDebounced();
saveSettingsDebounced();
} }
// @ts-ignore // @ts-ignore
@ -985,7 +1047,7 @@ function onGroupCreateClick() {
export function applyTagsOnCharacterSelect() { export function applyTagsOnCharacterSelect() {
//clearTagsFilter(); //clearTagsFilter();
const chid = Number($(this).attr('chid')); const chid = Number(this_chid);
printTagList($('#tagList'), { forEntityOrKey: chid, tagOptions: { removable: true } }); printTagList($('#tagList'), { forEntityOrKey: chid, tagOptions: { removable: true } });
} }
@ -1461,14 +1523,200 @@ function printViewTagList(empty = true) {
} }
} }
function registerTagsSlashCommands() {
/**
* Gets the key for char/group for a slash command. If none can be found, a toastr will be shown and null returned.
* @param {string?} [charName] The optionally provided char name
* @returns {string?} - The char/group key, or null if none found
*/
function paraGetCharKey(charName) {
const entity = charName
? (characters.find(x => x.name === charName) || groups.find(x => x.name == charName))
: (selected_group ? groups.find(x => x.id == selected_group) : characters[this_chid]);
const key = getTagKeyForEntity(entity);
if (!key) {
toastr.warning(`Character ${charName} not found.`);
return null;
}
return key;
}
/**
* Gets a tag by its name. Optionally can create the tag if it does not exist.
* @param {string} tagName - The name of the tag
* @param {object} options - Optional arguments
* @param {boolean} [options.allowCreate=false] - Whether a new tag should be created if no tag with the name exists
* @returns {Tag?} The tag, or null if not found
*/
function paraGetTag(tagName, { allowCreate = false } = {}) {
if (!tagName) {
toastr.warning('Tag name must be provided.');
return null;
}
let tag = tags.find(t => t.name === tagName);
if (allowCreate && !tag) {
tag = createNewTag(tagName);
}
if (!tag) {
toastr.warning(`Tag ${tagName} not found.`);
return null;
}
return tag;
}
function updateTagsList() {
switch (menu_type) {
case 'characters':
printTagFilters(tag_filter_types.character);
printTagFilters(tag_filter_types.group_member);
break;
case 'character_edit':
applyTagsOnCharacterSelect();
break;
case 'group_edit':
select_group_chats(selected_group, true);
break;
}
}
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'tag-add',
returns: 'true/false - Whether the tag was added or was assigned already',
/** @param {{name: string}} namedArgs @param {string} tagName @returns {string} */
callback: ({ name }, tagName) => {
const key = paraGetCharKey(name);
if (!key) return 'false';
const tag = paraGetTag(tagName, { allowCreate: true });
if (!tag) return 'false';
const result = addTagToEntity(tag, key);
updateTagsList();
return String(result);
},
namedArgumentList: [
new SlashCommandNamedArgument('name', 'Character name', [ARGUMENT_TYPE.STRING], false, false, '{{char}}'),
],
unnamedArgumentList: [
new SlashCommandArgument('tag name', [ARGUMENT_TYPE.STRING], true),
],
helpString: `
<div>
Adds a tag to the character. If no character is provided, it adds it to the current character (<code>{{char}}</code>).
If the tag doesn't exist, it is created.
</div>
<div>
<strong>Example:</strong>
<ul>
<li>
<pre><code>/tag-add name="Chloe" scenario</code></pre>
will add the tag "scenario" to the character named Chloe.
</li>
</ul>
</div>
`,
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'tag-remove',
returns: 'true/false - Whether the tag was removed or wasn\'t assigned already',
/** @param {{name: string}} namedArgs @param {string} tagName @returns {string} */
callback: ({ name }, tagName) => {
const key = paraGetCharKey(name);
if (!key) return 'false';
const tag = paraGetTag(tagName);
if (!tag) return 'false';
const result = removeTagFromEntity(tag, key);
updateTagsList();
return String(result);
},
namedArgumentList: [
new SlashCommandNamedArgument('name', 'Character name', [ARGUMENT_TYPE.STRING], false, false, '{{char}}'),
],
unnamedArgumentList: [
new SlashCommandArgument('tag name', [ARGUMENT_TYPE.STRING], true),
],
helpString: `
<div>
Removes a tag from the character. If no character is provided, it removes it from the current character (<code>{{char}}</code>).
</div>
<div>
<strong>Example:</strong>
<ul>
<li>
<pre><code>/tag-remove name="Chloe" scenario</code></pre>
will remove the tag "scenario" from the character named Chloe.
</li>
</ul>
</div>
`,
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'tag-exists',
returns: 'true/false - Whether the given tag name is assigned to the character',
/** @param {{name: string}} namedArgs @param {string} tagName @returns {string} */
callback: ({ name }, tagName) => {
const key = paraGetCharKey(name);
if (!key) return 'false';
const tag = paraGetTag(tagName);
if (!tag) return 'false';
return String(tag_map[key].includes(tag.id));
},
namedArgumentList: [
new SlashCommandNamedArgument('name', 'Character name', [ARGUMENT_TYPE.STRING], false, false, '{{char}}'),
],
unnamedArgumentList: [
new SlashCommandArgument('tag name', [ARGUMENT_TYPE.STRING], true),
],
helpString: `
<div>
Checks whether the given tag is assigned to the character. If no character is provided, it checks the current character (<code>{{char}}</code>).
</div>
<div>
<strong>Example:</strong>
<ul>
<li>
<pre><code>/tag-exists name="Chloe" scenario</code></pre>
will return true if the character named Chloe has the tag "scenario".
</li>
</ul>
</div>
`,
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'tag-list',
returns: 'Comma-separated list of all assigned tags',
/** @param {{name: string}} namedArgs @returns {string} */
callback: ({ name }) => {
const key = paraGetCharKey(name);
if (!key) return '';
const tags = getTagsList(key);
return tags.map(x => x.name).join(', ');
},
namedArgumentList: [
new SlashCommandNamedArgument('name', 'Character name', [ARGUMENT_TYPE.STRING], false, false, '{{char}}'),
],
helpString: `
<div>
Lists all assigned tags of the character. If no character is provided, it uses the current character (<code>{{char}}</code>).
<br />
Note that there is no special handling for tags containing commas, they will be printed as-is.
</div>
<div>
<strong>Example:</strong>
<ul>
<li>
<pre><code>/tag-list name="Chloe"</code></pre>
could return something like <code>OC, scenario, edited, funny</code>
</li>
</ul>
</div>
`,
}));
}
export function initTags() { export function initTags() {
createTagInput('#tagInput', '#tagList', { tagOptions: { removable: true } }); createTagInput('#tagInput', '#tagList', { tagOptions: { removable: true } });
createTagInput('#groupTagInput', '#groupTagList', { tagOptions: { removable: true } }); createTagInput('#groupTagInput', '#groupTagList', { tagOptions: { removable: true } });
$(document).on('click', '#rm_button_create', onCharacterCreateClick); $(document).on('click', '#rm_button_create', onCharacterCreateClick);
$(document).on('click', '#rm_button_group_chats', onGroupCreateClick); $(document).on('click', '#rm_button_group_chats', onGroupCreateClick);
$(document).on('click', '.character_select', applyTagsOnCharacterSelect);
$(document).on('click', '.group_select', applyTagsOnGroupSelect);
$(document).on('click', '.tag_remove', onTagRemoveClick); $(document).on('click', '.tag_remove', onTagRemoveClick);
$(document).on('input', '.tag_input', onTagInput); $(document).on('input', '.tag_input', onTagInput);
$(document).on('click', '.tags_view', onViewTagsListClick); $(document).on('click', '.tags_view', onViewTagsListClick);
@ -1479,6 +1727,7 @@ export function initTags() {
$(document).on('click', '.tag_view_backup', onTagsBackupClick); $(document).on('click', '.tag_view_backup', onTagsBackupClick);
$(document).on('click', '.tag_view_restore', onBackupRestoreClick); $(document).on('click', '.tag_view_restore', onBackupRestoreClick);
eventSource.on(event_types.CHARACTER_DUPLICATED, copyTags); eventSource.on(event_types.CHARACTER_DUPLICATED, copyTags);
eventSource.makeFirst(event_types.CHAT_CHANGED, () => selected_group ? applyTagsOnGroupSelect() : applyTagsOnCharacterSelect());
$(document).on('input', '#dialogue_popup input[name="auto_sort_tags"]', (evt) => { $(document).on('input', '#dialogue_popup input[name="auto_sort_tags"]', (evt) => {
const toggle = $(evt.target).is(':checked'); const toggle = $(evt.target).is(':checked');
@ -1506,4 +1755,6 @@ export function initTags() {
printCharactersDebounced(); printCharactersDebounced();
} }
} }
registerTagsSlashCommands();
} }

View File

@ -11,9 +11,10 @@
<span data-i18n="Click ">Click </span><code><i class="fa-solid fa-plug"></i></code><span data-i18n="and select a"> and select a </span><a href="https://docs.sillytavern.app/usage/api-connections/" target="_blank" data-i18n="Chat API">Chat API</a>.</span> <span data-i18n="Click ">Click </span><code><i class="fa-solid fa-plug"></i></code><span data-i18n="and select a"> and select a </span><a href="https://docs.sillytavern.app/usage/api-connections/" target="_blank" data-i18n="Chat API">Chat API</a>.</span>
</li> </li>
<li> <li>
<span data-i18n="Click ">Click </span><code><i class="fa-solid fa-address-card"></i></code><span data-i18n="and pick a character"> and pick a character</span> <span data-i18n="Click ">Click </span><code><i class="fa-solid fa-address-card"></i></code><span data-i18n="and pick a character."> and pick a character.</span>
</li> </li>
</ol> </ol>
<span data-i18n="You can browse a list of bundled characters in the Download Extensions & Assets menu within">You can browse a list of bundled characters in the <i>Download Extensions & Assets</i> menu within </span><code><i class="fa-solid fa-cubes"></i></code><span>.</span>
<hr> <hr>
<h3 data-i18n="Confused or lost?">Confused or lost?</h3> <h3 data-i18n="Confused or lost?">Confused or lost?</h3>
<ul> <ul>

View File

@ -991,7 +991,7 @@ export function getTextGenModel() {
} }
export function isJsonSchemaSupported() { export function isJsonSchemaSupported() {
return settings.type === TABBY && main_api === 'textgenerationwebui'; return [TABBY, LLAMACPP].includes(settings.type) && main_api === 'textgenerationwebui';
} }
export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate, isContinue, cfgValues, type) { export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate, isContinue, cfgValues, type) {
@ -1065,7 +1065,7 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate,
'guidance_scale': cfgValues?.guidanceScale?.value ?? settings.guidance_scale ?? 1, 'guidance_scale': cfgValues?.guidanceScale?.value ?? settings.guidance_scale ?? 1,
'negative_prompt': cfgValues?.negativePrompt ?? substituteParams(settings.negative_prompt) ?? '', 'negative_prompt': cfgValues?.negativePrompt ?? substituteParams(settings.negative_prompt) ?? '',
'grammar_string': settings.grammar_string, 'grammar_string': settings.grammar_string,
'json_schema': settings.type === TABBY ? settings.json_schema : undefined, 'json_schema': [TABBY, LLAMACPP].includes(settings.type) ? settings.json_schema : undefined,
// llama.cpp aliases. In case someone wants to use LM Studio as Text Completion API // llama.cpp aliases. In case someone wants to use LM Studio as Text Completion API
'repeat_penalty': settings.rep_pen, 'repeat_penalty': settings.rep_pen,
'tfs_z': settings.tfs, 'tfs_z': settings.tfs,
@ -1150,5 +1150,15 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate,
eventSource.emitAndWait(event_types.TEXT_COMPLETION_SETTINGS_READY, params); eventSource.emitAndWait(event_types.TEXT_COMPLETION_SETTINGS_READY, params);
// Grammar conflicts with with json_schema
if (settings.type === LLAMACPP) {
if (params.json_schema && Object.keys(params.json_schema).length > 0) {
delete params.grammar_string;
delete params.grammar;
} else {
delete params.json_schema;
}
}
return params; return params;
} }

View File

@ -1,5 +1,5 @@
import { saveSettings, callPopup, substituteParams, getRequestHeaders, chat_metadata, this_chid, characters, saveCharacterDebounced, menu_type, eventSource, event_types, getExtensionPromptByName, saveMetadata, getCurrentChatId, extension_prompt_roles } from '../script.js'; import { saveSettings, callPopup, substituteParams, getRequestHeaders, chat_metadata, this_chid, characters, saveCharacterDebounced, menu_type, eventSource, event_types, getExtensionPromptByName, saveMetadata, getCurrentChatId, extension_prompt_roles } from '../script.js';
import { download, debounce, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, getCharaFilename, getSortableDelay, escapeRegex, PAGINATION_TEMPLATE, navigation_option, waitUntilCondition, isTrueBoolean, setValueByPath, flashHighlight, select2ModifyOptions, getSelect2OptionId, dynamicSelect2DataViaAjax, highlightRegex, select2ChoiceClickSubscribe } from './utils.js'; import { download, debounce, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, getCharaFilename, getSortableDelay, escapeRegex, PAGINATION_TEMPLATE, navigation_option, waitUntilCondition, isTrueBoolean, setValueByPath, flashHighlight, select2ModifyOptions, getSelect2OptionId, dynamicSelect2DataViaAjax, highlightRegex, select2ChoiceClickSubscribe, isFalseBoolean } from './utils.js';
import { extension_settings, getContext } from './extensions.js'; import { extension_settings, getContext } from './extensions.js';
import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from './authors-note.js'; import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from './authors-note.js';
import { isMobile } from './RossAscends-mods.js'; import { isMobile } from './RossAscends-mods.js';
@ -548,6 +548,19 @@ function registerWorldInfoSlashCommands() {
return ''; return '';
} }
if (typeof newEntryTemplate[field] === 'boolean') {
const isTrue = isTrueBoolean(value);
const isFalse = isFalseBoolean(value);
if (isTrue) {
value = String(true);
}
if (isFalse) {
value = String(false);
}
}
const fuse = new Fuse(entries, { const fuse = new Fuse(entries, {
keys: [{ name: field, weight: 1 }], keys: [{ name: field, weight: 1 }],
includeScore: true, includeScore: true,
@ -1244,6 +1257,7 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
} }
worldEntriesList.sortable({ worldEntriesList.sortable({
items: '.world_entry',
delay: getSortableDelay(), delay: getSortableDelay(),
handle: '.drag-handle', handle: '.drag-handle',
stop: async function (_event, _ui) { stop: async function (_event, _ui) {

View File

@ -2068,6 +2068,7 @@ input[type="file"] {
gap: 5px; gap: 5px;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
flex-wrap: wrap;
} }
.bulk_select_checkbox { .bulk_select_checkbox {

View File

@ -7,7 +7,9 @@ const { getConfigValue, color } = require('../util');
const { jsonParser } = require('../express-common'); const { jsonParser } = require('../express-common');
const writeFileAtomicSync = require('write-file-atomic').sync; const writeFileAtomicSync = require('write-file-atomic').sync;
const contentDirectory = path.join(process.cwd(), 'default/content'); const contentDirectory = path.join(process.cwd(), 'default/content');
const scaffoldDirectory = path.join(process.cwd(), 'default/scaffold');
const contentIndexPath = path.join(contentDirectory, 'index.json'); const contentIndexPath = path.join(contentDirectory, 'index.json');
const scaffoldIndexPath = path.join(scaffoldDirectory, 'index.json');
const characterCardParser = require('../character-card-parser.js'); const characterCardParser = require('../character-card-parser.js');
const WHITELIST_GENERIC_URL_DOWNLOAD_SOURCES = getConfigValue('whitelistImportDomains', []); const WHITELIST_GENERIC_URL_DOWNLOAD_SOURCES = getConfigValue('whitelistImportDomains', []);
@ -16,6 +18,8 @@ const WHITELIST_GENERIC_URL_DOWNLOAD_SOURCES = getConfigValue('whitelistImportDo
* @typedef {Object} ContentItem * @typedef {Object} ContentItem
* @property {string} filename * @property {string} filename
* @property {string} type * @property {string} type
* @property {string} [name]
* @property {string|null} [folder]
*/ */
/** /**
@ -48,9 +52,7 @@ const CONTENT_TYPES = {
*/ */
function getDefaultPresets(directories) { function getDefaultPresets(directories) {
try { try {
const contentIndexText = fs.readFileSync(contentIndexPath, 'utf8'); const contentIndex = getContentIndex();
const contentIndex = JSON.parse(contentIndexText);
const presets = []; const presets = [];
for (const contentItem of contentIndex) { for (const contentItem of contentIndex) {
@ -112,8 +114,12 @@ async function seedContentForUser(contentIndex, directories, forceCategories) {
continue; continue;
} }
contentLog.push(contentItem.filename); if (!contentItem.folder) {
const contentPath = path.join(contentDirectory, contentItem.filename); console.log(`Content file ${contentItem.filename} has no parent folder`);
continue;
}
const contentPath = path.join(contentItem.folder, contentItem.filename);
if (!fs.existsSync(contentPath)) { if (!fs.existsSync(contentPath)) {
console.log(`Content file ${contentItem.filename} is missing`); console.log(`Content file ${contentItem.filename} is missing`);
@ -129,6 +135,7 @@ async function seedContentForUser(contentIndex, directories, forceCategories) {
const basePath = path.parse(contentItem.filename).base; const basePath = path.parse(contentItem.filename).base;
const targetPath = path.join(contentTarget, basePath); const targetPath = path.join(contentTarget, basePath);
contentLog.push(contentItem.filename);
if (fs.existsSync(targetPath)) { if (fs.existsSync(targetPath)) {
console.log(`Content file ${contentItem.filename} already exists in ${contentTarget}`); console.log(`Content file ${contentItem.filename} already exists in ${contentTarget}`);
@ -157,8 +164,7 @@ async function checkForNewContent(directoriesList, forceCategories = []) {
return; return;
} }
const contentIndexText = fs.readFileSync(contentIndexPath, 'utf8'); const contentIndex = getContentIndex();
const contentIndex = JSON.parse(contentIndexText);
let anyContentAdded = false; let anyContentAdded = false;
for (const directories of directoriesList) { for (const directories of directoriesList) {
@ -179,6 +185,38 @@ async function checkForNewContent(directoriesList, forceCategories = []) {
} }
} }
/**
* Gets combined content index from the content and scaffold directories.
* @returns {ContentItem[]} Array of content index
*/
function getContentIndex() {
const result = [];
if (fs.existsSync(scaffoldIndexPath)) {
const scaffoldIndexText = fs.readFileSync(scaffoldIndexPath, 'utf8');
const scaffoldIndex = JSON.parse(scaffoldIndexText);
if (Array.isArray(scaffoldIndex)) {
scaffoldIndex.forEach((item) => {
item.folder = scaffoldDirectory;
});
result.push(...scaffoldIndex);
}
}
if (fs.existsSync(contentIndexPath)) {
const contentIndexText = fs.readFileSync(contentIndexPath, 'utf8');
const contentIndex = JSON.parse(contentIndexText);
if (Array.isArray(contentIndex)) {
contentIndex.forEach((item) => {
item.folder = contentDirectory;
});
result.push(...contentIndex);
}
}
return result;
}
/** /**
* Gets the target directory for the specified asset type. * Gets the target directory for the specified asset type.
* @param {ContentType} type Asset type * @param {ContentType} type Asset type