index.vue 24 KB

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