11"use client" ;
22
3- import { useState , useEffect , useRef , useCallback } from "react" ;
4- import MessageList from "./MessageList" ;
5- import MessageInput from "./MessageInput" ;
63import { useSearchParams } from "next/navigation" ;
4+ import {
5+ useState ,
6+ useEffect ,
7+ useRef ,
8+ createContext ,
9+ PropsWithChildren ,
10+ useContext ,
11+ } from "react" ;
712import { 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
1314interface 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+ }
0 commit comments