diff --git a/src/games/storywriter/components/editor.tsx b/src/games/storywriter/components/editor.tsx index 538daa3..a92384b 100644 --- a/src/games/storywriter/components/editor.tsx +++ b/src/games/storywriter/components/editor.tsx @@ -2,7 +2,7 @@ import { ContentEditable } from "@common/components/ContentEditable"; import { highlight } from "@common/highlight"; import { useAppState, type Tab } from "../contexts/state"; import styles from '../assets/editor.module.css'; -import { useMemo } from "preact/hooks"; +import { useMemo, useRef, useEffect } from "preact/hooks"; import clsx from "clsx"; import { CharacterEditor } from "./character-editor"; import { LocationEditor } from "./location-editor"; @@ -17,7 +17,8 @@ const TABS: { id: Tab; label: string; right?: boolean }[] = [ { id: "lore", label: "Lore" }, { id: "characters", label: "Characters" }, { id: "locations", label: "Locations" }, - { id: "prompt", label: "Prompt", right: true }, + { id: "scratchpad", label: "Scratchpad", right: true }, + { id: "prompt", label: "Prompt" }, ]; export const Editor = () => { @@ -26,11 +27,12 @@ export const Editor = () => { const handleInput = useInputCallback((text: string) => { if (!currentStory) return; - dispatch({ - type: 'EDIT_STORY', - id: currentStory.id, - text, - }); + dispatch({ type: 'EDIT_STORY', id: currentStory.id, text }); + }, [currentStory?.id]); + + const handleScratchpadInput = useInputCallback((text: string) => { + if (!currentStory) return; + dispatch({ type: 'EDIT_STORY', id: currentStory.id, text }); }, [currentStory?.id]); const handleTabChange = (tab: Tab) => { @@ -42,6 +44,14 @@ export const Editor = () => { }); }; + const contentRef = useRef(null); + + useEffect(() => { + if (contentRef.current) { + contentRef.current.scrollTop = contentRef.current.scrollHeight; + } + }, [currentStory?.currentTab]); + const storyValue = useMemo(() => currentStory ? highlight(currentStory.text) : '', [currentStory?.text]); const promptPreview = useMemo(() => { if (currentStory?.currentTab !== 'prompt') return ''; @@ -58,7 +68,7 @@ export const Editor = () => {
{currentStory.title}
-
+
{currentStory.currentTab === "story" && ( { {currentStory.currentTab === "chapters" && ( )} + {currentStory.currentTab === "scratchpad" && ( + + )} {currentStory.currentTab === "prompt" && (
)} diff --git a/src/games/storywriter/contexts/state.tsx b/src/games/storywriter/contexts/state.tsx index bd54d2b..8099221 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" | "prompt"; +export type Tab = "story" | "lore" | "characters" | "locations" | "chapters" | "scratchpad" | "prompt"; export enum CharacterRole { Protagonist = 'protagonist', @@ -67,6 +67,7 @@ export interface Story { id: string; title: string; text: string; + scratchpad: string; lore: LoreEntry[]; characters: Character[]; locations: Location[]; @@ -93,6 +94,7 @@ type Action = | { type: 'CREATE_STORY'; title: string } | { type: 'RENAME_STORY'; id: string; title: string } | { type: 'EDIT_STORY'; id: string; text: string } + | { type: 'EDIT_SCRATCHPAD'; id: string; text: string } | { type: 'ADD_LORE_ENTRY'; storyId: string; entry: LoreEntry } | { type: 'EDIT_LORE_ENTRY'; storyId: string; entryId: string; updates: Partial } | { type: 'DELETE_LORE_ENTRY'; storyId: string; entryId: string } @@ -141,6 +143,7 @@ function reducer(state: IState, action: Action): IState { id: crypto.randomUUID(), title: action.title, text: '', + scratchpad: '', lore: [], characters: [], locations: [], @@ -256,6 +259,7 @@ function reducer(state: IState, action: Action): IState { id: crypto.randomUUID(), title: `${original.title} (Copy)`, text: '', + scratchpad: '', lore: [...original.lore], characters: original.characters, locations: original.locations, @@ -483,6 +487,14 @@ function reducer(state: IState, action: Action): IState { }), }; } + case 'EDIT_SCRATCHPAD': { + return { + ...state, + stories: state.stories.map(s => + s.id === action.id ? { ...s, scratchpad: action.text } : s + ), + }; + } case 'STORE_CHAPTER_SUMMARY': { return { ...state, diff --git a/src/games/storywriter/utils/prompt.ts b/src/games/storywriter/utils/prompt.ts index 6be23c1..c9e728e 100644 --- a/src/games/storywriter/utils/prompt.ts +++ b/src/games/storywriter/utils/prompt.ts @@ -299,6 +299,10 @@ namespace Prompt { parts.push(locationsSection); } + if (currentStory.scratchpad) { + parts.push(`## Scratchpad`, currentStory.scratchpad); + } + if (currentStory.text && storyTokenBudget > 0) { const storyText = formatStoryChunks(currentStory.text, currentStory.chapters ?? [], storyTokenBudget); if (storyText) {