|
@@ -1,56 +1,135 @@
|
|
|
<!-- eslint-disable vue/no-v-html -->
|
|
<!-- eslint-disable vue/no-v-html -->
|
|
|
<template>
|
|
<template>
|
|
|
- <div class="mini-chat">
|
|
|
|
|
|
|
+ <div class="btn" @click="showAIChat">
|
|
|
|
|
+ <div style="display: flex; flex-direction: row" class="btn-header">
|
|
|
|
|
+ <img src="@/assets/images/vent/home/wakeBtn.png" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div v-if="isShowChatBroad" class="mini-chat">
|
|
|
<!-- 左侧折叠区域 -->
|
|
<!-- 左侧折叠区域 -->
|
|
|
- <div class="left-side">
|
|
|
|
|
- <SvgIcon name="add" size="20" />
|
|
|
|
|
- <Popover trigger="click" :overlay-inner-style="{ padding: '1px' }">
|
|
|
|
|
- <template #content>
|
|
|
|
|
- <AIChat style="width: 700px; height: 500px" :visible="true" />
|
|
|
|
|
- </template>
|
|
|
|
|
- <SvgIcon :name="dialogVisible ? 'zoom-out' : 'zoom-in'" size="20" @click="openDialog" />
|
|
|
|
|
- </Popover>
|
|
|
|
|
|
|
+ <div class="left-side" :class="{ collapsed: isFold }" id="leftSide">
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-if="isFold"
|
|
|
|
|
+ class="historyBtn"
|
|
|
|
|
+ :style="{ backgroundImage: `url(${isFold ? '/src/assets/images/vent/home/history.svg' : '/src/assets/images/vent/home/history.svg'})` }"
|
|
|
|
|
+ @click="addNew"
|
|
|
|
|
+ ></div>
|
|
|
|
|
+ <div v-else class="historyBtn1">
|
|
|
|
|
+ <span
|
|
|
|
|
+ class="btn-text-bg"
|
|
|
|
|
+ :style="{
|
|
|
|
|
+ backgroundImage: `url(${!isFold ? '/src/assets/images/vent/home/history.svg' : ''})`,
|
|
|
|
|
+ }"
|
|
|
|
|
+ ></span>
|
|
|
|
|
+ <span v-if="!isFold" class="btn-text">历史对话</span>
|
|
|
|
|
+ <a-list style="width: 130px" :split="false" :data-source="sessionHistory" :scroll="200" class="custom-list">
|
|
|
|
|
+ <template #renderItem="{ item }">
|
|
|
|
|
+ <a-list-item
|
|
|
|
|
+ class="session-item"
|
|
|
|
|
+ :style="{
|
|
|
|
|
+ padding: '8px 10px 0 8px',
|
|
|
|
|
+ color: '#5e7081',
|
|
|
|
|
+ fontSize: '10px',
|
|
|
|
|
+ position: 'relative', // 新增定位
|
|
|
|
|
+ }"
|
|
|
|
|
+ >
|
|
|
|
|
+ <!-- 新增flex布局容器 -->
|
|
|
|
|
+ <div style="width: 100%">
|
|
|
|
|
+ <div v-if="editingId !== item.id" class="text-container">
|
|
|
|
|
+ <span class="edit-text" @click="sessionsHistory(item.id)">{{ item.name }}</span>
|
|
|
|
|
+ <div class="btn-container">
|
|
|
|
|
+ <EditOutlined class="edit-icon" @click="startEditing(item)" />
|
|
|
|
|
+ <DeleteOutlined class="delete-icon" @click="startDelete(item)" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <!-- 输入框 -->
|
|
|
|
|
+ <a-input
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ v-else
|
|
|
|
|
+ v-model:value="editText"
|
|
|
|
|
+ v-focus
|
|
|
|
|
+ @blur="handleSave(item)"
|
|
|
|
|
+ @keyup.enter="handleSave(item)"
|
|
|
|
|
+ class="edit-input"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </a-list-item>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </a-list>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div
|
|
|
|
|
+ class="foldBtn"
|
|
|
|
|
+ :style="{ backgroundImage: `url(${isFold ? '/src/assets/images/vent/home/Fold.svg' : '/src/assets/images/vent/home/unfold.svg'})` }"
|
|
|
|
|
+ @click="fold"
|
|
|
|
|
+ ></div>
|
|
|
</div>
|
|
</div>
|
|
|
<!-- 右侧对话框 -->
|
|
<!-- 右侧对话框 -->
|
|
|
<div class="right-side">
|
|
<div class="right-side">
|
|
|
<!-- 对话区域 -->
|
|
<!-- 对话区域 -->
|
|
|
- <div ref="dialogRef" class="dialog-area">
|
|
|
|
|
|
|
+ <div class="dialog-area">
|
|
|
<div
|
|
<div
|
|
|
- v-for="message in store.getMessageHistory"
|
|
|
|
|
|
|
+ v-for="message in messageHistory"
|
|
|
:key="message.id"
|
|
:key="message.id"
|
|
|
class="flex items-center w-100%"
|
|
class="flex items-center w-100%"
|
|
|
:style="{ alignSelf: message.type === 'user' ? 'flex-end' : 'flex-start' }"
|
|
:style="{ alignSelf: message.type === 'user' ? 'flex-end' : 'flex-start' }"
|
|
|
>
|
|
>
|
|
|
<template v-if="message.type === 'user'">
|
|
<template v-if="message.type === 'user'">
|
|
|
<div class="flex-grow-1"></div>
|
|
<div class="flex-grow-1"></div>
|
|
|
- <div class="ask-message">{{ message.content }}</div>
|
|
|
|
|
|
|
+ <div class="message-wrapper user-message-wrapper">
|
|
|
|
|
+ <div class="ask-message">{{ message.content }}</div>
|
|
|
|
|
+ <CopyOutlined class="copy-icon" @click="copyToClipboard(message.content)" title="复制消息" />
|
|
|
|
|
+ </div>
|
|
|
</template>
|
|
</template>
|
|
|
<template v-else>
|
|
<template v-else>
|
|
|
- <SvgIcon size="30" class="ml-2px mr-2px" name="ai-logo" />
|
|
|
|
|
- <div class="answer-message">
|
|
|
|
|
- <div v-if="message.contentR1" class="color-gray font-size-12px" v-html="formatMessage(message.contentR1)"> </div>
|
|
|
|
|
- <div v-html="formatMessage(message.content)"> </div>
|
|
|
|
|
|
|
+ <SvgIcon size="80" class="ml-2px mr-2px" name="ai-logo" />
|
|
|
|
|
+ <div class="message-wrapper ai-message-wrapper">
|
|
|
|
|
+ <div class="answer-message">
|
|
|
|
|
+ <div v-if="message.contentR1" class="color-gray font-size-12px" v-html="message.contentR1"> </div>
|
|
|
|
|
+ <div v-else v-html="message.content"> </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <CopyOutlined class="copy-icon" @click="copyToClipboard(message.contentR1 || message.content)" title="复制消息" />
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
- <!-- 底部操作栏 -->
|
|
|
|
|
|
|
+ <!-- 底部输入区 -->
|
|
|
<div class="input-area">
|
|
<div class="input-area">
|
|
|
- <TextArea v-model:value="inputText" placeholder="请输入你的问题" />
|
|
|
|
|
- <div class="action-bar">
|
|
|
|
|
- <!-- 左侧深度思考按钮 -->
|
|
|
|
|
- <div class="think-btn" :class="{ active: store.deepseekR1Enable }" @click="toggleThinking"> <span>深度思考</span> </div>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 右侧操作按钮 -->
|
|
|
|
|
- <Space>
|
|
|
|
|
- <SvgIcon name="send-image" />
|
|
|
|
|
- <SvgIcon name="send-file" />
|
|
|
|
|
- <Button type="primary" shape="circle" size="small" :loading="store.streaming" @click="handleSend">
|
|
|
|
|
- <template #icon>
|
|
|
|
|
- <SvgIcon name="send" />
|
|
|
|
|
- </template>
|
|
|
|
|
- </Button>
|
|
|
|
|
- </Space>
|
|
|
|
|
|
|
+ <a-input v-model:value="inputText" placeholder="请输入你的问题" @keyup.enter="handleSend(inputText)" class="ant-input" />
|
|
|
|
|
+ <div class="ctrl-btn">
|
|
|
|
|
+ <div class="input-controls">
|
|
|
|
|
+ <button class="control-btn">深度学习</button>
|
|
|
|
|
+ <button class="control-btn" @click="stopReq()">停止响应</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="action-bar">
|
|
|
|
|
+ <Space>
|
|
|
|
|
+ <Button class="control-btn1" size="small" @click="showModal()">
|
|
|
|
|
+ <template #icon>
|
|
|
|
|
+ <SvgIcon name="send-file" />
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ <Button class="control-btn2" size="small" @click="handleSend(inputText)">
|
|
|
|
|
+ <template #icon>
|
|
|
|
|
+ <SvgIcon name="send" />
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </Space>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <!-- 右侧文件上传区 -->
|
|
|
|
|
+ <div v-if="open" class="file-upload">
|
|
|
|
|
+ <!-- 输入框区域,包含确认按钮 -->
|
|
|
|
|
+ <div class="input-container">
|
|
|
|
|
+ <a-input v-model:value="filePath" placeholder="输入文件连接" class="file-input" @pressEnter="handlePathConfirm" />
|
|
|
|
|
+ <button class="confirm-btn" @click="handlePathConfirm">确认</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <!-- 上传按钮 -->
|
|
|
|
|
+ <!-- <a-upload> <button class="upload-btn" @click="customUpload">从本地上传</button></a-upload> -->
|
|
|
|
|
+ <a-upload class="custom-upload" name="file" :multiple="false">
|
|
|
|
|
+ <a-button class="upload-btn" @click="customUpload">
|
|
|
|
|
+ <UploadOutlined></UploadOutlined>
|
|
|
|
|
+ 从本地上传
|
|
|
|
|
+ </a-button>
|
|
|
|
|
+ </a-upload>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
@@ -58,151 +137,829 @@
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script lang="ts" setup>
|
|
<script lang="ts" setup>
|
|
|
- import { ref, onMounted } from 'vue';
|
|
|
|
|
- import { SvgIcon } from '../Icon';
|
|
|
|
|
- import { Space, Button, Popover, Input } from 'ant-design-vue';
|
|
|
|
|
- import AIChat from './index.vue';
|
|
|
|
|
- import { useAIChat } from '/@/store/modules/AIChat';
|
|
|
|
|
- const TextArea = Input.TextArea; // 直接导入TextArea组件使用时打包报错
|
|
|
|
|
- const dialogRef = ref<HTMLElement | null>(null);
|
|
|
|
|
- const dialogVisible = ref(true);
|
|
|
|
|
- const inputText = ref(''); // 输入框内容
|
|
|
|
|
- const store = useAIChat(); //获取用户信息
|
|
|
|
|
-
|
|
|
|
|
- const openDialog = () => {
|
|
|
|
|
- dialogVisible.value = !dialogVisible.value;
|
|
|
|
|
- };
|
|
|
|
|
- //启用深度思考
|
|
|
|
|
- const toggleThinking = () => {
|
|
|
|
|
- store.deepseekR1Enable = !store.deepseekR1Enable;
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- //获取消息列表
|
|
|
|
|
- async function handleSend() {
|
|
|
|
|
- store
|
|
|
|
|
- .sendQuestion(inputText.value, () => {
|
|
|
|
|
- if (dialogRef.value) {
|
|
|
|
|
- dialogRef.value.scrollTop = dialogRef.value.scrollHeight;
|
|
|
|
|
|
|
+import { ref, onMounted } from 'vue';
|
|
|
|
|
+import { SvgIcon } from '../Icon';
|
|
|
|
|
+import { Space, Button, Modal, Input, message } from 'ant-design-vue';
|
|
|
|
|
+// import AIChat from './index.vue';
|
|
|
|
|
+import { useUserStore } from '/@/store/modules/user';
|
|
|
|
|
+import { EditOutlined, DeleteOutlined, UploadOutlined, CopyOutlined } from '@ant-design/icons-vue';
|
|
|
|
|
+import { createVNode } from 'vue';
|
|
|
|
|
+const TextArea = Input.TextArea; // 直接导入TextArea组件使用时打包报错
|
|
|
|
|
+const inputText = ref(''); // 输入框内容
|
|
|
|
|
+const sessionHistory = ref([]);
|
|
|
|
|
+const isShowChatBroad = ref(false);
|
|
|
|
|
+const editingId = ref<number | null>(null);
|
|
|
|
|
+const editText = ref('');
|
|
|
|
|
+const currentSessionID = ref('');
|
|
|
|
|
+const taskID = ref('');
|
|
|
|
|
+const open = ref<boolean>(false);
|
|
|
|
|
+interface ListItem {
|
|
|
|
|
+ id: number;
|
|
|
|
|
+ name?: string;
|
|
|
|
|
+}
|
|
|
|
|
+interface Message {
|
|
|
|
|
+ id: string; // 唯一标识(可用时间戳生成)
|
|
|
|
|
+ type: 'user' | 'system' | 'response';
|
|
|
|
|
+ content: string;
|
|
|
|
|
+ /** 深度思考时的文本 */
|
|
|
|
|
+ contentR1: string;
|
|
|
|
|
+ timestamp: number; // 排序依据
|
|
|
|
|
+}
|
|
|
|
|
+// 定义消息历史数组类型
|
|
|
|
|
+const messageHistory = ref<Message[]>([]);
|
|
|
|
|
+const isFold = ref(true); // 是否折叠
|
|
|
|
|
+const userid = useUserStore().getUserInfo.id as string;
|
|
|
|
|
+const filePath = ref(''); // 绑定输入框值
|
|
|
|
|
+const showConfirmBtn = ref(false); // 控制确认按钮显示状态
|
|
|
|
|
+const fileList = ref([]);
|
|
|
|
|
+function showAIChat() {
|
|
|
|
|
+ isShowChatBroad.value = !isShowChatBroad.value;
|
|
|
|
|
+}
|
|
|
|
|
+//获取消息列表
|
|
|
|
|
+// async function handleSend(data) {
|
|
|
|
|
+// messageHistory.value.push({
|
|
|
|
|
+// id: `user_${Date.now()}`,
|
|
|
|
|
+// type: 'user',
|
|
|
|
|
+// content: data,
|
|
|
|
|
+// contentR1: '',
|
|
|
|
|
+// timestamp: Date.now(),
|
|
|
|
|
+// });
|
|
|
|
|
+// // 发送 POST 请求
|
|
|
|
|
+// fetch('http://39.97.59.228:8000/v1/chat-messages', {
|
|
|
|
|
+// method: 'POST',
|
|
|
|
|
+// headers: {
|
|
|
|
|
+// 'Content-Type': 'application/json',
|
|
|
|
|
+// Authorization: 'Bearer app-tSFRUnv0Qkbtik1dwtlhnpkd',
|
|
|
|
|
+// },
|
|
|
|
|
+// body: JSON.stringify({
|
|
|
|
|
+// conversation_id: currentSessionID.value,
|
|
|
|
|
+// query: data,
|
|
|
|
|
+// response_mode: 'streaming',
|
|
|
|
|
+// user: userid,
|
|
|
|
|
+// inputs: {},
|
|
|
|
|
+// }),
|
|
|
|
|
+// }).then((response) => {
|
|
|
|
|
+// const decoder = new TextDecoder('utf-8');
|
|
|
|
|
+// let buffer = [];
|
|
|
|
|
+// // 获取可读流
|
|
|
|
|
+// const reader = response.body.getReader();
|
|
|
|
|
+// const newMessage = {
|
|
|
|
|
+// id: `response_${Date.now()}`,
|
|
|
|
|
+// type: 'response' as any,
|
|
|
|
|
+// content: '',
|
|
|
|
|
+// contentR1: '',
|
|
|
|
|
+// timestamp: Date.now(),
|
|
|
|
|
+// };
|
|
|
|
|
+// messageHistory.value.push(newMessage);
|
|
|
|
|
+// // 读取数据
|
|
|
|
|
+// function read() {
|
|
|
|
|
+// return reader.read().then(({ done, value }) => {
|
|
|
|
|
+// if (done) {
|
|
|
|
|
+// return buffer;
|
|
|
|
|
+// }
|
|
|
|
|
+// // 解码数据块
|
|
|
|
|
+// const chunk = decoder.decode(value, { stream: false });
|
|
|
|
|
+// // 处理每段数据
|
|
|
|
|
+// const processedData = processStreamChunk(chunk);
|
|
|
|
|
+// buffer = buffer.concat(processedData);
|
|
|
|
|
+// // 继续读取
|
|
|
|
|
+// return read();
|
|
|
|
|
+// });
|
|
|
|
|
+// }
|
|
|
|
|
+// // 开始读取
|
|
|
|
|
+// return read();
|
|
|
|
|
+// function processStreamChunk(chunk) {
|
|
|
|
|
+// try {
|
|
|
|
|
+// // 移除 "data: " 前缀
|
|
|
|
|
+// const jsonStr = chunk.replace('data: ', '');
|
|
|
|
|
+// const data = JSON.parse(jsonStr);
|
|
|
|
|
+// const targetMessage = messageHistory.value.find((msg) => msg.id === newMessage.id);
|
|
|
|
|
+// if (!targetMessage) return;
|
|
|
|
|
+// // 根据事件类型分发处理
|
|
|
|
|
+// switch (data.event) {
|
|
|
|
|
+// case 'message':
|
|
|
|
|
+// if (!taskID.value && !currentSessionID.value) {
|
|
|
|
|
+// taskID.value = data.task_id;
|
|
|
|
|
+// currentSessionID.value = data.conversation_id;
|
|
|
|
|
+// }
|
|
|
|
|
+// targetMessage.content += data.answer; // 追加内容
|
|
|
|
|
+// break;
|
|
|
|
|
+// }
|
|
|
|
|
+// return data;
|
|
|
|
|
+// } catch (error) {
|
|
|
|
|
+// // 请求失败时设置系统消息
|
|
|
|
|
+// return null;
|
|
|
|
|
+// }
|
|
|
|
|
+// }
|
|
|
|
|
+// });
|
|
|
|
|
+// inputText.value = '';
|
|
|
|
|
+// }
|
|
|
|
|
+// 复制消息
|
|
|
|
|
+function copyToClipboard(text) {
|
|
|
|
|
+ if (!text || text.trim() === '') {
|
|
|
|
|
+ message.warn('没有可复制的内容');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 2. 创建临时textarea 元素
|
|
|
|
|
+ const textarea = document.createElement('textarea');
|
|
|
|
|
+ textarea.value = text;
|
|
|
|
|
+ textarea.style.position = 'fixed';
|
|
|
|
|
+ textarea.style.top = '-999px';
|
|
|
|
|
+ textarea.style.left = '-999px';
|
|
|
|
|
+ textarea.style.width = '200px';
|
|
|
|
|
+ textarea.style.height = '200px';
|
|
|
|
|
+ document.body.appendChild(textarea);
|
|
|
|
|
+ try {
|
|
|
|
|
+ textarea.select();
|
|
|
|
|
+ textarea.setSelectionRange(0, text.length);
|
|
|
|
|
+ const isSuccessful = document.execCommand('copy');
|
|
|
|
|
+ if (isSuccessful) {
|
|
|
|
|
+ message.success('复制成功!');
|
|
|
|
|
+ } else {
|
|
|
|
|
+ throw new Error('复制命令执行失败');
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ console.error('复制失败:', err);
|
|
|
|
|
+ message.error('复制失败,请手动复制');
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ document.body.removeChild(textarea);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+async function handleSend(data) {
|
|
|
|
|
+ inputText.value = '';
|
|
|
|
|
+ messageHistory.value.push({
|
|
|
|
|
+ id: `user_${Date.now()}`,
|
|
|
|
|
+ type: 'user',
|
|
|
|
|
+ content: data,
|
|
|
|
|
+ contentR1: '',
|
|
|
|
|
+ timestamp: Date.now(),
|
|
|
|
|
+ });
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await fetch('http://39.97.59.228:8000/v1/chat-messages', {
|
|
|
|
|
+ method: 'POST',
|
|
|
|
|
+ headers: {
|
|
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
|
|
+ Authorization: 'Bearer app-tSFRUnv0Qkbtik1dwtlhnpkd',
|
|
|
|
|
+ },
|
|
|
|
|
+ body: JSON.stringify({
|
|
|
|
|
+ conversation_id: currentSessionID.value,
|
|
|
|
|
+ query: data,
|
|
|
|
|
+ response_mode: 'streaming',
|
|
|
|
|
+ user: userid,
|
|
|
|
|
+ inputs: {},
|
|
|
|
|
+ }),
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ if (!response.ok) {
|
|
|
|
|
+ throw new Error(`HTTP error! status: ${response.status}`);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const decoder = new TextDecoder('utf-8');
|
|
|
|
|
+ const reader = response.body.getReader();
|
|
|
|
|
+ let textBuffer = ''; // 使用字符串缓冲区来累积数据
|
|
|
|
|
+ const newMessage = {
|
|
|
|
|
+ id: `response_${Date.now()}`,
|
|
|
|
|
+ type: 'response',
|
|
|
|
|
+ content: '',
|
|
|
|
|
+ contentR1: '',
|
|
|
|
|
+ timestamp: Date.now(),
|
|
|
|
|
+ };
|
|
|
|
|
+ messageHistory.value.push(newMessage);
|
|
|
|
|
+ while (true) {
|
|
|
|
|
+ const { done, value } = await reader.read();
|
|
|
|
|
+ if (done) {
|
|
|
|
|
+ if (textBuffer) {
|
|
|
|
|
+ processLine(textBuffer);
|
|
|
|
|
+ }
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ textBuffer += decoder.decode(value, { stream: true });
|
|
|
|
|
+ // 处理每一行数据
|
|
|
|
|
+ let lineIndex;
|
|
|
|
|
+ while ((lineIndex = textBuffer.indexOf('\n')) !== -1) {
|
|
|
|
|
+ const line = textBuffer.substring(0, lineIndex).trim();
|
|
|
|
|
+ textBuffer = textBuffer.substring(lineIndex + 1);
|
|
|
|
|
+
|
|
|
|
|
+ if (line) {
|
|
|
|
|
+ processLine(line);
|
|
|
}
|
|
}
|
|
|
- })
|
|
|
|
|
- .then(() => {
|
|
|
|
|
- inputText.value = '';
|
|
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ function processLine(line) {
|
|
|
|
|
+ if (line.startsWith('data: ')) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const jsonStr = line.substring('data: '.length);
|
|
|
|
|
+ const data = JSON.parse(jsonStr);
|
|
|
|
|
+ switch (data.event) {
|
|
|
|
|
+ case 'message':
|
|
|
|
|
+ if (data.answer) {
|
|
|
|
|
+ const targetMessage = messageHistory.value.find((msg) => msg.id === newMessage.id);
|
|
|
|
|
+ if (targetMessage) {
|
|
|
|
|
+ targetMessage.content += data.answer;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (data.task_id && !taskID.value) taskID.value = data.task_id;
|
|
|
|
|
+ if (data.conversation_id && !currentSessionID.value) currentSessionID.value = data.conversation_id;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.warn('Error parsing stream chunk:', error, 'Chunk:', line);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('Error in handleSend:', error);
|
|
|
|
|
+ // 在 UI 上显示错误信息
|
|
|
|
|
+ messageHistory.value.push({
|
|
|
|
|
+ id: `system_${Date.now()}`,
|
|
|
|
|
+ type: 'system',
|
|
|
|
|
+ content: '请求错误',
|
|
|
|
|
+ contentR1: '',
|
|
|
|
|
+ timestamp: Date.now(),
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+// 上传文件
|
|
|
|
|
+const showModal = () => {
|
|
|
|
|
+ open.value = !open.value;
|
|
|
|
|
+};
|
|
|
|
|
+async function customUpload(data) {
|
|
|
|
|
+ const formData = new FormData();
|
|
|
|
|
+ if (!data) {
|
|
|
|
|
+ return message.warn('请选择文件');
|
|
|
|
|
+ }
|
|
|
|
|
+ // 添加文件参数
|
|
|
|
|
+ formData.append('file', data.file);
|
|
|
|
|
+ // 添加用户标识参数
|
|
|
|
|
+ formData.append('user', userid);
|
|
|
|
|
+ try {
|
|
|
|
|
+ let response = await fetch(`http://39.97.59.228:8000/v1/files/upload`, {
|
|
|
|
|
+ method: 'POST',
|
|
|
|
|
+ headers: {
|
|
|
|
|
+ Authorization: 'Bearer app-tSFRUnv0Qkbtik1dwtlhnpkd',
|
|
|
|
|
+ },
|
|
|
|
|
+ body: formData,
|
|
|
|
|
+ });
|
|
|
|
|
+ // if (response) {
|
|
|
|
|
+ // message.success('上传成功');
|
|
|
|
|
+ // }
|
|
|
|
|
+ console.log(response, '123');
|
|
|
|
|
+ if (!response) {
|
|
|
|
|
+ throw new Error('Network response was not ok');
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('保存失败:', error);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+//停止响应
|
|
|
|
|
+async function stopReq() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ let response = await fetch(`http://39.97.59.228:8000/v1/chat-messages/${taskID}/stop`, {
|
|
|
|
|
+ method: 'POST',
|
|
|
|
|
+ headers: {
|
|
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
|
|
+ Authorization: 'Bearer app-tSFRUnv0Qkbtik1dwtlhnpkd',
|
|
|
|
|
+ },
|
|
|
|
|
+ body: JSON.stringify({
|
|
|
|
|
+ user: userid,
|
|
|
|
|
+ }),
|
|
|
|
|
+ });
|
|
|
|
|
+ if (!response) {
|
|
|
|
|
+ throw new Error('Network response was not ok');
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('保存失败:', error);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+//获取具体会话记录
|
|
|
|
|
+async function sessionsHistory(id: string) {
|
|
|
|
|
+ console.log(id, '123');
|
|
|
|
|
+ try {
|
|
|
|
|
+ let response = await fetch(`http://39.97.59.228:8000/v1/messages?conversation_id=${id}&user=${userid}`, {
|
|
|
|
|
+ method: 'GET',
|
|
|
|
|
+ headers: {
|
|
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
|
|
+ Authorization: 'Bearer app-tSFRUnv0Qkbtik1dwtlhnpkd',
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+ const data = await response.json();
|
|
|
|
|
+ console.log(data, '123');
|
|
|
|
|
+ if (data.data.length > 0) {
|
|
|
|
|
+ messageHistory.value = [];
|
|
|
|
|
+ data.data.forEach((item: any) => {
|
|
|
|
|
+ messageHistory.value.push({
|
|
|
|
|
+ id: `user_${Date.now()}`,
|
|
|
|
|
+ type: 'user',
|
|
|
|
|
+ content: item.query,
|
|
|
|
|
+ contentR1: '',
|
|
|
|
|
+ timestamp: Date.now(),
|
|
|
|
|
+ });
|
|
|
|
|
+ messageHistory.value.push({
|
|
|
|
|
+ id: `system_${Date.now()}`,
|
|
|
|
|
+ type: 'system',
|
|
|
|
|
+ content: item.answer,
|
|
|
|
|
+ contentR1: '',
|
|
|
|
|
+ timestamp: Date.now(),
|
|
|
|
|
+ });
|
|
|
});
|
|
});
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!response.ok) {
|
|
|
|
|
+ throw new Error('Network response was not ok');
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('保存失败:', error);
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- //格式化消息
|
|
|
|
|
- function formatMessage(text: string) {
|
|
|
|
|
- let formatted = text
|
|
|
|
|
- // 处理换行
|
|
|
|
|
- .replace(/\n\n/g, '<br>')
|
|
|
|
|
- .replace(/\n###/g, '<br> ')
|
|
|
|
|
- .replace(/###/g, '')
|
|
|
|
|
- .replace(/---/g, '')
|
|
|
|
|
- // 处理粗体
|
|
|
|
|
- .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
|
|
|
|
- // 处理斜体
|
|
|
|
|
- .replace(/\*(.*?)\*/g, '<em>$1</em>')
|
|
|
|
|
- // 处理行内代码
|
|
|
|
|
- .replace(/`([^`]+)`/g, '<code>$1</code>');
|
|
|
|
|
- return formatted;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 初始化按钮定位
|
|
|
|
|
- onMounted(() => {
|
|
|
|
|
- // store.getSessionHistory();
|
|
|
|
|
|
|
+ editingId.value = null;
|
|
|
|
|
+}
|
|
|
|
|
+//编辑标题
|
|
|
|
|
+const startEditing = (item: ListItem) => {
|
|
|
|
|
+ editingId.value = item.id;
|
|
|
|
|
+ editText.value = item.name || '';
|
|
|
|
|
+};
|
|
|
|
|
+// 输入框确认按钮点击事件
|
|
|
|
|
+async function handlePathConfirm() {
|
|
|
|
|
+ const formData = new FormData();
|
|
|
|
|
+ // 添加文件参数
|
|
|
|
|
+ formData.append('file', filePath.value);
|
|
|
|
|
+ // 添加用户标识参数
|
|
|
|
|
+ formData.append('user', userid);
|
|
|
|
|
+ try {
|
|
|
|
|
+ let response = await fetch(`http://39.97.59.228:8000/v1/files/upload`, {
|
|
|
|
|
+ method: 'POST',
|
|
|
|
|
+ headers: {
|
|
|
|
|
+ Authorization: 'Bearer app-tSFRUnv0Qkbtik1dwtlhnpkd',
|
|
|
|
|
+ },
|
|
|
|
|
+ body: formData,
|
|
|
|
|
+ });
|
|
|
|
|
+ if (!response.ok) {
|
|
|
|
|
+ throw new Error('Network response was not ok');
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('保存失败:', error);
|
|
|
|
|
+ }
|
|
|
|
|
+ console.log('确认的文件路径:', filePath.value);
|
|
|
|
|
+ // 这里可以添加路径验证、保存等逻辑
|
|
|
|
|
+ filePath.value = ''; // 可选:确认后清空输入框
|
|
|
|
|
+ showConfirmBtn.value = false;
|
|
|
|
|
+}
|
|
|
|
|
+// 保存修改
|
|
|
|
|
+const handleSave = async (item: ListItem) => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ let response = await fetch(`http://39.97.59.228:8000/v1/conversations/${item.id}/name`, {
|
|
|
|
|
+ method: 'POST',
|
|
|
|
|
+ headers: {
|
|
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
|
|
+ Authorization: 'Bearer app-tSFRUnv0Qkbtik1dwtlhnpkd',
|
|
|
|
|
+ },
|
|
|
|
|
+ body: JSON.stringify({
|
|
|
|
|
+ name: editText.value,
|
|
|
|
|
+ user: userid,
|
|
|
|
|
+ }),
|
|
|
|
|
+ });
|
|
|
|
|
+ if (!response.ok) {
|
|
|
|
|
+ throw new Error('Network response was not ok');
|
|
|
|
|
+ }
|
|
|
|
|
+ item.name = editText.value;
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('保存失败:', error);
|
|
|
|
|
+ }
|
|
|
|
|
+ editingId.value = null;
|
|
|
|
|
+};
|
|
|
|
|
+// 删除会话
|
|
|
|
|
+const startDelete = async (item: ListItem) => {
|
|
|
|
|
+ Modal.confirm({
|
|
|
|
|
+ title: '确认删除',
|
|
|
|
|
+ content: `确定要删除会话 "${item.name || '新会话'}" 吗?此操作不可撤销。`,
|
|
|
|
|
+ okText: '确认',
|
|
|
|
|
+ cancelText: '取消',
|
|
|
|
|
+ onOk: async () => {
|
|
|
|
|
+ // 原有删除逻辑不变
|
|
|
|
|
+ try {
|
|
|
|
|
+ let response = await fetch(`http://39.97.59.228:8000/v1/conversations/${item.id}`, {
|
|
|
|
|
+ method: 'DELETE',
|
|
|
|
|
+ headers: {
|
|
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
|
|
+ Authorization: 'Bearer app-tSFRUnv0Qkbtik1dwtlhnpkd',
|
|
|
|
|
+ },
|
|
|
|
|
+ body: JSON.stringify({
|
|
|
|
|
+ user: userid,
|
|
|
|
|
+ }),
|
|
|
|
|
+ });
|
|
|
|
|
+ if (!response.ok) {
|
|
|
|
|
+ throw new Error('Network response was not ok');
|
|
|
|
|
+ }
|
|
|
|
|
+ getHistoryList();
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('删除失败:', error);
|
|
|
|
|
+ Modal.error({
|
|
|
|
|
+ title: '删除失败',
|
|
|
|
|
+ content: '删除会话时出现错误,请稍后重试。',
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
});
|
|
});
|
|
|
|
|
+};
|
|
|
|
|
+const fold = () => {
|
|
|
|
|
+ isFold.value = !isFold.value;
|
|
|
|
|
+ if (!isFold.value) {
|
|
|
|
|
+ getHistoryList();
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+// 获取历史会话列表
|
|
|
|
|
+async function getHistoryList() {
|
|
|
|
|
+ let response = await fetch(`http://39.97.59.228:8000/v1/conversations?user=${userid}`, {
|
|
|
|
|
+ method: 'get',
|
|
|
|
|
+ headers: {
|
|
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
|
|
+ Authorization: 'Bearer app-tSFRUnv0Qkbtik1dwtlhnpkd',
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+ const data = await response.json();
|
|
|
|
|
+ sessionHistory.value = data.data;
|
|
|
|
|
+ console.log(sessionHistory.value, '123');
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 初始化按钮定位
|
|
|
|
|
+onMounted(() => {
|
|
|
|
|
+ getHistoryList();
|
|
|
|
|
+});
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<style lang="less" scoped>
|
|
<style lang="less" scoped>
|
|
|
- .mini-chat {
|
|
|
|
|
|
|
+.btn-header {
|
|
|
|
|
+ width: 40px;
|
|
|
|
|
+ height: 40px;
|
|
|
|
|
+ margin-right: 5px;
|
|
|
|
|
+ margin-bottom: 5px;
|
|
|
|
|
+}
|
|
|
|
|
+.mini-chat {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ width: 500px;
|
|
|
|
|
+ height: 400px;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ position: fixed;
|
|
|
|
|
+ top: 60px;
|
|
|
|
|
+ right: 20px;
|
|
|
|
|
+ background-color: rgb(255, 255, 255);
|
|
|
|
|
+ background: url('../../assets/images/warn-dialog-bg.png') no-repeat center;
|
|
|
|
|
+ background-size: 100% 100%;
|
|
|
|
|
+ z-index: 9999999;
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.left-side {
|
|
|
|
|
+ background: #0c2842;
|
|
|
|
|
+ transition: width 0.5s ease; /* 平滑过渡动画 */
|
|
|
|
|
+ width: 140px; /* 展开时宽度 */
|
|
|
|
|
+ position: relative; /* 用于按钮定位 */
|
|
|
|
|
+}
|
|
|
|
|
+.left-side.collapsed {
|
|
|
|
|
+ width: 40px; /* 折叠时宽度 */
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.custom-list {
|
|
|
|
|
+ height: 325px;
|
|
|
|
|
+ overflow-y: auto;
|
|
|
|
|
+}
|
|
|
|
|
+.text-container {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+}
|
|
|
|
|
+.btn-container {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+}
|
|
|
|
|
+.jeecg-layout-header-action span[role='img'] {
|
|
|
|
|
+ padding: 0;
|
|
|
|
|
+}
|
|
|
|
|
+.text-ellipsis {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+}
|
|
|
|
|
+.edit-text {
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ text-overflow: ellipsis;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+ width: 90px;
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+}
|
|
|
|
|
+.edit-icon {
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ margin-left: auto;
|
|
|
|
|
+ line-height: 23px;
|
|
|
|
|
+}
|
|
|
|
|
+.delete-icon {
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ margin-left: auto;
|
|
|
|
|
+ line-height: 23px;
|
|
|
|
|
+}
|
|
|
|
|
+.edit-icon:hover {
|
|
|
|
|
+ color: #1890ff !important;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+}
|
|
|
|
|
+.delete-icon:hover {
|
|
|
|
|
+ color: #1890ff !important;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+}
|
|
|
|
|
+.edit-input {
|
|
|
|
|
+ font-size: 10px;
|
|
|
|
|
+}
|
|
|
|
|
+.btn-text-bg {
|
|
|
|
|
+ width: 14px;
|
|
|
|
|
+ height: 14px;
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ background-size: 100% 100%;
|
|
|
|
|
+ right: 10px;
|
|
|
|
|
+ top: 10px;
|
|
|
|
|
+ left: 10px;
|
|
|
|
|
+ bottom: 10px;
|
|
|
|
|
+}
|
|
|
|
|
+.btn-text {
|
|
|
|
|
+ margin-left: 3px;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+ margin-left: 30px;
|
|
|
|
|
+ line-height: 35px;
|
|
|
|
|
+}
|
|
|
|
|
+.historyBtn {
|
|
|
|
|
+ width: 20px;
|
|
|
|
|
+ height: 20px;
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ background-size: 100% 100%;
|
|
|
|
|
+ background-position: center;
|
|
|
|
|
+ padding: 2px;
|
|
|
|
|
+ right: 10px;
|
|
|
|
|
+ top: 10px;
|
|
|
|
|
+}
|
|
|
|
|
+.historyBtn1 {
|
|
|
|
|
+ width: 20px;
|
|
|
|
|
+ height: 20px;
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ background-size: 100% 100%;
|
|
|
|
|
+ background-position: center;
|
|
|
|
|
+ left: 3px;
|
|
|
|
|
+}
|
|
|
|
|
+.divider0 {
|
|
|
|
|
+ border-bottom: 1px solid #1074c1;
|
|
|
|
|
+ width: auto;
|
|
|
|
|
+ margin: 0 10px;
|
|
|
|
|
+ height: 13%;
|
|
|
|
|
+ display: block;
|
|
|
|
|
+ background: transparent;
|
|
|
|
|
+}
|
|
|
|
|
+.foldBtn {
|
|
|
|
|
+ width: 20px;
|
|
|
|
|
+ height: 20px;
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ background-size: 100% 100%;
|
|
|
|
|
+ background-position: center;
|
|
|
|
|
+ padding: 2px;
|
|
|
|
|
+ right: 10px;
|
|
|
|
|
+ bottom: 10px;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.right-side {
|
|
|
|
|
+ flex: 1; /* 占据剩余空间 */
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+
|
|
|
|
|
+ .dialog-area {
|
|
|
|
|
+ flex: 1; /* 占据剩余空间 */
|
|
|
|
|
+ gap: 10px; /* 消息块间隔统一控制 */
|
|
|
|
|
+ overflow-y: auto; /* 垂直滚动条 */
|
|
|
|
|
+ padding: 5px;
|
|
|
display: flex;
|
|
display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+
|
|
|
|
|
+ .ask-message {
|
|
|
|
|
+ padding: 10px;
|
|
|
|
|
+ border-radius: 5px;
|
|
|
|
|
+ background: #0c2842;
|
|
|
|
|
+ max-width: 80%;
|
|
|
|
|
+ }
|
|
|
|
|
+ .answer-message {
|
|
|
|
|
+ padding: 10px;
|
|
|
|
|
+ border-radius: 5px;
|
|
|
|
|
+ background: #0c2842;
|
|
|
|
|
+ max-width: 90%;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- .left-side {
|
|
|
|
|
- width: 40px; /* 折叠时宽度 */
|
|
|
|
|
- background: #0c2842;
|
|
|
|
|
- transition: width 0.5s ease; /* 平滑过渡动画 */
|
|
|
|
|
|
|
+ .input-area {
|
|
|
|
|
+ margin: 10px 10px 20px 10px;
|
|
|
|
|
+ padding: 10px;
|
|
|
|
|
+ background-color: #043256;
|
|
|
|
|
+ border: 1px solid #2cb6ff;
|
|
|
|
|
+ border-radius: 5px;
|
|
|
display: flex;
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
flex-direction: column;
|
|
|
- justify-content: space-around;
|
|
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ gap: 5px;
|
|
|
|
|
+ height: 25%;
|
|
|
|
|
+ }
|
|
|
|
|
+ /* 文件列表容器 */
|
|
|
|
|
+ .uploaded-files {
|
|
|
|
|
+ padding: 8px;
|
|
|
|
|
+ background-color: #f5f5f5;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ min-height: 40px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /* 单个文件项 */
|
|
|
|
|
+ .file-item {
|
|
|
|
|
+ display: inline-flex;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
|
|
+ padding: 4px 8px;
|
|
|
|
|
+ margin-right: 8px;
|
|
|
|
|
+ margin-bottom: 8px;
|
|
|
|
|
+ background-color: #fff;
|
|
|
|
|
+ border: 1px solid #e9e9e9;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- .right-side {
|
|
|
|
|
- flex: 1; /* 占据剩余空间 */
|
|
|
|
|
- background: #09172c;
|
|
|
|
|
|
|
+ /* 文件名 */
|
|
|
|
|
+ .file-name {
|
|
|
|
|
+ margin-left: 8px;
|
|
|
|
|
+ margin-right: 8px;
|
|
|
|
|
+ max-width: 150px;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ text-overflow: ellipsis;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .ant-input {
|
|
|
|
|
+ background-color: rgba(255, 255, 255, 0) !important;
|
|
|
|
|
+ border: none;
|
|
|
|
|
+ outline: none;
|
|
|
|
|
+ }
|
|
|
|
|
+ .ant-input:focus {
|
|
|
|
|
+ border: none; /* 聚焦时无边框 */
|
|
|
|
|
+ outline: none; /* 聚焦时无轮廓 */
|
|
|
|
|
+ box-shadow: none; /* 移除可能存在的阴影效果 */
|
|
|
|
|
+ }
|
|
|
|
|
+ .ctrl-btn {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: row;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ }
|
|
|
|
|
+ .question-input {
|
|
|
|
|
+ background-color: #1e293b !important;
|
|
|
|
|
+ border-color: #334155 !important;
|
|
|
|
|
+ color: #e2e8f0 !important;
|
|
|
|
|
+ border-radius: 8px !important;
|
|
|
|
|
+ padding: 12px 16px !important;
|
|
|
|
|
+ font-size: 14px !important;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .question-input::placeholder {
|
|
|
|
|
+ color: #64748b !important;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .control-btn {
|
|
|
|
|
+ height: 25px;
|
|
|
|
|
+ background-color: #043256;
|
|
|
|
|
+ border: 1px solid #2cb6ff;
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ font-size: 10px;
|
|
|
|
|
+ margin-right: 10px;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ transition: all 0.2s;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .control-btn:hover {
|
|
|
|
|
+ background-color: #043256;
|
|
|
|
|
+ color: #e2e8f0;
|
|
|
|
|
+ }
|
|
|
|
|
+ .control-btn1 {
|
|
|
|
|
+ height: 20px;
|
|
|
|
|
+ background-color: #234a6b;
|
|
|
|
|
+ border: 1px solid #234a6b;
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ font-size: 10px;
|
|
|
|
|
+ margin-right: 10px;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ transition: all 0.2s;
|
|
|
|
|
+ }
|
|
|
|
|
+ .control-btn2 {
|
|
|
|
|
+ height: 20px;
|
|
|
|
|
+ background-color: #2cb6ff;
|
|
|
|
|
+ border: 1px solid #2cb6ff;
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ font-size: 10px;
|
|
|
|
|
+ margin-right: 10px;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ transition: all 0.2s;
|
|
|
|
|
+ }
|
|
|
|
|
+ /* 文件上传区 */
|
|
|
|
|
+ .file-upload {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ right: 20px;
|
|
|
|
|
+ bottom: 70px;
|
|
|
|
|
+ width: 180px;
|
|
|
display: flex;
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
flex-direction: column;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+ border: 1px solid #2cb6ff;
|
|
|
|
|
+ background-color: #234a6b;
|
|
|
|
|
+ border-radius: 6px;
|
|
|
|
|
+ padding: 10px;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- .dialog-area {
|
|
|
|
|
- flex: 1; /* 占据剩余空间 */
|
|
|
|
|
- gap: 10px; /* 消息块间隔统一控制 */
|
|
|
|
|
- overflow-y: auto; /* 垂直滚动条 */
|
|
|
|
|
- padding: 5px;
|
|
|
|
|
- display: flex;
|
|
|
|
|
- flex-direction: column;
|
|
|
|
|
- color: #fff;
|
|
|
|
|
-
|
|
|
|
|
- .ask-message {
|
|
|
|
|
- padding: 10px;
|
|
|
|
|
- border-radius: 5px;
|
|
|
|
|
- background: #0c2842;
|
|
|
|
|
- max-width: 80%;
|
|
|
|
|
- }
|
|
|
|
|
- .answer-message {
|
|
|
|
|
- padding: 10px;
|
|
|
|
|
- border-radius: 5px;
|
|
|
|
|
- background: #0c2842;
|
|
|
|
|
- max-width: 90%;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ .input-container {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- .input-area {
|
|
|
|
|
- background-color: #043256;
|
|
|
|
|
- padding: 5px;
|
|
|
|
|
-
|
|
|
|
|
- textarea {
|
|
|
|
|
- background-color: transparent;
|
|
|
|
|
- width: 100%;
|
|
|
|
|
- height: 40px;
|
|
|
|
|
- border: none;
|
|
|
|
|
- resize: none;
|
|
|
|
|
- outline: none;
|
|
|
|
|
- overflow: hidden;
|
|
|
|
|
- padding: 10px; /* 统一内边距 */
|
|
|
|
|
- color: #fff;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ .file-input {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ background-color: #234a6b;
|
|
|
|
|
+ border-color: #2cb6ff !important;
|
|
|
|
|
+ color: #e2e8f0 !important;
|
|
|
|
|
+ border-radius: 6px !important;
|
|
|
|
|
+ font-size: 10px !important;
|
|
|
|
|
+ padding-right: 70px !important;
|
|
|
|
|
+ height: 36px !important;
|
|
|
|
|
+ width: 100% !important;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- .action-bar {
|
|
|
|
|
- height: 30px;
|
|
|
|
|
- display: flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- justify-content: space-between;
|
|
|
|
|
-
|
|
|
|
|
- .think-btn {
|
|
|
|
|
- border: 1px solid #ccc;
|
|
|
|
|
- width: 100px;
|
|
|
|
|
- height: 20px;
|
|
|
|
|
- line-height: 20px;
|
|
|
|
|
- text-align: center;
|
|
|
|
|
- border-radius: 5px;
|
|
|
|
|
- cursor: pointer;
|
|
|
|
|
- background: transparent;
|
|
|
|
|
- color: white;
|
|
|
|
|
- transition: background 0.3s;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ .confirm-btn {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ right: 5px;
|
|
|
|
|
+ background-color: #2cb6ff;
|
|
|
|
|
+ border: none;
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ padding: 4px 10px;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ transition: all 0.2s;
|
|
|
|
|
+ height: 28px;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- .think-btn.active {
|
|
|
|
|
- background: #1890ff;
|
|
|
|
|
- color: white;
|
|
|
|
|
- border-color: #1890ff;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ .confirm-btn:hover {
|
|
|
|
|
+ background-color: #2cb6ff;
|
|
|
}
|
|
}
|
|
|
-</style>
|
|
|
|
|
-<style>
|
|
|
|
|
- .zxm-popover-inner-content {
|
|
|
|
|
- padding: 1px;
|
|
|
|
|
|
|
+
|
|
|
|
|
+ .custom-upload {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ padding: 0 !important;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ .upload-btn {
|
|
|
|
|
+ background-color: #234a6b !important;
|
|
|
|
|
+ border: 1px solid #2188c3 !important;
|
|
|
|
|
+ color: #dbeafe !important;
|
|
|
|
|
+ border-radius: 6px !important;
|
|
|
|
|
+ font-size: 12px !important;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ transition: all 0.2s;
|
|
|
|
|
+ padding: 8px 0 !important;
|
|
|
|
|
+ width: 190% !important;
|
|
|
|
|
+ height: 36px !important;
|
|
|
|
|
+ box-sizing: border-box !important;
|
|
|
|
|
+ }
|
|
|
|
|
+ .upload-btn:hover {
|
|
|
|
|
+ background-color: #1f84bd !important;
|
|
|
|
|
+ color: #fff !important;
|
|
|
|
|
+ }
|
|
|
|
|
+ .custom-upload .ant-upload-select:hover .ant-btn {
|
|
|
|
|
+ border-color: #1f84bd !important;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+.zxm-popover-inner-content {
|
|
|
|
|
+ padding: 1px;
|
|
|
|
|
+}
|
|
|
|
|
+.message-wrapper {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: flex-start;
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+}
|
|
|
|
|
+.user-message-wrapper {
|
|
|
|
|
+ flex-direction: row-reverse;
|
|
|
|
|
+}
|
|
|
|
|
+.ai-message-wrapper {
|
|
|
|
|
+ flex-direction: row;
|
|
|
|
|
+}
|
|
|
|
|
+/* 复制图标样式 */
|
|
|
|
|
+.copy-icon {
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ margin: 4px 8px;
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+}
|
|
|
|
|
+.message-wrapper:hover .copy-icon {
|
|
|
|
|
+ opacity: 1;
|
|
|
|
|
+}
|
|
|
|
|
+.copy-icon:hover {
|
|
|
|
|
+ color: #1890ff;
|
|
|
|
|
+}
|
|
|
</style>
|
|
</style>
|