Skip to content

Commit 661a1d9

Browse files
Alex HolmbergAlex Holmberg
authored andcommitted
feat(23-01): create agent chat route with CopilotKit
- New /agent route for AG-UI agent conversations - Uses useCopilotChat hook for message handling - Real-time streaming display with loading states - Shows connection info to AG-UI server endpoint
1 parent 372320b commit 661a1d9

1 file changed

Lines changed: 164 additions & 0 deletions

File tree

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
/**
2+
* Agent Chat Route
3+
*
4+
* Demonstrates CopilotKit integration with syncable-cli AG-UI server.
5+
* Uses CopilotKit's built-in chat components for agent conversations.
6+
*/
7+
import { createFileRoute } from "@tanstack/react-router";
8+
import { useState, useCallback, FormEvent } from "react";
9+
import { useCopilotChat } from "@copilotkit/react-core";
10+
import { MessageCircle, Send, Loader2, Bot, User, Terminal } from "lucide-react";
11+
12+
export const Route = createFileRoute("/agent")({
13+
component: AgentChat,
14+
});
15+
16+
function AgentChat() {
17+
const [input, setInput] = useState("");
18+
19+
const {
20+
visibleMessages,
21+
appendMessage,
22+
isLoading,
23+
} = useCopilotChat();
24+
25+
const handleSubmit = useCallback(
26+
async (e: FormEvent) => {
27+
e.preventDefault();
28+
if (!input.trim() || isLoading) return;
29+
30+
const message = input.trim();
31+
setInput("");
32+
33+
// Append user message and trigger agent response
34+
await appendMessage({
35+
id: crypto.randomUUID(),
36+
role: "user",
37+
content: message,
38+
});
39+
},
40+
[input, isLoading, appendMessage]
41+
);
42+
43+
return (
44+
<main className="min-h-screen bg-slate-950 relative overflow-hidden">
45+
{/* Background */}
46+
<div className="absolute inset-0 bg-gradient-to-br from-slate-950 via-slate-900 to-slate-950" />
47+
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top,rgba(34,211,238,0.1),transparent_50%)]" />
48+
49+
<div className="relative z-10 max-w-3xl mx-auto px-4 sm:px-6 py-12">
50+
{/* Header */}
51+
<header className="flex flex-col items-center text-center mb-8">
52+
<div className="flex items-center gap-3 mb-4">
53+
<div className="p-3 rounded-2xl bg-gradient-to-br from-emerald-500/20 to-cyan-600/20 border border-emerald-500/30 shadow-[0_0_30px_rgba(16,185,129,0.15)]">
54+
<Bot className="w-8 h-8 text-emerald-400" />
55+
</div>
56+
<h1 className="text-4xl font-bold tracking-tight bg-gradient-to-r from-emerald-400 via-cyan-400 to-blue-400 bg-clip-text text-transparent">
57+
Agent Chat
58+
</h1>
59+
</div>
60+
<p className="text-slate-400 max-w-md text-base leading-relaxed">
61+
Chat with the Syncable agent via AG-UI protocol.
62+
Messages are processed by the AG-UI server and streamed back in real-time.
63+
</p>
64+
</header>
65+
66+
{/* Chat Container */}
67+
<div className="bg-slate-900/50 border border-slate-800 rounded-2xl overflow-hidden">
68+
{/* Messages Area */}
69+
<div className="h-[500px] overflow-y-auto p-4 space-y-4">
70+
{visibleMessages.length === 0 ? (
71+
<div className="flex flex-col items-center justify-center h-full text-slate-500">
72+
<MessageCircle className="w-12 h-12 mb-4 opacity-50" />
73+
<p className="text-sm">No messages yet. Start a conversation!</p>
74+
</div>
75+
) : (
76+
visibleMessages.map((message) => (
77+
<div
78+
key={message.id}
79+
className={`flex gap-3 ${
80+
message.role === "user" ? "justify-end" : "justify-start"
81+
}`}
82+
>
83+
{message.role !== "user" && (
84+
<div className="flex-shrink-0 w-8 h-8 rounded-lg bg-emerald-500/20 flex items-center justify-center">
85+
<Bot className="w-4 h-4 text-emerald-400" />
86+
</div>
87+
)}
88+
<div
89+
className={`max-w-[80%] rounded-2xl px-4 py-3 ${
90+
message.role === "user"
91+
? "bg-cyan-600/20 border border-cyan-500/30 text-cyan-100"
92+
: "bg-slate-800/50 border border-slate-700/50 text-slate-200"
93+
}`}
94+
>
95+
<p className="text-sm whitespace-pre-wrap">{message.content}</p>
96+
</div>
97+
{message.role === "user" && (
98+
<div className="flex-shrink-0 w-8 h-8 rounded-lg bg-cyan-500/20 flex items-center justify-center">
99+
<User className="w-4 h-4 text-cyan-400" />
100+
</div>
101+
)}
102+
</div>
103+
))
104+
)}
105+
106+
{/* Loading indicator */}
107+
{isLoading && (
108+
<div className="flex gap-3 justify-start">
109+
<div className="flex-shrink-0 w-8 h-8 rounded-lg bg-emerald-500/20 flex items-center justify-center">
110+
<Bot className="w-4 h-4 text-emerald-400" />
111+
</div>
112+
<div className="bg-slate-800/50 border border-slate-700/50 rounded-2xl px-4 py-3">
113+
<div className="flex items-center gap-2 text-slate-400">
114+
<Loader2 className="w-4 h-4 animate-spin" />
115+
<span className="text-sm">Agent is thinking...</span>
116+
</div>
117+
</div>
118+
</div>
119+
)}
120+
</div>
121+
122+
{/* Input Area */}
123+
<form onSubmit={handleSubmit} className="border-t border-slate-800 p-4">
124+
<div className="flex gap-3">
125+
<input
126+
type="text"
127+
value={input}
128+
onChange={(e) => setInput(e.target.value)}
129+
placeholder="Type your message..."
130+
className="flex-1 bg-slate-800/50 border border-slate-700 rounded-xl px-4 py-3 text-slate-100 placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-cyan-500/50 focus:border-cyan-500/50 transition-all"
131+
disabled={isLoading}
132+
/>
133+
<button
134+
type="submit"
135+
disabled={!input.trim() || isLoading}
136+
className="px-6 py-3 rounded-xl bg-gradient-to-r from-emerald-500 to-cyan-500 text-white font-medium shadow-lg shadow-emerald-500/25 hover:shadow-emerald-500/40 hover:scale-105 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:scale-100 transition-all duration-200 flex items-center gap-2"
137+
>
138+
{isLoading ? (
139+
<Loader2 className="w-5 h-5 animate-spin" />
140+
) : (
141+
<Send className="w-5 h-5" />
142+
)}
143+
</button>
144+
</div>
145+
</form>
146+
</div>
147+
148+
{/* Connection Info */}
149+
<div className="mt-6 p-4 bg-slate-900/30 border border-slate-800/50 rounded-xl">
150+
<div className="flex items-center gap-2 text-slate-400 text-sm">
151+
<Terminal className="w-4 h-4" />
152+
<span>AG-UI Server: </span>
153+
<code className="px-2 py-0.5 bg-slate-800 rounded text-emerald-400 text-xs">
154+
{import.meta.env.VITE_AGENT_URL || "http://localhost:9090"}
155+
</code>
156+
</div>
157+
<p className="mt-2 text-xs text-slate-500">
158+
Messages are sent via POST /message and responses streamed via SSE/WebSocket.
159+
</p>
160+
</div>
161+
</div>
162+
</main>
163+
);
164+
}

0 commit comments

Comments
 (0)