890 lines
27 KiB
JavaScript
890 lines
27 KiB
JavaScript
import Database from 'better-sqlite3';
|
|
import path from 'path';
|
|
import fs from 'fs';
|
|
import { fileURLToPath } from 'url';
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const DATA_DIR = path.join(__dirname, '../data');
|
|
const DB_PATH = path.join(DATA_DIR, 'paradigms.db');
|
|
|
|
if (!fs.existsSync(DATA_DIR)) {
|
|
fs.mkdirSync(DATA_DIR, { recursive: true });
|
|
}
|
|
|
|
const db = new Database(DB_PATH);
|
|
|
|
db.pragma('journal_mode = WAL');
|
|
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS materials (
|
|
id TEXT PRIMARY KEY,
|
|
type TEXT NOT NULL,
|
|
title TEXT NOT NULL,
|
|
source TEXT,
|
|
date TEXT,
|
|
tags TEXT,
|
|
related_dimension_sets TEXT,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
is_default INTEGER DEFAULT 0
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS reference_excerpts (
|
|
id TEXT PRIMARY KEY,
|
|
reference_id TEXT NOT NULL,
|
|
topic TEXT NOT NULL,
|
|
content TEXT NOT NULL,
|
|
applicable_dimensions TEXT,
|
|
use_for TEXT,
|
|
FOREIGN KEY (reference_id) REFERENCES materials(id) ON DELETE CASCADE
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS paradigms (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
icon TEXT,
|
|
description TEXT,
|
|
tags TEXT,
|
|
tag_class TEXT,
|
|
system_constraints TEXT,
|
|
dimension_set_id TEXT,
|
|
custom_dimensions TEXT,
|
|
logic_paradigms TEXT,
|
|
auto_match_refs INTEGER DEFAULT 1,
|
|
selected_refs TEXT,
|
|
specialized_prompt TEXT,
|
|
expert_guidelines TEXT,
|
|
outline_template TEXT,
|
|
recommended_tags TEXT,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
is_custom INTEGER DEFAULT 0
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS dimension_sets (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
description TEXT,
|
|
applicable_for TEXT,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
is_custom INTEGER DEFAULT 0
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS dimensions (
|
|
id TEXT PRIMARY KEY,
|
|
dimension_set_id TEXT NOT NULL,
|
|
name TEXT NOT NULL,
|
|
focus TEXT,
|
|
keywords TEXT,
|
|
negative_keywords TEXT,
|
|
positive_benchmark TEXT,
|
|
sort_order INTEGER DEFAULT 0,
|
|
FOREIGN KEY (dimension_set_id) REFERENCES dimension_sets(id) ON DELETE CASCADE
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS user_config (
|
|
key TEXT PRIMARY KEY,
|
|
value TEXT,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS analysis_history (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
paradigm_id TEXT,
|
|
input_text TEXT,
|
|
result TEXT,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS documents (
|
|
id TEXT PRIMARY KEY,
|
|
title TEXT NOT NULL,
|
|
content TEXT,
|
|
paradigm_id TEXT,
|
|
dimension_set_id TEXT,
|
|
selected_refs TEXT,
|
|
status TEXT DEFAULT 'draft',
|
|
word_count INTEGER DEFAULT 0,
|
|
tags TEXT,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS document_versions (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
document_id TEXT NOT NULL,
|
|
content TEXT,
|
|
version_number INTEGER,
|
|
change_note TEXT,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (document_id) REFERENCES documents(id) ON DELETE CASCADE
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS outline_materials (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
content TEXT NOT NULL,
|
|
word_count INTEGER DEFAULT 0,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
`);
|
|
|
|
const ensureColumn = (table, column, definition) => {
|
|
const columns = db.prepare(`PRAGMA table_info(${table})`).all();
|
|
const exists = columns.some(col => col.name === column);
|
|
if (!exists) {
|
|
try {
|
|
db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
|
|
} catch (error) {
|
|
console.warn(`⚠️ 字段迁移失败: ${table}.${column}`, error.message);
|
|
}
|
|
}
|
|
};
|
|
|
|
const ensureSchema = () => {
|
|
ensureColumn('paradigms', 'system_constraints', 'TEXT');
|
|
ensureColumn('paradigms', 'dimension_set_id', 'TEXT');
|
|
ensureColumn('paradigms', 'custom_dimensions', 'TEXT');
|
|
ensureColumn('paradigms', 'logic_paradigms', 'TEXT');
|
|
ensureColumn('paradigms', 'auto_match_refs', 'INTEGER DEFAULT 1');
|
|
ensureColumn('paradigms', 'selected_refs', 'TEXT');
|
|
ensureColumn('paradigms', 'outline_template', 'TEXT');
|
|
ensureColumn('paradigms', 'recommended_tags', 'TEXT');
|
|
ensureColumn('paradigms', 'tag_class', "TEXT");
|
|
ensureColumn('paradigms', 'tags', 'TEXT');
|
|
ensureColumn('paradigms', 'specialized_prompt', 'TEXT');
|
|
ensureColumn('paradigms', 'expert_guidelines', 'TEXT');
|
|
ensureColumn('paradigms', 'is_custom', 'INTEGER DEFAULT 0');
|
|
ensureColumn('paradigms', 'created_at', 'TEXT');
|
|
ensureColumn('paradigms', 'updated_at', 'TEXT');
|
|
};
|
|
|
|
ensureSchema();
|
|
|
|
const parseJson = (value, fallback) => {
|
|
if (!value) return fallback;
|
|
try {
|
|
return JSON.parse(value);
|
|
} catch {
|
|
return fallback;
|
|
}
|
|
};
|
|
|
|
const getExcerptsByReferenceId = (referenceId) => {
|
|
const rows = db.prepare('SELECT * FROM reference_excerpts WHERE reference_id = ?').all(referenceId);
|
|
return rows.map(row => ({
|
|
...row,
|
|
applicableDimensions: parseJson(row.applicable_dimensions, []),
|
|
useFor: row.use_for || null
|
|
}));
|
|
};
|
|
|
|
const seedDefaultReferences = async () => {
|
|
const count = db.prepare('SELECT COUNT(*) as count FROM materials').get().count;
|
|
if (count > 0) return;
|
|
|
|
try {
|
|
const { REFERENCES } = await import('../src/config/references.js');
|
|
const insertMaterial = db.prepare(`
|
|
INSERT INTO materials (id, type, title, source, date, tags, related_dimension_sets, is_default)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
`);
|
|
const insertExcerpt = db.prepare(`
|
|
INSERT INTO reference_excerpts (id, reference_id, topic, content, applicable_dimensions, use_for)
|
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
`);
|
|
|
|
const insertMany = db.transaction((items) => {
|
|
items.forEach((ref) => {
|
|
insertMaterial.run(
|
|
ref.id,
|
|
ref.type,
|
|
ref.title,
|
|
ref.source || null,
|
|
ref.date || null,
|
|
JSON.stringify(ref.tags || []),
|
|
JSON.stringify(ref.relatedDimensionSets || []),
|
|
1
|
|
);
|
|
|
|
if (ref.excerpts?.length) {
|
|
ref.excerpts.forEach((excerpt, index) => {
|
|
insertExcerpt.run(
|
|
excerpt.id || `${ref.id}-excerpt-${index}`,
|
|
ref.id,
|
|
excerpt.topic,
|
|
excerpt.content,
|
|
JSON.stringify(excerpt.applicableDimensions || []),
|
|
excerpt.useFor || null
|
|
);
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
insertMany(Object.values(REFERENCES));
|
|
console.log('✅ 默认素材导入完成');
|
|
} catch (error) {
|
|
console.warn('⚠️ 默认素材导入失败:', error.message);
|
|
}
|
|
};
|
|
|
|
await seedDefaultReferences();
|
|
|
|
/**
|
|
* 同步现有的提纲素材到通用素材库
|
|
*/
|
|
const syncExistingOutlineMaterials = () => {
|
|
try {
|
|
const materials = db.prepare('SELECT * FROM outline_materials').all();
|
|
if (materials.length === 0) return;
|
|
|
|
console.log(`🔍 正在同步 ${materials.length} 个存量提纲素材到通用素材库...`);
|
|
db.transaction(() => {
|
|
materials.forEach(mat => {
|
|
syncToReferences(mat.id, mat.name, mat.content);
|
|
});
|
|
})();
|
|
|
|
console.log(`✅ 存量提纲素材同步完成`);
|
|
} catch (err) {
|
|
console.error('⚠️ 同步存量素材失败:', err.message);
|
|
}
|
|
};
|
|
|
|
syncExistingOutlineMaterials();
|
|
|
|
console.log('📦 SQLite 数据库初始化完成:', DB_PATH);
|
|
|
|
export function getAllReferences() {
|
|
const refs = db.prepare('SELECT * FROM materials ORDER BY created_at DESC').all();
|
|
return refs.map(ref => ({
|
|
...ref,
|
|
tags: parseJson(ref.tags, []),
|
|
relatedDimensionSets: parseJson(ref.related_dimension_sets, []),
|
|
excerpts: getExcerptsByReferenceId(ref.id)
|
|
}));
|
|
}
|
|
|
|
export function getReferenceById(id) {
|
|
const ref = db.prepare('SELECT * FROM materials WHERE id = ?').get(id);
|
|
if (!ref) return null;
|
|
return {
|
|
...ref,
|
|
tags: parseJson(ref.tags, []),
|
|
relatedDimensionSets: parseJson(ref.related_dimension_sets, []),
|
|
excerpts: getExcerptsByReferenceId(ref.id)
|
|
};
|
|
}
|
|
|
|
export function getReferencesByType(type) {
|
|
const refs = db.prepare('SELECT * FROM materials WHERE type = ? ORDER BY created_at DESC').all(type);
|
|
return refs.map(ref => ({
|
|
...ref,
|
|
tags: parseJson(ref.tags, []),
|
|
relatedDimensionSets: parseJson(ref.related_dimension_sets, []),
|
|
excerpts: getExcerptsByReferenceId(ref.id)
|
|
}));
|
|
}
|
|
|
|
export function addReference(reference) {
|
|
const id = reference.id || `ref-${Date.now()}`;
|
|
db.prepare(`
|
|
INSERT INTO materials (id, type, title, source, date, tags, related_dimension_sets, is_default)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
`).run(
|
|
id,
|
|
reference.type,
|
|
reference.title,
|
|
reference.source || null,
|
|
reference.date || null,
|
|
JSON.stringify(reference.tags || []),
|
|
JSON.stringify(reference.relatedDimensionSets || []),
|
|
reference.isDefault ? 1 : 0
|
|
);
|
|
|
|
if (reference.excerpts?.length) {
|
|
const insertExcerpt = db.prepare(`
|
|
INSERT INTO reference_excerpts (id, reference_id, topic, content, applicable_dimensions, use_for)
|
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
`);
|
|
|
|
reference.excerpts.forEach((excerpt, index) => {
|
|
insertExcerpt.run(
|
|
excerpt.id || `${id}-excerpt-${index}`,
|
|
id,
|
|
excerpt.topic,
|
|
excerpt.content,
|
|
JSON.stringify(excerpt.applicableDimensions || []),
|
|
excerpt.useFor || null
|
|
);
|
|
});
|
|
}
|
|
|
|
return id;
|
|
}
|
|
|
|
export function updateReference(id, updates) {
|
|
const setClauses = [];
|
|
const params = [];
|
|
|
|
if (updates.type !== undefined) {
|
|
setClauses.push('type = ?');
|
|
params.push(updates.type);
|
|
}
|
|
if (updates.title !== undefined) {
|
|
setClauses.push('title = ?');
|
|
params.push(updates.title);
|
|
}
|
|
if (updates.source !== undefined) {
|
|
setClauses.push('source = ?');
|
|
params.push(updates.source);
|
|
}
|
|
if (updates.tags !== undefined) {
|
|
setClauses.push('tags = ?');
|
|
params.push(JSON.stringify(updates.tags));
|
|
}
|
|
if (updates.relatedDimensionSets !== undefined) {
|
|
setClauses.push('related_dimension_sets = ?');
|
|
params.push(JSON.stringify(updates.relatedDimensionSets));
|
|
}
|
|
|
|
setClauses.push('updated_at = CURRENT_TIMESTAMP');
|
|
params.push(id);
|
|
|
|
db.prepare(`UPDATE materials SET ${setClauses.join(', ')} WHERE id = ?`).run(...params);
|
|
return true;
|
|
}
|
|
|
|
export function deleteReference(id) {
|
|
db.prepare('DELETE FROM reference_excerpts WHERE reference_id = ?').run(id);
|
|
db.prepare('DELETE FROM materials WHERE id = ?').run(id);
|
|
return true;
|
|
}
|
|
|
|
export function getAllParadigms() {
|
|
const paradigms = db.prepare('SELECT * FROM paradigms ORDER BY is_custom ASC, created_at DESC').all();
|
|
return paradigms.map(p => ({
|
|
...p,
|
|
tagClass: p.tag_class || 'bg-blue-900/30 text-blue-300',
|
|
tags: parseJson(p.tags, []),
|
|
systemConstraints: parseJson(p.system_constraints, []),
|
|
customDimensions: parseJson(p.custom_dimensions, null),
|
|
logicParadigms: parseJson(p.logic_paradigms, null),
|
|
selectedRefs: parseJson(p.selected_refs, []),
|
|
specializedPrompt: p.specialized_prompt || null,
|
|
expertGuidelines: parseJson(p.expert_guidelines, null),
|
|
outlineTemplate: p.outline_template || null,
|
|
recommendedTags: parseJson(p.recommended_tags, []),
|
|
isCustom: p.is_custom === 1,
|
|
autoMatchRefs: p.auto_match_refs === 1
|
|
}));
|
|
}
|
|
|
|
export function getParadigmById(id) {
|
|
const p = db.prepare('SELECT * FROM paradigms WHERE id = ?').get(id);
|
|
if (!p) return null;
|
|
return {
|
|
...p,
|
|
tagClass: p.tag_class || 'bg-blue-900/30 text-blue-300',
|
|
tags: parseJson(p.tags, []),
|
|
systemConstraints: parseJson(p.system_constraints, []),
|
|
customDimensions: parseJson(p.custom_dimensions, null),
|
|
logicParadigms: parseJson(p.logic_paradigms, null),
|
|
selectedRefs: parseJson(p.selected_refs, []),
|
|
specializedPrompt: p.specialized_prompt || null,
|
|
expertGuidelines: parseJson(p.expert_guidelines, null),
|
|
outlineTemplate: p.outline_template || null,
|
|
recommendedTags: parseJson(p.recommended_tags, []),
|
|
isCustom: p.is_custom === 1,
|
|
autoMatchRefs: p.auto_match_refs === 1
|
|
};
|
|
}
|
|
|
|
export function addParadigm(paradigm) {
|
|
const id = paradigm.id || `paradigm-${Date.now()}`;
|
|
db.prepare(`
|
|
INSERT INTO paradigms (id, name, icon, description, tags, tag_class, system_constraints,
|
|
dimension_set_id, custom_dimensions, logic_paradigms, auto_match_refs, selected_refs,
|
|
specialized_prompt, expert_guidelines, outline_template, recommended_tags, is_custom)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
`).run(
|
|
id,
|
|
paradigm.name,
|
|
paradigm.icon || '📝',
|
|
paradigm.description || null,
|
|
JSON.stringify(paradigm.tags || []),
|
|
paradigm.tagClass || 'bg-blue-900/30 text-blue-300',
|
|
JSON.stringify(paradigm.systemConstraints || []),
|
|
paradigm.dimensionSetId || null,
|
|
paradigm.customDimensions ? JSON.stringify(paradigm.customDimensions) : null,
|
|
paradigm.logicParadigms ? JSON.stringify(paradigm.logicParadigms) : null,
|
|
paradigm.autoMatchRefs !== false ? 1 : 0,
|
|
JSON.stringify(paradigm.selectedRefs || []),
|
|
paradigm.specializedPrompt || null,
|
|
paradigm.expertGuidelines ? JSON.stringify(paradigm.expertGuidelines) : null,
|
|
paradigm.outlineTemplate || null,
|
|
paradigm.recommendedTags ? JSON.stringify(paradigm.recommendedTags || []) : null,
|
|
paradigm.isCustom ? 1 : 0
|
|
);
|
|
|
|
return id;
|
|
}
|
|
|
|
export function updateParadigm(id, updates) {
|
|
const setClauses = [];
|
|
const params = [];
|
|
|
|
const fieldMap = {
|
|
name: 'name',
|
|
icon: 'icon',
|
|
description: 'description',
|
|
tagClass: 'tag_class',
|
|
dimensionSetId: 'dimension_set_id',
|
|
autoMatchRefs: 'auto_match_refs'
|
|
};
|
|
|
|
Object.entries(updates).forEach(([key, value]) => {
|
|
if (fieldMap[key]) {
|
|
setClauses.push(`${fieldMap[key]} = ?`);
|
|
params.push(key === 'autoMatchRefs' ? (value ? 1 : 0) : value);
|
|
}
|
|
});
|
|
|
|
if (updates.tags !== undefined) {
|
|
setClauses.push('tags = ?');
|
|
params.push(JSON.stringify(updates.tags));
|
|
}
|
|
if (updates.systemConstraints !== undefined) {
|
|
setClauses.push('system_constraints = ?');
|
|
params.push(JSON.stringify(updates.systemConstraints));
|
|
}
|
|
if (updates.customDimensions !== undefined) {
|
|
setClauses.push('custom_dimensions = ?');
|
|
params.push(updates.customDimensions ? JSON.stringify(updates.customDimensions) : null);
|
|
}
|
|
if (updates.logicParadigms !== undefined) {
|
|
setClauses.push('logic_paradigms = ?');
|
|
params.push(updates.logicParadigms ? JSON.stringify(updates.logicParadigms) : null);
|
|
}
|
|
if (updates.selectedRefs !== undefined) {
|
|
setClauses.push('selected_refs = ?');
|
|
params.push(JSON.stringify(updates.selectedRefs));
|
|
}
|
|
if (updates.specializedPrompt !== undefined) {
|
|
setClauses.push('specialized_prompt = ?');
|
|
params.push(updates.specializedPrompt);
|
|
}
|
|
if (updates.expertGuidelines !== undefined) {
|
|
setClauses.push('expert_guidelines = ?');
|
|
params.push(updates.expertGuidelines ? JSON.stringify(updates.expertGuidelines) : null);
|
|
}
|
|
if (updates.outlineTemplate !== undefined) {
|
|
setClauses.push('outline_template = ?');
|
|
params.push(updates.outlineTemplate);
|
|
}
|
|
if (updates.recommendedTags !== undefined) {
|
|
setClauses.push('recommended_tags = ?');
|
|
params.push(updates.recommendedTags ? JSON.stringify(updates.recommendedTags) : null);
|
|
}
|
|
|
|
setClauses.push('updated_at = CURRENT_TIMESTAMP');
|
|
params.push(id);
|
|
|
|
db.prepare(`UPDATE paradigms SET ${setClauses.join(', ')} WHERE id = ?`).run(...params);
|
|
return true;
|
|
}
|
|
|
|
export function deleteParadigm(id) {
|
|
db.prepare('DELETE FROM paradigms WHERE id = ?').run(id);
|
|
return true;
|
|
}
|
|
|
|
export function getConfig(key, defaultValue = null) {
|
|
const result = db.prepare('SELECT value FROM user_config WHERE key = ?').get(key);
|
|
if (!result) return defaultValue;
|
|
return parseJson(result.value, result.value);
|
|
}
|
|
|
|
export function setConfig(key, value) {
|
|
const valueStr = typeof value === 'string' ? value : JSON.stringify(value);
|
|
db.prepare(`
|
|
INSERT INTO user_config (key, value, updated_at)
|
|
VALUES (?, ?, CURRENT_TIMESTAMP)
|
|
ON CONFLICT(key) DO UPDATE SET value = ?, updated_at = CURRENT_TIMESTAMP
|
|
`).run(key, valueStr, valueStr);
|
|
return true;
|
|
}
|
|
|
|
export function getAllDocuments() {
|
|
const docs = db.prepare('SELECT * FROM documents ORDER BY updated_at DESC').all();
|
|
return docs.map(doc => ({
|
|
...doc,
|
|
tags: parseJson(doc.tags, []),
|
|
selectedRefs: parseJson(doc.selected_refs, [])
|
|
}));
|
|
}
|
|
|
|
export function getDocumentById(id) {
|
|
const doc = db.prepare('SELECT * FROM documents WHERE id = ?').get(id);
|
|
if (!doc) return null;
|
|
return {
|
|
...doc,
|
|
tags: parseJson(doc.tags, []),
|
|
selectedRefs: parseJson(doc.selected_refs, []),
|
|
versions: getDocumentVersions(id)
|
|
};
|
|
}
|
|
|
|
export function getDocumentVersions(documentId) {
|
|
return db.prepare('SELECT * FROM document_versions WHERE document_id = ? ORDER BY version_number DESC').all(documentId);
|
|
}
|
|
|
|
export function createDocument(document) {
|
|
const id = document.id || `doc-${Date.now()}`;
|
|
db.prepare(`
|
|
INSERT INTO documents (id, title, content, paradigm_id, dimension_set_id, selected_refs, status, word_count, tags)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
`).run(
|
|
id,
|
|
document.title || '未命名文稿',
|
|
document.content || '',
|
|
document.paradigmId || null,
|
|
document.dimensionSetId || null,
|
|
JSON.stringify(document.selectedRefs || []),
|
|
document.status || 'draft',
|
|
document.wordCount || 0,
|
|
JSON.stringify(document.tags || [])
|
|
);
|
|
|
|
return id;
|
|
}
|
|
|
|
export function updateDocument(id, updates) {
|
|
const setClauses = [];
|
|
const params = [];
|
|
|
|
if (updates.title !== undefined) {
|
|
setClauses.push('title = ?');
|
|
params.push(updates.title);
|
|
}
|
|
if (updates.content !== undefined) {
|
|
setClauses.push('content = ?');
|
|
params.push(updates.content);
|
|
setClauses.push('word_count = ?');
|
|
params.push(updates.content.length);
|
|
}
|
|
if (updates.paradigmId !== undefined) {
|
|
setClauses.push('paradigm_id = ?');
|
|
params.push(updates.paradigmId);
|
|
}
|
|
if (updates.dimensionSetId !== undefined) {
|
|
setClauses.push('dimension_set_id = ?');
|
|
params.push(updates.dimensionSetId);
|
|
}
|
|
if (updates.status !== undefined) {
|
|
setClauses.push('status = ?');
|
|
params.push(updates.status);
|
|
}
|
|
if (updates.tags !== undefined) {
|
|
setClauses.push('tags = ?');
|
|
params.push(JSON.stringify(updates.tags));
|
|
}
|
|
if (updates.selectedRefs !== undefined) {
|
|
setClauses.push('selected_refs = ?');
|
|
params.push(JSON.stringify(updates.selectedRefs));
|
|
}
|
|
|
|
setClauses.push('updated_at = CURRENT_TIMESTAMP');
|
|
params.push(id);
|
|
|
|
db.prepare(`UPDATE documents SET ${setClauses.join(', ')} WHERE id = ?`).run(...params);
|
|
return true;
|
|
}
|
|
|
|
export function saveDocumentVersion(documentId, content, changeNote = '') {
|
|
const result = db.prepare('SELECT MAX(version_number) as max_version FROM document_versions WHERE document_id = ?').get(documentId);
|
|
const nextVersion = (result?.max_version || 0) + 1;
|
|
|
|
db.prepare(`
|
|
INSERT INTO document_versions (document_id, content, version_number, change_note)
|
|
VALUES (?, ?, ?, ?)
|
|
`).run(documentId, content, nextVersion, changeNote);
|
|
|
|
return nextVersion;
|
|
}
|
|
|
|
export function deleteDocument(id) {
|
|
db.prepare('DELETE FROM document_versions WHERE document_id = ?').run(id);
|
|
db.prepare('DELETE FROM documents WHERE id = ?').run(id);
|
|
return true;
|
|
}
|
|
|
|
export function clearDocuments() {
|
|
db.prepare('DELETE FROM document_versions').run();
|
|
db.prepare('DELETE FROM documents').run();
|
|
return true;
|
|
}
|
|
|
|
export function getDocumentsByStatus(status) {
|
|
const docs = db.prepare('SELECT * FROM documents WHERE status = ? ORDER BY updated_at DESC').all(status);
|
|
return docs.map(doc => ({
|
|
...doc,
|
|
tags: parseJson(doc.tags, []),
|
|
selectedRefs: parseJson(doc.selected_refs, [])
|
|
}));
|
|
}
|
|
|
|
export function getAllOutlineMaterials() {
|
|
return db.prepare('SELECT * FROM outline_materials ORDER BY created_at DESC').all();
|
|
}
|
|
|
|
/**
|
|
* 将提纲素材同步到通用素材库 (materials/excerpts)
|
|
* 提纲素材作为 'outline' 类型存储,便于在通用素材库中管理
|
|
*/
|
|
const syncToReferences = (id, name, content) => {
|
|
const refId = `ref-${id}`; // 使用固定前缀关联
|
|
|
|
// 1. 更新或插入到 materials 表
|
|
db.prepare(`
|
|
INSERT INTO materials (id, type, title, source, is_default, created_at, updated_at)
|
|
VALUES (?, 'outline', ?, '提纲写作', 0, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
|
ON CONFLICT(id) DO UPDATE SET
|
|
title = EXCLUDED.title,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
`).run(refId, name);
|
|
|
|
// 2. 更新或插入到 reference_excerpts 表
|
|
const excerptId = `${refId}-content`;
|
|
db.prepare(`
|
|
INSERT INTO reference_excerpts (id, reference_id, topic, content)
|
|
VALUES (?, ?, '正文', ?)
|
|
ON CONFLICT(id) DO UPDATE SET
|
|
content = EXCLUDED.content
|
|
`).run(excerptId, refId, content);
|
|
};
|
|
|
|
export function addOutlineMaterial(material) {
|
|
const id = material.id || `omat-${Date.now()}`;
|
|
db.prepare(`
|
|
INSERT INTO outline_materials (id, name, content, word_count)
|
|
VALUES (?, ?, ?, ?)
|
|
`).run(
|
|
id,
|
|
material.name,
|
|
material.content,
|
|
material.content?.length || 0
|
|
);
|
|
|
|
// 同步到通用素材库
|
|
try {
|
|
syncToReferences(id, material.name, material.content);
|
|
} catch (err) {
|
|
console.error('同步素材到通用库失败:', err);
|
|
}
|
|
|
|
return id;
|
|
}
|
|
|
|
export function updateOutlineMaterial(id, updates) {
|
|
const setClauses = [];
|
|
const params = [];
|
|
|
|
if (updates.name !== undefined) {
|
|
setClauses.push('name = ?');
|
|
params.push(updates.name);
|
|
}
|
|
if (updates.content !== undefined) {
|
|
setClauses.push('content = ?');
|
|
params.push(updates.content);
|
|
setClauses.push('word_count = ?');
|
|
params.push(updates.content?.length || 0);
|
|
}
|
|
|
|
setClauses.push('updated_at = CURRENT_TIMESTAMP');
|
|
params.push(id);
|
|
|
|
db.prepare(`UPDATE outline_materials SET ${setClauses.join(', ')} WHERE id = ?`).run(...params);
|
|
|
|
// 如果更新了名称或内容,需要同步
|
|
try {
|
|
const current = db.prepare('SELECT * FROM outline_materials WHERE id = ?').get(id);
|
|
if (current) {
|
|
syncToReferences(id, current.name, current.content);
|
|
}
|
|
} catch (err) {
|
|
console.error('更新素材同步失败:', err);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
export function deleteOutlineMaterial(id) {
|
|
db.prepare('DELETE FROM outline_materials WHERE id = ?').run(id);
|
|
|
|
// 同时从通用库删除 (如果存在)
|
|
try {
|
|
const refId = `ref-${id}`;
|
|
db.prepare('DELETE FROM reference_excerpts WHERE reference_id = ?').run(refId);
|
|
db.prepare('DELETE FROM materials WHERE id = ?').run(refId);
|
|
} catch (err) {
|
|
console.error('删除素材同步失败:', err);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
export function exportDatabase() {
|
|
return fs.readFileSync(DB_PATH);
|
|
}
|
|
|
|
export function exportAsJSON() {
|
|
return {
|
|
references: getAllReferences(),
|
|
paradigms: getAllParadigms(),
|
|
documents: getAllDocuments(),
|
|
config: db.prepare('SELECT * FROM user_config').all(),
|
|
exportedAt: new Date().toISOString()
|
|
};
|
|
}
|
|
|
|
export async function resetDatabase() {
|
|
db.exec(`
|
|
DROP TABLE IF EXISTS reference_excerpts;
|
|
DROP TABLE IF EXISTS materials;
|
|
DROP TABLE IF EXISTS paradigms;
|
|
DROP TABLE IF EXISTS dimension_sets;
|
|
DROP TABLE IF EXISTS dimensions;
|
|
DROP TABLE IF EXISTS user_config;
|
|
DROP TABLE IF EXISTS analysis_history;
|
|
DROP TABLE IF EXISTS documents;
|
|
DROP TABLE IF EXISTS document_versions;
|
|
DROP TABLE IF EXISTS outline_materials;
|
|
`);
|
|
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS materials (
|
|
id TEXT PRIMARY KEY,
|
|
type TEXT NOT NULL,
|
|
title TEXT NOT NULL,
|
|
source TEXT,
|
|
date TEXT,
|
|
tags TEXT,
|
|
related_dimension_sets TEXT,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
is_default INTEGER DEFAULT 0
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS reference_excerpts (
|
|
id TEXT PRIMARY KEY,
|
|
reference_id TEXT NOT NULL,
|
|
topic TEXT NOT NULL,
|
|
content TEXT NOT NULL,
|
|
applicable_dimensions TEXT,
|
|
use_for TEXT,
|
|
FOREIGN KEY (reference_id) REFERENCES materials(id) ON DELETE CASCADE
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS paradigms (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
icon TEXT,
|
|
description TEXT,
|
|
tags TEXT,
|
|
tag_class TEXT,
|
|
system_constraints TEXT,
|
|
dimension_set_id TEXT,
|
|
custom_dimensions TEXT,
|
|
logic_paradigms TEXT,
|
|
auto_match_refs INTEGER DEFAULT 1,
|
|
selected_refs TEXT,
|
|
specialized_prompt TEXT,
|
|
expert_guidelines TEXT,
|
|
outline_template TEXT,
|
|
recommended_tags TEXT,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
is_custom INTEGER DEFAULT 0
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS dimension_sets (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
description TEXT,
|
|
applicable_for TEXT,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
is_custom INTEGER DEFAULT 0
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS dimensions (
|
|
id TEXT PRIMARY KEY,
|
|
dimension_set_id TEXT NOT NULL,
|
|
name TEXT NOT NULL,
|
|
focus TEXT,
|
|
keywords TEXT,
|
|
negative_keywords TEXT,
|
|
positive_benchmark TEXT,
|
|
sort_order INTEGER DEFAULT 0,
|
|
FOREIGN KEY (dimension_set_id) REFERENCES dimension_sets(id) ON DELETE CASCADE
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS user_config (
|
|
key TEXT PRIMARY KEY,
|
|
value TEXT,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS analysis_history (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
paradigm_id TEXT,
|
|
input_text TEXT,
|
|
result TEXT,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS documents (
|
|
id TEXT PRIMARY KEY,
|
|
title TEXT NOT NULL,
|
|
content TEXT,
|
|
paradigm_id TEXT,
|
|
dimension_set_id TEXT,
|
|
selected_refs TEXT,
|
|
status TEXT DEFAULT 'draft',
|
|
word_count INTEGER DEFAULT 0,
|
|
tags TEXT,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS document_versions (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
document_id TEXT NOT NULL,
|
|
content TEXT,
|
|
version_number INTEGER,
|
|
change_note TEXT,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (document_id) REFERENCES documents(id) ON DELETE CASCADE
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS outline_materials (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
content TEXT NOT NULL,
|
|
word_count INTEGER DEFAULT 0,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
`);
|
|
|
|
await seedDefaultReferences();
|
|
console.log('🔄 数据库已重置并恢复默认数据');
|
|
return true;
|
|
}
|
|
|
|
export default db;
|