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}`);