@vertesia/ui 1.5.0-dev.20260722.120446Z → 1.5.0-dev.20260725.083715Z
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/core/components/shadcn/filters/filterBar.d.ts.map +1 -1
- package/lib/core/components/shadcn/filters/filterBar.js +1 -1
- package/lib/core/components/shadcn/filters/filterBar.js.map +1 -1
- package/lib/core/components/shadcn/modal/dialog.d.ts +1 -1
- package/lib/core/components/shadcn/modal/dialog.d.ts.map +1 -1
- package/lib/core/components/shadcn/modal/dialog.js +2 -0
- package/lib/core/components/shadcn/modal/dialog.js.map +1 -1
- package/lib/core/components/shadcn/tabs.d.ts +12 -1
- package/lib/core/components/shadcn/tabs.d.ts.map +1 -1
- package/lib/core/components/shadcn/tabs.js +12 -4
- package/lib/core/components/shadcn/tabs.js.map +1 -1
- package/lib/features/agent/chat/AgentRightPanel.d.ts +5 -1
- package/lib/features/agent/chat/AgentRightPanel.d.ts.map +1 -1
- package/lib/features/agent/chat/AgentRightPanel.js +2 -2
- package/lib/features/agent/chat/AgentRightPanel.js.map +1 -1
- package/lib/features/agent/chat/ArtifactsTab.d.ts +5 -1
- package/lib/features/agent/chat/ArtifactsTab.d.ts.map +1 -1
- package/lib/features/agent/chat/ArtifactsTab.js +48 -11
- package/lib/features/agent/chat/ArtifactsTab.js.map +1 -1
- package/lib/features/agent/chat/DocumentEditingActionCard.d.ts +6 -0
- package/lib/features/agent/chat/DocumentEditingActionCard.d.ts.map +1 -0
- package/lib/features/agent/chat/DocumentEditingActionCard.js +129 -0
- package/lib/features/agent/chat/DocumentEditingActionCard.js.map +1 -0
- package/lib/features/agent/chat/ModernAgentConversation.d.ts +19 -1
- package/lib/features/agent/chat/ModernAgentConversation.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentConversation.js +125 -27
- package/lib/features/agent/chat/ModernAgentConversation.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/AgentMarkdownAnchor.d.ts +45 -0
- package/lib/features/agent/chat/ModernAgentOutput/AgentMarkdownAnchor.d.ts.map +1 -0
- package/lib/features/agent/chat/ModernAgentOutput/AgentMarkdownAnchor.js +40 -0
- package/lib/features/agent/chat/ModernAgentOutput/AgentMarkdownAnchor.js.map +1 -0
- package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.d.ts +3 -1
- package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js +94 -49
- package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/AttachmentPreview.d.ts +2 -1
- package/lib/features/agent/chat/ModernAgentOutput/AttachmentPreview.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/AttachmentPreview.js +9 -5
- package/lib/features/agent/chat/ModernAgentOutput/AttachmentPreview.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/CollapsibleAgentMarkdown.d.ts +25 -0
- package/lib/features/agent/chat/ModernAgentOutput/CollapsibleAgentMarkdown.d.ts.map +1 -0
- package/lib/features/agent/chat/ModernAgentOutput/CollapsibleAgentMarkdown.js +33 -0
- package/lib/features/agent/chat/ModernAgentOutput/CollapsibleAgentMarkdown.js.map +1 -0
- package/lib/features/agent/chat/ModernAgentOutput/MessageItem.d.ts +3 -1
- package/lib/features/agent/chat/ModernAgentOutput/MessageItem.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/MessageItem.js +22 -24
- package/lib/features/agent/chat/ModernAgentOutput/MessageItem.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/ResourceChangeSummary.d.ts +14 -0
- package/lib/features/agent/chat/ModernAgentOutput/ResourceChangeSummary.d.ts.map +1 -0
- package/lib/features/agent/chat/ModernAgentOutput/ResourceChangeSummary.js +21 -0
- package/lib/features/agent/chat/ModernAgentOutput/ResourceChangeSummary.js.map +1 -0
- package/lib/features/agent/chat/ModernAgentOutput/ResourceChip.d.ts +30 -0
- package/lib/features/agent/chat/ModernAgentOutput/ResourceChip.d.ts.map +1 -0
- package/lib/features/agent/chat/ModernAgentOutput/ResourceChip.js +53 -0
- package/lib/features/agent/chat/ModernAgentOutput/ResourceChip.js.map +1 -0
- package/lib/features/agent/chat/ModernAgentOutput/ToolCallGroup.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/ToolCallGroup.js +21 -2
- package/lib/features/agent/chat/ModernAgentOutput/ToolCallGroup.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/resourceLinks.d.ts +6 -0
- package/lib/features/agent/chat/ModernAgentOutput/resourceLinks.d.ts.map +1 -0
- package/lib/features/agent/chat/ModernAgentOutput/resourceLinks.js +29 -0
- package/lib/features/agent/chat/ModernAgentOutput/resourceLinks.js.map +1 -0
- package/lib/features/agent/chat/ModernAgentOutput/resourceSummary.d.ts +33 -0
- package/lib/features/agent/chat/ModernAgentOutput/resourceSummary.d.ts.map +1 -0
- package/lib/features/agent/chat/ModernAgentOutput/resourceSummary.js +129 -0
- package/lib/features/agent/chat/ModernAgentOutput/resourceSummary.js.map +1 -0
- package/lib/features/agent/chat/ModernAgentOutput/utils.d.ts +7 -0
- package/lib/features/agent/chat/ModernAgentOutput/utils.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/utils.js +11 -1
- package/lib/features/agent/chat/ModernAgentOutput/utils.js.map +1 -1
- package/lib/features/agent/chat/hooks/useAgentStream.d.ts +1 -1
- package/lib/features/agent/chat/hooks/useAgentStream.d.ts.map +1 -1
- package/lib/features/agent/chat/hooks/useAgentStream.js +24 -9
- package/lib/features/agent/chat/hooks/useAgentStream.js.map +1 -1
- package/lib/features/agent/chat/index.d.ts +2 -1
- package/lib/features/agent/chat/index.d.ts.map +1 -1
- package/lib/features/agent/chat/index.js +3 -0
- package/lib/features/agent/chat/index.js.map +1 -1
- package/lib/features/store/objects/DocumentSearchResults.js +2 -2
- package/lib/features/store/objects/DocumentSearchResults.js.map +1 -1
- package/lib/features/store/objects/components/ContentOverview.d.ts +2 -1
- package/lib/features/store/objects/components/ContentOverview.d.ts.map +1 -1
- package/lib/features/store/objects/components/ContentOverview.js +86 -15
- package/lib/features/store/objects/components/ContentOverview.js.map +1 -1
- package/lib/features/store/objects/components/DocumentEditingConfigurationSelector.d.ts +16 -0
- package/lib/features/store/objects/components/DocumentEditingConfigurationSelector.d.ts.map +1 -0
- package/lib/features/store/objects/components/DocumentEditingConfigurationSelector.js +116 -0
- package/lib/features/store/objects/components/DocumentEditingConfigurationSelector.js.map +1 -0
- package/lib/features/store/objects/components/DocumentEditingLockStatus.d.ts +7 -0
- package/lib/features/store/objects/components/DocumentEditingLockStatus.d.ts.map +1 -0
- package/lib/features/store/objects/components/DocumentEditingLockStatus.js +13 -0
- package/lib/features/store/objects/components/DocumentEditingLockStatus.js.map +1 -0
- package/lib/features/store/objects/components/DocumentEditingPanel.d.ts +48 -0
- package/lib/features/store/objects/components/DocumentEditingPanel.d.ts.map +1 -0
- package/lib/features/store/objects/components/DocumentEditingPanel.js +720 -0
- package/lib/features/store/objects/components/DocumentEditingPanel.js.map +1 -0
- package/lib/features/store/objects/components/TextEditorPanel.d.ts.map +1 -1
- package/lib/features/store/objects/components/TextEditorPanel.js +22 -9
- package/lib/features/store/objects/components/TextEditorPanel.js.map +1 -1
- package/lib/features/store/objects/components/documentArtifactRefs.d.ts +26 -0
- package/lib/features/store/objects/components/documentArtifactRefs.d.ts.map +1 -0
- package/lib/features/store/objects/components/documentArtifactRefs.js +52 -0
- package/lib/features/store/objects/components/documentArtifactRefs.js.map +1 -0
- package/lib/features/store/objects/components/documentEditingAgentConfig.d.ts +4 -0
- package/lib/features/store/objects/components/documentEditingAgentConfig.d.ts.map +1 -0
- package/lib/features/store/objects/components/documentEditingAgentConfig.js +40 -0
- package/lib/features/store/objects/components/documentEditingAgentConfig.js.map +1 -0
- package/lib/features/store/objects/components/documentEditingPrompts.d.ts +3 -0
- package/lib/features/store/objects/components/documentEditingPrompts.d.ts.map +1 -0
- package/lib/features/store/objects/components/documentEditingPrompts.js +13 -0
- package/lib/features/store/objects/components/documentEditingPrompts.js.map +1 -0
- package/lib/features/store/objects/components/documentEditingRun.d.ts +27 -0
- package/lib/features/store/objects/components/documentEditingRun.d.ts.map +1 -0
- package/lib/features/store/objects/components/documentEditingRun.js +126 -0
- package/lib/features/store/objects/components/documentEditingRun.js.map +1 -0
- package/lib/features/store/objects/components/documentEditingTarget.d.ts +20 -0
- package/lib/features/store/objects/components/documentEditingTarget.d.ts.map +1 -0
- package/lib/features/store/objects/components/documentEditingTarget.js +40 -0
- package/lib/features/store/objects/components/documentEditingTarget.js.map +1 -0
- package/lib/features/store/objects/components/index.d.ts +1 -0
- package/lib/features/store/objects/components/index.d.ts.map +1 -1
- package/lib/features/store/objects/components/index.js +1 -0
- package/lib/features/store/objects/components/index.js.map +1 -1
- package/lib/features/store/objects/components/useContentPanelHooks.d.ts +5 -1
- package/lib/features/store/objects/components/useContentPanelHooks.d.ts.map +1 -1
- package/lib/features/store/objects/components/useContentPanelHooks.js +53 -29
- package/lib/features/store/objects/components/useContentPanelHooks.js.map +1 -1
- package/lib/features/store/objects/components/useDownloadFile.d.ts +2 -1
- package/lib/features/store/objects/components/useDownloadFile.d.ts.map +1 -1
- package/lib/features/store/objects/components/useDownloadFile.js +3 -0
- package/lib/features/store/objects/components/useDownloadFile.js.map +1 -1
- package/lib/features/store/objects/selection/actions/StartWorkflowButton.js +1 -1
- package/lib/features/store/objects/selection/actions/StartWorkflowButton.js.map +1 -1
- package/lib/i18n/locales/ar.json +118 -1
- package/lib/i18n/locales/de.json +118 -1
- package/lib/i18n/locales/en.json +118 -0
- package/lib/i18n/locales/es.json +118 -1
- package/lib/i18n/locales/fr.json +118 -1
- package/lib/i18n/locales/it.json +118 -1
- package/lib/i18n/locales/ja.json +118 -1
- package/lib/i18n/locales/ko.json +118 -1
- package/lib/i18n/locales/pt.json +118 -1
- package/lib/i18n/locales/ru.json +118 -1
- package/lib/i18n/locales/tr.json +118 -1
- package/lib/i18n/locales/zh-TW.json +118 -1
- package/lib/i18n/locales/zh.json +118 -1
- package/lib/rich-text/EditorToolbar.d.ts +37 -0
- package/lib/rich-text/EditorToolbar.d.ts.map +1 -0
- package/lib/rich-text/EditorToolbar.js +215 -0
- package/lib/rich-text/EditorToolbar.js.map +1 -0
- package/lib/rich-text/VertesiaRichTextEditor.d.ts +26 -0
- package/lib/rich-text/VertesiaRichTextEditor.d.ts.map +1 -0
- package/lib/rich-text/VertesiaRichTextEditor.js +67 -0
- package/lib/rich-text/VertesiaRichTextEditor.js.map +1 -0
- package/lib/rich-text/index.d.ts +2 -0
- package/lib/rich-text/index.d.ts.map +1 -0
- package/lib/rich-text/index.js +2 -0
- package/lib/rich-text/index.js.map +1 -0
- package/lib/vertesia-ui-core.js +2 -2
- package/lib/vertesia-ui-core.js.map +1 -1
- package/lib/vertesia-ui-features.js +35 -30
- package/lib/vertesia-ui-features.js.map +1 -1
- package/lib/vertesia-ui-i18n.js +14 -14
- package/lib/vertesia-ui-i18n.js.map +1 -1
- package/lib/vertesia-ui-rich-text.js +5 -0
- package/lib/vertesia-ui-rich-text.js.map +1 -0
- package/lib/vertesia-ui-widgets.js +25 -7
- package/lib/vertesia-ui-widgets.js.map +1 -1
- package/lib/widgets/markdown/AgentResourceResolver.d.ts +31 -0
- package/lib/widgets/markdown/AgentResourceResolver.d.ts.map +1 -0
- package/lib/widgets/markdown/AgentResourceResolver.js +39 -0
- package/lib/widgets/markdown/AgentResourceResolver.js.map +1 -0
- package/lib/widgets/markdown/ArtifactEditingSurface.d.ts +63 -0
- package/lib/widgets/markdown/ArtifactEditingSurface.d.ts.map +1 -0
- package/lib/widgets/markdown/ArtifactEditingSurface.js +582 -0
- package/lib/widgets/markdown/ArtifactEditingSurface.js.map +1 -0
- package/lib/widgets/markdown/CollaborativeMarkdownRenderer.d.ts +69 -0
- package/lib/widgets/markdown/CollaborativeMarkdownRenderer.d.ts.map +1 -0
- package/lib/widgets/markdown/CollaborativeMarkdownRenderer.js +374 -0
- package/lib/widgets/markdown/CollaborativeMarkdownRenderer.js.map +1 -0
- package/lib/widgets/markdown/MarkdownLink.d.ts +3 -1
- package/lib/widgets/markdown/MarkdownLink.d.ts.map +1 -1
- package/lib/widgets/markdown/MarkdownLink.js +41 -14
- package/lib/widgets/markdown/MarkdownLink.js.map +1 -1
- package/lib/widgets/markdown/MarkdownRenderer.d.ts +8 -1
- package/lib/widgets/markdown/MarkdownRenderer.d.ts.map +1 -1
- package/lib/widgets/markdown/MarkdownRenderer.js +20 -4
- package/lib/widgets/markdown/MarkdownRenderer.js.map +1 -1
- package/lib/widgets/markdown/index.d.ts +5 -1
- package/lib/widgets/markdown/index.d.ts.map +1 -1
- package/lib/widgets/markdown/index.js +5 -1
- package/lib/widgets/markdown/index.js.map +1 -1
- package/lib/widgets/markdown/normalizeCustomSchemeLinks.d.ts.map +1 -1
- package/lib/widgets/markdown/normalizeCustomSchemeLinks.js +14 -2
- package/lib/widgets/markdown/normalizeCustomSchemeLinks.js.map +1 -1
- package/lib/widgets/markdown/textDiff.d.ts +52 -0
- package/lib/widgets/markdown/textDiff.d.ts.map +1 -0
- package/lib/widgets/markdown/textDiff.js +362 -0
- package/lib/widgets/markdown/textDiff.js.map +1 -0
- package/lib/widgets/markdown/useArtifactContent.d.ts +5 -3
- package/lib/widgets/markdown/useArtifactContent.d.ts.map +1 -1
- package/lib/widgets/markdown/useArtifactContent.js +6 -3
- package/lib/widgets/markdown/useArtifactContent.js.map +1 -1
- package/lib/widgets/markdown/useResolvedUrl.d.ts +1 -5
- package/lib/widgets/markdown/useResolvedUrl.d.ts.map +1 -1
- package/lib/widgets/markdown/useResolvedUrl.js +19 -31
- package/lib/widgets/markdown/useResolvedUrl.js.map +1 -1
- package/lib/widgets/xml-viewer/hooks/useXMLViewer.d.ts +9 -0
- package/lib/widgets/xml-viewer/hooks/useXMLViewer.d.ts.map +1 -1
- package/lib/widgets/xml-viewer/hooks/useXMLViewer.js +20 -17
- package/lib/widgets/xml-viewer/hooks/useXMLViewer.js.map +1 -1
- package/package.json +13 -9
- package/src/__tests__/a11y.test.tsx +4 -1
- package/src/__tests__/axe-helper.ts +17 -7
- package/src/__tests__/setup.ts +25 -0
- package/src/core/components/shadcn/filters/filterBar.tsx +1 -0
- package/src/core/components/shadcn/modal/dialog.tsx +3 -1
- package/src/core/components/shadcn/tabs.test.tsx +65 -0
- package/src/core/components/shadcn/tabs.tsx +25 -3
- package/src/features/agent/chat/AgentRightPanel.tsx +16 -1
- package/src/features/agent/chat/ArtifactsTab.test.tsx +55 -0
- package/src/features/agent/chat/ArtifactsTab.tsx +203 -52
- package/src/features/agent/chat/DocumentEditingActionCard.test.tsx +105 -0
- package/src/features/agent/chat/DocumentEditingActionCard.tsx +210 -0
- package/src/features/agent/chat/ModernAgentConversation.test.tsx +237 -40
- package/src/features/agent/chat/ModernAgentConversation.tsx +247 -75
- package/src/features/agent/chat/ModernAgentOutput/AgentMarkdownAnchor.test.tsx +79 -0
- package/src/features/agent/chat/ModernAgentOutput/AgentMarkdownAnchor.tsx +94 -0
- package/src/features/agent/chat/ModernAgentOutput/AllMessagesMixed.test.tsx +257 -34
- package/src/features/agent/chat/ModernAgentOutput/AllMessagesMixed.tsx +217 -105
- package/src/features/agent/chat/ModernAgentOutput/AttachmentPreview.tsx +17 -0
- package/src/features/agent/chat/ModernAgentOutput/CollapsibleAgentMarkdown.test.tsx +44 -0
- package/src/features/agent/chat/ModernAgentOutput/CollapsibleAgentMarkdown.tsx +88 -0
- package/src/features/agent/chat/ModernAgentOutput/MessageItem.tsx +30 -46
- package/src/features/agent/chat/ModernAgentOutput/ResourceChangeSummary.tsx +40 -0
- package/src/features/agent/chat/ModernAgentOutput/ResourceChip.test.tsx +148 -0
- package/src/features/agent/chat/ModernAgentOutput/ResourceChip.tsx +141 -0
- package/src/features/agent/chat/ModernAgentOutput/ToolCallGroup.tsx +33 -1
- package/src/features/agent/chat/ModernAgentOutput/resourceLinks.tsx +43 -0
- package/src/features/agent/chat/ModernAgentOutput/resourceSummary.test.ts +229 -0
- package/src/features/agent/chat/ModernAgentOutput/resourceSummary.ts +152 -0
- package/src/features/agent/chat/ModernAgentOutput/utils.test.ts +17 -0
- package/src/features/agent/chat/ModernAgentOutput/utils.ts +14 -1
- package/src/features/agent/chat/hooks/useAgentStream.test.tsx +188 -0
- package/src/features/agent/chat/hooks/useAgentStream.ts +28 -8
- package/src/features/agent/chat/index.ts +11 -0
- package/src/features/store/objects/DocumentSearchResults.tsx +2 -2
- package/src/features/store/objects/components/ContentOverview.tsx +216 -65
- package/src/features/store/objects/components/DocumentEditingConfigurationSelector.test.ts +34 -0
- package/src/features/store/objects/components/DocumentEditingConfigurationSelector.tsx +211 -0
- package/src/features/store/objects/components/DocumentEditingLockStatus.test.tsx +32 -0
- package/src/features/store/objects/components/DocumentEditingLockStatus.tsx +45 -0
- package/src/features/store/objects/components/DocumentEditingPanel.test.tsx +336 -0
- package/src/features/store/objects/components/DocumentEditingPanel.tsx +1236 -0
- package/src/features/store/objects/components/TextEditorPanel.test.tsx +48 -0
- package/src/features/store/objects/components/TextEditorPanel.tsx +41 -20
- package/src/features/store/objects/components/documentArtifactRefs.test.ts +65 -0
- package/src/features/store/objects/components/documentArtifactRefs.ts +72 -0
- package/src/features/store/objects/components/documentEditingAgentConfig.test.ts +28 -0
- package/src/features/store/objects/components/documentEditingAgentConfig.ts +41 -0
- package/src/features/store/objects/components/documentEditingPrompts.test.ts +26 -0
- package/src/features/store/objects/components/documentEditingPrompts.ts +12 -0
- package/src/features/store/objects/components/documentEditingRun.test.ts +176 -0
- package/src/features/store/objects/components/documentEditingRun.ts +184 -0
- package/src/features/store/objects/components/documentEditingTarget.test.ts +96 -0
- package/src/features/store/objects/components/documentEditingTarget.ts +63 -0
- package/src/features/store/objects/components/index.ts +1 -0
- package/src/features/store/objects/components/useContentPanelHooks.ts +52 -27
- package/src/features/store/objects/components/useDownloadFile.test.ts +20 -0
- package/src/features/store/objects/components/useDownloadFile.ts +5 -1
- package/src/features/store/objects/selection/actions/StartWorkflowButton.tsx +2 -2
- package/src/i18n/locales/ar.json +118 -1
- package/src/i18n/locales/de.json +118 -1
- package/src/i18n/locales/en.json +118 -0
- package/src/i18n/locales/es.json +118 -1
- package/src/i18n/locales/fr.json +118 -1
- package/src/i18n/locales/it.json +118 -1
- package/src/i18n/locales/ja.json +118 -1
- package/src/i18n/locales/ko.json +118 -1
- package/src/i18n/locales/pt.json +118 -1
- package/src/i18n/locales/ru.json +118 -1
- package/src/i18n/locales/tr.json +118 -1
- package/src/i18n/locales/zh-TW.json +118 -1
- package/src/i18n/locales/zh.json +118 -1
- package/src/rich-text/EditorToolbar.tsx +708 -0
- package/src/rich-text/VertesiaRichTextEditor.test.tsx +211 -0
- package/src/rich-text/VertesiaRichTextEditor.tsx +241 -0
- package/src/rich-text/index.ts +9 -0
- package/src/session/UserSession.test.ts +11 -11
- package/src/widgets/markdown/AgentResourceResolver.test.ts +24 -0
- package/src/widgets/markdown/AgentResourceResolver.tsx +70 -0
- package/src/widgets/markdown/ArtifactEditingSurface.test.tsx +537 -0
- package/src/widgets/markdown/ArtifactEditingSurface.tsx +878 -0
- package/src/widgets/markdown/CollaborativeMarkdownRenderer.test.tsx +484 -0
- package/src/widgets/markdown/CollaborativeMarkdownRenderer.tsx +825 -0
- package/src/widgets/markdown/MarkdownLink.test.tsx +122 -0
- package/src/widgets/markdown/MarkdownLink.tsx +102 -17
- package/src/widgets/markdown/MarkdownRenderer.tsx +30 -3
- package/src/widgets/markdown/index.ts +39 -1
- package/src/widgets/markdown/normalizeCustomSchemeLinks.ts +15 -2
- package/src/widgets/markdown/textDiff.test.ts +156 -0
- package/src/widgets/markdown/textDiff.ts +399 -0
- package/src/widgets/markdown/useArtifactContent.ts +9 -3
- package/src/widgets/markdown/useResolvedUrl.test.ts +29 -40
- package/src/widgets/markdown/useResolvedUrl.ts +33 -35
- package/src/widgets/xml-viewer/hooks/useXMLViewer.test.ts +26 -0
- package/src/widgets/xml-viewer/hooks/useXMLViewer.ts +23 -18
|
@@ -0,0 +1,878 @@
|
|
|
1
|
+
import { Button, Center, cn, errorMessage, MessageBox, Spinner, useToast, VTooltip } from '@vertesia/ui/core';
|
|
2
|
+
import { useUITranslation } from '@vertesia/ui/i18n';
|
|
3
|
+
import { useUserSession } from '@vertesia/ui/session';
|
|
4
|
+
import { Check, GitCommitHorizontal } from 'lucide-react';
|
|
5
|
+
import { lazy, Suspense, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
6
|
+
import {
|
|
7
|
+
applyMarkdownEditingChange,
|
|
8
|
+
CollaborativeMarkdownRenderer,
|
|
9
|
+
type MarkdownEditingAction,
|
|
10
|
+
} from './CollaborativeMarkdownRenderer.js';
|
|
11
|
+
import { getTextLineChangeRegions, rebaseTextChanges } from './textDiff.js';
|
|
12
|
+
|
|
13
|
+
const HYDRATION_RETRY_DELAY_MS = 500;
|
|
14
|
+
const HYDRATION_RETRY_LIMIT = 60;
|
|
15
|
+
const DOCUMENT_SAVE_DEBOUNCE_MS = 400;
|
|
16
|
+
|
|
17
|
+
const MarkdownDocumentEditor = lazy(() =>
|
|
18
|
+
import('@vertesia/ui/rich-text').then((module) => ({ default: module.VertesiaMarkdownDocumentEditor })),
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
export function isArtifactRefreshEvent(details: Record<string, unknown> | undefined, path: string): boolean {
|
|
22
|
+
if (details?.path !== path) return false;
|
|
23
|
+
if (details.event_class === 'artifact_updated') return true;
|
|
24
|
+
|
|
25
|
+
// Compatibility for runs handled by workers that predate the dedicated
|
|
26
|
+
// artifact_updated event. Their path-bearing completion is still reliable.
|
|
27
|
+
return (
|
|
28
|
+
details.event_class === 'activity' &&
|
|
29
|
+
details.tool_status === 'completed' &&
|
|
30
|
+
(details.tool === 'edit_artifact' || details.tool === 'write_artifact')
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function applyArtifactRefreshChanges(
|
|
35
|
+
content: string,
|
|
36
|
+
details: Record<string, unknown> | undefined,
|
|
37
|
+
): string | undefined {
|
|
38
|
+
if (!Array.isArray(details?.changes) || details.changes.length === 0) return undefined;
|
|
39
|
+
|
|
40
|
+
let updated = content;
|
|
41
|
+
for (const value of details.changes) {
|
|
42
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined;
|
|
43
|
+
const change = value as Record<string, unknown>;
|
|
44
|
+
if (change.operation !== 'replace' || typeof change.before !== 'string' || typeof change.after !== 'string') {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!updated.includes(change.before)) return undefined;
|
|
49
|
+
if (change.replace_all === true) {
|
|
50
|
+
updated = updated.split(change.before).join(change.after);
|
|
51
|
+
} else {
|
|
52
|
+
if (updated.indexOf(change.before) !== updated.lastIndexOf(change.before)) return undefined;
|
|
53
|
+
updated = updated.replace(change.before, change.after);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return updated;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function getErrorStatus(error: unknown): number | undefined {
|
|
61
|
+
if (!error || typeof error !== 'object' || !('status' in error)) return undefined;
|
|
62
|
+
return typeof error.status === 'number' ? error.status : undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function MarkdownChangeRuler({
|
|
66
|
+
regions,
|
|
67
|
+
totalLines,
|
|
68
|
+
onNavigate,
|
|
69
|
+
}: {
|
|
70
|
+
regions: MarkdownChangeRegion[];
|
|
71
|
+
totalLines: number;
|
|
72
|
+
onNavigate: (line: number) => void;
|
|
73
|
+
}) {
|
|
74
|
+
const { t } = useUITranslation();
|
|
75
|
+
if (regions.length === 0) return null;
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<nav
|
|
79
|
+
className="absolute inset-y-12 end-1 z-10 w-3 rounded-full border border-mixer-muted/25 bg-muted/20"
|
|
80
|
+
aria-label={t('agent.changeRuler')}
|
|
81
|
+
>
|
|
82
|
+
{regions.map((region) => {
|
|
83
|
+
const top = (region.startLine / Math.max(1, totalLines)) * 100;
|
|
84
|
+
const height = Math.max(1.5, ((region.endLine - region.startLine + 1) / Math.max(1, totalLines)) * 100);
|
|
85
|
+
return (
|
|
86
|
+
<VTooltip
|
|
87
|
+
key={`${region.startLine}:${region.endLine}`}
|
|
88
|
+
description={t('agent.changedRegion', { line: region.startLine + 1 })}
|
|
89
|
+
asChild
|
|
90
|
+
>
|
|
91
|
+
<button
|
|
92
|
+
type="button"
|
|
93
|
+
className="absolute inset-x-0 rounded-full bg-attention transition-colors hover:bg-info focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-info"
|
|
94
|
+
style={{ top: `${Math.min(98.5, top)}%`, height: `${Math.min(100 - top, height)}%` }}
|
|
95
|
+
onClick={() => onNavigate(region.startLine)}
|
|
96
|
+
aria-label={t('agent.changedRegion', { line: region.startLine + 1 })}
|
|
97
|
+
/>
|
|
98
|
+
</VTooltip>
|
|
99
|
+
);
|
|
100
|
+
})}
|
|
101
|
+
</nav>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface ArtifactEditingSurfaceDocumentEdit {
|
|
106
|
+
/** Working-copy content the agent last knew (hydration, its own edits, or the last hand-off). */
|
|
107
|
+
previous: string;
|
|
108
|
+
/** Persisted working-copy content after the user's direct edits. */
|
|
109
|
+
current: string;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
interface ArtifactDocumentConflict {
|
|
113
|
+
baseContent: string;
|
|
114
|
+
localContent: string;
|
|
115
|
+
remoteContent?: string;
|
|
116
|
+
remoteGeneration?: string;
|
|
117
|
+
pendingAction?: MarkdownEditingAction;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
interface MarkdownDocumentEditorHandle {
|
|
121
|
+
getMarkdown(): string;
|
|
122
|
+
commands: {
|
|
123
|
+
setContent(content: string, options: { contentType: 'markdown'; emitUpdate: boolean }): boolean;
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Working-copy save state of the document editor. `idle` means nothing to show. */
|
|
128
|
+
export type ArtifactSaveStatus = 'idle' | 'saving' | 'saved';
|
|
129
|
+
|
|
130
|
+
export interface ArtifactEditingSurfaceProps {
|
|
131
|
+
runId?: string;
|
|
132
|
+
path: string;
|
|
133
|
+
initialContent?: string;
|
|
134
|
+
refreshKey?: number;
|
|
135
|
+
refreshDetails?: Record<string, unknown>;
|
|
136
|
+
readOnly?: boolean;
|
|
137
|
+
/** Keep comment controls enabled when content mutations are read-only. */
|
|
138
|
+
allowComments?: boolean;
|
|
139
|
+
viewMode?: 'components' | 'document';
|
|
140
|
+
/** Canonical Markdown used by the full editor's change ruler. */
|
|
141
|
+
baselineContent?: string;
|
|
142
|
+
className?: string;
|
|
143
|
+
onAction?: (action: MarkdownEditingAction) => void | Promise<void>;
|
|
144
|
+
/** Send a composed batch of span comments to the agent as one conversation message. */
|
|
145
|
+
onSendMessage?: (message: string) => void | Promise<void>;
|
|
146
|
+
/** Hand persisted full-document edits to the active agent. */
|
|
147
|
+
onSendChangesToAgent?: () => void | Promise<void>;
|
|
148
|
+
hasUnsentChanges?: boolean;
|
|
149
|
+
isSendingChanges?: boolean;
|
|
150
|
+
sendChangesDisabled?: boolean;
|
|
151
|
+
/** Compact status rendered beside the full editor's document hand-off action. */
|
|
152
|
+
toolbarStatus?: React.ReactNode;
|
|
153
|
+
onContentChange?: (content: string, generation?: string) => void;
|
|
154
|
+
onDocumentEdit?: () => void;
|
|
155
|
+
/**
|
|
156
|
+
* Reports document-editor save state so a parent can render its own indicator
|
|
157
|
+
* (e.g. next to the file name). When provided, the built-in floating badge is
|
|
158
|
+
* suppressed and the parent owns the display.
|
|
159
|
+
*/
|
|
160
|
+
onSaveStatusChange?: (status: ArtifactSaveStatus) => void;
|
|
161
|
+
/**
|
|
162
|
+
* Imperative flush used before handing direct full-document edits to the agent.
|
|
163
|
+
* Resolves false when the flush failed; otherwise returns the delta since the
|
|
164
|
+
* agent's last known state and advances that baseline to the flushed content.
|
|
165
|
+
*/
|
|
166
|
+
flushChangesRef?: React.MutableRefObject<(() => Promise<false | ArtifactEditingSurfaceDocumentEdit>) | null>;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export interface MarkdownChangeRegion {
|
|
170
|
+
startLine: number;
|
|
171
|
+
endLine: number;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Maps structured diff hunks onto current-document line numbers. */
|
|
175
|
+
export function getMarkdownChangeRegions(before: string, after: string): MarkdownChangeRegion[] {
|
|
176
|
+
return getTextLineChangeRegions(before, after);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Shared artifact-backed Markdown editor used by both agent workspaces and
|
|
181
|
+
* document editing sessions. The artifact is updated conditionally using its
|
|
182
|
+
* backend-neutral blob generation; the previously rendered content stays in
|
|
183
|
+
* place while a newer generation is loading.
|
|
184
|
+
*/
|
|
185
|
+
export function ArtifactEditingSurface({
|
|
186
|
+
runId,
|
|
187
|
+
path,
|
|
188
|
+
initialContent = '',
|
|
189
|
+
refreshKey = 0,
|
|
190
|
+
refreshDetails,
|
|
191
|
+
readOnly = false,
|
|
192
|
+
allowComments = false,
|
|
193
|
+
viewMode = 'components',
|
|
194
|
+
baselineContent,
|
|
195
|
+
className,
|
|
196
|
+
onAction,
|
|
197
|
+
onSendMessage,
|
|
198
|
+
onSendChangesToAgent,
|
|
199
|
+
hasUnsentChanges = false,
|
|
200
|
+
isSendingChanges = false,
|
|
201
|
+
sendChangesDisabled = false,
|
|
202
|
+
toolbarStatus,
|
|
203
|
+
onContentChange,
|
|
204
|
+
onDocumentEdit,
|
|
205
|
+
onSaveStatusChange,
|
|
206
|
+
flushChangesRef,
|
|
207
|
+
}: ArtifactEditingSurfaceProps) {
|
|
208
|
+
const { client } = useUserSession();
|
|
209
|
+
const { t } = useUITranslation();
|
|
210
|
+
const toast = useToast();
|
|
211
|
+
const [content, setContent] = useState(initialContent);
|
|
212
|
+
const [generation, setGeneration] = useState<string | undefined>();
|
|
213
|
+
const [highlightChangesFrom, setHighlightChangesFrom] = useState<string | undefined>();
|
|
214
|
+
const [highlightVersion, setHighlightVersion] = useState(0);
|
|
215
|
+
const [isLoading, setIsLoading] = useState(Boolean(runId));
|
|
216
|
+
const [isSavingDocument, setIsSavingDocument] = useState(false);
|
|
217
|
+
const [isDocumentSavePending, setIsDocumentSavePending] = useState(false);
|
|
218
|
+
const [loadError, setLoadError] = useState<string | undefined>();
|
|
219
|
+
const [documentConflict, setDocumentConflict] = useState<ArtifactDocumentConflict>();
|
|
220
|
+
const [isResolvingConflict, setIsResolvingConflict] = useState(false);
|
|
221
|
+
const commentsEnabled = !readOnly || allowComments;
|
|
222
|
+
|
|
223
|
+
// Working-copy save state for the document editor, shared with the built-in badge and
|
|
224
|
+
// optionally reported to a parent that renders its own indicator.
|
|
225
|
+
const saveStatus: ArtifactSaveStatus =
|
|
226
|
+
viewMode !== 'document' || documentConflict
|
|
227
|
+
? 'idle'
|
|
228
|
+
: isSavingDocument || isDocumentSavePending
|
|
229
|
+
? 'saving'
|
|
230
|
+
: generation
|
|
231
|
+
? 'saved'
|
|
232
|
+
: 'idle';
|
|
233
|
+
useEffect(() => {
|
|
234
|
+
onSaveStatusChange?.(saveStatus);
|
|
235
|
+
}, [saveStatus, onSaveStatusChange]);
|
|
236
|
+
|
|
237
|
+
const contentRef = useRef(content);
|
|
238
|
+
const generationRef = useRef(generation);
|
|
239
|
+
const loadRequestRef = useRef(0);
|
|
240
|
+
const documentSaveTimeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
|
|
241
|
+
const pendingDocumentContentRef = useRef<string | undefined>(undefined);
|
|
242
|
+
const documentSaveInFlightRef = useRef(false);
|
|
243
|
+
const documentSavePromiseRef = useRef<Promise<boolean> | undefined>(undefined);
|
|
244
|
+
const persistPendingDocumentContentRef = useRef<() => Promise<boolean>>(() => Promise.resolve(false));
|
|
245
|
+
const documentEditorFocusedRef = useRef(false);
|
|
246
|
+
// Latches true once the user focuses the editor. The editor re-serializes (normalizes) the
|
|
247
|
+
// loaded Markdown when it mounts — e.g. after switching into edit mode — which fires an onChange
|
|
248
|
+
// that is NOT a user edit. We ignore those pre-interaction changes so they don't autosave the
|
|
249
|
+
// artifact or post a spurious "working copy updated" message to the chat.
|
|
250
|
+
const documentEditorInteractedRef = useRef(false);
|
|
251
|
+
const documentEditorRef = useRef<MarkdownDocumentEditorHandle | null>(null);
|
|
252
|
+
const documentBaseGenerationRef = useRef<string | undefined>(generation);
|
|
253
|
+
const documentBaseContentRef = useRef(initialContent);
|
|
254
|
+
const documentConflictRef = useRef<ArtifactDocumentConflict | undefined>(undefined);
|
|
255
|
+
// Working-copy content the agent is known to have seen: hydration and its own
|
|
256
|
+
// edits (loadContent) plus explicit hand-offs (flushDocumentChanges). Direct user
|
|
257
|
+
// edits are diffed against this baseline when they are sent to the agent.
|
|
258
|
+
const agentKnownContentRef = useRef(initialContent);
|
|
259
|
+
const documentEditorContainerRef = useRef<HTMLDivElement | null>(null);
|
|
260
|
+
const changeRegions = useMemo(
|
|
261
|
+
() => getMarkdownChangeRegions(baselineContent ?? content, content),
|
|
262
|
+
[baselineContent, content],
|
|
263
|
+
);
|
|
264
|
+
const documentLineCount = useMemo(() => content.split('\n').length, [content]);
|
|
265
|
+
|
|
266
|
+
const updateDocumentConflict = useCallback((conflict: ArtifactDocumentConflict | undefined) => {
|
|
267
|
+
documentConflictRef.current = conflict;
|
|
268
|
+
setDocumentConflict(conflict);
|
|
269
|
+
}, []);
|
|
270
|
+
|
|
271
|
+
useEffect(() => {
|
|
272
|
+
contentRef.current = content;
|
|
273
|
+
}, [content]);
|
|
274
|
+
|
|
275
|
+
useEffect(() => {
|
|
276
|
+
generationRef.current = generation;
|
|
277
|
+
}, [generation]);
|
|
278
|
+
|
|
279
|
+
// Entering edit mode mounts a fresh editor that re-serializes the loaded content. Require a new
|
|
280
|
+
// user focus before those serialization changes count as edits. Keyed on viewMode (a stable
|
|
281
|
+
// trigger) — not the per-render onEditor callback, which would reset the latch mid-typing.
|
|
282
|
+
useEffect(() => {
|
|
283
|
+
if (viewMode !== 'document') return;
|
|
284
|
+
documentEditorInteractedRef.current = false;
|
|
285
|
+
documentEditorFocusedRef.current = false;
|
|
286
|
+
}, [viewMode]);
|
|
287
|
+
|
|
288
|
+
const loadContent = useCallback(
|
|
289
|
+
async (allowHydrationRetry: boolean) => {
|
|
290
|
+
if (!runId) return;
|
|
291
|
+
const requestId = ++loadRequestRef.current;
|
|
292
|
+
setIsLoading(true);
|
|
293
|
+
setLoadError(undefined);
|
|
294
|
+
|
|
295
|
+
for (let attempt = 0; attempt <= HYDRATION_RETRY_LIMIT; attempt++) {
|
|
296
|
+
try {
|
|
297
|
+
const response = await client.agents.getArtifactContent(runId, path);
|
|
298
|
+
if (loadRequestRef.current !== requestId) return;
|
|
299
|
+
const previousContent = contentRef.current;
|
|
300
|
+
if (previousContent && previousContent !== response.content) {
|
|
301
|
+
setHighlightChangesFrom(previousContent);
|
|
302
|
+
setHighlightVersion((value) => value + 1);
|
|
303
|
+
}
|
|
304
|
+
contentRef.current = response.content;
|
|
305
|
+
generationRef.current = response.generation;
|
|
306
|
+
// Server states reached outside the user's typing pipeline come from
|
|
307
|
+
// hydration or the agent's own tools, so the agent knows them.
|
|
308
|
+
agentKnownContentRef.current = response.content;
|
|
309
|
+
if (
|
|
310
|
+
!documentEditorFocusedRef.current &&
|
|
311
|
+
pendingDocumentContentRef.current === undefined &&
|
|
312
|
+
!documentSaveInFlightRef.current
|
|
313
|
+
) {
|
|
314
|
+
documentBaseGenerationRef.current = response.generation;
|
|
315
|
+
documentBaseContentRef.current = response.content;
|
|
316
|
+
}
|
|
317
|
+
setContent(response.content);
|
|
318
|
+
setGeneration(response.generation);
|
|
319
|
+
setLoadError(undefined);
|
|
320
|
+
setIsLoading(false);
|
|
321
|
+
onContentChange?.(response.content, response.generation);
|
|
322
|
+
return;
|
|
323
|
+
} catch (error: unknown) {
|
|
324
|
+
if (
|
|
325
|
+
loadRequestRef.current !== requestId ||
|
|
326
|
+
!allowHydrationRetry ||
|
|
327
|
+
getErrorStatus(error) !== 404 ||
|
|
328
|
+
attempt === HYDRATION_RETRY_LIMIT
|
|
329
|
+
) {
|
|
330
|
+
if (loadRequestRef.current !== requestId) return;
|
|
331
|
+
setLoadError(errorMessage(error, t('agent.failedToLoadArtifact')));
|
|
332
|
+
setIsLoading(false);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
await new Promise((resolve) => setTimeout(resolve, HYDRATION_RETRY_DELAY_MS));
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
[client, onContentChange, path, runId, t],
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
useEffect(() => {
|
|
343
|
+
loadRequestRef.current++;
|
|
344
|
+
contentRef.current = initialContent;
|
|
345
|
+
generationRef.current = undefined;
|
|
346
|
+
agentKnownContentRef.current = initialContent;
|
|
347
|
+
documentBaseGenerationRef.current = undefined;
|
|
348
|
+
documentBaseContentRef.current = initialContent;
|
|
349
|
+
documentConflictRef.current = undefined;
|
|
350
|
+
documentEditorFocusedRef.current = false;
|
|
351
|
+
documentEditorInteractedRef.current = false;
|
|
352
|
+
setContent(initialContent);
|
|
353
|
+
setGeneration(undefined);
|
|
354
|
+
setHighlightChangesFrom(undefined);
|
|
355
|
+
setLoadError(undefined);
|
|
356
|
+
setDocumentConflict(undefined);
|
|
357
|
+
setIsResolvingConflict(false);
|
|
358
|
+
setIsDocumentSavePending(false);
|
|
359
|
+
setIsLoading(Boolean(runId));
|
|
360
|
+
if (runId) void loadContent(true);
|
|
361
|
+
return () => {
|
|
362
|
+
loadRequestRef.current++;
|
|
363
|
+
};
|
|
364
|
+
}, [initialContent, loadContent, runId]);
|
|
365
|
+
|
|
366
|
+
useEffect(() => {
|
|
367
|
+
if (!runId || refreshKey <= 0) return;
|
|
368
|
+
if (documentConflictRef.current) return;
|
|
369
|
+
|
|
370
|
+
const previousContent = contentRef.current;
|
|
371
|
+
const changedContent = applyArtifactRefreshChanges(previousContent, refreshDetails);
|
|
372
|
+
if (changedContent !== undefined && changedContent !== previousContent) {
|
|
373
|
+
contentRef.current = changedContent;
|
|
374
|
+
generationRef.current = undefined;
|
|
375
|
+
setHighlightChangesFrom(previousContent);
|
|
376
|
+
setHighlightVersion((value) => value + 1);
|
|
377
|
+
setContent(changedContent);
|
|
378
|
+
setGeneration(undefined);
|
|
379
|
+
onContentChange?.(changedContent);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
void loadContent(false);
|
|
383
|
+
}, [loadContent, onContentChange, refreshDetails, refreshKey, runId]);
|
|
384
|
+
|
|
385
|
+
const applyResolvedDocumentContent = useCallback(
|
|
386
|
+
(nextContent: string, nextGeneration: string) => {
|
|
387
|
+
const previousContent = contentRef.current;
|
|
388
|
+
contentRef.current = nextContent;
|
|
389
|
+
generationRef.current = nextGeneration;
|
|
390
|
+
documentBaseContentRef.current = nextContent;
|
|
391
|
+
documentBaseGenerationRef.current = nextGeneration;
|
|
392
|
+
pendingDocumentContentRef.current = undefined;
|
|
393
|
+
updateDocumentConflict(undefined);
|
|
394
|
+
if (documentEditorRef.current?.getMarkdown() !== nextContent) {
|
|
395
|
+
documentEditorRef.current?.commands.setContent(nextContent, {
|
|
396
|
+
contentType: 'markdown',
|
|
397
|
+
emitUpdate: false,
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
if (previousContent !== nextContent) {
|
|
401
|
+
setHighlightChangesFrom(previousContent);
|
|
402
|
+
setHighlightVersion((value) => value + 1);
|
|
403
|
+
}
|
|
404
|
+
setContent(nextContent);
|
|
405
|
+
setGeneration(nextGeneration);
|
|
406
|
+
setIsDocumentSavePending(false);
|
|
407
|
+
onContentChange?.(nextContent, nextGeneration);
|
|
408
|
+
},
|
|
409
|
+
[onContentChange, updateDocumentConflict],
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
const reconcileDocumentConflict = useCallback(
|
|
413
|
+
async (
|
|
414
|
+
baseContent: string,
|
|
415
|
+
localContent: string,
|
|
416
|
+
pendingAction?: MarkdownEditingAction,
|
|
417
|
+
): Promise<false | { content: string; generation: string }> => {
|
|
418
|
+
if (!runId) return false;
|
|
419
|
+
|
|
420
|
+
// Preserve the unsaved side immediately. No reload path may replace it
|
|
421
|
+
// until a merge succeeds or the user explicitly leaves the editor.
|
|
422
|
+
contentRef.current = localContent;
|
|
423
|
+
pendingDocumentContentRef.current = undefined;
|
|
424
|
+
setContent(localContent);
|
|
425
|
+
setIsDocumentSavePending(false);
|
|
426
|
+
setIsResolvingConflict(true);
|
|
427
|
+
|
|
428
|
+
let remoteContent: string | undefined;
|
|
429
|
+
let remoteGeneration: string | undefined;
|
|
430
|
+
try {
|
|
431
|
+
const remote = await client.agents.getArtifactContent(runId, path);
|
|
432
|
+
remoteContent = remote.content;
|
|
433
|
+
remoteGeneration = remote.generation;
|
|
434
|
+
agentKnownContentRef.current = remote.content;
|
|
435
|
+
|
|
436
|
+
const rebased = rebaseTextChanges(baseContent, localContent, remote.content);
|
|
437
|
+
if (rebased.status === 'conflict') {
|
|
438
|
+
updateDocumentConflict({
|
|
439
|
+
baseContent,
|
|
440
|
+
localContent,
|
|
441
|
+
remoteContent: remote.content,
|
|
442
|
+
remoteGeneration: remote.generation,
|
|
443
|
+
pendingAction,
|
|
444
|
+
});
|
|
445
|
+
return false;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
let nextGeneration = remote.generation;
|
|
449
|
+
if (rebased.content !== remote.content) {
|
|
450
|
+
try {
|
|
451
|
+
const response = await client.agents.updateArtifactContent(runId, path, {
|
|
452
|
+
content: rebased.content,
|
|
453
|
+
generation: remote.generation,
|
|
454
|
+
});
|
|
455
|
+
nextGeneration = response.generation;
|
|
456
|
+
} catch (error: unknown) {
|
|
457
|
+
if (getErrorStatus(error) === 412) {
|
|
458
|
+
updateDocumentConflict({
|
|
459
|
+
baseContent: remote.content,
|
|
460
|
+
localContent: rebased.content,
|
|
461
|
+
remoteContent: remote.content,
|
|
462
|
+
remoteGeneration: remote.generation,
|
|
463
|
+
pendingAction,
|
|
464
|
+
});
|
|
465
|
+
return false;
|
|
466
|
+
}
|
|
467
|
+
throw error;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
applyResolvedDocumentContent(rebased.content, nextGeneration);
|
|
472
|
+
toast({ status: 'success', title: t('agent.artifactConflictRebased'), duration: 3000 });
|
|
473
|
+
return { content: rebased.content, generation: nextGeneration };
|
|
474
|
+
} catch (error: unknown) {
|
|
475
|
+
updateDocumentConflict({
|
|
476
|
+
baseContent,
|
|
477
|
+
localContent,
|
|
478
|
+
remoteContent,
|
|
479
|
+
remoteGeneration,
|
|
480
|
+
pendingAction,
|
|
481
|
+
});
|
|
482
|
+
toast({
|
|
483
|
+
status: 'error',
|
|
484
|
+
title: t('agent.artifactConflictTitle'),
|
|
485
|
+
description: errorMessage(error, t('agent.failedToLoadArtifact')),
|
|
486
|
+
duration: 5000,
|
|
487
|
+
});
|
|
488
|
+
return false;
|
|
489
|
+
} finally {
|
|
490
|
+
setIsResolvingConflict(false);
|
|
491
|
+
}
|
|
492
|
+
},
|
|
493
|
+
[applyResolvedDocumentContent, client.agents, path, runId, t, toast, updateDocumentConflict],
|
|
494
|
+
);
|
|
495
|
+
|
|
496
|
+
const handleAction = useCallback(
|
|
497
|
+
async (action: MarkdownEditingAction) => {
|
|
498
|
+
if (action.action === 'comment') {
|
|
499
|
+
await onAction?.(action);
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (documentConflictRef.current) {
|
|
504
|
+
toast({ status: 'warning', title: t('agent.artifactConflictTitle'), duration: 3000 });
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
if (!runId || !generationRef.current) {
|
|
509
|
+
toast({ status: 'warning', title: t('agent.artifactEditingUnavailable'), duration: 3000 });
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const previousContent = contentRef.current;
|
|
514
|
+
const nextContent = applyMarkdownEditingChange(previousContent, action);
|
|
515
|
+
if (nextContent === undefined) {
|
|
516
|
+
toast({
|
|
517
|
+
status: 'error',
|
|
518
|
+
title: t('agent.documentEditApplyFailed'),
|
|
519
|
+
description: t('agent.documentEditApplyFailedDescription'),
|
|
520
|
+
duration: 5000,
|
|
521
|
+
});
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
try {
|
|
526
|
+
const response = await client.agents.updateArtifactContent(runId, path, {
|
|
527
|
+
content: nextContent,
|
|
528
|
+
generation: generationRef.current,
|
|
529
|
+
});
|
|
530
|
+
contentRef.current = nextContent;
|
|
531
|
+
generationRef.current = response.generation;
|
|
532
|
+
documentBaseContentRef.current = nextContent;
|
|
533
|
+
documentBaseGenerationRef.current = response.generation;
|
|
534
|
+
setHighlightChangesFrom(previousContent);
|
|
535
|
+
setHighlightVersion((value) => value + 1);
|
|
536
|
+
setContent(nextContent);
|
|
537
|
+
setGeneration(response.generation);
|
|
538
|
+
onContentChange?.(nextContent, response.generation);
|
|
539
|
+
await onAction?.({ ...action, applied: true, base_version: response.generation });
|
|
540
|
+
agentKnownContentRef.current = nextContent;
|
|
541
|
+
} catch (error: unknown) {
|
|
542
|
+
const isConflict = getErrorStatus(error) === 412;
|
|
543
|
+
if (isConflict) {
|
|
544
|
+
const resolved = await reconcileDocumentConflict(previousContent, nextContent, action);
|
|
545
|
+
if (resolved) {
|
|
546
|
+
await onAction?.({ ...action, applied: true, base_version: resolved.generation });
|
|
547
|
+
agentKnownContentRef.current = resolved.content;
|
|
548
|
+
}
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
toast({
|
|
552
|
+
status: 'error',
|
|
553
|
+
title: t('agent.documentEditApplyFailed'),
|
|
554
|
+
description: errorMessage(error, t('store.errorSavingTextDefault')),
|
|
555
|
+
duration: 5000,
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
},
|
|
559
|
+
[client, onAction, onContentChange, path, reconcileDocumentConflict, runId, t, toast],
|
|
560
|
+
);
|
|
561
|
+
|
|
562
|
+
const persistPendingDocumentContent = useCallback((): Promise<boolean> => {
|
|
563
|
+
if (!runId) return Promise.resolve(false);
|
|
564
|
+
if (documentSavePromiseRef.current) return documentSavePromiseRef.current;
|
|
565
|
+
|
|
566
|
+
const savePromise = (async () => {
|
|
567
|
+
documentSaveInFlightRef.current = true;
|
|
568
|
+
setIsSavingDocument(true);
|
|
569
|
+
try {
|
|
570
|
+
while (pendingDocumentContentRef.current !== undefined) {
|
|
571
|
+
const nextContent = pendingDocumentContentRef.current;
|
|
572
|
+
pendingDocumentContentRef.current = undefined;
|
|
573
|
+
const expectedGeneration = documentBaseGenerationRef.current;
|
|
574
|
+
if (!expectedGeneration) {
|
|
575
|
+
throw new Error(t('agent.artifactEditingUnavailable'));
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
const response = await client.agents.updateArtifactContent(runId, path, {
|
|
579
|
+
content: nextContent,
|
|
580
|
+
generation: expectedGeneration,
|
|
581
|
+
});
|
|
582
|
+
generationRef.current = response.generation;
|
|
583
|
+
documentBaseGenerationRef.current = response.generation;
|
|
584
|
+
documentBaseContentRef.current = nextContent;
|
|
585
|
+
setGeneration(response.generation);
|
|
586
|
+
onContentChange?.(contentRef.current, response.generation);
|
|
587
|
+
}
|
|
588
|
+
setIsDocumentSavePending(false);
|
|
589
|
+
return true;
|
|
590
|
+
} catch (error: unknown) {
|
|
591
|
+
const isConflict = getErrorStatus(error) === 412;
|
|
592
|
+
if (isConflict) {
|
|
593
|
+
const resolved = await reconcileDocumentConflict(
|
|
594
|
+
documentBaseContentRef.current,
|
|
595
|
+
contentRef.current,
|
|
596
|
+
);
|
|
597
|
+
return Boolean(resolved);
|
|
598
|
+
}
|
|
599
|
+
toast({
|
|
600
|
+
status: 'error',
|
|
601
|
+
title: t('agent.documentEditApplyFailed'),
|
|
602
|
+
description: errorMessage(error, t('store.errorSavingTextDefault')),
|
|
603
|
+
duration: 5000,
|
|
604
|
+
});
|
|
605
|
+
return false;
|
|
606
|
+
} finally {
|
|
607
|
+
documentSaveInFlightRef.current = false;
|
|
608
|
+
setIsSavingDocument(false);
|
|
609
|
+
}
|
|
610
|
+
})();
|
|
611
|
+
|
|
612
|
+
documentSavePromiseRef.current = savePromise;
|
|
613
|
+
void savePromise.finally(() => {
|
|
614
|
+
if (documentSavePromiseRef.current === savePromise) documentSavePromiseRef.current = undefined;
|
|
615
|
+
});
|
|
616
|
+
return savePromise;
|
|
617
|
+
}, [client.agents, onContentChange, path, reconcileDocumentConflict, runId, t, toast]);
|
|
618
|
+
persistPendingDocumentContentRef.current = persistPendingDocumentContent;
|
|
619
|
+
|
|
620
|
+
const flushDocumentChanges = useCallback(async (): Promise<false | ArtifactEditingSurfaceDocumentEdit> => {
|
|
621
|
+
if (documentSaveTimeoutRef.current !== undefined) {
|
|
622
|
+
clearTimeout(documentSaveTimeoutRef.current);
|
|
623
|
+
documentSaveTimeoutRef.current = undefined;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
const latestEditorContent = documentEditorRef.current?.getMarkdown();
|
|
627
|
+
if (latestEditorContent !== undefined && latestEditorContent !== contentRef.current) {
|
|
628
|
+
contentRef.current = latestEditorContent;
|
|
629
|
+
setContent(latestEditorContent);
|
|
630
|
+
onContentChange?.(latestEditorContent, generationRef.current);
|
|
631
|
+
pendingDocumentContentRef.current = latestEditorContent;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
const activeConflict = documentConflictRef.current;
|
|
635
|
+
if (activeConflict) {
|
|
636
|
+
const resolved = await reconcileDocumentConflict(activeConflict.baseContent, contentRef.current);
|
|
637
|
+
if (!resolved) return false;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
while (pendingDocumentContentRef.current !== undefined || documentSavePromiseRef.current) {
|
|
641
|
+
const saved = await persistPendingDocumentContent();
|
|
642
|
+
if (!saved) return false;
|
|
643
|
+
}
|
|
644
|
+
if (!runId || !generationRef.current) return false;
|
|
645
|
+
|
|
646
|
+
const previous = agentKnownContentRef.current;
|
|
647
|
+
agentKnownContentRef.current = contentRef.current;
|
|
648
|
+
return { previous, current: contentRef.current };
|
|
649
|
+
}, [onContentChange, persistPendingDocumentContent, reconcileDocumentConflict, runId]);
|
|
650
|
+
|
|
651
|
+
useEffect(() => {
|
|
652
|
+
if (!flushChangesRef) return;
|
|
653
|
+
flushChangesRef.current = flushDocumentChanges;
|
|
654
|
+
return () => {
|
|
655
|
+
if (flushChangesRef.current === flushDocumentChanges) flushChangesRef.current = null;
|
|
656
|
+
};
|
|
657
|
+
}, [flushChangesRef, flushDocumentChanges]);
|
|
658
|
+
|
|
659
|
+
const handleDocumentChange = useCallback(
|
|
660
|
+
(nextContent: string) => {
|
|
661
|
+
if (nextContent === contentRef.current) return;
|
|
662
|
+
// A focused editor intentionally keeps its local document mounted while an agent
|
|
663
|
+
// refresh updates the backing artifact. Ignore the editor's stale base-content
|
|
664
|
+
// emission so it cannot be mistaken for a user edit and saved over the refresh.
|
|
665
|
+
if (
|
|
666
|
+
documentEditorFocusedRef.current &&
|
|
667
|
+
pendingDocumentContentRef.current === undefined &&
|
|
668
|
+
!documentSaveInFlightRef.current &&
|
|
669
|
+
nextContent === documentBaseContentRef.current
|
|
670
|
+
) {
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
// The editor normalizes loaded Markdown on mount, firing onChange before the user has
|
|
674
|
+
// touched it. Adopt that serialization as the baseline silently — no save, no message.
|
|
675
|
+
if (!documentEditorInteractedRef.current) {
|
|
676
|
+
contentRef.current = nextContent;
|
|
677
|
+
setContent(nextContent);
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
contentRef.current = nextContent;
|
|
681
|
+
setContent(nextContent);
|
|
682
|
+
onDocumentEdit?.();
|
|
683
|
+
onContentChange?.(nextContent, generationRef.current);
|
|
684
|
+
|
|
685
|
+
const activeConflict = documentConflictRef.current;
|
|
686
|
+
if (activeConflict) {
|
|
687
|
+
updateDocumentConflict({ ...activeConflict, localContent: nextContent });
|
|
688
|
+
setIsDocumentSavePending(false);
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
setIsDocumentSavePending(true);
|
|
693
|
+
pendingDocumentContentRef.current = nextContent;
|
|
694
|
+
if (documentSaveTimeoutRef.current !== undefined) clearTimeout(documentSaveTimeoutRef.current);
|
|
695
|
+
documentSaveTimeoutRef.current = setTimeout(() => {
|
|
696
|
+
documentSaveTimeoutRef.current = undefined;
|
|
697
|
+
void persistPendingDocumentContent();
|
|
698
|
+
}, DOCUMENT_SAVE_DEBOUNCE_MS);
|
|
699
|
+
},
|
|
700
|
+
[onContentChange, onDocumentEdit, persistPendingDocumentContent, updateDocumentConflict],
|
|
701
|
+
);
|
|
702
|
+
|
|
703
|
+
const handleDocumentFocusChange = useCallback((focused: boolean) => {
|
|
704
|
+
documentEditorFocusedRef.current = focused;
|
|
705
|
+
if (focused) documentEditorInteractedRef.current = true;
|
|
706
|
+
if (focused) {
|
|
707
|
+
documentBaseGenerationRef.current = generationRef.current;
|
|
708
|
+
documentBaseContentRef.current = contentRef.current;
|
|
709
|
+
} else if (pendingDocumentContentRef.current === undefined && !documentSaveInFlightRef.current) {
|
|
710
|
+
documentBaseGenerationRef.current = generationRef.current;
|
|
711
|
+
documentBaseContentRef.current = contentRef.current;
|
|
712
|
+
}
|
|
713
|
+
}, []);
|
|
714
|
+
|
|
715
|
+
const retryDocumentConflict = useCallback(async () => {
|
|
716
|
+
const activeConflict = documentConflictRef.current;
|
|
717
|
+
if (!activeConflict || isResolvingConflict) return;
|
|
718
|
+
const resolved = await reconcileDocumentConflict(
|
|
719
|
+
activeConflict.baseContent,
|
|
720
|
+
contentRef.current,
|
|
721
|
+
activeConflict.pendingAction,
|
|
722
|
+
);
|
|
723
|
+
if (resolved && activeConflict.pendingAction) {
|
|
724
|
+
await onAction?.({
|
|
725
|
+
...activeConflict.pendingAction,
|
|
726
|
+
applied: true,
|
|
727
|
+
base_version: resolved.generation,
|
|
728
|
+
});
|
|
729
|
+
agentKnownContentRef.current = resolved.content;
|
|
730
|
+
}
|
|
731
|
+
}, [isResolvingConflict, onAction, reconcileDocumentConflict]);
|
|
732
|
+
|
|
733
|
+
const navigateToChangedLine = useCallback(
|
|
734
|
+
(line: number) => {
|
|
735
|
+
const scrollContainer = documentEditorContainerRef.current?.querySelector<HTMLElement>(
|
|
736
|
+
'.vertesia-markdown-document-editor-content',
|
|
737
|
+
);
|
|
738
|
+
if (!scrollContainer) return;
|
|
739
|
+
const ratio = Math.min(1, line / Math.max(1, documentLineCount - 1));
|
|
740
|
+
scrollContainer.scrollTo({
|
|
741
|
+
top: ratio * Math.max(0, scrollContainer.scrollHeight - scrollContainer.clientHeight),
|
|
742
|
+
behavior: 'smooth',
|
|
743
|
+
});
|
|
744
|
+
},
|
|
745
|
+
[documentLineCount],
|
|
746
|
+
);
|
|
747
|
+
|
|
748
|
+
useEffect(
|
|
749
|
+
() => () => {
|
|
750
|
+
if (documentSaveTimeoutRef.current !== undefined) clearTimeout(documentSaveTimeoutRef.current);
|
|
751
|
+
const latestEditorContent = documentEditorRef.current?.getMarkdown();
|
|
752
|
+
if (latestEditorContent !== undefined && latestEditorContent !== contentRef.current) {
|
|
753
|
+
contentRef.current = latestEditorContent;
|
|
754
|
+
pendingDocumentContentRef.current = latestEditorContent;
|
|
755
|
+
}
|
|
756
|
+
if (pendingDocumentContentRef.current !== undefined) {
|
|
757
|
+
void persistPendingDocumentContentRef.current();
|
|
758
|
+
}
|
|
759
|
+
},
|
|
760
|
+
[],
|
|
761
|
+
);
|
|
762
|
+
|
|
763
|
+
return (
|
|
764
|
+
<div className={className ?? 'relative h-full min-h-0 overflow-y-auto px-4 py-3'}>
|
|
765
|
+
{documentConflict ? (
|
|
766
|
+
<div className="absolute inset-x-4 top-3 z-30" role="alert">
|
|
767
|
+
<MessageBox status="warning" title={t('agent.artifactConflictTitle')} className="shadow-lg">
|
|
768
|
+
<div className="space-y-3">
|
|
769
|
+
<p>{t('agent.artifactConflictDescription')}</p>
|
|
770
|
+
<div className="flex justify-end">
|
|
771
|
+
<Button
|
|
772
|
+
variant="outline"
|
|
773
|
+
size="sm"
|
|
774
|
+
disabled={isResolvingConflict}
|
|
775
|
+
onClick={() => void retryDocumentConflict()}
|
|
776
|
+
>
|
|
777
|
+
{isResolvingConflict ? <Spinner size="sm" /> : null}
|
|
778
|
+
{t('agent.artifactConflictRetry')}
|
|
779
|
+
</Button>
|
|
780
|
+
</div>
|
|
781
|
+
</div>
|
|
782
|
+
</MessageBox>
|
|
783
|
+
</div>
|
|
784
|
+
) : null}
|
|
785
|
+
{isLoading && !content ? (
|
|
786
|
+
<Center className="h-full min-h-[200px]">
|
|
787
|
+
<Spinner size="lg" />
|
|
788
|
+
</Center>
|
|
789
|
+
) : loadError && !content ? (
|
|
790
|
+
<Center className="h-full min-h-[200px] flex-col gap-3 text-center text-muted">
|
|
791
|
+
<span>{loadError}</span>
|
|
792
|
+
<Button variant="outline" size="sm" onClick={() => void loadContent(false)}>
|
|
793
|
+
{t('agent.retry')}
|
|
794
|
+
</Button>
|
|
795
|
+
</Center>
|
|
796
|
+
) : viewMode === 'document' ? (
|
|
797
|
+
<Suspense
|
|
798
|
+
fallback={
|
|
799
|
+
<Center className="h-full min-h-[200px]">
|
|
800
|
+
<Spinner size="lg" />
|
|
801
|
+
</Center>
|
|
802
|
+
}
|
|
803
|
+
>
|
|
804
|
+
<div className="flex h-full min-h-0 flex-col">
|
|
805
|
+
<div ref={documentEditorContainerRef} className="relative min-h-0 flex-1">
|
|
806
|
+
<MarkdownDocumentEditor
|
|
807
|
+
value={content}
|
|
808
|
+
onChange={handleDocumentChange}
|
|
809
|
+
artifactRunId={runId}
|
|
810
|
+
editable={!readOnly && Boolean(runId)}
|
|
811
|
+
externalValueSync="when-blurred"
|
|
812
|
+
onFocusChange={handleDocumentFocusChange}
|
|
813
|
+
onEditor={(editor) => {
|
|
814
|
+
documentEditorRef.current =
|
|
815
|
+
editor as unknown as MarkdownDocumentEditorHandle | null;
|
|
816
|
+
}}
|
|
817
|
+
onSendComment={
|
|
818
|
+
onSendMessage && runId && !documentConflict && commentsEnabled
|
|
819
|
+
? onSendMessage
|
|
820
|
+
: undefined
|
|
821
|
+
}
|
|
822
|
+
onSendChangesToAgent={onSendChangesToAgent}
|
|
823
|
+
hasUnsentChanges={hasUnsentChanges}
|
|
824
|
+
isSendingChanges={isSendingChanges}
|
|
825
|
+
sendChangesDisabled={sendChangesDisabled || !runId || Boolean(documentConflict)}
|
|
826
|
+
toolbarStatus={toolbarStatus}
|
|
827
|
+
contentClassName="pe-5"
|
|
828
|
+
/>
|
|
829
|
+
<MarkdownChangeRuler
|
|
830
|
+
regions={changeRegions}
|
|
831
|
+
totalLines={documentLineCount}
|
|
832
|
+
onNavigate={navigateToChangedLine}
|
|
833
|
+
/>
|
|
834
|
+
{!onSaveStatusChange && saveStatus !== 'idle' ? (
|
|
835
|
+
<div
|
|
836
|
+
className={cn(
|
|
837
|
+
'pointer-events-none absolute bottom-3 end-6 flex items-center gap-1.5 rounded-full',
|
|
838
|
+
'border border-mixer-muted/25 bg-background/90 px-2.5 py-1 text-[11px] shadow-sm',
|
|
839
|
+
isSavingDocument || isDocumentSavePending ? 'text-muted' : 'text-success',
|
|
840
|
+
)}
|
|
841
|
+
>
|
|
842
|
+
{isSavingDocument || isDocumentSavePending ? (
|
|
843
|
+
<Spinner size="sm" />
|
|
844
|
+
) : (
|
|
845
|
+
<Check className="size-3" />
|
|
846
|
+
)}
|
|
847
|
+
{isSavingDocument || isDocumentSavePending
|
|
848
|
+
? t('agent.savingWorkingCopy')
|
|
849
|
+
: t('agent.savedToWorkingCopy')}
|
|
850
|
+
</div>
|
|
851
|
+
) : null}
|
|
852
|
+
</div>
|
|
853
|
+
</div>
|
|
854
|
+
</Suspense>
|
|
855
|
+
) : (
|
|
856
|
+
<div className="vprose prose-sm mx-auto max-w-5xl">
|
|
857
|
+
<CollaborativeMarkdownRenderer
|
|
858
|
+
artifactRunId={runId}
|
|
859
|
+
resource={{ kind: 'agent_artifact', run_id: runId ?? 'pending', path }}
|
|
860
|
+
baseVersion={generation}
|
|
861
|
+
readOnly={readOnly || !runId || Boolean(documentConflict)}
|
|
862
|
+
allowComments={commentsEnabled && Boolean(runId) && !documentConflict}
|
|
863
|
+
highlightChangesFrom={highlightChangesFrom}
|
|
864
|
+
highlightVersion={highlightVersion}
|
|
865
|
+
onAction={handleAction}
|
|
866
|
+
>
|
|
867
|
+
{content}
|
|
868
|
+
</CollaborativeMarkdownRenderer>
|
|
869
|
+
</div>
|
|
870
|
+
)}
|
|
871
|
+
{isLoading && content ? (
|
|
872
|
+
<div className="absolute end-3 top-3 rounded-full bg-background/80 p-2 shadow-sm">
|
|
873
|
+
<GitCommitHorizontal className="size-4 animate-pulse text-muted" />
|
|
874
|
+
</div>
|
|
875
|
+
) : null}
|
|
876
|
+
</div>
|
|
877
|
+
);
|
|
878
|
+
}
|