TextIn OCR Frontend前端开源组件库发布!

为什么开源 TextIn OCR Frontend 前端组件库?

在 TextIn 社群中,我们时常接到用户反馈,调取 API 进行票据等文件批量识别后,需要另行完成前端工程,实现比对环节。为助力用户节省工程成本,TextIn 团队正式开源 OCR Frontend 前端组件库,便于用户搭建前端界面,完成识别结果审核,提升使用体验。

此外,对于有翻译、校对等需求的开发者,也可灵活应用开源组件库,进行二次开发。

TextIn OCR Frontend 是一个用于展示 Textin 识别结果的 React 组件库,支持文件预览、坐标回显和结果展示。

组件库适配票据类解析结果(key-value)的展示,前端界面案例见下图。

特性

  • 📄 支持图片和 PDF 文件预览

  • 🎯 支持文本区域坐标回显和高亮

  • 🔄 预览区域和识别结果双向联动

  • 📊 支持 JSON 格式结果展示

  • 🎨 TODO:可自定义样式和主题

安装与使用

拉取项目

复制代码
git clone https://github.com/intsig-textin/textin-ocr-frontend.git

npm install textin-ocr-frontend
# 或
yarn add textin-ocr-frontend

快速开始

复制代码
import { FilePreview, ResultView, JsonView } from "textin-ocr-frontend";

functionApp() {
return (
    <div style={{ display: "flex" }}>
      <div style={{ flex: 1 }}>
        <FilePreview src="path/to/image.jpg" rects={rects} pages={pages} />
      </div>
      <div style={{ flex: 1 }}>
        <ResultView resultList={resultList} />
      </div>
    </div>
  );
}

组件说明

1. FilePreview 文件预览组件

文件预览组件,支持 PDF 和图片预览,支持缩放、旋转、分页等功能。

Props

2. ResultView 结果展示组件

结果展示组件,支持表格和列表两种展示方式。

Props

3. MarkLayer 标注层组件

标注层组件,用于在图片显示标注框。

Props

4. JsonView JSON 展示组件

JSON 数据展示组件,用于格式化展示 JSON 数据。 本项目 JSON 数据采用react-json-view库渲染,API 保持一致,详细属性可参考其官方文档。

Props

API Interface 定义

PDFSrc

PDF 文件源配置

复制代码
interface DocumentInitParameters {
  [key: string]: any;
  url?: string | URL;
  data?: TypedArray | ArrayBuffer | Array<number> | string;
  httpHeaders?: Object;
  withCredentials?: boolean;
  password?: string;
  length?: boolean;
}

type PDFSrc = DocumentInitParameters;
IRectItem

标注框数据

复制代码
interface IRectItem {
  [key: string]: any;
  key?: string;
  type?: string;
  rect_type?: string;
  uid: string;
  parent_uid?: string;
  content_id: string;
  parent_id?: string;
  position: number[];
  angle?: number;
  render_text?: string;
}
IPageItem

页面数据

复制代码
interface IPageItem {
  page_number: number;
  duration: number;
  ppi: number;
  width: number;
  height: number;
  angle?: number;
}
IResultListItem

结果列表项

复制代码
interface IResultListItem extends IRectItem {
  type: string;
  description: string;
  no: number;
  list: IFieldItem[];
  flightList: IFieldItem[][];
  page_id?: number;
}
IFieldItem

字段项

复制代码
interface IFieldItem extends IOriginFieldItem {
  uid: string;
  parent_uid?: string;
}

interface IOriginFieldItem {
  key: string;
  type?: string;
  value: string;
  description: string;
  position: number[];
}
ToolbarOptions

工具栏配置

复制代码
interface ToolbarOptions {
  tools: PreviewToolItem[];
}

interface PreviewToolItem {
  Icon: React.ComponentType<any>;
  onClick: () => void;
  type: string;
  disabled?: boolean;
}
PreviewToolItem

工具栏配置项

复制代码
interface PreviewToolItem {
  Icon: React.ComponentType<any>;  // 工具栏图标组件
  onClick: () => void;            // 点击事件处理函数
  type: string;                   // 工具类型
  disabled?: boolean;             // 是否禁用
}

