Skip to content

Commit eba7a21

Browse files
feat: add embed route
2 parents e19169d + 39efc93 commit eba7a21

File tree

10 files changed

+144
-121
lines changed

10 files changed

+144
-121
lines changed

chat/next.config.mjs

Lines changed: 0 additions & 23 deletions
This file was deleted.

chat/next.config.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,23 @@
11
import type { NextConfig } from "next";
2+
const basePath = process.env.BASE_PATH ?? "/chat";
23

34
const nextConfig: NextConfig = {
4-
/* config options here */
5+
// Enable static exports
6+
output: "export",
7+
8+
// Disable image optimization since it's not supported in static exports
9+
images: {
10+
unoptimized: true,
11+
},
12+
13+
// Configure base path for GitHub Pages (repo/chat)
14+
basePath,
15+
16+
// Configure asset prefix for GitHub Pages - helps with static asset loading
17+
assetPrefix: `${basePath}/`,
18+
19+
// Configure trailing slashes (recommended for static exports)
20+
trailingSlash: true,
521
};
622

723
export default nextConfig;

chat/src/app/embed/page.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Chat } from "@/components/chat";
2+
import { ChatProvider } from "@/components/chat-provider";
3+
import { Suspense } from "react";
4+
5+
export default function EmbedPage() {
6+
return (
7+
<Suspense
8+
fallback={
9+
<div className="text-center p-4 text-sm">Loading chat interface...</div>
10+
}
11+
>
12+
<ChatProvider>
13+
<div className="flex flex-col h-svh">
14+
<Chat />
15+
</div>
16+
</ChatProvider>
17+
</Suspense>
18+
);
19+
}

chat/src/app/globals.css

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@
7979
}
8080

