1
- ' use client' ;
1
+ " use client" ;
2
2
3
- import { useState , useEffect , useRef } from ' react' ;
4
- import MessageList from ' ./MessageList' ;
5
- import MessageInput from ' ./MessageInput' ;
6
- import { useSearchParams } from ' next/navigation'
3
+ import { useState , useEffect , useRef } from " react" ;
4
+ import MessageList from " ./MessageList" ;
5
+ import MessageInput from " ./MessageInput" ;
6
+ import { useSearchParams } from " next/navigation" ;
7
7
8
8
interface Message {
9
9
role : string ;
@@ -25,10 +25,10 @@ interface StatusChangeEvent {
25
25
export default function ChatInterface ( ) {
26
26
const [ messages , setMessages ] = useState < Message [ ] > ( [ ] ) ;
27
27
const [ loading , setLoading ] = useState < boolean > ( false ) ;
28
- const [ serverStatus , setServerStatus ] = useState < string > ( ' unknown' ) ;
28
+ const [ serverStatus , setServerStatus ] = useState < string > ( " unknown" ) ;
29
29
const searchParams = useSearchParams ( ) ;
30
30
// null port gets converted to NaN
31
- const parsedPort = parseInt ( searchParams . get ( ' port' ) as string ) ;
31
+ const parsedPort = parseInt ( searchParams . get ( " port" ) as string ) ;
32
32
const port = isNaN ( parsedPort ) ? 3284 : parsedPort ;
33
33
const AgentAPIUrl = `http://localhost:${ port } ` ;
34
34
const eventSourceRef = useRef < EventSource | null > ( null ) ;
@@ -40,159 +40,177 @@ export default function ChatInterface() {
40
40
if ( eventSourceRef . current ) {
41
41
eventSourceRef . current . close ( ) ;
42
42
}
43
-
43
+
44
44
const eventSource = new EventSource ( `${ AgentAPIUrl } /events` ) ;
45
45
eventSourceRef . current = eventSource ;
46
-
46
+
47
47
// Handle message updates
48
- eventSource . addEventListener ( ' message_update' , ( event ) => {
48
+ eventSource . addEventListener ( " message_update" , ( event ) => {
49
49
const data : MessageUpdateEvent = JSON . parse ( event . data ) ;
50
-
51
- setMessages ( prevMessages => {
50
+
51
+ setMessages ( ( prevMessages ) => {
52
52
// Check if message with this ID already exists
53
- const existingIndex = prevMessages . findIndex ( m => m . id === data . id ) ;
54
-
53
+ const existingIndex = prevMessages . findIndex ( ( m ) => m . id === data . id ) ;
54
+
55
55
if ( existingIndex !== - 1 ) {
56
56
// Update existing message
57
57
const updatedMessages = [ ...prevMessages ] ;
58
58
updatedMessages [ existingIndex ] = {
59
59
role : data . role ,
60
60
content : data . message ,
61
- id : data . id
61
+ id : data . id ,
62
62
} ;
63
63
return updatedMessages ;
64
64
} else {
65
65
// Add new message
66
- return [ ...prevMessages , {
67
- role : data . role ,
68
- content : data . message ,
69
- id : data . id
70
- } ] ;
66
+ return [
67
+ ...prevMessages ,
68
+ {
69
+ role : data . role ,
70
+ content : data . message ,
71
+ id : data . id ,
72
+ } ,
73
+ ] ;
71
74
}
72
75
} ) ;
73
76
} ) ;
74
-
77
+
75
78
// Handle status changes
76
- eventSource . addEventListener ( ' status_change' , ( event ) => {
79
+ eventSource . addEventListener ( " status_change" , ( event ) => {
77
80
const data : StatusChangeEvent = JSON . parse ( event . data ) ;
78
81
setServerStatus ( data . status ) ;
79
82
} ) ;
80
-
83
+
81
84
// Handle connection open (server is online)
82
85
eventSource . onopen = ( ) => {
83
86
// Connection is established, but we'll wait for status_change event
84
87
// for the actual server status
85
88
} ;
86
-
89
+
87
90
// Handle connection errors
88
91
eventSource . onerror = ( error ) => {
89
- console . error ( ' EventSource error:' , error ) ;
90
- setServerStatus ( ' offline' ) ;
91
-
92
+ console . error ( " EventSource error:" , error ) ;
93
+ setServerStatus ( " offline" ) ;
94
+
92
95
// Try to reconnect after delay
93
96
setTimeout ( ( ) => {
94
97
if ( eventSourceRef . current ) {
95
98
setupEventSource ( ) ;
96
99
}
97
100
} , 3000 ) ;
98
101
} ;
99
-
102
+
100
103
return eventSource ;
101
104
} ;
102
-
105
+
103
106
// Initial setup
104
107
const eventSource = setupEventSource ( ) ;
105
-
108
+
106
109
// Clean up on component unmount
107
110
return ( ) => {
108
111
eventSource . close ( ) ;
109
112
} ;
110
113
} , [ AgentAPIUrl ] ) ;
111
-
114
+
112
115
const [ error , setError ] = useState < string | null > ( null ) ;
113
116
114
117
// Send a new message
115
- const sendMessage = async ( content : string , type : 'user' | 'raw' = 'user' ) => {
118
+ const sendMessage = async (
119
+ content : string ,
120
+ type : "user" | "raw" = "user"
121
+ ) => {
116
122
// For user messages, require non-empty content
117
- if ( type === ' user' && ! content . trim ( ) ) return ;
118
-
123
+ if ( type === " user" && ! content . trim ( ) ) return ;
124
+
119
125
// Clear any previous errors
120
126
setError ( null ) ;
121
-
127
+
122
128
// For raw messages, don't set loading state as it's usually fast
123
- if ( type === ' user' ) {
129
+ if ( type === " user" ) {
124
130
setLoading ( true ) ;
125
131
}
126
-
132
+
127
133
try {
128
134
const response = await fetch ( `${ AgentAPIUrl } /message` , {
129
- method : ' POST' ,
135
+ method : " POST" ,
130
136
headers : {
131
- ' Content-Type' : ' application/json' ,
137
+ " Content-Type" : " application/json" ,
132
138
} ,
133
- body : JSON . stringify ( {
134
- content : content ,
135
- type
139
+ body : JSON . stringify ( {
140
+ content : content ,
141
+ type,
136
142
} ) ,
137
143
} ) ;
138
-
144
+
139
145
if ( ! response . ok ) {
140
146
const errorData = await response . json ( ) ;
141
- console . error ( ' Failed to send message:' , errorData ) ;
147
+ console . error ( " Failed to send message:" , errorData ) ;
142
148
const detail = errorData . detail ;
143
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
144
- const messages = "errors" in errorData ? errorData . errors . map ( ( e : any ) => e . message ) . join ( ", " ) : "" ;
149
+ const messages =
150
+ "errors" in errorData
151
+ ? // eslint-disable-next-line @typescript-eslint/no-explicit-any
152
+ errorData . errors . map ( ( e : any ) => e . message ) . join ( ", " )
153
+ : "" ;
145
154
146
155
const fullDetail = `${ detail } : ${ messages } ` ;
147
156
setError ( `Failed to send message: ${ fullDetail } ` ) ;
148
157
// Auto-clear error after 5 seconds
149
158
setTimeout ( ( ) => setError ( null ) , 5000 ) ;
150
159
}
151
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
160
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
152
161
} catch ( error : any ) {
153
- console . error ( ' Error sending message:' , error ) ;
162
+ console . error ( " Error sending message:" , error ) ;
154
163
const detail = error . detail ;
155
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
156
- const messages = "errors" in error ? error . errors . map ( ( e : any ) => e . message ) . join ( "\n" ) : "" ;
164
+ const messages =
165
+ "errors" in error
166
+ ? // eslint-disable-next-line @typescript-eslint/no-explicit-any
167
+ error . errors . map ( ( e : any ) => e . message ) . join ( "\n" )
168
+ : "" ;
157
169
158
170
const fullDetail = `${ detail } : ${ messages } ` ;
159
171
160
172
setError ( `Error sending message: ${ fullDetail } ` ) ;
161
173
// Auto-clear error after 5 seconds
162
174
setTimeout ( ( ) => setError ( null ) , 5000 ) ;
163
175
} finally {
164
- if ( type === ' user' ) {
176
+ if ( type === " user" ) {
165
177
setLoading ( false ) ;
166
178
}
167
179
}
168
180
} ;
169
-
181
+
170
182
return (
171
183
< div className = "flex flex-col h-[80vh] bg-gray-100 rounded-lg overflow-hidden border border-gray-300 shadow-lg w-full max-w-[95vw]" >
172
184
< div className = "p-3 bg-gray-800 text-white text-sm flex justify-between items-center" >
173
185
< span > AgentAPI Chat</ span >
174
186
< span className = "flex items-center" >
175
- < span className = { `w-2 h-2 rounded-full mr-2 ${ [ "offline" , "unknown" ] . includes ( serverStatus ) ? 'bg-red-500' : 'bg-green-500' } ` } > </ span >
187
+ < span
188
+ className = { `w-2 h-2 rounded-full mr-2 ${
189
+ [ "offline" , "unknown" ] . includes ( serverStatus )
190
+ ? "bg-red-500"
191
+ : "bg-green-500"
192
+ } `}
193
+ > </ span >
176
194
< span > Status: { serverStatus } </ span >
177
195
< span className = "ml-2" > Port: { port } </ span >
178
196
</ span >
179
197
</ div >
180
-
198
+
181
199
{ error && (
182
200
< div className = "bg-red-100 border border-red-400 text-red-700 px-4 py-2 text-sm relative" >
183
201
< span className = "block sm:inline" > { error } </ span >
184
- < button
202
+ < button
185
203
onClick = { ( ) => setError ( null ) }
186
204
className = "absolute top-0 bottom-0 right-0 px-4 py-2"
187
205
>
188
206
×
189
207
</ button >
190
208
</ div >
191
209
) }
192
-
210
+
193
211
< MessageList messages = { messages } />
194
-
212
+
195
213
< MessageInput onSendMessage = { sendMessage } disabled = { loading } />
196
214
</ div >
197
215
) ;
198
- }
216
+ }
0 commit comments