@genesislcap/ai-assistant 14.458.3 → 14.460.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.
- package/dist/ai-assistant.api.json +38 -11
- package/dist/ai-assistant.d.ts +49 -5
- package/dist/dts/components/chat-driver/chat-driver.d.ts +3 -1
- package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
- package/dist/dts/components/flowing-waves-indicator.d.ts +32 -0
- package/dist/dts/components/flowing-waves-indicator.d.ts.map +1 -0
- package/dist/dts/components/plasma-orb-indicator.d.ts +22 -0
- package/dist/dts/components/plasma-orb-indicator.d.ts.map +1 -0
- 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 +44 -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/chat-driver/chat-driver.js +7 -2
- package/dist/esm/components/chat-driver/chat-driver.test.js +24 -0
- package/dist/esm/components/flowing-waves-indicator.js +222 -0
- package/dist/esm/components/plasma-orb-indicator.js +280 -0
- package/dist/esm/components/waves-indicator.js +189 -0
- package/dist/esm/main/main.js +20 -9
- package/dist/esm/main/main.styles.js +62 -7
- package/dist/esm/main/main.template.js +75 -21
- package/dist/esm/main/main.types.js +46 -3
- package/dist/esm/utils/animation-exclusivity.js +33 -0
- package/dist/esm/utils/animation-exclusivity.test.js +52 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +16 -16
- package/src/components/chat-driver/chat-driver.test.ts +36 -0
- package/src/components/chat-driver/chat-driver.ts +10 -2
- package/src/components/flowing-waves-indicator.ts +260 -0
- package/src/components/plasma-orb-indicator.ts +281 -0
- package/src/components/waves-indicator.ts +221 -0
- package/src/main/main.styles.ts +62 -7
- package/src/main/main.template.ts +88 -27
- package/src/main/main.ts +24 -8
- package/src/main/main.types.ts +56 -5
- package/src/utils/animation-exclusivity.test.ts +72 -0
- package/src/utils/animation-exclusivity.ts +40 -0
package/dist/esm/main/main.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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) {
|
|
@@ -411,6 +427,13 @@ export const styles = css `
|
|
|
411
427
|
animation: slide-in-left 0.25s ease-out;
|
|
412
428
|
}
|
|
413
429
|
|
|
430
|
+
/* A 'bubble' interaction reads like a normal assistant message: avatar to the
|
|
431
|
+
left, bubble to the right — not the full-width column the other interaction
|
|
432
|
+
presentations use. */
|
|
433
|
+
.message-row.interaction.present-bubble {
|
|
434
|
+
flex-direction: row;
|
|
435
|
+
}
|
|
436
|
+
|
|
414
437
|
.avatar {
|
|
415
438
|
position: relative;
|
|
416
439
|
width: 32px;
|
|
@@ -519,7 +542,10 @@ export const styles = css `
|
|
|
519
542
|
border: 1px solid color-mix(in srgb, var(--ai-function-color, #86efac) 20%, transparent);
|
|
520
543
|
}
|
|
521
544
|
|
|
522
|
-
|
|
545
|
+
/* 'label' (default) and 'bare' interactions render the widget chrome-free and
|
|
546
|
+
full-width — the widget owns its body. */
|
|
547
|
+
.message-row.interaction.present-label .message,
|
|
548
|
+
.message-row.interaction.present-bare .message {
|
|
523
549
|
background: none;
|
|
524
550
|
border: none;
|
|
525
551
|
padding: 0;
|
|
@@ -527,6 +553,17 @@ export const styles = css `
|
|
|
527
553
|
width: 100%;
|
|
528
554
|
}
|
|
529
555
|
|
|
556
|
+
/* 'bubble' interactions reuse the standard assistant-message skin so the
|
|
557
|
+
widget sits inside a normal chat bubble. Padding, max-width and min-width
|
|
558
|
+
come from the base .message rule. */
|
|
559
|
+
.message-row.interaction.present-bubble .message {
|
|
560
|
+
background: linear-gradient(135deg, var(--neutral-layer-3) 0%, var(--neutral-layer-4) 100%);
|
|
561
|
+
color: var(--neutral-foreground-rest);
|
|
562
|
+
border-radius: 4px 18px 18px;
|
|
563
|
+
border: calc(var(--stroke-width) * 1px) solid var(--neutral-stroke-rest);
|
|
564
|
+
box-shadow: var(--ai-message-shadow, 0 2px 8px rgb(0 0 0 / 15%));
|
|
565
|
+
}
|
|
566
|
+
|
|
530
567
|
.sender {
|
|
531
568
|
font-weight: bold;
|
|
532
569
|
font-size: 0.9em;
|
|
@@ -803,6 +840,24 @@ export const styles = css `
|
|
|
803
840
|
}
|
|
804
841
|
}
|
|
805
842
|
|
|
843
|
+
.thinking-waves,
|
|
844
|
+
.thinking-flowing-waves,
|
|
845
|
+
.thinking-plasma {
|
|
846
|
+
display: flex;
|
|
847
|
+
flex-direction: column;
|
|
848
|
+
align-items: center;
|
|
849
|
+
align-self: center;
|
|
850
|
+
gap: 6px;
|
|
851
|
+
padding: calc(var(--design-unit) * 2px) calc(var(--design-unit) * 3px);
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
.thinking-caption {
|
|
855
|
+
font-size: var(--type-ramp-minus-1-font-size, 12px);
|
|
856
|
+
line-height: var(--type-ramp-minus-1-line-height, 16px);
|
|
857
|
+
color: var(--neutral-foreground-rest);
|
|
858
|
+
opacity: 70%;
|
|
859
|
+
}
|
|
860
|
+
|
|
806
861
|
.attachment-chips {
|
|
807
862
|
display: flex;
|
|
808
863
|
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
|
-
|
|
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
|
|
@@ -102,6 +100,12 @@ const senderLabel = {
|
|
|
102
100
|
interaction: 'Assistant',
|
|
103
101
|
ai: 'Assistant',
|
|
104
102
|
};
|
|
103
|
+
/**
|
|
104
|
+
* Resolves how the host frames an interaction widget, defaulting to `'label'`
|
|
105
|
+
* (the historical "Assistant" label, no bubble). Callers guard on
|
|
106
|
+
* `m.interaction` first; non-interaction messages never consult this.
|
|
107
|
+
*/
|
|
108
|
+
const interactionPresentation = (m) => { var _a, _b; return (_b = (_a = m.interaction) === null || _a === void 0 ? void 0 : _a.presentation) !== null && _b !== void 0 ? _b : 'label'; };
|
|
105
109
|
// ─── Sub-agent trace fragments ────────────────────────────────────────────────
|
|
106
110
|
const subAgentAssistantTemplate = html `
|
|
107
111
|
<div class="sub-agent-message sub-agent-assistant">${(m) => m.content}</div>
|
|
@@ -136,12 +140,61 @@ const liveSubAgentTraceTemplate = html `
|
|
|
136
140
|
${repeat((x) => x.liveSubAgentTrace.filter((m) => m.role !== 'user'), subAgentMessageRowTemplate)}
|
|
137
141
|
</div>
|
|
138
142
|
`;
|
|
143
|
+
// The interchangeable loading indicators. These MUST be stable module-level
|
|
144
|
+
// instances: the binding that selects between them reads `enabledAnimations`,
|
|
145
|
+
// which is backed by the redux store proxy and re-evaluates on every change to
|
|
146
|
+
// the aiAssistant slice (i.e. every new message, including hidden tool-call and
|
|
147
|
+
// thinking-step messages). Returning a fresh `html` instance from that binding
|
|
148
|
+
// would make FAST tear down and rebuild the indicator DOM each time, restarting
|
|
149
|
+
// the CSS animations and rAF loops. Stable references let FAST reuse the
|
|
150
|
+
// existing view so the animation runs uninterrupted.
|
|
151
|
+
const thinkingDotsTemplate = html `
|
|
152
|
+
<div class="thinking-dots">
|
|
153
|
+
<div class="dot dot-1"></div>
|
|
154
|
+
<div class="dot dot-2"></div>
|
|
155
|
+
<div class="dot dot-3"></div>
|
|
156
|
+
<div class="dot dot-4"></div>
|
|
157
|
+
</div>
|
|
158
|
+
`;
|
|
159
|
+
const thinkingWavesTemplate = html `
|
|
160
|
+
<div class="thinking-waves" part="thinking-waves">
|
|
161
|
+
<ai-waves-indicator></ai-waves-indicator>
|
|
162
|
+
<span class="thinking-caption">Thinking...</span>
|
|
163
|
+
</div>
|
|
164
|
+
`;
|
|
165
|
+
const thinkingFlowingWavesTemplate = html `
|
|
166
|
+
<div class="thinking-flowing-waves" part="thinking-flowing-waves">
|
|
167
|
+
<ai-flowing-waves-indicator></ai-flowing-waves-indicator>
|
|
168
|
+
<span class="thinking-caption">Thinking...</span>
|
|
169
|
+
</div>
|
|
170
|
+
`;
|
|
171
|
+
const thinkingPlasmaTemplate = html `
|
|
172
|
+
<div class="thinking-plasma" part="thinking-plasma">
|
|
173
|
+
<ai-plasma-orb-indicator></ai-plasma-orb-indicator>
|
|
174
|
+
<span class="thinking-caption">Thinking...</span>
|
|
175
|
+
</div>
|
|
176
|
+
`;
|
|
177
|
+
/**
|
|
178
|
+
* Picks the loading indicator for the currently enabled style, falling back to
|
|
179
|
+
* the dots (also the default when no animations config is supplied). Returns a
|
|
180
|
+
* stable template instance per style — see the note above.
|
|
181
|
+
*/
|
|
182
|
+
const selectThinkingTemplate = (x) => {
|
|
183
|
+
const enabled = x.enabledAnimations;
|
|
184
|
+
if (enabled.includes('waves'))
|
|
185
|
+
return thinkingWavesTemplate;
|
|
186
|
+
if (enabled.includes('flowingWaves'))
|
|
187
|
+
return thinkingFlowingWavesTemplate;
|
|
188
|
+
if (enabled.includes('plasma'))
|
|
189
|
+
return thinkingPlasmaTemplate;
|
|
190
|
+
return thinkingDotsTemplate;
|
|
191
|
+
};
|
|
139
192
|
// ─── Public factory ───────────────────────────────────────────────────────────
|
|
140
193
|
/** @internal */
|
|
141
194
|
export const FoundationAiAssistantTemplate = (designSystemPrefix) => {
|
|
142
195
|
const buttonTag = `${designSystemPrefix}-button`;
|
|
143
196
|
const switchTag = `${designSystemPrefix}-switch`;
|
|
144
|
-
const
|
|
197
|
+
const categorizedMultiselectTag = `${designSystemPrefix}-categorized-multiselect`;
|
|
145
198
|
const textareaTag = `${designSystemPrefix}-text-area`;
|
|
146
199
|
const iconTag = `${designSystemPrefix}-icon`;
|
|
147
200
|
const progressTag = `${designSystemPrefix}-progress`;
|
|
@@ -171,7 +224,9 @@ ${(tc) => { var _a; return (((_a = tc.foldPath) === null || _a === void 0 ? void
|
|
|
171
224
|
</div>
|
|
172
225
|
`)}
|
|
173
226
|
${when((m) => m.role !== 'system-event', html `
|
|
174
|
-
<div
|
|
227
|
+
<div
|
|
228
|
+
class="message-row ${(m) => messageType(m)} ${(m) => m.interaction ? `present-${interactionPresentation(m)}` : ''}"
|
|
229
|
+
>
|
|
175
230
|
<div class="avatar ${(m) => messageType(m)}">
|
|
176
231
|
${when(
|
|
177
232
|
// Keyed on messageType (not raw role) so a synthetic-user message,
|
|
@@ -180,8 +235,12 @@ ${(tc) => { var _a; return (((_a = tc.foldPath) === null || _a === void 0 ? void
|
|
|
180
235
|
(m) => messageType(m) === 'user', userAvatarTemplate)}${when((m) => messageType(m) !== 'user', assistantAvatarTemplate)}
|
|
181
236
|
</div>
|
|
182
237
|
<div class="message ${(m) => messageType(m)}">
|
|
183
|
-
|
|
184
|
-
|
|
238
|
+
${when(
|
|
239
|
+
// A 'bare' interaction owns its full presentation — suppress the
|
|
240
|
+
// host sender label. Every other message keeps it.
|
|
241
|
+
(m) => !(m.interaction && interactionPresentation(m) === 'bare'), html `
|
|
242
|
+
<div class="sender">
|
|
243
|
+
${(m, c) => {
|
|
185
244
|
var _a;
|
|
186
245
|
return messageType(m) === 'ai-function' &&
|
|
187
246
|
m.agentName &&
|
|
@@ -189,7 +248,8 @@ ${(tc) => { var _a; return (((_a = tc.foldPath) === null || _a === void 0 ? void
|
|
|
189
248
|
? `Tool Call · ${(_a = m.agentLabel) !== null && _a !== void 0 ? _a : m.agentName}`
|
|
190
249
|
: senderLabel[messageType(m)];
|
|
191
250
|
}}
|
|
192
|
-
|
|
251
|
+
</div>
|
|
252
|
+
`)}
|
|
193
253
|
<div class="content">
|
|
194
254
|
${when((m) => m.content, html `
|
|
195
255
|
<ai-chat-markdown :content="${(m) => m.content}"></ai-chat-markdown>
|
|
@@ -356,15 +416,13 @@ ${(tc) => { var _a; return (((_a = tc.foldPath) === null || _a === void 0 ? void
|
|
|
356
416
|
${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
417
|
<div class="settings-animations">
|
|
358
418
|
<span class="settings-label">Animations</span>
|
|
359
|
-
<${
|
|
419
|
+
<${categorizedMultiselectTag}
|
|
360
420
|
part="toggle-animations"
|
|
361
421
|
:selectedOptions=${(x) => x.enabledAnimations}
|
|
362
422
|
:options=${() => animationOptions}
|
|
363
|
-
:itemRenderer=${() => animationItemRenderer}
|
|
364
423
|
@selectionChange=${(x, c) => x.setEnabledAnimations(c.event.detail)}
|
|
365
424
|
search="false"
|
|
366
|
-
|
|
367
|
-
></${multiselectTag}>
|
|
425
|
+
></${categorizedMultiselectTag}>
|
|
368
426
|
</div>
|
|
369
427
|
`)}
|
|
370
428
|
</div>
|
|
@@ -453,7 +511,8 @@ ${(tc) => { var _a; return (((_a = tc.foldPath) === null || _a === void 0 ? void
|
|
|
453
511
|
${when((x) => {
|
|
454
512
|
var _a;
|
|
455
513
|
return x.showLoadingIndicator &&
|
|
456
|
-
(((_a = x.chatConfig.ui) === null || _a === void 0 ? void 0 : _a.animations) == null ||
|
|
514
|
+
(((_a = x.chatConfig.ui) === null || _a === void 0 ? void 0 : _a.animations) == null ||
|
|
515
|
+
LOADING_STYLE_ANIMATIONS.some((s) => x.enabledAnimations.includes(s)));
|
|
457
516
|
}, html `
|
|
458
517
|
<div class="message-row ai" part="thinking">
|
|
459
518
|
<div class="avatar">
|
|
@@ -465,12 +524,7 @@ ${(tc) => { var _a; return (((_a = tc.foldPath) === null || _a === void 0 ? void
|
|
|
465
524
|
<span class="avatar-icon" :innerHTML="${() => x.assistantIconSafe}"></span>
|
|
466
525
|
`}
|
|
467
526
|
</div>
|
|
468
|
-
|
|
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>
|
|
527
|
+
${(x) => selectThinkingTemplate(x)}
|
|
474
528
|
</div>
|
|
475
529
|
`)}
|
|
476
530
|
</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: '
|
|
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',
|
|
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
|
-
|
|
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.
|
|
4
|
+
"version": "14.460.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.
|
|
68
|
-
"@genesislcap/genx": "14.
|
|
69
|
-
"@genesislcap/rollup-builder": "14.
|
|
70
|
-
"@genesislcap/ts-builder": "14.
|
|
71
|
-
"@genesislcap/uvu-playwright-builder": "14.
|
|
72
|
-
"@genesislcap/vite-builder": "14.
|
|
73
|
-
"@genesislcap/webpack-builder": "14.
|
|
67
|
+
"@genesislcap/foundation-testing": "14.460.0",
|
|
68
|
+
"@genesislcap/genx": "14.460.0",
|
|
69
|
+
"@genesislcap/rollup-builder": "14.460.0",
|
|
70
|
+
"@genesislcap/ts-builder": "14.460.0",
|
|
71
|
+
"@genesislcap/uvu-playwright-builder": "14.460.0",
|
|
72
|
+
"@genesislcap/vite-builder": "14.460.0",
|
|
73
|
+
"@genesislcap/webpack-builder": "14.460.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.
|
|
79
|
-
"@genesislcap/foundation-logger": "14.
|
|
80
|
-
"@genesislcap/foundation-redux": "14.
|
|
81
|
-
"@genesislcap/foundation-ui": "14.
|
|
82
|
-
"@genesislcap/foundation-utils": "14.
|
|
83
|
-
"@genesislcap/rapid-design-system": "14.
|
|
84
|
-
"@genesislcap/web-core": "14.
|
|
78
|
+
"@genesislcap/foundation-ai": "14.460.0",
|
|
79
|
+
"@genesislcap/foundation-logger": "14.460.0",
|
|
80
|
+
"@genesislcap/foundation-redux": "14.460.0",
|
|
81
|
+
"@genesislcap/foundation-ui": "14.460.0",
|
|
82
|
+
"@genesislcap/foundation-utils": "14.460.0",
|
|
83
|
+
"@genesislcap/rapid-design-system": "14.460.0",
|
|
84
|
+
"@genesislcap/web-core": "14.460.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": "
|
|
96
|
+
"gitHead": "12d48ef6ec786b6fc0ad12bccb3682d0792e508c"
|
|
97
97
|
}
|