From 844dfb93008ff0f7e1f8d3b71ecf20eb43d4bdcb Mon Sep 17 00:00:00 2001 From: ciregenz Date: Tue, 21 Jul 2026 15:53:39 -0700 Subject: [PATCH] [eric] composer: web-search toggle (globe) forces WebSearch/WebFetch while on, like a search mode --- .../src/app/pages/AgentChat/ChatInput.tsx | 13 +++++++- .../ChatInput/toolbar/ChatInputToolbar.tsx | 6 +++- .../ChatInput/toolbar/ToolbarActions.tsx | 31 +++++++++++++++++++ .../ChatInput/view/ChatInputView.tsx | 4 +++ 4 files changed, 52 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/pages/AgentChat/ChatInput.tsx b/frontend/src/app/pages/AgentChat/ChatInput.tsx index b4cffe7b..ba1974b5 100644 --- a/frontend/src/app/pages/AgentChat/ChatInput.tsx +++ b/frontend/src/app/pages/AgentChat/ChatInput.tsx @@ -113,6 +113,10 @@ const ChatInput = forwardRef(({ onSend, disabled, mode, }, [prefillPrompt]); const [hasContent, setHasContent] = useState(() => !!loadDraft(ownerId)); + // Web-search mode: a conversation-level toggle (like Open WebUI's search switch). While on, every + // send forces the WebSearch/WebFetch tools so the model actually searches instead of answering + // from memory. Merged into forcedTools at send time so it doesn't clutter the attachment chips. + const [webSearchOn, setWebSearchOn] = useState(false); const [attachedSkills, setAttachedSkills] = useState>({}); const [previewPasteId, setPreviewPasteId] = useState(null); const attachedSkillsRef = useRef(attachedSkills); @@ -253,6 +257,11 @@ const ChatInput = forwardRef(({ onSend, disabled, mode, const sendImages = allImages.length > 0 ? allImages : undefined; const allForcedToolNames = forcedTools.flatMap((ft) => ft.tools); + if (webSearchOn) { + for (const t of ['WebSearch', 'WebFetch']) { + if (!allForcedToolNames.includes(t)) allForcedToolNames.push(t); + } + } const currentSkills = Object.values(attachedSkillsRef.current); const sendSkills = currentSkills.length > 0 ? currentSkills.map((s) => ({ id: s.id, name: s.name, content: s.content })) @@ -290,7 +299,7 @@ const ChatInput = forwardRef(({ onSend, disabled, mode, setAttachedSkills({}); setHasContent(false); elementSelection?.clearOwnerElements(ownerId); - }, [disabled, images, contextPaths, forcedTools, onSend, elementSelection, ownerId, summarizingPath, summarizingAll, oversizeQueue, pendingSendRef, sessionId, currentModelCtx, contextEstimate, sessionFrameworkOverhead, setSendBlock, onActivityLabelChange]); + }, [disabled, images, contextPaths, forcedTools, webSearchOn, onSend, elementSelection, ownerId, summarizingPath, summarizingAll, oversizeQueue, pendingSendRef, sessionId, currentModelCtx, contextEstimate, sessionFrameworkOverhead, setSendBlock, onActivityLabelChange]); const { picker: editorPicker, setPicker, @@ -358,6 +367,8 @@ const ChatInput = forwardRef(({ onSend, disabled, mode, isRunning={isRunning} queueLength={queueLength} modeConf={modeConf} + webSearchOn={webSearchOn} + onToggleWebSearch={() => setWebSearchOn((v) => !v)} placeholderOverride={placeholderOverride} ghostSuggestion={ghostSuggestion} runContext={runContext} diff --git a/frontend/src/app/pages/AgentChat/ChatInput/toolbar/ChatInputToolbar.tsx b/frontend/src/app/pages/AgentChat/ChatInput/toolbar/ChatInputToolbar.tsx index 045e199f..78cdb70f 100644 --- a/frontend/src/app/pages/AgentChat/ChatInput/toolbar/ChatInputToolbar.tsx +++ b/frontend/src/app/pages/AgentChat/ChatInput/toolbar/ChatInputToolbar.tsx @@ -50,6 +50,8 @@ interface Props { isRunning?: boolean; onStop?: () => void; handleSend: () => void; + webSearchOn?: boolean; + onToggleWebSearch?: () => void; } export const ChatInputToolbar: React.FC = (p) => { @@ -58,7 +60,7 @@ export const ChatInputToolbar: React.FC = (p) => { allModelFlat, model, onModelChange, onProviderChange, picker, pendingKinds, pendingPayloadEstimate, thinkingLevel, onThinkingLevelChange, contextEstimate, elementSelection, autoRunMode, ownerId, sessionId, generalFileInputRef, addImageFiles, uploadAndAttachFiles, - hasContent, disabled, isRunning, onStop, handleSend, + hasContent, disabled, isRunning, onStop, handleSend, webSearchOn, onToggleWebSearch, } = p; const menuPaperProps = { @@ -172,6 +174,8 @@ export const ChatInputToolbar: React.FC = (p) => { isRunning={isRunning} onStop={onStop} handleSend={handleSend} + webSearchOn={webSearchOn} + onToggleWebSearch={onToggleWebSearch} /> ); diff --git a/frontend/src/app/pages/AgentChat/ChatInput/toolbar/ToolbarActions.tsx b/frontend/src/app/pages/AgentChat/ChatInput/toolbar/ToolbarActions.tsx index 467a12c7..6cb493cd 100644 --- a/frontend/src/app/pages/AgentChat/ChatInput/toolbar/ToolbarActions.tsx +++ b/frontend/src/app/pages/AgentChat/ChatInput/toolbar/ToolbarActions.tsx @@ -7,6 +7,7 @@ import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward'; import StopIcon from '@mui/icons-material/Stop'; import AttachFileIcon from '@mui/icons-material/AttachFile'; import AdsClickIcon from '@mui/icons-material/AdsClick'; +import LanguageIcon from '@mui/icons-material/Language'; import { useElementSelection } from '@/app/components/editor/ElementSelectionContext'; import { ClaudeTokens } from '@/shared/styles/claudeTokens'; @@ -24,14 +25,44 @@ interface Props { isRunning?: boolean; onStop?: () => void; handleSend: () => void; + webSearchOn?: boolean; + onToggleWebSearch?: () => void; } export const ToolbarActions: React.FC = ({ c, elementSelection, autoRunMode, ownerId, sessionId, generalFileInputRef, addImageFiles, uploadAndAttachFiles, hasContent, disabled, isRunning, onStop, handleSend, + webSearchOn, onToggleWebSearch, }) => { return ( <> + {onToggleWebSearch && !autoRunMode && ( + + e.preventDefault()} + onClick={onToggleWebSearch} + sx={{ + p: 0.5, + ...(webSearchOn + ? { + bgcolor: '#3b82f6', + color: '#fff', + '&:hover': { bgcolor: '#2563eb' }, + boxShadow: '0 0 0 3px rgba(59,130,246,0.18)', + } + : { + color: c.text.tertiary, + '&:hover': { color: c.text.secondary, bgcolor: 'rgba(0,0,0,0.04)' }, + }), + transition: 'background-color 0.15s, color 0.15s', + }} + > + + + + )} + {elementSelection && !autoRunMode && (() => { const isMySelectMode = elementSelection.selectMode && elementSelection.activeOwnerId === ownerId; return ( diff --git a/frontend/src/app/pages/AgentChat/ChatInput/view/ChatInputView.tsx b/frontend/src/app/pages/AgentChat/ChatInput/view/ChatInputView.tsx index a224040c..fb9292ed 100644 --- a/frontend/src/app/pages/AgentChat/ChatInput/view/ChatInputView.tsx +++ b/frontend/src/app/pages/AgentChat/ChatInput/view/ChatInputView.tsx @@ -60,6 +60,8 @@ interface Props { isRunning?: boolean; queueLength: number; modeConf: ModeConf; + webSearchOn?: boolean; + onToggleWebSearch?: () => void; placeholderOverride?: string; ghostSuggestion?: string; runContext?: WorkflowsRunContext; @@ -281,6 +283,8 @@ export const ChatInputView: React.FC = (p) => { isRunning={p.isRunning} onStop={p.onStop} handleSend={p.handleSend} + webSearchOn={p.webSearchOn} + onToggleWebSearch={p.onToggleWebSearch} />