Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 38 additions & 20 deletions src/renderer/src/components/notebook/SourcePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ import { requestAppendExcerpt } from './note/appendExcerptCommand'
// 添加来源类型
type AddSourceType = 'file' | 'url' | 'text' | 'note'

/** “/home/me/notes.pdf” → “notes.pdf”。渲染进程没有 node 的 `path`。 */
const fileName = (filePath: string): string => filePath.split(/[\\/]/).pop() || filePath

// 添加来源弹窗组件
interface AddSourceModalProps {
type: AddSourceType
Expand Down Expand Up @@ -451,6 +454,22 @@ export default function SourcePanel(): ReactElement {
[notebookId, currentNote, createNote, t]
)

/**
* 导入失败必须说出来(#146)。
*
* store 把失败当 **返回值**交回来(`{ success: false, error }`),不是抛异常;只要调用点
* 不检查它,失败就和成功长得一模一样:面板保持空状态,用户以为上传成功了。
*
* `reason` 用于代替主进程的原因(空笔记有专门的文案)。
*/
const reportImportFailure = useCallback(
(name: string, result: { success: boolean; error?: string }, reason?: string): void => {
if (result.success) return
toast.error(t('importFailed', { name, error: reason ?? result.error ?? t('unknownError') }))
},
[t]
)

// 处理文件上传
const handleFileUpload = useCallback(async () => {
if (!notebookId) return
Expand All @@ -463,10 +482,13 @@ export default function SourcePanel(): ReactElement {

const files = await selectFiles()
for (const filePath of files) {
await addDocumentFromFile(notebookId, filePath)
// 一个文件失败不能影响其余文件,但也不能被吞掉:
// 吞掉它,用户看到的就是“什么都没发生”
const result = await addDocumentFromFile(notebookId, filePath)
reportImportFailure(fileName(filePath), result)
}
setShowAddMenu(false)
}, [notebookId, hasEmbeddingModel, selectFiles, addDocumentFromFile, t])
}, [notebookId, hasEmbeddingModel, selectFiles, addDocumentFromFile, reportImportFailure, t])

// 处理 URL 导入
const handleUrlImport = useCallback(
Expand All @@ -480,10 +502,11 @@ export default function SourcePanel(): ReactElement {
return
}

await addDocumentFromUrl(notebookId, data.url)
const result = await addDocumentFromUrl(notebookId, data.url)
reportImportFailure(data.url, result)
setModalType(null)
},
[notebookId, hasEmbeddingModel, addDocumentFromUrl, t]
[notebookId, hasEmbeddingModel, addDocumentFromUrl, reportImportFailure, t]
)

// 处理文本粘贴
Expand All @@ -498,14 +521,15 @@ export default function SourcePanel(): ReactElement {
return
}

await addDocument(notebookId, {
const result = await addDocument(notebookId, {
title: data.title,
type: 'text',
content: data.content
})
reportImportFailure(data.title, result)
setModalType(null)
},
[notebookId, hasEmbeddingModel, addDocument, t]
[notebookId, hasEmbeddingModel, addDocument, reportImportFailure, t]
)

// 处理笔记导入
Expand All @@ -520,21 +544,15 @@ export default function SourcePanel(): ReactElement {
return
}

try {
await addNoteToKnowledge(notebookId, data.noteId)
setModalType(null)
} catch (error) {
// 检查是否是空笔记错误
const errorMessage = (error as Error).message || ''
if (errorMessage.toLowerCase().includes('empty')) {
alert(t('emptyNoteCannotImport'))
} else {
alert(errorMessage || t('embeddingFailed', { title: '' }))
}
setModalType(null)
}
const noteTitle = notes.find((note) => note.id === data.noteId)?.title ?? data.noteId
const result = await addNoteToKnowledge(notebookId, data.noteId)
// 空笔记是唯一需要换文案的原因,其余由主进程给出
const isEmpty = (result.error ?? '').toLowerCase().includes('empty')
reportImportFailure(noteTitle, result, isEmpty ? t('emptyNoteCannotImport') : undefined)

setModalType(null)
},
[notebookId, hasEmbeddingModel, addNoteToKnowledge, t]
[notebookId, hasEmbeddingModel, addNoteToKnowledge, notes, reportImportFailure, t]
)

