1
0

index.vue 24 KB

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