bottomSider2.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  1. <template>
  2. <div>
  3. <div class="trigger-button">
  4. <div class="icon" @click="openMenu"></div>
  5. </div>
  6. <transition name="fade">
  7. <div v-if="dialogVisible" class="dialog-overlay">
  8. <!-- 左侧折叠区域 -->
  9. <div class="left-side" :class="{ collapsed: isFold }" id="leftSide">
  10. <div
  11. class="addBtn"
  12. :style="{
  13. backgroundImage: `url(${isFold ? '/src/assets/images/vent/home/add.svg' : ''})`,
  14. backgroundColor: isFold ? '' : '#2cb6ff',
  15. width: isFold ? '20px' : 'auto',
  16. }"
  17. >
  18. <span
  19. class="btn-text-bg"
  20. :style="{
  21. backgroundImage: `url(${!isFold ? '/src/assets/images/vent/home/addB.svg' : ''})`,
  22. }"
  23. ></span>
  24. <span v-if="!isFold" class="btn-text" @click="addNew">添加新对话</span>
  25. </div>
  26. <div class="divider0"></div>
  27. <div
  28. v-if="isFold"
  29. class="historyBtn"
  30. :style="{ backgroundImage: `url(${isFold ? '/src/assets/images/vent/home/history.svg' : '/src/assets/images/vent/home/history.svg'})` }"
  31. @click="addNew"
  32. ></div>
  33. <div v-else class="historyBtn1">
  34. <span
  35. class="btn-text-bg"
  36. :style="{
  37. backgroundImage: `url(${!isFold ? '/src/assets/images/vent/home/history.svg' : ''})`,
  38. }"
  39. ></span>
  40. <span v-if="!isFold" class="btn-text">历史对话</span>
  41. <a-list style="width: 100px" :split="false">
  42. <a-list-item style="padding: 5px 0 0 30px; color: #5e7081; font-size: 12px; white-space: nowrap; text-overflow: ellipsis"
  43. >历史数据1</a-list-item
  44. >
  45. <a-list-item style="padding: 5px 0 0 30px; color: #5e7081; font-size: 12px; white-space: nowrap; text-overflow: ellipsis"
  46. >历史数据12</a-list-item
  47. >
  48. <a-list-item style="padding: 5px 0 0 30px; color: #5e7081; font-size: 12px; white-space: nowrap; text-overflow: ellipsis"
  49. >历史数据123</a-list-item
  50. >
  51. </a-list>
  52. </div>
  53. <div
  54. class="foldBtn"
  55. :style="{ backgroundImage: `url(${isFold ? '/src/assets/images/vent/home/Fold.svg' : '/src/assets/images/vent/home/unfold.svg'})` }"
  56. @click="fold"
  57. ></div>
  58. </div>
  59. <!-- 右侧对话框 -->
  60. <div class="right-side">
  61. <div class="input-content">
  62. <!-- 对话区域 -->
  63. <div class="dialog-area">
  64. <div v-for="message in sortedMessages" :key="message.id" :class="['message-item', message.type]">
  65. <!-- 用户提问样式 -->
  66. <div v-if="message.type === 'user'" class="ask-message">
  67. <span>{{ message.content }}</span>
  68. </div>
  69. <!-- 系统回答样式 -->
  70. <div v-else class="system-message">
  71. <div class="answerIcon"></div>
  72. <div class="answer-message">
  73. <!-- <div>
  74. <span>{{ message.content }}</span>
  75. </div> -->
  76. <div v-html="formatMessage(message.content)"></div>
  77. </div>
  78. </div>
  79. </div>
  80. </div>
  81. <!-- 文本输入区域 -->
  82. <div v-if="spinning" class="thinking-area">
  83. <span style="color: #fff">思考中···</span>
  84. <a-spin :spinning="spinning"></a-spin>
  85. </div>
  86. <div class="input-area">
  87. <textarea v-model="inputText" placeholder="请输入你的问题"> </textarea>
  88. <!-- 底部操作栏 -->
  89. <div class="action-bar">
  90. <!-- 文件上传按钮 -->
  91. <label class="upload-btn">
  92. <div class="send-file" />
  93. <span class="divider"> | </span>
  94. <div class="send-img" />
  95. </label>
  96. <!-- 发送按钮 -->
  97. <div class="send-btn" @click="handleSend"></div>
  98. </div>
  99. </div>
  100. </div>
  101. </div>
  102. </div>
  103. </transition>
  104. </div>
  105. </template>
  106. <script lang="ts" setup>
  107. import { ref, onMounted, nextTick, computed } from 'vue';
  108. // 响应式变量声明
  109. const dialogVisible = ref(false);
  110. const isFold = ref(false); // 是否折叠
  111. const inputText = ref(''); // 输入框内容
  112. // const messages = ref([]); // 消息列表
  113. const spinning = ref(false); // 加载状态
  114. const systemMessage = ref(''); // 系统返回信息
  115. const session_id = ref(''); // 会话id
  116. const hasCreated = ref(false); // 标志位,防止重复调用create接口
  117. type MessageItem = {
  118. id: string; // 唯一标识(可用时间戳生成)
  119. type: 'user' | 'system';
  120. content: string;
  121. timestamp: number; // 排序依据
  122. };
  123. const messageList = ref<MessageItem[]>([]);
  124. const sortedMessages = computed(() => {
  125. return messageList.value.sort((a, b) => a.timestamp - b.timestamp);
  126. });
  127. // const scrollToBottom = () => {
  128. // nextTick(() => {
  129. // const container = document.querySelector('.dialog-area');
  130. // container.scrollTop = container.scrollHeight;
  131. // });
  132. // };
  133. const openMenu = () => {
  134. dialogVisible.value = !dialogVisible.value;
  135. if (dialogVisible.value) {
  136. hasCreated.value = true;
  137. addNew();
  138. }
  139. };
  140. const fold = () => {
  141. isFold.value = !isFold.value;
  142. };
  143. async function addNew() {
  144. let response = await fetch('http://182.92.126.35:6005/sessions/create', {
  145. method: 'post',
  146. headers: {
  147. 'Content-Type': 'application/json',
  148. },
  149. });
  150. const data = await response.json();
  151. session_id.value = data.session_id;
  152. }
  153. //获取消息列表
  154. async function handleSend() {
  155. spinning.value = true;
  156. // 添加用户消息
  157. messageList.value.push({
  158. id: `user_${Date.now()}`,
  159. type: 'user',
  160. content: inputText.value,
  161. timestamp: Date.now(),
  162. });
  163. // 调用接口获取答案
  164. // const answer = await fetchAnswerFromAPI(question);
  165. const params = {
  166. chat_session_id: session_id.value,
  167. prompt: inputText.value,
  168. ref_file_ids: [],
  169. thinking_enabled: false,
  170. };
  171. inputText.value = ''; // 清空输入框
  172. //将用户输入的内容发送到后端
  173. try {
  174. // 将用户输入的内容发送到后端
  175. let response = await fetch('http://182.92.126.35:6005/chat', {
  176. method: 'POST',
  177. headers: {
  178. 'Content-Type': 'application/json',
  179. },
  180. body: JSON.stringify(params),
  181. });
  182. if (!response.ok) {
  183. throw new Error('Network response was not ok');
  184. }
  185. const data = await response.json();
  186. const assistantReply = data.reply; // 获取助手回复
  187. formatMessage(assistantReply);
  188. systemMessage.value = assistantReply;
  189. // 添加系统回答
  190. messageList.value.push({
  191. id: `system_${Date.now()}`,
  192. type: 'system',
  193. content: systemMessage.value,
  194. timestamp: Date.now(),
  195. });
  196. } catch (error) {
  197. // 请求失败时设置系统消息为"服务器异常"
  198. systemMessage.value = '服务器异常';
  199. console.error('请求失败:', error);
  200. } finally {
  201. spinning.value = false; // 无论请求成功与否,都停止加载指示器
  202. }
  203. }
  204. //格式化消息
  205. function formatMessage(text: string) {
  206. let formatted = text
  207. // 处理换行
  208. .replace(/\n\n/g, '<br>')
  209. .replace(/\n###/g, '<br> ')
  210. .replace(/###/g, '')
  211. .replace(/---/g, '')
  212. // 处理粗体
  213. .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
  214. // 处理斜体
  215. .replace(/\*(.*?)\*/g, '<em>$1</em>')
  216. // 处理行内代码
  217. .replace(/`([^`]+)`/g, '<code>$1</code>');
  218. return formatted;
  219. }
  220. // 初始化按钮定位
  221. onMounted(() => {});
  222. </script>
  223. <style lang="less" scoped>
  224. @keyframes menuShow {
  225. 0% {
  226. width: 0;
  227. height: 0;
  228. }
  229. 100% {
  230. width: 480px;
  231. height: 100vh;
  232. }
  233. }
  234. .trigger-button {
  235. position: fixed;
  236. bottom: 10px;
  237. right: 10px;
  238. z-index: 1000000;
  239. .icon {
  240. width: 60px;
  241. height: 60px;
  242. position: relative;
  243. background-image: url('/@/assets/images/vent/home/wakeBtn.png');
  244. background-position: center;
  245. background-size: 100% 100%;
  246. }
  247. }
  248. .dialog-overlay {
  249. width: 32%;
  250. height: 55%;
  251. z-index: 999;
  252. display: flex;
  253. position: fixed;
  254. right: 90px;
  255. bottom: 20px;
  256. box-shadow: 0 0 3px 3px #1074c1;
  257. background-color: #09172c;
  258. }
  259. /* 遮罩层淡入淡出 */
  260. .fade-enter-active,
  261. .fade-leave-active {
  262. transition: opacity 0.3s;
  263. }
  264. .fade-enter-from,
  265. .fade-leave-to {
  266. opacity: 0;
  267. }
  268. /* 弹窗缩放动画 */
  269. .scale-enter-active,
  270. .scale-leave-active {
  271. transition: all 0.3s ease;
  272. }
  273. .scale-enter-from {
  274. transform: scale(0.5) translate(-50%, -50%);
  275. opacity: 0;
  276. }
  277. .scale-leave-to {
  278. transform: scale(1.2) translate(-50%, -50%);
  279. opacity: 0;
  280. }
  281. .left-side {
  282. background: #0c2842;
  283. transition: width 0.5s ease; /* 平滑过渡动画 */
  284. width: 120px; /* 展开时宽度 */
  285. position: relative; /* 用于按钮定位 */
  286. }
  287. .left-side.collapsed {
  288. width: 40px; /* 折叠时宽度 */
  289. }
  290. .addBtn {
  291. height: 30px;
  292. position: absolute;
  293. background-size: 100% 100%;
  294. background-position: center;
  295. padding: 2px;
  296. right: 10px;
  297. top: 10px;
  298. left: 10px;
  299. align-items: center;
  300. border-radius: 3px;
  301. cursor: pointer;
  302. }
  303. .btn-text-bg {
  304. width: 14px;
  305. height: 14px;
  306. position: absolute;
  307. background-size: 100% 100%;
  308. right: 10px;
  309. top: 9px;
  310. left: 10px;
  311. bottom: 10px;
  312. }
  313. .btn-text {
  314. margin-left: 3px;
  315. font-size: 12px;
  316. color: #fff;
  317. white-space: nowrap;
  318. margin-left: 30px;
  319. line-height: 26px;
  320. }
  321. .historyBtn {
  322. width: 20px;
  323. height: 20px;
  324. position: absolute;
  325. background-size: 100% 100%;
  326. background-position: center;
  327. padding: 2px;
  328. right: 10px;
  329. top: 100px;
  330. }
  331. .historyBtn1 {
  332. width: 20px;
  333. height: 20px;
  334. position: absolute;
  335. background-size: 100% 100%;
  336. background-position: center;
  337. left: 3px;
  338. top: 80px;
  339. }
  340. .divider0 {
  341. border-bottom: 1px solid #1074c1;
  342. width: auto;
  343. margin: 0 10px;
  344. height: 13%;
  345. display: block;
  346. background: transparent;
  347. }
  348. .foldBtn {
  349. width: 20px;
  350. height: 20px;
  351. position: absolute;
  352. background-size: 100% 100%;
  353. background-position: center;
  354. padding: 2px;
  355. right: 10px;
  356. bottom: 10px;
  357. cursor: pointer;
  358. }
  359. .right-side {
  360. flex: 1; /* 占据剩余空间 */
  361. background: #09172c;
  362. }
  363. .input-content {
  364. display: flex;
  365. flex-direction: column;
  366. justify-content: flex-end; /* 内容底部对齐 */
  367. height: 100%;
  368. padding: 20px; /* 统一内边距 */
  369. }
  370. .ask-message {
  371. align-self: flex-end;
  372. float: right;
  373. max-width: 70%;
  374. padding: 10px;
  375. margin: 10px;
  376. border-radius: 5px;
  377. color: #fff;
  378. background: #0c2842;
  379. align-self: flex-end; /* 右侧对齐‌:ml-citation{ref="2" data="citationList"} */
  380. }
  381. .answer {
  382. display: flex;
  383. flex-direction: row;
  384. }
  385. .answerIcon {
  386. flex-shrink: 0;
  387. margin-top: 10px;
  388. width: 35px;
  389. height: 35px;
  390. background-image: url('/@/assets/images/vent/home/answerIcon.svg');
  391. background-size: 100% 100%;
  392. }
  393. .answer-message {
  394. float: left;
  395. padding: 10px;
  396. margin: 10px;
  397. border-radius: 5px;
  398. color: #fff;
  399. background: #0c2842;
  400. }
  401. /** 系统返回信息**/
  402. .system-message {
  403. display: flex;
  404. flex-direction: row;
  405. align-self: flex-start;
  406. width: 100%;
  407. padding: 12px;
  408. display: flex;
  409. }
  410. .answerIcon {
  411. margin-top: 10px;
  412. width: 35px;
  413. height: 35px;
  414. background-image: url('/@/assets/images/vent/home/answerIcon.svg');
  415. background-size: 100% 100%;
  416. }
  417. .answer-message {
  418. float: left;
  419. width: 100%;
  420. padding: 10px;
  421. margin: 10px;
  422. border-radius: 5px;
  423. color: #fff;
  424. background: #0c2842;
  425. }
  426. .dialog-area {
  427. flex: 1; /* 占据剩余空间 */
  428. gap: 50px; /* 消息块间隔统一控制 */
  429. overflow-y: auto; /* 垂直滚动条 */
  430. margin-bottom: 10px;
  431. }
  432. .loading-wrapper,
  433. .content-wrapper {
  434. min-height: 40px; /* 避免高度塌陷 */
  435. }
  436. .message-item.user {
  437. margin-bottom: 50px;
  438. }
  439. .input-area {
  440. background-color: #043256 !important;
  441. display: flex;
  442. flex-direction: column;
  443. gap: 10px;
  444. }
  445. textarea {
  446. background-color: #043256 !important;
  447. width: 100%;
  448. height: 40px;
  449. border: none;
  450. resize: none;
  451. outline: none;
  452. overflow: hidden;
  453. padding: 10px; /* 统一内边距 */
  454. color: #fff;
  455. }
  456. .action-bar {
  457. display: flex;
  458. align-items: center;
  459. gap: 12px;
  460. }
  461. .upload-btn {
  462. display: flex;
  463. cursor: pointer;
  464. padding: 6px 12px;
  465. }
  466. .divider {
  467. color: #ccc;
  468. font-weight: 300;
  469. margin: 0 10px;
  470. }
  471. .send-file {
  472. width: 20px;
  473. height: 20px;
  474. background-image: url('/@/assets/images/vent/home/sendFile.svg');
  475. background-size: 100% 100%;
  476. border-radius: 4px;
  477. cursor: pointer;
  478. }
  479. .send-img {
  480. width: 20px;
  481. height: 20px;
  482. background-image: url('/@/assets/images/vent/home/sendImg.svg');
  483. background-size: 100% 100%;
  484. border-radius: 4px;
  485. cursor: pointer;
  486. }
  487. .send-btn {
  488. width: 20px;
  489. height: 20px;
  490. margin-left: auto;
  491. margin-right: 10px;
  492. background-color: #1074c1;
  493. background-image: url('/@/assets/images/vent/home/send.svg');
  494. background-position: center;
  495. background-size: 100% 100%;
  496. border-radius: 2px;
  497. cursor: pointer;
  498. }
  499. </style>