// 处理删除文档
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/src/locales/en-US/ui.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
"resizeNotes": "Resize the notes panel. Use the arrow keys, Shift for a larger step, Home or End for the limits.",
"addSource": "Add Source",
"uploadFile": "Upload File",
"importFailed": "Import failed for {{name}}: {{error}}",
"unknownError": "unknown reason",
"pasteText": "Paste Text",
"importUrl": "Import URL",
"importNote": "Import Note",
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/src/locales/zh-CN/ui.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
"resizeNotes": "调整笔记宽度。用方向键移动,按住 Shift 步长更大,Home / End 到两端。",
"addSource": "添加来源",
"uploadFile": "上传文件",
"importFailed": "「{{name}}」导入失败:{{error}}",
"unknownError": "未知原因",
"pasteText": "粘贴文本",
"importUrl": "导入网页",
"importNote": "导入笔记",
Expand Down
67 changes: 39 additions & 28 deletions src/renderer/src/store/knowledgeStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,29 @@ interface KnowledgeStore {
selectFiles: () => Promise<string[]>
}

/**
* 一次导入尝试结束之后(成功或失败)收尾。
*
* 无论成败都要重新读列表:失败时这一行可能已经以 `status: 'failed'` 落库了,而只刷新成功
* 的路径会让这次失败在界面上**完全不存在** —— 面板保持空状态,用户以为上传成功了(#146)。
* 失败同时记进 `error`:抛异常和返回 `{success:false}` 都是失败,没道理只记前者。
*/
async function refreshAfterImport(
notebookId: string,
result: { success: boolean; error?: string },
set: (partial: Partial<KnowledgeStore>) => void,
get: () => KnowledgeStore
): Promise<void> {
await get().loadDocuments(notebookId)
await get().loadStats(notebookId)

set({
isIndexing: false,
indexProgress: null,
error: result.success ? null : (result.error ?? null)
})
}

export const useKnowledgeStore = create<KnowledgeStore>()((set, get) => ({
// 初始状态
documents: [],
Expand Down Expand Up @@ -116,15 +139,12 @@ export const useKnowledgeStore = create<KnowledgeStore>()((set, get) => ({
set({ isIndexing: true, error: null })
try {
const result = await window.api.knowledge.addDocument(notebookId, options)
if (result.success) {
await get().loadDocuments(notebookId)
await get().loadStats(notebookId)
}
set({ isIndexing: false, indexProgress: null })
await refreshAfterImport(notebookId, result, set, get)
return result
} catch (error) {
set({ isIndexing: false, indexProgress: null, error: (error as Error).message })
return { success: false, error: (error as Error).message }
const message = (error as Error).message
await refreshAfterImport(notebookId, { success: false, error: message }, set, get)
return { success: false, error: message }
}
},

Expand All @@ -133,15 +153,12 @@ export const useKnowledgeStore = create<KnowledgeStore>()((set, get) => ({
set({ isIndexing: true, error: null })
try {
const result = await window.api.knowledge.addDocumentFromFile(notebookId, filePath)
if (result.success) {
await get().loadDocuments(notebookId)
await get().loadStats(notebookId)
}
set({ isIndexing: false, indexProgress: null })
await refreshAfterImport(notebookId, result, set, get)
return result
} catch (error) {
set({ isIndexing: false, indexProgress: null, error: (error as Error).message })
return { success: false, error: (error as Error).message }
const message = (error as Error).message
await refreshAfterImport(notebookId, { success: false, error: message }, set, get)
return { success: false, error: message }
}
},

Expand All @@ -150,15 +167,12 @@ export const useKnowledgeStore = create<KnowledgeStore>()((set, get) => ({
set({ isIndexing: true, error: null })
try {
const result = await window.api.knowledge.addDocumentFromUrl(notebookId, url)
if (result.success) {
await get().loadDocuments(notebookId)
await get().loadStats(notebookId)
}
set({ isIndexing: false, indexProgress: null })
await refreshAfterImport(notebookId, result, set, get)
return result
} catch (error) {
set({ isIndexing: false, indexProgress: null, error: (error as Error).message })
return { success: false, error: (error as Error).message }
const message = (error as Error).message
await refreshAfterImport(notebookId, { success: false, error: message }, set, get)
return { success: false, error: message }
}
},

Expand All @@ -167,15 +181,12 @@ export const useKnowledgeStore = create<KnowledgeStore>()((set, get) => ({
set({ isIndexing: true, error: null })
try {
const result = await window.api.knowledge.addNote(notebookId, noteId)
if (result.success) {
await get().loadDocuments(notebookId)
await get().loadStats(notebookId)
}
set({ isIndexing: false, indexProgress: null })
await refreshAfterImport(notebookId, result, set, get)
return result
} catch (error) {
set({ isIndexing: false, indexProgress: null, error: (error as Error).message })
return { success: false, error: (error as Error).message }
const message = (error as Error).message
await refreshAfterImport(notebookId, { success: false, error: message }, set, get)
return { success: false, error: message }
}
},

Expand Down
Loading
Loading