diff --git a/web/src/components/embed-container.tsx b/web/src/components/embed-container.tsx new file mode 100644 index 000000000..e96dab673 --- /dev/null +++ b/web/src/components/embed-container.tsx @@ -0,0 +1,48 @@ +import { useFetchAppConf } from '@/hooks/logic-hooks'; +import { RefreshCcw } from 'lucide-react'; +import { PropsWithChildren } from 'react'; +import { RAGFlowAvatar } from './ragflow-avatar'; +import { Button } from './ui/button'; + +type EmbedContainerProps = { + title: string; + avatar?: string; + handleReset?(): void; +} & PropsWithChildren; + +export function EmbedContainer({ + title, + avatar, + children, + handleReset, +}: EmbedContainerProps) { + const appConf = useFetchAppConf(); + + return ( +
+
+ + {appConf.appName} +
+
+
+
+ +
{title}
+
+ +
+ {children} +
+
+ ); +} diff --git a/web/src/pages/agent/embed-dialog/index.tsx b/web/src/components/embed-dialog/index.tsx similarity index 96% rename from web/src/pages/agent/embed-dialog/index.tsx rename to web/src/components/embed-dialog/index.tsx index 25556e311..48abfb0d0 100644 --- a/web/src/pages/agent/embed-dialog/index.tsx +++ b/web/src/components/embed-dialog/index.tsx @@ -23,6 +23,7 @@ import { } from '@/constants/common'; import { useTranslate } from '@/hooks/common-hooks'; import { IModalProps } from '@/interfaces/common'; +import { Routes } from '@/routes'; import { zodResolver } from '@hookform/resolvers/zod'; import { memo, useCallback, useMemo } from 'react'; import { useForm, useWatch } from 'react-hook-form'; @@ -68,7 +69,7 @@ function EmbedDialog({ const generateIframeSrc = useCallback(() => { const { visibleAvatar, locale } = values; - let src = `${location.origin}/next-chat/share?shared_id=${token}&from=${from}&auth=${beta}`; + let src = `${location.origin}${from === SharedFrom.Agent ? Routes.AgentShare : Routes.ChatShare}?shared_id=${token}&from=${from}&auth=${beta}`; if (visibleAvatar) { src += '&visible_avatar=1'; } diff --git a/web/src/components/embed-dialog/use-show-embed-dialog.ts b/web/src/components/embed-dialog/use-show-embed-dialog.ts new file mode 100644 index 000000000..c8ac12032 --- /dev/null +++ b/web/src/components/embed-dialog/use-show-embed-dialog.ts @@ -0,0 +1,87 @@ +import { useSetModalState, useTranslate } from '@/hooks/common-hooks'; +import { useFetchManualSystemTokenList } from '@/hooks/user-setting-hooks'; +import { useCallback } from 'react'; +import message from '../ui/message'; + +export const useShowTokenEmptyError = () => { + const { t } = useTranslate('chat'); + + const showTokenEmptyError = useCallback(() => { + message.error(t('tokenError')); + }, [t]); + return { showTokenEmptyError }; +}; + +export const useShowBetaEmptyError = () => { + const { t } = useTranslate('chat'); + + const showBetaEmptyError = useCallback(() => { + message.error(t('betaError')); + }, [t]); + return { showBetaEmptyError }; +}; + +export const useFetchTokenListBeforeOtherStep = () => { + const { showTokenEmptyError } = useShowTokenEmptyError(); + const { showBetaEmptyError } = useShowBetaEmptyError(); + + const { data: tokenList, fetchSystemTokenList } = + useFetchManualSystemTokenList(); + + let token = '', + beta = ''; + + if (Array.isArray(tokenList) && tokenList.length > 0) { + token = tokenList[0].token; + beta = tokenList[0].beta; + } + + token = + Array.isArray(tokenList) && tokenList.length > 0 ? tokenList[0].token : ''; + + const handleOperate = useCallback(async () => { + const ret = await fetchSystemTokenList(); + const list = ret; + if (Array.isArray(list) && list.length > 0) { + if (!list[0].beta) { + showBetaEmptyError(); + return false; + } + return list[0]?.token; + } else { + showTokenEmptyError(); + return false; + } + }, [fetchSystemTokenList, showBetaEmptyError, showTokenEmptyError]); + + return { + token, + beta, + handleOperate, + }; +}; + +export const useShowEmbedModal = () => { + const { + visible: embedVisible, + hideModal: hideEmbedModal, + showModal: showEmbedModal, + } = useSetModalState(); + + const { handleOperate, token, beta } = useFetchTokenListBeforeOtherStep(); + + const handleShowEmbedModal = useCallback(async () => { + const succeed = await handleOperate(); + if (succeed) { + showEmbedModal(); + } + }, [handleOperate, showEmbedModal]); + + return { + showEmbedModal: handleShowEmbedModal, + hideEmbedModal, + embedVisible, + embedToken: token, + beta, + }; +}; diff --git a/web/src/components/large-model-form-field.tsx b/web/src/components/large-model-form-field.tsx index a37ae6f47..d81229cb2 100644 --- a/web/src/components/large-model-form-field.tsx +++ b/web/src/components/large-model-form-field.tsx @@ -16,7 +16,7 @@ import { Funnel } from 'lucide-react'; import { useFormContext, useWatch } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; import { z } from 'zod'; -import { NextLLMSelect } from './llm-select/next'; +import { NextInnerLLMSelectProps, NextLLMSelect } from './llm-select/next'; import { Button } from './ui/button'; const ModelTypes = [ @@ -38,7 +38,10 @@ export const LargeModelFilterFormSchema = { llm_filter: z.string().optional(), }; -export function LargeModelFormField() { +type LargeModelFormFieldProps = Pick; +export function LargeModelFormField({ + showTTSModel, +}: LargeModelFormFieldProps) { const form = useFormContext(); const { t } = useTranslation(); const filter = useWatch({ control: form.control, name: 'llm_filter' }); @@ -85,7 +88,11 @@ export function LargeModelFormField() { /> - + diff --git a/web/src/components/llm-select/next.tsx b/web/src/components/llm-select/next.tsx index 45fc45e24..935af5df9 100644 --- a/web/src/components/llm-select/next.tsx +++ b/web/src/components/llm-select/next.tsx @@ -1,29 +1,41 @@ import { LlmModelType } from '@/constants/knowledge'; import { useComposeLlmOptionsByModelTypes } from '@/hooks/llm-hooks'; import * as SelectPrimitive from '@radix-ui/react-select'; -import { forwardRef, memo, useState } from 'react'; +import { forwardRef, memo, useMemo, useState } from 'react'; import { LlmSettingFieldItems } from '../llm-setting-items/next'; import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover'; import { Select, SelectTrigger, SelectValue } from '../ui/select'; -interface IProps { +export interface NextInnerLLMSelectProps { id?: string; value?: string; onInitialValue?: (value: string, option: any) => void; onChange?: (value: string) => void; disabled?: boolean; filter?: string; + showTTSModel?: boolean; } const NextInnerLLMSelect = forwardRef< React.ElementRef, - IProps ->(({ value, disabled, filter }, ref) => { + NextInnerLLMSelectProps +>(({ value, disabled, filter, showTTSModel = false }, ref) => { const [isPopoverOpen, setIsPopoverOpen] = useState(false); - const modelTypes = - filter === 'all' || filter === undefined - ? [LlmModelType.Chat, LlmModelType.Image2text] - : [filter as LlmModelType]; + + const ttsModel = useMemo(() => { + return showTTSModel ? [LlmModelType.TTS] : []; + }, [showTTSModel]); + + const modelTypes = useMemo(() => { + if (filter === LlmModelType.Chat) { + return [LlmModelType.Chat]; + } else if (filter === LlmModelType.Image2text) { + return [LlmModelType.Image2text, ...ttsModel]; + } else { + return [LlmModelType.Chat, LlmModelType.Image2text, ...ttsModel]; + } + }, [filter, ttsModel]); + const modelOptions = useComposeLlmOptionsByModelTypes(modelTypes); return ( diff --git a/web/src/components/message-input/next.tsx b/web/src/components/message-input/next.tsx index dcbed3694..200f3d139 100644 --- a/web/src/components/message-input/next.tsx +++ b/web/src/components/message-input/next.tsx @@ -14,6 +14,7 @@ import { } from '@/components/file-upload'; import { Button } from '@/components/ui/button'; import { Textarea } from '@/components/ui/textarea'; +import { cn } from '@/lib/utils'; import { CircleStop, Paperclip, Send, Upload, X } from 'lucide-react'; import * as React from 'react'; import { toast } from 'sonner'; @@ -135,7 +136,11 @@ export function NextMessageInput({ disabled={isUploading || disabled || sendLoading} onKeyDown={handleKeyDown} /> -
+
{showUploadIcon && ( @@ -133,6 +141,16 @@ export default function Chat() {
+ {embedVisible && ( + + )} ); } diff --git a/web/src/pages/next-chats/hooks/use-send-shared-message.ts b/web/src/pages/next-chats/hooks/use-send-shared-message.ts index d48978dc0..f4e10129c 100644 --- a/web/src/pages/next-chats/hooks/use-send-shared-message.ts +++ b/web/src/pages/next-chats/hooks/use-send-shared-message.ts @@ -1,10 +1,20 @@ -import { SharedFrom } from '@/constants/chat'; -import { useSetModalState } from '@/hooks/common-hooks'; -import { IEventList } from '@/hooks/use-send-message'; -import { useSendAgentMessage } from '@/pages/agent/chat/use-send-agent-message'; +import { MessageType, SharedFrom } from '@/constants/chat'; +import { useCreateNextSharedConversation } from '@/hooks/chat-hooks'; +import { + useHandleMessageInputChange, + useSelectDerivedMessages, + useSendMessageWithSse, +} from '@/hooks/logic-hooks'; +import { Message } from '@/interfaces/database/chat'; +import { message } from 'antd'; +import { get } from 'lodash'; import trim from 'lodash/trim'; -import { useCallback, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { useSearchParams } from 'umi'; +import { v4 as uuid } from 'uuid'; + +const isCompletionError = (res: any) => + res && (res?.response.status !== 200 || res?.data?.code !== 0); export const useSendButtonDisabled = (value: string) => { return trim(value) === ''; @@ -30,36 +40,114 @@ export const useGetSharedChatSearchParams = () => { }; }; -export const useSendNextSharedMessage = ( - addEventList: (data: IEventList, messageId: string) => void, -) => { - const { from, sharedId: conversationId } = useGetSharedChatSearchParams(); - const url = `/api/v1/${from === SharedFrom.Agent ? 'agentbots' : 'chatbots'}/${conversationId}/completions`; - - const [params, setParams] = useState([]); - +export const useSendSharedMessage = () => { const { - visible: parameterDialogVisible, - hideModal: hideParameterDialog, - showModal: showParameterDialog, - } = useSetModalState(); + from, + sharedId: conversationId, + data: data, + } = useGetSharedChatSearchParams(); + const { createSharedConversation: setConversation } = + useCreateNextSharedConversation(); + const { handleInputChange, value, setValue } = useHandleMessageInputChange(); + const { send, answer, done, stopOutputMessage } = useSendMessageWithSse( + `/api/v1/${from === SharedFrom.Agent ? 'agentbots' : 'chatbots'}/${conversationId}/completions`, + ); + const { + derivedMessages, + removeLatestMessage, + addNewestAnswer, + addNewestQuestion, + scrollRef, + messageContainerRef, + removeAllMessages, + } = useSelectDerivedMessages(); + const [hasError, setHasError] = useState(false); - const ret = useSendAgentMessage(url, addEventList, params); + const sendMessage = useCallback( + async (message: Message, id?: string) => { + const res = await send({ + conversation_id: id ?? conversationId, + quote: true, + question: message.content, + session_id: get(derivedMessages, '0.session_id'), + }); - const ok = useCallback( - (params: any[]) => { - setParams(params); - hideParameterDialog(); + if (isCompletionError(res)) { + // cancel loading + setValue(message.content); + removeLatestMessage(); + } }, - [hideParameterDialog], + [send, conversationId, derivedMessages, setValue, removeLatestMessage], + ); + + const handleSendMessage = useCallback( + async (message: Message) => { + if (conversationId !== '') { + sendMessage(message); + } else { + const data = await setConversation('user id'); + if (data.code === 0) { + const id = data.data.id; + sendMessage(message, id); + } + } + }, + [conversationId, setConversation, sendMessage], + ); + + const fetchSessionId = useCallback(async () => { + const payload = { question: '' }; + const ret = await send({ ...payload, ...data }); + if (isCompletionError(ret)) { + message.error(ret?.data.message); + setHasError(true); + } + }, [send]); + + useEffect(() => { + fetchSessionId(); + }, [fetchSessionId]); + + useEffect(() => { + if (answer.answer) { + addNewestAnswer(answer); + } + }, [answer, addNewestAnswer]); + + const handlePressEnter = useCallback( + (documentIds: string[]) => { + if (trim(value) === '') return; + const id = uuid(); + if (done) { + setValue(''); + addNewestQuestion({ + content: value, + doc_ids: documentIds, + id, + role: MessageType.User, + }); + handleSendMessage({ + content: value.trim(), + id, + role: MessageType.User, + }); + } + }, + [addNewestQuestion, done, handleSendMessage, setValue, value], ); return { - ...ret, - hasError: false, - parameterDialogVisible, - hideParameterDialog, - showParameterDialog, - ok, + handlePressEnter, + handleInputChange, + value, + sendLoading: !done, + loading: false, + derivedMessages, + hasError, + stopOutputMessage, + scrollRef, + messageContainerRef, + removeAllMessages, }; }; diff --git a/web/src/pages/next-chats/share/index.tsx b/web/src/pages/next-chats/share/index.tsx index e779df9be..bf0b7a7a8 100644 --- a/web/src/pages/next-chats/share/index.tsx +++ b/web/src/pages/next-chats/share/index.tsx @@ -1,166 +1,75 @@ -import { FileUploadProps } from '@/components/file-upload'; +import { EmbedContainer } from '@/components/embed-container'; import { NextMessageInput } from '@/components/message-input/next'; -import MessageItem from '@/components/next-message-item'; +import MessageItem from '@/components/message-item'; import PdfDrawer from '@/components/pdf-drawer'; import { useClickDrawer } from '@/components/pdf-drawer/hooks'; -import { RAGFlowAvatar } from '@/components/ragflow-avatar'; -import { Button } from '@/components/ui/button'; -import { MessageType } from '@/constants/chat'; -import { useFetchAppConf } from '@/hooks/logic-hooks'; -import { - useFetchExternalAgentInputs, - useUploadCanvasFileWithProgress, -} from '@/hooks/use-agent-request'; -import { cn } from '@/lib/utils'; +import { MessageType, SharedFrom } from '@/constants/chat'; +import { useFetchNextConversationSSE } from '@/hooks/chat-hooks'; +import { useFetchFlowSSE } from '@/hooks/flow-hooks'; +import { useFetchExternalChatInfo } from '@/hooks/use-chat-request'; import i18n from '@/locales/config'; -import DebugContent from '@/pages/agent/debug-content'; -import { useCacheChatLog } from '@/pages/agent/hooks/use-cache-chat-log'; -import { useAwaitCompentData } from '@/pages/agent/hooks/use-chat-logic'; -import { IInputs } from '@/pages/agent/interface'; import { useSendButtonDisabled } from '@/pages/chat/hooks'; import { buildMessageUuidWithRole } from '@/utils/chat'; -import { isEmpty } from 'lodash'; -import { RefreshCcw } from 'lucide-react'; -import React, { forwardRef, useCallback, useState } from 'react'; +import React, { forwardRef, useMemo } from 'react'; import { useGetSharedChatSearchParams, - useSendNextSharedMessage, + useSendSharedMessage, } from '../hooks/use-send-shared-message'; -import { ParameterDialog } from './parameter-dialog'; +import { buildMessageItemReference } from '../utils'; const ChatContainer = () => { const { sharedId: conversationId, + from, locale, visibleAvatar, } = useGetSharedChatSearchParams(); const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } = useClickDrawer(); - const { uploadCanvasFile, loading } = - useUploadCanvasFileWithProgress(conversationId); - const { - addEventList, - setCurrentMessageId, - currentEventListWithoutMessageById, - clearEventList, - } = useCacheChatLog(); const { handlePressEnter, handleInputChange, value, sendLoading, - scrollRef, - messageContainerRef, derivedMessages, hasError, stopOutputMessage, - findReferenceByMessageId, - appendUploadResponseList, - parameterDialogVisible, - showParameterDialog, - sendFormMessage, - addNewestOneAnswer, - ok, - resetSession, - } = useSendNextSharedMessage(addEventList); - const { buildInputList, handleOk, isWaitting } = useAwaitCompentData({ - derivedMessages, - sendFormMessage, - canvasId: conversationId as string, - }); + scrollRef, + messageContainerRef, + removeAllMessages, + } = useSendSharedMessage(); const sendDisabled = useSendButtonDisabled(value); - const appConf = useFetchAppConf(); - const { data: inputsData } = useFetchExternalAgentInputs(); - const [agentInfo, setAgentInfo] = useState({ - avatar: '', - title: '', - inputs: {}, - prologue: '', - }); - const handleUploadFile: NonNullable = - useCallback( - async (files, options) => { - const ret = await uploadCanvasFile({ files, options }); - appendUploadResponseList(ret.data, files); - }, - [appendUploadResponseList, uploadCanvasFile], - ); + const { data: chatInfo } = useFetchExternalChatInfo(); + const useFetchAvatar = useMemo(() => { + return from === SharedFrom.Agent + ? useFetchFlowSSE + : useFetchNextConversationSSE; + }, [from]); React.useEffect(() => { if (locale && i18n.language !== locale) { i18n.changeLanguage(locale); } }, [locale, visibleAvatar]); + const { data: avatarData } = useFetchAvatar(); - React.useEffect(() => { - const { avatar, title, inputs } = inputsData; - setAgentInfo({ - avatar, - title, - inputs: inputs, - prologue: '', - }); - }, [inputsData, setAgentInfo]); - - React.useEffect(() => { - if (inputsData.prologue) { - addNewestOneAnswer({ - answer: inputsData.prologue, - }); - } - }, [inputsData.prologue, addNewestOneAnswer]); - - React.useEffect(() => { - if (inputsData && inputsData.inputs && !isEmpty(inputsData.inputs)) { - showParameterDialog(); - } - }, [inputsData, showParameterDialog]); - - const handleInputsModalOk = (params: any[]) => { - ok(params); - }; - const handleReset = () => { - resetSession(); - clearEventList(); - }; if (!conversationId) { return
empty
; } + return ( -
-
- - {appConf.appName} -
-
-
-
- -
{agentInfo.title}
-
- -
+ <> +
@@ -168,51 +77,27 @@ const ChatContainer = () => { return ( - {message.role === MessageType.Assistant && - derivedMessages.length - 1 === i && ( - - )} - {message.role === MessageType.Assistant && - derivedMessages.length - 1 !== i && ( -
-
{message?.data?.tips}
- -
- {buildInputList(message)?.map((item) => item.value)} -
-
- )} -
+ > ); })}
@@ -223,20 +108,20 @@ const ChatContainer = () => {
-
+ {visible && ( { chunk={selectedChunk} > )} - {parameterDialogVisible && ( - - )} - + ); }; diff --git a/web/src/pages/next-search/embed-app-modal.tsx b/web/src/pages/next-search/embed-app-modal.tsx index 9907a636b..be99970dd 100644 --- a/web/src/pages/next-search/embed-app-modal.tsx +++ b/web/src/pages/next-search/embed-app-modal.tsx @@ -1,3 +1,4 @@ +import { useFetchTokenListBeforeOtherStep } from '@/components/embed-dialog/use-show-embed-dialog'; import HightLightMarkdown from '@/components/highlight-markdown'; import { Modal } from '@/components/ui/modal/modal'; import { RAGFlowSelect } from '@/components/ui/select'; @@ -9,7 +10,6 @@ import { import { useTranslate } from '@/hooks/common-hooks'; import { message } from 'antd'; import { useCallback, useEffect, useMemo, useState } from 'react'; -import { useFetchTokenListBeforeOtherStep } from '../agent/hooks/use-show-dialog'; type IEmbedAppModalProps = { open: any; diff --git a/web/src/routes.ts b/web/src/routes.ts index 563f12416..bd0d9fb59 100644 --- a/web/src/routes.ts +++ b/web/src/routes.ts @@ -38,6 +38,8 @@ export enum Routes { ResultView = `${Chunk}${Result}`, KnowledgeGraph = '/knowledge-graph', AgentLogPage = '/agent-log-page', + AgentShare = '/agent/share', + ChatShare = `${Chats}/share`, } const routes = [ @@ -57,8 +59,13 @@ const routes = [ layout: false, }, { - path: '/next-chat/share', - component: '@/pages/next-chats/share', + path: Routes.ChatShare, + component: `@/pages${Routes.ChatShare}`, + layout: false, + }, + { + path: Routes.AgentShare, + component: `@/pages${Routes.AgentShare}`, layout: false, }, { diff --git a/web/src/services/next-chat-service.ts b/web/src/services/next-chat-service.ts index 80a6e42a0..54aa066f6 100644 --- a/web/src/services/next-chat-service.ts +++ b/web/src/services/next-chat-service.ts @@ -28,6 +28,7 @@ const { getRelatedQuestions, listNextDialog, upload_and_parse, + fetchExternalChatInfo, } = api; const methods = { @@ -131,6 +132,10 @@ const methods = { method: 'post', url: upload_and_parse, }, + fetchExternalChatInfo: { + url: fetchExternalChatInfo, + method: 'get', + }, } as const; const chatService = registerNextServer(methods); diff --git a/web/src/utils/api.ts b/web/src/utils/api.ts index 1a57a6bbc..cbb0588b3 100644 --- a/web/src/utils/api.ts +++ b/web/src/utils/api.ts @@ -111,6 +111,8 @@ export default { // next chat listNextDialog: `${api_host}/dialog/next`, + fetchExternalChatInfo: (id: string) => + `${ExternalApi}${api_host}/chatbots/${id}/info`, // file manager listFile: `${api_host}/file/list`,