8181
.dark {
82-
--background: oklch(0.129 0.042 264.695);
82+
--background: oklch(0.14 0 0);
8383
--foreground: oklch(0.984 0.003 247.858);
8484
--card: oklch(0.208 0.042 265.755);
8585
--card-foreground: oklch(0.984 0.003 247.858);
@@ -90,11 +90,11 @@
9090
--secondary: oklch(0.279 0.041 260.031);
9191
--secondary-foreground: oklch(0.984 0.003 247.858);
9292
--muted: oklch(0.279 0.041 260.031);
93-
--muted-foreground: oklch(0.704 0.04 256.788);
93+
--muted-foreground: oklch(0.7118 0.0129 286.07);
9494
--accent: oklch(0.279 0.041 260.031);
9595
--accent-foreground: oklch(0.984 0.003 247.858);
9696
--destructive: oklch(0.704 0.191 22.216);
97-
--border: oklch(1 0 0 / 10%);
97+
--border: oklch(0.27 0.01 0);
9898
--input: oklch(1 0 0 / 15%);
9999
--ring: oklch(0.551 0.027 264.364);
100100
--chart-1: oklch(0.488 0.243 264.376);

chat/src/app/header.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
"use client";
2+
3+
import { useChat } from "@/components/chat-provider";
4+
import { ModeToggle } from "../components/mode-toggle";
5+
6+
export function Header() {
7+
const { serverStatus } = useChat();
8+
9+
return (
10+
<header className="p-4 flex items-center justify-between border-b">
11+
<span className="font-bold">AgentAPI Chat</span>
12+
13+
<div className="flex items-center gap-4">
14+
{serverStatus !== "unknown" && (
15+
<div className="flex items-center gap-2 text-sm font-medium">
16+
<span
17+
className={`text-secondary w-2 h-2 rounded-full ${
18+
["offline", "unknown"].includes(serverStatus)
19+
? "bg-red-500 ring-2 ring-red-500/35"
20+
: "bg-green-500 ring-2 ring-green-500/35"
21+
}`}
22+
/>
23+
<span className="sr-only">Status:</span>
24+
<span className="first-letter:uppercase">{serverStatus}</span>
25+
</div>
26+
)}
27+
<ModeToggle />
28+
</div>
29+
</header>
30+
);
31+
}

chat/src/app/page.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
1+
import { Chat } from "@/components/chat";
2+
import { ChatProvider } from "@/components/chat-provider";
3+
import { Header } from "./header";
14
import { Suspense } from "react";
2-
import ChatInterface from "@/components/ChatInterface";
35

46
export default function Home() {
57
return (
68
<Suspense
79
fallback={
8-
<div className="text-center p-4">Loading chat interface...</div>
10+
<div className="text-center p-4 text-sm">Loading chat interface...</div>
911
}
1012
>
11-
<ChatInterface />
13+
<ChatProvider>
14+
<div className="flex flex-col h-svh">
15+
<Header />
16+
<Chat />
17+
</div>
18+
</ChatProvider>
1219
</Suspense>
1320
);
1421
}

chat/src/components/ChatInterface.tsx renamed to chat/src/components/chat-provider.tsx

Lines changed: 45 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
"use client";
22

3-
import { useState, useEffect, useRef, useCallback } from "react";
4-
import MessageList from "./MessageList";
5-
import MessageInput from "./MessageInput";
63
import { useSearchParams } from "next/navigation";
4+
import {
5+
useState,
6+
useEffect,
7+
useRef,
8+
createContext,
9+
PropsWithChildren,
10+
useContext,
11+
} from "react";
712
import { toast } from "sonner";
8-
import { Button } from "./ui/button";
9-
import { TriangleAlertIcon } from "lucide-react";
10-
import { Alert, AlertTitle, AlertDescription } from "./ui/alert";
11-
import { ModeToggle } from "./mode-toggle";
1213

1314
interface Message {
1415
id: number;
@@ -33,42 +34,30 @@ interface StatusChangeEvent {
3334
status: string;
3435
}
3536

36-
const isDraftMessage = (message: Message | DraftMessage): boolean => {
37+
function isDraftMessage(message: Message | DraftMessage): boolean {
3738
return message.id === undefined;
38-
};
39+
}
3940

40-
export default function ChatInterface() {
41-
const [messages, setMessages] = useState<(Message | DraftMessage)[]>([]);
42-
const [loading, setLoading] = useState<boolean>(false);
43-
const [serverStatus, setServerStatus] = useState<string>("unknown");
44-
const searchParams = useSearchParams();
41+
type MessageType = "user" | "raw";
4542

46-
const getAgentApiUrl = useCallback(() => {
47-
const apiUrlFromParam = searchParams.get("url");
48-
if (apiUrlFromParam) {
49-
try {
50-
// Validate if it's a proper URL
51-
new URL(apiUrlFromParam);
52-
return apiUrlFromParam;
53-
} catch (e) {
54-
console.warn("Invalid url parameter, defaulting...", e);
55-
// Fallback if parsing fails or it's not a valid URL.
56-
// Ensure window is defined (for SSR/Node.js environments during build)
57-
return typeof window !== "undefined" ? window.location.origin : "";
58-
}
59-
}
60-
// Ensure window is defined
61-
return typeof window !== "undefined" ? window.location.origin : "";
62-
}, [searchParams]);
43+
type ServerStatus = "online" | "offline" | "unknown";
6344

64-
const [agentAPIUrl, setAgentAPIUrl] = useState<string>(getAgentApiUrl());
45+
interface ChatContextValue {
46+
messages: (Message | DraftMessage)[];
47+
loading: boolean;
48+
serverStatus: ServerStatus;
49+
sendMessage: (message: string, type?: MessageType) => void;
50+
}
6551

66-
const eventSourceRef = useRef<EventSource | null>(null);
52+
const ChatContext = createContext<ChatContextValue | undefined>(undefined);
6753

68-
// Update agentAPIUrl when searchParams change (e.g. url is added/removed)
69-
useEffect(() => {
70-
setAgentAPIUrl(getAgentApiUrl());
71-
}, [getAgentApiUrl, searchParams]);
54+
export function ChatProvider({ children }: PropsWithChildren) {
55+
const [messages, setMessages] = useState<(Message | DraftMessage)[]>([]);
56+
const [loading, setLoading] = useState<boolean>(false);
57+
const [serverStatus, setServerStatus] = useState<ServerStatus>("unknown");
58+
const eventSourceRef = useRef<EventSource | null>(null);
59+
const searchParams = useSearchParams();
60+
const agentAPIUrl = searchParams.get("url");
7261

7362
// Set up SSE connection to the events endpoint
7463
useEffect(() => {
@@ -132,7 +121,7 @@ export default function ChatInterface() {
132121
// Handle status changes
133122
eventSource.addEventListener("status_change", (event) => {
134123
const data: StatusChangeEvent = JSON.parse(event.data);
135-
setServerStatus(data.status);
124+
setServerStatus(data.status as ServerStatus);
136125
});
137126

138127
// Handle connection open (server is online)
@@ -240,55 +229,23 @@ export default function ChatInterface() {
240229
};
241230

242231
return (
243-
<div className="flex flex-col h-svh">
244-
<header className="p-4 flex items-center justify-between border-b">
245-
<span className="font-bold">AgentAPI Chat</span>
246-
247-
<div className="flex items-center gap-4">
248-
{serverStatus !== "unknown" && (
249-
<div className="flex items-center gap-2 text-sm font-medium">
250-
<span
251-
className={`text-secondary w-2 h-2 rounded-full ${
252-
["offline", "unknown"].includes(serverStatus)
253-
? "bg-red-500 ring-2 ring-red-500/35"
254-
: "bg-green-500 ring-2 ring-green-500/35"
255-
}`}
256-
/>
257-
<span className="sr-only">Status:</span>
258-
<span className="first-letter:uppercase">{serverStatus}</span>
259-
</div>
260-
)}
261-
<ModeToggle />
262-
</div>
263-
</header>
264-
265-
<main className="flex flex-1 flex-col w-full overflow-auto">
266-
{serverStatus === "offline" && (
267-
<div className="p-4 w-full max-w-4xl mx-auto">
268-
<Alert className="flex border-yellow-500">
269-
<TriangleAlertIcon className="h-4 w-4 stroke-yellow-600" />
270-
<div>
271-
<AlertTitle>API server is offline</AlertTitle>
272-
<AlertDescription>
273-
Please start the AgentAPI server. Attempting to connect to:{" "}
274-
{agentAPIUrl || "N/A"}.
275-
</AlertDescription>
276-
</div>
277-
<Button
278-
variant="ghost"
279-
size="sm"
280-
className="ml-auto"
281-
onClick={() => window.location.reload()}
282-
>
283-
Retry
284-
</Button>
285-
</Alert>
286-
</div>
287-
)}
288-
289-
<MessageList messages={messages} />
290-
<MessageInput onSendMessage={sendMessage} disabled={loading} />
291-
</main>
292-
</div>
232+
<ChatContext.Provider
233+
value={{
234+
messages,
235+
loading,
236+
sendMessage,
237+
serverStatus,
238+
}}
239+
>
240+
{children}
241+
</ChatContext.Provider>
293242
);
294243
}
244+
245+
export function useChat() {
246+
const context = useContext(ChatContext);
247+
if (!context) {
248+
throw new Error("useChat must be used within a ChatProvider");
249+
}
250+
return context;
251+
}

chat/src/components/chat.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
"use client";
2+
3+
import { useChat } from "./chat-provider";
4+
import MessageInput from "./message-input";
5+
import MessageList from "./message-list";
6+
7+
export function Chat() {
8+
const { messages, loading, sendMessage } = useChat();
9+
10+
return (
11+
<>
12+
<MessageList messages={messages} />
13+
<MessageInput onSendMessage={sendMessage} disabled={loading} />
14+
</>
15+
);
16+
}

chat/src/components/MessageInput.tsx renamed to chat/src/components/message-input.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ export default function MessageInput({
155155
onKeyDown={handleKeyDown as any}
156156
onFocus={() => setControlAreaFocused(true)}
157157
onBlur={() => setControlAreaFocused(false)}
158-
className="cursor-text p-4 h-20 text-muted-foreground flex items-center justify-center w-full outline-none"
158+
className="cursor-text p-4 h-20 text-muted-foreground flex items-center justify-center w-full outline-none text-sm"
159159
>
160160
{controlAreaFocused
161161
? "Press any key to send to terminal (arrows, Ctrl+C, Ctrl+R, etc.)"
@@ -175,7 +175,7 @@ export default function MessageInput({
175175
</div>
176176

177177
<div className="flex items-center justify-between p-4">
178-
<TabsList>
178+
<TabsList className="bg-transparent">
179179
<TabsTrigger
180180
value="text"
181181
onClick={() => {

chat/src/components/MessageList.tsx renamed to chat/src/components/message-list.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ export default function MessageList({ messages }: MessageListProps) {
120120
} ${message.id === undefined ? "animate-pulse" : ""}`}
121121
>
122122
<div
123-
className={`whitespace-pre-wrap break-words text-left text-sm ${
123+
className={`whitespace-pre-wrap break-words text-left text-xs md:text-sm leading-relaxed md:leading-normal ${
124124
message.role === "user" ? "" : "font-mono"
125125
}`}
126126
>

0 commit comments

Comments
 (0)