From d93e3c812e1aa92d582ca813a5d3e7bdc7e4f094 Mon Sep 17 00:00:00 2001 From: Pabloader Date: Tue, 7 Apr 2026 07:52:59 +0000 Subject: [PATCH] System instruction override --- src/games/storywriter/components/editor.tsx | 16 +++++++++++++++- src/games/storywriter/contexts/state.tsx | 10 +++++++++- src/games/storywriter/utils/prompt.ts | 4 ++-- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/games/storywriter/components/editor.tsx b/src/games/storywriter/components/editor.tsx index d49c1ea..760a5c4 100644 --- a/src/games/storywriter/components/editor.tsx +++ b/src/games/storywriter/components/editor.tsx @@ -11,7 +11,7 @@ import { LoreEditor } from "./lore-editor"; import { Menu } from "./menu"; import { useInputCallback } from "@common/hooks/useInputCallback"; import Prompt from "../utils/prompt"; -import { BookOpen, List, Users, MapPin, BookMarked, FileText, Code, Layers, MessageSquare, Globe, type LucideIcon } from "lucide-preact"; +import { BookOpen, List, Users, MapPin, BookMarked, FileText, Code, Layers, MessageSquare, Globe, BrainCircuit, type LucideIcon } from "lucide-preact"; // Tabs available when a story is selected const STORY_TABS: { id: Tab; label: string; icon: LucideIcon; right?: boolean }[] = [ @@ -31,6 +31,7 @@ const WORLD_TABS: { id: Tab; label: string; icon: LucideIcon; right?: boolean }[ { id: "lore", label: "Lore", icon: BookMarked }, { id: "characters", label: "Characters", icon: Users }, { id: "locations", label: "Locations", icon: MapPin }, + { id: "system", label: "System", icon: BrainCircuit }, ]; export const Editor = () => { @@ -47,6 +48,11 @@ export const Editor = () => { dispatch({ type: 'EDIT_SCRATCHPAD', worldId: currentWorld.id, id: currentStory.id, text }); }, [currentStory?.id, currentWorld?.id]); + const handleSystemOverrideInput = useInputCallback((text: string) => { + if (!currentWorld) return; + dispatch({ type: 'SET_WORLD_SYSTEM_INSTRUCTION_OVERRIDE', worldId: currentWorld.id, systemInstructionOverride: text || undefined }); + }, [currentWorld?.id]); + const handleTabChange = (tab: Tab) => { dispatch({ type: 'SET_CURRENT_TAB', tab }); }; @@ -150,6 +156,14 @@ export const Editor = () => { {currentTab === "prompt" && currentStory && (
)} + {currentTab === "system" && currentWorld && ( + + )}
{tabs.filter(tab => hasSelection || tab.id === 'menu').map((tab) => ( diff --git a/src/games/storywriter/contexts/state.tsx b/src/games/storywriter/contexts/state.tsx index 1327c66..f71c9df 100644 --- a/src/games/storywriter/contexts/state.tsx +++ b/src/games/storywriter/contexts/state.tsx @@ -11,7 +11,7 @@ export type ChatMessage = LLM.ChatMessage & { id: string; } -export type Tab = "story" | "lore" | "characters" | "locations" | "chapters" | "scratchpad" | "prompt" | "menu"; +export type Tab = "story" | "lore" | "characters" | "locations" | "chapters" | "scratchpad" | "prompt" | "menu" | "system"; export enum CharacterRole { Protagonist = 'protagonist', @@ -84,6 +84,7 @@ export interface World { characters: Character[]; locations: Location[]; stories: Story[]; + systemInstructionOverride?: string; } // ─── State ─────────────────────────────────────────────────────────────────── @@ -124,6 +125,7 @@ type Action = | { type: 'REORDER_LORE_ENTRIES'; worldId: string; storyId: string | null; entryIds: string[] } // Settings | { type: 'SET_SYSTEM_INSTRUCTION'; systemInstruction: string } + | { type: 'SET_WORLD_SYSTEM_INSTRUCTION_OVERRIDE'; worldId: string; systemInstructionOverride: string | undefined } | { type: 'SET_CURRENT_TAB'; tab: Tab } | { type: 'SET_CHAT_OPEN'; open: boolean } // Chat @@ -357,6 +359,9 @@ function reducer(state: IState, action: Action): IState { case 'SET_SYSTEM_INSTRUCTION': { return { ...state, systemInstruction: action.systemInstruction }; } + case 'SET_WORLD_SYSTEM_INSTRUCTION_OVERRIDE': { + return updateWorld(state, action.worldId, w => ({ ...w, systemInstructionOverride: action.systemInstructionOverride })); + } case 'SET_CURRENT_TAB': { return { ...state, currentTab: action.tab }; } @@ -513,6 +518,8 @@ export interface AppState { enableThinking: boolean; bannedTokens: string[]; systemInstruction: string; + /** Effective system instruction: world override if set, otherwise global */ + effectiveSystemInstruction: string; dispatch: (action: Action) => void; } @@ -557,6 +564,7 @@ export const StateContextProvider = ({ children }: { children?: any }) => { enableThinking: state.enableThinking, bannedTokens: state.bannedTokens ?? [], systemInstruction: state.systemInstruction ?? '', + effectiveSystemInstruction: currentWorld?.systemInstructionOverride ?? state.systemInstruction ?? '', dispatch, }; }, [state]); diff --git a/src/games/storywriter/utils/prompt.ts b/src/games/storywriter/utils/prompt.ts index dd64156..9c9de71 100644 --- a/src/games/storywriter/utils/prompt.ts +++ b/src/games/storywriter/utils/prompt.ts @@ -277,10 +277,10 @@ namespace Prompt { export function formatSystemPrompt(state: AppState, storyTokenBudget: number = 0): string { const { currentStory } = state; if (!currentStory) { - return state.systemInstruction; + return state.effectiveSystemInstruction; } - const parts: string[] = [state.systemInstruction]; + const parts: string[] = [state.effectiveSystemInstruction]; parts.push(`# Story Title: ${currentStory.title}`);