@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,186 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
attr,
|
|
3
|
-
customElement,
|
|
4
|
-
GenesisElement,
|
|
5
|
-
observable,
|
|
6
|
-
html,
|
|
7
|
-
css,
|
|
8
|
-
repeat,
|
|
9
|
-
when,
|
|
10
|
-
} from '@genesislcap/web-core';
|
|
11
|
-
import type { SuggestionsState } from '../main/main.types';
|
|
12
|
-
|
|
13
|
-
type LoadedState = { status: 'loaded'; suggestions: string[] };
|
|
14
|
-
|
|
15
|
-
const PILL_ANIMATION_STAGGER_MS = 70;
|
|
16
|
-
|
|
17
|
-
const template = html<ChatSuggestions>`
|
|
18
|
-
${when(
|
|
19
|
-
(x) => x.state.status === 'loading',
|
|
20
|
-
html<ChatSuggestions>`
|
|
21
|
-
<div class="suggestions-container">
|
|
22
|
-
${repeat(
|
|
23
|
-
() => Array.from({ length: 3 }),
|
|
24
|
-
html`
|
|
25
|
-
<div class="suggestion-placeholder"></div>
|
|
26
|
-
`,
|
|
27
|
-
)}
|
|
28
|
-
</div>
|
|
29
|
-
`,
|
|
30
|
-
)}
|
|
31
|
-
${when(
|
|
32
|
-
(x) => x.state.status === 'loaded',
|
|
33
|
-
html<ChatSuggestions>`
|
|
34
|
-
<div class="suggestions-container">
|
|
35
|
-
${repeat(
|
|
36
|
-
(x) => (x.state as LoadedState).suggestions,
|
|
37
|
-
html<string, ChatSuggestions>`
|
|
38
|
-
<button
|
|
39
|
-
class="suggestion-pill"
|
|
40
|
-
style="animation-delay: ${(_, c) => c.index * PILL_ANIMATION_STAGGER_MS}ms"
|
|
41
|
-
@click="${(suggestion, c) => c.parent.handleSuggestionClick(suggestion)}"
|
|
42
|
-
>
|
|
43
|
-
${(suggestion) => suggestion}
|
|
44
|
-
</button>
|
|
45
|
-
`,
|
|
46
|
-
)}
|
|
47
|
-
</div>
|
|
48
|
-
`,
|
|
49
|
-
)}
|
|
50
|
-
`;
|
|
51
|
-
|
|
52
|
-
const styles = css`
|
|
53
|
-
:host {
|
|
54
|
-
/* Collapsed unless there is something to render (see hasContent), so the empty
|
|
55
|
-
host never occupies space or shows through a consumer's border as a stray
|
|
56
|
-
line. */
|
|
57
|
-
display: none;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
:host([has-content]) {
|
|
61
|
-
display: block;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
.suggestions-container {
|
|
65
|
-
display: flex;
|
|
66
|
-
flex-wrap: wrap;
|
|
67
|
-
align-items: center;
|
|
68
|
-
gap: 6px;
|
|
69
|
-
padding: calc(var(--design-unit) * 2px) calc(var(--design-unit) * 3px);
|
|
70
|
-
background-color: var(--neutral-layer-2);
|
|
71
|
-
animation: suggestions-appear 0.2s ease-out both;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
@keyframes suggestions-appear {
|
|
75
|
-
from {
|
|
76
|
-
opacity: 0;
|
|
77
|
-
transform: translateY(6px);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
to {
|
|
81
|
-
opacity: 1;
|
|
82
|
-
transform: translateY(0);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
.suggestion-pill {
|
|
87
|
-
display: inline-flex;
|
|
88
|
-
align-items: center;
|
|
89
|
-
padding: 5px 14px;
|
|
90
|
-
background: color-mix(in srgb, var(--accent-fill-rest) 10%, var(--neutral-layer-3));
|
|
91
|
-
color: var(--neutral-foreground-rest);
|
|
92
|
-
border: 1px solid color-mix(in srgb, var(--accent-fill-rest) 25%, var(--neutral-stroke-rest));
|
|
93
|
-
border-radius: 20px;
|
|
94
|
-
font-family: var(--body-font);
|
|
95
|
-
font-size: 0.85em;
|
|
96
|
-
cursor: pointer;
|
|
97
|
-
transition:
|
|
98
|
-
background 0.15s ease,
|
|
99
|
-
border-color 0.15s ease,
|
|
100
|
-
transform 0.15s ease,
|
|
101
|
-
box-shadow 0.15s ease;
|
|
102
|
-
animation: pill-appear 0.3s ease-out both;
|
|
103
|
-
max-width: 100%;
|
|
104
|
-
white-space: normal;
|
|
105
|
-
text-align: left;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
.suggestion-pill:hover {
|
|
109
|
-
background: color-mix(in srgb, var(--accent-fill-rest) 20%, var(--neutral-layer-3));
|
|
110
|
-
border-color: color-mix(in srgb, var(--accent-fill-rest) 50%, var(--neutral-stroke-rest));
|
|
111
|
-
transform: translateY(-1px);
|
|
112
|
-
box-shadow: 0 2px 8px color-mix(in srgb, var(--accent-fill-rest) 20%, transparent);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
.suggestion-pill:active {
|
|
116
|
-
transform: translateY(0);
|
|
117
|
-
box-shadow: none;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
@keyframes pill-appear {
|
|
121
|
-
from {
|
|
122
|
-
opacity: 0;
|
|
123
|
-
transform: scale(0.88) translateY(4px);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
to {
|
|
127
|
-
opacity: 1;
|
|
128
|
-
transform: scale(1) translateY(0);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
.suggestion-placeholder {
|
|
133
|
-
height: 30px;
|
|
134
|
-
width: 110px;
|
|
135
|
-
background: var(--neutral-layer-3);
|
|
136
|
-
border-radius: 20px;
|
|
137
|
-
animation: placeholder-pulse 1.6s ease-in-out infinite;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
.suggestion-placeholder:nth-child(2) {
|
|
141
|
-
width: 150px;
|
|
142
|
-
animation-delay: 0.25s;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
.suggestion-placeholder:nth-child(3) {
|
|
146
|
-
width: 130px;
|
|
147
|
-
animation-delay: 0.5s;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
@keyframes placeholder-pulse {
|
|
151
|
-
0%,
|
|
152
|
-
100% {
|
|
153
|
-
opacity: 0.35;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
50% {
|
|
157
|
-
opacity: 0.65;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
`;
|
|
161
|
-
|
|
162
|
-
@customElement({
|
|
163
|
-
name: 'chat-suggestions',
|
|
164
|
-
template,
|
|
165
|
-
styles,
|
|
166
|
-
})
|
|
167
|
-
export class ChatSuggestions extends GenesisElement {
|
|
168
|
-
@observable
|
|
169
|
-
state: SuggestionsState = { status: 'idle' };
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Reflected to a `has-content` attribute so the host can collapse when there is
|
|
173
|
-
* nothing to render. True only while loading or when loaded with at least one
|
|
174
|
-
* suggestion; idle, errored, and empty-loaded states render nothing.
|
|
175
|
-
*/
|
|
176
|
-
@attr({ attribute: 'has-content', mode: 'boolean' }) hasContent = false;
|
|
177
|
-
|
|
178
|
-
stateChanged(): void {
|
|
179
|
-
const s = this.state;
|
|
180
|
-
this.hasContent = s.status === 'loading' || (s.status === 'loaded' && s.suggestions.length > 0);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
handleSuggestionClick(suggestion: string): void {
|
|
184
|
-
this.$emit('suggestion-clicked', suggestion);
|
|
185
|
-
}
|
|
186
|
-
}
|
package/src/tags/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import type { InteractionResult } from '@genesislcap/foundation-ai';
|
|
2
|
-
import type { InteractionContext } from './interaction-context';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* The two mutually-exclusive states an interaction widget is ever in (GENC-1390).
|
|
6
|
-
* Modelling them as a discriminated union makes illegal combinations
|
|
7
|
-
* unrepresentable — a resolved widget cannot carry a live `interactionContext` or
|
|
8
|
-
* a `shouldAutoFocus`, and a pending widget cannot be read for a `result`.
|
|
9
|
-
*
|
|
10
|
-
* The host sets the widget's `interaction` on mount and REPLACES it (pending →
|
|
11
|
-
* resolved) on completion.
|
|
12
|
-
*
|
|
13
|
-
* @beta
|
|
14
|
-
*/
|
|
15
|
-
export type AiChatInteraction<TData = unknown, TResult = unknown> =
|
|
16
|
-
| {
|
|
17
|
-
status: 'pending';
|
|
18
|
-
/** Immutable inputs for the interaction. */
|
|
19
|
-
data: TData;
|
|
20
|
-
/**
|
|
21
|
-
* Grab focus on mount? Meaningful ONLY while pending — a resolved widget is a
|
|
22
|
-
* history re-render and must never steal focus.
|
|
23
|
-
*/
|
|
24
|
-
shouldAutoFocus: boolean;
|
|
25
|
-
/**
|
|
26
|
-
* Live, remount-surviving scratch for this interaction. Present ONLY while
|
|
27
|
-
* pending — the host disposes it on resolve. See {@link InteractionContext}.
|
|
28
|
-
*/
|
|
29
|
-
interactionContext: InteractionContext;
|
|
30
|
-
}
|
|
31
|
-
| {
|
|
32
|
-
status: 'resolved';
|
|
33
|
-
/** Same inputs — the resolved view still reads them. */
|
|
34
|
-
data: TData;
|
|
35
|
-
/** The interaction outcome. */
|
|
36
|
-
result: InteractionResult<TResult>;
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Interface that AI inline interaction components implement. The
|
|
41
|
-
* `AiChatInteractionWrapper` sets `interactionId` and `interaction` on the
|
|
42
|
-
* rendered element on every (re)mount, and replaces `interaction` (pending →
|
|
43
|
-
* resolved) when the interaction completes.
|
|
44
|
-
*
|
|
45
|
-
* @remarks
|
|
46
|
-
* GENC-1390 replaced the previous flat `data` / `resolved` / `shouldAutoFocus`
|
|
47
|
-
* surface with the discriminated {@link AiChatInteraction}. This is a breaking
|
|
48
|
-
* change on this `@beta` interface — widgets read `this.interaction` (narrowing on
|
|
49
|
-
* `status`) instead of the removed flat fields.
|
|
50
|
-
*
|
|
51
|
-
* @remarks
|
|
52
|
-
* A widget talks back by dispatching two events, both caught by the wrapper:
|
|
53
|
-
* `interaction-completed` resolves the interaction (once — a second is ignored), and
|
|
54
|
-
* `interaction-cost` reports a positive USD delta for paid work done AFTER resolving,
|
|
55
|
-
* which repeated calls sum. The second exists for widgets that resolve early so the
|
|
56
|
-
* assistant stops waiting, then keep working for the user; without it that spend is
|
|
57
|
-
* billed but missing from the total the user sees.
|
|
58
|
-
*
|
|
59
|
-
* @beta
|
|
60
|
-
*/
|
|
61
|
-
export interface AiChatWidget<TData = unknown, TResult = unknown> {
|
|
62
|
-
/** Stable per-interaction UUID; constant across remounts, present in both states. */
|
|
63
|
-
interactionId: string;
|
|
64
|
-
|
|
65
|
-
/** The current interaction state (pending or resolved). */
|
|
66
|
-
interaction: AiChatInteraction<TData, TResult>;
|
|
67
|
-
}
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A live, per-interaction scratch space owned by the chat host (keyed by
|
|
3
|
-
* `interactionId`) and handed to a widget on every (re)mount.
|
|
4
|
-
*
|
|
5
|
-
* WHY: an interaction widget element is destroyed+recreated on layout reflow
|
|
6
|
-
* (GoldenLayout rebuilds/reparents on panel add/remove AND on drag), on
|
|
7
|
-
* dock/undock, and on transcript re-render (the keyless `repeat`). The element
|
|
8
|
-
* cannot hold anything live across that; this object can, because the host keeps
|
|
9
|
-
* it alive for the interaction's lifetime, independent of the element. (GENC-1390)
|
|
10
|
-
*
|
|
11
|
-
* LIVE-ONLY — never persisted. A reload / project switch / session restore wipes
|
|
12
|
-
* it; the interaction is then rebuilt purely from the serialisable `data` +
|
|
13
|
-
* `result`. Never store here anything that must survive a reload: durable inputs
|
|
14
|
-
* belong in the interaction's `data`, the outcome in its resolved payload.
|
|
15
|
-
*
|
|
16
|
-
* LIFETIME — created lazily when the interaction is requested, reused for every
|
|
17
|
-
* remount of that same `interactionId`. Torn down (its `onDispose` callbacks fire
|
|
18
|
-
* once) when the interaction RESOLVES or is evicted — NOT when the element
|
|
19
|
-
* unmounts. That inversion is the point: a transient unmount (drag, re-render)
|
|
20
|
-
* must leave context-owned resources (sockets, timers) untouched; only completion
|
|
21
|
-
* ends them.
|
|
22
|
-
*
|
|
23
|
-
* NOTE: `subscribe`/`notify` are a plain observer list, not a reactivity engine
|
|
24
|
-
* and not a FAST dependency. `notify()` renders nothing — it signals the
|
|
25
|
-
* currently-mounted element "state changed, re-read", and the element updates its
|
|
26
|
-
* own observables and re-renders with whatever primitives it has.
|
|
27
|
-
*
|
|
28
|
-
* @beta
|
|
29
|
-
*/
|
|
30
|
-
export interface InteractionContext<TState extends object = Record<string, unknown>> {
|
|
31
|
-
/** Stable id of the interaction this context belongs to. */
|
|
32
|
-
readonly interactionId: string;
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Durable, remount-surviving source of truth for the interaction — a socket
|
|
36
|
-
* handle, the current phase, a half-typed draft. A plain object, deliberately
|
|
37
|
-
* NOT reactive: a widget renders from its own observables and reads/writes this
|
|
38
|
-
* on mount and on change so the next remount can recover.
|
|
39
|
-
*/
|
|
40
|
-
readonly state: TState;
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Run `factory` at most once for this interaction's lifetime and memoise the
|
|
44
|
-
* result; every later call with the same `key` (including after a remount)
|
|
45
|
-
* returns the stored value without re-running. Covers one-shot side effects and
|
|
46
|
-
* create-once live resources alike:
|
|
47
|
-
*
|
|
48
|
-
* ```ts
|
|
49
|
-
* ctx.once('open-panel', () => this.dispatchEvent(openPanelEvent)); // fires once, ever
|
|
50
|
-
* const ws = ctx.once('ws', () => new TestDataWebSocketService()); // same instance each remount
|
|
51
|
-
* ```
|
|
52
|
-
*
|
|
53
|
-
* Returns `null` once the interaction has ended (resolve/timeout/evict): the
|
|
54
|
-
* factory is NOT run, because a resource created on a disposed context would
|
|
55
|
-
* never be torn down (its `onDispose` has already fired). A widget that opens a
|
|
56
|
-
* live resource here must guard on the resolved state and stop touching `once`
|
|
57
|
-
* when the interaction completes — a stray late call (e.g. an async callback
|
|
58
|
-
* landing after a `timeoutMs` timeout) gets `null` rather than a leaked resource.
|
|
59
|
-
*/
|
|
60
|
-
once<T>(key: string, factory: () => T): T | null;
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Signal that `state` changed so subscribers re-read. NOT a renderer —
|
|
64
|
-
* just a change ping; the subscribing element updates its own observables.
|
|
65
|
-
*/
|
|
66
|
-
notify(): void;
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Observe `state` changes. Call in `connectedCallback`; call the returned
|
|
70
|
-
* unsubscribe in `disconnectedCallback`.
|
|
71
|
-
*/
|
|
72
|
-
subscribe(listener: () => void): () => void;
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Register a teardown run exactly once when the interaction ends (resolve or
|
|
76
|
-
* evict) — the correct home for closing sockets/timers created via `once`.
|
|
77
|
-
* Decoupled from the element's `disconnectedCallback`, which fires on every
|
|
78
|
-
* transient unmount and must NOT tear these down. Registering after the context
|
|
79
|
-
* has already been disposed runs `fn` immediately.
|
|
80
|
-
*/
|
|
81
|
-
onDispose(fn: () => void): void;
|
|
82
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Drives the open/close lifecycle of a slide-out panel inside a component's
|
|
3
|
-
* shadow root, with a tracked close timer instead of an `animationend`
|
|
4
|
-
* listener so the panel cannot wedge open if it's unmounted mid-animation
|
|
5
|
-
* (pop-in/pop-out, dock change, host disconnect).
|
|
6
|
-
*
|
|
7
|
-
* @internal
|
|
8
|
-
*/
|
|
9
|
-
export class AnimatedPanelToggle {
|
|
10
|
-
private timer?: ReturnType<typeof setTimeout>;
|
|
11
|
-
|
|
12
|
-
constructor(
|
|
13
|
-
private readonly host: HTMLElement,
|
|
14
|
-
private readonly selector: string,
|
|
15
|
-
private readonly closeDurationMs: number,
|
|
16
|
-
private readonly getOpen: () => boolean,
|
|
17
|
-
private readonly setOpen: (v: boolean) => void,
|
|
18
|
-
) {}
|
|
19
|
-
|
|
20
|
-
private queryPanel(): HTMLElement | null {
|
|
21
|
-
return this.host.shadowRoot?.querySelector(this.selector) as HTMLElement | null;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
toggle(): void {
|
|
25
|
-
if (this.getOpen()) {
|
|
26
|
-
// Capture the panel reference now so the timer callback strips
|
|
27
|
-
// `closing` from the same node, regardless of any later remounts.
|
|
28
|
-
// Leftover `closing` is what breaks the next open: with
|
|
29
|
-
// `animation-fill-mode: forwards` the slide-out keyframe pins the panel
|
|
30
|
-
// to opacity 0, so a remount looks like a no-op.
|
|
31
|
-
const panel = this.queryPanel();
|
|
32
|
-
panel?.classList.add('closing');
|
|
33
|
-
if (this.timer != null) clearTimeout(this.timer);
|
|
34
|
-
this.timer = setTimeout(() => {
|
|
35
|
-
this.timer = undefined;
|
|
36
|
-
panel?.classList.remove('closing');
|
|
37
|
-
this.setOpen(false);
|
|
38
|
-
}, this.closeDurationMs);
|
|
39
|
-
} else {
|
|
40
|
-
// Re-opened mid-close: cancel the pending flip and strip the closing
|
|
41
|
-
// class so the panel snaps back to the open state.
|
|
42
|
-
if (this.timer != null) {
|
|
43
|
-
clearTimeout(this.timer);
|
|
44
|
-
this.timer = undefined;
|
|
45
|
-
this.queryPanel()?.classList.remove('closing');
|
|
46
|
-
}
|
|
47
|
-
this.setOpen(true);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Synchronously finalise an in-flight close — call from
|
|
53
|
-
* `disconnectedCallback` so the closed state lands in the store before any
|
|
54
|
-
* session ref is cleared.
|
|
55
|
-
*/
|
|
56
|
-
finalize(): void {
|
|
57
|
-
if (this.timer == null) return;
|
|
58
|
-
clearTimeout(this.timer);
|
|
59
|
-
this.timer = undefined;
|
|
60
|
-
this.setOpen(false);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
|
|
2
|
-
import type { AiAssistantAnimation } from '../main/main.types';
|
|
3
|
-
import { resolveExclusiveLoadingStyle } from './animation-exclusivity';
|
|
4
|
-
|
|
5
|
-
const suite = createLogicSuite('resolveExclusiveLoadingStyle');
|
|
6
|
-
|
|
7
|
-
suite('leaves a selection with only the dots loading style untouched', () => {
|
|
8
|
-
const next: AiAssistantAnimation[] = ['loading', 'halo'];
|
|
9
|
-
assert.equal(resolveExclusiveLoadingStyle(next, ['halo']), ['loading', 'halo']);
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
suite('leaves a selection with only the waves loading style untouched', () => {
|
|
13
|
-
const next: AiAssistantAnimation[] = ['waves', 'halo'];
|
|
14
|
-
assert.equal(resolveExclusiveLoadingStyle(next, ['halo']), ['waves', 'halo']);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
suite('leaves a selection with no loading style untouched', () => {
|
|
18
|
-
assert.equal(resolveExclusiveLoadingStyle(['halo'], []), ['halo']);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
suite('drops dots when waves was just added on top of dots', () => {
|
|
22
|
-
// Previously dots was on; user ticks waves → waves wins, dots removed.
|
|
23
|
-
const result = resolveExclusiveLoadingStyle(['loading', 'waves', 'halo'], ['loading', 'halo']);
|
|
24
|
-
assert.equal(result, ['waves', 'halo']);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
suite('drops waves when dots was just added on top of waves', () => {
|
|
28
|
-
// Previously waves was on; user ticks dots → dots wins, waves removed.
|
|
29
|
-
const result = resolveExclusiveLoadingStyle(['loading', 'waves', 'halo'], ['waves', 'halo']);
|
|
30
|
-
assert.equal(result, ['loading', 'halo']);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
suite('preserves the order of the surviving selection', () => {
|
|
34
|
-
const result = resolveExclusiveLoadingStyle(['halo', 'loading', 'waves'], ['halo', 'loading']);
|
|
35
|
-
// waves was just added → loading dropped; halo and waves keep their order.
|
|
36
|
-
assert.equal(result, ['halo', 'waves']);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
suite('falls back to dots when both are present with no prior state (e.g. bad config)', () => {
|
|
40
|
-
const result = resolveExclusiveLoadingStyle(['loading', 'waves', 'halo'], []);
|
|
41
|
-
assert.equal(result, ['loading', 'halo']);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
suite('falls back to dots when both were already present (neither newly added)', () => {
|
|
45
|
-
const result = resolveExclusiveLoadingStyle(['loading', 'waves'], ['loading', 'waves', 'halo']);
|
|
46
|
-
assert.equal(result, ['loading']);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
suite('handles an empty selection', () => {
|
|
50
|
-
assert.equal(resolveExclusiveLoadingStyle([], ['loading']), []);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
suite('switching between any two loading styles keeps only the newly added one', () => {
|
|
54
|
-
// flowingWaves was on; user picks plasma → plasma wins, flowingWaves dropped.
|
|
55
|
-
assert.equal(
|
|
56
|
-
resolveExclusiveLoadingStyle(['flowingWaves', 'plasma', 'halo'], ['flowingWaves', 'halo']),
|
|
57
|
-
['plasma', 'halo'],
|
|
58
|
-
);
|
|
59
|
-
// plasma was on; user picks the dots → dots win, plasma dropped.
|
|
60
|
-
assert.equal(resolveExclusiveLoadingStyle(['plasma', 'loading'], ['plasma']), ['loading']);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
suite('drops all but the newly added style when several are somehow selected', () => {
|
|
64
|
-
const result = resolveExclusiveLoadingStyle(
|
|
65
|
-
['loading', 'waves', 'flowingWaves', 'plasma'],
|
|
66
|
-
['loading', 'waves', 'plasma'],
|
|
67
|
-
);
|
|
68
|
-
// flowingWaves is the only one absent from `previous`, so it wins.
|
|
69
|
-
assert.equal(result, ['flowingWaves']);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
suite.run();
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import type { AiAssistantAnimation } from '../main/main.types';
|
|
2
|
-
import { LOADING_STYLE_ANIMATIONS } from '../main/main.types';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Enforces that at most one loading-indicator style (dots vs. waves) is enabled
|
|
6
|
-
* at a time. The two are alternative presentations of the same "assistant is
|
|
7
|
-
* working" state, so selecting one must deselect the other.
|
|
8
|
-
*
|
|
9
|
-
* The settings control is a multiselect (checkboxes), which permits selecting
|
|
10
|
-
* both; this resolver makes the loading-style group behave like a radio group
|
|
11
|
-
* by dropping the previously-selected style whenever a new one is added.
|
|
12
|
-
*
|
|
13
|
-
* @param next - The freshly-selected animation list (e.g. emitted by the
|
|
14
|
-
* multiselect, or read from consumer config).
|
|
15
|
-
* @param previous - The animation list in effect before this change. Used to
|
|
16
|
-
* work out which loading style was just added so the other can be dropped.
|
|
17
|
-
* Pass `[]` when resolving an initial/config value with no prior state.
|
|
18
|
-
* @returns `next` with at most one loading style retained. When both are
|
|
19
|
-
* present and neither is newly added (e.g. a misconfigured `enabled` list),
|
|
20
|
-
* the first entry of `LOADING_STYLE_ANIMATIONS` (dots) wins.
|
|
21
|
-
*
|
|
22
|
-
* @internal
|
|
23
|
-
*/
|
|
24
|
-
export function resolveExclusiveLoadingStyle(
|
|
25
|
-
next: AiAssistantAnimation[],
|
|
26
|
-
previous: AiAssistantAnimation[] = [],
|
|
27
|
-
): AiAssistantAnimation[] {
|
|
28
|
-
const selectedStyles = LOADING_STYLE_ANIMATIONS.filter((style) => next.includes(style));
|
|
29
|
-
if (selectedStyles.length <= 1) {
|
|
30
|
-
return next;
|
|
31
|
-
}
|
|
32
|
-
// Both styles selected — keep whichever was just added (absent from
|
|
33
|
-
// `previous`); fall back to the first declared style on ambiguity.
|
|
34
|
-
const justAdded = selectedStyles.find((style) => !previous.includes(style)) ?? selectedStyles[0];
|
|
35
|
-
return next.filter(
|
|
36
|
-
(animation) =>
|
|
37
|
-
animation === justAdded ||
|
|
38
|
-
!(LOADING_STYLE_ANIMATIONS as readonly AiAssistantAnimation[]).includes(animation),
|
|
39
|
-
);
|
|
40
|
-
}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import type { AggregateUsage } from '@genesislcap/foundation-ai';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Per-session banked baselines for the cost ledger (GENC-1475) — the spend a project had
|
|
5
|
-
* accrued *before* the transcript currently in memory. Module-level and keyed by the session
|
|
6
|
-
* `stateKey`, exactly like the diagnostics cursors and the meta-event buffer.
|
|
7
|
-
*
|
|
8
|
-
* The lifetime is the point of this module. A baseline is only meaningful paired with the
|
|
9
|
-
* transcript it complements: `row.usage = baseline + whatever that transcript proves`. The
|
|
10
|
-
* transcript lives in the per-key session store, so the baseline must live and die with that
|
|
11
|
-
* store — which means module state keyed the same way, NOT element state.
|
|
12
|
-
*
|
|
13
|
-
* Held on the element it silently double-counted. Element instances are created and destroyed
|
|
14
|
-
* many times inside one session (the assistant is remounted as the host re-renders its layout,
|
|
15
|
-
* and a popped-out bubble and a docked panel can be wired to the same session at once), and each
|
|
16
|
-
* new instance re-derived the baseline from the ledger rows. By then those rows contain the
|
|
17
|
-
* instance-before-last's own upserts, whose `usage` already includes the live transcript — so
|
|
18
|
-
* where the transcript is NOT restored on load (`resolveBankedUsage`'s "bank the whole prior
|
|
19
|
-
* total" branch, i.e. persistence off) the live session was banked as its own baseline. Lifetime
|
|
20
|
-
* then jumped by the whole session-to-date on every remount while the live figure kept climbing
|
|
21
|
-
* normally, which is the "the two totals went up by different amounts" report.
|
|
22
|
-
*
|
|
23
|
-
* Module state dies with the JS realm, so a real page reload starts clean — exactly right, since
|
|
24
|
-
* that is also when the transcript starts over.
|
|
25
|
-
*
|
|
26
|
-
* @internal
|
|
27
|
-
*/
|
|
28
|
-
const baselinesByKey = new Map<string, AggregateUsage>();
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* The baseline resolved for `key`, or `undefined` when this session has not resolved one yet.
|
|
32
|
-
*
|
|
33
|
-
* `undefined` rather than an empty usage: "nothing banked" is a legitimate resolved value (a
|
|
34
|
-
* project's first ever session), and it must not be mistaken for "not resolved", or every write
|
|
35
|
-
* would re-derive and hit the double-count above.
|
|
36
|
-
*
|
|
37
|
-
* Returns a copy — see `setBankedBaseline` for why both ends of this map are cloned.
|
|
38
|
-
*
|
|
39
|
-
* @internal
|
|
40
|
-
*/
|
|
41
|
-
export function getBankedBaseline(key: string): AggregateUsage | undefined {
|
|
42
|
-
const usage = baselinesByKey.get(key);
|
|
43
|
-
return usage ? { ...usage } : undefined;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Record the baseline for `key`, replacing any previous one.
|
|
48
|
-
*
|
|
49
|
-
* Stores a copy, and `getBankedBaseline` hands back a copy, so nothing outside this module can
|
|
50
|
-
* reach the object the map holds. Both ends have to clone, because a baseline is aliased at both:
|
|
51
|
-
* `resolveBankedUsage` returns a *reference into a ledger row* (`row.banked` or `row.usage`),
|
|
52
|
-
* which is what gets stored here, and the caller then assigns what it reads back onto the NEXT
|
|
53
|
-
* row's `banked` field. Uncloned, one object is reachable from page-global state, two rows, and —
|
|
54
|
-
* since the row list is public and gets handed to a host's `saveCostHistory` — from host code.
|
|
55
|
-
* Whoever mutated it would silently rewrite this project's baseline for the rest of the page.
|
|
56
|
-
*
|
|
57
|
-
* A shallow spread is a complete copy: `AggregateUsage` is five flat numbers, no nested state.
|
|
58
|
-
*
|
|
59
|
-
* @internal
|
|
60
|
-
*/
|
|
61
|
-
export function setBankedBaseline(key: string, usage: AggregateUsage): void {
|
|
62
|
-
baselinesByKey.set(key, { ...usage });
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Drop the baseline for `key`, so the next write re-derives it from the ledger rows.
|
|
67
|
-
*
|
|
68
|
-
* For when the pairing is broken deliberately at the *row* end — the row this baseline came from
|
|
69
|
-
* has been deleted, or the whole ledger cleared — and for a session teardown, where the store
|
|
70
|
-
* (and with it the transcript) is disposed and a later return re-restores from scratch.
|
|
71
|
-
*
|
|
72
|
-
* NOT for a mid-session reload of the rows: those rows already include this session's own
|
|
73
|
-
* upserts, so re-deriving from them folds the live transcript into its own baseline.
|
|
74
|
-
*
|
|
75
|
-
* @internal
|
|
76
|
-
*/
|
|
77
|
-
export function deleteBankedBaseline(key: string): void {
|
|
78
|
-
baselinesByKey.delete(key);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Forget every baseline — the page-load boundary, which production gets for free from module
|
|
83
|
-
* state dying with the realm. Exists so tests can model separate page loads (and so one test's
|
|
84
|
-
* session key cannot leak a baseline into the next).
|
|
85
|
-
*
|
|
86
|
-
* @internal
|
|
87
|
-
*/
|
|
88
|
-
export function resetBankedBaselines(): void {
|
|
89
|
-
baselinesByKey.clear();
|
|
90
|
-
}
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import type { AIProviderRegistryStatusEntry, ChatMessage } from '@genesislcap/foundation-ai';
|
|
2
|
-
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
|
|
3
|
-
import { collectSessionModels } from './collect-session-models';
|
|
4
|
-
|
|
5
|
-
const assistantMsg = (overrides: Partial<ChatMessage> = {}): ChatMessage => ({
|
|
6
|
-
role: 'assistant',
|
|
7
|
-
content: '',
|
|
8
|
-
...overrides,
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
const statusEntry = (
|
|
12
|
-
name: string,
|
|
13
|
-
model: string,
|
|
14
|
-
provider: AIProviderRegistryStatusEntry['status'] extends infer S
|
|
15
|
-
? S extends { provider: infer P }
|
|
16
|
-
? P
|
|
17
|
-
: never
|
|
18
|
-
: never,
|
|
19
|
-
): AIProviderRegistryStatusEntry => ({
|
|
20
|
-
name,
|
|
21
|
-
isDefault: false,
|
|
22
|
-
status: { model, provider },
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
const suite = createLogicSuite('collectSessionModels');
|
|
26
|
-
|
|
27
|
-
suite('collects unique models from assistant messages', () => {
|
|
28
|
-
const models = collectSessionModels(
|
|
29
|
-
[
|
|
30
|
-
assistantMsg({ model: 'claude-sonnet-4-6' }),
|
|
31
|
-
assistantMsg({ model: 'claude-haiku-4-5-20251001' }),
|
|
32
|
-
assistantMsg({ model: 'claude-sonnet-4-6', provider: 'anthropic' }),
|
|
33
|
-
assistantMsg({ model: 'gemini-2.5-pro', providerName: 'high', provider: 'gemini' }),
|
|
34
|
-
],
|
|
35
|
-
[],
|
|
36
|
-
);
|
|
37
|
-
assert.is(models.length, 3);
|
|
38
|
-
assert.is(models[0]?.model, 'claude-sonnet-4-6');
|
|
39
|
-
assert.is(models[0]?.provider, 'anthropic');
|
|
40
|
-
assert.is(models[2]?.model, 'gemini-2.5-pro');
|
|
41
|
-
assert.is(models[2]?.provider, 'gemini');
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
suite('resolves vendor from providerStatuses when messages lack provider', () => {
|
|
45
|
-
const models = collectSessionModels(
|
|
46
|
-
[assistantMsg({ model: 'gemini-3.1-flash-lite', providerName: 'low' })],
|
|
47
|
-
[statusEntry('low', 'gemini-3.1-flash-lite', 'gemini')],
|
|
48
|
-
);
|
|
49
|
-
assert.is(models.length, 1);
|
|
50
|
-
assert.is(models[0]?.provider, 'gemini');
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
suite('does not persist registry tier names as provider', () => {
|
|
54
|
-
const models = collectSessionModels(
|
|
55
|
-
[assistantMsg({ model: 'gemini-3.1-flash-lite', providerName: 'low' })],
|
|
56
|
-
[],
|
|
57
|
-
);
|
|
58
|
-
assert.is(models[0]?.provider, undefined);
|
|
59
|
-
assert.is('providerName' in (models[0] ?? {}), false);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
suite('includes models from sub-agent traces', () => {
|
|
63
|
-
const models = collectSessionModels(
|
|
64
|
-
[
|
|
65
|
-
assistantMsg({
|
|
66
|
-
toolCalls: [
|
|
67
|
-
{
|
|
68
|
-
id: 'tc1',
|
|
69
|
-
name: 'spawn',
|
|
70
|
-
args: {},
|
|
71
|
-
subAgentTrace: [assistantMsg({ model: 'claude-haiku-4-5-20251001' })],
|
|
72
|
-
},
|
|
73
|
-
],
|
|
74
|
-
}),
|
|
75
|
-
],
|
|
76
|
-
[],
|
|
77
|
-
);
|
|
78
|
-
assert.is(models.length, 1);
|
|
79
|
-
assert.is(models[0]?.model, 'claude-haiku-4-5-20251001');
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
suite.run();
|