/* global React */ const { useState, useEffect, useRef, useMemo } = React; // ============================================================ // MCP Skills catalog // ============================================================ const SKILLS = [ { id: "scaffold", name: "scaffold", group: "structure", desc: { es: "Genera andamiaje base", en: "Generates base scaffolding", fr: "Génère l'échafaudage de base" } }, { id: "ui-kit", name: "ui-kit", group: "ui", desc: { es: "Inserta componentes UI", en: "Insert UI components", fr: "Insérer des composants UI" } }, { id: "type-pair", name: "type-pair", group: "ui", desc: { es: "Pareja tipográfica", en: "Type pairing", fr: "Appariement typo" } }, { id: "color-palette", name: "color-palette", group: "ui", desc: { es: "Paleta armoniosa", en: "Harmonious palette", fr: "Palette harmonieuse" } }, { id: "copywriter", name: "copywriter", group: "content", desc: { es: "Escribe textos", en: "Writes copy", fr: "Rédige les textes" } }, { id: "seo-meta", name: "seo-meta", group: "content", desc: { es: "Metadatos SEO", en: "SEO metadata", fr: "Métadonnées SEO" } }, { id: "auth", name: "auth", group: "data", desc: { es: "Autenticación", en: "Authentication", fr: "Authentification" } }, { id: "db-schema", name: "db-schema", group: "data", desc: { es: "Esquema de base de datos", en: "Database schema", fr: "Schéma BDD" } }, { id: "ai-chat", name: "ai-chat", group: "ai", desc: { es: "Chat con IA", en: "AI chat", fr: "Chat IA" } }, { id: "ai-rag", name: "ai-rag", group: "ai", desc: { es: "Recuperación + IA", en: "Retrieval + AI", fr: "Récup. + IA" } }, { id: "responsive", name: "responsive", group: "ui", desc: { es: "Adapta a móvil", en: "Mobile-friendly", fr: "Adapter mobile" } }, { id: "a11y", name: "a11y", group: "quality", desc: { es: "Accesibilidad", en: "Accessibility", fr: "Accessibilité" } }, { id: "test", name: "test", group: "quality", desc: { es: "Pruebas básicas", en: "Basic tests", fr: "Tests basiques" } }, { id: "deploy", name: "deploy", group: "ops", desc: { es: "Despliegue", en: "Deployment", fr: "Déploiement" } }, ]; const GROUP_LABELS = { structure: { es: "Estructura", en: "Structure", fr: "Structure" }, ui: { es: "Interfaz", en: "UI", fr: "Interface" }, content: { es: "Contenido", en: "Content", fr: "Contenu" }, data: { es: "Datos", en: "Data", fr: "Données" }, ai: { es: "IA", en: "AI", fr: "IA" }, quality: { es: "Calidad", en: "Quality", fr: "Qualité" }, ops: { es: "Operación", en: "Ops", fr: "Ops" }, }; // ============================================================ // Sample code per project type — short, illustrative // ============================================================ function makeSampleCode(state) { const t = state.title || "my-project"; if (state.projectType === "landing") { return [ ``, ``, ` `, ` `, ` ${t}`, ` `, ` `, ` `, `
`, `

${state.goal || "A bold idea, ready to ship."}

`, `

${state.desc || "Tell the world what makes this different."}

`, ` Get early access →`, `
`, `
`, `

Fast

Built for speed.

`, `

Quiet

No noise, just signal.

`, `

Yours

You own everything.

`, `
`, ` `, ` `, ``, ]; } if (state.projectType === "ai") { return [ `// agent/system.md`, `// You are an assistant for "${t}".`, `// Goal: ${state.goal || "help the user, kindly."}`, ``, `import { stream } from "../lib/ai.js";`, `import { tools } from "./tools.json";`, ``, `export async function chat(messages) {`, ` const system = await load("agent/system.md");`, ` return stream({`, ` provider: "${state.aiProvider || "openai"}",`, ` system,`, ` tools,`, ` messages,`, ` });`, `}`, ]; } if (state.projectType === "app") { return [ `// src/App.jsx`, `import { Routes, Route } from "./routes/router.js";`, `import Home from "./routes/Home.jsx";`, `import Dashboard from "./routes/Dashboard.jsx";`, ``, `export default function App() {`, ` return (`, ` `, ` } />`, ` } />`, ` `, ` );`, `}`, ]; } // site return [ ``, ``, ``, ` `, ` `, ` ${t} — home`, ` `, ` `, ` `, ` `, `
`, `

${t}

`, `

${state.desc || "A small site with care."}

