// Name: Filter LLM Conversations // Description: Runs a Python export script (uv) to fetch LLM conversations from a SQLite DB, then lets you filter and view them. // Author: ScottGuthart // GitHub: ScottGuthart import "@johnlindquist/kit" type Message = { role: "user" | "assistant"; content: string } type Conversation = { id: number | null conversation_id: number | string | null datetime_utc: string | null datetime_est: string datetime_relative: string model: string messages: Message[] } // Ensure the SQLite DB path exists in env const DB_PATH = await env("DATABASE_URL", async () => { return await selectFile("Select your SQLite logs database (e.g., /path/to/logs.db)") }) // Optional: number of past conversations (0=all). Default 3. let defaultCount = 3 let count = defaultCount let cliCount = typeof flag?.count !== "undefined" ? String(flag.count) : "" if (cliCount && cliCount !== "true") { const n = Number(cliCount) if (!Number.isNaN(n)) count = n } else { const input = await arg( { placeholder: `How many past conversations? (0 = all, default ${defaultCount})`, strict: false, enter: "Run", }, ["3", "10", "20", "50", "0"] ) const n = Number(String(input).trim() || `${defaultCount}`) count = Number.isNaN(n) ? defaultCount : n } setLoading(true) let stdout = "" try { const cmd = `uv run export_logs.py "${DB_PATH}" --count ${count}` const result = await exec(cmd, { cwd: process.cwd(), all: true, shell: process.env.KIT_SHELL || "/bin/zsh", }) stdout = result.all || result.stdout || "" } catch (error: any) { setLoading(false) await div( md(` # Failed to export logs - Command: \`uv run export_logs.py "${DB_PATH}" --count ${count}\` - Error: \`\`\` ${error?.all || error?.message || String(error)} \`\`\` Make sure: - "uv" is installed and on your PATH - "export_logs.py" is available in the current directory - The database path is correct `) ) exit() } setLoading(false) // Parse JSON output let conversations: Conversation[] = [] try { conversations = JSON.parse(stdout || "[]") } catch { await div( md(` # Unexpected output from Python script Raw output: \`\`\` ${stdout.trim()} \`\`\` `) ) exit() } if (!Array.isArray(conversations) || conversations.length === 0) { await div(md(`# No conversations found`)) exit() } // Build choices for filtering const toTranscript = (c: Conversation) => { const meta = [ `Model: ${c.model || ""}`, `Date (EST): ${c.datetime_est || ""}`, `Relative: ${c.datetime_relative || ""}`, `UTC: ${c.datetime_utc || ""}`, `ID: ${c.id ?? ""}`, `Conversation ID: ${c.conversation_id ?? ""}`, ] .filter(Boolean) .join("\n") const msgs = c.messages ?.map(m => `### ${m.role}\n\n${m.content?.trim() || ""}`) .join("\n\n---\n\n") return `# Conversation ${meta} --- ${msgs || "_No messages_"} ` } const choices = conversations.map((c, i) => { const firstUser = c.messages?.find(m => m.role === "user") const titleBits = [ c.datetime_est || c.datetime_utc || "", c.model || "", firstUser?.content ? firstUser.content.slice(0, 80).replace(/\s+/g, " ") : "", ].filter(Boolean) const name = titleBits.join(" — ") const description = [c.datetime_relative || "", `ID: ${c.id ?? ""}`, `Conv: ${c.conversation_id ?? ""}`] .filter(Boolean) .join(" | ") return { name: name || `Conversation ${i + 1}`, description, value: c, preview: () => md(` ## ${name || `Conversation ${i + 1}`} - Model: ${c.model || ""} - EST: ${c.datetime_est || ""} - Relative: ${c.datetime_relative || ""} - UTC: ${c.datetime_utc || ""} - ID: ${c.id ?? ""} - Conversation ID: ${c.conversation_id ?? ""} --- ${c.messages ?.map(m => `**${m.role}**: ${m.content?.trim() || ""}`) .join("\n\n")} `), } }) // Let user filter and pick a conversation const picked = await arg<Conversation>( { placeholder: `Filter ${conversations.length} conversations`, enter: "Open", }, choices ) // Show transcript in editor with actions const transcript = toTranscript(picked) await editor({ value: transcript, language: "md", footer: `Conversation viewer — ${picked.model || ""} — ${picked.datetime_est || picked.datetime_utc || ""}`, actions: [ { name: "Copy Transcript", shortcut: `${cmd}+c`, visible: true, onAction: async () => { await copy(transcript) toast("Transcript copied") }, }, { name: "Copy Raw JSON", shortcut: `${cmd}+j`, visible: true, onAction: async () => { await copy(JSON.stringify(picked, null, 2)) toast("JSON copied") }, }, ], })