@musnows/scriverse 0.8.3 → 0.8.5
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.
- package/dist/ai-stream-timeout.js +9 -6
- package/dist/ai-stream-timeout.js.map +1 -1
- package/dist/ai.js +32 -6
- package/dist/ai.js.map +1 -1
- package/dist/app.js +10 -4
- package/dist/app.js.map +1 -1
- package/dist/database.js +48 -2
- package/dist/database.js.map +1 -1
- package/dist/public/app.js +275 -67
- package/dist/public/index.html +2 -2
- package/dist/public/styles.css +9 -0
- package/dist/server-runtime.js +4 -2
- package/dist/server-runtime.js.map +1 -1
- package/dist/store.js +29 -19
- package/dist/store.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/public/app.js
CHANGED
|
@@ -2035,6 +2035,7 @@ function createAiChatTabState(input = {}) {
|
|
|
2035
2035
|
return existing;
|
|
2036
2036
|
}
|
|
2037
2037
|
const feed = input.feed ?? createAiChatFeed();
|
|
2038
|
+
bindAiFeedAutoScroll(feed);
|
|
2038
2039
|
const tab = aiChatTabManager.open({
|
|
2039
2040
|
workId: String(state.work?.id ?? ""),
|
|
2040
2041
|
conversationId: input.conversationId ?? null,
|
|
@@ -2404,7 +2405,7 @@ function resetAiFeed(
|
|
|
2404
2405
|
const roleplayName = roleplayCharacter?.name;
|
|
2405
2406
|
const roleplayUserName = roleplayUserCharacter?.name;
|
|
2406
2407
|
feed.innerHTML = roleplayName
|
|
2407
|
-
? `<div class="assistant-message"><span class="message-heading"><span>${esc(roleplayName)}</span></span><div class="message-body"><p>正在扮演 ${esc(roleplayName)}。${roleplayUserName ? `你将以 ${esc(roleplayUserName)} 的身份与我互动。` : "
|
|
2408
|
+
? `<div class="assistant-message"><span class="message-heading"><span>${esc(roleplayName)}</span></span><div class="message-body"><p>正在扮演 ${esc(roleplayName)}。${roleplayUserName ? `你将以 ${esc(roleplayUserName)} 的身份与我互动。` : "我可以通过角色卡、人物关系和故事正文回答。"}</p></div></div>`
|
|
2408
2409
|
: '<div class="assistant-message"><span class="message-heading"><span>助手</span></span><div class="message-body"><p>选择章节和模型后,可以问答、续写或校对。所有引用都基于已保存正文。</p></div></div>';
|
|
2409
2410
|
}
|
|
2410
2411
|
|
|
@@ -2449,6 +2450,75 @@ function renderConversationCompactionDivider(compactedMessageCount, totalMessage
|
|
|
2449
2450
|
return appendAiContextCompactionDivider("conversation", messages[boundaryIndex] ?? null, feed);
|
|
2450
2451
|
}
|
|
2451
2452
|
|
|
2453
|
+
function findAiRetryUserMessage(message) {
|
|
2454
|
+
const feed = message.closest(".ai-feed");
|
|
2455
|
+
if (!feed) return null;
|
|
2456
|
+
const messages = [...feed.children].filter((candidate) => candidate.matches(".user-message, .assistant-message"));
|
|
2457
|
+
const messageIndex = messages.indexOf(message);
|
|
2458
|
+
for (let index = messageIndex - 1; index >= 0; index -= 1) {
|
|
2459
|
+
if (messages[index].classList.contains("user-message")) return messages[index];
|
|
2460
|
+
}
|
|
2461
|
+
return null;
|
|
2462
|
+
}
|
|
2463
|
+
|
|
2464
|
+
function aiMessageCitations(message) {
|
|
2465
|
+
try {
|
|
2466
|
+
const citations = JSON.parse(message.dataset.aiCitations ?? "[]");
|
|
2467
|
+
return Array.isArray(citations) ? citations : [];
|
|
2468
|
+
} catch {
|
|
2469
|
+
return [];
|
|
2470
|
+
}
|
|
2471
|
+
}
|
|
2472
|
+
|
|
2473
|
+
async function retryAiMessage(message) {
|
|
2474
|
+
const sourceTab = aiChatTabManager.get(message.closest(".ai-feed")?.dataset.aiTabId);
|
|
2475
|
+
const userMessage = findAiRetryUserMessage(message);
|
|
2476
|
+
const prompt = userMessage?.dataset.copyText ?? "";
|
|
2477
|
+
const userMessageId = userMessage?.dataset.messageId ?? "";
|
|
2478
|
+
if (!sourceTab?.conversationId || !userMessageId || !prompt.trim()) {
|
|
2479
|
+
toast("找不到需要重试的用户指令", "error");
|
|
2480
|
+
return;
|
|
2481
|
+
}
|
|
2482
|
+
if (aiRequestManager.hasActive(sourceTab.id)) {
|
|
2483
|
+
toast("当前对话仍在生成回复,请等待完成或取消后再重试", "error");
|
|
2484
|
+
return;
|
|
2485
|
+
}
|
|
2486
|
+
if (!isActiveAiChatTab(sourceTab)) activateAiChatTab(sourceTab.id, { persistCurrent: false, force: true });
|
|
2487
|
+
await sendAiWithOptions({
|
|
2488
|
+
retry: {
|
|
2489
|
+
message,
|
|
2490
|
+
prompt,
|
|
2491
|
+
citations: aiMessageCitations(userMessage),
|
|
2492
|
+
userMessageId
|
|
2493
|
+
}
|
|
2494
|
+
});
|
|
2495
|
+
}
|
|
2496
|
+
|
|
2497
|
+
function clearAiRetryComposer() {
|
|
2498
|
+
clearAiPromptComposer();
|
|
2499
|
+
}
|
|
2500
|
+
|
|
2501
|
+
function prepareAiRetryState(tab, modelId) {
|
|
2502
|
+
tab.modelId = modelId;
|
|
2503
|
+
tab.selectedModelId = modelId;
|
|
2504
|
+
tab.promptSent = true;
|
|
2505
|
+
clearAiChatTabComposer(tab);
|
|
2506
|
+
if (isActiveAiChatTab(tab)) {
|
|
2507
|
+
state.aiConversationModelId = modelId;
|
|
2508
|
+
state.aiPromptSent = true;
|
|
2509
|
+
syncAiTaskOptions();
|
|
2510
|
+
renderAiRoleplayCharacterSelect();
|
|
2511
|
+
renderAiQuickActions();
|
|
2512
|
+
clearAiRetryComposer();
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
|
|
2516
|
+
function aiRetryStreamRequestBody(body, retry) {
|
|
2517
|
+
return retry?.userMessageId
|
|
2518
|
+
? { ...body, currentMessageId: retry.userMessageId }
|
|
2519
|
+
: body;
|
|
2520
|
+
}
|
|
2521
|
+
|
|
2452
2522
|
function renderMessageCardActions(message) {
|
|
2453
2523
|
let actions = message.querySelector(".message-card-actions");
|
|
2454
2524
|
if (!actions) {
|
|
@@ -2477,7 +2547,20 @@ function renderMessageCardActions(message) {
|
|
|
2477
2547
|
});
|
|
2478
2548
|
actions.append(copy);
|
|
2479
2549
|
}
|
|
2480
|
-
if (message.dataset.
|
|
2550
|
+
if (message.dataset.status === "failed" && message.classList.contains("assistant-message")) {
|
|
2551
|
+
const retry = document.createElement("button");
|
|
2552
|
+
retry.type = "button";
|
|
2553
|
+
retry.className = "message-retry-button";
|
|
2554
|
+
retry.setAttribute("aria-label", "重试");
|
|
2555
|
+
retry.innerHTML = '<svg class="message-action-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M20 11a8 8 0 0 0-14.7-4L4 9"/><path d="M4 4v5h5"/><path d="M4 13a8 8 0 0 0 14.7 4L20 15"/><path d="M20 20v-5h-5"/></svg><span>重试</span>';
|
|
2556
|
+
retry.addEventListener("click", () => {
|
|
2557
|
+
retry.disabled = true;
|
|
2558
|
+
void retryAiMessage(message).finally(() => {
|
|
2559
|
+
if (retry.isConnected) retry.disabled = false;
|
|
2560
|
+
});
|
|
2561
|
+
});
|
|
2562
|
+
actions.append(retry);
|
|
2563
|
+
} else if (message.dataset.messageId && message.classList.contains("assistant-message")) {
|
|
2481
2564
|
const fork = document.createElement("button");
|
|
2482
2565
|
fork.type = "button";
|
|
2483
2566
|
fork.className = "message-fork-button";
|
|
@@ -2539,6 +2622,7 @@ const AI_TOOL_DISPLAY_NAMES = {
|
|
|
2539
2622
|
recall_self: "回忆自身",
|
|
2540
2623
|
image: "读取设定图片",
|
|
2541
2624
|
recall_relationship: "回忆人物关系",
|
|
2625
|
+
recall_story: "回忆故事",
|
|
2542
2626
|
calculate_time: "计算日期"
|
|
2543
2627
|
};
|
|
2544
2628
|
|
|
@@ -2552,10 +2636,14 @@ const AI_TOOL_DESCRIPTIONS = {
|
|
|
2552
2636
|
recall_self: "读取当前扮演角色自己的角色卡、档案,以及自己参与的关系、时间线和正文记忆。",
|
|
2553
2637
|
image: "读取设定正文引用的图片附件,并返回多模态模型的理解内容。",
|
|
2554
2638
|
recall_relationship: "不传角色列表时读取有关系的角色列表;传入一个或多个角色后读取当前角色与这些角色之间的关系详情。",
|
|
2639
|
+
recall_story: "查询当前作品已保存正文中的关键词,返回匹配段落及章节信息。",
|
|
2555
2640
|
calculate_time: "计算日期差值,或从起始日期推算目标日期。"
|
|
2556
2641
|
};
|
|
2557
2642
|
|
|
2558
2643
|
const aiFeedScrollFrames = new WeakMap();
|
|
2644
|
+
const aiFeedAutoScrollStates = new WeakMap();
|
|
2645
|
+
const aiFeedScrollBindings = new WeakSet();
|
|
2646
|
+
const AI_FEED_BOTTOM_THRESHOLD_PX = 24;
|
|
2559
2647
|
let markdownTableMenuTarget = null;
|
|
2560
2648
|
let markdownTableMenuTrigger = null;
|
|
2561
2649
|
|
|
@@ -2596,7 +2684,30 @@ function openMarkdownTableMenu(header, clientX, clientY) {
|
|
|
2596
2684
|
toggle.focus();
|
|
2597
2685
|
}
|
|
2598
2686
|
|
|
2687
|
+
function aiFeedIsNearBottom(feed) {
|
|
2688
|
+
if (!feed.clientHeight) return true;
|
|
2689
|
+
return feed.scrollHeight - feed.scrollTop - feed.clientHeight <= AI_FEED_BOTTOM_THRESHOLD_PX;
|
|
2690
|
+
}
|
|
2691
|
+
|
|
2692
|
+
function bindAiFeedAutoScroll(feed) {
|
|
2693
|
+
if (!feed || aiFeedScrollBindings.has(feed)) return;
|
|
2694
|
+
const update = () => {
|
|
2695
|
+
const shouldFollow = aiFeedIsNearBottom(feed);
|
|
2696
|
+
aiFeedAutoScrollStates.set(feed, shouldFollow);
|
|
2697
|
+
if (shouldFollow) return;
|
|
2698
|
+
const currentFrame = aiFeedScrollFrames.get(feed);
|
|
2699
|
+
if (currentFrame === undefined) return;
|
|
2700
|
+
window.cancelAnimationFrame(currentFrame);
|
|
2701
|
+
aiFeedScrollFrames.delete(feed);
|
|
2702
|
+
};
|
|
2703
|
+
feed.addEventListener("scroll", update, { passive: true });
|
|
2704
|
+
aiFeedScrollBindings.add(feed);
|
|
2705
|
+
update();
|
|
2706
|
+
}
|
|
2707
|
+
|
|
2599
2708
|
function scrollAiFeedToBottom(feed = $("#ai-feed")) {
|
|
2709
|
+
bindAiFeedAutoScroll(feed);
|
|
2710
|
+
if (!aiFeedAutoScrollStates.get(feed)) return;
|
|
2600
2711
|
feed.scrollTop = feed.scrollHeight;
|
|
2601
2712
|
const currentFrame = aiFeedScrollFrames.get(feed);
|
|
2602
2713
|
if (currentFrame !== undefined) window.cancelAnimationFrame(currentFrame);
|
|
@@ -2719,9 +2830,17 @@ function resolveAiProcessDuration(metadata, steps, completedAt) {
|
|
|
2719
2830
|
return Math.max(0, completedTime - Math.min(...startedTimes));
|
|
2720
2831
|
}
|
|
2721
2832
|
|
|
2833
|
+
function shouldRenderAiProcessStep(step) {
|
|
2834
|
+
if (step?.type === "context_compaction") return true;
|
|
2835
|
+
if (step?.type === "tool" && step.toolCall) return true;
|
|
2836
|
+
if (!step?.content || !["thinking", "intermediate"].includes(step.type)) return false;
|
|
2837
|
+
return step.type !== "intermediate" || typeof step.content !== "string" || step.content.trim().length > 0;
|
|
2838
|
+
}
|
|
2839
|
+
|
|
2722
2840
|
function renderAiProcessSteps(message, steps, completed, durationMs = null, visibleContents = null) {
|
|
2723
2841
|
message.querySelector(".ai-process-details")?.remove();
|
|
2724
|
-
|
|
2842
|
+
const renderableSteps = (Array.isArray(steps) ? steps : []).filter(shouldRenderAiProcessStep);
|
|
2843
|
+
if (!renderableSteps.length) return;
|
|
2725
2844
|
const details = document.createElement("details");
|
|
2726
2845
|
details.className = "ai-process-details";
|
|
2727
2846
|
details.open = !completed;
|
|
@@ -2730,11 +2849,11 @@ function renderAiProcessSteps(message, steps, completed, durationMs = null, visi
|
|
|
2730
2849
|
title.textContent = completed ? "思考与执行过程" : "正在思考与执行";
|
|
2731
2850
|
const status = document.createElement("small");
|
|
2732
2851
|
const duration = durationMs === null || durationMs === undefined ? "" : formatAiProcessDuration(durationMs);
|
|
2733
|
-
status.textContent = `${
|
|
2852
|
+
status.textContent = `${renderableSteps.length} 个步骤${duration ? ` · 耗时 ${duration}` : ""}`;
|
|
2734
2853
|
summary.append(title, status);
|
|
2735
2854
|
const list = document.createElement("div");
|
|
2736
2855
|
list.className = "ai-process-list";
|
|
2737
|
-
for (const step of
|
|
2856
|
+
for (const step of renderableSteps) {
|
|
2738
2857
|
if (step?.type === "context_compaction") {
|
|
2739
2858
|
list.append(createAiContextCompactionDivider({
|
|
2740
2859
|
kind: "tool",
|
|
@@ -2752,7 +2871,6 @@ function renderAiProcessSteps(message, steps, completed, durationMs = null, visi
|
|
|
2752
2871
|
list.append(tool);
|
|
2753
2872
|
continue;
|
|
2754
2873
|
}
|
|
2755
|
-
if (!step?.content || !["thinking", "intermediate"].includes(step.type)) continue;
|
|
2756
2874
|
const section = document.createElement("section");
|
|
2757
2875
|
section.className = `ai-process-step ai-process-${step.type}-step`;
|
|
2758
2876
|
const label = document.createElement("small");
|
|
@@ -3214,7 +3332,7 @@ function renderAiRoleplayCharacterSelect() {
|
|
|
3214
3332
|
select.title = canSelectCharacter
|
|
3215
3333
|
? state.aiPromptSent
|
|
3216
3334
|
? aiConversationOptionLockedMessage
|
|
3217
|
-
: "为当前对话选择角色卡;角色扮演时 Agent
|
|
3335
|
+
: "为当前对话选择角色卡;角色扮演时 Agent 可以查询角色记忆、人物关系和故事正文"
|
|
3218
3336
|
: "当前账户没有角色模块读取权限";
|
|
3219
3337
|
renderAiRoleplayUserCharacterSelect();
|
|
3220
3338
|
}
|
|
@@ -3255,6 +3373,10 @@ function renderAiRoleplayUserCharacterSelect() {
|
|
|
3255
3373
|
|
|
3256
3374
|
const aiConversationOptionLockedMessage = "会话选项在会话开始后不支持修改,若需要修改,请新建会话";
|
|
3257
3375
|
|
|
3376
|
+
function aiConversationModelLocked() {
|
|
3377
|
+
return typeof state.aiConversationModelId === "string" && state.aiConversationModelId.trim().length > 0;
|
|
3378
|
+
}
|
|
3379
|
+
|
|
3258
3380
|
function selectedAiModelLabel() {
|
|
3259
3381
|
return $("#ai-model").selectedOptions[0]?.textContent?.trim() || "尚未选择模型";
|
|
3260
3382
|
}
|
|
@@ -3263,20 +3385,21 @@ function syncAiModelPicker() {
|
|
|
3263
3385
|
const select = $("#ai-model");
|
|
3264
3386
|
const button = $("#ai-model-picker");
|
|
3265
3387
|
const interactionBusy = aiInteractionBusy();
|
|
3388
|
+
const modelLocked = aiConversationModelLocked();
|
|
3266
3389
|
const selectedLabel = selectedAiModelLabel();
|
|
3267
|
-
const label =
|
|
3390
|
+
const label = modelLocked
|
|
3268
3391
|
? `当前模型:${selectedLabel}。${aiConversationOptionLockedMessage}`
|
|
3269
3392
|
: `选择实际使用模型:${selectedLabel}`;
|
|
3270
3393
|
select.disabled = interactionBusy;
|
|
3271
|
-
select.title =
|
|
3394
|
+
select.title = modelLocked ? aiConversationOptionLockedMessage : "";
|
|
3272
3395
|
button.disabled = interactionBusy;
|
|
3273
3396
|
button.title = label;
|
|
3274
3397
|
button.setAttribute("aria-label", label);
|
|
3275
3398
|
if (interactionBusy) setAiModelPickerVisible(false);
|
|
3276
3399
|
}
|
|
3277
3400
|
|
|
3278
|
-
function notifyAiConversationOptionLocked(select) {
|
|
3279
|
-
if (!
|
|
3401
|
+
function notifyAiConversationOptionLocked(select, locked = state.aiPromptSent) {
|
|
3402
|
+
if (!locked) return false;
|
|
3280
3403
|
const now = Date.now();
|
|
3281
3404
|
const lastToastAt = Number(select.dataset.lockedToastAt ?? 0);
|
|
3282
3405
|
if (now - lastToastAt > 500) toast(aiConversationOptionLockedMessage);
|
|
@@ -3284,9 +3407,16 @@ function notifyAiConversationOptionLocked(select) {
|
|
|
3284
3407
|
return true;
|
|
3285
3408
|
}
|
|
3286
3409
|
|
|
3410
|
+
function notifyAiConversationModelLocked(control) {
|
|
3411
|
+
return notifyAiConversationOptionLocked(control, aiConversationModelLocked());
|
|
3412
|
+
}
|
|
3413
|
+
|
|
3287
3414
|
function blockLockedAiConversationOptionInteraction(event) {
|
|
3288
3415
|
const select = event.currentTarget;
|
|
3289
|
-
|
|
3416
|
+
const locked = select.id === "ai-model"
|
|
3417
|
+
? notifyAiConversationModelLocked(select)
|
|
3418
|
+
: notifyAiConversationOptionLocked(select);
|
|
3419
|
+
if (!locked) return;
|
|
3290
3420
|
event.preventDefault();
|
|
3291
3421
|
event.stopPropagation();
|
|
3292
3422
|
}
|
|
@@ -3311,7 +3441,7 @@ function syncAiTaskOptions() {
|
|
|
3311
3441
|
$("#ai-scope").disabled = interactionBusy || roleplaySelected;
|
|
3312
3442
|
$("#ai-scope").title = state.aiPromptSent
|
|
3313
3443
|
? aiConversationOptionLockedMessage
|
|
3314
|
-
: roleplaySelected ? "
|
|
3444
|
+
: roleplaySelected ? "角色扮演模式可以查询角色记忆、人物关系和故事正文" : "";
|
|
3315
3445
|
syncAiModelPicker();
|
|
3316
3446
|
}
|
|
3317
3447
|
|
|
@@ -10612,6 +10742,48 @@ function renderProviderCards(providers, models) {
|
|
|
10612
10742
|
: emptyModule("尚未配置 AI 供应商", "添加 OpenAI、Anthropic 或 Google Vertex 接口地址和凭据,测试成功后再添加模型。");
|
|
10613
10743
|
}
|
|
10614
10744
|
|
|
10745
|
+
async function deletePlatformModel(item) {
|
|
10746
|
+
if (!item) return;
|
|
10747
|
+
$("#form-dialog").close();
|
|
10748
|
+
if (!await confirmToast(`确认删除模型“${item.displayName}”吗?删除后将从当前供应商的模型列表移除。`, {
|
|
10749
|
+
title: "删除模型",
|
|
10750
|
+
confirmLabel: "继续删除"
|
|
10751
|
+
})) return;
|
|
10752
|
+
if (!await confirmToast(`删除模型“${item.displayName}”后,相关默认模型设置会被清空,且无法恢复。仍要删除吗?`, {
|
|
10753
|
+
title: "删除操作需要再次确认",
|
|
10754
|
+
confirmLabel: "确认删除"
|
|
10755
|
+
})) return;
|
|
10756
|
+
try {
|
|
10757
|
+
await api(`/api/models/${encodeURIComponent(item.id)}`, { method: "DELETE" });
|
|
10758
|
+
await renderPlatformAiConfig();
|
|
10759
|
+
await loadModels();
|
|
10760
|
+
deleteToast(`已删除模型“${item.displayName}”`);
|
|
10761
|
+
} catch (error) {
|
|
10762
|
+
toast(error.message, "error");
|
|
10763
|
+
}
|
|
10764
|
+
}
|
|
10765
|
+
|
|
10766
|
+
async function deletePlatformProvider(item) {
|
|
10767
|
+
if (!item) return;
|
|
10768
|
+
$("#form-dialog").close();
|
|
10769
|
+
if (!await confirmToast(`确认删除供应商“${item.name}”吗?该供应商下的模型也会一并删除。`, {
|
|
10770
|
+
title: "删除供应商",
|
|
10771
|
+
confirmLabel: "继续删除"
|
|
10772
|
+
})) return;
|
|
10773
|
+
if (!await confirmToast(`删除供应商“${item.name}”后,供应商、模型及相关默认模型设置都无法恢复。仍要删除吗?`, {
|
|
10774
|
+
title: "删除操作需要再次确认",
|
|
10775
|
+
confirmLabel: "确认删除"
|
|
10776
|
+
})) return;
|
|
10777
|
+
try {
|
|
10778
|
+
await api(`/api/providers/${encodeURIComponent(item.id)}`, { method: "DELETE" });
|
|
10779
|
+
await renderPlatformAiConfig();
|
|
10780
|
+
await loadModels();
|
|
10781
|
+
deleteToast(`已删除供应商“${item.name}”`);
|
|
10782
|
+
} catch (error) {
|
|
10783
|
+
toast(error.message, "error");
|
|
10784
|
+
}
|
|
10785
|
+
}
|
|
10786
|
+
|
|
10615
10787
|
function bindPlatformProviderActions(host, providers, models) {
|
|
10616
10788
|
host.querySelectorAll("[data-test-provider]").forEach((button) => button.addEventListener("click", async () => {
|
|
10617
10789
|
const providerId = button.dataset.testProvider;
|
|
@@ -10991,7 +11163,7 @@ async function renderPlatformAiConfig() {
|
|
|
10991
11163
|
const available = isSelectableModel({ ...model, providerStatus: provider?.status, providerConnectionStatus: provider?.connectionStatus });
|
|
10992
11164
|
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>`;
|
|
10993
11165
|
}).join("");
|
|
10994
|
-
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-providers-section"><div class="config-section-header"><div><h2>模型供应商配置</h2><p>管理供应商连接、模型列表和连接状态;模型的多模态能力在对应模型配置中设置。</p></div></div>${renderProviderCards(providers, models)}</section>`;
|
|
11166
|
+
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)}</section>`;
|
|
10995
11167
|
$("#save-platform-system-prompt").addEventListener("click", async () => {
|
|
10996
11168
|
const button = $("#save-platform-system-prompt");
|
|
10997
11169
|
button.disabled = true;
|
|
@@ -11017,6 +11189,20 @@ async function renderPlatformAiConfig() {
|
|
|
11017
11189
|
button.disabled = false;
|
|
11018
11190
|
}
|
|
11019
11191
|
});
|
|
11192
|
+
$("#save-platform-ai-stream-idle-timeout").addEventListener("click", async () => {
|
|
11193
|
+
const button = $("#save-platform-ai-stream-idle-timeout");
|
|
11194
|
+
const input = $("#platform-ai-stream-idle-timeout");
|
|
11195
|
+
button.disabled = true;
|
|
11196
|
+
try {
|
|
11197
|
+
await api("/api/platform/ai/settings", { method: "PATCH", body: { streamIdleTimeoutSeconds: Number(input.value) } });
|
|
11198
|
+
toast("AI 流事件空闲超时已更新");
|
|
11199
|
+
await renderPlatformAiConfig();
|
|
11200
|
+
} catch (error) {
|
|
11201
|
+
toast(error.message, "error");
|
|
11202
|
+
} finally {
|
|
11203
|
+
button.disabled = false;
|
|
11204
|
+
}
|
|
11205
|
+
});
|
|
11020
11206
|
bindPlatformProviderActions(host, providers, models);
|
|
11021
11207
|
}
|
|
11022
11208
|
|
|
@@ -14671,7 +14857,9 @@ function openProviderDialog(item) {
|
|
|
14671
14857
|
await loadModels();
|
|
14672
14858
|
if (item) toast(connectivityConfigurationSavedToast("provider"));
|
|
14673
14859
|
},
|
|
14674
|
-
item ? "协议、限流与凭据" : "OpenAI / Anthropic / Google Vertex"
|
|
14860
|
+
item ? "协议、限流与凭据" : "OpenAI / Anthropic / Google Vertex", {
|
|
14861
|
+
dangerAction: item ? { label: "删除供应商", onClick: () => deletePlatformProvider(item) } : null
|
|
14862
|
+
}
|
|
14675
14863
|
);
|
|
14676
14864
|
const protocolSelect = $("#dialog-fields select[name='protocol']");
|
|
14677
14865
|
const baseUrlInput = $("#dialog-fields input[name='baseUrl']");
|
|
@@ -14716,7 +14904,9 @@ function openModelDialog(providerId, item = null, provider = null) {
|
|
|
14716
14904
|
await renderPlatformAiConfig();
|
|
14717
14905
|
await loadModels();
|
|
14718
14906
|
if (item) toast(connectivityConfigurationSavedToast("model"));
|
|
14719
|
-
}, item ? "模型配置" : "供应商模型"
|
|
14907
|
+
}, item ? "模型配置" : "供应商模型", {
|
|
14908
|
+
dangerAction: item ? { label: "删除模型", onClick: () => deletePlatformModel(item) } : null
|
|
14909
|
+
});
|
|
14720
14910
|
const modelIdInput = $("#dialog-fields input[name='modelId']");
|
|
14721
14911
|
const contextWindowInput = $("#model-context-window");
|
|
14722
14912
|
const contextWindowHint = $("#model-context-window-hint");
|
|
@@ -14782,20 +14972,23 @@ async function sendAi() {
|
|
|
14782
14972
|
return sendAiWithOptions();
|
|
14783
14973
|
}
|
|
14784
14974
|
|
|
14785
|
-
async function sendAiWithOptions({ ignoreContextWarning = false } = {}) {
|
|
14975
|
+
async function sendAiWithOptions({ ignoreContextWarning = false, retry = null } = {}) {
|
|
14786
14976
|
if (!state.work) return toast("请先选择作品", "error");
|
|
14787
14977
|
const tab = activeAiChatTab();
|
|
14788
14978
|
if (!tab) return toast("Agent 对话页签尚未就绪", "error");
|
|
14789
14979
|
if (aiRequestManager.hasActive(tab.id)) return;
|
|
14790
14980
|
const composerSnapshot = captureAiPromptComposer();
|
|
14791
|
-
const
|
|
14981
|
+
const requestComposerSnapshot = retry
|
|
14982
|
+
? { text: retry.prompt, citations: retry.citations ?? [], references: [] }
|
|
14983
|
+
: composerSnapshot;
|
|
14984
|
+
const instruction = requestComposerSnapshot.text.trim();
|
|
14792
14985
|
if (!instruction) return toast("请输入指令", "error");
|
|
14793
14986
|
if ($("#ai-task").value === "roleplay" && !state.aiRoleplayCharacter) return toast("请先选择角色卡", "error");
|
|
14794
14987
|
const requestScope = currentAiRequestScope();
|
|
14795
14988
|
if (!requestScope) return toast("请先选择章节", "error");
|
|
14796
14989
|
const { taskType, scope, selection } = requestScope;
|
|
14797
14990
|
if (taskType === "polish" && !selection) return toast("请先在正文中选中一段文本", "error");
|
|
14798
|
-
const citations =
|
|
14991
|
+
const citations = requestComposerSnapshot.citations.map(({ chapterId, chapterTitle, startLine, endLine, text }) => ({ chapterId, chapterTitle, startLine, endLine, text }));
|
|
14799
14992
|
const selectedTaskType = $("#ai-task").value;
|
|
14800
14993
|
persistActiveAiChatTab();
|
|
14801
14994
|
tab.selectedModelId = $("#ai-model").value || tab.selectedModelId || null;
|
|
@@ -14806,6 +14999,9 @@ async function sendAiWithOptions({ ignoreContextWarning = false } = {}) {
|
|
|
14806
14999
|
conversationId: state.aiConversationId
|
|
14807
15000
|
})
|
|
14808
15001
|
};
|
|
15002
|
+
if (retry?.userMessageId) {
|
|
15003
|
+
requestHolder.snapshot = aiRequestManager.bind(requestHolder.snapshot, { userMessageId: retry.userMessageId });
|
|
15004
|
+
}
|
|
14809
15005
|
setAiChatTabStatus(tab, "streaming");
|
|
14810
15006
|
syncAiRequestControls();
|
|
14811
15007
|
try {
|
|
@@ -14828,38 +15024,45 @@ async function sendAiWithOptions({ ignoreContextWarning = false } = {}) {
|
|
|
14828
15024
|
return toast(`对话配置锁定失败:${error.message}`, "error");
|
|
14829
15025
|
}
|
|
14830
15026
|
setAiChatTabStatus(tab, "streaming");
|
|
15027
|
+
if (retry?.message?.isConnected) retry.message.remove();
|
|
14831
15028
|
if (taskType !== "chat") {
|
|
14832
|
-
|
|
14833
|
-
|
|
14834
|
-
|
|
14835
|
-
|
|
14836
|
-
|
|
14837
|
-
|
|
14838
|
-
|
|
14839
|
-
|
|
14840
|
-
|
|
14841
|
-
|
|
14842
|
-
|
|
14843
|
-
|
|
14844
|
-
|
|
14845
|
-
|
|
14846
|
-
|
|
14847
|
-
|
|
14848
|
-
|
|
14849
|
-
|
|
14850
|
-
|
|
14851
|
-
|
|
14852
|
-
|
|
14853
|
-
|
|
14854
|
-
|
|
14855
|
-
|
|
14856
|
-
|
|
15029
|
+
if (!retry) {
|
|
15030
|
+
try {
|
|
15031
|
+
const request = assertAiRequestCurrent(requestHolder.snapshot);
|
|
15032
|
+
const persistedUserMessage = await persistAiConversationMessage(
|
|
15033
|
+
request.conversationId,
|
|
15034
|
+
"user",
|
|
15035
|
+
instruction,
|
|
15036
|
+
citations,
|
|
15037
|
+
{ modelId },
|
|
15038
|
+
{ signal: request.signal }
|
|
15039
|
+
);
|
|
15040
|
+
assertAiRequestCurrent(request);
|
|
15041
|
+
updateAiConversationSummaryFromMessage(persistedUserMessage);
|
|
15042
|
+
requestHolder.snapshot = aiRequestManager.bind(request, { userMessageId: persistedUserMessage.id });
|
|
15043
|
+
tab.modelId = modelId;
|
|
15044
|
+
tab.selectedModelId = modelId;
|
|
15045
|
+
tab.promptSent = true;
|
|
15046
|
+
clearAiChatTabComposer(tab);
|
|
15047
|
+
appendMessage("user", instruction, citations, persistedUserMessage.createdAt, {}, persistedUserMessage.id, { tab });
|
|
15048
|
+
if (isActiveAiChatTab(tab)) {
|
|
15049
|
+
state.aiConversationModelId = modelId;
|
|
15050
|
+
state.aiPromptSent = true;
|
|
15051
|
+
syncAiTaskOptions();
|
|
15052
|
+
renderAiRoleplayCharacterSelect();
|
|
15053
|
+
renderAiQuickActions();
|
|
15054
|
+
clearAiPromptComposer();
|
|
15055
|
+
}
|
|
15056
|
+
} catch (error) {
|
|
15057
|
+
if (isAiRequestCancellation(error, requestHolder.snapshot) || !aiRequestTargetsCurrentState(requestHolder.snapshot)) throw error;
|
|
15058
|
+
setAiChatTabStatus(tab, "error");
|
|
15059
|
+
return toast(`对话记录创建失败:${error.message}`, "error");
|
|
14857
15060
|
}
|
|
14858
|
-
}
|
|
14859
|
-
|
|
14860
|
-
setAiChatTabStatus(tab, "error");
|
|
14861
|
-
return toast(`对话记录创建失败:${error.message}`, "error");
|
|
15061
|
+
} else {
|
|
15062
|
+
prepareAiRetryState(tab, modelId);
|
|
14862
15063
|
}
|
|
15064
|
+
} else if (retry) {
|
|
15065
|
+
prepareAiRetryState(tab, modelId);
|
|
14863
15066
|
}
|
|
14864
15067
|
let assistantContent = "";
|
|
14865
15068
|
let assistantMessage;
|
|
@@ -14867,14 +15070,14 @@ async function sendAiWithOptions({ ignoreContextWarning = false } = {}) {
|
|
|
14867
15070
|
let persistedStreamMessage = null;
|
|
14868
15071
|
let suggestion = null;
|
|
14869
15072
|
if (taskType === "chat") {
|
|
14870
|
-
const streamed = await streamChat(requestHolder, {
|
|
15073
|
+
const streamed = await streamChat(requestHolder, aiRetryStreamRequestBody({
|
|
14871
15074
|
instruction,
|
|
14872
15075
|
scope,
|
|
14873
15076
|
modelId,
|
|
14874
15077
|
citations,
|
|
14875
15078
|
conversationId: requestHolder.snapshot.conversationId,
|
|
14876
15079
|
...(ignoreContextWarning ? { ignoreContextWarning: true } : {})
|
|
14877
|
-
}, createAiIdempotencyKey());
|
|
15080
|
+
}, retry), createAiIdempotencyKey());
|
|
14878
15081
|
const request = assertAiRequestCurrent(requestHolder.snapshot);
|
|
14879
15082
|
if (streamed.action === "warn") return;
|
|
14880
15083
|
assistantContent = streamed.content;
|
|
@@ -14917,7 +15120,7 @@ async function sendAiWithOptions({ ignoreContextWarning = false } = {}) {
|
|
|
14917
15120
|
assistantContent,
|
|
14918
15121
|
[],
|
|
14919
15122
|
assistantMetadata,
|
|
14920
|
-
{ signal: request.signal, requestId: aiAssistantRequestId(request) }
|
|
15123
|
+
{ signal: request.signal, requestId: retry && taskType !== "chat" ? null : aiAssistantRequestId(request) }
|
|
14921
15124
|
);
|
|
14922
15125
|
assertAiRequestCurrent(request);
|
|
14923
15126
|
updateAiConversationSummaryFromMessage(persistedAssistantMessage);
|
|
@@ -14952,8 +15155,11 @@ async function sendAiWithOptions({ ignoreContextWarning = false } = {}) {
|
|
|
14952
15155
|
return;
|
|
14953
15156
|
}
|
|
14954
15157
|
if (error?.code === "AI_CONVERSATION_RESPONSE_IN_PROGRESS") {
|
|
14955
|
-
setAiChatTabComposerSnapshot(tab,
|
|
14956
|
-
if (isActiveAiChatTab(tab))
|
|
15158
|
+
setAiChatTabComposerSnapshot(tab, requestComposerSnapshot);
|
|
15159
|
+
if (isActiveAiChatTab(tab)) {
|
|
15160
|
+
if (retry) restoreAiPromptComposer(requestComposerSnapshot);
|
|
15161
|
+
else restoreAiPromptComposer(composerSnapshot);
|
|
15162
|
+
}
|
|
14957
15163
|
toast("当前对话仍在生成回复,请等待完成或取消后再发送", "error");
|
|
14958
15164
|
if (isActiveAiChatTab(tab)) $("#ai-prompt").focus();
|
|
14959
15165
|
return;
|
|
@@ -15009,7 +15215,7 @@ async function sendAiWithOptions({ ignoreContextWarning = false } = {}) {
|
|
|
15009
15215
|
failureMessage,
|
|
15010
15216
|
[],
|
|
15011
15217
|
{},
|
|
15012
|
-
{ requestId: aiAssistantRequestId(request) }
|
|
15218
|
+
{ requestId: retry && taskType !== "chat" ? null : aiAssistantRequestId(request) }
|
|
15013
15219
|
);
|
|
15014
15220
|
updateAiConversationSummaryFromMessage(persistedFailureMessage);
|
|
15015
15221
|
} catch { /* 主请求错误已显示,历史记录保存失败不覆盖原始错误 */ }
|
|
@@ -15032,13 +15238,9 @@ function createAiStreamCharacterCount(value) {
|
|
|
15032
15238
|
return count;
|
|
15033
15239
|
}
|
|
15034
15240
|
|
|
15035
|
-
function renderAiStreamingCharacterProgress(meta, visibleCharacters
|
|
15241
|
+
function renderAiStreamingCharacterProgress(meta, visibleCharacters) {
|
|
15036
15242
|
const visible = Math.max(0, Number(visibleCharacters) || 0);
|
|
15037
|
-
|
|
15038
|
-
const children = ["正在生成 · ", createAiStreamCharacterCount(visible)];
|
|
15039
|
-
if (received > visible) children.push(" / ", createAiStreamCharacterCount(received));
|
|
15040
|
-
children.push(" 字");
|
|
15041
|
-
meta.replaceChildren(...children);
|
|
15243
|
+
meta.replaceChildren("正在生成 · ", createAiStreamCharacterCount(visible), " 字");
|
|
15042
15244
|
}
|
|
15043
15245
|
|
|
15044
15246
|
async function streamChat(requestHolder, body, idempotencyKey) {
|
|
@@ -15067,8 +15269,7 @@ async function streamChat(requestHolder, body, idempotencyKey) {
|
|
|
15067
15269
|
onRender: (text, progress) => {
|
|
15068
15270
|
if (!aiRequestTargetsCurrentState(requestHolder.snapshot) || !mountAssistantMessage()) return;
|
|
15069
15271
|
content.innerHTML = renderMarkdown(text);
|
|
15070
|
-
|
|
15071
|
-
renderAiStreamingCharacterProgress(meta, progress.visibleCharacters, receivedCharacters);
|
|
15272
|
+
renderAiStreamingCharacterProgress(meta, progress.visibleCharacters);
|
|
15072
15273
|
scrollAiFeedToBottom(feed);
|
|
15073
15274
|
}
|
|
15074
15275
|
});
|
|
@@ -15161,7 +15362,11 @@ async function streamChat(requestHolder, body, idempotencyKey) {
|
|
|
15161
15362
|
tab.selectedModelId = lockedModelId;
|
|
15162
15363
|
tab.promptSent = true;
|
|
15163
15364
|
clearAiChatTabComposer(tab);
|
|
15164
|
-
|
|
15365
|
+
const existingUserMessage = [...tab.feed.querySelectorAll(".user-message[data-message-id]")]
|
|
15366
|
+
.find((candidate) => candidate.dataset.messageId === String(persistedUserMessage.id));
|
|
15367
|
+
if (!existingUserMessage) {
|
|
15368
|
+
appendMessage("user", persistedUserMessage.content, persistedUserMessage.citations, persistedUserMessage.createdAt, persistedUserMessage.metadata, persistedUserMessage.id, { tab });
|
|
15369
|
+
}
|
|
15165
15370
|
if (isActiveAiChatTab(tab)) {
|
|
15166
15371
|
state.aiConversationModelId = lockedModelId;
|
|
15167
15372
|
state.aiPromptSent = true;
|
|
@@ -15178,7 +15383,6 @@ async function streamChat(requestHolder, body, idempotencyKey) {
|
|
|
15178
15383
|
streamedText += delta;
|
|
15179
15384
|
streamedPendingText += delta;
|
|
15180
15385
|
typewriter.append(delta);
|
|
15181
|
-
meta.textContent = "正在生成回复……";
|
|
15182
15386
|
} else if (eventName === "process_step") {
|
|
15183
15387
|
mountAssistantMessage();
|
|
15184
15388
|
const step = { ...payload };
|
|
@@ -15383,7 +15587,10 @@ function appendMessage(role, text, citations = [], createdAt = null, metadata =
|
|
|
15383
15587
|
if (role === "assistant") {
|
|
15384
15588
|
renderAiProcessSteps(message, processSteps, true, processDurationMs);
|
|
15385
15589
|
}
|
|
15386
|
-
if (role === "user")
|
|
15590
|
+
if (role === "user") {
|
|
15591
|
+
message.dataset.aiCitations = JSON.stringify(Array.isArray(citations) ? citations : []);
|
|
15592
|
+
attachUserCopyAction(message, text);
|
|
15593
|
+
}
|
|
15387
15594
|
if (role === "assistant" && !text.startsWith("调用失败:")) {
|
|
15388
15595
|
const selectedModelId = tab?.modelId ?? tab?.selectedModelId ?? $("#ai-model").value;
|
|
15389
15596
|
const selectedModel = state.models.find((model) => model.id === selectedModelId) ?? state.models[0];
|
|
@@ -15397,6 +15604,7 @@ function appendMessage(role, text, citations = [], createdAt = null, metadata =
|
|
|
15397
15604
|
message.append(meta);
|
|
15398
15605
|
attachAssistantCopyAction(message, text);
|
|
15399
15606
|
}
|
|
15607
|
+
if (isFailure) renderMessageCardActions(message);
|
|
15400
15608
|
attachMessageIdentity(message, messageId);
|
|
15401
15609
|
feed.append(message);
|
|
15402
15610
|
scrollAiFeedToBottom(feed);
|
|
@@ -16996,7 +17204,7 @@ $("#ai-model").addEventListener("focus", () => {
|
|
|
16996
17204
|
ensureAiModelsLoaded().catch((error) => toast(`模型加载失败:${error.message}`, "error"));
|
|
16997
17205
|
});
|
|
16998
17206
|
$("#ai-model").addEventListener("change", (event) => {
|
|
16999
|
-
if (
|
|
17207
|
+
if (aiConversationModelLocked()) {
|
|
17000
17208
|
event.currentTarget.value = state.aiConversationModelId ?? event.currentTarget.value;
|
|
17001
17209
|
syncAiModelPicker();
|
|
17002
17210
|
return toast(aiConversationOptionLockedMessage);
|
|
@@ -17007,7 +17215,7 @@ $("#ai-model").addEventListener("change", (event) => {
|
|
|
17007
17215
|
});
|
|
17008
17216
|
$("#ai-model-picker").addEventListener("click", async (event) => {
|
|
17009
17217
|
const button = event.currentTarget;
|
|
17010
|
-
if (
|
|
17218
|
+
if (notifyAiConversationModelLocked(button)) return;
|
|
17011
17219
|
const willOpen = $("#ai-model-popover").classList.contains("hidden");
|
|
17012
17220
|
setAiContextDistributionVisible(false);
|
|
17013
17221
|
setAiModelPickerVisible(willOpen);
|
package/dist/public/index.html
CHANGED
|
@@ -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=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=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=annotation-marker-offset-v1&feature=mobile-ai-entry-hidden-v1&feature=phone-client-entry-v1">
|
|
13
|
+
<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=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=annotation-marker-offset-v1&feature=mobile-ai-entry-hidden-v1&feature=phone-client-entry-v1&feature=ai-stream-idle-timeout-v1">
|
|
14
14
|
</head>
|
|
15
15
|
<body class="auth-pending">
|
|
16
16
|
<section id="auth-view" class="auth-view hidden" aria-labelledby="auth-title">
|
|
@@ -1210,6 +1210,6 @@
|
|
|
1210
1210
|
<div id="auth-loading" class="auth-loading" role="status" aria-label="正在载入工作台"></div>
|
|
1211
1211
|
<script id="vditorIconScript" src="/vendor/vditor/dist/js/icons/ant.js?v=3.11.2"></script>
|
|
1212
1212
|
<script src="/vendor/vditor/dist/index.min.js?v=3.11.2"></script>
|
|
1213
|
-
<script type="module" src="/app.js?v=20260816-extended-thinking-effort-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-v1&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-v1&feature=volume-detail-icon-v1&feature=reader-manual-chapter-navigation-v1&feature=ai-message-reference-badges-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-model-picker-v1&feature=context-percent-format-v1&feature=annotation-precise-locate-v1&feature=markdown-word-count-stable-v1&feature=ai-stream-character-count-stable-
|
|
1213
|
+
<script type="module" src="/app.js?v=20260816-extended-thinking-effort-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-v1&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-v1&feature=volume-detail-icon-v1&feature=reader-manual-chapter-navigation-v1&feature=ai-message-reference-badges-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-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=phone-client-entry-v1&feature=ai-process-empty-intermediate-v1&feature=ai-feed-scroll-follow-v1&feature=ai-message-retry-v1&feature=ai-stream-idle-timeout-v2&feature=ai-config-delete-v1"></script>
|
|
1214
1214
|
</body>
|
|
1215
1215
|
</html>
|