"use strict"; import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; import { Type } from "typebox"; import { StringEnum } from "@earendil-works/pi-ai"; import { getConfig } from "../config"; import { processImage } from "../media"; import { visionCompletion } from "../client"; import { uiToArtifactPrompt } from "../prompts"; import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, truncateHead, } from "@earendil-works/pi-coding-agent"; export function registerUiToArtifactTool(pi: ExtensionAPI) { pi.registerTool({ name: "ui_to_artifact", label: "UI to Artifact", description: "Convert UI screenshots into production-ready frontend code (HTML/CSS/React), technical specifications, design prompts for other AI, or natural language descriptions. Choose output type via the outputType parameter. Defaults to code generation. Supports local files (png, jpg) and HTTP(S) URLs.", promptSnippet: "Convert UI screenshots to code (HTML/CSS/React), specs, prompts, or descriptions", promptGuidelines: [ "Use ui_to_artifact when the user wants to convert a UI design screenshot into code, a spec, a prompt, or a description. Default output is code (HTML/CSS). Use outputType='spec' for a technical specification, outputType='prompt' for an LLM design prompt, or outputType='description' for a natural language description.", ], parameters: Type.Object({ imagePath: Type.String({ description: "Path to the UI screenshot (relative to cwd, supports @ prefix) or HTTP(S) URL.", }), prompt: Type.Optional( Type.String({ description: "Additional instructions for the conversion (e.g., 'Use Tailwind CSS', 'Make it responsive', 'Focus on the sidebar component').", }) ), outputType: Type.Optional( StringEnum(["code", "prompt", "spec", "description"] as const) ), }), async execute(_toolCallId, params, signal, onUpdate, ctx) { const config = getConfig(); const outputType = params.outputType || "code"; const systemPrompt = uiToArtifactPrompt(outputType); if (!systemPrompt) { throw new Error( `Invalid outputType "${outputType}". Must be one of: code, prompt, spec, description.` ); } const typeLabels: Record = { code: "生成代码", prompt: "生成 AI 提示词", spec: "生成设计规范", description: "生成文字描述", }; onUpdate?.({ content: [ { type: "text", text: `正在调用 GLM-4.6V ${typeLabels[outputType] || "转换"}...`, }, ], }); const image = processImage(params.imagePath, ctx.cwd, config.maxImageSizeMB); const userPrompt = params.prompt || (outputType === "code" ? "Generate production-ready HTML/CSS code for this UI design." : outputType === "prompt" ? "Generate a comprehensive prompt that another AI could use to recreate this UI." : outputType === "spec" ? "Generate a detailed design specification document for this UI." : "Describe this UI in natural language, covering layout, components, and interactions."); const result = await visionCompletion( config, systemPrompt, [image], userPrompt, signal ); const truncated = truncateHead(result, { maxLines: DEFAULT_MAX_LINES, maxBytes: DEFAULT_MAX_BYTES, }); let text = truncated.content; if (truncated.truncated) { text += `\n\n[输出已截断: ${truncated.outputLines}/${truncated.totalLines} 行]`; } return { content: [{ type: "text", text }], details: { raw: result, outputType }, }; }, }); }