@genesislcap/ai-assistant 14.458.0 → 14.458.1-GENC-0.2
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/dist/ai-assistant.api.json +37 -10
- package/dist/ai-assistant.d.ts +36 -4
- package/dist/dts/components/waves-indicator.d.ts +30 -0
- package/dist/dts/components/waves-indicator.d.ts.map +1 -0
- package/dist/dts/main/main.d.ts.map +1 -1
- package/dist/dts/main/main.styles.d.ts.map +1 -1
- package/dist/dts/main/main.template.d.ts.map +1 -1
- package/dist/dts/main/main.types.d.ts +34 -4
- package/dist/dts/main/main.types.d.ts.map +1 -1
- package/dist/dts/utils/animation-exclusivity.d.ts +23 -0
- package/dist/dts/utils/animation-exclusivity.d.ts.map +1 -0
- package/dist/dts/utils/animation-exclusivity.test.d.ts +2 -0
- package/dist/dts/utils/animation-exclusivity.test.d.ts.map +1 -0
- package/dist/esm/components/waves-indicator.js +180 -0
- package/dist/esm/main/main.js +18 -9
- package/dist/esm/main/main.styles.js +38 -6
- package/dist/esm/main/main.template.js +23 -16
- package/dist/esm/main/main.types.js +34 -3
- package/dist/esm/utils/animation-exclusivity.js +33 -0
- package/dist/esm/utils/animation-exclusivity.test.js +41 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +16 -16
- package/src/components/waves-indicator.ts +212 -0
- package/src/main/main.styles.ts +38 -6
- package/src/main/main.template.ts +24 -17
- package/src/main/main.ts +20 -8
- package/src/main/main.types.ts +42 -5
- package/src/utils/animation-exclusivity.test.ts +53 -0
- package/src/utils/animation-exclusivity.ts +40 -0
|
@@ -95,14 +95,22 @@ export const styles = css `
|
|
|
95
95
|
animation: settings-slide-out 0.2s ease-in forwards;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
width:
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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,22 @@ export const styles = css `
|
|
|
803
819
|
}
|
|
804
820
|
}
|
|
805
821
|
|
|
822
|
+
.thinking-waves {
|
|
823
|
+
display: flex;
|
|
824
|
+
flex-direction: column;
|
|
825
|
+
align-items: center;
|
|
826
|
+
align-self: center;
|
|
827
|
+
gap: 6px;
|
|
828
|
+
padding: calc(var(--design-unit) * 2px) calc(var(--design-unit) * 3px);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
.thinking-caption {
|
|
832
|
+
font-size: var(--type-ramp-minus-1-font-size, 12px);
|
|
833
|
+
line-height: var(--type-ramp-minus-1-line-height, 16px);
|
|
834
|
+
color: var(--neutral-foreground-rest);
|
|
835
|
+
opacity: 70%;
|
|
836
|
+
}
|
|
837
|
+
|
|
806
838
|
.attachment-chips {
|
|
807
839
|
display: flex;
|
|
808
840
|
flex-wrap: wrap;
|
|
@@ -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
|
-
|
|
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
|
|
@@ -141,7 +139,7 @@ const liveSubAgentTraceTemplate = html `
|
|
|
141
139
|
export const FoundationAiAssistantTemplate = (designSystemPrefix) => {
|
|
142
140
|
const buttonTag = `${designSystemPrefix}-button`;
|
|
143
141
|
const switchTag = `${designSystemPrefix}-switch`;
|
|
144
|
-
const
|
|
142
|
+
const categorizedMultiselectTag = `${designSystemPrefix}-categorized-multiselect`;
|
|
145
143
|
const textareaTag = `${designSystemPrefix}-text-area`;
|
|
146
144
|
const iconTag = `${designSystemPrefix}-icon`;
|
|
147
145
|
const progressTag = `${designSystemPrefix}-progress`;
|
|
@@ -356,15 +354,13 @@ ${(tc) => { var _a; return (((_a = tc.foldPath) === null || _a === void 0 ? void
|
|
|
356
354
|
${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
355
|
<div class="settings-animations">
|
|
358
356
|
<span class="settings-label">Animations</span>
|
|
359
|
-
<${
|
|
357
|
+
<${categorizedMultiselectTag}
|
|
360
358
|
part="toggle-animations"
|
|
361
359
|
:selectedOptions=${(x) => x.enabledAnimations}
|
|
362
360
|
:options=${() => animationOptions}
|
|
363
|
-
:itemRenderer=${() => animationItemRenderer}
|
|
364
361
|
@selectionChange=${(x, c) => x.setEnabledAnimations(c.event.detail)}
|
|
365
362
|
search="false"
|
|
366
|
-
|
|
367
|
-
></${multiselectTag}>
|
|
363
|
+
></${categorizedMultiselectTag}>
|
|
368
364
|
</div>
|
|
369
365
|
`)}
|
|
370
366
|
</div>
|
|
@@ -453,7 +449,9 @@ ${(tc) => { var _a; return (((_a = tc.foldPath) === null || _a === void 0 ? void
|
|
|
453
449
|
${when((x) => {
|
|
454
450
|
var _a;
|
|
455
451
|
return x.showLoadingIndicator &&
|
|
456
|
-
(((_a = x.chatConfig.ui) === null || _a === void 0 ? void 0 : _a.animations) == null ||
|
|
452
|
+
(((_a = x.chatConfig.ui) === null || _a === void 0 ? void 0 : _a.animations) == null ||
|
|
453
|
+
x.enabledAnimations.includes('loading') ||
|
|
454
|
+
x.enabledAnimations.includes('waves'));
|
|
457
455
|
}, html `
|
|
458
456
|
<div class="message-row ai" part="thinking">
|
|
459
457
|
<div class="avatar">
|
|
@@ -465,12 +463,21 @@ ${(tc) => { var _a; return (((_a = tc.foldPath) === null || _a === void 0 ? void
|
|
|
465
463
|
<span class="avatar-icon" :innerHTML="${() => x.assistantIconSafe}"></span>
|
|
466
464
|
`}
|
|
467
465
|
</div>
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
466
|
+
${(x) => x.enabledAnimations.includes('waves')
|
|
467
|
+
? html `
|
|
468
|
+
<div class="thinking-waves" part="thinking-waves">
|
|
469
|
+
<ai-waves-indicator></ai-waves-indicator>
|
|
470
|
+
<span class="thinking-caption">Thinking...</span>
|
|
471
|
+
</div>
|
|
472
|
+
`
|
|
473
|
+
: html `
|
|
474
|
+
<div class="thinking-dots">
|
|
475
|
+
<div class="dot dot-1"></div>
|
|
476
|
+
<div class="dot dot-2"></div>
|
|
477
|
+
<div class="dot dot-3"></div>
|
|
478
|
+
<div class="dot dot-4"></div>
|
|
479
|
+
</div>
|
|
480
|
+
`}
|
|
474
481
|
</div>
|
|
475
482
|
`)}
|
|
476
483
|
</div>
|
|
@@ -2,16 +2,29 @@
|
|
|
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) and `waves` are two interchangeable styles of the same
|
|
7
|
+
* "is the assistant working" indicator and are therefore grouped under the same
|
|
8
|
+
* {@link AiAssistantAnimationDef.category}. They are mutually exclusive — see
|
|
9
|
+
* `LOADING_STYLE_ANIMATIONS`.
|
|
10
|
+
*
|
|
5
11
|
* @beta
|
|
6
12
|
*/
|
|
7
13
|
export const ANIMATION_DEFS = {
|
|
8
14
|
loading: {
|
|
9
|
-
label: '
|
|
10
|
-
|
|
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',
|
|
11
23
|
},
|
|
12
24
|
halo: {
|
|
13
25
|
label: 'Halo',
|
|
14
|
-
|
|
26
|
+
description: 'Displays a glowing halo around the assistant avatar while a response is streaming.',
|
|
27
|
+
category: 'Effects',
|
|
15
28
|
},
|
|
16
29
|
};
|
|
17
30
|
/**
|
|
@@ -20,3 +33,21 @@ export const ANIMATION_DEFS = {
|
|
|
20
33
|
* @internal
|
|
21
34
|
*/
|
|
22
35
|
export const ALL_ANIMATIONS = Object.keys(ANIMATION_DEFS);
|
|
36
|
+
/**
|
|
37
|
+
* The interchangeable "assistant is working" loading-indicator styles. At most
|
|
38
|
+
* one of these may be enabled at a time — enabling one disables the other.
|
|
39
|
+
*
|
|
40
|
+
* @internal
|
|
41
|
+
*/
|
|
42
|
+
export const LOADING_STYLE_ANIMATIONS = [
|
|
43
|
+
'loading',
|
|
44
|
+
'waves',
|
|
45
|
+
];
|
|
46
|
+
/**
|
|
47
|
+
* Animations enabled by default when a consumer opts into the animations
|
|
48
|
+
* feature without specifying an explicit `enabled` list. Keeps the dots loading
|
|
49
|
+
* style (the long-standing default); the waves style is opt-in.
|
|
50
|
+
*
|
|
51
|
+
* @internal
|
|
52
|
+
*/
|
|
53
|
+
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,41 @@
|
|
|
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.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/halo-overlay.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.0",
|
|
4
|
+
"version": "14.458.1-GENC-0.2",
|
|
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.0",
|
|
68
|
-
"@genesislcap/genx": "14.458.0",
|
|
69
|
-
"@genesislcap/rollup-builder": "14.458.0",
|
|
70
|
-
"@genesislcap/ts-builder": "14.458.0",
|
|
71
|
-
"@genesislcap/uvu-playwright-builder": "14.458.0",
|
|
72
|
-
"@genesislcap/vite-builder": "14.458.0",
|
|
73
|
-
"@genesislcap/webpack-builder": "14.458.0",
|
|
67
|
+
"@genesislcap/foundation-testing": "14.458.1-GENC-0.2",
|
|
68
|
+
"@genesislcap/genx": "14.458.1-GENC-0.2",
|
|
69
|
+
"@genesislcap/rollup-builder": "14.458.1-GENC-0.2",
|
|
70
|
+
"@genesislcap/ts-builder": "14.458.1-GENC-0.2",
|
|
71
|
+
"@genesislcap/uvu-playwright-builder": "14.458.1-GENC-0.2",
|
|
72
|
+
"@genesislcap/vite-builder": "14.458.1-GENC-0.2",
|
|
73
|
+
"@genesislcap/webpack-builder": "14.458.1-GENC-0.2",
|
|
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.0",
|
|
79
|
-
"@genesislcap/foundation-logger": "14.458.0",
|
|
80
|
-
"@genesislcap/foundation-redux": "14.458.0",
|
|
81
|
-
"@genesislcap/foundation-ui": "14.458.0",
|
|
82
|
-
"@genesislcap/foundation-utils": "14.458.0",
|
|
83
|
-
"@genesislcap/rapid-design-system": "14.458.0",
|
|
84
|
-
"@genesislcap/web-core": "14.458.0",
|
|
78
|
+
"@genesislcap/foundation-ai": "14.458.1-GENC-0.2",
|
|
79
|
+
"@genesislcap/foundation-logger": "14.458.1-GENC-0.2",
|
|
80
|
+
"@genesislcap/foundation-redux": "14.458.1-GENC-0.2",
|
|
81
|
+
"@genesislcap/foundation-ui": "14.458.1-GENC-0.2",
|
|
82
|
+
"@genesislcap/foundation-utils": "14.458.1-GENC-0.2",
|
|
83
|
+
"@genesislcap/rapid-design-system": "14.458.1-GENC-0.2",
|
|
84
|
+
"@genesislcap/web-core": "14.458.1-GENC-0.2",
|
|
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": "
|
|
96
|
+
"gitHead": "7506d57ff2fb4954bdba714195727a48ebb3d9a3"
|
|
97
97
|
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { avoidTreeShaking } from '@genesislcap/foundation-utils';
|
|
2
|
+
import { attr, css, customElement, GenesisElement, html } from '@genesislcap/web-core';
|
|
3
|
+
import {
|
|
4
|
+
AI_COLOUR_AMBER,
|
|
5
|
+
AI_COLOUR_CYAN,
|
|
6
|
+
AI_COLOUR_PINK,
|
|
7
|
+
AI_COLOUR_VIOLET,
|
|
8
|
+
} from '../styles/ai-colours';
|
|
9
|
+
import { AiHaloOverlay } from './halo-overlay';
|
|
10
|
+
|
|
11
|
+
const WAVES_DEFAULT_SIZE = 56;
|
|
12
|
+
/** CSS-ready form of `WAVES_DEFAULT_SIZE` (the `css` tag rejects raw numbers). */
|
|
13
|
+
const WAVES_DEFAULT_SIZE_CSS = `${WAVES_DEFAULT_SIZE}px`;
|
|
14
|
+
|
|
15
|
+
/** SVG coordinate space the waves are drawn in (square; the circle fills it). */
|
|
16
|
+
const VIEWBOX = 120;
|
|
17
|
+
const CENTRE = VIEWBOX / 2;
|
|
18
|
+
/** Horizontal sampling step when tracing each wave path. Lower = smoother. */
|
|
19
|
+
const SAMPLE_STEP = 6;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Per-wave parameters. Each wave is a travelling sine (amplitude/frequency/phase)
|
|
23
|
+
* plus a slower, longer-wavelength term that makes the line "slosh" like liquid.
|
|
24
|
+
*/
|
|
25
|
+
interface WaveConfig {
|
|
26
|
+
colour: string;
|
|
27
|
+
/** Peak height of the travelling wave, in viewBox units. */
|
|
28
|
+
amplitude: number;
|
|
29
|
+
/** Angular frequency of the travelling wave (radians per viewBox unit). */
|
|
30
|
+
frequency: number;
|
|
31
|
+
/** How fast the travelling wave scrolls (radians per frame). */
|
|
32
|
+
phaseSpeed: number;
|
|
33
|
+
/** Vertical centre offset so the waves stack rather than overlap exactly. */
|
|
34
|
+
verticalOffset: number;
|
|
35
|
+
/** Amplitude of the slow sloshing term. */
|
|
36
|
+
slosh: number;
|
|
37
|
+
/** Angular frequency of the sloshing term (radians per viewBox unit). */
|
|
38
|
+
sloshFrequency: number;
|
|
39
|
+
/** How fast the sloshing term evolves (radians per frame). */
|
|
40
|
+
sloshSpeed: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const WAVES: WaveConfig[] = [
|
|
44
|
+
{
|
|
45
|
+
colour: AI_COLOUR_AMBER,
|
|
46
|
+
amplitude: 11,
|
|
47
|
+
frequency: 0.085,
|
|
48
|
+
phaseSpeed: 0.05,
|
|
49
|
+
verticalOffset: -6,
|
|
50
|
+
slosh: 6,
|
|
51
|
+
sloshFrequency: 0.018,
|
|
52
|
+
sloshSpeed: 0.021,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
colour: AI_COLOUR_PINK,
|
|
56
|
+
amplitude: 14,
|
|
57
|
+
frequency: 0.07,
|
|
58
|
+
phaseSpeed: -0.043,
|
|
59
|
+
verticalOffset: -2,
|
|
60
|
+
slosh: 7,
|
|
61
|
+
sloshFrequency: 0.022,
|
|
62
|
+
sloshSpeed: -0.017,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
colour: AI_COLOUR_CYAN,
|
|
66
|
+
amplitude: 13,
|
|
67
|
+
frequency: 0.095,
|
|
68
|
+
phaseSpeed: 0.037,
|
|
69
|
+
verticalOffset: 2,
|
|
70
|
+
slosh: 5,
|
|
71
|
+
sloshFrequency: 0.015,
|
|
72
|
+
sloshSpeed: 0.025,
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
colour: AI_COLOUR_VIOLET,
|
|
76
|
+
amplitude: 10,
|
|
77
|
+
frequency: 0.06,
|
|
78
|
+
phaseSpeed: -0.055,
|
|
79
|
+
verticalOffset: 6,
|
|
80
|
+
slosh: 8,
|
|
81
|
+
sloshFrequency: 0.025,
|
|
82
|
+
sloshSpeed: -0.013,
|
|
83
|
+
},
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
const wavePathsMarkup = WAVES.map(
|
|
87
|
+
(w, i) => `<path class="wave" data-wave="${i}" stroke="${w.colour}" />`,
|
|
88
|
+
).join('');
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Animated "waves inside a circle" loading indicator — coloured sine waves that
|
|
92
|
+
* slosh like glowing liquid inside a circular window, ringed by a rotating
|
|
93
|
+
* gradient halo (the same effect as `<ai-halo-overlay>`).
|
|
94
|
+
*
|
|
95
|
+
* Visual sibling of the dots loading indicator; the two are interchangeable
|
|
96
|
+
* styles of the same "assistant is working" state.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```html
|
|
100
|
+
* <ai-waves-indicator size="56"></ai-waves-indicator>
|
|
101
|
+
* ```
|
|
102
|
+
*
|
|
103
|
+
* @beta
|
|
104
|
+
*/
|
|
105
|
+
@customElement({
|
|
106
|
+
name: 'ai-waves-indicator',
|
|
107
|
+
template: html<AiWavesIndicator>`
|
|
108
|
+
<div class="window" role="img" aria-label="Assistant is working">
|
|
109
|
+
<svg class="waves" viewBox="0 0 ${VIEWBOX} ${VIEWBOX}" preserveAspectRatio="none">
|
|
110
|
+
<defs>
|
|
111
|
+
<filter id="wave-glow" x="-20%" y="-20%" width="140%" height="140%">
|
|
112
|
+
<feGaussianBlur stdDeviation="1.6" result="blur" />
|
|
113
|
+
<feMerge>
|
|
114
|
+
<feMergeNode in="blur" />
|
|
115
|
+
<feMergeNode in="SourceGraphic" />
|
|
116
|
+
</feMerge>
|
|
117
|
+
</filter>
|
|
118
|
+
</defs>
|
|
119
|
+
<g filter="url(#wave-glow)">${wavePathsMarkup}</g>
|
|
120
|
+
</svg>
|
|
121
|
+
<ai-halo-overlay active border-size="2" glow-opacity="0.5" glow-spread="55"></ai-halo-overlay>
|
|
122
|
+
</div>
|
|
123
|
+
`,
|
|
124
|
+
styles: css`
|
|
125
|
+
:host {
|
|
126
|
+
display: inline-block;
|
|
127
|
+
width: var(--waves-size, ${WAVES_DEFAULT_SIZE_CSS});
|
|
128
|
+
height: var(--waves-size, ${WAVES_DEFAULT_SIZE_CSS});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.window {
|
|
132
|
+
position: relative;
|
|
133
|
+
width: 100%;
|
|
134
|
+
height: 100%;
|
|
135
|
+
border-radius: 50%;
|
|
136
|
+
overflow: hidden;
|
|
137
|
+
background: radial-gradient(circle at 50% 32%, #2b3140 0%, #11141c 55%, #05070c 100%);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.waves {
|
|
141
|
+
position: absolute;
|
|
142
|
+
inset: 0;
|
|
143
|
+
width: 100%;
|
|
144
|
+
height: 100%;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.wave {
|
|
148
|
+
fill: none;
|
|
149
|
+
stroke-width: 2;
|
|
150
|
+
stroke-linecap: round;
|
|
151
|
+
stroke-linejoin: round;
|
|
152
|
+
}
|
|
153
|
+
`,
|
|
154
|
+
})
|
|
155
|
+
export class AiWavesIndicator extends GenesisElement {
|
|
156
|
+
/** Diameter of the circular window in px. Default: 56. */
|
|
157
|
+
@attr({ converter: { fromView: Number, toView: String } }) size: number = WAVES_DEFAULT_SIZE;
|
|
158
|
+
|
|
159
|
+
sizeChanged() {
|
|
160
|
+
this.style.setProperty('--waves-size', `${this.size}px`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// A rAF loop drives the wave paths for the same reason `<ai-halo-overlay>`
|
|
164
|
+
// hand-drives its rotation: a pure-CSS approach can't produce per-frame sine
|
|
165
|
+
// geometry, and SMIL/`<animate>` can't express the combined travel + slosh.
|
|
166
|
+
|
|
167
|
+
private frame = 0;
|
|
168
|
+
private animFrame?: number;
|
|
169
|
+
private wavePaths?: SVGPathElement[];
|
|
170
|
+
|
|
171
|
+
connectedCallback() {
|
|
172
|
+
super.connectedCallback();
|
|
173
|
+
this.tick();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
disconnectedCallback() {
|
|
177
|
+
super.disconnectedCallback();
|
|
178
|
+
if (this.animFrame !== undefined) {
|
|
179
|
+
cancelAnimationFrame(this.animFrame);
|
|
180
|
+
this.animFrame = undefined;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private tick() {
|
|
185
|
+
if (!this.wavePaths) {
|
|
186
|
+
const paths = this.shadowRoot?.querySelectorAll<SVGPathElement>('.wave');
|
|
187
|
+
if (paths?.length) this.wavePaths = Array.from(paths);
|
|
188
|
+
}
|
|
189
|
+
this.wavePaths?.forEach((path, i) => {
|
|
190
|
+
path.setAttribute('d', AiWavesIndicator.buildWavePath(WAVES[i], this.frame));
|
|
191
|
+
});
|
|
192
|
+
this.frame += 1;
|
|
193
|
+
this.animFrame = requestAnimationFrame(() => this.tick());
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Trace one wave's polyline `d` attribute for the given frame. */
|
|
197
|
+
private static buildWavePath(cfg: WaveConfig, frame: number): string {
|
|
198
|
+
const segments: string[] = [];
|
|
199
|
+
for (let x = 0; x <= VIEWBOX; x += SAMPLE_STEP) {
|
|
200
|
+
const y =
|
|
201
|
+
CENTRE +
|
|
202
|
+
cfg.verticalOffset +
|
|
203
|
+
cfg.amplitude * Math.sin(x * cfg.frequency + frame * cfg.phaseSpeed) +
|
|
204
|
+
cfg.slosh * Math.sin(x * cfg.sloshFrequency + frame * cfg.sloshSpeed);
|
|
205
|
+
segments.push(`${x},${y.toFixed(2)}`);
|
|
206
|
+
}
|
|
207
|
+
return `M ${segments.join(' L ')}`;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Ensure the halo overlay used for the ring is registered alongside this component.
|
|
212
|
+
avoidTreeShaking(AiHaloOverlay);
|
package/src/main/main.styles.ts
CHANGED
|
@@ -101,14 +101,22 @@ export const styles = css`
|
|
|
101
101
|
animation: settings-slide-out 0.2s ease-in forwards;
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
width:
|
|
104
|
+
/* Collapsed control footprint stays compact (100px)... */
|
|
105
|
+
rapid-categorized-multiselect::part(root) {
|
|
106
|
+
min-width: 0;
|
|
107
|
+
width: 100px;
|
|
107
108
|
}
|
|
108
109
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
110
|
+
/* ...while the dropdown panel, being absolutely positioned, can be wider
|
|
111
|
+
(200px) without affecting the root's layout footprint. The control sits on
|
|
112
|
+
a different side of the settings panel depending on the container width
|
|
113
|
+
(see the layout rules below), so the dropdown must anchor to whichever side
|
|
114
|
+
keeps it inside the panel. Default (narrow) layout puts the control on the
|
|
115
|
+
LEFT, so the dropdown grows rightward. */
|
|
116
|
+
rapid-categorized-multiselect::part(options) {
|
|
117
|
+
width: 200px;
|
|
118
|
+
left: 0;
|
|
119
|
+
right: auto;
|
|
112
120
|
}
|
|
113
121
|
|
|
114
122
|
.settings-panel > [part='toggle-tool-calls'] {
|
|
@@ -127,6 +135,7 @@ export const styles = css`
|
|
|
127
135
|
}
|
|
128
136
|
|
|
129
137
|
.settings-panel > [part='download-button'] {
|
|
138
|
+
width: fit-content;
|
|
130
139
|
grid-column: 2;
|
|
131
140
|
grid-row: 2;
|
|
132
141
|
}
|
|
@@ -176,6 +185,13 @@ export const styles = css`
|
|
|
176
185
|
.settings-panel > .settings-animations {
|
|
177
186
|
grid-row: 2;
|
|
178
187
|
}
|
|
188
|
+
|
|
189
|
+
/* Control now sits on the RIGHT (justify-self: end / margin-left: auto in
|
|
190
|
+
the wider layouts), so the dropdown grows leftward to stay in the panel. */
|
|
191
|
+
rapid-categorized-multiselect::part(options) {
|
|
192
|
+
left: auto;
|
|
193
|
+
right: 0;
|
|
194
|
+
}
|
|
179
195
|
}
|
|
180
196
|
|
|
181
197
|
@container (min-width: 750px) {
|
|
@@ -809,6 +825,22 @@ export const styles = css`
|
|
|
809
825
|
}
|
|
810
826
|
}
|
|
811
827
|
|
|
828
|
+
.thinking-waves {
|
|
829
|
+
display: flex;
|
|
830
|
+
flex-direction: column;
|
|
831
|
+
align-items: center;
|
|
832
|
+
align-self: center;
|
|
833
|
+
gap: 6px;
|
|
834
|
+
padding: calc(var(--design-unit) * 2px) calc(var(--design-unit) * 3px);
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
.thinking-caption {
|
|
838
|
+
font-size: var(--type-ramp-minus-1-font-size, 12px);
|
|
839
|
+
line-height: var(--type-ramp-minus-1-line-height, 16px);
|
|
840
|
+
color: var(--neutral-foreground-rest);
|
|
841
|
+
opacity: 70%;
|
|
842
|
+
}
|
|
843
|
+
|
|
812
844
|
.attachment-chips {
|
|
813
845
|
display: flex;
|
|
814
846
|
flex-wrap: wrap;
|