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

相关推荐
袁煦丞37 分钟前
OpenKylin 桌面系统,开源自由,跨界协作:cpolar内网穿透实验室第624个成功挑战
前端·程序员·远程工作
excel41 分钟前
JavaScript 尾递归优化详解
前端
2 小时前
从Redmine及Jira到Codes:为什么我换了项目管理软件?
开源·jira·项目管理软件·零代码开发·redmine·免费项目管理软件·开源项目管理软件
sali-tec3 小时前
C# 基于halcon的视觉工作流-章29-边缘提取-亚像素
开发语言·图像处理·算法·计算机视觉·c#
Jiyoungxx5 小时前
DAY 39 图像数据与显存
人工智能·深度学习
一车小面包6 小时前
特征工程--机器学习
人工智能·机器学习·特征工程
tager7 小时前
🔥3行代码搞定全局代理!告别插件依赖的极简方案
前端·fiddler·charles
gnip8 小时前
axios 拦截器实现用户无感刷新 access_token
前端
程序员码歌8 小时前
【零代码AI编程实战】AI灯塔导航-成果展示篇
前端·ai编程·cursor
gnip8 小时前
前端实现即时通讯,常用的技术
前端