Hooks

useContentLinkage

用于实现预览区域和识别结果的双向联动。

复制代码
const { activeContentId, activeParentContentId, registerLinkage } =
  useContentLinkage({
    viewContainerRef,
    resultContainerRef,
  });

参数

返回值

usePDFMarkLayer

用于在 PDF 文档上实现标注层功能。

复制代码
const { run } = usePDFMarkLayer({
  containerRef,
  pdfViewerRef,
  rects,
  pages,
  dpi,
  activeContentId,
  showMark,
});

参数

返回值

usePreviewTool

用于实现预览工具栏功能,包括缩放、旋转和 1:1 还原。

复制代码
const { tools, scale, rotate, position, onMouseDown, onWheel, resizeScale } =
  usePreviewTool({
    viewContainerRef,
    viewRef,
    toolbarOptions,
  });

参数

返回值

示例

图片示例
复制代码
import { useLayoutEffect, useRef, useState } from "react";
import FilePreview from "../components/FilePreview";
import { RadioGroup } from "../components/RadioGroup";
import ResultView from "../components/ResultView";
import { imageExample } from "./data";
import JsonView from "../components/JsonView";
import { useContentLinkage } from "../hooks/useContentLinkage";

export default functionImageExample() {
  const [resultTab, setResultTab] = useState("text");
  const viewContainerRef = useRef<HTMLElement | null>(null);
  const resultContainerRef = useRef<HTMLElement | null>(null);

  const { activeParentContentId, activeContentId, registerLinkage } =
    useContentLinkage({
      viewContainerRef,
      resultContainerRef,
    });

  useLayoutEffect(() => {
    registerLinkage();
  }, []);

return (
    <div
      style={{
        display: "flex",
        width: "100%",
        height: "calc(100% - 80px)",
        padding: 16,
        textAlign: "center",
        columnGap: 32,
      }}
    >
      <div style={{ flex: 4, minWidth: "40%", maxWidth: "60%" }}>
        <div style={{ margin: 16 }}>预览</div>
        <div
          style={{
            position: "relative",
            overflow: "hidden",
            maxWidth: "100%",
            maxHeight: "calc(100% - 80px)",
            height: "calc(100% - 80px)",
          }}
        >
          <FilePreview
            src={imageExample.src}
            rects={imageExample.rects}
            pages={imageExample.pages}
            getContainerRef={viewContainerRef}
            activeContentId={activeContentId}
          />
        </div>
      </div>
      <div style={{ flex: 6, minWidth: "40%", maxWidth: "60%" }}>
        <RadioGroup
          style={{ margin: 16 }}
          optionStyle={{ flex: 1 }}
          type="line"
          options={[
            { label: "识别结果", value: "text" },
            { label: "JSON结果", value: "json" },
          ]}
          value={resultTab}
          onChange={setResultTab}
        />
        {resultTab === "text" && (
          <ResultView
            style={{
              position: "relative",
              overflow: "auto",
              maxWidth: "100%",
              maxHeight: "calc(100% - 80px)",
              height: "calc(100% - 80px)",
            }}
            // resultList={example2.result}
            resultList={imageExample.result}
            getContainerRef={resultContainerRef}
            activeContentId={activeContentId}
            activeParentContentId={activeParentContentId}
          />
        )}
        {resultTab === "json" && (
          <JsonView
            style={{
              padding: "0 16px",
              height: "calc(100% - 80px)",
              overflow: "auto",
            }}
            src={imageExample.json}
          />
        )}
      </div>
    </div>
  );
}
PDF 示例
复制代码
import { useLayoutEffect, useRef, useState } from "react";
import FilePreview from "../components/FilePreview";
import { RadioGroup } from "../components/RadioGroup";
import ResultView from "../components/ResultView";
import { pdfExample } from "./data";
import JsonView from "../components/JsonView";
import { useContentLinkage } from "../hooks/useContentLinkage";

