@@ -15,6 +15,7 @@ import { useUser } from '../contexts/UserContext'
1515import { Button } from './ui/button'
1616import { MessageSquarePlus } from 'lucide-react'
1717import { CodeInterpreterToggle } from './CodeInterpreterToggle'
18+ import { WebSearchToggle } from './WebSearchToggle'
1819import { ModelSelect } from './ModelSelect'
1920import { BotThinking } from './BotThinking'
2021import { BotError } from './BotError'
@@ -23,6 +24,7 @@ import { CodeInterpreterMessage } from './CodeInterpreterMessage'
2324import type { AnnotatedFile } from '@/lib/utils/code-interpreter'
2425import { isCodeInterpreterSupported } from '@/lib/utils/prompting'
2526import { useHasMounted } from '@/hooks/useHasMounted'
27+ import { WebSearchMessage } from './WebSearchMessage'
2628
2729type StreamEvent =
2830 | {
@@ -103,6 +105,15 @@ type StreamEvent =
103105 message : string
104106 details ?: unknown
105107 }
108+ | {
109+ type : 'web_search'
110+ id : string
111+ status : 'in_progress' | 'searching' | 'completed' | 'failed' | 'result'
112+ query ?: string
113+ results ?: Array < { title : string ; url : string ; snippet ?: string } >
114+ error ?: string
115+ raw ?: unknown
116+ }
106117
107118const getToolStatus = (
108119 toolType : string ,
@@ -149,6 +160,9 @@ const getEventKey = (event: StreamEvent | Message, idx: number): string => {
149160 // Use file_id or a combination for uniqueness
150161 return `file-annotation-${ event . annotation . file_id || idx } `
151162 }
163+ if ( event . type === 'web_search' ) {
164+ return `web-search-${ event . id } `
165+ }
152166 }
153167 // Fallback: use idx if id is not present
154168 return `message-${ 'id' in event ? ( event as any ) . id : idx } `
@@ -164,6 +178,7 @@ export function Chat() {
164178 const [ streamBuffer , setStreamBuffer ] = useState < StreamEvent [ ] > ( [ ] )
165179 const [ streaming , setStreaming ] = useState ( false )
166180 const [ useCodeInterpreter , setUseCodeInterpreter ] = useState ( false )
181+ const [ useWebSearch , setUseWebSearch ] = useState ( false )
167182 const { selectedModel, setSelectedModel } = useModel ( )
168183 const { user } = useUser ( )
169184
@@ -283,8 +298,16 @@ export function Chat() {
283298 model : selectedModel ,
284299 userId : user ?. id ,
285300 codeInterpreter : useCodeInterpreter ,
301+ webSearch : useWebSearch ,
286302 } ) ,
287- [ selectedServers , servers , selectedModel , user ?. id , useCodeInterpreter ] ,
303+ [
304+ selectedServers ,
305+ servers ,
306+ selectedModel ,
307+ user ?. id ,
308+ useCodeInterpreter ,
309+ useWebSearch ,
310+ ] ,
288311 )
289312
290313 const handleError = useCallback ( ( error : Error ) => {
@@ -575,6 +598,52 @@ export function Chat() {
575598 return
576599 }
577600
601+ // --- Web Search streaming events ---
602+ if (
603+ toolState . type &&
604+ toolState . type . startsWith ( 'response.web_search_call.' )
605+ ) {
606+ // Map event type to status
607+ let status :
608+ | 'in_progress'
609+ | 'searching'
610+ | 'completed'
611+ | 'failed'
612+ | 'result' = 'in_progress'
613+ if ( toolState . type . endsWith ( 'in_progress' ) ) status = 'in_progress'
614+ else if ( toolState . type . endsWith ( 'searching' ) )
615+ status = 'searching'
616+ else if ( toolState . type . endsWith ( 'completed' ) )
617+ status = 'completed'
618+ else if ( toolState . type . endsWith ( 'failed' ) ) status = 'failed'
619+ else if ( toolState . type . endsWith ( 'result' ) ) status = 'result'
620+ setStreamBuffer ( ( prev : StreamEvent [ ] ) => {
621+ const existingIdx = prev . findIndex (
622+ ( e ) => e . type === 'web_search' && e . id === toolState . item_id ,
623+ )
624+ const newEvent : Extract < StreamEvent , { type : 'web_search' } > = {
625+ type : 'web_search' ,
626+ id : toolState . item_id ,
627+ status,
628+ query : toolState . query ,
629+ error : toolState . error ,
630+ raw : toolState ,
631+ }
632+ if ( existingIdx !== - 1 ) {
633+ // Replace the old event
634+ return [
635+ ...prev . slice ( 0 , existingIdx ) ,
636+ newEvent ,
637+ ...prev . slice ( existingIdx + 1 ) ,
638+ ]
639+ } else {
640+ // Add new event
641+ return [ ...prev , newEvent ]
642+ }
643+ } )
644+ return
645+ }
646+
578647 if ( 'delta' in toolState ) {
579648 try {
580649 toolState . delta =
@@ -816,6 +885,7 @@ export function Chat() {
816885 setInput ( '' )
817886 setFocusTimestamp ( Date . now ( ) )
818887 setUseCodeInterpreter ( false )
888+ setUseWebSearch ( false )
819889
820890 textBufferRef . current = ''
821891 lastAssistantIdRef . current = null
@@ -937,6 +1007,8 @@ export function Chat() {
9371007 } }
9381008 />
9391009 )
1010+ } else if ( 'type' in event && event . type === 'web_search' ) {
1011+ return < WebSearchMessage key = { key } event = { event } />
9401012 } else {
9411013 // Fallback for Message type (from useChat)
9421014 const message = event as Message
@@ -1024,6 +1096,12 @@ export function Chat() {
10241096 selectedModel = { selectedModel }
10251097 disabled = { hasStartedChat }
10261098 />
1099+ < WebSearchToggle
1100+ useWebSearch = { useWebSearch }
1101+ onToggle = { setUseWebSearch }
1102+ selectedModel = { selectedModel }
1103+ disabled = { hasStartedChat }
1104+ />
10271105 </ ServerSelector >
10281106 </ div >
10291107 ) }
0 commit comments