@musnows/scriverse 0.2.0 → 0.3.0

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.
Files changed (70) hide show
  1. package/README.en.md +13 -4
  2. package/README.md +13 -4
  3. package/dist/ai.js +3374 -0
  4. package/dist/ai.js.map +1 -0
  5. package/dist/app.js +1046 -0
  6. package/dist/app.js.map +1 -0
  7. package/dist/cli-core.js +147 -20
  8. package/dist/cli-core.js.map +1 -1
  9. package/dist/credential-vault.js +40 -0
  10. package/dist/credential-vault.js.map +1 -0
  11. package/dist/database.js +1122 -0
  12. package/dist/database.js.map +1 -0
  13. package/dist/docx-security.js +184 -0
  14. package/dist/docx-security.js.map +1 -0
  15. package/dist/domain.js +21 -0
  16. package/dist/domain.js.map +1 -0
  17. package/dist/errors.js +16 -0
  18. package/dist/errors.js.map +1 -0
  19. package/dist/image-captcha.js +173 -0
  20. package/dist/image-captcha.js.map +1 -0
  21. package/dist/image-metadata.js +128 -0
  22. package/dist/image-metadata.js.map +1 -0
  23. package/dist/import-security.js +46 -0
  24. package/dist/import-security.js.map +1 -0
  25. package/dist/parser.js +223 -0
  26. package/dist/parser.js.map +1 -0
  27. package/dist/public/ai-context-meter.js +10 -0
  28. package/dist/public/ai-conversation.js +3 -0
  29. package/dist/public/ai-mentions.js +55 -0
  30. package/dist/public/ai-message-actions.js +27 -0
  31. package/dist/public/ai-message-meta.js +15 -0
  32. package/dist/public/ai-message-time.js +23 -0
  33. package/dist/public/ai-prompt-keyboard.js +3 -0
  34. package/dist/public/app.js +4255 -0
  35. package/dist/public/character-profile.d.ts +9 -0
  36. package/dist/public/character-profile.js +65 -0
  37. package/dist/public/character-version.d.ts +2 -0
  38. package/dist/public/character-version.js +31 -0
  39. package/dist/public/entity-version.js +34 -0
  40. package/dist/public/icon.svg +10 -0
  41. package/dist/public/index.html +547 -0
  42. package/dist/public/line-number-layout.js +15 -0
  43. package/dist/public/markdown.js +199 -0
  44. package/dist/public/model-config.d.ts +17 -0
  45. package/dist/public/model-config.js +57 -0
  46. package/dist/public/page-route.d.ts +11 -0
  47. package/dist/public/page-route.js +81 -0
  48. package/dist/public/relationship-graph.js +2017 -0
  49. package/dist/public/site.webmanifest +17 -0
  50. package/dist/public/styles.css +1399 -0
  51. package/dist/public/text-formatting.d.ts +2 -0
  52. package/dist/public/text-formatting.js +20 -0
  53. package/dist/public/theme-init.js +8 -0
  54. package/dist/public/theme.js +13 -0
  55. package/dist/public/whitespace-visualization.js +20 -0
  56. package/dist/request-context.js +9 -0
  57. package/dist/request-context.js.map +1 -0
  58. package/dist/security.js +235 -0
  59. package/dist/security.js.map +1 -0
  60. package/dist/server-runtime.js +77 -0
  61. package/dist/server-runtime.js.map +1 -0
  62. package/dist/server.js +15 -0
  63. package/dist/server.js.map +1 -0
  64. package/dist/store.js +2492 -0
  65. package/dist/store.js.map +1 -0
  66. package/dist/user-auth.js +489 -0
  67. package/dist/user-auth.js.map +1 -0
  68. package/dist/utils.js +50 -0
  69. package/dist/utils.js.map +1 -0
  70. package/package.json +4 -9
