@genesislcap/ai-assistant 14.458.3 → 14.459.0

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.
Files changed (37) hide show
  1. package/dist/ai-assistant.api.json +37 -10
  2. package/dist/ai-assistant.d.ts +46 -4
  3. package/dist/dts/components/flowing-waves-indicator.d.ts +32 -0
  4. package/dist/dts/components/flowing-waves-indicator.d.ts.map +1 -0
  5. package/dist/dts/components/plasma-orb-indicator.d.ts +22 -0
  6. package/dist/dts/components/plasma-orb-indicator.d.ts.map +1 -0
  7. package/dist/dts/components/waves-indicator.d.ts +30 -0
  8. package/dist/dts/components/waves-indicator.d.ts.map +1 -0
  9. package/dist/dts/main/main.d.ts.map +1 -1
  10. package/dist/dts/main/main.styles.d.ts.map +1 -1
  11. package/dist/dts/main/main.template.d.ts.map +1 -1
  12. package/dist/dts/main/main.types.d.ts +44 -4
  13. package/dist/dts/main/main.types.d.ts.map +1 -1
  14. package/dist/dts/utils/animation-exclusivity.d.ts +23 -0
  15. package/dist/dts/utils/animation-exclusivity.d.ts.map +1 -0
  16. package/dist/dts/utils/animation-exclusivity.test.d.ts +2 -0
  17. package/dist/dts/utils/animation-exclusivity.test.d.ts.map +1 -0
  18. package/dist/esm/components/flowing-waves-indicator.js +222 -0
  19. package/dist/esm/components/plasma-orb-indicator.js +280 -0
  20. package/dist/esm/components/waves-indicator.js +189 -0
  21. package/dist/esm/main/main.js +20 -9
  22. package/dist/esm/main/main.styles.js +40 -6
  23. package/dist/esm/main/main.template.js +58 -17
  24. package/dist/esm/main/main.types.js +46 -3
  25. package/dist/esm/utils/animation-exclusivity.js +33 -0
  26. package/dist/esm/utils/animation-exclusivity.test.js +52 -0
  27. package/dist/tsconfig.tsbuildinfo +1 -1
  28. package/package.json +16 -16
  29. package/src/components/flowing-waves-indicator.ts +260 -0
  30. package/src/components/plasma-orb-indicator.ts +281 -0
  31. package/src/components/waves-indicator.ts +221 -0
  32. package/src/main/main.styles.ts +40 -6
  33. package/src/main/main.template.ts +60 -18
  34. package/src/main/main.ts +24 -8
  35. package/src/main/main.types.ts +56 -5
  36. package/src/utils/animation-exclusivity.test.ts +72 -0
  37. package/src/utils/animation-exclusivity.ts +40 -0
@@ -33,21 +33,25 @@ import { AiChatBubble } from '../components/chat-bubble/chat-bubble';
33
33
  import { ChatDriver } from '../components/chat-driver/chat-driver';
34
34
  import { AiChatInteractionWrapper } from '../components/chat-interaction-wrapper/chat-interaction-wrapper';
35
35
  import { AiChatMarkdown } from '../components/chat-markdown/chat-markdown';
36
+ import { AiFlowingWavesIndicator } from '../components/flowing-waves-indicator';
36
37
  import { AiHaloOverlay } from '../components/halo-overlay';
37
38
  import { OrchestratingDriver } from '../components/orchestrating-driver/orchestrating-driver';
39
+ import { AiPlasmaOrbIndicator } from '../components/plasma-orb-indicator';
40
+ import { AiWavesIndicator } from '../components/waves-indicator';
38
41
  import { recordMetaEvent, getMetaEvents, DEBUG_LOG_README, } from '../state/debug-event-log';
39
42
  import { getOrCreateDriver, getDriver, deleteDriver } from '../state/driver-registry';
40
43
  import { getSessionStore, hasSessionStore } from '../state/session-store';
41
44
  import { AI_COLOUR_AMBER, AI_COLOUR_CYAN, AI_COLOUR_PINK, AI_COLOUR_VIOLET, } from '../styles/ai-colours';
42
45
  import { ChatSuggestions } from '../suggestions/chat-suggestions';
43
46
  import { AnimatedPanelToggle } from '../utils/animated-panel-toggle';
47
+ import { resolveExclusiveLoadingStyle } from '../utils/animation-exclusivity';
44
48
  import { logger } from '../utils/logger';
45
49
  import { filterVisibleMessages, trailingInteractionRow } from '../utils/message-partition';
46
50
  import { sumCosts } from '../utils/sum-costs';
47
51
  import { expandToolTree } from '../utils/tool-fold';
48
52
  import { styles } from './main.styles';
