@vertesia/ui 0.80.0-dev-20251118 → 0.80.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 (311) hide show
  1. package/README.md +105 -0
  2. package/lib/esm/core/components/MenuList.js +2 -5
  3. package/lib/esm/core/components/MenuList.js.map +1 -1
  4. package/lib/esm/core/components/MessageBox.js +1 -1
  5. package/lib/esm/core/components/MessageBox.js.map +1 -1
  6. package/lib/esm/core/components/TagsInput.js +194 -0
  7. package/lib/esm/core/components/TagsInput.js.map +1 -0
  8. package/lib/esm/core/components/index.js +2 -1
  9. package/lib/esm/core/components/index.js.map +1 -1
  10. package/lib/esm/core/components/shadcn/dialog.js +16 -2
  11. package/lib/esm/core/components/shadcn/dialog.js.map +1 -1
  12. package/lib/esm/core/components/shadcn/filters/filter/SelectFilter.js +6 -9
  13. package/lib/esm/core/components/shadcn/filters/filter/SelectFilter.js.map +1 -1
  14. package/lib/esm/core/components/shadcn/filters/filterBar.js +1 -1
  15. package/lib/esm/core/components/shadcn/filters/filterBar.js.map +1 -1
  16. package/lib/esm/core/components/shadcn/popover.js +1 -1
  17. package/lib/esm/core/components/shadcn/popover.js.map +1 -1
  18. package/lib/esm/core/components/shadcn/selectBox.js +23 -5
  19. package/lib/esm/core/components/shadcn/selectBox.js.map +1 -1
  20. package/lib/esm/core/components/shadcn/tabs.js +3 -3
  21. package/lib/esm/core/components/shadcn/tabs.js.map +1 -1
  22. package/lib/esm/env/index.js +4 -1
  23. package/lib/esm/env/index.js.map +1 -1
  24. package/lib/esm/features/agent/chat/AgentChart.js +184 -0
  25. package/lib/esm/features/agent/chat/AgentChart.js.map +1 -0
  26. package/lib/esm/features/agent/chat/ModernAgentConversation.js +87 -10
  27. package/lib/esm/features/agent/chat/ModernAgentConversation.js.map +1 -1
  28. package/lib/esm/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js +6 -2
  29. package/lib/esm/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js.map +1 -1
  30. package/lib/esm/features/agent/chat/ModernAgentOutput/Header.js +4 -4
  31. package/lib/esm/features/agent/chat/ModernAgentOutput/Header.js.map +1 -1
  32. package/lib/esm/features/agent/chat/ModernAgentOutput/InlineSlidingPlanPanel.js +4 -1
  33. package/lib/esm/features/agent/chat/ModernAgentOutput/InlineSlidingPlanPanel.js.map +1 -1
  34. package/lib/esm/features/agent/chat/ModernAgentOutput/MessageInput.js +12 -4
  35. package/lib/esm/features/agent/chat/ModernAgentOutput/MessageInput.js.map +1 -1
  36. package/lib/esm/features/agent/chat/ModernAgentOutput/MessageItem.js +60 -56
  37. package/lib/esm/features/agent/chat/ModernAgentOutput/MessageItem.js.map +1 -1
  38. package/lib/esm/features/agent/chat/index.js +1 -0
  39. package/lib/esm/features/agent/chat/index.js.map +1 -1
  40. package/lib/esm/features/agent/createChartTool.js +354 -0
  41. package/lib/esm/features/agent/createChartTool.js.map +1 -0
  42. package/lib/esm/features/agent/examples.js +295 -0
  43. package/lib/esm/features/agent/examples.js.map +1 -0
  44. package/lib/esm/features/agent/index.js +2 -0
  45. package/lib/esm/features/agent/index.js.map +1 -1
  46. package/lib/esm/features/agent/visualization.js +165 -0
  47. package/lib/esm/features/agent/visualization.js.map +1 -0
  48. package/lib/esm/features/facets/CollectionsFacetsNav.js +5 -1
  49. package/lib/esm/features/facets/CollectionsFacetsNav.js.map +1 -1
  50. package/lib/esm/features/index.js +1 -0
  51. package/lib/esm/features/index.js.map +1 -1
  52. package/lib/esm/features/layout/GenericPageNavHeader.js +14 -4
  53. package/lib/esm/features/layout/GenericPageNavHeader.js.map +1 -1
  54. package/lib/esm/features/magic-pdf/AnnotatedImageSlider.js +268 -0
  55. package/lib/esm/features/magic-pdf/AnnotatedImageSlider.js.map +1 -0
  56. package/lib/esm/features/magic-pdf/DownloadPopover.js +11 -11
  57. package/lib/esm/features/magic-pdf/DownloadPopover.js.map +1 -1
  58. package/lib/esm/features/magic-pdf/ExtractedContentView.js +77 -0
  59. package/lib/esm/features/magic-pdf/ExtractedContentView.js.map +1 -0
  60. package/lib/esm/features/magic-pdf/MagicPdfProvider.js +242 -0
  61. package/lib/esm/features/magic-pdf/MagicPdfProvider.js.map +1 -0
  62. package/lib/esm/features/magic-pdf/MagicPdfView.js +41 -47
  63. package/lib/esm/features/magic-pdf/MagicPdfView.js.map +1 -1
  64. package/lib/esm/features/pdf-viewer/PdfPageRenderer.js +261 -0
  65. package/lib/esm/features/pdf-viewer/PdfPageRenderer.js.map +1 -0
  66. package/lib/esm/features/pdf-viewer/PdfPageSlider.js +276 -0
  67. package/lib/esm/features/pdf-viewer/PdfPageSlider.js.map +1 -0
  68. package/lib/esm/features/pdf-viewer/SimplePdfViewer.js +71 -0
  69. package/lib/esm/features/pdf-viewer/SimplePdfViewer.js.map +1 -0
  70. package/lib/esm/features/pdf-viewer/index.js +4 -0
  71. package/lib/esm/features/pdf-viewer/index.js.map +1 -0
  72. package/lib/esm/features/store/collections/EditCollectionView.js +19 -10
  73. package/lib/esm/features/store/collections/EditCollectionView.js.map +1 -1
  74. package/lib/esm/features/store/collections/SelectCollection.js +104 -39
  75. package/lib/esm/features/store/collections/SelectCollection.js.map +1 -1
  76. package/lib/esm/features/store/collections/SharedPropsEditor.js +39 -0
  77. package/lib/esm/features/store/collections/SharedPropsEditor.js.map +1 -0
  78. package/lib/esm/features/store/collections/SyncMemberHeadsToggle.js +35 -0
  79. package/lib/esm/features/store/collections/SyncMemberHeadsToggle.js.map +1 -0
  80. package/lib/esm/features/store/collections/index.js +2 -0
  81. package/lib/esm/features/store/collections/index.js.map +1 -1
  82. package/lib/esm/features/store/objects/DocumentSearchResults.js +0 -7
  83. package/lib/esm/features/store/objects/DocumentSearchResults.js.map +1 -1
  84. package/lib/esm/features/store/objects/components/ContentOverview.js +273 -83
  85. package/lib/esm/features/store/objects/components/ContentOverview.js.map +1 -1
  86. package/lib/esm/features/store/objects/components/useContentPanelHooks.js +153 -0
  87. package/lib/esm/features/store/objects/components/useContentPanelHooks.js.map +1 -0
  88. package/lib/esm/features/store/objects/layout/DocumentTableColumn.js +3 -3
  89. package/lib/esm/features/store/objects/layout/DocumentTableColumn.js.map +1 -1
  90. package/lib/esm/features/store/objects/layout/renderers.js +13 -0
  91. package/lib/esm/features/store/objects/layout/renderers.js.map +1 -1
  92. package/lib/esm/features/store/objects/upload/useSmartFileUploadProcessing.js +10 -9
  93. package/lib/esm/features/store/objects/upload/useSmartFileUploadProcessing.js.map +1 -1
  94. package/lib/esm/features/utils/index.js +2 -0
  95. package/lib/esm/features/utils/index.js.map +1 -1
  96. package/lib/esm/features/utils/mimeType.js +8 -0
  97. package/lib/esm/features/utils/mimeType.js.map +1 -1
  98. package/lib/esm/features/utils/print.js +181 -0
  99. package/lib/esm/features/utils/print.js.map +1 -0
  100. package/lib/esm/features/utils/workflowStatus.js +43 -0
  101. package/lib/esm/features/utils/workflowStatus.js.map +1 -0
  102. package/lib/esm/router/HistoryNavigator.js +22 -2
  103. package/lib/esm/router/HistoryNavigator.js.map +1 -1
  104. package/lib/esm/session/UserSessionProvider.js +2 -2
  105. package/lib/esm/session/UserSessionProvider.js.map +1 -1
  106. package/lib/esm/shell/apps/AppProjectSelector.js +2 -2
  107. package/lib/esm/shell/apps/AppProjectSelector.js.map +1 -1
  108. package/lib/esm/shell/login/UserInfo.js +2 -1
  109. package/lib/esm/shell/login/UserInfo.js.map +1 -1
  110. package/lib/esm/shell/login/UserSessionMenu.js +7 -1
  111. package/lib/esm/shell/login/UserSessionMenu.js.map +1 -1
  112. package/lib/esm/widgets/form/Form.js +6 -2
  113. package/lib/esm/widgets/form/Form.js.map +1 -1
  114. package/lib/esm/widgets/markdown/MarkdownRenderer.js +226 -4
  115. package/lib/esm/widgets/markdown/MarkdownRenderer.js.map +1 -1
  116. package/lib/esm/widgets/schema-editor/ManagedSchema.js +0 -3
  117. package/lib/esm/widgets/schema-editor/ManagedSchema.js.map +1 -1
  118. package/lib/esm/widgets/schema-editor/json-schema4-utils.js +1 -1
  119. package/lib/esm/widgets/schema-editor/json-schema4-utils.js.map +1 -1
  120. package/lib/esm/widgets/xml-viewer/components/XMLViewer.js +18 -9
  121. package/lib/esm/widgets/xml-viewer/components/XMLViewer.js.map +1 -1
  122. package/lib/esm/widgets/xml-viewer/constants/index.js +10 -0
  123. package/lib/esm/widgets/xml-viewer/constants/index.js.map +1 -1
  124. package/lib/tsconfig.tsbuildinfo +1 -1
  125. package/lib/types/core/components/MessageBox.d.ts.map +1 -1
  126. package/lib/types/core/components/TagsInput.d.ts +16 -0
  127. package/lib/types/core/components/TagsInput.d.ts.map +1 -0
  128. package/lib/types/core/components/index.d.ts +2 -1
  129. package/lib/types/core/components/index.d.ts.map +1 -1
  130. package/lib/types/core/components/shadcn/dialog.d.ts +2 -1
  131. package/lib/types/core/components/shadcn/dialog.d.ts.map +1 -1
  132. package/lib/types/core/components/shadcn/filters/filterBar.d.ts.map +1 -1
  133. package/lib/types/core/components/shadcn/popover.d.ts +7 -0
  134. package/lib/types/core/components/shadcn/popover.d.ts.map +1 -1
  135. package/lib/types/core/components/shadcn/selectBox.d.ts +5 -1
  136. package/lib/types/core/components/shadcn/selectBox.d.ts.map +1 -1
  137. package/lib/types/core/components/shadcn/tabs.d.ts +3 -1
  138. package/lib/types/core/components/shadcn/tabs.d.ts.map +1 -1
  139. package/lib/types/env/index.d.ts +2 -0
  140. package/lib/types/env/index.d.ts.map +1 -1
  141. package/lib/types/features/agent/chat/AgentChart.d.ts +48 -0
  142. package/lib/types/features/agent/chat/AgentChart.d.ts.map +1 -0
  143. package/lib/types/features/agent/chat/ModernAgentConversation.d.ts.map +1 -1
  144. package/lib/types/features/agent/chat/ModernAgentOutput/AllMessagesMixed.d.ts +3 -2
  145. package/lib/types/features/agent/chat/ModernAgentOutput/AllMessagesMixed.d.ts.map +1 -1
  146. package/lib/types/features/agent/chat/ModernAgentOutput/Header.d.ts +2 -1
  147. package/lib/types/features/agent/chat/ModernAgentOutput/Header.d.ts.map +1 -1
  148. package/lib/types/features/agent/chat/ModernAgentOutput/InlineSlidingPlanPanel.d.ts +4 -2
  149. package/lib/types/features/agent/chat/ModernAgentOutput/InlineSlidingPlanPanel.d.ts.map +1 -1
  150. package/lib/types/features/agent/chat/ModernAgentOutput/MessageInput.d.ts +2 -4
  151. package/lib/types/features/agent/chat/ModernAgentOutput/MessageInput.d.ts.map +1 -1
  152. package/lib/types/features/agent/chat/ModernAgentOutput/MessageItem.d.ts.map +1 -1
  153. package/lib/types/features/agent/chat/index.d.ts +1 -0
  154. package/lib/types/features/agent/chat/index.d.ts.map +1 -1
  155. package/lib/types/features/agent/createChartTool.d.ts +178 -0
  156. package/lib/types/features/agent/createChartTool.d.ts.map +1 -0
  157. package/lib/types/features/agent/examples.d.ts +59 -0
  158. package/lib/types/features/agent/examples.d.ts.map +1 -0
  159. package/lib/types/features/agent/index.d.ts +2 -0
  160. package/lib/types/features/agent/index.d.ts.map +1 -1
  161. package/lib/types/features/agent/visualization.d.ts +95 -0
  162. package/lib/types/features/agent/visualization.d.ts.map +1 -0
  163. package/lib/types/features/facets/CollectionsFacetsNav.d.ts.map +1 -1
  164. package/lib/types/features/index.d.ts +1 -0
  165. package/lib/types/features/index.d.ts.map +1 -1
  166. package/lib/types/features/layout/GenericPageNavHeader.d.ts.map +1 -1
  167. package/lib/types/features/magic-pdf/AnnotatedImageSlider.d.ts +13 -0
  168. package/lib/types/features/magic-pdf/AnnotatedImageSlider.d.ts.map +1 -0
  169. package/lib/types/features/magic-pdf/DownloadPopover.d.ts.map +1 -1
  170. package/lib/types/features/magic-pdf/ExtractedContentView.d.ts +8 -0
  171. package/lib/types/features/magic-pdf/ExtractedContentView.d.ts.map +1 -0
  172. package/lib/types/features/magic-pdf/MagicPdfProvider.d.ts +58 -0
  173. package/lib/types/features/magic-pdf/MagicPdfProvider.d.ts.map +1 -0
  174. package/lib/types/features/magic-pdf/MagicPdfView.d.ts +1 -1
  175. package/lib/types/features/magic-pdf/MagicPdfView.d.ts.map +1 -1
  176. package/lib/types/features/pdf-viewer/PdfPageRenderer.d.ts +83 -0
  177. package/lib/types/features/pdf-viewer/PdfPageRenderer.d.ts.map +1 -0
  178. package/lib/types/features/pdf-viewer/PdfPageSlider.d.ts +29 -0
  179. package/lib/types/features/pdf-viewer/PdfPageSlider.d.ts.map +1 -0
  180. package/lib/types/features/pdf-viewer/SimplePdfViewer.d.ts +19 -0
  181. package/lib/types/features/pdf-viewer/SimplePdfViewer.d.ts.map +1 -0
  182. package/lib/types/features/pdf-viewer/index.d.ts +4 -0
  183. package/lib/types/features/pdf-viewer/index.d.ts.map +1 -0
  184. package/lib/types/features/store/collections/EditCollectionView.d.ts.map +1 -1
  185. package/lib/types/features/store/collections/SelectCollection.d.ts +2 -1
  186. package/lib/types/features/store/collections/SelectCollection.d.ts.map +1 -1
  187. package/lib/types/features/store/collections/SharedPropsEditor.d.ts +7 -0
  188. package/lib/types/features/store/collections/SharedPropsEditor.d.ts.map +1 -0
  189. package/lib/types/features/store/collections/SyncMemberHeadsToggle.d.ts +7 -0
  190. package/lib/types/features/store/collections/SyncMemberHeadsToggle.d.ts.map +1 -0
  191. package/lib/types/features/store/collections/index.d.ts +2 -0
  192. package/lib/types/features/store/collections/index.d.ts.map +1 -1
  193. package/lib/types/features/store/objects/DocumentSearchResults.d.ts.map +1 -1
  194. package/lib/types/features/store/objects/components/ContentOverview.d.ts.map +1 -1
  195. package/lib/types/features/store/objects/components/useContentPanelHooks.d.ts +30 -0
  196. package/lib/types/features/store/objects/components/useContentPanelHooks.d.ts.map +1 -0
  197. package/lib/types/features/store/objects/layout/renderers.d.ts.map +1 -1
  198. package/lib/types/features/store/objects/upload/useSmartFileUploadProcessing.d.ts.map +1 -1
  199. package/lib/types/features/utils/index.d.ts +2 -0
  200. package/lib/types/features/utils/index.d.ts.map +1 -1
  201. package/lib/types/features/utils/mimeType.d.ts +1 -0
  202. package/lib/types/features/utils/mimeType.d.ts.map +1 -1
  203. package/lib/types/features/utils/print.d.ts +10 -0
  204. package/lib/types/features/utils/print.d.ts.map +1 -0
  205. package/lib/types/features/utils/workflowStatus.d.ts +10 -0
  206. package/lib/types/features/utils/workflowStatus.d.ts.map +1 -0
  207. package/lib/types/router/HistoryNavigator.d.ts +3 -0
  208. package/lib/types/router/HistoryNavigator.d.ts.map +1 -1
  209. package/lib/types/shell/login/UserInfo.d.ts.map +1 -1
  210. package/lib/types/shell/login/UserSessionMenu.d.ts.map +1 -1
  211. package/lib/types/widgets/form/Form.d.ts.map +1 -1
  212. package/lib/types/widgets/markdown/MarkdownRenderer.d.ts +5 -1
  213. package/lib/types/widgets/markdown/MarkdownRenderer.d.ts.map +1 -1
  214. package/lib/types/widgets/schema-editor/ManagedSchema.d.ts.map +1 -1
  215. package/lib/types/widgets/xml-viewer/components/XMLViewer.d.ts.map +1 -1
  216. package/lib/types/widgets/xml-viewer/constants/index.d.ts +10 -0
  217. package/lib/types/widgets/xml-viewer/constants/index.d.ts.map +1 -1
  218. package/lib/vertesia-ui-core.js +1 -1
  219. package/lib/vertesia-ui-core.js.map +1 -1
  220. package/lib/vertesia-ui-env.js +1 -1
  221. package/lib/vertesia-ui-env.js.map +1 -1
  222. package/lib/vertesia-ui-features.js +1 -1
  223. package/lib/vertesia-ui-features.js.map +1 -1
  224. package/lib/vertesia-ui-layout.js +1 -1
  225. package/lib/vertesia-ui-layout.js.map +1 -1
  226. package/lib/vertesia-ui-router.js +1 -1
  227. package/lib/vertesia-ui-router.js.map +1 -1
  228. package/lib/vertesia-ui-session.js +1 -1
  229. package/lib/vertesia-ui-session.js.map +1 -1
  230. package/lib/vertesia-ui-shell.js +1 -1
  231. package/lib/vertesia-ui-shell.js.map +1 -1
  232. package/lib/vertesia-ui-widgets.js +1 -1
  233. package/lib/vertesia-ui-widgets.js.map +1 -1
  234. package/package.json +173 -170
  235. package/src/core/components/MenuList.tsx +3 -6
  236. package/src/core/components/MessageBox.tsx +7 -2
  237. package/src/core/components/SelectBox.tsx +1 -1
  238. package/src/core/components/shadcn/dialog.tsx +19 -1
  239. package/src/core/components/shadcn/filters/filter/SelectFilter.tsx +31 -31
  240. package/src/core/components/shadcn/filters/filterBar.tsx +1 -0
  241. package/src/core/components/shadcn/selectBox.tsx +32 -6
  242. package/src/core/components/shadcn/tabs.tsx +3 -2
  243. package/src/env/index.ts +6 -1
  244. package/src/features/agent/CHART_INSTRUCTIONS.md +228 -0
  245. package/src/features/agent/chat/AgentChart.tsx +601 -0
  246. package/src/features/agent/chat/ModernAgentConversation.tsx +123 -11
  247. package/src/features/agent/chat/ModernAgentOutput/AllMessagesMixed.tsx +8 -2
  248. package/src/features/agent/chat/ModernAgentOutput/Header.tsx +12 -2
  249. package/src/features/agent/chat/ModernAgentOutput/InlineSlidingPlanPanel.tsx +6 -1
  250. package/src/features/agent/chat/ModernAgentOutput/MessageInput.tsx +15 -10
  251. package/src/features/agent/chat/ModernAgentOutput/MessageItem.tsx +122 -87
  252. package/src/features/agent/chat/index.ts +1 -0
  253. package/src/features/agent/createChartTool.ts +364 -0
  254. package/src/features/agent/examples.ts +321 -0
  255. package/src/features/agent/index.ts +2 -0
  256. package/src/features/agent/visualization.ts +227 -0
  257. package/src/features/facets/CollectionsFacetsNav.tsx +5 -1
  258. package/src/features/index.ts +1 -0
  259. package/src/features/layout/GenericPageNavHeader.tsx +15 -4
  260. package/src/features/magic-pdf/AnnotatedImageSlider.tsx +482 -0
  261. package/src/features/magic-pdf/DownloadPopover.tsx +45 -40
  262. package/src/features/magic-pdf/ExtractedContentView.tsx +132 -0
  263. package/src/features/magic-pdf/MagicPdfProvider.tsx +297 -0
  264. package/src/features/magic-pdf/MagicPdfView.tsx +184 -91
  265. package/src/features/pdf-viewer/PdfPageRenderer.tsx +612 -0
  266. package/src/features/pdf-viewer/PdfPageSlider.tsx +473 -0
  267. package/src/features/pdf-viewer/SimplePdfViewer.tsx +142 -0
  268. package/src/features/pdf-viewer/index.ts +3 -0
  269. package/src/features/store/collections/EditCollectionView.tsx +3 -5
  270. package/src/features/store/collections/SharedPropsEditor.tsx +1 -2
  271. package/src/features/store/objects/DocumentSearchResults.tsx +0 -7
  272. package/src/features/store/objects/components/ContentOverview.tsx +677 -210
  273. package/src/features/store/objects/components/useContentPanelHooks.ts +169 -0
  274. package/src/features/store/objects/layout/DocumentTableColumn.tsx +3 -3
  275. package/src/features/store/objects/layout/knowledge.md +1 -0
  276. package/src/features/store/objects/layout/renderers.tsx +25 -0
  277. package/src/features/store/objects/upload/useSmartFileUploadProcessing.ts +1 -3
  278. package/src/features/utils/index.ts +3 -1
  279. package/src/features/utils/mimeType.ts +10 -1
  280. package/src/features/utils/print.ts +189 -0
  281. package/src/features/utils/workflowStatus.ts +44 -0
  282. package/src/router/HistoryNavigator.ts +30 -2
  283. package/src/session/UserSessionProvider.tsx +2 -2
  284. package/src/shell/login/UserInfo.tsx +2 -0
  285. package/src/shell/login/UserSessionMenu.tsx +12 -1
  286. package/src/widgets/form/Form.tsx +8 -3
  287. package/src/widgets/markdown/MarkdownRenderer.tsx +350 -6
  288. package/src/widgets/schema-editor/ManagedSchema.ts +0 -3
  289. package/src/widgets/schema-editor/json-schema4-utils.ts +1 -1
  290. package/src/widgets/xml-viewer/components/XMLViewer.tsx +22 -10
  291. package/src/widgets/xml-viewer/constants/index.ts +11 -0
  292. package/lib/esm/features/magic-pdf/PageSlider.js +0 -70
  293. package/lib/esm/features/magic-pdf/PageSlider.js.map +0 -1
  294. package/lib/esm/features/magic-pdf/PdfPageProvider.js +0 -188
  295. package/lib/esm/features/magic-pdf/PdfPageProvider.js.map +0 -1
  296. package/lib/esm/features/magic-pdf/TextPageView.js +0 -62
  297. package/lib/esm/features/magic-pdf/TextPageView.js.map +0 -1
  298. package/lib/esm/features/magic-pdf/useResizeOnDrag.js +0 -34
  299. package/lib/esm/features/magic-pdf/useResizeOnDrag.js.map +0 -1
  300. package/lib/types/features/magic-pdf/PageSlider.d.ts +0 -9
  301. package/lib/types/features/magic-pdf/PageSlider.d.ts.map +0 -1
  302. package/lib/types/features/magic-pdf/PdfPageProvider.d.ts +0 -39
  303. package/lib/types/features/magic-pdf/PdfPageProvider.d.ts.map +0 -1
  304. package/lib/types/features/magic-pdf/TextPageView.d.ts +0 -8
  305. package/lib/types/features/magic-pdf/TextPageView.d.ts.map +0 -1
  306. package/lib/types/features/magic-pdf/useResizeOnDrag.d.ts +0 -9
  307. package/lib/types/features/magic-pdf/useResizeOnDrag.d.ts.map +0 -1
  308. package/src/features/magic-pdf/PageSlider.tsx +0 -142
  309. package/src/features/magic-pdf/PdfPageProvider.tsx +0 -310
  310. package/src/features/magic-pdf/TextPageView.tsx +0 -91
  311. package/src/features/magic-pdf/useResizeOnDrag.ts +0 -42
