import useOverlayScroller from '@/hooks/use-overlay-scroller'; import { fetchChunkedData, readStreamData } from '@/utils/fetch-chunk-data'; import { useIntl, useSearchParams } from '@umijs/max'; import { Spin } from 'antd'; import classNames from 'classnames'; import _ from 'lodash'; import 'overlayscrollbars/overlayscrollbars.css'; import { forwardRef, memo, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'; import { CHAT_API } from '../apis'; import { Roles, generateMessages } from '../config'; import { MessageItem } from '../config/types'; import '../style/ground-left.less'; import '../style/system-message-wrap.less'; import MessageInput from './message-input'; import MessageContent from './multiple-chat/message-content'; import SystemMessage from './multiple-chat/system-message'; import ParamsSettings from './params-settings'; import ReferenceParams from './reference-params'; import ViewCodeModal from './view-code-modal'; interface MessageProps { modelList: Global.BaseOption[]; loaded?: boolean; ref?: any; } const GroundLeft: React.FC = forwardRef((props, ref) => { const { modelList } = props; const messageId = useRef(0); const [messageList, setMessageList] = useState([]); const intl = useIntl(); const [searchParams] = useSearchParams(); const selectModel = searchParams.get('model') || ''; const [parameters, setParams] = useState({}); const [systemMessage, setSystemMessage] = useState(''); const [show, setShow] = useState(false); const [loading, setLoading] = useState(false); const [tokenResult, setTokenResult] = useState(null); const [collapse, setCollapse] = useState(false); const contentRef = useRef(''); const controllerRef = useRef(null); const scroller = useRef(null); const currentMessageRef = useRef(null); const paramsRef = useRef(null); const messageListLengthCache = useRef(0); const { initialize, updateScrollerPosition } = useOverlayScroller(); const { initialize: innitializeParams } = useOverlayScroller(); useImperativeHandle(ref, () => { return { viewCode() { setShow(true); }, setCollapse() { setCollapse(!collapse); }, collapse: collapse }; }); const viewCodeMessage = useMemo(() => { return generateMessages([ { role: Roles.System, content: systemMessage }, ...messageList ]); }, [messageList, systemMessage]); const setMessageId = () => { messageId.current = messageId.current + 1; }; const handleNewMessage = (message?: { role: string; content: string }) => { const newMessage = message || { role: _.last(messageList)?.role === Roles.User ? Roles.Assistant : Roles.User, content: '' }; messageList.push({ ...newMessage, uid: messageId.current + 1 }); setMessageId(); setMessageList([...messageList]); }; const joinMessage = (chunk: any) => { setTokenResult({ ...(chunk?.usage ?? {}) }); if (!chunk || !_.get(chunk, 'choices', []).length) { return; } contentRef.current = contentRef.current + _.get(chunk, 'choices.0.delta.content', ''); setMessageList([ ...messageList, ...currentMessageRef.current, { role: Roles.Assistant, content: contentRef.current, uid: messageId.current } ]); }; const handleStopConversation = () => { controllerRef.current?.abort?.(); setLoading(false); }; const submitMessage = async (current?: { role: string; content: string }) => { if (!parameters.model) return; try { setLoading(true); setMessageId(); setTokenResult(null); controllerRef.current?.abort?.(); controllerRef.current = new AbortController(); const signal = controllerRef.current.signal; currentMessageRef.current = current ? [ { ...current, uid: messageId.current } ] : []; contentRef.current = ''; setMessageList((pre) => { return [...pre, ...currentMessageRef.current]; }); const messageParams = [ { role: Roles.System, content: systemMessage }, ...messageList, ...currentMessageRef.current ]; const messages = generateMessages(messageParams); const chatParams = { messages: messages, ...parameters, stream: true, stream_options: { include_usage: true } }; const result: any = await fetchChunkedData({ data: chatParams, url: CHAT_API, signal }); if (result?.error) { setTokenResult({ error: true, errorMessage: result?.data?.error?.message || result?.data?.message || '' }); return; } setMessageId(); const { reader, decoder } = result; await readStreamData(reader, decoder, (chunk: any) => { if (chunk?.error) { setTokenResult({ error: true, errorMessage: chunk?.error?.message || chunk?.message || '' }); return; } joinMessage(chunk); }); } catch (error) { // console.log('error:', error); } finally { setLoading(false); } }; const handleClear = () => { if (!messageList.length) { return; } setMessageId(); setMessageList([]); setTokenResult(null); }; const handleSendMessage = (message: Omit) => { console.log('message:', message); const currentMessage = message.content || message.imgs?.length ? message : undefined; submitMessage(currentMessage); }; const handleCloseViewCode = () => { setShow(false); }; const handleSelectModel = () => {}; const handlePresetPrompt = (list: { role: string; content: string }[]) => { const sysMsg = list.filter((item) => item.role === 'system'); const userMsg = list .filter((item) => item.role === 'user') .map((item) => { setMessageId(); return { ...item, uid: messageId.current }; }); setSystemMessage(sysMsg[0]?.content || ''); setMessageList(userMsg); }; useEffect(() => { if (scroller.current) { initialize(scroller.current); } }, [scroller.current, initialize]); useEffect(() => { if (paramsRef.current) { innitializeParams(paramsRef.current); } }, [paramsRef.current, innitializeParams]); useEffect(() => { if (loading) { updateScrollerPosition(); } }, [messageList, loading]); useEffect(() => { if (messageList.length > messageListLengthCache.current) { updateScrollerPosition(); } messageListLengthCache.current = messageList.length; }, [messageList.length]); return (
<>
{loading && (
)}
{tokenResult && (
)}
); }); export default memo(GroundLeft);