"use client"; import { useCallback, useEffect, useMemo, useState } from "react"; type Project = { id: string; name: string; revision: number; updatedAt: string; nodeModulesCleanedAt?: string }; type Report = { status: "passed" | "failed"; skillVersion: string; auditOutput: string; issues: Array<{ severity: string; code: string; file: string; line?: number; message: string; suggestion: string }>; limits: { zipBytes?: number; unpackedBytes: number } }; async function api(url: string, init?: RequestInit): Promise { const response = await fetch(url, { ...init, headers: { "Content-Type": "application/json", ...(init?.headers || {}) } }); const data = await response.json(); if (!response.ok) throw new Error(data.error || `请求失败:${response.status}`); return data; } export default function Studio() { const [projects, setProjects] = useState([]); const [selected, setSelected] = useState(null); const [files, setFiles] = useState([]); const [activeFile, setActiveFile] = useState("src/App.jsx"); const [content, setContent] = useState(""); const [report, setReport] = useState(null); const [busy, setBusy] = useState(""); const [message, setMessage] = useState("连接已就绪"); const [previewKey, setPreviewKey] = useState(0); const loadProjects = useCallback(async () => { const data = await api<{ projects: Project[] }>("/api/projects"); setProjects(data.projects); if (!selected && data.projects[0]) setSelected(data.projects[0]); }, [selected]); useEffect(() => { void loadProjects(); }, [loadProjects]); useEffect(() => { if (!selected) return; void api<{ files: string[] }>(`/api/projects/${selected.id}`).then((data) => setFiles(data.files)); }, [selected]); useEffect(() => { if (!selected || !activeFile) return; void api<{ content: string }>(`/api/projects/${selected.id}/files/${activeFile}`).then((data) => setContent(data.content)); }, [selected, activeFile]); const previewUrl = useMemo(() => selected ? `/api/projects/${selected.id}/preview/index.html?v=${previewKey}` : "about:blank", [selected, previewKey]); async function createProject() { setBusy("create"); try { const data = await api<{ project: Project }>("/api/projects", { method: "POST", body: JSON.stringify({ name: `灵感小工具 ${projects.length + 1}`, template: "basic" }) }); setSelected(data.project); setReport(null); setMessage("项目已创建"); await loadProjects(); } catch (error) { setMessage((error as Error).message); } finally { setBusy(""); } } async function save() { if (!selected) return; setBusy("save"); try { const data = await api<{ project: Project }>(`/api/projects/${selected.id}/files/${activeFile}`, { method: "PUT", body: JSON.stringify({ content, revision: selected.revision }) }); setSelected(data.project); setMessage(`已保存 · revision ${data.project.revision}`); } catch (error) { setMessage((error as Error).message); } finally { setBusy(""); } } async function run(action: "build" | "validate" | "package") { if (!selected) return; setBusy(action); try { const data = await api<{ report: Report; artifactUrl?: string }>(`/api/projects/${selected.id}/${action}`, { method: "POST", body: "{}" }); setReport(data.report); setPreviewKey((value) => value + 1); setMessage(data.report.status === "passed" ? `${action} 已通过` : `${action} 发现 ${data.report.issues.length} 个问题`); if (data.artifactUrl) setMessage(`ZIP 已生成:${data.artifactUrl}`); } catch (error) { setMessage((error as Error).message); } finally { setBusy(""); } } return
RRednote Tool StudioAgent-first 小工具工作台
OpenAPI
{activeFile}{selected ? selected.id : "未选择项目"}