@musnows/scriverse 0.9.5 → 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.
@@ -0,0 +1,56 @@
1
+ const chapterNumberTemplateToken = "{n}";
2
+ const existingChapterNumberPattern = /^(?:第\s*[〇零一二三四五六七八九十百千万两0-90-9]+\s*章(?:\s*[上中下])?|chap(?:ter)?\.?\s*[0-90-9]+|[0-90-9]+\s*[..、])\s*(?:[-—–::..、]\s*)?/iu;
3
+ const chineseDigits = ["零", "一", "二", "三", "四", "五", "六", "七", "八", "九"];
4
+ const chineseGroupUnits = ["", "十", "百", "千"];
5
+ function formatChineseGroup(value) {
6
+ const digits = String(value).padStart(4, "0");
7
+ let result = "";
8
+ let needsZero = false;
9
+ for (const [index, character] of [...digits].entries()) {
10
+ const digit = Number(character);
11
+ const position = digits.length - index - 1;
12
+ if (digit === 0) {
13
+ if (result && [...digits.slice(index + 1)].some((remaining) => remaining !== "0"))
14
+ needsZero = true;
15
+ continue;
16
+ }
17
+ if (needsZero)
18
+ result += chineseDigits[0];
19
+ result += `${chineseDigits[digit] ?? ""}${chineseGroupUnits[position] ?? ""}`;
20
+ needsZero = false;
21
+ }
22
+ return result;
23
+ }
24
+ export function isChapterNumberTemplate(value) {
25
+ const template = value.trim();
26
+ if (!template || template.length > 50 || /[\u0000-\u001f\u007f]/u.test(value))
27
+ return false;
28
+ return template.split(chapterNumberTemplateToken).length === 2;
29
+ }
30
+ export function formatChapterNumber(value, style) {
31
+ if (!Number.isSafeInteger(value) || value < 1 || value > 999_999) {
32
+ throw new RangeError("Chapter number must be an integer between 1 and 999999");
33
+ }
34
+ if (style === "arabic")
35
+ return String(value);
36
+ const highGroup = Math.floor(value / 10_000);
37
+ const lowGroup = value % 10_000;
38
+ let result = highGroup ? `${formatChineseGroup(highGroup)}万` : "";
39
+ if (lowGroup) {
40
+ if (highGroup && lowGroup < 1_000)
41
+ result += chineseDigits[0];
42
+ result += formatChineseGroup(lowGroup);
43
+ }
44
+ return result.replace(/^一十/u, "十");
45
+ }
46
+ export function chapterTitleWithoutNumber(title) {
47
+ return title.trim().replace(existingChapterNumberPattern, "").trim();
48
+ }
49
+ export function renumberChapterTitle(title, sequence, template, style) {
50
+ if (!isChapterNumberTemplate(template))
51
+ throw new Error("Invalid chapter number template");
52
+ const numberPrefix = template.trim().replace(chapterNumberTemplateToken, formatChapterNumber(sequence, style));
53
+ const suffix = chapterTitleWithoutNumber(title);
54
+ return suffix ? `${numberPrefix} ${suffix}` : numberPrefix;
55
+ }
56
+ //# sourceMappingURL=chapter-title-numbering.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chapter-title-numbering.js","sourceRoot":"","sources":["../src/chapter-title-numbering.ts"],"names":[],"mappings":"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"}
package/dist/database.js CHANGED
@@ -8,9 +8,9 @@ import { documentShortSearchTerms, id, normalizeDocumentSearchText, splitDocumen
8
8
  import { createChapterLineIds, parseChapterAnnotationLineHashes, parseChapterLineIds } from "./chapter-annotation-anchor.js";
9
9
  export const PLATFORM_AI_WORK_ID = "__scriverse_platform_ai__";
10
10
  export const SYSTEM_USER_ID = "__scriverse_system_user__";
11
- // 版本 81 用于列表查询索引;版本 82 由 Store 写入实体版本基线标记;版本 83 创建协作状态表;版本 84 创建备份加密表;版本 85 持久化协作变更动作;版本 86 扩展直接图片上传格式;版本 87 增加作品与分卷回收站;版本 88 持久化 AI 对话分支幂等键;版本 89 持久化 AI 对话流请求锁与幂等状态;版本 90 持久化 AI 连通性测试冷却状态;版本 91 建立章节段落行号索引;版本 92 增加 AI 对话收藏状态;版本 93 优化伏笔计划回收章节查询;版本 94 增加模型思考强度;版本 95 增加供应商最大输出参数选择;版本 96 扩展模型思考强度档位;版本 97 增加人物性别字段;版本 98 增加正文稳定等待配置;版本 99 持久化关系扮演中的用户角色;版本 100 增加平台 AI 流事件空闲超时配置;版本 101 将平台 AI 流事件空闲超时上限提升至 600 秒;版本 102 创建角色头像元数据表;版本 103 回填历史 AI 对话归属并建立用户列表索引;版本 104 扩大供应商协议约束以支持 OpenAI Responses;版本 105 增加独立分卷剧情顺序;版本 106 增加供应商思考类型配置;版本 107 增加 AI Cache Write 输入 Token 统计;版本 108 增加作品 AI 每月 Token 额度;版本 109 增加供应商日、月 Token 额度;版本 110 将日、月 Token 额度下限调整为大于 0;版本 111 扩展模型思考强度为 auto;版本 112 为 CLI API Key 增加可复制的加密密文;版本 113 增加角色收藏状态与列表索引;版本 114 增加组织、设定档案与想法收藏状态及列表索引;版本 115 增加 AI 对话会话级场景钉;版本 116 增加可持久化且可撤销的 Desktop Bearer 会话;版本 117 增加作品离线授权、同步变更游标与幂等变更结果;版本 118 增加供应商分析请求超时配置;版本 119 记录分析任务是否由 API Key 创建;版本 120 将书籍资料收藏按用户隔离并增加书籍级共享置顶;版本 121 强制作品 Owner 非空并建立用户外键约束;版本 122 创建 AI 写入审批与持久化提问表,并增加按角色归属的共享角色扮演记忆及作品级正文编辑偏好;版本 123 为正文评论持久化逐行哈希锚点;版本 124 为正文行和评论锚点持久化稳定行身份。
11
+ // 版本 81 用于列表查询索引;版本 82 由 Store 写入实体版本基线标记;版本 83 创建协作状态表;版本 84 创建备份加密表;版本 85 持久化协作变更动作;版本 86 扩展直接图片上传格式;版本 87 增加作品与分卷回收站;版本 88 持久化 AI 对话分支幂等键;版本 89 持久化 AI 对话流请求锁与幂等状态;版本 90 持久化 AI 连通性测试冷却状态;版本 91 建立章节段落行号索引;版本 92 增加 AI 对话收藏状态;版本 93 优化伏笔计划回收章节查询;版本 94 增加模型思考强度;版本 95 增加供应商最大输出参数选择;版本 96 扩展模型思考强度档位;版本 97 增加人物性别字段;版本 98 增加正文稳定等待配置;版本 99 持久化关系扮演中的用户角色;版本 100 增加平台 AI 流事件空闲超时配置;版本 101 将平台 AI 流事件空闲超时上限提升至 600 秒;版本 102 创建角色头像元数据表;版本 103 回填历史 AI 对话归属并建立用户列表索引;版本 104 扩大供应商协议约束以支持 OpenAI Responses;版本 105 增加独立分卷剧情顺序;版本 106 增加供应商思考类型配置;版本 107 增加 AI Cache Write 输入 Token 统计;版本 108 增加作品 AI 每月 Token 额度;版本 109 增加供应商日、月 Token 额度;版本 110 将日、月 Token 额度下限调整为大于 0;版本 111 扩展模型思考强度为 auto;版本 112 为 CLI API Key 增加可复制的加密密文;版本 113 增加角色收藏状态与列表索引;版本 114 增加组织、设定档案与想法收藏状态及列表索引;版本 115 增加 AI 对话会话级场景钉;版本 116 增加可持久化且可撤销的 Desktop Bearer 会话;版本 117 增加作品离线授权、同步变更游标与幂等变更结果;版本 118 增加供应商分析请求超时配置;版本 119 记录分析任务是否由 API Key 创建;版本 120 将书籍资料收藏按用户隔离并增加书籍级共享置顶;版本 121 强制作品 Owner 非空并建立用户外键约束;版本 122 创建 AI 写入审批与持久化提问表,并增加按角色归属的共享角色扮演记忆及作品级正文编辑偏好;版本 123 为正文评论持久化逐行哈希锚点;版本 124 为正文行和评论锚点持久化稳定行身份;版本 125 增加显式语义检索配置、分片索引、模型类型与上下文快照;版本 126 为作品增加加密的远程 MCP 配置与工具目录。
12
12
  export const ENTITY_VERSION_BASELINE_MIGRATION_VERSION = 82;
13
- export const DATABASE_SCHEMA_VERSION = 124;
13
+ export const DATABASE_SCHEMA_VERSION = 126;
14
14
  export const SQLITE_IOERR_SHMSIZE = 4874;
15
15
  export function isSqliteDiskIoError(error) {
16
16
  if (!(error instanceof Error))
@@ -568,6 +568,15 @@ export class Database {
568
568
  updated_at TEXT NOT NULL
569
569
  );
570
570
 
571
+ CREATE TABLE IF NOT EXISTS work_mcp_settings (
572
+ work_id TEXT PRIMARY KEY REFERENCES works(id) ON DELETE CASCADE,
573
+ config_encrypted TEXT NOT NULL,
574
+ config_iv TEXT NOT NULL,
575
+ config_tag TEXT NOT NULL,
576
+ tool_catalog_json TEXT NOT NULL DEFAULT '[]' CHECK(json_valid(tool_catalog_json) AND json_type(tool_catalog_json) = 'array'),
577
+ updated_at TEXT NOT NULL
578
+ );
579
+
571
580
  CREATE TABLE IF NOT EXISTS ai_calls (
572
581
  id TEXT PRIMARY KEY,
573
582
  work_id TEXT NOT NULL REFERENCES works(id) ON DELETE CASCADE,
@@ -4837,6 +4846,137 @@ export class Database {
4837
4846
  if (foreignKeys.length > 0)
4838
4847
  throw new Error(`数据库外键检查失败:发现 ${foreignKeys.length} 条异常记录`);
4839
4848
  }
4849
+ const modelKindPresent = this.all("PRAGMA table_info(models)")
4850
+ .some((column) => column.name === "model_kind");
4851
+ const semanticSettingsColumns = new Set(this.all("PRAGMA table_info(work_ai_settings)").map((column) => column.name));
4852
+ const semanticIndexPresent = Boolean(this.get("SELECT 1 AS present FROM sqlite_master WHERE type = 'table' AND name = 'semantic_index_entries'"));
4853
+ if (!applied.has(125) || !modelKindPresent || !semanticIndexPresent) {
4854
+ this.transaction(() => {
4855
+ if (!modelKindPresent) {
4856
+ this.run("ALTER TABLE models ADD COLUMN model_kind TEXT NOT NULL DEFAULT 'chat' CHECK(model_kind IN ('chat', 'embedding', 'rerank'))");
4857
+ }
4858
+ const semanticSettings = [
4859
+ ["semantic_search_enabled", "INTEGER NOT NULL DEFAULT 0 CHECK(semantic_search_enabled IN (0, 1))"],
4860
+ ["semantic_embedding_model_id", "TEXT REFERENCES models(id) ON DELETE SET NULL"],
4861
+ ["semantic_rerank_model_id", "TEXT REFERENCES models(id) ON DELETE SET NULL"],
4862
+ ["semantic_vector_dimension", "INTEGER NOT NULL DEFAULT 1024 CHECK(semantic_vector_dimension BETWEEN 1 AND 65536)"],
4863
+ ["semantic_recall_limit", "INTEGER NOT NULL DEFAULT 20 CHECK(semantic_recall_limit BETWEEN 1 AND 200)"],
4864
+ ["semantic_result_limit", "INTEGER NOT NULL DEFAULT 12 CHECK(semantic_result_limit BETWEEN 1 AND 100)"],
4865
+ ["semantic_budget_tokens", "INTEGER NOT NULL DEFAULT 4000 CHECK(semantic_budget_tokens BETWEEN 256 AND 100000)"],
4866
+ ["semantic_channel_weight", "REAL NOT NULL DEFAULT 1 CHECK(semantic_channel_weight BETWEEN 0.1 AND 5)"]
4867
+ ];
4868
+ for (const [column, definition] of semanticSettings) {
4869
+ if (!semanticSettingsColumns.has(column))
4870
+ this.run(`ALTER TABLE work_ai_settings ADD COLUMN ${column} ${definition}`);
4871
+ }
4872
+ this.raw.exec(`
4873
+ CREATE TABLE IF NOT EXISTS semantic_index_entries (
4874
+ id TEXT PRIMARY KEY,
4875
+ work_id TEXT NOT NULL REFERENCES works(id) ON DELETE CASCADE,
4876
+ source_type TEXT NOT NULL CHECK(source_type IN (
4877
+ 'chapter', 'setting', 'character', 'race', 'organization', 'timeline-track',
4878
+ 'timeline-event', 'relationship', 'chapter-outline', 'foreshadow'
4879
+ )),
4880
+ source_id TEXT NOT NULL,
4881
+ section_id TEXT NOT NULL DEFAULT '',
4882
+ source_version TEXT NOT NULL,
4883
+ source_title TEXT NOT NULL,
4884
+ chunk_order INTEGER NOT NULL CHECK(chunk_order >= 0),
4885
+ start_line INTEGER NOT NULL CHECK(start_line >= 1),
4886
+ end_line INTEGER NOT NULL CHECK(end_line >= start_line),
4887
+ start_offset INTEGER NOT NULL CHECK(start_offset >= 0),
4888
+ end_offset INTEGER NOT NULL CHECK(end_offset >= start_offset),
4889
+ content TEXT NOT NULL,
4890
+ content_hash TEXT NOT NULL,
4891
+ vector_json TEXT NOT NULL CHECK(json_valid(vector_json) AND json_type(vector_json) = 'array'),
4892
+ vector_dimension INTEGER NOT NULL CHECK(vector_dimension BETWEEN 1 AND 65536),
4893
+ embedding_model_id TEXT NOT NULL,
4894
+ config_fingerprint TEXT NOT NULL CHECK(length(config_fingerprint) = 64),
4895
+ chunk_rule_version INTEGER NOT NULL DEFAULT 1 CHECK(chunk_rule_version >= 1),
4896
+ created_at TEXT NOT NULL,
4897
+ UNIQUE(work_id, source_type, source_id, section_id, source_version, chunk_order, config_fingerprint)
4898
+ );
4899
+ CREATE INDEX IF NOT EXISTS idx_semantic_index_entries_recall
4900
+ ON semantic_index_entries(work_id, config_fingerprint, source_type, source_id);
4901
+ CREATE INDEX IF NOT EXISTS idx_semantic_index_entries_source
4902
+ ON semantic_index_entries(work_id, source_type, source_id, section_id, source_version);
4903
+
4904
+ CREATE TABLE IF NOT EXISTS semantic_index_state (
4905
+ work_id TEXT PRIMARY KEY REFERENCES works(id) ON DELETE CASCADE,
4906
+ status TEXT NOT NULL DEFAULT 'idle' CHECK(status IN ('disabled', 'idle', 'building', 'ready', 'failed', 'paused')),
4907
+ config_fingerprint TEXT NOT NULL DEFAULT '',
4908
+ total_sources INTEGER NOT NULL DEFAULT 0 CHECK(total_sources >= 0),
4909
+ processed_sources INTEGER NOT NULL DEFAULT 0 CHECK(processed_sources >= 0),
4910
+ failed_sources INTEGER NOT NULL DEFAULT 0 CHECK(failed_sources >= 0),
4911
+ consecutive_failures INTEGER NOT NULL DEFAULT 0 CHECK(consecutive_failures >= 0),
4912
+ error TEXT NOT NULL DEFAULT '',
4913
+ updated_at TEXT NOT NULL
4914
+ ) WITHOUT ROWID;
4915
+
4916
+ CREATE TABLE IF NOT EXISTS semantic_context_snapshots (
4917
+ id TEXT PRIMARY KEY,
4918
+ work_id TEXT NOT NULL REFERENCES works(id) ON DELETE CASCADE,
4919
+ conversation_id TEXT REFERENCES ai_conversations(id) ON DELETE SET NULL,
4920
+ query TEXT NOT NULL,
4921
+ scope_json TEXT NOT NULL CHECK(json_valid(scope_json) AND json_type(scope_json) = 'object'),
4922
+ config_fingerprint TEXT NOT NULL CHECK(length(config_fingerprint) = 64),
4923
+ created_by_user_id TEXT,
4924
+ created_at TEXT NOT NULL
4925
+ );
4926
+ CREATE INDEX IF NOT EXISTS idx_semantic_context_snapshots_work
4927
+ ON semantic_context_snapshots(work_id, created_at DESC);
4928
+
4929
+ CREATE TABLE IF NOT EXISTS semantic_context_snapshot_items (
4930
+ snapshot_id TEXT NOT NULL REFERENCES semantic_context_snapshots(id) ON DELETE CASCADE,
4931
+ position INTEGER NOT NULL CHECK(position >= 0),
4932
+ entry_id TEXT NOT NULL,
4933
+ source_type TEXT NOT NULL,
4934
+ source_id TEXT NOT NULL,
4935
+ section_id TEXT NOT NULL DEFAULT '',
4936
+ source_version TEXT NOT NULL,
4937
+ source_title TEXT NOT NULL,
4938
+ start_line INTEGER NOT NULL CHECK(start_line >= 1),
4939
+ end_line INTEGER NOT NULL CHECK(end_line >= start_line),
4940
+ content TEXT NOT NULL,
4941
+ estimated_tokens INTEGER NOT NULL CHECK(estimated_tokens >= 0),
4942
+ semantic_score REAL NOT NULL,
4943
+ rerank_score REAL,
4944
+ match_kinds_json TEXT NOT NULL CHECK(json_valid(match_kinds_json) AND json_type(match_kinds_json) = 'array'),
4945
+ PRIMARY KEY(snapshot_id, position),
4946
+ UNIQUE(snapshot_id, entry_id)
4947
+ ) WITHOUT ROWID;
4948
+ `);
4949
+ this.run("INSERT OR IGNORE INTO schema_migrations (version, applied_at) VALUES (125, ?)", new Date().toISOString());
4950
+ });
4951
+ const integrity = this.all("PRAGMA integrity_check");
4952
+ if (integrity.some((row) => row.integrity_check !== "ok")) {
4953
+ throw new Error(`数据库完整性检查失败:${integrity.map((row) => row.integrity_check).join(";")}`);
4954
+ }
4955
+ const foreignKeys = this.all("PRAGMA foreign_key_check");
4956
+ if (foreignKeys.length > 0)
4957
+ throw new Error(`数据库外键检查失败:发现 ${foreignKeys.length} 条异常记录`);
4958
+ }
4959
+ const workMcpSettingsPresent = this.get("SELECT 1 AS present FROM sqlite_master WHERE type = 'table' AND name = 'work_mcp_settings'") !== undefined;
4960
+ if (!applied.has(126) || !workMcpSettingsPresent) {
4961
+ this.transaction(() => {
4962
+ this.run(`CREATE TABLE IF NOT EXISTS work_mcp_settings (
4963
+ work_id TEXT PRIMARY KEY REFERENCES works(id) ON DELETE CASCADE,
4964
+ config_encrypted TEXT NOT NULL,
4965
+ config_iv TEXT NOT NULL,
4966
+ config_tag TEXT NOT NULL,
4967
+ tool_catalog_json TEXT NOT NULL DEFAULT '[]' CHECK(json_valid(tool_catalog_json) AND json_type(tool_catalog_json) = 'array'),
4968
+ updated_at TEXT NOT NULL
4969
+ )`);
4970
+ this.run("INSERT OR IGNORE INTO schema_migrations (version, applied_at) VALUES (126, ?)", new Date().toISOString());
4971
+ });
4972
+ const integrity = this.all("PRAGMA integrity_check");
4973
+ if (integrity.some((row) => row.integrity_check !== "ok")) {
4974
+ throw new Error(`数据库完整性检查失败:${integrity.map((row) => row.integrity_check).join(";")}`);
4975
+ }
4976
+ const foreignKeys = this.all("PRAGMA foreign_key_check");
4977
+ if (foreignKeys.length > 0)
4978
+ throw new Error(`数据库外键检查失败:发现 ${foreignKeys.length} 条异常记录`);
4979
+ }
4840
4980
  }
4841
4981
  normalizeCharacterName(value) {
4842
4982
  return value.normalize("NFKC").trim().replace(/\s+/gu, " ").toLocaleLowerCase("zh-CN");