@musnows/scriverse 0.7.3 → 0.7.4

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 (54) hide show
  1. package/README.en.md +3 -0
  2. package/README.md +3 -0
  3. package/dist/ai-connectivity-test.js +109 -0
  4. package/dist/ai-connectivity-test.js.map +1 -0
  5. package/dist/ai-conversation-export.js +70 -0
  6. package/dist/ai-conversation-export.js.map +1 -0
  7. package/dist/ai-stream-timeout.js +18 -0
  8. package/dist/ai-stream-timeout.js.map +1 -0
  9. package/dist/ai.js +681 -107
  10. package/dist/ai.js.map +1 -1
  11. package/dist/app.js +326 -53
  12. package/dist/app.js.map +1 -1
  13. package/dist/character-extraction.js +133 -0
  14. package/dist/character-extraction.js.map +1 -0
  15. package/dist/cli-core.js +7 -6
  16. package/dist/cli-core.js.map +1 -1
  17. package/dist/database.js +175 -2
  18. package/dist/database.js.map +1 -1
  19. package/dist/epub-export.js +319 -0
  20. package/dist/epub-export.js.map +1 -0
  21. package/dist/hybrid-search.js +8 -0
  22. package/dist/hybrid-search.js.map +1 -1
  23. package/dist/public/ai-connectivity-test.d.ts +7 -0
  24. package/dist/public/ai-connectivity-test.js +82 -0
  25. package/dist/public/ai-request-manager.js +99 -0
  26. package/dist/public/ai-stream-protocol.js +51 -0
  27. package/dist/public/app.js +2567 -265
  28. package/dist/public/chapter-version-diff.d.ts +20 -0
  29. package/dist/public/chapter-version-diff.js +116 -0
  30. package/dist/public/foreshadow-reminder.d.ts +32 -0
  31. package/dist/public/foreshadow-reminder.js +73 -0
  32. package/dist/public/global-replace-refresh.js +60 -0
  33. package/dist/public/index.html +120 -12
  34. package/dist/public/outline-board.d.ts +61 -0
  35. package/dist/public/outline-board.js +137 -0
  36. package/dist/public/page-route.d.ts +1 -0
  37. package/dist/public/page-route.js +8 -0
  38. package/dist/public/reading-preview.d.ts +32 -0
  39. package/dist/public/reading-preview.js +136 -0
  40. package/dist/public/styles.css +485 -4
  41. package/dist/public/upload-progress.d.ts +2 -0
  42. package/dist/public/upload-progress.js +10 -0
  43. package/dist/security.js +5 -2
  44. package/dist/security.js.map +1 -1
  45. package/dist/server-runtime.js +2 -0
  46. package/dist/server-runtime.js.map +1 -1
  47. package/dist/store.js +825 -88
  48. package/dist/store.js.map +1 -1
  49. package/dist/user-auth.js +45 -9
  50. package/dist/user-auth.js.map +1 -1
  51. package/dist/utils.js +3 -0
  52. package/dist/utils.js.map +1 -1
  53. package/dist/version.js +1 -1
  54. package/package.json +2 -1
package/README.en.md CHANGED
@@ -106,6 +106,7 @@ Run `scriverse --help` for all local server, default server, authentication, wor
106
106
  | `DATA_DIR` | `<project>/.data` | Default data directory |
107
107
  | `DATABASE_PATH` | `<DATA_DIR>/novel.db` | SQLite database path |
108
108
  | `AI_NOVEL_MASTER_KEY` | Generated and stored at `<DATA_DIR>/master.key` | Master key used to encrypt AI provider credentials; at least 32 characters when configured manually |
109
+ | `SCRIVERSE_AI_STREAM_IDLE_TIMEOUT_SECONDS` | `30` | Maximum idle time while an interactive AI stream waits for its first or next valid event; valid integers are clamped to `10`–`120`, and invalid values fall back to `30` |
109
110
  | `APP_AUTH_USERNAME` | Empty | Optional deployment gateway username; the in-app user system is always enabled |
110
111
  | `APP_AUTH_PASSWORD` | Empty | Optional deployment gateway password, at least 12 characters; must be transported over HTTPS |