`, `
`, ` `, ` `, ``, ]; } // ============================================================ // Editor screen // ============================================================ function Editor({ state, onDeploy, onBack }) { const { t, lang } = useT(); const tree = STRUCTURES[state.projectType] || STRUCTURES.landing; const initialFile = (tree.find(n => !n.dir) || tree[0]).name; const [activeFile, setActiveFile] = useState(initialFile); const [skillQuery, setSkillQuery] = useState(""); const [agentMessages, setAgentMessages] = useState(() => seedAgentMessages(state, lang)); const [agentInput, setAgentInput] = useState(""); const [agentBusy, setAgentBusy] = useState(false); const [activeSkill, setActiveSkill] = useState(null); const [tutor, setTutor] = useState(null); const [tab, setTab] = useState("preview"); // preview | console const codeLines = useMemo(() => makeSampleCode(state), [state.projectType, state.title, state.goal, state.desc, state.aiProvider]); // Tutor surfaces depending on context useEffect(() => { const id = setTimeout(() => setTutor(getTutorTip(activeFile, lang)), 1400); return () => clearTimeout(id); }, [activeFile, lang]); const filteredSkills = SKILLS.filter(s => s.name.includes(skillQuery.toLowerCase()) || s.group.includes(skillQuery.toLowerCase())); const grouped = useMemo(() => { const out = {}; for (const s of filteredSkills) { out[s.group] = out[s.group] || []; out[s.group].push(s); } return out; }, [filteredSkills]); const callSkill = (skill) => { setActiveSkill(skill.id); setAgentBusy(true); setAgentMessages((m) => [...m, { role: "user", text: `/${skill.name}`, kind: "skill" }]); setTimeout(() => { setAgentMessages((m) => [...m, { role: "agent", text: { es: `Aplicando \`${skill.name}\` en ${activeFile}. Editando contenido sin cambiar la estructura.`, en: `Applying \`${skill.name}\` to ${activeFile}. Editing content without changing the structure.`, fr: `Application de \`${skill.name}\` sur ${activeFile}. Modification du contenu sans changer la structure.`, }[lang], kind: "edit", file: activeFile, skill: skill.name, }]); setAgentBusy(false); }, 1200); }; const sendAgent = () => { if (!agentInput.trim()) return; setAgentMessages((m) => [...m, { role: "user", text: agentInput }]); setAgentBusy(true); setAgentInput(""); setTimeout(() => { setAgentMessages((m) => [...m, { role: "agent", text: { es: "Entendido. Voy a llamar las skills necesarias y editar los archivos correspondientes.", en: "Got it. I'll call the needed skills and edit the relevant files.", fr: "Compris. J'appelle les skills nécessaires et modifie les fichiers concernés.", }[lang], }]); setAgentBusy(false); }, 1100); }; return (
{/* LEFT: files + skills */} {/* CENTER: code editor */}
{activeFile}
            {codeLines.map((line, i) => (
              
{String(i + 1).padStart(2, "0")}
))} {agentBusy && (
·· {t.ed_thinking}
)}
utf-8 · lf · {codeLines.length} lines edited by agent · just now
{/* RIGHT: agent + preview */} {tutor && setTutor(null)} />}
); } function PreviewBox({ state }) { const t = state.title || "untitled"; return (
localhost:5173
{state.projectType === "ai" ? (
{state.goal || "How can I help today?"}
) : state.projectType === "app" ? (
{t}
) : (
{t}
{(state.desc || "A new beginning, gently typed.").slice(0, 80)}
— continue —
)}
); } function ConsoleBox() { const lines = [ "[mcp] connected · 14 skills available", "[scaffold] generated 7 files", "[ui-kit] inserted Button, Card, Pill", "[type-pair] Fraunces × Geist", "[agent] watching for edits…", "ready.", ]; return (
{lines.map((l, i) => (
{String(10 + i).padStart(2, "0")}:42:0{i} {l}
))}
); } function TutorBubble({ tip, onDismiss }) { const { t } = useT(); return (
{t.tutor_title}
{tip.body}
{tip.skills.map((s) => ( /{s} ))}
); } function getTutorTip(file, lang) { const tips = { es: { body: `Estás editando ${file}. Puedes invocar skills escribiendo \`/\` o haciendo clic en el panel izquierdo.`, skills: ["ui-kit", "copywriter", "responsive"], }, en: { body: `You're editing ${file}. Invoke skills by typing \`/\` or clicking them on the left.`, skills: ["ui-kit", "copywriter", "responsive"], }, fr: { body: `Vous éditez ${file}. Invoquez des skills en tapant \`/\` ou via le panneau de gauche.`, skills: ["ui-kit", "copywriter", "responsive"], }, }; return tips[lang]; } function seedAgentMessages(state, lang) { const greet = { es: `Listo. Tengo "${state.title || "tu proyecto"}" cargado y la estructura de ${state.projectType}. Voy a empezar por ${defaultFirstFile(state.projectType)}.`, en: `Ready. I loaded "${state.title || "your project"}" with the ${state.projectType} structure. Starting with ${defaultFirstFile(state.projectType)}.`, fr: `Prêt. "${state.title || "votre projet"}" chargé avec la structure ${state.projectType}. Je commence par ${defaultFirstFile(state.projectType)}.`, }[lang]; return [ { role: "agent", text: greet }, { role: "agent", kind: "edit", text: { es: "Aplicando type-pair y color-palette…", en: "Applying type-pair and color-palette…", fr: "Application de type-pair et color-palette…" }[lang], skill: "type-pair", file: defaultFirstFile(state.projectType) }, ]; } function defaultFirstFile(type) { return ({ landing: "index.html", site: "pages/index.html", app: "src/App.jsx", ai: "agent/system.md" })[type] || "index.html"; } // Lightweight token highlighter (regex-based, very small) function highlight(line) { const esc = (s) => s.replace(/&/g, "&").replace(//g, ">"); let h = esc(line); // comments h = h.replace(/(\/\/[^\n]*|)/g, '$1'); // strings h = h.replace(/("[^"]*"|'[^']*')/g, '$1'); // tags h = h.replace(/(<\/?[a-zA-Z][^&]*?>)/g, '$1'); // keywords h = h.replace(/\b(import|from|export|default|function|const|let|return|async|await|if|else|class|new)\b/g, '$1'); return h; } Object.assign(window, { Editor, SKILLS });