49
53
  import { FoundationAiAssistantTemplate } from './main.template';
50
- import { ALL_ANIMATIONS } from './main.types';
54
+ import { DEFAULT_ANIMATIONS } from './main.types';
51
55
  /** Context window sizes (in tokens) for known models. */
52
56
  /**
53
57
  * Pin tint palette, cycled by agent position. Matches the four brand colours
@@ -90,7 +94,7 @@ const COMPOSER_MAX_HEIGHT_PX = 400;
90
94
  /** Keep at least this much of the message list visible while growing the composer. */
91
95
  const COMPOSER_MIN_MESSAGES_PX = 80;
92
96
  // Register supporting components when the main component module is imported.
93
- avoidTreeShaking(AiChatMarkdown, AiChatInteractionWrapper, AiHaloOverlay, AiChatBubble, AiActivityHalo, ChatSuggestions, AgentPicker);
97
+ avoidTreeShaking(AiChatMarkdown, AiChatInteractionWrapper, AiHaloOverlay, AiWavesIndicator, AiFlowingWavesIndicator, AiPlasmaOrbIndicator, AiChatBubble, AiActivityHalo, ChatSuggestions, AgentPicker);
94
98
  /**
95
99
  * Recursively strips non-serializable fields from an agent before storing in Redux:
96
100
  * - `toolHandlers` (functions),
@@ -538,7 +542,9 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
538
542
  return;
539
543
  }
540
544
  const last = this.messages[this.messages.length - 1];
541
- if (last === null || last === void 0 ? void 0 : last.interaction) {
545
+ // Hide only while a pending interaction blocks on the user; a resolved
546
+ // interaction means the assistant is computing again (keep the halo).
547
+ if ((last === null || last === void 0 ? void 0 : last.interaction) && !last.interaction.resolved) {
542
548
  this.showHalo = 'no';
543
549
  }
544
550
  else if (this.showHalo !== 'orchestrating') {
@@ -911,8 +917,7 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
911
917
  this.showToolCalls = ui.showToolCalls === true;
912
918
  this.showThinkingSteps = ui.showThinkingSteps === true;
913
919
  this.showAgentSwitchIndicator = ui.showAgentSwitchIndicator === true;
914
- this.enabledAnimations =
915
- (_c = (_b = ui.animations) === null || _b === void 0 ? void 0 : _b.enabled) !== null && _c !== void 0 ? _c : (ui.animations ? [...ALL_ANIMATIONS] : []);
920
+ this.enabledAnimations = resolveExclusiveLoadingStyle((_c = (_b = ui.animations) === null || _b === void 0 ? void 0 : _b.enabled) !== null && _c !== void 0 ? _c : (ui.animations ? [...DEFAULT_ANIMATIONS] : []));
916
921
  const defaultAgent = (_d = this.chatConfig.picker) === null || _d === void 0 ? void 0 : _d.defaultAgent;
917
922
  if (defaultAgent && ((_e = this.agents) !== null && _e !== void 0 ? _e : []).some((a) => a.name === defaultAgent)) {
918
923
  this.pinnedAgentName = defaultAgent;
@@ -1078,10 +1083,14 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
1078
1083
  // waiting for the user, not computing.
1079
1084
  if (this.busy) {
1080
1085
  const last = this.messages[this.messages.length - 1];
1081
- if (last === null || last === void 0 ? void 0 : last.interaction) {
1086
+ // Only a *pending* interaction means the assistant is blocked waiting on
1087
+ // the user. Once it's resolved — or for any normal step — the assistant is
1088
+ // computing again, so the indicator should resume (e.g. while it works out
1089
+ // the next planning question after the user answers a widget).
1090
+ if ((last === null || last === void 0 ? void 0 : last.interaction) && !last.interaction.resolved) {
1082
1091
  this.stopLoadingTimer();
1083
1092
  }
1084
- else if ((last === null || last === void 0 ? void 0 : last.role) === 'assistant') {
1093
+ else {
1085
1094
  this.startLoadingTimer();
1086
1095
  }
1087
1096
  }
@@ -1334,7 +1343,9 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
1334
1343
  this.showAgentSwitchIndicator = !this.showAgentSwitchIndicator;
1335
1344
  }
1336
1345
  setEnabledAnimations(animations) {
1337
- this.enabledAnimations = animations;
1346
+ // The dots and waves loading styles are mutually exclusive — enabling one
1347
+ // disables the other (see resolveExclusiveLoadingStyle).
1348
+ this.enabledAnimations = resolveExclusiveLoadingStyle(animations, this.enabledAnimations);
1338
1349
  }
1339
1350
  getDebugLog() {
1340
1351
  var _a, _b, _c, _d, _e, _f, _j, _k, _l, _m, _o, _p, _q;
@@ -1822,7 +1833,7 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
1822
1833
  FoundationAiAssistant.SCROLL_BOTTOM_THRESHOLD_PX = 50;
1823
1834
  /** Context-usage percentage that fires the one-shot `context.threshold-crossed` event. */
