@musnows/scriverse 0.5.2 → 0.5.4

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.
@@ -21,6 +21,7 @@ import {
21
21
  occurrenceRoleLabel,
22
22
  outlineStatusLabel,
23
23
  providerConnectionLabel,
24
+ providerProtocolLabel,
24
25
  providerStatusLabel,
25
26
  relationshipCategoryLabel,
26
27
  relationshipConfirmationLabel,
@@ -31,7 +32,7 @@ import {
31
32
  taskScopeLabel,
32
33
  timelineStatusLabel,
33
34
  characterStateFieldLabel
34
- } from "/display-labels.js?v=20260725-state-field-zh";
35
+ } from "/display-labels.js?v=20260726-anthropic-messages-v2";
35
36
  import { parsePageRoute, serializePageRoute } from "/page-route.js?v=20260723-knowledge-editor-page";
36
37
  import { splitRelationshipKeywordInput, splitRelationshipKeywords, uniqueRelationshipKeywords } from "/relationship-keywords.js?v=20260720-relationship-keyword-chips";
37
38
  import { tokenizeVisibleSpaces } from "/whitespace-visualization.js?v=20260718-visible-whitespace";
@@ -147,6 +148,14 @@ function analysisTaskTypeLabel(taskType) {
147
148
  return analysisTaskTypeLabels.get(String(taskType)) ?? "其他分析";
148
149
  }
149
150
 
