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 (
{!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) => (
))}
);
};
export const StandaloneChatPage = () => (
);