Unverified Commit bcbbee8c authored by Xiaomeng Zhao's avatar Xiaomeng Zhao Committed by GitHub
Browse files

Merge pull request #2622 from myhloli/dev

Dev
parents 3cc3f754 ced5a7b4
.textBtn {
background-image: none !important;
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background: linear-gradient(111deg, #0D53DE -21.44%, #5246FF 102%) !important;
background-clip: text !important;
-webkit-background-clip: text !important;
-webkit-text-fill-color: transparent !important;
height: 1.5rem !important;
font-weight: 600;
height: 280px !important;
width: 600px !important;
overflow: hidden;
}
.uploadText {
font-feature-settings: 'liga' off, 'clig' off;
font-family: "PingFang SC";
font-size: 18px;
font-style: normal;
font-weight: 600;
line-height: 24px; /* 133.333% */
background: linear-gradient(107deg, #38A0FF -24.14%, #0D53DE 30.09%, #5246FF 86.61%);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.uploadDescText {
font-size: 13px;
line-height: 20px;
font-weight: 400;
background: linear-gradient(107deg, rgba(18,19,22,0.6) -24.14%, rgba(18,19,22,0.6) 100.09% );
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-bottom: 1rem;
margin-top: 0.5rem;
}
.linearText {
font-size: 13px;
line-height: 20px;
font-weight: 400;
background: linear-gradient(107deg, rgba(18,19,22,0.6) -24.14%, rgba(18,19,22,0.6) 100.09% );
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
&-item {
font-weight: 400;
font-size: 13px;
line-height: 20px;
margin-right: 1rem;
background: linear-gradient(107deg, #38A0FF -24.14%, #0D53DE 30.09%, #5246FF 86.61%);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
&:hover {
background: #3477EB;
background: linear-gradient(107deg, #3477EB -24.14%, #3477EB 100.09% );
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
}
}
.uploadSection {
border-radius: 12px;
border: 1px dashed var(---Brand1-6, #0D53DE);
background: linear-gradient(180deg, rgba(92, 147, 255, 0.10) -130.23%, rgba(255, 255, 255, 1) 83.57%);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
filter: blur(0px);
height: 280px !important;
width: 600px !important;
&:hover {
background: linear-gradient(180deg, rgb(245, 248, 255) -130.23%, rgb(245, 248, 255) 83.57%);
}
}
.item {
border-radius: 12px;
border: 1px solid rgba(198, 217, 255, 0.20);
background: linear-gradient(155deg, rgba(92, 147, 255, 0.10) -13.23%, rgba(255, 255, 255, 0.00) 83.57%);
filter: blur(0px);
padding: 42px 20px;
}
.customPopover {
:global {
.ant-popover-content, .ant-popover-inner {
border-radius: 12px !important;
overflow: hidden;
box-shadow: 0px 8px 26px 0px rgba(0, 0, 0, 0.12);
}
.ant-popover-inner-content {
padding: 12px !important;
}
.ant-popover-arrow {
display: none !important;
}
}
}
import UploadBg from "@/assets/imgs/online.experience/file-upload-bg.svg";
import style from "./index.module.scss";
import { ExtractorUploadButton } from "../pdf-upload-button";
import { useNavigate } from "react-router-dom";
import cls from "classnames";
import { SubmitRes } from "@/api/extract";
import { Checkbox, Popover } from "antd";
import { useIntl } from "react-intl";
import IconFont from "@/components/icon-font";
import { ADD_TASK_LIST } from "@/constant/event";
import { useState } from "react";
const PdfUpload = () => {
const navigate = useNavigate();
const { formatMessage } = useIntl();
const [checked, setChecked] = useState(false);
const afterUploadSuccess = (data: SubmitRes) => {
navigate(`/OpenSourceTools/Extractor/PDF/${data?.id}`);
setTimeout(() => {
document.dispatchEvent(
new CustomEvent(ADD_TASK_LIST, {
detail: data,
})
);
}, 10);
};
const afterAsyncCheck = async () => {
return Promise.resolve(true);
};
return (
<div className="w-full h-full flex flex-col relative items-center relative">
<div className="w-full h-full flex flex-col relative justify-center items-center translate-y-[-60px] z-0">
<div className="mb-6 text-[1.5rem] text-[#121316] font-semibold">
{formatMessage({ id: "extractor.pdf.title" })}
</div>
<div className="mb-12 text-[1.25rem] text-center text-[#121316]/[0.8] leading-[1.5rem] max-w-[48rem]">
{formatMessage({ id: "extractor.pdf.subTitle" })}
</div>
<ExtractorUploadButton
accept=".pdf"
taskType="pdf"
afterUploadSuccess={afterUploadSuccess}
afterAsyncCheck={afterAsyncCheck}
extractType={"pdf"}
isOcr={checked}
text={
<div
className={cls(
style.uploadSection,
"border-[1px] border-dashed border-[#0D53DE] rounded-xl flex flex-col items-center justify-center"
)}
>
<img src={UploadBg} className="mb-4" />
<span
className={cls(style.uploadText, "text-[18px] leading-[20px]")}
>
{formatMessage({ id: "extractor.common.upload" })}
</span>
<span
className={cls(style.uploadDescText, "!mb-0 flex items-center")}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
}}
>
<Checkbox
className="mr-1"
checked={checked}
onClick={() => setChecked(!checked)}
/>
{formatMessage({ id: "extractor.pdf.ocr" })}
<Popover
content={
<div className="max-w-[20rem]">
{formatMessage({
id: "extractor.pdf.ocr.popover",
})}
</div>
}
placement="right"
showArrow={false}
overlayClassName={style.customPopover}
>
<IconFont
type="icon-QuestionCircleOutlined"
className="text-[#121316]/[0.6] ml-1 text-[16px] hover:text-[#0D53DE]"
/>
</Popover>
</span>
{/* <span className={cls(style.uploadDescText)}>
{formatMessage({ id: "extractor.common.pdf.upload.tip" })}
</span> */}
</div>
}
className={style.textBtn}
showIcon={false}
/>
</div>
<div className="absolute bottom-[1.5rem] text-[13px] text-[#121316]/[0.35] text-center leading-[20px] max-w-[64rem]">
{formatMessage({
id: "extractor.law",
})}
</div>
</div>
);
};
export default PdfUpload;
import { MD_DRIVE_PDF } from "@/constant/event";
import { message } from "antd";
import { TaskIdProgress, TaskIdResItem } from "@/api/extract";
import React, { useEffect, useRef } from "react";
import { useLatest } from "ahooks";
import {
DEFAULT_COLOR_SECTION,
PDF_COLOR_PICKER,
} from "@/constant/pdf-color-picker";
interface PDFViewerState {
page: number;
}
interface Bbox {
type: "title" | "text" | "discarded" | "image";
bbox: [number, number, number, number];
color: any;
}
interface ExtractLayerItem {
preproc_blocks: Bbox[];
page_idx: number;
page_size: [number, number];
discarded_blocks: Bbox[];
}
// func
const formatJson = (layerList: ExtractLayerItem[]) => {
return layerList?.map((i) => {
let bboxes = [] as { type: string; bbox: number[]; color: any }[];
i?.preproc_blocks?.forEach((item) => {
bboxes.push({
type: item.type,
bbox: item.bbox,
color: PDF_COLOR_PICKER?.[item.type] || DEFAULT_COLOR_SECTION,
});
});
i?.discarded_blocks?.forEach((item) => {
bboxes.push({
type: item.type,
bbox: item.bbox,
color: PDF_COLOR_PICKER?.[item.type] || DEFAULT_COLOR_SECTION,
});
});
return {
...i,
bboxes,
};
});
};
const PDFViewer = ({
taskInfo,
onChange,
}: {
taskInfo: TaskIdProgress & TaskIdResItem;
onChange: (state: PDFViewerState) => void;
}) => {
const iframeRef = useRef<HTMLIFrameElement>(null);
const _layerData = useLatest(taskInfo?.content);
const pdfUrl = taskInfo?.url;
const sendMessageToIframe = (type: string, message: any) => {
if (iframeRef.current) {
iframeRef.current.contentWindow?.postMessage(
{
type,
data: message,
},
import.meta.env.BASE_URL || "*"
);
}
};
useEffect(() => {
const handleMessage = (event: MessageEvent) => {
if (event?.data?.pageNum) {
const num = event?.data?.pageNum || 1;
sendMessageToIframe("pageChange", num);
}
if (event?.data?.pageNumDetail) {
const pageNumDetail = event?.data?.pageNumDetail || 1;
onChange?.({
page: pageNumDetail,
});
sendMessageToIframe("pageNumDetail", pageNumDetail);
}
if (event?.data?.status) {
const status = event?.data?.status;
if (status === "loaded") {
sendMessageToIframe(
"initExtractLayerData",
formatJson(_layerData?.current as any)
);
sendMessageToIframe("title", "");
}
}
if (event?.data?.error) {
message?.error(event?.data?.error);
}
};
window.addEventListener("message", handleMessage);
return () => {
window.removeEventListener("message", handleMessage);
};
}, []);
useEffect(() => {
const handlePageChange = ({ detail }: CustomEvent) => {
sendMessageToIframe("setPage", detail + 1);
};
document.addEventListener(MD_DRIVE_PDF, handlePageChange as EventListener);
return () => {
document.removeEventListener(
MD_DRIVE_PDF,
handlePageChange as EventListener
);
};
}, []);
return (
<>
{pdfUrl ? (
<iframe
ref={iframeRef}
className="w-full border-0 h-full"
src={`${
import.meta.env.BASE_URL
}pdfjs-dist/web/viewer.html?file=${encodeURIComponent(pdfUrl)}`}
></iframe>
) : null}
</>
);
};
export default PDFViewer;
/*light*/
// 自定义滚动跳
.scrollBar {
// 火狐
scrollbar-color: #EBECF0 transparent;
scrollbar-width: thin;
// 定义滚动条高宽及背景 高宽分别对应横竖滚动条的尺寸
&::-webkit-scrollbar {
width: 4px;
height: 6px;
background-color: transparent;
}
// 定义滚动条轨道 内阴影+圆角
&::-webkit-scrollbar-track {
background-color: #fff;
border-radius: 10px;
box-shadow: transparent;
}
// 定义滑块 内阴影+圆角
&::-webkit-scrollbar-thumb {
background: #EBECF0;
border-radius: 10px;
box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.6);
}
}
.mdViewerWrap {
font-size: 10px;
}
.mdViewerWrap {
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
margin: 0;
color: #1f2328;
background-color: #ffffff;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans",
Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
font-size: 16px;
line-height: 1.5;
word-wrap: break-word;
scroll-behavior: auto;
}
.mdViewerWrap .octicon {
display: inline-block;
fill: currentColor;
vertical-align: text-bottom;
}
.mdViewerWrap h1:hover .anchor .octicon-link:before,
.mdViewerWrap h2:hover .anchor .octicon-link:before,
.mdViewerWrap h3:hover .anchor .octicon-link:before,
.mdViewerWrap h4:hover .anchor .octicon-link:before,
.mdViewerWrap h5:hover .anchor .octicon-link:before,
.mdViewerWrap h6:hover .anchor .octicon-link:before {
width: 16px;
height: 16px;
content: " ";
display: inline-block;
background-color: currentColor;
-webkit-mask-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' version='1.1' aria-hidden='true'><path fill-rule='evenodd' d='M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z'></path></svg>");
mask-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' version='1.1' aria-hidden='true'><path fill-rule='evenodd' d='M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z'></path></svg>");
}
.mdViewerWrap details,
.mdViewerWrap figcaption,
.mdViewerWrap figure {
display: block;
}
.mdViewerWrap summary {
display: list-item;
}
.mdViewerWrap [hidden] {
display: none !important;
}
.mdViewerWrap a {
background-color: transparent;
color: #0969da;
text-decoration: none;
}
.mdViewerWrap abbr[title] {
border-bottom: none;
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
}
.mdViewerWrap b,
.mdViewerWrap strong {
font-weight: 600;
}
.mdViewerWrap dfn {
font-style: italic;
}
.mdViewerWrap h1 {
margin: 0.67em 0;
font-weight: 600;
padding-bottom: 0.3em;
font-size: 2em;
border-bottom: 1px solid #d0d7deb3;
}
.mdViewerWrap mark {
background-color: #fff8c5;
color: #1f2328;
}
.mdViewerWrap small {
font-size: 90%;
}
.mdViewerWrap sub,
.mdViewerWrap sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
.mdViewerWrap sub {
bottom: -0.25em;
}
.mdViewerWrap sup {
top: -0.5em;
}
.mdViewerWrap img {
border-style: none;
max-width: 100%;
box-sizing: content-box;
background-color: #ffffff;
}
.mdViewerWrap code,
.mdViewerWrap kbd,
.mdViewerWrap pre,
.mdViewerWrap samp {
font-family: monospace;
font-size: 1em;
}
.mdViewerWrap figure {
margin: 1em 40px;
}
.mdViewerWrap hr {
box-sizing: content-box;
overflow: hidden;
background: transparent;
border-bottom: 1px solid #d0d7deb3;
height: 0.25em;
padding: 0;
margin: 24px 0;
background-color: #d0d7de;
border: 0;
}
.mdViewerWrap input {
font: inherit;
margin: 0;
overflow: visible;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
.mdViewerWrap [type="button"],
.mdViewerWrap [type="reset"],
.mdViewerWrap [type="submit"] {
-webkit-appearance: button;
appearance: button;
}
.mdViewerWrap [type="checkbox"],
.mdViewerWrap [type="radio"] {
box-sizing: border-box;
padding: 0;
}
.mdViewerWrap [type="number"]::-webkit-inner-spin-button,
.mdViewerWrap [type="number"]::-webkit-outer-spin-button {
height: auto;
}
.mdViewerWrap [type="search"]::-webkit-search-cancel-button,
.mdViewerWrap [type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
appearance: none;
}
.mdViewerWrap ::-webkit-input-placeholder {
color: inherit;
opacity: 0.54;
}
.mdViewerWrap ::-webkit-file-upload-button {
-webkit-appearance: button;
appearance: button;
font: inherit;
}
.mdViewerWrap a:hover {
text-decoration: underline;
}
.mdViewerWrap ::placeholder {
color: #636c76;
opacity: 1;
}
.mdViewerWrap hr::before {
display: table;
content: "";
}
.mdViewerWrap hr::after {
display: table;
clear: both;
content: "";
}
.mdViewerWrap table {
border-spacing: 0;
border-collapse: collapse;
display: block;
width: max-content;
max-width: 100%;
overflow: auto;
}
.mdViewerWrap td,
.mdViewerWrap th {
padding: 0;
}
.mdViewerWrap details summary {
cursor: pointer;
}
.mdViewerWrap details:not([open]) > *:not(summary) {
display: none;
}
.mdViewerWrap a:focus,
.mdViewerWrap [role="button"]:focus,
.mdViewerWrap input[type="radio"]:focus,
.mdViewerWrap input[type="checkbox"]:focus {
outline: 2px solid #0969da;
outline-offset: -2px;
box-shadow: none;
}
.mdViewerWrap a:focus:not(:focus-visible),
.mdViewerWrap [role="button"]:focus:not(:focus-visible),
.mdViewerWrap input[type="radio"]:focus:not(:focus-visible),
.mdViewerWrap input[type="checkbox"]:focus:not(:focus-visible) {
outline: solid 1px transparent;
}
.mdViewerWrap a:focus-visible,
.mdViewerWrap [role="button"]:focus-visible,
.mdViewerWrap input[type="radio"]:focus-visible,
.mdViewerWrap input[type="checkbox"]:focus-visible {
outline: 2px solid #0969da;
outline-offset: -2px;
box-shadow: none;
}
.mdViewerWrap a:not([class]):focus,
.mdViewerWrap a:not([class]):focus-visible,
.mdViewerWrap input[type="radio"]:focus,
.mdViewerWrap input[type="radio"]:focus-visible,
.mdViewerWrap input[type="checkbox"]:focus,
.mdViewerWrap input[type="checkbox"]:focus-visible {
outline-offset: 0;
}
.mdViewerWrap kbd {
display: inline-block;
padding: 3px 5px;
font: 11px ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas,
Liberation Mono, monospace;
line-height: 10px;
color: #1f2328;
vertical-align: middle;
background-color: #f6f8fa;
border: solid 1px #afb8c133;
border-bottom-color: #afb8c133;
border-radius: 6px;
box-shadow: inset 0 -1px 0 #afb8c133;
}
.mdViewerWrap h1,
.mdViewerWrap h2,
.mdViewerWrap h3,
.mdViewerWrap h4,
.mdViewerWrap h5,
.mdViewerWrap h6 {
margin-top: 24px;
margin-bottom: 16px;
font-weight: 600;
line-height: 1.25;
}
.mdViewerWrap h2 {
font-weight: 600;
padding-bottom: 0.3em;
font-size: 1.5em;
border-bottom: 1px solid #d0d7deb3;
}
.mdViewerWrap h3 {
font-weight: 600;
font-size: 1.25em;
}
.mdViewerWrap h4 {
font-weight: 600;
font-size: 1em;
}
.mdViewerWrap h5 {
font-weight: 600;
font-size: 0.875em;
}
.mdViewerWrap h6 {
font-weight: 600;
font-size: 0.85em;
color: #636c76;
}
.mdViewerWrap p {
margin-top: 0;
margin-bottom: 10px;
font-size: 1.25em;
}
.mdViewerWrap blockquote {
margin: 0;
padding: 0 1em;
color: #636c76;
border-left: 0.25em solid #d0d7de;
}
.mdViewerWrap ul,
.mdViewerWrap ol {
margin-top: 0;
margin-bottom: 0;
padding-left: 2em;
}
.mdViewerWrap ol ol,
.mdViewerWrap ul ol {
list-style-type: lower-roman;
}
.mdViewerWrap ul ul ol,
.mdViewerWrap ul ol ol,
.mdViewerWrap ol ul ol,
.mdViewerWrap ol ol ol {
list-style-type: lower-alpha;
}
.mdViewerWrap dd {
margin-left: 0;
}
.mdViewerWrap tt,
.mdViewerWrap code,
.mdViewerWrap samp {
font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas,
Liberation Mono, monospace;
font-size: 12px;
}
.mdViewerWrap pre {
margin-top: 0;
margin-bottom: 0;
font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas,
Liberation Mono, monospace;
font-size: 12px;
word-wrap: normal;
}
.mdViewerWrap .octicon {
display: inline-block;
overflow: visible !important;
vertical-align: text-bottom;
fill: currentColor;
}
.mdViewerWrap input::-webkit-outer-spin-button,
.mdViewerWrap input::-webkit-inner-spin-button {
margin: 0;
-webkit-appearance: none;
appearance: none;
}
.mdViewerWrap .mr-2 {
margin-right: 0.5rem !important;
}
.mdViewerWrap::before {
display: table;
content: "";
}
.mdViewerWrap::after {
display: table;
clear: both;
content: "";
}
.mdViewerWrap > *:first-child {
margin-top: 0 !important;
}
.mdViewerWrap > *:last-child {
margin-bottom: 0 !important;
}
.mdViewerWrap a:not([href]) {
color: inherit;
text-decoration: none;
}
.mdViewerWrap .absent {
color: #d1242f;
}
.mdViewerWrap .anchor {
float: left;
padding-right: 4px;
margin-left: -20px;
line-height: 1;
}
.mdViewerWrap .anchor:focus {
outline: none;
}
.mdViewerWrap p,
.mdViewerWrap blockquote,
.mdViewerWrap ul,
.mdViewerWrap ol,
.mdViewerWrap dl,
.mdViewerWrap table,
.mdViewerWrap pre,
.mdViewerWrap details {
margin-top: 0;
margin-bottom: 16px;
}
.mdViewerWrap blockquote > :first-child {
margin-top: 0;
}
.mdViewerWrap blockquote > :last-child {
margin-bottom: 0;
}
.mdViewerWrap h1 .octicon-link,
.mdViewerWrap h2 .octicon-link,
.mdViewerWrap h3 .octicon-link,
.mdViewerWrap h4 .octicon-link,
.mdViewerWrap h5 .octicon-link,
.mdViewerWrap h6 .octicon-link {
color: #1f2328;
vertical-align: middle;
visibility: hidden;
}
.mdViewerWrap h1:hover .anchor,
.mdViewerWrap h2:hover .anchor,
.mdViewerWrap h3:hover .anchor,
.mdViewerWrap h4:hover .anchor,
.mdViewerWrap h5:hover .anchor,
.mdViewerWrap h6:hover .anchor {
text-decoration: none;
}
.mdViewerWrap h1:hover .anchor .octicon-link,
.mdViewerWrap h2:hover .anchor .octicon-link,
.mdViewerWrap h3:hover .anchor .octicon-link,
.mdViewerWrap h4:hover .anchor .octicon-link,
.mdViewerWrap h5:hover .anchor .octicon-link,
.mdViewerWrap h6:hover .anchor .octicon-link {
visibility: visible;
}
.mdViewerWrap h1 tt,
.mdViewerWrap h1 code,
.mdViewerWrap h2 tt,
.mdViewerWrap h2 code,
.mdViewerWrap h3 tt,
.mdViewerWrap h3 code,
.mdViewerWrap h4 tt,
.mdViewerWrap h4 code,
.mdViewerWrap h5 tt,
.mdViewerWrap h5 code,
.mdViewerWrap h6 tt,
.mdViewerWrap h6 code {
padding: 0 0.2em;
font-size: inherit;
}
.mdViewerWrap summary h1,
.mdViewerWrap summary h2,
.mdViewerWrap summary h3,
.mdViewerWrap summary h4,
.mdViewerWrap summary h5,
.mdViewerWrap summary h6 {
display: inline-block;
}
.mdViewerWrap summary h1 .anchor,
.mdViewerWrap summary h2 .anchor,
.mdViewerWrap summary h3 .anchor,
.mdViewerWrap summary h4 .anchor,
.mdViewerWrap summary h5 .anchor,
.mdViewerWrap summary h6 .anchor {
margin-left: -40px;
}
.mdViewerWrap summary h1,
.mdViewerWrap summary h2 {
padding-bottom: 0;
border-bottom: 0;
}
.mdViewerWrap ul.no-list,
.mdViewerWrap ol.no-list {
padding: 0;
list-style-type: none;
}
.mdViewerWrap ol[type="a s"] {
list-style-type: lower-alpha;
}
.mdViewerWrap ol[type="A s"] {
list-style-type: upper-alpha;
}
.mdViewerWrap ol[type="i s"] {
list-style-type: lower-roman;
}
.mdViewerWrap ol[type="I s"] {
list-style-type: upper-roman;
}
.mdViewerWrap ol[type="1"] {
list-style-type: decimal;
}
.mdViewerWrap div > ol:not([type]) {
list-style-type: decimal;
}
.mdViewerWrap ul ul,
.mdViewerWrap ul ol,
.mdViewerWrap ol ol,
.mdViewerWrap ol ul {
margin-top: 0;
margin-bottom: 0;
}
.mdViewerWrap li > p {
margin-top: 16px;
}
.mdViewerWrap li + li {
margin-top: 0.25em;
}
.mdViewerWrap dl {
padding: 0;
}
.mdViewerWrap dl dt {
padding: 0;
margin-top: 16px;
font-size: 1em;
font-style: italic;
font-weight: 600;
}
.mdViewerWrap dl dd {
padding: 0 16px;
margin-bottom: 16px;
}
.mdViewerWrap table th {
font-weight: 600;
}
.mdViewerWrap table th,
.mdViewerWrap table td {
padding: 6px 13px;
border: 1px solid #d0d7de;
}
.mdViewerWrap table td > :last-child {
margin-bottom: 0;
}
.mdViewerWrap table tr {
background-color: #ffffff;
border-top: 1px solid #d0d7deb3;
}
.mdViewerWrap table tr:nth-child(2n) {
background-color: #f6f8fa;
}
.mdViewerWrap table img {
background-color: transparent;
}
.mdViewerWrap img[align="right"] {
padding-left: 20px;
}
.mdViewerWrap img[align="left"] {
padding-right: 20px;
}
.mdViewerWrap .emoji {
max-width: none;
vertical-align: text-top;
background-color: transparent;
}
.mdViewerWrap span.frame {
display: block;
overflow: hidden;
}
.mdViewerWrap span.frame > span {
display: block;
float: left;
width: auto;
padding: 7px;
margin: 13px 0 0;
overflow: hidden;
border: 1px solid #d0d7de;
}
.mdViewerWrap span.frame span img {
display: block;
float: left;
}
.mdViewerWrap span.frame span span {
display: block;
padding: 5px 0 0;
clear: both;
color: #1f2328;
}
.mdViewerWrap span.align-center {
display: block;
overflow: hidden;
clear: both;
}
.mdViewerWrap span.align-center > span {
display: block;
margin: 13px auto 0;
overflow: hidden;
text-align: center;
}
.mdViewerWrap span.align-center span img {
margin: 0 auto;
text-align: center;
}
.mdViewerWrap span.align-right {
display: block;
overflow: hidden;
clear: both;
}
.mdViewerWrap span.align-right > span {
display: block;
margin: 13px 0 0;
overflow: hidden;
text-align: right;
}
.mdViewerWrap span.align-right span img {
margin: 0;
text-align: right;
}
.mdViewerWrap span.float-left {
display: block;
float: left;
margin-right: 13px;
overflow: hidden;
}
.mdViewerWrap span.float-left span {
margin: 13px 0 0;
}
.mdViewerWrap span.float-right {
display: block;
float: right;
margin-left: 13px;
overflow: hidden;
}
.mdViewerWrap span.float-right > span {
display: block;
margin: 13px auto 0;
overflow: hidden;
text-align: right;
}
.mdViewerWrap code,
.mdViewerWrap tt {
padding: 0.2em 0.4em;
margin: 0;
font-size: 85%;
white-space: break-spaces;
background-color: #afb8c133;
border-radius: 6px;
}
.mdViewerWrap code br,
.mdViewerWrap tt br {
display: none;
}
.mdViewerWrap del code {
text-decoration: inherit;
}
.mdViewerWrap samp {
font-size: 85%;
}
.mdViewerWrap pre code {
font-size: 100%;
}
.mdViewerWrap pre > code {
padding: 0;
margin: 0;
word-break: normal;
white-space: pre;
background: transparent;
border: 0;
}
.mdViewerWrap .highlight {
margin-bottom: 16px;
}
.mdViewerWrap .highlight pre {
margin-bottom: 0;
word-break: normal;
}
.mdViewerWrap .highlight pre,
.mdViewerWrap pre {
padding: 16px;
overflow: auto;
font-size: 85%;
line-height: 1.45;
color: #1f2328;
background-color: #f6f8fa;
border-radius: 6px;
}
.mdViewerWrap pre code,
.mdViewerWrap pre tt {
display: inline;
max-width: auto;
padding: 0;
margin: 0;
overflow: visible;
line-height: inherit;
word-wrap: normal;
background-color: transparent;
border: 0;
}
.mdViewerWrap .csv-data td,
.mdViewerWrap .csv-data th {
padding: 5px;
overflow: hidden;
font-size: 12px;
line-height: 1;
text-align: left;
white-space: nowrap;
}
.mdViewerWrap .csv-data .blob-num {
padding: 10px 8px 9px;
text-align: right;
background: #ffffff;
border: 0;
}
.mdViewerWrap .csv-data tr {
border-top: 0;
}
.mdViewerWrap .csv-data th {
font-weight: 600;
background: #f6f8fa;
border-top: 0;
}
.mdViewerWrap [data-footnote-ref]::before {
content: "[";
}
.mdViewerWrap [data-footnote-ref]::after {
content: "]";
}
.mdViewerWrap .footnotes {
font-size: 12px;
color: #636c76;
border-top: 1px solid #d0d7de;
}
.mdViewerWrap .footnotes ol {
padding-left: 16px;
}
.mdViewerWrap .footnotes ol ul {
display: inline-block;
padding-left: 16px;
margin-top: 16px;
}
.mdViewerWrap .footnotes li {
position: relative;
}
.mdViewerWrap .footnotes li:target::before {
position: absolute;
top: -8px;
right: -8px;
bottom: -8px;
left: -24px;
pointer-events: none;
content: "";
border: 2px solid #0969da;
border-radius: 6px;
}
.mdViewerWrap .footnotes li:target {
color: #1f2328;
}
.mdViewerWrap .footnotes .data-footnote-backref g-emoji {
font-family: monospace;
}
.mdViewerWrap .pl-c {
color: #57606a;
}
.mdViewerWrap .pl-c1,
.mdViewerWrap .pl-s .pl-v {
color: #0550ae;
}
.mdViewerWrap .pl-e,
.mdViewerWrap .pl-en {
color: #6639ba;
}
.mdViewerWrap .pl-smi,
.mdViewerWrap .pl-s .pl-s1 {
color: #24292f;
}
.mdViewerWrap .pl-ent {
color: #0550ae;
}
.mdViewerWrap .pl-k {
color: #cf222e;
}
.mdViewerWrap .pl-s,
.mdViewerWrap .pl-pds,
.mdViewerWrap .pl-s .pl-pse .pl-s1,
.mdViewerWrap .pl-sr,
.mdViewerWrap .pl-sr .pl-cce,
.mdViewerWrap .pl-sr .pl-sre,
.mdViewerWrap .pl-sr .pl-sra {
color: #0a3069;
}
.mdViewerWrap .pl-v,
.mdViewerWrap .pl-smw {
color: #953800;
}
.mdViewerWrap .pl-bu {
color: #82071e;
}
.mdViewerWrap .pl-ii {
color: #f6f8fa;
background-color: #82071e;
}
.mdViewerWrap .pl-c2 {
color: #f6f8fa;
background-color: #cf222e;
}
.mdViewerWrap .pl-sr .pl-cce {
font-weight: bold;
color: #116329;
}
.mdViewerWrap .pl-ml {
color: #3b2300;
}
.mdViewerWrap .pl-mh,
.mdViewerWrap .pl-mh .pl-en,
.mdViewerWrap .pl-ms {
font-weight: bold;
color: #0550ae;
}
.mdViewerWrap .pl-mi {
font-style: italic;
color: #24292f;
}
.mdViewerWrap .pl-mb {
font-weight: bold;
color: #24292f;
}
.mdViewerWrap .pl-md {
color: #82071e;
background-color: #ffebe9;
}
.mdViewerWrap .pl-mi1 {
color: #116329;
background-color: #dafbe1;
}
.mdViewerWrap .pl-mc {
color: #953800;
background-color: #ffd8b5;
}
.mdViewerWrap .pl-mi2 {
color: #eaeef2;
background-color: #0550ae;
}
.mdViewerWrap .pl-mdr {
font-weight: bold;
color: #8250df;
}
.mdViewerWrap .pl-ba {
color: #57606a;
}
.mdViewerWrap .pl-sg {
color: #8c959f;
}
.mdViewerWrap .pl-corl {
text-decoration: underline;
color: #0a3069;
}
.mdViewerWrap [role="button"]:focus:not(:focus-visible),
.mdViewerWrap [role="tabpanel"][tabindex="0"]:focus:not(:focus-visible),
.mdViewerWrap button:focus:not(:focus-visible),
.mdViewerWrap summary:focus:not(:focus-visible),
.mdViewerWrap a:focus:not(:focus-visible) {
outline: none;
box-shadow: none;
}
.mdViewerWrap [tabindex="0"]:focus:not(:focus-visible),
.mdViewerWrap details-dialog:focus:not(:focus-visible) {
outline: none;
}
.mdViewerWrap g-emoji {
display: inline-block;
min-width: 1ch;
font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
font-size: 1em;
font-style: normal !important;
font-weight: 400;
line-height: 1;
vertical-align: -0.075em;
}
.mdViewerWrap g-emoji img {
width: 1em;
height: 1em;
}
.mdViewerWrap .task-list-item {
list-style-type: none;
}
.mdViewerWrap .task-list-item label {
font-weight: 400;
}
.mdViewerWrap .task-list-item.enabled label {
cursor: pointer;
}
.mdViewerWrap .task-list-item + .task-list-item {
margin-top: 0.25rem;
}
.mdViewerWrap .task-list-item .handle {
display: none;
}
.mdViewerWrap .task-list-item-checkbox {
margin: 0 0.2em 0.25em -1.4em;
vertical-align: middle;
}
.mdViewerWrap .contains-task-list:dir(rtl) .task-list-item-checkbox {
margin: 0 -1.6em 0.25em 0.2em;
}
.mdViewerWrap .contains-task-list {
position: relative;
}
.mdViewerWrap .contains-task-list:hover .task-list-item-convert-container,
.mdViewerWrap
.contains-task-list:focus-within
.task-list-item-convert-container {
display: block;
width: auto;
height: 24px;
overflow: visible;
clip: auto;
}
.mdViewerWrap ::-webkit-calendar-picker-indicator {
filter: invert(50%);
}
.mdViewerWrap .markdown-alert {
padding: 0.5rem 1rem;
margin-bottom: 1rem;
color: inherit;
border-left: 0.25em solid #d0d7de;
}
.mdViewerWrap .markdown-alert > :first-child {
margin-top: 0;
}
.mdViewerWrap .markdown-alert > :last-child {
margin-bottom: 0;
}
.mdViewerWrap .markdown-alert .markdown-alert-title {
display: flex;
font-weight: 500;
align-items: center;
line-height: 1;
}
.mdViewerWrap .markdown-alert.markdown-alert-note {
border-left-color: #0969da;
}
.mdViewerWrap .markdown-alert.markdown-alert-note .markdown-alert-title {
color: #0969da;
}
.mdViewerWrap .markdown-alert.markdown-alert-important {
border-left-color: #8250df;
}
.mdViewerWrap .markdown-alert.markdown-alert-important .markdown-alert-title {
color: #8250df;
}
.mdViewerWrap .markdown-alert.markdown-alert-warning {
border-left-color: #bf8700;
}
.mdViewerWrap .markdown-alert.markdown-alert-warning .markdown-alert-title {
color: #9a6700;
}
.mdViewerWrap .markdown-alert.markdown-alert-tip {
border-left-color: #1a7f37;
}
.mdViewerWrap .markdown-alert.markdown-alert-tip .markdown-alert-title {
color: #1a7f37;
}
.mdViewerWrap .markdown-alert.markdown-alert-caution {
border-left-color: #cf222e;
}
.mdViewerWrap .markdown-alert.markdown-alert-caution .markdown-alert-title {
color: #d1242f;
}
.mdViewerWrap > *:first-child > .heading-element:first-child {
margin-top: 0 !important;
}
\ No newline at end of file
import ReactMarkdown from "react-markdown";
import remarkMath from "remark-math";
import rehypeKatex from "rehype-katex";
import rehypeRaw from "rehype-raw";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import remarkGfm from "remark-gfm";
import styles from "./index.module.scss";
import { useRef } from "react";
import cls from "classnames";
interface IMarkdownProps {
content?: string;
markdownClass?: string;
markdownId?: string;
}
const LazyUrlMarkdown: React.FC<IMarkdownProps> = ({
content,
markdownClass = "",
}) => {
const ref = useRef<HTMLDivElement>(null);
return (
<div ref={ref} className="min-h-[100px]">
<div className={styles.mdViewerWrap}>
<ReactMarkdown
className={cls("bg-white text-[0.75rem]", markdownClass)}
remarkPlugins={[
remarkMath,
[remarkGfm, { singleTilde: false }, { strict: "ignore" }],
]}
rehypePlugins={[[rehypeKatex, { strict: "ignore" }], rehypeRaw]}
components={{
code(props) {
const { children, className, node, ...rest } = props;
const match = /language-(\w+)/.exec(className || "");
return match ? (
<SyntaxHighlighter
PreTag="div"
className="rounded-md"
// eslint-disable-next-line react/no-children-prop
children={String(children).replace(/\n$/, "")}
language={match[1]}
/>
) : (
<code
{...rest}
className="p-4 my-2 bg-[#f6f8fa] !bg-black rounded-md block"
>
{children}
</code>
);
},
}}
>
{content}
</ReactMarkdown>
</div>
</div>
);
};
export default LazyUrlMarkdown;
.item {
border-radius: 12px;
border: 1px solid rgba(198, 217, 255, 0.20);
background: linear-gradient(155deg, rgba(92, 147, 255, 0.10) -130.23%, rgba(255, 255, 255, 0.00) 83.57%);
filter: blur(0px);
padding: 42px 20px;
cursor: pointer;
position: relative;
&:hover {
border-radius: 12px;
border: 1px solid rgba(198, 217, 255, 0.20);
background: linear-gradient(155deg, rgba(92, 147, 255, 0.20) -83.23%, rgba(255, 255, 255, 0.00) 83.57%);
box-shadow: 0px 8px 26px 0px rgba(0, 0, 0, 0.12);
}
}
.itemComingSoon_zh-CN {
&:hover {
backdrop-filter: blur(-100px);
& > .itemContent {
opacity: 0;
z-index: 0;
}
&::before {
display: inline-block;
content: '敬请期待';
width: 100%;
height: 100%;
line-height: 184px;
position: absolute;
background-image: url('@/assets/pdf/comingSoonLayer.svg');
background-color: rgba(255, 255, 255, 0.8);
background-size: contain;
top: 0;
left: 0;
border-radius: 12px;
// background:white;
-webkit-backdrop-filter: blur(2030px) brightness(110%);
backdrop-filter: blur(2030px) brightness(110%);
color: var(--60-text-3, rgba(18, 19, 22, 0.60));
text-align: center;
font-feature-settings: 'liga' off, 'clig' off;
z-index: 1;
}
}
}
.itemComingSoon_en-US {
&:hover {
backdrop-filter: blur(-100px);
& > .itemContent {
opacity: 0;
z-index: 0;
}
&::before {
display: inline-block;
content: 'Stay Tuned';
width: 100%;
height: 100%;
line-height: 224px;
position: absolute;
background-image: url('@/assets/pdf/comingSoonLayer.svg');
background-color: rgba(255, 255, 255, 0.8);
background-size: contain;
top: 0;
left: 0;
border-radius: 12px;
// background:white;
-webkit-backdrop-filter: blur(2030px) brightness(110%);
backdrop-filter: blur(2030px) brightness(110%);
color: var(--60-text-3, rgba(18, 19, 22, 0.60));
text-align: center;
font-feature-settings: 'liga' off, 'clig' off;
z-index: 1;
}
}
}
import extractorPdfIcon from "@/assets/pdf/extractor-pdf.svg";
import extractorTableIcon from "@/assets/pdf/extractor-table.svg";
import extractorFormulaIcon from "@/assets/pdf/extractor-formula.svg";
import style from "./index.module.scss";
import cls from "classnames";
import { EXTRACTOR_TYPE_LIST } from "@/types/extract-task-type";
import { useNavigate } from "react-router-dom";
import { useIntl } from "react-intl";
const ITEM_LIST = [
{
id: 1,
icon: extractorPdfIcon,
[`zh-CN-title`]: "PDF文档提取",
[`en-US-title`]: "PDF Document Extraction",
type: EXTRACTOR_TYPE_LIST.pdf,
[`zh-CN-desc`]:
"支持文本/扫描型 pdf 解析,识别各类版面元素并转换为多模态 Markdown 格式",
[`en-US-desc`]:
"Supports text/scanned PDF parsing, identifies various layout elements and converts them into multimodal Markdown format",
},
{
id: 2,
icon: extractorFormulaIcon,
[`zh-CN-title`]: "公式检测与识别",
[`en-US-title`]: "Formula Detection and Recognition",
type: EXTRACTOR_TYPE_LIST.formula,
[`zh-CN-desc`]:
"对行内、行间公式进行检测,对数学公式进行识别并转换为 Latex 格式",
[`en-US-desc`]:
"Detect formulas within and between lines, identify mathematical formulas and convert them into Latex format",
},
{
id: 3,
icon: extractorTableIcon,
[`zh-CN-title`]: "表格识别",
[`en-US-title`]: "Table recognition",
type: EXTRACTOR_TYPE_LIST.table,
[`zh-CN-desc`]: "对表格进行检测并转换为 Markdown 格式",
[`en-US-desc`]: "Detect and convert tables to Markdown format",
comingSoon: true,
},
] as Record<string, any>;
const ExtractorHome = () => {
const navigate = useNavigate();
const { formatMessage, locale } = useIntl();
const defaultLocale = "zh-CN";
return (
<div className="flex flex-col items-center justify-center h-full">
<div className="mb-4 font-semibold text-base !text-[2rem] leading-[3rem]">
{formatMessage({ id: "extractor.home.title" })}
</div>
<div className="mb-12 text-[1.25rem] leading-[2rem]">
{formatMessage({ id: "extractor.home.subTitle" })}
</div>
<div className="flex mx-[5.25rem]">
{ITEM_LIST?.map((i: Record<string, any>) => {
return (
<div
className={cls(
style.item,
i?.comingSoon &&
style?.[`itemComingSoon_${locale || defaultLocale}`],
"mx-4 basis-4/12"
)}
key={i.desc}
onClick={() => {
if (i?.comingSoon) return;
navigate(`/OpenSourceTools/Extractor/${i?.type}`);
}}
>
<div className={style.itemContent}>
<div className="text-center leading-[1.5rem] flex items-center justify-center">
<img
src={i.icon}
alt={""}
className="w-[2.25rem] h-[2.25rem]"
/>
<span className="text-[#121316]/[0.8] ml-2 text-[20px] font-semibold">
{i?.[`${locale}-title`]}
</span>
</div>
<div className="text-center text-[#121316]/[0.6] mt-4 text-[14px] leading-[1.5rem]">
{i?.[`${locale}-desc`]}
</div>
</div>
</div>
);
})}
</div>
<div className="absolute bottom-[1.5rem] text-[13px] text-[#121316]/[0.35] text-center leading-[20px] max-w-[64rem]">
{formatMessage({
id: "extractor.law",
})}
</div>
</div>
);
};
export default ExtractorHome;
import showLayerIcon from "@/assets/pdf/extractor-show-layer.svg";
import hiddenLayerIcon from "@/assets/pdf/extractor-hidden-layer.svg";
import { useRef, useState } from "react";
import IconFont from "@/components/icon-font";
import ImageLayerViewer, {
ImageLayerViewerRef,
} from "../../components/image-layer-viwer";
import { useUpdate } from "ahooks";
import { TaskIdProgress, TaskIdResItem } from "@/api/extract";
import { Tooltip } from "antd";
import { useIntl } from "react-intl";
interface IFormulaDetailLeftProps {
taskInfo: TaskIdProgress & TaskIdResItem;
}
const FormulaDetailLeft = ({ taskInfo }: IFormulaDetailLeftProps) => {
const imageRef = useRef<ImageLayerViewerRef>(null);
const { formatMessage } = useIntl();
const [layerVisible, setLayerVisible] = useState(true);
const update = useUpdate();
if (!taskInfo?.fileInfo?.height || !taskInfo?.fileInfo?.width) {
console.info(
"formula extractor interface error: the picture size is invalid"
);
}
return (
<div className="w-full h-full">
<div
className={`flex border-solid border-0 !border-b-[1px] border-[#EBECF0] items-center px-4 h-[48px]`}
>
<Tooltip
title={
<>
{layerVisible
? formatMessage({
id: "extractor.button.hiddenLayer",
})
: formatMessage({
id: "extractor.button.showLayer",
})}
</>
}
>
<span
className="ml-auto mr-2 cursor-pointer hover:bg-[#f4f5f9] w-6 text-center inline-block rounded leading-normal"
onClick={() => setLayerVisible(!layerVisible)}
>
{taskInfo?.type === "formula-detect" ? null : layerVisible ? (
<img src={hiddenLayerIcon} alt="Hide Layer" />
) : (
<img src={showLayerIcon} alt="Show Layer" />
)}
</span>
</Tooltip>
{taskInfo?.type === "formula-detect" ? null : (
<span className="w-[1px] leading-normal h-[12px] bg-[#D7D8DD] mr-1"></span>
)}
<div className="select-none w-[7.8rem] flex justify-center">
<IconFont
className="rounded mx-2 cursor-pointer hover:bg-[#F4F5F9]"
type="icon-SubtractOutlined"
onClick={() => {
imageRef?.current?.zoomOut();
}}
/>
<span className="mx-2">
{((imageRef?.current?.scale || 0) * 100 || 1).toFixed(0)}%
</span>
<IconFont
className="rounded mx-2 cursor-pointer hover:bg-[#F4F5F9]"
type="icon-PlusOutlined"
onClick={() => {
imageRef?.current?.zoomIn();
}}
/>
</div>
</div>
<ImageLayerViewer
imageHeight={taskInfo?.fileInfo?.height || 0}
imageWidth={taskInfo?.fileInfo?.width || 0}
layout={taskInfo.content as any[]}
ref={imageRef}
onChange={() => {
// imageRef?.current?.scale为了这个更新
update();
}}
className={"!h-[calc(100%-48px)]"}
layerVisible={
taskInfo?.type === "formula-detect" ? false : layerVisible
}
imageUrl={taskInfo?.url}
/>
</div>
);
};
export default FormulaDetailLeft;
import ImageLayerViewer from "../../components/image-layer-viwer";
import exitFullScreenSvg from "@/assets/pdf/exitFullScreen.svg";
import fullScreenSvg from "@/assets/pdf/fullScreen.svg";
import { Tooltip } from "antd";
import { useIntl } from "react-intl";
import { TaskIdProgress, TaskIdResItem } from "@/api/extract";
import IconFont from "@/components/icon-font";
import { CopyToClipboard } from "react-copy-to-clipboard";
import { useEffect, useMemo, useRef, useState } from "react";
import { message } from "antd";
import { MD_PREVIEW_TYPE } from "@/types/extract-task-type";
import CodeMirror from "@/components/code-mirror";
import LatexRenderer from "../../components/latex-renderer";
import { useParams } from "react-router-dom";
interface IImageOriginViewerProps {
fullScreen?: boolean;
setFullScreen?: (val: boolean) => void;
taskInfo: TaskIdProgress & TaskIdResItem;
}
const FormulaDetailRight = ({
fullScreen,
setFullScreen,
taskInfo,
}: IImageOriginViewerProps) => {
const CONTROL_BAR_HEIGHT = 48;
const { formatMessage } = useIntl();
const [displayType, setDisplayType] = useState(MD_PREVIEW_TYPE.preview);
const imageViewerRef = useRef<any>();
const formulaType = taskInfo?.type;
const params = useParams();
const jobID = params?.jobID;
const formulaLateX = useMemo(() => {
return taskInfo?.content?.map((i: any) => i?.latex + "\\\\\n").join("");
}, [taskInfo?.content]);
const handleCopy = () => {};
const menuList = [
{
name: formatMessage({ id: "extractor.markdown.preview" }),
code: MD_PREVIEW_TYPE.preview,
},
{
name: formatMessage({ id: "extractor.markdown.code" }),
code: MD_PREVIEW_TYPE.code,
},
];
useEffect(() => {
imageViewerRef?.current?.updateScaleAndPosition();
}, [fullScreen]);
useEffect(() => {
setDisplayType(MD_PREVIEW_TYPE.preview);
}, [jobID]);
return (
<div className="w-full h-full">
<header
className={`flex border-solid border-0 !border-b-[1px] border-[#EBECF0] px-4 items-center h-[${CONTROL_BAR_HEIGHT}px]`}
>
{formulaType === "formula-extract" && (
<ul className="p-1 list-none mb-0 inline-block rounded-sm mr-auto bg-[#F4F5F9] select-none">
{menuList.map((item) => (
<li
key={item.code}
className={`mx-[0.125rem] px-2 leading-[25px] inline-block rounded-sm text-[14px] cursor-pointer text-color ${
displayType === item.code && "bg-white text-primary"
}`}
onClick={() => setDisplayType(item.code)}
>
{item.name}
</li>
))}
</ul>
)}
<Tooltip
title={
fullScreen
? formatMessage({ id: "extractor.button.exitFullScreen" })
: formatMessage({
id: "extractor.button.fullScreen",
})
}
>
<span
className="cursor-pointer ml-auto w-[1.5rem] select-none flex items-center justify-center h-[1.5rem] hover:bg-[#F4F5F9] rounded "
onClick={() => setFullScreen?.(!fullScreen)}
>
{!fullScreen ? (
<img
className=" w-[1.125rem] h-[1.125rem] "
src={fullScreenSvg}
/>
) : (
<img
className=" w-[1.125rem] h-[1.125rem] "
src={exitFullScreenSvg}
/>
)}
</span>
</Tooltip>
{formulaType === "formula-extract" && (
<div className="flex items-center">
<span className="w-[1px] h-[0.75rem] bg-[#D7D8DD] mx-[1rem]"></span>
<Tooltip title={formatMessage({ id: "common.copy" })}>
<CopyToClipboard
text={formulaLateX}
onCopy={() => {
message.success(formatMessage({ id: "common.copySuccess" }));
}}
>
<span>
<IconFont
type="icon-copy"
className="text-[#464a53] !text-[1.32rem] leading-0 cursor-pointer hover:bg-[#F4F5F9] rounded"
onClick={() => handleCopy()}
/>
</span>
</CopyToClipboard>
</Tooltip>
</div>
)}
</header>
{displayType === MD_PREVIEW_TYPE.preview ? (
formulaType === "formula-extract" ? (
<div className="w-full h-[calc(100%-48px)] flex items-center justify-center scrollbar-thin-layer overflow-auto">
<LatexRenderer
formula={formulaLateX}
className="text-base sm:text-lg md:text-xl lg:text-2xl xl:text-3xl"
/>
</div>
) : (
<ImageLayerViewer
imageHeight={taskInfo?.fileInfo?.height || 0}
imageWidth={taskInfo?.fileInfo?.width || 0}
layout={
taskInfo?.type === "formula-extract"
? []
: (taskInfo.content as any[])
}
className={"!h-[calc(100%-48px)]"}
imageUrl={taskInfo?.url}
ref={imageViewerRef}
/>
)
) : (
<div
className={
"!h-[calc(100%-48px)] flex items-center justify-center w-full px-4 scroll-thin overflow-auto"
}
>
<CodeMirror className="w-full" value={formulaLateX} />
</div>
)}
</div>
);
};
export default FormulaDetailRight;
import cls from "classnames";
import { useEffect, useState } from "react";
import LoadingIcon from "../../components/loading-icon";
import { SubmitRes } from "@/api/extract";
import emptySvg from "@/assets/svg/empty.svg";
import { FormattedMessage } from "react-intl";
import FormulaDetailLeft from "../formula-detail-left";
import FormulaDetailRight from "../formula-detail-right";
import { useIntl } from "react-intl";
import { ExtractorUploadButton } from "../../components/pdf-upload-button";
import useExtractorJobProgress from "@/store/jobProgress";
interface IPdfExtractionProps {
setUploadShow: (bool: boolean) => void;
className?: string;
}
const FormulaDetail = ({ className = "" }: IPdfExtractionProps) => {
const {
taskInfo,
queueLoading,
interfaceError: compileError,
refreshQueue,
jobID,
} = useExtractorJobProgress();
const [fullScreen, setFullScreen] = useState<boolean>(false);
const { formatMessage } = useIntl();
const isQueueAndExtract = queueLoading;
const hiddenQueuePage = !isQueueAndExtract ? "opacity-0 " : "";
const hiddenResultPage = isQueueAndExtract ? "z-[-1] opacity-0" : "";
const getLayoutClassName = (_fullScreen?: boolean) => {
return {
left: _fullScreen ? "w-0 overflow-hidden" : "w-[50%] max-w-[50%]",
right: _fullScreen ? "w-full " : "w-[50%] max-w-[50%]",
};
};
const afterUploadSuccess = (data: SubmitRes) => {
refreshQueue();
};
const afterAsyncCheck = () => {
return Promise.resolve(true);
};
useEffect(() => {
setFullScreen(false);
}, [jobID]);
return (
<>
<div
className={cls(
"flex flex-col justify-center items-center h-[60px] w-[300px] bg-white h-full w-full absolute top-0 left-0",
hiddenQueuePage
)}
>
<LoadingIcon className="w-12" color={"#0D53DE"} />
<div className="text-base text-[#121316]/[0.8] mt-4">
{taskInfo?.rank > 1 ? (
<FormattedMessage
id="extractor.common.extracting.queue"
values={{
id: taskInfo?.rank || 0,
}}
/>
) : taskInfo.state === "done" || taskInfo?.state === "unknown" ? (
formatMessage({
id: "extractor.common.loading",
})
) : (
formatMessage({
id: "extractor.common.extracting",
})
)}
</div>
</div>
<div
className={cls("h-full w-full relative", className, hiddenResultPage)}
>
{!compileError ? (
<div className="w-full flex h-full">
<div className={cls("h-full", getLayoutClassName(fullScreen).left)}>
<FormulaDetailLeft taskInfo={taskInfo} />
</div>
<div
className={cls(
"!overflow-auto",
getLayoutClassName(fullScreen).right
)}
style={{
borderLeft: "1px solid #EBECF0",
}}
>
<FormulaDetailRight
fullScreen={fullScreen}
setFullScreen={setFullScreen}
taskInfo={taskInfo}
/>
</div>
</div>
) : (
<div className="ml-[50%] translate-x-[-50%] !h-[calc(100%-70px)] flex-1 flex items-center h-[110px] flex-col justify-center">
<img src={emptySvg} alt="emptySvg" />
<span className="text-[#121316]/[0.8] mt-2">
{formatMessage({
id: "extractor.failed",
})}
</span>
<ExtractorUploadButton
className="!mb-0 !w-[120px] !m-6"
accept="image/png, image/jpg, .png ,.jpg"
afterUploadSuccess={afterUploadSuccess}
taskType="extract"
afterAsyncCheck={afterAsyncCheck}
extractType={taskInfo?.type}
submitType="reUpload"
showIcon={false}
text={
<span className="text-white">
{formatMessage({
id: "extractor.button.reUpload",
})}
</span>
}
/>
</div>
)}
</div>
</>
);
};
export default FormulaDetail;
.formulaPopover {
:global {
.ant-popover-content, .ant-popover-inner {
border-radius: 12px !important;
overflow: hidden;
box-shadow: 0px 8px 26px 0px rgba(0, 0, 0, 0.12);
}
.ant-popover-inner-content {
padding: 24px !important;
}
.ant-popover-arrow {
display: none !important;
}
}
}
import React, { ReactNode } from "react";
import { Popover } from "antd";
import IconFont from "@/components/icon-font";
import { useIntl } from "react-intl";
import style from "./index.module.scss";
interface IFormulaPopoverProps {
type: string;
text?: string | ReactNode;
}
const FormulaPopover = ({ type, text }: IFormulaPopoverProps) => {
const { formatMessage } = useIntl();
const content = (
<div className="flex flex-col w-[20rem] items-center">
{/* 顺序反了 */}
{formatMessage({
id:
type === "detect"
? "extractor.formula.popover.extract"
: "extractor.formula.popover.detect",
})}
<img
className="w-full mt-4"
src={
type === "extract"
? "https://static.openxlab.org.cn/opendatalab/assets/pdf/svg/extract-formula-extract.svg"
: "https://static.openxlab.org.cn/opendatalab/assets/pdf/svg/extract-formula-detect.svg"
}
alt="formula-popover"
/>
</div>
);
return (
<span className={""}>
<Popover
content={content}
placement="right"
showArrow={false}
overlayClassName={style.formulaPopover}
>
<span className="group inline-flex items-center">
{text}
<IconFont
type="icon-QuestionCircleOutlined"
className="text-[#121316]/[0.6] text-[15px] mt-[2px] leading-[1rem] group-hover:text-[#0D53DE]"
/>
</span>
</Popover>
</span>
);
};
export default FormulaPopover;
.uploadText {
font-feature-settings: 'liga' off, 'clig' off;
font-family: "PingFang SC";
font-size: 18px;
font-style: normal;
font-weight: 600;
line-height: 24px; /* 133.333% */
background: linear-gradient(107deg, #38A0FF -24.14%, #0D53DE 30.09%, #5246FF 86.61%);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.uploadDescText {
font-size: 13px;
line-height: 20px;
font-weight: 400;
background: linear-gradient(107deg, rgba(18,19,22,0.6) -24.14%, rgba(18,19,22,0.6) 100.09% );
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-bottom: 1rem;
margin-top: 0.5rem;
}
.linearText {
font-size: 13px;
line-height: 20px;
font-weight: 400;
background: linear-gradient(107deg, rgba(18,19,22,0.6) -24.14%, rgba(18,19,22,0.6) 100.09% );
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
&-item {
font-weight: 400;
font-size: 13px;
line-height: 20px;
margin-right: 1rem;
background: linear-gradient(107deg, #38A0FF -24.14%, #0D53DE 30.09%, #5246FF 86.61%);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
&:hover {
background: #3477EB;
background: linear-gradient(107deg, #3477EB -24.14%, #3477EB 100.09% );
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
}
}
.uploadSection {
border-radius: 12px;
border: 1px dashed var(---Brand1-6, #0D53DE);
background: linear-gradient(180deg, rgba(92, 147, 255, 0.10) -130.23%, rgba(255, 255, 255, 1) 83.57%);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
filter: blur(0px);
height: 280px !important;
width: 600px !important;
&:hover {
background: linear-gradient(180deg, rgb(245, 248, 255) -130.23%, rgb(245, 248, 255) 83.57%);
}
}
.textBtn {
background-image: none !important;
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background: linear-gradient(111deg, #0D53DE -21.44%, #5246FF 102%) !important;
background-clip: text !important;
-webkit-background-clip: text !important;
-webkit-text-fill-color: transparent !important;
height: 1.5rem !important;
font-weight: 600;
height: 280px !important;
width: 600px !important;
overflow: hidden;
}
import { useIntl } from "react-intl";
import IconFont from "@/components/icon-font";
import { useState } from "react";
import cls from "classnames";
import { ExtractorUploadButton } from "../../components/pdf-upload-button";
import UploadBg from "@/assets/imgs/online.experience/file-upload-bg.svg";
import style from "./index.module.scss";
import { SubmitRes } from "@/api/extract";
import { ADD_TASK_LIST } from "@/constant/event";
import { FORMULA_TYPE } from "@/types/extract-task-type";
import { useNavigate } from "react-router-dom";
const FORMULA_ITEM_LIST = [
{
type: FORMULA_TYPE.detect,
[`zh-CN-name`]: "公式检测",
[`en-US-name`]: "Formula Detection",
},
{
type: FORMULA_TYPE.extract,
[`zh-CN-name`]: "公式识别",
[`en-US-name`]: "Formula Recognition",
},
];
const FormulaUpload = () => {
const navigate = useNavigate();
const { formatMessage, locale } = useIntl();
const [formulaType, setFormulaType] = useState(FORMULA_TYPE.detect);
const afterUploadSuccess = (data: SubmitRes) => {
navigate(`/OpenSourceTools/Extractor/formula/${data?.id}`);
setTimeout(() => {
document.dispatchEvent(
new CustomEvent(ADD_TASK_LIST, {
detail: data,
})
);
}, 10);
};
const afterAsyncCheck = () => {
return Promise.resolve(true);
};
return (
<div className="relative w-full h-full flex flex-col items-center justify-center ">
<div
className="absolute top-10 left-8 hover:!text-[#0D53DE] cursor-pointer"
onClick={() => navigate("/OpenSourceTools/Extractor")}
>
<IconFont type="icon-fanhui" className="mr-2" />
<span>{formatMessage({ id: "extractor.home" })}</span>
</div>
<div className="translate-y-[-60px] flex flex-col items-center ">
<div className="mb-[2.25rem]">
{FORMULA_ITEM_LIST.map((i) => {
return (
<span
key={i.type}
onClick={() => setFormulaType(i?.type)}
className={cls(
"relative text-[1.5rem] text-[#121316] cursor-pointer mx-[1.5rem]",
formulaType === i?.type && "!text-[#0D53DE] font-semibold"
)}
>
{i?.[`${locale || "zh-CN"}-name` as "en-US-name"]}
{formulaType === i?.type && (
<span className="absolute bottom-[-0.75rem] right-[50%] translate-x-[50%] w-[3rem] bg-[#0D53DE] rounded-[2px] h-[0.25rem]"></span>
)}
</span>
);
})}
</div>
<div className="text-[1.25rem] text-[#121316]/[0.8] mb-[3rem] text-center w-max-[50rem]">
{formatMessage({
id:
formulaType === "extract"
? "extractor.formula.title2"
: "extractor.formula.title",
})}
</div>
<ExtractorUploadButton
accept="image/png, image/jpg, .png ,.jpg"
afterUploadSuccess={afterUploadSuccess}
taskType="extract"
afterAsyncCheck={afterAsyncCheck}
extractType={
formulaType === FORMULA_TYPE.extract
? "formula-extract"
: "formula-detect"
}
className={style.textBtn}
showIcon={false}
text={
<div
className={cls(
style.uploadSection,
"border-[1px] border-dashed border-[#0D53DE] rounded-xl flex flex-col items-center justify-center"
)}
>
<img src={UploadBg} className="mb-4" />
<span
className={cls(style.uploadText, "text-[18px] leading-[20px]")}
>
{formatMessage({ id: "extractor.formula.upload.text" })}
</span>
<span className={cls(style.uploadDescText)}>
{formatMessage({ id: "extractor.formula.upload.accept" })}
</span>
<div>
<span className={cls(style.linearText, "cursor-pointer")}>
{formatMessage({
id: "extractor.formula.upload.try",
})}
</span>
</div>
</div>
}
></ExtractorUploadButton>
</div>
<div className="absolute bottom-[1.5rem] text-[13px] text-[#121316]/[0.35] text-center leading-[20px] max-w-[64rem]">
{formatMessage({
id: "extractor.law",
})}
</div>
</div>
);
};
export default FormulaUpload;
import { Outlet } from "react-router-dom";
const Formula = () => {
return (
<div className="relative w-full h-full flex flex-col items-center justify-center ">
<Outlet />
</div>
);
};
export default Formula;
const ExtractorTable = () => {
return <>ExtractorTable</>;
};
export default ExtractorTable;
const TableDetail = () => {
return <>TableDetail</>;
};
export default TableDetail;
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment