initial: novel-app snapshot

This commit is contained in:
omo
2026-08-17 17:11:30 +08:00
commit 1ae06209ac
34 changed files with 2821 additions and 0 deletions

48
templates/base.html Normal file
View File

@@ -0,0 +1,48 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{% block title %}Novel Writer{% endblock %}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM"
crossorigin="anonymous">
</head>
<body class="d-flex flex-column min-vh-100">
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="/">Novel Writer</a>
<div class="collapse navbar-collapse" id="mainNav">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item"><a class="nav-link" href="/">首页</a></li>
<li class="nav-item"><a class="nav-link" href="/deconstruct">拆书</a></li>
</ul>
</div>
</div>
</nav>
<main class="container py-4 flex-grow-1">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category | default('info') }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</main>
<footer class="bg-dark text-light text-center py-3 mt-auto">
<div class="container">Novel Writer - 拆书 / 写书 / 人机协作</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"
integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz"
crossorigin="anonymous"></script>
{% block scripts %}{% endblock %}
</body>
</html>

View File

@@ -0,0 +1,57 @@
{% extends "base.html" %}
{% block title %}写章节 - Novel Writer{% endblock %}
{% block content %}
<div id="cwAlerts"></div>
<div class="d-flex justify-content-between align-items-center mb-3">
<h1 class="h4 mb-0" id="cwTitle">写章节</h1>
<a class="btn btn-outline-secondary btn-sm" href="/novel/{{ novel_id }}">← 返回小说</a>
</div>
<div class="row g-3">
<div class="col-md-4">
<div class="card mb-3">
<div class="card-header fw-bold">章节大纲</div>
<div class="card-body">
<textarea class="form-control" id="cwOutline" rows="6" placeholder="本章大纲…"></textarea>
<button type="button" class="btn btn-outline-primary btn-sm mt-2" id="cwSaveOutline">保存大纲</button>
</div>
</div>
<div class="card">
<div class="card-header fw-bold">上下文面板</div>
<div class="card-body" id="cwContext" style="max-height:50vh;overflow-y:auto;">
<span class="text-muted">加载中…</span>
</div>
</div>
</div>
<div class="col-md-8">
<div class="card">
<div class="card-header d-flex flex-wrap gap-2">
<button type="button" class="btn btn-success btn-sm" id="cwGenerate">生成</button>
<button type="button" class="btn btn-warning btn-sm" id="cwPause" disabled>暂停</button>
<button type="button" class="btn btn-info btn-sm" id="cwResume" disabled>继续</button>
<button type="button" class="btn btn-danger btn-sm" id="cwRegen" disabled>重新生成</button>
<button type="button" class="btn btn-outline-secondary btn-sm" id="cwEdit" disabled>编辑</button>
<button type="button" class="btn btn-outline-primary btn-sm" id="cwSave" disabled>保存修改</button>
<button type="button" class="btn btn-primary btn-sm" id="cwApprove" disabled>通过本章</button>
</div>
<div class="card-body">
<div id="cwContextSummary"></div>
<div id="cwOutput" class="border rounded p-3"
style="min-height:400px;max-height:65vh;overflow-y:auto;white-space:pre-wrap;word-break:break-word;">
<span class="text-muted">点击"生成"开始写作…</span></div>
<div class="text-muted small mt-2" id="cwWordCount"></div>
</div>
</div>
</div>
</div>
<script>
window.NOVEL_ID = {{ novel_id }};
window.CHAPTER_ID = {{ chapter_id }};
</script>
{% endblock %}
{% block scripts %}
<script type="module" src="{{ url_for('static', filename='js/pages/chapter-write.js') }}"></script>
{% endblock %}

View File

