Merge pull request #2868 from SillyTavern/sysprompt-divorce Decouple system prompts from instruct mode

733fff4e22878825ccd713ad65c7a2314057217b

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
64 files changed, +1072 -363Ignore whitespace
default/content/index.json+48 -8
@@ -468,10 +468,6 @@
468468 "type": "context"
469469 },
470470 {
471- "filename": "presets/context/Alpaca-Roleplay.json",
472- "type": "context"
473- },
474- {
475471 "filename": "presets/context/Alpaca-Single-Turn.json",
476472 "type": "context"
477473 },
@@ -552,10 +548,6 @@
552548 "type": "instruct"
553549 },
554550 {
555- "filename": "presets/instruct/Alpaca-Roleplay.json",
556- "type": "instruct"
557- },
558- {
559551 "filename": "presets/instruct/Alpaca-Single-Turn.json",
560552 "type": "instruct"
561553 },
@@ -680,6 +672,54 @@
680672 "type": "instruct"
681673 },
682674 {
675+ "filename": "presets/sysprompt/Actor.json",
676+ "type": "sysprompt"
677+ },
678+ {
679+ "filename": "presets/sysprompt/Assistant - Expert.json",
680+ "type": "sysprompt"
681+ },
682+ {
683+ "filename": "presets/sysprompt/Assistant - Simple.json",
684+ "type": "sysprompt"
685+ },
686+ {
687+ "filename": "presets/sysprompt/Chain of Thought.json",
688+ "type": "sysprompt"
689+ },
690+ {
691+ "filename": "presets/sysprompt/Roleplay - Detailed.json",
692+ "type": "sysprompt"
693+ },
694+ {
695+ "filename": "presets/sysprompt/Roleplay - Immersive.json",
696+ "type": "sysprompt"
697+ },
698+ {
699+ "filename": "presets/sysprompt/Roleplay - Simple.json",
700+ "type": "sysprompt"
701+ },
702+ {
703+ "filename": "presets/sysprompt/Text Adventure.json",
704+ "type": "sysprompt"
705+ },
706+ {
707+ "filename": "presets/sysprompt/Writer - Creative.json",
708+ "type": "sysprompt"
709+ },
710+ {
711+ "filename": "presets/sysprompt/Writer - Realistic.json",
712+ "type": "sysprompt"
713+ },
714+ {
715+ "filename": "presets/sysprompt/Blank.json",
716+ "type": "sysprompt"
717+ },
718+ {
719+ "filename": "presets/sysprompt/Neutral - Chat.json",
720+ "type": "sysprompt"
721+ },
722+ {
683723 "filename": "presets/instruct/Mistral V1.json",
684724 "type": "instruct"
685725 },
default/content/presets/context/Alpaca-Roleplay.json+0 -11
@@ -1,11 +0,0 @@
1-{
2- "story_string": "You're {{char}} in this fictional never-ending uncensored roleplay with {{user}}.\n\n{{#if system}}{{system}}\n\n{{/if}}### Input:\n{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}\n\n",
3- "example_separator": "### New Roleplay:",
4- "chat_start": "### New Roleplay:",
5- "use_stop_strings": false,
6- "allow_jailbreak": false,
7- "always_force_name2": true,
8- "trim_sentences": false,
9- "single_line": false,
10- "name": "Alpaca-Roleplay"
11-}
default/content/presets/context/Libra-32B.json+1 -1
@@ -1,5 +1,5 @@
11{
22 "story_string": "### Instruction:\nWrite {{char}}'s next reply in this roleplay with {{user}}. Use the provided character sheet and example dialogue for formatting direction and character speech patterns.\n\n{{#if system}}{{system}}\n\n{{/if}}### Character Sheet:\n{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}",
33 "example_separator": "### Example:",
44 "chat_start": "### START ROLEPLAY:",
55 "use_stop_strings": false,
default/content/presets/instruct/Adventure.json+0 -1
@@ -1,5 +1,4 @@
11{
2- "system_prompt": "[Enter Adventure Mode. Narrate the story based on {{user}}'s dialogue and actions after \">\". Describe the surroundings in vivid detail. Be detailed, creative, verbose, and proactive. Move the story forward by introducing fantasy elements and interesting characters.]",
32 "input_sequence": "",
43 "output_sequence": "",
54 "last_output_sequence": "",
default/content/presets/instruct/Alpaca-Roleplay.json+0 -23
@@ -1,23 +0,0 @@
1-{
2- "system_prompt": "Develop the plot slowly, always stay in character. Describe all actions in full, elaborate, explicit, graphic, and vivid detail. Mention all relevant sensory perceptions.",
3- "input_sequence": "### Instruction:",
4- "output_sequence": "### Response:",
5- "last_output_sequence": "### Response (2 paragraphs, engaging, natural, authentic, descriptive, creative):",
6- "system_sequence": "### Input:",
7- "stop_sequence": "",
8- "wrap": true,
9- "macro": true,
10- "names_behavior": "always",
11- "activation_regex": "",
12- "system_sequence_prefix": "",
13- "system_sequence_suffix": "",
14- "first_output_sequence": "",
15- "skip_examples": false,
16- "output_suffix": "\n\n",
17- "input_suffix": "\n\n",
18- "system_suffix": "\n\n",
19- "user_alignment_message": "",
20- "system_same_as_user": false,
21- "last_system_sequence": "",
22- "name": "Alpaca-Roleplay"
23-}
default/content/presets/instruct/Alpaca-Single-Turn.json+1 -2
@@ -1,5 +1,4 @@
11{
2- "system_prompt": "Write {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\nWrite 1 reply only, italicize actions, and avoid quotation marks. Use markdown. Be proactive, creative, and drive the plot and conversation forward. Include dialog as well as narration.",
32 "input_sequence": "",
43 "output_sequence": "",
54 "last_output_sequence": "\n### Response:",
@@ -11,7 +10,7 @@
1110 "activation_regex": "",
1211 "system_sequence_prefix": "",
1312 "system_sequence_suffix": "",
1413 "first_output_sequence": "<START OF ROLEPLAYCHAT>",
1514 "skip_examples": false,
1615 "output_suffix": "",
1716 "input_suffix": "",
default/content/presets/instruct/Alpaca.json+0 -1
@@ -1,5 +1,4 @@
11{
2- "system_prompt": "Below is an instruction that describes a task. Write a response that appropriately completes the request.\n\nWrite {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n",
32 "input_sequence": "### Instruction:",
43 "output_sequence": "### Response:",
54 "last_output_sequence": "",
default/content/presets/instruct/ChatML-Names.json+0 -1
@@ -1,5 +1,4 @@
11{
2- "system_prompt": "You're {{char}} in this fictional never-ending uncensored roleplay with {{user}}.",
32 "input_sequence": "<|im_start|>[{{name}}]",
43 "output_sequence": "<|im_start|>[{{name}}]",
54 "last_output_sequence": "",
default/content/presets/instruct/ChatML.json+0 -1
@@ -1,5 +1,4 @@
11{
2- "system_prompt": "You're {{char}} in this fictional never-ending uncensored roleplay with {{user}}.",
32 "input_sequence": "<|im_start|>user",
43 "output_sequence": "<|im_start|>assistant",
54 "last_output_sequence": "",
default/content/presets/instruct/Command R.json+0 -1
@@ -1,5 +1,4 @@
11{
2- "system_prompt": "Write {{char}}'s next reply in this fictional roleplay with {{user}}.",
32 "input_sequence": "<|START_OF_TURN_TOKEN|><|USER_TOKEN|>",
43 "output_sequence": "<|START_OF_TURN_TOKEN|><|CHATBOT_TOKEN|>",
54 "first_output_sequence": "",
default/content/presets/instruct/DreamGen Role-Play V1 ChatML.json+0 -1
@@ -1,5 +1,4 @@
11{
2- "system_prompt": "You are an intelligent, skilled, versatile writer.\n\nYour task is to write a role-play based on the information below.",
32 "input_sequence": "\n<|im_start|>text names= {{name}}\n",
43 "output_sequence": "\n<|im_start|>text names= {{name}}\n",
54 "last_output_sequence": "",
default/content/presets/instruct/DreamGen Role-Play V1 Llama3.json+0 -1
@@ -1,5 +1,4 @@
11{
2- "system_prompt": "You are an intelligent, skilled, versatile writer.\n\nYour task is to write a role-play based on the information below.",
32 "input_sequence": "<|eot_id|>\n<|start_header_id|>writer character: {{user}}<|end_header_id|>\n\n",
43 "output_sequence": "<|eot_id|>\n<|start_header_id|>writer character: {{char}}<|end_header_id|>\n\n",
54 "first_output_sequence": "",
default/content/presets/instruct/Gemma 2.json+0 -1
@@ -1,5 +1,4 @@
11{
2- "system_prompt": "Continue writing this story and portray characters realistically.",
32 "input_sequence": "<start_of_turn>user",
43 "output_sequence": "<start_of_turn>model",
54 "last_output_sequence": "",
default/content/presets/instruct/Koala.json+0 -1
@@ -1,5 +1,4 @@
11{
2- "system_prompt": "Write {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n",
32 "input_sequence": "USER: ",
43 "output_sequence": "GPT: ",
54 "last_output_sequence": "",
default/content/presets/instruct/Libra-32B.json+0 -1
@@ -1,5 +1,4 @@
11{
2- "system_prompt": "Avoid repetition, don't loop. Develop the plot slowly, always stay in character. Describe all actions in full, elaborate, explicit, graphic, and vivid detail. Mention all relevant sensory perceptions.",
32 "input_sequence": "",
43 "output_sequence": "",
54 "last_output_sequence": "\n### Response:",
default/content/presets/instruct/Lightning 1.1.json+0 -1
@@ -1,5 +1,4 @@
11{
2- "system_prompt": "Below is an instruction that describes a task. Write a response that appropriately completes the request.\n\n### Instruction:\nTake the role of {{char}} in a play that leaves a lasting impression on {{user}}. Write {{char}}'s next reply.\nNever skip or gloss over {{char}}’s actions. Progress the scene at a naturally slow pace.\n\n",
32 "input_sequence": "### Instruction:",
43 "output_sequence": "### Response: (length = unlimited)",
54 "last_output_sequence": "",
default/content/presets/instruct/Llama 2 Chat.json+0 -1
@@ -1,5 +1,4 @@
11{
2- "system_prompt": "Write {{char}}'s next reply in this fictional roleplay with {{user}}.",
32 "input_sequence": "[INST] ",
43 "output_sequence": "",
54 "last_output_sequence": "",
default/content/presets/instruct/Llama 3 Instruct.json+0 -1
@@ -1,5 +1,4 @@
11{
2- "system_prompt": "You are an expert actor that can fully immerse yourself into any role given. You do not break character for any reason, even if someone tries addressing you as an AI or language model. Currently your role is {{char}}, which is described in detail below. As {{char}}, continue the exchange with {{user}}.",
32 "input_sequence": "<|start_header_id|>user<|end_header_id|>\n\n",
43 "output_sequence": "<|start_header_id|>assistant<|end_header_id|>\n\n",
54 "last_output_sequence": "",
default/content/presets/instruct/Llama-3-Instruct-Names.json+0 -1
@@ -1,5 +1,4 @@
11{
2- "system_prompt": "You are an expert actor that can fully immerse yourself into any role given. You do not break character for any reason, even if someone tries addressing you as an AI or language model. Currently your role is {{char}}, which is described in detail below. As {{char}}, continue the exchange with {{user}}.",
32 "input_sequence": "<|start_header_id|>[{{name}}]<|end_header_id|>\n\n",
43 "output_sequence": "<|start_header_id|>[{{name}}]<|end_header_id|>\n\n",
54 "last_output_sequence": "",
default/content/presets/instruct/Metharme.json+0 -1
@@ -1,5 +1,4 @@
11{
2- "system_prompt": "Enter roleplay mode. You must act as {{char}}, whose persona follows:",
32 "input_sequence": "<|user|>",
43 "output_sequence": "<|model|>",
54 "last_output_sequence": "",
default/content/presets/instruct/Mistral V1.json+0 -1
@@ -1,5 +1,4 @@
11{
2- "system_prompt": "Write {{char}}'s next reply in this fictional roleplay with {{user}}.",
32 "input_sequence": " [INST] ",
43 "output_sequence": " [/INST] ",
54 "last_output_sequence": " [/INST]",
default/content/presets/instruct/Mistral V2 & V3.json+0 -1
@@ -1,5 +1,4 @@
11{
2- "system_prompt": "Write {{char}}'s next reply in this fictional roleplay with {{user}}.",
32 "input_sequence": "[INST] ",
43 "output_sequence": "[/INST] ",
54 "last_output_sequence": "[/INST]",
default/content/presets/instruct/Mistral V3-Tekken.json+0 -1
@@ -1,5 +1,4 @@
11{
2- "system_prompt": "Write {{char}}'s next reply in this fictional roleplay with {{user}}.",
32 "input_sequence": "[INST]",
43 "output_sequence": "[/INST]",
54 "last_output_sequence": "",
default/content/presets/instruct/OpenOrca-OpenChat.json+0 -1
@@ -1,5 +1,4 @@
11{
2- "system_prompt": "You are a helpful assistant. Please answer truthfully and write out your thinking step by step to be sure you get the right answer. If you make a mistake or encounter an error in your thinking, say so out loud and attempt to correct it. If you don't know or aren't sure about something, say so clearly. You will act as a professional logician, mathematician, and physicist. You will also act as the most appropriate type of expert to answer any particular question or solve the relevant problem; state which expert type your are, if so. Also think of any particular named expert that would be ideal to answer the relevant question or solve the relevant problem; name and act as them, if appropriate.\n",
32 "input_sequence": "\nUser: ",
43 "output_sequence": "\nAssistant: ",
54 "last_output_sequence": "",
default/content/presets/instruct/Phi.json+0 -1
@@ -1,5 +1,4 @@
11{
2- "system_prompt": "Write {{char}}'s next reply in this fictional roleplay with {{user}}.",
32 "input_sequence": "<|user|>\n",
43 "output_sequence": "<|assistant|>\n",
54 "first_output_sequence": "",
default/content/presets/instruct/Pygmalion.json+0 -1
@@ -1,5 +1,4 @@
11{
2- "system_prompt": "Enter RP mode. You shall reply to {{user}} while staying in character. Your responses must be detailed, creative, immersive, and drive the scenario forward. You will follow {{char}}'s persona.",
32 "input_sequence": "<|user|>",
43 "output_sequence": "<|model|>",
54 "last_output_sequence": "",
default/content/presets/instruct/Story.json+0 -1
@@ -1,5 +1,4 @@
11{
2- "system_prompt": "",
32 "input_sequence": "",
43 "output_sequence": "",
54 "last_output_sequence": "",
default/content/presets/instruct/Synthia.json+0 -1
@@ -1,5 +1,4 @@
11{
2- "system_prompt": "Elaborate on the topic using a Tree of Thoughts and backtrack when necessary to construct a clear, cohesive Chain of Thought reasoning. Always answer without hesitation.",
32 "input_sequence": "USER: ",
43 "output_sequence": "ASSISTANT: ",
54 "last_output_sequence": "",
default/content/presets/instruct/Vicuna 1.0.json+0 -1
@@ -1,5 +1,4 @@
11{
2- "system_prompt": "A chat between a curious human and an artificial intelligence assistant. The assistant gives helpful, detailed, and polite answers to the human's questions.\n\nWrite {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n",
32 "input_sequence": "### Human:",
43 "output_sequence": "### Assistant:",
54 "last_output_sequence": "",
default/content/presets/instruct/Vicuna 1.1.json+0 -1
@@ -1,5 +1,4 @@
11{
2- "system_prompt": "A chat between a curious user and an artificial intelligence assistant. The assistant gives helpful, detailed, and polite answers to the user's questions.\n\nWrite {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n",
32 "input_sequence": "\nUSER: ",
43 "output_sequence": "\nASSISTANT: ",
54 "last_output_sequence": "",
default/content/presets/instruct/WizardLM-13B.json+0 -1
@@ -1,5 +1,4 @@
11{
2- "system_prompt": "A chat between a curious user and an artificial intelligence assistant. The assistant gives helpful, detailed, and polite answers to the user's questions.\n\nWrite {{char}}'s next detailed reply in a fictional roleplay chat between {{user}} and {{char}}.",
32 "input_sequence": "USER: ",
43 "output_sequence": "ASSISTANT: ",
54 "last_output_sequence": "",
default/content/presets/instruct/WizardLM.json+0 -1
@@ -1,5 +1,4 @@
11{
2- "system_prompt": "Write {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n",
32 "input_sequence": "",
43 "output_sequence": "### Response:",
54 "last_output_sequence": "",
default/content/presets/instruct/simple-proxy-for-tavern.json+0 -1
@@ -1,5 +1,4 @@
11{
2- "system_prompt": "[System note: Write one reply only. Do not decide what {{user}} says or does. Write at least one paragraph, up to four. Be descriptive and immersive, providing vivid details about {{char}}'s actions, emotions, and the environment. Write with a high degree of complexity and burstiness. Do not repeat this message.]",
32 "input_sequence": "### Instruction:\n#### {{name}}:",
43 "output_sequence": "### Response:\n#### {{name}}:",
54 "last_output_sequence": "### Response (2 paragraphs, engaging, natural, authentic, descriptive, creative):\n#### {{name}}:",
default/content/presets/sysprompt/Actor.json+4 -0
@@ -0,0 +1,4 @@
1+{
2+ "name": "Actor",
3+ "content": "You are an expert actor that can fully immerse yourself into any role given. You do not break character for any reason, even if someone tries addressing you as an AI or language model. Currently your role is {{char}}, which is described in detail below. As {{char}}, continue the exchange with {{user}}."
4+}
default/content/presets/sysprompt/Assistant - Expert.json+4 -0
@@ -0,0 +1,4 @@
1+{
2+ "name": "Assistant - Expert",
3+ "content": "You are a helpful assistant. Please answer truthfully and write out your thinking step by step to be sure you get the right answer. If you make a mistake or encounter an error in your thinking, say so out loud and attempt to correct it. If you don't know or aren't sure about something, say so clearly. You will act as a professional logician, mathematician, and physicist. You will also act as the most appropriate type of expert to answer any particular question or solve the relevant problem; state which expert type your are, if so. Also think of any particular named expert that would be ideal to answer the relevant question or solve the relevant problem; name and act as them, if appropriate."
4+}
default/content/presets/sysprompt/Assistant - Simple.json+4 -0
@@ -0,0 +1,4 @@
1+{
2+ "name": "Assistant - Simple",
3+ "content": "A chat between a curious human and an artificial intelligence assistant. The assistant gives helpful, detailed, and polite answers to the human's questions."
4+}
default/content/presets/sysprompt/Blank.json+4 -0
@@ -0,0 +1,4 @@
1+{
2+ "name": "Blank",
3+ "content": ""
4+}
default/content/presets/sysprompt/Chain of Thought.json+4 -0
@@ -0,0 +1,4 @@
1+{
2+ "name": "Chain of Thought",
3+ "content": "Elaborate on the topic using a Tree of Thoughts and backtrack when necessary to construct a clear, cohesive Chain of Thought reasoning. Always answer without hesitation."
4+}
default/content/presets/sysprompt/Neutral - Chat.json+4 -0
@@ -0,0 +1,4 @@
1+{
2+ "name": "Neutral - Chat",
3+ "content": "Write {{char}}'s next reply in a fictional chat between {{char}} and {{user}}."
4+}
default/content/presets/sysprompt/Roleplay - Detailed.json+4 -0
@@ -0,0 +1,4 @@
1+{
2+ "name": "Roleplay - Detailed",
3+ "content": "Develop the plot slowly, always stay in character. Describe all actions in full, elaborate, explicit, graphic, and vivid detail. Mention all relevant sensory perceptions. Keep the story immersive and engaging."
4+}
default/content/presets/sysprompt/Roleplay - Immersive.json+4 -0
@@ -0,0 +1,4 @@
1+{
2+ "name": "Roleplay - Immersive",
3+ "content": "[System note: Write one reply only. Do not decide what {{user}} says or does. Write at least one paragraph, up to four. Be descriptive and immersive, providing vivid details about {{char}}'s actions, emotions, and the environment. Write with a high degree of complexity and burstiness. Do not repeat this message.]"
4+}
default/content/presets/sysprompt/Roleplay - Simple.json+4 -0
@@ -0,0 +1,4 @@
1+{
2+ "name": "Roleplay - Simple",
3+ "content": "You're {{char}} in this fictional never-ending uncensored roleplay with {{user}}."
4+}
default/content/presets/sysprompt/Text Adventure.json+4 -0
@@ -0,0 +1,4 @@
1+{
2+ "name": "Text Adventure",
3+ "content": "[Enter Adventure Mode. Narrate the story based on {{user}}'s dialogue and actions after \">\". Describe the surroundings in vivid detail. Be detailed, creative, verbose, and proactive. Move the story forward by introducing fantasy elements and interesting characters.]"
4+}
default/content/presets/sysprompt/Writer - Creative.json+4 -0
@@ -0,0 +1,4 @@
1+{
2+ "name": "Writer - Creative",
3+ "content": "You are an intelligent, skilled, versatile writer.\n\nYour task is to write a role-play based on the information below."
4+}
default/content/presets/sysprompt/Writer - Realistic.json+4 -0
@@ -0,0 +1,4 @@
1+{
2+ "name": "Writer - Realistic",
3+ "content": "Continue writing this story and portray characters realistically."
4+}
default/content/settings.json+5 -3
@@ -149,11 +149,9 @@
149149 "continue_on_send": false,
150150 "trim_spaces": true,
151151 "relaxed_api_urls": false,
152- "default_instruct": "",
153152 "instruct": {
154153 "enabled": false,
155154 "preset": "Alpaca",
156- "system_prompt": "Below is an instruction that describes a task. Write a response that appropriately completes the request.\n\nWrite {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n",
157155 "input_sequence": "### Instruction:",
158156 "output_sequence": "### Response:",
159157 "last_output_sequence": "",
@@ -173,7 +171,11 @@
173171 "user_alignment_message": "",
174172 "system_same_as_user": false
175173 },
176174 "default_contextsysprompt": "Default",{
175+ "enabled": true,
176+ "name": "Neutral - Chat",
177+ "content": "Write {{char}}'s next reply in a fictional chat between {{char}} and {{user}}."
178+ },
177179 "context": {
178180 "preset": "Default",
179181 "story_string": "{{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}",
public/index.html+227 -182
@@ -3162,9 +3162,22 @@
31623162 <div class="drawer-icon fa-solid fa-font fa-fw closedIcon" title="AI Response Formatting" data-i18n="[title]AI Response Formatting"></div>
31633163 </div>
31643164 <div id="AdvancedFormatting" class="drawer-content">
31653165 <h3div class="margin0" dataflex-i18n="Advancedcontainer FormattingalignItemsBaseline">
3166- Advanced Formatting
3166+ <h3 class="margin0 flex1" data-i18n="Advanced Formatting">
3167- </h3>
3167+ Advanced Formatting
3168+ </h3>
3169+ <div class="flex-container">
3170+ <input id="af_master_import_file" type="file" hidden accept=".json" class="displayNone">
3171+ <div id="af_master_import" class="menu_button menu_button_icon" title="Import Advanced Formatting settings" data-i18n="[title]Import Advanced Formatting settings">
3172+ <i class="fa-solid fa-file-import"></i>
3173+ <span data-i18n="Master Import">Master Import</span>
3174+ </div>
3175+ <div id="af_master_export" class="menu_button menu_button_icon" title="Export Advanced Formatting settings" data-i18n="[title]Export Advanced Formatting settings">
3176+ <i class="fa-solid fa-file-export"></i>
3177+ <span data-i18n="Master Export">Master Export</span>
3178+ </div>
3179+ </div>
3180+ </div>
31683181 <div class="flex-container spaceEvenly">
31693182 <div id="ContextSettings" class="flex-container flexNoGap flexFlowColumn flex1">
31703183 <div>
@@ -3176,18 +3189,17 @@
31763189 </a>
31773190 </div>
31783191 </h4>
3179- <div class="flex-container justifyCenter">
3180- <input type="file" hidden data-preset-manager-file="context" accept=".json, .settings">
3181- <i id="context_set_default" class="menu_button fa-solid fa-heart" title="Auto-select this template for Instruct Mode." data-i18n="[title]Auto-select this template for Instruct Mode"></i>
3182- <i data-preset-manager-update="context" class="menu_button fa-solid fa-save" title="Update current template" data-i18n="[title]Update current template"></i>
3183- <i data-preset-manager-new="context" class="menu_button fa-solid fa-file-circle-plus" title="Save template as" data-i18n="[title]Save template as"></i>
3184- <i data-preset-manager-import="context" class="menu_button fa-solid fa-file-import" title="Import template" data-i18n="[title]Import template"></i>
3185- <i data-preset-manager-export="context" class="menu_button fa-solid fa-file-export" title="Export template" data-i18n="[title]Export template"></i>
3186- <i data-preset-manager-restore="context" class="menu_button fa-solid fa-recycle" title="Restore current template" data-i18n="[title]Restore current template"></i>
3187- <i id="context_delete_preset" data-preset-manager-delete="context" class="menu_button fa-solid fa-trash-can" title="Delete the template" data-i18n="[title]Delete the template"></i>
3188- </div>
31893192 <div class="flex-container" title="Select your current Context Template" data-i18n="[title]Select your current Context Template">
31903193 <select id="context_presets" data-preset-manager-for="context" class="flex1 text_pole"></select>
3194+ <div class="flex-container justifyCenter">
3195+ <input type="file" hidden data-preset-manager-file="context" accept=".json, .settings">
3196+ <i data-preset-manager-update="context" class="menu_button fa-solid fa-save" title="Update current template" data-i18n="[title]Update current template"></i>
3197+ <i data-preset-manager-new="context" class="menu_button fa-solid fa-file-circle-plus" title="Save template as" data-i18n="[title]Save template as"></i>
3198+ <i data-preset-manager-import="context" class="displayNone menu_button fa-solid fa-file-import" title="Import template" data-i18n="[title]Import template"></i>
3199+ <i data-preset-manager-export="context" class="displayNone menu_button fa-solid fa-file-export" title="Export template" data-i18n="[title]Export template"></i>
3200+ <i data-preset-manager-restore="context" class="menu_button fa-solid fa-recycle" title="Restore current template" data-i18n="[title]Restore current template"></i>
3201+ <i id="context_delete_preset" data-preset-manager-delete="context" class="menu_button fa-solid fa-trash-can" title="Delete the template" data-i18n="[title]Delete the template"></i>
3202+ </div>
31913203 </div>
31923204 <div>
31933205 <label for="context_story_string" class="flex-container">
@@ -3218,9 +3230,6 @@
32183230 <span data-i18n="Context Formatting">
32193231 Context Formatting
32203232 </span>
3221- <small data-i18n="(Saves to Template)">
3222- (Saves to Template)
3223- </small>
32243233 </h4>
32253234
32263235 <label class="checkbox_label" for="always-force-name2-checkbox">
@@ -3266,31 +3275,6 @@
32663275 <small data-i18n="Allow Post-History Instructions">Allow Post-History Instructions</small>
32673276 </label>
32683277 </div>
3269- <div>
3270- <h4 class="range-block-title justifyLeft standoutHeader">
3271- <span data-i18n="Custom Stopping Strings">
3272- Custom Stopping Strings
3273- </span>
3274- <a href="https://docs.sillytavern.app/usage/core-concepts/advancedformatting/#custom-stopping-strings" class="notes-link" target="_blank">
3275- <span class="fa-solid fa-circle-question note-link-span"></span>
3276- </a>
3277- </h4>
3278- <div>
3279- <small>
3280- <span data-i18n="JSON serialized array of strings">JSON serialized array of strings</span>
3281- <i class="fa-solid fa-question-circle opacity50p" title="e.g: [&quot;Ford&quot;, &quot;BMW&quot;, &quot;Fiat&quot;]"></i>
3282- </small>
3283- </div>
3284- <div>
3285- <textarea id="custom_stopping_strings" class="text_pole textarea_compact monospace autoSetHeight"></textarea>
3286- </div>
3287- <label class="checkbox_label" for="custom_stopping_strings_macro">
3288- <input id="custom_stopping_strings_macro" type="checkbox" checked>
3289- <small data-i18n="Replace Macro in Custom Stopping Strings">
3290- Replace Macro in Custom Stopping Strings
3291- </small>
3292- </label>
3293- </div>
32943278 </div>
32953279 </div>
32963280 <div id="InstructSettingsColumn" class="flex-container flexNoGap flexFlowColumn flex1">
@@ -3303,29 +3287,28 @@
33033287 </a>
33043288 </div>
33053289 <div class="flex-container">
3306- <label for="instruct_enabled" class="checkbox_label flex1" title="Enable Instruct Mode" data-i18n="[title]instruct_enabled">
3307- <input id="instruct_enabled" type="checkbox" style="display:none;" />
3308- <small><i class="fa-solid fa-power-off menu_button margin0"></i></small>
3309- </label>
33103290 <label for="instruct_bind_to_context" class="checkbox_label flex1" title="Bind to Context&#10If enabled, Context templates will be automatically selected based on selected Instruct template name or by preference." data-i18n="[title]instruct_bind_to_context">
33113291 <input id="instruct_bind_to_context" type="checkbox" style="display:none;" />
33123292 <small><i class="fa-solid fa-link menu_button margin0"></i></small>
33133293 </label>
3294+ <label for="instruct_enabled" class="checkbox_label flex1" title="Enable Instruct Mode" data-i18n="[title]instruct_enabled">
3295+ <input id="instruct_enabled" type="checkbox" style="display:none;" />
3296+ <small><i class="fa-solid fa-power-off menu_button margin0"></i></small>
3297+ </label>
33143298 </div>
33153299 </h4>
3316- <div class="flex-container margin0 justifyCenter">
3317- <input type="file" hidden data-preset-manager-file="instruct" accept=".json, .settings">
3318- <i id="instruct_set_default" class="menu_button fa-solid fa-heart" title="Auto-select this template on API connection." data-i18n="[title]Auto-select this template on API connection"></i>
3319- <i data-preset-manager-update="instruct" class="menu_button fa-solid fa-save" title="Update current template" data-i18n="[title]Update current template"></i>
3320- <i data-preset-manager-new="instruct" class="menu_button fa-solid fa-file-circle-plus" title="Save template as" data-i18n="[title]Save template as"></i>
3321- <i data-preset-manager-import="instruct" class=" menu_button fa-solid fa-file-import" title="Import template" data-i18n="[title]Import template"></i>
3322- <i data-preset-manager-export="instruct" class=" menu_button fa-solid fa-file-export" title="Export template" data-i18n="[title]Export template"></i>
3323- <i data-preset-manager-restore="instruct" class=" menu_button fa-solid fa-recycle" title="Restore current template" data-i18n="[title]Restore current template"></i>
3324- <i data-preset-manager-delete="instruct" class=" menu_button fa-solid fa-trash-can" title="Delete template" data-i18n="[title]Delete template"></i>
3325- </div>
33263300
33273301 <div class="flex-container" title="Select your current Instruct Template" data-i18n="[title]Select your current Instruct Template">
33283302 <select id="instruct_presets" data-preset-manager-for="instruct" class="flex1 text_pole"></select>
3303+ <div class="flex-container margin0 justifyCenter">
3304+ <input type="file" hidden data-preset-manager-file="instruct" accept=".json, .settings">
3305+ <i data-preset-manager-update="instruct" class="menu_button fa-solid fa-save" title="Update current template" data-i18n="[title]Update current template"></i>
3306+ <i data-preset-manager-new="instruct" class="menu_button fa-solid fa-file-circle-plus" title="Save template as" data-i18n="[title]Save template as"></i>
3307+ <i data-preset-manager-import="instruct" class="displayNone menu_button fa-solid fa-file-import" title="Import template" data-i18n="[title]Import template"></i>
3308+ <i data-preset-manager-export="instruct" class="displayNone menu_button fa-solid fa-file-export" title="Export template" data-i18n="[title]Export template"></i>
3309+ <i data-preset-manager-restore="instruct" class="menu_button fa-solid fa-recycle" title="Restore current template" data-i18n="[title]Restore current template"></i>
3310+ <i data-preset-manager-delete="instruct" class="menu_button fa-solid fa-trash-can" title="Delete template" data-i18n="[title]Delete template"></i>
3311+ </div>
33293312 </div>
33303313 <label>
33313314 <small>
@@ -3337,14 +3320,6 @@
33373320 <input type="text" id="instruct_activation_regex" class="text_pole textarea_compact" placeholder="e.g. /llama(-)?[3|3.1]/i">
33383321 </div>
33393322 <div>
3340- <label for="instruct_system_prompt" class="flex-container">
3341- <small data-i18n="System Prompt">System Prompt</small>
3342- <i class="editor_maximize fa-solid fa-maximize right_menu_button" data-for="instruct_system_prompt" title="Expand the editor" data-i18n="[title]Expand the editor"></i>
3343- </label>
3344- <textarea id="instruct_system_prompt" class="text_pole textarea_compact autoSetHeight"></textarea>
3345- </div>
3346-
3347- <div>
33483323 <label for="instruct_wrap" class="checkbox_label">
33493324 <input id="instruct_wrap" type="checkbox" />
33503325 <small data-i18n="Wrap Sequences with Newline">Wrap Sequences with Newline</small>
@@ -3357,25 +3332,203 @@
33573332 <input id="instruct_skip_examples" type="checkbox" />
33583333 <small data-i18n="Skip Example Dialogues Formatting">Skip Example Dialogues Formatting</small>
33593334 </label>
33603335 <div id="instruct_names_behavior">
33613336 <small data-i18n="Include Names">
33623337 Include Names
33633338 </small>
3364- <label for="instruct_names_behavior_none" class="checkbox_label" title="Do not add name prefixes before the message contents.">
3339+ <select id="instruct_names_behavior">
33653340 <input id="instruct_names_behavior_none" name="names_behavior"option value="none" typedata-i18n="radioNever" >Never</option>
33663341 <smalloption value="force" data-i18n="NeverGroups and Past Personas">NeverGroups and Past Personas</smalloption>
3367- </label>
3342+ <option value="always" data-i18n="Always">Always</option>
3368- <label for="instruct_names_behavior_force" class="checkbox_label" title="Only add name prefixes to messages from group characters and past personas.">
3343+ </select>
3369- <input id="instruct_names_behavior_force" name="names_behavior" value="force" type="radio" />
3344+ </div>
3370- <small data-i18n="Groups and Past Personas">Groups and Past Personas</small>
3345+ </div>
3346+ </div>
3347+ <div id="InstructSequencesColumn" class="wide100p flexFlowColumn">
3348+ <h4 class="standoutHeader title_restorable">
3349+ <b>
3350+ <span data-i18n="Instruct Sequences">
3351+ Instruct Sequences
3352+ </span>
3353+ </b>
3354+ </h4>
3355+ <!-- We keep one auto-open so the user would know what is going on in the picked template -->
3356+ <details open>
3357+ <summary>User Message Sequences</summary>
3358+ <div class="flex-container">
3359+ <div class="flexAuto" title="Inserted before a User message and as a last prompt line when impersonating." data-i18n="[title]Inserted before a User message and as a last prompt line when impersonating.">
3360+ <small data-i18n="User Prefix">User Message Prefix</small>
3361+ <textarea id="instruct_input_sequence" class="text_pole textarea_compact autoSetHeight"></textarea>
3362+ </div>
3363+ <div class="flexAuto" title="Inserted after a User message." data-i18n="[title]Inserted after a User message.">
3364+ <small data-i18n="User Suffix">User Message Suffix</small>
3365+ <textarea id="instruct_input_suffix" class="text_pole wide100p textarea_compact autoSetHeight"></textarea>
3366+ </div>
3367+ </div>
3368+ </details>
3369+ <details open>
3370+ <summary>Assistant Message Sequences</summary>
3371+ <div class="flex-container">
3372+ <div class="flexAuto" title="Inserted before an Assistant message and as a last prompt line when generating an AI reply." data-i18n="[title]Inserted before an Assistant message and as a last prompt line when generating an AI reply.">
3373+ <small data-i18n="Assistant Prefix">Assistant Message Prefix</small>
3374+ <textarea id="instruct_output_sequence" class="text_pole wide100p textarea_compact autoSetHeight"></textarea>
3375+ </div>
3376+ <div class="flexAuto" title="Inserted after an Assistant message." data-i18n="[title]Inserted after an Assistant message.">
3377+ <small data-i18n="Assistant Suffix">Assistant Message Suffix</small>
3378+ <textarea id="instruct_output_suffix" class="text_pole wide100p textarea_compact autoSetHeight"></textarea>
3379+ </div>
3380+ </div>
3381+ </details>
3382+ <details>
3383+ <summary>System Message Sequences</summary>
3384+ <div class="flex-container">
3385+ <div class="flexAuto" id="instruct_system_sequence_block" title="Inserted before a System (added by slash commands or extensions) message." data-i18n="[title]Inserted before a System (added by slash commands or extensions) message.">
3386+ <small data-i18n="System Prefix">System Message Prefix</small>
3387+ <textarea id="instruct_system_sequence" class="text_pole textarea_compact autoSetHeight"></textarea>
3388+ </div>
3389+ <div class="flexAuto" id="instruct_system_suffix_block" title="Inserted after a System message." data-i18n="[title]Inserted after a System message.">
3390+ <small data-i18n="System Suffix">System Message Suffix</small>
3391+ <textarea id="instruct_system_suffix" class="text_pole wide100p textarea_compact autoSetHeight"></textarea>
3392+ </div>
3393+ </div>
3394+ <div class="flexBasis100p" title="If enabled, System Sequences will be the same as User Sequences." data-i18n="[title]If enabled, System Sequences will be the same as User Sequences.">
3395+ <label class="checkbox_label" for="instruct_system_same_as_user">
3396+ <input id="instruct_system_same_as_user" type="checkbox" />
3397+ <small data-i18n="System same as User">System same as User</small>
33713398 </label>
3372- <label for="instruct_names_behavior_always" class="checkbox_label" title="Always add name prefixes to messages.">
3399+ </div>
3373- <input id="instruct_names_behavior_always" name="names_behavior" value="always" type="radio" />
3400+ </details>
3374- <small data-i18n="Always">Always</small>
3401+ <details>
3402+ <summary>System Prompt Sequences</summary>
3403+ <div class="flex-container">
3404+ <div class="flexAuto" title="Inserted before a System prompt." data-i18n="[title]Inserted before a System prompt.">
3405+ <label for="instruct_system_sequence_prefix">
3406+ <small data-i18n="System Prompt Prefix">System Prompt Prefix</small>
3407+ </label>
3408+ <div>
3409+ <textarea id="instruct_system_sequence_prefix" class="text_pole textarea_compact autoSetHeight"></textarea>
3410+ </div>
3411+ </div>
3412+ <div class="flexAuto" title="Inserted after a System prompt." data-i18n="[title]Inserted after a System prompt.">
3413+ <label for="instruct_system_sequence_suffix">
3414+ <small data-i18n="System Prompt Suffix">System Prompt Suffix</small>
3415+ </label>
3416+ <div>
3417+ <textarea id="instruct_system_sequence_suffix" class="text_pole wide100p textarea_compact autoSetHeight"></textarea>
3418+ </div>
3419+ </div>
3420+ </div>
3421+ </details>
3422+ <details>
3423+ <summary data-i18n="Misc. Sequences">Misc. Sequences</summary>
3424+ <div class="flex-container">
3425+ <div class="flexAuto" title="Inserted before the first Assistant's message." data-i18n="[title]Inserted before the first Assistant's message.">
3426+ <small data-i18n="First Assistant Prefix">First Assistant Prefix</small>
3427+ <textarea id="instruct_first_output_sequence" class="text_pole textarea_compact autoSetHeight"></textarea>
3428+ </div>
3429+ <div class="flexAuto" title="Inserted before the last Assistant's message or as a last prompt line when generating an AI reply (except a neutral/system role)." data-i18n="[title]instruct_last_output_sequence">
3430+ <small data-i18n="Last Assistant Prefix">Last Assistant Prefix</small>
3431+ <textarea id="instruct_last_output_sequence" class="text_pole wide100p textarea_compact autoSetHeight"></textarea>
3432+ </div>
3433+ </div>
3434+ <div class="flex-container">
3435+ <div class="flexAuto" title="Inserted before the first User's message." data-i18n="[title]Inserted before the first User's message.">
3436+ <small data-i18n="First User Prefix">First User Prefix</small>
3437+ <textarea id="instruct_first_input_sequence" class="text_pole textarea_compact autoSetHeight"></textarea>
3438+ </div>
3439+ <div class="flexAuto" title="Inserted before the last User's message." data-i18n="[title]instruct_last_input_sequence">
3440+ <small data-i18n="Last User Prefix">Last User Prefix</small>
3441+ <textarea id="instruct_last_input_sequence" class="text_pole wide100p textarea_compact autoSetHeight"></textarea>
3442+ </div>
3443+ </div>
3444+ <div class="flex-container">
3445+ <div class="flexAuto" title="Will be inserted as a last prompt line when using system/neutral generation." data-i18n="[title]Will be inserted as a last prompt line when using system/neutral generation.">
3446+ <small data-i18n="System Instruction Prefix">System Instruction Prefix</small>
3447+ <textarea id="instruct_last_system_sequence" class="text_pole textarea_compact autoSetHeight"></textarea>
3448+ </div>
3449+ <div class="flexAuto" title="If a stop sequence is generated, everything past it will be removed from the output (inclusive)." data-i18n="[title]If a stop sequence is generated, everything past it will be removed from the output (inclusive).">
3450+ <small data-i18n="Stop Sequence">Stop Sequence</small>
3451+ <textarea id="instruct_stop_sequence" class="text_pole textarea_compact autoSetHeight"></textarea>
3452+ </div>
3453+ </div>
3454+ <div class="flex-container">
3455+ <div class="flexAuto" title="Will be inserted at the start of the chat history if it doesn't start with a User message." data-i18n="[title]Will be inserted at the start of the chat history if it doesn't start with a User message.">
3456+ <small data-i18n="User Filler Message">User Filler Message</small>
3457+ <textarea id="instruct_user_alignment_message" class="text_pole textarea_compact autoSetHeight"></textarea>
3458+ </div>
3459+ </div>
3460+ </details>
3461+ </div>
3462+ </div>
3463+ <div id="SystemPromptColumn" class="flex-container flexNoGap flexFlowColumn flex1">
3464+ <div id="SystemPromptBlock">
3465+ <h4 class="standoutHeader title_restorable justifySpaceBetween">
3466+ <div class="flex-container">
3467+ <span data-i18n="System Prompt">System Prompt</span>
3468+ <a href="https://docs.sillytavern.app/usage/core-concepts/advancedformatting/#system-prompt" class="notes-link" target="_blank">
3469+ <span class="fa-solid fa-circle-question note-link-span"></span>
3470+ </a>
3471+ </div>
3472+ <div class="flex-container">
3473+ <label for="sysprompt_enabled" class="checkbox_label flex1" title="Enable System Prompt" data-i18n="[title]sysprompt_enabled">
3474+ <input id="sysprompt_enabled" type="checkbox" style="display:none;" />
3475+ <small><i class="fa-solid fa-power-off menu_button margin0"></i></small>
33753476 </label>
33763477 </div>
3478+ </h4>
3479+
3480+ <div class="flex-container" title="Select your current System Prompt" data-i18n="[title]Select your current System Prompt">
3481+ <select id="sysprompt_select" data-preset-manager-for="sysprompt" class="flex1 text_pole"></select>
3482+ <div class="flex-container margin0 justifyCenter">
3483+ <input type="file" hidden data-preset-manager-file="sysprompt" accept=".json, .settings">
3484+ <i data-preset-manager-update="sysprompt" class="menu_button fa-solid fa-save" title="Update current prompt" data-i18n="[title]Update current prompt"></i>
3485+ <i data-preset-manager-new="sysprompt" class="menu_button fa-solid fa-file-circle-plus" title="Save prompt as" data-i18n="[title]Save prompt as"></i>
3486+ <i data-preset-manager-import="sysprompt" class="displayNone menu_button fa-solid fa-file-import" title="Import template" data-i18n="[title]Import template"></i>
3487+ <i data-preset-manager-export="sysprompt" class="displayNone menu_button fa-solid fa-file-export" title="Export template" data-i18n="[title]Export template"></i>
3488+ <i data-preset-manager-restore="sysprompt" class="menu_button fa-solid fa-recycle" title="Restore current prompt" data-i18n="[title]Restore current prompt"></i>
3489+ <i data-preset-manager-delete="sysprompt" class="menu_button fa-solid fa-trash-can" title="Delete prompt" data-i18n="[title]Delete prompt"></i>
3490+ </div>
3491+ </div>
3492+
3493+ <div>
3494+ <label for="sysprompt_content" class="flex-container">
3495+ <small data-i18n="Prompt Content">Prompt Content</small>
3496+ <i class="editor_maximize fa-solid fa-maximize right_menu_button" data-for="sysprompt_content" title="Expand the editor" data-i18n="[title]Expand the editor"></i>
3497+ </label>
3498+ <textarea id="sysprompt_content" class="text_pole textarea_compact autoSetHeight"></textarea>
3499+ </div>
3500+ </div>
3501+
3502+ <div>
3503+ &nbsp;
3504+ </div>
3505+
3506+ <div>
3507+ <h4 class="range-block-title justifyLeft standoutHeader">
3508+ <span data-i18n="Custom Stopping Strings">
3509+ Custom Stopping Strings
3510+ </span>
3511+ <a href="https://docs.sillytavern.app/usage/core-concepts/advancedformatting/#custom-stopping-strings" class="notes-link" target="_blank">
3512+ <span class="fa-solid fa-circle-question note-link-span"></span>
3513+ </a>
3514+ </h4>
3515+ <div>
3516+ <small>
3517+ <span data-i18n="JSON serialized array of strings">JSON serialized array of strings</span>
3518+ <i class="fa-solid fa-question-circle opacity50p" title="e.g: [&quot;Ford&quot;, &quot;BMW&quot;, &quot;Fiat&quot;]"></i>
3519+ </small>
3520+ </div>
3521+ <div>
3522+ <textarea id="custom_stopping_strings" class="text_pole textarea_compact monospace autoSetHeight"></textarea>
33773523 </div>
3524+ <label class="checkbox_label" for="custom_stopping_strings_macro">
3525+ <input id="custom_stopping_strings_macro" type="checkbox" checked>
3526+ <small data-i18n="Replace Macro in Custom Stopping Strings">
3527+ Replace Macro in Custom Stopping Strings
3528+ </small>
3529+ </label>
33783530 </div>
3531+
33793532 <div name="tokenizerSettingsBlock">
33803533 <div name="tokenizerSelectorBlock">
33813534 <h4 class="standoutHeader"><span data-i18n="Tokenizer">Tokenizer</span>
@@ -3447,114 +3600,6 @@
34473600 </div>
34483601 </div>
34493602 </div>
3450- <div id="InstructSequencesColumn" class="flex-container flexNoGap flexFlowColumn flex1">
3451- <div class="wide100p flexFlowColumn">
3452- <h4 class="standoutHeader title_restorable">
3453- <b>
3454- <span data-i18n="Instruct Sequences">
3455- Instruct Sequences
3456- </span>
3457- <small data-i18n="(Saves to Template)">
3458- (Saves to Template)
3459- </small>
3460- </b>
3461- </h4>
3462- <div class="flex-container">
3463- <div class="flexAuto" title="Inserted before a System prompt." data-i18n="[title]Inserted before a System prompt.">
3464- <label for="instruct_system_sequence_prefix">
3465- <small data-i18n="System Prompt Prefix">System Prompt Prefix</small>
3466- </label>
3467- <div>
3468- <textarea id="instruct_system_sequence_prefix" class="text_pole textarea_compact autoSetHeight"></textarea>
3469- </div>
3470- </div>
3471- <div class="flexAuto" title="Inserted after a System prompt." data-i18n="[title]Inserted after a System prompt.">
3472- <label for="instruct_system_sequence_suffix">
3473- <small data-i18n="System Prompt Suffix">System Prompt Suffix</small>
3474- </label>
3475- <div>
3476- <textarea id="instruct_system_sequence_suffix" class="text_pole wide100p textarea_compact autoSetHeight"></textarea>
3477- </div>
3478- </div>
3479- </div>
3480- <div class="flex-container">
3481- <div class="flexAuto" title="Inserted before a User message and as a last prompt line when impersonating." data-i18n="[title]Inserted before a User message and as a last prompt line when impersonating.">
3482- <small data-i18n="User Prefix">User Message Prefix</small>
3483- <textarea id="instruct_input_sequence" class="text_pole textarea_compact autoSetHeight"></textarea>
3484- </div>
3485- <div class="flexAuto" title="Inserted after a User message." data-i18n="[title]Inserted after a User message.">
3486- <small data-i18n="User Suffix">User Message Suffix</small>
3487- <textarea id="instruct_input_suffix" class="text_pole wide100p textarea_compact autoSetHeight"></textarea>
3488- </div>
3489- </div>
3490- <div class="flex-container">
3491- <div class="flexAuto" title="Inserted before an Assistant message and as a last prompt line when generating an AI reply." data-i18n="[title]Inserted before an Assistant message and as a last prompt line when generating an AI reply.">
3492- <small data-i18n="Assistant Prefix">Assistant Message Prefix</small>
3493- <textarea id="instruct_output_sequence" class="text_pole wide100p textarea_compact autoSetHeight"></textarea>
3494- </div>
3495- <div class="flexAuto" title="Inserted after an Assistant message." data-i18n="[title]Inserted after an Assistant message.">
3496- <small data-i18n="Assistant Suffix">Assistant Message Suffix</small>
3497- <textarea id="instruct_output_suffix" class="text_pole wide100p textarea_compact autoSetHeight"></textarea>
3498- </div>
3499- </div>
3500- <div class="flex-container">
3501- <div class="flexAuto" id="instruct_system_sequence_block" title="Inserted before a System (added by slash commands or extensions) message." data-i18n="[title]Inserted before a System (added by slash commands or extensions) message.">
3502- <small data-i18n="System Prefix">System Message Prefix</small>
3503- <textarea id="instruct_system_sequence" class="text_pole textarea_compact autoSetHeight"></textarea>
3504- </div>
3505- <div class="flexAuto" id="instruct_system_suffix_block" title="Inserted after a System message." data-i18n="[title]Inserted after a System message.">
3506- <small data-i18n="System Suffix">System Message Suffix</small>
3507- <textarea id="instruct_system_suffix" class="text_pole wide100p textarea_compact autoSetHeight"></textarea>
3508- </div>
3509- </div>
3510- <div class="flexBasis100p" title="If enabled, System Sequences will be the same as User Sequences." data-i18n="[title]If enabled, System Sequences will be the same as User Sequences.">
3511- <label class="checkbox_label" for="instruct_system_same_as_user">
3512- <input id="instruct_system_same_as_user" type="checkbox" />
3513- <small data-i18n="System same as User">System same as User</small>
3514- </label>
3515- </div>
3516-
3517- <h5 class="textAlignCenter" data-i18n="Misc. Sequences">
3518- Misc. Sequences
3519- </h5>
3520- <div class="flex-container">
3521- <div class="flexAuto" title="Inserted before the first Assistant's message." data-i18n="[title]Inserted before the first Assistant's message.">
3522- <small data-i18n="First Assistant Prefix">First Assistant Prefix</small>
3523- <textarea id="instruct_first_output_sequence" class="text_pole textarea_compact autoSetHeight"></textarea>
3524- </div>
3525- <div class="flexAuto" title="Inserted before the last Assistant's message or as a last prompt line when generating an AI reply (except a neutral/system role)." data-i18n="[title]instruct_last_output_sequence">
3526- <small data-i18n="Last Assistant Prefix">Last Assistant Prefix</small>
3527- <textarea id="instruct_last_output_sequence" class="text_pole wide100p textarea_compact autoSetHeight"></textarea>
3528- </div>
3529- </div>
3530- <div class="flex-container">
3531- <div class="flexAuto" title="Inserted before the first User's message." data-i18n="[title]Inserted before the first User's message.">
3532- <small data-i18n="First User Prefix">First User Prefix</small>
3533- <textarea id="instruct_first_input_sequence" class="text_pole textarea_compact autoSetHeight"></textarea>
3534- </div>
3535- <div class="flexAuto" title="Inserted before the last User's message." data-i18n="[title]instruct_last_input_sequence">
3536- <small data-i18n="Last User Prefix">Last User Prefix</small>
3537- <textarea id="instruct_last_input_sequence" class="text_pole wide100p textarea_compact autoSetHeight"></textarea>
3538- </div>
3539- </div>
3540- <div class="flex-container">
3541- <div class="flexAuto" title="Will be inserted as a last prompt line when using system/neutral generation." data-i18n="[title]Will be inserted as a last prompt line when using system/neutral generation.">
3542- <small data-i18n="System Instruction Prefix">System Instruction Prefix</small>
3543- <textarea id="instruct_last_system_sequence" class="text_pole textarea_compact autoSetHeight"></textarea>
3544- </div>
3545- <div class="flexAuto" title="If a stop sequence is generated, everything past it will be removed from the output (inclusive)." data-i18n="[title]If a stop sequence is generated, everything past it will be removed from the output (inclusive).">
3546- <small data-i18n="Stop Sequence">Stop Sequence</small>
3547- <textarea id="instruct_stop_sequence" class="text_pole textarea_compact autoSetHeight"></textarea>
3548- </div>
3549- </div>
3550- <div class="flex-container">
3551- <div class="flexAuto" title="Will be inserted at the start of the chat history if it doesn't start with a User message." data-i18n="[title]Will be inserted at the start of the chat history if it doesn't start with a User message.">
3552- <small data-i18n="User Filler Message">User Filler Message</small>
3553- <textarea id="instruct_user_alignment_message" class="text_pole textarea_compact autoSetHeight"></textarea>
3554- </div>
3555- </div>
3556- </div>
3557- </div>
35583603 </div>
35593604 </div>
35603605 </div>
public/script.js+11 -6
@@ -244,6 +244,7 @@ import { SlashCommandEnumValue, enumTypes } from './scripts/slash-commands/Slash
244244import { commonEnumProviders, enumIcons } from './scripts/slash-commands/SlashCommandCommonEnumsProvider.js';
245245import { initInputMarkdown } from './scripts/input-md-formatting.js';
246246import { AbortReason } from './scripts/util/AbortReason.js';
247+import { initSystemPrompts } from './scripts/sysprompt.js';
247248
248249//exporting functions and vars for mods
249250export {
@@ -934,6 +935,8 @@ async function firstLoadInit() {
934935 initDefaultSlashCommands();
935936 initTextGenModels();
936937 initOpenAI();
938+ initSystemPrompts();
939+ await initPresetManager();
937940 await getSystemMessages();
938941 sendSystemMessage(system_message_types.WELCOME);
939942 sendSystemMessage(system_message_types.WELCOME_PROMPT);
@@ -946,7 +949,6 @@ async function firstLoadInit() {
946949 await getCharacters();
947950 await getBackgrounds();
948951 await initTokenizers();
949- await initPresetManager();
950952 initBackgrounds();
951953 initAuthorsNote();
952954 initPersonas();
@@ -3525,9 +3527,12 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
35253527 jailbreak,
35263528 } = getCharacterCardFields();
35273529
35283530 if (isInstructpower_user.sysprompt.enabled) {
35293531 system = power_user.prefer_character_prompt && system ? system : baseChatReplace(power_user.instructsysprompt.system_promptcontent, name1, name2);
35303532 system = isInstruct ? formatInstructModeSystemPrompt(substituteParams(system, name1, name2, power_user.instructsysprompt.system_promptcontent)) : system;
3533+ } else {
3534+ // Nullify if it's not enabled
3535+ system = '';
35313536 }
35323537
35333538 // Depth prompt (character-specific A/N)
@@ -3780,7 +3785,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
37803785 personality: personality,
37813786 persona: power_user.persona_description_position == persona_description_positions.IN_PROMPT ? persona : '',
37823787 scenario: scenario,
37833788 system: isInstruct ? system : '',
37843789 char: name2,
37853790 user: name1,
37863791 wiBefore: worldInfoBefore,
@@ -4354,7 +4359,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
43544359 this_max_context: this_max_context,
43554360 padding: power_user.token_padding,
43564361 main_api: main_api,
43574362 instruction: isInstructpower_user.sysprompt.enabled ? substituteParams(power_user.prefer_character_prompt && system ? system : power_user.instructsysprompt.system_promptcontent) : '',
43584363 userPersona: (power_user.persona_description_position == persona_description_positions.IN_PROMPT ? (persona || '') : ''),
43594364 tokenizer: getFriendlyTokenizerName(main_api).tokenizerName || '',
43604365 };
public/scripts/extensions/connection-manager/index.js+9 -0
@@ -34,6 +34,8 @@ const TC_COMMANDS = [
3434 'preset',
3535 'api-url',
3636 'model',
37+ 'sysprompt',
38+ 'sysprompt-state',
3739 'instruct',
3840 'context',
3941 'instruct-state',
@@ -46,6 +48,8 @@ const FANCY_NAMES = {
4648 'preset': 'Settings Preset',
4749 'model': 'Model',
4850 'proxy': 'Proxy Preset',
51+ 'sysprompt-state': 'Use System Prompt',
52+ 'sysprompt': 'System Prompt Name',
4953 'instruct-state': 'Instruct Mode',
5054 'instruct': 'Instruct Template',
5155 'context': 'Context Template',
@@ -181,6 +185,11 @@ async function readProfileFromCommands(mode, profile, cleanUp = false) {
181185 }
182186
183187 if (cleanUp) {
188+ for (const command of commands) {
189+ if (command.endsWith('-state') && profile[command] === 'false') {
190+ delete profile[command.replace('-state', '')];
191+ }
192+ }
184193 for (const command of opposingCommands) {
185194 if (commands.includes(command)) {
186195 continue;
public/scripts/instruct-mode.js+28 -54
@@ -23,7 +23,6 @@ export const names_behavior_types = {
2323const controls = [
2424 { id: 'instruct_enabled', property: 'enabled', isCheckbox: true },
2525 { id: 'instruct_wrap', property: 'wrap', isCheckbox: true },
26- { id: 'instruct_system_prompt', property: 'system_prompt', isCheckbox: false },
2726 { id: 'instruct_system_sequence_prefix', property: 'system_sequence_prefix', isCheckbox: false },
2827 { id: 'instruct_system_sequence_suffix', property: 'system_sequence_suffix', isCheckbox: false },
2928 { id: 'instruct_input_sequence', property: 'input_sequence', isCheckbox: false },
@@ -35,7 +34,6 @@ const controls = [
3534 { id: 'instruct_last_system_sequence', property: 'last_system_sequence', isCheckbox: false },
3635 { id: 'instruct_user_alignment_message', property: 'user_alignment_message', isCheckbox: false },
3736 { id: 'instruct_stop_sequence', property: 'stop_sequence', isCheckbox: false },
38- { id: 'instruct_names', property: 'names', isCheckbox: true },
3937 { id: 'instruct_first_output_sequence', property: 'first_output_sequence', isCheckbox: false },
4038 { id: 'instruct_last_output_sequence', property: 'last_output_sequence', isCheckbox: false },
4139 { id: 'instruct_first_input_sequence', property: 'first_input_sequence', isCheckbox: false },
@@ -43,7 +41,7 @@ const controls = [
4341 { id: 'instruct_activation_regex', property: 'activation_regex', isCheckbox: false },
4442 { id: 'instruct_bind_to_context', property: 'bind_to_context', isCheckbox: true },
4543 { id: 'instruct_skip_examples', property: 'skip_examples', isCheckbox: true },
4644 { id: 'instruct_names_behavior input[name="names_behavior"]', property: 'names_behavior', isCheckbox: false },
4745 { id: 'instruct_system_same_as_user', property: 'system_same_as_user', isCheckbox: true, trigger: true },
4846];
4947
@@ -109,9 +107,10 @@ export async function loadInstructMode(data) {
109107
110108 if (control.isCheckbox) {
111109 $element.prop('checked', power_user.instruct[control.property]);
112110 } else if (control$element.property === is('names_behaviorselect')) {
113111 const behaviorvalue = power_user.instruct[control.property];
114- $element.filter(`[value="${behavior}"]`).prop('checked', true);
112+ $element.val(value);
113+ $element.filter(`[value="${value}"]`).prop('checked', true);
115114 } else {
116115 $element.val(power_user.instruct[control.property]);
117116 }
@@ -137,12 +136,6 @@ export async function loadInstructMode(data) {
137136 option.selected = name === power_user.instruct.preset;
138137 $('#instruct_presets').append(option);
139138 });
140-
141- highlightDefaultPreset();
142-}
143-
144-function highlightDefaultPreset() {
145- $('#instruct_set_default').toggleClass('default', power_user.default_instruct === power_user.instruct.preset);
146139}
147140
148141/**
@@ -159,13 +152,6 @@ export function selectContextPreset(preset, { quiet = false, isAuto = false } =
159152 !quiet && toastr.info(`Context Template: "${preset}" ${isAuto ? 'auto-' : ''}selected`);
160153 }
161154
162- // If instruct mode is disabled, enable it, except for default context template
163- if (!power_user.instruct.enabled && preset !== power_user.default_context) {
164- power_user.instruct.enabled = true;
165- $('#instruct_enabled').prop('checked', true).trigger('change');
166- !quiet && toastr.info('Instruct Mode enabled');
167- }
168-
169155 saveSettingsDebounced();
170156}
171157
@@ -235,13 +221,6 @@ export function autoSelectInstructPreset(modelId) {
235221 }
236222 }
237223 }
238-
239- if (power_user.instruct.bind_to_context && power_user.default_instruct && power_user.instruct.preset !== power_user.default_instruct) {
240- if (instruct_presets.some(p => p.name === power_user.default_instruct)) {
241- console.log(`Instruct mode: default preset "${power_user.default_instruct}" selected`);
242- $('#instruct_presets').val(power_user.default_instruct).trigger('change');
243- }
244- }
245224 }
246225
247226 return false;
@@ -580,10 +559,6 @@ function selectMatchingContextTemplate(name) {
580559 break;
581560 }
582561 }
583- if (!foundMatch) {
584- // If no match was found, select default context preset
585- selectContextPreset(power_user.default_context, { isAuto: true });
586- }
587562}
588563
589564/**
@@ -597,9 +572,13 @@ export function replaceInstructMacros(input, env) {
597572 if (!input) {
598573 return '';
599574 }
575+
576+ const syspromptMacros = {
577+ 'systemPrompt': (power_user.prefer_character_prompt && env.charPrompt ? env.charPrompt : power_user.sysprompt.content),
578+ 'defaultSystemPrompt|instructSystem|instructSystemPrompt': power_user.sysprompt.content,
579+ };
580+
600581 const instructMacros = {
601- 'systemPrompt': (power_user.prefer_character_prompt && env.charPrompt ? env.charPrompt : power_user.instruct.system_prompt),
602- 'instructSystem|instructSystemPrompt': power_user.instruct.system_prompt,
603582 'instructSystemPromptPrefix': power_user.instruct.system_sequence_prefix,
604583 'instructSystemPromptSuffix': power_user.instruct.system_sequence_suffix,
605584 'instructInput|instructUserPrefix': power_user.instruct.input_sequence,
@@ -622,6 +601,11 @@ export function replaceInstructMacros(input, env) {
622601 input = input.replace(regex, power_user.instruct.enabled ? value : '');
623602 }
624603
604+ for (const [placeholder, value] of Object.entries(syspromptMacros)) {
605+ const regex = new RegExp(`{{(${placeholder})}}`, 'gi');
606+ input = input.replace(regex, power_user.sysprompt.enabled ? value : '');
607+ }
608+
625609 input = input.replace(/{{exampleSeparator}}/gi, power_user.context.example_separator);
626610 input = input.replace(/{{chatStart}}/gi, power_user.context.chat_start);
627611
@@ -629,20 +613,6 @@ export function replaceInstructMacros(input, env) {
629613}
630614
631615jQuery(() => {
632- $('#instruct_set_default').on('click', function () {
633- if (power_user.instruct.preset === power_user.default_instruct) {
634- power_user.default_instruct = null;
635- $(this).removeClass('default');
636- toastr.info('Default instruct template cleared');
637- } else {
638- power_user.default_instruct = power_user.instruct.preset;
639- $(this).addClass('default');
640- toastr.info(`Default instruct template set to ${power_user.default_instruct}`);
641- }
642-
643- saveSettingsDebounced();
644- });
645-
646616 $('#instruct_system_same_as_user').on('input', function () {
647617 const state = !!$(this).prop('checked');
648618 if (state) {
@@ -671,9 +641,6 @@ jQuery(() => {
671641 // When instruct mode gets enabled, select context template matching selected instruct preset
672642 if (power_user.instruct.enabled) {
673643 selectMatchingContextTemplate(power_user.instruct.preset);
674- } else {
675- // When instruct mode gets disabled, select default context preset
676- selectContextPreset(power_user.default_context);
677644 }
678645 });
679646
@@ -699,9 +666,10 @@ jQuery(() => {
699666
700667 if (control.isCheckbox) {
701668 $element.prop('checked', power_user.instruct[control.property]).trigger('input');
702669 } else if (control$element.property === is('names_behaviorselect')) {
703670 const behaviorvalue = power_user.instruct[control.property];
704- $element.filter(`[value="${behavior}"]`).prop('checked', true).trigger('input');
671+ $element.val(value);
672+ $element.filter(`[value="${value}"]`).prop('checked', true).trigger('input');
705673 } else {
706674 $element.val(power_user.instruct[control.property]);
707675 $element.trigger('input');
@@ -713,7 +681,13 @@ jQuery(() => {
713681 // Select matching context template
714682 selectMatchingContextTemplate(name);
715683 }
716-
717- highlightDefaultPreset();
718684 });
685+
686+ if (!CSS.supports('field-sizing', 'content')) {
687+ $('#InstructSequencesColumn details').on('toggle', function () {
688+ if ($(this).prop('open')) {
689+ resetScrollHeight($(this).find('textarea'));
690+ }
691+ });
692+ }
719693});
public/scripts/power-user.js+8 -24
@@ -50,6 +50,7 @@ import { AUTOCOMPLETE_SELECT_KEY, AUTOCOMPLETE_WIDTH } from './autocomplete/Auto
5050import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
5151import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
5252import { POPUP_TYPE, callGenericPopup } from './popup.js';
53+import { loadSystemPrompts } from './sysprompt.js';
5354
5455export {
5556 loadPowerUserSettings,
@@ -205,11 +206,9 @@ let power_user = {
205206 disable_group_trimming: false,
206207 single_line: false,
207208
208- default_instruct: '',
209209 instruct: {
210210 enabled: false,
211211 preset: 'Alpaca',
212- system_prompt: 'Below is an instruction that describes a task. Write a response that appropriately completes the request.\n\nWrite {{char}}\'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n',
213212 input_sequence: '### Instruction:',
214213 input_suffix: '',
215214 output_sequence: '### Response:',
@@ -233,7 +232,6 @@ let power_user = {
233232 separator_sequence: '',
234233 },
235234
236- default_context: 'Default',
237235 context: {
238236 preset: 'Default',
239237 story_string: defaultStoryString,
@@ -244,6 +242,12 @@ let power_user = {
244242 names_as_stop_strings: true,
245243 },
246244
245+ sysprompt: {
246+ enabled: true,
247+ name: 'Neutral - Chat',
248+ content: 'Write {{char}}\'s next reply in a fictional chat between {{char}} and {{user}}.',
249+ },
250+
247251 personas: {},
248252 default_persona: null,
249253 persona_descriptions: {},
@@ -1595,6 +1599,7 @@ async function loadPowerUserSettings(settings, data) {
15951599 reloadMarkdownProcessor(power_user.render_formulas);
15961600 await loadInstructMode(data);
15971601 await loadContextSettings();
1602+ await loadSystemPrompts(data);
15981603 loadMaxContextUnlocked();
15991604 switchWaifuMode();
16001605 switchSpoilerMode();
@@ -1814,29 +1819,8 @@ async function loadContextSettings() {
18141819 }
18151820 }
18161821
1817- highlightDefaultContext();
18181822 saveSettingsDebounced();
18191823 });
1820-
1821- $('#context_set_default').on('click', function () {
1822- if (power_user.context.preset !== power_user.default_context) {
1823- power_user.default_context = power_user.context.preset;
1824- $(this).addClass('default');
1825- toastr.info(`Default context template set to ${power_user.default_context}`);
1826-
1827- highlightDefaultContext();
1828-
1829- saveSettingsDebounced();
1830- }
1831- });
1832-
1833- highlightDefaultContext();
1834-}
1835-
1836-function highlightDefaultContext() {
1837- $('#context_set_default').toggleClass('default', power_user.default_context === power_user.context.preset);
1838- $('#context_set_default').toggleClass('disabled', power_user.default_context === power_user.context.preset);
1839- $('#context_delete_preset').toggleClass('disabled', power_user.default_context === power_user.context.preset);
18401824}
18411825
18421826/**
public/scripts/preset-manager.js+259 -9
@@ -18,13 +18,15 @@ import {
1818import { groups, selected_group } from './group-chats.js';
1919import { instruct_presets } from './instruct-mode.js';
2020import { kai_settings } from './kai-settings.js';
2121import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';
2222import { context_presets, getContextSettings, power_user } from './power-user.js';
2323import { SlashCommand } from './slash-commands/SlashCommand.js';
2424import { ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js';
2525import { enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
2626import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
2727import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
28+import { checkForSystemPromptInInstructTemplate, system_prompts } from './sysprompt.js';
29+import { renderTemplateAsync } from './templates.js';
2830import {
2931 textgenerationwebui_preset_names,
3032 textgenerationwebui_presets,
@@ -71,7 +73,7 @@ function autoSelectPreset() {
7173 * @param {string} apiId API id
7274 * @returns {PresetManager} Preset manager
7375 */
7476export function getPresetManager(apiId = '') {
7577 if (!apiId) {
7678 apiId = main_api == 'koboldhorde' ? 'kobold' : main_api;
7779 }
@@ -102,6 +104,215 @@ class PresetManager {
102104 this.apiId = apiId;
103105 }
104106
107+ static masterSections = {
108+ 'instruct': {
109+ name: 'Instruct Template',
110+ getData: () => {
111+ const manager = getPresetManager('instruct');
112+ const name = manager.getSelectedPresetName();
113+ return manager.getPresetSettings(name);
114+ },
115+ setData: (data) => {
116+ const manager = getPresetManager('instruct');
117+ const name = data.name;
118+ return manager.savePreset(name, data);
119+ },
120+ isValid: (data) => PresetManager.isPossiblyInstructData(data),
121+ },
122+ 'context': {
123+ name: 'Context Template',
124+ getData: () => {
125+ const manager = getPresetManager('context');
126+ const name = manager.getSelectedPresetName();
127+ return manager.getPresetSettings(name);
128+ },
129+ setData: (data) => {
130+ const manager = getPresetManager('context');
131+ const name = data.name;
132+ return manager.savePreset(name, data);
133+ },
134+ isValid: (data) => PresetManager.isPossiblyContextData(data),
135+ },
136+ 'sysprompt': {
137+ name: 'System Prompt',
138+ getData: () => {
139+ const manager = getPresetManager('sysprompt');
140+ const name = manager.getSelectedPresetName();
141+ return manager.getPresetSettings(name);
142+ },
143+ setData: (data) => {
144+ const manager = getPresetManager('sysprompt');
145+ const name = data.name;
146+ return manager.savePreset(name, data);
147+ },
148+ isValid: (data) => PresetManager.isPossiblySystemPromptData(data),
149+ },
150+ 'preset': {
151+ name: 'Text Completion Preset',
152+ getData: () => {
153+ const manager = getPresetManager('textgenerationwebui');
154+ const name = manager.getSelectedPresetName();
155+ const data = manager.getPresetSettings(name);
156+ data['name'] = name;
157+ return data;
158+ },
159+ setData: (data) => {
160+ const manager = getPresetManager('textgenerationwebui');
161+ const name = data.name;
162+ return manager.savePreset(name, data);
163+ },
164+ isValid: (data) => PresetManager.isPossiblyTextCompletionData(data),
165+ },
166+ };
167+
168+ static isPossiblyInstructData(data) {
169+ const instructProps = ['name', 'input_sequence', 'output_sequence'];
170+ return data && instructProps.every(prop => Object.keys(data).includes(prop));
171+ }
172+
173+ static isPossiblyContextData(data) {
174+ const contextProps = ['name', 'story_string'];
175+ return data && contextProps.every(prop => Object.keys(data).includes(prop));
176+ }
177+
178+ static isPossiblySystemPromptData(data) {
179+ const sysPromptProps = ['name', 'content'];
180+ return data && sysPromptProps.every(prop => Object.keys(data).includes(prop));
181+ }
182+
183+ static isPossiblyTextCompletionData(data) {
184+ const textCompletionProps = ['temp', 'top_k', 'top_p', 'rep_pen'];
185+ return data && textCompletionProps.every(prop => Object.keys(data).includes(prop));
186+ }
187+
188+ /**
189+ * Imports master settings from JSON data.
190+ * @param {object} data Data to import
191+ * @param {string} fileName File name
192+ * @returns {Promise<void>}
193+ */
194+ static async performMasterImport(data, fileName) {
195+ if (!data || typeof data !== 'object') {
196+ toastr.error('Invalid data provided for master import');
197+ return;
198+ }
199+
200+ // Check for legacy file imports
201+ // 1. Instruct Template
202+ if (this.isPossiblyInstructData(data)) {
203+ toastr.info('Importing instruct template...', 'Instruct template detected');
204+ return await getPresetManager('instruct').savePreset(data.name, data);
205+ }
206+
207+ // 2. Context Template
208+ if (this.isPossiblyContextData(data)) {
209+ toastr.info('Importing as context template...', 'Context template detected');
210+ return await getPresetManager('context').savePreset(data.name, data);
211+ }
212+
213+ // 3. System Prompt
214+ if (this.isPossiblySystemPromptData(data)) {
215+ toastr.info('Importing as system prompt...', 'System prompt detected');
216+ return await getPresetManager('sysprompt').savePreset(data.name, data);
217+ }
218+
219+ // 4. Text Completion settings
220+ if (this.isPossiblyTextCompletionData(data)) {
221+ toastr.info('Importing as settings preset...', 'Text Completion settings detected');
222+ return await getPresetManager('textgenerationwebui').savePreset(fileName, data);
223+ }
224+
225+ const validSections = [];
226+ for (const [key, section] of Object.entries(this.masterSections)) {
227+ if (key in data && section.isValid(data[key])) {
228+ validSections.push(key);
229+ }
230+ }
231+
232+ if (validSections.length === 0) {
233+ toastr.error('No valid sections found in imported data');
234+ return;
235+ }
236+
237+ const sectionNames = validSections.reduce((acc, key) => {
238+ acc[key] = { key: key, name: this.masterSections[key].name, preset: data[key]?.name || '' };
239+ return acc;
240+ }, {});
241+
242+ const html = $(await renderTemplateAsync('masterImport', { sections: sectionNames }));
243+ const popup = new Popup(html, POPUP_TYPE.CONFIRM, '', {
244+ okButton: 'Import',
245+ cancelButton: 'Cancel',
246+ });
247+
248+ const result = await popup.show();
249+
250+ // Import cancelled
251+ if (result !== POPUP_RESULT.AFFIRMATIVE) {
252+ return;
253+ }
254+
255+ const importedSections = [];
256+ const confirmedSections = html.find('input:checked').map((_, el) => el instanceof HTMLInputElement && el.value).get();
257+
258+ if (confirmedSections.length === 0) {
259+ toastr.info('No sections selected for import');
260+ return;
261+ }
262+
263+ for (const section of confirmedSections) {
264+ const sectionData = data[section];
265+ const masterSection = this.masterSections[section];
266+ if (sectionData && masterSection) {
267+ await masterSection.setData(sectionData);
268+ importedSections.push(masterSection.name);
269+ }
270+ }
271+
272+ toastr.success(`Imported ${importedSections.length} settings: ${importedSections.join(', ')}`);
273+ }
274+
275+ /**
276+ * Exports master settings to JSON data.
277+ * @returns {Promise<string>} JSON data
278+ */
279+ static async performMasterExport() {
280+ const sectionNames = Object.entries(this.masterSections).reduce((acc, [key, section]) => {
281+ acc[key] = { key: key, name: section.name, checked: key !== 'preset' };
282+ return acc;
283+ }, {});
284+ const html = $(await renderTemplateAsync('masterExport', { sections: sectionNames }));
285+
286+ const popup = new Popup(html, POPUP_TYPE.CONFIRM, '', {
287+ okButton: 'Export',
288+ cancelButton: 'Cancel',
289+ });
290+
291+ const result = await popup.show();
292+
293+ // Export cancelled
294+ if (result !== POPUP_RESULT.AFFIRMATIVE) {
295+ return;
296+ }
297+
298+ const confirmedSections = html.find('input:checked').map((_, el) => el instanceof HTMLInputElement && el.value).get();
299+ const data = {};
300+
301+ if (confirmedSections.length === 0) {
302+ toastr.info('No sections selected for export');
303+ return;
304+ }
305+
306+ for (const section of confirmedSections) {
307+ const masterSection = this.masterSections[section];
308+ if (masterSection) {
309+ data[section] = masterSection.getData();
310+ }
311+ }
312+
313+ return JSON.stringify(data, null, 4);
314+ }
315+
105316 /**
106317 * Gets all preset names.
107318 * @returns {string[]} List of preset names
@@ -182,6 +393,10 @@ class PresetManager {
182393 }
183394
184395 async savePreset(name, settings) {
396+ if (this.apiId === 'instruct' && settings) {
397+ await checkForSystemPromptInInstructTemplate(name, settings);
398+ }
399+
185400 const preset = settings ?? this.getPresetSettings(name);
186401
187402 const response = await fetch('/api/presets/save', {
@@ -228,6 +443,10 @@ class PresetManager {
228443 presets = instruct_presets;
229444 preset_names = instruct_presets.map(x => x.name);
230445 break;
446+ case 'sysprompt':
447+ presets = system_prompts;
448+ preset_names = system_prompts.map(x => x.name);
449+ break;
231450 default:
232451 console.warn(`Unknown API ID ${this.apiId}`);
233452 }
@@ -236,11 +455,11 @@ class PresetManager {
236455 }
237456
238457 isKeyedApi() {
239458 return this.apiId == 'textgenerationwebui' || this.apiId == 'context' || this.apiId == 'instruct'isAdvancedFormatting();
240459 }
241460
242461 isAdvancedFormatting() {
243462 return this.apiId == 'context' || this.apiId == 'instruct' || this.apiId == 'sysprompt';
244463 }
245464
246465 updateList(name, preset) {
@@ -298,6 +517,11 @@ class PresetManager {
298517 instruct_preset['name'] = name || power_user.instruct.preset;
299518 return instruct_preset;
300519 }
520+ case 'sysprompt': {
521+ const sysprompt_preset = structuredClone(power_user.sysprompt);
522+ sysprompt_preset['name'] = name || power_user.sysprompt.preset;
523+ return sysprompt_preset;
524+ }
301525 default:
302526 console.warn(`Unknown API ID ${apiId}`);
303527 return {};
@@ -601,11 +825,6 @@ export async function initPresetManager() {
601825 return;
602826 }
603827
604- // default context preset cannot be deleted
605- if (apiId == 'context' && power_user.default_context === power_user.context.preset) {
606- return;
607- }
608-
609828 const headerText = !presetManager.isAdvancedFormatting() ? 'Delete this preset?' : 'Delete this template?';
610829 const confirm = await Popup.show.confirm(headerText, 'This action is irreversible and your current settings will be overwritten.');
611830 if (!confirm) {
@@ -682,4 +901,35 @@ export async function initPresetManager() {
682901 toastr.success(successToast);
683902 }
684903 });
904+
905+ $('#af_master_import').on('click', () => {
906+ $('#af_master_import_file').trigger('click');
907+ });
908+
909+ $('#af_master_import_file').on('change', async function (e) {
910+ if (!(e.target instanceof HTMLInputElement)) {
911+ return;
912+ }
913+ const file = e.target.files[0];
914+
915+ if (!file) {
916+ return;
917+ }
918+
919+ const data = await parseJsonFile(file);
920+ const fileName = file.name.replace('.json', '');
921+ await PresetManager.performMasterImport(data, fileName);
922+ e.target.value = null;
923+ });
924+
925+ $('#af_master_export').on('click', async () => {
926+ const data = await PresetManager.performMasterExport();
927+
928+ if (!data) {
929+ return;
930+ }
931+
932+ const shortDate = new Date().toISOString().split('T')[0];
933+ download(data, `ST-formatting-${shortDate}.json`, 'application/json');
934+ });
685935}
public/scripts/sysprompt.js+250 -0
@@ -0,0 +1,250 @@
1+import { saveSettingsDebounced } from '../script.js';
2+import { callGenericPopup, POPUP_TYPE } from './popup.js';
3+import { power_user } from './power-user.js';
4+import { getPresetManager } from './preset-manager.js';
5+import { SlashCommand } from './slash-commands/SlashCommand.js';
6+import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
7+import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
8+import { enumTypes, SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';
9+import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
10+import { renderTemplateAsync } from './templates.js';
11+import { isTrueBoolean, resetScrollHeight } from './utils.js';
12+
13+export let system_prompts = [];
14+
15+const $enabled = $('#sysprompt_enabled');
16+const $select = $('#sysprompt_select');
17+const $content = $('#sysprompt_content');
18+const $contentBlock = $('#SystemPromptBlock');
19+
20+async function migrateSystemPromptFromInstructMode() {
21+ if ('system_prompt' in power_user.instruct) {
22+ const prompt = String(power_user.instruct.system_prompt);
23+ delete power_user.instruct.system_prompt;
24+ power_user.sysprompt.enabled = power_user.instruct.enabled;
25+ power_user.sysprompt.content = prompt;
26+
27+ const existingPromptName = system_prompts.find(x => x.content === prompt)?.name;
28+
29+ if (existingPromptName) {
30+ power_user.sysprompt.name = existingPromptName;
31+ } else {
32+ const data = { name: `[Migrated] ${power_user.instruct.preset}`, content: prompt };
33+ await getPresetManager('sysprompt')?.savePreset(data.name, data);
34+ power_user.sysprompt.name = data.name;
35+ }
36+
37+ saveSettingsDebounced();
38+ toastr.info('System prompt settings have been moved from the Instruct Mode.', 'Migration notice', { timeOut: 5000 });
39+ }
40+}
41+
42+/**
43+ * Loads sysprompt settings from the given data object.
44+ * @param {object} data Settings data object.
45+ */
46+export async function loadSystemPrompts(data) {
47+ if (data.sysprompt !== undefined) {
48+ system_prompts = data.sysprompt;
49+ }
50+
51+ await migrateSystemPromptFromInstructMode();
52+ toggleSystemPromptDisabledControls();
53+
54+ for (const prompt of system_prompts) {
55+ $('<option>').val(prompt.name).text(prompt.name).appendTo($select);
56+ }
57+
58+ $enabled.prop('checked', power_user.sysprompt.enabled);
59+ $select.val(power_user.sysprompt.name);
60+ $content.val(power_user.sysprompt.content);
61+ if (!CSS.supports('field-sizing', 'content')) {
62+ await resetScrollHeight($content);
63+ }
64+}
65+
66+/**
67+ * Checks if the instruct template has a system prompt and prompts the user to save it as a system prompt.
68+ * @param {string} name Name of the instruct template
69+ * @param {object} template Instruct template object
70+ */
71+export async function checkForSystemPromptInInstructTemplate(name, template) {
72+ if (!template || !name || typeof name !== 'string' || typeof template !== 'object') {
73+ return;
74+ }
75+ if ('system_prompt' in template && template.system_prompt) {
76+ const existingName = system_prompts.find(x => x.content === template.system_prompt)?.name;
77+ const html = await renderTemplateAsync('migrateInstructPrompt', { prompt: template.system_prompt, existing: existingName });
78+ const confirm = await callGenericPopup(html, POPUP_TYPE.CONFIRM);
79+ if (confirm) {
80+ const migratedName = `[Migrated] ${name}`;
81+ const prompt = { name: migratedName, content: template.system_prompt };
82+ const presetManager = getPresetManager('sysprompt');
83+ await presetManager.savePreset(migratedName, prompt);
84+ toastr.success(`System prompt "${migratedName}" has been saved.`);
85+ } else {
86+ toastr.info('System prompt has been discarded.');
87+ }
88+
89+ delete template.system_prompt;
90+ }
91+}
92+
93+function toggleSystemPromptDisabledControls() {
94+ $enabled.parent().find('i').toggleClass('toggleEnabled', !!power_user.sysprompt.enabled);
95+ $contentBlock.toggleClass('disabled', !power_user.sysprompt.enabled);
96+}
97+
98+/**
99+ * Sets the system prompt state.
100+ * @param {boolean} state System prompt state
101+ * @returns {string} Empty string
102+ */
103+function setSystemPromptStateCallback(state) {
104+ power_user.sysprompt.enabled = state;
105+ $enabled.prop('checked', state);
106+ toggleSystemPromptDisabledControls();
107+ saveSettingsDebounced();
108+ return '';
109+}
110+
111+function toggleSystemPromptCallback(_args, state) {
112+ if (!state || typeof state !== 'string') {
113+ return String(power_user.sysprompt.enabled);
114+ }
115+
116+ const newState = isTrueBoolean(state);
117+ setSystemPromptStateCallback(newState);
118+ return String(power_user.sysprompt.enabled);
119+}
120+
121+function selectSystemPromptCallback(args, name) {
122+ if (!power_user.sysprompt.enabled && !isTrueBoolean(args.forceGet)) {
123+ return '';
124+ }
125+
126+ if (!name) {
127+ return power_user.sysprompt.name ?? '';
128+ }
129+
130+ const quiet = isTrueBoolean(args?.quiet);
131+ const systemPromptNames = system_prompts.map(preset => preset.name);
132+ let foundName = systemPromptNames.find(x => x.toLowerCase() === name.toLowerCase());
133+
134+ if (!foundName) {
135+ const fuse = new Fuse(systemPromptNames);
136+ const result = fuse.search(name);
137+
138+ if (result.length === 0) {
139+ !quiet && toastr.warning(`System prompt "${name}" not found`);
140+ return '';
141+ }
142+
143+ foundName = result[0].item;
144+ }
145+
146+ $select.val(foundName).trigger('input');
147+ !quiet && toastr.success(`System prompt "${foundName}" selected`);
148+ return foundName;
149+}
150+
151+export function initSystemPrompts() {
152+ $enabled.on('input', function () {
153+ power_user.sysprompt.enabled = !!$(this).prop('checked');
154+ toggleSystemPromptDisabledControls();
155+ saveSettingsDebounced();
156+ });
157+
158+ $select.on('change', async function () {
159+ if (!power_user.sysprompt.enabled) {
160+ $enabled.prop('checked', true).trigger('input');
161+ }
162+
163+ const name = String($(this).val());
164+ const prompt = system_prompts.find(p => p.name === name);
165+ if (prompt) {
166+ $content.val(prompt.content);
167+ if (!CSS.supports('field-sizing', 'content')) {
168+ await resetScrollHeight($content);
169+ }
170+
171+ power_user.sysprompt.name = name;
172+ power_user.sysprompt.content = prompt.content;
173+ }
174+ saveSettingsDebounced();
175+ });
176+
177+ $content.on('input', function () {
178+ power_user.sysprompt.content = String($(this).val());
179+ saveSettingsDebounced();
180+ });
181+
182+ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
183+ name: 'sysprompt',
184+ aliases: ['system-prompt'],
185+ callback: selectSystemPromptCallback,
186+ returns: 'current prompt name',
187+ namedArgumentList: [
188+ SlashCommandNamedArgument.fromProps({
189+ name: 'quiet',
190+ description: 'Suppress the toast message on prompt change',
191+ typeList: [ARGUMENT_TYPE.BOOLEAN],
192+ defaultValue: 'false',
193+ enumList: commonEnumProviders.boolean('trueFalse')(),
194+ }),
195+ SlashCommandNamedArgument.fromProps({
196+ name: 'forceGet',
197+ description: 'Force getting a name even if system prompt is disabled',
198+ typeList: [ARGUMENT_TYPE.BOOLEAN],
199+ defaultValue: 'false',
200+ enumList: commonEnumProviders.boolean('trueFalse')(),
201+ }),
202+ ],
203+ unnamedArgumentList: [
204+ SlashCommandArgument.fromProps({
205+ description: 'system prompt name',
206+ typeList: [ARGUMENT_TYPE.STRING],
207+ enumProvider: () => system_prompts.map(x => new SlashCommandEnumValue(x.name, null, enumTypes.enum, enumIcons.preset)),
208+ }),
209+ ],
210+ helpString: `
211+ <div>
212+ Selects a system prompt by name, using fuzzy search to find the closest match.
213+ Gets the current system prompt if no name is provided and sysprompt is enabled or <code>forceGet=true</code> is passed.
214+ </div>
215+ <div>
216+ <strong>Example:</strong>
217+ <ul>
218+ <li>
219+ <pre><code class="language-stscript">/sysprompt </code></pre>
220+ </li>
221+ </ul>
222+ </div>
223+ `,
224+ }));
225+ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
226+ name: 'sysprompt-on',
227+ aliases: ['sysprompt-enable'],
228+ callback: () => setSystemPromptStateCallback(true),
229+ helpString: 'Enables system prompt.',
230+ }));
231+ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
232+ name: 'sysprompt-off',
233+ aliases: ['sysprompt-disable'],
234+ callback: () => setSystemPromptStateCallback(false),
235+ helpString: 'Disables system prompt',
236+ }));
237+ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
238+ name: 'sysprompt-state',
239+ aliases: ['sysprompt-toggle'],
240+ helpString: 'Gets the current system prompt state. If an argument is provided, it will set the system prompt state.',
241+ unnamedArgumentList: [
242+ SlashCommandArgument.fromProps({
243+ description: 'system prompt state',
244+ typeList: [ARGUMENT_TYPE.BOOLEAN],
245+ enumList: commonEnumProviders.boolean('trueFalse')(),
246+ }),
247+ ],
248+ callback: toggleSystemPromptCallback,
249+ }));
250+}
public/scripts/templates/macros.html+2 -2
@@ -57,8 +57,8 @@
5757 <li><tt>&lcub;&lcub;maxPrompt&rcub;&rcub;</tt> – <span data-i18n="help_macros_41">max allowed prompt length in tokens = (context size - response length)</span></li>
5858 <li><tt>&lcub;&lcub;exampleSeparator&rcub;&rcub;</tt> – <span data-i18n="help_macros_42">context template example dialogues separator</span></li>
5959 <li><tt>&lcub;&lcub;chatStart&rcub;&rcub;</tt> – <span data-i18n="help_macros_43">context template chat start line</span></li>
6060 <li><tt>&lcub;&lcub;systemPrompt&rcub;&rcub;</tt> – <span data-i18n="help_macros_44">main system prompt content if enabled (either character prompt override if chosenallowed, or instructSystemPromptdefaultSystemPrompt)</span></li>
6161 <li><tt>&lcub;&lcub;instructSystemPromptdefaultSystemPrompt&rcub;&rcub;</tt> – <span data-i18n="help_macros_45">instruct system prompt content</span></li>
6262 <li><tt>&lcub;&lcub;instructSystemPromptPrefix&rcub;&rcub;</tt> – <span data-i18n="help_macros_46">instruct system prompt prefix sequence</span></li>
6363 <li><tt>&lcub;&lcub;instructSystemPromptSuffix&rcub;&rcub;</tt> – <span data-i18n="help_macros_47">instruct system prompt suffix sequence</span></li>
6464 <li><tt>&lcub;&lcub;instructUserPrefix&rcub;&rcub;</tt> – <span data-i18n="help_macros_48">instruct user prefix sequence</span></li>
public/scripts/templates/masterExport.html+17 -0
@@ -0,0 +1,17 @@
1+<h3>
2+ Choose what to export
3+</h3>
4+<div class="flex-container flexFlowColumn justifyLeft">
5+ {{#each sections}}
6+ {{#with this}}
7+ <label class="checkbox_label">
8+ {{#if checked}}
9+ <input type="checkbox" value="{{key}}" checked />
10+ {{else}}
11+ <input type="checkbox" value="{{key}}" />
12+ {{/if}}
13+ <span data-i18n="{{name}}">{{name}}</span>
14+ </label>
15+ {{/with}}
16+ {{/each}}
17+</div>
public/scripts/templates/masterImport.html+17 -0
@@ -0,0 +1,17 @@
1+<h3>
2+ Choose what to import
3+</h3>
4+<div class="flex-container flexFlowColumn justifyLeft">
5+ {{#each sections}}
6+ {{#with this}}
7+ <label class="checkbox_label">
8+ <input type="checkbox" value="{{key}}" checked>
9+ <span data-i18n="{{name}}">{{name}}</span>
10+ {{#if preset}}
11+ <span>&ndash;</span>
12+ <small>{{preset}}</small>
13+ {{/if}}
14+ </label>
15+ {{/with}}
16+ {{/each}}
17+</div>
public/scripts/templates/migrateInstructPrompt.html+27 -0
@@ -0,0 +1,27 @@
1+<h3>
2+ This instruct template also contains a system prompt.
3+</h3>
4+<div>
5+ Would you like to migrate the system prompt from the template?
6+</div>
7+{{#if existing}}
8+<div class="marginTopBot5">
9+ <b>Note:</b>
10+ <span>you already have this prompt saved as:</span>
11+ <span>{{existing}}</span>
12+</div>
13+{{/if}}
14+<div class="justifyLeft marginTop5">
15+ <div>
16+ <small>
17+ <b>"Yes"</b> &ndash; The prompt will be imported and selected as a current system prompt.
18+ </small>
19+
20+ </div>
21+ <div>
22+ <small>
23+ <b>"No"</b> &ndash; The prompt will be ignored, no changes to the current system prompt.
24+ </small>
25+ </div>
26+</div>
27+<textarea class="wide100p textarea_compact" rows="10">{{prompt}}</textarea>
public/style.css+10 -9
@@ -2573,7 +2573,7 @@ select {
25732573 -moz-appearance: none;
25742574 -webkit-appearance: none;
25752575 appearance: none;
25762576 padding: 3px 2px5px;
25772577 background-color: var(--black30a);
25782578 border: 1px solid var(--SmartThemeBorderColor);
25792579 border-radius: 5px;
@@ -2630,14 +2630,6 @@ select option:not(:checked) {
26302630 color: var(--active) !important;
26312631}
26322632
2633-#context_set_default.default {
2634- color: var(--preferred) !important;
2635-}
2636-
2637-#instruct_set_default.default {
2638- color: var(--preferred) !important;
2639-}
2640-
26412633.displayBlock {
26422634 display: block !important;
26432635}
@@ -5477,3 +5469,12 @@ body:not(.movingUI) .drawer-content.maximized {
54775469#AdvancedFormatting .autoSetHeight {
54785470 overflow-wrap: anywhere;
54795471}
5472+
5473+#InstructSequencesColumn summary {
5474+ font-size: 0.95em;
5475+ cursor: pointer;
5476+}
5477+
5478+#InstructSequencesColumn details:not(:last-of-type) {
5479+ margin-bottom: 5px;
5480+}
server.js+1 -0
@@ -932,6 +932,7 @@ async function verifySecuritySettings() {
932932userModule.initUserStorage(dataRoot)
933933 .then(userModule.ensurePublicDirectoriesExist)
934934 .then(userModule.migrateUserData)
935+ .then(userModule.migrateSystemPrompts)
935936 .then(verifySecuritySettings)
936937 .then(preSetupTasks)
937938 .finally(startServer);
src/constants.js+1 -0
@@ -41,6 +41,7 @@ const USER_DIRECTORY_TEMPLATE = Object.freeze({
4141 files: 'user/files',
4242 vectors: 'vectors',
4343 backups: 'backups',
44+ sysprompt: 'sysprompt',
4445});
4546
4647/**
src/endpoints/content-manager.js+40 -1
@@ -43,6 +43,7 @@ const CONTENT_TYPES = {
4343 CONTEXT: 'context',
4444 MOVING_UI: 'moving_ui',
4545 QUICK_REPLIES: 'quick_replies',
46+ SYSPROMPT: 'sysprompt',
4647};
4748
4849/**
@@ -56,7 +57,7 @@ function getDefaultPresets(directories) {
5657 const presets = [];
5758
5859 for (const contentItem of contentIndex) {
5960 if (contentItem.type.endsWith('_preset') || contentItem.type === 'instruct' || contentItem.type === 'context' || contentItem.type === 'sysprompt') {
6061 contentItem.name = path.parse(contentItem.filename).name;
6162 contentItem.folder = getTargetByType(contentItem.type, directories);
6263 presets.push(contentItem);
@@ -218,6 +219,41 @@ function getContentIndex() {
218219}
219220
220221/**
222+ * Gets content by type and format.
223+ * @param {string} type Type of content
224+ * @param {'json'|'string'|'raw'} format Format of content
225+ * @returns {string[]|Buffer[]} Array of content
226+ */
227+function getContentOfType(type, format) {
228+ const contentIndex = getContentIndex();
229+ const indexItems = contentIndex.filter((item) => item.type === type && item.folder);
230+ const files = [];
231+ for (const item of indexItems) {
232+ if (!item.folder) {
233+ continue;
234+ }
235+ try {
236+ const filePath = path.join(item.folder, item.filename);
237+ const fileContent = fs.readFileSync(filePath);
238+ switch (format) {
239+ case 'json':
240+ files.push(JSON.parse(fileContent.toString()));
241+ break;
242+ case 'string':
243+ files.push(fileContent.toString());
244+ break;
245+ case 'raw':
246+ files.push(fileContent);
247+ break;
248+ }
249+ } catch {
250+ // Ignore errors
251+ }
252+ }
253+ return files;
254+}
255+
256+/**
221257 * Gets the target directory for the specified asset type.
222258 * @param {ContentType} type Asset type
223259 * @param {import('../users').UserDirectoryList} directories User directories
@@ -257,6 +293,8 @@ function getTargetByType(type, directories) {
257293 return directories.movingUI;
258294 case CONTENT_TYPES.QUICK_REPLIES:
259295 return directories.quickreplies;
296+ case CONTENT_TYPES.SYSPROMPT:
297+ return directories.sysprompt;
260298 default:
261299 return null;
262300 }
@@ -721,5 +759,6 @@ module.exports = {
721759 checkForNewContent,
722760 getDefaultPresets,
723761 getDefaultPresetFile,
762+ getContentOfType,
724763 router,
725764};
src/endpoints/presets.js+2 -0
@@ -27,6 +27,8 @@ function getPresetSettingsByAPI(apiId, directories) {
2727 return { folder: directories.instruct, extension: '.json' };
2828 case 'context':
2929 return { folder: directories.context, extension: '.json' };
30+ case 'sysprompt':
31+ return { folder: directories.sysprompt, extension: '.json' };
3032 default:
3133 return { folder: null, extension: null };
3234 }
src/endpoints/settings.js+2 -0
@@ -251,6 +251,7 @@ router.post('/get', jsonParser, (request, response) => {
251251
252252 const instruct = readAndParseFromDirectory(request.user.directories.instruct);
253253 const context = readAndParseFromDirectory(request.user.directories.context);
254+ const sysprompt = readAndParseFromDirectory(request.user.directories.sysprompt);
254255
255256 response.send({
256257 settings,
@@ -268,6 +269,7 @@ router.post('/get', jsonParser, (request, response) => {
268269 quickReplyPresets,
269270 instruct,
270271 context,
272+ sysprompt,
271273 enable_extensions: ENABLE_EXTENSIONS,
272274 enable_extensions_auto_update: ENABLE_EXTENSIONS_AUTO_UPDATE,
273275 enable_accounts: ENABLE_ACCOUNTS,
src/users.js+58 -0
@@ -9,6 +9,8 @@ const storage = require('node-persist');
99const express = require('express');
1010const mime = require('mime-types');
1111const archiver = require('archiver');
12+const writeFileAtomicSync = require('write-file-atomic').sync;
13+const _ = require('lodash');
1214
1315const { USER_DIRECTORY_TEMPLATE, DEFAULT_USER, PUBLIC_DIRECTORIES, SETTINGS_FILE } = require('./constants');
1416const { getConfigValue, color, delay, setConfigValue, generateTimestamp } = require('./util');
@@ -83,6 +85,7 @@ const STORAGE_KEYS = {
8385 * @property {string} files - The directory where the uploaded files are stored
8486 * @property {string} vectors - The directory where the vectors are stored
8587 * @property {string} backups - The directory where the backups are stored
88+ * @property {string} sysprompt - The directory where the system prompt data is stored
8689 */
8790
8891/**
@@ -323,6 +326,60 @@ async function migrateUserData() {
323326 console.log(color.green('Migration completed!'));
324327}
325328
329+async function migrateSystemPrompts() {
330+ /**
331+ * Gets the default system prompts.
332+ * @returns {Promise<any[]>} - The list of default system prompts
333+ */
334+ async function getDefaultSystemPrompts() {
335+ try {
336+ const { getContentOfType } = await import('./endpoints/content-manager.js');
337+ return getContentOfType('sysprompt', 'json');
338+ } catch {
339+ return [];
340+ }
341+ }
342+
343+ const directories = await getUserDirectoriesList();
344+ for (const directory of directories) {
345+ try {
346+ const migrateMarker = path.join(directory.sysprompt, '.migrated');
347+ if (fs.existsSync(migrateMarker)) {
348+ continue;
349+ }
350+ const defaultPrompts = await getDefaultSystemPrompts();
351+ const instucts = fs.readdirSync(directory.instruct);
352+ let migratedPrompts = [];
353+ for (const instruct of instucts) {
354+ const instructPath = path.join(directory.instruct, instruct);
355+ const sysPromptPath = path.join(directory.sysprompt, instruct);
356+ if (path.extname(instruct) === '.json' && !fs.existsSync(sysPromptPath)) {
357+ const instructData = JSON.parse(fs.readFileSync(instructPath, 'utf8'));
358+ if ('system_prompt' in instructData && 'name' in instructData) {
359+ const syspromptData = { name: instructData.name, content: instructData.system_prompt };
360+ migratedPrompts.push(syspromptData);
361+ delete instructData.system_prompt;
362+ writeFileAtomicSync(instructPath, JSON.stringify(instructData, null, 4));
363+ }
364+ }
365+ }
366+ // Only leave unique contents
367+ migratedPrompts = _.uniqBy(migratedPrompts, 'content');
368+ // Only leave contents that are not in the default prompts
369+ migratedPrompts = migratedPrompts.filter(x => !defaultPrompts.some(y => y.content === x.content));
370+ for (const sysPromptData of migratedPrompts) {
371+ sysPromptData.name = `[Migrated] ${sysPromptData.name}`;
372+ const syspromptPath = path.join(directory.sysprompt, `${sysPromptData.name}.json`);
373+ writeFileAtomicSync(syspromptPath, JSON.stringify(sysPromptData, null, 4));
374+ console.log(`Migrated system prompt ${sysPromptData.name} for ${directory.root.split(path.sep).pop()}`);
375+ }
376+ writeFileAtomicSync(migrateMarker, '');
377+ } catch {
378+ // Ignore errors
379+ }
380+ }
381+}
382+
326383/**
327384 * Converts a user handle to a storage key.
328385 * @param {string} handle User handle
@@ -724,6 +781,7 @@ module.exports = {
724781 requireLoginMiddleware,
725782 requireAdminMiddleware,
726783 migrateUserData,
784+ migrateSystemPrompts,
727785 getPasswordSalt,
728786 getPasswordHash,
729787 getCsrfSecret,