@musnows/scriverse 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.en.md +13 -4
  2. package/README.md +13 -4
  3. package/dist/ai.js +3548 -0
  4. package/dist/ai.js.map +1 -0
  5. package/dist/app.js +1079 -0
  6. package/dist/app.js.map +1 -0
  7. package/dist/cli-core.js +147 -20
  8. package/dist/cli-core.js.map +1 -1
  9. package/dist/credential-vault.js +40 -0
  10. package/dist/credential-vault.js.map +1 -0
  11. package/dist/database.js +1220 -0
  12. package/dist/database.js.map +1 -0
  13. package/dist/docx-security.js +184 -0
  14. package/dist/docx-security.js.map +1 -0
  15. package/dist/domain.js +21 -0
  16. package/dist/domain.js.map +1 -0
  17. package/dist/errors.js +16 -0
  18. package/dist/errors.js.map +1 -0
  19. package/dist/http-logging.js +76 -0
  20. package/dist/http-logging.js.map +1 -0
  21. package/dist/image-captcha.js +173 -0
  22. package/dist/image-captcha.js.map +1 -0
  23. package/dist/image-metadata.js +128 -0
  24. package/dist/image-metadata.js.map +1 -0
  25. package/dist/import-security.js +46 -0
  26. package/dist/import-security.js.map +1 -0
  27. package/dist/logger.js +110 -0
  28. package/dist/logger.js.map +1 -0
  29. package/dist/parser.js +223 -0
  30. package/dist/parser.js.map +1 -0
  31. package/dist/public/ai-context-meter.js +10 -0
  32. package/dist/public/ai-conversation.js +3 -0
  33. package/dist/public/ai-mentions.js +55 -0
  34. package/dist/public/ai-message-actions.js +27 -0
  35. package/dist/public/ai-message-meta.js +15 -0
  36. package/dist/public/ai-message-time.js +23 -0
  37. package/dist/public/ai-prompt-keyboard.js +3 -0
  38. package/dist/public/app.js +4435 -0
  39. package/dist/public/character-profile.d.ts +9 -0
  40. package/dist/public/character-profile.js +65 -0
  41. package/dist/public/character-version.d.ts +2 -0
  42. package/dist/public/character-version.js +31 -0
  43. package/dist/public/entity-version.js +34 -0
  44. package/dist/public/icon.svg +10 -0
  45. package/dist/public/index.html +547 -0
  46. package/dist/public/line-number-layout.js +15 -0
  47. package/dist/public/markdown.js +199 -0
  48. package/dist/public/model-config.d.ts +17 -0
  49. package/dist/public/model-config.js +57 -0
  50. package/dist/public/page-route.d.ts +11 -0
  51. package/dist/public/page-route.js +81 -0
  52. package/dist/public/relationship-graph.js +2017 -0
  53. package/dist/public/site.webmanifest +17 -0
  54. package/dist/public/styles.css +1441 -0
  55. package/dist/public/text-formatting.d.ts +2 -0
  56. package/dist/public/text-formatting.js +20 -0
  57. package/dist/public/theme-init.js +8 -0
  58. package/dist/public/theme.js +13 -0
  59. package/dist/public/whitespace-visualization.js +20 -0
  60. package/dist/request-context.js +16 -0
  61. package/dist/request-context.js.map +1 -0
  62. package/dist/security.js +247 -0
  63. package/dist/security.js.map +1 -0
  64. package/dist/server-runtime.js +90 -0
  65. package/dist/server-runtime.js.map +1 -0
  66. package/dist/server.js +26 -0
  67. package/dist/server.js.map +1 -0
  68. package/dist/store.js +2568 -0
  69. package/dist/store.js.map +1 -0
  70. package/dist/user-auth.js +515 -0
  71. package/dist/user-auth.js.map +1 -0
  72. package/dist/utils.js +67 -0
  73. package/dist/utils.js.map +1 -0
  74. package/package.json +4 -9