@@ -1,20 +1,156 @@
1
- import { useEffect, useState, memo } from "react";
1
+ import { useEffect, useState, memo, useRef, type RefObject } from "react";
2
2
 
3
3
  import { useUserSession } from "@vertesia/ui/session";
4
- import { Button, ResizableHandle, ResizablePanel, ResizablePanelGroup, Spinner, useToast } from "@vertesia/ui/core";
5
- import { JSONDisplay, MarkdownRenderer } from "@vertesia/ui/widgets";
6
- import { ContentNature, ContentObject, ImageRenditionFormat, VideoMetadata, POSTER_RENDITION_NAME } from "@vertesia/common";
7
- import { Copy, Download, SquarePen, AlertTriangle } from "lucide-react";
4
+ import { Button, Portal, ResizableHandle, ResizablePanel, ResizablePanelGroup, Spinner, useToast, VModal, VModalBody, VModalFooter, VModalTitle } from "@vertesia/ui/core";
5
+ import { JSONDisplay, MarkdownRenderer, Progress, XMLViewer } from "@vertesia/ui/widgets";
6
+ import { ContentNature, ContentObject, ContentObjectStatus, DocAnalyzerProgress, DocProcessorOutputFormat, DocumentMetadata, ImageRenditionFormat, VideoMetadata, POSTER_RENDITION_NAME, WorkflowExecutionStatus, PDF_RENDITION_NAME } from "@vertesia/common";
7
+ import { Copy, Download, SquarePen, AlertTriangle, FileSearch } from "lucide-react";
8
+ import { isPreviewableAsPdf, printElementToPdf, getWorkflowStatusColor, getWorkflowStatusName } from "../../../utils/index.js";
8
9
  import { PropertiesEditorModal } from "./PropertiesEditorModal";
