@genesislcap/ai-assistant 15.19.0 → 15.19.1
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/package.json +17 -18
- package/api-extractor.json +0 -11
- package/dist/dts/components/chat-driver/chat-driver.compact.test.d.ts +0 -2
- package/dist/dts/components/chat-driver/chat-driver.compact.test.d.ts.map +0 -1
- package/dist/dts/components/chat-driver/chat-driver.invocation-scope.test.d.ts +0 -2
- package/dist/dts/components/chat-driver/chat-driver.invocation-scope.test.d.ts.map +0 -1
- package/dist/dts/components/chat-driver/chat-driver.test.d.ts +0 -2
- package/dist/dts/components/chat-driver/chat-driver.test.d.ts.map +0 -1
- package/dist/dts/components/chat-driver/chat-driver.thinking-policy.test.d.ts +0 -2
- package/dist/dts/components/chat-driver/chat-driver.thinking-policy.test.d.ts.map +0 -1
- package/dist/dts/components/chat-driver/chat-driver.trace-capture.test.d.ts +0 -2
- package/dist/dts/components/chat-driver/chat-driver.trace-capture.test.d.ts.map +0 -1
- package/dist/dts/components/chat-driver/chat-driver.turn-usage.test.d.ts +0 -2
- package/dist/dts/components/chat-driver/chat-driver.turn-usage.test.d.ts.map +0 -1
- package/dist/dts/components/chat-interaction-wrapper/chat-interaction-wrapper.test.d.ts +0 -2
- package/dist/dts/components/chat-interaction-wrapper/chat-interaction-wrapper.test.d.ts.map +0 -1
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.budget.test.d.ts +0 -2
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.budget.test.d.ts.map +0 -1
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.cost.test.d.ts +0 -2
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.cost.test.d.ts.map +0 -1
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.pin.test.d.ts +0 -2
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.pin.test.d.ts.map +0 -1
- package/dist/dts/components/settings-modal/settings-modal.styles.test.d.ts +0 -2
- package/dist/dts/components/settings-modal/settings-modal.styles.test.d.ts.map +0 -1
- package/dist/dts/components/settings-modal/settings-modal.template.test.d.ts +0 -2
- package/dist/dts/components/settings-modal/settings-modal.template.test.d.ts.map +0 -1
- package/dist/dts/config/define-stateful-agent.test.d.ts +0 -2
- package/dist/dts/config/define-stateful-agent.test.d.ts.map +0 -1
- package/dist/dts/config/validate-providers.test.d.ts +0 -2
- package/dist/dts/config/validate-providers.test.d.ts.map +0 -1
- package/dist/dts/main/blocked-state.test.d.ts +0 -2
- package/dist/dts/main/blocked-state.test.d.ts.map +0 -1
- package/dist/dts/main/budget-meter.test.d.ts +0 -2
- package/dist/dts/main/budget-meter.test.d.ts.map +0 -1
- package/dist/dts/main/cost-session-banking.test.d.ts +0 -2
- package/dist/dts/main/cost-session-banking.test.d.ts.map +0 -1
- package/dist/dts/main/file-attachments.test.d.ts +0 -2
- package/dist/dts/main/file-attachments.test.d.ts.map +0 -1
- package/dist/dts/main/interaction-cost.test.d.ts +0 -2
- package/dist/dts/main/interaction-cost.test.d.ts.map +0 -1
- package/dist/dts/main/main.styles.test.d.ts +0 -2
- package/dist/dts/main/main.styles.test.d.ts.map +0 -1
- package/dist/dts/main/persistence-broken-sources.test.d.ts +0 -2
- package/dist/dts/main/persistence-broken-sources.test.d.ts.map +0 -1
- package/dist/dts/main/popout-interaction-gate.test.d.ts +0 -2
- package/dist/dts/main/popout-interaction-gate.test.d.ts.map +0 -1
- package/dist/dts/state/ai-assistant-slice.test.d.ts +0 -2
- package/dist/dts/state/ai-assistant-slice.test.d.ts.map +0 -1
- package/dist/dts/state/debug-event-log.test.d.ts +0 -2
- package/dist/dts/state/debug-event-log.test.d.ts.map +0 -1
- package/dist/dts/state/driver-registry.test.d.ts +0 -2
- package/dist/dts/state/driver-registry.test.d.ts.map +0 -1
- package/dist/dts/state/interaction-context.test.d.ts +0 -2
- package/dist/dts/state/interaction-context.test.d.ts.map +0 -1
- package/dist/dts/state/persistence/diagnostics-cursors.test.d.ts +0 -2
- package/dist/dts/state/persistence/diagnostics-cursors.test.d.ts.map +0 -1
- package/dist/dts/state/persistence/diagnostics.test.d.ts +0 -2
- package/dist/dts/state/persistence/diagnostics.test.d.ts.map +0 -1
- package/dist/dts/state/persistence/session-persistence-provider.test.d.ts +0 -2
- package/dist/dts/state/persistence/session-persistence-provider.test.d.ts.map +0 -1
- package/dist/dts/state/persistence/session-persistence.integration.test.d.ts +0 -2
- package/dist/dts/state/persistence/session-persistence.integration.test.d.ts.map +0 -1
- package/dist/dts/state/persistence/session-persister.test.d.ts +0 -2
- package/dist/dts/state/persistence/session-persister.test.d.ts.map +0 -1
- package/dist/dts/state/persistence/session-snapshot.test.d.ts +0 -2
- package/dist/dts/state/persistence/session-snapshot.test.d.ts.map +0 -1
- package/dist/dts/state/persistence/stateful-restore.e2e.test.d.ts +0 -2
- package/dist/dts/state/persistence/stateful-restore.e2e.test.d.ts.map +0 -1
- package/dist/dts/utils/animation-exclusivity.test.d.ts +0 -2
- package/dist/dts/utils/animation-exclusivity.test.d.ts.map +0 -1
- package/dist/dts/utils/collect-session-models.test.d.ts +0 -2
- package/dist/dts/utils/collect-session-models.test.d.ts.map +0 -1
- package/dist/dts/utils/condense-history.test.d.ts +0 -2
- package/dist/dts/utils/condense-history.test.d.ts.map +0 -1
- package/dist/dts/utils/cost-session-history.test.d.ts +0 -2
- package/dist/dts/utils/cost-session-history.test.d.ts.map +0 -1
- package/dist/dts/utils/derive-cost-session-title.test.d.ts +0 -2
- package/dist/dts/utils/derive-cost-session-title.test.d.ts.map +0 -1
- package/dist/dts/utils/flatten-sub-agent-messages.test.d.ts +0 -2
- package/dist/dts/utils/flatten-sub-agent-messages.test.d.ts.map +0 -1
- package/dist/dts/utils/history-transform.test.d.ts +0 -2
- package/dist/dts/utils/history-transform.test.d.ts.map +0 -1
- package/dist/dts/utils/message-partition.test.d.ts +0 -2
- package/dist/dts/utils/message-partition.test.d.ts.map +0 -1
- package/dist/dts/utils/resolve-cost-history-config.test.d.ts +0 -2
- package/dist/dts/utils/resolve-cost-history-config.test.d.ts.map +0 -1
- package/dist/dts/utils/resolve-preference-baseline.test.d.ts +0 -2
- package/dist/dts/utils/resolve-preference-baseline.test.d.ts.map +0 -1
- package/dist/dts/utils/strip-agent-handlers.test.d.ts +0 -2
- package/dist/dts/utils/strip-agent-handlers.test.d.ts.map +0 -1
- package/dist/dts/utils/sum-costs.test.d.ts +0 -2
- package/dist/dts/utils/sum-costs.test.d.ts.map +0 -1
- package/dist/dts/utils/sum-tokens.test.d.ts +0 -2
- package/dist/dts/utils/sum-tokens.test.d.ts.map +0 -1
- package/dist/dts/utils/sum-usage.test.d.ts +0 -2
- package/dist/dts/utils/sum-usage.test.d.ts.map +0 -1
- package/dist/dts/utils/usage-rows.test.d.ts +0 -2
- package/dist/dts/utils/usage-rows.test.d.ts.map +0 -1
- package/dist/esm/components/chat-driver/chat-driver.compact.test.js +0 -93
- package/dist/esm/components/chat-driver/chat-driver.invocation-scope.test.js +0 -238
- package/dist/esm/components/chat-driver/chat-driver.test.js +0 -2770
- package/dist/esm/components/chat-driver/chat-driver.thinking-policy.test.js +0 -137
- package/dist/esm/components/chat-driver/chat-driver.trace-capture.test.js +0 -200
- package/dist/esm/components/chat-driver/chat-driver.turn-usage.test.js +0 -268
- package/dist/esm/components/chat-interaction-wrapper/chat-interaction-wrapper.test.js +0 -104
- package/dist/esm/components/orchestrating-driver/orchestrating-driver.budget.test.js +0 -312
- package/dist/esm/components/orchestrating-driver/orchestrating-driver.cost.test.js +0 -76
- package/dist/esm/components/orchestrating-driver/orchestrating-driver.pin.test.js +0 -61
- package/dist/esm/components/settings-modal/settings-modal.styles.test.js +0 -80
- package/dist/esm/components/settings-modal/settings-modal.template.test.js +0 -91
- package/dist/esm/config/define-stateful-agent.test.js +0 -53
- package/dist/esm/config/validate-providers.test.js +0 -100
- package/dist/esm/main/blocked-state.test.js +0 -1065
- package/dist/esm/main/budget-meter.test.js +0 -359
- package/dist/esm/main/cost-session-banking.test.js +0 -538
- package/dist/esm/main/file-attachments.test.js +0 -164
- package/dist/esm/main/interaction-cost.test.js +0 -107
- package/dist/esm/main/main.styles.test.js +0 -161
- package/dist/esm/main/persistence-broken-sources.test.js +0 -180
- package/dist/esm/main/popout-interaction-gate.test.js +0 -54
- package/dist/esm/state/ai-assistant-slice.test.js +0 -229
- package/dist/esm/state/debug-event-log.test.js +0 -117
- package/dist/esm/state/driver-registry.test.js +0 -55
- package/dist/esm/state/interaction-context.test.js +0 -78
- package/dist/esm/state/persistence/diagnostics-cursors.test.js +0 -30
- package/dist/esm/state/persistence/diagnostics.test.js +0 -280
- package/dist/esm/state/persistence/session-persistence-provider.test.js +0 -110
- package/dist/esm/state/persistence/session-persistence.integration.test.js +0 -113
- package/dist/esm/state/persistence/session-persister.test.js +0 -613
- package/dist/esm/state/persistence/session-snapshot.test.js +0 -256
- package/dist/esm/state/persistence/stateful-restore.e2e.test.js +0 -158
- package/dist/esm/utils/animation-exclusivity.test.js +0 -52
- package/dist/esm/utils/collect-session-models.test.js +0 -53
- package/dist/esm/utils/condense-history.test.js +0 -547
- package/dist/esm/utils/cost-session-history.test.js +0 -174
- package/dist/esm/utils/derive-cost-session-title.test.js +0 -37
- package/dist/esm/utils/flatten-sub-agent-messages.test.js +0 -139
- package/dist/esm/utils/history-transform.test.js +0 -158
- package/dist/esm/utils/message-partition.test.js +0 -90
- package/dist/esm/utils/resolve-cost-history-config.test.js +0 -32
- package/dist/esm/utils/resolve-preference-baseline.test.js +0 -69
- package/dist/esm/utils/strip-agent-handlers.test.js +0 -81
- package/dist/esm/utils/sum-costs.test.js +0 -114
- package/dist/esm/utils/sum-tokens.test.js +0 -51
- package/dist/esm/utils/sum-usage.test.js +0 -164
- package/dist/esm/utils/usage-rows.test.js +0 -189
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/license.txt +0 -46
- package/sandbox/tsconfig.json +0 -8
- package/src/channel/ai-activity-bus.ts +0 -209
- package/src/channel/ai-activity-channel.ts +0 -146
- package/src/chat-driver-node.ts +0 -159
- package/src/components/activity-halo/activity-halo.ts +0 -106
- package/src/components/agent-picker/agent-picker.constants.ts +0 -2
- package/src/components/agent-picker/agent-picker.styles.ts +0 -24
- package/src/components/agent-picker/agent-picker.template.ts +0 -54
- package/src/components/agent-picker/agent-picker.ts +0 -99
- package/src/components/agent-picker/index.ts +0 -1
- package/src/components/ai-driver/ai-driver.ts +0 -166
- package/src/components/ai-driver/index.ts +0 -1
- package/src/components/chat-bubble/chat-bubble.styles.ts +0 -194
- package/src/components/chat-bubble/chat-bubble.template.ts +0 -67
- package/src/components/chat-bubble/chat-bubble.ts +0 -436
- package/src/components/chat-bubble/index.ts +0 -1
- package/src/components/chat-driver/align-event-globals.ts +0 -23
- package/src/components/chat-driver/chat-driver.compact.test.ts +0 -131
- package/src/components/chat-driver/chat-driver.invocation-scope.test.ts +0 -285
- package/src/components/chat-driver/chat-driver.test.ts +0 -3679
- package/src/components/chat-driver/chat-driver.thinking-policy.test.ts +0 -185
- package/src/components/chat-driver/chat-driver.trace-capture.test.ts +0 -251
- package/src/components/chat-driver/chat-driver.ts +0 -3925
- package/src/components/chat-driver/chat-driver.turn-usage.test.ts +0 -362
- package/src/components/chat-driver/index.ts +0 -1
- package/src/components/chat-interaction-wrapper/chat-interaction-wrapper.styles.ts +0 -7
- package/src/components/chat-interaction-wrapper/chat-interaction-wrapper.template.ts +0 -6
- package/src/components/chat-interaction-wrapper/chat-interaction-wrapper.test.ts +0 -144
- package/src/components/chat-interaction-wrapper/chat-interaction-wrapper.ts +0 -243
- package/src/components/chat-interaction-wrapper/index.ts +0 -3
- package/src/components/chat-markdown/chat-markdown.ts +0 -89
- package/src/components/chat-markdown/index.ts +0 -1
- package/src/components/flowing-waves-indicator.ts +0 -286
- package/src/components/halo-overlay.ts +0 -184
- package/src/components/orchestrating-driver/index.ts +0 -1
- package/src/components/orchestrating-driver/orchestrating-driver.budget.test.ts +0 -438
- package/src/components/orchestrating-driver/orchestrating-driver.cost.test.ts +0 -107
- package/src/components/orchestrating-driver/orchestrating-driver.pin.test.ts +0 -96
- package/src/components/orchestrating-driver/orchestrating-driver.ts +0 -813
- package/src/components/plasma-orb-indicator.ts +0 -281
- package/src/components/popout-manager/index.ts +0 -1
- package/src/components/popout-manager/popout-manager.ts +0 -212
- package/src/components/settings-modal/index.ts +0 -2
- package/src/components/settings-modal/settings-modal.styles.test.ts +0 -94
- package/src/components/settings-modal/settings-modal.styles.ts +0 -853
- package/src/components/settings-modal/settings-modal.template.test.ts +0 -124
- package/src/components/settings-modal/settings-modal.template.ts +0 -943
- package/src/components/waves-indicator.ts +0 -241
- package/src/config/config.ts +0 -608
- package/src/config/define-stateful-agent.test.ts +0 -64
- package/src/config/define-stateful-agent.ts +0 -642
- package/src/config/fallback-agents.ts +0 -29
- package/src/config/index.ts +0 -2
- package/src/config/validate-providers.test.ts +0 -148
- package/src/config/validate-providers.ts +0 -58
- package/src/index.ts +0 -29
- package/src/main/blocked-state.test.ts +0 -1444
- package/src/main/budget-meter.test.ts +0 -477
- package/src/main/cost-session-banking.test.ts +0 -679
- package/src/main/file-attachments.test.ts +0 -215
- package/src/main/index.ts +0 -3
- package/src/main/interaction-cost.test.ts +0 -140
- package/src/main/main.styles.test.ts +0 -216
- package/src/main/main.styles.ts +0 -995
- package/src/main/main.template.ts +0 -1119
- package/src/main/main.ts +0 -5432
- package/src/main/main.types.ts +0 -225
- package/src/main/persistence-broken-sources.test.ts +0 -219
- package/src/main/popout-interaction-gate.test.ts +0 -87
- package/src/provider/ai-provider-switcher.ts +0 -40
- package/src/provider/assistant-app-settings.ts +0 -84
- package/src/state/ai-assistant-slice.test.ts +0 -336
- package/src/state/ai-assistant-slice.ts +0 -604
- package/src/state/debug-event-log.test.ts +0 -154
- package/src/state/debug-event-log.ts +0 -358
- package/src/state/driver-registry.test.ts +0 -70
- package/src/state/driver-registry.ts +0 -79
- package/src/state/interaction-context.test.ts +0 -90
- package/src/state/interaction-context.ts +0 -101
- package/src/state/persistence/build-timeline-entries.ts +0 -66
- package/src/state/persistence/diagnostics-cursors.test.ts +0 -53
- package/src/state/persistence/diagnostics-cursors.ts +0 -80
- package/src/state/persistence/diagnostics.test.ts +0 -346
- package/src/state/persistence/diagnostics.ts +0 -220
- package/src/state/persistence/index.ts +0 -10
- package/src/state/persistence/persister-registry.ts +0 -55
- package/src/state/persistence/session-persistence-provider.test.ts +0 -136
- package/src/state/persistence/session-persistence-provider.ts +0 -226
- package/src/state/persistence/session-persistence.integration.test.ts +0 -141
- package/src/state/persistence/session-persister.test.ts +0 -787
- package/src/state/persistence/session-persister.ts +0 -917
- package/src/state/persistence/session-snapshot.test.ts +0 -352
- package/src/state/persistence/session-snapshot.ts +0 -344
- package/src/state/persistence/stateful-restore.e2e.test.ts +0 -229
- package/src/state/session-store.ts +0 -56
- package/src/styles/ai-colours.ts +0 -30
- package/src/styles/menu.ts +0 -187
- package/src/styles/settings-section.ts +0 -40
- package/src/suggestions/chat-suggestions.ts +0 -186
- package/src/tags/index.ts +0 -1
- package/src/types/ai-chat-widget.ts +0 -67
- package/src/types/interaction-context.ts +0 -82
- package/src/utils/animated-panel-toggle.ts +0 -62
- package/src/utils/animation-exclusivity.test.ts +0 -72
- package/src/utils/animation-exclusivity.ts +0 -40
- package/src/utils/banked-usage-baselines.ts +0 -90
- package/src/utils/collect-session-models.test.ts +0 -82
- package/src/utils/collect-session-models.ts +0 -65
- package/src/utils/condense-history.test.ts +0 -746
- package/src/utils/condense-history.ts +0 -448
- package/src/utils/cost-session-history.test.ts +0 -226
- package/src/utils/cost-session-history.ts +0 -218
- package/src/utils/derive-cost-session-title.test.ts +0 -63
- package/src/utils/derive-cost-session-title.ts +0 -76
- package/src/utils/flatten-sub-agent-messages.test.ts +0 -163
- package/src/utils/flatten-sub-agent-messages.ts +0 -88
- package/src/utils/format-usd.ts +0 -26
- package/src/utils/history-transform.test.ts +0 -228
- package/src/utils/history-transform.ts +0 -163
- package/src/utils/index.ts +0 -3
- package/src/utils/logger.ts +0 -3
- package/src/utils/message-partition.test.ts +0 -129
- package/src/utils/message-partition.ts +0 -115
- package/src/utils/resolve-cost-history-config.test.ts +0 -38
- package/src/utils/resolve-cost-history-config.ts +0 -38
- package/src/utils/resolve-preference-baseline.test.ts +0 -84
- package/src/utils/resolve-preference-baseline.ts +0 -80
- package/src/utils/strip-agent-handlers.test.ts +0 -99
- package/src/utils/strip-agent-handlers.ts +0 -53
- package/src/utils/sum-costs.test.ts +0 -136
- package/src/utils/sum-costs.ts +0 -18
- package/src/utils/sum-tokens.test.ts +0 -93
- package/src/utils/sum-tokens.ts +0 -68
- package/src/utils/sum-usage.test.ts +0 -191
- package/src/utils/sum-usage.ts +0 -208
- package/src/utils/tool-fold.ts +0 -181
- package/src/utils/usage-rows.test.ts +0 -237
- package/src/utils/usage-rows.ts +0 -187
- package/src/utils/with-timeout.ts +0 -24
- package/tsconfig.json +0 -9
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
import type { InteractionResult } from '@genesislcap/foundation-ai';
|
|
2
|
-
import { customElement, GenesisElement, observable } from '@genesislcap/web-core';
|
|
3
|
-
import { createInteractionContext } from '../../state/interaction-context';
|
|
4
|
-
import type { AiChatInteraction, AiChatWidget } from '../../types/ai-chat-widget';
|
|
5
|
-
import type { InteractionContext } from '../../types/interaction-context';
|
|
6
|
-
import { logger } from '../../utils/logger';
|
|
7
|
-
import { AiChatInteractionWrapperStyles } from './chat-interaction-wrapper.styles';
|
|
8
|
-
import { AiChatInteractionWrapperTemplate } from './chat-interaction-wrapper.template';
|
|
9
|
-
|
|
10
|
-
/** Structural view of the assistant host, pierced across the wrapper's shadow boundary. */
|
|
11
|
-
type InteractionHost = {
|
|
12
|
-
isEngaged?: boolean;
|
|
13
|
-
getInteractionContext?: (id: string) => InteractionContext | undefined;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
export type ChatInteractionEventsMap = {
|
|
17
|
-
'interaction-completed': InteractionResult<unknown> & { interactionId: string };
|
|
18
|
-
'interaction-cost': { interactionId: string; costUsd: number };
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Detail payload for the `interaction-completed` event.
|
|
23
|
-
* @public
|
|
24
|
-
*/
|
|
25
|
-
export type InteractionCompletedEventDetail = ChatInteractionEventsMap['interaction-completed'];
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Detail payload for the `interaction-cost` event — a POSITIVE DELTA in USD, not a running total.
|
|
29
|
-
* @public
|
|
30
|
-
*/
|
|
31
|
-
export type InteractionCostEventDetail = ChatInteractionEventsMap['interaction-cost'];
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Hosts an interactive AI chat sub-component by tag name.
|
|
35
|
-
* @public
|
|
36
|
-
*
|
|
37
|
-
* @fires interaction-completed - Fired when the interaction finishes. detail: `InteractionCompletedEventDetail`
|
|
38
|
-
* @fires interaction-cost - Fired when a widget spends money AFTER resolving. detail: `InteractionCostEventDetail`
|
|
39
|
-
*/
|
|
40
|
-
@customElement({
|
|
41
|
-
name: 'ai-chat-interaction-wrapper',
|
|
42
|
-
template: AiChatInteractionWrapperTemplate,
|
|
43
|
-
styles: AiChatInteractionWrapperStyles,
|
|
44
|
-
})
|
|
45
|
-
export class AiChatInteractionWrapper extends GenesisElement {
|
|
46
|
-
/** @internal */
|
|
47
|
-
@observable componentName: string = '';
|
|
48
|
-
/** @internal */
|
|
49
|
-
@observable data: any;
|
|
50
|
-
/** @internal */
|
|
51
|
-
@observable interactionId: string = '';
|
|
52
|
-
/** The resolved result once the interaction has completed. Forwarded to the rendered component. @internal */
|
|
53
|
-
@observable resolved: InteractionResult<unknown> | undefined = undefined;
|
|
54
|
-
/** @internal */
|
|
55
|
-
container!: HTMLElement;
|
|
56
|
-
|
|
57
|
-
/** Observes the rendered widget for mid-lifecycle size changes — see {@link observeWidgetResize}. */
|
|
58
|
-
private _resizeObserver?: ResizeObserver;
|
|
59
|
-
|
|
60
|
-
connectedCallback() {
|
|
61
|
-
super.connectedCallback();
|
|
62
|
-
this.renderComponent();
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
disconnectedCallback() {
|
|
66
|
-
super.disconnectedCallback();
|
|
67
|
-
this._resizeObserver?.disconnect();
|
|
68
|
-
this._resizeObserver = undefined;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
componentNameChanged() {
|
|
72
|
-
this.renderComponent();
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Re-render the hosted widget when the interaction identity changes. This
|
|
77
|
-
* ensures widgets don't go stale with them being rendered with `recycle: true`
|
|
78
|
-
* when messages are dynamically filtered, showing/hiding thinking/tool calls
|
|
79
|
-
*
|
|
80
|
-
* `interactionId` is a per-interaction UUID, so it changes exactly when the
|
|
81
|
-
* hosted interaction does. Property bindings apply in template order
|
|
82
|
-
* (componentName, data, interactionId), so `data` is already rebound by the
|
|
83
|
-
* time this fires and the fresh widget is built with the correct data.
|
|
84
|
-
*/
|
|
85
|
-
interactionIdChanged() {
|
|
86
|
-
this.renderComponent();
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
resolvedChanged() {
|
|
90
|
-
const element = this.container?.firstElementChild as (AiChatWidget & HTMLElement) | null;
|
|
91
|
-
// The interaction just resolved: forward the resolved descriptor to the LIVE widget
|
|
92
|
-
// in place, preserving its DOM/state (no re-create). If no matching element exists
|
|
93
|
-
// yet (e.g. a history re-render straight into the resolved state), render it fresh.
|
|
94
|
-
if (element && element.tagName?.toLowerCase() === this.componentName) {
|
|
95
|
-
if (this.resolved) {
|
|
96
|
-
element.interaction = { status: 'resolved', data: this.data, result: this.resolved };
|
|
97
|
-
}
|
|
98
|
-
} else if (this.resolved) {
|
|
99
|
-
this.renderComponent();
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Watch the rendered widget element for box-size changes. When the widget
|
|
105
|
-
* grows or shrinks mid-lifecycle (e.g. expanding a "More info" panel,
|
|
106
|
-
* appending an SSE status row, revealing generated code), bubble a
|
|
107
|
-
* `request-scroll-to-bottom` event so `FoundationAiAssistant` re-snaps to
|
|
108
|
-
* the bottom. `scrollToBottom` respects `_userScrolledAway`, so a user
|
|
109
|
-
* reading old messages is not yanked back.
|
|
110
|
-
*
|
|
111
|
-
* The first ResizeObserver callback fires synchronously with the initial
|
|
112
|
-
* size when `observe()` is called; we skip it so freshly-rendered widgets
|
|
113
|
-
* don't double up with the existing `handleMessagesUpdate` auto-scroll for
|
|
114
|
-
* new messages.
|
|
115
|
-
*/
|
|
116
|
-
private observeWidgetResize(element: Element) {
|
|
117
|
-
this._resizeObserver?.disconnect();
|
|
118
|
-
let initialFireSkipped = false;
|
|
119
|
-
this._resizeObserver = new ResizeObserver(() => {
|
|
120
|
-
if (!initialFireSkipped) {
|
|
121
|
-
initialFireSkipped = true;
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
this.dispatchEvent(
|
|
125
|
-
new CustomEvent('request-scroll-to-bottom', { bubbles: true, composed: true }),
|
|
126
|
-
);
|
|
127
|
-
});
|
|
128
|
-
this._resizeObserver.observe(element);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
private renderComponent() {
|
|
132
|
-
if (!this.container || !this.componentName) return;
|
|
133
|
-
|
|
134
|
-
// Clear children without using innerHTML
|
|
135
|
-
this._resizeObserver?.disconnect();
|
|
136
|
-
this._resizeObserver = undefined;
|
|
137
|
-
this.container.replaceChildren();
|
|
138
|
-
|
|
139
|
-
if (!customElements.get(this.componentName)) {
|
|
140
|
-
logger.warn(
|
|
141
|
-
`Interactive component "${this.componentName}" is not registered in the customElements registry.`,
|
|
142
|
-
);
|
|
143
|
-
const errorDiv = document.createElement('div');
|
|
144
|
-
errorDiv.textContent = `Error: Component "${this.componentName}" is not available in this application.`;
|
|
145
|
-
this.container.appendChild(errorDiv);
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
try {
|
|
150
|
-
// Pierce the wrapper's shadow boundary to read host state: `isEngaged` (gates
|
|
151
|
-
// autofocus) and the driver-owned interaction context. Single-view model
|
|
152
|
-
// (GENC-1351): one assistant view always renders the live widget.
|
|
153
|
-
const root = this.getRootNode();
|
|
154
|
-
const host = root instanceof ShadowRoot ? (root.host as InteractionHost) : null;
|
|
155
|
-
|
|
156
|
-
const element = document.createElement(this.componentName) as AiChatWidget & HTMLElement;
|
|
157
|
-
|
|
158
|
-
// GENC-1390: the widget's whole input surface is the discriminated interaction
|
|
159
|
-
// descriptor + its stable id. Rebuilt on every (re)mount; the pending variant's
|
|
160
|
-
// `interactionContext` is the SAME driver-owned object across remounts, which is
|
|
161
|
-
// what lets a widget's live state survive the element teardown/recreate.
|
|
162
|
-
element.interactionId = this.interactionId;
|
|
163
|
-
element.interaction = this.buildInteraction(host);
|
|
164
|
-
|
|
165
|
-
// Handle completion from the inner component. Guard against re-emission if the
|
|
166
|
-
// interaction was already resolved (auto-resolve widgets fire on connectedCallback
|
|
167
|
-
// when rendering history).
|
|
168
|
-
element.addEventListener('interaction-completed', (e: CustomEvent) => {
|
|
169
|
-
e.stopPropagation();
|
|
170
|
-
if (this.resolved) return;
|
|
171
|
-
|
|
172
|
-
this.$emit('interaction-completed', {
|
|
173
|
-
interactionId: this.interactionId,
|
|
174
|
-
...e.detail,
|
|
175
|
-
});
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Spend the widget incurs AFTER it has resolved.
|
|
180
|
-
*
|
|
181
|
-
* Deliberately NOT guarded by `this.resolved`, which is the entire point: a widget that
|
|
182
|
-
* resolves early so the assistant stops waiting can stay interactive for minutes and do more
|
|
183
|
-
* paid work for the user. The completion listener above is guarded because a second
|
|
184
|
-
* RESOLUTION would be wrong; a second charge is simply true, and before this event that spend
|
|
185
|
-
* had no way to reach the total the user is shown.
|
|
186
|
-
*/
|
|
187
|
-
element.addEventListener('interaction-cost', (e: CustomEvent) => {
|
|
188
|
-
e.stopPropagation();
|
|
189
|
-
// Forwarded UNVALIDATED. `recordExternalCost` already rejects anything that is not a
|
|
190
|
-
// positive finite amount AND records why it dropped it; validating here as well made that
|
|
191
|
-
// trace unreachable through the event path — which is a widget's only way in — so a widget
|
|
192
|
-
// emitting NaN or a negative got silently nothing, anywhere. One validator, one trace.
|
|
193
|
-
this.$emit('interaction-cost', {
|
|
194
|
-
interactionId: this.interactionId,
|
|
195
|
-
costUsd: e.detail?.costUsd,
|
|
196
|
-
});
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
this.container.appendChild(element as unknown as Node);
|
|
200
|
-
this.observeWidgetResize(element as unknown as Element);
|
|
201
|
-
} catch (e) {
|
|
202
|
-
logger.error(`Failed to create interactive component: ${this.componentName}`, e);
|
|
203
|
-
const errorDiv = document.createElement('div');
|
|
204
|
-
errorDiv.textContent = `Error: Could not load component ${this.componentName}`;
|
|
205
|
-
this.container.appendChild(errorDiv);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Project the wrapper's current `data`/`resolved`/`interactionId` into the
|
|
211
|
-
* discriminated {@link AiChatInteraction} the widget consumes (GENC-1390).
|
|
212
|
-
*/
|
|
213
|
-
private buildInteraction(host: InteractionHost | null): AiChatInteraction {
|
|
214
|
-
if (this.resolved) {
|
|
215
|
-
return { status: 'resolved', data: this.data, result: this.resolved };
|
|
216
|
-
}
|
|
217
|
-
// Pending. The driver creates a live context for EVERY pending interaction BEFORE the widget
|
|
218
|
-
// mounts, returns the SAME instance on every remount, and drops it the instant the interaction
|
|
219
|
-
// resolves (see `ChatDriver.resolveInteraction`). So a MISSING context is not "pending, context
|
|
220
|
-
// lost" — it means this interaction is no longer live: it has resolved/evicted (the `resolved`
|
|
221
|
-
// binding above can lag a render behind that during a layout rebuild), or the widget was rendered
|
|
222
|
-
// outside the normal host flow (e.g. a unit test). In none of those cases may a `once`-guarded
|
|
223
|
-
// side effect run — re-firing one re-opened a just-closed side panel, whose layout rebuild
|
|
224
|
-
// re-mounted the widget, which re-opened it… a tab-locking loop (GENC-1390). Fall back to an
|
|
225
|
-
// INERT context (disposed up front, so `once` refuses and `state`/subscribers are inert) rather
|
|
226
|
-
// than a live throwaway, so the widget degrades safely instead of re-running its open-once effect.
|
|
227
|
-
let interactionContext = host?.getInteractionContext?.(this.interactionId);
|
|
228
|
-
if (!interactionContext) {
|
|
229
|
-
logger.warn(
|
|
230
|
-
`No live InteractionContext for interaction "${this.interactionId}"; using an inert context (once-guarded effects will not run).`,
|
|
231
|
-
);
|
|
232
|
-
const handle = createInteractionContext(this.interactionId);
|
|
233
|
-
handle.dispose();
|
|
234
|
-
interactionContext = handle.context;
|
|
235
|
-
}
|
|
236
|
-
return {
|
|
237
|
-
status: 'pending',
|
|
238
|
-
data: this.data,
|
|
239
|
-
shouldAutoFocus: !!host?.isEngaged,
|
|
240
|
-
interactionContext,
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
}
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { css, customElement, GenesisElement, html, observable } from '@genesislcap/web-core';
|
|
2
|
-
import DOMPurify from 'dompurify';
|
|
3
|
-
import { marked } from 'marked';
|
|
4
|
-
|
|
5
|
-
const styles = css`
|
|
6
|
-
:host {
|
|
7
|
-
display: block;
|
|
8
|
-
font-size: var(--type-ramp-base-font-size);
|
|
9
|
-
line-height: var(--type-ramp-base-line-height);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
p {
|
|
13
|
-
margin: 0 0 calc(var(--design-unit, 4) * 2px);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
p:last-child {
|
|
17
|
-
margin-bottom: 0;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
ul,
|
|
21
|
-
ol {
|
|
22
|
-
margin: 0 0 calc(var(--design-unit, 4) * 2px);
|
|
23
|
-
padding-left: calc(var(--design-unit, 4) * 5px);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
code {
|
|
27
|
-
background-color: var(--neutral-fill-rest);
|
|
28
|
-
padding: 2px 4px;
|
|
29
|
-
border-radius: calc(var(--design-unit, 4) * 0.5px);
|
|
30
|
-
font-family: monospace;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
pre {
|
|
34
|
-
background-color: var(--neutral-fill-rest);
|
|
35
|
-
padding: calc(var(--design-unit, 4) * 2px);
|
|
36
|
-
border-radius: calc(var(--design-unit, 4) * 1px);
|
|
37
|
-
overflow-x: auto;
|
|
38
|
-
border: 1px solid var(--neutral-stroke-rest);
|
|
39
|
-
margin: calc(var(--design-unit, 4) * 2px) 0;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
pre code {
|
|
43
|
-
background-color: transparent;
|
|
44
|
-
padding: 0;
|
|
45
|
-
border-radius: 0;
|
|
46
|
-
border: none;
|
|
47
|
-
font-family: inherit;
|
|
48
|
-
color: inherit;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
table {
|
|
52
|
-
display: block;
|
|
53
|
-
overflow-x: auto;
|
|
54
|
-
border-collapse: collapse;
|
|
55
|
-
max-width: 100%;
|
|
56
|
-
margin: calc(var(--design-unit, 4) * 2px) 0;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
th,
|
|
60
|
-
td {
|
|
61
|
-
border: 1px solid var(--neutral-stroke-rest);
|
|
62
|
-
padding: calc(var(--design-unit, 4) * 1px) calc(var(--design-unit, 4) * 2.5px);
|
|
63
|
-
white-space: nowrap;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
th {
|
|
67
|
-
background-color: var(--neutral-fill-stealth-rest);
|
|
68
|
-
font-weight: 600;
|
|
69
|
-
}
|
|
70
|
-
`;
|
|
71
|
-
|
|
72
|
-
@customElement({
|
|
73
|
-
name: 'ai-chat-markdown',
|
|
74
|
-
template: html<AiChatMarkdown>`
|
|
75
|
-
<div :innerHTML="${(x) => x.renderMarkdown()}"></div>
|
|
76
|
-
`,
|
|
77
|
-
styles,
|
|
78
|
-
})
|
|
79
|
-
export class AiChatMarkdown extends GenesisElement {
|
|
80
|
-
/** The markdown string to render. */
|
|
81
|
-
@observable content: string = '';
|
|
82
|
-
|
|
83
|
-
/** @internal */
|
|
84
|
-
renderMarkdown() {
|
|
85
|
-
if (!this.content) return '';
|
|
86
|
-
const rawHtml = marked.parse(this.content) as string;
|
|
87
|
-
return DOMPurify.sanitize(rawHtml);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './chat-markdown';
|
|
@@ -1,286 +0,0 @@
|
|
|
1
|
-
import { css, customElement, GenesisElement, html } from '@genesislcap/web-core';
|
|
2
|
-
import {
|
|
3
|
-
AI_COLOUR_AMBER,
|
|
4
|
-
AI_COLOUR_CYAN,
|
|
5
|
-
AI_COLOUR_PINK,
|
|
6
|
-
AI_COLOUR_VIOLET,
|
|
7
|
-
} from '../styles/ai-colours';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* SVG coordinate space the bands are drawn in. The bands stretch to fill the
|
|
11
|
-
* host box (the band SVGs use `preserveAspectRatio="none"`), so this aspect need
|
|
12
|
-
* not match the host's. `VIEW_H` is kept just tall enough to bound the crests,
|
|
13
|
-
* leaving minimal dead space above/below the line — otherwise the waves float in
|
|
14
|
-
* the vertical middle of the host and read as sitting too low beside the avatar.
|
|
15
|
-
*/
|
|
16
|
-
const VIEW_W = 225;
|
|
17
|
-
const VIEW_H = 50;
|
|
18
|
-
const CENTRE_Y = VIEW_H / 2;
|
|
19
|
-
/** Number of samples traced across each band. */
|
|
20
|
-
const POINTS = 90;
|
|
21
|
-
/** Fraction of the width over which thickness ramps from 0 up to full. */
|
|
22
|
-
const EDGE = 0.16;
|
|
23
|
-
/** Global multiplier on the harmonic drift speeds — raise to morph faster. */
|
|
24
|
-
const MORPH_SPEED = 3;
|
|
25
|
-
/** Global multiplier on the harmonic frequencies — raise for narrower peaks. */
|
|
26
|
-
const FREQ_SCALE = 2;
|
|
27
|
-
/** Milliseconds in one second. */
|
|
28
|
-
const MS_PER_SECOND = 1000;
|
|
29
|
-
/** Baseline frame rate (fps) the harmonic speeds are tuned against. */
|
|
30
|
-
const BASELINE_FPS = 60;
|
|
31
|
-
/**
|
|
32
|
-
* Frame duration (ms) at the baseline rate. The morph is driven by elapsed
|
|
33
|
-
* wall-clock time divided by this — not by the rAF callback count — so it
|
|
34
|
-
* advances at a constant real-world rate regardless of the display's refresh
|
|
35
|
-
* rate or dropped frames. At the baseline rate the resulting frame index
|
|
36
|
-
* matches the old per-callback counter, preserving the current look.
|
|
37
|
-
*/
|
|
38
|
-
const FRAME_MS = MS_PER_SECOND / BASELINE_FPS;
|
|
39
|
-
/**
|
|
40
|
-
* Half-thickness (viewBox units) each band keeps even between peaks, so the
|
|
41
|
-
* waves emerge from a continuous line rather than leaving gaps. The four bands
|
|
42
|
-
* overlap along this line and screen-blend to white.
|
|
43
|
-
*/
|
|
44
|
-
const LINE_THICKNESS = 1.2;
|
|
45
|
-
/**
|
|
46
|
-
* Amplitude boost added toward the middle of the width (a hump that is 0 at the
|
|
47
|
-
* ends, max in the centre). Makes peaks more likely to pop up centrally, while
|
|
48
|
-
* still allowing tall crests to appear nearer the edges.
|
|
49
|
-
*/
|
|
50
|
-
const CENTRE_BIAS = 4;
|
|
51
|
-
|
|
52
|
-
/** One sine component of a band's thickness profile. */
|
|
53
|
-
interface Harmonic {
|
|
54
|
-
/** Cycles across the band width. */
|
|
55
|
-
freq: number;
|
|
56
|
-
/** Contribution to the half-thickness, in viewBox units. */
|
|
57
|
-
amp: number;
|
|
58
|
-
/** Phase drift per frame (radians); differing speeds make the bumps morph. */
|
|
59
|
-
speed: number;
|
|
60
|
-
/** Starting phase offset. */
|
|
61
|
-
phase: number;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* One band's thickness profile: a baseline plus a few sine harmonics. Because
|
|
66
|
-
* the harmonics drift at different speeds, their sum continually changes shape —
|
|
67
|
-
* bumps grow, shrink, merge and split — and where the sum dips below zero the
|
|
68
|
-
* band pinches to nothing, so a wave may have a single bump one moment and
|
|
69
|
-
* several the next. A different harmonic mix per band keeps the colours from
|
|
70
|
-
* lining up.
|
|
71
|
-
*/
|
|
72
|
-
interface BandConfig {
|
|
73
|
-
/**
|
|
74
|
-
* Vertical offset the harmonics oscillate around. Negative values act as a
|
|
75
|
-
* threshold: only the taller crests clear zero, so the band shows fewer,
|
|
76
|
-
* narrower, isolated peaks (the small ripples between crests are clipped away).
|
|
77
|
-
*/
|
|
78
|
-
baseline: number;
|
|
79
|
-
harmonics: Harmonic[];
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const BANDS: BandConfig[] = [
|
|
83
|
-
{
|
|
84
|
-
baseline: -4,
|
|
85
|
-
harmonics: [
|
|
86
|
-
{ freq: 1.0, amp: 11, speed: 0.03, phase: 0.0 },
|
|
87
|
-
{ freq: 2.3, amp: 6, speed: -0.021, phase: 1.3 },
|
|
88
|
-
{ freq: 3.4, amp: 3, speed: 0.041, phase: 2.7 },
|
|
89
|
-
],
|
|
90
|
-
},
|
|
91
|
-
{
|
|
92
|
-
baseline: -4,
|
|
93
|
-
harmonics: [
|
|
94
|
-
{ freq: 1.3, amp: 10, speed: -0.026, phase: 0.8 },
|
|
95
|
-
{ freq: 2.0, amp: 7, speed: 0.034, phase: 2.1 },
|
|
96
|
-
{ freq: 3.1, amp: 3, speed: -0.045, phase: 0.4 },
|
|
97
|
-
],
|
|
98
|
-
},
|
|
99
|
-
{
|
|
100
|
-
baseline: -4,
|
|
101
|
-
harmonics: [
|
|
102
|
-
{ freq: 0.9, amp: 9, speed: 0.038, phase: 1.9 },
|
|
103
|
-
{ freq: 2.6, amp: 6, speed: 0.019, phase: 3.0 },
|
|
104
|
-
{ freq: 3.7, amp: 3, speed: -0.03, phase: 0.6 },
|
|
105
|
-
],
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
baseline: -4,
|
|
109
|
-
harmonics: [
|
|
110
|
-
{ freq: 1.1, amp: 8, speed: -0.034, phase: 2.4 },
|
|
111
|
-
{ freq: 2.4, amp: 5, speed: 0.027, phase: 0.2 },
|
|
112
|
-
{ freq: 3.2, amp: 3, speed: 0.048, phase: 1.5 },
|
|
113
|
-
],
|
|
114
|
-
},
|
|
115
|
-
];
|
|
116
|
-
|
|
117
|
-
// Each band is its own stacked <svg> LAYER. The white-on-overlap effect uses
|
|
118
|
-
// `mix-blend-mode: screen`, which composites reliably between HTML-level layers
|
|
119
|
-
// but NOT between sibling SVG <path>/<g> elements in many browsers — hence one
|
|
120
|
-
// svg per band rather than four paths in a single svg.
|
|
121
|
-
const bandLayersMarkup = BANDS.map(
|
|
122
|
-
(_b, i) =>
|
|
123
|
-
`<svg class="layer band-layer" viewBox="0 0 ${VIEW_W} ${VIEW_H}" preserveAspectRatio="none"><path class="band b${i + 1}" data-band="${i}" /></svg>`,
|
|
124
|
-
).join('');
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Animated "flowing waves" loading indicator — coloured organic waves (in the
|
|
128
|
-
* four brand colours) whose bumps swell, shrink, merge and split across a line,
|
|
129
|
-
* tapering to nothing at both ends. Where the colours overlap they blend
|
|
130
|
-
* additively toward white.
|
|
131
|
-
*
|
|
132
|
-
* Visual sibling of the dots loading indicator; interchangeable with the other
|
|
133
|
-
* loading styles for the "assistant is working" state.
|
|
134
|
-
*
|
|
135
|
-
* @example
|
|
136
|
-
* ```html
|
|
137
|
-
* <ai-flowing-waves-indicator></ai-flowing-waves-indicator>
|
|
138
|
-
* ```
|
|
139
|
-
*
|
|
140
|
-
* @beta
|
|
141
|
-
*/
|
|
142
|
-
@customElement({
|
|
143
|
-
name: 'ai-flowing-waves-indicator',
|
|
144
|
-
template: html<AiFlowingWavesIndicator>`
|
|
145
|
-
<div class="flow" role="img" aria-label="Assistant is working">${bandLayersMarkup}</div>
|
|
146
|
-
`,
|
|
147
|
-
styles: css`
|
|
148
|
-
:host {
|
|
149
|
-
display: inline-block;
|
|
150
|
-
width: 180px;
|
|
151
|
-
/* Height matches the message avatar (32px) so the wave line sits level
|
|
152
|
-
with it. The bands stretch to fill via preserveAspectRatio="none". */
|
|
153
|
-
height: 32px;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/* Transparent — sits directly on the chat surface (no backdrop). 'isolation:
|
|
157
|
-
isolate' scopes the band layers' 'screen' blend to each other, so where
|
|
158
|
-
colours overlap they add toward white, while the group still composites
|
|
159
|
-
normally onto the page (works on light and dark, no wash-out). */
|
|
160
|
-
.flow {
|
|
161
|
-
position: relative;
|
|
162
|
-
width: 100%;
|
|
163
|
-
height: 100%;
|
|
164
|
-
isolation: isolate;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
.layer {
|
|
168
|
-
position: absolute;
|
|
169
|
-
inset: 0;
|
|
170
|
-
width: 100%;
|
|
171
|
-
height: 100%;
|
|
172
|
-
overflow: hidden;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
.band-layer {
|
|
176
|
-
mix-blend-mode: screen;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
.band {
|
|
180
|
-
filter: drop-shadow(0 0 1.5px currentColor);
|
|
181
|
-
}
|
|
182
|
-
.band.b1 {
|
|
183
|
-
color: ${AI_COLOUR_AMBER};
|
|
184
|
-
fill: ${AI_COLOUR_AMBER};
|
|
185
|
-
}
|
|
186
|
-
.band.b2 {
|
|
187
|
-
color: ${AI_COLOUR_PINK};
|
|
188
|
-
fill: ${AI_COLOUR_PINK};
|
|
189
|
-
}
|
|
190
|
-
.band.b3 {
|
|
191
|
-
color: ${AI_COLOUR_CYAN};
|
|
192
|
-
fill: ${AI_COLOUR_CYAN};
|
|
193
|
-
}
|
|
194
|
-
.band.b4 {
|
|
195
|
-
color: ${AI_COLOUR_VIOLET};
|
|
196
|
-
fill: ${AI_COLOUR_VIOLET};
|
|
197
|
-
}
|
|
198
|
-
`,
|
|
199
|
-
})
|
|
200
|
-
export class AiFlowingWavesIndicator extends GenesisElement {
|
|
201
|
-
// A rAF loop morphs the band shapes: pure CSS can't continuously reshape the
|
|
202
|
-
// bumps (different sizes appearing at different times), and the per-frame sine
|
|
203
|
-
// sums are cheap. Same approach as the sine-tracing waves indicator. The morph
|
|
204
|
-
// is keyed to elapsed wall-clock time (see FRAME_MS), not the rAF callback
|
|
205
|
-
// count, so it holds a constant speed even when frames are dropped.
|
|
206
|
-
|
|
207
|
-
private startTime?: number;
|
|
208
|
-
private animFrame?: number;
|
|
209
|
-
private bandPaths?: SVGPathElement[];
|
|
210
|
-
|
|
211
|
-
connectedCallback() {
|
|
212
|
-
super.connectedCallback();
|
|
213
|
-
// Guard against a reconnect starting a second concurrent loop.
|
|
214
|
-
if (this.animFrame === undefined) {
|
|
215
|
-
this.tick();
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
disconnectedCallback() {
|
|
220
|
-
super.disconnectedCallback();
|
|
221
|
-
if (this.animFrame !== undefined) {
|
|
222
|
-
cancelAnimationFrame(this.animFrame);
|
|
223
|
-
this.animFrame = undefined;
|
|
224
|
-
}
|
|
225
|
-
// Reset the clock so a later reconnect restarts the morph cleanly at frame 0.
|
|
226
|
-
this.startTime = undefined;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
private tick(now?: number) {
|
|
230
|
-
// Stop ticking once disconnected (belt-and-braces alongside the cancel in
|
|
231
|
-
// disconnectedCallback) so no frames are scheduled while detached.
|
|
232
|
-
if (!this.isConnected) {
|
|
233
|
-
this.animFrame = undefined;
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
if (!this.bandPaths) {
|
|
237
|
-
const paths = this.shadowRoot?.querySelectorAll<SVGPathElement>('.band');
|
|
238
|
-
if (paths?.length) this.bandPaths = Array.from(paths);
|
|
239
|
-
}
|
|
240
|
-
// Anchor to the first real rAF timestamp; the initial synchronous call (no
|
|
241
|
-
// timestamp) renders frame 0. `frame` is then elapsed time in 60fps frames.
|
|
242
|
-
if (now !== undefined && this.startTime === undefined) this.startTime = now;
|
|
243
|
-
const frame =
|
|
244
|
-
now === undefined || this.startTime === undefined ? 0 : (now - this.startTime) / FRAME_MS;
|
|
245
|
-
this.bandPaths?.forEach((path, i) => {
|
|
246
|
-
const cfg = BANDS[i];
|
|
247
|
-
if (cfg) path.setAttribute('d', AiFlowingWavesIndicator.buildBand(cfg, frame));
|
|
248
|
-
});
|
|
249
|
-
this.animFrame = requestAnimationFrame((t) => this.tick(t));
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
/**
|
|
253
|
-
* Trace one band for the given frame: a baseline-plus-harmonics half-thickness
|
|
254
|
-
* profile, clamped at 0 (so the band pinches to nothing between bumps) and
|
|
255
|
-
* tapered to 0 at both ends.
|
|
256
|
-
*/
|
|
257
|
-
private static buildBand(cfg: BandConfig, frame: number): string {
|
|
258
|
-
const top: Array<[number, number]> = [];
|
|
259
|
-
const bottom: Array<[number, number]> = [];
|
|
260
|
-
for (let i = 0; i <= POINTS; i += 1) {
|
|
261
|
-
const u = i / POINTS;
|
|
262
|
-
const x = u * VIEW_W;
|
|
263
|
-
// Plateau edge taper: 0 at both ends, full across the middle.
|
|
264
|
-
const edge = Math.min(1, Math.min(u, 1 - u) / EDGE);
|
|
265
|
-
// Sum the drifting harmonics, then clamp so dips pinch the band to nothing.
|
|
266
|
-
let wave = 0;
|
|
267
|
-
for (const h of cfg.harmonics) {
|
|
268
|
-
wave +=
|
|
269
|
-
h.amp *
|
|
270
|
-
Math.sin(u * Math.PI * 2 * h.freq * FREQ_SCALE + frame * h.speed * MORPH_SPEED + h.phase);
|
|
271
|
-
}
|
|
272
|
-
// Hump that favours the middle (0 at the ends, 1 at the centre).
|
|
273
|
-
const centre = Math.sin(Math.PI * u);
|
|
274
|
-
const env = edge * (LINE_THICKNESS + Math.max(0, cfg.baseline + wave + CENTRE_BIAS * centre));
|
|
275
|
-
top.push([x, CENTRE_Y - env]);
|
|
276
|
-
bottom.push([x, CENTRE_Y + env]);
|
|
277
|
-
}
|
|
278
|
-
let d = `M${top[0][0].toFixed(1)} ${top[0][1].toFixed(1)}`;
|
|
279
|
-
for (let i = 1; i < top.length; i += 1)
|
|
280
|
-
d += ` L${top[i][0].toFixed(1)} ${top[i][1].toFixed(1)}`;
|
|
281
|
-
for (let i = bottom.length - 1; i >= 0; i -= 1) {
|
|
282
|
-
d += ` L${bottom[i][0].toFixed(1)} ${bottom[i][1].toFixed(1)}`;
|
|
283
|
-
}
|
|
284
|
-
return `${d} Z`;
|
|
285
|
-
}
|
|
286
|
-
}
|