@musnows/scriverse 0.9.4 → 0.9.6
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-protocol.js +6 -0
- package/dist/ai-protocol.js.map +1 -1
- package/dist/ai-skills.js +134 -0
- package/dist/ai-skills.js.map +1 -0
- package/dist/ai-write-plans.js +2279 -0
- package/dist/ai-write-plans.js.map +1 -0
- package/dist/ai.js +2781 -154
- package/dist/ai.js.map +1 -1
- package/dist/app.js +328 -17
- package/dist/app.js.map +1 -1
- package/dist/chapter-annotation-anchor.js +327 -0
- package/dist/chapter-annotation-anchor.js.map +1 -0
- package/dist/chapter-title-numbering.js +56 -0
- package/dist/chapter-title-numbering.js.map +1 -0
- package/dist/database.js +478 -3
- package/dist/database.js.map +1 -1
- package/dist/public/ai-context-meter.js +3 -3
- package/dist/public/ai-interactive.js +483 -0
- package/dist/public/app.js +1991 -268
- package/dist/public/chapter-editor-behavior.js +40 -0
- package/dist/public/chapter-line-id-tracker.d.ts +25 -0
- package/dist/public/chapter-line-id-tracker.js +151 -0
- package/dist/public/index.html +79 -19
- package/dist/public/model-config.d.ts +1 -0
- package/dist/public/model-config.js +9 -4
- package/dist/public/styles.css +337 -57
- package/dist/public/theme-init.js +25 -1
- package/dist/remote-mcp.js +496 -0
- package/dist/remote-mcp.js.map +1 -0
- package/dist/roleplay-memory.js +53 -0
- package/dist/roleplay-memory.js.map +1 -0
- package/dist/security.js +4 -0
- package/dist/security.js.map +1 -1
- package/dist/semantic-search.js +225 -0
- package/dist/semantic-search.js.map +1 -0
- package/dist/skills/continue-writing/SKILL.md +23 -0
- package/dist/skills/polish-writing/SKILL.md +23 -0
- package/dist/store.js +695 -51
- package/dist/store.js.map +1 -1
- package/dist/ui-module-preload.js +27 -0
- package/dist/ui-module-preload.js.map +1 -0
- package/dist/user-auth.js +33 -1
- package/dist/user-auth.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +6 -1
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export const CHAPTER_PARAGRAPH_INDENT = "\u3000\u3000";
|
|
2
|
+
|
|
3
|
+
export function insertIndentedParagraph(value, selectionStart, selectionEnd) {
|
|
4
|
+
const text = String(value ?? "");
|
|
5
|
+
const start = Math.max(0, Math.min(text.length, Number(selectionStart) || 0));
|
|
6
|
+
const end = Math.max(start, Math.min(text.length, Number(selectionEnd) || start));
|
|
7
|
+
const insertion = `\n${CHAPTER_PARAGRAPH_INDENT}`;
|
|
8
|
+
const cursor = start + insertion.length;
|
|
9
|
+
return {
|
|
10
|
+
value: `${text.slice(0, start)}${insertion}${text.slice(end)}`,
|
|
11
|
+
selectionStart: cursor,
|
|
12
|
+
selectionEnd: cursor
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function chapterLineIndexAtOffset(value, offset) {
|
|
17
|
+
const text = String(value ?? "");
|
|
18
|
+
const safeOffset = Math.max(0, Math.min(text.length, Number(offset) || 0));
|
|
19
|
+
return (text.slice(0, safeOffset).match(/\n/gu) ?? []).length;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function calculateChapterCaretScroll({
|
|
23
|
+
caretBottom,
|
|
24
|
+
scrollTop,
|
|
25
|
+
clientHeight,
|
|
26
|
+
scrollHeight,
|
|
27
|
+
activationRatio = 0.6,
|
|
28
|
+
targetRatio = 0.5
|
|
29
|
+
}) {
|
|
30
|
+
const current = Math.max(0, Number(scrollTop) || 0);
|
|
31
|
+
const viewportHeight = Math.max(0, Number(clientHeight) || 0);
|
|
32
|
+
const contentHeight = Math.max(viewportHeight, Number(scrollHeight) || 0);
|
|
33
|
+
const caretPosition = Math.max(0, Number(caretBottom) || 0);
|
|
34
|
+
if (viewportHeight === 0 || contentHeight <= viewportHeight) return current;
|
|
35
|
+
const activationPoint = current + viewportHeight * activationRatio;
|
|
36
|
+
if (caretPosition <= activationPoint) return current;
|
|
37
|
+
const maximum = Math.max(0, contentHeight - viewportHeight);
|
|
38
|
+
const target = caretPosition - viewportHeight * targetRatio;
|
|
39
|
+
return Math.min(maximum, Math.max(current, target));
|
|
40
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type ChapterLineEditHint = {
|
|
2
|
+
selectionStart: number;
|
|
3
|
+
selectionEnd: number;
|
|
4
|
+
inputType?: string;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export const MAX_CHAPTER_LINE_IDS: number;
|
|
8
|
+
|
|
9
|
+
export function normalizeChapterLineIdDraft(
|
|
10
|
+
content: unknown,
|
|
11
|
+
lineIds: unknown
|
|
12
|
+
): Array<string | null>;
|
|
13
|
+
|
|
14
|
+
export function remapChapterLineCounts(
|
|
15
|
+
beforeLineIds: unknown,
|
|
16
|
+
afterLineIds: unknown,
|
|
17
|
+
lineCounts: unknown
|
|
18
|
+
): Map<number, number>;
|
|
19
|
+
|
|
20
|
+
export function reconcileChapterLineIdDraft(
|
|
21
|
+
beforeContent: unknown,
|
|
22
|
+
afterContent: unknown,
|
|
23
|
+
beforeLineIds: unknown,
|
|
24
|
+
hint?: ChapterLineEditHint | null
|
|
25
|
+
): Array<string | null>;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
export const MAX_CHAPTER_LINE_IDS = 100_000;
|
|
2
|
+
|
|
3
|
+
function lineSpans(content) {
|
|
4
|
+
const text = String(content ?? "").replace(/\r\n?/gu, "\n");
|
|
5
|
+
const spans = [];
|
|
6
|
+
let start = 0;
|
|
7
|
+
for (let index = 0; index <= text.length; index += 1) {
|
|
8
|
+
if (index !== text.length && text[index] !== "\n") continue;
|
|
9
|
+
spans.push({ start, end: index });
|
|
10
|
+
start = index + 1;
|
|
11
|
+
}
|
|
12
|
+
return spans;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function lineIndexAtOffset(spans, offset) {
|
|
16
|
+
const target = Math.max(0, Number(offset) || 0);
|
|
17
|
+
let low = 0;
|
|
18
|
+
let high = spans.length;
|
|
19
|
+
while (low < high) {
|
|
20
|
+
const middle = Math.floor((low + high) / 2);
|
|
21
|
+
if (spans[middle].start <= target) low = middle + 1;
|
|
22
|
+
else high = middle;
|
|
23
|
+
}
|
|
24
|
+
return Math.max(0, Math.min(spans.length - 1, low - 1));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function previousCharacterStart(text, offset) {
|
|
28
|
+
if (offset <= 0) return 0;
|
|
29
|
+
const previous = text.charCodeAt(offset - 1);
|
|
30
|
+
return previous >= 0xdc00 && previous <= 0xdfff && offset >= 2 ? offset - 2 : offset - 1;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function nextCharacterEnd(text, offset) {
|
|
34
|
+
if (offset >= text.length) return text.length;
|
|
35
|
+
const current = text.charCodeAt(offset);
|
|
36
|
+
return current >= 0xd800 && current <= 0xdbff && offset + 1 < text.length ? offset + 2 : offset + 1;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function hintedChangeRange(beforeContent, afterContent, hint) {
|
|
40
|
+
if (!hint || !Number.isInteger(hint.selectionStart) || !Number.isInteger(hint.selectionEnd)) return null;
|
|
41
|
+
let start = Math.max(0, Math.min(beforeContent.length, hint.selectionStart));
|
|
42
|
+
let end = Math.max(start, Math.min(beforeContent.length, hint.selectionEnd));
|
|
43
|
+
if (start === end && hint.inputType === "deleteContentBackward") start = previousCharacterStart(beforeContent, start);
|
|
44
|
+
if (start === end && hint.inputType === "deleteContentForward") end = nextCharacterEnd(beforeContent, end);
|
|
45
|
+
const insertedLength = afterContent.length - (beforeContent.length - (end - start));
|
|
46
|
+
if (insertedLength < 0) return null;
|
|
47
|
+
const afterEnd = start + insertedLength;
|
|
48
|
+
if (
|
|
49
|
+
afterEnd > afterContent.length
|
|
50
|
+
|| beforeContent.slice(0, start) !== afterContent.slice(0, start)
|
|
51
|
+
|| beforeContent.slice(end) !== afterContent.slice(afterEnd)
|
|
52
|
+
) return null;
|
|
53
|
+
return { beforeStart: start, beforeEnd: end, afterStart: start, afterEnd };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function inferredChangeRange(beforeContent, afterContent) {
|
|
57
|
+
let prefixLength = 0;
|
|
58
|
+
const maximumPrefix = Math.min(beforeContent.length, afterContent.length);
|
|
59
|
+
while (prefixLength < maximumPrefix && beforeContent[prefixLength] === afterContent[prefixLength]) prefixLength += 1;
|
|
60
|
+
let suffixLength = 0;
|
|
61
|
+
while (
|
|
62
|
+
suffixLength < beforeContent.length - prefixLength
|
|
63
|
+
&& suffixLength < afterContent.length - prefixLength
|
|
64
|
+
&& beforeContent[beforeContent.length - suffixLength - 1] === afterContent[afterContent.length - suffixLength - 1]
|
|
65
|
+
) suffixLength += 1;
|
|
66
|
+
return {
|
|
67
|
+
beforeStart: prefixLength,
|
|
68
|
+
beforeEnd: beforeContent.length - suffixLength,
|
|
69
|
+
afterStart: prefixLength,
|
|
70
|
+
afterEnd: afterContent.length - suffixLength
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function normalizeChapterLineIdDraft(content, lineIds) {
|
|
75
|
+
const count = lineSpans(content).length;
|
|
76
|
+
if (count > MAX_CHAPTER_LINE_IDS) return [];
|
|
77
|
+
if (!Array.isArray(lineIds) || lineIds.length !== count) return Array.from({ length: count }, () => null);
|
|
78
|
+
const seen = new Set();
|
|
79
|
+
return lineIds.map((lineId) => {
|
|
80
|
+
if (lineId === null) return null;
|
|
81
|
+
if (typeof lineId !== "string" || !lineId || seen.has(lineId)) return null;
|
|
82
|
+
seen.add(lineId);
|
|
83
|
+
return lineId;
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function remapChapterLineCounts(beforeLineIdsValue, afterLineIdsValue, lineCountsValue) {
|
|
88
|
+
const counts = lineCountsValue instanceof Map ? lineCountsValue : new Map();
|
|
89
|
+
const beforeLineIds = Array.isArray(beforeLineIdsValue) ? beforeLineIdsValue : [];
|
|
90
|
+
const afterLineIds = Array.isArray(afterLineIdsValue) ? afterLineIdsValue : [];
|
|
91
|
+
if (beforeLineIds.length === 0 || afterLineIds.length === 0) return new Map(counts);
|
|
92
|
+
|
|
93
|
+
const countsByLineId = new Map();
|
|
94
|
+
counts.forEach((countValue, lineValue) => {
|
|
95
|
+
const line = Number(lineValue);
|
|
96
|
+
const count = Number(countValue);
|
|
97
|
+
const lineId = beforeLineIds[line - 1];
|
|
98
|
+
if (!Number.isInteger(line) || line < 1 || !Number.isInteger(count) || count < 1 || typeof lineId !== "string" || !lineId) return;
|
|
99
|
+
countsByLineId.set(lineId, count);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const remapped = new Map();
|
|
103
|
+
afterLineIds.forEach((lineId, index) => {
|
|
104
|
+
if (typeof lineId !== "string" || !lineId) return;
|
|
105
|
+
const count = countsByLineId.get(lineId);
|
|
106
|
+
if (count !== undefined) remapped.set(index + 1, count);
|
|
107
|
+
});
|
|
108
|
+
return remapped;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function reconcileChapterLineIdDraft(beforeContentValue, afterContentValue, beforeLineIdsValue, hint = null) {
|
|
112
|
+
const beforeContent = String(beforeContentValue ?? "").replace(/\r\n?/gu, "\n");
|
|
113
|
+
const afterContent = String(afterContentValue ?? "").replace(/\r\n?/gu, "\n");
|
|
114
|
+
const beforeSpans = lineSpans(beforeContent);
|
|
115
|
+
const afterSpans = lineSpans(afterContent);
|
|
116
|
+
if (beforeSpans.length > MAX_CHAPTER_LINE_IDS || afterSpans.length > MAX_CHAPTER_LINE_IDS) return [];
|
|
117
|
+
const beforeLineIds = normalizeChapterLineIdDraft(beforeContent, beforeLineIdsValue);
|
|
118
|
+
if (beforeContent === afterContent) return beforeLineIds;
|
|
119
|
+
const change = hintedChangeRange(beforeContent, afterContent, hint)
|
|
120
|
+
?? inferredChangeRange(beforeContent, afterContent);
|
|
121
|
+
const delta = (change.afterEnd - change.afterStart) - (change.beforeEnd - change.beforeStart);
|
|
122
|
+
const result = Array.from({ length: afterSpans.length }, () => null);
|
|
123
|
+
const affectedLineIds = [];
|
|
124
|
+
|
|
125
|
+
beforeSpans.forEach((span, beforeIndex) => {
|
|
126
|
+
const lineId = beforeLineIds[beforeIndex];
|
|
127
|
+
if (span.end <= change.beforeStart) {
|
|
128
|
+
const afterIndex = lineIndexAtOffset(afterSpans, span.start);
|
|
129
|
+
if (result[afterIndex] === null) result[afterIndex] = lineId;
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (span.start >= change.beforeEnd) {
|
|
133
|
+
const afterIndex = lineIndexAtOffset(afterSpans, span.start + delta);
|
|
134
|
+
if (result[afterIndex] === null) result[afterIndex] = lineId;
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (lineId !== null) affectedLineIds.push(lineId);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const affectedAfterStart = lineIndexAtOffset(afterSpans, change.afterStart);
|
|
141
|
+
const affectedAfterEnd = lineIndexAtOffset(afterSpans, Math.max(change.afterStart, change.afterEnd - 1));
|
|
142
|
+
const availableAfterIndexes = [];
|
|
143
|
+
for (let index = affectedAfterStart; index <= affectedAfterEnd; index += 1) {
|
|
144
|
+
if (result[index] === null) availableAfterIndexes.push(index);
|
|
145
|
+
}
|
|
146
|
+
affectedLineIds.forEach((lineId, index) => {
|
|
147
|
+
const afterIndex = availableAfterIndexes[index];
|
|
148
|
+
if (afterIndex !== undefined) result[afterIndex] = lineId;
|
|
149
|
+
});
|
|
150
|
+
return result;
|
|
151
|
+
}
|
package/dist/public/index.html
CHANGED
|
@@ -6,11 +6,10 @@
|
|
|
6
6
|
<meta name="description" content="面向长篇小说创作的 AI 协作工作台">
|
|
7
7
|
<meta name="theme-color" content="#8b3d2c">
|
|
8
8
|
<title>叙界 · 小说 AI 创作工作台</title>
|
|
9
|
-
<script src="/theme-init.js?v=
|
|
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="/
|
|
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&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">
|
|
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=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-header-toolbar-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=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">
|
|
14
13
|
</head>
|
|
15
14
|
<body class="auth-pending">
|
|
16
15
|
<section id="auth-view" class="auth-view hidden" aria-labelledby="auth-title">
|
|
@@ -251,12 +250,10 @@
|
|
|
251
250
|
<input id="chapter-title" class="chapter-title" aria-label="章节标题" placeholder="章节标题">
|
|
252
251
|
<div class="editor-actions">
|
|
253
252
|
<span id="chapter-stats" class="chapter-stats">0 字 · v0</span>
|
|
254
|
-
<button id="chapter-reader-button" class="ghost-button" type="button">阅读预览</button>
|
|
255
253
|
<button id="insight-button" class="ghost-button" type="button" aria-controls="chapter-insight-toast" aria-expanded="false">章节概览</button>
|
|
256
254
|
<button id="versions-button" class="ghost-button" type="button">版本</button>
|
|
257
255
|
<button id="chapter-annotations-button" class="ghost-button" type="button">评论</button>
|
|
258
|
-
<button id="chapter-
|
|
259
|
-
<button id="chapter-edit-button" class="primary-button hidden" type="button">编辑</button>
|
|
256
|
+
<button id="chapter-edit-button" class="primary-button hidden" type="button" aria-pressed="false" aria-label="切换到编辑模式">编辑</button>
|
|
260
257
|
<button id="tidy-blank-lines-button" class="ghost-button" type="button">整理空行</button>
|
|
261
258
|
<button id="save-button" class="primary-button" type="button">保存正文</button>
|
|
262
259
|
</div>
|
|
@@ -333,6 +330,8 @@
|
|
|
333
330
|
<div class="ai-heading-actions">
|
|
334
331
|
<button id="ai-conversation-switcher" class="ai-conversation-switcher" type="button" aria-label="切换打开的对话" aria-controls="ai-conversation-switcher-menu" aria-expanded="false" aria-haspopup="dialog" title="切换打开的对话"><span id="ai-conversation-title">新对话</span><small id="ai-open-conversation-count">1</small><svg viewBox="0 0 12 8" aria-hidden="true" focusable="false"><path d="M1 1.5 6 6.5l5-5"></path></svg></button>
|
|
335
332
|
<button id="ai-history-toggle" type="button" aria-label="历史记录" aria-controls="ai-history-dialog" aria-expanded="false" aria-haspopup="dialog" title="历史记录"><svg class="ai-heading-action-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M3 12a9 9 0 1 0 3-6.7L3 8"></path><path d="M3 3v5h5M12 7v5l3.5 2"></path></svg></button>
|
|
333
|
+
<button id="ai-semantic-search-toggle" type="button" aria-label="主动语义检索" aria-controls="ai-semantic-search-panel" aria-expanded="false" title="主动语义检索"><svg class="ai-heading-action-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="10.5" cy="10.5" r="6.5"></circle><path d="m15.5 15.5 5 5M7.5 10.5h6M10.5 7.5v6"></path></svg></button>
|
|
334
|
+
<button id="ai-approval-center-toggle" type="button" aria-label="AI 操作审批中心" aria-controls="ai-approval-center-dialog" aria-expanded="false" aria-haspopup="dialog" title="AI 操作审批中心"><svg class="ai-heading-action-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 3l7 3v5c0 4.4-2.9 8.2-7 10-4.1-1.8-7-5.6-7-10V6z"></path><path d="M9 12l2.2 2.2L15.5 9.5"></path></svg></button>
|
|
336
335
|
<button id="ai-new-conversation" type="button" aria-label="新建对话" title="新建对话">+</button>
|
|
337
336
|
</div>
|
|
338
337
|
</div>
|
|
@@ -344,7 +343,7 @@
|
|
|
344
343
|
<div id="ai-chat-tabs" class="ai-chat-tabs hidden" role="tablist" aria-label="Agent 对话页签" aria-hidden="true"></div>
|
|
345
344
|
<div class="quick-actions" aria-label="快捷指令">
|
|
346
345
|
<button type="button" data-task="chat" data-prompt="总结当前章节,并列出尚未解决的冲突。">总结本章</button>
|
|
347
|
-
<button type="button" data-task="
|
|
346
|
+
<button type="button" data-task="chat" data-prompt="续写当前章节,保持现有叙事风格和人物状态。">续写</button>
|
|
348
347
|
<button type="button" data-task="chat" data-prompt="提供三种后续剧情方向,并说明各自与既有设定的兼容性。">三种方向</button>
|
|
349
348
|
<button type="button" data-task="chat" data-prompt="检查当前章节与锁定设定、人物状态和时间线的冲突。">检查冲突</button>
|
|
350
349
|
</div>
|
|
@@ -352,22 +351,28 @@
|
|
|
352
351
|
<div id="ai-feed" class="ai-feed" role="tabpanel">
|
|
353
352
|
<div class="assistant-message">
|
|
354
353
|
<span>助手</span>
|
|
355
|
-
<div class="message-body"><p
|
|
354
|
+
<div class="message-body"><p>选择章节和模型后即可开始问答;提到续写或润色时会自动加载对应 Skill,也可用 /continue-writing 或 /polish-writing 强制加载。所有引用都基于已保存正文。</p></div>
|
|
356
355
|
</div>
|
|
357
356
|
</div>
|
|
358
357
|
</div>
|
|
359
358
|
<div class="prompt-box">
|
|
359
|
+
<section id="ai-semantic-search-panel" class="ai-semantic-search-panel hidden" aria-labelledby="ai-semantic-search-title">
|
|
360
|
+
<header><div><strong id="ai-semantic-search-title">主动语义检索</strong><small>只在点击检索后调用 embedding;结果默认仅查看。</small></div><button id="ai-semantic-search-close" type="button" aria-label="关闭主动语义检索">×</button></header>
|
|
361
|
+
<div class="ai-semantic-search-form"><label for="ai-semantic-query">自然语言问题<input id="ai-semantic-query" type="search" maxlength="2000" placeholder="例如:谁掌管北港的远航审批?"></label><label for="ai-semantic-scope">检索模块<select id="ai-semantic-scope"><option value="">全部可读模块</option><option value="chapter">正文</option><option value="setting">设定</option><option value="character">人物档案</option><option value="race">种族</option><option value="organization">组织</option><option value="timeline">时间线</option><option value="relationship">人物关系</option><option value="outlines">大纲与伏笔</option></select></label><button id="ai-semantic-search-run" class="primary-button" type="button">开始检索</button></div>
|
|
362
|
+
<p id="ai-semantic-search-status" class="usage-measurement-note" role="status" aria-live="polite">输入整句问题后主动检索;不会改变普通消息的隐藏上下文。</p>
|
|
363
|
+
<div id="ai-semantic-search-results" class="ai-semantic-search-results" tabindex="0" aria-label="语义检索结果,可滚动"></div>
|
|
364
|
+
<footer><span id="ai-semantic-selection-summary">尚未选择结果</span><button id="ai-semantic-inject" class="ghost-button" type="button" disabled>将选中结果注入本轮</button></footer>
|
|
365
|
+
</section>
|
|
366
|
+
<div id="ai-semantic-injection" class="ai-semantic-injection hidden" aria-live="polite"></div>
|
|
360
367
|
<div id="ai-citations" class="ai-citations hidden" aria-label="已添加的正文引用"></div>
|
|
361
368
|
<section id="ai-context-warning" class="ai-context-warning hidden" role="status" aria-live="polite">
|
|
362
369
|
<div><strong id="ai-context-warning-title">对话上下文过长</strong><p id="ai-context-warning-message">当前对话上下文过长,是否进行压缩?不压缩可能会导致后续请求失败</p></div>
|
|
363
370
|
<div><button id="ai-context-compact" type="button">压缩</button><button id="ai-context-dismiss" type="button">忽略</button><button id="ai-context-new-conversation" type="button">新开对话</button></div>
|
|
364
371
|
</section>
|
|
365
372
|
<div class="prompt-options">
|
|
366
|
-
<select id="ai-task" aria-label="
|
|
373
|
+
<select id="ai-task" aria-label="对话模式">
|
|
367
374
|
<option value="chat">问答</option>
|
|
368
375
|
<option value="roleplay">角色扮演</option>
|
|
369
|
-
<option value="continue">续写</option>
|
|
370
|
-
<option value="polish">润色选中文本</option>
|
|
371
376
|
</select>
|
|
372
377
|
<select id="ai-scope" aria-label="上下文范围">
|
|
373
378
|
<option value="none">无上下文</option>
|
|
@@ -463,7 +468,7 @@
|
|
|
463
468
|
<div id="reader-page-shell" class="reader-page-shell">
|
|
464
469
|
<div id="reader-content" class="reader-content"></div>
|
|
465
470
|
</div>
|
|
466
|
-
<div id="reader-continuation" class="reader-continuation">
|
|
471
|
+
<div id="reader-continuation" class="reader-continuation hidden">
|
|
467
472
|
<button id="reader-continue" class="ghost-button" type="button">继续下一章</button>
|
|
468
473
|
</div>
|
|
469
474
|
</article>
|
|
@@ -585,7 +590,7 @@
|
|
|
585
590
|
<dialog id="chapter-batch-dialog" class="dialog chapter-batch-dialog" aria-labelledby="chapter-batch-title">
|
|
586
591
|
<form id="chapter-batch-form" method="post">
|
|
587
592
|
<div class="dialog-header">
|
|
588
|
-
<div><span class="eyebrow">正文工具</span><h2 id="chapter-batch-title">章节批量管理</h2><p class="dialog-header-meta"
|
|
593
|
+
<div><span class="eyebrow">正文工具</span><h2 id="chapter-batch-title">章节批量管理</h2><p class="dialog-header-meta">选择多个章节后统一移动、重排标题、修改属性或软删除。</p></div>
|
|
589
594
|
<button id="chapter-batch-close" class="dialog-close" type="button" aria-label="关闭章节批量管理">×</button>
|
|
590
595
|
</div>
|
|
591
596
|
<div class="chapter-batch-toolbar">
|
|
@@ -603,9 +608,13 @@
|
|
|
603
608
|
</div>
|
|
604
609
|
<div id="chapter-batch-list" class="chapter-batch-list"></div>
|
|
605
610
|
<div class="chapter-batch-controls">
|
|
606
|
-
<label>批量操作<select id="chapter-batch-action"><option value="move">移动到分卷</option><option value="setType">修改章节类型</option><option value="exclude">排除 AI 分析</option><option value="include">纳入 AI 分析</option><option value="delete">软删除章节</option></select></label>
|
|
611
|
+
<label>批量操作<select id="chapter-batch-action"><option value="move">移动到分卷</option><option value="setType">修改章节类型</option><option value="renumberTitles">重排标题序号</option><option value="exclude">排除 AI 分析</option><option value="include">纳入 AI 分析</option><option value="delete">软删除章节</option></select></label>
|
|
607
612
|
<label id="chapter-batch-volume-field">目标分卷<select id="chapter-batch-volume"></select></label>
|
|
608
613
|
<label id="chapter-batch-type-field" class="hidden">章节类型<select id="chapter-batch-type"><option value="正文">正文</option><option value="设定">设定</option><option value="作者的话">作者的话</option><option value="其他">其他</option></select></label>
|
|
614
|
+
<label id="chapter-batch-template-field" class="hidden">标题格式<input id="chapter-batch-template" type="text" value="第{n}章" maxlength="50" autocomplete="off" aria-describedby="chapter-batch-renumber-note"></label>
|
|
615
|
+
<label id="chapter-batch-number-style-field" class="hidden">序号样式<select id="chapter-batch-number-style"><option value="chinese">中文数字(一、二、三)</option><option value="arabic">阿拉伯数字(1、2、3)</option></select></label>
|
|
616
|
+
<label id="chapter-batch-start-field" class="hidden">起始序号<input id="chapter-batch-start" type="number" value="1" min="1" max="999999" step="1" inputmode="numeric" aria-describedby="chapter-batch-renumber-note"></label>
|
|
617
|
+
<p id="chapter-batch-renumber-note" class="chapter-batch-renumber-note hidden">格式必须且只能包含一个 <code>{n}</code>。系统按目录顺序重排所选章节,清洗可识别的旧序号;异常标题会完整保留为副标题。</p>
|
|
609
618
|
</div>
|
|
610
619
|
<div class="dialog-actions">
|
|
611
620
|
<button id="chapter-batch-cancel" class="ghost-button" type="button">取消</button>
|
|
@@ -679,12 +688,12 @@
|
|
|
679
688
|
</div>
|
|
680
689
|
</form>
|
|
681
690
|
|
|
682
|
-
<form id="character-editor-form" class="entity-editor-page hidden" aria-
|
|
691
|
+
<form id="character-editor-form" class="entity-editor-page hidden" aria-label="人物档案编辑器" method="post">
|
|
683
692
|
<header class="character-editor-header">
|
|
684
693
|
<button id="character-editor-close" class="entity-editor-back" type="button">返回角色列表</button>
|
|
685
694
|
<div>
|
|
686
695
|
<span id="character-editor-eyebrow" class="eyebrow">人物主档案</span>
|
|
687
|
-
<div class="character-editor-title-row"><
|
|
696
|
+
<div class="character-editor-title-row"><input id="character-editor-name" class="character-editor-title-input" name="name" maxlength="200" placeholder="新建角色" aria-label="角色标准名" required><span id="character-editor-version" class="character-version-badge">新档案</span><span id="character-editor-readonly-badge" class="entity-editor-readonly-badge hidden">只读模式</span></div>
|
|
688
697
|
</div>
|
|
689
698
|
<div class="character-editor-header-actions">
|
|
690
699
|
<button id="character-editor-pin" class="entity-detail-pin-button hidden" type="button" aria-pressed="false"></button>
|
|
@@ -702,6 +711,7 @@
|
|
|
702
711
|
<button type="button" role="tab" data-character-editor-tab="settings" aria-selected="false">扩展设定<small>属性与长篇章节</small></button>
|
|
703
712
|
<button type="button" role="tab" data-character-editor-tab="state" aria-selected="false">状态与约束<small>当前状态与锁定项</small></button>
|
|
704
713
|
<button type="button" role="tab" data-character-editor-tab="relationships" aria-selected="false">人物关系<small>关联人物与关系关键词</small></button>
|
|
714
|
+
<button type="button" role="tab" data-character-editor-tab="roleplay-memory" aria-selected="false">角色扮演记忆<small>该角色的作品内共享记忆库</small></button>
|
|
705
715
|
</nav>
|
|
706
716
|
<main id="character-editor-fields" class="character-editor-fields"></main>
|
|
707
717
|
<aside id="character-history-panel" class="character-history-panel hidden" aria-label="人物版本历史">
|
|
@@ -1229,6 +1239,58 @@
|
|
|
1229
1239
|
</div>
|
|
1230
1240
|
</dialog>
|
|
1231
1241
|
|
|
1242
|
+
<dialog id="ai-write-plan-dialog" class="dialog wide-dialog ai-write-plan-dialog" aria-labelledby="ai-write-plan-title">
|
|
1243
|
+
<div class="dialog-header">
|
|
1244
|
+
<div><span class="eyebrow">写入审批</span><h2 id="ai-write-plan-title">AI 修改计划 · 完整明细</h2></div>
|
|
1245
|
+
<button id="ai-write-plan-close" class="dialog-close" aria-label="关闭审批详情" type="button">×</button>
|
|
1246
|
+
</div>
|
|
1247
|
+
<div id="ai-write-plan-body" class="ai-write-plan-body" aria-live="polite"></div>
|
|
1248
|
+
<div class="card-actions ai-plan-actions">
|
|
1249
|
+
<button id="ai-write-plan-refresh" class="ghost-button" type="button">刷新状态</button>
|
|
1250
|
+
<button id="ai-write-plan-reject" class="ghost-button" type="button">拒绝</button>
|
|
1251
|
+
<button id="ai-write-plan-undo" class="ghost-button hidden" type="button">撤销本次审批</button>
|
|
1252
|
+
<button id="ai-write-plan-confirm" class="primary-button" type="button">确认执行</button>
|
|
1253
|
+
</div>
|
|
1254
|
+
</dialog>
|
|
1255
|
+
|
|
1256
|
+
<dialog id="ai-question-dialog" class="dialog ai-question-dialog" aria-labelledby="ai-question-title">
|
|
1257
|
+
<div class="dialog-header">
|
|
1258
|
+
<div><span class="eyebrow">AI 提问</span><h2 id="ai-question-title">需要你的回答</h2></div>
|
|
1259
|
+
<button id="ai-question-close" class="dialog-close" aria-label="关闭提问" type="button">×</button>
|
|
1260
|
+
</div>
|
|
1261
|
+
<div class="ai-question-body">
|
|
1262
|
+
<p id="ai-question-text" class="ai-question-text"></p>
|
|
1263
|
+
<form id="ai-question-form">
|
|
1264
|
+
<div id="ai-question-options" class="ai-question-options" role="radiogroup" aria-label="预设选项"></div>
|
|
1265
|
+
<label class="ai-question-custom-field"><span class="field-label ai-question-custom-heading"><span>自定义回答 / 补充信息</span><output id="ai-question-answer-count" for="ai-question-custom-answer" aria-live="polite">已填写 0 / 3000</output></span><input id="ai-question-custom-answer" type="text" maxlength="3000" aria-describedby="ai-question-answer-count" placeholder="可补充所选方案;未选预设时作为自定义回答" autocomplete="off"></label>
|
|
1266
|
+
<p id="ai-question-expiry" class="usage-measurement-note" role="status"></p>
|
|
1267
|
+
</form>
|
|
1268
|
+
</div>
|
|
1269
|
+
<div class="card-actions ai-question-actions">
|
|
1270
|
+
<button id="ai-question-skip" class="ghost-button" type="button">暂不回答</button>
|
|
1271
|
+
<button id="ai-question-submit" class="primary-button" type="button" disabled>提交回答</button>
|
|
1272
|
+
</div>
|
|
1273
|
+
</dialog>
|
|
1274
|
+
|
|
1275
|
+
<dialog id="ai-approval-center-dialog" class="dialog wide-dialog ai-approval-center-dialog" aria-labelledby="ai-approval-center-title">
|
|
1276
|
+
<div class="dialog-header">
|
|
1277
|
+
<div><span class="eyebrow">审批工作流</span><h2 id="ai-approval-center-title">AI 操作审批中心</h2></div>
|
|
1278
|
+
<button id="ai-approval-center-close" class="dialog-close" aria-label="关闭审批中心" type="button">×</button>
|
|
1279
|
+
</div>
|
|
1280
|
+
<div class="ai-approval-center-body">
|
|
1281
|
+
<div class="ai-approval-filters" role="group" aria-label="按状态筛选审批记录">
|
|
1282
|
+
<button type="button" data-status-filter="" aria-pressed="true">全部</button>
|
|
1283
|
+
<button type="button" data-status-filter="pending" aria-pressed="false">待确认</button>
|
|
1284
|
+
<button type="button" data-status-filter="executed" aria-pressed="false">执行成功</button>
|
|
1285
|
+
<button type="button" data-status-filter="rejected" aria-pressed="false">已拒绝</button>
|
|
1286
|
+
<button type="button" data-status-filter="invalidated" aria-pressed="false">已失效</button>
|
|
1287
|
+
<button type="button" data-status-filter="expired" aria-pressed="false">已过期</button>
|
|
1288
|
+
<button type="button" data-status-filter="failed" aria-pressed="false">执行失败</button>
|
|
1289
|
+
</div>
|
|
1290
|
+
<div id="ai-approval-list-host" class="ai-approval-list-host" aria-live="polite"></div>
|
|
1291
|
+
</div>
|
|
1292
|
+
</dialog>
|
|
1293
|
+
|
|
1232
1294
|
<dialog id="relationship-map-dialog" class="relationship-map-dialog" aria-label="放大人物关系图" data-testid="relationship-map-expanded">
|
|
1233
1295
|
<button id="relationship-map-close" class="relationship-map-floating-close" aria-label="关闭放大关系图" type="button">×</button>
|
|
1234
1296
|
<div id="relationship-map-expanded-host" class="relationship-map-expanded-host"></div>
|
|
@@ -1260,8 +1322,6 @@
|
|
|
1260
1322
|
</dialog>
|
|
1261
1323
|
|
|
1262
1324
|
<div id="auth-loading" class="auth-loading" role="status" aria-label="正在载入工作台"></div>
|
|
1263
|
-
<script
|
|
1264
|
-
<script src="/vendor/vditor/dist/index.min.js?v=3.11.2"></script>
|
|
1265
|
-
<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-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-v1&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"></script>
|
|
1325
|
+
<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-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-v1&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-submit-guidance-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"></script>
|
|
1266
1326
|
</body>
|
|
1267
1327
|
</html>
|
|
@@ -8,6 +8,7 @@ export function supportsMultimodalModelProtocol(protocol: string | null | undefi
|
|
|
8
8
|
export type ModelFormValues = {
|
|
9
9
|
displayName: string;
|
|
10
10
|
modelId: string;
|
|
11
|
+
modelKind: "chat" | "embedding" | "rerank";
|
|
11
12
|
purposes: string[];
|
|
12
13
|
contextWindow: number;
|
|
13
14
|
temperature: number;
|
|
@@ -20,6 +20,7 @@ export const MODEL_THINKING_EFFORT_OPTIONS = Object.freeze([
|
|
|
20
20
|
]);
|
|
21
21
|
|
|
22
22
|
const modelThinkingEfforts = new Set(MODEL_THINKING_EFFORT_OPTIONS.map(([value]) => value));
|
|
23
|
+
const modelKinds = new Set(["chat", "embedding", "rerank"]);
|
|
23
24
|
|
|
24
25
|
export const MIN_MODEL_CONTEXT_WINDOW = 32_768;
|
|
25
26
|
export const RECOMMENDED_MODEL_CONTEXT_WINDOW = 128_000;
|
|
@@ -58,11 +59,13 @@ export function modelContextWindowGuidance(value) {
|
|
|
58
59
|
|
|
59
60
|
export function modelFormValues(model = null) {
|
|
60
61
|
const modelId = String(model?.modelId ?? "");
|
|
62
|
+
const modelKind = modelKinds.has(model?.modelKind) ? model.modelKind : "chat";
|
|
61
63
|
const configuredTemperature = model?.preset?.temperature;
|
|
62
64
|
return {
|
|
63
65
|
displayName: model?.displayName ?? "",
|
|
64
66
|
modelId,
|
|
65
|
-
|
|
67
|
+
modelKind,
|
|
68
|
+
purposes: modelKind === "chat" ? (model ? normalizeModelPurposes(model.purposes) : ["chat", "continue"]) : [],
|
|
66
69
|
contextWindow: model?.contextWindow ?? 128000,
|
|
67
70
|
temperature: isKimiModelId(modelId) && !(typeof configuredTemperature === "number" && Number.isFinite(configuredTemperature))
|
|
68
71
|
? 1
|
|
@@ -78,10 +81,12 @@ export function modelFormValues(model = null) {
|
|
|
78
81
|
|
|
79
82
|
export function modelPayload(values, existingPreset = {}) {
|
|
80
83
|
const modelId = String(values.modelId);
|
|
84
|
+
const modelKind = modelKinds.has(values.modelKind) ? values.modelKind : "chat";
|
|
81
85
|
return {
|
|
82
86
|
displayName: String(values.displayName),
|
|
83
87
|
modelId,
|
|
84
|
-
|
|
88
|
+
modelKind,
|
|
89
|
+
purposes: modelKind === "chat" ? normalizeModelPurposes(values.purposes) : [],
|
|
85
90
|
contextWindow: Number(values.contextWindow),
|
|
86
91
|
preset: {
|
|
87
92
|
...existingPreset,
|
|
@@ -90,8 +95,8 @@ export function modelPayload(values, existingPreset = {}) {
|
|
|
90
95
|
},
|
|
91
96
|
thinkingEnabled: Boolean(values.thinkingEnabled),
|
|
92
97
|
thinkingEffort: modelThinkingEfforts.has(values.thinkingEffort) ? values.thinkingEffort : "default",
|
|
93
|
-
multimodalEnabled: Boolean(values.multimodalEnabled),
|
|
94
|
-
imageToolDefault: Boolean(values.imageToolDefault),
|
|
98
|
+
multimodalEnabled: modelKind === "chat" && Boolean(values.multimodalEnabled),
|
|
99
|
+
imageToolDefault: modelKind === "chat" && Boolean(values.imageToolDefault),
|
|
95
100
|
enabled: Boolean(values.enabled)
|
|
96
101
|
};
|
|
97
102
|
}
|