|
|
@@ -5,162 +5,193 @@
|
|
|
<img src="@/assets/images/vent/home/wakeBtn.png" />
|
|
|
</div>
|
|
|
</div>
|
|
|
- <div v-if="isShowChatBroad" class="mini-chat">
|
|
|
- <!-- 左侧折叠区域 -->
|
|
|
- <div class="left-side" :class="{ collapsed: isFold }" id="leftSide">
|
|
|
- <div
|
|
|
- class="addBtn"
|
|
|
- :style="{
|
|
|
- backgroundColor: isFold ? '' : '#2cb6ff',
|
|
|
- width: isFold ? '20px' : 'auto',
|
|
|
- }"
|
|
|
- @click="addNew"
|
|
|
- >
|
|
|
- <SvgIcon v-if="isFold" name="add" size="20" :color="''" />
|
|
|
- <span v-if="!isFold" class="btn-text">添加新对话</span>
|
|
|
- </div>
|
|
|
- <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"
|
|
|
+ <div class="container">
|
|
|
+ <div v-if="isShowChatBroad" class="mini-chat">
|
|
|
+ <!-- 左侧折叠区域 -->
|
|
|
+ <div class="left-side" :class="{ collapsed: isFold }" id="leftSide">
|
|
|
+ <div
|
|
|
+ class="addBtn"
|
|
|
:style="{
|
|
|
- backgroundImage: `url(${!isFold ? '/src/assets/images/vent/home/history.svg' : ''})`,
|
|
|
+ backgroundColor: isFold ? '' : '#2cb6ff',
|
|
|
+ width: isFold ? '20px' : 'auto',
|
|
|
}"
|
|
|
- ></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)" />
|
|
|
+ @click="addNew"
|
|
|
+ >
|
|
|
+ <SvgIcon v-if="isFold" name="add" size="20" :color="''" />
|
|
|
+ <span v-if="!isFold" class="btn-text">添加新对话</span>
|
|
|
+ </div>
|
|
|
+ <div v-if="isFold" class="historyBtn" @click="addNew">
|
|
|
+ <SvgIcon v-if="isFold" name="history" size="20" :color="''" />
|
|
|
+ </div>
|
|
|
+ <div v-else class="historyBtn1">
|
|
|
+ <span v-if="!isFold" class="btn-text">历史对话</span>
|
|
|
+ <a-list style="width: 136px" :split="false" :data-source="sessionHistory" :scroll="190" 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-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>
|
|
|
+ </a-list-item>
|
|
|
+ </template>
|
|
|
+ </a-list>
|
|
|
+ </div>
|
|
|
+ <div class="foldBtn" @click="fold">
|
|
|
+ <SvgIcon v-if="isFold" name="Fold1" size="20" :color="''" />
|
|
|
+ <SvgIcon v-else name="unfold" size="20" :color="''" />
|
|
|
+ </div>
|
|
|
</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 class="right-side">
|
|
|
- <div class="title"> 智能问答 </div>
|
|
|
- <!-- 对话区域 -->
|
|
|
- <div class="dialog-area" ref="dialogAreaRef">
|
|
|
- <div
|
|
|
- v-for="message in messageHistory"
|
|
|
- :key="message.id"
|
|
|
- class="flex items-center w-100%"
|
|
|
- :style="{ alignSelf: message.type === 'user' ? 'flex-end' : 'flex-start' }"
|
|
|
- >
|
|
|
- <template v-if="message.type === 'user'">
|
|
|
- <div class="flex-grow-1"></div>
|
|
|
- <div class="message-wrapper user-message-wrapper">
|
|
|
- <div class="ask-message">{{ message.parsedContent }}</div>
|
|
|
- <div class="copy-icon-container">
|
|
|
- <CopyOutlined class="copy-icon" @click="copyToClipboard(message.parsedContent)" title="复制消息" />
|
|
|
- <EditOutlined class="copy-icon" @click="editAsk(message.parsedContent)" title="重新编辑" />
|
|
|
+ <!-- 右侧对话框 -->
|
|
|
+ <div class="right-side">
|
|
|
+ <div class="title"> 智能问答 </div>
|
|
|
+ <!-- 对话区域 -->
|
|
|
+ <div class="dialog-area" ref="dialogAreaRef">
|
|
|
+ <div
|
|
|
+ v-for="message in messageHistory"
|
|
|
+ :key="message.id"
|
|
|
+ class="flex items-center w-100%"
|
|
|
+ :style="{ alignSelf: message.type === 'user' ? 'flex-end' : 'flex-start' }"
|
|
|
+ >
|
|
|
+ <template v-if="message.type === 'user'">
|
|
|
+ <div class="flex-grow-1"></div>
|
|
|
+ <div class="message-wrapper user-message-wrapper">
|
|
|
+ <div class="ask-message">{{ message.parsedContent }}</div>
|
|
|
+ <div class="copy-icon-container">
|
|
|
+ <CopyOutlined class="copy-icon" @click="copyToClipboard(message.parsedContent)" title="复制消息" />
|
|
|
+ <EditOutlined class="copy-icon" @click="editAsk(message.parsedContent)" title="重新编辑" />
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- <template v-else>
|
|
|
- <SvgIcon size="40" class="answerIcon" name="ai-logo" />
|
|
|
- <div class="message-wrapper ai-message-wrapper">
|
|
|
- <div class="answer-message">
|
|
|
- <div v-if="message.parsedContentR1" class="thinking-section">
|
|
|
- <div class="thinking-header" @click="isShow(message)">
|
|
|
- <span class="thinking-title"
|
|
|
- >思考过程:<RightOutlined v-if="!message.isShowThink" /> <DownOutlined v-if="message.isShowThink"
|
|
|
- /></span>
|
|
|
+ </template>
|
|
|
+ <template v-else>
|
|
|
+ <SvgIcon size="40" class="answerIcon" name="ai-logo" />
|
|
|
+ <div class="message-wrapper ai-message-wrapper">
|
|
|
+ <div class="answer-message">
|
|
|
+ <div v-if="message.parsedContentR1" class="thinking-section">
|
|
|
+ <div class="thinking-header" @click="isShow(message)">
|
|
|
+ <span class="thinking-title"
|
|
|
+ >思考过程:<RightOutlined v-if="!message.isShowThink" /> <DownOutlined v-if="message.isShowThink"
|
|
|
+ /></span>
|
|
|
+ </div>
|
|
|
+ <div v-show="message.isShowThink" class="color-gray font-size-12px" v-html="message.parsedContentR1"></div>
|
|
|
</div>
|
|
|
- <div v-show="message.isShowThink" class="color-gray font-size-12px" v-html="message.parsedContentR1"></div>
|
|
|
+ <div v-if="message.parsedContent" v-html="message.parsedContent"> </div>
|
|
|
+ </div>
|
|
|
+ <div class="copy-icon-container">
|
|
|
+ <CopyOutlined class="copy-icon" @click="copyToClipboard(message.parsedContent)" title="复制消息" />
|
|
|
+ <RedoOutlined class="copy-icon" @click="refresh()" title="重新生成" />
|
|
|
</div>
|
|
|
- <div v-if="message.parsedContent" v-html="formatMessage(message.parsedContent)"> </div>
|
|
|
- </div>
|
|
|
- <div class="copy-icon-container">
|
|
|
- <CopyOutlined class="copy-icon" @click="copyToClipboard(message.parsedContent)" title="复制消息" />
|
|
|
- <RedoOutlined class="copy-icon" @click="refresh()" title="重新生成" />
|
|
|
</div>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+ <!-- 建议信息 -->
|
|
|
+ <div v-for="(item, index) in suggestList" :key="index" class="suggestion-item" @click="handleSuggestClick(item)">
|
|
|
+ <span class="suggestion-text">{{ item }}</span>
|
|
|
+ <a-icon type="right" class="suggestion-arrow" />
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- <!-- 建议信息 -->
|
|
|
- <div v-for="(item, index) in suggestList" :key="index" class="suggestion-item" @click="handleSuggestClick(item)">
|
|
|
- <span class="suggestion-text">{{ item }}</span>
|
|
|
- <a-icon type="right" class="suggestion-arrow" />
|
|
|
+ <!-- 底部输入区 -->
|
|
|
+ <div class="input-area">
|
|
|
+ <a-textarea v-model:value="inputText" placeholder="请输入你的问题" @keyup.enter="handleSend(inputText)" class="ant-input" auto-size />
|
|
|
+ <div class="ctrl-btn">
|
|
|
+ <div class="input-controls">
|
|
|
+ <button class="control-btn" :class="{ active: isThinking }" @click="toggleThinking">深度思考</button>
|
|
|
+ <button class="control-btn" @click="stopReq()">停止响应</button>
|
|
|
+ </div>
|
|
|
+ <div class="action-bar">
|
|
|
+ <Space>
|
|
|
+ <Button class="control-btn1" size="small" @mouseenter="showModal(true)">
|
|
|
+ <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" @mouseenter="showModal(true)" @mouseleave="showModal(false)" class="file-upload">
|
|
|
+ <!-- 上传按钮 -->
|
|
|
+ <a-upload
|
|
|
+ class="custom-upload"
|
|
|
+ name="file"
|
|
|
+ :multiple="false"
|
|
|
+ :before-upload="handleBeforeUpload"
|
|
|
+ :file-list="fileList"
|
|
|
+ :remove="handleRemove"
|
|
|
+ accept=".pdf,.docx,.xlsx,.xls"
|
|
|
+ >
|
|
|
+ <a-button class="upload-btn">
|
|
|
+ <UploadOutlined></UploadOutlined>
|
|
|
+ 从本地上传
|
|
|
+ </a-button>
|
|
|
+ </a-upload>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- <!-- 底部输入区 -->
|
|
|
- <div class="input-area">
|
|
|
- <a-textarea v-model:value="inputText" placeholder="请输入你的问题" @keyup.enter="handleSend(inputText)" class="ant-input" auto-size />
|
|
|
- <div class="ctrl-btn">
|
|
|
- <div class="input-controls">
|
|
|
- <button class="control-btn" :class="{ active: isThinking }" @click="toggleThinking">深度学习</button>
|
|
|
- <button class="control-btn" @click="stopReq()">停止响应</button>
|
|
|
+ </div>
|
|
|
+ <div class="doc" v-if="isShowDoc">
|
|
|
+ <div class="close"> <button class="closeBtn" @click="close">关闭</button></div>
|
|
|
+ <!-- 已上传文件列表 -->
|
|
|
+ <div class="file-list" v-if="uploadedFiles.length">
|
|
|
+ <div class="file-item" v-for="file in uploadedFiles" :key="file.id">
|
|
|
+ <div class="file-info">
|
|
|
+ <div class="file-name" :title="file.name">{{ file.name }}</div>
|
|
|
</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 class="file-actions">
|
|
|
+ <button class="btn btn-preview" @click="previewFile(file)"> <i class="fas fa-eye"></i> 预览 </button>
|
|
|
+ <button class="btn btn-delete" @click="deleteFile(file.id)"> <i class="fas fa-trash"></i> 删除 </button>
|
|
|
</div>
|
|
|
</div>
|
|
|
- <!-- 右侧文件上传区 -->
|
|
|
- <div v-if="open" class="file-upload">
|
|
|
- <!-- 上传按钮 -->
|
|
|
- <a-upload
|
|
|
- class="custom-upload"
|
|
|
- name="file"
|
|
|
- :multiple="false"
|
|
|
- :before-upload="handleBeforeUpload"
|
|
|
- :file-list="fileList"
|
|
|
- :remove="handleRemove"
|
|
|
- >
|
|
|
- <a-button class="upload-btn">
|
|
|
- <UploadOutlined></UploadOutlined>
|
|
|
- 从本地上传
|
|
|
- </a-button>
|
|
|
- </a-upload>
|
|
|
+ </div>
|
|
|
+ <!-- 预览内容 -->
|
|
|
+ <div class="pre-container">
|
|
|
+ <!-- PDF预览 -->
|
|
|
+ <VueOfficePdf v-if="fileType === 'pdf'" :src="fileUrl" @rendered="handleRendered" @error="handleError" />
|
|
|
+ <!-- Word文档预览 -->
|
|
|
+ <VueOfficeDocx v-else-if="fileType === 'docx'" :src="fileUrl" @rendered="handleRendered" @error="handleError" />
|
|
|
+ <!-- Excel预览 -->
|
|
|
+ <VueOfficeExcel
|
|
|
+ v-else-if="fileType === 'xlsx' || fileType === 'xls'"
|
|
|
+ :options="{ xls: true }"
|
|
|
+ :src="fileUrl"
|
|
|
+ @rendered="handleRendered"
|
|
|
+ @error="handleError"
|
|
|
+ />
|
|
|
+ <!-- 不支持的文件类型 -->
|
|
|
+ <div v-else class="unsupported">
|
|
|
+ <p>不支持预览该文件类型: {{ fileType }}</p>
|
|
|
+ </div>
|
|
|
+ <!-- 加载状态 -->
|
|
|
+ <div v-if="loading" class="loading">
|
|
|
+ <p>文件加载中...</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -168,6 +199,9 @@
|
|
|
</template>
|
|
|
|
|
|
<script lang="ts" setup>
|
|
|
+import VueOfficePdf from '@vue-office/pdf';
|
|
|
+import VueOfficeDocx from '@vue-office/docx';
|
|
|
+import VueOfficeExcel from '@vue-office/excel';
|
|
|
import { ref, onMounted, nextTick } from 'vue';
|
|
|
import { SvgIcon } from '../Icon';
|
|
|
import { Space, Button, Modal, Input, message } from 'ant-design-vue';
|
|
|
@@ -187,6 +221,8 @@ import { createVNode } from 'vue';
|
|
|
import { marked } from 'marked';
|
|
|
import katex from 'katex';
|
|
|
import 'katex/dist/katex.min.css';
|
|
|
+import '@vue-office/excel/lib/index.css';
|
|
|
+import '@vue-office/docx/lib/index.css';
|
|
|
const TextArea = Input.TextArea; // 直接导入TextArea组件使用时打包报错
|
|
|
const inputText = ref(''); // 输入框内容
|
|
|
const refreshText = ref(''); //重新生成文本
|
|
|
@@ -200,6 +236,9 @@ const messageID = ref('');
|
|
|
const open = ref<boolean>(false);
|
|
|
const isThinking = ref(false); //深度思考是否开启
|
|
|
const Thinking = ref(false);
|
|
|
+const isShowDoc = ref(false);
|
|
|
+const fileType = ref('');
|
|
|
+const fileUrl = ref('');
|
|
|
const APIKEY = ref('Bearer app-tSFRUnv0Qkbtik1dwtlhnpkd');
|
|
|
interface ListItem {
|
|
|
id: number;
|
|
|
@@ -220,9 +259,30 @@ const messageHistory = ref<Message[]>([]);
|
|
|
const isFold = ref(true); // 是否折叠
|
|
|
const userid = useUserStore().getUserInfo.id as string;
|
|
|
const filePath = ref(''); // 绑定输入框值
|
|
|
+const uploadedFiles = ref([]);
|
|
|
const showConfirmBtn = ref(false); // 控制确认按钮显示状态
|
|
|
const fileList = ref([]);
|
|
|
const suggestList = ref([]); //建议列表
|
|
|
+const loading = ref(false);
|
|
|
+// 文件预览
|
|
|
+const handleRendered = () => {
|
|
|
+ loading.value = false;
|
|
|
+ console.log('文件渲染完成');
|
|
|
+};
|
|
|
+const handleError = (error) => {
|
|
|
+ loading.value = false;
|
|
|
+ console.error('文件预览错误:', error);
|
|
|
+};
|
|
|
+function previewFile(data) {
|
|
|
+ fileType.value = data.name.split('.').pop().toLowerCase();
|
|
|
+ fileUrl.value = data.source_url;
|
|
|
+}
|
|
|
+function deleteFile(fileId) {
|
|
|
+ // 确认删除
|
|
|
+ if (confirm('确定要删除这个文件吗?')) {
|
|
|
+ uploadedFiles.value = uploadedFiles.value.filter((file) => file.id !== fileId);
|
|
|
+ }
|
|
|
+}
|
|
|
//启用深度思考
|
|
|
const toggleThinking = () => {
|
|
|
isThinking.value = !isThinking.value;
|
|
|
@@ -255,6 +315,9 @@ const handleSuggestClick = (text) => {
|
|
|
};
|
|
|
function showAIChat() {
|
|
|
isShowChatBroad.value = !isShowChatBroad.value;
|
|
|
+ if (isShowChatBroad) {
|
|
|
+ isShowDoc.value = false;
|
|
|
+ }
|
|
|
}
|
|
|
//复制消息
|
|
|
function copyToClipboard(text) {
|
|
|
@@ -299,7 +362,6 @@ const renderLatexInHtml = (html) => {
|
|
|
const blockRegex = /\$\$(.*?)\$\$/gs;
|
|
|
// 匹配行内公式($...$)
|
|
|
const inlineRegex = /\$(.*?)\$/g;
|
|
|
- let matchCount = 0; // 统计匹配次数
|
|
|
// // 替换块级公式(居中显示)
|
|
|
html = html.replace(blockRegex, (match, formula) => {
|
|
|
matchCount++;
|
|
|
@@ -319,7 +381,6 @@ const renderLatexInHtml = (html) => {
|
|
|
strict: false,
|
|
|
});
|
|
|
});
|
|
|
- console.log(`最终匹配次数:${matchCount}`); // 输出总匹配次数
|
|
|
return html;
|
|
|
};
|
|
|
// Markdown + LaTeX 解析
|
|
|
@@ -534,8 +595,11 @@ async function addNew() {
|
|
|
messageID.value = '';
|
|
|
}
|
|
|
// 上传文件
|
|
|
-const showModal = () => {
|
|
|
- open.value = !open.value;
|
|
|
+const showModal = (data) => {
|
|
|
+ open.value = data;
|
|
|
+};
|
|
|
+const close = () => {
|
|
|
+ isShowDoc.value = false;
|
|
|
};
|
|
|
// 上传文件
|
|
|
const handleBeforeUpload = async (file) => {
|
|
|
@@ -554,9 +618,11 @@ const handleBeforeUpload = async (file) => {
|
|
|
|
|
|
const result = await response.json();
|
|
|
if (response.ok) {
|
|
|
- message.success('上传成功');
|
|
|
const linkText = `[${result.name}](${result.source_url})`;
|
|
|
- inputText.value += `\n${linkText}`;
|
|
|
+ // inputText.value += `\n${linkText}`;
|
|
|
+ uploadedFiles.value.push(result);
|
|
|
+ isShowDoc.value = true;
|
|
|
+ previewFile(result);
|
|
|
} else {
|
|
|
message.error(`上传失败: ${result.message || '网络错误'}`);
|
|
|
}
|
|
|
@@ -594,17 +660,22 @@ async function stopReq() {
|
|
|
}
|
|
|
}
|
|
|
//获取具体会话记录
|
|
|
-async function sessionsHistory(id: string) {
|
|
|
+async function sessionsHistory(id: string, retryCount = 0) {
|
|
|
+ const API_KEYS = ['Bearer app-tSFRUnv0Qkbtik1dwtlhnpkd', 'Bearer app-kprgsFKtySM4Wjxs0ZGzaNFN'];
|
|
|
+ // 最大重试次数
|
|
|
+ const maxRetries = API_KEYS.length - 1;
|
|
|
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: APIKEY.value,
|
|
|
+ Authorization: API_KEYS[retryCount],
|
|
|
},
|
|
|
});
|
|
|
+ if (!response.ok) {
|
|
|
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
|
+ }
|
|
|
const data = await response.json();
|
|
|
- console.log(data, '123');
|
|
|
if (data.data.length > 0) {
|
|
|
messageHistory.value = [];
|
|
|
data.data.forEach((item: any) => {
|
|
|
@@ -654,11 +725,15 @@ async function sessionsHistory(id: string) {
|
|
|
});
|
|
|
});
|
|
|
}
|
|
|
- if (!response.ok) {
|
|
|
- throw new Error('Network response was not ok');
|
|
|
- }
|
|
|
} catch (error) {
|
|
|
- console.error('保存失败:', error);
|
|
|
+ // 判断是否达到最大重试次数
|
|
|
+ if (retryCount < maxRetries) {
|
|
|
+ console.warn(`请求失败,正在尝试第 ${retryCount + 2} 个 API Key...`);
|
|
|
+ return sessionsHistory(id, retryCount + 1);
|
|
|
+ } else {
|
|
|
+ console.error('所有 API Key 均尝试失败:', error);
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
}
|
|
|
editingId.value = null;
|
|
|
}
|
|
|
@@ -753,18 +828,51 @@ const fold = () => {
|
|
|
};
|
|
|
// 获取历史会话列表
|
|
|
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: APIKEY.value,
|
|
|
- },
|
|
|
- });
|
|
|
- const data = await response.json();
|
|
|
- sessionHistory.value = data.data;
|
|
|
- data.data.forEach((item) => {
|
|
|
- currentSessionID.value = item.conversation_id;
|
|
|
- });
|
|
|
+ sessionHistory.value = [];
|
|
|
+ try {
|
|
|
+ // 并行请求两个接口,提升效率
|
|
|
+ const [response1, response2] = await Promise.all([
|
|
|
+ // 第一个请求
|
|
|
+ fetch(`http://39.97.59.228:8000/v1/conversations?user=${userid}`, {
|
|
|
+ method: 'get',
|
|
|
+ headers: {
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
+ Authorization: 'Bearer app-tSFRUnv0Qkbtik1dwtlhnpkd',
|
|
|
+ },
|
|
|
+ }),
|
|
|
+ // 第二个请求
|
|
|
+ fetch(`http://39.97.59.228:8000/v1/conversations?user=${userid}`, {
|
|
|
+ method: 'get',
|
|
|
+ headers: {
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
+ Authorization: 'Bearer app-kprgsFKtySM4Wjxs0ZGzaNFN',
|
|
|
+ },
|
|
|
+ }),
|
|
|
+ ]);
|
|
|
+
|
|
|
+ // 检查响应是否成功
|
|
|
+ if (!response1.ok || !response2.ok) {
|
|
|
+ throw new Error('接口请求失败');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 解析两个响应的JSON数据
|
|
|
+ const [data1, data2] = await Promise.all([response1.json(), response2.json()]);
|
|
|
+
|
|
|
+ // 合并两个数组(核心修改:用扩展运算符合并)
|
|
|
+ // 确保 data1.data 和 data2.data 都是数组(防止接口返回异常)
|
|
|
+ const list1 = Array.isArray(data1.data) ? data1.data : [];
|
|
|
+ const list2 = Array.isArray(data2.data) ? data2.data : [];
|
|
|
+
|
|
|
+ // 合并数组并赋值给 sessionHistory
|
|
|
+ sessionHistory.value = [...list1, ...list2];
|
|
|
+ sessionHistory.value.forEach((item) => {
|
|
|
+ currentSessionID.value = item.id;
|
|
|
+ });
|
|
|
+ console.log(sessionHistory.value, '历史数据');
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取历史数据失败:', error);
|
|
|
+ // 错误处理(比如提示用户)
|
|
|
+ }
|
|
|
}
|
|
|
//格式化消息
|
|
|
const formatMessage = (text) => {
|
|
|
@@ -799,21 +907,70 @@ onMounted(() => {
|
|
|
margin-right: 5px;
|
|
|
margin-bottom: 5px;
|
|
|
}
|
|
|
+.container {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+}
|
|
|
.mini-chat {
|
|
|
display: flex;
|
|
|
- width: 800px;
|
|
|
+ min-width: none;
|
|
|
+ width: 950px;
|
|
|
height: 85%;
|
|
|
border-radius: 4px;
|
|
|
position: fixed;
|
|
|
- top: 70px;
|
|
|
+ top: 50px;
|
|
|
right: 20px;
|
|
|
- background-color: rgb(255, 255, 255);
|
|
|
- background: url('../../assets/images/warn-dialog-bg.png') no-repeat center;
|
|
|
- background-size: 103% 124%;
|
|
|
+ padding: 10px;
|
|
|
+ background-color: #0a1a2f;
|
|
|
+ background-image: linear-gradient(to bottom, #0b69b6 1%, #0a1a2f 100%), linear-gradient(to left, #0b69b6, #0a1a2f),
|
|
|
+ linear-gradient(to top, #0b69b6, #0a1a2f), linear-gradient(to right, #0b69b6, #0a1a2f);
|
|
|
+ background-size: 100% 5px, 5px 100%, 100% 5px, 5px 100%;
|
|
|
+ background-position: top left, top right, bottom right, bottom left;
|
|
|
+ background-repeat: no-repeat;
|
|
|
z-index: 9999999;
|
|
|
color: #fff;
|
|
|
}
|
|
|
+.doc {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ min-width: 600px;
|
|
|
+ height: 85%;
|
|
|
+ border-radius: 4px;
|
|
|
+ position: fixed;
|
|
|
+ top: 50px;
|
|
|
+ right: 51%;
|
|
|
+ padding: 10px;
|
|
|
+ background-color: #0a1a2f;
|
|
|
+ background-image: linear-gradient(to bottom, #0b69b6 1%, #0a1a2f 100%), linear-gradient(to left, #0b69b6, #0a1a2f),
|
|
|
+ linear-gradient(to top, #0b69b6, #0a1a2f), linear-gradient(to right, #0b69b6, #0a1a2f);
|
|
|
+ background-size: 100% 5px, 5px 100%, 100% 5px, 5px 100%;
|
|
|
+ background-position: top left, top right, bottom right, bottom left;
|
|
|
+ background-repeat: no-repeat;
|
|
|
+ z-index: 9999999;
|
|
|
+ color: #fff;
|
|
|
+}
|
|
|
+
|
|
|
+.close {
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+ align-items: center;
|
|
|
+ width: 100%;
|
|
|
+ padding: 1px;
|
|
|
+ border-radius: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.closeBtn {
|
|
|
+ background-color: #007bff;
|
|
|
+ color: white;
|
|
|
+ border: none;
|
|
|
+ border-radius: 4px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+}
|
|
|
|
|
|
+.closeBtn:hover {
|
|
|
+ background-color: #0056b3;
|
|
|
+}
|
|
|
.left-side {
|
|
|
background: #0c2842;
|
|
|
transition: width 0.5s ease; /* 平滑过渡动画 */
|
|
|
@@ -838,7 +995,7 @@ onMounted(() => {
|
|
|
cursor: pointer;
|
|
|
}
|
|
|
.custom-list {
|
|
|
- height: 680px;
|
|
|
+ height: 650px;
|
|
|
overflow-y: auto;
|
|
|
}
|
|
|
.text-container {
|
|
|
@@ -1262,7 +1419,6 @@ onMounted(() => {
|
|
|
.copy-icon:hover {
|
|
|
color: #1890ff;
|
|
|
}
|
|
|
-
|
|
|
::v-deep table {
|
|
|
border-collapse: collapse;
|
|
|
width: 100%;
|
|
|
@@ -1281,3 +1437,80 @@ onMounted(() => {
|
|
|
text-align: center;
|
|
|
}
|
|
|
</style>
|
|
|
+<style scoped>
|
|
|
+/* 已上传文件列表 */
|
|
|
+.file-list {
|
|
|
+ margin-top: 20px;
|
|
|
+}
|
|
|
+.pre-container {
|
|
|
+ flex: 1;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ overflow: auto;
|
|
|
+}
|
|
|
+.vue-office-excel {
|
|
|
+ background: #fff !important;
|
|
|
+}
|
|
|
+
|
|
|
+.file-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ padding: 12px 15px;
|
|
|
+ background-color: #ddd;
|
|
|
+ border-radius: 6px;
|
|
|
+ margin-bottom: 10px;
|
|
|
+ transition: background-color 0.2s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.file-item:hover {
|
|
|
+ background-color: #f0f2f5;
|
|
|
+}
|
|
|
+
|
|
|
+.file-info {
|
|
|
+ flex: 1;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.file-name {
|
|
|
+ font-size: 15px;
|
|
|
+ color: #1d2129;
|
|
|
+ white-space: nowrap;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ margin-bottom: 3px;
|
|
|
+}
|
|
|
+
|
|
|
+.file-actions {
|
|
|
+ display: flex;
|
|
|
+ gap: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.btn {
|
|
|
+ padding: 0px 15px;
|
|
|
+ margin-left: 10px;
|
|
|
+ border-radius: 4px;
|
|
|
+ border: none;
|
|
|
+ font-size: 14px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.btn-preview {
|
|
|
+ background-color: #165dff;
|
|
|
+ color: white;
|
|
|
+}
|
|
|
+
|
|
|
+.btn-preview:hover {
|
|
|
+ background-color: #0d47a1;
|
|
|
+}
|
|
|
+
|
|
|
+.btn-delete {
|
|
|
+ background-color: #f2f3f5;
|
|
|
+ color: #4e5969;
|
|
|
+}
|
|
|
+
|
|
|
+.btn-delete:hover {
|
|
|
+ background-color: #e5e6eb;
|
|
|
+ color: #1d2129;
|
|
|
+}
|
|
|
+</style>
|