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