import { create } from 'zustand'; import { persist } from 'zustand/middleware'; import { v4 as uuidv4 } from 'uuid'; export interface Character { id: string; name: string; description: string; role: 'protagonist' | 'antagonist' | 'supporting' | 'minor'; color: string; } export interface Relationship { id: string; sourceId: string; targetId: string; type: 'friend' | 'enemy' | 'family' | 'romantic' | 'colleague' | 'rival'; description: string; strength: number; } export interface Chapter { id: string; title: string; content: string; order: number; } export interface Volume { id: string; title: string; order: number; chapters: Chapter[]; } export interface Message { id: string; role: 'user' | 'assistant'; content: string; timestamp: number; } export interface Novel { id: string; title: string; description: string; volumes: Volume[]; characters: Character[]; relationships: Relationship[]; } interface NovelState { novel: Novel; selectedChapterId: string | null; selectedCharacterId: string | null; chatMessages: Message[]; isChatLoading: boolean; chatModel: string; ollamaUrl: string; availableModels: string[]; ollamaConnected: boolean; setNovel: (novel: Novel) => void; addVolume: (title: string) => void; addChapter: (volumeId: string, title: string) => void; updateChapter: (chapterId: string, content: string, title?: string) => void; selectChapter: (chapterId: string | null) => void; addCharacter: (name: string, description: string, role: Character['role']) => void; updateCharacter: (id: string, updates: Partial) => void; deleteCharacter: (id: string) => void; selectCharacter: (id: string | null) => void; addRelationship: (sourceId: string, targetId: string, type: Relationship['type'], description: string, strength: number) => void; updateRelationship: (id: string, updates: Partial) => void; deleteRelationship: (id: string) => void; addChatMessage: (message: Omit) => void; clearChat: () => void; setChatLoading: (loading: boolean) => void; setChatModel: (model: string) => void; setOllamaUrl: (url: string) => void; setAvailableModels: (models: string[]) => void; setOllamaConnected: (connected: boolean) => void; getContextForOllama: () => string; } const defaultNovel: Novel = { id: uuidv4(), title: 'My Webnovel', description: '', volumes: [], characters: [], relationships: [], }; export const useNovelStore = create()( persist( (set, get) => ({ novel: defaultNovel, selectedChapterId: null, selectedCharacterId: null, chatMessages: [], isChatLoading: false, chatModel: 'llama3.2', ollamaUrl: 'http://localhost:11434', availableModels: [], ollamaConnected: false, setNovel: (novel) => set({ novel }), addVolume: (title) => { const { novel } = get(); const newVolume: Volume = { id: uuidv4(), title, order: novel.volumes.length, chapters: [], }; set({ novel: { ...novel, volumes: [...novel.volumes, newVolume], }, }); }, addChapter: (volumeId, title) => { const { novel } = get(); const newChapter: Chapter = { id: uuidv4(), title, content: '', order: novel.volumes.find(v => v.id === volumeId)?.chapters.length || 0, }; set({ novel: { ...novel, volumes: novel.volumes.map(v => v.id === volumeId ? { ...v, chapters: [...v.chapters, newChapter] } : v ), }, selectedChapterId: newChapter.id, }); }, updateChapter: (chapterId, content, title) => { const { novel } = get(); set({ novel: { ...novel, volumes: novel.volumes.map(v => ({ ...v, chapters: v.chapters.map(c => c.id === chapterId ? { ...c, content, title: title || c.title } : c ), })), }, }); }, selectChapter: (chapterId) => set({ selectedChapterId: chapterId }), addCharacter: (name, description, role) => { const { novel } = get(); const colors = ['#6366f1', '#ec4899', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6', '#06b6d4']; const newCharacter: Character = { id: uuidv4(), name, description, role, color: colors[novel.characters.length % colors.length], }; set({ novel: { ...novel, characters: [...novel.characters, newCharacter], }, }); }, updateCharacter: (id, updates) => { const { novel } = get(); set({ novel: { ...novel, characters: novel.characters.map(c => c.id === id ? { ...c, ...updates } : c ), }, }); }, deleteCharacter: (id) => { const { novel } = get(); set({ novel: { ...novel, characters: novel.characters.filter(c => c.id !== id), relationships: novel.relationships.filter(r => r.sourceId !== id && r.targetId !== id), }, }); }, selectCharacter: (id) => set({ selectedCharacterId: id }), addRelationship: (sourceId, targetId, type, description, strength) => { const { novel } = get(); const newRelationship: Relationship = { id: uuidv4(), sourceId, targetId, type, description, strength, }; set({ novel: { ...novel, relationships: [...novel.relationships, newRelationship], }, }); }, updateRelationship: (id, updates) => { const { novel } = get(); set({ novel: { ...novel, relationships: novel.relationships.map(r => r.id === id ? { ...r, ...updates } : r ), }, }); }, deleteRelationship: (id) => { const { novel } = get(); set({ novel: { ...novel, relationships: novel.relationships.filter(r => r.id !== id), }, }); }, addChatMessage: (message) => { const newMessage: Message = { ...message, id: uuidv4(), timestamp: Date.now(), }; set(state => ({ chatMessages: [...state.chatMessages, newMessage], })); }, clearChat: () => set({ chatMessages: [] }), setChatLoading: (loading) => set({ isChatLoading: loading }), setChatModel: (model) => set({ chatModel: model }), setOllamaUrl: (url) => set({ ollamaUrl: url }), setAvailableModels: (models) => set({ availableModels: models }), setOllamaConnected: (connected) => set({ ollamaConnected: connected }), getContextForOllama: () => { const { novel } = get(); let context = `=== NOVEL OVERVIEW === Title: ${novel.title} Description: ${novel.description} === CHARACTERS === ${novel.characters.map(c => `- ${c.name} (${c.role}): ${c.description}`).join('\n')} === RELATIONSHIPS === ${novel.relationships.map(r => { const source = novel.characters.find(c => c.id === r.sourceId); const target = novel.characters.find(c => c.id === r.targetId); return `- ${source?.name} --[${r.type}]--> ${target?.name}: ${r.description}`; }).join('\n')} === CHAPTERS CONTENT === `; novel.volumes.forEach(volume => { context += `\n--- VOLUME: ${volume.title} ---\n`; volume.chapters.forEach(chapter => { context += `\n## ${chapter.title}\n${chapter.content}\n`; }); }); return context; }, }), { name: 'novel-writer-storage', } ) );