554 lines
27 KiB
HTML
554 lines
27 KiB
HTML
<!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,'&').replace(/</g,'<').replace(/>/g,'>').replace(/'/g,''').replace(/"/g,'"'):''}
|
||
|
||
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>
|