@musnows/scriverse 0.8.4 → 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 +253 -51
- 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 +12 -3
- 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
|
}
|
|
@@ -3323,7 +3441,7 @@ function syncAiTaskOptions() {
|
|
|
3323
3441
|
$("#ai-scope").disabled = interactionBusy || roleplaySelected;
|
|
3324
3442
|
$("#ai-scope").title = state.aiPromptSent
|
|
3325
3443
|
? aiConversationOptionLockedMessage
|
|
3326
|
-
: roleplaySelected ? "
|
|
3444
|
+
: roleplaySelected ? "角色扮演模式可以查询角色记忆、人物关系和故事正文" : "";
|
|
3327
3445
|
syncAiModelPicker();
|
|
3328
3446
|
}
|
|
3329
3447
|
|
|
@@ -10624,6 +10742,48 @@ function renderProviderCards(providers, models) {
|
|
|
10624
10742
|
: emptyModule("尚未配置 AI 供应商", "添加 OpenAI、Anthropic 或 Google Vertex 接口地址和凭据,测试成功后再添加模型。");
|
|
10625
10743
|
}
|
|
10626
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
|
+
|
|
10627
10787
|
function bindPlatformProviderActions(host, providers, models) {
|
|
10628
10788
|
host.querySelectorAll("[data-test-provider]").forEach((button) => button.addEventListener("click", async () => {
|
|
10629
10789
|
const providerId = button.dataset.testProvider;
|
|
@@ -11003,7 +11163,7 @@ async function renderPlatformAiConfig() {
|
|
|
11003
11163
|
const available = isSelectableModel({ ...model, providerStatus: provider?.status, providerConnectionStatus: provider?.connectionStatus });
|
|
11004
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>`;
|
|
11005
11165
|
}).join("");
|
|
11006
|
-
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>`;
|
|
11007
11167
|
$("#save-platform-system-prompt").addEventListener("click", async () => {
|
|
11008
11168
|
const button = $("#save-platform-system-prompt");
|
|
11009
11169
|
button.disabled = true;
|
|
@@ -11029,6 +11189,20 @@ async function renderPlatformAiConfig() {
|
|
|
11029
11189
|
button.disabled = false;
|
|
11030
11190
|
}
|
|
11031
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
|
+
});
|
|
11032
11206
|
bindPlatformProviderActions(host, providers, models);
|
|
11033
11207
|
}
|
|
11034
11208
|
|
|
@@ -14683,7 +14857,9 @@ function openProviderDialog(item) {
|
|
|
14683
14857
|
await loadModels();
|
|
14684
14858
|
if (item) toast(connectivityConfigurationSavedToast("provider"));
|
|
14685
14859
|
},
|
|
14686
|
-
item ? "协议、限流与凭据" : "OpenAI / Anthropic / Google Vertex"
|
|
14860
|
+
item ? "协议、限流与凭据" : "OpenAI / Anthropic / Google Vertex", {
|
|
14861
|
+
dangerAction: item ? { label: "删除供应商", onClick: () => deletePlatformProvider(item) } : null
|
|
14862
|
+
}
|
|
14687
14863
|
);
|
|
14688
14864
|
const protocolSelect = $("#dialog-fields select[name='protocol']");
|
|
14689
14865
|
const baseUrlInput = $("#dialog-fields input[name='baseUrl']");
|
|
@@ -14728,7 +14904,9 @@ function openModelDialog(providerId, item = null, provider = null) {
|
|
|
14728
14904
|
await renderPlatformAiConfig();
|
|
14729
14905
|
await loadModels();
|
|
14730
14906
|
if (item) toast(connectivityConfigurationSavedToast("model"));
|
|
14731
|
-
}, item ? "模型配置" : "供应商模型"
|
|
14907
|
+
}, item ? "模型配置" : "供应商模型", {
|
|
14908
|
+
dangerAction: item ? { label: "删除模型", onClick: () => deletePlatformModel(item) } : null
|
|
14909
|
+
});
|
|
14732
14910
|
const modelIdInput = $("#dialog-fields input[name='modelId']");
|
|
14733
14911
|
const contextWindowInput = $("#model-context-window");
|
|
14734
14912
|
const contextWindowHint = $("#model-context-window-hint");
|
|
@@ -14794,20 +14972,23 @@ async function sendAi() {
|
|
|
14794
14972
|
return sendAiWithOptions();
|
|
14795
14973
|
}
|
|
14796
14974
|
|
|
14797
|
-
async function sendAiWithOptions({ ignoreContextWarning = false } = {}) {
|
|
14975
|
+
async function sendAiWithOptions({ ignoreContextWarning = false, retry = null } = {}) {
|
|
14798
14976
|
if (!state.work) return toast("请先选择作品", "error");
|
|
14799
14977
|
const tab = activeAiChatTab();
|
|
14800
14978
|
if (!tab) return toast("Agent 对话页签尚未就绪", "error");
|
|
14801
14979
|
if (aiRequestManager.hasActive(tab.id)) return;
|
|
14802
14980
|
const composerSnapshot = captureAiPromptComposer();
|
|
14803
|
-
const
|
|
14981
|
+
const requestComposerSnapshot = retry
|
|
14982
|
+
? { text: retry.prompt, citations: retry.citations ?? [], references: [] }
|
|
14983
|
+
: composerSnapshot;
|
|
14984
|
+
const instruction = requestComposerSnapshot.text.trim();
|
|
14804
14985
|
if (!instruction) return toast("请输入指令", "error");
|
|
14805
14986
|
if ($("#ai-task").value === "roleplay" && !state.aiRoleplayCharacter) return toast("请先选择角色卡", "error");
|
|
14806
14987
|
const requestScope = currentAiRequestScope();
|
|
14807
14988
|
if (!requestScope) return toast("请先选择章节", "error");
|
|
14808
14989
|
const { taskType, scope, selection } = requestScope;
|
|
14809
14990
|
if (taskType === "polish" && !selection) return toast("请先在正文中选中一段文本", "error");
|
|
14810
|
-
const citations =
|
|
14991
|
+
const citations = requestComposerSnapshot.citations.map(({ chapterId, chapterTitle, startLine, endLine, text }) => ({ chapterId, chapterTitle, startLine, endLine, text }));
|
|
14811
14992
|
const selectedTaskType = $("#ai-task").value;
|
|
14812
14993
|
persistActiveAiChatTab();
|
|
14813
14994
|
tab.selectedModelId = $("#ai-model").value || tab.selectedModelId || null;
|
|
@@ -14818,6 +14999,9 @@ async function sendAiWithOptions({ ignoreContextWarning = false } = {}) {
|
|
|
14818
14999
|
conversationId: state.aiConversationId
|
|
14819
15000
|
})
|
|
14820
15001
|
};
|
|
15002
|
+
if (retry?.userMessageId) {
|
|
15003
|
+
requestHolder.snapshot = aiRequestManager.bind(requestHolder.snapshot, { userMessageId: retry.userMessageId });
|
|
15004
|
+
}
|
|
14821
15005
|
setAiChatTabStatus(tab, "streaming");
|
|
14822
15006
|
syncAiRequestControls();
|
|
14823
15007
|
try {
|
|
@@ -14840,38 +15024,45 @@ async function sendAiWithOptions({ ignoreContextWarning = false } = {}) {
|
|
|
14840
15024
|
return toast(`对话配置锁定失败:${error.message}`, "error");
|
|
14841
15025
|
}
|
|
14842
15026
|
setAiChatTabStatus(tab, "streaming");
|
|
15027
|
+
if (retry?.message?.isConnected) retry.message.remove();
|
|
14843
15028
|
if (taskType !== "chat") {
|
|
14844
|
-
|
|
14845
|
-
|
|
14846
|
-
|
|
14847
|
-
|
|
14848
|
-
|
|
14849
|
-
|
|
14850
|
-
|
|
14851
|
-
|
|
14852
|
-
|
|
14853
|
-
|
|
14854
|
-
|
|
14855
|
-
|
|
14856
|
-
|
|
14857
|
-
|
|
14858
|
-
|
|
14859
|
-
|
|
14860
|
-
|
|
14861
|
-
|
|
14862
|
-
|
|
14863
|
-
|
|
14864
|
-
|
|
14865
|
-
|
|
14866
|
-
|
|
14867
|
-
|
|
14868
|
-
|
|
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");
|
|
14869
15060
|
}
|
|
14870
|
-
}
|
|
14871
|
-
|
|
14872
|
-
setAiChatTabStatus(tab, "error");
|
|
14873
|
-
return toast(`对话记录创建失败:${error.message}`, "error");
|
|
15061
|
+
} else {
|
|
15062
|
+
prepareAiRetryState(tab, modelId);
|
|
14874
15063
|
}
|
|
15064
|
+
} else if (retry) {
|
|
15065
|
+
prepareAiRetryState(tab, modelId);
|
|
14875
15066
|
}
|
|
14876
15067
|
let assistantContent = "";
|
|
14877
15068
|
let assistantMessage;
|
|
@@ -14879,14 +15070,14 @@ async function sendAiWithOptions({ ignoreContextWarning = false } = {}) {
|
|
|
14879
15070
|
let persistedStreamMessage = null;
|
|
14880
15071
|
let suggestion = null;
|
|
14881
15072
|
if (taskType === "chat") {
|
|
14882
|
-
const streamed = await streamChat(requestHolder, {
|
|
15073
|
+
const streamed = await streamChat(requestHolder, aiRetryStreamRequestBody({
|
|
14883
15074
|
instruction,
|
|
14884
15075
|
scope,
|
|
14885
15076
|
modelId,
|
|
14886
15077
|
citations,
|
|
14887
15078
|
conversationId: requestHolder.snapshot.conversationId,
|
|
14888
15079
|
...(ignoreContextWarning ? { ignoreContextWarning: true } : {})
|
|
14889
|
-
}, createAiIdempotencyKey());
|
|
15080
|
+
}, retry), createAiIdempotencyKey());
|
|
14890
15081
|
const request = assertAiRequestCurrent(requestHolder.snapshot);
|
|
14891
15082
|
if (streamed.action === "warn") return;
|
|
14892
15083
|
assistantContent = streamed.content;
|
|
@@ -14929,7 +15120,7 @@ async function sendAiWithOptions({ ignoreContextWarning = false } = {}) {
|
|
|
14929
15120
|
assistantContent,
|
|
14930
15121
|
[],
|
|
14931
15122
|
assistantMetadata,
|
|
14932
|
-
{ signal: request.signal, requestId: aiAssistantRequestId(request) }
|
|
15123
|
+
{ signal: request.signal, requestId: retry && taskType !== "chat" ? null : aiAssistantRequestId(request) }
|
|
14933
15124
|
);
|
|
14934
15125
|
assertAiRequestCurrent(request);
|
|
14935
15126
|
updateAiConversationSummaryFromMessage(persistedAssistantMessage);
|
|
@@ -14964,8 +15155,11 @@ async function sendAiWithOptions({ ignoreContextWarning = false } = {}) {
|
|
|
14964
15155
|
return;
|
|
14965
15156
|
}
|
|
14966
15157
|
if (error?.code === "AI_CONVERSATION_RESPONSE_IN_PROGRESS") {
|
|
14967
|
-
setAiChatTabComposerSnapshot(tab,
|
|
14968
|
-
if (isActiveAiChatTab(tab))
|
|
15158
|
+
setAiChatTabComposerSnapshot(tab, requestComposerSnapshot);
|
|
15159
|
+
if (isActiveAiChatTab(tab)) {
|
|
15160
|
+
if (retry) restoreAiPromptComposer(requestComposerSnapshot);
|
|
15161
|
+
else restoreAiPromptComposer(composerSnapshot);
|
|
15162
|
+
}
|
|
14969
15163
|
toast("当前对话仍在生成回复,请等待完成或取消后再发送", "error");
|
|
14970
15164
|
if (isActiveAiChatTab(tab)) $("#ai-prompt").focus();
|
|
14971
15165
|
return;
|
|
@@ -15021,7 +15215,7 @@ async function sendAiWithOptions({ ignoreContextWarning = false } = {}) {
|
|
|
15021
15215
|
failureMessage,
|
|
15022
15216
|
[],
|
|
15023
15217
|
{},
|
|
15024
|
-
{ requestId: aiAssistantRequestId(request) }
|
|
15218
|
+
{ requestId: retry && taskType !== "chat" ? null : aiAssistantRequestId(request) }
|
|
15025
15219
|
);
|
|
15026
15220
|
updateAiConversationSummaryFromMessage(persistedFailureMessage);
|
|
15027
15221
|
} catch { /* 主请求错误已显示,历史记录保存失败不覆盖原始错误 */ }
|
|
@@ -15168,7 +15362,11 @@ async function streamChat(requestHolder, body, idempotencyKey) {
|
|
|
15168
15362
|
tab.selectedModelId = lockedModelId;
|
|
15169
15363
|
tab.promptSent = true;
|
|
15170
15364
|
clearAiChatTabComposer(tab);
|
|
15171
|
-
|
|
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
|
+
}
|
|
15172
15370
|
if (isActiveAiChatTab(tab)) {
|
|
15173
15371
|
state.aiConversationModelId = lockedModelId;
|
|
15174
15372
|
state.aiPromptSent = true;
|
|
@@ -15389,7 +15587,10 @@ function appendMessage(role, text, citations = [], createdAt = null, metadata =
|
|
|
15389
15587
|
if (role === "assistant") {
|
|
15390
15588
|
renderAiProcessSteps(message, processSteps, true, processDurationMs);
|
|
15391
15589
|
}
|
|
15392
|
-
if (role === "user")
|
|
15590
|
+
if (role === "user") {
|
|
15591
|
+
message.dataset.aiCitations = JSON.stringify(Array.isArray(citations) ? citations : []);
|
|
15592
|
+
attachUserCopyAction(message, text);
|
|
15593
|
+
}
|
|
15393
15594
|
if (role === "assistant" && !text.startsWith("调用失败:")) {
|
|
15394
15595
|
const selectedModelId = tab?.modelId ?? tab?.selectedModelId ?? $("#ai-model").value;
|
|
15395
15596
|
const selectedModel = state.models.find((model) => model.id === selectedModelId) ?? state.models[0];
|
|
@@ -15403,6 +15604,7 @@ function appendMessage(role, text, citations = [], createdAt = null, metadata =
|
|
|
15403
15604
|
message.append(meta);
|
|
15404
15605
|
attachAssistantCopyAction(message, text);
|
|
15405
15606
|
}
|
|
15607
|
+
if (isFailure) renderMessageCardActions(message);
|
|
15406
15608
|
attachMessageIdentity(message, messageId);
|
|
15407
15609
|
feed.append(message);
|
|
15408
15610
|
scrollAiFeedToBottom(feed);
|
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=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"></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-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>
|
package/dist/public/styles.css
CHANGED
|
@@ -1598,6 +1598,11 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
1598
1598
|
.platform-image-tool-field { display: grid; flex: 1 1 360px; gap: 7px; min-width: 0; color: var(--muted); font-size: 10px; }
|
|
1599
1599
|
.platform-image-tool-field select { width: 100%; height: 36px; min-height: 36px; padding: 8px 10px; font-size: 12px; }
|
|
1600
1600
|
.platform-image-tool-panel .config-save-button { flex: 0 0 auto; min-height: 36px; padding: 8px 14px; }
|
|
1601
|
+
.platform-stream-timeout-section { margin-bottom: 0; }
|
|
1602
|
+
.platform-stream-timeout-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); }
|
|
1603
|
+
.platform-stream-timeout-field { display: grid; flex: 0 1 260px; gap: 7px; min-width: 0; color: var(--muted); font-size: 10px; }
|
|
1604
|
+
.platform-stream-timeout-field input { width: 100%; height: 36px; min-height: 36px; padding: 8px 10px; font-size: 12px; }
|
|
1605
|
+
.platform-stream-timeout-panel .config-save-button { flex: 0 0 auto; min-height: 36px; padding: 8px 14px; }
|
|
1601
1606
|
.task-auto-run-panel {
|
|
1602
1607
|
position: relative;
|
|
1603
1608
|
display: grid;
|
|
@@ -2580,6 +2585,10 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
2580
2585
|
.platform-image-tool-field { flex: 0 0 auto; }
|
|
2581
2586
|
.platform-image-tool-field select, .platform-image-tool-panel .config-save-button { height: 40px; min-height: 40px; }
|
|
2582
2587
|
.platform-image-tool-panel .config-save-button { width: 100%; }
|
|
2588
|
+
.platform-stream-timeout-panel { align-items: stretch; flex-direction: column; }
|
|
2589
|
+
.platform-stream-timeout-field { flex: 0 0 auto; }
|
|
2590
|
+
.platform-stream-timeout-field input, .platform-stream-timeout-panel .config-save-button { height: 40px; min-height: 40px; }
|
|
2591
|
+
.platform-stream-timeout-panel .config-save-button { width: 100%; }
|
|
2583
2592
|
}
|
|
2584
2593
|
@media (max-width: 640px) {
|
|
2585
2594
|
.config-inline-save .book-summary-context-percent-field,
|
package/dist/server-runtime.js
CHANGED
|
@@ -4,7 +4,7 @@ import { basename, join } from "node:path";
|
|
|
4
4
|
import { createRuntime } from "./app.js";
|
|
5
5
|
import { resolveAiChatTabLimit } from "./ai-chat-tab-limit.js";
|
|
6
6
|
import { resolveAiRetryPolicy } from "./ai-retry.js";
|
|
7
|
-
import { resolveAiStreamIdleTimeoutMs } from "./ai-stream-timeout.js";
|
|
7
|
+
import { AI_STREAM_IDLE_TIMEOUT_SECONDS_ENV, resolveAiStreamIdleTimeoutMs } from "./ai-stream-timeout.js";
|
|
8
8
|
import { DATABASE_SCHEMA_VERSION, readDatabaseSchemaVersion } from "./database.js";
|
|
9
9
|
import { loadMasterSecret } from "./credential-vault.js";
|
|
10
10
|
import { isDevelopmentAuthBypassEnabled, resolveRuntimeSecurity } from "./security.js";
|
|
@@ -204,7 +204,9 @@ export async function startLocalServer(options) {
|
|
|
204
204
|
releaseCheckRetries: resolveReleaseCheckRetries(options.env.APP_UPDATE_CHECK_RETRIES),
|
|
205
205
|
aiChatTabLimit: resolveAiChatTabLimit(options.env),
|
|
206
206
|
aiRetryPolicy: resolveAiRetryPolicy(options.env),
|
|
207
|
-
|
|
207
|
+
...(options.env[AI_STREAM_IDLE_TIMEOUT_SECONDS_ENV]?.trim()
|
|
208
|
+
? { aiStreamIdleTimeoutMs: resolveAiStreamIdleTimeoutMs(options.env) }
|
|
209
|
+
: {}),
|
|
208
210
|
uploadLimits: resolveImageUploadLimits(options.env)
|
|
209
211
|
});
|
|
210
212
|
await runtime.cleanupAttachments();
|