@@ -0,0 +1,199 @@
1
+ const escapeHtml = (value) => String(value ?? "").replace(/[&<>'"]/gu, (character) => ({
2
+ "&": "&amp;",
3
+ "<": "&lt;",
4
+ ">": "&gt;",
5
+ "'": "&#39;",
6
+ '"': "&quot;"
7
+ })[character]);
8
+
9
+ function safeLinkTarget(value) {
10
+ const target = String(value ?? "").trim();
11
+ if (/^(https?:|mailto:)/iu.test(target)) return target;
12
+ if (/^(\/|#)/u.test(target) && !target.startsWith("//")) return target;
13
+ return null;
14
+ }
15
+
16
+ function renderInlineMarkdown(value) {
17
+ const tokens = [];
18
+ const preserve = (html) => {
19
+ const marker = `\uE000${tokens.length}\uE001`;
20
+ tokens.push(html);
21
+ return marker;
22
+ };
23
+ let source = String(value ?? "");
24
+ source = source.replace(/`([^`\n]+)`/gu, (_match, code) => preserve(`<code>${escapeHtml(code)}</code>`));
25
+ source = source.replace(/\[([^\]\n]+)\]\(([^)\s]+)\)/gu, (_match, label, href) => {
26
+ const target = safeLinkTarget(href);
27
+ if (!target) return `${label}(${href})`;
28
+ const external = /^https?:/iu.test(target) ? ' target="_blank" rel="noopener noreferrer"' : "";
29
+ return preserve(`<a href="${escapeHtml(target)}"${external}>${escapeHtml(label)}</a>`);
30
+ });
31
+ let html = escapeHtml(source);
32
+ html = html.replace(/\*\*([^*\n]+)\*\*/gu, "<strong>$1</strong>");
33
+ html = html.replace(/__([^_\n]+)__/gu, "<strong>$1</strong>");
34
+ html = html.replace(/(^|[^*])\*([^*\n]+)\*/gu, "$1<em>$2</em>");
35
+ html = html.replace(/~~([^~\n]+)~~/gu, "<del>$1</del>");
36
+ tokens.forEach((token, index) => {
37
+ html = html.replace(`\uE000${index}\uE001`, token);
38
+ });
39
+ return html;
40
+ }
41
+
42
+ function splitMarkdownTableRow(value) {
43
+ const source = String(value ?? "").trim();
44
+ if (!source.includes("|")) return null;
45
+ const cells = [];
46
+ let cell = "";
47
+ let inCode = false;
48
+ let separators = 0;
49
+ for (let index = 0; index < source.length; index += 1) {
50
+ const character = source[index];
51
+ if (character === "\\" && source[index + 1] === "|") {
52
+ cell += "|";
53
+ index += 1;
54
+ continue;
55
+ }
56
+ if (character === "`") inCode = !inCode;
57
+ if (character === "|" && !inCode) {
58
+ cells.push(cell.trim());
59
+ cell = "";
60
+ separators += 1;
61
+ continue;
62
+ }
63
+ cell += character;
64
+ }
65
+ cells.push(cell.trim());
66
+ if (!separators) return null;
67
+ if (source.startsWith("|")) cells.shift();
68
+ if (source.endsWith("|") && cells.at(-1) === "") cells.pop();
69
+ return cells;
70
+ }
71
+
72
+ function tableAlignments(cells) {
73
+ if (!cells?.length || !cells.every((cell) => /^:?-{3,}:?$/u.test(cell))) return null;
74
+ return cells.map((cell) => {
75
+ if (cell.startsWith(":") && cell.endsWith(":")) return "center";
76
+ if (cell.endsWith(":")) return "right";
77
+ return "left";
78
+ });
79
+ }
80
+
81
+ function renderMarkdownTable(headers, alignments, rows) {
82
+ const renderCell = (tag, value, alignment) => `<${tag} class="markdown-align-${alignment}">${renderInlineMarkdown(value)}</${tag}>`;
83
+ const header = headers.map((cell, index) => renderCell("th", cell, alignments[index])).join("");
84
+ const body = rows.map((row) => {
85
+ const cells = headers.map((_header, index) => renderCell("td", row[index] ?? "", alignments[index])).join("");
86
+ return `<tr>${cells}</tr>`;
87
+ }).join("");
88
+ return `<div class="markdown-table-scroll" role="region" aria-label="Markdown 表格" tabindex="0"><table><thead><tr>${header}</tr></thead>${body ? `<tbody>${body}</tbody>` : ""}</table></div>`;
89
+ }
90
+
91
+ export function renderMarkdown(value) {
92
+ const lines = String(value ?? "").replace(/\r\n?/gu, "\n").split("\n");
93
+ const output = [];
94
+ let paragraph = [];
95
+ let list = null;
96
+ let quote = null;
97
+ let codeFence = null;
98
+
99
+ const flushParagraph = () => {
100
+ if (!paragraph.length) return;
101
+ output.push(`<p>${paragraph.map(renderInlineMarkdown).join("<br>")}</p>`);
102
+ paragraph = [];
103
+ };
104
+ const flushList = () => {
105
+ if (!list) return;
106
+ output.push(`<${list.tag}>${list.items.map((item) => `<li class="markdown-depth-${item.depth}">${renderInlineMarkdown(item.text)}</li>`).join("")}</${list.tag}>`);
107
+ list = null;
108
+ };
109
+ const flushQuote = () => {
110
+ if (!quote) return;
111
+ const content = quote.join("\n").trim();
112
+ if (content) {
113
+ const html = content.split(/\n\s*\n/gu)
114
+ .map((part) => part.split("\n").map(renderInlineMarkdown).join("<br>"))
115
+ .join("<br><br>");
116
+ output.push(`<blockquote>${html}</blockquote>`);
117
+ }
118
+ quote = null;
119
+ };
120
+ const flushBlocks = () => {
121
+ flushParagraph();
122
+ flushList();
123
+ flushQuote();
124
+ };
125
+
126
+ for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
127
+ const line = lines[lineIndex];
128
+ if (codeFence) {
129
+ if (/^\s*```/u.test(line)) {
130
+ output.push(`<pre><code${codeFence.language ? ` class="language-${codeFence.language}"` : ""}>${escapeHtml(codeFence.lines.join("\n"))}</code></pre>`);
131
+ codeFence = null;
132
+ } else {
133
+ codeFence.lines.push(line);
134
+ }
135
+ continue;
136
+ }
137
+ const fence = line.match(/^\s*```([\w-]*)\s*$/u);
138
+ if (fence) {
139
+ flushBlocks();
140
+ codeFence = { language: fence[1].replace(/[^\w-]/gu, ""), lines: [] };
141
+ continue;
142
+ }
143
+ const quoteLine = line.match(/^>\s?(.*)$/u);
144
+ if (quoteLine) {
145
+ flushParagraph();
146
+ flushList();
147
+ if (!quote) quote = [];
148
+ quote.push(quoteLine[1]);
149
+ continue;
150
+ }
151
+ flushQuote();
152
+ if (!line.trim()) {
153
+ flushBlocks();
154
+ continue;
155
+ }
156
+ const heading = line.match(/^(#{1,4})\s+(.+)$/u);
157
+ if (heading) {
158
+ flushBlocks();
159
+ const level = heading[1].length;
160
+ output.push(`<h${level}>${renderInlineMarkdown(heading[2])}</h${level}>`);
161
+ continue;
162
+ }
163
+ if (/^\s*(---+|___+|\*\*\*+)\s*$/u.test(line)) {
164
+ flushBlocks();
165
+ output.push("<hr>");
166
+ continue;
167
+ }
168
+ const tableHeaders = splitMarkdownTableRow(line);
169
+ const alignments = tableAlignments(splitMarkdownTableRow(lines[lineIndex + 1]));
170
+ if (tableHeaders && alignments && tableHeaders.length === alignments.length) {
171
+ flushBlocks();
172
+ const rows = [];
173
+ lineIndex += 2;
174
+ while (lineIndex < lines.length && lines[lineIndex].trim()) {
175
+ const row = splitMarkdownTableRow(lines[lineIndex]);
176
+ if (!row) break;
177
+ rows.push(row);
178
+ lineIndex += 1;
179
+ }
180
+ lineIndex -= 1;
181
+ output.push(renderMarkdownTable(tableHeaders, alignments, rows));
182
+ continue;
183
+ }
184
+ const listItem = line.match(/^(\s*)([-+*]|\d+\.)\s+(.+)$/u);
185
+ if (listItem) {
186
+ flushParagraph();
187
+ const tag = /\d+\./u.test(listItem[2]) ? "ol" : "ul";
188
+ if (list && list.tag !== tag) flushList();
189
+ if (!list) list = { tag, items: [] };
190
+ list.items.push({ depth: Math.min(3, Math.floor(listItem[1].length / 2)), text: listItem[3] });
191
+ continue;
192
+ }
193
+ if (list) flushList();
194
+ paragraph.push(line);
195
+ }
196
+ if (codeFence) output.push(`<pre><code${codeFence.language ? ` class="language-${codeFence.language}"` : ""}>${escapeHtml(codeFence.lines.join("\n"))}</code></pre>`);
197
+ flushBlocks();
198
+ return output.join("");
199
+ }
@@ -0,0 +1,17 @@
1
+ export const MODEL_PURPOSE_OPTIONS: ReadonlyArray<readonly [string, string]>;
2
+
3
+ export type ModelFormValues = {
4
+ displayName: string;
5
+ modelId: string;
6
+ purposes: string[];
7
+ contextWindow: number;
8
+ temperature: number;
9
+ maxTokens: number;
10
+ thinkingEnabled: boolean;
11
+ enabled: boolean;
12
+ };
13
+
14
+ export function normalizeModelPurposes(purposes: unknown): string[];
15
+ export function modelFormValues(model?: Record<string, unknown> | null): ModelFormValues;
16
+ export function modelPayload(values: ModelFormValues, existingPreset?: Record<string, unknown>): Record<string, unknown> & { thinkingEnabled: boolean };
17
+ export function modelOptionLabel(model: Record<string, unknown> | null | undefined): string;
@@ -0,0 +1,57 @@
1
+ export const MODEL_PURPOSE_OPTIONS = Object.freeze([
2
+ ["chat", "通用对话"],
3
+ ["continue", "创作续写"],
4
+ ["polish", "文本润色"],
5
+ ["chapter-analysis", "章节理解"],
6
+ ["book-analysis", "全书分析"],
7
+ ["timeline-analysis", "时间轴抽取"],
8
+ ["relationship-analysis", "人物关系分析"],
9
+ ["consistency-check", "一致性校对"]
10
+ ]);
11
+
12
+ const purposeAliases = new Map([
13
+ ...MODEL_PURPOSE_OPTIONS.flatMap(([key, label]) => [[key, key], [label, key]]),
14
+ ["章节分析", "chapter-analysis"],
15
+ ["时间轴分析", "timeline-analysis"],
16
+ ["关系分析", "relationship-analysis"]
17
+ ]);
18
+
19
+ export function normalizeModelPurposes(purposes) {
20
+ const values = Array.isArray(purposes) ? purposes : String(purposes ?? "").split(/[,,]/u);
21
+ return [...new Set(values.map((value) => purposeAliases.get(String(value).trim())).filter(Boolean))];
22
+ }
23
+
24
+ export function modelFormValues(model = null) {
25
+ return {
26
+ displayName: model?.displayName ?? "",
27
+ modelId: model?.modelId ?? "",
28
+ purposes: model ? normalizeModelPurposes(model.purposes) : ["chat", "continue"],
29
+ contextWindow: model?.contextWindow ?? 128000,
30
+ temperature: model?.preset?.temperature ?? 0.7,
31
+ maxTokens: model?.preset?.max_tokens ?? 32000,
32
+ thinkingEnabled: model?.thinkingEnabled ?? true,
33
+ enabled: model?.enabled ?? true
34
+ };
35
+ }
36
+
37
+ export function modelPayload(values, existingPreset = {}) {
38
+ return {
39
+ displayName: String(values.displayName),
40
+ modelId: String(values.modelId),
41
+ purposes: normalizeModelPurposes(values.purposes),
42
+ contextWindow: Number(values.contextWindow),
43
+ preset: {
44
+ ...existingPreset,
45
+ temperature: Number(values.temperature),
46
+ max_tokens: Number(values.maxTokens)
47
+ },
48
+ thinkingEnabled: Boolean(values.thinkingEnabled),
49
+ enabled: Boolean(values.enabled)
50
+ };
51
+ }
52
+
53
+ export function modelOptionLabel(model) {
54
+ const providerName = String(model?.providerName ?? "").trim();
55
+ const modelName = String(model?.displayName ?? model?.modelId ?? "").trim();
56
+ return [providerName, modelName].filter(Boolean).join(" · ");
57
+ }
@@ -0,0 +1,11 @@
1
+ export type RestorableModule = "settings" | "characters" | "races" | "organizations" | "timeline" | "outlines" | "relationships" | "reviews" | "tasks" | "ai-settings";
2
+ export type PageRoute =
3
+ | { view: "shelf" }
4
+ | { view: "editor"; workId: string; chapterId: string | null }
5
+ | { view: "module"; workId: string; module: RestorableModule }
6
+ | { view: "welcome"; workId: string }
7
+ | { view: "settings" | "platform-ai"; workId: string | null; returnView?: "shelf" | "editor" | "module" | "welcome"; returnModule?: RestorableModule; returnChapterId?: string };
8
+
9
+ export const RESTORABLE_MODULES: readonly RestorableModule[];
10
+ export function serializePageRoute(route?: Record<string, unknown>): string;
11
+ export function parsePageRoute(hash?: string): PageRoute;
@@ -0,0 +1,81 @@
1
+ export const RESTORABLE_MODULES = Object.freeze([
2
+ "settings",
3
+ "characters",
4
+ "races",
5
+ "organizations",
6
+ "timeline",
7
+ "outlines",
8
+ "relationships",
9
+ "reviews",
10
+ "tasks",
11
+ "ai-settings"
12
+ ]);
13
+
14
+ const moduleSet = new Set(RESTORABLE_MODULES);
15
+ const returnViewSet = new Set(["shelf", "editor", "module", "welcome"]);
16
+
17
+ function value(params, key) {
18
+ return String(params.get(key) ?? "").trim();
19
+ }
20
+
21
+ function appendReturnContext(params, route) {
22
+ const returnView = returnViewSet.has(route.returnView) ? route.returnView : "";
23
+ if (!returnView) return;
24
+ params.set("from", returnView);
25
+ if (returnView === "module" && moduleSet.has(route.returnModule)) params.set("fromModule", route.returnModule);
26
+ if (returnView === "editor" && route.returnChapterId) params.set("fromChapter", String(route.returnChapterId));
27
+ }
28
+
29
+ export function serializePageRoute(route = {}) {
30
+ const params = new URLSearchParams();
31
+ const view = String(route.view ?? "shelf");
32
+ const workId = String(route.workId ?? "").trim();
33
+
34
+ if (view === "editor" && workId) {
35
+ params.set("view", "editor");
36
+ params.set("work", workId);
37
+ if (route.chapterId) params.set("chapter", String(route.chapterId));
38
+ } else if (view === "module" && workId && moduleSet.has(route.module)) {
39
+ params.set("view", "module");
40
+ params.set("work", workId);
41
+ params.set("module", route.module);
42
+ } else if (view === "welcome" && workId) {
43
+ params.set("view", "welcome");
44
+ params.set("work", workId);
45
+ } else if (view === "settings" || view === "platform-ai") {
46
+ params.set("view", view);
47
+ if (workId) params.set("work", workId);
48
+ appendReturnContext(params, route);
49
+ } else {
50
+ params.set("view", "shelf");
51
+ }
52
+
53
+ return `#${params.toString()}`;
54
+ }
55
+
56
+ export function parsePageRoute(hash = "") {
57
+ const params = new URLSearchParams(String(hash).replace(/^#/, ""));
58
+ const view = value(params, "view");
59
+ const workId = value(params, "work");
60
+
61
+ if (view === "editor" && workId) {
62
+ const chapterId = value(params, "chapter");
63
+ return { view, workId, chapterId: chapterId || null };
64
+ }
65
+ if (view === "module" && workId) {
66
+ const module = value(params, "module");
67
+ return moduleSet.has(module) ? { view, workId, module } : { view: "shelf" };
68
+ }
69
+ if (view === "welcome" && workId) return { view, workId };
70
+ if (view === "settings" || view === "platform-ai") {
71
+ const route = { view, workId: workId || null };
72
+ const returnView = value(params, "from");
73
+ if (returnViewSet.has(returnView)) route.returnView = returnView;
74
+ const returnModule = value(params, "fromModule");
75
+ if (returnView === "module" && moduleSet.has(returnModule)) route.returnModule = returnModule;
76
+ const returnChapterId = value(params, "fromChapter");
77
+ if (returnView === "editor" && returnChapterId) route.returnChapterId = returnChapterId;
78
+ return route;
79
+ }
80
+ return { view: "shelf" };
81
+ }