feat: add document OCR and offline model archives
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import {
|
||||
defaultDocumentParsingSettings
|
||||
} from './document-parsing-settings-store'
|
||||
import { DocumentParsingService } from './document-parsing-service'
|
||||
|
||||
function createPdfFixture(text: string): Buffer {
|
||||
const stream = `BT /F1 18 Tf 50 100 Td (${text}) Tj ET`
|
||||
const objects = [
|
||||
'<< /Type /Catalog /Pages 2 0 R >>',
|
||||
'<< /Type /Pages /Kids [3 0 R] /Count 1 >>',
|
||||
'<< /Type /Page /Parent 2 0 R /MediaBox [0 0 300 200] /Resources << /Font << /F1 4 0 R >> >> /Contents 5 0 R >>',
|
||||
'<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>',
|
||||
`<< /Length ${Buffer.byteLength(stream)} >>\nstream\n${stream}\nendstream`
|
||||
]
|
||||
let content = '%PDF-1.4\n'
|
||||
const offsets = [0]
|
||||
for (const [index, object] of objects.entries()) {
|
||||
offsets.push(Buffer.byteLength(content))
|
||||
content += `${index + 1} 0 obj\n${object}\nendobj\n`
|
||||
}
|
||||
const xrefOffset = Buffer.byteLength(content)
|
||||
content += `xref\n0 ${objects.length + 1}\n`
|
||||
content += '0000000000 65535 f \n'
|
||||
content += offsets
|
||||
.slice(1)
|
||||
.map((offset) => `${String(offset).padStart(10, '0')} 00000 n \n`)
|
||||
.join('')
|
||||
content += `trailer\n<< /Size ${objects.length + 1} /Root 1 0 R >>\n`
|
||||
content += `startxref\n${xrefOffset}\n%%EOF\n`
|
||||
return Buffer.from(content)
|
||||
}
|
||||
|
||||
function createService(overrides?: {
|
||||
settings?: Partial<typeof defaultDocumentParsingSettings>
|
||||
}) {
|
||||
const settings = {
|
||||
...defaultDocumentParsingSettings,
|
||||
...overrides?.settings
|
||||
}
|
||||
const recognize = vi.fn(async () => ({
|
||||
requestId: crypto.randomUUID(),
|
||||
sections: [
|
||||
{
|
||||
locator: '第 1 页',
|
||||
content: '扫描件识别正文',
|
||||
confidence: 0.93
|
||||
}
|
||||
],
|
||||
pageCount: 1,
|
||||
warnings: []
|
||||
}))
|
||||
const service = new DocumentParsingService(
|
||||
{
|
||||
get: vi.fn(async () => settings),
|
||||
update: vi.fn(async () => settings)
|
||||
} as never,
|
||||
{
|
||||
getStatus: vi.fn(async () => ({
|
||||
id: 'pp-ocrv6-tiny',
|
||||
displayName: 'PP-OCRv6 Tiny',
|
||||
available: true,
|
||||
verified: true,
|
||||
runtime: 'onnxruntime-web-wasm',
|
||||
detail: '可用'
|
||||
}))
|
||||
} as never,
|
||||
{ recognize } as never
|
||||
)
|
||||
return { recognize, service }
|
||||
}
|
||||
|
||||
describe('DocumentParsingService', () => {
|
||||
it('keeps useful PDF text local without invoking OCR', async () => {
|
||||
const { recognize, service } = createService()
|
||||
|
||||
const parsed = await service.parse(
|
||||
'native.pdf',
|
||||
createPdfFixture('Native PDF body text'),
|
||||
'knowledge-index'
|
||||
)
|
||||
|
||||
expect(parsed.content).toContain('Native PDF body text')
|
||||
expect(parsed.sections[0]?.method).toBe('native')
|
||||
expect(recognize).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('uses OCR for a PDF without useful text', async () => {
|
||||
const { recognize, service } = createService()
|
||||
|
||||
const parsed = await service.parse(
|
||||
'scan.pdf',
|
||||
createPdfFixture(''),
|
||||
'chat-attachment'
|
||||
)
|
||||
|
||||
expect(parsed.content).toBe('扫描件识别正文')
|
||||
expect(parsed.sections).toEqual([
|
||||
{
|
||||
locator: '第 1 页',
|
||||
content: '扫描件识别正文',
|
||||
method: 'ocr',
|
||||
confidence: 0.93
|
||||
}
|
||||
])
|
||||
expect(recognize).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
fileName: 'scan.pdf',
|
||||
modelId: 'pp-ocrv6-tiny',
|
||||
mimeType: 'application/pdf',
|
||||
pageNumbers: [1]
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('does not use OCR in a fast-text workflow', async () => {
|
||||
const { recognize, service } = createService({
|
||||
settings: { chatWorkflow: 'fast-text' }
|
||||
})
|
||||
|
||||
await expect(
|
||||
service.parse(
|
||||
'scan.pdf',
|
||||
createPdfFixture(''),
|
||||
'chat-attachment'
|
||||
)
|
||||
).rejects.toThrow('未启用 OCR')
|
||||
expect(recognize).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user