diff --git a/packages/web-ui/src/components/sandbox/ArtifactsRuntimeProvider.ts b/packages/web-ui/src/components/sandbox/ArtifactsRuntimeProvider.ts index b5067847..75a6be27 100644 --- a/packages/web-ui/src/components/sandbox/ArtifactsRuntimeProvider.ts +++ b/packages/web-ui/src/components/sandbox/ArtifactsRuntimeProvider.ts @@ -90,9 +90,13 @@ export class ArtifactsRuntimeProvider implements SandboxRuntimeProvider { return content; }; - (window as any).createArtifact = async (filename: string, content: any, mimeType?: string): Promise => { + (window as any).createOrUpdateArtifact = async ( + filename: string, + content: any, + mimeType?: string, + ): Promise => { if (!(window as any).sendRuntimeMessage) { - throw new Error("Cannot create artifacts in offline mode (read-only)"); + throw new Error("Cannot create/update artifacts in offline mode (read-only)"); } let finalContent = content; @@ -105,30 +109,7 @@ export class ArtifactsRuntimeProvider implements SandboxRuntimeProvider { const response = await (window as any).sendRuntimeMessage({ type: "artifact-operation", - action: "create", - filename, - content: finalContent, - mimeType, - }); - if (!response.success) throw new Error(response.error); - }; - - (window as any).updateArtifact = async (filename: string, content: any, mimeType?: string): Promise => { - if (!(window as any).sendRuntimeMessage) { - throw new Error("Cannot update artifacts in offline mode (read-only)"); - } - - let finalContent = content; - // Auto-stringify .json files - if (isJsonFile(filename) && typeof content !== "string") { - finalContent = JSON.stringify(content, null, 2); - } else if (typeof content !== "string") { - finalContent = JSON.stringify(content, null, 2); - } - - const response = await (window as any).sendRuntimeMessage({ - type: "artifact-operation", - action: "update", + action: "createOrUpdate", filename, content: finalContent, mimeType, @@ -176,40 +157,23 @@ export class ArtifactsRuntimeProvider implements SandboxRuntimeProvider { break; } - case "create": { + case "createOrUpdate": { try { - await this.artifactsPanel.tool.execute("", { - command: "create", - filename, - content, - }); - this.agent?.appendMessage({ - role: "artifact", - action: "create", - filename, - content, - title: filename, - timestamp: new Date().toISOString(), - }); - respond({ success: true }); - } catch (err: any) { - respond({ success: false, error: err.message }); - } - break; - } + const exists = this.artifactsPanel.artifacts.has(filename); + const command = exists ? "rewrite" : "create"; + const action = exists ? "update" : "create"; - case "update": { - try { await this.artifactsPanel.tool.execute("", { - command: "rewrite", + command, filename, content, }); this.agent?.appendMessage({ role: "artifact", - action: "update", + action, filename, content, + ...(action === "create" && { title: filename }), timestamp: new Date().toISOString(), }); respond({ success: true }); diff --git a/packages/web-ui/src/prompts/tool-prompts.ts b/packages/web-ui/src/prompts/tool-prompts.ts index 761a5129..2b376960 100644 --- a/packages/web-ui/src/prompts/tool-prompts.ts +++ b/packages/web-ui/src/prompts/tool-prompts.ts @@ -193,29 +193,26 @@ Artifact Management (persistent session files you can access/modify programmatic * Auto-parses .json files to objects, otherwise returns raw string content * Example: const data = await getArtifact('data.json'); // Returns parsed object * Example: const markdown = await getArtifact('notes.md'); // Returns string -- await createArtifact(filename, content) - Create new persistent artifact +- await createOrUpdateArtifact(filename, content, mimeType?) - Create or update a persistent artifact + * Automatically creates if new, updates if exists (no need to check hasArtifact first) * Auto-stringifies objects for .json files - * Example: await createArtifact('data.json', {items: []}) // Auto-stringifies - * Example: await createArtifact('research-notes.md', '# Research Notes\\n', 'text/markdown') -- await updateArtifact(filename, content) - Completely replace artifact content - * Auto-stringifies objects for .json files - * Full content replacement (not diff-based) - * Example: const data = await getArtifact('data.json'); data.items.push(newItem); await updateArtifact('data.json', data); - * Example: await updateArtifact('research-notes.md', updatedMarkdown, 'text/markdown') + * Example: await createOrUpdateArtifact('data.json', {items: []}) // Auto-stringifies + * Example: await createOrUpdateArtifact('research-notes.md', '# Research Notes\\n', 'text/markdown') + * Full content replacement (not diff-based) when updating - await deleteArtifact(filename) - Delete an artifact * Example: await deleteArtifact('old-notes.md') Powerful pattern for evolving data: const data = await hasArtifact('data.json') ? await getArtifact('data.json') : {items: []}; data.items.push(newScrapedItem); - await (await hasArtifact('data.json') ? updateArtifact : createArtifact)('data.json', data); + await createOrUpdateArtifact('data.json', data); -Binary data must be converted to a base64 string before passing to createArtifact or updateArtifact. +Binary data must be converted to a base64 string before passing to createOrUpdateArtifact. Example: const blob = await new Promise(resolve => canvas.toBlob(resolve, 'image/png')); const arrayBuffer = await blob.arrayBuffer(); const base64 = btoa(String.fromCharCode(...new Uint8Array(arrayBuffer))); - await createArtifact('image.png', base64); + await createOrUpdateArtifact('image.png', base64); `; // ============================================================================