mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-03-15 11:30:09 +01:00
Merge branch 'staging' of https://github.com/Cohee1207/SillyTavern into staging
This commit is contained in:
commit
84fb5b8ffd
2
.gitignore
vendored
2
.gitignore
vendored
@ -6,12 +6,14 @@ public/backgrounds/
|
||||
public/groups/
|
||||
public/group chats/
|
||||
public/worlds/
|
||||
public/user/
|
||||
public/css/bg_load.css
|
||||
public/themes/
|
||||
public/OpenAI Settings/
|
||||
public/KoboldAI Settings/
|
||||
public/NovelAI Settings/
|
||||
public/TextGen Settings/
|
||||
public/instruct/
|
||||
public/scripts/extensions/third-party/
|
||||
public/stats.json
|
||||
/uploads/
|
||||
|
@ -133,7 +133,7 @@
|
||||
"output_sequence": "### Response:",
|
||||
"preset": "Alpaca",
|
||||
"separator_sequence": "",
|
||||
"macro": false
|
||||
"macro": true
|
||||
},
|
||||
"personas": {},
|
||||
"default_persona": null,
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Default",
|
||||
"story_string": "{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}{{/if}}",
|
||||
"story_string": "{{#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}}",
|
||||
"chat_start": "***",
|
||||
"example_separator": "***"
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Pygmalion",
|
||||
"story_string": "{{#if description}}{{{char}}}'s Persona: {{description}}{{/if}}\n{{#if personality}}Personality: {{personality}}{{/if}}\n{{#if scenario}}Scenario: {{scenario}}{{/if}}",
|
||||
"story_string": "{{#if system}}{{system}}\n{{/if}}{{#if description}}{{{char}}}'s Persona: {{description}}\n{{/if}}{{#if personality}}Personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}",
|
||||
"chat_start": "<START>",
|
||||
"example_separator": "<START>"
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Roleplay",
|
||||
"story_string": "### Input:\n{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}{{/if}}",
|
||||
"story_string": "{{#if system}}{{system}}\n{{/if}}### Input:\n{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}",
|
||||
"chat_start": "### New Roleplay:",
|
||||
"example_separator": "### New Roleplay:"
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "simple-proxy-for-tavern",
|
||||
"story_string": "### Input:\n{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}### Response:\n(OOC) Understood. I will take this info into account for the roleplay. (end OOC)",
|
||||
"story_string": "## {{char}}\n- You're \"{{char}}\" in this never-ending roleplay with \"{{user}}\".\n### Input:\n{{#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}}### Response:\n(OOC) Understood. I will take this info into account for the roleplay. (end OOC)",
|
||||
"chat_start": "### New Roleplay:",
|
||||
"example_separator": "### New Roleplay:"
|
||||
}
|
||||
|
@ -363,8 +363,8 @@
|
||||
"Not Connected": "未连接",
|
||||
"Persona Management": "用户角色设置",
|
||||
"Persona Description": "用户角色描述",
|
||||
"Before Character Card": "角色卡之前",
|
||||
"After Character Card": "角色卡之后",
|
||||
"In Story String / Chat Completion: Before Character Card": "在故事串中 / Chat Completion: 角色卡之前",
|
||||
"In Story String / Chat Completion: After Character Card": "在故事串中 / Chat Completion: 角色卡之后",
|
||||
"Top of Author's Note": "作者注释之前",
|
||||
"Bottom of Author's Note": "作者注释之后",
|
||||
"How do I use this?": "用户角色设置说明",
|
||||
@ -915,8 +915,8 @@
|
||||
"Not Connected": "NEEDS TRANSLATION",
|
||||
"Persona Management": "NEEDS TRANSLATION",
|
||||
"Persona Description": "NEEDS TRANSLATION",
|
||||
"Before Character Card": "NEEDS TRANSLATION",
|
||||
"After Character Card": "NEEDS TRANSLATION",
|
||||
"In Story String / Chat Completion: Before Character Card": "NEEDS TRANSLATION",
|
||||
"In Story String / Chat Completion: After Character Card": "NEEDS TRANSLATION",
|
||||
"Top of Author's Note": "NEEDS TRANSLATION",
|
||||
"Bottom of Author's Note": "NEEDS TRANSLATION",
|
||||
"How do I use this?": "NEEDS TRANSLATION",
|
||||
@ -1472,8 +1472,8 @@
|
||||
"Not Connected": "접속되지 않음",
|
||||
"Persona Management": "주인공 관리",
|
||||
"Persona Description": "주인공 묘사",
|
||||
"Before Character Card": "캐릭터 카드 앞에",
|
||||
"After Character Card": "캐릭터 카드 다음에",
|
||||
"In Story String / Chat Completion: Before Character Card": "스토리 문자열에서 / 문장완성: 캐릭터 카드 앞에",
|
||||
"In Story String / Chat Completion: After Character Card": "스토리 문자열에서 / 문장완성: 캐릭터 카드 다음에",
|
||||
"Top of Author's Note": "글쓴이 쪽지 위에",
|
||||
"Bottom of Author's Note": "글쓴이 쪽지 밑에",
|
||||
"How do I use this?": "이건 어떻게 써먹나요?",
|
||||
@ -2029,8 +2029,8 @@
|
||||
"Not Connected": "Не подключено",
|
||||
"Persona Management": "Управление Персоной",
|
||||
"Persona Description": "Описание Персоны",
|
||||
"Before Character Card": "Перед Карточкой Персонажа",
|
||||
"After Character Card": "После Карточки Персонажа",
|
||||
"In Story String / Chat Completion: Before Character Card": "В строке истории / Дополнение диалога: Перед Карточкой Персонажа",
|
||||
"In Story String / Chat Completion: After Character Card": "В строке истории / Дополнение диалога: После Карточки Персонажа",
|
||||
"Top of Author's Note": "Перед Авторскими Заметками",
|
||||
"Bottom of Author's Note": "После Авторских Заметок",
|
||||
"How do I use this?": "Как мне это использовать?",
|
||||
|
@ -149,7 +149,6 @@
|
||||
<div class="scrollableInner">
|
||||
<div class="flex-container" id="ai_response_configuration">
|
||||
<div id="respective-presets-block" class="width100p">
|
||||
<input type="file" hidden data-preset-manager-file="" accept=".json, .settings">
|
||||
<div id="kobold_api-presets">
|
||||
<h3><span data-i18n="kobldpresets">Kobold Presets</span>
|
||||
<a href="https://docs.sillytavern.app/usage/api-connections/koboldai/" class="notes-link" target="_blank">
|
||||
@ -161,6 +160,7 @@
|
||||
<select id="settings_perset" data-preset-manager-for="kobold">
|
||||
<option value="gui" data-i18n="guikoboldaisettings">GUI KoboldAI Settings</option>
|
||||
</select>
|
||||
<input type="file" hidden data-preset-manager-file="kobold" accept=".json, .settings">
|
||||
<i data-preset-manager-update="kobold" class="menu_button fa-solid fa-save" title="Update current preset" data-i18n="[title]Update current preset"></i>
|
||||
<i data-preset-manager-new="kobold" class="menu_button fa-solid fa-plus" title="Create new preset" data-i18n="[title]Create new preset"></i>
|
||||
<i data-preset-manager-import="kobold" class="menu_button fa-solid fa-file-import" title="Import preset" data-i18n="[title]Import preset"></i>
|
||||
@ -179,6 +179,7 @@
|
||||
<select id="settings_perset_novel" data-preset-manager-for="novel">
|
||||
<option value="gui" data-i18n="default">Default</option>
|
||||
</select>
|
||||
<input type="file" hidden data-preset-manager-file="novel" accept=".json, .settings">
|
||||
<i data-preset-manager-update="novel" class="menu_button fa-solid fa-save" title="Update current preset" data-i18n="[title]Update current preset"></i>
|
||||
<i data-preset-manager-new="novel" class="menu_button fa-solid fa-plus" title="Create new preset" data-i18n="[title]Create new preset"></i>
|
||||
<i data-preset-manager-import="novel" class="menu_button fa-solid fa-file-import" title="Import preset" data-i18n="[title]Import preset"></i>
|
||||
@ -208,6 +209,7 @@
|
||||
<div class="preset_buttons">
|
||||
<select id="settings_preset_textgenerationwebui" data-preset-manager-for="textgenerationwebui">
|
||||
</select>
|
||||
<input type="file" hidden data-preset-manager-file="textgenerationwebui" accept=".json, .settings">
|
||||
<i data-preset-manager-update="textgenerationwebui" class="menu_button fa-solid fa-save" title="Update current preset" data-i18n="[title]Update current preset"></i>
|
||||
<i data-preset-manager-new="textgenerationwebui" class="menu_button fa-solid fa-plus" title="Create new preset" data-i18n="[title]Create new preset"></i>
|
||||
<i data-preset-manager-import="textgenerationwebui" class="menu_button fa-solid fa-file-import" title="Import preset" data-i18n="[title]Import preset"></i>
|
||||
@ -1454,6 +1456,14 @@
|
||||
<span data-i18n="Use the appropriate tokenizer for Jurassic models, which is more efficient than GPT's.">Use the appropriate tokenizer for Jurassic models, which is more efficient than GPT's.</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="claude">
|
||||
<label for="exclude_assistant" title="Exclude Assistant suffix" class="checkbox_label widthFreeExpand">
|
||||
<input id="exclude_assistant" type="checkbox" /><span data-i18n="Exclude Assistant suffix">Exclude Assistant suffix</span>
|
||||
</label>
|
||||
<div class="toggle-description justifyLeft">
|
||||
<span data-i18n="Exclude the assistant suffix from being added to the end of prompt.">Exclude the assistant suffix from being added to the end of prompt (Requires jailbreak with 'Assistant:' in it).</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="inline-drawer m-t-1 wide100p">
|
||||
<div class="inline-drawer-toggle inline-drawer-header">
|
||||
<b data-i18n="Quick Edit">Quick Edit</b>
|
||||
@ -1465,8 +1475,8 @@
|
||||
<span data-i18n="Select a character to show quick edit options.">Select a character to show quick edit options.</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="claude">
|
||||
<span data-i18n="Assistant Prefill">Assistant Prefill</span>
|
||||
<div id="claude_assistant_prefill_block" data-source="claude" class="range-block">
|
||||
<span id="claude_assistant_prefill_text" data-i18n="Assistant Prefill">Assistant Prefill</span>
|
||||
<textarea id="claude_assistant_prefill" class="text_pole textarea_compact" name="assistant_prefill" rows="3" maxlength="5000" placeholder="Start Claude's answer with..."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
@ -1797,22 +1807,27 @@
|
||||
<input id="use-mancer-api-checkbox" type="checkbox" />
|
||||
</label>
|
||||
</div>
|
||||
<div id="mancer-api-ui" style="display:none;">
|
||||
<h4 data-i18n="Mancer API key">Mancer API key</h4>
|
||||
<div class="flex-container">
|
||||
<input id="api_key_mancer" name="api_key_mancer" class="text_pole flex1 wide100p" maxlength="500" size="35" type="text" autocomplete="off">
|
||||
<div title="Clear your API key" data-i18n="[title]Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_mancer">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex-container flexFlowColumn">
|
||||
<div id="mancer_api_subpanel" class="flex-container flexFlowColumn" style="display:none;">
|
||||
<h4 data-i18n="Mancer API key">Mancer API key</h4>
|
||||
<div class="flex-container">
|
||||
<input id="api_key_mancer" name="api_key_mancer" class="text_pole flex1 wide100p" maxlength="500" size="35" type="text" autocomplete="off">
|
||||
<div title="Clear your API key" data-i18n="[title]Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_mancer">
|
||||
</div>
|
||||
</div>
|
||||
<div data-for="api_key_mancer" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">
|
||||
For privacy reasons, your API key will be hidden after you reload the page.
|
||||
</div>
|
||||
<div class="flex1">
|
||||
<h4 data-i18n="Mancer API url">Mancer API url</h4>
|
||||
<small>Example: https://neuro.mancer.tech/webui/MODEL/api</small>
|
||||
<input id="mancer_api_url_text" name="mancer_api_url" class="text_pole wide100p" maxlength="500" value="" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
<div id="tgwebui_api_subpanel" class="flex-container flexFlowColumn">
|
||||
<div class="flex1">
|
||||
<h4 data-i18n="Blocking API url">Blocking API url</h4>
|
||||
<small>Example: http://127.0.0.1:5000/</small>
|
||||
<small>Example: http://127.0.0.1:5000/api</small>
|
||||
<input id="textgenerationwebui_api_url_text" name="textgenerationwebui_api_url" class="text_pole wide100p" maxlength="500" value="" autocomplete="off">
|
||||
</div>
|
||||
<div class="flex1">
|
||||
@ -2128,12 +2143,15 @@
|
||||
<label for="instruct_presets">
|
||||
<span data-i18n="Presets">Presets</span>
|
||||
</label>
|
||||
<div class="flex-container">
|
||||
<select id="instruct_presets" class="flex1 margin0"></select>
|
||||
<div id="instruct_set_default" class="menu_button menu_button_icon margin0">
|
||||
<i class="fa-solid fa-xs fa-fw fa-heart"></i>
|
||||
<span data-i18n="Default">Default</span>
|
||||
</div>
|
||||
<div class="preset_buttons">
|
||||
<select id="instruct_presets" data-preset-manager-for="instruct" class="flex1"></select>
|
||||
<input type="file" hidden data-preset-manager-file="instruct" accept=".json, .settings">
|
||||
<i id="instruct_set_default" class="menu_button fa-solid fa-heart" title="Auto-select this preset on API connection."></i>
|
||||
<i data-preset-manager-update="instruct" class="menu_button fa-solid fa-save" title="Update current preset" data-i18n="[title]Update current preset"></i>
|
||||
<i data-preset-manager-new="instruct" class="menu_button fa-solid fa-plus" title="Create new preset" data-i18n="[title]Create new preset"></i>
|
||||
<i data-preset-manager-import="instruct" class="menu_button fa-solid fa-file-import" title="Import preset" data-i18n="[title]Import preset"></i>
|
||||
<i data-preset-manager-export="instruct" class="menu_button fa-solid fa-file-export" title="Export preset" data-i18n="[title]Export preset"></i>
|
||||
<i data-preset-manager-delete="instruct" class="menu_button fa-solid fa-trash-can" title="Delete the preset" data-i18n="[title]Delete the preset"></i>
|
||||
</div>
|
||||
<label>
|
||||
<small data-i18n="Activation Regex">
|
||||
@ -2156,7 +2174,7 @@
|
||||
<small data-i18n="Input Sequence">Input Sequence</small>
|
||||
</label>
|
||||
<div>
|
||||
<textarea id="instruct_input_sequence" class="text_pole textarea_compact" maxlength="500" rows="1"></textarea>
|
||||
<textarea id="instruct_input_sequence" class="text_pole textarea_compact" maxlength="500" rows="2"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex1">
|
||||
@ -2164,7 +2182,7 @@
|
||||
<small data-i18n="Output Sequence">Output Sequence</small>
|
||||
</label>
|
||||
<div>
|
||||
<textarea id="instruct_output_sequence" class="text_pole wide100p textarea_compact" maxlength="500" rows="1"></textarea>
|
||||
<textarea id="instruct_output_sequence" class="text_pole wide100p textarea_compact" maxlength="500" rows="2"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex1">
|
||||
@ -2172,7 +2190,7 @@
|
||||
<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="1"></textarea>
|
||||
<textarea id="instruct_last_output_sequence" class="text_pole wide100p textarea_compact" maxlength="500" rows="2"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -2182,7 +2200,7 @@
|
||||
<small data-i18n="System Sequence">System Sequence</small>
|
||||
</label>
|
||||
<div>
|
||||
<textarea id="instruct_system_sequence" class="text_pole textarea_compact" maxlength="500" rows="1"></textarea>
|
||||
<textarea id="instruct_system_sequence" class="text_pole textarea_compact" maxlength="500" rows="2"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex1">
|
||||
@ -2190,7 +2208,7 @@
|
||||
<small data-i18n="Stop Sequence">Stop Sequence</small>
|
||||
</label>
|
||||
<div>
|
||||
<textarea id="instruct_stop_sequence" class="text_pole wide100p textarea_compact" maxlength="500" rows="1"></textarea>
|
||||
<textarea id="instruct_stop_sequence" class="text_pole wide100p textarea_compact" maxlength="500" rows="2"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex1">
|
||||
@ -2198,7 +2216,7 @@
|
||||
<small data-i18n="Separator">Separator</small>
|
||||
</label>
|
||||
<div>
|
||||
<textarea id="instruct_separator_sequence" class="text_pole wide100p textarea_compact" maxlength="500" rows="1"></textarea>
|
||||
<textarea id="instruct_separator_sequence" class="text_pole wide100p textarea_compact" maxlength="500" rows="2"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -2252,6 +2270,10 @@
|
||||
Keep Example Messages in Prompt
|
||||
</span>
|
||||
</label>
|
||||
<label class="checkbox_label" for="remove-examples-checkbox">
|
||||
<input id="remove-examples-checkbox" type="checkbox" />
|
||||
Strip Example Messages from Prompt
|
||||
</label>
|
||||
<label class="checkbox_label" for="collapse-newlines-checkbox"><input id="collapse-newlines-checkbox" type="checkbox" />
|
||||
<span data-i18n="Remove Empty New Lines from Output">
|
||||
Remove Empty New Lines from Output
|
||||
@ -2989,8 +3011,8 @@
|
||||
<textarea id="persona_description" name="persona_description" placeholder="Example: [{{user}} is a 28-year-old Romanian cat girl.]" class="text_pole textarea_compact" maxlength="5000" value="" autocomplete="off" rows="4"></textarea>
|
||||
<label for="persona_description_position" data-i18n="Position:">Position:</label>
|
||||
<select id="persona_description_position">
|
||||
<option value="0" data-i18n="Before Character Card">Before Character Card</option>
|
||||
<option value="1" data-i18n="After Character Card">After Character Card</option>
|
||||
<option value="0" data-i18n="In Story String / Chat Completion: Before Character Card">In Story String / Chat Completion: Before Character Card</option>
|
||||
<option value="1" data-i18n="In Story String / Chat Completion: After Character Card">In Story String / Chat Completion: After Character Card</option>
|
||||
<option value="2" data-i18n="Top of Author's Note">Top of Author's Note</option>
|
||||
<option value="3" data-i18n="Bottom of Author's Note">Bottom of Author's Note</option>
|
||||
</select>
|
||||
@ -3708,9 +3730,7 @@
|
||||
<small>
|
||||
<span data-i18n="Content">
|
||||
Content
|
||||
<span>(Tokens:
|
||||
<span class="world_entry_form_token_counter">0</span>
|
||||
)
|
||||
<span>(Tokens: <span class="world_entry_form_token_counter" data-first-run="true">counting...</span>)
|
||||
</span>
|
||||
</span>
|
||||
</small>
|
||||
@ -4351,4 +4371,4 @@
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
@ -1,11 +1,15 @@
|
||||
{
|
||||
"name": "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",
|
||||
"system_sequence": "",
|
||||
"stop_sequence": "",
|
||||
"input_sequence": "### Instruction:",
|
||||
"output_sequence": "### Response:",
|
||||
"last_output_sequence": "",
|
||||
"system_sequence": "",
|
||||
"stop_sequence": "",
|
||||
"separator_sequence": "",
|
||||
"wrap": true
|
||||
"wrap": true,
|
||||
"macro": true,
|
||||
"names": false,
|
||||
"names_force_groups": true,
|
||||
"activation_regex": ""
|
||||
}
|
||||
|
@ -1,11 +1,15 @@
|
||||
{
|
||||
"name": "Koala",
|
||||
"system_prompt": "Write {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n",
|
||||
"system_sequence": "BEGINNING OF CONVERSATION: ",
|
||||
"stop_sequence": "",
|
||||
"input_sequence": "USER: ",
|
||||
"output_sequence": "GPT: ",
|
||||
"last_output_sequence": "",
|
||||
"system_sequence": "BEGINNING OF CONVERSATION: ",
|
||||
"stop_sequence": "",
|
||||
"separator_sequence": "</s>",
|
||||
"wrap": false
|
||||
"wrap": false,
|
||||
"macro": true,
|
||||
"names": false,
|
||||
"names_force_groups": true,
|
||||
"activation_regex": ""
|
||||
}
|
||||
|
@ -1,11 +1,15 @@
|
||||
{
|
||||
"name": "Llama 2",
|
||||
"system_prompt": "Write {{char}}'s next reply in this fictional roleplay with {{user}}.\n<</SYS>>\n",
|
||||
"system_prompt": "[INST] <<SYS>>\nWrite {{char}}'s next reply in this fictional roleplay with {{user}}.\n<</SYS>>\n",
|
||||
"input_sequence": "[INST] ",
|
||||
"output_sequence": " [/INST] ",
|
||||
"last_output_sequence": "",
|
||||
"system_sequence": "[INST] <<SYS>>\n",
|
||||
"stop_sequence": "",
|
||||
"input_sequence": "[INST]",
|
||||
"output_sequence": "[/INST]",
|
||||
"last_output_sequence": "",
|
||||
"separator_sequence": "\n",
|
||||
"wrap": false
|
||||
"wrap": false,
|
||||
"macro": true,
|
||||
"names": false,
|
||||
"names_force_groups": true,
|
||||
"activation_regex": ""
|
||||
}
|
||||
|
@ -1,11 +1,15 @@
|
||||
{
|
||||
"name": "Metharme",
|
||||
"system_prompt": "Enter roleplay mode. You must act as {{char}}, whose persona follows:",
|
||||
"system_sequence": "<|system|>",
|
||||
"stop_sequence": "</s>",
|
||||
"input_sequence": "<|user|>",
|
||||
"output_sequence": "<|model|>",
|
||||
"last_output_sequence": "",
|
||||
"system_sequence": "<|system|>",
|
||||
"stop_sequence": "</s>",
|
||||
"separator_sequence": "",
|
||||
"wrap": false
|
||||
"wrap": false,
|
||||
"macro": true,
|
||||
"names": false,
|
||||
"names_force_groups": true,
|
||||
"activation_regex": ""
|
||||
}
|
||||
|
@ -1,13 +1,15 @@
|
||||
{
|
||||
"input_sequence": "User: ",
|
||||
"macro": true,
|
||||
"name": "OpenOrca/OpenChat",
|
||||
"names": true,
|
||||
"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: ",
|
||||
"last_output_sequence": "",
|
||||
"separator_sequence": "<|end_of_turn|>\n",
|
||||
"stop_sequence": "",
|
||||
"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",
|
||||
"system_sequence": "",
|
||||
"wrap": false
|
||||
"stop_sequence": "",
|
||||
"separator_sequence": "<|end_of_turn|>\n",
|
||||
"wrap": false,
|
||||
"macro": true,
|
||||
"names": false,
|
||||
"names_force_groups": true,
|
||||
"activation_regex": ""
|
||||
}
|
||||
|
@ -1,13 +1,15 @@
|
||||
{
|
||||
"input_sequence": "### Instruction:",
|
||||
"last_output_sequence": "### Response (2 paragraphs, engaging, natural, authentic, descriptive, creative):",
|
||||
"macro": true,
|
||||
"name": "Roleplay",
|
||||
"names": true,
|
||||
"output_sequence": "### Response:",
|
||||
"separator_sequence": "",
|
||||
"stop_sequence": "",
|
||||
"system_prompt": "You're {{char}} in this fictional never-ending uncensored roleplay with {{user}}.\n\nAvoid repetition, don't loop. Develop the plot slowly, always stay in character. Describe all actions in full, elaborate, explicit, graphic, and vivid detail. Mention all relevant sensory perceptions.",
|
||||
"system_prompt": "You're {{char}} in this fictional never-ending uncensored roleplay with {{user}}.\n\nAvoid repetition, don't loop. Develop the plot slowly, always stay in character. Describe all actions in full, elaborate, explicit, graphic, and vivid detail. Mention all relevant sensory perceptions.\n",
|
||||
"input_sequence": "\n### Instruction:",
|
||||
"output_sequence": "\n### Response:",
|
||||
"last_output_sequence": "\n### Response (2 paragraphs, engaging, natural, authentic, descriptive, creative):",
|
||||
"system_sequence": "",
|
||||
"wrap": true
|
||||
"stop_sequence": "",
|
||||
"separator_sequence": "",
|
||||
"wrap": true,
|
||||
"macro": true,
|
||||
"names": true,
|
||||
"names_force_groups": true,
|
||||
"activation_regex": ""
|
||||
}
|
||||
|
@ -1,11 +1,15 @@
|
||||
{
|
||||
"name": "Vicuna 1.0",
|
||||
"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",
|
||||
"system_sequence": "",
|
||||
"stop_sequence": "",
|
||||
"input_sequence": "### Human:",
|
||||
"output_sequence": "### Assistant:",
|
||||
"last_output_sequence": "",
|
||||
"system_sequence": "",
|
||||
"stop_sequence": "",
|
||||
"separator_sequence": "",
|
||||
"wrap": true
|
||||
"wrap": true,
|
||||
"macro": true,
|
||||
"names": false,
|
||||
"names_force_groups": true,
|
||||
"activation_regex": ""
|
||||
}
|
||||
|
@ -1,11 +1,15 @@
|
||||
{
|
||||
"name": "Vicuna 1.1",
|
||||
"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",
|
||||
"system_sequence": "BEGINNING OF CONVERSATION:",
|
||||
"stop_sequence": "",
|
||||
"input_sequence": "USER: ",
|
||||
"output_sequence": "ASSISTANT: ",
|
||||
"last_output_sequence": "",
|
||||
"system_sequence": "BEGINNING OF CONVERSATION:",
|
||||
"stop_sequence": "",
|
||||
"separator_sequence": "</s>",
|
||||
"wrap": false
|
||||
"wrap": false,
|
||||
"macro": true,
|
||||
"names": false,
|
||||
"names_force_groups": true,
|
||||
"activation_regex": ""
|
||||
}
|
||||
|
@ -1,11 +1,15 @@
|
||||
{
|
||||
"name": "WizardLM-13B",
|
||||
"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}}.",
|
||||
"system_sequence": "",
|
||||
"stop_sequence": "",
|
||||
"input_sequence": "USER: ",
|
||||
"output_sequence": "ASSISTANT: ",
|
||||
"last_output_sequence": "",
|
||||
"system_sequence": "",
|
||||
"stop_sequence": "",
|
||||
"separator_sequence": "",
|
||||
"wrap": true
|
||||
"wrap": true,
|
||||
"macro": true,
|
||||
"names": false,
|
||||
"names_force_groups": true,
|
||||
"activation_regex": ""
|
||||
}
|
||||
|
@ -1,11 +1,15 @@
|
||||
{
|
||||
"name": "WizardLM",
|
||||
"system_prompt": "Write {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n",
|
||||
"system_sequence": "",
|
||||
"stop_sequence": "",
|
||||
"input_sequence": "",
|
||||
"output_sequence": "### Response:",
|
||||
"last_output_sequence": "",
|
||||
"system_sequence": "",
|
||||
"stop_sequence": "",
|
||||
"separator_sequence": "</s>",
|
||||
"wrap": true
|
||||
"wrap": true,
|
||||
"macro": true,
|
||||
"names": false,
|
||||
"names_force_groups": true,
|
||||
"activation_regex": ""
|
||||
}
|
||||
|
@ -1,13 +1,15 @@
|
||||
{
|
||||
"input_sequence": "### Instruction:\n#### {{user}}:",
|
||||
"last_output_sequence": "### Response (2 paragraphs, engaging, natural, authentic, descriptive, creative):\n#### {{char}}:",
|
||||
"macro": true,
|
||||
"name": "simple-proxy-for-tavern",
|
||||
"names": false,
|
||||
"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}}:",
|
||||
"separator_sequence": "",
|
||||
"stop_sequence": "",
|
||||
"system_prompt": "## {{char}}\n- You're \"{{char}}\" in this never-ending roleplay with \"{{user}}\".",
|
||||
"last_output_sequence": "### Response (2 paragraphs, engaging, natural, authentic, descriptive, creative):\n#### {{char}}:",
|
||||
"system_sequence": "",
|
||||
"wrap": true
|
||||
"stop_sequence": "",
|
||||
"separator_sequence": "",
|
||||
"wrap": true,
|
||||
"macro": true,
|
||||
"names": false,
|
||||
"names_force_groups": false,
|
||||
"activation_regex": ""
|
||||
}
|
||||
|
136
public/script.js
136
public/script.js
@ -55,6 +55,7 @@ import {
|
||||
renameGroupChat,
|
||||
importGroupChat,
|
||||
getGroupBlock,
|
||||
getGroupChatNames,
|
||||
} from "./scripts/group-chats.js";
|
||||
|
||||
import {
|
||||
@ -65,9 +66,6 @@ import {
|
||||
power_user,
|
||||
pygmalion_options,
|
||||
tokenizers,
|
||||
formatInstructModeChat,
|
||||
formatInstructStoryString,
|
||||
formatInstructModePrompt,
|
||||
persona_description_positions,
|
||||
loadMovingUIState,
|
||||
getCustomStoppingStrings,
|
||||
@ -164,6 +162,13 @@ import { deviceInfo } from "./scripts/RossAscends-mods.js";
|
||||
import { registerPromptManagerMigration } from "./scripts/PromptManager.js";
|
||||
import { getRegexedString, regex_placement } from "./scripts/extensions/regex/engine.js";
|
||||
import { FILTER_TYPES, FilterHelper } from "./scripts/filters.js";
|
||||
import {
|
||||
formatInstructModeChat,
|
||||
formatInstructModePrompt,
|
||||
formatInstructModeExamples,
|
||||
getInstructStoppingSequences,
|
||||
autoSelectInstructPreset,
|
||||
} from "./scripts/instruct-mode.js";
|
||||
|
||||
//exporting functions and vars for mods
|
||||
export {
|
||||
@ -329,7 +334,6 @@ let scrollLock = false;
|
||||
const durationSaveEdit = 1000;
|
||||
const saveSettingsDebounced = debounce(() => saveSettings(), durationSaveEdit);
|
||||
export const saveCharacterDebounced = debounce(() => $("#create_button").trigger('click'), durationSaveEdit);
|
||||
const getStatusDebounced = debounce(() => getStatus(), 300_000);
|
||||
const saveChatDebounced = debounce(() => saveChatConditional(), durationSaveEdit);
|
||||
|
||||
const system_message_types = {
|
||||
@ -883,10 +887,6 @@ async function getStatus() {
|
||||
const hordeStatus = await checkHordeStatus();
|
||||
online_status = hordeStatus ? 'Connected' : 'no_connection';
|
||||
resultCheckStatus();
|
||||
|
||||
if (online_status !== "no_connection") {
|
||||
getStatusDebounced();
|
||||
}
|
||||
}
|
||||
catch {
|
||||
online_status = "no_connection";
|
||||
@ -915,6 +915,10 @@ async function getStatus() {
|
||||
if (online_status == undefined) {
|
||||
online_status = "no_connection";
|
||||
}
|
||||
|
||||
// Determine instruct mode preset
|
||||
autoSelectInstructPreset(online_status);
|
||||
|
||||
if ((online_status.toLowerCase().indexOf("pygmalion") != -1 && power_user.pygmalion_formatting == pygmalion_options.AUTO)
|
||||
|| (online_status !== "no_connection" && power_user.pygmalion_formatting == pygmalion_options.ENABLED)) {
|
||||
is_pygmalion = true;
|
||||
@ -936,9 +940,6 @@ async function getStatus() {
|
||||
|
||||
//console.log(online_status);
|
||||
resultCheckStatus();
|
||||
if (online_status !== "no_connection") {
|
||||
getStatusDebounced();
|
||||
}
|
||||
},
|
||||
error: function (jqXHR, exception) {
|
||||
console.log(exception);
|
||||
@ -1604,8 +1605,18 @@ function addOneMessage(mes, { type = "normal", insertAfter = null, scroll = true
|
||||
mes.is_user,
|
||||
);
|
||||
const bias = messageFormatting(mes.extra?.bias ?? "");
|
||||
const bookmarkLink = mes?.extra?.bookmark_link ?? '';
|
||||
let bookmarkLink = mes?.extra?.bookmark_link ?? '';
|
||||
// Verify bookmarked chat still exists
|
||||
// Cohee: Commented out for now. I'm worried of performance issues.
|
||||
/*if (bookmarkLink !== '') {
|
||||
let chat_names = selected_group
|
||||
? getGroupChatNames(selected_group)
|
||||
: Object.values(getPastCharacterChats()).map(({ file_name }) => file_name);
|
||||
|
||||
if (!chat_names.includes(bookmarkLink)) {
|
||||
bookmarkLink = ''
|
||||
}
|
||||
}*/
|
||||
let params = {
|
||||
mesId: count_view_mes,
|
||||
characterName: characterName,
|
||||
@ -1902,32 +1913,7 @@ function getStoppingStrings(isImpersonate, addSpace) {
|
||||
}
|
||||
}
|
||||
|
||||
function addInstructSequence(sequence) {
|
||||
// Cohee: oobabooga's textgen always appends newline before the sequence as a stopping string
|
||||
// But it's a problem for Metharme which doesn't use newlines to separate them.
|
||||
const wrap = (s) => power_user.instruct.wrap ? '\n' + s : s;
|
||||
// Sequence must be a non-empty string
|
||||
if (typeof sequence === 'string' && sequence.length > 0) {
|
||||
// If sequence is just a whitespace or newline - we don't want to make it a stopping string
|
||||
// User can always add it as a custom stop string if really needed
|
||||
if (sequence.trim().length > 0) {
|
||||
const wrappedSequence = wrap(sequence);
|
||||
// Need to respect "insert macro" setting
|
||||
const stopString = power_user.instruct.macro ? substituteParams(wrappedSequence) : wrappedSequence;
|
||||
result.push(stopString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (power_user.instruct.enabled) {
|
||||
const input_sequence = power_user.instruct.input_sequence;
|
||||
const output_sequence = power_user.instruct.output_sequence;
|
||||
const last_output_sequence = power_user.instruct.last_output_sequence;
|
||||
|
||||
const combined_sequence = `${input_sequence}\n${output_sequence}\n${last_output_sequence}`;
|
||||
|
||||
combined_sequence.split('\n').filter((line, index, self) => self.indexOf(line) === index).forEach(addInstructSequence);
|
||||
}
|
||||
result.push(...getInstructStoppingSequences());
|
||||
|
||||
if (power_user.custom_stopping_strings) {
|
||||
const customStoppingStrings = getCustomStoppingStrings();
|
||||
@ -2069,9 +2055,8 @@ function getPersonaDescription(storyString) {
|
||||
|
||||
switch (power_user.persona_description_position) {
|
||||
case persona_description_positions.BEFORE_CHAR:
|
||||
return `${substituteParams(power_user.persona_description)}\n${storyString}`;
|
||||
case persona_description_positions.AFTER_CHAR:
|
||||
return `${storyString}${substituteParams(power_user.persona_description)}\n`;
|
||||
return storyString;
|
||||
default:
|
||||
if (shouldWIAddPrompt) {
|
||||
const originalAN = extension_prompts[NOTE_MODULE_NAME].value
|
||||
@ -2378,10 +2363,6 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
||||
abortController = new AbortController();
|
||||
}
|
||||
|
||||
if (main_api == 'novel' && quiet_prompt) {
|
||||
quiet_prompt = adjustNovelInstructionPrompt(quiet_prompt);
|
||||
}
|
||||
|
||||
// OpenAI doesn't need instruct mode. Use OAI main prompt instead.
|
||||
const isInstruct = power_user.instruct.enabled && main_api !== 'openai';
|
||||
const isImpersonate = type == "impersonate";
|
||||
@ -2470,6 +2451,11 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
||||
}
|
||||
}
|
||||
|
||||
if (quiet_prompt) {
|
||||
quiet_prompt = substituteParams(quiet_prompt);
|
||||
quiet_prompt = main_api == 'novel' ? adjustNovelInstructionPrompt(quiet_prompt) : quiet_prompt;
|
||||
}
|
||||
|
||||
if (true === dryRun ||
|
||||
(online_status != 'no_connection' && this_chid != undefined && this_chid !== 'invalid-safety-id')) {
|
||||
let textareaText;
|
||||
@ -2527,11 +2513,16 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
||||
const scenarioText = chat_metadata['scenario'] || characters[this_chid].scenario;
|
||||
let charDescription = baseChatReplace(characters[this_chid].description.trim(), name1, name2);
|
||||
let charPersonality = baseChatReplace(characters[this_chid].personality.trim(), name1, name2);
|
||||
let personaDescription = baseChatReplace(power_user.persona_description.trim(), name1, name2);
|
||||
let Scenario = baseChatReplace(scenarioText.trim(), name1, name2);
|
||||
let mesExamples = baseChatReplace(characters[this_chid].mes_example.trim(), name1, name2);
|
||||
let systemPrompt = power_user.prefer_character_prompt ? baseChatReplace(characters[this_chid].data?.system_prompt?.trim(), name1, name2) : '';
|
||||
let jailbreakPrompt = power_user.prefer_character_jailbreak ? baseChatReplace(characters[this_chid].data?.post_history_instructions?.trim(), name1, name2) : '';
|
||||
|
||||
if (isInstruct) {
|
||||
systemPrompt = power_user.prefer_character_prompt && systemPrompt ? systemPrompt : baseChatReplace(power_user.instruct.system_prompt, name1, name2);
|
||||
}
|
||||
|
||||
// Parse example messages
|
||||
if (!mesExamples.startsWith('<START>')) {
|
||||
mesExamples = '<START>\n' + mesExamples.trim();
|
||||
@ -2539,11 +2530,17 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
||||
if (mesExamples.replace(/<START>/gi, '').trim().length === 0) {
|
||||
mesExamples = '';
|
||||
}
|
||||
if (mesExamples && isInstruct) {
|
||||
mesExamples = formatInstructModeExamples(mesExamples, name1, name2)
|
||||
}
|
||||
|
||||
const exampleSeparator = power_user.context.example_separator ? `${power_user.context.example_separator}\n` : '';
|
||||
const blockHeading = main_api === 'openai' ? '<START>\n' : exampleSeparator;
|
||||
let mesExamplesArray = mesExamples.split(/<START>/gi).slice(1).map(block => `${blockHeading}${block.trim()}\n`);
|
||||
|
||||
if (power_user.strip_examples)
|
||||
mesExamplesArray = []
|
||||
|
||||
// First message in fresh 1-on-1 chat reacts to user/character settings changes
|
||||
if (chat.length) {
|
||||
chat[0].mes = substituteParams(chat[0].mes);
|
||||
@ -2570,7 +2567,9 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
||||
const storyStringParams = {
|
||||
description: charDescription,
|
||||
personality: charPersonality,
|
||||
persona: personaDescription,
|
||||
scenario: Scenario,
|
||||
system: isInstruct ? systemPrompt : '',
|
||||
char: name2,
|
||||
user: name1,
|
||||
};
|
||||
@ -2639,11 +2638,6 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
||||
storyString = worldInfoBefore + storyString + worldInfoAfter;
|
||||
}
|
||||
|
||||
// Format the instruction string
|
||||
if (isInstruct) {
|
||||
storyString = formatInstructStoryString(storyString, systemPrompt);
|
||||
}
|
||||
|
||||
if (main_api === 'openai') {
|
||||
message_already_generated = ''; // OpenAI doesn't have multigen
|
||||
setOpenAIMessages(coreChat);
|
||||
@ -5402,9 +5396,8 @@ async function getSettings(type) {
|
||||
setWorldInfoSettings(settings.world_info_settings ?? settings, data);
|
||||
|
||||
api_server_textgenerationwebui = settings.api_server_textgenerationwebui;
|
||||
$("#textgenerationwebui_api_url_text").val(
|
||||
api_server_textgenerationwebui
|
||||
);
|
||||
$("#textgenerationwebui_api_url_text").val(api_server_textgenerationwebui);
|
||||
$("#mancer_api_url_text").val(api_server_textgenerationwebui);
|
||||
api_use_mancer_webui = settings.api_use_mancer_webui
|
||||
$('#use-mancer-api-checkbox').prop("checked", api_use_mancer_webui);
|
||||
$('#use-mancer-api-checkbox').trigger("change");
|
||||
@ -5768,7 +5761,6 @@ export async function displayPastChats() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
displayChats(''); // Display all by default
|
||||
|
||||
@ -7518,7 +7510,7 @@ $(document).ready(function () {
|
||||
$("#character_search_bar").val("").trigger("input");
|
||||
});
|
||||
|
||||
$(document).on("click", ".character_select", function() {
|
||||
$(document).on("click", ".character_select", function () {
|
||||
const id = $(this).attr("chid");
|
||||
selectCharacterById(id);
|
||||
});
|
||||
@ -8002,7 +7994,9 @@ $(document).ready(function () {
|
||||
|
||||
$("#use-mancer-api-checkbox").on("change", function (e) {
|
||||
const enabled = $("#use-mancer-api-checkbox").prop("checked");
|
||||
$("#mancer-api-ui").toggle(enabled);
|
||||
$("#mancer_api_subpanel").toggle(enabled);
|
||||
$("#tgwebui_api_subpanel").toggle(!enabled);
|
||||
|
||||
api_use_mancer_webui = enabled;
|
||||
saveSettingsDebounced();
|
||||
getStatus();
|
||||
@ -8010,8 +8004,9 @@ $(document).ready(function () {
|
||||
|
||||
$("#api_button_textgenerationwebui").click(async function (e) {
|
||||
e.stopPropagation();
|
||||
if ($("#textgenerationwebui_api_url_text").val() != "") {
|
||||
let value = formatTextGenURL($("#textgenerationwebui_api_url_text").val().trim(), api_use_mancer_webui);
|
||||
const url_source = api_use_mancer_webui ? "#mancer_api_url_text" : "#textgenerationwebui_api_url_text";
|
||||
if ($(url_source).val() != "") {
|
||||
let value = formatTextGenURL($(url_source).val().trim(), api_use_mancer_webui);
|
||||
if (!value) {
|
||||
callPopup("Please enter a valid URL.<br/>WebUI URLs should end with <tt>/api</tt><br/>Enable 'Relaxed API URLs' to allow other paths.", 'text');
|
||||
return;
|
||||
@ -8022,9 +8017,13 @@ $(document).ready(function () {
|
||||
await writeSecret(SECRET_KEYS.MANCER, mancer_key);
|
||||
}
|
||||
|
||||
$("#textgenerationwebui_api_url_text").val(value);
|
||||
$(url_source).val(value);
|
||||
$("#api_loading_textgenerationwebui").css("display", "inline-block");
|
||||
$("#api_button_textgenerationwebui").css("display", "none");
|
||||
|
||||
if (api_use_mancer_webui) {
|
||||
textgenerationwebui_settings.streaming_url = value.replace("http", "ws") + "/v1/stream";
|
||||
}
|
||||
api_server_textgenerationwebui = value;
|
||||
main_api = "textgenerationwebui";
|
||||
saveSettingsDebounced();
|
||||
@ -8577,8 +8576,10 @@ $(document).ready(function () {
|
||||
|
||||
$(document).on("click", ".mes_edit_delete", async function (event, customData) {
|
||||
const fromSlashCommand = customData?.fromSlashCommand || false;
|
||||
const swipeExists = (!chat[this_edit_mes_id].swipes || chat[this_edit_mes_id].swipes.length <= 1 || chat.is_user || parseInt(this_edit_mes_id) !== chat.length - 1);
|
||||
if (power_user.confirm_message_delete && fromSlashCommand !== true) {
|
||||
const confirmation = await callPopup("Are you sure you want to delete this message?", 'confirm');
|
||||
const confirmation = swipeExists ? await callPopup("Are you sure you want to delete this message?", 'confirm')
|
||||
: await callPopup("<h3>Delete this...</h3> <select id='del_type'><option value='swipe'>Swipe</option><option value='message'>Message</option></select>", 'confirm')
|
||||
if (!confirmation) {
|
||||
return;
|
||||
}
|
||||
@ -8590,10 +8591,21 @@ $(document).ready(function () {
|
||||
return;
|
||||
}
|
||||
|
||||
chat.splice(this_edit_mes_id, 1);
|
||||
if ($('#del_type').val() === 'swipe') {
|
||||
const swipe_id = chat[this_edit_mes_id]['swipe_id'];
|
||||
chat[this_edit_mes_id]['swipes'].splice(swipe_id, 1);
|
||||
if (swipe_id > 0) {
|
||||
$('.swipe_left:last').click();
|
||||
} else {
|
||||
$('.swipe_right:last').click()
|
||||
}
|
||||
} else {
|
||||
chat.splice(this_edit_mes_id, 1);
|
||||
mes.remove();
|
||||
count_view_mes--;
|
||||
}
|
||||
|
||||
this_edit_mes_id = undefined;
|
||||
mes.remove();
|
||||
count_view_mes--;
|
||||
|
||||
updateViewMessageIds();
|
||||
saveChatConditional();
|
||||
|
@ -1,3 +1,5 @@
|
||||
"use strict";
|
||||
|
||||
import { callPopup, event_types, eventSource, is_send_press, main_api, substituteParams } from "../script.js";
|
||||
import { is_group_generating } from "./group-chats.js";
|
||||
import { TokenHandler } from "./openai.js";
|
||||
@ -271,6 +273,8 @@ PromptManagerModule.prototype.init = function (moduleConfiguration, serviceSetti
|
||||
this.serviceSettings = serviceSettings;
|
||||
this.containerElement = document.getElementById(this.configuration.containerIdentifier);
|
||||
|
||||
if ('global' === this.configuration.promptOrder.strategy) this.activeCharacter = {id: this.configuration.promptOrder.dummyId};
|
||||
|
||||
this.sanitizeServiceSettings();
|
||||
|
||||
// Enable and disable prompts
|
||||
@ -590,7 +594,7 @@ PromptManagerModule.prototype.init = function (moduleConfiguration, serviceSetti
|
||||
PromptManagerModule.prototype.render = function (afterTryGenerate = true) {
|
||||
if (main_api !== 'openai') return;
|
||||
|
||||
if (null === this.activeCharacter) return;
|
||||
if ('character' === this.configuration.promptOrder.strategy && null === this.activeCharacter) return;
|
||||
this.error = null;
|
||||
|
||||
waitUntilCondition(() => !is_send_press && !is_group_generating, 1024 * 1024, 100).then(() => {
|
||||
@ -604,6 +608,11 @@ PromptManagerModule.prototype.render = function (afterTryGenerate = true) {
|
||||
this.renderPromptManagerListItems()
|
||||
this.makeDraggable();
|
||||
this.profileEnd('render');
|
||||
}).catch(error => {
|
||||
this.log('Error caught during render: ' + error);
|
||||
this.renderPromptManager();
|
||||
this.renderPromptManagerListItems()
|
||||
this.makeDraggable();
|
||||
});
|
||||
} else {
|
||||
// Executed during live communication
|
||||
@ -1155,7 +1164,6 @@ PromptManagerModule.prototype.setChatCompletion = function (chatCompletion) {
|
||||
|
||||
this.setMessages(messages);
|
||||
this.populateTokenCounts(messages);
|
||||
this.populateLegacyTokenCounts(messages);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1185,7 +1193,7 @@ PromptManagerModule.prototype.populateTokenCounts = function (messages) {
|
||||
PromptManagerModule.prototype.populateLegacyTokenCounts = function (messages) {
|
||||
// Update general token counts
|
||||
const chatHistory = messages.getItemByIdentifier('chatHistory');
|
||||
const startChat = chatHistory?.getCollection()[0].getTokens() || 0;
|
||||
const startChat = chatHistory?.getCollection()[0]?.getTokens() || 0;
|
||||
const continueNudge = chatHistory?.getCollection().find(message => message.identifier === 'continueNudge')?.getTokens() || 0;
|
||||
|
||||
this.tokenHandler.counts = {
|
||||
|
@ -499,7 +499,7 @@ async function moduleWorker() {
|
||||
const context = getContext();
|
||||
|
||||
// non-characters not supported
|
||||
if (!context.groupId && context.characterId === undefined) {
|
||||
if (!context.groupId && (context.characterId === undefined || context.characterId === 'invalid-safety-id')) {
|
||||
removeExpression();
|
||||
return;
|
||||
}
|
||||
|
@ -380,8 +380,7 @@ async function summarizeChatMain(context, force) {
|
||||
}
|
||||
|
||||
console.log('Summarizing chat, messages since last summary: ' + messagesSinceLastSummary, 'words since last summary: ' + wordsSinceLastSummary);
|
||||
const prompt = substituteParams(extension_settings.memory.prompt)
|
||||
.replace(/{{words}}/gi, extension_settings.memory.promptWords);
|
||||
const prompt = extension_settings.memory.prompt?.replace(/{{words}}/gi, extension_settings.memory.promptWords);
|
||||
|
||||
if (!prompt) {
|
||||
console.debug('Summarization prompt is empty. Skipping summarization.');
|
||||
|
@ -72,20 +72,22 @@ function getTaskByIdRecurse(taskId, task) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function substituteParamsPrompts(content) {
|
||||
function substituteParamsPrompts(content, substituteGlobal) {
|
||||
content = content.replace(/{{objective}}/gi, currentObjective.description)
|
||||
content = content.replace(/{{task}}/gi, currentTask.description)
|
||||
if (currentTask.parent){
|
||||
content = content.replace(/{{parent}}/gi, currentTask.parent.description)
|
||||
}
|
||||
content = substituteParams(content)
|
||||
if (substituteGlobal) {
|
||||
content = substituteParams(content)
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
// Call Quiet Generate to create task list using character context, then convert to tasks. Should not be called much.
|
||||
async function generateTasks() {
|
||||
|
||||
const prompt = substituteParamsPrompts(objectivePrompts.createTask);
|
||||
const prompt = substituteParamsPrompts(objectivePrompts.createTask, false);
|
||||
console.log(`Generating tasks for objective with prompt`)
|
||||
toastr.info('Generating tasks for objective', 'Please wait...');
|
||||
const taskResponse = await generateQuietPrompt(prompt)
|
||||
@ -128,7 +130,7 @@ async function checkTaskCompleted() {
|
||||
checkCounter = $('#objective-check-frequency').val()
|
||||
toastr.info("Checking for task completion.")
|
||||
|
||||
const prompt = substituteParamsPrompts(objectivePrompts.checkTaskCompleted);
|
||||
const prompt = substituteParamsPrompts(objectivePrompts.checkTaskCompleted, false);
|
||||
const taskResponse = (await generateQuietPrompt(prompt)).toLowerCase()
|
||||
|
||||
// Check response if task complete
|
||||
@ -178,7 +180,7 @@ function setCurrentTask(taskId = null) {
|
||||
// Don't just check for a current task, check if it has data
|
||||
const description = currentTask.description || null;
|
||||
if (description) {
|
||||
const extensionPromptText = substituteParamsPrompts(objectivePrompts.currentTask);
|
||||
const extensionPromptText = substituteParamsPrompts(objectivePrompts.currentTask, true);
|
||||
|
||||
// Remove highlights
|
||||
$('.objective-task').css({'border-color':'','border-width':''})
|
||||
|
@ -1,5 +1,4 @@
|
||||
import {
|
||||
substituteParams,
|
||||
saveSettingsDebounced,
|
||||
systemUserName,
|
||||
hideSwipeButtons,
|
||||
@ -14,7 +13,8 @@ import {
|
||||
} from "../../../script.js";
|
||||
import { getApiUrl, getContext, extension_settings, doExtrasFetch, modules } from "../../extensions.js";
|
||||
import { selected_group } from "../../group-chats.js";
|
||||
import { stringFormat, initScrollHeight, resetScrollHeight, timestampToMoment, getCharaFilename } from "../../utils.js";
|
||||
import { stringFormat, initScrollHeight, resetScrollHeight, timestampToMoment, getCharaFilename, saveBase64AsFile } from "../../utils.js";
|
||||
import { humanizedDateTime } from "../../RossAscends-mods.js";
|
||||
export { MODULE_NAME };
|
||||
|
||||
// Wraps a string into monospace font-face span
|
||||
@ -512,7 +512,7 @@ function getQuietPrompt(mode, trigger) {
|
||||
return trigger;
|
||||
}
|
||||
|
||||
return substituteParams(stringFormat(extension_settings.sd.prompts[mode], trigger));
|
||||
return stringFormat(extension_settings.sd.prompts[mode], trigger);
|
||||
}
|
||||
|
||||
function processReply(str) {
|
||||
@ -537,6 +537,7 @@ function processReply(str) {
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
function getRawLastMessage() {
|
||||
const context = getContext();
|
||||
const lastMessage = context.chat.slice(-1)[0].mes,
|
||||
@ -565,6 +566,10 @@ async function generatePicture(_, trigger, message, callback) {
|
||||
const quiet_prompt = getQuietPrompt(generationType, trigger);
|
||||
const context = getContext();
|
||||
|
||||
// if context.characterId is not null, then we get context.characters[context.characterId].avatar, else we get groupId and context.groups[groupId].id
|
||||
// sadly, groups is not an array, but is a dict with keys being index numbers, so we have to filter it
|
||||
const characterName = context.characterId ? context.characters[context.characterId].name : context.groups[Object.keys(context.groups).filter(x => context.groups[x].id === context.groupId)[0]].id.toString();
|
||||
|
||||
const prevSDHeight = extension_settings.sd.height;
|
||||
const prevSDWidth = extension_settings.sd.width;
|
||||
const aspectRatio = extension_settings.sd.width / extension_settings.sd.height;
|
||||
@ -580,8 +585,10 @@ async function generatePicture(_, trigger, message, callback) {
|
||||
// Round to nearest multiple of 64
|
||||
extension_settings.sd.width = Math.round(extension_settings.sd.height * 1.8 / 64) * 64;
|
||||
const callbackOriginal = callback;
|
||||
callback = function (prompt, base64Image) {
|
||||
const imgUrl = `url(${base64Image})`;
|
||||
callback = async function (prompt, base64Image) {
|
||||
const imagePath = base64Image;
|
||||
const imgUrl = `url('${encodeURIComponent(base64Image)}')`;
|
||||
|
||||
if ('forceSetBackground' in window) {
|
||||
forceSetBackground(imgUrl);
|
||||
} else {
|
||||
@ -590,9 +597,9 @@ async function generatePicture(_, trigger, message, callback) {
|
||||
}
|
||||
|
||||
if (typeof callbackOriginal === 'function') {
|
||||
callbackOriginal(prompt, base64Image);
|
||||
callbackOriginal(prompt, imagePath);
|
||||
} else {
|
||||
sendMessage(prompt, base64Image);
|
||||
sendMessage(prompt, imagePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -604,7 +611,7 @@ async function generatePicture(_, trigger, message, callback) {
|
||||
context.deactivateSendButtons();
|
||||
hideSwipeButtons();
|
||||
|
||||
await sendGenerationRequest(generationType, prompt, callback);
|
||||
await sendGenerationRequest(generationType, prompt, characterName, callback);
|
||||
} catch (err) {
|
||||
console.trace(err);
|
||||
throw new Error('SD prompt text generation failed.')
|
||||
@ -644,19 +651,31 @@ async function generatePrompt(quiet_prompt) {
|
||||
return processReply(reply);
|
||||
}
|
||||
|
||||
async function sendGenerationRequest(generationType, prompt, callback) {
|
||||
async function sendGenerationRequest(generationType, prompt, characterName = null, callback) {
|
||||
const prefix = generationType !== generationMode.BACKGROUND
|
||||
? combinePrefixes(extension_settings.sd.prompt_prefix, getCharacterPrefix())
|
||||
: extension_settings.sd.prompt_prefix;
|
||||
|
||||
if (extension_settings.sd.horde) {
|
||||
await generateHordeImage(prompt, prefix, callback);
|
||||
await generateHordeImage(prompt, prefix, characterName, callback);
|
||||
} else {
|
||||
await generateExtrasImage(prompt, prefix, callback);
|
||||
await generateExtrasImage(prompt, prefix, characterName, callback);
|
||||
}
|
||||
}
|
||||
|
||||
async function generateExtrasImage(prompt, prefix, callback) {
|
||||
/**
|
||||
* Generates an "extras" image using a provided prompt and other settings,
|
||||
* then saves the generated image and either invokes a callback or sends a message with the image.
|
||||
*
|
||||
* @param {string} prompt - The main instruction used to guide the image generation.
|
||||
* @param {string} prefix - Additional context or prefix to guide the image generation.
|
||||
* @param {string} characterName - The name used to determine the sub-directory for saving.
|
||||
* @param {function} [callback] - Optional callback function invoked with the prompt and saved image.
|
||||
* If not provided, `sendMessage` is called instead.
|
||||
*
|
||||
* @returns {Promise<void>} - A promise that resolves when the image generation and processing are complete.
|
||||
*/
|
||||
async function generateExtrasImage(prompt, prefix, characterName, callback) {
|
||||
console.debug(extension_settings.sd);
|
||||
const url = new URL(getApiUrl());
|
||||
url.pathname = '/api/image';
|
||||
@ -680,14 +699,28 @@ async function generateExtrasImage(prompt, prefix, callback) {
|
||||
|
||||
if (result.ok) {
|
||||
const data = await result.json();
|
||||
const base64Image = `data:image/jpeg;base64,${data.image}`;
|
||||
//filename should be character name + human readable timestamp + generation mode
|
||||
const filename = `${characterName}_${humanizedDateTime()}`;
|
||||
const base64Image = await saveBase64AsFile(data.image, characterName, filename, "jpg");
|
||||
callback ? callback(prompt, base64Image) : sendMessage(prompt, base64Image);
|
||||
} else {
|
||||
callPopup('Image generation has failed. Please try again.', 'text');
|
||||
}
|
||||
}
|
||||
|
||||
async function generateHordeImage(prompt, prefix, callback) {
|
||||
/**
|
||||
* Generates a "horde" image using the provided prompt and configuration settings,
|
||||
* then saves the generated image and either invokes a callback or sends a message with the image.
|
||||
*
|
||||
* @param {string} prompt - The main instruction used to guide the image generation.
|
||||
* @param {string} prefix - Additional context or prefix to guide the image generation.
|
||||
* @param {string} characterName - The name used to determine the sub-directory for saving.
|
||||
* @param {function} [callback] - Optional callback function invoked with the prompt and saved image.
|
||||
* If not provided, `sendMessage` is called instead.
|
||||
*
|
||||
* @returns {Promise<void>} - A promise that resolves when the image generation and processing are complete.
|
||||
*/
|
||||
async function generateHordeImage(prompt, prefix, characterName, callback) {
|
||||
const result = await fetch('/horde_generateimage', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
@ -709,7 +742,8 @@ async function generateHordeImage(prompt, prefix, callback) {
|
||||
|
||||
if (result.ok) {
|
||||
const data = await result.text();
|
||||
const base64Image = `data:image/webp;base64,${data}`;
|
||||
const filename = `${characterName}_${humanizedDateTime()}`;
|
||||
const base64Image = await saveBase64AsFile(data, characterName, filename, "webp");
|
||||
callback ? callback(prompt, base64Image) : sendMessage(prompt, base64Image);
|
||||
} else {
|
||||
toastr.error('Image generation has failed. Please try again.');
|
||||
@ -827,7 +861,7 @@ async function sdMessageButton(e) {
|
||||
const message_id = $mes.attr('mesid');
|
||||
const message = context.chat[message_id];
|
||||
const characterName = message?.name || context.name2;
|
||||
const messageText = substituteParams(message?.mes);
|
||||
const messageText = message?.mes;
|
||||
const hasSavedImage = message?.extra?.image && message?.extra?.title;
|
||||
|
||||
if ($icon.hasClass(busyClass)) {
|
||||
@ -842,7 +876,7 @@ async function sdMessageButton(e) {
|
||||
message.extra.title = prompt;
|
||||
|
||||
console.log('Regenerating an image, using existing prompt:', prompt);
|
||||
await sendGenerationRequest(generationMode.FREE, prompt, saveGeneratedImage);
|
||||
await sendGenerationRequest(generationMode.FREE, prompt, characterName, saveGeneratedImage);
|
||||
}
|
||||
else {
|
||||
console.log("doing /sd raw last");
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
isDataURL,
|
||||
createThumbnail,
|
||||
extractAllWords,
|
||||
saveBase64AsFile
|
||||
} from './utils.js';
|
||||
import { RA_CountCharTokens, humanizedDateTime, dragElement, favsToHotswap } from "./RossAscends-mods.js";
|
||||
import { loadMovingUIState, sortEntitiesList } from './power-user.js';
|
||||
@ -80,6 +81,7 @@ export {
|
||||
regenerateGroup,
|
||||
resetSelectedGroup,
|
||||
select_group_chats,
|
||||
getGroupChatNames,
|
||||
}
|
||||
|
||||
let is_group_generating = false; // Group generation flag
|
||||
@ -336,25 +338,25 @@ async function getGroups() {
|
||||
}
|
||||
|
||||
export function getGroupBlock(group) {
|
||||
const template = $("#group_list_template .group_select").clone();
|
||||
template.data("id", group.id);
|
||||
template.attr("grid", group.id);
|
||||
template.find(".ch_name").html(group.name);
|
||||
template.find('.group_fav_icon').css("display", 'none');
|
||||
template.addClass(group.fav ? 'is_fav' : '');
|
||||
template.find(".ch_fav").val(group.fav);
|
||||
const template = $("#group_list_template .group_select").clone();
|
||||
template.data("id", group.id);
|
||||
template.attr("grid", group.id);
|
||||
template.find(".ch_name").text(group.name);
|
||||
template.find('.group_fav_icon').css("display", 'none');
|
||||
template.addClass(group.fav ? 'is_fav' : '');
|
||||
template.find(".ch_fav").val(group.fav);
|
||||
|
||||
// Display inline tags
|
||||
const tags = getTagsList(group.id);
|
||||
const tagsElement = template.find('.tags');
|
||||
tags.forEach(tag => appendTagToList(tagsElement, tag, {}));
|
||||
// Display inline tags
|
||||
const tags = getTagsList(group.id);
|
||||
const tagsElement = template.find('.tags');
|
||||
tags.forEach(tag => appendTagToList(tagsElement, tag, {}));
|
||||
|
||||
const avatar = getGroupAvatar(group);
|
||||
if (avatar) {
|
||||
$(template).find(".avatar").replaceWith(avatar);
|
||||
}
|
||||
const avatar = getGroupAvatar(group);
|
||||
if (avatar) {
|
||||
$(template).find(".avatar").replaceWith(avatar);
|
||||
}
|
||||
|
||||
return template;
|
||||
return template;
|
||||
}
|
||||
|
||||
function updateGroupAvatar(group) {
|
||||
@ -362,17 +364,26 @@ function updateGroupAvatar(group) {
|
||||
|
||||
$(".group_select").each(function () {
|
||||
if ($(this).data("id") == group.id) {
|
||||
$(this).find(".avatar").replaceWith(getGroupAvatar(group));
|
||||
$(this).find(".avatar").replaceWith(getGroupAvatar(group));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// check if isDataURLor if it's a valid local file url
|
||||
function isValidImageUrl(url) {
|
||||
// check if empty dict
|
||||
if (Object.keys(url).length === 0) {
|
||||
return false;
|
||||
}
|
||||
return isDataURL(url) || (url && url.startsWith("user"));
|
||||
}
|
||||
|
||||
function getGroupAvatar(group) {
|
||||
if (!group) {
|
||||
return $(`<div class="avatar"><img src="${default_avatar}"></div>`);
|
||||
}
|
||||
|
||||
if (isDataURL(group.avatar_url)) {
|
||||
// if isDataURL or if it's a valid local file url
|
||||
if (isValidImageUrl(group.avatar_url)) {
|
||||
return $(`<div class="avatar"><img src="${group.avatar_url}"></div>`);
|
||||
}
|
||||
|
||||
@ -408,6 +419,19 @@ function getGroupAvatar(group) {
|
||||
return groupAvatar;
|
||||
}
|
||||
|
||||
function getGroupChatNames(groupId) {
|
||||
const group = groups.find(x => x.id === groupId);
|
||||
|
||||
if (!group) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const names = [];
|
||||
for (const chatId of group.chats) {
|
||||
names.push(chatId);
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
|
||||
if (online_status === "no_connection") {
|
||||
@ -1079,8 +1103,7 @@ function select_group_chats(groupId, skipAnimation) {
|
||||
|
||||
setMenuType(!!group ? 'group_edit' : 'group_create');
|
||||
$("#group_avatar_preview").empty().append(getGroupAvatar(group));
|
||||
$("#rm_group_restore_avatar").toggle(!!group && isDataURL(group.avatar_url));
|
||||
$("#rm_group_chat_name").val(groupName);
|
||||
$("#rm_group_restore_avatar").toggle(!!group && isValidImageUrl(group.avatar_url));
|
||||
$("#rm_group_filter").val("").trigger("input");
|
||||
$(`input[name="rm_group_activation_strategy"][value="${replyStrategy}"]`).prop('checked', true);
|
||||
|
||||
@ -1122,9 +1145,18 @@ function select_group_chats(groupId, skipAnimation) {
|
||||
$("#rm_group_automode_label").hide();
|
||||
}
|
||||
|
||||
eventSource.emit('groupSelected', {detail: {id: openGroupId, group: group}});
|
||||
eventSource.emit('groupSelected', { detail: { id: openGroupId, group: group } });
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the upload and processing of a group avatar.
|
||||
* The selected image is read, cropped using a popup, processed into a thumbnail,
|
||||
* and then uploaded to the server.
|
||||
*
|
||||
* @param {Event} event - The event triggered by selecting a file input, containing the image file to upload.
|
||||
*
|
||||
* @returns {Promise<void>} - A promise that resolves when the processing and upload is complete.
|
||||
*/
|
||||
async function uploadGroupAvatar(event) {
|
||||
const file = event.target.files[0];
|
||||
|
||||
@ -1147,16 +1179,22 @@ async function uploadGroupAvatar(event) {
|
||||
return;
|
||||
}
|
||||
|
||||
const thumbnail = await createThumbnail(croppedImage, 96, 144);
|
||||
|
||||
let thumbnail = await createThumbnail(croppedImage, 96, 144);
|
||||
//remove data:image/whatever;base64
|
||||
thumbnail = thumbnail.replace(/^data:image\/[a-z]+;base64,/, "");
|
||||
let _thisGroup = groups.find((x) => x.id == openGroupId);
|
||||
// filename should be group id + human readable timestamp
|
||||
const filename = `${_thisGroup.id}_${humanizedDateTime()}`;
|
||||
let thumbnailUrl = await saveBase64AsFile(thumbnail, openGroupId.toString(), filename, 'jpg');
|
||||
if (!openGroupId) {
|
||||
$('#group_avatar_preview img').attr('src', thumbnail);
|
||||
$('#group_avatar_preview img').attr('src', thumbnailUrl);
|
||||
$('#rm_group_restore_avatar').show();
|
||||
return;
|
||||
}
|
||||
|
||||
let _thisGroup = groups.find((x) => x.id == openGroupId);
|
||||
_thisGroup.avatar_url = thumbnail;
|
||||
|
||||
|
||||
_thisGroup.avatar_url = thumbnailUrl;
|
||||
$("#group_avatar_preview").empty().append(getGroupAvatar(_thisGroup));
|
||||
$("#rm_group_restore_avatar").show();
|
||||
await editGroup(openGroupId, true, true);
|
||||
@ -1238,6 +1276,11 @@ function updateFavButtonState(state) {
|
||||
}
|
||||
|
||||
export async function openGroupById(groupId) {
|
||||
if (!groups.find(x => x.id === groupId)) {
|
||||
console.log('Group not found', groupId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_send_press && !is_group_generating) {
|
||||
if (selected_group !== groupId) {
|
||||
cancelTtsPlay();
|
||||
@ -1303,7 +1346,7 @@ async function createGroup() {
|
||||
body: JSON.stringify({
|
||||
name: name,
|
||||
members: members,
|
||||
avatar_url: isDataURL(avatar_url) ? avatar_url : default_avatar,
|
||||
avatar_url: isValidImageUrl(avatar_url) ? avatar_url : default_avatar,
|
||||
allow_self_responses: allow_self_responses,
|
||||
activation_strategy: activation_strategy,
|
||||
disabled_members: [],
|
||||
|
@ -8,6 +8,8 @@ import {
|
||||
import { SECRET_KEYS, writeSecret } from "./secrets.js";
|
||||
import { delay } from "./utils.js";
|
||||
import { deviceInfo } from "./RossAscends-mods.js";
|
||||
import { power_user } from "./power-user.js";
|
||||
import { autoSelectInstructPreset } from "./instruct-mode.js";
|
||||
|
||||
export {
|
||||
horde_settings,
|
||||
@ -226,19 +228,11 @@ async function showKudos() {
|
||||
|
||||
jQuery(function () {
|
||||
$("#horde_model").on('mousedown change', async function (e) {
|
||||
//desktop-only routine for multi-select without CTRL
|
||||
/*if (deviceInfo.device.type === 'desktop') {
|
||||
let hordeModelSelectScrollTop = null;
|
||||
e.preventDefault();
|
||||
const option = $(e.target);
|
||||
const selectElement = $(this)[0];
|
||||
hordeModelSelectScrollTop = selectElement.scrollTop;
|
||||
option.prop('selected', !option.prop('selected'));
|
||||
await delay(1);
|
||||
selectElement.scrollTop = hordeModelSelectScrollTop;
|
||||
}*/
|
||||
horde_settings.models = $('#horde_model').val();
|
||||
console.log('Updated Horde models', horde_settings.models);
|
||||
|
||||
// Try select instruct preset
|
||||
autoSelectInstructPreset(horde_settings.models.join(' '));
|
||||
});
|
||||
|
||||
$("#horde_auto_adjust_response_length").on("input", function () {
|
||||
|
277
public/scripts/instruct-mode.js
Normal file
277
public/scripts/instruct-mode.js
Normal file
@ -0,0 +1,277 @@
|
||||
"use strict";
|
||||
|
||||
import { saveSettingsDebounced, substituteParams } from "../script.js";
|
||||
import { selected_group } from "./group-chats.js";
|
||||
import { power_user } from "./power-user.js";
|
||||
|
||||
export let instruct_presets = [];
|
||||
|
||||
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_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 },
|
||||
{ id: "instruct_stop_sequence", property: "stop_sequence", isCheckbox: false },
|
||||
{ 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_last_output_sequence", property: "last_output_sequence", isCheckbox: false },
|
||||
{ id: "instruct_activation_regex", property: "activation_regex", isCheckbox: false },
|
||||
];
|
||||
|
||||
/**
|
||||
* Loads instruct mode settings from the given data object.
|
||||
* @param {object} data Settings data object.
|
||||
*/
|
||||
export function loadInstructMode(data) {
|
||||
if (data.instruct !== undefined) {
|
||||
instruct_presets = data.instruct;
|
||||
}
|
||||
|
||||
if (power_user.instruct.names_force_groups === undefined) {
|
||||
power_user.instruct.names_force_groups = true;
|
||||
}
|
||||
|
||||
controls.forEach(control => {
|
||||
const $element = $(`#${control.id}`);
|
||||
|
||||
if (control.isCheckbox) {
|
||||
$element.prop('checked', power_user.instruct[control.property]);
|
||||
} else {
|
||||
$element.val(power_user.instruct[control.property]);
|
||||
}
|
||||
|
||||
$element.on('input', function () {
|
||||
power_user.instruct[control.property] = control.isCheckbox ? !!$(this).prop('checked') : $(this).val();
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
});
|
||||
|
||||
instruct_presets.forEach((preset) => {
|
||||
const name = preset.name;
|
||||
const option = document.createElement('option');
|
||||
option.value = name;
|
||||
option.innerText = name;
|
||||
option.selected = name === power_user.instruct.preset;
|
||||
$('#instruct_presets').append(option);
|
||||
});
|
||||
|
||||
highlightDefaultPreset();
|
||||
}
|
||||
|
||||
function highlightDefaultPreset() {
|
||||
$('#instruct_set_default').toggleClass('default', power_user.default_instruct === power_user.instruct.preset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically select instruct preset based on model id.
|
||||
* Otherwise, if default instruct preset is set, selects it.
|
||||
* @param {string} modelId Model name reported by the API.
|
||||
* @returns {boolean} True if instruct preset was activated by model id, false otherwise.
|
||||
*/
|
||||
export function autoSelectInstructPreset(modelId) {
|
||||
// If instruct mode is disabled, don't do anything
|
||||
if (!power_user.instruct.enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const preset of instruct_presets) {
|
||||
// If activation regex is set, check if it matches the model id
|
||||
if (preset.activation_regex) {
|
||||
try {
|
||||
const regex = new RegExp(preset.activation_regex, 'i');
|
||||
|
||||
// Stop on first match so it won't cycle back and forth between presets if multiple regexes match
|
||||
if (regex.test(modelId)) {
|
||||
// If preset is not already selected, select it
|
||||
if (power_user.instruct.preset !== preset.name) {
|
||||
$('#instruct_presets').val(preset.name).trigger('change');
|
||||
toastr.info(`Instruct mode: preset "${preset.name}" auto-selected`);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// If regex is invalid, ignore it
|
||||
console.warn(`Invalid instruct activation regex in preset "${preset.name}"`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (power_user.default_instruct && power_user.instruct.preset !== power_user.default_instruct) {
|
||||
if (instruct_presets.some(p => p.name === power_user.default_instruct)) {
|
||||
console.log(`Instruct mode: default preset "${power_user.default_instruct}" selected`);
|
||||
$('#instruct_presets').val(power_user.default_instruct).trigger('change');
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts instruct mode sequences to an array of stopping strings.
|
||||
* @returns {string[]} Array of instruct mode stopping strings.
|
||||
*/
|
||||
export function getInstructStoppingSequences() {
|
||||
function addInstructSequence(sequence) {
|
||||
// Cohee: oobabooga's textgen always appends newline before the sequence as a stopping string
|
||||
// But it's a problem for Metharme which doesn't use newlines to separate them.
|
||||
const wrap = (s) => power_user.instruct.wrap ? '\n' + s : s;
|
||||
// Sequence must be a non-empty string
|
||||
if (typeof sequence === 'string' && sequence.length > 0) {
|
||||
// If sequence is just a whitespace or newline - we don't want to make it a stopping string
|
||||
// User can always add it as a custom stop string if really needed
|
||||
if (sequence.trim().length > 0) {
|
||||
const wrappedSequence = wrap(sequence);
|
||||
// Need to respect "insert macro" setting
|
||||
const stopString = power_user.instruct.macro ? substituteParams(wrappedSequence) : wrappedSequence;
|
||||
result.push(stopString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = [];
|
||||
|
||||
if (power_user.instruct.enabled) {
|
||||
const input_sequence = power_user.instruct.input_sequence;
|
||||
const output_sequence = power_user.instruct.output_sequence;
|
||||
const last_output_sequence = power_user.instruct.last_output_sequence;
|
||||
|
||||
const combined_sequence = `${input_sequence}\n${output_sequence}\n${last_output_sequence}`;
|
||||
|
||||
combined_sequence.split('\n').filter((line, index, self) => self.indexOf(line) === index).forEach(addInstructSequence);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats instruct mode chat message.
|
||||
* @param {string} name Character name.
|
||||
* @param {string} mes Message text.
|
||||
* @param {boolean} isUser Is the message from the user.
|
||||
* @param {boolean} isNarrator Is the message from the narrator.
|
||||
* @param {string} forceAvatar Force avatar string.
|
||||
* @param {string} name1 User name.
|
||||
* @param {string} name2 Character name.
|
||||
* @returns {string} Formatted instruct mode chat message.
|
||||
*/
|
||||
export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvatar, name1, name2) {
|
||||
let includeNames = isNarrator ? false : power_user.instruct.names;
|
||||
|
||||
if (!isNarrator && power_user.instruct.names_force_groups && (selected_group || forceAvatar)) {
|
||||
includeNames = true;
|
||||
}
|
||||
|
||||
let sequence = (isUser || isNarrator) ? power_user.instruct.input_sequence : power_user.instruct.output_sequence;
|
||||
|
||||
if (power_user.instruct.macro) {
|
||||
sequence = substituteParams(sequence, name1, name2);
|
||||
}
|
||||
|
||||
const separator = power_user.instruct.wrap ? '\n' : '';
|
||||
const separatorSequence = power_user.instruct.separator_sequence && !isUser
|
||||
? power_user.instruct.separator_sequence
|
||||
: separator;
|
||||
const textArray = includeNames ? [sequence, `${name}: ${mes}` + separatorSequence] : [sequence, mes + separatorSequence];
|
||||
const text = textArray.filter(x => x).join(separator);
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats example messages according to instruct mode settings.
|
||||
* @param {string} mesExamples Example messages string.
|
||||
* @param {string} name1 User name.
|
||||
* @param {string} name2 Character name.
|
||||
* @returns {string} Formatted example messages string.
|
||||
*/
|
||||
export function formatInstructModeExamples(mesExamples, name1, name2) {
|
||||
const includeNames = power_user.instruct.names || (!!selected_group && power_user.instruct.names_force_groups);
|
||||
|
||||
let inputSequence = power_user.instruct.input_sequence;
|
||||
let outputSequence = power_user.instruct.output_sequence;
|
||||
|
||||
if (power_user.instruct.macro) {
|
||||
inputSequence = substituteParams(inputSequence, name1, name2);
|
||||
outputSequence = substituteParams(outputSequence, name1, name2);
|
||||
}
|
||||
|
||||
const separator = power_user.instruct.wrap ? '\n' : '';
|
||||
const separatorSequence = power_user.instruct.separator_sequence ? power_user.instruct.separator_sequence : separator;
|
||||
|
||||
mesExamples = mesExamples.replace(new RegExp(`\n${name1}: `, "gm"), separatorSequence + inputSequence + separator + (includeNames ? `${name1}: ` : ''));
|
||||
mesExamples = mesExamples.replace(new RegExp(`\n${name2}: `, "gm"), separator + outputSequence + separator + (includeNames ? `${name2}: ` : ''));
|
||||
|
||||
return mesExamples;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats instruct mode last prompt line.
|
||||
* @param {string} name Character name.
|
||||
* @param {boolean} isImpersonate Is generation in impersonation mode.
|
||||
* @param {string} promptBias Prompt bias string.
|
||||
* @param {string} name1 User name.
|
||||
* @param {string} name2 Character name.
|
||||
*/
|
||||
export function formatInstructModePrompt(name, isImpersonate, promptBias, name1, name2) {
|
||||
const includeNames = power_user.instruct.names || (!!selected_group && power_user.instruct.names_force_groups);
|
||||
const getOutputSequence = () => power_user.instruct.last_output_sequence || power_user.instruct.output_sequence;
|
||||
let sequence = isImpersonate ? power_user.instruct.input_sequence : getOutputSequence();
|
||||
|
||||
if (power_user.instruct.macro) {
|
||||
sequence = substituteParams(sequence, name1, name2);
|
||||
}
|
||||
|
||||
const separator = power_user.instruct.wrap ? '\n' : '';
|
||||
let text = includeNames ? (separator + sequence + separator + `${name}:`) : (separator + sequence);
|
||||
|
||||
if (!isImpersonate && promptBias) {
|
||||
text += (includeNames ? promptBias : (separator + promptBias));
|
||||
}
|
||||
|
||||
return text.trimEnd() + (includeNames ? '' : separator);
|
||||
}
|
||||
|
||||
jQuery(() => {
|
||||
$('#instruct_set_default').on('click', function () {
|
||||
if (power_user.instruct.preset === power_user.default_instruct) {
|
||||
power_user.default_instruct = null;
|
||||
$(this).removeClass('default');
|
||||
toastr.info('Default instruct preset cleared');
|
||||
} else {
|
||||
power_user.default_instruct = power_user.instruct.preset;
|
||||
$(this).addClass('default');
|
||||
toastr.info(`Default instruct preset set to ${power_user.default_instruct}`);
|
||||
}
|
||||
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#instruct_presets').on('change', function () {
|
||||
const name = $(this).find(':selected').val();
|
||||
const preset = instruct_presets.find(x => x.name === name);
|
||||
|
||||
if (!preset) {
|
||||
return;
|
||||
}
|
||||
|
||||
power_user.instruct.preset = name;
|
||||
controls.forEach(control => {
|
||||
if (preset[control.property] !== undefined) {
|
||||
power_user.instruct[control.property] = preset[control.property];
|
||||
const $element = $(`#${control.id}`);
|
||||
|
||||
if (control.isCheckbox) {
|
||||
$element.prop('checked', power_user.instruct[control.property]).trigger('input');
|
||||
} else {
|
||||
$element.val(power_user.instruct[control.property]).trigger('input');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
highlightDefaultPreset();
|
||||
});
|
||||
});
|
@ -578,7 +578,7 @@ function calculateLogitBias() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms instruction into compatible format for Novel AI.
|
||||
* Transforms instruction into compatible format for Novel AI if Novel AI instruct format not already detected.
|
||||
* 1. Instruction must begin and end with curly braces followed and preceded by a space.
|
||||
* 2. Instruction must not contain square brackets as it serves different purpose in NAI.
|
||||
* @param {string} prompt Original instruction prompt
|
||||
@ -586,7 +586,10 @@ function calculateLogitBias() {
|
||||
*/
|
||||
export function adjustNovelInstructionPrompt(prompt) {
|
||||
const stripedPrompt = prompt.replace(/[\[\]]/g, '').trim();
|
||||
return `{ ${stripedPrompt} }`;
|
||||
if (!stripedPrompt.includes('{ ')) {
|
||||
return `{ ${stripedPrompt} }`;
|
||||
}
|
||||
return stripedPrompt;
|
||||
}
|
||||
|
||||
export async function generateNovelWithStreaming(generate_data, signal) {
|
||||
|
@ -19,7 +19,6 @@ import {
|
||||
system_message_types,
|
||||
replaceBiasMarkup,
|
||||
is_send_press,
|
||||
saveSettings,
|
||||
Generate,
|
||||
main_api,
|
||||
eventSource,
|
||||
@ -219,6 +218,7 @@ const default_settings = {
|
||||
proxy_password: '',
|
||||
assistant_prefill: '',
|
||||
use_ai21_tokenizer: false,
|
||||
exclude_assistant: false,
|
||||
};
|
||||
|
||||
const oai_settings = {
|
||||
@ -260,6 +260,7 @@ const oai_settings = {
|
||||
proxy_password: '',
|
||||
assistant_prefill: '',
|
||||
use_ai21_tokenizer: false,
|
||||
exclude_assistant: false,
|
||||
};
|
||||
|
||||
let openai_setting_names;
|
||||
@ -389,7 +390,7 @@ function setupChatCompletionPromptManager(openAiSettings) {
|
||||
promptManager.tokenHandler = tokenHandler;
|
||||
|
||||
promptManager.init(configuration, openAiSettings);
|
||||
promptManager.render();
|
||||
promptManager.render(false);
|
||||
|
||||
return promptManager;
|
||||
}
|
||||
@ -479,11 +480,19 @@ function populateChatHistory(prompts, chatCompletion, type = null, cyclePrompt =
|
||||
// Chat History
|
||||
chatCompletion.add(new MessageCollection('chatHistory'), prompts.index('chatHistory'));
|
||||
|
||||
let names = (selected_group && groups.find(x => x.id === selected_group)?.members.map(member => characters.find(c => c.avatar === member)?.name).filter(Boolean).join(', ')) || '';
|
||||
// Reserve budget for new chat message
|
||||
const newChat = selected_group ? oai_settings.new_group_chat_prompt : oai_settings.new_chat_prompt;
|
||||
const newChatMessage = new Message('system', newChat, 'newMainChat');
|
||||
const newChatMessage = new Message('system', substituteParams(newChat, null, null, null, names), 'newMainChat');
|
||||
chatCompletion.reserveBudget(newChatMessage);
|
||||
|
||||
// Reserve budget for group nudge
|
||||
let groupNudgeMessage = null;
|
||||
if (selected_group) {
|
||||
const groupNudgeMessage = Message.fromPrompt(prompts.get('groupNudge'));
|
||||
chatCompletion.reserveBudget(groupNudgeMessage);
|
||||
}
|
||||
|
||||
// Reserve budget for continue nudge
|
||||
let continueMessage = null;
|
||||
if (type === 'continue' && cyclePrompt) {
|
||||
@ -512,7 +521,8 @@ function populateChatHistory(prompts, chatCompletion, type = null, cyclePrompt =
|
||||
const chatMessage = Message.fromPrompt(promptManager.preparePrompt(prompt));
|
||||
|
||||
if (true === promptManager.serviceSettings.names_in_completion && prompt.name) {
|
||||
chatMessage.name = promptManager.isValidName(prompt.name) ? prompt.name : promptManager.sanitizeName(prompt.name);
|
||||
const messageName = promptManager.isValidName(prompt.name) ? prompt.name : promptManager.sanitizeName(prompt.name);
|
||||
chatMessage.setName(messageName);
|
||||
}
|
||||
|
||||
if (chatCompletion.canAfford(chatMessage)) chatCompletion.insertAtStart(chatMessage, 'chatHistory');
|
||||
@ -524,6 +534,12 @@ function populateChatHistory(prompts, chatCompletion, type = null, cyclePrompt =
|
||||
chatCompletion.freeBudget(newChatMessage);
|
||||
chatCompletion.insertAtStart(newChatMessage, 'chatHistory');
|
||||
|
||||
// Reserve budget for group nudge
|
||||
if (selected_group && groupNudgeMessage) {
|
||||
chatCompletion.freeBudget(groupNudgeMessage);
|
||||
chatCompletion.insertAtEnd(groupNudgeMessage, 'chatHistory');
|
||||
}
|
||||
|
||||
// Insert and free continue nudge
|
||||
if (type === 'continue' && continueMessage) {
|
||||
chatCompletion.freeBudget(continueMessage);
|
||||
@ -541,9 +557,11 @@ function populateDialogueExamples(prompts, chatCompletion) {
|
||||
chatCompletion.add(new MessageCollection('dialogueExamples'), prompts.index('dialogueExamples'));
|
||||
if (openai_msgs_example.length) {
|
||||
const newExampleChat = new Message('system', oai_settings.new_example_chat_prompt, 'newChat');
|
||||
chatCompletion.reserveBudget(newExampleChat);
|
||||
|
||||
[...openai_msgs_example].forEach((dialogue, dialogueIndex) => {
|
||||
let examplesAdded = 0;
|
||||
|
||||
if (chatCompletion.canAfford(newExampleChat)) chatCompletion.insert(newExampleChat, 'dialogueExamples');
|
||||
|
||||
dialogue.forEach((prompt, promptIndex) => {
|
||||
const role = 'system';
|
||||
const content = prompt.content || '';
|
||||
@ -553,14 +571,14 @@ function populateDialogueExamples(prompts, chatCompletion) {
|
||||
chatMessage.setName(prompt.name);
|
||||
if (chatCompletion.canAfford(chatMessage)) {
|
||||
chatCompletion.insert(chatMessage, 'dialogueExamples');
|
||||
examplesAdded++;
|
||||
}
|
||||
});
|
||||
|
||||
if (0 === examplesAdded) {
|
||||
chatCompletion.removeLastFrom('dialogueExamples');
|
||||
}
|
||||
});
|
||||
|
||||
chatCompletion.freeBudget(newExampleChat);
|
||||
|
||||
const chatExamples = chatCompletion.getMessages().getItemByIdentifier('dialogueExamples').getCollection();
|
||||
if (chatExamples.length) chatCompletion.insertAtStart(newExampleChat, 'dialogueExamples');
|
||||
}
|
||||
}
|
||||
|
||||
@ -701,7 +719,8 @@ function populateChatCompletion(prompts, chatCompletion, { bias, quietPrompt, ty
|
||||
*/
|
||||
function preparePromptsForChatCompletion(Scenario, charPersonality, name2, worldInfoBefore, worldInfoAfter, charDescription, quietPrompt, bias, extensionPrompts, systemPromptOverride, jailbreakPromptOverride) {
|
||||
const scenarioText = Scenario ? `[Circumstances and context of the dialogue: ${Scenario}]` : '';
|
||||
const charPersonalityText = charPersonality ? `[${name2}'s personality: ${charPersonality}]` : '';
|
||||
const charPersonalityText = charPersonality ? `[${name2}'s personality: ${charPersonality}]` : ''
|
||||
const groupNudge = `[Write the next reply only as ${name2}]`;
|
||||
|
||||
// Create entries for system prompts
|
||||
const systemPrompts = [
|
||||
@ -715,7 +734,8 @@ function preparePromptsForChatCompletion(Scenario, charPersonality, name2, world
|
||||
{ role: 'system', content: oai_settings.nsfw_avoidance_prompt, identifier: 'nsfwAvoidance' },
|
||||
{ role: 'system', content: oai_settings.impersonation_prompt, identifier: 'impersonate' },
|
||||
{ role: 'system', content: quietPrompt, identifier: 'quietPrompt' },
|
||||
{ role: 'system', content: bias, identifier: 'bias' }
|
||||
{ role: 'system', content: bias, identifier: 'bias' },
|
||||
{ role: 'system', content: groupNudge, identifier: 'groupNudge' }
|
||||
];
|
||||
|
||||
// Tavern Extras - Summary
|
||||
@ -1130,9 +1150,9 @@ async function sendOpenAIRequest(type, openai_msgs_tosend, signal) {
|
||||
if (isClaude) {
|
||||
generate_data['use_claude'] = true;
|
||||
generate_data['top_k'] = parseFloat(oai_settings.top_k_openai);
|
||||
|
||||
generate_data['exclude_assistant'] = oai_settings.exclude_assistant;
|
||||
// Don't add a prefill on quiet gens (summarization)
|
||||
if (!isQuiet) {
|
||||
if (!isQuiet && !oai_settings.exclude_assistant) {
|
||||
generate_data['assistant_prefill'] = substituteParams(oai_settings.assistant_prefill);
|
||||
}
|
||||
}
|
||||
@ -1151,7 +1171,7 @@ async function sendOpenAIRequest(type, openai_msgs_tosend, signal) {
|
||||
generate_data['use_ai21'] = true;
|
||||
generate_data['top_k'] = parseFloat(oai_settings.top_k_openai);
|
||||
generate_data['count_pen'] = parseFloat(oai_settings.count_pen);
|
||||
generate_data['stop_tokens'] = [name1 + ':', 'prompt: [Start a new chat]'];
|
||||
generate_data['stop_tokens'] = [name1 + ':', oai_settings.new_chat_prompt, oai_settings.new_group_chat_prompt];
|
||||
}
|
||||
|
||||
const generate_url = '/generate_openai';
|
||||
@ -1370,7 +1390,7 @@ function countTokens(messages, full = false) {
|
||||
|
||||
for (const message of messages) {
|
||||
const model = getTokenizerModel();
|
||||
const hash = getStringHash(message.content);
|
||||
const hash = getStringHash(JSON.stringify(message));
|
||||
const cacheKey = `${model}-${hash}`;
|
||||
const cachedCount = tokenCache[chatId][cacheKey];
|
||||
|
||||
@ -1442,8 +1462,8 @@ class Message {
|
||||
this.role = role;
|
||||
this.content = content;
|
||||
|
||||
if (this.content) {
|
||||
this.tokens = tokenHandler.count({ role: this.role, content: this.content })
|
||||
if (typeof this.content === 'string') {
|
||||
this.tokens = tokenHandler.count({ role: this.role, content: this.content });
|
||||
} else {
|
||||
this.tokens = 0;
|
||||
}
|
||||
@ -1451,6 +1471,7 @@ class Message {
|
||||
|
||||
setName(name) {
|
||||
this.name = name;
|
||||
this.tokens = tokenHandler.count({ role: this.role, content: this.content, name: this.name });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1662,6 +1683,21 @@ class ChatCompletion {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove the last item of the collection
|
||||
*
|
||||
* @param identifier
|
||||
*/
|
||||
removeLastFrom(identifier) {
|
||||
const index = this.findMessageIndex(identifier);
|
||||
const message = this.messages.collection[index].collection.pop();
|
||||
|
||||
this.increaseTokenBudgetBy(message.getTokens());
|
||||
|
||||
this.log(`Removed ${message.identifier} from ${identifier}. Remaining tokens: ${this.tokenBudget}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the token budget can afford the tokens of the specified message.
|
||||
*
|
||||
@ -1933,7 +1969,8 @@ function loadOpenAISettings(data, settings) {
|
||||
if (settings.wrap_in_quotes !== undefined) oai_settings.wrap_in_quotes = !!settings.wrap_in_quotes;
|
||||
if (settings.names_in_completion !== undefined) oai_settings.names_in_completion = !!settings.names_in_completion;
|
||||
if (settings.openai_model !== undefined) oai_settings.openai_model = settings.openai_model;
|
||||
if (settings.use_ai21_tokenizer !== undefined) oai_settings.use_ai21_tokenizer = !!settings.use_ai21_tokenizer;
|
||||
if (settings.use_ai21_tokenizer !== undefined) { oai_settings.use_ai21_tokenizer = !!settings.use_ai21_tokenizer; oai_settings.use_ai21_tokenizer ? ai21_max = 8191 : ai21_max = 9200; }
|
||||
if (settings.exclude_assistant !== undefined) oai_settings.exclude_assistant = !!settings.exclude_assistant;
|
||||
$('#stream_toggle').prop('checked', oai_settings.stream_openai);
|
||||
$('#api_url_scale').val(oai_settings.api_url_scale);
|
||||
$('#openai_proxy_password').val(oai_settings.proxy_password);
|
||||
@ -1963,6 +2000,7 @@ function loadOpenAISettings(data, settings) {
|
||||
$('#openai_show_external_models').prop('checked', oai_settings.show_external_models);
|
||||
$('#openai_external_category').toggle(oai_settings.show_external_models);
|
||||
$('#use_ai21_tokenizer').prop('checked', oai_settings.use_ai21_tokenizer);
|
||||
$('#exclude_assistant').prop('checked', oai_settings.exclude_assistant);
|
||||
if (settings.impersonation_prompt !== undefined) oai_settings.impersonation_prompt = settings.impersonation_prompt;
|
||||
|
||||
$('#impersonation_prompt_textarea').val(oai_settings.impersonation_prompt);
|
||||
@ -2160,6 +2198,7 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) {
|
||||
show_external_models: settings.show_external_models,
|
||||
assistant_prefill: settings.assistant_prefill,
|
||||
use_ai21_tokenizer: settings.use_ai21_tokenizer,
|
||||
exclude_assistant: settings.exclude_assistant,
|
||||
};
|
||||
|
||||
const savePresetSettings = await fetch(`/savepreset_openai?name=${name}`, {
|
||||
@ -2496,6 +2535,7 @@ function onSettingsPresetChange() {
|
||||
proxy_password: ['#openai_proxy_password', 'proxy_password', false],
|
||||
assistant_prefill: ['#claude_assistant_prefill', 'assistant_prefill', false],
|
||||
use_ai21_tokenizer: ['#use_ai21_tokenizer', 'use_ai21_tokenizer', false],
|
||||
exclude_assistant: ['#exclude_assistant', 'exclude_assistant', false],
|
||||
};
|
||||
|
||||
const presetName = $('#settings_perset_openai').find(":selected").text();
|
||||
@ -2882,6 +2922,10 @@ function toggleChatCompletionForms() {
|
||||
const validSources = $(this).data('source').split(',');
|
||||
$(this).toggle(validSources.includes(oai_settings.chat_completion_source));
|
||||
});
|
||||
|
||||
if (chat_completion_sources.CLAUDE == oai_settings.chat_completion_source) {
|
||||
$('#claude_assistant_prefill_block').toggle(!oai_settings.exclude_assistant);
|
||||
}
|
||||
}
|
||||
|
||||
async function testApiConnection() {
|
||||
@ -2986,6 +3030,12 @@ $(document).ready(async function () {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#exclude_assistant').on('change', function () {
|
||||
oai_settings.exclude_assistant = !!$('#exclude_assistant').prop('checked');
|
||||
$('#claude_assistant_prefill_block').toggle(!oai_settings.exclude_assistant);
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#names_in_completion').on('change', function () {
|
||||
oai_settings.names_in_completion = !!$('#names_in_completion').prop('checked');
|
||||
saveSettingsDebounced();
|
||||
|
@ -12,8 +12,6 @@ import {
|
||||
event_types,
|
||||
getCurrentChatId,
|
||||
printCharacters,
|
||||
name1,
|
||||
name2,
|
||||
setCharacterId,
|
||||
setEditedMessageId
|
||||
} from "../script.js";
|
||||
@ -21,8 +19,8 @@ import { isMobile, initMovingUI } from "./RossAscends-mods.js";
|
||||
import {
|
||||
groups,
|
||||
resetSelectedGroup,
|
||||
selected_group,
|
||||
} from "./group-chats.js";
|
||||
import { loadInstructMode } from "./instruct-mode.js";
|
||||
|
||||
import { registerSlashCommand } from "./slash-commands.js";
|
||||
|
||||
@ -44,7 +42,7 @@ export {
|
||||
export const MAX_CONTEXT_DEFAULT = 4096;
|
||||
const MAX_CONTEXT_UNLOCKED = 65536;
|
||||
|
||||
const defaultStoryString = "{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}{{/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 = '***';
|
||||
|
||||
@ -95,6 +93,7 @@ let power_user = {
|
||||
collapse_newlines: false,
|
||||
pygmalion_formatting: pygmalion_options.AUTO,
|
||||
pin_examples: false,
|
||||
strip_examples: false,
|
||||
trim_sentences: false,
|
||||
include_newline: false,
|
||||
always_force_name2: false,
|
||||
@ -205,7 +204,6 @@ let power_user = {
|
||||
|
||||
let themes = [];
|
||||
let movingUIPresets = [];
|
||||
let instruct_presets = [];
|
||||
let context_presets = [];
|
||||
|
||||
const storage_keys = {
|
||||
@ -665,9 +663,6 @@ function loadPowerUserSettings(settings, data) {
|
||||
movingUIPresets = data.movingUIPresets;
|
||||
}
|
||||
|
||||
if (data.instruct !== undefined) {
|
||||
instruct_presets = data.instruct;
|
||||
}
|
||||
|
||||
if (data.context !== undefined) {
|
||||
context_presets = data.context;
|
||||
@ -731,6 +726,7 @@ function loadPowerUserSettings(settings, data) {
|
||||
$("#spoiler_free_mode").prop("checked", power_user.spoiler_free_mode);
|
||||
$("#collapse-newlines-checkbox").prop("checked", power_user.collapse_newlines);
|
||||
$("#pin-examples-checkbox").prop("checked", power_user.pin_examples);
|
||||
$("#remove-examples-checkbox").prop("checked", power_user.strip_examples);
|
||||
$("#always-force-name2-checkbox").prop("checked", power_user.always_force_name2);
|
||||
$("#trim_sentences_checkbox").prop("checked", power_user.trim_sentences);
|
||||
$("#include_newline_checkbox").prop("checked", power_user.include_newline);
|
||||
@ -802,7 +798,7 @@ function loadPowerUserSettings(settings, data) {
|
||||
|
||||
$(`#character_sort_order option[data-order="${power_user.sort_order}"][data-field="${power_user.sort_field}"]`).prop("selected", true);
|
||||
reloadMarkdownProcessor(power_user.render_formulas);
|
||||
loadInstructMode();
|
||||
loadInstructMode(data);
|
||||
loadContextSettings();
|
||||
loadMaxContextUnlocked();
|
||||
switchWaifuMode();
|
||||
@ -926,90 +922,6 @@ function loadContextSettings() {
|
||||
});
|
||||
}
|
||||
|
||||
function loadInstructMode() {
|
||||
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_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 },
|
||||
{ id: "instruct_stop_sequence", property: "stop_sequence", isCheckbox: false },
|
||||
{ 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_last_output_sequence", property: "last_output_sequence", isCheckbox: false },
|
||||
{ id: "instruct_activation_regex", property: "activation_regex", isCheckbox: false },
|
||||
];
|
||||
|
||||
if (power_user.instruct.names_force_groups === undefined) {
|
||||
power_user.instruct.names_force_groups = true;
|
||||
}
|
||||
|
||||
controls.forEach(control => {
|
||||
const $element = $(`#${control.id}`);
|
||||
|
||||
if (control.isCheckbox) {
|
||||
$element.prop('checked', power_user.instruct[control.property]);
|
||||
} else {
|
||||
$element.val(power_user.instruct[control.property]);
|
||||
}
|
||||
|
||||
$element.on('input', function () {
|
||||
power_user.instruct[control.property] = control.isCheckbox ? !!$(this).prop('checked') : $(this).val();
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
});
|
||||
|
||||
instruct_presets.forEach((preset) => {
|
||||
const name = preset.name;
|
||||
const option = document.createElement('option');
|
||||
option.value = name;
|
||||
option.innerText = name;
|
||||
option.selected = name === power_user.instruct.preset;
|
||||
$('#instruct_presets').append(option);
|
||||
});
|
||||
|
||||
function highlightDefaultPreset() {
|
||||
$('#instruct_set_default').toggleClass('default', power_user.default_instruct === power_user.instruct.preset);
|
||||
}
|
||||
|
||||
$('#instruct_set_default').on('click', function () {
|
||||
power_user.default_instruct = power_user.instruct.preset;
|
||||
$(this).addClass('default');
|
||||
toastr.success(`Default instruct preset set to ${power_user.default_instruct}`);
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
highlightDefaultPreset();
|
||||
|
||||
$('#instruct_presets').on('change', function () {
|
||||
const name = $(this).find(':selected').val();
|
||||
const preset = instruct_presets.find(x => x.name === name);
|
||||
|
||||
if (!preset) {
|
||||
return;
|
||||
}
|
||||
|
||||
power_user.instruct.preset = name;
|
||||
controls.forEach(control => {
|
||||
if (preset[control.property] !== undefined) {
|
||||
power_user.instruct[control.property] = preset[control.property];
|
||||
const $element = $(`#${control.id}`);
|
||||
|
||||
if (control.isCheckbox) {
|
||||
$element.prop('checked', power_user.instruct[control.property]).trigger('input');
|
||||
} else {
|
||||
$element.val(power_user.instruct[control.property]).trigger('input');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
highlightDefaultPreset();
|
||||
});
|
||||
}
|
||||
|
||||
export function fuzzySearchCharacters(searchValue) {
|
||||
const fuse = new Fuse(characters, {
|
||||
keys: [
|
||||
@ -1066,58 +978,6 @@ export function renderStoryString(params) {
|
||||
}
|
||||
}
|
||||
|
||||
export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvatar, name1, name2) {
|
||||
let includeNames = isNarrator ? false : power_user.instruct.names;
|
||||
|
||||
if (!isNarrator && power_user.instruct.names_force_groups && (selected_group || forceAvatar)) {
|
||||
includeNames = true;
|
||||
}
|
||||
|
||||
let sequence = (isUser || isNarrator) ? power_user.instruct.input_sequence : power_user.instruct.output_sequence;
|
||||
|
||||
if (power_user.instruct.macro) {
|
||||
sequence = substituteParams(sequence, name1, name2);
|
||||
}
|
||||
|
||||
const separator = power_user.instruct.wrap ? '\n' : '';
|
||||
const separatorSequence = power_user.instruct.separator_sequence && !isUser
|
||||
? power_user.instruct.separator_sequence
|
||||
: separator;
|
||||
const textArray = includeNames ? [sequence, `${name}: ${mes}` + separatorSequence] : [sequence, mes + separatorSequence];
|
||||
const text = textArray.filter(x => x).join(separator);
|
||||
return text;
|
||||
}
|
||||
|
||||
export function formatInstructStoryString(story, systemPrompt) {
|
||||
// If the character has a custom system prompt AND user has it preferred, use that instead of the default
|
||||
systemPrompt = power_user.prefer_character_prompt && systemPrompt ? systemPrompt : power_user.instruct.system_prompt;
|
||||
const sequence = power_user.instruct.system_sequence || '';
|
||||
const prompt = substituteParams(systemPrompt, name1, name2, power_user.instruct.system_prompt) || '';
|
||||
const separator = power_user.instruct.wrap ? '\n' : '';
|
||||
const textArray = [sequence, prompt + '\n' + story];
|
||||
const text = textArray.filter(x => x).join(separator);
|
||||
return text;
|
||||
}
|
||||
|
||||
export function formatInstructModePrompt(name, isImpersonate, promptBias, name1, name2) {
|
||||
const includeNames = power_user.instruct.names || (!!selected_group && power_user.instruct.names_force_groups);
|
||||
const getOutputSequence = () => power_user.instruct.last_output_sequence || power_user.instruct.output_sequence;
|
||||
let sequence = isImpersonate ? power_user.instruct.input_sequence : getOutputSequence();
|
||||
|
||||
if (power_user.instruct.macro) {
|
||||
sequence = substituteParams(sequence, name1, name2);
|
||||
}
|
||||
|
||||
const separator = power_user.instruct.wrap ? '\n' : '';
|
||||
let text = includeNames ? (separator + sequence + separator + `${name}:`) : (separator + sequence);
|
||||
|
||||
if (!isImpersonate && promptBias) {
|
||||
text += (includeNames ? promptBias : (separator + promptBias));
|
||||
}
|
||||
|
||||
return text.trimEnd() + (includeNames ? '' : separator);
|
||||
}
|
||||
|
||||
const sortFunc = (a, b) => power_user.sort_order == 'asc' ? compareFunc(a, b) : compareFunc(b, a);
|
||||
const compareFunc = (first, second) => {
|
||||
if (power_user.sort_order == 'random') {
|
||||
@ -1693,10 +1553,27 @@ $(document).ready(() => {
|
||||
});
|
||||
|
||||
$("#pin-examples-checkbox").change(function () {
|
||||
if ($(this).prop("checked")) {
|
||||
$("#remove-examples-checkbox").prop("checked", false).prop("disabled", true);
|
||||
power_user.strip_examples = false;
|
||||
} else {
|
||||
$("#remove-examples-checkbox").prop("disabled", false);
|
||||
}
|
||||
power_user.pin_examples = !!$(this).prop("checked");
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$("#remove-examples-checkbox").change(function () {
|
||||
if ($(this).prop("checked")) {
|
||||
$("#pin-examples-checkbox").prop("checked", false).prop("disabled", true);
|
||||
power_user.pin_examples = false;
|
||||
} else {
|
||||
$("#pin-examples-checkbox").prop("disabled", false);
|
||||
}
|
||||
power_user.strip_examples = !!$(this).prop("checked");
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
// include newline is the child of trim sentences
|
||||
// if include newline is checked, trim sentences must be checked
|
||||
// if trim sentences is unchecked, include newline must be unchecked
|
||||
|
@ -16,13 +16,15 @@ import {
|
||||
this_chid,
|
||||
} from "../script.js";
|
||||
import { groups, selected_group } from "./group-chats.js";
|
||||
import { instruct_presets } from "./instruct-mode.js";
|
||||
import { kai_settings } from "./kai-settings.js";
|
||||
import { power_user } from "./power-user.js";
|
||||
import {
|
||||
textgenerationwebui_preset_names,
|
||||
textgenerationwebui_presets,
|
||||
textgenerationwebui_settings,
|
||||
} from "./textgen-settings.js";
|
||||
import { download, parseJsonFile, waitUntilCondition } from "./utils.js";
|
||||
import { deepClone, download, parseJsonFile, waitUntilCondition } from "./utils.js";
|
||||
|
||||
const presetManagers = {};
|
||||
|
||||
@ -55,8 +57,10 @@ function autoSelectPreset() {
|
||||
}
|
||||
}
|
||||
|
||||
function getPresetManager() {
|
||||
const apiId = main_api == 'koboldhorde' ? 'kobold' : main_api;
|
||||
function getPresetManager(apiId) {
|
||||
if (!apiId) {
|
||||
apiId = main_api == 'koboldhorde' ? 'kobold' : main_api;
|
||||
}
|
||||
|
||||
if (!Object.keys(presetManagers).includes(apiId)) {
|
||||
return null;
|
||||
@ -162,6 +166,10 @@ class PresetManager {
|
||||
presets = textgenerationwebui_presets;
|
||||
preset_names = textgenerationwebui_preset_names;
|
||||
break;
|
||||
case "instruct":
|
||||
presets = instruct_presets;
|
||||
preset_names = instruct_presets.map(x => x.name);
|
||||
break;
|
||||
default:
|
||||
console.warn(`Unknown API ID ${this.apiId}`);
|
||||
}
|
||||
@ -169,12 +177,20 @@ class PresetManager {
|
||||
return { presets, preset_names };
|
||||
}
|
||||
|
||||
isKeyedApi() {
|
||||
return this.apiId == "textgenerationwebui" || this.apiId == "instruct";
|
||||
}
|
||||
|
||||
isNonGenericApi() {
|
||||
return this.apiId == "instruct";
|
||||
}
|
||||
|
||||
updateList(name, preset) {
|
||||
const { presets, preset_names } = this.getPresetList();
|
||||
const presetExists = this.apiId == "textgenerationwebui" ? preset_names.includes(name) : Object.keys(preset_names).includes(name);
|
||||
const presetExists = this.isKeyedApi() ? preset_names.includes(name) : Object.keys(preset_names).includes(name);
|
||||
|
||||
if (presetExists) {
|
||||
if (this.apiId == "textgenerationwebui") {
|
||||
if (this.isKeyedApi()) {
|
||||
presets[preset_names.indexOf(name)] = preset;
|
||||
$(this.select).find(`option[value="${name}"]`).prop('selected', true);
|
||||
$(this.select).val(name).trigger("change");
|
||||
@ -189,8 +205,8 @@ class PresetManager {
|
||||
else {
|
||||
presets.push(preset);
|
||||
const value = presets.length - 1;
|
||||
// ooba is reversed
|
||||
if (this.apiId == "textgenerationwebui") {
|
||||
|
||||
if (this.isKeyedApi()) {
|
||||
preset_names[value] = name;
|
||||
const option = $('<option></option>', { value: name, text: name, selected: true });
|
||||
$(this.select).append(option);
|
||||
@ -214,6 +230,10 @@ class PresetManager {
|
||||
return nai_settings;
|
||||
case "textgenerationwebui":
|
||||
return textgenerationwebui_settings;
|
||||
case "instruct":
|
||||
const preset = deepClone(power_user.instruct);
|
||||
preset['name'] = power_user.instruct.preset;
|
||||
return preset;
|
||||
default:
|
||||
console.warn(`Unknown API ID ${apiId}`);
|
||||
return {};
|
||||
@ -229,6 +249,7 @@ class PresetManager {
|
||||
'streaming_novel',
|
||||
'nai_preamble',
|
||||
'model_novel',
|
||||
"enabled",
|
||||
];
|
||||
const settings = Object.assign({}, getSettingsByApiId(this.apiId));
|
||||
|
||||
@ -238,8 +259,10 @@ class PresetManager {
|
||||
}
|
||||
}
|
||||
|
||||
settings['genamt'] = amount_gen;
|
||||
settings['max_length'] = max_context;
|
||||
if (!this.isNonGenericApi()) {
|
||||
settings['genamt'] = amount_gen;
|
||||
settings['max_length'] = max_context;
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
@ -256,7 +279,7 @@ class PresetManager {
|
||||
|
||||
$(this.select).find(`option[value="${value}"]`).remove();
|
||||
|
||||
if (this.apiId == "textgenerationwebui") {
|
||||
if (this.isKeyedApi()) {
|
||||
preset_names.splice(preset_names.indexOf(value), 1);
|
||||
} else {
|
||||
delete preset_names[nameToDelete];
|
||||
@ -289,9 +312,11 @@ jQuery(async () => {
|
||||
eventSource.on(event_types.CHAT_CHANGED, autoSelectPreset);
|
||||
registerPresetManagers();
|
||||
$(document).on("click", "[data-preset-manager-update]", async function () {
|
||||
const presetManager = getPresetManager();
|
||||
const apiId = $(this).data("preset-manager-update");
|
||||
const presetManager = getPresetManager(apiId);
|
||||
|
||||
if (!presetManager) {
|
||||
console.warn(`Preset Manager not found for API: ${apiId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -299,9 +324,11 @@ jQuery(async () => {
|
||||
});
|
||||
|
||||
$(document).on("click", "[data-preset-manager-new]", async function () {
|
||||
const presetManager = getPresetManager();
|
||||
const apiId = $(this).data("preset-manager-new");
|
||||
const presetManager = getPresetManager(apiId);
|
||||
|
||||
if (!presetManager) {
|
||||
console.warn(`Preset Manager not found for API: ${apiId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -309,9 +336,11 @@ jQuery(async () => {
|
||||
});
|
||||
|
||||
$(document).on("click", "[data-preset-manager-export]", async function () {
|
||||
const presetManager = getPresetManager();
|
||||
const apiId = $(this).data("preset-manager-export");
|
||||
const presetManager = getPresetManager(apiId);
|
||||
|
||||
if (!presetManager) {
|
||||
console.warn(`Preset Manager not found for API: ${apiId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -323,13 +352,16 @@ jQuery(async () => {
|
||||
});
|
||||
|
||||
$(document).on("click", "[data-preset-manager-import]", async function () {
|
||||
$('[data-preset-manager-file]').trigger('click');
|
||||
const apiId = $(this).data("preset-manager-import");
|
||||
$(`[data-preset-manager-file="${apiId}"]`).trigger('click');
|
||||
});
|
||||
|
||||
$(document).on("change", "[data-preset-manager-file]", async function (e) {
|
||||
const presetManager = getPresetManager();
|
||||
const apiId = $(this).data("preset-manager-file");
|
||||
const presetManager = getPresetManager(apiId);
|
||||
|
||||
if (!presetManager) {
|
||||
console.warn(`Preset Manager not found for API: ${apiId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -348,9 +380,11 @@ jQuery(async () => {
|
||||
});
|
||||
|
||||
$(document).on("click", "[data-preset-manager-delete]", async function () {
|
||||
const presetManager = getPresetManager();
|
||||
const apiId = $(this).data("preset-manager-delete");
|
||||
const presetManager = getPresetManager(apiId);
|
||||
|
||||
if (!presetManager) {
|
||||
console.warn(`Preset Manager not found for API: ${apiId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { getContext } from "./extensions.js";
|
||||
import { getRequestHeaders } from "../script.js";
|
||||
|
||||
export function onlyUnique(value, index, array) {
|
||||
return array.indexOf(value) === index;
|
||||
@ -554,6 +555,48 @@ export function extractDataFromPng(data, identifier = 'chara') {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a base64 encoded image to the backend to be saved as a file.
|
||||
*
|
||||
* @param {string} base64Data - The base64 encoded image data.
|
||||
* @param {string} characterName - The character name to determine the sub-directory for saving.
|
||||
* @param {string} ext - The file extension for the image (e.g., 'jpg', 'png', 'webp').
|
||||
*
|
||||
* @returns {Promise<string>} - Resolves to the saved image's path on the server.
|
||||
* Rejects with an error if the upload fails.
|
||||
*/
|
||||
export async function saveBase64AsFile(base64Data, characterName, filename = "", ext) {
|
||||
// Construct the full data URL
|
||||
const format = ext; // Extract the file extension (jpg, png, webp)
|
||||
const dataURL = `data:image/${format};base64,${base64Data}`;
|
||||
|
||||
// Prepare the request body
|
||||
const requestBody = {
|
||||
image: dataURL,
|
||||
ch_name: characterName,
|
||||
filename: filename
|
||||
};
|
||||
|
||||
// Send the data URL to your backend using fetch
|
||||
const response = await fetch('/uploadimage', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(requestBody),
|
||||
headers: {
|
||||
...getRequestHeaders(),
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
});
|
||||
|
||||
// If the response is successful, get the saved image path from the server's response
|
||||
if (response.ok) {
|
||||
const responseData = await response.json();
|
||||
return responseData.path;
|
||||
} else {
|
||||
const errorData = await response.json();
|
||||
throw new Error(errorData.error || 'Failed to upload the image to the server');
|
||||
}
|
||||
}
|
||||
|
||||
export function createThumbnail(dataUrl, maxWidth, maxHeight) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
|
@ -464,7 +464,7 @@ function appendWorldEntry(name, data, entry) {
|
||||
|
||||
const contentInput = template.find('textarea[name="content"]');
|
||||
contentInput.data("uid", entry.uid);
|
||||
contentInput.on("input", function () {
|
||||
contentInput.on("input", function (_, { skipCount } = {}) {
|
||||
const uid = $(this).data("uid");
|
||||
const value = $(this).val();
|
||||
data.entries[uid].content = value;
|
||||
@ -472,12 +472,25 @@ function appendWorldEntry(name, data, entry) {
|
||||
setOriginalDataValue(data, uid, "content", data.entries[uid].content);
|
||||
saveWorldInfo(name, data);
|
||||
|
||||
if (skipCount) {
|
||||
return;
|
||||
}
|
||||
|
||||
// count tokens
|
||||
countTokensDebounced(this, value);
|
||||
});
|
||||
contentInput.val(entry.content).trigger("input");
|
||||
contentInput.val(entry.content).trigger("input", { skipCount: true });
|
||||
//initScrollHeight(contentInput);
|
||||
|
||||
template.find('.inline-drawer-toggle').on('click', function () {
|
||||
const counter = template.find(".world_entry_form_token_counter");
|
||||
|
||||
if (counter.data('first-run')) {
|
||||
counter.data('first-run', false);
|
||||
countTokensDebounced(contentInput, contentInput.val());
|
||||
}
|
||||
});
|
||||
|
||||
// selective
|
||||
const selectiveInput = template.find('input[name="selective"]');
|
||||
selectiveInput.data("uid", entry.uid);
|
||||
|
@ -35,7 +35,10 @@
|
||||
--cobalt30a: rgba(100, 100, 255, 0.3);
|
||||
--greyCAIbg: rgb(36, 36, 37);
|
||||
--ivory: rgb(220, 220, 210);
|
||||
--golden: rgba(212, 175, 55, 1);
|
||||
--golden: rgb(248, 211, 0);
|
||||
--warning: rgba(255, 0, 0, 0.9);
|
||||
--active: rgb(88, 182, 0);
|
||||
--preferred: rgb(244, 67, 54);
|
||||
|
||||
|
||||
/*Default Theme, will be changed by ToolCool Color Picker*/
|
||||
@ -228,7 +231,7 @@ table.responsiveTable {
|
||||
display: block;
|
||||
font-size: calc(var(--mainFontSize) - 0.1rem);
|
||||
font-weight: 500;
|
||||
color: darkgoldenrod;
|
||||
color: var(--SmartThemeQuoteColor);
|
||||
}
|
||||
|
||||
.mes_text i,
|
||||
@ -1168,7 +1171,7 @@ input[type="file"] {
|
||||
|
||||
#extension_floating_counter {
|
||||
font-weight: 600;
|
||||
color: orange;
|
||||
color: var(--SmartThemeQuoteColor);
|
||||
}
|
||||
|
||||
.extension_token_counter {
|
||||
@ -1250,19 +1253,15 @@ select option:not(:checked) {
|
||||
}
|
||||
|
||||
.fav_on {
|
||||
color: #c5b457 !important;
|
||||
color: var(--golden) !important;
|
||||
}
|
||||
|
||||
.world_set {
|
||||
color: #4b9c00 !important;
|
||||
}
|
||||
|
||||
#instruct_set_default {
|
||||
font-size: smaller;
|
||||
color: var(--active) !important;
|
||||
}
|
||||
|
||||
#instruct_set_default.default {
|
||||
color: #f44336 !important;
|
||||
color: var(--preferred) !important;
|
||||
}
|
||||
|
||||
.displayBlock {
|
||||
@ -1280,7 +1279,7 @@ select option:not(:checked) {
|
||||
#api_button:hover,
|
||||
#api_button_novel:hover,
|
||||
#api_button_textgenerationwebui:hover {
|
||||
background-color: green;
|
||||
background-color: var(--active);
|
||||
}
|
||||
|
||||
.api-load-icon {
|
||||
@ -1554,7 +1553,7 @@ input[type=search]:focus::-webkit-search-cancel-button {
|
||||
|
||||
.ch_fav_icon {
|
||||
filter: drop-shadow(1px 1px 2px black);
|
||||
color: #c5b457;
|
||||
color: var(--golden);
|
||||
font-size: 14px;
|
||||
order: -1;
|
||||
margin-left: -75px;
|
||||
@ -1566,7 +1565,13 @@ input[type=search]:focus::-webkit-search-cancel-button {
|
||||
.character_select.is_fav .avatar,
|
||||
.group_select.is_fav .avatar,
|
||||
.group_member.is_fav .avatar {
|
||||
outline: 2px solid #c5b457;
|
||||
outline: 2px solid var(--golden);
|
||||
}
|
||||
|
||||
.character_select.is_fav .ch_name,
|
||||
.group_select.is_fav .ch_name,
|
||||
.group_member.is_fav .ch_name {
|
||||
color: var(--golden);
|
||||
}
|
||||
|
||||
#fav_chara_wrap {
|
||||
@ -2754,7 +2759,7 @@ body .ui-widget-content li:hover {
|
||||
}
|
||||
|
||||
span.warning {
|
||||
color: rgba(255, 0, 0, 0.5);
|
||||
color: var(--warning);
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
@ -3315,8 +3320,8 @@ a {
|
||||
}
|
||||
|
||||
.reverse_proxy_warning {
|
||||
color: red;
|
||||
background-color: black;
|
||||
color: var(--warning);
|
||||
background-color: var(--black70a);
|
||||
text-shadow: none !important;
|
||||
margin-top: 12px !important;
|
||||
border-radius: 5px;
|
||||
@ -3325,7 +3330,7 @@ a {
|
||||
}
|
||||
|
||||
.neutral_warning {
|
||||
color: rgba(225, 0, 0, 0.9);
|
||||
color: var(--warning);
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
@ -3359,7 +3364,7 @@ a {
|
||||
width: calc((100vw - var(--sheldWidth)) /2);
|
||||
position: absolute;
|
||||
padding: 0;
|
||||
filter: drop-shadow(2px 2px 2px #51515199);
|
||||
filter: drop-shadow(2px 2px 2px var(--grey7070a));
|
||||
z-index: 29;
|
||||
overflow: hidden;
|
||||
display: none;
|
||||
@ -3485,7 +3490,7 @@ a {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
color: white;
|
||||
color: var(--SmartThemeBodyColor);
|
||||
display: inline-block;
|
||||
/* Change display to inline-block */
|
||||
vertical-align: middle;
|
||||
@ -3495,4 +3500,4 @@ a {
|
||||
z-index: 10;
|
||||
margin-left: 10px;
|
||||
/* Give some space between the button and search box */
|
||||
}
|
||||
}
|
||||
|
109
server.js
109
server.js
@ -297,6 +297,8 @@ const baseRequestArgs = { headers: { "Content-Type": "application/json" } };
|
||||
const directories = {
|
||||
worlds: 'public/worlds/',
|
||||
avatars: 'public/User Avatars',
|
||||
images: 'public/img/',
|
||||
userImages: 'public/user/images/',
|
||||
groups: 'public/groups/',
|
||||
groupChats: 'public/group chats',
|
||||
chats: 'public/chats/',
|
||||
@ -599,7 +601,7 @@ app.post("/generate_textgenerationwebui", jsonParser, async function (request, r
|
||||
});
|
||||
|
||||
async function* readWebsocket() {
|
||||
const streamingUrl = request.header('X-Streaming-URL');
|
||||
const streamingUrl = request.header('X-Streaming-URL').replace("localhost", "127.0.0.1");
|
||||
const websocket = new WebSocket(streamingUrl);
|
||||
|
||||
websocket.on('open', async function () {
|
||||
@ -2611,6 +2613,73 @@ app.post('/uploaduseravatar', urlencodedParser, async (request, response) => {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Ensure the directory for the provided file path exists.
|
||||
* If not, it will recursively create the directory.
|
||||
*
|
||||
* @param {string} filePath - The full path of the file for which the directory should be ensured.
|
||||
*/
|
||||
function ensureDirectoryExistence(filePath) {
|
||||
const dirname = path.dirname(filePath);
|
||||
if (fs.existsSync(dirname)) {
|
||||
return true;
|
||||
}
|
||||
ensureDirectoryExistence(dirname);
|
||||
fs.mkdirSync(dirname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Endpoint to handle image uploads.
|
||||
* The image should be provided in the request body in base64 format.
|
||||
* Optionally, a character name can be provided to save the image in a sub-folder.
|
||||
*
|
||||
* @route POST /uploadimage
|
||||
* @param {Object} request.body - The request payload.
|
||||
* @param {string} request.body.image - The base64 encoded image data.
|
||||
* @param {string} [request.body.ch_name] - Optional character name to determine the sub-directory.
|
||||
* @returns {Object} response - The response object containing the path where the image was saved.
|
||||
*/
|
||||
app.post('/uploadimage', jsonParser, async (request, response) => {
|
||||
// Check for image data
|
||||
if (!request.body || !request.body.image) {
|
||||
return response.status(400).send({ error: "No image data provided" });
|
||||
}
|
||||
|
||||
// Extracting the base64 data and the image format
|
||||
const match = request.body.image.match(/^data:image\/(png|jpg|webp);base64,(.+)$/);
|
||||
if (!match) {
|
||||
return response.status(400).send({ error: "Invalid image format" });
|
||||
}
|
||||
|
||||
const [, format, base64Data] = match;
|
||||
|
||||
// Constructing filename and path
|
||||
let filename = `${Date.now()}.${format}`;
|
||||
if (request.body.filename) {
|
||||
filename = `${request.body.filename}.${format}`;
|
||||
}
|
||||
|
||||
// if character is defined, save to a sub folder for that character
|
||||
let pathToNewFile = path.join(directories.userImages, filename);
|
||||
if (request.body.ch_name) {
|
||||
pathToNewFile = path.join(directories.userImages, request.body.ch_name, filename);
|
||||
}
|
||||
|
||||
try {
|
||||
ensureDirectoryExistence(pathToNewFile);
|
||||
const imageBuffer = Buffer.from(base64Data, 'base64');
|
||||
await fs.promises.writeFile(pathToNewFile, imageBuffer);
|
||||
// send the path to the image, relative to the client folder, which means removing the first folder from the path which is 'public'
|
||||
pathToNewFile = pathToNewFile.split(path.sep).slice(1).join(path.sep);
|
||||
response.send({ path: pathToNewFile });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
response.status(500).send({ error: "Failed to save the image" });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
app.post('/getgroups', jsonParser, (_, response) => {
|
||||
const groups = [];
|
||||
|
||||
@ -3248,9 +3317,9 @@ async function sendClaudeRequest(request, response) {
|
||||
controller.abort();
|
||||
});
|
||||
|
||||
let requestPrompt = convertClaudePrompt(request.body.messages, true, true);
|
||||
let requestPrompt = convertClaudePrompt(request.body.messages, true, !request.body.exclude_assistant);
|
||||
|
||||
if (request.body.assistant_prefill) {
|
||||
if (request.body.assistant_prefill && !request.body.exclude_assistant) {
|
||||
requestPrompt += request.body.assistant_prefill;
|
||||
}
|
||||
|
||||
@ -3391,7 +3460,7 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op
|
||||
config.responseType = 'stream';
|
||||
}
|
||||
|
||||
async function makeRequest(config, response_generate_openai, request, retries = 5, timeout = 1000) {
|
||||
async function makeRequest(config, response_generate_openai, request, retries = 5, timeout = 5000) {
|
||||
try {
|
||||
const response = await axios(config);
|
||||
|
||||
@ -3413,7 +3482,7 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.response && error.response.status === 429 && retries > 0) {
|
||||
console.log('Out of quota, retrying...');
|
||||
console.log(`Out of quota, retrying in ${Math.round(timeout / 1000)}s`);
|
||||
setTimeout(() => {
|
||||
makeRequest(config, response_generate_openai, request, retries - 1);
|
||||
}, timeout);
|
||||
@ -3610,14 +3679,14 @@ app.post("/save_preset", jsonParser, function (request, response) {
|
||||
return response.sendStatus(400);
|
||||
}
|
||||
|
||||
const filename = `${name}.settings`;
|
||||
const directory = getPresetFolderByApiId(request.body.apiId);
|
||||
const settings = getPresetSettingsByAPI(request.body.apiId);
|
||||
const filename = name + settings.extension;
|
||||
|
||||
if (!directory) {
|
||||
if (!settings.folder) {
|
||||
return response.sendStatus(400);
|
||||
}
|
||||
|
||||
const fullpath = path.join(directory, filename);
|
||||
const fullpath = path.join(settings.folder, filename);
|
||||
writeFileAtomicSync(fullpath, JSON.stringify(request.body.preset, null, 4), 'utf-8');
|
||||
return response.send({ name });
|
||||
});
|
||||
@ -3628,16 +3697,16 @@ app.post("/delete_preset", jsonParser, function (request, response) {
|
||||
return response.sendStatus(400);
|
||||
}
|
||||
|
||||
const filename = `${name}.settings`;
|
||||
const directory = getPresetFolderByApiId(request.body.apiId);
|
||||
const settings = getPresetSettingsByAPI(request.body.apiId);
|
||||
const filename = name + settings.extension;
|
||||
|
||||
if (!directory) {
|
||||
if (!settings.folder) {
|
||||
return response.sendStatus(400);
|
||||
}
|
||||
|
||||
const fullpath = path.join(directory, filename);
|
||||
const fullpath = path.join(settings.folder, filename);
|
||||
|
||||
if (fs.existsSync) {
|
||||
if (fs.existsSync(fullpath)) {
|
||||
fs.unlinkSync(fullpath);
|
||||
return response.sendStatus(200);
|
||||
} else {
|
||||
@ -3657,17 +3726,19 @@ app.post("/savepreset_openai", jsonParser, function (request, response) {
|
||||
return response.send({ name });
|
||||
});
|
||||
|
||||
function getPresetFolderByApiId(apiId) {
|
||||
function getPresetSettingsByAPI(apiId) {
|
||||
switch (apiId) {
|
||||
case 'kobold':
|
||||
case 'koboldhorde':
|
||||
return directories.koboldAI_Settings;
|
||||
return { folder: directories.koboldAI_Settings, extension: '.settings' };
|
||||
case 'novel':
|
||||
return directories.novelAI_Settings;
|
||||
return { folder: directories.novelAI_Settings, extension: '.settings' };
|
||||
case 'textgenerationwebui':
|
||||
return directories.textGen_Settings;
|
||||
return { folder: directories.textGen_Settings, extension: '.settings' };
|
||||
case 'instruct':
|
||||
return { folder: directories.instruct, extension: '.json' };
|
||||
default:
|
||||
return null;
|
||||
return { folder: null, extension: null };
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user