import { tool } from "ai"; import { z } from "zod"; /** * Tools for the Embed Studio AI. These return instructions that the * frontend executes on the EmbedStore (Y.Map). The backend never * touches the Yjs document directly. */ export const embedTools = { createEmbed: tool({ description: "Create or fully replace an HTML embed chart. Use when building a new chart from scratch " + "or when changes are so extensive that a full rewrite is cleaner than patching. " + "The HTML must be a self-contained fragment following D3 embed conventions " + "(root div + scoped style + IIFE script).", inputSchema: z.object({ html: z .string() .describe( "Complete self-contained HTML fragment (root div + scoped style + IIFE script)", ), title: z .string() .optional() .describe("Short descriptive title for the chart"), source: z .string() .optional() .describe("Data source attribution"), filename: z .string() .optional() .describe( "Descriptive file name slug for this chart, WITHOUT extension " + "(e.g. 'attention-heatmap', 'model-accuracy', 'sales-by-region'). " + "Use kebab-case, ASCII only, concise (2-4 words, <40 chars). " + "REQUIRED on the first createEmbed call for a new chart - the " + "client will slugify it, ensure uniqueness, and rename the file " + "automatically. Omit on subsequent calls that only update the " + "existing chart (the current name will be kept).", ), }), }), patchEmbed: tool({ description: "Replace a specific block of code in the current chart HTML. " + "Use for targeted edits (color change, label update, data tweak, bug fix). " + "Always prefer this over createEmbed when modifying an existing chart. " + "The search string must be an exact verbatim excerpt from the current HTML " + "(whitespace included). Call readEmbed first if unsure of the exact content.", inputSchema: z.object({ search: z .string() .describe( "Exact verbatim excerpt from the current chart HTML to find and replace. " + "Must be long enough to be unique (at least 3-5 lines of context).", ), replace: z .string() .describe("The new code that replaces the search block"), }), }), readEmbed: tool({ description: "Read the full current chart HTML. Use before calling patchEmbed " + "to verify the exact content, or to understand the current structure before editing.", inputSchema: z.object({}), }), listDataFiles: tool({ description: "List all data files (CSV, TSV, JSON, NDJSON, TXT) the user has " + "attached to this document. Returns one line per file with name, " + "extension, size, row count, and column names when available. " + "Call this when the user mentions data, a dataset, or wants to " + "visualize content from a specific file.", inputSchema: z.object({}), }), readDataFile: tool({ description: "Read the full raw content of a data file previously listed by " + "listDataFiles. Use this to inspect the exact structure, sample " + "values, or to embed small datasets directly into the chart. Large " + "files may be truncated - prefer loading via fetch from " + "/data/ for charts that rely on the full dataset.", inputSchema: z.object({ name: z .string() .describe("Exact file name as returned by listDataFiles (e.g. 'sales.csv')"), }), }), };