'use client' import { useState, useRef, useEffect, useCallback } from "react" import Link from "next/link" interface OcrBlock { page: number ocr_result: Array< [ [[number, number], [number, number], [number, number], [number, number]], [string, number], ] > } interface OcrResponse { resultcode: number message: string data: OcrBlock[] } interface BoundingBox { left: number top: number width: number height: number } interface TextBlock { text: string confidence: number box: BoundingBox } export default function OcrPage() { const [file, setFile] = useState(null) const [imageUrl, setImageUrl] = useState(null) const [isPdf, setIsPdf] = useState(false) const [ocrData, setOcrData] = useState(null) const [loading, setLoading] = useState(false) const [error, setError] = useState("") const [dragOver, setDragOver] = useState(false) const [hoveredIndex, setHoveredIndex] = useState(null) const [copied, setCopied] = useState(false) const imageRef = useRef(null) const containerRef = useRef(null) const fileInputRef = useRef(null) const [scale, setScale] = useState({ x: 1, y: 1 }) const [imgLoaded, setImgLoaded] = useState(false) const resetState = useCallback(() => { setOcrData(null) setError("") setCopied(false) setHoveredIndex(null) }, []) const handleFile = useCallback((f: File) => { resetState() setFile(f) setLoading(true) const isImage = f.type.startsWith("image/") const isPdfFile = f.type === "application/pdf" setIsPdf(isPdfFile) if (isImage) { if (imageUrl) URL.revokeObjectURL(imageUrl) const url = URL.createObjectURL(f) setImageUrl(url) setImgLoaded(false) } else { setImageUrl(null) setImgLoaded(false) } const formData = new FormData() formData.append("file", f) fetch("/api/ocr", { method: "POST", body: formData, }) .then((res) => res.json().catch(() => null)) .then((data: OcrResponse | null) => { if (!data || data.resultcode !== 200) { throw new Error(data?.message || "OCR 识别失败") } setOcrData(data) }) .catch((err) => { setError(err.message || "OCR 识别失败,请稍后重试") }) .finally(() => { setLoading(false) }) }, [resetState, imageUrl]) useEffect(() => { return () => { if (imageUrl) URL.revokeObjectURL(imageUrl) } }, [imageUrl]) useEffect(() => { const handlePaste = (e: ClipboardEvent) => { const items = e.clipboardData?.items if (!items) return for (const item of items) { if (item.type.startsWith("image/")) { e.preventDefault() const blob = item.getAsFile() if (blob) { handleFile(blob) } break } } } document.addEventListener("paste", handlePaste) return () => document.removeEventListener("paste", handlePaste) }, [handleFile]) const handleDrop = useCallback( (e: React.DragEvent) => { e.preventDefault() setDragOver(false) const f = e.dataTransfer.files[0] if (f) handleFile(f) }, [handleFile], ) const handleDragOver = useCallback((e: React.DragEvent) => { e.preventDefault() setDragOver(true) }, []) const handleDragLeave = useCallback((e: React.DragEvent) => { e.preventDefault() setDragOver(false) }, []) const handleFileSelect = useCallback( (e: React.ChangeEvent) => { const f = e.target.files?.[0] if (f) handleFile(f) }, [handleFile], ) const handleImageLoad = useCallback(() => { const img = imageRef.current if (!img) return setScale({ x: img.clientWidth / img.naturalWidth, y: img.clientHeight / img.naturalHeight, }) setImgLoaded(true) }, []) const allTextBlocks: TextBlock[] = ocrData?.data?.flatMap((page) => page.ocr_result.map(([coords, [text, confidence]]) => { const xs = coords.map((c) => c[0]) const ys = coords.map((c) => c[1]) const left = Math.min(...xs) const top = Math.min(...ys) const right = Math.max(...xs) const bottom = Math.max(...ys) return { text, confidence, box: { left, top, width: right - left, height: bottom - top, }, } }), ) ?? [] const handleCopyAll = useCallback(async () => { const text = allTextBlocks.map((b) => b.text).join("\n") await navigator.clipboard.writeText(text) setCopied(true) setTimeout(() => setCopied(false), 2000) }, [allTextBlocks]) const handleCopyBlock = useCallback(async (text: string) => { await navigator.clipboard.writeText(text) setCopied(true) setTimeout(() => setCopied(false), 2000) }, []) return (

OCR 文字识别

上传图片或截图粘贴,自动识别图片中的文字内容

fileInputRef.current?.click()} > {loading ? (

正在识别中...

) : (

点击上传或拖拽文件到此区域

支持图片格式 (PNG, JPG) 或 Ctrl+V 粘贴截图

)}
{error && (
{error}
)} {(imageUrl || isPdf) && ocrData && (
{imageUrl && (
图片预览
上传图片 e.preventDefault()} /> {imgLoaded && allTextBlocks.map((block, idx) => (
))}
)} {isPdf && !imageUrl && (
PDF 文件

{file?.name}

)}
识别结果 ({ocrData?.message || ""})
{allTextBlocks.length === 0 ? (

未识别到文字

) : ( allTextBlocks.map((block, idx) => (
setHoveredIndex(idx)} onMouseLeave={() => setHoveredIndex(null)} onClick={() => handleCopyBlock(block.text)} > {idx + 1}

{block.text}

置信度: {(block.confidence * 100).toFixed(1)}%

)) )}
)} {!file && !loading && (

图片上传

支持 PNG、JPG 等常见图片格式

截图粘贴

使用 Ctrl+V 快速粘贴截图识别

拖拽上传

拖拽文件到上传区域即可识别

)}
AI
江苏冲浪软件科技有限公司
© 2026 江苏冲浪软件科技有限公司 · AI 智能体广场
) }