From bdeacb9f02dfa74bac74296a4a2c989a8e0d45ff Mon Sep 17 00:00:00 2001 From: xusd <330628789@qq.com> Date: Thu, 26 Jun 2025 17:52:12 +0800 Subject: [PATCH] feature:AI演示 --- src/views/register/visit/component/AiChat.jsx | 273 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 273 insertions(+), 0 deletions(-) diff --git a/src/views/register/visit/component/AiChat.jsx b/src/views/register/visit/component/AiChat.jsx new file mode 100644 index 0000000..a25a4e8 --- /dev/null +++ b/src/views/register/visit/component/AiChat.jsx @@ -0,0 +1,273 @@ +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; -- Gitblit v1.8.0