@genesislcap/ai-assistant 15.7.3-alpha-e7d4aa5.0 → 15.8.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 +191 -1
- package/dist/ai-assistant.d.ts +155 -1
- package/dist/custom-elements.json +114 -2
- package/dist/dts/components/settings-modal/settings-modal.styles.d.ts.map +1 -1
- package/dist/dts/components/settings-modal/settings-modal.styles.test.d.ts +2 -0
- package/dist/dts/components/settings-modal/settings-modal.styles.test.d.ts.map +1 -0
- package/dist/dts/components/settings-modal/settings-modal.template.d.ts +22 -0
- package/dist/dts/components/settings-modal/settings-modal.template.d.ts.map +1 -1
- package/dist/dts/components/settings-modal/settings-modal.template.test.d.ts +2 -0
- package/dist/dts/components/settings-modal/settings-modal.template.test.d.ts.map +1 -0
- package/dist/dts/main/budget-meter.test.d.ts +2 -0
- package/dist/dts/main/budget-meter.test.d.ts.map +1 -0
- package/dist/dts/main/main.d.ts +113 -2
- package/dist/dts/main/main.d.ts.map +1 -1
- package/dist/dts/main/main.types.d.ts +25 -0
- package/dist/dts/main/main.types.d.ts.map +1 -1
- package/dist/dts/state/ai-assistant-slice.d.ts +47 -0
- package/dist/dts/state/ai-assistant-slice.d.ts.map +1 -1
- package/dist/dts/state/session-store.d.ts +4 -0
- package/dist/dts/state/session-store.d.ts.map +1 -1
- package/dist/dts/utils/format-usd.d.ts +24 -0
- package/dist/dts/utils/format-usd.d.ts.map +1 -0
- package/dist/esm/components/settings-modal/settings-modal.styles.js +33 -3
- package/dist/esm/components/settings-modal/settings-modal.styles.test.js +80 -0
- package/dist/esm/components/settings-modal/settings-modal.template.js +64 -14
- package/dist/esm/components/settings-modal/settings-modal.template.test.js +91 -0
- package/dist/esm/main/budget-meter.test.js +317 -0
- package/dist/esm/main/main.js +189 -3
- package/dist/esm/state/ai-assistant-slice.js +38 -0
- package/dist/esm/utils/format-usd.js +23 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/docs/migration-GENC-1464.md +63 -0
- package/docs/styling.md +23 -0
- package/package.json +17 -17
- package/src/components/settings-modal/settings-modal.styles.test.ts +94 -0
- package/src/components/settings-modal/settings-modal.styles.ts +33 -3
- package/src/components/settings-modal/settings-modal.template.test.ts +124 -0
- package/src/components/settings-modal/settings-modal.template.ts +82 -13
- package/src/main/budget-meter.test.ts +427 -0
- package/src/main/main.ts +198 -3
- package/src/main/main.types.ts +26 -0
- package/src/state/ai-assistant-slice.ts +74 -0
- package/src/utils/format-usd.ts +26 -0
|
@@ -17,6 +17,22 @@ export interface InputOverride {
|
|
|
17
17
|
/** The mode requested for this invocation. */
|
|
18
18
|
mode: ChatInputDuringExecutionMode;
|
|
19
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* One vendor's AI-spend figures, as fed by the host from the budget endpoint
|
|
22
|
+
* (GENC-1464 — the settings-modal budget meter).
|
|
23
|
+
*
|
|
24
|
+
* Dollars, not a percentage: the meter's text shows the real figures (spend CAN
|
|
25
|
+
* exceed the cap — the last allowed call may overshoot it), and only the bar
|
|
26
|
+
* clamps. Storing a pre-computed percentage would destroy the overshoot.
|
|
27
|
+
*
|
|
28
|
+
* @beta
|
|
29
|
+
*/
|
|
30
|
+
export interface VendorBudgetFigures {
|
|
31
|
+
/** The vendor's budget cap in USD. Finite and `>= 0`. */
|
|
32
|
+
budgetUsd: number;
|
|
33
|
+
/** Metered spend against that cap in USD. Finite and `>= 0`; may exceed the cap. */
|
|
34
|
+
spentUsd: number;
|
|
35
|
+
}
|
|
20
36
|
/**
|
|
21
37
|
* Shape of a single AI assistant session's serializable state.
|
|
22
38
|
*
|
|
@@ -172,6 +188,20 @@ export interface AiAssistantSessionState {
|
|
|
172
188
|
* copy composed from the vendor's display name.
|
|
173
189
|
*/
|
|
174
190
|
blockedVendorReasons: Partial<Record<AIProviderType, string>>;
|
|
191
|
+
/**
|
|
192
|
+
* Per-vendor AI-spend figures for the settings-modal budget meter (GENC-1464),
|
|
193
|
+
* fed by the host via the element's `setVendorBudget`. A vendor with no entry
|
|
194
|
+
* renders no meter row — which is also how "unlimited" is expressed: the host
|
|
195
|
+
* simply never feeds a vendor the server marks unlimited.
|
|
196
|
+
*
|
|
197
|
+
* NOT persisted to the session snapshot, deliberately. The server is
|
|
198
|
+
* authoritative for spend and the host re-feeds on boot (and after each turn),
|
|
199
|
+
* so a persisted copy could only ever show stale dollars across a reload —
|
|
200
|
+
* wrong money is worse than a beat of no meter. The snapshot assembler
|
|
201
|
+
* (`session-snapshot.ts`) builds from an explicit field list, so absence is
|
|
202
|
+
* structural, not an accident of serialization.
|
|
203
|
+
*/
|
|
204
|
+
vendorBudgets: Partial<Record<AIProviderType, VendorBudgetFigures>>;
|
|
175
205
|
/** Draft text in the input box — preserved across pop-in/pop-out cycles. */
|
|
176
206
|
inputValue: string;
|
|
177
207
|
/** Live trace from a currently-executing sub-agent. Cleared on sub-agent-stop. */
|
|
@@ -295,6 +325,23 @@ export declare const aiAssistantSlice: import("@reduxjs/toolkit").Slice<AiAssist
|
|
|
295
325
|
*/
|
|
296
326
|
swept?: boolean;
|
|
297
327
|
}>): void;
|
|
328
|
+
/**
|
|
329
|
+
* Store (or clear, with `null`) one vendor's budget figures for the meter.
|
|
330
|
+
*
|
|
331
|
+
* Validation lives at the element boundary (`setVendorBudget` on the
|
|
332
|
+
* element), not here — the reducer is the storage layer and trusts its one
|
|
333
|
+
* caller, exactly as `setVendorBlocked` above trusts its.
|
|
334
|
+
*
|
|
335
|
+
* Idempotent by VALUE, not just by effect: re-feeding the figures a vendor
|
|
336
|
+
* already holds leaves the stored object untouched. The documented feed
|
|
337
|
+
* re-runs after every turn, so without this every turn would publish a new
|
|
338
|
+
* store reference for the same dollars and re-render a meter that has not
|
|
339
|
+
* changed.
|
|
340
|
+
*/
|
|
341
|
+
setVendorBudget(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<{
|
|
342
|
+
vendor: AIProviderType;
|
|
343
|
+
figures: VendorBudgetFigures | null;
|
|
344
|
+
}>): void;
|
|
298
345
|
setInputValue(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<string>): void;
|
|
299
346
|
setLiveSubAgentTrace(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<ChatMessage[]>): void;
|
|
300
347
|
setLiveSubAgentName(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<string | null>): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-assistant-slice.d.ts","sourceRoot":"","sources":["../../../src/state/ai-assistant-slice.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"ai-assistant-slice.d.ts","sourceRoot":"","sources":["../../../src/state/ai-assistant-slice.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,6BAA6B,EAC7B,cAAc,EACd,4BAA4B,EAC5B,WAAW,EACZ,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAEnE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAGnG;;;;;;;;GAQG;AACH,MAAM,WAAW,aAAa;IAC5B,mEAAmE;IACnE,EAAE,EAAE,MAAM,CAAC;IACX,8CAA8C;IAC9C,IAAI,EAAE,4BAA4B,CAAC;CACpC;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,mBAAmB;IAClC,yDAAyD;IACzD,SAAS,EAAE,MAAM,CAAC;IAClB,oFAAoF;IACpF,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;GAIG;AACH,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,KAAK,EAAE,gBAAgB,CAAC;IACxB,aAAa,EAAE,OAAO,CAAC;IACvB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,aAAa,EAAE,OAAO,CAAC;IACvB,wBAAwB,EAAE,OAAO,CAAC;IAClC,iBAAiB,EAAE,oBAAoB,EAAE,CAAC;IAC1C,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC;;;;;;;OAOG;IACH,YAAY,EAAE,cAAc,CAAC;IAC7B,uEAAuE;IACvE,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC;;;OAGG;IACH,kBAAkB,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC;;;;;;;;OAQG;IACH,gBAAgB,EAAE,6BAA6B,EAAE,CAAC;IAClD,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,SAAS,CAAC;IAC3D;;;;;;OAMG;IACH,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B;;;;;;;OAOG;IACH,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC;;;;;;;OAOG;IACH,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC;;;;OAIG;IACH,eAAe,EAAE,OAAO,CAAC;IACzB;;;OAGG;IACH,eAAe,EAAE,OAAO,CAAC;IACzB;;;;OAIG;IACH,UAAU,EAAE,OAAO,CAAC;IACpB;;;;;;;OAOG;IACH,SAAS,EAAE,OAAO,CAAC;IACnB;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB;;;;OAIG;IACH,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B;;;;;;;;;;;;OAYG;IACH,cAAc,EAAE,cAAc,EAAE,CAAC;IACjC;;;;;;;;;OASG;IACH,YAAY,EAAE,cAAc,EAAE,CAAC;IAC/B;;;;OAIG;IACH,oBAAoB,EAAE,OAAO,CAAC,MAAM,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC,CAAC;IAC9D;;;;;;;;;;;;OAYG;IACH,aAAa,EAAE,OAAO,CAAC,MAAM,CAAC,cAAc,EAAE,mBAAmB,CAAC,CAAC,CAAC;IACpE,4EAA4E;IAC5E,UAAU,EAAE,MAAM,CAAC;IACnB,kFAAkF;IAClF,iBAAiB,EAAE,WAAW,EAAE,CAAC;IACjC,oEAAoE;IACpE,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC;;;;;OAKG;IACH,cAAc,EAAE,aAAa,EAAE,CAAC;CACjC;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,kBAAkB,EAAE,MAAM,GAAG,SAAS,CAAC;CACxC;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,IAAI,uBAAuB,CAmCnE;AAED,eAAO,MAAM,mBAAmB,EAAE,uBAAqD,CAAC;AAExF,eAAO,MAAM,gBAAgB;uFAIE,aAAa,CAAC,WAAW,EAAE,CAAC;oFAG/B,aAAa,CAAC,gBAAgB,CAAC;4FAGvB,aAAa,CAAC,OAAO,CAAC;gGAGlB,aAAa,CAAC,OAAO,CAAC;4FAG1B,aAAa,CAAC,OAAO,CAAC;uGAGX,aAAa,CAAC,OAAO,CAAC;gGAG7B,aAAa,CAAC,oBAAoB,EAAE,CAAC;+FAGtC,aAAa,CAAC,gBAAgB,CAAC;4FAGlC,aAAa,CAAC,MAAM,GAAG,SAAS,CAAC;2FAGlC,aAAa,CAAC,MAAM,GAAG,SAAS,CAAC;2FAGjC,aAAa,CAAC,cAAc,CAAC;0FAG9B,aAAa,CAAC,MAAM,GAAG,SAAS,CAAC;iGAG1B,aAAa,CAAC,MAAM,GAAG,SAAS,CAAC;+FAGnC,aAAa,CAAC,6BAA6B,EAAE,CAAC;0FAGnD,aAAa,CAAC,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,SAAS,CAAC;8FAGxD,aAAa,CAAC,MAAM,GAAG,IAAI,CAAC;iGAGzB,aAAa,CAAC,MAAM,GAAG,IAAI,CAAC;mGAG1B,aAAa,CAAC,MAAM,GAAG,IAAI,CAAC;8FAGjC,aAAa,CAAC,OAAO,CAAC;8FAGtB,aAAa,CAAC,OAAO,CAAC;yFAG3B,aAAa,CAAC,OAAO,CAAC;wFAGvB,aAAa,CAAC,OAAO,CAAC;IAGlD;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;sFACuB,aAAa,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IAWrF;;;;;;;;OAQG;4FAC6B,aAAa,CAAC,MAAM,GAAG,IAAI,CAAC;IAG5D;;;;;;;;;OASG;4FAGO,aAAa,CAAC;QACpB,MAAM,EAAE,cAAc,CAAC;QACvB,OAAO,EAAE,OAAO,CAAC;QACjB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACvB;;;;;;WAMG;QACH,KAAK,CAAC,EAAE,OAAO,CAAC;KACjB,CAAC;IA2BJ;;;;;;;;;;;;OAYG;2FAGO,aAAa,CAAC;QAAE,MAAM,EAAE,cAAc,CAAC;QAAC,OAAO,EAAE,mBAAmB,GAAG,IAAI,CAAA;KAAE,CAAC;yFAoB3D,aAAa,CAAC,MAAM,CAAC;gGAGd,aAAa,CAAC,WAAW,EAAE,CAAC;+FAG7B,aAAa,CAAC,MAAM,GAAG,IAAI,CAAC;4FAG/B,aAAa,CAAC,aAAa,CAAC;+FAGzB,aAAa,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IAGhE;;;;;OAKG;uFACwB,aAAa,CAAC,kBAAkB,CAAC;IAkB5D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAqCG;;oCA+BL,CAAC"}
|
|
@@ -34,6 +34,10 @@ declare function makeStore(devTools: boolean | undefined): import("@genesislcap/
|
|
|
34
34
|
reason?: string | null;
|
|
35
35
|
swept?: boolean;
|
|
36
36
|
}>): void;
|
|
37
|
+
setVendorBudget(state: import("immer").WritableDraft<AiAssistantSessionState>, action: import("@reduxjs/toolkit").PayloadAction<{
|
|
38
|
+
vendor: import("@genesislcap/foundation-ai").AIProviderType;
|
|
39
|
+
figures: import("./ai-assistant-slice").VendorBudgetFigures | null;
|
|
40
|
+
}>): void;
|
|
37
41
|
setInputValue(state: import("immer").WritableDraft<AiAssistantSessionState>, action: import("@reduxjs/toolkit").PayloadAction<string>): void;
|
|
38
42
|
setLiveSubAgentTrace(state: import("immer").WritableDraft<AiAssistantSessionState>, action: import("@reduxjs/toolkit").PayloadAction<import("@genesislcap/foundation-ai").ChatMessage[]>): void;
|
|
39
43
|
setLiveSubAgentName(state: import("immer").WritableDraft<AiAssistantSessionState>, action: import("@reduxjs/toolkit").PayloadAction<string | null>): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../../../src/state/session-store.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,uBAAuB,EAC7B,MAAM,sBAAsB,CAAC;AAE9B,KAAK,kBAAkB,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAIvD,iBAAS,SAAS,CAAC,QAAQ,EAAE,OAAO,GAAG,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../../../src/state/session-store.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,uBAAuB,EAC7B,MAAM,sBAAsB,CAAC;AAE9B,KAAK,kBAAkB,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAIvD,iBAAS,SAAS,CAAC,QAAQ,EAAE,OAAO,GAAG,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;cA6Cysd,CAAC;;;;;;cAAggD,CAAC;aAAob,CAAC;;;;;;;;;;;;;;;wCAvC/qhB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,GAAG,kBAAkB,CAMnF;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEpD;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C;AAED,YAAY,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ONE place this package renders a USD amount as text.
|
|
3
|
+
*
|
|
4
|
+
* Extracted (GENC-1464 budget meter) from the ad-hoc `.toFixed(2)` call sites
|
|
5
|
+
* that had grown in `formatBlockedReason` and the settings modal's cost blocks,
|
|
6
|
+
* so the blocked banner, the session-cost figures and the budget meter cannot
|
|
7
|
+
* drift to different precisions. Two decimals always — money columns jitter
|
|
8
|
+
* when "$5.1" sits above "$5.10".
|
|
9
|
+
*
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
/** Decimal places for every USD figure this package renders. */
|
|
13
|
+
export declare const USD_DECIMALS = 2;
|
|
14
|
+
/**
|
|
15
|
+
* The bare amount, no currency sign — `(12.3) => "12.30"`.
|
|
16
|
+
*
|
|
17
|
+
* Exists for the settings modal's cost blocks, where the `$` is a separately
|
|
18
|
+
* styled `<span class="prefix">` and folding it into the text would change the
|
|
19
|
+
* DOM those styles target.
|
|
20
|
+
*/
|
|
21
|
+
export declare const formatUsdAmount: (value: number) => string;
|
|
22
|
+
/** A signed USD figure — `(12.3) => "$12.30"`. */
|
|
23
|
+
export declare const formatUsd: (value: number) => string;
|
|
24
|
+
//# sourceMappingURL=format-usd.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-usd.d.ts","sourceRoot":"","sources":["../../../src/utils/format-usd.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,gEAAgE;AAChE,eAAO,MAAM,YAAY,IAAI,CAAC;AAE9B;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,GAAI,OAAO,MAAM,KAAG,MAAqC,CAAC;AAEtF,kDAAkD;AAClD,eAAO,MAAM,SAAS,GAAI,OAAO,MAAM,KAAG,MAAsC,CAAC"}
|
|
@@ -274,7 +274,17 @@ export const settingsModalStyles = css `
|
|
|
274
274
|
font-style: italic;
|
|
275
275
|
}
|
|
276
276
|
|
|
277
|
-
|
|
277
|
+
/*
|
|
278
|
+
* The budget meter (GENC-1464) is the context indicator's money sibling and
|
|
279
|
+
* shares its row anatomy 1:1, so the budget classes JOIN these rules rather
|
|
280
|
+
* than restating them — one place to restyle the header/label/figures shape,
|
|
281
|
+
* and the two indicators cannot drift apart. Design-system tokens only, and
|
|
282
|
+
* unfallbacked: every token here ships a DS default, so a var(--x, y)
|
|
283
|
+
* fallback would be dead code that hides a broken token name (#2411/#2436
|
|
284
|
+
* review rule, pinned by settings-modal.styles.test.ts).
|
|
285
|
+
*/
|
|
286
|
+
.settings-context-header,
|
|
287
|
+
.settings-budget-header {
|
|
278
288
|
display: flex;
|
|
279
289
|
justify-content: space-between;
|
|
280
290
|
gap: calc(var(--design-unit) * 2px);
|
|
@@ -282,7 +292,8 @@ export const settingsModalStyles = css `
|
|
|
282
292
|
font-size: 12px;
|
|
283
293
|
}
|
|
284
294
|
|
|
285
|
-
.settings-context-label
|
|
295
|
+
.settings-context-label,
|
|
296
|
+
.settings-budget-label {
|
|
286
297
|
color: var(--neutral-foreground-hint);
|
|
287
298
|
font-size: 11px;
|
|
288
299
|
font-weight: 600;
|
|
@@ -290,10 +301,29 @@ export const settingsModalStyles = css `
|
|
|
290
301
|
letter-spacing: 0.06em;
|
|
291
302
|
}
|
|
292
303
|
|
|
293
|
-
.settings-context-tokens
|
|
304
|
+
.settings-context-tokens,
|
|
305
|
+
.settings-budget-figures {
|
|
294
306
|
color: var(--neutral-foreground-rest);
|
|
295
307
|
}
|
|
296
308
|
|
|
309
|
+
/*
|
|
310
|
+
* ONE rhythm for every meter row. The outer wrapper holds the context
|
|
311
|
+
* indicator and the budget meter; the inner one holds the budget meter's
|
|
312
|
+
* vendor rows — and both gap by the same 2 units (half the section body's 4)
|
|
313
|
+
* so that visually identical meter rows are evenly spaced whatever mix of
|
|
314
|
+
* them is on screen. Without the wrapper the context row inherits the body's
|
|
315
|
+
* 4-unit gap while the vendor rows keep 2, and three identical-looking rows
|
|
316
|
+
* read as an uneven 4/2/2. The wrapper renders only when at least one meter
|
|
317
|
+
* does — see settingsUsageMetersTemplate for why an always-on one would cost
|
|
318
|
+
* a stray gap.
|
|
319
|
+
*/
|
|
320
|
+
.settings-usage-meters,
|
|
321
|
+
.settings-budget-usage {
|
|
322
|
+
display: flex;
|
|
323
|
+
flex-direction: column;
|
|
324
|
+
gap: calc(var(--design-unit) * 2px);
|
|
325
|
+
}
|
|
326
|
+
|
|
297
327
|
.settings-current-build,
|
|
298
328
|
.settings-lifetime-usage {
|
|
299
329
|
background: var(--neutral-layer-2);
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
var _a;
|
|
2
|
+
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
|
|
3
|
+
import { settingsModalStyles } from './settings-modal.styles';
|
|
4
|
+
// GENC-1464 budget meter — the styling invariants a reviewer would otherwise
|
|
5
|
+
// have to catch by eye, following main.styles.test.ts (see there for why CSS
|
|
6
|
+
// gets asserted from the generated text at all, and for the scoping lesson:
|
|
7
|
+
// these assertions are about the BUDGET rules only, not a global style policy).
|
|
8
|
+
//
|
|
9
|
+
// 1. Design-system tokens are consumed UNFALLBACKED — the #2411/#2436 review
|
|
10
|
+
// rule. Every token the meter reads ships a DS default, so a
|
|
11
|
+
// `var(--token, fallback)` is dead code at best and a silently-broken
|
|
12
|
+
// token name at worst.
|
|
13
|
+
// 2. The meter's row anatomy is SHARED with the context indicator via grouped
|
|
14
|
+
// selectors, not copied. The two indicators are siblings by design;
|
|
15
|
+
// copied declarations would let them drift apart one edit at a time, which
|
|
16
|
+
// is exactly what the grouping was chosen to make impossible.
|
|
17
|
+
const suite = createLogicSuite('settings-modal.styles budget-meter rules');
|
|
18
|
+
/**
|
|
19
|
+
* The generated stylesheet text, flattened recursively — `css` composes nested
|
|
20
|
+
* `ElementStyles`, and a shallow read can return almost nothing, making every
|
|
21
|
+
* negative assertion below pass vacuously. The first test guards that.
|
|
22
|
+
*/
|
|
23
|
+
const flatten = (value) => {
|
|
24
|
+
if (typeof value === 'string')
|
|
25
|
+
return value;
|
|
26
|
+
if (Array.isArray(value))
|
|
27
|
+
return value.map(flatten).join('\n');
|
|
28
|
+
const nested = value === null || value === void 0 ? void 0 : value.styles;
|
|
29
|
+
return nested ? flatten(nested) : '';
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Comments stripped before matching: the assertions below are about
|
|
33
|
+
* DECLARATIONS, and the sheet's own prose legitimately mentions the very
|
|
34
|
+
* pattern (`var(--x, y)`) the negative assertion hunts for.
|
|
35
|
+
*/
|
|
36
|
+
const cssText = flatten(settingsModalStyles).replace(/\/\*[\s\S]*?\*\//g, '');
|
|
37
|
+
/** Every declaration block whose selector mentions `.settings-budget-`, joined. */
|
|
38
|
+
const budgetRules = ((_a = cssText.match(/[^{}]*\.settings-budget-[^{}]*\{[^}]*\}/g)) !== null && _a !== void 0 ? _a : []).join('\n');
|
|
39
|
+
suite('the budget rules were extracted as text (guards the assertions below)', () => {
|
|
40
|
+
assert.ok(cssText.includes('.settings-budget-usage'), 'the meter container rule is in the sheet');
|
|
41
|
+
assert.ok(budgetRules.length > 0, 'the extraction found the budget rules');
|
|
42
|
+
});
|
|
43
|
+
suite('the meter consumes design-system tokens without dead fallbacks', () => {
|
|
44
|
+
// One structural probe rather than a per-token list: ANY `var(--x,` inside a
|
|
45
|
+
// budget rule is a fallback, and the meter reads only tokens that ship DS
|
|
46
|
+
// defaults (--design-unit, --neutral-foreground-*), so there is no legitimate
|
|
47
|
+
// fallback for this scope. Scoped to the budget rules on purpose — see
|
|
48
|
+
// main.styles.test.ts for why a whole-sheet grep is the wrong test.
|
|
49
|
+
assert.not.ok(/var\(--[a-z-]+\s*,/.test(budgetRules), 'no var(--token, fallback) in the meter');
|
|
50
|
+
assert.ok(budgetRules.includes('var(--design-unit)'), 'and it does consume tokens');
|
|
51
|
+
});
|
|
52
|
+
suite('every meter row sits on one shared rhythm', () => {
|
|
53
|
+
// The 4/2/2 fix. `.settings-modal-section-body` gaps at 4 units and the
|
|
54
|
+
// budget meter at 2, so an unwrapped context indicator sat 4 units above the
|
|
55
|
+
// FIRST vendor row while the vendor rows sat 2 apart — three visually
|
|
56
|
+
// identical meter rows, unevenly spaced. Both meters now live in
|
|
57
|
+
// `.settings-usage-meters`, which must carry the SAME gap as the meter it
|
|
58
|
+
// wraps: asserted as ONE grouped rule, so the two cannot drift to different
|
|
59
|
+
// rhythms. (settings-modal.template.test.ts pins the other half — that the
|
|
60
|
+
// wrapper is conditional, so a host with no meters pays no stray gap for it.)
|
|
61
|
+
const shared = /\.settings-usage-meters\s*,\s*\.settings-budget-usage\s*\{([^}]*)\}/.exec(cssText);
|
|
62
|
+
assert.ok(shared, '.settings-usage-meters is grouped with .settings-budget-usage');
|
|
63
|
+
assert.ok(shared[1].includes('flex-direction: column'), 'the shared container is a column');
|
|
64
|
+
assert.ok(shared[1].includes('gap: calc(var(--design-unit) * 2px)'), 'gapped at the meter rhythm (2 units), not the section body 4');
|
|
65
|
+
});
|
|
66
|
+
suite("the meter shares the context indicator's row anatomy by grouped selector", () => {
|
|
67
|
+
// The invariant is the SHARING, not the shape: each budget class must sit in
|
|
68
|
+
// the same selector list as its context sibling, so restyling one restyles
|
|
69
|
+
// both and the two indicators cannot drift apart.
|
|
70
|
+
const sharedPairs = [
|
|
71
|
+
['.settings-context-header', '.settings-budget-header'],
|
|
72
|
+
['.settings-context-label', '.settings-budget-label'],
|
|
73
|
+
['.settings-context-tokens', '.settings-budget-figures'],
|
|
74
|
+
];
|
|
75
|
+
for (const [contextClass, budgetClass] of sharedPairs) {
|
|
76
|
+
const grouped = new RegExp(`${contextClass.replace(/\./g, '\\.')}\\s*,\\s*${budgetClass.replace(/\./g, '\\.')}\\s*\\{`);
|
|
77
|
+
assert.ok(grouped.test(cssText), `${budgetClass} is grouped with ${contextClass}`);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
suite.run();
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { html, ref, repeat, slotted, when } from '@genesislcap/web-core';
|
|
2
|
-
import { ANIMATION_DEFS } from '../../main/main.types';
|
|
2
|
+
import { ANIMATION_DEFS, } from '../../main/main.types';
|
|
3
3
|
import { formatCostSessionDate, } from '../../utils/cost-session-history';
|
|
4
|
+
import { formatUsd, formatUsdAmount } from '../../utils/format-usd';
|
|
4
5
|
import { formatCompactTokenCountSafe } from '../../utils/sum-tokens';
|
|
5
|
-
const SESSION_COST_DECIMALS = 2;
|
|
6
6
|
/** Animation options grouped by {@link ANIMATION_DEFS} category for the settings list. */
|
|
7
7
|
const animationCategoryGroups = (() => {
|
|
8
8
|
var _a;
|
|
@@ -92,12 +92,11 @@ const chatBotToggleRow = (title, description, switchTag, part, checked, onChange
|
|
|
92
92
|
></${switchTag}>
|
|
93
93
|
`);
|
|
94
94
|
const platformContextUsageTemplate = (progressTag) => html `
|
|
95
|
-
${when(
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}, html `
|
|
95
|
+
${when(
|
|
96
|
+
// The getter, not a copy of its expression: `settingsUsageMetersTemplate`
|
|
97
|
+
// gates its shared wrapper on this same getter, and the two must agree —
|
|
98
|
+
// a drifted copy would leave the wrapper rendering around nothing.
|
|
99
|
+
(x) => x.settingsContextUsageVisible, html `
|
|
101
100
|
<div class="settings-context-usage" part="context-usage">
|
|
102
101
|
<div class="settings-context-header">
|
|
103
102
|
<span class="settings-context-label">Context window</span>
|
|
@@ -114,6 +113,59 @@ const platformContextUsageTemplate = (progressTag) => html `
|
|
|
114
113
|
</div>
|
|
115
114
|
`)}
|
|
116
115
|
`;
|
|
116
|
+
/**
|
|
117
|
+
* Per-vendor AI budget meter (GENC-1464) — the money sibling of
|
|
118
|
+
* {@link platformContextUsageTemplate}, one row per vendor the host has fed
|
|
119
|
+
* figures for. The row text is unclamped (overshoot is real and shown); only
|
|
120
|
+
* the bar clamps — both computed in `settingsBudgetRows`, so the bindings here
|
|
121
|
+
* stay pure reads and the arithmetic is unit-testable.
|
|
122
|
+
*/
|
|
123
|
+
const platformBudgetUsageTemplate = (progressTag) => html `
|
|
124
|
+
${when((x) => x.settingsBudgetUsageVisible, html `
|
|
125
|
+
<div class="settings-budget-usage" part="budget-usage">
|
|
126
|
+
${repeat((x) => x.settingsBudgetRows, html `
|
|
127
|
+
<div class="settings-budget-vendor">
|
|
128
|
+
<div class="settings-budget-header">
|
|
129
|
+
<span class="settings-budget-label">${(row) => row.label} budget</span>
|
|
130
|
+
<span class="settings-budget-figures">${(row) => row.figures}</span>
|
|
131
|
+
</div>
|
|
132
|
+
<${progressTag}
|
|
133
|
+
part="budget-progress"
|
|
134
|
+
value="${(row) => row.barValue}"
|
|
135
|
+
></${progressTag}>
|
|
136
|
+
</div>
|
|
137
|
+
`)}
|
|
138
|
+
</div>
|
|
139
|
+
`)}
|
|
140
|
+
`;
|
|
141
|
+
/**
|
|
142
|
+
* The two usage meters in one shared, evenly-gapped column.
|
|
143
|
+
*
|
|
144
|
+
* Without it the context row and the FIRST budget row sit the section body's
|
|
145
|
+
* 4 units apart while the budget rows sit 2 apart — three visually identical
|
|
146
|
+
* meter rows reading as an uneven 4/2/2. The wrapper puts every meter row on
|
|
147
|
+
* the same 2-unit rhythm; see `.settings-usage-meters` in the styles.
|
|
148
|
+
*
|
|
149
|
+
* CONDITIONAL on purpose. An always-present wrapper is a permanent flex child
|
|
150
|
+
* of `.settings-modal-section-body`, so a host with neither meter would get a
|
|
151
|
+
* stray 4-unit gap after its model slot from a div that renders nothing. The
|
|
152
|
+
* gate is exactly the disjunction of the two children's own gates — both of
|
|
153
|
+
* which are `when`, which emplaces only comment markers when false, so a
|
|
154
|
+
* hidden meter contributes no flex child inside the wrapper either.
|
|
155
|
+
*
|
|
156
|
+
* Exported for `settings-modal.template.test.ts`, which renders it in isolation
|
|
157
|
+
* to pin both of those properties; `index.ts` does not re-export it, so it is
|
|
158
|
+
* not package API.
|
|
159
|
+
*
|
|
160
|
+
* @internal
|
|
161
|
+
*/
|
|
162
|
+
export const settingsUsageMetersTemplate = (progressTag) => html `
|
|
163
|
+
${when((x) => x.settingsContextUsageVisible || x.settingsBudgetUsageVisible, html `
|
|
164
|
+
<div class="settings-usage-meters">
|
|
165
|
+
${platformContextUsageTemplate(progressTag)} ${platformBudgetUsageTemplate(progressTag)}
|
|
166
|
+
</div>
|
|
167
|
+
`)}
|
|
168
|
+
`;
|
|
117
169
|
const chatBotSettingsTemplate = (switchTag, buttonTag) => html `
|
|
118
170
|
${when((x) => { var _a; return ((_a = x.chatConfig.ui) === null || _a === void 0 ? void 0 : _a.showToolCalls) != null; }, chatBotToggleRow('Show tool calls', "Display each tool the chatbot invokes — file reads, searches, edits and commands — inline as it works, so you can follow exactly what it's doing behind the scenes.", switchTag, 'toggle-tool-calls', (x) => x.showToolCalls, (x, c) => {
|
|
119
171
|
x.showToolCalls = readSwitchChecked(c.event);
|
|
@@ -306,9 +358,7 @@ const lifetimeUsageTemplate = () => html `
|
|
|
306
358
|
<div class="settings-build-stat-label">Total build cost</div>
|
|
307
359
|
<div class="settings-build-stat-value">
|
|
308
360
|
<span class="prefix">$</span>
|
|
309
|
-
<span class="value">
|
|
310
|
-
${(x) => x.cumulativeCostUsd.toFixed(SESSION_COST_DECIMALS)}
|
|
311
|
-
</span>
|
|
361
|
+
<span class="value">${(x) => formatUsdAmount(x.cumulativeCostUsd)}</span>
|
|
312
362
|
</div>
|
|
313
363
|
</div>
|
|
314
364
|
<div
|
|
@@ -350,7 +400,7 @@ const sessionCostSummaryTemplate = () => html `
|
|
|
350
400
|
</div>
|
|
351
401
|
<div class="settings-build-stat-value">
|
|
352
402
|
<span class="prefix">$</span>
|
|
353
|
-
<span class="value">${(x) => x.sessionCostUsd
|
|
403
|
+
<span class="value">${(x) => formatUsdAmount(x.sessionCostUsd)}</span>
|
|
354
404
|
</div>
|
|
355
405
|
</div>
|
|
356
406
|
<div
|
|
@@ -394,7 +444,7 @@ const costSessionHistoryTemplate = (iconTag, buttonTag) => html `
|
|
|
394
444
|
<div class="settings-cost-history-row-right">
|
|
395
445
|
<div class="settings-cost-history-row-metrics">
|
|
396
446
|
<div class="settings-cost-history-row-cost">
|
|
397
|
-
|
|
447
|
+
${(record) => formatUsd(record.usage.costUsd)}
|
|
398
448
|
</div>
|
|
399
449
|
</div>
|
|
400
450
|
<${buttonTag}
|
|
@@ -461,7 +511,7 @@ export const SettingsModalTemplate = (designSystemPrefix) => {
|
|
|
461
511
|
</h3>
|
|
462
512
|
<div class="settings-modal-section-body">
|
|
463
513
|
<slot name="settings-model" ${slotted('settingsModelSlotted')}></slot>
|
|
464
|
-
${
|
|
514
|
+
${settingsUsageMetersTemplate(progressTag)}
|
|
465
515
|
</div>
|
|
466
516
|
</section>
|
|
467
517
|
<!--
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
|
|
2
|
+
import { FoundationAiAssistant } from '../../main/main';
|
|
3
|
+
import { getSessionStore, clearAllSessionStores } from '../../state/session-store';
|
|
4
|
+
import { settingsUsageMetersTemplate } from './settings-modal.template';
|
|
5
|
+
// Hold a reference so the custom-element registration isn't tree-shaken.
|
|
6
|
+
FoundationAiAssistant;
|
|
7
|
+
// GENC-1464 — the shared meter column in the settings modal's "AI Model
|
|
8
|
+
// Settings" section.
|
|
9
|
+
//
|
|
10
|
+
// The section body is a 4-unit flex column and the budget meter is a 2-unit
|
|
11
|
+
// one, so an unwrapped context indicator sat 4 units above the first vendor row
|
|
12
|
+
// while the vendor rows sat 2 apart — three visually identical meter rows
|
|
13
|
+
// reading as an uneven 4/2/2. Both meters now share ONE 2-unit column
|
|
14
|
+
// (`.settings-usage-meters`, grouped with `.settings-budget-usage` in the
|
|
15
|
+
// styles — see settings-modal.styles.test.ts for the gap itself).
|
|
16
|
+
//
|
|
17
|
+
// The wrapper must also be ABSENT when neither meter shows: a permanent wrapper
|
|
18
|
+
// is a permanent flex child of the section body, so a host with no meters would
|
|
19
|
+
// pay a stray 4-unit gap after its model slot for a div that renders nothing.
|
|
20
|
+
// That is a structural claim about the template, so this suite renders the
|
|
21
|
+
// template rather than reading a getter — the getter cannot show whether the
|
|
22
|
+
// `when` was wired to it.
|
|
23
|
+
//
|
|
24
|
+
// The element is created but never CONNECTED (`document.createElement` upgrades
|
|
25
|
+
// only), for the reason budget-meter.test.ts gives: connecting subscribes to
|
|
26
|
+
// `agenticActivityBus` and leaves the runner's event loop open.
|
|
27
|
+
const Suite = createLogicSuite('settings-modal usage meters structure');
|
|
28
|
+
const template = settingsUsageMetersTemplate('test-progress');
|
|
29
|
+
let storeSeq = 0;
|
|
30
|
+
/** A fresh (unconnected) element wired to its own real session store. */
|
|
31
|
+
function element() {
|
|
32
|
+
const el = document.createElement('foundation-ai-assistant');
|
|
33
|
+
storeSeq += 1;
|
|
34
|
+
el._sessionRef = getSessionStore(`usage-meters-test-${storeSeq}`, false);
|
|
35
|
+
return el;
|
|
36
|
+
}
|
|
37
|
+
/** Renders the meters for `el` into a detached host and returns it. */
|
|
38
|
+
function render(el) {
|
|
39
|
+
const host = document.createElement('div');
|
|
40
|
+
template.render(el, host);
|
|
41
|
+
return host;
|
|
42
|
+
}
|
|
43
|
+
Suite.after(() => {
|
|
44
|
+
clearAllSessionStores();
|
|
45
|
+
});
|
|
46
|
+
Suite('both meters render inside one shared column', () => {
|
|
47
|
+
const el = element();
|
|
48
|
+
el.contextTokens = 1000;
|
|
49
|
+
el.contextLimit = 4000;
|
|
50
|
+
el.setVendorBudget('anthropic', { budgetUsd: 50, spentUsd: 45 });
|
|
51
|
+
const host = render(el);
|
|
52
|
+
const wrapper = host.querySelector('.settings-usage-meters');
|
|
53
|
+
assert.ok(wrapper, 'the shared column is rendered');
|
|
54
|
+
assert.ok(wrapper.querySelector('.settings-context-usage'), 'the context indicator is inside it');
|
|
55
|
+
assert.ok(wrapper.querySelector('.settings-budget-usage'), 'so is the budget meter');
|
|
56
|
+
// The rhythm the wrapper exists for: every meter row is a child of a 2-unit
|
|
57
|
+
// column, none of them of the section body's 4-unit one.
|
|
58
|
+
assert.is(host.querySelectorAll('.settings-usage-meters').length, 1, 'one column, not one per meter');
|
|
59
|
+
});
|
|
60
|
+
Suite('neither meter visible → no wrapper at all, not an empty one', () => {
|
|
61
|
+
// The stray-gap case. Nothing fed, no context figures: the section body must
|
|
62
|
+
// see no flex child from the meters.
|
|
63
|
+
const el = element();
|
|
64
|
+
const host = render(el);
|
|
65
|
+
assert.is(el.settingsContextUsageVisible, false, 'precondition: no context data');
|
|
66
|
+
assert.is(el.settingsBudgetUsageVisible, false, 'precondition: no budget data');
|
|
67
|
+
assert.is(host.querySelector('.settings-usage-meters'), null, 'no wrapper element');
|
|
68
|
+
assert.is(host.querySelectorAll('*').length, 0, 'and no element of any kind — comment markers only');
|
|
69
|
+
});
|
|
70
|
+
Suite('one meter alone still gets the shared column, with only its own child', () => {
|
|
71
|
+
// Both single-meter cases: the wrapper appears for either meter on its own,
|
|
72
|
+
// and the hidden sibling contributes NO element inside it (both gate with
|
|
73
|
+
// `when`, which emplaces comment markers, not empty divs — an empty div would
|
|
74
|
+
// be a second stray gap, inside the column this time).
|
|
75
|
+
const contextOnly = element();
|
|
76
|
+
contextOnly.contextTokens = 1000;
|
|
77
|
+
contextOnly.contextLimit = 4000;
|
|
78
|
+
const contextHost = render(contextOnly);
|
|
79
|
+
const contextWrapper = contextHost.querySelector('.settings-usage-meters');
|
|
80
|
+
assert.ok(contextWrapper, 'context alone still gets the column');
|
|
81
|
+
assert.is(contextWrapper.children.length, 1, 'the hidden budget meter adds no flex child');
|
|
82
|
+
assert.ok(contextWrapper.querySelector('.settings-context-usage'));
|
|
83
|
+
const budgetOnly = element();
|
|
84
|
+
budgetOnly.setVendorBudget('gemini', { budgetUsd: 50, spentUsd: 15 });
|
|
85
|
+
const budgetHost = render(budgetOnly);
|
|
86
|
+
const budgetWrapper = budgetHost.querySelector('.settings-usage-meters');
|
|
87
|
+
assert.ok(budgetWrapper, 'budget alone still gets the column');
|
|
88
|
+
assert.is(budgetWrapper.children.length, 1, 'the hidden context indicator adds no flex child');
|
|
89
|
+
assert.ok(budgetWrapper.querySelector('.settings-budget-usage'));
|
|
90
|
+
});
|
|
91
|
+
Suite.run();
|