@musnows/scriverse 1.0.2 → 1.0.3
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.js +21 -4
- package/dist/ai.js.map +1 -1
- package/dist/app.js +68 -6
- package/dist/app.js.map +1 -1
- package/dist/public/ai-request-manager.js +10 -2
- package/dist/public/app.js +54 -19
- package/dist/public/index.html +2 -2
- package/dist/public/stream-typewriter.d.ts +1 -0
- package/dist/public/stream-typewriter.js +22 -3
- package/dist/public/styles.css +3 -0
- package/dist/public/text-count.d.ts +1 -0
- package/dist/public/text-count.js +7 -0
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -50,13 +50,17 @@ export function createAiRequestManager() {
|
|
|
50
50
|
if (!current) return false;
|
|
51
51
|
activeRequests.delete(key);
|
|
52
52
|
current.controller.abort(createAiRequestAbortError(reason));
|
|
53
|
+
current.resolveFinished();
|
|
53
54
|
return true;
|
|
54
55
|
};
|
|
55
56
|
|
|
56
57
|
const cancelAll = (reason = "AI 请求已取消") => {
|
|
57
58
|
const requests = [...activeRequests.values()];
|
|
58
59
|
activeRequests.clear();
|
|
59
|
-
for (const current of requests)
|
|
60
|
+
for (const current of requests) {
|
|
61
|
+
current.controller.abort(createAiRequestAbortError(reason));
|
|
62
|
+
current.resolveFinished();
|
|
63
|
+
}
|
|
60
64
|
return requests.length;
|
|
61
65
|
};
|
|
62
66
|
|
|
@@ -67,7 +71,9 @@ export function createAiRequestManager() {
|
|
|
67
71
|
generation += 1;
|
|
68
72
|
const request = snapshot(input, controller, generation);
|
|
69
73
|
if (!request.workId) throw new Error("AI 请求必须绑定作品");
|
|
70
|
-
|
|
74
|
+
let resolveFinished;
|
|
75
|
+
const finished = new Promise((resolve) => { resolveFinished = resolve; });
|
|
76
|
+
activeRequests.set(key, { controller, snapshot: request, finished, resolveFinished });
|
|
71
77
|
return request;
|
|
72
78
|
};
|
|
73
79
|
|
|
@@ -98,6 +104,7 @@ export function createAiRequestManager() {
|
|
|
98
104
|
|
|
99
105
|
const finish = (request) => {
|
|
100
106
|
if (!isCurrent(request)) return false;
|
|
107
|
+
activeRequests.get(request.tabId).resolveFinished();
|
|
101
108
|
activeRequests.delete(request.tabId);
|
|
102
109
|
return true;
|
|
103
110
|
};
|
|
@@ -108,6 +115,7 @@ export function createAiRequestManager() {
|
|
|
108
115
|
cancel,
|
|
109
116
|
cancelAll,
|
|
110
117
|
finish,
|
|
118
|
+
whenIdle: (tabId) => activeRequests.get(requestKey({ tabId }))?.finished ?? Promise.resolve(),
|
|
111
119
|
hasActive: (tabId = null) => tabId === null
|
|
112
120
|
? activeRequests.size > 0
|
|
113
121
|
: activeRequests.has(requestKey({ tabId })),
|
package/dist/public/app.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { buildRelationshipGraph, createGalaxyRenderer, normalizeGalaxyFrameRate, normalizeGalaxyMotionMode, renderRelationshipMindMap } from "/relationship-graph.js?v=20260817-relationship-canvas-scale-v1&feature=galaxy-motion-mode-v3&feature=galaxy-edge-label-threshold-v1";
|
|
2
2
|
import { formatDateTime, normalizeParagraphSpacing } from "/text-formatting.js?v=20260713-saved-at-seconds";
|
|
3
|
+
import { countProseWords } from "/text-count.js?v=20260906-chapter-word-count-consistency-v1";
|
|
3
4
|
import { renderMarkdown } from "/markdown.js?v=20260830-adjacent-blockquotes-v1";
|
|
4
5
|
import { createImWorkspace } from "/im.js?v=20260904-im-judge-outcomes-v106";
|
|
5
6
|
import { findAiMention, listAiMentionOptions, mergeAiReferenceScope, userMessageMentionNames } from "/ai-mentions.js?v=20260811-user-message-mentions-v1";
|
|
@@ -14,7 +15,7 @@ import {
|
|
|
14
15
|
} from "/roleplay-turn.js?v=20260823-ai-roleplay-scene-turn-v2";
|
|
15
16
|
import { shouldShowAiQuickActions } from "/ai-conversation.js?v=20260713-quick-actions";
|
|
16
17
|
import { createAiChatTabManager, normalizeAiChatTabLimit } from "/ai-chat-tabs.js?v=20260816-ai-chat-switcher-v2";
|
|
17
|
-
import { aiRequestTargetsState, createAiRequestAbortError, createAiRequestManager, isAiRequestCancellation } from "/ai-request-manager.js?v=
|
|
18
|
+
import { aiRequestTargetsState, createAiRequestAbortError, createAiRequestManager, isAiRequestCancellation } from "/ai-request-manager.js?v=20260905-question-stream-v2";
|
|
18
19
|
import { calculateLineNumberTextOffset, calculateLineNumberTop } from "/line-number-layout.js?v=20260713-row-box-alignment";
|
|
19
20
|
import { buildChapterLineMirror, findChapterLineWindow } from "/chapter-editor-virtualization.js?v=20260810-visible-lines-v1";
|
|
20
21
|
import { CHAPTER_PARAGRAPH_INDENT, calculateChapterCaretScroll, chapterLineIndexAtOffset, insertIndentedParagraph } from "/chapter-editor-behavior.js?v=20260828-centered-scroll-v1";
|
|
@@ -31,7 +32,7 @@ import { MIN_MODEL_CONTEXT_WINDOW, MODEL_PURPOSE_OPTIONS, MODEL_THINKING_EFFORT_
|
|
|
31
32
|
import { connectivityConfigurationSavedToast, connectivityTestErrorToast, connectivityTestResultToast } from "/ai-connectivity-test.js?v=20260822-private-ai-endpoint-hint-v1";
|
|
32
33
|
import { shouldSendAiPrompt } from "/ai-prompt-keyboard.js?v=20260713-enter-to-send";
|
|
33
34
|
import { estimateAiMessageTokens, formatAiMessageMeta } from "/ai-message-meta.js?v=20260814-ai-model-lock-v1";
|
|
34
|
-
import { createStreamTypewriter, createStreamTypewriterSpeedController } from "/stream-typewriter.js?v=
|
|
35
|
+
import { createStreamTypewriter, createStreamTypewriterSpeedController } from "/stream-typewriter.js?v=20260906-background-stream-v2";
|
|
35
36
|
import { assertAiStreamCompleted, readAiEventStream } from "/ai-stream-protocol.js?v=20260812-ai-stream-complete-v1";
|
|
36
37
|
import { buildUsageCalendar, formatCacheHitRate, formatEstimatedCost, formatTokenCount, usageCalendarYears } from "/ai-usage.js?v=20260830-ai-usage-year-v1";
|
|
37
38
|
import { formatAiMessageTime } from "/ai-message-time.js?v=20260801-month-day-time";
|
|
@@ -665,8 +666,8 @@ function aiSendButtonIconMarkup(stateName) {
|
|
|
665
666
|
|
|
666
667
|
function syncAiRequestControls() {
|
|
667
668
|
const activeTabId = activeAiChatTab()?.id;
|
|
668
|
-
const sending = aiRequestManager.hasActive(activeTabId);
|
|
669
669
|
const continuingQuestion = activeTabId ? aiQuestionContinuationTabIds.has(activeTabId) : false;
|
|
670
|
+
const sending = aiRequestManager.hasActive(activeTabId) && !continuingQuestion;
|
|
670
671
|
const switching = aiConversationNavigationPending !== null;
|
|
671
672
|
const button = $("#ai-send");
|
|
672
673
|
const stateName = sending ? "stop" : (switching || continuingQuestion) ? "switching" : "send";
|
|
@@ -2952,6 +2953,7 @@ function renderMessageCardActions(message) {
|
|
|
2952
2953
|
return;
|
|
2953
2954
|
}
|
|
2954
2955
|
fork.disabled = true;
|
|
2956
|
+
const dismissForkingToast = persistentToast("正在创建分支对话…");
|
|
2955
2957
|
try {
|
|
2956
2958
|
const sourceTab = aiChatTabManager.get(message.closest(".ai-feed")?.dataset.aiTabId);
|
|
2957
2959
|
if (!sourceTab?.conversationId) throw new Error("无法确定消息所属对话");
|
|
@@ -2965,6 +2967,8 @@ function renderMessageCardActions(message) {
|
|
|
2965
2967
|
} catch (error) {
|
|
2966
2968
|
fork.disabled = false;
|
|
2967
2969
|
toast(error.message, "error");
|
|
2970
|
+
} finally {
|
|
2971
|
+
dismissForkingToast();
|
|
2968
2972
|
}
|
|
2969
2973
|
});
|
|
2970
2974
|
actions.append(fork);
|
|
@@ -3532,18 +3536,23 @@ async function openAiUserQuestionDialog(questionId) {
|
|
|
3532
3536
|
}
|
|
3533
3537
|
}
|
|
3534
3538
|
|
|
3535
|
-
function beginAiQuestionContinuationUi(conversationId) {
|
|
3539
|
+
async function beginAiQuestionContinuationUi(conversationId) {
|
|
3536
3540
|
const tab = conversationId ? aiChatTabManager.findByConversation(conversationId) : null;
|
|
3537
3541
|
if (!tab) return null;
|
|
3542
|
+
const workId = state.work.id;
|
|
3543
|
+
await aiRequestManager.whenIdle(tab.id);
|
|
3544
|
+
if (state.work?.id !== workId || !aiChatTabManager.get(tab.id)) throw createAiRequestAbortError("AI 请求目标已切换");
|
|
3545
|
+
const requestHolder = { snapshot: aiRequestManager.begin({ tabId: tab.id, workId, conversationId }) };
|
|
3538
3546
|
aiQuestionContinuationTabIds.add(tab.id);
|
|
3539
3547
|
setAiChatTabStatus(tab, "streaming");
|
|
3540
3548
|
if (isActiveAiChatTab(tab)) syncAiRequestControls();
|
|
3541
3549
|
scrollAiFeedToBottom(tab.feed);
|
|
3542
|
-
return { tab };
|
|
3550
|
+
return { tab, requestHolder };
|
|
3543
3551
|
}
|
|
3544
3552
|
|
|
3545
3553
|
function finishAiQuestionContinuationUi(continuationUi, failed = false) {
|
|
3546
3554
|
if (!continuationUi) return;
|
|
3555
|
+
aiRequestManager.finish(continuationUi.requestHolder.snapshot);
|
|
3547
3556
|
aiQuestionContinuationTabIds.delete(continuationUi.tab.id);
|
|
3548
3557
|
if (aiChatTabManager.get(continuationUi.tab.id)) setAiChatTabStatus(continuationUi.tab, failed ? "error" : "ready");
|
|
3549
3558
|
if (isActiveAiChatTab(continuationUi.tab)) syncAiRequestControls();
|
|
@@ -3557,7 +3566,7 @@ async function reloadAiQuestionConversation(conversationId) {
|
|
|
3557
3566
|
if (String(conversation.workId ?? "") !== String(state.work?.id ?? "")) throw new Error("AI 对话不属于当前作品");
|
|
3558
3567
|
upsertAiConversationSummary(conversation);
|
|
3559
3568
|
applyConversationToAiChatTab(tab, conversation);
|
|
3560
|
-
activateAiChatTab(tab.id, { persistCurrent: false, force: true });
|
|
3569
|
+
if (isActiveAiChatTab(tab)) activateAiChatTab(tab.id, { persistCurrent: false, force: true });
|
|
3561
3570
|
return conversation;
|
|
3562
3571
|
}
|
|
3563
3572
|
|
|
@@ -3577,9 +3586,13 @@ async function respondAiUserQuestion(questionId, payload) {
|
|
|
3577
3586
|
const conversationId = typeof knownQuestion?.conversationId === "string" ? knownQuestion.conversationId : null;
|
|
3578
3587
|
if (questionDialog.open) questionDialog.close();
|
|
3579
3588
|
if (approvalCenterDialog.open) approvalCenterDialog.close();
|
|
3580
|
-
continuationUi = beginAiQuestionContinuationUi(conversationId);
|
|
3589
|
+
continuationUi = await beginAiQuestionContinuationUi(conversationId);
|
|
3581
3590
|
let question;
|
|
3582
|
-
if (
|
|
3591
|
+
if (continuationUi) {
|
|
3592
|
+
const endpoint = questionsEndpoint(`/${encodeURIComponent(String(questionId))}/${payload.action === "reject" ? "reject" : "answer"}`);
|
|
3593
|
+
const streamed = await streamChat(continuationUi.requestHolder, payload.action === "reject" ? {} : { answers: payload.answers }, createAiIdempotencyKey(), { endpoint });
|
|
3594
|
+
question = streamed.question;
|
|
3595
|
+
} else if (payload.action === "reject") {
|
|
3583
3596
|
question = await api(questionsEndpoint(`/${encodeURIComponent(String(questionId))}/reject`), { method: "POST" });
|
|
3584
3597
|
} else {
|
|
3585
3598
|
question = await api(questionsEndpoint(`/${encodeURIComponent(String(questionId))}/answer`), {
|
|
@@ -3588,8 +3601,7 @@ async function respondAiUserQuestion(questionId, payload) {
|
|
|
3588
3601
|
});
|
|
3589
3602
|
}
|
|
3590
3603
|
cacheAiQuestionView(question);
|
|
3591
|
-
currentAiQuestionDialogView = question;
|
|
3592
|
-
aiQuestionDialogQuestionId = String(question.id);
|
|
3604
|
+
if (aiQuestionDialogQuestionId === String(question.id)) currentAiQuestionDialogView = question;
|
|
3593
3605
|
await reloadAiQuestionConversation(question.conversationId ?? conversationId);
|
|
3594
3606
|
toast(payload.action === "reject" ? "已跳过该提问批次,AI 已继续处理" : "全部回答已提交,AI 已继续处理");
|
|
3595
3607
|
return question;
|
|
@@ -3609,6 +3621,7 @@ async function respondAiUserQuestion(questionId, payload) {
|
|
|
3609
3621
|
} finally {
|
|
3610
3622
|
finishAiQuestionContinuationUi(continuationUi, continuationFailed);
|
|
3611
3623
|
aiQuestionDialogBusy = false;
|
|
3624
|
+
if (questionDialog.open && currentAiQuestionDialogView?.status === "pending") renderAiUserQuestionOptions(currentAiQuestionDialogView);
|
|
3612
3625
|
}
|
|
3613
3626
|
}
|
|
3614
3627
|
|
|
@@ -9293,8 +9306,8 @@ async function selectChapter(chapterId, { editMode = false } = {}) {
|
|
|
9293
9306
|
function updateChapterStats() {
|
|
9294
9307
|
if (!state.chapter) return;
|
|
9295
9308
|
const text = $("#chapter-content").value;
|
|
9296
|
-
const count =
|
|
9297
|
-
$("#chapter-stats").textContent = `${count} 字 · v${state.chapter.versionNo}`;
|
|
9309
|
+
const count = countProseWords(text);
|
|
9310
|
+
$("#chapter-stats").textContent = `${count.toLocaleString("zh-CN")} 字 · v${state.chapter.versionNo}`;
|
|
9298
9311
|
}
|
|
9299
9312
|
|
|
9300
9313
|
function readReadingStorage(key) {
|
|
@@ -18313,7 +18326,7 @@ function renderAiStreamingCharacterProgress(meta, visibleCharacters) {
|
|
|
18313
18326
|
meta.replaceChildren("正在生成 · ", createAiStreamCharacterCount(visible), " 字");
|
|
18314
18327
|
}
|
|
18315
18328
|
|
|
18316
|
-
async function streamChat(requestHolder, body, idempotencyKey) {
|
|
18329
|
+
async function streamChat(requestHolder, body, idempotencyKey, { endpoint = null } = {}) {
|
|
18317
18330
|
const tab = aiChatTabForRequest(requestHolder.snapshot);
|
|
18318
18331
|
if (!tab) throw createAiRequestAbortError("Agent 对话页签已关闭");
|
|
18319
18332
|
const feed = tab.feed;
|
|
@@ -18336,7 +18349,7 @@ async function streamChat(requestHolder, body, idempotencyKey) {
|
|
|
18336
18349
|
window.clearInterval(streamConnectionTimer);
|
|
18337
18350
|
streamConnectionTimer = null;
|
|
18338
18351
|
};
|
|
18339
|
-
const streamConnectionEstablishedEvents = new Set(["delta", "process_step", "tool_call", "context_compacted", "complete", "request_status", "error"]);
|
|
18352
|
+
const streamConnectionEstablishedEvents = new Set(["continuation", "delta", "process_step", "tool_call", "context_compacted", "complete", "request_status", "error"]);
|
|
18340
18353
|
const streamSpeedController = createStreamTypewriterSpeedController();
|
|
18341
18354
|
let messageMounted = false;
|
|
18342
18355
|
const mountAssistantMessage = () => {
|
|
@@ -18366,12 +18379,14 @@ async function streamChat(requestHolder, body, idempotencyKey) {
|
|
|
18366
18379
|
let persistedMessageCreatedAt = null;
|
|
18367
18380
|
let conversationTitle = null;
|
|
18368
18381
|
let writingSuggestion = null;
|
|
18382
|
+
let question = null;
|
|
18369
18383
|
let persistedUserMessage = null;
|
|
18370
18384
|
let contextAction = "ready";
|
|
18371
18385
|
let warningOnly = false;
|
|
18372
18386
|
let streamContextCompacted = false;
|
|
18373
18387
|
const processStartedAt = Date.now();
|
|
18374
|
-
|
|
18388
|
+
let previousProcessDurationMs = 0;
|
|
18389
|
+
const elapsedProcessTime = () => previousProcessDurationMs + Math.max(0, Date.now() - processStartedAt);
|
|
18375
18390
|
const processStepTypewriters = new Map();
|
|
18376
18391
|
const processStepVisibleContents = new Map();
|
|
18377
18392
|
const renderStreamingProcessSteps = (completed, durationMs = elapsedProcessTime()) => {
|
|
@@ -18400,7 +18415,7 @@ async function streamChat(requestHolder, body, idempotencyKey) {
|
|
|
18400
18415
|
};
|
|
18401
18416
|
try {
|
|
18402
18417
|
const request = assertAiRequestCurrent(requestHolder.snapshot);
|
|
18403
|
-
const response = await fetch(`/api/works/${encodeURIComponent(request.workId)}/chat/stream`, {
|
|
18418
|
+
const response = await fetch(endpoint ?? `/api/works/${encodeURIComponent(request.workId)}/chat/stream`, {
|
|
18404
18419
|
method: "POST",
|
|
18405
18420
|
headers: { "Content-Type": "application/json", Accept: "text/event-stream", "X-CSRF-Token": state.csrfToken, "Idempotency-Key": idempotencyKey },
|
|
18406
18421
|
body: JSON.stringify(body),
|
|
@@ -18415,7 +18430,23 @@ async function streamChat(requestHolder, body, idempotencyKey) {
|
|
|
18415
18430
|
const consume = async (eventName, payload) => {
|
|
18416
18431
|
assertAiRequestCurrent(requestHolder.snapshot);
|
|
18417
18432
|
if (streamConnectionEstablishedEvents.has(eventName)) stopStreamConnectionTimer();
|
|
18418
|
-
if (eventName === "
|
|
18433
|
+
if (eventName === "continuation") {
|
|
18434
|
+
if (String(payload.conversationId ?? "") !== requestHolder.snapshot.conversationId) throw new Error("流式续接返回了其他对话");
|
|
18435
|
+
toolCalls = Array.isArray(payload.toolCalls) ? payload.toolCalls : [];
|
|
18436
|
+
processSteps = Array.isArray(payload.processSteps) ? payload.processSteps : [];
|
|
18437
|
+
previousProcessDurationMs = Math.max(0, Number(payload.processDurationMs) || 0);
|
|
18438
|
+
const previousMessage = [...feed.querySelectorAll(".assistant-message[data-message-id]")]
|
|
18439
|
+
.find((candidate) => candidate.dataset.messageId === String(payload.messageId));
|
|
18440
|
+
if (previousMessage) {
|
|
18441
|
+
attachMessageHeading(message, aiAssistantLabel("正在生成", tab.roleplayCharacter), undefined, tab);
|
|
18442
|
+
previousMessage.replaceWith(message);
|
|
18443
|
+
messageMounted = true;
|
|
18444
|
+
} else mountAssistantMessage();
|
|
18445
|
+
attachMessageIdentity(message, payload.messageId);
|
|
18446
|
+
renderStreamingProcessSteps(false);
|
|
18447
|
+
meta.textContent = "已收到回答,正在继续思考与执行……";
|
|
18448
|
+
scrollAiFeedToBottom(feed);
|
|
18449
|
+
} else if (eventName === "context") {
|
|
18419
18450
|
contextAction = typeof payload.action === "string" ? payload.action : "ready";
|
|
18420
18451
|
if (!tab.promptSent) setAiChatTabContextUsage(tab, payload.usage);
|
|
18421
18452
|
if (payload.conversation?.id) {
|
|
@@ -18512,6 +18543,7 @@ async function streamChat(requestHolder, body, idempotencyKey) {
|
|
|
18512
18543
|
setAiChatTabContextUsage(tab, payload.contextUsage);
|
|
18513
18544
|
if (messageMounted) meta.textContent = "已压缩工具上下文,正在继续生成";
|
|
18514
18545
|
} else if (eventName === "complete") {
|
|
18546
|
+
question = payload.question ?? null;
|
|
18515
18547
|
if (payload.warningOnly === true) {
|
|
18516
18548
|
warningOnly = true;
|
|
18517
18549
|
setAiChatTabContextUsage(tab, payload.contextUsage);
|
|
@@ -18568,7 +18600,7 @@ async function streamChat(requestHolder, body, idempotencyKey) {
|
|
|
18568
18600
|
assertAiRequestCurrent(requestHolder.snapshot);
|
|
18569
18601
|
if (streamError) throw streamError;
|
|
18570
18602
|
assertAiStreamCompleted(streamCompleted);
|
|
18571
|
-
return { action: warningOnly ? "warn" : contextAction, content: streamedText, message, metadata: generatedMetadata, messageId: persistedMessageId, createdAt: persistedMessageCreatedAt, conversationTitle, writingSuggestion, userMessage: persistedUserMessage };
|
|
18603
|
+
return { action: warningOnly ? "warn" : contextAction, content: streamedText, message, metadata: generatedMetadata, messageId: persistedMessageId, createdAt: persistedMessageCreatedAt, conversationTitle, writingSuggestion, userMessage: persistedUserMessage, question };
|
|
18572
18604
|
} catch (error) {
|
|
18573
18605
|
const streamFailure = error instanceof Error ? error : new Error(String(error ?? "AI 流式调用失败"));
|
|
18574
18606
|
const interruptionCode = typeof streamFailure.code === "string" ? streamFailure.code.slice(0, 100) : "AI_STREAM_FAILED";
|
|
@@ -18793,7 +18825,10 @@ function appendMessage(role, text, citations = [], createdAt = null, metadata =
|
|
|
18793
18825
|
function continuationGuardMarkup(guard) {
|
|
18794
18826
|
if (!guard) return "";
|
|
18795
18827
|
const issues = Array.isArray(guard.issues) ? guard.issues : [];
|
|
18796
|
-
|
|
18828
|
+
const failure = typeof guard.failure === "string" && guard.failure.trim()
|
|
18829
|
+
? guard.failure.trim()
|
|
18830
|
+
: "无法完成检查,请谨慎采纳";
|
|
18831
|
+
return `<section class="guard-card ${esc(guard.status)}" data-testid="continuation-guard"><strong>${guard.status === "clear" ? "一致性守卫:未发现冲突" : guard.status === "warning" ? `一致性守卫:发现 ${issues.length} 项风险` : "一致性守卫:检查失败"}</strong>${guard.status === "failed" ? `<details class="guard-failure-details"><summary>查看失败原因</summary><p>${esc(failure)}</p></details>` : issues.map((issue) => `<p><b>${esc(levelLabel(issue.severity))} · ${esc(reviewItemTypeLabel(issue.type))}</b> ${esc(issue.title)}${issue.description ? `:${esc(issue.description)}` : ""}</p>`).join("")}</section>`;
|
|
18797
18832
|
}
|
|
18798
18833
|
|
|
18799
18834
|
async function applyAcceptedWritingSuggestion(message, suggestion) {
|
package/dist/public/index.html
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
<script src="/theme-init.js?v=20260827-reader-prefetch-v1"></script>
|
|
10
10
|
<link rel="icon" href="/icon.svg?v=20260712" type="image/svg+xml">
|
|
11
11
|
<link rel="manifest" href="/site.webmanifest">
|
|
12
|
-
<link rel="stylesheet" href="/styles.css?v=20260816-task-scope-volume-collapse-v2&feature=ai-tool-call-copy-feedback-v2&feature=ai-send-control-v3&feature=character-gender-v1&feature=character-filter-state-v1&feature=relationship-canvas-scale-v1&feature=relationship-table-scroll-v1&feature=galaxy-compact-controls-v2&feature=galaxy-motion-mode-v2&feature=chapter-search-replace-v3&feature=task-auto-run-ring-center-v3&feature=character-relationship-delete-v1&feature=ai-assistant-workspace-v2&feature=mobile-module-tab-position-v1&feature=volume-detail-icon-v1&feature=editor-actions-flow-v1&feature=reader-controls-subpanel-v1&feature=reader-focus-ring-v1&feature=ai-message-actions-v1&feature=assistant-responsive-navigation-v2&feature=ai-composer-square-controls-v2&feature=annotation-line-counts-v1&feature=chapter-comment-filters-v1&feature=line-number-gutter-fill-v1&feature=ai-relationship-roleplay-v1&feature=ai-model-picker-v1&feature=markdown-word-count-five-digit-v2&feature=ai-stream-character-count-stable-v1&feature=ai-stream-character-count-five-digit-v1&feature=annotation-marker-offset-v1&feature=mobile-ai-entry-hidden-v1&feature=phone-client-entry-v1&feature=ai-stream-idle-timeout-v1&feature=ai-user-message-width-v2&feature=ai-chat-image-attachments-v9&feature=ai-message-image-preview-v1&feature=ai-thinking-scroll-v2&feature=toast-click-dismiss-v3&feature=character-avatar-v6&feature=admin-ai-conversations-v1&feature=ai-usage-pricing-v1&feature=ai-usage-pricing-badge-v2&feature=ai-token-quota-positive-v5&feature=ai-token-usage-details-v1&feature=ai-token-usage-details-centered-v1&feature=ai-stream-connection-seconds-v1&feature=ai-token-usage-estimated-price-v1&feature=record-favorites-v1&feature=entity-detail-favorites-v1&feature=entity-pin-v1&feature=character-persona-summary-v1&feature=ai-roleplay-scene-turn-v1&feature=admin-account-identity-v2&feature=task-detail-failure-orange-v1&feature=character-card-header-alignment-v6&feature=character-card-title-fit-v2&feature=book-import-progress-v1&feature=editor-toolbar-compact-v2&feature=reader-first-frame-v1&feature=auth-compact-v2&feature=editor-preview-toggle-v2&feature=setting-title-chrome-v2&feature=entity-pin-icon-center-v1&feature=chapter-center-bottom-space-v1&feature=work-editor-preferences-v1&feature=work-editor-preference-checkbox-v1&feature=ai-roleplay-memory-v2&feature=ai-roleplay-memory-v3&feature=ai-roleplay-memory-v5&feature=character-editor-header-actions-v1&feature=roleplay-memory-header-actions-v1&feature=roleplay-memory-action-icons-v1&feature=roleplay-memory-action-colors-v1&feature=roleplay-memory-pin-border-v1&feature=ai-write-tools-v2&feature=ai-write-card-actions-compact-v1&feature=ai-write-plan-actions-footer-v1&feature=ai-question-actions-footer-v1&feature=ai-question-selection-highlight-v1&feature=ai-question-submit-guidance-v1&feature=ai-question-answer-limit-v1&feature=ai-question-batch-v1&feature=semantic-search-v6&feature=chapter-title-renumber-v1&feature=ai-writing-skills-v1&feature=remote-mcp-v1&feature=character-alias-chips-v2&feature=character-title-input-v1&feature=character-detail-value-wrap-v2&feature=ai-settings-textarea-font-v1&feature=ai-usage-stat-display-v1&feature=ai-usage-year-v1&feature=ai-skill-slash-menu-v1&feature=ai-message-citation-popover-v1&feature=line-citation-menu-separator-v1&feature=global-im-v106&feature=im-sidebar-compact-v1&feature=im-narration-contrast-v1&feature=im-member-add-plus-v2&feature=im-button-hierarchy-v1&feature=im-icon-button-size-v1&feature=compact-sidebar-directory-v5&feature=ai-model-config-dialog-v1&feature=system-prompt-override-v3">
|
|
12
|
+
<link rel="stylesheet" href="/styles.css?v=20260816-task-scope-volume-collapse-v2&feature=ai-tool-call-copy-feedback-v2&feature=ai-send-control-v3&feature=character-gender-v1&feature=character-filter-state-v1&feature=relationship-canvas-scale-v1&feature=relationship-table-scroll-v1&feature=galaxy-compact-controls-v2&feature=galaxy-motion-mode-v2&feature=chapter-search-replace-v3&feature=task-auto-run-ring-center-v3&feature=character-relationship-delete-v1&feature=ai-assistant-workspace-v2&feature=mobile-module-tab-position-v1&feature=volume-detail-icon-v1&feature=editor-actions-flow-v1&feature=reader-controls-subpanel-v1&feature=reader-focus-ring-v1&feature=ai-message-actions-v1&feature=assistant-responsive-navigation-v2&feature=ai-composer-square-controls-v2&feature=annotation-line-counts-v1&feature=chapter-comment-filters-v1&feature=line-number-gutter-fill-v1&feature=ai-relationship-roleplay-v1&feature=ai-model-picker-v1&feature=markdown-word-count-five-digit-v2&feature=ai-stream-character-count-stable-v1&feature=ai-stream-character-count-five-digit-v1&feature=annotation-marker-offset-v1&feature=mobile-ai-entry-hidden-v1&feature=phone-client-entry-v1&feature=ai-stream-idle-timeout-v1&feature=ai-user-message-width-v2&feature=ai-chat-image-attachments-v9&feature=ai-message-image-preview-v1&feature=ai-thinking-scroll-v2&feature=toast-click-dismiss-v3&feature=character-avatar-v6&feature=admin-ai-conversations-v1&feature=ai-usage-pricing-v1&feature=ai-usage-pricing-badge-v2&feature=ai-token-quota-positive-v5&feature=ai-token-usage-details-v1&feature=ai-token-usage-details-centered-v1&feature=ai-stream-connection-seconds-v1&feature=ai-token-usage-estimated-price-v1&feature=record-favorites-v1&feature=entity-detail-favorites-v1&feature=entity-pin-v1&feature=character-persona-summary-v1&feature=ai-roleplay-scene-turn-v1&feature=admin-account-identity-v2&feature=task-detail-failure-orange-v1&feature=character-card-header-alignment-v6&feature=character-card-title-fit-v2&feature=book-import-progress-v1&feature=editor-toolbar-compact-v2&feature=reader-first-frame-v1&feature=auth-compact-v2&feature=editor-preview-toggle-v2&feature=setting-title-chrome-v2&feature=entity-pin-icon-center-v1&feature=chapter-center-bottom-space-v1&feature=work-editor-preferences-v1&feature=work-editor-preference-checkbox-v1&feature=ai-roleplay-memory-v2&feature=ai-roleplay-memory-v3&feature=ai-roleplay-memory-v5&feature=character-editor-header-actions-v1&feature=roleplay-memory-header-actions-v1&feature=roleplay-memory-action-icons-v1&feature=roleplay-memory-action-colors-v1&feature=roleplay-memory-pin-border-v1&feature=ai-write-tools-v2&feature=ai-write-card-actions-compact-v1&feature=ai-write-plan-actions-footer-v1&feature=ai-question-actions-footer-v1&feature=ai-question-selection-highlight-v1&feature=ai-question-submit-guidance-v1&feature=ai-question-answer-limit-v1&feature=ai-question-batch-v1&feature=semantic-search-v6&feature=chapter-title-renumber-v1&feature=ai-writing-skills-v1&feature=remote-mcp-v1&feature=character-alias-chips-v2&feature=character-title-input-v1&feature=character-detail-value-wrap-v2&feature=ai-settings-textarea-font-v1&feature=ai-usage-stat-display-v1&feature=ai-usage-year-v1&feature=ai-skill-slash-menu-v1&feature=ai-message-citation-popover-v1&feature=line-citation-menu-separator-v1&feature=global-im-v106&feature=im-sidebar-compact-v1&feature=im-narration-contrast-v1&feature=im-member-add-plus-v2&feature=im-button-hierarchy-v1&feature=im-icon-button-size-v1&feature=compact-sidebar-directory-v5&feature=ai-model-config-dialog-v1&feature=system-prompt-override-v3&feature=continuation-guard-failure-details-v1">
|
|
13
13
|
</head>
|
|
14
14
|
<body class="auth-pending">
|
|
15
15
|
<section id="auth-view" class="auth-view hidden" aria-labelledby="auth-title">
|
|
@@ -1425,6 +1425,6 @@
|
|
|
1425
1425
|
</dialog>
|
|
1426
1426
|
|
|
1427
1427
|
<div id="auth-loading" class="auth-loading" role="status" aria-label="正在载入工作台"></div>
|
|
1428
|
-
<script type="module" src="/app.js?v=20260816-extended-thinking-effort-v1&feature=ai-history-rename-v1&feature=ai-tool-call-copy-feedback-v2&feature=ai-send-control-v2&feature=analysis-task-queue-refresh-v1&feature=character-gender-v1&feature=character-filter-state-v1&feature=relationship-canvas-scale-v1&feature=relationship-table-scroll-v1&feature=ai-session-id-copy-v2&feature=galaxy-motion-mode-v3&feature=galaxy-edge-label-threshold-v1&feature=calculate-time-tool-v2&feature=analysis-task-expired-toast-v1&feature=global-replace-volume-v1&feature=chapter-search-replace-v1&feature=chapter-save-toast-v1&feature=character-relationship-delete-v1&feature=character-relationship-group-v1&feature=analysis-task-stability-delay-v1&feature=ai-assistant-workspace-v2&feature=volume-detail-icon-v1&feature=volume-story-order-v1&feature=reader-manual-chapter-navigation-v1&feature=ai-message-reference-badges-v1&feature=ai-roleplay-message-reference-v1&feature=ai-message-actions-v1&feature=assistant-responsive-navigation-v3&feature=annotation-permissions-v1&feature=annotation-line-counts-v1&feature=ai-relationship-roleplay-v1&feature=ai-roleplay-user-character-visibility-v1&feature=ai-roleplay-story-recall-v1&feature=ai-model-picker-v1&feature=ai-fork-model-unlock-v1&feature=context-percent-format-v1&feature=annotation-precise-locate-v1&feature=markdown-word-count-stable-v1&feature=ai-stream-character-count-stable-v2&feature=ai-process-empty-intermediate-v1&feature=ai-feed-scroll-follow-v2&feature=ai-message-retry-v1&feature=ai-stream-idle-timeout-v2&feature=ai-config-delete-v1&feature=ai-provider-protocol-options-v1&feature=ai-provider-thinking-type-v1&feature=ai-chat-image-attachments-v8&feature=ai-message-image-preview-v1&feature=ai-thinking-scroll-v2&feature=ai-image-conversation-model-lock-v1&feature=toast-click-dismiss-v2&feature=system-restart-dialog-delay-v1&feature=character-avatar-v6&feature=character-death-position-v1&feature=admin-ai-conversations-v1&feature=ai-usage-pricing-v1&feature=ai-usage-pricing-badge-v2&feature=ai-usage-pricing-label-v1&feature=ai-usage-token-breakdown-v1&feature=ai-usage-pricing-cache-v2&feature=ai-usage-pricing-manual-refresh-v1&feature=ai-monthly-token-quota-v1&feature=ai-provider-token-quota-v1&feature=ai-token-quota-positive-v6&feature=ai-model-thinking-label-v1&feature=ai-model-picker-focus-v1&feature=ai-provider-model-import-v1&feature=ai-assistant-brain-icon-v1&feature=ai-token-usage-details-v1&feature=ai-token-usage-details-centered-v1&feature=ai-token-usage-raw-input-v1&feature=ai-stream-connection-seconds-v1&feature=phone-client-entry-v1&feature=ai-usage-pricing-cache-v1&feature=ai-model-thinking-label-v3&feature=ai-roleplay-speaker-label-v1&feature=toast-modal-host-v1&feature=api-key-copy-existing-v1&feature=character-favorite-v1&feature=record-favorites-v1&feature=ai-token-usage-estimated-price-v1&feature=entity-detail-favorites-v1&feature=entity-pin-v1&feature=entity-pin-icon-v1&feature=roleplay-favorite-label-v1&feature=ai-roleplay-knowledge-tools-v1&feature=character-persona-summary-v1&feature=ai-roleplay-scene-turn-v2&feature=admin-account-identity-v2&feature=ai-provider-analysis-timeout-v1&feature=task-detail-failure-orange-v1&feature=book-import-progress-v1&feature=presence-multiple-users-v1&feature=ai-context-input-output-v1&feature=editor-blank-lines-preserved-v1&feature=reader-first-frame-v1&feature=vditor-lazy-load-v1&feature=ui-module-preload-v1&feature=reader-initial-prefetch-v1&feature=editor-preview-toggle-v2&feature=vditor-fullscreen-disabled-v1&feature=chapter-auto-indent-v1&feature=chapter-centered-scroll-v1&feature=work-editor-preferences-v1&feature=ai-context-output-usage-v1&feature=ai-roleplay-memory-v4&feature=ai-roleplay-memory-v5&feature=roleplay-memory-header-actions-v1&feature=roleplay-memory-header-toolbar-v1&feature=roleplay-memory-action-icons-v1&feature=roleplay-memory-action-colors-v1&feature=annotation-line-anchor-v1&feature=stable-line-ids-v1&feature=live-annotation-anchors-v1&feature=chapter-comment-filters-v1&feature=ai-write-tools-v3&feature=ai-question-option-supplement-v1&feature=ai-question-selection-highlight-v1&feature=ai-question-continuation-ui-v1&feature=ai-question-tool-result-v1&feature=ai-question-tool-summary-v1&feature=ai-question-submit-guidance-v1&feature=ai-question-batch-v1&feature=ai-question-answer-limit-v1&feature=semantic-search-v6&feature=chapter-title-renumber-v1&feature=ai-writing-skills-v3&feature=ai-cancel-preserve-process-v2&feature=remote-mcp-v1&feature=character-alias-chips-v2&feature=character-title-input-v1&feature=character-detail-value-wrap-v2&feature=chapter-batch-editor-refresh-v1&feature=ai-usage-stat-display-v1&feature=ai-usage-year-v1&feature=semantic-search-rag-label-v1&feature=ai-skill-slash-menu-v1&feature=ai-roleplay-scene-bubble-v1&feature=ai-roleplay-scene-collapse-v1&feature=markdown-adjacent-blockquotes-v1&feature=chapter-save-shortcut-v2&feature=ai-message-citation-popover-v1&feature=line-citation-menu-separator-v1&feature=global-im-v106&feature=im-sidebar-compact-v1&feature=im-settings-gear-v1&feature=compact-sidebar-directory-v2"></script>
|
|
1428
|
+
<script type="module" src="/app.js?v=20260816-extended-thinking-effort-v1&feature=ai-history-rename-v1&feature=ai-tool-call-copy-feedback-v2&feature=ai-send-control-v2&feature=analysis-task-queue-refresh-v1&feature=character-gender-v1&feature=character-filter-state-v1&feature=relationship-canvas-scale-v1&feature=relationship-table-scroll-v1&feature=ai-session-id-copy-v2&feature=galaxy-motion-mode-v3&feature=galaxy-edge-label-threshold-v1&feature=calculate-time-tool-v2&feature=analysis-task-expired-toast-v1&feature=global-replace-volume-v1&feature=chapter-search-replace-v1&feature=chapter-save-toast-v1&feature=character-relationship-delete-v1&feature=character-relationship-group-v1&feature=analysis-task-stability-delay-v1&feature=ai-assistant-workspace-v2&feature=volume-detail-icon-v1&feature=volume-story-order-v1&feature=reader-manual-chapter-navigation-v1&feature=ai-message-reference-badges-v1&feature=ai-roleplay-message-reference-v1&feature=ai-message-actions-v1&feature=assistant-responsive-navigation-v3&feature=annotation-permissions-v1&feature=annotation-line-counts-v1&feature=ai-relationship-roleplay-v1&feature=ai-roleplay-user-character-visibility-v1&feature=ai-roleplay-story-recall-v1&feature=ai-model-picker-v1&feature=ai-fork-model-unlock-v1&feature=context-percent-format-v1&feature=annotation-precise-locate-v1&feature=markdown-word-count-stable-v1&feature=ai-stream-character-count-stable-v2&feature=ai-process-empty-intermediate-v1&feature=ai-feed-scroll-follow-v2&feature=ai-message-retry-v1&feature=ai-stream-idle-timeout-v2&feature=ai-config-delete-v1&feature=ai-provider-protocol-options-v1&feature=ai-provider-thinking-type-v1&feature=ai-chat-image-attachments-v8&feature=ai-message-image-preview-v1&feature=ai-thinking-scroll-v2&feature=ai-image-conversation-model-lock-v1&feature=toast-click-dismiss-v2&feature=system-restart-dialog-delay-v1&feature=character-avatar-v6&feature=character-death-position-v1&feature=admin-ai-conversations-v1&feature=ai-usage-pricing-v1&feature=ai-usage-pricing-badge-v2&feature=ai-usage-pricing-label-v1&feature=ai-usage-token-breakdown-v1&feature=ai-usage-pricing-cache-v2&feature=ai-usage-pricing-manual-refresh-v1&feature=ai-monthly-token-quota-v1&feature=ai-provider-token-quota-v1&feature=ai-token-quota-positive-v6&feature=ai-model-thinking-label-v1&feature=ai-model-picker-focus-v1&feature=ai-provider-model-import-v1&feature=ai-assistant-brain-icon-v1&feature=ai-token-usage-details-v1&feature=ai-token-usage-details-centered-v1&feature=ai-token-usage-raw-input-v1&feature=ai-stream-connection-seconds-v1&feature=phone-client-entry-v1&feature=ai-usage-pricing-cache-v1&feature=ai-model-thinking-label-v3&feature=ai-roleplay-speaker-label-v1&feature=toast-modal-host-v1&feature=api-key-copy-existing-v1&feature=character-favorite-v1&feature=record-favorites-v1&feature=ai-token-usage-estimated-price-v1&feature=entity-detail-favorites-v1&feature=entity-pin-v1&feature=entity-pin-icon-v1&feature=roleplay-favorite-label-v1&feature=ai-roleplay-knowledge-tools-v1&feature=character-persona-summary-v1&feature=ai-roleplay-scene-turn-v2&feature=admin-account-identity-v2&feature=ai-provider-analysis-timeout-v1&feature=task-detail-failure-orange-v1&feature=book-import-progress-v1&feature=presence-multiple-users-v1&feature=ai-context-input-output-v1&feature=editor-blank-lines-preserved-v1&feature=reader-first-frame-v1&feature=vditor-lazy-load-v1&feature=ui-module-preload-v1&feature=reader-initial-prefetch-v1&feature=editor-preview-toggle-v2&feature=vditor-fullscreen-disabled-v1&feature=chapter-auto-indent-v1&feature=chapter-centered-scroll-v1&feature=work-editor-preferences-v1&feature=ai-context-output-usage-v1&feature=ai-roleplay-memory-v4&feature=ai-roleplay-memory-v5&feature=roleplay-memory-header-actions-v1&feature=roleplay-memory-header-toolbar-v1&feature=roleplay-memory-action-icons-v1&feature=roleplay-memory-action-colors-v1&feature=annotation-line-anchor-v1&feature=stable-line-ids-v1&feature=live-annotation-anchors-v1&feature=chapter-comment-filters-v1&feature=ai-write-tools-v3&feature=ai-question-option-supplement-v1&feature=ai-question-selection-highlight-v1&feature=ai-question-continuation-ui-v3&feature=ai-background-stream-v1&feature=ai-question-tool-result-v1&feature=ai-question-tool-summary-v1&feature=ai-question-submit-guidance-v1&feature=ai-question-batch-v1&feature=ai-question-answer-limit-v1&feature=semantic-search-v6&feature=chapter-title-renumber-v1&feature=ai-writing-skills-v3&feature=ai-cancel-preserve-process-v2&feature=remote-mcp-v1&feature=character-alias-chips-v2&feature=character-title-input-v1&feature=character-detail-value-wrap-v2&feature=chapter-batch-editor-refresh-v1&feature=ai-usage-stat-display-v1&feature=ai-usage-year-v1&feature=semantic-search-rag-label-v1&feature=ai-skill-slash-menu-v1&feature=ai-roleplay-scene-bubble-v1&feature=ai-roleplay-scene-collapse-v1&feature=markdown-adjacent-blockquotes-v1&feature=chapter-save-shortcut-v2&feature=ai-message-citation-popover-v1&feature=line-citation-menu-separator-v1&feature=global-im-v106&feature=im-sidebar-compact-v1&feature=ai-fork-progress-toast-v1&feature=im-settings-gear-v1&feature=compact-sidebar-directory-v2&feature=chapter-word-count-consistency-v1&feature=continuation-guard-failure-details-v1"></script>
|
|
1429
1429
|
</body>
|
|
1430
1430
|
</html>
|
|
@@ -28,4 +28,5 @@ export function createStreamTypewriter<FrameHandle = number>(options: {
|
|
|
28
28
|
cancelFrame?: (handle: FrameHandle) => void;
|
|
29
29
|
reducedMotion?: boolean;
|
|
30
30
|
speedController?: StreamTypewriterSpeedController | null;
|
|
31
|
+
visibilitySource?: Pick<Document, "visibilityState" | "addEventListener" | "removeEventListener"> | null;
|
|
31
32
|
}): StreamTypewriter;
|
|
@@ -74,7 +74,8 @@ export function createStreamTypewriter({
|
|
|
74
74
|
scheduleFrame = (callback) => window.requestAnimationFrame(callback),
|
|
75
75
|
cancelFrame = (handle) => window.cancelAnimationFrame(handle),
|
|
76
76
|
reducedMotion = typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches === true,
|
|
77
|
-
speedController = null
|
|
77
|
+
speedController = null,
|
|
78
|
+
visibilitySource = typeof document === "undefined" ? null : document
|
|
78
79
|
}) {
|
|
79
80
|
if (typeof onRender !== "function") throw new TypeError("onRender must be a function");
|
|
80
81
|
|
|
@@ -83,10 +84,18 @@ export function createStreamTypewriter({
|
|
|
83
84
|
const idleResolvers = [];
|
|
84
85
|
let scheduledFrame = null;
|
|
85
86
|
let finishing = false;
|
|
87
|
+
let listeningForVisibility = false;
|
|
86
88
|
|
|
87
89
|
const snapshot = () => visibleCharacters.join("");
|
|
90
|
+
const visibilityChanged = () => {
|
|
91
|
+
if (pendingCharacters.length) typewriter.reveal();
|
|
92
|
+
};
|
|
88
93
|
const resolveIdle = () => {
|
|
89
94
|
if (pendingCharacters.length || scheduledFrame !== null) return;
|
|
95
|
+
if (listeningForVisibility) {
|
|
96
|
+
visibilitySource.removeEventListener("visibilitychange", visibilityChanged);
|
|
97
|
+
listeningForVisibility = false;
|
|
98
|
+
}
|
|
90
99
|
const value = snapshot();
|
|
91
100
|
for (const resolve of idleResolvers.splice(0)) resolve(value);
|
|
92
101
|
};
|
|
@@ -97,7 +106,15 @@ export function createStreamTypewriter({
|
|
|
97
106
|
});
|
|
98
107
|
};
|
|
99
108
|
const schedule = () => {
|
|
109
|
+
if (visibilitySource?.visibilityState === "hidden") {
|
|
110
|
+
typewriter.reveal();
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
100
113
|
if (scheduledFrame !== null || pendingCharacters.length === 0) return;
|
|
114
|
+
if (visibilitySource && !listeningForVisibility) {
|
|
115
|
+
visibilitySource.addEventListener("visibilitychange", visibilityChanged);
|
|
116
|
+
listeningForVisibility = true;
|
|
117
|
+
}
|
|
101
118
|
scheduledFrame = scheduleFrame(() => {
|
|
102
119
|
scheduledFrame = null;
|
|
103
120
|
const batchSize = reducedMotion
|
|
@@ -114,7 +131,7 @@ export function createStreamTypewriter({
|
|
|
114
131
|
});
|
|
115
132
|
};
|
|
116
133
|
|
|
117
|
-
|
|
134
|
+
const typewriter = {
|
|
118
135
|
append(value) {
|
|
119
136
|
const characters = Array.from(String(value ?? ""));
|
|
120
137
|
if (!characters.length) return;
|
|
@@ -137,8 +154,9 @@ export function createStreamTypewriter({
|
|
|
137
154
|
finish() {
|
|
138
155
|
if (!pendingCharacters.length && scheduledFrame === null) return Promise.resolve(snapshot());
|
|
139
156
|
finishing = true;
|
|
157
|
+
const completed = new Promise((resolve) => idleResolvers.push(resolve));
|
|
140
158
|
schedule();
|
|
141
|
-
return
|
|
159
|
+
return completed;
|
|
142
160
|
},
|
|
143
161
|
reveal() {
|
|
144
162
|
if (scheduledFrame !== null) {
|
|
@@ -152,4 +170,5 @@ export function createStreamTypewriter({
|
|
|
152
170
|
return snapshot();
|
|
153
171
|
}
|
|
154
172
|
};
|
|
173
|
+
return typewriter;
|
|
155
174
|
}
|
package/dist/public/styles.css
CHANGED
|
@@ -3092,6 +3092,9 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
3092
3092
|
.writing-suggestion-ui { display: contents; }
|
|
3093
3093
|
.guard-card.warning { border-left-color: #c07b31; }.guard-card.failed { border-left-color: var(--accent); }
|
|
3094
3094
|
.guard-card strong { display: block; margin-bottom: 5px; font-size: 10px; }.guard-card p { margin: 4px 0; color: var(--muted); font-size: 9px; line-height: 1.45; }.guard-card p b { color: var(--ink); }
|
|
3095
|
+
.guard-failure-details { margin-top: 6px; }
|
|
3096
|
+
.guard-failure-details summary { width: max-content; max-width: 100%; color: var(--accent-dark); font-size: 9px; line-height: 1.45; cursor: pointer; }
|
|
3097
|
+
.guard-failure-details p { margin-top: 6px; padding: 7px 8px; border: 1px solid var(--line); border-radius: 3px; background: var(--surface); white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
3095
3098
|
.message-actions { display: flex; gap: 7px; margin-top: 10px; }
|
|
3096
3099
|
.message-actions button { border: 1px solid rgba(139,61,44,.25); background: var(--surface); color: var(--accent-dark); font-size: 10px; padding: 5px 8px; border-radius: 3px; }
|
|
3097
3100
|
.prompt-box { border-top: 1px solid var(--line); padding-top: 12px; }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function countProseWords(text: unknown): number;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export function countProseWords(text) {
|
|
2
|
+
const value = String(text ?? "");
|
|
3
|
+
const chinese = value.match(/[\p{Script=Han}]/gu)?.length ?? 0;
|
|
4
|
+
const withoutChinese = value.replace(/[\p{Script=Han}]/gu, " ");
|
|
5
|
+
const latin = withoutChinese.match(/[\p{L}\p{N}]+/gu)?.length ?? 0;
|
|
6
|
+
return chinese + latin;
|
|
7
|
+
}
|
package/dist/version.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const APP_VERSION = "1.0.
|
|
1
|
+
export const APP_VERSION = "1.0.3";
|
|
2
2
|
export const SCRIVERSE_BETA_COMMIT_ENV = "SCRIVERSE_BETA_COMMIT";
|
|
3
3
|
export function resolveBetaVersionLabel(environment) {
|
|
4
4
|
const commit = environment[SCRIVERSE_BETA_COMMIT_ENV]?.trim().toLocaleLowerCase() ?? "";
|