/** * ArtifactStore.ts — Dexie/IndexedDB persistence for workspace artifacts (FASE 7) * * Artifacts are first-class workspace objects: code files, HTML pages, * Markdown docs, CSV tables, JSON data, SVG images generated by the agent. * * Uses the same Dexie instance pattern as the rest of the app. * Zero external dependencies beyond Dexie (already in package). */ import Dexie, { type Table } from "dexie"; export type ArtifactKind = | "code" // runnable code (python, typescript, javascript, rust, …) | "html" // full HTML page → sandboxed iframe | "markdown" // rendered markdown document | "csv" // tabular data → rendered table | "json" // structured data | "svg" // vector image | "text"; // plain text / unknown export interface Artifact { id: string; // nanoid / uuid kind: ArtifactKind; language?: string; // for "code" kind: python, typescript, … title: string; // short descriptive name content: string; // raw text content sessionId?: string; // conversation session it belongs to messageId?: string; // message that generated it createdAt: number; // unix ms updatedAt: number; // unix ms version: number; // monotonic — bump on edit pinned: boolean; // user pinned to sidebar tags: string[]; } class ArtifactDatabase extends Dexie { artifacts!: Table; constructor() { super("agente_artifacts_v1"); this.version(1).stores({ artifacts: "id, kind, sessionId, createdAt, updatedAt, pinned", }); } } let _db: ArtifactDatabase | null = null; function db(): ArtifactDatabase { if (!_db) _db = new ArtifactDatabase(); return _db; } // ── CRUD ───────────────────────────────────────────────────────────────────── export async function saveArtifact(artifact: Artifact): Promise { await db().artifacts.put(artifact); } export async function getArtifact(id: string): Promise { return db().artifacts.get(id); } export async function updateArtifact(id: string, patch: Partial>): Promise { const existing = await db().artifacts.get(id); if (!existing) return; await db().artifacts.put({ ...existing, ...patch, id, updatedAt: Date.now(), version: existing.version + 1, }); } export async function deleteArtifact(id: string): Promise { await db().artifacts.delete(id); } export async function listArtifacts(opts: { sessionId?: string; kind?: ArtifactKind; pinned?: boolean; limit?: number; } = {}): Promise { let query = db().artifacts.orderBy("updatedAt").reverse(); const { sessionId, kind, pinned, limit } = opts; const results = await query.filter(a => { if (sessionId && a.sessionId !== sessionId) return false; if (kind && a.kind !== kind) return false; if (pinned !== undefined && a.pinned !== pinned) return false; return true; }).toArray(); return limit ? results.slice(0, limit) : results; } export async function searchArtifacts(query: string): Promise { const q = query.toLowerCase(); return db().artifacts .filter(a => a.title.toLowerCase().includes(q) || a.content.toLowerCase().includes(q)) .toArray(); } export async function clearSessionArtifacts(sessionId: string): Promise { return db().artifacts.where("sessionId").equals(sessionId).delete(); }