Add scratchpad
This commit is contained in:
parent
b770e5e394
commit
c36e5654ed
|
|
@ -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<HTMLDivElement>(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 = () => {
|
|||
<div class={styles.title}>
|
||||
{currentStory.title}
|
||||
</div>
|
||||
<div class={styles.content}>
|
||||
<div class={styles.content} ref={contentRef}>
|
||||
{currentStory.currentTab === "story" && (
|
||||
<ContentEditable
|
||||
class={styles.editable}
|
||||
|
|
@ -79,6 +89,14 @@ export const Editor = () => {
|
|||
{currentStory.currentTab === "chapters" && (
|
||||
<ChaptersEditor />
|
||||
)}
|
||||
{currentStory.currentTab === "scratchpad" && (
|
||||
<ContentEditable
|
||||
class={styles.editable}
|
||||
value={currentStory.scratchpad ?? ''}
|
||||
onInput={handleScratchpadInput}
|
||||
placeholder="Notes, ideas, outlines — anything you don't want in the story..."
|
||||
/>
|
||||
)}
|
||||
{currentStory.currentTab === "prompt" && (
|
||||
<div class={styles.promptPreview} dangerouslySetInnerHTML={{ __html: promptPreview }} />
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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<LoreEntry> }
|
||||
| { 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,
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue