@musnows/scriverse 0.7.6 → 0.7.8
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 +48 -8
- package/dist/ai.js.map +1 -1
- package/dist/app.js +22 -4
- package/dist/app.js.map +1 -1
- package/dist/attachment-storage.js +4 -0
- package/dist/attachment-storage.js.map +1 -1
- package/dist/public/ai-message-meta.js +11 -2
- package/dist/public/app.js +110 -53
- package/dist/public/index.html +5 -5
- package/dist/public/styles.css +14 -9
- package/dist/store.js +24 -0
- package/dist/store.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -8,6 +8,7 @@ import { DEFAULT_ATTACHMENT_IMAGE_MAX_BYTES, formatUploadLimit } from "./upload-
|
|
|
8
8
|
const maximumPixels = 25_000_000;
|
|
9
9
|
const maximumAnimationFrames = 100;
|
|
10
10
|
const maximumAnimationPixels = 50_000_000;
|
|
11
|
+
const maximumGifFrames = 10_000;
|
|
11
12
|
const allowedFormats = new Set(["png", "jpeg", "webp", "gif"]);
|
|
12
13
|
function mimeType(format) {
|
|
13
14
|
if (format === "png")
|
|
@@ -120,6 +121,9 @@ export class AttachmentStorage {
|
|
|
120
121
|
if (!Number.isInteger(width) || width <= 0 || !Number.isInteger(pageHeight) || pageHeight <= 0) {
|
|
121
122
|
throw new AppError(415, "INVALID_ATTACHMENT_IMAGE", "无法读取附件图片尺寸");
|
|
122
123
|
}
|
|
124
|
+
if (isGif && pageCount > maximumGifFrames) {
|
|
125
|
+
throw new AppError(413, "ATTACHMENT_GIF_TOO_MANY_FRAMES", "GIF 动画不能超过 10,000 帧");
|
|
126
|
+
}
|
|
123
127
|
if (!isGif && width * pageHeight > maximumPixels)
|
|
124
128
|
throw new AppError(413, "ATTACHMENT_IMAGE_TOO_LARGE", "附件图片像素尺寸过大");
|
|
125
129
|
if (!isGif && (!Number.isInteger(pageCount) || pageCount > maximumAnimationFrames)) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attachment-storage.js","sourceRoot":"","sources":["../src/attachment-storage.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"attachment-storage.js","sourceRoot":"","sources":["../src/attachment-storage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,SAAS,IAAI,WAAW,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AACxD,OAAO,KAAwB,MAAM,OAAO,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,kCAAkC,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE3F,MAAM,aAAa,GAAG,UAAU,CAAC;AACjC,MAAM,sBAAsB,GAAG,GAAG,CAAC;AACnC,MAAM,sBAAsB,GAAG,UAAU,CAAC;AAC1C,MAAM,gBAAgB,GAAG,MAAM,CAAC;AAChC,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC;AAkB/D,SAAS,QAAQ,CAAC,MAAc;IAC9B,IAAI,MAAM,KAAK,KAAK;QAAE,OAAO,WAAW,CAAC;IACzC,IAAI,MAAM,KAAK,MAAM;QAAE,OAAO,YAAY,CAAC;IAC3C,IAAI,MAAM,KAAK,MAAM;QAAE,OAAO,YAAY,CAAC;IAC3C,IAAI,MAAM,KAAK,KAAK;QAAE,OAAO,WAAW,CAAC;IACzC,MAAM,IAAI,QAAQ,CAAC,GAAG,EAAE,wBAAwB,EAAE,8BAA8B,CAAC,CAAC;AACpF,CAAC;AAED,SAAS,gBAAgB,CAAC,KAA0B;IAClD,IAAI,KAAK,KAAK,YAAY;QAAE,OAAO,KAAK,CAAC;IACzC,OAAO,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AACtC,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,IAAY;IACpC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACrC,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IAClC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,CAAC,WAAW,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;QAC/C,OAAO,IAAI,EAAE,CAAC;YACZ,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;YACxE,IAAI,SAAS,KAAK,CAAC;gBAAE,MAAM;YAC3B,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC;QAC7C,CAAC;IACH,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;IACD,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC5B,CAAC;AAED,KAAK,UAAU,eAAe,CAAC,IAAY;IACzC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACrC,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC/B,IAAI,CAAC;QACH,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QACrE,IAAI,SAAS,KAAK,MAAM,CAAC,MAAM;YAAE,OAAO,KAAK,CAAC;QAC9C,MAAM,SAAS,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAC3C,OAAO,SAAS,KAAK,QAAQ,IAAI,SAAS,KAAK,QAAQ,CAAC;IAC1D,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;AACH,CAAC;AAED,MAAM,OAAO,iBAAiB;IAIjB;IACA;IAJF,kBAAkB,CAAS;IAEpC,YACW,aAAqB,EACrB,qBAAqB,kCAAkC;QADvD,kBAAa,GAAb,aAAa,CAAQ;QACrB,uBAAkB,GAAlB,kBAAkB,CAAqC;QAEhE,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;IACxD,CAAC;IAED,KAAK,CAAC,OAAO;QACX,MAAM,KAAK,CAAC,IAAI,CAAC,kBAAkB,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACzE,CAAC;IAEO,mBAAmB,CAAC,UAAkB;QAC5C,IAAI,CAAC,sDAAsD,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;YAC7E,MAAM,IAAI,QAAQ,CAAC,GAAG,EAAE,yBAAyB,EAAE,UAAU,CAAC,CAAC;QACjE,CAAC;QACD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,aAAa,EAAE,UAAU,CAAC,CAAC;QACrD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QACzC,IAAI,IAAI,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,IAAI,GAAG,GAAG,EAAE,CAAC,EAAE,CAAC;YACvD,MAAM,IAAI,QAAQ,CAAC,GAAG,EAAE,yBAAyB,EAAE,UAAU,CAAC,CAAC;QACjE,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,CAAC,UAAkB;QACrB,OAAO,IAAI,CAAC,mBAAmB,CAAC,UAAU,CAAC,CAAC;IAC9C,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,UAAkB;QAC3B,IAAI,CAAC;YACH,OAAO,MAAM,QAAQ,CAAC,IAAI,CAAC,mBAAmB,CAAC,UAAU,CAAC,CAAC,CAAC;QAC9D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACvD,MAAM,IAAI,QAAQ,CAAC,GAAG,EAAE,2BAA2B,EAAE,SAAS,CAAC,CAAC;YAClE,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,UAAkB;QAC7B,MAAM,EAAE,CAAC,IAAI,CAAC,mBAAmB,CAAC,UAAU,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAClE,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,UAAkB;QAC7B,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QACrB,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,CAAC;QAC7C,IAAI,aAAa,CAAC,IAAI,GAAG,IAAI,CAAC,kBAAkB,EAAE,CAAC;YACjD,MAAM,IAAI,QAAQ,CAAC,GAAG,EAAE,sBAAsB,EAAE,YAAY,iBAAiB,CAAC,IAAI,CAAC,kBAAkB,CAAC,EAAE,CAAC,CAAC;QAC5G,CAAC;QACD,MAAM,cAAc,GAAG,MAAM,UAAU,CAAC,UAAU,CAAC,CAAC;QACpD,MAAM,KAAK,GAAG,MAAM,eAAe,CAAC,UAAU,CAAC,CAAC;QAChD,IAAI,QAAkB,CAAC;QACvB,IAAI,CAAC;YACH,QAAQ,GAAG,MAAM,KAAK,CAAC,UAAU,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,gBAAgB,EAAE,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,aAAa,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC3I,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,QAAQ,CAAC,GAAG,EAAE,0BAA0B,EAAE,aAAa,CAAC,CAAC;QACrE,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;QAC7C,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC;YAAE,MAAM,IAAI,QAAQ,CAAC,GAAG,EAAE,wBAAwB,EAAE,8BAA8B,CAAC,CAAC;QACnH,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC;QAC1C,MAAM,UAAU,GAAG,MAAM,CAAC,QAAQ,CAAC,UAAU,IAAI,QAAQ,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;QACvE,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC;QAC3D,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,IAAI,UAAU,IAAI,CAAC,EAAE,CAAC;YAC/F,MAAM,IAAI,QAAQ,CAAC,GAAG,EAAE,0BAA0B,EAAE,YAAY,CAAC,CAAC;QACpE,CAAC;QACD,IAAI,KAAK,IAAI,SAAS,GAAG,gBAAgB,EAAE,CAAC;YAC1C,MAAM,IAAI,QAAQ,CAAC,GAAG,EAAE,gCAAgC,EAAE,qBAAqB,CAAC,CAAC;QACnF,CAAC;QACD,IAAI,CAAC,KAAK,IAAI,KAAK,GAAG,UAAU,GAAG,aAAa;YAAE,MAAM,IAAI,QAAQ,CAAC,GAAG,EAAE,4BAA4B,EAAE,YAAY,CAAC,CAAC;QACtH,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,SAAS,GAAG,sBAAsB,CAAC,EAAE,CAAC;YACnF,MAAM,IAAI,QAAQ,CAAC,GAAG,EAAE,gCAAgC,EAAE,UAAU,CAAC,CAAC;QACxE,CAAC;QACD,IAAI,CAAC,KAAK,IAAI,KAAK,GAAG,UAAU,GAAG,SAAS,GAAG,sBAAsB,EAAE,CAAC;YACtE,MAAM,IAAI,QAAQ,CAAC,GAAG,EAAE,gCAAgC,EAAE,YAAY,CAAC,CAAC;QAC1E,CAAC;QAED,MAAM,gBAAgB,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC1C,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,GAAG,cAAc,IAAI,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QAC5F,IAAI,YAAY,GAAG,UAAU,CAAC;QAC9B,IAAI,cAAc,GAAG,gBAAgB,CAAC;QACtC,IAAI,MAAM,KAAK,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;YAChC,IAAI,CAAC;gBACH,MAAM,KAAK,CAAC,UAAU,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,gBAAgB,EAAE,aAAa,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC;qBAC/F,IAAI,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;qBACnC,MAAM,CAAC,aAAa,CAAC,CAAC;gBACzB,MAAM,SAAS,GAAG,MAAM,KAAK,CAAC,aAAa,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,gBAAgB,EAAE,aAAa,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC;gBAC7G,MAAM,cAAc,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,SAAS,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC;gBACjE,IAAI,SAAS,GAAG,CAAC,IAAI,cAAc,KAAK,SAAS,EAAE,CAAC;oBAClD,MAAM,IAAI,QAAQ,CAAC,GAAG,EAAE,2BAA2B,EAAE,eAAe,CAAC,CAAC;gBACxE,CAAC;gBACD,IAAI,CAAC,MAAM,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,GAAG,aAAa,CAAC,IAAI,EAAE,CAAC;oBAC1D,YAAY,GAAG,aAAa,CAAC;oBAC7B,cAAc,GAAG,YAAY,CAAC;gBAChC,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,MAAM,EAAE,CAAC,aAAa,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YAC3C,CAAC;QACH,CAAC;QAED,IAAI,CAAC;YACH,MAAM,YAAY,GAAG,YAAY,KAAK,UAAU,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,MAAM,UAAU,CAAC,YAAY,CAAC,CAAC;YACnG,MAAM,gBAAgB,GAAG,CAAC,MAAM,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC;YACzD,MAAM,UAAU,GAAG,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,YAAY,IAAI,gBAAgB,CAAC,cAAc,CAAC,EAAE,CAAC;YACrG,MAAM,UAAU,GAAG,IAAI,CAAC,mBAAmB,CAAC,UAAU,CAAC,CAAC;YACxD,MAAM,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;YACnE,IAAI,CAAC;gBACH,MAAM,QAAQ,CAAC,YAAY,EAAE,UAAU,EAAE,WAAW,CAAC,aAAa,CAAC,CAAC;YACtE,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;oBAAE,MAAM,KAAK,CAAC;YACtE,CAAC;YACD,OAAO;gBACL,gBAAgB;gBAChB,cAAc;gBACd,kBAAkB,EAAE,aAAa,CAAC,IAAI;gBACtC,gBAAgB;gBAChB,cAAc;gBACd,YAAY;gBACZ,UAAU;gBACV,KAAK;gBACL,MAAM,EAAE,UAAU;gBAClB,SAAS;gBACT,QAAQ,EAAE,SAAS,GAAG,CAAC;aACxB,CAAC;QACJ,CAAC;gBAAS,CAAC;YACT,MAAM,EAAE,CAAC,aAAa,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3C,CAAC;IACH,CAAC;CACF"}
|
|
@@ -1,12 +1,21 @@
|
|
|
1
|
-
export function
|
|
1
|
+
export function resolveAiTokensPerSecond(outputTokens, durationMs) {
|
|
2
|
+
const tokenCount = Number(outputTokens);
|
|
3
|
+
const duration = Number(durationMs);
|
|
4
|
+
if (!Number.isFinite(tokenCount) || tokenCount < 0 || !Number.isFinite(duration) || duration <= 0) return null;
|
|
5
|
+
return Math.max(0, Math.round(tokenCount / (duration / 1000)));
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function formatAiMessageMeta(modelDisplayName, outputTokens, cacheHitPercent, suffix = "", durationMs) {
|
|
2
9
|
const modelName = String(modelDisplayName || "模型").trim();
|
|
3
10
|
const tokenCount = Math.max(0, Math.round(Number(outputTokens) || 0)).toLocaleString("zh-CN");
|
|
11
|
+
const tokensPerSecond = resolveAiTokensPerSecond(outputTokens, durationMs);
|
|
4
12
|
const cachePercent = Number(cacheHitPercent);
|
|
5
13
|
const roundedCachePercent = Math.round((cachePercent + Number.EPSILON) * 10) / 10;
|
|
6
14
|
const cacheLabel = Number.isFinite(cachePercent)
|
|
7
15
|
? `缓存命中 ${Math.max(0, Math.min(100, roundedCachePercent)).toLocaleString("zh-CN")}%`
|
|
8
16
|
: "";
|
|
9
|
-
|
|
17
|
+
const speedLabel = tokensPerSecond === null ? "" : `${tokensPerSecond.toLocaleString("zh-CN")}\u00a0tok/s`;
|
|
18
|
+
return [modelName, `${tokenCount} tok`, speedLabel, cacheLabel, String(suffix || "").trim()].filter(Boolean).join(" · ");
|
|
10
19
|
}
|
|
11
20
|
|
|
12
21
|
export function estimateAiMessageTokens(value) {
|
package/dist/public/app.js
CHANGED
|
@@ -18,7 +18,7 @@ import { buildVditorLineNumberRows } from "/vditor-line-number-layout.js?v=20260
|
|
|
18
18
|
import { MIN_MODEL_CONTEXT_WINDOW, MODEL_PURPOSE_OPTIONS, isKimiModelId, modelContextWindowGuidance, modelFormValues, modelOptionLabel, modelPayload, supportsMultimodalModelProtocol } from "/model-config.js?v=20260803-multimodal-model-config-v2";
|
|
19
19
|
import { connectivityConfigurationSavedToast, connectivityTestErrorToast, connectivityTestResultToast } from "/ai-connectivity-test.js?v=20260812-connectivity-cooldown-v1";
|
|
20
20
|
import { shouldSendAiPrompt } from "/ai-prompt-keyboard.js?v=20260713-enter-to-send";
|
|
21
|
-
import { estimateAiMessageTokens, formatAiMessageMeta } from "/ai-message-meta.js?v=
|
|
21
|
+
import { estimateAiMessageTokens, formatAiMessageMeta } from "/ai-message-meta.js?v=20260814-ai-model-lock-v1";
|
|
22
22
|
import { createStreamTypewriter } from "/stream-typewriter.js?v=20260730-ai-stream-typewriter-v3";
|
|
23
23
|
import { assertAiStreamCompleted, readAiEventStream } from "/ai-stream-protocol.js?v=20260812-ai-stream-complete-v1";
|
|
24
24
|
import { buildUsageCalendar, formatCacheHitRate, formatTokenCount } from "/ai-usage.js?v=20260727-ai-usage-v1";
|
|
@@ -156,6 +156,7 @@ const state = {
|
|
|
156
156
|
aiTaskType: "chat",
|
|
157
157
|
aiContextScope: { type: "none" },
|
|
158
158
|
aiConversationId: null,
|
|
159
|
+
aiConversationModelId: null,
|
|
159
160
|
aiConversations: [],
|
|
160
161
|
aiRoleplayCharacter: null,
|
|
161
162
|
aiLastMessageAt: null,
|
|
@@ -2324,6 +2325,7 @@ async function openAiConversation(conversationId, hideHistory = true, focusMessa
|
|
|
2324
2325
|
if (String(conversation.workId ?? "") !== navigation.workId) throw new Error("AI 对话不属于当前作品");
|
|
2325
2326
|
upsertAiConversationSummary(conversation);
|
|
2326
2327
|
state.aiConversationId = conversation.id;
|
|
2328
|
+
state.aiConversationModelId = typeof conversation.modelId === "string" ? conversation.modelId : null;
|
|
2327
2329
|
state.aiPromptSent = conversation.messages.some((message) => message.role === "user");
|
|
2328
2330
|
applyAiConversationTaskType(conversation.taskType);
|
|
2329
2331
|
applyAiConversationContextScope(conversation.contextScope);
|
|
@@ -2366,6 +2368,7 @@ function focusAiConversationMessage(messageId) {
|
|
|
2366
2368
|
function applyNewAiConversation(conversation) {
|
|
2367
2369
|
upsertAiConversationSummary(conversation);
|
|
2368
2370
|
state.aiConversationId = conversation.id;
|
|
2371
|
+
state.aiConversationModelId = null;
|
|
2369
2372
|
state.aiPromptSent = false;
|
|
2370
2373
|
applyAiConversationTaskType(conversation.taskType);
|
|
2371
2374
|
applyAiConversationContextScope(conversation.contextScope);
|
|
@@ -2401,7 +2404,7 @@ function renderAiRoleplayCharacterSelect() {
|
|
|
2401
2404
|
const availableCharacters = state.characters.filter((character) => !character.mergedIntoCharacterId);
|
|
2402
2405
|
const options = [{ id: "", name: "选择角色卡" }, ...availableCharacters.map((character) => ({
|
|
2403
2406
|
id: String(character.id),
|
|
2404
|
-
name: String(character.name)
|
|
2407
|
+
name: `${String(character.name)}${character.isDead ? "(已死亡)" : ""}`
|
|
2405
2408
|
}))];
|
|
2406
2409
|
if (selectedId && !options.some((option) => option.id === selectedId)) {
|
|
2407
2410
|
options.push({ id: selectedId, name: String(state.aiRoleplayCharacter.name) });
|
|
@@ -2435,6 +2438,8 @@ function syncAiTaskOptions() {
|
|
|
2435
2438
|
$("#ai-scope").title = state.aiPromptSent
|
|
2436
2439
|
? "对话开始后不能切换上下文引用"
|
|
2437
2440
|
: roleplaySelected ? "角色扮演模式只使用角色自身的记忆" : "";
|
|
2441
|
+
$("#ai-model").disabled = interactionBusy || state.aiPromptSent;
|
|
2442
|
+
$("#ai-model").title = state.aiPromptSent ? "对话开始后不能切换模型" : "";
|
|
2438
2443
|
}
|
|
2439
2444
|
|
|
2440
2445
|
function applyAiConversationTaskType(taskType) {
|
|
@@ -3740,6 +3745,19 @@ function raiseToastRegion() {
|
|
|
3740
3745
|
region.showPopover();
|
|
3741
3746
|
}
|
|
3742
3747
|
|
|
3748
|
+
function dismissDeleteToasts() {
|
|
3749
|
+
const region = $("#toast-region");
|
|
3750
|
+
region.querySelectorAll(".delete-toast").forEach((element) => element.remove());
|
|
3751
|
+
if (!region.childElementCount && typeof region.hidePopover === "function" && region.matches(":popover-open")) {
|
|
3752
|
+
region.hidePopover();
|
|
3753
|
+
}
|
|
3754
|
+
}
|
|
3755
|
+
|
|
3756
|
+
function deleteToast(message) {
|
|
3757
|
+
toast(message);
|
|
3758
|
+
$("#toast-region").lastElementChild?.classList.add("delete-toast");
|
|
3759
|
+
}
|
|
3760
|
+
|
|
3743
3761
|
function dismissChapterInsightToast() {
|
|
3744
3762
|
chapterInsightRequestId += 1;
|
|
3745
3763
|
const region = $("#toast-region");
|
|
@@ -4263,6 +4281,7 @@ async function initializePage() {
|
|
|
4263
4281
|
function showShelf() {
|
|
4264
4282
|
stopBackgroundTaskCenter();
|
|
4265
4283
|
dismissChapterInsightToast();
|
|
4284
|
+
dismissDeleteToasts();
|
|
4266
4285
|
state.dirty = false;
|
|
4267
4286
|
settingsReturnContext = null;
|
|
4268
4287
|
updateDocumentTitle();
|
|
@@ -4563,6 +4582,7 @@ async function showWorkAudit() {
|
|
|
4563
4582
|
workAuditRecords = [];
|
|
4564
4583
|
workAuditNextPage = null;
|
|
4565
4584
|
dismissChapterInsightToast();
|
|
4585
|
+
dismissDeleteToasts();
|
|
4566
4586
|
updateDocumentTitle(state.work);
|
|
4567
4587
|
$("#app").classList.add("shelf-mode");
|
|
4568
4588
|
$("#shelf-view").classList.add("hidden");
|
|
@@ -4740,7 +4760,7 @@ function renderS3BackupTargets() {
|
|
|
4740
4760
|
try {
|
|
4741
4761
|
await api(`/api/platform/backups/targets/${encodeURIComponent(target.id)}`, { method: "DELETE" });
|
|
4742
4762
|
await loadS3BackupData();
|
|
4743
|
-
|
|
4763
|
+
deleteToast(`备份目标“${target.name}”已删除`);
|
|
4744
4764
|
} catch (error) {
|
|
4745
4765
|
button.disabled = false;
|
|
4746
4766
|
toast(error.message, "error");
|
|
@@ -5108,7 +5128,7 @@ function renderMembers(members) {
|
|
|
5108
5128
|
const updated = await api(`/api/works/${encodeURIComponent(work.id)}/members/${encodeURIComponent(button.dataset.removeMember)}`, { method: "DELETE" });
|
|
5109
5129
|
renderMembers(updated);
|
|
5110
5130
|
renderMemberSelector();
|
|
5111
|
-
|
|
5131
|
+
deleteToast("协作者已移除");
|
|
5112
5132
|
} catch (error) { toast(error.message, "error"); }
|
|
5113
5133
|
}));
|
|
5114
5134
|
}
|
|
@@ -5485,6 +5505,7 @@ async function showSettingsHub() {
|
|
|
5485
5505
|
state.dirty = false;
|
|
5486
5506
|
}
|
|
5487
5507
|
dismissChapterInsightToast();
|
|
5508
|
+
dismissDeleteToasts();
|
|
5488
5509
|
updateDocumentTitle(state.work);
|
|
5489
5510
|
$("#app").classList.add("shelf-mode");
|
|
5490
5511
|
$("#shelf-view").classList.add("hidden");
|
|
@@ -5532,6 +5553,7 @@ async function showPlatformAi() {
|
|
|
5532
5553
|
if (state.dirty && !(await confirmDiscardChanges("当前章节有未保存修改,进入平台 AI 管理将放弃本地修改。是否继续?"))) return false;
|
|
5533
5554
|
state.dirty = false;
|
|
5534
5555
|
dismissChapterInsightToast();
|
|
5556
|
+
dismissDeleteToasts();
|
|
5535
5557
|
updateDocumentTitle();
|
|
5536
5558
|
$("#app").classList.add("shelf-mode");
|
|
5537
5559
|
$("#shelf-view").classList.add("hidden");
|
|
@@ -5553,6 +5575,8 @@ async function showPlatformAi() {
|
|
|
5553
5575
|
async function showPlatformUsage() {
|
|
5554
5576
|
if (state.dirty && !(await confirmDiscardChanges("当前章节有未保存修改,进入 Token 用量面板将放弃本地修改。是否继续?"))) return false;
|
|
5555
5577
|
state.dirty = false;
|
|
5578
|
+
dismissChapterInsightToast();
|
|
5579
|
+
dismissDeleteToasts();
|
|
5556
5580
|
updateDocumentTitle();
|
|
5557
5581
|
$("#app").classList.add("shelf-mode");
|
|
5558
5582
|
$("#shelf-view").classList.add("hidden");
|
|
@@ -5648,6 +5672,7 @@ function resetWorkScopedUiCaches() {
|
|
|
5648
5672
|
state.aiReferences = [];
|
|
5649
5673
|
state.aiPromptSent = false;
|
|
5650
5674
|
state.aiConversationId = null;
|
|
5675
|
+
state.aiConversationModelId = null;
|
|
5651
5676
|
state.aiConversations = [];
|
|
5652
5677
|
state.aiRoleplayCharacter = null;
|
|
5653
5678
|
renderAiCitations();
|
|
@@ -6107,7 +6132,7 @@ async function deleteChapter(chapterId) {
|
|
|
6107
6132
|
} else {
|
|
6108
6133
|
renderTree();
|
|
6109
6134
|
}
|
|
6110
|
-
|
|
6135
|
+
deleteToast(`已删除章节“${chapter.title}”,正文和版本记录已保留`);
|
|
6111
6136
|
} catch (error) {
|
|
6112
6137
|
resumeAutoSave();
|
|
6113
6138
|
toast(error.message, "error");
|
|
@@ -6316,6 +6341,7 @@ async function selectChapter(chapterId, { editMode = false } = {}) {
|
|
|
6316
6341
|
if (selectionRequestId === chapterSelectionRequestId) void loadChapterForeshadowReminders();
|
|
6317
6342
|
return false;
|
|
6318
6343
|
}
|
|
6344
|
+
dismissDeleteToasts();
|
|
6319
6345
|
if (selectionGeneration !== chapterSelectionRequestGeneration || selectionRequestId !== chapterSelectionRequestId || state.work?.id !== workId) return false;
|
|
6320
6346
|
cancelChapterAutoSave();
|
|
6321
6347
|
let selectedChapter = state.chapter;
|
|
@@ -6865,6 +6891,7 @@ function showWelcome(hasWork = false) {
|
|
|
6865
6891
|
chapterSelectionRequestId += 1;
|
|
6866
6892
|
clearChapterForeshadowReminders({ invalidateRequest: true });
|
|
6867
6893
|
dismissChapterInsightToast();
|
|
6894
|
+
dismissDeleteToasts();
|
|
6868
6895
|
$("#editor-view").classList.add("hidden");
|
|
6869
6896
|
$("#module-view").classList.add("hidden");
|
|
6870
6897
|
$("#welcome-view").classList.remove("hidden");
|
|
@@ -6909,6 +6936,7 @@ async function showModule(module) {
|
|
|
6909
6936
|
}
|
|
6910
6937
|
if (module !== "editor" && state.module === "editor" && !(await confirmDiscardChanges())) return;
|
|
6911
6938
|
if (module !== "editor" && state.module === "editor" && state.dirty) setSaveState("已放弃修改");
|
|
6939
|
+
dismissDeleteToasts();
|
|
6912
6940
|
state.module = module;
|
|
6913
6941
|
if (module !== "editor") {
|
|
6914
6942
|
chapterSelectionRequestId += 1;
|
|
@@ -7107,7 +7135,7 @@ async function deleteManagedEntity({ typeLabel, item, endpoint, refresh, warning
|
|
|
7107
7135
|
await onDeleted?.();
|
|
7108
7136
|
await refresh();
|
|
7109
7137
|
await loadAiReferences();
|
|
7110
|
-
|
|
7138
|
+
deleteToast(`已删除${typeLabel}“${item.name}”`);
|
|
7111
7139
|
} catch (error) {
|
|
7112
7140
|
toast(error.message, "error");
|
|
7113
7141
|
}
|
|
@@ -7409,28 +7437,17 @@ async function deleteDraft(item) {
|
|
|
7409
7437
|
if (!await confirmToast(`确认删除想法“${item.title}”吗?想法将从当前列表移除。`, {
|
|
7410
7438
|
title: "删除想法",
|
|
7411
7439
|
confirmLabel: "继续删除"
|
|
7412
|
-
}))
|
|
7413
|
-
openDraftDialog(item);
|
|
7414
|
-
return;
|
|
7415
|
-
}
|
|
7440
|
+
})) return;
|
|
7416
7441
|
if (!await confirmToast(`删除想法“${item.title}”后将从想法列表移除,版本历史仍会保留。仍要删除吗?`, {
|
|
7417
7442
|
title: "删除操作需要再次确认",
|
|
7418
7443
|
confirmLabel: "确认删除"
|
|
7419
|
-
}))
|
|
7420
|
-
openDraftDialog(item);
|
|
7421
|
-
return;
|
|
7422
|
-
}
|
|
7444
|
+
})) return;
|
|
7423
7445
|
try {
|
|
7424
7446
|
await api(`/api/drafts/${encodeURIComponent(item.id)}`, { method: "DELETE", body: { expectedVersionNo: item.versionNo } });
|
|
7425
7447
|
await renderDrafts(moduleListPages.drafts);
|
|
7426
|
-
|
|
7448
|
+
deleteToast("想法已删除");
|
|
7427
7449
|
} catch (error) {
|
|
7428
7450
|
toast(error.message, "error");
|
|
7429
|
-
try {
|
|
7430
|
-
openDraftDialog(await api(`/api/drafts/${encodeURIComponent(item.id)}`));
|
|
7431
|
-
} catch (reloadError) {
|
|
7432
|
-
toast(reloadError.message, "error");
|
|
7433
|
-
}
|
|
7434
7451
|
}
|
|
7435
7452
|
}
|
|
7436
7453
|
|
|
@@ -7438,19 +7455,14 @@ function openDraftDialog(item = null, { readOnly = false } = {}) {
|
|
|
7438
7455
|
const viewOnly = readOnly || !canEditModule("drafts");
|
|
7439
7456
|
const volumeOptions = [["", "全局(不绑定分卷)"], ...(state.work?.volumes ?? []).map((volume) => [volume.id, volume.title])];
|
|
7440
7457
|
const settingModuleOptions = [["", "全局(不绑定设定模块)"], ...draftSettingModules];
|
|
7441
|
-
const management = item && !viewOnly ? `<section class="entity-dialog-management" aria-label="想法操作">
|
|
7442
|
-
<div><strong>想法操作</strong><small>删除后将从想法列表移除,版本历史仍会保留。</small></div>
|
|
7443
|
-
<div class="entity-dialog-management-actions"><button class="danger-button" type="button" data-dialog-draft-delete>删除想法</button></div>
|
|
7444
|
-
</section>` : "";
|
|
7445
7458
|
const fields = field("draftType", "想法类型", "select", item?.draftType ?? "prose", [["prose", "正文想法"], ["setting", "设定想法"]])
|
|
7446
7459
|
+ `<div class="draft-binding-field" data-draft-binding-field="prose">${field("volumeId", "绑定分卷", "select", item?.volumeId ?? "", volumeOptions)}</div>`
|
|
7447
7460
|
+ `<div class="draft-binding-field" data-draft-binding-field="setting">${field("settingModule", "绑定设定模块", "select", item?.settingModule ?? "", settingModuleOptions)}</div>`
|
|
7448
|
-
+ field("title", "标题", "text", item?.title ?? "")
|
|
7449
7461
|
+ field("content", "内容", "markdown", item?.content ?? "", {
|
|
7450
7462
|
placeholder: "记录尚未定稿的片段、方向或设定想法……",
|
|
7451
7463
|
attachmentModule: "drafts",
|
|
7452
7464
|
readOnly: viewOnly
|
|
7453
|
-
})
|
|
7465
|
+
});
|
|
7454
7466
|
openDialog(item ? viewOnly ? "查看想法" : "编辑想法" : "新建想法", fields, async (form) => {
|
|
7455
7467
|
if (viewOnly) return;
|
|
7456
7468
|
const title = String(form.get("title") ?? "").trim();
|
|
@@ -7475,7 +7487,9 @@ function openDraftDialog(item = null, { readOnly = false } = {}) {
|
|
|
7475
7487
|
hideCancel: viewOnly,
|
|
7476
7488
|
editor: true,
|
|
7477
7489
|
errorPrefix: "想法保存失败:",
|
|
7478
|
-
meta: "这里记录未确认的临时想法,可能采用,也可能永远不会写入正文或正式设定。"
|
|
7490
|
+
meta: "这里记录未确认的临时想法,可能采用,也可能永远不会写入正文或正式设定。",
|
|
7491
|
+
titleInput: { value: item?.title ?? "", readOnly: viewOnly, placeholder: "输入想法标题", label: "想法标题" },
|
|
7492
|
+
dangerAction: item && !viewOnly ? { label: "删除想法", onClick: () => deleteDraft(item) } : null
|
|
7479
7493
|
});
|
|
7480
7494
|
const draftTypeSelect = $("#dialog-fields").querySelector('select[name="draftType"]');
|
|
7481
7495
|
const syncDraftBindingFields = () => {
|
|
@@ -7495,9 +7509,6 @@ function openDraftDialog(item = null, { readOnly = false } = {}) {
|
|
|
7495
7509
|
else control.readOnly = true;
|
|
7496
7510
|
});
|
|
7497
7511
|
}
|
|
7498
|
-
$("#dialog-fields").querySelector("[data-dialog-draft-delete]")?.addEventListener("click", () => {
|
|
7499
|
-
void deleteDraft(item);
|
|
7500
|
-
});
|
|
7501
7512
|
}
|
|
7502
7513
|
|
|
7503
7514
|
async function renderDrafts(page = moduleListPages.drafts) {
|
|
@@ -7979,7 +7990,7 @@ async function deleteTimelineEvent(item, page = moduleListPages.timeline) {
|
|
|
7979
7990
|
try {
|
|
7980
7991
|
await api(`/api/timeline/${encodeURIComponent(item.id)}`, { method: "DELETE", body: { expectedVersionNo: item.versionNo } });
|
|
7981
7992
|
await renderTimeline(page);
|
|
7982
|
-
|
|
7993
|
+
deleteToast(`已删除时间事件“${item.name}”`);
|
|
7983
7994
|
} catch (error) {
|
|
7984
7995
|
toast(error.message, "error");
|
|
7985
7996
|
}
|
|
@@ -8039,6 +8050,7 @@ async function renderTimeline(page = moduleListPages.timeline) {
|
|
|
8039
8050
|
$("#module-content").querySelectorAll("[data-timeline-track-tab]").forEach((button) => button.addEventListener("click", async () => {
|
|
8040
8051
|
const nextTrackId = button.dataset.timelineTrackTab ?? "";
|
|
8041
8052
|
if (String(nextTrackId) === String(timelineActiveTrackId)) return;
|
|
8053
|
+
dismissDeleteToasts();
|
|
8042
8054
|
timelineActiveTrackId = nextTrackId;
|
|
8043
8055
|
await renderTimeline(1);
|
|
8044
8056
|
}));
|
|
@@ -10462,7 +10474,11 @@ function applyAiModels(models) {
|
|
|
10462
10474
|
select.innerHTML = state.models.length
|
|
10463
10475
|
? state.models.map((model) => `<option value="${esc(model.id)}">${esc(modelOptionLabel(model))}</option>`).join("")
|
|
10464
10476
|
: '<option value="">请先配置模型</option>';
|
|
10477
|
+
if (state.aiConversationModelId && state.models.some((model) => model.id === state.aiConversationModelId)) {
|
|
10478
|
+
select.value = state.aiConversationModelId;
|
|
10479
|
+
}
|
|
10465
10480
|
setAiContextMeter(null);
|
|
10481
|
+
syncAiTaskOptions();
|
|
10466
10482
|
}
|
|
10467
10483
|
|
|
10468
10484
|
async function ensureAiModelsLoaded() {
|
|
@@ -10830,10 +10846,18 @@ function openDialog(title, fields, onSubmit, eyebrow = "新增", options = {}) {
|
|
|
10830
10846
|
const submit = $("#dialog-submit");
|
|
10831
10847
|
const submitStatus = $("#dialog-submit-status");
|
|
10832
10848
|
const submitStatusMessage = $("#dialog-submit-status-message");
|
|
10849
|
+
const titleInput = $("#dialog-title-input");
|
|
10833
10850
|
const submitLabel = options.submitLabel ?? "保存";
|
|
10834
10851
|
let submitting = false;
|
|
10835
10852
|
let disabledStates = [];
|
|
10836
10853
|
$("#dialog-title").textContent = title;
|
|
10854
|
+
$("#dialog-title").classList.toggle("hidden", Boolean(options.titleInput));
|
|
10855
|
+
titleInput.classList.toggle("hidden", !options.titleInput);
|
|
10856
|
+
titleInput.name = options.titleInput ? "title" : "";
|
|
10857
|
+
titleInput.value = options.titleInput ? String(options.titleInput.value ?? "") : "";
|
|
10858
|
+
titleInput.placeholder = options.titleInput?.placeholder ?? "";
|
|
10859
|
+
titleInput.readOnly = Boolean(options.titleInput?.readOnly);
|
|
10860
|
+
titleInput.setAttribute("aria-label", options.titleInput?.label ?? "记录标题");
|
|
10837
10861
|
$("#dialog-eyebrow").textContent = eyebrow;
|
|
10838
10862
|
$("#dialog-meta").textContent = options.meta ?? "";
|
|
10839
10863
|
$("#dialog-meta").classList.toggle("hidden", !options.meta);
|
|
@@ -10844,6 +10868,17 @@ function openDialog(title, fields, onSubmit, eyebrow = "新增", options = {}) {
|
|
|
10844
10868
|
form.classList.remove("is-submitting");
|
|
10845
10869
|
form.removeAttribute("aria-busy");
|
|
10846
10870
|
$("#dynamic-form .dialog-actions [value='cancel']").classList.toggle("hidden", Boolean(options.hideCancel));
|
|
10871
|
+
const dialogActions = form.querySelector(".dialog-actions");
|
|
10872
|
+
dialogActions?.querySelector("[data-dialog-danger-action]")?.remove();
|
|
10873
|
+
if (options.dangerAction) {
|
|
10874
|
+
const dangerButton = document.createElement("button");
|
|
10875
|
+
dangerButton.className = "danger-button dialog-danger-action";
|
|
10876
|
+
dangerButton.type = "button";
|
|
10877
|
+
dangerButton.dataset.dialogDangerAction = "";
|
|
10878
|
+
dangerButton.textContent = options.dangerAction.label;
|
|
10879
|
+
dangerButton.addEventListener("click", () => { void options.dangerAction.onClick(); });
|
|
10880
|
+
dialogActions?.insertBefore(dangerButton, dialogActions.querySelector("[value='cancel']"));
|
|
10881
|
+
}
|
|
10847
10882
|
dialog.classList.toggle("wide-dialog", Boolean(options.wide));
|
|
10848
10883
|
dialog.classList.toggle("trace-dialog", Boolean(options.trace));
|
|
10849
10884
|
dialog.classList.toggle("large-dialog", Boolean(options.large));
|
|
@@ -10970,7 +11005,7 @@ function bindWorkCoverControls(work) {
|
|
|
10970
11005
|
Object.assign(work, updated);
|
|
10971
11006
|
refreshWorkCoverField(work);
|
|
10972
11007
|
renderShelf();
|
|
10973
|
-
|
|
11008
|
+
deleteToast("封面已移除");
|
|
10974
11009
|
} catch (error) {
|
|
10975
11010
|
toast(error.message, "error");
|
|
10976
11011
|
}
|
|
@@ -11157,7 +11192,7 @@ async function deleteWork(work) {
|
|
|
11157
11192
|
state.chapter = null;
|
|
11158
11193
|
}
|
|
11159
11194
|
await loadWorks();
|
|
11160
|
-
|
|
11195
|
+
deleteToast(`作品“${work.title}”已移入回收站`);
|
|
11161
11196
|
} catch (error) {
|
|
11162
11197
|
if (deletingCurrentWork && state.dirty) scheduleChapterAutoSave();
|
|
11163
11198
|
toast(error.message, "error");
|
|
@@ -11227,7 +11262,7 @@ async function deleteVolume(item) {
|
|
|
11227
11262
|
state.collapsedVolumeIds.delete(item.id);
|
|
11228
11263
|
state.work = await api(`/api/works/${state.work.id}`);
|
|
11229
11264
|
renderTree();
|
|
11230
|
-
|
|
11265
|
+
deleteToast(`分卷“${item.title}”已移入回收站`);
|
|
11231
11266
|
} catch (error) {
|
|
11232
11267
|
toast(error.message, "error");
|
|
11233
11268
|
openVolumeDialog(item);
|
|
@@ -11920,7 +11955,7 @@ function knowledgeSectionEditorHtml(section = null) {
|
|
|
11920
11955
|
<div><span class="eyebrow">${label} Markdown 设定</span><input id="knowledge-section-title" class="character-section-editor-title-input" maxlength="200" value="${esc(section?.title ?? "")}" placeholder="新建设定" aria-label="设定标题" required></div>
|
|
11921
11956
|
<button class="entity-editor-back" type="button" data-knowledge-section-edit-close>返回设定列表</button>
|
|
11922
11957
|
</header>
|
|
11923
|
-
<section class="character-markdown-editor" aria-label="${section ? "编辑" : "新建"}${label} Markdown 设定">
|
|
11958
|
+
<section class="character-markdown-editor knowledge-markdown-editor" aria-label="${section ? "编辑" : "新建"}${label} Markdown 设定">
|
|
11924
11959
|
<div id="knowledge-section-markdown" class="vditor-editor-host" data-vditor-editor aria-label="Markdown 编辑器"></div>
|
|
11925
11960
|
<div class="character-markdown-editor-footer">
|
|
11926
11961
|
<div class="character-markdown-editor-actions"><button type="button" data-knowledge-section-edit-cancel>取消</button><button type="button" class="primary-button" data-knowledge-section-edit-save>${section ? "保存设定" : "添加设定"}</button></div>
|
|
@@ -12196,7 +12231,7 @@ function renderCharacterMarkdownSections() {
|
|
|
12196
12231
|
characterEditorSections = await api(`/api/characters/${characterEditorItem.id}/sections`);
|
|
12197
12232
|
renderCharacterMarkdownSections();
|
|
12198
12233
|
await Promise.all([renderCharacters(), loadAiReferences()]);
|
|
12199
|
-
|
|
12234
|
+
deleteToast("人物 Markdown 章节已删除");
|
|
12200
12235
|
} catch (error) {
|
|
12201
12236
|
button.disabled = false;
|
|
12202
12237
|
toast(error.message, "error");
|
|
@@ -12904,7 +12939,7 @@ async function openRelationshipDialog(item, options = {}) {
|
|
|
12904
12939
|
try {
|
|
12905
12940
|
await api(`/api/relationships/${item.id}`, { method: "DELETE", body: { expectedVersionNo: item.versionNo } });
|
|
12906
12941
|
await Promise.all([refreshRelationshipSurfaces(options.characterId ?? null), loadAiReferences()]);
|
|
12907
|
-
|
|
12942
|
+
deleteToast(`已删除人物关系“${relationshipName}”`);
|
|
12908
12943
|
} catch (error) {
|
|
12909
12944
|
reopenDialog();
|
|
12910
12945
|
toast(error.message, "error");
|
|
@@ -13429,12 +13464,13 @@ async function sendAiWithOptions({ ignoreContextWarning = false } = {}) {
|
|
|
13429
13464
|
"user",
|
|
13430
13465
|
instruction,
|
|
13431
13466
|
citations,
|
|
13432
|
-
{},
|
|
13467
|
+
{ modelId },
|
|
13433
13468
|
{ signal: request.signal }
|
|
13434
13469
|
);
|
|
13435
13470
|
assertAiRequestCurrent(request);
|
|
13436
13471
|
updateAiConversationSummaryFromMessage(persistedUserMessage);
|
|
13437
13472
|
requestHolder.snapshot = aiRequestManager.bind(request, { userMessageId: persistedUserMessage.id });
|
|
13473
|
+
state.aiConversationModelId = modelId;
|
|
13438
13474
|
state.aiPromptSent = true;
|
|
13439
13475
|
syncAiTaskOptions();
|
|
13440
13476
|
renderAiRoleplayCharacterSelect();
|
|
@@ -13472,7 +13508,7 @@ async function sendAiWithOptions({ ignoreContextWarning = false } = {}) {
|
|
|
13472
13508
|
const request = assertAiRequestCurrent(requestHolder.snapshot);
|
|
13473
13509
|
suggestion = await api(`/api/works/${encodeURIComponent(request.workId)}/suggestions`, {
|
|
13474
13510
|
method: "POST",
|
|
13475
|
-
body: { taskType, instruction, scope, modelId, citations },
|
|
13511
|
+
body: { taskType, instruction, scope, modelId, citations, conversationId: requestHolder.snapshot.conversationId },
|
|
13476
13512
|
signal: request.signal
|
|
13477
13513
|
});
|
|
13478
13514
|
assertAiRequestCurrent(request);
|
|
@@ -13482,7 +13518,7 @@ async function sendAiWithOptions({ ignoreContextWarning = false } = {}) {
|
|
|
13482
13518
|
if (suggestionFailed) setAiAssistantStatus("error");
|
|
13483
13519
|
setAiContextMeter(suggestion.contextUsage, false);
|
|
13484
13520
|
assistantContent = suggestion.content;
|
|
13485
|
-
assistantMetadata = { modelDisplayName: suggestion.model?.displayName, outputTokens: suggestion.outputTokens, cacheHitPercent: suggestion.cacheHitPercent };
|
|
13521
|
+
assistantMetadata = { modelId, modelDisplayName: suggestion.model?.displayName, outputTokens: suggestion.outputTokens, cacheHitPercent: suggestion.cacheHitPercent, processDurationMs: suggestion.processDurationMs };
|
|
13486
13522
|
}
|
|
13487
13523
|
try {
|
|
13488
13524
|
const request = assertAiRequestCurrent(requestHolder.snapshot);
|
|
@@ -13702,6 +13738,10 @@ async function streamChat(requestHolder, body, idempotencyKey) {
|
|
|
13702
13738
|
}
|
|
13703
13739
|
requestHolder.snapshot = aiRequestManager.bind(requestHolder.snapshot, { userMessageId: persistedUserMessage.id });
|
|
13704
13740
|
updateAiConversationSummaryFromMessage(persistedUserMessage);
|
|
13741
|
+
const lockedModelId = typeof persistedUserMessage.metadata?.modelId === "string"
|
|
13742
|
+
? persistedUserMessage.metadata.modelId
|
|
13743
|
+
: typeof body.modelId === "string" ? body.modelId : null;
|
|
13744
|
+
state.aiConversationModelId = lockedModelId;
|
|
13705
13745
|
state.aiPromptSent = true;
|
|
13706
13746
|
syncAiTaskOptions();
|
|
13707
13747
|
renderAiRoleplayCharacterSelect();
|
|
@@ -13772,10 +13812,12 @@ async function streamChat(requestHolder, body, idempotencyKey) {
|
|
|
13772
13812
|
message.querySelector(".message-heading > span").textContent = "助手";
|
|
13773
13813
|
toolCalls = Array.isArray(payload.toolCalls) ? payload.toolCalls : toolCalls;
|
|
13774
13814
|
processSteps = Array.isArray(payload.processSteps) ? payload.processSteps : processSteps;
|
|
13775
|
-
const processDurationMs =
|
|
13815
|
+
const processDurationMs = Number.isFinite(payload.processDurationMs) && payload.processDurationMs >= 0
|
|
13816
|
+
? payload.processDurationMs
|
|
13817
|
+
: elapsedProcessTime();
|
|
13776
13818
|
generatedMetadata = { modelDisplayName: payload.model?.displayName, outputTokens: payload.outputTokens, cacheHitPercent: payload.cacheHitPercent, toolCalls, processSteps, processDurationMs };
|
|
13777
13819
|
renderAiProcessSteps(message, processSteps, true, processDurationMs);
|
|
13778
|
-
meta.textContent = formatAiMessageMeta(payload.model?.displayName, payload.outputTokens, payload.cacheHitPercent);
|
|
13820
|
+
meta.textContent = formatAiMessageMeta(payload.model?.displayName, payload.outputTokens, payload.cacheHitPercent, "", processDurationMs);
|
|
13779
13821
|
attachAssistantCopyAction(message, streamedText);
|
|
13780
13822
|
scrollAiFeedToBottom();
|
|
13781
13823
|
} else if (eventName === "request_status") {
|
|
@@ -13893,11 +13935,16 @@ function appendMessage(role, text, citations = [], createdAt = null, metadata =
|
|
|
13893
13935
|
}
|
|
13894
13936
|
message.append(references);
|
|
13895
13937
|
}
|
|
13896
|
-
|
|
13897
|
-
|
|
13938
|
+
const processSteps = role === "assistant"
|
|
13939
|
+
? (Array.isArray(metadata?.processSteps) && metadata.processSteps.length
|
|
13898
13940
|
? metadata.processSteps
|
|
13899
|
-
: (Array.isArray(metadata?.toolCalls) ? metadata.toolCalls : []).map((toolCall) => aiToolProcessStep(toolCall))
|
|
13900
|
-
|
|
13941
|
+
: (Array.isArray(metadata?.toolCalls) ? metadata.toolCalls : []).map((toolCall) => aiToolProcessStep(toolCall)))
|
|
13942
|
+
: [];
|
|
13943
|
+
const processDurationMs = role === "assistant"
|
|
13944
|
+
? resolveAiProcessDuration(metadata, processSteps, createdAt)
|
|
13945
|
+
: null;
|
|
13946
|
+
if (role === "assistant") {
|
|
13947
|
+
renderAiProcessSteps(message, processSteps, true, processDurationMs);
|
|
13901
13948
|
}
|
|
13902
13949
|
if (role === "assistant" && !text.startsWith("调用失败:")) {
|
|
13903
13950
|
const selectedModel = state.models.find((model) => model.id === $("#ai-model").value) ?? state.models[0];
|
|
@@ -13907,7 +13954,7 @@ function appendMessage(role, text, citations = [], createdAt = null, metadata =
|
|
|
13907
13954
|
meta.className = "message-meta";
|
|
13908
13955
|
meta.textContent = isInterrupted
|
|
13909
13956
|
? formatAiStreamInterruptionMeta(interruptionCode, text.length)
|
|
13910
|
-
: formatAiMessageMeta(modelDisplayName, outputTokens, metadata?.cacheHitPercent);
|
|
13957
|
+
: formatAiMessageMeta(modelDisplayName, outputTokens, metadata?.cacheHitPercent, "", processDurationMs);
|
|
13911
13958
|
message.append(meta);
|
|
13912
13959
|
attachAssistantCopyAction(message, text);
|
|
13913
13960
|
}
|
|
@@ -13922,7 +13969,7 @@ function appendSuggestion(suggestion, createdAt = null, messageId = null) {
|
|
|
13922
13969
|
const applicable = suggestion.action !== "note";
|
|
13923
13970
|
const guard = suggestion.guard;
|
|
13924
13971
|
const guardHtml = guard ? `<section class="guard-card ${esc(guard.status)}" data-testid="continuation-guard"><strong>${guard.status === "clear" ? "一致性守卫:未发现冲突" : guard.status === "warning" ? `一致性守卫:发现 ${guard.issues.length} 项风险` : "一致性守卫:检查失败"}</strong>${guard.status === "failed" ? `<p>${esc(guard.failure || "无法完成检查,请谨慎采纳")}</p>` : guard.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>` : "";
|
|
13925
|
-
message.innerHTML = `<div class="message-body">${renderMarkdown(suggestion.content)}</div><div class="message-meta">${esc(formatAiMessageMeta(suggestion.model?.displayName, suggestion.outputTokens, suggestion.cacheHitPercent, `基于 v${suggestion.chapterVersion ?? "-"}
|
|
13972
|
+
message.innerHTML = `<div class="message-body">${renderMarkdown(suggestion.content)}</div><div class="message-meta">${esc(formatAiMessageMeta(suggestion.model?.displayName, suggestion.outputTokens, suggestion.cacheHitPercent, `基于 v${suggestion.chapterVersion ?? "-"}`, suggestion.processDurationMs))}</div>${guardHtml}${applicable ? '<div class="message-actions"><button data-action="accept">采纳到正文</button><button data-action="reject">拒绝</button></div>' : ""}`;
|
|
13926
13973
|
attachMessageHeading(message, "助手建议", createdAt ?? undefined);
|
|
13927
13974
|
attachAssistantCopyAction(message, suggestion.content);
|
|
13928
13975
|
attachMessageIdentity(message, messageId);
|
|
@@ -14234,7 +14281,7 @@ function renderWorkRecycleBin(result) {
|
|
|
14234
14281
|
});
|
|
14235
14282
|
await loadWorkRecycleBin();
|
|
14236
14283
|
dialog.showModal();
|
|
14237
|
-
|
|
14284
|
+
deleteToast(`已彻底删除作品“${work.title}”`);
|
|
14238
14285
|
} catch (error) {
|
|
14239
14286
|
dialog.showModal();
|
|
14240
14287
|
toast(error.message, "error");
|
|
@@ -14340,7 +14387,7 @@ function renderChapterRecycleBin(result) {
|
|
|
14340
14387
|
renderTree();
|
|
14341
14388
|
await loadChapterRecycleBin();
|
|
14342
14389
|
dialog.showModal();
|
|
14343
|
-
|
|
14390
|
+
deleteToast(`已彻底删除分卷“${volume.title}”`);
|
|
14344
14391
|
} catch (error) {
|
|
14345
14392
|
dialog.showModal();
|
|
14346
14393
|
toast(error.message, "error");
|
|
@@ -14396,7 +14443,7 @@ function renderChapterRecycleBin(result) {
|
|
|
14396
14443
|
renderTree();
|
|
14397
14444
|
await loadChapterRecycleBin();
|
|
14398
14445
|
dialog.showModal();
|
|
14399
|
-
|
|
14446
|
+
deleteToast(`已彻底删除章节“${chapter.title}”`);
|
|
14400
14447
|
} catch (error) {
|
|
14401
14448
|
dialog.showModal();
|
|
14402
14449
|
toast(error.message, "error");
|
|
@@ -14901,7 +14948,7 @@ $("#avatar-remove-button").addEventListener("click", async () => {
|
|
|
14901
14948
|
const updated = await api("/api/auth/avatar", { method: "DELETE" });
|
|
14902
14949
|
applyAuthenticatedUser({ user: updated, csrfToken: state.csrfToken });
|
|
14903
14950
|
renderProfileAvatar();
|
|
14904
|
-
|
|
14951
|
+
deleteToast("头像已移除");
|
|
14905
14952
|
} catch (error) {
|
|
14906
14953
|
toast(error.message, "error");
|
|
14907
14954
|
} finally {
|
|
@@ -15446,7 +15493,13 @@ $("#ai-prompt").addEventListener("focus", () => {
|
|
|
15446
15493
|
$("#ai-model").addEventListener("focus", () => {
|
|
15447
15494
|
ensureAiModelsLoaded().catch((error) => toast(`模型加载失败:${error.message}`, "error"));
|
|
15448
15495
|
});
|
|
15449
|
-
$("#ai-model").addEventListener("change", () =>
|
|
15496
|
+
$("#ai-model").addEventListener("change", (event) => {
|
|
15497
|
+
if (state.aiPromptSent) {
|
|
15498
|
+
event.currentTarget.value = state.aiConversationModelId ?? event.currentTarget.value;
|
|
15499
|
+
return toast("当前对话已经开始,请新建对话后再切换模型", "error");
|
|
15500
|
+
}
|
|
15501
|
+
setAiContextMeter(null);
|
|
15502
|
+
});
|
|
15450
15503
|
$("#ai-roleplay-character").addEventListener("focus", async () => {
|
|
15451
15504
|
try {
|
|
15452
15505
|
await ensureAiReferencesLoaded();
|
|
@@ -15954,6 +16007,10 @@ document.addEventListener("visibilitychange", () => {
|
|
|
15954
16007
|
if (state.user?.role === "admin" && !systemRestartDetected) void refreshS3BackupEvents();
|
|
15955
16008
|
void refreshSystemHealth();
|
|
15956
16009
|
});
|
|
16010
|
+
document.addEventListener("click", (event) => {
|
|
16011
|
+
if (event.target instanceof Element && event.target.closest('[role="tab"]')) dismissDeleteToasts();
|
|
16012
|
+
}, true);
|
|
16013
|
+
window.addEventListener("pagehide", dismissDeleteToasts);
|
|
15957
16014
|
window.addEventListener("beforeunload", (event) => {
|
|
15958
16015
|
if (hasUnsavedEditorChanges()) event.preventDefault();
|
|
15959
16016
|
});
|
package/dist/public/index.html
CHANGED
|
@@ -10,12 +10,12 @@
|
|
|
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=
|
|
13
|
+
<link rel="stylesheet" href="/styles.css?v=20260814-vditor-ui-fixes-v1">
|
|
14
14
|
</head>
|
|
15
15
|
<body class="auth-pending">
|
|
16
16
|
<section id="auth-view" class="auth-view hidden" aria-labelledby="auth-title">
|
|
17
17
|
<div class="auth-card">
|
|
18
|
-
<div class="auth-brand"><
|
|
18
|
+
<div class="auth-brand"><img class="brand-mark" src="/icon.svg?v=20260712" alt=""><div><strong>叙界</strong><small>小说 AI 创作工作台</small></div></div>
|
|
19
19
|
<span class="eyebrow">账户与作品空间</span>
|
|
20
20
|
<h1 id="auth-title">登录后继续创作</h1>
|
|
21
21
|
<p id="auth-description">你的作品、协作权限和每一次修改都会绑定到账户。</p>
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
<div id="app" class="app-shell">
|
|
66
66
|
<header class="topbar">
|
|
67
67
|
<button id="home-button" class="brand-block" type="button" aria-label="返回书架">
|
|
68
|
-
<
|
|
68
|
+
<img class="brand-mark" src="/icon.svg?v=20260712" alt="">
|
|
69
69
|
<div>
|
|
70
70
|
<strong>叙界</strong>
|
|
71
71
|
<small>小说 AI 创作工作台</small>
|
|
@@ -512,7 +512,7 @@
|
|
|
512
512
|
<dialog id="form-dialog" class="dialog">
|
|
513
513
|
<form id="dynamic-form" method="dialog">
|
|
514
514
|
<div class="dialog-header">
|
|
515
|
-
<div><span id="dialog-eyebrow" class="eyebrow">新增</span><h2 id="dialog-title">创建记录</h2><p id="dialog-meta" class="dialog-header-meta hidden"></p></div>
|
|
515
|
+
<div><span id="dialog-eyebrow" class="eyebrow">新增</span><h2 id="dialog-title">创建记录</h2><input id="dialog-title-input" class="dialog-title-input hidden" type="text" autocomplete="off" aria-label="记录标题"><p id="dialog-meta" class="dialog-header-meta hidden"></p></div>
|
|
516
516
|
<button class="dialog-close" value="cancel" aria-label="关闭" type="submit">×</button>
|
|
517
517
|
</div>
|
|
518
518
|
<div id="dialog-fields" class="dialog-fields"></div>
|
|
@@ -1156,6 +1156,6 @@
|
|
|
1156
1156
|
<div id="auth-loading" class="auth-loading" role="status" aria-label="正在载入工作台"></div>
|
|
1157
1157
|
<script id="vditorIconScript" src="/vendor/vditor/dist/js/icons/ant.js?v=3.11.2"></script>
|
|
1158
1158
|
<script src="/vendor/vditor/dist/index.min.js?v=3.11.2"></script>
|
|
1159
|
-
<script type="module" src="/app.js?v=
|
|
1159
|
+
<script type="module" src="/app.js?v=20260814-vditor-ui-fixes-v1"></script>
|
|
1160
1160
|
</body>
|
|
1161
1161
|
</html>
|