@vertesia/ui 0.80.0-dev.20251121 → 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.
- package/README.md +105 -0
- package/lib/esm/core/components/MenuList.js +2 -5
- package/lib/esm/core/components/MenuList.js.map +1 -1
- package/lib/esm/core/components/MessageBox.js +1 -1
- package/lib/esm/core/components/MessageBox.js.map +1 -1
- package/lib/esm/core/components/shadcn/dialog.js +16 -2
- package/lib/esm/core/components/shadcn/dialog.js.map +1 -1
- package/lib/esm/core/components/shadcn/filters/filter/SelectFilter.js +6 -9
- package/lib/esm/core/components/shadcn/filters/filter/SelectFilter.js.map +1 -1
- package/lib/esm/core/components/shadcn/filters/filterBar.js +1 -1
- package/lib/esm/core/components/shadcn/filters/filterBar.js.map +1 -1
- package/lib/esm/core/components/shadcn/selectBox.js +23 -5
- package/lib/esm/core/components/shadcn/selectBox.js.map +1 -1
- package/lib/esm/core/components/shadcn/tabs.js +3 -3
- package/lib/esm/core/components/shadcn/tabs.js.map +1 -1
- package/lib/esm/env/index.js +3 -0
- package/lib/esm/env/index.js.map +1 -1
- package/lib/esm/features/agent/chat/AgentChart.js +184 -0
- package/lib/esm/features/agent/chat/AgentChart.js.map +1 -0
- package/lib/esm/features/agent/chat/ModernAgentConversation.js +87 -10
- package/lib/esm/features/agent/chat/ModernAgentConversation.js.map +1 -1
- package/lib/esm/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js +6 -2
- package/lib/esm/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js.map +1 -1
- package/lib/esm/features/agent/chat/ModernAgentOutput/Header.js +4 -4
- package/lib/esm/features/agent/chat/ModernAgentOutput/Header.js.map +1 -1
- package/lib/esm/features/agent/chat/ModernAgentOutput/InlineSlidingPlanPanel.js +4 -1
- package/lib/esm/features/agent/chat/ModernAgentOutput/InlineSlidingPlanPanel.js.map +1 -1
- package/lib/esm/features/agent/chat/ModernAgentOutput/MessageInput.js +12 -4
- package/lib/esm/features/agent/chat/ModernAgentOutput/MessageInput.js.map +1 -1
- package/lib/esm/features/agent/chat/ModernAgentOutput/MessageItem.js +60 -56
- package/lib/esm/features/agent/chat/ModernAgentOutput/MessageItem.js.map +1 -1
- package/lib/esm/features/agent/chat/index.js +1 -0
- package/lib/esm/features/agent/chat/index.js.map +1 -1
- package/lib/esm/features/agent/createChartTool.js +354 -0
- package/lib/esm/features/agent/createChartTool.js.map +1 -0
- package/lib/esm/features/agent/examples.js +295 -0
- package/lib/esm/features/agent/examples.js.map +1 -0
- package/lib/esm/features/agent/index.js +2 -0
- package/lib/esm/features/agent/index.js.map +1 -1
- package/lib/esm/features/agent/visualization.js +165 -0
- package/lib/esm/features/agent/visualization.js.map +1 -0
- package/lib/esm/features/facets/CollectionsFacetsNav.js +5 -1
- package/lib/esm/features/facets/CollectionsFacetsNav.js.map +1 -1
- package/lib/esm/features/index.js +1 -0
- package/lib/esm/features/index.js.map +1 -1
- package/lib/esm/features/layout/GenericPageNavHeader.js +14 -4
- package/lib/esm/features/layout/GenericPageNavHeader.js.map +1 -1
- package/lib/esm/features/magic-pdf/AnnotatedImageSlider.js +268 -0
- package/lib/esm/features/magic-pdf/AnnotatedImageSlider.js.map +1 -0
- package/lib/esm/features/magic-pdf/DownloadPopover.js +11 -11
- package/lib/esm/features/magic-pdf/DownloadPopover.js.map +1 -1
- package/lib/esm/features/magic-pdf/ExtractedContentView.js +77 -0
- package/lib/esm/features/magic-pdf/ExtractedContentView.js.map +1 -0
- package/lib/esm/features/magic-pdf/MagicPdfProvider.js +242 -0
- package/lib/esm/features/magic-pdf/MagicPdfProvider.js.map +1 -0
- package/lib/esm/features/magic-pdf/MagicPdfView.js +41 -47
- package/lib/esm/features/magic-pdf/MagicPdfView.js.map +1 -1
- package/lib/esm/features/pdf-viewer/PdfPageRenderer.js +261 -0
- package/lib/esm/features/pdf-viewer/PdfPageRenderer.js.map +1 -0
- package/lib/esm/features/pdf-viewer/PdfPageSlider.js +276 -0
- package/lib/esm/features/pdf-viewer/PdfPageSlider.js.map +1 -0
- package/lib/esm/features/pdf-viewer/SimplePdfViewer.js +71 -0
- package/lib/esm/features/pdf-viewer/SimplePdfViewer.js.map +1 -0
- package/lib/esm/features/pdf-viewer/index.js +4 -0
- package/lib/esm/features/pdf-viewer/index.js.map +1 -0
- package/lib/esm/features/store/collections/EditCollectionView.js +3 -5
- package/lib/esm/features/store/collections/EditCollectionView.js.map +1 -1
- package/lib/esm/features/store/collections/SharedPropsEditor.js +1 -2
- package/lib/esm/features/store/collections/SharedPropsEditor.js.map +1 -1
- package/lib/esm/features/store/objects/DocumentSearchResults.js +0 -7
- package/lib/esm/features/store/objects/DocumentSearchResults.js.map +1 -1
- package/lib/esm/features/store/objects/components/ContentOverview.js +273 -83
- package/lib/esm/features/store/objects/components/ContentOverview.js.map +1 -1
- package/lib/esm/features/store/objects/components/useContentPanelHooks.js +153 -0
- package/lib/esm/features/store/objects/components/useContentPanelHooks.js.map +1 -0
- package/lib/esm/features/store/objects/layout/DocumentTableColumn.js +3 -3
- package/lib/esm/features/store/objects/layout/DocumentTableColumn.js.map +1 -1
- package/lib/esm/features/store/objects/layout/renderers.js +13 -0
- package/lib/esm/features/store/objects/layout/renderers.js.map +1 -1
- package/lib/esm/features/utils/index.js +2 -0
- package/lib/esm/features/utils/index.js.map +1 -1
- package/lib/esm/features/utils/mimeType.js +8 -0
- package/lib/esm/features/utils/mimeType.js.map +1 -1
- package/lib/esm/features/utils/print.js +181 -0
- package/lib/esm/features/utils/print.js.map +1 -0
- package/lib/esm/features/utils/workflowStatus.js +43 -0
- package/lib/esm/features/utils/workflowStatus.js.map +1 -0
- package/lib/esm/router/HistoryNavigator.js +22 -2
- package/lib/esm/router/HistoryNavigator.js.map +1 -1
- package/lib/esm/shell/login/UserInfo.js +2 -1
- package/lib/esm/shell/login/UserInfo.js.map +1 -1
- package/lib/esm/shell/login/UserSessionMenu.js +7 -1
- package/lib/esm/shell/login/UserSessionMenu.js.map +1 -1
- package/lib/esm/widgets/form/Form.js +6 -2
- package/lib/esm/widgets/form/Form.js.map +1 -1
- package/lib/esm/widgets/markdown/MarkdownRenderer.js +226 -4
- package/lib/esm/widgets/markdown/MarkdownRenderer.js.map +1 -1
- package/lib/esm/widgets/schema-editor/ManagedSchema.js +0 -3
- package/lib/esm/widgets/schema-editor/ManagedSchema.js.map +1 -1
- package/lib/esm/widgets/schema-editor/json-schema4-utils.js +1 -1
- package/lib/esm/widgets/schema-editor/json-schema4-utils.js.map +1 -1
- package/lib/esm/widgets/xml-viewer/components/XMLViewer.js +18 -9
- package/lib/esm/widgets/xml-viewer/components/XMLViewer.js.map +1 -1
- package/lib/esm/widgets/xml-viewer/constants/index.js +10 -0
- package/lib/esm/widgets/xml-viewer/constants/index.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/types/core/components/MessageBox.d.ts.map +1 -1
- package/lib/types/core/components/shadcn/dialog.d.ts +2 -1
- package/lib/types/core/components/shadcn/dialog.d.ts.map +1 -1
- package/lib/types/core/components/shadcn/filters/filterBar.d.ts.map +1 -1
- package/lib/types/core/components/shadcn/selectBox.d.ts +5 -1
- package/lib/types/core/components/shadcn/selectBox.d.ts.map +1 -1
- package/lib/types/core/components/shadcn/tabs.d.ts +3 -1
- package/lib/types/core/components/shadcn/tabs.d.ts.map +1 -1
- package/lib/types/env/index.d.ts +2 -0
- package/lib/types/env/index.d.ts.map +1 -1
- package/lib/types/features/agent/chat/AgentChart.d.ts +48 -0
- package/lib/types/features/agent/chat/AgentChart.d.ts.map +1 -0
- package/lib/types/features/agent/chat/ModernAgentConversation.d.ts.map +1 -1
- package/lib/types/features/agent/chat/ModernAgentOutput/AllMessagesMixed.d.ts +3 -2
- package/lib/types/features/agent/chat/ModernAgentOutput/AllMessagesMixed.d.ts.map +1 -1
- package/lib/types/features/agent/chat/ModernAgentOutput/Header.d.ts +2 -1
- package/lib/types/features/agent/chat/ModernAgentOutput/Header.d.ts.map +1 -1
- package/lib/types/features/agent/chat/ModernAgentOutput/InlineSlidingPlanPanel.d.ts +4 -2
- package/lib/types/features/agent/chat/ModernAgentOutput/InlineSlidingPlanPanel.d.ts.map +1 -1
- package/lib/types/features/agent/chat/ModernAgentOutput/MessageInput.d.ts +2 -4
- package/lib/types/features/agent/chat/ModernAgentOutput/MessageInput.d.ts.map +1 -1
- package/lib/types/features/agent/chat/ModernAgentOutput/MessageItem.d.ts.map +1 -1
- package/lib/types/features/agent/chat/index.d.ts +1 -0
- package/lib/types/features/agent/chat/index.d.ts.map +1 -1
- package/lib/types/features/agent/createChartTool.d.ts +178 -0
- package/lib/types/features/agent/createChartTool.d.ts.map +1 -0
- package/lib/types/features/agent/examples.d.ts +59 -0
- package/lib/types/features/agent/examples.d.ts.map +1 -0
- package/lib/types/features/agent/index.d.ts +2 -0
- package/lib/types/features/agent/index.d.ts.map +1 -1
- package/lib/types/features/agent/visualization.d.ts +95 -0
- package/lib/types/features/agent/visualization.d.ts.map +1 -0
- package/lib/types/features/facets/CollectionsFacetsNav.d.ts.map +1 -1
- package/lib/types/features/index.d.ts +1 -0
- package/lib/types/features/index.d.ts.map +1 -1
- package/lib/types/features/layout/GenericPageNavHeader.d.ts.map +1 -1
- package/lib/types/features/magic-pdf/AnnotatedImageSlider.d.ts +13 -0
- package/lib/types/features/magic-pdf/AnnotatedImageSlider.d.ts.map +1 -0
- package/lib/types/features/magic-pdf/DownloadPopover.d.ts.map +1 -1
- package/lib/types/features/magic-pdf/ExtractedContentView.d.ts +8 -0
- package/lib/types/features/magic-pdf/ExtractedContentView.d.ts.map +1 -0
- package/lib/types/features/magic-pdf/MagicPdfProvider.d.ts +58 -0
- package/lib/types/features/magic-pdf/MagicPdfProvider.d.ts.map +1 -0
- package/lib/types/features/magic-pdf/MagicPdfView.d.ts +1 -1
- package/lib/types/features/magic-pdf/MagicPdfView.d.ts.map +1 -1
- package/lib/types/features/pdf-viewer/PdfPageRenderer.d.ts +83 -0
- package/lib/types/features/pdf-viewer/PdfPageRenderer.d.ts.map +1 -0
- package/lib/types/features/pdf-viewer/PdfPageSlider.d.ts +29 -0
- package/lib/types/features/pdf-viewer/PdfPageSlider.d.ts.map +1 -0
- package/lib/types/features/pdf-viewer/SimplePdfViewer.d.ts +19 -0
- package/lib/types/features/pdf-viewer/SimplePdfViewer.d.ts.map +1 -0
- package/lib/types/features/pdf-viewer/index.d.ts +4 -0
- package/lib/types/features/pdf-viewer/index.d.ts.map +1 -0
- package/lib/types/features/store/collections/EditCollectionView.d.ts.map +1 -1
- package/lib/types/features/store/collections/SharedPropsEditor.d.ts.map +1 -1
- package/lib/types/features/store/objects/DocumentSearchResults.d.ts.map +1 -1
- package/lib/types/features/store/objects/components/ContentOverview.d.ts.map +1 -1
- package/lib/types/features/store/objects/components/useContentPanelHooks.d.ts +30 -0
- package/lib/types/features/store/objects/components/useContentPanelHooks.d.ts.map +1 -0
- package/lib/types/features/store/objects/layout/renderers.d.ts.map +1 -1
- package/lib/types/features/utils/index.d.ts +2 -0
- package/lib/types/features/utils/index.d.ts.map +1 -1
- package/lib/types/features/utils/mimeType.d.ts +1 -0
- package/lib/types/features/utils/mimeType.d.ts.map +1 -1
- package/lib/types/features/utils/print.d.ts +10 -0
- package/lib/types/features/utils/print.d.ts.map +1 -0
- package/lib/types/features/utils/workflowStatus.d.ts +10 -0
- package/lib/types/features/utils/workflowStatus.d.ts.map +1 -0
- package/lib/types/router/HistoryNavigator.d.ts +3 -0
- package/lib/types/router/HistoryNavigator.d.ts.map +1 -1
- package/lib/types/shell/login/UserInfo.d.ts.map +1 -1
- package/lib/types/shell/login/UserSessionMenu.d.ts.map +1 -1
- package/lib/types/widgets/form/Form.d.ts.map +1 -1
- package/lib/types/widgets/markdown/MarkdownRenderer.d.ts +5 -1
- package/lib/types/widgets/markdown/MarkdownRenderer.d.ts.map +1 -1
- package/lib/types/widgets/schema-editor/ManagedSchema.d.ts.map +1 -1
- package/lib/types/widgets/xml-viewer/components/XMLViewer.d.ts.map +1 -1
- package/lib/types/widgets/xml-viewer/constants/index.d.ts +10 -0
- package/lib/types/widgets/xml-viewer/constants/index.d.ts.map +1 -1
- package/lib/vertesia-ui-core.js +1 -1
- package/lib/vertesia-ui-core.js.map +1 -1
- package/lib/vertesia-ui-env.js +1 -1
- package/lib/vertesia-ui-env.js.map +1 -1
- package/lib/vertesia-ui-features.js +1 -1
- package/lib/vertesia-ui-features.js.map +1 -1
- package/lib/vertesia-ui-layout.js +1 -1
- package/lib/vertesia-ui-layout.js.map +1 -1
- package/lib/vertesia-ui-router.js +1 -1
- package/lib/vertesia-ui-router.js.map +1 -1
- package/lib/vertesia-ui-session.js +1 -1
- package/lib/vertesia-ui-session.js.map +1 -1
- package/lib/vertesia-ui-shell.js +1 -1
- package/lib/vertesia-ui-shell.js.map +1 -1
- package/lib/vertesia-ui-widgets.js +1 -1
- package/lib/vertesia-ui-widgets.js.map +1 -1
- package/package.json +11 -8
- package/src/core/components/MenuList.tsx +3 -6
- package/src/core/components/MessageBox.tsx +7 -2
- package/src/core/components/SelectBox.tsx +1 -1
- package/src/core/components/shadcn/dialog.tsx +19 -1
- package/src/core/components/shadcn/filters/filter/SelectFilter.tsx +31 -31
- package/src/core/components/shadcn/filters/filterBar.tsx +1 -0
- package/src/core/components/shadcn/selectBox.tsx +32 -6
- package/src/core/components/shadcn/tabs.tsx +3 -2
- package/src/env/index.ts +5 -0
- package/src/features/agent/CHART_INSTRUCTIONS.md +228 -0
- package/src/features/agent/chat/AgentChart.tsx +601 -0
- package/src/features/agent/chat/ModernAgentConversation.tsx +123 -11
- package/src/features/agent/chat/ModernAgentOutput/AllMessagesMixed.tsx +8 -2
- package/src/features/agent/chat/ModernAgentOutput/Header.tsx +12 -2
- package/src/features/agent/chat/ModernAgentOutput/InlineSlidingPlanPanel.tsx +6 -1
- package/src/features/agent/chat/ModernAgentOutput/MessageInput.tsx +15 -10
- package/src/features/agent/chat/ModernAgentOutput/MessageItem.tsx +122 -87
- package/src/features/agent/chat/index.ts +1 -0
- package/src/features/agent/createChartTool.ts +364 -0
- package/src/features/agent/examples.ts +321 -0
- package/src/features/agent/index.ts +2 -0
- package/src/features/agent/visualization.ts +227 -0
- package/src/features/facets/CollectionsFacetsNav.tsx +5 -1
- package/src/features/index.ts +1 -0
- package/src/features/layout/GenericPageNavHeader.tsx +15 -4
- package/src/features/magic-pdf/AnnotatedImageSlider.tsx +482 -0
- package/src/features/magic-pdf/DownloadPopover.tsx +45 -40
- package/src/features/magic-pdf/ExtractedContentView.tsx +132 -0
- package/src/features/magic-pdf/MagicPdfProvider.tsx +297 -0
- package/src/features/magic-pdf/MagicPdfView.tsx +184 -91
- package/src/features/pdf-viewer/PdfPageRenderer.tsx +612 -0
- package/src/features/pdf-viewer/PdfPageSlider.tsx +473 -0
- package/src/features/pdf-viewer/SimplePdfViewer.tsx +142 -0
- package/src/features/pdf-viewer/index.ts +3 -0
- package/src/features/store/collections/EditCollectionView.tsx +3 -5
- package/src/features/store/collections/SharedPropsEditor.tsx +1 -2
- package/src/features/store/objects/DocumentSearchResults.tsx +0 -7
- package/src/features/store/objects/components/ContentOverview.tsx +677 -210
- package/src/features/store/objects/components/useContentPanelHooks.ts +169 -0
- package/src/features/store/objects/layout/DocumentTableColumn.tsx +3 -3
- package/src/features/store/objects/layout/knowledge.md +1 -0
- package/src/features/store/objects/layout/renderers.tsx +25 -0
- package/src/features/utils/index.ts +3 -1
- package/src/features/utils/mimeType.ts +10 -1
- package/src/features/utils/print.ts +189 -0
- package/src/features/utils/workflowStatus.ts +44 -0
- package/src/router/HistoryNavigator.ts +30 -2
- package/src/shell/login/UserInfo.tsx +2 -0
- package/src/shell/login/UserSessionMenu.tsx +12 -1
- package/src/widgets/form/Form.tsx +8 -3
- package/src/widgets/markdown/MarkdownRenderer.tsx +350 -6
- package/src/widgets/schema-editor/ManagedSchema.ts +0 -3
- package/src/widgets/schema-editor/json-schema4-utils.ts +1 -1
- package/src/widgets/xml-viewer/components/XMLViewer.tsx +22 -10
- package/src/widgets/xml-viewer/constants/index.ts +11 -0
- package/lib/esm/features/magic-pdf/PageSlider.js +0 -70
- package/lib/esm/features/magic-pdf/PageSlider.js.map +0 -1
- package/lib/esm/features/magic-pdf/PdfPageProvider.js +0 -188
- package/lib/esm/features/magic-pdf/PdfPageProvider.js.map +0 -1
- package/lib/esm/features/magic-pdf/TextPageView.js +0 -62
- package/lib/esm/features/magic-pdf/TextPageView.js.map +0 -1
- package/lib/esm/features/magic-pdf/useResizeOnDrag.js +0 -34
- package/lib/esm/features/magic-pdf/useResizeOnDrag.js.map +0 -1
- package/lib/types/features/magic-pdf/PageSlider.d.ts +0 -9
- package/lib/types/features/magic-pdf/PageSlider.d.ts.map +0 -1
- package/lib/types/features/magic-pdf/PdfPageProvider.d.ts +0 -39
- package/lib/types/features/magic-pdf/PdfPageProvider.d.ts.map +0 -1
- package/lib/types/features/magic-pdf/TextPageView.d.ts +0 -8
- package/lib/types/features/magic-pdf/TextPageView.d.ts.map +0 -1
- package/lib/types/features/magic-pdf/useResizeOnDrag.d.ts +0 -9
- package/lib/types/features/magic-pdf/useResizeOnDrag.d.ts.map +0 -1
- package/src/features/magic-pdf/PageSlider.tsx +0 -142
- package/src/features/magic-pdf/PdfPageProvider.tsx +0 -310
- package/src/features/magic-pdf/TextPageView.tsx +0 -91
- 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
|
-
//
|
|
12
|
-
const
|
|
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("
|
|
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=
|
|
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=
|
|
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=
|
|
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
|
-
|
|
179
|
-
const
|
|
180
|
-
|
|
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 (
|
|
184
|
-
|
|
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
|
-
}, [
|
|
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-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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.
|
|
382
|
+
variant={currentPanel === PanelView.Text ? "primary" : "ghost"}
|
|
224
383
|
size="sm"
|
|
225
|
-
alt="View
|
|
226
|
-
onClick={() => setCurrentPanel(PanelView.
|
|
384
|
+
alt="View Text"
|
|
385
|
+
onClick={() => setCurrentPanel(PanelView.Text)}
|
|
227
386
|
>
|
|
228
|
-
|
|
387
|
+
Text
|
|
229
388
|
</Button>
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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 &&
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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({
|
|
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
|
-
|
|
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
|
-
|
|
360
|
-
<div className="flex items-center
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
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
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
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
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
693
|
+
PDF
|
|
694
|
+
</Button>
|
|
695
|
+
</>
|
|
696
|
+
)}
|
|
697
|
+
</div>
|
|
398
698
|
</div>
|
|
399
|
-
|
|
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's print dialog with the current document content.
|
|
704
|
+
</p>
|
|
705
|
+
<p className="text-sm text-muted">
|
|
706
|
+
To save a PDF, choose "Save as PDF" 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(({
|
|
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
|
-
//
|
|
413
|
-
const
|
|
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("
|
|
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
|
|
439
|
-
{
|
|
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
|
|
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
|
|
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=
|
|
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">•</span>
|
|
1109
|
+
<span className={statusColor}>{statusName}</span>
|
|
1110
|
+
{progress.started_at && (
|
|
1111
|
+
<>
|
|
1112
|
+
<span className="px-2">•</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
|
}
|