@vertesia/ui 0.73.0 → 0.76.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.
Files changed (157) hide show
  1. package/lib/esm/core/components/Center.js +1 -1
  2. package/lib/esm/core/components/Center.js.map +1 -1
  3. package/lib/esm/core/components/Overlay.js +57 -0
  4. package/lib/esm/core/components/Overlay.js.map +1 -0
  5. package/lib/esm/core/components/SidePanel.js +6 -6
  6. package/lib/esm/core/components/SidePanel.js.map +1 -1
  7. package/lib/esm/core/components/index.js +1 -0
  8. package/lib/esm/core/components/index.js.map +1 -1
  9. package/lib/esm/core/components/shadcn/tabs.js +43 -5
  10. package/lib/esm/core/components/shadcn/tabs.js.map +1 -1
  11. package/lib/esm/features/agent/PayloadBuilder.js +9 -2
  12. package/lib/esm/features/agent/PayloadBuilder.js.map +1 -1
  13. package/lib/esm/features/facets/DocumentsFacetsNav.js +4 -2
  14. package/lib/esm/features/facets/DocumentsFacetsNav.js.map +1 -1
  15. package/lib/esm/features/facets/VTypeFacet.js +2 -1
  16. package/lib/esm/features/facets/VTypeFacet.js.map +1 -1
  17. package/lib/esm/features/magic-pdf/DownloadPopover.js +17 -2
  18. package/lib/esm/features/magic-pdf/DownloadPopover.js.map +1 -1
  19. package/lib/esm/features/magic-pdf/MagicPdfView.js +26 -3
  20. package/lib/esm/features/magic-pdf/MagicPdfView.js.map +1 -1
  21. package/lib/esm/features/magic-pdf/PageSlider.js +21 -8
  22. package/lib/esm/features/magic-pdf/PageSlider.js.map +1 -1
  23. package/lib/esm/features/magic-pdf/PdfPageProvider.js +55 -0
  24. package/lib/esm/features/magic-pdf/PdfPageProvider.js.map +1 -1
  25. package/lib/esm/features/magic-pdf/TextPageView.js +20 -2
  26. package/lib/esm/features/magic-pdf/TextPageView.js.map +1 -1
  27. package/lib/esm/features/store/collections/CreateCollection.js +1 -1
  28. package/lib/esm/features/store/collections/CreateCollection.js.map +1 -1
  29. package/lib/esm/features/store/objects/DocumentPreviewPanel.js +2 -4
  30. package/lib/esm/features/store/objects/DocumentPreviewPanel.js.map +1 -1
  31. package/lib/esm/features/store/objects/DocumentSearchResults.js +19 -21
  32. package/lib/esm/features/store/objects/DocumentSearchResults.js.map +1 -1
  33. package/lib/esm/features/store/objects/components/ContentOverview.js +2 -4
  34. package/lib/esm/features/store/objects/components/ContentOverview.js.map +1 -1
  35. package/lib/esm/features/store/objects/components/VectorSearchWidget.js +51 -46
  36. package/lib/esm/features/store/objects/components/VectorSearchWidget.js.map +1 -1
  37. package/lib/esm/features/store/objects/layout/documentLayout.js +1 -1
  38. package/lib/esm/features/store/objects/layout/documentLayout.js.map +1 -1
  39. package/lib/esm/features/store/objects/search/DocumentSearchContext.js +50 -34
  40. package/lib/esm/features/store/objects/search/DocumentSearchContext.js.map +1 -1
  41. package/lib/esm/features/store/objects/search/DocumentSearchProvider.js +1 -3
  42. package/lib/esm/features/store/objects/search/DocumentSearchProvider.js.map +1 -1
  43. package/lib/esm/features/store/objects/upload/useSmartFileUploadProcessing.js +4 -11
  44. package/lib/esm/features/store/objects/upload/useSmartFileUploadProcessing.js.map +1 -1
  45. package/lib/esm/features/user/UserInfo.js +2 -2
  46. package/lib/esm/features/user/UserInfo.js.map +1 -1
  47. package/lib/esm/session/UserSessionProvider.js +6 -3
  48. package/lib/esm/session/UserSessionProvider.js.map +1 -1
  49. package/lib/esm/session/auth/composable.js +3 -3
  50. package/lib/esm/session/auth/composable.js.map +1 -1
  51. package/lib/esm/session/auth/firebase.js +7 -0
  52. package/lib/esm/session/auth/firebase.js.map +1 -1
  53. package/lib/esm/session/auth/useAuthState.js +0 -3
  54. package/lib/esm/session/auth/useAuthState.js.map +1 -1
  55. package/lib/esm/shell/apps/StandaloneApp.js +1 -1
  56. package/lib/esm/shell/apps/StandaloneApp.js.map +1 -1
  57. package/lib/esm/shell/login/EnterpriseSigninButton.js +3 -0
  58. package/lib/esm/shell/login/EnterpriseSigninButton.js.map +1 -1
  59. package/lib/esm/shell/login/InviteAcceptModal.js +1 -1
  60. package/lib/esm/shell/login/InviteAcceptModal.js.map +1 -1
  61. package/lib/esm/widgets/form/ManagedObject.js +1 -1
  62. package/lib/esm/widgets/index.js +1 -0
  63. package/lib/esm/widgets/index.js.map +1 -1
  64. package/lib/esm/widgets/markdown/MarkdownRenderer.js +24 -0
  65. package/lib/esm/widgets/markdown/MarkdownRenderer.js.map +1 -0
  66. package/lib/esm/widgets/markdown/index.js +2 -0
  67. package/lib/esm/widgets/markdown/index.js.map +1 -0
  68. package/lib/tsconfig.tsbuildinfo +1 -1
  69. package/lib/types/core/components/Overlay.d.ts +25 -0
  70. package/lib/types/core/components/Overlay.d.ts.map +1 -0
  71. package/lib/types/core/components/SidePanel.d.ts +3 -1
  72. package/lib/types/core/components/SidePanel.d.ts.map +1 -1
  73. package/lib/types/core/components/index.d.ts +1 -0
  74. package/lib/types/core/components/index.d.ts.map +1 -1
  75. package/lib/types/core/components/shadcn/tabs.d.ts.map +1 -1
  76. package/lib/types/env/index.d.ts +2 -2
  77. package/lib/types/env/index.d.ts.map +1 -1
  78. package/lib/types/features/agent/PayloadBuilder.d.ts.map +1 -1
  79. package/lib/types/features/facets/DocumentsFacetsNav.d.ts.map +1 -1
  80. package/lib/types/features/facets/VTypeFacet.d.ts.map +1 -1
  81. package/lib/types/features/magic-pdf/DownloadPopover.d.ts.map +1 -1
  82. package/lib/types/features/magic-pdf/PageSlider.d.ts +2 -1
  83. package/lib/types/features/magic-pdf/PageSlider.d.ts.map +1 -1
  84. package/lib/types/features/magic-pdf/PdfPageProvider.d.ts +10 -0
  85. package/lib/types/features/magic-pdf/PdfPageProvider.d.ts.map +1 -1
  86. package/lib/types/features/magic-pdf/TextPageView.d.ts.map +1 -1
  87. package/lib/types/features/magic-pdf/types.d.ts +1 -1
  88. package/lib/types/features/magic-pdf/types.d.ts.map +1 -1
  89. package/lib/types/features/store/objects/DocumentPreviewPanel.d.ts.map +1 -1
  90. package/lib/types/features/store/objects/DocumentSearchResults.d.ts.map +1 -1
  91. package/lib/types/features/store/objects/components/ContentOverview.d.ts.map +1 -1
  92. package/lib/types/features/store/objects/components/VectorSearchWidget.d.ts +4 -3
  93. package/lib/types/features/store/objects/components/VectorSearchWidget.d.ts.map +1 -1
  94. package/lib/types/features/store/objects/search/DocumentSearchContext.d.ts +5 -2
  95. package/lib/types/features/store/objects/search/DocumentSearchContext.d.ts.map +1 -1
  96. package/lib/types/features/store/objects/search/DocumentSearchProvider.d.ts +2 -4
  97. package/lib/types/features/store/objects/search/DocumentSearchProvider.d.ts.map +1 -1
  98. package/lib/types/features/store/objects/upload/useSmartFileUploadProcessing.d.ts.map +1 -1
  99. package/lib/types/session/UserSessionProvider.d.ts.map +1 -1
  100. package/lib/types/session/auth/composable.d.ts +1 -1
  101. package/lib/types/session/auth/composable.d.ts.map +1 -1
  102. package/lib/types/session/auth/firebase.d.ts.map +1 -1
  103. package/lib/types/session/auth/useAuthState.d.ts.map +1 -1
  104. package/lib/types/shell/login/EnterpriseSigninButton.d.ts.map +1 -1
  105. package/lib/types/widgets/index.d.ts +1 -0
  106. package/lib/types/widgets/index.d.ts.map +1 -1
  107. package/lib/types/widgets/markdown/MarkdownRenderer.d.ts +9 -0
  108. package/lib/types/widgets/markdown/MarkdownRenderer.d.ts.map +1 -0
  109. package/lib/types/widgets/markdown/index.d.ts +2 -0
  110. package/lib/types/widgets/markdown/index.d.ts.map +1 -0
  111. package/lib/vertesia-ui-core.js +1 -1
  112. package/lib/vertesia-ui-core.js.map +1 -1
  113. package/lib/vertesia-ui-features.js +1 -1
  114. package/lib/vertesia-ui-features.js.map +1 -1
  115. package/lib/vertesia-ui-session.js +1 -1
  116. package/lib/vertesia-ui-session.js.map +1 -1
  117. package/lib/vertesia-ui-shell.js +1 -1
  118. package/lib/vertesia-ui-shell.js.map +1 -1
  119. package/lib/vertesia-ui-widgets.js +1 -1
  120. package/lib/vertesia-ui-widgets.js.map +1 -1
  121. package/package.json +6 -4
  122. package/src/core/components/Center.tsx +1 -1
  123. package/src/core/components/Overlay.tsx +129 -0
  124. package/src/core/components/SidePanel.tsx +38 -33
  125. package/src/core/components/index.ts +1 -0
  126. package/src/core/components/shadcn/tabs.tsx +48 -5
  127. package/src/env/index.ts +1 -1
  128. package/src/features/agent/PayloadBuilder.tsx +8 -2
  129. package/src/features/facets/DocumentsFacetsNav.tsx +4 -2
  130. package/src/features/facets/VTypeFacet.tsx +2 -1
  131. package/src/features/magic-pdf/DownloadPopover.tsx +38 -5
  132. package/src/features/magic-pdf/MagicPdfView.tsx +31 -5
  133. package/src/features/magic-pdf/PageSlider.tsx +44 -14
  134. package/src/features/magic-pdf/PdfPageProvider.tsx +81 -0
  135. package/src/features/magic-pdf/TextPageView.tsx +29 -2
  136. package/src/features/magic-pdf/types.ts +1 -1
  137. package/src/features/store/collections/CreateCollection.tsx +1 -1
  138. package/src/features/store/objects/DocumentPreviewPanel.tsx +2 -4
  139. package/src/features/store/objects/DocumentSearchResults.tsx +24 -26
  140. package/src/features/store/objects/components/ContentOverview.tsx +3 -6
  141. package/src/features/store/objects/components/VectorSearchWidget.tsx +88 -60
  142. package/src/features/store/objects/layout/documentLayout.tsx +1 -1
  143. package/src/features/store/objects/search/DocumentSearchContext.ts +57 -36
  144. package/src/features/store/objects/search/DocumentSearchProvider.tsx +2 -6
  145. package/src/features/store/objects/upload/useSmartFileUploadProcessing.ts +6 -12
  146. package/src/features/user/UserInfo.tsx +2 -2
  147. package/src/session/UserSessionProvider.tsx +5 -3
  148. package/src/session/auth/composable.ts +3 -3
  149. package/src/session/auth/firebase.ts +8 -0
  150. package/src/session/auth/useAuthState.ts +0 -4
  151. package/src/shell/apps/StandaloneApp.tsx +1 -1
  152. package/src/shell/login/EnterpriseSigninButton.tsx +3 -0
  153. package/src/shell/login/InviteAcceptModal.tsx +1 -1
  154. package/src/widgets/form/ManagedObject.ts +1 -1
  155. package/src/widgets/index.ts +1 -0
  156. package/src/widgets/markdown/MarkdownRenderer.tsx +45 -0
  157. package/src/widgets/markdown/index.ts +1 -0
