import React, { useEffect, useState, useRef } from "react"; import { Editor, DiffEditor } from "@monaco-editor/react"; import { create } from "zustand"; import { io } from "socket.io-client"; const BASE_URL = "http://localhost:7860"; const useStore = create((set, get) => ({ files: [], selectedFile: null, editorContent: "", dbSchema: { tables: [] }, chat: [], consoleOutput: "", loading: false, fileProposals: {}, showDiffForFile: null, fileContents: {}, setFiles: (files) => set({ files }), setSelectedFile: (name) => set({ selectedFile: { name } }), setEditorContent: (editorContent) => set({ editorContent }), setDbSchema: (dbSchema) => set({ dbSchema }), pushChat: (msg) => set((s) => ({ chat: [...s.chat, msg] })), setConsoleOutput: (consoleOutput) => set({ consoleOutput }), setLoading: (loading) => set({ loading }), setFileProposal: (name, details) => set((s) => ({ fileProposals: { ...s.fileProposals, [name]: { ...details, active: true } } })), setShowDiffForFile: (fileName) => set({ showDiffForFile: fileName }), openFile: async (name) => { const currentState = get(); if (currentState.selectedFile?.name && currentState.editorContent) { set({ fileContents: { ...currentState.fileContents, [currentState.selectedFile.name]: currentState.editorContent } }); } set({ loading: true }); let content = ""; const state = get(); const proposal = state.fileProposals[name]; if (proposal?.isNew) { content = ""; } else { content = state.fileContents[name]; if (!content) { try { const res = await fetch(`${BASE_URL}/files/${encodeURIComponent(name)}`); if (!res.ok) throw new Error("Failed to load file"); const data = await res.json(); content = data.content; set({ fileContents: { ...get().fileContents, [name]: content } }); } catch (e) { console.warn(e); content = `-- demo content for ${name}\nSELECT 1;`; set({ fileContents: { ...get().fileContents, [name]: content } }); } } } set({ selectedFile: { name }, editorContent: content, loading: false }); const finalState = get(); const prop = finalState.fileProposals[name]; if (prop?.active) { set({ showDiffForFile: name }); } else { set({ showDiffForFile: null }); } }, acceptProposal: () => { const state = get(); const file = state.selectedFile?.name; if (state.showDiffForFile !== file) return; const prop = state.fileProposals[file]; if (!prop) return; if (prop.isNew) { const confirmedName = prompt("Save new file as (.sql required):", prop.suggestedName); if (!confirmedName?.trim() || !confirmedName.endsWith(".sql")) { return; } const finalName = confirmedName.trim(); fetch(`${BASE_URL}/files`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ filename: finalName, content: prop.code }), }) .then((res) => { if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`); return res.json(); }) .then(() => { const newProps = { ...state.fileProposals }; delete newProps[file]; const currentFiles = get().files; set({ files: [...currentFiles, { name: finalName }], editorContent: prop.code, fileContents: { ...get().fileContents, [finalName]: prop.code }, showDiffForFile: null, fileProposals: newProps, }); if (finalName !== file) { set({ selectedFile: { name: finalName } }); } }) .catch((e) => console.error("Failed to create file:", e)); } else { fetch(`${BASE_URL}/files/${encodeURIComponent(file)}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ content: prop.code }), }) .then((res) => { if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`); const newProps = { ...state.fileProposals }; delete newProps[file]; set({ editorContent: prop.code, fileContents: { ...state.fileContents, [file]: prop.code }, showDiffForFile: null, fileProposals: newProps, }); }) .catch((e) => console.error("Failed to save after accept:", e)); } }, rejectProposal: () => { const state = get(); const file = state.selectedFile?.name; if (state.showDiffForFile !== file) return; const newProps = { ...state.fileProposals }; delete newProps[file]; set({ showDiffForFile: null, fileProposals: newProps }); }, refreshFiles: async () => { try { const res = await fetch(`${BASE_URL}/files`); if (!res.ok) throw new Error("Failed to load files"); let data = await res.json(); data = data.filter((f) => f.name.endsWith(".sql")); set({ files: data || [] }); } catch (e) { console.warn(e); } }, })); const Card = ({ children, className = "" }) => (
{children}
); const CardContent = ({ children, className = "" }) => (
{children}
); const IconButton = ({ children, onClick, className = "" }) => ( ); function FileExplorer() { const { files, selectedFile, loading, fileProposals, fileContents, editorContent } = useStore(); const openFile = useStore((s) => s.openFile); const loadFiles = async () => { useStore.setState({ loading: true }); try { const res = await fetch(`${BASE_URL}/files`); if (!res.ok) throw new Error("Failed to load files"); let data = await res.json(); data = data.filter((f) => f.name.endsWith(".sql")); useStore.setState({ files: data || [] }); } catch (e) { console.warn(e); } finally { useStore.setState({ loading: false }); } }; useEffect(() => { loadFiles(); }, []); useEffect(() => { if (files.length > 0 && !selectedFile?.name) { openFile(files[0].name); } }, [files, selectedFile?.name, openFile]); async function createNewFile() { const name = prompt("New file name (must end with .sql):"); if (!name || !name.endsWith(".sql")) return; try { await fetch(`${BASE_URL}/files`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ filename: name, content: "" }), }); loadFiles(); } catch (e) { console.warn(e); } } const proposedNews = Object.entries(fileProposals).filter(([n, p]) => p.isNew); return (

Files

New
App Status: {loading ? "loading" : "ready"}
); } function DBViewer() { const { dbSchema, setDbSchema, setConsoleOutput, setFileProposal, setShowDiffForFile} = useStore(); useEffect(() => { async function loadSchema() { try { const tres = await fetch(`${BASE_URL}/tables`); if (!tres.ok) throw new Error("no tables"); const tables = await tres.json(); const tablesWithCols = await Promise.all( tables.map(async (t) => { const qres = await fetch(`${BASE_URL}/query`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query: `PRAGMA table_info(${t})` }), }); const colsData = await qres.json(); console.log(colsData) const columns = colsData.map((c) => `${c.name} ${c.type}`); return { name: t, columns }; }) ); setDbSchema({ tables: tables.map((t) => ({ name: t, columns: [] })) }); } catch (e) { } } loadSchema(); }, [setDbSchema]); async function inspectTable(table) { try { const res = await fetch(`${BASE_URL}/query`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query: `SELECT * FROM ${table} LIMIT 50;` }), }); const data = await res.json(); setConsoleOutput(JSON.stringify(data, null, 2)); } catch (e) { setConsoleOutput(`Failed to query table ${table}: ${e.message}`); } } return (

DB Viewer

SQLite
{dbSchema.tables.map((t) => (
{t.name}
{t.columns.length} columns
inspectTable(t.name)}>Preview
))}
); } function WSListener() { const { setFileProposal, setShowDiffForFile, selectedFile, pushChat } = useStore(); const [isWaiting, setIsWaiting] = useState(true); useEffect(() => { const socket = io("http://localhost:7860"); socket.on("connect", () => { console.log("Connected to WebSocket server"); setIsWaiting(false); }); socket.on("event", (data) => { console.log("WS message:", data); if (data.kind === "code_change") { setFileProposal(data.filename || selectedFile?.name, { code: data.proposedCode, isNew: data.isNew, suggestedName: data.newFileName, }); setShowDiffForFile(data.filename || selectedFile?.name); } }); socket.on("log", (data) => { console.log("WS message:", data); pushChat({ role: "agent", text: data.msg.replace(/\u001b\[[0-9;]*m/g, '') }); }); socket.on("disconnect", () => { console.log("Disconnected from WebSocket server"); setIsWaiting(true); }); return () => { socket.disconnect(); }; }, []); return isWaiting ?
Waiting for agent response...
: null; } function AgentChat() { const { chat, pushChat, setFileProposal, setShowDiffForFile, selectedFile, openFile } = useStore(); const [input, setInput] = useState(""); const [sending, setSending] = useState(false); async function sendMessage() { if (!input.trim() || sending) return; pushChat({ role: "user", text: input }); setInput(""); setSending(true); try { const res = await fetch(`${BASE_URL}/run_crew`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ instructions: input, file: selectedFile?.name }), }); const data = await res.json(); pushChat({ role: "agent", text: data.status || "Crew is Working..." }); } catch (e) { pushChat({ role: "agent", text: `Error: ${e.message}` }); } finally { setSending(false); } } return (

Agent

{chat.map((m, i) => (
{m.text} {m.proposedCode && (
{m.proposedCode}
)}
))}
setInput(e.target.value)} placeholder="Instruct SQL agent" onKeyDown={(e) => { if (e.key === "Enter") sendMessage(); }} /> {sending ? "..." : "Send"}
); } function OutputConsole() { const { consoleOutput } = useStore(); return (
{consoleOutput || "Console output will appear here."}
); } function SQLMonacoEditor() { const { editorContent, setEditorContent, selectedFile, showDiffForFile, fileProposals, fileContents, acceptProposal, rejectProposal, } = useStore(); const proposal = fileProposals[selectedFile?.name]; const showDiff = !!proposal?.active && showDiffForFile === selectedFile?.name; const proposedContent = proposal?.code || ""; const isDirty = selectedFile?.name && (!fileContents[selectedFile.name] || editorContent !== fileContents[selectedFile.name]); const saveFile = async () => { let fileName = selectedFile?.name; if (!fileName) { const name = prompt("Save as (must end with .sql):"); if (!name || !name.endsWith(".sql")) { alert("Invalid filename"); return; } fileName = name; try { const res = await fetch(`${BASE_URL}/files`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ filename: name, content: editorContent }), }); if (!res.ok) throw new Error("Create failed"); const currentFiles = useStore.getState().files; useStore.setState({ files: [...currentFiles, { name }] }); useStore.setState({ selectedFile: { name }, editorContent, fileContents: { ...useStore.getState().fileContents, [name]: editorContent } }); alert("File created and saved"); return; } catch (e) { alert("Failed to create file: " + e.message); return; } } try { await fetch(`${BASE_URL}/files/${encodeURIComponent(fileName)}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ content: editorContent }), }); useStore.setState({ fileContents: { ...useStore.getState().fileContents, [fileName]: editorContent } }); alert("Saved"); } catch (e) { alert("Save failed: " + e.message); } }; const runQuery = async () => { let fileName = selectedFile?.name; if (!fileName) { const name = prompt("Save query as (must end with .sql):"); if (!name || !name.endsWith(".sql")) { alert("Invalid filename"); return; } fileName = name; try { const res = await fetch(`${BASE_URL}/files`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ filename: name, content: editorContent }), }); if (!res.ok) throw new Error("Create failed"); const currentFiles = useStore.getState().files; useStore.setState({ files: [...currentFiles, { name }], selectedFile: { name }, editorContent, fileContents: { ...useStore.getState().fileContents, [name]: editorContent } }); } catch (e) { alert("Failed to create file: " + e.message); return; } } try { const res = await fetch(`${BASE_URL}/query`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query: editorContent }), }); if (!res.ok) { let err; try { err = await res.json(); } catch { err = "Query failed"; } throw new Error(err.error || JSON.stringify(err) || err); } const data = await res.json(); useStore.setState({ consoleOutput: JSON.stringify(data, null, 2) }); } catch (e) { useStore.setState({ consoleOutput: "Run failed: " + e.message }); } }; const commonOptions = { minimap: { enabled: false }, fontSize: 13, wordWrap: "on" }; const editorKey = `${selectedFile?.name}-${showDiff ? "diff" : "normal"}`; return (
{selectedFile?.name || "No file selected"}{isDirty ? " *" : ""}
SQL Editor {showDiff ? "(Diff View)" : ""}
{showDiff ? ( <> Accept Reject ) : ( <> Save Run )}
{showDiff ? ( ) : ( setEditorContent(v || "")} options={commonOptions} /> )}
); } export default function App() { return (
); }