import { createFileRoute, useNavigate } from "@tanstack/react-router"; import { supabase } from "@/integrations/supabase/client"; import { useServerFn } from "@tanstack/react-start"; import { useCallback, useEffect, useRef, useState } from "react"; import { Bot, Camera, CameraOff, Loader2, MessageSquareText, Mic, MicOff, PhoneOff, Phone, SendHorizontal, Trash2, Video, AudioLines } from "lucide-react"; import { askEcho, buildEchoSite } from "@/lib/gemini.functions"; import { SitePreview } from "@/components/site-preview"; import { useGeminiLive } from "@/hooks/use-gemini-live"; import type { LiveEvent } from "@/hooks/use-live-voice"; import { useAuth, displayName } from "@/hooks/use-auth"; import { SignIn } from "@/components/sign-in"; import { AgentFace } from "@/components/agent-face"; import { Textarea } from "@/components/ui/textarea"; export const Route = createFileRoute("/autonomous")({ head: () => ({ meta: [ { title: "Autonomous Systems — Codesynux Agent Studio" }, { name: "description", content: "Chat, plan and research with the Codesynux agent by typing, dictation, live voice or a face-to-face video call." }, { property: "og:title", content: "Autonomous Systems — Codesynux" }, { property: "og:description", content: "An AI agent studio with typing, microphone, live voice and an animated video-call agent." }, { property: "og:type", content: "website" }, { name: "twitter:card", content: "summary_large_image" }, ], }), validateSearch: (s: Record): { project?: string } => { const p = s["project"]; return typeof p === "string" && /^[0-9a-f-]{36}$/i.test(p) ? { project: p } : {}; }, component: Studio, }); type Msg = { role: "user" | "assistant"; text: string }; type Mode = "chat" | "plan" | "research" | "code"; const MODES: { id: Mode; label: string }[] = [ { id: "chat", label: "Chat" }, { id: "plan", label: "Plan" }, { id: "research", label: "Research" }, { id: "code", label: "Code" }, ]; const STORE = "codesynux-agent-chat"; function Studio() { const { user, ready } = useAuth(); const [view, setView] = useState<"chat" | "voice" | "video">("chat"); const { project } = Route.useSearch(); const navigate = useNavigate(); const [captions, setCaptions] = useState([]); const [voiceSite, setVoiceSite] = useState<{ id: string; html: string; title?: string | undefined } | null>(null); const onEvent = useCallback((e: LiveEvent) => { if (e.type === "app.started") setCaptions([]); if (e.type === "app.project.opened") setVoiceSite((p) => ({ id: p?.id ?? "", html: p?.html ?? "", title: String(e["title"] ?? "") })); if (e.type === "app.build.done") setVoiceSite((p) => ({ id: String(e["id"] ?? ""), html: String(e["html"] ?? ""), title: p?.title })); if (e.type === "session.input_transcript.delta" || e.type === "session.output_transcript.delta") { const role = e.type === "session.input_transcript.delta" ? "user" : "assistant"; const d = typeof e["delta"] === "string" ? e["delta"] : ""; if (!d) return; setCaptions((p) => { const l = p[p.length - 1]; return l && l.role === role ? [...p.slice(0, -1), { role, text: l.text + d }] : [...p, { role, text: d }]; }); } }, []); const voice = useGeminiLive({ onEvent }); if (!ready) return
Loading…
; if (!user) return ; const name = displayName(user); const tabs = [ { id: "chat", label: "Type & dictate", icon: MessageSquareText }, { id: "voice", label: "Nova voice", icon: AudioLines }, { id: "video", label: "Video call", icon: Video }, ] as const; return (

Autonomous Systems

Tap the robot to start a new project — everything is saved to your profile

{tabs.map((t) => ( ))}
{view === "chat" ? : <>{voiceSite?.html &&
{voiceSite.title &&

Open project: {voiceSite.title}

}
}}
); } const CODE_RE = /\b(build|create|make|develop|code|design|generate)\b[^.?!]{0,60}\b(website|web ?site|web ?app|app|landing page|page|portfolio|dashboard|game|store|shop|calculator|todo|form|component|project)\b|\b(html|css|javascript|react|tailwind)\b/i; /** True for prompts that ask to build a website, app or other coding project. */ function isCodingPrompt(t: string) { return CODE_RE.test(t); } function Chat({ project }: { project?: string | undefined }) { const ask = useServerFn(askEcho); const build = useServerFn(buildEchoSite); const [site, setSite] = useState<{ id: string; html: string } | null>(null); const [sessionId, setSessionId] = useState(() => project ?? crypto.randomUUID()); const [msgs, setMsgs] = useState([]); const [mode, setMode] = useState("chat"); const [text, setText] = useState(""); const [busy, setBusy] = useState(false); const [listening, setListening] = useState(false); const [canDictate, setCanDictate] = useState(false); const rec = useRef<{ stop: () => void } | null>(null); const end = useRef(null); useEffect(() => { if (!project) return; void supabase.from("voice_builds").select("id, html, prompt").eq("session_id", project).order("created_at").then(({ data }) => { const last = data?.[data.length - 1]; if (!last) return; setSite({ id: last.id, html: last.html }); setMode("code"); setMsgs(data!.flatMap((b, i) => [{ role: "user" as const, text: b.prompt }, { role: "assistant" as const, text: i === 0 ? "Built your website." : "Updated the website." }])); }); }, [project]); useEffect(() => { if (project) return; try { const s = JSON.parse(localStorage.getItem(STORE) ?? "[]"); if (Array.isArray(s)) setMsgs(s.slice(-50)); } catch { /* ignore */ } const w = window as unknown as Record; setCanDictate(!!(w["SpeechRecognition"] || w["webkitSpeechRecognition"])); }, []); useEffect(() => { if (!project) localStorage.setItem(STORE, JSON.stringify(msgs.slice(-50))); end.current?.scrollIntoView({ behavior: "smooth", block: "end" }); }, [msgs]); const send = async () => { const t = text.trim(); if (!t || busy) return; const next = [...msgs, { role: "user" as const, text: t }]; setMsgs(next); setText(""); setBusy(true); try { const auto = mode !== "code" && isCodingPrompt(t); if (auto) setMode("code"); if (mode === "code" || auto) { const r = await build({ data: { sessionId, request: t, previous: site?.html } }); if ("error" in r) setMsgs([...next, { role: "assistant", text: r.error ?? "Build failed." }]); else { setSite({ id: r.id, html: r.html }); setMsgs([...next, { role: "assistant", text: site ? "Updated the preview with your changes." : "Built your website — see the live preview." }]); } setBusy(false); return; } const r = await ask({ data: { messages: next.slice(-20), mode } }); setMsgs([...next, { role: "assistant", text: r.answer ?? r.error ?? "No answer." }]); } catch { setMsgs([...next, { role: "assistant", text: "The agent couldn't answer right now. Try again." }]); } setBusy(false); }; const dictate = () => { if (listening) { rec.current?.stop(); return; } const w = window as unknown as Record any>; // eslint-disable-line @typescript-eslint/no-explicit-any const R = w["SpeechRecognition"] || w["webkitSpeechRecognition"]; if (!R) return; const r = new R(); r.interimResults = false; r.lang = navigator.language; r.onresult = (e: { results: ArrayLike> }) => { const said = Array.from(e.results).map((x) => x[0]?.transcript ?? "").join(" "); setText((p) => (p ? p + " " : "") + said); }; r.onend = () => setListening(false); r.onerror = () => setListening(false); rec.current = r; setListening(true); r.start(); }; const fix = useCallback((errors: string[]) => { setText(`Fix these errors: ${errors.join(" | ")}`); }, []); return (
{MODES.map((m) => ( ))} {msgs.length > 0 && }
{msgs.length === 0 && (
Pick a mode and ask anything — “Plan a food delivery app”, “Research the best database for chat apps”.
)} {msgs.map((m, i) => (

{m.text}

))} {busy &&

Thinking…

}
{ e.preventDefault(); void send(); }} className="flex items-end gap-2 border-t border-border p-3">