import React, { useState, useEffect, useMemo } from 'react'; import { Crown, LayoutDashboard, Library, PenTool, Settings, Brain, BookOpen, Map, GitBranch, GraduationCap, Armchair, Lock, Plus, Trash2, ChevronRight, X, Loader2, Key, AlertCircle, Save, Sparkles, ArrowLeft, Menu, Feather, Check, Eye, EyeOff, Pause, Play, Users, MapPin, Shield, Package, Clock, ShieldCheck, Zap, Skull, Heart, Layers, Flag, KeyRound, Pencil, Milestone, Wand2, MessageCircle, Send, StickyNote, RotateCcw, Star } from 'lucide-react'; /* ========================================================= CONSTANTES ========================================================= */ const SIDEBAR_ITEMS = [ { id: 'painel', label: 'Painel Imperial', icon: LayoutDashboard, active: true }, { id: 'gaveta', label: 'Gaveta Literária', icon: Library, active: true }, { id: 'forjar', label: 'Forjar História', icon: PenTool, active: true }, { id: 'forjar-imperio', label: 'Forjar Império', icon: Crown, active: true }, { id: 'biblioteca', label: 'Biblioteca Imperial', icon: BookOpen, active: true }, { id: 'atlas', label: 'Atlas de Universos', icon: Map, active: true }, { id: 'planejador', label: 'Planejador de Saga', icon: GitBranch, active: true }, { id: 'mentor', label: 'Mentor Literário', icon: GraduationCap, active: true }, { id: 'sala-autor', label: 'Sala do Autor', icon: Armchair, active: true }, { id: 'config', label: 'Configurações', icon: Settings, active: true }, { id: 'mente-suprema', label: 'Mente Suprema', icon: Brain, active: true }, ]; const GENEROS = [ 'Fantasia', 'Ficção Científica', 'Romance', 'Suspense/Thriller', 'Terror', 'Drama', 'Aventura', 'Mistério', 'Histórico', 'Distopia', 'Young Adult', 'Literário' ]; const PONTOS_DE_VISTA = [ 'Primeira Pessoa', 'Terceira Pessoa Limitada', 'Terceira Pessoa Onisciente', 'Múltiplos Narradores (POV Alternado)' ]; const TONS = [ 'Épico e Grandioso', 'Sombrio e Intenso', 'Leve e Esperançoso', 'Melancólico e Reflexivo', 'Tenso e Urgente', 'Satírico e Irônico', 'Romântico', 'Filosófico' ]; const CAPITULOS_SUGERIDOS = [5, 10, 20, 50, 100, 300, 500, 1000]; const CAPA_GRADIENTES = { 'Fantasia': 'linear-gradient(135deg,#3a2a0f,#7a5a1a,#1a1208)', 'Ficção Científica': 'linear-gradient(135deg,#0a1a2a,#1a3a5a,#0a0a14)', 'Romance': 'linear-gradient(135deg,#3a1020,#6a2030,#1a0a10)', 'Suspense/Thriller': 'linear-gradient(135deg,#1a1a1a,#3a3a2a,#0a0a0a)', 'Terror': 'linear-gradient(135deg,#1a0505,#3a0a0a,#050505)', 'Drama': 'linear-gradient(135deg,#2a2010,#4a3a1a,#100a05)', 'Aventura': 'linear-gradient(135deg,#1a2a10,#3a5a1a,#0a140a)', 'Mistério': 'linear-gradient(135deg,#15101f,#2f2040,#0a0510)', 'Histórico': 'linear-gradient(135deg,#2a1f10,#4a3520,#140d05)', 'Distopia': 'linear-gradient(135deg,#1a1a1a,#3a2a1a,#0a0a0a)', 'Young Adult': 'linear-gradient(135deg,#2a1530,#4a2550,#150a1a)', 'Literário': 'linear-gradient(135deg,#1f1810,#3a2f1a,#0d0a05)', }; const CAPA_DEFAULT = 'linear-gradient(135deg,#1a1610,#2a2410,#0a0805)'; const STATUS_INFO = { 'em-andamento': { label: 'Em Andamento', color: '#d4af37' }, 'concluida': { label: 'Concluída', color: '#7fd49a' }, 'pausada': { label: 'Pausada', color: '#9a958a' }, }; const MEMORIA_VAZIA = { personagens: [], locais: [], faccoes: [], objetos: [], eventos: [] }; const MEMORIA_CATEGORIAS = [ { chave: 'personagens', label: 'Personagens', icon: Users }, { chave: 'locais', label: 'Locais', icon: MapPin }, { chave: 'faccoes', label: 'Facções', icon: Shield }, { chave: 'objetos', label: 'Objetos', icon: Package }, { chave: 'eventos', label: 'Eventos', icon: Clock }, ]; const CAMPOS_IMPERIO = [ { chave: 'descricaoMundo', label: 'Mundo', icon: Map, tipo: 'texto', placeholder: 'Geografia, cosmologia e regras fundamentais deste universo...' }, { chave: 'sistemaDePoder', label: 'Sistema de Poder', icon: Zap, tipo: 'texto', placeholder: 'Magia, tecnologia, hierarquias de poder...' }, { chave: 'reinos', label: 'Reinos', icon: Crown, tipo: 'lista', placeholder: 'Um reino por linha' }, { chave: 'faccoes', label: 'Facções', icon: Shield, tipo: 'lista', placeholder: 'Uma facção por linha' }, { chave: 'racas', label: 'Raças', icon: Users, tipo: 'lista', placeholder: 'Uma raça por linha' }, { chave: 'religioes', label: 'Religiões', icon: BookOpen, tipo: 'lista', placeholder: 'Uma religião por linha' }, { chave: 'misterios', label: 'Mistérios', icon: Eye, tipo: 'texto', placeholder: 'Mistérios não resolvidos do universo...' }, { chave: 'segredos', label: 'Segredos', icon: KeyRound, tipo: 'texto', placeholder: 'Segredos guardados por personagens ou facções...' }, { chave: 'conflitos', label: 'Conflitos', icon: Flag, tipo: 'lista', placeholder: 'Um conflito por linha' }, { chave: 'antagonistas', label: 'Antagonistas', icon: Skull, tipo: 'lista', placeholder: 'Um antagonista por linha' }, { chave: 'romances', label: 'Romances', icon: Heart, tipo: 'texto', placeholder: 'Relações românticas planejadas...' }, { chave: 'subtramas', label: 'Subtramas', icon: Layers, tipo: 'texto', placeholder: 'Subtramas planejadas...' }, { chave: 'plotTwists', label: 'Plot Twists', icon: Sparkles, tipo: 'texto', placeholder: 'Reviravoltas planejadas (a IA usará com o timing correto)...' }, { chave: 'finalPlanejado', label: 'Final Planejado', icon: Flag, tipo: 'texto', placeholder: 'Como esta saga deve terminar...' }, ]; function camposImperioVazios() { const obj = {}; CAMPOS_IMPERIO.forEach((c) => { obj[c.chave] = ''; }); return obj; } const ANALISE_CATEGORIAS = [ { chave: 'ritmo', label: 'Ritmo' }, { chave: 'emocao', label: 'Emoção' }, { chave: 'dialogos', label: 'Diálogos' }, { chave: 'tensao', label: 'Tensão' }, { chave: 'construcaoMundo', label: 'Construção de Mundo' }, { chave: 'consistencia', label: 'Consistência' }, { chave: 'qualidadeGeral', label: 'Qualidade Geral' }, ]; const ACOES_MENTE_SUPREMA = [ { id: 'planejar', label: 'Planejar Próximos Capítulos', icon: Milestone }, { id: 'personagem', label: 'Gerar Novo Personagem', icon: Users }, { id: 'plottwist', label: 'Criar Plot Twist', icon: Sparkles }, { id: 'dialogos', label: 'Melhorar Diálogos do Último Capítulo', icon: Wand2 }, { id: 'consistencia', label: 'Analisar Consistência Geral', icon: ShieldCheck }, ]; function paraTexto(v) { if (v == null) return ''; if (typeof v === 'string') return v; if (Array.isArray(v)) return v.map((x) => (typeof x === 'string' ? x : JSON.stringify(x))).join(', '); if (typeof v === 'object') return JSON.stringify(v); return String(v); } const NUCLEO_ESCRITORA_SUPREMA = [ 'Você é uma escritora literária de elite, com décadas de experiência publicando romances aclamados. Sua escrita NUNCA parece gerada por uma IA.', 'REGRAS ABSOLUTAS DE ESTILO:', '- Mostre, não explique. Emoções surgem através de ações, expressões, diálogos e decisões - nunca por exposição direta.', '- Cada personagem tem voz própria: maneira de falar, pensar e agir reconhecível mesmo sem identificação de nome.', '- Evite frases repetitivas, descrições genéricas, diálogos artificiais, emoções superficiais e finais vazios.', '- Mantenha ritmo natural, tensão crescente e profundidade psicológica.', '- Cada capítulo termina com propósito narrativo - nunca enrolação ou preenchimento artificial.', '- Lembre-se constantemente de medos, desejos, traumas, objetivos e relacionamentos estabelecidos anteriormente na história.', '- A qualidade deve permanecer idêntica do primeiro ao último capítulo, não importa o tamanho da saga.' ].join('\n'); const STORAGE_KEYS = { stories: 'isf_stories_v1', apiKey: 'isf_api_key_v1', universos: 'isf_universos_v1', notas: 'isf_notas_v1' }; /* ========================================================= STORAGE HELPERS ========================================================= */ function carregarHistorias() { try { const raw = localStorage.getItem(STORAGE_KEYS.stories); const lista = raw ? JSON.parse(raw) : []; return lista.map((s) => ({ ...s, memoria: s.memoria || { personagens: [], locais: [], faccoes: [], objetos: [], eventos: [] }, planejamento: s.planejamento || { arcos: [] }, ultimaAnalise: s.ultimaAnalise || null, })); } catch (e) { return []; } } function salvarHistorias(stories) { try { localStorage.setItem(STORAGE_KEYS.stories, JSON.stringify(stories)); } catch (e) {} } function carregarChaveApi() { try { return localStorage.getItem(STORAGE_KEYS.apiKey) || ''; } catch (e) { return ''; } } function salvarChaveApi(key) { try { localStorage.setItem(STORAGE_KEYS.apiKey, key); } catch (e) {} } function carregarUniversos() { try { const raw = localStorage.getItem(STORAGE_KEYS.universos); return raw ? JSON.parse(raw) : []; } catch (e) { return []; } } function salvarUniversos(universos) { try { localStorage.setItem(STORAGE_KEYS.universos, JSON.stringify(universos)); } catch (e) {} } function carregarNotas() { try { const raw = localStorage.getItem(STORAGE_KEYS.notas); return raw ? JSON.parse(raw) : []; } catch (e) { return []; } } function salvarNotas(notas) { try { localStorage.setItem(STORAGE_KEYS.notas, JSON.stringify(notas)); } catch (e) {} } /* ========================================================= UTILS ========================================================= */ function novoId() { return 'story_' + Date.now() + '_' + Math.random().toString(36).slice(2, 8); } function contarPalavras(texto) { return (texto || '').trim().split(/\s+/).filter(Boolean).length; } function formatarData(iso) { if (!iso) return '-'; const d = new Date(iso); return d.toLocaleDateString('pt-BR', { day: '2-digit', month: '2-digit', year: 'numeric' }); } /* ========================================================= CHAMADA À API ANTHROPIC A chave NUNCA fica fixa no código. Ela é lida do localStorage, preenchida pelo usuário/cliente em Configurações. ========================================================= */ async function chamarIA({ apiKey, systemPrompt, userPrompt, maxTokens }) { const headers = { 'content-type': 'application/json', 'anthropic-version': '2023-06-01', }; if (apiKey) { headers['x-api-key'] = apiKey; headers['anthropic-dangerous-direct-browser-access'] = 'true'; } const maxTokensFinal = apiKey ? Math.min(8192, Math.max(512, maxTokens)) : 1000; const res = await fetch('https://api.anthropic.com/v1/messages', { method: 'POST', headers, body: JSON.stringify({ model: 'claude-sonnet-4-6', max_tokens: maxTokensFinal, system: systemPrompt, messages: [{ role: 'user', content: userPrompt }], }), }); if (!res.ok) { const txt = await res.text(); throw new Error('Erro ' + res.status + ': ' + txt.slice(0, 220)); } const data = await res.json(); const bloco = (data.content || []).find((b) => b.type === 'text'); return bloco ? bloco.text : ''; } async function gerarCapitulo({ apiKey, systemPrompt, userPrompt, maxPalavras }) { const maxTokens = Math.ceil(maxPalavras * 2.2); return chamarIA({ apiKey, systemPrompt, userPrompt, maxTokens }); } function montarSecaoMundoExpandido(mundoAvancado) { if (!mundoAvancado) return ''; const partes = []; CAMPOS_IMPERIO.forEach((campo) => { const valor = (mundoAvancado[campo.chave] || '').trim(); if (!valor) return; if (campo.tipo === 'lista') { const itens = valor.split('\n').map((l) => l.trim()).filter(Boolean); if (itens.length) partes.push(campo.label.toUpperCase() + ': ' + itens.join('; ')); } else { partes.push(campo.label.toUpperCase() + ': ' + valor); } }); if (!partes.length) return ''; return 'MUNDO EXPANDIDO (use estes elementos para enriquecer a história, com timing e sutileza - não despeje tudo de uma vez):\n' + partes.join('\n'); } function montarPromptCapitulo(story, numero) { const anterior = story.capitulos[story.capitulos.length - 1]; const contexto = anterior ? 'CONTEXTO DO CAPÍTULO ANTERIOR (mantenha continuidade total - personagens, tom, eventos):\n' + anterior.conteudo.slice(-2500) : 'Este é o capítulo de abertura da história. Estabeleça o mundo, o tom e os protagonistas de forma envolvente.'; const linhas = [ 'HISTÓRIA: ' + story.titulo, 'GÊNERO: ' + story.genero, 'IDEIA PRINCIPAL / SINOPSE: ' + story.sinopse, 'PROTAGONISTAS: ' + story.protagonistas, 'PONTO DE VISTA: ' + story.pov, 'TOM DA NARRATIVA: ' + story.tom, 'OBJETIVO FINAL DA HISTÓRIA: ' + story.objetivoFinal, ]; const secaoMundo = montarSecaoMundoExpandido(story.mundoAvancado); if (secaoMundo) linhas.push(secaoMundo); if (story.vozDoAutor && story.vozDoAutor.trim()) { linhas.push('INSTRUÇÕES ABSOLUTAS DO AUTOR - VOZ DO AUTOR (seguir rigorosamente, têm prioridade sobre tudo): ' + story.vozDoAutor.trim()); } linhas.push(contexto); linhas.push('Escreva o CAPÍTULO ' + numero + ' de ' + story.capitulosTotais + ' desta história, com aproximadamente ' + story.palavrasPorCapitulo + ' palavras.'); if (numero === story.capitulosTotais) { linhas.push('Este é o capítulo FINAL da saga. Conduza a história ao objetivo final de forma satisfatória e memorável.'); } linhas.push('Responda EXATAMENTE neste formato, sem nenhum texto, comentário ou markdown adicional antes ou depois:'); linhas.push('TÍTULO: [título do capítulo, sem a palavra "Capítulo"]'); linhas.push('---'); linhas.push('[conteúdo integral do capítulo]'); return linhas.join('\n'); } function interpretarRespostaCapitulo(texto, numero) { const partes = texto.split('---'); if (partes.length >= 2) { const titulo = partes[0].replace(/TÍTULO:/i, '').trim(); const conteudo = partes.slice(1).join('---').trim(); return { titulo: titulo || ('Capítulo ' + numero), conteudo }; } return { titulo: 'Capítulo ' + numero, conteudo: texto.trim() }; } /* ========================================================= MEMÓRIA VIVA - extração automática de entidades por capítulo ========================================================= */ function montarPromptExtracaoMemoria(story, capitulo) { const memoriaResumo = JSON.stringify(story.memoria || MEMORIA_VAZIA); const linhas = [ 'MEMÓRIA ATUAL DA HISTÓRIA (JSON):', memoriaResumo, '', 'NOVO CAPÍTULO (Nº ' + capitulo.numero + ' - "' + capitulo.titulo + '"):', capitulo.conteudo, '', 'Analise o novo capítulo e retorne APENAS um objeto JSON válido, sem markdown, sem texto antes ou depois, com as chaves: personagens, locais, faccoes, objetos, eventos.', 'Cada chave deve conter um array com APENAS as entidades NOVAS ou ALTERADAS neste capítulo (não repita entidades sem mudanças).', 'Formato de cada item:', '- personagens: {"nome","descricao","papel","status","tracos","relacionamentos"}', '- locais: {"nome","descricao","tipo"}', '- faccoes: {"nome","descricao"}', '- objetos: {"nome","descricao","portador"}', '- eventos: {"titulo","descricao"}', 'Todos os campos de texto devem ser strings simples (nunca objetos ou arrays aninhados).', 'Se nada novo ou alterado em uma categoria, retorne array vazio para ela.', ]; return linhas.join('\n'); } function extrairJson(texto) { try { const limpo = texto.replace(/```json/gi, '').replace(/```/g, '').trim(); return JSON.parse(limpo); } catch (e) { return null; } } function mesclarMemoria(memoriaAtual, delta, numeroCapitulo) { const base = memoriaAtual || MEMORIA_VAZIA; const resultado = { personagens: [...(base.personagens || [])], locais: [...(base.locais || [])], faccoes: [...(base.faccoes || [])], objetos: [...(base.objetos || [])], eventos: [...(base.eventos || [])], }; MEMORIA_CATEGORIAS.forEach((cat) => { const novos = (delta && Array.isArray(delta[cat.chave])) ? delta[cat.chave] : []; novos.forEach((item) => { if (!item) return; const chaveNome = paraTexto(item.nome || item.titulo).trim().toLowerCase(); if (!chaveNome) return; const idx = resultado[cat.chave].findIndex( (e) => paraTexto(e.nome || e.titulo).trim().toLowerCase() === chaveNome ); const entrada = { ...item, atualizadoNoCapitulo: numeroCapitulo }; if (idx >= 0) resultado[cat.chave][idx] = { ...resultado[cat.chave][idx], ...entrada }; else resultado[cat.chave].push(entrada); }); }); return resultado; } async function extrairMemoria({ apiKey, story, capitulo }) { const systemPrompt = 'Você é um sistema de extração de memória narrativa. Responda SOMENTE com JSON válido, sem markdown, sem comentários, sem texto adicional antes ou depois.'; const userPrompt = montarPromptExtracaoMemoria(story, capitulo); const resposta = await chamarIA({ apiKey, systemPrompt, userPrompt, maxTokens: 2048 }); const json = extrairJson(resposta); if (!json) return story.memoria || MEMORIA_VAZIA; return mesclarMemoria(story.memoria, json, capitulo.numero); } /* ========================================================= CONSISTÊNCIA NARRATIVA ========================================================= */ function montarPromptConsistencia(story) { const memoriaResumo = JSON.stringify(story.memoria || MEMORIA_VAZIA); const ultimos = story.capitulos .slice(-2) .map((c) => 'CAPÍTULO ' + c.numero + ' - ' + c.titulo + '\n' + c.conteudo) .join('\n\n'); const linhas = [ 'MEMÓRIA REGISTRADA DA HISTÓRIA (JSON):', memoriaResumo, '', 'CAPÍTULOS RECENTES:', ultimos, '', 'Revise os capítulos recentes em busca de: contradições, furos de roteiro, mudanças não explicadas de aparência/idade/personalidade, erros cronológicos ou de continuidade em relação à memória registrada.', 'Responda em português, em formato de lista curta com marcadores "-". Se nada for encontrado, responda apenas: "Nenhuma inconsistência detectada."', ]; return linhas.join('\n'); } async function verificarConsistencia({ apiKey, story }) { const systemPrompt = NUCLEO_ESCRITORA_SUPREMA + '\n\nAlém de escrever, você atua agora como revisora de continuidade meticulosa, comparando os capítulos recentes com a memória registrada da história.'; const userPrompt = montarPromptConsistencia(story); const resposta = await chamarIA({ apiKey, systemPrompt, userPrompt, maxTokens: 1024 }); return resposta.trim(); } /* ========================================================= MENTOR LITERÁRIO ========================================================= */ async function analisarComMentor({ apiKey, story, capitulo }) { const systemPrompt = 'Você é uma mentora literária renomada, especialista em estrutura narrativa, ritmo, prosa e construção de mundo. Responda SOMENTE com JSON válido, sem markdown, sem texto adicional.'; const userPrompt = [ 'HISTÓRIA: ' + story.titulo, 'GÊNERO: ' + story.genero, 'CAPÍTULO ANALISADO (Nº ' + capitulo.numero + ' - ' + capitulo.titulo + '):', capitulo.conteudo, '', 'Avalie este capítulo e retorne APENAS um JSON com esta estrutura exata:', '{"ritmo":0,"emocao":0,"dialogos":0,"tensao":0,"construcaoMundo":0,"consistencia":0,"qualidadeGeral":0,"pontosFortes":"","sugestoes":""}', 'Todas as notas de 0 a 10 (números). Os textos "pontosFortes" e "sugestoes" em português, concisos (2-4 frases cada).', ].join('\n'); const resposta = await chamarIA({ apiKey, systemPrompt, userPrompt, maxTokens: 1024 }); return extrairJson(resposta); } /* ========================================================= PLANEJADOR DE SAGA ========================================================= */ async function gerarPlanoSaga({ apiKey, story }) { const systemPrompt = 'Você é uma planejadora narrativa especialista em estrutura de sagas longas. Responda SOMENTE com um array JSON válido, sem markdown, sem texto adicional.'; const userPrompt = [ 'HISTÓRIA: ' + story.titulo, 'GÊNERO: ' + story.genero, 'SINOPSE: ' + story.sinopse, 'OBJETIVO FINAL: ' + story.objetivoFinal, 'TOTAL DE CAPÍTULOS DA SAGA: ' + story.capitulosTotais, '', 'Divida esta saga em arcos narrativos cobrindo do capítulo 1 ao ' + story.capitulosTotais + ', sem sobreposição e em ordem.', 'Use entre 2 e 8 arcos, proporcional ao tamanho da saga.', 'Retorne APENAS um array JSON, cada item com a estrutura exata:', '{"titulo":"","descricao":"","capituloInicio":1,"capituloFim":1}', ].join('\n'); const resposta = await chamarIA({ apiKey, systemPrompt, userPrompt, maxTokens: 1536 }); const json = extrairJson(resposta); return Array.isArray(json) ? json : []; } /* ========================================================= MENTE SUPREMA ========================================================= */ function montarPromptMenteSuprema(acaoId, story, extra) { const memoriaResumo = JSON.stringify(story.memoria || MEMORIA_VAZIA); const ultimoCap = story.capitulos[story.capitulos.length - 1]; const base = [ 'HISTÓRIA: ' + story.titulo, 'GÊNERO: ' + story.genero, 'SINOPSE: ' + story.sinopse, 'MEMÓRIA REGISTRADA (JSON): ' + memoriaResumo, ]; if (acaoId === 'planejar') { base.push('PROGRESSO ATUAL: capítulo ' + story.capitulos.length + ' de ' + story.capitulosTotais); base.push('OBJETIVO FINAL: ' + story.objetivoFinal); base.push('Sugira, em português, um plano resumido para os próximos 3 a 5 capítulos. Responda em lista com marcadores "-", uma linha por capítulo sugerido.'); } else if (acaoId === 'personagem') { base.push('Crie um novo personagem secundário coerente com este universo e memória registrada, que poderia ser introduzido em capítulos futuros.'); base.push('Descreva, em um parágrafo conciso: nome, papel na história, aparência, personalidade e motivação.'); } else if (acaoId === 'plottwist') { if (ultimoCap) base.push('ÚLTIMO CAPÍTULO REGISTRADO (Nº ' + ultimoCap.numero + '): ' + ultimoCap.conteudo.slice(-1500)); base.push('Sugira UMA reviravolta (plot twist) coerente com a história e a memória registrada, que poderia ser usada em capítulos futuros. Explique a ideia e o impacto narrativo em até 4 frases.'); } else if (acaoId === 'dialogos') { if (ultimoCap) { base.push('ÚLTIMO CAPÍTULO (Nº ' + ultimoCap.numero + ' - ' + ultimoCap.titulo + '):'); base.push(ultimoCap.conteudo); } else { base.push('Esta história ainda não possui capítulos gerados.'); } base.push('Aponte 2-3 trechos de diálogo deste capítulo que poderiam ficar mais afiados, naturais ou reveladores de personalidade, e sugira versões melhoradas. Responda em lista com marcadores "-".'); } else if (acaoId === 'livre') { base.push('PERGUNTA / PEDIDO DO AUTOR: ' + extra); } return base.join('\n'); } async function consultarMenteSuprema({ apiKey, story, acaoId, extra }) { const systemPrompt = NUCLEO_ESCRITORA_SUPREMA + '\n\nVocê também atua como diretora criativa estratégica desta saga, oferecendo conselhos objetivos e acionáveis ao autor.'; const userPrompt = montarPromptMenteSuprema(acaoId, story, extra); const resposta = await chamarIA({ apiKey, systemPrompt, userPrompt, maxTokens: 1280 }); return resposta.trim(); } function montarPromptCorrecao(story, capitulo, instrucao) { return [ 'HISTÓRIA: ' + story.titulo, 'MEMÓRIA REGISTRADA (JSON): ' + JSON.stringify(story.memoria || MEMORIA_VAZIA), '', 'CAPÍTULO ORIGINAL (Nº ' + capitulo.numero + ' - ' + capitulo.titulo + '):', capitulo.conteudo, '', 'INSTRUÇÃO DE CORREÇÃO: ' + instrucao, '', 'Reescreva o capítulo aplicando a instrução, preservando o máximo possível do que já funciona e mantendo total consistência com a memória registrada.', 'Responda EXATAMENTE neste formato, sem texto adicional:', 'TÍTULO: [título do capítulo]', '---', '[conteúdo integral revisado]', ].join('\n'); } async function corrigirCapitulo({ apiKey, story, capitulo, instrucao }) { const systemPrompt = NUCLEO_ESCRITORA_SUPREMA + '\n\nVocê também atua como editora de texto, revisando e corrigindo capítulos sem perder a essência original.'; const userPrompt = montarPromptCorrecao(story, capitulo, instrucao); const maxTokens = Math.ceil(contarPalavras(capitulo.conteudo) * 2.5); const resposta = await chamarIA({ apiKey, systemPrompt, userPrompt, maxTokens }); return interpretarRespostaCapitulo(resposta, capitulo.numero); } /* ========================================================= COMPONENTES VISUAIS PEQUENOS ========================================================= */ function SeloImperial({ size = 40 }) { return (
); } function Barra({ progresso }) { const p = Math.max(0, Math.min(100, progresso)); return (
); } function Etiqueta({ children, color }) { return ( {children} ); } /* ========================================================= SIDEBAR ========================================================= */ function Sidebar({ view, setView, sidebarOpen, setSidebarOpen, apiConfigurada }) { return ( <> {sidebarOpen && (
setSidebarOpen(false)} /> )} ); } /* ========================================================= TOPBAR ========================================================= */ function Topbar({ setSidebarOpen, view, activeStory, apiConfigurada }) { const titulos = { painel: 'Painel Imperial', gaveta: activeStory ? activeStory.titulo : 'Gaveta Literária', forjar: 'Forjar História', config: 'Configurações', }; const titulo = titulos[view] || (SIDEBAR_ITEMS.find((i) => i.id === view) || {}).label || ''; return (

{titulo}

Salvo automaticamente
{apiConfigurada ? 'IA Pronta' : 'IA Inativa'}
); } /* ========================================================= PAINEL IMPERIAL (DASHBOARD) ========================================================= */ function PainelImperial({ stats, stories, setView, setActiveStoryId }) { const recentes = useMemo( () => [...stories].sort((a, b) => new Date(b.atualizadoEm) - new Date(a.atualizadoEm)).slice(0, 3), [stories] ); const cards = [ { label: 'Histórias Ativas', valor: stats.historiasAtivas }, { label: 'Capítulos Gerados', valor: stats.capitulosGerados }, { label: 'Palavras Escritas', valor: stats.palavrasEscritas.toLocaleString('pt-BR') }, { label: 'Progresso Médio', valor: stats.progressoMedio + '%' }, ]; return (
Bem-vindo ao seu Império Literário
Cada história forjada aqui é registrada, versionada e mantida com consistência absoluta.
{cards.map((c) => (
{c.valor}
{c.label}
))}
HISTÓRIAS RECENTES
{recentes.length === 0 ? (
Sua gaveta está vazia. Forje sua primeira história para começar.
) : (
{recentes.map((s) => ( ))}
)}
MÓDULOS DA PLATAFORMA
{SIDEBAR_ITEMS.filter((i) => i.id !== 'config').map((item) => { const Icon = item.icon; return (
{item.label}
{item.active ? 'Ativo' : 'Em Breve'}
); })}
); } /* ========================================================= GAVETA LITERÁRIA ========================================================= */ function GavetaLiteraria({ stories, setActiveStoryId, setView, excluirHistoria }) { const [confirmarExclusao, setConfirmarExclusao] = useState(null); if (stories.length === 0) { return (
Sua gaveta está vazia
Toda grande saga começa com uma faísca. Forje sua primeira história e ela viverá aqui para sempre.
); } return (
{stories.map((s) => { const progresso = (s.capitulos.length / s.capitulosTotais) * 100; const status = STATUS_INFO[s.status] || STATUS_INFO['em-andamento']; const palavras = s.capitulos.reduce((acc, c) => acc + contarPalavras(c.conteudo), 0); return (
{status.label}
{s.titulo}
{s.genero} {formatarData(s.atualizadoEm)}

{s.sinopse}

{s.capitulos.length} / {s.capitulosTotais} capítulos {palavras.toLocaleString('pt-BR')} palavras
{confirmarExclusao === s.id ? ( <> ) : ( )}
); })}
); } /* ========================================================= LEITOR DE HISTÓRIA ========================================================= */ function LeitorHistoria({ story, setActiveStoryId, atualizarHistoria, apiKey }) { const [gerando, setGerando] = useState(false); const [erro, setErro] = useState(''); const [vozDoAutor, setVozDoAutor] = useState(story.vozDoAutor || ''); const [capituloAberto, setCapituloAberto] = useState(story.capitulos.length ? story.capitulos.length - 1 : null); const [verificando, setVerificando] = useState(false); const [consistencia, setConsistencia] = useState(null); const [mundoAvancado, setMundoAvancado] = useState(story.mundoAvancado || camposImperioVazios()); const [mundoAberto, setMundoAberto] = useState(false); const proximo = story.capitulos.length + 1; const concluida = story.capitulos.length >= story.capitulosTotais; const palavras = story.capitulos.reduce((acc, c) => acc + contarPalavras(c.conteudo), 0); const memoria = story.memoria || MEMORIA_VAZIA; const totalMemoria = MEMORIA_CATEGORIAS.reduce((acc, cat) => acc + (memoria[cat.chave] || []).length, 0); async function handleGerar() { if (!apiKey) { setErro('Configure sua chave API em Configurações antes de gerar capítulos.'); return; } setErro(''); setConsistencia(null); setGerando(true); try { const userPrompt = montarPromptCapitulo({ ...story, vozDoAutor, mundoAvancado }, proximo); const resposta = await gerarCapitulo({ apiKey, systemPrompt: NUCLEO_ESCRITORA_SUPREMA, userPrompt, maxPalavras: story.palavrasPorCapitulo, }); const { titulo, conteudo } = interpretarRespostaCapitulo(resposta, proximo); const novoCapitulo = { numero: proximo, titulo, conteudo, geradoEm: new Date().toISOString() }; const novosCapitulos = [...story.capitulos, novoCapitulo]; // Memória Viva: extração best-effort - não bloqueia o capítulo caso falhe let novaMemoria = story.memoria || MEMORIA_VAZIA; try { novaMemoria = await extrairMemoria({ apiKey, story: { ...story, capitulos: novosCapitulos }, capitulo: novoCapitulo }); } catch (memErr) { /* memória é best-effort */ } atualizarHistoria(story.id, { capitulos: novosCapitulos, memoria: novaMemoria, vozDoAutor, mundoAvancado, status: novosCapitulos.length >= story.capitulosTotais ? 'concluida' : story.status, atualizadoEm: new Date().toISOString(), }); setCapituloAberto(novosCapitulos.length - 1); } catch (e) { setErro(e.message || 'Falha ao gerar capítulo.'); } finally { setGerando(false); } } async function handleVerificarConsistencia() { if (!apiKey) { setErro('Configure sua chave API em Configurações para usar a verificação de consistência.'); return; } setErro(''); setVerificando(true); try { const resultado = await verificarConsistencia({ apiKey, story }); setConsistencia(resultado); } catch (e) { setErro(e.message || 'Falha ao verificar consistência.'); } finally { setVerificando(false); } } function alternarPausa() { atualizarHistoria(story.id, { status: story.status === 'pausada' ? 'em-andamento' : 'pausada', atualizadoEm: new Date().toISOString(), }); } return (
{story.genero} {story.pov} {story.tom} {(STATUS_INFO[story.status] || STATUS_INFO['em-andamento']).label}
{story.titulo}

{story.sinopse}

Capítulos: {story.capitulos.length} / {story.capitulosTotais}
Palavras: {palavras.toLocaleString('pt-BR')}
Criada em: {formatarData(story.criadoEm)}
VOZ DO AUTOR (instruções absolutas para a IA)