@memberjunction/ng-conversations 6.2.0-edge.2 → 6.2.0-edge.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -7
- package/dist/lib/components/collection/collection-view.component.d.ts.map +1 -1
- package/dist/lib/components/collection/collection-view.component.js +1 -0
- package/dist/lib/components/collection/collection-view.component.js.map +1 -1
- package/dist/lib/components/composer/ai-composer.component.d.ts +14 -2
- package/dist/lib/components/composer/ai-composer.component.d.ts.map +1 -1
- package/dist/lib/components/composer/ai-composer.component.js +35 -4
- package/dist/lib/components/composer/ai-composer.component.js.map +1 -1
- package/dist/lib/components/conversation/conversation-chat-area.component.d.ts +632 -55
- package/dist/lib/components/conversation/conversation-chat-area.component.d.ts.map +1 -1
- package/dist/lib/components/conversation/conversation-chat-area.component.js +2748 -624
- package/dist/lib/components/conversation/conversation-chat-area.component.js.map +1 -1
- package/dist/lib/components/conversation/forks-popover.component.d.ts +37 -0
- package/dist/lib/components/conversation/forks-popover.component.d.ts.map +1 -0
- package/dist/lib/components/conversation/forks-popover.component.js +164 -0
- package/dist/lib/components/conversation/forks-popover.component.js.map +1 -0
- package/dist/lib/components/export/export-modal.component.d.ts +7 -2
- package/dist/lib/components/export/export-modal.component.d.ts.map +1 -1
- package/dist/lib/components/export/export-modal.component.js +16 -3
- package/dist/lib/components/export/export-modal.component.js.map +1 -1
- package/dist/lib/components/message/conversation-message-rating.component.d.ts.map +1 -1
- package/dist/lib/components/message/conversation-message-rating.component.js +11 -9
- package/dist/lib/components/message/conversation-message-rating.component.js.map +1 -1
- package/dist/lib/components/message/message-input.component.d.ts +121 -11
- package/dist/lib/components/message/message-input.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-input.component.js +307 -106
- package/dist/lib/components/message/message-input.component.js.map +1 -1
- package/dist/lib/components/message/message-item.component.d.ts +178 -5
- package/dist/lib/components/message/message-item.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-item.component.js +919 -306
- package/dist/lib/components/message/message-item.component.js.map +1 -1
- package/dist/lib/components/message/message-list.component.d.ts +92 -21
- package/dist/lib/components/message/message-list.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-list.component.js +337 -52
- package/dist/lib/components/message/message-list.component.js.map +1 -1
- package/dist/lib/components/realtime/avatar/realtime-avatar-channel.d.ts +34 -0
- package/dist/lib/components/realtime/avatar/realtime-avatar-channel.d.ts.map +1 -0
- package/dist/lib/components/realtime/avatar/realtime-avatar-channel.js +75 -0
- package/dist/lib/components/realtime/avatar/realtime-avatar-channel.js.map +1 -0
- package/dist/lib/components/realtime/avatar/realtime-avatar-surface.component.d.ts +80 -0
- package/dist/lib/components/realtime/avatar/realtime-avatar-surface.component.d.ts.map +1 -0
- package/dist/lib/components/realtime/avatar/realtime-avatar-surface.component.js +202 -0
- package/dist/lib/components/realtime/avatar/realtime-avatar-surface.component.js.map +1 -0
- package/dist/lib/components/realtime/capture/realtime-camera-check-card.component.d.ts +45 -0
- package/dist/lib/components/realtime/capture/realtime-camera-check-card.component.d.ts.map +1 -0
- package/dist/lib/components/realtime/capture/realtime-camera-check-card.component.js +123 -0
- package/dist/lib/components/realtime/capture/realtime-camera-check-card.component.js.map +1 -0
- package/dist/lib/components/realtime/capture/realtime-capture-channel.d.ts +90 -0
- package/dist/lib/components/realtime/capture/realtime-capture-channel.d.ts.map +1 -0
- package/dist/lib/components/realtime/capture/realtime-capture-channel.js +223 -0
- package/dist/lib/components/realtime/capture/realtime-capture-channel.js.map +1 -0
- package/dist/lib/components/realtime/capture/realtime-capture-model.d.ts +98 -0
- package/dist/lib/components/realtime/capture/realtime-capture-model.d.ts.map +1 -0
- package/dist/lib/components/realtime/capture/realtime-capture-model.js +161 -0
- package/dist/lib/components/realtime/capture/realtime-capture-model.js.map +1 -0
- package/dist/lib/components/realtime/capture/realtime-capture-surface.component.d.ts +48 -0
- package/dist/lib/components/realtime/capture/realtime-capture-surface.component.d.ts.map +1 -0
- package/dist/lib/components/realtime/capture/realtime-capture-surface.component.js +205 -0
- package/dist/lib/components/realtime/capture/realtime-capture-surface.component.js.map +1 -0
- package/dist/lib/components/realtime/channels/client-context-channel.d.ts +19 -2
- package/dist/lib/components/realtime/channels/client-context-channel.d.ts.map +1 -1
- package/dist/lib/components/realtime/channels/client-context-channel.js +83 -9
- package/dist/lib/components/realtime/channels/client-context-channel.js.map +1 -1
- package/dist/lib/components/realtime/channels/realtime-channel-pane.component.d.ts +30 -9
- package/dist/lib/components/realtime/channels/realtime-channel-pane.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/channels/realtime-channel-pane.component.js +78 -14
- package/dist/lib/components/realtime/channels/realtime-channel-pane.component.js.map +1 -1
- package/dist/lib/components/realtime/identity-verification/identity-verification-channel.d.ts +71 -0
- package/dist/lib/components/realtime/identity-verification/identity-verification-channel.d.ts.map +1 -0
- package/dist/lib/components/realtime/identity-verification/identity-verification-channel.js +259 -0
- package/dist/lib/components/realtime/identity-verification/identity-verification-channel.js.map +1 -0
- package/dist/lib/components/realtime/identity-verification/identity-verification-model.d.ts +129 -0
- package/dist/lib/components/realtime/identity-verification/identity-verification-model.d.ts.map +1 -0
- package/dist/lib/components/realtime/identity-verification/identity-verification-model.js +373 -0
- package/dist/lib/components/realtime/identity-verification/identity-verification-model.js.map +1 -0
- package/dist/lib/components/realtime/identity-verification/identity-verification-surface.component.d.ts +77 -0
- package/dist/lib/components/realtime/identity-verification/identity-verification-surface.component.d.ts.map +1 -0
- package/dist/lib/components/realtime/identity-verification/identity-verification-surface.component.js +420 -0
- package/dist/lib/components/realtime/identity-verification/identity-verification-surface.component.js.map +1 -0
- package/dist/lib/components/realtime/interactive-component/component-contract.d.ts +83 -0
- package/dist/lib/components/realtime/interactive-component/component-contract.d.ts.map +1 -0
- package/dist/lib/components/realtime/interactive-component/component-contract.js +292 -0
- package/dist/lib/components/realtime/interactive-component/component-contract.js.map +1 -0
- package/dist/lib/components/realtime/interactive-component/component-data-state.d.ts +39 -0
- package/dist/lib/components/realtime/interactive-component/component-data-state.d.ts.map +1 -0
- package/dist/lib/components/realtime/interactive-component/component-data-state.js +152 -0
- package/dist/lib/components/realtime/interactive-component/component-data-state.js.map +1 -0
- package/dist/lib/components/realtime/interactive-component/component-instance-engine.d.ts +109 -0
- package/dist/lib/components/realtime/interactive-component/component-instance-engine.d.ts.map +1 -0
- package/dist/lib/components/realtime/interactive-component/component-instance-engine.js +146 -0
- package/dist/lib/components/realtime/interactive-component/component-instance-engine.js.map +1 -0
- package/dist/lib/components/realtime/interactive-component/component-spec-content.d.ts +19 -0
- package/dist/lib/components/realtime/interactive-component/component-spec-content.d.ts.map +1 -0
- package/dist/lib/components/realtime/interactive-component/component-spec-content.js +39 -0
- package/dist/lib/components/realtime/interactive-component/component-spec-content.js.map +1 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-channel.d.ts +183 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-channel.d.ts.map +1 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-channel.js +761 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-channel.js.map +1 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-config.d.ts +25 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-config.d.ts.map +1 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-config.js +57 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-config.js.map +1 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-host.component.d.ts +40 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-host.component.d.ts.map +1 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-host.component.js +97 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-host.component.js.map +1 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-types.d.ts +144 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-types.d.ts.map +1 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-types.js +57 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-types.js.map +1 -0
- package/dist/lib/components/realtime/interactive-component/react-component-handle.d.ts +36 -0
- package/dist/lib/components/realtime/interactive-component/react-component-handle.d.ts.map +1 -0
- package/dist/lib/components/realtime/interactive-component/react-component-handle.js +55 -0
- package/dist/lib/components/realtime/interactive-component/react-component-handle.js.map +1 -0
- package/dist/lib/components/realtime/interactive-component/realtime-interactive-component-surface.component.d.ts +55 -0
- package/dist/lib/components/realtime/interactive-component/realtime-interactive-component-surface.component.d.ts.map +1 -0
- package/dist/lib/components/realtime/interactive-component/realtime-interactive-component-surface.component.js +210 -0
- package/dist/lib/components/realtime/interactive-component/realtime-interactive-component-surface.component.js.map +1 -0
- package/dist/lib/components/realtime/interactive-component/run-view-component-artifact-source.d.ts +32 -0
- package/dist/lib/components/realtime/interactive-component/run-view-component-artifact-source.d.ts.map +1 -0
- package/dist/lib/components/realtime/interactive-component/run-view-component-artifact-source.js +90 -0
- package/dist/lib/components/realtime/interactive-component/run-view-component-artifact-source.js.map +1 -0
- package/dist/lib/components/realtime/media/media-channel.d.ts +7 -0
- package/dist/lib/components/realtime/media/media-channel.d.ts.map +1 -1
- package/dist/lib/components/realtime/media/media-channel.js +28 -1
- package/dist/lib/components/realtime/media/media-channel.js.map +1 -1
- package/dist/lib/components/realtime/realtime-agent-banner.component.d.ts +8 -1
- package/dist/lib/components/realtime/realtime-agent-banner.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-agent-banner.component.js +89 -71
- package/dist/lib/components/realtime/realtime-agent-banner.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-agent-orb.component.d.ts +21 -0
- package/dist/lib/components/realtime/realtime-agent-orb.component.d.ts.map +1 -0
- package/dist/lib/components/realtime/realtime-agent-orb.component.js +49 -0
- package/dist/lib/components/realtime/realtime-agent-orb.component.js.map +1 -0
- package/dist/lib/components/realtime/realtime-agent-picker.component.d.ts +44 -11
- package/dist/lib/components/realtime/realtime-agent-picker.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-agent-picker.component.js +162 -50
- package/dist/lib/components/realtime/realtime-agent-picker.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-audio-visuals.d.ts +8 -2
- package/dist/lib/components/realtime/realtime-audio-visuals.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-audio-visuals.js +14 -18
- package/dist/lib/components/realtime/realtime-audio-visuals.js.map +1 -1
- package/dist/lib/components/realtime/realtime-composer.component.d.ts +42 -2
- package/dist/lib/components/realtime/realtime-composer.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-composer.component.js +137 -102
- package/dist/lib/components/realtime/realtime-composer.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-perception-chip.component.d.ts +71 -0
- package/dist/lib/components/realtime/realtime-perception-chip.component.d.ts.map +1 -0
- package/dist/lib/components/realtime/realtime-perception-chip.component.js +221 -0
- package/dist/lib/components/realtime/realtime-perception-chip.component.js.map +1 -0
- package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts +264 -22
- package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-session-overlay.component.js +851 -135
- package/dist/lib/components/realtime/realtime-session-overlay.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-surface-placement-prefs.d.ts +14 -0
- package/dist/lib/components/realtime/realtime-surface-placement-prefs.d.ts.map +1 -0
- package/dist/lib/components/realtime/realtime-surface-placement-prefs.js +14 -0
- package/dist/lib/components/realtime/realtime-surface-placement-prefs.js.map +1 -0
- package/dist/lib/components/realtime/realtime-surface-stage.model.d.ts +74 -0
- package/dist/lib/components/realtime/realtime-surface-stage.model.d.ts.map +1 -0
- package/dist/lib/components/realtime/realtime-surface-stage.model.js +171 -0
- package/dist/lib/components/realtime/realtime-surface-stage.model.js.map +1 -0
- package/dist/lib/components/realtime/realtime-surface-tabs.component.d.ts +71 -13
- package/dist/lib/components/realtime/realtime-surface-tabs.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-surface-tabs.component.js +232 -73
- package/dist/lib/components/realtime/realtime-surface-tabs.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-surface-tabs.model.d.ts +5 -0
- package/dist/lib/components/realtime/realtime-surface-tabs.model.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-surface-tabs.model.js +8 -0
- package/dist/lib/components/realtime/realtime-surface-tabs.model.js.map +1 -1
- package/dist/lib/components/realtime/realtime-ui-config.d.ts +12 -2
- package/dist/lib/components/realtime/realtime-ui-config.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-ui-config.js +5 -1
- package/dist/lib/components/realtime/realtime-ui-config.js.map +1 -1
- package/dist/lib/components/realtime/remote-browser/remote-browser-channel.d.ts +10 -1
- package/dist/lib/components/realtime/remote-browser/remote-browser-channel.d.ts.map +1 -1
- package/dist/lib/components/realtime/remote-browser/remote-browser-channel.js +42 -2
- package/dist/lib/components/realtime/remote-browser/remote-browser-channel.js.map +1 -1
- package/dist/lib/components/realtime/remote-browser/remote-browser-surface.component.d.ts +20 -3
- package/dist/lib/components/realtime/remote-browser/remote-browser-surface.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/remote-browser/remote-browser-surface.component.js +44 -10
- package/dist/lib/components/realtime/remote-browser/remote-browser-surface.component.js.map +1 -1
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel.d.ts +28 -44
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel.d.ts.map +1 -1
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel.js +76 -162
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel.js.map +1 -1
- package/dist/lib/components/search/search-panel.component.d.ts +2 -0
- package/dist/lib/components/search/search-panel.component.d.ts.map +1 -1
- package/dist/lib/components/search/search-panel.component.js +25 -12
- package/dist/lib/components/search/search-panel.component.js.map +1 -1
- package/dist/lib/components/workspace/conversation-workspace.component.d.ts +22 -31
- package/dist/lib/components/workspace/conversation-workspace.component.d.ts.map +1 -1
- package/dist/lib/components/workspace/conversation-workspace.component.js +50 -63
- package/dist/lib/components/workspace/conversation-workspace.component.js.map +1 -1
- package/dist/lib/conversations.module.d.ts +30 -29
- package/dist/lib/conversations.module.d.ts.map +1 -1
- package/dist/lib/conversations.module.js +27 -5
- package/dist/lib/conversations.module.js.map +1 -1
- package/dist/lib/events/chat-events.d.ts +25 -0
- package/dist/lib/events/chat-events.d.ts.map +1 -1
- package/dist/lib/events/chat-events.js +36 -0
- package/dist/lib/events/chat-events.js.map +1 -1
- package/dist/lib/models/agent-turn.model.d.ts +5 -0
- package/dist/lib/models/agent-turn.model.d.ts.map +1 -1
- package/dist/lib/models/agent-turn.model.js.map +1 -1
- package/dist/lib/services/agent-state.service.d.ts +34 -8
- package/dist/lib/services/agent-state.service.d.ts.map +1 -1
- package/dist/lib/services/agent-state.service.js +82 -29
- package/dist/lib/services/agent-state.service.js.map +1 -1
- package/dist/lib/services/artifact-state.service.d.ts +0 -9
- package/dist/lib/services/artifact-state.service.d.ts.map +1 -1
- package/dist/lib/services/artifact-state.service.js +1 -32
- package/dist/lib/services/artifact-state.service.js.map +1 -1
- package/dist/lib/services/browser-realtime-media-host.d.ts +12 -0
- package/dist/lib/services/browser-realtime-media-host.d.ts.map +1 -1
- package/dist/lib/services/browser-realtime-media-host.js +19 -0
- package/dist/lib/services/browser-realtime-media-host.js.map +1 -1
- package/dist/lib/services/browser-recording-unload-guard.d.ts +30 -0
- package/dist/lib/services/browser-recording-unload-guard.d.ts.map +1 -0
- package/dist/lib/services/browser-recording-unload-guard.js +43 -0
- package/dist/lib/services/browser-recording-unload-guard.js.map +1 -0
- package/dist/lib/services/conversation-agent.service.d.ts +35 -11
- package/dist/lib/services/conversation-agent.service.d.ts.map +1 -1
- package/dist/lib/services/conversation-agent.service.js +65 -20
- package/dist/lib/services/conversation-agent.service.js.map +1 -1
- package/dist/lib/services/conversation-detail-window.store.d.ts +22 -4
- package/dist/lib/services/conversation-detail-window.store.d.ts.map +1 -1
- package/dist/lib/services/conversation-detail-window.store.js +50 -9
- package/dist/lib/services/conversation-detail-window.store.js.map +1 -1
- package/dist/lib/services/conversation-naming.d.ts +72 -22
- package/dist/lib/services/conversation-naming.d.ts.map +1 -1
- package/dist/lib/services/conversation-naming.js +84 -10
- package/dist/lib/services/conversation-naming.js.map +1 -1
- package/dist/lib/services/data-cache.service.d.ts +13 -5
- package/dist/lib/services/data-cache.service.d.ts.map +1 -1
- package/dist/lib/services/data-cache.service.js +39 -19
- package/dist/lib/services/data-cache.service.js.map +1 -1
- package/dist/lib/services/export.service.d.ts +26 -9
- package/dist/lib/services/export.service.d.ts.map +1 -1
- package/dist/lib/services/export.service.js +61 -18
- package/dist/lib/services/export.service.js.map +1 -1
- package/dist/lib/services/realtime-audio-recorder.d.ts +33 -5
- package/dist/lib/services/realtime-audio-recorder.d.ts.map +1 -1
- package/dist/lib/services/realtime-audio-recorder.js +71 -11
- package/dist/lib/services/realtime-audio-recorder.js.map +1 -1
- package/dist/lib/services/realtime-pairing.d.ts +10 -5
- package/dist/lib/services/realtime-pairing.d.ts.map +1 -1
- package/dist/lib/services/realtime-pairing.js +15 -6
- package/dist/lib/services/realtime-pairing.js.map +1 -1
- package/dist/lib/services/realtime-session.service.d.ts +3 -2
- package/dist/lib/services/realtime-session.service.d.ts.map +1 -1
- package/dist/lib/services/realtime-session.service.js +7 -2
- package/dist/lib/services/realtime-session.service.js.map +1 -1
- package/dist/lib/services/search.service.d.ts +7 -2
- package/dist/lib/services/search.service.d.ts.map +1 -1
- package/dist/lib/services/search.service.js +13 -4
- package/dist/lib/services/search.service.js.map +1 -1
- package/dist/lib/util/frame-zone.d.ts +10 -0
- package/dist/lib/util/frame-zone.d.ts.map +1 -0
- package/dist/lib/util/frame-zone.js +10 -0
- package/dist/lib/util/frame-zone.js.map +1 -0
- package/dist/lib/utils/conversation-branching.d.ts +4 -0
- package/dist/lib/utils/conversation-branching.d.ts.map +1 -0
- package/dist/lib/utils/conversation-branching.js +5 -0
- package/dist/lib/utils/conversation-branching.js.map +1 -0
- package/dist/lib/utils/conversation-forks.d.ts +315 -0
- package/dist/lib/utils/conversation-forks.d.ts.map +1 -0
- package/dist/lib/utils/conversation-forks.js +456 -0
- package/dist/lib/utils/conversation-forks.js.map +1 -0
- package/dist/public-api.d.ts +23 -1
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +30 -1
- package/dist/public-api.js.map +1 -1
- package/package.json +37 -32
- package/dist/lib/components/thread/thread-panel.component.d.ts +0 -126
- package/dist/lib/components/thread/thread-panel.component.d.ts.map +0 -1
- package/dist/lib/components/thread/thread-panel.component.js +0 -440
- package/dist/lib/components/thread/thread-panel.component.js.map +0 -1
|
@@ -0,0 +1,761 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { REALTIME_CHANNEL_CONTRACT_VERSION, ValidateJsonAgainstSchemaSubset, } from '@memberjunction/ai-core-plus';
|
|
8
|
+
import { LogError } from '@memberjunction/core';
|
|
9
|
+
import { IsPlainObject, RegisterClass } from '@memberjunction/global';
|
|
10
|
+
import { BaseRealtimeChannelClient, FormatChannelNote, FormatParameterList, } from '@memberjunction/realtime-runtime';
|
|
11
|
+
import { DeriveComponentContract, MergeVerbOffers } from './component-contract';
|
|
12
|
+
import { SummarizeDataState, ToBoundedJson } from './component-data-state';
|
|
13
|
+
import { ComponentInstanceEngine, InstanceLimitError } from './component-instance-engine';
|
|
14
|
+
import { ParseInteractiveComponentConfig } from './interactive-component-config';
|
|
15
|
+
import { ChannelFrameCapture } from '@memberjunction/ng-realtime-channels';
|
|
16
|
+
import { RealtimeInteractiveComponentSurfaceComponent } from './realtime-interactive-component-surface.component';
|
|
17
|
+
import { RunViewComponentArtifactSource } from './run-view-component-artifact-source';
|
|
18
|
+
import { BUILTIN_VERBS, ComponentArtifactError, DEFAULT_INTERACTIVE_COMPONENT_CONFIG, INTERACTIVE_COMPONENT_CHANNEL_KEY, INTERACTIVE_COMPONENT_CLIENT_PLUGIN_CLASS, } from './interactive-component-types';
|
|
19
|
+
/** How long a verb waits for a freshly opened component to finish initializing (the poll interval times the attempt cap). */
|
|
20
|
+
const HANDLE_POLL_INTERVAL_MS = 100;
|
|
21
|
+
const HANDLE_POLL_MAX_ATTEMPTS = 80;
|
|
22
|
+
/** How long after the user (or the agent) acts the data is read once more, because a component's data arrives after the action that asked for it. */
|
|
23
|
+
const DATA_SETTLE_MS = 1200;
|
|
24
|
+
/** The events every instance can report, whatever its spec declares. */
|
|
25
|
+
const BUILTIN_EVENTS = [
|
|
26
|
+
{ Name: 'opened', Description: 'A component was opened. Carries the inputs it was opened with.' },
|
|
27
|
+
{ Name: 'closed', Description: 'A component was closed.' },
|
|
28
|
+
{
|
|
29
|
+
Name: 'version_changed',
|
|
30
|
+
Description: 'A component now shows a different version of its artifact (you asked for it, or a newer version appeared).',
|
|
31
|
+
PayloadSchema: {
|
|
32
|
+
type: 'object',
|
|
33
|
+
properties: {
|
|
34
|
+
from: { type: 'string', description: 'The version id it showed.' },
|
|
35
|
+
to: { type: 'string', description: 'The version id it shows now.' },
|
|
36
|
+
reason: { type: 'string', enum: ['agent', 'newer-version'] },
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
Name: 'newer_version_available',
|
|
42
|
+
Description: 'A newer version of an open component exists, and this session is not set to swap to it automatically.',
|
|
43
|
+
PayloadSchema: { type: 'object', properties: { current: { type: 'string' }, available: { type: 'string' } } },
|
|
44
|
+
},
|
|
45
|
+
];
|
|
46
|
+
/** A failure the dispatcher turns into a message the model can act on. */
|
|
47
|
+
function failure(code, error, details) {
|
|
48
|
+
return details ? { Success: false, ErrorCode: code, Error: error, Details: details } : { Success: false, ErrorCode: code, Error: error };
|
|
49
|
+
}
|
|
50
|
+
/** A non-empty string argument, or `undefined`. */
|
|
51
|
+
function readString(bag, key) {
|
|
52
|
+
const value = bag[key];
|
|
53
|
+
return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The INTERACTIVE COMPONENT channel: shows the user any interactive component artifact (a dashboard, a report, a
|
|
57
|
+
* form, a chart) next to the voice conversation, and lets the agent operate it.
|
|
58
|
+
*
|
|
59
|
+
* It is a MULTI-INSTANCE channel (several components may be open at once, each addressed by `target.instance`) and
|
|
60
|
+
* its contract is DERIVED from what it hosts: the actions an agent can take on an instance are that component's own
|
|
61
|
+
* `customMethods` and the standard methods its spec says it supports; its state is the component's data state;
|
|
62
|
+
* its events are the spec's `events`; the inputs for opening it are the spec's `properties`. A component nobody
|
|
63
|
+
* wrote channel code for is therefore operable from its spec alone. See `DeriveComponentContract`.
|
|
64
|
+
*
|
|
65
|
+
* Built-in: `open { artifactId | artifactVersionId, inputs? }` (the dispatcher's reserved action), `show_version`, `close`.
|
|
66
|
+
*
|
|
67
|
+
* ### What the model perceives
|
|
68
|
+
* Per instance: which artifact version is open, what the user last did in it (its events), and its data state,
|
|
69
|
+
* bounded to a handful of rows. All of it is gated by exposure policy (`min(channel, agent config, zero-data-retention,
|
|
70
|
+
* user)`). Pixels flow only if the host registered a frame capturer ({@link ChannelFrameCapture}).
|
|
71
|
+
* Agent-INVOKED verbs return what the component returns; exposure gates what is volunteered to the model, not what it
|
|
72
|
+
* asked for.
|
|
73
|
+
*
|
|
74
|
+
* ### Access
|
|
75
|
+
* Artifacts are loaded through the signed-in user's own session and read permission (see `RunViewComponentArtifactSource`).
|
|
76
|
+
* The agent cannot open anything the user cannot.
|
|
77
|
+
*
|
|
78
|
+
* ### Lifetime
|
|
79
|
+
* Open components are live-only: they are not persisted with the session and are not restored on resume.
|
|
80
|
+
*/
|
|
81
|
+
let InteractiveComponentChannel = class InteractiveComponentChannel extends BaseRealtimeChannelClient {
|
|
82
|
+
/** The components that are open. */
|
|
83
|
+
Engine = new ComponentInstanceEngine(DEFAULT_INTERACTIVE_COMPONENT_CONFIG.MaxInstances);
|
|
84
|
+
/** The resolved configuration; the defaults until {@link Initialize} reads the cascade. */
|
|
85
|
+
Config = { ...DEFAULT_INTERACTIVE_COMPONENT_CONFIG };
|
|
86
|
+
/** How long a verb waits for a component to initialize; overridable so tests need not sleep. */
|
|
87
|
+
HandlePollIntervalMs = HANDLE_POLL_INTERVAL_MS;
|
|
88
|
+
/** How long after an action the data state is re-read; overridable for tests. */
|
|
89
|
+
DataSettleMs = DATA_SETTLE_MS;
|
|
90
|
+
artifactSource = null;
|
|
91
|
+
surface = null;
|
|
92
|
+
settleTimer = null;
|
|
93
|
+
get ChannelName() {
|
|
94
|
+
return INTERACTIVE_COMPONENT_CHANNEL_KEY;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* The id state notes carry. A multi-instance channel's snapshot describes ALL its instances (keyed by their own ids,
|
|
98
|
+
* `c1`, `c2`, ...), so the notes about the snapshot as a whole are addressed to `all` rather than to one of them.
|
|
99
|
+
*/
|
|
100
|
+
get InstanceId() {
|
|
101
|
+
return 'all';
|
|
102
|
+
}
|
|
103
|
+
get TabTitle() {
|
|
104
|
+
return 'Components';
|
|
105
|
+
}
|
|
106
|
+
get TabIcon() {
|
|
107
|
+
return 'fa-solid fa-cubes';
|
|
108
|
+
}
|
|
109
|
+
/** The channel has no native tools: every verb is reached through the `ContextTool` proxy, because the verbs depend on what is open. */
|
|
110
|
+
GetToolDefinitions() {
|
|
111
|
+
return [];
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Replaces where artifacts are loaded from (a host with its own store, or a test). Without it the channel loads
|
|
115
|
+
* through the session's provider and the signed-in user's access.
|
|
116
|
+
*/
|
|
117
|
+
SetArtifactSource(source) {
|
|
118
|
+
this.artifactSource = source;
|
|
119
|
+
}
|
|
120
|
+
// ── Contract: descriptor ───────────────────────────────────────────────────
|
|
121
|
+
/**
|
|
122
|
+
* The channel's self-description. The verbs and events are the built-ins plus those of every component that is
|
|
123
|
+
* open right now, so the contract grows as components are opened and the dispatcher validates against it.
|
|
124
|
+
*/
|
|
125
|
+
GetDescriptor() {
|
|
126
|
+
return {
|
|
127
|
+
Key: this.ChannelName,
|
|
128
|
+
Version: REALTIME_CHANNEL_CONTRACT_VERSION,
|
|
129
|
+
DisplayName: 'Interactive Components',
|
|
130
|
+
OwningPackage: '@memberjunction/ng-conversations',
|
|
131
|
+
Instructions: 'Shows the user an interactive component (a dashboard, report, chart or form) built as an artifact, next to the conversation. ' +
|
|
132
|
+
'Open one with action "open" and an artifactId (its latest version) or an artifactVersionId. The result lists the actions THAT component supports ' +
|
|
133
|
+
'and the events it reports; use them as actions of this channel. Several components can be open: pass the instance id to say which, ' +
|
|
134
|
+
'otherwise the one the user is looking at is used. You can open components other agents built (they come back as artifacts). ' +
|
|
135
|
+
'You are told what the user is looking at and what they do in it; do not narrate routine changes.',
|
|
136
|
+
Nouns: [
|
|
137
|
+
{
|
|
138
|
+
Name: 'components',
|
|
139
|
+
Description: 'The open components keyed by instance id: which artifact version each shows, whether it is the one the user is looking at, ' +
|
|
140
|
+
'what the user last did in it, and (when the component reports one) a bounded summary of its data.',
|
|
141
|
+
Schema: { type: 'object', additionalProperties: { type: 'object' } },
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
Verbs: this.collectVerbs(),
|
|
145
|
+
Inputs: {
|
|
146
|
+
type: 'object',
|
|
147
|
+
properties: {
|
|
148
|
+
artifactId: { type: 'string', description: 'The artifact to open (its latest version is shown).' },
|
|
149
|
+
artifactVersionId: { type: 'string', description: 'A specific artifact version to open (instead of artifactId).' },
|
|
150
|
+
inputs: { type: 'object', description: 'Values for the component\'s own properties, as its spec declares them.' },
|
|
151
|
+
},
|
|
152
|
+
additionalProperties: false,
|
|
153
|
+
},
|
|
154
|
+
Events: this.collectEvents(),
|
|
155
|
+
DisplayPolicy: 'on-demand',
|
|
156
|
+
DefaultAvailability: 'opt-in',
|
|
157
|
+
MaxExposure: 'pixels',
|
|
158
|
+
MultiInstance: true,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/** The built-in verbs plus each open component's, merged by name. */
|
|
162
|
+
collectVerbs() {
|
|
163
|
+
const builtins = [
|
|
164
|
+
{
|
|
165
|
+
Name: BUILTIN_VERBS.ShowVersion,
|
|
166
|
+
Description: 'Shows another version of an open component\'s own artifact in place (the instance stays open and keeps its id).',
|
|
167
|
+
ParametersSchema: {
|
|
168
|
+
type: 'object',
|
|
169
|
+
properties: { versionId: { type: 'string', description: 'The artifact version to show.' } },
|
|
170
|
+
required: ['versionId'],
|
|
171
|
+
additionalProperties: false,
|
|
172
|
+
},
|
|
173
|
+
InvokableBy: 'agent',
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
Name: BUILTIN_VERBS.Close,
|
|
177
|
+
Description: 'Closes an open component.',
|
|
178
|
+
ParametersSchema: { type: 'object', properties: {}, additionalProperties: false },
|
|
179
|
+
InvokableBy: 'agent',
|
|
180
|
+
},
|
|
181
|
+
];
|
|
182
|
+
const byName = new Map();
|
|
183
|
+
for (const record of this.Engine.Instances) {
|
|
184
|
+
for (const derived of record.Contract.Verbs) {
|
|
185
|
+
const key = derived.Verb.Name.toLowerCase();
|
|
186
|
+
const offers = byName.get(key) ?? [];
|
|
187
|
+
offers.push({ Component: record.Artifact.Name, Verb: derived.Verb });
|
|
188
|
+
byName.set(key, offers);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return [...builtins, ...[...byName.values()].map((offers) => MergeVerbOffers(offers))];
|
|
192
|
+
}
|
|
193
|
+
/** The built-in events plus each open component's declared ones. */
|
|
194
|
+
collectEvents() {
|
|
195
|
+
const events = new Map(BUILTIN_EVENTS.map((e) => [e.Name.toLowerCase(), e]));
|
|
196
|
+
for (const record of this.Engine.Instances) {
|
|
197
|
+
for (const event of record.Contract.Events) {
|
|
198
|
+
if (!events.has(event.Name.toLowerCase())) {
|
|
199
|
+
events.set(event.Name.toLowerCase(), event);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return [...events.values()];
|
|
204
|
+
}
|
|
205
|
+
GetOnboardingDetails() {
|
|
206
|
+
return {
|
|
207
|
+
Heading: 'Interactive components',
|
|
208
|
+
Description: 'The agent can open an interactive component — a dashboard, report or form — right here during the call, and work with it alongside you.',
|
|
209
|
+
Tips: [
|
|
210
|
+
'Switch between open components using the tabs along the top.',
|
|
211
|
+
'The agent sees what you select and change, so you can say "this one" and point.',
|
|
212
|
+
'Use the "Agent can see" control to limit what the agent perceives.',
|
|
213
|
+
],
|
|
214
|
+
IconClass: 'fa-solid fa-cubes',
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
// ── Lifecycle ──────────────────────────────────────────────────────────────
|
|
218
|
+
OnInitialize() {
|
|
219
|
+
const parsed = ParseInteractiveComponentConfig(this.Context?.ChannelConfig);
|
|
220
|
+
this.Config = parsed.Config;
|
|
221
|
+
this.Engine.SetMaxInstances(this.Config.MaxInstances);
|
|
222
|
+
for (const problem of parsed.Problems) {
|
|
223
|
+
console.warn(`[RealtimeChannel:${INTERACTIVE_COMPONENT_CHANNEL_KEY}] Configuration: ${problem}`);
|
|
224
|
+
}
|
|
225
|
+
if (!this.artifactSource) {
|
|
226
|
+
const provider = this.Context?.Provider;
|
|
227
|
+
const user = provider?.CurrentUser;
|
|
228
|
+
if (provider && user) {
|
|
229
|
+
this.artifactSource = new RunViewComponentArtifactSource(provider, user);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
this.enableVisualPerceptionIfAvailable();
|
|
233
|
+
}
|
|
234
|
+
/** Opts into pixels only when the host registered something that can rasterize a component. */
|
|
235
|
+
enableVisualPerceptionIfAvailable() {
|
|
236
|
+
if (!ChannelFrameCapture.Instance.Capturer) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
this.EnableVisualPerception({ GetLatestFrame: () => this.CaptureActiveFrame() });
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* A picture of the component the user is looking at, as base64 JPEG, or `null` when there is none to send: exposure is
|
|
243
|
+
* below `pixels`, the session has no inbound video track, nothing is on screen, or the host's rasterizer produced nothing
|
|
244
|
+
* (it reports why, once, itself). Never throws.
|
|
245
|
+
*
|
|
246
|
+
* The base channel's visual pump has already checked exposure and the track before it asks; they are checked again so a
|
|
247
|
+
* frame is never rasterized (the costly part) for a model that may not see it, whoever asks.
|
|
248
|
+
*/
|
|
249
|
+
async CaptureActiveFrame() {
|
|
250
|
+
const capturer = ChannelFrameCapture.Instance.Capturer;
|
|
251
|
+
if (!capturer || this.Exposure !== 'pixels' || !this.Context?.Client?.IsTrackEstablished('video', 'inbound')) {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
const element = this.surface?.GetActiveElement() ?? null;
|
|
255
|
+
if (!element) {
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
try {
|
|
259
|
+
return await capturer(element);
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
LogError(`[RealtimeChannel:${INTERACTIVE_COMPONENT_CHANNEL_KEY}] Capturing a component frame failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
GetSurfaceComponent() {
|
|
267
|
+
return RealtimeInteractiveComponentSurfaceComponent;
|
|
268
|
+
}
|
|
269
|
+
/** Hands the dynamically created surface the engine it renders and the hooks it reports through. */
|
|
270
|
+
BindSurface(instance) {
|
|
271
|
+
this.surface = instance;
|
|
272
|
+
instance.Engine = this.Engine;
|
|
273
|
+
instance.AgentName = this.Context?.AgentName ?? 'The agent';
|
|
274
|
+
instance.Provider = this.Context?.Provider ?? null;
|
|
275
|
+
instance.ActivityHandler = (instanceId, activity) => this.OnComponentActivity(instanceId, activity);
|
|
276
|
+
}
|
|
277
|
+
UnbindSurface() {
|
|
278
|
+
this.surface = null;
|
|
279
|
+
}
|
|
280
|
+
Dispose() {
|
|
281
|
+
if (this.settleTimer !== null) {
|
|
282
|
+
clearTimeout(this.settleTimer);
|
|
283
|
+
this.settleTimer = null;
|
|
284
|
+
}
|
|
285
|
+
this.surface = null;
|
|
286
|
+
this.Engine.Dispose();
|
|
287
|
+
super.Dispose();
|
|
288
|
+
}
|
|
289
|
+
// ── Contract: state ────────────────────────────────────────────────────────
|
|
290
|
+
/**
|
|
291
|
+
* Open components are LIVE-ONLY: there is no state of record to persist, so nothing is saved with the session
|
|
292
|
+
* (`null`, which the host never writes) and {@link RestoreState} refuses what it is offered. A resumed session starts
|
|
293
|
+
* with no component open; the agent opens them again. What the model perceives right now is {@link GetState}.
|
|
294
|
+
*/
|
|
295
|
+
SerializeState() {
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
/** Live-only: a resumed session never restores open components (see {@link SerializeState}). Always `false`. */
|
|
299
|
+
RestoreState(_stateJson) {
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
/** The open components, keyed by instance id (see the `components` noun). A pure read. */
|
|
303
|
+
GetState() {
|
|
304
|
+
const components = {};
|
|
305
|
+
for (const record of this.Engine.Instances) {
|
|
306
|
+
components[record.InstanceID] = this.describeForState(record);
|
|
307
|
+
}
|
|
308
|
+
return { components };
|
|
309
|
+
}
|
|
310
|
+
/** One instance as the model perceives it. */
|
|
311
|
+
describeForState(record) {
|
|
312
|
+
const entry = {
|
|
313
|
+
name: record.Artifact.Name,
|
|
314
|
+
artifactId: record.Artifact.ArtifactID,
|
|
315
|
+
artifactVersionId: record.Artifact.VersionID,
|
|
316
|
+
versionNumber: record.Artifact.VersionNumber,
|
|
317
|
+
active: this.Engine.ActiveID === record.InstanceID,
|
|
318
|
+
};
|
|
319
|
+
if (Object.keys(record.Inputs).length > 0) {
|
|
320
|
+
entry['inputs'] = ToBoundedJson(record.Inputs);
|
|
321
|
+
}
|
|
322
|
+
if (record.LastEvent) {
|
|
323
|
+
entry['lastEvent'] = { type: record.LastEvent.Type, payload: record.LastEvent.Payload, seq: record.LastEvent.Seq };
|
|
324
|
+
}
|
|
325
|
+
if (record.Contract.SupportsDataState && record.Handle?.IsReady) {
|
|
326
|
+
const data = SummarizeDataState(record.Handle.GetCurrentDataState(), {
|
|
327
|
+
MaxRows: this.Config.MaxStateRows,
|
|
328
|
+
MaxChars: this.Config.MaxStateChars,
|
|
329
|
+
});
|
|
330
|
+
if (data) {
|
|
331
|
+
entry['data'] = data;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return entry;
|
|
335
|
+
}
|
|
336
|
+
// ── Opening ────────────────────────────────────────────────────────────────
|
|
337
|
+
/**
|
|
338
|
+
* Loads the artifact and opens it as a new instance (or, when that artifact is already open, brings the existing
|
|
339
|
+
* instance forward and moves it to the requested version). Refusals are structured results, never exceptions.
|
|
340
|
+
*/
|
|
341
|
+
async OnOpen(inputs) {
|
|
342
|
+
const artifactId = readString(inputs, 'artifactId');
|
|
343
|
+
const versionId = readString(inputs, 'artifactVersionId');
|
|
344
|
+
if ((artifactId === undefined) === (versionId === undefined)) {
|
|
345
|
+
return failure('invalid_params', 'Provide exactly one of artifactId (the latest version) or artifactVersionId (a specific version).');
|
|
346
|
+
}
|
|
347
|
+
const source = this.artifactSource;
|
|
348
|
+
if (!source) {
|
|
349
|
+
return failure('open_failed', 'Components cannot be loaded in this session (no signed-in user to load them for).');
|
|
350
|
+
}
|
|
351
|
+
let artifact;
|
|
352
|
+
try {
|
|
353
|
+
artifact = versionId ? await source.LoadVersion(versionId) : await source.LoadLatest(artifactId);
|
|
354
|
+
}
|
|
355
|
+
catch (error) {
|
|
356
|
+
return this.mapLoadFailure(error);
|
|
357
|
+
}
|
|
358
|
+
const existing = this.Engine.FindByArtifact(artifact.ArtifactID);
|
|
359
|
+
if (existing) {
|
|
360
|
+
return this.reuseOpenInstance(existing, artifact);
|
|
361
|
+
}
|
|
362
|
+
return this.addInstance(artifact, inputs);
|
|
363
|
+
}
|
|
364
|
+
/** Opens a new instance for a loaded artifact. */
|
|
365
|
+
addInstance(artifact, inputs) {
|
|
366
|
+
const componentInputs = IsPlainObject(inputs['inputs']) ? inputs['inputs'] : {};
|
|
367
|
+
const contractIssues = ValidateJsonAgainstSchemaSubset(componentInputs, this.inputsSchemaFor(artifact), '$.inputs');
|
|
368
|
+
if (contractIssues.length > 0) {
|
|
369
|
+
return failure('invalid_params', `The inputs for "${artifact.Name}" are invalid.`, contractIssues);
|
|
370
|
+
}
|
|
371
|
+
try {
|
|
372
|
+
const record = this.Engine.Add(artifact, componentInputs);
|
|
373
|
+
return { Success: true, Instance: record.InstanceID, Result: this.describeOpened(record) };
|
|
374
|
+
}
|
|
375
|
+
catch (error) {
|
|
376
|
+
if (error instanceof InstanceLimitError) {
|
|
377
|
+
return failure('open_failed', error.message, [`open: ${this.Engine.Instances.map((r) => `${r.InstanceID} (${r.Artifact.Name})`).join(', ')}`]);
|
|
378
|
+
}
|
|
379
|
+
throw error;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
/** The inputs schema a freshly derived contract would have, so opening is validated before an instance exists. */
|
|
383
|
+
inputsSchemaFor(artifact) {
|
|
384
|
+
return DeriveComponentContract(artifact.Spec).InputsSchema;
|
|
385
|
+
}
|
|
386
|
+
/** An artifact that is already open is not opened twice: focus it, and show the requested version. */
|
|
387
|
+
async reuseOpenInstance(existing, artifact) {
|
|
388
|
+
this.Engine.SetActive(existing.InstanceID);
|
|
389
|
+
if (existing.Artifact.VersionID !== artifact.VersionID) {
|
|
390
|
+
this.swapVersion(existing, artifact, 'agent');
|
|
391
|
+
}
|
|
392
|
+
return {
|
|
393
|
+
Success: true,
|
|
394
|
+
Instance: existing.InstanceID,
|
|
395
|
+
Result: { ...this.describeOpened(existing), alreadyOpen: true },
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
/** What the agent is told when it opens (or revisits) a component: how to operate it. */
|
|
399
|
+
describeOpened(record) {
|
|
400
|
+
return {
|
|
401
|
+
artifactId: record.Artifact.ArtifactID,
|
|
402
|
+
artifactVersionId: record.Artifact.VersionID,
|
|
403
|
+
versionNumber: record.Artifact.VersionNumber,
|
|
404
|
+
name: record.Artifact.Name,
|
|
405
|
+
actions: record.Contract.Verbs.map((v) => `${v.Verb.Name}(${FormatParameterList(v.Verb.ParametersSchema)}) — ${v.Verb.Description}`),
|
|
406
|
+
events: record.Contract.Events.map((e) => e.Name),
|
|
407
|
+
...(record.Contract.Skipped.length > 0
|
|
408
|
+
? { unavailable: record.Contract.Skipped.map((s) => `${s.Name}: ${s.Reason}`) }
|
|
409
|
+
: {}),
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
/** Maps a failed artifact load to a result the model can act on. */
|
|
413
|
+
mapLoadFailure(error) {
|
|
414
|
+
if (error instanceof ComponentArtifactError) {
|
|
415
|
+
return failure(error.Code === 'invalid_id' ? 'invalid_params' : 'open_failed', error.message, [error.Code]);
|
|
416
|
+
}
|
|
417
|
+
LogError(`[RealtimeChannel:${INTERACTIVE_COMPONENT_CHANNEL_KEY}] Loading a component artifact failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
418
|
+
return failure('open_failed', 'That artifact could not be loaded.');
|
|
419
|
+
}
|
|
420
|
+
// ── Delegated artifacts ────────────────────────────────────────────────────
|
|
421
|
+
/**
|
|
422
|
+
* Asks to be offered a delegated run's artifacts when the operator turned on auto-open, or when one of them is
|
|
423
|
+
* already open here (so a revised version replaces it). Whether an artifact is a component is only known once it
|
|
424
|
+
* is loaded, so with auto-open on the channel is mounted for any delegated artifact and ignores the ones that are not.
|
|
425
|
+
*/
|
|
426
|
+
AcceptsDelegationArtifacts(artifacts, config) {
|
|
427
|
+
if (artifacts.some((a) => this.Engine.FindByArtifact(a.ArtifactID))) {
|
|
428
|
+
return true;
|
|
429
|
+
}
|
|
430
|
+
return artifacts.length > 0 && ParseInteractiveComponentConfig(config).Config.AutoOpenDelegatedComponents;
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Takes a delegated run's artifacts: an artifact that is already open moves to its newer version (or, when the
|
|
434
|
+
* configuration says not to swap, the agent is told one exists); with auto-open on, a component that is not open is opened.
|
|
435
|
+
*/
|
|
436
|
+
async OnDelegationArtifacts(artifacts) {
|
|
437
|
+
for (const artifact of artifacts) {
|
|
438
|
+
const open = this.Engine.FindByArtifact(artifact.ArtifactID);
|
|
439
|
+
if (open) {
|
|
440
|
+
await this.takeNewerVersion(open, artifact);
|
|
441
|
+
}
|
|
442
|
+
else if (this.Config.AutoOpenDelegatedComponents) {
|
|
443
|
+
await this.autoOpen(artifact);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
/** Opens a delegated artifact; one that is not a component is ignored (expected), anything else is logged. */
|
|
448
|
+
async autoOpen(artifact) {
|
|
449
|
+
const opened = await this.Open({ artifactVersionId: artifact.ArtifactVersionID });
|
|
450
|
+
if (!opened.Success && !(opened.Details ?? []).includes('not_a_component')) {
|
|
451
|
+
console.warn(`[RealtimeChannel:${INTERACTIVE_COMPONENT_CHANNEL_KEY}] Could not open delegated artifact "${artifact.Name}": ${opened.Error ?? opened.ErrorCode}`);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
/** Moves an open instance to the version a delegated run produced, when that version is newer. */
|
|
455
|
+
async takeNewerVersion(open, artifact) {
|
|
456
|
+
if (open.Artifact.VersionID.toLowerCase() === artifact.ArtifactVersionID.toLowerCase() || !this.artifactSource) {
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
let loaded;
|
|
460
|
+
try {
|
|
461
|
+
loaded = await this.artifactSource.LoadVersion(artifact.ArtifactVersionID);
|
|
462
|
+
}
|
|
463
|
+
catch (error) {
|
|
464
|
+
console.warn(`[RealtimeChannel:${INTERACTIVE_COMPONENT_CHANNEL_KEY}] Could not load the newer version of "${open.Artifact.Name}": ${error instanceof Error ? error.message : String(error)}`);
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
if (loaded.VersionNumber <= open.Artifact.VersionNumber) {
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
if (this.Config.SwapToNewerVersions) {
|
|
471
|
+
this.swapVersion(open, loaded, 'newer-version');
|
|
472
|
+
}
|
|
473
|
+
else {
|
|
474
|
+
this.EmitChannelEvent('newer_version_available', { current: open.Artifact.VersionID, available: loaded.VersionID }, undefined, open.InstanceID);
|
|
475
|
+
if (this.Exposure !== 'none') {
|
|
476
|
+
this.Context?.SendContextNote(FormatChannelNote(INTERACTIVE_COMPONENT_CHANNEL_KEY, open.InstanceID, 'newer_version_available', {
|
|
477
|
+
current: open.Artifact.VersionID,
|
|
478
|
+
available: loaded.VersionID,
|
|
479
|
+
}));
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
// ── Version swap ───────────────────────────────────────────────────────────
|
|
484
|
+
/** Shows another version in an existing instance, keeping its id; tells observers, and the model when it did not ask. */
|
|
485
|
+
swapVersion(record, artifact, reason) {
|
|
486
|
+
const from = record.Artifact.VersionID;
|
|
487
|
+
this.Engine.SwapVersion(record.InstanceID, artifact);
|
|
488
|
+
const payload = { from, to: artifact.VersionID, reason };
|
|
489
|
+
this.EmitChannelEvent('version_changed', payload, undefined, record.InstanceID);
|
|
490
|
+
if (reason === 'newer-version' && this.Exposure !== 'none') {
|
|
491
|
+
this.Context?.SendContextNote(FormatChannelNote(INTERACTIVE_COMPONENT_CHANNEL_KEY, record.InstanceID, 'version_changed', {
|
|
492
|
+
...payload,
|
|
493
|
+
actions: record.Contract.Verbs.map((v) => v.Verb.Name),
|
|
494
|
+
}));
|
|
495
|
+
}
|
|
496
|
+
this.RecordChange({ Author: reason === 'agent' ? 'agent' : 'system' });
|
|
497
|
+
}
|
|
498
|
+
// ── Verbs ──────────────────────────────────────────────────────────────────
|
|
499
|
+
/**
|
|
500
|
+
* Runs one verb. The dispatcher has already checked the verb against the (merged) descriptor; the exact check against
|
|
501
|
+
* the addressed component's own schema happens here.
|
|
502
|
+
*/
|
|
503
|
+
async ApplyVerb(verb, args, actor, instanceId) {
|
|
504
|
+
const name = verb.trim().toLowerCase();
|
|
505
|
+
const target = this.resolveInstance(name, instanceId);
|
|
506
|
+
if ('Success' in target) {
|
|
507
|
+
return target;
|
|
508
|
+
}
|
|
509
|
+
const refusal = actor === 'agent' ? this.refuseForExposure(target, name) : null;
|
|
510
|
+
if (refusal) {
|
|
511
|
+
return refusal;
|
|
512
|
+
}
|
|
513
|
+
if (name === BUILTIN_VERBS.ShowVersion) {
|
|
514
|
+
return this.showVersion(target, args);
|
|
515
|
+
}
|
|
516
|
+
if (name === BUILTIN_VERBS.Close) {
|
|
517
|
+
return this.closeInstance(target);
|
|
518
|
+
}
|
|
519
|
+
return this.invokeDerived(target, name, args, actor);
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Refuses a verb whose result carries the component's data while exposure keeps that from the agent. The dispatcher
|
|
523
|
+
* checks the same thing against the (merged) descriptor; this is the exact per-instance check, and it keeps the
|
|
524
|
+
* channel safe when it is driven by something other than the dispatcher.
|
|
525
|
+
*/
|
|
526
|
+
refuseForExposure(record, verbName) {
|
|
527
|
+
const derived = record.Contract.Verbs.find((v) => v.Verb.Name.toLowerCase() === verbName);
|
|
528
|
+
const message = derived ? this.RefuseVerbForExposure(derived.Verb) : null;
|
|
529
|
+
return message ? failure('exposure_restricted', message, [...this.ExposureReasons]) : null;
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Finds the instance a verb addresses: the one named, else the one the user is looking at when it supports the verb,
|
|
533
|
+
* else the only one that does. Anything else is a refusal that lists what would have worked.
|
|
534
|
+
*/
|
|
535
|
+
resolveInstance(verb, instanceId) {
|
|
536
|
+
const supports = (r) => verb === BUILTIN_VERBS.ShowVersion || verb === BUILTIN_VERBS.Close || r.Contract.Verbs.some((v) => v.Verb.Name.toLowerCase() === verb);
|
|
537
|
+
if (instanceId !== undefined) {
|
|
538
|
+
const named = this.Engine.Get(instanceId);
|
|
539
|
+
if (!named) {
|
|
540
|
+
return failure('unknown_instance', `No component "${instanceId}" is open.`, [`open: ${this.listOpen()}`]);
|
|
541
|
+
}
|
|
542
|
+
return supports(named) ? named : failure('verb_failed', `"${named.Artifact.Name}" has no action "${verb}".`);
|
|
543
|
+
}
|
|
544
|
+
const candidates = this.Engine.Instances.filter(supports);
|
|
545
|
+
const active = candidates.find((r) => r.InstanceID === this.Engine.ActiveID);
|
|
546
|
+
if (active) {
|
|
547
|
+
return active;
|
|
548
|
+
}
|
|
549
|
+
if (candidates.length === 1) {
|
|
550
|
+
return candidates[0];
|
|
551
|
+
}
|
|
552
|
+
if (candidates.length === 0) {
|
|
553
|
+
return failure('verb_failed', `No open component has an action "${verb}".`, [`open: ${this.listOpen() || 'nothing'}`]);
|
|
554
|
+
}
|
|
555
|
+
return failure('ambiguous_instance', `Several open components have "${verb}"; say which with the instance.`, [`open: ${this.listOpen()}`]);
|
|
556
|
+
}
|
|
557
|
+
/** `c1 (Revenue), c2 (Pipeline)`. */
|
|
558
|
+
listOpen() {
|
|
559
|
+
return this.Engine.Instances.map((r) => `${r.InstanceID} (${r.Artifact.Name})`).join(', ');
|
|
560
|
+
}
|
|
561
|
+
/** `show_version`: swap an instance to another version of ITS artifact. */
|
|
562
|
+
async showVersion(record, args) {
|
|
563
|
+
const versionId = readString(args, 'versionId');
|
|
564
|
+
if (!versionId || !this.artifactSource) {
|
|
565
|
+
return failure('invalid_params', versionId ? 'Components cannot be loaded in this session.' : 'show_version needs a versionId.');
|
|
566
|
+
}
|
|
567
|
+
let loaded;
|
|
568
|
+
try {
|
|
569
|
+
loaded = await this.artifactSource.LoadVersion(versionId);
|
|
570
|
+
}
|
|
571
|
+
catch (error) {
|
|
572
|
+
return this.mapLoadFailure(error);
|
|
573
|
+
}
|
|
574
|
+
if (loaded.ArtifactID.toLowerCase() !== record.Artifact.ArtifactID.toLowerCase()) {
|
|
575
|
+
return failure('verb_failed', `That version belongs to a different artifact ("${loaded.Name}"). Open it with "open" instead.`);
|
|
576
|
+
}
|
|
577
|
+
if (loaded.VersionID.toLowerCase() === record.Artifact.VersionID.toLowerCase()) {
|
|
578
|
+
return { Success: true, Result: { changed: false, instance: record.InstanceID, versionNumber: loaded.VersionNumber } };
|
|
579
|
+
}
|
|
580
|
+
this.swapVersion(record, loaded, 'agent');
|
|
581
|
+
return { Success: true, Result: { changed: true, instance: record.InstanceID, ...this.describeOpened(record) } };
|
|
582
|
+
}
|
|
583
|
+
/** `close`. */
|
|
584
|
+
closeInstance(record) {
|
|
585
|
+
const name = record.Artifact.Name;
|
|
586
|
+
this.Engine.Remove(record.InstanceID);
|
|
587
|
+
this.EmitChannelEvent('closed', { name }, undefined, record.InstanceID);
|
|
588
|
+
this.RecordChange({ Author: 'agent' });
|
|
589
|
+
return { Success: true, Result: { closed: true, instance: record.InstanceID } };
|
|
590
|
+
}
|
|
591
|
+
/** Runs a verb derived from the component's spec. */
|
|
592
|
+
async invokeDerived(record, name, args, actor) {
|
|
593
|
+
const derived = record.Contract.Verbs.find((v) => v.Verb.Name.toLowerCase() === name);
|
|
594
|
+
if (!derived) {
|
|
595
|
+
return failure('verb_failed', `"${record.Artifact.Name}" has no action "${name}".`);
|
|
596
|
+
}
|
|
597
|
+
const issues = ValidateJsonAgainstSchemaSubset(args, derived.Verb.ParametersSchema);
|
|
598
|
+
if (issues.length > 0) {
|
|
599
|
+
return failure('invalid_params', `The parameters for ${derived.Verb.Name} on "${record.Artifact.Name}" are invalid. Expected ${derived.Verb.Name}(${FormatParameterList(derived.Verb.ParametersSchema)}).`, issues);
|
|
600
|
+
}
|
|
601
|
+
const handle = await this.waitForHandle(record);
|
|
602
|
+
if (!handle) {
|
|
603
|
+
return failure('verb_failed', `"${record.Artifact.Name}" is still loading. Try again in a moment.`);
|
|
604
|
+
}
|
|
605
|
+
try {
|
|
606
|
+
const result = await this.runBinding(record, handle, derived, args);
|
|
607
|
+
this.afterAction(record, derived, actor);
|
|
608
|
+
return { Success: true, Result: { instance: record.InstanceID, ...result } };
|
|
609
|
+
}
|
|
610
|
+
catch (error) {
|
|
611
|
+
console.error(`[RealtimeChannel:${INTERACTIVE_COMPONENT_CHANNEL_KEY}] ${derived.Verb.Name} on "${record.Artifact.Name}" threw:`, error);
|
|
612
|
+
return failure('verb_failed', `${derived.Verb.Name} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
/** Waits (bounded) for the surface to mount the component and report it ready. */
|
|
616
|
+
async waitForHandle(record) {
|
|
617
|
+
for (let attempt = 0; attempt < HANDLE_POLL_MAX_ATTEMPTS; attempt++) {
|
|
618
|
+
if (record.Handle?.IsReady) {
|
|
619
|
+
return record.Handle;
|
|
620
|
+
}
|
|
621
|
+
if (!this.Engine.Get(record.InstanceID)) {
|
|
622
|
+
return null;
|
|
623
|
+
}
|
|
624
|
+
await new Promise((resolve) => setTimeout(resolve, this.HandlePollIntervalMs));
|
|
625
|
+
}
|
|
626
|
+
return record.Handle?.IsReady ? record.Handle : null;
|
|
627
|
+
}
|
|
628
|
+
/** Executes a derived verb against the live component and returns what to tell the model. */
|
|
629
|
+
async runBinding(record, handle, derived, args) {
|
|
630
|
+
const binding = derived.Binding;
|
|
631
|
+
if (binding.Kind === 'custom') {
|
|
632
|
+
if (!handle.HasMethod(binding.Method)) {
|
|
633
|
+
throw new Error(`the component does not implement ${binding.Method}`);
|
|
634
|
+
}
|
|
635
|
+
const positional = binding.ParameterNames.map((n) => args[n]);
|
|
636
|
+
const returned = await handle.InvokeMethod(binding.Method, positional);
|
|
637
|
+
return { result: ToBoundedJson(returned) };
|
|
638
|
+
}
|
|
639
|
+
switch (binding.Standard) {
|
|
640
|
+
case 'Refresh':
|
|
641
|
+
handle.Refresh();
|
|
642
|
+
return { refreshed: true };
|
|
643
|
+
case 'Print':
|
|
644
|
+
handle.Print();
|
|
645
|
+
return { printing: true };
|
|
646
|
+
case 'Validate':
|
|
647
|
+
return { validation: ToBoundedJson(handle.Validate()) };
|
|
648
|
+
case 'IsDirty':
|
|
649
|
+
return { dirty: handle.IsDirty() };
|
|
650
|
+
case 'Reset':
|
|
651
|
+
handle.Reset();
|
|
652
|
+
return { reset: true };
|
|
653
|
+
case 'ScrollTo':
|
|
654
|
+
return this.scrollTo(handle, args);
|
|
655
|
+
case 'Focus':
|
|
656
|
+
handle.Focus(readString(args, 'target'));
|
|
657
|
+
return { focused: true };
|
|
658
|
+
case 'GetDataState': {
|
|
659
|
+
const data = SummarizeDataState(handle.GetCurrentDataState(), {
|
|
660
|
+
MaxRows: this.Config.MaxStateRows,
|
|
661
|
+
MaxChars: this.Config.MaxStateChars,
|
|
662
|
+
});
|
|
663
|
+
return data ? { data } : { data: null, note: 'The component has no data state yet.' };
|
|
664
|
+
}
|
|
665
|
+
default:
|
|
666
|
+
throw new Error(`unsupported standard action ${String(binding.Standard)} on ${record.Artifact.Name}`);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
/** `scroll_to`: a selector, or an offset. */
|
|
670
|
+
scrollTo(handle, args) {
|
|
671
|
+
const target = readString(args, 'target');
|
|
672
|
+
if (target !== undefined) {
|
|
673
|
+
handle.ScrollTo(target);
|
|
674
|
+
return { scrolledTo: target };
|
|
675
|
+
}
|
|
676
|
+
const top = typeof args['top'] === 'number' ? args['top'] : undefined;
|
|
677
|
+
const left = typeof args['left'] === 'number' ? args['left'] : undefined;
|
|
678
|
+
if (top === undefined && left === undefined) {
|
|
679
|
+
throw new Error('scroll_to needs a target selector or a top/left offset');
|
|
680
|
+
}
|
|
681
|
+
handle.ScrollTo({ top, left });
|
|
682
|
+
return { scrolled: true };
|
|
683
|
+
}
|
|
684
|
+
/** What happens after a verb ran: the state may have changed, and the picture may have too. */
|
|
685
|
+
afterAction(record, derived, actor) {
|
|
686
|
+
const readOnly = derived.Binding.Kind === 'standard' &&
|
|
687
|
+
(derived.Binding.Standard === 'GetDataState' || derived.Binding.Standard === 'IsDirty' || derived.Binding.Standard === 'Validate' || derived.Binding.Standard === 'Print');
|
|
688
|
+
if (readOnly) {
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
this.Engine.SetActive(record.InstanceID);
|
|
692
|
+
this.RecordChange({ Author: actor });
|
|
693
|
+
this.scheduleSettle(actor);
|
|
694
|
+
void this.ConfirmVisualChange();
|
|
695
|
+
}
|
|
696
|
+
// ── What the user does ─────────────────────────────────────────────────────
|
|
697
|
+
/**
|
|
698
|
+
* Called by the surface when something happens inside a rendered component: an event it emits, a state change,
|
|
699
|
+
* or the end of its initialization. Becomes a change in the state the model perceives and a typed event for observers.
|
|
700
|
+
*/
|
|
701
|
+
OnComponentActivity(instanceId, activity) {
|
|
702
|
+
const record = this.Engine.Get(instanceId);
|
|
703
|
+
if (!record) {
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
if (activity.Kind === 'closed') {
|
|
707
|
+
this.userClosed(record);
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
if (activity.Kind === 'selected') {
|
|
711
|
+
if (this.Engine.ActiveID !== instanceId) {
|
|
712
|
+
this.Engine.SetActive(instanceId);
|
|
713
|
+
this.RecordChange({ Author: 'user' });
|
|
714
|
+
void this.NotifyVisualChange();
|
|
715
|
+
}
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
if (activity.Kind === 'event' && activity.Name) {
|
|
719
|
+
this.Engine.RecordEvent(instanceId, activity.Name, ToBoundedJson(activity.Payload));
|
|
720
|
+
}
|
|
721
|
+
const changeId = this.RecordChange({ Author: activity.Kind === 'initialized' ? 'system' : 'user' });
|
|
722
|
+
if (activity.Kind === 'event' && activity.Name) {
|
|
723
|
+
this.EmitChannelEvent(activity.Name, { payload: ToBoundedJson(activity.Payload) }, changeId, instanceId);
|
|
724
|
+
}
|
|
725
|
+
this.scheduleSettle('user');
|
|
726
|
+
void this.NotifyVisualChange();
|
|
727
|
+
}
|
|
728
|
+
/** The user closed a component's tab. */
|
|
729
|
+
userClosed(record) {
|
|
730
|
+
const name = record.Artifact.Name;
|
|
731
|
+
this.Engine.Remove(record.InstanceID);
|
|
732
|
+
this.EmitChannelEvent('closed', { name, by: 'user' }, undefined, record.InstanceID);
|
|
733
|
+
this.RecordChange({ Author: 'user' });
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* A component's data usually arrives AFTER the action that asked for it, so the state is described once more shortly
|
|
737
|
+
* afterward. One timer covers a burst.
|
|
738
|
+
*/
|
|
739
|
+
scheduleSettle(author) {
|
|
740
|
+
if (this.settleTimer !== null || this.Exposure === 'none') {
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
this.settleTimer = setTimeout(() => {
|
|
744
|
+
this.settleTimer = null;
|
|
745
|
+
this.RecordChange({ Author: author });
|
|
746
|
+
}, this.DataSettleMs);
|
|
747
|
+
}
|
|
748
|
+
};
|
|
749
|
+
InteractiveComponentChannel = __decorate([
|
|
750
|
+
RegisterClass(BaseRealtimeChannelClient, INTERACTIVE_COMPONENT_CLIENT_PLUGIN_CLASS)
|
|
751
|
+
], InteractiveComponentChannel);
|
|
752
|
+
export { InteractiveComponentChannel };
|
|
753
|
+
/**
|
|
754
|
+
* Tree-shaking prevention: the channel is resolved through the ClassFactory by the registry row's `ClientPluginClass`
|
|
755
|
+
* key, so this static call keeps its `@RegisterClass` side effect from being eliminated by the bundler. Called alongside
|
|
756
|
+
* `LoadRealtimeMediaChannel()`.
|
|
757
|
+
*/
|
|
758
|
+
export function LoadRealtimeInteractiveComponentChannel() {
|
|
759
|
+
// intentional no-op: the import side effect performs the registration
|
|
760
|
+
}
|
|
761
|
+
//# sourceMappingURL=interactive-component-channel.js.map
|