import aiosqlite from database.db import DB_PATH async def get_or_create_session(session_id: str, persona_id: str = "default") -> dict: async with aiosqlite.connect(DB_PATH) as db: db.row_factory = aiosqlite.Row async with db.execute( "SELECT * FROM sessions WHERE session_id = ?", (session_id,) ) as cursor: row = await cursor.fetchone() if row: return dict(row) await db.execute( "INSERT INTO sessions (session_id, persona_id) VALUES (?, ?)", (session_id, persona_id), ) await db.commit() async with db.execute( "SELECT * FROM sessions WHERE session_id = ?", (session_id,) ) as cursor: row = await cursor.fetchone() return dict(row) async def get_all_sessions() -> list: async with aiosqlite.connect(DB_PATH) as db: db.row_factory = aiosqlite.Row async with db.execute( "SELECT * FROM sessions ORDER BY updated_at DESC" ) as cursor: rows = await cursor.fetchall() return [dict(r) for r in rows] async def get_session(session_id: str) -> dict | None: async with aiosqlite.connect(DB_PATH) as db: db.row_factory = aiosqlite.Row async with db.execute( "SELECT * FROM sessions WHERE session_id = ?", (session_id,), ) as cursor: row = await cursor.fetchone() return dict(row) if row else None async def update_session_title(session_id: str, title: str): async with aiosqlite.connect(DB_PATH) as db: await db.execute( "UPDATE sessions SET title = ?, updated_at = CURRENT_TIMESTAMP WHERE session_id = ?", (title, session_id), ) await db.commit() async def update_session_persona(session_id: str, persona_id: str): async with aiosqlite.connect(DB_PATH) as db: db.row_factory = aiosqlite.Row async with db.execute( "SELECT persona_id FROM sessions WHERE session_id = ?", (session_id,), ) as cur: row = await cur.fetchone() prev = row["persona_id"] if row else None await db.execute( "UPDATE sessions SET persona_id = ?, updated_at = CURRENT_TIMESTAMP WHERE session_id = ?", (persona_id, session_id), ) # If persona changed, reset RPG state bound to the persona/arc. if prev is not None and prev != persona_id: await db.execute( """UPDATE sessions SET facts_json = '[]', global_plot = '', status_quo = '', plot_arc_json = '{}' WHERE session_id = ?""", (session_id,), ) await db.execute( "DELETE FROM action_resolutions WHERE session_id = ?", (session_id,), ) await db.commit() async def update_session_rpg(session_id: str, rpg_enabled: bool): async with aiosqlite.connect(DB_PATH) as db: await db.execute( "UPDATE sessions SET rpg_enabled = ?, updated_at = CURRENT_TIMESTAMP WHERE session_id = ?", (1 if rpg_enabled else 0, session_id), ) await db.commit() async def update_session_facts(session_id: str, facts_json: str): async with aiosqlite.connect(DB_PATH) as db: await db.execute( "UPDATE sessions SET facts_json = ?, updated_at = CURRENT_TIMESTAMP WHERE session_id = ?", (facts_json, session_id), ) await db.commit() async def update_session_global_plot(session_id: str, global_plot: str): async with aiosqlite.connect(DB_PATH) as db: await db.execute( "UPDATE sessions SET global_plot = ?, updated_at = CURRENT_TIMESTAMP WHERE session_id = ?", (global_plot, session_id), ) await db.commit() async def update_session_status_quo(session_id: str, status_quo: str): async with aiosqlite.connect(DB_PATH) as db: await db.execute( "UPDATE sessions SET status_quo = ?, updated_at = CURRENT_TIMESTAMP WHERE session_id = ?", (status_quo, session_id), ) await db.commit() async def update_session_plot_arc(session_id: str, plot_arc_json: str): async with aiosqlite.connect(DB_PATH) as db: await db.execute( "UPDATE sessions SET plot_arc_json = ?, updated_at = CURRENT_TIMESTAMP WHERE session_id = ?", (plot_arc_json, session_id), ) await db.commit() async def add_action_resolution( session_id: str, intent_text: str, roll: int, outcome: str, resolution_text: str, message_id: int | None = None, ): async with aiosqlite.connect(DB_PATH) as db: await db.execute( """INSERT INTO action_resolutions (session_id, message_id, intent_text, roll, outcome, resolution_text) VALUES (?, ?, ?, ?, ?, ?)""", (session_id, message_id, intent_text, roll, outcome, resolution_text), ) await db.commit() async def get_last_action_resolution(session_id: str) -> dict | None: async with aiosqlite.connect(DB_PATH) as db: db.row_factory = aiosqlite.Row async with db.execute( """SELECT * FROM action_resolutions WHERE session_id = ? ORDER BY id DESC LIMIT 1""", (session_id,), ) as cur: row = await cur.fetchone() return dict(row) if row else None async def delete_session(session_id: str): async with aiosqlite.connect(DB_PATH) as db: await db.execute("DELETE FROM messages WHERE session_id = ?", (session_id,)) await db.execute("DELETE FROM rpg_quests WHERE session_id = ?", (session_id,)) await db.execute("DELETE FROM action_resolutions WHERE session_id = ?", (session_id,)) await db.execute("DELETE FROM sessions WHERE session_id = ?", (session_id,)) await db.commit() async def get_history(session_id: str) -> list: async with aiosqlite.connect(DB_PATH) as db: db.row_factory = aiosqlite.Row async with db.execute( """SELECT id, role, content, image_prompt, image_path FROM messages WHERE session_id = ? ORDER BY id""", (session_id,), ) as cursor: rows = await cursor.fetchall() return [ { "id": r["id"], "role": r["role"], "content": r["content"], "image_prompt": r["image_prompt"], "image_path": r["image_path"], } for r in rows ] async def get_message(message_id: int) -> dict | None: async with aiosqlite.connect(DB_PATH) as db: db.row_factory = aiosqlite.Row async with db.execute( "SELECT id, session_id, role, content FROM messages WHERE id = ?", (message_id,), ) as cursor: row = await cursor.fetchone() return dict(row) if row else None async def update_message_content(message_id: int, content: str): async with aiosqlite.connect(DB_PATH) as db: await db.execute( "UPDATE messages SET content = ? WHERE id = ?", (content, message_id), ) await db.commit() async def delete_messages_after(session_id: str, message_id: int): async with aiosqlite.connect(DB_PATH) as db: await db.execute( "DELETE FROM messages WHERE session_id = ? AND id > ?", (session_id, message_id), ) await db.commit() async def delete_message(message_id: int): async with aiosqlite.connect(DB_PATH) as db: await db.execute("DELETE FROM messages WHERE id = ?", (message_id,)) await db.commit() async def get_last_message_preview(session_id: str, max_len: int = 80) -> str: async with aiosqlite.connect(DB_PATH) as db: db.row_factory = aiosqlite.Row async with db.execute( """SELECT content, role FROM messages WHERE session_id = ? AND role IN ('user', 'assistant') ORDER BY id DESC LIMIT 1""", (session_id,), ) as cursor: row = await cursor.fetchone() if not row: return "" prefix = "Вы: " if row["role"] == "user" else "AI: " text = (row["content"] or "").replace("\n", " ").strip() if len(text) > max_len: text = text[:max_len] + "…" return prefix + text async def fork_session(source_session_id: str, until_message_id: int) -> str | None: source = await get_session(source_session_id) if not source: return None import uuid new_id = "sess_" + uuid.uuid4().hex[:8] async with aiosqlite.connect(DB_PATH) as db: await db.execute( """INSERT INTO sessions (session_id, persona_id, title, rpg_enabled, facts_json, global_plot, status_quo, plot_arc_json, genre, rpg_settings_json, affinity) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", ( new_id, source["persona_id"], (source.get("title") or "Новый чат") + " (ветка)", source.get("rpg_enabled", 0), source.get("facts_json", "[]"), source.get("global_plot", ""), source.get("status_quo", ""), source.get("plot_arc_json", "{}"), source.get("genre", "adventure"), source.get("rpg_settings_json", "{}"), source.get("affinity", 0), ), ) async with db.execute( """SELECT role, content, image_prompt, image_path FROM messages WHERE session_id = ? AND id <= ? ORDER BY id""", (source_session_id, until_message_id), ) as cur: rows = await cur.fetchall() for r in rows: await db.execute( """INSERT INTO messages (session_id, role, content, image_prompt, image_path) VALUES (?, ?, ?, ?, ?)""", (new_id, r[0], r[1], r[2], r[3]), ) async with db.execute( "SELECT title, status FROM rpg_quests WHERE session_id = ?", (source_session_id,), ) as cur: quests = await cur.fetchall() for q in quests: await db.execute( "INSERT INTO rpg_quests (session_id, title, status) VALUES (?, ?, ?)", (new_id, q[0], q[1]), ) await db.commit() return new_id async def add_message( session_id: str, role: str, content: str, image_prompt: str | None = None, image_path: str | None = None, ): async with aiosqlite.connect(DB_PATH) as db: await db.execute( """INSERT INTO messages (session_id, role, content, image_prompt, image_path) VALUES (?, ?, ?, ?, ?)""", (session_id, role, content, image_prompt, image_path), ) await db.execute( "UPDATE sessions SET updated_at = CURRENT_TIMESTAMP WHERE session_id = ?", (session_id,), ) await db.commit() async def update_message_image(message_id: int, image_path: str): async with aiosqlite.connect(DB_PATH) as db: await db.execute( "UPDATE messages SET image_path = ? WHERE id = ?", (image_path, message_id), ) await db.commit() async def get_last_assistant_message_id(session_id: str) -> int | None: async with aiosqlite.connect(DB_PATH) as db: db.row_factory = aiosqlite.Row async with db.execute( """SELECT id FROM messages WHERE session_id = ? AND role = 'assistant' ORDER BY id DESC LIMIT 1""", (session_id,), ) as cursor: row = await cursor.fetchone() return row["id"] if row else None async def clear_history(session_id: str): async with aiosqlite.connect(DB_PATH) as db: await db.execute( "DELETE FROM messages WHERE session_id = ?", (session_id,) ) await db.commit() async def update_session_affinity(session_id: str, delta: int): async with aiosqlite.connect(DB_PATH) as db: await db.execute( "UPDATE sessions SET affinity = affinity + ?, updated_at = CURRENT_TIMESTAMP WHERE session_id = ?", (delta, session_id), ) await db.commit() async def update_session_genre(session_id: str, genre: str): async with aiosqlite.connect(DB_PATH) as db: await db.execute( "UPDATE sessions SET genre = ?, updated_at = CURRENT_TIMESTAMP WHERE session_id = ?", (genre, session_id), ) await db.commit() async def update_session_rpg_settings(session_id: str, settings_json: str): async with aiosqlite.connect(DB_PATH) as db: await db.execute( "UPDATE sessions SET rpg_settings_json = ?, updated_at = CURRENT_TIMESTAMP WHERE session_id = ?", (settings_json, session_id), ) await db.commit() async def update_session_outfit(session_id: str, outfit_json: str): async with aiosqlite.connect(DB_PATH) as db: await db.execute( "UPDATE sessions SET outfit_json = ?, updated_at = CURRENT_TIMESTAMP WHERE session_id = ?", (outfit_json, session_id), ) await db.commit() async def upsert_quest(session_id: str, title: str, status: str = "active"): async with aiosqlite.connect(DB_PATH) as db: async with db.execute( "SELECT id FROM rpg_quests WHERE session_id = ? AND title = ?", (session_id, title), ) as cur: row = await cur.fetchone() if row: await db.execute( "UPDATE rpg_quests SET status = ? WHERE id = ?", (status, row[0]), ) else: await db.execute( "INSERT INTO rpg_quests (session_id, title, status) VALUES (?, ?, ?)", (session_id, title, status), ) await db.commit() async def get_quests(session_id: str) -> list: async with aiosqlite.connect(DB_PATH) as db: db.row_factory = aiosqlite.Row async with db.execute( "SELECT id, title, status FROM rpg_quests WHERE session_id = ? ORDER BY id", (session_id,), ) as cur: rows = await cur.fetchall() return [dict(r) for r in rows] async def update_quest_status(session_id: str, title: str, status: str): async with aiosqlite.connect(DB_PATH) as db: await db.execute( "UPDATE rpg_quests SET status = ? WHERE session_id = ? AND title = ?", (status, session_id, title), ) await db.commit() async def get_message_count(session_id: str) -> int: async with aiosqlite.connect(DB_PATH) as db: db.row_factory = aiosqlite.Row async with db.execute( "SELECT COUNT(*) as cnt FROM messages WHERE session_id = ? AND role != 'system'", (session_id,), ) as cursor: row = await cursor.fetchone() return row["cnt"]