@parhelia/core 0.1.12881 → 0.1.12882
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/dist/components/ui/card.d.ts +1 -3
- package/dist/components/ui/card.js +2 -2
- package/dist/components/ui/card.js.map +1 -1
- package/dist/components/ui/context-menu.js +2 -2
- package/dist/config/config.js +7 -8
- package/dist/config/config.js.map +1 -1
- package/dist/config/types.d.ts +7 -0
- package/dist/config/types.js.map +1 -1
- package/dist/editor/FieldActionsOverlay.d.ts +1 -0
- package/dist/editor/FieldActionsOverlay.js +45 -1
- package/dist/editor/FieldActionsOverlay.js.map +1 -1
- package/dist/editor/FieldListField.d.ts +1 -1
- package/dist/editor/FieldListField.js +18 -20
- package/dist/editor/FieldListField.js.map +1 -1
- package/dist/editor/ImageEditor.d.ts +1 -6
- package/dist/editor/ImageEditor.js +3 -19
- package/dist/editor/ImageEditor.js.map +1 -1
- package/dist/editor/PictureEditor.d.ts +1 -2
- package/dist/editor/PictureEditor.js +14 -5
- package/dist/editor/PictureEditor.js.map +1 -1
- package/dist/editor/ai/Agents.js +2 -2
- package/dist/editor/ai/Agents.js.map +1 -1
- package/dist/editor/ai/GuidanceOverlay.js +11 -1
- package/dist/editor/ai/GuidanceOverlay.js.map +1 -1
- package/dist/editor/ai/InlineAiDialog.js +11 -22
- package/dist/editor/ai/InlineAiDialog.js.map +1 -1
- package/dist/editor/ai/InlineAiTrigger.js +57 -17
- package/dist/editor/ai/InlineAiTrigger.js.map +1 -1
- package/dist/editor/ai/dialogs/capturePageDom.js +36 -66
- package/dist/editor/ai/dialogs/capturePageDom.js.map +1 -1
- package/dist/editor/ai/dialogs/capturePageScreenshot.js +162 -281
- package/dist/editor/ai/dialogs/capturePageScreenshot.js.map +1 -1
- package/dist/editor/ai/terminal/agentSessionState.d.ts +0 -3
- package/dist/editor/ai/terminal/agentSessionState.js +1 -3
- package/dist/editor/ai/terminal/agentSessionState.js.map +1 -1
- package/dist/editor/ai/terminal/agentStartRequest.d.ts +1 -2
- package/dist/editor/ai/terminal/agentStartRequest.js +1 -2
- package/dist/editor/ai/terminal/agentStartRequest.js.map +1 -1
- package/dist/editor/ai/terminal/components/AgentCostDisplay.js +1 -1
- package/dist/editor/ai/terminal/components/AgentCostDisplay.js.map +1 -1
- package/dist/editor/ai/terminal/components/AgentDocumentList.d.ts +0 -7
- package/dist/editor/ai/terminal/components/AgentDocumentList.js +13 -55
- package/dist/editor/ai/terminal/components/AgentDocumentList.js.map +1 -1
- package/dist/editor/ai/terminal/components/AgentFullPromptControls.d.ts +1 -3
- package/dist/editor/ai/terminal/components/AgentFullPromptControls.js +14 -22
- package/dist/editor/ai/terminal/components/AgentFullPromptControls.js.map +1 -1
- package/dist/editor/ai/terminal/components/AgentModeSelector.js +4 -4
- package/dist/editor/ai/terminal/components/AgentModeSelector.js.map +1 -1
- package/dist/editor/ai/terminal/components/AgentPromptActionButtons.js +4 -4
- package/dist/editor/ai/terminal/components/AgentPromptActionButtons.js.map +1 -1
- package/dist/editor/ai/terminal/components/AgentPromptComposer.js +1 -1
- package/dist/editor/ai/terminal/components/AgentPromptComposer.js.map +1 -1
- package/dist/editor/ai/terminal/components/AgentPromptInputArea.d.ts +1 -2
- package/dist/editor/ai/terminal/components/AgentPromptInputArea.js +11 -8
- package/dist/editor/ai/terminal/components/AgentPromptInputArea.js.map +1 -1
- package/dist/editor/ai/terminal/components/AgentPromptTrayPopovers.d.ts +4 -1
- package/dist/editor/ai/terminal/components/AgentPromptTrayPopovers.js +14 -31
- package/dist/editor/ai/terminal/components/AgentPromptTrayPopovers.js.map +1 -1
- package/dist/editor/ai/terminal/components/AgentSettingsPopover.js +1 -1
- package/dist/editor/ai/terminal/components/AgentSettingsPopover.js.map +1 -1
- package/dist/editor/ai/terminal/components/AgentTerminalFullLayout.d.ts +1 -2
- package/dist/editor/ai/terminal/components/AgentTerminalFullLayout.js +4 -2
- package/dist/editor/ai/terminal/components/AgentTerminalFullLayout.js.map +1 -1
- package/dist/editor/ai/terminal/components/AgentTerminalMessageGroups.js +1 -1
- package/dist/editor/ai/terminal/components/AgentTerminalMessageGroups.js.map +1 -1
- package/dist/editor/ai/terminal/components/AgentTerminalView.js +2 -13
- package/dist/editor/ai/terminal/components/AgentTerminalView.js.map +1 -1
- package/dist/editor/ai/terminal/components/AiResponseMessage.js +14 -16
- package/dist/editor/ai/terminal/components/AiResponseMessage.js.map +1 -1
- package/dist/editor/ai/terminal/components/ContextInfoBar.js +2 -22
- package/dist/editor/ai/terminal/components/ContextInfoBar.js.map +1 -1
- package/dist/editor/ai/terminal/components/QueuedPromptsPanel.js +26 -37
- package/dist/editor/ai/terminal/components/QueuedPromptsPanel.js.map +1 -1
- package/dist/editor/ai/terminal/components/UserMessage.d.ts +1 -2
- package/dist/editor/ai/terminal/components/UserMessage.js +8 -144
- package/dist/editor/ai/terminal/components/UserMessage.js.map +1 -1
- package/dist/editor/ai/terminal/useAgentPromptComposerHandlers.js +1 -1
- package/dist/editor/ai/terminal/useAgentPromptComposerHandlers.js.map +1 -1
- package/dist/editor/ai/terminal/useAgentSessionSync.d.ts +0 -1
- package/dist/editor/ai/terminal/useAgentSubmitHandlers.d.ts +1 -3
- package/dist/editor/ai/terminal/useAgentSubmitHandlers.js +3 -9
- package/dist/editor/ai/terminal/useAgentSubmitHandlers.js.map +1 -1
- package/dist/editor/ai/terminal/useAgentTerminalController.js +0 -7
- package/dist/editor/ai/terminal/useAgentTerminalController.js.map +1 -1
- package/dist/editor/ai/terminal/useAgentTerminalUiState.js +1 -1
- package/dist/editor/ai/terminal/useAgentTerminalUiState.js.map +1 -1
- package/dist/editor/ai/terminal/useAgentUserMessageSocketHandler.js +1 -3
- package/dist/editor/ai/terminal/useAgentUserMessageSocketHandler.js.map +1 -1
- package/dist/editor/ai/useInlineAiPosition.d.ts +1 -1
- package/dist/editor/ai/useInlineAiPosition.js +52 -22
- package/dist/editor/ai/useInlineAiPosition.js.map +1 -1
- package/dist/editor/ai-image-editor/AiImageResultOverlay.js +62 -30
- package/dist/editor/ai-image-editor/AiImageResultOverlay.js.map +1 -1
- package/dist/editor/client/EditorShell.d.ts +1 -5
- package/dist/editor/client/EditorShell.js +136 -285
- package/dist/editor/client/EditorShell.js.map +1 -1
- package/dist/editor/client/editContext.d.ts +5 -33
- package/dist/editor/client/editContext.js.map +1 -1
- package/dist/editor/client/hooks/useSocketMessageHandler.js +17 -14
- package/dist/editor/client/hooks/useSocketMessageHandler.js.map +1 -1
- package/dist/editor/client/itemsRepository.d.ts +0 -2
- package/dist/editor/client/itemsRepository.js +8 -15
- package/dist/editor/client/itemsRepository.js.map +1 -1
- package/dist/editor/client/operations.d.ts +1 -1
- package/dist/editor/client/operations.js +17 -41
- package/dist/editor/client/operations.js.map +1 -1
- package/dist/editor/client/pageModelBuilder.js +7 -24
- package/dist/editor/client/pageModelBuilder.js.map +1 -1
- package/dist/editor/commands/handlers/uiActionHandlers.js +5 -1
- package/dist/editor/commands/handlers/uiActionHandlers.js.map +1 -1
- package/dist/editor/editor-warnings/FinalWorkflowStateReadOnly.js +5 -0
- package/dist/editor/editor-warnings/FinalWorkflowStateReadOnly.js.map +1 -1
- package/dist/editor/editor-warnings/ItemLocked.js +6 -3
- package/dist/editor/editor-warnings/ItemLocked.js.map +1 -1
- package/dist/editor/field-types/MultiLineText.js +3 -10
- package/dist/editor/field-types/MultiLineText.js.map +1 -1
- package/dist/editor/field-types/RawEditor.js +1 -8
- package/dist/editor/field-types/RawEditor.js.map +1 -1
- package/dist/editor/field-types/RichTextEditorComponent.js +45 -156
- package/dist/editor/field-types/RichTextEditorComponent.js.map +1 -1
- package/dist/editor/field-types/SingleLineText.js +3 -10
- package/dist/editor/field-types/SingleLineText.js.map +1 -1
- package/dist/editor/field-types/richtext/components/ReactSlate.js +2 -8
- package/dist/editor/field-types/richtext/components/ReactSlate.js.map +1 -1
- package/dist/editor/field-types/richtext/contextMenuFactory.d.ts +2 -1
- package/dist/editor/field-types/richtext/contextMenuFactory.js +303 -100
- package/dist/editor/field-types/richtext/contextMenuFactory.js.map +1 -1
- package/dist/editor/field-types/richtext/types.d.ts +0 -2
- package/dist/editor/field-types/richtext/types.js.map +1 -1
- package/dist/editor/media-selector/MediaFolderBrowser.d.ts +2 -1
- package/dist/editor/media-selector/MediaFolderBrowser.js +19 -9
- package/dist/editor/media-selector/MediaFolderBrowser.js.map +1 -1
- package/dist/editor/media-selector/TreeSelector.js +30 -24
- package/dist/editor/media-selector/TreeSelector.js.map +1 -1
- package/dist/editor/media-selector/UploadZone.d.ts +2 -1
- package/dist/editor/media-selector/UploadZone.js +21 -9
- package/dist/editor/media-selector/UploadZone.js.map +1 -1
- package/dist/editor/menubar/PageSelector.js +2 -8
- package/dist/editor/menubar/PageSelector.js.map +1 -1
- package/dist/editor/menubar/VersionPreviewCard.js +249 -4
- package/dist/editor/menubar/VersionPreviewCard.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/EditControls.js +2 -2
- package/dist/editor/menubar/toolbar-sections/EditControls.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/ManualBrowser.d.ts +10 -0
- package/dist/editor/menubar/toolbar-sections/ManualBrowser.js +462 -63
- package/dist/editor/menubar/toolbar-sections/ManualBrowser.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/ViewportControls.js +1 -1
- package/dist/editor/page-editor-chrome/CommentHighlightings.d.ts +2 -5
- package/dist/editor/page-editor-chrome/CommentHighlightings.js +215 -340
- package/dist/editor/page-editor-chrome/CommentHighlightings.js.map +1 -1
- package/dist/editor/page-editor-chrome/FeedbackHighlightBadge.d.ts +1 -5
- package/dist/editor/page-editor-chrome/FeedbackHighlightBadge.js +4 -11
- package/dist/editor/page-editor-chrome/FeedbackHighlightBadge.js.map +1 -1
- package/dist/editor/page-editor-chrome/FieldActionIndicator.js +13 -21
- package/dist/editor/page-editor-chrome/FieldActionIndicator.js.map +1 -1
- package/dist/editor/page-editor-chrome/FieldEditedIndicator.js +29 -23
- package/dist/editor/page-editor-chrome/FieldEditedIndicator.js.map +1 -1
- package/dist/editor/page-editor-chrome/FrameMenu.js +19 -110
- package/dist/editor/page-editor-chrome/FrameMenu.js.map +1 -1
- package/dist/editor/page-editor-chrome/InlineEditor.d.ts +7 -0
- package/dist/editor/page-editor-chrome/InlineEditor.js +1719 -0
- package/dist/editor/page-editor-chrome/InlineEditor.js.map +1 -0
- package/dist/editor/page-editor-chrome/LockedFieldIndicator.d.ts +2 -3
- package/dist/editor/page-editor-chrome/LockedFieldIndicator.js +45 -148
- package/dist/editor/page-editor-chrome/LockedFieldIndicator.js.map +1 -1
- package/dist/editor/page-editor-chrome/PageEditorChrome.d.ts +0 -2
- package/dist/editor/page-editor-chrome/PageEditorChrome.js +21 -25
- package/dist/editor/page-editor-chrome/PageEditorChrome.js.map +1 -1
- package/dist/editor/page-editor-chrome/PictureEditorOverlay.js +128 -163
- package/dist/editor/page-editor-chrome/PictureEditorOverlay.js.map +1 -1
- package/dist/editor/page-editor-chrome/PlaceholderDropZone.d.ts +1 -1
- package/dist/editor/page-editor-chrome/PlaceholderDropZone.js +3 -6
- package/dist/editor/page-editor-chrome/PlaceholderDropZone.js.map +1 -1
- package/dist/editor/page-editor-chrome/PlaceholderDropZones.d.ts +2 -1
- package/dist/editor/page-editor-chrome/PlaceholderDropZones.js +146 -83
- package/dist/editor/page-editor-chrome/PlaceholderDropZones.js.map +1 -1
- package/dist/editor/page-editor-chrome/SuggestionHighlightings.d.ts +2 -5
- package/dist/editor/page-editor-chrome/SuggestionHighlightings.js +63 -144
- package/dist/editor/page-editor-chrome/SuggestionHighlightings.js.map +1 -1
- package/dist/editor/page-editor-chrome/VersionDiffHighlightings.d.ts +2 -1
- package/dist/editor/page-editor-chrome/VersionDiffHighlightings.js +30 -101
- package/dist/editor/page-editor-chrome/VersionDiffHighlightings.js.map +1 -1
- package/dist/editor/page-editor-chrome/overlay/IframeOverlayProvider.d.ts +1 -10
- package/dist/editor/page-editor-chrome/overlay/IframeOverlayProvider.js +122 -105
- package/dist/editor/page-editor-chrome/overlay/IframeOverlayProvider.js.map +1 -1
- package/dist/editor/page-editor-chrome/overlay/geometry.d.ts +4 -11
- package/dist/editor/page-editor-chrome/overlay/geometry.js +36 -139
- package/dist/editor/page-editor-chrome/overlay/geometry.js.map +1 -1
- package/dist/editor/page-editor-chrome/overlay/iframeAccess.d.ts +2 -0
- package/dist/editor/page-editor-chrome/overlay/iframeAccess.js +21 -0
- package/dist/editor/page-editor-chrome/overlay/iframeAccess.js.map +1 -0
- package/dist/editor/page-editor-chrome/useInlineAICompletion.d.ts +7 -0
- package/dist/editor/page-editor-chrome/useInlineAICompletion.js +758 -0
- package/dist/editor/page-editor-chrome/useInlineAICompletion.js.map +1 -0
- package/dist/editor/page-viewer/EditorForm.js +1 -17
- package/dist/editor/page-viewer/EditorForm.js.map +1 -1
- package/dist/editor/page-viewer/MiniMap.d.ts +2 -2
- package/dist/editor/page-viewer/MiniMap.js +364 -176
- package/dist/editor/page-viewer/MiniMap.js.map +1 -1
- package/dist/editor/page-viewer/PageViewer.js +13 -40
- package/dist/editor/page-viewer/PageViewer.js.map +1 -1
- package/dist/editor/page-viewer/PageViewerFrame.d.ts +5 -0
- package/dist/editor/page-viewer/PageViewerFrame.js +1509 -1527
- package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
- package/dist/editor/page-viewer/pageModelSkeletonBuilder.d.ts +3 -0
- package/dist/editor/page-viewer/pageModelSkeletonBuilder.js +796 -0
- package/dist/editor/page-viewer/pageModelSkeletonBuilder.js.map +1 -0
- package/dist/editor/page-viewer/pageViewContext.d.ts +0 -32
- package/dist/editor/page-viewer/pageViewContext.js +6 -37
- package/dist/editor/page-viewer/pageViewContext.js.map +1 -1
- package/dist/editor/reviews/Comment.d.ts +1 -2
- package/dist/editor/reviews/Comment.js +4 -9
- package/dist/editor/reviews/Comment.js.map +1 -1
- package/dist/editor/reviews/CommentEditor.js +1 -1
- package/dist/editor/reviews/CommentEditor.js.map +1 -1
- package/dist/editor/reviews/CommentPopover.js +9 -68
- package/dist/editor/reviews/CommentPopover.js.map +1 -1
- package/dist/editor/reviews/CommentView.js +4 -24
- package/dist/editor/reviews/CommentView.js.map +1 -1
- package/dist/editor/reviews/Comments.d.ts +2 -0
- package/dist/editor/reviews/Comments.js +30 -29
- package/dist/editor/reviews/Comments.js.map +1 -1
- package/dist/editor/reviews/FeedbackCard.d.ts +2 -4
- package/dist/editor/reviews/FeedbackCard.js +5 -5
- package/dist/editor/reviews/FeedbackCard.js.map +1 -1
- package/dist/editor/reviews/SuggestedEdit.js +6 -4
- package/dist/editor/reviews/SuggestedEdit.js.map +1 -1
- package/dist/editor/reviews/SuggestionDisplayPopover.js +2 -3
- package/dist/editor/reviews/SuggestionDisplayPopover.js.map +1 -1
- package/dist/editor/reviews/commentAi.js +27 -96
- package/dist/editor/reviews/commentAi.js.map +1 -1
- package/dist/editor/reviews/feedbackSelection.js +4 -32
- package/dist/editor/reviews/feedbackSelection.js.map +1 -1
- package/dist/editor/services/agentService.d.ts +0 -15
- package/dist/editor/services/agentService.js +1 -11
- package/dist/editor/services/agentService.js.map +1 -1
- package/dist/editor/services/contentService.d.ts +1 -0
- package/dist/editor/services/contentService.js.map +1 -1
- package/dist/editor/services/reviewsService.d.ts +2 -2
- package/dist/editor/services/reviewsService.js.map +1 -1
- package/dist/editor/settings/SettingsView.js +2 -2
- package/dist/editor/settings/SettingsView.js.map +1 -1
- package/dist/editor/settings/panels/ProjectTemplatesPanel.js +1 -1
- package/dist/editor/settings/panels/ProjectTemplatesPanel.js.map +1 -1
- package/dist/editor/settings/panels/ProvidersPanel.js +3 -2
- package/dist/editor/settings/panels/ProvidersPanel.js.map +1 -1
- package/dist/editor/sidebar/MorePanelsButton.js +1 -1
- package/dist/editor/sidebar/MorePanelsButton.js.map +1 -1
- package/dist/editor/sidebar/Workbox.js +1 -1
- package/dist/editor/sidebar/Workbox.js.map +1 -1
- package/dist/editor/ui/IconSelectorDialog.js +1 -1
- package/dist/editor/ui/IconSelectorDialog.js.map +1 -1
- package/dist/editor/ui/SimpleIconButton.d.ts +2 -2
- package/dist/editor/ui/SimpleIconButton.js +1 -1
- package/dist/editor/ui/SimpleIconButton.js.map +1 -1
- package/dist/editor/utils.d.ts +17 -1
- package/dist/editor/utils.js +143 -0
- package/dist/editor/utils.js.map +1 -1
- package/dist/editor/version-diff/versionDiffTargets.d.ts +8 -3
- package/dist/editor/version-diff/versionDiffTargets.js +94 -37
- package/dist/editor/version-diff/versionDiffTargets.js.map +1 -1
- package/dist/editor/views/MediaFolderEditView.js +1 -1
- package/dist/editor/views/MediaFolderEditView.js.map +1 -1
- package/dist/revision.d.ts +2 -2
- package/dist/revision.js +2 -2
- package/dist/splash-screen/DialogWrappers.js +2 -2
- package/dist/splash-screen/DialogWrappers.js.map +1 -1
- package/dist/splash-screen/ModernSplashScreen.js +3 -11
- package/dist/splash-screen/ModernSplashScreen.js.map +1 -1
- package/dist/splash-screen/NewPage.js +5 -7
- package/dist/splash-screen/NewPage.js.map +1 -1
- package/dist/splash-screen/OpenPage.js +3 -5
- package/dist/splash-screen/OpenPage.js.map +1 -1
- package/dist/splash-screen/RecentPages.js +3 -3
- package/dist/splash-screen/RecentPages.js.map +1 -1
- package/package.json +1 -2
- package/styles.css +0 -49
- package/dist/editor/ai/terminal/components/AgentEditHistoryButton.d.ts +0 -5
- package/dist/editor/ai/terminal/components/AgentEditHistoryButton.js +0 -12
- package/dist/editor/ai/terminal/components/AgentEditHistoryButton.js.map +0 -1
- package/dist/editor/bridge/BridgeClient.d.ts +0 -80
- package/dist/editor/bridge/BridgeClient.js +0 -417
- package/dist/editor/bridge/BridgeClient.js.map +0 -1
- package/dist/editor/field-types/useFormFieldCaretPresence.d.ts +0 -13
- package/dist/editor/field-types/useFormFieldCaretPresence.js +0 -92
- package/dist/editor/field-types/useFormFieldCaretPresence.js.map +0 -1
- package/dist/editor/page-editor-chrome/BridgeInlineFormatOverlay.d.ts +0 -6
- package/dist/editor/page-editor-chrome/BridgeInlineFormatOverlay.js +0 -123
- package/dist/editor/page-editor-chrome/BridgeInlineFormatOverlay.js.map +0 -1
- package/dist/editor/page-editor-chrome/useBridgeInlineEditing.d.ts +0 -26
- package/dist/editor/page-editor-chrome/useBridgeInlineEditing.js +0 -222
- package/dist/editor/page-editor-chrome/useBridgeInlineEditing.js.map +0 -1
- package/dist/editor/page-viewer/bridgeFieldPatch.d.ts +0 -20
- package/dist/editor/page-viewer/bridgeFieldPatch.js +0 -33
- package/dist/editor/page-viewer/bridgeFieldPatch.js.map +0 -1
- package/dist/editor/reviews/commentTransientSelection.d.ts +0 -23
- package/dist/editor/reviews/commentTransientSelection.js +0 -7
- package/dist/editor/reviews/commentTransientSelection.js.map +0 -1
- package/dist/editor/reviews/feedbackOrdering.d.ts +0 -5
- package/dist/editor/reviews/feedbackOrdering.js +0 -27
- package/dist/editor/reviews/feedbackOrdering.js.map +0 -1
- package/dist/editor/reviews/suggestedEditState.d.ts +0 -12
- package/dist/editor/reviews/suggestedEditState.js +0 -90
- package/dist/editor/reviews/suggestedEditState.js.map +0 -1
- package/dist/editor/reviews/suggestionDisplayValue.d.ts +0 -43
- package/dist/editor/reviews/suggestionDisplayValue.js +0 -93
- package/dist/editor/reviews/suggestionDisplayValue.js.map +0 -1
|
@@ -0,0 +1,758 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useCallback, useState, useMemo, useRef, } from "react";
|
|
3
|
+
import { useDebouncedCallback } from "use-debounce";
|
|
4
|
+
import { useEditContext, useFieldsEditContext } from "../client/editContext";
|
|
5
|
+
import { generatePageContext, getCachedContext, } from "../services/contextService";
|
|
6
|
+
import { WandSparkles } from "lucide-react";
|
|
7
|
+
import { createRoot } from "react-dom/client";
|
|
8
|
+
import { LicenseFeatures, useFeature } from "../../licensing";
|
|
9
|
+
import { useSlotContext } from "../views/editorSlotContext";
|
|
10
|
+
function InlineCompletionHint({ hintText, isMobile, onAccept, positionStyle, }) {
|
|
11
|
+
return (_jsxs("div", { className: "shadow-[0_4px_14px_color-mix(in_srgb,var(--color-neutral-grey-100)_14%,transparent),0_0_0_1px_color-mix(in_srgb,var(--color-neutral-grey-100)_6%,transparent)]pointer-events-auto border-border-default bg-neutral-grey-5 text-neutral-grey-100 fixed z-95 inline-flex max-w-none cursor-pointer items-center gap-2 rounded-md border px-2.5 py-2 text-xs leading-snug font-medium whitespace-nowrap", style: positionStyle, onClick: isMobile ? onAccept : undefined, role: isMobile ? "button" : undefined, children: [_jsx(WandSparkles, { className: "text-highlight-100 size-3.5 shrink-0", strokeWidth: 2, "aria-hidden": true }), _jsx("span", { children: hintText })] }));
|
|
12
|
+
}
|
|
13
|
+
function getAccessibleIframeDocument(iframe) {
|
|
14
|
+
if (!iframe)
|
|
15
|
+
return null;
|
|
16
|
+
try {
|
|
17
|
+
return iframe.contentDocument || iframe.contentWindow?.document || null;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function getAccessibleIframeWindow(iframe) {
|
|
24
|
+
if (!iframe)
|
|
25
|
+
return null;
|
|
26
|
+
try {
|
|
27
|
+
return iframe.contentWindow;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function getAccessibleIframeSelection(iframe) {
|
|
34
|
+
try {
|
|
35
|
+
return getAccessibleIframeWindow(iframe)?.getSelection() ?? null;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export function useInlineAiCompletion({ pageViewContext, cursorSpanId, isUpdatingRef, }) {
|
|
42
|
+
const editContext = useEditContext();
|
|
43
|
+
const fieldsContext = useFieldsEditContext();
|
|
44
|
+
const slotContext = useSlotContext();
|
|
45
|
+
const canUseAi = useFeature(LicenseFeatures.AI);
|
|
46
|
+
const [currentCompletion, setCurrentCompletion] = useState(null);
|
|
47
|
+
const [, setIsLoading] = useState(false);
|
|
48
|
+
const abortControllerRef = useRef(null);
|
|
49
|
+
const loadingAnimationRef = useRef(null);
|
|
50
|
+
const applyCompletionRef = useRef(null);
|
|
51
|
+
const hintReactRootRef = useRef(null);
|
|
52
|
+
// Clean up hint element on unmount
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
return () => {
|
|
55
|
+
hintReactRootRef.current?.unmount();
|
|
56
|
+
hintReactRootRef.current = null;
|
|
57
|
+
const hintElement = document.getElementById(`${cursorSpanId}-hint`);
|
|
58
|
+
if (hintElement) {
|
|
59
|
+
hintElement.remove();
|
|
60
|
+
}
|
|
61
|
+
if (loadingAnimationRef.current) {
|
|
62
|
+
cancelAnimationFrame(loadingAnimationRef.current);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}, [cursorSpanId]);
|
|
66
|
+
const lastCaretPosRef = useRef(null);
|
|
67
|
+
// Simple function to track caret position without inserting spans
|
|
68
|
+
const positionCursorSpan = useCallback(() => {
|
|
69
|
+
if (isUpdatingRef.current)
|
|
70
|
+
return;
|
|
71
|
+
isUpdatingRef.current = true;
|
|
72
|
+
try {
|
|
73
|
+
const iframeWindow = getAccessibleIframeWindow(pageViewContext.editorIframe);
|
|
74
|
+
const iframeDocument = getAccessibleIframeDocument(pageViewContext.editorIframe);
|
|
75
|
+
const editableElement = fieldsContext?.inlineEditingFieldElement;
|
|
76
|
+
if (!iframeWindow || !iframeDocument || !editableElement)
|
|
77
|
+
return;
|
|
78
|
+
// Get current selection
|
|
79
|
+
const selection = iframeWindow.getSelection();
|
|
80
|
+
if (!selection || selection.rangeCount === 0)
|
|
81
|
+
return;
|
|
82
|
+
const range = selection.getRangeAt(0);
|
|
83
|
+
// Only proceed if selection is within our editing element
|
|
84
|
+
if (!editableElement.contains(range.commonAncestorContainer))
|
|
85
|
+
return;
|
|
86
|
+
// Save detailed information about the cursor position
|
|
87
|
+
const currentNode = range.startContainer;
|
|
88
|
+
const currentOffset = range.startOffset;
|
|
89
|
+
// Create a temporary range to get text up to cursor
|
|
90
|
+
const tempRange = iframeDocument.createRange();
|
|
91
|
+
tempRange.selectNodeContents(editableElement);
|
|
92
|
+
tempRange.setEnd(currentNode, currentOffset);
|
|
93
|
+
const textUpToCursor = tempRange.toString();
|
|
94
|
+
// Check if cursor position has changed
|
|
95
|
+
const cursorMoved = !lastCaretPosRef.current ||
|
|
96
|
+
lastCaretPosRef.current.node !== currentNode ||
|
|
97
|
+
lastCaretPosRef.current.offset !== currentOffset ||
|
|
98
|
+
textUpToCursor.length !== (lastCaretPosRef.current.textLength || 0);
|
|
99
|
+
if (cursorMoved) {
|
|
100
|
+
// Reset completion if cursor position changed
|
|
101
|
+
lastCaretPosRef.current = {
|
|
102
|
+
node: currentNode,
|
|
103
|
+
offset: currentOffset,
|
|
104
|
+
textLength: textUpToCursor.length,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
// Save a copy of the current range
|
|
108
|
+
const originalRange = range.cloneRange();
|
|
109
|
+
// Remove any existing cursor spans
|
|
110
|
+
const existingSpan = iframeDocument.getElementById(cursorSpanId);
|
|
111
|
+
if (existingSpan) {
|
|
112
|
+
existingSpan.parentNode?.removeChild(existingSpan);
|
|
113
|
+
}
|
|
114
|
+
// Create our cursor span
|
|
115
|
+
const cursorSpan = existingSpan ?? iframeDocument.createElement("span");
|
|
116
|
+
cursorSpan.id = cursorSpanId;
|
|
117
|
+
cursorSpan.style.fontWeight = "bold";
|
|
118
|
+
cursorSpan.style.display = "inline";
|
|
119
|
+
cursorSpan.style.pointerEvents = "none";
|
|
120
|
+
cursorSpan.contentEditable = "false";
|
|
121
|
+
cursorSpan.setAttribute("data-cursor-indicator", "true");
|
|
122
|
+
// Get a working range for insertion
|
|
123
|
+
const workingRange = range.cloneRange();
|
|
124
|
+
workingRange.collapse(true);
|
|
125
|
+
// Insert the span at cursor position
|
|
126
|
+
workingRange.insertNode(cursorSpan);
|
|
127
|
+
// Restore original selection
|
|
128
|
+
selection.removeAllRanges();
|
|
129
|
+
selection.addRange(originalRange);
|
|
130
|
+
// setTimeout(() => {
|
|
131
|
+
// getCompletionDebounced();
|
|
132
|
+
// }, 100);
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
console.error("Error updating cursor:", error);
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
setTimeout(() => {
|
|
139
|
+
isUpdatingRef.current = false;
|
|
140
|
+
}, 10);
|
|
141
|
+
}
|
|
142
|
+
}, [
|
|
143
|
+
pageViewContext.editorIframe,
|
|
144
|
+
fieldsContext?.inlineEditingFieldElement,
|
|
145
|
+
cursorSpanId,
|
|
146
|
+
isUpdatingRef,
|
|
147
|
+
]);
|
|
148
|
+
// Extracts the text up to the cursor position in the editable element
|
|
149
|
+
const getContentUpToCursor = useCallback((element) => {
|
|
150
|
+
const selection = getAccessibleIframeSelection(pageViewContext.editorIframe);
|
|
151
|
+
if (!element || !selection || selection.rangeCount === 0)
|
|
152
|
+
return null;
|
|
153
|
+
const range = selection.getRangeAt(0);
|
|
154
|
+
const cursorPosition = range.startOffset;
|
|
155
|
+
let contentUpToCursor = "";
|
|
156
|
+
if (element.childNodes.length === 0) {
|
|
157
|
+
contentUpToCursor = "";
|
|
158
|
+
}
|
|
159
|
+
else if (element.childNodes.length === 1 &&
|
|
160
|
+
element.firstChild?.nodeType === Node.TEXT_NODE) {
|
|
161
|
+
const full = element.innerText || "";
|
|
162
|
+
contentUpToCursor = full.substring(0, Math.min(cursorPosition, full.length));
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
const tempRange = document.createRange();
|
|
166
|
+
tempRange.selectNodeContents(element);
|
|
167
|
+
tempRange.setEnd(range.startContainer, range.startOffset);
|
|
168
|
+
contentUpToCursor = tempRange.toString();
|
|
169
|
+
}
|
|
170
|
+
return contentUpToCursor;
|
|
171
|
+
}, [pageViewContext.editorIframe]);
|
|
172
|
+
// Loading animation with three dots changing color
|
|
173
|
+
const startLoadingAnimation = useCallback(() => {
|
|
174
|
+
const doc = getAccessibleIframeDocument(pageViewContext.editorIframe);
|
|
175
|
+
const span = doc?.getElementById(cursorSpanId);
|
|
176
|
+
if (!doc || !span)
|
|
177
|
+
return;
|
|
178
|
+
span.innerHTML = "";
|
|
179
|
+
span.style.display = "inline-flex";
|
|
180
|
+
span.style.gap = "4px";
|
|
181
|
+
span.style.alignItems = "center";
|
|
182
|
+
for (let i = 0; i < 3; i++) {
|
|
183
|
+
const dot = doc.createElement("span");
|
|
184
|
+
dot.textContent = "•";
|
|
185
|
+
dot.style.transition = "color 0.3s ease";
|
|
186
|
+
dot.style.color = "var(--color-border-default)";
|
|
187
|
+
dot.style.fontSize = "16px";
|
|
188
|
+
span.appendChild(dot);
|
|
189
|
+
}
|
|
190
|
+
let step = 0;
|
|
191
|
+
const animate = () => {
|
|
192
|
+
const dots = span.querySelectorAll("span");
|
|
193
|
+
dots.forEach((dot, index) => {
|
|
194
|
+
if (index === step % 3) {
|
|
195
|
+
dot.style.color = "var(--color-neutral-grey-50)";
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
dot.style.color = "var(--color-border-default)";
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
step++;
|
|
202
|
+
loadingAnimationRef.current = requestAnimationFrame(() => {
|
|
203
|
+
if (step % 15 === 0) {
|
|
204
|
+
animate();
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
loadingAnimationRef.current = requestAnimationFrame(animate);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
};
|
|
211
|
+
animate();
|
|
212
|
+
}, [pageViewContext.editorIframe, cursorSpanId]);
|
|
213
|
+
const stopLoadingAnimation = useCallback(() => {
|
|
214
|
+
if (loadingAnimationRef.current) {
|
|
215
|
+
cancelAnimationFrame(loadingAnimationRef.current);
|
|
216
|
+
loadingAnimationRef.current = null;
|
|
217
|
+
}
|
|
218
|
+
}, []);
|
|
219
|
+
const getCompletion = useCallback(async (element, isManualTrigger = false) => {
|
|
220
|
+
const contentUpToCursor = getContentUpToCursor(element);
|
|
221
|
+
if (!contentUpToCursor?.trim())
|
|
222
|
+
return null;
|
|
223
|
+
if (abortControllerRef.current) {
|
|
224
|
+
abortControllerRef.current.abort();
|
|
225
|
+
}
|
|
226
|
+
abortControllerRef.current = new AbortController();
|
|
227
|
+
const fieldId = element.getAttribute("data-fieldid");
|
|
228
|
+
const itemId = element.getAttribute("data-itemid");
|
|
229
|
+
const language = element.getAttribute("data-language");
|
|
230
|
+
const version = element.getAttribute("data-version");
|
|
231
|
+
if (!fieldId || !itemId || !language || !version)
|
|
232
|
+
return null;
|
|
233
|
+
if (!isManualTrigger) {
|
|
234
|
+
const lastChar = contentUpToCursor.slice(-1);
|
|
235
|
+
if (lastChar !== " " && lastChar !== "\u00A0") {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if (!editContext)
|
|
240
|
+
return null;
|
|
241
|
+
let pageContext = getCachedContext(editContext);
|
|
242
|
+
if (!pageContext) {
|
|
243
|
+
try {
|
|
244
|
+
pageContext = await generatePageContext(editContext, pageViewContext);
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
console.warn("Failed to generate page context:", error);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
let contextString = "";
|
|
251
|
+
if (pageContext) {
|
|
252
|
+
contextString = `Page Name: ${pageContext.pageTitle} (${pageContext.pageType})\n PageSummary: ${pageContext.abstract}`;
|
|
253
|
+
}
|
|
254
|
+
setIsLoading(true);
|
|
255
|
+
startLoadingAnimation();
|
|
256
|
+
try {
|
|
257
|
+
const endpoint = `/parhelia/agent/GetTextCompletion`;
|
|
258
|
+
const response = await fetch(endpoint, {
|
|
259
|
+
method: "POST",
|
|
260
|
+
headers: {
|
|
261
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
262
|
+
},
|
|
263
|
+
body: new URLSearchParams({
|
|
264
|
+
textToComplete: contentUpToCursor,
|
|
265
|
+
contextInfo: contextString,
|
|
266
|
+
}).toString(),
|
|
267
|
+
});
|
|
268
|
+
const data = await response.json();
|
|
269
|
+
return data.completion || null;
|
|
270
|
+
}
|
|
271
|
+
catch (error) {
|
|
272
|
+
if (error instanceof Error && error.name !== "AbortError") {
|
|
273
|
+
console.error("Error getting completion:", error);
|
|
274
|
+
}
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
finally {
|
|
278
|
+
setIsLoading(false);
|
|
279
|
+
stopLoadingAnimation();
|
|
280
|
+
}
|
|
281
|
+
}, [
|
|
282
|
+
getContentUpToCursor,
|
|
283
|
+
editContext,
|
|
284
|
+
pageViewContext,
|
|
285
|
+
startLoadingAnimation,
|
|
286
|
+
stopLoadingAnimation,
|
|
287
|
+
]);
|
|
288
|
+
// Debounced AI call: recompute the sentence, call getCompletion, and extract only the "tail" for the ghost text
|
|
289
|
+
const getCompletionDebounced = useDebouncedCallback(async (isManualTrigger = false) => {
|
|
290
|
+
const el = fieldsContext?.inlineEditingFieldElement;
|
|
291
|
+
if (!el)
|
|
292
|
+
return;
|
|
293
|
+
// 1) Recompute the exact sentence at this moment
|
|
294
|
+
const full = getContentUpToCursor(el) || "";
|
|
295
|
+
const sentence = full.split(/[.?!]\s*/).pop() || "";
|
|
296
|
+
// 2) Ask AI for a completion
|
|
297
|
+
const rawSuggestion = await getCompletion(el, isManualTrigger);
|
|
298
|
+
if (!rawSuggestion) {
|
|
299
|
+
setCurrentCompletion(null);
|
|
300
|
+
clearCursorSpan();
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
// 3) Strip off the already-typed sentence to leave just the "completion tail"
|
|
304
|
+
const suggestion = rawSuggestion.startsWith(sentence)
|
|
305
|
+
? rawSuggestion
|
|
306
|
+
: sentence + rawSuggestion;
|
|
307
|
+
setCurrentCompletion(suggestion);
|
|
308
|
+
updateCursorSpan(suggestion.substring(sentence.length));
|
|
309
|
+
}, 250);
|
|
310
|
+
// Inserts or clears the ghost text inside the cursor span
|
|
311
|
+
const updateCursorSpan = useCallback((text) => {
|
|
312
|
+
const doc = getAccessibleIframeDocument(pageViewContext.editorIframe);
|
|
313
|
+
const span = doc?.getElementById(cursorSpanId);
|
|
314
|
+
if (!doc || !span)
|
|
315
|
+
return;
|
|
316
|
+
// Update the completion text
|
|
317
|
+
if (text) {
|
|
318
|
+
span.textContent = text;
|
|
319
|
+
span.style.color = "var(--color-neutral-grey-50)";
|
|
320
|
+
span.style.fontStyle = "italic";
|
|
321
|
+
// Create or update hint element in the main document
|
|
322
|
+
let hintElement = document.getElementById(`${cursorSpanId}-hint`);
|
|
323
|
+
if (!hintElement) {
|
|
324
|
+
hintElement = document.createElement("div");
|
|
325
|
+
hintElement.id = `${cursorSpanId}-hint`;
|
|
326
|
+
document.body.appendChild(hintElement);
|
|
327
|
+
}
|
|
328
|
+
if (!hintReactRootRef.current) {
|
|
329
|
+
hintElement.replaceChildren();
|
|
330
|
+
hintReactRootRef.current = createRoot(hintElement);
|
|
331
|
+
}
|
|
332
|
+
const hintLabel = editContext?.isMobile
|
|
333
|
+
? "Tap to accept"
|
|
334
|
+
: "Press Tab to accept ⇥";
|
|
335
|
+
const isMobile = editContext?.isMobile ?? false;
|
|
336
|
+
const iframeRect = pageViewContext.editorIframe?.getBoundingClientRect();
|
|
337
|
+
const spanRect = span.getBoundingClientRect();
|
|
338
|
+
let positionStyle = {
|
|
339
|
+
marginLeft: "0.25rem",
|
|
340
|
+
marginRight: "0.25rem",
|
|
341
|
+
};
|
|
342
|
+
if (iframeRect) {
|
|
343
|
+
if (isMobile) {
|
|
344
|
+
const centerX = (spanRect.left + spanRect.right) / 2;
|
|
345
|
+
const gap = 6;
|
|
346
|
+
positionStyle = {
|
|
347
|
+
...positionStyle,
|
|
348
|
+
left: centerX,
|
|
349
|
+
top: spanRect.top - gap,
|
|
350
|
+
transform: "translate(-50%, -100%)",
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
const textLength = span.textContent?.length || 0;
|
|
355
|
+
let left;
|
|
356
|
+
let top;
|
|
357
|
+
if (textLength > 0 && span.firstChild) {
|
|
358
|
+
const range = doc.createRange();
|
|
359
|
+
range.setStart(span.firstChild, Math.max(0, textLength - 1));
|
|
360
|
+
range.setEnd(span.firstChild, textLength);
|
|
361
|
+
const rangeRect = range.getBoundingClientRect();
|
|
362
|
+
left = iframeRect.left + rangeRect.right;
|
|
363
|
+
top = iframeRect.top + rangeRect.top;
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
left = iframeRect.left + spanRect.right;
|
|
367
|
+
top = iframeRect.top + spanRect.top;
|
|
368
|
+
}
|
|
369
|
+
positionStyle = {
|
|
370
|
+
...positionStyle,
|
|
371
|
+
left,
|
|
372
|
+
top,
|
|
373
|
+
transform: undefined,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
hintReactRootRef.current.render(_jsx(InlineCompletionHint, { hintText: hintLabel, isMobile: isMobile, onAccept: () => applyCompletionRef.current?.(), positionStyle: positionStyle }));
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
span.textContent = "";
|
|
381
|
+
// Remove hint element if it exists
|
|
382
|
+
const hintElement = document.getElementById(`${cursorSpanId}-hint`);
|
|
383
|
+
if (hintElement) {
|
|
384
|
+
hintReactRootRef.current?.unmount();
|
|
385
|
+
hintReactRootRef.current = null;
|
|
386
|
+
hintElement.remove();
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}, [pageViewContext.editorIframe, cursorSpanId, editContext?.isMobile]);
|
|
390
|
+
const clearCursorSpan = useCallback(() => {
|
|
391
|
+
const doc = getAccessibleIframeDocument(pageViewContext.editorIframe);
|
|
392
|
+
if (!doc)
|
|
393
|
+
return;
|
|
394
|
+
// Clear the completion text
|
|
395
|
+
updateCursorSpan("");
|
|
396
|
+
}, [pageViewContext.editorIframe, updateCursorSpan]);
|
|
397
|
+
// Handle keydown events for cursor movement (arrow keys, etc.)
|
|
398
|
+
useEffect(() => {
|
|
399
|
+
const keyHandler = (e) => {
|
|
400
|
+
if (e.key === "ArrowLeft" ||
|
|
401
|
+
e.key === "ArrowRight" ||
|
|
402
|
+
e.key === "ArrowUp" ||
|
|
403
|
+
e.key === "ArrowDown" ||
|
|
404
|
+
e.key === " " ||
|
|
405
|
+
e.key === "Tab" ||
|
|
406
|
+
e.key === "End" ||
|
|
407
|
+
e.key === "Backspace" ||
|
|
408
|
+
e.key === "Delete") {
|
|
409
|
+
setCurrentCompletion(null);
|
|
410
|
+
clearCursorSpan();
|
|
411
|
+
setTimeout(() => {
|
|
412
|
+
if (!isUpdatingRef.current) {
|
|
413
|
+
positionCursorSpan();
|
|
414
|
+
}
|
|
415
|
+
}, 10);
|
|
416
|
+
if (e.key === "ArrowRight" || e.key === "Delete") {
|
|
417
|
+
const cursorSpan = getAccessibleIframeDocument(pageViewContext.editorIframe)?.getElementById(cursorSpanId);
|
|
418
|
+
if (cursorSpan) {
|
|
419
|
+
const originalDisplay = cursorSpan.style.display;
|
|
420
|
+
cursorSpan.style.display = "none";
|
|
421
|
+
setTimeout(() => {
|
|
422
|
+
if (cursorSpan.parentNode) {
|
|
423
|
+
cursorSpan.style.display = originalDisplay;
|
|
424
|
+
}
|
|
425
|
+
}, 0);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
const doc = getAccessibleIframeDocument(pageViewContext.editorIframe);
|
|
431
|
+
if (!doc)
|
|
432
|
+
return;
|
|
433
|
+
doc.addEventListener("keydown", keyHandler);
|
|
434
|
+
return () => doc.removeEventListener("keydown", keyHandler);
|
|
435
|
+
}, [
|
|
436
|
+
clearCursorSpan,
|
|
437
|
+
cursorSpanId,
|
|
438
|
+
isUpdatingRef,
|
|
439
|
+
pageViewContext.editorIframe,
|
|
440
|
+
pageViewContext.page,
|
|
441
|
+
positionCursorSpan,
|
|
442
|
+
]);
|
|
443
|
+
// Function to apply the completion (must be before handleInput)
|
|
444
|
+
const applyCompletion = useCallback(() => {
|
|
445
|
+
const iframeWindow = getAccessibleIframeWindow(pageViewContext.editorIframe);
|
|
446
|
+
const iframeDocument = getAccessibleIframeDocument(pageViewContext.editorIframe);
|
|
447
|
+
if (!iframeWindow ||
|
|
448
|
+
!iframeDocument ||
|
|
449
|
+
!fieldsContext?.inlineEditingFieldElement)
|
|
450
|
+
return;
|
|
451
|
+
const cursorSpan = iframeDocument.getElementById(cursorSpanId);
|
|
452
|
+
if (!cursorSpan)
|
|
453
|
+
return;
|
|
454
|
+
const completionToApply = cursorSpan.textContent || "";
|
|
455
|
+
if (!completionToApply)
|
|
456
|
+
return;
|
|
457
|
+
const element = fieldsContext.inlineEditingFieldElement;
|
|
458
|
+
const fieldId = element.getAttribute("data-fieldid");
|
|
459
|
+
const fieldName = element.getAttribute("data-fieldname");
|
|
460
|
+
const itemId = element.getAttribute("data-itemid");
|
|
461
|
+
const language = element.getAttribute("data-language");
|
|
462
|
+
const versionStr = element.getAttribute("data-version");
|
|
463
|
+
const isRichText = element.getAttribute("data-is-richtext") === "true";
|
|
464
|
+
const version = versionStr ? parseInt(versionStr, 10) : undefined;
|
|
465
|
+
if (!fieldId || !itemId || !language || !version)
|
|
466
|
+
return;
|
|
467
|
+
const selection = getAccessibleIframeSelection(pageViewContext.editorIframe);
|
|
468
|
+
if (!selection || selection.rangeCount === 0)
|
|
469
|
+
return;
|
|
470
|
+
const range = selection.getRangeAt(0);
|
|
471
|
+
const tempRange = document.createRange();
|
|
472
|
+
tempRange.selectNodeContents(element);
|
|
473
|
+
tempRange.setEnd(range.startContainer, range.startOffset);
|
|
474
|
+
const textUpToCursor = tempRange.toString();
|
|
475
|
+
const wordBoundaryRegex = /[\s.,;:!?"'()[\]{}<>/|=+\-*&^%$#@~`](?=[^\s.,;:!?"'()[\]{}<>/|=+\-*&^%$#@~`]*$)/;
|
|
476
|
+
const match = textUpToCursor.match(wordBoundaryRegex);
|
|
477
|
+
const lastWordBoundaryIndex = match && match.index !== undefined ? match.index + 1 : 0;
|
|
478
|
+
const currentPartialWord = textUpToCursor
|
|
479
|
+
.substring(lastWordBoundaryIndex)
|
|
480
|
+
.trim();
|
|
481
|
+
const isOverlapping = currentPartialWord.length > 0 &&
|
|
482
|
+
completionToApply
|
|
483
|
+
.toLowerCase()
|
|
484
|
+
.startsWith(currentPartialWord.toLowerCase());
|
|
485
|
+
if (isOverlapping) {
|
|
486
|
+
const wordRange = document.createRange();
|
|
487
|
+
const startContainer = range.startContainer;
|
|
488
|
+
const startOffset = range.startOffset - currentPartialWord.length;
|
|
489
|
+
if (startOffset >= 0 && startContainer.nodeType === Node.TEXT_NODE) {
|
|
490
|
+
wordRange.setStart(startContainer, startOffset);
|
|
491
|
+
wordRange.setEnd(range.startContainer, range.startOffset);
|
|
492
|
+
wordRange.deleteContents();
|
|
493
|
+
}
|
|
494
|
+
else {
|
|
495
|
+
if (textUpToCursor.length > 0 && !textUpToCursor.endsWith(" ")) {
|
|
496
|
+
const spaceNode = document.createTextNode(" ");
|
|
497
|
+
range.insertNode(spaceNode);
|
|
498
|
+
range.setStartAfter(spaceNode);
|
|
499
|
+
range.setEndAfter(spaceNode);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
if (textUpToCursor.length > 0 &&
|
|
505
|
+
!textUpToCursor.endsWith(" ") &&
|
|
506
|
+
!textUpToCursor.endsWith("\n") &&
|
|
507
|
+
!/[.!?\-—:;({[\s]$/.test(textUpToCursor)) {
|
|
508
|
+
const spaceNode = document.createTextNode(" ");
|
|
509
|
+
range.insertNode(spaceNode);
|
|
510
|
+
range.setStartAfter(spaceNode);
|
|
511
|
+
range.setEndAfter(spaceNode);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
const textNode = document.createTextNode(completionToApply);
|
|
515
|
+
range.insertNode(textNode);
|
|
516
|
+
range.setStartAfter(textNode);
|
|
517
|
+
range.setEndAfter(textNode);
|
|
518
|
+
selection.removeAllRanges();
|
|
519
|
+
selection.addRange(range);
|
|
520
|
+
setCurrentCompletion(null);
|
|
521
|
+
clearCursorSpan();
|
|
522
|
+
setTimeout(() => {
|
|
523
|
+
let valueToSave;
|
|
524
|
+
if (isRichText) {
|
|
525
|
+
const clone = element.cloneNode(true);
|
|
526
|
+
const cursorElem = clone.querySelector(`#${cursorSpanId}`);
|
|
527
|
+
if (cursorElem)
|
|
528
|
+
cursorElem.parentNode?.removeChild(cursorElem);
|
|
529
|
+
const ownerDoc = clone.ownerDocument || document;
|
|
530
|
+
const walker = ownerDoc.createTreeWalker(clone, NodeFilter.SHOW_TEXT);
|
|
531
|
+
const toClean = [];
|
|
532
|
+
while (walker.nextNode()) {
|
|
533
|
+
const tn = walker.currentNode;
|
|
534
|
+
if (tn.nodeValue && tn.nodeValue.includes("\u200B"))
|
|
535
|
+
toClean.push(tn);
|
|
536
|
+
}
|
|
537
|
+
toClean.forEach((tn) => (tn.nodeValue = tn.nodeValue?.replaceAll("\u200B", "") || ""));
|
|
538
|
+
valueToSave = clone.innerHTML;
|
|
539
|
+
}
|
|
540
|
+
else {
|
|
541
|
+
valueToSave = (element.innerText || "").replaceAll("\u200B", "");
|
|
542
|
+
}
|
|
543
|
+
editContext?.operations.editField({
|
|
544
|
+
field: {
|
|
545
|
+
fieldId,
|
|
546
|
+
fieldName: fieldName ?? undefined,
|
|
547
|
+
item: { id: itemId, language, version },
|
|
548
|
+
},
|
|
549
|
+
originatingSlotId: slotContext?.slotId,
|
|
550
|
+
refresh: "none",
|
|
551
|
+
value: valueToSave,
|
|
552
|
+
});
|
|
553
|
+
}, 0);
|
|
554
|
+
}, [
|
|
555
|
+
pageViewContext.editorIframe,
|
|
556
|
+
fieldsContext?.inlineEditingFieldElement,
|
|
557
|
+
cursorSpanId,
|
|
558
|
+
clearCursorSpan,
|
|
559
|
+
editContext?.operations,
|
|
560
|
+
slotContext?.slotId,
|
|
561
|
+
]);
|
|
562
|
+
applyCompletionRef.current = applyCompletion;
|
|
563
|
+
// Manual completion trigger (non-debounced)
|
|
564
|
+
const getCompletionManual = useCallback(async () => {
|
|
565
|
+
if (!canUseAi)
|
|
566
|
+
return;
|
|
567
|
+
const el = fieldsContext?.inlineEditingFieldElement;
|
|
568
|
+
if (!el)
|
|
569
|
+
return;
|
|
570
|
+
const full = getContentUpToCursor(el) || "";
|
|
571
|
+
const sentence = full.split(/[.?!]\s*/).pop() || "";
|
|
572
|
+
const rawSuggestion = await getCompletion(el, true);
|
|
573
|
+
if (!rawSuggestion) {
|
|
574
|
+
setCurrentCompletion(null);
|
|
575
|
+
clearCursorSpan();
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
const suggestion = rawSuggestion.startsWith(sentence)
|
|
579
|
+
? rawSuggestion
|
|
580
|
+
: sentence + rawSuggestion;
|
|
581
|
+
setCurrentCompletion(suggestion);
|
|
582
|
+
updateCursorSpan(suggestion.substring(sentence.length));
|
|
583
|
+
}, [
|
|
584
|
+
fieldsContext?.inlineEditingFieldElement,
|
|
585
|
+
canUseAi,
|
|
586
|
+
getContentUpToCursor,
|
|
587
|
+
getCompletion,
|
|
588
|
+
clearCursorSpan,
|
|
589
|
+
updateCursorSpan,
|
|
590
|
+
]);
|
|
591
|
+
// On every input: either reuse the existing suggestion or fire a new one
|
|
592
|
+
const handleInput = useCallback((e) => {
|
|
593
|
+
if (!canUseAi) {
|
|
594
|
+
setCurrentCompletion(null);
|
|
595
|
+
clearCursorSpan();
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
const el = fieldsContext?.inlineEditingFieldElement;
|
|
599
|
+
if (!el)
|
|
600
|
+
return;
|
|
601
|
+
// Clear completion when Escape is pressed
|
|
602
|
+
if (e.key === "Escape") {
|
|
603
|
+
e.preventDefault();
|
|
604
|
+
e.stopPropagation();
|
|
605
|
+
setCurrentCompletion(null);
|
|
606
|
+
clearCursorSpan();
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
// Request new completion when Ctrl+Space is pressed
|
|
610
|
+
if (e.key === " " && (e.ctrlKey || e.metaKey)) {
|
|
611
|
+
e.preventDefault();
|
|
612
|
+
e.stopPropagation();
|
|
613
|
+
setCurrentCompletion(null);
|
|
614
|
+
clearCursorSpan();
|
|
615
|
+
getCompletionManual();
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
// Apply completion when Tab is pressed
|
|
619
|
+
if (e.key === "Tab") {
|
|
620
|
+
// Check if there's an actual completion in the cursor span
|
|
621
|
+
const cursorSpan = getAccessibleIframeDocument(pageViewContext.editorIframe)?.getElementById(cursorSpanId);
|
|
622
|
+
if (cursorSpan &&
|
|
623
|
+
cursorSpan.textContent &&
|
|
624
|
+
cursorSpan.textContent.trim() !== "") {
|
|
625
|
+
e.preventDefault();
|
|
626
|
+
e.stopPropagation();
|
|
627
|
+
applyCompletion();
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
const full = getContentUpToCursor(el) || "";
|
|
632
|
+
const sentence = full.split(/[.?!]\s*/).pop() || "";
|
|
633
|
+
// If we already have a suggestion, check if the user is still typing along it
|
|
634
|
+
// Normalize spaces to handle both regular and non-breaking spaces
|
|
635
|
+
const normalizedSentence = sentence.replace(/\s/g, " ");
|
|
636
|
+
const normalizedCompletion = currentCompletion?.replace(/\s/g, " ");
|
|
637
|
+
if (normalizedCompletion &&
|
|
638
|
+
normalizedCompletion.startsWith(normalizedSentence)) {
|
|
639
|
+
// user is still matching the suggestion → update the remaining tail
|
|
640
|
+
const remaining = normalizedCompletion.slice(normalizedSentence.length);
|
|
641
|
+
updateCursorSpan(remaining);
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
// else: user diverged → drop it
|
|
645
|
+
setCurrentCompletion(null);
|
|
646
|
+
clearCursorSpan();
|
|
647
|
+
// No active suggestion → fire new request once they've typed 2+ words
|
|
648
|
+
const words = sentence.trim().split(/\s+/).filter(Boolean);
|
|
649
|
+
// Check if cursor is at the end of the text
|
|
650
|
+
const isAtEnd = () => {
|
|
651
|
+
const el = fieldsContext?.inlineEditingFieldElement;
|
|
652
|
+
if (!el)
|
|
653
|
+
return false;
|
|
654
|
+
const selection = getAccessibleIframeSelection(pageViewContext.editorIframe);
|
|
655
|
+
if (!selection || selection.rangeCount === 0)
|
|
656
|
+
return false;
|
|
657
|
+
const range = selection.getRangeAt(0);
|
|
658
|
+
// Create a range for the whole editable element
|
|
659
|
+
const fullRange = document.createRange();
|
|
660
|
+
fullRange.selectNodeContents(el);
|
|
661
|
+
// Get the full text content
|
|
662
|
+
const fullText = el.textContent || "";
|
|
663
|
+
// Get text up to cursor
|
|
664
|
+
const tempRange = document.createRange();
|
|
665
|
+
tempRange.selectNodeContents(el);
|
|
666
|
+
tempRange.setEnd(range.startContainer, range.startOffset);
|
|
667
|
+
const textUpToCursor = tempRange.toString();
|
|
668
|
+
// Cursor is at the end if the text up to cursor equals the full text
|
|
669
|
+
// (accounting for whitespace differences)
|
|
670
|
+
return (textUpToCursor.trim() === fullText.trim() ||
|
|
671
|
+
(fullText.trim().startsWith(textUpToCursor.trim()) &&
|
|
672
|
+
fullText.trim().length === textUpToCursor.trim().length));
|
|
673
|
+
};
|
|
674
|
+
if (words.length >= 2 && isAtEnd()) {
|
|
675
|
+
getCompletionDebounced();
|
|
676
|
+
}
|
|
677
|
+
else {
|
|
678
|
+
setCurrentCompletion(null);
|
|
679
|
+
clearCursorSpan();
|
|
680
|
+
}
|
|
681
|
+
}, [
|
|
682
|
+
fieldsContext?.inlineEditingFieldElement,
|
|
683
|
+
getContentUpToCursor,
|
|
684
|
+
currentCompletion,
|
|
685
|
+
canUseAi,
|
|
686
|
+
clearCursorSpan,
|
|
687
|
+
getCompletionManual,
|
|
688
|
+
pageViewContext.editorIframe,
|
|
689
|
+
cursorSpanId,
|
|
690
|
+
applyCompletion,
|
|
691
|
+
updateCursorSpan,
|
|
692
|
+
getCompletionDebounced,
|
|
693
|
+
]);
|
|
694
|
+
// Wire up the input listener
|
|
695
|
+
useEffect(() => {
|
|
696
|
+
if (!canUseAi || !editContext?.enableCompletions)
|
|
697
|
+
return;
|
|
698
|
+
const el = fieldsContext?.inlineEditingFieldElement;
|
|
699
|
+
if (!el)
|
|
700
|
+
return;
|
|
701
|
+
el.addEventListener("keydown", handleInput);
|
|
702
|
+
return () => el.removeEventListener("keydown", handleInput);
|
|
703
|
+
}, [
|
|
704
|
+
fieldsContext?.inlineEditingFieldElement,
|
|
705
|
+
handleInput,
|
|
706
|
+
canUseAi,
|
|
707
|
+
editContext?.enableCompletions,
|
|
708
|
+
]);
|
|
709
|
+
// Add mouse click handler to update cursor span position
|
|
710
|
+
useEffect(() => {
|
|
711
|
+
if (!canUseAi || !editContext?.enableCompletions)
|
|
712
|
+
return;
|
|
713
|
+
const el = fieldsContext?.inlineEditingFieldElement;
|
|
714
|
+
if (!el)
|
|
715
|
+
return;
|
|
716
|
+
const handleMouseUp = () => {
|
|
717
|
+
setTimeout(() => {
|
|
718
|
+
if (!isUpdatingRef.current) {
|
|
719
|
+
positionCursorSpan();
|
|
720
|
+
// Clear existing completion when cursor position changes via mouse
|
|
721
|
+
setCurrentCompletion(null);
|
|
722
|
+
clearCursorSpan();
|
|
723
|
+
}
|
|
724
|
+
}, 10);
|
|
725
|
+
};
|
|
726
|
+
el.addEventListener("mouseup", handleMouseUp);
|
|
727
|
+
return () => el.removeEventListener("mouseup", handleMouseUp);
|
|
728
|
+
}, [
|
|
729
|
+
fieldsContext?.inlineEditingFieldElement,
|
|
730
|
+
canUseAi,
|
|
731
|
+
editContext?.enableCompletions,
|
|
732
|
+
isUpdatingRef,
|
|
733
|
+
positionCursorSpan,
|
|
734
|
+
clearCursorSpan,
|
|
735
|
+
]);
|
|
736
|
+
// Clean up abort controller on unmount
|
|
737
|
+
useEffect(() => {
|
|
738
|
+
return () => {
|
|
739
|
+
if (abortControllerRef.current) {
|
|
740
|
+
abortControllerRef.current.abort();
|
|
741
|
+
abortControllerRef.current = null;
|
|
742
|
+
}
|
|
743
|
+
if (loadingAnimationRef.current) {
|
|
744
|
+
cancelAnimationFrame(loadingAnimationRef.current);
|
|
745
|
+
loadingAnimationRef.current = null;
|
|
746
|
+
}
|
|
747
|
+
};
|
|
748
|
+
}, []);
|
|
749
|
+
// Exposed manual trigger (if needed)
|
|
750
|
+
return useMemo(() => () => {
|
|
751
|
+
setCurrentCompletion(null);
|
|
752
|
+
clearCursorSpan();
|
|
753
|
+
if (canUseAi) {
|
|
754
|
+
getCompletionManual();
|
|
755
|
+
}
|
|
756
|
+
}, [canUseAi, clearCursorSpan, getCompletionManual]);
|
|
757
|
+
}
|
|
758
|
+
//# sourceMappingURL=useInlineAICompletion.js.map
|