| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # ç¥è¯åºæ¨¡å宿´å端å®ç°ææ¡£ |
| | | |
| | | ## ä¸ãæ¨¡åæ¦è¿° |
| | | |
| | | ç¥è¯åºæ¨¡åæ¯ä¸ä¸ªéæäºRAG(æ£ç´¢å¢å¼ºçæ)ææ¯çæºè½ç¥è¯ç®¡çç³»ç»,æ¯æ: |
| | | - **ç¥è¯åºCRUD管ç** - å建ãç¼è¾ãå é¤ãæ¥è¯¢ç¥è¯åº |
| | | - **æä»¶ä¸ä¼ ä¸åéå** - æ¯æå¤ç§æä»¶æ ¼å¼,èªå¨è¿è¡åéåçå¤ç |
| | | - **æºè½é®ç** - åºäºä¸ä¼ æä»¶å
容è¿è¡AIé®ç |
| | | - **æä»¶ç®¡ç** - æ¥çæä»¶åéåç¶æ,æ¯æéæ°å¤çåå é¤ |
| | | |
| | | ### ææ¯æ¶æ |
| | | - **åç«¯æ¡æ¶**: Vue 3 + Composition API |
| | | - **UIç»ä»¶åº**: Element Plus |
| | | - **åéæ°æ®åº**: Pinecone |
| | | - **AI模å**: é¿éäºéä¹åé® |
| | | - **æä»¶å¤ç**: æ¯ædocxãxlsxãpdfãtxtãmdçæ ¼å¼ |
| | | |
| | | --- |
| | | |
| | | ## äºãæä»¶ç»æ |
| | | |
| | | ``` |
| | | src/ |
| | | âââ api/ |
| | | â âââ collaborativeApproval/ |
| | | â âââ knowledgeBase.js # APIæ¥å£å°è£
|
| | | âââ views/ |
| | | â âââ collaborativeApproval/ |
| | | â âââ knowledgeBase/ |
| | | â âââ index.vue # 主页é¢ç»ä»¶ |
| | | âââ components/ |
| | | âââ PIMTable/ |
| | | â âââ PIMTable.vue # è¡¨æ ¼ç»ä»¶ |
| | | âââ Dialog/ |
| | | âââ FormDialog.vue # å¼¹çªç»ä»¶ |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## ä¸ãAPIæ¥å£å®ä¹ |
| | | |
| | | ### 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, // ç¥è¯åºID |
| | | storageBlobIds: [123, 124] // ä¸ä¼ æä»¶è¿åçblob 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页ç¾å¸å±,å
å«ä¸¤ä¸ªä¸»è¦åè½æ¨¡å: |
| | | - **ç¥è¯åºç®¡ç** - ç¥è¯åºCRUDæä½ |
| | | - **ç¥è¯åºé®ç** - åºäº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: "è§£å³æç", |
| | | 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ç§å·æ°æä»¶å表 |
| | | â |
| | | æ¾ç¤ºåéåç¶æ(å¾
å¤çâå¤çä¸â已宿) |
| | | ``` |
| | | |
| | | #### 代ç å®ç° |
| | | |
| | | ```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ãmdãdocxãxlsxãxlsãpdf æ ¼å¼çæä»¶'); |
| | | 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("æä»¶å
³èä¿åæå,æ£å¨åå°å¤çåéå"); |
| | | 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 (æµå¼æ¥å£) |
| | | â |
| | | å端å¤ç: |
| | | - 对é®é¢è¿è¡åéå |
| | | - å¨Pinecone䏿£ç´¢ç¸å
³åç |
| | | - æå»ºä¸ä¸æPrompt |
| | | - è°ç¨LLMçæåç |
| | | â |
| | | æµå¼è¿åAIåç |
| | | â |
| | | åç«¯å®æ¶æ¾ç¤ºåçå
容 |
| | | â |
| | | èªå¨æ»å¨å°åºé¨ |
| | | ``` |
| | | |
| | | #### 代ç å®ç° |
| | | |
| | | ```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">æ£å¨æèä¸...</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 | å¤çä¸ | æ£å¨è¿è¡åéåçå¤ç | 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: 'è§£å³æ¹æ¡', elTagType: 'primary' }, |
| | | { value: 'experience', label: 'ç»éªå享', elTagType: 'info' }, |
| | | { value: 'guide', label: 'æä½æå', elTagType: 'danger' } |
| | | ]; |
| | | ``` |
| | | |
| | | ### 7.3 è§£å³æçæ å° |
| | | |
| | | ```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ãmdãdocxãxlsxãxlsãpdf æ ¼å¼ |
| | | 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 ç¥è¯åºç®¡ç |
| | | |
| | | - [ ] æ°å¢ç¥è¯åºæå |
| | | - [ ] ç¼è¾ç¥è¯åºæå |
| | | - [ ] å é¤ç¥è¯åºæå(å个/æ¹é) |
| | | - [ ] æç´¢åè½æ£å¸¸(ææ é¢ãç±»å) |
| | | - [ ] å页åè½æ£å¸¸ |
| | | - [ ] 导åºåè½æ£å¸¸ |
| | | |
| | | ### 10.2 æä»¶ä¸ä¼ |
| | | |
| | | - [ ] åæä»¶ä¸ä¼ æå |
| | | - [ ] 夿件ä¸ä¼ æå |
| | | - [ ] æä»¶ç±»åæ ¡éªæ£å¸¸ |
| | | - [ ] æä»¶å¤§å°æ ¡éªæ£å¸¸ |
| | | - [ ] ä¿åæä»¶å
³èæå |
| | | - [ ] åéåç¶ææ£ç¡®æ¾ç¤º |
| | | |
| | | ### 10.3 æä»¶ç®¡ç |
| | | |
| | | - [ ] æ¥çæä»¶å表æ£å¸¸ |
| | | - [ ] åéåç¶æè½®è¯¢æ£å¸¸ |
| | | - [ ] éæ°å¤ç失败æä»¶æå |
| | | - [ ] å 餿件æå |
| | | - [ ] æä»¶é¢è§/ä¸è½½æ£å¸¸ |
| | | |
| | | ### 10.4 ç¥è¯åºé®ç |
| | | |
| | | - [ ] é®çå¼¹çªæå¼æ£å¸¸ |
| | | - [ ] åéé®é¢æå |
| | | - [ ] æµå¼ååºæ¾ç¤ºæ£å¸¸ |
| | | - [ ] å¤è½®å¯¹è¯æ£å¸¸ |
| | | - [ ] èªå¨æ»å¨å°åºé¨ |
| | | - [ ] é误å¤çæ£å¸¸ |
| | | |
| | | --- |
| | | |
| | | ## åä¸ã常è§é®é¢ |
| | | |
| | | ### Q1: æä»¶ä¸ä¼ åæ²¡æè§¦ååéå? |
| | | |
| | | **A**: æ£æ¥æ¯å¦è°ç¨äº `/knowledgeBase/file/save` æ¥å£ãä¸ä¼ æååå¿
é¡»è°ç¨æ¤æ¥å£æè½è§¦ååéåã |
| | | |
| | | ### 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) |
| | | - â
宿ç¥è¯åºCRUDåè½ |
| | | - â
宿æä»¶ä¸ä¼ ä¸åéååè½ |
| | | - â
宿ç¥è¯åºé®çåè½ |
| | | - â
宿æä»¶ç®¡çåè½ |
| | | - â
宿åéåç¶ææ¾ç¤º |
| | | |
| | | ### v1.1.0 (计åä¸) |
| | | - ð² æ·»å åå²è®°å½æ¥è¯¢ |
| | | - ð² æ·»å æ¹éæä½åè½ |
| | | - ð² æ·»å æä»¶é¢è§åè½ |
| | | - ð² ä¼ååéåè¿åº¦æ¾ç¤º |