Merge branch 'staging' into generate-array
This commit is contained in:
commit
44661d0e2b
|
@ -32,3 +32,4 @@ public/movingUI/
|
|||
public/QuickReplies/
|
||||
content.log
|
||||
cloudflared.exe
|
||||
public/assets/
|
|
@ -139,8 +139,10 @@
|
|||
"system_prompt": "Avoid repetition, don't loop. Develop the plot slowly, always stay in character. Describe all actions in full, elaborate, explicit, graphic, and vivid detail. Mention all relevant sensory perceptions.",
|
||||
"input_sequence": "\n### Instruction:",
|
||||
"output_sequence": "\n### Response:",
|
||||
"first_output_sequence": "",
|
||||
"last_output_sequence": "\n### Response (2 paragraphs, engaging, natural, authentic, descriptive, creative):",
|
||||
"system_sequence": "",
|
||||
"system_sequence_prefix": "",
|
||||
"system_sequence_suffix": "",
|
||||
"stop_sequence": "",
|
||||
"separator_sequence": "",
|
||||
"wrap": true,
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Put ambient audio files here.
|
|
@ -0,0 +1 @@
|
|||
Put bgm audio files here
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "Minimalist",
|
||||
"story_string": "{{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{personality}}\n{{/if}}{{#if scenario}}{{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}",
|
||||
"chat_start": "###",
|
||||
"example_separator": "###"
|
||||
}
|
|
@ -205,7 +205,7 @@
|
|||
width: 100%;
|
||||
border-radius: 0 0 20px 20px;
|
||||
margin-top: 0px;
|
||||
height: calc(100% - 40px);
|
||||
height: calc(100% - var(--topBarBlockSize));
|
||||
}
|
||||
|
||||
.drawer25pWidth {
|
||||
|
|
|
@ -94,7 +94,7 @@
|
|||
|
||||
#completion_prompt_manager_popup .completion_prompt_manager_popup_entry {
|
||||
padding: 1em;
|
||||
margin-top:2em;
|
||||
margin-top: 2em;
|
||||
}
|
||||
|
||||
#completion_prompt_manager_popup #completion_prompt_manager_popup_inspect .completion_prompt_manager_popup_entry {
|
||||
|
@ -123,7 +123,7 @@
|
|||
}
|
||||
|
||||
.completion_prompt_manager_popup_entry_form_control {
|
||||
margin-top:1em;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
#prompt-manager-reset-character,
|
||||
|
@ -236,13 +236,13 @@
|
|||
font-size: 16px;
|
||||
}
|
||||
|
||||
#prompt-manager-export-format-popup {
|
||||
#prompt-manager-export-format-popup {
|
||||
padding: 0.25em;
|
||||
display:none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#prompt-manager-export-format-popup[data-show] {
|
||||
display:block;
|
||||
#prompt-manager-export-format-popup[data-show] {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#completion_prompt_manager_popup {
|
||||
|
@ -251,24 +251,24 @@
|
|||
|
||||
#completion_prompt_manager_popup {
|
||||
overflow-y: auto;
|
||||
height: calc(100% - 40px);
|
||||
height: calc(100% - var(--topBarBlockSize));
|
||||
position: absolute;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 40px;
|
||||
top: var(--topBarBlockSize);
|
||||
box-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
|
||||
padding: 1em;
|
||||
border: 1px solid #333333;
|
||||
flex-direction: column;
|
||||
z-index: 3010 !important;
|
||||
border-radius: 0 0 20px 20px;
|
||||
background-color: var(--SmartThemeBlurTintColor);
|
||||
background-color: var(--SmartThemeBlurTintColor);
|
||||
}
|
||||
|
||||
#prompt-manager-export-format-popup {
|
||||
display:none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.prompt-manager-export-format-popup-flex {
|
||||
|
@ -296,7 +296,8 @@
|
|||
#completion_prompt_manager_popup {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
#completion_prompt_manager #completion_prompt_manager_list li.completion_prompt_manager_prompt span span span {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
}
|
||||
}
|
172
public/i18n.json
172
public/i18n.json
|
@ -4,7 +4,7 @@
|
|||
"ja-jp",
|
||||
"ko-kr",
|
||||
"ru-ru",
|
||||
"it-it",
|
||||
"it-it",
|
||||
"nl-nl"
|
||||
],
|
||||
"zh-cn": {
|
||||
|
@ -140,9 +140,13 @@
|
|||
"Wrap Sequences with Newline": "用换行符换行序列",
|
||||
"Include Names": "包括名称",
|
||||
"System Prompt": "系统提示",
|
||||
"Instruct Mode Sequences": "指令模式序列",
|
||||
"Input Sequence": "输入序列",
|
||||
"Output Sequence": "输出序列",
|
||||
"System Sequence": "系统顺序",
|
||||
"First Output Sequence": "第一个输出序列",
|
||||
"Last Output Sequence": "最后输出序列",
|
||||
"System Sequence Prefix": "系统序列前缀",
|
||||
"System Sequence Suffix": "系统序列后缀",
|
||||
"Stop Sequence": "停止序列",
|
||||
"Context Formatting": "上下文格式",
|
||||
"Tokenizer": "分词器",
|
||||
|
@ -693,9 +697,12 @@
|
|||
"Wrap Sequences with Newline": "シーケンスを改行でラップする",
|
||||
"Include Names": "名前を含める",
|
||||
"System Prompt": "システムプロンプト",
|
||||
"Instruct Mode Sequences": "命令モードシーケンス",
|
||||
"Input Sequence": "入力シーケンス",
|
||||
"Output Sequence": "出力シーケンス",
|
||||
"System Sequence": "システムシーケンス",
|
||||
"First Output Sequence": "最初の出力シーケンス",
|
||||
"Last Output Sequence": "最後の出力シーケンス",
|
||||
"System Sequence Prefix": "システムシーケンスプレフィックス",
|
||||
"System Sequence Suffix": "システムシーケンスサフィックス",
|
||||
"Stop Sequence": "停止シーケンス",
|
||||
"Context Formatting": "コンテキストフォーマッティング",
|
||||
"Tokenizer": "トークナイザー",
|
||||
|
@ -1248,9 +1255,12 @@
|
|||
"Wrap Sequences with Newline": "배열 명령 양 끝에 줄바꿈 삽입",
|
||||
"Include Names": "이름 포함",
|
||||
"System Prompt": "시스템 프롬프트",
|
||||
"Instruct Mode Sequences": "지시 모드 순서",
|
||||
"Input Sequence": "입력 배열",
|
||||
"Output Sequence": "출력 배열",
|
||||
"System Sequence": "시스템 배열",
|
||||
"First Output Sequence": "첫 번째 출력 시퀀스",
|
||||
"Last Output Sequence": "마지막 출력 순서",
|
||||
"System Sequence Prefix": "시스템 시퀀스 접두사",
|
||||
"System Sequence Suffix": "시스템 시퀀스 접미사",
|
||||
"Stop Sequence": "정지 배열",
|
||||
"Context Formatting": "맥락 서식",
|
||||
"Tokenizer": "토큰화 장치",
|
||||
|
@ -1807,9 +1817,12 @@
|
|||
"Wrap Sequences with Newline": "Отделять последовательности красной строкой",
|
||||
"Include Names": "Показывать имена",
|
||||
"System Prompt": "Системная инструкция",
|
||||
"Instruct Mode Sequences": "Последовательности режима обучения",
|
||||
"Input Sequence": "Input Sequence",
|
||||
"Output Sequence": "Output Sequence",
|
||||
"System Sequence": "System Sequence",
|
||||
"First Output Sequence": "Первая выходная последовательность",
|
||||
"Last Output Sequence": "Последняя выходная последовательность",
|
||||
"System Sequence Prefix": "Префикс системной последовательности",
|
||||
"System Sequence Suffix": "Суффикс системной последовательности",
|
||||
"Stop Sequence": "Stop Sequence",
|
||||
"Context Formatting": "Форматирование контекста",
|
||||
"Tokenizer": "Токенайзер",
|
||||
|
@ -2363,9 +2376,12 @@
|
|||
"Wrap Sequences with Newline": "Ogni sequenza viene rimandata a capo",
|
||||
"Include Names": "Includi i nomi",
|
||||
"System Prompt": "Prompt di sistema",
|
||||
"Instruct Mode Sequences": "Sequenze in modalità istruzione",
|
||||
"Input Sequence": "Sequenza di input",
|
||||
"Output Sequence": "Sequenza di output",
|
||||
"System Sequence": "Sequenza di sistema",
|
||||
"First Output Sequence": "Prima sequenza di output",
|
||||
"Last Output Sequence": "Ultima sequenza di output",
|
||||
"System Sequence Prefix": "Prefisso sequenza di sistema",
|
||||
"System Sequence Suffix": "Suffisso della sequenza del sistema",
|
||||
"Stop Sequence": "Sequenza d'arresto",
|
||||
"Context Formatting": "Formattazione del contesto",
|
||||
"Tokenizer": "Tokenizer",
|
||||
|
@ -2782,7 +2798,134 @@
|
|||
"Bind user name to that avatar": "Lega il nome utente a questo avatar",
|
||||
"Select this as default persona for the new chats.": "Seleziona questo alterego come predefinito per tutte le nuove chat",
|
||||
"Change persona image": "Cambia l'immagine del tuo alterego",
|
||||
"Delete persona": "Elimina il tuo alterego"
|
||||
"Delete persona": "Elimina il tuo alterego",
|
||||
"--- Pick to Edit ---": "--- Scegli per modificare ---",
|
||||
"Add text here that would make the AI generate things you don't want in your outputs.": "Scrivi qui ciò che non vuoi l'IA generi nel suo output.",
|
||||
"write short replies, write replies using past tense": "Scrivi risposte brevi, scrivi risposte usando il passato",
|
||||
"Alert if your world info is greater than the allocated budget.": "Questo avvisa nel momento in cui le 'Info Mondo' consumano più di quanto allocato nel budget.",
|
||||
"Clear your cookie": "Cancella i cookie",
|
||||
"Restore new group chat prompt": "Ripristina il prompt della nuova chat di gruppo",
|
||||
"Save movingUI changes to a new file": "Salva i cambiamenti apportati alla posizione dei pannelli dell'UI (MovingUI) in un nuovo file",
|
||||
"Export all": "Esporta tutto",
|
||||
"Import": "Importa",
|
||||
"Insert": "Inserisci",
|
||||
"New": "Nuovo",
|
||||
"Prompts": "Prompt",
|
||||
"Tokens": "Token",
|
||||
"Reset current character": "Ripristina il personaggio attuale",
|
||||
"(0 = disabled)": "(0 = disabilitato)",
|
||||
"1 = disabled": "1 = disabilitato",
|
||||
"Activation Regex": "Attivazione Regex",
|
||||
"Active World(s) for all chats": "Attiva i Mondi per tutte le chat",
|
||||
"Add character names": "Aggiungi i nomi dei personaggi",
|
||||
"Add Memo": "Aggiungi note",
|
||||
"Advanced Character Search": "Ricerca dei personaggi avanzata",
|
||||
"Aggressive": "Aggressivo",
|
||||
"AI21 Model": "Modello AI21",
|
||||
"Alert On Overflow": "Avviso in caso di Overflow",
|
||||
"Allow fallback routes": "Permetti fallback routes",
|
||||
"Allow fallback routes Description": "Permetti la descrizione di fallback routes",
|
||||
"Alt Method": "Metodo Alt",
|
||||
"Alternate Greetings": "Alterna i saluti",
|
||||
"Alternate Greetings Hint": "Suggerimenti per i saluti alternati",
|
||||
"Alternate Greetings Subtitle": "Sottotitoli per i saluti alternati",
|
||||
"Assistant Prefill": "Assistant Prefill",
|
||||
"Banned Tokens": "Token banditi",
|
||||
"Blank": "In bianco",
|
||||
"Browser default": "Predefinito del browser",
|
||||
"Budget Cap": "Limite budget",
|
||||
"CFG": "CFG",
|
||||
"CFG Scale": "CFG Scale",
|
||||
"Changes the style of the generated text.": "Cambia lo stile del testo generato.",
|
||||
"Character Negatives": "Character Negatives",
|
||||
"Chat Negatives": "Chat Negatives",
|
||||
"Chat Scenario Override": "Sovrascrittura dello scenario della chat",
|
||||
"Chat Start": "Avvio della chat",
|
||||
"Claude Model": "Modello Claude",
|
||||
"Close chat": "Chiudi chat",
|
||||
"Context %": "Context %",
|
||||
"Context Template": "Context Template",
|
||||
"Count Penalty": "Count Penalty",
|
||||
"Example Separator": "Separatore d'esempio",
|
||||
"Exclude Assistant suffix": "Escludi il suffisso assistente",
|
||||
"Exclude the assistant suffix from being added to the end of prompt.": "Esclude il suffisso assistente dall'essere aggiunto alla fine del prompt.",
|
||||
"Force for Groups and Personas": "Forzalo per gruppi e alterego",
|
||||
"Global Negatives": "Global Negatives",
|
||||
"In Story String / Chat Completion: After Character Card": "Nella stringa narrativa / Chat Completion: Dopo la 'Carta Personaggio'",
|
||||
"In Story String / Chat Completion: Before Character Card": "Nella stringa narrativa / Chat Completion: Prima della 'Carta Personaggio",
|
||||
"Instruct": "Instruct",
|
||||
"Instruct Mode": "Modalità Instruct",
|
||||
"Last Sequence": "Ultima sequenza",
|
||||
"Lazy Chat Loading": "Caricamento svogliato della chat",
|
||||
"Least tokens": "Token minimi",
|
||||
"Light": "Leggero",
|
||||
"Load koboldcpp order": "Ripristina l'ordine di koboldcpp",
|
||||
"Main": "Principale",
|
||||
"Mancer API key": "Chiave API di Mancer",
|
||||
"Mancer API url": "Url API di Mancer",
|
||||
"May help the model to understand context. Names must only contain letters or numbers.": "Può aiutare il modello a comprendere meglio il contesto. I nomi devono contenere solo numeri e lettere.",
|
||||
"Medium": "Medium",
|
||||
"Mirostat": "Mirostat",
|
||||
"Mirostat (mode=1 is only for llama.cpp)": "Mirostat (mode=1 è valido solo per llama.cpp)",
|
||||
"Mirostat Eta": "Mirostat Eta",
|
||||
"Mirostat LR": "Mirostat LR",
|
||||
"Mirostat Mode": "Mirostat Mode",
|
||||
"Mirostat Tau": "Mirostat Tau",
|
||||
"Model Icon": "Icona del modello",
|
||||
"Most tokens": "Token massimi",
|
||||
"MovingUI Preset": "Preset MovingUI",
|
||||
"Negative Prompt": "Prompt negativo",
|
||||
"No Module": "Nessun modulo",
|
||||
"NSFW": "NSFW",
|
||||
"Nucleus Sampling": "Nucleus Sampling",
|
||||
"Off": "Spento",
|
||||
"OpenRouter API Key": "Chiave API di OpenRouter",
|
||||
"OpenRouter Model": "Modello OpenRouter",
|
||||
"or": "o",
|
||||
"Phrase Repetition Penalty": "Phrase Repetition Penalty",
|
||||
"Positive Prompt": "Prompt positivo",
|
||||
"Preamble": "Premessa",
|
||||
"Prompt Overrides (For OpenAI/Claude/Scale APIs, Window/OpenRouter, and Instruct Mode)": "Sovrascrittura del prompt (Per le API di OpenAI/Claude/Scale, Window/OpenRouter, e la Modalità Instruct)",
|
||||
"Prompt that is used when the NSFW toggle is O": "Prompt utilizzato quando l'interruttore NSFW è disattivato.",
|
||||
"Prose Augmenter": "Prose Augmenter",
|
||||
"Proxy Password": "Password proxy",
|
||||
"Quick Edit": "Editing rapido",
|
||||
"Random": "Casuale",
|
||||
"Relaxed API URLS": "URL API sciatto",
|
||||
"Replace Macro in Custom Stopping Strings": "Rimpiazza le macro nelle stringe d'arresto personalizzate",
|
||||
"Scale": "Scale",
|
||||
"Scale": "Scale",
|
||||
"Sequences you don't want to appear in the output. One per line.": "Sequenze che non vuoi appaiano nell'output. Una per linea.",
|
||||
"Set at the beginning of Dialogue examples to indicate that a new example chat is about to start.": "Impostato all'inizio degli Esempi di dialogo per indicare che un nuovo esempio di chat sta per iniziare.",
|
||||
"Set at the beginning of the chat history to indicate that a new chat is about to start.": "Impostato all'inizio degli esempi di dialogo per indicare che una nuova chat sta per iniziare.",
|
||||
"Set at the beginning of the chat history to indicate that a new chat is about to start.": "Impostato all'inizio della cronologia chat per indicare che una nuova chat sta per iniziare.",
|
||||
"Set at the beginning of the chat history to indicate that a new group chat is about to start.": "Impostato all'inizio della cronologia chat per indicare che un nuova chat di gruppo sta per iniziare.",
|
||||
"Show External models (provided by API)": "Mostra modelli esterni (Forniti dall'API)",
|
||||
"Show Notifications Show notifications on switching personas": "Mostra una notifica quando l'alterego viene cambiato",
|
||||
"Show tags in responses": "Mostra i tag nelle risposte",
|
||||
"Story String": "Stringa narrativa",
|
||||
"Text Adventure": "Avventura testuale",
|
||||
"Text Gen WebUI (ooba/Mancer) presets": "Preset Text Gen WebUI (ooba/Mancer)",
|
||||
"Toggle Panels": "Interruttore pannelli",
|
||||
"Top A Sampling": "Top A Sampling",
|
||||
"Top K Sampling": "Top K Sampling",
|
||||
"UI Language": "Linguaggio interfaccia grafica",
|
||||
"Unlocked Context Size": "Sblocca dimensione contesto",
|
||||
"Usage Stats": "Statistiche di utilizzo",
|
||||
"Use AI21 Tokenizer": "Utilizza il Tokenizer di AI21",
|
||||
"Use API key (Only required for Mancer)": "Utilizza la chiave API (Necessario soltanto per Mancer)",
|
||||
"Use character author's note": "Utilizza le note d'autore del personaggio",
|
||||
"Use character CFG scales": "Utilizza CFG scales del personaggio",
|
||||
"Use Proxy password field instead. This input will be ignored.": "Utilizza il campo del password proxy al suo posto. Questo input verrà ignorato.",
|
||||
"Use style tags to modify the writing style of the output": "Utilizza lo stile delle tag per modificare lo stile di scrittura in output",
|
||||
"Use the appropriate tokenizer for Jurassic models, which is more efficient than GPT's.": "Utilizza il tokenizer appropiato per i modelli giurassici, visto che è più efficente di quello di GPT.",
|
||||
"Used if CFG Scale is unset globally, per chat or character": "Usato se CFG Scale non è settato globalmente, per le chat o per i personaggi",
|
||||
"Very aggressive": "Esageratamente aggressivo",
|
||||
"Very light": "Esageratamente leggero",
|
||||
"Welcome to SillyTavern!": "Benvenuto in SillyTavern!",
|
||||
"Will be used as a password for the proxy instead of API key.": "Verrà usato come password per il proxy invece che la chiave API.",
|
||||
"Window AI Model": "Modello Window AI",
|
||||
"Your Persona": "Il tuo alterego"
|
||||
},
|
||||
"nl-nl": {
|
||||
"clickslidertips": "klikregel tips",
|
||||
|
@ -2917,9 +3060,12 @@
|
|||
"Wrap Sequences with Newline": "Wikkel sequenties in met een nieuwe regel",
|
||||
"Include Names": "Inclusief namen",
|
||||
"System Prompt": "Systeemprompt",
|
||||
"Instruct Mode Sequences": "Instrueermodusreeksen",
|
||||
"Input Sequence": "Invoersequentie",
|
||||
"Output Sequence": "Uitvoersequentie",
|
||||
"System Sequence": "Systeemsequentie",
|
||||
"First Output Sequence": "Eerste uitvoerreeks",
|
||||
"Last Output Sequence": "Laatste uitvoerreeks",
|
||||
"System Sequence Prefix": "Systeemreeksvoorvoegsel",
|
||||
"System Sequence Suffix": "Systeemreeksachtervoegsel",
|
||||
"Stop Sequence": "Stopsequentie",
|
||||
"Context Formatting": "Contextopmaak",
|
||||
"Tokenizer": "Tokenizer",
|
||||
|
|
|
@ -1023,7 +1023,7 @@
|
|||
</div>
|
||||
<div class="range-block-range-and-counter">
|
||||
<div class="range-block-range">
|
||||
<input type="range" id="typical_p_novel" name="volume" min="0" max="1" step="0.01">
|
||||
<input type="range" id="typical_p_novel" name="volume" min="0" max="1" step="0.001">
|
||||
</div>
|
||||
<div class="range-block-counter">
|
||||
<div contenteditable="true" data-for="typical_p_novel" id="typical_p_counter_novel">
|
||||
|
@ -2098,14 +2098,14 @@
|
|||
<label for="context_story_string">
|
||||
<small data-i18n="Story String">Story String</small>
|
||||
</label>
|
||||
<textarea id="context_story_string" class="text_pole textarea_compact" rows="6"></textarea>
|
||||
<textarea id="context_story_string" class="text_pole textarea_compact autoSetHeight" rows="1"></textarea>
|
||||
<div class="flex-container">
|
||||
<div class="flex1">
|
||||
<label for="context_example_separator">
|
||||
<small data-i18n="Example Separator">Example Separator</small>
|
||||
</label>
|
||||
<div>
|
||||
<textarea id="context_example_separator" class="text_pole textarea_compact" maxlength="500" rows="2"></textarea>
|
||||
<textarea id="context_example_separator" class="text_pole textarea_compact autoSetHeight" maxlength="500" rows="1"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex1">
|
||||
|
@ -2113,7 +2113,7 @@
|
|||
<small data-i18n="Chat Start">Chat Start</small>
|
||||
</label>
|
||||
<div>
|
||||
<textarea id="context_chat_start" class="text_pole textarea_compact" maxlength="500" rows="2"></textarea>
|
||||
<textarea id="context_chat_start" class="text_pole textarea_compact autoSetHeight" maxlength="500" rows="1"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -2151,7 +2151,7 @@
|
|||
</small>
|
||||
</label>
|
||||
<div>
|
||||
<textarea id="instruct_activation_regex" class="text_pole textarea_compact" maxlength="5000" rows="1"></textarea>
|
||||
<textarea id="instruct_activation_regex" class="text_pole textarea_compact autoSetHeight" maxlength="5000" rows="1"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label for="instruct_wrap" class="checkbox_label">
|
||||
|
@ -2176,59 +2176,89 @@
|
|||
<div class="prompt_overridden">
|
||||
Overridden by the Character Definitions.
|
||||
</div>
|
||||
<textarea id="instruct_system_prompt" class="text_pole textarea_compact" rows="4"></textarea>
|
||||
<div class="flex-container">
|
||||
<div class="flex1">
|
||||
<label for="instruct_input_sequence">
|
||||
<small data-i18n="Input Sequence">Input Sequence</small>
|
||||
</label>
|
||||
<div>
|
||||
<textarea id="instruct_input_sequence" class="text_pole textarea_compact" maxlength="500" rows="2"></textarea>
|
||||
</div>
|
||||
<textarea id="instruct_system_prompt" class="text_pole textarea_compact autoSetHeight" rows="1"></textarea>
|
||||
|
||||
<div class="inline-drawer wide100p flexFlowColumn">
|
||||
<div class="inline-drawer-toggle inline-drawer-header">
|
||||
<b><span data-i18n="Instruct Mode Sequences">Instruct Mode Sequences</span></b>
|
||||
<div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
|
||||
</div>
|
||||
<div class="flex1">
|
||||
<label for="instruct_output_sequence">
|
||||
<small data-i18n="Output Sequence">Output Sequence</small>
|
||||
</label>
|
||||
<div>
|
||||
<textarea id="instruct_output_sequence" class="text_pole wide100p textarea_compact" maxlength="500" rows="2"></textarea>
|
||||
<div class="inline-drawer-content">
|
||||
<div class="flex-container">
|
||||
<div class="flex1">
|
||||
<label for="instruct_input_sequence">
|
||||
<small data-i18n="Input Sequence">Input Sequence</small>
|
||||
</label>
|
||||
<div>
|
||||
<textarea id="instruct_input_sequence" class="text_pole textarea_compact autoSetHeight" maxlength="500" placeholder="—" rows="1"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex1">
|
||||
<label for="instruct_output_sequence">
|
||||
<small data-i18n="Output Sequence">Output Sequence</small>
|
||||
</label>
|
||||
<div>
|
||||
<textarea id="instruct_output_sequence" class="text_pole wide100p textarea_compact autoSetHeight" maxlength="500" placeholder="—" rows="1"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex1">
|
||||
<label for="instruct_last_output_sequence">
|
||||
<small data-i18n="Last Sequence">Last Sequence</small>
|
||||
</label>
|
||||
<div>
|
||||
<textarea id="instruct_last_output_sequence" class="text_pole wide100p textarea_compact" maxlength="500" rows="2"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-container">
|
||||
<div class="flex1">
|
||||
<label for="instruct_system_sequence">
|
||||
<small data-i18n="System Sequence">System Sequence</small>
|
||||
</label>
|
||||
<div>
|
||||
<textarea id="instruct_system_sequence" class="text_pole textarea_compact" maxlength="500" rows="2"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex1">
|
||||
<label for="instruct_stop_sequence">
|
||||
<small data-i18n="Stop Sequence">Stop Sequence</small>
|
||||
</label>
|
||||
<div>
|
||||
<textarea id="instruct_stop_sequence" class="text_pole wide100p textarea_compact" maxlength="500" rows="2"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex1">
|
||||
<label for="instruct_separator_sequence">
|
||||
<small data-i18n="Separator">Separator</small>
|
||||
</label>
|
||||
<div>
|
||||
<textarea id="instruct_separator_sequence" class="text_pole wide100p textarea_compact" maxlength="500" rows="2"></textarea>
|
||||
<div class="flex-container">
|
||||
<div class="flex1">
|
||||
<label for="instruct_first_output_sequence">
|
||||
<small data-i18n="First Output Sequence">First Output Sequence</small>
|
||||
</label>
|
||||
<div>
|
||||
<textarea id="instruct_first_output_sequence" class="text_pole textarea_compact autoSetHeight" maxlength="500" placeholder="—" rows="1"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex1">
|
||||
<label for="instruct_last_output_sequence">
|
||||
<small data-i18n="Last Output Sequence">Last Output Sequence</small>
|
||||
</label>
|
||||
<div>
|
||||
<textarea id="instruct_last_output_sequence" class="text_pole wide100p textarea_compact autoSetHeight" maxlength="500" placeholder="—" rows="1"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-container">
|
||||
<div class="flex1">
|
||||
<label for="instruct_system_sequence_prefix">
|
||||
<small data-i18n="System Sequence Prefix">System Sequence Prefix</small>
|
||||
</label>
|
||||
<div>
|
||||
<textarea id="instruct_system_sequence_prefix" class="text_pole textarea_compact autoSetHeight" maxlength="500" placeholder="—" rows="1"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex1">
|
||||
<label for="instruct_system_sequence_suffix">
|
||||
<small data-i18n="System Sequence Suffix">System Sequence Suffix</small>
|
||||
</label>
|
||||
<div>
|
||||
<textarea id="instruct_system_sequence_suffix" class="text_pole wide100p textarea_compact autoSetHeight" maxlength="500" placeholder="—" rows="1"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-container">
|
||||
<div class="flex1">
|
||||
<label for="instruct_stop_sequence">
|
||||
<small data-i18n="Stop Sequence">Stop Sequence</small>
|
||||
</label>
|
||||
<div>
|
||||
<textarea id="instruct_stop_sequence" class="text_pole textarea_compact autoSetHeight" maxlength="500" placeholder="—" rows="1"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex1">
|
||||
<label for="instruct_separator_sequence">
|
||||
<small data-i18n="Separator">Separator</small>
|
||||
</label>
|
||||
<div>
|
||||
<textarea id="instruct_separator_sequence" class="text_pole wide100p textarea_compact autoSetHeight" maxlength="500" placeholder="—" rows="1"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div name="ContextFormatting" class="flex1">
|
||||
|
@ -2315,9 +2345,14 @@
|
|||
</div>
|
||||
</div>
|
||||
<h4>
|
||||
<span data-i18n="Custom Stopping Strings">
|
||||
Custom Stopping Strings (KoboldAI/TextGen/NovelAI)
|
||||
</span>
|
||||
<div class="range-block-title justifyLeft">
|
||||
<span data-i18n="Custom Stopping Strings">
|
||||
Custom Stopping Strings
|
||||
</span>
|
||||
<a href="https://docs.sillytavern.app/usage/core-concepts/advancedformatting/#custom-stopping-strings" class="notes-link" target="_blank">
|
||||
<span class="note-link-span">?</span>
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<small>
|
||||
<span data-i18n="JSON serialized array of strings">JSON serialized array of strings, for example:</span><br>
|
||||
|
@ -2593,7 +2628,7 @@
|
|||
</div>
|
||||
<div class="range-block-range-and-counter">
|
||||
<div class="range-block-range">
|
||||
<input type="range" id="font_scale" name="font_scale" min="0.75" max="1.25" step="0.01">
|
||||
<input type="range" id="font_scale" name="font_scale" min="0.8" max="1.2" step="0.02">
|
||||
</div>
|
||||
<div class="range-block-counter">
|
||||
<div contenteditable="true" data-for="font_scale" id="font_scale_counter">
|
||||
|
@ -4122,7 +4157,7 @@
|
|||
<div class="floating_prompt_radio_group">
|
||||
<label>
|
||||
<input type="radio" name="extension_floating_position" value="0" />
|
||||
After scenario
|
||||
After Main Prompt / Story String
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="extension_floating_position" value="1" />
|
||||
|
@ -4190,7 +4225,7 @@
|
|||
<div class="floating_prompt_radio_group">
|
||||
<label>
|
||||
<input type="radio" name="extension_default_position" value="0" />
|
||||
After scenario
|
||||
After Main Prompt / Story String
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="extension_default_position" value="1" />
|
||||
|
|
|
@ -3,8 +3,10 @@
|
|||
"system_prompt": "Below is an instruction that describes a task. Write a response that appropriately completes the request.\n\nWrite {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n",
|
||||
"input_sequence": "### Instruction:",
|
||||
"output_sequence": "### Response:",
|
||||
"first_output_sequence": "",
|
||||
"last_output_sequence": "",
|
||||
"system_sequence": "",
|
||||
"system_sequence_prefix": "",
|
||||
"system_sequence_suffix": "",
|
||||
"stop_sequence": "",
|
||||
"separator_sequence": "",
|
||||
"wrap": true,
|
||||
|
|
|
@ -3,8 +3,10 @@
|
|||
"system_prompt": "Write {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n",
|
||||
"input_sequence": "USER: ",
|
||||
"output_sequence": "GPT: ",
|
||||
"first_output_sequence": "",
|
||||
"last_output_sequence": "",
|
||||
"system_sequence": "BEGINNING OF CONVERSATION: ",
|
||||
"system_sequence_prefix": "BEGINNING OF CONVERSATION: ",
|
||||
"system_sequence_suffix": "",
|
||||
"stop_sequence": "",
|
||||
"separator_sequence": "</s>",
|
||||
"wrap": false,
|
||||
|
|
|
@ -3,8 +3,10 @@
|
|||
"system_prompt": "Write {{char}}'s next reply in this fictional roleplay with {{user}}.",
|
||||
"input_sequence": "[INST] ",
|
||||
"output_sequence": " [/INST] ",
|
||||
"first_output_sequence": "[/INST] ",
|
||||
"last_output_sequence": "",
|
||||
"system_sequence": "[INST] <<SYS>>\n{{sys}}\n<</SYS>>\n",
|
||||
"system_sequence_prefix": "[INST] <<SYS>>\n",
|
||||
"system_sequence_suffix": "\n<</SYS>>\n",
|
||||
"stop_sequence": "",
|
||||
"separator_sequence": "\n",
|
||||
"wrap": false,
|
||||
|
|
|
@ -3,8 +3,10 @@
|
|||
"system_prompt": "Enter roleplay mode. You must act as {{char}}, whose persona follows:",
|
||||
"input_sequence": "<|user|>",
|
||||
"output_sequence": "<|model|>",
|
||||
"first_output_sequence": "",
|
||||
"last_output_sequence": "",
|
||||
"system_sequence": "<|system|>",
|
||||
"system_sequence_prefix": "<|system|>",
|
||||
"system_sequence_suffix": "",
|
||||
"stop_sequence": "</s>",
|
||||
"separator_sequence": "",
|
||||
"wrap": false,
|
||||
|
|
|
@ -3,8 +3,10 @@
|
|||
"system_prompt": "You are a helpful assistant. Please answer truthfully and write out your thinking step by step to be sure you get the right answer. If you make a mistake or encounter an error in your thinking, say so out loud and attempt to correct it. If you don't know or aren't sure about something, say so clearly. You will act as a professional logician, mathematician, and physicist. You will also act as the most appropriate type of expert to answer any particular question or solve the relevant problem; state which expert type your are, if so. Also think of any particular named expert that would be ideal to answer the relevant question or solve the relevant problem; name and act as them, if appropriate.\n",
|
||||
"input_sequence": "User: ",
|
||||
"output_sequence": "<|end_of_turn|>\nAssistant: ",
|
||||
"first_output_sequence": "",
|
||||
"last_output_sequence": "",
|
||||
"system_sequence": "",
|
||||
"system_sequence_prefix": "",
|
||||
"system_sequence_suffix": "",
|
||||
"stop_sequence": "",
|
||||
"separator_sequence": "<|end_of_turn|>\n",
|
||||
"wrap": false,
|
||||
|
|
|
@ -3,8 +3,10 @@
|
|||
"system_prompt": "Avoid repetition, don't loop. Develop the plot slowly, always stay in character. Describe all actions in full, elaborate, explicit, graphic, and vivid detail. Mention all relevant sensory perceptions.",
|
||||
"input_sequence": "\n### Instruction:",
|
||||
"output_sequence": "\n### Response:",
|
||||
"first_output_sequence": "",
|
||||
"last_output_sequence": "\n### Response (2 paragraphs, engaging, natural, authentic, descriptive, creative):",
|
||||
"system_sequence": "",
|
||||
"system_sequence_prefix": "",
|
||||
"system_sequence_suffix": "",
|
||||
"stop_sequence": "",
|
||||
"separator_sequence": "",
|
||||
"wrap": true,
|
||||
|
|
|
@ -3,8 +3,10 @@
|
|||
"system_prompt": "A chat between a curious human and an artificial intelligence assistant. The assistant gives helpful, detailed, and polite answers to the human's questions.\n\nWrite {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n",
|
||||
"input_sequence": "### Human:",
|
||||
"output_sequence": "### Assistant:",
|
||||
"first_output_sequence": "",
|
||||
"last_output_sequence": "",
|
||||
"system_sequence": "",
|
||||
"system_sequence_prefix": "",
|
||||
"system_sequence_suffix": "",
|
||||
"stop_sequence": "",
|
||||
"separator_sequence": "",
|
||||
"wrap": true,
|
||||
|
|
|
@ -3,8 +3,10 @@
|
|||
"system_prompt": "A chat between a curious user and an artificial intelligence assistant. The assistant gives helpful, detailed, and polite answers to the user's questions.\n\nWrite {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n",
|
||||
"input_sequence": "\nUSER: ",
|
||||
"output_sequence": "\nASSISTANT: ",
|
||||
"first_output_sequence": "",
|
||||
"last_output_sequence": "",
|
||||
"system_sequence": "BEGINNING OF CONVERSATION:",
|
||||
"system_sequence_prefix": "BEGINNING OF CONVERSATION:",
|
||||
"system_sequence_suffix": "",
|
||||
"stop_sequence": "",
|
||||
"separator_sequence": "</s>",
|
||||
"wrap": false,
|
||||
|
|
|
@ -3,8 +3,10 @@
|
|||
"system_prompt": "A chat between a curious user and an artificial intelligence assistant. The assistant gives helpful, detailed, and polite answers to the user's questions.\n\nWrite {{char}}'s next detailed reply in a fictional roleplay chat between {{user}} and {{char}}.",
|
||||
"input_sequence": "USER: ",
|
||||
"output_sequence": "ASSISTANT: ",
|
||||
"first_output_sequence": "",
|
||||
"last_output_sequence": "",
|
||||
"system_sequence": "",
|
||||
"system_sequence_prefix": "",
|
||||
"system_sequence_suffix": "",
|
||||
"stop_sequence": "",
|
||||
"separator_sequence": "",
|
||||
"wrap": true,
|
||||
|
|
|
@ -3,8 +3,10 @@
|
|||
"system_prompt": "Write {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n",
|
||||
"input_sequence": "",
|
||||
"output_sequence": "### Response:",
|
||||
"first_output_sequence": "",
|
||||
"last_output_sequence": "",
|
||||
"system_sequence": "",
|
||||
"system_sequence_prefix": "",
|
||||
"system_sequence_suffix": "",
|
||||
"stop_sequence": "",
|
||||
"separator_sequence": "</s>",
|
||||
"wrap": true,
|
||||
|
|
|
@ -3,8 +3,10 @@
|
|||
"system_prompt": "[System note: Write one reply only. Do not decide what {{user}} says or does. Write at least one paragraph, up to four. Be descriptive and immersive, providing vivid details about {{char}}'s actions, emotions, and the environment. Write with a high degree of complexity and burstiness. Do not repeat this message.]",
|
||||
"input_sequence": "### Instruction:\n#### {{user}}:",
|
||||
"output_sequence": "### Response:\n#### {{char}}:",
|
||||
"first_output_sequence": "",
|
||||
"last_output_sequence": "### Response (2 paragraphs, engaging, natural, authentic, descriptive, creative):\n#### {{char}}:",
|
||||
"system_sequence": "",
|
||||
"system_sequence_prefix": "",
|
||||
"system_sequence_suffix": "",
|
||||
"stop_sequence": "",
|
||||
"separator_sequence": "",
|
||||
"wrap": true,
|
||||
|
|
|
@ -131,6 +131,8 @@ import {
|
|||
isDigitsOnly,
|
||||
PAGINATION_TEMPLATE,
|
||||
waitUntilCondition,
|
||||
escapeRegex,
|
||||
resetScrollHeight,
|
||||
} from "./scripts/utils.js";
|
||||
|
||||
import { extension_settings, getContext, loadExtensionSettings, processExtensionHelpers, registerExtensionHelper, runGenerationInterceptors, saveMetadataDebounced } from "./scripts/extensions.js";
|
||||
|
@ -162,6 +164,7 @@ import { getRegexedString, regex_placement } from "./scripts/extensions/regex/en
|
|||
import { FILTER_TYPES, FilterHelper } from "./scripts/filters.js";
|
||||
import { getCfgPrompt, getGuidanceScale } from "./scripts/extensions/cfg/util.js";
|
||||
import {
|
||||
force_output_sequence,
|
||||
formatInstructModeChat,
|
||||
formatInstructModePrompt,
|
||||
formatInstructModeExamples,
|
||||
|
@ -372,6 +375,9 @@ const system_message_types = {
|
|||
};
|
||||
|
||||
const extension_prompt_types = {
|
||||
/**
|
||||
* @deprecated Outdated term. In reality it's "after main prompt or story string"
|
||||
*/
|
||||
AFTER_SCENARIO: 0,
|
||||
IN_CHAT: 1
|
||||
};
|
||||
|
@ -495,9 +501,10 @@ function getUrlSync(url, cache = true) {
|
|||
}).responseText;
|
||||
}
|
||||
|
||||
function renderTemplate(templateId, templateData = {}, sanitize = true, localize = true) {
|
||||
export function renderTemplate(templateId, templateData = {}, sanitize = true, localize = true, fullPath = false) {
|
||||
try {
|
||||
const templateContent = getUrlSync(`/scripts/templates/${templateId}.html`);
|
||||
const pathToTemplate = fullPath ? templateId : `/scripts/templates/${templateId}.html`;
|
||||
const templateContent = getUrlSync(pathToTemplate);
|
||||
const template = Handlebars.compile(templateContent);
|
||||
let result = template(templateData);
|
||||
|
||||
|
@ -1883,9 +1890,10 @@ function cleanGroupMessage(getMessage) {
|
|||
continue;
|
||||
}
|
||||
|
||||
const indexOfMember = getMessage.indexOf(`${name}:`);
|
||||
if (indexOfMember != -1) {
|
||||
getMessage = getMessage.substr(0, indexOfMember);
|
||||
const regex = new RegExp(`(^|\n)${escapeRegex(name)}:`);
|
||||
const nameMatch = getMessage.match(regex);
|
||||
if (nameMatch) {
|
||||
getMessage = getMessage.substring(nameMatch.index + nameMatch[0].length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2051,7 +2059,7 @@ class StreamingProcessor {
|
|||
chat[messageId]['gen_started'] = this.timeStarted;
|
||||
chat[messageId]['gen_finished'] = currentTime;
|
||||
|
||||
if (this.type == 'swipe' && Array.isArray(chat[messageId]['swipes'])) {
|
||||
if ((this.type == 'swipe' || this.type === 'continue') && Array.isArray(chat[messageId]['swipes'])) {
|
||||
chat[messageId]['swipes'][chat[messageId]['swipe_id']] = processedText;
|
||||
chat[messageId]['swipe_info'][chat[messageId]['swipe_id']] = { 'send_date': chat[messageId]['send_date'], 'gen_started': chat[messageId]['gen_started'], 'gen_finished': chat[messageId]['gen_finished'], 'extra': JSON.parse(JSON.stringify(chat[messageId]['extra'])) };
|
||||
}
|
||||
|
@ -2254,8 +2262,8 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
|||
return;
|
||||
}
|
||||
|
||||
// Hide swipes on either multigen or real streaming
|
||||
if ((isStreamingEnabled() || isMultigenEnabled()) && !dryRun) {
|
||||
// Hide swipes if not in a dry run.
|
||||
if (!dryRun) {
|
||||
hideSwipeButtons();
|
||||
}
|
||||
|
||||
|
@ -2431,11 +2439,16 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
|||
|
||||
chat2[i] = formatMessageHistoryItem(coreChat[j], isInstruct, false);
|
||||
|
||||
if (j === 0 && isInstruct) {
|
||||
// Reformat with the first output sequence (if any)
|
||||
chat2[i] = formatMessageHistoryItem(coreChat[j], isInstruct, force_output_sequence.FIRST);
|
||||
}
|
||||
|
||||
// Do not suffix the message for continuation
|
||||
if (i === 0 && isContinue) {
|
||||
if (isInstruct) {
|
||||
// Reformat with the last output line (if any)
|
||||
chat2[i] = formatMessageHistoryItem(coreChat[j], isInstruct, true);
|
||||
// Reformat with the last output sequence (if any)
|
||||
chat2[i] = formatMessageHistoryItem(coreChat[j], isInstruct, force_output_sequence.LAST);
|
||||
}
|
||||
|
||||
chat2[i] = chat2[i].slice(0, chat2[i].lastIndexOf(coreChat[j].mes) + coreChat[j].mes.length);
|
||||
|
@ -3245,9 +3258,9 @@ export function getBiasStrings(textareaText, type) {
|
|||
/**
|
||||
* @param {Object} chatItem Message history item.
|
||||
* @param {boolean} isInstruct Whether instruct mode is enabled.
|
||||
* @param {boolean} forceLastOutputSequence Whether to force the last output sequence for instruct mode.
|
||||
* @param {boolean|number} forceOutputSequence Whether to force the first/last output sequence for instruct mode.
|
||||
*/
|
||||
function formatMessageHistoryItem(chatItem, isInstruct, forceLastOutputSequence) {
|
||||
function formatMessageHistoryItem(chatItem, isInstruct, forceOutputSequence) {
|
||||
const isNarratorType = chatItem?.extra?.type === system_message_types.NARRATOR;
|
||||
const characterName = (selected_group || chatItem.force_avatar) ? chatItem.name : name2;
|
||||
const itemName = chatItem.is_user ? chatItem['name'] : characterName;
|
||||
|
@ -3256,7 +3269,7 @@ function formatMessageHistoryItem(chatItem, isInstruct, forceLastOutputSequence)
|
|||
let textResult = shouldPrependName ? `${itemName}: ${chatItem.mes}\n` : `${chatItem.mes}\n`;
|
||||
|
||||
if (isInstruct) {
|
||||
textResult = formatInstructModeChat(itemName, chatItem.mes, chatItem.is_user, isNarratorType, chatItem.force_avatar, name1, name2, forceLastOutputSequence);
|
||||
textResult = formatInstructModeChat(itemName, chatItem.mes, chatItem.is_user, isNarratorType, chatItem.force_avatar, name1, name2, forceOutputSequence);
|
||||
}
|
||||
|
||||
textResult = replaceBiasMarkup(textResult);
|
||||
|
@ -3918,8 +3931,9 @@ async function saveReply(type, getMessage, this_mes_is_name, title) {
|
|||
item["swipe_info"] = [];
|
||||
}
|
||||
if (item["swipe_id"] !== undefined) {
|
||||
item["swipes"][item["swipes"].length - 1] = item["mes"];
|
||||
item["swipe_info"][item["swipes"].length - 1] = {
|
||||
const swipeId = item["swipe_id"];
|
||||
item["swipes"][swipeId] = item["mes"];
|
||||
item["swipe_info"][swipeId] = {
|
||||
send_date: item["send_date"],
|
||||
gen_started: item["gen_started"],
|
||||
gen_finished: item["gen_finished"],
|
||||
|
@ -8060,28 +8074,27 @@ $(document).ready(function () {
|
|||
var sliderTimer;
|
||||
|
||||
$("input[type='range']").on("touchstart", function () {
|
||||
// Unlock the slider after 500ms
|
||||
sliderTimer = setTimeout(function () {
|
||||
// Unlock the slider after 300ms
|
||||
setTimeout(function () {
|
||||
sliderLocked = false;
|
||||
}, 500);
|
||||
$(this).css('background-color', 'var(--SmartThemeQuoteColor)');
|
||||
}.bind(this), 300);
|
||||
});
|
||||
|
||||
$("input[type='range']").on("touchend", function () {
|
||||
clearTimeout(sliderTimer);
|
||||
$(this).css('background-color', '')
|
||||
sliderLocked = true
|
||||
$(this).css('background-color', '');
|
||||
sliderLocked = true;
|
||||
});
|
||||
|
||||
$("input[type='range']").on("touchmove", function (event) {
|
||||
if (sliderLocked) {
|
||||
event.preventDefault();
|
||||
}
|
||||
else {
|
||||
$(this).css('background-color', 'var(--SmartThemeQuoteColor)')
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
const sliders = [
|
||||
{
|
||||
sliderId: "#amount_gen",
|
||||
|
@ -8647,6 +8660,10 @@ $(document).ready(function () {
|
|||
});
|
||||
}
|
||||
|
||||
// Set the height of "autoSetHeight" textareas within the drawer to their scroll height
|
||||
$(this).closest('.drawer').find('.drawer-content textarea.autoSetHeight').each(function() {
|
||||
resetScrollHeight($(this));
|
||||
});
|
||||
|
||||
} else if (drawerWasOpenAlready) { //to close manually
|
||||
icon.toggleClass('closedIcon openIcon');
|
||||
|
@ -8713,6 +8730,11 @@ $(document).ready(function () {
|
|||
icon.toggleClass('down up');
|
||||
icon.toggleClass('fa-circle-chevron-down fa-circle-chevron-up');
|
||||
$(this).closest('.inline-drawer').find('.inline-drawer-content').stop().slideToggle();
|
||||
|
||||
// Set the height of "autoSetHeight" textareas within the inline-drawer to their scroll height
|
||||
$(this).closest('.inline-drawer').find('.inline-drawer-content textarea.autoSetHeight').each(function() {
|
||||
resetScrollHeight($(this));
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '.mes .avatar', function () {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { callPopup, eventSource, event_types, saveSettings, saveSettingsDebounced, getRequestHeaders, substituteParams } from "../script.js";
|
||||
import { isSubsetOf, debounce, waitUntilCondition } from "./utils.js";
|
||||
import { callPopup, eventSource, event_types, saveSettings, saveSettingsDebounced, getRequestHeaders, substituteParams, renderTemplate } from "../script.js";
|
||||
import { isSubsetOf } from "./utils.js";
|
||||
export {
|
||||
getContext,
|
||||
getApiUrl,
|
||||
|
@ -48,6 +48,18 @@ export function saveMetadataDebounced() {
|
|||
|
||||
export const extensionsHandlebars = Handlebars.create();
|
||||
|
||||
/**
|
||||
* Provides an ability for extensions to render HTML templates.
|
||||
* Templates sanitation and localization is forced.
|
||||
* @param {string} extensionName Extension name
|
||||
* @param {string} templateId Template ID
|
||||
* @param {object} templateData Additional data to pass to the template
|
||||
* @returns {string} Rendered HTML
|
||||
*/
|
||||
export function renderExtensionTemplate(extensionName, templateId, templateData = {}, sanitize = true, localize = true) {
|
||||
return renderTemplate(`scripts/extensions/${extensionName}/${templateId}.html`, templateData, sanitize, localize, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a Handlebars helper for use in extensions.
|
||||
* @param {string} name Handlebars helper name
|
||||
|
|
|
@ -0,0 +1,246 @@
|
|||
/*
|
||||
TODO:
|
||||
- Check failed install file (0kb size ?)
|
||||
*/
|
||||
//const DEBUG_TONY_SAMA_FORK_MODE = false
|
||||
|
||||
import { getRequestHeaders, callPopup } from "../../../script.js";
|
||||
export { MODULE_NAME };
|
||||
|
||||
const MODULE_NAME = 'Assets';
|
||||
const DEBUG_PREFIX = "<Assets module> ";
|
||||
let ASSETS_JSON_URL = "https://raw.githubusercontent.com/SillyTavern/SillyTavern-Content/main/index.json"
|
||||
|
||||
const extensionName = "assets";
|
||||
const extensionFolderPath = `scripts/extensions/${extensionName}`;
|
||||
|
||||
// DBG
|
||||
//if (DEBUG_TONY_SAMA_FORK_MODE)
|
||||
// ASSETS_JSON_URL = "https://raw.githubusercontent.com/Tony-sama/SillyTavern-Content/main/index.json"
|
||||
let availableAssets = {};
|
||||
let currentAssets = {};
|
||||
|
||||
//#############################//
|
||||
// Extension UI and Settings //
|
||||
//#############################//
|
||||
|
||||
const defaultSettings = {
|
||||
}
|
||||
|
||||
function downloadAssetsList(url) {
|
||||
updateCurrentAssets().then(function () {
|
||||
fetch(url)
|
||||
.then(response => response.json())
|
||||
.then(json => {
|
||||
|
||||
availableAssets = {};
|
||||
$("#assets_menu").empty();
|
||||
|
||||
console.debug(DEBUG_PREFIX, "Received assets dictionary", json);
|
||||
|
||||
for (const i of json) {
|
||||
//console.log(DEBUG_PREFIX,i)
|
||||
if (availableAssets[i["type"]] === undefined)
|
||||
availableAssets[i["type"]] = [];
|
||||
|
||||
availableAssets[i["type"]].push(i);
|
||||
}
|
||||
|
||||
console.debug(DEBUG_PREFIX, "Updated available assets to", availableAssets);
|
||||
|
||||
for (const assetType in availableAssets) {
|
||||
let assetTypeMenu = $('<div />', { id: "assets_audio_ambient_div", class: "assets-list-div" });
|
||||
assetTypeMenu.append(`<h3>${assetType}</h3>`)
|
||||
for (const i in availableAssets[assetType]) {
|
||||
const asset = availableAssets[assetType][i];
|
||||
const elemId = `assets_install_${assetType}_${i}`;
|
||||
let element = $('<button />', { id: elemId, type: "button", class: "asset-download-button menu_button" })
|
||||
const label = $("<i class=\"fa-solid fa-download fa-xl\"></i>");
|
||||
element.append(label);
|
||||
|
||||
//if (DEBUG_TONY_SAMA_FORK_MODE)
|
||||
// assetUrl = assetUrl.replace("https://github.com/SillyTavern/","https://github.com/Tony-sama/"); // DBG
|
||||
|
||||
console.debug(DEBUG_PREFIX, "Checking asset", asset["id"], asset["url"]);
|
||||
|
||||
const assetInstall = async function () {
|
||||
element.off("click");
|
||||
label.removeClass("fa-download");
|
||||
this.classList.add('asset-download-button-loading');
|
||||
await installAsset(asset["url"], assetType, asset["id"]);
|
||||
label.addClass("fa-check");
|
||||
this.classList.remove('asset-download-button-loading');
|
||||
element.on("click", assetDelete);
|
||||
element.on("mouseenter", function(){
|
||||
label.removeClass("fa-check");
|
||||
label.addClass("fa-trash");
|
||||
label.addClass("redOverlayGlow");
|
||||
}).on("mouseleave", function(){
|
||||
label.addClass("fa-check");
|
||||
label.removeClass("fa-trash");
|
||||
label.removeClass("redOverlayGlow");
|
||||
});
|
||||
};
|
||||
|
||||
const assetDelete = async function() {
|
||||
element.off("click");
|
||||
await deleteAsset(assetType, asset["id"]);
|
||||
label.removeClass("fa-check");
|
||||
label.removeClass("redOverlayGlow");
|
||||
label.removeClass("fa-trash");
|
||||
label.addClass("fa-download");
|
||||
element.off("mouseenter").off("mouseleave");
|
||||
element.on("click", assetInstall);
|
||||
}
|
||||
|
||||
if (isAssetInstalled(assetType, asset["id"])) {
|
||||
console.debug(DEBUG_PREFIX, "installed, checked");
|
||||
label.toggleClass("fa-download");
|
||||
label.toggleClass("fa-check");
|
||||
element.on("click", assetDelete);
|
||||
element.on("mouseenter", function(){
|
||||
label.removeClass("fa-check");
|
||||
label.addClass("fa-trash");
|
||||
label.addClass("redOverlayGlow");
|
||||
}).on("mouseleave", function(){
|
||||
label.addClass("fa-check");
|
||||
label.removeClass("fa-trash");
|
||||
label.removeClass("redOverlayGlow");
|
||||
});
|
||||
}
|
||||
else {
|
||||
console.debug(DEBUG_PREFIX, "not installed, unchecked")
|
||||
element.prop("checked", false);
|
||||
element.on("click", assetInstall);
|
||||
}
|
||||
|
||||
console.debug(DEBUG_PREFIX, "Created element for BGM", asset["id"])
|
||||
|
||||
$(`<i></i>`)
|
||||
.append(element)
|
||||
.append(`<span>${asset["id"]}</span>`)
|
||||
.appendTo(assetTypeMenu);
|
||||
}
|
||||
assetTypeMenu.appendTo("#assets_menu");
|
||||
}
|
||||
|
||||
$("#assets_menu").show();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
toastr.error("Problem with assets URL", DEBUG_PREFIX + "Cannot get assets list");
|
||||
$('#assets-connect-button').addClass("fa-plug-circle-exclamation");
|
||||
$('#assets-connect-button').addClass("redOverlayGlow");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function isAssetInstalled(assetType, filename) {
|
||||
for (const i of currentAssets[assetType]) {
|
||||
//console.debug(DEBUG_PREFIX,i,filename)
|
||||
if (i.includes(filename))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async function installAsset(url, assetType, filename) {
|
||||
console.debug(DEBUG_PREFIX, "Downloading ", url);
|
||||
const category = assetType;
|
||||
try {
|
||||
const body = { url, category, filename };
|
||||
const result = await fetch('/asset_download', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify(body),
|
||||
cache: 'no-cache',
|
||||
});
|
||||
if (result.ok) {
|
||||
console.debug(DEBUG_PREFIX, "Download success.")
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteAsset(assetType, filename) {
|
||||
console.debug(DEBUG_PREFIX, "Deleting ", assetType, filename);
|
||||
const category = assetType;
|
||||
try {
|
||||
const body = { category, filename };
|
||||
const result = await fetch('/asset_delete', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify(body),
|
||||
cache: 'no-cache',
|
||||
});
|
||||
if (result.ok) {
|
||||
console.debug(DEBUG_PREFIX, "Deletion success.")
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
//#############################//
|
||||
// API Calls //
|
||||
//#############################//
|
||||
|
||||
async function updateCurrentAssets() {
|
||||
console.debug(DEBUG_PREFIX, "Checking installed assets...")
|
||||
try {
|
||||
const result = await fetch(`/get_assets`, {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
});
|
||||
currentAssets = result.ok ? (await result.json()) : {};
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
console.debug(DEBUG_PREFIX, "Current assets found:", currentAssets)
|
||||
}
|
||||
|
||||
|
||||
//#############################//
|
||||
// Extension load //
|
||||
//#############################//
|
||||
|
||||
// This function is called when the extension is loaded
|
||||
jQuery(async () => {
|
||||
// This is an example of loading HTML from a file
|
||||
const windowHtml = $(await $.get(`${extensionFolderPath}/window.html`));
|
||||
|
||||
const assetsJsonUrl = windowHtml.find('#assets-json-url-field');
|
||||
assetsJsonUrl.val(ASSETS_JSON_URL);
|
||||
|
||||
const connectButton = windowHtml.find('#assets-connect-button');
|
||||
connectButton.on("click", async function () {
|
||||
const confirmation = await callPopup(`Are you sure you want to connect to '${assetsJsonUrl.val()}'?`, 'confirm')
|
||||
if (confirmation) {
|
||||
try {
|
||||
console.debug(DEBUG_PREFIX, "Confimation, loading assets...");
|
||||
downloadAssetsList(assetsJsonUrl.val());
|
||||
connectButton.removeClass("fa-plug-circle-exclamation");
|
||||
connectButton.removeClass("redOverlayGlow");
|
||||
connectButton.addClass("fa-plug-circle-check");
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
toastr.error(`Cannot get assets list from ${assetsJsonUrl.val()}`);
|
||||
connectButton.removeClass("fa-plug-circle-check");
|
||||
connectButton.addClass("fa-plug-circle-exclamation");
|
||||
connectButton.removeClass("redOverlayGlow");
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.debug(DEBUG_PREFIX, "Connection refused by user");
|
||||
}
|
||||
});
|
||||
|
||||
$('#extensions_settings').append(windowHtml);
|
||||
});
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"display_name": "Assets",
|
||||
"loading_order": 15,
|
||||
"requires": [],
|
||||
"optional": [],
|
||||
"js": "index.js",
|
||||
"css": "style.css",
|
||||
"author": "Keij#6799",
|
||||
"version": "0.1.0",
|
||||
"homePage": "https://github.com/SillyTavern/SillyTavern"
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
#assets-json-url-field {
|
||||
width: 85%;
|
||||
}
|
||||
|
||||
#assets-connect-button {
|
||||
width: 15%;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.assets-connect-div {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.assets-list-div i {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: left;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.assets-list-div i span{
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.asset-download-button {
|
||||
position: relative;
|
||||
width: 50px;
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
outline: none;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.asset-download-button:active {
|
||||
background: #007a63;
|
||||
}
|
||||
|
||||
.asset-download-button-text {
|
||||
font: bold 20px "Quicksand", san-serif;
|
||||
color: #ffffff;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.asset-download-button-loading .asset-download-button-text {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.asset-download-button-loading::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
border: 4px solid transparent;
|
||||
border-top-color: #ffffff;
|
||||
border-radius: 50%;
|
||||
animation: asset-download-button-loading-spinner 1s ease infinite;
|
||||
}
|
||||
|
||||
@keyframes asset-download-button-loading-spinner {
|
||||
from {
|
||||
transform: rotate(0turn);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(1turn);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<div id="assets_ui">
|
||||
<div class="inline-drawer">
|
||||
<div class="inline-drawer-toggle inline-drawer-header">
|
||||
<b>Assets</b>
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
<label for="assets-json-url-field">Assets URL</label>
|
||||
<div class="assets-connect-div">
|
||||
<input id="assets-json-url-field" class="text_pole widthUnset flex1">
|
||||
<i id="assets-connect-button" class="menu_button fa-solid fa-plug-circle-exclamation fa-xl redOverlayGlow"></i>
|
||||
</div>
|
||||
<div class="inline-drawer-content" id="assets_menu">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,593 @@
|
|||
/*
|
||||
Ideas:
|
||||
- cross fading between bgm / start a different time
|
||||
- Background based ambient sounds
|
||||
- import option on background UI ?
|
||||
- Allow background music edition using background menu
|
||||
- https://fontawesome.com/icons/music?f=classic&s=solid
|
||||
- https://codepen.io/noirsociety/pen/rNQxQwm
|
||||
- https://codepen.io/xrocker/pen/abdKVGy
|
||||
*/
|
||||
|
||||
import { saveSettingsDebounced, getRequestHeaders } from "../../../script.js";
|
||||
import { getContext, extension_settings, ModuleWorkerWrapper } from "../../extensions.js";
|
||||
import { isDataURL } from "../../utils.js";
|
||||
export { MODULE_NAME };
|
||||
|
||||
const extensionName = "audio";
|
||||
const extensionFolderPath = `scripts/extensions/${extensionName}`;
|
||||
|
||||
const MODULE_NAME = 'Audio';
|
||||
const DEBUG_PREFIX = "<Audio module> ";
|
||||
const UPDATE_INTERVAL = 1000;
|
||||
|
||||
const ASSETS_BGM_FOLDER = "bgm";
|
||||
const ASSETS_AMBIENT_FOLDER = "ambient";
|
||||
const CHARACTER_BGM_FOLDER = "bgm"
|
||||
|
||||
const FALLBACK_EXPRESSION = "neutral";
|
||||
const DEFAULT_EXPRESSIONS = [
|
||||
//"talkinghead",
|
||||
"admiration",
|
||||
"amusement",
|
||||
"anger",
|
||||
"annoyance",
|
||||
"approval",
|
||||
"caring",
|
||||
"confusion",
|
||||
"curiosity",
|
||||
"desire",
|
||||
"disappointment",
|
||||
"disapproval",
|
||||
"disgust",
|
||||
"embarrassment",
|
||||
"excitement",
|
||||
"fear",
|
||||
"gratitude",
|
||||
"grief",
|
||||
"joy",
|
||||
"love",
|
||||
"nervousness",
|
||||
"optimism",
|
||||
"pride",
|
||||
"realization",
|
||||
"relief",
|
||||
"remorse",
|
||||
"sadness",
|
||||
"surprise",
|
||||
"neutral"
|
||||
];
|
||||
const SPRITE_DOM_ID = "#expression-image";
|
||||
|
||||
let fallback_BGMS = null; // Initialized only once with module workers
|
||||
let ambients = null; // Initialized only once with module workers
|
||||
let characterMusics = {}; // Updated with module workers
|
||||
|
||||
let currentCharacterBGM = null;
|
||||
let currentExpressionBGM = null;
|
||||
let currentBackground = null;
|
||||
|
||||
let cooldownBGM = 0;
|
||||
|
||||
//#############################//
|
||||
// Extension UI and Settings //
|
||||
//#############################//
|
||||
|
||||
const defaultSettings = {
|
||||
enabled: false,
|
||||
bgm_muted: true,
|
||||
ambient_muted: true,
|
||||
bgm_volume: 50,
|
||||
ambient_volume: 50,
|
||||
bgm_cooldown: 30
|
||||
}
|
||||
|
||||
function loadSettings() {
|
||||
if (extension_settings.audio === undefined)
|
||||
extension_settings.audio = {};
|
||||
|
||||
if (Object.keys(extension_settings.audio).length === 0) {
|
||||
Object.assign(extension_settings.audio, defaultSettings)
|
||||
}
|
||||
$("#audio_enabled").prop('checked', extension_settings.audio.enabled);
|
||||
|
||||
$("#audio_bgm_volume").text(extension_settings.audio.bgm_volume);
|
||||
$("#audio_ambient_volume").text(extension_settings.audio.ambient_volume);
|
||||
|
||||
$("#audio_bgm_volume_slider").val(extension_settings.audio.bgm_volume);
|
||||
$("#audio_ambient_volume_slider").val(extension_settings.audio.ambient_volume);
|
||||
|
||||
if (extension_settings.audio.bgm_muted) {
|
||||
$("#audio_bgm_mute_icon").removeClass("fa-volume-high");
|
||||
$("#audio_bgm_mute_icon").addClass("fa-volume-mute");
|
||||
$("#audio_bgm_mute").addClass("redOverlayGlow");
|
||||
$("#audio_bgm").prop("muted", true);
|
||||
}
|
||||
else{
|
||||
$("#audio_bgm_mute_icon").addClass("fa-volume-high");
|
||||
$("#audio_bgm_mute_icon").removeClass("fa-volume-mute");
|
||||
$("#audio_bgm_mute").removeClass("redOverlayGlow");
|
||||
$("#audio_bgm").prop("muted", false);
|
||||
}
|
||||
|
||||
if (extension_settings.audio.ambient_muted) {
|
||||
$("#audio_ambient_mute_icon").removeClass("fa-volume-high");
|
||||
$("#audio_ambient_mute_icon").addClass("fa-volume-mute");
|
||||
$("#audio_ambient_mute").addClass("redOverlayGlow");
|
||||
$("#audio_ambient").prop("muted", true);
|
||||
}
|
||||
else{
|
||||
$("#audio_ambient_mute_icon").addClass("fa-volume-high");
|
||||
$("#audio_ambient_mute_icon").removeClass("fa-volume-mute");
|
||||
$("#audio_ambient_mute").removeClass("redOverlayGlow");
|
||||
$("#audio_ambient").prop("muted", false);
|
||||
}
|
||||
|
||||
$("#audio_bgm_cooldown").val(extension_settings.audio.bgm_cooldown);
|
||||
|
||||
$("#audio_debug_div").hide(); // DBG
|
||||
}
|
||||
|
||||
async function onEnabledClick() {
|
||||
extension_settings.audio.enabled = $('#audio_enabled').is(':checked');
|
||||
if (extension_settings.audio.enabled) {
|
||||
if ($("#audio_bgm").attr("src") != "")
|
||||
$("#audio_bgm")[0].play();
|
||||
if ($("#audio_ambient").attr("src") != "")
|
||||
$("#audio_ambient")[0].play();
|
||||
} else {
|
||||
$("#audio_bgm")[0].pause();
|
||||
$("#audio_ambient")[0].pause();
|
||||
}
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
async function onBGMMuteClick() {
|
||||
extension_settings.audio.bgm_muted = !extension_settings.audio.bgm_muted;
|
||||
$("#audio_bgm_mute_icon").toggleClass("fa-volume-high");
|
||||
$("#audio_bgm_mute_icon").toggleClass("fa-volume-mute");
|
||||
$("#audio_bgm").prop("muted", !$("#audio_bgm").prop("muted"));
|
||||
$("#audio_bgm_mute").toggleClass("redOverlayGlow");
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
async function onAmbientMuteClick() {
|
||||
extension_settings.audio.ambient_muted = !extension_settings.audio.ambient_muted;
|
||||
$("#audio_ambient_mute_icon").toggleClass("fa-volume-high");
|
||||
$("#audio_ambient_mute_icon").toggleClass("fa-volume-mute");
|
||||
$("#audio_ambient").prop("muted", !$("#audio_ambient").prop("muted"));
|
||||
$("#audio_ambient_mute").toggleClass("redOverlayGlow");
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
async function onBGMVolumeChange() {
|
||||
extension_settings.audio.bgm_volume = ~~($("#audio_bgm_volume_slider").val());
|
||||
$("#audio_bgm").prop("volume", extension_settings.audio.bgm_volume * 0.01);
|
||||
$("#audio_bgm_volume").text(extension_settings.audio.bgm_volume);
|
||||
saveSettingsDebounced();
|
||||
//console.debug(DEBUG_PREFIX,"UPDATED BGM MAX TO",extension_settings.audio.bgm_volume);
|
||||
}
|
||||
|
||||
async function onAmbientVolumeChange() {
|
||||
extension_settings.audio.ambient_volume = ~~($("#audio_ambient_volume_slider").val());
|
||||
$("#audio_ambient").prop("volume", extension_settings.audio.ambient_volume * 0.01);
|
||||
$("#audio_ambient_volume").text(extension_settings.audio.ambient_volume);
|
||||
saveSettingsDebounced();
|
||||
//console.debug(DEBUG_PREFIX,"UPDATED Ambient MAX TO",extension_settings.audio.ambient_volume);
|
||||
}
|
||||
|
||||
async function onBGMCooldownInput() {
|
||||
extension_settings.audio.bgm_cooldown = ~~($("#audio_bgm_cooldown").val());
|
||||
cooldownBGM = extension_settings.audio.bgm_cooldown * 1000;
|
||||
saveSettingsDebounced();
|
||||
console.debug(DEBUG_PREFIX, "UPDATED BGM cooldown to", extension_settings.audio.bgm_cooldown);
|
||||
}
|
||||
|
||||
//#############################//
|
||||
// API Calls //
|
||||
//#############################//
|
||||
|
||||
async function getAssetsList(type) {
|
||||
console.debug(DEBUG_PREFIX, "getting assets of type", type);
|
||||
|
||||
try {
|
||||
const result = await fetch(`/get_assets`, {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
});
|
||||
const assets = result.ok ? (await result.json()) : { type: [] };
|
||||
console.debug(DEBUG_PREFIX, "Found assets:", assets);
|
||||
return assets[type];
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function getCharacterBgmList(name) {
|
||||
console.debug(DEBUG_PREFIX, "getting bgm list for", name);
|
||||
|
||||
try {
|
||||
const result = await fetch(`/get_character_assets_list?name=${encodeURIComponent(name)}&category=${CHARACTER_BGM_FOLDER}`, {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
});
|
||||
let musics = result.ok ? (await result.json()) : [];
|
||||
return musics;
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//#############################//
|
||||
// Module Worker //
|
||||
//#############################//
|
||||
|
||||
/*
|
||||
- Update ambient sound
|
||||
- Update character BGM
|
||||
- Solo dynamique expression
|
||||
- Group only neutral bgm
|
||||
*/
|
||||
async function moduleWorker() {
|
||||
const moduleEnabled = extension_settings.audio.enabled;
|
||||
|
||||
if (moduleEnabled) {
|
||||
|
||||
if (cooldownBGM > 0)
|
||||
cooldownBGM -= UPDATE_INTERVAL;
|
||||
|
||||
if (fallback_BGMS == null) {
|
||||
console.debug(DEBUG_PREFIX, "Updating audio bgm assets...");
|
||||
fallback_BGMS = await getAssetsList(ASSETS_BGM_FOLDER);
|
||||
fallback_BGMS = fallback_BGMS.filter((filename) => filename != ".placeholder")
|
||||
console.debug(DEBUG_PREFIX, "Detected assets:", fallback_BGMS);
|
||||
}
|
||||
|
||||
if (ambients == null) {
|
||||
console.debug(DEBUG_PREFIX, "Updating audio ambient assets...");
|
||||
ambients = await getAssetsList(ASSETS_AMBIENT_FOLDER);
|
||||
ambients = ambients.filter((filename) => filename != ".placeholder")
|
||||
console.debug(DEBUG_PREFIX, "Detected assets:", ambients);
|
||||
}
|
||||
|
||||
// 1) Update ambient audio
|
||||
// ---------------------------
|
||||
let newBackground = $("#bg1").css("background-image");
|
||||
const custom_background = getContext()["chatMetadata"]["custom_background"];
|
||||
|
||||
if (custom_background !== undefined)
|
||||
newBackground = custom_background
|
||||
|
||||
if (!isDataURL(newBackground)) {
|
||||
newBackground = newBackground.substring(newBackground.lastIndexOf("/") + 1).replace(/\.[^/.]+$/, "").replaceAll("%20", "-").replaceAll(" ", "-"); // remove path and spaces
|
||||
|
||||
//console.debug(DEBUG_PREFIX,"Current backgroung:",newBackground);
|
||||
|
||||
if (currentBackground !== newBackground) {
|
||||
currentBackground = newBackground;
|
||||
|
||||
console.debug(DEBUG_PREFIX, "Changing ambient audio for", currentBackground);
|
||||
updateAmbient();
|
||||
}
|
||||
}
|
||||
|
||||
const context = getContext();
|
||||
//console.debug(DEBUG_PREFIX,context);
|
||||
|
||||
if (context.chat.length == 0)
|
||||
return;
|
||||
|
||||
let chatIsGroup = context.chat[0].is_group;
|
||||
let newCharacter = null;
|
||||
|
||||
// 1) Update BGM (single chat)
|
||||
// -----------------------------
|
||||
if (!chatIsGroup) {
|
||||
newCharacter = context.name2;
|
||||
|
||||
//console.log(DEBUG_PREFIX,"SOLO CHAT MODE"); // DBG
|
||||
|
||||
// 1.1) First time loading chat
|
||||
if (characterMusics[newCharacter] === undefined) {
|
||||
await loadCharacterBGM(newCharacter);
|
||||
currentExpressionBGM = FALLBACK_EXPRESSION;
|
||||
//currentCharacterBGM = newCharacter;
|
||||
|
||||
//updateBGM();
|
||||
//cooldownBGM = BGM_UPDATE_COOLDOWN;
|
||||
return;
|
||||
}
|
||||
|
||||
// 1.2) Switched chat
|
||||
if (currentCharacterBGM !== newCharacter) {
|
||||
currentCharacterBGM = newCharacter;
|
||||
try {
|
||||
await updateBGM();
|
||||
cooldownBGM = extension_settings.audio.bgm_cooldown * 1000;
|
||||
}
|
||||
catch (error) {
|
||||
console.debug(DEBUG_PREFIX, "Error while trying to update BGM character, will try again");
|
||||
currentCharacterBGM = null
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const newExpression = getNewExpression();
|
||||
|
||||
// 1.3) Same character but different expression
|
||||
if (currentExpressionBGM !== newExpression) {
|
||||
|
||||
// Check cooldown
|
||||
if (cooldownBGM > 0) {
|
||||
//console.debug(DEBUG_PREFIX,"(SOLO) BGM switch on cooldown:",cooldownBGM);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await updateBGM();
|
||||
cooldownBGM = extension_settings.audio.bgm_cooldown * 1000;
|
||||
currentExpressionBGM = newExpression;
|
||||
console.debug(DEBUG_PREFIX, "(SOLO) Updated current character expression to", currentExpressionBGM, "cooldown", cooldownBGM);
|
||||
}
|
||||
catch (error) {
|
||||
console.debug(DEBUG_PREFIX, "Error while trying to update BGM expression, will try again");
|
||||
currentCharacterBGM = null
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// 2) Update BGM (group chat)
|
||||
// -----------------------------
|
||||
newCharacter = context.chat[context.chat.length - 1].name;
|
||||
const userName = context.name1;
|
||||
|
||||
if (newCharacter !== undefined && newCharacter != userName) {
|
||||
|
||||
//console.log(DEBUG_PREFIX,"GROUP CHAT MODE"); // DBG
|
||||
|
||||
// 2.1) First time character appear
|
||||
if (characterMusics[newCharacter] === undefined) {
|
||||
await loadCharacterBGM(newCharacter);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2.2) Switched chat
|
||||
if (currentCharacterBGM !== newCharacter) {
|
||||
// Check cooldown
|
||||
if (cooldownBGM > 0) {
|
||||
//console.debug(DEBUG_PREFIX,"(GROUP) BGM switch on cooldown:",cooldownBGM);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
currentCharacterBGM = newCharacter;
|
||||
await updateBGM();
|
||||
cooldownBGM = extension_settings.audio.bgm_cooldown * 1000;
|
||||
currentCharacterBGM = newCharacter;
|
||||
currentExpressionBGM = FALLBACK_EXPRESSION;
|
||||
console.debug(DEBUG_PREFIX, "(GROUP) Updated current character BGM to", currentExpressionBGM, "cooldown", cooldownBGM);
|
||||
}
|
||||
catch (error) {
|
||||
console.debug(DEBUG_PREFIX, "Error while trying to update BGM group, will try again");
|
||||
currentCharacterBGM = null
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
const newExpression = getNewExpression();
|
||||
|
||||
// 1.3) Same character but different expression
|
||||
if (currentExpressionBGM !== newExpression) {
|
||||
|
||||
// Check cooldown
|
||||
if (cooldownBGM > 0) {
|
||||
console.debug(DEBUG_PREFIX,"BGM switch on cooldown:",cooldownBGM);
|
||||
return;
|
||||
}
|
||||
|
||||
cooldownBGM = BGM_UPDATE_COOLDOWN;
|
||||
currentExpressionBGM = newExpression;
|
||||
console.debug(DEBUG_PREFIX,"Updated current character expression to",currentExpressionBGM);
|
||||
updateBGM();
|
||||
return;
|
||||
}
|
||||
|
||||
return;*/
|
||||
|
||||
}
|
||||
|
||||
// Case 3: Same character/expression or BGM switch on cooldown keep playing same BGM
|
||||
//console.debug(DEBUG_PREFIX,"Nothing to do for",currentCharacterBGM, newCharacter, currentExpressionBGM, cooldownBGM);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadCharacterBGM(newCharacter) {
|
||||
console.debug(DEBUG_PREFIX, "New character detected, loading BGM folder of", newCharacter);
|
||||
|
||||
// 1.1) First time character appear, load its music folder
|
||||
const audio_file_paths = await getCharacterBgmList(newCharacter);
|
||||
//console.debug(DEBUG_PREFIX, "Recieved", audio_file_paths);
|
||||
|
||||
// Initialise expression/files mapping
|
||||
characterMusics[newCharacter] = {};
|
||||
for (const e of DEFAULT_EXPRESSIONS)
|
||||
characterMusics[newCharacter][e] = [];
|
||||
|
||||
for (const i of audio_file_paths) {
|
||||
//console.debug(DEBUG_PREFIX,"File found:",i);
|
||||
for (const e of DEFAULT_EXPRESSIONS)
|
||||
if (i.includes(e))
|
||||
characterMusics[newCharacter][e].push(i);
|
||||
}
|
||||
console.debug(DEBUG_PREFIX, "Updated BGM map of", newCharacter, "to", characterMusics[newCharacter]);
|
||||
}
|
||||
|
||||
function getNewExpression() {
|
||||
let newExpression;
|
||||
|
||||
// HACK: use sprite file name as expression detection
|
||||
if (!$(SPRITE_DOM_ID).length) {
|
||||
console.error(DEBUG_PREFIX, "ERROR: expression sprite does not exist, cannot extract expression from ", SPRITE_DOM_ID)
|
||||
return FALLBACK_EXPRESSION;
|
||||
}
|
||||
|
||||
const spriteFile = $("#expression-image").attr("src");
|
||||
newExpression = spriteFile.substring(spriteFile.lastIndexOf("/") + 1).replace(/\.[^/.]+$/, "");
|
||||
//
|
||||
|
||||
// No sprite to detect expression
|
||||
if (newExpression == "") {
|
||||
//console.info(DEBUG_PREFIX,"Warning: no expression extracted from sprite, switch to",FALLBACK_EXPRESSION);
|
||||
newExpression = FALLBACK_EXPRESSION;
|
||||
}
|
||||
|
||||
if (!DEFAULT_EXPRESSIONS.includes(newExpression)) {
|
||||
console.info(DEBUG_PREFIX, "Warning:", newExpression, " is not a handled expression, expected one of", FALLBACK_EXPRESSION);
|
||||
return FALLBACK_EXPRESSION;
|
||||
}
|
||||
|
||||
return newExpression;
|
||||
}
|
||||
|
||||
async function updateBGM() {
|
||||
let audio_files = characterMusics[currentCharacterBGM][currentExpressionBGM];// Try char expression BGM
|
||||
|
||||
if (audio_files === undefined || audio_files.length == 0) {
|
||||
console.debug(DEBUG_PREFIX, "No BGM for", currentCharacterBGM, currentExpressionBGM);
|
||||
audio_files = characterMusics[currentCharacterBGM][FALLBACK_EXPRESSION]; // Try char FALLBACK BGM
|
||||
if (audio_files === undefined || audio_files.length == 0) {
|
||||
console.debug(DEBUG_PREFIX, "No default BGM for", currentCharacterBGM, FALLBACK_EXPRESSION, "switch to ST BGM");
|
||||
audio_files = fallback_BGMS; // ST FALLBACK BGM
|
||||
|
||||
if (audio_files.length == 0) {
|
||||
console.debug(DEBUG_PREFIX, "No default BGM file found, bgm folder may be empty.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const audio_file_path = audio_files[Math.floor(Math.random() * audio_files.length)];
|
||||
console.log(DEBUG_PREFIX, "Updating BGM");
|
||||
console.log(DEBUG_PREFIX, "Checking file", audio_file_path);
|
||||
try {
|
||||
const response = await fetch(audio_file_path);
|
||||
|
||||
if (!response.ok) {
|
||||
console.log(DEBUG_PREFIX, "File not found!")
|
||||
}
|
||||
else {
|
||||
console.log(DEBUG_PREFIX, "Switching BGM to", currentExpressionBGM)
|
||||
const audio = $("#audio_bgm");
|
||||
|
||||
if (audio.attr("src") == audio_file_path) {
|
||||
console.log(DEBUG_PREFIX, "Already playing, ignored");
|
||||
return;
|
||||
}
|
||||
|
||||
audio.animate({ volume: 0.0 }, 2000, function () {
|
||||
audio.attr("src", audio_file_path);
|
||||
audio[0].play();
|
||||
audio.volume = extension_settings.audio.bgm_volume * 0.01;
|
||||
audio.animate({ volume: extension_settings.audio.bgm_volume * 0.01 }, 2000);
|
||||
})
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.log(DEBUG_PREFIX, "Error while trying to fetch", audio_file_path, ":", error);
|
||||
}
|
||||
}
|
||||
|
||||
async function updateAmbient() {
|
||||
let audio_file_path = null;
|
||||
for (const i of ambients) {
|
||||
console.debug(i)
|
||||
if (i.includes(currentBackground)) {
|
||||
audio_file_path = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (audio_file_path === null) {
|
||||
console.debug(DEBUG_PREFIX, "No ambient file found for background", currentBackground);
|
||||
const audio = $("#audio_ambient");
|
||||
audio.attr("src", "");
|
||||
audio[0].pause();
|
||||
return;
|
||||
}
|
||||
|
||||
//const audio_file_path = AMBIENT_FOLDER+currentBackground+".mp3";
|
||||
console.log(DEBUG_PREFIX, "Updating ambient");
|
||||
console.log(DEBUG_PREFIX, "Checking file", audio_file_path);
|
||||
|
||||
const audio = $("#audio_ambient");
|
||||
audio.animate({ volume: 0.0 }, 2000, function () {
|
||||
audio.attr("src", audio_file_path);
|
||||
audio[0].play();
|
||||
audio.volume = extension_settings.audio.ambient_volume * 0.01;
|
||||
audio.animate({ volume: extension_settings.audio.ambient_volume * 0.01 }, 2000);
|
||||
});
|
||||
}
|
||||
|
||||
//#############################//
|
||||
// Extension load //
|
||||
//#############################//
|
||||
|
||||
// This function is called when the extension is loaded
|
||||
jQuery(async () => {
|
||||
// This is an example of loading HTML from a file
|
||||
const windowHtml = $(await $.get(`${extensionFolderPath}/window.html`));
|
||||
|
||||
$('#extensions_settings').append(windowHtml);
|
||||
loadSettings();
|
||||
|
||||
$("#audio_bgm").attr("loop", true);
|
||||
$("#audio_ambient").attr("loop", true);
|
||||
|
||||
$("#audio_bgm").hide();
|
||||
$("#audio_ambient").hide();
|
||||
$("#audio_bgm_mute").on("click", onBGMMuteClick);
|
||||
$("#audio_ambient_mute").on("click", onAmbientMuteClick);
|
||||
|
||||
$("#audio_enabled").on("click", onEnabledClick);
|
||||
$("#audio_bgm_volume_slider").on("input", onBGMVolumeChange);
|
||||
$("#audio_ambient_volume_slider").on("input", onAmbientVolumeChange);
|
||||
|
||||
$("#audio_bgm_cooldown").on("input", onBGMCooldownInput);
|
||||
|
||||
// Reset assets container, will be redected like if ST restarted
|
||||
$("#audio_refresh_assets").on("click", function(){
|
||||
console.debug(DEBUG_PREFIX,"Refreshing audio assets");
|
||||
fallback_BGMS = null;
|
||||
ambients = null;
|
||||
characterMusics = {};
|
||||
currentCharacterBGM = null;
|
||||
currentExpressionBGM = null;
|
||||
currentBackground = null;
|
||||
})
|
||||
|
||||
// DBG
|
||||
$("#audio_debug").on("click", function () {
|
||||
if ($("#audio_debug").is(':checked')) {
|
||||
$("#audio_bgm").show();
|
||||
$("#audio_ambient").show();
|
||||
}
|
||||
else {
|
||||
$("#audio_bgm").hide();
|
||||
$("#audio_ambient").hide();
|
||||
}
|
||||
});
|
||||
//
|
||||
|
||||
const wrapper = new ModuleWorkerWrapper(moduleWorker);
|
||||
setInterval(wrapper.update.bind(wrapper), UPDATE_INTERVAL);
|
||||
moduleWorker();
|
||||
});
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"display_name": "Dynamic Audio",
|
||||
"loading_order": 14,
|
||||
"requires": [],
|
||||
"optional": ["classify"],
|
||||
"js": "index.js",
|
||||
"css": "style.css",
|
||||
"author": "Keij#6799 and Deffcolony",
|
||||
"version": "0.1.0",
|
||||
"homePage": "https://github.com/SillyTavern/SillyTavern"
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
.mixer-div {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.audio-mute-button {
|
||||
padding: 5px;
|
||||
width: 50px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.audio-mute-button-muted {
|
||||
color: red;
|
||||
}
|
||||
|
||||
#audio_refresh_assets {
|
||||
width: 50px;
|
||||
height: 30px;
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
<div id="audio_settings">
|
||||
<div class="inline-drawer">
|
||||
<div class="inline-drawer-toggle inline-drawer-header">
|
||||
<b>Dynamic Audio</b>
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
<div>
|
||||
<label class="checkbox_label" for="audio_enabled">
|
||||
<input type="checkbox" id="audio_enabled" name="audio_enabled">
|
||||
<small>Enabled</small>
|
||||
</label>
|
||||
<div id="audio_debug_div">
|
||||
<label class="checkbox_label" for="audio_debug">
|
||||
<input type="checkbox" id="audio_debug" name="audio_debug">
|
||||
<small>Debug</small>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label for="audio_refresh_assets">Refresh assets</label>
|
||||
<div id="audio_refresh_assets" class="menu_button">
|
||||
<i class="fa-solid fa-refresh fa-lg"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<label for="audio_bgm_volume_slider">Music <span id="audio_bgm_volume"></span></label>
|
||||
<div class="mixer-div">
|
||||
<div id="audio_bgm_mute" class="menu_button audio-mute-button">
|
||||
<i class="fa-solid fa-volume-high fa-lg" id="audio_bgm_mute_icon"></i>
|
||||
</div>
|
||||
<input type="range" class ="slider" id ="audio_bgm_volume_slider" value = "0" maxlength ="100">
|
||||
</div>
|
||||
<audio id="audio_bgm" controls src="">
|
||||
</div>
|
||||
<div>
|
||||
<label for="audio_ambient_volume_slider">Ambient <span id="audio_ambient_volume"></span></label>
|
||||
<div class="mixer-div">
|
||||
<div id="audio_ambient_mute" class="menu_button audio-mute-button">
|
||||
<i class="fa-solid fa-volume-high fa-lg" id="audio_ambient_mute_icon"></i>
|
||||
</div>
|
||||
<input type="range" class ="slider" id ="audio_ambient_volume_slider" value = "0" maxlength ="100">
|
||||
</div>
|
||||
<audio id="audio_ambient" controls src="">
|
||||
</div>
|
||||
<div>
|
||||
<label for="audio_bgm_cooldown">Music update cooldown (in seconds)</label>
|
||||
<input id="audio_bgm_cooldown" class="text_pole wide30p">
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<b>Hint:</b>
|
||||
<i>
|
||||
Create new folder in the
|
||||
<b>public/characters/</b>
|
||||
folder and name it as the name of the character.
|
||||
Create a folder name <b>bgm</b> inside of it.
|
||||
Put bgm music with expressions there. File names should follow the pattern:
|
||||
<it>[expression_label]_[number].mp3</it>
|
||||
By default one of the <it>neutral_[number].mp3</it> will play if classify module is not active.
|
||||
</i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -57,7 +57,6 @@ export function getCfgPrompt(guidanceScale, isNegative) {
|
|||
substituteParams(
|
||||
chat_metadata[isNegative ? metadataKeys.negative_prompt : metadataKeys.positive_prompt]
|
||||
)
|
||||
?.trim()
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -67,7 +66,6 @@ export function getCfgPrompt(guidanceScale, isNegative) {
|
|||
substituteParams(
|
||||
isNegative ? charaCfg.negative_prompt : charaCfg.positive_prompt
|
||||
)
|
||||
?.trim()
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -76,7 +74,6 @@ export function getCfgPrompt(guidanceScale, isNegative) {
|
|||
substituteParams(
|
||||
isNegative ? extension_settings.cfg.global.negative_prompt : extension_settings.cfg.global.positive_prompt
|
||||
)
|
||||
?.trim()
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { callPopup, eventSource, event_types, getRequestHeaders, saveSettingsDebounced } from "../../../script.js";
|
||||
import { dragElement, isMobile } from "../../RossAscends-mods.js";
|
||||
import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch } from "../../extensions.js";
|
||||
import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch, renderExtensionTemplate } from "../../extensions.js";
|
||||
import { loadMovingUIState, power_user } from "../../power-user.js";
|
||||
import { onlyUnique, debounce, getCharaFilename } from "../../utils.js";
|
||||
export { MODULE_NAME };
|
||||
|
@ -334,7 +334,7 @@ async function setImage(img, path) {
|
|||
expressionHolder.css('min-height', imgHeight > 100 ? imgHeight : 100);
|
||||
|
||||
//position absolute prevent the original from jumping around during transition
|
||||
img.css('position', 'absolute');
|
||||
img.css('position', 'absolute').width(imgWidth).height(imgHeight);
|
||||
expressionClone.addClass('expression-animating');
|
||||
//fade the clone in
|
||||
expressionClone.css({
|
||||
|
@ -800,20 +800,7 @@ function drawSpritesList(character, labels, sprites) {
|
|||
}
|
||||
|
||||
function getListItem(item, imageSrc, textClass) {
|
||||
return `
|
||||
<div id="${item}" class="expression_list_item">
|
||||
<div class="expression_list_buttons">
|
||||
<div class="menu_button expression_list_upload" title="Upload image">
|
||||
<i class="fa-solid fa-upload"></i>
|
||||
</div>
|
||||
<div class="menu_button expression_list_delete" title="Delete image">
|
||||
<i class="fa-solid fa-trash"></i>
|
||||
</div>
|
||||
</div>
|
||||
<span class="expression_list_title ${textClass}">${item}</span>
|
||||
<img class="expression_list_image" src="${imageSrc}" />
|
||||
</div>
|
||||
`;
|
||||
return renderExtensionTemplate(MODULE_NAME, 'list-item', { item, imageSrc, textClass });
|
||||
}
|
||||
|
||||
async function getSpritesList(name) {
|
||||
|
@ -919,7 +906,7 @@ async function setExpression(character, expression, force) {
|
|||
expressionHolder.css('min-height', imgHeight > 100 ? imgHeight : 100);
|
||||
|
||||
//position absolute prevent the original from jumping around during transition
|
||||
img.css('position', 'absolute');
|
||||
img.css('position', 'absolute').width(imgWidth).height(imgHeight);
|
||||
expressionClone.addClass('expression-animating');
|
||||
//fade the clone in
|
||||
expressionClone.css({
|
||||
|
@ -992,7 +979,6 @@ async function setExpression(character, expression, force) {
|
|||
});
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1241,54 +1227,7 @@ function setExpressionOverrideHtml(forceClear = false) {
|
|||
$('body').append(element);
|
||||
}
|
||||
function addSettings() {
|
||||
const html = `
|
||||
<div class="expression_settings">
|
||||
<div class="inline-drawer">
|
||||
<div class="inline-drawer-toggle inline-drawer-header">
|
||||
<b>Character Expressions</b>
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
|
||||
<div class="inline-drawer-content">
|
||||
<!-- Toggle button for aituber/static images -->
|
||||
<div class="toggle_button">
|
||||
<label class="switch">
|
||||
<input id="image_type_toggle" type="checkbox">
|
||||
<span class="slider round"></span>
|
||||
<label for="image_type_toggle">Image Type - talkinghead (extras)</label>
|
||||
</label>
|
||||
</div>
|
||||
<div class="offline_mode">
|
||||
<small>You are in offline mode. Click on the image below to set the expression.</small>
|
||||
</div>
|
||||
<div class="flex-container flexnowrap">
|
||||
<input id="expression_override" type="text" class="text_pole" placeholder="Override folder name" />
|
||||
<input id="expression_override_button" class="menu_button" type="submit" value="Submit" />
|
||||
</div>
|
||||
<div id="image_list"></div>
|
||||
<div class="expression_buttons flex-container spaceEvenly">
|
||||
<div id="expression_upload_pack_button" class="menu_button">
|
||||
<i class="fa-solid fa-file-zipper"></i>
|
||||
<span>Upload sprite pack (ZIP)</span>
|
||||
</div>
|
||||
<div id="expression_override_cleanup_button" class="menu_button">
|
||||
<i class="fa-solid fa-trash-can"></i>
|
||||
<span>Remove all image overrides</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="hint"><b>Hint:</b> <i>Create new folder in the <b>public/characters/</b> folder and name it as the name of the character.
|
||||
Put images with expressions there. File names should follow the pattern: <tt>[expression_label].[image_format]</tt></i></p>
|
||||
<label for="expressions_show_default"><input id="expressions_show_default" type="checkbox">Show default images (emojis) if missing</label>
|
||||
</div>
|
||||
</div>
|
||||
<form>
|
||||
<input type="file" id="expression_upload_pack" name="expression_upload_pack" accept="application/zip" hidden>
|
||||
<input type="file" id="expression_upload" name="expression_upload" accept="image/*" hidden>
|
||||
</form>
|
||||
</div>
|
||||
`;
|
||||
|
||||
$('#extensions_settings').append(html);
|
||||
$('#extensions_settings').append(renderExtensionTemplate(MODULE_NAME, 'settings'));
|
||||
$('#expression_override_button').on('click', onClickExpressionOverrideButton);
|
||||
$('#expressions_show_default').on('input', onExpressionsShowDefaultInput);
|
||||
$('#expression_upload_pack_button').on('click', onClickExpressionUploadPackButton);
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
<div id="{{item}}" class="expression_list_item">
|
||||
<div class="expression_list_buttons">
|
||||
<div class="menu_button expression_list_upload" title="Upload image">
|
||||
<i class="fa-solid fa-upload"></i>
|
||||
</div>
|
||||
<div class="menu_button expression_list_delete" title="Delete image">
|
||||
<i class="fa-solid fa-trash"></i>
|
||||
</div>
|
||||
</div>
|
||||
<span class="expression_list_title {{textClass}}">{{item}}</span>
|
||||
<img class="expression_list_image" src="{{imageSrc}}" />
|
||||
</div>
|
|
@ -0,0 +1,44 @@
|
|||
<div class="expression_settings">
|
||||
<div class="inline-drawer">
|
||||
<div class="inline-drawer-toggle inline-drawer-header">
|
||||
<b>Character Expressions</b>
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
|
||||
<div class="inline-drawer-content">
|
||||
<!-- Toggle button for aituber/static images -->
|
||||
<div class="toggle_button">
|
||||
<label class="switch">
|
||||
<input id="image_type_toggle" type="checkbox">
|
||||
<span class="slider round"></span>
|
||||
<label for="image_type_toggle">Image Type - talkinghead (extras)</label>
|
||||
</label>
|
||||
</div>
|
||||
<div class="offline_mode">
|
||||
<small>You are in offline mode. Click on the image below to set the expression.</small>
|
||||
</div>
|
||||
<div class="flex-container flexnowrap">
|
||||
<input id="expression_override" type="text" class="text_pole" placeholder="Override folder name" />
|
||||
<input id="expression_override_button" class="menu_button" type="submit" value="Submit" />
|
||||
</div>
|
||||
<div id="image_list"></div>
|
||||
<div class="expression_buttons flex-container spaceEvenly">
|
||||
<div id="expression_upload_pack_button" class="menu_button">
|
||||
<i class="fa-solid fa-file-zipper"></i>
|
||||
<span>Upload sprite pack (ZIP)</span>
|
||||
</div>
|
||||
<div id="expression_override_cleanup_button" class="menu_button">
|
||||
<i class="fa-solid fa-trash-can"></i>
|
||||
<span>Remove all image overrides</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="hint"><b>Hint:</b> <i>Create new folder in the <b>public/characters/</b> folder and name it as the name of the character.
|
||||
Put images with expressions there. File names should follow the pattern: <tt>[expression_label].[image_format]</tt></i></p>
|
||||
<label for="expressions_show_default"><input id="expressions_show_default" type="checkbox">Show default images (emojis) if missing</label>
|
||||
</div>
|
||||
</div>
|
||||
<form>
|
||||
<input type="file" id="expression_upload_pack" name="expression_upload_pack" accept="application/zip" hidden>
|
||||
<input type="file" id="expression_upload" name="expression_upload" accept="image/*" hidden>
|
||||
</form>
|
||||
</div>
|
|
@ -6,13 +6,13 @@
|
|||
|
||||
#expression-wrapper {
|
||||
display: flex;
|
||||
height: calc(100vh - 40px);
|
||||
height: calc(100vh - var(--topBarBlockSize));
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
#visual-novel-wrapper {
|
||||
display: flex;
|
||||
height: calc(100vh - 40px);
|
||||
height: calc(100vh - var(--topBarBlockSize));
|
||||
width: 100vw;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
@ -180,4 +180,4 @@ img.expression.default {
|
|||
div.expression {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -567,7 +567,7 @@ jQuery(function () {
|
|||
<div class="radio_group">
|
||||
<label>
|
||||
<input type="radio" name="memory_position" value="0" />
|
||||
After scenario
|
||||
After Main Prompt / Story String
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="memory_position" value="1" />
|
||||
|
|
|
@ -55,6 +55,9 @@ const defaultSettings = {
|
|||
}
|
||||
|
||||
function loadSettings() {
|
||||
if (extension_settings.rvc === undefined)
|
||||
extension_settings.rvc = {};
|
||||
|
||||
if (Object.keys(extension_settings.rvc).length === 0) {
|
||||
Object.assign(extension_settings.rvc, defaultSettings)
|
||||
}
|
||||
|
@ -174,9 +177,9 @@ async function onDeleteClick() {
|
|||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
async function onClickUpload() {
|
||||
async function onChangeUploadFiles() {
|
||||
const url = new URL(getApiUrl());
|
||||
const inputFiles = $("#rvc_model_upload_file").get(0).files;
|
||||
const inputFiles = $("#rvc_model_upload_files").get(0).files;
|
||||
let formData = new FormData();
|
||||
|
||||
for (const file of inputFiles)
|
||||
|
@ -195,7 +198,7 @@ async function onClickUpload() {
|
|||
throw new Error(`HTTP ${apiResult.status}: ${await apiResult.text()}`);
|
||||
}
|
||||
|
||||
alert('The file has been uploaded successfully.');
|
||||
alert('The files have been uploaded successfully.');
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
|
@ -208,6 +211,7 @@ $(document).ready(function () {
|
|||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
<h4 class="center">Characters Voice Mapping</h4>
|
||||
<div>
|
||||
<label class="checkbox_label" for="rvc_enabled">
|
||||
<input type="checkbox" id="rvc_enabled" name="rvc_enabled">
|
||||
|
@ -218,54 +222,103 @@ $(document).ready(function () {
|
|||
placeholder="Voice map will appear here for debug purpose"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label for="rvc_character_select">Character:</label>
|
||||
<select id="rvc_character_select">
|
||||
<!-- Populated by JS -->
|
||||
</select>
|
||||
<label for="rvc_model_select">Voice:</label>
|
||||
<select id="rvc_model_select">
|
||||
<!-- Populated by JS -->
|
||||
</select>
|
||||
<div>
|
||||
<label for="rvc_model_upload_file">Select models to upload (zip files)</label>
|
||||
<input
|
||||
type="file"
|
||||
id="rvc_model_upload_file"
|
||||
accept=".zip,.rar,.7zip,.7z" multiple />
|
||||
<button id="rvc_model_upload_button"> Upload </button>
|
||||
<button id="rvc_model_refresh_button"> Refresh Voices </button>
|
||||
<div class="background_controls">
|
||||
<label for="rvc_character_select">Character:</label>
|
||||
<select id="rvc_character_select">
|
||||
<!-- Populated by JS -->
|
||||
</select>
|
||||
<div id="rvc_delete" class="menu_button">
|
||||
<i class="fa-solid fa-times"></i>
|
||||
Remove
|
||||
</div>
|
||||
</div>
|
||||
<div class="background_controls">
|
||||
<label for="rvc_model_select">Voice:</label>
|
||||
<select id="rvc_model_select">
|
||||
<!-- Populated by JS -->
|
||||
</select>
|
||||
<div id="rvc_model_refresh_button" class="menu_button">
|
||||
<i class="fa-solid fa-refresh"></i>
|
||||
<!-- Refresh -->
|
||||
</div>
|
||||
<div id="rvc_model_upload_select_button" class="menu_button">
|
||||
<i class="fa-solid fa-upload"></i>
|
||||
Upload
|
||||
</div>
|
||||
<input
|
||||
type="file"
|
||||
id="rvc_model_upload_files"
|
||||
accept=".zip,.rar,.7zip,.7z" multiple />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<small>
|
||||
Upload one archive per model. With .pth and .index (optional) inside.<br/>
|
||||
Supported format: .zip .rar .7zip .7z
|
||||
</small>
|
||||
</div>
|
||||
<div>
|
||||
<h4>Model Settings</h4>
|
||||
</div>
|
||||
<div>
|
||||
<label for="rvc_pitch_extraction">
|
||||
Pitch Extraction
|
||||
</label>
|
||||
<select id="rvc_pitch_extraction">
|
||||
<option value="dio">dio</option>
|
||||
<option value="pm">pm</option>
|
||||
<option value="harvest">harvest</option>
|
||||
<option value="torchcrepe">torchcrepe</option>
|
||||
<option value="rmvpe">rmvpe</option>
|
||||
<option value="">None</option>
|
||||
</select>
|
||||
<small>
|
||||
Tips: dio and pm faster, harvest slower but good.<br/>
|
||||
Torchcrepe and rmvpe are good but uses GPU.
|
||||
</small>
|
||||
</div>
|
||||
<div>
|
||||
<label for="rvc_index_rate">
|
||||
Search feature ratio (<span id="rvc_index_rate_value"></span>)
|
||||
</label>
|
||||
<input id="rvc_index_rate" type="range" min="0" max="1" step="0.01" value="0.5" />
|
||||
<small>
|
||||
Controls accent strength, too high may produce artifact.
|
||||
</small>
|
||||
</div>
|
||||
<div>
|
||||
<label for="rvc_filter_radius">Filter radius (<span id="rvc_filter_radius_value"></span>)</label>
|
||||
<input id="rvc_filter_radius" type="range" min="0" max="7" step="1" value="3" />
|
||||
<small>
|
||||
Higher can reduce breathiness but may increase run time.
|
||||
</small>
|
||||
</div>
|
||||
<div>
|
||||
<label for="rvc_pitch_offset">Pitch offset (<span id="rvc_pitch_offset_value"></span>)</label>
|
||||
<input id="rvc_pitch_offset" type="range" min="-20" max="20" step="1" value="0" />
|
||||
<small>
|
||||
Recommended +12 key for male to female conversion and -12 key for female to male conversion.
|
||||
</small>
|
||||
</div>
|
||||
<div>
|
||||
<label for="rvc_rms_mix_rate">Mix rate (<span id="rvc_rms_mix_rate_value"></span>)</label>
|
||||
<input id="rvc_rms_mix_rate" type="range" min="0" max="1" step="0.01" value="1" />
|
||||
<small>
|
||||
Closer to 0 is closer to TTS and 1 is closer to trained voice.
|
||||
Can help mask noise and sound more natural when set relatively low.
|
||||
</small>
|
||||
</div>
|
||||
<div>
|
||||
<label for="rvc_protect">Protect amount (<span id="rvc_protect_value"></span>)</label>
|
||||
<input id="rvc_protect" type="range" min="0" max="1" step="0.01" value="0.33" />
|
||||
<small>
|
||||
Avoid non voice sounds. Lower is more being ignored.
|
||||
</small>
|
||||
</div>
|
||||
<span>Select Pitch Extraction</span> </br>
|
||||
<select id="rvc_pitch_extraction">
|
||||
<option value="dio">dio</option>
|
||||
<option value="pm">pm</option>
|
||||
<option value="harvest">harvest</option>
|
||||
<option value="torchcrepe">torchcrepe</option>
|
||||
<option value="rmvpe">rmvpe</option>
|
||||
<option value="">None</option>
|
||||
</select>
|
||||
<label for="rvc_index_rate">
|
||||
Index rate for feature retrieval (<span id="rvc_index_rate_value"></span>)
|
||||
</label>
|
||||
<input id="rvc_index_rate" type="range" min="0" max="1" step="0.01" value="0.5" />
|
||||
|
||||
<label for="rvc_filter_radius">Filter radius (<span id="rvc_filter_radius_value"></span>)</label>
|
||||
<input id="rvc_filter_radius" type="range" min="0" max="7" step="1" value="3" />
|
||||
|
||||
<label for="rvc_pitch_offset">Pitch offset (<span id="rvc_pitch_offset_value"></span>)</label>
|
||||
<input id="rvc_pitch_offset" type="range" min="-100" max="100" step="1" value="0" />
|
||||
|
||||
<label for="rvc_rms_mix_rate">Mix rate (<span id="rvc_rms_mix_rate_value"></span>)</label>
|
||||
<input id="rvc_rms_mix_rate" type="range" min="0" max="1" step="0.01" value="1" />
|
||||
|
||||
<label for="rvc_protect">Protect amount (<span id="rvc_protect_value"></span>)</label>
|
||||
<input id="rvc_protect" type="range" min="0" max="1" step="0.01" value="0.33" />
|
||||
|
||||
<div id="rvc_status">
|
||||
</div>
|
||||
<div class="rvc_buttons">
|
||||
<input id="rvc_apply" class="menu_button" type="submit" value="Apply" />
|
||||
<input id="rvc_delete" class="menu_button" type="submit" value="Delete" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -284,8 +337,11 @@ $(document).ready(function () {
|
|||
$("#rvc_apply").on("click", onApplyClick);
|
||||
$("#rvc_delete").on("click", onDeleteClick);
|
||||
|
||||
$("#rvc_model_upload_file").show();
|
||||
$("#rvc_model_upload_button").on("click", onClickUpload);
|
||||
$("#rvc_model_upload_files").hide();
|
||||
$("#rvc_model_upload_select_button").on("click", function() {$("#rvc_model_upload_files").click()});
|
||||
|
||||
$("#rvc_model_upload_files").on("change", onChangeUploadFiles);
|
||||
//$("#rvc_model_upload_button").on("click", onClickUpload);
|
||||
$("#rvc_model_refresh_button").on("click", refreshVoiceList);
|
||||
|
||||
}
|
||||
|
@ -323,7 +379,7 @@ async function get_models_list(model_id) {
|
|||
/*
|
||||
Send an audio file to RVC to convert voice
|
||||
*/
|
||||
async function rvcVoiceConversion(response, character) {
|
||||
async function rvcVoiceConversion(response, character, text) {
|
||||
let apiResult
|
||||
|
||||
// Check voice map
|
||||
|
@ -341,8 +397,6 @@ async function rvcVoiceConversion(response, character) {
|
|||
|
||||
const voice_settings = extension_settings.rvc.voiceMap[character];
|
||||
|
||||
console.log("Sending tts audio data to RVC on extras server")
|
||||
|
||||
var requestData = new FormData();
|
||||
requestData.append('AudioFile', audioData, 'record');
|
||||
requestData.append("json", JSON.stringify({
|
||||
|
@ -352,9 +406,12 @@ async function rvcVoiceConversion(response, character) {
|
|||
"indexRate": voice_settings["indexRate"],
|
||||
"filterRadius": voice_settings["filterRadius"],
|
||||
"rmsMixRate": voice_settings["rmsMixRate"],
|
||||
"protect": voice_settings["protect"]
|
||||
"protect": voice_settings["protect"],
|
||||
"text": text
|
||||
}));
|
||||
|
||||
console.log("Sending tts audio data to RVC on extras server",requestData)
|
||||
|
||||
const url = new URL(getApiUrl());
|
||||
url.pathname = '/api/voice-conversion/rvc/process-audio';
|
||||
|
||||
|
@ -405,11 +462,13 @@ async function moduleWorker() {
|
|||
|
||||
function updateCharactersList() {
|
||||
let currentcharacters = new Set();
|
||||
for (const i of getContext().characters) {
|
||||
const context = getContext();
|
||||
for (const i of context.characters) {
|
||||
currentcharacters.add(i.name);
|
||||
}
|
||||
|
||||
currentcharacters = Array.from(currentcharacters)
|
||||
currentcharacters = Array.from(currentcharacters);
|
||||
currentcharacters.unshift(context.name1);
|
||||
|
||||
if (JSON.stringify(charactersList) !== JSON.stringify(currentcharacters)) {
|
||||
charactersList = currentcharacters
|
||||
|
|
|
@ -11,7 +11,6 @@ export { CoquiTtsProvider }
|
|||
const DEBUG_PREFIX = "<Coqui TTS module> ";
|
||||
const UPDATE_INTERVAL = 1000;
|
||||
|
||||
let inApiCall = false;
|
||||
let charactersList = []; // Updated with module worker
|
||||
let coquiApiModels = {}; // Initialized only once
|
||||
let coquiApiModelsFull = {}; // Initialized only once
|
||||
|
@ -40,6 +39,12 @@ const languageLabels = {
|
|||
"ja": "Japanese"
|
||||
}
|
||||
|
||||
|
||||
const defaultSettings = {
|
||||
voiceMap: "",
|
||||
voiceMapDict: {}
|
||||
}
|
||||
|
||||
function throwIfModuleMissing() {
|
||||
if (!modules.includes('coqui-tts')) {
|
||||
toastr.error(`Add coqui-tts to enable-modules and restart the Extras API.`, "Coqui TTS module not loaded.", { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
|
||||
|
@ -54,11 +59,13 @@ function resetModelSettings() {
|
|||
|
||||
function updateCharactersList() {
|
||||
let currentcharacters = new Set();
|
||||
for (const i of getContext().characters) {
|
||||
const context = getContext();
|
||||
for (const i of context.characters) {
|
||||
currentcharacters.add(i.name);
|
||||
}
|
||||
|
||||
currentcharacters = Array.from(currentcharacters)
|
||||
currentcharacters = Array.from(currentcharacters);
|
||||
currentcharacters.unshift(context.name1);
|
||||
|
||||
if (JSON.stringify(charactersList) !== JSON.stringify(currentcharacters)) {
|
||||
charactersList = currentcharacters
|
||||
|
@ -83,11 +90,13 @@ class CoquiTtsProvider {
|
|||
// Extension UI and Settings //
|
||||
//#############################//
|
||||
|
||||
settings
|
||||
static instance;
|
||||
settings = {};
|
||||
|
||||
defaultSettings = {
|
||||
voiceMap: "",
|
||||
voiceMapDict: {}
|
||||
// Singleton to allow acces to instance in event functions
|
||||
constructor() {
|
||||
if (CoquiTtsProvider.instance === undefined)
|
||||
CoquiTtsProvider.instance = this;
|
||||
}
|
||||
|
||||
get settingsHtml() {
|
||||
|
@ -145,8 +154,12 @@ class CoquiTtsProvider {
|
|||
}
|
||||
|
||||
loadSettings(settings) {
|
||||
if (Object.keys(this.settings).length === 0) {
|
||||
Object.assign(this.settings, defaultSettings)
|
||||
}
|
||||
|
||||
// Only accept keys defined in defaultSettings
|
||||
this.settings = this.defaultSettings
|
||||
this.settings = defaultSettings;
|
||||
|
||||
for (const key in settings) {
|
||||
if (key in this.settings) {
|
||||
|
@ -156,7 +169,7 @@ class CoquiTtsProvider {
|
|||
}
|
||||
}
|
||||
|
||||
this.updateVoiceMap(); // Overide any manual modification
|
||||
CoquiTtsProvider.updateVoiceMap(); // Overide any manual modification
|
||||
|
||||
$("#coqui_api_model_div").hide();
|
||||
$("#coqui_local_model_div").hide();
|
||||
|
@ -167,24 +180,12 @@ class CoquiTtsProvider {
|
|||
$("#coqui_api_model_install_status").hide();
|
||||
$("#coqui_api_model_install_button").hide();
|
||||
|
||||
let that = this
|
||||
$("#coqui_model_origin").on("change", function () { that.onModelOriginChange() });
|
||||
$("#coqui_api_language").on("change", function () { that.onModelLanguageChange() });
|
||||
$("#coqui_api_model_name").on("change", function () { that.onModelNameChange() });
|
||||
$("#coqui_model_origin").on("change", CoquiTtsProvider.onModelOriginChange);
|
||||
$("#coqui_api_language").on("change", CoquiTtsProvider.onModelLanguageChange);
|
||||
$("#coqui_api_model_name").on("change", CoquiTtsProvider.onModelNameChange);
|
||||
$("#coqui_remove_char_mapping").on("click", CoquiTtsProvider.onRemoveClick);
|
||||
|
||||
$("#coqui_remove_char_mapping").on("click", function () { that.onRemoveClick() });
|
||||
|
||||
// Load characters list
|
||||
$('#coqui_character_select')
|
||||
.find('option')
|
||||
.remove()
|
||||
.end()
|
||||
.append('<option value="none">Select Character</option>')
|
||||
.val('none')
|
||||
|
||||
for (const charName of charactersList) {
|
||||
$("#coqui_character_select").append(new Option(charName, charName));
|
||||
}
|
||||
updateCharactersList();
|
||||
|
||||
// Load coqui-api settings from json file
|
||||
fetch("/scripts/extensions/tts/coqui_api_models_settings.json")
|
||||
|
@ -192,18 +193,6 @@ class CoquiTtsProvider {
|
|||
.then(json => {
|
||||
coquiApiModels = json;
|
||||
console.debug(DEBUG_PREFIX,"initialized coqui-api model list to", coquiApiModels);
|
||||
/*
|
||||
$('#coqui_api_language')
|
||||
.find('option')
|
||||
.remove()
|
||||
.end()
|
||||
.append('<option value="none">Select model language</option>')
|
||||
.val('none');
|
||||
|
||||
for(let language in coquiApiModels) {
|
||||
$("#coqui_api_language").append(new Option(languageLabels[language],language));
|
||||
console.log(DEBUG_PREFIX,"added language",language);
|
||||
}*/
|
||||
});
|
||||
|
||||
// Load coqui-api FULL settings from json file
|
||||
|
@ -212,49 +201,33 @@ class CoquiTtsProvider {
|
|||
.then(json => {
|
||||
coquiApiModelsFull = json;
|
||||
console.debug(DEBUG_PREFIX,"initialized coqui-api full model list to", coquiApiModelsFull);
|
||||
/*
|
||||
$('#coqui_api_full_language')
|
||||
.find('option')
|
||||
.remove()
|
||||
.end()
|
||||
.append('<option value="none">Select model language</option>')
|
||||
.val('none');
|
||||
|
||||
for(let language in coquiApiModelsFull) {
|
||||
$("#coqui_api_full_language").append(new Option(languageLabels[language],language));
|
||||
console.log(DEBUG_PREFIX,"added language",language);
|
||||
}*/
|
||||
});
|
||||
}
|
||||
|
||||
updateVoiceMap() {
|
||||
this.settings.voiceMap = "";
|
||||
for (let i in this.settings.voiceMapDict) {
|
||||
const voice_settings = this.settings.voiceMapDict[i];
|
||||
this.settings.voiceMap += i + ":" + voice_settings["model_id"];
|
||||
static updateVoiceMap() {
|
||||
CoquiTtsProvider.instance.settings.voiceMap = "";
|
||||
for (let i in CoquiTtsProvider.instance.settings.voiceMapDict) {
|
||||
const voice_settings = CoquiTtsProvider.instance.settings.voiceMapDict[i];
|
||||
CoquiTtsProvider.instance.settings.voiceMap += i + ":" + voice_settings["model_id"];
|
||||
|
||||
if (voice_settings["model_language"] != null)
|
||||
this.settings.voiceMap += "[" + voice_settings["model_language"] + "]";
|
||||
CoquiTtsProvider.instance.settings.voiceMap += "[" + voice_settings["model_language"] + "]";
|
||||
|
||||
if (voice_settings["model_speaker"] != null)
|
||||
this.settings.voiceMap += "[" + voice_settings["model_speaker"] + "]";
|
||||
CoquiTtsProvider.instance.settings.voiceMap += "[" + voice_settings["model_speaker"] + "]";
|
||||
|
||||
this.settings.voiceMap += ",";
|
||||
CoquiTtsProvider.instance.settings.voiceMap += ",";
|
||||
}
|
||||
$("#tts_voice_map").val(this.settings.voiceMap);
|
||||
extension_settings.tts.Coqui = this.settings;
|
||||
$("#tts_voice_map").val(CoquiTtsProvider.instance.settings.voiceMap);
|
||||
//extension_settings.tts.Coqui = extension_settings.tts.Coqui;
|
||||
}
|
||||
|
||||
onSettingsChange() {
|
||||
console.debug(DEBUG_PREFIX, "Settings changes", this.settings);
|
||||
extension_settings.tts.Coqui = this.settings;
|
||||
//console.debug(DEBUG_PREFIX, "Settings changes", CoquiTtsProvider.instance.settings);
|
||||
CoquiTtsProvider.updateVoiceMap();
|
||||
}
|
||||
|
||||
async onApplyClick() {
|
||||
if (inApiCall) {
|
||||
return; // TOdo block dropdown
|
||||
}
|
||||
|
||||
const character = $("#coqui_character_select").val();
|
||||
const model_origin = $("#coqui_model_origin").val();
|
||||
const model_language = $("#coqui_api_language").val();
|
||||
|
@ -262,16 +235,15 @@ class CoquiTtsProvider {
|
|||
let model_setting_language = $("#coqui_api_model_settings_language").val();
|
||||
let model_setting_speaker = $("#coqui_api_model_settings_speaker").val();
|
||||
|
||||
|
||||
if (character === "none") {
|
||||
toastr.error(`Character not selected, please select one.`, DEBUG_PREFIX + " voice mapping character", { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
|
||||
this.updateVoiceMap(); // Overide any manual modification
|
||||
CoquiTtsProvider.updateVoiceMap(); // Overide any manual modification
|
||||
return;
|
||||
}
|
||||
|
||||
if (model_origin == "none") {
|
||||
toastr.error(`Origin not selected, please select one.`, DEBUG_PREFIX + " voice mapping origin", { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
|
||||
this.updateVoiceMap(); // Overide any manual modification
|
||||
CoquiTtsProvider.updateVoiceMap(); // Overide any manual modification
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -280,25 +252,25 @@ class CoquiTtsProvider {
|
|||
|
||||
if (model_name == "none") {
|
||||
toastr.error(`Model not selected, please select one.`, DEBUG_PREFIX + " voice mapping model", { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
|
||||
this.updateVoiceMap(); // Overide any manual modification
|
||||
CoquiTtsProvider.updateVoiceMap(); // Overide any manual modification
|
||||
return;
|
||||
}
|
||||
|
||||
this.settings.voiceMapDict[character] = { model_type: "local", model_id: "local/" + model_id };
|
||||
console.debug(DEBUG_PREFIX, "Registered new voice map: ", character, ":", this.settings.voiceMapDict[character]);
|
||||
this.updateVoiceMap(); // Overide any manual modification
|
||||
CoquiTtsProvider.instance.settings.voiceMapDict[character] = { model_type: "local", model_id: "local/" + model_id };
|
||||
console.debug(DEBUG_PREFIX, "Registered new voice map: ", character, ":", CoquiTtsProvider.instance.settings.voiceMapDict[character]);
|
||||
CoquiTtsProvider.updateVoiceMap(); // Overide any manual modification
|
||||
return;
|
||||
}
|
||||
|
||||
if (model_language == "none") {
|
||||
toastr.error(`Language not selected, please select one.`, DEBUG_PREFIX + " voice mapping language", { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
|
||||
this.updateVoiceMap(); // Overide any manual modification
|
||||
CoquiTtsProvider.updateVoiceMap(); // Overide any manual modification
|
||||
return;
|
||||
}
|
||||
|
||||
if (model_name == "none") {
|
||||
toastr.error(`Model not selected, please select one.`, DEBUG_PREFIX + " voice mapping model", { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
|
||||
this.updateVoiceMap(); // Overide any manual modification
|
||||
CoquiTtsProvider.updateVoiceMap(); // Overide any manual modification
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -327,13 +299,13 @@ class CoquiTtsProvider {
|
|||
return;
|
||||
}
|
||||
|
||||
console.debug(DEBUG_PREFIX, "Current voice map: ", this.settings.voiceMap);
|
||||
console.debug(DEBUG_PREFIX, "Current voice map: ", CoquiTtsProvider.instance.settings.voiceMap);
|
||||
|
||||
this.settings.voiceMapDict[character] = { model_type: "coqui-api", model_id: model_id, model_language: model_setting_language, model_speaker: model_setting_speaker };
|
||||
CoquiTtsProvider.instance.settings.voiceMapDict[character] = { model_type: "coqui-api", model_id: model_id, model_language: model_setting_language, model_speaker: model_setting_speaker };
|
||||
|
||||
console.debug(DEBUG_PREFIX, "Registered new voice map: ", character, ":", this.settings.voiceMapDict[character]);
|
||||
console.debug(DEBUG_PREFIX, "Registered new voice map: ", character, ":", CoquiTtsProvider.instance.settings.voiceMapDict[character]);
|
||||
|
||||
this.updateVoiceMap();
|
||||
CoquiTtsProvider.updateVoiceMap();
|
||||
|
||||
let successMsg = character + ":" + model_id;
|
||||
if (model_setting_language != null)
|
||||
|
@ -352,7 +324,7 @@ class CoquiTtsProvider {
|
|||
return output;
|
||||
}
|
||||
|
||||
async onRemoveClick() {
|
||||
static async onRemoveClick() {
|
||||
const character = $("#coqui_character_select").val();
|
||||
|
||||
if (character === "none") {
|
||||
|
@ -361,11 +333,11 @@ class CoquiTtsProvider {
|
|||
}
|
||||
|
||||
// Todo erase from voicemap
|
||||
delete (this.settings.voiceMapDict[character]);
|
||||
this.updateVoiceMap(); // TODO
|
||||
delete (CoquiTtsProvider.instance.settings.voiceMapDict[character]);
|
||||
CoquiTtsProvider.updateVoiceMap(); // TODO
|
||||
}
|
||||
|
||||
async onModelOriginChange() {
|
||||
static async onModelOriginChange() {
|
||||
throwIfModuleMissing()
|
||||
resetModelSettings();
|
||||
const model_origin = $('#coqui_model_origin').val();
|
||||
|
@ -378,6 +350,9 @@ class CoquiTtsProvider {
|
|||
// show coqui model selected list (SAFE)
|
||||
if (model_origin == "coqui-api") {
|
||||
$("#coqui_local_model_div").hide();
|
||||
$("#coqui_api_model_div").hide();
|
||||
$("#coqui_api_model_name").hide();
|
||||
$("#coqui_api_model_settings").hide();
|
||||
|
||||
$('#coqui_api_language')
|
||||
.find('option')
|
||||
|
@ -400,6 +375,9 @@ class CoquiTtsProvider {
|
|||
// show coqui model full list (UNSAFE)
|
||||
if (model_origin == "coqui-api-full") {
|
||||
$("#coqui_local_model_div").hide();
|
||||
$("#coqui_api_model_div").hide();
|
||||
$("#coqui_api_model_name").hide();
|
||||
$("#coqui_api_model_settings").hide();
|
||||
|
||||
$('#coqui_api_language')
|
||||
.find('option')
|
||||
|
@ -427,7 +405,7 @@ class CoquiTtsProvider {
|
|||
}
|
||||
}
|
||||
|
||||
async onModelLanguageChange() {
|
||||
static async onModelLanguageChange() {
|
||||
throwIfModuleMissing();
|
||||
resetModelSettings();
|
||||
$("#coqui_api_model_settings").hide();
|
||||
|
@ -460,7 +438,7 @@ class CoquiTtsProvider {
|
|||
}
|
||||
}
|
||||
|
||||
async onModelNameChange() {
|
||||
static async onModelNameChange() {
|
||||
throwIfModuleMissing();
|
||||
resetModelSettings();
|
||||
$("#coqui_api_model_settings").hide();
|
||||
|
@ -551,8 +529,6 @@ class CoquiTtsProvider {
|
|||
$("#coqui_api_model_install_status").text("Model not found on extras server");
|
||||
}
|
||||
|
||||
const onModelNameChange_pointer = this.onModelNameChange;
|
||||
|
||||
$("#coqui_api_model_install_button").off("click").on("click", async function () {
|
||||
try {
|
||||
$("#coqui_api_model_install_status").text("Downloading model...");
|
||||
|
@ -566,7 +542,7 @@ class CoquiTtsProvider {
|
|||
if (apiResult["status"] == "done") {
|
||||
$("#coqui_api_model_install_status").text("Model installed and ready to use!");
|
||||
$("#coqui_api_model_install_button").hide();
|
||||
onModelNameChange_pointer();
|
||||
CoquiTtsProvider.onModelNameChange();
|
||||
}
|
||||
|
||||
if (apiResult["status"] == "downloading") {
|
||||
|
@ -577,7 +553,7 @@ class CoquiTtsProvider {
|
|||
} catch (error) {
|
||||
console.error(error)
|
||||
toastr.error(error, DEBUG_PREFIX + " error with model download", { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
|
||||
onModelNameChange_pointer();
|
||||
CoquiTtsProvider.onModelNameChange();
|
||||
}
|
||||
// will refresh model status
|
||||
});
|
||||
|
|
|
@ -412,7 +412,7 @@ async function tts(text, voiceId, char) {
|
|||
|
||||
// RVC injection
|
||||
if (extension_settings.rvc.enabled)
|
||||
response = await rvcVoiceConversion(response, char)
|
||||
response = await rvcVoiceConversion(response, char, text)
|
||||
|
||||
addAudioJob(response)
|
||||
completeTtsJob()
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
power_user,
|
||||
context_presets,
|
||||
} from "./power-user.js";
|
||||
import { resetScrollHeight } from "./utils.js";
|
||||
|
||||
/**
|
||||
* @type {any[]} Instruct mode presets.
|
||||
|
@ -16,7 +17,8 @@ const controls = [
|
|||
{ id: "instruct_enabled", property: "enabled", isCheckbox: true },
|
||||
{ id: "instruct_wrap", property: "wrap", isCheckbox: true },
|
||||
{ id: "instruct_system_prompt", property: "system_prompt", isCheckbox: false },
|
||||
{ id: "instruct_system_sequence", property: "system_sequence", isCheckbox: false },
|
||||
{ id: "instruct_system_sequence_prefix", property: "system_sequence_prefix", isCheckbox: false },
|
||||
{ id: "instruct_system_sequence_suffix", property: "system_sequence_suffix", isCheckbox: false },
|
||||
{ id: "instruct_separator_sequence", property: "separator_sequence", isCheckbox: false },
|
||||
{ id: "instruct_input_sequence", property: "input_sequence", isCheckbox: false },
|
||||
{ id: "instruct_output_sequence", property: "output_sequence", isCheckbox: false },
|
||||
|
@ -24,6 +26,7 @@ const controls = [
|
|||
{ id: "instruct_names", property: "names", isCheckbox: true },
|
||||
{ id: "instruct_macro", property: "macro", isCheckbox: true },
|
||||
{ id: "instruct_names_force_groups", property: "names_force_groups", isCheckbox: true },
|
||||
{ id: "instruct_first_output_sequence", property: "first_output_sequence", isCheckbox: false },
|
||||
{ id: "instruct_last_output_sequence", property: "last_output_sequence", isCheckbox: false },
|
||||
{ id: "instruct_activation_regex", property: "activation_regex", isCheckbox: false },
|
||||
];
|
||||
|
@ -53,6 +56,9 @@ export function loadInstructMode(data) {
|
|||
$element.on('input', function () {
|
||||
power_user.instruct[control.property] = control.isCheckbox ? !!$(this).prop('checked') : $(this).val();
|
||||
saveSettingsDebounced();
|
||||
if (!control.isCheckbox) {
|
||||
resetScrollHeight($element);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -200,9 +206,10 @@ export function getInstructStoppingSequences() {
|
|||
if (power_user.instruct.enabled) {
|
||||
const input_sequence = power_user.instruct.input_sequence;
|
||||
const output_sequence = power_user.instruct.output_sequence;
|
||||
const first_output_sequence = power_user.instruct.first_output_sequence;
|
||||
const last_output_sequence = power_user.instruct.last_output_sequence;
|
||||
|
||||
const combined_sequence = `${input_sequence}\n${output_sequence}\n${last_output_sequence}`;
|
||||
const combined_sequence = `${input_sequence}\n${output_sequence}\n${first_output_sequence}\n${last_output_sequence}`;
|
||||
|
||||
combined_sequence.split('\n').filter((line, index, self) => self.indexOf(line) === index).forEach(addInstructSequence);
|
||||
}
|
||||
|
@ -210,6 +217,11 @@ export function getInstructStoppingSequences() {
|
|||
return result;
|
||||
}
|
||||
|
||||
export const force_output_sequence = {
|
||||
FIRST: 1,
|
||||
LAST: 2,
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats instruct mode chat message.
|
||||
* @param {string} name Character name.
|
||||
|
@ -219,10 +231,10 @@ export function getInstructStoppingSequences() {
|
|||
* @param {string} forceAvatar Force avatar string.
|
||||
* @param {string} name1 User name.
|
||||
* @param {string} name2 Character name.
|
||||
* @param {boolean} forceLastOutputSequence Force to use last outline sequence (if configured).
|
||||
* @param {boolean|number} forceOutputSequence Force to use first/last output sequence (if configured).
|
||||
* @returns {string} Formatted instruct mode chat message.
|
||||
*/
|
||||
export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvatar, name1, name2, forceLastOutputSequence) {
|
||||
export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvatar, name1, name2, forceOutputSequence) {
|
||||
let includeNames = isNarrator ? false : power_user.instruct.names;
|
||||
|
||||
if (!isNarrator && power_user.instruct.names_force_groups && (selected_group || forceAvatar)) {
|
||||
|
@ -231,8 +243,12 @@ export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvata
|
|||
|
||||
let sequence = (isUser || isNarrator) ? power_user.instruct.input_sequence : power_user.instruct.output_sequence;
|
||||
|
||||
if (sequence === power_user.instruct.output_sequence && forceLastOutputSequence && power_user.instruct.last_output_sequence) {
|
||||
sequence = power_user.instruct.last_output_sequence;
|
||||
if (forceOutputSequence && sequence === power_user.instruct.output_sequence) {
|
||||
if (forceOutputSequence === force_output_sequence.FIRST && power_user.instruct.first_output_sequence) {
|
||||
sequence = power_user.instruct.first_output_sequence;
|
||||
} else if (forceOutputSequence === force_output_sequence.LAST && power_user.instruct.last_output_sequence) {
|
||||
sequence = power_user.instruct.last_output_sequence;
|
||||
}
|
||||
}
|
||||
|
||||
if (power_user.instruct.macro) {
|
||||
|
@ -254,14 +270,14 @@ export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvata
|
|||
* @returns {string} Formatted instruct mode system prompt.
|
||||
*/
|
||||
export function formatInstructModeSystemPrompt(systemPrompt){
|
||||
if (power_user.instruct.system_sequence) {
|
||||
const separator = power_user.instruct.wrap ? '\n' : '';
|
||||
const separator = power_user.instruct.wrap ? '\n' : '';
|
||||
|
||||
if (power_user.instruct.system_sequence.includes("{{sys}}")) {
|
||||
return power_user.instruct.system_sequence.replace(/{{sys}}/gi, systemPrompt);
|
||||
} else {
|
||||
return power_user.instruct.system_sequence + separator + systemPrompt;
|
||||
}
|
||||
if (power_user.instruct.system_sequence_prefix) {
|
||||
systemPrompt = power_user.instruct.system_sequence_prefix + separator + systemPrompt;
|
||||
}
|
||||
|
||||
if (power_user.instruct.system_sequence_suffix) {
|
||||
systemPrompt = systemPrompt + separator + power_user.instruct.system_sequence_suffix;
|
||||
}
|
||||
|
||||
return systemPrompt;
|
||||
|
|
|
@ -185,7 +185,7 @@ function loadNovelSettingsUi(ui_settings) {
|
|||
$("#top_a_novel").val(ui_settings.top_a);
|
||||
$("#top_a_counter_novel").text(Number(ui_settings.top_a).toFixed(2));
|
||||
$("#typical_p_novel").val(ui_settings.typical_p);
|
||||
$("#typical_p_counter_novel").text(Number(ui_settings.typical_p).toFixed(2));
|
||||
$("#typical_p_counter_novel").text(Number(ui_settings.typical_p).toFixed(3));
|
||||
$("#cfg_scale_novel").val(ui_settings.cfg_scale);
|
||||
$("#cfg_scale_counter_novel").text(Number(ui_settings.cfg_scale).toFixed(2));
|
||||
$("#phrase_rep_pen_novel").val(ui_settings.phrase_rep_pen || "off");
|
||||
|
@ -269,8 +269,8 @@ const sliders = [
|
|||
{
|
||||
sliderId: "#typical_p_novel",
|
||||
counterId: "#typical_p_counter_novel",
|
||||
format: (val) => Number(val).toFixed(2),
|
||||
setValue: (val) => { nai_settings.typical_p = Number(val).toFixed(2); },
|
||||
format: (val) => Number(val).toFixed(3),
|
||||
setValue: (val) => { nai_settings.typical_p = Number(val).toFixed(3); },
|
||||
},
|
||||
{
|
||||
sliderId: "#mirostat_tau_novel",
|
||||
|
|
|
@ -34,6 +34,7 @@ import {
|
|||
} from "./PromptManager.js";
|
||||
|
||||
import {
|
||||
getCustomStoppingStrings,
|
||||
persona_description_positions,
|
||||
power_user,
|
||||
} from "./power-user.js";
|
||||
|
@ -120,6 +121,7 @@ const j2_max_topk = 10.0;
|
|||
const j2_max_freq = 5.0;
|
||||
const j2_max_pres = 5.0;
|
||||
const openrouter_website_model = 'OR_Website';
|
||||
const openai_max_stop_strings = 4;
|
||||
|
||||
let biasCache = undefined;
|
||||
let model_list = [];
|
||||
|
@ -683,9 +685,9 @@ function preparePromptsForChatCompletion({Scenario, charPersonality, name2, worl
|
|||
|
||||
// Tavern Extras - Summary
|
||||
const summary = extensionPrompts['1_memory'];
|
||||
if (summary && summary.content) systemPrompts.push({
|
||||
if (summary && summary.value) systemPrompts.push({
|
||||
role: 'system',
|
||||
content: summary.content,
|
||||
content: summary.value,
|
||||
identifier: 'summary'
|
||||
});
|
||||
|
||||
|
@ -1138,6 +1140,7 @@ async function sendOpenAIRequest(type, openai_msgs_tosend, signal) {
|
|||
"max_tokens": oai_settings.openai_max_tokens,
|
||||
"stream": stream,
|
||||
"logit_bias": logit_bias,
|
||||
"stop": getCustomStoppingStrings(openai_max_stop_strings),
|
||||
};
|
||||
|
||||
// Proxy is only supported for Claude and OpenAI
|
||||
|
@ -1151,6 +1154,7 @@ async function sendOpenAIRequest(type, openai_msgs_tosend, signal) {
|
|||
generate_data['use_claude'] = true;
|
||||
generate_data['top_k'] = Number(oai_settings.top_k_openai);
|
||||
generate_data['exclude_assistant'] = oai_settings.exclude_assistant;
|
||||
generate_data['stop'] = getCustomStoppingStrings(); // Claude shouldn't have limits on stop strings.
|
||||
// Don't add a prefill on quiet gens (summarization)
|
||||
if (!isQuiet && !oai_settings.exclude_assistant) {
|
||||
generate_data['assistant_prefill'] = substituteParams(oai_settings.assistant_prefill);
|
||||
|
@ -2687,8 +2691,8 @@ async function onModelChange() {
|
|||
oai_settings.freq_pen_openai = Math.min(2.0, oai_settings.freq_pen_openai);
|
||||
$('#freq_pen_openai').attr('min', -2.0).attr('max', 2.0).val(oai_settings.freq_pen_openai).trigger('input');
|
||||
|
||||
oai_settings.freq_pen_openai = Math.min(2.0, oai_settings.pres_pen_openai);
|
||||
$('#pres_pen_openai').attr('min', -2.0).attr('max', 2.0).val(oai_settings.freq_pen_openai).trigger('input');
|
||||
oai_settings.pres_pen_openai = Math.min(2.0, oai_settings.pres_pen_openai);
|
||||
$('#pres_pen_openai').attr('min', -2.0).attr('max', 2.0).val(oai_settings.pres_pen_openai).trigger('input');
|
||||
|
||||
oai_settings.top_k_openai = Math.min(200, oai_settings.top_k_openai);
|
||||
$('#top_k_openai').attr('max', 200).val(oai_settings.top_k_openai).trigger('input');
|
||||
|
|
|
@ -29,7 +29,7 @@ import {
|
|||
import { registerSlashCommand } from "./slash-commands.js";
|
||||
import { tokenizers } from "./tokenizers.js";
|
||||
|
||||
import { delay } from "./utils.js";
|
||||
import { delay, resetScrollHeight } from "./utils.js";
|
||||
|
||||
export {
|
||||
loadPowerUserSettings,
|
||||
|
@ -46,7 +46,7 @@ export {
|
|||
export const MAX_CONTEXT_DEFAULT = 4096;
|
||||
const MAX_CONTEXT_UNLOCKED = 65536;
|
||||
|
||||
const defaultStoryString = "{{#if system}}{{system}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}";
|
||||
const defaultStoryString = "{{#if system}}{{system}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}";
|
||||
const defaultExampleSeparator = '***';
|
||||
const defaultChatStart = '***';
|
||||
|
||||
|
@ -159,19 +159,21 @@ let power_user = {
|
|||
default_instruct: '',
|
||||
instruct: {
|
||||
enabled: false,
|
||||
preset: "Alpaca",
|
||||
system_prompt: "Below is an instruction that describes a task. Write a response that appropriately completes the request.\n\nWrite {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n",
|
||||
input_sequence: "### Instruction:",
|
||||
output_sequence: "### Response:",
|
||||
first_output_sequence: "",
|
||||
last_output_sequence: "",
|
||||
system_sequence_prefix: "",
|
||||
system_sequence_suffix: "",
|
||||
stop_sequence: "",
|
||||
separator_sequence: "",
|
||||
wrap: true,
|
||||
macro: true,
|
||||
names: false,
|
||||
system_prompt: "Below is an instruction that describes a task. Write a response that appropriately completes the request.\n\nWrite {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}. Write 1 reply only.",
|
||||
system_sequence: '',
|
||||
stop_sequence: '',
|
||||
input_sequence: '### Instruction:',
|
||||
output_sequence: '### Response:',
|
||||
last_output_sequence: '',
|
||||
preset: 'Alpaca',
|
||||
separator_sequence: '',
|
||||
macro: false,
|
||||
names_force_groups: true,
|
||||
activation_regex: '',
|
||||
activation_regex: "",
|
||||
},
|
||||
|
||||
context: {
|
||||
|
@ -482,9 +484,19 @@ async function applyShadowWidth() {
|
|||
|
||||
}
|
||||
|
||||
async function applyFontScale() {
|
||||
async function applyFontScale(type) {
|
||||
|
||||
power_user.font_scale = Number(localStorage.getItem(storage_keys.font_scale) ?? 1);
|
||||
document.documentElement.style.setProperty('--fontScale', power_user.font_scale);
|
||||
//this is to allow forced setting on page load, theme swap, etc
|
||||
if (type === 'forced') {
|
||||
document.documentElement.style.setProperty('--fontScale', power_user.font_scale);
|
||||
} else {
|
||||
//this is to prevent the slider from updating page in real time
|
||||
$("#font_scale").off('mouseup touchend').on('mouseup touchend', () => {
|
||||
document.documentElement.style.setProperty('--fontScale', power_user.font_scale);
|
||||
})
|
||||
}
|
||||
|
||||
$("#font_scale_counter").text(power_user.font_scale);
|
||||
$("#font_scale").val(power_user.font_scale);
|
||||
}
|
||||
|
@ -522,7 +534,7 @@ async function applyTheme(name) {
|
|||
key: 'font_scale',
|
||||
action: async () => {
|
||||
localStorage.setItem(storage_keys.font_scale, power_user.font_scale);
|
||||
await applyFontScale();
|
||||
await applyFontScale('forced');
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -641,7 +653,7 @@ async function applyMovingUIPreset(name) {
|
|||
}
|
||||
|
||||
switchUiMode();
|
||||
applyFontScale();
|
||||
applyFontScale('forced');
|
||||
applyThemeColor();
|
||||
applyChatWidth();
|
||||
applyAvatarStyle();
|
||||
|
@ -894,6 +906,9 @@ function loadContextSettings() {
|
|||
$element.on('input', function () {
|
||||
power_user.context[control.property] = control.isCheckbox ? !!$(this).prop('checked') : $(this).val();
|
||||
saveSettingsDebounced();
|
||||
if (!control.isCheckbox) {
|
||||
resetScrollHeight($element);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1527,8 +1542,19 @@ function setAvgBG() {
|
|||
|
||||
}
|
||||
|
||||
export function getCustomStoppingStrings() {
|
||||
|
||||
/**
|
||||
* Gets the custom stopping strings from the power user settings.
|
||||
* @param {number | undefined} limit Number of strings to return. If undefined, returns all strings.
|
||||
* @returns {string[]} An array of custom stopping strings
|
||||
*/
|
||||
export function getCustomStoppingStrings(limit = undefined) {
|
||||
try {
|
||||
// If there's no custom stopping strings, return an empty array
|
||||
if (!power_user.custom_stopping_strings) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Parse the JSON string
|
||||
const strings = JSON.parse(power_user.custom_stopping_strings);
|
||||
|
||||
|
@ -1537,8 +1563,8 @@ export function getCustomStoppingStrings() {
|
|||
return [];
|
||||
}
|
||||
|
||||
// Make sure all the elements are strings
|
||||
return strings.filter((s) => typeof s === 'string');
|
||||
// Make sure all the elements are strings. Apply the limit.
|
||||
return strings.filter((s) => typeof s === 'string').slice(0, limit);
|
||||
} catch (error) {
|
||||
// If there's an error, return an empty array
|
||||
console.warn('Error parsing custom stopping strings:', error);
|
||||
|
|
|
@ -338,6 +338,10 @@ function onTagFilterClick(listElement) {
|
|||
}
|
||||
}
|
||||
|
||||
runTagFilters(listElement);
|
||||
}
|
||||
|
||||
function runTagFilters(listElement) {
|
||||
const tagIds = [...($(listElement).find(".tag.selected:not(.actionable)").map((_, el) => $(el).attr("id")))];
|
||||
const excludedTagIds = [...($(listElement).find(".tag.excluded:not(.actionable)").map((_, el) => $(el).attr("id")))];
|
||||
const filterHelper = getFilterHelper($(listElement));
|
||||
|
@ -364,6 +368,9 @@ function printTagFilters(type = tag_filter_types.character) {
|
|||
}
|
||||
for (const tag of tagsToDisplay) {
|
||||
appendTagToList(FILTER_SELECTOR, tag, { removable: false, selectable: true, isGeneralList: true });
|
||||
if (tag.excluded) {
|
||||
runTagFilters(FILTER_SELECTOR);
|
||||
}
|
||||
}
|
||||
|
||||
for (const tagId of selectedTagIds) {
|
||||
|
|
|
@ -72,6 +72,11 @@
|
|||
--bottomFormIconSize: calc(var(--mainFontSize) * 2);
|
||||
--bottomFormBlockSize: calc(var(--bottomFormIconSize) + var(--bottomFormBlockPadding));
|
||||
|
||||
/*Top Bar Scaling Variables*/
|
||||
--topBarIconSize: calc(var(--mainFontSize) * 2);
|
||||
--topBarBlockSize: calc(var(--topBarIconSize) + var(--topBarBlockPadding));
|
||||
--topBarBlockPadding: calc(var(--mainFontSize) / 3);
|
||||
|
||||
/*styles for the color picker*/
|
||||
--tool-cool-color-picker-btn-bg: transparent;
|
||||
--tool-cool-color-picker-btn-border-color: transparent;
|
||||
|
@ -381,7 +386,7 @@ hr {
|
|||
left: 0;
|
||||
right: 0;
|
||||
display: inline-block;
|
||||
height: 35px;
|
||||
height: var(--bottomFormBlockSize);
|
||||
position: absolute;
|
||||
border-bottom: 1px solid var(--grey30a);
|
||||
box-shadow: 0 2px 20px 0 var(--black70a);
|
||||
|
@ -394,14 +399,15 @@ hr {
|
|||
#sheld {
|
||||
display: grid;
|
||||
grid-template-rows: auto min-content;
|
||||
height: calc(100vh - 36px);
|
||||
height: calc(100svh - 36px);
|
||||
height: calc(100vh - var(--topBarBlockSize));
|
||||
height: calc(100svh - var(--topBarBlockSize));
|
||||
max-height: calc(100svh - var(--topBarBlockSize));
|
||||
overflow-x: hidden;
|
||||
/* max-width: 50vw; */
|
||||
position: absolute;
|
||||
left: calc((100vw - var(--sheldWidth))/2);
|
||||
left: calc((100svw - var(--sheldWidth))/2);
|
||||
top: 36px;
|
||||
top: var(--topBarBlockSize);
|
||||
margin: 0 auto;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
@ -466,7 +472,7 @@ hr {
|
|||
}
|
||||
|
||||
#chat {
|
||||
max-height: calc(100vh - 42px);
|
||||
max-height: calc(100vh - calc(var(--topBarBlockSize) + var(--bottomFormBlockSize)));
|
||||
overflow-x: hidden;
|
||||
padding-bottom: 0;
|
||||
overflow-y: scroll;
|
||||
|
@ -910,12 +916,22 @@ select {
|
|||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1000px) {
|
||||
#form_create textarea {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1001px) {
|
||||
#description_textarea {
|
||||
height: 33vh;
|
||||
height: 33svh;
|
||||
}
|
||||
|
||||
#description_textarea,
|
||||
#firstmessage_textarea {
|
||||
height: -webkit-fill-available;
|
||||
width: -moz-available;
|
||||
#firstmessage_textarea {
|
||||
resize: none;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
#character_name_pole {
|
||||
|
@ -1544,10 +1560,6 @@ input[type=search]:focus::-webkit-search-cancel-button {
|
|||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#form_create textarea {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.avatar_div {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
|
@ -2444,7 +2456,7 @@ input[type="range"]::-webkit-slider-thumb {
|
|||
margin-right: auto;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 40px;
|
||||
top: var(--topBarBlockSize);
|
||||
box-shadow: 0 0 20px var(--black70a);
|
||||
padding: 10px;
|
||||
border: 1px solid var(--black30a);
|
||||
|
@ -2529,8 +2541,8 @@ h5 {
|
|||
grid-template-rows: auto auto;
|
||||
max-width: var(--sheldWidth);
|
||||
height: min-content;
|
||||
max-height: calc(100vh - 40px);
|
||||
max-height: calc(100svh - 40px);
|
||||
max-height: calc(100vh - var(--topBarBlockSize));
|
||||
max-height: calc(100svh - var(--topBarBlockSize));
|
||||
min-height: 100px;
|
||||
position: absolute;
|
||||
z-index: 2066;
|
||||
|
@ -2891,10 +2903,10 @@ a {
|
|||
#right-nav-panel {
|
||||
width: calc((100vw - var(--sheldWidth) - 2px) /2);
|
||||
width: calc((100svw - var(--sheldWidth) - 2px) /2);
|
||||
max-height: calc(100vh - 42px);
|
||||
max-height: calc(100svh - 42px);
|
||||
height: calc(100vh - 42px);
|
||||
height: calc(100svh - 42px);
|
||||
max-height: calc(100vh - var(--topBarBlockSize));
|
||||
max-height: calc(100svh - var(--topBarBlockSize));
|
||||
height: calc(100vh - var(--topBarBlockSize));
|
||||
height: calc(100svh - var(--topBarBlockSize));
|
||||
position: fixed;
|
||||
top: 0;
|
||||
margin: 0;
|
||||
|
@ -3026,31 +3038,28 @@ a {
|
|||
/*------------ TOP SIDE SETTINGS ----------------*/
|
||||
|
||||
#top-settings-holder {
|
||||
display: flex;
|
||||
margin: 0 auto;
|
||||
padding-top: 2px;
|
||||
height: 30px;
|
||||
/* max-width: var(--sheldWidth); */
|
||||
height: var(--topBarBlockSize);
|
||||
justify-content: center;
|
||||
display: grid;
|
||||
grid-template-columns: 10% 10% 10% 10% 10% 10% 10% 10% 10%;
|
||||
z-index: 3000;
|
||||
position: relative;
|
||||
grid-gap: 1%;
|
||||
width: var(--sheldWidth);
|
||||
|
||||
}
|
||||
|
||||
.drawer {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
flex-flow: row;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.drawer-icon {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
font-size: 25px;
|
||||
font-size: var(--topBarIconSize);
|
||||
}
|
||||
|
||||
.drawer-icon.openIcon {
|
||||
|
@ -3123,11 +3132,11 @@ a {
|
|||
min-width: 450px;
|
||||
width: var(--sheldWidth);
|
||||
overflow-y: auto;
|
||||
max-height: calc(100vh - 70px);
|
||||
max-height: calc(100svh - 70px);
|
||||
max-height: calc(100vh - calc(var(--topBarBlockSize) + var(--bottomFormBlockSize)));
|
||||
max-height: calc(100svh - calc(var(--topBarBlockSize) + var(--bottomFormBlockSize)));
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 41px;
|
||||
top: var(--topBarBlockSize);
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: 0 auto;
|
||||
|
@ -3149,10 +3158,10 @@ a {
|
|||
.fillLeft {
|
||||
width: calc((100vw - var(--sheldWidth) - 2px) /2);
|
||||
width: calc((100svw - var(--sheldWidth) - 2px) /2);
|
||||
height: calc(100vh - 42px);
|
||||
height: calc(100svh - 42px);
|
||||
max-height: calc(100vh - 42px);
|
||||
max-height: calc(100svh - 42px);
|
||||
height: calc(100vh - var(--topBarBlockSize));
|
||||
height: calc(100svh - var(--topBarBlockSize));
|
||||
max-height: calc(100vh - var(--topBarBlockSize));
|
||||
max-height: calc(100svh - var(--topBarBlockSize));
|
||||
position: fixed;
|
||||
top: 0;
|
||||
margin: 0;
|
||||
|
|
255
server.js
255
server.js
|
@ -317,7 +317,8 @@ const directories = {
|
|||
instruct: 'public/instruct',
|
||||
context: 'public/context',
|
||||
backups: 'backups/',
|
||||
quickreplies: 'public/QuickReplies'
|
||||
quickreplies: 'public/QuickReplies',
|
||||
assets: 'public/assets',
|
||||
};
|
||||
|
||||
// CSRF Protection //
|
||||
|
@ -3390,6 +3391,12 @@ async function sendClaudeRequest(request, response) {
|
|||
}
|
||||
|
||||
console.log('Claude request:', requestPrompt);
|
||||
const stop_sequences = ["\n\nHuman:", "\n\nSystem:", "\n\nAssistant:"];
|
||||
|
||||
// Add custom stop sequences
|
||||
if (Array.isArray(request.body.stop)) {
|
||||
stop_sequences.push(...request.body.stop);
|
||||
}
|
||||
|
||||
const generateResponse = await fetch(api_url + '/complete', {
|
||||
method: "POST",
|
||||
|
@ -3398,7 +3405,7 @@ async function sendClaudeRequest(request, response) {
|
|||
prompt: requestPrompt,
|
||||
model: request.body.model,
|
||||
max_tokens_to_sample: request.body.max_tokens,
|
||||
stop_sequences: ["\n\nHuman:", "\n\nSystem:", "\n\nAssistant:"],
|
||||
stop_sequences: stop_sequences,
|
||||
temperature: request.body.temperature,
|
||||
top_p: request.body.top_p,
|
||||
top_k: request.body.top_k,
|
||||
|
@ -3488,6 +3495,11 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op
|
|||
return response_generate_openai.status(401).send({ error: true });
|
||||
}
|
||||
|
||||
// Add custom stop sequences
|
||||
if (Array.isArray(request.body.stop)) {
|
||||
bodyParams['stop'] = request.body.stop;
|
||||
}
|
||||
|
||||
const isTextCompletion = Boolean(request.body.model && (request.body.model.startsWith('text-') || request.body.model.startsWith('code-')));
|
||||
const textPrompt = isTextCompletion ? convertChatMLPrompt(request.body.messages) : '';
|
||||
const endpointUrl = isTextCompletion ? `${api_url}/completions` : `${api_url}/chat/completions`;
|
||||
|
@ -5011,3 +5023,242 @@ app.post('/delete_extension', jsonParser, async (request, response) => {
|
|||
return response.status(500).send(`Server Error: ${error.message}`);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* HTTP POST handler function to retrieve name of all files of a given folder path.
|
||||
*
|
||||
* @param {Object} request - HTTP Request object. Require folder path in query
|
||||
* @param {Object} response - HTTP Response object will contain a list of file path.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
app.post('/get_assets', jsonParser, async (request, response) => {
|
||||
const folderPath = path.join(directories.assets);
|
||||
let output = {}
|
||||
//console.info("Checking files into",folderPath);
|
||||
|
||||
try {
|
||||
if (fs.existsSync(folderPath) && fs.statSync(folderPath).isDirectory()) {
|
||||
const folders = fs.readdirSync(folderPath)
|
||||
.filter(filename => {
|
||||
return fs.statSync(path.join(folderPath, filename)).isDirectory();
|
||||
});
|
||||
|
||||
for (const folder of folders) {
|
||||
if (folder == "temp")
|
||||
continue;
|
||||
const files = fs.readdirSync(path.join(folderPath, folder))
|
||||
.filter(filename => {
|
||||
return filename != ".placeholder";
|
||||
});
|
||||
output[folder] = [];
|
||||
for (const file of files) {
|
||||
output[folder].push(path.join("assets", folder, file));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
finally {
|
||||
return response.send(output);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function checkAssetFileName(inputFilename) {
|
||||
// Sanitize filename
|
||||
if (inputFilename.indexOf('\0') !== -1) {
|
||||
console.debug("Bad request: poisong null bytes in filename.");
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!/^[a-zA-Z0-9_\-\.]+$/.test(inputFilename)) {
|
||||
console.debug("Bad request: illegal character in filename, only alphanumeric, '_', '-' are accepted.");
|
||||
return '';
|
||||
}
|
||||
|
||||
if (contentManager.unsafeExtensions.some(ext => inputFilename.toLowerCase().endsWith(ext))) {
|
||||
console.debug("Bad request: forbidden file extension.");
|
||||
return '';
|
||||
}
|
||||
|
||||
if (inputFilename.startsWith('.')) {
|
||||
console.debug("Bad request: filename cannot start with '.'");
|
||||
return '';
|
||||
}
|
||||
|
||||
return path.normalize(inputFilename).replace(/^(\.\.(\/|\\|$))+/, '');;
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP POST handler function to download the requested asset.
|
||||
*
|
||||
* @param {Object} request - HTTP Request object, expects a url, a category and a filename.
|
||||
* @param {Object} response - HTTP Response only gives status.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
app.post('/asset_download', jsonParser, async (request, response) => {
|
||||
const { Readable } = require('stream');
|
||||
const { finished } = require('stream/promises');
|
||||
const url = request.body.url;
|
||||
const inputCategory = request.body.category;
|
||||
const inputFilename = sanitize(request.body.filename);
|
||||
const validCategories = ["bgm", "ambient"];
|
||||
|
||||
// Check category
|
||||
let category = null;
|
||||
for (i of validCategories)
|
||||
if (i == inputCategory)
|
||||
category = i;
|
||||
|
||||
if (category === null) {
|
||||
console.debug("Bad request: unsuported asset category.");
|
||||
return response.sendStatus(400);
|
||||
}
|
||||
|
||||
// Sanitize filename
|
||||
const safe_input = checkAssetFileName(inputFilename);
|
||||
if (safe_input == '')
|
||||
return response.sendFile(400);
|
||||
|
||||
const temp_path = path.join(directories.assets, "temp", safe_input)
|
||||
const file_path = path.join(directories.assets, category, safe_input)
|
||||
console.debug("Request received to download", url, "to", file_path);
|
||||
|
||||
try {
|
||||
// Download to temp
|
||||
const downloadFile = (async (url, temp_path) => {
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) {
|
||||
throw new Error(`Unexpected response ${res.statusText}`);
|
||||
}
|
||||
const destination = path.resolve(temp_path);
|
||||
// Delete if previous download failed
|
||||
if (fs.existsSync(temp_path)) {
|
||||
fs.unlink(temp_path, (err) => {
|
||||
if (err) throw err;
|
||||
});
|
||||
}
|
||||
const fileStream = fs.createWriteStream(destination, { flags: 'wx' });
|
||||
await finished(Readable.fromWeb(res.body).pipe(fileStream));
|
||||
});
|
||||
|
||||
await downloadFile(url, temp_path);
|
||||
|
||||
// Move into asset place
|
||||
console.debug("Download finished, moving file from", temp_path, "to", file_path);
|
||||
fs.renameSync(temp_path, file_path);
|
||||
response.sendStatus(200);
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error);
|
||||
response.sendStatus(500);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* HTTP POST handler function to delete the requested asset.
|
||||
*
|
||||
* @param {Object} request - HTTP Request object, expects a category and a filename
|
||||
* @param {Object} response - HTTP Response only gives stats.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
app.post('/asset_delete', jsonParser, async (request, response) => {
|
||||
const { Readable } = require('stream');
|
||||
const { finished } = require('stream/promises');
|
||||
const inputCategory = request.body.category;
|
||||
const inputFilename = sanitize(request.body.filename);
|
||||
const validCategories = ["bgm", "ambient"];
|
||||
|
||||
// Check category
|
||||
let category = null;
|
||||
for (i of validCategories)
|
||||
if (i == inputCategory)
|
||||
category = i;
|
||||
|
||||
if (category === null) {
|
||||
console.debug("Bad request: unsuported asset category.");
|
||||
return response.sendStatus(400);
|
||||
}
|
||||
|
||||
// Sanitize filename
|
||||
const safe_input = checkAssetFileName(inputFilename);
|
||||
if (safe_input == '')
|
||||
return response.sendFile(400);
|
||||
|
||||
const file_path = path.join(directories.assets, category, safe_input)
|
||||
console.debug("Request received to delete", category, file_path);
|
||||
|
||||
try {
|
||||
// Delete if previous download failed
|
||||
if (fs.existsSync(file_path)) {
|
||||
fs.unlink(file_path, (err) => {
|
||||
if (err) throw err;
|
||||
});
|
||||
console.debug("Asset deleted.");
|
||||
}
|
||||
else {
|
||||
console.debug("Asset not found.");
|
||||
response.sendStatus(400);
|
||||
}
|
||||
// Move into asset place
|
||||
response.sendStatus(200);
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error);
|
||||
response.sendStatus(500);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
///////////////////////////////
|
||||
/**
|
||||
* HTTP POST handler function to retrieve a character background music list.
|
||||
*
|
||||
* @param {Object} request - HTTP Request object, expects a character name in the query.
|
||||
* @param {Object} response - HTTP Response object will contain a list of audio file path.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
app.post('/get_character_assets_list', jsonParser, async (request, response) => {
|
||||
const name = sanitize(request.query.name);
|
||||
const inputCategory = request.query.category;
|
||||
const validCategories = ["bgm", "ambient"]
|
||||
|
||||
// Check category
|
||||
let category = null
|
||||
for (i of validCategories)
|
||||
if (i == inputCategory)
|
||||
category = i
|
||||
|
||||
if (category === null) {
|
||||
console.debug("Bad request: unsuported asset category.");
|
||||
return response.sendStatus(400);
|
||||
}
|
||||
|
||||
const folderPath = path.join(directories.characters, name, category);
|
||||
|
||||
let output = [];
|
||||
try {
|
||||
if (fs.existsSync(folderPath) && fs.statSync(folderPath).isDirectory()) {
|
||||
const files = fs.readdirSync(folderPath)
|
||||
.filter(filename => {
|
||||
return filename != ".placeholder";
|
||||
});
|
||||
|
||||
for (i of files)
|
||||
output.push(`/characters/${name}/${category}/${i}`);
|
||||
|
||||
}
|
||||
return response.send(output);
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
return response.sendStatus(500);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,10 +1,87 @@
|
|||
const fs = require('fs');
|
||||
const path= require('path');
|
||||
const path = require('path');
|
||||
const config = require(path.join(process.cwd(), './config.conf'));
|
||||
const contentDirectory = path.join(process.cwd(), 'default/content');
|
||||
const contentLogPath = path.join(contentDirectory, 'content.log');
|
||||
const contentIndexPath = path.join(contentDirectory, 'index.json');
|
||||
|
||||
const unsafeExtensions = [
|
||||
".php",
|
||||
".exe",
|
||||
".com",
|
||||
".dll",
|
||||
".pif",
|
||||
".application",
|
||||
".gadget",
|
||||
".msi",
|
||||
".jar",
|
||||
".cmd",
|
||||
".bat",
|
||||
".reg",
|
||||
".sh",
|
||||
".py",
|
||||
".js",
|
||||
".jse",
|
||||
".jsp",
|
||||
".pdf",
|
||||
".html",
|
||||
".htm",
|
||||
".hta",
|
||||
".vb",
|
||||
".vbs",
|
||||
".vbe",
|
||||
".cpl",
|
||||
".msc",
|
||||
".scr",
|
||||
".sql",
|
||||
".iso",
|
||||
".img",
|
||||
".dmg",
|
||||
".ps1",
|
||||
".ps1xml",
|
||||
".ps2",
|
||||
".ps2xml",
|
||||
".psc1",
|
||||
".psc2",
|
||||
".msh",
|
||||
".msh1",
|
||||
".msh2",
|
||||
".mshxml",
|
||||
".msh1xml",
|
||||
".msh2xml",
|
||||
".scf",
|
||||
".lnk",
|
||||
".inf",
|
||||
".reg",
|
||||
".doc",
|
||||
".docm",
|
||||
".docx",
|
||||
".dot",
|
||||
".dotm",
|
||||
".dotx",
|
||||
".xls",
|
||||
".xlsm",
|
||||
".xlsx",
|
||||
".xlt",
|
||||
".xltm",
|
||||
".xltx",
|
||||
".xlam",
|
||||
".ppt",
|
||||
".pptm",
|
||||
".pptx",
|
||||
".pot",
|
||||
".potm",
|
||||
".potx",
|
||||
".ppam",
|
||||
".ppsx",
|
||||
".ppsm",
|
||||
".pps",
|
||||
".ppam",
|
||||
".sldx",
|
||||
".sldm",
|
||||
".ws",
|
||||
];
|
||||
|
||||
function checkForNewContent() {
|
||||
try {
|
||||
if (config.skipContentCheck) {
|
||||
|
@ -85,4 +162,5 @@ function getContentLog() {
|
|||
|
||||
module.exports = {
|
||||
checkForNewContent,
|
||||
unsafeExtensions,
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue