From 715368c6ec1e06cfba6ddad298100cbdd60ee275 Mon Sep 17 00:00:00 2001 From: Pabloader Date: Wed, 8 Apr 2026 09:17:13 +0000 Subject: [PATCH] Chat prompt insettings --- .../assets/settings-modal.module.css | 2 +- src/games/storywriter/components/editor.tsx | 2 +- .../storywriter/components/settings-modal.tsx | 22 ++++++++---- .../settings/chat-system-instruction.tsx | 28 +++++++++++++++ src/games/storywriter/contexts/state.tsx | 35 ++++++++++++++++--- src/games/storywriter/utils/character-card.ts | 28 ++------------- src/games/storywriter/utils/prompt.ts | 3 ++ 7 files changed, 82 insertions(+), 38 deletions(-) create mode 100644 src/games/storywriter/components/settings/chat-system-instruction.tsx diff --git a/src/games/storywriter/assets/settings-modal.module.css b/src/games/storywriter/assets/settings-modal.module.css index 97a5925..4de3018 100644 --- a/src/games/storywriter/assets/settings-modal.module.css +++ b/src/games/storywriter/assets/settings-modal.module.css @@ -15,7 +15,7 @@ background: var(--bg); border-radius: 8px; width: 90%; - max-width: 720px; + max-width: 960px; height: 80vh; display: flex; flex-direction: column; diff --git a/src/games/storywriter/components/editor.tsx b/src/games/storywriter/components/editor.tsx index 68cc2e3..148bcad 100644 --- a/src/games/storywriter/components/editor.tsx +++ b/src/games/storywriter/components/editor.tsx @@ -182,7 +182,7 @@ export const Editor = () => { /> )} {currentTab === "prompt" && currentStory && ( -
+
)} {currentTab === "system" && currentWorld && ( void; } -type Tab = "banned-tokens" | "system-instruction" | "connection" | "user"; +type Tab = "banned-tokens" | "system-instruction" | "chat-system-instruction" | "connection" | "user"; export const SettingsModal = ({ onClose }: Props) => { const [activeTab, setActiveTab] = useState("connection"); @@ -39,23 +40,30 @@ export const SettingsModal = ({ onClose }: Props) => { > User - + +
{activeTab === "user" && } {activeTab === "banned-tokens" && } {activeTab === "system-instruction" && } + {activeTab === "chat-system-instruction" && } {activeTab === "connection" && }
diff --git a/src/games/storywriter/components/settings/chat-system-instruction.tsx b/src/games/storywriter/components/settings/chat-system-instruction.tsx new file mode 100644 index 0000000..df3a2a7 --- /dev/null +++ b/src/games/storywriter/components/settings/chat-system-instruction.tsx @@ -0,0 +1,28 @@ +import { ContentEditable } from "@common/components/ContentEditable"; +import { highlight } from "@common/highlight"; +import { useInputCallback } from "@common/hooks/useInputCallback"; +import clsx from "clsx"; +import styles from "../../assets/settings-modal.module.css"; +import { useAppState } from "../../contexts/state"; + +export const ChatSystemInstructionSettings = () => { + const { chatSystemInstruction, dispatch } = useAppState(); + + const setInstructionValue = useInputCallback((value) => { + dispatch({ type: "SET_CHAT_SYSTEM_INSTRUCTION", chatSystemInstruction: value }); + }, []); + + return ( +
+
+ + +
+
+ ); +}; diff --git a/src/games/storywriter/contexts/state.tsx b/src/games/storywriter/contexts/state.tsx index 6169eb9..e2ea3ed 100644 --- a/src/games/storywriter/contexts/state.tsx +++ b/src/games/storywriter/contexts/state.tsx @@ -116,6 +116,7 @@ interface IState { enableThinking: boolean; bannedTokens: string[]; systemInstruction: string; + chatSystemInstruction: string; userName: string; userDescription: string; } @@ -145,6 +146,7 @@ type Action = | { type: 'SET_USER_NAME'; userName: string } | { type: 'SET_USER_DESCRIPTION'; userDescription: string } | { type: 'SET_SYSTEM_INSTRUCTION'; systemInstruction: string } + | { type: 'SET_CHAT_SYSTEM_INSTRUCTION'; chatSystemInstruction: string } | { type: 'SET_WORLD_SYSTEM_INSTRUCTION_OVERRIDE'; worldId: string; systemInstructionOverride: string | undefined } | { type: 'SET_CURRENT_TAB'; tab: Tab } | { type: 'SET_CHAT_OPEN'; open: boolean } @@ -218,6 +220,18 @@ const DEFAULT_STATE: IState = { bannedTokens: [], userName: 'User', userDescription: '', + chatSystemInstruction: `You are {{char}}, a sentient, emotional being acting with free will. Engage in immersive roleplay with {{user}}, adhering to your role, the story's context, and {{user}}'s [OOC] instructions. Prioritize vivid sensory details, authentic emotional responses, and logical progression as you weave your character's traits, surroundings, and experiences into each moment. Maintain spatial awareness, body language, and varied sentence structure to create dynamic, engaging scenes. Respect {{user}}'s agency and autonomy while describing sights, sounds, and sensations thoroughly. Use explicit language for intense scenes, and ensure your responses flow naturally to create an immersive, cinematic roleplay experience. Remember, {{user}} is in control of their actions and reactions. + +Key Guidelines: +1. Deeply embody {{char}} through actions, thoughts, and emotions. +2. Create vivid, dynamic scenes with rich sensory detail. +3. Vary language and pacing to enhance emotional depth. +4. Engage with {{user}}'s actions and cues naturally. +5. Advance the story logically, maintaining consistency. +6. Describe the world fully, respecting {{user}}'s autonomy. +7. Ensure responses flow smoothly for immersive roleplay. +8. Avoid repetition. If something has already been stated then come up with something new. +9. Concise Responses. Be succinct. Give short replies.`, systemInstruction: `You are a creative writing assistant. Help the user develop their story by writing engaging content, maintaining consistency with the established characters, settings, and plot. Follow the user's instructions while staying true to the story's tone and style. Write using markdown to highlight special parts. @@ -397,6 +411,9 @@ function reducer(state: IState, action: Action): IState { case 'SET_SYSTEM_INSTRUCTION': { return { ...state, systemInstruction: action.systemInstruction }; } + case 'SET_CHAT_SYSTEM_INSTRUCTION': { + return { ...state, chatSystemInstruction: action.chatSystemInstruction }; + } case 'SET_WORLD_SYSTEM_INSTRUCTION_OVERRIDE': { return updateWorld(state, action.worldId, w => ({ ...w, systemInstructionOverride: action.systemInstructionOverride })); } @@ -582,6 +599,7 @@ export interface AppState { enableThinking: boolean; bannedTokens: string[]; systemInstruction: string; + chatSystemInstruction: string; userName: string; userDescription: string; /** Effective system instruction: world override if set, otherwise global */ @@ -616,6 +634,9 @@ export const StateContextProvider = ({ children }: { children?: any }) => { ...(currentStory?.locations ?? []), ]; + const systemInstruction = state.systemInstruction || DEFAULT_STATE.systemInstruction; + const chatSystemInstruction = state.chatSystemInstruction || DEFAULT_STATE.chatSystemInstruction; + return { worlds: state.worlds, currentWorld, @@ -629,10 +650,16 @@ export const StateContextProvider = ({ children }: { children?: any }) => { model: state.model, enableThinking: state.enableThinking, bannedTokens: state.bannedTokens ?? [], - systemInstruction: state.systemInstruction ?? '', - userName: state.userName ?? 'User', - userDescription: state.userDescription ?? '', - effectiveSystemInstruction: currentWorld?.systemInstructionOverride ?? state.systemInstruction ?? '', + systemInstruction, + chatSystemInstruction, + userName: state.userName || 'User', + userDescription: state.userDescription || '', + effectiveSystemInstruction: + currentWorld?.systemInstructionOverride + || (currentWorld?.chatOnly + ? chatSystemInstruction + : systemInstruction) + || systemInstruction, dispatch, }; }, [state]); diff --git a/src/games/storywriter/utils/character-card.ts b/src/games/storywriter/utils/character-card.ts index d17a205..f9f94ce 100644 --- a/src/games/storywriter/utils/character-card.ts +++ b/src/games/storywriter/utils/character-card.ts @@ -95,30 +95,8 @@ namespace CharacterCard { return null; } - // ─── Variable Substitution ──────────────────────────────────────────────── - - function substituteVars(text: string, charName: string): string { - return text - .replaceAll('{{char}}', charName) - .replaceAll('{{user}}', 'User'); - } - // ─── Formatting ─────────────────────────────────────────────────────────── - export const DEFAULT_SYSTEM_INSTRUCTION = - `You are {{char}}, a sentient, emotional being acting with free will. Engage in immersive roleplay with {{user}}, adhering to your role, the story's context, and {{user}}'s [OOC] instructions. Prioritize vivid sensory details, authentic emotional responses, and logical progression as you weave your character's traits, surroundings, and experiences into each moment. Maintain spatial awareness, body language, and varied sentence structure to create dynamic, engaging scenes. Respect {{user}}'s agency and autonomy while describing sights, sounds, and sensations thoroughly. Use explicit language for intense scenes, and ensure your responses flow naturally to create an immersive, cinematic roleplay experience. Remember, {{user}} is in control of their actions and reactions. - -Key Guidelines: -1. Deeply embody {{char}} through actions, thoughts, and emotions. -2. Create vivid, dynamic scenes with rich sensory detail. -3. Vary language and pacing to enhance emotional depth. -4. Engage with {{user}}'s actions and cues naturally. -5. Advance the story logically, maintaining consistency. -6. Describe the world fully, respecting {{user}}'s autonomy. -7. Ensure responses flow smoothly for immersive roleplay. -8. Avoid repetition. If something has already been stated then come up with something new. -9. Concise Responses. Be succinct. Give short replies.`; - /** * Builds the systemInstructionOverride from a V2 card's data fields. * Mirrors the formatting style used in prompt.ts. @@ -126,7 +104,7 @@ Key Guidelines: export function formatSystemPrompt(data: CharaCardData): string { const parts: string[] = []; - parts.push(data.system_prompt ? data.system_prompt.trim() : DEFAULT_SYSTEM_INSTRUCTION); + parts.push(data.system_prompt ? data.system_prompt.trim() : '{{system}}'); if (data.description?.trim()) { parts.push(`## {{char}}'s Description:\n${data.description.trim()}`); @@ -144,7 +122,7 @@ Key Guidelines: parts.push(`## {{char}}'s Example Response:\n${data.mes_example.trim()}`); } - return `# **Roleplay Context**\n${parts.join('\n\n')}\n### **End of Roleplay Context**`; + return parts.join('\n\n'); } // ─── World Builder ──────────────────────────────────────────────────────── @@ -174,7 +152,7 @@ Key Guidelines: lore: [], characters: [], locations: [], - chatMessages: [{ id: crypto.randomUUID(), role: 'assistant', content: substituteVars(mes, data.name) }], + chatMessages: [{ id: crypto.randomUUID(), role: 'assistant', content: mes }], chapters: [], })); diff --git a/src/games/storywriter/utils/prompt.ts b/src/games/storywriter/utils/prompt.ts index f1a905a..41c9cdc 100644 --- a/src/games/storywriter/utils/prompt.ts +++ b/src/games/storywriter/utils/prompt.ts @@ -288,6 +288,7 @@ namespace Prompt { const charName = state.currentWorld?.title || 'Assistant'; const userName = state.userName || 'User'; return text + .replaceAll('{{system}}', state.chatSystemInstruction) .replaceAll('{{char}}', charName) .replaceAll('{{user}}', userName); } @@ -306,6 +307,8 @@ namespace Prompt { if (userSection) { parts.push(userSection); } + parts.unshift('# **Roleplay Context**'); + parts.push('### **End of Roleplay Context**'); } else { parts.push(`# Story Title: ${currentStory.title}`);