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)
This commit is contained in:
balibabu
2026-01-23 09:33:50 +08:00
committed by GitHub
parent e9453a3971
commit 8dd4a41bf8
12 changed files with 110 additions and 12 deletions

View File

@ -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()),
}
)

View File

@ -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);

View File

@ -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<HTMLTextAreaElement>;
createConversationBeforeUploadDocument?(message: string): Promise<any>;
stopOutputMessage?(): void;
onUpload?: NonNullable<FileUploadProps['onUpload']>;
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<File[]>([]);
const [audioInputValue, setAudioInputValue] = React.useState<string | null>(
@ -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<HTMLTextAreaElement>);
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<HTMLTextAreaElement>) => {
if (e.key === 'Enter' && !e.shiftKey) {
@ -205,9 +241,25 @@ export function NextMessageInput({
)}
onClick={handleThinkingToggle}
>
<Atom />
<span>Thinking</span>
</Button>
)}
{showInternet && (
<Button
type="button"
variant="ghost"
className={cn(
'rounded-sm h-7 focus-visible:bg-none! hover:bg-none!',
{
'bg-accent-primary text-white': enableInternet,
},
)}
onClick={handleInternetToggle}
>
<Globe />
</Button>
)}
</div>
{sendLoading ? (
<Button onClick={stopOutputMessage} className="size-5 rounded-sm">

View File

@ -13,6 +13,7 @@ export interface PromptConfig {
use_kg: boolean;
reasoning?: boolean;
cross_languages?: Array<string>;
tavily_api_key?: string;
}
export interface Parameter {
@ -100,6 +101,7 @@ export interface Message {
chatBoxId?: string;
attachment?: IAttachment;
reasoning?: boolean;
internet?: boolean;
}
export interface IReferenceChunk {
@ -183,6 +185,7 @@ export interface IExternalChatInfo {
avatar?: string;
title: string;
prologue?: string;
has_tavily_key?: boolean;
}
export interface IMessage extends Message {

View File

@ -291,6 +291,7 @@ export const useSendAgentMessage = ({
params.session_id = sessionId;
params.reasoning = message.reasoning;
params.internet = message.internet;
}
try {
@ -358,13 +359,21 @@ export const useSendAgentMessage = ({
]);
const handlePressEnter = useCallback(
(...[{ enableThinking }]: NextMessageInputOnPressEnterParameter) => {
(
...[
{ enableThinking, enableInternet },
]: NextMessageInputOnPressEnterParameter
) => {
if (trim(value) === '') return;
const msgBody = buildRequestBody(value);
if (done) {
setValue('');
sendMessage({
message: { ...msgBody, reasoning: enableThinking },
message: {
...msgBody,
reasoning: enableThinking,
internet: enableInternet,
},
});
}
addNewestOneQuestion({ ...msgBody, files: fileList });

View File

@ -39,6 +39,7 @@ import { useSendMessage } from '../../hooks/use-send-chat-message';
import { useSendMultipleChatMessage } from '../../hooks/use-send-multiple-message';
import { buildMessageItemReference } from '../../utils';
import { useAddChatBox } from '../use-add-box';
import { useShowInternet } from '../use-show-internet';
import { useSetDefaultModel } from './use-set-default-model';
type MultipleChatBoxProps = {
@ -226,6 +227,8 @@ export function MultipleChatBox({
const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } =
useClickDrawer();
const showInternet = useShowInternet();
return (
<section className="h-full flex flex-col px-5">
<div className="flex gap-4 flex-1 px-5 pb-14 min-h-0">
@ -261,6 +264,7 @@ export function MultipleChatBox({
stopOutputMessage={stopOutputMessage}
onUpload={handleUploadFile}
showReasoning
showInternet={showInternet}
/>
</div>
{visible && (

View File

@ -18,6 +18,7 @@ import {
import { useCreateConversationBeforeUploadDocument } from '../../hooks/use-create-conversation';
import { useSendMessage } from '../../hooks/use-send-chat-message';
import { buildMessageItemReference } from '../../utils';
import { useShowInternet } from '../use-show-internet';
interface IProps {
controller: AbortController;
@ -55,6 +56,8 @@ export function SingleChatBox({
const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } =
useClickDrawer();
const showInternet = useShowInternet();
useEffect(() => {
const messages = conversation?.message;
if (Array.isArray(messages)) {
@ -120,6 +123,7 @@ export function SingleChatBox({
isUploading={isUploading}
removeFile={removeFile}
showReasoning
showInternet={showInternet}
/>
{visible && (
<PdfSheet

View File

@ -0,0 +1,8 @@
import { useFetchDialog } from '@/hooks/use-chat-request';
import { isEmpty } from 'lodash';
export function useShowInternet() {
const { data: currentDialog } = useFetchDialog();
return !isEmpty(currentDialog?.prompt_config?.tavily_api_key);
}

View File

@ -135,7 +135,11 @@ export const useSendMessage = (controller: AbortController) => {
useCreateConversationBeforeSendMessage();
const handlePressEnter = useCallback(
async (...[{ enableThinking }]: NextMessageInputOnPressEnterParameter) => {
async (
...[
{ enableThinking, enableInternet },
]: NextMessageInputOnPressEnterParameter
) => {
if (trim(value) === '') return;
const data = await createConversationBeforeSendMessage(value);
@ -168,6 +172,7 @@ export const useSendMessage = (controller: AbortController) => {
files: files,
conversationId: targetConversationId,
reasoning: enableThinking,
internet: enableInternet,
},
});
}

View File

@ -177,7 +177,11 @@ export function useSendMultipleChatMessage(
);
const handlePressEnter = useCallback(
async (...[{ enableThinking }]: NextMessageInputOnPressEnterParameter) => {
async (
...[
{ enableThinking, enableInternet },
]: NextMessageInputOnPressEnterParameter
) => {
if (trim(value) === '') return;
const id = uuid();
@ -214,6 +218,7 @@ export function useSendMultipleChatMessage(
files,
conversationId: targetConversationId,
reasoning: enableThinking,
internet: enableInternet,
},
chatBoxId,
currentConversationId: targetConversationId,

View File

@ -73,6 +73,7 @@ export const useSendSharedMessage = () => {
question: message.content,
session_id: get(derivedMessages, '0.session_id'),
reasoning: message.reasoning,
internet: message.internet,
});
if (isCompletionError(res)) {
@ -119,7 +120,11 @@ export const useSendSharedMessage = () => {
}, [answer, addNewestAnswer]);
const handlePressEnter = useCallback(
(...[{ enableThinking }]: NextMessageInputOnPressEnterParameter) => {
(
...[
{ enableThinking, enableInternet },
]: NextMessageInputOnPressEnterParameter
) => {
if (trim(value) === '') return;
const id = uuid();
if (done) {
@ -135,6 +140,7 @@ export const useSendSharedMessage = () => {
id,
role: MessageType.User,
reasoning: enableThinking,
internet: enableInternet,
});
}
},

View File

@ -124,6 +124,7 @@ const ChatContainer = () => {
showUploadIcon={false}
stopOutputMessage={stopOutputMessage}
showReasoning
showInternet={chatInfo?.has_tavily_key}
></NextMessageInput>
</div>
</div>