import AutoTooltip from '@/components/auto-tooltip'; import IconFont from '@/components/icon-font'; import useOverlayScroller from '@/hooks/use-overlay-scroller'; import { fetchChunkedData, readStreamData } from '@/utils/fetch-chunk-data'; import { ClearOutlined, DeleteOutlined, MoreOutlined, SettingOutlined } from '@ant-design/icons'; import { useIntl } from '@umijs/max'; import { Button, Checkbox, Dropdown, Popover, Select, Spin } from 'antd'; import _ from 'lodash'; import 'overlayscrollbars/overlayscrollbars.css'; import React, { forwardRef, useCallback, useContext, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'; import 'simplebar-react/dist/simplebar.min.css'; import { CHAT_API } from '../../apis'; import { OpenAIViewCode, Roles, generateMessages } from '../../config'; import CompareContext from '../../config/compare-context'; import { MessageItem, ModelSelectionItem } from '../../config/types'; import '../../style/model-item.less'; import ParamsSettings from '../params-settings'; import ReferenceParams from '../reference-params'; import ViewCodeModal from '../view-code-modal'; import MessageContent from './message-content'; import SystemMessage from './system-message'; interface ModelItemProps { model: string; modelList: ModelSelectionItem[]; instanceId: symbol; ref: any; } const ModelItem: React.FC = forwardRef( ({ model, modelList, instanceId }, ref) => { const { globalParams, setGlobalParams, setLoadingStatus, handleDeleteModel, handleApplySystemChangeToAll, modelFullList, loadingStatus } = useContext(CompareContext); const intl = useIntl(); const isApplyToAllModels = useRef(false); const [systemMessage, setSystemMessage] = useState(''); const [params, setParams] = useState>({ model: model }); const [loading, setLoading] = useState(false); const messageId = useRef(0); const [messageList, setMessageList] = useState([]); const [tokenResult, setTokenResult] = useState(null); const [show, setShow] = useState(false); const contentRef = useRef(''); const controllerRef = useRef(null); const currentMessageRef = useRef([]); const modelScrollRef = useRef(null); const messageListLengthCache = useRef(0); const { initialize, updateScrollerPosition } = useOverlayScroller(); const viewCodeMessage = useMemo(() => { return generateMessages([ { role: Roles.System, content: systemMessage }, ...messageList ]); }, [messageList, systemMessage]); const setMessageId = () => { messageId.current = messageId.current + 1; }; const abortFetch = () => { controllerRef.current?.abort?.(); setLoadingStatus(instanceId, false); }; 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 submitMessage = async (currentMessage?: Omit) => { if (!params.model) return; try { setLoadingStatus(instanceId, true); setMessageId(); controllerRef.current?.abort?.(); controllerRef.current = new AbortController(); const signal = controllerRef.current.signal; currentMessageRef.current = currentMessage ? [ { ...currentMessage, uid: messageId.current } ] : []; setMessageList((preList) => { return [...preList, ...currentMessageRef.current]; }); contentRef.current = ''; // ====== payload ================= const messageParams = [ { role: Roles.System, content: systemMessage }, ...messageList, ...currentMessageRef.current ]; const messages = generateMessages(messageParams); const chatParams = { messages: messages, ...params, stream: true, stream_options: { include_usage: true } }; // ============== payload end ================ 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 { setLoadingStatus(instanceId, false); } }; const handleDelete = () => { handleDeleteModel(instanceId); }; const handleSubmit = (currentMessage: Omit) => { const currentMsg = currentMessage.content || currentMessage.imgs?.length ? currentMessage : undefined; submitMessage(currentMsg); }; const handleApplyToAllModels = (e: any) => { isApplyToAllModels.current = e.target.checked; if (e.target.checked) { setGlobalParams({ ..._.omit(params, 'model') }); } }; const handleOnValuesChange = useCallback( (changeValues: any, allValues: Record) => { if (isApplyToAllModels.current) { setParams({ ...params, ...allValues }); setGlobalParams({ ...allValues }); } else { setParams({ ...params, ...changeValues }); } }, [params, isApplyToAllModels.current] ); const handleClearMessage = () => { setMessageList([]); setTokenResult(null); currentMessageRef.current = []; }; const addNewMessage = (message: Omit) => { setMessageId(); setMessageList((preList) => { return [ ...preList, { ...message, uid: messageId.current } ]; }); }; const handleCloseViewCode = () => { setShow(false); }; const handleModelChange = (value: string) => { setParams({ ...params, model: value }); handleClearMessage(); }; const handlePresetMessageList = (list: MessageItem[]) => { currentMessageRef.current = []; const messages = _.map(list, (item: Omit) => { setMessageId(); return { role: item.role, content: item.content, uid: messageId.current }; }); setTokenResult(null); setMessageList(messages); }; const modelOptions = useMemo(() => { return modelFullList.filter((item) => { return item.type !== 'empty'; }); }, [modelFullList]); const actionItems = useMemo(() => { const list = [ { label: intl.formatMessage({ id: 'common.button.clear' }), key: 'clear', icon: , danger: false, onClick: () => { setMessageList([]); setSystemMessage(''); } }, { label: intl.formatMessage({ id: 'playground.viewcode' }), key: 'viewcode', icon: , onClick: () => { setShow(true); } } ]; if (modelList.length > 2) { list.push({ label: intl.formatMessage({ id: 'common.button.delete' }), key: 'delete', icon: , danger: true, onClick: () => { handleDelete(); } }); } return list; }, [modelList, intl]); useEffect(() => { setParams({ ...params, ...globalParams }); }, [globalParams]); useEffect(() => { return () => { abortFetch(); }; }, []); useEffect(() => { if (modelScrollRef.current) { initialize(modelScrollRef.current); } }, [modelScrollRef.current, initialize]); useEffect(() => { if (loadingStatus[instanceId]) { updateScrollerPosition(); } }, [messageList]); useEffect(() => { if (messageList.length > messageListLengthCache.current) { updateScrollerPosition(); } messageListLengthCache.current = messageList.length; }, [messageList.length]); useImperativeHandle(ref, () => { return { submit: handleSubmit, abortFetch, addNewMessage, clear: handleClearMessage, presetPrompt: handlePresetMessageList, setSystemMessage, loading }; }); return (
} trigger={['click']} arrow={false} fresh={true} title={
{intl.formatMessage({ id: 'playground.compare.applytoall' })}
} >
); } ); export default React.memo(ModelItem);