import React, { useState, useEffect, useRef, useMemo } from 'react';
import './index.less';
import { fetchEventSource } from '@microsoft/fetch-event-source';
import { getAccessToken, getCurrentOrganizationId } from 'hzero-front/lib/utils/utils';
import { getQuestionHistory, getTreeList, getRole, getTreeByOrgName, getTreeByUnitId } from "@/services/api-page";
import chatGPT from '@/assets/images/chatGPT.webp';
import user from '@/assets/images/user.webp';
import { Form, Tooltip, TreeSelect, useDataSet, Icon } from 'choerodon-ui/pro';
import { dataSet, optionDs } from "./store/index";
import { ButtonColor } from 'choerodon-ui/pro/lib/button/enum';
import { Button } from 'choerodon-ui/pro';
import { message } from 'hzero-ui';
import Search from 'choerodon-ui/lib/input/Search';
import { Input } from 'element-react';
import { Select } from 'element-react'
import 'element-theme-default';
const { TreeNode } = TreeSelect;
interface Message {errorTip: string | undefined;id: string;sender: 'user' | 'bot';text: string | undefined;files?: any[];fileName?: string; truncatedContent?: string; finish?: boolean;
}const ChatApp: React.FC = () => {const [messages, setMessages] = useState<Message[]>([]); const [userInput, setUserInput] = useState('');const [showButton, setShowButton] = useState(false)const showData = useDataSet(dataSet, []);const currentBotMessageRef = useRef<{truncatedContent: any;fileName: any; id: string; text: string; files?: any[];}>({id: '',text: '',files: [], truncatedContent: '',fileName: '',}); const [process, setProcess] = useState([]);const controllerRef = useRef(new AbortController());const [orgId, setOrgId] = useState<string>(); const [userId, setUserId] = useState('') const [selectValue, setSelectValue] = useState() const [selectionOptions, setSelectionOptions] = useState([]) const [isSelectVisible, setIsSelectVisible] = useState(false); const startSseWithPost = async (userMessageId: string) => {currentBotMessageRef.current = { id: `${userMessageId}-bot`, text: '' }; await fetchEventSource(`http://${IP.TEST}${BASIC.CWDMX_MODEL}/${tenantId}/knowledgeBase/getQuestionAnswer`, {method: 'POST',headers: {'Content-Type': 'application/json','Authorization': 'Bearer ' + getAccessToken(),},body: JSON.stringify({orgId: orgId,question: userInput,stream: true,}),signal: controllerRef.current.signal,onmessage(event) {try {if (event.data) {try {const messageData = JSON.parse(event.data)const process = messageData?.message?.features?.progress;setProcess(process);const content = messageData?.message?.content;currentBotMessageRef.current.text += content;if (messageData?.message?.features && messageData?.message?.features?.doc_citations) {let fileInfoString = '';messageData?.message?.features?.doc_citations.forEach(citation => {citation?.documents && citation?.documents.forEach((item) => {const fileName = item?.metadata?.nameconst truncatedContent = item?.metadata?.doc_ref?.content.length > 14 ? item.metadata?.doc_ref?.content.substring(0, 14) + '...' : item.metadata.doc_ref.content;const fileObject = {content: truncatedContent,url: item?.metadata?.doc_ref?.documentUrl};const files: any[] = currentBotMessageRef.current.files || [];files.push(fileObject)currentBotMessageRef.current.fileName = fileName;currentBotMessageRef.current.truncatedContent = truncatedContent;currentBotMessageRef?.current && (currentBotMessageRef.current.files = files)});})setMessages((prevMessages) =>prevMessages.map((msg) =>msg.id === currentBotMessageRef.current.id? {...msg,text: `${currentBotMessageRef.current.text}\n${fileInfoString}`,fileName: currentBotMessageRef.current.fileName,truncatedContent: currentBotMessageRef.current.truncatedContent,files: currentBotMessageRef.current.files,finish: messageData?.finish, errorTip: messageData?.error}: msg));} else {setMessages((prevMessages) =>prevMessages.map((msg) =>msg.id === currentBotMessageRef.current.id? {...msg, text: currentBotMessageRef.current.text,finish: messageData?.finish,errorTip: messageData?.error}: msg));}} catch (err) {console.error('Error parsing message data:', err);}}} catch (error) {message.error('获取失败');}},onerror(err) {console.error('SSE error:', err);throw (err)},onclose() {setShowButton(false)controllerRef.current.abort();},});};const handleSendMessage = () => {setShowButton(true)if (userInput.trim()) {const userMessageId = `${Date.now()}`; setMessages((prevMessages) => [...prevMessages,{ id: userMessageId, sender: 'user', text: userInput },{ id: `${userMessageId}-bot`, sender: 'bot', text: '' }, ]);startSseWithPost(userMessageId); setUserInput(''); }};const handleStopFetch = () => {if (controllerRef.current) {controllerRef.current.abort();}}const nodeCover = ({ record }) => {const nodeProps = {title: record.get('unitName'),};if (record.get('power') === 'false') {nodeProps.disabled = true;}return nodeProps;}const handleChange = (val) => {setOrgId(val)}const [displayFlag, setdisplayFlag] = useState('false') useEffect(() => {console.log('logfangwende第一次');const user = async () => {const res = await getRole();if (res) {setUserId(res?.user?.id)if (userId) {const res = await getTreeList({ guestId: userId, displayFlag: 'true' });setdisplayFlag(res?.data?.displayFlag)}}}user()}, [userId])useEffect(() => {console.log('logfangwende第二次');if (displayFlag === 'true') {optionDs.setQueryParameter("guestId", userId);optionDs.query()}}, [displayFlag])useEffect(() => {console.log('logfangwende第三次');const chatBox = document.querySelector('.chat-box');if (chatBox) {chatBox.scrollTop = chatBox.scrollHeight;}}, [messages]);useEffect(() => {console.log('logfangwende第四次');const fetchData = async () => {try {const res = await getQuestionHistory();res.data.forEach((item) => {if (item?.direction === 1) {setMessages((prevMessages) => [...prevMessages,{ id: item?.mesTime, sender: 'user', text: item?.content, },]);} else if (item?.direction === 2) {const fileName = item?.ref?.[0]?.title;const files: any[] = []item?.ref?.forEach((item) => {if (item?.content) {if (item?.content.length > 14) {const fileObject = {content: item?.content.substring(0, 14) + '...',url: item?.docUrl};files.push(fileObject);}}})setMessages((prevMessages) => [...prevMessages,{ id: `${item?.mesTime}-bot`, sender: 'bot', text: item?.content, fileName: fileName, files: files, finish: true }, ]);}})} catch (error) {console.error('Error fetching data:', error);}};fetchData();}, []);const selectChange = (val) => {if (!val) returnsetIsSelectVisible(false)setSelectValue(val)showData.loadData([{ unitName: val, unitCode: val }])}const handleEnter = async (value) => {setSelectionOptions([])if (value !== '') {const params = { guestId: userId, unitName: value }const result = await getTreeByOrgName(params)if (Array.isArray(result.data)) {const newArray = result.data.map(item => {return {value: item.unitCode,label: item.unitName}})setSelectionOptions(newArray)}}}const handleIconClick = () => {setSelectValue(undefined)setIsSelectVisible(true);setSelectionOptions([])};const handleVisibleChange = (isVisible) => {if (!isVisible) {setIsSelectVisible(false);}};console.log(displayFlag, 'displayFlag');return (<div className="chat-container"><div className="+">{displayFlag === 'true' && <div className='search-box'>{isSelectVisible ? (<Selectfilterable={true}remote={true}remoteMethod={(value) => { handleEnter(value) }}value={selectValue}onChange={selectChange}clearable={true}placeholder="请输入单位名称"onVisibleChange={handleVisibleChange}>{selectionOptions.map((el, index) => {return <Select.Option key={el?.value} label={el.label} value={el.value} />})}</Select>) : (<><TreeSelectplaceholder="请选择公司名称"name="unitName"dataSet={showData}onOption={nodeCover}onChange={handleChange}style={{ marginRight: '7px' }}popupCls={'down_select'}/><Icon type="search" onClick={handleIconClick} style={{ cursor: 'pointer', fontSize: '20px' }} /></>)}</div>}{messages.map((msg) => (<div key={msg.id} className={`message-wrapper ${msg.sender}`}><div className="avatar"><img src={msg.sender === 'user' ? user : chatGPT} alt="avatar" /></div><div className="message">{msg.errorTip ?? msg.text ?? '...'}{msg.sender === 'bot' && msg.fileName && msg.finish === true && <div ><br />文档来源:{msg.fileName}<br /></div>}{msg.files && msg.finish === true && msg.files?.map((item, index) => {const url = `https://${item?.url}`return <div key={index}>{item?.content} <a href={url} target="_blank">查看文档</a></div>})}</div></div>))}</div><div className='tip'><div className='process'>当前进度:{process}</div>{showButton ? <p className='stop' onClick={handleStopFetch}>停止生成</p> : ''}</div><div className="input-box"><inputtype="text"value={userInput}onChange={(e) => setUserInput(e.target.value)}onKeyDown={(e) => e.key === 'Enter' && handleSendMessage()}placeholder="请输入消息"/>{<Tooltip title="请选择单位名称"><Button color="primary" disabled={displayFlag === 'true' && !orgId} onClick={handleSendMessage}>发送</Button></Tooltip>}</div></div>);
};export default ChatApp;
.chat-container {display: flex;flex-direction: column;height: 100vh; width: 100%;background-color: rgb(230, 230, 230);font-family: Arial, sans-serif;overflow: auto;:global {.el-select .el-input__inner {height: 27px;width: 183px;margin-right: 27px;font-size: 12px;border-color: #e6e6e6;border-radius: 0;}}
}.chat-box {flex: 1; overflow-y: auto; padding: 16px;display: flex;flex-direction: column;gap: 8px;background-color: #ffffff;
}.message-wrapper {display: flex;gap: 10px;
}.message-wrapper.user {flex-direction: row-reverse;
}.message-wrapper.bot {justify-content: flex-start;
}.message {padding: 10px 14px;border-radius: 12px;background-color: #007bff;color: #ffffff;word-break: break-word;white-space: pre-wrap;
}.message-wrapper.bot .message {background-color: #f1f1f1;color: #333333;
}.search-box {position: sticky;top: 0; margin: 10px;margin-left: auto; z-index: 9999; background-color: #007bff;padding: 5px;border-radius: 4px;display: flex;align-items: center;justify-content: flex-end;
}.tip {display: flex;justify-content: space-between;.process {color: #999;font-size: 16px;margin: 10px;margin-left: 20px;}.stop {color: #0078d4;font-size: 16px;margin: 10px;cursor: pointer;margin-right: 20px;}
}.input-box {position: sticky;bottom: 0; display: flex;justify-content: space-between;padding: 20px;background-color: #fff;border-top: 1px solid #e0e0e0;
}.input-box input {flex: 1;padding: 4px;border-radius: 20px;border: 1px solid #ccc;margin-right: 14px;font-size: 14px;
}.avatar {width: 40px;height: 40px;border-radius: 50%;overflow: hidden;margin-right: 10px;
}.avatar img {width: 100%;height: 100%;object-fit: cover;
}:global {.el-select-dropdown {top: 33px !important;}.down_select {.c7n-tree-treenode {.c7n-tree-node-content-wrapper {.c7n-tree-title {color: #007bff !important;}}}.c7n-tree-treenode-disabled {.c7n-tree-node-content-wrapper {.c7n-tree-title {color: #d9d9d9 !important;}}}}.aipage_form {.c7n-pro-field-label {width: 35px !important;color: #333333 !important;}}
}