111
112
  | `APP_TRUST_PROXY` | `false` | Set to the trusted proxy hop count (usually `1`) or `true` behind a trusted reverse proxy |
@@ -113,6 +114,8 @@ Run `scriverse --help` for all local server, default server, authentication, wor
113
114
  | `APP_ALLOW_REGISTRATION` | `false` | Registration is enabled only when explicitly set to `true`; unset and all other values stay closed, including first-admin setup |
114
115
  | `APP_SETUP_TOKEN` | Empty | Required when registration is enabled and must contain at least 32 characters; only the first administrator must enter it |
115
116
 
117
+ `SCRIVERSE_AI_STREAM_IDLE_TIMEOUT_SECONDS` is read when the service starts and only controls how long an interactive AI stream may remain without a new event. Every valid stream event restarts the timer, so generation may continue beyond 60 seconds; this setting does not impose a total-duration limit or change timeout behavior for analysis tasks and other AI requests. Restart the service after changing it.
118
+
116
119
  Custom configuration example:
117
120
 
118
121
  ```bash
package/README.md CHANGED
@@ -114,6 +114,7 @@ CLI 会按服务器保存登录凭据。所有连接服务的数据命令都可
114
114
  | `SCRIVERSE_PRE_MIGRATION_BACKUP_RETENTION` | `5` | 启动迁移备份最多保留的完整版本数;小于 `2` 时按 `2` 处理,启动时清理最旧的超额备份 |
115
115
  | `SCRIVERSE_STARTUP_RETRY_LIMIT` | `2` | 连续启动失败最多允许的次数;达到上限后停止重复初始化,修复问题并删除 `<DATA_DIR>/.startup-retry.json` 后才能重新启动 |
116
116
  | `AI_NOVEL_MASTER_KEY` | 自动生成并保存在 `<DATA_DIR>/master.key` | 加密 AI 供应商密钥的主密钥;手动配置时至少 32 个字符 |
117
+ | `SCRIVERSE_AI_STREAM_IDLE_TIMEOUT_SECONDS` | `30` | 交互式 AI 流等待首个或下一个有效事件的最长空闲秒数;有效整数按 `10`–`120` 钳制,非法值回退为 `30` |
117
118
  | `APP_AUTH_USERNAME` | 空 | 可选的部署网关账号;应用内用户系统始终启用 |
118
119
  | `APP_AUTH_PASSWORD` | 空 | 可选的部署网关密码,至少 12 个字符;必须通过 HTTPS 传输 |
119
120
  | `APP_TRUST_PROXY` | `false` | 位于可信反向代理后时设为代理跳数(通常为 `1`)或 `true` |
@@ -128,6 +129,8 @@ CLI 会按服务器保存登录凭据。所有连接服务的数据命令都可
128
129
 
129
130
  上述三个图片大小限制均使用字节配置;非法、小于 1 或超过 1 GiB 的值会回退到默认值或按 1 GiB 处理。
130
131
 
132
+ `SCRIVERSE_AI_STREAM_IDLE_TIMEOUT_SECONDS` 在服务启动时读取,只控制交互式 AI 流连续没有新事件的等待时间。每收到一个有效流事件都会重新计时,持续生成超过 60 秒不会因此中断;该配置不设置总时长上限,也不改变分析任务等其他 AI 请求的超时策略。修改后需重启服务生效。
133
+
131
134
  自定义示例:
132
135
 
133
136
  ```bash
