import { forwardRef, useImperativeHandle, useState, useRef } from 'react' import { useTranslation } from 'react-i18next' import clsx from 'clsx' import { Input, Form } from 'antd' import { Space, Button } from 'antd' import ChatIcon from '@/assets/images/application/chat.png' import RbDrawer from '@/components/RbDrawer'; import VariableConfigModal from './VariableConfigModal' import { draftRun } from '@/api/application'; import Empty from '@/components/Empty' import ChatContent from '@/components/Chat/ChatContent' import type { ChatItem } from '@/components/Chat/types' import ChatSendIcon from '@/assets/images/application/chatSend.svg' import dayjs from 'dayjs' import type { ChatRef, VariableEditModalRef, StartVariableItem, GraphRef } from '../../types' import { type SSEMessage } from '@/utils/stream' const Chat = forwardRef(({ appId, graphRef }, ref) => { const { t } = useTranslation() const [form] = Form.useForm<{ message: string }>() const variableConfigModalRef = useRef(null) const [open, setOpen] = useState(false) const [loading, setLoading] = useState(false) const [chatList, setChatList] = useState([]) const [variables, setVariables] = useState([]) const [streamLoading, setStreamLoading] = useState(false) const handleOpen = () => { setOpen(true) getVariables() } const getVariables = () => { const nodes = graphRef.current?.getNodes() const list = nodes?.map(node => node.getData()) || [] const startNodes = list.filter(vo => vo.type === 'start') if (startNodes.length) { const curVariables = startNodes[0].config.variables?.defaultValue const initialValue: Record = {} curVariables.forEach((vo: StartVariableItem) => { if (vo.default) { initialValue[vo.name] = vo.default } }) setVariables(curVariables) form.setFieldsValue(initialValue) } } const handleClose = () => { setOpen(false) setChatList([]) } const handleEditVariables = () => { variableConfigModalRef.current?.handleOpen() } const handleSave = (values: StartVariableItem[]) => { setVariables([...values]) } const handleClusterSend = () => { if (loading || !appId) return setLoading(true) const message = form.getFieldValue('message') setChatList(prev => [...prev, { role: 'user', content: message, created_at: Date.now(), }]) setChatList(prev => [...prev, { role: 'assistant', content: message, created_at: Date.now(), }]) const handleStreamMessage = (data: SSEMessage[]) => { setStreamLoading(false) data.map(item => { const { chunk } = item.data as { chunk: string; }; switch(item.event) { case 'message': setChatList(prev => { const lastChat = { ...prev[prev.length - 1] } lastChat.content = lastChat.content + chunk return [ ...prev.slice(0, prev.length - 1), lastChat ] }) break case 'workflow_end': setStreamLoading(false); break; } }) }; const params: Record = {} if (variables.length > 0) { variables.forEach(vo => { params[vo.name] = vo.value ?? vo.defaultValue }) } form.setFieldValue('message', undefined) draftRun(appId, { message: message, variables: params, stream: true }, handleStreamMessage) .finally(() => { setLoading(false) }) } // 暴露给父组件的方法 useImperativeHandle(ref, () => ({ handleOpen, handleClose })); return ( {t('workflow.run')} {variables.length > 0 && } } classNames={{ body: 'rb:p-0!' }} open={open} onClose={handleClose} > } data={chatList} streamLoading={streamLoading} labelPosition="bottom" labelFormat={(item) => dayjs(item.created_at).locale('en').format('MMMM D, YYYY [at] h:mm A')} errorDesc={t('application.ReplyException')} />
) }) export default Chat