@@ -0,0 +1,55 @@
{% extends "base.html" %}
{% block title %}拆书 - Novel Writer{% endblock %}
{% block content %}
<h1 class="mb-4">拆书: 从零构建小说框架</h1>
<div class="card mb-4">
<div class="card-header fw-bold">基本信息</div>
<div class="card-body">
<div class="row g-3">
<div class="col-md-4">
<label class="form-label" for="dcTitle">书名 *</label>
<input type="text" class="form-control" id="dcTitle" placeholder="例: 剑啸江湖">
</div>
<div class="col-md-2">
<label class="form-label" for="dcGenre">类型</label>
<select class="form-select" id="dcGenre">
<option>玄幻</option><option>武侠</option><option>言情</option>
<option>科幻</option><option>现实</option><option>悬疑</option><option>其他</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label" for="dcStyle">文风</label>
<input type="text" class="form-control" id="dcStyle" placeholder="例: 金庸+古龙混合">
</div>
<div class="col-md-3">
<label class="form-label" for="dcWords">目标字数</label>
<select class="form-select" id="dcWords">
<option value="300000">30 万字</option>
<option value="1000000">100 万字</option>
<option value="3000000">300 万字</option>
</select>
</div>
<div class="col-md-6">
<label class="form-label" for="dcRef">参考小说 (可选)</label>
<input type="text" class="form-control" id="dcRef" placeholder="例: 雪中悍刀行">
</div>
<div class="col-md-6 d-flex align-items-end">
<button type="button" class="btn btn-success" id="dcStartBtn">开始拆书</button>
</div>
</div>
</div>
</div>
<div id="dcAlerts"></div>
<div class="accordion" id="dcSections"></div>
<div class="mt-4 text-end">
<button type="button" class="btn btn-primary btn-lg" id="dcCreateBtn" disabled>创建小说</button>
</div>
{% endblock %}
{% block scripts %}
<script type="module" src="{{ url_for('static', filename='js/pages/deconstruct.js') }}"></script>
{% endblock %}

16
templates/index.html Normal file
View File

@@ -0,0 +1,16 @@
{% extends "base.html" %}
{% block title %}首页 - Novel Writer{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="mb-0">我的小说</h1>
<a class="btn btn-primary" href="/deconstruct">+ 拆书创建新小说</a>
</div>
<div class="row g-3" id="novelCards">
<div class="text-muted">加载中…</div>
</div>
{% endblock %}
{% block scripts %}
<script type="module" src="{{ url_for('static', filename='js/pages/index.js') }}"></script>
{% endblock %}

View File

@@ -0,0 +1,46 @@
{% extends "base.html" %}
{% block title %}小说详情 - Novel Writer{% endblock %}
{% block content %}
<div id="ndAlerts"></div>
<div class="card mb-4" id="ndHeader">
<div class="card-body text-muted">加载中…</div>
</div>
<ul class="nav nav-tabs" id="ndTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#ndPaneChapters" type="button" role="tab">章节</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#ndPaneCharacters" type="button" role="tab">角色</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#ndPaneForeshadowing" type="button" role="tab">伏笔</button>
</li>
</ul>
<div class="tab-content border border-top-0 rounded-bottom p-3">
<div class="tab-pane fade show active" id="ndPaneChapters" role="tabpanel">
<form class="row g-2 mb-3" id="ndAddChapterForm">
<div class="col-auto"><input type="number" class="form-control" id="ndChVolume" value="1" min="1" title="卷" style="width:80px"></div>
<div class="col-auto"><input type="number" class="form-control" id="ndChNumber" value="1" min="1" title="章节号" style="width:90px"></div>
<div class="col-auto"><input type="text" class="form-control" id="ndChTitle" placeholder="章节标题"></div>
<div class="col-auto"><input type="text" class="form-control" id="ndChOutline" placeholder="一句话大纲 (可选)" style="width:280px"></div>
<div class="col-auto"><button type="submit" class="btn btn-outline-primary">添加章节</button></div>
</form>
<div id="ndChapterList" class="text-muted">加载中…</div>
</div>
<div class="tab-pane fade" id="ndPaneCharacters" role="tabpanel">
<div id="ndCharacterList" class="text-muted">加载中…</div>
</div>
<div class="tab-pane fade" id="ndPaneForeshadowing" role="tabpanel">
<div id="ndForeshadowingList" class="text-muted">加载中…</div>
</div>
</div>
<script>window.NOVEL_ID = {{ novel_id }};</script>
{% endblock %}
{% block scripts %}
<script type="module" src="{{ url_for('static', filename='js/pages/novel-detail.js') }}"></script>
{% endblock %}