diff --git a/src/games/storywriter/components/editor.tsx b/src/games/storywriter/components/editor.tsx
index 9f122ba..2a89980 100644
--- a/src/games/storywriter/components/editor.tsx
+++ b/src/games/storywriter/components/editor.tsx
@@ -20,27 +20,26 @@ const TABS: { id: Tab; label: string }[] = [
export const Editor = () => {
const { currentStory, dispatch } = useAppState();
- if (!currentStory) {
- return
diff --git a/src/games/storywriter/components/menu-sidebar.tsx b/src/games/storywriter/components/menu-sidebar.tsx
index 1e43545..e1ddd8d 100644
--- a/src/games/storywriter/components/menu-sidebar.tsx
+++ b/src/games/storywriter/components/menu-sidebar.tsx
@@ -7,7 +7,7 @@ import { useBool } from "@common/hooks/useBool";
import type { Story } from "../contexts/state";
import styles from '../assets/menu-sidebar.module.css';
import { useState } from "preact/hooks";
-import { Pencil, X, Plus, Plug, Settings } from "lucide-preact";
+import { Pencil, X, Plus, Plug, Settings, Copy } from "lucide-preact";
// ─── Story Item ───────────────────────────────────────────────────────────────
@@ -17,9 +17,10 @@ interface StoryItemProps {
onSelect: () => void;
onRename: (newTitle: string) => void;
onDelete: () => void;
+ onDuplicate: () => void;
}
-const StoryItem = ({ story, active, onSelect, onRename, onDelete }: StoryItemProps) => {
+const StoryItem = ({ story, active, onSelect, onRename, onDelete, onDuplicate }: StoryItemProps) => {
const [isEditing, setIsEditing] = useState(false);
const [editTitle, setEditTitle] = useState(story.title);
@@ -70,6 +71,9 @@ const StoryItem = ({ story, active, onSelect, onRename, onDelete }: StoryItemPro
+
@@ -105,6 +109,10 @@ export const MenuSidebar = () => {
}
};
+ const handleDuplicate = (id: string) => {
+ dispatch({ type: 'DUPLICATE_STORY', id });
+ };
+
return (
diff --git a/src/games/storywriter/contexts/state.tsx b/src/games/storywriter/contexts/state.tsx
index 7252e04..e1b9c2f 100644
--- a/src/games/storywriter/contexts/state.tsx
+++ b/src/games/storywriter/contexts/state.tsx
@@ -81,6 +81,7 @@ type Action =
| { type: 'SET_CURRENT_TAB'; id: string; tab: Tab }
| { type: 'DELETE_STORY'; id: string }
| { type: 'SELECT_STORY'; id: string }
+ | { type: 'DUPLICATE_STORY'; id: string }
| { type: 'ADD_CHAT_MESSAGE'; storyId: string; message: ChatMessage }
| { type: 'CLEAR_CHAT'; storyId: string }
| { type: 'SET_CONNECTION'; connection: LLM.Connection | null }
@@ -180,6 +181,26 @@ function reducer(state: IState, action: Action): IState {
currentStoryId: deletingCurrent ? null : state.currentStoryId,
};
}
+ case 'DUPLICATE_STORY': {
+ const original = state.stories.find(s => s.id === action.id);
+ if (!original) return state;
+ const newStory: Story = {
+ id: crypto.randomUUID(),
+ title: `${original.title} (Copy)`,
+ text: '',
+ lore: original.lore,
+ characters: original.characters,
+ locations: original.locations,
+ currentTab: 'story',
+ chatMessages: [],
+ chapters: [],
+ };
+ return {
+ ...state,
+ stories: [...state.stories, newStory],
+ currentStoryId: newStory.id,
+ };
+ }
case 'SELECT_STORY': {
return {
...state,