@vertesia/ui 0.72.0 → 0.74.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/esm/features/facets/RunsFacetsNav.js +15 -5
- package/lib/esm/features/facets/RunsFacetsNav.js.map +1 -1
- package/lib/esm/features/magic-pdf/DownloadPopover.js +17 -2
- package/lib/esm/features/magic-pdf/DownloadPopover.js.map +1 -1
- package/lib/esm/features/magic-pdf/MagicPdfView.js +26 -3
- package/lib/esm/features/magic-pdf/MagicPdfView.js.map +1 -1
- package/lib/esm/features/magic-pdf/PageSlider.js +21 -8
- package/lib/esm/features/magic-pdf/PageSlider.js.map +1 -1
- package/lib/esm/features/magic-pdf/PdfPageProvider.js +55 -0
- package/lib/esm/features/magic-pdf/PdfPageProvider.js.map +1 -1
- package/lib/esm/features/magic-pdf/TextPageView.js +21 -1
- package/lib/esm/features/magic-pdf/TextPageView.js.map +1 -1
- package/lib/esm/features/store/objects/layout/documentLayout.js +1 -1
- package/lib/esm/features/store/objects/layout/documentLayout.js.map +1 -1
- package/lib/esm/shell/apps/AppInstallationProvider.js +13 -0
- package/lib/esm/shell/apps/AppInstallationProvider.js.map +1 -0
- package/lib/esm/shell/apps/AppProjectSelector.js +25 -0
- package/lib/esm/shell/apps/AppProjectSelector.js.map +1 -0
- package/lib/esm/shell/apps/StandaloneApp.js +60 -0
- package/lib/esm/shell/apps/StandaloneApp.js.map +1 -0
- package/lib/esm/shell/apps/index.js +3 -1
- package/lib/esm/shell/apps/index.js.map +1 -1
- package/lib/esm/shell/login/InviteAcceptModal.js +1 -1
- package/lib/esm/shell/login/InviteAcceptModal.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/types/features/facets/RunsFacetsNav.d.ts +1 -0
- package/lib/types/features/facets/RunsFacetsNav.d.ts.map +1 -1
- package/lib/types/features/facets/VFacetsNav.d.ts +1 -1
- package/lib/types/features/facets/VFacetsNav.d.ts.map +1 -1
- package/lib/types/features/magic-pdf/DownloadPopover.d.ts.map +1 -1
- package/lib/types/features/magic-pdf/PageSlider.d.ts +2 -1
- package/lib/types/features/magic-pdf/PageSlider.d.ts.map +1 -1
- package/lib/types/features/magic-pdf/PdfPageProvider.d.ts +10 -0
- package/lib/types/features/magic-pdf/PdfPageProvider.d.ts.map +1 -1
- package/lib/types/features/magic-pdf/TextPageView.d.ts.map +1 -1
- package/lib/types/features/magic-pdf/types.d.ts +1 -1
- package/lib/types/features/magic-pdf/types.d.ts.map +1 -1
- package/lib/types/shell/apps/AppInstallationProvider.d.ts +12 -0
- package/lib/types/shell/apps/AppInstallationProvider.d.ts.map +1 -0
- package/lib/types/shell/apps/AppProjectSelector.d.ts +12 -0
- package/lib/types/shell/apps/AppProjectSelector.d.ts.map +1 -0
- package/lib/types/shell/apps/StandaloneApp.d.ts +23 -0
- package/lib/types/shell/apps/StandaloneApp.d.ts.map +1 -0
- package/lib/types/shell/apps/index.d.ts +3 -1
- package/lib/types/shell/apps/index.d.ts.map +1 -1
- package/lib/vertesia-ui-features.js +1 -1
- package/lib/vertesia-ui-features.js.map +1 -1
- package/lib/vertesia-ui-shell.js +1 -1
- package/lib/vertesia-ui-shell.js.map +1 -1
- package/package.json +7 -7
- package/src/features/facets/RunsFacetsNav.tsx +17 -5
- package/src/features/facets/VFacetsNav.tsx +1 -1
- package/src/features/magic-pdf/DownloadPopover.tsx +38 -5
- package/src/features/magic-pdf/MagicPdfView.tsx +31 -5
- package/src/features/magic-pdf/PageSlider.tsx +44 -14
- package/src/features/magic-pdf/PdfPageProvider.tsx +81 -0
- package/src/features/magic-pdf/TextPageView.tsx +29 -1
- package/src/features/magic-pdf/types.ts +1 -1
- package/src/features/store/objects/layout/documentLayout.tsx +1 -1
- package/src/shell/apps/AppInstallationProvider.tsx +22 -0
- package/src/shell/apps/AppProjectSelector.tsx +47 -0
- package/src/shell/apps/StandaloneApp.tsx +108 -0
- package/src/shell/apps/index.ts +3 -1
- package/src/shell/login/InviteAcceptModal.tsx +1 -1
- package/lib/esm/shell/apps/CheckAppAccess.js +0 -23
- package/lib/esm/shell/apps/CheckAppAccess.js.map +0 -1
- package/lib/types/shell/apps/CheckAppAccess.d.ts +0 -6
- package/lib/types/shell/apps/CheckAppAccess.d.ts.map +0 -1
- package/src/shell/apps/CheckAppAccess.tsx +0 -25
|
@@ -13,6 +13,7 @@ interface RunsFacetsNavProps {
|
|
|
13
13
|
environments?: any[];
|
|
14
14
|
models?: any[];
|
|
15
15
|
statuses?: any[];
|
|
16
|
+
tags?: any[];
|
|
16
17
|
finish_reason?: any[];
|
|
17
18
|
created_by?: any[];
|
|
18
19
|
};
|
|
@@ -35,16 +36,25 @@ export function useRunsFilterGroups(facets: RunsFacetsNavProps['facets']): Filte
|
|
|
35
36
|
if (facets.environments) {
|
|
36
37
|
const environmentFilterGroup = VEnvironmentFacet({
|
|
37
38
|
buckets: facets.environments || [],
|
|
38
|
-
name: '
|
|
39
|
+
name: 'environments',
|
|
39
40
|
});
|
|
40
41
|
customFilterGroups.push(environmentFilterGroup);
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
// Add tags filter as stringList type (allows custom input)
|
|
45
|
+
const tagsFilterGroup = {
|
|
46
|
+
name: 'tags',
|
|
47
|
+
placeholder: 'Tags',
|
|
48
|
+
type: 'stringList' as const,
|
|
49
|
+
multiple: true
|
|
50
|
+
};
|
|
51
|
+
customFilterGroups.push(tagsFilterGroup);
|
|
52
|
+
|
|
43
53
|
if (facets.models) {
|
|
44
54
|
const modelFilterGroup = VStringFacet({
|
|
45
55
|
search: null as any, // This will be provided by the search context
|
|
46
56
|
buckets: facets.models || [],
|
|
47
|
-
name: '
|
|
57
|
+
name: 'model'
|
|
48
58
|
});
|
|
49
59
|
customFilterGroups.push(modelFilterGroup);
|
|
50
60
|
}
|
|
@@ -53,7 +63,7 @@ export function useRunsFilterGroups(facets: RunsFacetsNavProps['facets']): Filte
|
|
|
53
63
|
const statusFilterGroup = VStringFacet({
|
|
54
64
|
search: null as any, // This will be provided by the search context
|
|
55
65
|
buckets: facets.statuses || [],
|
|
56
|
-
name: '
|
|
66
|
+
name: 'status'
|
|
57
67
|
});
|
|
58
68
|
customFilterGroups.push(statusFilterGroup);
|
|
59
69
|
}
|
|
@@ -105,11 +115,13 @@ export function useRunsFilterGroups(facets: RunsFacetsNavProps['facets']): Filte
|
|
|
105
115
|
export function useRunsFilterHandler(search: SearchInterface) {
|
|
106
116
|
return (newFilters: BaseFilter[]) => {
|
|
107
117
|
if (newFilters.length === 0) {
|
|
108
|
-
|
|
118
|
+
// Clear filters without applying defaults - user wants to remove all filters
|
|
119
|
+
search.clearFilters(true, false);
|
|
109
120
|
return;
|
|
110
121
|
}
|
|
111
122
|
|
|
112
|
-
|
|
123
|
+
// Clear all filters first without defaults, then apply new ones
|
|
124
|
+
search.clearFilters(false, false);
|
|
113
125
|
|
|
114
126
|
newFilters.forEach(filter => {
|
|
115
127
|
if (filter.value && filter.value.length > 0) {
|
|
@@ -4,7 +4,7 @@ import { useState } from 'react';
|
|
|
4
4
|
export interface SearchInterface {
|
|
5
5
|
getFilterValue(name: string): any;
|
|
6
6
|
setFilterValue(name: string, value: any): void;
|
|
7
|
-
clearFilters(autoSearch?: boolean): void;
|
|
7
|
+
clearFilters(autoSearch?: boolean, applyDefaults?: boolean): void;
|
|
8
8
|
search(): Promise<boolean | undefined>;
|
|
9
9
|
readonly isRunning: boolean;
|
|
10
10
|
query: Record<string, any>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ContentObject } from "@vertesia/common";
|
|
1
|
+
import { ContentObject, DocumentMetadata } from "@vertesia/common";
|
|
2
2
|
import { useUserSession } from "@vertesia/ui/session";
|
|
3
3
|
import { Popover } from "@vertesia/ui/widgets";
|
|
4
4
|
import { CloudDownload } from "lucide-react";
|
|
@@ -12,18 +12,51 @@ export function DownloadPopover({ object }: DownloadPopoverProps) {
|
|
|
12
12
|
const onDownload = (name: string) => {
|
|
13
13
|
getResourceUrl(client, object.id, name).then(url => window.open(url, '_blank'));
|
|
14
14
|
}
|
|
15
|
+
|
|
16
|
+
const getProcessorType = (): string => {
|
|
17
|
+
if (object.metadata?.type === "document") {
|
|
18
|
+
const docMetadata = object.metadata as DocumentMetadata;
|
|
19
|
+
return docMetadata.content_processor?.type || "xml";
|
|
20
|
+
}
|
|
21
|
+
return "xml"; // default
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const processorType = getProcessorType();
|
|
25
|
+
|
|
26
|
+
const renderDownloadOptions = () => {
|
|
27
|
+
if (processorType === "markdown") {
|
|
28
|
+
return (
|
|
29
|
+
<button className="p-2 cursor-pointer hover:bg-slate-50 dark:hover:bg-slate-100" onClick={() => onDownload("document.md")}>
|
|
30
|
+
document.md
|
|
31
|
+
</button>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Default XML processor options
|
|
36
|
+
return (
|
|
37
|
+
<>
|
|
38
|
+
<button className="p-2 cursor-pointer hover:bg-slate-50 dark:hover:bg-slate-100" onClick={() => onDownload("annotated.pdf")}>
|
|
39
|
+
annotated.pdf
|
|
40
|
+
</button>
|
|
41
|
+
<button className="p-2 cursor-pointer hover:bg-slate-50 dark:hover:bg-slate-100" onClick={() => onDownload("document.xml")}>
|
|
42
|
+
document.xml
|
|
43
|
+
</button>
|
|
44
|
+
<button className="p-2 cursor-pointer hover:bg-slate-50 dark:hover:bg-slate-100" onClick={() => onDownload("analyzed-pages.json")}>
|
|
45
|
+
analyzed-pages.json
|
|
46
|
+
</button>
|
|
47
|
+
</>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
|
|
15
51
|
return (
|
|
16
52
|
<div className="absolute bottom-[58px] right-[20px] w-[36px] h-[36px] cursor-pointer text-indigo-400 border-indigo-400 hover:border-indigo-500 hover:text-indigo-500 border-2 rounded-full shadow-xs flex items-center justify-center">
|
|
17
53
|
<Popover strategy='absolute' placement='top-end' zIndex={100} offset={20}>
|
|
18
54
|
<Popover.Trigger click>
|
|
19
55
|
<CloudDownload className='size-6' />
|
|
20
|
-
|
|
21
56
|
</Popover.Trigger>
|
|
22
57
|
<Popover.Content>
|
|
23
58
|
<div className="rounded-md shadow-md border border-gray-100 bg-white dark:bg-slate-50 dark:border-slate-100 min-w-[200px] flex flex-col divide-y">
|
|
24
|
-
|
|
25
|
-
<button className="p-2 cursor-pointer hover:bg-slate-50 dark:hover:bg-slate-100" onClick={() => onDownload("document.xml")}>document.xml</button>
|
|
26
|
-
<button className="p-2 cursor-pointer hover:bg-slate-50 dark:hover:bg-slate-100" onClick={() => onDownload("analyzed-pages.json")}>analyzed-pages.json</button>
|
|
59
|
+
{renderDownloadOptions()}
|
|
27
60
|
</div>
|
|
28
61
|
</Popover.Content>
|
|
29
62
|
</Popover>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ContentObject } from "@vertesia/common";
|
|
1
|
+
import { ContentObject, DocumentMetadata } from "@vertesia/common";
|
|
2
2
|
import { ErrorBox, useFetch } from "@vertesia/ui/core";
|
|
3
3
|
import { useUserSession } from "@vertesia/ui/session";
|
|
4
4
|
import { X } from "lucide-react";
|
|
@@ -39,8 +39,27 @@ interface _MagicPdfViewProps {
|
|
|
39
39
|
onClose?: () => void;
|
|
40
40
|
}
|
|
41
41
|
function MagicPdfViewImpl({ object, onClose }: _MagicPdfViewProps) {
|
|
42
|
-
const
|
|
42
|
+
const getInitialViewType = (): ViewType => {
|
|
43
|
+
if (object.metadata?.type === "document") {
|
|
44
|
+
const docMetadata = object.metadata as DocumentMetadata;
|
|
45
|
+
const processorType = docMetadata.content_processor?.type;
|
|
46
|
+
if (processorType === "markdown") return "markdown";
|
|
47
|
+
if (processorType === "xml") return "xml";
|
|
48
|
+
}
|
|
49
|
+
return "xml"; // default
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const getProcessorType = (): string => {
|
|
53
|
+
if (object.metadata?.type === "document") {
|
|
54
|
+
const docMetadata = object.metadata as DocumentMetadata;
|
|
55
|
+
return docMetadata.content_processor?.type || "xml";
|
|
56
|
+
}
|
|
57
|
+
return "xml"; // default
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const [viewType, setViewType] = useState<ViewType>(getInitialViewType());
|
|
43
61
|
const [pageNumber, setPageNumber] = useState(1);
|
|
62
|
+
const processorType = getProcessorType();
|
|
44
63
|
const handler = useRef<HTMLDivElement>(null);
|
|
45
64
|
const left = useRef<HTMLDivElement>(null);
|
|
46
65
|
const right = useRef<HTMLDivElement>(null);
|
|
@@ -51,14 +70,14 @@ function MagicPdfViewImpl({ object, onClose }: _MagicPdfViewProps) {
|
|
|
51
70
|
return (
|
|
52
71
|
<>
|
|
53
72
|
<div ref={left} className={`absolute top-0 left-0 bottom-0 w-[50%] bg-gray-100 dark:bg-slate-800 flex items-stretch justify-stretch py-2`}>
|
|
54
|
-
<PageSlider className="flex-1" currentPage={pageNumber} onChange={setPageNumber} />
|
|
73
|
+
<PageSlider className="flex-1" currentPage={pageNumber} onChange={setPageNumber} object={object} />
|
|
55
74
|
<div ref={handler} className='w-[2px] p-[2px] m-0 bg-slate-300 cursor-ew-resize'></div>
|
|
56
75
|
</div>
|
|
57
76
|
<div ref={right} className={`absolute top-0 left-[50%] right-0 bottom-0 flex items-stretch justify-stretch overflow-auto p-2`}>
|
|
58
77
|
<TextPageView pageNumber={pageNumber} viewType={viewType} />
|
|
59
78
|
</div>
|
|
60
79
|
<DownloadPopover object={object} />
|
|
61
|
-
<ContentSwitcher type={viewType} onSwitch={setViewType} />
|
|
80
|
+
{processorType === "xml" && <ContentSwitcher type={viewType} onSwitch={setViewType} />}
|
|
62
81
|
{!!onClose &&
|
|
63
82
|
<div className="absolute top-6 right-7 w-9 h-9 cursor-pointer text-red-400 border-red-400 hover:border-red-500 hover:text-red-500 border-2 rounded-full shadow-xs flex items-center justify-center"
|
|
64
83
|
onClick={onClose}>
|
|
@@ -80,6 +99,8 @@ function ContentSwitcher({ type = "xml", onSwitch }: ContentSwitcherProps) {
|
|
|
80
99
|
if (type === "xml") {
|
|
81
100
|
onSwitch("json");
|
|
82
101
|
} else if (type === "json") {
|
|
102
|
+
onSwitch("markdown");
|
|
103
|
+
} else if (type === "markdown") {
|
|
83
104
|
onSwitch("xml");
|
|
84
105
|
}
|
|
85
106
|
}
|
|
@@ -87,7 +108,8 @@ function ContentSwitcher({ type = "xml", onSwitch }: ContentSwitcherProps) {
|
|
|
87
108
|
<div className="absolute bottom-[16px] right-[20px] w-[36px] h-[36px] cursor-pointer text-indigo-400 border-indigo-400 hover:border-indigo-500 hover:text-indigo-500 border-2 rounded-full shadow-xs flex items-center justify-center"
|
|
88
109
|
onClick={_onSwitch}>
|
|
89
110
|
{type === "xml" && JSON}
|
|
90
|
-
{type === "json" &&
|
|
111
|
+
{type === "json" && MARKDOWN}
|
|
112
|
+
{type === "markdown" && XML}
|
|
91
113
|
</div>
|
|
92
114
|
)
|
|
93
115
|
|
|
@@ -100,3 +122,7 @@ const JSON = <svg width="16px" height="16px" viewBox="0 0 16 16" xmlns="http://w
|
|
|
100
122
|
const XML = <svg width="16px" height="16px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="currentColor">
|
|
101
123
|
<path d="M4.708 5.578L2.061 8.224l2.647 2.646-.708.708-3-3V7.87l3-3 .708.708zm7-.708L11 5.578l2.647 2.646L11 10.87l.708.708 3-3V7.87l-3-3zM4.908 13l.894.448 5-10L9.908 3l-5 10z" />
|
|
102
124
|
</svg>
|
|
125
|
+
|
|
126
|
+
const MARKDOWN = <svg width="16px" height="16px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="currentColor">
|
|
127
|
+
<path d="M14.85 3H1.15C.52 3 0 3.52 0 4.15v7.69C0 12.48.52 13 1.15 13h13.69c.64 0 1.15-.52 1.15-1.15v-7.7C16 3.52 15.48 3 14.85 3zM9 11H7.5L5.5 9l-1 1.5H3V5h1.5l1 2 2-2H9v6zm2.99.5L9.5 8H11V5h1v3h1.5l-2.51 3.5z"/>
|
|
128
|
+
</svg>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DocumentMetadata } from "@vertesia/common";
|
|
1
2
|
import { Center } from "@vertesia/ui/core";
|
|
2
3
|
import clsx from "clsx";
|
|
3
4
|
import { AtSignIcon, ChevronsDown, ChevronsUp, ImageIcon, InfoIcon } from "lucide-react";
|
|
@@ -6,7 +7,8 @@ import { usePdfPagesInfo } from "./PdfPageProvider";
|
|
|
6
7
|
|
|
7
8
|
enum ImageType {
|
|
8
9
|
default,
|
|
9
|
-
|
|
10
|
+
original,
|
|
11
|
+
instrumented,
|
|
10
12
|
annotated,
|
|
11
13
|
}
|
|
12
14
|
|
|
@@ -14,11 +16,25 @@ interface PageSliderProps {
|
|
|
14
16
|
currentPage: number;
|
|
15
17
|
onChange: (pageNumber: number) => void;
|
|
16
18
|
className?: string;
|
|
19
|
+
object: any; // ContentObject type
|
|
17
20
|
}
|
|
18
|
-
export function PageSlider({ className, currentPage, onChange }: PageSliderProps) {
|
|
19
|
-
const
|
|
21
|
+
export function PageSlider({ className, currentPage, onChange, object }: PageSliderProps) {
|
|
22
|
+
const getProcessorType = (): string => {
|
|
23
|
+
if (object.metadata?.type === "document") {
|
|
24
|
+
const docMetadata = object.metadata as DocumentMetadata;
|
|
25
|
+
return docMetadata.content_processor?.type || "xml";
|
|
26
|
+
}
|
|
27
|
+
return "xml"; // default
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const getDefaultImageType = (): ImageType => {
|
|
31
|
+
const processorType = getProcessorType();
|
|
32
|
+
return processorType === "markdown" ? ImageType.original : ImageType.default;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const [imageType, setImageType] = useState<ImageType>(getDefaultImageType());
|
|
20
36
|
const ref = useRef<HTMLDivElement>(null);
|
|
21
|
-
const { urls, annotatedUrls, instrumentedUrls } = usePdfPagesInfo();
|
|
37
|
+
const { urls, originalUrls, annotatedUrls, instrumentedUrls } = usePdfPagesInfo();
|
|
22
38
|
const goPrev = () => {
|
|
23
39
|
if (currentPage > 1) {
|
|
24
40
|
onChange(currentPage - 1);
|
|
@@ -46,7 +62,8 @@ export function PageSlider({ className, currentPage, onChange }: PageSliderProps
|
|
|
46
62
|
}
|
|
47
63
|
|
|
48
64
|
const actualUrls = imageType === ImageType.instrumented ? instrumentedUrls :
|
|
49
|
-
(imageType === ImageType.annotated ? annotatedUrls :
|
|
65
|
+
(imageType === ImageType.annotated ? annotatedUrls :
|
|
66
|
+
(imageType === ImageType.original ? originalUrls : urls));
|
|
50
67
|
|
|
51
68
|
return (
|
|
52
69
|
<div ref={ref} className={clsx('flex flex-col items-stretch gap-y-2', className)}>
|
|
@@ -56,15 +73,28 @@ export function PageSlider({ className, currentPage, onChange }: PageSliderProps
|
|
|
56
73
|
<ChevronsUp className='w-5 h-5' />
|
|
57
74
|
</button>
|
|
58
75
|
<div className="absolute right-3 flex gap-x-1">
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
76
|
+
{getProcessorType() === "markdown" ? (
|
|
77
|
+
<>
|
|
78
|
+
<button className={getRadioButtonClass(ImageType.original, imageType)}
|
|
79
|
+
onClick={() => setImageType(ImageType.original)}
|
|
80
|
+
><ImageIcon className="w-5 h-5 mt-1" /></button>
|
|
81
|
+
<button className={getRadioButtonClass(ImageType.instrumented, imageType)}
|
|
82
|
+
onClick={() => setImageType(ImageType.instrumented)}
|
|
83
|
+
><InfoIcon className="w-5 h-5 mt-1" /></button>
|
|
84
|
+
</>
|
|
85
|
+
) : (
|
|
86
|
+
<>
|
|
87
|
+
<button className={getRadioButtonClass(ImageType.default, imageType)}
|
|
88
|
+
onClick={() => setImageType(ImageType.default)}
|
|
89
|
+
><ImageIcon className="w-5 h-5 mt-1" /></button>
|
|
90
|
+
<button className={getRadioButtonClass(ImageType.instrumented, imageType)}
|
|
91
|
+
onClick={() => setImageType(ImageType.instrumented)}
|
|
92
|
+
><InfoIcon className="w-5 h-5 mt-1" /></button>
|
|
93
|
+
<button className={getRadioButtonClass(ImageType.annotated, imageType)}
|
|
94
|
+
onClick={() => setImageType(ImageType.annotated)}
|
|
95
|
+
><AtSignIcon className="w-5 h-5 mt-1" /></button>
|
|
96
|
+
</>
|
|
97
|
+
)}
|
|
68
98
|
</div>
|
|
69
99
|
</div>
|
|
70
100
|
<div className='flex flex-col items-center gap-2 flex-1 overflow-y-auto px-2'>
|
|
@@ -13,9 +13,11 @@ const ADVANCED_PROCESSING_PREFIX = "magic-pdf";
|
|
|
13
13
|
interface PdfPagesInfo {
|
|
14
14
|
count: number;
|
|
15
15
|
urls: string[];
|
|
16
|
+
originalUrls: string[];
|
|
16
17
|
annotatedUrls: string[];
|
|
17
18
|
instrumentedUrls: string[];
|
|
18
19
|
layoutProvider: PageLayoutProvider;
|
|
20
|
+
markdownProvider: PageMarkdownProvider;
|
|
19
21
|
xml: string;
|
|
20
22
|
xmlPages: string[];
|
|
21
23
|
}
|
|
@@ -54,6 +56,40 @@ class PageLayoutProvider {
|
|
|
54
56
|
}
|
|
55
57
|
}
|
|
56
58
|
|
|
59
|
+
class PageMarkdownProvider {
|
|
60
|
+
markdownUrls: string[] = [];
|
|
61
|
+
cache: string[];
|
|
62
|
+
constructor(public totalPages: number) {
|
|
63
|
+
this.cache = new Array<string>(totalPages);
|
|
64
|
+
}
|
|
65
|
+
async loadUrls(vertesia: VertesiaClient, objectId: string) {
|
|
66
|
+
const markdownPromises: Promise<GetFileUrlResponse>[] = [];
|
|
67
|
+
for (let i = 0; i < this.totalPages; i++) {
|
|
68
|
+
markdownPromises.push(getMarkdownUrlForPage(vertesia, objectId, i + 1));
|
|
69
|
+
}
|
|
70
|
+
const markdownUrls = await Promise.all(markdownPromises);
|
|
71
|
+
this.markdownUrls = markdownUrls.map((r) => r.url);
|
|
72
|
+
}
|
|
73
|
+
async getPageMarkdown(page: number) {
|
|
74
|
+
const index = page - 1;
|
|
75
|
+
let content = this.cache[index];
|
|
76
|
+
if (content === undefined) {
|
|
77
|
+
const url = this.markdownUrls[index];
|
|
78
|
+
content = await fetch(url, { method: "GET" }).then((r) => {
|
|
79
|
+
if (r.ok) {
|
|
80
|
+
return r.text();
|
|
81
|
+
} else {
|
|
82
|
+
throw new Error(
|
|
83
|
+
"Failed to fetch markdown: " + r.statusText,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
this.cache[index] = content;
|
|
88
|
+
}
|
|
89
|
+
return content;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
57
93
|
const PdfPageContext = createContext<PdfPagesInfo | undefined>(undefined);
|
|
58
94
|
|
|
59
95
|
interface PdfPageProviderProps {
|
|
@@ -103,10 +139,22 @@ function getPageInstrumentedImagePath(
|
|
|
103
139
|
return `${getBasePath(objectId)}/pages/page-${pageNumber}.instrumented${ext}`;
|
|
104
140
|
}
|
|
105
141
|
|
|
142
|
+
function getPageOriginalImagePath(
|
|
143
|
+
objectId: string,
|
|
144
|
+
pageNumber: number,
|
|
145
|
+
ext = ".jpg",
|
|
146
|
+
) {
|
|
147
|
+
return `${getBasePath(objectId)}/pages/page-${pageNumber}.original${ext}`;
|
|
148
|
+
}
|
|
149
|
+
|
|
106
150
|
function getLayoutJsonPath(objectId: string, pageNumber: number) {
|
|
107
151
|
return `${getBasePath(objectId)}/pages/page-${pageNumber}.layout.json`;
|
|
108
152
|
}
|
|
109
153
|
|
|
154
|
+
function getMarkdownPath(objectId: string, pageNumber: number) {
|
|
155
|
+
return `${getBasePath(objectId)}/pages/page-${pageNumber}.md`;
|
|
156
|
+
}
|
|
157
|
+
|
|
110
158
|
export function getResourceUrl(
|
|
111
159
|
vertesia: VertesiaClient,
|
|
112
160
|
objectId: string,
|
|
@@ -147,6 +195,16 @@ function getInstrumentedImageUrlForPage(
|
|
|
147
195
|
);
|
|
148
196
|
}
|
|
149
197
|
|
|
198
|
+
function getOriginalImageUrlForPage(
|
|
199
|
+
vertesia: VertesiaClient,
|
|
200
|
+
objectId: string,
|
|
201
|
+
pageNumber: number,
|
|
202
|
+
): Promise<GetFileUrlResponse> {
|
|
203
|
+
return vertesia.files.getDownloadUrl(
|
|
204
|
+
getPageOriginalImagePath(objectId, pageNumber),
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
150
208
|
function getLayoutUrlForPage(
|
|
151
209
|
vertesia: VertesiaClient,
|
|
152
210
|
objectId: string,
|
|
@@ -157,6 +215,16 @@ function getLayoutUrlForPage(
|
|
|
157
215
|
);
|
|
158
216
|
}
|
|
159
217
|
|
|
218
|
+
function getMarkdownUrlForPage(
|
|
219
|
+
vertesia: VertesiaClient,
|
|
220
|
+
objectId: string,
|
|
221
|
+
pageNumber: number,
|
|
222
|
+
): Promise<GetFileUrlResponse> {
|
|
223
|
+
return vertesia.files.getDownloadUrl(
|
|
224
|
+
getMarkdownPath(objectId, pageNumber),
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
160
228
|
async function getPdfPagesInfo(
|
|
161
229
|
vertesia: VertesiaClient,
|
|
162
230
|
object: ContentObject,
|
|
@@ -186,17 +254,30 @@ async function getPdfPagesInfo(
|
|
|
186
254
|
instrumentedImageUrlPromises,
|
|
187
255
|
);
|
|
188
256
|
|
|
257
|
+
const originalImageUrlPromises: Promise<GetFileUrlResponse>[] = [];
|
|
258
|
+
for (let i = 0; i < page_count; i++) {
|
|
259
|
+
originalImageUrlPromises.push(
|
|
260
|
+
getOriginalImageUrlForPage(vertesia, object.id, i + 1),
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
const originalImageUrls = await Promise.all(originalImageUrlPromises);
|
|
264
|
+
|
|
189
265
|
const layoutProvider = new PageLayoutProvider(page_count);
|
|
190
266
|
await layoutProvider.loadUrls(vertesia, object.id);
|
|
191
267
|
|
|
268
|
+
const markdownProvider = new PageMarkdownProvider(page_count);
|
|
269
|
+
await markdownProvider.loadUrls(vertesia, object.id);
|
|
270
|
+
|
|
192
271
|
const xml = object.text ? cleanXml(object.text) : "";
|
|
193
272
|
|
|
194
273
|
return {
|
|
195
274
|
count: page_count,
|
|
196
275
|
urls: imageUrls.map((r) => r.url),
|
|
276
|
+
originalUrls: originalImageUrls.map((r) => r.url),
|
|
197
277
|
annotatedUrls: annotatedImageUrls.map((r) => r.url),
|
|
198
278
|
instrumentedUrls: instrumentedImageUrls.map((r) => r.url),
|
|
199
279
|
layoutProvider,
|
|
280
|
+
markdownProvider,
|
|
200
281
|
xml,
|
|
201
282
|
xmlPages: object.text ? extractXmlPages(xml) : [],
|
|
202
283
|
};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { JSONCode, Theme, XMLViewer } from '@vertesia/ui/widgets';
|
|
2
2
|
import { useEffect, useLayoutEffect, useState } from "react";
|
|
3
|
+
import Markdown from "react-markdown";
|
|
4
|
+
import remarkGfm from "remark-gfm";
|
|
3
5
|
import { usePdfPagesInfo } from "./PdfPageProvider";
|
|
4
6
|
import { ViewType } from "./types";
|
|
5
7
|
|
|
@@ -19,7 +21,14 @@ interface TextPageViewProps {
|
|
|
19
21
|
viewType: ViewType;
|
|
20
22
|
}
|
|
21
23
|
export function TextPageView({ viewType, pageNumber }: TextPageViewProps) {
|
|
22
|
-
|
|
24
|
+
switch (viewType) {
|
|
25
|
+
case "json":
|
|
26
|
+
return <JsonPageLayoutView pageNumber={pageNumber} />;
|
|
27
|
+
case "markdown":
|
|
28
|
+
return <MarkdownPageView pageNumber={pageNumber} />;
|
|
29
|
+
default:
|
|
30
|
+
return <XmlPageView pageNumber={pageNumber} />;
|
|
31
|
+
}
|
|
23
32
|
}
|
|
24
33
|
|
|
25
34
|
interface XmlPageViewProps {
|
|
@@ -62,3 +71,22 @@ function JsonPageLayoutView({ pageNumber }: JsonPageLayoutViewProps) {
|
|
|
62
71
|
content && <JSONCode className="w-full" data={content} />
|
|
63
72
|
)
|
|
64
73
|
}
|
|
74
|
+
|
|
75
|
+
interface MarkdownPageViewProps {
|
|
76
|
+
pageNumber: number;
|
|
77
|
+
}
|
|
78
|
+
function MarkdownPageView({ pageNumber }: MarkdownPageViewProps) {
|
|
79
|
+
const [content, setContent] = useState<string>();
|
|
80
|
+
const { markdownProvider } = usePdfPagesInfo();
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
markdownProvider.getPageMarkdown(pageNumber).then(setContent).catch((err: any) => {
|
|
83
|
+
console.error(err);
|
|
84
|
+
setContent(undefined);
|
|
85
|
+
});
|
|
86
|
+
}, [pageNumber]);
|
|
87
|
+
return (
|
|
88
|
+
<div className="px-4 py-2 prose prose-sm max-w-none dark:prose-invert">
|
|
89
|
+
{content ? <Markdown remarkPlugins={[remarkGfm]}>{content}</Markdown> : <div>No markdown content available</div>}
|
|
90
|
+
</div>
|
|
91
|
+
)
|
|
92
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export type ViewType = "xml" | "json";
|
|
1
|
+
export type ViewType = "xml" | "json" | "markdown";
|
|
@@ -28,7 +28,7 @@ export function DocumentTableView({ objects, selection, isLoading, onRowClick, c
|
|
|
28
28
|
))}
|
|
29
29
|
</tr>
|
|
30
30
|
</thead>
|
|
31
|
-
<TBody isLoading={isLoading} columns={columns.length}>
|
|
31
|
+
<TBody isLoading={isLoading} columns={columns.length + 1}>
|
|
32
32
|
{
|
|
33
33
|
objects?.map((obj: ContentObjectItem) => {
|
|
34
34
|
return (
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { AppInstallationWithManifest } from "@vertesia/common";
|
|
2
|
+
import { createContext, ReactNode, useContext } from "react";
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export const AppInstallationContext = createContext<AppInstallationWithManifest | null>(null);
|
|
6
|
+
|
|
7
|
+
export function AppInstallationProvider({ installation, children }: { installation: AppInstallationWithManifest, children: ReactNode }) {
|
|
8
|
+
return (
|
|
9
|
+
<AppInstallationContext.Provider value={installation}>
|
|
10
|
+
{children}
|
|
11
|
+
</AppInstallationContext.Provider>
|
|
12
|
+
)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Get the current app installation obejct when called in an app context otheriwse returns null
|
|
17
|
+
*/
|
|
18
|
+
export function useAppInstallation() {
|
|
19
|
+
return useContext(AppInstallationContext);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { ProjectRef, RequireAtLeastOne } from "@vertesia/common";
|
|
2
|
+
import { SelectBox, useFetch } from "@vertesia/ui/core";
|
|
3
|
+
import { useUserSession } from "@vertesia/ui/session";
|
|
4
|
+
import { useState } from "react";
|
|
5
|
+
|
|
6
|
+
interface AppProjectSelectorProps {
|
|
7
|
+
app: RequireAtLeastOne<{ id?: string, name?: string }, 'id' | 'name'>;
|
|
8
|
+
onChange: (value: ProjectRef) => void;
|
|
9
|
+
placeholder?: string;
|
|
10
|
+
}
|
|
11
|
+
export function AppProjectSelector({ app, onChange, placeholder }: AppProjectSelectorProps) {
|
|
12
|
+
const { client, project } = useUserSession();
|
|
13
|
+
const { data: projects, error } = useFetch(() => {
|
|
14
|
+
return client.apps.getAppInstallationProjects(app);
|
|
15
|
+
}, [app.id, app.name])
|
|
16
|
+
|
|
17
|
+
if (error) {
|
|
18
|
+
return <span className='text-red-600'>Error: failed to fetch projects: {error.message}</span>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return <SelectProject placeholder={placeholder} initialValue={project?.id} projects={projects || []} onChange={onChange} />
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface SelectProjectProps {
|
|
25
|
+
initialValue?: string
|
|
26
|
+
projects: ProjectRef[]
|
|
27
|
+
onChange: (value: ProjectRef) => void
|
|
28
|
+
placeholder?: string;
|
|
29
|
+
}
|
|
30
|
+
function SelectProject({ initialValue, projects, onChange, placeholder = "Select Project" }: Readonly<SelectProjectProps>) {
|
|
31
|
+
const [value, setValue] = useState<ProjectRef | undefined>(() => {
|
|
32
|
+
return initialValue ? projects.find(p => p.id === initialValue) : undefined
|
|
33
|
+
});
|
|
34
|
+
const _onChange = (value: ProjectRef) => {
|
|
35
|
+
setValue(value)
|
|
36
|
+
onChange(value)
|
|
37
|
+
}
|
|
38
|
+
return (
|
|
39
|
+
<SelectBox
|
|
40
|
+
by="id"
|
|
41
|
+
value={value}
|
|
42
|
+
options={projects}
|
|
43
|
+
optionLabel={(option) => option.name}
|
|
44
|
+
placeholder={placeholder}
|
|
45
|
+
onChange={_onChange} />
|
|
46
|
+
)
|
|
47
|
+
}
|