@memberjunction/ng-conversations 6.2.0-edge.3 → 6.2.0-edge.5
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 +33 -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-agent-picker.component.d.ts +1 -1
- package/dist/lib/components/conversation/conversation-agent-picker.component.js +3 -3
- package/dist/lib/components/conversation/conversation-agent-picker.component.js.map +1 -1
- package/dist/lib/components/conversation/conversation-chat-area.component.d.ts +651 -53
- package/dist/lib/components/conversation/conversation-chat-area.component.d.ts.map +1 -1
- package/dist/lib/components/conversation/conversation-chat-area.component.js +3002 -661
- 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 +96 -11
- package/dist/lib/components/message/message-input.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-input.component.js +253 -112
- package/dist/lib/components/message/message-input.component.js.map +1 -1
- package/dist/lib/components/message/message-item.component.d.ts +304 -16
- package/dist/lib/components/message/message-item.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-item.component.js +2029 -363
- package/dist/lib/components/message/message-item.component.js.map +1 -1
- package/dist/lib/components/message/message-list.component.d.ts +101 -22
- package/dist/lib/components/message/message-list.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-list.component.js +326 -55
- 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/sidebar/routines-section.component.js +1 -1
- package/dist/lib/components/sidebar/routines-section.component.js.map +1 -1
- package/dist/lib/components/task/tasks-full-view.component.d.ts +152 -31
- package/dist/lib/components/task/tasks-full-view.component.d.ts.map +1 -1
- package/dist/lib/components/task/tasks-full-view.component.js +2227 -214
- package/dist/lib/components/task/tasks-full-view.component.js.map +1 -1
- package/dist/lib/components/tasks/tasks-dropdown.component.js +4 -4
- package/dist/lib/components/tasks/tasks-dropdown.component.js.map +1 -1
- package/dist/lib/components/workspace/conversation-workspace.component.d.ts +33 -32
- package/dist/lib/components/workspace/conversation-workspace.component.d.ts.map +1 -1
- package/dist/lib/components/workspace/conversation-workspace.component.js +72 -88
- 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 +90 -37
- 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 +19 -7
- package/dist/lib/services/browser-realtime-media-host.d.ts.map +1 -1
- package/dist/lib/services/browser-realtime-media-host.js +21 -72
- package/dist/lib/services/browser-realtime-media-host.js.map +1 -1
- package/dist/lib/services/conversation-agent.service.d.ts +48 -16
- package/dist/lib/services/conversation-agent.service.d.ts.map +1 -1
- package/dist/lib/services/conversation-agent.service.js +110 -35
- 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 +93 -12
- package/dist/lib/services/conversation-naming.js.map +1 -1
- package/dist/lib/services/conversation-tasks-filter.d.ts +15 -0
- package/dist/lib/services/conversation-tasks-filter.d.ts.map +1 -0
- package/dist/lib/services/conversation-tasks-filter.js +69 -0
- package/dist/lib/services/conversation-tasks-filter.js.map +1 -0
- 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 +115 -79
- package/dist/lib/services/realtime-audio-recorder.d.ts.map +1 -1
- package/dist/lib/services/realtime-audio-recorder.js +273 -186
- 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 +31 -2
- package/dist/lib/services/realtime-session.service.d.ts.map +1 -1
- package/dist/lib/services/realtime-session.service.js +34 -2
- package/dist/lib/services/realtime-session.service.js.map +1 -1
- package/dist/lib/services/recording/libopus-wasm-binary.d.ts +3 -0
- package/dist/lib/services/recording/libopus-wasm-binary.d.ts.map +1 -0
- package/dist/lib/services/recording/libopus-wasm-binary.js +489 -0
- package/dist/lib/services/recording/libopus-wasm-binary.js.map +1 -0
- package/dist/lib/services/recording/libopus-worker-source.d.ts +48 -0
- package/dist/lib/services/recording/libopus-worker-source.d.ts.map +1 -0
- package/dist/lib/services/recording/libopus-worker-source.js +170 -0
- package/dist/lib/services/recording/libopus-worker-source.js.map +1 -0
- package/dist/lib/services/recording/linear-resampler.d.ts +45 -0
- package/dist/lib/services/recording/linear-resampler.d.ts.map +1 -0
- package/dist/lib/services/recording/linear-resampler.js +85 -0
- package/dist/lib/services/recording/linear-resampler.js.map +1 -0
- package/dist/lib/services/recording/opus-packet-encoder.d.ts +50 -0
- package/dist/lib/services/recording/opus-packet-encoder.d.ts.map +1 -0
- package/dist/lib/services/recording/opus-packet-encoder.js +41 -0
- package/dist/lib/services/recording/opus-packet-encoder.js.map +1 -0
- package/dist/lib/services/recording/wasm-opus-encoder.d.ts +86 -0
- package/dist/lib/services/recording/wasm-opus-encoder.d.ts.map +1 -0
- package/dist/lib/services/recording/wasm-opus-encoder.js +232 -0
- package/dist/lib/services/recording/wasm-opus-encoder.js.map +1 -0
- package/dist/lib/services/recording/webcodecs-opus-encoder.d.ts +39 -0
- package/dist/lib/services/recording/webcodecs-opus-encoder.d.ts.map +1 -0
- package/dist/lib/services/recording/webcodecs-opus-encoder.js +101 -0
- package/dist/lib/services/recording/webcodecs-opus-encoder.js.map +1 -0
- 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 +16 -20
- package/dist/lib/services/search.service.js.map +1 -1
- 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/lib/utils/distinct-artifacts.d.ts +9 -3
- package/dist/lib/utils/distinct-artifacts.d.ts.map +1 -1
- package/dist/lib/utils/distinct-artifacts.js +24 -5
- package/dist/lib/utils/distinct-artifacts.js.map +1 -1
- 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 -33
- 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
- package/dist/lib/services/realtime-pcm-wav.d.ts +0 -79
- package/dist/lib/services/realtime-pcm-wav.d.ts.map +0 -1
- package/dist/lib/services/realtime-pcm-wav.js +0 -213
- package/dist/lib/services/realtime-pcm-wav.js.map +0 -1
|
@@ -4,16 +4,15 @@ import { GraphQLAIClient } from '@memberjunction/graphql-dataprovider';
|
|
|
4
4
|
import { ConversationEngine } from '@memberjunction/core-entities';
|
|
5
5
|
/** The seeded name of the AI prompt that generates conversation names. */
|
|
6
6
|
export const NAME_CONVERSATION_PROMPT = 'Name Conversation';
|
|
7
|
+
/** Longest fork name the `MJ: Conversation Branches` Name field holds. */
|
|
8
|
+
export const MAX_FORK_NAME_LENGTH = 255;
|
|
7
9
|
/**
|
|
8
|
-
*
|
|
9
|
-
* (DB + engine cache) — the single naming implementation shared by the regular composer
|
|
10
|
-
* and the realtime session path.
|
|
10
|
+
* Runs the `Name Conversation` prompt on a message and parses its `{ name, description }`. Saves nothing.
|
|
11
11
|
*
|
|
12
|
-
* @returns The
|
|
13
|
-
*
|
|
14
|
-
* the conversation keeps its current name in every null case.
|
|
12
|
+
* @returns The generated `{ Name, Description }`, or `null` when the prompt is missing, the provider is
|
|
13
|
+
* unavailable, the run fails or times out, or the output has no name. Failures are logged.
|
|
15
14
|
*/
|
|
16
|
-
export async function
|
|
15
|
+
export async function GenerateConversationName(options) {
|
|
17
16
|
try {
|
|
18
17
|
await AIEngineBase.Instance.Config(false);
|
|
19
18
|
const prompt = AIEngineBase.Instance.Prompts.find(pr => pr.Name === NAME_CONVERSATION_PROMPT);
|
|
@@ -31,28 +30,110 @@ export async function GenerateAndApplyConversationName(options) {
|
|
|
31
30
|
messages: [{ role: 'user', content: messageText }]
|
|
32
31
|
}));
|
|
33
32
|
const timeoutMs = options.TimeoutMs ?? 30000;
|
|
33
|
+
let timeoutHandle;
|
|
34
34
|
const timeoutPromise = new Promise((_, reject) => {
|
|
35
|
-
setTimeout(() => reject(new Error(`Conversation naming timed out after ${Math.round(timeoutMs / 1000)} seconds`)), timeoutMs);
|
|
35
|
+
timeoutHandle = setTimeout(() => reject(new Error(`Conversation naming timed out after ${Math.round(timeoutMs / 1000)} seconds`)), timeoutMs);
|
|
36
36
|
});
|
|
37
|
-
|
|
37
|
+
let result;
|
|
38
|
+
try {
|
|
39
|
+
result = await Promise.race([run(prompt.ID, options.MessageText), timeoutPromise]);
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
clearTimeout(timeoutHandle);
|
|
43
|
+
}
|
|
38
44
|
if (!result || !result.success || (!result.parsedResult && !result.output)) {
|
|
39
45
|
return null;
|
|
40
46
|
}
|
|
41
47
|
// parsedResult preferred; CleanAndParseJSON tolerates ```json fences in raw output.
|
|
42
48
|
const parsed = (result.parsedResult ?? (result.output ? CleanAndParseJSON(result.output) : null));
|
|
43
|
-
|
|
49
|
+
const name = typeof parsed?.name === 'string' ? parsed.name.trim() : '';
|
|
50
|
+
if (!name) {
|
|
44
51
|
return null;
|
|
45
52
|
}
|
|
46
|
-
|
|
53
|
+
return { Name: name, Description: parsed?.description || '' };
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
console.warn('⚠️ Conversation naming failed (the current name stays):', error);
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Generates a name + description for a conversation from its first message and SAVES it
|
|
62
|
+
* (DB + engine cache) — the single naming implementation shared by the regular composer
|
|
63
|
+
* and the realtime session path.
|
|
64
|
+
*
|
|
65
|
+
* @returns The applied `{ Name, Description }`, or `null` when naming was skipped/failed
|
|
66
|
+
* (missing prompt, provider unavailable, timeout, unparseable output, save failure) —
|
|
67
|
+
* the conversation keeps its current name in every null case.
|
|
68
|
+
*/
|
|
69
|
+
export async function GenerateAndApplyConversationName(options) {
|
|
70
|
+
const generated = await GenerateConversationName(options);
|
|
71
|
+
if (!generated) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
const saved = await ConversationEngine.Instance.SaveConversation(options.ConversationId, { Name: generated.Name, Description: generated.Description }, options.CurrentUser);
|
|
47
76
|
if (!saved) {
|
|
48
77
|
console.warn('⚠️ Conversation naming generated a name but the save failed');
|
|
49
78
|
return null;
|
|
50
79
|
}
|
|
51
|
-
return
|
|
80
|
+
return generated;
|
|
52
81
|
}
|
|
53
82
|
catch (error) {
|
|
54
83
|
console.warn('⚠️ Conversation naming failed (conversation keeps its default name):', error);
|
|
55
84
|
return null;
|
|
56
85
|
}
|
|
57
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* Saves a fork's Name through the `MJ: Conversation Branches` entity, after reading the row fresh.
|
|
89
|
+
*
|
|
90
|
+
* @returns true when the name was saved; false when the row cannot be read, when `OnlyIfUnnamed` is
|
|
91
|
+
* set and the row has a name, or when the save fails (logged).
|
|
92
|
+
* @throws when the entity read or save throws
|
|
93
|
+
*/
|
|
94
|
+
export async function SaveForkName(options) {
|
|
95
|
+
const fork = await options.Provider.GetEntityObject('MJ: Conversation Branches', options.CurrentUser);
|
|
96
|
+
if (!(await fork.Load(options.ForkId))) {
|
|
97
|
+
console.warn(`⚠️ Could not read fork ${options.ForkId} to save its name`);
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
if (options.OnlyIfUnnamed && fork.Name?.trim()) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
const name = options.Name?.trim().slice(0, MAX_FORK_NAME_LENGTH).trim();
|
|
104
|
+
fork.Name = name ? name : null;
|
|
105
|
+
if (!(await fork.Save())) {
|
|
106
|
+
console.warn(`⚠️ Could not save the name of fork ${options.ForkId}: ${fork.LatestResult?.CompleteMessage ?? 'unknown error'}`);
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Names a fork from its first message: runs the `Name Conversation` prompt ({@link GenerateConversationName})
|
|
113
|
+
* and saves the name as the fork's Name only when the fork, read fresh right before the save, still has
|
|
114
|
+
* no name, so a person's rename wins.
|
|
115
|
+
*
|
|
116
|
+
* @returns The saved name, or `null` when naming failed, the fork already has a name, or the save failed.
|
|
117
|
+
* Failures are logged.
|
|
118
|
+
*/
|
|
119
|
+
export async function GenerateAndApplyForkName(options) {
|
|
120
|
+
const generated = await GenerateConversationName(options);
|
|
121
|
+
if (!generated) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
const saved = await SaveForkName({
|
|
126
|
+
ForkId: options.ForkId,
|
|
127
|
+
Name: generated.Name,
|
|
128
|
+
Provider: options.Provider,
|
|
129
|
+
CurrentUser: options.CurrentUser,
|
|
130
|
+
OnlyIfUnnamed: true,
|
|
131
|
+
});
|
|
132
|
+
return saved ? generated.Name.slice(0, MAX_FORK_NAME_LENGTH).trim() : null;
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
console.warn(`⚠️ Fork naming failed (fork ${options.ForkId} keeps its current name):`, error);
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
58
139
|
//# sourceMappingURL=conversation-naming.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conversation-naming.js","sourceRoot":"","sources":["../../../src/lib/services/conversation-naming.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview Shared conversation auto-naming helper — extracted from the message\n * input's first-message naming flow so BOTH chat paths use one implementation:\n *\n * - **Regular chat**: the composer names a brand-new conversation from its first\n * user message (the original behavior, now delegated here).\n * - **Realtime sessions**: a voice call that created a fresh conversation names it\n * from the first user utterance when the call ends.\n *\n * The naming itself is DB-driven: the `Name Conversation` AI prompt runs through the\n * GraphQL AI client (same path agents use) with a timeout guard; the parsed\n * `{ name, description }` is saved through {@link ConversationEngine.SaveConversation},\n * which updates the engine's cached conversation list in place (the sidebar reacts\n * through the engine's observables). Failures are logged and return `null` — naming is\n * always best-effort background work that must never affect the user experience.\n */\nimport { UserInfo } from '@memberjunction/core';\nimport { CleanAndParseJSON } from '@memberjunction/global';\nimport { AIEngineBase } from '@memberjunction/ai-engine-base';\nimport { GraphQLDataProvider, GraphQLAIClient } from '@memberjunction/graphql-dataprovider';\nimport { ConversationEngine } from '@memberjunction/core-entities';\n\n/** The applied result of a successful auto-naming run. */\nexport interface ConversationNameResult {\n /** The generated conversation name (already saved). */\n Name: string;\n /** The generated description (already saved; empty string when the prompt omitted it). */\n Description: string;\n}\n\n/** Options for {@link GenerateAndApplyConversationName}. */\nexport interface GenerateConversationNameOptions {\n /** The conversation to name (must exist; the caller owns membership checks). */\n ConversationId: string;\n /**\n * The text the name is derived from — the first user message (regular chat) or the\n * first spoken user utterance (realtime). Callers strip mentions/markup first.\n */\n MessageText: string;\n /** The GraphQL provider the AI client runs over. */\n Provider: GraphQLDataProvider;\n /** The acting user (threaded into the conversation save). */\n CurrentUser: UserInfo;\n /** Abort guard for the prompt run. Default 30s — the conversation keeps its default name on timeout. */\n TimeoutMs?: number;\n /**\n * Test seam: overrides the prompt execution. Production leaves this undefined and the\n * helper runs the `Name Conversation` prompt via {@link GraphQLAIClient.RunAIPrompt}.\n */\n RunPrompt?: (promptId: string, messageText: string) => Promise<{ success: boolean; parsedResult?: unknown; output?: string } | null>;\n}\n\n/** The seeded name of the AI prompt that generates conversation names. */\nexport const NAME_CONVERSATION_PROMPT = 'Name Conversation';\n\n/**\n * Generates a name + description for a conversation from its first message and SAVES it\n * (DB + engine cache) — the single naming implementation shared by the regular composer\n * and the realtime session path.\n *\n * @returns The applied `{ Name, Description }`, or `null` when naming was skipped/failed\n * (missing prompt, provider unavailable, timeout, unparseable output, save failure) —\n * the conversation keeps its current name in every null case.\n */\nexport async function GenerateAndApplyConversationName(\n options: GenerateConversationNameOptions\n): Promise<ConversationNameResult | null> {\n try {\n await AIEngineBase.Instance.Config(false);\n const prompt = AIEngineBase.Instance.Prompts.find(pr => pr.Name === NAME_CONVERSATION_PROMPT);\n if (!prompt) {\n console.warn(`⚠️ ${NAME_CONVERSATION_PROMPT} prompt not found`);\n return null;\n }\n if (!options.Provider) {\n console.warn('⚠️ GraphQLDataProvider not available for conversation naming');\n return null;\n }\n\n const run = options.RunPrompt\n ?? ((promptId: string, messageText: string) =>\n new GraphQLAIClient(options.Provider).RunAIPrompt({\n promptId,\n messages: [{ role: 'user', content: messageText }]\n }));\n\n const timeoutMs = options.TimeoutMs ?? 30000;\n const timeoutPromise = new Promise<never>((_, reject) => {\n setTimeout(() => reject(new Error(`Conversation naming timed out after ${Math.round(timeoutMs / 1000)} seconds`)), timeoutMs);\n });\n\n const result = await Promise.race([run(prompt.ID, options.MessageText), timeoutPromise]);\n if (!result || !result.success || (!result.parsedResult && !result.output)) {\n return null;\n }\n\n // parsedResult preferred; CleanAndParseJSON tolerates ```json fences in raw output.\n const parsed = (result.parsedResult ?? (result.output ? CleanAndParseJSON(result.output) : null)) as\n | { name?: string; description?: string }\n | null;\n if (!parsed?.name) {\n return null;\n }\n\n const saved = await ConversationEngine.Instance.SaveConversation(\n options.ConversationId,\n { Name: parsed.name, Description: parsed.description || '' },\n options.CurrentUser\n );\n if (!saved) {\n console.warn('⚠️ Conversation naming generated a name but the save failed');\n return null;\n }\n return { Name: parsed.name, Description: parsed.description || '' };\n } catch (error) {\n console.warn('⚠️ Conversation naming failed (conversation keeps its default name):', error);\n return null;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"conversation-naming.js","sourceRoot":"","sources":["../../../src/lib/services/conversation-naming.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview Shared conversation auto-naming helper — extracted from the message\n * input's first-message naming flow so BOTH chat paths use one implementation:\n *\n * - **Regular chat**: the composer names a brand-new conversation from its first\n * user message (the original behavior, now delegated here).\n * - **Realtime sessions**: a voice call that created a fresh conversation names it\n * from the first user utterance when the call ends.\n * - **Forks**: a Fork or Edit fork is named from the first message sent into it, when it\n * still has no name ({@link GenerateAndApplyForkName}).\n *\n * The naming itself is DB-driven: the `Name Conversation` AI prompt runs through the\n * GraphQL AI client (same path agents use) with a timeout guard ({@link GenerateConversationName}).\n * A conversation's parsed `{ name, description }` is saved through\n * {@link ConversationEngine.SaveConversation}, which updates the engine's cached conversation\n * list in place (the sidebar reacts through the engine's observables); a fork's name is saved\n * on its `MJ: Conversation Branches` row. Failures are logged and return `null` — naming is\n * always best-effort background work that must never affect the user experience.\n */\nimport { IMetadataProvider, UserInfo } from '@memberjunction/core';\nimport { CleanAndParseJSON } from '@memberjunction/global';\nimport { AIEngineBase } from '@memberjunction/ai-engine-base';\nimport { GraphQLDataProvider, GraphQLAIClient, type RunAIPromptResult } from '@memberjunction/graphql-dataprovider';\nimport { ConversationEngine, type MJConversationBranchEntity } from '@memberjunction/core-entities';\n\n/** The result of a successful naming run. */\nexport interface ConversationNameResult {\n /** The generated name, trimmed. */\n Name: string;\n /** The generated description; empty string when the prompt omitted it. */\n Description: string;\n}\n\n/** The prompt runner {@link GenerateConversationName} calls; a test seam for the GraphQL AI client. */\nexport type NamePromptRunner = (\n promptId: string,\n messageText: string\n) => Promise<Pick<RunAIPromptResult, 'success' | 'parsedResult' | 'output'> | null>;\n\n/** Options for {@link GenerateConversationName}. */\nexport interface GenerateNameOptions {\n /**\n * The text the name is derived from — the first user message (regular chat and forks) or the\n * first spoken user utterance (realtime). Callers strip mentions/markup first.\n */\n MessageText: string;\n /** The GraphQL provider the AI client runs over. */\n Provider: GraphQLDataProvider;\n /** Abort guard for the prompt run. Default 30s — the target keeps its current name on timeout. */\n TimeoutMs?: number;\n /**\n * Test seam: overrides the prompt execution. Production leaves this undefined and the\n * helper runs the `Name Conversation` prompt via {@link GraphQLAIClient.RunAIPrompt}.\n */\n RunPrompt?: NamePromptRunner;\n}\n\n/** Options for {@link GenerateAndApplyConversationName}. */\nexport interface GenerateConversationNameOptions extends GenerateNameOptions {\n /** The conversation to name (must exist; the caller owns membership checks). */\n ConversationId: string;\n /** The acting user (threaded into the conversation save). */\n CurrentUser: UserInfo;\n}\n\n/** Options for {@link GenerateAndApplyForkName}. */\nexport interface GenerateForkNameOptions extends GenerateNameOptions {\n /** The fork (`MJ: Conversation Branches` row) to name. */\n ForkId: string;\n /** The acting user; the branch entity's write rule applies to the save. */\n CurrentUser: UserInfo;\n}\n\n/** The seeded name of the AI prompt that generates conversation names. */\nexport const NAME_CONVERSATION_PROMPT = 'Name Conversation';\n\n/** Longest fork name the `MJ: Conversation Branches` Name field holds. */\nexport const MAX_FORK_NAME_LENGTH = 255;\n\n/**\n * Runs the `Name Conversation` prompt on a message and parses its `{ name, description }`. Saves nothing.\n *\n * @returns The generated `{ Name, Description }`, or `null` when the prompt is missing, the provider is\n * unavailable, the run fails or times out, or the output has no name. Failures are logged.\n */\nexport async function GenerateConversationName(options: GenerateNameOptions): Promise<ConversationNameResult | null> {\n try {\n await AIEngineBase.Instance.Config(false);\n const prompt = AIEngineBase.Instance.Prompts.find(pr => pr.Name === NAME_CONVERSATION_PROMPT);\n if (!prompt) {\n console.warn(`⚠️ ${NAME_CONVERSATION_PROMPT} prompt not found`);\n return null;\n }\n if (!options.Provider) {\n console.warn('⚠️ GraphQLDataProvider not available for conversation naming');\n return null;\n }\n\n const run: NamePromptRunner = options.RunPrompt\n ?? ((promptId: string, messageText: string) =>\n new GraphQLAIClient(options.Provider).RunAIPrompt({\n promptId,\n messages: [{ role: 'user', content: messageText }]\n }));\n\n const timeoutMs = options.TimeoutMs ?? 30000;\n let timeoutHandle: ReturnType<typeof setTimeout> | undefined;\n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutHandle = setTimeout(() => reject(new Error(`Conversation naming timed out after ${Math.round(timeoutMs / 1000)} seconds`)), timeoutMs);\n });\n\n let result: Awaited<ReturnType<NamePromptRunner>>;\n try {\n result = await Promise.race([run(prompt.ID, options.MessageText), timeoutPromise]);\n } finally {\n clearTimeout(timeoutHandle);\n }\n if (!result || !result.success || (!result.parsedResult && !result.output)) {\n return null;\n }\n\n // parsedResult preferred; CleanAndParseJSON tolerates ```json fences in raw output.\n const parsed = (result.parsedResult ?? (result.output ? CleanAndParseJSON(result.output) : null)) as\n | { name?: string; description?: string }\n | null;\n const name = typeof parsed?.name === 'string' ? parsed.name.trim() : '';\n if (!name) {\n return null;\n }\n return { Name: name, Description: parsed?.description || '' };\n } catch (error) {\n console.warn('⚠️ Conversation naming failed (the current name stays):', error);\n return null;\n }\n}\n\n/**\n * Generates a name + description for a conversation from its first message and SAVES it\n * (DB + engine cache) — the single naming implementation shared by the regular composer\n * and the realtime session path.\n *\n * @returns The applied `{ Name, Description }`, or `null` when naming was skipped/failed\n * (missing prompt, provider unavailable, timeout, unparseable output, save failure) —\n * the conversation keeps its current name in every null case.\n */\nexport async function GenerateAndApplyConversationName(\n options: GenerateConversationNameOptions\n): Promise<ConversationNameResult | null> {\n const generated = await GenerateConversationName(options);\n if (!generated) {\n return null;\n }\n try {\n const saved = await ConversationEngine.Instance.SaveConversation(\n options.ConversationId,\n { Name: generated.Name, Description: generated.Description },\n options.CurrentUser\n );\n if (!saved) {\n console.warn('⚠️ Conversation naming generated a name but the save failed');\n return null;\n }\n return generated;\n } catch (error) {\n console.warn('⚠️ Conversation naming failed (conversation keeps its default name):', error);\n return null;\n }\n}\n\n/** Options for {@link SaveForkName}. */\nexport interface SaveForkNameOptions {\n /** The fork (`MJ: Conversation Branches` row) to name. */\n ForkId: string;\n /** The new name; null or blank clears it, so the default label shows. Cut to {@link MAX_FORK_NAME_LENGTH}. */\n Name: string | null;\n /** The provider the branch entity is read and saved through. */\n Provider: IMetadataProvider;\n /** The acting user; the branch entity's write rule applies. */\n CurrentUser: UserInfo;\n /** When true, saves nothing if the row read fresh already has a name. */\n OnlyIfUnnamed?: boolean;\n}\n\n/**\n * Saves a fork's Name through the `MJ: Conversation Branches` entity, after reading the row fresh.\n *\n * @returns true when the name was saved; false when the row cannot be read, when `OnlyIfUnnamed` is\n * set and the row has a name, or when the save fails (logged).\n * @throws when the entity read or save throws\n */\nexport async function SaveForkName(options: SaveForkNameOptions): Promise<boolean> {\n const fork = await options.Provider.GetEntityObject<MJConversationBranchEntity>('MJ: Conversation Branches', options.CurrentUser);\n if (!(await fork.Load(options.ForkId))) {\n console.warn(`⚠️ Could not read fork ${options.ForkId} to save its name`);\n return false;\n }\n if (options.OnlyIfUnnamed && fork.Name?.trim()) {\n return false;\n }\n const name = options.Name?.trim().slice(0, MAX_FORK_NAME_LENGTH).trim();\n fork.Name = name ? name : null;\n if (!(await fork.Save())) {\n console.warn(`⚠️ Could not save the name of fork ${options.ForkId}: ${fork.LatestResult?.CompleteMessage ?? 'unknown error'}`);\n return false;\n }\n return true;\n}\n\n/**\n * Names a fork from its first message: runs the `Name Conversation` prompt ({@link GenerateConversationName})\n * and saves the name as the fork's Name only when the fork, read fresh right before the save, still has\n * no name, so a person's rename wins.\n *\n * @returns The saved name, or `null` when naming failed, the fork already has a name, or the save failed.\n * Failures are logged.\n */\nexport async function GenerateAndApplyForkName(options: GenerateForkNameOptions): Promise<string | null> {\n const generated = await GenerateConversationName(options);\n if (!generated) {\n return null;\n }\n try {\n const saved = await SaveForkName({\n ForkId: options.ForkId,\n Name: generated.Name,\n Provider: options.Provider,\n CurrentUser: options.CurrentUser,\n OnlyIfUnnamed: true,\n });\n return saved ? generated.Name.slice(0, MAX_FORK_NAME_LENGTH).trim() : null;\n } catch (error) {\n console.warn(`⚠️ Fork naming failed (fork ${options.ForkId} keeps its current name):`, error);\n return null;\n }\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type IMetadataProvider, type UserInfo } from '@memberjunction/core';
|
|
2
|
+
/** The ExtraFilter for the top-level tasks a user owns. */
|
|
3
|
+
export declare function OwnTasksFilter(user: UserInfo): string;
|
|
4
|
+
/**
|
|
5
|
+
* The ExtraFilter for the top-level tasks the conversations UI shows a user: the tasks the user
|
|
6
|
+
* owns, and the tasks linked to a message the user wrote or that is in a conversation the user
|
|
7
|
+
* owns.
|
|
8
|
+
*
|
|
9
|
+
* The conversation and message IDs are read with their own RunView, so the row-level security on
|
|
10
|
+
* those entities applies. A subquery over their views would skip it, and the server refuses one
|
|
11
|
+
* for a user whose read of them is row-filtered. When a read fails, the result is the user's own
|
|
12
|
+
* tasks.
|
|
13
|
+
*/
|
|
14
|
+
export declare function BuildConversationTasksFilter(provider: IMetadataProvider, user: UserInfo): Promise<string>;
|
|
15
|
+
//# sourceMappingURL=conversation-tasks-filter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversation-tasks-filter.d.ts","sourceRoot":"","sources":["../../../src/lib/services/conversation-tasks-filter.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,KAAK,iBAAiB,EAAsB,KAAK,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAG3H,2DAA2D;AAC3D,wBAAgB,cAAc,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAErD;AAED;;;;;;;;;GASG;AACH,wBAAsB,4BAA4B,CAAC,QAAQ,EAAE,iBAAiB,EAAE,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAwB/G"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { RunView } from '@memberjunction/core';
|
|
2
|
+
import { EscapeSQLString } from '@memberjunction/global';
|
|
3
|
+
/** The ExtraFilter for the top-level tasks a user owns. */
|
|
4
|
+
export function OwnTasksFilter(user) {
|
|
5
|
+
return `ParentID IS NULL AND UserID = '${EscapeSQLString(user.ID)}'`;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The ExtraFilter for the top-level tasks the conversations UI shows a user: the tasks the user
|
|
9
|
+
* owns, and the tasks linked to a message the user wrote or that is in a conversation the user
|
|
10
|
+
* owns.
|
|
11
|
+
*
|
|
12
|
+
* The conversation and message IDs are read with their own RunView, so the row-level security on
|
|
13
|
+
* those entities applies. A subquery over their views would skip it, and the server refuses one
|
|
14
|
+
* for a user whose read of them is row-filtered. When a read fails, the result is the user's own
|
|
15
|
+
* tasks.
|
|
16
|
+
*/
|
|
17
|
+
export async function BuildConversationTasksFilter(provider, user) {
|
|
18
|
+
const ownTasks = OwnTasksFilter(user);
|
|
19
|
+
const tasks = provider.EntityByName('MJ: Tasks');
|
|
20
|
+
if (!tasks) {
|
|
21
|
+
return ownTasks;
|
|
22
|
+
}
|
|
23
|
+
const rv = RunView.FromMetadataProvider(provider);
|
|
24
|
+
const conversationIds = await readIds(rv, user, {
|
|
25
|
+
EntityName: 'MJ: Conversations',
|
|
26
|
+
ExtraFilter: `UserID = '${EscapeSQLString(user.ID)}'`,
|
|
27
|
+
Fields: ['ID'],
|
|
28
|
+
});
|
|
29
|
+
if (conversationIds == null) {
|
|
30
|
+
return ownTasks;
|
|
31
|
+
}
|
|
32
|
+
const messageIds = await readIds(rv, user, {
|
|
33
|
+
EntityName: 'MJ: Conversation Details',
|
|
34
|
+
ExtraFilter: taskMessagesFilter(user, conversationIds, tasks),
|
|
35
|
+
Fields: ['ID'],
|
|
36
|
+
});
|
|
37
|
+
if (messageIds == null || messageIds.length === 0) {
|
|
38
|
+
return ownTasks;
|
|
39
|
+
}
|
|
40
|
+
return `ParentID IS NULL AND (UserID = '${EscapeSQLString(user.ID)}' OR ConversationDetailID IN (${sqlInList(messageIds)}))`;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The messages that have a top-level task and that the user wrote or that are in one of the
|
|
44
|
+
* user's conversations. The task subquery reads Tasks, which has no row filter in a default
|
|
45
|
+
* install. Its identifiers are bracketed so the PostgreSQL provider quotes them.
|
|
46
|
+
*/
|
|
47
|
+
function taskMessagesFilter(user, conversationIds, tasks) {
|
|
48
|
+
const inConversations = conversationIds.length > 0 ? ` OR ConversationID IN (${sqlInList(conversationIds)})` : '';
|
|
49
|
+
const taskView = `[${tasks.SchemaName}].[${tasks.BaseView}]`;
|
|
50
|
+
return (`(UserID = '${EscapeSQLString(user.ID)}'${inConversations}) AND ID IN ` +
|
|
51
|
+
`(SELECT [ConversationDetailID] FROM ${taskView} WHERE [ParentID] IS NULL AND [ConversationDetailID] IS NOT NULL)`);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The IDs of the rows `read` selects, with no row cap; null when the read fails. Each caller names
|
|
55
|
+
* an MJ core entity in `read`, so its key column is `ID`.
|
|
56
|
+
*/
|
|
57
|
+
async function readIds(rv, user, read) {
|
|
58
|
+
const result = await rv.RunView({ ...read, ResultType: 'simple', IgnoreMaxRows: true }, user);
|
|
59
|
+
if (!result.Success) {
|
|
60
|
+
console.error(`Failed to read ${read.EntityName} for the tasks filter:`, result.ErrorMessage);
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
return (result.Results ?? []).map(row => row.ID);
|
|
64
|
+
}
|
|
65
|
+
/** A quoted, comma-separated list of IDs for an IN clause. */
|
|
66
|
+
function sqlInList(ids) {
|
|
67
|
+
return ids.map(id => `'${EscapeSQLString(id)}'`).join(',');
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=conversation-tasks-filter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversation-tasks-filter.js","sourceRoot":"","sources":["../../../src/lib/services/conversation-tasks-filter.ts"],"names":[],"mappings":"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","sourcesContent":["import { RunView, type EntityInfo, type IMetadataProvider, type RunViewParams, type UserInfo } from '@memberjunction/core';\nimport { EscapeSQLString } from '@memberjunction/global';\n\n/** The ExtraFilter for the top-level tasks a user owns. */\nexport function OwnTasksFilter(user: UserInfo): string {\n return `ParentID IS NULL AND UserID = '${EscapeSQLString(user.ID)}'`;\n}\n\n/**\n * The ExtraFilter for the top-level tasks the conversations UI shows a user: the tasks the user\n * owns, and the tasks linked to a message the user wrote or that is in a conversation the user\n * owns.\n *\n * The conversation and message IDs are read with their own RunView, so the row-level security on\n * those entities applies. A subquery over their views would skip it, and the server refuses one\n * for a user whose read of them is row-filtered. When a read fails, the result is the user's own\n * tasks.\n */\nexport async function BuildConversationTasksFilter(provider: IMetadataProvider, user: UserInfo): Promise<string> {\n const ownTasks = OwnTasksFilter(user);\n const tasks = provider.EntityByName('MJ: Tasks');\n if (!tasks) {\n return ownTasks;\n }\n const rv = RunView.FromMetadataProvider(provider);\n const conversationIds = await readIds(rv, user, {\n EntityName: 'MJ: Conversations',\n ExtraFilter: `UserID = '${EscapeSQLString(user.ID)}'`,\n Fields: ['ID'],\n });\n if (conversationIds == null) {\n return ownTasks;\n }\n const messageIds = await readIds(rv, user, {\n EntityName: 'MJ: Conversation Details',\n ExtraFilter: taskMessagesFilter(user, conversationIds, tasks),\n Fields: ['ID'],\n });\n if (messageIds == null || messageIds.length === 0) {\n return ownTasks;\n }\n return `ParentID IS NULL AND (UserID = '${EscapeSQLString(user.ID)}' OR ConversationDetailID IN (${sqlInList(messageIds)}))`;\n}\n\n/**\n * The messages that have a top-level task and that the user wrote or that are in one of the\n * user's conversations. The task subquery reads Tasks, which has no row filter in a default\n * install. Its identifiers are bracketed so the PostgreSQL provider quotes them.\n */\nfunction taskMessagesFilter(user: UserInfo, conversationIds: readonly string[], tasks: EntityInfo): string {\n const inConversations = conversationIds.length > 0 ? ` OR ConversationID IN (${sqlInList(conversationIds)})` : '';\n const taskView = `[${tasks.SchemaName}].[${tasks.BaseView}]`;\n return (\n `(UserID = '${EscapeSQLString(user.ID)}'${inConversations}) AND ID IN ` +\n `(SELECT [ConversationDetailID] FROM ${taskView} WHERE [ParentID] IS NULL AND [ConversationDetailID] IS NOT NULL)`\n );\n}\n\n/**\n * The IDs of the rows `read` selects, with no row cap; null when the read fails. Each caller names\n * an MJ core entity in `read`, so its key column is `ID`.\n */\nasync function readIds(rv: RunView, user: UserInfo, read: Pick<RunViewParams, 'EntityName' | 'ExtraFilter' | 'Fields'>): Promise<string[] | null> {\n const result = await rv.RunView<{ ID: string }>({ ...read, ResultType: 'simple', IgnoreMaxRows: true }, user);\n if (!result.Success) {\n console.error(`Failed to read ${read.EntityName} for the tasks filter:`, result.ErrorMessage);\n return null;\n }\n return (result.Results ?? []).map(row => row.ID);\n}\n\n/** A quoted, comma-separated list of IDs for an IN clause. */\nfunction sqlInList(ids: readonly string[]): string {\n return ids.map(id => `'${EscapeSQLString(id)}'`).join(',');\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { MJConversationEntity, MJConversationDetailEntity } from '@memberjunction/core-entities';
|
|
1
|
+
import { ConversationScope, MJConversationEntity, MJConversationDetailEntity } from '@memberjunction/core-entities';
|
|
2
2
|
import { UserInfo, IMetadataProvider } from '@memberjunction/core';
|
|
3
3
|
import * as i0 from "@angular/core";
|
|
4
4
|
/**
|
|
@@ -72,15 +72,23 @@ export declare class DataCacheService {
|
|
|
72
72
|
/** @deprecated Use {@link CreateConversationDetail}. */
|
|
73
73
|
createConversationDetail(currentUser: UserInfo): Promise<MJConversationDetailEntity>;
|
|
74
74
|
/**
|
|
75
|
-
* Load
|
|
76
|
-
*
|
|
75
|
+
* Load the ConversationDetail entities of one scope of a conversation (Main, or one fork's
|
|
76
|
+
* path) that no rerun replaced, and cache them
|
|
77
77
|
* @param conversationId The conversation ID
|
|
78
78
|
* @param currentUser User context
|
|
79
|
+
* @param scope The rows to read; Main when omitted
|
|
79
80
|
* @returns Array of MJConversationDetailEntity objects
|
|
81
|
+
* @throws when the scope belongs to another conversation
|
|
80
82
|
*/
|
|
81
|
-
LoadConversationDetails(conversationId: string, currentUser: UserInfo): Promise<MJConversationDetailEntity[]>;
|
|
83
|
+
LoadConversationDetails(conversationId: string, currentUser: UserInfo, scope?: ConversationScope): Promise<MJConversationDetailEntity[]>;
|
|
84
|
+
/**
|
|
85
|
+
* Adds loaded details to the cache, replacing any cached detail with the same ID in place.
|
|
86
|
+
* Positions are indexed once up front: the cache spans every loaded conversation, so searching
|
|
87
|
+
* it for each loaded detail grew with both.
|
|
88
|
+
*/
|
|
89
|
+
private upsertCachedDetails;
|
|
82
90
|
/** @deprecated Use {@link LoadConversationDetails}. */
|
|
83
|
-
loadConversationDetails(conversationId: string, currentUser: UserInfo): Promise<MJConversationDetailEntity[]>;
|
|
91
|
+
loadConversationDetails(conversationId: string, currentUser: UserInfo, scope?: ConversationScope): Promise<MJConversationDetailEntity[]>;
|
|
84
92
|
/**
|
|
85
93
|
* Get all cached ConversationDetail entities for a conversation (no DB call)
|
|
86
94
|
* @param conversationId The conversation ID
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data-cache.service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/data-cache.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"data-cache.service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/data-cache.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Injectable } from '@angular/core';
|
|
2
|
+
import { ConversationEngine } from '@memberjunction/core-entities';
|
|
2
3
|
import { Metadata, RunView } from '@memberjunction/core';
|
|
3
|
-
import { UUIDsEqual } from '@memberjunction/global';
|
|
4
|
+
import { UUIDMap, UUIDsEqual } from '@memberjunction/global';
|
|
4
5
|
import * as i0 from "@angular/core";
|
|
5
6
|
/**
|
|
6
7
|
* Global singleton cache for conversation-related entities.
|
|
@@ -148,46 +149,65 @@ export class DataCacheService {
|
|
|
148
149
|
return this.CreateConversationDetail(currentUser);
|
|
149
150
|
}
|
|
150
151
|
/**
|
|
151
|
-
* Load
|
|
152
|
-
*
|
|
152
|
+
* Load the ConversationDetail entities of one scope of a conversation (Main, or one fork's
|
|
153
|
+
* path) that no rerun replaced, and cache them
|
|
153
154
|
* @param conversationId The conversation ID
|
|
154
155
|
* @param currentUser User context
|
|
156
|
+
* @param scope The rows to read; Main when omitted
|
|
155
157
|
* @returns Array of MJConversationDetailEntity objects
|
|
158
|
+
* @throws when the scope belongs to another conversation
|
|
156
159
|
*/
|
|
157
|
-
async LoadConversationDetails(conversationId, currentUser) {
|
|
160
|
+
async LoadConversationDetails(conversationId, currentUser, scope) {
|
|
161
|
+
if (scope && !UUIDsEqual(scope.ConversationID, conversationId)) {
|
|
162
|
+
throw new Error(`Conversation ${conversationId} cannot be read with the scope of conversation ${scope.ConversationID}`);
|
|
163
|
+
}
|
|
164
|
+
const resolved = scope ?? ConversationEngine.TrunkScope(conversationId);
|
|
158
165
|
const timestamp = new Date().toISOString();
|
|
159
166
|
console.log(`[${timestamp}] 💾 DataCacheService.loadConversationDetails - Loading messages for conversation ${conversationId}`);
|
|
160
167
|
const rv = RunView.FromMetadataProvider(this.Provider);
|
|
161
168
|
console.log(`[${timestamp}] 💾 DataCacheService - Executing RunView for Conversation Details`);
|
|
162
169
|
const result = await rv.RunView({
|
|
163
170
|
EntityName: 'MJ: Conversation Details',
|
|
164
|
-
ExtraFilter:
|
|
171
|
+
ExtraFilter: ConversationEngine.LiveRowsFilter(ConversationEngine.ScopeFilter(resolved)),
|
|
165
172
|
OrderBy: '__mj_CreatedAt ASC',
|
|
166
173
|
ResultType: 'entity_object'
|
|
167
174
|
}, currentUser);
|
|
168
175
|
if (result.Success) {
|
|
169
176
|
const details = result.Results || [];
|
|
170
177
|
console.log(`[${timestamp}] 💾 DataCacheService - Loaded ${details.length} message(s) for conversation ${conversationId}`);
|
|
171
|
-
|
|
172
|
-
for (const detail of details) {
|
|
173
|
-
const existingIndex = this.conversationDetails.findIndex(d => UUIDsEqual(d.ID, detail.ID));
|
|
174
|
-
if (existingIndex >= 0) {
|
|
175
|
-
// Replace existing (ensures we keep the same object reference)
|
|
176
|
-
this.conversationDetails[existingIndex] = detail;
|
|
177
|
-
}
|
|
178
|
-
else {
|
|
179
|
-
// Add new
|
|
180
|
-
this.conversationDetails.push(detail);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
178
|
+
this.upsertCachedDetails(details);
|
|
183
179
|
return details;
|
|
184
180
|
}
|
|
185
181
|
console.log(`[${timestamp}] 💾 DataCacheService - Failed to load messages for conversation ${conversationId}`);
|
|
186
182
|
return [];
|
|
187
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* Adds loaded details to the cache, replacing any cached detail with the same ID in place.
|
|
186
|
+
* Positions are indexed once up front: the cache spans every loaded conversation, so searching
|
|
187
|
+
* it for each loaded detail grew with both.
|
|
188
|
+
*/
|
|
189
|
+
upsertCachedDetails(details) {
|
|
190
|
+
const positionByID = new UUIDMap();
|
|
191
|
+
this.conversationDetails.forEach((d, i) => {
|
|
192
|
+
if (!positionByID.Has(d.ID))
|
|
193
|
+
positionByID.Set(d.ID, i);
|
|
194
|
+
});
|
|
195
|
+
for (const detail of details) {
|
|
196
|
+
const existingIndex = positionByID.Get(detail.ID);
|
|
197
|
+
if (existingIndex !== undefined) {
|
|
198
|
+
// Replace existing (ensures we keep the same object reference)
|
|
199
|
+
this.conversationDetails[existingIndex] = detail;
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
// Add new
|
|
203
|
+
positionByID.Set(detail.ID, this.conversationDetails.length);
|
|
204
|
+
this.conversationDetails.push(detail);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
188
208
|
/** @deprecated Use {@link LoadConversationDetails}. */
|
|
189
|
-
async loadConversationDetails(conversationId, currentUser) {
|
|
190
|
-
return this.LoadConversationDetails(conversationId, currentUser);
|
|
209
|
+
async loadConversationDetails(conversationId, currentUser, scope) {
|
|
210
|
+
return this.LoadConversationDetails(conversationId, currentUser, scope);
|
|
191
211
|
}
|
|
192
212
|
/**
|
|
193
213
|
* Get all cached ConversationDetail entities for a conversation (no DB call)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data-cache.service.js","sourceRoot":"","sources":["../../../src/lib/services/data-cache.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable } from '@angular/core';\nimport { MJConversationEntity, MJConversationDetailEntity } from '@memberjunction/core-entities';\nimport { Metadata, UserInfo, BaseEntity, RunView, IMetadataProvider } from '@memberjunction/core';\nimport { UUIDsEqual } from '@memberjunction/global';\n\n/**\n * Global singleton cache for conversation-related entities.\n * Maintains single instances of entities to prevent race conditions and ensure\n * all components see the same object state.\n *\n * Pattern: Instead of creating new entity instances with GetEntityObject() everywhere,\n * request entities from this cache. The cache returns the same instance each time,\n * so when one component updates entity.Status = 'Complete', all other components\n * see that change immediately without needing to reload from the database.\n */\n@Injectable({\n providedIn: 'root'\n})\nexport class DataCacheService {\n // Separate arrays for each entity type\n private conversations: MJConversationEntity[] = [];\n private conversationDetails: MJConversationDetailEntity[] = [];\n\n private _provider: IMetadataProvider | null = null;\n\n constructor() {}\n\n /**\n * Set the metadata provider this service should use. When unset, falls back to Metadata.Provider.\n */\n public set Provider(value: IMetadataProvider | null) {\n this._provider = value;\n }\n\n public get Provider(): IMetadataProvider {\n return this._provider ?? Metadata.Provider;\n }\n\n // =============================================================================\n // MJConversationEntity Methods\n // =============================================================================\n\n /**\n * Get a MJConversationEntity by ID from cache, or load it if not cached\n * @param id The conversation ID\n * @param currentUser User context for loading\n * @returns The cached or loaded MJConversationEntity, or null if not found\n */\n async GetConversation(id: string, currentUser: UserInfo): Promise<MJConversationEntity | null> {\n // Check cache first\n const cached = this.conversations.find(c => UUIDsEqual(c.ID, id));\n if (cached) {\n return cached;\n }\n\n // Not in cache - load from DB\n const md = this.Provider;\n const conversation = await md.GetEntityObject<MJConversationEntity>('MJ: Conversations', currentUser);\n const loaded = await conversation.Load(id);\n\n if (loaded) {\n // Add to cache\n this.conversations.push(conversation);\n return conversation;\n }\n\n return null;\n }\n\n /** @deprecated Use {@link GetConversation}. */\n async getConversation(id: string, currentUser: UserInfo): Promise<MJConversationEntity | null> {\n return this.GetConversation(id, currentUser);\n }\n\n /**\n * Create a new MJConversationEntity and automatically cache it\n * The cache is the ONLY place GetEntityObject() should be called\n * @param currentUser User context\n * @returns New MJConversationEntity instance (already cached)\n */\n async CreateConversation(currentUser: UserInfo): Promise<MJConversationEntity> {\n const md = this.Provider;\n const conversation = await md.GetEntityObject<MJConversationEntity>('MJ: Conversations', currentUser);\n\n // Automatically add to cache - user code doesn't need to do anything\n // Note: Conversation has no ID yet (not saved), so we can't deduplicate\n // After Save(), the ID will be populated and future getConversation() calls will find it\n this.conversations.push(conversation);\n\n return conversation;\n }\n\n /** @deprecated Use {@link CreateConversation}. */\n async createConversation(currentUser: UserInfo): Promise<MJConversationEntity> {\n return this.CreateConversation(currentUser);\n }\n\n /**\n * Remove a MJConversationEntity from the cache\n * @param id The conversation ID to remove\n */\n RemoveConversation(id: string): void {\n this.conversations = this.conversations.filter(c => !UUIDsEqual(c.ID, id));\n }\n\n /** @deprecated Use {@link RemoveConversation}. */\n removeConversation(id: string): void {\n return this.RemoveConversation(id);\n }\n\n /**\n * Get all cached MJConversationEntity objects (no DB call)\n * @returns Array of cached conversations\n */\n GetCachedConversations(): MJConversationEntity[] {\n return this.conversations;\n }\n\n /** @deprecated Use {@link GetCachedConversations}. */\n getCachedConversations(): MJConversationEntity[] {\n return this.GetCachedConversations();\n }\n\n // =============================================================================\n // MJConversationDetailEntity Methods\n // =============================================================================\n\n /**\n * Get a MJConversationDetailEntity by ID from cache, or load it if not cached\n * @param id The conversation detail ID\n * @param currentUser User context for loading\n * @returns The cached or loaded MJConversationDetailEntity, or null if not found\n */\n async GetConversationDetail(id: string, currentUser: UserInfo): Promise<MJConversationDetailEntity | null> {\n // Check cache first\n const cached = this.conversationDetails.find(d => UUIDsEqual(d.ID, id));\n if (cached) {\n return cached;\n }\n\n // Not in cache - load from DB\n const md = this.Provider;\n const detail = await md.GetEntityObject<MJConversationDetailEntity>('MJ: Conversation Details', currentUser);\n const loaded = await detail.Load(id);\n\n if (loaded) {\n // Add to cache\n this.conversationDetails.push(detail);\n return detail;\n }\n\n return null;\n }\n\n /** @deprecated Use {@link GetConversationDetail}. */\n async getConversationDetail(id: string, currentUser: UserInfo): Promise<MJConversationDetailEntity | null> {\n return this.GetConversationDetail(id, currentUser);\n }\n\n /**\n * Create a new MJConversationDetailEntity and automatically cache it\n * The cache is the ONLY place GetEntityObject() should be called\n * @param currentUser User context\n * @returns New MJConversationDetailEntity instance (already cached)\n */\n async CreateConversationDetail(currentUser: UserInfo): Promise<MJConversationDetailEntity> {\n const md = this.Provider;\n const detail = await md.GetEntityObject<MJConversationDetailEntity>('MJ: Conversation Details', currentUser);\n\n // Automatically add to cache - user code doesn't need to do anything\n // Note: Detail has no ID yet (not saved), so we can't deduplicate\n // After Save(), the ID will be populated and future getConversationDetail() calls will find it\n\n this.conversationDetails.push(detail);\n\n return detail;\n }\n\n /** @deprecated Use {@link CreateConversationDetail}. */\n async createConversationDetail(currentUser: UserInfo): Promise<MJConversationDetailEntity> {\n return this.CreateConversationDetail(currentUser);\n }\n\n /**\n * Load all ConversationDetail entities for a conversation and cache them\n * Used when loading a conversation's message history\n * @param conversationId The conversation ID\n * @param currentUser User context\n * @returns Array of MJConversationDetailEntity objects\n */\n async LoadConversationDetails(conversationId: string, currentUser: UserInfo): Promise<MJConversationDetailEntity[]> {\n const timestamp = new Date().toISOString();\n console.log(`[${timestamp}] 💾 DataCacheService.loadConversationDetails - Loading messages for conversation ${conversationId}`);\n\n const rv = RunView.FromMetadataProvider(this.Provider);\n\n console.log(`[${timestamp}] 💾 DataCacheService - Executing RunView for Conversation Details`);\n const result = await rv.RunView<MJConversationDetailEntity>(\n {\n EntityName: 'MJ: Conversation Details',\n ExtraFilter: `ConversationID='${conversationId}'`,\n OrderBy: '__mj_CreatedAt ASC',\n ResultType: 'entity_object'\n },\n currentUser\n );\n\n if (result.Success) {\n const details = result.Results || [];\n console.log(`[${timestamp}] 💾 DataCacheService - Loaded ${details.length} message(s) for conversation ${conversationId}`);\n\n // Add all to cache (avoid duplicates by checking ID)\n for (const detail of details) {\n const existingIndex = this.conversationDetails.findIndex(d => UUIDsEqual(d.ID, detail.ID));\n if (existingIndex >= 0) {\n // Replace existing (ensures we keep the same object reference)\n this.conversationDetails[existingIndex] = detail;\n } else {\n // Add new\n this.conversationDetails.push(detail);\n }\n }\n\n return details;\n }\n\n console.log(`[${timestamp}] 💾 DataCacheService - Failed to load messages for conversation ${conversationId}`);\n return [];\n }\n\n /** @deprecated Use {@link LoadConversationDetails}. */\n async loadConversationDetails(conversationId: string, currentUser: UserInfo): Promise<MJConversationDetailEntity[]> {\n return this.LoadConversationDetails(conversationId, currentUser);\n }\n\n /**\n * Get all cached ConversationDetail entities for a conversation (no DB call)\n * @param conversationId The conversation ID\n * @returns Array of cached conversation details\n */\n GetCachedConversationDetails(conversationId: string): MJConversationDetailEntity[] {\n return this.conversationDetails.filter(d => UUIDsEqual(d.ConversationID, conversationId));\n }\n\n /** @deprecated Use {@link GetCachedConversationDetails}. */\n getCachedConversationDetails(conversationId: string): MJConversationDetailEntity[] {\n return this.GetCachedConversationDetails(conversationId);\n }\n\n /**\n * Remove a MJConversationDetailEntity from the cache\n * @param id The conversation detail ID to remove\n */\n RemoveConversationDetail(id: string): void {\n this.conversationDetails = this.conversationDetails.filter(d => !UUIDsEqual(d.ID, id));\n }\n\n /** @deprecated Use {@link RemoveConversationDetail}. */\n removeConversationDetail(id: string): void {\n return this.RemoveConversationDetail(id);\n }\n\n // =============================================================================\n // Cache Management\n // =============================================================================\n\n /**\n * Refresh entity data by clearing cached entities of a specific type\n * This forces a reload from the database on next access\n * @param entityName The entity name to refresh (e.g., 'Conversations', 'MJ: Conversation Details')\n */\n async RefreshEntity(entityName: string): Promise<void> {\n switch (entityName) {\n case 'Conversations':\n this.conversations = [];\n break;\n case 'MJ: Conversation Details':\n this.conversationDetails = [];\n break;\n default:\n console.warn('Unknown entity name for refresh:', entityName);\n }\n }\n\n /** @deprecated Use {@link RefreshEntity}. */\n async refreshEntity(entityName: string): Promise<void> {\n return this.RefreshEntity(entityName);\n }\n\n /**\n * Refresh a specific cache by name\n * This method can be extended to handle different cache types\n * @param cacheName The cache name ('Core', 'AI', 'Actions')\n */\n async RefreshCache(cacheName: 'Core' | 'AI' | 'Actions'): Promise<void> {\n switch (cacheName) {\n case 'Core':\n // Clear core entity caches\n this.Clear();\n break;\n case 'AI':\n // AI-specific cache refresh would go here\n // For now, just log it\n console.log('AI cache refresh requested (not yet implemented)');\n break;\n case 'Actions':\n // Actions-specific cache refresh would go here\n console.log('Actions cache refresh requested (not yet implemented)');\n break;\n default:\n console.warn('Unknown cache name for refresh:', cacheName);\n }\n }\n\n /** @deprecated Use {@link RefreshCache}. */\n async refreshCache(cacheName: 'Core' | 'AI' | 'Actions'): Promise<void> {\n return this.RefreshCache(cacheName);\n }\n\n /**\n * Clear all cached entities\n * Call when switching environments, users, or when needed\n */\n Clear(): void {\n this.conversations = [];\n this.conversationDetails = [];\n }\n\n /** @deprecated Use {@link Clear}. */\n clear(): void {\n return this.Clear();\n }\n\n /**\n * Clear cached entities for a specific conversation\n * @param conversationId The conversation ID\n */\n ClearConversation(conversationId: string): void {\n this.conversations = this.conversations.filter(c => !UUIDsEqual(c.ID, conversationId));\n this.conversationDetails = this.conversationDetails.filter(d => !UUIDsEqual(d.ConversationID, conversationId));\n }\n\n /** @deprecated Use {@link ClearConversation}. */\n clearConversation(conversationId: string): void {\n return this.ClearConversation(conversationId);\n }\n\n /**\n * Get cache statistics for debugging\n * @returns Object with cache sizes\n */\n GetStats(): { conversations: number; conversationDetails: number } {\n return {\n conversations: this.conversations.length,\n conversationDetails: this.conversationDetails.length\n };\n }\n\n /** @deprecated Use {@link GetStats}. */\n getStats(): { conversations: number; conversationDetails: number } {\n return this.GetStats();\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"data-cache.service.js","sourceRoot":"","sources":["../../../src/lib/services/data-cache.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable } from '@angular/core';\nimport { ConversationEngine, ConversationScope, MJConversationEntity, MJConversationDetailEntity } from '@memberjunction/core-entities';\nimport { Metadata, UserInfo, BaseEntity, RunView, IMetadataProvider } from '@memberjunction/core';\nimport { UUIDMap, UUIDsEqual } from '@memberjunction/global';\n\n/**\n * Global singleton cache for conversation-related entities.\n * Maintains single instances of entities to prevent race conditions and ensure\n * all components see the same object state.\n *\n * Pattern: Instead of creating new entity instances with GetEntityObject() everywhere,\n * request entities from this cache. The cache returns the same instance each time,\n * so when one component updates entity.Status = 'Complete', all other components\n * see that change immediately without needing to reload from the database.\n */\n@Injectable({\n providedIn: 'root'\n})\nexport class DataCacheService {\n // Separate arrays for each entity type\n private conversations: MJConversationEntity[] = [];\n private conversationDetails: MJConversationDetailEntity[] = [];\n\n private _provider: IMetadataProvider | null = null;\n\n constructor() {}\n\n /**\n * Set the metadata provider this service should use. When unset, falls back to Metadata.Provider.\n */\n public set Provider(value: IMetadataProvider | null) {\n this._provider = value;\n }\n\n public get Provider(): IMetadataProvider {\n return this._provider ?? Metadata.Provider;\n }\n\n // =============================================================================\n // MJConversationEntity Methods\n // =============================================================================\n\n /**\n * Get a MJConversationEntity by ID from cache, or load it if not cached\n * @param id The conversation ID\n * @param currentUser User context for loading\n * @returns The cached or loaded MJConversationEntity, or null if not found\n */\n async GetConversation(id: string, currentUser: UserInfo): Promise<MJConversationEntity | null> {\n // Check cache first\n const cached = this.conversations.find(c => UUIDsEqual(c.ID, id));\n if (cached) {\n return cached;\n }\n\n // Not in cache - load from DB\n const md = this.Provider;\n const conversation = await md.GetEntityObject<MJConversationEntity>('MJ: Conversations', currentUser);\n const loaded = await conversation.Load(id);\n\n if (loaded) {\n // Add to cache\n this.conversations.push(conversation);\n return conversation;\n }\n\n return null;\n }\n\n /** @deprecated Use {@link GetConversation}. */\n async getConversation(id: string, currentUser: UserInfo): Promise<MJConversationEntity | null> {\n return this.GetConversation(id, currentUser);\n }\n\n /**\n * Create a new MJConversationEntity and automatically cache it\n * The cache is the ONLY place GetEntityObject() should be called\n * @param currentUser User context\n * @returns New MJConversationEntity instance (already cached)\n */\n async CreateConversation(currentUser: UserInfo): Promise<MJConversationEntity> {\n const md = this.Provider;\n const conversation = await md.GetEntityObject<MJConversationEntity>('MJ: Conversations', currentUser);\n\n // Automatically add to cache - user code doesn't need to do anything\n // Note: Conversation has no ID yet (not saved), so we can't deduplicate\n // After Save(), the ID will be populated and future getConversation() calls will find it\n this.conversations.push(conversation);\n\n return conversation;\n }\n\n /** @deprecated Use {@link CreateConversation}. */\n async createConversation(currentUser: UserInfo): Promise<MJConversationEntity> {\n return this.CreateConversation(currentUser);\n }\n\n /**\n * Remove a MJConversationEntity from the cache\n * @param id The conversation ID to remove\n */\n RemoveConversation(id: string): void {\n this.conversations = this.conversations.filter(c => !UUIDsEqual(c.ID, id));\n }\n\n /** @deprecated Use {@link RemoveConversation}. */\n removeConversation(id: string): void {\n return this.RemoveConversation(id);\n }\n\n /**\n * Get all cached MJConversationEntity objects (no DB call)\n * @returns Array of cached conversations\n */\n GetCachedConversations(): MJConversationEntity[] {\n return this.conversations;\n }\n\n /** @deprecated Use {@link GetCachedConversations}. */\n getCachedConversations(): MJConversationEntity[] {\n return this.GetCachedConversations();\n }\n\n // =============================================================================\n // MJConversationDetailEntity Methods\n // =============================================================================\n\n /**\n * Get a MJConversationDetailEntity by ID from cache, or load it if not cached\n * @param id The conversation detail ID\n * @param currentUser User context for loading\n * @returns The cached or loaded MJConversationDetailEntity, or null if not found\n */\n async GetConversationDetail(id: string, currentUser: UserInfo): Promise<MJConversationDetailEntity | null> {\n // Check cache first\n const cached = this.conversationDetails.find(d => UUIDsEqual(d.ID, id));\n if (cached) {\n return cached;\n }\n\n // Not in cache - load from DB\n const md = this.Provider;\n const detail = await md.GetEntityObject<MJConversationDetailEntity>('MJ: Conversation Details', currentUser);\n const loaded = await detail.Load(id);\n\n if (loaded) {\n // Add to cache\n this.conversationDetails.push(detail);\n return detail;\n }\n\n return null;\n }\n\n /** @deprecated Use {@link GetConversationDetail}. */\n async getConversationDetail(id: string, currentUser: UserInfo): Promise<MJConversationDetailEntity | null> {\n return this.GetConversationDetail(id, currentUser);\n }\n\n /**\n * Create a new MJConversationDetailEntity and automatically cache it\n * The cache is the ONLY place GetEntityObject() should be called\n * @param currentUser User context\n * @returns New MJConversationDetailEntity instance (already cached)\n */\n async CreateConversationDetail(currentUser: UserInfo): Promise<MJConversationDetailEntity> {\n const md = this.Provider;\n const detail = await md.GetEntityObject<MJConversationDetailEntity>('MJ: Conversation Details', currentUser);\n\n // Automatically add to cache - user code doesn't need to do anything\n // Note: Detail has no ID yet (not saved), so we can't deduplicate\n // After Save(), the ID will be populated and future getConversationDetail() calls will find it\n\n this.conversationDetails.push(detail);\n\n return detail;\n }\n\n /** @deprecated Use {@link CreateConversationDetail}. */\n async createConversationDetail(currentUser: UserInfo): Promise<MJConversationDetailEntity> {\n return this.CreateConversationDetail(currentUser);\n }\n\n /**\n * Load the ConversationDetail entities of one scope of a conversation (Main, or one fork's\n * path) that no rerun replaced, and cache them\n * @param conversationId The conversation ID\n * @param currentUser User context\n * @param scope The rows to read; Main when omitted\n * @returns Array of MJConversationDetailEntity objects\n * @throws when the scope belongs to another conversation\n */\n async LoadConversationDetails(conversationId: string, currentUser: UserInfo, scope?: ConversationScope): Promise<MJConversationDetailEntity[]> {\n if (scope && !UUIDsEqual(scope.ConversationID, conversationId)) {\n throw new Error(`Conversation ${conversationId} cannot be read with the scope of conversation ${scope.ConversationID}`);\n }\n const resolved = scope ?? ConversationEngine.TrunkScope(conversationId);\n const timestamp = new Date().toISOString();\n console.log(`[${timestamp}] 💾 DataCacheService.loadConversationDetails - Loading messages for conversation ${conversationId}`);\n\n const rv = RunView.FromMetadataProvider(this.Provider);\n\n console.log(`[${timestamp}] 💾 DataCacheService - Executing RunView for Conversation Details`);\n const result = await rv.RunView<MJConversationDetailEntity>(\n {\n EntityName: 'MJ: Conversation Details',\n ExtraFilter: ConversationEngine.LiveRowsFilter(ConversationEngine.ScopeFilter(resolved)),\n OrderBy: '__mj_CreatedAt ASC',\n ResultType: 'entity_object'\n },\n currentUser\n );\n\n if (result.Success) {\n const details = result.Results || [];\n console.log(`[${timestamp}] 💾 DataCacheService - Loaded ${details.length} message(s) for conversation ${conversationId}`);\n\n this.upsertCachedDetails(details);\n return details;\n }\n\n console.log(`[${timestamp}] 💾 DataCacheService - Failed to load messages for conversation ${conversationId}`);\n return [];\n }\n\n /**\n * Adds loaded details to the cache, replacing any cached detail with the same ID in place.\n * Positions are indexed once up front: the cache spans every loaded conversation, so searching\n * it for each loaded detail grew with both.\n */\n private upsertCachedDetails(details: MJConversationDetailEntity[]): void {\n const positionByID = new UUIDMap<number>();\n this.conversationDetails.forEach((d, i) => {\n if (!positionByID.Has(d.ID)) positionByID.Set(d.ID, i);\n });\n for (const detail of details) {\n const existingIndex = positionByID.Get(detail.ID);\n if (existingIndex !== undefined) {\n // Replace existing (ensures we keep the same object reference)\n this.conversationDetails[existingIndex] = detail;\n } else {\n // Add new\n positionByID.Set(detail.ID, this.conversationDetails.length);\n this.conversationDetails.push(detail);\n }\n }\n }\n\n /** @deprecated Use {@link LoadConversationDetails}. */\n async loadConversationDetails(conversationId: string, currentUser: UserInfo, scope?: ConversationScope): Promise<MJConversationDetailEntity[]> {\n return this.LoadConversationDetails(conversationId, currentUser, scope);\n }\n\n /**\n * Get all cached ConversationDetail entities for a conversation (no DB call)\n * @param conversationId The conversation ID\n * @returns Array of cached conversation details\n */\n GetCachedConversationDetails(conversationId: string): MJConversationDetailEntity[] {\n return this.conversationDetails.filter(d => UUIDsEqual(d.ConversationID, conversationId));\n }\n\n /** @deprecated Use {@link GetCachedConversationDetails}. */\n getCachedConversationDetails(conversationId: string): MJConversationDetailEntity[] {\n return this.GetCachedConversationDetails(conversationId);\n }\n\n /**\n * Remove a MJConversationDetailEntity from the cache\n * @param id The conversation detail ID to remove\n */\n RemoveConversationDetail(id: string): void {\n this.conversationDetails = this.conversationDetails.filter(d => !UUIDsEqual(d.ID, id));\n }\n\n /** @deprecated Use {@link RemoveConversationDetail}. */\n removeConversationDetail(id: string): void {\n return this.RemoveConversationDetail(id);\n }\n\n // =============================================================================\n // Cache Management\n // =============================================================================\n\n /**\n * Refresh entity data by clearing cached entities of a specific type\n * This forces a reload from the database on next access\n * @param entityName The entity name to refresh (e.g., 'Conversations', 'MJ: Conversation Details')\n */\n async RefreshEntity(entityName: string): Promise<void> {\n switch (entityName) {\n case 'Conversations':\n this.conversations = [];\n break;\n case 'MJ: Conversation Details':\n this.conversationDetails = [];\n break;\n default:\n console.warn('Unknown entity name for refresh:', entityName);\n }\n }\n\n /** @deprecated Use {@link RefreshEntity}. */\n async refreshEntity(entityName: string): Promise<void> {\n return this.RefreshEntity(entityName);\n }\n\n /**\n * Refresh a specific cache by name\n * This method can be extended to handle different cache types\n * @param cacheName The cache name ('Core', 'AI', 'Actions')\n */\n async RefreshCache(cacheName: 'Core' | 'AI' | 'Actions'): Promise<void> {\n switch (cacheName) {\n case 'Core':\n // Clear core entity caches\n this.Clear();\n break;\n case 'AI':\n // AI-specific cache refresh would go here\n // For now, just log it\n console.log('AI cache refresh requested (not yet implemented)');\n break;\n case 'Actions':\n // Actions-specific cache refresh would go here\n console.log('Actions cache refresh requested (not yet implemented)');\n break;\n default:\n console.warn('Unknown cache name for refresh:', cacheName);\n }\n }\n\n /** @deprecated Use {@link RefreshCache}. */\n async refreshCache(cacheName: 'Core' | 'AI' | 'Actions'): Promise<void> {\n return this.RefreshCache(cacheName);\n }\n\n /**\n * Clear all cached entities\n * Call when switching environments, users, or when needed\n */\n Clear(): void {\n this.conversations = [];\n this.conversationDetails = [];\n }\n\n /** @deprecated Use {@link Clear}. */\n clear(): void {\n return this.Clear();\n }\n\n /**\n * Clear cached entities for a specific conversation\n * @param conversationId The conversation ID\n */\n ClearConversation(conversationId: string): void {\n this.conversations = this.conversations.filter(c => !UUIDsEqual(c.ID, conversationId));\n this.conversationDetails = this.conversationDetails.filter(d => !UUIDsEqual(d.ConversationID, conversationId));\n }\n\n /** @deprecated Use {@link ClearConversation}. */\n clearConversation(conversationId: string): void {\n return this.ClearConversation(conversationId);\n }\n\n /**\n * Get cache statistics for debugging\n * @returns Object with cache sizes\n */\n GetStats(): { conversations: number; conversationDetails: number } {\n return {\n conversations: this.conversations.length,\n conversationDetails: this.conversationDetails.length\n };\n }\n\n /** @deprecated Use {@link GetStats}. */\n getStats(): { conversations: number; conversationDetails: number } {\n return this.GetStats();\n }\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { MJConversationEntity, MJConversationDetailEntity } from '@memberjunction/core-entities';
|
|
1
|
+
import { ConversationScope, MJConversationEntity, MJConversationDetailEntity } from '@memberjunction/core-entities';
|
|
2
2
|
import { UserInfo, IMetadataProvider } from '@memberjunction/core';
|
|
3
3
|
import * as i0 from "@angular/core";
|
|
4
4
|
export type ExportFormat = 'json' | 'markdown' | 'html' | 'text';
|
|
@@ -88,6 +88,16 @@ export interface ExportOptions {
|
|
|
88
88
|
* map via {@link ExportBranding.brandTokens} instead.
|
|
89
89
|
*/
|
|
90
90
|
export declare const DEFAULT_EXPORT_THEME_TOKENS: readonly string[];
|
|
91
|
+
/** The conversation and the messages one export writes. */
|
|
92
|
+
export interface ExportConversationData {
|
|
93
|
+
conversation: MJConversationEntity;
|
|
94
|
+
details: MJConversationDetailEntity[];
|
|
95
|
+
/**
|
|
96
|
+
* The fork the messages come from, as the header names it (its display name). Absent or null
|
|
97
|
+
* in Main.
|
|
98
|
+
*/
|
|
99
|
+
fork?: string | null;
|
|
100
|
+
}
|
|
91
101
|
export declare class ExportService {
|
|
92
102
|
private _provider;
|
|
93
103
|
/**
|
|
@@ -95,19 +105,23 @@ export declare class ExportService {
|
|
|
95
105
|
*/
|
|
96
106
|
set Provider(value: IMetadataProvider | null);
|
|
97
107
|
get Provider(): IMetadataProvider;
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
108
|
+
/**
|
|
109
|
+
* Exports the messages of one scope of a conversation (Main, or one fork's path) and
|
|
110
|
+
* downloads the file. In a fork the export header names the fork.
|
|
111
|
+
* @throws when the scope belongs to another conversation, or the conversation is not found.
|
|
112
|
+
*/
|
|
113
|
+
ExportConversation(conversationId: string, scope: ConversationScope, format: ExportFormat, currentUser: UserInfo, options?: ExportOptions): Promise<void>;
|
|
114
|
+
/**
|
|
115
|
+
* @deprecated Use {@link ExportConversation}. Without `scope`, exports Main.
|
|
116
|
+
*/
|
|
117
|
+
exportConversation(conversationId: string, format: ExportFormat, currentUser: UserInfo, options?: ExportOptions, scope?: ConversationScope): Promise<void>;
|
|
101
118
|
/**
|
|
102
119
|
* Build the export document for already-loaded conversation data — the
|
|
103
|
-
* download-free seam of {@link
|
|
120
|
+
* download-free seam of {@link ExportConversation}. Async because HTML-format
|
|
104
121
|
* branding may inline a logo. Exposed publicly so hosts (and tests) can build
|
|
105
122
|
* export content without triggering a browser download.
|
|
106
123
|
*/
|
|
107
|
-
BuildExportContent(data: {
|
|
108
|
-
conversation: MJConversationEntity;
|
|
109
|
-
details: MJConversationDetailEntity[];
|
|
110
|
-
}, format: ExportFormat, options?: ExportOptions): Promise<{
|
|
124
|
+
BuildExportContent(data: ExportConversationData, format: ExportFormat, options?: ExportOptions): Promise<{
|
|
111
125
|
content: string;
|
|
112
126
|
filename: string;
|
|
113
127
|
mimeType: string;
|
|
@@ -164,7 +178,10 @@ export declare class ExportService {
|
|
|
164
178
|
* (or when the response isn't actually an image — don't inline HTML/other as
|
|
165
179
|
* a bogus `data:` image). */
|
|
166
180
|
private inlineLogo;
|
|
181
|
+
/** Loads the conversation and the messages in the scope that no rerun replaced, in Sequence order. */
|
|
167
182
|
private loadConversationData;
|
|
183
|
+
/** The header name of the scope's fork: its display name; null in Main. */
|
|
184
|
+
private forkLabel;
|
|
168
185
|
private exportAsJSON;
|
|
169
186
|
private exportAsMarkdown;
|
|
170
187
|
private exportAsHTML;
|