From f2d004a07d198d6d483f93228005506ae5c70ed2 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期二, 09 六月 2026 16:43:04 +0800
Subject: [PATCH] feat(collaborativeApproval): 添加知识库RAG向量检索问答功能
---
doc/知识库模块前端实现文档.md | 1212 ++++++++++++++++++++++++++++++++++++++++++++++++++++
src/api/collaborativeApproval/knowledgeBase.js | 19
vite.config.js | 2
src/views/collaborativeApproval/knowledgeBase/index.vue | 133 +++++
4 files changed, 1,352 insertions(+), 14 deletions(-)
diff --git "a/doc/\347\237\245\350\257\206\345\272\223\346\250\241\345\235\227\345\211\215\347\253\257\345\256\236\347\216\260\346\226\207\346\241\243.md" "b/doc/\347\237\245\350\257\206\345\272\223\346\250\241\345\235\227\345\211\215\347\253\257\345\256\236\347\216\260\346\226\207\346\241\243.md"
new file mode 100644
index 0000000..c2ea6e2
--- /dev/null
+++ "b/doc/\347\237\245\350\257\206\345\272\223\346\250\241\345\235\227\345\211\215\347\253\257\345\256\236\347\216\260\346\226\207\346\241\243.md"
@@ -0,0 +1,1212 @@
+# 鐭ヨ瘑搴撴ā鍧楀畬鏁村墠绔疄鐜版枃妗�
+
+## 涓�銆佹ā鍧楁杩�
+
+鐭ヨ瘑搴撴ā鍧楁槸涓�涓泦鎴愪簡RAG(妫�绱㈠寮虹敓鎴�)鎶�鏈殑鏅鸿兘鐭ヨ瘑绠$悊绯荤粺,鏀寔:
+- **鐭ヨ瘑搴揅RUD绠$悊** - 鍒涘缓銆佺紪杈戙�佸垹闄ゃ�佹煡璇㈢煡璇嗗簱
+- **鏂囦欢涓婁紶涓庡悜閲忓寲** - 鏀寔澶氱鏂囦欢鏍煎紡,鑷姩杩涜鍚戦噺鍒囩墖澶勭悊
+- **鏅鸿兘闂瓟** - 鍩轰簬涓婁紶鏂囦欢鍐呭杩涜AI闂瓟
+- **鏂囦欢绠$悊** - 鏌ョ湅鏂囦欢鍚戦噺鍖栫姸鎬�,鏀寔閲嶆柊澶勭悊鍜屽垹闄�
+
+### 鎶�鏈灦鏋�
+- **鍓嶇妗嗘灦**: Vue 3 + Composition API
+- **UI缁勪欢搴�**: Element Plus
+- **鍚戦噺鏁版嵁搴�**: Pinecone
+- **AI妯″瀷**: 闃块噷浜戦�氫箟鍗冮棶
+- **鏂囦欢澶勭悊**: 鏀寔docx銆亁lsx銆乸df銆乼xt銆乵d绛夋牸寮�
+
+---
+
+## 浜屻�佹枃浠剁粨鏋�
+
+```
+src/
+鈹溾攢鈹� api/
+鈹� 鈹斺攢鈹� collaborativeApproval/
+鈹� 鈹斺攢鈹� knowledgeBase.js # API鎺ュ彛灏佽
+鈹溾攢鈹� views/
+鈹� 鈹斺攢鈹� collaborativeApproval/
+鈹� 鈹斺攢鈹� knowledgeBase/
+鈹� 鈹斺攢鈹� index.vue # 涓婚〉闈㈢粍浠�
+鈹斺攢鈹� components/
+ 鈹溾攢鈹� PIMTable/
+ 鈹� 鈹斺攢鈹� PIMTable.vue # 琛ㄦ牸缁勪欢
+ 鈹斺攢鈹� Dialog/
+ 鈹斺攢鈹� FormDialog.vue # 寮圭獥缁勪欢
+```
+
+---
+
+## 涓夈�丄PI鎺ュ彛瀹氫箟
+
+### 3.1 鏂囦欢浣嶇疆
+`src/api/collaborativeApproval/knowledgeBase.js`
+
+### 3.2 瀹屾暣鎺ュ彛鍒楄〃
+
+```javascript
+import request from "@/utils/request";
+import { getToken } from '@/utils/auth';
+
+// 1. 鏌ヨ鐭ヨ瘑搴撳垪琛�(鍒嗛〉)
+export function listKnowledgeBase(query) {
+ return request({
+ url: "/knowledgeBase/getList",
+ method: "get",
+ params: query,
+ });
+}
+
+// 2. 鏂板鐭ヨ瘑搴�
+export function addKnowledgeBase(data) {
+ return request({
+ url: "/knowledgeBase/add",
+ method: "post",
+ data: data,
+ });
+}
+
+// 3. 淇敼鐭ヨ瘑搴�
+export function updateKnowledgeBase(data) {
+ return request({
+ url: "/knowledgeBase/update",
+ method: "post",
+ data: data,
+ });
+}
+
+// 4. 鍒犻櫎鐭ヨ瘑搴�
+export function delKnowledgeBase(query) {
+ return request({
+ url: "/knowledgeBase/delete",
+ method: "delete",
+ data: query,
+ });
+}
+
+// 5. 鏌ヨ鐭ヨ瘑搴撴枃浠跺悜閲忓寲鐘舵��(鍖呭惈鏂囦欢鍒楄〃)
+export function getVectorStatus(knowledgeBaseId) {
+ return request({
+ url: `/knowledgeBase/vector/status/${knowledgeBaseId}`,
+ method: "get",
+ });
+}
+
+// 6. 淇濆瓨鐭ヨ瘑搴撴枃浠跺叧鑱�(瑙﹀彂鍚戦噺鍖�)
+export function saveKnowledgeBaseFiles(data) {
+ return request({
+ url: "/knowledgeBase/file/save",
+ method: "post",
+ data,
+ });
+}
+
+// 7. 鍒犻櫎鐭ヨ瘑搴撴枃浠�
+export function deleteKnowledgeBaseFile(ids) {
+ return request({
+ url: "/knowledgeBase/file/delete",
+ method: "delete",
+ data: ids,
+ });
+}
+
+// 8. 閲嶆柊鍚戦噺鍖栨枃浠�
+export function reprocessVector(vectorId) {
+ return request({
+ url: `/knowledgeBase/vector/reprocess/${vectorId}`,
+ method: "post",
+ });
+}
+
+// 9. 鐭ヨ瘑搴撻棶绛�(娴佸紡)
+export function knowledgeChat(data, onMessage) {
+ const token = getToken();
+ return fetch(import.meta.env.VITE_APP_BASE_API + '/ai/knowledge/chat', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': 'Bearer ' + token
+ },
+ body: JSON.stringify(data)
+ });
+}
+
+// 10. 鏌ヨ鐭ヨ瘑搴撻棶绛斿巻鍙�
+export function getKnowledgeHistory(memoryId) {
+ return request({
+ url: `/ai/knowledge/history/${memoryId}`,
+ method: "get",
+ });
+}
+```
+
+### 3.3 鎺ュ彛鍙傛暟璇存槑
+
+#### 鐭ヨ瘑搴撳垪琛ㄦ煡璇�
+```javascript
+// 璇锋眰鍙傛暟
+{
+ current: 1, // 褰撳墠椤电爜
+ size: 20, // 姣忛〉鏉℃暟
+ title: "", // 鐭ヨ瘑鏍囬(鍙��)
+ type: "" // 鐭ヨ瘑绫诲瀷(鍙��)
+}
+
+// 鍝嶅簲鏁版嵁
+{
+ code: 200,
+ data: {
+ total: 100,
+ records: [
+ {
+ id: 1,
+ title: "鎿嶄綔鎵嬪唽",
+ type: "guide",
+ scenario: "绯荤粺鎿嶄綔鎸囧",
+ efficiency: "high",
+ problem: "鐢ㄦ埛涓嶄細鎿嶄綔绯荤粺",
+ solution: "鎸夌収鎿嶄綔鎵嬪唽鎵ц...",
+ keyPoints: "姝ラ1,姝ラ2,姝ラ3",
+ creator: "寮犱笁",
+ usageCount: 10,
+ fileCount: 3, // 鏂囦欢鏁伴噺
+ totalChunkCount: 45, // 鎬诲垏鐗囨暟閲�
+ createTime: "2026-06-08 10:00:00"
+ }
+ ]
+ }
+}
+```
+
+#### 淇濆瓨鏂囦欢鍏宠仈
+```javascript
+// 璇锋眰鍙傛暟
+{
+ knowledgeBaseId: 10, // 鐭ヨ瘑搴揑D
+ storageBlobIds: [123, 124] // 涓婁紶鏂囦欢杩斿洖鐨刡lob ID鍒楄〃
+}
+
+// 鍝嶅簲鏁版嵁
+{
+ code: 200,
+ msg: "鎿嶄綔鎴愬姛"
+}
+```
+
+#### 鍚戦噺鍖栫姸鎬佹煡璇�
+```javascript
+// 鍝嶅簲鏁版嵁
+{
+ code: 200,
+ data: [
+ {
+ id: 1,
+ storageBlobId: 123,
+ fileName: "鎿嶄綔鎵嬪唽.docx",
+ fileType: "docx",
+ vectorStatus: 2, // 0-寰呭鐞�,1-澶勭悊涓�,2-宸插畬鎴�,3-澶辫触
+ chunkCount: 15, // 鍒囩墖鏁伴噺
+ namespace: "kb-10",
+ vectorError: null,
+ createTime: "2026-06-08 10:00:00"
+ }
+ ]
+}
+```
+
+#### 鐭ヨ瘑搴撻棶绛�
+```javascript
+// 璇锋眰鍙傛暟
+{
+ knowledgeBaseId: 10,
+ memoryId: "session-xxx", // 浼氳瘽ID,鐢ㄤ簬淇濇寔涓婁笅鏂�
+ question: "濡備綍鎿嶄綔瀹℃壒娴佺▼?"
+}
+
+// 鍝嶅簲(娴佸紡杩斿洖 text/stream;charset=utf-8)
+// 鏍规嵁鐭ヨ瘑搴撳唴瀹�,瀹℃壒娴佺▼鐨勬搷浣滄楠ゅ涓�:
+// 1. 鐧诲綍绯荤粺鍚庤繘鍏ュ鎵圭鐞嗘ā鍧�...
+```
+
+---
+
+## 鍥涖�佹牳蹇冪粍浠跺疄鐜�
+
+### 4.1 涓婚〉闈㈢粨鏋�
+
+椤甸潰閲囩敤Tab椤电甯冨眬,鍖呭惈涓や釜涓昏鍔熻兘妯″潡:
+- **鐭ヨ瘑搴撶鐞�** - 鐭ヨ瘑搴揅RUD鎿嶄綔
+- **鐭ヨ瘑搴撻棶绛�** - 鍩轰簬RAG鐨勬櫤鑳介棶绛�
+
+### 4.2 鏁版嵁妯″瀷瀹氫箟
+
+```javascript
+// 鍝嶅簲寮忔暟鎹�
+const data = reactive({
+ // 鎼滅储琛ㄥ崟
+ searchForm: {
+ title: "",
+ type: "",
+ },
+
+ // 鍒嗛〉閰嶇疆
+ page: {
+ current: 1,
+ size: 20,
+ total: 0,
+ },
+
+ // 琛ㄦ牸鏁版嵁
+ tableData: [],
+ tableLoading: false,
+ selectedIds: [],
+
+ // 鐭ヨ瘑搴撹〃鍗�
+ form: {
+ title: "",
+ type: "",
+ scenario: "",
+ efficiency: "",
+ problem: "",
+ solution: "",
+ keyPoints: "",
+ creator: "",
+ usageCount: 0
+ },
+
+ // 寮圭獥鎺у埗
+ dialogVisible: false,
+ dialogTitle: "",
+ dialogType: "add", // add or edit
+ viewDialogVisible: false,
+ currentKnowledge: {},
+
+ // 鏂囦欢绠$悊
+ filesDialogVisible: false,
+ currentKnowledgeBase: null,
+ fileList: [],
+ uploadedBlobIds: [],
+ savingFiles: false,
+
+ // 鐭ヨ瘑搴撻棶绛�
+ chatDialogVisible: false,
+ messages: [],
+ inputQuestion: "",
+ chatLoading: false,
+ memoryId: ""
+});
+```
+
+### 4.3 琛ㄦ牸鍒楅厤缃�
+
+```javascript
+const tableColumn = ref([
+ {
+ label: "鐭ヨ瘑鏍囬",
+ prop: "title",
+ showOverflowTooltip: true,
+ },
+ {
+ label: "鐭ヨ瘑绫诲瀷",
+ prop: "type",
+ dataType: "tag",
+ formatData: (params) => getKnowledgeTypeLabel(params),
+ formatType: (params) => getKnowledgeTypeTagType(params)
+ },
+ {
+ label: "閫傜敤鍦烘櫙",
+ prop: "scenario",
+ width: 150,
+ showOverflowTooltip: true,
+ },
+ {
+ label: "瑙e喅鏁堢巼",
+ prop: "efficiency",
+ dataType: "tag",
+ formatData: (params) => {
+ const efficiencyMap = {
+ high: "鏄捐憲鎻愬崌",
+ medium: "涓�鑸彁鍗�",
+ low: "杞诲井鎻愬崌"
+ };
+ return efficiencyMap[params] || params;
+ },
+ formatType: (params) => {
+ const typeMap = {
+ high: "success",
+ medium: "warning",
+ low: "info"
+ };
+ return typeMap[params] || "info";
+ }
+ },
+ {
+ label: "鏂囦欢鏁伴噺",
+ prop: "fileCount",
+ width: 100,
+ align: "center"
+ },
+ {
+ label: "鍒囩墖鏁伴噺",
+ prop: "totalChunkCount",
+ width: 100,
+ align: "center"
+ },
+ {
+ label: "浣跨敤娆℃暟",
+ prop: "usageCount",
+ width: 100,
+ align: "center"
+ },
+ {
+ label: "鍒涘缓浜�",
+ prop: "creator",
+ width: 120,
+ },
+ {
+ label: "鍒涘缓鏃堕棿",
+ prop: "createTime",
+ width: 180,
+ },
+ {
+ dataType: "action",
+ label: "鎿嶄綔",
+ align: "center",
+ fixed: "right",
+ width: 280,
+ operation: [
+ {
+ name: "缂栬緫",
+ type: "text",
+ clickFun: (row) => openForm("edit", row)
+ },
+ {
+ name: "鏂囦欢",
+ type: "text",
+ clickFun: (row) => openFilesDialog(row)
+ },
+ {
+ name: "闂瓟",
+ type: "text",
+ clickFun: (row) => openChatDialog(row)
+ },
+ {
+ name: "璇︽儏",
+ type: "text",
+ clickFun: (row) => viewKnowledge(row)
+ }
+ ]
+ }
+]);
+```
+
+---
+
+## 浜斻�佹牳蹇冧笟鍔¢�昏緫
+
+### 5.1 鏂囦欢涓婁紶涓庡悜閲忓寲娴佺▼
+
+#### 娴佺▼鍥�
+```
+鐢ㄦ埛鐐瑰嚮"涓婁紶鏂囦欢"
+ 鈫�
+閫夋嫨鏂囦欢(鏀寔澶氶��)
+ 鈫�
+鍓嶇鏍¢獙鏂囦欢绫诲瀷鍜屽ぇ灏�
+ 鈫�
+璋冪敤 /common/upload 涓婁紶鏂囦欢
+ 鈫�
+鑾峰彇 storageBlobId 鍒楄〃
+ 鈫�
+鐢ㄦ埛鐐瑰嚮"淇濆瓨鏂囦欢鍏宠仈"
+ 鈫�
+璋冪敤 /knowledgeBase/file/save
+ 鈫�
+鍚庣鍒涘缓鍚戦噺璁板綍 + 寮傛瑙﹀彂鍚戦噺鍖�
+ 鈫�
+鍓嶇寤惰繜1绉掑埛鏂版枃浠跺垪琛�
+ 鈫�
+鏄剧ず鍚戦噺鍖栫姸鎬�(寰呭鐞嗏啋澶勭悊涓啋宸插畬鎴�)
+```
+
+#### 浠g爜瀹炵幇
+
+```vue
+<template>
+ <div class="file-manager">
+ <!-- 鏂囦欢涓婁紶 -->
+ <div class="upload-section">
+ <el-upload
+ :action="uploadUrl"
+ :headers="uploadHeaders"
+ :on-success="handleUploadSuccess"
+ :on-error="handleUploadError"
+ :before-upload="beforeUpload"
+ multiple
+ :show-file-list="false"
+ accept=".txt,.md,.docx,.xlsx,.xls,.pdf"
+ >
+ <el-button type="primary">涓婁紶鏂囦欢</el-button>
+ </el-upload>
+ <el-button
+ type="success"
+ @click="saveFiles"
+ :disabled="uploadedBlobIds.length === 0"
+ :loading="savingFiles"
+ >
+ 淇濆瓨鏂囦欢鍏宠仈
+ </el-button>
+ </div>
+
+ <!-- 鏂囦欢鍒楄〃 -->
+ <el-table :data="fileList" style="margin-top: 20px" border>
+ <el-table-column prop="fileName" label="鏂囦欢鍚�" />
+ <el-table-column prop="fileType" label="鏂囦欢绫诲瀷" width="100" />
+ <el-table-column label="鍚戦噺鍖栫姸鎬�" width="120">
+ <template #default="{ row }">
+ <el-tag :type="getStatusType(row.vectorStatus)">
+ {{ getStatusText(row.vectorStatus) }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column prop="chunkCount" label="鍒囩墖鏁�" width="100" />
+ <el-table-column label="鎿嶄綔" width="150">
+ <template #default="{ row }">
+ <el-button
+ v-if="row.vectorStatus === 3"
+ type="text"
+ @click="reprocessFile(row)"
+ >
+ 閲嶆柊澶勭悊
+ </el-button>
+ <el-button type="text" @click="deleteFile(row)" style="color: #f56c6c">
+ 鍒犻櫎
+ </el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+</template>
+
+<script setup>
+import { getToken } from "@/utils/auth";
+
+// 鏂囦欢涓婁紶閰嶇疆
+const uploadUrl = import.meta.env.VITE_APP_BASE_API + "/common/upload";
+const uploadHeaders = { Authorization: "Bearer " + getToken() };
+const uploadedBlobIds = ref([]);
+const fileList = ref([]);
+
+// 涓婁紶鍓嶆牎楠�
+const beforeUpload = (file) => {
+ const allowedTypes = ['.txt', '.md', '.docx', '.xlsx', '.xls', '.pdf'];
+ const fileName = file.name.toLowerCase();
+ const isAllowed = allowedTypes.some(type => fileName.endsWith(type));
+
+ if (!isAllowed) {
+ ElMessage.error('鍙敮鎸� txt銆乵d銆乨ocx銆亁lsx銆亁ls銆乸df 鏍煎紡鐨勬枃浠�');
+ return false;
+ }
+
+ const isLt50M = file.size / 1024 / 1024 < 50;
+ if (!isLt50M) {
+ ElMessage.error('鏂囦欢澶у皬涓嶈兘瓒呰繃 50MB');
+ return false;
+ }
+
+ return true;
+};
+
+// 涓婁紶鎴愬姛
+const handleUploadSuccess = (response, file) => {
+ if (response.code === 200) {
+ uploadedBlobIds.value.push(response.data.id);
+ ElMessage.success(`鏂囦欢 ${file.name} 涓婁紶鎴愬姛`);
+ } else {
+ ElMessage.error(response.msg || "涓婁紶澶辫触");
+ }
+};
+
+// 淇濆瓨鏂囦欢鍏宠仈
+const saveFiles = async () => {
+ if (uploadedBlobIds.value.length === 0) {
+ ElMessage.warning("璇峰厛涓婁紶鏂囦欢");
+ return;
+ }
+
+ savingFiles.value = true;
+ try {
+ await saveKnowledgeBaseFiles({
+ knowledgeBaseId: currentKnowledgeBase.value.id,
+ storageBlobIds: uploadedBlobIds.value
+ });
+
+ ElMessage.success("鏂囦欢鍏宠仈淇濆瓨鎴愬姛,姝e湪鍚庡彴澶勭悊鍚戦噺鍖�");
+ uploadedBlobIds.value = [];
+
+ // 寤惰繜鍒锋柊鏂囦欢鍒楄〃
+ setTimeout(() => {
+ loadFileList();
+ }, 1000);
+ } catch (error) {
+ console.error("淇濆瓨鏂囦欢鍏宠仈澶辫触:", error);
+ ElMessage.error("淇濆瓨鏂囦欢鍏宠仈澶辫触");
+ } finally {
+ savingFiles.value = false;
+ }
+};
+
+// 鍔犺浇鏂囦欢鍒楄〃
+const loadFileList = async () => {
+ if (!currentKnowledgeBase.value?.id) return;
+
+ try {
+ const res = await getVectorStatus(currentKnowledgeBase.value.id);
+ fileList.value = res.data || [];
+ } catch (error) {
+ console.error("鍔犺浇鏂囦欢鍒楄〃澶辫触:", error);
+ ElMessage.error("鍔犺浇鏂囦欢鍒楄〃澶辫触");
+ }
+};
+
+// 鐘舵�佹槧灏�
+const getStatusText = (status) => {
+ const map = {
+ 0: '寰呭鐞�',
+ 1: '澶勭悊涓�',
+ 2: '宸插畬鎴�',
+ 3: '澶辫触'
+ };
+ return map[status] || '鏈煡';
+};
+
+const getStatusType = (status) => {
+ const map = {
+ 0: 'info',
+ 1: 'warning',
+ 2: 'success',
+ 3: 'danger'
+ };
+ return map[status] || 'info';
+};
+
+// 閲嶆柊澶勭悊鍚戦噺鍖栫殑鏂囦欢
+const reprocessFile = async (row) => {
+ try {
+ await reprocessVector(row.id);
+ ElMessage.success("宸查噸鏂版彁浜ゅ悜閲忓寲浠诲姟");
+ setTimeout(() => {
+ loadFileList();
+ }, 1000);
+ } catch (error) {
+ console.error("閲嶆柊澶勭悊澶辫触:", error);
+ ElMessage.error("閲嶆柊澶勭悊澶辫触");
+ }
+};
+
+// 鍒犻櫎鏂囦欢
+const deleteFile = async (row) => {
+ try {
+ await ElMessageBox.confirm(
+ "纭畾瑕佸垹闄よ鏂囦欢鍚�?鍒犻櫎鍚庡皢鏃犳硶鎭㈠鍚戦噺鏁版嵁",
+ "鍒犻櫎纭",
+ {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ }
+ );
+
+ await deleteKnowledgeBaseFiles([row.id]);
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ loadFileList();
+ } catch (error) {
+ if (error !== 'cancel') {
+ console.error("鍒犻櫎鏂囦欢澶辫触:", error);
+ ElMessage.error("鍒犻櫎鏂囦欢澶辫触");
+ }
+ }
+};
+</script>
+```
+
+### 5.2 鐭ヨ瘑搴撻棶绛旀祦绋�
+
+#### 娴佺▼鍥�
+```
+鐢ㄦ埛閫夋嫨鐭ヨ瘑搴�
+ 鈫�
+杈撳叆闂骞舵彁浜�
+ 鈫�
+鍓嶇鐢熸垚memoryId(鐢ㄤ簬浼氳瘽涓婁笅鏂�)
+ 鈫�
+璋冪敤 /ai/knowledge/chat (娴佸紡鎺ュ彛)
+ 鈫�
+鍚庣澶勭悊:
+ - 瀵归棶棰樿繘琛屽悜閲忓寲
+ - 鍦≒inecone涓绱㈢浉鍏冲垏鐗�
+ - 鏋勫缓涓婁笅鏂嘝rompt
+ - 璋冪敤LLM鐢熸垚鍥炵瓟
+ 鈫�
+娴佸紡杩斿洖AI鍥炵瓟
+ 鈫�
+鍓嶇瀹炴椂鏄剧ず鍥炵瓟鍐呭
+ 鈫�
+鑷姩婊氬姩鍒板簳閮�
+```
+
+#### 浠g爜瀹炵幇
+
+```vue
+<template>
+ <div class="knowledge-chat">
+ <div class="chat-header">
+ <el-tag type="success">褰撳墠鐭ヨ瘑搴�: {{ currentKnowledgeBase?.title }}</el-tag>
+ </div>
+
+ <!-- 瀵硅瘽鍖哄煙 -->
+ <div class="chat-messages" ref="chatMessagesRef">
+ <div
+ v-for="(msg, index) in messages"
+ :key="index"
+ :class="['message', msg.role]"
+ >
+ <div class="message-role">{{ msg.role === 'user' ? '鎴�' : 'AI鍔╂墜' }}</div>
+ <div class="message-content">{{ msg.content }}</div>
+ </div>
+ <div v-if="chatLoading" class="message assistant">
+ <div class="message-role">AI鍔╂墜</div>
+ <div class="message-content typing">姝e湪鎬濊�冧腑...</div>
+ </div>
+ </div>
+
+ <!-- 杈撳叆妗� -->
+ <div class="chat-input">
+ <el-input
+ v-model="inputQuestion"
+ placeholder="璇疯緭鍏ラ棶棰�,鎸夊洖杞﹀彂閫�"
+ @keyup.enter="sendMessage"
+ :disabled="chatLoading"
+ >
+ <template #append>
+ <el-button @click="sendMessage" :loading="chatLoading">鍙戦��</el-button>
+ </template>
+ </el-input>
+ </div>
+ </div>
+</template>
+
+<script setup>
+import { nextTick } from 'vue';
+import { getToken } from "@/utils/auth";
+
+const messages = ref([]);
+const inputQuestion = ref("");
+const chatLoading = ref(false);
+const memoryId = ref("");
+const chatMessagesRef = ref();
+
+// 鎵撳紑闂瓟寮圭獥
+const openChatDialog = (row) => {
+ currentKnowledgeBase.value = row;
+ chatDialogVisible.value = true;
+ memoryId.value = crypto.randomUUID(); // 鐢熸垚鍞竴浼氳瘽ID
+ messages.value = [];
+ inputQuestion.value = "";
+};
+
+// 鍙戦�佹秷鎭�
+const sendMessage = async () => {
+ if (!inputQuestion.value.trim()) {
+ ElMessage.warning("璇疯緭鍏ラ棶棰�");
+ return;
+ }
+
+ const question = inputQuestion.value.trim();
+
+ // 娣诲姞鐢ㄦ埛娑堟伅
+ messages.value.push({
+ role: 'user',
+ content: question
+ });
+
+ inputQuestion.value = "";
+ chatLoading.value = true;
+
+ // 婊氬姩鍒板簳閮�
+ await nextTick();
+ scrollToBottom();
+
+ try {
+ // 娴佸紡璇锋眰
+ const response = await fetch('/api/ai/knowledge/chat', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': 'Bearer ' + getToken()
+ },
+ body: JSON.stringify({
+ knowledgeBaseId: currentKnowledgeBase.value.id,
+ memoryId: memoryId.value,
+ question: question
+ })
+ });
+
+ if (!response.ok) {
+ throw new Error('璇锋眰澶辫触');
+ }
+
+ // 澶勭悊SSE娴佸紡鍝嶅簲
+ const reader = response.body.getReader();
+ const decoder = new TextDecoder();
+ let aiContent = '';
+
+ messages.value.push({ role: 'assistant', content: '' });
+
+ while (true) {
+ const { done, value } = await reader.read();
+ if (done) break;
+
+ const text = decoder.decode(value);
+ aiContent += text;
+ messages.value[messages.value.length - 1].content = aiContent;
+
+ // 婊氬姩鍒板簳閮�
+ await nextTick();
+ scrollToBottom();
+ }
+ } catch (error) {
+ console.error("闂瓟璇锋眰澶辫触:", error);
+ ElMessage.error("闂瓟璇锋眰澶辫触,璇风◢鍚庨噸璇�");
+ messages.value.push({
+ role: 'assistant',
+ content: '鎶辨瓑,鍙戠敓浜嗛敊璇�,璇风◢鍚庨噸璇�'
+ });
+ } finally {
+ chatLoading.value = false;
+ }
+};
+
+// 婊氬姩鍒板簳閮�
+const scrollToBottom = () => {
+ if (chatMessagesRef.value) {
+ chatMessagesRef.value.scrollTop = chatMessagesRef.value.scrollHeight;
+ }
+};
+</script>
+
+<style scoped>
+.knowledge-chat {
+ display: flex;
+ flex-direction: column;
+ height: 500px;
+}
+
+.chat-messages {
+ flex: 1;
+ overflow-y: auto;
+ padding: 16px;
+ background: #f5f7fa;
+ border-radius: 8px;
+ margin-bottom: 16px;
+}
+
+.message {
+ margin-bottom: 16px;
+ max-width: 80%;
+}
+
+.message.user {
+ margin-left: auto;
+ text-align: right;
+}
+
+.message.assistant {
+ margin-right: auto;
+}
+
+.message-role {
+ font-size: 12px;
+ color: #909399;
+ margin-bottom: 4px;
+}
+
+.message-content {
+ display: inline-block;
+ padding: 10px 14px;
+ border-radius: 8px;
+ line-height: 1.6;
+ word-wrap: break-word;
+ white-space: pre-wrap;
+}
+
+.message.user .message-content {
+ background: #409eff;
+ color: white;
+}
+
+.message.assistant .message-content {
+ background: white;
+ color: #303133;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.typing {
+ animation: typing 1.5s infinite;
+}
+
+@keyframes typing {
+ 0%, 50%, 100% { opacity: 1; }
+ 25%, 75% { opacity: 0.5; }
+}
+</style>
+```
+
+---
+
+## 鍏�佸叧閿疄鐜扮粏鑺�
+
+### 6.1 鏂囦欢涓婁紶閰嶇疆
+
+```javascript
+// 涓婁紶鍦板潃
+const uploadUrl = import.meta.env.VITE_APP_BASE_API + "/common/upload";
+
+// 璇锋眰澶�(蹇呴』鎼哄甫Token)
+const uploadHeaders = {
+ Authorization: "Bearer " + getToken()
+};
+
+// 鏀寔鐨勬枃浠剁被鍨�
+const acceptTypes = '.txt,.md,.docx,.xlsx,.xls,.pdf';
+
+// 鏂囦欢澶у皬闄愬埗
+const maxSize = 50 * 1024 * 1024; // 50MB
+```
+
+### 6.2 鍚戦噺鍖栫姸鎬佽疆璇�
+
+```javascript
+// 寮�濮嬭疆璇㈠悜閲忓寲鐘舵��
+const startVectorStatusPolling = () => {
+ const timer = setInterval(async () => {
+ const res = await getVectorStatus(currentKnowledgeBase.value.id);
+ const hasProcessing = res.data.some(item => item.vectorStatus === 1);
+
+ if (!hasProcessing) {
+ clearInterval(timer);
+ }
+
+ fileList.value = res.data;
+ }, 3000); // 姣�3绉掕疆璇竴娆�
+};
+```
+
+### 6.3 娴佸紡鍝嶅簲澶勭悊
+
+```javascript
+// 浣跨敤 Fetch API 澶勭悊娴佸紡鍝嶅簲
+const response = await fetch('/api/ai/knowledge/chat', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': 'Bearer ' + getToken()
+ },
+ body: JSON.stringify({
+ knowledgeBaseId: 10,
+ memoryId: "session-xxx",
+ question: "闂鍐呭"
+ })
+});
+
+// 鑾峰彇鍙娴�
+const reader = response.body.getReader();
+const decoder = new TextDecoder();
+
+// 閫愬潡璇诲彇鏁版嵁
+while (true) {
+ const { done, value } = await reader.read();
+ if (done) break;
+
+ const text = decoder.decode(value);
+ // 澶勭悊鏂囨湰鍧�
+ processText(text);
+}
+```
+
+### 6.4 浼氳瘽绠$悊
+
+```javascript
+// 鐢熸垚鍞竴浼氳瘽ID
+const memoryId = crypto.randomUUID();
+
+// 鎴栦娇鐢ㄦ椂闂存埑
+const memoryId = 'kb-chat-' + Date.now();
+
+// 浼氳瘽ID鐢ㄤ簬:
+// 1. 淇濇寔瀵硅瘽涓婁笅鏂�
+// 2. 鏀寔澶氳疆瀵硅瘽
+// 3. 鏌ヨ鍘嗗彶璁板綍
+```
+
+---
+
+## 涓冦�佺姸鎬佺鐞�
+
+### 7.1 鍚戦噺鍖栫姸鎬佸畾涔�
+
+| 鐘舵�佸�� | 鐘舵�佸悕绉� | 璇存槑 | 鏍囩棰滆壊 |
+|--------|----------|------|----------|
+| 0 | 寰呭鐞� | 鏂囦欢宸蹭笂浼�,绛夊緟鍚戦噺鍖栧鐞� | info(鐏拌壊) |
+| 1 | 澶勭悊涓� | 姝e湪杩涜鍚戦噺鍒囩墖澶勭悊 | warning(姗欒壊) |
+| 2 | 宸插畬鎴� | 鍚戦噺鍖栧畬鎴�,鍙繘琛屾绱㈤棶绛� | success(缁胯壊) |
+| 3 | 澶辫触 | 鍚戦噺鍖栧け璐�,闇�閲嶆柊澶勭悊 | danger(绾㈣壊) |
+
+### 7.2 鐭ヨ瘑绫诲瀷閰嶇疆
+
+```javascript
+// 浣跨敤瀛楀吀閰嶇疆鐭ヨ瘑绫诲瀷
+const { knowledge_type } = proxy.useDict("knowledge_type");
+
+// 绀轰緥鏁版嵁
+const knowledgeTypeOptions = [
+ { value: 'contract', label: '鍚堝悓鐭ヨ瘑', elTagType: 'success' },
+ { value: 'approval', label: '瀹℃壒娴佺▼', elTagType: 'warning' },
+ { value: 'solution', label: '瑙e喅鏂规', elTagType: 'primary' },
+ { value: 'experience', label: '缁忛獙鍒嗕韩', elTagType: 'info' },
+ { value: 'guide', label: '鎿嶄綔鎸囧崡', elTagType: 'danger' }
+];
+```
+
+### 7.3 瑙e喅鏁堢巼鏄犲皠
+
+```javascript
+const efficiencyMap = {
+ high: { label: '鏄捐憲鎻愬崌', color: 'success', score: 40, time: '2-3澶�' },
+ medium: { label: '涓�鑸彁鍗�', color: 'warning', score: 25, time: '1-2澶�' },
+ low: { label: '杞诲井鎻愬崌', color: 'info', score: 15, time: '0.5-1澶�' }
+};
+```
+
+---
+
+## 鍏�佹牱寮忚璁�
+
+### 8.1 鏂囦欢绠$悊鏍峰紡
+
+```css
+.file-manager {
+ padding: 20px 0;
+}
+
+.upload-section {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+```
+
+### 8.2 闂瓟鐣岄潰鏍峰紡
+
+```css
+.knowledge-chat {
+ display: flex;
+ flex-direction: column;
+ height: 500px;
+}
+
+.chat-messages {
+ flex: 1;
+ overflow-y: auto;
+ padding: 16px;
+ background: #f5f7fa;
+ border-radius: 8px;
+ margin-bottom: 16px;
+}
+
+.message {
+ margin-bottom: 16px;
+ max-width: 80%;
+}
+
+.message.user {
+ margin-left: auto;
+ text-align: right;
+}
+
+.message.assistant {
+ margin-right: auto;
+}
+
+.message-content {
+ display: inline-block;
+ padding: 10px 14px;
+ border-radius: 8px;
+ line-height: 1.6;
+ word-wrap: break-word;
+ white-space: pre-wrap;
+}
+
+.message.user .message-content {
+ background: #409eff;
+ color: white;
+}
+
+.message.assistant .message-content {
+ background: white;
+ color: #303133;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+```
+
+---
+
+## 涔濄�佹敞鎰忎簨椤�
+
+### 9.1 鏂囦欢涓婁紶
+
+1. **蹇呴』璋冪敤淇濆瓨鎺ュ彛**: 涓婁紶鎴愬姛鍚庡繀椤昏皟鐢� `/knowledgeBase/file/save` 鎵嶈兘瑙﹀彂鍚戦噺鍖�
+2. **鏂囦欢绫诲瀷闄愬埗**: 鍙敮鎸� txt銆乵d銆乨ocx銆亁lsx銆亁ls銆乸df 鏍煎紡
+3. **鏂囦欢澶у皬闄愬埗**: 鍗曟枃浠舵渶澶� 50MB
+4. **寮傛澶勭悊**: 鍚戦噺鍖栨槸寮傛澶勭悊,涓嶄細闃诲鐢ㄦ埛鎿嶄綔
+
+### 9.2 鍚戦噺鍖栫姸鎬�
+
+1. **鐘舵�佽疆璇�**: 寤鸿姣�3-5绉掕疆璇竴娆$姸鎬�
+2. **鍋滄杞**: 褰撴墍鏈夋枃浠剁姸鎬侀兘涓嶆槸"澶勭悊涓�"鏃跺仠姝㈣疆璇�
+3. **澶辫触澶勭悊**: 鐘舵�佷负"澶辫触"鏃跺彲鐐瑰嚮"閲嶆柊澶勭悊"鎸夐挳
+
+### 9.3 鐭ヨ瘑搴撻棶绛�
+
+1. **浼氳瘽ID**: 姣忔鎵撳紑闂瓟寮圭獥闇�瑕佺敓鎴愭柊鐨� memoryId
+2. **娴佸紡澶勭悊**: 浣跨敤 Fetch API 澶勭悊娴佸紡鍝嶅簲,涓嶆敮鎸� axios
+3. **閿欒澶勭悊**: 闇�瑕佸鐞嗙綉缁滈敊璇拰AI鍝嶅簲閿欒
+4. **鑷姩婊氬姩**: 姣忔鏀跺埌鏂版秷鎭嚜鍔ㄦ粴鍔ㄥ埌搴曢儴
+
+### 9.4 鏁版嵁涓�鑷存��
+
+1. **鍒犻櫎鐭ヨ瘑搴�**: 闇�瑕佸悓鏃跺垹闄ゅ叧鑱旂殑鏂囦欢鍜屽悜閲忔暟鎹�
+2. **鍒犻櫎鏂囦欢**: 鍒犻櫎鏂囦欢鏃跺悓姝ュ垹闄ゅ悜閲忓簱涓殑鐩稿叧鍒囩墖
+3. **鍒锋柊鍒楄〃**: 鏂囦欢鎿嶄綔鍚庨渶瑕佸埛鏂扮煡璇嗗簱鍒楄〃,鏇存柊鏂囦欢鏁伴噺鍜屽垏鐗囨暟閲�
+
+---
+
+## 鍗併�佹祴璇曟鏌ユ竻鍗�
+
+### 10.1 鐭ヨ瘑搴撶鐞�
+
+- [ ] 鏂板鐭ヨ瘑搴撴垚鍔�
+- [ ] 缂栬緫鐭ヨ瘑搴撴垚鍔�
+- [ ] 鍒犻櫎鐭ヨ瘑搴撴垚鍔�(鍗曚釜/鎵归噺)
+- [ ] 鎼滅储鍔熻兘姝e父(鎸夋爣棰樸�佺被鍨�)
+- [ ] 鍒嗛〉鍔熻兘姝e父
+- [ ] 瀵煎嚭鍔熻兘姝e父
+
+### 10.2 鏂囦欢涓婁紶
+
+- [ ] 鍗曟枃浠朵笂浼犳垚鍔�
+- [ ] 澶氭枃浠朵笂浼犳垚鍔�
+- [ ] 鏂囦欢绫诲瀷鏍¢獙姝e父
+- [ ] 鏂囦欢澶у皬鏍¢獙姝e父
+- [ ] 淇濆瓨鏂囦欢鍏宠仈鎴愬姛
+- [ ] 鍚戦噺鍖栫姸鎬佹纭樉绀�
+
+### 10.3 鏂囦欢绠$悊
+
+- [ ] 鏌ョ湅鏂囦欢鍒楄〃姝e父
+- [ ] 鍚戦噺鍖栫姸鎬佽疆璇㈡甯�
+- [ ] 閲嶆柊澶勭悊澶辫触鏂囦欢鎴愬姛
+- [ ] 鍒犻櫎鏂囦欢鎴愬姛
+- [ ] 鏂囦欢棰勮/涓嬭浇姝e父
+
+### 10.4 鐭ヨ瘑搴撻棶绛�
+
+- [ ] 闂瓟寮圭獥鎵撳紑姝e父
+- [ ] 鍙戦�侀棶棰樻垚鍔�
+- [ ] 娴佸紡鍝嶅簲鏄剧ず姝e父
+- [ ] 澶氳疆瀵硅瘽姝e父
+- [ ] 鑷姩婊氬姩鍒板簳閮�
+- [ ] 閿欒澶勭悊姝e父
+
+---
+
+## 鍗佷竴銆佸父瑙侀棶棰�
+
+### Q1: 鏂囦欢涓婁紶鍚庢病鏈夎Е鍙戝悜閲忓寲?
+
+**A**: 妫�鏌ユ槸鍚﹁皟鐢ㄤ簡 `/knowledgeBase/file/save` 鎺ュ彛銆備笂浼犳垚鍔熷悗蹇呴』璋冪敤姝ゆ帴鍙f墠鑳借Е鍙戝悜閲忓寲銆�
+
+### Q2: 鍚戦噺鍖栫姸鎬佷竴鐩存槸"澶勭悊涓�"?
+
+**A**: 鍙兘鍘熷洜:
+1. 鍚庡彴鏈嶅姟鏈惎鍔�
+2. Embedding妯″瀷璋冪敤澶辫触
+3. Pinecone杩炴帴澶辫触
+
+寤鸿鏌ョ湅鍚庡彴鏃ュ織鎺掓煡闂銆�
+
+### Q3: 闂瓟杩斿洖绌哄唴瀹�?
+
+**A**: 鍙兘鍘熷洜:
+1. 鐭ヨ瘑搴撲腑娌℃湁鏂囦欢
+2. 鏂囦欢鏈畬鎴愬悜閲忓寲
+3. 妫�绱㈢浉浼煎害浣庝簬闃堝��
+
+寤鸿妫�鏌ユ枃浠舵暟閲忓拰鍚戦噺鍖栫姸鎬併��
+
+### Q4: 娴佸紡鍝嶅簲鏄剧ず涔辩爜?
+
+**A**: 纭繚璇锋眰澶村寘鍚纭殑缂栫爜璁剧疆:
+```javascript
+headers: {
+ 'Content-Type': 'application/json'
+}
+```
+
+### Q5: 濡備綍璋冭瘯娴佸紡鎺ュ彛?
+
+**A**: 浣跨敤娴忚鍣ㄥ紑鍙戣�呭伐鍏�:
+1. 鎵撳紑 Network 鏍囩
+2. 鎵惧埌 `/ai/knowledge/chat` 璇锋眰
+3. 鏌ョ湅 Response 鏍囩,鍙互鐪嬪埌娴佸紡杩斿洖鐨勫唴瀹�
+
+---
+
+## 鍗佷簩銆佷紭鍖栧缓璁�
+
+### 12.1 鎬ц兘浼樺寲
+
+1. **铏氭嫙婊氬姩**: 娑堟伅鍒楄〃瓒呰繃100鏉℃椂浣跨敤铏氭嫙婊氬姩
+2. **闃叉姈鑺傛祦**: 鎼滅储杈撳叆浣跨敤闃叉姈,鐘舵�佽疆璇娇鐢ㄨ妭娴�
+3. **鎳掑姞杞�**: 鏂囦欢鍒楄〃浣跨敤鎳掑姞杞�
+
+### 12.2 鐢ㄦ埛浣撻獙浼樺寲
+
+1. **杩涘害鎻愮ず**: 鍚戦噺鍖栨椂鏄剧ず杩涘害鏉�
+2. **蹇嵎閿�**: 鏀寔蹇嵎閿搷浣�(濡� Ctrl+Enter 鍙戦��)
+3. **鍘嗗彶璁板綍**: 鏀寔鏌ョ湅鍘嗗彶闂瓟璁板綍
+4. **瀵煎嚭瀵硅瘽**: 鏀寔瀵煎嚭瀵硅瘽鍐呭
+
+### 12.3 鍔熻兘鎵╁睍
+
+1. **鏂囦欢棰勮**: 鏀寔鍦ㄧ嚎棰勮鏂囦欢鍐呭
+2. **鎵归噺鎿嶄綔**: 鏀寔鎵归噺鍒犻櫎銆佹壒閲忛噸鏂板鐞�
+3. **鍚戦噺鍖栭厤缃�**: 鍏佽鐢ㄦ埛閰嶇疆鍒囩墖澶у皬銆侀噸鍙犲ぇ灏�
+4. **鐩镐技搴﹂槇鍊�**: 鍏佽鐢ㄦ埛璋冩暣妫�绱㈢浉浼煎害闃堝��
+
+---
+
+## 鍗佷笁銆佹洿鏂版棩蹇�
+
+### v1.0.0 (2026-06-08)
+- 鉁� 瀹屾垚鐭ヨ瘑搴揅RUD鍔熻兘
+- 鉁� 瀹屾垚鏂囦欢涓婁紶涓庡悜閲忓寲鍔熻兘
+- 鉁� 瀹屾垚鐭ヨ瘑搴撻棶绛斿姛鑳�
+- 鉁� 瀹屾垚鏂囦欢绠$悊鍔熻兘
+- 鉁� 瀹屾垚鍚戦噺鍖栫姸鎬佹樉绀�
+
+### v1.1.0 (璁″垝涓�)
+- 馃敳 娣诲姞鍘嗗彶璁板綍鏌ヨ
+- 馃敳 娣诲姞鎵归噺鎿嶄綔鍔熻兘
+- 馃敳 娣诲姞鏂囦欢棰勮鍔熻兘
+- 馃敳 浼樺寲鍚戦噺鍖栬繘搴︽樉绀�
\ No newline at end of file
diff --git a/src/api/collaborativeApproval/knowledgeBase.js b/src/api/collaborativeApproval/knowledgeBase.js
index 8ad24dd..d7dce3e 100644
--- a/src/api/collaborativeApproval/knowledgeBase.js
+++ b/src/api/collaborativeApproval/knowledgeBase.js
@@ -1,4 +1,5 @@
import request from "@/utils/request";
+import { getToken } from '@/utils/auth';
// 鏌ヨ鐭ヨ瘑搴撳垪琛�
export function listKnowledgeBase(query) {
@@ -87,20 +88,24 @@
});
}
-// 鑾峰彇鐭ヨ瘑搴撳垪琛�(闂瓟鐢�)
-export function getKnowledgeBaseListForChat() {
+// 鏌ヨ鐭ヨ瘑搴撻棶绛斿巻鍙�
+export function getKnowledgeHistory(memoryId) {
return request({
- url: "/ai/knowledge/list",
+ url: `/ai/knowledge/history/${memoryId}`,
method: "get",
});
}
// 鐭ヨ瘑搴撻棶绛�(娴佸紡)
export async function knowledgeChat(data) {
- const response = await fetch("/api/ai/knowledge/chat", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify(data),
+ const token = getToken();
+ const response = await fetch(import.meta.env.VITE_APP_BASE_API + '/ai/knowledge/chat', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': 'Bearer ' + token
+ },
+ body: JSON.stringify(data)
});
return response.body;
}
diff --git a/src/views/collaborativeApproval/knowledgeBase/index.vue b/src/views/collaborativeApproval/knowledgeBase/index.vue
index 8d625cf..907e51f 100644
--- a/src/views/collaborativeApproval/knowledgeBase/index.vue
+++ b/src/views/collaborativeApproval/knowledgeBase/index.vue
@@ -259,6 +259,22 @@
>
淇濆瓨鏂囦欢鍏宠仈
</el-button>
+ <el-button
+ v-if="uploadedBlobIds.length > 0"
+ type="text"
+ @click="clearUploadedFiles"
+ style="margin-left: 10px"
+ >
+ 娓呯┖寰呬繚瀛樺垪琛�
+ </el-button>
+ </div>
+
+ <!-- 寰呬繚瀛樼殑鏂囦欢鍒楄〃 -->
+ <div v-if="uploadedBlobIds.length > 0" class="uploaded-list">
+ <div class="uploaded-tip">
+ <el-icon style="color: #409eff"><InfoFilled /></el-icon>
+ <span>宸蹭笂浼� {{ uploadedBlobIds.length }} 涓枃浠�,璇风偣鍑�"淇濆瓨鏂囦欢鍏宠仈"鎸夐挳瑙﹀彂鍚戦噺鍖栧鐞�</span>
+ </div>
</div>
<!-- 鏂囦欢鍒楄〃涓庡悜閲忓寲鐘舵�� -->
@@ -273,6 +289,12 @@
</template>
</el-table-column>
<el-table-column prop="chunkCount" label="鍒囩墖鏁�" width="100" align="center" />
+ <el-table-column label="閿欒淇℃伅" width="200" show-overflow-tooltip>
+ <template #default="{ row }">
+ <span v-if="row.vectorError" style="color: #f56c6c">{{ row.vectorError }}</span>
+ <span v-else style="color: #909399">-</span>
+ </template>
+ </el-table-column>
<el-table-column prop="createTime" label="涓婁紶鏃堕棿" width="180" />
<el-table-column label="鎿嶄綔" width="150" align="center">
<template #default="{ row }">
@@ -326,7 +348,7 @@
<div class="chat-input">
<el-input
v-model="inputQuestion"
- placeholder="璇疯緭鍏ラ棶棰橈紝鎸夊洖杞﹀彂閫�"
+ placeholder="璇疯緭鍏ラ棶棰�,鎸夊洖杞﹀彂閫�(Ctrl+Enter蹇嵎鍙戦��)"
@keyup.enter="sendMessage"
:disabled="chatLoading"
>
@@ -334,6 +356,9 @@
<el-button @click="sendMessage" :loading="chatLoading">鍙戦��</el-button>
</template>
</el-input>
+ <div class="chat-actions">
+ <el-button type="text" size="small" @click="clearMessages">娓呯┖瀵硅瘽</el-button>
+ </div>
</div>
</div>
</FormDialog>
@@ -341,7 +366,7 @@
</template>
<script setup>
-import { Search } from "@element-plus/icons-vue";
+import { Search, InfoFilled } from "@element-plus/icons-vue";
import { onMounted, ref, reactive, toRefs, getCurrentInstance, computed, watch, nextTick } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import PIMTable from "@/components/PIMTable/PIMTable.vue";
@@ -412,6 +437,7 @@
fileList: [],
uploadedBlobIds: [],
savingFiles: false,
+ vectorStatusTimer: null, // 鍚戦噺鍖栫姸鎬佽疆璇㈠畾鏃跺櫒
chatDialogVisible: false,
messages: [],
inputQuestion: "",
@@ -436,6 +462,7 @@
fileList,
uploadedBlobIds,
savingFiles,
+ vectorStatusTimer,
chatDialogVisible,
messages,
inputQuestion,
@@ -872,9 +899,45 @@
try {
const res = await getVectorStatus(currentKnowledgeBase.value.id);
fileList.value = res.data || [];
+
+ // 妫�鏌ユ槸鍚︽湁澶勭悊涓殑鏂囦欢,濡傛灉鏈夊垯鍚姩杞
+ const hasProcessing = res.data.some(item => item.vectorStatus === 1);
+ if (hasProcessing && !vectorStatusTimer.value) {
+ startVectorStatusPolling();
+ } else if (!hasProcessing && vectorStatusTimer.value) {
+ stopVectorStatusPolling();
+ }
} catch (error) {
console.error("鍔犺浇鏂囦欢鍒楄〃澶辫触:", error);
ElMessage.error("鍔犺浇鏂囦欢鍒楄〃澶辫触");
+ }
+};
+
+// 寮�濮嬭疆璇㈠悜閲忓寲鐘舵��
+const startVectorStatusPolling = () => {
+ vectorStatusTimer.value = setInterval(async () => {
+ try {
+ const res = await getVectorStatus(currentKnowledgeBase.value.id);
+ fileList.value = res.data || [];
+
+ // 妫�鏌ユ槸鍚﹁繕鏈夊鐞嗕腑鐨勬枃浠�
+ const hasProcessing = res.data.some(item => item.vectorStatus === 1);
+ if (!hasProcessing) {
+ stopVectorStatusPolling();
+ ElMessage.success("鎵�鏈夋枃浠跺悜閲忓寲澶勭悊瀹屾垚");
+ }
+ } catch (error) {
+ console.error("杞鍚戦噺鍖栫姸鎬佸け璐�:", error);
+ stopVectorStatusPolling();
+ }
+ }, 3000); // 姣�3绉掕疆璇竴娆�
+};
+
+// 鍋滄杞鍚戦噺鍖栫姸鎬�
+const stopVectorStatusPolling = () => {
+ if (vectorStatusTimer.value) {
+ clearInterval(vectorStatusTimer.value);
+ vectorStatusTimer.value = null;
}
};
@@ -957,6 +1020,12 @@
}
};
+// 娓呯┖寰呬繚瀛樼殑鏂囦欢鍒楄〃
+const clearUploadedFiles = () => {
+ uploadedBlobIds.value = [];
+ ElMessage.success("宸叉竻绌哄緟淇濆瓨鏂囦欢鍒楄〃");
+};
+
// 鍒犻櫎鏂囦欢
const deleteFile = async (row) => {
try {
@@ -1009,7 +1078,8 @@
currentKnowledgeBase.value = null;
fileList.value = [];
uploadedBlobIds.value = [];
- getList(); // 鍒锋柊涓诲垪琛紝鏇存柊鏂囦欢鏁伴噺
+ stopVectorStatusPolling(); // 鍋滄杞
+ getList(); // 鍒锋柊涓诲垪琛�,鏇存柊鏂囦欢鏁伴噺
};
// ============ 鐭ヨ瘑搴撻棶绛旂浉鍏� ============
@@ -1027,6 +1097,11 @@
const sendMessage = async () => {
if (!inputQuestion.value.trim()) {
ElMessage.warning("璇疯緭鍏ラ棶棰�");
+ return;
+ }
+
+ if (!currentKnowledgeBase.value?.id) {
+ ElMessage.error("鐭ヨ瘑搴撲俊鎭紓甯�");
return;
}
@@ -1061,7 +1136,8 @@
});
if (!response.ok) {
- throw new Error('璇锋眰澶辫触');
+ const errorText = await response.text();
+ throw new Error(errorText || '璇锋眰澶辫触');
}
// 澶勭悊SSE娴佸紡鍝嶅簲
@@ -1083,16 +1159,40 @@
await nextTick();
scrollToBottom();
}
+
+ // 濡傛灉AI杩斿洖绌哄唴瀹�,鏄剧ず鎻愮ず
+ if (!aiContent.trim()) {
+ messages.value[messages.value.length - 1].content = '鎶辨瓑,鐭ヨ瘑搴撲腑鏈壘鍒扮浉鍏冲唴瀹�,璇峰皾璇曞叾浠栭棶棰樸��';
+ }
} catch (error) {
console.error("闂瓟璇锋眰澶辫触:", error);
- ElMessage.error("闂瓟璇锋眰澶辫触锛岃绋嶅悗閲嶈瘯");
+ ElMessage.error("闂瓟璇锋眰澶辫触,璇风◢鍚庨噸璇�");
messages.value.push({
role: 'assistant',
- content: '鎶辨瓑锛屽彂鐢熶簡閿欒锛岃绋嶅悗閲嶈瘯'
+ content: '鎶辨瓑,鍙戠敓浜嗛敊璇�,璇风◢鍚庨噸璇�'
});
} finally {
chatLoading.value = false;
}
+};
+
+// 娓呯┖瀵硅瘽
+const clearMessages = () => {
+ ElMessageBox.confirm(
+ "纭畾瑕佹竻绌烘墍鏈夊璇濊褰曞悧?",
+ "娓呯┖纭",
+ {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ }
+ ).then(() => {
+ messages.value = [];
+ memoryId.value = crypto.randomUUID(); // 閲嶆柊鐢熸垚浼氳瘽ID
+ ElMessage.success("瀵硅瘽宸叉竻绌�");
+ }).catch(() => {
+ // 鐢ㄦ埛鍙栨秷
+ });
};
// 婊氬姩鍒板簳閮�
@@ -1195,6 +1295,22 @@
align-items: center;
}
+.uploaded-list {
+ margin-top: 16px;
+ padding: 12px;
+ background: #f0f9ff;
+ border-radius: 6px;
+ border: 1px solid #b3d8ff;
+}
+
+.uploaded-tip {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ color: #409eff;
+ font-size: 14px;
+}
+
/* 鐭ヨ瘑搴撻棶绛旀牱寮� */
.knowledge-chat {
display: flex;
@@ -1272,4 +1388,9 @@
margin-top: auto;
}
+.chat-actions {
+ margin-top: 8px;
+ text-align: right;
+}
+
</style>
diff --git a/vite.config.js b/vite.config.js
index fcc019d..474f7ac 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -8,7 +8,7 @@
const { VITE_APP_ENV } = env;
const baseUrl =
env.VITE_APP_ENV === "development"
- ? "http://1.15.17.182:9048"
+ ? "http://localhost:7005"
: env.VITE_BASE_API;
const javaUrl =
env.VITE_APP_ENV === "development"
--
Gitblit v1.9.3