1824
1835
  FoundationAiAssistant.CONTEXT_USAGE_WARN_PERCENT = 80;
1825
- FoundationAiAssistant.DEFAULT_LOADING_DELAY_S = 5;
1836
+ FoundationAiAssistant.DEFAULT_LOADING_DELAY_S = 0;
1826
1837
  FoundationAiAssistant.DEFAULT_SUGGESTION_COUNT = 3;
1827
1838
  FoundationAiAssistant.MS_PER_SECOND = 1000;
1828
1839
  __decorate([
@@ -95,14 +95,22 @@ export const styles = css `
95
95
  animation: settings-slide-out 0.2s ease-in forwards;
96
96
  }
97
97
 
98
- rapid-multiselect::part(root) {
99
- min-width: 80px;
100
- width: 300%;
98
+ /* Collapsed control footprint stays compact (100px)... */
99
+ rapid-categorized-multiselect::part(root) {
100
+ min-width: 0;
101
+ width: 100px;
101
102
  }
102
103
 
103
- rapid-multiselect::part(control),
104
- .settings-panel > [part='download-button'] {
105
- width: fit-content;
104
+ /* ...while the dropdown panel, being absolutely positioned, can be wider
105
+ (200px) without affecting the root's layout footprint. The control sits on
106
+ a different side of the settings panel depending on the container width
107
+ (see the layout rules below), so the dropdown must anchor to whichever side
108
+ keeps it inside the panel. Default (narrow) layout puts the control on the
109
+ LEFT, so the dropdown grows rightward. */
110
+ rapid-categorized-multiselect::part(options) {
111
+ width: 200px;
112
+ left: 0;
113
+ right: auto;
106
114
  }
107
115
 
108
116
  .settings-panel > [part='toggle-tool-calls'] {
@@ -121,6 +129,7 @@ export const styles = css `
121
129
  }
122
130
 
123
131
  .settings-panel > [part='download-button'] {
132
+ width: fit-content;
124
133
  grid-column: 2;
125
134
  grid-row: 2;
126
135
  }
@@ -170,6 +179,13 @@ export const styles = css `
170
179
  .settings-panel > .settings-animations {
171
180
  grid-row: 2;
172
181
  }
182
+
183
+ /* Control now sits on the RIGHT (justify-self: end / margin-left: auto in
184
+ the wider layouts), so the dropdown grows leftward to stay in the panel. */
185
+ rapid-categorized-multiselect::part(options) {
186
+ left: auto;
187
+ right: 0;
188
+ }
173
189
  }
174
190
 
175
191
  @container (min-width: 750px) {
@@ -803,6 +819,24 @@ export const styles = css `
803
819
  }
804
820
  }
805
821
 
