export function createChatStatements(db) { return { insertChatSession: db.prepare( "INSERT INTO chat_sessions (username, title, assistant_persona_id, user_persona_id, folder_name, is_pinned, archived_at, scenario_prompt, scenario_summary) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)" ), listChatSessions: db.prepare(` SELECT cs.id, CASE WHEN cs.assistant_persona_id IS NOT NULL AND cs.source_chat_id IS NULL THEN COALESCE(p.name, cs.title) ELSE cs.title END AS title, cs.title AS stored_title, cs.assistant_persona_id, cs.personality_id, p.name AS assistant_persona_name, cs.user_persona_id, up.name AS user_persona_name, cs.folder_name, cs.is_pinned, cs.archived_at, cs.scenario_prompt, cs.scenario_summary, cs.context_summary, cs.context_summary_message_id, cs.active_leaf_message_id, cs.source_chat_id, cs.branched_from_message_id, cs.preferred_model, cs.temperature, cs.context_length, cs.response_length, cs.system_instruction, cs.created_at, cs.updated_at, (SELECT substr(m.content, 1, 180) FROM chat_messages m WHERE m.chat_id = cs.id AND m.content <> '' ORDER BY m.id DESC LIMIT 1) AS last_message_preview FROM chat_sessions cs LEFT JOIN personas p ON p.id = cs.assistant_persona_id LEFT JOIN personas up ON up.id = cs.user_persona_id WHERE cs.username = ? ORDER BY cs.is_pinned DESC, datetime(cs.updated_at) DESC `), countChatSessions: db.prepare("SELECT COUNT(*) as count FROM chat_sessions WHERE username = ? AND archived_at IS NULL"), getChatSession: db.prepare(` SELECT cs.id, CASE WHEN cs.assistant_persona_id IS NOT NULL AND cs.source_chat_id IS NULL THEN COALESCE(p.name, cs.title) ELSE cs.title END AS title, cs.title AS stored_title, cs.assistant_persona_id, cs.personality_id, p.name AS assistant_persona_name, cs.user_persona_id, up.name AS user_persona_name, cs.folder_name, cs.is_pinned, cs.archived_at, cs.scenario_prompt, cs.scenario_summary, cs.context_summary, cs.context_summary_message_id, cs.active_leaf_message_id, cs.source_chat_id, cs.branched_from_message_id, cs.preferred_model, cs.temperature, cs.context_length, cs.response_length, cs.system_instruction, cs.created_at, cs.updated_at FROM chat_sessions cs LEFT JOIN personas p ON p.id = cs.assistant_persona_id LEFT JOIN personas up ON up.id = cs.user_persona_id WHERE cs.id = ? AND cs.username = ? `), getChatSessionByParticipants: db.prepare(` SELECT cs.id, CASE WHEN cs.assistant_persona_id IS NOT NULL AND cs.source_chat_id IS NULL THEN COALESCE(p.name, cs.title) ELSE cs.title END AS title, cs.title AS stored_title, cs.assistant_persona_id, cs.personality_id, p.name AS assistant_persona_name, cs.user_persona_id, up.name AS user_persona_name, cs.folder_name, cs.is_pinned, cs.archived_at, cs.scenario_prompt, cs.scenario_summary, cs.context_summary, cs.context_summary_message_id, cs.active_leaf_message_id, cs.source_chat_id, cs.branched_from_message_id, cs.preferred_model, cs.temperature, cs.context_length, cs.response_length, cs.system_instruction, cs.created_at, cs.updated_at FROM chat_sessions cs LEFT JOIN personas p ON p.id = cs.assistant_persona_id LEFT JOIN personas up ON up.id = cs.user_persona_id WHERE cs.username = ? AND cs.assistant_persona_id = ? AND ((cs.user_persona_id IS NULL AND ? IS NULL) OR cs.user_persona_id = ?) AND cs.source_chat_id IS NULL ORDER BY cs.is_pinned DESC, datetime(cs.updated_at) DESC LIMIT 1 `), updateChatSessionTitle: db.prepare("UPDATE chat_sessions SET title = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND username = ?"), updateChatUserPersona: db.prepare("UPDATE chat_sessions SET user_persona_id = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND username = ?"), updateChatPersonality: db.prepare("UPDATE chat_sessions SET personality_id = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND username = ?"), updateChatSessionScene: db.prepare("UPDATE chat_sessions SET scenario_prompt = ?, scenario_summary = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND username = ?"), updateChatContextSummary: db.prepare("UPDATE chat_sessions SET context_summary = ?, context_summary_message_id = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND username = ?"), updateChatGenerationSettings: db.prepare(` UPDATE chat_sessions SET preferred_model = ?, temperature = ?, context_length = ?, response_length = ?, system_instruction = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND username = ? `), updateChatPreferredModel: db.prepare("UPDATE chat_sessions SET preferred_model = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND username = ?"), updateChatActiveLeaf: db.prepare("UPDATE chat_sessions SET active_leaf_message_id = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND username = ?"), updateChatBranchSource: db.prepare("UPDATE chat_sessions SET source_chat_id = ?, branched_from_message_id = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND username = ?"), setChatActiveLeafById: db.prepare("UPDATE chat_sessions SET active_leaf_message_id = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?"), updateChatSessionOrganization: db.prepare(` UPDATE chat_sessions SET folder_name = ?, is_pinned = ?, archived_at = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND username = ? `), touchChatSession: db.prepare("UPDATE chat_sessions SET updated_at = CURRENT_TIMESTAMP WHERE id = ? AND username = ?"), deleteChatSession: db.prepare("DELETE FROM chat_sessions WHERE id = ? AND username = ?"), insertChatMessage: db.prepare("INSERT INTO chat_messages (chat_id, role, content, model_name, retry_variants, retry_active_index, retry_retries_used, retry_prompt_message_id, delivery_status, error_message, parent_message_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"), updateChatMessageDelivery: db.prepare("UPDATE chat_messages SET delivery_status = ?, error_message = ? WHERE id = ? AND chat_id = ?"), updateChatMessageSources: db.prepare("UPDATE chat_messages SET sources = ? WHERE id = ? AND chat_id = ?"), updateChatMessageThinking: db.prepare("UPDATE chat_messages SET thinking = ? WHERE id = ? AND chat_id = ?"), getChatMessages: db.prepare(` SELECT thinking, sources, id, role, content, model_name, retry_variants, retry_active_index, retry_retries_used, retry_prompt_message_id, delivery_status, error_message, parent_message_id, created_at FROM chat_messages WHERE chat_id = ? ORDER BY id `), getRecentChatMessages: db.prepare(` WITH RECURSIVE branch AS ( SELECT cm.* FROM chat_messages cm JOIN chat_sessions cs ON cs.id = cm.chat_id WHERE cm.chat_id = ? AND cm.id = COALESCE(cs.active_leaf_message_id, (SELECT MAX(id) FROM chat_messages WHERE chat_id = cm.chat_id)) UNION ALL SELECT parent.* FROM chat_messages parent JOIN branch child ON child.parent_message_id = parent.id ) SELECT id, role, content FROM branch WHERE delivery_status != 'failed' ORDER BY id DESC LIMIT ? `), getRecentChatMessagesUpToId: db.prepare(` WITH RECURSIVE branch AS ( SELECT * FROM chat_messages WHERE chat_id = ? AND id = ? UNION ALL SELECT parent.* FROM chat_messages parent JOIN branch child ON child.parent_message_id = parent.id ) SELECT id, role, content FROM branch WHERE delivery_status != 'failed' ORDER BY id DESC LIMIT ? `), getChatBranchThroughMessage: db.prepare(` WITH RECURSIVE branch AS ( SELECT * FROM chat_messages WHERE chat_id = ? AND id = ? UNION ALL SELECT parent.* FROM chat_messages parent JOIN branch child ON child.parent_message_id = parent.id ) SELECT id, role, content, model_name, retry_variants, retry_active_index, retry_retries_used, retry_prompt_message_id, delivery_status, error_message, parent_message_id, created_at, sources, thinking FROM branch WHERE delivery_status = 'complete' ORDER BY id `), getActiveBranchMessages: db.prepare(` WITH RECURSIVE branch AS ( SELECT cm.* FROM chat_messages cm JOIN chat_sessions cs ON cs.id = cm.chat_id WHERE cm.chat_id = ? AND cm.id = COALESCE(cs.active_leaf_message_id, (SELECT MAX(id) FROM chat_messages WHERE chat_id = cm.chat_id)) UNION ALL SELECT parent.* FROM chat_messages parent JOIN branch child ON child.parent_message_id = parent.id ) SELECT id, role, content, model_name, retry_variants, retry_active_index, retry_retries_used, retry_prompt_message_id, delivery_status, error_message, parent_message_id, created_at, sources, thinking FROM branch ORDER BY id `), getSiblingChatMessages: db.prepare(` SELECT id, role, content, parent_message_id FROM chat_messages WHERE chat_id = ? AND role = ? AND parent_message_id IS ? ORDER BY id `), getLatestDescendantMessage: db.prepare(` WITH RECURSIVE descendants AS ( SELECT id FROM chat_messages WHERE chat_id = ? AND id = ? UNION ALL SELECT child.id FROM chat_messages child JOIN descendants parent ON child.parent_message_id = parent.id WHERE child.chat_id = ? AND child.delivery_status != 'failed' ) SELECT cm.* FROM chat_messages cm JOIN descendants d ON d.id = cm.id ORDER BY cm.id DESC LIMIT 1 `), getChatMessageById: db.prepare(` SELECT thinking, sources, id, role, content, model_name, retry_variants, retry_active_index, retry_retries_used, retry_prompt_message_id, delivery_status, error_message, parent_message_id, created_at FROM chat_messages WHERE id = ? AND chat_id = ? `), getChatMessageByIndex: db.prepare(` SELECT thinking, sources, id, role, content, model_name, retry_variants, retry_active_index, retry_retries_used, retry_prompt_message_id, delivery_status, error_message, parent_message_id, created_at FROM chat_messages WHERE chat_id = ? ORDER BY id LIMIT 1 OFFSET ? `), getLatestChatMessage: db.prepare(` SELECT id, role, content, model_name, retry_variants, retry_active_index, retry_retries_used, retry_prompt_message_id, delivery_status, error_message, parent_message_id, created_at FROM chat_messages WHERE chat_id = ? ORDER BY id DESC LIMIT 1 `), getPreviousUserMessage: db.prepare(` SELECT id, role, content, model_name, retry_variants, retry_active_index, retry_retries_used, retry_prompt_message_id, delivery_status, error_message, parent_message_id, created_at FROM chat_messages WHERE chat_id = ? AND role = 'user' AND id < ? ORDER BY id DESC LIMIT 1 `), listChatMemories: db.prepare(` SELECT id, username, chat_id, assistant_persona_id, user_persona_id, fact, created_at, updated_at FROM chat_memories WHERE chat_id = ? AND username = ? ORDER BY datetime(updated_at) DESC, id DESC `), getChatMemory: db.prepare(` SELECT id, username, chat_id, assistant_persona_id, user_persona_id, fact, created_at, updated_at FROM chat_memories WHERE id = ? AND chat_id = ? AND username = ? `), insertChatMemory: db.prepare(` INSERT INTO chat_memories (username, chat_id, assistant_persona_id, user_persona_id, fact) VALUES (?, ?, ?, ?, ?) `), updateChatMemory: db.prepare(` UPDATE chat_memories SET fact = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND chat_id = ? AND username = ? `), deleteChatMemory: db.prepare("DELETE FROM chat_memories WHERE id = ? AND chat_id = ? AND username = ?"), getPersonaChatState: db.prepare(` SELECT id, username, chat_id, assistant_persona_id, user_persona_id, relationship_notes, tone, current_location, goals, unresolved_threads, boundaries, created_at, updated_at FROM persona_chat_state WHERE chat_id = ? AND username = ? `), upsertPersonaChatState: db.prepare(` INSERT INTO persona_chat_state ( username, chat_id, assistant_persona_id, user_persona_id, relationship_notes, tone, current_location, goals, unresolved_threads, boundaries ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(chat_id) DO UPDATE SET username = excluded.username, assistant_persona_id = excluded.assistant_persona_id, user_persona_id = excluded.user_persona_id, relationship_notes = excluded.relationship_notes, tone = excluded.tone, current_location = excluded.current_location, goals = excluded.goals, unresolved_threads = excluded.unresolved_threads, boundaries = excluded.boundaries, updated_at = CURRENT_TIMESTAMP `), updateChatMessage: db.prepare("UPDATE chat_messages SET content = ? WHERE id = ? AND chat_id = ?"), updateChatMessageWithRetryState: db.prepare("UPDATE chat_messages SET content = ?, retry_variants = ?, retry_active_index = ?, retry_retries_used = ?, retry_prompt_message_id = ? WHERE id = ? AND chat_id = ?"), deleteChatMessage: db.prepare("DELETE FROM chat_messages WHERE id = ? AND chat_id = ?"), deleteChatMessages: db.prepare("DELETE FROM chat_messages WHERE chat_id = ?"), searchChats: db.prepare(` SELECT cs.id, CASE WHEN cs.assistant_persona_id IS NOT NULL AND cs.source_chat_id IS NULL THEN COALESCE(p.name, cs.title) ELSE cs.title END AS title, cs.title AS stored_title, cs.assistant_persona_id, cs.personality_id, p.name AS assistant_persona_name, cs.user_persona_id, up.name AS user_persona_name, cs.folder_name, cs.is_pinned, cs.archived_at, cs.scenario_prompt, cs.scenario_summary, cs.context_summary, cs.context_summary_message_id, cs.active_leaf_message_id, cs.source_chat_id, cs.branched_from_message_id, cs.preferred_model, cs.temperature, cs.context_length, cs.response_length, cs.system_instruction, cs.created_at, cs.updated_at, (SELECT substr(m.content, 1, 180) FROM chat_messages m WHERE m.chat_id = cs.id AND m.content <> '' ORDER BY m.id DESC LIMIT 1) AS last_message_preview, cm.id AS matched_message_id, cm.content AS matched_content FROM chat_sessions cs LEFT JOIN personas p ON p.id = cs.assistant_persona_id LEFT JOIN personas up ON up.id = cs.user_persona_id LEFT JOIN chat_messages cm ON cm.id = ( SELECT m.id FROM chat_messages m WHERE m.chat_id = cs.id AND LOWER(m.content) LIKE ? ORDER BY m.id DESC LIMIT 1 ) WHERE cs.username = ? AND ( LOWER(CASE WHEN cs.assistant_persona_id IS NOT NULL AND cs.source_chat_id IS NULL THEN COALESCE(p.name, cs.title) ELSE cs.title END) LIKE ? OR cm.id IS NOT NULL ) ORDER BY cs.is_pinned DESC, datetime(cs.updated_at) DESC `) }; } export function createChatRepository(db, config, statements) { const completeChatTurnTransaction = db.transaction((chatId, userMessageId, assistantContent, retryState, modelName, sources, thinking) => { const assistantResult = statements.insertChatMessage.run( chatId, "bot", String(assistantContent || ""), modelName || null, JSON.stringify(retryState.retryVariants), retryState.retryActiveIndex, retryState.retryRetriesUsed, retryState.retryPromptMessageId ?? null, "complete", null, userMessageId ); statements.updateChatMessageDelivery.run("complete", null, userMessageId, chatId); if (sources) statements.updateChatMessageSources.run(JSON.stringify(sources), assistantResult.lastInsertRowid, chatId); if (thinking) statements.updateChatMessageThinking.run(thinking, assistantResult.lastInsertRowid, chatId); statements.setChatActiveLeafById.run(assistantResult.lastInsertRowid, chatId); return assistantResult; }); const insertBranchChatMessageTransaction = db.transaction((chatId, role, content, retryState, modelName, deliveryStatus, errorMessage, parentMessageId) => { const result = statements.insertChatMessage.run( chatId, role, String(content || ""), modelName || null, retryState ? JSON.stringify(retryState.retryVariants) : null, retryState?.retryActiveIndex ?? 0, retryState?.retryRetriesUsed ?? 0, retryState?.retryPromptMessageId ?? null, deliveryStatus, errorMessage, parentMessageId ?? null ); statements.setChatActiveLeafById.run(result.lastInsertRowid, chatId); return result; }); const failChatTurnTransaction = db.transaction((chatId, userMessageId, errorMessage, parentMessageId) => { statements.updateChatMessageDelivery.run("failed", errorMessage, userMessageId, chatId); statements.setChatActiveLeafById.run(parentMessageId ?? null, chatId); }); return { listChats: (username) => statements.listChatSessions.all(username), searchChats(username, query) { const like = `%${String(query || "").trim().toLowerCase()}%`; return statements.searchChats.all(like, username, like); }, countChats: (username) => statements.countChatSessions.get(username).count, getChat: (chatId, username) => statements.getChatSession.get(chatId, username), getChatByParticipants: (username, assistantPersonaId, userPersonaId) => statements.getChatSessionByParticipants.get(username, assistantPersonaId, userPersonaId, userPersonaId), createChat(username, title, assistantPersonaId = null, userPersonaId = null, scenarioPrompt = null, scenarioSummary = null, organization = {}) { const chatId = statements.insertChatSession.run( username, title, assistantPersonaId, userPersonaId, organization.folderName ?? null, organization.isPinned ? 1 : 0, organization.archivedAt ?? null, scenarioPrompt, scenarioSummary ).lastInsertRowid; return statements.getChatSession.get(chatId, username); }, updateChatTitle: (chatId, username, title) => statements.updateChatSessionTitle.run(title, chatId, username), updateChatUserPersona: (chatId, username, personaId) => statements.updateChatUserPersona.run(personaId ?? null, chatId, username), updateChatPersonality: (chatId, username, personalityId) => statements.updateChatPersonality.run(personalityId, chatId, username), updateChatScene: (chatId, username, scenarioPrompt, scenarioSummary) => statements.updateChatSessionScene.run(scenarioPrompt, scenarioSummary, chatId, username), updateChatContextSummary: (chatId, username, summary, messageId) => statements.updateChatContextSummary.run(summary, Number(messageId || 0), chatId, username), getPersonaChatState: (chatId, username) => statements.getPersonaChatState.get(chatId, username) || null, updatePersonaChatState(username, chat, state) { statements.upsertPersonaChatState.run( username, chat.id, chat.assistant_persona_id ?? null, chat.user_persona_id ?? null, state.relationship_notes ?? null, state.tone ?? null, state.current_location ?? null, state.goals ?? null, state.unresolved_threads ?? null, state.boundaries ?? null ); return statements.getPersonaChatState.get(chat.id, username); }, updateChatOrganization(chatId, username, {folderName = null, isPinned = false, archivedAt = null}) { return statements.updateChatSessionOrganization.run(folderName, isPinned ? 1 : 0, archivedAt, chatId, username); }, touchChat: (chatId, username) => statements.touchChatSession.run(chatId, username), deleteChat: (chatId, username) => statements.deleteChatSession.run(chatId, username), listChatMessages: (chatId) => statements.getChatMessages.all(chatId), listActiveBranchMessages: (chatId) => statements.getActiveBranchMessages.all(chatId), listChatBranchThroughMessage: (chatId, messageId) => statements.getChatBranchThroughMessage.all(chatId, messageId), listSiblingChatMessages: (chatId, role, parentMessageId) => statements.getSiblingChatMessages.all(chatId, role, parentMessageId ?? null), getLatestDescendantMessage: (chatId, messageId) => statements.getLatestDescendantMessage.get(chatId, messageId, chatId), getRecentChatMessages: (chatId) => statements.getRecentChatMessages.all(chatId, config.chatHistoryLimit), getRecentChatMessagesUpToId: (chatId, messageId) => statements.getRecentChatMessagesUpToId.all(chatId, messageId, config.chatHistoryLimit), getChatMessage: (chatId, messageId) => statements.getChatMessageById.get(messageId, chatId), getChatMessageByIndex: (chatId, index) => statements.getChatMessageByIndex.get(chatId, index), getLatestChatMessage: (chatId) => statements.getLatestChatMessage.get(chatId), getPreviousUserMessage: (chatId, messageId) => statements.getPreviousUserMessage.get(chatId, messageId), listChatMemories: (chatId, username) => statements.listChatMemories.all(chatId, username), getChatMemory: (chatId, memoryId, username) => statements.getChatMemory.get(memoryId, chatId, username), createChatMemory(username, chat, fact) { const result = statements.insertChatMemory.run( username, chat.id, chat.assistant_persona_id ?? null, chat.user_persona_id ?? null, String(fact || "").trim() ); return statements.getChatMemory.get(result.lastInsertRowid, chat.id, username); }, updateChatMemory: (chatId, memoryId, username, fact) => statements.updateChatMemory.run(String(fact || "").trim(), memoryId, chatId, username), deleteChatMemory: (chatId, memoryId, username) => statements.deleteChatMemory.run(memoryId, chatId, username), insertChatMessage(chatId, role, content, retryState = null, modelName = null, deliveryStatus = "complete", errorMessage = null, parentMessageId = null) { return statements.insertChatMessage.run( chatId, role, String(content || ""), modelName || null, retryState ? JSON.stringify(retryState.retryVariants) : null, retryState?.retryActiveIndex ?? 0, retryState?.retryRetriesUsed ?? 0, retryState?.retryPromptMessageId ?? null, deliveryStatus, errorMessage, parentMessageId ); }, insertBranchChatMessage: (chatId, role, content, retryState = null, modelName = null, deliveryStatus = "complete", errorMessage = null, parentMessageId = null) => insertBranchChatMessageTransaction(chatId, role, content, retryState, modelName, deliveryStatus, errorMessage, parentMessageId), updateChatMessageDelivery: (chatId, messageId, deliveryStatus, errorMessage = null) => statements.updateChatMessageDelivery.run(deliveryStatus, errorMessage, messageId, chatId), completeChatTurn: (chatId, userMessageId, assistantContent, retryState, modelName = null, sources = null, thinking = null) => completeChatTurnTransaction(chatId, userMessageId, assistantContent, retryState, modelName, sources, thinking), updateChatMessageThinking: (chatId, messageId, thinking) => statements.updateChatMessageThinking.run(thinking, messageId, chatId), updateChatMessageSources: (chatId, messageId, sources) => statements.updateChatMessageSources.run(JSON.stringify(sources), messageId, chatId), failChatTurn: (chatId, userMessageId, errorMessage, parentMessageId = null) => failChatTurnTransaction(chatId, userMessageId, errorMessage, parentMessageId), updateChatActiveLeaf: (chatId, username, messageId) => statements.updateChatActiveLeaf.run(messageId, chatId, username), updateChatBranchSource: (chatId, username, sourceChatId, messageId) => statements.updateChatBranchSource.run(sourceChatId, messageId ?? null, chatId, username), updateChatGenerationSettings: (chatId, username, settings) => statements.updateChatGenerationSettings.run( settings.preferredModel ?? null, settings.temperature ?? null, settings.contextLength ?? null, settings.responseLength ?? null, settings.systemInstruction ?? null, chatId, username ), updateChatPreferredModel: (chatId, username, model) => statements.updateChatPreferredModel.run(model || null, chatId, username), listMessageVersions: (messageId) => db.prepare("SELECT * FROM chat_message_versions WHERE message_id = ? ORDER BY id").all(messageId), saveMessageVersion: (chatId, messageId, version = null, selectedIndex = null) => db.transaction(() => { const row = statements.getChatMessageById.get(messageId, chatId); if (!row) throw new Error("Message not found"); let versions = db.prepare("SELECT * FROM chat_message_versions WHERE message_id = ? ORDER BY id").all(messageId); const insert = db.prepare("INSERT INTO chat_message_versions (message_id, content, thinking, sources, model_name) VALUES (?, ?, ?, ?, ?)"); if (!versions.length) { db.prepare("INSERT INTO chat_message_versions (message_id, content, thinking, sources, model_name, created_at) VALUES (?, ?, ?, ?, ?, ?)") .run(messageId, row.content, row.thinking, row.sources, row.model_name, row.created_at); } else { // Edits keep their existing semantics: update the selected answer. db.prepare("UPDATE chat_message_versions SET content = ?, thinking = ?, sources = ?, model_name = ? WHERE id = ?") .run(row.content, row.thinking, row.sources, row.model_name, versions[row.retry_active_index]?.id ?? versions[0].id); } if (version) insert.run(messageId, version.content, version.thinking || null, JSON.stringify(version.sources ?? null), version.modelName); versions = db.prepare("SELECT * FROM chat_message_versions WHERE message_id = ? ORDER BY id").all(messageId); const index = version ? versions.length - 1 : selectedIndex; if (!Number.isInteger(index) || !versions[index]) throw new Error("Version is out of range"); const active = versions[index]; db.prepare(`UPDATE chat_messages SET content = ?, thinking = ?, sources = ?, model_name = ?, retry_variants = ?, retry_active_index = ?, retry_retries_used = ? WHERE id = ? AND chat_id = ?`) .run(active.content, active.thinking, active.sources, active.model_name, JSON.stringify(versions.map(item => item.content)), index, versions.length - 1, messageId, chatId); })(), updateChatMessage: (chatId, messageId, content) => statements.updateChatMessage.run(content, messageId, chatId), updateChatMessageRetryState: (chatId, messageId, payload) => statements.updateChatMessageWithRetryState.run( payload.content, JSON.stringify(payload.retryVariants), payload.retryActiveIndex, payload.retryRetriesUsed, payload.retryPromptMessageId ?? null, messageId, chatId ), deleteChatMessage: (chatId, messageId) => statements.deleteChatMessage.run(messageId, chatId), clearChatMessages: (chatId) => statements.deleteChatMessages.run(chatId) }; }