2026-01-15 06:01:34 +08:00
|
|
|
|
import { defineStore } from 'pinia'
|
|
|
|
|
|
import { ref, reactive } from 'vue'
|
|
|
|
|
|
import { message, Modal } from 'ant-design-vue'
|
|
|
|
|
|
import { databaseApi, documentApi, queryApi } from '@/apis/knowledge_api'
|
|
|
|
|
|
import { useTaskerStore } from '@/stores/tasker'
|
2026-03-17 00:58:56 +08:00
|
|
|
|
import { useUserStore } from '@/stores/user'
|
2026-01-15 06:01:34 +08:00
|
|
|
|
import { useRouter } from 'vue-router'
|
|
|
|
|
|
import { parseToShanghai } from '@/utils/time'
|
2024-07-28 16:16:52 +08:00
|
|
|
|
|
|
|
|
|
|
export const useDatabaseStore = defineStore('database', () => {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const router = useRouter()
|
|
|
|
|
|
const taskerStore = useTaskerStore()
|
2026-03-17 00:58:56 +08:00
|
|
|
|
const userStore = useUserStore()
|
2025-08-05 18:00:45 +08:00
|
|
|
|
|
|
|
|
|
|
// State
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const databases = ref([])
|
|
|
|
|
|
const database = ref({})
|
|
|
|
|
|
const databaseId = ref(null)
|
|
|
|
|
|
const selectedFile = ref(null)
|
2025-08-05 22:58:30 +08:00
|
|
|
|
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const queryParams = ref([])
|
|
|
|
|
|
const meta = reactive({})
|
|
|
|
|
|
const selectedRowKeys = ref([])
|
2025-08-05 18:00:45 +08:00
|
|
|
|
|
|
|
|
|
|
const state = reactive({
|
2025-12-30 19:01:45 +08:00
|
|
|
|
listLoading: false,
|
|
|
|
|
|
creating: false,
|
2025-08-05 18:00:45 +08:00
|
|
|
|
databaseLoading: false,
|
|
|
|
|
|
refrashing: false,
|
|
|
|
|
|
searchLoading: false,
|
|
|
|
|
|
lock: false,
|
|
|
|
|
|
fileDetailModalVisible: false,
|
|
|
|
|
|
fileDetailLoading: false,
|
|
|
|
|
|
batchDeleting: false,
|
|
|
|
|
|
chunkLoading: false,
|
|
|
|
|
|
autoRefresh: false,
|
|
|
|
|
|
queryParamsLoading: false,
|
2026-01-15 06:01:34 +08:00
|
|
|
|
rightPanelVisible: true
|
|
|
|
|
|
})
|
2025-08-05 18:00:45 +08:00
|
|
|
|
|
2026-01-15 06:01:34 +08:00
|
|
|
|
let refreshInterval = null
|
|
|
|
|
|
let autoRefreshSource = null // Tracks whether auto-refresh was user-triggered or automatic
|
|
|
|
|
|
let autoRefreshManualOverride = false // Indicates user explicitly disabled auto-refresh
|
2025-08-05 18:00:45 +08:00
|
|
|
|
|
|
|
|
|
|
// Actions
|
2026-03-17 00:58:56 +08:00
|
|
|
|
// 管理员获取所有知识库,普通用户获取有权限访问的知识库
|
2025-12-30 19:01:45 +08:00
|
|
|
|
async function loadDatabases() {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
state.listLoading = true
|
2025-12-30 19:01:45 +08:00
|
|
|
|
try {
|
2026-03-17 00:58:56 +08:00
|
|
|
|
const data = userStore.isAdmin
|
|
|
|
|
|
? await databaseApi.getDatabases()
|
|
|
|
|
|
: await databaseApi.getAccessibleDatabases()
|
|
|
|
|
|
const list = data?.databases || []
|
|
|
|
|
|
databases.value = list.sort((a, b) => {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const timeA = parseToShanghai(a.created_at)
|
|
|
|
|
|
const timeB = parseToShanghai(b.created_at)
|
|
|
|
|
|
if (!timeA && !timeB) return 0
|
|
|
|
|
|
if (!timeA) return 1
|
|
|
|
|
|
if (!timeB) return -1
|
|
|
|
|
|
return timeB.valueOf() - timeA.valueOf() // 降序排列,最新的在前面
|
|
|
|
|
|
})
|
2025-12-30 19:01:45 +08:00
|
|
|
|
} catch (error) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
console.error('加载数据库列表失败:', error)
|
2025-12-30 19:01:45 +08:00
|
|
|
|
if (error.message.includes('权限')) {
|
2026-01-22 12:15:46 +08:00
|
|
|
|
message.error('没有权限访问知识库')
|
2025-12-30 19:01:45 +08:00
|
|
|
|
}
|
2026-01-15 06:01:34 +08:00
|
|
|
|
throw error
|
2025-12-30 19:01:45 +08:00
|
|
|
|
} finally {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
state.listLoading = false
|
2025-12-30 19:01:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function createDatabase(formData) {
|
|
|
|
|
|
// 验证
|
|
|
|
|
|
if (!formData.database_name?.trim()) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
message.error('数据库名称不能为空')
|
|
|
|
|
|
return false
|
2025-12-30 19:01:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!formData.kb_type) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
message.error('请选择知识库类型')
|
|
|
|
|
|
return false
|
2025-12-30 19:01:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-15 06:01:34 +08:00
|
|
|
|
state.creating = true
|
2025-12-30 19:01:45 +08:00
|
|
|
|
try {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const data = await databaseApi.createDatabase(formData)
|
|
|
|
|
|
message.success('创建成功')
|
|
|
|
|
|
await loadDatabases() // 刷新列表
|
|
|
|
|
|
return data
|
2025-12-30 19:01:45 +08:00
|
|
|
|
} catch (error) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
console.error('创建数据库失败:', error)
|
|
|
|
|
|
message.error(error.message || '创建失败')
|
|
|
|
|
|
throw error
|
2025-12-30 19:01:45 +08:00
|
|
|
|
} finally {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
state.creating = false
|
2025-12-30 19:01:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-07 11:16:08 +08:00
|
|
|
|
async function getDatabaseInfo(id, skipQueryParams = false, isBackground = false) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const db_id = id || databaseId.value
|
|
|
|
|
|
if (!db_id) return
|
2025-08-05 18:00:45 +08:00
|
|
|
|
|
2026-01-07 11:16:08 +08:00
|
|
|
|
if (!isBackground) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
state.lock = true
|
|
|
|
|
|
state.databaseLoading = true
|
2026-01-07 11:16:08 +08:00
|
|
|
|
}
|
2025-08-05 18:00:45 +08:00
|
|
|
|
try {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const data = await databaseApi.getDatabaseInfo(db_id)
|
|
|
|
|
|
database.value = data
|
|
|
|
|
|
ensureAutoRefreshForProcessing(data?.files)
|
2025-11-06 19:47:22 +08:00
|
|
|
|
|
|
|
|
|
|
// Only load query parameters if explicitly requested or if not loaded yet
|
|
|
|
|
|
if (!skipQueryParams && queryParams.value.length === 0) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
await loadQueryParams(db_id)
|
2025-11-06 19:47:22 +08:00
|
|
|
|
}
|
2025-08-05 18:00:45 +08:00
|
|
|
|
} catch (error) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
console.error(error)
|
|
|
|
|
|
message.error(error.message || '获取数据库信息失败')
|
2025-08-05 18:00:45 +08:00
|
|
|
|
} finally {
|
2026-01-07 11:16:08 +08:00
|
|
|
|
if (!isBackground) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
state.lock = false
|
|
|
|
|
|
state.databaseLoading = false
|
2026-01-07 11:16:08 +08:00
|
|
|
|
}
|
2025-08-05 18:00:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function updateDatabaseInfo(formData) {
|
|
|
|
|
|
try {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
state.lock = true
|
|
|
|
|
|
await databaseApi.updateDatabase(databaseId.value, formData)
|
|
|
|
|
|
message.success('知识库信息更新成功')
|
|
|
|
|
|
await getDatabaseInfo() // Load query params after updating database info
|
2025-08-05 18:00:45 +08:00
|
|
|
|
} catch (error) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
console.error(error)
|
|
|
|
|
|
message.error(error.message || '更新失败')
|
2025-08-05 18:00:45 +08:00
|
|
|
|
} finally {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
state.lock = false
|
2025-08-05 18:00:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function deleteDatabase() {
|
|
|
|
|
|
Modal.confirm({
|
|
|
|
|
|
title: '删除数据库',
|
|
|
|
|
|
content: '确定要删除该数据库吗?',
|
|
|
|
|
|
okText: '确认',
|
|
|
|
|
|
cancelText: '取消',
|
|
|
|
|
|
onOk: async () => {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
state.lock = true
|
2025-08-05 18:00:45 +08:00
|
|
|
|
try {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const data = await databaseApi.deleteDatabase(databaseId.value)
|
|
|
|
|
|
message.success(data.message || '删除成功')
|
|
|
|
|
|
router.push('/database')
|
2025-08-05 18:00:45 +08:00
|
|
|
|
} catch (error) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
console.error(error)
|
|
|
|
|
|
message.error(error.message || '删除失败')
|
2025-08-05 18:00:45 +08:00
|
|
|
|
} finally {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
state.lock = false
|
2025-08-05 18:00:45 +08:00
|
|
|
|
}
|
2026-01-15 06:01:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
})
|
2025-08-05 18:00:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function deleteFile(fileId) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
state.lock = true
|
2025-08-05 18:00:45 +08:00
|
|
|
|
try {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
await documentApi.deleteDocument(databaseId.value, fileId)
|
|
|
|
|
|
await getDatabaseInfo(undefined, true) // Skip query params for file deletion
|
2025-08-05 18:00:45 +08:00
|
|
|
|
} catch (error) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
console.error(error)
|
|
|
|
|
|
message.error(error.message || '删除失败')
|
|
|
|
|
|
throw error
|
2025-08-05 18:00:45 +08:00
|
|
|
|
} finally {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
state.lock = false
|
2025-08-05 18:00:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function handleDeleteFile(fileId) {
|
|
|
|
|
|
Modal.confirm({
|
|
|
|
|
|
title: '删除文件',
|
|
|
|
|
|
content: '确定要删除该文件吗?',
|
|
|
|
|
|
okText: '确认',
|
|
|
|
|
|
cancelText: '取消',
|
2026-01-15 06:01:34 +08:00
|
|
|
|
onOk: () => deleteFile(fileId)
|
|
|
|
|
|
})
|
2025-08-05 18:00:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function handleBatchDelete() {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const files = database.value.files || {}
|
|
|
|
|
|
const validFileIds = selectedRowKeys.value.filter((fileId) => {
|
|
|
|
|
|
const file = files[fileId]
|
|
|
|
|
|
return file && !(file.status === 'processing' || file.status === 'waiting')
|
|
|
|
|
|
})
|
2025-08-05 18:00:45 +08:00
|
|
|
|
|
|
|
|
|
|
if (validFileIds.length === 0) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
message.info('没有可删除的文件')
|
|
|
|
|
|
return
|
2025-08-05 18:00:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Modal.confirm({
|
|
|
|
|
|
title: '批量删除文件',
|
|
|
|
|
|
content: `确定要删除选中的 ${validFileIds.length} 个文件吗?`,
|
|
|
|
|
|
okText: '确认',
|
|
|
|
|
|
cancelText: '取消',
|
|
|
|
|
|
onOk: async () => {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
state.batchDeleting = true
|
|
|
|
|
|
let successCount = 0
|
|
|
|
|
|
let failureCount = 0
|
2026-03-01 16:18:59 +08:00
|
|
|
|
let processedCount = 0
|
|
|
|
|
|
const totalCount = validFileIds.length
|
|
|
|
|
|
const progressKey = `batch-delete-${Date.now()}`
|
|
|
|
|
|
message.loading({ content: `正在删除文件 0/${totalCount}`, key: progressKey, duration: 0 })
|
2025-08-05 18:00:45 +08:00
|
|
|
|
|
|
|
|
|
|
try {
|
2026-03-01 16:18:59 +08:00
|
|
|
|
const CHUNK_SIZE = 50
|
|
|
|
|
|
for (let i = 0; i < totalCount; i += CHUNK_SIZE) {
|
|
|
|
|
|
const chunk = validFileIds.slice(i, i + CHUNK_SIZE)
|
|
|
|
|
|
|
2025-08-05 18:00:45 +08:00
|
|
|
|
try {
|
2026-03-01 16:18:59 +08:00
|
|
|
|
const res = await documentApi.batchDeleteDocuments(databaseId.value, chunk)
|
|
|
|
|
|
successCount += res.deleted_count || 0
|
|
|
|
|
|
if (res.failed_items) {
|
|
|
|
|
|
failureCount += res.failed_items.length
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.error(`删除批次 ${i / CHUNK_SIZE + 1} 失败:`, err)
|
|
|
|
|
|
failureCount += chunk.length
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
processedCount += chunk.length
|
|
|
|
|
|
message.loading({
|
|
|
|
|
|
content: `正在删除文件 ${processedCount}/${totalCount}`,
|
|
|
|
|
|
key: progressKey,
|
|
|
|
|
|
duration: 0
|
|
|
|
|
|
})
|
2025-08-05 18:00:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-01 16:18:59 +08:00
|
|
|
|
|
|
|
|
|
|
message.destroy(progressKey)
|
2025-08-05 18:00:45 +08:00
|
|
|
|
if (successCount > 0 && failureCount === 0) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
message.success(`成功删除 ${successCount} 个文件`)
|
2025-08-05 18:00:45 +08:00
|
|
|
|
} else if (successCount > 0 && failureCount > 0) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
message.warning(`成功删除 ${successCount} 个文件,${failureCount} 个文件删除失败`)
|
2025-08-05 18:00:45 +08:00
|
|
|
|
} else if (failureCount > 0) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
message.error(`${failureCount} 个文件删除失败`)
|
2025-08-05 18:00:45 +08:00
|
|
|
|
}
|
2026-03-01 16:18:59 +08:00
|
|
|
|
|
2026-01-15 06:01:34 +08:00
|
|
|
|
selectedRowKeys.value = []
|
|
|
|
|
|
await getDatabaseInfo(undefined, true) // Skip query params for batch deletion
|
2025-08-05 18:00:45 +08:00
|
|
|
|
} catch (error) {
|
2026-03-01 16:18:59 +08:00
|
|
|
|
message.destroy(progressKey)
|
2026-01-15 06:01:34 +08:00
|
|
|
|
console.error('批量删除出错:', error)
|
2026-03-01 16:18:59 +08:00
|
|
|
|
message.error(error.message || '批量删除过程中发生错误')
|
2025-08-05 18:00:45 +08:00
|
|
|
|
} finally {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
state.batchDeleting = false
|
2025-08-05 18:00:45 +08:00
|
|
|
|
}
|
2026-01-15 06:01:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
})
|
2025-08-05 18:00:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const processingStatuses = new Set(['processing', 'waiting', 'parsing', 'indexing'])
|
2025-10-25 16:01:50 +08:00
|
|
|
|
|
|
|
|
|
|
function enableAutoRefresh(source = 'auto') {
|
|
|
|
|
|
if (autoRefreshManualOverride && source === 'auto') {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
return
|
2025-10-25 16:01:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!state.autoRefresh) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
state.autoRefresh = true
|
|
|
|
|
|
autoRefreshSource = source
|
|
|
|
|
|
autoRefreshManualOverride = false
|
|
|
|
|
|
startAutoRefresh()
|
|
|
|
|
|
return
|
2025-10-25 16:01:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (source === 'auto' && autoRefreshSource !== 'manual') {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
autoRefreshSource = 'auto'
|
2025-10-25 16:01:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function ensureAutoRefreshForProcessing(filesMap) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const files = Object.values(filesMap || {})
|
|
|
|
|
|
const hasPending = files.some((file) => file && processingStatuses.has(file.status))
|
2025-10-25 16:01:50 +08:00
|
|
|
|
if (hasPending) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
enableAutoRefresh('auto')
|
2025-10-25 16:01:50 +08:00
|
|
|
|
} else if (autoRefreshSource === 'auto' && state.autoRefresh) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
state.autoRefresh = false
|
|
|
|
|
|
autoRefreshSource = null
|
|
|
|
|
|
autoRefreshManualOverride = false
|
|
|
|
|
|
stopAutoRefresh()
|
2025-10-25 16:01:50 +08:00
|
|
|
|
}
|
2026-01-15 06:01:34 +08:00
|
|
|
|
return hasPending
|
2025-10-25 16:01:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-30 14:28:48 +08:00
|
|
|
|
async function moveFile(fileId, newParentId) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
state.lock = true
|
2025-12-30 14:28:48 +08:00
|
|
|
|
try {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
await documentApi.moveDocument(databaseId.value, fileId, newParentId)
|
|
|
|
|
|
await getDatabaseInfo(undefined, true) // Skip query params for file movement
|
|
|
|
|
|
message.success('移动成功')
|
2025-12-30 14:28:48 +08:00
|
|
|
|
} catch (error) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
console.error(error)
|
|
|
|
|
|
message.error(error.message || '移动失败')
|
|
|
|
|
|
throw error
|
2025-12-30 14:28:48 +08:00
|
|
|
|
} finally {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
state.lock = false
|
2025-12-30 14:28:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function addFiles({ items, contentType, params, parentId }) {
|
2025-08-05 18:00:45 +08:00
|
|
|
|
if (items.length === 0) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
message.error(contentType === 'file' ? '请先上传文件' : '请输入有效的网页链接')
|
|
|
|
|
|
return
|
2025-08-05 18:00:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-15 06:01:34 +08:00
|
|
|
|
state.chunkLoading = true
|
2025-08-05 18:00:45 +08:00
|
|
|
|
try {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const requestParams = { ...params, content_type: contentType }
|
2025-12-30 14:28:48 +08:00
|
|
|
|
if (parentId) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
requestParams.parent_id = parentId
|
2025-12-30 14:28:48 +08:00
|
|
|
|
}
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const data = await documentApi.addDocuments(databaseId.value, items, requestParams)
|
2025-10-11 15:02:24 +08:00
|
|
|
|
if (data.status === 'success' || data.status === 'queued') {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const itemType = contentType === 'file' ? '文件' : 'URL'
|
|
|
|
|
|
enableAutoRefresh('auto')
|
|
|
|
|
|
message.success(data.message || `${itemType}已提交处理,请在任务中心查看进度`)
|
2025-10-11 15:02:24 +08:00
|
|
|
|
if (data.task_id) {
|
|
|
|
|
|
taskerStore.registerQueuedTask({
|
|
|
|
|
|
task_id: data.task_id,
|
|
|
|
|
|
name: `知识库导入 (${databaseId.value || ''})`,
|
|
|
|
|
|
task_type: 'knowledge_ingest',
|
|
|
|
|
|
message: data.message,
|
|
|
|
|
|
payload: {
|
|
|
|
|
|
db_id: databaseId.value,
|
|
|
|
|
|
count: items.length,
|
2026-01-15 06:01:34 +08:00
|
|
|
|
content_type: contentType
|
2025-10-11 15:02:24 +08:00
|
|
|
|
}
|
2026-01-15 06:01:34 +08:00
|
|
|
|
})
|
2025-10-11 15:02:24 +08:00
|
|
|
|
}
|
2026-01-25 23:00:03 +08:00
|
|
|
|
await delayedRefresh() // 延迟1秒后刷新
|
2026-01-15 06:01:34 +08:00
|
|
|
|
return true // Indicate success
|
2025-08-05 18:00:45 +08:00
|
|
|
|
} else {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
message.error(data.message || '处理失败')
|
|
|
|
|
|
return false
|
2025-08-05 18:00:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
console.error(error)
|
|
|
|
|
|
message.error(error.message || '处理请求失败')
|
|
|
|
|
|
return false
|
2025-08-05 18:00:45 +08:00
|
|
|
|
} finally {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
state.chunkLoading = false
|
2025-08-05 18:00:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-04 21:35:25 +08:00
|
|
|
|
async function parseFiles(fileIds) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
if (fileIds.length === 0) return
|
|
|
|
|
|
state.chunkLoading = true
|
2026-01-04 21:35:25 +08:00
|
|
|
|
try {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const data = await documentApi.parseDocuments(databaseId.value, fileIds)
|
2026-01-04 21:35:25 +08:00
|
|
|
|
if (data.status === 'success' || data.status === 'queued') {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
enableAutoRefresh('auto')
|
|
|
|
|
|
message.success(data.message || '解析任务已提交')
|
2026-01-04 21:35:25 +08:00
|
|
|
|
if (data.task_id) {
|
|
|
|
|
|
taskerStore.registerQueuedTask({
|
|
|
|
|
|
task_id: data.task_id,
|
|
|
|
|
|
name: `文档解析 (${databaseId.value})`,
|
|
|
|
|
|
task_type: 'knowledge_parse',
|
|
|
|
|
|
message: data.message,
|
|
|
|
|
|
payload: { db_id: databaseId.value, count: fileIds.length }
|
2026-01-15 06:01:34 +08:00
|
|
|
|
})
|
2026-01-04 21:35:25 +08:00
|
|
|
|
}
|
2026-01-25 23:00:03 +08:00
|
|
|
|
await delayedRefresh() // 延迟1秒后刷新
|
2026-01-15 06:01:34 +08:00
|
|
|
|
return true
|
2026-01-04 21:35:25 +08:00
|
|
|
|
} else {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
message.error(data.message || '提交失败')
|
|
|
|
|
|
return false
|
2026-01-04 21:35:25 +08:00
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
console.error(error)
|
|
|
|
|
|
message.error(error.message || '请求失败')
|
|
|
|
|
|
return false
|
2026-01-04 21:35:25 +08:00
|
|
|
|
} finally {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
state.chunkLoading = false
|
2025-11-12 23:14:56 +08:00
|
|
|
|
}
|
2026-01-04 21:35:25 +08:00
|
|
|
|
}
|
2025-11-12 23:14:56 +08:00
|
|
|
|
|
2026-01-04 21:35:25 +08:00
|
|
|
|
async function indexFiles(fileIds, params = {}) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
if (fileIds.length === 0) return
|
|
|
|
|
|
state.chunkLoading = true
|
2025-11-12 23:14:56 +08:00
|
|
|
|
try {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const data = await documentApi.indexDocuments(databaseId.value, fileIds, params)
|
2025-11-12 23:14:56 +08:00
|
|
|
|
if (data.status === 'success' || data.status === 'queued') {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
enableAutoRefresh('auto')
|
|
|
|
|
|
message.success(data.message || '入库任务已提交')
|
2025-11-12 23:14:56 +08:00
|
|
|
|
if (data.task_id) {
|
|
|
|
|
|
taskerStore.registerQueuedTask({
|
|
|
|
|
|
task_id: data.task_id,
|
2026-01-04 21:35:25 +08:00
|
|
|
|
name: `文档入库 (${databaseId.value})`,
|
|
|
|
|
|
task_type: 'knowledge_index',
|
2025-11-12 23:14:56 +08:00
|
|
|
|
message: data.message,
|
2026-01-04 21:35:25 +08:00
|
|
|
|
payload: { db_id: databaseId.value, count: fileIds.length }
|
2026-01-15 06:01:34 +08:00
|
|
|
|
})
|
2025-11-12 23:14:56 +08:00
|
|
|
|
}
|
2026-01-25 23:00:03 +08:00
|
|
|
|
await delayedRefresh() // 延迟1秒后刷新
|
2026-01-15 06:01:34 +08:00
|
|
|
|
return true
|
2025-11-12 23:14:56 +08:00
|
|
|
|
} else {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
message.error(data.message || '提交失败')
|
|
|
|
|
|
return false
|
2025-11-12 23:14:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
console.error(error)
|
|
|
|
|
|
message.error(error.message || '请求失败')
|
|
|
|
|
|
return false
|
2025-11-12 23:14:56 +08:00
|
|
|
|
} finally {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
state.chunkLoading = false
|
2025-11-12 23:14:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-05 18:00:45 +08:00
|
|
|
|
async function openFileDetail(record) {
|
2026-01-04 21:35:25 +08:00
|
|
|
|
// 只要有 markdown_file (隐含在 status >= parsed 中) 或者是 error_indexing (说明解析成功但入库失败),就可以查看
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const allowStatuses = ['done', 'parsed', 'indexed', 'error_indexing']
|
2026-01-04 21:35:25 +08:00
|
|
|
|
if (!allowStatuses.includes(record.status)) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
message.error('文件未处理完成,请稍后再试')
|
|
|
|
|
|
return
|
2025-08-05 18:00:45 +08:00
|
|
|
|
}
|
2026-01-15 06:01:34 +08:00
|
|
|
|
state.fileDetailModalVisible = true
|
|
|
|
|
|
selectedFile.value = { ...record, lines: [] }
|
|
|
|
|
|
state.fileDetailLoading = true
|
|
|
|
|
|
state.lock = true
|
2025-08-05 18:00:45 +08:00
|
|
|
|
|
|
|
|
|
|
try {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const data = await documentApi.getDocumentInfo(databaseId.value, record.file_id)
|
|
|
|
|
|
if (data.status == 'failed') {
|
|
|
|
|
|
message.error(data.message)
|
|
|
|
|
|
state.fileDetailModalVisible = false
|
|
|
|
|
|
return
|
2025-08-05 18:00:45 +08:00
|
|
|
|
}
|
2026-01-15 06:01:34 +08:00
|
|
|
|
selectedFile.value = { ...record, lines: data.lines || [], content: data.content }
|
2025-08-05 18:00:45 +08:00
|
|
|
|
} catch (error) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
console.error(error)
|
|
|
|
|
|
message.error(error.message)
|
|
|
|
|
|
state.fileDetailModalVisible = false
|
2025-08-05 18:00:45 +08:00
|
|
|
|
} finally {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
state.fileDetailLoading = false
|
|
|
|
|
|
state.lock = false
|
2025-08-05 18:00:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function loadQueryParams(id) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const db_id = id || databaseId.value
|
|
|
|
|
|
if (!db_id) return
|
2025-08-05 18:00:45 +08:00
|
|
|
|
|
2026-01-15 06:01:34 +08:00
|
|
|
|
state.queryParamsLoading = true
|
2025-08-05 18:00:45 +08:00
|
|
|
|
try {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const response = await queryApi.getKnowledgeBaseQueryParams(db_id)
|
|
|
|
|
|
queryParams.value = response.params?.options || []
|
2025-08-16 20:07:12 +08:00
|
|
|
|
|
2025-08-05 18:00:45 +08:00
|
|
|
|
// Create a set of currently supported parameter keys
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const supportedParamKeys = new Set(queryParams.value.map((param) => param.key))
|
2025-08-16 20:07:12 +08:00
|
|
|
|
|
2025-08-05 18:00:45 +08:00
|
|
|
|
// Remove unsupported parameters from meta
|
|
|
|
|
|
for (const key in meta) {
|
|
|
|
|
|
if (key !== 'db_id' && !supportedParamKeys.has(key)) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
delete meta[key]
|
2025-08-05 18:00:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-16 20:07:12 +08:00
|
|
|
|
|
2025-08-05 18:00:45 +08:00
|
|
|
|
// Add default values for supported parameters that are not in meta
|
2026-01-15 06:01:34 +08:00
|
|
|
|
queryParams.value.forEach((param) => {
|
2025-08-05 18:00:45 +08:00
|
|
|
|
if (!(param.key in meta)) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
meta[param.key] = param.default
|
2025-08-05 18:00:45 +08:00
|
|
|
|
}
|
2026-01-15 06:01:34 +08:00
|
|
|
|
})
|
2025-08-05 18:00:45 +08:00
|
|
|
|
} catch (error) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
console.error('Failed to load query params:', error)
|
|
|
|
|
|
message.error('加载查询参数失败')
|
2025-08-05 18:00:45 +08:00
|
|
|
|
} finally {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
state.queryParamsLoading = false
|
2025-08-05 18:00:45 +08:00
|
|
|
|
}
|
2024-07-28 16:16:52 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-05 18:00:45 +08:00
|
|
|
|
function startAutoRefresh() {
|
|
|
|
|
|
if (state.autoRefresh && !refreshInterval) {
|
|
|
|
|
|
refreshInterval = setInterval(() => {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
getDatabaseInfo(undefined, true, true) // Skip loading query params during auto-refresh
|
|
|
|
|
|
}, 1000)
|
2025-08-05 18:00:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function stopAutoRefresh() {
|
|
|
|
|
|
if (refreshInterval) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
clearInterval(refreshInterval)
|
|
|
|
|
|
refreshInterval = null
|
2025-08-05 18:00:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-25 23:00:03 +08:00
|
|
|
|
// 延时刷新文件理解(延迟1秒后刷新)
|
|
|
|
|
|
async function delayedRefresh() {
|
|
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
|
|
|
|
await getDatabaseInfo(undefined, true)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-05 18:00:45 +08:00
|
|
|
|
function toggleAutoRefresh() {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const nextState = !state.autoRefresh
|
|
|
|
|
|
state.autoRefresh = nextState
|
2025-10-25 16:01:50 +08:00
|
|
|
|
if (nextState) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
autoRefreshSource = 'manual'
|
|
|
|
|
|
autoRefreshManualOverride = false
|
|
|
|
|
|
startAutoRefresh()
|
2025-08-05 18:00:45 +08:00
|
|
|
|
} else {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
autoRefreshManualOverride = true
|
|
|
|
|
|
autoRefreshSource = null
|
|
|
|
|
|
stopAutoRefresh()
|
2025-08-05 18:00:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-16 20:07:12 +08:00
|
|
|
|
|
2025-08-05 18:00:45 +08:00
|
|
|
|
function selectAllFailedFiles() {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const files = Object.values(database.value.files || {})
|
|
|
|
|
|
const failedFiles = files.filter((file) => file.status === 'failed').map((file) => file.file_id)
|
2025-08-16 20:07:12 +08:00
|
|
|
|
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const newSelectedKeys = [...new Set([...selectedRowKeys.value, ...failedFiles])]
|
|
|
|
|
|
selectedRowKeys.value = newSelectedKeys
|
2025-08-16 20:07:12 +08:00
|
|
|
|
|
2025-08-05 18:00:45 +08:00
|
|
|
|
if (failedFiles.length > 0) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
message.success(`已选择 ${failedFiles.length} 个失败的文件`)
|
2025-08-05 18:00:45 +08:00
|
|
|
|
} else {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
message.info('当前没有失败的文件')
|
2025-08-05 18:00:45 +08:00
|
|
|
|
}
|
2024-10-24 13:09:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-05 18:00:45 +08:00
|
|
|
|
return {
|
2025-12-30 19:01:45 +08:00
|
|
|
|
databases,
|
2025-08-05 18:00:45 +08:00
|
|
|
|
database,
|
|
|
|
|
|
databaseId,
|
|
|
|
|
|
selectedFile,
|
|
|
|
|
|
queryParams,
|
|
|
|
|
|
meta,
|
|
|
|
|
|
selectedRowKeys,
|
|
|
|
|
|
state,
|
2025-12-30 19:01:45 +08:00
|
|
|
|
loadDatabases,
|
|
|
|
|
|
createDatabase,
|
2025-08-05 18:00:45 +08:00
|
|
|
|
getDatabaseInfo,
|
|
|
|
|
|
updateDatabaseInfo,
|
|
|
|
|
|
deleteDatabase,
|
|
|
|
|
|
deleteFile,
|
|
|
|
|
|
handleDeleteFile,
|
|
|
|
|
|
handleBatchDelete,
|
2025-12-30 14:28:48 +08:00
|
|
|
|
moveFile,
|
2025-08-05 18:00:45 +08:00
|
|
|
|
addFiles,
|
2026-01-04 21:35:25 +08:00
|
|
|
|
parseFiles,
|
|
|
|
|
|
indexFiles,
|
2025-08-05 18:00:45 +08:00
|
|
|
|
openFileDetail,
|
|
|
|
|
|
loadQueryParams,
|
2025-08-05 22:58:30 +08:00
|
|
|
|
|
2025-08-05 18:00:45 +08:00
|
|
|
|
startAutoRefresh,
|
|
|
|
|
|
stopAutoRefresh,
|
|
|
|
|
|
toggleAutoRefresh,
|
2026-01-15 06:01:34 +08:00
|
|
|
|
selectAllFailedFiles
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|