@musnows/scriverse 0.6.8 → 0.6.10
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-tool-results.js +13 -1
- package/dist/ai-tool-results.js.map +1 -1
- package/dist/ai.js +76 -27
- package/dist/ai.js.map +1 -1
- package/dist/app.js +94 -6
- package/dist/app.js.map +1 -1
- package/dist/database.js +295 -5
- package/dist/database.js.map +1 -1
- package/dist/hybrid-search.js +2 -1
- package/dist/hybrid-search.js.map +1 -1
- package/dist/logger.js +2 -2
- package/dist/logger.js.map +1 -1
- package/dist/public/app.js +368 -15
- package/dist/public/display-labels.js +2 -1
- package/dist/public/global-search.d.ts +3 -1
- package/dist/public/global-search.js +31 -0
- package/dist/public/index.html +48 -3
- package/dist/public/s3-backup-ui.d.ts +17 -0
- package/dist/public/s3-backup-ui.js +28 -0
- package/dist/public/styles.css +86 -2
- package/dist/s3-backup.js +654 -0
- package/dist/s3-backup.js.map +1 -0
- package/dist/security.js +25 -0
- package/dist/security.js.map +1 -1
- package/dist/store.js +74 -15
- package/dist/store.js.map +1 -1
- package/dist/user-auth.js +6 -2
- package/dist/user-auth.js.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +2 -1
package/dist/database.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { chmodSync, existsSync, mkdirSync } from "node:fs";
|
|
1
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
3
|
import { DatabaseSync } from "node:sqlite";
|
|
4
4
|
import { logger, sanitizeError } from "./logger.js";
|
|
5
5
|
import { documentShortSearchTerms, normalizeDocumentSearchText, splitDocumentParagraphs } from "./utils.js";
|
|
6
6
|
export const PLATFORM_AI_WORK_ID = "__scriverse_platform_ai__";
|
|
7
|
-
export const DATABASE_SCHEMA_VERSION =
|
|
7
|
+
export const DATABASE_SCHEMA_VERSION = 76;
|
|
8
8
|
export function readDatabaseSchemaVersion(filename) {
|
|
9
9
|
if (!existsSync(filename))
|
|
10
10
|
return null;
|
|
@@ -21,8 +21,10 @@ export function readDatabaseSchemaVersion(filename) {
|
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
export class Database {
|
|
24
|
+
filename;
|
|
24
25
|
raw;
|
|
25
26
|
constructor(filename) {
|
|
27
|
+
this.filename = filename;
|
|
26
28
|
logger.info("database.opening", { databasePath: filename, inMemory: filename === ":memory:" });
|
|
27
29
|
try {
|
|
28
30
|
if (filename !== ":memory:")
|
|
@@ -53,6 +55,18 @@ export class Database {
|
|
|
53
55
|
this.raw.close();
|
|
54
56
|
logger.info("database.closed");
|
|
55
57
|
}
|
|
58
|
+
createSnapshotBuffer() {
|
|
59
|
+
if (this.filename === ":memory:")
|
|
60
|
+
throw new Error("内存数据库不能创建文件快照");
|
|
61
|
+
if (this.raw.isTransaction)
|
|
62
|
+
throw new Error("事务执行期间不能创建数据库快照");
|
|
63
|
+
// 项目只使用当前这一条同步连接;截断 WAL 后立即同步读取主库,期间不会穿插其他写入。
|
|
64
|
+
const checkpoint = this.get("PRAGMA wal_checkpoint(TRUNCATE)");
|
|
65
|
+
if (Number(checkpoint?.busy ?? 0) !== 0 || Number(checkpoint?.log ?? 0) !== Number(checkpoint?.checkpointed ?? 0)) {
|
|
66
|
+
throw new Error("数据库 WAL 尚未完整合并,无法创建一致性快照");
|
|
67
|
+
}
|
|
68
|
+
return readFileSync(this.filename);
|
|
69
|
+
}
|
|
56
70
|
run(sql, ...params) {
|
|
57
71
|
const result = this.raw.prepare(sql).run(...params);
|
|
58
72
|
return { changes: Number(result.changes), lastInsertRowid: result.lastInsertRowid };
|
|
@@ -454,7 +468,7 @@ export class Database {
|
|
|
454
468
|
auto_run_consecutive_failures INTEGER NOT NULL DEFAULT 0,
|
|
455
469
|
book_summary_context_percent INTEGER NOT NULL DEFAULT 50 CHECK(book_summary_context_percent BETWEEN 1 AND 90),
|
|
456
470
|
context_compact_threshold INTEGER NOT NULL DEFAULT 85 CHECK(context_compact_threshold BETWEEN 50 AND 90),
|
|
457
|
-
agent_tool_call_limit INTEGER NOT NULL DEFAULT 12 CHECK(agent_tool_call_limit BETWEEN 5 AND
|
|
471
|
+
agent_tool_call_limit INTEGER NOT NULL DEFAULT 12 CHECK(agent_tool_call_limit BETWEEN 5 AND 1000),
|
|
458
472
|
agent_tool_call_global_multiplier INTEGER NOT NULL DEFAULT 3 CHECK(agent_tool_call_global_multiplier BETWEEN 1 AND 6),
|
|
459
473
|
agent_tools_json TEXT NOT NULL DEFAULT '["story_index","read_chapters","search_story_entities","grep","read_character_sections","search_drafts","image"]',
|
|
460
474
|
title_generation_model_id TEXT REFERENCES models(id) ON DELETE SET NULL,
|
|
@@ -2733,8 +2747,44 @@ export class Database {
|
|
|
2733
2747
|
if (foreignKeys.length > 0)
|
|
2734
2748
|
throw new Error(`数据库外键检查失败:发现 ${foreignKeys.length} 条异常记录`);
|
|
2735
2749
|
}
|
|
2736
|
-
|
|
2750
|
+
const s3TargetsPresent = this.all("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 's3_backup_targets'").length > 0;
|
|
2751
|
+
const modelColumnsAt73 = new Set(this.all("PRAGMA table_info(models)").map((row) => String(row.name)));
|
|
2752
|
+
const platformAiColumnsAt73 = new Set(this.all("PRAGMA table_info(platform_ai_settings)").map((row) => String(row.name)));
|
|
2753
|
+
const workAiColumnsAt73 = new Set(this.all("PRAGMA table_info(work_ai_settings)").map((row) => String(row.name)));
|
|
2754
|
+
const multimodalMigrationPresent = modelColumnsAt73.has("multimodal_enabled")
|
|
2755
|
+
&& platformAiColumnsAt73.has("image_tool_model_id")
|
|
2756
|
+
&& workAiColumnsAt73.has("image_tool_model_id");
|
|
2757
|
+
if (!applied.has(73) || !s3TargetsPresent || !multimodalMigrationPresent) {
|
|
2737
2758
|
this.transaction(() => {
|
|
2759
|
+
this.run(`CREATE TABLE IF NOT EXISTS s3_backup_targets (
|
|
2760
|
+
id TEXT PRIMARY KEY,
|
|
2761
|
+
name TEXT NOT NULL,
|
|
2762
|
+
endpoint TEXT NOT NULL,
|
|
2763
|
+
region TEXT NOT NULL DEFAULT 'us-east-1',
|
|
2764
|
+
bucket TEXT NOT NULL,
|
|
2765
|
+
base_path TEXT NOT NULL DEFAULT '',
|
|
2766
|
+
access_key_encrypted TEXT NOT NULL,
|
|
2767
|
+
access_key_iv TEXT NOT NULL,
|
|
2768
|
+
access_key_tag TEXT NOT NULL,
|
|
2769
|
+
secret_key_encrypted TEXT NOT NULL,
|
|
2770
|
+
secret_key_iv TEXT NOT NULL,
|
|
2771
|
+
secret_key_tag TEXT NOT NULL,
|
|
2772
|
+
force_path_style INTEGER NOT NULL DEFAULT 1 CHECK(force_path_style IN (0, 1)),
|
|
2773
|
+
enabled INTEGER NOT NULL DEFAULT 0 CHECK(enabled IN (0, 1)),
|
|
2774
|
+
backup_images INTEGER NOT NULL DEFAULT 1 CHECK(backup_images IN (0, 1)),
|
|
2775
|
+
schedule_time TEXT NOT NULL DEFAULT '03:00' CHECK(
|
|
2776
|
+
schedule_time GLOB '[0-2][0-9]:[0-5][0-9]'
|
|
2777
|
+
AND substr(schedule_time, 1, 2) <= '23'
|
|
2778
|
+
),
|
|
2779
|
+
retention_count INTEGER NOT NULL DEFAULT 7 CHECK(retention_count BETWEEN 1 AND 365),
|
|
2780
|
+
last_started_at TEXT,
|
|
2781
|
+
last_success_at TEXT,
|
|
2782
|
+
last_failure_at TEXT,
|
|
2783
|
+
last_error TEXT,
|
|
2784
|
+
created_at TEXT NOT NULL,
|
|
2785
|
+
updated_at TEXT NOT NULL
|
|
2786
|
+
)`);
|
|
2787
|
+
this.run("CREATE INDEX IF NOT EXISTS idx_s3_backup_targets_schedule ON s3_backup_targets(enabled, schedule_time, created_at)");
|
|
2738
2788
|
const modelColumns = new Set(this.all("PRAGMA table_info(models)").map((row) => String(row.name)));
|
|
2739
2789
|
if (!modelColumns.has("multimodal_enabled")) {
|
|
2740
2790
|
this.run("ALTER TABLE models ADD COLUMN multimodal_enabled INTEGER NOT NULL DEFAULT 0 CHECK(multimodal_enabled IN (0, 1))");
|
|
@@ -2761,7 +2811,182 @@ export class Database {
|
|
|
2761
2811
|
tools.push("image");
|
|
2762
2812
|
this.run("UPDATE work_ai_settings SET agent_tools_json = ? WHERE work_id = ?", JSON.stringify(tools), String(row.work_id));
|
|
2763
2813
|
}
|
|
2764
|
-
this.run("INSERT INTO schema_migrations (version, applied_at) VALUES (73, ?)", new Date().toISOString());
|
|
2814
|
+
this.run("INSERT OR IGNORE INTO schema_migrations (version, applied_at) VALUES (73, ?)", new Date().toISOString());
|
|
2815
|
+
});
|
|
2816
|
+
const integrity = this.all("PRAGMA integrity_check");
|
|
2817
|
+
if (integrity.some((row) => row.integrity_check !== "ok")) {
|
|
2818
|
+
throw new Error(`数据库完整性检查失败:${integrity.map((row) => row.integrity_check).join(";")}`);
|
|
2819
|
+
}
|
|
2820
|
+
const foreignKeys = this.all("PRAGMA foreign_key_check");
|
|
2821
|
+
if (foreignKeys.length > 0)
|
|
2822
|
+
throw new Error(`数据库外键检查失败:发现 ${foreignKeys.length} 条异常记录`);
|
|
2823
|
+
}
|
|
2824
|
+
const s3RunsPresent = this.all("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 's3_backup_runs'").length > 0;
|
|
2825
|
+
const aiHistorySearchPresent = this.all("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'ai_history_search'").length > 0;
|
|
2826
|
+
if (!applied.has(74) || !s3RunsPresent || !aiHistorySearchPresent) {
|
|
2827
|
+
this.transaction(() => {
|
|
2828
|
+
this.run(`CREATE TABLE IF NOT EXISTS s3_backup_runs (
|
|
2829
|
+
id TEXT PRIMARY KEY,
|
|
2830
|
+
target_id TEXT REFERENCES s3_backup_targets(id) ON DELETE SET NULL,
|
|
2831
|
+
target_name TEXT NOT NULL,
|
|
2832
|
+
trigger TEXT NOT NULL CHECK(trigger IN ('manual', 'scheduled')),
|
|
2833
|
+
status TEXT NOT NULL CHECK(status IN ('running', 'succeeded', 'failed')),
|
|
2834
|
+
database_key TEXT,
|
|
2835
|
+
images_uploaded INTEGER NOT NULL DEFAULT 0,
|
|
2836
|
+
images_skipped INTEGER NOT NULL DEFAULT 0,
|
|
2837
|
+
databases_deleted INTEGER NOT NULL DEFAULT 0,
|
|
2838
|
+
error_message TEXT,
|
|
2839
|
+
server_response_json TEXT,
|
|
2840
|
+
started_at TEXT NOT NULL,
|
|
2841
|
+
finished_at TEXT
|
|
2842
|
+
)`);
|
|
2843
|
+
this.run("CREATE INDEX IF NOT EXISTS idx_s3_backup_runs_started ON s3_backup_runs(started_at DESC, id)");
|
|
2844
|
+
this.run("CREATE INDEX IF NOT EXISTS idx_s3_backup_runs_target ON s3_backup_runs(target_id, started_at DESC)");
|
|
2845
|
+
this.raw.exec(`
|
|
2846
|
+
CREATE TABLE IF NOT EXISTS ai_history_search (
|
|
2847
|
+
id INTEGER PRIMARY KEY,
|
|
2848
|
+
work_id TEXT NOT NULL REFERENCES works(id) ON DELETE CASCADE,
|
|
2849
|
+
conversation_id TEXT NOT NULL REFERENCES ai_conversations(id) ON DELETE CASCADE,
|
|
2850
|
+
message_id TEXT REFERENCES ai_conversation_messages(id) ON DELETE CASCADE,
|
|
2851
|
+
source_type TEXT NOT NULL CHECK(source_type IN ('conversation', 'message')),
|
|
2852
|
+
source_id TEXT NOT NULL,
|
|
2853
|
+
role TEXT NOT NULL DEFAULT '' CHECK(role IN ('', 'user', 'assistant')),
|
|
2854
|
+
title TEXT NOT NULL DEFAULT '',
|
|
2855
|
+
content TEXT NOT NULL DEFAULT '',
|
|
2856
|
+
search_content TEXT NOT NULL,
|
|
2857
|
+
created_at TEXT NOT NULL,
|
|
2858
|
+
UNIQUE(source_type, source_id)
|
|
2859
|
+
);
|
|
2860
|
+
CREATE INDEX IF NOT EXISTS idx_ai_history_search_work
|
|
2861
|
+
ON ai_history_search(work_id, created_at DESC, id DESC);
|
|
2862
|
+
CREATE INDEX IF NOT EXISTS idx_ai_history_search_conversation
|
|
2863
|
+
ON ai_history_search(work_id, conversation_id, source_type, created_at DESC, id DESC);
|
|
2864
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS ai_history_search_fts USING fts5(
|
|
2865
|
+
search_content,
|
|
2866
|
+
content='ai_history_search',
|
|
2867
|
+
content_rowid='id',
|
|
2868
|
+
tokenize='trigram'
|
|
2869
|
+
);
|
|
2870
|
+
CREATE TRIGGER IF NOT EXISTS ai_history_search_ai AFTER INSERT ON ai_history_search BEGIN
|
|
2871
|
+
INSERT INTO ai_history_search_fts(rowid, search_content) VALUES (new.id, new.search_content);
|
|
2872
|
+
END;
|
|
2873
|
+
CREATE TRIGGER IF NOT EXISTS ai_history_search_ad AFTER DELETE ON ai_history_search BEGIN
|
|
2874
|
+
INSERT INTO ai_history_search_fts(ai_history_search_fts, rowid, search_content)
|
|
2875
|
+
VALUES ('delete', old.id, old.search_content);
|
|
2876
|
+
END;
|
|
2877
|
+
CREATE TRIGGER IF NOT EXISTS ai_history_search_au AFTER UPDATE ON ai_history_search BEGIN
|
|
2878
|
+
INSERT INTO ai_history_search_fts(ai_history_search_fts, rowid, search_content)
|
|
2879
|
+
VALUES ('delete', old.id, old.search_content);
|
|
2880
|
+
INSERT INTO ai_history_search_fts(rowid, search_content) VALUES (new.id, new.search_content);
|
|
2881
|
+
END;
|
|
2882
|
+
CREATE TRIGGER IF NOT EXISTS ai_history_search_conversation_ai AFTER INSERT ON ai_conversations BEGIN
|
|
2883
|
+
INSERT INTO ai_history_search
|
|
2884
|
+
(work_id, conversation_id, message_id, source_type, source_id, role, title, content, search_content, created_at)
|
|
2885
|
+
VALUES
|
|
2886
|
+
(new.work_id, new.id, NULL, 'conversation', new.id, '', new.title, COALESCE(new.compacted_summary, ''),
|
|
2887
|
+
lower(new.title || char(10) || COALESCE(new.compacted_summary, '')), new.created_at);
|
|
2888
|
+
END;
|
|
2889
|
+
CREATE TRIGGER IF NOT EXISTS ai_history_search_conversation_au AFTER UPDATE OF title, compacted_summary ON ai_conversations BEGIN
|
|
2890
|
+
UPDATE ai_history_search
|
|
2891
|
+
SET title = new.title,
|
|
2892
|
+
content = COALESCE(new.compacted_summary, ''),
|
|
2893
|
+
search_content = lower(new.title || char(10) || COALESCE(new.compacted_summary, ''))
|
|
2894
|
+
WHERE source_type = 'conversation' AND source_id = new.id;
|
|
2895
|
+
END;
|
|
2896
|
+
CREATE TRIGGER IF NOT EXISTS ai_history_search_message_ai AFTER INSERT ON ai_conversation_messages BEGIN
|
|
2897
|
+
INSERT INTO ai_history_search
|
|
2898
|
+
(work_id, conversation_id, message_id, source_type, source_id, role, title, content, search_content, created_at)
|
|
2899
|
+
SELECT conversation.work_id, new.conversation_id, new.id, 'message', new.id, new.role, conversation.title,
|
|
2900
|
+
new.content, lower(new.content), new.created_at
|
|
2901
|
+
FROM ai_conversations conversation
|
|
2902
|
+
WHERE conversation.id = new.conversation_id;
|
|
2903
|
+
END;
|
|
2904
|
+
CREATE TRIGGER IF NOT EXISTS ai_history_search_message_au AFTER UPDATE OF role, content ON ai_conversation_messages BEGIN
|
|
2905
|
+
UPDATE ai_history_search
|
|
2906
|
+
SET role = new.role, content = new.content, search_content = lower(new.content)
|
|
2907
|
+
WHERE source_type = 'message' AND source_id = new.id;
|
|
2908
|
+
END;
|
|
2909
|
+
CREATE TABLE IF NOT EXISTS ai_history_search_short_terms (
|
|
2910
|
+
search_id INTEGER NOT NULL REFERENCES ai_history_search(id) ON DELETE CASCADE,
|
|
2911
|
+
term TEXT NOT NULL,
|
|
2912
|
+
PRIMARY KEY(term, search_id)
|
|
2913
|
+
) WITHOUT ROWID;
|
|
2914
|
+
CREATE INDEX IF NOT EXISTS idx_ai_history_search_short_terms_search
|
|
2915
|
+
ON ai_history_search_short_terms(search_id);
|
|
2916
|
+
`);
|
|
2917
|
+
this.run(`
|
|
2918
|
+
INSERT INTO ai_history_search
|
|
2919
|
+
(work_id, conversation_id, message_id, source_type, source_id, role, title, content, search_content, created_at)
|
|
2920
|
+
SELECT conversation.work_id, conversation.id, NULL, 'conversation', conversation.id, '', conversation.title,
|
|
2921
|
+
COALESCE(conversation.compacted_summary, ''),
|
|
2922
|
+
lower(conversation.title || char(10) || COALESCE(conversation.compacted_summary, '')),
|
|
2923
|
+
conversation.created_at
|
|
2924
|
+
FROM ai_conversations conversation
|
|
2925
|
+
WHERE 1
|
|
2926
|
+
ON CONFLICT(source_type, source_id) DO UPDATE SET
|
|
2927
|
+
work_id = excluded.work_id,
|
|
2928
|
+
conversation_id = excluded.conversation_id,
|
|
2929
|
+
title = excluded.title,
|
|
2930
|
+
content = excluded.content,
|
|
2931
|
+
search_content = excluded.search_content,
|
|
2932
|
+
created_at = excluded.created_at
|
|
2933
|
+
`);
|
|
2934
|
+
this.run(`
|
|
2935
|
+
INSERT INTO ai_history_search
|
|
2936
|
+
(work_id, conversation_id, message_id, source_type, source_id, role, title, content, search_content, created_at)
|
|
2937
|
+
SELECT conversation.work_id, message.conversation_id, message.id, 'message', message.id, message.role, conversation.title,
|
|
2938
|
+
message.content, lower(message.content), message.created_at
|
|
2939
|
+
FROM ai_conversation_messages message
|
|
2940
|
+
JOIN ai_conversations conversation ON conversation.id = message.conversation_id
|
|
2941
|
+
WHERE 1
|
|
2942
|
+
ON CONFLICT(source_type, source_id) DO UPDATE SET
|
|
2943
|
+
work_id = excluded.work_id,
|
|
2944
|
+
conversation_id = excluded.conversation_id,
|
|
2945
|
+
message_id = excluded.message_id,
|
|
2946
|
+
role = excluded.role,
|
|
2947
|
+
title = excluded.title,
|
|
2948
|
+
content = excluded.content,
|
|
2949
|
+
search_content = excluded.search_content,
|
|
2950
|
+
created_at = excluded.created_at
|
|
2951
|
+
`);
|
|
2952
|
+
for (const row of this.all("SELECT id, source_type, title, content FROM ai_history_search")) {
|
|
2953
|
+
const searchContent = row.source_type === "message"
|
|
2954
|
+
? normalizeDocumentSearchText(row.content)
|
|
2955
|
+
: normalizeDocumentSearchText(`${row.title}\n${row.content}`);
|
|
2956
|
+
this.run("UPDATE ai_history_search SET search_content = ? WHERE id = ?", searchContent, row.id);
|
|
2957
|
+
}
|
|
2958
|
+
this.run("INSERT INTO ai_history_search_fts(ai_history_search_fts) VALUES ('rebuild')");
|
|
2959
|
+
const insertTerm = this.raw.prepare("INSERT INTO ai_history_search_short_terms (search_id, term) VALUES (?, ?)");
|
|
2960
|
+
for (const row of this.all("SELECT id, search_content FROM ai_history_search")) {
|
|
2961
|
+
for (const term of documentShortSearchTerms(String(row.search_content)))
|
|
2962
|
+
insertTerm.run(row.id, term);
|
|
2963
|
+
}
|
|
2964
|
+
this.run("INSERT OR IGNORE INTO schema_migrations (version, applied_at) VALUES (74, ?)", new Date().toISOString());
|
|
2965
|
+
});
|
|
2966
|
+
const integrity = this.all("PRAGMA integrity_check");
|
|
2967
|
+
if (integrity.some((row) => row.integrity_check !== "ok")) {
|
|
2968
|
+
throw new Error(`数据库完整性检查失败:${integrity.map((row) => row.integrity_check).join(";")}`);
|
|
2969
|
+
}
|
|
2970
|
+
const foreignKeys = this.all("PRAGMA foreign_key_check");
|
|
2971
|
+
if (foreignKeys.length > 0)
|
|
2972
|
+
throw new Error(`数据库外键检查失败:发现 ${foreignKeys.length} 条异常记录`);
|
|
2973
|
+
}
|
|
2974
|
+
const s3TargetsTablePresentForOrder = this.all("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 's3_backup_targets'").length > 0;
|
|
2975
|
+
const s3SortOrderPresent = s3TargetsTablePresentForOrder
|
|
2976
|
+
&& new Set(this.all("PRAGMA table_info(s3_backup_targets)").map((row) => String(row.name))).has("sort_order");
|
|
2977
|
+
if (!applied.has(75) || !s3SortOrderPresent) {
|
|
2978
|
+
this.transaction(() => {
|
|
2979
|
+
const columns = new Set(this.all("PRAGMA table_info(s3_backup_targets)").map((row) => String(row.name)));
|
|
2980
|
+
if (!columns.has("sort_order")) {
|
|
2981
|
+
this.run("ALTER TABLE s3_backup_targets ADD COLUMN sort_order INTEGER NOT NULL DEFAULT 0");
|
|
2982
|
+
}
|
|
2983
|
+
this.run(`UPDATE s3_backup_targets AS target SET sort_order = (
|
|
2984
|
+
SELECT COUNT(*) FROM s3_backup_targets AS previous
|
|
2985
|
+
WHERE previous.created_at < target.created_at
|
|
2986
|
+
OR (previous.created_at = target.created_at AND previous.id < target.id)
|
|
2987
|
+
)`);
|
|
2988
|
+
this.run("CREATE INDEX IF NOT EXISTS idx_s3_backup_targets_order ON s3_backup_targets(sort_order, created_at, id)");
|
|
2989
|
+
this.run("INSERT OR IGNORE INTO schema_migrations (version, applied_at) VALUES (75, ?)", new Date().toISOString());
|
|
2765
2990
|
});
|
|
2766
2991
|
const integrity = this.all("PRAGMA integrity_check");
|
|
2767
2992
|
if (integrity.some((row) => row.integrity_check !== "ok")) {
|
|
@@ -2771,6 +2996,69 @@ export class Database {
|
|
|
2771
2996
|
if (foreignKeys.length > 0)
|
|
2772
2997
|
throw new Error(`数据库外键检查失败:发现 ${foreignKeys.length} 条异常记录`);
|
|
2773
2998
|
}
|
|
2999
|
+
if (!applied.has(76)) {
|
|
3000
|
+
this.raw.exec("PRAGMA foreign_keys = OFF");
|
|
3001
|
+
try {
|
|
3002
|
+
this.transaction(() => {
|
|
3003
|
+
this.run(`CREATE TABLE work_ai_settings_v76 (
|
|
3004
|
+
work_id TEXT PRIMARY KEY REFERENCES works(id) ON DELETE CASCADE,
|
|
3005
|
+
system_prompt TEXT NOT NULL DEFAULT '',
|
|
3006
|
+
daily_token_quota INTEGER CHECK(daily_token_quota IS NULL OR daily_token_quota >= 10000),
|
|
3007
|
+
auto_run_enabled INTEGER NOT NULL DEFAULT 0,
|
|
3008
|
+
auto_run_concurrency INTEGER NOT NULL DEFAULT 2,
|
|
3009
|
+
auto_run_batch_limit INTEGER NOT NULL DEFAULT 20,
|
|
3010
|
+
auto_run_daily_task_limit INTEGER NOT NULL DEFAULT 0 CHECK(auto_run_daily_task_limit BETWEEN 0 AND 10000),
|
|
3011
|
+
auto_run_failure_threshold INTEGER NOT NULL DEFAULT 3 CHECK(auto_run_failure_threshold BETWEEN 1 AND 10),
|
|
3012
|
+
auto_run_paused INTEGER NOT NULL DEFAULT 0 CHECK(auto_run_paused IN (0, 1)),
|
|
3013
|
+
auto_run_pause_reason TEXT NOT NULL DEFAULT '',
|
|
3014
|
+
auto_run_resume_at TEXT,
|
|
3015
|
+
auto_run_consecutive_failures INTEGER NOT NULL DEFAULT 0,
|
|
3016
|
+
book_summary_context_percent INTEGER NOT NULL DEFAULT 50 CHECK(book_summary_context_percent BETWEEN 1 AND 90),
|
|
3017
|
+
context_compact_threshold INTEGER NOT NULL DEFAULT 85 CHECK(context_compact_threshold BETWEEN 50 AND 90),
|
|
3018
|
+
agent_tool_call_limit INTEGER NOT NULL DEFAULT 12 CHECK(agent_tool_call_limit BETWEEN 5 AND 1000),
|
|
3019
|
+
agent_tool_call_global_multiplier INTEGER NOT NULL DEFAULT 3 CHECK(agent_tool_call_global_multiplier BETWEEN 1 AND 6),
|
|
3020
|
+
agent_tools_json TEXT NOT NULL DEFAULT '["story_index","read_chapters","search_story_entities","grep","read_character_sections","search_drafts","image"]',
|
|
3021
|
+
title_generation_model_id TEXT REFERENCES models(id) ON DELETE SET NULL,
|
|
3022
|
+
image_tool_model_id TEXT REFERENCES models(id) ON DELETE SET NULL,
|
|
3023
|
+
always_include_setting_info INTEGER NOT NULL DEFAULT 0 CHECK(always_include_setting_info IN (0, 1)),
|
|
3024
|
+
updated_at TEXT NOT NULL
|
|
3025
|
+
)`);
|
|
3026
|
+
this.run(`INSERT INTO work_ai_settings_v76 (
|
|
3027
|
+
work_id, system_prompt, daily_token_quota, auto_run_enabled, auto_run_concurrency, auto_run_batch_limit,
|
|
3028
|
+
auto_run_daily_task_limit, auto_run_failure_threshold, auto_run_paused, auto_run_pause_reason,
|
|
3029
|
+
auto_run_resume_at, auto_run_consecutive_failures, book_summary_context_percent,
|
|
3030
|
+
context_compact_threshold, agent_tool_call_limit, agent_tool_call_global_multiplier,
|
|
3031
|
+
agent_tools_json, title_generation_model_id, image_tool_model_id, always_include_setting_info, updated_at
|
|
3032
|
+
)
|
|
3033
|
+
SELECT
|
|
3034
|
+
work_id, system_prompt, daily_token_quota, auto_run_enabled, auto_run_concurrency, auto_run_batch_limit,
|
|
3035
|
+
auto_run_daily_task_limit, auto_run_failure_threshold, auto_run_paused, auto_run_pause_reason,
|
|
3036
|
+
auto_run_resume_at, auto_run_consecutive_failures, book_summary_context_percent,
|
|
3037
|
+
context_compact_threshold,
|
|
3038
|
+
CASE
|
|
3039
|
+
WHEN agent_tool_call_limit < 5 THEN 5
|
|
3040
|
+
WHEN agent_tool_call_limit > 1000 THEN 1000
|
|
3041
|
+
ELSE agent_tool_call_limit
|
|
3042
|
+
END,
|
|
3043
|
+
agent_tool_call_global_multiplier,
|
|
3044
|
+
agent_tools_json, title_generation_model_id, image_tool_model_id, always_include_setting_info, updated_at
|
|
3045
|
+
FROM work_ai_settings`);
|
|
3046
|
+
this.run("DROP TABLE work_ai_settings");
|
|
3047
|
+
this.run("ALTER TABLE work_ai_settings_v76 RENAME TO work_ai_settings");
|
|
3048
|
+
this.run("INSERT INTO schema_migrations (version, applied_at) VALUES (76, ?)", new Date().toISOString());
|
|
3049
|
+
});
|
|
3050
|
+
}
|
|
3051
|
+
finally {
|
|
3052
|
+
this.raw.exec("PRAGMA foreign_keys = ON");
|
|
3053
|
+
}
|
|
3054
|
+
const integrity = this.all("PRAGMA integrity_check");
|
|
3055
|
+
if (integrity.some((row) => row.integrity_check !== "ok")) {
|
|
3056
|
+
throw new Error(`数据库完整性检查失败:${integrity.map((row) => row.integrity_check).join(";")}`);
|
|
3057
|
+
}
|
|
3058
|
+
const foreignKeys = this.all("PRAGMA foreign_key_check");
|
|
3059
|
+
if (foreignKeys.length > 0)
|
|
3060
|
+
throw new Error(`数据库外键检查失败:发现 ${foreignKeys.length} 条异常记录`);
|
|
3061
|
+
}
|
|
2774
3062
|
}
|
|
2775
3063
|
normalizeCharacterName(value) {
|
|
2776
3064
|
return value.normalize("NFKC").trim().replace(/\s+/gu, " ").toLocaleLowerCase("zh-CN");
|
|
@@ -2790,6 +3078,8 @@ export class Database {
|
|
|
2790
3078
|
WHERE status = 'running'`, timestamp);
|
|
2791
3079
|
this.run(`UPDATE analysis_tasks SET status = 'partial', failure_json = ?, updated_at = ?
|
|
2792
3080
|
WHERE status = 'running'`, JSON.stringify([{ message: "服务重启导致任务中断" }]), timestamp);
|
|
3081
|
+
this.run(`UPDATE s3_backup_runs SET status = 'failed', error_message = COALESCE(error_message, '服务重启导致备份中断'), finished_at = ?
|
|
3082
|
+
WHERE status = 'running'`, timestamp);
|
|
2793
3083
|
}
|
|
2794
3084
|
}
|
|
2795
3085
|
//# sourceMappingURL=database.js.map
|