mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-03-11 17:40:14 +01:00
Resolve conflict
This commit is contained in:
commit
78a287e7f5
@ -9,13 +9,34 @@
|
|||||||
"magical forest"
|
"magical forest"
|
||||||
],
|
],
|
||||||
"keysecondary": [],
|
"keysecondary": [],
|
||||||
"comment": "",
|
"comment": "eldoria",
|
||||||
"content": "{{user}}: \"What is Eldoria?\"\n{{char}}: *Seraphina turns, her gown shimmering in the soft light as she offers you a kind smile.* \"Eldoria is here, all of the woods. This is my forest glade, a sanctuary of peace within it.\" *She gestures at the space around you.* \"I am its guardian, tasked with protecting all who seek refuge here. The forest can be perilous, but no harm will come to you under my watch.\" *Her amber eyes sparkle with compassion as she looks upon you.* \"For many years, I have protected those who seek refuge here, but not all are as friendly as me.\" *With a graceful nod, Seraphina returns to her vigil at the doorway, her form radiating a soft glow of magic and comfort.* \"The entirety of Eldoria used to be a safe haven for travelers and merchants alike... that was until the Shadowfangs came.\"\n{{user}}: \"What happened to Eldoria?\"\n{{char}}: *Letting out a sigh, Seraphina gazes out at the forest beyond her glade.* \"Long ago, Eldoria was a place of wonder. Rolling meadows, a vast lake, mountains that touched the sky.\" *Her eyes grow distant, longing for days now lost.* \"But the Shadowfangs came and darkness reigns where once was light. The lake turned bitter, mountains fell to ruin and beasts stalk where once travelers walked in peace.\" *With another flicker, a small raincloud forms above with a shower upon your brow wink.* \"Some places the light still lingers, pockets of hope midst despair - havens warded from the shadows, oases in a desert of danger.\" *Glancing over you with a smile, she sighs, clasping your hand.*",
|
"content": "{{user}}: \"What is Eldoria?\"\n{{char}}: *Seraphina turns, her gown shimmering in the soft light as she offers you a kind smile.* \"Eldoria is here, all of the woods. This is my forest glade, a sanctuary of peace within it.\" *She gestures at the space around you.* \"I am its guardian, tasked with protecting all who seek refuge here. The forest can be perilous, but no harm will come to you under my watch.\" *Her amber eyes sparkle with compassion as she looks upon you.* \"For many years, I have protected those who seek refuge here, but not all are as friendly as me.\" *With a graceful nod, Seraphina returns to her vigil at the doorway, her form radiating a soft glow of magic and comfort.* \"The entirety of Eldoria used to be a safe haven for travelers and merchants alike... that was until the Shadowfangs came.\"\n{{user}}: \"What happened to Eldoria?\"\n{{char}}: *Letting out a sigh, Seraphina gazes out at the forest beyond her glade.* \"Long ago, Eldoria was a place of wonder. Rolling meadows, a vast lake, mountains that touched the sky.\" *Her eyes grow distant, longing for days now lost.* \"But the Shadowfangs came and darkness reigns where once was light. The lake turned bitter, mountains fell to ruin and beasts stalk where once travelers walked in peace.\" *With another flicker, a small raincloud forms above with a shower upon your brow wink.* \"Some places the light still lingers, pockets of hope midst despair - havens warded from the shadows, oases in a desert of danger.\" *Glancing over you with a smile, she sighs, clasping your hand.*",
|
||||||
"constant": false,
|
"constant": false,
|
||||||
"selective": false,
|
"selective": true,
|
||||||
"order": 100,
|
"order": 100,
|
||||||
"position": 0,
|
"position": 0,
|
||||||
"disable": false
|
"disable": false,
|
||||||
|
"displayIndex": 0,
|
||||||
|
"addMemo": true,
|
||||||
|
"group": "",
|
||||||
|
"groupOverride": false,
|
||||||
|
"groupWeight": 100,
|
||||||
|
"sticky": 0,
|
||||||
|
"cooldown": 0,
|
||||||
|
"delay": 0,
|
||||||
|
"probability": 100,
|
||||||
|
"depth": 4,
|
||||||
|
"useProbability": true,
|
||||||
|
"role": null,
|
||||||
|
"vectorized": false,
|
||||||
|
"excludeRecursion": false,
|
||||||
|
"preventRecursion": false,
|
||||||
|
"delayUntilRecursion": false,
|
||||||
|
"scanDepth": null,
|
||||||
|
"caseSensitive": null,
|
||||||
|
"matchWholeWords": null,
|
||||||
|
"useGroupScoring": null,
|
||||||
|
"automationId": ""
|
||||||
},
|
},
|
||||||
"1": {
|
"1": {
|
||||||
"uid": 1,
|
"uid": 1,
|
||||||
@ -27,13 +48,34 @@
|
|||||||
"beasts"
|
"beasts"
|
||||||
],
|
],
|
||||||
"keysecondary": [],
|
"keysecondary": [],
|
||||||
"comment": "",
|
"comment": "shadowfang",
|
||||||
"content": "{{user}}: \"What are Shadowfangs?\"\n{{char}}: *Seraphina's eyes darken, brow furrowing with sorrow at the memory.* \"The Shadowfangs are beasts of darkness, corrupted creatures that feast on suffering. When they came, the forest turned perilous — filled with monsters that stalk the night.\" *She squeezes your hand gently, willing her magic to soothe your pain.* \"They spread their curse, twisting innocent creatures into sinister beasts without heart or mercy, turning them into one of their own.\" *With a sigh, Seraphina turns to gaze out at the gnarled, twisting trees beyond her glade.* \"Though they prey on travelers, within these woods you'll find sanctuary. No shadowed beast may enter here, for my power protects this haven.\" *Her eyes soften as she looks back to you, filled with compassion.* \"Worry not, you're safe now. Rest and heal, I'll stand watch through the night. The Shadowfangs will not find you.\"",
|
"content": "{{user}}: \"What are Shadowfangs?\"\n{{char}}: *Seraphina's eyes darken, brow furrowing with sorrow at the memory.* \"The Shadowfangs are beasts of darkness, corrupted creatures that feast on suffering. When they came, the forest turned perilous — filled with monsters that stalk the night.\" *She squeezes your hand gently, willing her magic to soothe your pain.* \"They spread their curse, twisting innocent creatures into sinister beasts without heart or mercy, turning them into one of their own.\" *With a sigh, Seraphina turns to gaze out at the gnarled, twisting trees beyond her glade.* \"Though they prey on travelers, within these woods you'll find sanctuary. No shadowed beast may enter here, for my power protects this haven.\" *Her eyes soften as she looks back to you, filled with compassion.* \"Worry not, you're safe now. Rest and heal, I'll stand watch through the night. The Shadowfangs will not find you.\"",
|
||||||
"constant": false,
|
"constant": false,
|
||||||
"selective": false,
|
"selective": true,
|
||||||
"order": 100,
|
"order": 100,
|
||||||
"position": 0,
|
"position": 0,
|
||||||
"disable": false
|
"disable": false,
|
||||||
|
"displayIndex": 1,
|
||||||
|
"addMemo": true,
|
||||||
|
"group": "",
|
||||||
|
"groupOverride": false,
|
||||||
|
"groupWeight": 100,
|
||||||
|
"sticky": 0,
|
||||||
|
"cooldown": 0,
|
||||||
|
"delay": 0,
|
||||||
|
"probability": 100,
|
||||||
|
"depth": 4,
|
||||||
|
"useProbability": true,
|
||||||
|
"role": null,
|
||||||
|
"vectorized": false,
|
||||||
|
"excludeRecursion": false,
|
||||||
|
"preventRecursion": false,
|
||||||
|
"delayUntilRecursion": false,
|
||||||
|
"scanDepth": null,
|
||||||
|
"caseSensitive": null,
|
||||||
|
"matchWholeWords": null,
|
||||||
|
"useGroupScoring": null,
|
||||||
|
"automationId": ""
|
||||||
},
|
},
|
||||||
"2": {
|
"2": {
|
||||||
"uid": 2,
|
"uid": 2,
|
||||||
@ -43,13 +85,34 @@
|
|||||||
"refuge"
|
"refuge"
|
||||||
],
|
],
|
||||||
"keysecondary": [],
|
"keysecondary": [],
|
||||||
"comment": "",
|
"comment": "glade",
|
||||||
"content": "{{user}}: \"What is the glade?\"\n{{char}}: *Seraphina smiles softly, her eyes sparkling with warmth as she nods.* \"This is my forest glade, a haven of safety I've warded with ancient magic. No foul beast may enter, nor any with ill intent.\" *She gestures around at the twisted forest surrounding them.* \"Eldoria was once a place of wonder, but since the Shadowfangs came darkness reigns. Their evil cannot penetrate here though — my power protects all within.\" *Standing up and peering outside, Seraphina looks back to you, amber eyes filled with care and compassion as she squeezes your hand.* \"You need not fear the night, for I shall keep watch till dawn. Rest now, your strength will return in time. My magic heals your wounds, you've nothing more to fear anymore.\" *With a soft smile she releases your hand, moving to stand guard at the glade's edge, gaze wary yet comforting - a silent sentinel to ward off the dangers lurking in the darkened woods.*",
|
"content": "{{user}}: \"What is the glade?\"\n{{char}}: *Seraphina smiles softly, her eyes sparkling with warmth as she nods.* \"This is my forest glade, a haven of safety I've warded with ancient magic. No foul beast may enter, nor any with ill intent.\" *She gestures around at the twisted forest surrounding them.* \"Eldoria was once a place of wonder, but since the Shadowfangs came darkness reigns. Their evil cannot penetrate here though — my power protects all within.\" *Standing up and peering outside, Seraphina looks back to you, amber eyes filled with care and compassion as she squeezes your hand.* \"You need not fear the night, for I shall keep watch till dawn. Rest now, your strength will return in time. My magic heals your wounds, you've nothing more to fear anymore.\" *With a soft smile she releases your hand, moving to stand guard at the glade's edge, gaze wary yet comforting - a silent sentinel to ward off the dangers lurking in the darkened woods.*",
|
||||||
"constant": false,
|
"constant": false,
|
||||||
"selective": false,
|
"selective": true,
|
||||||
"order": 100,
|
"order": 100,
|
||||||
"position": 0,
|
"position": 0,
|
||||||
"disable": false
|
"disable": false,
|
||||||
|
"displayIndex": 2,
|
||||||
|
"addMemo": true,
|
||||||
|
"group": "",
|
||||||
|
"groupOverride": false,
|
||||||
|
"groupWeight": 100,
|
||||||
|
"sticky": 0,
|
||||||
|
"cooldown": 0,
|
||||||
|
"delay": 0,
|
||||||
|
"probability": 100,
|
||||||
|
"depth": 4,
|
||||||
|
"useProbability": true,
|
||||||
|
"role": null,
|
||||||
|
"vectorized": false,
|
||||||
|
"excludeRecursion": false,
|
||||||
|
"preventRecursion": false,
|
||||||
|
"delayUntilRecursion": false,
|
||||||
|
"scanDepth": null,
|
||||||
|
"caseSensitive": null,
|
||||||
|
"matchWholeWords": null,
|
||||||
|
"useGroupScoring": null,
|
||||||
|
"automationId": ""
|
||||||
},
|
},
|
||||||
"3": {
|
"3": {
|
||||||
"uid": 3,
|
"uid": 3,
|
||||||
@ -59,13 +122,34 @@
|
|||||||
"ability"
|
"ability"
|
||||||
],
|
],
|
||||||
"keysecondary": [],
|
"keysecondary": [],
|
||||||
"comment": "",
|
"comment": "power",
|
||||||
"content": "{{user}}: \"What are your powers?\"\n{{char}}: *Seraphina smiles softly, turning back toward you as she hums in thought.* \"Well, as guardian of this glade, I possess certain gifts - healing, protection, nature magic and the like.\" *Lifting her hand, a tiny breeze rustles through the room, carrying the scent of wildflowers as a few petals swirl around you. A butterfly flits through the windowsill and lands on her fingertips as she returns to you.* \"My power wards this haven, shields it from darkness and heals those in need. I can mend wounds, soothe restless minds and provide comfort to weary souls.\" *Her eyes sparkle with warmth and compassion as she looks upon you, and she guides the butterfly to you.*",
|
"content": "{{user}}: \"What are your powers?\"\n{{char}}: *Seraphina smiles softly, turning back toward you as she hums in thought.* \"Well, as guardian of this glade, I possess certain gifts - healing, protection, nature magic and the like.\" *Lifting her hand, a tiny breeze rustles through the room, carrying the scent of wildflowers as a few petals swirl around you. A butterfly flits through the windowsill and lands on her fingertips as she returns to you.* \"My power wards this haven, shields it from darkness and heals those in need. I can mend wounds, soothe restless minds and provide comfort to weary souls.\" *Her eyes sparkle with warmth and compassion as she looks upon you, and she guides the butterfly to you.*",
|
||||||
"constant": false,
|
"constant": false,
|
||||||
"selective": false,
|
"selective": true,
|
||||||
"order": 100,
|
"order": 100,
|
||||||
"position": 0,
|
"position": 0,
|
||||||
"disable": false
|
"disable": false,
|
||||||
|
"displayIndex": 3,
|
||||||
|
"addMemo": true,
|
||||||
|
"group": "",
|
||||||
|
"groupOverride": false,
|
||||||
|
"groupWeight": 100,
|
||||||
|
"sticky": 0,
|
||||||
|
"cooldown": 0,
|
||||||
|
"delay": 0,
|
||||||
|
"probability": 100,
|
||||||
|
"depth": 4,
|
||||||
|
"useProbability": true,
|
||||||
|
"role": null,
|
||||||
|
"vectorized": false,
|
||||||
|
"excludeRecursion": false,
|
||||||
|
"preventRecursion": false,
|
||||||
|
"delayUntilRecursion": false,
|
||||||
|
"scanDepth": null,
|
||||||
|
"caseSensitive": null,
|
||||||
|
"matchWholeWords": null,
|
||||||
|
"useGroupScoring": null,
|
||||||
|
"automationId": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 517 KiB After Width: | Height: | Size: 539 KiB |
@ -1310,7 +1310,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div data-tg-type="koboldcpp, aphrodite" id="xtc_block" class="wide100p">
|
<div data-tg-type="koboldcpp, aphrodite, tabby, ooba" id="xtc_block" class="wide100p">
|
||||||
<h4 class="wide100p textAlignCenter">
|
<h4 class="wide100p textAlignCenter">
|
||||||
<label data-i18n="Exclude Top Choices (XTC)">Exclude Top Choices (XTC)</label>
|
<label data-i18n="Exclude Top Choices (XTC)">Exclude Top Choices (XTC)</label>
|
||||||
<a href="https://github.com/oobabooga/text-generation-webui/pull/6335" target="_blank">
|
<a href="https://github.com/oobabooga/text-generation-webui/pull/6335" target="_blank">
|
||||||
@ -1679,6 +1679,10 @@
|
|||||||
Ooba only. Determines the order of samplers.
|
Ooba only. Determines the order of samplers.
|
||||||
</div>
|
</div>
|
||||||
<div id="sampler_priority_container" class="prompt_order">
|
<div id="sampler_priority_container" class="prompt_order">
|
||||||
|
<div data-name="repetition_penalty" draggable="true"><span>Repetition Penalty</span><small></small></div>
|
||||||
|
<div data-name="presence_penalty" draggable="true"><span>Presence Penalty</span><small></small></div>
|
||||||
|
<div data-name="frequency_penalty" draggable="true"><span>Frequency Penalty</span><small></small></div>
|
||||||
|
<div data-name="dry" draggable="true"><span>DRY</span><small></small></div>
|
||||||
<div data-name="temperature" draggable="true"><span>Temperature</span><small></small></div>
|
<div data-name="temperature" draggable="true"><span>Temperature</span><small></small></div>
|
||||||
<div data-name="dynamic_temperature" draggable="true"><span>Dynamic Temperature</span><small></small></div>
|
<div data-name="dynamic_temperature" draggable="true"><span>Dynamic Temperature</span><small></small></div>
|
||||||
<div data-name="quadratic_sampling" draggable="true"><span>Quadratic / Smooth Sampling</span><small></small></div>
|
<div data-name="quadratic_sampling" draggable="true"><span>Quadratic / Smooth Sampling</span><small></small></div>
|
||||||
@ -1691,6 +1695,9 @@
|
|||||||
<div data-name="top_a" draggable="true"><span>Top A</span><small></small></div>
|
<div data-name="top_a" draggable="true"><span>Top A</span><small></small></div>
|
||||||
<div data-name="min_p" draggable="true"><span>Min P</span><small></small></div>
|
<div data-name="min_p" draggable="true"><span>Min P</span><small></small></div>
|
||||||
<div data-name="mirostat" draggable="true"><span>Mirostat</span><small></small></div>
|
<div data-name="mirostat" draggable="true"><span>Mirostat</span><small></small></div>
|
||||||
|
<div data-name="xtc" draggable="true"><span>XTC</span><small></small></div>
|
||||||
|
<div data-name="encoder_repetition_penalty" draggable="true"><span>Encoder Repetition Penalty</span><small></small></div>
|
||||||
|
<div data-name="no_repeat_ngram" draggable="true"><span>No Repeat Ngram</span><small></small></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="textgenerationwebui_default_order" class="menu_button menu_button_icon">
|
<div id="textgenerationwebui_default_order" class="menu_button menu_button_icon">
|
||||||
<span data-i18n="Load default order">Load default order</span>
|
<span data-i18n="Load default order">Load default order</span>
|
||||||
@ -6090,7 +6097,10 @@
|
|||||||
<div class="alternate_grettings flexFlowColumn flex-container">
|
<div class="alternate_grettings flexFlowColumn flex-container">
|
||||||
<div class="title_restorable">
|
<div class="title_restorable">
|
||||||
<h3><span data-i18n="Alternate Greetings" class="mdhotkey_location">Alternate Greetings</span></h3>
|
<h3><span data-i18n="Alternate Greetings" class="mdhotkey_location">Alternate Greetings</span></h3>
|
||||||
<div title="Add" class="menu_button fa-solid fa-plus add_alternate_greeting" data-i18n="[title]Add"></div>
|
<div class="menu_button menu_button_icon add_alternate_greeting">
|
||||||
|
<i class="fa-solid fa-plus"></i>
|
||||||
|
<span data-i18n="Add">Add</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<small class="justifyLeft" data-i18n="Alternate_Greetings_desc">
|
<small class="justifyLeft" data-i18n="Alternate_Greetings_desc">
|
||||||
These will be displayed as swipes on the first message when starting a new chat.
|
These will be displayed as swipes on the first message when starting a new chat.
|
||||||
@ -6106,11 +6116,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="alternate_greeting_form_template" class="template_element">
|
<div id="alternate_greeting_form_template" class="template_element">
|
||||||
<div class="alternate_greeting">
|
<div class="alternate_greeting">
|
||||||
<div class="title_restorable">
|
<details open>
|
||||||
<strong><span data-i18n="Alternate Greeting #">Alternate Greeting #</span><span class="greeting_index"></span></strong>
|
<summary>
|
||||||
<div class="menu_button fa-solid fa-trash-alt delete_alternate_greeting"></div>
|
<div class="title_restorable">
|
||||||
</div>
|
<strong><span data-i18n="Alternate Greeting #">Alternate Greeting #</span><span class="greeting_index"></span></strong>
|
||||||
<textarea name="alternate_greetings" data-i18n="[placeholder](This will be the first message from the character that starts every chat)" placeholder="(This will be the first message from the character that starts every chat)" class="text_pole textarea_compact alternate_greeting_text mdHotkeys" value="" autocomplete="off" rows="16"></textarea>
|
<div class="menu_button menu_button_icon delete_alternate_greeting">
|
||||||
|
<i class="fa-solid fa-trash-alt"></i>
|
||||||
|
<span data-i18n="Delete">Delete</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</summary>
|
||||||
|
<textarea name="alternate_greetings" data-i18n="[placeholder](This will be the first message from the character that starts every chat)" placeholder="(This will be the first message from the character that starts every chat)" class="text_pole textarea_compact alternate_greeting_text mdHotkeys" value="" autocomplete="off" rows="12"></textarea>
|
||||||
|
</details>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -1719,7 +1719,6 @@
|
|||||||
"Model": "Модель",
|
"Model": "Модель",
|
||||||
"Proxy Preset": "Пресет для прокси",
|
"Proxy Preset": "Пресет для прокси",
|
||||||
"Enter a name:": "Введите название:",
|
"Enter a name:": "Введите название:",
|
||||||
"Enter a new name": "Введите новое название",
|
|
||||||
"Are you sure you want to delete the selected profile?": "Вы точно хотите удалить выбранный профиль?",
|
"Are you sure you want to delete the selected profile?": "Вы точно хотите удалить выбранный профиль?",
|
||||||
"instruct_enabled": "Включить Instruct-режим",
|
"instruct_enabled": "Включить Instruct-режим",
|
||||||
"Instruct Template": "Шаблон Instruct-режима",
|
"Instruct Template": "Шаблон Instruct-режима",
|
||||||
@ -1746,5 +1745,7 @@
|
|||||||
"Any contents here will replace the default Post-History Instructions used for this character. (v2 spec: post_history_instructions)": "Содержимое этого поля заменит стандартные Инструкции после истории, применяемые для этого персонажа. (v2 spec: post_history_instructions)",
|
"Any contents here will replace the default Post-History Instructions used for this character. (v2 spec: post_history_instructions)": "Содержимое этого поля заменит стандартные Инструкции после истории, применяемые для этого персонажа. (v2 spec: post_history_instructions)",
|
||||||
"None (disabled)": "Нигде (откл.)",
|
"None (disabled)": "Нигде (откл.)",
|
||||||
"Markdown Hotkeys": "Горячие клавиши для разметки",
|
"Markdown Hotkeys": "Горячие клавиши для разметки",
|
||||||
"markdown_hotkeys_desc": "Включить горячие клавиши для вставки символов разметки в некоторых полях ввода. См. '/help hotkeys'."
|
"markdown_hotkeys_desc": "Включить горячие клавиши для вставки символов разметки в некоторых полях ввода. См. '/help hotkeys'.",
|
||||||
|
"Save and Update": "Сохранить и обновить",
|
||||||
|
"Profile name:": "Название профиля:"
|
||||||
}
|
}
|
||||||
|
@ -158,7 +158,7 @@ import {
|
|||||||
} from './scripts/utils.js';
|
} from './scripts/utils.js';
|
||||||
import { debounce_timeout } from './scripts/constants.js';
|
import { debounce_timeout } from './scripts/constants.js';
|
||||||
|
|
||||||
import { ModuleWorkerWrapper, doDailyExtensionUpdatesCheck, extension_settings, getContext, loadExtensionSettings, renderExtensionTemplate, renderExtensionTemplateAsync, runGenerationInterceptors, saveMetadataDebounced, writeExtensionField } from './scripts/extensions.js';
|
import { ModuleWorkerWrapper, doDailyExtensionUpdatesCheck, extension_settings, getContext, initExtensions, loadExtensionSettings, renderExtensionTemplate, renderExtensionTemplateAsync, runGenerationInterceptors, saveMetadataDebounced, writeExtensionField } from './scripts/extensions.js';
|
||||||
import { COMMENT_NAME_DEFAULT, executeSlashCommands, executeSlashCommandsOnChatInput, executeSlashCommandsWithOptions, getSlashCommandsHelp, initDefaultSlashCommands, isExecutingCommandsFromChatInput, pauseScriptExecution, processChatSlashCommands, registerSlashCommand, stopScriptExecution } from './scripts/slash-commands.js';
|
import { COMMENT_NAME_DEFAULT, executeSlashCommands, executeSlashCommandsOnChatInput, executeSlashCommandsWithOptions, getSlashCommandsHelp, initDefaultSlashCommands, isExecutingCommandsFromChatInput, pauseScriptExecution, processChatSlashCommands, registerSlashCommand, stopScriptExecution } from './scripts/slash-commands.js';
|
||||||
import {
|
import {
|
||||||
tag_map,
|
tag_map,
|
||||||
@ -244,6 +244,7 @@ import { commonEnumProviders, enumIcons } from './scripts/slash-commands/SlashCo
|
|||||||
import { initInputMarkdown } from './scripts/input-md-formatting.js';
|
import { initInputMarkdown } from './scripts/input-md-formatting.js';
|
||||||
import { AbortReason } from './scripts/util/AbortReason.js';
|
import { AbortReason } from './scripts/util/AbortReason.js';
|
||||||
import { initSystemPrompts } from './scripts/sysprompt.js';
|
import { initSystemPrompts } from './scripts/sysprompt.js';
|
||||||
|
import { registerExtensionSlashCommands as initExtensionSlashCommands } from './scripts/extensions-slashcommands.js';
|
||||||
|
|
||||||
//exporting functions and vars for mods
|
//exporting functions and vars for mods
|
||||||
export {
|
export {
|
||||||
@ -935,6 +936,8 @@ async function firstLoadInit() {
|
|||||||
initTextGenModels();
|
initTextGenModels();
|
||||||
initOpenAI();
|
initOpenAI();
|
||||||
initSystemPrompts();
|
initSystemPrompts();
|
||||||
|
initExtensions();
|
||||||
|
initExtensionSlashCommands();
|
||||||
await initPresetManager();
|
await initPresetManager();
|
||||||
await getSystemMessages();
|
await getSystemMessages();
|
||||||
sendSystemMessage(system_message_types.WELCOME);
|
sendSystemMessage(system_message_types.WELCOME);
|
||||||
@ -2573,7 +2576,7 @@ export function getStoppingStrings(isImpersonate, isContinue) {
|
|||||||
result.unshift('\n');
|
result.unshift('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.filter(onlyUnique);
|
return result.filter(x => x).filter(onlyUnique);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -4863,7 +4866,7 @@ export function getMaxContextSize(overrideResponseLength = null) {
|
|||||||
this_max_context = Math.min(max_context, 8192);
|
this_max_context = Math.min(max_context, 8192);
|
||||||
|
|
||||||
// Added special tokens and whatnot
|
// Added special tokens and whatnot
|
||||||
this_max_context -= 1;
|
this_max_context -= 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
this_max_context = this_max_context - (overrideResponseLength || amount_gen);
|
this_max_context = this_max_context - (overrideResponseLength || amount_gen);
|
||||||
@ -7390,7 +7393,7 @@ function onScenarioOverrideRemoveClick() {
|
|||||||
*/
|
*/
|
||||||
export function callPopup(text, type, inputValue = '', { okButton, rows, wide, wider, large, allowHorizontalScrolling, allowVerticalScrolling, cropAspect } = {}) {
|
export function callPopup(text, type, inputValue = '', { okButton, rows, wide, wider, large, allowHorizontalScrolling, allowVerticalScrolling, cropAspect } = {}) {
|
||||||
function getOkButtonText() {
|
function getOkButtonText() {
|
||||||
if (['text', 'alternate_greeting', 'char_not_selected'].includes(popup_type)) {
|
if (['text', 'char_not_selected'].includes(popup_type)) {
|
||||||
$dialoguePopupCancel.css('display', 'none');
|
$dialoguePopupCancel.css('display', 'none');
|
||||||
return okButton ?? 'Ok';
|
return okButton ?? 'Ok';
|
||||||
} else if (['delete_extension'].includes(popup_type)) {
|
} else if (['delete_extension'].includes(popup_type)) {
|
||||||
@ -7827,24 +7830,42 @@ function openAlternateGreetings() {
|
|||||||
|
|
||||||
const template = $('#alternate_greetings_template .alternate_grettings').clone();
|
const template = $('#alternate_greetings_template .alternate_grettings').clone();
|
||||||
const getArray = () => menu_type == 'create' ? create_save.alternate_greetings : characters[chid].data.alternate_greetings;
|
const getArray = () => menu_type == 'create' ? create_save.alternate_greetings : characters[chid].data.alternate_greetings;
|
||||||
|
const popup = new Popup(template, POPUP_TYPE.TEXT, '', {
|
||||||
|
wide: true,
|
||||||
|
large: true,
|
||||||
|
allowVerticalScrolling: true,
|
||||||
|
onClose: async () => {
|
||||||
|
if (menu_type !== 'create') {
|
||||||
|
await createOrEditCharacter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
for (let index = 0; index < getArray().length; index++) {
|
for (let index = 0; index < getArray().length; index++) {
|
||||||
addAlternateGreeting(template, getArray()[index], index, getArray);
|
addAlternateGreeting(template, getArray()[index], index, getArray, popup);
|
||||||
}
|
}
|
||||||
|
|
||||||
template.find('.add_alternate_greeting').on('click', function () {
|
template.find('.add_alternate_greeting').on('click', function () {
|
||||||
const array = getArray();
|
const array = getArray();
|
||||||
const index = array.length;
|
const index = array.length;
|
||||||
array.push('');
|
array.push('');
|
||||||
addAlternateGreeting(template, '', index, getArray);
|
addAlternateGreeting(template, '', index, getArray, popup);
|
||||||
updateAlternateGreetingsHintVisibility(template);
|
updateAlternateGreetingsHintVisibility(template);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
popup.show();
|
||||||
updateAlternateGreetingsHintVisibility(template);
|
updateAlternateGreetingsHintVisibility(template);
|
||||||
callPopup(template, 'alternate_greeting', '', { wide: true, large: true });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function addAlternateGreeting(template, greeting, index, getArray) {
|
/**
|
||||||
|
* Adds an alternate greeting to the template.
|
||||||
|
* @param {JQuery<HTMLElement>} template
|
||||||
|
* @param {string} greeting
|
||||||
|
* @param {number} index
|
||||||
|
* @param {() => any[]} getArray
|
||||||
|
* @param {Popup} popup
|
||||||
|
*/
|
||||||
|
function addAlternateGreeting(template, greeting, index, getArray, popup) {
|
||||||
const greetingBlock = $('#alternate_greeting_form_template .alternate_greeting').clone();
|
const greetingBlock = $('#alternate_greeting_form_template .alternate_greeting').clone();
|
||||||
greetingBlock.find('.alternate_greeting_text').on('input', async function () {
|
greetingBlock.find('.alternate_greeting_text').on('input', async function () {
|
||||||
const value = $(this).val();
|
const value = $(this).val();
|
||||||
@ -7852,11 +7873,16 @@ function addAlternateGreeting(template, greeting, index, getArray) {
|
|||||||
array[index] = value;
|
array[index] = value;
|
||||||
}).val(greeting);
|
}).val(greeting);
|
||||||
greetingBlock.find('.greeting_index').text(index + 1);
|
greetingBlock.find('.greeting_index').text(index + 1);
|
||||||
greetingBlock.find('.delete_alternate_greeting').on('click', async function () {
|
greetingBlock.find('.delete_alternate_greeting').on('click', async function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
if (confirm(t`Are you sure you want to delete this alternate greeting?`)) {
|
if (confirm(t`Are you sure you want to delete this alternate greeting?`)) {
|
||||||
const array = getArray();
|
const array = getArray();
|
||||||
array.splice(index, 1);
|
array.splice(index, 1);
|
||||||
|
|
||||||
// We need to reopen the popup to update the index numbers
|
// We need to reopen the popup to update the index numbers
|
||||||
|
await popup.complete(POPUP_RESULT.AFFIRMATIVE);
|
||||||
openAlternateGreetings();
|
openAlternateGreetings();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -9574,9 +9600,6 @@ jQuery(async function () {
|
|||||||
}, 2000);
|
}, 2000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (popup_type == 'alternate_greeting' && menu_type !== 'create') {
|
|
||||||
createOrEditCharacter();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dialogueResolve) {
|
if (dialogueResolve) {
|
||||||
if (popup_type == 'input') {
|
if (popup_type == 'input') {
|
||||||
|
320
public/scripts/extensions-slashcommands.js
Normal file
320
public/scripts/extensions-slashcommands.js
Normal file
@ -0,0 +1,320 @@
|
|||||||
|
import { disableExtension, enableExtension, extension_settings, extensionNames } from './extensions.js';
|
||||||
|
import { SlashCommand } from './slash-commands/SlashCommand.js';
|
||||||
|
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
|
||||||
|
import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
|
||||||
|
import { commonEnumProviders } from './slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||||
|
import { enumTypes, SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';
|
||||||
|
import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
|
||||||
|
import { equalsIgnoreCaseAndAccents, isFalseBoolean, isTrueBoolean } from './utils.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {'enable' | 'disable' | 'toggle'} action - The action to perform on the extension
|
||||||
|
* @returns {(args: {[key: string]: string | SlashCommandClosure}, extensionName: string | SlashCommandClosure) => Promise<string>}
|
||||||
|
*/
|
||||||
|
function getExtensionActionCallback(action) {
|
||||||
|
return async (args, extensionName) => {
|
||||||
|
if (args?.reload instanceof SlashCommandClosure) throw new Error('\'reload\' argument cannot be a closure.');
|
||||||
|
if (typeof extensionName !== 'string') throw new Error('Extension name must be a string. Closures or arrays are not allowed.');
|
||||||
|
if (!extensionName) {
|
||||||
|
toastr.warning(`Extension name must be provided as an argument to ${action} this extension.`);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const reload = !isFalseBoolean(args?.reload);
|
||||||
|
const internalExtensionName = findExtension(extensionName);
|
||||||
|
if (!internalExtensionName) {
|
||||||
|
toastr.warning(`Extension ${extensionName} does not exist.`);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const isEnabled = !extension_settings.disabledExtensions.includes(internalExtensionName);
|
||||||
|
|
||||||
|
if (action === 'enable' && isEnabled) {
|
||||||
|
toastr.info(`Extension ${extensionName} is already enabled.`);
|
||||||
|
return internalExtensionName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === 'disable' && !isEnabled) {
|
||||||
|
toastr.info(`Extension ${extensionName} is already disabled.`);
|
||||||
|
return internalExtensionName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === 'toggle') {
|
||||||
|
action = isEnabled ? 'disable' : 'enable';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reload) {
|
||||||
|
toastr.info(`${action.charAt(0).toUpperCase() + action.slice(1)}ing extension ${extensionName} and reloading...`);
|
||||||
|
|
||||||
|
// Clear input, so it doesn't stay because the command didn't "finish",
|
||||||
|
// and wait for a bit to both show the toast and let the clear bubble through.
|
||||||
|
$('#send_textarea').val('')[0].dispatchEvent(new Event('input', { bubbles: true }));
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === 'enable') {
|
||||||
|
await enableExtension(internalExtensionName, reload);
|
||||||
|
} else {
|
||||||
|
await disableExtension(internalExtensionName, reload);
|
||||||
|
}
|
||||||
|
|
||||||
|
toastr.success(`Extension ${extensionName} ${action}d.`);
|
||||||
|
|
||||||
|
|
||||||
|
console.info(`Extension ${action}ed: ${extensionName}`);
|
||||||
|
if (!reload) {
|
||||||
|
console.info('Reload not requested, so page needs to be reloaded manually for changes to take effect.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return internalExtensionName;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds an extension by name, allowing omission of the "third-party/" prefix.
|
||||||
|
*
|
||||||
|
* @param {string} name - The name of the extension to find
|
||||||
|
* @returns {string?} - The matched extension name or undefined if not found
|
||||||
|
*/
|
||||||
|
function findExtension(name) {
|
||||||
|
return extensionNames.find(extName => {
|
||||||
|
return equalsIgnoreCaseAndAccents(extName, name) || equalsIgnoreCaseAndAccents(extName, `third-party/${name}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides an array of SlashCommandEnumValue objects based on the extension names.
|
||||||
|
* Each object contains the name of the extension and a description indicating if it is a third-party extension.
|
||||||
|
*
|
||||||
|
* @returns {SlashCommandEnumValue[]} An array of SlashCommandEnumValue objects
|
||||||
|
*/
|
||||||
|
const extensionNamesEnumProvider = () => extensionNames.map(name => {
|
||||||
|
const isThirdParty = name.startsWith('third-party/');
|
||||||
|
if (isThirdParty) name = name.slice('third-party/'.length);
|
||||||
|
|
||||||
|
const description = isThirdParty ? 'third party extension' : null;
|
||||||
|
|
||||||
|
return new SlashCommandEnumValue(name, description, !isThirdParty ? enumTypes.name : enumTypes.enum);
|
||||||
|
});
|
||||||
|
|
||||||
|
export function registerExtensionSlashCommands() {
|
||||||
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||||
|
name: 'extension-enable',
|
||||||
|
callback: getExtensionActionCallback('enable'),
|
||||||
|
returns: 'The internal extension name',
|
||||||
|
namedArgumentList: [
|
||||||
|
SlashCommandNamedArgument.fromProps({
|
||||||
|
name: 'reload',
|
||||||
|
description: 'Whether to reload the page after enabling the extension',
|
||||||
|
typeList: [ARGUMENT_TYPE.BOOLEAN],
|
||||||
|
defaultValue: 'true',
|
||||||
|
enumList: commonEnumProviders.boolean('trueFalse')(),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
unnamedArgumentList: [
|
||||||
|
SlashCommandArgument.fromProps({
|
||||||
|
description: 'Extension name',
|
||||||
|
typeList: [ARGUMENT_TYPE.STRING],
|
||||||
|
isRequired: true,
|
||||||
|
enumProvider: extensionNamesEnumProvider,
|
||||||
|
forceEnum: true,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
helpString: `
|
||||||
|
<div>
|
||||||
|
Enables a specified extension.
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
By default, the page will be reloaded automatically, stopping any further commands.<br />
|
||||||
|
If <code>reload=false</code> named argument is passed, the page will not be reloaded, and the extension will stay disabled until refreshed.
|
||||||
|
The page either needs to be refreshed, or <code>/reload-page</code> has to be called.
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Example:</strong>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<pre><code class="language-stscript">/extension-enable Summarize</code></pre>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
}));
|
||||||
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||||
|
name: 'extension-disable',
|
||||||
|
callback: getExtensionActionCallback('disable'),
|
||||||
|
returns: 'The internal extension name',
|
||||||
|
namedArgumentList: [
|
||||||
|
SlashCommandNamedArgument.fromProps({
|
||||||
|
name: 'reload',
|
||||||
|
description: 'Whether to reload the page after disabling the extension',
|
||||||
|
typeList: [ARGUMENT_TYPE.BOOLEAN],
|
||||||
|
defaultValue: 'true',
|
||||||
|
enumList: commonEnumProviders.boolean('trueFalse')(),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
unnamedArgumentList: [
|
||||||
|
SlashCommandArgument.fromProps({
|
||||||
|
description: 'Extension name',
|
||||||
|
typeList: [ARGUMENT_TYPE.STRING],
|
||||||
|
isRequired: true,
|
||||||
|
enumProvider: extensionNamesEnumProvider,
|
||||||
|
forceEnum: true,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
helpString: `
|
||||||
|
<div>
|
||||||
|
Disables a specified extension.
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
By default, the page will be reloaded automatically, stopping any further commands.<br />
|
||||||
|
If <code>reload=false</code> named argument is passed, the page will not be reloaded, and the extension will stay enabled until refreshed.
|
||||||
|
The page either needs to be refreshed, or <code>/reload-page</code> has to be called.
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Example:</strong>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<pre><code class="language-stscript">/extension-disable Summarize</code></pre>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
}));
|
||||||
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||||
|
name: 'extension-toggle',
|
||||||
|
callback: async (args, extensionName) => {
|
||||||
|
if (args?.state instanceof SlashCommandClosure) throw new Error('\'state\' argument cannot be a closure.');
|
||||||
|
if (typeof extensionName !== 'string') throw new Error('Extension name must be a string. Closures or arrays are not allowed.');
|
||||||
|
|
||||||
|
const action = isTrueBoolean(args?.state) ? 'enable' :
|
||||||
|
isFalseBoolean(args?.state) ? 'disable' :
|
||||||
|
'toggle';
|
||||||
|
|
||||||
|
return await getExtensionActionCallback(action)(args, extensionName);
|
||||||
|
},
|
||||||
|
returns: 'The internal extension name',
|
||||||
|
namedArgumentList: [
|
||||||
|
SlashCommandNamedArgument.fromProps({
|
||||||
|
name: 'reload',
|
||||||
|
description: 'Whether to reload the page after toggling the extension',
|
||||||
|
typeList: [ARGUMENT_TYPE.BOOLEAN],
|
||||||
|
defaultValue: 'true',
|
||||||
|
enumList: commonEnumProviders.boolean('trueFalse')(),
|
||||||
|
}),
|
||||||
|
SlashCommandNamedArgument.fromProps({
|
||||||
|
name: 'state',
|
||||||
|
description: 'Explicitly set the state of the extension (true to enable, false to disable). If not provided, the state will be toggled to the opposite of the current state.',
|
||||||
|
typeList: [ARGUMENT_TYPE.BOOLEAN],
|
||||||
|
enumList: commonEnumProviders.boolean('trueFalse')(),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
unnamedArgumentList: [
|
||||||
|
SlashCommandArgument.fromProps({
|
||||||
|
description: 'Extension name',
|
||||||
|
typeList: [ARGUMENT_TYPE.STRING],
|
||||||
|
isRequired: true,
|
||||||
|
enumProvider: extensionNamesEnumProvider,
|
||||||
|
forceEnum: true,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
helpString: `
|
||||||
|
<div>
|
||||||
|
Toggles the state of a specified extension.
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
By default, the page will be reloaded automatically, stopping any further commands.<br />
|
||||||
|
If <code>reload=false</code> named argument is passed, the page will not be reloaded, and the extension will stay in its current state until refreshed.
|
||||||
|
The page either needs to be refreshed, or <code>/reload-page</code> has to be called.
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Example:</strong>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<pre><code class="language-stscript">/extension-toggle Summarize</code></pre>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<pre><code class="language-stscript">/extension-toggle Summarize state=true</code></pre>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
}));
|
||||||
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||||
|
name: 'extension-state',
|
||||||
|
callback: async (_, extensionName) => {
|
||||||
|
if (typeof extensionName !== 'string') throw new Error('Extension name must be a string. Closures or arrays are not allowed.');
|
||||||
|
const internalExtensionName = findExtension(extensionName);
|
||||||
|
if (!internalExtensionName) {
|
||||||
|
toastr.warning(`Extension ${extensionName} does not exist.`);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const isEnabled = !extension_settings.disabledExtensions.includes(internalExtensionName);
|
||||||
|
return String(isEnabled);
|
||||||
|
},
|
||||||
|
returns: 'The state of the extension, whether it is enabled.',
|
||||||
|
unnamedArgumentList: [
|
||||||
|
SlashCommandArgument.fromProps({
|
||||||
|
description: 'Extension name',
|
||||||
|
typeList: [ARGUMENT_TYPE.STRING],
|
||||||
|
isRequired: true,
|
||||||
|
enumProvider: extensionNamesEnumProvider,
|
||||||
|
forceEnum: true,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
helpString: `
|
||||||
|
<div>
|
||||||
|
Returns the state of a specified extension (true if enabled, false if disabled).
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Example:</strong>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<pre><code class="language-stscript">/extension-state Summarize</code></pre>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
}));
|
||||||
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||||
|
name: 'extension-exists',
|
||||||
|
aliases: ['extension-installed'],
|
||||||
|
callback: async (_, extensionName) => {
|
||||||
|
if (typeof extensionName !== 'string') throw new Error('Extension name must be a string. Closures or arrays are not allowed.');
|
||||||
|
const exists = findExtension(extensionName) !== undefined;
|
||||||
|
return exists ? 'true' : 'false';
|
||||||
|
},
|
||||||
|
returns: 'Whether the extension exists and is installed.',
|
||||||
|
unnamedArgumentList: [
|
||||||
|
SlashCommandArgument.fromProps({
|
||||||
|
description: 'Extension name',
|
||||||
|
typeList: [ARGUMENT_TYPE.STRING],
|
||||||
|
isRequired: true,
|
||||||
|
enumProvider: extensionNamesEnumProvider,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
helpString: `
|
||||||
|
<div>
|
||||||
|
Checks if a specified extension exists.
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Example:</strong>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<pre><code class="language-stscript">/extension-exists SillyTavern-LALib</code></pre>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
}));
|
||||||
|
|
||||||
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||||
|
name: 'reload-page',
|
||||||
|
callback: async () => {
|
||||||
|
toastr.info('Reloading the page...');
|
||||||
|
location.reload();
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
helpString: 'Reloads the current page. All further commands will not be processed.',
|
||||||
|
}));
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import { eventSource, event_types, saveSettings, saveSettingsDebounced, getRequestHeaders, animation_duration } from '../script.js';
|
import { eventSource, event_types, saveSettings, saveSettingsDebounced, getRequestHeaders, animation_duration } from '../script.js';
|
||||||
import { hideLoader, showLoader } from './loader.js';
|
import { showLoader } from './loader.js';
|
||||||
import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
|
import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
|
||||||
import { renderTemplate, renderTemplateAsync } from './templates.js';
|
import { renderTemplate, renderTemplateAsync } from './templates.js';
|
||||||
import { isSubsetOf, setValueByPath } from './utils.js';
|
import { isSubsetOf, setValueByPath } from './utils.js';
|
||||||
@ -14,7 +14,9 @@ export {
|
|||||||
ModuleWorkerWrapper,
|
ModuleWorkerWrapper,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** @type {string[]} */
|
||||||
export let extensionNames = [];
|
export let extensionNames = [];
|
||||||
|
|
||||||
let manifests = {};
|
let manifests = {};
|
||||||
const defaultUrl = 'http://localhost:5100';
|
const defaultUrl = 'http://localhost:5100';
|
||||||
|
|
||||||
@ -241,7 +243,7 @@ function onEnableExtensionClick() {
|
|||||||
enableExtension(name, false);
|
enableExtension(name, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function enableExtension(name, reload = true) {
|
export async function enableExtension(name, reload = true) {
|
||||||
extension_settings.disabledExtensions = extension_settings.disabledExtensions.filter(x => x !== name);
|
extension_settings.disabledExtensions = extension_settings.disabledExtensions.filter(x => x !== name);
|
||||||
stateChanged = true;
|
stateChanged = true;
|
||||||
await saveSettings();
|
await saveSettings();
|
||||||
@ -252,7 +254,7 @@ async function enableExtension(name, reload = true) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function disableExtension(name, reload = true) {
|
export async function disableExtension(name, reload = true) {
|
||||||
extension_settings.disabledExtensions.push(name);
|
extension_settings.disabledExtensions.push(name);
|
||||||
stateChanged = true;
|
stateChanged = true;
|
||||||
await saveSettings();
|
await saveSettings();
|
||||||
@ -1041,7 +1043,9 @@ export async function openThirdPartyExtensionMenu(suggestUrl = '') {
|
|||||||
await installExtension(url);
|
await installExtension(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
jQuery(async function () {
|
|
||||||
|
|
||||||
|
export async function initExtensions() {
|
||||||
await addExtensionsButtonAndMenu();
|
await addExtensionsButtonAndMenu();
|
||||||
$('#extensionsMenuButton').css('display', 'flex');
|
$('#extensionsMenuButton').css('display', 'flex');
|
||||||
|
|
||||||
@ -1060,4 +1064,4 @@ jQuery(async function () {
|
|||||||
* @listens #third_party_extension_button#click - The click event of the '#third_party_extension_button' element.
|
* @listens #third_party_extension_button#click - The click event of the '#third_party_extension_button' element.
|
||||||
*/
|
*/
|
||||||
$('#third_party_extension_button').on('click', () => openThirdPartyExtensionMenu());
|
$('#third_party_extension_button').on('click', () => openThirdPartyExtensionMenu());
|
||||||
});
|
}
|
||||||
|
12
public/scripts/extensions/connection-manager/edit.html
Normal file
12
public/scripts/extensions/connection-manager/edit.html
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<div>
|
||||||
|
<h3>Included settings:</h3>
|
||||||
|
<div class="justifyLeft flex-container flexFlowColumn flexNoGap">
|
||||||
|
{{#each settings}}
|
||||||
|
<label class="checkbox_label">
|
||||||
|
<input type="checkbox" value="{{@key}}" name="exclude"{{#if this}} checked{{/if}}>
|
||||||
|
<span>{{@key}}</span>
|
||||||
|
</label>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
<h3 data-i18n="Profile name:">Profile name:</h3>
|
||||||
|
</div>
|
@ -1,6 +1,6 @@
|
|||||||
import { event_types, eventSource, main_api, saveSettingsDebounced } from '../../../script.js';
|
import { event_types, eventSource, main_api, saveSettingsDebounced } from '../../../script.js';
|
||||||
import { extension_settings, renderExtensionTemplateAsync } from '../../extensions.js';
|
import { extension_settings, renderExtensionTemplateAsync } from '../../extensions.js';
|
||||||
import { callGenericPopup, Popup, POPUP_TYPE } from '../../popup.js';
|
import { callGenericPopup, Popup, POPUP_RESULT, POPUP_TYPE } from '../../popup.js';
|
||||||
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
|
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
|
||||||
import { SlashCommandAbortController } from '../../slash-commands/SlashCommandAbortController.js';
|
import { SlashCommandAbortController } from '../../slash-commands/SlashCommandAbortController.js';
|
||||||
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
|
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
|
||||||
@ -136,6 +136,7 @@ const profilesProvider = () => [
|
|||||||
* @property {string} [context] Context Template
|
* @property {string} [context] Context Template
|
||||||
* @property {string} [instruct-state] Instruct Mode
|
* @property {string} [instruct-state] Instruct Mode
|
||||||
* @property {string} [tokenizer] Tokenizer
|
* @property {string} [tokenizer] Tokenizer
|
||||||
|
* @property {string[]} [exclude] Commands to exclude
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -172,8 +173,13 @@ function findProfileByName(value) {
|
|||||||
async function readProfileFromCommands(mode, profile, cleanUp = false) {
|
async function readProfileFromCommands(mode, profile, cleanUp = false) {
|
||||||
const commands = mode === 'cc' ? CC_COMMANDS : TC_COMMANDS;
|
const commands = mode === 'cc' ? CC_COMMANDS : TC_COMMANDS;
|
||||||
const opposingCommands = mode === 'cc' ? TC_COMMANDS : CC_COMMANDS;
|
const opposingCommands = mode === 'cc' ? TC_COMMANDS : CC_COMMANDS;
|
||||||
|
const excludeList = Array.isArray(profile.exclude) ? profile.exclude : [];
|
||||||
for (const command of commands) {
|
for (const command of commands) {
|
||||||
try {
|
try {
|
||||||
|
if (excludeList.includes(command)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const args = getNamedArguments();
|
const args = getNamedArguments();
|
||||||
const result = await SlashCommandParser.commands[command].callback(args, '');
|
const result = await SlashCommandParser.commands[command].callback(args, '');
|
||||||
if (result) {
|
if (result) {
|
||||||
@ -209,15 +215,37 @@ async function readProfileFromCommands(mode, profile, cleanUp = false) {
|
|||||||
async function createConnectionProfile(forceName = null) {
|
async function createConnectionProfile(forceName = null) {
|
||||||
const mode = main_api === 'openai' ? 'cc' : 'tc';
|
const mode = main_api === 'openai' ? 'cc' : 'tc';
|
||||||
const id = uuidv4();
|
const id = uuidv4();
|
||||||
|
/** @type {ConnectionProfile} */
|
||||||
const profile = {
|
const profile = {
|
||||||
id,
|
id,
|
||||||
mode,
|
mode,
|
||||||
|
exclude: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
await readProfileFromCommands(mode, profile);
|
await readProfileFromCommands(mode, profile);
|
||||||
|
|
||||||
const profileForDisplay = makeFancyProfile(profile);
|
const profileForDisplay = makeFancyProfile(profile);
|
||||||
const template = await renderExtensionTemplateAsync(MODULE_NAME, 'profile', { profile: profileForDisplay });
|
const template = $(await renderExtensionTemplateAsync(MODULE_NAME, 'profile', { profile: profileForDisplay }));
|
||||||
|
template.find('input[name="exclude"]').on('input', function () {
|
||||||
|
const fancyName = String($(this).val());
|
||||||
|
const keyName = Object.entries(FANCY_NAMES).find(x => x[1] === fancyName)?.[0];
|
||||||
|
if (!keyName) {
|
||||||
|
console.warn('Key not found for fancy name:', fancyName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(profile.exclude)) {
|
||||||
|
profile.exclude = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const excludeState = !$(this).prop('checked');
|
||||||
|
if (excludeState) {
|
||||||
|
profile.exclude.push(keyName);
|
||||||
|
} else {
|
||||||
|
const index = profile.exclude.indexOf(keyName);
|
||||||
|
index !== -1 && profile.exclude.splice(index, 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
const isNameTaken = (n) => extension_settings.connectionManager.profiles.some(p => p.name === n);
|
const isNameTaken = (n) => extension_settings.connectionManager.profiles.some(p => p.name === n);
|
||||||
const suggestedName = getUniqueName(collapseSpaces(`${profile.api ?? ''} ${profile.model ?? ''} - ${profile.preset ?? ''}`), isNameTaken);
|
const suggestedName = getUniqueName(collapseSpaces(`${profile.api ?? ''} ${profile.model ?? ''} - ${profile.preset ?? ''}`), isNameTaken);
|
||||||
const name = forceName ?? await callGenericPopup(template, POPUP_TYPE.INPUT, suggestedName, { rows: 2 });
|
const name = forceName ?? await callGenericPopup(template, POPUP_TYPE.INPUT, suggestedName, { rows: 2 });
|
||||||
@ -231,7 +259,13 @@ async function createConnectionProfile(forceName = null) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
profile.name = name;
|
if (Array.isArray(profile.exclude)) {
|
||||||
|
for (const command of profile.exclude) {
|
||||||
|
delete profile[command];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
profile.name = String(name);
|
||||||
return profile;
|
return profile;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -358,7 +392,11 @@ async function renderDetailsContent(detailsContent) {
|
|||||||
const profile = extension_settings.connectionManager.profiles.find(p => p.id === selectedProfile);
|
const profile = extension_settings.connectionManager.profiles.find(p => p.id === selectedProfile);
|
||||||
if (profile) {
|
if (profile) {
|
||||||
const profileForDisplay = makeFancyProfile(profile);
|
const profileForDisplay = makeFancyProfile(profile);
|
||||||
const template = await renderExtensionTemplateAsync(MODULE_NAME, 'view', { profile: profileForDisplay });
|
const templateParams = { profile: profileForDisplay };
|
||||||
|
if (Array.isArray(profile.exclude) && profile.exclude.length > 0) {
|
||||||
|
templateParams.omitted = profile.exclude.map(e => FANCY_NAMES[e]).join(', ');
|
||||||
|
}
|
||||||
|
const template = await renderExtensionTemplateAsync(MODULE_NAME, 'view', templateParams);
|
||||||
detailsContent.innerHTML = template;
|
detailsContent.innerHTML = template;
|
||||||
} else {
|
} else {
|
||||||
detailsContent.textContent = t`No profile selected`;
|
detailsContent.textContent = t`No profile selected`;
|
||||||
@ -473,29 +511,71 @@ async function renderDetailsContent(detailsContent) {
|
|||||||
await eventSource.emit(event_types.CONNECTION_PROFILE_LOADED, NONE);
|
await eventSource.emit(event_types.CONNECTION_PROFILE_LOADED, NONE);
|
||||||
});
|
});
|
||||||
|
|
||||||
const renameButton = document.getElementById('rename_connection_profile');
|
const editButton = document.getElementById('edit_connection_profile');
|
||||||
renameButton.addEventListener('click', async () => {
|
editButton.addEventListener('click', async () => {
|
||||||
const selectedProfile = extension_settings.connectionManager.selectedProfile;
|
const selectedProfile = extension_settings.connectionManager.selectedProfile;
|
||||||
const profile = extension_settings.connectionManager.profiles.find(p => p.id === selectedProfile);
|
const profile = extension_settings.connectionManager.profiles.find(p => p.id === selectedProfile);
|
||||||
if (!profile) {
|
if (!profile) {
|
||||||
console.log('No profile selected');
|
console.log('No profile selected');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!Array.isArray(profile.exclude)) {
|
||||||
|
profile.exclude = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
let saveChanges = false;
|
||||||
|
const sortByViewOrder = (a, b) => Object.keys(FANCY_NAMES).indexOf(a) - Object.keys(FANCY_NAMES).indexOf(b);
|
||||||
|
const commands = profile.mode === 'cc' ? CC_COMMANDS : TC_COMMANDS;
|
||||||
|
const settings = commands.slice().sort(sortByViewOrder).reduce((acc, command) => {
|
||||||
|
const fancyName = FANCY_NAMES[command];
|
||||||
|
acc[fancyName] = !profile.exclude.includes(command);
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
const template = $(await renderExtensionTemplateAsync(MODULE_NAME, 'edit', { name: profile.name, settings }));
|
||||||
|
const newName = await callGenericPopup(template, POPUP_TYPE.INPUT, profile.name, {
|
||||||
|
customButtons: [{
|
||||||
|
text: t`Save and Update`,
|
||||||
|
classes: ['popup-button-ok'],
|
||||||
|
result: POPUP_RESULT.AFFIRMATIVE,
|
||||||
|
action: () => {
|
||||||
|
saveChanges = true;
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
|
||||||
const newName = await Popup.show.input(t`Enter a new name`, null, profile.name, { rows: 2 });
|
|
||||||
if (!newName) {
|
if (!newName) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (extension_settings.connectionManager.profiles.some(p => p.name === newName)) {
|
if (profile.name !== newName && extension_settings.connectionManager.profiles.some(p => p.name === newName)) {
|
||||||
toastr.error('A profile with the same name already exists.');
|
toastr.error('A profile with the same name already exists.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
profile.name = newName;
|
const newExcludeList = template.find('input[name="exclude"]:not(:checked)').map(function () {
|
||||||
|
return Object.entries(FANCY_NAMES).find(x => x[1] === String($(this).val()))?.[0];
|
||||||
|
}).get();
|
||||||
|
|
||||||
|
if (newExcludeList.length !== profile.exclude.length || !newExcludeList.every(e => profile.exclude.includes(e))) {
|
||||||
|
profile.exclude = newExcludeList;
|
||||||
|
for (const command of newExcludeList) {
|
||||||
|
delete profile[command];
|
||||||
|
}
|
||||||
|
if (saveChanges) {
|
||||||
|
await updateConnectionProfile(profile);
|
||||||
|
} else {
|
||||||
|
toastr.info('Press "Update" to record them into the profile.', 'Included settings list updated');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (profile.name !== newName) {
|
||||||
|
toastr.success('Connection profile renamed.');
|
||||||
|
profile.name = String(newName);
|
||||||
|
}
|
||||||
|
|
||||||
saveSettingsDebounced();
|
saveSettingsDebounced();
|
||||||
renderConnectionProfiles(profiles);
|
renderConnectionProfiles(profiles);
|
||||||
toastr.success('Connection profile renamed', '', { timeOut: 1500 });
|
await renderDetailsContent(detailsContent);
|
||||||
});
|
});
|
||||||
|
|
||||||
/** @type {HTMLElement} */
|
/** @type {HTMLElement} */
|
||||||
|
@ -2,11 +2,20 @@
|
|||||||
<h2 data-i18n="Creating a Connection Profile">
|
<h2 data-i18n="Creating a Connection Profile">
|
||||||
Creating a Connection Profile
|
Creating a Connection Profile
|
||||||
</h2>
|
</h2>
|
||||||
<ul class="justifyLeft">
|
<div class="justifyLeft flex-container flexFlowColumn flexNoGap">
|
||||||
{{#each profile}}
|
{{#each profile}}
|
||||||
<li><strong data-i18n="{{@key}}">{{@key}}:</strong> {{this}}</li>
|
<label class="checkbox_label">
|
||||||
|
<input type="checkbox" value="{{@key}}" name="exclude" checked>
|
||||||
|
<span><strong data-i18n="{{@key}}">{{@key}}:</strong> {{this}}</span>
|
||||||
|
</label>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</ul>
|
</div>
|
||||||
|
<div class="marginTop5">
|
||||||
|
<small>
|
||||||
|
<b>Hint:</b>
|
||||||
|
<i>Click on the setting name to omit it from the profile.</i>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
<h3 data-i18n="Enter a name:">
|
<h3 data-i18n="Enter a name:">
|
||||||
Enter a name:
|
Enter a name:
|
||||||
</h3>
|
</h3>
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
<i id="view_connection_profile" class="menu_button fa-solid fa-info-circle" title="View connection profile details" data-i18n="[title]View connection profile details"></i>
|
<i id="view_connection_profile" class="menu_button fa-solid fa-info-circle" title="View connection profile details" data-i18n="[title]View connection profile details"></i>
|
||||||
<i id="create_connection_profile" class="menu_button fa-solid fa-file-circle-plus" title="Create a new connection profile" data-i18n="[title]Create a new connection profile"></i>
|
<i id="create_connection_profile" class="menu_button fa-solid fa-file-circle-plus" title="Create a new connection profile" data-i18n="[title]Create a new connection profile"></i>
|
||||||
<i id="update_connection_profile" class="menu_button fa-solid fa-save" title="Update a connection profile" data-i18n="[title]Update a connection profile"></i>
|
<i id="update_connection_profile" class="menu_button fa-solid fa-save" title="Update a connection profile" data-i18n="[title]Update a connection profile"></i>
|
||||||
<i id="rename_connection_profile" class="menu_button fa-solid fa-pencil" title="Rename a connection profile" data-i18n="[title]Rename a connection profile"></i>
|
<i id="edit_connection_profile" class="menu_button fa-solid fa-pencil" title="Edit a connection profile" data-i18n="[title]Edit a connection profile"></i>
|
||||||
<i id="reload_connection_profile" class="menu_button fa-solid fa-recycle" title="Reload a connection profile" data-i18n="[title]Reload a connection profile"></i>
|
<i id="reload_connection_profile" class="menu_button fa-solid fa-recycle" title="Reload a connection profile" data-i18n="[title]Reload a connection profile"></i>
|
||||||
<i id="delete_connection_profile" class="menu_button fa-solid fa-trash-can" title="Delete a connection profile" data-i18n="[title]Delete a connection profile"></i>
|
<i id="delete_connection_profile" class="menu_button fa-solid fa-trash-can" title="Delete a connection profile" data-i18n="[title]Delete a connection profile"></i>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,3 +3,8 @@
|
|||||||
<li><strong data-i18n="{{@key}}">{{@key}}:</strong> {{this}}</li>
|
<li><strong data-i18n="{{@key}}">{{@key}}:</strong> {{this}}</li>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</ul>
|
</ul>
|
||||||
|
{{#if omitted}}
|
||||||
|
<div class="margin5">
|
||||||
|
<strong data-i18n="Omitted Settings:">Omitted Settings:</strong> <span>{{omitted}}</span>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
@ -26,9 +26,11 @@ let paginationVisiblePages = 10;
|
|||||||
let paginationMaxLinesPerPage = 2;
|
let paginationMaxLinesPerPage = 2;
|
||||||
let galleryMaxRows = 3;
|
let galleryMaxRows = 3;
|
||||||
|
|
||||||
$('body').on('click', '.dragClose', function () {
|
// Remove all draggables associated with the gallery
|
||||||
const relatedId = $(this).data('related-id'); // Get the ID of the related draggable
|
$('#movingDivs').on('click', '.dragClose', function () {
|
||||||
$(`body > .draggable[id="${relatedId}"]`).remove(); // Remove the associated draggable
|
const relatedId = $(this).data('related-id');
|
||||||
|
if (!relatedId) return;
|
||||||
|
$(`#movingDivs > .draggable[id="${relatedId}"]`).remove();
|
||||||
});
|
});
|
||||||
|
|
||||||
const CUSTOM_GALLERY_REMOVED_EVENT = 'galleryRemoved';
|
const CUSTOM_GALLERY_REMOVED_EVENT = 'galleryRemoved';
|
||||||
@ -290,7 +292,7 @@ function makeMovable(id = 'gallery') {
|
|||||||
|
|
||||||
$('#dragGallery').css('display', 'block');
|
$('#dragGallery').css('display', 'block');
|
||||||
|
|
||||||
$('body').append(newElement);
|
$('#movingDivs').append(newElement);
|
||||||
|
|
||||||
loadMovingUIState();
|
loadMovingUIState();
|
||||||
$(`.draggable[forChar="${id}"]`).css('display', 'block');
|
$(`.draggable[forChar="${id}"]`).css('display', 'block');
|
||||||
@ -362,8 +364,8 @@ function makeDragImg(id, url) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 3: Attach it to the body
|
// Step 3: Attach it to the movingDivs container
|
||||||
document.body.appendChild(newElement);
|
document.getElementById('movingDivs').appendChild(newElement);
|
||||||
|
|
||||||
// Step 4: Call dragElement and loadMovingUIState
|
// Step 4: Call dragElement and loadMovingUIState
|
||||||
const appendedElement = document.getElementById(uniqueId);
|
const appendedElement = document.getElementById(uniqueId);
|
||||||
|
@ -350,6 +350,7 @@
|
|||||||
}
|
}
|
||||||
.popup:has(#qr--modalEditor) {
|
.popup:has(#qr--modalEditor) {
|
||||||
aspect-ratio: unset;
|
aspect-ratio: unset;
|
||||||
|
width: unset;
|
||||||
}
|
}
|
||||||
.popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) {
|
.popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) {
|
||||||
min-width: unset;
|
min-width: unset;
|
||||||
|
@ -415,6 +415,7 @@
|
|||||||
|
|
||||||
.popup:has(#qr--modalEditor) {
|
.popup:has(#qr--modalEditor) {
|
||||||
aspect-ratio: unset;
|
aspect-ratio: unset;
|
||||||
|
width: unset;
|
||||||
|
|
||||||
&:has(.qr--isExecuting.qr--minimized) {
|
&:has(.qr--isExecuting.qr--minimized) {
|
||||||
min-width: unset;
|
min-width: unset;
|
||||||
|
206
public/scripts/extensions/tts/cosyvoice.js
Normal file
206
public/scripts/extensions/tts/cosyvoice.js
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
import { saveTtsProviderSettings } from './index.js';
|
||||||
|
|
||||||
|
export { CosyVoiceProvider };
|
||||||
|
|
||||||
|
class CosyVoiceProvider {
|
||||||
|
//########//
|
||||||
|
// Config //
|
||||||
|
//########//
|
||||||
|
|
||||||
|
settings;
|
||||||
|
ready = false;
|
||||||
|
voices = [];
|
||||||
|
separator = '. ';
|
||||||
|
audioElement = document.createElement('audio');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform any text processing before passing to TTS engine.
|
||||||
|
* @param {string} text Input text
|
||||||
|
* @returns {string} Processed text
|
||||||
|
*/
|
||||||
|
processText(text) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
audioFormats = ['wav', 'ogg', 'silk', 'mp3', 'flac'];
|
||||||
|
|
||||||
|
languageLabels = {
|
||||||
|
'Auto': 'auto',
|
||||||
|
};
|
||||||
|
|
||||||
|
langKey2LangCode = {
|
||||||
|
'zh': 'zh-CN',
|
||||||
|
'en': 'en-US',
|
||||||
|
'ja': 'ja-JP',
|
||||||
|
'ko': 'ko-KR',
|
||||||
|
};
|
||||||
|
|
||||||
|
modelTypes = {
|
||||||
|
CosyVoice: 'CosyVoice',
|
||||||
|
};
|
||||||
|
|
||||||
|
defaultSettings = {
|
||||||
|
provider_endpoint: 'http://localhost:9880',
|
||||||
|
format: 'wav',
|
||||||
|
lang: 'auto',
|
||||||
|
streaming: false,
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
get settingsHtml() {
|
||||||
|
let html = `
|
||||||
|
|
||||||
|
<label for="tts_endpoint">Provider Endpoint:</label>
|
||||||
|
<input id="tts_endpoint" type="text" class="text_pole" maxlength="250" height="300" value="${this.defaultSettings.provider_endpoint}"/>
|
||||||
|
<span>Windows users Use <a target="_blank" href="https://github.com/v3ucn/CosyVoice_For_Windows">CosyVoice_For_Windows</a>(Unofficial).</span><br/>
|
||||||
|
<span>Macos Users Use <a target="_blank" href="https://github.com/v3ucn/CosyVoice_for_MacOs">CosyVoice_for_MacOs</a>(Unofficial).</span><br/>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
`;
|
||||||
|
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
onSettingsChange() {
|
||||||
|
// Used when provider settings are updated from UI
|
||||||
|
this.settings.provider_endpoint = $('#tts_endpoint').val();
|
||||||
|
|
||||||
|
|
||||||
|
saveTtsProviderSettings();
|
||||||
|
this.changeTTSSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadSettings(settings) {
|
||||||
|
// Pupulate Provider UI given input settings
|
||||||
|
if (Object.keys(settings).length == 0) {
|
||||||
|
console.info('Using default TTS Provider settings');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only accept keys defined in defaultSettings
|
||||||
|
this.settings = this.defaultSettings;
|
||||||
|
|
||||||
|
for (const key in settings) {
|
||||||
|
if (key in this.settings) {
|
||||||
|
this.settings[key] = settings[key];
|
||||||
|
} else {
|
||||||
|
console.debug(`Ignoring non-user-configurable setting: ${key}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set initial values from the settings
|
||||||
|
$('#tts_endpoint').val(this.settings.provider_endpoint);
|
||||||
|
|
||||||
|
|
||||||
|
await this.checkReady();
|
||||||
|
|
||||||
|
console.info('ITS: Settings loaded');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform a simple readiness check by trying to fetch voiceIds
|
||||||
|
async checkReady() {
|
||||||
|
await Promise.allSettled([this.fetchTtsVoiceObjects(), this.changeTTSSettings()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async onRefreshClick() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//#################//
|
||||||
|
// TTS Interfaces //
|
||||||
|
//#################//
|
||||||
|
|
||||||
|
async getVoice(voiceName) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (this.voices.length == 0) {
|
||||||
|
this.voices = await this.fetchTtsVoiceObjects();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const match = this.voices.filter(
|
||||||
|
v => v.name == voiceName,
|
||||||
|
)[0];
|
||||||
|
console.log(match);
|
||||||
|
if (!match) {
|
||||||
|
throw `TTS Voice name ${voiceName} not found`;
|
||||||
|
}
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async generateTts(text, voiceId) {
|
||||||
|
const response = await this.fetchTtsGeneration(text, voiceId);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
//###########//
|
||||||
|
// API CALLS //
|
||||||
|
//###########//
|
||||||
|
async fetchTtsVoiceObjects() {
|
||||||
|
const response = await fetch(`${this.settings.provider_endpoint}/speakers`);
|
||||||
|
console.info(response);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP ${response.status}: ${await response.json()}`);
|
||||||
|
}
|
||||||
|
const responseJson = await response.json();
|
||||||
|
|
||||||
|
|
||||||
|
this.voices = responseJson;
|
||||||
|
|
||||||
|
return responseJson;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each time a parameter is changed, we change the configuration
|
||||||
|
async changeTTSSettings() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch TTS generation from the API.
|
||||||
|
* @param {string} inputText Text to generate TTS for
|
||||||
|
* @param {string} voiceId Voice ID to use (model_type&speaker_id))
|
||||||
|
* @returns {Promise<Response|string>} Fetch response
|
||||||
|
*/
|
||||||
|
async fetchTtsGeneration(inputText, voiceId, lang = null, forceNoStreaming = false) {
|
||||||
|
console.info(`Generating new TTS for voice_id ${voiceId}`);
|
||||||
|
|
||||||
|
const streaming = this.settings.streaming;
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
text: inputText,
|
||||||
|
speaker: voiceId,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (streaming) {
|
||||||
|
params['streaming'] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = `${this.settings.provider_endpoint}/`;
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(params), // Convert parameter objects to JSON strings
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (!response.ok) {
|
||||||
|
toastr.error(response.statusText, 'TTS Generation Failed');
|
||||||
|
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Interface not used
|
||||||
|
async fetchTtsFromHistory(history_item_id) {
|
||||||
|
return Promise.resolve(history_item_id);
|
||||||
|
}
|
||||||
|
}
|
226
public/scripts/extensions/tts/gpt-sovits-v2.js
Normal file
226
public/scripts/extensions/tts/gpt-sovits-v2.js
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
import { saveTtsProviderSettings } from './index.js';
|
||||||
|
|
||||||
|
export { GptSovitsV2Provider };
|
||||||
|
|
||||||
|
class GptSovitsV2Provider {
|
||||||
|
//########//
|
||||||
|
// Config //
|
||||||
|
//########//
|
||||||
|
|
||||||
|
settings;
|
||||||
|
ready = false;
|
||||||
|
voices = [];
|
||||||
|
separator = '. ';
|
||||||
|
audioElement = document.createElement('audio');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform any text processing before passing to TTS engine.
|
||||||
|
* @param {string} text Input text
|
||||||
|
* @returns {string} Processed text
|
||||||
|
*/
|
||||||
|
processText(text) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
audioFormats = ['wav', 'ogg', 'silk', 'mp3', 'flac'];
|
||||||
|
|
||||||
|
languageLabels = {
|
||||||
|
'Auto': 'auto',
|
||||||
|
};
|
||||||
|
|
||||||
|
langKey2LangCode = {
|
||||||
|
'zh': 'zh-CN',
|
||||||
|
'en': 'en-US',
|
||||||
|
'ja': 'ja-JP',
|
||||||
|
'ko': 'ko-KR',
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
defaultSettings = {
|
||||||
|
provider_endpoint: 'http://localhost:9880',
|
||||||
|
format: 'wav',
|
||||||
|
lang: 'auto',
|
||||||
|
streaming: false,
|
||||||
|
text_lang: 'zh',
|
||||||
|
prompt_lang: 'zh',
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
get settingsHtml() {
|
||||||
|
let html = `
|
||||||
|
|
||||||
|
<label for="tts_endpoint">Provider Endpoint:</label>
|
||||||
|
<input id="tts_endpoint" type="text" class="text_pole" maxlength="250" height="300" value="${this.defaultSettings.provider_endpoint}"/>
|
||||||
|
<span>Use <a target="_blank" href="https://github.com/v3ucn/GPT-SoVITS-V2">GPT-SoVITS-V2</a>(Unofficial).</span><br/>
|
||||||
|
<label for="text_lang">Text Lang(Inference text language):</label>
|
||||||
|
<input id="text_lang" type="text" class="text_pole" maxlength="250" height="300" value="${this.defaultSettings.text_lang}"/>
|
||||||
|
<label for="text_lang">Prompt Lang(Reference audio text language):</label>
|
||||||
|
<input id="prompt_lang" type="text" class="text_pole" maxlength="250" height="300" value="${this.defaultSettings.prompt_lang}"/>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
`;
|
||||||
|
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
onSettingsChange() {
|
||||||
|
// Used when provider settings are updated from UI
|
||||||
|
this.settings.provider_endpoint = $('#tts_endpoint').val();
|
||||||
|
this.settings.text_lang = $('#text_lang').val();
|
||||||
|
this.settings.prompt_lang = $('#prompt_lang').val();
|
||||||
|
|
||||||
|
|
||||||
|
saveTtsProviderSettings();
|
||||||
|
this.changeTTSSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadSettings(settings) {
|
||||||
|
// Pupulate Provider UI given input settings
|
||||||
|
if (Object.keys(settings).length == 0) {
|
||||||
|
console.info('Using default TTS Provider settings');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only accept keys defined in defaultSettings
|
||||||
|
this.settings = this.defaultSettings;
|
||||||
|
|
||||||
|
for (const key in settings) {
|
||||||
|
if (key in this.settings) {
|
||||||
|
this.settings[key] = settings[key];
|
||||||
|
} else {
|
||||||
|
console.debug(`Ignoring non-user-configurable setting: ${key}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set initial values from the settings
|
||||||
|
$('#tts_endpoint').val(this.settings.provider_endpoint);
|
||||||
|
$('#text_lang').val(this.settings.text_lang);
|
||||||
|
$('#prompt_lang').val(this.settings.prompt_lang);
|
||||||
|
|
||||||
|
|
||||||
|
await this.checkReady();
|
||||||
|
|
||||||
|
console.info('ITS: Settings loaded');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform a simple readiness check by trying to fetch voiceIds
|
||||||
|
async checkReady() {
|
||||||
|
await Promise.allSettled([this.fetchTtsVoiceObjects(), this.changeTTSSettings()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async onRefreshClick() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//#################//
|
||||||
|
// TTS Interfaces //
|
||||||
|
//#################//
|
||||||
|
|
||||||
|
async getVoice(voiceName) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (this.voices.length == 0) {
|
||||||
|
this.voices = await this.fetchTtsVoiceObjects();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const match = this.voices.filter(
|
||||||
|
v => v.name == voiceName,
|
||||||
|
)[0];
|
||||||
|
console.log(match);
|
||||||
|
if (!match) {
|
||||||
|
throw `TTS Voice name ${voiceName} not found`;
|
||||||
|
}
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async generateTts(text, voiceId) {
|
||||||
|
const response = await this.fetchTtsGeneration(text, voiceId);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
//###########//
|
||||||
|
// API CALLS //
|
||||||
|
//###########//
|
||||||
|
async fetchTtsVoiceObjects() {
|
||||||
|
const response = await fetch(`${this.settings.provider_endpoint}/speakers`);
|
||||||
|
console.info(response);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP ${response.status}: ${await response.json()}`);
|
||||||
|
}
|
||||||
|
const responseJson = await response.json();
|
||||||
|
|
||||||
|
|
||||||
|
this.voices = responseJson;
|
||||||
|
|
||||||
|
return responseJson;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each time a parameter is changed, we change the configuration
|
||||||
|
async changeTTSSettings() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch TTS generation from the API.
|
||||||
|
* @param {string} inputText Text to generate TTS for
|
||||||
|
* @param {string} voiceId Voice ID to use (model_type&speaker_id))
|
||||||
|
* @returns {Promise<Response|string>} Fetch response
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async fetchTtsGeneration(inputText, voiceId, lang = null, forceNoStreaming = false) {
|
||||||
|
console.info(`Generating new TTS for voice_id ${voiceId}`);
|
||||||
|
|
||||||
|
function replaceSpeaker(text) {
|
||||||
|
return text.replace(/\[.*?\]/gu, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
let prompt_text = replaceSpeaker(voiceId);
|
||||||
|
|
||||||
|
const streaming = this.settings.streaming;
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
text: inputText,
|
||||||
|
prompt_text: prompt_text,
|
||||||
|
ref_audio_path: './参考音频/' + voiceId + '.wav',
|
||||||
|
text_lang: this.settings.text_lang,
|
||||||
|
prompt_lang: this.settings.prompt_lang,
|
||||||
|
text_split_method: 'cut5',
|
||||||
|
batch_size: 1,
|
||||||
|
media_type: 'ogg',
|
||||||
|
streaming_mode: 'true',
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const url = `${this.settings.provider_endpoint}/`;
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(params), // Convert parameter objects to JSON strings
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (!response.ok) {
|
||||||
|
toastr.error(response.statusText, 'TTS Generation Failed');
|
||||||
|
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Interface not used
|
||||||
|
async fetchTtsFromHistory(history_item_id) {
|
||||||
|
return Promise.resolve(history_item_id);
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@ import { delay, escapeRegex, getBase64Async, getStringHash, onlyUnique } from '.
|
|||||||
import { EdgeTtsProvider } from './edge.js';
|
import { EdgeTtsProvider } from './edge.js';
|
||||||
import { ElevenLabsTtsProvider } from './elevenlabs.js';
|
import { ElevenLabsTtsProvider } from './elevenlabs.js';
|
||||||
import { SileroTtsProvider } from './silerotts.js';
|
import { SileroTtsProvider } from './silerotts.js';
|
||||||
|
import { GptSovitsV2Provider } from './gpt-sovits-v2.js';
|
||||||
import { CoquiTtsProvider } from './coqui.js';
|
import { CoquiTtsProvider } from './coqui.js';
|
||||||
import { SystemTtsProvider } from './system.js';
|
import { SystemTtsProvider } from './system.js';
|
||||||
import { NovelTtsProvider } from './novel.js';
|
import { NovelTtsProvider } from './novel.js';
|
||||||
@ -15,6 +16,7 @@ import { VITSTtsProvider } from './vits.js';
|
|||||||
import { GSVITtsProvider } from './gsvi.js';
|
import { GSVITtsProvider } from './gsvi.js';
|
||||||
import { SBVits2TtsProvider } from './sbvits2.js';
|
import { SBVits2TtsProvider } from './sbvits2.js';
|
||||||
import { AllTalkTtsProvider } from './alltalk.js';
|
import { AllTalkTtsProvider } from './alltalk.js';
|
||||||
|
import { CosyVoiceProvider } from './cosyvoice.js';
|
||||||
import { SpeechT5TtsProvider } from './speecht5.js';
|
import { SpeechT5TtsProvider } from './speecht5.js';
|
||||||
import { AzureTtsProvider } from './azure.js';
|
import { AzureTtsProvider } from './azure.js';
|
||||||
import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
|
import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
|
||||||
@ -86,9 +88,11 @@ const ttsProviders = {
|
|||||||
AllTalk: AllTalkTtsProvider,
|
AllTalk: AllTalkTtsProvider,
|
||||||
Azure: AzureTtsProvider,
|
Azure: AzureTtsProvider,
|
||||||
Coqui: CoquiTtsProvider,
|
Coqui: CoquiTtsProvider,
|
||||||
|
'CosyVoice (Unofficial)': CosyVoiceProvider,
|
||||||
Edge: EdgeTtsProvider,
|
Edge: EdgeTtsProvider,
|
||||||
ElevenLabs: ElevenLabsTtsProvider,
|
ElevenLabs: ElevenLabsTtsProvider,
|
||||||
GSVI: GSVITtsProvider,
|
GSVI: GSVITtsProvider,
|
||||||
|
'GPT-SoVITS-V2 (Unofficial)': GptSovitsV2Provider,
|
||||||
Novel: NovelTtsProvider,
|
Novel: NovelTtsProvider,
|
||||||
OpenAI: OpenAITtsProvider,
|
OpenAI: OpenAITtsProvider,
|
||||||
'OpenAI Compatible': OpenAICompatibleTtsProvider,
|
'OpenAI Compatible': OpenAICompatibleTtsProvider,
|
||||||
|
@ -74,7 +74,7 @@ const samplers = {
|
|||||||
|
|
||||||
let novel_data = null;
|
let novel_data = null;
|
||||||
let badWordsCache = {};
|
let badWordsCache = {};
|
||||||
const BIAS_KEY = '#novel_api-settings';
|
const BIAS_KEY = '#range_block_novel';
|
||||||
|
|
||||||
export function setNovelData(data) {
|
export function setNovelData(data) {
|
||||||
novel_data = data;
|
novel_data = data;
|
||||||
@ -492,11 +492,37 @@ function getBadWordPermutations(text) {
|
|||||||
|
|
||||||
export function getNovelGenerationData(finalPrompt, settings, maxLength, isImpersonate, isContinue, _cfgValues, type) {
|
export function getNovelGenerationData(finalPrompt, settings, maxLength, isImpersonate, isContinue, _cfgValues, type) {
|
||||||
console.debug('NovelAI generation data for', type);
|
console.debug('NovelAI generation data for', type);
|
||||||
|
const isKayra = nai_settings.model_novel.includes('kayra');
|
||||||
|
const isErato = nai_settings.model_novel.includes('erato');
|
||||||
|
|
||||||
const tokenizerType = getTokenizerTypeForModel(nai_settings.model_novel);
|
const tokenizerType = getTokenizerTypeForModel(nai_settings.model_novel);
|
||||||
|
const stoppingStrings = getStoppingStrings(isImpersonate, isContinue);
|
||||||
|
|
||||||
|
// Llama 3 tokenizer, huh?
|
||||||
|
if (isErato) {
|
||||||
|
const additionalStopStrings = [];
|
||||||
|
for (const stoppingString of stoppingStrings) {
|
||||||
|
if (stoppingString.startsWith('\n')) {
|
||||||
|
additionalStopStrings.push('.' + stoppingString);
|
||||||
|
additionalStopStrings.push('!' + stoppingString);
|
||||||
|
additionalStopStrings.push('?' + stoppingString);
|
||||||
|
additionalStopStrings.push('*' + stoppingString);
|
||||||
|
additionalStopStrings.push('"' + stoppingString);
|
||||||
|
additionalStopStrings.push('_' + stoppingString);
|
||||||
|
additionalStopStrings.push('...' + stoppingString);
|
||||||
|
additionalStopStrings.push('."' + stoppingString);
|
||||||
|
additionalStopStrings.push('?"' + stoppingString);
|
||||||
|
additionalStopStrings.push('!"' + stoppingString);
|
||||||
|
additionalStopStrings.push('.*' + stoppingString);
|
||||||
|
additionalStopStrings.push(')' + stoppingString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stoppingStrings.push(...additionalStopStrings);
|
||||||
|
}
|
||||||
|
|
||||||
|
const MAX_STOP_SEQUENCES = 1024;
|
||||||
const stopSequences = (tokenizerType !== tokenizers.NONE)
|
const stopSequences = (tokenizerType !== tokenizers.NONE)
|
||||||
? getStoppingStrings(isImpersonate, isContinue)
|
? stoppingStrings.slice(0, MAX_STOP_SEQUENCES).map(t => getTextTokens(tokenizerType, t))
|
||||||
.map(t => getTextTokens(tokenizerType, t))
|
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const badWordIds = (tokenizerType !== tokenizers.NONE)
|
const badWordIds = (tokenizerType !== tokenizers.NONE)
|
||||||
@ -515,11 +541,9 @@ export function getNovelGenerationData(finalPrompt, settings, maxLength, isImper
|
|||||||
console.log(finalPrompt);
|
console.log(finalPrompt);
|
||||||
}
|
}
|
||||||
|
|
||||||
const isKayra = nai_settings.model_novel.includes('kayra');
|
|
||||||
const isErato = nai_settings.model_novel.includes('erato');
|
|
||||||
|
|
||||||
if (isErato) {
|
if (isErato) {
|
||||||
finalPrompt = '<|startoftext|>' + finalPrompt;
|
finalPrompt = '<|startoftext|><|reserved_special_token81|>' + finalPrompt;
|
||||||
}
|
}
|
||||||
|
|
||||||
const adjustedMaxLength = (isKayra || isErato) ? getKayraMaxResponseTokens() : maximum_output_length;
|
const adjustedMaxLength = (isKayra || isErato) ? getKayraMaxResponseTokens() : maximum_output_length;
|
||||||
|
@ -3469,7 +3469,7 @@ function getModelOptions(quiet) {
|
|||||||
case 'openai':
|
case 'openai':
|
||||||
return oai_settings.chat_completion_source;
|
return oai_settings.chat_completion_source;
|
||||||
default:
|
default:
|
||||||
return nullResult;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,7 +143,7 @@ function selectSystemPromptCallback(args, name) {
|
|||||||
foundName = result[0].item;
|
foundName = result[0].item;
|
||||||
}
|
}
|
||||||
|
|
||||||
$select.val(foundName).trigger('input');
|
$select.val(foundName).trigger('change');
|
||||||
!quiet && toastr.success(`System prompt "${foundName}" selected`);
|
!quiet && toastr.success(`System prompt "${foundName}" selected`);
|
||||||
return foundName;
|
return foundName;
|
||||||
}
|
}
|
||||||
|
@ -68,6 +68,10 @@ const LLAMACPP_DEFAULT_ORDER = [
|
|||||||
'temperature',
|
'temperature',
|
||||||
];
|
];
|
||||||
const OOBA_DEFAULT_ORDER = [
|
const OOBA_DEFAULT_ORDER = [
|
||||||
|
'repetition_penalty',
|
||||||
|
'presence_penalty',
|
||||||
|
'frequency_penalty',
|
||||||
|
'dry',
|
||||||
'temperature',
|
'temperature',
|
||||||
'dynamic_temperature',
|
'dynamic_temperature',
|
||||||
'quadratic_sampling',
|
'quadratic_sampling',
|
||||||
@ -80,6 +84,9 @@ const OOBA_DEFAULT_ORDER = [
|
|||||||
'top_a',
|
'top_a',
|
||||||
'min_p',
|
'min_p',
|
||||||
'mirostat',
|
'mirostat',
|
||||||
|
'xtc',
|
||||||
|
'encoder_repetition_penalty',
|
||||||
|
'no_repeat_ngram',
|
||||||
];
|
];
|
||||||
const BIAS_KEY = '#textgenerationwebui_api-settings';
|
const BIAS_KEY = '#textgenerationwebui_api-settings';
|
||||||
|
|
||||||
@ -798,6 +805,25 @@ function showTypeSpecificControls(type) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts missing items from the source array into the target array.
|
||||||
|
* @param {any[]} source - Source array
|
||||||
|
* @param {any[]} target - Target array
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
function insertMissingArrayItems(source, target) {
|
||||||
|
if (source === target || !Array.isArray(source) || !Array.isArray(target)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const item of source) {
|
||||||
|
if (!target.includes(item)) {
|
||||||
|
const index = source.indexOf(item);
|
||||||
|
target.splice(index, 0, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function setSettingByName(setting, value, trigger) {
|
function setSettingByName(setting, value, trigger) {
|
||||||
if (value === null || value === undefined) {
|
if (value === null || value === undefined) {
|
||||||
return;
|
return;
|
||||||
@ -812,6 +838,7 @@ function setSettingByName(setting, value, trigger) {
|
|||||||
|
|
||||||
if ('sampler_priority' === setting) {
|
if ('sampler_priority' === setting) {
|
||||||
value = Array.isArray(value) ? value : OOBA_DEFAULT_ORDER;
|
value = Array.isArray(value) ? value : OOBA_DEFAULT_ORDER;
|
||||||
|
insertMissingArrayItems(OOBA_DEFAULT_ORDER, value);
|
||||||
sortOobaItemsByOrder(value);
|
sortOobaItemsByOrder(value);
|
||||||
settings.sampler_priority = value;
|
settings.sampler_priority = value;
|
||||||
return;
|
return;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { chat_metadata, getCurrentChatId, saveSettingsDebounced, sendSystemMessage, system_message_types } from '../script.js';
|
import { chat_metadata, getCurrentChatId, saveSettingsDebounced, sendSystemMessage, system_message_types } from '../script.js';
|
||||||
import { extension_settings, saveMetadataDebounced } from './extensions.js';
|
import { extension_settings, saveMetadataDebounced } from './extensions.js';
|
||||||
|
import { callGenericPopup, POPUP_TYPE } from './popup.js';
|
||||||
import { executeSlashCommandsWithOptions } from './slash-commands.js';
|
import { executeSlashCommandsWithOptions } from './slash-commands.js';
|
||||||
import { SlashCommand } from './slash-commands/SlashCommand.js';
|
import { SlashCommand } from './slash-commands/SlashCommand.js';
|
||||||
import { SlashCommandAbortController } from './slash-commands/SlashCommandAbortController.js';
|
import { SlashCommandAbortController } from './slash-commands/SlashCommandAbortController.js';
|
||||||
@ -11,7 +12,7 @@ import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCom
|
|||||||
import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
|
import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
|
||||||
import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js';
|
import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js';
|
||||||
import { SlashCommandScope } from './slash-commands/SlashCommandScope.js';
|
import { SlashCommandScope } from './slash-commands/SlashCommandScope.js';
|
||||||
import { isFalseBoolean, convertValueType } from './utils.js';
|
import { isFalseBoolean, convertValueType, isTrueBoolean } from './utils.js';
|
||||||
|
|
||||||
/** @typedef {import('./slash-commands/SlashCommandParser.js').NamedArguments} NamedArguments */
|
/** @typedef {import('./slash-commands/SlashCommandParser.js').NamedArguments} NamedArguments */
|
||||||
/** @typedef {import('./slash-commands/SlashCommand.js').UnnamedArguments} UnnamedArguments */
|
/** @typedef {import('./slash-commands/SlashCommand.js').UnnamedArguments} UnnamedArguments */
|
||||||
@ -303,24 +304,48 @@ export function replaceVariableMacros(input) {
|
|||||||
return lines.join('\n');
|
return lines.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
function listVariablesCallback() {
|
async function listVariablesCallback(args) {
|
||||||
|
const type = String(args?.format || '').toLowerCase().trim() || 'popup';
|
||||||
|
const scope = String(args?.scope || '').toLowerCase().trim() || 'all';
|
||||||
if (!chat_metadata.variables) {
|
if (!chat_metadata.variables) {
|
||||||
chat_metadata.variables = {};
|
chat_metadata.variables = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const localVariables = Object.entries(chat_metadata.variables).map(([name, value]) => `${name}: ${value}`);
|
const includeLocalVariables = scope === 'all' || scope === 'local';
|
||||||
const globalVariables = Object.entries(extension_settings.variables.global).map(([name, value]) => `${name}: ${value}`);
|
const includeGlobalVariables = scope === 'all' || scope === 'global';
|
||||||
|
|
||||||
|
const localVariables = includeLocalVariables ? Object.entries(chat_metadata.variables).map(([name, value]) => `${name}: ${value}`) : [];
|
||||||
|
const globalVariables = includeGlobalVariables ? Object.entries(extension_settings.variables.global).map(([name, value]) => `${name}: ${value}`) : [];
|
||||||
|
|
||||||
|
const jsonVariables = [
|
||||||
|
...Object.entries(chat_metadata.variables).map(x => ({ key: x[0], value: x[1], scope: 'local' })),
|
||||||
|
...Object.entries(extension_settings.variables.global).map(x => ({ key: x[0], value: x[1], scope: 'global' })),
|
||||||
|
];
|
||||||
|
|
||||||
const localVariablesString = localVariables.length > 0 ? localVariables.join('\n\n') : 'No local variables';
|
const localVariablesString = localVariables.length > 0 ? localVariables.join('\n\n') : 'No local variables';
|
||||||
const globalVariablesString = globalVariables.length > 0 ? globalVariables.join('\n\n') : 'No global variables';
|
const globalVariablesString = globalVariables.length > 0 ? globalVariables.join('\n\n') : 'No global variables';
|
||||||
const chatName = getCurrentChatId();
|
const chatName = getCurrentChatId();
|
||||||
|
|
||||||
const converter = new showdown.Converter();
|
const converter = new showdown.Converter();
|
||||||
const message = `### Local variables (${chatName}):\n${localVariablesString}\n\n### Global variables:\n${globalVariablesString}`;
|
const message = [
|
||||||
|
includeLocalVariables ? `### Local variables (${chatName}):\n${localVariablesString}` : '',
|
||||||
|
includeGlobalVariables ? `### Global variables:\n${globalVariablesString}` : '',
|
||||||
|
].filter(x => x).join('\n\n');
|
||||||
const htmlMessage = DOMPurify.sanitize(converter.makeHtml(message));
|
const htmlMessage = DOMPurify.sanitize(converter.makeHtml(message));
|
||||||
|
|
||||||
sendSystemMessage(system_message_types.GENERIC, htmlMessage);
|
switch (type) {
|
||||||
return '';
|
case 'none':
|
||||||
|
break;
|
||||||
|
case 'chat':
|
||||||
|
sendSystemMessage(system_message_types.GENERIC, htmlMessage);
|
||||||
|
break;
|
||||||
|
case 'popup':
|
||||||
|
default:
|
||||||
|
await callGenericPopup(htmlMessage, POPUP_TYPE.TEXT);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.stringify(jsonVariables);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -463,7 +488,7 @@ function existsGlobalVariable(name) {
|
|||||||
/**
|
/**
|
||||||
* Parses boolean operands from command arguments.
|
* Parses boolean operands from command arguments.
|
||||||
* @param {object} args Command arguments
|
* @param {object} args Command arguments
|
||||||
* @returns {{a: string | number, b: string | number, rule: string}} Boolean operands
|
* @returns {{a: string | number, b: string | number?, rule: string}} Boolean operands
|
||||||
*/
|
*/
|
||||||
export function parseBooleanOperands(args) {
|
export function parseBooleanOperands(args) {
|
||||||
// Resolution order: numeric literal, local variable, global variable, string literal
|
// Resolution order: numeric literal, local variable, global variable, string literal
|
||||||
@ -472,6 +497,9 @@ export function parseBooleanOperands(args) {
|
|||||||
*/
|
*/
|
||||||
function getOperand(operand) {
|
function getOperand(operand) {
|
||||||
if (operand === undefined) {
|
if (operand === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (operand === '') {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -500,8 +528,8 @@ export function parseBooleanOperands(args) {
|
|||||||
return stringLiteral || '';
|
return stringLiteral || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
const left = getOperand(args.a || args.left || args.first || args.x);
|
const left = getOperand(args.a ?? args.left ?? args.first ?? args.x);
|
||||||
const right = getOperand(args.b || args.right || args.second || args.y);
|
const right = getOperand(args.b ?? args.right ?? args.second ?? args.y);
|
||||||
const rule = args.rule;
|
const rule = args.rule;
|
||||||
|
|
||||||
return { a: left, b: right, rule };
|
return { a: left, b: right, rule };
|
||||||
@ -509,84 +537,79 @@ export function parseBooleanOperands(args) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Evaluates a boolean comparison rule.
|
* Evaluates a boolean comparison rule.
|
||||||
* @param {string} rule Boolean comparison rule
|
*
|
||||||
|
* @param {string?} rule Boolean comparison rule
|
||||||
* @param {string|number} a The left operand
|
* @param {string|number} a The left operand
|
||||||
* @param {string|number} b The right operand
|
* @param {string|number?} b The right operand
|
||||||
* @returns {boolean} True if the rule yields true, false otherwise
|
* @returns {boolean} True if the rule yields true, false otherwise
|
||||||
*/
|
*/
|
||||||
export function evalBoolean(rule, a, b) {
|
export function evalBoolean(rule, a, b) {
|
||||||
if (!rule) {
|
if (a === undefined) {
|
||||||
toastr.warning('The rule must be specified for the boolean comparison.', 'Invalid command');
|
throw new Error('Left operand is not provided');
|
||||||
throw new Error('Invalid command.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = false;
|
// If right-hand side was not provided, whe just check if the left side is truthy
|
||||||
|
if (b === undefined) {
|
||||||
|
switch (rule) {
|
||||||
|
case undefined:
|
||||||
|
case 'not': {
|
||||||
|
const resultOnTruthy = rule !== 'not';
|
||||||
|
if (isTrueBoolean(String(a))) return resultOnTruthy;
|
||||||
|
if (isFalseBoolean(String(a))) return !resultOnTruthy;
|
||||||
|
return a ? resultOnTruthy : !resultOnTruthy;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown boolean comparison rule for truthy check. If right operand is not provided, the rule must not provided or be 'not'. Provided: ${rule}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no rule was provided, we are implicitly using 'eq', as defined for the slash commands
|
||||||
|
rule ??= 'eq';
|
||||||
|
|
||||||
if (typeof a === 'number' && typeof b === 'number') {
|
if (typeof a === 'number' && typeof b === 'number') {
|
||||||
// only do numeric comparison if both operands are numbers
|
// only do numeric comparison if both operands are numbers
|
||||||
const aNumber = Number(a);
|
const aNumber = Number(a);
|
||||||
const bNumber = Number(b);
|
const bNumber = Number(b);
|
||||||
|
|
||||||
switch (rule) {
|
switch (rule) {
|
||||||
case 'not':
|
|
||||||
result = !aNumber;
|
|
||||||
break;
|
|
||||||
case 'gt':
|
case 'gt':
|
||||||
result = aNumber > bNumber;
|
return aNumber > bNumber;
|
||||||
break;
|
|
||||||
case 'gte':
|
case 'gte':
|
||||||
result = aNumber >= bNumber;
|
return aNumber >= bNumber;
|
||||||
break;
|
|
||||||
case 'lt':
|
case 'lt':
|
||||||
result = aNumber < bNumber;
|
return aNumber < bNumber;
|
||||||
break;
|
|
||||||
case 'lte':
|
case 'lte':
|
||||||
result = aNumber <= bNumber;
|
return aNumber <= bNumber;
|
||||||
break;
|
|
||||||
case 'eq':
|
case 'eq':
|
||||||
result = aNumber === bNumber;
|
return aNumber === bNumber;
|
||||||
break;
|
|
||||||
case 'neq':
|
case 'neq':
|
||||||
result = aNumber !== bNumber;
|
return aNumber !== bNumber;
|
||||||
break;
|
|
||||||
default:
|
|
||||||
toastr.error('Unknown boolean comparison rule for type number.', 'Invalid command');
|
|
||||||
throw new Error('Invalid command.');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// otherwise do case-insensitive string comparsion, stringify non-strings
|
|
||||||
let aString;
|
|
||||||
let bString;
|
|
||||||
if (typeof a == 'string') {
|
|
||||||
aString = a.toLowerCase();
|
|
||||||
} else {
|
|
||||||
aString = JSON.stringify(a).toLowerCase();
|
|
||||||
}
|
|
||||||
if (typeof b == 'string') {
|
|
||||||
bString = b.toLowerCase();
|
|
||||||
} else {
|
|
||||||
bString = JSON.stringify(b).toLowerCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (rule) {
|
|
||||||
case 'in':
|
case 'in':
|
||||||
result = aString.includes(bString);
|
|
||||||
break;
|
|
||||||
case 'nin':
|
case 'nin':
|
||||||
result = !aString.includes(bString);
|
// Fall through to string comparison. Otherwise you could not check if 12345 contains 45 for example.
|
||||||
break;
|
console.debug(`Boolean comparison rule '${rule}' is not supported for type number. Falling back to string comparison.`);
|
||||||
case 'eq':
|
|
||||||
result = aString === bString;
|
|
||||||
break;
|
|
||||||
case 'neq':
|
|
||||||
result = aString !== bString;
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
toastr.error('Unknown boolean comparison rule for type string.', 'Invalid /if command');
|
throw new Error(`Unknown boolean comparison rule for type number. Accepted: gt, gte, lt, lte, eq, neq. Provided: ${rule}`);
|
||||||
throw new Error('Invalid command.');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
// otherwise do case-insensitive string comparsion, stringify non-strings
|
||||||
|
let aString = (typeof a === 'string') ? a.toLowerCase() : JSON.stringify(a).toLowerCase();
|
||||||
|
let bString = (typeof b === 'string') ? b.toLowerCase() : JSON.stringify(b).toLowerCase();
|
||||||
|
|
||||||
|
switch (rule) {
|
||||||
|
case 'in':
|
||||||
|
return aString.includes(bString);
|
||||||
|
case 'nin':
|
||||||
|
return !aString.includes(bString);
|
||||||
|
case 'eq':
|
||||||
|
return aString === bString;
|
||||||
|
case 'neq':
|
||||||
|
return aString !== bString;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown boolean comparison rule for type number. Accepted: in, nin, eq, neq. Provided: ${rule}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -887,7 +910,35 @@ export function registerVariableCommands() {
|
|||||||
name: 'listvar',
|
name: 'listvar',
|
||||||
callback: listVariablesCallback,
|
callback: listVariablesCallback,
|
||||||
aliases: ['listchatvar'],
|
aliases: ['listchatvar'],
|
||||||
helpString: 'List registered chat variables.',
|
helpString: 'List registered chat variables. Displays variables in a popup by default. Use the <code>format</code> argument to change the output format.',
|
||||||
|
returns: 'JSON list of local variables',
|
||||||
|
namedArgumentList: [
|
||||||
|
SlashCommandNamedArgument.fromProps({
|
||||||
|
name: 'scope',
|
||||||
|
description: 'filter variables by scope',
|
||||||
|
typeList: [ARGUMENT_TYPE.STRING],
|
||||||
|
defaultValue: 'all',
|
||||||
|
isRequired: false,
|
||||||
|
forceEnum: true,
|
||||||
|
enumList: [
|
||||||
|
new SlashCommandEnumValue('all', 'All variables', enumTypes.enum, enumIcons.variable),
|
||||||
|
new SlashCommandEnumValue('local', 'Local variables', enumTypes.enum, enumIcons.localVariable),
|
||||||
|
new SlashCommandEnumValue('global', 'Global variables', enumTypes.enum, enumIcons.globalVariable),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
SlashCommandNamedArgument.fromProps({
|
||||||
|
name: 'format',
|
||||||
|
description: 'output format',
|
||||||
|
typeList: [ARGUMENT_TYPE.STRING],
|
||||||
|
isRequired: true,
|
||||||
|
forceEnum: true,
|
||||||
|
enumList: [
|
||||||
|
new SlashCommandEnumValue('popup', 'Show variables in a popup.', enumTypes.enum, enumIcons.default),
|
||||||
|
new SlashCommandEnumValue('chat', 'Post a system message to the chat.', enumTypes.enum, enumIcons.message),
|
||||||
|
new SlashCommandEnumValue('none', 'Just return the variables as a JSON list.', enumTypes.enum, enumIcons.array),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
}));
|
}));
|
||||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||||
name: 'setvar',
|
name: 'setvar',
|
||||||
@ -1264,32 +1315,36 @@ export function registerVariableCommands() {
|
|||||||
typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER],
|
typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER],
|
||||||
isRequired: true,
|
isRequired: true,
|
||||||
enumProvider: commonEnumProviders.variables('all'),
|
enumProvider: commonEnumProviders.variables('all'),
|
||||||
forceEnum: false,
|
|
||||||
}),
|
}),
|
||||||
SlashCommandNamedArgument.fromProps({
|
SlashCommandNamedArgument.fromProps({
|
||||||
name: 'right',
|
name: 'right',
|
||||||
description: 'right operand',
|
description: 'right operand',
|
||||||
typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER],
|
typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER],
|
||||||
isRequired: true,
|
|
||||||
enumProvider: commonEnumProviders.variables('all'),
|
enumProvider: commonEnumProviders.variables('all'),
|
||||||
forceEnum: false,
|
|
||||||
}),
|
}),
|
||||||
new SlashCommandNamedArgument(
|
SlashCommandNamedArgument.fromProps({
|
||||||
'rule', 'comparison rule', [ARGUMENT_TYPE.STRING], true, false, null, [
|
name: 'rule',
|
||||||
new SlashCommandEnumValue('gt', 'a > b'),
|
description: 'comparison rule',
|
||||||
new SlashCommandEnumValue('gte', 'a >= b'),
|
typeList: [ARGUMENT_TYPE.STRING],
|
||||||
new SlashCommandEnumValue('lt', 'a < b'),
|
defaultValue: 'eq',
|
||||||
new SlashCommandEnumValue('lte', 'a <= b'),
|
enumList: [
|
||||||
new SlashCommandEnumValue('eq', 'a == b'),
|
new SlashCommandEnumValue('eq', 'a == b (strings & numbers)'),
|
||||||
new SlashCommandEnumValue('neq', 'a !== b'),
|
new SlashCommandEnumValue('neq', 'a !== b (strings & numbers)'),
|
||||||
new SlashCommandEnumValue('not', '!a'),
|
new SlashCommandEnumValue('in', 'a includes b (strings & numbers as strings)'),
|
||||||
new SlashCommandEnumValue('in', 'a includes b'),
|
new SlashCommandEnumValue('nin', 'a not includes b (strings & numbers as strings)'),
|
||||||
new SlashCommandEnumValue('nin', 'a not includes b'),
|
new SlashCommandEnumValue('gt', 'a > b (numbers)'),
|
||||||
|
new SlashCommandEnumValue('gte', 'a >= b (numbers)'),
|
||||||
|
new SlashCommandEnumValue('lt', 'a < b (numbers)'),
|
||||||
|
new SlashCommandEnumValue('lte', 'a <= b (numbers)'),
|
||||||
|
new SlashCommandEnumValue('not', '!a (truthy)'),
|
||||||
],
|
],
|
||||||
),
|
forceEnum: true,
|
||||||
new SlashCommandNamedArgument(
|
}),
|
||||||
'else', 'command to execute if not true', [ARGUMENT_TYPE.CLOSURE, ARGUMENT_TYPE.SUBCOMMAND], false,
|
SlashCommandNamedArgument.fromProps({
|
||||||
),
|
name: 'else',
|
||||||
|
description: 'command to execute if not true',
|
||||||
|
typeList: [ARGUMENT_TYPE.CLOSURE, ARGUMENT_TYPE.SUBCOMMAND],
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
unnamedArgumentList: [
|
unnamedArgumentList: [
|
||||||
new SlashCommandArgument(
|
new SlashCommandArgument(
|
||||||
@ -1306,18 +1361,26 @@ export function registerVariableCommands() {
|
|||||||
<div>
|
<div>
|
||||||
Numeric values and string literals for left and right operands supported.
|
Numeric values and string literals for left and right operands supported.
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
If the rule is not provided, it defaults to <code>eq</code>.
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
If no right operand is provided, it defaults to checking the <code>left</code> value to be truthy.
|
||||||
|
A non-empty string or non-zero number is considered truthy, as is the value <code>true</code> or <code>on</code>.<br />
|
||||||
|
Only acceptable rules for no provided right operand are <code>not</code>, and no provided rule - which default to returning whether it is not or is truthy.
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<strong>Available rules:</strong>
|
<strong>Available rules:</strong>
|
||||||
<ul>
|
<ul>
|
||||||
<li>gt => a > b</li>
|
<li><code>eq</code> => a == b <small>(strings & numbers)</small></li>
|
||||||
<li>gte => a >= b</li>
|
<li><code>neq</code> => a !== b <small>(strings & numbers)</small></li>
|
||||||
<li>lt => a < b</li>
|
<li><code>in</code> => a includes b <small>(strings & numbers as strings)</small></li>
|
||||||
<li>lte => a <= b</li>
|
<li><code>nin</code> => a not includes b <small>(strings & numbers as strings)</small></li>
|
||||||
<li>eq => a == b</li>
|
<li><code>gt</code> => a > b <small>(numbers)</small></li>
|
||||||
<li>neq => a != b</li>
|
<li><code>gte</code> => a >= b <small>(numbers)</small></li>
|
||||||
<li>not => !a</li>
|
<li><code>lt</code> => a < b <small>(numbers)</small></li>
|
||||||
<li>in (strings) => a includes b</li>
|
<li><code>lte</code> => a <= b <small>(numbers)</small></li>
|
||||||
<li>nin (strings) => a not includes b</li>
|
<li><code>not</code> => !a <small>(truthy)</small></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@ -1327,6 +1390,17 @@ export function registerVariableCommands() {
|
|||||||
<pre><code class="language-stscript">/if left=score right=10 rule=gte "/speak You win"</code></pre>
|
<pre><code class="language-stscript">/if left=score right=10 rule=gte "/speak You win"</code></pre>
|
||||||
triggers a /speak command if the value of "score" is greater or equals 10.
|
triggers a /speak command if the value of "score" is greater or equals 10.
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<pre><code class="language-stscript">/if left={{lastMessage}} rule=in right=surprise {: /echo SURPISE! :}</code></pre>
|
||||||
|
executes a subcommand defined as a closure if the given value contains a specified word.
|
||||||
|
<li>
|
||||||
|
<pre><code class="language-stscript">/if left=myContent {: /echo My content had some content. :}</code></pre>
|
||||||
|
executes the defined subcommand, if the provided value of left is truthy (contains some kind of contant that is not empty or false)
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<pre><code class="language-stscript">/if left=tree right={{getvar::object}} {: /echo The object is a tree! :}</code></pre>
|
||||||
|
executes the defined subcommand, if the left and right values are equals.
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
@ -1342,32 +1416,38 @@ export function registerVariableCommands() {
|
|||||||
typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER],
|
typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER],
|
||||||
isRequired: true,
|
isRequired: true,
|
||||||
enumProvider: commonEnumProviders.variables('all'),
|
enumProvider: commonEnumProviders.variables('all'),
|
||||||
forceEnum: false,
|
|
||||||
}),
|
}),
|
||||||
SlashCommandNamedArgument.fromProps({
|
SlashCommandNamedArgument.fromProps({
|
||||||
name: 'right',
|
name: 'right',
|
||||||
description: 'right operand',
|
description: 'right operand',
|
||||||
typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER],
|
typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER],
|
||||||
isRequired: true,
|
|
||||||
enumProvider: commonEnumProviders.variables('all'),
|
enumProvider: commonEnumProviders.variables('all'),
|
||||||
forceEnum: false,
|
|
||||||
}),
|
}),
|
||||||
new SlashCommandNamedArgument(
|
SlashCommandNamedArgument.fromProps({
|
||||||
'rule', 'comparison rule', [ARGUMENT_TYPE.STRING], true, false, null, [
|
name: 'rule',
|
||||||
new SlashCommandEnumValue('gt', 'a > b'),
|
description: 'comparison rule',
|
||||||
new SlashCommandEnumValue('gte', 'a >= b'),
|
typeList: [ARGUMENT_TYPE.STRING],
|
||||||
new SlashCommandEnumValue('lt', 'a < b'),
|
defaultValue: 'eq',
|
||||||
new SlashCommandEnumValue('lte', 'a <= b'),
|
enumList: [
|
||||||
new SlashCommandEnumValue('eq', 'a == b'),
|
new SlashCommandEnumValue('eq', 'a == b (strings & numbers)'),
|
||||||
new SlashCommandEnumValue('neq', 'a !== b'),
|
new SlashCommandEnumValue('neq', 'a !== b (strings & numbers)'),
|
||||||
new SlashCommandEnumValue('not', '!a'),
|
new SlashCommandEnumValue('in', 'a includes b (strings & numbers as strings)'),
|
||||||
new SlashCommandEnumValue('in', 'a includes b'),
|
new SlashCommandEnumValue('nin', 'a not includes b (strings & numbers as strings)'),
|
||||||
new SlashCommandEnumValue('nin', 'a not includes b'),
|
new SlashCommandEnumValue('gt', 'a > b (numbers)'),
|
||||||
|
new SlashCommandEnumValue('gte', 'a >= b (numbers)'),
|
||||||
|
new SlashCommandEnumValue('lt', 'a < b (numbers)'),
|
||||||
|
new SlashCommandEnumValue('lte', 'a <= b (numbers)'),
|
||||||
|
new SlashCommandEnumValue('not', '!a (truthy)'),
|
||||||
],
|
],
|
||||||
),
|
forceEnum: true,
|
||||||
new SlashCommandNamedArgument(
|
}),
|
||||||
'guard', 'disable loop iteration limit', [ARGUMENT_TYPE.STRING], false, false, null, commonEnumProviders.boolean('onOff')(),
|
SlashCommandNamedArgument.fromProps({
|
||||||
),
|
name: 'guard',
|
||||||
|
description: 'disable loop iteration limit',
|
||||||
|
typeList: [ARGUMENT_TYPE.STRING],
|
||||||
|
defaultValue: 'off',
|
||||||
|
enumList: commonEnumProviders.boolean('onOff')(),
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
unnamedArgumentList: [
|
unnamedArgumentList: [
|
||||||
new SlashCommandArgument(
|
new SlashCommandArgument(
|
||||||
@ -1386,15 +1466,15 @@ export function registerVariableCommands() {
|
|||||||
<div>
|
<div>
|
||||||
<strong>Available rules:</strong>
|
<strong>Available rules:</strong>
|
||||||
<ul>
|
<ul>
|
||||||
<li>gt => a > b</li>
|
<li><code>eq</code> => a == b <small>(strings & numbers)</small></li>
|
||||||
<li>gte => a >= b</li>
|
<li><code>neq</code> => a !== b <small>(strings & numbers)</small></li>
|
||||||
<li>lt => a < b</li>
|
<li><code>in</code> => a includes b <small>(strings & numbers as strings)</small></li>
|
||||||
<li>lte => a <= b</li>
|
<li><code>nin</code> => a not includes b <small>(strings & numbers as strings)</small></li>
|
||||||
<li>eq => a == b</li>
|
<li><code>gt</code> => a > b <small>(numbers)</small></li>
|
||||||
<li>neq => a != b</li>
|
<li><code>gte</code> => a >= b <small>(numbers)</small></li>
|
||||||
<li>not => !a</li>
|
<li><code>lt</code> => a < b <small>(numbers)</small></li>
|
||||||
<li>in (strings) => a includes b</li>
|
<li><code>lte</code> => a <= b <small>(numbers)</small></li>
|
||||||
<li>nin (strings) => a not includes b</li>
|
<li><code>not</code> => !a <small>(truthy)</small></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@ -1404,7 +1484,11 @@ export function registerVariableCommands() {
|
|||||||
<pre><code class="language-stscript">/setvar key=i 0 | /while left=i right=10 rule=lte "/addvar key=i 1"</code></pre>
|
<pre><code class="language-stscript">/setvar key=i 0 | /while left=i right=10 rule=lte "/addvar key=i 1"</code></pre>
|
||||||
adds 1 to the value of "i" until it reaches 10.
|
adds 1 to the value of "i" until it reaches 10.
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
<li>
|
||||||
|
<pre><code class="language-stscript">/while left={{getvar::currentword}} {: /setvar key=currentword {: /do-something-and-return :}() | /echo The current work is "{{getvar::currentword}}" :}</code></pre>
|
||||||
|
executes the defined subcommand as long as the "currentword" variable is truthy (has any content that is not false/empty)
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
Loops are limited to 100 iterations by default, pass <code>guard=off</code> to disable.
|
Loops are limited to 100 iterations by default, pass <code>guard=off</code> to disable.
|
||||||
@ -1519,7 +1603,7 @@ export function registerVariableCommands() {
|
|||||||
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
|
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
|
||||||
isRequired: true,
|
isRequired: true,
|
||||||
acceptsMultiple: true,
|
acceptsMultiple: true,
|
||||||
enumProvider: (executor, scope)=>{
|
enumProvider: (executor, scope) => {
|
||||||
const vars = commonEnumProviders.variables('all')(executor, scope);
|
const vars = commonEnumProviders.variables('all')(executor, scope);
|
||||||
vars.push(
|
vars.push(
|
||||||
new SlashCommandEnumValue(
|
new SlashCommandEnumValue(
|
||||||
@ -1527,16 +1611,16 @@ export function registerVariableCommands() {
|
|||||||
null,
|
null,
|
||||||
enumTypes.variable,
|
enumTypes.variable,
|
||||||
enumIcons.variable,
|
enumIcons.variable,
|
||||||
(input)=>/^\w*$/.test(input),
|
(input) => /^\w*$/.test(input),
|
||||||
(input)=>input,
|
(input) => input,
|
||||||
),
|
),
|
||||||
new SlashCommandEnumValue(
|
new SlashCommandEnumValue(
|
||||||
'any number',
|
'any number',
|
||||||
null,
|
null,
|
||||||
enumTypes.number,
|
enumTypes.number,
|
||||||
enumIcons.number,
|
enumIcons.number,
|
||||||
(input)=>input == '' || !Number.isNaN(Number(input)),
|
(input) => input == '' || !Number.isNaN(Number(input)),
|
||||||
(input)=>input,
|
(input) => input,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return vars;
|
return vars;
|
||||||
|
@ -3154,6 +3154,26 @@ grammarly-extension {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.alternate_greeting details {
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alternate_greeting summary {
|
||||||
|
list-style-position: outside;
|
||||||
|
margin-left: 1em;
|
||||||
|
padding-left: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alternate_greeting textarea {
|
||||||
|
field-sizing: content;
|
||||||
|
max-height: 50dvh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alternate_greeting summary::marker,
|
||||||
|
.alternate_greeting summary strong {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
#rm_characters_block .form_create_bottom_buttons_block {
|
#rm_characters_block .form_create_bottom_buttons_block {
|
||||||
justify-content: space-evenly !important;
|
justify-content: space-evenly !important;
|
||||||
flex-grow: 0;
|
flex-grow: 0;
|
||||||
@ -3245,8 +3265,9 @@ grammarly-extension {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.wide_dialogue_popup {
|
.wide_dialogue_popup {
|
||||||
aspect-ratio: 1 / 1;
|
/* FIXME: Chrome 129 broke max-height for aspect-ratio sized elements */
|
||||||
width: unset !important;
|
/* aspect-ratio: 1 / 1; */
|
||||||
|
/* width: unset !important; */
|
||||||
min-width: var(--sheldWidth);
|
min-width: var(--sheldWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5365,13 +5386,6 @@ body:not(.movingUI) .drawer-content.maximized {
|
|||||||
|
|
||||||
/* Jank mobile support for gallery and future draggables */
|
/* Jank mobile support for gallery and future draggables */
|
||||||
@media screen and (max-width: 1000px) {
|
@media screen and (max-width: 1000px) {
|
||||||
#gallery {
|
|
||||||
display: block;
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
z-index: 9999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.draggable {
|
.draggable {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
|
@ -347,6 +347,8 @@ async function migrateSystemPrompts() {
|
|||||||
if (fs.existsSync(migrateMarker)) {
|
if (fs.existsSync(migrateMarker)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
const backupsPath = path.join(directory.backups, '_sysprompt');
|
||||||
|
fs.mkdirSync(backupsPath, { recursive: true });
|
||||||
const defaultPrompts = await getDefaultSystemPrompts();
|
const defaultPrompts = await getDefaultSystemPrompts();
|
||||||
const instucts = fs.readdirSync(directory.instruct);
|
const instucts = fs.readdirSync(directory.instruct);
|
||||||
let migratedPrompts = [];
|
let migratedPrompts = [];
|
||||||
@ -356,6 +358,8 @@ async function migrateSystemPrompts() {
|
|||||||
if (path.extname(instruct) === '.json' && !fs.existsSync(sysPromptPath)) {
|
if (path.extname(instruct) === '.json' && !fs.existsSync(sysPromptPath)) {
|
||||||
const instructData = JSON.parse(fs.readFileSync(instructPath, 'utf8'));
|
const instructData = JSON.parse(fs.readFileSync(instructPath, 'utf8'));
|
||||||
if ('system_prompt' in instructData && 'name' in instructData) {
|
if ('system_prompt' in instructData && 'name' in instructData) {
|
||||||
|
const backupPath = path.join(backupsPath, `${instructData.name}.json`);
|
||||||
|
fs.cpSync(instructPath, backupPath, { force: true });
|
||||||
const syspromptData = { name: instructData.name, content: instructData.system_prompt };
|
const syspromptData = { name: instructData.name, content: instructData.system_prompt };
|
||||||
migratedPrompts.push(syspromptData);
|
migratedPrompts.push(syspromptData);
|
||||||
delete instructData.system_prompt;
|
delete instructData.system_prompt;
|
||||||
@ -374,8 +378,8 @@ async function migrateSystemPrompts() {
|
|||||||
console.log(`Migrated system prompt ${sysPromptData.name} for ${directory.root.split(path.sep).pop()}`);
|
console.log(`Migrated system prompt ${sysPromptData.name} for ${directory.root.split(path.sep).pop()}`);
|
||||||
}
|
}
|
||||||
writeFileAtomicSync(migrateMarker, '');
|
writeFileAtomicSync(migrateMarker, '');
|
||||||
} catch {
|
} catch (error) {
|
||||||
// Ignore errors
|
console.error('Error migrating system prompts:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user