From a6dd0c868784dbebe881a463b7962b94f952915a Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期一, 08 六月 2026 16:00:54 +0800
Subject: [PATCH] feat(knowledgeBase): 添加知识库文件管理和问答功能
---
src/views/collaborativeApproval/knowledgeBase/index.vue | 1334 ++++++++++++++++++++++++++++++++++++++-------------------
1 files changed, 882 insertions(+), 452 deletions(-)
diff --git a/src/views/collaborativeApproval/knowledgeBase/index.vue b/src/views/collaborativeApproval/knowledgeBase/index.vue
index 279738e..669b260 100644
--- a/src/views/collaborativeApproval/knowledgeBase/index.vue
+++ b/src/views/collaborativeApproval/knowledgeBase/index.vue
@@ -1,238 +1,409 @@
<template>
<div class="app-container">
- <div class="search_form">
- <div>
- <span class="search_title">鐭ヨ瘑鏍囬锛�</span>
- <el-input
- v-model="searchForm.title"
- style="width: 240px"
- placeholder="璇疯緭鍏ョ煡璇嗘爣棰樻悳绱�"
- @change="handleQuery"
- clearable
- :prefix-icon="Search"
- />
- <span class="search_title ml10">鐭ヨ瘑绫诲瀷锛�</span>
- <el-select v-model="searchForm.type" clearable @change="handleQuery" style="width: 240px">
- <el-option label="鍚堝悓鐗规壒" :value="'contract'" />
- <el-option label="瀹℃壒妗堜緥" :value="'approval'" />
- <el-option label="瑙e喅鏂规" :value="'solution'" />
- <el-option label="缁忛獙鎬荤粨" :value="'experience'" />
- <el-option label="鎿嶄綔鎸囧崡" :value="'guide'" />
- </el-select>
- <el-button type="primary" @click="handleQuery" style="margin-left: 10px">
- 鎼滅储
- </el-button>
- </div>
- <div>
- <el-button type="primary" @click="openForm('add')">鏂板鐭ヨ瘑</el-button>
- <el-button type="danger" plain @click="handleDelete">鍒犻櫎</el-button>
- </div>
- </div>
-
- <div class="table_list">
- <PIMTable
- rowKey="id"
- :column="tableColumn"
- :tableData="tableData"
- :page="page"
- :isSelection="true"
- @selection-change="handleSelectionChange"
- :tableLoading="tableLoading"
- @pagination="pagination"
- :total="page.total"
- ></PIMTable>
- </div>
-
- <!-- 鏂板/缂栬緫鐭ヨ瘑寮圭獥 -->
- <el-dialog
- v-model="dialogVisible"
- :title="dialogTitle"
- width="800px"
- :close-on-click-modal="false"
- >
- <el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
- <el-row :gutter="20">
- <el-col :span="12">
- <el-form-item label="鐭ヨ瘑鏍囬" prop="title">
- <el-input v-model="form.title" placeholder="璇疯緭鍏ョ煡璇嗘爣棰�" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="鐭ヨ瘑绫诲瀷" prop="type">
- <el-select v-model="form.type" placeholder="璇烽�夋嫨鐭ヨ瘑绫诲瀷" style="width: 100%">
- <el-option label="鍚堝悓鐗规壒" value="contract" />
- <el-option label="瀹℃壒妗堜緥" value="approval" />
- <el-option label="瑙e喅鏂规" value="solution" />
- <el-option label="缁忛獙鎬荤粨" value="experience" />
- <el-option label="鎿嶄綔鎸囧崡" value="guide" />
- </el-select>
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="20">
- <el-col :span="12">
- <el-form-item label="閫傜敤鍦烘櫙" prop="scenario">
- <el-input v-model="form.scenario" placeholder="璇疯緭鍏ラ�傜敤鍦烘櫙" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="瑙e喅鏁堢巼" prop="efficiency">
- <el-select v-model="form.efficiency" placeholder="璇烽�夋嫨瑙e喅鏁堢巼" style="width: 100%">
- <el-option label="鏄捐憲鎻愬崌" value="high" />
- <el-option label="涓�鑸彁鍗�" value="medium" />
- <el-option label="杞诲井鎻愬崌" value="low" />
- </el-select>
- </el-form-item>
- </el-col>
- </el-row>
- <el-form-item label="闂鎻忚堪" prop="problem">
- <el-input
- v-model="form.problem"
- type="textarea"
- :rows="3"
- placeholder="璇锋弿杩伴亣鍒扮殑闂"
- />
- </el-form-item>
- <el-form-item label="瑙e喅鏂规" prop="solution">
- <el-input
- v-model="form.solution"
- type="textarea"
- :rows="4"
- placeholder="璇疯缁嗘弿杩拌В鍐虫柟妗�"
- />
- </el-form-item>
- <el-form-item label="鍏抽敭瑕佺偣" prop="keyPoints">
- <el-input
- v-model="form.keyPoints"
- type="textarea"
- :rows="3"
- placeholder="璇疯緭鍏ュ叧閿鐐癸紝鐢ㄩ�楀彿鍒嗛殧"
- />
- </el-form-item>
- <el-row :gutter="20">
- <el-col :span="12">
- <el-form-item label="鍒涘缓浜�" prop="creator">
- <el-input v-model="form.creator" placeholder="璇疯緭鍏ュ垱寤轰汉" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="浣跨敤娆℃暟" prop="usageCount">
- <el-input-number v-model="form.usageCount" :min="0" style="width: 100%" />
- </el-form-item>
- </el-col>
- </el-row>
- </el-form>
- <template #footer>
- <span class="dialog-footer">
- <el-button @click="dialogVisible = false">鍙栨秷</el-button>
- <el-button type="primary" @click="submitForm">纭畾</el-button>
- </span>
- </template>
- </el-dialog>
-
- <!-- 鏌ョ湅鐭ヨ瘑璇︽儏寮圭獥 -->
- <el-dialog
- v-model="viewDialogVisible"
- title="鐭ヨ瘑璇︽儏"
- width="900px"
- :close-on-click-modal="false"
- >
- <div class="knowledge-detail">
- <el-descriptions :column="2" border>
- <el-descriptions-item label="鐭ヨ瘑鏍囬" :span="2">
- <span class="detail-title">{{ currentKnowledge.title }}</span>
- </el-descriptions-item>
- <el-descriptions-item label="鐭ヨ瘑绫诲瀷">
- <el-tag :type="getTypeTagType(currentKnowledge.type)">
- {{ getTypeLabel(currentKnowledge.type) }}
- </el-tag>
- </el-descriptions-item>
- <el-descriptions-item label="閫傜敤鍦烘櫙">
- {{ currentKnowledge.scenario }}
- </el-descriptions-item>
- <el-descriptions-item label="瑙e喅鏁堢巼">
- <el-tag :type="getEfficiencyTagType(currentKnowledge.efficiency)">
- {{ getEfficiencyLabel(currentKnowledge.efficiency) }}
- </el-tag>
- </el-descriptions-item>
- <el-descriptions-item label="浣跨敤娆℃暟">
- <el-tag type="info">{{ currentKnowledge.usageCount }} 娆�</el-tag>
- </el-descriptions-item>
- <el-descriptions-item label="鍒涘缓浜�">
- {{ currentKnowledge.creator }}
- </el-descriptions-item>
- <el-descriptions-item label="鍒涘缓鏃堕棿">
- {{ currentKnowledge.createTime }}
- </el-descriptions-item>
- </el-descriptions>
-
- <div class="detail-section">
- <h4>闂鎻忚堪</h4>
- <div class="detail-content">{{ currentKnowledge.problem }}</div>
- </div>
-
- <div class="detail-section">
- <h4>瑙e喅鏂规</h4>
- <div class="detail-content">{{ currentKnowledge.solution }}</div>
- </div>
-
- <div class="detail-section">
- <h4>鍏抽敭瑕佺偣</h4>
- <div class="key-points">
- <el-tag
- v-for="(point, index) in currentKnowledge.keyPoints.split(',')"
- :key="index"
- type="success"
- style="margin-right: 8px; margin-bottom: 8px;"
- >
- {{ point.trim() }}
- </el-tag>
+ <!-- Tab椤电鍒囨崲 -->
+ <el-tabs v-model="activeTab" class="knowledge-tabs">
+ <!-- 鐭ヨ瘑搴撶鐞員ab -->
+ <el-tab-pane label="鐭ヨ瘑搴撶鐞�" name="manage">
+ <div class="search_form" style="margin-bottom: 20px;">
+ <div>
+ <span class="search_title">鐭ヨ瘑鏍囬锛�</span>
+ <el-input
+ v-model="searchForm.title"
+ style="width: 240px"
+ placeholder="璇疯緭鍏ョ煡璇嗘爣棰樻悳绱�"
+ @change="handleQuery"
+ clearable
+ :prefix-icon="Search"
+ />
+ <span class="search_title ml10">鐭ヨ瘑绫诲瀷锛�</span>
+ <el-select v-model="searchForm.type" clearable @change="handleQuery" style="width: 240px">
+ <el-option
+ v-for="item in knowledgeTypeOptions"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"
+ />
+ </el-select>
+ <el-button type="primary" @click="handleQuery" style="margin-left: 10px">
+ 鎼滅储
+ </el-button>
+ </div>
+ <div>
+ <el-button @click="handleExport" style="margin-right: 10px">瀵煎嚭</el-button>
+ <el-button type="primary" @click="openForm('add')">鏂板鐭ヨ瘑</el-button>
+ <el-button type="danger" plain @click="handleDelete">鍒犻櫎</el-button>
</div>
</div>
- <div class="detail-section">
- <h4>浣跨敤缁熻</h4>
- <div class="usage-stats">
+ <div class="table_list">
+ <PIMTable
+ rowKey="id"
+ :column="tableColumn"
+ :tableData="tableData"
+ :page="page"
+ :isSelection="true"
+ @selection-change="handleSelectionChange"
+ :tableLoading="tableLoading"
+ @pagination="pagination"
+ :total="page.total"
+ ></PIMTable>
+ </div>
+
+ <!-- 鏂板/缂栬緫鐭ヨ瘑寮圭獥 -->
+ <FormDialog
+ v-model="dialogVisible"
+ :title="dialogTitle"
+ :width="'800px'"
+ @close="closeKnowledgeDialog"
+ @confirm="submitForm"
+ @cancel="closeKnowledgeDialog"
+ >
+ <el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
<el-row :gutter="20">
- <el-col :span="8">
- <div class="stat-item">
- <div class="stat-number">{{ currentKnowledge.usageCount }}</div>
- <div class="stat-label">浣跨敤娆℃暟</div>
- </div>
+ <el-col :span="12">
+ <el-form-item label="鐭ヨ瘑鏍囬" prop="title">
+ <el-input v-model="form.title" placeholder="璇疯緭鍏ョ煡璇嗘爣棰�" />
+ </el-form-item>
</el-col>
- <el-col :span="8">
- <div class="stat-item">
- <div class="stat-number">{{ getEfficiencyScore(currentKnowledge.efficiency) }}%</div>
- <div class="stat-label">鏁堢巼鎻愬崌</div>
- </div>
- </el-col>
- <el-col :span="8">
- <div class="stat-item">
- <div class="stat-number">{{ getTimeSaved(currentKnowledge.efficiency) }}</div>
- <div class="stat-label">骞冲潎鑺傜渷鏃堕棿</div>
- </div>
+ <el-col :span="12">
+ <el-form-item label="鐭ヨ瘑绫诲瀷" prop="type">
+ <el-select v-model="form.type" placeholder="璇烽�夋嫨鐭ヨ瘑绫诲瀷" style="width: 100%">
+ <el-option
+ v-for="item in knowledgeTypeOptions"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"
+ />
+ </el-select>
+ </el-form-item>
</el-col>
</el-row>
- </div>
- </div>
- </div>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="閫傜敤鍦烘櫙" prop="scenario">
+ <el-input v-model="form.scenario" placeholder="璇疯緭鍏ラ�傜敤鍦烘櫙" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="瑙e喅鏁堢巼" prop="efficiency">
+ <el-select v-model="form.efficiency" placeholder="璇烽�夋嫨瑙e喅鏁堢巼" style="width: 100%">
+ <el-option label="鏄捐憲鎻愬崌" value="high" />
+ <el-option label="涓�鑸彁鍗�" value="medium" />
+ <el-option label="杞诲井鎻愬崌" value="low" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-form-item label="闂鎻忚堪" prop="problem">
+ <el-input
+ v-model="form.problem"
+ type="textarea"
+ :rows="3"
+ placeholder="璇锋弿杩伴亣鍒扮殑闂"
+ />
+ </el-form-item>
+ <el-form-item label="瑙e喅鏂规" prop="solution">
+ <el-input
+ v-model="form.solution"
+ type="textarea"
+ :rows="4"
+ placeholder="璇疯缁嗘弿杩拌В鍐虫柟妗�"
+ />
+ </el-form-item>
+ <el-form-item label="鍏抽敭瑕佺偣" prop="keyPoints">
+ <el-input
+ v-model="form.keyPoints"
+ type="textarea"
+ :rows="3"
+ placeholder="璇疯緭鍏ュ叧閿鐐癸紝鐢ㄩ�楀彿鍒嗛殧"
+ />
+ </el-form-item>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鍒涘缓浜�" prop="creator">
+ <el-select v-model="form.creator" placeholder="璇烽�夋嫨鍒涘缓浜�" style="width: 100%" filterable>
+ <el-option
+ v-for="user in userList"
+ :key="user.userId"
+ :label="user.nickName"
+ :value="user.nickName"
+ />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="浣跨敤娆℃暟" prop="usageCount">
+ <el-input-number v-model="form.usageCount" :min="0" style="width: 100%" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ </FormDialog>
- <template #footer>
- <span class="dialog-footer">
- <el-button @click="viewDialogVisible = false">鍏抽棴</el-button>
- <el-button type="primary" @click="copyKnowledge">澶嶅埗鐭ヨ瘑</el-button>
- <el-button type="success" @click="markAsFavorite">鏀惰棌</el-button>
- </span>
- </template>
- </el-dialog>
+ <!-- 鏌ョ湅鐭ヨ瘑璇︽儏寮圭獥 -->
+ <FormDialog
+ v-model="viewDialogVisible"
+ title="鐭ヨ瘑璇︽儏"
+ :width="'900px'"
+ @close="closeViewDialog"
+ @confirm="handleViewDialogConfirm"
+ @cancel="closeViewDialog"
+ >
+ <div class="knowledge-detail">
+ <el-descriptions :column="2" border>
+ <el-descriptions-item label="鐭ヨ瘑鏍囬" :span="2">
+ <span class="detail-title">{{ currentKnowledge.title }}</span>
+ </el-descriptions-item>
+ <el-descriptions-item label="鐭ヨ瘑绫诲瀷">
+ <el-tag :type="getTypeTagType(currentKnowledge.type)">
+ {{ getTypeLabel(currentKnowledge.type) }}
+ </el-tag>
+ </el-descriptions-item>
+ <el-descriptions-item label="閫傜敤鍦烘櫙">
+ {{ currentKnowledge.scenario }}
+ </el-descriptions-item>
+ <el-descriptions-item label="瑙e喅鏁堢巼">
+ <el-tag :type="getEfficiencyTagType(currentKnowledge.efficiency)">
+ {{ getEfficiencyLabel(currentKnowledge.efficiency) }}
+ </el-tag>
+ </el-descriptions-item>
+ <el-descriptions-item label="浣跨敤娆℃暟">
+ <el-tag type="info">{{ currentKnowledge.usageCount }} 娆�</el-tag>
+ </el-descriptions-item>
+ <el-descriptions-item label="鍒涘缓浜�">
+ {{ currentKnowledge.creator }}
+ </el-descriptions-item>
+ <el-descriptions-item label="鍒涘缓鏃堕棿">
+ {{ currentKnowledge.createTime }}
+ </el-descriptions-item>
+ </el-descriptions>
+
+ <div class="detail-section">
+ <h4>闂鎻忚堪</h4>
+ <div class="detail-content">{{ currentKnowledge.problem }}</div>
+ </div>
+
+ <div class="detail-section">
+ <h4>瑙e喅鏂规</h4>
+ <div class="detail-content">{{ currentKnowledge.solution }}</div>
+ </div>
+
+ <div class="detail-section">
+ <h4>鍏抽敭瑕佺偣</h4>
+ <div class="key-points">
+ <el-tag
+ v-for="(point, index) in currentKnowledge.keyPoints?.split(',') || []"
+ :key="index"
+ type="success"
+ style="margin-right: 8px; margin-bottom: 8px;"
+ >
+ {{ point.trim() }}
+ </el-tag>
+ </div>
+ </div>
+
+ <div class="detail-section">
+ <h4>浣跨敤缁熻</h4>
+ <div class="usage-stats">
+ <el-row :gutter="20">
+ <el-col :span="8">
+ <div class="stat-item">
+ <div class="stat-number">{{ currentKnowledge.usageCount }}</div>
+ <div class="stat-label">浣跨敤娆℃暟</div>
+ </div>
+ </el-col>
+ <el-col :span="8">
+ <div class="stat-item">
+ <div class="stat-number">{{ getEfficiencyScore(currentKnowledge.efficiency) }}%</div>
+ <div class="stat-label">鏁堢巼鎻愬崌</div>
+ </div>
+ </el-col>
+ <el-col :span="8">
+ <div class="stat-item">
+ <div class="stat-number">{{ getTimeSaved(currentKnowledge.efficiency) }}</div>
+ <div class="stat-label">骞冲潎鑺傜渷鏃堕棿</div>
+ </div>
+ </el-col>
+ </el-row>
+ </div>
+ </div>
+ </div>
+ </FormDialog>
+ </el-tab-pane>
+
+ <!-- 鏂囦欢绠$悊Tab -->
+ <el-tab-pane label="鏂囦欢绠$悊" name="files">
+ <div class="file-manage-container">
+ <!-- 鐭ヨ瘑搴撻�夋嫨 -->
+ <div class="kb-selector" style="margin-bottom: 20px;">
+ <span class="search_title">閫夋嫨鐭ヨ瘑搴擄細</span>
+ <el-select v-model="selectedKnowledgeBaseId" placeholder="璇烽�夋嫨鐭ヨ瘑搴�" style="width: 300px" @change="handleKnowledgeBaseChange">
+ <el-option
+ v-for="kb in tableData"
+ :key="kb.id"
+ :label="kb.title"
+ :value="kb.id"
+ />
+ </el-select>
+ <el-button type="primary" style="margin-left: 10px" @click="refreshFileList" :disabled="!selectedKnowledgeBaseId">
+ 鍒锋柊鐘舵��
+ </el-button>
+ </div>
+
+ <!-- 鏂囦欢涓婁紶鍖哄煙 -->
+ <div v-if="selectedKnowledgeBaseId" class="upload-section">
+ <el-upload
+ :action="uploadUrl"
+ :headers="uploadHeaders"
+ :on-success="handleUploadSuccess"
+ :before-upload="beforeUpload"
+ :accept="acceptTypes"
+ :file-list="uploadFileList"
+ :show-file-list="false"
+ name="files"
+ multiple
+ >
+ <el-button type="primary">
+ <el-icon><Upload /></el-icon>
+ 涓婁紶鏂囦欢
+ </el-button>
+ </el-upload>
+ <div class="upload-tip">
+ 鏀寔 docx銆亁lsx銆亁ls銆乸df銆乼xt銆乵d銆乯son銆乧sv 绛夋牸寮忥紝鍗曟枃浠朵笉瓒呰繃10MB
+ </div>
+ </div>
+
+ <!-- 鏂囦欢鍒楄〃 -->
+ <div v-if="selectedKnowledgeBaseId" class="file-list-section">
+ <el-table :data="fileList" v-loading="fileLoading" border>
+ <el-table-column prop="name" label="鏂囦欢鍚�" min-width="200" show-overflow-tooltip />
+ <el-table-column prop="fileType" label="绫诲瀷" width="80">
+ <template #default="{ row }">
+ {{ getFileType(row.name) }}
+ </template>
+ </el-table-column>
+ <el-table-column prop="vectorStatus" label="鍚戦噺鍖栫姸鎬�" width="120">
+ <template #default="{ row }">
+ <el-tag :type="getVectorStatusType(row.vectorStatus)">
+ {{ getVectorStatusText(row.vectorStatus) }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column prop="chunkCount" label="鍒囩墖鏁�" width="80" align="center">
+ <template #default="{ row }">
+ {{ row.chunkCount || 0 }}
+ </template>
+ </el-table-column>
+ <el-table-column prop="createTime" label="涓婁紶鏃堕棿" width="160" />
+ <el-table-column label="鎿嶄綔" width="200" fixed="right">
+ <template #default="{ row }">
+ <el-button type="primary" size="small" link @click="previewFile(row)">棰勮</el-button>
+ <el-button type="primary" size="small" link @click="downloadFile(row)">涓嬭浇</el-button>
+ <el-button
+ v-if="row.vectorStatus === 3"
+ type="warning"
+ size="small"
+ link
+ @click="revectorFile(row)"
+ >閲嶆柊澶勭悊</el-button>
+ <el-button type="danger" size="small" link @click="deleteFile(row)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+
+ <!-- 鏈�夋嫨鐭ヨ瘑搴撴彁绀� -->
+ <el-empty v-if="!selectedKnowledgeBaseId" description="璇峰厛閫夋嫨鐭ヨ瘑搴�" />
+ </div>
+ </el-tab-pane>
+
+ <!-- 鐭ヨ瘑闂瓟Tab -->
+ <el-tab-pane label="鐭ヨ瘑闂瓟" name="chat">
+ <div class="knowledge-chat-container">
+ <!-- 鐭ヨ瘑搴撻�夋嫨 -->
+ <div class="kb-selector">
+ <span class="search_title">閫夋嫨鐭ヨ瘑搴擄細</span>
+ <el-select v-model="chatKnowledgeBaseId" placeholder="璇烽�夋嫨鐭ヨ瘑搴�" style="width: 300px" @change="handleChatKnowledgeBaseChange">
+ <el-option
+ v-for="kb in tableData"
+ :key="kb.id"
+ :label="kb.title"
+ :value="kb.id"
+ />
+ </el-select>
+ <el-button type="primary" plain style="margin-left: 10px" @click="clearChatHistory" :disabled="!chatKnowledgeBaseId">
+ 娓呯┖瀵硅瘽
+ </el-button>
+ </div>
+
+ <!-- 鑱婂ぉ鍖哄煙 -->
+ <div class="chat-container">
+ <div class="message-list" ref="messageListRef">
+ <div v-for="msg in chatMessages" :key="msg.id" :class="['message', msg.role]">
+ <div class="message-role">
+ <el-avatar v-if="msg.role === 'user'" :size="28" style="background: #409eff;">
+ <el-icon><User /></el-icon>
+ </el-avatar>
+ <el-avatar v-else :size="28" style="background: #67c23a;">
+ <el-icon><ChatDotRound /></el-icon>
+ </el-avatar>
+ <span class="role-text">{{ msg.role === 'user' ? '鎴�' : 'AI鍔╂墜' }}</span>
+ </div>
+ <div class="message-content">{{ msg.content }}</div>
+ <div class="message-time">{{ msg.createTime }}</div>
+ </div>
+ <div v-if="streamingContent" class="message assistant">
+ <div class="message-role">
+ <el-avatar :size="28" style="background: #67c23a;">
+ <el-icon><ChatDotRound /></el-icon>
+ </el-avatar>
+ <span class="role-text">AI鍔╂墜</span>
+ </div>
+ <div class="message-content">{{ streamingContent }}</div>
+ </div>
+ </div>
+
+ <!-- 杈撳叆鍖哄煙 -->
+ <div class="input-area">
+ <el-input
+ v-model="questionInput"
+ placeholder="杈撳叆闂锛屽熀浜庣煡璇嗗簱鍐呭鍥炵瓟..."
+ @keyup.enter="sendQuestion"
+ :disabled="!chatKnowledgeBaseId || sending"
+ />
+ <el-button type="primary" @click="sendQuestion" :loading="sending" :disabled="!chatKnowledgeBaseId">
+ 鍙戦��
+ </el-button>
+ </div>
+ </div>
+
+ <!-- 鏈�夋嫨鐭ヨ瘑搴撴彁绀� -->
+ <el-empty v-if="!chatKnowledgeBaseId" description="璇峰厛閫夋嫨鐭ヨ瘑搴撳紑濮嬮棶绛�" />
+ </div>
+ </el-tab-pane>
+ </el-tabs>
</div>
</template>
<script setup>
-import { Search } from "@element-plus/icons-vue";
-import { onMounted, ref, reactive, toRefs } from "vue";
+import { Search, Upload, User, ChatDotRound } 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";
+import FormDialog from '@/components/Dialog/FormDialog.vue';
+import {
+ listKnowledgeBase,
+ delKnowledgeBase,
+ addKnowledgeBase,
+ updateKnowledgeBase,
+ getVectorStatus,
+ reprocessVector,
+ knowledgeChat
+} from "@/api/collaborativeApproval/knowledgeBase.js";
+import { attachmentList, createAttachment, deleteAttachment } from "@/api/basicData/storageAttachment.js";
+import useUserStore from '@/store/modules/user';
+import { userListNoPageByTenantId } from '@/api/system/user.js';
+import { getToken } from '@/utils/auth';
// 琛ㄥ崟楠岃瘉瑙勫垯
const rules = {
@@ -252,6 +423,7 @@
// 鍝嶅簲寮忔暟鎹�
const data = reactive({
+ activeTab: 'manage',
searchForm: {
title: "",
type: "",
@@ -259,7 +431,7 @@
tableLoading: false,
page: {
current: 1,
- size: 100,
+ size: 20,
total: 0,
},
tableData: [],
@@ -268,7 +440,7 @@
title: "",
type: "",
scenario: "",
- efficiency: "medium",
+ efficiency: "",
problem: "",
solution: "",
keyPoints: "",
@@ -279,25 +451,58 @@
dialogTitle: "",
dialogType: "add",
viewDialogVisible: false,
- currentKnowledge: {}
+ currentKnowledge: {},
+ // 鏂囦欢绠$悊鐩稿叧
+ selectedKnowledgeBaseId: null,
+ fileList: [],
+ fileLoading: false,
+ uploadFileList: [],
+ // 鐭ヨ瘑闂瓟鐩稿叧
+ chatKnowledgeBaseId: null,
+ chatMessages: [],
+ questionInput: '',
+ sending: false,
+ streamingContent: '',
+ memoryId: ''
});
-const {
- searchForm,
- tableLoading,
- page,
- tableData,
+const {
+ activeTab,
+ searchForm,
+ tableLoading,
+ page,
+ tableData,
selectedIds,
form,
dialogVisible,
dialogTitle,
dialogType,
viewDialogVisible,
- currentKnowledge
+ currentKnowledge,
+ selectedKnowledgeBaseId,
+ fileList,
+ fileLoading,
+ uploadFileList,
+ chatKnowledgeBaseId,
+ chatMessages,
+ questionInput,
+ sending,
+ streamingContent,
+ memoryId
} = toRefs(data);
// 琛ㄥ崟寮曠敤
const formRef = ref();
+const messageListRef = ref();
+// 鐢ㄦ埛鐩稿叧
+const userStore = useUserStore();
+const userList = ref([]);
+
+// 鏂囦欢涓婁紶鐩稿叧
+const uploadUrl = import.meta.env.VITE_APP_BASE_API + '/common/upload';
+const uploadHeaders = { Authorization: 'Bearer ' + getToken() };
+const acceptTypes = '.docx,.xlsx,.xls,.pdf,.txt,.md,.json,.csv';
+const uploadedBlobs = ref([]);
// 琛ㄦ牸鍒楅厤缃�
const tableColumn = ref([
@@ -311,24 +516,10 @@
prop: "type",
dataType: "tag",
formatData: (params) => {
- const typeMap = {
- contract: "鍚堝悓鐗规壒",
- approval: "瀹℃壒妗堜緥",
- solution: "瑙e喅鏂规",
- experience: "缁忛獙鎬荤粨",
- guide: "鎿嶄綔鎸囧崡"
- };
- return typeMap[params] || params;
+ return getKnowledgeTypeLabel(params);
},
formatType: (params) => {
- const typeMap = {
- contract: "success",
- approval: "warning",
- solution: "primary",
- experience: "info",
- guide: "danger"
- };
- return typeMap[params] || "info";
+ return getKnowledgeTypeTagType(params);
}
},
{
@@ -389,7 +580,7 @@
}
},
{
- name: "鏌ョ湅",
+ name: "璇︽儏",
type: "text",
clickFun: (row) => {
viewKnowledge(row);
@@ -399,110 +590,14 @@
}
]);
-// 妯℃嫙鏁版嵁
-let mockData = [
- {
- id: "1",
- title: "鐗规畩鍚堝悓瀹℃壒娴佺▼浼樺寲鏂规",
- type: "contract",
- scenario: "澶ч鍚堝悓蹇�熷鎵�",
- efficiency: "high",
- problem: "澶ч鍚堝悓瀹℃壒娴佺▼澶嶆潅锛屽鎵规椂闂撮暱锛屽奖鍝嶄笟鍔¤繘灞�",
- solution: "寤虹珛缁胯壊閫氶亾锛屽绗﹀悎鏉′欢鐨勫悎鍚岄噰鐢ㄧ畝鍖栧鎵规祦绋嬶紝鐢遍儴闂ㄨ礋璐d汉鐩存帴瀹℃壒锛屽钩鍧囧鎵规椂闂翠粠3澶╃缉鐭嚦1澶�",
- keyPoints: "缁胯壊閫氶亾鏉′欢,绠�鍖栨祦绋�,瀹℃壒鏉冮檺,鏃堕棿鎺у埗",
- creator: "闄堝織寮�",
- usageCount: 15,
- createTime: "2025-01-15 10:30:00"
- },
- {
- id: "2",
- title: "璺ㄩ儴闂ㄥ崗浣滃鎵圭粡楠屾�荤粨",
- type: "experience",
- scenario: "澶氶儴闂ㄥ崗浣滈」鐩�",
- efficiency: "medium",
- problem: "璺ㄩ儴闂ㄩ」鐩鎵规椂锛屽悇閮ㄩ棬鎰忚涓嶇粺涓�锛屽鎵硅繘搴︾紦鎱�",
- solution: "寤虹珛椤圭洰鍗忚皟鏈哄埗锛屾寚瀹氶」鐩礋璐d汉锛屽畾鏈熷彫寮�鍗忚皟浼氳锛岀粺涓�鍚勬柟鎰忚鍚庡啀杩涜瀹℃壒",
- keyPoints: "椤圭洰鍗忚皟,瀹氭湡浼氳,缁熶竴鎰忚,璐熻矗浜哄埗搴�",
- creator: "鏉庝富绠�",
- usageCount: 8,
- createTime: "2025-01-14 15:20:00"
- },
- {
- id: "3",
- title: "绱ф�ラ噰璐鎵规搷浣滄寚鍗�",
- type: "guide",
- scenario: "绱ф�ラ噰璐渶姹�",
- efficiency: "high",
- problem: "绱ф�ラ噰璐椂瀹℃壒娴佺▼澶嶆潅锛屾棤娉曟弧瓒崇揣鎬ラ渶姹�",
- solution: "鍒跺畾绱ф�ラ噰璐鎵规爣鍑嗭紝鏄庣‘绱ф�ョ▼搴﹀垎绾э紝涓嶅悓绾у埆閲囩敤涓嶅悓瀹℃壒娴佺▼锛岀‘淇濈揣鎬ラ渶姹傚緱鍒板強鏃跺鐞�",
- keyPoints: "绱ф�ュ垎绾�,鏍囧噯鍒跺畾,娴佺▼绠�鍖�,鍙婃椂澶勭悊",
- creator: "鐜嬩笓鍛�",
- usageCount: 12,
- createTime: "2025-01-13 09:15:00"
+// 鐩戝惉瀵硅瘽妗嗘墦寮�锛岃幏鍙栫敤鎴峰垪琛�
+watch(dialogVisible, (newVal) => {
+ if (newVal) {
+ userListNoPageByTenantId().then((res) => {
+ userList.value = res.data || [];
+ });
}
-];
-
-// 鐭ヨ瘑鏍囬妯℃澘
-const titleTemplates = [
- "{type}瀹℃壒娴佺▼浼樺寲鏂规",
- "{scenario}澶勭悊缁忛獙鎬荤粨",
- "{type}鐗规畩鎯呭喌澶勭悊鎸囧崡",
- "{scenario}蹇�熷鎵规柟妗�",
- "{type}鏍囧噯鍖栨搷浣滄祦绋�",
- "{scenario}闂瑙e喅鏂规",
- "{type}鏈�浣冲疄璺垫�荤粨",
- "{scenario}鏁堢巼鎻愬崌鏂规"
-];
-
-// 鐭ヨ瘑绫诲瀷閰嶇疆
-const knowledgeTypes = [
- { type: "contract", label: "鍚堝悓鐗规壒", efficiency: "high" },
- { type: "approval", label: "瀹℃壒妗堜緥", efficiency: "medium" },
- { type: "solution", label: "瑙e喅鏂规", efficiency: "high" },
- { type: "experience", label: "缁忛獙鎬荤粨", efficiency: "medium" },
- { type: "guide", label: "鎿嶄綔鎸囧崡", efficiency: "low" }
-];
-
-// 鍦烘櫙鍒楄〃
-const scenarios = ["澶ч鍚堝悓瀹℃壒", "璺ㄩ儴闂ㄥ崗浣�", "绱ф�ラ噰璐�", "鐗规畩鐢宠", "娴佺▼浼樺寲", "闂澶勭悊", "鏍囧噯鍖栧缓璁�", "鏁堢巼鎻愬崌"];
-
-// 鑷姩鐢熸垚鏂版暟鎹�
-const generateNewData = () => {
- const newId = (mockData.length + 1).toString();
- const now = new Date();
- const randomType = knowledgeTypes[Math.floor(Math.random() * knowledgeTypes.length)];
- const randomScenario = scenarios[Math.floor(Math.random() * scenarios.length)];
-
- // 鐢熸垚闅忔満鏍囬
- let title = titleTemplates[Math.floor(Math.random() * titleTemplates.length)];
- title = title
- .replace('{type}', randomType.label)
- .replace('{scenario}', randomScenario);
-
- const newKnowledge = {
- id: newId,
- title: title,
- type: randomType.type,
- scenario: randomScenario,
- efficiency: randomType.efficiency,
- problem: `鍦�${randomScenario}杩囩▼涓亣鍒扮殑闂鎻忚堪...`,
- solution: `閽堝${randomScenario}鐨勮В鍐虫柟妗堝拰鎿嶄綔姝ラ...`,
- keyPoints: "鍏抽敭瑕佺偣1,鍏抽敭瑕佺偣2,鍏抽敭瑕佺偣3,鍏抽敭瑕佺偣4",
- creator: ["闄堝織寮�", "鍒橀泤濠�", "鐜嬪缓鍥�", "璧典附鍗�"][Math.floor(Math.random() * 4)],
- usageCount: Math.floor(Math.random() * 20) + 1,
- createTime: now.toLocaleString()
- };
-
- // 娣诲姞鍒版暟鎹紑澶�
- mockData.unshift(newKnowledge);
-
- // 淇濇寔鏁版嵁閲忓湪鍚堢悊鑼冨洿鍐咃紙鏈�澶氫繚鐣�30鏉★級
- if (mockData.length > 30) {
- mockData = mockData.slice(0, 30);
- }
-
- console.log(`[${new Date().toLocaleString()}] 鑷姩鐢熸垚鏂扮煡璇�: ${title}`);
-};
+});
// 鐢熷懡鍛ㄦ湡
onMounted(() => {
@@ -513,9 +608,8 @@
// 寮�濮嬭嚜鍔ㄥ埛鏂�
const startAutoRefresh = () => {
setInterval(() => {
- generateNewData();
getList();
- }, 600000); // 10鍒嗛挓鍒锋柊涓�娆� (10 * 60 * 1000 = 600000ms)
+ }, 600000);
};
// 鏌ヨ鏁版嵁
@@ -526,31 +620,30 @@
const getList = () => {
tableLoading.value = true;
-
- setTimeout(() => {
- let filteredData = [...mockData];
-
- if (searchForm.value.title) {
- filteredData = filteredData.filter(item =>
- item.title.toLowerCase().includes(searchForm.value.title.toLowerCase())
- );
- }
-
- if (searchForm.value.type) {
- filteredData = filteredData.filter(item => item.type === searchForm.value.type);
- }
-
- tableData.value = filteredData;
- page.value.total = filteredData.length;
+ listKnowledgeBase({...page.value, ...searchForm.value})
+ .then(res => {
tableLoading.value = false;
- }, 500);
+ page.value.total = res.data.total;
+ const maxPage = Math.ceil(res.data.total / page.value.size) || 1;
+ if (page.value.current > maxPage && maxPage > 0) {
+ page.value.current = 1;
+ return getList();
+ }
+ tableData.value = res.data.records;
+ }).catch(err => {
+ tableLoading.value = false;
+ })
};
// 鍒嗛〉澶勭悊
const pagination = (obj) => {
+ const oldSize = page.value.size;
page.value.current = obj.page;
page.value.size = obj.limit;
- handleQuery();
+ if (oldSize !== obj.limit) {
+ page.value.current = 1;
+ }
+ getList();
};
// 閫夋嫨鍙樺寲澶勭悊
@@ -563,21 +656,21 @@
dialogType.value = type;
if (type === "add") {
dialogTitle.value = "鏂板鐭ヨ瘑";
- // 閲嶇疆琛ㄥ崟
Object.assign(form.value, {
title: "",
type: "",
scenario: "",
- efficiency: "medium",
+ efficiency: "",
problem: "",
solution: "",
keyPoints: "",
- creator: "",
+ creator: userStore.nickName || "",
usageCount: 0
});
} else if (type === "edit" && row) {
dialogTitle.value = "缂栬緫鐭ヨ瘑";
Object.assign(form.value, {
+ id: row.id,
title: row.title,
type: row.type,
scenario: row.scenario,
@@ -612,14 +705,7 @@
// 鑾峰彇绫诲瀷鏍囩鏂囨湰
const getTypeLabel = (type) => {
- const typeMap = {
- contract: "鍚堝悓鐗规壒",
- approval: "瀹℃壒妗堜緥",
- solution: "瑙e喅鏂规",
- experience: "缁忛獙鎬荤粨",
- guide: "鎿嶄綔鎸囧崡"
- };
- return typeMap[type] || type;
+ return getKnowledgeTypeLabel(type);
};
// 鑾峰彇鏁堢巼鏍囩绫诲瀷
@@ -665,16 +751,15 @@
// 澶嶅埗鐭ヨ瘑
const copyKnowledge = () => {
const knowledgeText = `
-鐭ヨ瘑鏍囬锛�${currentKnowledge.value.title}
-鐭ヨ瘑绫诲瀷锛�${getTypeLabel(currentKnowledge.value.type)}
-閫傜敤鍦烘櫙锛�${currentKnowledge.value.scenario}
-闂鎻忚堪锛�${currentKnowledge.value.problem}
-瑙e喅鏂规锛�${currentKnowledge.value.solution}
-鍏抽敭瑕佺偣锛�${currentKnowledge.value.keyPoints}
-鍒涘缓浜猴細${currentKnowledge.value.creator}
+ 鐭ヨ瘑鏍囬锛�${currentKnowledge.value.title}
+ 鐭ヨ瘑绫诲瀷锛�${getTypeLabel(currentKnowledge.value.type)}
+ 閫傜敤鍦烘櫙锛�${currentKnowledge.value.scenario}
+ 闂鎻忚堪锛�${currentKnowledge.value.problem}
+ 瑙e喅鏂规锛�${currentKnowledge.value.solution}
+ 鍏抽敭瑕佺偣锛�${currentKnowledge.value.keyPoints}
+ 鍒涘缓浜猴細${currentKnowledge.value.creator}
`.trim();
-
- // 澶嶅埗鍒板壀璐存澘
+
navigator.clipboard.writeText(knowledgeText).then(() => {
ElMessage.success("鐭ヨ瘑鍐呭宸插鍒跺埌鍓创鏉�");
}).catch(() => {
@@ -682,62 +767,62 @@
});
};
-// 鏀惰棌鐭ヨ瘑
-const markAsFavorite = () => {
- // 澧炲姞浣跨敤娆℃暟
- const index = mockData.findIndex(item => item.id === currentKnowledge.value.id);
- if (index !== -1) {
- mockData[index].usageCount += 1;
- currentKnowledge.value.usageCount += 1;
+// 鍏抽棴鐭ヨ瘑琛ㄥ崟瀵硅瘽妗�
+const closeKnowledgeDialog = () => {
+ Object.assign(form.value, {
+ id: undefined,
+ title: "",
+ type: "",
+ scenario: "",
+ efficiency: "",
+ problem: "",
+ solution: "",
+ keyPoints: "",
+ creator: userStore.nickName || "",
+ usageCount: 0
+ });
+ if (formRef.value) {
+ formRef.value.clearValidate();
}
-
- ElMessage.success("宸叉敹钘忥紝浣跨敤娆℃暟+1");
+ dialogVisible.value = false;
+};
+
+// 鍏抽棴鏌ョ湅璇︽儏瀵硅瘽妗�
+const closeViewDialog = () => {
+ viewDialogVisible.value = false;
+};
+
+// 澶勭悊鏌ョ湅璇︽儏瀵硅瘽妗嗙‘璁�
+const handleViewDialogConfirm = () => {
+ copyKnowledge();
+ closeViewDialog();
};
// 鎻愪氦鐭ヨ瘑琛ㄥ崟
const submitForm = async () => {
try {
await formRef.value.validate();
-
if (dialogType.value === "add") {
- // 鏂板鐭ヨ瘑
- const newKnowledge = {
- id: (mockData.length + 1).toString(),
- title: form.value.title,
- type: form.value.type,
- scenario: form.value.scenario,
- efficiency: form.value.efficiency,
- problem: form.value.problem,
- solution: form.value.solution,
- keyPoints: form.value.keyPoints,
- creator: form.value.creator,
- usageCount: form.value.usageCount,
- createTime: new Date().toLocaleString()
- };
-
- mockData.unshift(newKnowledge);
- ElMessage.success("鐭ヨ瘑鍒涘缓鎴愬姛");
+ addKnowledgeBase({...form.value}).then(res => {
+ if(res.code == 200){
+ ElMessage.success("娣诲姞鎴愬姛");
+ closeKnowledgeDialog();
+ getList();
+ }
+ }).catch(err => {
+ ElMessage.error(err.msg);
+ })
} else {
- // 缂栬緫鐭ヨ瘑
- const index = mockData.findIndex(item => item.id === selectedIds.value[0]);
- if (index !== -1) {
- Object.assign(mockData[index], {
- title: form.value.title,
- type: form.value.type,
- scenario: form.value.scenario,
- efficiency: form.value.efficiency,
- problem: form.value.problem,
- solution: form.value.solution,
- keyPoints: form.value.keyPoints,
- creator: form.value.creator,
- usageCount: form.value.usageCount
- });
- ElMessage.success("鐭ヨ瘑鏇存柊鎴愬姛");
- }
+ updateKnowledgeBase({...form.value}).then(res => {
+ if(res.code == 200){
+ ElMessage.success("鏇存柊鎴愬姛");
+ closeKnowledgeDialog();
+ getList();
+ }
+ }).catch(err => {
+ ElMessage.error(err.msg);
+ })
}
-
- dialogVisible.value = false;
- getList();
} catch (error) {
console.error("琛ㄥ崟楠岃瘉澶辫触:", error);
}
@@ -749,42 +834,387 @@
ElMessage.warning("璇烽�夋嫨瑕佸垹闄ょ殑鐭ヨ瘑");
return;
}
-
+
ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "鍒犻櫎", {
confirmButtonText: "纭",
cancelButtonText: "鍙栨秷",
type: "warning",
}).then(() => {
- // 浠巑ockData涓垹闄ら�変腑鐨勯」
- selectedIds.value.forEach(id => {
- const index = mockData.findIndex(item => item.id === id);
- if (index !== -1) {
- mockData.splice(index, 1);
+ delKnowledgeBase(selectedIds.value).then(res => {
+ if(res.code == 200){
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ selectedIds.value = [];
+ getList();
}
+ })
+ }).catch(() => {});
+};
+
+// 瀵煎嚭
+const { proxy } = getCurrentInstance()
+const { knowledge_type } = proxy.useDict("knowledge_type")
+
+const knowledgeTypeOptions = computed(() => knowledge_type?.value || [])
+const getKnowledgeTypeLabel = (val) => {
+ const item = knowledgeTypeOptions.value.find(i => String(i.value) === String(val))
+ return item ? item.label : val
+}
+const getKnowledgeTypeTagType = (val) => {
+ const item = knowledgeTypeOptions.value.find(i => String(i.value) === String(val))
+ return item?.elTagType || "info"
+}
+const handleExport = () => {
+ proxy.download('/knowledgeBase/export', { ...searchForm.value }, '鐭ヨ瘑搴�.xlsx')
+}
+
+// ========== 鏂囦欢绠$悊鐩稿叧鏂规硶 ==========
+
+// 鐭ヨ瘑搴撻�夋嫨鍙樺寲
+const handleKnowledgeBaseChange = () => {
+ refreshFileList();
+};
+
+// 鍒锋柊鏂囦欢鍒楄〃
+const refreshFileList = async () => {
+ if (!selectedKnowledgeBaseId.value) return;
+
+ fileLoading.value = true;
+ try {
+ // 鑾峰彇闄勪欢鍒楄〃
+ const attachmentRes = await attachmentList({
+ recordType: 'knowledge_base',
+ recordId: selectedKnowledgeBaseId.value,
+ application: 'rag_file'
});
-
- ElMessage.success("鍒犻櫎鎴愬姛");
- selectedIds.value = [];
- getList();
- }).catch(() => {
- // 鐢ㄦ埛鍙栨秷
+
+ // 鑾峰彇鍚戦噺鍖栫姸鎬�
+ const vectorRes = await getVectorStatus(selectedKnowledgeBaseId.value);
+
+ // 鍚堝苟鏁版嵁
+ const vectorMap = {};
+ (vectorRes.data || []).forEach(v => {
+ vectorMap[v.storageBlobId] = v;
+ });
+
+ fileList.value = (attachmentRes.data || []).map(file => ({
+ ...file,
+ vectorStatus: vectorMap[file.storageBlobId]?.vectorStatus ?? 0,
+ chunkCount: vectorMap[file.storageBlobId]?.chunkCount ?? 0,
+ vectorId: vectorMap[file.storageBlobId]?.id
+ }));
+ } catch (error) {
+ console.error('鑾峰彇鏂囦欢鍒楄〃澶辫触:', error);
+ ElMessage.error('鑾峰彇鏂囦欢鍒楄〃澶辫触');
+ } finally {
+ fileLoading.value = false;
+ }
+};
+
+// 涓婁紶鍓嶆牎楠�
+const beforeUpload = (file) => {
+ const maxSize = 10 * 1024 * 1024;
+ if (file.size > maxSize) {
+ ElMessage.error('鏂囦欢澶у皬涓嶈兘瓒呰繃10MB');
+ return false;
+ }
+ return true;
+};
+
+// 涓婁紶鎴愬姛澶勭悊
+const handleUploadSuccess = async (response, file) => {
+ if (response.code === 200) {
+ uploadedBlobs.value.push(...response.data);
+ await saveAttachment();
+ ElMessage.success('鏂囦欢涓婁紶鎴愬姛锛屾鍦ㄥ鐞嗗悜閲忓寲...');
+ uploadedBlobs.value = [];
+ refreshFileList();
+ } else {
+ ElMessage.error(response.msg || '涓婁紶澶辫触');
+ }
+};
+
+// 淇濆瓨闄勪欢鍏宠仈鍒扮煡璇嗗簱
+const saveAttachment = async () => {
+ await createAttachment({
+ recordType: 'knowledge_base',
+ recordId: selectedKnowledgeBaseId.value,
+ application: 'rag_file',
+ storageBlobDTOs: uploadedBlobs.value.map(b => b.id)
+ });
+};
+
+// 鑾峰彇鏂囦欢绫诲瀷
+const getFileType = (name) => {
+ return name?.split('.').pop()?.toLowerCase() || '';
+};
+
+// 鍚戦噺鍖栫姸鎬佹枃鏈�
+const getVectorStatusText = (status) => {
+ const statusMap = { 0: '寰呭鐞�', 1: '澶勭悊涓�', 2: '宸插畬鎴�', 3: '澶辫触' };
+ return statusMap[status] || '鏈煡';
+};
+
+// 鍚戦噺鍖栫姸鎬佹爣绛剧被鍨�
+const getVectorStatusType = (status) => {
+ const typeMap = { 0: 'info', 1: 'warning', 2: 'success', 3: 'danger' };
+ return typeMap[status] || 'info';
+};
+
+// 棰勮鏂囦欢
+const previewFile = (row) => {
+ if (row.previewURL) {
+ window.open(row.previewURL, '_blank');
+ } else {
+ ElMessage.warning('鏆傛棤棰勮閾炬帴');
+ }
+};
+
+// 涓嬭浇鏂囦欢
+const downloadFile = (row) => {
+ if (row.downloadURL) {
+ window.open(row.downloadURL, '_blank');
+ } else {
+ ElMessage.warning('鏆傛棤涓嬭浇閾炬帴');
+ }
+};
+
+// 鍒犻櫎鏂囦欢
+const deleteFile = async (row) => {
+ try {
+ await ElMessageBox.confirm('纭畾鍒犻櫎璇ユ枃浠跺悧锛熷垹闄ゅ悗灏嗗悓姝ュ垹闄ゅ悜閲忓簱涓殑鐩稿叧鏁版嵁', '鍒犻櫎纭', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ });
+
+ await deleteAttachment([row.id]);
+ ElMessage.success('鍒犻櫎鎴愬姛');
+ refreshFileList();
+ } catch (error) {
+ if (error !== 'cancel') {
+ ElMessage.error('鍒犻櫎澶辫触');
+ }
+ }
+};
+
+// 閲嶆柊鍚戦噺鍖栨枃浠�
+const revectorFile = async (row) => {
+ try {
+ await reprocessVector(row.vectorId);
+ ElMessage.success('宸查噸鏂版彁浜ゅ悜閲忓寲浠诲姟');
+ refreshFileList();
+ } catch (error) {
+ ElMessage.error('閲嶆柊澶勭悊澶辫触');
+ }
+};
+
+// ========== 鐭ヨ瘑闂瓟鐩稿叧鏂规硶 ==========
+
+// 鐭ヨ瘑搴撻�夋嫨鍙樺寲
+const handleChatKnowledgeBaseChange = () => {
+ // 閲嶇疆浼氳瘽
+ memoryId.value = 'kb-chat-' + Date.now();
+ chatMessages.value = [];
+ questionInput.value = '';
+ streamingContent.value = '';
+};
+
+// 娓呯┖瀵硅瘽鍘嗗彶
+const clearChatHistory = () => {
+ memoryId.value = 'kb-chat-' + Date.now();
+ chatMessages.value = [];
+ streamingContent.value = '';
+ ElMessage.success('瀵硅瘽宸叉竻绌�');
+};
+
+// 鍙戦�侀棶棰�
+const sendQuestion = async () => {
+ if (!questionInput.value.trim() || !chatKnowledgeBaseId.value || sending.value) return;
+
+ sending.value = true;
+ streamingContent.value = '';
+
+ // 娣诲姞鐢ㄦ埛娑堟伅
+ const userMsg = {
+ id: Date.now(),
+ role: 'user',
+ content: questionInput.value,
+ createTime: new Date().toLocaleString()
+ };
+ chatMessages.value.push(userMsg);
+
+ const currentQuestion = questionInput.value;
+ questionInput.value = '';
+
+ try {
+ const response = await knowledgeChat({
+ knowledgeBaseId: chatKnowledgeBaseId.value,
+ memoryId: memoryId.value,
+ question: currentQuestion
+ });
+
+ if (!response.ok) {
+ throw new Error('璇锋眰澶辫触');
+ }
+
+ // 娴佸紡璇诲彇鍝嶅簲
+ const reader = response.body.getReader();
+ const decoder = new TextDecoder();
+ let accumulatedContent = '';
+
+ while (true) {
+ const { done, value } = await reader.read();
+ if (done) break;
+
+ accumulatedContent += decoder.decode(value, { stream: true });
+ streamingContent.value = accumulatedContent;
+
+ // 婊氬姩鍒板簳閮�
+ scrollToBottom();
+ }
+
+ // 娣诲姞AI鍥炲娑堟伅
+ if (accumulatedContent) {
+ chatMessages.value.push({
+ id: Date.now(),
+ role: 'assistant',
+ content: accumulatedContent,
+ createTime: new Date().toLocaleString()
+ });
+ }
+
+ streamingContent.value = '';
+ } catch (error) {
+ console.error('闂瓟璇锋眰澶辫触:', error);
+ ElMessage.error('闂瓟璇锋眰澶辫触锛岃绋嶅悗閲嶈瘯');
+ // 绉婚櫎鐢ㄦ埛娑堟伅
+ chatMessages.value.pop();
+ } finally {
+ sending.value = false;
+ }
+};
+
+// 婊氬姩鍒板簳閮�
+const scrollToBottom = () => {
+ nextTick(() => {
+ if (messageListRef.value) {
+ messageListRef.value.scrollTop = messageListRef.value.scrollHeight;
+ }
});
};
</script>
<style scoped>
-.auto-refresh-info {
- margin-bottom: 15px;
+.knowledge-tabs {
+ margin-bottom: 20px;
}
-.auto-refresh-info .el-alert {
+/* 鏂囦欢绠$悊鏍峰紡 */
+.file-manage-container {
+ padding: 20px;
+}
+
+.upload-section {
+ margin-bottom: 20px;
+ padding: 20px;
+ background: #f8f9fa;
border-radius: 8px;
}
-.dialog-footer {
- text-align: right;
+.upload-tip {
+ margin-top: 10px;
+ color: #909399;
+ font-size: 12px;
}
+.file-list-section {
+ margin-top: 20px;
+}
+
+/* 鐭ヨ瘑闂瓟鏍峰紡 */
+.knowledge-chat-container {
+ height: calc(100vh - 200px);
+ display: flex;
+ flex-direction: column;
+}
+
+.kb-selector {
+ padding: 10px 0;
+ border-bottom: 1px solid #eee;
+}
+
+.chat-container {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ min-height: 400px;
+}
+
+.message-list {
+ flex: 1;
+ overflow-y: auto;
+ padding: 20px;
+ background: #f5f7fa;
+ border-radius: 8px;
+ margin: 10px 0;
+}
+
+.message {
+ margin-bottom: 20px;
+ padding: 16px;
+ border-radius: 12px;
+ background: #fff;
+ box-shadow: 0 1px 2px rgba(0,0,0,0.1);
+}
+
+.message.user {
+ background: #e6f7ff;
+ border-left: 3px solid #409eff;
+}
+
+.message.assistant {
+ background: #f0f9eb;
+ border-left: 3px solid #67c23a;
+}
+
+.message-role {
+ display: flex;
+ align-items: center;
+ margin-bottom: 8px;
+}
+
+.role-text {
+ margin-left: 8px;
+ font-weight: 500;
+ color: #606266;
+}
+
+.message-content {
+ line-height: 1.6;
+ color: #303133;
+ white-space: pre-wrap;
+}
+
+.message-time {
+ margin-top: 8px;
+ font-size: 12px;
+ color: #909399;
+}
+
+.input-area {
+ padding: 15px;
+ display: flex;
+ gap: 10px;
+ background: #fff;
+ border-radius: 8px;
+ border: 1px solid #eee;
+}
+
+.input-area .el-input {
+ flex: 1;
+}
+
+/* 鐭ヨ瘑璇︽儏鏍峰紡 */
.knowledge-detail {
padding: 20px 0;
}
@@ -845,4 +1275,4 @@
font-size: 14px;
color: #909399;
}
-</style>
+</style>
\ No newline at end of file
--
Gitblit v1.9.3