822
+ .thinking-waves,
823
+ .thinking-flowing-waves,
824
+ .thinking-plasma {
825
+ display: flex;
826
+ flex-direction: column;
827
+ align-items: center;
828
+ align-self: center;
829
+ gap: 6px;
830
+ padding: calc(var(--design-unit) * 2px) calc(var(--design-unit) * 3px);
831
+ }
832
+
833
+ .thinking-caption {
834
+ font-size: var(--type-ramp-minus-1-font-size, 12px);
835
+ line-height: var(--type-ramp-minus-1-line-height, 16px);
836
+ color: var(--neutral-foreground-rest);
837
+ opacity: 70%;
838
+ }
839
+
806
840
  .attachment-chips {
807
841
  display: flex;
808
842
  flex-wrap: wrap;
@@ -16,7 +16,7 @@
16
16
  */
17
17
  import { isChatToolCallUnknown } from '@genesislcap/foundation-ai';
18
18
  import { classNames, html, ref, repeat, when } from '@genesislcap/web-core';
19
- import { ANIMATION_DEFS } from './main.types';
19
+ import { ANIMATION_DEFS, LOADING_STYLE_ANIMATIONS } from './main.types';
20
20
  function unknownToolPayload(tc) {
21
21
  if (!isChatToolCallUnknown(tc))
22
22
  return '';
@@ -31,18 +31,16 @@ function unknownToolPayload(tc) {
31
31
  }
32
32
  return lines.join('\n');
33
33
  }
34
- const animationItemRenderer = (option) => html `
35
- <span part="option-label" title="${() => option.tooltip}">${() => option.label}</span>
36
- `;
37
34
  const HALO_SPEED_DEFAULT = 1.5;
38
35
  const HALO_SPEED_ORCHESTRATING = 0.4;
39
36
  const HALO_BORDER_SIZE_DEFAULT = 3;
40
37
  /** Decimal places shown for the running session cost (USD). 4 ≈ $0.0001 resolution. */
41
38
  const SESSION_COST_DECIMALS = 4;
42
39
  const animationOptions = Object.entries(ANIMATION_DEFS).map(([value, def]) => ({
40
+ type: def.category,
43
41
  value,
44
42
  label: def.label,
45
- tooltip: def.tooltip,
43
+ description: def.description,
46
44
  }));
47
45
  // Avatar markup is owned by the component (`assistantIconSafe` / `userIconSafe`),
48
46
  // which holds the sanitized SVG string for the default or any consumer override
@@ -136,12 +134,61 @@ const liveSubAgentTraceTemplate = html `
136
134
  ${repeat((x) => x.liveSubAgentTrace.filter((m) => m.role !== 'user'), subAgentMessageRowTemplate)}
137
135
  </div>
138
136
  `;
137
+ // The interchangeable loading indicators. These MUST be stable module-level
138
+ // instances: the binding that selects between them reads `enabledAnimations`,
139
+ // which is backed by the redux store proxy and re-evaluates on every change to
140
+ // the aiAssistant slice (i.e. every new message, including hidden tool-call and
141
+ // thinking-step messages). Returning a fresh `html` instance from that binding
142
+ // would make FAST tear down and rebuild the indicator DOM each time, restarting
143
+ // the CSS animations and rAF loops. Stable references let FAST reuse the
144
+ // existing view so the animation runs uninterrupted.
145
+ const thinkingDotsTemplate = html `
146
+ <div class="thinking-dots">
147
+ <div class="dot dot-1"></div>
148
+ <div class="dot dot-2"></div>
149
+ <div class="dot dot-3"></div>
150
+ <div class="dot dot-4"></div>
151
+ </div>
152
+ `;
153
+ const thinkingWavesTemplate = html `
154
+ <div class="thinking-waves" part="thinking-waves">
155
+ <ai-waves-indicator></ai-waves-indicator>
156
+ <span class="thinking-caption">Thinking...</span>
157
+ </div>
158
+ `;
159
+ const thinkingFlowingWavesTemplate = html `
160
+ <div class="thinking-flowing-waves" part="thinking-flowing-waves">
161
+ <ai-flowing-waves-indicator></ai-flowing-waves-indicator>
162
+ <span class="thinking-caption">Thinking...</span>
163
+ </div>
164
+ `;
165
+ const thinkingPlasmaTemplate = html `
166
+ <div class="thinking-plasma" part="thinking-plasma">
167
+ <ai-plasma-orb-indicator></ai-plasma-orb-indicator>
168
+ <span class="thinking-caption">Thinking...</span>
169
+ </div>
170
+ `;
171
+ /**
172
+ * Picks the loading indicator for the currently enabled style, falling back to
173
+ * the dots (also the default when no animations config is supplied). Returns a
174
+ * stable template instance per style — see the note above.
175
+ */
176
+ const selectThinkingTemplate = (x) => {
177
+ const enabled = x.enabledAnimations;
178
+ if (enabled.includes('waves'))
179
+ return thinkingWavesTemplate;
180
+ if (enabled.includes('flowingWaves'))
181
+ return thinkingFlowingWavesTemplate;
182
+ if (enabled.includes('plasma'))
183
+ return thinkingPlasmaTemplate;
184
+ return thinkingDotsTemplate;
185
+ };
139
186
  // ─── Public factory ───────────────────────────────────────────────────────────
140
187
  /** @internal */
141
188
  export const FoundationAiAssistantTemplate = (designSystemPrefix) => {
142
189
  const buttonTag = `${designSystemPrefix}-button`;
143
190
  const switchTag = `${designSystemPrefix}-switch`;
144
- const multiselectTag = `${designSystemPrefix}-multiselect`;
191
+ const categorizedMultiselectTag = `${designSystemPrefix}-categorized-multiselect`;
145
192
  const textareaTag = `${designSystemPrefix}-text-area`;
146
193
  const iconTag = `${designSystemPrefix}-icon`;
147
194
  const progressTag = `${designSystemPrefix}-progress`;
@@ -356,15 +403,13 @@ ${(tc) => { var _a; return (((_a = tc.foldPath) === null || _a === void 0 ? void
356
403
  ${when((x) => { var _a, _b; return ((_b = (_a = x.chatConfig.ui) === null || _a === void 0 ? void 0 : _a.animations) === null || _b === void 0 ? void 0 : _b.userConfigurable) === true; }, html `
357
404
  <div class="settings-animations">
358
405
  <span class="settings-label">Animations</span>
359
- <${multiselectTag}
406
+ <${categorizedMultiselectTag}
360
407
  part="toggle-animations"
361
408
  :selectedOptions=${(x) => x.enabledAnimations}
362
409
  :options=${() => animationOptions}
363
- :itemRenderer=${() => animationItemRenderer}
364
410
  @selectionChange=${(x, c) => x.setEnabledAnimations(c.event.detail)}
365
411
  search="false"
366
- all="false"
367
- ></${multiselectTag}>
412
+ ></${categorizedMultiselectTag}>
368
413
  </div>
369
414
  `)}
370
415
  </div>
@@ -453,7 +498,8 @@ ${(tc) => { var _a; return (((_a = tc.foldPath) === null || _a === void 0 ? void
453
498
  ${when((x) => {
454
499
  var _a;
455
500
  return x.showLoadingIndicator &&
456
- (((_a = x.chatConfig.ui) === null || _a === void 0 ? void 0 : _a.animations) == null || x.enabledAnimations.includes('loading'));
501
+ (((_a = x.chatConfig.ui) === null || _a === void 0 ? void 0 : _a.animations) == null ||
502
+ LOADING_STYLE_ANIMATIONS.some((s) => x.enabledAnimations.includes(s)));
457
503
  }, html `
458
504
  <div class="message-row ai" part="thinking">
459
505
  <div class="avatar">
@@ -465,12 +511,7 @@ ${(tc) => { var _a; return (((_a = tc.foldPath) === null || _a === void 0 ? void
465
511
  <span class="avatar-icon" :innerHTML="${() => x.assistantIconSafe}"></span>
466
512
  `}
467
513
  </div>
468
- <div class="thinking-dots">
469
- <div class="dot dot-1"></div>
470
- <div class="dot dot-2"></div>
471
- <div class="dot dot-3"></div>
472
- <div class="dot dot-4"></div>
473
- </div>
514
+ ${(x) => selectThinkingTemplate(x)}
474
515
  </div>
475
516
  `)}
476
517
  </div>
@@ -2,16 +2,39 @@
2
2
  * Registry of all available animations with their display metadata.
3
3
  * Adding an entry here automatically extends the {@link AiAssistantAnimation} type.
4
4
  *
5
+ * @remarks
6
+ * `loading` (dots), `waves`, `flowingWaves` and `plasma` are interchangeable
7
+ * styles of the same "is the assistant working" indicator and are therefore
8
+ * grouped under the same {@link AiAssistantAnimationDef.category}. They are
9
+ * mutually exclusive — see `LOADING_STYLE_ANIMATIONS`.
10
+ *
5
11
  * @beta
6
12
  */
7
13
  export const ANIMATION_DEFS = {
8
14
  loading: {
9
- label: 'Loading indicator',
10
- tooltip: 'Shows a pulsing animation while the assistant is generating a response.',
15
+ label: 'Dots',
16
+ description: 'Shows pulsing dots while the assistant is generating a response.',
17
+ category: 'Loading style',
18
+ },
19
+ waves: {
20
+ label: 'Waves',
21
+ description: 'Shows glowing sine waves inside a circle while the assistant is generating a response.',
22
+ category: 'Loading style',
23
+ },
24
+ flowingWaves: {
25
+ label: 'Flowing waves',
26
+ description: 'Shows coloured waves rising and settling from a line while the assistant is generating a response.',
27
+ category: 'Loading style',
28
+ },
29
+ plasma: {
30
+ label: 'Plasma orb',
31
+ description: 'Shows a glowing plasma sphere with drifting energy while the assistant is generating a response.',
32
+ category: 'Loading style',
11
33
  },
12
34
  halo: {
13
35
  label: 'Halo',
14
- tooltip: 'Displays a glowing halo around the assistant avatar while a response is streaming.',
36
+ description: 'Displays a glowing halo around the assistant avatar while a response is streaming.',
37
+ category: 'Effects',
15
38
  },
16
39
  };
17
40
  /**
@@ -20,3 +43,23 @@ export const ANIMATION_DEFS = {
20
43
  * @internal
21
44
  */
22
45
  export const ALL_ANIMATIONS = Object.keys(ANIMATION_DEFS);
46
+ /**
47
+ * The interchangeable "assistant is working" loading-indicator styles. At most
48
+ * one of these may be enabled at a time — enabling one disables the other.
49
+ *
50
+ * @internal
51
+ */
52
+ export const LOADING_STYLE_ANIMATIONS = [
53
+ 'loading',
54
+ 'waves',
55
+ 'flowingWaves',
56
+ 'plasma',
57
+ ];
58
+ /**
59
+ * Animations enabled by default when a consumer opts into the animations
60
+ * feature without specifying an explicit `enabled` list. Keeps the dots loading
61
+ * style (the long-standing default); the waves style is opt-in.
62
+ *
63
+ * @internal
64
+ */
65
+ export const DEFAULT_ANIMATIONS = ['loading', 'halo'];
@@ -0,0 +1,33 @@
1
+ import { LOADING_STYLE_ANIMATIONS } from '../main/main.types';
2
+ /**
3
+ * Enforces that at most one loading-indicator style (dots vs. waves) is enabled
4
+ * at a time. The two are alternative presentations of the same "assistant is
5
+ * working" state, so selecting one must deselect the other.
6
+ *
7
+ * The settings control is a multiselect (checkboxes), which permits selecting
8
+ * both; this resolver makes the loading-style group behave like a radio group
9
+ * by dropping the previously-selected style whenever a new one is added.
10
+ *
11
+ * @param next - The freshly-selected animation list (e.g. emitted by the
12
+ * multiselect, or read from consumer config).
13
+ * @param previous - The animation list in effect before this change. Used to
14
+ * work out which loading style was just added so the other can be dropped.
15
+ * Pass `[]` when resolving an initial/config value with no prior state.
16
+ * @returns `next` with at most one loading style retained. When both are
17
+ * present and neither is newly added (e.g. a misconfigured `enabled` list),
18
+ * the first entry of `LOADING_STYLE_ANIMATIONS` (dots) wins.
19
+ *
20
+ * @internal
21
+ */
22
+ export function resolveExclusiveLoadingStyle(next, previous = []) {
23
+ var _a;
24
+ const selectedStyles = LOADING_STYLE_ANIMATIONS.filter((style) => next.includes(style));
25
+ if (selectedStyles.length <= 1) {
26
+ return next;
27
+ }
28
+ // Both styles selected — keep whichever was just added (absent from
29
+ // `previous`); fall back to the first declared style on ambiguity.
30
+ const justAdded = (_a = selectedStyles.find((style) => !previous.includes(style))) !== null && _a !== void 0 ? _a : selectedStyles[0];
31
+ return next.filter((animation) => animation === justAdded ||
32
+ !LOADING_STYLE_ANIMATIONS.includes(animation));
33
+ }
@@ -0,0 +1,52 @@
1
+ import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
2
+ import { resolveExclusiveLoadingStyle } from './animation-exclusivity';
3
+ const suite = createLogicSuite('resolveExclusiveLoadingStyle');
4
+ suite('leaves a selection with only the dots loading style untouched', () => {
5
+ const next = ['loading', 'halo'];
6
+ assert.equal(resolveExclusiveLoadingStyle(next, ['halo']), ['loading', 'halo']);
7
+ });
8
+ suite('leaves a selection with only the waves loading style untouched', () => {
9
+ const next = ['waves', 'halo'];
10
+ assert.equal(resolveExclusiveLoadingStyle(next, ['halo']), ['waves', 'halo']);
11
+ });
12
+ suite('leaves a selection with no loading style untouched', () => {
13
+ assert.equal(resolveExclusiveLoadingStyle(['halo'], []), ['halo']);
14
+ });
15
+ suite('drops dots when waves was just added on top of dots', () => {
16
+ // Previously dots was on; user ticks waves → waves wins, dots removed.
17
+ const result = resolveExclusiveLoadingStyle(['loading', 'waves', 'halo'], ['loading', 'halo']);
18
+ assert.equal(result, ['waves', 'halo']);
19
+ });
20
+ suite('drops waves when dots was just added on top of waves', () => {
21
+ // Previously waves was on; user ticks dots → dots wins, waves removed.
22
+ const result = resolveExclusiveLoadingStyle(['loading', 'waves', 'halo'], ['waves', 'halo']);
23
+ assert.equal(result, ['loading', 'halo']);
24
+ });
25
+ suite('preserves the order of the surviving selection', () => {
26
+ const result = resolveExclusiveLoadingStyle(['halo', 'loading', 'waves'], ['halo', 'loading']);
27
+ // waves was just added → loading dropped; halo and waves keep their order.
28
+ assert.equal(result, ['halo', 'waves']);
29
+ });
30
+ suite('falls back to dots when both are present with no prior state (e.g. bad config)', () => {
31
+ const result = resolveExclusiveLoadingStyle(['loading', 'waves', 'halo'], []);
32
+ assert.equal(result, ['loading', 'halo']);
33
+ });
34
+ suite('falls back to dots when both were already present (neither newly added)', () => {
35
+ const result = resolveExclusiveLoadingStyle(['loading', 'waves'], ['loading', 'waves', 'halo']);
36
+ assert.equal(result, ['loading']);
37
+ });
38
+ suite('handles an empty selection', () => {
39
+ assert.equal(resolveExclusiveLoadingStyle([], ['loading']), []);
40
+ });
41
+ suite('switching between any two loading styles keeps only the newly added one', () => {
42
+ // flowingWaves was on; user picks plasma → plasma wins, flowingWaves dropped.
43
+ assert.equal(resolveExclusiveLoadingStyle(['flowingWaves', 'plasma', 'halo'], ['flowingWaves', 'halo']), ['plasma', 'halo']);
44
+ // plasma was on; user picks the dots → dots win, plasma dropped.
45
+ assert.equal(resolveExclusiveLoadingStyle(['plasma', 'loading'], ['plasma']), ['loading']);
46
+ });
47
+ suite('drops all but the newly added style when several are somehow selected', () => {
48
+ const result = resolveExclusiveLoadingStyle(['loading', 'waves', 'flowingWaves', 'plasma'], ['loading', 'waves', 'plasma']);
49
+ // flowingWaves is the only one absent from `previous`, so it wins.
50
+ assert.equal(result, ['flowingWaves']);
51
+ });
52
+ suite.run();
@@ -1 +1 @@
1
- {"root":["../src/index.ts","../src/channel/ai-activity-bus.ts","../src/channel/ai-activity-channel.ts","../src/components/halo-overlay.ts","../src/components/activity-halo/activity-halo.ts","../src/components/agent-picker/agent-picker.constants.ts","../src/components/agent-picker/agent-picker.styles.ts","../src/components/agent-picker/agent-picker.template.ts","../src/components/agent-picker/agent-picker.ts","../src/components/agent-picker/index.ts","../src/components/ai-driver/ai-driver.ts","../src/components/ai-driver/index.ts","../src/components/chat-bubble/chat-bubble.styles.ts","../src/components/chat-bubble/chat-bubble.template.ts","../src/components/chat-bubble/chat-bubble.ts","../src/components/chat-bubble/index.ts","../src/components/chat-driver/align-event-globals.ts","../src/components/chat-driver/chat-driver.test.ts","../src/components/chat-driver/chat-driver.ts","../src/components/chat-driver/index.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.styles.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.template.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.test.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.ts","../src/components/chat-interaction-wrapper/index.ts","../src/components/chat-markdown/chat-markdown.ts","../src/components/chat-markdown/index.ts","../src/components/orchestrating-driver/index.ts","../src/components/orchestrating-driver/orchestrating-driver.ts","../src/components/popout-manager/index.ts","../src/components/popout-manager/popout-manager.ts","../src/config/config.ts","../src/config/define-stateful-agent.ts","../src/config/fallback-agents.ts","../src/config/index.ts","../src/config/validate-providers.test.ts","../src/config/validate-providers.ts","../src/main/index.ts","../src/main/main.styles.ts","../src/main/main.template.ts","../src/main/main.ts","../src/main/main.types.ts","../src/state/ai-assistant-slice.ts","../src/state/debug-event-log.ts","../src/state/driver-registry.ts","../src/state/session-store.ts","../src/styles/ai-colours.ts","../src/styles/index.ts","../src/styles/styles.ts","../src/suggestions/chat-suggestions.ts","../src/tags/index.ts","../src/types/ai-chat-widget.ts","../src/utils/animated-panel-toggle.ts","../src/utils/history-transform.ts","../src/utils/index.ts","../src/utils/logger.ts","../src/utils/message-partition.test.ts","../src/utils/message-partition.ts","../src/utils/sum-costs.test.ts","../src/utils/sum-costs.ts","../src/utils/tool-fold.ts"],"version":"5.9.2"}
1
+ {"root":["../src/index.ts","../src/channel/ai-activity-bus.ts","../src/channel/ai-activity-channel.ts","../src/components/flowing-waves-indicator.ts","../src/components/halo-overlay.ts","../src/components/plasma-orb-indicator.ts","../src/components/waves-indicator.ts","../src/components/activity-halo/activity-halo.ts","../src/components/agent-picker/agent-picker.constants.ts","../src/components/agent-picker/agent-picker.styles.ts","../src/components/agent-picker/agent-picker.template.ts","../src/components/agent-picker/agent-picker.ts","../src/components/agent-picker/index.ts","../src/components/ai-driver/ai-driver.ts","../src/components/ai-driver/index.ts","../src/components/chat-bubble/chat-bubble.styles.ts","../src/components/chat-bubble/chat-bubble.template.ts","../src/components/chat-bubble/chat-bubble.ts","../src/components/chat-bubble/index.ts","../src/components/chat-driver/align-event-globals.ts","../src/components/chat-driver/chat-driver.test.ts","../src/components/chat-driver/chat-driver.ts","../src/components/chat-driver/index.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.styles.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.template.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.test.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.ts","../src/components/chat-interaction-wrapper/index.ts","../src/components/chat-markdown/chat-markdown.ts","../src/components/chat-markdown/index.ts","../src/components/orchestrating-driver/index.ts","../src/components/orchestrating-driver/orchestrating-driver.ts","../src/components/popout-manager/index.ts","../src/components/popout-manager/popout-manager.ts","../src/config/config.ts","../src/config/define-stateful-agent.ts","../src/config/fallback-agents.ts","../src/config/index.ts","../src/config/validate-providers.test.ts","../src/config/validate-providers.ts","../src/main/index.ts","../src/main/main.styles.ts","../src/main/main.template.ts","../src/main/main.ts","../src/main/main.types.ts","../src/state/ai-assistant-slice.ts","../src/state/debug-event-log.ts","../src/state/driver-registry.ts","../src/state/session-store.ts","../src/styles/ai-colours.ts","../src/styles/index.ts","../src/styles/styles.ts","../src/suggestions/chat-suggestions.ts","../src/tags/index.ts","../src/types/ai-chat-widget.ts","../src/utils/animated-panel-toggle.ts","../src/utils/animation-exclusivity.test.ts","../src/utils/animation-exclusivity.ts","../src/utils/history-transform.ts","../src/utils/index.ts","../src/utils/logger.ts","../src/utils/message-partition.test.ts","../src/utils/message-partition.ts","../src/utils/sum-costs.test.ts","../src/utils/sum-costs.ts","../src/utils/tool-fold.ts"],"version":"5.9.2"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@genesislcap/ai-assistant",
3
3
  "description": "Genesis AI Assistant micro-frontend",
4
- "version": "14.458.3",
4
+ "version": "14.459.0",
5
5
  "license": "SEE LICENSE IN license.txt",
6
6
  "main": "dist/esm/index.js",
7
7
  "types": "dist/ai-assistant.d.ts",
@@ -64,24 +64,24 @@
64
64
  }
65
65
  },
66
66
  "devDependencies": {
67
- "@genesislcap/foundation-testing": "14.458.3",
68
- "@genesislcap/genx": "14.458.3",
69
- "@genesislcap/rollup-builder": "14.458.3",
70
- "@genesislcap/ts-builder": "14.458.3",
71
- "@genesislcap/uvu-playwright-builder": "14.458.3",
72
- "@genesislcap/vite-builder": "14.458.3",
73
- "@genesislcap/webpack-builder": "14.458.3",
67
+ "@genesislcap/foundation-testing": "14.459.0",
68
+ "@genesislcap/genx": "14.459.0",
69
+ "@genesislcap/rollup-builder": "14.459.0",
70
+ "@genesislcap/ts-builder": "14.459.0",
71
+ "@genesislcap/uvu-playwright-builder": "14.459.0",
72
+ "@genesislcap/vite-builder": "14.459.0",
73
+ "@genesislcap/webpack-builder": "14.459.0",
74
74
  "@types/dompurify": "^3.0.5",
75
75
  "@types/marked": "^5.0.2"
76
76
  },
77
77
  "dependencies": {
78
- "@genesislcap/foundation-ai": "14.458.3",
79
- "@genesislcap/foundation-logger": "14.458.3",
80
- "@genesislcap/foundation-redux": "14.458.3",
81
- "@genesislcap/foundation-ui": "14.458.3",
82
- "@genesislcap/foundation-utils": "14.458.3",
83
- "@genesislcap/rapid-design-system": "14.458.3",
84
- "@genesislcap/web-core": "14.458.3",
78
+ "@genesislcap/foundation-ai": "14.459.0",
79
+ "@genesislcap/foundation-logger": "14.459.0",
80
+ "@genesislcap/foundation-redux": "14.459.0",
81
+ "@genesislcap/foundation-ui": "14.459.0",
82
+ "@genesislcap/foundation-utils": "14.459.0",
83
+ "@genesislcap/rapid-design-system": "14.459.0",
84
+ "@genesislcap/web-core": "14.459.0",
85
85
  "dompurify": "^3.3.1",
86
86
  "marked": "^17.0.3"
87
87
  },
@@ -93,5 +93,5 @@
93
93
  "publishConfig": {
94
94
  "access": "public"
95
95
  },
96
- "gitHead": "b15ab1abb2bb8420f0c67e5c90472e74ff98d0ad"
96
+ "gitHead": "b501804b11ff99945c7c6d2afbe7a101e1bb735a"
97
97
  }