506 lines
25 KiB
JavaScript
506 lines
25 KiB
JavaScript
/**
|
|
* @file Definiert das Datenbankschema und migriert bestehende Daten auf die benötigte Struktur.
|
|
*/
|
|
export function initializeSchema(db) {
|
|
db.pragma("journal_mode = WAL");
|
|
db.pragma("foreign_keys = ON");
|
|
|
|
db.prepare(`
|
|
CREATE TABLE IF NOT EXISTS users
|
|
(
|
|
username TEXT PRIMARY KEY,
|
|
password TEXT NOT NULL,
|
|
role TEXT NOT NULL DEFAULT 'user' CHECK (role IN ('user', 'admin')),
|
|
session_version INTEGER DEFAULT 0
|
|
)
|
|
`).run();
|
|
|
|
db.prepare(`
|
|
CREATE TABLE IF NOT EXISTS chat_sessions
|
|
(
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
username TEXT NOT NULL,
|
|
title TEXT NOT NULL,
|
|
assistant_persona_id INTEGER,
|
|
user_persona_id INTEGER,
|
|
folder_name TEXT,
|
|
is_pinned INTEGER DEFAULT 0,
|
|
archived_at DATETIME,
|
|
scenario_prompt TEXT,
|
|
scenario_summary TEXT,
|
|
context_summary TEXT,
|
|
context_summary_message_id INTEGER DEFAULT 0,
|
|
active_leaf_message_id INTEGER,
|
|
source_chat_id INTEGER,
|
|
branched_from_message_id INTEGER,
|
|
preferred_model TEXT,
|
|
temperature REAL,
|
|
context_length INTEGER,
|
|
response_length INTEGER,
|
|
system_instruction TEXT,
|
|
personality_id TEXT NOT NULL DEFAULT 'general-assistant',
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (assistant_persona_id) REFERENCES personas (id) ON DELETE SET NULL,
|
|
FOREIGN KEY (user_persona_id) REFERENCES personas (id) ON DELETE SET NULL,
|
|
FOREIGN KEY (source_chat_id) REFERENCES chat_sessions (id) ON DELETE SET NULL,
|
|
FOREIGN KEY (branched_from_message_id) REFERENCES chat_messages (id) ON DELETE SET NULL
|
|
)
|
|
`).run();
|
|
|
|
db.prepare(`
|
|
CREATE TABLE IF NOT EXISTS chat_messages
|
|
(
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
chat_id INTEGER NOT NULL,
|
|
role TEXT NOT NULL,
|
|
content TEXT NOT NULL,
|
|
model_name TEXT,
|
|
retry_variants TEXT,
|
|
retry_active_index INTEGER DEFAULT 0,
|
|
retry_retries_used INTEGER DEFAULT 0,
|
|
retry_prompt_message_id INTEGER,
|
|
delivery_status TEXT DEFAULT 'complete',
|
|
error_message TEXT,
|
|
parent_message_id INTEGER,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (chat_id) REFERENCES chat_sessions (id) ON DELETE CASCADE,
|
|
FOREIGN KEY (parent_message_id) REFERENCES chat_messages (id) ON DELETE SET NULL
|
|
)
|
|
`).run();
|
|
|
|
db.prepare(`
|
|
CREATE TABLE IF NOT EXISTS chat_memories
|
|
(
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
username TEXT NOT NULL,
|
|
chat_id INTEGER NOT NULL,
|
|
assistant_persona_id INTEGER,
|
|
user_persona_id INTEGER,
|
|
fact TEXT NOT NULL,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (username) REFERENCES users (username) ON DELETE CASCADE,
|
|
FOREIGN KEY (chat_id) REFERENCES chat_sessions (id) ON DELETE CASCADE,
|
|
FOREIGN KEY (assistant_persona_id) REFERENCES personas (id) ON DELETE SET NULL,
|
|
FOREIGN KEY (user_persona_id) REFERENCES personas (id) ON DELETE SET NULL
|
|
)
|
|
`).run();
|
|
|
|
db.prepare(`
|
|
CREATE TABLE IF NOT EXISTS persona_chat_state
|
|
(
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
username TEXT NOT NULL,
|
|
chat_id INTEGER NOT NULL UNIQUE,
|
|
assistant_persona_id INTEGER,
|
|
user_persona_id INTEGER,
|
|
relationship_notes TEXT,
|
|
tone TEXT,
|
|
current_location TEXT,
|
|
goals TEXT,
|
|
unresolved_threads TEXT,
|
|
boundaries TEXT,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (username) REFERENCES users (username) ON DELETE CASCADE,
|
|
FOREIGN KEY (chat_id) REFERENCES chat_sessions (id) ON DELETE CASCADE,
|
|
FOREIGN KEY (assistant_persona_id) REFERENCES personas (id) ON DELETE SET NULL,
|
|
FOREIGN KEY (user_persona_id) REFERENCES personas (id) ON DELETE SET NULL
|
|
)
|
|
`).run();
|
|
|
|
db.prepare(`
|
|
CREATE TABLE IF NOT EXISTS personas
|
|
(
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
username TEXT NOT NULL,
|
|
name TEXT NOT NULL,
|
|
pronouns TEXT,
|
|
appearance TEXT,
|
|
background TEXT,
|
|
details TEXT,
|
|
custom_fields TEXT,
|
|
persona_type TEXT DEFAULT 'assistant',
|
|
source_market_id INTEGER,
|
|
source_creator_username TEXT,
|
|
example_dialogues TEXT,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
`).run();
|
|
|
|
db.prepare(`
|
|
CREATE TABLE IF NOT EXISTS user_settings
|
|
(
|
|
username TEXT PRIMARY KEY,
|
|
active_persona_id INTEGER,
|
|
active_user_persona_id INTEGER,
|
|
FOREIGN KEY (username) REFERENCES users (username) ON DELETE CASCADE,
|
|
FOREIGN KEY (active_persona_id) REFERENCES personas (id) ON DELETE SET NULL,
|
|
FOREIGN KEY (active_user_persona_id) REFERENCES personas (id) ON DELETE SET NULL
|
|
)
|
|
`).run();
|
|
|
|
db.prepare(`
|
|
CREATE TABLE IF NOT EXISTS persona_market
|
|
(
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
persona_id INTEGER,
|
|
creator_username TEXT NOT NULL,
|
|
name TEXT NOT NULL,
|
|
pronouns TEXT,
|
|
appearance TEXT,
|
|
background TEXT,
|
|
details TEXT,
|
|
example_dialogues TEXT,
|
|
persona_type TEXT DEFAULT 'assistant',
|
|
tags TEXT,
|
|
usage_count INTEGER DEFAULT 0,
|
|
favorite_count INTEGER DEFAULT 0,
|
|
rating_total INTEGER DEFAULT 0,
|
|
rating_count INTEGER DEFAULT 0,
|
|
moderation_status TEXT DEFAULT 'approved',
|
|
moderation_notes TEXT,
|
|
soft_deleted_at DATETIME,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
UNIQUE (persona_id, creator_username)
|
|
)
|
|
`).run();
|
|
|
|
db.prepare(`
|
|
CREATE TABLE IF NOT EXISTS persona_versions
|
|
(
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
persona_id INTEGER NOT NULL,
|
|
username TEXT NOT NULL,
|
|
version_number INTEGER NOT NULL,
|
|
snapshot_json TEXT NOT NULL,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (persona_id) REFERENCES personas (id) ON DELETE CASCADE
|
|
)
|
|
`).run();
|
|
|
|
db.prepare(`
|
|
CREATE TABLE IF NOT EXISTS persona_market_favorites
|
|
(
|
|
market_id INTEGER NOT NULL,
|
|
username TEXT NOT NULL,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (market_id, username),
|
|
FOREIGN KEY (market_id) REFERENCES persona_market (id) ON DELETE CASCADE
|
|
)
|
|
`).run();
|
|
|
|
db.prepare(`
|
|
CREATE TABLE IF NOT EXISTS persona_market_ratings
|
|
(
|
|
market_id INTEGER NOT NULL,
|
|
username TEXT NOT NULL,
|
|
rating INTEGER NOT NULL,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (market_id, username),
|
|
FOREIGN KEY (market_id) REFERENCES persona_market (id) ON DELETE CASCADE
|
|
)
|
|
`).run();
|
|
|
|
db.prepare(`
|
|
CREATE TABLE IF NOT EXISTS persona_market_reports
|
|
(
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
market_id INTEGER NOT NULL,
|
|
reporter_username TEXT NOT NULL,
|
|
reason TEXT NOT NULL,
|
|
details TEXT,
|
|
status TEXT DEFAULT 'open',
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (market_id) REFERENCES persona_market (id) ON DELETE CASCADE
|
|
)
|
|
`).run();
|
|
|
|
db.prepare(`
|
|
CREATE TABLE IF NOT EXISTS prompt_templates
|
|
(
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
username TEXT,
|
|
persona_id INTEGER,
|
|
name TEXT NOT NULL,
|
|
description TEXT,
|
|
category TEXT,
|
|
prompt_text TEXT NOT NULL,
|
|
starter_text TEXT,
|
|
is_shared INTEGER DEFAULT 0,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (persona_id) REFERENCES personas (id) ON DELETE CASCADE
|
|
)
|
|
`).run();
|
|
}
|
|
|
|
export function runMigrations(db, {dropLegacyChats = true} = {}) {
|
|
db.exec(`CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
version INTEGER PRIMARY KEY,
|
|
applied_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
)`);
|
|
db.exec(`CREATE TABLE IF NOT EXISTS chat_message_versions (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
message_id INTEGER NOT NULL REFERENCES chat_messages(id) ON DELETE CASCADE,
|
|
content TEXT NOT NULL,
|
|
thinking TEXT,
|
|
sources TEXT,
|
|
model_name TEXT,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
CREATE INDEX IF NOT EXISTS chat_message_versions_message ON chat_message_versions(message_id);`);
|
|
const ensureColumn = (tableName, columns, name, sql) => {
|
|
if (!columns.some((column) => column.name === name)) {
|
|
db.prepare(sql).run();
|
|
}
|
|
};
|
|
|
|
const userColumns = db.prepare("PRAGMA table_info(users)").all();
|
|
ensureColumn("users", userColumns, "role", "ALTER TABLE users ADD COLUMN role TEXT NOT NULL DEFAULT 'user' CHECK (role IN ('user', 'admin'))");
|
|
ensureColumn("users", userColumns, "session_version", "ALTER TABLE users ADD COLUMN session_version INTEGER DEFAULT 0");
|
|
db.prepare("UPDATE users SET session_version = 0 WHERE session_version IS NULL").run();
|
|
|
|
const personaColumns = db.prepare("PRAGMA table_info(personas)").all();
|
|
ensureColumn("personas", personaColumns, "custom_fields", "ALTER TABLE personas ADD COLUMN custom_fields TEXT");
|
|
ensureColumn("personas", personaColumns, "persona_type", "ALTER TABLE personas ADD COLUMN persona_type TEXT DEFAULT 'assistant'");
|
|
ensureColumn("personas", personaColumns, "source_market_id", "ALTER TABLE personas ADD COLUMN source_market_id INTEGER");
|
|
ensureColumn("personas", personaColumns, "source_creator_username", "ALTER TABLE personas ADD COLUMN source_creator_username TEXT");
|
|
ensureColumn("personas", personaColumns, "example_dialogues", "ALTER TABLE personas ADD COLUMN example_dialogues TEXT");
|
|
|
|
const userSettingsColumns = db.prepare("PRAGMA table_info(user_settings)").all();
|
|
ensureColumn("user_settings", userSettingsColumns, "active_user_persona_id", "ALTER TABLE user_settings ADD COLUMN active_user_persona_id INTEGER");
|
|
|
|
const marketColumns = db.prepare("PRAGMA table_info(persona_market)").all();
|
|
ensureColumn("persona_market", marketColumns, "persona_id", "ALTER TABLE persona_market ADD COLUMN persona_id INTEGER");
|
|
ensureColumn("persona_market", marketColumns, "usage_count", "ALTER TABLE persona_market ADD COLUMN usage_count INTEGER DEFAULT 0");
|
|
ensureColumn("persona_market", marketColumns, "example_dialogues", "ALTER TABLE persona_market ADD COLUMN example_dialogues TEXT");
|
|
ensureColumn("persona_market", marketColumns, "tags", "ALTER TABLE persona_market ADD COLUMN tags TEXT");
|
|
ensureColumn("persona_market", marketColumns, "favorite_count", "ALTER TABLE persona_market ADD COLUMN favorite_count INTEGER DEFAULT 0");
|
|
ensureColumn("persona_market", marketColumns, "rating_total", "ALTER TABLE persona_market ADD COLUMN rating_total INTEGER DEFAULT 0");
|
|
ensureColumn("persona_market", marketColumns, "rating_count", "ALTER TABLE persona_market ADD COLUMN rating_count INTEGER DEFAULT 0");
|
|
ensureColumn("persona_market", marketColumns, "moderation_status", "ALTER TABLE persona_market ADD COLUMN moderation_status TEXT DEFAULT 'approved'");
|
|
ensureColumn("persona_market", marketColumns, "moderation_notes", "ALTER TABLE persona_market ADD COLUMN moderation_notes TEXT");
|
|
ensureColumn("persona_market", marketColumns, "soft_deleted_at", "ALTER TABLE persona_market ADD COLUMN soft_deleted_at DATETIME");
|
|
db.prepare("UPDATE persona_market SET usage_count = 0 WHERE usage_count IS NULL").run();
|
|
db.prepare("UPDATE persona_market SET favorite_count = 0 WHERE favorite_count IS NULL").run();
|
|
db.prepare("UPDATE persona_market SET rating_total = 0 WHERE rating_total IS NULL").run();
|
|
db.prepare("UPDATE persona_market SET rating_count = 0 WHERE rating_count IS NULL").run();
|
|
|
|
const chatMessageColumns = db.prepare("PRAGMA table_info(chat_messages)").all();
|
|
ensureColumn("chat_messages", chatMessageColumns, "sources", "ALTER TABLE chat_messages ADD COLUMN sources TEXT");
|
|
ensureColumn("chat_messages", chatMessageColumns, "thinking", "ALTER TABLE chat_messages ADD COLUMN thinking TEXT");
|
|
const hadParentMessageId = chatMessageColumns.some((column) => column.name === "parent_message_id");
|
|
ensureColumn("chat_messages", chatMessageColumns, "model_name", "ALTER TABLE chat_messages ADD COLUMN model_name TEXT");
|
|
ensureColumn("chat_messages", chatMessageColumns, "retry_variants", "ALTER TABLE chat_messages ADD COLUMN retry_variants TEXT");
|
|
ensureColumn("chat_messages", chatMessageColumns, "retry_active_index", "ALTER TABLE chat_messages ADD COLUMN retry_active_index INTEGER DEFAULT 0");
|
|
ensureColumn("chat_messages", chatMessageColumns, "retry_retries_used", "ALTER TABLE chat_messages ADD COLUMN retry_retries_used INTEGER DEFAULT 0");
|
|
ensureColumn("chat_messages", chatMessageColumns, "retry_prompt_message_id", "ALTER TABLE chat_messages ADD COLUMN retry_prompt_message_id INTEGER");
|
|
ensureColumn("chat_messages", chatMessageColumns, "delivery_status", "ALTER TABLE chat_messages ADD COLUMN delivery_status TEXT DEFAULT 'complete'");
|
|
ensureColumn("chat_messages", chatMessageColumns, "error_message", "ALTER TABLE chat_messages ADD COLUMN error_message TEXT");
|
|
ensureColumn("chat_messages", chatMessageColumns, "parent_message_id", "ALTER TABLE chat_messages ADD COLUMN parent_message_id INTEGER");
|
|
db.exec(`CREATE INDEX IF NOT EXISTS chat_messages_family ON chat_messages(chat_id, parent_message_id, role, id);
|
|
CREATE INDEX IF NOT EXISTS chat_messages_chat_order ON chat_messages(chat_id, id);`);
|
|
db.prepare("UPDATE chat_messages SET retry_active_index = 0 WHERE retry_active_index IS NULL").run();
|
|
db.prepare("UPDATE chat_messages SET retry_retries_used = 0 WHERE retry_retries_used IS NULL").run();
|
|
db.prepare("UPDATE chat_messages SET delivery_status = 'complete' WHERE delivery_status IS NULL").run();
|
|
|
|
const chatSessionColumns = db.prepare("PRAGMA table_info(chat_sessions)").all();
|
|
ensureColumn("chat_sessions", chatSessionColumns, "personality_id", "ALTER TABLE chat_sessions ADD COLUMN personality_id TEXT NOT NULL DEFAULT 'general-assistant'");
|
|
db.prepare("UPDATE chat_sessions SET personality_id = 'general-assistant' WHERE personality_id IS NULL OR personality_id = ''").run();
|
|
ensureColumn("chat_sessions", chatSessionColumns, "assistant_persona_id", "ALTER TABLE chat_sessions ADD COLUMN assistant_persona_id INTEGER");
|
|
ensureColumn("chat_sessions", chatSessionColumns, "user_persona_id", "ALTER TABLE chat_sessions ADD COLUMN user_persona_id INTEGER");
|
|
ensureColumn("chat_sessions", chatSessionColumns, "folder_name", "ALTER TABLE chat_sessions ADD COLUMN folder_name TEXT");
|
|
ensureColumn("chat_sessions", chatSessionColumns, "is_pinned", "ALTER TABLE chat_sessions ADD COLUMN is_pinned INTEGER DEFAULT 0");
|
|
ensureColumn("chat_sessions", chatSessionColumns, "archived_at", "ALTER TABLE chat_sessions ADD COLUMN archived_at DATETIME");
|
|
ensureColumn("chat_sessions", chatSessionColumns, "scenario_prompt", "ALTER TABLE chat_sessions ADD COLUMN scenario_prompt TEXT");
|
|
ensureColumn("chat_sessions", chatSessionColumns, "scenario_summary", "ALTER TABLE chat_sessions ADD COLUMN scenario_summary TEXT");
|
|
ensureColumn("chat_sessions", chatSessionColumns, "context_summary", "ALTER TABLE chat_sessions ADD COLUMN context_summary TEXT");
|
|
ensureColumn("chat_sessions", chatSessionColumns, "context_summary_message_id", "ALTER TABLE chat_sessions ADD COLUMN context_summary_message_id INTEGER DEFAULT 0");
|
|
ensureColumn("chat_sessions", chatSessionColumns, "active_leaf_message_id", "ALTER TABLE chat_sessions ADD COLUMN active_leaf_message_id INTEGER");
|
|
ensureColumn("chat_sessions", chatSessionColumns, "source_chat_id", "ALTER TABLE chat_sessions ADD COLUMN source_chat_id INTEGER");
|
|
ensureColumn("chat_sessions", chatSessionColumns, "branched_from_message_id", "ALTER TABLE chat_sessions ADD COLUMN branched_from_message_id INTEGER");
|
|
ensureColumn("chat_sessions", chatSessionColumns, "preferred_model", "ALTER TABLE chat_sessions ADD COLUMN preferred_model TEXT");
|
|
ensureColumn("chat_sessions", chatSessionColumns, "temperature", "ALTER TABLE chat_sessions ADD COLUMN temperature REAL");
|
|
ensureColumn("chat_sessions", chatSessionColumns, "context_length", "ALTER TABLE chat_sessions ADD COLUMN context_length INTEGER");
|
|
ensureColumn("chat_sessions", chatSessionColumns, "response_length", "ALTER TABLE chat_sessions ADD COLUMN response_length INTEGER");
|
|
ensureColumn("chat_sessions", chatSessionColumns, "system_instruction", "ALTER TABLE chat_sessions ADD COLUMN system_instruction TEXT");
|
|
db.prepare("UPDATE chat_sessions SET is_pinned = 0 WHERE is_pinned IS NULL").run();
|
|
db.prepare("UPDATE chat_sessions SET context_summary_message_id = 0 WHERE context_summary_message_id IS NULL").run();
|
|
db.exec(`
|
|
CREATE INDEX IF NOT EXISTS chat_sessions_owner_updated ON chat_sessions(username, updated_at DESC, id DESC);
|
|
CREATE INDEX IF NOT EXISTS chat_sessions_owner_archived_pinned ON chat_sessions(username, archived_at, is_pinned, updated_at DESC);
|
|
CREATE INDEX IF NOT EXISTS chat_messages_chat_created ON chat_messages(chat_id, created_at, id);
|
|
CREATE INDEX IF NOT EXISTS chat_memories_owner_chat ON chat_memories(username, chat_id, updated_at DESC);
|
|
`);
|
|
if (!hadParentMessageId) {
|
|
db.exec(`
|
|
WITH ordered AS (
|
|
SELECT id, LAG(id) OVER (PARTITION BY chat_id ORDER BY id) AS previous_id
|
|
FROM chat_messages
|
|
)
|
|
UPDATE chat_messages
|
|
SET parent_message_id = (SELECT previous_id FROM ordered WHERE ordered.id = chat_messages.id)
|
|
WHERE parent_message_id IS NULL
|
|
AND EXISTS (SELECT 1 FROM ordered WHERE ordered.id = chat_messages.id AND previous_id IS NOT NULL);
|
|
|
|
UPDATE chat_sessions
|
|
SET active_leaf_message_id = (SELECT MAX(id) FROM chat_messages WHERE chat_id = chat_sessions.id)
|
|
WHERE active_leaf_message_id IS NULL;
|
|
`);
|
|
}
|
|
|
|
db.prepare(`
|
|
CREATE TABLE IF NOT EXISTS chat_memories
|
|
(
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
username TEXT NOT NULL,
|
|
chat_id INTEGER NOT NULL,
|
|
assistant_persona_id INTEGER,
|
|
user_persona_id INTEGER,
|
|
fact TEXT NOT NULL,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (username) REFERENCES users (username) ON DELETE CASCADE,
|
|
FOREIGN KEY (chat_id) REFERENCES chat_sessions (id) ON DELETE CASCADE,
|
|
FOREIGN KEY (assistant_persona_id) REFERENCES personas (id) ON DELETE SET NULL,
|
|
FOREIGN KEY (user_persona_id) REFERENCES personas (id) ON DELETE SET NULL
|
|
)
|
|
`).run();
|
|
|
|
db.prepare(`
|
|
CREATE TABLE IF NOT EXISTS persona_chat_state
|
|
(
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
username TEXT NOT NULL,
|
|
chat_id INTEGER NOT NULL UNIQUE,
|
|
assistant_persona_id INTEGER,
|
|
user_persona_id INTEGER,
|
|
relationship_notes TEXT,
|
|
tone TEXT,
|
|
current_location TEXT,
|
|
goals TEXT,
|
|
unresolved_threads TEXT,
|
|
boundaries TEXT,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (username) REFERENCES users (username) ON DELETE CASCADE,
|
|
FOREIGN KEY (chat_id) REFERENCES chat_sessions (id) ON DELETE CASCADE,
|
|
FOREIGN KEY (assistant_persona_id) REFERENCES personas (id) ON DELETE SET NULL,
|
|
FOREIGN KEY (user_persona_id) REFERENCES personas (id) ON DELETE SET NULL
|
|
)
|
|
`).run();
|
|
|
|
db.prepare(`
|
|
CREATE TABLE IF NOT EXISTS persona_versions
|
|
(
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
persona_id INTEGER NOT NULL,
|
|
username TEXT NOT NULL,
|
|
version_number INTEGER NOT NULL,
|
|
snapshot_json TEXT NOT NULL,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (persona_id) REFERENCES personas (id) ON DELETE CASCADE
|
|
)
|
|
`).run();
|
|
|
|
db.prepare(`
|
|
CREATE TABLE IF NOT EXISTS persona_market_favorites
|
|
(
|
|
market_id INTEGER NOT NULL,
|
|
username TEXT NOT NULL,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (market_id, username),
|
|
FOREIGN KEY (market_id) REFERENCES persona_market (id) ON DELETE CASCADE
|
|
)
|
|
`).run();
|
|
|
|
db.prepare(`
|
|
CREATE TABLE IF NOT EXISTS persona_market_ratings
|
|
(
|
|
market_id INTEGER NOT NULL,
|
|
username TEXT NOT NULL,
|
|
rating INTEGER NOT NULL,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (market_id, username),
|
|
FOREIGN KEY (market_id) REFERENCES persona_market (id) ON DELETE CASCADE
|
|
)
|
|
`).run();
|
|
|
|
db.prepare(`
|
|
CREATE TABLE IF NOT EXISTS persona_market_reports
|
|
(
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
market_id INTEGER NOT NULL,
|
|
reporter_username TEXT NOT NULL,
|
|
reason TEXT NOT NULL,
|
|
details TEXT,
|
|
status TEXT DEFAULT 'open',
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (market_id) REFERENCES persona_market (id) ON DELETE CASCADE
|
|
)
|
|
`).run();
|
|
|
|
db.prepare(`
|
|
CREATE TABLE IF NOT EXISTS prompt_templates
|
|
(
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
username TEXT,
|
|
persona_id INTEGER,
|
|
name TEXT NOT NULL,
|
|
description TEXT,
|
|
category TEXT,
|
|
prompt_text TEXT NOT NULL,
|
|
starter_text TEXT,
|
|
is_shared INTEGER DEFAULT 0,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (persona_id) REFERENCES personas (id) ON DELETE CASCADE
|
|
)
|
|
`).run();
|
|
|
|
const legacyChatsExist = db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'chats'").get();
|
|
if (!legacyChatsExist) {
|
|
db.prepare("INSERT OR IGNORE INTO schema_migrations(version) VALUES (?)").run(1);
|
|
return;
|
|
}
|
|
|
|
const migrationNeeded = db.prepare("SELECT COUNT(*) as count FROM chat_messages").get();
|
|
if (migrationNeeded.count === 0) {
|
|
const insertChatSessionStmt = db.prepare(
|
|
"INSERT INTO chat_sessions (username, title, assistant_persona_id, user_persona_id, scenario_prompt, scenario_summary) VALUES (?, ?, ?, ?, ?, ?)"
|
|
);
|
|
const getLegacyUsersStmt = db.prepare("SELECT DISTINCT username FROM chats");
|
|
const getLegacyChatsStmt = db.prepare(`
|
|
SELECT role, content
|
|
FROM chats
|
|
WHERE username = ?
|
|
ORDER BY datetime(created_at)
|
|
`);
|
|
const insertChatMessageStmt = db.prepare(
|
|
"INSERT INTO chat_messages (chat_id, role, content, retry_variants, retry_active_index, retry_retries_used, retry_prompt_message_id) VALUES (?, ?, ?, ?, ?, ?, ?)"
|
|
);
|
|
const migrateLegacyChats = db.transaction(() => {
|
|
for (const {username} of getLegacyUsersStmt.all()) {
|
|
const chatId = insertChatSessionStmt.run(username, "Imported chat", null, null, null, null).lastInsertRowid;
|
|
for (const row of getLegacyChatsStmt.all(username)) {
|
|
insertChatMessageStmt.run(chatId, row.role, row.content, null, 0, 0, null);
|
|
}
|
|
}
|
|
});
|
|
migrateLegacyChats();
|
|
}
|
|
|
|
if (dropLegacyChats) {
|
|
db.prepare("DROP TABLE chats").run();
|
|
}
|
|
db.prepare("INSERT OR IGNORE INTO schema_migrations(version) VALUES (?)").run(1);
|
|
}
|