1
1
"use client" ;
2
2
3
- import { useState , useEffect , useRef , useCallback } from "react" ;
4
- import MessageList from "./MessageList" ;
5
- import MessageInput from "./MessageInput" ;
6
3
import { useSearchParams } from "next/navigation" ;
4
+ import {
5
+ useState ,
6
+ useEffect ,
7
+ useRef ,
8
+ createContext ,
9
+ PropsWithChildren ,
10
+ useContext ,
11
+ } from "react" ;
7
12
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" ;
12
13
13
14
interface Message {
14
15
id : number ;
@@ -33,42 +34,30 @@ interface StatusChangeEvent {
33
34
status : string ;
34
35
}
35
36
36
- const isDraftMessage = ( message : Message | DraftMessage ) : boolean => {
37
+ function isDraftMessage ( message : Message | DraftMessage ) : boolean {
37
38
return message . id === undefined ;
38
- } ;
39
+ }
39
40
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" ;
45
42
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" ;
63
44
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
+ }
65
51
66
- const eventSourceRef = useRef < EventSource | null > ( null ) ;
52
+ const ChatContext = createContext < ChatContextValue | undefined > ( undefined ) ;
67
53
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" ) ;
72
61
73
62
// Set up SSE connection to the events endpoint
74
63
useEffect ( ( ) => {
@@ -132,7 +121,7 @@ export default function ChatInterface() {
132
121
// Handle status changes
133
122
eventSource . addEventListener ( "status_change" , ( event ) => {
134
123
const data : StatusChangeEvent = JSON . parse ( event . data ) ;
135
- setServerStatus ( data . status ) ;
124
+ setServerStatus ( data . status as ServerStatus ) ;
136
125
} ) ;
137
126
138
127
// Handle connection open (server is online)
@@ -240,55 +229,23 @@ export default function ChatInterface() {
240
229
} ;
241
230
242
231
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 >
293
242
) ;
294
243
}
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
+ }
0 commit comments