export default functionPDFExample() {
  const [resultTab, setResultTab] = useState("text");
  const viewContainerRef = useRef<HTMLElement | null>(null);
  const resultContainerRef = useRef<HTMLElement | null>(null);

  const { activeParentContentId, activeContentId, registerLinkage } =
    useContentLinkage({
      viewContainerRef,
      resultContainerRef,
    });

  useLayoutEffect(() => {
    registerLinkage();
  }, []);

return (
    <div
      style={{
        display: "flex",
        width: "100%",
        height: "calc(100vh - 100px)",
        padding: 16,
        textAlign: "center",
        columnGap: 32,
      }}
    >
      <div style={{ flex: 4, minWidth: "40%", maxWidth: "60%" }}>
        <div style={{ margin: 16 }}>预览</div>
        <div
          style={{
            position: "relative",
            overflow: "hidden",
            maxWidth: "100%",
            maxHeight: "calc(100% - 80px)",
            height: "calc(100% - 80px)",
          }}
        >
          <FilePreview
            src={{
              url: pdfExample.src,
            }}
            rects={pdfExample.rects}
            pages={pdfExample.pages}
            getContainerRef={viewContainerRef}
            activeContentId={activeContentId}
          />
        </div>
      </div>
      <div style={{ flex: 6, minWidth: "40%", maxWidth: "60%" }}>
        <RadioGroup
          style={{ margin: 16 }}
          optionStyle={{ flex: 1 }}
          type="line"
          options={[
            { label: "识别结果", value: "text" },
            { label: "JSON结果", value: "json" },
          ]}
          value={resultTab}
          onChange={setResultTab}
        />
        {resultTab === "text" && (
          <ResultView
            style={{
              position: "relative",
              overflow: "auto",
              maxWidth: "100%",
              maxHeight: "calc(100% - 80px)",
              height: "calc(100% - 80px)",
            }}
            resultList={pdfExample.result}
            getContainerRef={resultContainerRef}
            activeContentId={activeContentId}
            activeParentContentId={activeParentContentId}
          />
        )}
        {resultTab === "json" && (
          <JsonView
            style={{
              padding: "0 16px",
              height: "calc(100% - 80px)",
              overflow: "auto",
            }}
            src={pdfExample.json}
          />
        )}
      </div>
    </div>
  );
}

二次开发

项目基于 vite 和 react 构建,您可将该项目 fork 到本地自主扩展:

拉取项目

复制代码
git clone https://github.com/intsig-textin/textin-ocr-frontend.git

安装依赖

复制代码
npm install

启动项目

复制代码
npm run dev

浏览器访问 http://localhost:5173/

在线预览前端界面:https://cc.co/16YSTY

以上为 TextIn OCR Frontend 开源组件库当前版本介绍。根据规划,组件库将持续迭代,实现:

  • 组件支持更多自定义配置、样式覆盖等特性

  • 支持可编辑、复制、导出结果

  • 支持更多复杂类型识别结果展示

在线体验https://cc.co/16YSTY

相关推荐
Xf3n1an35 分钟前
html语法
前端·html
张拭心36 分钟前
亚马逊 AI IDE Kiro “狙击”Cursor?实测心得
前端·ai编程
批量小王子1 小时前
2025-07-15通过边缘线检测图像里的主体有没有出血
人工智能·opencv·计算机视觉
烛阴1 小时前
为什么你的Python项目总是混乱?层级包构建全解析
前端·python
@大迁世界1 小时前
React 及其生态新闻 — 2025年6月
前端·javascript·react.js·前端框架·ecmascript
zyhomepage1 小时前
科技的成就(六十九)
开发语言·网络·人工智能·科技·内容运营
停走的风1 小时前
(李宏毅)deep learning(五)--learning rate
人工智能·深度学习·机器学习
fishjar1002 小时前
LLaMA-Factory安装部署
人工智能·深度学习
feifeikon2 小时前
模型篇(Bert llama deepseek)
人工智能·深度学习·自然语言处理
红尘散仙2 小时前
Rust 终端 UI 开发新玩法:用 Ratatui Kit 轻松打造高颜值 CLI
前端·后端·rust