@@ -0,0 +1,109 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { AppError } from "./errors.js";
3
+ export const AI_CONNECTIVITY_TEST_SUCCESS_COOLDOWN_MS = 120_000;
4
+ export const AI_CONNECTIVITY_TEST_FAILURE_COOLDOWN_MS = 10_000;
5
+ export const AI_CONNECTIVITY_TEST_IN_PROGRESS_LEASE_MS = 65_000;
6
+ export function hashAiConnectivityConfiguration(parts) {
7
+ return createHash("sha256").update(JSON.stringify(parts)).digest("hex");
8
+ }
9
+ function retryDetails(reason, retryAtMs, nowMs) {
10
+ return {
11
+ reason,
12
+ retryAfterSeconds: Math.max(1, Math.ceil((retryAtMs - nowMs) / 1_000)),
13
+ retryAt: new Date(retryAtMs).toISOString()
14
+ };
15
+ }
16
+ function blockedError(objectType, cooldown) {
17
+ const label = objectType === "provider" ? "供应商" : "模型";
18
+ if (cooldown.reason === "in_progress") {
19
+ return new AppError(429, "AI_CONNECTIVITY_TEST_IN_PROGRESS", `${label}连接测试正在进行,请稍后重试`, cooldown);
20
+ }
21
+ return new AppError(429, "AI_CONNECTIVITY_TEST_COOLDOWN", `${label}连接测试仍在冷却中,请稍后重试`, cooldown);
22
+ }
23
+ export class AiConnectivityTestGate {
24
+ database;
25
+ currentTime;
26
+ inProgressLeaseMs;
27
+ constructor(database, currentTime = () => Date.now(), inProgressLeaseMs = AI_CONNECTIVITY_TEST_IN_PROGRESS_LEASE_MS) {
28
+ this.database = database;
29
+ this.currentTime = currentTime;
30
+ this.inProgressLeaseMs = inProgressLeaseMs;
31
+ }
32
+ acquire(objectType, objectId, configFingerprint) {
33
+ return this.acquireWithConfiguration(objectType, objectId, () => ({ configFingerprint, configuration: undefined })).claim;
34
+ }
35
+ acquireWithConfiguration(objectType, objectId, readConfiguration) {
36
+ const nowMs = this.currentTime();
37
+ const result = this.database.transaction(() => {
38
+ const { configFingerprint, configuration } = readConfiguration();
39
+ const current = this.database.get(`SELECT config_fingerprint, state, retry_at_ms
40
+ FROM ai_connectivity_test_states WHERE object_type = ? AND object_id = ?`, objectType, objectId);
41
+ if (current && current.config_fingerprint === configFingerprint && Number(current.retry_at_ms) > nowMs) {
42
+ const reason = current.state === "in_progress"
43
+ ? "in_progress"
44
+ : current.state === "success" ? "success_cooldown" : "failure_cooldown";
45
+ return {
46
+ claim: null,
47
+ blocked: retryDetails(reason, Number(current.retry_at_ms), nowMs),
48
+ configFingerprint: null,
49
+ configuration: null
50
+ };
51
+ }
52
+ const attemptId = randomUUID();
53
+ this.database.run(`INSERT INTO ai_connectivity_test_states
54
+ (object_type, object_id, config_fingerprint, state, attempt_id, retry_at_ms, updated_at)
55
+ VALUES (?, ?, ?, 'in_progress', ?, ?, ?)
56
+ ON CONFLICT(object_type, object_id) DO UPDATE SET
57
+ config_fingerprint = excluded.config_fingerprint,
58
+ state = excluded.state,
59
+ attempt_id = excluded.attempt_id,
60
+ retry_at_ms = excluded.retry_at_ms,
61
+ updated_at = excluded.updated_at`, objectType, objectId, configFingerprint, attemptId, nowMs + this.inProgressLeaseMs, new Date(nowMs).toISOString());
62
+ return {
63
+ claim: { objectType, objectId, configFingerprint, attemptId },
64
+ blocked: null,
65
+ configFingerprint,
66
+ configuration
67
+ };
68
+ });
69
+ if (result.blocked)
70
+ throw blockedError(objectType, result.blocked);
71
+ return {
72
+ claim: result.claim,
73
+ configFingerprint: result.configFingerprint,
74
+ configuration: result.configuration
75
+ };
76
+ }
77
+ complete(claim, outcome, options = {}) {
78
+ const nowMs = this.currentTime();
79
+ const cooldownMs = outcome === "success"
80
+ ? AI_CONNECTIVITY_TEST_SUCCESS_COOLDOWN_MS
81
+ : AI_CONNECTIVITY_TEST_FAILURE_COOLDOWN_MS;
82
+ const retryAtMs = nowMs + cooldownMs;
83
+ const completedAt = new Date(nowMs).toISOString();
84
+ const applied = this.database.transaction(() => {
85
+ if (options.isConfigurationCurrent && !options.isConfigurationCurrent()) {
86
+ this.database.run(`DELETE FROM ai_connectivity_test_states
87
+ WHERE object_type = ? AND object_id = ? AND config_fingerprint = ? AND attempt_id = ?`, claim.objectType, claim.objectId, claim.configFingerprint, claim.attemptId);
88
+ return false;
89
+ }
90
+ const update = this.database.run(`UPDATE ai_connectivity_test_states
91
+ SET state = ?, retry_at_ms = ?, updated_at = ?
92
+ WHERE object_type = ? AND object_id = ? AND config_fingerprint = ?
93
+ AND attempt_id = ? AND state = 'in_progress'`, outcome, retryAtMs, completedAt, claim.objectType, claim.objectId, claim.configFingerprint, claim.attemptId);
94
+ if (update.changes !== 1)
95
+ return false;
96
+ options.onApplied?.(completedAt);
97
+ return true;
98
+ });
99
+ if (!applied) {
100
+ return { reason: "configuration_changed", retryAfterSeconds: 0, retryAt: null };
101
+ }
102
+ return {
103
+ reason: outcome === "success" ? "success_cooldown" : "failure_cooldown",
104
+ retryAfterSeconds: cooldownMs / 1_000,
105
+ retryAt: new Date(retryAtMs).toISOString()
106
+ };
107
+ }
108
+ }
109
+ //# sourceMappingURL=ai-connectivity-test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-connectivity-test.js","sourceRoot":"","sources":["../src/ai-connectivity-test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ function recordValue(value) {
2
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
3
+ }
4
+ function stringValue(value, fallback = "") {
5
+ return typeof value === "string" ? value : fallback;
6
+ }
7
+ function markdownInline(value) {
8
+ return value
9
+ .replace(/[\r\n]+/gu, " ")
10
+ .replace(/\\/gu, "\\\\")
11
+ .replace(/([`*_{}\[\]<>#+.!|])/gu, "\\$1");
12
+ }
13
+ function timestamp(value) {
14
+ const parsed = new Date(stringValue(value));
15
+ return Number.isNaN(parsed.getTime()) ? "未知时间" : parsed.toISOString();
16
+ }
17
+ function safeFilenameTitle(value) {
18
+ const normalized = stringValue(value, "AI 对话")
19
+ .normalize("NFKC")
20
+ .replace(/[\u0000-\u001f\u007f]/gu, " ")
21
+ .replace(/[<>:"/\\|?*]/gu, "-")
22
+ .replace(/\s+/gu, " ")
23
+ .replace(/^[.\s-]+|[.\s-]+$/gu, "");
24
+ return Array.from(normalized || "AI 对话").slice(0, 80).join("");
25
+ }
26
+ function asciiIdentifier(value) {
27
+ return stringValue(value)
28
+ .replace(/[^A-Za-z0-9_-]/gu, "-")
29
+ .replace(/^-+|-+$/gu, "")
30
+ .slice(0, 80) || "export";
31
+ }
32
+ function rfc5987(value) {
33
+ return encodeURIComponent(value).replace(/['()*]/gu, (character) => (`%${character.codePointAt(0).toString(16).toUpperCase()}`));
34
+ }
35
+ export function aiConversationExportFilename(conversation) {
36
+ const record = recordValue(conversation);
37
+ return `${safeFilenameTitle(record?.title)}-${asciiIdentifier(record?.id)}.md`;
38
+ }
39
+ export function aiConversationExportContentDisposition(conversation) {
40
+ const record = recordValue(conversation);
41
+ const fallback = `ai-conversation-${asciiIdentifier(record?.id)}.md`;
42
+ return `attachment; filename="${fallback}"; filename*=UTF-8''${rfc5987(aiConversationExportFilename(conversation))}`;
43
+ }
44
+ export function exportAiConversationMarkdown(conversation) {
45
+ const record = recordValue(conversation) ?? {};
46
+ const title = stringValue(record.title, "AI 对话");
47
+ const roleplayCharacter = recordValue(record.roleplayCharacter);
48
+ const assistantLabel = stringValue(roleplayCharacter?.name, "助手") || "助手";
49
+ const messages = Array.isArray(record.messages)
50
+ ? record.messages.map(recordValue).filter((message) => message !== null)
51
+ : [];
52
+ const sections = [
53
+ `# ${markdownInline(title)}`,
54
+ "",
55
+ `- 创建时间:${timestamp(record.createdAt)}`,
56
+ `- 更新时间:${timestamp(record.updatedAt)}`,
57
+ `- 消息数:${messages.length}`
58
+ ];
59
+ if (messages.length === 0) {
60
+ sections.push("", "_暂无消息。_");
61
+ return `${sections.join("\n")}\n`;
62
+ }
63
+ for (const message of messages) {
64
+ const role = message.role === "user" ? "作者" : assistantLabel;
65
+ const content = stringValue(message.content);
66
+ sections.push("", "---", "", `## ${markdownInline(role)} · ${timestamp(message.createdAt)}`, "", content);
67
+ }
68
+ return `${sections.join("\n")}\n`;
69
+ }
70
+ //# sourceMappingURL=ai-conversation-export.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-conversation-export.js","sourceRoot":"","sources":["../src/ai-conversation-export.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ export const AI_STREAM_IDLE_TIMEOUT_SECONDS_ENV = "SCRIVERSE_AI_STREAM_IDLE_TIMEOUT_SECONDS";
2
+ export const DEFAULT_AI_STREAM_IDLE_TIMEOUT_SECONDS = 30;
3
+ export const MIN_AI_STREAM_IDLE_TIMEOUT_SECONDS = 10;
4
+ export const MAX_AI_STREAM_IDLE_TIMEOUT_SECONDS = 120;
5
+ export const DEFAULT_AI_STREAM_IDLE_TIMEOUT_MS = DEFAULT_AI_STREAM_IDLE_TIMEOUT_SECONDS * 1_000;
6
+ export function resolveAiStreamIdleTimeoutSeconds(environment = process.env) {
7
+ const raw = environment[AI_STREAM_IDLE_TIMEOUT_SECONDS_ENV]?.trim() ?? "";
8
+ if (!/^\d+$/u.test(raw))
9
+ return DEFAULT_AI_STREAM_IDLE_TIMEOUT_SECONDS;
10
+ const configured = Number(raw);
11
+ if (!Number.isSafeInteger(configured))
12
+ return DEFAULT_AI_STREAM_IDLE_TIMEOUT_SECONDS;
13
+ return Math.min(MAX_AI_STREAM_IDLE_TIMEOUT_SECONDS, Math.max(MIN_AI_STREAM_IDLE_TIMEOUT_SECONDS, configured));
14
+ }
15
+ export function resolveAiStreamIdleTimeoutMs(environment = process.env) {
16
+ return resolveAiStreamIdleTimeoutSeconds(environment) * 1_000;
17
+ }
18
+ //# sourceMappingURL=ai-stream-timeout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-stream-timeout.js","sourceRoot":"","sources":["../src/ai-stream-timeout.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,kCAAkC,GAAG,0CAA0C,CAAC;AAC7F,MAAM,CAAC,MAAM,sCAAsC,GAAG,EAAE,CAAC;AACzD,MAAM,CAAC,MAAM,kCAAkC,GAAG,EAAE,CAAC;AACrD,MAAM,CAAC,MAAM,kCAAkC,GAAG,GAAG,CAAC;AACtD,MAAM,CAAC,MAAM,iCAAiC,GAAG,sCAAsC,GAAG,KAAK,CAAC;AAEhG,MAAM,UAAU,iCAAiC,CAAC,cAAiC,OAAO,CAAC,GAAG;IAC5F,MAAM,GAAG,GAAG,WAAW,CAAC,kCAAkC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAC1E,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,sCAAsC,CAAC;IACvE,MAAM,UAAU,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IAC/B,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,UAAU,CAAC;QAAE,OAAO,sCAAsC,CAAC;IACrF,OAAO,IAAI,CAAC,GAAG,CACb,kCAAkC,EAClC,IAAI,CAAC,GAAG,CAAC,kCAAkC,EAAE,UAAU,CAAC,CACzD,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,4BAA4B,CAAC,cAAiC,OAAO,CAAC,GAAG;IACvF,OAAO,iCAAiC,CAAC,WAAW,CAAC,GAAG,KAAK,CAAC;AAChE,CAAC"}