bottomSider2.vue 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849
  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. @click="addNew"
  18. >
  19. <span
  20. class="btn-text-bg"
  21. :style="{
  22. backgroundImage: `url(${!isFold ? '/src/assets/images/vent/home/addB.svg' : ''})`,
  23. }"
  24. ></span>
  25. <span v-if="!isFold" class="btn-text">添加新对话</span>
  26. </div>
  27. <div class="divider0"></div>
  28. <div
  29. v-if="isFold"
  30. class="historyBtn"
  31. :style="{ backgroundImage: `url(${isFold ? '/src/assets/images/vent/home/history.svg' : '/src/assets/images/vent/home/history.svg'})` }"
  32. @click="addNew"
  33. ></div>
  34. <div v-else class="historyBtn1">
  35. <span
  36. class="btn-text-bg"
  37. :style="{
  38. backgroundImage: `url(${!isFold ? '/src/assets/images/vent/home/history.svg' : ''})`,
  39. }"
  40. ></span>
  41. <span v-if="!isFold" class="btn-text">历史对话</span>
  42. <a-list style="width: 110px" :split="false" :data-source="historySessions" :scroll="200" class="custom-list">
  43. <template #renderItem="{ item }">
  44. <a-list-item
  45. :style="{
  46. padding: '8px 10px 0 8px',
  47. color: '#5e7081',
  48. fontSize: '10px',
  49. position: 'relative', // 新增定位
  50. }"
  51. @click="sessionsHistory(item.id)"
  52. >
  53. <!-- 新增flex布局容器 -->
  54. <div style="display: flex; justify-content: space-between; width: 100%">
  55. <div v-if="editingId !== item.id" class="text-container">
  56. <span class="edit-text">{{ item.title || '新会话' }}</span>
  57. <edit-outlined class="edit-icon" @click="startEditing(item)" />
  58. </div>
  59. <!-- 输入框 -->
  60. <a-input
  61. size="small"
  62. v-else
  63. v-model:value="editText"
  64. v-focus
  65. @blur="handleSave(item)"
  66. @keyup.enter="handleSave(item)"
  67. class="edit-input"
  68. />
  69. </div>
  70. </a-list-item>
  71. </template>
  72. </a-list>
  73. </div>
  74. <div
  75. class="foldBtn"
  76. :style="{ backgroundImage: `url(${isFold ? '/src/assets/images/vent/home/Fold.svg' : '/src/assets/images/vent/home/unfold.svg'})` }"
  77. @click="fold"
  78. ></div>
  79. </div>
  80. <!-- 右侧对话框 -->
  81. <div class="right-side">
  82. <div class="input-content">
  83. <!-- 对话区域 -->
  84. <div class="dialog-area">
  85. <div v-for="message in sortedMessages" :key="message.id" :class="['message-item', message.type]">
  86. <!-- 用户提问样式 -->
  87. <div v-if="message.type === 'user'" class="ask-message">
  88. <span>{{ message.content }}</span>
  89. </div>
  90. <!-- 系统回答样式 -->
  91. <div v-else class="system-message">
  92. <div class="answerIcon"></div>
  93. <div class="answer-message">
  94. <div v-html="formatMessage(message.content)"></div>
  95. </div>
  96. </div>
  97. </div>
  98. </div>
  99. <!-- 文本输入区域 -->
  100. <div v-if="spinning" class="thinking-area">
  101. <span style="color: #fff">思考中···</span>
  102. <a-spin :spinning="spinning"></a-spin>
  103. </div>
  104. <div class="input-area">
  105. <textarea v-model="inputText" placeholder="请输入你的问题"> </textarea>
  106. <!-- 底部操作栏 -->
  107. <div class="action-bar">
  108. <!-- 左侧深度思考按钮 -->
  109. <div class="think-btn" :class="{ active: isThinking }" @click="toggleThinking"> <span>深度思考</span> </div>
  110. <!-- 右侧操作按钮 -->
  111. <div class="right-actions">
  112. <label class="upload-btn">
  113. <div class="send-file"></div>
  114. <span class="divider"> | </span>
  115. <div class="send-img"></div>
  116. <div class="send-btn" @click="handleSend"></div>
  117. </label>
  118. </div>
  119. </div>
  120. </div>
  121. </div>
  122. </div>
  123. </div>
  124. </transition>
  125. </div>
  126. </template>
  127. <script lang="ts" setup>
  128. import { ref, onMounted, unref, nextTick, computed } from 'vue';
  129. import { useUserStore } from '/@/store/modules/user';
  130. import { EditOutlined } from '@ant-design/icons-vue';
  131. // 响应式变量声明
  132. const dialogVisible = ref(false);
  133. const isFold = ref(false); // 是否折叠
  134. const inputText = ref(''); // 输入框内容
  135. const historySessions = ref([]); // 消会话历史
  136. const spinning = ref(false); // 加载状态
  137. const systemMessage = ref(''); // 系统返回信息
  138. const session_id = ref(''); // 会话id
  139. const hasCreated = ref(false); // 标志位,防止重复调用create接口
  140. const hasAdd = ref(false); // 标志位,防止重复调用create接口
  141. const userStore = useUserStore(); //获取用户信息
  142. const editingId = ref<number | null>(null);
  143. const editText = ref('');
  144. const isThinking = ref(false);
  145. interface ListItem {
  146. id: number;
  147. title?: string;
  148. }
  149. let userId = unref(userStore.getUserInfo).id;
  150. // const userId = ref(0);
  151. type MessageItem = {
  152. id: string; // 唯一标识(可用时间戳生成)
  153. type: 'user' | 'system';
  154. content: string;
  155. timestamp: number; // 排序依据
  156. };
  157. const messageList = ref<MessageItem[]>([]);
  158. const sortedMessages = computed(() => {
  159. return messageList.value.sort((a, b) => a.timestamp - b.timestamp);
  160. });
  161. const vFocus = {
  162. mounted: (el: HTMLElement) => el.querySelector('input')?.focus(),
  163. };
  164. const scrollToBottom = () => {
  165. const dialogArea = document.querySelector('.dialog-area');
  166. if (dialogArea) {
  167. dialogArea.scrollTop = dialogArea.scrollHeight;
  168. }
  169. };
  170. const openMenu = () => {
  171. dialogVisible.value = !dialogVisible.value;
  172. if (dialogVisible.value) {
  173. // addNew();
  174. hasCreated.value = true;
  175. }
  176. };
  177. const fold = () => {
  178. isFold.value = !isFold.value;
  179. if (!isFold.value) {
  180. sessionsHistoryList();
  181. }
  182. };
  183. //启用深度思考
  184. const toggleThinking = () => {
  185. isThinking.value = !isThinking.value;
  186. };
  187. //创建新对话
  188. async function addNew() {
  189. hasAdd.value = !hasAdd.value;
  190. const params = {
  191. user_id: userId,
  192. };
  193. let response = await fetch('http://182.92.126.35:6005/sessions/create', {
  194. method: 'post',
  195. headers: {
  196. 'Content-Type': 'application/json',
  197. },
  198. body: JSON.stringify(params),
  199. });
  200. const data = await response.json();
  201. session_id.value = data.id;
  202. messageList.value = [];
  203. }
  204. //编辑标题
  205. const startEditing = (item: ListItem) => {
  206. editingId.value = item.id;
  207. editText.value = item.title || '';
  208. };
  209. // 保存修改
  210. const handleSave = async (item: ListItem) => {
  211. const params = {
  212. chat_session_id: item.id,
  213. new_title: editText.value,
  214. };
  215. try {
  216. let response = await fetch('http://182.92.126.35:6005/sessions/change_title', {
  217. method: 'POST',
  218. headers: {
  219. 'Content-Type': 'application/json',
  220. },
  221. body: JSON.stringify(params),
  222. });
  223. if (!response.ok) {
  224. throw new Error('Network response was not ok');
  225. }
  226. item.title = editText.value;
  227. } catch (error) {
  228. console.error('保存失败:', error);
  229. }
  230. editingId.value = null;
  231. };
  232. //获取消息列表
  233. async function handleSend() {
  234. if (session_id.value === '') {
  235. await addNew();
  236. createSessionTitle({ session_id: session_id.value, title: inputText.value });
  237. sendMessage1();
  238. } else {
  239. createSessionTitle({ session_id: session_id.value, title: inputText.value });
  240. sendMessage1();
  241. }
  242. }
  243. //发送消息
  244. async function sendMessage() {
  245. spinning.value = true;
  246. // 添加用户消息
  247. messageList.value.push({
  248. id: `user_${Date.now()}`,
  249. type: 'user',
  250. content: inputText.value,
  251. timestamp: Date.now(),
  252. });
  253. const params = {
  254. chat_session_id: session_id.value,
  255. prompt: inputText.value,
  256. ref_file_ids: [],
  257. thinking_enabled: false,
  258. };
  259. inputText.value = ''; // 清空输入框
  260. //将用户输入的内容发送到后端
  261. try {
  262. // 将用户输入的内容发送到后端
  263. let response = await fetch('http://182.92.126.35:6005/chat', {
  264. method: 'POST',
  265. headers: {
  266. 'Content-Type': 'application/json',
  267. },
  268. body: JSON.stringify(params),
  269. });
  270. if (!response.ok) {
  271. throw new Error('Network response was not ok');
  272. }
  273. const data = await response.json();
  274. const assistantReply = data.reply.content; // 获取助手回复
  275. // formatMessage(assistantReply);
  276. systemMessage.value = assistantReply;
  277. // 添加系统回答
  278. messageList.value.push({
  279. id: `system_${Date.now()}`,
  280. type: 'system',
  281. content: systemMessage.value,
  282. timestamp: Date.now(),
  283. });
  284. } catch (error) {
  285. // 请求失败时设置系统消息为"服务器异常"
  286. systemMessage.value = '服务器异常';
  287. console.error('请求失败:', error);
  288. } finally {
  289. spinning.value = false; // 无论请求成功与否,都停止加载指示器
  290. }
  291. }
  292. //发送消息 流式响应
  293. const sendMessage1 = async () => {
  294. spinning.value = true; // 开始加载
  295. messageList.value.push({
  296. id: `user_${Date.now()}`,
  297. type: 'user',
  298. content: inputText.value,
  299. timestamp: Date.now(),
  300. });
  301. // 构造请求参数
  302. const params = {
  303. chat_session_id: session_id.value, // 替换为实际的会话 ID
  304. prompt: inputText.value,
  305. ref_file_ids: [],
  306. thinking_enabled: isThinking.value,
  307. };
  308. inputText.value = ''; // 清空输入框
  309. try {
  310. // 发送 POST 请求
  311. const response = await fetch('http://182.92.126.35:6005/chat_stream', {
  312. method: 'POST',
  313. headers: {
  314. 'Content-Type': 'application/json',
  315. },
  316. body: JSON.stringify(params),
  317. });
  318. // 检查响应是否成功
  319. if (!response.ok) {
  320. throw new Error('Network response was not ok');
  321. }
  322. // 获取可读流
  323. const reader = response.body.getReader();
  324. // 创建一条新的消息对象
  325. const newMessage = {
  326. id: `response_${Date.now()}`,
  327. type: 'response', // 消息类型
  328. content: '',
  329. timestamp: Date.now(), // 时间戳用来排序
  330. };
  331. // 将新消息添加到消息列表
  332. messageList.value.push(newMessage);
  333. // 读取流式数据
  334. while (true) {
  335. const { done, value } = await reader.read();
  336. if (done) {
  337. console.log('Stream complete');
  338. break;
  339. }
  340. // 将流数据转换为字符串
  341. const chunk = new TextDecoder().decode(value);
  342. console.log('Received chunk:', chunk);
  343. // 使用正则表达式匹配完整的 JSON 对象
  344. const jsonRegex = /{.*?}/g;
  345. const matches = chunk.match(jsonRegex);
  346. if (matches) {
  347. matches.forEach((match) => {
  348. try {
  349. const data = JSON.parse(match);
  350. if (data.type === 'text') {
  351. // 找到当前消息对象并更新 content
  352. const targetMessage = messageList.value.find((msg) => msg.id === newMessage.id);
  353. if (targetMessage) {
  354. targetMessage.content += data.content; // 追加内容
  355. scrollToBottom();
  356. }
  357. }
  358. } catch (error) {
  359. console.error('Failed to parse JSON:', error);
  360. }
  361. });
  362. }
  363. }
  364. } catch (error) {
  365. // 请求失败时设置系统消息
  366. if (!response || !response.ok) {
  367. systemMessage.value = '服务器异常';
  368. messageList.value.push({
  369. id: `system_${Date.now()}`,
  370. type: 'system',
  371. content: systemMessage.value,
  372. timestamp: Date.now(),
  373. });
  374. console.error('请求失败:', error);
  375. }
  376. } finally {
  377. spinning.value = false; // 停止加载
  378. }
  379. };
  380. //创建标题
  381. async function createSessionTitle({ session_id, title }) {
  382. const params = {
  383. chat_session_id: session_id,
  384. prompt: title,
  385. };
  386. let response = await fetch('http://182.92.126.35:6005/sessions/title', {
  387. method: 'post',
  388. headers: {
  389. 'Content-Type': 'application/json',
  390. },
  391. body: JSON.stringify(params),
  392. });
  393. const data = await response.json();
  394. }
  395. //获取会话历史
  396. async function sessionsHistoryList() {
  397. const params = {
  398. user_id: userId,
  399. };
  400. let response = await fetch(`http://182.92.126.35:6005/sessions`, {
  401. method: 'post',
  402. headers: {
  403. 'Content-Type': 'application/json',
  404. },
  405. body: JSON.stringify(params),
  406. });
  407. const data = await response.json();
  408. historySessions.value = data.chat_sessions;
  409. }
  410. //获取具体会话记录
  411. async function sessionsHistory(id: string) {
  412. let response = await fetch(`http://182.92.126.35:6005/sessions/history_chat/?chat_session_id=${id}`, {
  413. method: 'get',
  414. headers: {
  415. 'Content-Type': 'application/json',
  416. },
  417. });
  418. const data = await response.json();
  419. if (data.chat_messages.length > 0) {
  420. messageList.value = [];
  421. data.chat_messages.forEach((item: any) => {
  422. // role== user 用户提问
  423. if (item.role === 'user') {
  424. messageList.value.push({
  425. id: `user_${Date.now()}`,
  426. type: 'user',
  427. content: item.content,
  428. timestamp: Date.now(),
  429. });
  430. } else {
  431. // role== assistant 机器回答
  432. messageList.value.push({
  433. id: `system_${Date.now()}`,
  434. type: 'system',
  435. content: item.content,
  436. timestamp: Date.now(),
  437. });
  438. }
  439. });
  440. }
  441. }
  442. //格式化消息
  443. function formatMessage(text: string) {
  444. let formatted = text
  445. // 处理换行
  446. .replace(/\n\n/g, '<br>')
  447. .replace(/\n###/g, '<br> ')
  448. .replace(/###/g, '')
  449. .replace(/---/g, '')
  450. // 处理粗体
  451. .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
  452. // 处理斜体
  453. .replace(/\*(.*?)\*/g, '<em>$1</em>')
  454. // 处理行内代码
  455. .replace(/`([^`]+)`/g, '<code>$1</code>');
  456. return formatted;
  457. }
  458. // 初始化按钮定位
  459. onMounted(() => {
  460. sessionsHistoryList();
  461. });
  462. </script>
  463. <style lang="less" scoped>
  464. @keyframes menuShow {
  465. 0% {
  466. width: 0;
  467. height: 0;
  468. }
  469. 100% {
  470. width: 480px;
  471. height: 100vh;
  472. }
  473. }
  474. .custom-list {
  475. height: 360px;
  476. overflow-y: auto;
  477. }
  478. /* 穿透组件作用域 */
  479. ::v-deep .custom-list {
  480. scrollbar-width: thin;
  481. scrollbar-color: #1890ff #f0f0f0;
  482. &::-webkit-scrollbar {
  483. width: 4px;
  484. height: 6px;
  485. }
  486. &::-webkit-scrollbar-thumb {
  487. background: #1890ff;
  488. border-radius: 4px;
  489. }
  490. &::-webkit-scrollbar-track {
  491. background: #f0f0f0;
  492. border-radius: 4px;
  493. }
  494. }
  495. ::v-deep .zxm-list-items {
  496. color: #1890ff;
  497. }
  498. ::v-deep .zxm-list-item:hover {
  499. text-decoration: underline;
  500. color: #1890ff !important;
  501. }
  502. .text-container {
  503. display: flex;
  504. align-items: center;
  505. width: 100%;
  506. overflow: hidden;
  507. }
  508. .text-ellipsis {
  509. flex: 1;
  510. }
  511. .edit-text {
  512. overflow: hidden;
  513. text-overflow: ellipsis;
  514. white-space: nowrap;
  515. min-width: 0;
  516. }
  517. .edit-icon {
  518. flex-shrink: 0;
  519. cursor: pointer;
  520. margin-left: auto;
  521. }
  522. .edit-input {
  523. font-size: 10px;
  524. }
  525. .trigger-button {
  526. position: fixed;
  527. bottom: 10px;
  528. right: 10px;
  529. z-index: 1000000;
  530. .icon {
  531. width: 60px;
  532. height: 60px;
  533. position: relative;
  534. background-image: url('/@/assets/images/vent/home/wakeBtn.png');
  535. background-position: center;
  536. background-size: 100% 100%;
  537. }
  538. }
  539. .dialog-overlay {
  540. width: 32%;
  541. height: 55%;
  542. z-index: 999;
  543. display: flex;
  544. position: fixed;
  545. right: 90px;
  546. bottom: 20px;
  547. box-shadow: 0 0 3px 3px #1074c1;
  548. background-color: #09172c;
  549. }
  550. /* 遮罩层淡入淡出 */
  551. .fade-enter-active,
  552. .fade-leave-active {
  553. transition: opacity 0.3s;
  554. }
  555. .fade-enter-from,
  556. .fade-leave-to {
  557. opacity: 0;
  558. }
  559. /* 弹窗缩放动画 */
  560. .scale-enter-active,
  561. .scale-leave-active {
  562. transition: all 0.3s ease;
  563. }
  564. .scale-enter-from {
  565. transform: scale(0.5) translate(-50%, -50%);
  566. opacity: 0;
  567. }
  568. .scale-leave-to {
  569. transform: scale(1.2) translate(-50%, -50%);
  570. opacity: 0;
  571. }
  572. .left-side {
  573. background: #0c2842;
  574. transition: width 0.5s ease; /* 平滑过渡动画 */
  575. width: 120px; /* 展开时宽度 */
  576. position: relative; /* 用于按钮定位 */
  577. }
  578. .left-side.collapsed {
  579. width: 40px; /* 折叠时宽度 */
  580. }
  581. .addBtn {
  582. height: 30px;
  583. position: absolute;
  584. background-size: 100% 100%;
  585. background-position: center;
  586. padding: 2px;
  587. right: 10px;
  588. top: 10px;
  589. left: 10px;
  590. align-items: center;
  591. border-radius: 3px;
  592. cursor: pointer;
  593. }
  594. .btn-text-bg {
  595. width: 14px;
  596. height: 14px;
  597. position: absolute;
  598. background-size: 100% 100%;
  599. right: 10px;
  600. top: 9px;
  601. left: 10px;
  602. bottom: 10px;
  603. }
  604. .btn-text {
  605. margin-left: 3px;
  606. font-size: 12px;
  607. color: #fff;
  608. white-space: nowrap;
  609. margin-left: 30px;
  610. line-height: 26px;
  611. }
  612. .historyBtn {
  613. width: 20px;
  614. height: 20px;
  615. position: absolute;
  616. background-size: 100% 100%;
  617. background-position: center;
  618. padding: 2px;
  619. right: 10px;
  620. top: 100px;
  621. }
  622. .historyBtn1 {
  623. width: 20px;
  624. height: 20px;
  625. position: absolute;
  626. background-size: 100% 100%;
  627. background-position: center;
  628. left: 3px;
  629. top: 80px;
  630. }
  631. .divider0 {
  632. border-bottom: 1px solid #1074c1;
  633. width: auto;
  634. margin: 0 10px;
  635. height: 13%;
  636. display: block;
  637. background: transparent;
  638. }
  639. .foldBtn {
  640. width: 20px;
  641. height: 20px;
  642. position: absolute;
  643. background-size: 100% 100%;
  644. background-position: center;
  645. padding: 2px;
  646. right: 10px;
  647. bottom: 10px;
  648. cursor: pointer;
  649. }
  650. .right-side {
  651. flex: 1; /* 占据剩余空间 */
  652. background: #09172c;
  653. }
  654. .input-content {
  655. display: flex;
  656. flex-direction: column;
  657. justify-content: flex-end; /* 内容底部对齐 */
  658. height: 100%;
  659. padding: 20px; /* 统一内边距 */
  660. }
  661. .ask-message {
  662. align-self: flex-end;
  663. float: right;
  664. max-width: 70%;
  665. padding: 10px;
  666. margin: 10px;
  667. border-radius: 5px;
  668. color: #fff;
  669. background: #0c2842;
  670. align-self: flex-end; /* 右侧对齐‌:ml-citation{ref="2" data="citationList"} */
  671. }
  672. .answer {
  673. display: flex;
  674. flex-direction: row;
  675. }
  676. .answerIcon {
  677. flex-shrink: 0;
  678. margin-top: 10px;
  679. width: 35px;
  680. height: 35px;
  681. background-image: url('/@/assets/images/vent/home/answerIcon.svg');
  682. background-size: 100% 100%;
  683. }
  684. .answer-message {
  685. float: left;
  686. padding: 10px;
  687. margin: 10px;
  688. border-radius: 5px;
  689. background: #0c2842;
  690. }
  691. /** 系统返回信息**/
  692. .system-message {
  693. display: flex;
  694. flex-direction: row;
  695. align-self: flex-start;
  696. width: 100%;
  697. padding: 12px;
  698. display: flex;
  699. }
  700. .answerIcon {
  701. margin-top: 10px;
  702. width: 35px;
  703. height: 35px;
  704. background-image: url('/@/assets/images/vent/home/answerIcon.svg');
  705. background-size: 100% 100%;
  706. }
  707. .think-area {
  708. color: #7979799f;
  709. }
  710. .answer-area {
  711. color: #fff;
  712. }
  713. .dialog-area {
  714. flex: 1; /* 占据剩余空间 */
  715. gap: 50px; /* 消息块间隔统一控制 */
  716. overflow-y: auto; /* 垂直滚动条 */
  717. margin-bottom: 10px;
  718. }
  719. .loading-wrapper,
  720. .content-wrapper {
  721. min-height: 40px;
  722. }
  723. .message-item.user {
  724. margin-bottom: 50px;
  725. }
  726. .input-area {
  727. background-color: #043256 !important;
  728. display: flex;
  729. flex-direction: column;
  730. gap: 10px;
  731. }
  732. textarea {
  733. background-color: #043256 !important;
  734. width: 100%;
  735. height: 40px;
  736. border: none;
  737. resize: none;
  738. outline: none;
  739. overflow: hidden;
  740. padding: 10px; /* 统一内边距 */
  741. color: #fff;
  742. }
  743. .action-bar {
  744. display: flex;
  745. justify-content: space-between;
  746. align-items: center;
  747. padding: 8px 16px;
  748. }
  749. .think-btn {
  750. border: 1px solid #ccc;
  751. width: 120px;
  752. height: 20px;
  753. line-height: 20px;
  754. text-align: center;
  755. border-radius: 50px;
  756. cursor: pointer;
  757. background: white;
  758. transition: background 0.3s;
  759. }
  760. .think-btn.active {
  761. background: #1890ff;
  762. color: white;
  763. border-color: #1890ff;
  764. }
  765. .right-actions {
  766. display: flex;
  767. align-items: center;
  768. gap: 8px;
  769. }
  770. .upload-btn {
  771. display: flex;
  772. align-items: center;
  773. gap: 8px;
  774. }
  775. .upload-btn {
  776. float: right;
  777. display: flex;
  778. cursor: pointer;
  779. padding: 6px 12px;
  780. }
  781. .divider {
  782. color: #ccc;
  783. font-weight: 300;
  784. margin: 0 10px;
  785. }
  786. .send-file {
  787. width: 20px;
  788. height: 20px;
  789. background-image: url('/@/assets/images/vent/home/sendFile.svg');
  790. background-size: 100% 100%;
  791. border-radius: 4px;
  792. cursor: pointer;
  793. }
  794. .send-img {
  795. width: 20px;
  796. height: 20px;
  797. background-image: url('/@/assets/images/vent/home/sendImg.svg');
  798. background-size: 100% 100%;
  799. border-radius: 4px;
  800. cursor: pointer;
  801. }
  802. .send-btn {
  803. width: 20px;
  804. height: 20px;
  805. margin-left: 10px;
  806. margin-right: 10px;
  807. background-color: #1074c1;
  808. background-image: url('/@/assets/images/vent/home/send.svg');
  809. background-position: center;
  810. background-size: 100% 100%;
  811. border-radius: 2px;
  812. cursor: pointer;
  813. }
  814. </style>