import React, { useState, useRef, useEffect } from 'react';
|
import { Input, Button, Spin } from '@arco-design/web-react';
|
import * as $$ from '@/utils/utility';
|
import { IconSend } from '@arco-design/web-react/icon';
|
const { TextArea } = Input;
|
|
const SESSION_KEY = 'ai_conversation_id';
|
const MSG_KEY = 'ai_chat_messages';
|
|
const AiChat = ({ user = 'system', onClose, initialQuery = '', visible = false }) => {
|
// 初始化时从sessionStorage恢复消息
|
const [messages, setMessages] = useState(() => {
|
try {
|
const msgStr = sessionStorage.getItem(MSG_KEY);
|
return msgStr ? JSON.parse(msgStr) : [];
|
} catch {
|
return [];
|
}
|
});
|
const [input, setInput] = useState('');
|
const [loading, setLoading] = useState(false);
|
const scrollRef = useRef(null);
|
const [conversationId, setConversationId] = useState(() => sessionStorage.getItem(SESSION_KEY) || '');
|
const [typing, setTyping] = useState(false);
|
const [hasInitialized, setHasInitialized] = useState(false);
|
|
// 每次messages变化时保存到sessionStorage
|
useEffect(() => {
|
sessionStorage.setItem(MSG_KEY, JSON.stringify(messages));
|
// 每次消息变化时自动滚动到底部
|
if (scrollRef.current) {
|
setTimeout(() => {
|
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
}, 50);
|
}
|
}, [messages]);
|
|
// 初始化时如果有initialQuery,自动发送
|
useEffect(() => {
|
console.log('AiChat useEffect - initialQuery:', initialQuery, 'hasInitialized:', hasInitialized);
|
if (initialQuery && !hasInitialized) {
|
console.log('准备自动发送initialQuery:', initialQuery);
|
setHasInitialized(true);
|
setInput(initialQuery);
|
// 延迟一下确保组件完全加载
|
setTimeout(() => {
|
console.log('开始自动发送initialQuery:', initialQuery);
|
handleSend(initialQuery);
|
}, 100);
|
}
|
}, [initialQuery, hasInitialized]);
|
|
// 弹窗显示时自动滚动到底部
|
useEffect(() => {
|
if (visible && scrollRef.current) {
|
setTimeout(() => {
|
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
}, 100);
|
}
|
}, [visible]);
|
|
// 关闭时不清除会话id和消息
|
// useEffect(() => {
|
// return () => {
|
// sessionStorage.removeItem(SESSION_KEY);
|
// sessionStorage.removeItem(MSG_KEY);
|
// };
|
// }, []);
|
|
// 发送消息
|
const handleSend = async (customInput = null) => {
|
const query = customInput || input.trim();
|
if (!query) return;
|
|
setMessages([...messages, { role: 'user', text: query }]);
|
// 无论是否为自定义输入,都清空输入框
|
setInput('');
|
setLoading(true);
|
|
// 请求AI接口
|
const res = await $$.ax.request({
|
url: 'ai/chat/chat',
|
type: 'post',
|
service: 'sys',
|
data: {
|
inputs: {},
|
response_mode: 'blocking',
|
user,
|
query,
|
conversation_id: conversationId || '',
|
},
|
});
|
|
setLoading(false);
|
|
if (res.type && res.data && res.data.answer) {
|
// 打字机效果
|
let answer = res.data.answer;
|
let i = 0;
|
setTyping(true);
|
setMessages(msgs => [...msgs, { role: 'ai', text: '' }]);
|
const typeWriter = () => {
|
setMessages(msgs => {
|
const newMsgs = [...msgs];
|
newMsgs[newMsgs.length - 1] = { role: 'ai', text: answer.slice(0, i + 1) };
|
return newMsgs;
|
});
|
i++;
|
// 每次打字后自动滚动到底部
|
setTimeout(() => {
|
if (scrollRef.current) {
|
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
}
|
}, 0);
|
if (i < answer.length) {
|
setTimeout(typeWriter, 15); // 速度可调
|
} else {
|
setTyping(false);
|
// 结束后再滚动一次
|
setTimeout(() => {
|
if (scrollRef.current) {
|
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
}
|
}, 0);
|
}
|
};
|
typeWriter();
|
|
// 首次返回有conversationId则保存
|
if (res.data.conversationId && !conversationId) {
|
setConversationId(res.data.conversationId);
|
sessionStorage.setItem(SESSION_KEY, res.data.conversationId);
|
}
|
}
|
};
|
|
// 回车发送
|
const handleKeyDown = (e) => {
|
if (e.key === 'Enter' && !e.shiftKey) {
|
e.preventDefault();
|
handleSend();
|
}
|
};
|
|
// 关闭对话框时清除会话id
|
const handleClose = () => {
|
// sessionStorage.removeItem(SESSION_KEY);
|
// setConversationId('');
|
if (onClose) onClose();
|
};
|
|
return (
|
<div style={{ width: '60vw', height: '80vh', margin: '40px', display: 'flex', flexDirection: 'column', background: '#fff', borderRadius: 8, boxShadow: '0 2px 16px #ddd', overflow: 'hidden' }}>
|
{/* 顶部标题栏 */}
|
<div style={{ height: 48, background: '#fff', display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '0 24px', borderTopLeftRadius: 8, borderTopRightRadius: 8, borderBottom: '1px solid #f0f0f0' }}>
|
<span style={{ color: '#222', fontWeight: 500, fontSize: 16, letterSpacing: 1 }}>解纷数智人</span>
|
<span
|
onClick={handleClose}
|
style={{
|
width: 28,
|
height: 28,
|
borderRadius: '50%',
|
background: '#f0f1f2',
|
display: 'flex',
|
alignItems: 'center',
|
justifyContent: 'center',
|
cursor: 'pointer',
|
color: '#6b7280',
|
fontSize: 18,
|
transition: 'background 0.2s',
|
}}
|
onMouseOver={e => (e.currentTarget.style.background = '#e5e7eb')}
|
onMouseOut={e => (e.currentTarget.style.background = '#f0f1f2')}
|
>
|
×
|
</span>
|
</div>
|
{/* 对话内容区 */}
|
<div
|
ref={scrollRef}
|
style={{
|
flex: 1,
|
overflowY: 'auto',
|
padding: 32,
|
borderBottom: '1px solid #f0f0f0',
|
}}
|
>
|
{messages.map((msg, idx) => (
|
<div
|
key={idx}
|
style={{
|
marginBottom: 18,
|
display: 'flex',
|
justifyContent: 'flex-start',
|
}}
|
>
|
<div
|
style={{
|
background: msg.role === 'user' ? '#e6f7ff' : '#f7f8fa',
|
color: '#1a6fb8',
|
borderRadius: 8,
|
padding: '10px 20px',
|
maxWidth: '95%',
|
minWidth: 60,
|
wordBreak: 'break-all',
|
textAlign: 'left',
|
boxShadow: msg.role === 'ai' ? '0 2px 8px #f0f1f2' : 'none',
|
}}
|
>
|
{msg.text}
|
</div>
|
</div>
|
))}
|
{loading && (
|
<div style={{ textAlign: 'left', marginBottom: 18 }}>
|
<Spin />
|
</div>
|
)}
|
</div>
|
{/* 优化后的输入区 */}
|
<div style={{
|
background: '#fff',
|
borderRadius: 16,
|
boxShadow: '0 2px 8px #f0f1f2',
|
padding: 20,
|
margin: 24,
|
display: 'flex',
|
alignItems: 'flex-end',
|
minHeight: 80
|
}}>
|
<TextArea
|
value={input}
|
onChange={setInput}
|
onKeyDown={handleKeyDown}
|
placeholder="您想咨询什么..."
|
autoSize={{ minRows: 2, maxRows: 4 }}
|
style={{
|
border: 'none',
|
outline: 'none',
|
resize: 'none',
|
background: 'transparent',
|
fontSize: 16,
|
flex: 1,
|
boxShadow: 'none',
|
padding: '12px 0 12px 8px',
|
borderRadius: 12,
|
minHeight: 48
|
}}
|
/>
|
<Button
|
type="primary"
|
icon={<IconSend />}
|
onClick={handleSend}
|
disabled={loading || !input.trim()}
|
style={{
|
marginLeft: 16,
|
borderRadius: 8,
|
height: 40,
|
minWidth: 64,
|
fontSize: 16,
|
display: 'flex',
|
alignItems: 'center',
|
justifyContent: 'center'
|
}}
|
>
|
发送
|
</Button>
|
</div>
|
</div>
|
);
|
};
|
|
export default AiChat;
|