This commit is contained in:
SengokuCola
2026-05-07 00:05:39 +08:00
9 changed files with 590 additions and 61 deletions

View File

@@ -154,6 +154,38 @@ function mockImportDetail(taskId: string): memoryApi.MemoryImportTaskPayload {
}
}
function mockImportCompletedWithErrorsDetail(taskId: string): memoryApi.MemoryImportTaskPayload {
return {
...mockImportDetail(taskId),
status: 'completed_with_errors',
current_step: 'completed_with_errors',
total_chunks: 12,
done_chunks: 9,
failed_chunks: 3,
cancelled_chunks: 0,
progress: 75,
files: [
{
file_id: 'file-error',
name: 'error.txt',
source_kind: 'paste',
input_mode: 'text',
status: 'failed',
current_step: 'failed',
detected_strategy_type: 'auto',
total_chunks: 12,
done_chunks: 9,
failed_chunks: 3,
cancelled_chunks: 0,
progress: 75,
error: 'mock error',
created_at: 1_710_000_000,
updated_at: 1_710_000_100,
},
],
}
}
describe('KnowledgeBasePage import workflow', () => {
beforeEach(() => {
navigateMock.mockReset()
@@ -606,6 +638,21 @@ describe('KnowledgeBasePage import workflow', () => {
)
}, 20_000)
it('shows import failures separately from successful chunks', async () => {
vi.mocked(memoryApi.getMemoryImportTask).mockResolvedValue({
success: true,
task: mockImportCompletedWithErrorsDetail('import-run-1'),
})
const user = userEvent.setup()
render(<KnowledgeBasePage />)
await screen.findByText('长期记忆控制台', undefined, { timeout: 10_000 })
await user.click(screen.getByRole('tab', { name: '导入' }))
expect((await screen.findAllByText('完成(有错误)')).length).toBeGreaterThan(0)
expect(await screen.findByText('成功 9 / 12 分块 · 失败 3')).toBeInTheDocument()
}, 20_000)
it('supports cancel and retry actions for selected task', async () => {
const user = userEvent.setup()
render(<KnowledgeBasePage />)

View File

@@ -39,6 +39,21 @@ import {
normalizeProgress,
} from '../utils'
function formatChunkSummary(done: unknown, total: unknown, failed: unknown, cancelled: unknown = 0): string {
const doneCount = Number(done ?? 0)
const totalCount = Number(total ?? 0)
const failedCount = Number(failed ?? 0)
const cancelledCount = Number(cancelled ?? 0)
const parts = [`成功 ${doneCount} / ${totalCount} 分块`]
if (failedCount > 0) {
parts.push(`失败 ${failedCount}`)
}
if (cancelledCount > 0) {
parts.push(`取消 ${cancelledCount}`)
}
return parts.join(' · ')
}
export interface ImportTabProps {
importCreateMode: MemoryImportTaskKind
setImportCreateMode: Dispatch<SetStateAction<MemoryImportTaskKind>>
@@ -1073,12 +1088,19 @@ export function ImportTab(props: ImportTabProps) {
? 'success'
: String(selectedImportTaskResolved.status ?? '') === 'failed'
? 'destructive'
: String(selectedImportTaskResolved.status ?? '') === 'cancelled'
: String(selectedImportTaskResolved.status ?? '') === 'completed_with_errors'
? 'warning'
: String(selectedImportTaskResolved.status ?? '') === 'cancelled'
? 'muted'
: 'default'
}
busy={RUNNING_IMPORT_STATUS.has(String(selectedImportTaskResolved.status ?? ''))}
detail={`已完成 ${Number(selectedImportTaskResolved.done_chunks ?? 0)} / ${Number(selectedImportTaskResolved.total_chunks ?? 0)} 分块`}
detail={formatChunkSummary(
selectedImportTaskResolved.done_chunks,
selectedImportTaskResolved.total_chunks,
selectedImportTaskResolved.failed_chunks,
selectedImportTaskResolved.cancelled_chunks,
)}
/>
</TableCell>
</TableRow>
@@ -1160,7 +1182,12 @@ export function ImportTab(props: ImportTabProps) {
</div>
<Progress value={normalizeProgress(file.progress)} className="mt-2 h-1.5" />
<div className="mt-2 text-xs text-muted-foreground">
{formatProgressPercent(file.progress)} · {Number(file.done_chunks ?? 0)} / {Number(file.total_chunks ?? 0)}
{formatProgressPercent(file.progress)} · {formatChunkSummary(
file.done_chunks,
file.total_chunks,
file.failed_chunks,
file.cancelled_chunks,
)}
</div>
{file.error ? (
<div className="mt-2 truncate text-xs text-destructive">{file.error}</div>