Resolve conflict
| @@ -9,13 +9,34 @@ | |||
| 9 | "magical forest" | 9 | "magical forest" |
| 10 | ], | 10 | ], |
| 11 | "keysecondary": [], | 11 | "keysecondary": [], |
| 12 | "comment": "", | 12 | "comment": "eldoria", |
| 13 | "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.*", | 13 | "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.*", |
| 14 | "constant": false, | 14 | "constant": false, |
| 15 | "selective": false, | 15 | "selective": true, |
| 16 | "order": 100, | 16 | "order": 100, |
| 17 | "position": 0, | 17 | "position": 0, |
| 18 | "disable": false | 18 | "disable": false, |
| 19 | "displayIndex": 0, | ||
| 20 | "addMemo": true, | ||
| 21 | "group": "", | ||
| 22 | "groupOverride": false, | ||
| 23 | "groupWeight": 100, | ||
| 24 | "sticky": 0, | ||
| 25 | "cooldown": 0, | ||
| 26 | "delay": 0, | ||
| 27 | "probability": 100, | ||
| 28 | "depth": 4, | ||
| 29 | "useProbability": true, | ||
| 30 | "role": null, | ||
| 31 | "vectorized": false, | ||
| 32 | "excludeRecursion": false, | ||
| 33 | "preventRecursion": false, | ||
| 34 | "delayUntilRecursion": false, | ||
| 35 | "scanDepth": null, | ||
| 36 | "caseSensitive": null, | ||
| 37 | "matchWholeWords": null, | ||
| 38 | "useGroupScoring": null, | ||
| 39 | "automationId": "" | ||
| 19 | }, | 40 | }, |
| 20 | "1": { | 41 | "1": { |
| 21 | "uid": 1, | 42 | "uid": 1, |
| @@ -27,13 +48,34 @@ | |||
| 27 | "beasts" | 48 | "beasts" |
| 28 | ], | 49 | ], |
| 29 | "keysecondary": [], | 50 | "keysecondary": [], |
| 30 | "comment": "", | 51 | "comment": "shadowfang", |
| 31 | "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.\"", | 52 | "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.\"", |
| 32 | "constant": false, | 53 | "constant": false, |
| 33 | "selective": false, | 54 | "selective": true, |
| 34 | "order": 100, | 55 | "order": 100, |
| 35 | "position": 0, | 56 | "position": 0, |
| 36 | "disable": false | 57 | "disable": false, |
| 58 | "displayIndex": 1, | ||
| 59 | "addMemo": true, | ||
| 60 | "group": "", | ||
| 61 | "groupOverride": false, | ||
| 62 | "groupWeight": 100, | ||
| 63 | "sticky": 0, | ||
| 64 | "cooldown": 0, | ||
| 65 | "delay": 0, | ||
| 66 | "probability": 100, | ||
| 67 | "depth": 4, | ||
| 68 | "useProbability": true, | ||
| 69 | "role": null, | ||
| 70 | "vectorized": false, | ||
| 71 | "excludeRecursion": false, | ||
| 72 | "preventRecursion": false, | ||
| 73 | "delayUntilRecursion": false, | ||
| 74 | "scanDepth": null, | ||
| 75 | "caseSensitive": null, | ||
| 76 | "matchWholeWords": null, | ||
| 77 | "useGroupScoring": null, | ||
| 78 | "automationId": "" | ||
| 37 | }, | 79 | }, |
| 38 | "2": { | 80 | "2": { |
| 39 | "uid": 2, | 81 | "uid": 2, |
| @@ -43,13 +85,34 @@ | |||
| 43 | "refuge" | 85 | "refuge" |
| 44 | ], | 86 | ], |
| 45 | "keysecondary": [], | 87 | "keysecondary": [], |
| 46 | "comment": "", | 88 | "comment": "glade", |
| 47 | "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.*", | 89 | "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.*", |
| 48 | "constant": false, | 90 | "constant": false, |
| 49 | "selective": false, | 91 | "selective": true, |
| 50 | "order": 100, | 92 | "order": 100, |
| 51 | "position": 0, | 93 | "position": 0, |
| 52 | "disable": false | 94 | "disable": false, |
| 95 | "displayIndex": 2, | ||
| 96 | "addMemo": true, | ||
| 97 | "group": "", | ||
| 98 | "groupOverride": false, | ||
| 99 | "groupWeight": 100, | ||
| 100 | "sticky": 0, | ||
| 101 | "cooldown": 0, | ||
| 102 | "delay": 0, | ||
| 103 | "probability": 100, | ||
| 104 | "depth": 4, | ||
| 105 | "useProbability": true, | ||
| 106 | "role": null, | ||
| 107 | "vectorized": false, | ||
| 108 | "excludeRecursion": false, | ||
| 109 | "preventRecursion": false, | ||
| 110 | "delayUntilRecursion": false, | ||
| 111 | "scanDepth": null, | ||
| 112 | "caseSensitive": null, | ||
| 113 | "matchWholeWords": null, | ||
| 114 | "useGroupScoring": null, | ||
| 115 | "automationId": "" | ||
| 53 | }, | 116 | }, |
| 54 | "3": { | 117 | "3": { |
| 55 | "uid": 3, | 118 | "uid": 3, |
| @@ -59,13 +122,34 @@ | |||
| 59 | "ability" | 122 | "ability" |
| 60 | ], | 123 | ], |
| 61 | "keysecondary": [], | 124 | "keysecondary": [], |
| 62 | "comment": "", | 125 | "comment": "power", |
| 63 | "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.*", | 126 | "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.*", |
| 64 | "constant": false, | 127 | "constant": false, |
| 65 | "selective": false, | 128 | "selective": true, |
| 66 | "order": 100, | 129 | "order": 100, |
| 67 | "position": 0, | 130 | "position": 0, |
| 68 | "disable": false | 131 | "disable": false, |
| 132 | "displayIndex": 3, | ||
| 133 | "addMemo": true, | ||
| 134 | "group": "", | ||
| 135 | "groupOverride": false, | ||
| 136 | "groupWeight": 100, | ||
| 137 | "sticky": 0, | ||
| 138 | "cooldown": 0, | ||
| 139 | "delay": 0, | ||
| 140 | "probability": 100, | ||
| 141 | "depth": 4, | ||
| 142 | "useProbability": true, | ||
| 143 | "role": null, | ||
| 144 | "vectorized": false, | ||
| 145 | "excludeRecursion": false, | ||
| 146 | "preventRecursion": false, | ||
| 147 | "delayUntilRecursion": false, | ||
| 148 | "scanDepth": null, | ||
| 149 | "caseSensitive": null, | ||
| 150 | "matchWholeWords": null, | ||
| 151 | "useGroupScoring": null, | ||
| 152 | "automationId": "" | ||
| 69 | } | 153 | } |
| 70 | } | 154 | } |
| 71 | } | 155 | } |
Binary file
| @@ -1310,7 +1310,7 @@ | |||
| 1310 | </div> | 1310 | </div> |
| 1311 | </div> | 1311 | </div> |
| 1312 | 1312 | ||
| 1313 | <div data-tg-type="koboldcpp, aphrodite" id="xtc_block" class="wide100p"> | 1313 | <div data-tg-type="koboldcpp, aphrodite, tabby, ooba" id="xtc_block" class="wide100p"> |
| 1314 | <h4 class="wide100p textAlignCenter"> | 1314 | <h4 class="wide100p textAlignCenter"> |
| 1315 | <label data-i18n="Exclude Top Choices (XTC)">Exclude Top Choices (XTC)</label> | 1315 | <label data-i18n="Exclude Top Choices (XTC)">Exclude Top Choices (XTC)</label> |
| 1316 | <a href="https://github.com/oobabooga/text-generation-webui/pull/6335" target="_blank"> | 1316 | <a href="https://github.com/oobabooga/text-generation-webui/pull/6335" target="_blank"> |
| @@ -1679,6 +1679,10 @@ | |||
| 1679 | Ooba only. Determines the order of samplers. | 1679 | Ooba only. Determines the order of samplers. |
| 1680 | </div> | 1680 | </div> |
| 1681 | <div id="sampler_priority_container" class="prompt_order"> | 1681 | <div id="sampler_priority_container" class="prompt_order"> |
| 1682 | <div data-name="repetition_penalty" draggable="true"><span>Repetition Penalty</span><small></small></div> | ||
| 1683 | <div data-name="presence_penalty" draggable="true"><span>Presence Penalty</span><small></small></div> | ||
| 1684 | <div data-name="frequency_penalty" draggable="true"><span>Frequency Penalty</span><small></small></div> | ||
| 1685 | <div data-name="dry" draggable="true"><span>DRY</span><small></small></div> | ||
| 1682 | <div data-name="temperature" draggable="true"><span>Temperature</span><small></small></div> | 1686 | <div data-name="temperature" draggable="true"><span>Temperature</span><small></small></div> |
| 1683 | <div data-name="dynamic_temperature" draggable="true"><span>Dynamic Temperature</span><small></small></div> | 1687 | <div data-name="dynamic_temperature" draggable="true"><span>Dynamic Temperature</span><small></small></div> |
| 1684 | <div data-name="quadratic_sampling" draggable="true"><span>Quadratic / Smooth Sampling</span><small></small></div> | 1688 | <div data-name="quadratic_sampling" draggable="true"><span>Quadratic / Smooth Sampling</span><small></small></div> |
| @@ -1691,6 +1695,9 @@ | |||
| 1691 | <div data-name="top_a" draggable="true"><span>Top A</span><small></small></div> | 1695 | <div data-name="top_a" draggable="true"><span>Top A</span><small></small></div> |
| 1692 | <div data-name="min_p" draggable="true"><span>Min P</span><small></small></div> | 1696 | <div data-name="min_p" draggable="true"><span>Min P</span><small></small></div> |
| 1693 | <div data-name="mirostat" draggable="true"><span>Mirostat</span><small></small></div> | 1697 | <div data-name="mirostat" draggable="true"><span>Mirostat</span><small></small></div> |
| 1698 | <div data-name="xtc" draggable="true"><span>XTC</span><small></small></div> | ||
| 1699 | <div data-name="encoder_repetition_penalty" draggable="true"><span>Encoder Repetition Penalty</span><small></small></div> | ||
| 1700 | <div data-name="no_repeat_ngram" draggable="true"><span>No Repeat Ngram</span><small></small></div> | ||
| 1694 | </div> | 1701 | </div> |
| 1695 | <div id="textgenerationwebui_default_order" class="menu_button menu_button_icon"> | 1702 | <div id="textgenerationwebui_default_order" class="menu_button menu_button_icon"> |
| 1696 | <span data-i18n="Load default order">Load default order</span> | 1703 | <span data-i18n="Load default order">Load default order</span> |
| @@ -6090,7 +6097,10 @@ | |||
| 6090 | <div class="alternate_grettings flexFlowColumn flex-container"> | 6097 | <div class="alternate_grettings flexFlowColumn flex-container"> |
| 6091 | <div class="title_restorable"> | 6098 | <div class="title_restorable"> |
| 6092 | <h3><span data-i18n="Alternate Greetings" class="mdhotkey_location">Alternate Greetings</span></h3> | 6099 | <h3><span data-i18n="Alternate Greetings" class="mdhotkey_location">Alternate Greetings</span></h3> |
| 6093 | <div title="Add" class="menu_button fa-solid fa-plus add_alternate_greeting" data-i18n="[title]Add"></div> | 6100 | <div class="menu_button menu_button_icon add_alternate_greeting"> |
| 6101 | <i class="fa-solid fa-plus"></i> | ||
| 6102 | <span data-i18n="Add">Add</span> | ||
| 6103 | </div> | ||
| 6094 | </div> | 6104 | </div> |
| 6095 | <small class="justifyLeft" data-i18n="Alternate_Greetings_desc"> | 6105 | <small class="justifyLeft" data-i18n="Alternate_Greetings_desc"> |
| 6096 | These will be displayed as swipes on the first message when starting a new chat. | 6106 | These will be displayed as swipes on the first message when starting a new chat. |
| @@ -6106,11 +6116,18 @@ | |||
| 6106 | </div> | 6116 | </div> |
| 6107 | <div id="alternate_greeting_form_template" class="template_element"> | 6117 | <div id="alternate_greeting_form_template" class="template_element"> |
| 6108 | <div class="alternate_greeting"> | 6118 | <div class="alternate_greeting"> |
| 6109 | <div class="title_restorable"> | 6119 | <details open> |
| 6110 | <strong><span data-i18n="Alternate Greeting #">Alternate Greeting #</span><span class="greeting_index"></span></strong> | 6120 | <summary> |
| 6111 | <div class="menu_button fa-solid fa-trash-alt delete_alternate_greeting"></div> | 6121 | <div class="title_restorable"> |
| 6112 | </div> | 6122 | <strong><span data-i18n="Alternate Greeting #">Alternate Greeting #</span><span class="greeting_index"></span></strong> |
| 6113 | <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> | 6123 | <div class="menu_button menu_button_icon delete_alternate_greeting"> |
| 6124 | <i class="fa-solid fa-trash-alt"></i> | ||
| 6125 | <span data-i18n="Delete">Delete</span> | ||
| 6126 | </div> | ||
| 6127 | </div> | ||
| 6128 | </summary> | ||
| 6129 | <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> | ||
| 6130 | </details> | ||
| 6114 | </div> | 6131 | </div> |
| 6115 | </div> | 6132 | </div> |
| 6116 | 6133 | ||
| @@ -1719,7 +1719,6 @@ | |||
| 1719 | "Model": "Модель", | 1719 | "Model": "Модель", |
| 1720 | "Proxy Preset": "Пресет для прокси", | 1720 | "Proxy Preset": "Пресет для прокси", |
| 1721 | "Enter a name:": "Введите название:", | 1721 | "Enter a name:": "Введите название:", |
| 1722 | "Enter a new name": "Введите новое название", | ||
| 1723 | "Are you sure you want to delete the selected profile?": "Вы точно хотите удалить выбранный профиль?", | 1722 | "Are you sure you want to delete the selected profile?": "Вы точно хотите удалить выбранный профиль?", |
| 1724 | "instruct_enabled": "Включить Instruct-режим", | 1723 | "instruct_enabled": "Включить Instruct-режим", |
| 1725 | "Instruct Template": "Шаблон Instruct-режима", | 1724 | "Instruct Template": "Шаблон Instruct-режима", |
| @@ -1746,5 +1745,7 @@ | |||
| 1746 | "Any contents here will replace the default Post-History Instructions used for this character. (v2 spec: post_history_instructions)": "Содержимое этого поля заменит стандартные Инструкции после истории, применяемые для этого персонажа. (v2 spec: post_history_instructions)", | 1745 | "Any contents here will replace the default Post-History Instructions used for this character. (v2 spec: post_history_instructions)": "Содержимое этого поля заменит стандартные Инструкции после истории, применяемые для этого персонажа. (v2 spec: post_history_instructions)", |
| 1747 | "None (disabled)": "Нигде (откл.)", | 1746 | "None (disabled)": "Нигде (откл.)", |
| 1748 | "Markdown Hotkeys": "Горячие клавиши для разметки", | 1747 | "Markdown Hotkeys": "Горячие клавиши для разметки", |
| 1749 | "markdown_hotkeys_desc": "Включить горячие клавиши для вставки символов разметки в некоторых полях ввода. См. '/help hotkeys'." | 1748 | "markdown_hotkeys_desc": "Включить горячие клавиши для вставки символов разметки в некоторых полях ввода. См. '/help hotkeys'.", |
| 1749 | "Save and Update": "Сохранить и обновить", | ||
| 1750 | "Profile name:": "Название профиля:" | ||
| 1750 | } | 1751 | } |
| @@ -158,7 +158,7 @@ import { | |||
| 158 | } from './scripts/utils.js'; | 158 | } from './scripts/utils.js'; |
| 159 | import { debounce_timeout } from './scripts/constants.js'; | 159 | import { debounce_timeout } from './scripts/constants.js'; |
| 160 | 160 | ||
| 161 | import { ModuleWorkerWrapper, doDailyExtensionUpdatesCheck, extension_settings, getContext, loadExtensionSettings, renderExtensionTemplate, renderExtensionTemplateAsync, runGenerationInterceptors, saveMetadataDebounced, writeExtensionField } from './scripts/extensions.js'; | 161 | import { ModuleWorkerWrapper, doDailyExtensionUpdatesCheck, extension_settings, getContext, initExtensions, loadExtensionSettings, renderExtensionTemplate, renderExtensionTemplateAsync, runGenerationInterceptors, saveMetadataDebounced, writeExtensionField } from './scripts/extensions.js'; |
| 162 | import { COMMENT_NAME_DEFAULT, executeSlashCommands, executeSlashCommandsOnChatInput, executeSlashCommandsWithOptions, getSlashCommandsHelp, initDefaultSlashCommands, isExecutingCommandsFromChatInput, pauseScriptExecution, processChatSlashCommands, registerSlashCommand, stopScriptExecution } from './scripts/slash-commands.js'; | 162 | import { COMMENT_NAME_DEFAULT, executeSlashCommands, executeSlashCommandsOnChatInput, executeSlashCommandsWithOptions, getSlashCommandsHelp, initDefaultSlashCommands, isExecutingCommandsFromChatInput, pauseScriptExecution, processChatSlashCommands, registerSlashCommand, stopScriptExecution } from './scripts/slash-commands.js'; |
| 163 | import { | 163 | import { |
| 164 | tag_map, | 164 | tag_map, |
| @@ -244,6 +244,7 @@ import { commonEnumProviders, enumIcons } from './scripts/slash-commands/SlashCo | |||
| 244 | import { initInputMarkdown } from './scripts/input-md-formatting.js'; | 244 | import { initInputMarkdown } from './scripts/input-md-formatting.js'; |
| 245 | import { AbortReason } from './scripts/util/AbortReason.js'; | 245 | import { AbortReason } from './scripts/util/AbortReason.js'; |
| 246 | import { initSystemPrompts } from './scripts/sysprompt.js'; | 246 | import { initSystemPrompts } from './scripts/sysprompt.js'; |
| 247 | import { registerExtensionSlashCommands as initExtensionSlashCommands } from './scripts/extensions-slashcommands.js'; | ||
| 247 | 248 | ||
| 248 | //exporting functions and vars for mods | 249 | //exporting functions and vars for mods |
| 249 | export { | 250 | export { |
| @@ -935,6 +936,8 @@ async function firstLoadInit() { | |||
| 935 | initTextGenModels(); | 936 | initTextGenModels(); |
| 936 | initOpenAI(); | 937 | initOpenAI(); |
| 937 | initSystemPrompts(); | 938 | initSystemPrompts(); |
| 939 | initExtensions(); | ||
| 940 | initExtensionSlashCommands(); | ||
| 938 | await initPresetManager(); | 941 | await initPresetManager(); |
| 939 | await getSystemMessages(); | 942 | await getSystemMessages(); |
| 940 | sendSystemMessage(system_message_types.WELCOME); | 943 | sendSystemMessage(system_message_types.WELCOME); |
| @@ -2573,7 +2576,7 @@ export function getStoppingStrings(isImpersonate, isContinue) { | |||
| 2573 | result.unshift('\n'); | 2576 | result.unshift('\n'); |
| 2574 | } | 2577 | } |
| 2575 | 2578 | ||
| 2576 | return result.filter(onlyUnique); | 2579 | return result.filter(x => x).filter(onlyUnique); |
| 2577 | } | 2580 | } |
| 2578 | 2581 | ||
| 2579 | /** | 2582 | /** |
| @@ -4863,7 +4866,7 @@ export function getMaxContextSize(overrideResponseLength = null) { | |||
| 4863 | this_max_context = Math.min(max_context, 8192); | 4866 | this_max_context = Math.min(max_context, 8192); |
| 4864 | 4867 | ||
| 4865 | // Added special tokens and whatnot | 4868 | // Added special tokens and whatnot |
| 4866 | this_max_context -= 1; | 4869 | this_max_context -= 10; |
| 4867 | } | 4870 | } |
| 4868 | 4871 | ||
| 4869 | this_max_context = this_max_context - (overrideResponseLength || amount_gen); | 4872 | this_max_context = this_max_context - (overrideResponseLength || amount_gen); |
| @@ -7390,7 +7393,7 @@ function onScenarioOverrideRemoveClick() { | |||
| 7390 | */ | 7393 | */ |
| 7391 | export function callPopup(text, type, inputValue = '', { okButton, rows, wide, wider, large, allowHorizontalScrolling, allowVerticalScrolling, cropAspect } = {}) { | 7394 | export function callPopup(text, type, inputValue = '', { okButton, rows, wide, wider, large, allowHorizontalScrolling, allowVerticalScrolling, cropAspect } = {}) { |
| 7392 | function getOkButtonText() { | 7395 | function getOkButtonText() { |
| 7393 | if (['text', 'alternate_greeting', 'char_not_selected'].includes(popup_type)) { | 7396 | if (['text', 'char_not_selected'].includes(popup_type)) { |
| 7394 | $dialoguePopupCancel.css('display', 'none'); | 7397 | $dialoguePopupCancel.css('display', 'none'); |
| 7395 | return okButton ?? 'Ok'; | 7398 | return okButton ?? 'Ok'; |
| 7396 | } else if (['delete_extension'].includes(popup_type)) { | 7399 | } else if (['delete_extension'].includes(popup_type)) { |
| @@ -7827,24 +7830,42 @@ function openAlternateGreetings() { | |||
| 7827 | 7830 | ||
| 7828 | const template = $('#alternate_greetings_template .alternate_grettings').clone(); | 7831 | const template = $('#alternate_greetings_template .alternate_grettings').clone(); |
| 7829 | const getArray = () => menu_type == 'create' ? create_save.alternate_greetings : characters[chid].data.alternate_greetings; | 7832 | const getArray = () => menu_type == 'create' ? create_save.alternate_greetings : characters[chid].data.alternate_greetings; |
| 7833 | const popup = new Popup(template, POPUP_TYPE.TEXT, '', { | ||
| 7834 | wide: true, | ||
| 7835 | large: true, | ||
| 7836 | allowVerticalScrolling: true, | ||
| 7837 | onClose: async () => { | ||
| 7838 | if (menu_type !== 'create') { | ||
| 7839 | await createOrEditCharacter(); | ||
| 7840 | } | ||
| 7841 | } | ||
| 7842 | }); | ||
| 7830 | 7843 | ||
| 7831 | for (let index = 0; index < getArray().length; index++) { | 7844 | for (let index = 0; index < getArray().length; index++) { |
| 7832 | addAlternateGreeting(template, getArray()[index], index, getArray); | 7845 | addAlternateGreeting(template, getArray()[index], index, getArray, popup); |
| 7833 | } | 7846 | } |
| 7834 | 7847 | ||
| 7835 | template.find('.add_alternate_greeting').on('click', function () { | 7848 | template.find('.add_alternate_greeting').on('click', function () { |
| 7836 | const array = getArray(); | 7849 | const array = getArray(); |
| 7837 | const index = array.length; | 7850 | const index = array.length; |
| 7838 | array.push(''); | 7851 | array.push(''); |
| 7839 | addAlternateGreeting(template, '', index, getArray); | 7852 | addAlternateGreeting(template, '', index, getArray, popup); |
| 7840 | updateAlternateGreetingsHintVisibility(template); | 7853 | updateAlternateGreetingsHintVisibility(template); |
| 7841 | }); | 7854 | }); |
| 7842 | 7855 | ||
| 7856 | popup.show(); | ||
| 7843 | updateAlternateGreetingsHintVisibility(template); | 7857 | updateAlternateGreetingsHintVisibility(template); |
| 7844 | callPopup(template, 'alternate_greeting', '', { wide: true, large: true }); | ||
| 7845 | } | 7858 | } |
| 7846 | 7859 | ||
| 7847 | function addAlternateGreeting(template, greeting, index, getArray) { | 7860 | /** |
| 7861 | * Adds an alternate greeting to the template. | ||
| 7862 | * @param {JQuery<HTMLElement>} template | ||
| 7863 | * @param {string} greeting | ||
| 7864 | * @param {number} index | ||
| 7865 | * @param {() => any[]} getArray | ||
| 7866 | * @param {Popup} popup | ||
| 7867 | */ | ||
| 7868 | function addAlternateGreeting(template, greeting, index, getArray, popup) { | ||
| 7848 | const greetingBlock = $('#alternate_greeting_form_template .alternate_greeting').clone(); | 7869 | const greetingBlock = $('#alternate_greeting_form_template .alternate_greeting').clone(); |
| 7849 | greetingBlock.find('.alternate_greeting_text').on('input', async function () { | 7870 | greetingBlock.find('.alternate_greeting_text').on('input', async function () { |
| 7850 | const value = $(this).val(); | 7871 | const value = $(this).val(); |
| @@ -7852,11 +7873,16 @@ function addAlternateGreeting(template, greeting, index, getArray) { | |||
| 7852 | array[index] = value; | 7873 | array[index] = value; |
| 7853 | }).val(greeting); | 7874 | }).val(greeting); |
| 7854 | greetingBlock.find('.greeting_index').text(index + 1); | 7875 | greetingBlock.find('.greeting_index').text(index + 1); |
| 7855 | greetingBlock.find('.delete_alternate_greeting').on('click', async function () { | 7876 | greetingBlock.find('.delete_alternate_greeting').on('click', async function (event) { |
| 7877 | event.preventDefault(); | ||
| 7878 | event.stopPropagation(); | ||
| 7879 | |||
| 7856 | if (confirm(t`Are you sure you want to delete this alternate greeting?`)) { | 7880 | if (confirm(t`Are you sure you want to delete this alternate greeting?`)) { |
| 7857 | const array = getArray(); | 7881 | const array = getArray(); |
| 7858 | array.splice(index, 1); | 7882 | array.splice(index, 1); |
| 7883 | |||
| 7859 | // We need to reopen the popup to update the index numbers | 7884 | // We need to reopen the popup to update the index numbers |
| 7885 | await popup.complete(POPUP_RESULT.AFFIRMATIVE); | ||
| 7860 | openAlternateGreetings(); | 7886 | openAlternateGreetings(); |
| 7861 | } | 7887 | } |
| 7862 | }); | 7888 | }); |
| @@ -9574,9 +9600,6 @@ jQuery(async function () { | |||
| 9574 | }, 2000); | 9600 | }, 2000); |
| 9575 | } | 9601 | } |
| 9576 | } | 9602 | } |
| 9577 | if (popup_type == 'alternate_greeting' && menu_type !== 'create') { | ||
| 9578 | createOrEditCharacter(); | ||
| 9579 | } | ||
| 9580 | 9603 | ||
| 9581 | if (dialogueResolve) { | 9604 | if (dialogueResolve) { |
| 9582 | if (popup_type == 'input') { | 9605 | if (popup_type == 'input') { |
| @@ -0,0 +1,320 @@ | |||
| 1 | import { disableExtension, enableExtension, extension_settings, extensionNames } from './extensions.js'; | ||
| 2 | import { SlashCommand } from './slash-commands/SlashCommand.js'; | ||
| 3 | import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js'; | ||
| 4 | import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js'; | ||
| 5 | import { commonEnumProviders } from './slash-commands/SlashCommandCommonEnumsProvider.js'; | ||
| 6 | import { enumTypes, SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js'; | ||
| 7 | import { SlashCommandParser } from './slash-commands/SlashCommandParser.js'; | ||
| 8 | import { equalsIgnoreCaseAndAccents, isFalseBoolean, isTrueBoolean } from './utils.js'; | ||
| 9 | |||
| 10 | /** | ||
| 11 | * @param {'enable' | 'disable' | 'toggle'} action - The action to perform on the extension | ||
| 12 | * @returns {(args: {[key: string]: string | SlashCommandClosure}, extensionName: string | SlashCommandClosure) => Promise<string>} | ||
| 13 | */ | ||
| 14 | function getExtensionActionCallback(action) { | ||
| 15 | return async (args, extensionName) => { | ||
| 16 | if (args?.reload instanceof SlashCommandClosure) throw new Error('\'reload\' argument cannot be a closure.'); | ||
| 17 | if (typeof extensionName !== 'string') throw new Error('Extension name must be a string. Closures or arrays are not allowed.'); | ||
| 18 | if (!extensionName) { | ||
| 19 | toastr.warning(`Extension name must be provided as an argument to ${action} this extension.`); | ||
| 20 | return ''; | ||
| 21 | } | ||
| 22 | |||
| 23 | const reload = !isFalseBoolean(args?.reload); | ||
| 24 | const internalExtensionName = findExtension(extensionName); | ||
| 25 | if (!internalExtensionName) { | ||
| 26 | toastr.warning(`Extension ${extensionName} does not exist.`); | ||
| 27 | return ''; | ||
| 28 | } | ||
| 29 | |||
| 30 | const isEnabled = !extension_settings.disabledExtensions.includes(internalExtensionName); | ||
| 31 | |||
| 32 | if (action === 'enable' && isEnabled) { | ||
| 33 | toastr.info(`Extension ${extensionName} is already enabled.`); | ||
| 34 | return internalExtensionName; | ||
| 35 | } | ||
| 36 | |||
| 37 | if (action === 'disable' && !isEnabled) { | ||
| 38 | toastr.info(`Extension ${extensionName} is already disabled.`); | ||
| 39 | return internalExtensionName; | ||
| 40 | } | ||
| 41 | |||
| 42 | if (action === 'toggle') { | ||
| 43 | action = isEnabled ? 'disable' : 'enable'; | ||
| 44 | } | ||
| 45 | |||
| 46 | if (reload) { | ||
| 47 | toastr.info(`${action.charAt(0).toUpperCase() + action.slice(1)}ing extension ${extensionName} and reloading...`); | ||
| 48 | |||
| 49 | // Clear input, so it doesn't stay because the command didn't "finish", | ||
| 50 | // and wait for a bit to both show the toast and let the clear bubble through. | ||
| 51 | $('#send_textarea').val('')[0].dispatchEvent(new Event('input', { bubbles: true })); | ||
| 52 | await new Promise(resolve => setTimeout(resolve, 100)); | ||
| 53 | } | ||
| 54 | |||
| 55 | if (action === 'enable') { | ||
| 56 | await enableExtension(internalExtensionName, reload); | ||
| 57 | } else { | ||
| 58 | await disableExtension(internalExtensionName, reload); | ||
| 59 | } | ||
| 60 | |||
| 61 | toastr.success(`Extension ${extensionName} ${action}d.`); | ||
| 62 | |||
| 63 | |||
| 64 | console.info(`Extension ${action}ed: ${extensionName}`); | ||
| 65 | if (!reload) { | ||
| 66 | console.info('Reload not requested, so page needs to be reloaded manually for changes to take effect.'); | ||
| 67 | } | ||
| 68 | |||
| 69 | return internalExtensionName; | ||
| 70 | }; | ||
| 71 | } | ||
| 72 | |||
| 73 | /** | ||
| 74 | * Finds an extension by name, allowing omission of the "third-party/" prefix. | ||
| 75 | * | ||
| 76 | * @param {string} name - The name of the extension to find | ||
| 77 | * @returns {string?} - The matched extension name or undefined if not found | ||
| 78 | */ | ||
| 79 | function findExtension(name) { | ||
| 80 | return extensionNames.find(extName => { | ||
| 81 | return equalsIgnoreCaseAndAccents(extName, name) || equalsIgnoreCaseAndAccents(extName, `third-party/${name}`); | ||
| 82 | }); | ||
| 83 | } | ||
| 84 | |||
| 85 | /** | ||
| 86 | * Provides an array of SlashCommandEnumValue objects based on the extension names. | ||
| 87 | * Each object contains the name of the extension and a description indicating if it is a third-party extension. | ||
| 88 | * | ||
| 89 | * @returns {SlashCommandEnumValue[]} An array of SlashCommandEnumValue objects | ||
| 90 | */ | ||
| 91 | const extensionNamesEnumProvider = () => extensionNames.map(name => { | ||
| 92 | const isThirdParty = name.startsWith('third-party/'); | ||
| 93 | if (isThirdParty) name = name.slice('third-party/'.length); | ||
| 94 | |||
| 95 | const description = isThirdParty ? 'third party extension' : null; | ||
| 96 | |||
| 97 | return new SlashCommandEnumValue(name, description, !isThirdParty ? enumTypes.name : enumTypes.enum); | ||
| 98 | }); | ||
| 99 | |||
| 100 | export function registerExtensionSlashCommands() { | ||
| 101 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | ||
| 102 | name: 'extension-enable', | ||
| 103 | callback: getExtensionActionCallback('enable'), | ||
| 104 | returns: 'The internal extension name', | ||
| 105 | namedArgumentList: [ | ||
| 106 | SlashCommandNamedArgument.fromProps({ | ||
| 107 | name: 'reload', | ||
| 108 | description: 'Whether to reload the page after enabling the extension', | ||
| 109 | typeList: [ARGUMENT_TYPE.BOOLEAN], | ||
| 110 | defaultValue: 'true', | ||
| 111 | enumList: commonEnumProviders.boolean('trueFalse')(), | ||
| 112 | }), | ||
| 113 | ], | ||
| 114 | unnamedArgumentList: [ | ||
| 115 | SlashCommandArgument.fromProps({ | ||
| 116 | description: 'Extension name', | ||
| 117 | typeList: [ARGUMENT_TYPE.STRING], | ||
| 118 | isRequired: true, | ||
| 119 | enumProvider: extensionNamesEnumProvider, | ||
| 120 | forceEnum: true, | ||
| 121 | }), | ||
| 122 | ], | ||
| 123 | helpString: ` | ||
| 124 | <div> | ||
| 125 | Enables a specified extension. | ||
| 126 | </div> | ||
| 127 | <div> | ||
| 128 | By default, the page will be reloaded automatically, stopping any further commands.<br /> | ||
| 129 | If <code>reload=false</code> named argument is passed, the page will not be reloaded, and the extension will stay disabled until refreshed. | ||
| 130 | The page either needs to be refreshed, or <code>/reload-page</code> has to be called. | ||
| 131 | </div> | ||
| 132 | <div> | ||
| 133 | <strong>Example:</strong> | ||
| 134 | <ul> | ||
| 135 | <li> | ||
| 136 | <pre><code class="language-stscript">/extension-enable Summarize</code></pre> | ||
| 137 | </li> | ||
| 138 | </ul> | ||
| 139 | </div> | ||
| 140 | `, | ||
| 141 | })); | ||
| 142 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | ||
| 143 | name: 'extension-disable', | ||
| 144 | callback: getExtensionActionCallback('disable'), | ||
| 145 | returns: 'The internal extension name', | ||
| 146 | namedArgumentList: [ | ||
| 147 | SlashCommandNamedArgument.fromProps({ | ||
| 148 | name: 'reload', | ||
| 149 | description: 'Whether to reload the page after disabling the extension', | ||
| 150 | typeList: [ARGUMENT_TYPE.BOOLEAN], | ||
| 151 | defaultValue: 'true', | ||
| 152 | enumList: commonEnumProviders.boolean('trueFalse')(), | ||
| 153 | }), | ||
| 154 | ], | ||
| 155 | unnamedArgumentList: [ | ||
| 156 | SlashCommandArgument.fromProps({ | ||
| 157 | description: 'Extension name', | ||
| 158 | typeList: [ARGUMENT_TYPE.STRING], | ||
| 159 | isRequired: true, | ||
| 160 | enumProvider: extensionNamesEnumProvider, | ||
| 161 | forceEnum: true, | ||
| 162 | }), | ||
| 163 | ], | ||
| 164 | helpString: ` | ||
| 165 | <div> | ||
| 166 | Disables a specified extension. | ||
| 167 | </div> | ||
| 168 | <div> | ||
| 169 | By default, the page will be reloaded automatically, stopping any further commands.<br /> | ||
| 170 | If <code>reload=false</code> named argument is passed, the page will not be reloaded, and the extension will stay enabled until refreshed. | ||
| 171 | The page either needs to be refreshed, or <code>/reload-page</code> has to be called. | ||
| 172 | </div> | ||
| 173 | <div> | ||
| 174 | <strong>Example:</strong> | ||
| 175 | <ul> | ||
| 176 | <li> | ||
| 177 | <pre><code class="language-stscript">/extension-disable Summarize</code></pre> | ||
| 178 | </li> | ||
| 179 | </ul> | ||
| 180 | </div> | ||
| 181 | `, | ||
| 182 | })); | ||
| 183 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | ||
| 184 | name: 'extension-toggle', | ||
| 185 | callback: async (args, extensionName) => { | ||
| 186 | if (args?.state instanceof SlashCommandClosure) throw new Error('\'state\' argument cannot be a closure.'); | ||
| 187 | if (typeof extensionName !== 'string') throw new Error('Extension name must be a string. Closures or arrays are not allowed.'); | ||
| 188 | |||
| 189 | const action = isTrueBoolean(args?.state) ? 'enable' : | ||
| 190 | isFalseBoolean(args?.state) ? 'disable' : | ||
| 191 | 'toggle'; | ||
| 192 | |||
| 193 | return await getExtensionActionCallback(action)(args, extensionName); | ||
| 194 | }, | ||
| 195 | returns: 'The internal extension name', | ||
| 196 | namedArgumentList: [ | ||
| 197 | SlashCommandNamedArgument.fromProps({ | ||
| 198 | name: 'reload', | ||
| 199 | description: 'Whether to reload the page after toggling the extension', | ||
| 200 | typeList: [ARGUMENT_TYPE.BOOLEAN], | ||
| 201 | defaultValue: 'true', | ||
| 202 | enumList: commonEnumProviders.boolean('trueFalse')(), | ||
| 203 | }), | ||
| 204 | SlashCommandNamedArgument.fromProps({ | ||
| 205 | name: 'state', | ||
| 206 | 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.', | ||
| 207 | typeList: [ARGUMENT_TYPE.BOOLEAN], | ||
| 208 | enumList: commonEnumProviders.boolean('trueFalse')(), | ||
| 209 | }), | ||
| 210 | ], | ||
| 211 | unnamedArgumentList: [ | ||
| 212 | SlashCommandArgument.fromProps({ | ||
| 213 | description: 'Extension name', | ||
| 214 | typeList: [ARGUMENT_TYPE.STRING], | ||
| 215 | isRequired: true, | ||
| 216 | enumProvider: extensionNamesEnumProvider, | ||
| 217 | forceEnum: true, | ||
| 218 | }), | ||
| 219 | ], | ||
| 220 | helpString: ` | ||
| 221 | <div> | ||
| 222 | Toggles the state of a specified extension. | ||
| 223 | </div> | ||
| 224 | <div> | ||
| 225 | By default, the page will be reloaded automatically, stopping any further commands.<br /> | ||
| 226 | 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. | ||
| 227 | The page either needs to be refreshed, or <code>/reload-page</code> has to be called. | ||
| 228 | </div> | ||
| 229 | <div> | ||
| 230 | <strong>Example:</strong> | ||
| 231 | <ul> | ||
| 232 | <li> | ||
| 233 | <pre><code class="language-stscript">/extension-toggle Summarize</code></pre> | ||
| 234 | </li> | ||
| 235 | <li> | ||
| 236 | <pre><code class="language-stscript">/extension-toggle Summarize state=true</code></pre> | ||
| 237 | </li> | ||
| 238 | </ul> | ||
| 239 | </div> | ||
| 240 | `, | ||
| 241 | })); | ||
| 242 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | ||
| 243 | name: 'extension-state', | ||
| 244 | callback: async (_, extensionName) => { | ||
| 245 | if (typeof extensionName !== 'string') throw new Error('Extension name must be a string. Closures or arrays are not allowed.'); | ||
| 246 | const internalExtensionName = findExtension(extensionName); | ||
| 247 | if (!internalExtensionName) { | ||
| 248 | toastr.warning(`Extension ${extensionName} does not exist.`); | ||
| 249 | return ''; | ||
| 250 | } | ||
| 251 | |||
| 252 | const isEnabled = !extension_settings.disabledExtensions.includes(internalExtensionName); | ||
| 253 | return String(isEnabled); | ||
| 254 | }, | ||
| 255 | returns: 'The state of the extension, whether it is enabled.', | ||
| 256 | unnamedArgumentList: [ | ||
| 257 | SlashCommandArgument.fromProps({ | ||
| 258 | description: 'Extension name', | ||
| 259 | typeList: [ARGUMENT_TYPE.STRING], | ||
| 260 | isRequired: true, | ||
| 261 | enumProvider: extensionNamesEnumProvider, | ||
| 262 | forceEnum: true, | ||
| 263 | }), | ||
| 264 | ], | ||
| 265 | helpString: ` | ||
| 266 | <div> | ||
| 267 | Returns the state of a specified extension (true if enabled, false if disabled). | ||
| 268 | </div> | ||
| 269 | <div> | ||
| 270 | <strong>Example:</strong> | ||
| 271 | <ul> | ||
| 272 | <li> | ||
| 273 | <pre><code class="language-stscript">/extension-state Summarize</code></pre> | ||
| 274 | </li> | ||
| 275 | </ul> | ||
| 276 | </div> | ||
| 277 | `, | ||
| 278 | })); | ||
| 279 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | ||
| 280 | name: 'extension-exists', | ||
| 281 | aliases: ['extension-installed'], | ||
| 282 | callback: async (_, extensionName) => { | ||
| 283 | if (typeof extensionName !== 'string') throw new Error('Extension name must be a string. Closures or arrays are not allowed.'); | ||
| 284 | const exists = findExtension(extensionName) !== undefined; | ||
| 285 | return exists ? 'true' : 'false'; | ||
| 286 | }, | ||
| 287 | returns: 'Whether the extension exists and is installed.', | ||
| 288 | unnamedArgumentList: [ | ||
| 289 | SlashCommandArgument.fromProps({ | ||
| 290 | description: 'Extension name', | ||
| 291 | typeList: [ARGUMENT_TYPE.STRING], | ||
| 292 | isRequired: true, | ||
| 293 | enumProvider: extensionNamesEnumProvider, | ||
| 294 | }), | ||
| 295 | ], | ||
| 296 | helpString: ` | ||
| 297 | <div> | ||
| 298 | Checks if a specified extension exists. | ||
| 299 | </div> | ||
| 300 | <div> | ||
| 301 | <strong>Example:</strong> | ||
| 302 | <ul> | ||
| 303 | <li> | ||
| 304 | <pre><code class="language-stscript">/extension-exists SillyTavern-LALib</code></pre> | ||
| 305 | </li> | ||
| 306 | </ul> | ||
| 307 | </div> | ||
| 308 | `, | ||
| 309 | })); | ||
| 310 | |||
| 311 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | ||
| 312 | name: 'reload-page', | ||
| 313 | callback: async () => { | ||
| 314 | toastr.info('Reloading the page...'); | ||
| 315 | location.reload(); | ||
| 316 | return ''; | ||
| 317 | }, | ||
| 318 | helpString: 'Reloads the current page. All further commands will not be processed.', | ||
| 319 | })); | ||
| 320 | } | ||
| @@ -1,5 +1,5 @@ | |||
| 1 | import { eventSource, event_types, saveSettings, saveSettingsDebounced, getRequestHeaders, animation_duration } from '../script.js'; | 1 | import { eventSource, event_types, saveSettings, saveSettingsDebounced, getRequestHeaders, animation_duration } from '../script.js'; |
| 2 | import { hideLoader, showLoader } from './loader.js'; | 2 | import { showLoader } from './loader.js'; |
| 3 | import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js'; | 3 | import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js'; |
| 4 | import { renderTemplate, renderTemplateAsync } from './templates.js'; | 4 | import { renderTemplate, renderTemplateAsync } from './templates.js'; |
| 5 | import { isSubsetOf, setValueByPath } from './utils.js'; | 5 | import { isSubsetOf, setValueByPath } from './utils.js'; |
| @@ -14,7 +14,9 @@ export { | |||
| 14 | ModuleWorkerWrapper, | 14 | ModuleWorkerWrapper, |
| 15 | }; | 15 | }; |
| 16 | 16 | ||
| 17 | /** @type {string[]} */ | ||
| 17 | export let extensionNames = []; | 18 | export let extensionNames = []; |
| 19 | |||
| 18 | let manifests = {}; | 20 | let manifests = {}; |
| 19 | const defaultUrl = 'http://localhost:5100'; | 21 | const defaultUrl = 'http://localhost:5100'; |
| 20 | 22 | ||
| @@ -241,7 +243,7 @@ function onEnableExtensionClick() { | |||
| 241 | enableExtension(name, false); | 243 | enableExtension(name, false); |
| 242 | } | 244 | } |
| 243 | 245 | ||
| 244 | async function enableExtension(name, reload = true) { | 246 | export async function enableExtension(name, reload = true) { |
| 245 | extension_settings.disabledExtensions = extension_settings.disabledExtensions.filter(x => x !== name); | 247 | extension_settings.disabledExtensions = extension_settings.disabledExtensions.filter(x => x !== name); |
| 246 | stateChanged = true; | 248 | stateChanged = true; |
| 247 | await saveSettings(); | 249 | await saveSettings(); |
| @@ -252,7 +254,7 @@ async function enableExtension(name, reload = true) { | |||
| 252 | } | 254 | } |
| 253 | } | 255 | } |
| 254 | 256 | ||
| 255 | async function disableExtension(name, reload = true) { | 257 | export async function disableExtension(name, reload = true) { |
| 256 | extension_settings.disabledExtensions.push(name); | 258 | extension_settings.disabledExtensions.push(name); |
| 257 | stateChanged = true; | 259 | stateChanged = true; |
| 258 | await saveSettings(); | 260 | await saveSettings(); |
| @@ -1041,7 +1043,9 @@ export async function openThirdPartyExtensionMenu(suggestUrl = '') { | |||
| 1041 | await installExtension(url); | 1043 | await installExtension(url); |
| 1042 | } | 1044 | } |
| 1043 | 1045 | ||
| 1044 | jQuery(async function () { | 1046 | |
| 1047 | |||
| 1048 | export async function initExtensions() { | ||
| 1045 | await addExtensionsButtonAndMenu(); | 1049 | await addExtensionsButtonAndMenu(); |
| 1046 | $('#extensionsMenuButton').css('display', 'flex'); | 1050 | $('#extensionsMenuButton').css('display', 'flex'); |
| 1047 | 1051 | ||
| @@ -1060,4 +1064,4 @@ jQuery(async function () { | |||
| 1060 | * @listens #third_party_extension_button#click - The click event of the '#third_party_extension_button' element. | 1064 | * @listens #third_party_extension_button#click - The click event of the '#third_party_extension_button' element. |
| 1061 | */ | 1065 | */ |
| 1062 | $('#third_party_extension_button').on('click', () => openThirdPartyExtensionMenu()); | 1066 | $('#third_party_extension_button').on('click', () => openThirdPartyExtensionMenu()); |
| 1063 | }); | 1067 | } |
| @@ -0,0 +1,12 @@ | |||
| 1 | <div> | ||
| 2 | <h3>Included settings:</h3> | ||
| 3 | <div class="justifyLeft flex-container flexFlowColumn flexNoGap"> | ||
| 4 | {{#each settings}} | ||
| 5 | <label class="checkbox_label"> | ||
| 6 | <input type="checkbox" value="{{@key}}" name="exclude"{{#if this}} checked{{/if}}> | ||
| 7 | <span>{{@key}}</span> | ||
| 8 | </label> | ||
| 9 | {{/each}} | ||
| 10 | </div> | ||
| 11 | <h3 data-i18n="Profile name:">Profile name:</h3> | ||
| 12 | </div> | ||
| @@ -1,6 +1,6 @@ | |||
| 1 | import { event_types, eventSource, main_api, saveSettingsDebounced } from '../../../script.js'; | 1 | import { event_types, eventSource, main_api, saveSettingsDebounced } from '../../../script.js'; |
| 2 | import { extension_settings, renderExtensionTemplateAsync } from '../../extensions.js'; | 2 | import { extension_settings, renderExtensionTemplateAsync } from '../../extensions.js'; |
| 3 | import { callGenericPopup, Popup, POPUP_TYPE } from '../../popup.js'; | 3 | import { callGenericPopup, Popup, POPUP_RESULT, POPUP_TYPE } from '../../popup.js'; |
| 4 | import { SlashCommand } from '../../slash-commands/SlashCommand.js'; | 4 | import { SlashCommand } from '../../slash-commands/SlashCommand.js'; |
| 5 | import { SlashCommandAbortController } from '../../slash-commands/SlashCommandAbortController.js'; | 5 | import { SlashCommandAbortController } from '../../slash-commands/SlashCommandAbortController.js'; |
| 6 | import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js'; | 6 | import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js'; |
| @@ -136,6 +136,7 @@ const profilesProvider = () => [ | |||
| 136 | * @property {string} [context] Context Template | 136 | * @property {string} [context] Context Template |
| 137 | * @property {string} [instruct-state] Instruct Mode | 137 | * @property {string} [instruct-state] Instruct Mode |
| 138 | * @property {string} [tokenizer] Tokenizer | 138 | * @property {string} [tokenizer] Tokenizer |
| 139 | * @property {string[]} [exclude] Commands to exclude | ||
| 139 | */ | 140 | */ |
| 140 | 141 | ||
| 141 | /** | 142 | /** |
| @@ -172,8 +173,13 @@ function findProfileByName(value) { | |||
| 172 | async function readProfileFromCommands(mode, profile, cleanUp = false) { | 173 | async function readProfileFromCommands(mode, profile, cleanUp = false) { |
| 173 | const commands = mode === 'cc' ? CC_COMMANDS : TC_COMMANDS; | 174 | const commands = mode === 'cc' ? CC_COMMANDS : TC_COMMANDS; |
| 174 | const opposingCommands = mode === 'cc' ? TC_COMMANDS : CC_COMMANDS; | 175 | const opposingCommands = mode === 'cc' ? TC_COMMANDS : CC_COMMANDS; |
| 176 | const excludeList = Array.isArray(profile.exclude) ? profile.exclude : []; | ||
| 175 | for (const command of commands) { | 177 | for (const command of commands) { |
| 176 | try { | 178 | try { |
| 179 | if (excludeList.includes(command)) { | ||
| 180 | continue; | ||
| 181 | } | ||
| 182 | |||
| 177 | const args = getNamedArguments(); | 183 | const args = getNamedArguments(); |
| 178 | const result = await SlashCommandParser.commands[command].callback(args, ''); | 184 | const result = await SlashCommandParser.commands[command].callback(args, ''); |
| 179 | if (result) { | 185 | if (result) { |
| @@ -209,15 +215,37 @@ async function readProfileFromCommands(mode, profile, cleanUp = false) { | |||
| 209 | async function createConnectionProfile(forceName = null) { | 215 | async function createConnectionProfile(forceName = null) { |
| 210 | const mode = main_api === 'openai' ? 'cc' : 'tc'; | 216 | const mode = main_api === 'openai' ? 'cc' : 'tc'; |
| 211 | const id = uuidv4(); | 217 | const id = uuidv4(); |
| 218 | /** @type {ConnectionProfile} */ | ||
| 212 | const profile = { | 219 | const profile = { |
| 213 | id, | 220 | id, |
| 214 | mode, | 221 | mode, |
| 222 | exclude: [], | ||
| 215 | }; | 223 | }; |
| 216 | 224 | ||
| 217 | await readProfileFromCommands(mode, profile); | 225 | await readProfileFromCommands(mode, profile); |
| 218 | 226 | ||
| 219 | const profileForDisplay = makeFancyProfile(profile); | 227 | const profileForDisplay = makeFancyProfile(profile); |
| 220 | const template = await renderExtensionTemplateAsync(MODULE_NAME, 'profile', { profile: profileForDisplay }); | 228 | const template = $(await renderExtensionTemplateAsync(MODULE_NAME, 'profile', { profile: profileForDisplay })); |
| 229 | template.find('input[name="exclude"]').on('input', function () { | ||
| 230 | const fancyName = String($(this).val()); | ||
| 231 | const keyName = Object.entries(FANCY_NAMES).find(x => x[1] === fancyName)?.[0]; | ||
| 232 | if (!keyName) { | ||
| 233 | console.warn('Key not found for fancy name:', fancyName); | ||
| 234 | return; | ||
| 235 | } | ||
| 236 | |||
| 237 | if (!Array.isArray(profile.exclude)) { | ||
| 238 | profile.exclude = []; | ||
| 239 | } | ||
| 240 | |||
| 241 | const excludeState = !$(this).prop('checked'); | ||
| 242 | if (excludeState) { | ||
| 243 | profile.exclude.push(keyName); | ||
| 244 | } else { | ||
| 245 | const index = profile.exclude.indexOf(keyName); | ||
| 246 | index !== -1 && profile.exclude.splice(index, 1); | ||
| 247 | } | ||
| 248 | }); | ||
| 221 | const isNameTaken = (n) => extension_settings.connectionManager.profiles.some(p => p.name === n); | 249 | const isNameTaken = (n) => extension_settings.connectionManager.profiles.some(p => p.name === n); |
| 222 | const suggestedName = getUniqueName(collapseSpaces(`${profile.api ?? ''} ${profile.model ?? ''} - ${profile.preset ?? ''}`), isNameTaken); | 250 | const suggestedName = getUniqueName(collapseSpaces(`${profile.api ?? ''} ${profile.model ?? ''} - ${profile.preset ?? ''}`), isNameTaken); |
| 223 | const name = forceName ?? await callGenericPopup(template, POPUP_TYPE.INPUT, suggestedName, { rows: 2 }); | 251 | const name = forceName ?? await callGenericPopup(template, POPUP_TYPE.INPUT, suggestedName, { rows: 2 }); |
| @@ -231,7 +259,13 @@ async function createConnectionProfile(forceName = null) { | |||
| 231 | return null; | 259 | return null; |
| 232 | } | 260 | } |
| 233 | 261 | ||
| 234 | profile.name = name; | 262 | if (Array.isArray(profile.exclude)) { |
| 263 | for (const command of profile.exclude) { | ||
| 264 | delete profile[command]; | ||
| 265 | } | ||
| 266 | } | ||
| 267 | |||
| 268 | profile.name = String(name); | ||
| 235 | return profile; | 269 | return profile; |
| 236 | } | 270 | } |
| 237 | 271 | ||
| @@ -358,7 +392,11 @@ async function renderDetailsContent(detailsContent) { | |||
| 358 | const profile = extension_settings.connectionManager.profiles.find(p => p.id === selectedProfile); | 392 | const profile = extension_settings.connectionManager.profiles.find(p => p.id === selectedProfile); |
| 359 | if (profile) { | 393 | if (profile) { |
| 360 | const profileForDisplay = makeFancyProfile(profile); | 394 | const profileForDisplay = makeFancyProfile(profile); |
| 361 | const template = await renderExtensionTemplateAsync(MODULE_NAME, 'view', { profile: profileForDisplay }); | 395 | const templateParams = { profile: profileForDisplay }; |
| 396 | if (Array.isArray(profile.exclude) && profile.exclude.length > 0) { | ||
| 397 | templateParams.omitted = profile.exclude.map(e => FANCY_NAMES[e]).join(', '); | ||
| 398 | } | ||
| 399 | const template = await renderExtensionTemplateAsync(MODULE_NAME, 'view', templateParams); | ||
| 362 | detailsContent.innerHTML = template; | 400 | detailsContent.innerHTML = template; |
| 363 | } else { | 401 | } else { |
| 364 | detailsContent.textContent = t`No profile selected`; | 402 | detailsContent.textContent = t`No profile selected`; |
| @@ -473,29 +511,71 @@ async function renderDetailsContent(detailsContent) { | |||
| 473 | await eventSource.emit(event_types.CONNECTION_PROFILE_LOADED, NONE); | 511 | await eventSource.emit(event_types.CONNECTION_PROFILE_LOADED, NONE); |
| 474 | }); | 512 | }); |
| 475 | 513 | ||
| 476 | const renameButton = document.getElementById('rename_connection_profile'); | 514 | const editButton = document.getElementById('edit_connection_profile'); |
| 477 | renameButton.addEventListener('click', async () => { | 515 | editButton.addEventListener('click', async () => { |
| 478 | const selectedProfile = extension_settings.connectionManager.selectedProfile; | 516 | const selectedProfile = extension_settings.connectionManager.selectedProfile; |
| 479 | const profile = extension_settings.connectionManager.profiles.find(p => p.id === selectedProfile); | 517 | const profile = extension_settings.connectionManager.profiles.find(p => p.id === selectedProfile); |
| 480 | if (!profile) { | 518 | if (!profile) { |
| 481 | console.log('No profile selected'); | 519 | console.log('No profile selected'); |
| 482 | return; | 520 | return; |
| 483 | } | 521 | } |
| 522 | if (!Array.isArray(profile.exclude)) { | ||
| 523 | profile.exclude = []; | ||
| 524 | } | ||
| 525 | |||
| 526 | let saveChanges = false; | ||
| 527 | const sortByViewOrder = (a, b) => Object.keys(FANCY_NAMES).indexOf(a) - Object.keys(FANCY_NAMES).indexOf(b); | ||
| 528 | const commands = profile.mode === 'cc' ? CC_COMMANDS : TC_COMMANDS; | ||
| 529 | const settings = commands.slice().sort(sortByViewOrder).reduce((acc, command) => { | ||
| 530 | const fancyName = FANCY_NAMES[command]; | ||
| 531 | acc[fancyName] = !profile.exclude.includes(command); | ||
| 532 | return acc; | ||
| 533 | }, {}); | ||
| 534 | const template = $(await renderExtensionTemplateAsync(MODULE_NAME, 'edit', { name: profile.name, settings })); | ||
| 535 | const newName = await callGenericPopup(template, POPUP_TYPE.INPUT, profile.name, { | ||
| 536 | customButtons: [{ | ||
| 537 | text: t`Save and Update`, | ||
| 538 | classes: ['popup-button-ok'], | ||
| 539 | result: POPUP_RESULT.AFFIRMATIVE, | ||
| 540 | action: () => { | ||
| 541 | saveChanges = true; | ||
| 542 | }, | ||
| 543 | }], | ||
| 544 | }); | ||
| 484 | 545 | ||
| 485 | const newName = await Popup.show.input(t`Enter a new name`, null, profile.name, { rows: 2 }); | ||
| 486 | if (!newName) { | 546 | if (!newName) { |
| 487 | return; | 547 | return; |
| 488 | } | 548 | } |
| 489 | 549 | ||
| 490 | if (extension_settings.connectionManager.profiles.some(p => p.name === newName)) { | 550 | if (profile.name !== newName && extension_settings.connectionManager.profiles.some(p => p.name === newName)) { |
| 491 | toastr.error('A profile with the same name already exists.'); | 551 | toastr.error('A profile with the same name already exists.'); |
| 492 | return; | 552 | return; |
| 493 | } | 553 | } |
| 494 | 554 | ||
| 495 | profile.name = newName; | 555 | const newExcludeList = template.find('input[name="exclude"]:not(:checked)').map(function () { |
| 556 | return Object.entries(FANCY_NAMES).find(x => x[1] === String($(this).val()))?.[0]; | ||
| 557 | }).get(); | ||
| 558 | |||
| 559 | if (newExcludeList.length !== profile.exclude.length || !newExcludeList.every(e => profile.exclude.includes(e))) { | ||
| 560 | profile.exclude = newExcludeList; | ||
| 561 | for (const command of newExcludeList) { | ||
| 562 | delete profile[command]; | ||
| 563 | } | ||
| 564 | if (saveChanges) { | ||
| 565 | await updateConnectionProfile(profile); | ||
| 566 | } else { | ||
| 567 | toastr.info('Press "Update" to record them into the profile.', 'Included settings list updated'); | ||
| 568 | } | ||
| 569 | } | ||
| 570 | |||
| 571 | if (profile.name !== newName) { | ||
| 572 | toastr.success('Connection profile renamed.'); | ||
| 573 | profile.name = String(newName); | ||
| 574 | } | ||
| 575 | |||
| 496 | saveSettingsDebounced(); | 576 | saveSettingsDebounced(); |
| 497 | renderConnectionProfiles(profiles); | 577 | renderConnectionProfiles(profiles); |
| 498 | toastr.success('Connection profile renamed', '', { timeOut: 1500 }); | 578 | await renderDetailsContent(detailsContent); |
| 499 | }); | 579 | }); |
| 500 | 580 | ||
| 501 | /** @type {HTMLElement} */ | 581 | /** @type {HTMLElement} */ |
| @@ -2,11 +2,20 @@ | |||
| 2 | <h2 data-i18n="Creating a Connection Profile"> | 2 | <h2 data-i18n="Creating a Connection Profile"> |
| 3 | Creating a Connection Profile | 3 | Creating a Connection Profile |
| 4 | </h2> | 4 | </h2> |
| 5 | <ul class="justifyLeft"> | 5 | <div class="justifyLeft flex-container flexFlowColumn flexNoGap"> |
| 6 | {{#each profile}} | 6 | {{#each profile}} |
| 7 | <li><strong data-i18n="{{@key}}">{{@key}}:</strong> {{this}}</li> | 7 | <label class="checkbox_label"> |
| 8 | <input type="checkbox" value="{{@key}}" name="exclude" checked> | ||
| 9 | <span><strong data-i18n="{{@key}}">{{@key}}:</strong> {{this}}</span> | ||
| 10 | </label> | ||
| 8 | {{/each}} | 11 | {{/each}} |
| 9 | </ul> | 12 | </div> |
| 13 | <div class="marginTop5"> | ||
| 14 | <small> | ||
| 15 | <b>Hint:</b> | ||
| 16 | <i>Click on the setting name to omit it from the profile.</i> | ||
| 17 | </small> | ||
| 18 | </div> | ||
| 10 | <h3 data-i18n="Enter a name:"> | 19 | <h3 data-i18n="Enter a name:"> |
| 11 | Enter a name: | 20 | Enter a name: |
| 12 | </h3> | 21 | </h3> |
| @@ -13,7 +13,7 @@ | |||
| 13 | <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> | 13 | <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> |
| 14 | <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> | 14 | <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> |
| 15 | <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> | 15 | <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> |
| 16 | <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> | 16 | <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> |
| 17 | <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> | 17 | <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> |
| 18 | <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> | 18 | <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> |
| 19 | </div> | 19 | </div> |
| @@ -3,3 +3,8 @@ | |||
| 3 | <li><strong data-i18n="{{@key}}">{{@key}}:</strong> {{this}}</li> | 3 | <li><strong data-i18n="{{@key}}">{{@key}}:</strong> {{this}}</li> |
| 4 | {{/each}} | 4 | {{/each}} |
| 5 | </ul> | 5 | </ul> |
| 6 | {{#if omitted}} | ||
| 7 | <div class="margin5"> | ||
| 8 | <strong data-i18n="Omitted Settings:">Omitted Settings:</strong> <span>{{omitted}}</span> | ||
| 9 | </div> | ||
| 10 | {{/if}} | ||
| @@ -26,9 +26,11 @@ let paginationVisiblePages = 10; | |||
| 26 | let paginationMaxLinesPerPage = 2; | 26 | let paginationMaxLinesPerPage = 2; |
| 27 | let galleryMaxRows = 3; | 27 | let galleryMaxRows = 3; |
| 28 | 28 | ||
| 29 | $('body').on('click', '.dragClose', function () { | 29 | // Remove all draggables associated with the gallery |
| 30 | const relatedId = $(this).data('related-id'); // Get the ID of the related draggable | 30 | $('#movingDivs').on('click', '.dragClose', function () { |
| 31 | $(`body > .draggable[id="${relatedId}"]`).remove(); // Remove the associated draggable | 31 | const relatedId = $(this).data('related-id'); |
| 32 | if (!relatedId) return; | ||
| 33 | $(`#movingDivs > .draggable[id="${relatedId}"]`).remove(); | ||
| 32 | }); | 34 | }); |
| 33 | 35 | ||
| 34 | const CUSTOM_GALLERY_REMOVED_EVENT = 'galleryRemoved'; | 36 | const CUSTOM_GALLERY_REMOVED_EVENT = 'galleryRemoved'; |
| @@ -290,7 +292,7 @@ function makeMovable(id = 'gallery') { | |||
| 290 | 292 | ||
| 291 | $('#dragGallery').css('display', 'block'); | 293 | $('#dragGallery').css('display', 'block'); |
| 292 | 294 | ||
| 293 | $('body').append(newElement); | 295 | $('#movingDivs').append(newElement); |
| 294 | 296 | ||
| 295 | loadMovingUIState(); | 297 | loadMovingUIState(); |
| 296 | $(`.draggable[forChar="${id}"]`).css('display', 'block'); | 298 | $(`.draggable[forChar="${id}"]`).css('display', 'block'); |
| @@ -362,8 +364,8 @@ function makeDragImg(id, url) { | |||
| 362 | } | 364 | } |
| 363 | } | 365 | } |
| 364 | 366 | ||
| 365 | // Step 3: Attach it to the body | 367 | // Step 3: Attach it to the movingDivs container |
| 366 | document.body.appendChild(newElement); | 368 | document.getElementById('movingDivs').appendChild(newElement); |
| 367 | 369 | ||
| 368 | // Step 4: Call dragElement and loadMovingUIState | 370 | // Step 4: Call dragElement and loadMovingUIState |
| 369 | const appendedElement = document.getElementById(uniqueId); | 371 | const appendedElement = document.getElementById(uniqueId); |
| @@ -350,6 +350,7 @@ | |||
| 350 | } | 350 | } |
| 351 | .popup:has(#qr--modalEditor) { | 351 | .popup:has(#qr--modalEditor) { |
| 352 | aspect-ratio: unset; | 352 | aspect-ratio: unset; |
| 353 | width: unset; | ||
| 353 | } | 354 | } |
| 354 | .popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) { | 355 | .popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) { |
| 355 | min-width: unset; | 356 | min-width: unset; |
| @@ -415,6 +415,7 @@ | |||
| 415 | 415 | ||
| 416 | .popup:has(#qr--modalEditor) { | 416 | .popup:has(#qr--modalEditor) { |
| 417 | aspect-ratio: unset; | 417 | aspect-ratio: unset; |
| 418 | width: unset; | ||
| 418 | 419 | ||
| 419 | &:has(.qr--isExecuting.qr--minimized) { | 420 | &:has(.qr--isExecuting.qr--minimized) { |
| 420 | min-width: unset; | 421 | min-width: unset; |
| @@ -0,0 +1,206 @@ | |||
| 1 | import { saveTtsProviderSettings } from './index.js'; | ||
| 2 | |||
| 3 | export { CosyVoiceProvider }; | ||
| 4 | |||
| 5 | class CosyVoiceProvider { | ||
| 6 | //########// | ||
| 7 | // Config // | ||
| 8 | //########// | ||
| 9 | |||
| 10 | settings; | ||
| 11 | ready = false; | ||
| 12 | voices = []; | ||
| 13 | separator = '. '; | ||
| 14 | audioElement = document.createElement('audio'); | ||
| 15 | |||
| 16 | /** | ||
| 17 | * Perform any text processing before passing to TTS engine. | ||
| 18 | * @param {string} text Input text | ||
| 19 | * @returns {string} Processed text | ||
| 20 | */ | ||
| 21 | processText(text) { | ||
| 22 | return text; | ||
| 23 | } | ||
| 24 | |||
| 25 | audioFormats = ['wav', 'ogg', 'silk', 'mp3', 'flac']; | ||
| 26 | |||
| 27 | languageLabels = { | ||
| 28 | 'Auto': 'auto', | ||
| 29 | }; | ||
| 30 | |||
| 31 | langKey2LangCode = { | ||
| 32 | 'zh': 'zh-CN', | ||
| 33 | 'en': 'en-US', | ||
| 34 | 'ja': 'ja-JP', | ||
| 35 | 'ko': 'ko-KR', | ||
| 36 | }; | ||
| 37 | |||
| 38 | modelTypes = { | ||
| 39 | CosyVoice: 'CosyVoice', | ||
| 40 | }; | ||
| 41 | |||
| 42 | defaultSettings = { | ||
| 43 | provider_endpoint: 'http://localhost:9880', | ||
| 44 | format: 'wav', | ||
| 45 | lang: 'auto', | ||
| 46 | streaming: false, | ||
| 47 | |||
| 48 | }; | ||
| 49 | |||
| 50 | get settingsHtml() { | ||
| 51 | let html = ` | ||
| 52 | |||
| 53 | <label for="tts_endpoint">Provider Endpoint:</label> | ||
| 54 | <input id="tts_endpoint" type="text" class="text_pole" maxlength="250" height="300" value="${this.defaultSettings.provider_endpoint}"/> | ||
| 55 | <span>Windows users Use <a target="_blank" href="https://github.com/v3ucn/CosyVoice_For_Windows">CosyVoice_For_Windows</a>(Unofficial).</span><br/> | ||
| 56 | <span>Macos Users Use <a target="_blank" href="https://github.com/v3ucn/CosyVoice_for_MacOs">CosyVoice_for_MacOs</a>(Unofficial).</span><br/> | ||
| 57 | <br/> | ||
| 58 | |||
| 59 | `; | ||
| 60 | |||
| 61 | return html; | ||
| 62 | } | ||
| 63 | |||
| 64 | onSettingsChange() { | ||
| 65 | // Used when provider settings are updated from UI | ||
| 66 | this.settings.provider_endpoint = $('#tts_endpoint').val(); | ||
| 67 | |||
| 68 | |||
| 69 | saveTtsProviderSettings(); | ||
| 70 | this.changeTTSSettings(); | ||
| 71 | } | ||
| 72 | |||
| 73 | async loadSettings(settings) { | ||
| 74 | // Pupulate Provider UI given input settings | ||
| 75 | if (Object.keys(settings).length == 0) { | ||
| 76 | console.info('Using default TTS Provider settings'); | ||
| 77 | } | ||
| 78 | |||
| 79 | // Only accept keys defined in defaultSettings | ||
| 80 | this.settings = this.defaultSettings; | ||
| 81 | |||
| 82 | for (const key in settings) { | ||
| 83 | if (key in this.settings) { | ||
| 84 | this.settings[key] = settings[key]; | ||
| 85 | } else { | ||
| 86 | console.debug(`Ignoring non-user-configurable setting: ${key}`); | ||
| 87 | } | ||
| 88 | } | ||
| 89 | |||
| 90 | // Set initial values from the settings | ||
| 91 | $('#tts_endpoint').val(this.settings.provider_endpoint); | ||
| 92 | |||
| 93 | |||
| 94 | await this.checkReady(); | ||
| 95 | |||
| 96 | console.info('ITS: Settings loaded'); | ||
| 97 | } | ||
| 98 | |||
| 99 | // Perform a simple readiness check by trying to fetch voiceIds | ||
| 100 | async checkReady() { | ||
| 101 | await Promise.allSettled([this.fetchTtsVoiceObjects(), this.changeTTSSettings()]); | ||
| 102 | } | ||
| 103 | |||
| 104 | async onRefreshClick() { | ||
| 105 | return; | ||
| 106 | } | ||
| 107 | |||
| 108 | //#################// | ||
| 109 | // TTS Interfaces // | ||
| 110 | //#################// | ||
| 111 | |||
| 112 | async getVoice(voiceName) { | ||
| 113 | |||
| 114 | |||
| 115 | |||
| 116 | if (this.voices.length == 0) { | ||
| 117 | this.voices = await this.fetchTtsVoiceObjects(); | ||
| 118 | } | ||
| 119 | |||
| 120 | |||
| 121 | |||
| 122 | const match = this.voices.filter( | ||
| 123 | v => v.name == voiceName, | ||
| 124 | )[0]; | ||
| 125 | console.log(match); | ||
| 126 | if (!match) { | ||
| 127 | throw `TTS Voice name ${voiceName} not found`; | ||
| 128 | } | ||
| 129 | return match; | ||
| 130 | } | ||
| 131 | |||
| 132 | |||
| 133 | |||
| 134 | async generateTts(text, voiceId) { | ||
| 135 | const response = await this.fetchTtsGeneration(text, voiceId); | ||
| 136 | return response; | ||
| 137 | } | ||
| 138 | |||
| 139 | //###########// | ||
| 140 | // API CALLS // | ||
| 141 | //###########// | ||
| 142 | async fetchTtsVoiceObjects() { | ||
| 143 | const response = await fetch(`${this.settings.provider_endpoint}/speakers`); | ||
| 144 | console.info(response); | ||
| 145 | |||
| 146 | if (!response.ok) { | ||
| 147 | throw new Error(`HTTP ${response.status}: ${await response.json()}`); | ||
| 148 | } | ||
| 149 | const responseJson = await response.json(); | ||
| 150 | |||
| 151 | |||
| 152 | this.voices = responseJson; | ||
| 153 | |||
| 154 | return responseJson; | ||
| 155 | } | ||
| 156 | |||
| 157 | // Each time a parameter is changed, we change the configuration | ||
| 158 | async changeTTSSettings() { | ||
| 159 | } | ||
| 160 | |||
| 161 | /** | ||
| 162 | * Fetch TTS generation from the API. | ||
| 163 | * @param {string} inputText Text to generate TTS for | ||
| 164 | * @param {string} voiceId Voice ID to use (model_type&speaker_id)) | ||
| 165 | * @returns {Promise<Response|string>} Fetch response | ||
| 166 | */ | ||
| 167 | async fetchTtsGeneration(inputText, voiceId, lang = null, forceNoStreaming = false) { | ||
| 168 | console.info(`Generating new TTS for voice_id ${voiceId}`); | ||
| 169 | |||
| 170 | const streaming = this.settings.streaming; | ||
| 171 | |||
| 172 | const params = { | ||
| 173 | text: inputText, | ||
| 174 | speaker: voiceId, | ||
| 175 | }; | ||
| 176 | |||
| 177 | if (streaming) { | ||
| 178 | params['streaming'] = 1; | ||
| 179 | } | ||
| 180 | |||
| 181 | const url = `${this.settings.provider_endpoint}/`; | ||
| 182 | |||
| 183 | const response = await fetch( | ||
| 184 | url, | ||
| 185 | { | ||
| 186 | method: 'POST', | ||
| 187 | headers: { | ||
| 188 | 'Content-Type': 'application/json', | ||
| 189 | }, | ||
| 190 | body: JSON.stringify(params), // Convert parameter objects to JSON strings | ||
| 191 | }, | ||
| 192 | ); | ||
| 193 | if (!response.ok) { | ||
| 194 | toastr.error(response.statusText, 'TTS Generation Failed'); | ||
| 195 | throw new Error(`HTTP ${response.status}: ${await response.text()}`); | ||
| 196 | } | ||
| 197 | return response; | ||
| 198 | } | ||
| 199 | |||
| 200 | |||
| 201 | |||
| 202 | // Interface not used | ||
| 203 | async fetchTtsFromHistory(history_item_id) { | ||
| 204 | return Promise.resolve(history_item_id); | ||
| 205 | } | ||
| 206 | } | ||
| @@ -0,0 +1,226 @@ | |||
| 1 | import { saveTtsProviderSettings } from './index.js'; | ||
| 2 | |||
| 3 | export { GptSovitsV2Provider }; | ||
| 4 | |||
| 5 | class GptSovitsV2Provider { | ||
| 6 | //########// | ||
| 7 | // Config // | ||
| 8 | //########// | ||
| 9 | |||
| 10 | settings; | ||
| 11 | ready = false; | ||
| 12 | voices = []; | ||
| 13 | separator = '. '; | ||
| 14 | audioElement = document.createElement('audio'); | ||
| 15 | |||
| 16 | /** | ||
| 17 | * Perform any text processing before passing to TTS engine. | ||
| 18 | * @param {string} text Input text | ||
| 19 | * @returns {string} Processed text | ||
| 20 | */ | ||
| 21 | processText(text) { | ||
| 22 | return text; | ||
| 23 | } | ||
| 24 | |||
| 25 | audioFormats = ['wav', 'ogg', 'silk', 'mp3', 'flac']; | ||
| 26 | |||
| 27 | languageLabels = { | ||
| 28 | 'Auto': 'auto', | ||
| 29 | }; | ||
| 30 | |||
| 31 | langKey2LangCode = { | ||
| 32 | 'zh': 'zh-CN', | ||
| 33 | 'en': 'en-US', | ||
| 34 | 'ja': 'ja-JP', | ||
| 35 | 'ko': 'ko-KR', | ||
| 36 | }; | ||
| 37 | |||
| 38 | |||
| 39 | defaultSettings = { | ||
| 40 | provider_endpoint: 'http://localhost:9880', | ||
| 41 | format: 'wav', | ||
| 42 | lang: 'auto', | ||
| 43 | streaming: false, | ||
| 44 | text_lang: 'zh', | ||
| 45 | prompt_lang: 'zh', | ||
| 46 | |||
| 47 | }; | ||
| 48 | |||
| 49 | get settingsHtml() { | ||
| 50 | let html = ` | ||
| 51 | |||
| 52 | <label for="tts_endpoint">Provider Endpoint:</label> | ||
| 53 | <input id="tts_endpoint" type="text" class="text_pole" maxlength="250" height="300" value="${this.defaultSettings.provider_endpoint}"/> | ||
| 54 | <span>Use <a target="_blank" href="https://github.com/v3ucn/GPT-SoVITS-V2">GPT-SoVITS-V2</a>(Unofficial).</span><br/> | ||
| 55 | <label for="text_lang">Text Lang(Inference text language):</label> | ||
| 56 | <input id="text_lang" type="text" class="text_pole" maxlength="250" height="300" value="${this.defaultSettings.text_lang}"/> | ||
| 57 | <label for="text_lang">Prompt Lang(Reference audio text language):</label> | ||
| 58 | <input id="prompt_lang" type="text" class="text_pole" maxlength="250" height="300" value="${this.defaultSettings.prompt_lang}"/> | ||
| 59 | <br/> | ||
| 60 | |||
| 61 | `; | ||
| 62 | |||
| 63 | return html; | ||
| 64 | } | ||
| 65 | |||
| 66 | onSettingsChange() { | ||
| 67 | // Used when provider settings are updated from UI | ||
| 68 | this.settings.provider_endpoint = $('#tts_endpoint').val(); | ||
| 69 | this.settings.text_lang = $('#text_lang').val(); | ||
| 70 | this.settings.prompt_lang = $('#prompt_lang').val(); | ||
| 71 | |||
| 72 | |||
| 73 | saveTtsProviderSettings(); | ||
| 74 | this.changeTTSSettings(); | ||
| 75 | } | ||
| 76 | |||
| 77 | async loadSettings(settings) { | ||
| 78 | // Pupulate Provider UI given input settings | ||
| 79 | if (Object.keys(settings).length == 0) { | ||
| 80 | console.info('Using default TTS Provider settings'); | ||
| 81 | } | ||
| 82 | |||
| 83 | // Only accept keys defined in defaultSettings | ||
| 84 | this.settings = this.defaultSettings; | ||
| 85 | |||
| 86 | for (const key in settings) { | ||
| 87 | if (key in this.settings) { | ||
| 88 | this.settings[key] = settings[key]; | ||
| 89 | } else { | ||
| 90 | console.debug(`Ignoring non-user-configurable setting: ${key}`); | ||
| 91 | } | ||
| 92 | } | ||
| 93 | |||
| 94 | // Set initial values from the settings | ||
| 95 | $('#tts_endpoint').val(this.settings.provider_endpoint); | ||
| 96 | $('#text_lang').val(this.settings.text_lang); | ||
| 97 | $('#prompt_lang').val(this.settings.prompt_lang); | ||
| 98 | |||
| 99 | |||
| 100 | await this.checkReady(); | ||
| 101 | |||
| 102 | console.info('ITS: Settings loaded'); | ||
| 103 | } | ||
| 104 | |||
| 105 | // Perform a simple readiness check by trying to fetch voiceIds | ||
| 106 | async checkReady() { | ||
| 107 | await Promise.allSettled([this.fetchTtsVoiceObjects(), this.changeTTSSettings()]); | ||
| 108 | } | ||
| 109 | |||
| 110 | async onRefreshClick() { | ||
| 111 | return; | ||
| 112 | } | ||
| 113 | |||
| 114 | //#################// | ||
| 115 | // TTS Interfaces // | ||
| 116 | //#################// | ||
| 117 | |||
| 118 | async getVoice(voiceName) { | ||
| 119 | |||
| 120 | |||
| 121 | |||
| 122 | if (this.voices.length == 0) { | ||
| 123 | this.voices = await this.fetchTtsVoiceObjects(); | ||
| 124 | } | ||
| 125 | |||
| 126 | |||
| 127 | |||
| 128 | const match = this.voices.filter( | ||
| 129 | v => v.name == voiceName, | ||
| 130 | )[0]; | ||
| 131 | console.log(match); | ||
| 132 | if (!match) { | ||
| 133 | throw `TTS Voice name ${voiceName} not found`; | ||
| 134 | } | ||
| 135 | return match; | ||
| 136 | } | ||
| 137 | |||
| 138 | |||
| 139 | |||
| 140 | async generateTts(text, voiceId) { | ||
| 141 | const response = await this.fetchTtsGeneration(text, voiceId); | ||
| 142 | return response; | ||
| 143 | } | ||
| 144 | |||
| 145 | //###########// | ||
| 146 | // API CALLS // | ||
| 147 | //###########// | ||
| 148 | async fetchTtsVoiceObjects() { | ||
| 149 | const response = await fetch(`${this.settings.provider_endpoint}/speakers`); | ||
| 150 | console.info(response); | ||
| 151 | |||
| 152 | if (!response.ok) { | ||
| 153 | throw new Error(`HTTP ${response.status}: ${await response.json()}`); | ||
| 154 | } | ||
| 155 | const responseJson = await response.json(); | ||
| 156 | |||
| 157 | |||
| 158 | this.voices = responseJson; | ||
| 159 | |||
| 160 | return responseJson; | ||
| 161 | } | ||
| 162 | |||
| 163 | // Each time a parameter is changed, we change the configuration | ||
| 164 | async changeTTSSettings() { | ||
| 165 | } | ||
| 166 | |||
| 167 | /** | ||
| 168 | * Fetch TTS generation from the API. | ||
| 169 | * @param {string} inputText Text to generate TTS for | ||
| 170 | * @param {string} voiceId Voice ID to use (model_type&speaker_id)) | ||
| 171 | * @returns {Promise<Response|string>} Fetch response | ||
| 172 | */ | ||
| 173 | |||
| 174 | |||
| 175 | |||
| 176 | |||
| 177 | async fetchTtsGeneration(inputText, voiceId, lang = null, forceNoStreaming = false) { | ||
| 178 | console.info(`Generating new TTS for voice_id ${voiceId}`); | ||
| 179 | |||
| 180 | function replaceSpeaker(text) { | ||
| 181 | return text.replace(/\[.*?\]/gu, ''); | ||
| 182 | } | ||
| 183 | |||
| 184 | let prompt_text = replaceSpeaker(voiceId); | ||
| 185 | |||
| 186 | const streaming = this.settings.streaming; | ||
| 187 | |||
| 188 | const params = { | ||
| 189 | text: inputText, | ||
| 190 | prompt_text: prompt_text, | ||
| 191 | ref_audio_path: './参考音频/' + voiceId + '.wav', | ||
| 192 | text_lang: this.settings.text_lang, | ||
| 193 | prompt_lang: this.settings.prompt_lang, | ||
| 194 | text_split_method: 'cut5', | ||
| 195 | batch_size: 1, | ||
| 196 | media_type: 'ogg', | ||
| 197 | streaming_mode: 'true', | ||
| 198 | }; | ||
| 199 | |||
| 200 | |||
| 201 | const url = `${this.settings.provider_endpoint}/`; | ||
| 202 | |||
| 203 | const response = await fetch( | ||
| 204 | url, | ||
| 205 | { | ||
| 206 | method: 'POST', | ||
| 207 | headers: { | ||
| 208 | 'Content-Type': 'application/json', | ||
| 209 | }, | ||
| 210 | body: JSON.stringify(params), // Convert parameter objects to JSON strings | ||
| 211 | }, | ||
| 212 | ); | ||
| 213 | if (!response.ok) { | ||
| 214 | toastr.error(response.statusText, 'TTS Generation Failed'); | ||
| 215 | throw new Error(`HTTP ${response.status}: ${await response.text()}`); | ||
| 216 | } | ||
| 217 | return response; | ||
| 218 | } | ||
| 219 | |||
| 220 | |||
| 221 | |||
| 222 | // Interface not used | ||
| 223 | async fetchTtsFromHistory(history_item_id) { | ||
| 224 | return Promise.resolve(history_item_id); | ||
| 225 | } | ||
| 226 | } | ||
| @@ -4,6 +4,7 @@ import { delay, escapeRegex, getBase64Async, getStringHash, onlyUnique } from '. | |||
| 4 | import { EdgeTtsProvider } from './edge.js'; | 4 | import { EdgeTtsProvider } from './edge.js'; |
| 5 | import { ElevenLabsTtsProvider } from './elevenlabs.js'; | 5 | import { ElevenLabsTtsProvider } from './elevenlabs.js'; |
| 6 | import { SileroTtsProvider } from './silerotts.js'; | 6 | import { SileroTtsProvider } from './silerotts.js'; |
| 7 | import { GptSovitsV2Provider } from './gpt-sovits-v2.js'; | ||
| 7 | import { CoquiTtsProvider } from './coqui.js'; | 8 | import { CoquiTtsProvider } from './coqui.js'; |
| 8 | import { SystemTtsProvider } from './system.js'; | 9 | import { SystemTtsProvider } from './system.js'; |
| 9 | import { NovelTtsProvider } from './novel.js'; | 10 | import { NovelTtsProvider } from './novel.js'; |
| @@ -15,6 +16,7 @@ import { VITSTtsProvider } from './vits.js'; | |||
| 15 | import { GSVITtsProvider } from './gsvi.js'; | 16 | import { GSVITtsProvider } from './gsvi.js'; |
| 16 | import { SBVits2TtsProvider } from './sbvits2.js'; | 17 | import { SBVits2TtsProvider } from './sbvits2.js'; |
| 17 | import { AllTalkTtsProvider } from './alltalk.js'; | 18 | import { AllTalkTtsProvider } from './alltalk.js'; |
| 19 | import { CosyVoiceProvider } from './cosyvoice.js'; | ||
| 18 | import { SpeechT5TtsProvider } from './speecht5.js'; | 20 | import { SpeechT5TtsProvider } from './speecht5.js'; |
| 19 | import { AzureTtsProvider } from './azure.js'; | 21 | import { AzureTtsProvider } from './azure.js'; |
| 20 | import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js'; | 22 | import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js'; |
| @@ -86,9 +88,11 @@ const ttsProviders = { | |||
| 86 | AllTalk: AllTalkTtsProvider, | 88 | AllTalk: AllTalkTtsProvider, |
| 87 | Azure: AzureTtsProvider, | 89 | Azure: AzureTtsProvider, |
| 88 | Coqui: CoquiTtsProvider, | 90 | Coqui: CoquiTtsProvider, |
| 91 | 'CosyVoice (Unofficial)': CosyVoiceProvider, | ||
| 89 | Edge: EdgeTtsProvider, | 92 | Edge: EdgeTtsProvider, |
| 90 | ElevenLabs: ElevenLabsTtsProvider, | 93 | ElevenLabs: ElevenLabsTtsProvider, |
| 91 | GSVI: GSVITtsProvider, | 94 | GSVI: GSVITtsProvider, |
| 95 | 'GPT-SoVITS-V2 (Unofficial)': GptSovitsV2Provider, | ||
| 92 | Novel: NovelTtsProvider, | 96 | Novel: NovelTtsProvider, |
| 93 | OpenAI: OpenAITtsProvider, | 97 | OpenAI: OpenAITtsProvider, |
| 94 | 'OpenAI Compatible': OpenAICompatibleTtsProvider, | 98 | 'OpenAI Compatible': OpenAICompatibleTtsProvider, |
| @@ -74,7 +74,7 @@ const samplers = { | |||
| 74 | 74 | ||
| 75 | let novel_data = null; | 75 | let novel_data = null; |
| 76 | let badWordsCache = {}; | 76 | let badWordsCache = {}; |
| 77 | const BIAS_KEY = '#novel_api-settings'; | 77 | const BIAS_KEY = '#range_block_novel'; |
| 78 | 78 | ||
| 79 | export function setNovelData(data) { | 79 | export function setNovelData(data) { |
| 80 | novel_data = data; | 80 | novel_data = data; |
| @@ -492,11 +492,37 @@ function getBadWordPermutations(text) { | |||
| 492 | 492 | ||
| 493 | export function getNovelGenerationData(finalPrompt, settings, maxLength, isImpersonate, isContinue, _cfgValues, type) { | 493 | export function getNovelGenerationData(finalPrompt, settings, maxLength, isImpersonate, isContinue, _cfgValues, type) { |
| 494 | console.debug('NovelAI generation data for', type); | 494 | console.debug('NovelAI generation data for', type); |
| 495 | const isKayra = nai_settings.model_novel.includes('kayra'); | ||
| 496 | const isErato = nai_settings.model_novel.includes('erato'); | ||
| 495 | 497 | ||
| 496 | const tokenizerType = getTokenizerTypeForModel(nai_settings.model_novel); | 498 | const tokenizerType = getTokenizerTypeForModel(nai_settings.model_novel); |
| 499 | const stoppingStrings = getStoppingStrings(isImpersonate, isContinue); | ||
| 500 | |||
| 501 | // Llama 3 tokenizer, huh? | ||
| 502 | if (isErato) { | ||
| 503 | const additionalStopStrings = []; | ||
| 504 | for (const stoppingString of stoppingStrings) { | ||
| 505 | if (stoppingString.startsWith('\n')) { | ||
| 506 | additionalStopStrings.push('.' + stoppingString); | ||
| 507 | additionalStopStrings.push('!' + stoppingString); | ||
| 508 | additionalStopStrings.push('?' + stoppingString); | ||
| 509 | additionalStopStrings.push('*' + stoppingString); | ||
| 510 | additionalStopStrings.push('"' + stoppingString); | ||
| 511 | additionalStopStrings.push('_' + stoppingString); | ||
| 512 | additionalStopStrings.push('...' + stoppingString); | ||
| 513 | additionalStopStrings.push('."' + stoppingString); | ||
| 514 | additionalStopStrings.push('?"' + stoppingString); | ||
| 515 | additionalStopStrings.push('!"' + stoppingString); | ||
| 516 | additionalStopStrings.push('.*' + stoppingString); | ||
| 517 | additionalStopStrings.push(')' + stoppingString); | ||
| 518 | } | ||
| 519 | } | ||
| 520 | stoppingStrings.push(...additionalStopStrings); | ||
| 521 | } | ||
| 522 | |||
| 523 | const MAX_STOP_SEQUENCES = 1024; | ||
| 497 | const stopSequences = (tokenizerType !== tokenizers.NONE) | 524 | const stopSequences = (tokenizerType !== tokenizers.NONE) |
| 498 | ? getStoppingStrings(isImpersonate, isContinue) | 525 | ? stoppingStrings.slice(0, MAX_STOP_SEQUENCES).map(t => getTextTokens(tokenizerType, t)) |
| 499 | .map(t => getTextTokens(tokenizerType, t)) | ||
| 500 | : undefined; | 526 | : undefined; |
| 501 | 527 | ||
| 502 | const badWordIds = (tokenizerType !== tokenizers.NONE) | 528 | const badWordIds = (tokenizerType !== tokenizers.NONE) |
| @@ -515,11 +541,9 @@ export function getNovelGenerationData(finalPrompt, settings, maxLength, isImper | |||
| 515 | console.log(finalPrompt); | 541 | console.log(finalPrompt); |
| 516 | } | 542 | } |
| 517 | 543 | ||
| 518 | const isKayra = nai_settings.model_novel.includes('kayra'); | ||
| 519 | const isErato = nai_settings.model_novel.includes('erato'); | ||
| 520 | 544 | ||
| 521 | if (isErato) { | 545 | if (isErato) { |
| 522 | finalPrompt = '<|startoftext|>' + finalPrompt; | 546 | finalPrompt = '<|startoftext|><|reserved_special_token81|>' + finalPrompt; |
| 523 | } | 547 | } |
| 524 | 548 | ||
| 525 | const adjustedMaxLength = (isKayra || isErato) ? getKayraMaxResponseTokens() : maximum_output_length; | 549 | const adjustedMaxLength = (isKayra || isErato) ? getKayraMaxResponseTokens() : maximum_output_length; |
| @@ -3469,7 +3469,7 @@ function getModelOptions(quiet) { | |||
| 3469 | case 'openai': | 3469 | case 'openai': |
| 3470 | return oai_settings.chat_completion_source; | 3470 | return oai_settings.chat_completion_source; |
| 3471 | default: | 3471 | default: |
| 3472 | return nullResult; | 3472 | return null; |
| 3473 | } | 3473 | } |
| 3474 | } | 3474 | } |
| 3475 | 3475 | ||
| @@ -143,7 +143,7 @@ function selectSystemPromptCallback(args, name) { | |||
| 143 | foundName = result[0].item; | 143 | foundName = result[0].item; |
| 144 | } | 144 | } |
| 145 | 145 | ||
| 146 | $select.val(foundName).trigger('input'); | 146 | $select.val(foundName).trigger('change'); |
| 147 | !quiet && toastr.success(`System prompt "${foundName}" selected`); | 147 | !quiet && toastr.success(`System prompt "${foundName}" selected`); |
| 148 | return foundName; | 148 | return foundName; |
| 149 | } | 149 | } |
| @@ -68,6 +68,10 @@ const LLAMACPP_DEFAULT_ORDER = [ | |||
| 68 | 'temperature', | 68 | 'temperature', |
| 69 | ]; | 69 | ]; |
| 70 | const OOBA_DEFAULT_ORDER = [ | 70 | const OOBA_DEFAULT_ORDER = [ |
| 71 | 'repetition_penalty', | ||
| 72 | 'presence_penalty', | ||
| 73 | 'frequency_penalty', | ||
| 74 | 'dry', | ||
| 71 | 'temperature', | 75 | 'temperature', |
| 72 | 'dynamic_temperature', | 76 | 'dynamic_temperature', |
| 73 | 'quadratic_sampling', | 77 | 'quadratic_sampling', |
| @@ -80,6 +84,9 @@ const OOBA_DEFAULT_ORDER = [ | |||
| 80 | 'top_a', | 84 | 'top_a', |
| 81 | 'min_p', | 85 | 'min_p', |
| 82 | 'mirostat', | 86 | 'mirostat', |
| 87 | 'xtc', | ||
| 88 | 'encoder_repetition_penalty', | ||
| 89 | 'no_repeat_ngram', | ||
| 83 | ]; | 90 | ]; |
| 84 | const BIAS_KEY = '#textgenerationwebui_api-settings'; | 91 | const BIAS_KEY = '#textgenerationwebui_api-settings'; |
| 85 | 92 | ||
| @@ -798,6 +805,25 @@ function showTypeSpecificControls(type) { | |||
| 798 | }); | 805 | }); |
| 799 | } | 806 | } |
| 800 | 807 | ||
| 808 | /** | ||
| 809 | * Inserts missing items from the source array into the target array. | ||
| 810 | * @param {any[]} source - Source array | ||
| 811 | * @param {any[]} target - Target array | ||
| 812 | * @returns {void} | ||
| 813 | */ | ||
| 814 | function insertMissingArrayItems(source, target) { | ||
| 815 | if (source === target || !Array.isArray(source) || !Array.isArray(target)) { | ||
| 816 | return; | ||
| 817 | } | ||
| 818 | |||
| 819 | for (const item of source) { | ||
| 820 | if (!target.includes(item)) { | ||
| 821 | const index = source.indexOf(item); | ||
| 822 | target.splice(index, 0, item); | ||
| 823 | } | ||
| 824 | } | ||
| 825 | } | ||
| 826 | |||
| 801 | function setSettingByName(setting, value, trigger) { | 827 | function setSettingByName(setting, value, trigger) { |
| 802 | if (value === null || value === undefined) { | 828 | if (value === null || value === undefined) { |
| 803 | return; | 829 | return; |
| @@ -812,6 +838,7 @@ function setSettingByName(setting, value, trigger) { | |||
| 812 | 838 | ||
| 813 | if ('sampler_priority' === setting) { | 839 | if ('sampler_priority' === setting) { |
| 814 | value = Array.isArray(value) ? value : OOBA_DEFAULT_ORDER; | 840 | value = Array.isArray(value) ? value : OOBA_DEFAULT_ORDER; |
| 841 | insertMissingArrayItems(OOBA_DEFAULT_ORDER, value); | ||
| 815 | sortOobaItemsByOrder(value); | 842 | sortOobaItemsByOrder(value); |
| 816 | settings.sampler_priority = value; | 843 | settings.sampler_priority = value; |
| 817 | return; | 844 | return; |
| @@ -1,5 +1,6 @@ | |||
| 1 | import { chat_metadata, getCurrentChatId, saveSettingsDebounced, sendSystemMessage, system_message_types } from '../script.js'; | 1 | import { chat_metadata, getCurrentChatId, saveSettingsDebounced, sendSystemMessage, system_message_types } from '../script.js'; |
| 2 | import { extension_settings, saveMetadataDebounced } from './extensions.js'; | 2 | import { extension_settings, saveMetadataDebounced } from './extensions.js'; |
| 3 | import { callGenericPopup, POPUP_TYPE } from './popup.js'; | ||
| 3 | import { executeSlashCommandsWithOptions } from './slash-commands.js'; | 4 | import { executeSlashCommandsWithOptions } from './slash-commands.js'; |
| 4 | import { SlashCommand } from './slash-commands/SlashCommand.js'; | 5 | import { SlashCommand } from './slash-commands/SlashCommand.js'; |
| 5 | import { SlashCommandAbortController } from './slash-commands/SlashCommandAbortController.js'; | 6 | import { SlashCommandAbortController } from './slash-commands/SlashCommandAbortController.js'; |
| @@ -11,7 +12,7 @@ import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCom | |||
| 11 | import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js'; | 12 | import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js'; |
| 12 | import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js'; | 13 | import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js'; |
| 13 | import { SlashCommandScope } from './slash-commands/SlashCommandScope.js'; | 14 | import { SlashCommandScope } from './slash-commands/SlashCommandScope.js'; |
| 14 | import { isFalseBoolean, convertValueType } from './utils.js'; | 15 | import { isFalseBoolean, convertValueType, isTrueBoolean } from './utils.js'; |
| 15 | 16 | ||
| 16 | /** @typedef {import('./slash-commands/SlashCommandParser.js').NamedArguments} NamedArguments */ | 17 | /** @typedef {import('./slash-commands/SlashCommandParser.js').NamedArguments} NamedArguments */ |
| 17 | /** @typedef {import('./slash-commands/SlashCommand.js').UnnamedArguments} UnnamedArguments */ | 18 | /** @typedef {import('./slash-commands/SlashCommand.js').UnnamedArguments} UnnamedArguments */ |
| @@ -303,24 +304,48 @@ export function replaceVariableMacros(input) { | |||
| 303 | return lines.join('\n'); | 304 | return lines.join('\n'); |
| 304 | } | 305 | } |
| 305 | 306 | ||
| 306 | function listVariablesCallback() { | 307 | async function listVariablesCallback(args) { |
| 308 | const type = String(args?.format || '').toLowerCase().trim() || 'popup'; | ||
| 309 | const scope = String(args?.scope || '').toLowerCase().trim() || 'all'; | ||
| 307 | if (!chat_metadata.variables) { | 310 | if (!chat_metadata.variables) { |
| 308 | chat_metadata.variables = {}; | 311 | chat_metadata.variables = {}; |
| 309 | } | 312 | } |
| 310 | 313 | ||
| 311 | const localVariables = Object.entries(chat_metadata.variables).map(([name, value]) => `${name}: ${value}`); | 314 | const includeLocalVariables = scope === 'all' || scope === 'local'; |
| 312 | const globalVariables = Object.entries(extension_settings.variables.global).map(([name, value]) => `${name}: ${value}`); | 315 | const includeGlobalVariables = scope === 'all' || scope === 'global'; |
| 316 | |||
| 317 | const localVariables = includeLocalVariables ? Object.entries(chat_metadata.variables).map(([name, value]) => `${name}: ${value}`) : []; | ||
| 318 | const globalVariables = includeGlobalVariables ? Object.entries(extension_settings.variables.global).map(([name, value]) => `${name}: ${value}`) : []; | ||
| 319 | |||
| 320 | const jsonVariables = [ | ||
| 321 | ...Object.entries(chat_metadata.variables).map(x => ({ key: x[0], value: x[1], scope: 'local' })), | ||
| 322 | ...Object.entries(extension_settings.variables.global).map(x => ({ key: x[0], value: x[1], scope: 'global' })), | ||
| 323 | ]; | ||
| 313 | 324 | ||
| 314 | const localVariablesString = localVariables.length > 0 ? localVariables.join('\n\n') : 'No local variables'; | 325 | const localVariablesString = localVariables.length > 0 ? localVariables.join('\n\n') : 'No local variables'; |
| 315 | const globalVariablesString = globalVariables.length > 0 ? globalVariables.join('\n\n') : 'No global variables'; | 326 | const globalVariablesString = globalVariables.length > 0 ? globalVariables.join('\n\n') : 'No global variables'; |
| 316 | const chatName = getCurrentChatId(); | 327 | const chatName = getCurrentChatId(); |
| 317 | 328 | ||
| 318 | const converter = new showdown.Converter(); | 329 | const converter = new showdown.Converter(); |
| 319 | const message = `### Local variables (${chatName}):\n${localVariablesString}\n\n### Global variables:\n${globalVariablesString}`; | 330 | const message = [ |
| 331 | includeLocalVariables ? `### Local variables (${chatName}):\n${localVariablesString}` : '', | ||
| 332 | includeGlobalVariables ? `### Global variables:\n${globalVariablesString}` : '', | ||
| 333 | ].filter(x => x).join('\n\n'); | ||
| 320 | const htmlMessage = DOMPurify.sanitize(converter.makeHtml(message)); | 334 | const htmlMessage = DOMPurify.sanitize(converter.makeHtml(message)); |
| 321 | 335 | ||
| 322 | sendSystemMessage(system_message_types.GENERIC, htmlMessage); | 336 | switch (type) { |
| 323 | return ''; | 337 | case 'none': |
| 338 | break; | ||
| 339 | case 'chat': | ||
| 340 | sendSystemMessage(system_message_types.GENERIC, htmlMessage); | ||
| 341 | break; | ||
| 342 | case 'popup': | ||
| 343 | default: | ||
| 344 | await callGenericPopup(htmlMessage, POPUP_TYPE.TEXT); | ||
| 345 | break; | ||
| 346 | } | ||
| 347 | |||
| 348 | return JSON.stringify(jsonVariables); | ||
| 324 | } | 349 | } |
| 325 | 350 | ||
| 326 | /** | 351 | /** |
| @@ -463,7 +488,7 @@ function existsGlobalVariable(name) { | |||
| 463 | /** | 488 | /** |
| 464 | * Parses boolean operands from command arguments. | 489 | * Parses boolean operands from command arguments. |
| 465 | * @param {object} args Command arguments | 490 | * @param {object} args Command arguments |
| 466 | * @returns {{a: string | number, b: string | number, rule: string}} Boolean operands | 491 | * @returns {{a: string | number, b: string | number?, rule: string}} Boolean operands |
| 467 | */ | 492 | */ |
| 468 | export function parseBooleanOperands(args) { | 493 | export function parseBooleanOperands(args) { |
| 469 | // Resolution order: numeric literal, local variable, global variable, string literal | 494 | // Resolution order: numeric literal, local variable, global variable, string literal |
| @@ -472,6 +497,9 @@ export function parseBooleanOperands(args) { | |||
| 472 | */ | 497 | */ |
| 473 | function getOperand(operand) { | 498 | function getOperand(operand) { |
| 474 | if (operand === undefined) { | 499 | if (operand === undefined) { |
| 500 | return undefined; | ||
| 501 | } | ||
| 502 | if (operand === '') { | ||
| 475 | return ''; | 503 | return ''; |
| 476 | } | 504 | } |
| 477 | 505 | ||
| @@ -500,8 +528,8 @@ export function parseBooleanOperands(args) { | |||
| 500 | return stringLiteral || ''; | 528 | return stringLiteral || ''; |
| 501 | } | 529 | } |
| 502 | 530 | ||
| 503 | const left = getOperand(args.a || args.left || args.first || args.x); | 531 | const left = getOperand(args.a ?? args.left ?? args.first ?? args.x); |
| 504 | const right = getOperand(args.b || args.right || args.second || args.y); | 532 | const right = getOperand(args.b ?? args.right ?? args.second ?? args.y); |
| 505 | const rule = args.rule; | 533 | const rule = args.rule; |
| 506 | 534 | ||
| 507 | return { a: left, b: right, rule }; | 535 | return { a: left, b: right, rule }; |
| @@ -509,84 +537,79 @@ export function parseBooleanOperands(args) { | |||
| 509 | 537 | ||
| 510 | /** | 538 | /** |
| 511 | * Evaluates a boolean comparison rule. | 539 | * Evaluates a boolean comparison rule. |
| 512 | * @param {string} rule Boolean comparison rule | 540 | * |
| 541 | * @param {string?} rule Boolean comparison rule | ||
| 513 | * @param {string|number} a The left operand | 542 | * @param {string|number} a The left operand |
| 514 | * @param {string|number} b The right operand | 543 | * @param {string|number?} b The right operand |
| 515 | * @returns {boolean} True if the rule yields true, false otherwise | 544 | * @returns {boolean} True if the rule yields true, false otherwise |
| 516 | */ | 545 | */ |
| 517 | export function evalBoolean(rule, a, b) { | 546 | export function evalBoolean(rule, a, b) { |
| 518 | if (!rule) { | 547 | if (a === undefined) { |
| 519 | toastr.warning('The rule must be specified for the boolean comparison.', 'Invalid command'); | 548 | throw new Error('Left operand is not provided'); |
| 520 | throw new Error('Invalid command.'); | 549 | } |
| 550 | |||
| 551 | // If right-hand side was not provided, whe just check if the left side is truthy | ||
| 552 | if (b === undefined) { | ||
| 553 | switch (rule) { | ||
| 554 | case undefined: | ||
| 555 | case 'not': { | ||
| 556 | const resultOnTruthy = rule !== 'not'; | ||
| 557 | if (isTrueBoolean(String(a))) return resultOnTruthy; | ||
| 558 | if (isFalseBoolean(String(a))) return !resultOnTruthy; | ||
| 559 | return a ? resultOnTruthy : !resultOnTruthy; | ||
| 560 | } | ||
| 561 | default: | ||
| 562 | 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}`); | ||
| 563 | } | ||
| 521 | } | 564 | } |
| 522 | 565 | ||
| 523 | let result = false; | 566 | // If no rule was provided, we are implicitly using 'eq', as defined for the slash commands |
| 567 | rule ??= 'eq'; | ||
| 568 | |||
| 524 | if (typeof a === 'number' && typeof b === 'number') { | 569 | if (typeof a === 'number' && typeof b === 'number') { |
| 525 | // only do numeric comparison if both operands are numbers | 570 | // only do numeric comparison if both operands are numbers |
| 526 | const aNumber = Number(a); | 571 | const aNumber = Number(a); |
| 527 | const bNumber = Number(b); | 572 | const bNumber = Number(b); |
| 528 | 573 | ||
| 529 | switch (rule) { | 574 | switch (rule) { |
| 530 | case 'not': | ||
| 531 | result = !aNumber; | ||
| 532 | break; | ||
| 533 | case 'gt': | 575 | case 'gt': |
| 534 | result = aNumber > bNumber; | 576 | return aNumber > bNumber; |
| 535 | break; | ||
| 536 | case 'gte': | 577 | case 'gte': |
| 537 | result = aNumber >= bNumber; | 578 | return aNumber >= bNumber; |
| 538 | break; | ||
| 539 | case 'lt': | 579 | case 'lt': |
| 540 | result = aNumber < bNumber; | 580 | return aNumber < bNumber; |
| 541 | break; | ||
| 542 | case 'lte': | 581 | case 'lte': |
| 543 | result = aNumber <= bNumber; | 582 | return aNumber <= bNumber; |
| 544 | break; | ||
| 545 | case 'eq': | 583 | case 'eq': |
| 546 | result = aNumber === bNumber; | 584 | return aNumber === bNumber; |
| 547 | break; | ||
| 548 | case 'neq': | 585 | case 'neq': |
| 549 | result = aNumber !== bNumber; | 586 | return aNumber !== bNumber; |
| 550 | break; | ||
| 551 | default: | ||
| 552 | toastr.error('Unknown boolean comparison rule for type number.', 'Invalid command'); | ||
| 553 | throw new Error('Invalid command.'); | ||
| 554 | } | ||
| 555 | } else { | ||
| 556 | // otherwise do case-insensitive string comparsion, stringify non-strings | ||
| 557 | let aString; | ||
| 558 | let bString; | ||
| 559 | if (typeof a == 'string') { | ||
| 560 | aString = a.toLowerCase(); | ||
| 561 | } else { | ||
| 562 | aString = JSON.stringify(a).toLowerCase(); | ||
| 563 | } | ||
| 564 | if (typeof b == 'string') { | ||
| 565 | bString = b.toLowerCase(); | ||
| 566 | } else { | ||
| 567 | bString = JSON.stringify(b).toLowerCase(); | ||
| 568 | } | ||
| 569 | |||
| 570 | switch (rule) { | ||
| 571 | case 'in': | 587 | case 'in': |
| 572 | result = aString.includes(bString); | ||
| 573 | break; | ||
| 574 | case 'nin': | 588 | case 'nin': |
| 575 | result = !aString.includes(bString); | 589 | // Fall through to string comparison. Otherwise you could not check if 12345 contains 45 for example. |
| 576 | break; | 590 | console.debug(`Boolean comparison rule '${rule}' is not supported for type number. Falling back to string comparison.`); |
| 577 | case 'eq': | ||
| 578 | result = aString === bString; | ||
| 579 | break; | ||
| 580 | case 'neq': | ||
| 581 | result = aString !== bString; | ||
| 582 | break; | 591 | break; |
| 583 | default: | 592 | default: |
| 584 | toastr.error('Unknown boolean comparison rule for type string.', 'Invalid /if command'); | 593 | throw new Error(`Unknown boolean comparison rule for type number. Accepted: gt, gte, lt, lte, eq, neq. Provided: ${rule}`); |
| 585 | throw new Error('Invalid command.'); | ||
| 586 | } | 594 | } |
| 587 | } | 595 | } |
| 588 | 596 | ||
| 589 | return result; | 597 | // otherwise do case-insensitive string comparsion, stringify non-strings |
| 598 | let aString = (typeof a === 'string') ? a.toLowerCase() : JSON.stringify(a).toLowerCase(); | ||
| 599 | let bString = (typeof b === 'string') ? b.toLowerCase() : JSON.stringify(b).toLowerCase(); | ||
| 600 | |||
| 601 | switch (rule) { | ||
| 602 | case 'in': | ||
| 603 | return aString.includes(bString); | ||
| 604 | case 'nin': | ||
| 605 | return !aString.includes(bString); | ||
| 606 | case 'eq': | ||
| 607 | return aString === bString; | ||
| 608 | case 'neq': | ||
| 609 | return aString !== bString; | ||
| 610 | default: | ||
| 611 | throw new Error(`Unknown boolean comparison rule for type number. Accepted: in, nin, eq, neq. Provided: ${rule}`); | ||
| 612 | } | ||
| 590 | } | 613 | } |
| 591 | 614 | ||
| 592 | /** | 615 | /** |
| @@ -887,7 +910,35 @@ export function registerVariableCommands() { | |||
| 887 | name: 'listvar', | 910 | name: 'listvar', |
| 888 | callback: listVariablesCallback, | 911 | callback: listVariablesCallback, |
| 889 | aliases: ['listchatvar'], | 912 | aliases: ['listchatvar'], |
| 890 | helpString: 'List registered chat variables.', | 913 | helpString: 'List registered chat variables. Displays variables in a popup by default. Use the <code>format</code> argument to change the output format.', |
| 914 | returns: 'JSON list of local variables', | ||
| 915 | namedArgumentList: [ | ||
| 916 | SlashCommandNamedArgument.fromProps({ | ||
| 917 | name: 'scope', | ||
| 918 | description: 'filter variables by scope', | ||
| 919 | typeList: [ARGUMENT_TYPE.STRING], | ||
| 920 | defaultValue: 'all', | ||
| 921 | isRequired: false, | ||
| 922 | forceEnum: true, | ||
| 923 | enumList: [ | ||
| 924 | new SlashCommandEnumValue('all', 'All variables', enumTypes.enum, enumIcons.variable), | ||
| 925 | new SlashCommandEnumValue('local', 'Local variables', enumTypes.enum, enumIcons.localVariable), | ||
| 926 | new SlashCommandEnumValue('global', 'Global variables', enumTypes.enum, enumIcons.globalVariable), | ||
| 927 | ], | ||
| 928 | }), | ||
| 929 | SlashCommandNamedArgument.fromProps({ | ||
| 930 | name: 'format', | ||
| 931 | description: 'output format', | ||
| 932 | typeList: [ARGUMENT_TYPE.STRING], | ||
| 933 | isRequired: true, | ||
| 934 | forceEnum: true, | ||
| 935 | enumList: [ | ||
| 936 | new SlashCommandEnumValue('popup', 'Show variables in a popup.', enumTypes.enum, enumIcons.default), | ||
| 937 | new SlashCommandEnumValue('chat', 'Post a system message to the chat.', enumTypes.enum, enumIcons.message), | ||
| 938 | new SlashCommandEnumValue('none', 'Just return the variables as a JSON list.', enumTypes.enum, enumIcons.array), | ||
| 939 | ], | ||
| 940 | }), | ||
| 941 | ], | ||
| 891 | })); | 942 | })); |
| 892 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | 943 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| 893 | name: 'setvar', | 944 | name: 'setvar', |
| @@ -1264,32 +1315,36 @@ export function registerVariableCommands() { | |||
| 1264 | typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER], | 1315 | typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER], |
| 1265 | isRequired: true, | 1316 | isRequired: true, |
| 1266 | enumProvider: commonEnumProviders.variables('all'), | 1317 | enumProvider: commonEnumProviders.variables('all'), |
| 1267 | forceEnum: false, | ||
| 1268 | }), | 1318 | }), |
| 1269 | SlashCommandNamedArgument.fromProps({ | 1319 | SlashCommandNamedArgument.fromProps({ |
| 1270 | name: 'right', | 1320 | name: 'right', |
| 1271 | description: 'right operand', | 1321 | description: 'right operand', |
| 1272 | typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER], | 1322 | typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER], |
| 1273 | isRequired: true, | ||
| 1274 | enumProvider: commonEnumProviders.variables('all'), | 1323 | enumProvider: commonEnumProviders.variables('all'), |
| 1275 | forceEnum: false, | ||
| 1276 | }), | 1324 | }), |
| 1277 | new SlashCommandNamedArgument( | 1325 | SlashCommandNamedArgument.fromProps({ |
| 1278 | 'rule', 'comparison rule', [ARGUMENT_TYPE.STRING], true, false, null, [ | 1326 | name: 'rule', |
| 1279 | new SlashCommandEnumValue('gt', 'a > b'), | 1327 | description: 'comparison rule', |
| 1280 | new SlashCommandEnumValue('gte', 'a >= b'), | 1328 | typeList: [ARGUMENT_TYPE.STRING], |
| 1281 | new SlashCommandEnumValue('lt', 'a < b'), | 1329 | defaultValue: 'eq', |
| 1282 | new SlashCommandEnumValue('lte', 'a <= b'), | 1330 | enumList: [ |
| 1283 | new SlashCommandEnumValue('eq', 'a == b'), | 1331 | new SlashCommandEnumValue('eq', 'a == b (strings & numbers)'), |
| 1284 | new SlashCommandEnumValue('neq', 'a !== b'), | 1332 | new SlashCommandEnumValue('neq', 'a !== b (strings & numbers)'), |
| 1285 | new SlashCommandEnumValue('not', '!a'), | 1333 | new SlashCommandEnumValue('in', 'a includes b (strings & numbers as strings)'), |
| 1286 | new SlashCommandEnumValue('in', 'a includes b'), | 1334 | new SlashCommandEnumValue('nin', 'a not includes b (strings & numbers as strings)'), |
| 1287 | new SlashCommandEnumValue('nin', 'a not includes b'), | 1335 | new SlashCommandEnumValue('gt', 'a > b (numbers)'), |
| 1336 | new SlashCommandEnumValue('gte', 'a >= b (numbers)'), | ||
| 1337 | new SlashCommandEnumValue('lt', 'a < b (numbers)'), | ||
| 1338 | new SlashCommandEnumValue('lte', 'a <= b (numbers)'), | ||
| 1339 | new SlashCommandEnumValue('not', '!a (truthy)'), | ||
| 1288 | ], | 1340 | ], |
| 1289 | ), | 1341 | forceEnum: true, |
| 1290 | new SlashCommandNamedArgument( | 1342 | }), |
| 1291 | 'else', 'command to execute if not true', [ARGUMENT_TYPE.CLOSURE, ARGUMENT_TYPE.SUBCOMMAND], false, | 1343 | SlashCommandNamedArgument.fromProps({ |
| 1292 | ), | 1344 | name: 'else', |
| 1345 | description: 'command to execute if not true', | ||
| 1346 | typeList: [ARGUMENT_TYPE.CLOSURE, ARGUMENT_TYPE.SUBCOMMAND], | ||
| 1347 | }), | ||
| 1293 | ], | 1348 | ], |
| 1294 | unnamedArgumentList: [ | 1349 | unnamedArgumentList: [ |
| 1295 | new SlashCommandArgument( | 1350 | new SlashCommandArgument( |
| @@ -1307,17 +1362,25 @@ export function registerVariableCommands() { | |||
| 1307 | Numeric values and string literals for left and right operands supported. | 1362 | Numeric values and string literals for left and right operands supported. |
| 1308 | </div> | 1363 | </div> |
| 1309 | <div> | 1364 | <div> |
| 1365 | If the rule is not provided, it defaults to <code>eq</code>. | ||
| 1366 | </div> | ||
| 1367 | <div> | ||
| 1368 | If no right operand is provided, it defaults to checking the <code>left</code> value to be truthy. | ||
| 1369 | A non-empty string or non-zero number is considered truthy, as is the value <code>true</code> or <code>on</code>.<br /> | ||
| 1370 | 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. | ||
| 1371 | </div> | ||
| 1372 | <div> | ||
| 1310 | <strong>Available rules:</strong> | 1373 | <strong>Available rules:</strong> |
| 1311 | <ul> | 1374 | <ul> |
| 1312 | <li>gt => a > b</li> | 1375 | <li><code>eq</code> => a == b <small>(strings & numbers)</small></li> |
| 1313 | <li>gte => a >= b</li> | 1376 | <li><code>neq</code> => a !== b <small>(strings & numbers)</small></li> |
| 1314 | <li>lt => a < b</li> | 1377 | <li><code>in</code> => a includes b <small>(strings & numbers as strings)</small></li> |
| 1315 | <li>lte => a <= b</li> | 1378 | <li><code>nin</code> => a not includes b <small>(strings & numbers as strings)</small></li> |
| 1316 | <li>eq => a == b</li> | 1379 | <li><code>gt</code> => a > b <small>(numbers)</small></li> |
| 1317 | <li>neq => a != b</li> | 1380 | <li><code>gte</code> => a >= b <small>(numbers)</small></li> |
| 1318 | <li>not => !a</li> | 1381 | <li><code>lt</code> => a < b <small>(numbers)</small></li> |
| 1319 | <li>in (strings) => a includes b</li> | 1382 | <li><code>lte</code> => a <= b <small>(numbers)</small></li> |
| 1320 | <li>nin (strings) => a not includes b</li> | 1383 | <li><code>not</code> => !a <small>(truthy)</small></li> |
| 1321 | </ul> | 1384 | </ul> |
| 1322 | </div> | 1385 | </div> |
| 1323 | <div> | 1386 | <div> |
| @@ -1327,6 +1390,17 @@ export function registerVariableCommands() { | |||
| 1327 | <pre><code class="language-stscript">/if left=score right=10 rule=gte "/speak You win"</code></pre> | 1390 | <pre><code class="language-stscript">/if left=score right=10 rule=gte "/speak You win"</code></pre> |
| 1328 | triggers a /speak command if the value of "score" is greater or equals 10. | 1391 | triggers a /speak command if the value of "score" is greater or equals 10. |
| 1329 | </li> | 1392 | </li> |
| 1393 | <li> | ||
| 1394 | <pre><code class="language-stscript">/if left={{lastMessage}} rule=in right=surprise {: /echo SURPISE! :}</code></pre> | ||
| 1395 | executes a subcommand defined as a closure if the given value contains a specified word. | ||
| 1396 | <li> | ||
| 1397 | <pre><code class="language-stscript">/if left=myContent {: /echo My content had some content. :}</code></pre> | ||
| 1398 | executes the defined subcommand, if the provided value of left is truthy (contains some kind of contant that is not empty or false) | ||
| 1399 | </li> | ||
| 1400 | <li> | ||
| 1401 | <pre><code class="language-stscript">/if left=tree right={{getvar::object}} {: /echo The object is a tree! :}</code></pre> | ||
| 1402 | executes the defined subcommand, if the left and right values are equals. | ||
| 1403 | </li> | ||
| 1330 | </ul> | 1404 | </ul> |
| 1331 | </div> | 1405 | </div> |
| 1332 | `, | 1406 | `, |
| @@ -1342,32 +1416,38 @@ export function registerVariableCommands() { | |||
| 1342 | typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER], | 1416 | typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER], |
| 1343 | isRequired: true, | 1417 | isRequired: true, |
| 1344 | enumProvider: commonEnumProviders.variables('all'), | 1418 | enumProvider: commonEnumProviders.variables('all'), |
| 1345 | forceEnum: false, | ||
| 1346 | }), | 1419 | }), |
| 1347 | SlashCommandNamedArgument.fromProps({ | 1420 | SlashCommandNamedArgument.fromProps({ |
| 1348 | name: 'right', | 1421 | name: 'right', |
| 1349 | description: 'right operand', | 1422 | description: 'right operand', |
| 1350 | typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER], | 1423 | typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER], |
| 1351 | isRequired: true, | ||
| 1352 | enumProvider: commonEnumProviders.variables('all'), | 1424 | enumProvider: commonEnumProviders.variables('all'), |
| 1353 | forceEnum: false, | ||
| 1354 | }), | 1425 | }), |
| 1355 | new SlashCommandNamedArgument( | 1426 | SlashCommandNamedArgument.fromProps({ |
| 1356 | 'rule', 'comparison rule', [ARGUMENT_TYPE.STRING], true, false, null, [ | 1427 | name: 'rule', |
| 1357 | new SlashCommandEnumValue('gt', 'a > b'), | 1428 | description: 'comparison rule', |
| 1358 | new SlashCommandEnumValue('gte', 'a >= b'), | 1429 | typeList: [ARGUMENT_TYPE.STRING], |
| 1359 | new SlashCommandEnumValue('lt', 'a < b'), | 1430 | defaultValue: 'eq', |
| 1360 | new SlashCommandEnumValue('lte', 'a <= b'), | 1431 | enumList: [ |
| 1361 | new SlashCommandEnumValue('eq', 'a == b'), | 1432 | new SlashCommandEnumValue('eq', 'a == b (strings & numbers)'), |
| 1362 | new SlashCommandEnumValue('neq', 'a !== b'), | 1433 | new SlashCommandEnumValue('neq', 'a !== b (strings & numbers)'), |
| 1363 | new SlashCommandEnumValue('not', '!a'), | 1434 | new SlashCommandEnumValue('in', 'a includes b (strings & numbers as strings)'), |
| 1364 | new SlashCommandEnumValue('in', 'a includes b'), | 1435 | new SlashCommandEnumValue('nin', 'a not includes b (strings & numbers as strings)'), |
| 1365 | new SlashCommandEnumValue('nin', 'a not includes b'), | 1436 | new SlashCommandEnumValue('gt', 'a > b (numbers)'), |
| 1437 | new SlashCommandEnumValue('gte', 'a >= b (numbers)'), | ||
| 1438 | new SlashCommandEnumValue('lt', 'a < b (numbers)'), | ||
| 1439 | new SlashCommandEnumValue('lte', 'a <= b (numbers)'), | ||
| 1440 | new SlashCommandEnumValue('not', '!a (truthy)'), | ||
| 1366 | ], | 1441 | ], |
| 1367 | ), | 1442 | forceEnum: true, |
| 1368 | new SlashCommandNamedArgument( | 1443 | }), |
| 1369 | 'guard', 'disable loop iteration limit', [ARGUMENT_TYPE.STRING], false, false, null, commonEnumProviders.boolean('onOff')(), | 1444 | SlashCommandNamedArgument.fromProps({ |
| 1370 | ), | 1445 | name: 'guard', |
| 1446 | description: 'disable loop iteration limit', | ||
| 1447 | typeList: [ARGUMENT_TYPE.STRING], | ||
| 1448 | defaultValue: 'off', | ||
| 1449 | enumList: commonEnumProviders.boolean('onOff')(), | ||
| 1450 | }), | ||
| 1371 | ], | 1451 | ], |
| 1372 | unnamedArgumentList: [ | 1452 | unnamedArgumentList: [ |
| 1373 | new SlashCommandArgument( | 1453 | new SlashCommandArgument( |
| @@ -1386,15 +1466,15 @@ export function registerVariableCommands() { | |||
| 1386 | <div> | 1466 | <div> |
| 1387 | <strong>Available rules:</strong> | 1467 | <strong>Available rules:</strong> |
| 1388 | <ul> | 1468 | <ul> |
| 1389 | <li>gt => a > b</li> | 1469 | <li><code>eq</code> => a == b <small>(strings & numbers)</small></li> |
| 1390 | <li>gte => a >= b</li> | 1470 | <li><code>neq</code> => a !== b <small>(strings & numbers)</small></li> |
| 1391 | <li>lt => a < b</li> | 1471 | <li><code>in</code> => a includes b <small>(strings & numbers as strings)</small></li> |
| 1392 | <li>lte => a <= b</li> | 1472 | <li><code>nin</code> => a not includes b <small>(strings & numbers as strings)</small></li> |
| 1393 | <li>eq => a == b</li> | 1473 | <li><code>gt</code> => a > b <small>(numbers)</small></li> |
| 1394 | <li>neq => a != b</li> | 1474 | <li><code>gte</code> => a >= b <small>(numbers)</small></li> |
| 1395 | <li>not => !a</li> | 1475 | <li><code>lt</code> => a < b <small>(numbers)</small></li> |
| 1396 | <li>in (strings) => a includes b</li> | 1476 | <li><code>lte</code> => a <= b <small>(numbers)</small></li> |
| 1397 | <li>nin (strings) => a not includes b</li> | 1477 | <li><code>not</code> => !a <small>(truthy)</small></li> |
| 1398 | </ul> | 1478 | </ul> |
| 1399 | </div> | 1479 | </div> |
| 1400 | <div> | 1480 | <div> |
| @@ -1404,7 +1484,11 @@ export function registerVariableCommands() { | |||
| 1404 | <pre><code class="language-stscript">/setvar key=i 0 | /while left=i right=10 rule=lte "/addvar key=i 1"</code></pre> | 1484 | <pre><code class="language-stscript">/setvar key=i 0 | /while left=i right=10 rule=lte "/addvar key=i 1"</code></pre> |
| 1405 | adds 1 to the value of "i" until it reaches 10. | 1485 | adds 1 to the value of "i" until it reaches 10. |
| 1406 | </li> | 1486 | </li> |
| 1407 | </ul> | 1487 | <li> |
| 1488 | <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> | ||
| 1489 | executes the defined subcommand as long as the "currentword" variable is truthy (has any content that is not false/empty) | ||
| 1490 | </ul> | ||
| 1491 | </li> | ||
| 1408 | </div> | 1492 | </div> |
| 1409 | <div> | 1493 | <div> |
| 1410 | Loops are limited to 100 iterations by default, pass <code>guard=off</code> to disable. | 1494 | Loops are limited to 100 iterations by default, pass <code>guard=off</code> to disable. |
| @@ -1519,7 +1603,7 @@ export function registerVariableCommands() { | |||
| 1519 | typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], | 1603 | typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], |
| 1520 | isRequired: true, | 1604 | isRequired: true, |
| 1521 | acceptsMultiple: true, | 1605 | acceptsMultiple: true, |
| 1522 | enumProvider: (executor, scope)=>{ | 1606 | enumProvider: (executor, scope) => { |
| 1523 | const vars = commonEnumProviders.variables('all')(executor, scope); | 1607 | const vars = commonEnumProviders.variables('all')(executor, scope); |
| 1524 | vars.push( | 1608 | vars.push( |
| 1525 | new SlashCommandEnumValue( | 1609 | new SlashCommandEnumValue( |
| @@ -1527,16 +1611,16 @@ export function registerVariableCommands() { | |||
| 1527 | null, | 1611 | null, |
| 1528 | enumTypes.variable, | 1612 | enumTypes.variable, |
| 1529 | enumIcons.variable, | 1613 | enumIcons.variable, |
| 1530 | (input)=>/^\w*$/.test(input), | 1614 | (input) => /^\w*$/.test(input), |
| 1531 | (input)=>input, | 1615 | (input) => input, |
| 1532 | ), | 1616 | ), |
| 1533 | new SlashCommandEnumValue( | 1617 | new SlashCommandEnumValue( |
| 1534 | 'any number', | 1618 | 'any number', |
| 1535 | null, | 1619 | null, |
| 1536 | enumTypes.number, | 1620 | enumTypes.number, |
| 1537 | enumIcons.number, | 1621 | enumIcons.number, |
| 1538 | (input)=>input == '' || !Number.isNaN(Number(input)), | 1622 | (input) => input == '' || !Number.isNaN(Number(input)), |
| 1539 | (input)=>input, | 1623 | (input) => input, |
| 1540 | ), | 1624 | ), |
| 1541 | ); | 1625 | ); |
| 1542 | return vars; | 1626 | return vars; |
| @@ -3154,6 +3154,26 @@ grammarly-extension { | |||
| 3154 | align-items: center; | 3154 | align-items: center; |
| 3155 | } | 3155 | } |
| 3156 | 3156 | ||
| 3157 | .alternate_greeting details { | ||
| 3158 | padding: 2px; | ||
| 3159 | } | ||
| 3160 | |||
| 3161 | .alternate_greeting summary { | ||
| 3162 | list-style-position: outside; | ||
| 3163 | margin-left: 1em; | ||
| 3164 | padding-left: 1em; | ||
| 3165 | } | ||
| 3166 | |||
| 3167 | .alternate_greeting textarea { | ||
| 3168 | field-sizing: content; | ||
| 3169 | max-height: 50dvh; | ||
| 3170 | } | ||
| 3171 | |||
| 3172 | .alternate_greeting summary::marker, | ||
| 3173 | .alternate_greeting summary strong { | ||
| 3174 | cursor: pointer; | ||
| 3175 | } | ||
| 3176 | |||
| 3157 | #rm_characters_block .form_create_bottom_buttons_block { | 3177 | #rm_characters_block .form_create_bottom_buttons_block { |
| 3158 | justify-content: space-evenly !important; | 3178 | justify-content: space-evenly !important; |
| 3159 | flex-grow: 0; | 3179 | flex-grow: 0; |
| @@ -3245,8 +3265,9 @@ grammarly-extension { | |||
| 3245 | } | 3265 | } |
| 3246 | 3266 | ||
| 3247 | .wide_dialogue_popup { | 3267 | .wide_dialogue_popup { |
| 3248 | aspect-ratio: 1 / 1; | 3268 | /* FIXME: Chrome 129 broke max-height for aspect-ratio sized elements */ |
| 3249 | width: unset !important; | 3269 | /* aspect-ratio: 1 / 1; */ |
| 3270 | /* width: unset !important; */ | ||
| 3250 | min-width: var(--sheldWidth); | 3271 | min-width: var(--sheldWidth); |
| 3251 | } | 3272 | } |
| 3252 | 3273 | ||
| @@ -5365,13 +5386,6 @@ body:not(.movingUI) .drawer-content.maximized { | |||
| 5365 | 5386 | ||
| 5366 | /* Jank mobile support for gallery and future draggables */ | 5387 | /* Jank mobile support for gallery and future draggables */ |
| 5367 | @media screen and (max-width: 1000px) { | 5388 | @media screen and (max-width: 1000px) { |
| 5368 | #gallery { | ||
| 5369 | display: block; | ||
| 5370 | width: 100vw; | ||
| 5371 | height: 100vh; | ||
| 5372 | z-index: 9999; | ||
| 5373 | } | ||
| 5374 | |||
| 5375 | .draggable { | 5389 | .draggable { |
| 5376 | display: block; | 5390 | display: block; |
| 5377 | width: 100vw; | 5391 | width: 100vw; |
| @@ -347,6 +347,8 @@ async function migrateSystemPrompts() { | |||
| 347 | if (fs.existsSync(migrateMarker)) { | 347 | if (fs.existsSync(migrateMarker)) { |
| 348 | continue; | 348 | continue; |
| 349 | } | 349 | } |
| 350 | const backupsPath = path.join(directory.backups, '_sysprompt'); | ||
| 351 | fs.mkdirSync(backupsPath, { recursive: true }); | ||
| 350 | const defaultPrompts = await getDefaultSystemPrompts(); | 352 | const defaultPrompts = await getDefaultSystemPrompts(); |
| 351 | const instucts = fs.readdirSync(directory.instruct); | 353 | const instucts = fs.readdirSync(directory.instruct); |
| 352 | let migratedPrompts = []; | 354 | let migratedPrompts = []; |
| @@ -356,6 +358,8 @@ async function migrateSystemPrompts() { | |||
| 356 | if (path.extname(instruct) === '.json' && !fs.existsSync(sysPromptPath)) { | 358 | if (path.extname(instruct) === '.json' && !fs.existsSync(sysPromptPath)) { |
| 357 | const instructData = JSON.parse(fs.readFileSync(instructPath, 'utf8')); | 359 | const instructData = JSON.parse(fs.readFileSync(instructPath, 'utf8')); |
| 358 | if ('system_prompt' in instructData && 'name' in instructData) { | 360 | if ('system_prompt' in instructData && 'name' in instructData) { |
| 361 | const backupPath = path.join(backupsPath, `${instructData.name}.json`); | ||
| 362 | fs.cpSync(instructPath, backupPath, { force: true }); | ||
| 359 | const syspromptData = { name: instructData.name, content: instructData.system_prompt }; | 363 | const syspromptData = { name: instructData.name, content: instructData.system_prompt }; |
| 360 | migratedPrompts.push(syspromptData); | 364 | migratedPrompts.push(syspromptData); |
| 361 | delete instructData.system_prompt; | 365 | delete instructData.system_prompt; |
| @@ -374,8 +378,8 @@ async function migrateSystemPrompts() { | |||
| 374 | console.log(`Migrated system prompt ${sysPromptData.name} for ${directory.root.split(path.sep).pop()}`); | 378 | console.log(`Migrated system prompt ${sysPromptData.name} for ${directory.root.split(path.sep).pop()}`); |
| 375 | } | 379 | } |
| 376 | writeFileAtomicSync(migrateMarker, ''); | 380 | writeFileAtomicSync(migrateMarker, ''); |
| 377 | } catch { | 381 | } catch (error) { |
| 378 | // Ignore errors | 382 | console.error('Error migrating system prompts:', error); |
| 379 | } | 383 | } |
| 380 | } | 384 | } |
| 381 | } | 385 | } |