[eric] composer: web-search toggle (globe) forces WebSearch/WebFetch while on, like a search mode

This commit is contained in:
ciregenz
2026-07-21 15:53:39 -07:00
parent 56038ebbb4
commit 844dfb9300
4 changed files with 52 additions and 2 deletions
+12 -1
View File
@@ -113,6 +113,10 @@ const ChatInput = forwardRef<ChatInputHandle, Props>(({ 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<Record<string, AttachedSkill>>({});
const [previewPasteId, setPreviewPasteId] = useState<string | null>(null);
const attachedSkillsRef = useRef(attachedSkills);
@@ -253,6 +257,11 @@ const ChatInput = forwardRef<ChatInputHandle, Props>(({ 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<ChatInputHandle, Props>(({ 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<ChatInputHandle, Props>(({ onSend, disabled, mode,
isRunning={isRunning}
queueLength={queueLength}
modeConf={modeConf}
webSearchOn={webSearchOn}
onToggleWebSearch={() => setWebSearchOn((v) => !v)}
placeholderOverride={placeholderOverride}
ghostSuggestion={ghostSuggestion}
runContext={runContext}
@@ -50,6 +50,8 @@ interface Props {
isRunning?: boolean;
onStop?: () => void;
handleSend: () => void;
webSearchOn?: boolean;
onToggleWebSearch?: () => void;
}
export const ChatInputToolbar: React.FC<Props> = (p) => {
@@ -58,7 +60,7 @@ export const ChatInputToolbar: React.FC<Props> = (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<Props> = (p) => {
isRunning={isRunning}
onStop={onStop}
handleSend={handleSend}
webSearchOn={webSearchOn}
onToggleWebSearch={onToggleWebSearch}
/>
</Box>
);
@@ -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<Props> = ({
c, elementSelection, autoRunMode, ownerId, sessionId, generalFileInputRef,
addImageFiles, uploadAndAttachFiles, hasContent, disabled, isRunning, onStop, handleSend,
webSearchOn, onToggleWebSearch,
}) => {
return (
<>
{onToggleWebSearch && !autoRunMode && (
<Tooltip title={webSearchOn ? 'Web search on: replies search the web' : 'Search the web'}>
<IconButton
size="small"
onMouseDown={(e) => 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',
}}
>
<LanguageIcon sx={{ fontSize: 18 }} />
</IconButton>
</Tooltip>
)}
{elementSelection && !autoRunMode && (() => {
const isMySelectMode = elementSelection.selectMode && elementSelection.activeOwnerId === ownerId;
return (
@@ -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<Props> = (p) => {
isRunning={p.isRunning}
onStop={p.onStop}
handleSend={p.handleSend}
webSearchOn={p.webSearchOn}
onToggleWebSearch={p.onToggleWebSearch}
/>
<ChatInputOverlays