9
10
  import { NavLink } from "@vertesia/ui/router";
11
+ import { MagicPdfView } from "../../../magic-pdf";
12
+ import { SimplePdfViewer } from "../../../pdf-viewer";
13
+ import { useObjectText, usePdfProcessingStatus, useOfficePdfConversion } from "./useContentPanelHooks.js";
10
14
 
11
- // Maximum text size before cropping (128K characters)
12
- const MAX_TEXT_DISPLAY_SIZE = 128 * 1024;
15
+ // Web-supported image formats for browser display
16
+ const WEB_SUPPORTED_IMAGE_FORMATS = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml'];
17
+
18
+ // Web-supported video formats for browser display
19
+ const WEB_SUPPORTED_VIDEO_FORMATS = ['video/mp4', 'video/webm'];
20
+
21
+ // Panel height constants for consistent layout
22
+ const PANEL_HEIGHTS = {
23
+ /** Main resizable panel group */
24
+ main: "h-[calc(100vh-208px)]",
25
+ /** Properties panel content area */
26
+ properties: "h-[calc(100vh-228px)]",
27
+ /** Text/PDF content panel */
28
+ content: "h-[calc(100vh-218px)]",
29
+ /** Video max height */
30
+ video: "max-h-[calc(100vh-268px)]",
31
+ } as const;
32
+
33
+ // ----- Type Definitions -----
34
+
35
+ interface TextActionsProps {
36
+ object: ContentObject;
37
+ text: string | undefined;
38
+ fullText: string | undefined;
39
+ handleCopyContent: (content: string, type: "text" | "properties") => Promise<void>;
40
+ textContainerRef: RefObject<HTMLDivElement | null>;
41
+ }
42
+
43
+ interface TextPanelProps {
44
+ object: ContentObject;
45
+ text: string | undefined;
46
+ isTextCropped: boolean;
47
+ textContainerRef: RefObject<HTMLDivElement | null>;
48
+ }
49
+
50
+ interface OfficePdfPreviewPanelProps {
51
+ pdfRendition?: { content?: { source?: string } };
52
+ officePdfUrl?: string;
53
+ officePdfConverting: boolean;
54
+ officePdfError?: string;
55
+ onConvert: () => void;
56
+ }
57
+
58
+ interface OfficePdfActionsProps {
59
+ object: ContentObject;
60
+ pdfRendition?: { name: string; content: { source?: string } };
61
+ officePdfUrl?: string;
62
+ }
63
+
64
+ // ----- Markdown Components Configuration -----
65
+
66
+ /** Common props for markdown component overrides */
67
+ interface MarkdownComponentProps {
68
+ node?: unknown;
69
+ children?: React.ReactNode;
70
+ }
71
+
72
+ /**
73
+ * Custom markdown components for the content overview.
74
+ * Handles internal links to store objects and provides consistent styling.
75
+ */
76
+ const createMarkdownComponents = () => ({
77
+ a: ({ node, ...props }: MarkdownComponentProps & { href?: string }) => {
78
+ const href = props.href || "";
79
+ if (href.includes("/store/objects/")) {
80
+ return (
81
+ <NavLink topLevelNav href={href} className="text-info">
82
+ {props.children}
83
+ </NavLink>
84
+ );
85
+ }
86
+ return <a {...props} target="_blank" rel="noopener noreferrer" />;
87
+ },
88
+ p: ({ node, ...props }: MarkdownComponentProps) => (
89
+ <p {...props} className="my-0" />
90
+ ),
91
+ pre: ({ node, ...props }: MarkdownComponentProps) => (
92
+ <pre {...props} className="my-2 p-2 rounded" />
93
+ ),
94
+ code: ({ node, className, children, ...props }: MarkdownComponentProps & { className?: string }) => {
95
+ const match = /language-(\w+)/.exec(className || "");
96
+ const isInline = !match;
97
+ return (
98
+ <code
99
+ {...props}
100
+ className={isInline ? "px-1.5 py-0.5 rounded" : "text-muted"}
101
+ >
102
+ {children}
103
+ </code>
104
+ );
105
+ },
106
+ h1: ({ node, ...props }: MarkdownComponentProps) => (
107
+ <h1 {...props} className="font-bold text-2xl my-2" />
108
+ ),
109
+ h2: ({ node, ...props }: MarkdownComponentProps) => (
110
+ <h2 {...props} className="font-bold text-xl my-2" />
111
+ ),
112
+ h3: ({ node, ...props }: MarkdownComponentProps) => (
113
+ <h3 {...props} className="font-bold text-lg my-2" />
114
+ ),
115
+ li: ({ node, ...props }: MarkdownComponentProps) => <li {...props} />,
116
+ });
117
+
118
+ /**
119
+ * Check if an object is in created or processing status.
120
+ */
121
+ function isCreatedOrProcessingStatus(status?: ContentObjectStatus): boolean {
122
+ return status === ContentObjectStatus.created || status === ContentObjectStatus.processing;
123
+ }
124
+
125
+ /**
126
+ * Get the content processor type from object metadata.
127
+ */
128
+ function getContentProcessorType(object: ContentObject): string | undefined {
129
+ return (object.metadata as DocumentMetadata)?.content_processor?.type;
130
+ }
131
+
132
+ /**
133
+ * Check if text content appears to be markdown based on common patterns.
134
+ */
135
+ function looksLikeMarkdown(text: string | undefined): boolean {
136
+ if (!text) return false;
137
+ return (
138
+ text.includes("\n# ") ||
139
+ text.includes("\n## ") ||
140
+ text.includes("\n### ") ||
141
+ text.includes("\n* ") ||
142
+ text.includes("\n- ") ||
143
+ text.includes("\n+ ") ||
144
+ text.includes("![") ||
145
+ text.includes("](")
146
+ );
147
+ }
13
148
 
