@musnows/scriverse 0.9.6 → 0.9.7
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 +11 -9
- package/dist/ai.js.map +1 -1
- package/dist/app.js +5 -5
- package/dist/app.js.map +1 -1
- package/dist/public/ai-skill-menu.js +42 -0
- package/dist/public/ai-usage.d.ts +5 -2
- package/dist/public/ai-usage.js +50 -37
- package/dist/public/app.js +97 -25
- package/dist/public/index.html +3 -3
- package/dist/public/styles.css +28 -3
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export const AI_SKILL_COMMAND_OPTIONS = Object.freeze([
|
|
2
|
+
Object.freeze({
|
|
3
|
+
name: "continue-writing",
|
|
4
|
+
label: "续写正文",
|
|
5
|
+
description: "延续当前章节,并保持情节、人物与设定一致。"
|
|
6
|
+
}),
|
|
7
|
+
Object.freeze({
|
|
8
|
+
name: "polish-writing",
|
|
9
|
+
label: "润色选中文本",
|
|
10
|
+
description: "润色当前章节的精确选区,并生成可确认的替换建议。"
|
|
11
|
+
})
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
export function findAiSkillCommand(value, cursor = String(value ?? "").length) {
|
|
15
|
+
const text = String(value ?? "");
|
|
16
|
+
const safeCursor = Math.max(0, Math.min(Number(cursor) || 0, text.length));
|
|
17
|
+
const match = text.slice(0, safeCursor).match(/(?:^|\s)(\/([^/\s]*))$/u);
|
|
18
|
+
if (!match) return null;
|
|
19
|
+
return {
|
|
20
|
+
start: safeCursor - match[1].length,
|
|
21
|
+
end: safeCursor,
|
|
22
|
+
query: match[2]
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function listAiSkillOptions(query = "") {
|
|
27
|
+
const keyword = String(query).trim().toLocaleLowerCase("zh-CN");
|
|
28
|
+
if (!keyword) return [...AI_SKILL_COMMAND_OPTIONS];
|
|
29
|
+
return AI_SKILL_COMMAND_OPTIONS.filter((skill) => [skill.name, skill.label]
|
|
30
|
+
.some((value) => value.toLocaleLowerCase("zh-CN").includes(keyword)));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function applyAiSkillCommand(value, match, skillName) {
|
|
34
|
+
const text = String(value ?? "");
|
|
35
|
+
const command = `/${String(skillName).trim()}`;
|
|
36
|
+
const separator = /^\s/u.test(text.slice(match.end)) ? "" : " ";
|
|
37
|
+
return {
|
|
38
|
+
text: `${text.slice(0, match.start)}${command}${separator}${text.slice(match.end)}`,
|
|
39
|
+
command,
|
|
40
|
+
cursor: match.start + command.length + separator.length
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export type AiUsageCalendarCell = {
|
|
2
2
|
date: string;
|
|
3
3
|
totalTokens: number;
|
|
4
|
+
outsideYear: boolean;
|
|
4
5
|
future: boolean;
|
|
5
6
|
week: number;
|
|
6
7
|
weekday: number;
|
|
@@ -10,12 +11,14 @@ export type AiUsageCalendarCell = {
|
|
|
10
11
|
export function formatTokenCount(value: unknown): string;
|
|
11
12
|
export function formatCacheHitRate(value: unknown): string;
|
|
12
13
|
export function formatEstimatedCost(value: unknown): string;
|
|
14
|
+
export function usageCalendarYears(daily: Array<{ date: string; totalTokens: number }> | unknown): number[];
|
|
13
15
|
export function buildUsageCalendar(
|
|
14
16
|
daily: Array<{ date: string; totalTokens: number }> | unknown,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
+
year: number,
|
|
18
|
+
todayDateKey?: string
|
|
17
19
|
): {
|
|
18
20
|
cells: AiUsageCalendarCell[];
|
|
19
21
|
months: Array<{ week: number; label: string }>;
|
|
20
22
|
weekCount: number;
|
|
23
|
+
year: number | null;
|
|
21
24
|
};
|
package/dist/public/ai-usage.js
CHANGED
|
@@ -1,19 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
return
|
|
1
|
+
const usageDateKeyPattern = /^(\d{4})-(\d{2})-(\d{2})$/u;
|
|
2
|
+
|
|
3
|
+
function usageDateFromKey(value) {
|
|
4
|
+
const match = usageDateKeyPattern.exec(String(value));
|
|
5
|
+
if (!match) return null;
|
|
6
|
+
const year = Number(match[1]);
|
|
7
|
+
const month = Number(match[2]);
|
|
8
|
+
const day = Number(match[3]);
|
|
9
|
+
const date = new Date(Date.UTC(year, month - 1, day));
|
|
10
|
+
return year >= 1_000 && date.toISOString().slice(0, 10) === String(value) ? date : null;
|
|
6
11
|
}
|
|
7
12
|
|
|
8
|
-
function
|
|
9
|
-
|
|
10
|
-
date.setHours(0, 0, 0, 0);
|
|
11
|
-
return date;
|
|
13
|
+
function usageDateKey(date) {
|
|
14
|
+
return date.toISOString().slice(0, 10);
|
|
12
15
|
}
|
|
13
16
|
|
|
14
17
|
export function formatTokenCount(value) {
|
|
15
18
|
const count = Math.max(0, Math.round(Number(value) || 0));
|
|
16
19
|
if (count < 10_000) return count.toLocaleString("zh-CN");
|
|
20
|
+
if (count >= 100_000_000) return `${(count / 100_000_000).toFixed(2)}亿`;
|
|
17
21
|
return new Intl.NumberFormat("zh-CN", {
|
|
18
22
|
notation: "compact",
|
|
19
23
|
compactDisplay: "short",
|
|
@@ -38,26 +42,43 @@ export function formatEstimatedCost(value) {
|
|
|
38
42
|
})}`;
|
|
39
43
|
}
|
|
40
44
|
|
|
41
|
-
export function
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
export function usageCalendarYears(daily) {
|
|
46
|
+
return [...new Set((Array.isArray(daily) ? daily : [])
|
|
47
|
+
.filter((item) => usageDateFromKey(item?.date) && Math.max(0, Number(item?.totalTokens) || 0) > 0)
|
|
48
|
+
.map((item) => Number(String(item.date).slice(0, 4))))]
|
|
49
|
+
.sort((left, right) => right - left);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function buildUsageCalendar(daily, year, todayDateKey) {
|
|
53
|
+
const selectedYear = Number.isInteger(Number(year)) ? Number(year) : usageCalendarYears(daily)[0];
|
|
54
|
+
if (!selectedYear || selectedYear < 1_000 || selectedYear > 9_999) {
|
|
55
|
+
return { cells: [], months: [], weekCount: 0, year: null };
|
|
56
|
+
}
|
|
57
|
+
const firstDay = new Date(Date.UTC(selectedYear, 0, 1));
|
|
58
|
+
const lastDay = new Date(Date.UTC(selectedYear, 11, 31));
|
|
59
|
+
const start = new Date(firstDay);
|
|
60
|
+
start.setUTCDate(start.getUTCDate() - start.getUTCDay());
|
|
61
|
+
const end = new Date(lastDay);
|
|
62
|
+
end.setUTCDate(end.getUTCDate() + (6 - end.getUTCDay()));
|
|
63
|
+
const weekCount = Math.floor((end.getTime() - start.getTime()) / (7 * 24 * 60 * 60_000)) + 1;
|
|
64
|
+
const normalizedTodayKey = usageDateFromKey(todayDateKey) ? String(todayDateKey) : null;
|
|
65
|
+
const usageByDate = new Map((Array.isArray(daily) ? daily : [])
|
|
66
|
+
.filter((item) => usageDateFromKey(item?.date)?.getUTCFullYear() === selectedYear)
|
|
67
|
+
.map((item) => [String(item.date), Math.max(0, Number(item.totalTokens) || 0)]));
|
|
49
68
|
const visibleValues = [];
|
|
50
69
|
const cells = [];
|
|
51
|
-
for (let index = 0; index <
|
|
70
|
+
for (let index = 0; index < weekCount * 7; index += 1) {
|
|
52
71
|
const date = new Date(start);
|
|
53
|
-
date.
|
|
54
|
-
const dateKey =
|
|
55
|
-
const
|
|
56
|
-
const
|
|
57
|
-
|
|
72
|
+
date.setUTCDate(start.getUTCDate() + index);
|
|
73
|
+
const dateKey = usageDateKey(date);
|
|
74
|
+
const outsideYear = date.getUTCFullYear() !== selectedYear;
|
|
75
|
+
const totalTokens = outsideYear ? 0 : usageByDate.get(dateKey) ?? 0;
|
|
76
|
+
const future = !outsideYear && normalizedTodayKey !== null && dateKey > normalizedTodayKey;
|
|
77
|
+
if (!outsideYear && !future && totalTokens > 0) visibleValues.push(totalTokens);
|
|
58
78
|
cells.push({
|
|
59
79
|
date: dateKey,
|
|
60
80
|
totalTokens,
|
|
81
|
+
outsideYear,
|
|
61
82
|
future,
|
|
62
83
|
week: Math.floor(index / 7),
|
|
63
84
|
weekday: index % 7,
|
|
@@ -66,21 +87,13 @@ export function buildUsageCalendar(daily, today = new Date(), weekCount = 53) {
|
|
|
66
87
|
}
|
|
67
88
|
const maximum = Math.max(0, ...visibleValues);
|
|
68
89
|
for (const cell of cells) {
|
|
69
|
-
cell.level = cell.future || cell.totalTokens <= 0 || maximum <= 0
|
|
90
|
+
cell.level = cell.outsideYear || cell.future || cell.totalTokens <= 0 || maximum <= 0
|
|
70
91
|
? 0
|
|
71
92
|
: Math.max(1, Math.min(4, Math.ceil(Math.sqrt(cell.totalTokens / maximum) * 4)));
|
|
72
93
|
}
|
|
73
|
-
const months =
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (week === 0 || previous?.getMonth() !== date.getMonth()) {
|
|
79
|
-
months.push({
|
|
80
|
-
week,
|
|
81
|
-
label: new Intl.DateTimeFormat("zh-CN", { month: "short" }).format(date)
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
return { cells, months, weekCount: Math.max(1, weekCount) };
|
|
94
|
+
const months = Array.from({ length: 12 }, (_, month) => ({
|
|
95
|
+
week: Math.floor((Date.UTC(selectedYear, month, 1) - start.getTime()) / (7 * 24 * 60 * 60_000)),
|
|
96
|
+
label: `${month + 1}月`
|
|
97
|
+
}));
|
|
98
|
+
return { cells, months, weekCount, year: selectedYear };
|
|
86
99
|
}
|
package/dist/public/app.js
CHANGED
|
@@ -2,6 +2,7 @@ import { buildRelationshipGraph, createGalaxyRenderer, normalizeGalaxyFrameRate,
|
|
|
2
2
|
import { formatDateTime, normalizeParagraphSpacing } from "/text-formatting.js?v=20260713-saved-at-seconds";
|
|
3
3
|
import { renderMarkdown } from "/markdown.js?v=20260731-no-external-images-v1";
|
|
4
4
|
import { findAiMention, listAiMentionOptions, mergeAiReferenceScope, userMessageMentionNames } from "/ai-mentions.js?v=20260811-user-message-mentions-v1";
|
|
5
|
+
import { applyAiSkillCommand, findAiSkillCommand, listAiSkillOptions } from "/ai-skill-menu.js?v=20260830-ai-skill-slash-menu-v1";
|
|
5
6
|
import {
|
|
6
7
|
emptyRoleplayScenePin,
|
|
7
8
|
normalizeRoleplayScenePin,
|
|
@@ -31,7 +32,7 @@ import { shouldSendAiPrompt } from "/ai-prompt-keyboard.js?v=20260713-enter-to-s
|
|
|
31
32
|
import { estimateAiMessageTokens, formatAiMessageMeta } from "/ai-message-meta.js?v=20260814-ai-model-lock-v1";
|
|
32
33
|
import { createStreamTypewriter, createStreamTypewriterSpeedController } from "/stream-typewriter.js?v=20260818-ai-agent-turn-process-v1";
|
|
33
34
|
import { assertAiStreamCompleted, readAiEventStream } from "/ai-stream-protocol.js?v=20260812-ai-stream-complete-v1";
|
|
34
|
-
import { buildUsageCalendar, formatCacheHitRate, formatEstimatedCost, formatTokenCount } from "/ai-usage.js?v=
|
|
35
|
+
import { buildUsageCalendar, formatCacheHitRate, formatEstimatedCost, formatTokenCount, usageCalendarYears } from "/ai-usage.js?v=20260830-ai-usage-year-v1";
|
|
35
36
|
import { formatAiMessageTime } from "/ai-message-time.js?v=20260801-month-day-time";
|
|
36
37
|
import { formatAiContextUsagePercent, formatAiContextUsageTooltip, mergeAiContextUsage, normalizeAiContextTokenDistribution, resolveAiContextUsage } from "/ai-context-meter.js?v=20260828-context-output-usage-v1";
|
|
37
38
|
import { isPhoneClient } from "/phone-client.js?v=20260819-phone-client-v1";
|
|
@@ -1526,6 +1527,7 @@ let moduleNavExpanded = false;
|
|
|
1526
1527
|
const chapterAutoSaveDelay = 800;
|
|
1527
1528
|
const chapterLineInputRenderDelay = 32;
|
|
1528
1529
|
let aiMentionMatch = null;
|
|
1530
|
+
let aiSkillMatch = null;
|
|
1529
1531
|
let aiMentionRange = null;
|
|
1530
1532
|
let aiMentionActiveIndex = -1;
|
|
1531
1533
|
let settingsReturnContext = null;
|
|
@@ -2705,7 +2707,7 @@ function resetAiFeed(
|
|
|
2705
2707
|
const roleplayUserName = roleplayUserCharacter?.name;
|
|
2706
2708
|
feed.innerHTML = roleplayName
|
|
2707
2709
|
? `<div class="assistant-message"><span class="message-heading"><span>${esc(roleplayName)}</span></span><div class="message-body"><p>正在扮演 ${esc(roleplayName)}。${roleplayUserName ? `你将以 ${esc(roleplayUserName)} 的身份与我互动。` : "我可以通过角色卡、人物关系、知情设定和故事正文回答。"}</p></div></div>`
|
|
2708
|
-
: '<div class="assistant-message"><span class="message-heading"><span>助手</span></span><div class="message-body"><p
|
|
2710
|
+
: '<div class="assistant-message"><span class="message-heading"><span>助手</span></span><div class="message-body"><p>选择章节和模型后即可开始问答;所有引用都基于已保存正文。</p></div></div>';
|
|
2709
2711
|
}
|
|
2710
2712
|
|
|
2711
2713
|
function aiAssistantLabel(suffix = "", roleplayCharacter = state.aiRoleplayCharacter) {
|
|
@@ -5030,6 +5032,7 @@ function aiPromptTextBoundary(prompt, offset) {
|
|
|
5030
5032
|
|
|
5031
5033
|
function hideAiMentionMenu() {
|
|
5032
5034
|
aiMentionMatch = null;
|
|
5035
|
+
aiSkillMatch = null;
|
|
5033
5036
|
aiMentionRange = null;
|
|
5034
5037
|
aiMentionActiveIndex = -1;
|
|
5035
5038
|
const prompt = $("#ai-prompt");
|
|
@@ -5086,21 +5089,37 @@ function syncAiReferencesWithPrompt() {
|
|
|
5086
5089
|
function updateAiMentionMenu() {
|
|
5087
5090
|
syncAiReferencesWithPrompt();
|
|
5088
5091
|
const prompt = $("#ai-prompt");
|
|
5089
|
-
const
|
|
5090
|
-
|
|
5092
|
+
const textBeforeCursor = aiPromptTextBeforeCursor();
|
|
5093
|
+
const skillMatch = $("#ai-task").value === "roleplay" ? null : findAiSkillCommand(textBeforeCursor);
|
|
5094
|
+
const mentionMatch = skillMatch ? null : findAiMention(textBeforeCursor);
|
|
5095
|
+
if (!skillMatch && !mentionMatch) return hideAiMentionMenu();
|
|
5091
5096
|
const selection = window.getSelection();
|
|
5092
5097
|
if (!selection?.rangeCount || !prompt.contains(selection.anchorNode)) return hideAiMentionMenu();
|
|
5093
|
-
|
|
5098
|
+
aiSkillMatch = skillMatch;
|
|
5099
|
+
aiMentionMatch = mentionMatch;
|
|
5094
5100
|
aiMentionRange = selection.getRangeAt(0).cloneRange();
|
|
5095
5101
|
const menu = $("#ai-mention-menu");
|
|
5102
|
+
if (skillMatch) {
|
|
5103
|
+
const options = listAiSkillOptions(skillMatch.query);
|
|
5104
|
+
aiMentionActiveIndex = -1;
|
|
5105
|
+
prompt.removeAttribute("aria-activedescendant");
|
|
5106
|
+
menu.setAttribute("aria-label", "选择写作 Skill");
|
|
5107
|
+
menu.innerHTML = options.length
|
|
5108
|
+
? options.map((item, index) => `<button id="ai-skill-option-${index}" class="ai-mention-option ai-skill-option" type="button" role="option" aria-selected="false" tabindex="-1" data-ai-skill-name="${esc(item.name)}"><small>Skill</small><span><strong>/${esc(item.name)}</strong><em>${esc(item.label)} · ${esc(item.description)}</em></span></button>`).join("")
|
|
5109
|
+
: '<p class="ai-mention-empty">没有匹配的写作 Skill</p>';
|
|
5110
|
+
menu.classList.remove("hidden");
|
|
5111
|
+
prompt.setAttribute("aria-expanded", "true");
|
|
5112
|
+
return;
|
|
5113
|
+
}
|
|
5096
5114
|
const chapters = state.work?.volumes.flatMap((volume) => volume.chapters.map((chapter) => ({
|
|
5097
5115
|
...chapter,
|
|
5098
5116
|
volumeTitle: volume.title
|
|
5099
5117
|
}))) ?? [];
|
|
5100
|
-
const options = listAiMentionOptions(state.characters, state.settings, chapters,
|
|
5118
|
+
const options = listAiMentionOptions(state.characters, state.settings, chapters, mentionMatch.query)
|
|
5101
5119
|
.filter((item) => item.kind !== "context-settings" || $("#ai-task").value !== "roleplay");
|
|
5102
5120
|
aiMentionActiveIndex = -1;
|
|
5103
5121
|
prompt.removeAttribute("aria-activedescendant");
|
|
5122
|
+
menu.setAttribute("aria-label", "引用角色、设定、章节或上下文能力");
|
|
5104
5123
|
menu.innerHTML = options.length
|
|
5105
5124
|
? options.map((item, index) => `<button id="ai-mention-option-${index}" class="ai-mention-option" type="button" role="option" aria-selected="false" tabindex="-1" data-ai-reference-kind="${esc(item.kind)}" data-ai-reference-id="${esc(item.id)}" data-ai-reference-name="${esc(item.name)}"><small>${esc(item.kindLabel)}</small><strong>${esc(item.name)}</strong></button>`).join("")
|
|
5106
5125
|
: '<p class="ai-mention-empty">没有匹配的角色、设定、章节或上下文能力</p>';
|
|
@@ -5141,6 +5160,30 @@ function selectAiMention(button) {
|
|
|
5141
5160
|
hideAiMentionMenu();
|
|
5142
5161
|
}
|
|
5143
5162
|
|
|
5163
|
+
function selectAiSkill(button) {
|
|
5164
|
+
if (!aiSkillMatch || !aiMentionRange) return;
|
|
5165
|
+
const prompt = $("#ai-prompt");
|
|
5166
|
+
const cursorText = aiPromptTextFromRange(aiMentionRange, prompt);
|
|
5167
|
+
const localSkill = findAiSkillCommand(cursorText);
|
|
5168
|
+
if (!localSkill) return hideAiMentionMenu();
|
|
5169
|
+
const applied = applyAiSkillCommand(cursorText, localSkill, button.dataset.aiSkillName);
|
|
5170
|
+
const range = document.createRange();
|
|
5171
|
+
const startBoundary = aiPromptTextBoundary(prompt, localSkill.start);
|
|
5172
|
+
const endBoundary = aiPromptTextBoundary(prompt, cursorText.length);
|
|
5173
|
+
range.setStart(startBoundary.node, startBoundary.offset);
|
|
5174
|
+
range.setEnd(endBoundary.node, endBoundary.offset);
|
|
5175
|
+
range.deleteContents();
|
|
5176
|
+
const command = document.createTextNode(`${applied.command} `);
|
|
5177
|
+
range.insertNode(command);
|
|
5178
|
+
const selection = window.getSelection();
|
|
5179
|
+
selection?.removeAllRanges();
|
|
5180
|
+
range.setStartAfter(command);
|
|
5181
|
+
range.collapse(true);
|
|
5182
|
+
selection?.addRange(range);
|
|
5183
|
+
prompt.focus();
|
|
5184
|
+
hideAiMentionMenu();
|
|
5185
|
+
}
|
|
5186
|
+
|
|
5144
5187
|
function addSelectedLinesAsCitation() {
|
|
5145
5188
|
if (!state.chapter || !chapterLineSelection) return;
|
|
5146
5189
|
const selection = selectedChapterLinePayload(chapterLineSelection.start, chapterLineSelection.end);
|
|
@@ -13301,20 +13344,22 @@ async function renderPlatformAiConfig() {
|
|
|
13301
13344
|
}
|
|
13302
13345
|
|
|
13303
13346
|
function tokenUsageDateLabel(date) {
|
|
13347
|
+
const [year, month, day] = String(date).split("-").map(Number);
|
|
13304
13348
|
return new Intl.DateTimeFormat("zh-CN", {
|
|
13305
13349
|
year: "numeric",
|
|
13306
13350
|
month: "short",
|
|
13307
13351
|
day: "numeric",
|
|
13308
|
-
weekday: "short"
|
|
13309
|
-
|
|
13352
|
+
weekday: "short",
|
|
13353
|
+
timeZone: "UTC"
|
|
13354
|
+
}).format(new Date(Date.UTC(year, month - 1, day)));
|
|
13310
13355
|
}
|
|
13311
13356
|
|
|
13312
|
-
function tokenUsageCalendarMarkup(daily) {
|
|
13313
|
-
const calendar = buildUsageCalendar(daily);
|
|
13357
|
+
function tokenUsageCalendarMarkup(daily, year, serverDate) {
|
|
13358
|
+
const calendar = buildUsageCalendar(daily, year, serverDate);
|
|
13314
13359
|
const cells = calendar.cells.map((cell) => {
|
|
13315
13360
|
const label = `${tokenUsageDateLabel(cell.date)}:${Number(cell.totalTokens).toLocaleString("zh-CN")} Token`;
|
|
13316
|
-
return cell.future
|
|
13317
|
-
? `<span class="usage-calendar-cell is-future" data-level="${cell.level}" role="gridcell" aria-disabled="true"></span>`
|
|
13361
|
+
return cell.outsideYear || cell.future
|
|
13362
|
+
? `<span class="usage-calendar-cell ${cell.outsideYear ? "is-outside-year" : "is-future"}" data-level="${cell.level}" role="gridcell" aria-disabled="true"></span>`
|
|
13318
13363
|
: `<button class="usage-calendar-cell" type="button" data-level="${cell.level}" data-usage-calendar-label="${esc(label)}" role="gridcell" aria-label="${esc(label)}"></button>`;
|
|
13319
13364
|
}).join("");
|
|
13320
13365
|
const months = calendar.months.map((month) => `<span style="grid-column:${month.week + 1}">${esc(month.label)}</span>`).join("");
|
|
@@ -13324,7 +13369,7 @@ function tokenUsageCalendarMarkup(daily) {
|
|
|
13324
13369
|
<div class="usage-calendar-months" aria-hidden="true">${months}</div>
|
|
13325
13370
|
<div class="usage-calendar-body">
|
|
13326
13371
|
<div class="usage-calendar-weekdays" aria-hidden="true"><span>一</span><span>三</span><span>五</span></div>
|
|
13327
|
-
<div class="usage-calendar-grid" role="grid" aria-label="
|
|
13372
|
+
<div class="usage-calendar-grid" role="grid" aria-label="${calendar.year} 年每日 Token 用量">${cells}</div>
|
|
13328
13373
|
</div>
|
|
13329
13374
|
</div>
|
|
13330
13375
|
</div>
|
|
@@ -13333,6 +13378,21 @@ function tokenUsageCalendarMarkup(daily) {
|
|
|
13333
13378
|
<div class="usage-calendar-legend"><span>少</span>${[0, 1, 2, 3, 4].map((level) => `<i data-level="${level}" aria-hidden="true"></i>`).join("")}<span>多</span></div>`;
|
|
13334
13379
|
}
|
|
13335
13380
|
|
|
13381
|
+
function bindUsageCalendar(root, usage) {
|
|
13382
|
+
root.querySelectorAll("[data-usage-calendar-year]").forEach((select) => {
|
|
13383
|
+
select.addEventListener("change", () => {
|
|
13384
|
+
const section = select.closest(".usage-calendar-section");
|
|
13385
|
+
const calendarHost = section?.querySelector("[data-usage-calendar-host]");
|
|
13386
|
+
if (!calendarHost) return;
|
|
13387
|
+
calendarHost.innerHTML = tokenUsageCalendarMarkup(usage?.daily, Number(select.value), usage?.serverDate);
|
|
13388
|
+
bindUsageCalendarInteractions(calendarHost);
|
|
13389
|
+
scrollUsageCalendarsToLatest(calendarHost);
|
|
13390
|
+
});
|
|
13391
|
+
});
|
|
13392
|
+
bindUsageCalendarInteractions(root);
|
|
13393
|
+
scrollUsageCalendarsToLatest(root);
|
|
13394
|
+
}
|
|
13395
|
+
|
|
13336
13396
|
function bindUsageCalendarInteractions(root) {
|
|
13337
13397
|
root.querySelectorAll(".usage-calendar-widget").forEach((widget) => {
|
|
13338
13398
|
const tooltip = widget.querySelector(".usage-calendar-tooltip");
|
|
@@ -13556,6 +13616,15 @@ function tokenUsageOverviewMarkup(usage, { title, description, showWorks = false
|
|
|
13556
13616
|
const callTypeUsage = (Array.isArray(usage?.callTypes) ? usage.callTypes : [])
|
|
13557
13617
|
.map((item) => `<span class="usage-call-type-chip"><strong>${esc(callTypeLabels[item.callType] ?? item.callType)}</strong><span>${esc(formatTokenCount(item.totalTokens))} Token · ${Number(item.requestCount || 0).toLocaleString("zh-CN")} 次</span></span>`)
|
|
13558
13618
|
.join("");
|
|
13619
|
+
const calendarYears = usageCalendarYears(usage?.daily);
|
|
13620
|
+
const selectedCalendarYear = calendarYears[0] ?? null;
|
|
13621
|
+
const usageTimezone = String(usage?.timezone || "服务器本地时区");
|
|
13622
|
+
const calendarYearSelect = selectedCalendarYear === null
|
|
13623
|
+
? ""
|
|
13624
|
+
: `<select class="usage-calendar-year-select" data-usage-calendar-year aria-label="每日用量年份">${calendarYears.map((year) => `<option value="${year}">${year} 年</option>`).join("")}</select>`;
|
|
13625
|
+
const calendarMarkup = selectedCalendarYear === null
|
|
13626
|
+
? '<p class="usage-calendar-empty">尚无每日 Token 用量记录。</p>'
|
|
13627
|
+
: tokenUsageCalendarMarkup(usage?.daily, selectedCalendarYear, usage?.serverDate);
|
|
13559
13628
|
return `<section class="usage-overview" aria-labelledby="${showWorks ? "platform-usage-overview-title" : "work-usage-overview-title"}">
|
|
13560
13629
|
<div class="config-section-header usage-overview-header"><div><h2 id="${showWorks ? "platform-usage-overview-title" : "work-usage-overview-title"}">${esc(title || "Token 用量")}</h2><p>${esc(description || "统计该范围内的全部 AI 调用。")}</p></div><button class="ghost-button usage-details-button" type="button" data-token-usage-details aria-haspopup="dialog" aria-expanded="false" aria-controls="token-usage-details-toast">详细数据</button></div>
|
|
13561
13630
|
<div class="usage-stat-grid">
|
|
@@ -13567,8 +13636,8 @@ function tokenUsageOverviewMarkup(usage, { title, description, showWorks = false
|
|
|
13567
13636
|
<p class="usage-measurement-note">${requestCount.toLocaleString("zh-CN")} 次有用量记录的调用。${esc(estimateNote)} 有 ${unpricedModelCount.toLocaleString("zh-CN")} 个模型在价格表中未找到对应价格</p>
|
|
13568
13637
|
${callTypeUsage ? `<div class="usage-call-types" aria-label="按调用类型区分的 Token 用量">${callTypeUsage}</div>` : ""}
|
|
13569
13638
|
<section class="usage-calendar-section" aria-labelledby="${showWorks ? "platform-usage-calendar-title" : "work-usage-calendar-title"}">
|
|
13570
|
-
<header><div><h3 id="${showWorks ? "platform-usage-calendar-title" : "work-usage-calendar-title"}">每日用量</h3><p>GitHub
|
|
13571
|
-
|
|
13639
|
+
<header><div><h3 id="${showWorks ? "platform-usage-calendar-title" : "work-usage-calendar-title"}">每日用量</h3><p>GitHub 风格网格按服务器时区(${esc(usageTimezone)})分年展示;颜色越深,当天消耗越高。</p></div>${calendarYearSelect}</header>
|
|
13640
|
+
<div data-usage-calendar-host>${calendarMarkup}</div>
|
|
13572
13641
|
</section>
|
|
13573
13642
|
${showWorks ? `<section class="usage-work-section" aria-labelledby="usage-work-title"><header><div><h3 id="usage-work-title">各作品用量</h3><p>按 Token 总消耗从高到低排列,包含尚未使用 AI 的作品。</p></div></header><div class="usage-work-table-scroll"><table class="usage-work-table"><thead><tr><th>作品</th><th>总消耗</th><th>输入</th><th>输出</th><th>缓存命中率</th><th>调用</th></tr></thead><tbody>${workRows || '<tr><td colspan="6">还没有作品用量记录。</td></tr>'}</tbody></table></div></section>` : ""}
|
|
13574
13643
|
</section>`;
|
|
@@ -13577,16 +13646,14 @@ function tokenUsageOverviewMarkup(usage, { title, description, showWorks = false
|
|
|
13577
13646
|
async function renderPlatformTokenUsage() {
|
|
13578
13647
|
const host = $("#platform-usage-content");
|
|
13579
13648
|
host.innerHTML = '<div class="empty-state">正在汇总 Token 用量……</div>';
|
|
13580
|
-
const
|
|
13581
|
-
const usage = await api(`/api/platform/ai/usage?timezoneOffset=${timezoneOffset}`);
|
|
13649
|
+
const usage = await api("/api/platform/ai/usage");
|
|
13582
13650
|
host.innerHTML = tokenUsageOverviewMarkup(usage, {
|
|
13583
13651
|
title: "项目累计用量",
|
|
13584
13652
|
description: "汇总所有作品迄今产生的输入与输出 Token;缓存命中率仅基于供应商返回了缓存明细的调用。",
|
|
13585
13653
|
showWorks: true
|
|
13586
13654
|
});
|
|
13587
13655
|
bindTokenUsageDetails(host, usage, "项目累计用量");
|
|
13588
|
-
|
|
13589
|
-
scrollUsageCalendarsToLatest(host);
|
|
13656
|
+
bindUsageCalendar(host, usage);
|
|
13590
13657
|
}
|
|
13591
13658
|
|
|
13592
13659
|
async function renderBookAiSettings() {
|
|
@@ -13606,7 +13673,7 @@ async function renderBookAiSettings() {
|
|
|
13606
13673
|
moduleApi("ai-settings", `/api/works/${state.work.id}/task-defaults`),
|
|
13607
13674
|
moduleApi("ai-settings", `/api/works/${state.work.id}/ai-settings/relationship-search-index`),
|
|
13608
13675
|
moduleApi("ai-settings", `/api/works/${state.work.id}/ai-settings/semantic-search-index`),
|
|
13609
|
-
moduleApi("ai-settings", `/api/works/${state.work.id}/ai-settings/usage
|
|
13676
|
+
moduleApi("ai-settings", `/api/works/${state.work.id}/ai-settings/usage`),
|
|
13610
13677
|
moduleApi("ai-settings", "/api/platform/ai/protocols"),
|
|
13611
13678
|
// 可写工具开关独立于 ai-settings 存储;加载失败时仍可展示其余配置。
|
|
13612
13679
|
api(`/api/works/${state.work.id}/ai/tools`).catch(() => null),
|
|
@@ -13681,8 +13748,7 @@ async function renderBookAiSettings() {
|
|
|
13681
13748
|
if (section.querySelector("h2")?.textContent === "Agent 工具调用上限") section.id = "agent-tool-call-limit-settings";
|
|
13682
13749
|
});
|
|
13683
13750
|
host.insertAdjacentHTML("beforeend", `<section class="config-section"><div class="config-section-header"><div><h2>AI 可写工具</h2><p>默认全部关闭:逐项开启后,侧边栏 AI 才能在对应模块提交修改计划。计划只包含操作描述与 AI 简述;确认前系统会按当前数据库生成字段级明细(含修改前后值),执行时整体原子完成并再次校验权限、开关与目标版本,全程可在「AI 操作审批中心」追溯。AI 不能删除任何条目,也不能改写正文。</p></div><div class="card-actions"><button id="open-ai-approval-center-from-settings" class="ghost-button" type="button">打开 AI 操作审批中心</button></div></div><div class="ai-agent-tools ai-write-tools">${AI_WRITE_TOOLS_META.map((tool) => `<label><input name="ai-write-tool" type="checkbox" value="${esc(tool.id)}" ${writeToolsState?.[tool.id] === true ? "checked" : ""}><span><strong>${esc(tool.label)}</strong><small>${esc(tool.description)}</small></span></label>`).join("")}</div><p class="usage-measurement-note">${writeTools ? `当前单次审批最多 ${writeToolsMaxOperations} 个操作,可通过环境变量 AI_WRITE_PLAN_MAX_OPERATIONS 调整。` : "工具开关状态暂时无法加载,显示的勾选可能不是最新值。"}</p><div class="card-actions"><button id="save-ai-write-tools" class="ghost-button config-save-button" type="button">保存开关设置</button></div></section>`);
|
|
13684
|
-
|
|
13685
|
-
scrollUsageCalendarsToLatest(host);
|
|
13751
|
+
bindUsageCalendar(host, usage);
|
|
13686
13752
|
host.querySelector('input[name="agent-tool"][value="search_story_entities"]').closest("label").insertAdjacentHTML(
|
|
13687
13753
|
"beforebegin",
|
|
13688
13754
|
`<label><input name="agent-tool" type="checkbox" value="grep" ${agentTools.has("grep") ? "checked" : ""}><span><strong>查询正文关键字</strong><small>从段落索引查询关键字,默认返回前 20 条完整段落和章节信息。</small></span></label>`
|
|
@@ -13693,7 +13759,7 @@ async function renderBookAiSettings() {
|
|
|
13693
13759
|
);
|
|
13694
13760
|
host.querySelector('input[name="agent-tool"][value="read_character_sections"]').closest("label").insertAdjacentHTML(
|
|
13695
13761
|
"afterend",
|
|
13696
|
-
`<label><input name="agent-tool" type="checkbox" value="semantic_search_story" ${agentTools.has("semantic_search_story") ? "checked" : ""} ${settings.semanticSearchEnabled ? "" : "disabled"}><span><strong
|
|
13762
|
+
`<label><input name="agent-tool" type="checkbox" value="semantic_search_story" ${agentTools.has("semantic_search_story") ? "checked" : ""} ${settings.semanticSearchEnabled ? "" : "disabled"}><span><strong>语义检索作品原文(RAG)</strong><small>允许 Agent 显式调用 semantic_search_story;只影响保存后新建的对话,普通消息不会自动检索。</small></span></label>`
|
|
13697
13763
|
);
|
|
13698
13764
|
host.querySelector(".ai-agent-tools").insertAdjacentHTML(
|
|
13699
13765
|
"beforeend",
|
|
@@ -20333,7 +20399,9 @@ $("#module-create-button").addEventListener("click", () => ({ drafts: openDraftD
|
|
|
20333
20399
|
$("#ai-prompt").addEventListener("input", async () => {
|
|
20334
20400
|
updateAiMentionMenu();
|
|
20335
20401
|
setAiContextMeter(null);
|
|
20336
|
-
|
|
20402
|
+
const textBeforeCursor = aiPromptTextBeforeCursor();
|
|
20403
|
+
if ($("#ai-task").value !== "roleplay" && findAiSkillCommand(textBeforeCursor)) return;
|
|
20404
|
+
if (!findAiMention(textBeforeCursor)) return;
|
|
20337
20405
|
try {
|
|
20338
20406
|
await ensureAiReferencesLoaded();
|
|
20339
20407
|
updateAiMentionMenu();
|
|
@@ -20463,6 +20531,7 @@ $("#ai-task").addEventListener("change", async (event) => {
|
|
|
20463
20531
|
}
|
|
20464
20532
|
}
|
|
20465
20533
|
setAiContextMeter(null);
|
|
20534
|
+
updateAiMentionMenu();
|
|
20466
20535
|
});
|
|
20467
20536
|
$("#ai-scope").addEventListener("change", (event) => {
|
|
20468
20537
|
if (state.aiPromptSent) {
|
|
@@ -20473,6 +20542,8 @@ $("#ai-scope").addEventListener("change", (event) => {
|
|
|
20473
20542
|
setAiContextMeter(null);
|
|
20474
20543
|
});
|
|
20475
20544
|
$("#ai-mention-menu").addEventListener("click", (event) => {
|
|
20545
|
+
const skillButton = event.target.closest("[data-ai-skill-name]");
|
|
20546
|
+
if (skillButton) return selectAiSkill(skillButton);
|
|
20476
20547
|
const button = event.target.closest("[data-ai-reference-id]");
|
|
20477
20548
|
if (button) selectAiMention(button);
|
|
20478
20549
|
});
|
|
@@ -21016,7 +21087,8 @@ $("#ai-prompt").addEventListener("keydown", (event) => {
|
|
|
21016
21087
|
const activeOption = $("#ai-mention-menu").querySelector('[role="option"][aria-selected="true"]');
|
|
21017
21088
|
if (activeOption) {
|
|
21018
21089
|
event.preventDefault();
|
|
21019
|
-
|
|
21090
|
+
if (activeOption.dataset.aiSkillName) selectAiSkill(activeOption);
|
|
21091
|
+
else selectAiMention(activeOption);
|
|
21020
21092
|
return;
|
|
21021
21093
|
}
|
|
21022
21094
|
}
|
package/dist/public/index.html
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
<script src="/theme-init.js?v=20260827-reader-prefetch-v1"></script>
|
|
10
10
|
<link rel="icon" href="/icon.svg?v=20260712" type="image/svg+xml">
|
|
11
11
|
<link rel="manifest" href="/site.webmanifest">
|
|
12
|
-
<link rel="stylesheet" href="/styles.css?v=20260816-task-scope-volume-collapse-v2&feature=ai-tool-call-copy-feedback-v2&feature=ai-send-control-v3&feature=character-gender-v1&feature=character-filter-state-v1&feature=relationship-canvas-scale-v1&feature=relationship-table-scroll-v1&feature=galaxy-compact-controls-v2&feature=galaxy-motion-mode-v2&feature=chapter-search-replace-v3&feature=task-auto-run-ring-center-v3&feature=character-relationship-delete-v1&feature=ai-assistant-workspace-v2&feature=mobile-module-tab-position-v1&feature=volume-detail-icon-v1&feature=editor-actions-flow-v1&feature=reader-controls-subpanel-v1&feature=reader-focus-ring-v1&feature=ai-message-actions-v1&feature=assistant-responsive-navigation-v2&feature=ai-composer-square-controls-v2&feature=annotation-line-counts-v1&feature=chapter-comment-filters-v1&feature=line-number-gutter-fill-v1&feature=ai-relationship-roleplay-v1&feature=ai-model-picker-v1&feature=markdown-word-count-five-digit-v2&feature=ai-stream-character-count-stable-v1&feature=ai-stream-character-count-five-digit-v1&feature=annotation-marker-offset-v1&feature=mobile-ai-entry-hidden-v1&feature=phone-client-entry-v1&feature=ai-stream-idle-timeout-v1&feature=ai-user-message-width-v2&feature=ai-chat-image-attachments-v9&feature=ai-message-image-preview-v1&feature=ai-thinking-scroll-v2&feature=toast-click-dismiss-v3&feature=character-avatar-v6&feature=admin-ai-conversations-v1&feature=ai-usage-pricing-v1&feature=ai-usage-pricing-badge-v2&feature=ai-token-quota-positive-v5&feature=ai-token-usage-details-v1&feature=ai-token-usage-details-centered-v1&feature=ai-stream-connection-seconds-v1&feature=ai-token-usage-estimated-price-v1&feature=record-favorites-v1&feature=entity-detail-favorites-v1&feature=entity-pin-v1&feature=character-persona-summary-v1&feature=ai-roleplay-scene-turn-v1&feature=admin-account-identity-v2&feature=task-detail-failure-orange-v1&feature=character-card-header-alignment-v6&feature=character-card-title-fit-v2&feature=book-import-progress-v1&feature=editor-toolbar-compact-v2&feature=reader-first-frame-v1&feature=auth-compact-v2&feature=editor-preview-toggle-v2&feature=setting-title-chrome-v2&feature=entity-pin-icon-center-v1&feature=chapter-center-bottom-space-v1&feature=work-editor-preferences-v1&feature=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">
|
|
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&feature=ai-settings-textarea-font-v1&feature=ai-usage-stat-display-v1&feature=ai-usage-year-v1&feature=ai-skill-slash-menu-v1">
|
|
13
13
|
</head>
|
|
14
14
|
<body class="auth-pending">
|
|
15
15
|
<section id="auth-view" class="auth-view hidden" aria-labelledby="auth-title">
|
|
@@ -351,7 +351,7 @@
|
|
|
351
351
|
<div id="ai-feed" class="ai-feed" role="tabpanel">
|
|
352
352
|
<div class="assistant-message">
|
|
353
353
|
<span>助手</span>
|
|
354
|
-
<div class="message-body"><p
|
|
354
|
+
<div class="message-body"><p>选择章节和模型后即可开始问答;所有引用都基于已保存正文。</p></div>
|
|
355
355
|
</div>
|
|
356
356
|
</div>
|
|
357
357
|
</div>
|
|
@@ -1322,6 +1322,6 @@
|
|
|
1322
1322
|
</dialog>
|
|
1323
1323
|
|
|
1324
1324
|
<div id="auth-loading" class="auth-loading" role="status" aria-label="正在载入工作台"></div>
|
|
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>
|
|
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&feature=ai-usage-stat-display-v1&feature=ai-usage-year-v1&feature=semantic-search-rag-label-v1&feature=ai-skill-slash-menu-v1"></script>
|
|
1326
1326
|
</body>
|
|
1327
1327
|
</html>
|
package/dist/public/styles.css
CHANGED
|
@@ -697,14 +697,35 @@ body.is-panel-resizing { cursor: col-resize; user-select: none; }
|
|
|
697
697
|
.usage-stat > span { color: var(--muted); font-size: 10px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; }
|
|
698
698
|
.usage-stat-label > span:first-child { min-width: 0; overflow: hidden; color: var(--muted); font-size: 10px; font-weight: 600; letter-spacing: .06em; text-overflow: ellipsis; text-transform: uppercase; white-space: nowrap; }
|
|
699
699
|
.usage-cost-bubble { flex: 0 0 auto; max-width: 100%; padding: 6px 10px; overflow: hidden; border: 1px solid color-mix(in srgb, var(--accent) 62%, var(--line)); border-radius: 999px; background: color-mix(in srgb, var(--accent) 22%, var(--surface)); box-shadow: 0 4px 12px color-mix(in srgb, var(--accent) 14%, transparent); color: var(--accent-dark); font: 700 10px/1.1 var(--font-latin), var(--font-cjk), monospace; letter-spacing: .02em; text-overflow: ellipsis; white-space: nowrap; }
|
|
700
|
-
.usage-stat strong { margin: 10px 0 5px; overflow: hidden; color: var(--ink); font: 600 clamp(
|
|
700
|
+
.usage-stat strong { margin: 10px 0 5px; overflow: hidden; color: var(--ink); font: 600 clamp(21px, 2.5vw, 30px)/1 var(--font-latin), ui-monospace, monospace; letter-spacing: -.035em; text-overflow: ellipsis; white-space: nowrap; }
|
|
701
701
|
.usage-stat.is-primary strong { color: var(--accent-dark); }
|
|
702
702
|
.usage-stat small { overflow-wrap: anywhere; color: var(--muted); font-size: 9px; line-height: 1.45; }
|
|
703
703
|
.usage-measurement-note { margin: 11px 0 0; color: var(--muted); font-size: 10px; line-height: 1.6; }
|
|
704
704
|
.usage-calendar-section, .usage-work-section { margin-top: 26px; padding-top: 22px; border-top: 1px solid var(--line); }
|
|
705
705
|
.usage-calendar-section > header, .usage-work-section > header { display: flex; align-items: end; justify-content: space-between; gap: 18px; margin-bottom: 14px; }
|
|
706
|
+
.usage-calendar-section > header { align-items: flex-start; }
|
|
706
707
|
.usage-calendar-section h3, .usage-work-section h3 { margin: 0 0 4px; font-size: 15px; font-weight: 650; }
|
|
707
708
|
.usage-calendar-section header p, .usage-work-section header p { margin: 0; color: var(--muted); font-size: 10px; }
|
|
709
|
+
.usage-calendar-year-select {
|
|
710
|
+
appearance: none;
|
|
711
|
+
-webkit-appearance: none;
|
|
712
|
+
flex: 0 0 auto;
|
|
713
|
+
min-width: 78px;
|
|
714
|
+
min-height: 30px;
|
|
715
|
+
padding: 5px 27px 5px 9px;
|
|
716
|
+
border: 1px solid var(--line);
|
|
717
|
+
border-radius: 4px;
|
|
718
|
+
background-color: var(--surface);
|
|
719
|
+
background-image: linear-gradient(45deg, transparent 50%, var(--muted) 50%), linear-gradient(135deg, var(--muted) 50%, transparent 50%);
|
|
720
|
+
background-position: right 13px center, right 9px center;
|
|
721
|
+
background-repeat: no-repeat;
|
|
722
|
+
background-size: 4px 4px, 4px 4px;
|
|
723
|
+
color: var(--ink);
|
|
724
|
+
font: 10px/1.2 var(--font-latin), var(--font-cjk), monospace;
|
|
725
|
+
cursor: pointer;
|
|
726
|
+
}
|
|
727
|
+
.usage-calendar-year-select:focus-visible { border-color: var(--accent); outline: 2px solid color-mix(in srgb, var(--accent) 18%, transparent); outline-offset: 1px; }
|
|
728
|
+
.usage-calendar-empty { margin: 0; padding: 24px 18px; border: 1px dashed var(--line); border-radius: 6px; color: var(--muted); font-size: 10px; text-align: center; }
|
|
708
729
|
.usage-calendar-widget { position: relative; }
|
|
709
730
|
.usage-calendar-scroll { max-width: 100%; padding: 10px 12px 12px; overflow-x: auto; border: 1px solid var(--line); border-radius: 6px; background: var(--surface); }
|
|
710
731
|
.usage-calendar-scroll:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
@@ -738,8 +759,8 @@ body.is-panel-resizing { cursor: col-resize; user-select: none; }
|
|
|
738
759
|
.usage-calendar-cell[data-level="2"], .usage-calendar-legend i[data-level="2"] { border-color: color-mix(in srgb, var(--accent) 46%, var(--line)); background: color-mix(in srgb, var(--accent) 42%, var(--surface)); }
|
|
739
760
|
.usage-calendar-cell[data-level="3"], .usage-calendar-legend i[data-level="3"] { border-color: color-mix(in srgb, var(--accent) 72%, var(--line)); background: color-mix(in srgb, var(--accent) 70%, var(--surface)); }
|
|
740
761
|
.usage-calendar-cell[data-level="4"], .usage-calendar-legend i[data-level="4"] { border-color: var(--accent-dark); background: var(--accent-dark); }
|
|
741
|
-
.usage-calendar-cell.is-future { border-color: transparent; background: transparent; cursor: default; }
|
|
742
|
-
.usage-calendar-cell:hover, .usage-calendar-cell:focus-visible { position: relative; z-index: 1; border-color: var(--ink); outline: 2px solid color-mix(in srgb, var(--ink) 48%, transparent); outline-offset: 1px; }
|
|
762
|
+
.usage-calendar-cell.is-future, .usage-calendar-cell.is-outside-year { border-color: transparent; background: transparent; cursor: default; }
|
|
763
|
+
button.usage-calendar-cell:hover, button.usage-calendar-cell:focus-visible { position: relative; z-index: 1; border-color: var(--ink); outline: 2px solid color-mix(in srgb, var(--ink) 48%, transparent); outline-offset: 1px; }
|
|
743
764
|
.usage-calendar-tooltip { position: absolute; z-index: 4; width: max-content; max-width: min(280px, calc(100% - 16px)); padding: 7px 9px; border: 1px solid color-mix(in srgb, var(--ink) 24%, transparent); border-radius: 5px; background: color-mix(in srgb, var(--ink) 94%, transparent); box-shadow: 0 8px 22px rgba(0,0,0,.22); color: var(--panel); font: 10px/1.45 var(--font-latin), var(--font-cjk), monospace; overflow-wrap: anywhere; text-align: center; pointer-events: none; transform: translate(-50%, -100%); }
|
|
744
765
|
.usage-calendar-tooltip[data-placement="bottom"] { transform: translateX(-50%); }
|
|
745
766
|
.usage-calendar-tooltip[hidden] { display: none; }
|
|
@@ -3156,6 +3177,8 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
3156
3177
|
.ai-mention-option:hover, .ai-mention-option:focus-visible, .ai-mention-option.is-active { background: var(--paper-deep); }
|
|
3157
3178
|
.ai-mention-option small { color: var(--accent); font-size: 8px; }
|
|
3158
3179
|
.ai-mention-option strong { overflow: hidden; font-size: 10px; font-weight: 500; text-overflow: ellipsis; white-space: nowrap; }
|
|
3180
|
+
.ai-skill-option > span { display: grid; gap: 2px; min-width: 0; }
|
|
3181
|
+
.ai-skill-option em { color: var(--muted); font-size: 9px; font-style: normal; line-height: 1.35; overflow-wrap: anywhere; }
|
|
3159
3182
|
.ai-mention-empty { margin: 0; padding: 10px; color: var(--muted); font-size: 10px; text-align: center; }
|
|
3160
3183
|
.ai-prompt { display: block; width: 100%; min-height: 104px; margin: 0; padding: 10px 10px 44px; overflow-y: auto; border: 1px solid var(--line); border-radius: 4px; background: var(--surface); color: var(--ink); font-size: 12px; line-height: 1.45; white-space: pre-wrap; word-break: break-word; cursor: text; }
|
|
3161
3184
|
.ai-prompt:empty::before { color: var(--muted); content: attr(data-placeholder); pointer-events: none; }
|
|
@@ -4519,6 +4542,7 @@ body { font-size: var(--ui-font-size); }
|
|
|
4519
4542
|
.eyebrow, .field-label, .dialog-header-meta, .settings-hub-card small, .book-card small, .book-info > span { font-size: calc(10px * var(--ui-font-scale)); }
|
|
4520
4543
|
.panel-heading, .dialog-fields label, .dialog-fields .form-field-note, .appearance-grid label,
|
|
4521
4544
|
.config-section-header p, .config-section .config-save-button { font-size: calc(11px * var(--ui-font-scale)); }
|
|
4545
|
+
#work-system-prompt, #remote-mcp-config { font-size: calc(11px * var(--ui-font-scale)); }
|
|
4522
4546
|
.settings-hub-card strong, .book-info strong { font-size: calc(16px * var(--ui-font-scale)); }
|
|
4523
4547
|
.shelf-header p, .topbar { font-size: var(--ui-font-size); }
|
|
4524
4548
|
.appearance-grid select, .dialog-fields input, .dialog-fields textarea, .dialog-fields select { font-size: calc(13px * var(--ui-font-scale)); }
|
|
@@ -4550,6 +4574,7 @@ body { font-size: var(--ui-font-size); }
|
|
|
4550
4574
|
.ai-context-warning p, .ai-context-warning button { font-size: calc(9px * var(--ai-font-scale)); }
|
|
4551
4575
|
.prompt-options select, .ai-model-popover > label, .ai-model-option-copy strong, .ai-model-options-empty, .ai-mention-option strong, .ai-mention-empty { font-size: calc(10px * var(--ai-font-scale)); }
|
|
4552
4576
|
.ai-mention-option small { font-size: calc(8px * var(--ai-font-scale)); }
|
|
4577
|
+
.ai-skill-option em { font-size: calc(9px * var(--ai-font-scale)); }
|
|
4553
4578
|
.ai-prompt { font-size: var(--ai-font-size); }
|
|
4554
4579
|
.ai-scene-panel label { font-size: calc(9px * var(--ai-font-scale)); }
|
|
4555
4580
|
.ai-scene-panel input, .ai-scene-panel textarea { font-size: calc(10px * var(--ai-font-scale)); }
|
package/dist/version.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const APP_VERSION = "0.9.
|
|
1
|
+
export const APP_VERSION = "0.9.7";
|
|
2
2
|
export const SCRIVERSE_BETA_COMMIT_ENV = "SCRIVERSE_BETA_COMMIT";
|
|
3
3
|
export function resolveBetaVersionLabel(environment) {
|
|
4
4
|
const commit = environment[SCRIVERSE_BETA_COMMIT_ENV]?.trim().toLocaleLowerCase() ?? "";
|