@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["../src/chat-driver-node.ts","../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.compact.test.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.budget.test.ts","../src/components/orchestrating-driver/orchestrating-driver.pin.test.ts","../src/components/orchestrating-driver/orchestrating-driver.ts","../src/components/popout-manager/index.ts","../src/components/popout-manager/popout-manager.ts","../src/components/settings-modal/index.ts","../src/components/settings-modal/settings-modal.styles.ts","../src/components/settings-modal/settings-modal.template.ts","../src/config/config.ts","../src/config/define-stateful-agent.test.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/blocked-state.test.ts","../src/main/cost-session-banking.test.ts","../src/main/index.ts","../src/main/main.styles.test.ts","../src/main/main.styles.ts","../src/main/main.template.ts","../src/main/main.ts","../src/main/main.types.ts","../src/main/popout-interaction-gate.test.ts","../src/provider/ai-provider-switcher.ts","../src/provider/assistant-app-settings.ts","../src/state/ai-assistant-slice.test.ts","../src/state/ai-assistant-slice.ts","../src/state/debug-event-log.test.ts","../src/state/debug-event-log.ts","../src/state/driver-registry.test.ts","../src/state/driver-registry.ts","../src/state/interaction-context.test.ts","../src/state/interaction-context.ts","../src/state/session-store.ts","../src/state/persistence/build-timeline-entries.ts","../src/state/persistence/diagnostics-cursors.test.ts","../src/state/persistence/diagnostics-cursors.ts","../src/state/persistence/diagnostics.test.ts","../src/state/persistence/diagnostics.ts","../src/state/persistence/index.ts","../src/state/persistence/persister-registry.ts","../src/state/persistence/session-persistence-provider.test.ts","../src/state/persistence/session-persistence-provider.ts","../src/state/persistence/session-persistence.integration.test.ts","../src/state/persistence/session-persister.test.ts","../src/state/persistence/session-persister.ts","../src/state/persistence/session-snapshot.test.ts","../src/state/persistence/session-snapshot.ts","../src/state/persistence/stateful-restore.e2e.test.ts","../src/styles/ai-colours.ts","../src/styles/settings-section.ts","../src/suggestions/chat-suggestions.ts","../src/tags/index.ts","../src/types/ai-chat-widget.ts","../src/types/interaction-context.ts","../src/utils/animated-panel-toggle.ts","../src/utils/animation-exclusivity.test.ts","../src/utils/animation-exclusivity.ts","../src/utils/banked-usage-baselines.ts","../src/utils/collect-session-models.test.ts","../src/utils/collect-session-models.ts","../src/utils/condense-history.test.ts","../src/utils/condense-history.ts","../src/utils/cost-session-history.test.ts","../src/utils/cost-session-history.ts","../src/utils/derive-cost-session-title.test.ts","../src/utils/derive-cost-session-title.ts","../src/utils/flatten-sub-agent-messages.test.ts","../src/utils/flatten-sub-agent-messages.ts","../src/utils/history-transform.test.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/resolve-cost-history-config.test.ts","../src/utils/resolve-cost-history-config.ts","../src/utils/resolve-preference-baseline.test.ts","../src/utils/resolve-preference-baseline.ts","../src/utils/strip-agent-handlers.test.ts","../src/utils/strip-agent-handlers.ts","../src/utils/sum-costs.test.ts","../src/utils/sum-costs.ts","../src/utils/sum-tokens.test.ts","../src/utils/sum-tokens.ts","../src/utils/sum-usage.test.ts","../src/utils/sum-usage.ts","../src/utils/tool-fold.ts","../src/utils/with-timeout.ts"],"version":"5.9.2"}
|
|
1
|
+
{"root":["../src/chat-driver-node.ts","../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.compact.test.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.budget.test.ts","../src/components/orchestrating-driver/orchestrating-driver.pin.test.ts","../src/components/orchestrating-driver/orchestrating-driver.ts","../src/components/popout-manager/index.ts","../src/components/popout-manager/popout-manager.ts","../src/components/settings-modal/index.ts","../src/components/settings-modal/settings-modal.styles.test.ts","../src/components/settings-modal/settings-modal.styles.ts","../src/components/settings-modal/settings-modal.template.test.ts","../src/components/settings-modal/settings-modal.template.ts","../src/config/config.ts","../src/config/define-stateful-agent.test.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/blocked-state.test.ts","../src/main/budget-meter.test.ts","../src/main/cost-session-banking.test.ts","../src/main/index.ts","../src/main/main.styles.test.ts","../src/main/main.styles.ts","../src/main/main.template.ts","../src/main/main.ts","../src/main/main.types.ts","../src/main/popout-interaction-gate.test.ts","../src/provider/ai-provider-switcher.ts","../src/provider/assistant-app-settings.ts","../src/state/ai-assistant-slice.test.ts","../src/state/ai-assistant-slice.ts","../src/state/debug-event-log.test.ts","../src/state/debug-event-log.ts","../src/state/driver-registry.test.ts","../src/state/driver-registry.ts","../src/state/interaction-context.test.ts","../src/state/interaction-context.ts","../src/state/session-store.ts","../src/state/persistence/build-timeline-entries.ts","../src/state/persistence/diagnostics-cursors.test.ts","../src/state/persistence/diagnostics-cursors.ts","../src/state/persistence/diagnostics.test.ts","../src/state/persistence/diagnostics.ts","../src/state/persistence/index.ts","../src/state/persistence/persister-registry.ts","../src/state/persistence/session-persistence-provider.test.ts","../src/state/persistence/session-persistence-provider.ts","../src/state/persistence/session-persistence.integration.test.ts","../src/state/persistence/session-persister.test.ts","../src/state/persistence/session-persister.ts","../src/state/persistence/session-snapshot.test.ts","../src/state/persistence/session-snapshot.ts","../src/state/persistence/stateful-restore.e2e.test.ts","../src/styles/ai-colours.ts","../src/styles/settings-section.ts","../src/suggestions/chat-suggestions.ts","../src/tags/index.ts","../src/types/ai-chat-widget.ts","../src/types/interaction-context.ts","../src/utils/animated-panel-toggle.ts","../src/utils/animation-exclusivity.test.ts","../src/utils/animation-exclusivity.ts","../src/utils/banked-usage-baselines.ts","../src/utils/collect-session-models.test.ts","../src/utils/collect-session-models.ts","../src/utils/condense-history.test.ts","../src/utils/condense-history.ts","../src/utils/cost-session-history.test.ts","../src/utils/cost-session-history.ts","../src/utils/derive-cost-session-title.test.ts","../src/utils/derive-cost-session-title.ts","../src/utils/flatten-sub-agent-messages.test.ts","../src/utils/flatten-sub-agent-messages.ts","../src/utils/format-usd.ts","../src/utils/history-transform.test.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/resolve-cost-history-config.test.ts","../src/utils/resolve-cost-history-config.ts","../src/utils/resolve-preference-baseline.test.ts","../src/utils/resolve-preference-baseline.ts","../src/utils/strip-agent-handlers.test.ts","../src/utils/strip-agent-handlers.ts","../src/utils/sum-costs.test.ts","../src/utils/sum-costs.ts","../src/utils/sum-tokens.test.ts","../src/utils/sum-tokens.ts","../src/utils/sum-usage.test.ts","../src/utils/sum-usage.ts","../src/utils/tool-fold.ts","../src/utils/with-timeout.ts"],"version":"5.9.2"}
|
|
@@ -512,6 +512,69 @@ you `switch` exhaustively over either with a `never` guard, TypeScript will now
|
|
|
512
512
|
member — that is the intended prompt to decide how your app should treat it, not a breaking
|
|
513
513
|
change to runtime behaviour.
|
|
514
514
|
|
|
515
|
+
### Feeding the budget meter
|
|
516
|
+
|
|
517
|
+
Orthogonal to Options A–C (combine it with any of them): the settings modal can show a
|
|
518
|
+
**per-vendor budget meter** — one row per vendor with `$spent / $limit (P%)` text and a
|
|
519
|
+
progress bar — but only if the host feeds it. The element meters nothing itself; the
|
|
520
|
+
meter renders exactly the vendors that currently have figures, fed through the public
|
|
521
|
+
`setVendorBudget(vendor, figures)` API from the same budget endpoint the pre-flight
|
|
522
|
+
(Option B) already consumes:
|
|
523
|
+
|
|
524
|
+
```ts
|
|
525
|
+
async function feedBudgetMeter(assistantEl: FoundationAiAssistant) {
|
|
526
|
+
// `vendors` is an OBJECT keyed by vendor id, not an array.
|
|
527
|
+
const { vendors } = await (await fetch('/api/budget')).json();
|
|
528
|
+
for (const v of Object.values(vendors)) {
|
|
529
|
+
// An UNLIMITED vendor (`unlimited: true` / `limitUsd: null`) gets no row —
|
|
530
|
+
// simply do not feed it. There is no "unlimited" figure shape: a bar with
|
|
531
|
+
// no cap has nothing truthful to fill to.
|
|
532
|
+
if (v.unlimited || v.limitUsd == null) {
|
|
533
|
+
assistantEl.setVendorBudget(v.vendor, null);
|
|
534
|
+
continue;
|
|
535
|
+
}
|
|
536
|
+
assistantEl.setVendorBudget(v.vendor, { budgetUsd: v.limitUsd, spentUsd: v.spentUsd });
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// on mount (alongside the Option B pre-flight, if you run one)…
|
|
541
|
+
await feedBudgetMeter(assistantEl);
|
|
542
|
+
// …and re-run it after every completed turn, so the meter tracks the spend
|
|
543
|
+
// that turn just caused.
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
What to expect from that loop:
|
|
547
|
+
|
|
548
|
+
- **Re-feed after each turn, unconditionally.** The call is idempotent — identical
|
|
549
|
+
figures are a stored no-op — so there is no cheaper "only if changed" protocol worth
|
|
550
|
+
building. `setVendorBudget(vendor, null)` clears a vendor's row.
|
|
551
|
+
- **The figures lag by one turn, and that is expected.** The server meters a turn's
|
|
552
|
+
spend as it happens, but the host only re-fetches after the turn completes — so the
|
|
553
|
+
meter the user watches *during* a turn shows the total up to the previous one. The
|
|
554
|
+
meter is a gauge, not a live ticker; the authoritative gate is the server's (the 402
|
|
555
|
+
and the pre-flight), which reads the live account.
|
|
556
|
+
- **Spend can exceed the limit.** The last allowed call may overshoot the cap, and the
|
|
557
|
+
row's text shows the real figures (`$62.34 / $50.00 (125%)`); only the bar clamps
|
|
558
|
+
at 100%.
|
|
559
|
+
- **Validation is forgiving at run time.** An unbudgeted vendor (`chrome`, `openai`,
|
|
560
|
+
`'none'`, anything unknown) or non-finite/negative figures make the call return
|
|
561
|
+
`false` — with a single warning per element, not one per turn — and change nothing.
|
|
562
|
+
`undefined` figures behave exactly like `null` and clear the row, so an untyped
|
|
563
|
+
per-vendor lookup that misses is a clear, not a crash.
|
|
564
|
+
- **`false` also means "too early".** A feed that lands before the element has its
|
|
565
|
+
session store — ahead of the first append, or during a pop-out remount — stores
|
|
566
|
+
nothing and returns `false`, without a warning (a lifecycle window is not bad
|
|
567
|
+
input). Nothing to handle: the per-turn re-feed above lands the figures as soon as
|
|
568
|
+
the element is ready.
|
|
569
|
+
- **Lifetime follows the blocked latch, minus persistence.** The figures survive
|
|
570
|
+
pop-in/out and "Clear" / "New chat" (a new chat refills no budget), but are *not*
|
|
571
|
+
written to the session snapshot: the server is authoritative, and your mount-time
|
|
572
|
+
feed is what restores the meter after a reload — a persisted copy could only ever
|
|
573
|
+
show stale dollars.
|
|
574
|
+
- **Config:** `chatConfig.ui.showBudgetUsage: false` hides the meter (default is on,
|
|
575
|
+
like `showContextUsage`). Styling hooks: `::part(budget-usage)` and
|
|
576
|
+
`::part(budget-progress)` — see `docs/styling.md`.
|
|
577
|
+
|
|
515
578
|
---
|
|
516
579
|
|
|
517
580
|
## Styling the banner
|
package/docs/styling.md
CHANGED
|
@@ -81,6 +81,29 @@ foundation-ai-assistant::part(message) {
|
|
|
81
81
|
}
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
+
### The settings-modal budget meter
|
|
85
|
+
|
|
86
|
+
The per-vendor AI budget meter (GENC-1464) renders in the settings modal's
|
|
87
|
+
"AI Model Settings" section — one row per vendor the host has fed via
|
|
88
|
+
`setVendorBudget` — and follows the context-usage indicator's part convention:
|
|
89
|
+
|
|
90
|
+
| Part | Element |
|
|
91
|
+
| --- | --- |
|
|
92
|
+
| `budget-usage` | The meter's container (all vendor rows) |
|
|
93
|
+
| `budget-progress` | One vendor's budget bar (repeats per rendered row) |
|
|
94
|
+
|
|
95
|
+
```css
|
|
96
|
+
/* Tighten the meter's row spacing */
|
|
97
|
+
foundation-ai-assistant::part(budget-usage) {
|
|
98
|
+
gap: 4px;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* Restyle every vendor's bar at once */
|
|
102
|
+
foundation-ai-assistant::part(budget-progress) {
|
|
103
|
+
height: 6px;
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
84
107
|
### The conversation column (`messages-content`)
|
|
85
108
|
|
|
86
109
|
Messages sit in a column capped at `1000px` and centred inside the full-width
|
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": "15.
|
|
4
|
+
"version": "15.8.0",
|
|
5
5
|
"license": "SEE LICENSE IN license.txt",
|
|
6
6
|
"main": "dist/esm/index.js",
|
|
7
7
|
"types": "dist/ai-assistant.d.ts",
|
|
@@ -73,26 +73,26 @@
|
|
|
73
73
|
}
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
|
-
"@genesislcap/foundation-testing": "15.
|
|
77
|
-
"@genesislcap/genx": "15.
|
|
78
|
-
"@genesislcap/rollup-builder": "15.
|
|
79
|
-
"@genesislcap/ts-builder": "15.
|
|
80
|
-
"@genesislcap/uvu-playwright-builder": "15.
|
|
81
|
-
"@genesislcap/vite-builder": "15.
|
|
82
|
-
"@genesislcap/webpack-builder": "15.
|
|
76
|
+
"@genesislcap/foundation-testing": "15.8.0",
|
|
77
|
+
"@genesislcap/genx": "15.8.0",
|
|
78
|
+
"@genesislcap/rollup-builder": "15.8.0",
|
|
79
|
+
"@genesislcap/ts-builder": "15.8.0",
|
|
80
|
+
"@genesislcap/uvu-playwright-builder": "15.8.0",
|
|
81
|
+
"@genesislcap/vite-builder": "15.8.0",
|
|
82
|
+
"@genesislcap/webpack-builder": "15.8.0",
|
|
83
83
|
"@types/dompurify": "^3.0.5",
|
|
84
84
|
"@types/marked": "^5.0.2",
|
|
85
85
|
"esbuild": "0.25.12"
|
|
86
86
|
},
|
|
87
87
|
"dependencies": {
|
|
88
|
-
"@genesislcap/foundation-ai": "15.
|
|
89
|
-
"@genesislcap/foundation-logger": "15.
|
|
90
|
-
"@genesislcap/foundation-notifications": "15.
|
|
91
|
-
"@genesislcap/foundation-redux": "15.
|
|
92
|
-
"@genesislcap/foundation-ui": "15.
|
|
93
|
-
"@genesislcap/foundation-utils": "15.
|
|
94
|
-
"@genesislcap/rapid-design-system": "15.
|
|
95
|
-
"@genesislcap/web-core": "15.
|
|
88
|
+
"@genesislcap/foundation-ai": "15.8.0",
|
|
89
|
+
"@genesislcap/foundation-logger": "15.8.0",
|
|
90
|
+
"@genesislcap/foundation-notifications": "15.8.0",
|
|
91
|
+
"@genesislcap/foundation-redux": "15.8.0",
|
|
92
|
+
"@genesislcap/foundation-ui": "15.8.0",
|
|
93
|
+
"@genesislcap/foundation-utils": "15.8.0",
|
|
94
|
+
"@genesislcap/rapid-design-system": "15.8.0",
|
|
95
|
+
"@genesislcap/web-core": "15.8.0",
|
|
96
96
|
"dompurify": "^3.3.1",
|
|
97
97
|
"marked": "^17.0.3"
|
|
98
98
|
},
|
|
@@ -105,5 +105,5 @@
|
|
|
105
105
|
"access": "public"
|
|
106
106
|
},
|
|
107
107
|
"customElements": "dist/custom-elements.json",
|
|
108
|
-
"gitHead": "
|
|
108
|
+
"gitHead": "10412a7d63fbdf104e0c8f8704a03a7f3b7ad347"
|
|
109
109
|
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
|
|
2
|
+
import { settingsModalStyles } from './settings-modal.styles';
|
|
3
|
+
|
|
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
|
+
|
|
18
|
+
const suite = createLogicSuite('settings-modal.styles budget-meter rules');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The generated stylesheet text, flattened recursively — `css` composes nested
|
|
22
|
+
* `ElementStyles`, and a shallow read can return almost nothing, making every
|
|
23
|
+
* negative assertion below pass vacuously. The first test guards that.
|
|
24
|
+
*/
|
|
25
|
+
const flatten = (value: unknown): string => {
|
|
26
|
+
if (typeof value === 'string') return value;
|
|
27
|
+
if (Array.isArray(value)) return value.map(flatten).join('\n');
|
|
28
|
+
const nested = (value as { styles?: unknown } | null | undefined)?.styles;
|
|
29
|
+
return nested ? flatten(nested) : '';
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Comments stripped before matching: the assertions below are about
|
|
34
|
+
* DECLARATIONS, and the sheet's own prose legitimately mentions the very
|
|
35
|
+
* pattern (`var(--x, y)`) the negative assertion hunts for.
|
|
36
|
+
*/
|
|
37
|
+
const cssText = flatten(settingsModalStyles).replace(/\/\*[\s\S]*?\*\//g, '');
|
|
38
|
+
|
|
39
|
+
/** Every declaration block whose selector mentions `.settings-budget-`, joined. */
|
|
40
|
+
const budgetRules = (cssText.match(/[^{}]*\.settings-budget-[^{}]*\{[^}]*\}/g) ?? []).join('\n');
|
|
41
|
+
|
|
42
|
+
suite('the budget rules were extracted as text (guards the assertions below)', () => {
|
|
43
|
+
assert.ok(cssText.includes('.settings-budget-usage'), 'the meter container rule is in the sheet');
|
|
44
|
+
assert.ok(budgetRules.length > 0, 'the extraction found the budget rules');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
suite('the meter consumes design-system tokens without dead fallbacks', () => {
|
|
48
|
+
// One structural probe rather than a per-token list: ANY `var(--x,` inside a
|
|
49
|
+
// budget rule is a fallback, and the meter reads only tokens that ship DS
|
|
50
|
+
// defaults (--design-unit, --neutral-foreground-*), so there is no legitimate
|
|
51
|
+
// fallback for this scope. Scoped to the budget rules on purpose — see
|
|
52
|
+
// main.styles.test.ts for why a whole-sheet grep is the wrong test.
|
|
53
|
+
assert.not.ok(/var\(--[a-z-]+\s*,/.test(budgetRules), 'no var(--token, fallback) in the meter');
|
|
54
|
+
assert.ok(budgetRules.includes('var(--design-unit)'), 'and it does consume tokens');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
suite('every meter row sits on one shared rhythm', () => {
|
|
58
|
+
// The 4/2/2 fix. `.settings-modal-section-body` gaps at 4 units and the
|
|
59
|
+
// budget meter at 2, so an unwrapped context indicator sat 4 units above the
|
|
60
|
+
// FIRST vendor row while the vendor rows sat 2 apart — three visually
|
|
61
|
+
// identical meter rows, unevenly spaced. Both meters now live in
|
|
62
|
+
// `.settings-usage-meters`, which must carry the SAME gap as the meter it
|
|
63
|
+
// wraps: asserted as ONE grouped rule, so the two cannot drift to different
|
|
64
|
+
// rhythms. (settings-modal.template.test.ts pins the other half — that the
|
|
65
|
+
// wrapper is conditional, so a host with no meters pays no stray gap for it.)
|
|
66
|
+
const shared = /\.settings-usage-meters\s*,\s*\.settings-budget-usage\s*\{([^}]*)\}/.exec(
|
|
67
|
+
cssText,
|
|
68
|
+
);
|
|
69
|
+
assert.ok(shared, '.settings-usage-meters is grouped with .settings-budget-usage');
|
|
70
|
+
assert.ok(shared![1].includes('flex-direction: column'), 'the shared container is a column');
|
|
71
|
+
assert.ok(
|
|
72
|
+
shared![1].includes('gap: calc(var(--design-unit) * 2px)'),
|
|
73
|
+
'gapped at the meter rhythm (2 units), not the section body 4',
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
suite("the meter shares the context indicator's row anatomy by grouped selector", () => {
|
|
78
|
+
// The invariant is the SHARING, not the shape: each budget class must sit in
|
|
79
|
+
// the same selector list as its context sibling, so restyling one restyles
|
|
80
|
+
// both and the two indicators cannot drift apart.
|
|
81
|
+
const sharedPairs: Array<[string, string]> = [
|
|
82
|
+
['.settings-context-header', '.settings-budget-header'],
|
|
83
|
+
['.settings-context-label', '.settings-budget-label'],
|
|
84
|
+
['.settings-context-tokens', '.settings-budget-figures'],
|
|
85
|
+
];
|
|
86
|
+
for (const [contextClass, budgetClass] of sharedPairs) {
|
|
87
|
+
const grouped = new RegExp(
|
|
88
|
+
`${contextClass.replace(/\./g, '\\.')}\\s*,\\s*${budgetClass.replace(/\./g, '\\.')}\\s*\\{`,
|
|
89
|
+
);
|
|
90
|
+
assert.ok(grouped.test(cssText), `${budgetClass} is grouped with ${contextClass}`);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
suite.run();
|
|
@@ -275,7 +275,17 @@ export const settingsModalStyles = css`
|
|
|
275
275
|
font-style: italic;
|
|
276
276
|
}
|
|
277
277
|
|
|
278
|
-
|
|
278
|
+
/*
|
|
279
|
+
* The budget meter (GENC-1464) is the context indicator's money sibling and
|
|
280
|
+
* shares its row anatomy 1:1, so the budget classes JOIN these rules rather
|
|
281
|
+
* than restating them — one place to restyle the header/label/figures shape,
|
|
282
|
+
* and the two indicators cannot drift apart. Design-system tokens only, and
|
|
283
|
+
* unfallbacked: every token here ships a DS default, so a var(--x, y)
|
|
284
|
+
* fallback would be dead code that hides a broken token name (#2411/#2436
|
|
285
|
+
* review rule, pinned by settings-modal.styles.test.ts).
|
|
286
|
+
*/
|
|
287
|
+
.settings-context-header,
|
|
288
|
+
.settings-budget-header {
|
|
279
289
|
display: flex;
|
|
280
290
|
justify-content: space-between;
|
|
281
291
|
gap: calc(var(--design-unit) * 2px);
|
|
@@ -283,7 +293,8 @@ export const settingsModalStyles = css`
|
|
|
283
293
|
font-size: 12px;
|
|
284
294
|
}
|
|
285
295
|
|
|
286
|
-
.settings-context-label
|
|
296
|
+
.settings-context-label,
|
|
297
|
+
.settings-budget-label {
|
|
287
298
|
color: var(--neutral-foreground-hint);
|
|
288
299
|
font-size: 11px;
|
|
289
300
|
font-weight: 600;
|
|
@@ -291,10 +302,29 @@ export const settingsModalStyles = css`
|
|
|
291
302
|
letter-spacing: 0.06em;
|
|
292
303
|
}
|
|
293
304
|
|
|
294
|
-
.settings-context-tokens
|
|
305
|
+
.settings-context-tokens,
|
|
306
|
+
.settings-budget-figures {
|
|
295
307
|
color: var(--neutral-foreground-rest);
|
|
296
308
|
}
|
|
297
309
|
|
|
310
|
+
/*
|
|
311
|
+
* ONE rhythm for every meter row. The outer wrapper holds the context
|
|
312
|
+
* indicator and the budget meter; the inner one holds the budget meter's
|
|
313
|
+
* vendor rows — and both gap by the same 2 units (half the section body's 4)
|
|
314
|
+
* so that visually identical meter rows are evenly spaced whatever mix of
|
|
315
|
+
* them is on screen. Without the wrapper the context row inherits the body's
|
|
316
|
+
* 4-unit gap while the vendor rows keep 2, and three identical-looking rows
|
|
317
|
+
* read as an uneven 4/2/2. The wrapper renders only when at least one meter
|
|
318
|
+
* does — see settingsUsageMetersTemplate for why an always-on one would cost
|
|
319
|
+
* a stray gap.
|
|
320
|
+
*/
|
|
321
|
+
.settings-usage-meters,
|
|
322
|
+
.settings-budget-usage {
|
|
323
|
+
display: flex;
|
|
324
|
+
flex-direction: column;
|
|
325
|
+
gap: calc(var(--design-unit) * 2px);
|
|
326
|
+
}
|
|
327
|
+
|
|
298
328
|
.settings-current-build,
|
|
299
329
|
.settings-lifetime-usage {
|
|
300
330
|
background: var(--neutral-layer-2);
|
|
@@ -0,0 +1,124 @@
|
|
|
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
|
+
|
|
6
|
+
// Hold a reference so the custom-element registration isn't tree-shaken.
|
|
7
|
+
FoundationAiAssistant;
|
|
8
|
+
|
|
9
|
+
// GENC-1464 — the shared meter column in the settings modal's "AI Model
|
|
10
|
+
// Settings" section.
|
|
11
|
+
//
|
|
12
|
+
// The section body is a 4-unit flex column and the budget meter is a 2-unit
|
|
13
|
+
// one, so an unwrapped context indicator sat 4 units above the first vendor row
|
|
14
|
+
// while the vendor rows sat 2 apart — three visually identical meter rows
|
|
15
|
+
// reading as an uneven 4/2/2. Both meters now share ONE 2-unit column
|
|
16
|
+
// (`.settings-usage-meters`, grouped with `.settings-budget-usage` in the
|
|
17
|
+
// styles — see settings-modal.styles.test.ts for the gap itself).
|
|
18
|
+
//
|
|
19
|
+
// The wrapper must also be ABSENT when neither meter shows: a permanent wrapper
|
|
20
|
+
// is a permanent flex child of the section body, so a host with no meters would
|
|
21
|
+
// pay a stray 4-unit gap after its model slot for a div that renders nothing.
|
|
22
|
+
// That is a structural claim about the template, so this suite renders the
|
|
23
|
+
// template rather than reading a getter — the getter cannot show whether the
|
|
24
|
+
// `when` was wired to it.
|
|
25
|
+
//
|
|
26
|
+
// The element is created but never CONNECTED (`document.createElement` upgrades
|
|
27
|
+
// only), for the reason budget-meter.test.ts gives: connecting subscribes to
|
|
28
|
+
// `agenticActivityBus` and leaves the runner's event loop open.
|
|
29
|
+
|
|
30
|
+
const Suite = createLogicSuite('settings-modal usage meters structure');
|
|
31
|
+
|
|
32
|
+
const template = settingsUsageMetersTemplate('test-progress');
|
|
33
|
+
|
|
34
|
+
let storeSeq = 0;
|
|
35
|
+
|
|
36
|
+
/** A fresh (unconnected) element wired to its own real session store. */
|
|
37
|
+
function element(): FoundationAiAssistant {
|
|
38
|
+
const el = document.createElement('foundation-ai-assistant') as FoundationAiAssistant;
|
|
39
|
+
storeSeq += 1;
|
|
40
|
+
(el as unknown as { _sessionRef: unknown })._sessionRef = getSessionStore(
|
|
41
|
+
`usage-meters-test-${storeSeq}`,
|
|
42
|
+
false,
|
|
43
|
+
);
|
|
44
|
+
return el;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Renders the meters for `el` into a detached host and returns it. */
|
|
48
|
+
function render(el: FoundationAiAssistant): HTMLElement {
|
|
49
|
+
const host = document.createElement('div');
|
|
50
|
+
template.render(el, host);
|
|
51
|
+
return host;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
Suite.after(() => {
|
|
55
|
+
clearAllSessionStores();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
Suite('both meters render inside one shared column', () => {
|
|
59
|
+
const el = element();
|
|
60
|
+
el.contextTokens = 1000;
|
|
61
|
+
el.contextLimit = 4000;
|
|
62
|
+
el.setVendorBudget('anthropic', { budgetUsd: 50, spentUsd: 45 });
|
|
63
|
+
|
|
64
|
+
const host = render(el);
|
|
65
|
+
const wrapper = host.querySelector('.settings-usage-meters');
|
|
66
|
+
|
|
67
|
+
assert.ok(wrapper, 'the shared column is rendered');
|
|
68
|
+
assert.ok(
|
|
69
|
+
wrapper!.querySelector('.settings-context-usage'),
|
|
70
|
+
'the context indicator is inside it',
|
|
71
|
+
);
|
|
72
|
+
assert.ok(wrapper!.querySelector('.settings-budget-usage'), 'so is the budget meter');
|
|
73
|
+
// The rhythm the wrapper exists for: every meter row is a child of a 2-unit
|
|
74
|
+
// column, none of them of the section body's 4-unit one.
|
|
75
|
+
assert.is(
|
|
76
|
+
host.querySelectorAll('.settings-usage-meters').length,
|
|
77
|
+
1,
|
|
78
|
+
'one column, not one per meter',
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
Suite('neither meter visible → no wrapper at all, not an empty one', () => {
|
|
83
|
+
// The stray-gap case. Nothing fed, no context figures: the section body must
|
|
84
|
+
// see no flex child from the meters.
|
|
85
|
+
const el = element();
|
|
86
|
+
|
|
87
|
+
const host = render(el);
|
|
88
|
+
|
|
89
|
+
assert.is(el.settingsContextUsageVisible, false, 'precondition: no context data');
|
|
90
|
+
assert.is(el.settingsBudgetUsageVisible, false, 'precondition: no budget data');
|
|
91
|
+
assert.is(host.querySelector('.settings-usage-meters'), null, 'no wrapper element');
|
|
92
|
+
assert.is(
|
|
93
|
+
host.querySelectorAll('*').length,
|
|
94
|
+
0,
|
|
95
|
+
'and no element of any kind — comment markers only',
|
|
96
|
+
);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
Suite('one meter alone still gets the shared column, with only its own child', () => {
|
|
100
|
+
// Both single-meter cases: the wrapper appears for either meter on its own,
|
|
101
|
+
// and the hidden sibling contributes NO element inside it (both gate with
|
|
102
|
+
// `when`, which emplaces comment markers, not empty divs — an empty div would
|
|
103
|
+
// be a second stray gap, inside the column this time).
|
|
104
|
+
const contextOnly = element();
|
|
105
|
+
contextOnly.contextTokens = 1000;
|
|
106
|
+
contextOnly.contextLimit = 4000;
|
|
107
|
+
|
|
108
|
+
const contextHost = render(contextOnly);
|
|
109
|
+
const contextWrapper = contextHost.querySelector('.settings-usage-meters');
|
|
110
|
+
assert.ok(contextWrapper, 'context alone still gets the column');
|
|
111
|
+
assert.is(contextWrapper!.children.length, 1, 'the hidden budget meter adds no flex child');
|
|
112
|
+
assert.ok(contextWrapper!.querySelector('.settings-context-usage'));
|
|
113
|
+
|
|
114
|
+
const budgetOnly = element();
|
|
115
|
+
budgetOnly.setVendorBudget('gemini', { budgetUsd: 50, spentUsd: 15 });
|
|
116
|
+
|
|
117
|
+
const budgetHost = render(budgetOnly);
|
|
118
|
+
const budgetWrapper = budgetHost.querySelector('.settings-usage-meters');
|
|
119
|
+
assert.ok(budgetWrapper, 'budget alone still gets the column');
|
|
120
|
+
assert.is(budgetWrapper!.children.length, 1, 'the hidden context indicator adds no flex child');
|
|
121
|
+
assert.ok(budgetWrapper!.querySelector('.settings-budget-usage'));
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
Suite.run();
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import type { AggregateUsage } from '@genesislcap/foundation-ai';
|
|
2
2
|
import { html, ref, repeat, slotted, when, type ViewTemplate } from '@genesislcap/web-core';
|
|
3
3
|
import type { FoundationAiAssistant } from '../../main/main';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
ANIMATION_DEFS,
|
|
6
|
+
type AiAssistantAnimation,
|
|
7
|
+
type SettingsBudgetRow,
|
|
8
|
+
} from '../../main/main.types';
|
|
5
9
|
import type { AssistantAppSettingsToggle } from '../../provider/assistant-app-settings';
|
|
6
10
|
import {
|
|
7
11
|
formatCostSessionDate,
|
|
8
12
|
type CostSessionModelEntry,
|
|
9
13
|
type CostSessionRecord,
|
|
10
14
|
} from '../../utils/cost-session-history';
|
|
15
|
+
import { formatUsd, formatUsdAmount } from '../../utils/format-usd';
|
|
11
16
|
import { formatCompactTokenCountSafe } from '../../utils/sum-tokens';
|
|
12
17
|
|
|
13
|
-
const SESSION_COST_DECIMALS = 2;
|
|
14
|
-
|
|
15
18
|
type AnimationCategoryGroup = {
|
|
16
19
|
category: string;
|
|
17
20
|
items: Array<{ value: AiAssistantAnimation; label: string; description: string }>;
|
|
@@ -182,10 +185,10 @@ const platformContextUsageTemplate = (
|
|
|
182
185
|
progressTag: string,
|
|
183
186
|
): ViewTemplate<FoundationAiAssistant> => html<FoundationAiAssistant>`
|
|
184
187
|
${when(
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
188
|
+
// The getter, not a copy of its expression: `settingsUsageMetersTemplate`
|
|
189
|
+
// gates its shared wrapper on this same getter, and the two must agree —
|
|
190
|
+
// a drifted copy would leave the wrapper rendering around nothing.
|
|
191
|
+
(x) => x.settingsContextUsageVisible,
|
|
189
192
|
html<FoundationAiAssistant>`
|
|
190
193
|
<div class="settings-context-usage" part="context-usage">
|
|
191
194
|
<div class="settings-context-header">
|
|
@@ -205,6 +208,74 @@ const platformContextUsageTemplate = (
|
|
|
205
208
|
)}
|
|
206
209
|
`;
|
|
207
210
|
|
|
211
|
+
/**
|
|
212
|
+
* Per-vendor AI budget meter (GENC-1464) — the money sibling of
|
|
213
|
+
* {@link platformContextUsageTemplate}, one row per vendor the host has fed
|
|
214
|
+
* figures for. The row text is unclamped (overshoot is real and shown); only
|
|
215
|
+
* the bar clamps — both computed in `settingsBudgetRows`, so the bindings here
|
|
216
|
+
* stay pure reads and the arithmetic is unit-testable.
|
|
217
|
+
*/
|
|
218
|
+
const platformBudgetUsageTemplate = (
|
|
219
|
+
progressTag: string,
|
|
220
|
+
): ViewTemplate<FoundationAiAssistant> => html<FoundationAiAssistant>`
|
|
221
|
+
${when(
|
|
222
|
+
(x) => x.settingsBudgetUsageVisible,
|
|
223
|
+
html<FoundationAiAssistant>`
|
|
224
|
+
<div class="settings-budget-usage" part="budget-usage">
|
|
225
|
+
${repeat(
|
|
226
|
+
(x) => x.settingsBudgetRows,
|
|
227
|
+
html<SettingsBudgetRow, FoundationAiAssistant>`
|
|
228
|
+
<div class="settings-budget-vendor">
|
|
229
|
+
<div class="settings-budget-header">
|
|
230
|
+
<span class="settings-budget-label">${(row) => row.label} budget</span>
|
|
231
|
+
<span class="settings-budget-figures">${(row) => row.figures}</span>
|
|
232
|
+
</div>
|
|
233
|
+
<${progressTag}
|
|
234
|
+
part="budget-progress"
|
|
235
|
+
value="${(row) => row.barValue}"
|
|
236
|
+
></${progressTag}>
|
|
237
|
+
</div>
|
|
238
|
+
`,
|
|
239
|
+
)}
|
|
240
|
+
</div>
|
|
241
|
+
`,
|
|
242
|
+
)}
|
|
243
|
+
`;
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* The two usage meters in one shared, evenly-gapped column.
|
|
247
|
+
*
|
|
248
|
+
* Without it the context row and the FIRST budget row sit the section body's
|
|
249
|
+
* 4 units apart while the budget rows sit 2 apart — three visually identical
|
|
250
|
+
* meter rows reading as an uneven 4/2/2. The wrapper puts every meter row on
|
|
251
|
+
* the same 2-unit rhythm; see `.settings-usage-meters` in the styles.
|
|
252
|
+
*
|
|
253
|
+
* CONDITIONAL on purpose. An always-present wrapper is a permanent flex child
|
|
254
|
+
* of `.settings-modal-section-body`, so a host with neither meter would get a
|
|
255
|
+
* stray 4-unit gap after its model slot from a div that renders nothing. The
|
|
256
|
+
* gate is exactly the disjunction of the two children's own gates — both of
|
|
257
|
+
* which are `when`, which emplaces only comment markers when false, so a
|
|
258
|
+
* hidden meter contributes no flex child inside the wrapper either.
|
|
259
|
+
*
|
|
260
|
+
* Exported for `settings-modal.template.test.ts`, which renders it in isolation
|
|
261
|
+
* to pin both of those properties; `index.ts` does not re-export it, so it is
|
|
262
|
+
* not package API.
|
|
263
|
+
*
|
|
264
|
+
* @internal
|
|
265
|
+
*/
|
|
266
|
+
export const settingsUsageMetersTemplate = (
|
|
267
|
+
progressTag: string,
|
|
268
|
+
): ViewTemplate<FoundationAiAssistant> => html<FoundationAiAssistant>`
|
|
269
|
+
${when(
|
|
270
|
+
(x) => x.settingsContextUsageVisible || x.settingsBudgetUsageVisible,
|
|
271
|
+
html<FoundationAiAssistant>`
|
|
272
|
+
<div class="settings-usage-meters">
|
|
273
|
+
${platformContextUsageTemplate(progressTag)} ${platformBudgetUsageTemplate(progressTag)}
|
|
274
|
+
</div>
|
|
275
|
+
`,
|
|
276
|
+
)}
|
|
277
|
+
`;
|
|
278
|
+
|
|
208
279
|
const chatBotSettingsTemplate = (
|
|
209
280
|
switchTag: string,
|
|
210
281
|
buttonTag: string,
|
|
@@ -484,9 +555,7 @@ const lifetimeUsageTemplate =
|
|
|
484
555
|
<div class="settings-build-stat-label">Total build cost</div>
|
|
485
556
|
<div class="settings-build-stat-value">
|
|
486
557
|
<span class="prefix">$</span>
|
|
487
|
-
<span class="value">
|
|
488
|
-
${(x) => x.cumulativeCostUsd.toFixed(SESSION_COST_DECIMALS)}
|
|
489
|
-
</span>
|
|
558
|
+
<span class="value">${(x) => formatUsdAmount(x.cumulativeCostUsd)}</span>
|
|
490
559
|
</div>
|
|
491
560
|
</div>
|
|
492
561
|
<div
|
|
@@ -536,7 +605,7 @@ const sessionCostSummaryTemplate =
|
|
|
536
605
|
</div>
|
|
537
606
|
<div class="settings-build-stat-value">
|
|
538
607
|
<span class="prefix">$</span>
|
|
539
|
-
<span class="value">${(x) => x.sessionCostUsd
|
|
608
|
+
<span class="value">${(x) => formatUsdAmount(x.sessionCostUsd)}</span>
|
|
540
609
|
</div>
|
|
541
610
|
</div>
|
|
542
611
|
<div
|
|
@@ -590,7 +659,7 @@ const costSessionHistoryTemplate = (
|
|
|
590
659
|
<div class="settings-cost-history-row-right">
|
|
591
660
|
<div class="settings-cost-history-row-metrics">
|
|
592
661
|
<div class="settings-cost-history-row-cost">
|
|
593
|
-
|
|
662
|
+
${(record) => formatUsd(record.usage.costUsd)}
|
|
594
663
|
</div>
|
|
595
664
|
</div>
|
|
596
665
|
<${buttonTag}
|
|
@@ -666,7 +735,7 @@ export const SettingsModalTemplate = (
|
|
|
666
735
|
</h3>
|
|
667
736
|
<div class="settings-modal-section-body">
|
|
668
737
|
<slot name="settings-model" ${slotted('settingsModelSlotted')}></slot>
|
|
669
|
-
${
|
|
738
|
+
${settingsUsageMetersTemplate(progressTag)}
|
|
670
739
|
</div>
|
|
671
740
|
</section>
|
|
672
741
|
<!--
|