@@ -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
- instrumented,
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 [imageType, setImageType] = useState<ImageType>(ImageType.default);
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 : urls);
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
- <button className={getRadioButtonClass(ImageType.default, imageType)}
60
- onClick={() => setImageType(ImageType.default)}
61
- ><ImageIcon className="w-5 h-5 mt-1" /></button>
62
- <button className={getRadioButtonClass(ImageType.instrumented, imageType)}
63
- onClick={() => setImageType(ImageType.instrumented)}
64
- ><InfoIcon className="w-5 h-5 mt-1" /></button>
65
- <button className={getRadioButtonClass(ImageType.annotated, imageType)}
66
- onClick={() => setImageType(ImageType.annotated)}
67
- ><AtSignIcon className="w-5 h-5 mt-1" /></button>
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,4 +1,4 @@
1
- import { JSONCode, Theme, XMLViewer } from '@vertesia/ui/widgets';
1
+ import { JSONCode, Theme, XMLViewer, MarkdownRenderer } from '@vertesia/ui/widgets';
2
2
  import { useEffect, useLayoutEffect, useState } from "react";
3
3
  import { usePdfPagesInfo } from "./PdfPageProvider";
4
4
  import { ViewType } from "./types";
@@ -14,12 +14,20 @@ const darkTheme: Theme = {
14
14
  cdataColor: "#33CC66",
15
15
  }
