@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
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
import { IsForkingSettingOn, MAX_BRANCH_DEPTH } from '@memberjunction/core-entities';
|
|
2
|
+
import { IndexByUUID, NormalizeUUID, UUIDSet, UUIDsEqual } from '@memberjunction/global';
|
|
3
|
+
/** The Main view. */
|
|
4
|
+
export const MAIN_OPEN_VIEW = { Kind: 'Main' };
|
|
5
|
+
/**
|
|
6
|
+
* True when two views show the same thing. Fork ids, and the replaced messages of two drafts, are
|
|
7
|
+
* compared without regard to case.
|
|
8
|
+
*/
|
|
9
|
+
export function SameOpenView(a, b) {
|
|
10
|
+
if (a.Kind === 'Fork' && b.Kind === 'Fork') {
|
|
11
|
+
return UUIDsEqual(a.BranchID, b.BranchID);
|
|
12
|
+
}
|
|
13
|
+
if (a.Kind === 'DraftFork' && b.Kind === 'DraftFork') {
|
|
14
|
+
return UUIDsEqual(a.SourceDetailID, b.SourceDetailID);
|
|
15
|
+
}
|
|
16
|
+
return a.Kind === b.Kind;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The branch whose path the view reads: null for Main, the fork for a fork. A draft reads its
|
|
20
|
+
* parent's path.
|
|
21
|
+
*/
|
|
22
|
+
export function ReadBranchIdOf(view) {
|
|
23
|
+
switch (view.Kind) {
|
|
24
|
+
case 'Fork':
|
|
25
|
+
return view.BranchID;
|
|
26
|
+
case 'DraftFork':
|
|
27
|
+
return view.ParentBranchID;
|
|
28
|
+
default:
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* True when a new row belongs on screen in the view: a Main row in Main, a row of the fork in that
|
|
34
|
+
* fork. A draft has no rows of its own, so no new row belongs in it.
|
|
35
|
+
*/
|
|
36
|
+
export function IsRowInOpenView(view, row) {
|
|
37
|
+
switch (view.Kind) {
|
|
38
|
+
case 'Fork':
|
|
39
|
+
return UUIDsEqual(row.BranchID ?? null, view.BranchID);
|
|
40
|
+
case 'DraftFork':
|
|
41
|
+
return false;
|
|
42
|
+
default:
|
|
43
|
+
return row.BranchID == null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The fork one level up from a view; null is Main. A fork goes up to its ParentBranchID (Main when its
|
|
48
|
+
* row is not in `rows`); a draft goes back to the fork it started from, its ParentBranchID.
|
|
49
|
+
*/
|
|
50
|
+
export function ParentForkIdOf(view, rows) {
|
|
51
|
+
switch (view.Kind) {
|
|
52
|
+
case 'Fork':
|
|
53
|
+
return rows.find(r => UUIDsEqual(r.ID, view.BranchID))?.ParentBranchID ?? null;
|
|
54
|
+
case 'DraftFork':
|
|
55
|
+
return view.ParentBranchID;
|
|
56
|
+
default:
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The fork ids of a fork's chain, from the fork under Main down to `branchId`, following ParentBranchID.
|
|
62
|
+
* The chain stops at a fork whose row is not in `rows`, at a fork seen before, and at
|
|
63
|
+
* {@link MAX_BRANCH_DEPTH} forks. Empty for null (Main).
|
|
64
|
+
*/
|
|
65
|
+
export function ForkChainOf(branchId, rows) {
|
|
66
|
+
const chain = [];
|
|
67
|
+
const seen = new Set();
|
|
68
|
+
const byId = IndexByUUID(rows);
|
|
69
|
+
let id = branchId;
|
|
70
|
+
while (id && !seen.has(NormalizeUUID(id)) && chain.length < MAX_BRANCH_DEPTH) {
|
|
71
|
+
seen.add(NormalizeUUID(id));
|
|
72
|
+
chain.unshift(id);
|
|
73
|
+
id = byId.Get(id)?.ParentBranchID ?? null;
|
|
74
|
+
}
|
|
75
|
+
return chain;
|
|
76
|
+
}
|
|
77
|
+
/** The label of the last crumb of a draft fork. */
|
|
78
|
+
export const DRAFT_FORK_TITLE = 'New fork';
|
|
79
|
+
/** The end of a draft's fork marker. */
|
|
80
|
+
export const DRAFT_MARKER_SUFFIX = ' · nothing is saved until you send';
|
|
81
|
+
/**
|
|
82
|
+
* The breadcrumb of a fork or draft view: the conversation (opens Main), each ancestor fork from the
|
|
83
|
+
* one under Main down, then the open fork (a draft: "New fork"). Empty for Main.
|
|
84
|
+
*/
|
|
85
|
+
export function BuildForkBreadcrumbs(view, conversationName, rows, forkLabel) {
|
|
86
|
+
if (view.Kind === 'Main') {
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
const ancestors = view.Kind === 'Fork' ? ForkChainOf(view.BranchID, rows).slice(0, -1) : ForkChainOf(view.ParentBranchID, rows);
|
|
90
|
+
const crumbs = [{ BranchID: null, Label: conversationName, IsCurrent: false }];
|
|
91
|
+
for (const id of ancestors) {
|
|
92
|
+
crumbs.push({ BranchID: id, Label: forkLabel(id), IsCurrent: false });
|
|
93
|
+
}
|
|
94
|
+
crumbs.push(view.Kind === 'Fork'
|
|
95
|
+
? { BranchID: view.BranchID, Label: forkLabel(view.BranchID), IsCurrent: true }
|
|
96
|
+
: { BranchID: null, Label: DRAFT_FORK_TITLE, IsCurrent: true });
|
|
97
|
+
return crumbs;
|
|
98
|
+
}
|
|
99
|
+
/** Paths longer than this show the first crumb, an ellipsis, and the last two entries. */
|
|
100
|
+
export const MAX_FULL_PATH_ENTRIES = 3;
|
|
101
|
+
/**
|
|
102
|
+
* The header path of a breadcrumb: the first crumb (the conversation) always shows; past three entries the
|
|
103
|
+
* crumbs between it and the parent of the current entry collapse into an ellipsis.
|
|
104
|
+
*/
|
|
105
|
+
export function BuildForkHeaderPath(crumbs) {
|
|
106
|
+
const parents = crumbs.filter(c => !c.IsCurrent);
|
|
107
|
+
const collapsesWide = crumbs.length > MAX_FULL_PATH_ENTRIES;
|
|
108
|
+
const lastParent = parents.length - 1;
|
|
109
|
+
return {
|
|
110
|
+
Parents: parents.map((crumb, i) => ({
|
|
111
|
+
Crumb: crumb,
|
|
112
|
+
IsMiddle: i > 0,
|
|
113
|
+
CollapsedWide: collapsesWide && i > 0 && i < lastParent,
|
|
114
|
+
})),
|
|
115
|
+
CollapsesWide: collapsesWide,
|
|
116
|
+
HasMiddle: parents.length > 1,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/** The label of the fork view's back button: "Back to <parent fork>", or "Back to Main" with no parent. */
|
|
120
|
+
export function BackLabelOf(parentLabel) {
|
|
121
|
+
return parentLabel ? `Back to ${parentLabel}` : 'Back to Main';
|
|
122
|
+
}
|
|
123
|
+
/** The chips of a message with no fork placed under it. */
|
|
124
|
+
export const EMPTY_FORK_CHIPS = [];
|
|
125
|
+
/** The name of Main on an original chip. */
|
|
126
|
+
export const MAIN_VIEW_LABEL = 'Main';
|
|
127
|
+
/** The Font Awesome icon of a fork kind. */
|
|
128
|
+
export function ForkKindIcon(kind) {
|
|
129
|
+
switch (kind) {
|
|
130
|
+
case 'Edit':
|
|
131
|
+
return 'fa-solid fa-pen';
|
|
132
|
+
case 'Regenerate':
|
|
133
|
+
return 'fa-solid fa-rotate-left';
|
|
134
|
+
default:
|
|
135
|
+
return 'fa-solid fa-code-branch';
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/** The icon of a chip: the kind icon of a fork chip; on an original chip, a house for Main and the fork icon for a fork. */
|
|
139
|
+
export function ForkChipIcon(chip) {
|
|
140
|
+
if (chip.Kind === 'Original') {
|
|
141
|
+
return chip.BranchID ? 'fa-solid fa-code-branch' : 'fa-solid fa-house';
|
|
142
|
+
}
|
|
143
|
+
return ForkKindIcon(chip.Kind);
|
|
144
|
+
}
|
|
145
|
+
/** The aria-label of a chip: "Open fork <name>, <N messages>", or "Open the original in <name>". */
|
|
146
|
+
export function ForkChipAriaLabel(chip) {
|
|
147
|
+
return chip.Kind === 'Original'
|
|
148
|
+
? `Open the original in ${chip.DisplayName}`
|
|
149
|
+
: `Open fork ${chip.DisplayName}, ${chip.MessageLabel}`;
|
|
150
|
+
}
|
|
151
|
+
/** The request a clicked chip sends: its fork, or for an original chip, its view and the replaced message. */
|
|
152
|
+
export function ForkChipOpenRequestOf(chip) {
|
|
153
|
+
return chip.Kind === 'Original' ? { BranchID: chip.BranchID, Sequence: chip.Sequence } : { BranchID: chip.BranchID };
|
|
154
|
+
}
|
|
155
|
+
/** "1 message", or "N messages". */
|
|
156
|
+
export function ForkMessageLabel(count) {
|
|
157
|
+
return count === 1 ? '1 message' : `${count} messages`;
|
|
158
|
+
}
|
|
159
|
+
/** The initials of a name (first and last word), upper case; "?" for no name. */
|
|
160
|
+
export function ForkInitials(name) {
|
|
161
|
+
const words = (name ?? '').trim().split(/\s+/).filter(w => w.length > 0);
|
|
162
|
+
if (words.length === 0) {
|
|
163
|
+
return '?';
|
|
164
|
+
}
|
|
165
|
+
const last = words.length > 1 ? words[words.length - 1][0] : '';
|
|
166
|
+
return `${words[0][0]}${last}`.toUpperCase();
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* The last activity of a fork relative to `now`: "just now" under a minute, "N min ago" under an
|
|
170
|
+
* hour, "N h ago" under a day, "yesterday", "N days ago" under a week, else the short date.
|
|
171
|
+
*/
|
|
172
|
+
export function FormatForkActivity(at, now) {
|
|
173
|
+
const minutes = Math.floor((now.getTime() - at.getTime()) / 60000);
|
|
174
|
+
if (minutes < 1) {
|
|
175
|
+
return 'just now';
|
|
176
|
+
}
|
|
177
|
+
if (minutes < 60) {
|
|
178
|
+
return `${minutes} min ago`;
|
|
179
|
+
}
|
|
180
|
+
const hours = Math.floor(minutes / 60);
|
|
181
|
+
if (hours < 24) {
|
|
182
|
+
return `${hours} h ago`;
|
|
183
|
+
}
|
|
184
|
+
const days = Math.floor(hours / 24);
|
|
185
|
+
if (days === 1) {
|
|
186
|
+
return 'yesterday';
|
|
187
|
+
}
|
|
188
|
+
if (days < 7) {
|
|
189
|
+
return `${days} days ago`;
|
|
190
|
+
}
|
|
191
|
+
return at.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });
|
|
192
|
+
}
|
|
193
|
+
/** The chip of one fork. */
|
|
194
|
+
function forkChipOf(summary, avatarFor, now) {
|
|
195
|
+
return {
|
|
196
|
+
BranchID: summary.Branch.ID,
|
|
197
|
+
Kind: summary.Kind,
|
|
198
|
+
DisplayName: summary.DisplayName,
|
|
199
|
+
MessageLabel: ForkMessageLabel(summary.MessageCount),
|
|
200
|
+
ActivityLabel: FormatForkActivity(summary.LastActivityAt, now),
|
|
201
|
+
Avatars: summary.Participants.map(avatarFor),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* The chips of each placement message: one chip per fork whose PlacementDetailID is that message,
|
|
206
|
+
* in the order of `summaries`. Keys are normalized detail ids; a fork with no placement has no chip.
|
|
207
|
+
*/
|
|
208
|
+
export function BuildForkChipMap(summaries, avatarFor, now) {
|
|
209
|
+
const map = new Map();
|
|
210
|
+
for (const summary of summaries) {
|
|
211
|
+
if (!summary.PlacementDetailID) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
const chip = forkChipOf(summary, avatarFor, now);
|
|
215
|
+
const key = NormalizeUUID(summary.PlacementDetailID);
|
|
216
|
+
const list = map.get(key);
|
|
217
|
+
if (list) {
|
|
218
|
+
list.push(chip);
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
map.set(key, [chip]);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return map;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* The alternatives a fork shows on its first own message: an original chip for the view that holds the
|
|
228
|
+
* message the fork replaced (its SourceDetailID), then a chip for every other fork placed under that
|
|
229
|
+
* message with the same parent branch, in the order of `summaries`. Empty when the fork is not in
|
|
230
|
+
* `summaries` or replaced no message (its chips are then under its anchor, which its view shows).
|
|
231
|
+
* @param forkLabel The display name of a fork, for an original chip of a fork.
|
|
232
|
+
*/
|
|
233
|
+
export function BuildForkAlternativeChips(forkId, summaries, avatarFor, now, forkLabel) {
|
|
234
|
+
const fork = summaries.find(s => UUIDsEqual(s.Branch.ID, forkId));
|
|
235
|
+
const placement = fork?.PlacementDetailID;
|
|
236
|
+
if (!fork || !fork.Branch.SourceDetailID || !placement) {
|
|
237
|
+
return [];
|
|
238
|
+
}
|
|
239
|
+
const position = fork.PlacementPosition ?? null;
|
|
240
|
+
const branchId = position ? position.BranchID : fork.ParentBranchID;
|
|
241
|
+
const original = {
|
|
242
|
+
Kind: 'Original',
|
|
243
|
+
BranchID: branchId,
|
|
244
|
+
Sequence: position?.Sequence ?? null,
|
|
245
|
+
DisplayName: branchId ? forkLabel(branchId) : MAIN_VIEW_LABEL,
|
|
246
|
+
};
|
|
247
|
+
const siblings = summaries.filter(s => !UUIDsEqual(s.Branch.ID, fork.Branch.ID)
|
|
248
|
+
&& s.PlacementDetailID != null
|
|
249
|
+
&& UUIDsEqual(s.PlacementDetailID, placement)
|
|
250
|
+
&& UUIDsEqual(s.ParentBranchID ?? null, fork.ParentBranchID ?? null));
|
|
251
|
+
return [original, ...siblings.map(s => forkChipOf(s, avatarFor, now))];
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* The chips of a view's messages: {@link BuildForkChipMap} over every fork but the one a fork view shows
|
|
255
|
+
* (its chip would open the view already open). In a fork view, the fork's alternatives
|
|
256
|
+
* ({@link BuildForkAlternativeChips}) come first on its first own message `firstOwnDetailId`.
|
|
257
|
+
*/
|
|
258
|
+
export function BuildViewForkChipMap(summaries, view, firstOwnDetailId, avatarFor, now, forkLabel) {
|
|
259
|
+
if (view.Kind !== 'Fork') {
|
|
260
|
+
return BuildForkChipMap(summaries, avatarFor, now);
|
|
261
|
+
}
|
|
262
|
+
const map = new Map(BuildForkChipMap(summaries.filter(s => !UUIDsEqual(s.Branch.ID, view.BranchID)), avatarFor, now));
|
|
263
|
+
const alternatives = firstOwnDetailId ? BuildForkAlternativeChips(view.BranchID, summaries, avatarFor, now, forkLabel) : [];
|
|
264
|
+
if (firstOwnDetailId && alternatives.length > 0) {
|
|
265
|
+
const key = NormalizeUUID(firstOwnDetailId);
|
|
266
|
+
map.set(key, [...alternatives, ...(map.get(key) ?? [])]);
|
|
267
|
+
}
|
|
268
|
+
return map;
|
|
269
|
+
}
|
|
270
|
+
/** The composer placeholder in Main. */
|
|
271
|
+
export const MAIN_COMPOSER_PLACEHOLDER = 'Type a message... (Ctrl+Enter to send)';
|
|
272
|
+
/** The composer placeholder in a fork or draft view. */
|
|
273
|
+
export const FORK_COMPOSER_PLACEHOLDER = 'Message this fork';
|
|
274
|
+
/** Splits loaded rows for a view (see {@link ForkRows}). Keeps their order. */
|
|
275
|
+
export function SplitForkRows(rows, view) {
|
|
276
|
+
if (view.Kind === 'Main') {
|
|
277
|
+
return { Inherited: [], Own: [...rows] };
|
|
278
|
+
}
|
|
279
|
+
if (view.Kind === 'DraftFork') {
|
|
280
|
+
const anchorSequence = view.ForkFromSequence;
|
|
281
|
+
return { Inherited: anchorSequence == null ? [] : rows.filter(r => r.Sequence <= anchorSequence), Own: [] };
|
|
282
|
+
}
|
|
283
|
+
const inherited = [];
|
|
284
|
+
const own = [];
|
|
285
|
+
for (const r of rows) {
|
|
286
|
+
(UUIDsEqual(r.BranchID ?? null, view.BranchID) ? own : inherited).push(r);
|
|
287
|
+
}
|
|
288
|
+
return { Inherited: inherited, Own: own };
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* The id of a fork view's first own row (lowest Sequence) when the loaded rows reach it: an inherited
|
|
292
|
+
* row is loaded, or no older rows remain (`hasMoreAbove` false). Null with no own row loaded.
|
|
293
|
+
*/
|
|
294
|
+
export function FirstOwnRowID(rows, hasMoreAbove) {
|
|
295
|
+
if (rows.Own.length === 0 || (rows.Inherited.length === 0 && hasMoreAbove)) {
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
let first = rows.Own[0];
|
|
299
|
+
for (const row of rows.Own) {
|
|
300
|
+
if (row.Sequence < first.Sequence) {
|
|
301
|
+
first = row;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return first.ID;
|
|
305
|
+
}
|
|
306
|
+
/** The layout of `displayRows` in a fork or draft view; null in Main. */
|
|
307
|
+
export function BuildForkViewLayout(displayRows, view, markerText) {
|
|
308
|
+
if (view.Kind === 'Main') {
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
const inherited = SplitForkRows(displayRows, view).Inherited;
|
|
312
|
+
const last = inherited.length > 0 ? inherited[inherited.length - 1] : null;
|
|
313
|
+
return {
|
|
314
|
+
InheritedIDs: new Set(inherited.map(r => NormalizeUUID(r.ID))),
|
|
315
|
+
MarkerDetailID: last ? last.ID : null,
|
|
316
|
+
MarkerText: last || view.Kind === 'DraftFork' ? markerText : null,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* The fork marker: "<person> forked from <author>'s message · <time>" ("Someone" with no person; the
|
|
321
|
+
* "from" part only with an author; the time only when known). A draft: "New fork from <author>'s
|
|
322
|
+
* message · nothing is saved until you send". `AnchorAuthorName` is the author the "from" part names: the
|
|
323
|
+
* source message's author for a fork with a source, else the anchor's.
|
|
324
|
+
*/
|
|
325
|
+
export function BuildForkMarkerText(input, formatTime) {
|
|
326
|
+
const from = input.AnchorAuthorName ? ` from ${input.AnchorAuthorName}'s message` : '';
|
|
327
|
+
if (input.IsDraft) {
|
|
328
|
+
return `New fork${from}${DRAFT_MARKER_SUFFIX}`;
|
|
329
|
+
}
|
|
330
|
+
const time = input.StartedAt ? ` · ${formatTime(input.StartedAt)}` : '';
|
|
331
|
+
return `${input.StarterName ?? 'Someone'} forked${from}${time}`;
|
|
332
|
+
}
|
|
333
|
+
/** The composer hint of a fork or draft view. */
|
|
334
|
+
export function BuildForkComposerHint(anchorAt, formatTime) {
|
|
335
|
+
return anchorAt
|
|
336
|
+
? `Agents see the conversation up to ${formatTime(anchorAt)}, plus this fork`
|
|
337
|
+
: 'Agents see only this fork';
|
|
338
|
+
}
|
|
339
|
+
/** How the fork started, in words: "Forked by Priya", "Edit of Maya's message", "Regenerated by Jordan". */
|
|
340
|
+
export function ForkOriginText(summary) {
|
|
341
|
+
const starter = summary.StartedByName ?? 'someone';
|
|
342
|
+
switch (summary.Kind) {
|
|
343
|
+
case 'Edit':
|
|
344
|
+
return `Edit of ${starter}'s message`;
|
|
345
|
+
case 'Regenerate':
|
|
346
|
+
return `Regenerated by ${starter}`;
|
|
347
|
+
default:
|
|
348
|
+
return `Forked by ${starter}`;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* The rows of the forks list: roots (no parent, or a parent the filter left out) newest activity
|
|
353
|
+
* first, each followed by its nested forks (one level deeper), also newest first. 'Mine' keeps the
|
|
354
|
+
* forks the person started or wrote in.
|
|
355
|
+
*/
|
|
356
|
+
export function BuildForkListRows(summaries, filter, userId, now) {
|
|
357
|
+
const me = NormalizeUUID(userId);
|
|
358
|
+
const kept = filter === 'All'
|
|
359
|
+
? [...summaries]
|
|
360
|
+
: summaries.filter(s => NormalizeUUID(s.StartedByUserID) === me || s.AuthorUserIDs.some(id => NormalizeUUID(id) === me));
|
|
361
|
+
const keptIds = new Set(kept.map(s => NormalizeUUID(s.Branch.ID)));
|
|
362
|
+
const newestFirst = (a, b) => b.LastActivityAt.getTime() - a.LastActivityAt.getTime();
|
|
363
|
+
const children = new Map();
|
|
364
|
+
const roots = [];
|
|
365
|
+
for (const summary of kept) {
|
|
366
|
+
const parent = summary.ParentBranchID ? NormalizeUUID(summary.ParentBranchID) : null;
|
|
367
|
+
if (parent && keptIds.has(parent)) {
|
|
368
|
+
const list = children.get(parent);
|
|
369
|
+
if (list) {
|
|
370
|
+
list.push(summary);
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
children.set(parent, [summary]);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
roots.push(summary);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
const rows = [];
|
|
381
|
+
const visit = (summary, depth) => {
|
|
382
|
+
const origin = ForkOriginText(summary);
|
|
383
|
+
rows.push({
|
|
384
|
+
Summary: summary,
|
|
385
|
+
Depth: depth,
|
|
386
|
+
Icon: ForkKindIcon(summary.Kind),
|
|
387
|
+
PreviewText: summary.LastMessagePreview
|
|
388
|
+
? (summary.LastMessageAuthorName ? `${summary.LastMessageAuthorName}: ${summary.LastMessagePreview}` : summary.LastMessagePreview)
|
|
389
|
+
: null,
|
|
390
|
+
MetaText: `${origin} · ${ForkMessageLabel(summary.MessageCount)} · ${FormatForkActivity(summary.LastActivityAt, now)}`,
|
|
391
|
+
});
|
|
392
|
+
for (const child of (children.get(NormalizeUUID(summary.Branch.ID)) ?? []).sort(newestFirst)) {
|
|
393
|
+
visit(child, depth + 1);
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
for (const root of roots.sort(newestFirst)) {
|
|
397
|
+
visit(root, 0);
|
|
398
|
+
}
|
|
399
|
+
return rows;
|
|
400
|
+
}
|
|
401
|
+
/** True when the person may start forks: the authorization, a forking setting that is not off, and write access. */
|
|
402
|
+
export function CanForkFrom(input) {
|
|
403
|
+
return input.HoldsAuthorization && IsForkingSettingOn(input.SettingValue) && input.MayWrite;
|
|
404
|
+
}
|
|
405
|
+
/** True when `row` is one of the view's own rows: a Main row in Main, a row of the fork in a fork view. */
|
|
406
|
+
function isOwnRowOf(view, row) {
|
|
407
|
+
return view.Kind === 'Fork' ? UUIDsEqual(row.BranchID ?? null, view.BranchID) : view.Kind === 'Main' && row.BranchID == null;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* The latest turn of a view: its last User row and, when the last row after it is an AI row, that row.
|
|
411
|
+
* Replaced rows are skipped. Null in a draft, with no User row, when the last User row is inherited (not
|
|
412
|
+
* one of the view's own rows), and when it belongs to a voice session.
|
|
413
|
+
*/
|
|
414
|
+
export function FindLatestTurn(rows, view) {
|
|
415
|
+
if (view.Kind === 'DraftFork') {
|
|
416
|
+
return null;
|
|
417
|
+
}
|
|
418
|
+
const live = rows.filter(r => r.ReplacedAt == null).sort((a, b) => a.Sequence - b.Sequence);
|
|
419
|
+
let user;
|
|
420
|
+
for (let i = live.length - 1; i >= 0; i--) {
|
|
421
|
+
if (live[i].Role === 'User') {
|
|
422
|
+
user = live[i];
|
|
423
|
+
break;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
if (!user || user.AgentSessionID || !isOwnRowOf(view, user)) {
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
const last = live[live.length - 1];
|
|
430
|
+
const isAnswer = last !== user && last.Role === 'AI' && !last.AgentSessionID && UUIDsEqual(last.BranchID ?? null, user.BranchID ?? null);
|
|
431
|
+
return { UserDetailID: user.ID, AnswerDetailID: isAnswer ? last.ID : null };
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* The rows after `userMessage` on its branch, in Sequence order, without replaced rows: for the latest
|
|
435
|
+
* User row, the rows of its answer (the reply and any delegated or status rows).
|
|
436
|
+
*/
|
|
437
|
+
export function TurnRowsAfter(rows, userMessage) {
|
|
438
|
+
return rows
|
|
439
|
+
.filter(r => r.Sequence > userMessage.Sequence && r.ReplacedAt == null && UUIDsEqual(r.BranchID ?? null, userMessage.BranchID ?? null))
|
|
440
|
+
.sort((a, b) => a.Sequence - b.Sequence);
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* True when hiding `rows` (rows of branch `branchId`; null is Main) would change a fork: a fork of that
|
|
444
|
+
* branch whose fork point is at or after the first of them (it inherits them), or a fork placed under one
|
|
445
|
+
* of them (its SourceDetailID). False for no rows.
|
|
446
|
+
*/
|
|
447
|
+
export function ForkDependsOnRows(branchId, rows, branches) {
|
|
448
|
+
if (rows.length === 0) {
|
|
449
|
+
return false;
|
|
450
|
+
}
|
|
451
|
+
const first = Math.min(...rows.map(r => r.Sequence));
|
|
452
|
+
const rowIds = new UUIDSet(rows.map(r => r.ID));
|
|
453
|
+
return branches.some(b => (b.ForkFromSequence != null && b.ForkFromSequence >= first && UUIDsEqual(b.ParentBranchID ?? null, branchId))
|
|
454
|
+
|| (b.SourceDetailID != null && rowIds.Has(b.SourceDetailID)));
|
|
455
|
+
}
|
|
456
|
+
//# sourceMappingURL=conversation-forks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversation-forks.js","sourceRoot":"","sources":["../../../src/lib/utils/conversation-forks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAqF,MAAM,+BAA+B,CAAC;AACxK,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAkCzF,qBAAqB;AACrB,MAAM,CAAC,MAAM,cAAc,GAAiB,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AAE7D;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,CAAuB,EAAE,CAAuB;IACzE,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QACzC,OAAO,UAAU,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC;IAC9C,CAAC;IACD,IAAI,CAAC,CAAC,IAAI,KAAK,WAAW,IAAI,CAAC,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;QACnD,OAAO,UAAU,CAAC,CAAC,CAAC,cAAc,EAAE,CAAC,CAAC,cAAc,CAAC,CAAC;IAC1D,CAAC;IACD,OAAO,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,CAAC;AAC7B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,IAA0B;IACrD,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;QAChB,KAAK,MAAM;YACP,OAAO,IAAI,CAAC,QAAQ,CAAC;QACzB,KAAK,WAAW;YACZ,OAAO,IAAI,CAAC,cAAc,CAAC;QAC/B;YACI,OAAO,IAAI,CAAC;IACpB,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,IAA0B,EAAE,GAAiC;IACzF,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;QAChB,KAAK,MAAM;YACP,OAAO,UAAU,CAAC,GAAG,CAAC,QAAQ,IAAI,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC3D,KAAK,WAAW;YACZ,OAAO,KAAK,CAAC;QACjB;YACI,OAAO,GAAG,CAAC,QAAQ,IAAI,IAAI,CAAC;IACpC,CAAC;AACL,CAAC;AAKD;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,IAA0B,EAAE,IAAiC;IACxF,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;QAChB,KAAK,MAAM;YACP,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,cAAc,IAAI,IAAI,CAAC;QACnF,KAAK,WAAW;YACZ,OAAO,IAAI,CAAC,cAAc,CAAC;QAC/B;YACI,OAAO,IAAI,CAAC;IACpB,CAAC;AACL,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,QAAuB,EAAE,IAAiC;IAClF,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,EAAE,GAAG,QAAQ,CAAC;IAClB,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,gBAAgB,EAAE,CAAC;QAC3E,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC,CAAC;QAC5B,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAClB,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,cAAc,IAAI,IAAI,CAAC;IAC9C,CAAC;IACD,OAAO,KAAK,CAAC;AACjB,CAAC;AAWD,mDAAmD;AACnD,MAAM,CAAC,MAAM,gBAAgB,GAAG,UAAU,CAAC;AAE3C,wCAAwC;AACxC,MAAM,CAAC,MAAM,mBAAmB,GAAG,oCAAoC,CAAC;AAExE;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAChC,IAA0B,EAC1B,gBAAwB,EACxB,IAAiC,EACjC,SAAuC;IAEvC,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QACvB,OAAO,EAAE,CAAC;IACd,CAAC;IACD,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC;IAChI,MAAM,MAAM,GAAqB,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;IACjG,KAAK,MAAM,EAAE,IAAI,SAAS,EAAE,CAAC;QACzB,MAAM,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;IAC1E,CAAC;IACD,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,MAAM;QAC5B,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE;QAC/E,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACpE,OAAO,MAAM,CAAC;AAClB,CAAC;AAqBD,0FAA0F;AAC1F,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC;AAEvC;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAAiC;IACjE,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IACjD,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,GAAG,qBAAqB,CAAC;IAC5D,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;IACtC,OAAO;QACH,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;YAChC,KAAK,EAAE,KAAK;YACZ,QAAQ,EAAE,CAAC,GAAG,CAAC;YACf,aAAa,EAAE,aAAa,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,UAAU;SAC1D,CAAC,CAAC;QACH,aAAa,EAAE,aAAa;QAC5B,SAAS,EAAE,OAAO,CAAC,MAAM,GAAG,CAAC;KAChC,CAAC;AACN,CAAC;AAED,2GAA2G;AAC3G,MAAM,UAAU,WAAW,CAAC,WAA0B;IAClD,OAAO,WAAW,CAAC,CAAC,CAAC,WAAW,WAAW,EAAE,CAAC,CAAC,CAAC,cAAc,CAAC;AACnE,CAAC;AAiDD,2DAA2D;AAC3D,MAAM,CAAC,MAAM,gBAAgB,GAAwB,EAAE,CAAC;AAExD,4CAA4C;AAC5C,MAAM,CAAC,MAAM,eAAe,GAAG,MAAM,CAAC;AAEtC,4CAA4C;AAC5C,MAAM,UAAU,YAAY,CAAC,IAAc;IACvC,QAAQ,IAAI,EAAE,CAAC;QACX,KAAK,MAAM;YACP,OAAO,iBAAiB,CAAC;QAC7B,KAAK,YAAY;YACb,OAAO,yBAAyB,CAAC;QACrC;YACI,OAAO,yBAAyB,CAAC;IACzC,CAAC;AACL,CAAC;AAED,4HAA4H;AAC5H,MAAM,UAAU,YAAY,CAAC,IAAqB;IAC9C,IAAI,IAAI,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;QAC3B,OAAO,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,yBAAyB,CAAC,CAAC,CAAC,mBAAmB,CAAC;IAC3E,CAAC;IACD,OAAO,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACnC,CAAC;AAED,oGAAoG;AACpG,MAAM,UAAU,iBAAiB,CAAC,IAAqB;IACnD,OAAO,IAAI,CAAC,IAAI,KAAK,UAAU;QAC3B,CAAC,CAAC,wBAAwB,IAAI,CAAC,WAAW,EAAE;QAC5C,CAAC,CAAC,aAAa,IAAI,CAAC,WAAW,KAAK,IAAI,CAAC,YAAY,EAAE,CAAC;AAChE,CAAC;AAED,8GAA8G;AAC9G,MAAM,UAAU,qBAAqB,CAAC,IAAqB;IACvD,OAAO,IAAI,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC;AACzH,CAAC;AAED,oCAAoC;AACpC,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC1C,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,KAAK,WAAW,CAAC;AAC3D,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,YAAY,CAAC,IAA+B;IACxD,MAAM,KAAK,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACzE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO,GAAG,CAAC;IACf,CAAC;IACD,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAChE,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AACjD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,EAAQ,EAAE,GAAS;IAClD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,KAAK,CAAC,CAAC;IACnE,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;QACd,OAAO,UAAU,CAAC;IACtB,CAAC;IACD,IAAI,OAAO,GAAG,EAAE,EAAE,CAAC;QACf,OAAO,GAAG,OAAO,UAAU,CAAC;IAChC,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;IACvC,IAAI,KAAK,GAAG,EAAE,EAAE,CAAC;QACb,OAAO,GAAG,KAAK,QAAQ,CAAC;IAC5B,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC;IACpC,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;QACb,OAAO,WAAW,CAAC;IACvB,CAAC;IACD,IAAI,IAAI,GAAG,CAAC,EAAE,CAAC;QACX,OAAO,GAAG,IAAI,WAAW,CAAC;IAC9B,CAAC;IACD,OAAO,EAAE,CAAC,kBAAkB,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC,CAAC;AAChF,CAAC;AAED,4BAA4B;AAC5B,SAAS,UAAU,CAAC,OAAoB,EAAE,SAA2D,EAAE,GAAS;IAC5G,OAAO;QACH,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE;QAC3B,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,WAAW,EAAE,OAAO,CAAC,WAAW;QAChC,YAAY,EAAE,gBAAgB,CAAC,OAAO,CAAC,YAAY,CAAC;QACpD,aAAa,EAAE,kBAAkB,CAAC,OAAO,CAAC,cAAc,EAAE,GAAG,CAAC;QAC9D,OAAO,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC;KAC/C,CAAC;AACN,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAC5B,SAAqC,EACrC,SAA2D,EAC3D,GAAS;IAET,MAAM,GAAG,GAAG,IAAI,GAAG,EAAsB,CAAC;IAC1C,KAAK,MAAM,OAAO,IAAI,SAAS,EAAE,CAAC;QAC9B,IAAI,CAAC,OAAO,CAAC,iBAAiB,EAAE,CAAC;YAC7B,SAAS;QACb,CAAC;QACD,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC;QACjD,MAAM,GAAG,GAAG,aAAa,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;QACrD,MAAM,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC1B,IAAI,IAAI,EAAE,CAAC;YACP,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpB,CAAC;aAAM,CAAC;YACJ,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;QACzB,CAAC;IACL,CAAC;IACD,OAAO,GAAG,CAAC;AACf,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,yBAAyB,CACrC,MAAc,EACd,SAAqC,EACrC,SAA2D,EAC3D,GAAS,EACT,SAAuC;IAEvC,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC;IAClE,MAAM,SAAS,GAAG,IAAI,EAAE,iBAAiB,CAAC;IAC1C,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,IAAI,CAAC,SAAS,EAAE,CAAC;QACrD,OAAO,EAAE,CAAC;IACd,CAAC;IACD,MAAM,QAAQ,GAAG,IAAI,CAAC,iBAAiB,IAAI,IAAI,CAAC;IAChD,MAAM,QAAQ,GAAG,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC;IACpE,MAAM,QAAQ,GAAqB;QAC/B,IAAI,EAAE,UAAU;QAChB,QAAQ,EAAE,QAAQ;QAClB,QAAQ,EAAE,QAAQ,EAAE,QAAQ,IAAI,IAAI;QACpC,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,eAAe;KAChE,CAAC;IACF,MAAM,QAAQ,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAClC,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;WACrC,CAAC,CAAC,iBAAiB,IAAI,IAAI;WAC3B,UAAU,CAAC,CAAC,CAAC,iBAAiB,EAAE,SAAS,CAAC;WAC1C,UAAU,CAAC,CAAC,CAAC,cAAc,IAAI,IAAI,EAAE,IAAI,CAAC,cAAc,IAAI,IAAI,CAAC,CAAC,CAAC;IAC1E,OAAO,CAAC,QAAQ,EAAE,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;AAC3E,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAChC,SAAqC,EACrC,IAA0B,EAC1B,gBAA+B,EAC/B,SAA2D,EAC3D,GAAS,EACT,SAAuC;IAEvC,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QACvB,OAAO,gBAAgB,CAAC,SAAS,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC;IACvD,CAAC;IACD,MAAM,GAAG,GAA4C,IAAI,GAAG,CACxD,gBAAgB,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,SAAS,EAAE,GAAG,CAAC,CACnG,CAAC;IACF,MAAM,YAAY,GAAG,gBAAgB,CAAC,CAAC,CAAC,yBAAyB,CAAC,IAAI,CAAC,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC5H,IAAI,gBAAgB,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9C,MAAM,GAAG,GAAG,aAAa,CAAC,gBAAgB,CAAC,CAAC;QAC5C,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,YAAY,EAAE,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAC7D,CAAC;IACD,OAAO,GAAG,CAAC;AACf,CAAC;AAED,wCAAwC;AACxC,MAAM,CAAC,MAAM,yBAAyB,GAAG,wCAAwC,CAAC;AAElF,wDAAwD;AACxD,MAAM,CAAC,MAAM,yBAAyB,GAAG,mBAAmB,CAAC;AAU7D,+EAA+E;AAC/E,MAAM,UAAU,aAAa,CAA2D,IAAkB,EAAE,IAA0B;IAClI,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QACvB,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC;IAC7C,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;QAC5B,MAAM,cAAc,GAAG,IAAI,CAAC,gBAAgB,CAAC;QAC7C,OAAO,EAAE,SAAS,EAAE,cAAc,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,IAAI,cAAc,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC;IAChH,CAAC;IACD,MAAM,SAAS,GAAQ,EAAE,CAAC;IAC1B,MAAM,GAAG,GAAQ,EAAE,CAAC;IACpB,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACnB,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,IAAI,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC9E,CAAC;IACD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;AAC9C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAA6C,IAAiB,EAAE,YAAqB;IAC9G,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC,IAAI,YAAY,CAAC,EAAE,CAAC;QACzE,OAAO,IAAI,CAAC;IAChB,CAAC;IACD,IAAI,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IACxB,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;QACzB,IAAI,GAAG,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAC;YAChC,KAAK,GAAG,GAAG,CAAC;QAChB,CAAC;IACL,CAAC;IACD,OAAO,KAAK,CAAC,EAAE,CAAC;AACpB,CAAC;AAYD,yEAAyE;AACzE,MAAM,UAAU,mBAAmB,CAC/B,WAAyB,EACzB,IAA0B,EAC1B,UAAyB;IAEzB,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QACvB,OAAO,IAAI,CAAC;IAChB,CAAC;IACD,MAAM,SAAS,GAAG,aAAa,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC,SAAS,CAAC;IAC7D,MAAM,IAAI,GAAG,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC3E,OAAO;QACH,YAAY,EAAE,IAAI,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAC9D,cAAc,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI;QACrC,UAAU,EAAE,IAAI,IAAI,IAAI,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI;KACpE,CAAC;AACN,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAC/B,KAAgH,EAChH,UAA+B;IAE/B,MAAM,IAAI,GAAG,KAAK,CAAC,gBAAgB,CAAC,CAAC,CAAC,SAAS,KAAK,CAAC,gBAAgB,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;IACvF,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QAChB,OAAO,WAAW,IAAI,GAAG,mBAAmB,EAAE,CAAC;IACnD,CAAC;IACD,MAAM,IAAI,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,UAAU,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACxE,OAAO,GAAG,KAAK,CAAC,WAAW,IAAI,SAAS,UAAU,IAAI,GAAG,IAAI,EAAE,CAAC;AACpE,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,qBAAqB,CAAC,QAAqB,EAAE,UAA+B;IACxF,OAAO,QAAQ;QACX,CAAC,CAAC,qCAAqC,UAAU,CAAC,QAAQ,CAAC,kBAAkB;QAC7E,CAAC,CAAC,2BAA2B,CAAC;AACtC,CAAC;AAiBD,4GAA4G;AAC5G,MAAM,UAAU,cAAc,CAAC,OAAoB;IAC/C,MAAM,OAAO,GAAG,OAAO,CAAC,aAAa,IAAI,SAAS,CAAC;IACnD,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;QACnB,KAAK,MAAM;YACP,OAAO,WAAW,OAAO,YAAY,CAAC;QAC1C,KAAK,YAAY;YACb,OAAO,kBAAkB,OAAO,EAAE,CAAC;QACvC;YACI,OAAO,aAAa,OAAO,EAAE,CAAC;IACtC,CAAC;AACL,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,SAAqC,EAAE,MAAsB,EAAE,MAAc,EAAE,GAAS;IACtH,MAAM,EAAE,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IACjC,MAAM,IAAI,GAAG,MAAM,KAAK,KAAK;QACzB,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;QAChB,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,eAAe,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAC7H,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACnE,MAAM,WAAW,GAAG,CAAC,CAAc,EAAE,CAAc,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,cAAc,CAAC,OAAO,EAAE,CAAC;IAChH,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAyB,CAAC;IAClD,MAAM,KAAK,GAAkB,EAAE,CAAC;IAChC,KAAK,MAAM,OAAO,IAAI,IAAI,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,aAAa,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QACrF,IAAI,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YAChC,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YAClC,IAAI,IAAI,EAAE,CAAC;gBACP,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACvB,CAAC;iBAAM,CAAC;gBACJ,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;YACpC,CAAC;QACL,CAAC;aAAM,CAAC;YACJ,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACxB,CAAC;IACL,CAAC;IACD,MAAM,IAAI,GAAkB,EAAE,CAAC;IAC/B,MAAM,KAAK,GAAG,CAAC,OAAoB,EAAE,KAAa,EAAQ,EAAE;QACxD,MAAM,MAAM,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;QACvC,IAAI,CAAC,IAAI,CAAC;YACN,OAAO,EAAE,OAAO;YAChB,KAAK,EAAE,KAAK;YACZ,IAAI,EAAE,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC;YAChC,WAAW,EAAE,OAAO,CAAC,kBAAkB;gBACnC,CAAC,CAAC,CAAC,OAAO,CAAC,qBAAqB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,qBAAqB,KAAK,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,kBAAkB,CAAC;gBAClI,CAAC,CAAC,IAAI;YACV,QAAQ,EAAE,GAAG,MAAM,MAAM,gBAAgB,CAAC,OAAO,CAAC,YAAY,CAAC,MAAM,kBAAkB,CAAC,OAAO,CAAC,cAAc,EAAE,GAAG,CAAC,EAAE;SACzH,CAAC,CAAC;QACH,KAAK,MAAM,KAAK,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,aAAa,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;YAC3F,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;QAC5B,CAAC;IACL,CAAC,CAAC;IACF,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;QACzC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACnB,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC;AAYD,oHAAoH;AACpH,MAAM,UAAU,WAAW,CAAC,KAAmB;IAC3C,OAAO,KAAK,CAAC,kBAAkB,IAAI,kBAAkB,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC;AAChG,CAAC;AAoBD,2GAA2G;AAC3G,SAAS,UAAU,CAAC,IAA0B,EAAE,GAAiC;IAC7E,OAAO,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,IAAI,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,GAAG,CAAC,QAAQ,IAAI,IAAI,CAAC;AACjI,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAoB,IAAkB,EAAE,IAA0B;IAC5F,IAAI,IAAI,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;QAC5B,OAAO,IAAI,CAAC;IAChB,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC;IAC5F,IAAI,IAAmB,CAAC;IACxB,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACxC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAC1B,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;YACf,MAAM;QACV,CAAC;IACL,CAAC;IACD,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,cAAc,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;QAC1D,OAAO,IAAI,CAAC;IAChB,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACnC,MAAM,QAAQ,GAAG,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,cAAc,IAAI,UAAU,CAAC,IAAI,CAAC,QAAQ,IAAI,IAAI,EAAE,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,CAAC;IACzI,OAAO,EAAE,YAAY,EAAE,IAAI,CAAC,EAAE,EAAE,cAAc,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AAChF,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CACzB,IAAkB,EAClB,WAA2D;IAE3D,OAAO,IAAI;SACN,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,GAAG,WAAW,CAAC,QAAQ,IAAI,CAAC,CAAC,UAAU,IAAI,IAAI,IAAI,UAAU,CAAC,CAAC,CAAC,QAAQ,IAAI,IAAI,EAAE,WAAW,CAAC,QAAQ,IAAI,IAAI,CAAC,CAAC;SACtI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC;AACjD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAC7B,QAAuB,EACvB,IAAqD,EACrD,QAA8G;IAE9G,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpB,OAAO,KAAK,CAAC;IACjB,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;IACrD,MAAM,MAAM,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAChD,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CACrB,CAAC,CAAC,CAAC,gBAAgB,IAAI,IAAI,IAAI,CAAC,CAAC,gBAAgB,IAAI,KAAK,IAAI,UAAU,CAAC,CAAC,CAAC,cAAc,IAAI,IAAI,EAAE,QAAQ,CAAC,CAAC;WAC1G,CAAC,CAAC,CAAC,cAAc,IAAI,IAAI,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC;AACvE,CAAC","sourcesContent":["import { IsForkingSettingOn, MAX_BRANCH_DEPTH, type ConversationBranchRow, type ForkKind, type ForkParticipant, type ForkSummary } from '@memberjunction/core-entities';\nimport { IndexByUUID, NormalizeUUID, UUIDSet, UUIDsEqual } from '@memberjunction/global';\n\n/** Main: the conversation's trunk, shared by everyone. */\nexport interface MainOpenView {\n readonly Kind: 'Main';\n}\n\n/** One fork, shown in the full view. */\nexport interface ForkOpenView {\n readonly Kind: 'Fork';\n readonly BranchID: string;\n}\n\n/**\n * A new fork that replaces a message (Fork from here): shown like a fork, created on the first send.\n * It starts at the row before the replaced message on its path (the anchor).\n */\nexport interface DraftForkOpenView {\n readonly Kind: 'DraftFork';\n /** The branch of the anchor; null is Main. */\n readonly ParentBranchID: string | null;\n /** The Sequence of the anchor; null when the replaced message is the first one (nothing is inherited). */\n readonly ForkFromSequence: number | null;\n /** The anchor; null when there is none. */\n readonly AnchorDetailID: string | null;\n /** The replaced message; the new fork's SourceDetailID, so its chip shows under that message. */\n readonly SourceDetailID: string;\n /** The author of the replaced message, read as the draft opens: the draft's window ends before that message. */\n readonly SourceAuthorName?: string | null;\n}\n\n/** What one person is looking at in a conversation. In memory only; opening a conversation shows Main. */\nexport type ConversationOpenView = MainOpenView | ForkOpenView | DraftForkOpenView;\n\n/** The Main view. */\nexport const MAIN_OPEN_VIEW: MainOpenView = { Kind: 'Main' };\n\n/**\n * True when two views show the same thing. Fork ids, and the replaced messages of two drafts, are\n * compared without regard to case.\n */\nexport function SameOpenView(a: ConversationOpenView, b: ConversationOpenView): boolean {\n if (a.Kind === 'Fork' && b.Kind === 'Fork') {\n return UUIDsEqual(a.BranchID, b.BranchID);\n }\n if (a.Kind === 'DraftFork' && b.Kind === 'DraftFork') {\n return UUIDsEqual(a.SourceDetailID, b.SourceDetailID);\n }\n return a.Kind === b.Kind;\n}\n\n/**\n * The branch whose path the view reads: null for Main, the fork for a fork. A draft reads its\n * parent's path.\n */\nexport function ReadBranchIdOf(view: ConversationOpenView): string | null {\n switch (view.Kind) {\n case 'Fork':\n return view.BranchID;\n case 'DraftFork':\n return view.ParentBranchID;\n default:\n return null;\n }\n}\n\n/**\n * True when a new row belongs on screen in the view: a Main row in Main, a row of the fork in that\n * fork. A draft has no rows of its own, so no new row belongs in it.\n */\nexport function IsRowInOpenView(view: ConversationOpenView, row: { BranchID?: string | null }): boolean {\n switch (view.Kind) {\n case 'Fork':\n return UUIDsEqual(row.BranchID ?? null, view.BranchID);\n case 'DraftFork':\n return false;\n default:\n return row.BranchID == null;\n }\n}\n\n/** The fields of a fork row the breadcrumb and the back button read. */\nexport type ForkChainRow = Pick<ConversationBranchRow, 'ID' | 'ParentBranchID'>;\n\n/**\n * The fork one level up from a view; null is Main. A fork goes up to its ParentBranchID (Main when its\n * row is not in `rows`); a draft goes back to the fork it started from, its ParentBranchID.\n */\nexport function ParentForkIdOf(view: ConversationOpenView, rows: ReadonlyArray<ForkChainRow>): string | null {\n switch (view.Kind) {\n case 'Fork':\n return rows.find(r => UUIDsEqual(r.ID, view.BranchID))?.ParentBranchID ?? null;\n case 'DraftFork':\n return view.ParentBranchID;\n default:\n return null;\n }\n}\n\n/**\n * The fork ids of a fork's chain, from the fork under Main down to `branchId`, following ParentBranchID.\n * The chain stops at a fork whose row is not in `rows`, at a fork seen before, and at\n * {@link MAX_BRANCH_DEPTH} forks. Empty for null (Main).\n */\nexport function ForkChainOf(branchId: string | null, rows: ReadonlyArray<ForkChainRow>): string[] {\n const chain: string[] = [];\n const seen = new Set<string>();\n const byId = IndexByUUID(rows);\n let id = branchId;\n while (id && !seen.has(NormalizeUUID(id)) && chain.length < MAX_BRANCH_DEPTH) {\n seen.add(NormalizeUUID(id));\n chain.unshift(id);\n id = byId.Get(id)?.ParentBranchID ?? null;\n }\n return chain;\n}\n\n/** One crumb of the fork view's breadcrumb. */\nexport interface ForkBreadcrumb {\n /** The fork the crumb opens; null opens Main. Null on the crumb of a draft. */\n readonly BranchID: string | null;\n readonly Label: string;\n /** True for the last crumb: the view on screen, which opens nothing. */\n readonly IsCurrent: boolean;\n}\n\n/** The label of the last crumb of a draft fork. */\nexport const DRAFT_FORK_TITLE = 'New fork';\n\n/** The end of a draft's fork marker. */\nexport const DRAFT_MARKER_SUFFIX = ' · nothing is saved until you send';\n\n/**\n * The breadcrumb of a fork or draft view: the conversation (opens Main), each ancestor fork from the\n * one under Main down, then the open fork (a draft: \"New fork\"). Empty for Main.\n */\nexport function BuildForkBreadcrumbs(\n view: ConversationOpenView,\n conversationName: string,\n rows: ReadonlyArray<ForkChainRow>,\n forkLabel: (branchId: string) => string\n): ForkBreadcrumb[] {\n if (view.Kind === 'Main') {\n return [];\n }\n const ancestors = view.Kind === 'Fork' ? ForkChainOf(view.BranchID, rows).slice(0, -1) : ForkChainOf(view.ParentBranchID, rows);\n const crumbs: ForkBreadcrumb[] = [{ BranchID: null, Label: conversationName, IsCurrent: false }];\n for (const id of ancestors) {\n crumbs.push({ BranchID: id, Label: forkLabel(id), IsCurrent: false });\n }\n crumbs.push(view.Kind === 'Fork'\n ? { BranchID: view.BranchID, Label: forkLabel(view.BranchID), IsCurrent: true }\n : { BranchID: null, Label: DRAFT_FORK_TITLE, IsCurrent: true });\n return crumbs;\n}\n\n/** A crumb before the current one in the header path. */\nexport interface ForkPathParent {\n readonly Crumb: ForkBreadcrumb;\n /** A crumb between the first and the current one; narrow screens hide every one of them behind the ellipsis. */\n readonly IsMiddle: boolean;\n /** Hidden behind the ellipsis at every width: a middle crumb other than the parent, in a path of more than three entries. */\n readonly CollapsedWide: boolean;\n}\n\n/** The header path of a fork view, built from its breadcrumb. */\nexport interface ForkHeaderPath {\n /** The crumbs before the current one, in order. */\n readonly Parents: readonly ForkPathParent[];\n /** True when the path has more than three entries, so the ellipsis shows at every width. */\n readonly CollapsesWide: boolean;\n /** True when there is a crumb between the first and the current one, so narrow screens show the ellipsis. */\n readonly HasMiddle: boolean;\n}\n\n/** Paths longer than this show the first crumb, an ellipsis, and the last two entries. */\nexport const MAX_FULL_PATH_ENTRIES = 3;\n\n/**\n * The header path of a breadcrumb: the first crumb (the conversation) always shows; past three entries the\n * crumbs between it and the parent of the current entry collapse into an ellipsis.\n */\nexport function BuildForkHeaderPath(crumbs: readonly ForkBreadcrumb[]): ForkHeaderPath {\n const parents = crumbs.filter(c => !c.IsCurrent);\n const collapsesWide = crumbs.length > MAX_FULL_PATH_ENTRIES;\n const lastParent = parents.length - 1;\n return {\n Parents: parents.map((crumb, i) => ({\n Crumb: crumb,\n IsMiddle: i > 0,\n CollapsedWide: collapsesWide && i > 0 && i < lastParent,\n })),\n CollapsesWide: collapsesWide,\n HasMiddle: parents.length > 1,\n };\n}\n\n/** The label of the fork view's back button: \"Back to <parent fork>\", or \"Back to Main\" with no parent. */\nexport function BackLabelOf(parentLabel: string | null): string {\n return parentLabel ? `Back to ${parentLabel}` : 'Back to Main';\n}\n\n/** One avatar on a fork chip. */\nexport interface ForkChipAvatar {\n readonly Kind: 'User' | 'Agent';\n readonly ID: string;\n /** The participant's name, for the tooltip. */\n readonly Label: string;\n /** Up to two initials, shown when there is no image and no icon. */\n readonly Initials: string;\n readonly ImageURL: string | null;\n readonly IconClass: string | null;\n}\n\n/** What a chip under a message shows for one fork placed there. */\nexport interface ForkChip {\n readonly BranchID: string;\n readonly Kind: ForkKind;\n readonly DisplayName: string;\n readonly MessageLabel: string;\n readonly ActivityLabel: string;\n readonly Avatars: ReadonlyArray<ForkChipAvatar>;\n}\n\n/**\n * The chip, on a fork's first own message, that opens the view holding the message the fork replaced,\n * positioned at that message.\n */\nexport interface OriginalForkChip {\n readonly Kind: 'Original';\n /** The fork that holds the replaced message; null is Main. */\n readonly BranchID: string | null;\n /** The replaced message's Sequence; null when it is not known (the view opens at its latest rows). */\n readonly Sequence: number | null;\n /** \"Main\", or the display name of the fork that holds the replaced message. */\n readonly DisplayName: string;\n}\n\n/** A chip under a message: a fork placed there, or the original of the message a fork replaced. */\nexport type MessageForkChip = ForkChip | OriginalForkChip;\n\n/** What a clicked chip asks the host to open. */\nexport interface ForkChipOpenRequest {\n /** The fork to open; null opens Main. */\n readonly BranchID: string | null;\n /** When set, the view opens positioned at the row with this Sequence on that branch. */\n readonly Sequence?: number | null;\n}\n\n/** The chips of a message with no fork placed under it. */\nexport const EMPTY_FORK_CHIPS: readonly ForkChip[] = [];\n\n/** The name of Main on an original chip. */\nexport const MAIN_VIEW_LABEL = 'Main';\n\n/** The Font Awesome icon of a fork kind. */\nexport function ForkKindIcon(kind: ForkKind): string {\n switch (kind) {\n case 'Edit':\n return 'fa-solid fa-pen';\n case 'Regenerate':\n return 'fa-solid fa-rotate-left';\n default:\n return 'fa-solid fa-code-branch';\n }\n}\n\n/** The icon of a chip: the kind icon of a fork chip; on an original chip, a house for Main and the fork icon for a fork. */\nexport function ForkChipIcon(chip: MessageForkChip): string {\n if (chip.Kind === 'Original') {\n return chip.BranchID ? 'fa-solid fa-code-branch' : 'fa-solid fa-house';\n }\n return ForkKindIcon(chip.Kind);\n}\n\n/** The aria-label of a chip: \"Open fork <name>, <N messages>\", or \"Open the original in <name>\". */\nexport function ForkChipAriaLabel(chip: MessageForkChip): string {\n return chip.Kind === 'Original'\n ? `Open the original in ${chip.DisplayName}`\n : `Open fork ${chip.DisplayName}, ${chip.MessageLabel}`;\n}\n\n/** The request a clicked chip sends: its fork, or for an original chip, its view and the replaced message. */\nexport function ForkChipOpenRequestOf(chip: MessageForkChip): ForkChipOpenRequest {\n return chip.Kind === 'Original' ? { BranchID: chip.BranchID, Sequence: chip.Sequence } : { BranchID: chip.BranchID };\n}\n\n/** \"1 message\", or \"N messages\". */\nexport function ForkMessageLabel(count: number): string {\n return count === 1 ? '1 message' : `${count} messages`;\n}\n\n/** The initials of a name (first and last word), upper case; \"?\" for no name. */\nexport function ForkInitials(name: string | null | undefined): string {\n const words = (name ?? '').trim().split(/\\s+/).filter(w => w.length > 0);\n if (words.length === 0) {\n return '?';\n }\n const last = words.length > 1 ? words[words.length - 1][0] : '';\n return `${words[0][0]}${last}`.toUpperCase();\n}\n\n/**\n * The last activity of a fork relative to `now`: \"just now\" under a minute, \"N min ago\" under an\n * hour, \"N h ago\" under a day, \"yesterday\", \"N days ago\" under a week, else the short date.\n */\nexport function FormatForkActivity(at: Date, now: Date): string {\n const minutes = Math.floor((now.getTime() - at.getTime()) / 60000);\n if (minutes < 1) {\n return 'just now';\n }\n if (minutes < 60) {\n return `${minutes} min ago`;\n }\n const hours = Math.floor(minutes / 60);\n if (hours < 24) {\n return `${hours} h ago`;\n }\n const days = Math.floor(hours / 24);\n if (days === 1) {\n return 'yesterday';\n }\n if (days < 7) {\n return `${days} days ago`;\n }\n return at.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });\n}\n\n/** The chip of one fork. */\nfunction forkChipOf(summary: ForkSummary, avatarFor: (participant: ForkParticipant) => ForkChipAvatar, now: Date): ForkChip {\n return {\n BranchID: summary.Branch.ID,\n Kind: summary.Kind,\n DisplayName: summary.DisplayName,\n MessageLabel: ForkMessageLabel(summary.MessageCount),\n ActivityLabel: FormatForkActivity(summary.LastActivityAt, now),\n Avatars: summary.Participants.map(avatarFor),\n };\n}\n\n/**\n * The chips of each placement message: one chip per fork whose PlacementDetailID is that message,\n * in the order of `summaries`. Keys are normalized detail ids; a fork with no placement has no chip.\n */\nexport function BuildForkChipMap(\n summaries: ReadonlyArray<ForkSummary>,\n avatarFor: (participant: ForkParticipant) => ForkChipAvatar,\n now: Date\n): ReadonlyMap<string, readonly ForkChip[]> {\n const map = new Map<string, ForkChip[]>();\n for (const summary of summaries) {\n if (!summary.PlacementDetailID) {\n continue;\n }\n const chip = forkChipOf(summary, avatarFor, now);\n const key = NormalizeUUID(summary.PlacementDetailID);\n const list = map.get(key);\n if (list) {\n list.push(chip);\n } else {\n map.set(key, [chip]);\n }\n }\n return map;\n}\n\n/**\n * The alternatives a fork shows on its first own message: an original chip for the view that holds the\n * message the fork replaced (its SourceDetailID), then a chip for every other fork placed under that\n * message with the same parent branch, in the order of `summaries`. Empty when the fork is not in\n * `summaries` or replaced no message (its chips are then under its anchor, which its view shows).\n * @param forkLabel The display name of a fork, for an original chip of a fork.\n */\nexport function BuildForkAlternativeChips(\n forkId: string,\n summaries: ReadonlyArray<ForkSummary>,\n avatarFor: (participant: ForkParticipant) => ForkChipAvatar,\n now: Date,\n forkLabel: (branchId: string) => string\n): MessageForkChip[] {\n const fork = summaries.find(s => UUIDsEqual(s.Branch.ID, forkId));\n const placement = fork?.PlacementDetailID;\n if (!fork || !fork.Branch.SourceDetailID || !placement) {\n return [];\n }\n const position = fork.PlacementPosition ?? null;\n const branchId = position ? position.BranchID : fork.ParentBranchID;\n const original: OriginalForkChip = {\n Kind: 'Original',\n BranchID: branchId,\n Sequence: position?.Sequence ?? null,\n DisplayName: branchId ? forkLabel(branchId) : MAIN_VIEW_LABEL,\n };\n const siblings = summaries.filter(s =>\n !UUIDsEqual(s.Branch.ID, fork.Branch.ID)\n && s.PlacementDetailID != null\n && UUIDsEqual(s.PlacementDetailID, placement)\n && UUIDsEqual(s.ParentBranchID ?? null, fork.ParentBranchID ?? null));\n return [original, ...siblings.map(s => forkChipOf(s, avatarFor, now))];\n}\n\n/**\n * The chips of a view's messages: {@link BuildForkChipMap} over every fork but the one a fork view shows\n * (its chip would open the view already open). In a fork view, the fork's alternatives\n * ({@link BuildForkAlternativeChips}) come first on its first own message `firstOwnDetailId`.\n */\nexport function BuildViewForkChipMap(\n summaries: ReadonlyArray<ForkSummary>,\n view: ConversationOpenView,\n firstOwnDetailId: string | null,\n avatarFor: (participant: ForkParticipant) => ForkChipAvatar,\n now: Date,\n forkLabel: (branchId: string) => string\n): ReadonlyMap<string, readonly MessageForkChip[]> {\n if (view.Kind !== 'Fork') {\n return BuildForkChipMap(summaries, avatarFor, now);\n }\n const map: Map<string, readonly MessageForkChip[]> = new Map(\n BuildForkChipMap(summaries.filter(s => !UUIDsEqual(s.Branch.ID, view.BranchID)), avatarFor, now)\n );\n const alternatives = firstOwnDetailId ? BuildForkAlternativeChips(view.BranchID, summaries, avatarFor, now, forkLabel) : [];\n if (firstOwnDetailId && alternatives.length > 0) {\n const key = NormalizeUUID(firstOwnDetailId);\n map.set(key, [...alternatives, ...(map.get(key) ?? [])]);\n }\n return map;\n}\n\n/** The composer placeholder in Main. */\nexport const MAIN_COMPOSER_PLACEHOLDER = 'Type a message... (Ctrl+Enter to send)';\n\n/** The composer placeholder in a fork or draft view. */\nexport const FORK_COMPOSER_PLACEHOLDER = 'Message this fork';\n\n/** The loaded rows of a view, split by rail. */\nexport interface ForkRows<T> {\n /** Rows the view inherits: every loaded row that is not the fork's own; in a draft, the rows up to the anchor. */\n readonly Inherited: T[];\n /** The fork's own rows; in Main, every row. */\n readonly Own: T[];\n}\n\n/** Splits loaded rows for a view (see {@link ForkRows}). Keeps their order. */\nexport function SplitForkRows<T extends { BranchID?: string | null; Sequence: number }>(rows: readonly T[], view: ConversationOpenView): ForkRows<T> {\n if (view.Kind === 'Main') {\n return { Inherited: [], Own: [...rows] };\n }\n if (view.Kind === 'DraftFork') {\n const anchorSequence = view.ForkFromSequence;\n return { Inherited: anchorSequence == null ? [] : rows.filter(r => r.Sequence <= anchorSequence), Own: [] };\n }\n const inherited: T[] = [];\n const own: T[] = [];\n for (const r of rows) {\n (UUIDsEqual(r.BranchID ?? null, view.BranchID) ? own : inherited).push(r);\n }\n return { Inherited: inherited, Own: own };\n}\n\n/**\n * The id of a fork view's first own row (lowest Sequence) when the loaded rows reach it: an inherited\n * row is loaded, or no older rows remain (`hasMoreAbove` false). Null with no own row loaded.\n */\nexport function FirstOwnRowID<T extends { ID: string; Sequence: number }>(rows: ForkRows<T>, hasMoreAbove: boolean): string | null {\n if (rows.Own.length === 0 || (rows.Inherited.length === 0 && hasMoreAbove)) {\n return null;\n }\n let first = rows.Own[0];\n for (const row of rows.Own) {\n if (row.Sequence < first.Sequence) {\n first = row;\n }\n }\n return first.ID;\n}\n\n/** How the message list draws a fork or draft view. */\nexport interface ForkViewLayout {\n /** Normalized ids of the inherited rows on screen; every other row is an own row. */\n readonly InheritedIDs: ReadonlySet<string>;\n /** The row the fork marker shows under: the last inherited row on screen; null when none is. */\n readonly MarkerDetailID: string | null;\n /** The marker text; null when no inherited row is on screen, except in a draft, whose marker then stands alone. */\n readonly MarkerText: string | null;\n}\n\n/** The layout of `displayRows` in a fork or draft view; null in Main. */\nexport function BuildForkViewLayout<T extends { ID: string; BranchID?: string | null; Sequence: number }>(\n displayRows: readonly T[],\n view: ConversationOpenView,\n markerText: string | null\n): ForkViewLayout | null {\n if (view.Kind === 'Main') {\n return null;\n }\n const inherited = SplitForkRows(displayRows, view).Inherited;\n const last = inherited.length > 0 ? inherited[inherited.length - 1] : null;\n return {\n InheritedIDs: new Set(inherited.map(r => NormalizeUUID(r.ID))),\n MarkerDetailID: last ? last.ID : null,\n MarkerText: last || view.Kind === 'DraftFork' ? markerText : null,\n };\n}\n\n/**\n * The fork marker: \"<person> forked from <author>'s message · <time>\" (\"Someone\" with no person; the\n * \"from\" part only with an author; the time only when known). A draft: \"New fork from <author>'s\n * message · nothing is saved until you send\". `AnchorAuthorName` is the author the \"from\" part names: the\n * source message's author for a fork with a source, else the anchor's.\n */\nexport function BuildForkMarkerText(\n input: { IsDraft: boolean; StarterName: string | null; AnchorAuthorName: string | null; StartedAt: Date | null },\n formatTime: (d: Date) => string\n): string {\n const from = input.AnchorAuthorName ? ` from ${input.AnchorAuthorName}'s message` : '';\n if (input.IsDraft) {\n return `New fork${from}${DRAFT_MARKER_SUFFIX}`;\n }\n const time = input.StartedAt ? ` · ${formatTime(input.StartedAt)}` : '';\n return `${input.StarterName ?? 'Someone'} forked${from}${time}`;\n}\n\n/** The composer hint of a fork or draft view. */\nexport function BuildForkComposerHint(anchorAt: Date | null, formatTime: (d: Date) => string): string {\n return anchorAt\n ? `Agents see the conversation up to ${formatTime(anchorAt)}, plus this fork`\n : 'Agents see only this fork';\n}\n\n/** The filters of the forks list: every fork, or the forks the person started or wrote in. */\nexport type ForkListFilter = 'All' | 'Mine';\n\n/** One row of the forks list. */\nexport interface ForkListRow {\n readonly Summary: ForkSummary;\n /** Nesting depth under the row's parent in the list; 0 for a root. */\n readonly Depth: number;\n readonly Icon: string;\n /** \"<author>: <last message>\", or the last message alone; null with no message. */\n readonly PreviewText: string | null;\n /** \"<origin> · N messages · <time>\". */\n readonly MetaText: string;\n}\n\n/** How the fork started, in words: \"Forked by Priya\", \"Edit of Maya's message\", \"Regenerated by Jordan\". */\nexport function ForkOriginText(summary: ForkSummary): string {\n const starter = summary.StartedByName ?? 'someone';\n switch (summary.Kind) {\n case 'Edit':\n return `Edit of ${starter}'s message`;\n case 'Regenerate':\n return `Regenerated by ${starter}`;\n default:\n return `Forked by ${starter}`;\n }\n}\n\n/**\n * The rows of the forks list: roots (no parent, or a parent the filter left out) newest activity\n * first, each followed by its nested forks (one level deeper), also newest first. 'Mine' keeps the\n * forks the person started or wrote in.\n */\nexport function BuildForkListRows(summaries: ReadonlyArray<ForkSummary>, filter: ForkListFilter, userId: string, now: Date): ForkListRow[] {\n const me = NormalizeUUID(userId);\n const kept = filter === 'All'\n ? [...summaries]\n : summaries.filter(s => NormalizeUUID(s.StartedByUserID) === me || s.AuthorUserIDs.some(id => NormalizeUUID(id) === me));\n const keptIds = new Set(kept.map(s => NormalizeUUID(s.Branch.ID)));\n const newestFirst = (a: ForkSummary, b: ForkSummary) => b.LastActivityAt.getTime() - a.LastActivityAt.getTime();\n const children = new Map<string, ForkSummary[]>();\n const roots: ForkSummary[] = [];\n for (const summary of kept) {\n const parent = summary.ParentBranchID ? NormalizeUUID(summary.ParentBranchID) : null;\n if (parent && keptIds.has(parent)) {\n const list = children.get(parent);\n if (list) {\n list.push(summary);\n } else {\n children.set(parent, [summary]);\n }\n } else {\n roots.push(summary);\n }\n }\n const rows: ForkListRow[] = [];\n const visit = (summary: ForkSummary, depth: number): void => {\n const origin = ForkOriginText(summary);\n rows.push({\n Summary: summary,\n Depth: depth,\n Icon: ForkKindIcon(summary.Kind),\n PreviewText: summary.LastMessagePreview\n ? (summary.LastMessageAuthorName ? `${summary.LastMessageAuthorName}: ${summary.LastMessagePreview}` : summary.LastMessagePreview)\n : null,\n MetaText: `${origin} · ${ForkMessageLabel(summary.MessageCount)} · ${FormatForkActivity(summary.LastActivityAt, now)}`,\n });\n for (const child of (children.get(NormalizeUUID(summary.Branch.ID)) ?? []).sort(newestFirst)) {\n visit(child, depth + 1);\n }\n };\n for (const root of roots.sort(newestFirst)) {\n visit(root, 0);\n }\n return rows;\n}\n\n/** The inputs of {@link CanForkFrom}. */\nexport interface CanForkInput {\n /** The person holds the `Conversations: Fork` authorization. */\n readonly HoldsAuthorization: boolean;\n /** The stored value of the person's forking setting; undefined when there is none. */\n readonly SettingValue: string | null | undefined;\n /** The person may write to the conversation. */\n readonly MayWrite: boolean;\n}\n\n/** True when the person may start forks: the authorization, a forking setting that is not off, and write access. */\nexport function CanForkFrom(input: CanForkInput): boolean {\n return input.HoldsAuthorization && IsForkingSettingOn(input.SettingValue) && input.MayWrite;\n}\n\n/** The latest turn of a view, which in-place Edit and Regenerate may change when the person's forking is off. */\nexport interface LatestTurn {\n /** The view's last own User row. */\n readonly UserDetailID: string;\n /** The last row after it when that row is an AI row (the answer); null when the turn has no answer. */\n readonly AnswerDetailID: string | null;\n}\n\n/** The fields {@link FindLatestTurn} reads from a row. */\nexport interface TurnRow {\n readonly ID: string;\n readonly Role: string | null;\n readonly Sequence: number;\n readonly BranchID?: string | null;\n readonly AgentSessionID?: string | null;\n readonly ReplacedAt?: Date | string | null;\n}\n\n/** True when `row` is one of the view's own rows: a Main row in Main, a row of the fork in a fork view. */\nfunction isOwnRowOf(view: ConversationOpenView, row: { BranchID?: string | null }): boolean {\n return view.Kind === 'Fork' ? UUIDsEqual(row.BranchID ?? null, view.BranchID) : view.Kind === 'Main' && row.BranchID == null;\n}\n\n/**\n * The latest turn of a view: its last User row and, when the last row after it is an AI row, that row.\n * Replaced rows are skipped. Null in a draft, with no User row, when the last User row is inherited (not\n * one of the view's own rows), and when it belongs to a voice session.\n */\nexport function FindLatestTurn<T extends TurnRow>(rows: readonly T[], view: ConversationOpenView): LatestTurn | null {\n if (view.Kind === 'DraftFork') {\n return null;\n }\n const live = rows.filter(r => r.ReplacedAt == null).sort((a, b) => a.Sequence - b.Sequence);\n let user: T | undefined;\n for (let i = live.length - 1; i >= 0; i--) {\n if (live[i].Role === 'User') {\n user = live[i];\n break;\n }\n }\n if (!user || user.AgentSessionID || !isOwnRowOf(view, user)) {\n return null;\n }\n const last = live[live.length - 1];\n const isAnswer = last !== user && last.Role === 'AI' && !last.AgentSessionID && UUIDsEqual(last.BranchID ?? null, user.BranchID ?? null);\n return { UserDetailID: user.ID, AnswerDetailID: isAnswer ? last.ID : null };\n}\n\n/**\n * The rows after `userMessage` on its branch, in Sequence order, without replaced rows: for the latest\n * User row, the rows of its answer (the reply and any delegated or status rows).\n */\nexport function TurnRowsAfter<T extends { Sequence: number; BranchID?: string | null; ReplacedAt?: Date | string | null }>(\n rows: readonly T[],\n userMessage: { Sequence: number; BranchID?: string | null }\n): T[] {\n return rows\n .filter(r => r.Sequence > userMessage.Sequence && r.ReplacedAt == null && UUIDsEqual(r.BranchID ?? null, userMessage.BranchID ?? null))\n .sort((a, b) => a.Sequence - b.Sequence);\n}\n\n/**\n * True when hiding `rows` (rows of branch `branchId`; null is Main) would change a fork: a fork of that\n * branch whose fork point is at or after the first of them (it inherits them), or a fork placed under one\n * of them (its SourceDetailID). False for no rows.\n */\nexport function ForkDependsOnRows(\n branchId: string | null,\n rows: ReadonlyArray<{ ID: string; Sequence: number }>,\n branches: ReadonlyArray<Pick<ConversationBranchRow, 'ParentBranchID' | 'ForkFromSequence' | 'SourceDetailID'>>\n): boolean {\n if (rows.length === 0) {\n return false;\n }\n const first = Math.min(...rows.map(r => r.Sequence));\n const rowIds = new UUIDSet(rows.map(r => r.ID));\n return branches.some(b =>\n (b.ForkFromSequence != null && b.ForkFromSequence >= first && UUIDsEqual(b.ParentBranchID ?? null, branchId))\n || (b.SourceDetailID != null && rowIds.Has(b.SourceDetailID)));\n}\n"]}
|
|
@@ -6,17 +6,23 @@
|
|
|
6
6
|
* surface all of them. At the same time, a single artifact can have multiple
|
|
7
7
|
* versions, and only the latest should render. This helper reconciles both.
|
|
8
8
|
*/
|
|
9
|
-
/** Minimal shape needed to dedupe artifacts — satisfied by `LazyArtifactInfo`. */
|
|
9
|
+
/** Minimal shape needed to dedupe and chronologically order artifacts — satisfied by `LazyArtifactInfo`. */
|
|
10
10
|
export interface DistinctArtifactKey {
|
|
11
11
|
artifactId: string;
|
|
12
12
|
versionNumber: number;
|
|
13
|
+
versionCreatedAt?: Date | string | null;
|
|
13
14
|
}
|
|
14
15
|
/**
|
|
15
16
|
* Collapses a message's artifact list to one entry per distinct `artifactId`,
|
|
16
17
|
* keeping the highest `versionNumber` for each. Distinct artifacts are all
|
|
17
18
|
* retained (so a report and an image both surface); multiple versions of the
|
|
18
|
-
* SAME artifact collapse to the latest.
|
|
19
|
-
*
|
|
19
|
+
* SAME artifact collapse to the latest.
|
|
20
|
+
*
|
|
21
|
+
* When `versionCreatedAt` timestamps are present on the latest versions, the
|
|
22
|
+
* resulting distinct artifacts are ordered chronologically (`versionCreatedAt`
|
|
23
|
+
* ascending) so multi-step workflow outputs render in creation order with the
|
|
24
|
+
* final deliverable last. When timestamps are absent or equal, first-seen input
|
|
25
|
+
* order is preserved.
|
|
20
26
|
*
|
|
21
27
|
* Ids are grouped as UUIDs, not as raw strings: SQL Server returns them upper-case and PostgreSQL
|
|
22
28
|
* lower-case, so a list assembled from differently-cased sources would otherwise render the same
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"distinct-artifacts.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/distinct-artifacts.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,
|
|
1
|
+
{"version":3,"file":"distinct-artifacts.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/distinct-artifacts.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,4GAA4G;AAC5G,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAAC;CACzC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,6BAA6B,CAAC,CAAC,SAAS,mBAAmB,EAAE,IAAI,EAAE,SAAS,CAAC,EAAE,GAAG,CAAC,EAAE,CAuBpG;AAED,6DAA6D;AAC7D,wBAAgB,6BAA6B,CAAC,CAAC,SAAS,mBAAmB,EAAE,IAAI,EAAE,SAAS,CAAC,EAAE,GAAG,CAAC,EAAE,CAEpG"}
|
|
@@ -11,8 +11,13 @@ import { NormalizeUUID } from '@memberjunction/global';
|
|
|
11
11
|
* Collapses a message's artifact list to one entry per distinct `artifactId`,
|
|
12
12
|
* keeping the highest `versionNumber` for each. Distinct artifacts are all
|
|
13
13
|
* retained (so a report and an image both surface); multiple versions of the
|
|
14
|
-
* SAME artifact collapse to the latest.
|
|
15
|
-
*
|
|
14
|
+
* SAME artifact collapse to the latest.
|
|
15
|
+
*
|
|
16
|
+
* When `versionCreatedAt` timestamps are present on the latest versions, the
|
|
17
|
+
* resulting distinct artifacts are ordered chronologically (`versionCreatedAt`
|
|
18
|
+
* ascending) so multi-step workflow outputs render in creation order with the
|
|
19
|
+
* final deliverable last. When timestamps are absent or equal, first-seen input
|
|
20
|
+
* order is preserved.
|
|
16
21
|
*
|
|
17
22
|
* Ids are grouped as UUIDs, not as raw strings: SQL Server returns them upper-case and PostgreSQL
|
|
18
23
|
* lower-case, so a list assembled from differently-cased sources would otherwise render the same
|
|
@@ -24,14 +29,28 @@ import { NormalizeUUID } from '@memberjunction/global';
|
|
|
24
29
|
*/
|
|
25
30
|
export function SelectDistinctLatestArtifacts(list) {
|
|
26
31
|
const latestByArtifact = new Map();
|
|
32
|
+
let index = 0;
|
|
27
33
|
for (const info of list) {
|
|
28
34
|
const key = NormalizeUUID(info.artifactId);
|
|
29
35
|
const existing = latestByArtifact.get(key);
|
|
30
|
-
if (!existing
|
|
31
|
-
latestByArtifact.set(key, info);
|
|
36
|
+
if (!existing) {
|
|
37
|
+
latestByArtifact.set(key, { item: info, firstSeenIndex: index });
|
|
38
|
+
}
|
|
39
|
+
else if (info.versionNumber > existing.item.versionNumber) {
|
|
40
|
+
latestByArtifact.set(key, { item: info, firstSeenIndex: existing.firstSeenIndex });
|
|
32
41
|
}
|
|
42
|
+
index++;
|
|
33
43
|
}
|
|
34
|
-
|
|
44
|
+
const entries = Array.from(latestByArtifact.values());
|
|
45
|
+
entries.sort((a, b) => {
|
|
46
|
+
const aTime = a.item.versionCreatedAt ? new Date(a.item.versionCreatedAt).getTime() : NaN;
|
|
47
|
+
const bTime = b.item.versionCreatedAt ? new Date(b.item.versionCreatedAt).getTime() : NaN;
|
|
48
|
+
if (!Number.isNaN(aTime) && !Number.isNaN(bTime) && aTime !== bTime) {
|
|
49
|
+
return aTime - bTime;
|
|
50
|
+
}
|
|
51
|
+
return a.firstSeenIndex - b.firstSeenIndex;
|
|
52
|
+
});
|
|
53
|
+
return entries.map((e) => e.item);
|
|
35
54
|
}
|
|
36
55
|
/** @deprecated Use {@link SelectDistinctLatestArtifacts}. */
|
|
37
56
|
export function selectDistinctLatestArtifacts(list) {
|