import { ChatContext } from '@/app/chat-context'; import { apiInterceptors, getAppInfo, getChatHistory, getDialogueList, postChatModeParamsList } from '@/client/api'; import useUser from '@/hooks/use-user'; import { IApp } from '@/types/app'; import { ChatHistoryResponse } from '@/types/chat'; import { getUserId } from '@/utils'; import { HEADER_USER_ID_KEY } from '@/utils/constants/index'; import { EventStreamContentType, fetchEventSource } from '@microsoft/fetch-event-source'; import { useRequest } from 'ahooks'; import { Spin } from 'antd'; import dynamic from 'next/dynamic'; import { useSearchParams } from 'next/navigation'; import React, { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react'; import Header from './components/Header'; import InputContainer from './components/InputContainer'; const Content = dynamic(() => import('@/pages/mobile/chat/components/Content'), { ssr: false }); interface MobileChatProps { model: string; temperature: number; resource: any; setResource: React.Dispatch>; setTemperature: React.Dispatch>; setModel: React.Dispatch>; scene: string; history: ChatHistoryResponse; // 会话内容 setHistory: React.Dispatch>; scrollViewRef: React.RefObject; // 会话可滚动区域 appInfo: IApp; conv_uid: string; resourceList?: Record[]; order: React.MutableRefObject; handleChat: (_content?: string) => Promise; canAbort: boolean; setCarAbort: React.Dispatch>; canNewChat: boolean; setCanNewChat: React.Dispatch>; ctrl: React.MutableRefObject; userInput: string; setUserInput: React.Dispatch>; getChatHistoryRun: () => void; } export const MobileChatContext = createContext({ model: '', temperature: 0.5, resource: null, setModel: () => {}, setTemperature: () => {}, setResource: () => {}, scene: '', history: [], setHistory: () => {}, scrollViewRef: { current: null }, appInfo: {} as IApp, conv_uid: '', resourceList: [], order: { current: 1 }, handleChat: () => Promise.resolve(), canAbort: false, setCarAbort: () => {}, canNewChat: false, setCanNewChat: () => {}, ctrl: { current: undefined }, userInput: '', setUserInput: () => {}, getChatHistoryRun: () => {}, }); const MobileChat: React.FC = () => { // 从url上获取基本参数 const searchParams = useSearchParams(); const chatScene = searchParams?.get('chat_scene') ?? ''; const appCode = searchParams?.get('app_code') ?? ''; // 模型列表 const { modelList } = useContext(ChatContext); const [history, setHistory] = useState([]); const [model, setModel] = useState(''); const [temperature, setTemperature] = useState(0.5); const [resource, setResource] = useState(null); const scrollViewRef = useRef(null); // 用户输入 const [userInput, setUserInput] = useState(''); // 回复可以终止 const [canAbort, setCarAbort] = useState(false); // 是否可以开始新的提问,上一次回答结束或者暂停才可以开始新的提问 const [canNewChat, setCanNewChat] = useState(true); const ctrl = useRef(); const order = useRef(1); // 用户信息 const userInfo = useUser(); // 会话id const conv_uid = useMemo(() => `${userInfo?.user_no}_${appCode}`, [appCode, userInfo]); // 获取历史会话记录 const { run: getChatHistoryRun, loading: historyLoading } = useRequest( async () => await apiInterceptors(getChatHistory(`${userInfo?.user_no}_${appCode}`)), { manual: true, onSuccess: data => { const [, res] = data; const viewList = res?.filter(item => item.role === 'view'); if (viewList && viewList.length > 0) { order.current = viewList[viewList.length - 1].order + 1; } setHistory(res || []); }, }, ); // 获取应用信息 const { data: appInfo, run: getAppInfoRun, loading: appInfoLoading, } = useRequest( async (params: { chat_scene: string; app_code: string }) => { const [, res] = await apiInterceptors(getAppInfo(params)); return res ?? ({} as IApp); }, { manual: true, }, ); // 获取可选择的资源类型列表 const { run, data, loading: resourceLoading, } = useRequest( async () => { const [, res] = await apiInterceptors(postChatModeParamsList(chatScene)); setResource(res?.[0]?.space_id || res?.[0]?.param); return res ?? []; }, { manual: true, }, ); // 获取会话列表 const { run: getDialogueListRun, loading: dialogueListLoading } = useRequest( async () => { const [, res] = await apiInterceptors(getDialogueList()); return res ?? []; }, { manual: true, onSuccess: data => { const filterDialogue = data?.filter(item => item.conv_uid === conv_uid)?.[0]; filterDialogue?.select_param && setResource(JSON.parse(filterDialogue?.select_param)); }, }, ); // 获取应用信息 useEffect(() => { if (chatScene && appCode && modelList.length) { getAppInfoRun({ chat_scene: chatScene, app_code: appCode }); } }, [appCode, chatScene, getAppInfoRun, modelList]); // 设置历史会话记录 useEffect(() => { appCode && getChatHistoryRun(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [appCode]); // 设置默认模型 useEffect(() => { if (modelList.length > 0) { // 获取应用信息中的model值 const infoModel = appInfo?.param_need?.filter(item => item.type === 'model')?.[0]?.value; setModel(infoModel || modelList[0]); } }, [modelList, appInfo]); // 设置默认温度; useEffect(() => { // 获取应用信息中的model值 const infoTemperature = appInfo?.param_need?.filter(item => item.type === 'temperature')?.[0]?.value; setTemperature(infoTemperature || 0.5); }, [appInfo]); // 获取可选择资源列表 useEffect(() => { if (chatScene && appInfo?.app_code) { const resourceVal = appInfo?.param_need?.filter(item => item.type === 'resource')?.[0]?.value; const bindResourceVal = appInfo?.param_need?.filter(item => item.type === 'resource')?.[0]?.bind_value; bindResourceVal && setResource(bindResourceVal); ['database', 'knowledge', 'plugin', 'awel_flow'].includes(resourceVal) && !bindResourceVal && run(); } }, [appInfo, chatScene, run]); // 处理会话 const handleChat = async (content?: string) => { setUserInput(''); ctrl.current = new AbortController(); const params = { chat_mode: chatScene, model_name: model, user_input: content || userInput, conv_uid, temperature, app_code: appInfo?.app_code, ...(resource && { select_param: resource }), }; if (history && history.length > 0) { const viewList = history?.filter(item => item.role === 'view'); order.current = viewList[viewList.length - 1].order + 1; } const tempHistory: ChatHistoryResponse = [ { role: 'human', context: content || userInput, model_name: model, order: order.current, time_stamp: 0, }, { role: 'view', context: '', model_name: model, order: order.current, time_stamp: 0, thinking: true, }, ]; const index = tempHistory.length - 1; setHistory([...history, ...tempHistory]); setCanNewChat(false); try { await fetchEventSource(`${process.env.API_BASE_URL ?? ''}/api/v1/chat/completions`, { method: 'POST', headers: { 'Content-Type': 'application/json', [HEADER_USER_ID_KEY]: getUserId() ?? '', }, signal: ctrl.current.signal, body: JSON.stringify(params), openWhenHidden: true, async onopen(response) { if (response.ok && response.headers.get('content-type') === EventStreamContentType) { return; } }, onclose() { ctrl.current?.abort(); setCanNewChat(true); setCarAbort(false); }, onerror(err) { throw new Error(err); }, onmessage: event => { let message = event.data; try { message = JSON.parse(message).vis; } catch { message.replaceAll('\\n', '\n'); } if (message === '[DONE]') { setCanNewChat(true); setCarAbort(false); } else if (message?.startsWith('[ERROR]')) { tempHistory[index].context = message?.replace('[ERROR]', ''); tempHistory[index].thinking = false; setHistory([...history, ...tempHistory]); setCanNewChat(true); setCarAbort(false); } else { setCarAbort(true); tempHistory[index].context = message; tempHistory[index].thinking = false; setHistory([...history, ...tempHistory]); } }, }); } catch { ctrl.current?.abort(); tempHistory[index].context = 'Sorry, we meet some error, please try again later.'; tempHistory[index].thinking = false; setHistory([...tempHistory]); setCanNewChat(true); setCarAbort(false); } }; // 如果是原生应用,拉取会话列表获取资源参数 useEffect(() => { if (chatScene && chatScene !== 'chat_agent') { getDialogueListRun(); } }, [chatScene, getDialogueListRun]); return (
{appInfo?.app_code && }
); }; export default MobileChat;