14
149
  enum PanelView {
15
150
  Text = "text",
16
151
  Image = "image",
17
- Video = "video"
152
+ Video = "video",
153
+ Pdf = "pdf"
18
154
  }
19
155
 
20
156
  interface ContentOverviewProps {
@@ -54,14 +190,14 @@ export function ContentOverview({
54
190
 
55
191
  return (
56
192
  <>
57
- <ResizablePanelGroup direction="horizontal" className="h-[calc(100vh-200px)]">
193
+ <ResizablePanelGroup direction="horizontal" className={PANEL_HEIGHTS.main}>
58
194
  <ResizablePanel className="min-w-[100px]">
59
195
  <PropertiesPanel object={object} refetch={refetch ?? (() => Promise.resolve())} handleCopyContent={handleCopyContent} />
60
196
  </ResizablePanel>
61
197
  <ResizableHandle withHandle />
62
198
 
63
199
  <ResizablePanel className="min-w-[100px]">
64
- <DataPanel object={object} loadText={loadText ?? false} handleCopyContent={handleCopyContent} />
200
+ <DataPanel object={object} loadText={loadText ?? false} handleCopyContent={handleCopyContent} refetch={refetch} />
65
201
  </ResizablePanel>
66
202
  </ResizablePanelGroup>
67
203
 
@@ -136,7 +272,7 @@ function PropertiesPanel({ object, refetch, handleCopyContent }: { object: Conte
136
272
 
137
273
  {
138
274
  object.properties ? (
139
- <div className="h-[calc(100vh-220px)] overflow-auto px-2">
275
+ <div className={`${PANEL_HEIGHTS.properties} overflow-auto px-2`}>
140
276
  <JSONDisplay
141
277
  value={object.properties}
142
278
  viewCode={viewCode}
@@ -144,7 +280,7 @@ function PropertiesPanel({ object, refetch, handleCopyContent }: { object: Conte
144
280
  />
145
281
  </div>
146
282
  ) : (
147
- <div className="h-[calc(100vh-220px)] overflow-auto px-2">
283
+ <div className={`${PANEL_HEIGHTS.properties} overflow-auto px-2`}>
148
284
  <div>No properties defined</div>
149
285
  </div>
150
286
  )
@@ -160,11 +296,16 @@ function PropertiesPanel({ object, refetch, handleCopyContent }: { object: Conte
160
296
  );
161
297
  }
162
298
 
163
- function DataPanel({ object, loadText, handleCopyContent }: { object: ContentObject, loadText: boolean, handleCopyContent: (content: string, type: "text" | "properties") => Promise<void> }) {
164
- const { store } = useUserSession();
165
-
299
+ function DataPanel({ object, loadText, handleCopyContent, refetch }: { object: ContentObject, loadText: boolean, handleCopyContent: (content: string, type: "text" | "properties") => Promise<void>, refetch?: () => Promise<unknown> }) {
166
300
  const isImage = object?.metadata?.type === ContentNature.Image;
167
301
  const isVideo = object?.metadata?.type === ContentNature.Video;
302
+ const isPdf = object?.content?.type === 'application/pdf';
303
+ const isPreviewableAsPdfDoc = object?.content?.type ? isPreviewableAsPdf(object.content.type) : false;
304
+ const isCreatedOrProcessing = isCreatedOrProcessingStatus(object?.status);
305
+
306
+ // Check if PDF rendition exists for Office documents
307
+ const metadata = object.metadata as DocumentMetadata;
308
+ const pdfRendition = metadata?.renditions?.find(r => r.name === PDF_RENDITION_NAME);
168
309
 
169
310
  // Determine initial panel view
170
311
  const getInitialView = (): PanelView => {
@@ -175,94 +316,171 @@ function DataPanel({ object, loadText, handleCopyContent }: { object: ContentObj
175
316
 
176
317
  const [currentPanel, setCurrentPanel] = useState<PanelView>(getInitialView());
177
318
 
178
- const [text, setText] = useState<string | undefined>(object.text);
179
- const [isLoadingText, setIsLoadingText] = useState<boolean>(false);
180
- const [isTextCropped, setIsTextCropped] = useState<boolean>(false);
319
+ // Use custom hooks for text loading, PDF processing, and Office conversion
320
+ const {
321
+ fullText,
322
+ displayText,
323
+ isLoading: isLoadingText,
324
+ isCropped: isTextCropped,
325
+ } = useObjectText(object.id, object.text, loadText);
326
+
327
+ // Poll for PDF/document processing status when object is created or processing
328
+ const shouldPollProgress = (isPdf || isPreviewableAsPdfDoc) && isCreatedOrProcessing;
329
+ const {
330
+ progress: pdfProgress,
331
+ status: pdfStatus,
332
+ outputFormat: pdfOutputFormat,
333
+ isComplete: processingComplete,
334
+ } = usePdfProcessingStatus(object.id, shouldPollProgress);
181
335
 
336
+ // Office document PDF conversion
337
+ const {
338
+ pdfUrl: officePdfUrl,
339
+ isConverting: officePdfConverting,
340
+ error: officePdfError,
341
+ triggerConversion: triggerOfficePdfConversion,
342
+ } = useOfficePdfConversion(object.id, isPreviewableAsPdfDoc);
343
+
344
+ // Reload object when PDF processing completes
182
345
  useEffect(() => {
183
- if (loadText && !text) {
184
- setIsLoadingText(true);
185
- store.objects
186
- .getObjectText(object.id)
187
- .then((res) => {
188
- if (res.text.length > MAX_TEXT_DISPLAY_SIZE) {
189
- // Crop the text to 128K characters
190
- const croppedText = res.text.substring(0, MAX_TEXT_DISPLAY_SIZE);
191
- setText(croppedText);
192
- setIsTextCropped(true);
193
- } else {
194
- setText(res.text);
195
- setIsTextCropped(false);
196
- }
197
- })
198
- .catch((err) => {
199
- console.error("Failed to load text", err);
200
- })
201
- .finally(() => {
202
- setIsLoadingText(false);
203
- });
346
+ if (processingComplete && pdfStatus === WorkflowExecutionStatus.COMPLETED) {
347
+ refetch?.();
204
348
  }
205
- }, [loadText]);
349
+ }, [processingComplete, pdfStatus, refetch]);
350
+
351
+ // Show processing panel when workflow is running (for both PDFs and Office documents)
352
+ const showProcessingPanel = (isPdf || isPreviewableAsPdfDoc) && isCreatedOrProcessing && !processingComplete && pdfStatus === WorkflowExecutionStatus.RUNNING;
353
+
354
+ const textContainerRef = useRef<HTMLDivElement | null>(null);
206
355
 
207
356
  return (
208
- <>
209
- <div className="flex justify-between items-center px-2">
210
- <div className="flex items-center gap-1 bg-muted mb-2 p-1 rounded">
211
- {isImage &&
212
- <Button
213
- variant={currentPanel === PanelView.Image ? "primary" : "ghost"}
214
- size="sm"
215
- alt="View Image"
216
- onClick={() => setCurrentPanel(PanelView.Image)}
217
- >
218
- Image
219
- </Button>
220
- }
221
- {isVideo &&
357
+ <div className="flex flex-col h-full">
358
+ <div className="flex justify-between items-center px-2 shrink-0">
359
+ <div className="flex items-center gap-2 mb-2">
360
+ <div className="flex items-center gap-1 bg-muted p-1 rounded">
361
+ {isImage &&
362
+ <Button
363
+ variant={currentPanel === PanelView.Image ? "primary" : "ghost"}
364
+ size="sm"
365
+ alt="View Image"
366
+ onClick={() => setCurrentPanel(PanelView.Image)}
367
+ >
368
+ Image
369
+ </Button>
370
+ }
371
+ {isVideo &&
372
+ <Button
373
+ variant={currentPanel === PanelView.Video ? "primary" : "ghost"}
374
+ size="sm"
375
+ alt="View Video"
376
+ onClick={() => setCurrentPanel(PanelView.Video)}
377
+ >
378
+ Video
379
+ </Button>
380
+ }
222
381
  <Button
223
- variant={currentPanel === PanelView.Video ? "primary" : "ghost"}
382
+ variant={currentPanel === PanelView.Text ? "primary" : "ghost"}
224
383
  size="sm"
225
- alt="View Video"
226
- onClick={() => setCurrentPanel(PanelView.Video)}
384
+ alt="View Text"
385
+ onClick={() => setCurrentPanel(PanelView.Text)}
227
386
  >
228
- Video
387
+ Text
229
388
  </Button>
230
- }
231
- <Button
232
- variant={currentPanel === PanelView.Text ? "primary" : "ghost"}
233
- size="sm"
234
- alt="View Text"
235
- onClick={() => setCurrentPanel(PanelView.Text)}
236
- >
237
- Text
238
- </Button>
239
-
389
+ {isPdf &&
390
+ <Button
391
+ variant={currentPanel === PanelView.Pdf ? "primary" : "ghost"}
392
+ size="sm"
393
+ alt="View PDF"
394
+ onClick={() => setCurrentPanel(PanelView.Pdf)}
395
+ >
396
+ PDF
397
+ </Button>
398
+ }
399
+ {isPreviewableAsPdfDoc && (
400
+ <Button
401
+ variant={currentPanel === PanelView.Pdf ? "primary" : "ghost"}
402
+ size="sm"
403
+ alt="View as PDF"
404
+ onClick={() => {
405
+ setCurrentPanel(PanelView.Pdf);
406
+ if (!pdfRendition && !officePdfUrl && !officePdfConverting) {
407
+ triggerOfficePdfConversion();
408
+ }
409
+ }}
410
+ disabled={officePdfConverting}
411
+ >
412
+ {officePdfConverting ? <Spinner size="sm" /> : "PDF"}
413
+ </Button>
414
+ )}
415
+ </div>
416
+ <PdfActions object={object} />
240
417
  </div>
241
- {currentPanel === PanelView.Text && <TextActions object={object} text={text} handleCopyContent={handleCopyContent} />}
418
+ {currentPanel === PanelView.Text && !showProcessingPanel && (
419
+ <TextActions
420
+ object={object}
421
+ text={displayText}
422
+ fullText={fullText}
423
+ handleCopyContent={handleCopyContent}
424
+ textContainerRef={textContainerRef}
425
+ />
426
+ )}
427
+ {currentPanel === PanelView.Pdf && isPreviewableAsPdfDoc && (pdfRendition || officePdfUrl) && (
428
+ <OfficePdfActions
429
+ object={object}
430
+ pdfRendition={pdfRendition}
431
+ officePdfUrl={officePdfUrl}
432
+ />
433
+ )}
242
434
  </div>
243
- {
244
- currentPanel === PanelView.Image ? (
245
- <ImagePanel object={object} />
246
- ) : currentPanel === PanelView.Video ? (
247
- <VideoPanel object={object} />
248
- ) : (
249
- isLoadingText ? (
250
- <div className="flex justify-center items-center h-[calc(100vh-260px)]">
251
- <Spinner size="lg" />
252
- </div>
253
- ) : (
254
- <TextPanel object={object} text={text} isTextCropped={isTextCropped} />
255
- )
256
- )
257
- }
258
- </>
435
+ {currentPanel === PanelView.Image && (
436
+ <ImagePanel object={object} />
437
+ )}
438
+ {currentPanel === PanelView.Video && (
439
+ <VideoPanel object={object} />
440
+ )}
441
+ {currentPanel === PanelView.Pdf && isPdf && (
442
+ <PdfPreviewPanel object={object} />
443
+ )}
444
+ {currentPanel === PanelView.Pdf && isPreviewableAsPdfDoc && (
445
+ <OfficePdfPreviewPanel
446
+ pdfRendition={pdfRendition}
447
+ officePdfUrl={officePdfUrl}
448
+ officePdfConverting={officePdfConverting}
449
+ officePdfError={officePdfError}
450
+ onConvert={triggerOfficePdfConversion}
451
+ />
452
+ )}
453
+ {currentPanel === PanelView.Text && showProcessingPanel && (
454
+ <PdfProcessingPanel progress={pdfProgress} status={pdfStatus} outputFormat={pdfOutputFormat} />
455
+ )}
456
+ {currentPanel === PanelView.Text && !showProcessingPanel && isLoadingText && (
457
+ <div className="flex justify-center items-center flex-1">
458
+ <Spinner size="lg" />
459
+ </div>
460
+ )}
461
+ {currentPanel === PanelView.Text && !showProcessingPanel && !isLoadingText && (
462
+ <TextPanel
463
+ object={object}
464
+ text={displayText}
465
+ isTextCropped={isTextCropped}
466
+ textContainerRef={textContainerRef}
467
+ />
468
+ )}
469
+ </div>
259
470
  );
260
471
  }
261
472
 
262
- function TextActions({ object, text, handleCopyContent }: { object: ContentObject, handleCopyContent: (content: string, type: "text" | "properties") => Promise<void>, text: string | undefined }) {
473
+ function TextActions({
474
+ object,
475
+ text,
476
+ fullText,
477
+ handleCopyContent,
478
+ textContainerRef,
479
+ }: TextActionsProps) {
263
480
  const { client } = useUserSession();
264
481
  const toast = useToast();
265
482
  const [loadingFormat, setLoadingFormat] = useState<"docx" | "pdf" | null>(null);
483
+ const [isPdfModalOpen, setIsPdfModalOpen] = useState(false);
266
484
 
267
485
  const content = object.content;
268
486
 
@@ -271,6 +489,9 @@ function TextActions({ object, text, handleCopyContent }: { object: ContentObjec
271
489
  content.type &&
272
490
  content.type === "text/markdown";
273
491
 
492
+ // Get content processor type for file extension detection
493
+ const contentProcessorType = getContentProcessorType(object);
494
+
274
495
  const handleExportDocument = async (format: "docx" | "pdf") => {
275
496
  // Prevent multiple concurrent exports
276
497
  if (loadingFormat) return;
@@ -354,54 +575,162 @@ function TextActions({ object, text, handleCopyContent }: { object: ContentObjec
354
575
  };
355
576
 
356
577
  const handleExportDocx = () => handleExportDocument("docx");
357
- const handleExportPdf = () => handleExportDocument("pdf");
578
+
579
+ const handleClientPdfExport = () => {
580
+ if (!textContainerRef.current) {
581
+ toast({
582
+ status: "error",
583
+ title: "PDF export failed",
584
+ description: "No content available to export",
585
+ duration: 3000,
586
+ });
587
+ return;
588
+ }
589
+ setIsPdfModalOpen(true);
590
+ };
591
+
592
+ const handleConfirmClientPdfExport = () => {
593
+ if (!textContainerRef.current) {
594
+ toast({
595
+ status: "error",
596
+ title: "PDF export failed",
597
+ description: "No content available to export",
598
+ duration: 3000,
599
+ });
600
+ return;
601
+ }
602
+
603
+ const baseName = object.name || object.id;
604
+ const pdfTitle = `${baseName || "document"} - content`;
605
+ const success = printElementToPdf(textContainerRef.current, pdfTitle);
606
+
607
+ if (!success) {
608
+ toast({
609
+ status: "error",
610
+ title: "PDF export failed",
611
+ description: "Unable to open print preview",
612
+ duration: 4000,
613
+ });
614
+ return;
615
+ }
616
+
617
+ toast({
618
+ status: "success",
619
+ title: "PDF export ready",
620
+ description: "Use your browser's Print dialog to save as PDF",
621
+ duration: 4000,
622
+ });
623
+ setIsPdfModalOpen(false);
624
+ };
625
+
626
+ const handleDownloadText = (e: React.MouseEvent) => {
627
+ e.preventDefault();
628
+ e.stopPropagation();
629
+ if (!fullText) return;
630
+ // Determine file extension based on content processor type
631
+ let ext = "txt";
632
+ let mimeType = "text/plain";
633
+ if (contentProcessorType === "xml") {
634
+ ext = "xml";
635
+ mimeType = "text/xml";
636
+ } else if (contentProcessorType === "markdown" || isMarkdown) {
637
+ ext = "md";
638
+ mimeType = "text/markdown";
639
+ }
640
+ const blob = new Blob([fullText], { type: mimeType });
641
+ const url = URL.createObjectURL(blob);
642
+ const filename = `${object.name || "document"}.${ext}`;
643
+
644
+ // Use the download attribute with an anchor, but avoid triggering navigation
645
+ const link = document.createElement("a");
646
+ link.href = url;
647
+ link.download = filename;
648
+ link.style.display = "none";
649
+ // Temporarily remove from DOM event flow
650
+ setTimeout(() => {
651
+ link.click();
652
+ URL.revokeObjectURL(url);
653
+ }, 0);
654
+ };
655
+
358
656
  return (
359
- <div className="h-[41px] text-lg font-semibold flex justify-between items-center px-2">
360
- <div className="flex items-center gap-2">
361
- {text && (
362
- <Button variant="ghost" size="sm" title="Copy text" className="flex items-center gap-2" onClick={() => handleCopyContent(text, "text")}>
363
- <Copy className="size-4" />
364
- </Button>
365
- )}
366
- {isMarkdown && text && (
367
- <>
368
- <Button
369
- variant="ghost"
370
- size="sm"
371
- onClick={handleExportDocx}
372
- disabled={loadingFormat !== null}
373
- className="flex items-center gap-2"
374
- >
375
- {loadingFormat === "docx" ? (
376
- <Spinner size="sm" />
377
- ) : (
657
+ <>
658
+ <div className="h-[41px] text-lg font-semibold flex justify-between items-center px-2">
659
+ <div className="flex items-center gap-2">
660
+ {fullText && (
661
+ <>
662
+ <Button variant="ghost" size="sm" title="Copy text" onClick={() => handleCopyContent(fullText, "text")}>
663
+ <Copy className="size-4" />
664
+ </Button>
665
+ <Button variant="ghost" size="sm" title="Download text" onClick={handleDownloadText}>
378
666
  <Download className="size-4" />
379
- )}
380
- DOCX
381
- </Button>
382
- <Button
383
- variant="ghost"
384
- size="sm"
385
- onClick={handleExportPdf}
386
- disabled={loadingFormat !== null}
387
- className="flex items-center gap-2"
388
- >
389
- {loadingFormat === "pdf" ? (
390
- <Spinner size="sm" />
391
- ) : (
667
+ </Button>
668
+ </>
669
+ )}
670
+ {isMarkdown && text && (
671
+ <>
672
+ <Button
673
+ variant="ghost"
674
+ size="sm"
675
+ onClick={handleExportDocx}
676
+ disabled={loadingFormat !== null}
677
+ className="flex items-center gap-2"
678
+ >
679
+ {loadingFormat === "docx" ? (
680
+ <Spinner size="sm" />
681
+ ) : (
682
+ <Download className="size-4" />
683
+ )}
684
+ DOCX
685
+ </Button>
686
+ <Button
687
+ variant="ghost"
688
+ size="sm"
689
+ onClick={handleClientPdfExport}
690
+ className="flex items-center gap-2"
691
+ >
392
692
  <Download className="size-4" />
393
- )}
394
- PDF
395
- </Button>
396
- </>
397
- )}
693
+ PDF
694
+ </Button>
695
+ </>
696
+ )}
697
+ </div>
398
698
  </div>
399
- </div>
699
+ <VModal isOpen={isPdfModalOpen} onClose={() => setIsPdfModalOpen(false)}>
700
+ <VModalTitle>Export document as PDF</VModalTitle>
701
+ <VModalBody>
702
+ <p className="mb-2">
703
+ This will open your browser&apos;s print dialog with the current document content.
704
+ </p>
705
+ <p className="text-sm text-muted">
706
+ To save a PDF, choose &quot;Save as PDF&quot; or a similar option in the print dialog.
707
+ </p>
708
+ </VModalBody>
709
+ <VModalFooter align="right">
710
+ <Button variant="ghost" size="sm" onClick={() => setIsPdfModalOpen(false)}>
711
+ Cancel
712
+ </Button>
713
+ <Button size="sm" onClick={handleConfirmClientPdfExport}>
714
+ Open print dialog
715
+ </Button>
716
+ </VModalFooter>
717
+ </VModal>
718
+ </>
400
719
  );
401
720
  }
402
721
 
403
- const TextPanel = memo(({ object, text, isTextCropped }: { object: ContentObject, text: string | undefined, isTextCropped: boolean }) => {
722
+ const TextPanel = memo(({
723
+ object,
724
+ text,
725
+ isTextCropped,
726
+ textContainerRef,
727
+ }: TextPanelProps) => {
404
728
  const content = object.content;
729
+ const isCreatedOrProcessing = isCreatedOrProcessingStatus(object?.status);
730
+
731
+ // Check content processor type for XML
732
+ const contentProcessorType = getContentProcessorType(object);
733
+ const isXml = contentProcessorType === "xml";
405
734
 
406
735
  // Check if content type is markdown or plain text
407
736
  const isMarkdownOrText =
@@ -409,20 +738,8 @@ const TextPanel = memo(({ object, text, isTextCropped }: { object: ContentObject
409
738
  content.type &&
410
739
  (content.type === "text/markdown" || content.type === "text/plain");
411
740
 
412
- // Check if text content looks like markdown
413
- const seemsMarkdown = text && (
414
- text.includes("\n# ") ||
415
- text.includes("\n## ") ||
416
- text.includes("\n### ") ||
417
- text.includes("\n* ") ||
418
- text.includes("\n- ") ||
419
- text.includes("\n+ ") ||
420
- text.includes("![") ||
421
- text.includes("](")
422
- );
423
-
424
- // Render as markdown if it's markdown/text type OR if text looks like markdown
425
- const shouldRenderAsMarkdown = isMarkdownOrText || seemsMarkdown;
741
+ // Render as markdown if it's markdown/text type OR if text looks like markdown (but not if XML)
742
+ const shouldRenderAsMarkdown = !isXml && (isMarkdownOrText || looksLikeMarkdown(text));
426
743
 
427
744
  return (
428
745
  text ? (
@@ -435,71 +752,17 @@ const TextPanel = memo(({ object, text, isTextCropped }: { object: ContentObject
435
752
  </div>
436
753
  </div>
437
754
  )}
438
- <div className="max-w-7xl px-2 h-[calc(100vh-210px)] overflow-auto">
439
- {shouldRenderAsMarkdown ? (
755
+ <div
756
+ className={`max-w-7xl px-2 ${PANEL_HEIGHTS.content} overflow-auto`}
757
+ ref={textContainerRef}
758
+ >
759
+ {isXml ? (
760
+ <div className="px-4 py-2">
761
+ <XMLViewer xml={text} collapsible />
762
+ </div>
763
+ ) : shouldRenderAsMarkdown ? (
440
764
  <div className="vprose prose-sm p-1">
441
- <MarkdownRenderer
442
- components={{
443
- a: ({ node, ...props }: { node?: any; href?: string; children?: React.ReactNode }) => {
444
- const href = props.href || "";
445
- if (href.includes("/store/objects/")) {
446
- return (
447
- <NavLink
448
- topLevelNav
449
- href={href}
450
- className="text-info"
451
- >
452
- {props.children}
453
- </NavLink>
454
- );
455
- }
456
- return <a {...props} data-debug="test" target="_blank" rel="noopener noreferrer" />;
457
- },
458
- p: ({ node, ...props }: { node?: any; children?: React.ReactNode }) => (
459
- <p {...props} className={`my-0`} />
460
- ),
461
- pre: ({ node, ...props }: { node?: any; children?: React.ReactNode }) => (
462
- <pre {...props} className={`my-2 p-2 rounded`} />
463
- ),
464
- code: ({
465
- node,
466
- className,
467
- children,
468
- ...props
469
- }: {
470
- node?: any;
471
- className?: string;
472
- children?: React.ReactNode;
473
- }) => {
474
- const match = /language-(\w+)/.exec(className || "");
475
- const isInline = !match;
476
- return (
477
- <code
478
- {...props}
479
- className={
480
- isInline
481
- ? `px-1.5 py-0.5 rounded`
482
- : "text-muted"
483
- }
484
- >
485
- {children}
486
- </code>
487
- );
488
- },
489
- h1: ({ node, ...props }: { node?: any; children?: React.ReactNode }) => (
490
- <h1 {...props} className={`font-bold text-2xl my-2`} />
491
- ),
492
- h2: ({ node, ...props }: { node?: any; children?: React.ReactNode }) => (
493
- <h2 {...props} className={`font-bold text-xl my-2`} />
494
- ),
495
- h3: ({ node, ...props }: { node?: any; children?: React.ReactNode }) => (
496
- <h3 {...props} className={`font-bold text-lg my-2`} />
497
- ),
498
- li: ({ node, ...props }: { node?: any; children?: React.ReactNode }) => (
499
- <li {...props} />
500
- ),
501
- }}
502
- >
765
+ <MarkdownRenderer components={createMarkdownComponents()}>
503
766
  {text}
504
767
  </MarkdownRenderer>
505
768
  </div>
@@ -512,7 +775,7 @@ const TextPanel = memo(({ object, text, isTextCropped }: { object: ContentObject
512
775
  </>
513
776
  ) :
514
777
  <div className="px-2">
515
- <div>No content</div>
778
+ <div>{isCreatedOrProcessing ? "Extracting content..." : "No content"}</div>
516
779
  </div>
517
780
  );
518
781
  });
@@ -527,8 +790,7 @@ function ImagePanel({ object }: { object: ContentObject }) {
527
790
  useEffect(() => {
528
791
  if (isImage) {
529
792
  const loadImage = async () => {
530
- const webSupportedFormats = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml'];
531
- const isOriginalWebSupported = content?.type && webSupportedFormats.includes(content.type);
793
+ const isOriginalWebSupported = content?.type && WEB_SUPPORTED_IMAGE_FORMATS.includes(content.type);
532
794
 
533
795
  try {
534
796
  const rendition = await client.objects.getRendition(object.id, {
@@ -591,8 +853,7 @@ function VideoPanel({ object }: { object: ContentObject }) {
591
853
  renditions.find(r => r.content.type === 'video/webm');
592
854
 
593
855
  // Check if original file is web-compatible
594
- const webSupportedFormats = ['video/mp4', 'video/webm'];
595
- const isOriginalWebSupported = content?.type && webSupportedFormats.includes(content.type);
856
+ const isOriginalWebSupported = content?.type && WEB_SUPPORTED_VIDEO_FORMATS.includes(content.type);
596
857
 
597
858
  // Get poster
598
859
  const poster = renditions.find(r => r.name === POSTER_RENDITION_NAME);
@@ -656,7 +917,7 @@ function VideoPanel({ object }: { object: ContentObject }) {
656
917
  src={videoUrl}
657
918
  poster={posterUrl}
658
919
  controls
659
- className="w-full max-h-[calc(100vh-260px)] object-contain"
920
+ className={`w-full ${PANEL_HEIGHTS.video} object-contain`}
660
921
  >
661
922
  Your browser does not support the video tag.
662
923
  </video>
@@ -667,4 +928,210 @@ function VideoPanel({ object }: { object: ContentObject }) {
667
928
  )}
668
929
  </div>
669
930
  );
931
+ }
932
+
933
+ function PdfActions({ object }: { object: ContentObject }) {
934
+ const [isPdfPreviewOpen, setPdfPreviewOpen] = useState(false);
935
+
936
+ // Check if PDF has been processed (content_processor.type is xml or markdown)
937
+ const contentProcessorType = getContentProcessorType(object);
938
+ const hasPdfAnalysis = contentProcessorType === "xml" || contentProcessorType === "markdown";
939
+
940
+ if (!hasPdfAnalysis) return null;
941
+
942
+ return (
943
+ <>
944
+ <Button
945
+ variant="ghost"
946
+ size="sm"
947
+ onClick={() => setPdfPreviewOpen(true)}
948
+ title="Side by side view"
949
+ >
950
+ <FileSearch className="size-4" />
951
+ </Button>
952
+ {isPdfPreviewOpen && (
953
+ <Portal>
954
+ <MagicPdfView objectId={object.id} onClose={() => setPdfPreviewOpen(false)} />
955
+ </Portal>
956
+ )}
957
+ </>
958
+ );
959
+ }
960
+
961
+ function OfficePdfActions({
962
+ object,
963
+ pdfRendition,
964
+ officePdfUrl,
965
+ }: OfficePdfActionsProps) {
966
+ const { client } = useUserSession();
967
+ const toast = useToast();
968
+ const [isDownloading, setIsDownloading] = useState(false);
969
+
970
+ const handleDownloadPdf = async () => {
971
+ setIsDownloading(true);
972
+ try {
973
+ let downloadUrl = officePdfUrl;
974
+
975
+ // If we have a rendition source but no signed URL yet, get a signed URL
976
+ if (!downloadUrl && pdfRendition?.content?.source) {
977
+ const response = await client.files.getDownloadUrl(
978
+ pdfRendition.content.source,
979
+ `${object.name || 'document'}.pdf`,
980
+ 'attachment'
981
+ );
982
+ downloadUrl = response.url;
983
+ }
984
+
985
+ if (downloadUrl) {
986
+ // Open in new tab - browser will handle as download due to content-disposition
987
+ window.open(downloadUrl, '_blank');
988
+ }
989
+ } catch (err) {
990
+ console.error('Failed to download PDF:', err);
991
+ toast({
992
+ status: 'error',
993
+ title: 'Download failed',
994
+ description: 'Failed to download the PDF file',
995
+ duration: 5000,
996
+ });
997
+ } finally {
998
+ setIsDownloading(false);
999
+ }
1000
+ };
1001
+
1002
+ return (
1003
+ <div className="flex items-center gap-2">
1004
+ <Button
1005
+ variant="ghost"
1006
+ size="sm"
1007
+ onClick={handleDownloadPdf}
1008
+ disabled={isDownloading}
1009
+ title="Download PDF"
1010
+ >
1011
+ {isDownloading ? <Spinner size="sm" /> : <Download className="size-4" />}
1012
+ </Button>
1013
+ </div>
1014
+ );
1015
+ }
1016
+
1017
+ function PdfPreviewPanel({ object }: { object: ContentObject }) {
1018
+ return (
1019
+ <div className={PANEL_HEIGHTS.content}>
1020
+ <SimplePdfViewer
1021
+ object={object}
1022
+ className="h-full"
1023
+ />
1024
+ </div>
1025
+ );
1026
+ }
1027
+
1028
+ /**
1029
+ * Panel for displaying Office documents converted to PDF.
1030
+ * Handles the various states: converting, error, showing PDF.
1031
+ */
1032
+ function OfficePdfPreviewPanel({
1033
+ pdfRendition,
1034
+ officePdfUrl,
1035
+ officePdfConverting,
1036
+ officePdfError,
1037
+ onConvert,
1038
+ }: OfficePdfPreviewPanelProps) {
1039
+ if (officePdfConverting) {
1040
+ return (
1041
+ <div className="flex flex-col justify-center items-center flex-1 gap-2">
1042
+ <Spinner size="lg" />
1043
+ <span className="text-muted">Converting to PDF...</span>
1044
+ </div>
1045
+ );
1046
+ }
1047
+
1048
+ if (officePdfError) {
1049
+ return (
1050
+ <div className="flex flex-col justify-center items-center flex-1 gap-2 text-destructive">
1051
+ <AlertTriangle className="size-8" />
1052
+ <span>{officePdfError}</span>
1053
+ </div>
1054
+ );
1055
+ }
1056
+
1057
+ if (pdfRendition?.content?.source) {
1058
+ return (
1059
+ <div className={PANEL_HEIGHTS.content}>
1060
+ <SimplePdfViewer source={pdfRendition.content.source} className="h-full" />
1061
+ </div>
1062
+ );
1063
+ }
1064
+
1065
+ if (officePdfUrl) {
1066
+ return (
1067
+ <div className={PANEL_HEIGHTS.content}>
1068
+ <SimplePdfViewer url={officePdfUrl} className="h-full" />
1069
+ </div>
1070
+ );
1071
+ }
1072
+
1073
+ return (
1074
+ <div className="flex flex-col justify-center items-center flex-1 gap-2">
1075
+ <Button onClick={onConvert}>
1076
+ Convert to PDF
1077
+ </Button>
1078
+ </div>
1079
+ );
1080
+ }
1081
+
1082
+ function PdfProcessingPanel({ progress, status, outputFormat }: { progress?: DocAnalyzerProgress, status?: WorkflowExecutionStatus, outputFormat?: DocProcessorOutputFormat }) {
1083
+ const statusColor = getWorkflowStatusColor(status);
1084
+ const statusName = getWorkflowStatusName(status);
1085
+
1086
+ // Show detailed progress (tables, images, visuals) for XML processing
1087
+ const isXmlProcessing = outputFormat === "xml";
1088
+
1089
+ // Ensure percent is a valid number (handle undefined and NaN from division by zero)
1090
+ const percent = progress?.percent != null && !isNaN(progress.percent) ? progress.percent : 0;
1091
+
1092
+ return (
1093
+ <div className="px-4 py-4">
1094
+ {progress && (
1095
+ <div className="space-y-2">
1096
+ <div className="flex flex-col gap-1">
1097
+ <ProgressLine name={isXmlProcessing ? "Analyze Layouts" : "Analyze Page"} progress={progress.pages} />
1098
+ {isXmlProcessing && (
1099
+ <>
1100
+ <ProgressLine name="Extract Tables" progress={progress.tables} />
1101
+ <ProgressLine name="Describe Images" progress={progress.images} />
1102
+ <ProgressLine name="Process Visually" progress={progress.visuals} />
1103
+ </>
1104
+ )}
1105
+ </div>
1106
+ <div className="pt-2 text-sm text-muted">
1107
+ Progress: {percent}%
1108
+ <span className="px-2">&bull;</span>
1109
+ <span className={statusColor}>{statusName}</span>
1110
+ {progress.started_at && (
1111
+ <>
1112
+ <span className="px-2">&bull;</span>
1113
+ <span>{((Date.now() - progress.started_at) / 1000).toFixed(0)} sec. elapsed</span>
1114
+ </>
1115
+ )}
1116
+ </div>
1117
+ <Progress percent={percent} />
1118
+ </div>
1119
+ )}
1120
+ {!progress && (
1121
+ <div className="flex items-center gap-2 text-muted">
1122
+ <Spinner size="sm" />
1123
+ <span>Loading processing status...</span>
1124
+ </div>
1125
+ )}
1126
+ </div>
1127
+ );
1128
+ }
1129
+
1130
+ function ProgressLine({ name, progress }: { name: string, progress: { total: number; processed: number } }) {
1131
+ return (
1132
+ <div className="flex gap-2 text-sm">
1133
+ <span className="text-muted min-w-36">{name}:</span>
1134
+ <span>{progress.processed} of {progress.total}</span>
1135
+ </div>
1136
+ );
670
1137
  }