@musnows/scriverse 0.5.12 → 0.6.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.
@@ -1,7 +1,7 @@
1
1
  import { levelLabel, relationshipCategoryLabel } from "./display-labels.js?v=20260725-enum-labels-zh";
2
2
 
3
3
  export const VERSIONED_ENTITY_LABELS = Object.freeze({
4
- draft: "创作草稿",
4
+ draft: "创作想法",
5
5
  setting: "世界观设定",
6
6
  race: "种族档案",
7
7
  organization: "组织档案",
@@ -25,7 +25,7 @@ export function entityVersionSourceLabel(source) {
25
25
  }
26
26
 
27
27
  export function entityVersionSnapshotSummary(type, snapshot = {}) {
28
- if (type === "draft") return `${snapshot.draftType === "setting" ? "设定草稿" : "正文草稿"} · ${snapshot.title || "未命名草稿"}`;
28
+ if (type === "draft") return `${snapshot.draftType === "setting" ? "设定想法" : "正文想法"} · ${snapshot.title || "未命名想法"}`;
29
29
  if (type === "setting") return `${snapshot.category || "未分类"} · ${snapshot.title || "未命名设定"}`;
30
30
  if (type === "race") return `${snapshot.name || "未命名种族"} · ${(snapshot.memberIds ?? []).length} 位角色`;
31
31
  if (type === "organization") return `${snapshot.name || "未命名组织"} · ${(snapshot.memberIds ?? []).length} 位成员`;
@@ -10,7 +10,7 @@
10
10
  <link rel="icon" href="/icon.svg?v=20260712" type="image/svg+xml">
11
11
  <link rel="manifest" href="/site.webmanifest">
12
12
  <link rel="stylesheet" href="/vendor/vditor/dist/index.css?v=3.11.2">
13
- <link rel="stylesheet" href="/styles.css?v=20260730-drafts-stream-v1">
13
+ <link rel="stylesheet" href="/styles.css?v=20260731-model-context-guidance-v1">
14
14
  </head>
15
15
  <body class="auth-pending">
16
16
  <section id="auth-view" class="auth-view hidden" aria-labelledby="auth-title">
@@ -75,7 +75,7 @@
75
75
  <div class="top-actions">
76
76
  <span id="save-state" class="save-state">就绪</span>
77
77
  <div id="presence-control" class="presence-control hidden">
78
- <button id="presence-button" class="presence-button" type="button" aria-expanded="false" aria-controls="presence-panel"><span class="presence-status-dot" aria-hidden="true"></span><span id="presence-count">1 人在线</span></button>
78
+ <button id="presence-button" class="presence-button" type="button" aria-expanded="false" aria-controls="presence-panel"><span class="presence-status-dot" aria-hidden="true"></span><span id="presence-count">1 人</span></button>
79
79
  <section id="presence-panel" class="presence-panel hidden" aria-label="当前作品在线协作者">
80
80
  <header><strong>正在访问这部作品</strong><small>离开后状态会自动过期</small></header>
81
81
  <div id="presence-list" class="presence-list" aria-live="polite"></div>
@@ -123,7 +123,7 @@
123
123
  </div>
124
124
  <nav id="module-nav" class="module-nav" aria-label="作品模块">
125
125
  <button type="button" data-module="editor" class="active"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></svg>正文</button>
126
- <button type="button" data-module="drafts"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 20h4l11-11a2.8 2.8 0 0 0-4-4L4 16v4Z"/><path d="m13.5 6.5 4 4"/></svg>草稿</button>
126
+ <button type="button" data-module="drafts"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 20h4l11-11a2.8 2.8 0 0 0-4-4L4 16v4Z"/><path d="m13.5 6.5 4 4"/></svg>想法</button>
127
127
  <button type="button" data-module="settings"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="m16 6 4 14"/><path d="M12 6v14"/><path d="M8 8v12"/><path d="M4 4v16"/></svg>设定库</button>
128
128
  <button type="button" data-module="characters"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>角色</button>
129
129
  <button type="button" data-module="organizations"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>组织</button>
@@ -132,6 +132,7 @@
132
132
  <button type="button" data-module="races"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><circle cx="11" cy="4" r="2"/><circle cx="18" cy="8" r="2"/><circle cx="20" cy="16" r="2"/><path d="M9 10a5 5 0 0 1 5 5v3.5a3.5 3.5 0 0 1-6.84 1.045Q6.52 17.48 4.46 16.84A3.5 3.5 0 0 1 5.5 10Z"/></svg>种族</button>
133
133
  <button class="ai-analysis-entry" type="button" data-module="tasks"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"/><path d="M20 3v4"/><path d="M22 5h-4"/><path d="M4 17v2"/><path d="M5 18H3"/></svg>AI 分析</button>
134
134
  <button id="module-more-button" type="button" aria-expanded="false"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg><span class="nav-label">更多</span></button>
135
+ <button class="module-nav-secondary hidden" type="button" data-module="comments"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M21 15a4 4 0 0 1-4 4H8l-5 3V7a4 4 0 0 1 4-4h10a4 4 0 0 1 4 4Z"/><path d="M8 9h8M8 13h5"/></svg><span class="nav-label">正文评论</span></button>
135
136
  <button class="module-nav-secondary hidden" type="button" data-module="outlines"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="m3 17 2 2 4-4"/><path d="m3 7 2 2 4-4"/><path d="M13 6h8"/><path d="M13 12h8"/><path d="M13 18h8"/></svg><span class="nav-label">大纲/伏笔</span></button>
136
137
  <button class="module-nav-secondary hidden" type="button" data-module="reviews"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><rect width="8" height="4" x="8" y="2" rx="1" ry="1"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><path d="m9 14 2 2 4-4"/></svg>审核</button>
137
138
  <button class="module-nav-secondary hidden" type="button" data-module="ai-settings"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><rect width="16" height="16" x="4" y="4" rx="2"/><rect width="6" height="6" x="9" y="9" rx="1"/><path d="M15 2v2"/><path d="M15 20v2"/><path d="M2 15h2"/><path d="M2 9h2"/><path d="M20 15h2"/><path d="M20 9h2"/><path d="M9 2v2"/><path d="M9 20v2"/></svg>AI 设置</button>
@@ -227,7 +228,7 @@
227
228
  <span id="chapter-stats" class="chapter-stats">0 字 · v0</span>
228
229
  <button id="insight-button" class="ghost-button" type="button" aria-controls="chapter-insight-toast" aria-expanded="false">章节概览</button>
229
230
  <button id="versions-button" class="ghost-button" type="button">版本</button>
230
- <button id="chapter-annotations-button" class="ghost-button" type="button">批注与待办</button>
231
+ <button id="chapter-annotations-button" class="ghost-button" type="button">评论</button>
231
232
  <button id="chapter-delete-button" class="danger-button" type="button">删除章节</button>
232
233
  <button id="chapter-edit-button" class="primary-button hidden" type="button">编辑</button>
233
234
  <button id="tidy-blank-lines-button" class="ghost-button" type="button">整理空行</button>
@@ -309,7 +310,11 @@
309
310
  <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>
310
311
  <div id="ai-mention-menu" class="ai-mention-menu hidden" role="listbox" aria-label="引用角色、设定或章节"></div>
311
312
  <div class="prompt-composer-actions">
312
- <output id="ai-context-meter" class="ai-context-meter is-empty" aria-live="polite" aria-label="当前上下文用量" tabindex="0"><b>—</b></output>
313
+ <button id="ai-context-meter" class="ai-context-meter is-empty" type="button" aria-haspopup="dialog" aria-expanded="false" aria-controls="ai-context-popover" aria-live="polite" aria-label="当前上下文用量"><b>—</b></button>
314
+ <section id="ai-context-popover" class="ai-context-popover hidden" role="dialog" aria-labelledby="ai-context-popover-title" aria-describedby="ai-context-popover-description">
315
+ <header class="ai-context-popover-header"><div><strong id="ai-context-popover-title">Token 分布</strong><small id="ai-context-popover-description">按当前模型上下文窗口计算</small></div><button id="ai-context-popover-close" class="ai-context-popover-close" type="button" aria-label="关闭 Token 分布">×</button></header>
316
+ <div id="ai-context-distribution" class="ai-context-distribution" role="list" aria-label="当前 Token 分布"></div>
317
+ </section>
313
318
  <button id="ai-send" class="ai-send-button" type="button" aria-label="发送消息">发送</button>
314
319
  </div>
315
320
  </div>
@@ -328,10 +333,10 @@
328
333
  <button class="danger-button" type="button" role="menuitem" data-delete-chapter>删除章节</button>
329
334
  </div>
330
335
  </div>
331
- <div id="line-citation-menu" class="line-citation-menu hidden" role="menu" aria-label="正文行引用操作">
336
+ <div id="line-citation-menu" class="line-citation-menu hidden" role="menu" aria-label="正文行操作">
332
337
  <strong id="line-citation-label">已选择正文</strong>
333
338
  <button id="add-line-citation" type="button" role="menuitem">添加到创作助手引用</button>
334
- <button id="add-line-annotation" type="button" role="menuitem">添加批注</button>
339
+ <button id="add-line-annotation" type="button" role="menuitem">添加评论</button>
335
340
  <button id="add-line-todo" type="button" role="menuitem">添加待办</button>
336
341
  </div>
337
342
  <div id="markdown-table-menu" class="markdown-table-menu hidden" role="menu" aria-label="Markdown 表格显示方式">
@@ -687,8 +692,8 @@
687
692
  </dialog>
688
693
 
689
694
  <dialog id="chapter-annotations-dialog" class="dialog wide-dialog chapter-annotations-dialog" aria-labelledby="chapter-annotations-title">
690
- <div class="dialog-header"><div><span class="eyebrow">正文协作</span><h2 id="chapter-annotations-title">批注与待办</h2><p id="chapter-annotations-meta" class="dialog-header-meta">选择正文行后可添加批注或待办。</p></div><button id="chapter-annotations-close" class="dialog-close" aria-label="关闭批注与待办" type="button">×</button></div>
691
- <div id="chapter-annotations-list" class="chapter-annotations-list"></div>
695
+ <div class="dialog-header"><div><span class="eyebrow">正文协作</span><h2 id="chapter-annotations-title">正文评论</h2><p id="chapter-annotations-meta" class="dialog-header-meta">在正文行上点击右键即可添加评论。</p></div><button id="chapter-annotations-close" class="dialog-close" aria-label="关闭正文评论" type="button">×</button></div>
696
+ <div id="chapter-annotations-list" class="chapter-annotations-list" aria-live="polite"></div>
692
697
  <div class="dialog-actions"><button id="chapter-annotations-refresh" class="ghost-button" type="button">刷新</button><button id="chapter-annotations-done" class="primary-button" type="button">完成</button></div>
693
698
  </dialog>
694
699
 
@@ -720,13 +725,14 @@
720
725
  <legend>可分页模块单页数量</legend>
721
726
  <small>以下模块独立设置,允许 10–100 条;默认均为 30 条。种族层级始终完整展示。</small>
722
727
  <div class="pagination-settings-grid">
723
- <label>草稿列表<input id="page-size-drafts" name="pageSizeDrafts" type="number" min="10" max="100" step="1" required aria-label="草稿列表单页数量"></label>
728
+ <label>想法列表<input id="page-size-drafts" name="pageSizeDrafts" type="number" min="10" max="100" step="1" required aria-label="想法列表单页数量"></label>
724
729
  <label>设定库<input id="page-size-settings" name="pageSizeSettings" type="number" min="10" max="100" step="1" required aria-label="设定库单页数量"></label>
725
730
  <label>角色列表<input id="page-size-characters" name="pageSizeCharacters" type="number" min="10" max="100" step="1" required aria-label="角色列表单页数量"></label>
726
731
  <label>组织列表<input id="page-size-organizations" name="pageSizeOrganizations" type="number" min="10" max="100" step="1" required aria-label="组织列表单页数量"></label>
727
732
  <label>时间线事件<input id="page-size-timeline" name="pageSizeTimeline" type="number" min="10" max="100" step="1" required aria-label="时间线事件单页数量"></label>
728
733
  <label>大纲与伏笔<input id="page-size-outlines" name="pageSizeOutlines" type="number" min="10" max="100" step="1" required aria-label="大纲与伏笔单页数量"></label>
729
734
  <label>人物关系列表<input id="page-size-relationships" name="pageSizeRelationships" type="number" min="10" max="100" step="1" required aria-label="人物关系列表单页数量"></label>
735
+ <label>正文评论<input id="page-size-comments" name="pageSizeComments" type="number" min="10" max="100" step="1" required aria-label="正文评论单页数量"></label>
730
736
  <label>审核列表<input id="page-size-reviews" name="pageSizeReviews" type="number" min="10" max="100" step="1" required aria-label="审核列表单页数量"></label>
731
737
  <label>AI 分析任务<input id="page-size-analysis-tasks" name="pageSizeAnalysisTasks" type="number" min="10" max="100" step="1" required aria-label="AI 分析任务单页数量"></label>
732
738
  <label>正文导入历史<input id="page-size-file-versions" name="pageSizeFileVersions" type="number" min="10" max="100" step="1" required aria-label="正文导入历史单页数量"></label>
@@ -850,6 +856,11 @@
850
856
  </div>
851
857
  <div class="ai-history-dialog-body">
852
858
  <div id="ai-history-list" class="ai-history-list" aria-live="polite"></div>
859
+ <nav id="ai-history-pagination" class="module-pagination ai-history-pagination hidden" aria-label="历史记录分页">
860
+ <button id="ai-history-previous" type="button" disabled>上一页</button>
861
+ <span id="ai-history-page-label">第 1 页</span>
862
+ <button id="ai-history-next" type="button" disabled>下一页</button>
863
+ </nav>
853
864
  </div>
854
865
  </dialog>
855
866
 
@@ -884,6 +895,6 @@
884
895
  <div id="auth-loading" class="auth-loading" role="status" aria-label="正在载入工作台"></div>
885
896
  <script id="vditorIconScript" src="/vendor/vditor/dist/js/icons/ant.js?v=3.11.2"></script>
886
897
  <script src="/vendor/vditor/dist/index.min.js?v=3.11.2"></script>
887
- <script type="module" src="/app.js?v=20260730-volume-lazy-module-cache-v1"></script>
898
+ <script type="module" src="/app.js?v=20260731-model-context-guidance-v1"></script>
888
899
  </body>
889
900
  </html>
@@ -1,4 +1,6 @@
1
1
  export const MODEL_PURPOSE_OPTIONS: ReadonlyArray<readonly [string, string]>;
2
+ export const MIN_MODEL_CONTEXT_WINDOW: number;
3
+ export const RECOMMENDED_MODEL_CONTEXT_WINDOW: number;
2
4
 
3
5
  export type ModelFormValues = {
4
6
  displayName: string;
@@ -13,6 +15,7 @@ export type ModelFormValues = {
13
15
 
14
16
  export function normalizeModelPurposes(purposes: unknown): string[];
15
17
  export function isKimiModelId(modelId: unknown): boolean;
18
+ export function modelContextWindowGuidance(value: unknown): { belowMinimum: boolean; showRecommendation: boolean };
16
19
  export function modelFormValues(model?: Record<string, unknown> | null): ModelFormValues;
17
20
  export function modelPayload(values: ModelFormValues, existingPreset?: Record<string, unknown>): Record<string, unknown> & { thinkingEnabled: boolean };
18
21
  export function modelOptionLabel(model: Record<string, unknown> | null | undefined): string;
@@ -9,6 +9,9 @@ export const MODEL_PURPOSE_OPTIONS = Object.freeze([
9
9
  ["consistency-check", "一致性校对"]
10
10
  ]);
11
11
 
12
+ export const MIN_MODEL_CONTEXT_WINDOW = 32_768;
13
+ export const RECOMMENDED_MODEL_CONTEXT_WINDOW = 128_000;
14
+
12
15
  const purposeAliases = new Map([
13
16
  ...MODEL_PURPOSE_OPTIONS.flatMap(([key, label]) => [[key, key], [label, key]]),
14
17
  ["章节分析", "chapter-analysis"],
@@ -25,6 +28,17 @@ export function isKimiModelId(modelId) {
25
28
  return String(modelId ?? "").toLowerCase().includes("kimi");
26
29
  }
27
30
 
31
+ export function modelContextWindowGuidance(value) {
32
+ const contextWindow = Number(value);
33
+ const hasNumericValue = String(value ?? "").trim() !== "" && Number.isFinite(contextWindow);
34
+ return {
35
+ belowMinimum: hasNumericValue && contextWindow < MIN_MODEL_CONTEXT_WINDOW,
36
+ showRecommendation: hasNumericValue
37
+ && contextWindow >= MIN_MODEL_CONTEXT_WINDOW
38
+ && contextWindow < RECOMMENDED_MODEL_CONTEXT_WINDOW
39
+ };
40
+ }
41
+
28
42
  export function modelFormValues(model = null) {
29
43
  const modelId = String(model?.modelId ?? "");
30
44
  const configuredTemperature = model?.preset?.temperature;
@@ -5,6 +5,7 @@ export const RESTORABLE_MODULES = Object.freeze([
5
5
  "races",
6
6
  "organizations",
7
7
  "timeline",
8
+ "comments",
8
9
  "outlines",
9
10
  "relationships",
10
11
  "reviews",
@@ -509,7 +509,7 @@ input[type="checkbox"][hidden] { display: none; }
509
509
 
510
510
  .left-panel, .ai-panel { position: relative; min-height: 0; background: var(--panel); }
511
511
  .left-panel { display: flex; flex-direction: column; border-right: 1px solid var(--line); padding: 0; overflow: hidden; }
512
- .left-panel-body { flex: 1 1 auto; min-height: 0; padding: 18px 28px 16px 14px; overflow-y: auto; scrollbar-gutter: stable; }
512
+ .left-panel-body { flex: 1 1 auto; min-height: 0; padding: 18px 24px 16px 14px; overflow-y: auto; scrollbar-gutter: stable; }
513
513
  .ai-panel { border-left: 1px solid var(--line); display: flex; flex-direction: column; padding: 14px 16px 18px; min-width: 0; }
514
514
  .panel-collapse-button { z-index: 8; flex: 0 0 auto; width: 24px; height: 24px; padding: 0; border: 1px solid var(--line); border-radius: 4px; background: var(--panel); color: var(--muted); font-size: 18px; line-height: 1; }
515
515
  .ai-heading #ai-panel-toggle { flex-basis: 30px; width: 30px; height: 30px; }
@@ -1275,7 +1275,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
1275
1275
  .character-filter-toolbar-actions { display: flex; align-items: center; justify-content: flex-end; gap: 12px; min-height: 38px; }
1276
1276
  .character-filter-result-count { color: var(--muted); font-size: 11px; white-space: nowrap; }
1277
1277
  .character-filter-toolbar-actions button:disabled { cursor: default; opacity: .45; }
1278
- .draft-filter-toolbar { display: flex; align-items: end; gap: 10px; margin-bottom: 18px; padding: 12px 14px; border: 1px solid var(--line); background: var(--surface-soft); }
1278
+ .draft-filter-toolbar { display: flex; align-items: center; gap: 10px; margin-bottom: 18px; padding: 12px 14px; border: 1px solid var(--line); background: var(--surface-soft); }
1279
1279
  .draft-filter-toolbar label { color: var(--muted); font-size: 11px; }
1280
1280
  .draft-filter-toolbar select { min-width: 180px; min-height: 38px; padding: 8px 10px; background: var(--surface); font-size: 11px; }
1281
1281
  .draft-filter-toolbar span { margin-left: auto; align-self: center; color: var(--muted); font-size: 11px; }
@@ -1301,11 +1301,30 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
1301
1301
  .card-actions .primary-button:hover, .card-actions .primary-button:focus-visible { background: var(--accent-dark); }
1302
1302
  .provider-card-grid { grid-template-columns: repeat(auto-fit, minmax(min(100%, 420px), 1fr)); }
1303
1303
  .provider-card { min-width: 0; }
1304
+ .provider-card.is-disabled { border-style: dashed; border-color: color-mix(in srgb, var(--accent) 68%, var(--line)); background: color-mix(in srgb, var(--accent) 5%, var(--surface-soft)); }
1305
+ .provider-card-meta { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px; min-width: 0; }
1306
+ .provider-card-meta small { min-width: 0; }
1307
+ .provider-status-badge, .model-status-badge { display: inline-flex; flex: 0 0 auto; align-items: center; min-height: 22px; padding: 3px 8px; border: 1px solid var(--line); border-radius: 999px; font-size: 9px; font-weight: 700; line-height: 1.1; white-space: nowrap; }
1308
+ .provider-status-badge.is-enabled { border-color: color-mix(in srgb, var(--green) 48%, var(--line)); background: color-mix(in srgb, var(--green) 10%, var(--surface)); color: var(--green); }
1309
+ .provider-status-badge.is-disabled, .model-status-badge.is-disabled { border-color: color-mix(in srgb, var(--accent) 68%, var(--line)); background: color-mix(in srgb, var(--accent) 18%, var(--surface)); color: var(--accent-dark); }
1310
+ .provider-status-badge.is-error, .model-status-badge.is-unavailable { border-color: color-mix(in srgb, var(--accent) 46%, var(--line)); background: color-mix(in srgb, var(--accent) 8%, var(--surface)); color: var(--accent-dark); }
1311
+ .provider-disabled-notice { display: flex; align-items: baseline; gap: 8px; margin: 12px 0; padding: 9px 11px; border-left: 3px solid var(--accent); background: color-mix(in srgb, var(--accent) 10%, var(--surface)); }
1312
+ .provider-disabled-notice strong { color: var(--accent-dark); font-size: 12px; }
1313
+ .provider-disabled-notice span { color: var(--muted); font-size: 10px; line-height: 1.4; }
1304
1314
  .provider-card p, .provider-card .model-pill { overflow-wrap: anywhere; word-break: break-word; }
1305
1315
  .provider-models { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
1306
- .provider-card .model-pill { max-width: 100%; margin: 0; border: 0; text-align: left; white-space: normal; cursor: pointer; }
1316
+ .provider-model-row { display: flex; align-items: center; gap: 6px; min-width: 0; max-width: 100%; }
1317
+ .provider-model-row.is-unavailable { padding: 4px 6px; border: 1px dashed color-mix(in srgb, var(--accent) 42%, var(--line)); border-radius: 4px; background: color-mix(in srgb, var(--accent) 4%, transparent); }
1318
+ .provider-card .model-pill { flex: 1 1 auto; max-width: 100%; margin: 0; border: 0; text-align: left; white-space: normal; cursor: pointer; }
1319
+ .provider-model-row .model-pill { min-width: 0; }
1320
+ .provider-model-row.is-unavailable .model-pill { color: var(--muted); opacity: .72; }
1307
1321
  .provider-card .model-pill:hover, .provider-card .model-pill:focus-visible { background: var(--surface-hover); color: var(--ink); }
1308
1322
  .provider-card .card-actions { flex-wrap: wrap; }
1323
+ .model-connection-test { display: flex; align-items: center; justify-content: space-between; gap: 14px; padding: 12px; border: 1px solid var(--line); border-radius: 4px; background: var(--surface-soft); }
1324
+ .model-connection-test > div { display: grid; gap: 4px; }
1325
+ .model-connection-test strong { color: var(--ink); font-size: 11px; }
1326
+ .model-connection-test p { margin: 0; color: var(--muted); font-size: 10px; line-height: 1.5; }
1327
+ .model-connection-test button { flex: 0 0 auto; min-height: 34px; }
1309
1328
  .pill { display: inline-block; padding: 3px 7px; margin: 0 4px 4px 0; border: 1px solid var(--line); border-radius: 12px; background: var(--surface); color: var(--muted); font-size: 9px; }
1310
1329
  .organization-links { display: flex; flex-wrap: wrap; gap: 5px; align-items: center; margin: 8px 0 6px; }.organization-links b { color: var(--muted); font-size: 9px; font-weight: 500; }.organization-links .pill { margin: 0; }.organization-empty { color: var(--muted); font-size: 9px; }.organization-pill { border: 1px solid color-mix(in srgb, var(--accent) 48%, var(--line)); color: var(--accent-dark); background: color-mix(in srgb, var(--accent) 20%, var(--surface)); }.organization-settings { margin: 10px 0 6px; }.organization-members { margin-top: 9px !important; }.relationship-keyword { white-space: nowrap; }
1311
1330
  .race-settings { display: flex; flex-wrap: wrap; gap: 5px; margin: 10px 0 6px; }.race-settings .pill { margin: 0; }.race-members { margin-top: 9px !important; }
@@ -2031,13 +2050,23 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
2031
2050
  .quick-actions button { min-width: 0; min-height: 30px; border: 1px solid var(--line); background: transparent; border-radius: 3px; padding: 5px 3px; color: var(--muted); font-size: 10px; white-space: nowrap; }
2032
2051
  .quick-actions button:hover { color: var(--ink); background: var(--paper-deep); }
2033
2052
  .ai-feed { flex: 1; min-height: 0; overflow-y: auto; margin: 10px -5px 12px; padding: 0 5px; }
2053
+ .ai-context-compaction-divider { display: grid; grid-template-columns: minmax(16px, 1fr) auto minmax(16px, 1fr); gap: 9px; align-items: center; margin: 4px 0 16px; color: var(--muted); font-size: 9px; line-height: 1; text-align: center; }
2054
+ .ai-context-compaction-divider::before, .ai-context-compaction-divider::after { height: 1px; background: color-mix(in srgb, var(--muted) 36%, transparent); content: ""; }
2055
+ .ai-context-compaction-divider span { letter-spacing: .08em; white-space: nowrap; }
2034
2056
  .assistant-message, .user-message { position: relative; margin-bottom: 12px; padding: 12px 13px; border-radius: 5px; font-size: 12px; line-height: 1.55; }
2035
2057
  .assistant-message { background: var(--paper-deep); }
2058
+ .assistant-message.is-error { border: 1px solid color-mix(in srgb, var(--accent) 42%, var(--line)); border-left: 3px solid var(--accent); background: color-mix(in srgb, var(--accent) 8%, var(--paper-deep)); }
2036
2059
  .assistant-message.has-message-actions, .user-message.has-message-actions { margin-bottom: 42px; }
2037
2060
  .user-message { margin-left: 24px; background: var(--accent); color: #fff; }
2038
2061
  .assistant-message > .message-heading, .user-message > .message-heading { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 5px; opacity: .65; font-size: 9px; }
2039
2062
  .message-heading > span { letter-spacing: .12em; }.message-heading time { flex: 0 0 auto; font-variant-numeric: tabular-nums; letter-spacing: .03em; }
2063
+ .assistant-message.is-error > .message-heading { color: var(--accent-dark); opacity: 1; }
2064
+ .assistant-message.is-error > .message-heading > span { display: inline-flex; align-items: center; gap: 7px; }
2065
+ .ai-message-status { display: inline-flex; align-items: center; min-height: 20px; padding: 2px 7px; border: 1px solid var(--line); border-radius: 999px; font-size: 9px; font-weight: 700; line-height: 1; letter-spacing: .08em; }
2066
+ .ai-message-status.is-error { border-color: var(--accent); background: var(--accent); color: #fff; }
2040
2067
  .message-body { min-width: 0; overflow-wrap: anywhere; }
2068
+ .assistant-message.is-error .message-body { font-family: inherit; font-size: inherit; line-height: inherit; }
2069
+ .assistant-message.is-error .ai-error-text { margin: 0; white-space: pre-wrap; font: inherit; }
2041
2070
  .message-card-actions { position: absolute; right: 0; bottom: -31px; display: flex; align-items: center; gap: 12px; height: 24px; }
2042
2071
  .message-card-actions button { display: inline-flex; align-items: center; gap: 4px; min-width: 0; padding: 3px 2px; border: 0; background: transparent; color: var(--muted); font-size: 9px; }
2043
2072
  .message-card-actions button:hover, .message-card-actions button:focus-visible { color: var(--accent-dark); }
@@ -2085,6 +2114,9 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
2085
2114
  .ai-process-step-body { max-height: 220px; overflow: auto; padding: 8px 9px; border-left: 2px solid color-mix(in srgb, var(--accent) 46%, var(--line)); background: var(--paper-deep); color: var(--muted); font-size: 10px; }
2086
2115
  .ai-process-intermediate-step .ai-process-step-body { border-left-color: var(--muted); }
2087
2116
  .ai-process-tool-step { padding-top: 1px; }
2117
+ .ai-process-context-compaction { display: grid; grid-template-columns: minmax(12px, 1fr) auto minmax(12px, 1fr); gap: 8px; align-items: center; min-width: 0; padding: 2px 0; color: var(--muted); font-size: 8px; line-height: 1; text-align: center; }
2118
+ .ai-process-context-compaction::before, .ai-process-context-compaction::after { height: 1px; background: color-mix(in srgb, var(--muted) 38%, transparent); content: ""; }
2119
+ .ai-process-context-compaction span { letter-spacing: .08em; white-space: nowrap; }
2088
2120
  @keyframes ai-process-pulse { 0%, 100% { opacity: .45; transform: scale(.8); } 50% { opacity: 1; transform: scale(1); } }
2089
2121
  @media (prefers-reduced-motion: reduce) { .is-streaming .ai-process-details[open] > summary::before { animation: none; } }
2090
2122
  .ai-tool-call-list { display: grid; gap: 5px; margin-top: 10px; }
@@ -2139,11 +2171,33 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
2139
2171
  .ai-prompt-reference button { width: 16px; height: 16px; padding: 0; border: 0; border-radius: 50%; background: transparent; color: inherit; font: 14px/1 var(--font-latin), monospace; }
2140
2172
  .ai-prompt-reference button:hover, .ai-prompt-reference button:focus-visible { background: color-mix(in srgb, var(--accent) 16%, transparent); color: var(--accent); }
2141
2173
  .prompt-composer-actions { position: absolute; right: 8px; bottom: 8px; display: flex; gap: 6px; align-items: center; }
2142
- .ai-context-meter { --context-usage: 0; --context-meter-color: var(--green); position: relative; display: grid; flex: 0 0 32px; place-items: center; width: 32px; height: 32px; overflow: visible; border: 0; border-radius: 50%; background: conic-gradient(var(--context-meter-color) calc(var(--context-usage) * 1%), rgba(143,132,116,.2) 0); color: var(--ink); font-family: var(--font-latin); line-height: 1; }
2174
+ .ai-context-meter { --context-usage: 0; --context-meter-color: var(--green); position: relative; display: grid; flex: 0 0 32px; place-items: center; width: 32px; height: 32px; overflow: visible; border: 0; border-radius: 50%; background: conic-gradient(var(--context-meter-color) calc(var(--context-usage) * 1%), rgba(143,132,116,.2) 0); color: var(--ink); font-family: var(--font-latin); line-height: 1; cursor: pointer; }
2143
2175
  .ai-context-meter::before { content: ""; position: absolute; inset: 3px; border-radius: 50%; background: var(--paper); box-shadow: inset 0 0 0 1px rgba(143,132,116,.12); }
2144
- .ai-context-meter::after { content: attr(data-tooltip); position: absolute; z-index: 30; right: 0; bottom: calc(100% + 8px); width: max-content; max-width: 220px; padding: 6px 8px; border: 1px solid rgba(255,255,255,.1); border-radius: 4px; background: #292724; color: #fff; font: 10px/1.35 var(--font-latin), monospace; white-space: nowrap; box-shadow: 0 6px 18px rgba(49,42,32,.25); opacity: 0; pointer-events: none; transform: translateY(3px); transition: opacity .12s ease, transform .12s ease; }
2145
- .ai-context-meter:hover::after, .ai-context-meter:focus-visible::after { opacity: 1; transform: translateY(0); }
2176
+ .ai-context-meter:hover, .ai-context-meter:focus-visible, .ai-context-meter[aria-expanded="true"] { outline: 0; filter: brightness(.96); }
2177
+ .ai-context-meter:focus-visible { box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent); }
2146
2178
  .ai-context-meter b { position: relative; z-index: 1; font-size: 9px; font-weight: 600; }.ai-context-meter.is-warning { --context-meter-color: #bf6a35; }.ai-context-meter.is-danger { --context-meter-color: #ad463c; }.ai-context-meter.is-empty { opacity: .62; }
2179
+ .ai-context-popover { position: absolute; right: 0; bottom: calc(100% + 10px); z-index: 40; display: grid; width: min(320px, calc(100vw - 32px)); gap: 12px; padding: 14px; border: 1px solid var(--line); border-radius: 7px; background: var(--paper); color: var(--ink); box-shadow: 0 14px 38px rgba(36, 30, 24, .18); }
2180
+ .ai-context-popover.hidden { display: none; }
2181
+ .ai-context-popover::after { position: absolute; right: 71px; bottom: -6px; width: 10px; height: 10px; border-right: 1px solid var(--line); border-bottom: 1px solid var(--line); background: var(--paper); content: ""; transform: rotate(45deg); }
2182
+ .ai-context-popover-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; padding-bottom: 10px; border-bottom: 1px solid var(--line); }
2183
+ .ai-context-popover-header > div { display: grid; gap: 3px; min-width: 0; }
2184
+ .ai-context-popover-header strong { font-size: 12px; }
2185
+ .ai-context-popover-header small { color: var(--muted); font: 9px/1.4 var(--font-latin), var(--font-cjk), sans-serif; }
2186
+ .ai-context-popover-close { flex: 0 0 auto; width: 24px; height: 24px; padding: 0; border: 1px solid var(--line); border-radius: 4px; background: transparent; color: var(--muted); font: 17px/1 var(--font-latin), sans-serif; }
2187
+ .ai-context-popover-close:hover, .ai-context-popover-close:focus-visible { border-color: var(--accent); color: var(--accent-dark); outline: 0; }
2188
+ .ai-context-distribution { display: grid; gap: 10px; }
2189
+ .ai-context-distribution-row { display: grid; gap: 5px; }
2190
+ .ai-context-distribution-label { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; font: 10px/1.3 var(--font-latin), sans-serif; }
2191
+ .ai-context-distribution-label > span { min-width: 0; color: var(--ink); }
2192
+ .ai-context-distribution-label small { color: var(--muted); font: 9px/1.3 var(--font-cjk), sans-serif; }
2193
+ .ai-context-distribution-label strong { flex: 0 0 auto; color: var(--muted); font: 10px/1.3 var(--font-latin), monospace; font-weight: 500; }
2194
+ .ai-context-distribution-track { height: 5px; overflow: hidden; border-radius: 3px; background: color-mix(in srgb, var(--muted) 14%, transparent); }
2195
+ .ai-context-distribution-track > span { display: block; width: calc(var(--distribution-percent) * 1%); height: 100%; min-width: 0; border-radius: inherit; background: var(--distribution-color, var(--accent)); transition: width .16s ease; }
2196
+ .ai-context-distribution-row[data-key="system-prompt"] { --distribution-color: var(--accent); }
2197
+ .ai-context-distribution-row[data-key="function"] { --distribution-color: #9a7651; }
2198
+ .ai-context-distribution-row[data-key="skills"] { --distribution-color: var(--muted); }
2199
+ .ai-context-distribution-row[data-key="context"] { --distribution-color: var(--green); }
2200
+ .ai-context-distribution-row[data-key="left"] { --distribution-color: color-mix(in srgb, var(--muted) 55%, transparent); }
2147
2201
  .ai-send-button { min-width: 54px; height: 32px; padding: 0 11px; border: 0; border-radius: 4px; background: var(--accent); color: #fff; font-size: 10px; font-weight: 600; box-shadow: 0 4px 12px rgba(139,61,44,.2); }
2148
2202
  .ai-send-button:hover { background: var(--accent-dark); }.ai-send-button:disabled { cursor: wait; opacity: .62; }
2149
2203
  .ai-citations { display: grid; gap: 6px; max-height: 190px; overflow-y: auto; margin-bottom: 8px; }
@@ -2256,14 +2310,17 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
2256
2310
  .markdown-table-menu button small { color: var(--muted); font-size: 9px; }
2257
2311
  .chapter-annotations-dialog { width: min(820px, 94vw); }
2258
2312
  .chapter-annotations-list { display: grid; gap: 10px; max-height: 62vh; overflow: auto; padding: 18px 24px; }
2313
+ .chapter-comment-module-list { display: grid; gap: 10px; }
2259
2314
  .chapter-annotation-card { display: grid; gap: 8px; padding: 13px; border: 1px solid var(--line); border-radius: 6px; background: var(--surface-soft); }
2260
2315
  .chapter-annotation-card.is-resolved { opacity: .65; }
2261
2316
  .chapter-annotation-card header { display: flex; align-items: center; gap: 8px; }
2262
2317
  .chapter-annotation-card header strong { font-size: 11px; }
2318
+ .chapter-annotation-card header .chapter-annotation-source { min-width: 0; flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2319
+ .chapter-annotation-line { flex: none; color: var(--muted); font-family: var(--font-latin), monospace; font-size: 9px; }
2263
2320
  .chapter-annotation-card header em { margin-left: auto; color: var(--muted); font-size: 9px; font-style: normal; }
2264
2321
  .chapter-annotation-kind { padding: 2px 6px; border: 1px solid var(--accent); border-radius: 10px; color: var(--accent-dark); font-size: 8px; }
2265
- .chapter-annotation-card blockquote { max-height: 100px; overflow: auto; margin: 0; padding: 8px 10px; border-left: 3px solid var(--line); background: var(--surface); color: var(--muted); font: 10px/1.6 var(--font-mono), monospace; white-space: pre-wrap; }
2266
- .chapter-annotation-card p { margin: 0; font-size: 12px; line-height: 1.6; white-space: pre-wrap; }
2322
+ .chapter-annotation-card blockquote { max-height: 100px; overflow: auto; margin: 0; padding: 8px 10px; border-left: 3px solid var(--line); background: var(--surface); color: var(--muted); font-family: var(--font-mono), monospace; font-size: 10px; line-height: 1.6; white-space: pre-wrap; }
2323
+ .chapter-annotation-card p { margin: 0; font-size: 12px; line-height: 1.6; overflow-wrap: anywhere; white-space: pre-wrap; }
2267
2324
  .chapter-annotation-card > small { color: var(--muted); font-size: 9px; }
2268
2325
  .chapter-annotation-card footer { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 7px; }
2269
2326
  .chapter-annotation-card footer button { min-height: 30px; padding: 5px 9px; font-size: 9px; }
@@ -2384,7 +2441,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
2384
2441
  .relationship-source-preview-match { padding: 2px 6px; border-radius: 10px; background: color-mix(in srgb, var(--accent) 10%, transparent); color: var(--accent-dark); font-size: 8px; white-space: nowrap; }
2385
2442
  .relationship-source-preview-match.is-fuzzy { background: color-mix(in srgb, var(--warning, #a56f19) 12%, transparent); color: var(--warning, #8b5c10); }
2386
2443
  .relationship-source-preview-empty { margin: 0; padding: 9px; border: 1px dashed var(--line); border-radius: 4px; }
2387
- .model-temperature-hint { margin: 0; color: var(--accent-dark); font-size: 10px; line-height: 1.55; }
2444
+ .model-temperature-hint, .model-context-window-hint { margin: 0; color: var(--accent-dark); font-size: 10px; line-height: 1.55; }
2388
2445
  .item-list-rows { display: grid; gap: 7px; }
2389
2446
  .item-list-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 7px; }
2390
2447
  .item-list-row button, .item-list-add { border: 1px solid var(--line); border-radius: 4px; background: var(--panel); color: var(--muted); font-size: 10px; }
@@ -2894,9 +2951,9 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
2894
2951
  .brand-block .brand-mark { width: 30px; height: 30px; }
2895
2952
  .brand-block strong { font-size: 14px; }
2896
2953
  .brand-block small { display: none; }
2897
- .mobile-module-tab { position: fixed; z-index: 32; top: 132px; right: 0; display: grid; width: 34px; height: 42px; padding: 0; place-items: center; border: 1px solid var(--line); border-right: 0; border-radius: 5px 0 0 5px; background: var(--surface-soft); color: var(--accent-dark); font-size: 10px; writing-mode: vertical-rl; }
2954
+ .mobile-module-tab { position: fixed; z-index: 38; top: 132px; right: 0; display: grid; width: 34px; height: 42px; padding: 0; place-items: center; border: 1px solid var(--line); border-right: 0; border-radius: 5px 0 0 5px; background: var(--surface-soft); color: var(--accent-dark); font-size: 10px; writing-mode: vertical-rl; }
2898
2955
  .app-shell.shelf-mode .mobile-module-tab { display: none; }
2899
- .mobile-panel-backdrop { position: fixed; z-index: 30; inset: 0; width: 100%; height: 100%; padding: 0; border: 0; background: rgba(20, 17, 14, .28); }
2956
+ .mobile-panel-backdrop { position: fixed; z-index: 36; inset: 0; width: 100%; height: 100%; padding: 0; border: 0; background: rgba(20, 17, 14, .28); }
2900
2957
  .app-shell:not(.shelf-mode):not(.left-panel-collapsed) .mobile-panel-backdrop { display: block; }
2901
2958
  .work-meta {
2902
2959
  grid-column: 1 / -1;
@@ -2921,7 +2978,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
2921
2978
 
2922
2979
  .left-panel {
2923
2980
  position: fixed;
2924
- z-index: 31;
2981
+ z-index: 37;
2925
2982
  top: 0;
2926
2983
  bottom: 0;
2927
2984
  right: 0;
@@ -2970,7 +3027,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
2970
3027
  .app-shell.shelf-mode .main-panel { grid-column: 1; grid-row: 2; min-height: 0; }
2971
3028
  .ai-panel {
2972
3029
  position: fixed;
2973
- z-index: 25;
3030
+ z-index: 35;
2974
3031
  right: 0;
2975
3032
  bottom: 0;
2976
3033
  left: 0;
@@ -3044,7 +3101,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
3044
3101
  .dialog-header { padding: 14px 16px 12px; }
3045
3102
  .dialog-header h2 { font-size: 20px; }
3046
3103
  .settings-dialog-header-actions { gap: 6px; }
3047
- .dialog-fields, .appearance-grid, .access-dialog-body, .account-settings-body, .versions-list, .entity-history-list { padding-inline: 16px; }
3104
+ .dialog-fields, .appearance-grid, .access-dialog-body, .account-settings-body, .versions-list, .entity-history-list, .chapter-annotations-list { padding-inline: 16px; }
3048
3105
  .task-detail-overview { grid-template-columns: minmax(0, 1fr); padding: 12px; }
3049
3106
  .task-detail-overview > * { grid-column: 1 !important; }
3050
3107
  .task-result-metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@@ -3070,6 +3127,8 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
3070
3127
  .dialog-submit-status { padding: 10px 16px 4px; }
3071
3128
  .dialog-actions { flex-wrap: wrap; padding: 12px 16px 16px; }
3072
3129
  .dialog-actions > button { flex: 1 1 120px; }
3130
+ .model-connection-test { align-items: stretch; flex-direction: column; }
3131
+ .model-connection-test button { width: 100%; }
3073
3132
  .search-form { grid-template-columns: minmax(0, 1fr); }
3074
3133
  .search-form .primary-button { width: 100%; }
3075
3134
  .appearance-grid { grid-template-columns: minmax(0, 1fr); }
@@ -1,5 +1,5 @@
1
1
  export type WorkPermissionModule = "prose" | "drafts" | "settings" | "characters" | "races" | "organizations" | "timeline" | "relationships" | "outlines" | "reviews" | "ai-chat" | "ai-analysis" | "ai-settings";
2
- export type WorkUiModule = "editor" | "drafts" | "settings" | "characters" | "races" | "organizations" | "timeline" | "relationships" | "outlines" | "reviews" | "tasks" | "ai-settings";
2
+ export type WorkUiModule = "editor" | "drafts" | "settings" | "characters" | "races" | "organizations" | "timeline" | "comments" | "relationships" | "outlines" | "reviews" | "tasks" | "ai-settings";
3
3
  export type WorkModuleAccess = "none" | "read" | "write";
4
4
  export type WorkModulePermissions = Record<WorkPermissionModule, WorkModuleAccess>;
5
5
  export type WorkPermissionAware = { accessRole?: string | null; modulePermissions?: WorkModulePermissions };
@@ -1,6 +1,6 @@
1
1
  export const WORK_PERMISSION_MODULES = Object.freeze([
2
2
  { id: "prose", uiModule: "editor", label: "正文" },
3
- { id: "drafts", uiModule: "drafts", label: "草稿" },
3
+ { id: "drafts", uiModule: "drafts", label: "想法" },
4
4
  { id: "settings", uiModule: "settings", label: "设定库" },
5
5
  { id: "characters", uiModule: "characters", label: "角色" },
6
6
  { id: "races", uiModule: "races", label: "种族" },