Skip to content

Commit f3f204f

Browse files
committed
Add embed page
1 parent e19169d commit f3f204f

File tree

9 files changed

+155
-121
lines changed

9 files changed

+155
-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: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { Chat } from "@/components/chat";
2+
import { ChatProvider } from "@/components/chat-provider";
3+
4+
export default function EmbedPage() {
5+
return (
6+
<ChatProvider>
7+
<Chat />
8+
</ChatProvider>
9+
);
10+
}

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: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,41 @@
1-
import { Suspense } from "react";
2-
import ChatInterface from "@/components/ChatInterface";
1+
import { Chat } from "@/components/chat";
2+
import { ChatProvider, useChat } from "@/components/chat-provider";
3+
import { ModeToggle } from "@/components/mode-toggle";
34

45
export default function Home() {
56
return (
6-
<Suspense
7-
fallback={
8-
<div className="text-center p-4">Loading chat interface...</div>
9-
}
10-
>
11-
<ChatInterface />
12-
</Suspense>
7+
<ChatProvider>
8+
<div className="flex flex-col h-svh">
9+
<Header />
10+
<Chat />
11+
</div>
12+
</ChatProvider>
13+
);
14+
}
15+
16+
function Header() {
17+
const { serverStatus } = useChat();
18+
19+
return (
20+
<header className="p-4 flex items-center justify-between border-b">
21+
<span className="font-bold">AgentAPI Chat</span>
22+
23+
<div className="flex items-center gap-4">
24+
{serverStatus !== "unknown" && (
25+
<div className="flex items-center gap-2 text-sm font-medium">
26+
<span
27+
className={`text-secondary w-2 h-2 rounded-full ${
28+
["offline", "unknown"].includes(serverStatus)
29+
? "bg-red-500 ring-2 ring-red-500/35"
30+
: "bg-green-500 ring-2 ring-green-500/35"
31+
}`}
32+
/>
33+
<span className="sr-only">Status:</span>
34+
<span className="first-letter:uppercase">{serverStatus}</span>
35+
</div>
36+
)}
37+
<ModeToggle />
38+
</div>
39+
</header>
1340
);
1441
}

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+
}

0 commit comments

Comments
 (0)