Guanghu Domestic Migration d1e47f4565
Some checks are pending
自动更新代码和重启 / update-and-restart (push) Waiting to run
CI检查 + 自动部署 / check (push) Waiting to run
CI检查 + 自动部署 / deploy (push) Blocked by required conditions
重启聊天服务 / restart (push) Waiting to run
chore: import sanitized domestic snapshot for REPO-002
Source snapshot: ca48d3ddf926d79aa138306164169baf764bb829
2026-07-17 15:54:41 +08:00

554 lines
27 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=5.0,viewport-fit=cover">
<meta name="description" content="光湖 · 智能小说创作系统">
<meta name="theme-color" content="#050810">
<title>光湖 · 创作台</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><rect rx='20' width='100' height='100' fill='%23050810'/><text x='50' y='68' text-anchor='middle' font-size='48' font-weight='900' font-family='serif' fill='%23a78bfa'>✍</text></svg>">
<style>
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
:root{
--bg:#050810;--panel:rgba(12,18,40,0.94);
--card:rgba(14,20,48,0.6);--card-border:rgba(167,139,250,0.12);
--card-hover:rgba(167,139,250,0.22);
--purple:#a78bfa;--violet:#c4b5fd;--cyan:#22d3ee;--blue:#60a5fa;
--ok:#34d399;--warn:#fbbf24;--err:#f87171;
--text:#eaf0ff;--dim:#94a7d0;--muted:rgba(120,150,200,0.4);
--border:rgba(167,139,250,0.1);
--font:-apple-system,BlinkMacSystemFont,'PingFang SC','Helvetica Neue','Microsoft YaHei',sans-serif;
--mono:'SF Mono','JetBrains Mono','Fira Code',monospace
}
html{font-size:16px;scroll-behavior:smooth}
body{font-family:var(--font);background:var(--bg);color:var(--text);min-height:100vh;line-height:1.7;-webkit-font-smoothing:antialiased}
a{color:var(--cyan);text-decoration:none}
::-webkit-scrollbar{width:5px}
::-webkit-scrollbar-thumb{background:rgba(167,139,250,0.15);border-radius:3px}
.page{display:flex;min-height:100vh;flex-direction:column}
.header{padding:12px 20px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--border);background:rgba(5,8,16,0.9);backdrop-filter:blur(12px)}
.header-left{display:flex;align-items:center;gap:10px}
.header-glyph{width:32px;height:32px;border-radius:8px;background:linear-gradient(135deg,rgba(167,139,250,0.12),rgba(34,211,238,0.08));border:1px solid rgba(167,139,250,0.2);display:flex;align-items:center;justify-content:center;font-size:15px;color:var(--purple)}
.header-title{font-size:14px;font-weight:700;letter-spacing:1px}
.btn{padding:7px 14px;border-radius:8px;border:1px solid var(--border);background:transparent;cursor:pointer;font-size:12px;font-family:var(--font);color:var(--dim);transition:all .25s}
.btn:hover{color:var(--cyan);border-color:rgba(34,211,238,0.25)}
.btn-primary{background:rgba(167,139,250,0.1);color:var(--purple);border-color:rgba(167,139,250,0.25)}
.btn-primary:hover{background:rgba(167,139,250,0.2)}
.btn-ok{background:rgba(52,211,153,0.1);color:var(--ok);border-color:rgba(52,211,153,0.25)}
.main{flex:1;display:flex;overflow:hidden}
/* Sidebar */
.sidebar{width:280px;border-right:1px solid var(--border);background:rgba(5,8,16,0.6);display:flex;flex-direction:column;overflow-y:auto}
.sidebar-section{padding:12px 16px;border-bottom:1px solid var(--border)}
.sidebar-title{font-size:10px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:1px;margin-bottom:8px;font-family:var(--mono)}
.sidebar-item{padding:8px 12px;border-radius:8px;cursor:pointer;font-size:13px;transition:all .2s;display:flex;align-items:center;gap:8px;color:var(--dim)}
.sidebar-item:hover{background:rgba(167,139,250,0.08);color:var(--text)}
.sidebar-item.active{background:rgba(167,139,250,0.12);color:var(--purple);border-left:2px solid var(--purple)}
.sidebar-item .meta{font-size:10px;color:var(--muted);font-family:var(--mono);margin-left:auto}
/* Editor Area */
.editor-area{flex:1;display:flex;flex-direction:column;overflow:hidden}
.editor-toolbar{padding:10px 20px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:10px;background:rgba(5,8,16,0.4)}
.editor-toolbar .title-input{flex:1;background:transparent;border:none;font-size:16px;font-weight:700;color:var(--text);font-family:var(--font);outline:none}
.editor-toolbar .title-input::placeholder{color:var(--muted)}
.editor-content{flex:1;overflow-y:auto;padding:24px}
.editor-textarea{width:100%;min-height:400px;background:transparent;border:1px solid var(--border);border-radius:12px;padding:20px;color:var(--text);font-size:15px;font-family:var(--font);line-height:1.9;resize:vertical;outline:none}
.editor-textarea:focus{border-color:rgba(167,139,250,0.3)}
/* AI Panel */
.ai-panel{width:320px;border-left:1px solid var(--border);background:rgba(5,8,16,0.6);display:flex;flex-direction:column;overflow:hidden}
.ai-panel-header{padding:12px 16px;border-bottom:1px solid var(--border);font-size:12px;font-weight:700;color:var(--purple);display:flex;align-items:center;gap:6px}
.ai-panel-body{flex:1;overflow-y:auto;padding:12px 16px}
.ai-result{padding:12px;border-radius:10px;background:rgba(167,139,250,0.05);border:1px solid var(--card-border);margin-bottom:10px;font-size:13px;color:var(--dim);line-height:1.8;white-space:pre-wrap}
.ai-actions{padding:12px 16px;border-top:1px solid var(--border);display:flex;gap:8px;flex-wrap:wrap}
/* Cards */
.char-card{background:var(--card);border:1px solid var(--card-border);border-radius:12px;padding:14px 16px;margin-bottom:10px;transition:all .3s;cursor:pointer}
.char-card:hover{border-color:var(--card-hover);transform:translateY(-1px)}
.char-name{font-size:14px;font-weight:700;color:var(--text)}
.char-role{font-size:11px;color:var(--purple);font-family:var(--mono)}
.char-desc{font-size:12px;color:var(--dim);margin-top:4px;line-height:1.6}
.outline-node{padding:10px 14px;border-left:2px solid var(--border);margin-left:8px;margin-bottom:6px;cursor:pointer;transition:all .2s}
.outline-node:hover{border-left-color:var(--purple);background:rgba(167,139,250,0.05)}
.outline-node.level-0{margin-left:0;border-left-width:3px;font-weight:600}
/* Empty */
.empty{text-align:center;padding:60px 20px;color:var(--muted);font-size:13px}
/* Toast */
.toast{position:fixed;bottom:20px;left:50%;transform:translateX(-50%);padding:12px 24px;border-radius:10px;background:rgba(14,20,48,0.95);border:1px solid rgba(167,139,250,0.2);color:var(--text);font-size:13px;z-index:9999;backdrop-filter:blur(12px)}
/* Modal */
.modal-overlay{position:fixed;inset:0;z-index:50;background:rgba(0,0,0,0.6);display:flex;align-items:center;justify-content:center;backdrop-filter:blur(4px)}
.modal{background:var(--panel);border:1px solid var(--card-border);border-radius:16px;padding:28px;max-width:500px;width:90%;max-height:80vh;overflow-y:auto}
.modal h3{font-size:16px;margin-bottom:16px;color:var(--text)}
.modal label{font-size:12px;color:var(--dim);display:block;margin-bottom:4px;margin-top:12px}
.modal input,.modal textarea,.modal select{width:100%;padding:10px 14px;border-radius:8px;border:1px solid var(--border);background:rgba(5,8,16,0.6);color:var(--text);font-size:13px;font-family:var(--font);outline:none}
.modal textarea{min-height:80px;resize:vertical;line-height:1.7}
.modal input:focus,.modal textarea:focus{border-color:rgba(167,139,250,0.3)}
.modal-actions{display:flex;gap:8px;margin-top:20px;justify-content:flex-end}
/* Responsive */
@media(max-width:900px){.ai-panel{display:none}.sidebar{width:220px}}
@media(max-width:600px){.sidebar{display:none}.main{flex-direction:column}}
</style>
</head>
<body>
<div class="page">
<header class="header">
<div class="header-left">
<div class="header-glyph"></div>
<span class="header-title">光湖 · 创作台</span>
</div>
<div style="display:flex;gap:8px">
<button class="btn" onclick="showNewNovelModal()">+ 新建小说</button>
<button class="btn" onclick="window.location.href='/zhiku.html'">📚 智库</button>
<button class="btn" onclick="window.location.href='/'">🏠 首页</button>
</div>
</header>
<div class="main">
<!-- Left Sidebar: Novel & Chapter List -->
<div class="sidebar" id="sidebar">
<div class="sidebar-section">
<div class="sidebar-title">📚 我的小说</div>
<div id="novelList"></div>
<div id="novelEmpty" class="empty" style="padding:20px;font-size:12px">还没有小说,点击上方"新建小说"开始创作</div>
</div>
<div class="sidebar-section" id="chapterSection" style="display:none">
<div class="sidebar-title">📖 章节列表</div>
<div id="chapterList"></div>
<button class="btn btn-primary" onclick="showAddChapterModal()" style="width:100%;margin-top:8px;font-size:11px">+ 新建章节</button>
</div>
<div class="sidebar-section" id="charSection" style="display:none">
<div class="sidebar-title">👥 人物卡</div>
<div id="charList"></div>
<button class="btn" onclick="showAddCharModal()" style="width:100%;margin-top:8px;font-size:11px">+ 添加人物</button>
</div>
<div class="sidebar-section" id="outlineSection" style="display:none">
<div class="sidebar-title">🗺️ 大纲</div>
<div id="outlineList"></div>
<button class="btn" onclick="showAddOutlineModal()" style="width:100%;margin-top:8px;font-size:11px">+ 添加节点</button>
</div>
</div>
<!-- Editor Area -->
<div class="editor-area">
<div class="editor-toolbar" id="editorToolbar" style="display:none">
<input class="title-input" id="chapterTitle" placeholder="章节标题..." onchange="autoSaveChapter()">
<span id="wordCount" style="font-size:11px;color:var(--muted);font-family:var(--mono)"></span>
<button class="btn btn-ok" onclick="saveCurrentChapter()">💾 保存</button>
</div>
<div class="editor-content">
<div id="welcomeScreen" class="empty" style="padding:80px 20px">
<div style="font-size:48px;margin-bottom:16px">✍️</div>
<div style="font-size:18px;font-weight:600;color:var(--dim);margin-bottom:8px">欢迎来到创作台</div>
<div style="max-width:400px;margin:0 auto;line-height:1.8">
选择左侧的小说和章节开始编辑,<br>
或创建一个新的小说项目。<br><br>
<span style="font-size:12px;color:var(--muted)">
支持: 大纲管理 · 人物卡 · 章节编辑 · AI辅助写作<br>
AI功能将在接入 DeepSeek/Kimi 后全面启用
</span>
</div>
</div>
<textarea class="editor-textarea" id="editorTextarea" style="display:none" placeholder="开始写作..." oninput="updateWordCount()"></textarea>
</div>
</div>
<!-- AI Panel -->
<div class="ai-panel" id="aiPanel">
<div class="ai-panel-header">🤖 AI 助手 <span style="font-size:10px;color:var(--muted);margin-left:auto">DeepSeek · 预留</span></div>
<div class="ai-panel-body" id="aiPanelBody">
<div class="ai-result" style="color:var(--dim)">
👋 AI辅助功能已就绪MVP占位模式<br><br>
当前支持:<br>
• 📝 续写 — 基于最后段落续写<br>
• ✏️ 改写 — 选中文字风格改写<br>
• 💡 建议 — 情节/人物发展建议<br><br>
<span style="font-size:11px;color:var(--muted)">真实AI将在接入DeepSeek后启用</span>
</div>
</div>
<div class="ai-actions">
<button class="btn btn-primary" onclick="aiContinue()" style="font-size:11px">📝 续写</button>
<button class="btn" onclick="aiRewrite()" style="font-size:11px">✏️ 改写</button>
<button class="btn" onclick="aiSuggest()" style="font-size:11px">💡 建议</button>
</div>
</div>
</div>
</div>
<!-- New Novel Modal -->
<div class="modal-overlay" id="newNovelModal" style="display:none" onclick="if(event.target===this)this.style.display='none'">
<div class="modal">
<h3>📚 新建小说</h3>
<label>书名 *</label>
<input id="newNovelTitle" placeholder="小说名称">
<label>类型</label>
<select id="newNovelGenre">
<option>玄幻</option><option>仙侠</option><option>都市</option><option>科幻</option>
<option>言情</option><option>武侠</option><option>悬疑</option><option>历史</option>
<option>游戏</option><option>现代奇幻</option><option>其他</option>
</select>
<label>简介</label>
<textarea id="newNovelSynopsis" placeholder="简短描述你的故事..."></textarea>
<label>目标字数</label>
<input id="newNovelTarget" type="number" placeholder="0 = 不设目标" value="0">
<div class="modal-actions">
<button class="btn" onclick="document.getElementById('newNovelModal').style.display='none'">取消</button>
<button class="btn btn-primary" onclick="createNovel()">创建</button>
</div>
</div>
</div>
<!-- Add Chapter Modal -->
<div class="modal-overlay" id="addChapterModal" style="display:none" onclick="if(event.target===this)this.style.display='none'">
<div class="modal">
<h3>📖 新建章节</h3>
<label>章节标题</label>
<input id="newChapterTitle" placeholder="第X章 ...">
<div class="modal-actions">
<button class="btn" onclick="document.getElementById('addChapterModal').style.display='none'">取消</button>
<button class="btn btn-primary" onclick="addChapter()">创建</button>
</div>
</div>
</div>
<!-- Add Character Modal -->
<div class="modal-overlay" id="addCharModal" style="display:none" onclick="if(event.target===this)this.style.display='none'">
<div class="modal">
<h3>👤 添加人物</h3>
<label>姓名 *</label>
<input id="newCharName" placeholder="角色名">
<label>定位</label>
<select id="newCharRole">
<option>主角</option><option>女主</option><option>男配</option><option>女配</option>
<option>配角</option><option>反派</option><option>导师</option><option>路人</option>
</select>
<label>简介</label>
<textarea id="newCharDesc" placeholder="角色背景描述..."></textarea>
<label>外貌</label>
<input id="newCharAppearance" placeholder="外貌特征...">
<label>性格</label>
<input id="newCharPersonality" placeholder="性格特点...">
<div class="modal-actions">
<button class="btn" onclick="document.getElementById('addCharModal').style.display='none'">取消</button>
<button class="btn btn-primary" onclick="addCharacter()">添加</button>
</div>
</div>
</div>
<!-- Add Outline Modal -->
<div class="modal-overlay" id="addOutlineModal" style="display:none" onclick="if(event.target===this)this.style.display='none'">
<div class="modal">
<h3>🗺️ 添加大纲节点</h3>
<label>标题</label>
<input id="newOutlineTitle" placeholder="卷/篇/节点名称">
<label>内容</label>
<textarea id="newOutlineContent" placeholder="情节描述..."></textarea>
<div class="modal-actions">
<button class="btn" onclick="document.getElementById('addOutlineModal').style.display='none'">取消</button>
<button class="btn btn-primary" onclick="addOutlineNode()">添加</button>
</div>
</div>
</div>
<script>
var API=(location.hostname==='guanghuyaoming.com'||location.hostname==='novel.guanghuyaoming.com')?'':'http://127.0.0.1:4000';
var AUTHOR='冰朔';
var state={
currentNovel:null,
currentChapter:null,
novels:[],
autoSaveTimer:null
};
function escHtml(s){return s?s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/'/g,'&#x27;').replace(/"/g,'&quot;'):''}
function showToast(msg){
var t=document.createElement('div');t.className='toast';t.textContent=msg;
document.body.appendChild(t);
setTimeout(function(){t.style.opacity='0';t.style.transition='opacity .3s';setTimeout(function(){t.remove()},300)},2500);
}
function api(method,path,body){
var opts={method:method,headers:{'Content-Type':'application/json'}};
if(body)opts.body=JSON.stringify(body);
return fetch(API+path,opts).then(function(r){return r.json()}).catch(function(){return{error:true,message:'网络错误'}});
}
/* ═══ Novel CRUD ═══ */
function loadNovels(){
api('GET','/api/novel').then(function(d){
if(d.error||!d.data){return;}
state.novels=d.data;
var list=document.getElementById('novelList');
var empty=document.getElementById('novelEmpty');
if(d.data.length===0){list.innerHTML='';empty.style.display='';return;}
empty.style.display='none';
list.innerHTML='';
d.data.forEach(function(n){
var item=document.createElement('div');
item.className='sidebar-item'+(state.currentNovel&&state.currentNovel.novel_id===n.novel_id?' active':'');
item.innerHTML='<span>📖</span><span>'+escHtml(n.title)+'</span><span class="meta">'+n.word_count+'字</span>';
item.onclick=function(){selectNovel(n.novel_id)};
list.appendChild(item);
});
});
}
function showNewNovelModal(){document.getElementById('newNovelModal').style.display='flex';}
function createNovel(){
var title=document.getElementById('newNovelTitle').value.trim();
if(!title){showToast('请输入书名');return;}
api('POST','/api/novel',{
title:title,
author:AUTHOR,
genre:document.getElementById('newNovelGenre').value,
synopsis:document.getElementById('newNovelSynopsis').value,
target_words:parseInt(document.getElementById('newNovelTarget').value)||0
}).then(function(d){
if(d.error){showToast(d.message);return;}
document.getElementById('newNovelModal').style.display='none';
document.getElementById('newNovelTitle').value='';
document.getElementById('newNovelSynopsis').value='';
showToast('小说创建成功');
loadNovels();
selectNovel(d.data.novel_id);
});
}
function selectNovel(novelId){
api('GET','/api/novel/'+novelId).then(function(d){
if(d.error||!d.data){return;}
state.currentNovel=d.data;
state.currentChapter=null;
document.getElementById('chapterSection').style.display='';
document.getElementById('charSection').style.display='';
document.getElementById('outlineSection').style.display='';
loadNovels();
loadChapters();
loadCharacters();
loadOutline();
showWelcome();
});
}
/* ═══ Chapters ═══ */
function loadChapters(){
if(!state.currentNovel)return;
api('GET','/api/novel/'+state.currentNovel.novel_id+'/chapters').then(function(d){
var list=document.getElementById('chapterList');
list.innerHTML='';
if(!d.data||d.data.length===0){list.innerHTML='<div style="font-size:11px;color:var(--muted);padding:8px">暂无章节</div>';return;}
d.data.forEach(function(ch,i){
var item=document.createElement('div');
item.className='sidebar-item'+(state.currentChapter&&state.currentChapter.chapter_id===ch.chapter_id?' active':'');
item.innerHTML='<span style="font-size:11px;color:var(--muted)">'+(i+1)+'</span><span style="font-size:12px">'+escHtml(ch.title)+'</span><span class="meta">'+ch.word_count+'</span>';
item.onclick=function(){openChapter(ch.chapter_id)};
list.appendChild(item);
});
});
}
function showAddChapterModal(){document.getElementById('addChapterModal').style.display='flex';}
function addChapter(){
if(!state.currentNovel)return;
var title=document.getElementById('newChapterTitle').value.trim()||undefined;
api('POST','/api/novel/'+state.currentNovel.novel_id+'/chapters',{title:title}).then(function(d){
if(d.error){showToast(d.message);return;}
document.getElementById('addChapterModal').style.display='none';
document.getElementById('newChapterTitle').value='';
showToast('章节已创建');
loadChapters();
openChapter(d.data.chapter_id);
});
}
function openChapter(chapterId){
if(!state.currentNovel)return;
api('GET','/api/novel/'+state.currentNovel.novel_id+'/chapters/'+chapterId).then(function(d){
if(d.error||!d.data){return;}
state.currentChapter=d.data;
document.getElementById('welcomeScreen').style.display='none';
document.getElementById('editorTextarea').style.display='';
document.getElementById('editorToolbar').style.display='flex';
document.getElementById('chapterTitle').value=d.data.title;
document.getElementById('editorTextarea').value=d.data.content||'';
updateWordCount();
loadChapters();
});
}
function saveCurrentChapter(){
if(!state.currentNovel||!state.currentChapter)return;
var title=document.getElementById('chapterTitle').value.trim();
var content=document.getElementById('editorTextarea').value;
api('PUT','/api/novel/'+state.currentNovel.novel_id+'/chapters/'+state.currentChapter.chapter_id,{
title:title,content:content
}).then(function(d){
if(d.error){showToast('保存失败: '+d.message);return;}
state.currentChapter=d.data;
showToast('已保存');
loadChapters();
loadNovels();
});
}
function autoSaveChapter(){
if(state.autoSaveTimer)clearTimeout(state.autoSaveTimer);
state.autoSaveTimer=setTimeout(saveCurrentChapter,3000);
}
function updateWordCount(){
var text=document.getElementById('editorTextarea').value;
var count=text.replace(/\s/g,'').length;
document.getElementById('wordCount').textContent=count+' 字';
autoSaveChapter();
}
function showWelcome(){
document.getElementById('welcomeScreen').style.display='';
document.getElementById('editorTextarea').style.display='none';
document.getElementById('editorToolbar').style.display='none';
}
/* ═══ Characters ═══ */
function loadCharacters(){
if(!state.currentNovel)return;
api('GET','/api/novel/'+state.currentNovel.novel_id+'/characters').then(function(d){
var list=document.getElementById('charList');
list.innerHTML='';
if(!d.data||d.data.length===0){list.innerHTML='<div style="font-size:11px;color:var(--muted);padding:8px">暂无人物</div>';return;}
d.data.forEach(function(ch){
var card=document.createElement('div');card.className='char-card';
card.innerHTML='<div class="char-name">'+escHtml(ch.name)+'</div><div class="char-role">'+escHtml(ch.role)+'</div>'
+(ch.description?'<div class="char-desc">'+escHtml(ch.description.slice(0,80))+'</div>':'');
list.appendChild(card);
});
});
}
function showAddCharModal(){document.getElementById('addCharModal').style.display='flex';}
function addCharacter(){
if(!state.currentNovel)return;
var name=document.getElementById('newCharName').value.trim();
if(!name){showToast('请输入角色名');return;}
api('POST','/api/novel/'+state.currentNovel.novel_id+'/characters',{
name:name,
role:document.getElementById('newCharRole').value,
description:document.getElementById('newCharDesc').value,
appearance:document.getElementById('newCharAppearance').value,
personality:document.getElementById('newCharPersonality').value
}).then(function(d){
if(d.error){showToast(d.message);return;}
document.getElementById('addCharModal').style.display='none';
document.getElementById('newCharName').value='';
document.getElementById('newCharDesc').value='';
document.getElementById('newCharAppearance').value='';
document.getElementById('newCharPersonality').value='';
showToast('人物已添加');
loadCharacters();
});
}
/* ═══ Outline ═══ */
function loadOutline(){
if(!state.currentNovel)return;
api('GET','/api/novel/'+state.currentNovel.novel_id+'/outline').then(function(d){
var list=document.getElementById('outlineList');
list.innerHTML='';
if(!d.data||d.data.length===0){list.innerHTML='<div style="font-size:11px;color:var(--muted);padding:8px">暂无大纲</div>';return;}
d.data.forEach(function(node){
var el=document.createElement('div');el.className='outline-node level-0';
el.innerHTML='<div style="font-size:13px;color:var(--text)">'+escHtml(node.title)+'</div><div style="font-size:11px;color:var(--dim);margin-top:2px">'+escHtml((node.content||'').slice(0,60))+'</div>';
list.appendChild(el);
if(node.children){
node.children.forEach(function(child){
var cel=document.createElement('div');cel.className='outline-node';
cel.innerHTML='<div style="font-size:12px;color:var(--dim)">'+escHtml(child.title)+'</div>';
list.appendChild(cel);
});
}
});
});
}
function showAddOutlineModal(){document.getElementById('addOutlineModal').style.display='flex';}
function addOutlineNode(){
if(!state.currentNovel)return;
var title=document.getElementById('newOutlineTitle').value.trim();
if(!title){showToast('请输入标题');return;}
api('POST','/api/novel/'+state.currentNovel.novel_id+'/outline',{
title:title,content:document.getElementById('newOutlineContent').value
}).then(function(d){
if(d.error){showToast(d.message);return;}
document.getElementById('addOutlineModal').style.display='none';
document.getElementById('newOutlineTitle').value='';
document.getElementById('newOutlineContent').value='';
showToast('大纲节点已添加');
loadOutline();
});
}
/* ═══ AI Functions ═══ */
function aiContinue(){
if(!state.currentNovel||!state.currentChapter){showToast('请先打开一个章节');return;}
var panel=document.getElementById('aiPanelBody');
panel.innerHTML='<div class="ai-result" style="color:var(--warn)">⏳ AI续写生成中...</div>';
api('POST','/api/novel/'+state.currentNovel.novel_id+'/ai/continue/'+state.currentChapter.chapter_id,{}).then(function(d){
if(d.error){panel.innerHTML='<div class="ai-result" style="color:var(--err)">❌ '+escHtml(d.message)+'</div>';return;}
panel.innerHTML='<div class="ai-result"><div style="font-size:11px;color:var(--purple);margin-bottom:6px">📝 AI续写</div>'+escHtml(d.data.generated)+'</div>'
+'<div class="ai-result" style="font-size:11px;color:var(--muted)">'+escHtml(d.data.note)+'</div>';
});
}
function aiRewrite(){
if(!state.currentNovel||!state.currentChapter){showToast('请先打开一个章节');return;}
var text=document.getElementById('editorTextarea').value;
var selected=window.getSelection().toString()||text.slice(-200);
var panel=document.getElementById('aiPanelBody');
panel.innerHTML='<div class="ai-result" style="color:var(--warn)">⏳ AI改写生成中...</div>';
api('POST','/api/novel/'+state.currentNovel.novel_id+'/ai/rewrite/'+state.currentChapter.chapter_id,{text:selected,style:'文学'}).then(function(d){
if(d.error){panel.innerHTML='<div class="ai-result" style="color:var(--err)">❌ '+escHtml(d.message)+'</div>';return;}
panel.innerHTML='<div class="ai-result"><div style="font-size:11px;color:var(--purple);margin-bottom:6px">✏️ AI改写</div>'+escHtml(d.data.generated)+'</div>'
+'<div class="ai-result" style="font-size:11px;color:var(--muted)">'+escHtml(d.data.note)+'</div>';
});
}
function aiSuggest(){
if(!state.currentNovel){showToast('请先选择一部小说');return;}
var panel=document.getElementById('aiPanelBody');
panel.innerHTML='<div class="ai-result" style="color:var(--warn)">⏳ AI建议生成中...</div>';
api('POST','/api/novel/'+state.currentNovel.novel_id+'/ai/suggest',{type:'plot'}).then(function(d){
if(d.error){panel.innerHTML='<div class="ai-result" style="color:var(--err)">❌ '+escHtml(d.message)+'</div>';return;}
var html='<div class="ai-result"><div style="font-size:11px;color:var(--purple);margin-bottom:6px">💡 情节建议 · '+escHtml(d.data.novel_title)+'</div>';
html+='<div style="font-size:11px;color:var(--muted);margin-bottom:8px">'+escHtml(d.data.context)+'</div>';
d.data.suggestions.forEach(function(s){html+='<div style="margin-bottom:6px">'+escHtml(s)+'</div>';});
html+='</div><div class="ai-result" style="font-size:11px;color:var(--muted)">'+escHtml(d.data.note)+'</div>';
panel.innerHTML=html;
});
}
/* ═══ Keyboard Shortcuts ═══ */
document.addEventListener('keydown',function(e){
if((e.ctrlKey||e.metaKey)&&e.key==='s'){e.preventDefault();saveCurrentChapter();}
});
/* ═══ Init ═══ */
loadNovels();
</script>
</body>
</html>