From 8dd4a41bf84799b991eaa3496465a96728a7eddb Mon Sep 17 00:00:00 2001 From: balibabu Date: Fri, 23 Jan 2026 09:33:50 +0800 Subject: [PATCH] Feat: Add a web search button to the chat box on the chat page. (#12786) ### What problem does this PR solve? Feat: Add a web search button to the chat box on the chat page. ### Type of change - [x] New Feature (non-breaking change which adds functionality) --- api/apps/sdk/session.py | 1 + web/src/components/floating-chat-widget.tsx | 2 +- web/src/components/message-input/next.tsx | 64 +++++++++++++++++-- web/src/interfaces/database/chat.ts | 3 + .../agent/chat/use-send-agent-message.ts | 13 +++- .../chat/chat-box/multiple-chat-box.tsx | 4 ++ .../chat/chat-box/single-chat-box.tsx | 4 ++ .../next-chats/chat/use-show-internet.ts | 8 +++ .../next-chats/hooks/use-send-chat-message.ts | 7 +- .../hooks/use-send-multiple-message.ts | 7 +- .../hooks/use-send-shared-message.ts | 8 ++- web/src/pages/next-chats/share/index.tsx | 1 + 12 files changed, 110 insertions(+), 12 deletions(-) create mode 100644 web/src/pages/next-chats/chat/use-show-internet.ts diff --git a/api/apps/sdk/session.py b/api/apps/sdk/session.py index 36b407cea..0468381d3 100644 --- a/api/apps/sdk/session.py +++ b/api/apps/sdk/session.py @@ -934,6 +934,7 @@ async def chatbots_inputs(dialog_id): "title": dialog.name, "avatar": dialog.icon, "prologue": dialog.prompt_config.get("prologue", ""), + "has_tavily_key": bool(dialog.prompt_config.get("tavily_api_key", "").strip()), } ) diff --git a/web/src/components/floating-chat-widget.tsx b/web/src/components/floating-chat-widget.tsx index c63695031..14b85018d 100644 --- a/web/src/components/floating-chat-widget.tsx +++ b/web/src/components/floating-chat-widget.tsx @@ -268,7 +268,7 @@ const FloatingChatWidget = () => { // Wait for state to update, then send setTimeout(() => { - handlePressEnter({ enableThinking: false }); + handlePressEnter({ enableThinking: false, enableInternet: false }); // Clear our local input after sending setInputValue(''); }, 50); diff --git a/web/src/components/message-input/next.tsx b/web/src/components/message-input/next.tsx index 20f8e88db..972f7eb3e 100644 --- a/web/src/components/message-input/next.tsx +++ b/web/src/components/message-input/next.tsx @@ -16,7 +16,15 @@ import { Button } from '@/components/ui/button'; import { Textarea } from '@/components/ui/textarea'; import { cn } from '@/lib/utils'; import { t } from 'i18next'; -import { CircleStop, Paperclip, Send, Upload, X } from 'lucide-react'; +import { + Atom, + CircleStop, + Globe, + Paperclip, + Send, + Upload, + X, +} from 'lucide-react'; import * as React from 'react'; import { useCallback, useEffect, useState } from 'react'; import { toast } from 'sonner'; @@ -32,13 +40,20 @@ interface NextMessageInputProps { isShared?: boolean; showUploadIcon?: boolean; isUploading?: boolean; - onPressEnter({ enableThinking }: { enableThinking: boolean }): void; + onPressEnter({ + enableThinking, + enableInternet, + }: { + enableThinking: boolean; + enableInternet: boolean; + }): void; onInputChange: React.ChangeEventHandler; createConversationBeforeUploadDocument?(message: string): Promise; stopOutputMessage?(): void; onUpload?: NonNullable; removeFile?(file: File): void; showReasoning?: boolean; + showInternet?: boolean; } export type NextMessageInputOnPressEnterParameter = Parameters< @@ -58,6 +73,7 @@ export function NextMessageInput({ onPressEnter, removeFile, showReasoning = false, + showInternet = false, }: NextMessageInputProps) { const [files, setFiles] = React.useState([]); const [audioInputValue, setAudioInputValue] = React.useState( @@ -65,11 +81,23 @@ export function NextMessageInput({ ); const [enableThinking, setEnableThinking] = useState(false); + const [enableInternet, setEnableInternet] = useState(false); const handleThinkingToggle = useCallback(() => { setEnableThinking((prev) => !prev); }, []); + const handleInternetToggle = useCallback(() => { + setEnableInternet((prev) => !prev); + }, []); + + const pressEnter = useCallback(() => { + onPressEnter({ + enableThinking, + enableInternet: showInternet ? enableInternet : false, + }); + }, [onPressEnter, enableThinking, enableInternet, showInternet]); + useEffect(() => { if (audioInputValue !== null) { onInputChange({ @@ -77,11 +105,19 @@ export function NextMessageInput({ } as React.ChangeEvent); setTimeout(() => { - onPressEnter({ enableThinking }); + pressEnter(); setAudioInputValue(null); }, 0); } - }, [audioInputValue, onInputChange, onPressEnter, enableThinking]); + }, [ + audioInputValue, + onInputChange, + onPressEnter, + enableThinking, + enableInternet, + showInternet, + pressEnter, + ]); const onFileReject = React.useCallback((file: File, message: string) => { toast(message, { @@ -91,9 +127,9 @@ export function NextMessageInput({ const submit = React.useCallback(() => { if (isUploading) return; - onPressEnter({ enableThinking }); + pressEnter(); setFiles([]); - }, [isUploading, onPressEnter, enableThinking]); + }, [isUploading, pressEnter]); const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && !e.shiftKey) { @@ -205,9 +241,25 @@ export function NextMessageInput({ )} onClick={handleThinkingToggle} > + Thinking )} + {showInternet && ( + + )} {sendLoading ? (