16
16
 
17
+
17
18
  interface TextPageViewProps {
18
19
  pageNumber: number;
19
20
  viewType: ViewType;
20
21
  }
21
22
  export function TextPageView({ viewType, pageNumber }: TextPageViewProps) {
22
- return viewType === "json" ? <JsonPageLayoutView pageNumber={pageNumber} /> : <XmlPageView pageNumber={pageNumber} />
23
+ switch (viewType) {
24
+ case "json":
25
+ return <JsonPageLayoutView pageNumber={pageNumber} />;
26
+ case "markdown":
27
+ return <MarkdownPageView pageNumber={pageNumber} />;
28
+ default:
29
+ return <XmlPageView pageNumber={pageNumber} />;
30
+ }
23
31
  }
24
32
 
25
33
  interface XmlPageViewProps {
@@ -62,3 +70,22 @@ function JsonPageLayoutView({ pageNumber }: JsonPageLayoutViewProps) {
62
70
  content && <JSONCode className="w-full" data={content} />
63
71
  )
64
72
  }
73
+
74
+ interface MarkdownPageViewProps {
75
+ pageNumber: number;
76
+ }
77
+ function MarkdownPageView({ pageNumber }: MarkdownPageViewProps) {
78
+ const [content, setContent] = useState<string>();
79
+ const { markdownProvider } = usePdfPagesInfo();
80
+ useEffect(() => {
81
+ markdownProvider.getPageMarkdown(pageNumber).then(setContent).catch((err: any) => {
82
+ console.error(err);
83
+ setContent(undefined);
84
+ });
85
+ }, [pageNumber]);
86
+ return (
87
+ <div className="px-4 py-2 prose prose-sm max-w-none dark:prose-invert">
88
+ {content ? <MarkdownRenderer>{content}</MarkdownRenderer> : <div>No markdown content available</div>}
89
+ </div>
90
+ )
91
+ }
@@ -1 +1 @@
1
- export type ViewType = "xml" | "json";
1
+ export type ViewType = "xml" | "json" | "markdown";
@@ -74,7 +74,7 @@ export function CreateCollectionForm({ onClose, redirect = true, onAddToCollecti
74
74
  };
75
75
 
76
76
  return (
77
- <form>
77
+ <form onSubmit={(e) => e.preventDefault()}>
78
78
  <VModalBody>
79
79
  <FormItem label="Name" required>
80
80
  <Input type="text" value={payload.name || ""} onChange={(value) => setPayloadProp("name", value)} />
@@ -5,7 +5,7 @@ import {
5
5
  import { Button, Spinner, useToast } from "@vertesia/ui/core";
6
6
  import { useNavigate } from "@vertesia/ui/router";
7
7
  import { useUserSession } from "@vertesia/ui/session";
8
- import { JSONDisplay } from "@vertesia/ui/widgets";
8
+ import { JSONDisplay, MarkdownRenderer } from "@vertesia/ui/widgets";
9
9
  import {
10
10
  ChevronRight,
11
11
  Download,
@@ -16,8 +16,6 @@ import {
16
16
  X,
17
17
  } from "lucide-react";
18
18
  import { useEffect, useState } from "react";
19
- import Markdown from "react-markdown";
20
- import remarkGfm from "remark-gfm";
21
19
 
22
20
  interface DocumentPreviewPanelProps {
23
21
  objectId: string | null;
@@ -233,7 +231,7 @@ export function DocumentPreviewPanel({
233
231
  <div className="shadow rounded-md p-4 border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800">
234
232
  {seemsMarkdown ? (
235
233
  <div className="prose prose-sm max-w-none prose-p:my-2 prose-pre:bg-gray-800 prose-pre:my-2 prose-headings:text-indigo-700 dark:prose-invert dark:prose-headings:text-indigo-300">
236
- <Markdown remarkPlugins={[remarkGfm]}>{text}</Markdown>
234
+ <MarkdownRenderer>{text}</MarkdownRenderer>
237
235
  </div>
238
236
  ) : (
239
237
  <pre className="text-wrap whitespace-pre-wrap dark:text-gray-200">
@@ -1,14 +1,15 @@
1
- import { useEffect, useRef, useState } from "react";
2
-
3
- import { ColumnLayout, ContentObject, ContentObjectItem, VectorSearchQuery } from '@vertesia/common';
4
- import { Button, Divider, ErrorBox, SidePanel, Spinner, useDebounce, useIntersectionObserver, useToast } from '@vertesia/ui/core';
1
+ import { useRef, useState } from "react";
2
+ import { ColumnLayout, ContentObject, ContentObjectItem, ComplexSearchQuery } from '@vertesia/common';
3
+ import {
4
+
5
+ Button, Divider, ErrorBox, SidePanel, Spinner, useIntersectionObserver, useToast,
6
+ FilterProvider, FilterBtn, FilterBar, FilterClear, Filter as BaseFilter
7
+ } from '@vertesia/ui/core';
5
8
  import { useNavigate } from "@vertesia/ui/router";
6
9
  import { TypeRegistry, useUserSession } from '@vertesia/ui/session';
7
10
  import { Download, RefreshCw, Eye } from 'lucide-react';
8
- import { FilterProvider, FilterBtn, FilterBar, FilterClear, Filter as BaseFilter } from '@vertesia/ui/core';
9
11
  import { useDocumentFilterGroups, useDocumentFilterHandler } from "../../facets/DocumentsFacetsNav";
10
12
  import { VectorSearchWidget } from './components/VectorSearchWidget';
11
-
12
13
  import { ContentDispositionButton } from './components/ContentDispositionButton';
13
14
  import { DocumentTable } from './DocumentTable';
14
15
  import { useDocumentSearch, useWatchDocumentSearchFacets, useWatchDocumentSearchResult } from './search/DocumentSearchContext';
@@ -90,7 +91,6 @@ export function DocumentSearchResults({ layout, onUpload, allowFilter = true, al
90
91
  const [selectedObject, setSelectedObject] = useState<ContentObjectItem | null>(null);
91
92
  const { typeRegistry } = useUserSession();
92
93
  const { search, isLoading, error, objects } = useWatchDocumentSearchResult();
93
- const [vQuery, setVQuery] = useState<VectorSearchQuery | undefined>(undefined);
94
94
  const [actualLayout, setActualLayout] = useState<ColumnLayout[]>(
95
95
  typeRegistry ? layout || getTableLayout(typeRegistry, search.query.type) : defaultLayout,
96
96
  );
@@ -111,37 +111,35 @@ export function DocumentSearchResults({ layout, onUpload, allowFilter = true, al
111
111
  }
112
112
  }, { deps: [isReady, objects.length] });
113
113
 
114
- useEffect(() => {
115
- search.search().then(() => setIsReady(true));
116
- }, []);
117
114
 
118
- //TODO _setSearchTerm state not used
119
- const [searchTerm, _setSearchTerm] = useState("");
120
- const debounceValue = useDebounce(searchTerm, 500);
121
- useEffect(() => {
122
- search.query.name = searchTerm;
123
- search.search().then(() => setIsReady(true));
124
- }, [debounceValue]);
125
-
126
- useEffect(() => {
127
- if (vQuery) {
128
- search.query.vector = vQuery;
129
- if (!actualLayout.find((c) => c.name === "Vector Similarity")) {
115
+ // Handler for vector search widget
116
+ const handleVectorSearch = (query?: ComplexSearchQuery) => {
117
+ if (query && query.vector) {
118
+ search.query.vector = query.vector;
119
+ search.query.full_text = query.full_text;
120
+ search.query.weights = query.weights;
121
+ search.query.score_aggregation = query.score_aggregation;
122
+ search.query.dynamic_scaling = query.dynamic_scaling;
123
+ if (!actualLayout.find((c) => c.name === "Search Score")) {
130
124
  const layout = [
131
125
  ...actualLayout,
132
126
  {
133
- name: "Vector Similarity",
127
+ name: "Search Score",
134
128
  field: "score",
135
129
  } satisfies ColumnLayout,
136
130
  ];
137
131
  setActualLayout(layout);
138
132
  }
139
133
  search.search().then(() => setIsReady(true));
134
+ } else if (query && query.full_text) {
135
+ search.query.full_text = query.full_text;
136
+ search.search().then(() => setIsReady(true));
140
137
  } else {
141
138
  delete search.query.vector;
139
+ delete search.query.full_text;
142
140
  search.search().then(() => setIsReady(true));
143
141
  }
144
- }, [vQuery?.values]);
142
+ };
145
143
 
146
144
  const facets = useWatchDocumentSearchFacets();
147
145
  const facetSearch = useDocumentSearch();
@@ -202,7 +200,7 @@ export function DocumentSearchResults({ layout, onUpload, allowFilter = true, al
202
200
  <div className="flex flex-row gap-4 items-center justify-between w-full">
203
201
  <div className="flex gap-2 items-center w-2/3">
204
202
  {
205
- allowSearch && <VectorSearchWidget onChange={setVQuery} isLoading={isLoading} refresh={refreshTrigger} className="w-wull" />
203
+ allowSearch && <VectorSearchWidget onChange={handleVectorSearch} isLoading={isLoading} refresh={refreshTrigger} className="w-full" />
206
204
  }
207
205
  <FilterBtn />
208
206
  </div>
@@ -223,7 +221,7 @@ export function DocumentSearchResults({ layout, onUpload, allowFilter = true, al
223
221
  <div className="flex flex-row gap-4 items-center justify-between w-full">
224
222
  <div className="flex gap-2 items-center w-2/3">
225
223
  {
226
- allowSearch && <VectorSearchWidget onChange={setVQuery} isLoading={isLoading} refresh={refreshTrigger} />
224
+ allowSearch && <VectorSearchWidget onChange={handleVectorSearch} isLoading={isLoading} refresh={refreshTrigger} />
227
225
  }
228
226
  </div>
229
227
  <div className="flex gap-1 items-center">
@@ -1,10 +1,8 @@
1
1
  import { useEffect, useState } from "react";
2
- import Markdown from "react-markdown";
3
- import remarkGfm from "remark-gfm";
4
2
 
5
3
  import { useUserSession } from "@vertesia/ui/session";
6
4
  import { Button, Spinner, useToast } from "@vertesia/ui/core";
7
- import { JSONDisplay } from "@vertesia/ui/widgets";
5
+ import { JSONDisplay, MarkdownRenderer } from "@vertesia/ui/widgets";
8
6
  import { ContentObject, ImageRenditionFormat } from "@vertesia/common";
9
7
  import { Copy, Download, SquarePen } from "lucide-react";
10
8
  import { PropertiesEditorModal } from "./PropertiesEditorModal";
@@ -314,8 +312,7 @@ export function ContentOverview({
314
312
  <div className="border shadow-xs rounded-xs max-w-7xl">
315
313
  {seemsMarkdown ? (
316
314
  <div className="vprose prose-sm p-1">
317
- <Markdown
318
- remarkPlugins={[remarkGfm]}
315
+ <MarkdownRenderer
319
316
  components={{
320
317
  a: ({ node, ...props }: { node?: any; href?: string; children?: React.ReactNode }) => {
321
318
  const href = props.href || "";
@@ -378,7 +375,7 @@ export function ContentOverview({
378
375
  }}
379
376
  >
380
377
  {text}
381
- </Markdown>
378
+ </MarkdownRenderer>
382
379
  </div>
383
380
  ) : (
384
381
  <pre className="text-wrap bg-muted text-muted p-2">
@@ -1,37 +1,61 @@
1
1
  import { useEffect, useState } from 'react';
2
2
 
3
- import { ProjectConfiguration, SupportedEmbeddingTypes, VectorSearchQuery } from '@vertesia/common';
4
- import { Button, Input, useToast } from '@vertesia/ui/core';
3
+ import { ProjectConfiguration, ComplexSearchQuery, SupportedEmbeddingTypes, SearchTypes } from '@vertesia/common';
4
+ import { Button, Input, useToast, Modal, ModalTitle, ModalBody, ModalFooter, Checkbox, NumberInput } from '@vertesia/ui/core';
5
5
  import { useUserSession } from '@vertesia/ui/session';
6
+ import { Settings } from 'lucide-react';
6
7
 
7
8
  interface VectorSearchWidgetProps {
8
- onChange: (query?: VectorSearchQuery) => void;
9
+ onChange: (query?: ComplexSearchQuery) => void;
9
10
  className?: string;
10
11
  status?: boolean;
11
12
  isLoading?: boolean;
12
- refresh: number
13
+ refresh: number;
14
+ searchTypes?: (keyof typeof SearchTypes)[];
13
15
  }
14
- export function VectorSearchWidget({ onChange, isLoading, refresh }: VectorSearchWidgetProps) {
16
+
17
+ const allTypes = Object.values(SearchTypes);
18
+ const embeddingTypes = Object.values(SupportedEmbeddingTypes);
19
+
20
+ export function VectorSearchWidget({ onChange, isLoading, refresh, searchTypes }: VectorSearchWidgetProps) {
15
21
  const { client, project } = useUserSession();
16
22
  const toast = useToast();
17
23
 
18
24
  const [searchText, setSearchText] = useState<string | undefined>(undefined);
19
- const [searchSketch, setSearchSketch] = useState<string | undefined>(undefined);
20
25
  const [config, setConfig] = useState<ProjectConfiguration | undefined>(undefined);
21
26
  const isReady = !!project && (!!config?.embeddings.text || !!config?.embeddings.image);
22
27
  const [status, setStatus] = useState<string | undefined>(undefined);
23
28
 
29
+ const [showSettings, setShowSettings] = useState(false);
30
+ // Default to all types, or use prop if provided
31
+ const [selectedTypes, setSelectedTypes] = useState<(keyof typeof SearchTypes)[]>(searchTypes || allTypes);
32
+ const [limit, setLimit] = useState<number>(100);
33
+ useEffect(() => {
34
+ if (searchTypes) setSelectedTypes(searchTypes);
35
+ }, [searchTypes]);
36
+
37
+ // Always derive embeddingSearchTypes and full_text from selectedTypes
38
+ const embeddingSearchTypes: Record<string, boolean> = {};
39
+ let fullTextEnabled = false;
40
+ let vectorSearchEnabled = false;
41
+ selectedTypes.forEach(type => {
42
+ if (type === SearchTypes.full_text) {
43
+ fullTextEnabled = true;
44
+ } else {
45
+ vectorSearchEnabled = true;
46
+ }
47
+ if (embeddingTypes.includes(type as SupportedEmbeddingTypes)) {
48
+ embeddingSearchTypes[type] = true;
49
+ }
50
+ });
51
+
24
52
  useEffect(() => {
25
53
  setSearchText(undefined);
26
- setSearchSketch(undefined);
27
54
  setStatus(undefined);
28
55
  }, [refresh]);
29
56
 
30
57
  useEffect(() => {
31
- if (!project) {
32
- return;
33
- }
34
-
58
+ if (!project) return;
35
59
  client.projects.retrieve(project.id).then((project) => {
36
60
  setConfig(project.configuration);
37
61
  })
@@ -49,67 +73,71 @@ export function VectorSearchWidget({ onChange, isLoading, refresh }: VectorSearc
49
73
  }
50
74
  }, [searchText]);
51
75
 
52
- const generateTextEmbeddings = async () => {
53
- if (!isReady || !searchText || !config?.embeddings.text) {
54
- return;
55
- }
56
-
57
- setStatus('Generating text embeddings...');
58
- const response = await client.environments.embeddings(config.embeddings.text?.environment, {
59
- model: config.embeddings.text?.model,
60
- text: searchText,
61
- });
62
-
63
- const query: VectorSearchQuery = {
64
- values: response.values,
65
- type: SupportedEmbeddingTypes.text
76
+ const fireSearch = () => {
77
+ if (!isReady || !searchText) return;
78
+ const query: ComplexSearchQuery = {
79
+ vector: vectorSearchEnabled ? {
80
+ text: searchText,
81
+ config: embeddingSearchTypes,
82
+ } : undefined,
83
+ full_text: fullTextEnabled ? searchText : undefined,
84
+ limit: limit
66
85
  };
67
-
68
- return query;
86
+ onChange(query);
87
+ setStatus("Searching...");
69
88
  };
70
89
 
71
- const generateImageEmbeddings = async () => {
72
- if (!isReady || !searchText || !config?.embeddings.image) {
73
- return;
90
+ const handleKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
91
+ if (e.key === "Enter") {
92
+ fireSearch();
74
93
  }
75
-
76
- setStatus('Generating image embeddings...');
77
- const response = await client.environments.embeddings(config.embeddings.image?.environment, {
78
- model: config.embeddings.image?.model,
79
- image: searchSketch,
80
- });
81
-
82
- const query: VectorSearchQuery = {
83
- values: response.values,
84
- type: SupportedEmbeddingTypes.image
85
- };
86
-
87
- return query;
88
94
  };
89
95
 
90
- const fireSearch = (type: "text" | "image" = "text") => {
91
- const generateEmbedding = type === "text" ? generateTextEmbeddings : generateImageEmbeddings;
92
-
93
- generateEmbedding().then((query) => {
94
- setStatus("Embeddings generated");
95
- if (!query) {
96
- return;
97
- }
98
- onChange(query);
99
- setStatus("Searching...");
100
- });
101
- };
102
-
103
- const handleKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
104
- if (e.key === "Enter") {
105
- fireSearch("text");
96
+ // Modal state for search type selection
97
+ const handleCheckboxChange = (type: keyof typeof SearchTypes) => (checked: boolean) => {
98
+ if (checked) {
99
+ setSelectedTypes(prev => Array.from(new Set([...prev, type])));
100
+ } else {
101
+ setSelectedTypes(prev => prev.filter(t => t !== type));
106
102
  }
107
103
  };
108
104
 
109
105
  return (
110
106
  <div className="flex gap-1 items-center w-1/2">
111
107
  <Input placeholder="Type what you are looking for, or select a filter" value={searchText} onChange={setSearchText} onKeyDown={handleKeyPress} />
112
- <Button variant="secondary" isLoading={isLoading} onClick={() => fireSearch("text")} isDisabled={!isReady} alt="semantic search">Search</Button>
108
+ <Button variant="ghost" onClick={() => setShowSettings(true)} alt="Semantic search settings" className="ml-1"><Settings size={18} /></Button>
109
+ <Modal isOpen={showSettings} onClose={() => setShowSettings(false)}>
110
+ <ModalTitle>Search Types</ModalTitle>
111
+ <ModalBody>
112
+ <div className="flex flex-col gap-2">
113
+ <label className="flex items-center gap-2">
114
+ <Checkbox
115
+ checked={selectedTypes.includes(SearchTypes.full_text)}
116
+ onCheckedChange={handleCheckboxChange(SearchTypes.full_text)}
117
+ />
118
+ <span>Full Text</span>
119
+ </label>
120
+ <div className="font-semibold mt-2 mb-1">Embeddings</div>
121
+ {embeddingTypes.map(type => (
122
+ <label key={type} className="flex items-center gap-2">
123
+ <Checkbox
124
+ checked={selectedTypes.includes(type)}
125
+ onCheckedChange={handleCheckboxChange(type)}
126
+ />
127
+ <span>{type.charAt(0).toUpperCase() + type.slice(1)}</span>
128
+ </label>
129
+ ))}
130
+ <div className="mt-3">
131
+ <span className="mr-2">Limit</span>
132
+ <NumberInput type="number" min={1} value={limit} onChange={v => setLimit(Number(v) || 1)} style={{ width: 80 }} />
133
+ </div>
134
+ </div>
135
+ </ModalBody>
136
+ <ModalFooter>
137
+ <Button variant="outline" onClick={() => setShowSettings(false)}>Close</Button>
138
+ </ModalFooter>
139
+ </Modal>
140
+ <Button variant="secondary" isLoading={isLoading} onClick={fireSearch} isDisabled={!isReady} alt="Semantic search">Search</Button>
113
141
  </div>
114
142
  );
115
143
  }
@@ -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 (