import { env } from "@code/env/web"; import { FlueProvider, useFlueAgent } from "@flue/react"; import { createFlueClient } from "@flue/sdk"; import { ArrowUp, Loader2, MessageSquare } from "lucide-react"; import { useEffect, useMemo, useRef, useState } from "react"; const STANDALONE_CHAT_ID = "zopu-standalone"; const StandaloneFlueProvider = ({ children, }: { children: React.ReactNode; }) => { const client = useMemo( () => createFlueClient({ baseUrl: env.VITE_ZOPU_SERVER_URL, }), [] ); return {children}; }; const extractText = (parts: { type: string; text?: string }[]): string => parts .filter((p) => p.type === "text" && p.text) .map((p) => p.text ?? "") .join(""); const ChatMessage = ({ parts, role, }: { parts: { type: string; text?: string }[]; role: string; }) => { const isUser = role === "user"; return (
{!isUser && (
)}
{extractText(parts) || ...}
); }; const ChatContent = () => { const agent = useFlueAgent({ id: STANDALONE_CHAT_ID, name: "zopu", }); const [input, setInput] = useState(""); const scrollRef = useRef(null); const hasMessages = agent.messages.length > 0; const isBusy = agent.status === "submitted" || agent.status === "streaming"; useEffect(() => { const el = scrollRef.current; if (el) { el.scrollTop = el.scrollHeight; } }, [agent.messages, isBusy]); const submit = async () => { const message = input.trim(); if (!message || isBusy) { return; } setInput(""); await agent.sendMessage(message); }; return (
Z

Zopu

{isBusy ? "working..." : "ready"}

{!hasMessages && !agent.historyReady && (
)} {!hasMessages && agent.historyReady && (

Chat with Zopu

Ask about issues, trigger work, or check PR status.

{[ "List recent pull requests", "Create an issue for adding a settings page", ].map((suggestion) => ( ))}
)} {agent.messages.map((message) => ( ))}