@@ -0,0 +1,547 @@
1
+ <!doctype html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <meta name="description" content="面向长篇小说创作的 AI 协作工作台">
7
+ <meta name="theme-color" content="#8b3d2c">
8
+ <title>叙界 · 小说 AI 创作工作台</title>
9
+ <script src="/theme-init.js?v=20260713-dark-mode"></script>
10
+ <link rel="icon" href="/icon.svg?v=20260712" type="image/svg+xml">
11
+ <link rel="manifest" href="/site.webmanifest">
12
+ <link rel="stylesheet" href="/styles.css?v=20260719-viewer-toast-layer">
13
+ </head>
14
+ <body class="auth-pending">
15
+ <section id="auth-view" class="auth-view" aria-labelledby="auth-title">
16
+ <div class="auth-card">
17
+ <div class="auth-brand"><span class="brand-mark">叙</span><div><strong>叙界</strong><small>小说 AI 创作工作台</small></div></div>
18
+ <span class="eyebrow">账户与作品空间</span>
19
+ <h1 id="auth-title">登录后继续创作</h1>
20
+ <p id="auth-description">你的作品、协作权限和每一次修改都会绑定到账户。</p>
21
+ <div class="auth-tabs" role="tablist" aria-label="登录或注册">
22
+ <button id="auth-login-tab" type="button" role="tab" aria-selected="true">登录</button>
23
+ <button id="auth-register-tab" type="button" role="tab" aria-selected="false">注册</button>
24
+ </div>
25
+ <form id="login-form" class="auth-form">
26
+ <label>用户名<input name="username" autocomplete="username" maxlength="100" required></label>
27
+ <label>密码<span class="password-field"><input id="login-password" name="password" type="password" autocomplete="current-password" maxlength="200" required><button class="password-toggle" type="button" data-password-toggle="login-password" aria-label="显示密码" aria-pressed="false" title="显示密码"><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M2.5 12s3.3-5.5 9.5-5.5S21.5 12 21.5 12 18.2 17.5 12 17.5 2.5 12 2.5 12Z"/><circle cx="12" cy="12" r="2.8"/></svg></button></span></label>
28
+ <div class="auth-captcha">
29
+ <label>验证码<input name="captchaAnswer" autocomplete="off" inputmode="text" maxlength="8" spellcheck="false" required aria-describedby="login-captcha-hint"></label>
30
+ <button id="login-captcha-refresh" class="auth-captcha-image" type="button" aria-label="刷新验证码" title="刷新验证码">
31
+ <img id="login-captcha-image" alt="验证码图片">
32
+ </button>
33
+ <input id="login-captcha-id" name="captchaId" type="hidden" required>
34
+ </div>
35
+ <p id="login-captcha-hint" class="auth-captcha-hint">点击图片可刷新</p>
36
+ <button class="primary-button" type="submit">登录</button>
37
+ </form>
38
+ <form id="register-form" class="auth-form hidden">
39
+ <label>用户名<input name="username" autocomplete="username" minlength="3" maxlength="40" pattern="[A-Za-z0-9_.\-\u4e00-\u9fff]+" required></label>
40
+ <label>密码<span class="password-field"><input id="register-password" name="password" type="password" autocomplete="new-password" minlength="10" maxlength="200" required><button class="password-toggle" type="button" data-password-toggle="register-password" aria-label="显示密码" aria-pressed="false" title="显示密码"><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M2.5 12s3.3-5.5 9.5-5.5S21.5 12 21.5 12 18.2 17.5 12 17.5 2.5 12 2.5 12Z"/><circle cx="12" cy="12" r="2.8"/></svg></button></span></label>
41
+ <label>确认密码<span class="password-field"><input id="register-password-confirmation" name="passwordConfirmation" type="password" autocomplete="new-password" minlength="10" maxlength="200" required><button class="password-toggle" type="button" data-password-toggle="register-password-confirmation" aria-label="显示确认密码" aria-pressed="false" title="显示确认密码"><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M2.5 12s3.3-5.5 9.5-5.5S21.5 12 21.5 12 18.2 17.5 12 17.5 2.5 12 2.5 12Z"/><circle cx="12" cy="12" r="2.8"/></svg></button></span></label>
42
+ <div class="auth-captcha">
43
+ <label>验证码<input name="captchaAnswer" autocomplete="off" inputmode="text" maxlength="8" spellcheck="false" required aria-describedby="register-captcha-hint"></label>
44
+ <button id="register-captcha-refresh" class="auth-captcha-image" type="button" aria-label="刷新验证码" title="刷新验证码">
45
+ <img id="register-captcha-image" alt="验证码图片">
46
+ </button>
47
+ <input id="register-captcha-id" name="captchaId" type="hidden" required>
48
+ </div>
49
+ <p id="register-captcha-hint" class="auth-captcha-hint">点击图片可刷新</p>
50
+ <small>至少 10 个字符。系统中的第一个注册用户会自动成为管理员。</small>
51
+ <button class="primary-button" type="submit">创建账户</button>
52
+ </form>
53
+ <p id="auth-error" class="auth-error" role="alert"></p>
54
+ </div>
55
+ </section>
56
+ <div id="app" class="app-shell">
57
+ <header class="topbar">
58
+ <button id="home-button" class="brand-block" type="button" aria-label="返回书架">
59
+ <span class="brand-mark">叙</span>
60
+ <div>
61
+ <strong>叙界</strong>
62
+ <small>小说 AI 创作工作台</small>
63
+ </div>
64
+ </button>
65
+ <div id="work-meta" class="work-meta">尚未选择作品</div>
66
+ <div class="top-actions">
67
+ <span id="save-state" class="save-state">就绪</span>
68
+ <button id="account-button" class="account-button" type="button" aria-haspopup="menu" aria-expanded="false"><span id="account-avatar" class="user-avatar account-avatar" aria-hidden="true"><span class="user-avatar-fallback">作</span></span><span id="account-name">账户</span></button>
69
+ <div id="account-menu" class="account-menu hidden" role="menu">
70
+ <strong id="account-menu-name">账户</strong><small id="account-menu-role">普通用户</small>
71
+ <button id="onboarding-menu-button" type="button" role="menuitem">功能导览</button>
72
+ <button id="account-settings-button" type="button" role="menuitem">账户设置</button>
73
+ <button id="logout-button" type="button" role="menuitem">退出登录</button>
74
+ </div>
75
+ <button id="top-search-button" class="topbar-icon-button" type="button" aria-label="全文检索" title="全文检索" disabled>
76
+ <svg class="topbar-icon" viewBox="0 0 24 24" aria-hidden="true"><circle cx="11" cy="11" r="6.5"/><path d="m16.2 16.2 4.3 4.3"/></svg>
77
+ </button>
78
+ <button id="theme-toggle" class="theme-toggle" type="button" aria-label="切换到黑夜模式" title="切换到黑夜模式" aria-pressed="false">
79
+ <svg class="theme-icon theme-icon-moon" viewBox="0 0 24 24" aria-hidden="true"><path d="M20.2 15.3A8.6 8.6 0 0 1 8.7 3.8 8.8 8.8 0 1 0 20.2 15.3Z"/></svg>
80
+ <svg class="theme-icon theme-icon-sun" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="3.8"/><path d="M12 2v2.1M12 19.9V22M4.9 4.9l1.5 1.5M17.6 17.6l1.5 1.5M2 12h2.1M19.9 12H22M4.9 19.1l1.5-1.5M17.6 6.4l1.5-1.5"/></svg>
81
+ </button>
82
+ <button id="settings-button" class="settings-button" type="button" aria-label="设置" title="设置">
83
+ <svg class="settings-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.09a2 2 0 0 1 1 1.74v.5a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.38a2 2 0 0 0-.73-2.73l-.15-.09a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2Z"/><circle cx="12" cy="12" r="3"/></svg>
84
+ </button>
85
+ </div>
86
+ </header>
87
+
88
+ <aside class="left-panel">
89
+ <div id="left-panel-resize" class="panel-resize-handle" role="separator" aria-label="调整作品侧栏宽度" aria-orientation="vertical" tabindex="0"></div>
90
+ <div class="left-actions">
91
+ <div class="left-primary-actions">
92
+ <label class="file-button" aria-label="导入 TXT / DOCX">
93
+ <span class="import-file-label import-file-label-full" aria-hidden="true">导入 TXT / DOCX</span>
94
+ <span class="import-file-label import-file-label-compact" aria-hidden="true">导入TXT/DOCX</span>
95
+ <span class="import-file-label import-file-label-short" aria-hidden="true">导入</span>
96
+ <input id="import-file" type="file" accept=".txt,.docx">
97
+ </label>
98
+ <button id="left-panel-toggle" class="panel-collapse-button" type="button" aria-label="收起作品侧栏" aria-expanded="true">‹</button>
99
+ </div>
100
+ <button id="new-volume-button" class="secondary-button" type="button">新建分卷</button>
101
+ <button id="new-chapter-button" class="secondary-button" type="button">新建章节</button>
102
+ </div>
103
+ <nav id="module-nav" class="module-nav" aria-label="作品模块">
104
+ <button type="button" data-module="editor" class="active">正文</button>
105
+ <button type="button" data-module="settings">设定库</button>
106
+ <button type="button" data-module="characters">角色</button>
107
+ <button type="button" data-module="organizations">组织</button>
108
+ <button type="button" data-module="timeline">时间轴</button>
109
+ <button type="button" data-module="relationships">关系</button>
110
+ <button type="button" data-module="outlines">大纲与伏笔</button>
111
+ <button class="ai-analysis-entry" type="button" data-module="tasks">AI 分析</button>
112
+ <button id="module-more-button" type="button" aria-expanded="false">更多</button>
113
+ <button class="module-nav-secondary hidden" type="button" data-module="races">种族</button>
114
+ <button class="module-nav-secondary hidden" type="button" data-module="reviews">审核</button>
115
+ <button class="module-nav-secondary hidden" type="button" data-module="ai-settings">AI 设置</button>
116
+ <button class="module-nav-secondary hidden" type="button" data-work-settings>作品设置</button>
117
+ </nav>
118
+ <div class="panel-heading">
119
+ <span>作品目录</span>
120
+ <span id="chapter-count">0 章</span>
121
+ </div>
122
+ <div id="novel-tree" class="novel-tree empty-copy">创建或选择作品后开始写作</div>
123
+ </aside>
124
+
125
+ <main id="main-panel" class="main-panel">
126
+ <section id="shelf-view" class="shelf-view hidden" aria-labelledby="shelf-title">
127
+ <div class="shelf-header">
128
+ <div><span class="eyebrow">我的创作书架</span><h1 id="shelf-title">选择一本书,继续写下去</h1><p>封面、正文、人物关系和创作约束都保存在同一部作品中。</p></div>
129
+ <button id="shelf-new-work" class="primary-button" type="button">新建作品</button>
130
+ </div>
131
+ <div id="book-shelf" class="book-shelf" data-testid="book-shelf"></div>
132
+ </section>
133
+
134
+ <section id="platform-ai-view" class="shelf-view hidden" aria-labelledby="platform-ai-title">
135
+ <div class="shelf-header">
136
+ <div><span class="eyebrow">平台资源</span><h1 id="platform-ai-title">AI 管理</h1><p>供应商、模型与全局系统提示词对所有作品生效;每本书可在“AI 设置”中追加自己的系统提示词。</p></div>
137
+ <button id="platform-new-provider" class="primary-button" type="button">新建供应商</button>
138
+ </div>
139
+ <div id="platform-ai-content" class="module-content"></div>
140
+ </section>
141
+
142
+ <section id="settings-hub-view" class="shelf-view hidden" aria-labelledby="settings-hub-title">
143
+ <div class="shelf-header settings-hub-header">
144
+ <div><span class="eyebrow">工作台配置</span><h1 id="settings-hub-title">设置</h1><p>集中管理 AI、显示偏好、内容检索与作品导出。</p></div>
145
+ <button id="settings-return" class="ghost-button" type="button">返回书架</button>
146
+ </div>
147
+ <div class="settings-hub-grid">
148
+ <button id="platform-ai-button" class="settings-hub-card hidden" type="button" data-settings-action="ai"><span class="settings-card-mark">AI</span><strong>AI 管理</strong><small>供应商、模型、上下文与全局提示词</small></button>
149
+ <button id="user-management-button" class="settings-hub-card hidden" type="button"><span class="settings-card-mark">人</span><strong>用户管理</strong><small>管理员、普通用户与账户状态</small></button>
150
+ <button id="platform-ui-settings-button" class="settings-hub-card hidden" type="button"><span class="settings-card-mark">界</span><strong>界面通知</strong><small>设置所有用户的 Toast 提示位置</small></button>
151
+ <button id="collaboration-button" class="settings-hub-card" type="button"><span class="settings-card-mark">协</span><strong>作品协作</strong><small>邀请注册用户共同编辑当前作品</small></button>
152
+ <button id="appearance-button" class="settings-hub-card" type="button" data-settings-action="appearance"><span class="settings-card-mark">Aa</span><strong>显示设置</strong><small>中文字体、等宽英文字体、字号与行距</small></button>
153
+ <button id="export-button" class="settings-hub-card" type="button" data-settings-action="export"><span class="settings-card-mark">MD</span><strong>导出作品</strong><small>将当前作品安全导出为 Markdown</small></button>
154
+ </div>
155
+ <p id="settings-work-note" class="settings-work-note"></p>
156
+ </section>
157
+
158
+ <section id="welcome-view" class="welcome-view hidden">
159
+ <span class="eyebrow">从一个世界开始</span>
160
+ <h1>把长篇故事的每条线索,<br>留在作者掌控之中。</h1>
161
+ <p>管理章节、人物、设定和时间线。AI 只提出有依据的建议,是否写入正文始终由你决定。</p>
162
+ <button id="welcome-new-work" class="primary-button" type="button">创建第一部作品</button>
163
+ <div class="welcome-grid">
164
+ <article><b>版本可回溯</b><span>每次保存正文都形成可恢复版本</span></article>
165
+ <article><b>设定有边界</b><span>锁定事实成为续写和校对的硬约束</span></article>
166
+ <article><b>结论有证据</b><span>分析、关系与事件保留原文依据</span></article>
167
+ </div>
168
+ </section>
169
+
170
+ <section id="editor-view" class="editor-view hidden">
171
+ <div class="editor-toolbar">
172
+ <span id="chapter-path" class="eyebrow">未选择章节</span>
173
+ <input id="chapter-title" class="chapter-title" aria-label="章节标题" placeholder="章节标题">
174
+ <div class="editor-actions">
175
+ <span id="chapter-stats" class="chapter-stats">0 字 · v0</span>
176
+ <button id="insight-button" class="ghost-button" type="button">章节概览</button>
177
+ <button id="versions-button" class="ghost-button" type="button">版本</button>
178
+ <button id="tidy-blank-lines-button" class="ghost-button" type="button">整理空行</button>
179
+ <button id="toggle-whitespace-button" class="ghost-button" type="button" aria-pressed="true" title="用点标记半角空格,用方框标记全角空格,用箭头标记 Tab">隐藏空白符</button>
180
+ <button id="save-button" class="primary-button" type="button">保存正文</button>
181
+ </div>
182
+ </div>
183
+ <div class="editor-body">
184
+ <div id="chapter-insight" class="chapter-insight hidden"></div>
185
+ <div class="chapter-editor-frame">
186
+ <aside id="chapter-line-numbers" class="chapter-line-numbers" aria-label="正文行号,可拖拽引用多行"><div id="chapter-line-numbers-inner"></div></aside>
187
+ <textarea id="chapter-content" class="chapter-content" aria-label="章节正文" placeholder="在这里开始你的故事……"></textarea>
188
+ <div id="chapter-whitespace-overlay" class="chapter-whitespace-overlay is-visible" aria-hidden="true"><div id="chapter-whitespace-inner"></div></div>
189
+ <div id="chapter-line-measure" class="chapter-line-measure" aria-hidden="true"></div>
190
+ </div>
191
+ </div>
192
+ </section>
193
+
194
+ <section id="module-view" class="module-view hidden">
195
+ <div class="module-header">
196
+ <div>
197
+ <span id="module-eyebrow" class="eyebrow">作品知识</span>
198
+ <h1 id="module-title">模块</h1>
199
+ <p id="module-description"></p>
200
+ </div>
201
+ <button id="module-create-button" class="primary-button" type="button">新建</button>
202
+ </div>
203
+ <div id="module-content" class="module-content"></div>
204
+ </section>
205
+ </main>
206
+
207
+ <aside class="ai-panel">
208
+ <div id="ai-panel-resize" class="panel-resize-handle" role="separator" aria-label="调整创作助手宽度" aria-orientation="vertical" tabindex="0"></div>
209
+ <div class="ai-heading">
210
+ <button id="ai-panel-toggle" class="panel-collapse-button" type="button" aria-label="收起创作助手" aria-expanded="true">›</button>
211
+ <div><span class="status-dot"></span><strong>创作助手</strong></div>
212
+ <small>结果作为建议</small>
213
+ </div>
214
+ <div class="ai-conversation-toolbar">
215
+ <span id="ai-conversation-title">新对话</span>
216
+ <button id="ai-new-conversation" type="button">新建对话</button>
217
+ <button id="ai-history-toggle" type="button" aria-controls="ai-history-dialog" aria-expanded="false" aria-haspopup="dialog">历史记录</button>
218
+ </div>
219
+ <div class="quick-actions" aria-label="快捷指令">
220
+ <button type="button" data-task="chat" data-prompt="总结当前章节,并列出尚未解决的冲突。">总结本章</button>
221
+ <button type="button" data-task="continue" data-prompt="续写当前章节,保持现有叙事风格和人物状态。">续写</button>
222
+ <button type="button" data-task="chat" data-prompt="提供三种后续剧情方向,并说明各自与既有设定的兼容性。">三种方向</button>
223
+ <button type="button" data-task="chat" data-prompt="检查当前章节与锁定设定、人物状态和时间线的冲突。">检查冲突</button>
224
+ </div>
225
+ <div id="ai-feed" class="ai-feed">
226
+ <div class="assistant-message">
227
+ <span>助手</span>
228
+ <div class="message-body"><p>选择章节和模型后,可以问答、续写或校对。所有引用都基于已保存正文。</p></div>
229
+ </div>
230
+ </div>
231
+ <div class="prompt-box">
232
+ <div id="ai-citations" class="ai-citations hidden" aria-label="已添加的正文引用"></div>
233
+ <section id="ai-context-warning" class="ai-context-warning hidden" role="status" aria-live="polite">
234
+ <div><strong id="ai-context-warning-title">对话历史接近整理阈值</strong><p id="ai-context-warning-message">建议整理为长期记忆或新开对话。</p></div>
235
+ <div><button id="ai-context-compact" type="button">整理长期记忆</button><button id="ai-context-new-conversation" type="button">新开对话</button><button id="ai-context-dismiss" type="button">稍后处理</button></div>
236
+ </section>
237
+ <div class="prompt-options">
238
+ <select id="ai-task" aria-label="任务类型">
239
+ <option value="chat">问答</option>
240
+ <option value="continue">续写</option>
241
+ <option value="polish">润色选中文本</option>
242
+ </select>
243
+ <select id="ai-scope" aria-label="上下文范围">
244
+ <option value="none">无上下文</option>
245
+ <option value="chapter">当前章节</option>
246
+ <option value="chapter-summary">当前章节 + 全书概要</option>
247
+ <option value="volume">当前卷</option>
248
+ <option value="book">全书</option>
249
+ </select>
250
+ </div>
251
+ <div class="field-label prompt-model-field">
252
+ <select id="ai-model" aria-label="实际使用模型"></select>
253
+ </div>
254
+ <div class="prompt-composer">
255
+ <div id="ai-prompt" class="ai-prompt" contenteditable="true" role="textbox" aria-multiline="true" aria-keyshortcuts="Enter" title="Enter 发送,Shift+Enter 换行" data-placeholder="告诉 AI 你想讨论或修改什么……"></div>
256
+ <div id="ai-mention-menu" class="ai-mention-menu hidden" role="listbox" aria-label="引用角色、设定或章节"></div>
257
+ <div class="prompt-composer-actions">
258
+ <output id="ai-context-meter" class="ai-context-meter is-empty" aria-live="polite" aria-label="当前上下文用量" tabindex="0"><b>—</b></output>
259
+ <button id="ai-send" class="ai-send-button" type="button" aria-label="发送消息">发送</button>
260
+ </div>
261
+ </div>
262
+ </div>
263
+ </aside>
264
+ </div>
265
+
266
+ <div id="toast-region" class="toast-region" data-position="bottom-right" aria-live="polite" popover="manual"></div>
267
+ <div id="chapter-type-menu" class="chapter-type-menu hidden" role="menu" aria-label="设置章节类型" data-testid="chapter-type-menu">
268
+ <strong>标记章节</strong>
269
+ <div>
270
+ <button type="button" role="menuitemradio" data-chapter-type="正文">正文</button>
271
+ <button type="button" role="menuitemradio" data-chapter-type="设定">设定</button>
272
+ <button type="button" role="menuitemradio" data-chapter-type="作者的话">作者的话</button>
273
+ <button type="button" role="menuitemradio" data-chapter-type="其他">其他</button>
274
+ </div>
275
+ </div>
276
+ <div id="line-citation-menu" class="line-citation-menu hidden" role="menu" aria-label="正文行引用操作">
277
+ <strong id="line-citation-label">已选择正文</strong>
278
+ <button id="add-line-citation" type="button" role="menuitem">添加到创作助手引用</button>
279
+ </div>
280
+ <input id="new-import-file" class="hidden" type="file" accept=".txt,.docx">
281
+ <input id="cover-file" class="hidden" type="file" accept="image/png,image/jpeg,image/webp">
282
+ <input id="avatar-file" class="hidden" type="file" accept="image/png,image/jpeg,image/webp">
283
+
284
+ <dialog id="onboarding-dialog" class="onboarding-dialog" aria-labelledby="onboarding-dialog-title" aria-describedby="onboarding-dialog-description" data-testid="first-use-onboarding">
285
+ <div id="onboarding-spotlight" class="onboarding-spotlight" aria-hidden="true"></div>
286
+ <section id="onboarding-popover" class="onboarding-popover" data-placement="bottom">
287
+ <header class="onboarding-popover-header">
288
+ <span id="onboarding-progress" aria-live="polite">第 1 步</span>
289
+ <button id="onboarding-skip" class="onboarding-skip" type="button">跳过导览</button>
290
+ </header>
291
+ <div class="onboarding-popover-body">
292
+ <span id="onboarding-eyebrow" class="eyebrow">快速导览</span>
293
+ <h2 id="onboarding-dialog-title" tabindex="-1">认识叙界</h2>
294
+ <p id="onboarding-dialog-description">跟随箭头认识工作台里的常用入口。</p>
295
+ </div>
296
+ <footer class="onboarding-popover-footer">
297
+ <button id="onboarding-previous" class="ghost-button" type="button" disabled>上一步</button>
298
+ <button id="onboarding-next" class="primary-button" type="button">下一步</button>
299
+ </footer>
300
+ </section>
301
+ </dialog>
302
+
303
+ <dialog id="form-dialog" class="dialog">
304
+ <form id="dynamic-form" method="dialog">
305
+ <div class="dialog-header">
306
+ <div><span id="dialog-eyebrow" class="eyebrow">新增</span><h2 id="dialog-title">创建记录</h2></div>
307
+ <button class="dialog-close" value="cancel" aria-label="关闭" type="submit">×</button>
308
+ </div>
309
+ <div id="dialog-fields" class="dialog-fields"></div>
310
+ <div class="dialog-actions">
311
+ <button class="ghost-button" value="cancel" type="submit">取消</button>
312
+ <button id="dialog-submit" class="primary-button" value="default" type="submit">保存</button>
313
+ </div>
314
+ </form>
315
+ </dialog>
316
+
317
+ <dialog id="character-editor-dialog" class="character-editor-dialog" aria-labelledby="character-editor-title">
318
+ <form id="character-editor-form">
319
+ <header class="character-editor-header">
320
+ <div>
321
+ <span id="character-editor-eyebrow" class="eyebrow">人物主档案</span>
322
+ <div class="character-editor-title-row"><h2 id="character-editor-title">新建角色</h2><span id="character-editor-version" class="character-version-badge">新档案</span></div>
323
+ </div>
324
+ <div class="character-editor-header-actions">
325
+ <button id="character-history-button" class="ghost-button" type="button" aria-controls="character-history-panel" aria-expanded="false">版本历史</button>
326
+ <button id="character-editor-close" class="dialog-close" aria-label="关闭人物编辑器" type="button">×</button>
327
+ </div>
328
+ </header>
329
+ <div class="character-editor-workspace">
330
+ <nav class="character-editor-nav" role="tablist" aria-label="人物档案编辑分区">
331
+ <button type="button" role="tab" data-character-editor-tab="basic" aria-selected="true">基础资料<small>名称、种族与归属</small></button>
332
+ <button type="button" role="tab" data-character-editor-tab="profile" aria-selected="false">人物档案<small>简介、身份与动机</small></button>
333
+ <button type="button" role="tab" data-character-editor-tab="settings" aria-selected="false">扩展设定<small>属性与长篇章节</small></button>
334
+ <button type="button" role="tab" data-character-editor-tab="state" aria-selected="false">状态与约束<small>当前状态与锁定项</small></button>
335
+ </nav>
336
+ <main id="character-editor-fields" class="character-editor-fields"></main>
337
+ <aside id="character-history-panel" class="character-history-panel hidden" aria-label="人物版本历史">
338
+ <div class="character-history-heading"><div><span class="eyebrow">可回滚记录</span><h3>版本历史</h3></div><button id="character-history-close" type="button" aria-label="关闭版本历史">×</button></div>
339
+ <div id="character-history-list" class="character-history-list"></div>
340
+ </aside>
341
+ </div>
342
+ <footer class="character-editor-actions">
343
+ <label>版本说明<input id="character-change-note" name="changeNote" maxlength="500" placeholder="可选,例如:补充红莲形态与能力限制"></label>
344
+ <div><button id="character-editor-cancel" class="ghost-button" type="button">取消</button><button id="character-editor-submit" class="primary-button" type="submit">保存新版本</button></div>
345
+ </footer>
346
+ </form>
347
+ </dialog>
348
+
349
+ <dialog id="search-dialog" class="dialog wide-dialog" aria-labelledby="search-dialog-title">
350
+ <div class="dialog-header">
351
+ <div><span class="eyebrow">当前作品</span><h2 id="search-dialog-title">全文检索</h2></div>
352
+ <button id="search-dialog-close" class="dialog-close" aria-label="关闭" type="button">×</button>
353
+ </div>
354
+ <div class="access-dialog-body">
355
+ <form id="search-form" class="search-form">
356
+ <label>关键词<input id="search-query" name="query" type="search" maxlength="100" placeholder="搜索正文、设定、角色、种族或组织" required></label>
357
+ <button class="primary-button" type="submit">搜索</button>
358
+ </form>
359
+ <div id="search-results" class="search-results" aria-live="polite">
360
+ <p class="search-results-empty">输入关键词后开始检索。</p>
361
+ </div>
362
+ </div>
363
+ </dialog>
364
+
365
+ <dialog id="appearance-dialog" class="dialog">
366
+ <form id="appearance-form" method="dialog">
367
+ <div class="dialog-header">
368
+ <div><span class="eyebrow">本机偏好</span><h2>显示设置</h2></div>
369
+ <button class="dialog-close" value="cancel" aria-label="关闭" type="submit">×</button>
370
+ </div>
371
+ <div class="appearance-grid">
372
+ <label>中文字体
373
+ <select id="appearance-cjk-font" name="cjkFont">
374
+ <option value="system">系统黑体</option>
375
+ <option value="pingfang">苹方</option>
376
+ <option value="heiti">黑体</option>
377
+ <option value="yahei">微软雅黑</option>
378
+ <option value="noto-sans">思源黑体</option>
379
+ </select>
380
+ </label>
381
+ <label>英文字体(仅等宽)
382
+ <select id="appearance-latin-font" name="latinFont">
383
+ <option value="system">系统等宽</option>
384
+ <option value="sf-mono">SF Mono</option>
385
+ <option value="menlo">Menlo</option>
386
+ <option value="monaco">Monaco</option>
387
+ <option value="consolas">Consolas</option>
388
+ </select>
389
+ </label>
390
+ <label>正文字号
391
+ <select id="appearance-font-size" name="fontSize">
392
+ <option value="15">15 px</option>
393
+ <option value="16">16 px</option>
394
+ <option value="17">17 px</option>
395
+ <option value="18">18 px</option>
396
+ <option value="20">20 px</option>
397
+ </select>
398
+ </label>
399
+ <label>正文行距与段落密度
400
+ <select id="appearance-density" name="density">
401
+ <option value="compact">紧凑</option>
402
+ <option value="balanced">标准</option>
403
+ <option value="relaxed">宽松</option>
404
+ </select>
405
+ </label>
406
+ <div id="font-preview" class="font-preview" aria-live="polite">
407
+ <strong>字体预览 Typography 0123</strong>
408
+ <span>中文默认使用黑体,English letters always use monospace.</span>
409
+ <span>第二段用于预览正文的行距与段落密度。</span>
410
+ </div>
411
+ </div>
412
+ <div class="dialog-actions">
413
+ <button id="appearance-reset" class="ghost-button reset-button" type="button">恢复默认</button>
414
+ <button class="ghost-button" value="cancel" type="submit">取消</button>
415
+ <button class="primary-button" value="save" type="submit">保存设置</button>
416
+ </div>
417
+ </form>
418
+ </dialog>
419
+
420
+ <dialog id="versions-dialog" class="dialog wide-dialog">
421
+ <div class="dialog-header">
422
+ <div><span class="eyebrow">版本历史</span><h2>章节保存记录</h2></div>
423
+ <button id="versions-close" class="dialog-close" aria-label="关闭" type="button">×</button>
424
+ </div>
425
+ <div id="versions-list" class="versions-list"></div>
426
+ </dialog>
427
+
428
+ <dialog id="users-dialog" class="dialog wide-dialog" aria-labelledby="users-dialog-title">
429
+ <div class="dialog-header"><div><span class="eyebrow">系统管理员</span><h2 id="users-dialog-title">用户管理</h2></div><button id="users-dialog-close" class="dialog-close" aria-label="关闭" type="button">×</button></div>
430
+ <div class="access-dialog-body">
431
+ <div id="users-list" class="access-list"></div>
432
+ </div>
433
+ </dialog>
434
+
435
+ <dialog id="platform-ui-settings-dialog" class="dialog" aria-labelledby="platform-ui-settings-title">
436
+ <div class="dialog-header"><div><span class="eyebrow">系统管理员</span><h2 id="platform-ui-settings-title">界面通知</h2></div><button id="platform-ui-settings-close" class="dialog-close" aria-label="关闭" type="button">×</button></div>
437
+ <form id="platform-ui-settings-form">
438
+ <div class="dialog-fields">
439
+ <label>Toast 提示位置<select id="toast-position" name="toastPosition" aria-label="Toast 提示位置"><option value="bottom-right">右下角(默认)</option><option value="top-right">右上角</option></select></label>
440
+ <small>该设置对所有用户生效,保存后的提示会立即显示在新位置。</small>
441
+ </div>
442
+ <div class="dialog-actions"><button class="ghost-button" id="platform-ui-settings-cancel" type="button">取消</button><button class="primary-button" id="platform-ui-settings-save" type="submit">保存设置</button></div>
443
+ </form>
444
+ </dialog>
445
+
446
+ <dialog id="account-dialog" class="dialog account-dialog" aria-labelledby="account-dialog-title">
447
+ <div class="dialog-header"><div><span class="eyebrow">个人账户</span><h2 id="account-dialog-title">账户设置</h2></div><button id="account-dialog-close" class="dialog-close" aria-label="关闭" type="button">×</button></div>
448
+ <div class="account-settings-body">
449
+ <section class="account-settings-section" aria-labelledby="profile-settings-title">
450
+ <header class="account-settings-section-header"><h3 id="profile-settings-title">个人资料</h3><p>用于作品协作和历史记录中的作者标识。</p></header>
451
+ <div class="avatar-settings">
452
+ <div id="profile-avatar-preview" class="user-avatar profile-avatar-preview" role="img" aria-label="当前头像"><span class="user-avatar-fallback">作</span></div>
453
+ <div class="avatar-settings-copy"><strong>头像图片</strong><small>支持 PNG、JPEG、WebP,文件不超过 5 MB,尺寸不超过 4096 × 4096 像素。</small></div>
454
+ <div class="avatar-settings-actions"><button id="avatar-upload-button" class="ghost-button" type="button">上传头像</button><button id="avatar-remove-button" class="ghost-button hidden" type="button">移除头像</button></div>
455
+ </div>
456
+ <form id="profile-form" class="account-settings-form"><label>显示名称<input id="profile-display-name" name="displayName" maxlength="80" required></label><button class="primary-button" type="submit">保存名称</button></form>
457
+ </section>
458
+ <section class="account-settings-section" aria-labelledby="password-settings-title">
459
+ <header class="account-settings-section-header"><h3 id="password-settings-title">修改密码</h3><p>新密码至少需要 10 个字符。</p></header>
460
+ <form id="password-form" class="account-settings-form password-settings-form"><label>当前密码<input name="currentPassword" type="password" autocomplete="current-password" maxlength="200" required></label><label>新密码<input name="newPassword" type="password" autocomplete="new-password" minlength="10" maxlength="200" required></label><button class="primary-button" type="submit">更新密码</button></form>
461
+ </section>
462
+ <section class="account-settings-section" aria-labelledby="api-key-settings-title">
463
+ <header class="account-settings-section-header"><h3 id="api-key-settings-title">CLI API Key</h3><p>用于 CLI 登录。服务端只保存摘要,重置后旧 Key 会立即失效,新 Key 仅显示一次。</p></header>
464
+ <div class="api-key-settings">
465
+ <p id="api-key-status">正在读取 API Key 状态…</p>
466
+ <button id="api-key-reset-button" class="primary-button" type="button">生成 API Key</button>
467
+ <div id="api-key-result" class="api-key-result hidden">
468
+ <label>请立即保存<input id="api-key-value" readonly spellcheck="false"></label>
469
+ <button id="api-key-copy-button" type="button">复制</button>
470
+ </div>
471
+ </div>
472
+ </section>
473
+ </div>
474
+ </dialog>
475
+
476
+ <dialog id="members-dialog" class="dialog wide-dialog" aria-labelledby="members-dialog-title">
477
+ <div class="dialog-header"><div><span id="members-dialog-eyebrow" class="eyebrow">作品权限</span><h2 id="members-dialog-title">可访问人</h2></div><button id="members-dialog-close" class="dialog-close" aria-label="关闭" type="button">×</button></div>
478
+ <div class="access-dialog-body">
479
+ <form id="member-invite-form" class="member-invite-form"><label>邀请已注册用户<select id="member-user-select" required></select></label><label>访问权限<select id="member-role-select" required><option value="viewer">仅查看</option><option value="editor">可编辑</option></select></label><button class="primary-button" type="submit">发送邀请</button></form>
480
+ <div id="members-list" class="access-list"></div>
481
+ </div>
482
+ </dialog>
483
+
484
+ <dialog id="entity-history-dialog" class="dialog wide-dialog" aria-labelledby="entity-history-title">
485
+ <div class="dialog-header">
486
+ <div><span id="entity-history-eyebrow" class="eyebrow">可回滚记录</span><h2 id="entity-history-title">版本历史</h2></div>
487
+ <button id="entity-history-close" class="dialog-close" aria-label="关闭版本历史" type="button">×</button>
488
+ </div>
489
+ <div id="entity-history-list" class="entity-history-list" aria-live="polite"></div>
490
+ </dialog>
491
+
492
+ <dialog id="ai-tool-call-dialog" class="dialog ai-tool-call-dialog" aria-labelledby="ai-tool-call-title">
493
+ <div class="dialog-header">
494
+ <div><span id="ai-tool-call-status" class="eyebrow">工具调用</span><h2 id="ai-tool-call-title">工具调用详情</h2></div>
495
+ <button id="ai-tool-call-close" class="dialog-close" aria-label="关闭工具调用详情" type="button">×</button>
496
+ </div>
497
+ <div class="ai-tool-call-detail">
498
+ <dl class="ai-tool-call-info">
499
+ <div><dt>函数名称</dt><dd><code id="ai-tool-call-name"></code></dd></div>
500
+ <div><dt>调用时间</dt><dd><time id="ai-tool-call-time"></time></dd></div>
501
+ <div class="ai-tool-call-description"><dt>函数信息</dt><dd id="ai-tool-call-description"></dd></div>
502
+ </dl>
503
+ <section><h3>参数</h3><pre id="ai-tool-call-arguments"></pre></section>
504
+ <section><h3>返回值</h3><pre id="ai-tool-call-result"></pre></section>
505
+ </div>
506
+ </dialog>
507
+
508
+ <dialog id="ai-history-dialog" class="dialog wide-dialog ai-history-dialog" aria-labelledby="ai-history-dialog-title">
509
+ <div class="dialog-header">
510
+ <div><span class="eyebrow">对话管理</span><h2 id="ai-history-dialog-title">历史记录</h2></div>
511
+ <button id="ai-history-close" class="dialog-close" aria-label="关闭对话历史" type="button">×</button>
512
+ </div>
513
+ <div class="ai-history-dialog-body">
514
+ <div id="ai-history-list" class="ai-history-list" aria-live="polite"></div>
515
+ </div>
516
+ </dialog>
517
+
518
+ <dialog id="relationship-map-dialog" class="relationship-map-dialog" aria-label="放大人物关系图" data-testid="relationship-map-expanded">
519
+ <button id="relationship-map-close" class="relationship-map-floating-close" aria-label="关闭放大关系图" type="button">×</button>
520
+ <div id="relationship-map-expanded-host" class="relationship-map-expanded-host"></div>
521
+ </dialog>
522
+
523
+ <dialog id="relationship-galaxy-dialog" class="galaxy-dialog" aria-labelledby="galaxy-title" data-testid="relationship-fullscreen">
524
+ <section class="galaxy-shell">
525
+ <h2 id="galaxy-title" class="sr-only">人物关系银河图</h2>
526
+ <canvas id="galaxy-background" class="galaxy-background" aria-hidden="true" data-testid="galaxy-3d-starfield"></canvas>
527
+ <canvas id="galaxy-graph" class="galaxy-graph" aria-hidden="true" data-testid="galaxy-3d-relationships"></canvas>
528
+ <div id="galaxy-node-layer" class="galaxy-node-layer"></div>
529
+ <button id="galaxy-close" class="galaxy-close" type="button" aria-label="关闭人物关系银河图" data-testid="galaxy-close">×</button>
530
+ <output id="galaxy-stats" class="galaxy-stats" aria-live="polite" data-testid="galaxy-stats"></output>
531
+ <aside class="galaxy-controls" aria-label="银河图控制">
532
+ <button id="galaxy-stars" type="button" aria-pressed="true" data-testid="galaxy-stars">隐藏背景星点</button>
533
+ <button id="galaxy-rotation" type="button" aria-pressed="false" data-testid="galaxy-rotation">暂停旋转</button>
534
+ <button id="galaxy-zoom-out" type="button" aria-label="缩小">−</button>
535
+ <button id="galaxy-zoom-in" type="button" aria-label="放大">+</button>
536
+ <button id="galaxy-reset" type="button" data-testid="galaxy-reset">重置视角</button>
537
+ </aside>
538
+ <ul class="galaxy-legend" aria-label="关系类型图例">
539
+ <li><i class="family"></i>亲属</li><li><i class="social"></i>社交</li><li><i class="emotional"></i>情感</li><li><i class="conflict"></i>冲突</li><li><i class="uncertain"></i>未确定</li>
540
+ </ul>
541
+ <aside id="galaxy-detail" class="galaxy-detail hidden" aria-live="polite"></aside>
542
+ </section>
543
+ </dialog>
544
+
545
+ <script type="module" src="/app.js?v=20260719-ai-grep-lazy-viewer-toast"></script>
546
+ </body>
547
+ </html>
@@ -0,0 +1,15 @@
1
+ export function calculateLineNumberTop(paddingTop, contentLineHeight, numberLineHeight) {
2
+ return Number(paddingTop);
3
+ }
4
+
5
+ export function calculateLineNumberTextOffset(contentLineHeight, numberLineHeight) {
6
+ return Math.max(0, (Number(contentLineHeight) - Number(numberLineHeight)) / 2);
7
+ }
8
+
9
+ export function calculateLineNumberRowHeight(contentLineHeight, measuredHeight) {
10
+ return Math.max(Number(contentLineHeight), Number(measuredHeight));
11
+ }
12
+
13
+ export function calculateLineNumberRowTop(measureTop, rowTop) {
14
+ return Math.max(0, Number(rowTop) - Number(measureTop));
15
+ }