@musnows/scriverse 1.0.0 → 1.0.2

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 { buildRelationshipGraph, createGalaxyRenderer, normalizeGalaxyFrameRate, normalizeGalaxyMotionMode, renderRelationshipMindMap } from "/relationship-graph.js?v=20260817-relationship-canvas-scale-v1&feature=galaxy-motion-mode-v3&feature=galaxy-edge-label-threshold-v1";
2
2
  import { formatDateTime, normalizeParagraphSpacing } from "/text-formatting.js?v=20260713-saved-at-seconds";
3
3
  import { renderMarkdown } from "/markdown.js?v=20260830-adjacent-blockquotes-v1";
4
- import { createImWorkspace } from "/im.js?v=20260903-global-im-v103";
4
+ import { createImWorkspace } from "/im.js?v=20260904-im-judge-outcomes-v106";
5
5
  import { findAiMention, listAiMentionOptions, mergeAiReferenceScope, userMessageMentionNames } from "/ai-mentions.js?v=20260811-user-message-mentions-v1";
6
6
  import { applyAiSkillCommand, findAiSkillCommand, listAiSkillOptions } from "/ai-skill-menu.js?v=20260830-ai-skill-slash-menu-v1";
7
7
  import {
@@ -9825,7 +9825,7 @@ const moduleMeta = {
9825
9825
  comments: ["正文协作", "正文评论与待办", "集中查看并处理当前作品所有章节的评论与待办。", ""],
9826
9826
  reviews: ["作者决策", "审核队列", "集中处理冲突、候选设定、低置信度关系和时间问题。", "新增审核项"],
9827
9827
  tasks: ["AI 深度分析", "AI 分析中心", "对全书或指定章节运行人物关系、世界观、设定、事件与一致性分析。", "开始 AI 分析"],
9828
- "ai-settings": ["书籍提示词", "本书 AI 设置", "本书系统提示词会追加在内置提示词和平台全局提示词之后;任务默认模型只作用于当前作品。", "保存设置"]
9828
+ "ai-settings": ["书籍提示词", "本书 AI 设置", "本书系统提示词默认追加在内置提示词和平台全局提示词之后;可在折叠的高级设置中改为彻底覆写。任务默认模型只作用于当前作品。", "保存设置"]
9829
9829
  };
9830
9830
 
9831
9831
  async function showModule(module) {
@@ -13423,6 +13423,53 @@ function startBackgroundTaskCenter(workId) {
13423
13423
  void refreshBackgroundTaskCenter();
13424
13424
  }
13425
13425
 
13426
+ function systemPromptOverrideDetails({ inputId, checked, scope }) {
13427
+ const platformScope = scope === "platform";
13428
+ const consequence = platformScope
13429
+ ? "保存后,普通 AI 请求只会把上方平台提示词作为 system 消息;Scriverse 内置规则、本书提示词、工具指引和时间信息都不会发送。"
13430
+ : "保存后,本书的普通 AI 请求只会把上方本书提示词作为 system 消息;Scriverse 内置规则、平台提示词、工具指引和时间信息都不会发送。";
13431
+ return `<details class="system-prompt-override-settings${checked ? " is-enabled" : ""}" ${checked ? "open" : ""}><summary><span>高级设置</span><strong data-system-prompt-override-state>${checked ? "已选择覆写" : "默认追加"}</strong></summary><div class="system-prompt-override-body"><label class="checkbox-field config-checkbox-field"><input id="${inputId}" type="checkbox" ${checked ? "checked" : ""} aria-describedby="${inputId}-warning">彻底覆写系统提示词</label><div id="${inputId}-warning" class="system-prompt-override-warning" role="alert"><strong>高风险设置</strong><p>${consequence} 空提示词会让请求不携带 system 消息,可能导致上下文、工具和安全约束失效。</p></div></div></details>`;
13432
+ }
13433
+
13434
+ function syncSystemPromptOverridePresentation(input) {
13435
+ const details = input.closest(".system-prompt-override-settings");
13436
+ details?.classList.toggle("is-enabled", input.checked);
13437
+ const stateLabel = details?.querySelector("[data-system-prompt-override-state]");
13438
+ if (stateLabel) stateLabel.textContent = input.checked ? "已选择覆写" : "默认追加";
13439
+ }
13440
+
13441
+ const systemPromptOverrideConfirmationByInput = new WeakMap();
13442
+
13443
+ async function confirmSystemPromptOverride(input, scopeLabel) {
13444
+ if (!input.checked) {
13445
+ syncSystemPromptOverridePresentation(input);
13446
+ return;
13447
+ }
13448
+ const pendingConfirmation = systemPromptOverrideConfirmationByInput.get(input);
13449
+ if (pendingConfirmation) return pendingConfirmation;
13450
+ input.disabled = true;
13451
+ const confirmation = (async () => {
13452
+ const confirmed = await confirmToast(
13453
+ `开启后,${scopeLabel}提示词将彻底替换 Scriverse 内置系统提示词,模型不会收到内置规则、工具指引和其他追加提示。错误配置可能导致功能异常或安全约束失效,确认继续吗?`,
13454
+ { title: "确认开启系统提示词覆写", confirmLabel: "确认开启" }
13455
+ );
13456
+ if (!confirmed) input.checked = false;
13457
+ syncSystemPromptOverridePresentation(input);
13458
+ })().finally(() => {
13459
+ input.disabled = false;
13460
+ if (systemPromptOverrideConfirmationByInput.get(input) === confirmation) {
13461
+ systemPromptOverrideConfirmationByInput.delete(input);
13462
+ }
13463
+ });
13464
+ systemPromptOverrideConfirmationByInput.set(input, confirmation);
13465
+ return confirmation;
13466
+ }
13467
+
13468
+ async function waitForSystemPromptOverrideConfirmation(input) {
13469
+ const confirmation = systemPromptOverrideConfirmationByInput.get(input);
13470
+ if (confirmation) await confirmation;
13471
+ }
13472
+
13426
13473
  async function renderPlatformAiConfig() {
13427
13474
  const [providers, models, settings, protocolOptions] = await Promise.all([
13428
13475
  api("/api/platform/ai/providers"),
@@ -13440,12 +13487,23 @@ async function renderPlatformAiConfig() {
13440
13487
  const available = isSelectableModel({ ...model, providerStatus: provider?.status, providerConnectionStatus: provider?.connectionStatus });
13441
13488
  return `<option value="${esc(model.id)}" ${model.id === settings.imageToolModelId ? "selected" : ""} ${available || model.id === settings.imageToolModelId ? "" : "disabled"}>${esc(`${available ? "" : "不可用 · "}${modelOptionLabel({ ...model, providerName: model.providerName || provider?.name })}`)}</option>`;
13442
13489
  }).join("");
13443
- host.innerHTML = `<section class="config-section platform-system-prompt-section"><div class="config-section-header"><div><h2>平台全局系统提示词</h2><p>会追加在内置系统提示词之后,并在所有作品的专属提示词之前发送给模型。</p></div></div><div class="field-label"><textarea id="platform-system-prompt" rows="7" aria-label="全局系统提示词" placeholder="例如:默认使用简体中文,避免代替作者做最终决定。">${esc(settings.systemPrompt)}</textarea></div><div class="card-actions"><button id="save-platform-system-prompt" class="primary-button">保存全局提示词</button></div></section><section class="config-section platform-image-tool-section"><div class="config-section-header"><div><h2>多模态读图默认模型</h2><p>Agent 的 image 工具使用这里配置的模型读取设定库图片;作品可以在自己的 AI 设置中覆盖此选择。</p></div></div><div class="platform-image-tool-panel"><label class="platform-image-tool-field"><span>当前平台默认模型</span><select id="platform-image-tool-model" aria-label="平台多模态读图默认模型"><option value="">未配置</option>${imageModelOptions}</select></label><button id="save-platform-image-tool-model" class="ghost-button config-save-button" type="button">保存默认模型</button></div></section><section class="config-section platform-stream-timeout-section"><div class="config-section-header"><div><h2>AI 流事件空闲超时</h2><p>首个流事件或相邻流事件在此时间内没有新数据时,请求会被关闭。默认 90 秒,最低 30 秒,最高 600 秒。</p></div></div><div class="platform-stream-timeout-panel"><label class="platform-stream-timeout-field"><span>超时时间(秒)</span><input id="platform-ai-stream-idle-timeout" type="number" min="30" max="600" step="1" value="${esc(String(settings.streamIdleTimeoutSeconds ?? 90))}" aria-label="AI 流事件空闲超时时间(秒)"></label><button id="save-platform-ai-stream-idle-timeout" class="ghost-button config-save-button" type="button">保存流超时设置</button></div></section><section class="config-section platform-providers-section"><div class="config-section-header"><div><h2>模型供应商配置</h2><p>管理供应商连接、模型列表和连接状态;模型的多模态能力在对应模型配置中设置。</p></div></div>${renderProviderCards(providers, models, protocolOptions)}</section>`;
13490
+ host.innerHTML = `<section class="config-section platform-system-prompt-section"><div class="config-section-header"><div><h2>平台全局系统提示词</h2><p>默认追加在内置系统提示词之后,并在所有作品的专属提示词之前发送给模型。</p></div></div><div class="field-label"><textarea id="platform-system-prompt" rows="7" aria-label="全局系统提示词" placeholder="例如:默认使用简体中文,避免代替作者做最终决定。">${esc(settings.systemPrompt)}</textarea></div>${systemPromptOverrideDetails({ inputId: "platform-system-prompt-override", checked: settings.systemPromptOverride === true, scope: "platform" })}<div class="card-actions"><button id="save-platform-system-prompt" class="primary-button">保存全局提示词</button></div></section><section class="config-section platform-image-tool-section"><div class="config-section-header"><div><h2>多模态读图默认模型</h2><p>Agent 的 image 工具使用这里配置的模型读取设定库图片;作品可以在自己的 AI 设置中覆盖此选择。</p></div></div><div class="platform-image-tool-panel"><label class="platform-image-tool-field"><span>当前平台默认模型</span><select id="platform-image-tool-model" aria-label="平台多模态读图默认模型"><option value="">未配置</option>${imageModelOptions}</select></label><button id="save-platform-image-tool-model" class="ghost-button config-save-button" type="button">保存默认模型</button></div></section><section class="config-section platform-stream-timeout-section"><div class="config-section-header"><div><h2>AI 流事件空闲超时</h2><p>首个流事件或相邻流事件在此时间内没有新数据时,请求会被关闭。默认 90 秒,最低 30 秒,最高 600 秒。</p></div></div><div class="platform-stream-timeout-panel"><label class="platform-stream-timeout-field"><span>超时时间(秒)</span><input id="platform-ai-stream-idle-timeout" type="number" min="30" max="600" step="1" value="${esc(String(settings.streamIdleTimeoutSeconds ?? 90))}" aria-label="AI 流事件空闲超时时间(秒)"></label><button id="save-platform-ai-stream-idle-timeout" class="ghost-button config-save-button" type="button">保存流超时设置</button></div></section><section class="config-section platform-providers-section"><div class="config-section-header"><div><h2>模型供应商配置</h2><p>管理供应商连接、模型列表和连接状态;模型的多模态能力在对应模型配置中设置。</p></div></div>${renderProviderCards(providers, models, protocolOptions)}</section>`;
13491
+ $("#platform-system-prompt-override").addEventListener("change", (event) => {
13492
+ void confirmSystemPromptOverride(event.currentTarget, "平台全局");
13493
+ });
13444
13494
  $("#save-platform-system-prompt").addEventListener("click", async () => {
13445
13495
  const button = $("#save-platform-system-prompt");
13446
13496
  button.disabled = true;
13447
13497
  try {
13448
- await api("/api/platform/ai/settings", { method: "PATCH", body: { systemPrompt: $("#platform-system-prompt").value } });
13498
+ const overrideInput = $("#platform-system-prompt-override");
13499
+ await waitForSystemPromptOverrideConfirmation(overrideInput);
13500
+ await api("/api/platform/ai/settings", {
13501
+ method: "PATCH",
13502
+ body: {
13503
+ systemPrompt: $("#platform-system-prompt").value,
13504
+ systemPromptOverride: overrideInput.checked
13505
+ }
13506
+ });
13449
13507
  toast("平台全局系统提示词已保存");
13450
13508
  } catch (error) {
13451
13509
  toast(error.message, "error");
@@ -13861,6 +13919,18 @@ async function renderBookAiSettings() {
13861
13919
  const available = isAvailableConfiguredModel(model);
13862
13920
  return `<option value="${esc(model.id)}" ${model.id === selectedId ? "selected" : ""} ${available || model.id === selectedId ? "" : "disabled"}>${esc(`${available ? "" : "不可用 · "}${modelOptionLabel(model)}`)}</option>`;
13863
13921
  }).join("");
13922
+ const workSystemPrompt = host.querySelector("#work-system-prompt");
13923
+ const workSystemPromptDescription = workSystemPromptSection?.querySelector(".config-section-header p");
13924
+ if (workSystemPromptDescription) {
13925
+ workSystemPromptDescription.textContent = `默认追加在内置系统提示词和平台全局系统提示词之后,只影响《${state.work.title}》的 AI 请求。`;
13926
+ }
13927
+ workSystemPrompt?.closest(".field-label")?.insertAdjacentHTML(
13928
+ "afterend",
13929
+ systemPromptOverrideDetails({ inputId: "work-system-prompt-override", checked: settings.systemPromptOverride === true, scope: "work" })
13930
+ );
13931
+ $("#work-system-prompt-override").addEventListener("change", (event) => {
13932
+ void confirmSystemPromptOverride(event.currentTarget, "本书");
13933
+ });
13864
13934
  const semanticSection = `<section id="semantic-search-settings" class="config-section semantic-search-settings"><div class="config-section-header"><div><h2>主动语义检索(RAG)</h2><p>与拼音索引并列维护。Embedding 和 rerank 复用平台供应商的端点与凭证保险库;普通聊天、续写、润色和分析不会自动调用此通道。</p></div></div><div class="semantic-settings-grid"><label class="checkbox-field config-checkbox-field semantic-enabled-field"><input id="semantic-search-enabled" type="checkbox" ${settings.semanticSearchEnabled ? "checked" : ""}>启用主动语义检索</label><label>Embedding 模型<select id="semantic-embedding-model" aria-label="RAG Embedding 模型"><option value="">请选择 embedding 模型</option>${semanticModelOptions("embedding", settings.semanticEmbeddingModelId)}</select></label><label>Rerank 模型(可选)<select id="semantic-rerank-model" aria-label="RAG Rerank 模型"><option value="">不使用 rerank</option>${semanticModelOptions("rerank", settings.semanticRerankModelId)}</select></label><label>向量维度<input id="semantic-vector-dimension" type="number" min="1" max="65536" value="${esc(String(settings.semanticVectorDimension ?? 1024))}"></label><label>语义召回数量<input id="semantic-recall-limit" type="number" min="1" max="200" value="${esc(String(settings.semanticRecallLimit ?? 20))}"></label><label>展示结果数量<input id="semantic-result-limit" type="number" min="1" max="100" value="${esc(String(settings.semanticResultLimit ?? 12))}"></label><label>上下文预算(Token)<input id="semantic-budget-tokens" type="number" min="256" max="100000" value="${esc(String(settings.semanticBudgetTokens ?? 4000))}"></label><label>RRF 语义通道权重<input id="semantic-channel-weight" type="number" min="0.1" max="5" step="0.1" value="${esc(String(settings.semanticChannelWeight ?? 1))}"></label></div><p class="usage-measurement-note">API Key 由所选模型所属供应商的凭证保险库加密保存;此页面不会读取或返回明文密钥。更换端点、模型、维度或分片规则后必须完整重建,旧向量不会继续参与检索。</p><div id="semantic-search-index-status" role="status" aria-live="polite">${semanticIndexStatusMarkup(semanticIndex)}</div><div class="relationship-index-actions"><button id="save-semantic-search-settings" class="primary-button config-save-button" type="button">保存 RAG 配置</button><button id="sync-semantic-search-index" class="ghost-button config-save-button" type="button">同步增量</button><button id="refresh-semantic-search-index" class="ghost-button" type="button">刷新状态</button><button id="rebuild-semantic-search-index" class="ghost-button config-save-button" type="button">完整重建 RAG</button></div></section>`;
13865
13935
  const relationshipSection = [...host.querySelectorAll(".config-section")].find((section) => section.querySelector("h2")?.textContent === "人物关系拼音索引");
13866
13936
  relationshipSection?.insertAdjacentHTML("afterend", semanticSection);
@@ -14123,7 +14193,15 @@ async function renderBookAiSettings() {
14123
14193
  const button = $("#save-work-system-prompt");
14124
14194
  button.disabled = true;
14125
14195
  try {
14126
- await api(`/api/works/${state.work.id}/ai-settings`, { method: "PATCH", body: { systemPrompt: $("#work-system-prompt").value } });
14196
+ const overrideInput = $("#work-system-prompt-override");
14197
+ await waitForSystemPromptOverrideConfirmation(overrideInput);
14198
+ await api(`/api/works/${state.work.id}/ai-settings`, {
14199
+ method: "PATCH",
14200
+ body: {
14201
+ systemPrompt: $("#work-system-prompt").value,
14202
+ systemPromptOverride: overrideInput.checked
14203
+ }
14204
+ });
14127
14205
  toast("本书系统提示词已保存");
14128
14206
  setAiContextMeter(null);
14129
14207
  } catch (error) {
@@ -42,6 +42,10 @@ export function mergeImFailedReplyPages(
42
42
  ...pages: Array<Array<Record<string, unknown>>>
43
43
  ): Array<Record<string, unknown>>;
44
44
 
45
+ export function mergeImTriggeredTurnPages(
46
+ ...pages: Array<Array<Record<string, unknown>>>
47
+ ): Array<Record<string, unknown>>;
48
+
45
49
  export function imMessageSequenceBounds(messages: Array<Record<string, unknown>>): {
46
50
  minimum: number;
47
51
  maximum: number;
package/dist/public/im.js CHANGED
@@ -134,10 +134,14 @@ export function mergeImMessagePages(previousMessages, ...nextPages) {
134
134
  }
135
135
 
136
136
  export function mergeImFailedReplyPages(...pages) {
137
+ return mergeImTriggeredTurnPages(...pages);
138
+ }
139
+
140
+ export function mergeImTriggeredTurnPages(...pages) {
137
141
  const byId = new Map();
138
142
  for (const reply of pages.flatMap(array)) byId.set(String(reply?.id || ""), reply);
139
143
  byId.delete("");
140
- return [...byId.values()].sort((left, right) => Number(left.triggerSequence) - Number(right.triggerSequence)
144
+ return [...byId.values()].sort((left, right) => Number(left.triggerSequence ?? left.sourceSequence) - Number(right.triggerSequence ?? right.sourceSequence)
141
145
  || String(left.createdAt || "").localeCompare(String(right.createdAt || ""))
142
146
  || String(left.id || "").localeCompare(String(right.id || "")));
143
147
  }
@@ -220,6 +224,7 @@ export function createImWorkspace({ api, esc, renderMarkdown, toast, confirmToas
220
224
  let settings = null;
221
225
  let eventSource = null;
222
226
  const provisionalReplies = new Map();
227
+ const provisionalJudges = new Map();
223
228
  const conversationDrafts = new Map();
224
229
  const groupSettingsDrafts = new Map();
225
230
  let mentionOptions = [];
@@ -561,6 +566,52 @@ export function createImWorkspace({ api, esc, renderMarkdown, toast, confirmToas
561
566
  }
562
567
  }
563
568
 
569
+ function upsertProvisionalJudge(payload) {
570
+ const turnId = String(payload?.turnId || payload?.id || "");
571
+ if (!turnId) return null;
572
+ const previous = provisionalJudges.get(turnId) || {};
573
+ const eventCharacter = value(payload, "character", {});
574
+ const characterId = String(payload?.characterId || eventCharacter.characterId || previous.characterId || "");
575
+ const character = activeCharacters().find((item) => item.characterId === characterId);
576
+ const eventError = value(payload, "error", {});
577
+ const next = {
578
+ ...previous,
579
+ turnId,
580
+ chainId: String(payload?.chainId || previous.chainId || ""),
581
+ sourceMessageId: String(payload?.sourceMessageId || previous.sourceMessageId || ""),
582
+ characterId,
583
+ name: eventCharacter.name || character?.name || previous.name || "角色",
584
+ avatarUrl: eventCharacter.avatarUrl || character?.avatarUrl || previous.avatarUrl || null,
585
+ status: String(payload?.status || previous.status || "pending"),
586
+ selected: payload?.selected === true,
587
+ error: eventError.message || payload?.failure || previous.error || ""
588
+ };
589
+ provisionalJudges.set(turnId, next);
590
+ return next;
591
+ }
592
+
593
+ function syncProvisionalJudges() {
594
+ const previous = new Map(provisionalJudges);
595
+ provisionalJudges.clear();
596
+ for (const turn of array(current?.judgeOutcomes)) {
597
+ const retained = previous.get(String(turn.id));
598
+ const next = upsertProvisionalJudge({ ...retained, ...turn, turnId: turn.id });
599
+ if (next && retained?.error && !next.error) next.error = retained.error;
600
+ }
601
+ for (const turn of array(current?.activeChain?.judges)) {
602
+ if (turn.selected === true) continue;
603
+ const retained = previous.get(String(turn.id));
604
+ const next = upsertProvisionalJudge({ ...retained, ...turn, turnId: turn.id });
605
+ if (next && retained?.error && !next.error) next.error = retained.error;
606
+ }
607
+ }
608
+
609
+ function clearPendingProvisionalJudges() {
610
+ for (const [turnId, judge] of provisionalJudges) {
611
+ if (["pending", "running"].includes(String(judge.status))) provisionalJudges.delete(turnId);
612
+ }
613
+ }
614
+
564
615
  function provisionalReplyBodyHtml(reply) {
565
616
  const failed = ["failed", "skipped"].includes(reply.status);
566
617
  const pendingCopy = reply.status === "pending" ? "等待角色开始回答…" : "正在组织回答…";
@@ -569,10 +620,27 @@ export function createImWorkspace({ api, esc, renderMarkdown, toast, confirmToas
569
620
  return `${content}${failure}`;
570
621
  }
571
622
 
623
+ function provisionalJudgeStatusLabel(judge) {
624
+ if (judge.status === "pending") return "等待判断";
625
+ if (judge.status === "running") return "模型判断中";
626
+ if (judge.status === "completed") return "无需回答";
627
+ if (judge.status === "cancelled") return "判断已取消";
628
+ return "判断失败";
629
+ }
630
+
631
+ function provisionalJudgeBodyHtml(judge) {
632
+ if (["failed", "cancelled"].includes(judge.status)) {
633
+ return `<p class="im-provisional-error">${esc(judge.error || "模型判断失败")}</p>`;
634
+ }
635
+ if (judge.status === "completed") return '<p class="im-provisional-placeholder">模型判断当前无需回答</p>';
636
+ return '<p class="im-provisional-placeholder">模型正在判断是否回答…</p>';
637
+ }
638
+
572
639
  function syncGeneratingSummary() {
573
640
  const count = [...provisionalReplies.values()].filter((reply) => ["pending", "running"].includes(reply.status)).length;
574
641
  const summary = feed.querySelector(".im-generating-summary");
575
- if (summary && count > 0) summary.textContent = `${count} 个角色正在生成回答`;
642
+ const label = count ? `${count} 个角色正在生成回答` : "";
643
+ if (summary && label) summary.textContent = label;
576
644
  else if (summary) summary.remove();
577
645
  return count;
578
646
  }
@@ -595,6 +663,11 @@ export function createImWorkspace({ api, esc, renderMarkdown, toast, confirmToas
595
663
  if (follow) feed.scrollTop = feed.scrollHeight;
596
664
  }
597
665
 
666
+ function updateProvisionalJudgeElement(judge) {
667
+ const follow = feed.scrollHeight - feed.scrollTop - feed.clientHeight < 80;
668
+ renderMessages({ scrollToBottom: follow });
669
+ }
670
+
598
671
  function commitRealtimeMessage(message) {
599
672
  if (!message?.id) return;
600
673
  current.messages = mergeImMessagePages(current.messages, [message]);
@@ -609,7 +682,9 @@ export function createImWorkspace({ api, esc, renderMarkdown, toast, confirmToas
609
682
  const previousTop = feed.scrollTop;
610
683
  const messages = array(current?.messages);
611
684
  const provisional = [...provisionalReplies.values()];
685
+ const provisionalJudgeList = [...provisionalJudges.values()];
612
686
  const failedRepliesByMessage = new Map();
687
+ const judgesByMessage = new Map();
613
688
  for (const reply of array(current?.failedReplies)) {
614
689
  const triggerMessageId = String(reply.triggerMessageId || "");
615
690
  if (!triggerMessageId) continue;
@@ -617,7 +692,14 @@ export function createImWorkspace({ api, esc, renderMarkdown, toast, confirmToas
617
692
  replies.push(reply);
618
693
  failedRepliesByMessage.set(triggerMessageId, replies);
619
694
  }
620
- if (!messages.length && !provisional.length && failedRepliesByMessage.size === 0) {
695
+ for (const judge of provisionalJudgeList) {
696
+ const sourceMessageId = String(judge.sourceMessageId || "");
697
+ if (!sourceMessageId) continue;
698
+ const judges = judgesByMessage.get(sourceMessageId) ?? [];
699
+ judges.push(judge);
700
+ judgesByMessage.set(sourceMessageId, judges);
701
+ }
702
+ if (!messages.length && !provisional.length && !provisionalJudgeList.length && failedRepliesByMessage.size === 0) {
621
703
  feed.innerHTML = '<p class="im-feed-empty">从一条消息开始。角色单聊会直接回复;群聊按当前回复模式调度 AI。</p>';
622
704
  return;
623
705
  }
@@ -628,6 +710,14 @@ export function createImWorkspace({ api, esc, renderMarkdown, toast, confirmToas
628
710
  const generatingSummary = generatingCount
629
711
  ? `<div class="im-generating-summary" role="status">${generatingCount} 个角色正在生成回答</div>`
630
712
  : "";
713
+ const provisionalJudgeHtml = (judge) => {
714
+ const failed = ['failed', 'cancelled'].includes(judge.status);
715
+ const quiet = judge.status === "completed";
716
+ return `<article class="im-message is-character is-provisional is-judge${failed ? " is-failed" : ""}${quiet ? " is-quiet" : ""}" data-im-provisional-judge="${esc(judge.turnId)}" data-im-provisional-judge-status="${esc(judge.status)}">
717
+ <header>${imAvatarHtml(judge, "character", "im-message-avatar")}<strong>${esc(judge.name || "角色")}</strong><span class="im-provisional-status">${provisionalJudgeStatusLabel(judge)}</span></header>
718
+ <div class="im-message-body message-body">${provisionalJudgeBodyHtml(judge)}</div>
719
+ </article>`;
720
+ };
631
721
  const provisionalHtml = provisional.map((reply) => {
632
722
  const failed = ['failed', 'skipped'].includes(reply.status);
633
723
  const statusLabel = reply.status === "pending" ? "等待生成" : reply.status === "running" ? "正在生成" : reply.status === "skipped" ? "未生成" : "生成失败";
@@ -645,6 +735,12 @@ export function createImWorkspace({ api, esc, renderMarkdown, toast, confirmToas
645
735
  const avatar = announcement || message.senderKind === "system"
646
736
  ? ""
647
737
  : imAvatarHtml(sender, message.senderKind === "character" ? "character" : "user", "im-message-avatar");
738
+ const judges = array(judgesByMessage.get(String(message.id)));
739
+ const judgingCount = judges.filter((judge) => ['pending', 'running'].includes(judge.status)).length;
740
+ const judgeSummary = judgingCount
741
+ ? `<div class="im-generating-summary" role="status">${judgingCount} 个角色正在判断是否回答</div>`
742
+ : "";
743
+ const judgeResults = judges.map(provisionalJudgeHtml).join("");
648
744
  const failedReplies = array(failedRepliesByMessage.get(String(message.id))).map((reply) => {
649
745
  const character = value(reply, "character", {});
650
746
  return `<article class="im-message is-character is-provisional is-failed" data-im-failed-turn="${esc(reply.id)}">
@@ -656,7 +752,7 @@ export function createImWorkspace({ api, esc, renderMarkdown, toast, confirmToas
656
752
  <header>${avatar}<strong>${esc(label)}</strong><time>${esc(new Date(message.createdAt).toLocaleString("zh-CN", { month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit" }))}</time></header>
657
753
  <div class="im-message-body message-body">${messageHtml(message)}</div>
658
754
  ${message.senderKind === "character" ? `<details class="im-model-details"><summary>调用详情</summary><span>${esc(model.modelDisplayName || model.modelId || "未知模型")} · ${model.modelStage === "fallback" ? "fallback" : "主模型"} · ${Number(model.attemptCount || 1)} 次请求 · ${Number(model.durationMs || 0)} ms</span></details>` : ""}
659
- </article>${failedReplies}`;
755
+ </article>${judgeSummary}${judgeResults}${failedReplies}`;
660
756
  }).join("") + generatingSummary + provisionalHtml;
661
757
  bindImAvatarFallbacks(feed);
662
758
  feed.scrollTop = follow ? feed.scrollHeight : previousTop;
@@ -679,6 +775,7 @@ export function createImWorkspace({ api, esc, renderMarkdown, toast, confirmToas
679
775
  ].map((message) => [message.id, message]));
680
776
  current.messages = [...messagesById.values()].sort((left, right) => Number(left.sequence) - Number(right.sequence));
681
777
  current.failedReplies = mergeImFailedReplyPages(page.failedReplies, current.failedReplies);
778
+ current.judgeOutcomes = mergeImTriggeredTurnPages(page.judgeOutcomes, current.judgeOutcomes);
682
779
  current.hasMoreMessages = page.hasMoreMessages === true;
683
780
  renderMessages();
684
781
  feed.scrollTop = previousTop + Math.max(0, feed.scrollHeight - previousHeight);
@@ -989,17 +1086,20 @@ export function createImWorkspace({ api, esc, renderMarkdown, toast, confirmToas
989
1086
  const conversationChanged = !previousConversation;
990
1087
  if (previousConversation) {
991
1088
  const gapFailedReplies = [];
1089
+ const gapJudgeOutcomes = [];
992
1090
  const gapMessages = await collectImMessageGap(previousConversation.messages, nextConversation.messages, async (cursor) => {
993
1091
  const page = await api(`/api/im/conversations/${encodeURIComponent(conversationId)}?afterSequence=${encodeURIComponent(cursor)}`);
994
1092
  if (request !== conversationRequest || (requestedConversationId && requestedConversationId !== conversationId)) {
995
1093
  throw new Error("IM 会话请求已失效");
996
1094
  }
997
1095
  gapFailedReplies.push(...array(page.failedReplies));
1096
+ gapJudgeOutcomes.push(...array(page.judgeOutcomes));
998
1097
  return page;
999
1098
  });
1000
1099
  if (request !== conversationRequest || (requestedConversationId && requestedConversationId !== conversationId)) return;
1001
1100
  nextConversation.messages = mergeImMessagePages(previousConversation.messages, gapMessages, nextConversation.messages);
1002
1101
  nextConversation.failedReplies = mergeImFailedReplyPages(previousConversation.failedReplies, gapFailedReplies, nextConversation.failedReplies);
1102
+ nextConversation.judgeOutcomes = mergeImTriggeredTurnPages(previousConversation.judgeOutcomes, gapJudgeOutcomes, nextConversation.judgeOutcomes);
1003
1103
  nextConversation.hasMoreMessages = previousConversation.hasMoreMessages === true;
1004
1104
  }
1005
1105
  if (current?.id && current.id !== conversationId) {
@@ -1015,6 +1115,7 @@ export function createImWorkspace({ api, esc, renderMarkdown, toast, confirmToas
1015
1115
  if (requestedConversationId === conversationId) requestedConversationId = null;
1016
1116
  workspace.classList.add("has-conversation");
1017
1117
  syncProvisionalReplies();
1118
+ syncProvisionalJudges();
1018
1119
  const shouldRestoreDetailsFocus = detailsFocus && document.activeElement?.id === detailsFocus.id;
1019
1120
  renderConversationList();
1020
1121
  renderConversation(conversationChanged);
@@ -1427,6 +1528,7 @@ export function createImWorkspace({ api, esc, renderMarkdown, toast, confirmToas
1427
1528
  conversationDrafts.delete(conversationId);
1428
1529
  closeMentionMenu();
1429
1530
  provisionalReplies.clear();
1531
+ clearPendingProvisionalJudges();
1430
1532
  let committed = false;
1431
1533
  try {
1432
1534
  const result = await api(`/api/im/conversations/${encodeURIComponent(conversationId)}/messages`, {
@@ -1473,7 +1575,11 @@ export function createImWorkspace({ api, esc, renderMarkdown, toast, confirmToas
1473
1575
  return;
1474
1576
  }
1475
1577
  if (envelope.type === "message" && current?.id === eventConversationId && envelope.payload.message) {
1476
- if (Object.prototype.hasOwnProperty.call(envelope.payload, "chain")) current.activeChain = envelope.payload.chain ?? null;
1578
+ if (Object.prototype.hasOwnProperty.call(envelope.payload, "chain")) {
1579
+ const nextChain = envelope.payload.chain ?? null;
1580
+ if (String(current.activeChain?.id || "") !== String(nextChain?.id || "")) clearPendingProvisionalJudges();
1581
+ current.activeChain = nextChain;
1582
+ }
1477
1583
  commitRealtimeMessage(envelope.payload.message);
1478
1584
  }
1479
1585
  if (envelope.type === "turn" && current?.id === eventConversationId && envelope.payload.kind === "reply") {
@@ -1490,6 +1596,18 @@ export function createImWorkspace({ api, esc, renderMarkdown, toast, confirmToas
1490
1596
  }
1491
1597
  return;
1492
1598
  }
1599
+ if (envelope.type === "turn" && current?.id === eventConversationId && envelope.payload.kind === "judge") {
1600
+ if (!isImRealtimeChainCurrent(current.activeChain, envelope.payload)) return;
1601
+ const turnId = String(envelope.payload.turnId || "");
1602
+ if (envelope.payload.selected === true) {
1603
+ provisionalJudges.delete(turnId);
1604
+ renderMessages();
1605
+ } else {
1606
+ const provisional = upsertProvisionalJudge(envelope.payload);
1607
+ if (provisional) updateProvisionalJudgeElement(provisional);
1608
+ }
1609
+ return;
1610
+ }
1493
1611
  if (envelope.type === "delta" && current?.id === eventConversationId) {
1494
1612
  if (!isImRealtimeChainCurrent(current.activeChain, envelope.payload)) return;
1495
1613
  const provisional = upsertProvisionalReply({ ...envelope.payload, status: "running" });
@@ -1570,6 +1688,7 @@ export function createImWorkspace({ api, esc, renderMarkdown, toast, confirmToas
1570
1688
  resetDetailsDrawer();
1571
1689
  document.querySelector("#app").classList.remove("im-mode");
1572
1690
  provisionalReplies.clear();
1691
+ provisionalJudges.clear();
1573
1692
  closeMentionMenu();
1574
1693
  }
1575
1694
 
@@ -1715,6 +1834,7 @@ export function createImWorkspace({ api, esc, renderMarkdown, toast, confirmToas
1715
1834
  const result = await performMutation(event.currentTarget, () => api(`/api/im/conversations/${encodeURIComponent(conversationId)}/stop`, { method: "POST", body: {} }));
1716
1835
  if (!result.ok) return;
1717
1836
  provisionalReplies.clear();
1837
+ provisionalJudges.clear();
1718
1838
  if (current?.id === conversationId) await refreshAfterMutation("停止 AI", () => openConversation(conversationId));
1719
1839
  });
1720
1840
  document.querySelector("#im-retry").addEventListener("click", async (event) => {
@@ -1744,6 +1864,7 @@ export function createImWorkspace({ api, esc, renderMarkdown, toast, confirmToas
1744
1864
  workspace.classList.remove("has-conversation");
1745
1865
  resetDetailsDrawer();
1746
1866
  provisionalReplies.clear();
1867
+ provisionalJudges.clear();
1747
1868
  renderConversationList();
1748
1869
  renderConversation();
1749
1870
  const previousConversationButton = [...listHost.querySelectorAll("[data-im-conversation]")]
@@ -9,7 +9,7 @@
9
9
  <script src="/theme-init.js?v=20260827-reader-prefetch-v1"></script>
10
10
  <link rel="icon" href="/icon.svg?v=20260712" type="image/svg+xml">
11
11
  <link rel="manifest" href="/site.webmanifest">
12
- <link rel="stylesheet" href="/styles.css?v=20260816-task-scope-volume-collapse-v2&feature=ai-tool-call-copy-feedback-v2&feature=ai-send-control-v3&feature=character-gender-v1&feature=character-filter-state-v1&feature=relationship-canvas-scale-v1&feature=relationship-table-scroll-v1&feature=galaxy-compact-controls-v2&feature=galaxy-motion-mode-v2&feature=chapter-search-replace-v3&feature=task-auto-run-ring-center-v3&feature=character-relationship-delete-v1&feature=ai-assistant-workspace-v2&feature=mobile-module-tab-position-v1&feature=volume-detail-icon-v1&feature=editor-actions-flow-v1&feature=reader-controls-subpanel-v1&feature=reader-focus-ring-v1&feature=ai-message-actions-v1&feature=assistant-responsive-navigation-v2&feature=ai-composer-square-controls-v2&feature=annotation-line-counts-v1&feature=chapter-comment-filters-v1&feature=line-number-gutter-fill-v1&feature=ai-relationship-roleplay-v1&feature=ai-model-picker-v1&feature=markdown-word-count-five-digit-v2&feature=ai-stream-character-count-stable-v1&feature=ai-stream-character-count-five-digit-v1&feature=annotation-marker-offset-v1&feature=mobile-ai-entry-hidden-v1&feature=phone-client-entry-v1&feature=ai-stream-idle-timeout-v1&feature=ai-user-message-width-v2&feature=ai-chat-image-attachments-v9&feature=ai-message-image-preview-v1&feature=ai-thinking-scroll-v2&feature=toast-click-dismiss-v3&feature=character-avatar-v6&feature=admin-ai-conversations-v1&feature=ai-usage-pricing-v1&feature=ai-usage-pricing-badge-v2&feature=ai-token-quota-positive-v5&feature=ai-token-usage-details-v1&feature=ai-token-usage-details-centered-v1&feature=ai-stream-connection-seconds-v1&feature=ai-token-usage-estimated-price-v1&feature=record-favorites-v1&feature=entity-detail-favorites-v1&feature=entity-pin-v1&feature=character-persona-summary-v1&feature=ai-roleplay-scene-turn-v1&feature=admin-account-identity-v2&feature=task-detail-failure-orange-v1&feature=character-card-header-alignment-v6&feature=character-card-title-fit-v2&feature=book-import-progress-v1&feature=editor-toolbar-compact-v2&feature=reader-first-frame-v1&feature=auth-compact-v2&feature=editor-preview-toggle-v2&feature=setting-title-chrome-v2&feature=entity-pin-icon-center-v1&feature=chapter-center-bottom-space-v1&feature=work-editor-preferences-v1&feature=work-editor-preference-checkbox-v1&feature=ai-roleplay-memory-v2&feature=ai-roleplay-memory-v3&feature=ai-roleplay-memory-v5&feature=character-editor-header-actions-v1&feature=roleplay-memory-header-actions-v1&feature=roleplay-memory-header-toolbar-v1&feature=roleplay-memory-action-icons-v1&feature=roleplay-memory-action-colors-v1&feature=roleplay-memory-pin-border-v1&feature=ai-write-tools-v2&feature=ai-write-card-actions-compact-v1&feature=ai-write-plan-actions-footer-v1&feature=ai-question-actions-footer-v1&feature=ai-question-selection-highlight-v1&feature=ai-question-submit-guidance-v1&feature=ai-question-answer-limit-v1&feature=ai-question-batch-v1&feature=semantic-search-v6&feature=chapter-title-renumber-v1&feature=ai-writing-skills-v1&feature=remote-mcp-v1&feature=character-alias-chips-v2&feature=character-title-input-v1&feature=character-detail-value-wrap-v2&feature=ai-settings-textarea-font-v1&feature=ai-usage-stat-display-v1&feature=ai-usage-year-v1&feature=ai-skill-slash-menu-v1&feature=ai-message-citation-popover-v1&feature=line-citation-menu-separator-v1&feature=global-im-v103&feature=im-sidebar-compact-v1&feature=im-narration-contrast-v1&feature=im-member-add-plus-v2&feature=im-button-hierarchy-v1&feature=im-icon-button-size-v1&feature=compact-sidebar-directory-v4">
12
+ <link rel="stylesheet" href="/styles.css?v=20260816-task-scope-volume-collapse-v2&feature=ai-tool-call-copy-feedback-v2&feature=ai-send-control-v3&feature=character-gender-v1&feature=character-filter-state-v1&feature=relationship-canvas-scale-v1&feature=relationship-table-scroll-v1&feature=galaxy-compact-controls-v2&feature=galaxy-motion-mode-v2&feature=chapter-search-replace-v3&feature=task-auto-run-ring-center-v3&feature=character-relationship-delete-v1&feature=ai-assistant-workspace-v2&feature=mobile-module-tab-position-v1&feature=volume-detail-icon-v1&feature=editor-actions-flow-v1&feature=reader-controls-subpanel-v1&feature=reader-focus-ring-v1&feature=ai-message-actions-v1&feature=assistant-responsive-navigation-v2&feature=ai-composer-square-controls-v2&feature=annotation-line-counts-v1&feature=chapter-comment-filters-v1&feature=line-number-gutter-fill-v1&feature=ai-relationship-roleplay-v1&feature=ai-model-picker-v1&feature=markdown-word-count-five-digit-v2&feature=ai-stream-character-count-stable-v1&feature=ai-stream-character-count-five-digit-v1&feature=annotation-marker-offset-v1&feature=mobile-ai-entry-hidden-v1&feature=phone-client-entry-v1&feature=ai-stream-idle-timeout-v1&feature=ai-user-message-width-v2&feature=ai-chat-image-attachments-v9&feature=ai-message-image-preview-v1&feature=ai-thinking-scroll-v2&feature=toast-click-dismiss-v3&feature=character-avatar-v6&feature=admin-ai-conversations-v1&feature=ai-usage-pricing-v1&feature=ai-usage-pricing-badge-v2&feature=ai-token-quota-positive-v5&feature=ai-token-usage-details-v1&feature=ai-token-usage-details-centered-v1&feature=ai-stream-connection-seconds-v1&feature=ai-token-usage-estimated-price-v1&feature=record-favorites-v1&feature=entity-detail-favorites-v1&feature=entity-pin-v1&feature=character-persona-summary-v1&feature=ai-roleplay-scene-turn-v1&feature=admin-account-identity-v2&feature=task-detail-failure-orange-v1&feature=character-card-header-alignment-v6&feature=character-card-title-fit-v2&feature=book-import-progress-v1&feature=editor-toolbar-compact-v2&feature=reader-first-frame-v1&feature=auth-compact-v2&feature=editor-preview-toggle-v2&feature=setting-title-chrome-v2&feature=entity-pin-icon-center-v1&feature=chapter-center-bottom-space-v1&feature=work-editor-preferences-v1&feature=work-editor-preference-checkbox-v1&feature=ai-roleplay-memory-v2&feature=ai-roleplay-memory-v3&feature=ai-roleplay-memory-v5&feature=character-editor-header-actions-v1&feature=roleplay-memory-header-actions-v1&feature=roleplay-memory-action-icons-v1&feature=roleplay-memory-action-colors-v1&feature=roleplay-memory-pin-border-v1&feature=ai-write-tools-v2&feature=ai-write-card-actions-compact-v1&feature=ai-write-plan-actions-footer-v1&feature=ai-question-actions-footer-v1&feature=ai-question-selection-highlight-v1&feature=ai-question-submit-guidance-v1&feature=ai-question-answer-limit-v1&feature=ai-question-batch-v1&feature=semantic-search-v6&feature=chapter-title-renumber-v1&feature=ai-writing-skills-v1&feature=remote-mcp-v1&feature=character-alias-chips-v2&feature=character-title-input-v1&feature=character-detail-value-wrap-v2&feature=ai-settings-textarea-font-v1&feature=ai-usage-stat-display-v1&feature=ai-usage-year-v1&feature=ai-skill-slash-menu-v1&feature=ai-message-citation-popover-v1&feature=line-citation-menu-separator-v1&feature=global-im-v106&feature=im-sidebar-compact-v1&feature=im-narration-contrast-v1&feature=im-member-add-plus-v2&feature=im-button-hierarchy-v1&feature=im-icon-button-size-v1&feature=compact-sidebar-directory-v5&feature=ai-model-config-dialog-v1&feature=system-prompt-override-v3">
13
13
  </head>
14
14
  <body class="auth-pending">
15
15
  <section id="auth-view" class="auth-view hidden" aria-labelledby="auth-title">
@@ -1425,6 +1425,6 @@
1425
1425
  </dialog>
1426
1426
 
1427
1427
  <div id="auth-loading" class="auth-loading" role="status" aria-label="正在载入工作台"></div>
1428
- <script type="module" src="/app.js?v=20260816-extended-thinking-effort-v1&feature=ai-history-rename-v1&feature=ai-tool-call-copy-feedback-v2&feature=ai-send-control-v2&feature=analysis-task-queue-refresh-v1&feature=character-gender-v1&feature=character-filter-state-v1&feature=relationship-canvas-scale-v1&feature=relationship-table-scroll-v1&feature=ai-session-id-copy-v2&feature=galaxy-motion-mode-v3&feature=galaxy-edge-label-threshold-v1&feature=calculate-time-tool-v2&feature=analysis-task-expired-toast-v1&feature=global-replace-volume-v1&feature=chapter-search-replace-v1&feature=chapter-save-toast-v1&feature=character-relationship-delete-v1&feature=character-relationship-group-v1&feature=analysis-task-stability-delay-v1&feature=ai-assistant-workspace-v2&feature=volume-detail-icon-v1&feature=volume-story-order-v1&feature=reader-manual-chapter-navigation-v1&feature=ai-message-reference-badges-v1&feature=ai-roleplay-message-reference-v1&feature=ai-message-actions-v1&feature=assistant-responsive-navigation-v3&feature=annotation-permissions-v1&feature=annotation-line-counts-v1&feature=ai-relationship-roleplay-v1&feature=ai-roleplay-user-character-visibility-v1&feature=ai-roleplay-story-recall-v1&feature=ai-model-picker-v1&feature=ai-fork-model-unlock-v1&feature=context-percent-format-v1&feature=annotation-precise-locate-v1&feature=markdown-word-count-stable-v1&feature=ai-stream-character-count-stable-v2&feature=ai-process-empty-intermediate-v1&feature=ai-feed-scroll-follow-v2&feature=ai-message-retry-v1&feature=ai-stream-idle-timeout-v2&feature=ai-config-delete-v1&feature=ai-provider-protocol-options-v1&feature=ai-provider-thinking-type-v1&feature=ai-chat-image-attachments-v8&feature=ai-message-image-preview-v1&feature=ai-thinking-scroll-v2&feature=ai-image-conversation-model-lock-v1&feature=toast-click-dismiss-v2&feature=system-restart-dialog-delay-v1&feature=character-avatar-v6&feature=character-death-position-v1&feature=admin-ai-conversations-v1&feature=ai-usage-pricing-v1&feature=ai-usage-pricing-badge-v2&feature=ai-usage-pricing-label-v1&feature=ai-usage-token-breakdown-v1&feature=ai-usage-pricing-cache-v2&feature=ai-usage-pricing-manual-refresh-v1&feature=ai-monthly-token-quota-v1&feature=ai-provider-token-quota-v1&feature=ai-token-quota-positive-v6&feature=ai-model-thinking-label-v1&feature=ai-model-picker-focus-v1&feature=ai-provider-model-import-v1&feature=ai-assistant-brain-icon-v1&feature=ai-token-usage-details-v1&feature=ai-token-usage-details-centered-v1&feature=ai-token-usage-raw-input-v1&feature=ai-stream-connection-seconds-v1&feature=phone-client-entry-v1&feature=ai-usage-pricing-cache-v1&feature=ai-model-thinking-label-v3&feature=ai-roleplay-speaker-label-v1&feature=toast-modal-host-v1&feature=api-key-copy-existing-v1&feature=character-favorite-v1&feature=record-favorites-v1&feature=ai-token-usage-estimated-price-v1&feature=entity-detail-favorites-v1&feature=entity-pin-v1&feature=entity-pin-icon-v1&feature=roleplay-favorite-label-v1&feature=ai-roleplay-knowledge-tools-v1&feature=character-persona-summary-v1&feature=ai-roleplay-scene-turn-v2&feature=admin-account-identity-v2&feature=ai-provider-analysis-timeout-v1&feature=task-detail-failure-orange-v1&feature=book-import-progress-v1&feature=presence-multiple-users-v1&feature=ai-context-input-output-v1&feature=editor-blank-lines-preserved-v1&feature=reader-first-frame-v1&feature=vditor-lazy-load-v1&feature=ui-module-preload-v1&feature=reader-initial-prefetch-v1&feature=editor-preview-toggle-v2&feature=vditor-fullscreen-disabled-v1&feature=chapter-auto-indent-v1&feature=chapter-centered-scroll-v1&feature=work-editor-preferences-v1&feature=ai-context-output-usage-v1&feature=ai-roleplay-memory-v4&feature=ai-roleplay-memory-v5&feature=roleplay-memory-header-actions-v1&feature=roleplay-memory-header-toolbar-v1&feature=roleplay-memory-action-icons-v1&feature=roleplay-memory-action-colors-v1&feature=annotation-line-anchor-v1&feature=stable-line-ids-v1&feature=live-annotation-anchors-v1&feature=chapter-comment-filters-v1&feature=ai-write-tools-v3&feature=ai-question-option-supplement-v1&feature=ai-question-selection-highlight-v1&feature=ai-question-continuation-ui-v1&feature=ai-question-tool-result-v1&feature=ai-question-tool-summary-v1&feature=ai-question-submit-guidance-v1&feature=ai-question-batch-v1&feature=ai-question-answer-limit-v1&feature=semantic-search-v6&feature=chapter-title-renumber-v1&feature=ai-writing-skills-v3&feature=ai-cancel-preserve-process-v2&feature=remote-mcp-v1&feature=character-alias-chips-v2&feature=character-title-input-v1&feature=character-detail-value-wrap-v2&feature=chapter-batch-editor-refresh-v1&feature=ai-usage-stat-display-v1&feature=ai-usage-year-v1&feature=semantic-search-rag-label-v1&feature=ai-skill-slash-menu-v1&feature=ai-roleplay-scene-bubble-v1&feature=ai-roleplay-scene-collapse-v1&feature=markdown-adjacent-blockquotes-v1&feature=chapter-save-shortcut-v2&feature=ai-message-citation-popover-v1&feature=line-citation-menu-separator-v1&feature=global-im-v103&feature=im-sidebar-compact-v1&feature=im-settings-gear-v1&feature=compact-sidebar-directory-v2"></script>
1428
+ <script type="module" src="/app.js?v=20260816-extended-thinking-effort-v1&feature=ai-history-rename-v1&feature=ai-tool-call-copy-feedback-v2&feature=ai-send-control-v2&feature=analysis-task-queue-refresh-v1&feature=character-gender-v1&feature=character-filter-state-v1&feature=relationship-canvas-scale-v1&feature=relationship-table-scroll-v1&feature=ai-session-id-copy-v2&feature=galaxy-motion-mode-v3&feature=galaxy-edge-label-threshold-v1&feature=calculate-time-tool-v2&feature=analysis-task-expired-toast-v1&feature=global-replace-volume-v1&feature=chapter-search-replace-v1&feature=chapter-save-toast-v1&feature=character-relationship-delete-v1&feature=character-relationship-group-v1&feature=analysis-task-stability-delay-v1&feature=ai-assistant-workspace-v2&feature=volume-detail-icon-v1&feature=volume-story-order-v1&feature=reader-manual-chapter-navigation-v1&feature=ai-message-reference-badges-v1&feature=ai-roleplay-message-reference-v1&feature=ai-message-actions-v1&feature=assistant-responsive-navigation-v3&feature=annotation-permissions-v1&feature=annotation-line-counts-v1&feature=ai-relationship-roleplay-v1&feature=ai-roleplay-user-character-visibility-v1&feature=ai-roleplay-story-recall-v1&feature=ai-model-picker-v1&feature=ai-fork-model-unlock-v1&feature=context-percent-format-v1&feature=annotation-precise-locate-v1&feature=markdown-word-count-stable-v1&feature=ai-stream-character-count-stable-v2&feature=ai-process-empty-intermediate-v1&feature=ai-feed-scroll-follow-v2&feature=ai-message-retry-v1&feature=ai-stream-idle-timeout-v2&feature=ai-config-delete-v1&feature=ai-provider-protocol-options-v1&feature=ai-provider-thinking-type-v1&feature=ai-chat-image-attachments-v8&feature=ai-message-image-preview-v1&feature=ai-thinking-scroll-v2&feature=ai-image-conversation-model-lock-v1&feature=toast-click-dismiss-v2&feature=system-restart-dialog-delay-v1&feature=character-avatar-v6&feature=character-death-position-v1&feature=admin-ai-conversations-v1&feature=ai-usage-pricing-v1&feature=ai-usage-pricing-badge-v2&feature=ai-usage-pricing-label-v1&feature=ai-usage-token-breakdown-v1&feature=ai-usage-pricing-cache-v2&feature=ai-usage-pricing-manual-refresh-v1&feature=ai-monthly-token-quota-v1&feature=ai-provider-token-quota-v1&feature=ai-token-quota-positive-v6&feature=ai-model-thinking-label-v1&feature=ai-model-picker-focus-v1&feature=ai-provider-model-import-v1&feature=ai-assistant-brain-icon-v1&feature=ai-token-usage-details-v1&feature=ai-token-usage-details-centered-v1&feature=ai-token-usage-raw-input-v1&feature=ai-stream-connection-seconds-v1&feature=phone-client-entry-v1&feature=ai-usage-pricing-cache-v1&feature=ai-model-thinking-label-v3&feature=ai-roleplay-speaker-label-v1&feature=toast-modal-host-v1&feature=api-key-copy-existing-v1&feature=character-favorite-v1&feature=record-favorites-v1&feature=ai-token-usage-estimated-price-v1&feature=entity-detail-favorites-v1&feature=entity-pin-v1&feature=entity-pin-icon-v1&feature=roleplay-favorite-label-v1&feature=ai-roleplay-knowledge-tools-v1&feature=character-persona-summary-v1&feature=ai-roleplay-scene-turn-v2&feature=admin-account-identity-v2&feature=ai-provider-analysis-timeout-v1&feature=task-detail-failure-orange-v1&feature=book-import-progress-v1&feature=presence-multiple-users-v1&feature=ai-context-input-output-v1&feature=editor-blank-lines-preserved-v1&feature=reader-first-frame-v1&feature=vditor-lazy-load-v1&feature=ui-module-preload-v1&feature=reader-initial-prefetch-v1&feature=editor-preview-toggle-v2&feature=vditor-fullscreen-disabled-v1&feature=chapter-auto-indent-v1&feature=chapter-centered-scroll-v1&feature=work-editor-preferences-v1&feature=ai-context-output-usage-v1&feature=ai-roleplay-memory-v4&feature=ai-roleplay-memory-v5&feature=roleplay-memory-header-actions-v1&feature=roleplay-memory-header-toolbar-v1&feature=roleplay-memory-action-icons-v1&feature=roleplay-memory-action-colors-v1&feature=annotation-line-anchor-v1&feature=stable-line-ids-v1&feature=live-annotation-anchors-v1&feature=chapter-comment-filters-v1&feature=ai-write-tools-v3&feature=ai-question-option-supplement-v1&feature=ai-question-selection-highlight-v1&feature=ai-question-continuation-ui-v1&feature=ai-question-tool-result-v1&feature=ai-question-tool-summary-v1&feature=ai-question-submit-guidance-v1&feature=ai-question-batch-v1&feature=ai-question-answer-limit-v1&feature=semantic-search-v6&feature=chapter-title-renumber-v1&feature=ai-writing-skills-v3&feature=ai-cancel-preserve-process-v2&feature=remote-mcp-v1&feature=character-alias-chips-v2&feature=character-title-input-v1&feature=character-detail-value-wrap-v2&feature=chapter-batch-editor-refresh-v1&feature=ai-usage-stat-display-v1&feature=ai-usage-year-v1&feature=semantic-search-rag-label-v1&feature=ai-skill-slash-menu-v1&feature=ai-roleplay-scene-bubble-v1&feature=ai-roleplay-scene-collapse-v1&feature=markdown-adjacent-blockquotes-v1&feature=chapter-save-shortcut-v2&feature=ai-message-citation-popover-v1&feature=line-citation-menu-separator-v1&feature=global-im-v106&feature=im-sidebar-compact-v1&feature=im-settings-gear-v1&feature=compact-sidebar-directory-v2"></script>
1429
1429
  </body>
1430
1430
  </html>
@@ -1440,6 +1440,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
1440
1440
  display: grid;
1441
1441
  grid-template-columns: auto minmax(0, 2em);
1442
1442
  align-items: start;
1443
+ justify-content: start;
1443
1444
  word-break: break-all;
1444
1445
  }
1445
1446
  .module-nav > button[data-module="outlines"] {
@@ -1778,6 +1779,21 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
1778
1779
  .config-section { margin-top: 28px; padding-top: 24px; border-top: 1px solid var(--line); }
1779
1780
  .config-section:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
1780
1781
  .platform-system-prompt-section { margin-bottom: 36px; }
1782
+ .system-prompt-override-settings { margin-top: 12px; overflow: hidden; border: 1px solid var(--line); border-radius: 5px; background: var(--surface-soft); }
1783
+ .system-prompt-override-settings.is-enabled { border-color: color-mix(in srgb, var(--accent) 55%, var(--line)); }
1784
+ .system-prompt-override-settings summary { display: grid; min-height: 40px; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: 8px; padding: 9px 12px; cursor: pointer; color: var(--muted); font-size: 11px; list-style: none; }
1785
+ .system-prompt-override-settings summary::-webkit-details-marker { display: none; }
1786
+ .system-prompt-override-settings summary::before { content: "›"; color: var(--muted); font-size: 16px; line-height: 1; transform: rotate(0deg); transition: transform .16s ease; }
1787
+ .system-prompt-override-settings[open] summary::before { transform: rotate(90deg); }
1788
+ .system-prompt-override-settings summary:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
1789
+ .system-prompt-override-settings summary strong { color: var(--ink); font-size: 10px; font-weight: 600; }
1790
+ .system-prompt-override-settings.is-enabled summary strong { color: var(--accent-dark); }
1791
+ .system-prompt-override-body { display: grid; gap: 10px; padding: 12px; border-top: 1px solid var(--line); }
1792
+ .system-prompt-override-body > .config-checkbox-field { display: inline-flex; width: fit-content; align-items: center; gap: 8px; margin: 0; color: var(--ink); font-size: 11px; }
1793
+ .system-prompt-override-body > .config-checkbox-field input { width: 18px; min-width: 18px; height: 18px; }
1794
+ .system-prompt-override-warning { padding: 10px 12px; border: 1px solid color-mix(in srgb, var(--accent) 48%, var(--line)); border-radius: 5px; background: color-mix(in srgb, var(--accent) 8%, var(--surface)); }
1795
+ .system-prompt-override-warning strong { color: var(--ink); font-size: 11px; }
1796
+ .system-prompt-override-warning p { margin: 3px 0 0; color: var(--muted); font-size: 10px; line-height: 1.55; }
1781
1797
  .platform-image-tool-section { margin-bottom: 0; }
1782
1798
  .platform-image-tool-panel { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; padding: 16px; border: 1px solid var(--line); border-radius: 6px; background: var(--surface-soft); }
1783
1799
  .platform-image-tool-field { display: grid; flex: 1 1 360px; gap: 7px; min-width: 0; color: var(--muted); font-size: 10px; }
@@ -3520,6 +3536,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
3520
3536
  .dialog-fields .form-field-note { margin: 0; color: var(--muted); font-size: 11px; line-height: 1.65; }
3521
3537
  .dialog-fields label { display: grid; gap: 6px; color: var(--muted); font-size: 11px; }
3522
3538
  .dialog-fields input, .dialog-fields textarea, .dialog-fields select { width: 100%; padding: 9px 10px; font-size: 13px; }
3539
+ .dialog-fields input[type="checkbox"] { flex: 0 0 16px; width: 16px; min-width: 16px; height: 16px; min-height: 16px; margin: 0; padding: 0; }
3523
3540
  .dialog-fields select { padding-inline-end: 28px; }
3524
3541
  .dialog-fields .character-extraction-candidate-toggle { display: inline-flex; width: auto; align-items: center; gap: 7px; margin: 0; color: var(--ink); font-size: 9px; white-space: nowrap; }
3525
3542
  .dialog-fields .character-extraction-candidate-toggle input[type="checkbox"] { flex: 0 0 18px; width: 18px; min-width: 18px; height: 18px; padding: 0; }
@@ -3570,6 +3587,14 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
3570
3587
  .model-capability-option small, .model-multimodal-note { color: var(--muted); font-size: 9px; line-height: 1.5; }
3571
3588
  .model-capability-option:has(input:checked) { border-color: color-mix(in srgb, var(--accent) 62%, var(--line)); background: color-mix(in srgb, var(--accent) 7%, var(--surface)); }
3572
3589
  .model-multimodal-note { margin: 0 2px; }
3590
+ #form-dialog:has(.model-kind-fields) { width: min(720px, 92vw); }
3591
+ #dialog-fields:has(.model-kind-fields) { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px 14px; }
3592
+ #dialog-fields:has(.model-kind-fields) [data-chat-model-fields] { display: contents; }
3593
+ #dialog-fields:has(.model-kind-fields) > .model-kind-fields,
3594
+ #dialog-fields:has(.model-kind-fields) > .checkbox-field,
3595
+ #dialog-fields:has(.model-kind-fields) .chip-field,
3596
+ #dialog-fields:has(.model-kind-fields) .model-multimodal-fields,
3597
+ #dialog-fields:has(.model-kind-fields) .model-connection-test { grid-column: 1 / -1; }
3573
3598
  .analysis-type-description { margin: 0; color: var(--muted); font-size: 10px; line-height: 1.55; }
3574
3599
  .task-chapter-field { transition: opacity .15s ease; }
3575
3600
  .task-chapter-field.is-disabled { opacity: .48; }
@@ -4467,6 +4492,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
4467
4492
  margin: auto;
4468
4493
  border-radius: 10px;
4469
4494
  }
4495
+ #form-dialog:has(.model-kind-fields) { width: calc(100vw - 20px); max-width: calc(100vw - 20px); }
4470
4496
  .dialog-header { padding: 14px 16px 12px; }
4471
4497
  .dialog-header h2 { font-size: 20px; }
4472
4498
  .dialog-title-input { width: min(100%, calc(100vw - 100px)); font-size: 20px; }
@@ -4573,6 +4599,10 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
4573
4599
  .onboarding-popover { width: calc(100vw - 20px); }
4574
4600
  }
4575
4601
 
4602
+ @media (max-width: 480px) {
4603
+ #dialog-fields:has(.model-kind-fields) { grid-template-columns: minmax(0, 1fr); }
4604
+ }
4605
+
4576
4606
  @media (max-width: 850px) {
4577
4607
  #onboarding-menu-button { display: none; }
4578
4608
  }
@@ -4800,6 +4830,8 @@ body.is-im-conversations-resizing { cursor: ew-resize; user-select: none; }
4800
4830
  .im-generating-summary { width: min(78%, 720px); margin: 2px auto 9px 0; color: var(--muted); font-size: 9px; line-height: 1.4; }
4801
4831
  .im-message.is-provisional { opacity: .82; }
4802
4832
  .im-message.is-provisional.is-failed { opacity: 1; }
4833
+ .im-message.is-provisional.is-judge .im-message-body { border-style: dashed; background: color-mix(in srgb, var(--panel) 88%, var(--paper-deep)); }
4834
+ .im-message.is-provisional.is-judge.is-quiet { opacity: .72; }
4803
4835
  .im-provisional-status { color: var(--muted); font-size: 8px; }
4804
4836
  .im-provisional-placeholder { margin: 0; color: var(--muted); }
4805
4837
  .im-provisional-error { margin: 0; color: color-mix(in srgb, #c44949 82%, var(--ink)); font-weight: 600; }