Files
wehub-resource-sync c48612c494
Docs Build / Build docs site (push) Waiting to run
Publish @openmaic packages / Build, validate & publish (push) Waiting to run
CI / E2E Tests (push) Has been cancelled
CI / Lint, Typecheck & Unit Tests (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:03:23 +08:00

202 lines
5.8 KiB
TypeScript

import type { ParsedPdfContent } from '@/lib/types/pdf';
import type {
DocumentArtifact,
DocumentAsset,
DocumentBlock,
DocumentExtractorInput,
} from './types';
function positionToBbox(position?: {
x: number;
y: number;
width: number;
height: number;
}): DocumentBlock['bbox'] {
if (!position) return undefined;
return position;
}
function bboxToPosition(
bbox?: DocumentBlock['bbox'],
): { x: number; y: number; width: number; height: number } | undefined {
if (!bbox) return undefined;
return bbox;
}
function dataUrlMimeType(src: string): string | undefined {
if (!src.startsWith('data:')) return undefined;
const metadataEnd = src.indexOf(',');
const header = src.slice(5, metadataEnd === -1 ? undefined : metadataEnd);
const mime = header.split(';')[0];
return mime || undefined;
}
function isParsedPdfContent(value: unknown): value is ParsedPdfContent {
return (
!!value &&
typeof value === 'object' &&
'text' in value &&
'images' in value &&
Array.isArray((value as ParsedPdfContent).images)
);
}
function pdfLayoutType(value: unknown): NonNullable<ParsedPdfContent['layout']>[number]['type'] {
return value === 'title' ||
value === 'text' ||
value === 'image' ||
value === 'table' ||
value === 'formula'
? value
: 'text';
}
function isMinerUMarkdownParser(parser: unknown): boolean {
return parser === 'mineru' || parser === 'mineru-cloud';
}
export function parsedPdfToDocumentArtifact(
parsed: ParsedPdfContent,
input: DocumentExtractorInput,
): DocumentArtifact {
const blocks: DocumentBlock[] = [];
if (parsed.text) {
blocks.push({
id: 'document-text',
type: isMinerUMarkdownParser(parsed.metadata?.parser) ? 'markdown' : 'text',
text: parsed.text,
});
}
parsed.tables?.forEach((table, index) => {
blocks.push({
id: `table_${index + 1}`,
type: 'table',
text: table.caption,
pageNumber: table.page,
metadata: { data: table.data, caption: table.caption },
});
});
parsed.formulas?.forEach((formula, index) => {
blocks.push({
id: `formula_${index + 1}`,
type: 'formula',
text: formula.latex,
pageNumber: formula.page,
bbox: positionToBbox(formula.position),
});
});
parsed.layout?.forEach((layout, index) => {
blocks.push({
id: `layout_${index + 1}`,
type: 'layout',
text: layout.content,
pageNumber: layout.page,
bbox: positionToBbox(layout.position),
metadata: { layoutType: layout.type },
});
});
const pdfImages = parsed.metadata?.pdfImages;
const assets: DocumentAsset[] =
pdfImages && pdfImages.length > 0
? pdfImages.map((image) => ({
id: image.id,
type: 'image',
mimeType: dataUrlMimeType(image.src),
data: image.src,
pageNumber: image.pageNumber,
description: image.description,
width: image.width,
height: image.height,
}))
: parsed.images.map((src, index) => ({
id: `img_${index + 1}`,
type: 'image',
mimeType: dataUrlMimeType(src),
data: src,
}));
return {
metadata: {
fileName: input.fileName ?? parsed.metadata?.fileName,
fileSize: input.fileSize ?? parsed.metadata?.fileSize,
mimeType: input.mimeType,
pageCount: parsed.metadata?.pageCount,
providerId: input.config.providerId,
processingTime: parsed.metadata?.processingTime,
},
blocks,
assets,
providerRaw: parsed,
};
}
export function documentArtifactToParsedPdfContent(artifact: DocumentArtifact): ParsedPdfContent {
const raw = isParsedPdfContent(artifact.providerRaw) ? artifact.providerRaw : undefined;
const rawMetadata = raw?.metadata;
const text = artifact.blocks
.filter((block) => block.type === 'text' || block.type === 'markdown')
.map((block) => block.text)
.filter(Boolean)
.join('\n\n');
const imageAssets = artifact.assets.filter(
(asset): asset is DocumentAsset & { data: string } =>
asset.type === 'image' && typeof asset.data === 'string',
);
const images = imageAssets.map((asset) => asset.data as string);
const imageMapping = Object.fromEntries(imageAssets.map((asset) => [asset.id, asset.data]));
const tables = artifact.blocks
.filter((block) => block.type === 'table')
.map((block) => ({
page: block.pageNumber ?? 0,
data: Array.isArray(block.metadata?.data) ? (block.metadata.data as string[][]) : [],
caption: typeof block.metadata?.caption === 'string' ? block.metadata.caption : block.text,
}));
const formulas = artifact.blocks
.filter((block) => block.type === 'formula' && block.text)
.map((block) => ({
page: block.pageNumber ?? 0,
latex: block.text as string,
position: bboxToPosition(block.bbox),
}));
const layout = artifact.blocks
.filter((block) => block.type === 'layout')
.map((block) => ({
page: block.pageNumber ?? 0,
type: pdfLayoutType(block.metadata?.layoutType),
content: block.text ?? '',
position: bboxToPosition(block.bbox),
}));
return {
text,
images,
...(tables.length > 0 ? { tables } : {}),
...(formulas.length > 0 ? { formulas } : {}),
...(layout.length > 0 ? { layout } : {}),
metadata: {
...rawMetadata,
fileName: artifact.metadata.fileName,
fileSize: artifact.metadata.fileSize,
pageCount: artifact.metadata.pageCount ?? rawMetadata?.pageCount ?? 0,
parser: rawMetadata?.parser,
processingTime: artifact.metadata.processingTime ?? rawMetadata?.processingTime,
imageMapping,
pdfImages: imageAssets.map((asset) => ({
id: asset.id,
src: asset.data as string,
pageNumber: asset.pageNumber ?? 0,
description: asset.description,
width: asset.width,
height: asset.height,
})),
},
};
}