151
+ function analysisTaskModelPurpose(taskType) {
152
+ if (taskType === "timeline-analysis") return "timeline-analysis";
153
+ if (taskType === "relationship-analysis") return "relationship-analysis";
154
+ if (taskType === "consistency-check") return "consistency-check";
155
+ if (taskType === "chapter-analysis") return "chapter-analysis";
156
+ return "book-analysis";
157
+ }
158
+
150
159
  function analysisTaskStatusLabel(status) {
151
160
  return ({
152
161
  pending: "待执行",
@@ -2646,6 +2655,7 @@ async function openMembersDialog(targetWork = state.work) {
2646
2655
  if (!targetWork) return;
2647
2656
  memberDialogWork = targetWork;
2648
2657
  const canManage = ["admin", "owner"].includes(String(targetWork.accessRole));
2658
+ $("#members-settings-return").classList.toggle("hidden", $("#settings-hub-view").classList.contains("hidden"));
2649
2659
  $("#members-dialog-eyebrow").textContent = `作品权限 · 《${targetWork.title}》`;
2650
2660
  $("#members-dialog-title").textContent = "成员模块权限";
2651
2661
  $("#members-list").innerHTML = '<p class="empty-state">正在读取成员……</p>';
@@ -2755,6 +2765,13 @@ async function showSettingsHub() {
2755
2765
  return true;
2756
2766
  }
2757
2767
 
2768
+ async function returnToSettingsHub(actionSelector, dialogSelector = null) {
2769
+ const dialog = dialogSelector ? $(dialogSelector) : null;
2770
+ if (dialog?.open) dialog.close();
2771
+ if (!(await showSettingsHub())) return;
2772
+ queueMicrotask(() => $(actionSelector)?.focus());
2773
+ }
2774
+
2758
2775
  async function returnFromSettings() {
2759
2776
  const context = settingsReturnContext ?? { view: "shelf" };
2760
2777
  settingsReturnContext = null;
@@ -3438,9 +3455,11 @@ function openReviewDetailDialog(item) {
3438
3455
  const evidenceHtml = evidence.length
3439
3456
  ? `<ul>${evidence.map((entry) => {
3440
3457
  if (!entry || typeof entry !== "object") return `<li>${esc(String(entry))}</li>`;
3441
- const source = entry.chapterTitle || entry.chapterId || "相关证据";
3458
+ const source = entry.sourceTitle || entry.chapterTitle || entry.chapterId || "相关证据";
3442
3459
  const quote = entry.quote ? `<blockquote>${esc(entry.quote)}</blockquote>` : "";
3443
- const supports = entry.supports ? `<small>${esc(entry.supports)}</small>` : "";
3460
+ const supports = entry.reason
3461
+ ? `<small>${esc(entry.reason)}${typeof entry.confidence === "number" ? ` · 置信度 ${Math.round(entry.confidence * 100)}%` : ""}</small>`
3462
+ : entry.supports ? `<small>${esc(entry.supports)}</small>` : "";
3444
3463
  return `<li><strong>${esc(source)}</strong>${quote}${supports}</li>`;
3445
3464
  }).join("")}</ul>`
3446
3465
  : "<p>暂无证据</p>";
@@ -4031,9 +4050,10 @@ async function renderTasks(page = taskListPage) {
4031
4050
  </div>
4032
4051
  </div>
4033
4052
  </section>
4034
- ${tasks.length ? `<table class="table-list task-table"><thead><tr><th>分析类型</th><th>范围</th><th>状态</th><th>进度</th><th>操作</th></tr></thead><tbody>${tasks.map((item) => `
4053
+ ${tasks.length ? `<table class="table-list task-table"><thead><tr><th>分析类型</th><th>任务模型</th><th>范围</th><th>状态</th><th>进度</th><th>操作</th></tr></thead><tbody>${tasks.map((item) => `
4035
4054
  <tr>
4036
4055
  <td>${esc(analysisTaskTypeLabel(item.taskType))}</td>
4056
+ <td>${esc(item.model?.displayName || "运行时使用默认模型")}</td>
4037
4057
  <td>${esc(item.scopeSummary || taskScopeLabel(item.scope?.type || "book"))}</td>
4038
4058
  <td class="task-status-cell">${renderAnalysisTaskStatus(item)}</td>
4039
4059
  <td class="task-progress-cell">${renderAnalysisTaskProgress(item)}</td>
@@ -4113,7 +4133,7 @@ async function renderTasks(page = taskListPage) {
4113
4133
  const cancel = button.parentElement.querySelector("[data-cancel-task]");
4114
4134
  if (cancel) cancel.textContent = "取消运行";
4115
4135
  scheduleTaskProgressRefresh(workId, 1);
4116
- const completed = await api(`/api/tasks/${button.dataset.runTask}/run`, { method: "POST", body: { modelId: $("#ai-model").value || undefined } });
4136
+ const completed = await api(`/api/tasks/${button.dataset.runTask}/run`, { method: "POST", body: {} });
4117
4137
  toast(completed.status === "cancelled" ? "分析任务已取消" : completed.status === "expired" ? "正文已变化,本次分析已过期" : "分析已完成");
4118
4138
  if (state.module === "tasks" && state.work?.id === workId) await renderTasks();
4119
4139
  } catch (error) {
@@ -4440,6 +4460,7 @@ function openTaskDetailDialog(task, trace) {
4440
4460
  <section class="task-detail-overview">
4441
4461
  <p><strong>任务 ID</strong><br><code>${esc(task.id)}</code><br><small>创建于 ${esc(formatDateTime(task.createdAt))} · 更新于 ${esc(formatDateTime(task.updatedAt))}</small></p>
4442
4462
  <p><strong>类型</strong> ${esc(analysisTaskTypeLabel(task.taskType))}</p>
4463
+ <p><strong>任务模型</strong> ${esc(task.model?.displayName || "运行时使用默认模型")}${task.model?.modelId ? ` · <code>${esc(task.model.modelId)}</code>` : ""}</p>
4443
4464
  <p><strong>状态</strong> ${esc(analysisTaskStatusLabel(task.status))} · 进度 ${Number(task.progress ?? 0)}%</p>
4444
4465
  <p><strong>范围摘要</strong> ${esc(task.scopeSummary || "未指定")}</p>
4445
4466
  <div><strong>范围详情</strong><ul>${detailHtml}</ul></div>
@@ -4457,11 +4478,11 @@ function openTaskDetailDialog(task, trace) {
4457
4478
 
4458
4479
  function renderProviderCards(providers, models) {
4459
4480
  return providers.length ? `<div class="card-grid provider-card-grid">${providers.map((provider) => `
4460
- <article class="record-card provider-card"><small>平台级 · ${esc(providerStatusLabel(provider.status))} · ${esc(providerConnectionLabel(provider.connectionStatus))}</small><h3>${esc(provider.name)}</h3>
4481
+ <article class="record-card provider-card"><small>平台级 · ${esc(providerProtocolLabel(provider.protocol))} · ${esc(providerStatusLabel(provider.status))} · ${esc(providerConnectionLabel(provider.connectionStatus))}</small><h3>${esc(provider.name)}</h3>
4461
4482
  <p>${esc(provider.baseUrl)}\n密钥:${esc(provider.apiKey)}\n并发:${provider.concurrencyLimit} · 每分钟请求:${provider.rpmLimit} · 最大输出:${provider.maxTokens ?? 32000}${provider.lastError ? `\n错误:${esc(provider.lastError)}` : ""}</p>
4462
4483
  <div class="provider-models">${models.filter((model) => model.providerId === provider.id).map((model) => `<button class="pill model-pill" type="button" data-edit-model="${esc(model.id)}" aria-label="编辑模型 ${esc(model.displayName)}">${esc(model.displayName)} · ${model.enabled ? "启用" : "停用"} · 思考模式 ${model.thinkingEnabled ? "开启" : "关闭"} · 上下文 ${Number(model.contextWindow ?? 128000).toLocaleString("zh-CN")} 令牌 · 最大输出 ${Number(model.preset?.max_tokens ?? 32000).toLocaleString("zh-CN")}</button>`).join("")}</div>
4463
4484
  <div class="card-actions"><button data-edit-provider="${esc(provider.id)}">编辑配置</button><button data-test-provider="${esc(provider.id)}">测试连接</button><button data-add-model="${esc(provider.id)}">添加模型</button></div></article>`).join("")}</div>`
4464
- : emptyModule("尚未配置 AI 供应商", "添加 OpenAI 兼容接口地址和密钥,测试成功后再添加模型。");
4485
+ : emptyModule("尚未配置 AI 供应商", "添加 OpenAI 或 Anthropic 兼容接口地址和密钥,测试成功后再添加模型。");
4465
4486
  }
4466
4487
 
4467
4488
  function bindPlatformProviderActions(host, providers, models) {
@@ -6401,14 +6422,26 @@ function openReviewDialog() {
6401
6422
  async function openTaskDialog() {
6402
6423
  const chapterOptions = state.work.volumes.flatMap((volume) => volume.chapters.map((chapter) => [chapter.id, `${volume.title} / ${chapter.title}`]));
6403
6424
  let relationshipCharacters = [];
6425
+ let taskModels = [];
6426
+ let taskDefaults = [];
6404
6427
  try {
6405
- relationshipCharacters = canReadModule("characters")
6406
- ? await apiAllPages(`/api/works/${state.work.id}/characters`)
6407
- : [];
6428
+ [relationshipCharacters, taskModels, taskDefaults] = await Promise.all([
6429
+ canReadModule("characters")
6430
+ ? apiAllPages(`/api/works/${state.work.id}/characters`)
6431
+ : Promise.resolve([]),
6432
+ api(`/api/works/${state.work.id}/models`),
6433
+ api(`/api/works/${state.work.id}/task-defaults`)
6434
+ ]);
6408
6435
  } catch (error) {
6409
- toast(`角色列表加载失败:${error.message}`, "error");
6436
+ toast(`分析任务配置加载失败:${error.message}`, "error");
6410
6437
  return;
6411
6438
  }
6439
+ const defaultModelByTask = new Map(taskDefaults.map((item) => [item.taskType, item.model.id]));
6440
+ const availableTaskModels = taskModels.filter((model) =>
6441
+ model.enabled
6442
+ && model.providerStatus === "enabled"
6443
+ && model.providerConnectionStatus === "success"
6444
+ );
6412
6445
  const characterOptions = relationshipCharacters.map((character) => [character.id, character.name]);
6413
6446
  const relationshipCharacterPicker = `<div class="form-field relationship-character-field">
6414
6447
  <span id="relationship-character-label">被分析角色(可多选)</span>
@@ -6429,6 +6462,11 @@ async function openTaskDialog() {
6429
6462
  </div>`;
6430
6463
  const defaultTaskType = ANALYSIS_TYPES[0].value;
6431
6464
  const taskTypeField = `<div class="form-field analysis-type-field"><label>分析类型<select name="taskType" aria-describedby="analysis-type-description">${ANALYSIS_TYPES.map(({ value, label }) => `<option value="${esc(value)}" ${value === defaultTaskType ? "selected" : ""}>${esc(label)}</option>`).join("")}</select></label><p id="analysis-type-description" class="analysis-type-description" aria-live="polite">${esc(analysisTypeDescription(defaultTaskType))}</p></div>`;
6465
+ const defaultModelId = defaultModelByTask.get(analysisTaskModelPurpose(defaultTaskType)) ?? "";
6466
+ const modelField = `<label>任务模型<select name="modelId" required aria-describedby="analysis-task-model-help">
6467
+ <option value="" ${availableTaskModels.some((model) => model.id === defaultModelId) ? "" : "selected"} disabled>${availableTaskModels.length ? "请选择模型" : "没有可用模型"}</option>
6468
+ ${availableTaskModels.map((model) => `<option value="${esc(model.id)}" ${model.id === defaultModelId ? "selected" : ""}>${esc(modelOptionLabel(model))}</option>`).join("")}
6469
+ </select><small id="analysis-task-model-help">默认值来自“本书 AI 设置”,只修改当前任务,不会改变全书默认模型。</small></label>`;
6432
6470
  const chapterField = `<label class="task-chapter-field">章节<select name="chapterId">${chapterOptions.map(([key, text], index) => `<option value="${esc(key)}" ${index === 0 ? "selected" : ""}>${esc(text)}</option>`).join("")}</select></label>`;
6433
6471
  const relationshipFields = `<div class="relationship-analysis-options hidden">
6434
6472
  ${relationshipCharacterPicker}
@@ -6439,8 +6477,9 @@ async function openTaskDialog() {
6439
6477
  </div>
6440
6478
  <label>额外分析提示<textarea name="additionalPrompt" maxlength="10000" placeholder="例如:重点识别权力继承、师承变化或隐秘亲缘关系"></textarea><small>将同时追加到证据收集和全局关系归纳提示词,仅影响本次任务。</small></label>
6441
6479
  </div>`;
6442
- openDialog("开始 AI 分析", taskTypeField + field("scopeType", "分析范围", "select", "chapter", [["chapter", "指定章节"], ["book", "全书"]]) + chapterField + relationshipFields, async (form) => {
6480
+ openDialog("开始 AI 分析", taskTypeField + modelField + field("scopeType", "分析范围", "select", "chapter", [["chapter", "指定章节"], ["book", "全书"]]) + chapterField + relationshipFields, async (form) => {
6443
6481
  const taskType = String(form.get("taskType"));
6482
+ const modelId = String(form.get("modelId"));
6444
6483
  const scopeType = String(form.get("scopeType"));
6445
6484
  const includeAllSettings = taskType === "relationship-analysis" && scopeType === "book-with-settings";
6446
6485
  const settingsOnly = taskType === "relationship-analysis" && scopeType === "settings";
@@ -6452,7 +6491,7 @@ async function openTaskDialog() {
6452
6491
  : taskType === "character-identity-audit" || scopeType === "book" || includeAllSettings
6453
6492
  ? { type: "book", ...(includeAllSettings ? { includeAllSettings: true } : {}), ...(additionalPrompt ? { additionalPrompt } : {}), ...(characterIds.length ? { characterIds } : {}), ...(replaceExistingRelationships ? { replaceExistingRelationships: true } : {}) }
6454
6493
  : { type: "chapter", chapterId: form.get("chapterId"), ...(additionalPrompt ? { additionalPrompt } : {}), ...(characterIds.length ? { characterIds } : {}), ...(replaceExistingRelationships ? { replaceExistingRelationships: true } : {}) };
6455
- await api(`/api/works/${state.work.id}/tasks`, { method: "POST", body: { taskType, scope } });
6494
+ await api(`/api/works/${state.work.id}/tasks`, { method: "POST", body: { taskType, scope, modelId } });
6456
6495
  taskListPage = 1;
6457
6496
  toast("分析任务已创建,已进入任务队列");
6458
6497
  void renderTasks(1).catch((error) => toast(`任务已创建,但列表刷新失败:${error.message}`, "error"));
@@ -6463,6 +6502,7 @@ async function openTaskDialog() {
6463
6502
  errorPrefix: "任务创建失败:"
6464
6503
  });
6465
6504
  const taskTypeSelect = $("#dialog-fields").querySelector('select[name="taskType"]');
6505
+ const taskModelSelect = $("#dialog-fields").querySelector('select[name="modelId"]');
6466
6506
  const scopeTypeSelect = $("#dialog-fields").querySelector('select[name="scopeType"]');
6467
6507
  const chapterSelect = $("#dialog-fields").querySelector('select[name="chapterId"]');
6468
6508
  const chapterFieldElement = chapterSelect.closest(".task-chapter-field");
@@ -6544,8 +6584,13 @@ async function openTaskDialog() {
6544
6584
  filterRelationshipCharacters();
6545
6585
  syncChapterField();
6546
6586
  };
6587
+ const syncTaskModelDefault = () => {
6588
+ const defaultId = defaultModelByTask.get(analysisTaskModelPurpose(taskTypeSelect.value)) ?? "";
6589
+ taskModelSelect.value = availableTaskModels.some((model) => model.id === defaultId) ? defaultId : "";
6590
+ };
6547
6591
  taskTypeSelect.addEventListener("change", () => {
6548
6592
  description.textContent = analysisTypeDescription(taskTypeSelect.value);
6593
+ syncTaskModelDefault();
6549
6594
  syncRelationshipOptions();
6550
6595
  });
6551
6596
  relationshipCharacterTrigger.addEventListener("click", () => {
@@ -6571,13 +6616,24 @@ async function openTaskDialog() {
6571
6616
  }
6572
6617
 
6573
6618
  function openProviderDialog(item) {
6574
- openDialog(item ? "编辑 AI 供应商" : "新建 AI 供应商", field("name", "显示名称", "text", item?.name) + field("baseUrl", "OpenAI 兼容接口地址", "url", item?.baseUrl ?? "https://api.openai.com/v1") + field("apiKey", item ? "替换 API 密钥(留空则不变)" : "API 密钥", "password") + field("concurrencyLimit", "最大并发请求数", "number", item?.concurrencyLimit ?? 10) + field("rpmLimit", "每分钟请求上限", "number", item?.rpmLimit ?? 10) + field("maxTokens", "最大输出令牌数", "number", item?.maxTokens ?? 32000) + field("note", "用途备注", "textarea", item?.note) + field("enabled", item ? "启用供应商" : "立即启用", "checkbox", item ? item.status === "enabled" : true), async (form) => {
6575
- const body = { name: form.get("name"), baseUrl: form.get("baseUrl"), concurrencyLimit: Number(form.get("concurrencyLimit")), rpmLimit: Number(form.get("rpmLimit")), maxTokens: Number(form.get("maxTokens")), note: form.get("note"), status: form.get("enabled") === "on" ? "enabled" : "disabled" };
6619
+ const protocol = item?.protocol ?? "openai-chat-completions";
6620
+ const defaultBaseUrl = protocol === "anthropic-messages" ? "https://api.anthropic.com" : "https://api.openai.com/v1";
6621
+ openDialog(item ? "编辑 AI 供应商" : "新建 AI 供应商", field("name", "显示名称", "text", item?.name) + field("protocol", "接口协议", "select", protocol, [["openai-chat-completions", "OpenAI Chat Completions"], ["anthropic-messages", "Anthropic Messages"]]) + field("baseUrl", "API 基础地址", "url", item?.baseUrl ?? defaultBaseUrl) + field("apiKey", item ? "替换 API 密钥(留空则不变)" : "API 密钥", "password") + field("concurrencyLimit", "最大并发请求数", "number", item?.concurrencyLimit ?? 10) + field("rpmLimit", "每分钟请求上限", "number", item?.rpmLimit ?? 10) + field("maxTokens", "最大输出令牌数", "number", item?.maxTokens ?? 32000) + field("note", "用途备注", "textarea", item?.note) + field("enabled", item ? "启用供应商" : "立即启用", "checkbox", item ? item.status === "enabled" : true), async (form) => {
6622
+ const body = { name: form.get("name"), protocol: form.get("protocol"), baseUrl: form.get("baseUrl"), concurrencyLimit: Number(form.get("concurrencyLimit")), rpmLimit: Number(form.get("rpmLimit")), maxTokens: Number(form.get("maxTokens")), note: form.get("note"), status: form.get("enabled") === "on" ? "enabled" : "disabled" };
6576
6623
  if (!item || String(form.get("apiKey") ?? "").trim()) body.apiKey = form.get("apiKey");
6577
6624
  await api(item ? `/api/providers/${item.id}` : "/api/platform/ai/providers", { method: item ? "PATCH" : "POST", body });
6578
6625
  await renderPlatformAiConfig();
6579
6626
  await loadModels();
6580
- }, item ? "限流与凭据" : "OpenAI 兼容协议");
6627
+ }, item ? "协议、限流与凭据" : "OpenAI / Anthropic 兼容协议");
6628
+ if (!item) {
6629
+ const protocolSelect = $("#dialog-fields select[name='protocol']");
6630
+ const baseUrlInput = $("#dialog-fields input[name='baseUrl']");
6631
+ protocolSelect.addEventListener("change", () => {
6632
+ baseUrlInput.value = protocolSelect.value === "anthropic-messages"
6633
+ ? "https://api.anthropic.com"
6634
+ : "https://api.openai.com/v1";
6635
+ });
6636
+ }
6581
6637
  }
6582
6638
 
6583
6639
  function openModelDialog(providerId, item = null) {
@@ -7549,6 +7605,7 @@ $("#register-form").addEventListener("submit", async (event) => {
7549
7605
  });
7550
7606
  $("#settings-return").addEventListener("click", () => returnFromSettings().catch((error) => toast(error.message, "error")));
7551
7607
  $("#platform-ai-button").addEventListener("click", () => showPlatformAi().catch((error) => toast(error.message, "error")));
7608
+ $("#platform-ai-return").addEventListener("click", () => returnToSettingsHub("#platform-ai-button").catch((error) => toast(error.message, "error")));
7552
7609
  $("#user-management-button").addEventListener("click", openUsersDialog);
7553
7610
  $("#platform-ui-settings-button").addEventListener("click", openPlatformUiSettingsDialog);
7554
7611
  $("#collaboration-button").addEventListener("click", () => openMembersDialog());
@@ -7558,7 +7615,9 @@ $("#presence-button").addEventListener("click", () => {
7558
7615
  $("#presence-button").setAttribute("aria-expanded", String(open));
7559
7616
  });
7560
7617
  $("#users-dialog-close").addEventListener("click", () => $("#users-dialog").close());
7618
+ $("#users-settings-return").addEventListener("click", () => returnToSettingsHub("#user-management-button", "#users-dialog").catch((error) => toast(error.message, "error")));
7561
7619
  $("#platform-ui-settings-close").addEventListener("click", () => $("#platform-ui-settings-dialog").close());
7620
+ $("#platform-ui-settings-return").addEventListener("click", () => returnToSettingsHub("#platform-ui-settings-button", "#platform-ui-settings-dialog").catch((error) => toast(error.message, "error")));
7562
7621
  $("#platform-ui-settings-cancel").addEventListener("click", () => $("#platform-ui-settings-dialog").close());
7563
7622
  $("#platform-ui-settings-form").addEventListener("submit", async (event) => {
7564
7623
  event.preventDefault();
@@ -7588,6 +7647,7 @@ $("#platform-ui-settings-form").addEventListener("submit", async (event) => {
7588
7647
  }
7589
7648
  });
7590
7649
  $("#members-dialog-close").addEventListener("click", () => $("#members-dialog").close());
7650
+ $("#members-settings-return").addEventListener("click", () => returnToSettingsHub("#collaboration-button", "#members-dialog").catch((error) => toast(error.message, "error")));
7591
7651
  $("#members-dialog").addEventListener("close", () => {
7592
7652
  memberDialogWork = null;
7593
7653
  memberDialogMembers = [];
@@ -7657,6 +7717,7 @@ function cleanupExpandedRelationshipMap() {
7657
7717
  $("#relationship-map-close").addEventListener("click", () => $("#relationship-map-dialog").close());
7658
7718
  $("#relationship-map-dialog").addEventListener("close", cleanupExpandedRelationshipMap);
7659
7719
  $("#appearance-button").addEventListener("click", openAppearanceDialog);
7720
+ $("#appearance-settings-return").addEventListener("click", () => returnToSettingsHub("#appearance-button", "#appearance-dialog").catch((error) => toast(error.message, "error")));
7660
7721
  $("#toggle-whitespace-appearance").addEventListener("click", toggleChapterWhitespaceVisibility);
7661
7722
  $("#theme-toggle").addEventListener("click", () => {
7662
7723
  const theme = nextTheme(currentColorTheme());
@@ -10,6 +10,7 @@ export function reviewStatusLabel(value: unknown): string;
10
10
  export function taskScopeLabel(value: unknown): string;
11
11
  export function providerStatusLabel(value: unknown): string;
12
12
  export function providerConnectionLabel(value: unknown): string;
13
+ export function providerProtocolLabel(value: unknown): string;
13
14
  export function chapterVersionSourceLabel(value: unknown): string;
14
15
  export function occurrenceRoleLabel(value: unknown): string;
15
16
  export function searchResultTypeLabel(value: unknown): string;
@@ -37,6 +37,7 @@ export function reviewItemTypeLabel(value) {
37
37
  return enumLabel({
38
38
  consistency: "一致性问题",
39
39
  "character-duplicate": "角色重复",
40
+ "character-name-variant": "疑似人物名错字",
40
41
  "timeline-conflict": "时间线冲突",
41
42
  "setting-conflict": "设定冲突",
42
43
  "relationship-conflict": "关系冲突",
@@ -64,6 +65,13 @@ export function providerConnectionLabel(value) {
64
65
  return enumLabel({ unchecked: "未测试", success: "连接正常", failed: "连接失败" }, value, "未知连接状态");
65
66
  }
66
67
 
68
+ export function providerProtocolLabel(value) {
69
+ return enumLabel({
70
+ "openai-chat-completions": "OpenAI Chat Completions",
71
+ "anthropic-messages": "Anthropic Messages"
72
+ }, value, "未知接口协议");
73
+ }
74
+
67
75
  export function chapterVersionSourceLabel(value) {
68
76
  return enumLabel({ manual: "人工保存", auto: "自动保存", "ai-suggestion": "AI 建议", restore: "历史恢复", import: "文件导入", create: "初始版本" }, value, "其他来源");
69
77
  }
@@ -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=20260726-relationship-trace-results-v8">
13
+ <link rel="stylesheet" href="/styles.css?v=20260726-settings-select-spacing-v1">
14
14
  </head>
15
15
  <body class="auth-pending">
16
16
  <section id="auth-view" class="auth-view hidden" aria-labelledby="auth-title">
@@ -159,7 +159,10 @@
159
159
  <section id="platform-ai-view" class="shelf-view hidden" aria-labelledby="platform-ai-title">
160
160
  <div class="shelf-header">
161
161
  <div><span class="eyebrow">平台资源</span><h1 id="platform-ai-title">AI 管理</h1><p>供应商、模型与全局系统提示词对所有作品生效;每本书可在“AI 设置”中追加自己的系统提示词。</p></div>
162
- <button id="platform-new-provider" class="primary-button" type="button">新建供应商</button>
162
+ <div class="settings-detail-actions">
163
+ <button id="platform-ai-return" class="ghost-button settings-parent-button" type="button">返回设置</button>
164
+ <button id="platform-new-provider" class="primary-button" type="button">新建供应商</button>
165
+ </div>
163
166
  </div>
164
167
  <div id="platform-ai-content" class="module-content"></div>
165
168
  </section>
@@ -497,7 +500,10 @@
497
500
  <form id="appearance-form" method="dialog">
498
501
  <div class="dialog-header">
499
502
  <div><span class="eyebrow">本机偏好</span><h2>显示设置</h2></div>
500
- <button class="dialog-close" value="cancel" aria-label="关闭" type="submit">×</button>
503
+ <div class="settings-dialog-header-actions">
504
+ <button id="appearance-settings-return" class="ghost-button settings-parent-button" type="button">返回设置</button>
505
+ <button class="dialog-close" value="cancel" aria-label="关闭" type="submit">×</button>
506
+ </div>
501
507
  </div>
502
508
  <div class="appearance-grid">
503
509
  <label>中文字体
@@ -572,14 +578,14 @@
572
578
  </dialog>
573
579
 
574
580
  <dialog id="users-dialog" class="dialog wide-dialog" aria-labelledby="users-dialog-title">
575
- <div class="dialog-header"><div><span class="eyebrow">系统管理员</span><h2 id="users-dialog-title">用户管理</h2></div><button id="users-dialog-close" class="dialog-close" aria-label="关闭" type="button">×</button></div>
581
+ <div class="dialog-header"><div><span class="eyebrow">系统管理员</span><h2 id="users-dialog-title">用户管理</h2></div><div class="settings-dialog-header-actions"><button id="users-settings-return" class="ghost-button settings-parent-button" type="button">返回设置</button><button id="users-dialog-close" class="dialog-close" aria-label="关闭" type="button">×</button></div></div>
576
582
  <div class="access-dialog-body">
577
583
  <div id="users-list" class="access-list"></div>
578
584
  </div>
579
585
  </dialog>
580
586
 
581
587
  <dialog id="platform-ui-settings-dialog" class="dialog" aria-labelledby="platform-ui-settings-title">
582
- <div class="dialog-header"><div><span class="eyebrow">系统管理员</span><h2 id="platform-ui-settings-title">界面与分页</h2></div><button id="platform-ui-settings-close" class="dialog-close" aria-label="关闭" type="button">×</button></div>
588
+ <div class="dialog-header"><div><span class="eyebrow">系统管理员</span><h2 id="platform-ui-settings-title">界面与分页</h2></div><div class="settings-dialog-header-actions"><button id="platform-ui-settings-return" class="ghost-button settings-parent-button" type="button">返回设置</button><button id="platform-ui-settings-close" class="dialog-close" aria-label="关闭" type="button">×</button></div></div>
583
589
  <form id="platform-ui-settings-form">
584
590
  <div class="dialog-fields">
585
591
  <label>Toast 提示位置<select id="toast-position" name="toastPosition" aria-label="Toast 提示位置"><option value="bottom-right">右下角(默认)</option><option value="top-right">右上角</option></select></label>
@@ -660,7 +666,7 @@
660
666
  </dialog>
661
667
 
662
668
  <dialog id="members-dialog" class="dialog wide-dialog" aria-labelledby="members-dialog-title">
663
- <div class="dialog-header"><div><span id="members-dialog-eyebrow" class="eyebrow">作品权限</span><h2 id="members-dialog-title">成员模块权限</h2></div><button id="members-dialog-close" class="dialog-close" aria-label="关闭" type="button">×</button></div>
669
+ <div class="dialog-header"><div><span id="members-dialog-eyebrow" class="eyebrow">作品权限</span><h2 id="members-dialog-title">成员模块权限</h2></div><div class="settings-dialog-header-actions"><button id="members-settings-return" class="ghost-button settings-parent-button hidden" type="button">返回设置</button><button id="members-dialog-close" class="dialog-close" aria-label="关闭" type="button">×</button></div></div>
664
670
  <div class="access-dialog-body">
665
671
  <form id="member-permission-form" class="member-permission-form">
666
672
  <label class="member-person-field">选择成员<select id="member-user-select" required></select><small>先选择已注册用户,再逐项配置模块权限。</small></label>
@@ -745,6 +751,6 @@
745
751
  <div id="auth-loading" class="auth-loading" role="status" aria-label="正在载入工作台"></div>
746
752
  <script id="vditorIconScript" src="/vendor/vditor/dist/js/icons/ant.js?v=3.11.2"></script>
747
753
  <script src="/vendor/vditor/dist/index.min.js?v=3.11.2"></script>
748
- <script type="module" src="/app.js?v=20260726-relationship-trace-results-v8"></script>
754
+ <script type="module" src="/app.js?v=20260726-anthropic-messages-v2"></script>
749
755
  </body>
750
756
  </html>
@@ -521,6 +521,9 @@ body.is-panel-resizing { cursor: col-resize; user-select: none; }
521
521
  .shelf-header { display: flex; align-items: flex-end; justify-content: space-between; gap: 30px; width: 100%; max-width: 1400px; margin: 0 auto 38px; }
522
522
  .shelf-header h1 { margin: 0 0 10px; font-size: clamp(30px, 4vw, 52px); font-weight: 560; letter-spacing: -.04em; }
523
523
  .shelf-header p { margin: 0; color: var(--muted); }
524
+ .settings-detail-actions, .settings-dialog-header-actions { display: flex; align-items: center; gap: 10px; }
525
+ .settings-detail-actions { flex: 0 0 auto; }
526
+ .settings-parent-button { white-space: nowrap; }
524
527
  .settings-hub-header { align-items: center; }
525
528
  .settings-hub-header h1 { font-size: clamp(26px, 3vw, 36px); line-height: 1.15; letter-spacing: -.02em; }
526
529
  .settings-hub-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; max-width: 1000px; margin: 0 auto; }
@@ -620,6 +623,17 @@ body.is-panel-resizing { cursor: col-resize; user-select: none; }
620
623
  .member-permission-row select { min-height: 34px; padding-block: 6px; font-size: 11px; }
621
624
  .member-permission-form .primary-button { justify-self: end; min-height: 38px; white-space: nowrap; }
622
625
  .permission-hidden { display: none !important; }
626
+ :is(#users-dialog, #platform-ui-settings-dialog, #members-dialog, #appearance-dialog) select:not([multiple]) {
627
+ appearance: none;
628
+ -webkit-appearance: none;
629
+ padding-inline-end: 40px;
630
+ background-image:
631
+ linear-gradient(45deg, transparent 50%, var(--muted) 50%),
632
+ linear-gradient(135deg, var(--muted) 50%, transparent 50%);
633
+ background-position: right 21px center, right 16px center;
634
+ background-repeat: no-repeat;
635
+ background-size: 5px 5px, 5px 5px;
636
+ }
623
637
  .app-shell.ai-hidden-mode:not(.shelf-mode) { grid-template-columns: var(--left-panel-width) minmax(480px, 1fr); }
624
638
  .app-shell.prose-hidden-mode:not(.shelf-mode) .left-actions,
625
639
  .app-shell.prose-hidden-mode:not(.shelf-mode) .panel-heading,
@@ -2587,6 +2601,8 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
2587
2601
  .shelf-header, .module-header { align-items: stretch; flex-direction: column; gap: 14px; margin-bottom: 20px; }
2588
2602
  .shelf-header h1 { font-size: clamp(24px, 7.5vw, 32px); }
2589
2603
  .shelf-header .primary-button { align-self: flex-start; min-width: 150px; min-height: 40px; padding: 10px 18px; }
2604
+ .settings-detail-actions { align-items: stretch; flex-direction: column; }
2605
+ .settings-detail-actions .primary-button, .settings-detail-actions .settings-parent-button { align-self: stretch; width: 100%; }
2590
2606
  .module-header-actions { width: 100%; }
2591
2607
  .book-shelf { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px 14px; }
2592
2608
  .book-info { padding-top: 10px; padding-right: 0; }
@@ -2632,6 +2648,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
2632
2648
  }
2633
2649
  .dialog-header { padding: 14px 16px 12px; }
2634
2650
  .dialog-header h2 { font-size: 20px; }
2651
+ .settings-dialog-header-actions { gap: 6px; }
2635
2652
  .dialog-fields, .appearance-grid, .access-dialog-body, .account-settings-body, .versions-list, .entity-history-list { padding-inline: 16px; }
2636
2653
  .task-detail-overview { grid-template-columns: minmax(0, 1fr); padding: 12px; }
2637
2654
  .task-detail-overview > * { grid-column: 1 !important; }
@@ -0,0 +1,196 @@
1
+ import { pinyin } from "pinyin-pro";
2
+ export const RELATIONSHIP_SEARCH_POLICY_VERSION = 1;
3
+ export function normalizeRelationshipSearchText(value) {
4
+ return value.normalize("NFKC").toLocaleLowerCase("zh-CN");
5
+ }
6
+ function encodedCodePoint(value) {
7
+ return [...value].map((character) => character.codePointAt(0).toString(16)).join("x");
8
+ }
9
+ function safePinyinToken(value) {
10
+ const normalized = value.normalize("NFKC").toLocaleLowerCase("zh-CN").replaceAll("ü", "v");
11
+ return /^[a-z0-9]+$/u.test(normalized) ? `p${normalized}` : `u${encodedCodePoint(normalized)}`;
12
+ }
13
+ export function relationshipCharacterTokens(value) {
14
+ return [...normalizeRelationshipSearchText(value)].map((character) => `u${encodedCodePoint(character)}`);
15
+ }
16
+ export function relationshipPinyinSyllables(value) {
17
+ const normalized = normalizeRelationshipSearchText(value);
18
+ return pinyin(normalized, { toneType: "none", type: "array" })
19
+ .map((item) => item.normalize("NFKC").toLocaleLowerCase("zh-CN").replaceAll("ü", "v"));
20
+ }
21
+ export function relationshipPinyinTokens(value) {
22
+ return relationshipPinyinSyllables(value).map(safePinyinToken);
23
+ }
24
+ export function relationshipCharacterTokenText(value) {
25
+ return relationshipCharacterTokens(value).join(" ");
26
+ }
27
+ export function relationshipPinyinTokenText(value) {
28
+ return relationshipPinyinTokens(value).join(" ");
29
+ }
30
+ export function ftsPhrase(tokens) {
31
+ return `"${tokens.join(" ").replaceAll('"', '""')}"`;
32
+ }
33
+ export function damerauLevenshteinDistance(left, right, maximum = Number.POSITIVE_INFINITY) {
34
+ if (Math.abs(left.length - right.length) > maximum)
35
+ return maximum + 1;
36
+ const previousPrevious = new Array(right.length + 1).fill(0);
37
+ let previous = Array.from({ length: right.length + 1 }, (_, index) => index);
38
+ for (let leftIndex = 1; leftIndex <= left.length; leftIndex += 1) {
39
+ const current = new Array(right.length + 1).fill(0);
40
+ current[0] = leftIndex;
41
+ let rowMinimum = current[0];
42
+ for (let rightIndex = 1; rightIndex <= right.length; rightIndex += 1) {
43
+ const substitutionCost = left[leftIndex - 1] === right[rightIndex - 1] ? 0 : 1;
44
+ current[rightIndex] = Math.min(previous[rightIndex] + 1, current[rightIndex - 1] + 1, previous[rightIndex - 1] + substitutionCost);
45
+ if (leftIndex > 1 && rightIndex > 1
46
+ && left[leftIndex - 1] === right[rightIndex - 2]
47
+ && left[leftIndex - 2] === right[rightIndex - 1]) {
48
+ current[rightIndex] = Math.min(current[rightIndex], previousPrevious[rightIndex - 2] + 1);
49
+ }
50
+ rowMinimum = Math.min(rowMinimum, current[rightIndex]);
51
+ }
52
+ if (rowMinimum > maximum)
53
+ return maximum + 1;
54
+ for (let index = 0; index < previous.length; index += 1)
55
+ previousPrevious[index] = previous[index];
56
+ previous = current;
57
+ }
58
+ return previous[right.length];
59
+ }
60
+ export class RelationshipApproximateMatchLimitError extends Error {
61
+ maximumCandidates;
62
+ constructor(maximumCandidates) {
63
+ super(`Approximate relationship match candidates exceeded ${maximumCandidates}`);
64
+ this.maximumCandidates = maximumCandidates;
65
+ this.name = "RelationshipApproximateMatchLimitError";
66
+ }
67
+ }
68
+ function selectApproximateNameMatches(matches, referenceLength, limit) {
69
+ const ranked = matches.sort((left, right) => Math.min(left.characterDistance, left.pinyinDistance) - Math.min(right.characterDistance, right.pinyinDistance)
70
+ || Math.abs([...left.observed].length - referenceLength) - Math.abs([...right.observed].length - referenceLength)
71
+ || left.start - right.start);
72
+ const selected = [];
73
+ for (const match of ranked) {
74
+ if (selected.some((item) => match.start < item.end && match.end > item.start))
75
+ continue;
76
+ selected.push(match);
77
+ if (selected.length >= limit)
78
+ break;
79
+ }
80
+ return selected.sort((left, right) => left.start - right.start);
81
+ }
82
+ export function findApproximateNameMatches(content, reference, limit = 3, excludedObserved = new Set(), maximumCandidates = 256) {
83
+ const normalizedContent = normalizeRelationshipSearchText(content);
84
+ const normalizedReference = normalizeRelationshipSearchText(reference).trim();
85
+ const sourceCharacters = [...normalizedContent];
86
+ const utf16Offsets = new Array(sourceCharacters.length + 1).fill(0);
87
+ for (let index = 0; index < sourceCharacters.length; index += 1) {
88
+ utf16Offsets[index + 1] = utf16Offsets[index] + sourceCharacters[index].length;
89
+ }
90
+ const referenceCharacters = [...normalizedReference];
91
+ if (referenceCharacters.length < 2 || sourceCharacters.length === 0)
92
+ return [];
93
+ const hanReference = referenceCharacters.every((character) => /\p{Script=Han}/u.test(character));
94
+ const normalizedExcluded = new Set([...excludedObserved].map((item) => normalizeRelationshipSearchText(item).trim()));
95
+ const referencePinyin = relationshipPinyinSyllables(normalizedReference);
96
+ const sourcePinyin = relationshipPinyinSyllables(normalizedContent);
97
+ const exactCoverage = new Uint8Array(sourceCharacters.length);
98
+ for (let start = 0; start + referenceCharacters.length <= sourceCharacters.length; start += 1) {
99
+ if (!referenceCharacters.every((character, index) => sourceCharacters[start + index] === character))
100
+ continue;
101
+ exactCoverage.fill(1, start, start + referenceCharacters.length);
102
+ }
103
+ const matches = [];
104
+ const seen = new Set();
105
+ for (const windowLength of [referenceCharacters.length, referenceCharacters.length - 1, referenceCharacters.length + 1]) {
106
+ if (windowLength < 1)
107
+ continue;
108
+ for (let start = 0; start + windowLength <= sourceCharacters.length; start += 1) {
109
+ const observedCharacters = sourceCharacters.slice(start, start + windowLength);
110
+ if (observedCharacters.length < 2)
111
+ continue;
112
+ if (hanReference && !observedCharacters.every((character) => /\p{Script=Han}/u.test(character)))
113
+ continue;
114
+ const observed = observedCharacters.join("");
115
+ if (!observed.trim() || observed === normalizedReference || normalizedExcluded.has(observed))
116
+ continue;
117
+ if (exactCoverage.subarray(start, start + windowLength).some((value) => value === 1))
118
+ continue;
119
+ const characterDistance = damerauLevenshteinDistance(referenceCharacters, observedCharacters, 1);
120
+ const observedPinyin = sourcePinyin.slice(start, start + windowLength);
121
+ const pinyinDistance = damerauLevenshteinDistance(referencePinyin, observedPinyin, 1);
122
+ if (characterDistance > 1 && pinyinDistance > 1)
123
+ continue;
124
+ const key = `${start}:${observed}`;
125
+ if (seen.has(key))
126
+ continue;
127
+ seen.add(key);
128
+ matches.push({
129
+ observed,
130
+ start,
131
+ end: start + windowLength,
132
+ utf16Start: utf16Offsets[start],
133
+ utf16End: utf16Offsets[start + windowLength],
134
+ characterDistance,
135
+ pinyinDistance
136
+ });
137
+ if (matches.length > maximumCandidates)
138
+ throw new RelationshipApproximateMatchLimitError(maximumCandidates);
139
+ }
140
+ }
141
+ return selectApproximateNameMatches(matches, referenceCharacters.length, limit);
142
+ }
143
+ export async function findApproximateNameMatchesChunked(content, reference, limit = 3, excludedObserved = new Set(), maximumCandidates = 256, chunkSize = 16_384) {
144
+ const normalizedContent = normalizeRelationshipSearchText(content);
145
+ const referenceLength = [...normalizeRelationshipSearchText(reference).trim()].length;
146
+ if (referenceLength < 2 || !normalizedContent || chunkSize < 1)
147
+ return [];
148
+ const overlapSize = referenceLength + 1;
149
+ const matches = [];
150
+ const seen = new Set();
151
+ let processedCharacters = 0;
152
+ let processedCodeUnits = 0;
153
+ let tail = [];
154
+ let chunk = [];
155
+ const processChunk = async () => {
156
+ if (chunk.length === 0)
157
+ return;
158
+ const prefixLength = tail.length;
159
+ const prefixCodeUnits = tail.reduce((total, character) => total + character.length, 0);
160
+ const combined = [...tail, ...chunk];
161
+ const baseOffset = processedCharacters - prefixLength;
162
+ const baseCodeUnitOffset = processedCodeUnits - prefixCodeUnits;
163
+ const chunkMatches = findApproximateNameMatches(combined.join(""), reference, maximumCandidates + 1, excludedObserved, maximumCandidates);
164
+ for (const match of chunkMatches) {
165
+ if (match.end <= prefixLength)
166
+ continue;
167
+ const adjusted = {
168
+ ...match,
169
+ start: match.start + baseOffset,
170
+ end: match.end + baseOffset,
171
+ utf16Start: match.utf16Start + baseCodeUnitOffset,
172
+ utf16End: match.utf16End + baseCodeUnitOffset
173
+ };
174
+ const key = `${adjusted.start}:${adjusted.observed}`;
175
+ if (seen.has(key))
176
+ continue;
177
+ seen.add(key);
178
+ matches.push(adjusted);
179
+ if (matches.length > maximumCandidates)
180
+ throw new RelationshipApproximateMatchLimitError(maximumCandidates);
181
+ }
182
+ processedCharacters += chunk.length;
183
+ processedCodeUnits += chunk.reduce((total, character) => total + character.length, 0);
184
+ tail = combined.slice(-overlapSize);
185
+ chunk = [];
186
+ await new Promise((resolve) => setImmediate(resolve));
187
+ };
188
+ for (const character of normalizedContent) {
189
+ chunk.push(character);
190
+ if (chunk.length >= chunkSize)
191
+ await processChunk();
192
+ }
193
+ await processChunk();
194
+ return selectApproximateNameMatches(matches, referenceLength, limit);
195
+ }
196
+ //# sourceMappingURL=relationship-search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relationship-search.js","sourceRoot":"","sources":["../src/relationship-search.ts"],"names":[],"mappings":"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"}