feat: 优化文件上传模块,添加批内去重和错误处理
- 提取 fetchSingleUrlItem 函数,复用 URL 获取逻辑 - 添加 mergeSameNameFiles 函数合并同名文件列表 - 批内按内容哈希去重,避免同一批次重复入库 - 优化内容重复错误提示
This commit is contained in:
parent
42aa4f4771
commit
c980dedd6b
@ -453,6 +453,7 @@ const fileList = ref([])
|
|||||||
const urlList = ref([])
|
const urlList = ref([])
|
||||||
const newUrl = ref('')
|
const newUrl = ref('')
|
||||||
const fetchingUrls = ref(false)
|
const fetchingUrls = ref(false)
|
||||||
|
const CONTENT_EXISTS_ERROR_TEXT = '内容已存在于知识库中'
|
||||||
|
|
||||||
// 同名文件列表(用于显示提示)
|
// 同名文件列表(用于显示提示)
|
||||||
const sameNameFiles = ref([])
|
const sameNameFiles = ref([])
|
||||||
@ -467,6 +468,38 @@ const isValidUrl = (string) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mergeSameNameFiles = (sameNameList = []) => {
|
||||||
|
if (!Array.isArray(sameNameList) || sameNameList.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const existingIds = new Set(sameNameFiles.value.map((f) => f.file_id))
|
||||||
|
const newConflicts = sameNameList.filter((f) => !existingIds.has(f.file_id))
|
||||||
|
sameNameFiles.value.push(...newConflicts)
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchSingleUrlItem = async (item) => {
|
||||||
|
item.status = 'fetching'
|
||||||
|
try {
|
||||||
|
const res = await fileApi.fetchUrl(item.url, databaseId.value)
|
||||||
|
item.status = 'success'
|
||||||
|
item.data = res
|
||||||
|
mergeSameNameFiles(res.same_name_files)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch URL:', error)
|
||||||
|
item.status = 'error'
|
||||||
|
|
||||||
|
const detailData = error.response?.data?.detail
|
||||||
|
const detailMessage =
|
||||||
|
(typeof detailData === 'string' ? detailData : detailData?.message) || error.message || ''
|
||||||
|
if (detailMessage.includes('same content') || detailMessage.includes('相同内容')) {
|
||||||
|
item.error = CONTENT_EXISTS_ERROR_TEXT
|
||||||
|
mergeSameNameFiles(detailData?.same_name_files)
|
||||||
|
} else {
|
||||||
|
item.error = detailMessage || '加载失败'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleFetchUrls = async () => {
|
const handleFetchUrls = async () => {
|
||||||
const text = newUrl.value
|
const text = newUrl.value
|
||||||
if (!text) return
|
if (!text) return
|
||||||
@ -500,37 +533,7 @@ const handleFetchUrls = async () => {
|
|||||||
newUrl.value = '' // 清空输入框
|
newUrl.value = '' // 清空输入框
|
||||||
fetchingUrls.value = true
|
fetchingUrls.value = true
|
||||||
|
|
||||||
// 2. 并发处理
|
await Promise.all(newItems.map(fetchSingleUrlItem))
|
||||||
// 为避免过多并发请求,可以考虑使用 p-limit 或简单的分批,但此处直接并发
|
|
||||||
const processItem = async (item) => {
|
|
||||||
item.status = 'fetching'
|
|
||||||
try {
|
|
||||||
const res = await fileApi.fetchUrl(item.url, databaseId.value)
|
|
||||||
item.status = 'success'
|
|
||||||
item.data = res
|
|
||||||
|
|
||||||
// 处理同名文件冲突提示
|
|
||||||
if (res.has_same_name && res.same_name_files && res.same_name_files.length > 0) {
|
|
||||||
// 合并到现有的同名文件列表中,去重
|
|
||||||
const existingIds = new Set(sameNameFiles.value.map((f) => f.file_id))
|
|
||||||
const newConflicts = res.same_name_files.filter((f) => !existingIds.has(f.file_id))
|
|
||||||
sameNameFiles.value.push(...newConflicts)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to fetch URL:', error)
|
|
||||||
item.status = 'error'
|
|
||||||
|
|
||||||
// 特别处理内容重复 (409)
|
|
||||||
const detail = error.response?.data?.detail || error.message || ''
|
|
||||||
if (detail.includes('same content') || detail.includes('相同内容')) {
|
|
||||||
item.error = '内容已存在于知识库中'
|
|
||||||
} else {
|
|
||||||
item.error = detail || '加载失败'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.all(newItems.map(processItem))
|
|
||||||
fetchingUrls.value = false
|
fetchingUrls.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1006,6 +1009,29 @@ const chunkData = async () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 批内按内容哈希去重,避免同一批次重复入库
|
||||||
|
const deduplicatedItems = []
|
||||||
|
const seenKeys = new Set()
|
||||||
|
let skippedDuplicates = 0
|
||||||
|
for (const item of successfulItems) {
|
||||||
|
const dedupKey = item.data?.content_hash || item.data?.file_path || item.url
|
||||||
|
if (seenKeys.has(dedupKey)) {
|
||||||
|
skippedDuplicates += 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seenKeys.add(dedupKey)
|
||||||
|
deduplicatedItems.push(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deduplicatedItems.length === 0) {
|
||||||
|
message.error('URL 内容均为重复项,请更换后重试')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skippedDuplicates > 0) {
|
||||||
|
message.warning(`检测到 ${skippedDuplicates} 个重复 URL 内容,已保留首个并跳过其余项`)
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
store.state.chunkLoading = true
|
store.state.chunkLoading = true
|
||||||
const params = { ...chunkParams.value }
|
const params = { ...chunkParams.value }
|
||||||
@ -1017,7 +1043,7 @@ const chunkData = async () => {
|
|||||||
// 构造 _preprocessed_map 和 items (minio urls)
|
// 构造 _preprocessed_map 和 items (minio urls)
|
||||||
const items = []
|
const items = []
|
||||||
const preprocessedMap = {}
|
const preprocessedMap = {}
|
||||||
for (const item of successfulItems) {
|
for (const item of deduplicatedItems) {
|
||||||
// item.data = { file_path: "http://minio...", content_hash: "...", filename: "...", ... }
|
// item.data = { file_path: "http://minio...", content_hash: "...", filename: "...", ... }
|
||||||
// 注意:fetch-url 返回的 file_path 其实是 MinIO URL
|
// 注意:fetch-url 返回的 file_path 其实是 MinIO URL
|
||||||
// 我们需要传递 MinIO URL 给 addDocuments
|
// 我们需要传递 MinIO URL 给 addDocuments
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user