|
@@ -1,6 +1,19 @@
|
|
<template>
|
|
<template>
|
|
<div class="layout">
|
|
<div class="layout">
|
|
- <div class="menu">
|
|
|
|
|
|
+ <div class="menu" :class="{ collapsed: isMenuCollapsed }">
|
|
|
|
+ <!-- 添加收缩按钮 -->
|
|
|
|
+ <div
|
|
|
|
+ class="collapse-left-button"
|
|
|
|
+ :class="{ 'menu-collapsed': isMenuCollapsed }"
|
|
|
|
+ @click="toggleMenu"
|
|
|
|
+ >
|
|
|
|
+ <img
|
|
|
|
+ :src="isMenuCollapsed ? rightIcon : leftIcon"
|
|
|
|
+ alt="toggle"
|
|
|
|
+ class="collapse-icon"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+
|
|
<!-- Logo -->
|
|
<!-- Logo -->
|
|
<div class="logo">
|
|
<div class="logo">
|
|
<img
|
|
<img
|
|
@@ -44,14 +57,38 @@
|
|
<div class="chat">
|
|
<div class="chat">
|
|
<!-- 欢迎消息 -->
|
|
<!-- 欢迎消息 -->
|
|
<div v-if="messages.length === 0" class="welcome-section">
|
|
<div v-if="messages.length === 0" class="welcome-section">
|
|
-
|
|
|
|
<!-- Do you want 部分 -->
|
|
<!-- Do you want 部分 -->
|
|
<div class="suggestion-section">
|
|
<div class="suggestion-section">
|
|
<h3>嗨,我是你的智能助手</h3>
|
|
<h3>嗨,我是你的智能助手</h3>
|
|
|
|
|
|
<div class="cards-container">
|
|
<div class="cards-container">
|
|
- <!-- Hot Topics 卡片 -->
|
|
|
|
<div class="suggestion-card">
|
|
<div class="suggestion-card">
|
|
|
|
+ <div class="card-header">
|
|
|
|
+ <img
|
|
|
|
+ src="../assets/svg/hot-for-ux.svg"
|
|
|
|
+ alt="hot topics"
|
|
|
|
+ class="tab-icon"
|
|
|
|
+ />
|
|
|
|
+ <h4>推荐问题</h4>
|
|
|
|
+ </div>
|
|
|
|
+ <p class="card-subtitle">为您精选的热门问题</p>
|
|
|
|
+ <div class="suggestion-items">
|
|
|
|
+ <!-- Combine both QA pairs and guides -->
|
|
|
|
+ <div
|
|
|
|
+ v-for="(item, index) in [
|
|
|
|
+ ...currentQaPairs,
|
|
|
|
+ ...currentDesignGuides,
|
|
|
|
+ ]"
|
|
|
|
+ :key="index"
|
|
|
|
+ class="suggestion-item"
|
|
|
|
+ @click="handleQaClick(item)"
|
|
|
|
+ >
|
|
|
|
+ {{ item.question }}
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <!-- Hot Topics 卡片 --><!-- Design Guide 卡片 -->
|
|
|
|
+ <!-- <div class="suggestion-card">
|
|
<div class="card-header">
|
|
<div class="card-header">
|
|
<img
|
|
<img
|
|
src="../assets/svg/hot-for-ux.svg"
|
|
src="../assets/svg/hot-for-ux.svg"
|
|
@@ -62,9 +99,9 @@
|
|
</div>
|
|
</div>
|
|
<p class="card-subtitle">我们可以这样向知识库提问</p>
|
|
<p class="card-subtitle">我们可以这样向知识库提问</p>
|
|
<div class="suggestion-items">
|
|
<div class="suggestion-items">
|
|
- <div
|
|
|
|
- v-for="(qa, index) in currentQaPairs"
|
|
|
|
- :key="index"
|
|
|
|
|
|
+ <div
|
|
|
|
+ v-for="(qa, index) in currentQaPairs"
|
|
|
|
+ :key="index"
|
|
class="suggestion-item"
|
|
class="suggestion-item"
|
|
@click="handleQaClick(qa)"
|
|
@click="handleQaClick(qa)"
|
|
>
|
|
>
|
|
@@ -73,7 +110,7 @@
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
- <!-- Design Guide 卡片 -->
|
|
|
|
|
|
+
|
|
<div class="suggestion-card">
|
|
<div class="suggestion-card">
|
|
<div class="card-header">
|
|
<div class="card-header">
|
|
<img
|
|
<img
|
|
@@ -85,16 +122,16 @@
|
|
</div>
|
|
</div>
|
|
<p class="card-subtitle">搜索网上的热门回答</p>
|
|
<p class="card-subtitle">搜索网上的热门回答</p>
|
|
<div class="suggestion-items">
|
|
<div class="suggestion-items">
|
|
- <div
|
|
|
|
- v-for="(guide, index) in currentDesignGuides"
|
|
|
|
- :key="index"
|
|
|
|
|
|
+ <div
|
|
|
|
+ v-for="(guide, index) in currentDesignGuides"
|
|
|
|
+ :key="index"
|
|
class="suggestion-item"
|
|
class="suggestion-item"
|
|
@click="handleQaClick(guide)"
|
|
@click="handleQaClick(guide)"
|
|
>
|
|
>
|
|
{{ guide.question }}
|
|
{{ guide.question }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
- </div>
|
|
|
|
|
|
+ </div> -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@@ -142,7 +179,9 @@
|
|
<div class="file-info-display">
|
|
<div class="file-info-display">
|
|
<img :src="getFileIcon(file)" class="file-type-icon" />
|
|
<img :src="getFileIcon(file)" class="file-type-icon" />
|
|
<div class="file-details">
|
|
<div class="file-details">
|
|
- <span class="file-name" :title="file.name">{{ file.name }}</span>
|
|
|
|
|
|
+ <span class="file-name" :title="file.name">{{
|
|
|
|
+ file.name
|
|
|
|
+ }}</span>
|
|
<span class="file-size">{{ file.size }}</span>
|
|
<span class="file-size">{{ file.size }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@@ -176,12 +215,6 @@
|
|
<img v-else :src="playIcon" alt="播放" class="audio-icon" />
|
|
<img v-else :src="playIcon" alt="播放" class="audio-icon" />
|
|
</button>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
-
|
|
|
|
-
|
|
|
|
-
|
|
|
|
-
|
|
|
|
-
|
|
|
|
-
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@@ -207,6 +240,26 @@
|
|
/>
|
|
/>
|
|
<span>Design Guide</span>
|
|
<span>Design Guide</span>
|
|
</div>
|
|
</div>
|
|
|
|
+ <!-- 添加互联网搜索开关 -->
|
|
|
|
+ <div class="web-search-toggle">
|
|
|
|
+ <input
|
|
|
|
+ type="checkbox"
|
|
|
|
+ id="webSearchToggle"
|
|
|
|
+ v-model="enableWebSearch"
|
|
|
|
+ :disabled="attachedFiles.length > 0"
|
|
|
|
+ class="toggle-input"
|
|
|
|
+ />
|
|
|
|
+ <label
|
|
|
|
+ for="webSearchToggle"
|
|
|
|
+ class="toggle-label"
|
|
|
|
+ :class="{ disabled: attachedFiles.length > 0 }"
|
|
|
|
+ >
|
|
|
|
+ 启用互联网搜索
|
|
|
|
+ <!-- <span v-if="attachedFiles.length > 0" class="disabled-hint">
|
|
|
|
+ (文件上传时不可用)
|
|
|
|
+ </span> -->
|
|
|
|
+ </label>
|
|
|
|
+ </div>
|
|
</div>
|
|
</div>
|
|
<!-- 添加附件面板 -->
|
|
<!-- 添加附件面板 -->
|
|
<div v-if="showAttachments" class="attachments-panel">
|
|
<div v-if="showAttachments" class="attachments-panel">
|
|
@@ -289,10 +342,14 @@
|
|
v-else
|
|
v-else
|
|
:src="getFileIcon(file)"
|
|
:src="getFileIcon(file)"
|
|
alt="file"
|
|
alt="file"
|
|
- :class="isImageFile(file) || isVideoFile(file) ? 'file-icon' : ''"
|
|
|
|
|
|
+ :class="
|
|
|
|
+ isImageFile(file) || isVideoFile(file)
|
|
|
|
+ ? 'file-icon'
|
|
|
|
+ : ''
|
|
|
|
+ "
|
|
/>
|
|
/>
|
|
- <button
|
|
|
|
- class="remove-btn"
|
|
|
|
|
|
+ <button
|
|
|
|
+ class="remove-btn"
|
|
@click.stop="removeFile(file)"
|
|
@click.stop="removeFile(file)"
|
|
v-if="!file.isUploading"
|
|
v-if="!file.isUploading"
|
|
>
|
|
>
|
|
@@ -367,7 +424,11 @@
|
|
/>
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div class="right-menu-content">
|
|
<div class="right-menu-content">
|
|
- <SearchResults />
|
|
|
|
|
|
+ <SearchResults
|
|
|
|
+ :searchResults="searchResults"
|
|
|
|
+ :loading="isSearchLoading"
|
|
|
|
+ :total="searchTotal"
|
|
|
|
+ />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@@ -406,11 +467,11 @@ import playIcon from "../assets/svg/play.svg";
|
|
import axios from "axios";
|
|
import axios from "axios";
|
|
import { useStore } from "vuex";
|
|
import { useStore } from "vuex";
|
|
import MarkdownIt from "markdown-it";
|
|
import MarkdownIt from "markdown-it";
|
|
-import ModelConfig from '../components/ModelConfig/index.vue';
|
|
|
|
-import SearchResults from '../components/SearchResults/index.vue';
|
|
|
|
-import VoiceConfig from '../components/VoiceConfig/index.vue';
|
|
|
|
-import KnowledgeConfig from '../components/KnowledgeConfig/index.vue';
|
|
|
|
-import ExportButton from '../components/ExportButton/index.vue';
|
|
|
|
|
|
+import ModelConfig from "../components/ModelConfig/index.vue";
|
|
|
|
+import SearchResults from "../components/SearchResults/index.vue";
|
|
|
|
+import VoiceConfig from "../components/VoiceConfig/index.vue";
|
|
|
|
+import KnowledgeConfig from "../components/KnowledgeConfig/index.vue";
|
|
|
|
+import ExportButton from "../components/ExportButton/index.vue";
|
|
|
|
|
|
// 初始化 markdown-it
|
|
// 初始化 markdown-it
|
|
const md = new MarkdownIt({
|
|
const md = new MarkdownIt({
|
|
@@ -463,20 +524,38 @@ const currentQaPairs = ref([]);
|
|
const currentDesignGuides = ref([]);
|
|
const currentDesignGuides = ref([]);
|
|
let rotationInterval = null;
|
|
let rotationInterval = null;
|
|
|
|
|
|
|
|
+// 添加新的响应式状态
|
|
|
|
+const searchResults = ref([]);
|
|
|
|
+const isSearchLoading = ref(false);
|
|
|
|
+const searchTotal = ref(0);
|
|
|
|
+
|
|
|
|
+// 添加互联网搜索开关状态
|
|
|
|
+const enableWebSearch = ref(false);
|
|
|
|
+
|
|
|
|
+// 添加新的响应式变量
|
|
|
|
+const isMenuCollapsed = ref(false);
|
|
|
|
+
|
|
|
|
+// 添加切换菜单方法
|
|
|
|
+const toggleMenu = () => {
|
|
|
|
+ isMenuCollapsed.value = !isMenuCollapsed.value;
|
|
|
|
+};
|
|
|
|
+
|
|
// 修改获取文档摘要的方法
|
|
// 修改获取文档摘要的方法
|
|
const fetchDocumentSummary = async () => {
|
|
const fetchDocumentSummary = async () => {
|
|
isLoadingSummary.value = true;
|
|
isLoadingSummary.value = true;
|
|
try {
|
|
try {
|
|
- const response = await axios.get(`${import.meta.env.VITE_BASE_AI_API}/api/document/summary`);
|
|
|
|
- console.log('API Response:', response.data);
|
|
|
|
-
|
|
|
|
|
|
+ const response = await axios.get(
|
|
|
|
+ `${import.meta.env.VITE_BASE_AI_API}/api/document/summary`
|
|
|
|
+ );
|
|
|
|
+ console.log("API Response:", response.data);
|
|
|
|
+
|
|
if (response.data.code === 200) {
|
|
if (response.data.code === 200) {
|
|
documentSummary.value = response.data.data.items;
|
|
documentSummary.value = response.data.data.items;
|
|
- console.log('Document Summary:', documentSummary.value);
|
|
|
|
|
|
+ console.log("Document Summary:", documentSummary.value);
|
|
startRotation();
|
|
startRotation();
|
|
}
|
|
}
|
|
} catch (error) {
|
|
} catch (error) {
|
|
- console.error('获取文档摘要失败:', error);
|
|
|
|
|
|
+ console.error("获取文档摘要失败:", error);
|
|
} finally {
|
|
} finally {
|
|
isLoadingSummary.value = false;
|
|
isLoadingSummary.value = false;
|
|
}
|
|
}
|
|
@@ -490,50 +569,54 @@ const startRotation = () => {
|
|
|
|
|
|
const updateItems = () => {
|
|
const updateItems = () => {
|
|
if (!documentSummary.value?.length) {
|
|
if (!documentSummary.value?.length) {
|
|
- console.log('No document summary data available');
|
|
|
|
|
|
+ console.log("No document summary data available");
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
// 处理第一项数据(Hot Topics)
|
|
// 处理第一项数据(Hot Topics)
|
|
try {
|
|
try {
|
|
const hotTopicsData = documentSummary.value[0];
|
|
const hotTopicsData = documentSummary.value[0];
|
|
console.log(hotTopicsData);
|
|
console.log(hotTopicsData);
|
|
if (hotTopicsData?.questions) {
|
|
if (hotTopicsData?.questions) {
|
|
- const parsedQaPairs = /* JSON.parse( */hotTopicsData.questions/* .replace(/'/g, '"')); */
|
|
|
|
- const formattedQaPairs = parsedQaPairs.map(question => ({ question }));
|
|
|
|
|
|
+ const parsedQaPairs =
|
|
|
|
+ /* JSON.parse( */ hotTopicsData.questions; /* .replace(/'/g, '"')); */
|
|
|
|
+ const formattedQaPairs = parsedQaPairs.map((question) => ({
|
|
|
|
+ question,
|
|
|
|
+ }));
|
|
// 随机选择3个问题
|
|
// 随机选择3个问题
|
|
const shuffled = [...formattedQaPairs].sort(() => 0.5 - Math.random());
|
|
const shuffled = [...formattedQaPairs].sort(() => 0.5 - Math.random());
|
|
currentQaPairs.value = shuffled.slice(0, 3);
|
|
currentQaPairs.value = shuffled.slice(0, 3);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
} catch (error) {
|
|
- console.error('解析 Hot Topics 失败:', error);
|
|
|
|
|
|
+ console.error("解析 Hot Topics 失败:", error);
|
|
}
|
|
}
|
|
|
|
|
|
// 处理第二项数据(Design Guide)
|
|
// 处理第二项数据(Design Guide)
|
|
try {
|
|
try {
|
|
const designGuideData = documentSummary.value[1];
|
|
const designGuideData = documentSummary.value[1];
|
|
if (designGuideData?.questions) {
|
|
if (designGuideData?.questions) {
|
|
- const parsedGuides = /* JSON.parse( */designGuideData.questions/* .replace(/'/g, '"')); */
|
|
|
|
- const formattedGuides = parsedGuides.map(question => ({ question }));
|
|
|
|
|
|
+ const parsedGuides =
|
|
|
|
+ /* JSON.parse( */ designGuideData.questions; /* .replace(/'/g, '"')); */
|
|
|
|
+ const formattedGuides = parsedGuides.map((question) => ({ question }));
|
|
// 随机选择3个指南
|
|
// 随机选择3个指南
|
|
const shuffled = [...formattedGuides].sort(() => 0.5 - Math.random());
|
|
const shuffled = [...formattedGuides].sort(() => 0.5 - Math.random());
|
|
currentDesignGuides.value = shuffled.slice(0, 3);
|
|
currentDesignGuides.value = shuffled.slice(0, 3);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
} catch (error) {
|
|
- console.error('解析 Design Guide 失败:', error);
|
|
|
|
|
|
+ console.error("解析 Design Guide 失败:", error);
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
|
|
// 初始更新
|
|
// 初始更新
|
|
updateItems();
|
|
updateItems();
|
|
-
|
|
|
|
|
|
+
|
|
// 每5秒更新一次
|
|
// 每5秒更新一次
|
|
rotationInterval = setInterval(updateItems, 5000);
|
|
rotationInterval = setInterval(updateItems, 5000);
|
|
};
|
|
};
|
|
|
|
|
|
// 处理QA点击事件
|
|
// 处理QA点击事件
|
|
const handleQaClick = (qa) => {
|
|
const handleQaClick = (qa) => {
|
|
- console.log('QA clicked:', qa); // 添加日志
|
|
|
|
|
|
+ console.log("QA clicked:", qa); // 添加日志
|
|
if (qa?.question) {
|
|
if (qa?.question) {
|
|
inputContent.value = qa.question;
|
|
inputContent.value = qa.question;
|
|
sendMessage();
|
|
sendMessage();
|
|
@@ -596,9 +679,9 @@ const uploadFile = async (file) => {
|
|
const handleFileUpload = async (event) => {
|
|
const handleFileUpload = async (event) => {
|
|
const files = Array.from(event.target.files);
|
|
const files = Array.from(event.target.files);
|
|
if (files.length === 0) return;
|
|
if (files.length === 0) return;
|
|
- // 添加文件大小检查
|
|
|
|
- const MAX_VIDEO_SIZE = 10 * 1024 * 1024; // 10MB in bytes
|
|
|
|
- const invalidFiles = files.filter(file => {
|
|
|
|
|
|
+ // 添加文件大小检查
|
|
|
|
+ const MAX_VIDEO_SIZE = 10 * 1024 * 1024; // 10MB in bytes
|
|
|
|
+ const invalidFiles = files.filter((file) => {
|
|
if (isVideoFile(file) && file.size > MAX_VIDEO_SIZE) {
|
|
if (isVideoFile(file) && file.size > MAX_VIDEO_SIZE) {
|
|
return true;
|
|
return true;
|
|
}
|
|
}
|
|
@@ -607,8 +690,8 @@ const handleFileUpload = async (event) => {
|
|
|
|
|
|
if (invalidFiles.length > 0) {
|
|
if (invalidFiles.length > 0) {
|
|
// 如果有超出大小限制的视频文件,显示错误提示
|
|
// 如果有超出大小限制的视频文件,显示错误提示
|
|
- alert('Video files must not exceed 10MB');
|
|
|
|
- event.target.value = ''; // 清空input
|
|
|
|
|
|
+ alert("Video files must not exceed 10MB");
|
|
|
|
+ event.target.value = ""; // 清空input
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
isUploading.value = true;
|
|
isUploading.value = true;
|
|
@@ -621,27 +704,31 @@ const handleFileUpload = async (event) => {
|
|
size: file.size,
|
|
size: file.size,
|
|
sizeFormatted: formatFileSize(file.size),
|
|
sizeFormatted: formatFileSize(file.size),
|
|
type: file.type,
|
|
type: file.type,
|
|
- isUploading: true
|
|
|
|
|
|
+ isUploading: true,
|
|
};
|
|
};
|
|
-
|
|
|
|
|
|
+
|
|
// 先添加到文件列表中
|
|
// 先添加到文件列表中
|
|
attachedFiles.value.push(fileWithStatus);
|
|
attachedFiles.value.push(fileWithStatus);
|
|
|
|
|
|
try {
|
|
try {
|
|
const url = await uploadFile(file);
|
|
const url = await uploadFile(file);
|
|
// 直接修改数组中的对象
|
|
// 直接修改数组中的对象
|
|
- const index = attachedFiles.value.findIndex(f => f.name === file.name);
|
|
|
|
|
|
+ const index = attachedFiles.value.findIndex(
|
|
|
|
+ (f) => f.name === file.name
|
|
|
|
+ );
|
|
if (index !== -1) {
|
|
if (index !== -1) {
|
|
// 使用数组替换方式更新,确保响应式更新
|
|
// 使用数组替换方式更新,确保响应式更新
|
|
attachedFiles.value.splice(index, 1, {
|
|
attachedFiles.value.splice(index, 1, {
|
|
...fileWithStatus,
|
|
...fileWithStatus,
|
|
url,
|
|
url,
|
|
- isUploading: false
|
|
|
|
|
|
+ isUploading: false,
|
|
});
|
|
});
|
|
}
|
|
}
|
|
} catch (error) {
|
|
} catch (error) {
|
|
// 如果上传失败,从列表中移除该文件
|
|
// 如果上传失败,从列表中移除该文件
|
|
- attachedFiles.value = attachedFiles.value.filter(f => f.name !== file.name);
|
|
|
|
|
|
+ attachedFiles.value = attachedFiles.value.filter(
|
|
|
|
+ (f) => f.name !== file.name
|
|
|
|
+ );
|
|
console.error("单个文件上传失败:", error);
|
|
console.error("单个文件上传失败:", error);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -651,6 +738,10 @@ const handleFileUpload = async (event) => {
|
|
isUploading.value = false;
|
|
isUploading.value = false;
|
|
event.target.value = ""; // 清空input以允许重复上传相同文件
|
|
event.target.value = ""; // 清空input以允许重复上传相同文件
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ if (files.length > 0 && enableWebSearch.value) {
|
|
|
|
+ enableWebSearch.value = false; // 如果有文件上传,自动关闭互联网搜索
|
|
|
|
+ }
|
|
};
|
|
};
|
|
|
|
|
|
const formatFileSize = (bytes) => {
|
|
const formatFileSize = (bytes) => {
|
|
@@ -746,6 +837,12 @@ const sendMessage = async () => {
|
|
messages.value.push(userMessage);
|
|
messages.value.push(userMessage);
|
|
scrollToBottom();
|
|
scrollToBottom();
|
|
|
|
|
|
|
|
+ // 异步执行搜索,但不等待结果
|
|
|
|
+
|
|
|
|
+ sendMessageS(messageContent).catch((error) => {
|
|
|
|
+ console.error("Web search failed:", error);
|
|
|
|
+ });
|
|
|
|
+
|
|
// 保存当前输入内容并清空输入框和附件
|
|
// 保存当前输入内容并清空输入框和附件
|
|
const currentInput = inputContent.value;
|
|
const currentInput = inputContent.value;
|
|
showAttachments.value = false;
|
|
showAttachments.value = false;
|
|
@@ -765,19 +862,21 @@ const sendMessage = async () => {
|
|
scrollToBottom();
|
|
scrollToBottom();
|
|
|
|
|
|
try {
|
|
try {
|
|
- const response = await axios.post(
|
|
|
|
- `${import.meta.env.VITE_BASE_AI_API}/chatbot/multimodal`,
|
|
|
|
- {
|
|
|
|
- message: currentInput,
|
|
|
|
- chat_config_id: "2",
|
|
|
|
- user_id: "13365429324",
|
|
|
|
- session_id: session_id.value,
|
|
|
|
- source: "pc",
|
|
|
|
- image_urls: imageUrls,
|
|
|
|
- video_urls: videoUrls,
|
|
|
|
- documents: documentUrls,
|
|
|
|
- }
|
|
|
|
- );
|
|
|
|
|
|
+ // 根据开关状态选择不同的 API 端点
|
|
|
|
+ const apiEndpoint = enableWebSearch.value
|
|
|
|
+ ? `${import.meta.env.VITE_BASE_AI_API}/api/chat/web-search-llm/`
|
|
|
|
+ : `${import.meta.env.VITE_BASE_AI_API}/chatbot/multimodal`;
|
|
|
|
+
|
|
|
|
+ const response = await axios.post(apiEndpoint, {
|
|
|
|
+ message: currentInput,
|
|
|
|
+ chat_config_id: "2",
|
|
|
|
+ user_id: "13365429324",
|
|
|
|
+ session_id: session_id.value,
|
|
|
|
+ source: "pc",
|
|
|
|
+ image_urls: imageUrls,
|
|
|
|
+ video_urls: videoUrls,
|
|
|
|
+ documents: documentUrls,
|
|
|
|
+ });
|
|
|
|
|
|
// 更新 session_id
|
|
// 更新 session_id
|
|
if (response.data.data.session_id) {
|
|
if (response.data.data.session_id) {
|
|
@@ -795,7 +894,7 @@ const sendMessage = async () => {
|
|
displayContent: "",
|
|
displayContent: "",
|
|
audioData: response.data.data.audio_info?.audio || null, // 保存音频数据
|
|
audioData: response.data.data.audio_info?.audio || null, // 保存音频数据
|
|
});
|
|
});
|
|
-
|
|
|
|
|
|
+ enableWebSearch.value=false
|
|
messages.value.push(aiMessage);
|
|
messages.value.push(aiMessage);
|
|
// 如果有音频数据,自动播放
|
|
// 如果有音频数据,自动播放
|
|
if (aiMessage.audioData) {
|
|
if (aiMessage.audioData) {
|
|
@@ -820,6 +919,34 @@ const sendMessage = async () => {
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
|
|
|
|
+// 修改 sendMessageS 方法
|
|
|
|
+const sendMessageS = async (query) => {
|
|
|
|
+ isSearchLoading.value = true;
|
|
|
|
+ try {
|
|
|
|
+ const response = await axios.post(
|
|
|
|
+ `${import.meta.env.VITE_BASE_AI_API}/api/web-search-results/`,
|
|
|
|
+ {
|
|
|
|
+ query: query,
|
|
|
|
+ num_results: 20,
|
|
|
|
+ page: 1,
|
|
|
|
+ page_size: 20,
|
|
|
|
+ engine: "bing",
|
|
|
|
+ }
|
|
|
|
+ );
|
|
|
|
+
|
|
|
|
+ if (response.data && response.data.code === 200) {
|
|
|
|
+ searchResults.value = response.data.data.results || [];
|
|
|
|
+ searchTotal.value = response.data.data.total_results || 0;
|
|
|
|
+ }
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error("Web search failed:", error);
|
|
|
|
+ searchResults.value = [];
|
|
|
|
+ searchTotal.value = 0;
|
|
|
|
+ } finally {
|
|
|
|
+ isSearchLoading.value = false;
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
+
|
|
// 修改打字效果方法
|
|
// 修改打字效果方法
|
|
const typeMessage = async (message) => {
|
|
const typeMessage = async (message) => {
|
|
if (!message || !message.content) {
|
|
if (!message || !message.content) {
|
|
@@ -881,12 +1008,12 @@ const toggleRightMenu = () => {
|
|
|
|
|
|
const handleFileDrop = async (event) => {
|
|
const handleFileDrop = async (event) => {
|
|
if (isUploading.value) return;
|
|
if (isUploading.value) return;
|
|
-
|
|
|
|
|
|
+
|
|
const files = Array.from(event.dataTransfer.files);
|
|
const files = Array.from(event.dataTransfer.files);
|
|
if (files.length === 0) return;
|
|
if (files.length === 0) return;
|
|
-// 添加文件大小检查
|
|
|
|
-const MAX_VIDEO_SIZE = 10 * 1024 * 1024; // 10MB in bytes
|
|
|
|
- const invalidFiles = files.filter(file => {
|
|
|
|
|
|
+ // 添加文件大小检查
|
|
|
|
+ const MAX_VIDEO_SIZE = 10 * 1024 * 1024; // 10MB in bytes
|
|
|
|
+ const invalidFiles = files.filter((file) => {
|
|
if (isVideoFile(file) && file.size > MAX_VIDEO_SIZE) {
|
|
if (isVideoFile(file) && file.size > MAX_VIDEO_SIZE) {
|
|
return true;
|
|
return true;
|
|
}
|
|
}
|
|
@@ -902,25 +1029,29 @@ const MAX_VIDEO_SIZE = 10 * 1024 * 1024; // 10MB in bytes
|
|
size: file.size,
|
|
size: file.size,
|
|
sizeFormatted: formatFileSize(file.size),
|
|
sizeFormatted: formatFileSize(file.size),
|
|
type: file.type,
|
|
type: file.type,
|
|
- isUploading: true
|
|
|
|
|
|
+ isUploading: true,
|
|
};
|
|
};
|
|
-
|
|
|
|
|
|
+
|
|
attachedFiles.value.push(fileWithStatus);
|
|
attachedFiles.value.push(fileWithStatus);
|
|
|
|
|
|
try {
|
|
try {
|
|
const url = await uploadFile(file);
|
|
const url = await uploadFile(file);
|
|
// 直接修改数组中的对象
|
|
// 直接修改数组中的对象
|
|
- const index = attachedFiles.value.findIndex(f => f.name === file.name);
|
|
|
|
|
|
+ const index = attachedFiles.value.findIndex(
|
|
|
|
+ (f) => f.name === file.name
|
|
|
|
+ );
|
|
if (index !== -1) {
|
|
if (index !== -1) {
|
|
// 使用数组替换方式更新,确保响应式更新
|
|
// 使用数组替换方式更新,确保响应式更新
|
|
attachedFiles.value.splice(index, 1, {
|
|
attachedFiles.value.splice(index, 1, {
|
|
...fileWithStatus,
|
|
...fileWithStatus,
|
|
url,
|
|
url,
|
|
- isUploading: false
|
|
|
|
|
|
+ isUploading: false,
|
|
});
|
|
});
|
|
}
|
|
}
|
|
} catch (error) {
|
|
} catch (error) {
|
|
- attachedFiles.value = attachedFiles.value.filter(f => f.name !== file.name);
|
|
|
|
|
|
+ attachedFiles.value = attachedFiles.value.filter(
|
|
|
|
+ (f) => f.name !== file.name
|
|
|
|
+ );
|
|
console.error("单个文件上传失败:", error);
|
|
console.error("单个文件上传失败:", error);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -929,6 +1060,10 @@ const MAX_VIDEO_SIZE = 10 * 1024 * 1024; // 10MB in bytes
|
|
} finally {
|
|
} finally {
|
|
isUploading.value = false;
|
|
isUploading.value = false;
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ if (files.length > 0 && enableWebSearch.value) {
|
|
|
|
+ enableWebSearch.value = false; // 如果有文件上传,自动关闭互联网搜索
|
|
|
|
+ }
|
|
};
|
|
};
|
|
|
|
|
|
// 添加音频点击处理方法
|
|
// 添加音频点击处理方法
|
|
@@ -998,6 +1133,7 @@ const renderMarkdown = (content) => {
|
|
width: 100%;
|
|
width: 100%;
|
|
height: 100vh;
|
|
height: 100vh;
|
|
overflow: hidden;
|
|
overflow: hidden;
|
|
|
|
+ position: relative;
|
|
}
|
|
}
|
|
|
|
|
|
.menu {
|
|
.menu {
|
|
@@ -1007,8 +1143,16 @@ const renderMarkdown = (content) => {
|
|
display: flex;
|
|
display: flex;
|
|
flex-direction: column;
|
|
flex-direction: column;
|
|
border-right: 1px solid rgba(0, 0, 0, 0.06);
|
|
border-right: 1px solid rgba(0, 0, 0, 0.06);
|
|
- position: relative;
|
|
|
|
- z-index: 1;
|
|
|
|
|
|
+ position: absolute;
|
|
|
|
+ left: 0;
|
|
|
|
+ transform: translateX(0);
|
|
|
|
+ transition: transform 0.3s ease;
|
|
|
|
+ z-index: 2;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.menu.collapsed {
|
|
|
|
+ transform: translateX(-260px);
|
|
|
|
+ width: 260px;
|
|
}
|
|
}
|
|
|
|
|
|
.conversations {
|
|
.conversations {
|
|
@@ -1025,6 +1169,31 @@ const renderMarkdown = (content) => {
|
|
gap: 24px;
|
|
gap: 24px;
|
|
overflow-y: auto;
|
|
overflow-y: auto;
|
|
overflow-x: hidden;
|
|
overflow-x: hidden;
|
|
|
|
+ margin-left: 0;
|
|
|
|
+ margin-right: 0;
|
|
|
|
+ transition: margin 0.3s ease;
|
|
|
|
+ position: absolute;
|
|
|
|
+ left: 0;
|
|
|
|
+ right: 0;
|
|
|
|
+ top: 0;
|
|
|
|
+ bottom: 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.menu:not(.collapsed) + .chat {
|
|
|
|
+ left: 260px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.chat {
|
|
|
|
+ right: 0;
|
|
|
|
+ transition: left 0.3s ease, right 0.3s ease;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.layout:has(.right_menu:not(.collapsed)) .chat {
|
|
|
|
+ right: 600px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.right_menu:not(.collapsed) ~ .chat {
|
|
|
|
+ right: 600px;
|
|
}
|
|
}
|
|
|
|
|
|
.messages {
|
|
.messages {
|
|
@@ -1104,7 +1273,6 @@ const renderMarkdown = (content) => {
|
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
|
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
|
|
}
|
|
}
|
|
|
|
|
|
-/* 滚动条样式 */
|
|
|
|
.conversation-list::-webkit-scrollbar {
|
|
.conversation-list::-webkit-scrollbar {
|
|
width: 4px;
|
|
width: 4px;
|
|
}
|
|
}
|
|
@@ -1176,7 +1344,6 @@ const renderMarkdown = (content) => {
|
|
gap: 8px;
|
|
gap: 8px;
|
|
}
|
|
}
|
|
|
|
|
|
-/* 标签栏样式 */
|
|
|
|
.tabs-wrapper {
|
|
.tabs-wrapper {
|
|
display: flex;
|
|
display: flex;
|
|
gap: 8px;
|
|
gap: 8px;
|
|
@@ -1251,10 +1418,6 @@ const renderMarkdown = (content) => {
|
|
}
|
|
}
|
|
|
|
|
|
.attachments-panel {
|
|
.attachments-panel {
|
|
- /* position: absolute;
|
|
|
|
- bottom: calc(100% + 8px);
|
|
|
|
- left: 0;
|
|
|
|
- right: 0; */
|
|
|
|
background: white;
|
|
background: white;
|
|
border-radius: 12px;
|
|
border-radius: 12px;
|
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
|
@@ -1292,19 +1455,12 @@ const renderMarkdown = (content) => {
|
|
|
|
|
|
.upload-area {
|
|
.upload-area {
|
|
padding: 24px;
|
|
padding: 24px;
|
|
- /* border: 2px dashed #d9d9d9; */
|
|
|
|
border-radius: 8px;
|
|
border-radius: 8px;
|
|
text-align: center;
|
|
text-align: center;
|
|
cursor: pointer;
|
|
cursor: pointer;
|
|
transition: all 0.3s;
|
|
transition: all 0.3s;
|
|
- /* background: #fafafa; */
|
|
|
|
}
|
|
}
|
|
|
|
|
|
-/* .upload-area:hover {
|
|
|
|
- border-color: #1677ff;
|
|
|
|
- background: #f0f7ff;
|
|
|
|
-} */
|
|
|
|
-
|
|
|
|
.upload-area.has-files {
|
|
.upload-area.has-files {
|
|
padding: 12px;
|
|
padding: 12px;
|
|
background: #fff;
|
|
background: #fff;
|
|
@@ -1361,10 +1517,11 @@ const renderMarkdown = (content) => {
|
|
object-fit: cover;
|
|
object-fit: cover;
|
|
border-radius: 4px;
|
|
border-radius: 4px;
|
|
}
|
|
}
|
|
-/* 视频预览特定样式 */
|
|
|
|
|
|
+
|
|
.file-wrapper video.file-icon {
|
|
.file-wrapper video.file-icon {
|
|
- background: #000; /* 视频加载前的背景色 */
|
|
|
|
|
|
+ background: #000;
|
|
}
|
|
}
|
|
|
|
+
|
|
.remove-btn {
|
|
.remove-btn {
|
|
position: absolute;
|
|
position: absolute;
|
|
top: -8px;
|
|
top: -8px;
|
|
@@ -1395,18 +1552,18 @@ const renderMarkdown = (content) => {
|
|
|
|
|
|
.file-name {
|
|
.file-name {
|
|
display: block;
|
|
display: block;
|
|
- max-width: 150px; /* 设置最大宽度 */
|
|
|
|
|
|
+ max-width: 150px;
|
|
color: rgba(0, 0, 0, 0.88);
|
|
color: rgba(0, 0, 0, 0.88);
|
|
font-size: 14px;
|
|
font-size: 14px;
|
|
white-space: nowrap;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
text-overflow: ellipsis;
|
|
}
|
|
}
|
|
|
|
+
|
|
.file-info .file-name {
|
|
.file-info .file-name {
|
|
- max-width: 150px; /* 可以根据实际布局调整宽度 */
|
|
|
|
|
|
+ max-width: 150px;
|
|
}
|
|
}
|
|
|
|
|
|
-/* 如果需要在hover时显示完整文件名,可以添加 title 属性和 tooltip 效果 */
|
|
|
|
.file-name:hover {
|
|
.file-name:hover {
|
|
cursor: pointer;
|
|
cursor: pointer;
|
|
}
|
|
}
|
|
@@ -1698,12 +1855,47 @@ const renderMarkdown = (content) => {
|
|
height: 100%;
|
|
height: 100%;
|
|
background: #f7f7f8;
|
|
background: #f7f7f8;
|
|
border-left: 1px solid rgba(0, 0, 0, 0.06);
|
|
border-left: 1px solid rgba(0, 0, 0, 0.06);
|
|
- position: relative;
|
|
|
|
- transition: width 0.3s ease;
|
|
|
|
|
|
+ position: absolute; /* 改为绝对定位 */
|
|
|
|
+ right: 0;
|
|
|
|
+ transform: translateX(0);
|
|
|
|
+ transition: transform 0.3s ease;
|
|
|
|
+ z-index: 2;
|
|
}
|
|
}
|
|
|
|
|
|
.right_menu.collapsed {
|
|
.right_menu.collapsed {
|
|
- width: 0px;
|
|
|
|
|
|
+ transform: translateX(100%); /* 使用 transform 代替 width */
|
|
|
|
+ width: 600px; /* 保持固定宽度 */
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* 当右侧菜单展开时,给聊天区域添加右边距 */
|
|
|
|
+.right_menu:not(.collapsed) + .chat {
|
|
|
|
+ margin-right: 600px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.collapse-left-button{
|
|
|
|
+ position: absolute;
|
|
|
|
+ left: 260px;
|
|
|
|
+ top: 50%;
|
|
|
|
+ transform: translateY(-50%);
|
|
|
|
+ width: 20px;
|
|
|
|
+ height: 40px;
|
|
|
|
+ background: #fff;
|
|
|
|
+ border: 1px solid rgba(0, 0, 0, 0.06);
|
|
|
|
+ border-right: none;
|
|
|
|
+ border-radius: 4px 0 0 4px;
|
|
|
|
+ display: flex;
|
|
|
|
+ align-items: center;
|
|
|
|
+ justify-content: center;
|
|
|
|
+ cursor: pointer;
|
|
|
|
+ z-index: 2;
|
|
|
|
+ transition: left 0.3s ease;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.collapse-left-button.menu-collapsed {
|
|
|
|
+ left: 260px;
|
|
|
|
+ /* border-right: 1px solid rgba(0, 0, 0, 0.06);
|
|
|
|
+ border-left: none;
|
|
|
|
+ border-radius: 0 4px 4px 0; */
|
|
}
|
|
}
|
|
|
|
|
|
.collapse-button {
|
|
.collapse-button {
|
|
@@ -2048,8 +2240,12 @@ const renderMarkdown = (content) => {
|
|
}
|
|
}
|
|
|
|
|
|
@keyframes spin {
|
|
@keyframes spin {
|
|
- 0% { transform: rotate(0deg); }
|
|
|
|
- 100% { transform: rotate(360deg); }
|
|
|
|
|
|
+ 0% {
|
|
|
|
+ transform: rotate(0deg);
|
|
|
|
+ }
|
|
|
|
+ 100% {
|
|
|
|
+ transform: rotate(360deg);
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
/* 上传区域在上传时的样式 */
|
|
/* 上传区域在上传时的样式 */
|
|
@@ -2067,6 +2263,107 @@ const renderMarkdown = (content) => {
|
|
margin-top: auto;
|
|
margin-top: auto;
|
|
border-top: 1px solid rgba(0, 0, 0, 0.06);
|
|
border-top: 1px solid rgba(0, 0, 0, 0.06);
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+.web-search-toggle {
|
|
|
|
+ display: flex;
|
|
|
|
+ align-items: center;
|
|
|
|
+ margin-left: auto;
|
|
|
|
+ padding: 0 12px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.toggle-input {
|
|
|
|
+ position: relative;
|
|
|
|
+ width: 36px;
|
|
|
|
+ height: 20px;
|
|
|
|
+ margin-right: 8px;
|
|
|
|
+ appearance: none;
|
|
|
|
+ background: #ccc;
|
|
|
|
+ border-radius: 10px;
|
|
|
|
+ cursor: pointer;
|
|
|
|
+ transition: background 0.3s;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.toggle-input:checked {
|
|
|
|
+ background: #1677ff;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.toggle-input::before {
|
|
|
|
+ content: "";
|
|
|
|
+ position: absolute;
|
|
|
|
+ top: 2px;
|
|
|
|
+ left: 2px;
|
|
|
|
+ width: 16px;
|
|
|
|
+ height: 16px;
|
|
|
|
+ background: white;
|
|
|
|
+ border-radius: 50%;
|
|
|
|
+ transition: transform 0.3s;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.toggle-input:checked::before {
|
|
|
|
+ transform: translateX(16px);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.toggle-label {
|
|
|
|
+ font-size: 14px;
|
|
|
|
+ color: rgba(0, 0, 0, 0.88);
|
|
|
|
+ cursor: pointer;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.toggle-input:disabled {
|
|
|
|
+ opacity: 0.5;
|
|
|
|
+ cursor: not-allowed;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.toggle-label.disabled {
|
|
|
|
+ color: rgba(0, 0, 0, 0.45);
|
|
|
|
+ cursor: not-allowed;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.disabled-hint {
|
|
|
|
+ font-size: 12px;
|
|
|
|
+ color: rgba(0, 0, 0, 0.45);
|
|
|
|
+ margin-left: 4px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* 添加菜单收缩相关样式 */
|
|
|
|
+.menu {
|
|
|
|
+ /* 现有样式保持不变 */
|
|
|
|
+ transition: width 0.3s ease;
|
|
|
|
+ position: relative;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.menu.collapsed {
|
|
|
|
+ width: 0;
|
|
|
|
+ padding: 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* 添加收缩按钮样式 */
|
|
|
|
+.collapse-button {
|
|
|
|
+ position: absolute;
|
|
|
|
+ right: -20px;
|
|
|
|
+ top: 50%;
|
|
|
|
+ transform: translateY(-50%);
|
|
|
|
+ width: 20px;
|
|
|
|
+ height: 40px;
|
|
|
|
+ background: #fff;
|
|
|
|
+ border: 1px solid rgba(0, 0, 0, 0.06);
|
|
|
|
+ border-left: none;
|
|
|
|
+ border-radius: 0 4px 4px 0;
|
|
|
|
+ display: flex;
|
|
|
|
+ align-items: center;
|
|
|
|
+ justify-content: center;
|
|
|
|
+ cursor: pointer;
|
|
|
|
+ z-index: 1;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.collapse-button:hover {
|
|
|
|
+ background: #f5f5f5;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.collapse-icon {
|
|
|
|
+ width: 12px;
|
|
|
|
+ height: 12px;
|
|
|
|
+}
|
|
</style>
|
|
</style>
|
|
<style lang="less" scoped>
|
|
<style lang="less" scoped>
|
|
::v-deep .message-content {
|
|
::v-deep .message-content {
|