import aiosqlite import os DB_PATH = os.getenv("DB_PATH", "data/chat.db") async def init_db(): os.makedirs(os.path.dirname(DB_PATH), exist_ok=True) async with aiosqlite.connect(DB_PATH) as db: await db.executescript(""" CREATE TABLE IF NOT EXISTS sessions ( session_id TEXT PRIMARY KEY, persona_id TEXT DEFAULT 'default', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, title TEXT DEFAULT 'Новый чат', rpg_enabled INTEGER DEFAULT 0, facts_json TEXT DEFAULT '[]', global_plot TEXT DEFAULT '', status_quo TEXT DEFAULT '', plot_arc_json TEXT DEFAULT '{}', rng_seed INTEGER ); CREATE TABLE IF NOT EXISTS messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT NOT NULL, role TEXT NOT NULL, content TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (session_id) REFERENCES sessions(session_id) ); CREATE INDEX IF NOT EXISTS idx_messages_session ON messages(session_id); CREATE TABLE IF NOT EXISTS personas ( persona_id TEXT PRIMARY KEY, name TEXT NOT NULL, emoji TEXT DEFAULT '🤖', description TEXT DEFAULT '', prompt TEXT NOT NULL, custom INTEGER DEFAULT 1, sd_enabled INTEGER DEFAULT 0, lora_name TEXT DEFAULT '', lora_weight REAL DEFAULT 0.8, appearance_tags TEXT DEFAULT '', personality TEXT DEFAULT '', scenario TEXT DEFAULT '', first_mes TEXT DEFAULT '', mes_example TEXT DEFAULT '', lorebook_json TEXT DEFAULT '[]', avatar_path TEXT DEFAULT '' ); CREATE TABLE IF NOT EXISTS characters ( card_id TEXT PRIMARY KEY, name TEXT NOT NULL, description TEXT DEFAULT '', personality TEXT DEFAULT '', scenario TEXT DEFAULT '', first_mes TEXT DEFAULT '', mes_example TEXT DEFAULT '', raw_json TEXT NOT NULL, lora_name TEXT DEFAULT '', lora_weight REAL DEFAULT 0.8, appearance_tags TEXT DEFAULT '', lorebook_json TEXT DEFAULT '[]', avatar_path TEXT DEFAULT '', created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); """) await _migrate_messages_columns(db) await _migrate_personas_columns(db) await _migrate_sessions_columns(db) await _migrate_characters_columns(db) await _migrate_rpg_quests(db) await _migrate_action_resolutions(db) await _migrate_state_snapshots(db) await db.commit() async def _migrate_messages_columns(db): async with db.execute("PRAGMA table_info(messages)") as cur: cols = {row[1] for row in await cur.fetchall()} if "image_prompt" not in cols: await db.execute("ALTER TABLE messages ADD COLUMN image_prompt TEXT") if "image_path" not in cols: await db.execute("ALTER TABLE messages ADD COLUMN image_path TEXT") if "image_prompt_alt" not in cols: await db.execute("ALTER TABLE messages ADD COLUMN image_prompt_alt TEXT") if "image_path_alt" not in cols: await db.execute("ALTER TABLE messages ADD COLUMN image_path_alt TEXT") if "choices_json" not in cols: await db.execute("ALTER TABLE messages ADD COLUMN choices_json TEXT") async def _migrate_personas_columns(db): async with db.execute("PRAGMA table_info(personas)") as cur: cols = {row[1] for row in await cur.fetchall()} if "personality" not in cols: await db.execute("ALTER TABLE personas ADD COLUMN personality TEXT DEFAULT ''") if "scenario" not in cols: await db.execute("ALTER TABLE personas ADD COLUMN scenario TEXT DEFAULT ''") if "first_mes" not in cols: await db.execute("ALTER TABLE personas ADD COLUMN first_mes TEXT DEFAULT ''") if "mes_example" not in cols: await db.execute("ALTER TABLE personas ADD COLUMN mes_example TEXT DEFAULT ''") if "lorebook_json" not in cols: await db.execute("ALTER TABLE personas ADD COLUMN lorebook_json TEXT DEFAULT '[]'") if "avatar_path" not in cols: await db.execute("ALTER TABLE personas ADD COLUMN avatar_path TEXT DEFAULT ''") if "alternate_greetings_json" not in cols: await db.execute("ALTER TABLE personas ADD COLUMN alternate_greetings_json TEXT DEFAULT '[]'") if "appearance_prose" not in cols: await db.execute("ALTER TABLE personas ADD COLUMN appearance_prose TEXT DEFAULT ''") async def _migrate_sessions_columns(db): async with db.execute("PRAGMA table_info(sessions)") as cur: cols = {row[1] for row in await cur.fetchall()} if "rpg_enabled" not in cols: await db.execute("ALTER TABLE sessions ADD COLUMN rpg_enabled INTEGER DEFAULT 0") if "facts_json" not in cols: await db.execute("ALTER TABLE sessions ADD COLUMN facts_json TEXT DEFAULT '[]'") if "global_plot" not in cols: await db.execute("ALTER TABLE sessions ADD COLUMN global_plot TEXT DEFAULT ''") if "status_quo" not in cols: await db.execute("ALTER TABLE sessions ADD COLUMN status_quo TEXT DEFAULT ''") if "plot_arc_json" not in cols: await db.execute("ALTER TABLE sessions ADD COLUMN plot_arc_json TEXT DEFAULT '{}'") if "rng_seed" not in cols: await db.execute("ALTER TABLE sessions ADD COLUMN rng_seed INTEGER") if "affinity" not in cols: await db.execute("ALTER TABLE sessions ADD COLUMN affinity INTEGER DEFAULT 0") if "genre" not in cols: await db.execute("ALTER TABLE sessions ADD COLUMN genre TEXT DEFAULT 'adventure'") if "rpg_settings_json" not in cols: await db.execute("ALTER TABLE sessions ADD COLUMN rpg_settings_json TEXT DEFAULT '{}'") if "outfit_json" not in cols: await db.execute("ALTER TABLE sessions ADD COLUMN outfit_json TEXT DEFAULT '[]'") if "scene_json" not in cols: await db.execute("ALTER TABLE sessions ADD COLUMN scene_json TEXT DEFAULT '{}'") if "narrative_stats_json" not in cols: await db.execute( "ALTER TABLE sessions ADD COLUMN narrative_stats_json TEXT DEFAULT " "'{\"lust\":0,\"stamina\":10,\"tension\":0}'" ) async def _migrate_rpg_quests(db): await db.executescript(""" CREATE TABLE IF NOT EXISTS rpg_quests ( id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT NOT NULL, title TEXT NOT NULL, status TEXT DEFAULT 'active', created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_rpg_quests_session ON rpg_quests(session_id); """) async def _migrate_action_resolutions(db): await db.executescript( """ CREATE TABLE IF NOT EXISTS action_resolutions ( id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT NOT NULL, message_id INTEGER, intent_text TEXT NOT NULL, roll INTEGER NOT NULL, outcome TEXT NOT NULL, resolution_text TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_action_resolutions_session ON action_resolutions(session_id); """ ) async def _migrate_state_snapshots(db): await db.executescript( """ CREATE TABLE IF NOT EXISTS session_state_snapshots ( message_id INTEGER PRIMARY KEY, session_id TEXT NOT NULL, facts_json TEXT NOT NULL DEFAULT '[]', global_plot TEXT NOT NULL DEFAULT '', status_quo TEXT NOT NULL DEFAULT '', plot_arc_json TEXT NOT NULL DEFAULT '{}', affinity INTEGER NOT NULL DEFAULT 0, outfit_json TEXT NOT NULL DEFAULT '[]', scene_json TEXT NOT NULL DEFAULT '{}', narrative_stats_json TEXT NOT NULL DEFAULT '{"lust":0,"stamina":10,"tension":0}', quests_json TEXT NOT NULL DEFAULT '[]', action_resolutions_json TEXT NOT NULL DEFAULT '[]', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (message_id) REFERENCES messages(id) ON DELETE CASCADE ); CREATE INDEX IF NOT EXISTS idx_state_snapshots_session ON session_state_snapshots(session_id); """ ) async def _migrate_characters_columns(db): async with db.execute("PRAGMA table_info(characters)") as cur: cols = {row[1] for row in await cur.fetchall()} if "avatar_path" not in cols: await db.execute("ALTER TABLE characters ADD COLUMN avatar_path TEXT DEFAULT ''") if "alternate_greetings_json" not in cols: await db.execute("ALTER TABLE characters ADD COLUMN alternate_greetings_json TEXT DEFAULT '[]'") if "appearance_prose" not in cols: await db.execute("ALTER TABLE characters ADD COLUMN appearance_prose TEXT DEFAULT ''")