@@ -80,6 +80,7 @@ const ChatInterface: React.FC<ChatInterfaceProps> = ({
8080
8181 const sessionIdRef = useRef < string > ( session ?. id || nanoid ( ) )
8282 const [ expandingToolCalls , setExpandingToolCalls ] = useState < string [ ] > ( [ ] )
83+ const [ pendingToolConfirmations , setPendingToolConfirmations ] = useState < string [ ] > ( [ ] )
8384
8485 const scrollRef = useRef < HTMLDivElement > ( null )
8586 const isAtBottomRef = useRef ( false )
@@ -141,7 +142,7 @@ const ChatInterface: React.FC<ChatInterfaceProps> = ({
141142 last . content . at ( - 1 ) &&
142143 last . content . at ( - 1 ) ! . type === 'text'
143144 ) {
144- ; ( last . content . at ( - 1 ) as { text : string } ) . text += data . text
145+ ; ( last . content . at ( - 1 ) as { text : string } ) . text += data . text
145146 }
146147 } else {
147148 prev . push ( {
@@ -203,6 +204,118 @@ const ChatInterface: React.FC<ChatInterfaceProps> = ({
203204 [ sessionId ]
204205 )
205206
207+ const handleToolCallPendingConfirmation = useCallback (
208+ ( data : TEvents [ 'Socket::Session::ToolCallPendingConfirmation' ] ) => {
209+ if ( data . session_id && data . session_id !== sessionId ) {
210+ return
211+ }
212+
213+ const existToolCall = messages . find (
214+ ( m ) =>
215+ m . role === 'assistant' &&
216+ m . tool_calls &&
217+ m . tool_calls . find ( ( t ) => t . id == data . id )
218+ )
219+
220+ if ( existToolCall ) {
221+ return
222+ }
223+
224+ setMessages (
225+ produce ( ( prev ) => {
226+ console . log ( '👇tool_call_pending_confirmation event get' , data )
227+ setPending ( 'tool' )
228+ prev . push ( {
229+ role : 'assistant' ,
230+ content : '' ,
231+ tool_calls : [
232+ {
233+ type : 'function' ,
234+ function : {
235+ name : data . name ,
236+ arguments : data . arguments ,
237+ } ,
238+ id : data . id ,
239+ } ,
240+ ] ,
241+ } )
242+ } )
243+ )
244+
245+ setPendingToolConfirmations (
246+ produce ( ( prev ) => {
247+ prev . push ( data . id )
248+ } )
249+ )
250+
251+ // 自动展开需要确认的工具调用
252+ setExpandingToolCalls (
253+ produce ( ( prev ) => {
254+ if ( ! prev . includes ( data . id ) ) {
255+ prev . push ( data . id )
256+ }
257+ } )
258+ )
259+ } ,
260+ [ sessionId ]
261+ )
262+
263+ const handleToolCallConfirmed = useCallback (
264+ ( data : TEvents [ 'Socket::Session::ToolCallConfirmed' ] ) => {
265+ if ( data . session_id && data . session_id !== sessionId ) {
266+ return
267+ }
268+
269+ setPendingToolConfirmations (
270+ produce ( ( prev ) => {
271+ return prev . filter ( ( id ) => id !== data . id )
272+ } )
273+ )
274+
275+ setExpandingToolCalls (
276+ produce ( ( prev ) => {
277+ if ( ! prev . includes ( data . id ) ) {
278+ prev . push ( data . id )
279+ }
280+ } )
281+ )
282+ } ,
283+ [ sessionId ]
284+ )
285+
286+ const handleToolCallCancelled = useCallback (
287+ ( data : TEvents [ 'Socket::Session::ToolCallCancelled' ] ) => {
288+ if ( data . session_id && data . session_id !== sessionId ) {
289+ return
290+ }
291+
292+ setPendingToolConfirmations (
293+ produce ( ( prev ) => {
294+ return prev . filter ( ( id ) => id !== data . id )
295+ } )
296+ )
297+
298+ // 更新工具调用的状态
299+ setMessages (
300+ produce ( ( prev ) => {
301+ prev . forEach ( ( msg ) => {
302+ if ( msg . role === 'assistant' && msg . tool_calls ) {
303+ msg . tool_calls . forEach ( ( tc ) => {
304+ if ( tc . id === data . id ) {
305+ // 添加取消状态标记
306+ tc . result = "工具调用已取消"
307+ }
308+ } )
309+ }
310+ } )
311+ } )
312+ )
313+ } ,
314+ [ sessionId ]
315+ )
316+
317+
318+
206319 const handleToolCallArguments = useCallback (
207320 ( data : TEvents [ 'Socket::Session::ToolCallArguments' ] ) => {
208321 if ( data . session_id && data . session_id !== sessionId ) {
@@ -224,14 +337,18 @@ const ChatInterface: React.FC<ChatInterfaceProps> = ({
224337 ( t ) => t . id == data . id
225338 )
226339 if ( toolCall ) {
340+ // 检查是否是待确认的工具调用,如果是则跳过参数追加
341+ if ( pendingToolConfirmations . includes ( data . id ) ) {
342+ return
343+ }
227344 toolCall . function . arguments += data . text
228345 }
229346 }
230347 } )
231348 )
232349 scrollToBottom ( )
233350 } ,
234- [ sessionId , scrollToBottom ]
351+ [ sessionId , scrollToBottom , pendingToolConfirmations ]
235352 )
236353
237354 const handleToolCallResult = useCallback (
@@ -333,6 +450,9 @@ const ChatInterface: React.FC<ChatInterfaceProps> = ({
333450
334451 eventBus . on ( 'Socket::Session::Delta' , handleDelta )
335452 eventBus . on ( 'Socket::Session::ToolCall' , handleToolCall )
453+ eventBus . on ( 'Socket::Session::ToolCallPendingConfirmation' , handleToolCallPendingConfirmation )
454+ eventBus . on ( 'Socket::Session::ToolCallConfirmed' , handleToolCallConfirmed )
455+ eventBus . on ( 'Socket::Session::ToolCallCancelled' , handleToolCallCancelled )
336456 eventBus . on ( 'Socket::Session::ToolCallArguments' , handleToolCallArguments )
337457 eventBus . on ( 'Socket::Session::ToolCallResult' , handleToolCallResult )
338458 eventBus . on ( 'Socket::Session::ImageGenerated' , handleImageGenerated )
@@ -345,6 +465,9 @@ const ChatInterface: React.FC<ChatInterfaceProps> = ({
345465
346466 eventBus . off ( 'Socket::Session::Delta' , handleDelta )
347467 eventBus . off ( 'Socket::Session::ToolCall' , handleToolCall )
468+ eventBus . off ( 'Socket::Session::ToolCallPendingConfirmation' , handleToolCallPendingConfirmation )
469+ eventBus . off ( 'Socket::Session::ToolCallConfirmed' , handleToolCallConfirmed )
470+ eventBus . off ( 'Socket::Session::ToolCallCancelled' , handleToolCallCancelled )
348471 eventBus . off (
349472 'Socket::Session::ToolCallArguments' ,
350473 handleToolCallArguments
@@ -506,6 +629,35 @@ const ChatInterface: React.FC<ChatInterfaceProps> = ({
506629 ] )
507630 }
508631 } }
632+ requiresConfirmation = { pendingToolConfirmations . includes ( toolCall . id ) }
633+ onConfirm = { ( ) => {
634+ // 发送确认事件到后端
635+ fetch ( '/api/tool_confirmation' , {
636+ method : 'POST' ,
637+ headers : {
638+ 'Content-Type' : 'application/json' ,
639+ } ,
640+ body : JSON . stringify ( {
641+ session_id : sessionId ,
642+ tool_call_id : toolCall . id ,
643+ confirmed : true ,
644+ } ) ,
645+ } )
646+ } }
647+ onCancel = { ( ) => {
648+ // 发送取消事件到后端
649+ fetch ( '/api/tool_confirmation' , {
650+ method : 'POST' ,
651+ headers : {
652+ 'Content-Type' : 'application/json' ,
653+ } ,
654+ body : JSON . stringify ( {
655+ session_id : sessionId ,
656+ tool_call_id : toolCall . id ,
657+ confirmed : false ,
658+ } ) ,
659+ } )
660+ } }
509661 />
510662 )
511663 } ) }
0 commit comments