Spaces:
Sleeping
Sleeping
| /** | |
| * 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<Artifact, string>; | |
| 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<void> { | |
| await db().artifacts.put(artifact); | |
| } | |
| export async function getArtifact(id: string): Promise<Artifact | undefined> { | |
| return db().artifacts.get(id); | |
| } | |
| export async function updateArtifact(id: string, patch: Partial<Omit<Artifact, "id" | "createdAt">>): Promise<void> { | |
| 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<void> { | |
| await db().artifacts.delete(id); | |
| } | |
| export async function listArtifacts(opts: { | |
| sessionId?: string; | |
| kind?: ArtifactKind; | |
| pinned?: boolean; | |
| limit?: number; | |
| } = {}): Promise<Artifact[]> { | |
| 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<Artifact[]> { | |
| 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<number> { | |
| return db().artifacts.where("sessionId").equals(sessionId).delete(); | |
| } | |