first commit

This commit is contained in:
Beyhan Oğur
2026-04-26 21:52:23 +03:00
commit 880f412e2c
2662 changed files with 866266 additions and 0 deletions

View File

@@ -0,0 +1,167 @@
import { useState, useEffect } from "react";
import { BifrostOCRResponse, OCRDocument } from "@/lib/types/logs";
import { Button } from "@/components/ui/button";
import { CodeEditor } from "@/components/ui/codeEditor";
import { ChevronLeft, ChevronRight, FileText } from "lucide-react";
function getImageSrc(b64: string): string {
if (b64.startsWith("/9j/")) return `data:image/jpeg;base64,${b64}`;
if (b64.startsWith("iVBOR")) return `data:image/png;base64,${b64}`;
if (b64.startsWith("UklGR")) return `data:image/webp;base64,${b64}`;
if (b64.startsWith("R0lGO")) return `data:image/gif;base64,${b64}`;
return `data:image/png;base64,${b64}`;
}
interface OCRViewProps {
ocrInput?: OCRDocument;
ocrOutput?: BifrostOCRResponse;
}
export default function OCRView({ ocrInput, ocrOutput }: OCRViewProps) {
const pages = ocrOutput?.pages ?? [];
const totalPages = pages.length;
const [currentIndex, setCurrentIndex] = useState(0);
useEffect(() => {
if (totalPages === 0) {
setCurrentIndex(0);
} else {
setCurrentIndex((prev) => Math.min(prev, totalPages - 1));
}
}, [totalPages]);
const goToPrevious = () => setCurrentIndex((prev) => (prev === 0 ? totalPages - 1 : prev - 1));
const goToNext = () => setCurrentIndex((prev) => (prev === totalPages - 1 ? 0 : prev + 1));
const currentPage = pages[currentIndex] ?? null;
const pageImages = currentPage?.images?.filter((img) => img.image_base64) ?? [];
return (
<div className="space-y-4">
{/* OCR Input */}
{ocrInput && (
<div className="w-full rounded-sm border">
<div className="flex items-center gap-2 border-b px-6 py-2 text-sm font-medium">
<FileText className="h-4 w-4" />
OCR Input
</div>
<div className="space-y-4 p-6">
<div>
<div className="text-muted-foreground mb-2 text-xs font-medium">TYPE</div>
<div className="font-mono text-xs">{ocrInput.type === "document_url" ? "Document" : "Image"}</div>
</div>
{(ocrInput.document_url || ocrInput.image_url) && (
<div>
<div className="text-muted-foreground mb-2 text-xs font-medium">
{ocrInput.type === "document_url" ? "DOCUMENT URL" : "IMAGE URL"}
</div>
<div className="font-mono text-xs break-all">{ocrInput.document_url ?? ocrInput.image_url}</div>
</div>
)}
</div>
</div>
)}
{/* OCR Output */}
{ocrOutput && (
<div className="w-full rounded-sm border">
<div className="flex items-center gap-2 border-b px-6 py-2 text-sm font-medium">
<FileText className="h-4 w-4" />
OCR Output
</div>
<div className="space-y-4 p-6">
{ocrOutput.usage_info && (
<div className="grid grid-cols-3 gap-3">
<div className="space-y-1">
<div className="text-muted-foreground text-xs font-medium">PAGES PROCESSED</div>
<div className="font-mono text-xs">{ocrOutput.usage_info.pages_processed}</div>
</div>
<div className="space-y-1">
<div className="text-muted-foreground text-xs font-medium">DOCUMENT SIZE</div>
<div className="font-mono text-xs">{(ocrOutput.usage_info.doc_size_bytes / 1024).toFixed(1)} KB</div>
</div>
</div>
)}
{ocrOutput.document_annotation && (
<div>
<div className="text-muted-foreground mb-2 text-xs font-medium">DOCUMENT ANNOTATION</div>
<div className="font-mono text-xs">{ocrOutput.document_annotation}</div>
</div>
)}
{currentPage && (
<>
{currentPage.dimensions && (
<div className="grid grid-cols-3 gap-3">
<div className="space-y-1">
<div className="text-muted-foreground text-xs font-medium">DIMENSIONS</div>
<div className="font-mono text-xs">{currentPage.dimensions.width} × {currentPage.dimensions.height}px</div>
</div>
<div className="space-y-1">
<div className="text-muted-foreground text-xs font-medium">DPI</div>
<div className="font-mono text-xs">{currentPage.dimensions.dpi}</div>
</div>
</div>
)}
{currentPage.markdown ? (
<div>
<div className="text-muted-foreground mb-2 text-xs font-medium">MARKDOWN</div>
<CodeEditor
className="z-0 w-full"
shouldAdjustInitialHeight
maxHeight={400}
wrap
code={currentPage.markdown}
lang="markdown"
readonly
options={{
scrollBeyondLastLine: false,
lineNumbers: "off",
alwaysConsumeMouseWheel: false,
}}
/>
</div>
) : (
<div className="text-muted-foreground font-mono text-xs">No text extracted from this page.</div>
)}
{pageImages.length > 0 && (
<div>
<div className="text-muted-foreground mb-2 text-xs font-medium">EXTRACTED IMAGES ({pageImages.length})</div>
<div className="flex flex-wrap gap-2">
{pageImages.map((img) => (
<img
key={img.id}
src={getImageSrc(img.image_base64!)}
alt={`Image ${img.id}`}
className="max-h-48 max-w-48 rounded border object-contain"
/>
))}
</div>
</div>
)}
{totalPages > 1 && (
<div className="mt-3 flex items-center justify-center gap-4">
<Button variant="outline" size="sm" onClick={goToPrevious} aria-label="Previous page" title="Previous page" data-testid="ocr-view-pagination-prev-button">
<ChevronLeft className="h-4 w-4" />
</Button>
<span className="text-muted-foreground text-sm">
Page {currentIndex + 1} / {totalPages}
</span>
<Button variant="outline" size="sm" onClick={goToNext} aria-label="Next page" title="Next page" data-testid="ocr-view-pagination-next-button">
<ChevronRight className="h-4 w-4" />
</Button>
</div>
)}
</>
)}
</div>
</div>
)}
</div>
);
}