知识库上传、分段
This commit is contained in:
@@ -25,64 +25,43 @@
|
||||
<!-- 分段列表 -->
|
||||
<div class="segment-list" v-loading="loading">
|
||||
<div
|
||||
v-for="(segment, index) in segments"
|
||||
v-for="segment in segments"
|
||||
:key="segment.id"
|
||||
class="segment-item"
|
||||
>
|
||||
<div class="segment-header">
|
||||
<span class="segment-index">分段 {{ index + 1 }}</span>
|
||||
<span class="segment-index">分段 {{ segment.position }}</span>
|
||||
<span class="segment-info">
|
||||
{{ segment.word_count }} 字 · {{ segment.tokens }} tokens
|
||||
</span>
|
||||
<div class="segment-actions">
|
||||
<el-button
|
||||
size="small"
|
||||
@click="editSegment(segment)"
|
||||
:icon="Edit"
|
||||
<div class="segment-status">
|
||||
<el-tag
|
||||
:type="segment.enabled ? 'success' : 'info'"
|
||||
size="small"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
type="danger"
|
||||
@click="deleteSegment(segment)"
|
||||
:icon="Delete"
|
||||
{{ segment.enabled ? '已启用' : '已禁用' }}
|
||||
</el-tag>
|
||||
<el-tag
|
||||
:type="getStatusType(segment.status)"
|
||||
size="small"
|
||||
style="margin-left: 8px;"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
{{ getStatusText(segment.status) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分段内容显示/编辑 -->
|
||||
<!-- 分段内容显示(只读) -->
|
||||
<div class="segment-content">
|
||||
<div v-if="editingSegmentId === segment.id" class="segment-editor">
|
||||
<el-input
|
||||
v-model="editingContent"
|
||||
type="textarea"
|
||||
:rows="6"
|
||||
placeholder="编辑分段内容"
|
||||
/>
|
||||
<div class="editor-actions">
|
||||
<el-button size="small" @click="cancelEdit">取消</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="saveSegment(segment)"
|
||||
:loading="saving"
|
||||
>
|
||||
保存
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="segment-text">
|
||||
<div class="segment-text">
|
||||
{{ segment.content }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 关键词标签 -->
|
||||
<div class="segment-keywords" v-if="segment.parentKeywords?.length">
|
||||
<div class="segment-keywords" v-if="segment.keywords?.length">
|
||||
<el-tag
|
||||
v-for="keyword in segment.parentKeywords"
|
||||
v-for="keyword in segment.keywords"
|
||||
:key="keyword"
|
||||
size="small"
|
||||
style="margin-right: 8px;"
|
||||
@@ -90,6 +69,22 @@
|
||||
{{ keyword }}
|
||||
</el-tag>
|
||||
</div>
|
||||
|
||||
<!-- 分段元数据 -->
|
||||
<div class="segment-meta">
|
||||
<span class="meta-item">
|
||||
<el-icon><Clock /></el-icon>
|
||||
创建时间: {{ formatTimestamp(segment.created_at) }}
|
||||
</span>
|
||||
<span class="meta-item" v-if="segment.completed_at">
|
||||
<el-icon><Check /></el-icon>
|
||||
完成时间: {{ formatTimestamp(segment.completed_at) }}
|
||||
</span>
|
||||
<span class="meta-item">
|
||||
<el-icon><View /></el-icon>
|
||||
命中次数: {{ segment.hit_count }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
@@ -102,64 +97,21 @@
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="visible = false">关闭</el-button>
|
||||
<el-button type="primary" @click="showAddSegment" :icon="Plus">
|
||||
添加分段
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="loadSegments"
|
||||
>
|
||||
刷新
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 添加分段对话框 -->
|
||||
<el-dialog
|
||||
v-model="addDialogVisible"
|
||||
title="添加新分段"
|
||||
width="600px"
|
||||
append-to-body
|
||||
>
|
||||
<el-form label-position="top">
|
||||
<el-form-item label="选择父分段(可选)">
|
||||
<el-select
|
||||
v-model="selectedParentSegment"
|
||||
placeholder="选择一个分段作为父级"
|
||||
style="width: 100%"
|
||||
clearable
|
||||
>
|
||||
<el-option
|
||||
v-for="(seg, idx) in segments"
|
||||
:key="seg.id"
|
||||
:label="`分段 ${idx + 1}: ${seg.content.substring(0, 30)}...`"
|
||||
:value="seg.parentSegmentId"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="分段内容" required>
|
||||
<el-input
|
||||
v-model="newSegmentContent"
|
||||
type="textarea"
|
||||
:rows="8"
|
||||
placeholder="请输入分段内容"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="addDialogVisible = false">取消</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="createNewSegment"
|
||||
:loading="creating"
|
||||
>
|
||||
创建
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { Edit, Delete, Plus } from '@element-plus/icons-vue';
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { Clock, Check, View } from '@element-plus/icons-vue';
|
||||
import { documentSegmentApi } from '../../../../../apis/ai';
|
||||
|
||||
defineOptions({
|
||||
@@ -186,19 +138,8 @@ const visible = computed({
|
||||
|
||||
// 数据状态
|
||||
const loading = ref(false);
|
||||
const saving = ref(false);
|
||||
const creating = ref(false);
|
||||
const segments = ref<any[]>([]);
|
||||
|
||||
// 编辑状态
|
||||
const editingSegmentId = ref<string | null>(null);
|
||||
const editingContent = ref('');
|
||||
|
||||
// 添加分段状态
|
||||
const addDialogVisible = ref(false);
|
||||
const selectedParentSegment = ref<string>('');
|
||||
const newSegmentContent = ref('');
|
||||
|
||||
// 统计信息
|
||||
const totalSegments = computed(() => segments.value.length);
|
||||
const totalWords = computed(() =>
|
||||
@@ -208,11 +149,9 @@ const totalTokens = computed(() =>
|
||||
segments.value.reduce((sum, seg) => sum + (seg.tokens || 0), 0)
|
||||
);
|
||||
|
||||
// 监听对话框显示状态,加载数据
|
||||
watch(visible, async (val) => {
|
||||
if (val) {
|
||||
await loadSegments();
|
||||
}
|
||||
// 组件挂载时自动加载数据(v-if 确保每次打开都会重新挂载)
|
||||
onMounted(() => {
|
||||
loadSegments();
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -222,13 +161,19 @@ async function loadSegments() {
|
||||
try {
|
||||
loading.value = true;
|
||||
|
||||
// 使用辅助方法获取所有分段和子块
|
||||
const allChunks = await documentSegmentApi.getAllSegmentsWithChunks(
|
||||
// 直接获取父级分段列表(Dify的分段本身就是主要内容)
|
||||
const result = await documentSegmentApi.getDocumentSegments(
|
||||
props.datasetId,
|
||||
props.documentId
|
||||
);
|
||||
|
||||
segments.value = allChunks;
|
||||
if (result.success && result.dataList) {
|
||||
// 直接使用父级分段数据(后端已经从Dify响应中提取了data数组)
|
||||
segments.value = result.dataList || [];
|
||||
} else {
|
||||
segments.value = [];
|
||||
ElMessage.error(result.message || '加载分段失败');
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('加载分段失败:', error);
|
||||
ElMessage.error(error.message || '加载分段失败');
|
||||
@@ -238,147 +183,47 @@ async function loadSegments() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑分段
|
||||
* 获取状态类型
|
||||
*/
|
||||
function editSegment(segment: any) {
|
||||
editingSegmentId.value = segment.id;
|
||||
editingContent.value = segment.content;
|
||||
function getStatusType(status: string): 'success' | 'info' | 'warning' | 'danger' {
|
||||
const typeMap: Record<string, 'success' | 'info' | 'warning' | 'danger'> = {
|
||||
'completed': 'success',
|
||||
'indexing': 'warning',
|
||||
'error': 'danger',
|
||||
'paused': 'info'
|
||||
};
|
||||
return typeMap[status] || 'info';
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消编辑
|
||||
* 获取状态文本
|
||||
*/
|
||||
function cancelEdit() {
|
||||
editingSegmentId.value = null;
|
||||
editingContent.value = '';
|
||||
function getStatusText(status: string): string {
|
||||
const textMap: Record<string, string> = {
|
||||
'completed': '已完成',
|
||||
'indexing': '索引中',
|
||||
'error': '错误',
|
||||
'paused': '已暂停'
|
||||
};
|
||||
return textMap[status] || status;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存分段
|
||||
* 格式化时间戳
|
||||
*/
|
||||
async function saveSegment(segment: any) {
|
||||
if (!editingContent.value.trim()) {
|
||||
ElMessage.warning('分段内容不能为空');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
saving.value = true;
|
||||
|
||||
const result = await documentSegmentApi.updateChildChunk(
|
||||
props.datasetId,
|
||||
props.documentId,
|
||||
segment.segment_id,
|
||||
segment.id,
|
||||
editingContent.value
|
||||
);
|
||||
|
||||
if (result.success && result.data?.data) {
|
||||
// 更新本地数据
|
||||
const index = segments.value.findIndex(s => s.id === segment.id);
|
||||
if (index !== -1) {
|
||||
segments.value[index] = {
|
||||
...segments.value[index],
|
||||
...result.data.data
|
||||
};
|
||||
}
|
||||
|
||||
ElMessage.success('保存成功');
|
||||
cancelEdit();
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('保存分段失败:', error);
|
||||
ElMessage.error(error.message || '保存失败');
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
function formatTimestamp(timestamp: number): string {
|
||||
if (!timestamp) return '-';
|
||||
const date = new Date(timestamp * 1000);
|
||||
return date.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除分段
|
||||
*/
|
||||
async function deleteSegment(segment: any) {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
'确定要删除这个分段吗?此操作不可恢复。',
|
||||
'警告',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
);
|
||||
|
||||
const result = await documentSegmentApi.deleteChildChunk(
|
||||
props.datasetId,
|
||||
props.documentId,
|
||||
segment.segment_id,
|
||||
segment.id
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
segments.value = segments.value.filter(s => s.id !== segment.id);
|
||||
ElMessage.success('删除成功');
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('删除分段失败:', error);
|
||||
ElMessage.error(error.message || '删除失败');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示添加分段对话框
|
||||
*/
|
||||
function showAddSegment() {
|
||||
// 如果有分段,默认选择第一个作为父级
|
||||
if (segments.value.length > 0) {
|
||||
selectedParentSegment.value = segments.value[0].parentSegmentId;
|
||||
}
|
||||
addDialogVisible.value = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建新分段
|
||||
*/
|
||||
async function createNewSegment() {
|
||||
if (!newSegmentContent.value.trim()) {
|
||||
ElMessage.warning('请输入分段内容');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!selectedParentSegment.value) {
|
||||
ElMessage.warning('请选择一个父分段');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
creating.value = true;
|
||||
|
||||
const result = await documentSegmentApi.createChildChunk(
|
||||
props.datasetId,
|
||||
props.documentId,
|
||||
selectedParentSegment.value,
|
||||
newSegmentContent.value
|
||||
);
|
||||
|
||||
if (result.success && result.data?.data) {
|
||||
ElMessage.success('创建成功');
|
||||
addDialogVisible.value = false;
|
||||
newSegmentContent.value = '';
|
||||
selectedParentSegment.value = '';
|
||||
|
||||
// 重新加载列表
|
||||
await loadSegments();
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('创建分段失败:', error);
|
||||
ElMessage.error(error.message || '创建失败');
|
||||
} finally {
|
||||
creating.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -510,42 +355,9 @@ async function createNewSegment() {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.segment-actions {
|
||||
.segment-status {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
|
||||
:deep(.el-button) {
|
||||
border-radius: 8px;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.01em;
|
||||
padding: 5px 12px;
|
||||
|
||||
&.el-button--small {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
&.el-button--default {
|
||||
background: #F3F3F5;
|
||||
border-color: transparent;
|
||||
color: #0A0A0A;
|
||||
|
||||
&:hover {
|
||||
background: #E5E5E7;
|
||||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
&.el-button--danger {
|
||||
background: #FEF2F2;
|
||||
border-color: transparent;
|
||||
color: #DC2626;
|
||||
|
||||
&:hover {
|
||||
background: #FEE2E2;
|
||||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -562,63 +374,6 @@ async function createNewSegment() {
|
||||
word-break: break-word;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
.segment-editor {
|
||||
:deep(.el-textarea) {
|
||||
.el-textarea__inner {
|
||||
background: #F3F3F5;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: #0A0A0A;
|
||||
letter-spacing: -0.01em;
|
||||
|
||||
&:hover {
|
||||
border-color: rgba(231, 0, 11, 0.2);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color: #E7000B;
|
||||
background: #FFFFFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.editor-actions {
|
||||
margin-top: 12px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
justify-content: flex-end;
|
||||
|
||||
:deep(.el-button) {
|
||||
border-radius: 8px;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.01em;
|
||||
|
||||
&.el-button--default {
|
||||
background: #F3F3F5;
|
||||
border-color: transparent;
|
||||
color: #0A0A0A;
|
||||
|
||||
&:hover {
|
||||
background: #E5E5E7;
|
||||
}
|
||||
}
|
||||
|
||||
&.el-button--primary {
|
||||
background: #E7000B;
|
||||
border-color: #E7000B;
|
||||
|
||||
&:hover {
|
||||
background: #C90009;
|
||||
border-color: #C90009;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.segment-keywords {
|
||||
@@ -639,6 +394,29 @@ async function createNewSegment() {
|
||||
}
|
||||
}
|
||||
|
||||
.segment-meta {
|
||||
margin-top: 12px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.06);
|
||||
|
||||
.meta-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 13px;
|
||||
color: #667085;
|
||||
letter-spacing: -0.01em;
|
||||
|
||||
.el-icon {
|
||||
font-size: 14px;
|
||||
color: #9CA3AF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 80px 20px;
|
||||
|
||||
@@ -259,7 +259,7 @@
|
||||
<el-button @click="handleCancel">
|
||||
取消
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -362,7 +362,9 @@ watch(() => props.knowledge, (newVal) => {
|
||||
watch(() => formData.rerankingEnable, () => {
|
||||
if (formRef.value) {
|
||||
// 触发 rerankModel 字段的验证
|
||||
formRef.value.validateField('rerankModel', () => {});
|
||||
formRef.value.validateField('rerankModel', () => {
|
||||
console.log('rerankModel 字段验证通过');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -54,24 +54,28 @@
|
||||
<el-icon color="#409EFF"><Document /></el-icon>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-value">{{ knowledge.documentCount || 0 }}</div>
|
||||
<div class="stat-label">文档数量</div>
|
||||
<div class="stat-value">{{ documents.length }}</div>
|
||||
<div class="stat-label">总文档数</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon">
|
||||
<el-icon color="#67C23A"><Files /></el-icon>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon">
|
||||
<el-icon color="#E6A23C"><Paperclip /></el-icon>
|
||||
<el-icon color="#67C23A"><Check /></el-icon>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-value">{{ documents.length }}</div>
|
||||
<div class="stat-label">上传文件</div>
|
||||
<div class="stat-value">{{ enabledDocumentsCount }}</div>
|
||||
<div class="stat-label">启用的文档</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="stat-card">
|
||||
<div class="stat-icon">
|
||||
<el-icon color="#E6A23C"><Files /></el-icon>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-value">{{ knowledge.totalChunks || 0 }}</div>
|
||||
<div class="stat-label">总分段数</div>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
<!-- 文档列表 -->
|
||||
@@ -104,7 +108,7 @@
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="fileType" label="类型" width="100">
|
||||
<el-table-column prop="fileType" label="文件类型" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small">{{ row.fileType || 'unknown' }}</el-tag>
|
||||
</template>
|
||||
@@ -114,24 +118,40 @@
|
||||
{{ formatFileSize(row.fileSize) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="状态" width="100">
|
||||
<el-table-column label="状态" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag
|
||||
:type="row.status === 2 ? 'success' : row.status === 1 ? 'warning' : 'danger'"
|
||||
size="small"
|
||||
>
|
||||
{{ getStatusText(row.status) }}
|
||||
</el-tag>
|
||||
<div style="display: flex; gap: 8px;">
|
||||
<el-tag
|
||||
:type="row.enabled ? 'success' : 'info'"
|
||||
size="small"
|
||||
>
|
||||
{{ row.enabled ? '已启用' : '已禁用' }}
|
||||
</el-tag>
|
||||
<el-tag
|
||||
v-if="row.displayStatus === 'indexing'"
|
||||
type="warning"
|
||||
size="small"
|
||||
>
|
||||
索引中
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="chunkCount" label="分段数" width="100" />
|
||||
<el-table-column prop="chunkCount" label="字数" width="100" />
|
||||
<el-table-column prop="createTime" label="上传时间" width="180">
|
||||
<template #default="{ row }">
|
||||
{{ formatDate(row.createTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<el-table-column label="操作" width="280" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-switch
|
||||
:model-value="row.enabled"
|
||||
:active-text="row.enabled ? '已启用' : '已禁用'"
|
||||
:loading="row._switching"
|
||||
@change="handleToggleEnabled(row, $event)"
|
||||
style="--el-switch-on-color: #67C23A; margin-right: 12px;"
|
||||
/>
|
||||
<el-button link type="primary" @click="handleViewSegments(row)">
|
||||
查看分段
|
||||
</el-button>
|
||||
@@ -199,18 +219,16 @@ import type { UploadUserFile, UploadInstance } from 'element-plus';
|
||||
import {
|
||||
ArrowLeft,
|
||||
Upload,
|
||||
Refresh,
|
||||
Edit,
|
||||
Document,
|
||||
Clock,
|
||||
Link,
|
||||
Files,
|
||||
Paperclip,
|
||||
Search,
|
||||
UploadFilled
|
||||
UploadFilled,
|
||||
Check
|
||||
} from '@element-plus/icons-vue';
|
||||
import type { AiKnowledge, AiUploadFile } from '@/types/ai';
|
||||
import { fileUploadApi, knowledgeApi } from '@/apis/ai';
|
||||
import { fileUploadApi } from '@/apis/ai';
|
||||
import { DocumentSegmentDialog } from './';
|
||||
|
||||
defineOptions({
|
||||
@@ -235,6 +253,11 @@ const searchQuery = ref('');
|
||||
const uploadDialogVisible = ref(false);
|
||||
const segmentDialogVisible = ref(false);
|
||||
const selectedDocument = ref<AiUploadFile | null>(null);
|
||||
|
||||
// 计算启用的文档数量(从 Dify 返回的 enabled 字段)
|
||||
const enabledDocumentsCount = computed(() => {
|
||||
return documents.value.filter(doc => doc.enabled === true).length;
|
||||
});
|
||||
const uploading = ref(false);
|
||||
|
||||
// 上传相关
|
||||
@@ -257,12 +280,13 @@ watch(() => props.knowledge, (newVal) => {
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
// 加载文档列表
|
||||
// 加载文档列表(从本地数据库查询,方便下载)
|
||||
async function loadDocuments() {
|
||||
if (!props.knowledge?.id) return;
|
||||
|
||||
try {
|
||||
loading.value = true;
|
||||
// 使用本地数据库的文件列表接口(包含 sys_file_id 和 file_path,方便下载)
|
||||
const result = await fileUploadApi.listFilesByKnowledge(props.knowledge.id);
|
||||
if (result.success) {
|
||||
documents.value = (result.dataList || []) as AiUploadFile[];
|
||||
@@ -351,6 +375,39 @@ function handleDownload(document: AiUploadFile) {
|
||||
}
|
||||
}
|
||||
|
||||
// 切换文档启用/禁用状态
|
||||
async function handleToggleEnabled(document: AiUploadFile, enabled: boolean) {
|
||||
if (!props.knowledge?.difyDatasetId || !document.difyDocumentId) {
|
||||
ElMessage.error('文档信息不完整');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 添加loading状态
|
||||
(document as any)._switching = true;
|
||||
|
||||
// 调用 Dify API 更新文档状态
|
||||
const result = await fileUploadApi.updateDocumentStatus(
|
||||
props.knowledge.difyDatasetId,
|
||||
document.difyDocumentId,
|
||||
enabled
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
// 更新本地状态
|
||||
document.enabled = enabled;
|
||||
ElMessage.success(enabled ? '已启用文档' : '已禁用文档');
|
||||
} else {
|
||||
ElMessage.error(result.message || '更新失败');
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('更新文档状态失败:', error);
|
||||
ElMessage.error('更新失败');
|
||||
} finally {
|
||||
(document as any)._switching = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 删除文档
|
||||
async function handleDeleteDocument(document: AiUploadFile) {
|
||||
try {
|
||||
@@ -404,18 +461,6 @@ function formatFileSize(bytes: number | undefined): string {
|
||||
return (bytes / Math.pow(k, i)).toFixed(2) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
function getStatusText(status: number | undefined): string {
|
||||
switch (status) {
|
||||
case 1:
|
||||
return '处理中';
|
||||
case 2:
|
||||
return '已完成';
|
||||
case 3:
|
||||
return '失败';
|
||||
default:
|
||||
return '未知';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
Reference in New Issue
Block a user