You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

492 lines
13 KiB

3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
  1. <script setup>
  2. import { ref, nextTick, watch, onMounted } from 'vue'
  3. import { useUserInfo } from '../store/userPermissionCode'
  4. import axios from 'axios'
  5. import { ElMessage } from 'element-plus';
  6. import { Loading, Position } from '@element-plus/icons-vue'; // 引入图标组件
  7. const { getQueryVariable } = useUserInfo()
  8. // 假设 getSessionId 已经正确定义
  9. const { getSessionId } = useUserInfo()
  10. const isTokenValid = ref(false)
  11. const fnGetToken = () => {
  12. localStorage.setItem('localToken', decodeURIComponent(String(getQueryVariable('token'))))
  13. console.log(localStorage.getItem('localToken'));
  14. }
  15. setTimeout(() => {
  16. fnGetToken()
  17. }, 800)
  18. const token = localStorage.getItem('localToken')
  19. // 验证 token
  20. const validateToken = async () => {
  21. const token = localStorage.getItem('localToken')
  22. console.log('token',token);
  23. if (!token) {
  24. console.error('未找到 token,请重新登录')
  25. return false
  26. }
  27. return true
  28. }
  29. // 创建新对话
  30. const sessionId = ref(localStorage.getItem('sessionId') || {});
  31. const add = async () => {
  32. try {
  33. const result = await axios.post(
  34. "http://t3zf7v.natappfree.cc/api/v1/chats/8b37cd9cf0c811efa4210242ac120003/completions",
  35. {},
  36. {
  37. headers: {
  38. 'Content-Type': 'application/json',
  39. Authorization: 'Bearer ragflow-hkNjEwYjcwZjBlMDExZWZiYjYzMDI0Mm'
  40. }
  41. }
  42. );
  43. const data = result.data;
  44. console.log(data);
  45. console.log(data, 'data11111111');
  46. // 判断 data 是否为空
  47. if (Object.keys(data).length === 0) {
  48. return;
  49. }
  50. // 假设 getSessionId 函数已经正确定义
  51. const sss = String(getSessionId("session_id", data));
  52. sessionId.value = sss;
  53. localStorage.setItem('sessionId', sss);
  54. console.log('sss', sss);
  55. } catch (error) {
  56. console.log("请求失败", error);
  57. ElMessage.error("添加失败,请检查输入内容是否正确");
  58. }
  59. };
  60. // 页面加载时验证 token
  61. validateToken().then((isValid) => {
  62. isTokenValid.value = isValid
  63. if (isValid) {
  64. console.log('Token 验证成功')
  65. if (!localStorage.getItem('sessionId')) {
  66. console.log('没有sessionId,创建新对话')
  67. add()
  68. }
  69. }
  70. })
  71. // 定义Props(可配置参数)
  72. const props = defineProps({
  73. apiUrl: {
  74. type: String,
  75. default: 'http://localhost:5000/ask'
  76. },
  77. initialGreeting: {
  78. type: String,
  79. default: '您好!我是夺宝奇兵ai智能客服,有什么可以帮助您的?'
  80. }
  81. })
  82. // 响应式数据
  83. const messages = ref([
  84. {
  85. content: props.initialGreeting,
  86. sender: 'bot',
  87. timestamp: new Date()
  88. }
  89. ])
  90. const inputMessage = ref('')
  91. const messageContainer = ref(null)
  92. const isLoading = ref(false) // 新增:加载状态
  93. // 自动滚动到底部
  94. const scrollToBottom = () => {
  95. nextTick(() => {
  96. if (messageContainer.value) {
  97. messageContainer.value.scrollTop = messageContainer.value.scrollHeight
  98. }
  99. })
  100. }
  101. // 发送消息
  102. const sendMessage = async () => {
  103. if (!isTokenValid.value) {
  104. console.error('Token 验证失败,无法发送消息')
  105. return
  106. }
  107. if (isLoading.value) return;
  108. const content = inputMessage.value.trim()
  109. if (!content) return
  110. // 添加用户消息
  111. messages.value.push({
  112. content,
  113. sender: 'user',
  114. timestamp: new Date()
  115. })
  116. // 清空输入框
  117. inputMessage.value = ''
  118. scrollToBottom()
  119. // 显示加载动画
  120. isLoading.value = true
  121. messages.value.push({
  122. content: '我正在思考...',
  123. sender: 'bot',
  124. timestamp: new Date(),
  125. isLoading: true
  126. })
  127. scrollToBottom()
  128. try {
  129. // 调用API获取回复
  130. const response = await fetch("http://t3zf7v.natappfree.cc/api/v1/chats/8b37cd9cf0c811efa4210242ac120003/completions", {
  131. method: 'POST',
  132. headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ragflow-hkNjEwYjcwZjBlMDExZWZiYjYzMDI0Mm' },
  133. body: JSON.stringify({ question: content, stream: false, session_id: sessionId.value })
  134. })
  135. const data = await response.json()
  136. // 移除加载消息
  137. messages.value = messages.value.filter(msg => !msg.isLoading)
  138. // 提取推理思考部分和结果部分
  139. const regex = /<think>(.*?)<\/think>(.*)/s;
  140. const match = data.data.answer.match(regex);
  141. let thinking = '';
  142. let result = data.data.answer;
  143. if (match) {
  144. thinking = match[1].trim();
  145. result = match[2].trim();
  146. }
  147. // 组合推理思考内容和结果内容,并添加 HTML 标签
  148. let combinedContent = '';
  149. // if (thinking) {
  150. // combinedContent += `<span class="thinking-content">推理思考:${thinking}</span><br>`;
  151. // }
  152. combinedContent += `<span class="result-content">${result}</span>`;
  153. // 添加包含推理思考和结果的消息
  154. messages.value.push({
  155. content: combinedContent,
  156. sender: 'bot',
  157. timestamp: new Date()
  158. })
  159. scrollToBottom()
  160. } catch (error) {
  161. console.error('API请求失败:', error)
  162. // 移除加载消息
  163. messages.value = messages.value.filter(msg => !msg.isLoading)
  164. messages.value.push({
  165. content: '服务暂时不可用,请稍后再试',
  166. sender: 'bot',
  167. timestamp: new Date()
  168. })
  169. scrollToBottom()
  170. } finally {
  171. // 隐藏加载动画
  172. isLoading.value = false
  173. }
  174. }
  175. // 格式化时间显示
  176. const formatTime = (date) => {
  177. return new Date(date).toLocaleTimeString([], {
  178. hour: '2-digit',
  179. minute: '2-digit'
  180. })
  181. }
  182. // 自适应输入框高度
  183. const adjustInputHeight = () => {
  184. const textarea = document.querySelector('.message-input')
  185. textarea.style.height = 'auto'
  186. textarea.style.height = `${textarea.scrollHeight}px`
  187. }
  188. // 监听输入内容变化
  189. watch(inputMessage, adjustInputHeight)
  190. // 在组件挂载后按顺序执行操作
  191. onMounted(async () => {
  192. // 先获取 token
  193. fnGetToken()
  194. // 再验证 token
  195. const isValid = await validateToken()
  196. isTokenValid.value = isValid
  197. if (!isValid) {
  198. console.error('Token 验证失败,请重新登录')
  199. }
  200. })
  201. </script>
  202. <template>
  203. <!-- 聊天容器 -->
  204. <div class="chat-container">
  205. <!-- 聊天框头部 -->
  206. <div class="chat-header">夺宝奇兵智能客服</div>
  207. <!-- 消息展示区域 -->
  208. <div class="message-list" ref="messageContainer">
  209. <div v-for="(message, index) in messages" :key="index" class="message-item" :class="[message.sender]">
  210. <!-- 机器人头像 -->
  211. <div v-if="message.sender === 'bot'" class="bot-avatar">
  212. <img src="/src/assets/img/avatar/超级云脑按钮.png" alt="Bot Avatar">
  213. </div>
  214. <div class="message-bubble">
  215. <div class="message-content" v-html="message.content">
  216. </div>
  217. <div class="message-time">{{ formatTime(message.timestamp) }}</div>
  218. </div>
  219. <!-- 用户头像 -->
  220. <div v-if="message.sender === 'user'" class="user-avatar">
  221. <img src="/src/assets/img/avatar/小柒.png" alt="User Avatar">
  222. </div>
  223. </div>
  224. </div>
  225. <!-- 输入区域 -->
  226. <div class="input-area">
  227. <textarea v-model="inputMessage" @keydown.enter.exact.prevent="isLoading ? null : sendMessage()"
  228. placeholder="输入您的问题..." rows="1" class="message-input"></textarea>
  229. <el-tooltip content="机器人正在思考" :disabled="!isLoading">
  230. <template #content>
  231. 机器人正在思考
  232. </template>
  233. <button @click="sendMessage" :disabled="!isTokenValid || isLoading" class="send-button">
  234. <!-- 使用ElementPlus的发送图标 -->
  235. <span v-if="isLoading">
  236. <el-icon class="is-loading">
  237. <Loading />
  238. </el-icon>
  239. </span>
  240. <span v-else class="send-button-content">
  241. <el-icon>
  242. <Position />
  243. </el-icon>
  244. <span> 发送</span>
  245. </span>
  246. </button>
  247. </el-tooltip>
  248. </div>
  249. <!-- 未登录覆盖层 -->
  250. <div v-if="!isTokenValid" class="overlay">
  251. <div class="overlay-content">用户未登录</div>
  252. </div>
  253. </div>
  254. </template>
  255. <style scoped>
  256. /* 使用更具体的选择器 */
  257. .message-bubble .message-content .thinking-content {
  258. color: #c8c4c4;
  259. font-style: italic;
  260. }
  261. /* 其他样式保持不变 */
  262. .message-item.bot {
  263. justify-content: flex-start; /* 让机器人消息靠左对齐 */
  264. }
  265. .chat-container {
  266. display: flex;
  267. flex-direction: column;
  268. height: 90vh;
  269. width: 90vw;
  270. max-width: 800px;
  271. margin: 0;
  272. border: 1px solid #e0e0e0;
  273. border-radius: 12px;
  274. background: #f8f9fa;
  275. overflow: hidden;
  276. /* 新增样式,实现水平和垂直居中 */
  277. position: absolute;
  278. top: 50%;
  279. left: 50%;
  280. transform: translate(-50%, -50%);
  281. }
  282. /* 聊天框头部样式 */
  283. .chat-header {
  284. background-color: #007bff;
  285. color: white;
  286. padding: 16px;
  287. font-size: 1.2rem;
  288. text-align: center;
  289. }
  290. .message-list {
  291. flex: 1;
  292. padding: 20px;
  293. overflow-y: auto;
  294. background: white;
  295. }
  296. .message-item {
  297. display: flex;
  298. margin-bottom: 16px;
  299. }
  300. .message-item.user {
  301. justify-content: flex-end;
  302. }
  303. .bot-avatar {
  304. margin-right: 10px;
  305. }
  306. .bot-avatar img {
  307. width: 40px;
  308. height: 40px;
  309. border-radius: 50%;
  310. object-fit: cover;
  311. }
  312. .user-avatar {
  313. margin-left: 10px;
  314. }
  315. .user-avatar img {
  316. width: 40px;
  317. height: 40px;
  318. border-radius: 50%;
  319. object-fit: cover;
  320. }
  321. .message-bubble {
  322. max-width: 80%;
  323. padding: 12px 16px;
  324. border-radius: 15px;
  325. position: relative;
  326. }
  327. .message-content span {
  328. display: block;
  329. /* 确保元素显示 */
  330. }
  331. .message-item.user .message-bubble {
  332. background: #007bff;
  333. color: white;
  334. border-bottom-right-radius: 4px;
  335. }
  336. .message-item.bot .message-bubble {
  337. background: #f1f3f5;
  338. color: #212529;
  339. border-bottom-left-radius: 4px;
  340. }
  341. .message-time {
  342. font-size: 0.75rem;
  343. color: rgba(255, 255, 255, 0.8);
  344. margin-top: 4px;
  345. text-align: right;
  346. }
  347. .message-item.bot .message-time {
  348. color: rgba(0, 0, 0, 0.6);
  349. }
  350. .input-area {
  351. display: flex;
  352. align-items: center;
  353. gap: 12px;
  354. padding: 16px;
  355. border-top: 1px solid #e0e0e0;
  356. background: white;
  357. }
  358. .message-input {
  359. flex: 1;
  360. padding: 10px 16px;
  361. border: 1px solid #e0e0e0;
  362. border-radius: 20px;
  363. resize: none;
  364. max-height: 120px;
  365. font-family: inherit;
  366. }
  367. .send-button {
  368. display: flex;
  369. align-items: center;
  370. justify-content: center;
  371. width: 100px;
  372. /* 调整宽度以适应文字 */
  373. height: 40px;
  374. border: none;
  375. border-radius: 20px;
  376. /* 调整圆角 */
  377. background: #007bff;
  378. color: white;
  379. cursor: pointer;
  380. transition: all 0.3s ease;
  381. /* 添加过渡效果 */
  382. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
  383. /* 添加阴影 */
  384. font-size: 16px;
  385. /* 调整字体大小 */
  386. font-weight: 600;
  387. /* 调整字体粗细 */
  388. }
  389. .send-button:hover {
  390. background: #0056b3;
  391. transform: translateY(-2px);
  392. /* 悬停时向上移动 */
  393. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
  394. /* 悬停时增加阴影 */
  395. }
  396. /* 新增加载状态样式 */
  397. .loading-state {
  398. background: #ccc;
  399. cursor: not-allowed;
  400. }
  401. .send-button-content {
  402. display: flex;
  403. align-items: center;
  404. justify-content: center;
  405. gap: 8px;
  406. /* 调整文字和图标间距 */
  407. }
  408. /* .send-button {
  409. display: flex;
  410. align-items: center;
  411. justify-content: center;
  412. width: 40px;
  413. height: 40px;
  414. border: none;
  415. border-radius: 50%;
  416. background: #007bff;
  417. color: white;
  418. cursor: pointer;
  419. transition: background 0.2s;
  420. }
  421. .send-button:hover {
  422. background: #0056b3;
  423. } */
  424. .send-button svg {
  425. width: 20px;
  426. height: 20px;
  427. }
  428. .overlay {
  429. position: absolute;
  430. top: 0;
  431. left: 0;
  432. width: 100%;
  433. height: 100%;
  434. background-color: rgba(255, 255, 255, 0.8);
  435. /* 透明度 50% 的白色背景 */
  436. display: flex;
  437. justify-content: center;
  438. align-items: center;
  439. z-index: 1;
  440. /* 确保覆盖层在聊天框上方 */
  441. }
  442. .overlay-content {
  443. background-color: white;
  444. padding: 20px;
  445. border-radius: 8px;
  446. box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
  447. text-align: center;
  448. }
  449. </style>