1
0
Fork 0

Compare commits

...

2 Commits

Author SHA1 Message Date
Pabloader 215d320e9e Scratchpad editing with tool 2026-03-26 11:25:13 +00:00
Pabloader ebcf5ecba2 Fix story editing instead of scratchpad 2026-03-26 11:11:45 +00:00
3 changed files with 47 additions and 31 deletions

View File

@ -32,7 +32,7 @@ export const Editor = () => {
const handleScratchpadInput = useInputCallback((text: string) => { const handleScratchpadInput = useInputCallback((text: string) => {
if (!currentStory) return; if (!currentStory) return;
dispatch({ type: 'EDIT_STORY', id: currentStory.id, text }); dispatch({ type: 'EDIT_SCRATCHPAD', id: currentStory.id, text });
}, [currentStory?.id]); }, [currentStory?.id]);
const handleTabChange = (tab: Tab) => { const handleTabChange = (tab: Tab) => {

View File

@ -131,7 +131,28 @@ const DEFAULT_STATE: IState = {
model: null, model: null,
enableThinking: false, enableThinking: false,
bannedTokens: [], bannedTokens: [],
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.`, 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.
Supported markdown subset:
- *italic*
- **bold**
- "quotes"
- \`monospace\`
- # Header 1
- ## Header 2
- ### Header 3
- > blockquote
- Tables
- Ordered lists
- Unordered lists (only with \`- \` markers)
- Only top-level lists (no nesting)
Show the chapters with \`# Chapter\` headers.
Add important details not yet ready to be included in the story to the scratchpad: character motivations, hidden plot points, etc.
You **must** use \`edit_text\` tool to write to the story.
Keep the reports after editing concise to save token budget, just say "Done" with minimal to no thinking.
The most actual state of the story is provided below, use it as ground truth.`,
}; };
// ─── Reducer ───────────────────────────────────────────────────────────────── // ─── Reducer ─────────────────────────────────────────────────────────────────

View File

@ -334,48 +334,43 @@ export namespace Tools {
return 'Error: No story selected'; return 'Error: No story selected';
} }
// Append mode: when old_text is not provided, append new_text to the story const target = args.target ?? 'story';
const isScratchpad = target === 'scratchpad';
const currentText = isScratchpad ? (appState.currentStory.scratchpad ?? '') : appState.currentStory.text;
const tab = isScratchpad ? 'scratchpad' : 'story';
const dispatchEdit = (text: string) => appState.dispatch(
isScratchpad
? { type: 'EDIT_SCRATCHPAD', id: appState.currentStory!.id, text }
: { type: 'EDIT_STORY', id: appState.currentStory!.id, text }
);
// Append mode: when old_text is not provided, append new_text
if (args.old_text == null) { if (args.old_text == null) {
appState.dispatch({ dispatchEdit(currentText + '\n' + args.new_text);
type: 'EDIT_STORY', appState.dispatch({ type: 'SET_CURRENT_TAB', id: appState.currentStory.id, tab });
id: appState.currentStory.id, return `Text appended to ${target} successfully`;
text: appState.currentStory.text + '\n' + args.new_text,
});
appState.dispatch({
type: 'SET_CURRENT_TAB',
id: appState.currentStory.id,
tab: 'story'
});
return 'Text appended to story successfully';
} }
// Replace mode: find and replace old_text with new_text in story // Replace mode
const source = appState.currentStory.text; const occurrences = currentText.split(args.old_text).length - 1;
const occurrences = source.split(args.old_text).length - 1;
if (occurrences === 0) { if (occurrences === 0) {
return 'Error: old_text not found in story'; return `Error: old_text not found in ${target}`;
} }
if (occurrences > 1 && !args.replace_all) { if (occurrences > 1 && !args.replace_all) {
return 'Error: old_text appears multiple times in story'; return `Error: old_text appears multiple times in ${target}`;
} }
appState.dispatch({ dispatchEdit(currentText.replaceAll(args.old_text, args.new_text));
type: 'EDIT_STORY', appState.dispatch({ type: 'SET_CURRENT_TAB', id: appState.currentStory.id, tab });
id: appState.currentStory.id, return `${target.charAt(0).toUpperCase() + target.slice(1)} edited successfully`;
text: appState.currentStory.text.replaceAll(args.old_text, args.new_text),
});
appState.dispatch({
type: 'SET_CURRENT_TAB',
id: appState.currentStory.id,
tab: 'story'
});
return 'Story edited successfully';
}, },
description: "Replace text in the story. When old_text is omitted, appends new_text to the story's end. Case-sensitive.", description: "Replace or append text in the story or scratchpad. When old_text is omitted, appends new_text to the end. Case-sensitive.",
parameters: Type.Object({ parameters: Type.Object({
new_text: Type.String({ description: 'The new text to replace old_text with, or to append if old_text is omitted' }), new_text: Type.String({ description: 'The new text to replace old_text with, or to append if old_text is omitted' }),
old_text: Type.Optional(Type.String({ description: 'The text to find and replace. If omitted, new_text will be appended' })), old_text: Type.Optional(Type.String({ description: 'The text to find and replace. If omitted, new_text will be appended' })),
replace_all: Type.Optional(Type.Boolean({ description: 'If true, replace all occurrences of old_text' })), replace_all: Type.Optional(Type.Boolean({ description: 'If true, replace all occurrences of old_text' })),
target: Type.Optional(Type.Enum(['story', 'scratchpad'] as const, { description: 'Which text to edit: "story" (default) or "scratchpad"' })),
}), }),
}), }),
'grep': tool({ 'grep': tool({