@genesislcap/ai-assistant 15.15.2 → 15.16.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 +271 -2
- package/dist/ai-assistant.d.ts +90 -1
- package/dist/chat-driver.cjs +72 -0
- package/dist/chat-driver.cjs.map +2 -2
- package/dist/chat-driver.mjs +72 -0
- package/dist/chat-driver.mjs.map +2 -2
- package/dist/custom-elements.json +221 -97
- package/dist/dts/components/ai-driver/ai-driver.d.ts +11 -0
- package/dist/dts/components/ai-driver/ai-driver.d.ts.map +1 -1
- package/dist/dts/components/chat-driver/chat-driver.d.ts +31 -1
- package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
- package/dist/dts/components/chat-interaction-wrapper/chat-interaction-wrapper.d.ts +10 -0
- package/dist/dts/components/chat-interaction-wrapper/chat-interaction-wrapper.d.ts.map +1 -1
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.cost.test.d.ts +2 -0
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.cost.test.d.ts.map +1 -0
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts +3 -0
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts.map +1 -1
- package/dist/dts/index.d.ts +1 -1
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/dts/main/file-attachments.test.d.ts +2 -0
- package/dist/dts/main/file-attachments.test.d.ts.map +1 -0
- package/dist/dts/main/interaction-cost.test.d.ts +2 -0
- package/dist/dts/main/interaction-cost.test.d.ts.map +1 -0
- package/dist/dts/main/main.d.ts +27 -0
- package/dist/dts/main/main.d.ts.map +1 -1
- package/dist/dts/main/main.template.d.ts.map +1 -1
- package/dist/dts/react.d.ts +2 -1
- package/dist/dts/state/debug-event-log.d.ts +1 -1
- package/dist/dts/state/debug-event-log.d.ts.map +1 -1
- package/dist/dts/types/ai-chat-widget.d.ts +8 -0
- package/dist/dts/types/ai-chat-widget.d.ts.map +1 -1
- package/dist/esm/components/chat-driver/chat-driver.js +75 -1
- package/dist/esm/components/chat-driver/chat-driver.test.js +90 -0
- package/dist/esm/components/chat-interaction-wrapper/chat-interaction-wrapper.js +22 -0
- package/dist/esm/components/orchestrating-driver/orchestrating-driver.cost.test.js +76 -0
- package/dist/esm/components/orchestrating-driver/orchestrating-driver.js +14 -0
- package/dist/esm/main/file-attachments.test.js +164 -0
- package/dist/esm/main/interaction-cost.test.js +107 -0
- package/dist/esm/main/main.js +193 -21
- package/dist/esm/main/main.template.js +1 -0
- package/dist/esm/state/debug-event-log.js +6 -0
- package/dist/react.cjs +6 -1
- package/dist/react.mjs +6 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +17 -17
- package/src/components/ai-driver/ai-driver.ts +11 -0
- package/src/components/chat-driver/chat-driver.test.ts +109 -0
- package/src/components/chat-driver/chat-driver.ts +78 -1
- package/src/components/chat-interaction-wrapper/chat-interaction-wrapper.ts +29 -0
- package/src/components/orchestrating-driver/orchestrating-driver.cost.test.ts +107 -0
- package/src/components/orchestrating-driver/orchestrating-driver.ts +17 -0
- package/src/index.ts +4 -1
- package/src/main/file-attachments.test.ts +215 -0
- package/src/main/interaction-cost.test.ts +140 -0
- package/src/main/main.template.ts +1 -0
- package/src/main/main.ts +204 -22
- package/src/state/debug-event-log.ts +8 -0
- package/src/types/ai-chat-widget.ts +8 -0
|
@@ -598,6 +598,14 @@
|
|
|
598
598
|
"module": "./components/chat-interaction-wrapper/chat-interaction-wrapper"
|
|
599
599
|
}
|
|
600
600
|
},
|
|
601
|
+
{
|
|
602
|
+
"kind": "js",
|
|
603
|
+
"name": "InteractionCostEventDetail",
|
|
604
|
+
"declaration": {
|
|
605
|
+
"name": "InteractionCostEventDetail",
|
|
606
|
+
"module": "./components/chat-interaction-wrapper/chat-interaction-wrapper"
|
|
607
|
+
}
|
|
608
|
+
},
|
|
601
609
|
{
|
|
602
610
|
"kind": "js",
|
|
603
611
|
"name": "*",
|
|
@@ -3620,6 +3628,25 @@
|
|
|
3620
3628
|
"name": "syncShowingSplash",
|
|
3621
3629
|
"privacy": "private"
|
|
3622
3630
|
},
|
|
3631
|
+
{
|
|
3632
|
+
"kind": "method",
|
|
3633
|
+
"name": "syncUsageTotals",
|
|
3634
|
+
"privacy": "private",
|
|
3635
|
+
"return": {
|
|
3636
|
+
"type": {
|
|
3637
|
+
"text": "void"
|
|
3638
|
+
}
|
|
3639
|
+
},
|
|
3640
|
+
"parameters": [
|
|
3641
|
+
{
|
|
3642
|
+
"name": "messages",
|
|
3643
|
+
"type": {
|
|
3644
|
+
"text": "readonly ChatMessage[]"
|
|
3645
|
+
}
|
|
3646
|
+
}
|
|
3647
|
+
],
|
|
3648
|
+
"description": "Recompute the session's cost and token buckets from a transcript.\n\nTakes the list rather than reading `this.messages`, because it has two callers with\ndifferent sources: the `messages` setter passes the rendered list, and post-resolve external\ncost passes the DRIVER's history — spend recorded there must reach the total WITHOUT\nreassigning `messages`, which would re-render the live interaction row. See\n`onExternalCostRecorded`.\n\nRecomputing (rather than incrementing on append) keeps the totals correct under any mutation\nof the message list — clear-chat, re-render, restore. `sumUsage` recurses into\n`toolCall.subAgentTrace`, so work a sub-agent did (possibly on another provider at other\nrates) counts, and it reads the usage a compaction banked onto its summary, so shrinking\nhistory no longer shrinks the totals."
|
|
3649
|
+
},
|
|
3623
3650
|
{
|
|
3624
3651
|
"kind": "method",
|
|
3625
3652
|
"name": "handleMessagesUpdate",
|
|
@@ -4565,6 +4592,25 @@
|
|
|
4565
4592
|
}
|
|
4566
4593
|
]
|
|
4567
4594
|
},
|
|
4595
|
+
{
|
|
4596
|
+
"kind": "method",
|
|
4597
|
+
"name": "readFileAsBase64",
|
|
4598
|
+
"privacy": "private",
|
|
4599
|
+
"return": {
|
|
4600
|
+
"type": {
|
|
4601
|
+
"text": "Promise<string>"
|
|
4602
|
+
}
|
|
4603
|
+
},
|
|
4604
|
+
"parameters": [
|
|
4605
|
+
{
|
|
4606
|
+
"name": "file",
|
|
4607
|
+
"type": {
|
|
4608
|
+
"text": "File"
|
|
4609
|
+
}
|
|
4610
|
+
}
|
|
4611
|
+
],
|
|
4612
|
+
"description": "The base64 payload of a file, without any `data:` prefix — the raw form\n`ChatImageAttachment.data` specifies (transports add their own framing)."
|
|
4613
|
+
},
|
|
4568
4614
|
{
|
|
4569
4615
|
"kind": "method",
|
|
4570
4616
|
"name": "processFiles",
|
|
@@ -4580,6 +4626,10 @@
|
|
|
4580
4626
|
"type": {
|
|
4581
4627
|
"text": "File[]"
|
|
4582
4628
|
}
|
|
4629
|
+
},
|
|
4630
|
+
{
|
|
4631
|
+
"name": "existingBytes",
|
|
4632
|
+
"default": "0"
|
|
4583
4633
|
}
|
|
4584
4634
|
]
|
|
4585
4635
|
},
|
|
@@ -4683,6 +4733,19 @@
|
|
|
4683
4733
|
}
|
|
4684
4734
|
]
|
|
4685
4735
|
},
|
|
4736
|
+
{
|
|
4737
|
+
"kind": "method",
|
|
4738
|
+
"name": "handleInteractionCost",
|
|
4739
|
+
"parameters": [
|
|
4740
|
+
{
|
|
4741
|
+
"name": "e",
|
|
4742
|
+
"type": {
|
|
4743
|
+
"text": "Event"
|
|
4744
|
+
}
|
|
4745
|
+
}
|
|
4746
|
+
],
|
|
4747
|
+
"description": "A widget reporting spend it incurred AFTER resolving.\n\nNote what this deliberately does NOT do: no `startLoadingTimer()`. This is bookkeeping about a\nturn that already finished, so it must be invisible — showing a spinner would tell the user the\nassistant is working when nothing is pending."
|
|
4748
|
+
},
|
|
4686
4749
|
{
|
|
4687
4750
|
"kind": "field",
|
|
4688
4751
|
"name": "isComposerResizable",
|
|
@@ -5066,7 +5129,7 @@
|
|
|
5066
5129
|
"type": {
|
|
5067
5130
|
"text": "Record<MetaEventType, MetaEventImportance>"
|
|
5068
5131
|
},
|
|
5069
|
-
"default": "{\n 'turn.error': 'high',\n 'tool.failed': 'high',\n 'subagent.failed': 'high',\n 'file.read-failed': 'high',\n 'suggestions.failed': 'high',\n 'context.threshold-crossed': 'high',\n 'context.compacted': 'high',\n 'persistence.restore-orphan': 'high',\n // A save that did not land is silent data loss in progress: the transcript on screen\n // is no longer backed by the store, and every later turn widens the gap. GENC-1511.\n 'persistence.save-failed': 'high',\n\n 'assistant.connected': 'normal',\n 'assistant.disconnected': 'normal',\n 'assistant.popout': 'normal',\n 'assistant.popin': 'normal',\n 'driver.created': 'normal',\n 'state.changed': 'normal',\n 'turn.start': 'normal',\n 'turn.end': 'normal',\n 'turn.retry': 'normal',\n 'tool.unresolved': 'normal',\n 'subagent.started': 'normal',\n 'subagent.completed': 'normal',\n 'agent.handoff': 'normal',\n 'agent.pinned': 'normal',\n 'agent.unpinned': 'normal',\n 'provider.selected': 'normal',\n 'interaction.requested': 'normal',\n 'interaction.resolved': 'normal',\n 'external-diagnostics.folded': 'normal',\n 'session.cleared': 'normal',\n 'session.switched': 'low',\n 'session.switch-deferred': 'low',\n 'persistence.restored': 'normal',\n 'persistence.suppressed': 'normal',\n 'persistence.not-resumable': 'normal',\n\n 'driver.wired': 'low',\n 'driver.unwired': 'low',\n 'context.updated': 'low',\n 'context.condensed': 'low',\n 'panel.toggled': 'low',\n 'persistence.toggled': 'low',\n 'persistence.saved': 'low',\n 'attachment.added': 'low',\n}",
|
|
5132
|
+
"default": "{\n 'turn.error': 'high',\n 'tool.failed': 'high',\n 'subagent.failed': 'high',\n 'file.read-failed': 'high',\n 'suggestions.failed': 'high',\n 'context.threshold-crossed': 'high',\n 'context.compacted': 'high',\n 'persistence.restore-orphan': 'high',\n // A save that did not land is silent data loss in progress: the transcript on screen\n // is no longer backed by the store, and every later turn widens the gap. GENC-1511.\n 'persistence.save-failed': 'high',\n\n 'assistant.connected': 'normal',\n 'assistant.disconnected': 'normal',\n 'assistant.popout': 'normal',\n 'assistant.popin': 'normal',\n 'driver.created': 'normal',\n 'state.changed': 'normal',\n 'turn.start': 'normal',\n 'turn.end': 'normal',\n 'turn.retry': 'normal',\n 'tool.unresolved': 'normal',\n 'subagent.started': 'normal',\n 'subagent.completed': 'normal',\n 'agent.handoff': 'normal',\n 'agent.pinned': 'normal',\n 'agent.unpinned': 'normal',\n 'provider.selected': 'normal',\n 'interaction.requested': 'normal',\n 'interaction.resolved': 'normal',\n // Bookkeeping about an already-finished turn; interesting when reconciling a session total.\n 'interaction.external-cost': 'normal',\n // Spend that was billed but could NOT be recorded (the message was trimmed/condensed away, or\n // the value was not real spend). 'high': it is the one way the session total goes quietly\n // wrong, so it must stand out when reconciling.\n 'interaction.external-cost-dropped': 'high',\n 'external-diagnostics.folded': 'normal',\n 'session.cleared': 'normal',\n 'session.switched': 'low',\n 'session.switch-deferred': 'low',\n 'persistence.restored': 'normal',\n 'persistence.suppressed': 'normal',\n 'persistence.not-resumable': 'normal',\n\n 'driver.wired': 'low',\n 'driver.unwired': 'low',\n 'context.updated': 'low',\n 'context.condensed': 'low',\n 'panel.toggled': 'low',\n 'persistence.toggled': 'low',\n 'persistence.saved': 'low',\n 'attachment.added': 'low',\n}",
|
|
5070
5133
|
"description": "Importance is intrinsic to the event *type*, not the instance, so it's\ndefined once here and stamped automatically by recordMetaEvent — call\nsites never pass it. The `Record` is exhaustive: adding a `MetaEventType`\nwithout a level here is a compile error.\n\n- `high` — failures and hard limits you almost always want to see.\n- `normal` — meaningful session flow you read to follow what happened.\n- `low` — frequent UI / bookkeeping noise, usually safe to skip."
|
|
5071
5134
|
},
|
|
5072
5135
|
{
|
|
@@ -9248,6 +9311,33 @@
|
|
|
9248
9311
|
],
|
|
9249
9312
|
"description": "Request a custom UI interaction. Emits a new message with the interaction.\nTool handlers can call this to pause execution until the user completes the UI interaction.\n\nIf a host requester is wired (sub-agent case), the call delegates upward\nso the interaction lives on the parent — the main UI is only listening to\nthe root driver. Only one interaction may be in flight at any time on a\ngiven root: concurrent calls (e.g. two parallel sub-agents both spawning a\nwidget) throw. Parallel sub-agents are for parallel work, not for user\ninteraction, which is inherently sequential."
|
|
9250
9313
|
},
|
|
9314
|
+
{
|
|
9315
|
+
"kind": "method",
|
|
9316
|
+
"name": "recordExternalCost",
|
|
9317
|
+
"privacy": "public",
|
|
9318
|
+
"return": {
|
|
9319
|
+
"type": {
|
|
9320
|
+
"text": ""
|
|
9321
|
+
}
|
|
9322
|
+
},
|
|
9323
|
+
"parameters": [
|
|
9324
|
+
{
|
|
9325
|
+
"name": "interactionId",
|
|
9326
|
+
"type": {
|
|
9327
|
+
"text": "string"
|
|
9328
|
+
},
|
|
9329
|
+
"description": "the interaction whose message owns the spend."
|
|
9330
|
+
},
|
|
9331
|
+
{
|
|
9332
|
+
"name": "costUsd",
|
|
9333
|
+
"type": {
|
|
9334
|
+
"text": "number"
|
|
9335
|
+
},
|
|
9336
|
+
"description": "a positive delta in USD. Non-finite, zero and negative values are ignored."
|
|
9337
|
+
}
|
|
9338
|
+
],
|
|
9339
|
+
"description": "Add external (non-LLM) spend to an interaction's message AFTER it has resolved.\n\n**Why this exists separately from `resolveInteraction`.** A widget's spend reaches the session\ntotal only through `InteractionResult.costUsd`, which is read exactly once — `resolveInteraction`\ndoes its whole job inside `if (interaction)` and then deletes the entry from\n`pendingInteractions`. That is correct for the resolution itself, but it assumes a widget stops\ncosting money when it resolves, and several deliberately do not: a widget that resolves EARLY so\nthe assistant stops waiting (options shown, a preview rendered) may stay interactive for minutes\nafterwards and do more paid work on the user's behalf. Re-emitting `interaction-completed` does\nnot help — with the interaction gone from the map the call is a silent no-op — so that spend had\nnowhere to go and was simply missing from the figure shown to the user.\n\nAdditive by design: the caller reports a DELTA, not a running total, so repeated calls sum. It\ntouches only `externalCostUsd`; the recorded `interaction.resolved` payload is left exactly as\nthe resolution wrote it, because this is not a second resolution and must not read like one.\n\nDeliberately does NOT require the interaction to be pending, does not resurrect it, and has no\nloading/turn side effects — this is bookkeeping about a turn that already happened."
|
|
9340
|
+
},
|
|
9251
9341
|
{
|
|
9252
9342
|
"kind": "method",
|
|
9253
9343
|
"name": "resolveInteraction",
|
|
@@ -9654,12 +9744,18 @@
|
|
|
9654
9744
|
"text": "CustomEvent"
|
|
9655
9745
|
}
|
|
9656
9746
|
},
|
|
9747
|
+
{
|
|
9748
|
+
"name": "external-cost-recorded",
|
|
9749
|
+
"type": {
|
|
9750
|
+
"text": "CustomEvent"
|
|
9751
|
+
}
|
|
9752
|
+
},
|
|
9657
9753
|
{
|
|
9658
9754
|
"name": "history-updated",
|
|
9659
9755
|
"type": {
|
|
9660
9756
|
"text": "CustomEvent"
|
|
9661
9757
|
},
|
|
9662
|
-
"description": "Fired whenever the in-memory chat history changes (append, tool loop, interaction resolution) with the full history snapshot. detail: `ReadonlyArray<ChatMessage>`"
|
|
9758
|
+
"description": "Fired whenever the in-memory chat history changes (append, tool loop, interaction resolution, post-resolve external cost) with the full history snapshot. detail: `ReadonlyArray<ChatMessage>`"
|
|
9663
9759
|
},
|
|
9664
9760
|
{
|
|
9665
9761
|
"name": "interaction-stop",
|
|
@@ -9857,6 +9953,10 @@
|
|
|
9857
9953
|
{
|
|
9858
9954
|
"description": "Fired when the interaction finishes. detail: `InteractionCompletedEventDetail`",
|
|
9859
9955
|
"name": "interaction-completed"
|
|
9956
|
+
},
|
|
9957
|
+
{
|
|
9958
|
+
"description": "Fired when a widget spends money AFTER resolving. detail: `InteractionCostEventDetail`",
|
|
9959
|
+
"name": "interaction-cost"
|
|
9860
9960
|
}
|
|
9861
9961
|
],
|
|
9862
9962
|
"superclass": {
|
|
@@ -10154,6 +10254,30 @@
|
|
|
10154
10254
|
}
|
|
10155
10255
|
]
|
|
10156
10256
|
},
|
|
10257
|
+
{
|
|
10258
|
+
"kind": "method",
|
|
10259
|
+
"name": "recordExternalCost",
|
|
10260
|
+
"return": {
|
|
10261
|
+
"type": {
|
|
10262
|
+
"text": "boolean"
|
|
10263
|
+
}
|
|
10264
|
+
},
|
|
10265
|
+
"parameters": [
|
|
10266
|
+
{
|
|
10267
|
+
"name": "interactionId",
|
|
10268
|
+
"type": {
|
|
10269
|
+
"text": "string"
|
|
10270
|
+
}
|
|
10271
|
+
},
|
|
10272
|
+
{
|
|
10273
|
+
"name": "costUsd",
|
|
10274
|
+
"type": {
|
|
10275
|
+
"text": "number"
|
|
10276
|
+
}
|
|
10277
|
+
}
|
|
10278
|
+
],
|
|
10279
|
+
"description": "Delegated for the same reason as `resolveInteraction`: the history that owns the\ninteraction — and therefore the cost — lives on the wrapped driver, not here."
|
|
10280
|
+
},
|
|
10157
10281
|
{
|
|
10158
10282
|
"kind": "method",
|
|
10159
10283
|
"name": "getInteractionContext",
|
|
@@ -10807,101 +10931,6 @@
|
|
|
10807
10931
|
}
|
|
10808
10932
|
]
|
|
10809
10933
|
},
|
|
10810
|
-
{
|
|
10811
|
-
"kind": "javascript-module",
|
|
10812
|
-
"path": "src/components/settings-modal/index.ts",
|
|
10813
|
-
"declarations": [],
|
|
10814
|
-
"exports": [
|
|
10815
|
-
{
|
|
10816
|
-
"kind": "js",
|
|
10817
|
-
"name": "SettingsModalTemplate",
|
|
10818
|
-
"declaration": {
|
|
10819
|
-
"name": "SettingsModalTemplate",
|
|
10820
|
-
"module": "./settings-modal.template"
|
|
10821
|
-
}
|
|
10822
|
-
},
|
|
10823
|
-
{
|
|
10824
|
-
"kind": "js",
|
|
10825
|
-
"name": "type",
|
|
10826
|
-
"declaration": {
|
|
10827
|
-
"name": "type",
|
|
10828
|
-
"module": "./settings-modal.template"
|
|
10829
|
-
}
|
|
10830
|
-
},
|
|
10831
|
-
{
|
|
10832
|
-
"kind": "js",
|
|
10833
|
-
"name": "SettingsModalTab",
|
|
10834
|
-
"declaration": {
|
|
10835
|
-
"name": "SettingsModalTab",
|
|
10836
|
-
"module": "./settings-modal.template"
|
|
10837
|
-
}
|
|
10838
|
-
},
|
|
10839
|
-
{
|
|
10840
|
-
"kind": "js",
|
|
10841
|
-
"name": "settingsModalStyles",
|
|
10842
|
-
"declaration": {
|
|
10843
|
-
"name": "settingsModalStyles",
|
|
10844
|
-
"module": "./settings-modal.styles"
|
|
10845
|
-
}
|
|
10846
|
-
}
|
|
10847
|
-
]
|
|
10848
|
-
},
|
|
10849
|
-
{
|
|
10850
|
-
"kind": "javascript-module",
|
|
10851
|
-
"path": "src/components/settings-modal/settings-modal.styles.ts",
|
|
10852
|
-
"declarations": [
|
|
10853
|
-
{
|
|
10854
|
-
"kind": "variable",
|
|
10855
|
-
"name": "settingsModalStyles",
|
|
10856
|
-
"default": "css`\n /*\n * Section headings come from the shared fragment the package also exports, so a host titling\n * its own slotted section applies the same rule rather than a copy of it.\n */\n ${assistantSettingsSectionTitleStyles}\n\n *,\n *::before,\n *::after {\n box-sizing: border-box;\n }\n\n .settings-modal {\n --ai-tier-badge-bg-mix: 14%;\n --ai-tier-badge-text-lighten: 18%;\n }\n\n .settings-modal::part(dialog) {\n max-width: 620px;\n width: calc(100vw - 48px);\n max-height: calc(100vh - 48px);\n padding: 0;\n }\n\n .settings-modal::part(content) {\n display: flex;\n flex-direction: column;\n min-height: 0;\n padding: 0;\n width: 100%;\n }\n\n .settings-modal::part(top) {\n padding: calc(var(--design-unit) * 4px) calc(var(--design-unit) * 5px);\n border-bottom: 1px solid var(--neutral-stroke-rest);\n }\n\n .settings-modal::part(bottom) {\n padding: calc(var(--design-unit) * 4px) calc(var(--design-unit) * 5px);\n border-top: 1px solid var(--neutral-stroke-rest);\n border-bottom-left-radius: var(--card-border-radius, calc(var(--control-corner-radius) * 1px));\n border-bottom-right-radius: var(--card-border-radius, calc(var(--control-corner-radius) * 1px));\n background-color: var(--neutral-layer-2);\n }\n\n .settings-modal-header {\n display: flex;\n align-items: center;\n min-width: 0;\n }\n\n .settings-modal-title {\n margin: 0;\n font-size: 16px;\n font-weight: 600;\n display: flex;\n align-items: center;\n gap: calc(var(--design-unit) * 2px);\n color: var(--neutral-foreground-rest);\n }\n\n .settings-modal-content {\n display: flex;\n flex-direction: column;\n min-height: 0;\n flex: 1 1 auto;\n }\n\n .settings-modal-tabs::part(tablist) {\n border-bottom: 0;\n padding: 0 calc(var(--design-unit) * 5px);\n column-gap: calc(var(--design-unit) * 5px);\n }\n\n .settings-modal-tabs::part(activeIndicator) {\n height: 2px;\n }\n\n .settings-modal-tab {\n padding: 0;\n font-weight: 600;\n gap: 6px;\n }\n\n .settings-modal-tab[aria-selected='true'] {\n color: var(--neutral-foreground-rest);\n }\n\n .settings-modal-tabs::part(tabpanel) {\n border-top: 1px solid var(--neutral-stroke-rest);\n }\n\n .settings-modal-tab-panel {\n display: flex;\n flex-direction: column;\n min-height: 0;\n flex: 1 1 auto;\n padding: 0;\n border-radius: 0;\n }\n\n .settings-modal-tab-panel[hidden] {\n display: none !important;\n }\n\n .settings-modal-body {\n overflow-y: auto;\n padding: 0 calc(var(--design-unit) * 5px) calc(var(--design-unit) * 2px);\n max-height: min(60vh, 520px);\n }\n\n .settings-modal-section {\n padding: calc(var(--design-unit) * 5px) 0;\n border-bottom: 1px solid var(--neutral-stroke-rest);\n }\n\n .settings-modal-section:last-child {\n border-bottom: none;\n }\n\n /*\n * Only the spacing. Everything else about this heading comes from the exported fragment\n * above, which the assistant and its hosts share; margin is deliberately not in there,\n * since the gap below a heading depends on the layout around it.\n */\n .ai-settings-section-title {\n margin: 0 0 calc(var(--design-unit) * 3px);\n }\n\n .settings-modal-section-note {\n margin: calc(var(--design-unit) * -2px) 0 calc(var(--design-unit) * 3px);\n font-size: 12px;\n line-height: 1.5;\n color: var(--neutral-foreground-hint);\n }\n\n .settings-modal-section-body {\n display: flex;\n flex-direction: column;\n gap: calc(var(--design-unit) * 4px);\n }\n\n .settings-toggle-row {\n display: flex;\n align-items: flex-start;\n gap: calc(var(--design-unit) * 3px);\n padding: 0 0 calc(var(--design-unit) * 4px) 0;\n border-bottom: 1px solid var(--neutral-stroke-rest);\n }\n\n .settings-toggle-row:last-child {\n border-bottom: none;\n padding-bottom: calc(var(--design-unit) * 1px);\n }\n\n .settings-toggle-row.is-stacked {\n flex-direction: column;\n gap: calc(var(--design-unit) * 2px);\n }\n\n /*\n * For rows whose control is a button rather than a switch. The base row top-aligns, which suits\n * a switch (it lands level with the title), but a button is tall enough that top-aligning leaves\n * it above the title-plus-description block it acts on.\n */\n .settings-toggle-row.is-centered {\n align-items: center;\n }\n\n .settings-animation-list {\n display: flex;\n flex-direction: column;\n width: 100%;\n border: 1px solid var(--neutral-stroke-rest);\n border-radius: calc(var(--control-corner-radius) * 1.2px);\n padding: calc(var(--design-unit) * 2px);\n gap: calc(var(--design-unit) * 2px);\n }\n\n .settings-animation-list .settings-toggle-row {\n border: 0;\n }\n\n .settings-animation-category {\n display: flex;\n flex-direction: column;\n gap: calc(var(--design-unit) * 4px);\n }\n\n .settings-animation-category-label {\n font-weight: 600;\n color: var(--neutral-foreground-hint);\n }\n\n .settings-toggle-row + .settings-animation-category-label {\n border-top: 1px solid var(--neutral-stroke-rest);\n padding-top: calc(var(--design-unit) * 2px);\n }\n\n .settings-animation-option {\n padding: 0;\n margin: 0;\n }\n\n .settings-animation-option:last-child {\n border-bottom: none;\n padding-bottom: calc(var(--design-unit) * 2px);\n }\n\n .settings-toggle-text {\n flex: 1;\n min-width: 0;\n }\n\n .settings-toggle-title {\n font-size: 14px;\n font-weight: 500;\n color: var(--neutral-foreground-rest);\n }\n\n .settings-toggle-desc {\n font-size: 12px;\n color: var(--neutral-foreground-hint);\n line-height: 1.5;\n max-width: 92%;\n }\n\n .settings-toggle-control {\n flex: 0 0 auto;\n min-width: 0;\n }\n\n .settings-toggle-control-wide {\n width: 100%;\n max-width: 280px;\n }\n\n .settings-toggle-row.is-stacked .settings-toggle-desc {\n max-width: none;\n }\n\n .settings-toggle-row.is-stacked .settings-toggle-control-wide {\n width: 100%;\n max-width: none;\n }\n\n /*\n * Sub-heading over the provider toggle group. Unlike the section headings it sits INSIDE\n * .settings-modal-section-body, so the parent's 4-unit flex gap already separates it from the\n * first toggle — and flex does not collapse margins, so the shared rule's 3-unit margin-bottom\n * would add to that gap rather than replace it, leaving the label adrift from the rows it\n * names. Zero the margin and pull back 2 of the 4 gap units, expressed in the same token so it\n * tracks if that gap changes.\n */\n .settings-app-toggles-title {\n margin: 0 0 calc(var(--design-unit) * -2px);\n }\n\n .settings-app-toggle-block {\n display: flex;\n flex-direction: column;\n gap: calc(var(--design-unit) * 1px);\n }\n\n .settings-toggle-hint {\n font-size: 12px;\n color: var(--neutral-foreground-hint);\n line-height: 1.4;\n padding-left: calc(var(--design-unit) * 1px);\n font-style: italic;\n }\n\n /*\n * The budget meter (GENC-1464) is the context indicator's money sibling and\n * shares its row anatomy 1:1, so the budget classes JOIN these rules rather\n * than restating them — one place to restyle the header/label/figures shape,\n * and the two indicators cannot drift apart. Design-system tokens only, and\n * unfallbacked: every token here ships a DS default, so a var(--x, y)\n * fallback would be dead code that hides a broken token name (#2411/#2436\n * review rule, pinned by settings-modal.styles.test.ts).\n */\n .settings-context-header,\n .settings-budget-header {\n display: flex;\n justify-content: space-between;\n gap: calc(var(--design-unit) * 2px);\n margin-bottom: calc(var(--design-unit) * 2px);\n font-size: 12px;\n }\n\n .settings-context-label,\n .settings-budget-label {\n color: var(--neutral-foreground-hint);\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n }\n\n .settings-context-tokens,\n .settings-budget-figures {\n color: var(--neutral-foreground-rest);\n }\n\n /* Freshness footer (GENC-1464): refresh button + label under the vendor\n rows, left-aligned — a footnote to the figures above it. The button's\n inline padding is pinned and cancelled by the container margin so the\n ICON GLYPH (not the button box) sits flush with the budget labels'\n left edge, whatever the density tokens say. */\n .settings-budget-toolbar {\n display: flex;\n justify-content: flex-start;\n align-items: center;\n gap: 4px;\n margin-inline-start: -4px;\n }\n\n .settings-budget-refresh::part(control) {\n padding-inline: 4px;\n }\n\n .settings-budget-updated {\n color: var(--neutral-foreground-hint);\n font-size: 11px;\n }\n\n /*\n * ONE rhythm for every meter row. The outer wrapper holds the context\n * indicator and the budget meter; the inner one holds the budget meter's\n * vendor rows — and both gap by the same 2 units (half the section body's 4)\n * so that visually identical meter rows are evenly spaced whatever mix of\n * them is on screen. Without the wrapper the context row inherits the body's\n * 4-unit gap while the vendor rows keep 2, and three identical-looking rows\n * read as an uneven 4/2/2. The wrapper renders only when at least one meter\n * does — see settingsUsageMetersTemplate for why an always-on one would cost\n * a stray gap.\n */\n .settings-usage-meters,\n .settings-budget-usage {\n display: flex;\n flex-direction: column;\n gap: calc(var(--design-unit) * 2px);\n }\n\n .settings-current-build,\n .settings-lifetime-usage {\n background: var(--neutral-layer-2);\n border: 1px solid var(--neutral-stroke-rest);\n border-radius: calc(var(--control-corner-radius) * 1.5px);\n padding: calc(var(--design-unit) * 4px);\n margin: calc(var(--design-unit) * 3px) 0;\n }\n\n /*\n * Deliberately quieter than the live block: no pulsing badge and no accent, so the eye lands\n * on what is being built now — which is why that block leads the tab and this one follows,\n * under the history heading whose rows it totals.\n */\n .settings-lifetime-usage-top {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: calc(var(--design-unit) * 2px);\n margin-bottom: calc(var(--design-unit) * 3px);\n }\n\n .settings-lifetime-label {\n font-size: 10px;\n line-height: 1;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n color: var(--neutral-foreground-hint);\n }\n\n .settings-current-build-top {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: calc(var(--design-unit) * 2px);\n margin-bottom: calc(var(--design-unit) * 3px);\n }\n\n /*\n * Optically centring these 10px caps against the 14px project name beside them. Flex offers\n * only two alignments and neither lands it, because a box centre and a text baseline are\n * different lines:\n * - align-items: center sits the badge baseline about 1.2px high\n * - align-items: baseline matches baselines, but the caps then read as bottom-aligned\n * against the name's ascenders, so the badge looks low\n * The eye wants the midpoint, so: centre the boxes, then translate down by the ~1px of\n * overshoot. This is a nudge, but it corrects a fixed relationship between two font sizes\n * rather than some neighbouring element's layout, so unlike a margin tuned to a sibling it\n * cannot rot when that sibling changes. In em, so it tracks the badge text.\n *\n * display: block, not inline-flex: a flex container has no text baseline of its own and\n * synthesizes one from its first child, which here is the dot, an empty span. As a block box\n * the badge takes the baseline of its first line, i.e. the text.\n */\n .settings-live-badge {\n display: block;\n font-size: 10px;\n line-height: 1;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n color: var(--accent-fill-rest);\n white-space: nowrap;\n transform: translateY(0.1em);\n }\n\n .settings-live-dot {\n display: inline-block;\n width: 7px;\n height: 7px;\n\n /* Replaces the flex gap the badge no longer has. */\n margin-right: calc(var(--design-unit) * 1px);\n\n /* Centres the dot on the text beside it without disturbing that text's baseline. */\n vertical-align: middle;\n border-radius: 50%;\n background: var(--accent-fill-rest);\n animation: settings-live-pulse 1.6s ease-out infinite;\n }\n\n @keyframes settings-live-pulse {\n 0% {\n box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent-fill-rest) 55%, transparent);\n }\n\n 70% {\n box-shadow: 0 0 0 7px transparent;\n }\n\n 100% {\n box-shadow: 0 0 0 0 transparent;\n }\n }\n\n .settings-current-build-name {\n font-size: 14px;\n line-height: 1;\n font-weight: 600;\n }\n\n /*\n * Cost and the token buckets side by side: one number in its own box, the four buckets\n * sharing the box next to it. Stretch, so the two are the same height however tall the 22px\n * cost value makes the left one.\n */\n .settings-usage-row {\n display: flex;\n flex-wrap: wrap;\n align-items: stretch;\n gap: calc(var(--design-unit) * 3px);\n margin-bottom: calc(var(--design-unit) * 3px);\n }\n\n .settings-usage-row-tokens {\n display: flex;\n flex: 1 1 auto;\n min-width: 0;\n }\n\n .settings-build-stat {\n min-width: 0;\n background: var(--neutral-layer-1);\n border: 1px solid var(--neutral-stroke-rest);\n border-radius: calc(var(--control-corner-radius) * 1px);\n padding: calc(var(--design-unit) * 3px);\n }\n\n .settings-build-stat-label {\n font-size: 10px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n color: var(--neutral-foreground-hint);\n }\n\n .settings-build-stat-value {\n font-size: 22px;\n font-weight: 700;\n margin-top: calc(var(--design-unit) * 1px);\n color: var(--neutral-foreground-rest);\n }\n\n .settings-build-stat-value .prefix,\n .settings-build-stat-value .suffix {\n color: var(--accent-fill-rest);\n }\n\n /*\n * Per-bucket token split, beneath the headline cost. Small paired label/value cells\n * rather than four more stat cards: they are the detail behind the cost above, so they\n * have to read as subordinate to it rather than compete with it. The four buckets ARE\n * the token figure now — there is no merged total anywhere for them to break down.\n */\n .settings-token-breakdown {\n display: flex;\n flex-direction: column;\n\n /*\n * Tight against the buckets below. One design unit rather than two, because each bucket\n * separates its own label from its value by only 2px — at two units the title sat four\n * times further from the bucket labels than they sit from their own numbers, which read\n * as a gap between unrelated blocks instead of a heading over its content.\n */\n gap: calc(var(--design-unit) * 1px);\n margin-bottom: calc(var(--design-unit) * 3px);\n padding: calc(var(--design-unit) * 2px) calc(var(--design-unit) * 3px);\n background: var(--neutral-layer-1);\n border: 1px solid var(--neutral-stroke-rest);\n border-radius: calc(var(--control-corner-radius) * 1px);\n }\n\n /*\n * Titles the box, because nothing else in it named the unit. Same styling and position as\n * the cost box's label, so two boxes sitting side by side are titled the same way; the\n * hierarchy against the bucket labels comes from position, not a second type size.\n */\n .settings-token-breakdown-label {\n font-size: 10px;\n line-height: 1;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n color: var(--neutral-foreground-hint);\n }\n\n .settings-token-buckets {\n display: flex;\n flex-wrap: wrap;\n gap: calc(var(--design-unit) * 4px);\n }\n\n .settings-token-bucket {\n display: flex;\n\n /*\n * min-content (not 0) so a bucket cannot be shrunk narrower than its own unbreakable label.\n * With the label set to nowrap, a zero minimum would overflow it into the next column instead\n * of wrapping — so the container's flex-wrap takes over and moves a bucket to a second row,\n * which is the degradation we want when the modal is dragged narrow.\n */\n min-width: min-content;\n flex-direction: column;\n gap: 2px;\n }\n\n .settings-token-bucket-label {\n font-size: 10px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n color: var(--neutral-foreground-hint);\n\n /*\n * One line, always. The rate marker is a separate inline box after collapsible whitespace, so\n * a wrappable label gives it a break opportunity of its own — in a narrow column it orphaned\n * onto line 2, which also pushed that bucket's value a line below the other three and broke\n * the row of figures. nowrap removes every break opportunity in the label, the space before\n * the marker included, so no glue character is needed.\n */\n white-space: nowrap;\n }\n\n .settings-token-bucket-value {\n font-size: 13px;\n font-weight: 600;\n color: var(--neutral-foreground-rest);\n }\n\n /*\n * The price-direction marker on a cache bucket's label — down for reads (a fraction of the\n * uncached input rate), up for writes (a premium over it). Three tiers, so a bucket with no\n * marker is the reference rate rather than an unlabelled fourth case.\n *\n * Carried by the label rather than the value, which is what this used to do: cache reads are\n * usually the largest bucket by a wide margin in an agentic session, so the value was muted to\n * stop the biggest number reading as the dominant cost driver. That priced the treatment to the\n * wrong thing — a dimmed 0 (every Gemini cache write) looked disabled next to a normal 0, and a\n * rate is a property of the bucket, not of this turn's figure.\n *\n * Sized down and nudged up rather than tinted: the label is already the hint colour, so a\n * dimmer marker would vanish and a brighter one would out-shout the word it qualifies.\n */\n .settings-token-bucket-rate {\n font-size: 8px;\n line-height: 1;\n vertical-align: 0.15em;\n letter-spacing: 0;\n }\n\n /*\n * Usage-row overrides. Placed after the base rules they refine rather than up beside\n * .settings-usage-row itself, because stylelint's no-descending-specificity rejects a\n * compound selector appearing before the plain one it overrides.\n */\n\n /* Sized by its content; the buckets take the rest, since four cells need the width. */\n .settings-usage-row > .settings-build-stat {\n flex: 0 1 auto;\n }\n\n .settings-usage-row .settings-token-breakdown {\n flex: 1 1 auto;\n\n /* The row owns the spacing now; this centres the column against the taller cost box. */\n justify-content: center;\n margin-bottom: 0;\n }\n\n /*\n * Basis auto, not 0: a zero basis splits the row into four equal columns, so CACHE WRITE is\n * squeezed exactly as hard as INPUT despite being twice the label. Sizing from content gives the\n * long labels the room their text needs and lets the short ones concede it, which is what makes\n * white-space: nowrap above safe — every column starts at least as wide as its own content,\n * then shares the slack. Still grows and shrinks, so the four stay flush edge to edge.\n */\n .settings-usage-row .settings-token-bucket {\n flex: 1 1 auto;\n }\n\n .settings-model-tags {\n display: flex;\n flex-wrap: wrap;\n gap: calc(var(--design-unit) * 2px);\n }\n\n .settings-model-tag {\n font-size: 11px;\n font-weight: 600;\n padding: 3px 9px;\n border-radius: 20px;\n font-family: ui-monospace, monospace;\n border: 1px solid var(--neutral-stroke-rest);\n }\n\n .settings-model-tag,\n .settings-cost-history-icon {\n --model-tag-color: var(--neutral-foreground-rest);\n\n background: color-mix(\n in srgb,\n var(--model-tag-color) var(--ai-tier-badge-bg-mix),\n var(--neutral-layer-1)\n );\n border-color: color-mix(in srgb, var(--model-tag-color) 20%, var(--neutral-layer-1));\n color: color-mix(in srgb, var(--model-tag-color), white var(--ai-tier-badge-text-lighten));\n }\n\n .settings-cost-history-title {\n font-size: 11px;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n color: var(--neutral-foreground-hint);\n font-weight: 600;\n margin: calc(var(--design-unit) * 4px) 0 calc(var(--design-unit) * 2px);\n }\n\n /*\n * The heading labels the lifetime card as well as the rows, so the card gives up its own top\n * margin and the gap becomes the heading's alone. Adjacent siblings collapse to the LARGER of\n * the two margins, so leaving both would space the caps 3 units off the block they label while\n * the rows below sit at 2 — reading as though the heading belonged to neither.\n */\n .settings-cost-history-title + .settings-lifetime-usage {\n margin-top: 0;\n }\n\n .settings-cost-history {\n display: flex;\n flex-direction: column;\n gap: calc(var(--design-unit) * 2px);\n padding-bottom: calc(var(--design-unit) * 2px);\n }\n\n .settings-cost-history-icon {\n flex: 0 0 auto;\n width: 36px;\n height: 36px;\n border-radius: calc(var(--control-corner-radius) * 1px);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-size: 16px;\n font-weight: 700;\n border: 1px solid var(--neutral-stroke-rest);\n }\n\n .settings-cost-history-row {\n display: flex;\n flex-direction: column;\n background: var(--neutral-layer-1);\n border: 1px solid var(--neutral-stroke-rest);\n border-radius: calc(var(--control-corner-radius) * 1.5px);\n padding: calc(var(--design-unit) * 4px);\n }\n\n .settings-cost-history-row-content {\n display: flex;\n align-items: flex-start;\n gap: calc(var(--design-unit) * 2px);\n }\n\n /*\n * All of these carry a ?hidden binding, and all declare display: flex — which silently beat the\n * UA [hidden] { display: none } rule, because author-origin declarations outrank user-agent\n * origin whatever the specificity. Without this, showTokens: false still rendered the full\n * breakdown in the lifetime block, the live block and every history row, and an empty row list\n * still occupied its padding under the lifetime total. Sibling elements that hide correctly do\n * so only by declaring no display at all. Same treatment as .settings-modal-tab-panel[hidden]\n * near the top of this file.\n */\n .settings-usage-row-tokens[hidden],\n .settings-cost-history[hidden],\n .settings-cost-history-row-content[hidden] {\n display: none !important;\n }\n\n /*\n * The buckets inside a history row drop the panel treatment they carry in the summary\n * blocks — the row is already a bordered card, and a second box inside it reads as a\n * nested panel. The adjacent-sibling rule above supplies the separator instead.\n */\n .settings-cost-history-row-breakdown .settings-token-breakdown {\n flex: 1 1 auto;\n margin-bottom: 0;\n padding: 0;\n background: none;\n border: none;\n }\n\n .settings-cost-history-row-content + .settings-cost-history-row-content {\n border-top: 1px solid var(--neutral-stroke-rest);\n margin-top: calc(var(--design-unit) * 2px);\n padding-top: calc(var(--design-unit) * 2px);\n }\n\n .settings-cost-history-row-body {\n flex: 1 1 auto;\n min-width: 0;\n display: flex;\n gap: calc(var(--design-unit) * 2px);\n justify-content: space-between;\n }\n\n /*\n * Holds the cost and the delete button as one centred pair. The button used to be a sibling\n * of the whole row body, kept in place by a -2px nudge tuned for when the metrics column had\n * two lines (cost plus a merged token total). Dropping that second line left the nudge\n * compensating for nothing. Centring the two against each other needs no magic number and\n * cannot drift again if the metrics content changes.\n *\n * NB this rule previously set align-items with no display:flex, so it had no effect at all.\n */\n .settings-cost-history-row-right {\n display: flex;\n align-items: center;\n gap: calc(var(--design-unit) * 1px);\n flex: 0 0 auto;\n }\n\n .settings-cost-history-row-cost,\n .settings-cost-history-row-title {\n font-size: 14px;\n font-weight: 600;\n color: var(--neutral-foreground-rest);\n min-width: 0;\n }\n\n .settings-cost-history-row-metrics {\n display: flex;\n flex-direction: column;\n flex: 0 0 auto;\n font-size: 13px;\n font-family: ui-monospace, monospace;\n color: var(--neutral-foreground-rest);\n white-space: nowrap;\n }\n\n .settings-cost-history-row-date {\n font-size: 12px;\n color: var(--neutral-foreground-hint);\n }\n\n .settings-cost-history-delete {\n flex: 0 0 auto;\n color: var(--neutral-foreground-hint);\n }\n\n .settings-cost-history-delete:hover {\n color: var(--error-color);\n }\n\n .settings-cost-history ::slotted(*) {\n display: block;\n }\n\n .settings-modal-footer {\n width: 100%;\n }\n\n .settings-done-button {\n margin-left: auto;\n }\n\n .settings-modal-footer-actions {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: calc(var(--design-unit) * 2px);\n width: 100%;\n }\n\n .settings-disclaimer {\n font-size: 12px;\n color: var(--neutral-foreground-hint);\n max-width: 60%;\n line-height: 1.4;\n }\n\n /*\n * Inline spinner shown in the \"Download agent log\" button while the log builds\n * (GENC-1351 §5.8) — the lifetime-stitch path is an async provider round-trip.\n * Sized to the button label and tinted with currentColor so it tracks the\n * button's text colour in every appearance / theme.\n */\n [part='download-button'] .download-spinner {\n display: inline-block;\n width: 12px;\n height: 12px;\n margin-right: calc(var(--design-unit) * 1px);\n vertical-align: -1px;\n border: 2px solid currentColor;\n border-right-color: transparent;\n border-radius: 50%;\n animation: download-spin 0.6s linear infinite;\n }\n\n @keyframes download-spin {\n to {\n transform: rotate(360deg);\n }\n }\n`",
|
|
10857
|
-
"description": "Styles for the assistant settings modal (tabs, sections, toggles, cost summary)."
|
|
10858
|
-
}
|
|
10859
|
-
],
|
|
10860
|
-
"exports": [
|
|
10861
|
-
{
|
|
10862
|
-
"kind": "js",
|
|
10863
|
-
"name": "settingsModalStyles",
|
|
10864
|
-
"declaration": {
|
|
10865
|
-
"name": "settingsModalStyles",
|
|
10866
|
-
"module": "src/components/settings-modal/settings-modal.styles.ts"
|
|
10867
|
-
}
|
|
10868
|
-
}
|
|
10869
|
-
]
|
|
10870
|
-
},
|
|
10871
|
-
{
|
|
10872
|
-
"kind": "javascript-module",
|
|
10873
|
-
"path": "src/components/settings-modal/settings-modal.template.ts",
|
|
10874
|
-
"declarations": [
|
|
10875
|
-
{
|
|
10876
|
-
"kind": "function",
|
|
10877
|
-
"name": "SettingsModalTemplate",
|
|
10878
|
-
"return": {
|
|
10879
|
-
"type": {
|
|
10880
|
-
"text": "ViewTemplate<FoundationAiAssistant>"
|
|
10881
|
-
}
|
|
10882
|
-
},
|
|
10883
|
-
"parameters": [
|
|
10884
|
-
{
|
|
10885
|
-
"name": "designSystemPrefix",
|
|
10886
|
-
"type": {
|
|
10887
|
-
"text": "string"
|
|
10888
|
-
}
|
|
10889
|
-
}
|
|
10890
|
-
],
|
|
10891
|
-
"description": "Modal shell for assistant settings — Configuration and Usage tabs, section slots for\napp-specific UI, and built-in chat-bot toggles."
|
|
10892
|
-
}
|
|
10893
|
-
],
|
|
10894
|
-
"exports": [
|
|
10895
|
-
{
|
|
10896
|
-
"kind": "js",
|
|
10897
|
-
"name": "SettingsModalTemplate",
|
|
10898
|
-
"declaration": {
|
|
10899
|
-
"name": "SettingsModalTemplate",
|
|
10900
|
-
"module": "src/components/settings-modal/settings-modal.template.ts"
|
|
10901
|
-
}
|
|
10902
|
-
}
|
|
10903
|
-
]
|
|
10904
|
-
},
|
|
10905
10934
|
{
|
|
10906
10935
|
"kind": "javascript-module",
|
|
10907
10936
|
"path": "src/state/persistence/build-timeline-entries.ts",
|
|
@@ -11487,6 +11516,101 @@
|
|
|
11487
11516
|
}
|
|
11488
11517
|
}
|
|
11489
11518
|
]
|
|
11519
|
+
},
|
|
11520
|
+
{
|
|
11521
|
+
"kind": "javascript-module",
|
|
11522
|
+
"path": "src/components/settings-modal/index.ts",
|
|
11523
|
+
"declarations": [],
|
|
11524
|
+
"exports": [
|
|
11525
|
+
{
|
|
11526
|
+
"kind": "js",
|
|
11527
|
+
"name": "SettingsModalTemplate",
|
|
11528
|
+
"declaration": {
|
|
11529
|
+
"name": "SettingsModalTemplate",
|
|
11530
|
+
"module": "./settings-modal.template"
|
|
11531
|
+
}
|
|
11532
|
+
},
|
|
11533
|
+
{
|
|
11534
|
+
"kind": "js",
|
|
11535
|
+
"name": "type",
|
|
11536
|
+
"declaration": {
|
|
11537
|
+
"name": "type",
|
|
11538
|
+
"module": "./settings-modal.template"
|
|
11539
|
+
}
|
|
11540
|
+
},
|
|
11541
|
+
{
|
|
11542
|
+
"kind": "js",
|
|
11543
|
+
"name": "SettingsModalTab",
|
|
11544
|
+
"declaration": {
|
|
11545
|
+
"name": "SettingsModalTab",
|
|
11546
|
+
"module": "./settings-modal.template"
|
|
11547
|
+
}
|
|
11548
|
+
},
|
|
11549
|
+
{
|
|
11550
|
+
"kind": "js",
|
|
11551
|
+
"name": "settingsModalStyles",
|
|
11552
|
+
"declaration": {
|
|
11553
|
+
"name": "settingsModalStyles",
|
|
11554
|
+
"module": "./settings-modal.styles"
|
|
11555
|
+
}
|
|
11556
|
+
}
|
|
11557
|
+
]
|
|
11558
|
+
},
|
|
11559
|
+
{
|
|
11560
|
+
"kind": "javascript-module",
|
|
11561
|
+
"path": "src/components/settings-modal/settings-modal.styles.ts",
|
|
11562
|
+
"declarations": [
|
|
11563
|
+
{
|
|
11564
|
+
"kind": "variable",
|
|
11565
|
+
"name": "settingsModalStyles",
|
|
11566
|
+
"default": "css`\n /*\n * Section headings come from the shared fragment the package also exports, so a host titling\n * its own slotted section applies the same rule rather than a copy of it.\n */\n ${assistantSettingsSectionTitleStyles}\n\n *,\n *::before,\n *::after {\n box-sizing: border-box;\n }\n\n .settings-modal {\n --ai-tier-badge-bg-mix: 14%;\n --ai-tier-badge-text-lighten: 18%;\n }\n\n .settings-modal::part(dialog) {\n max-width: 620px;\n width: calc(100vw - 48px);\n max-height: calc(100vh - 48px);\n padding: 0;\n }\n\n .settings-modal::part(content) {\n display: flex;\n flex-direction: column;\n min-height: 0;\n padding: 0;\n width: 100%;\n }\n\n .settings-modal::part(top) {\n padding: calc(var(--design-unit) * 4px) calc(var(--design-unit) * 5px);\n border-bottom: 1px solid var(--neutral-stroke-rest);\n }\n\n .settings-modal::part(bottom) {\n padding: calc(var(--design-unit) * 4px) calc(var(--design-unit) * 5px);\n border-top: 1px solid var(--neutral-stroke-rest);\n border-bottom-left-radius: var(--card-border-radius, calc(var(--control-corner-radius) * 1px));\n border-bottom-right-radius: var(--card-border-radius, calc(var(--control-corner-radius) * 1px));\n background-color: var(--neutral-layer-2);\n }\n\n .settings-modal-header {\n display: flex;\n align-items: center;\n min-width: 0;\n }\n\n .settings-modal-title {\n margin: 0;\n font-size: 16px;\n font-weight: 600;\n display: flex;\n align-items: center;\n gap: calc(var(--design-unit) * 2px);\n color: var(--neutral-foreground-rest);\n }\n\n .settings-modal-content {\n display: flex;\n flex-direction: column;\n min-height: 0;\n flex: 1 1 auto;\n }\n\n .settings-modal-tabs::part(tablist) {\n border-bottom: 0;\n padding: 0 calc(var(--design-unit) * 5px);\n column-gap: calc(var(--design-unit) * 5px);\n }\n\n .settings-modal-tabs::part(activeIndicator) {\n height: 2px;\n }\n\n .settings-modal-tab {\n padding: 0;\n font-weight: 600;\n gap: 6px;\n }\n\n .settings-modal-tab[aria-selected='true'] {\n color: var(--neutral-foreground-rest);\n }\n\n .settings-modal-tabs::part(tabpanel) {\n border-top: 1px solid var(--neutral-stroke-rest);\n }\n\n .settings-modal-tab-panel {\n display: flex;\n flex-direction: column;\n min-height: 0;\n flex: 1 1 auto;\n padding: 0;\n border-radius: 0;\n }\n\n .settings-modal-tab-panel[hidden] {\n display: none !important;\n }\n\n .settings-modal-body {\n overflow-y: auto;\n padding: 0 calc(var(--design-unit) * 5px) calc(var(--design-unit) * 2px);\n max-height: min(60vh, 520px);\n }\n\n .settings-modal-section {\n padding: calc(var(--design-unit) * 5px) 0;\n border-bottom: 1px solid var(--neutral-stroke-rest);\n }\n\n .settings-modal-section:last-child {\n border-bottom: none;\n }\n\n /*\n * Only the spacing. Everything else about this heading comes from the exported fragment\n * above, which the assistant and its hosts share; margin is deliberately not in there,\n * since the gap below a heading depends on the layout around it.\n */\n .ai-settings-section-title {\n margin: 0 0 calc(var(--design-unit) * 3px);\n }\n\n .settings-modal-section-note {\n margin: calc(var(--design-unit) * -2px) 0 calc(var(--design-unit) * 3px);\n font-size: 12px;\n line-height: 1.5;\n color: var(--neutral-foreground-hint);\n }\n\n .settings-modal-section-body {\n display: flex;\n flex-direction: column;\n gap: calc(var(--design-unit) * 4px);\n }\n\n .settings-toggle-row {\n display: flex;\n align-items: flex-start;\n gap: calc(var(--design-unit) * 3px);\n padding: 0 0 calc(var(--design-unit) * 4px) 0;\n border-bottom: 1px solid var(--neutral-stroke-rest);\n }\n\n .settings-toggle-row:last-child {\n border-bottom: none;\n padding-bottom: calc(var(--design-unit) * 1px);\n }\n\n .settings-toggle-row.is-stacked {\n flex-direction: column;\n gap: calc(var(--design-unit) * 2px);\n }\n\n /*\n * For rows whose control is a button rather than a switch. The base row top-aligns, which suits\n * a switch (it lands level with the title), but a button is tall enough that top-aligning leaves\n * it above the title-plus-description block it acts on.\n */\n .settings-toggle-row.is-centered {\n align-items: center;\n }\n\n .settings-animation-list {\n display: flex;\n flex-direction: column;\n width: 100%;\n border: 1px solid var(--neutral-stroke-rest);\n border-radius: calc(var(--control-corner-radius) * 1.2px);\n padding: calc(var(--design-unit) * 2px);\n gap: calc(var(--design-unit) * 2px);\n }\n\n .settings-animation-list .settings-toggle-row {\n border: 0;\n }\n\n .settings-animation-category {\n display: flex;\n flex-direction: column;\n gap: calc(var(--design-unit) * 4px);\n }\n\n .settings-animation-category-label {\n font-weight: 600;\n color: var(--neutral-foreground-hint);\n }\n\n .settings-toggle-row + .settings-animation-category-label {\n border-top: 1px solid var(--neutral-stroke-rest);\n padding-top: calc(var(--design-unit) * 2px);\n }\n\n .settings-animation-option {\n padding: 0;\n margin: 0;\n }\n\n .settings-animation-option:last-child {\n border-bottom: none;\n padding-bottom: calc(var(--design-unit) * 2px);\n }\n\n .settings-toggle-text {\n flex: 1;\n min-width: 0;\n }\n\n .settings-toggle-title {\n font-size: 14px;\n font-weight: 500;\n color: var(--neutral-foreground-rest);\n }\n\n .settings-toggle-desc {\n font-size: 12px;\n color: var(--neutral-foreground-hint);\n line-height: 1.5;\n max-width: 92%;\n }\n\n .settings-toggle-control {\n flex: 0 0 auto;\n min-width: 0;\n }\n\n .settings-toggle-control-wide {\n width: 100%;\n max-width: 280px;\n }\n\n .settings-toggle-row.is-stacked .settings-toggle-desc {\n max-width: none;\n }\n\n .settings-toggle-row.is-stacked .settings-toggle-control-wide {\n width: 100%;\n max-width: none;\n }\n\n /*\n * Sub-heading over the provider toggle group. Unlike the section headings it sits INSIDE\n * .settings-modal-section-body, so the parent's 4-unit flex gap already separates it from the\n * first toggle — and flex does not collapse margins, so the shared rule's 3-unit margin-bottom\n * would add to that gap rather than replace it, leaving the label adrift from the rows it\n * names. Zero the margin and pull back 2 of the 4 gap units, expressed in the same token so it\n * tracks if that gap changes.\n */\n .settings-app-toggles-title {\n margin: 0 0 calc(var(--design-unit) * -2px);\n }\n\n .settings-app-toggle-block {\n display: flex;\n flex-direction: column;\n gap: calc(var(--design-unit) * 1px);\n }\n\n .settings-toggle-hint {\n font-size: 12px;\n color: var(--neutral-foreground-hint);\n line-height: 1.4;\n padding-left: calc(var(--design-unit) * 1px);\n font-style: italic;\n }\n\n /*\n * The budget meter (GENC-1464) is the context indicator's money sibling and\n * shares its row anatomy 1:1, so the budget classes JOIN these rules rather\n * than restating them — one place to restyle the header/label/figures shape,\n * and the two indicators cannot drift apart. Design-system tokens only, and\n * unfallbacked: every token here ships a DS default, so a var(--x, y)\n * fallback would be dead code that hides a broken token name (#2411/#2436\n * review rule, pinned by settings-modal.styles.test.ts).\n */\n .settings-context-header,\n .settings-budget-header {\n display: flex;\n justify-content: space-between;\n gap: calc(var(--design-unit) * 2px);\n margin-bottom: calc(var(--design-unit) * 2px);\n font-size: 12px;\n }\n\n .settings-context-label,\n .settings-budget-label {\n color: var(--neutral-foreground-hint);\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n }\n\n .settings-context-tokens,\n .settings-budget-figures {\n color: var(--neutral-foreground-rest);\n }\n\n /* Freshness footer (GENC-1464): refresh button + label under the vendor\n rows, left-aligned — a footnote to the figures above it. The button's\n inline padding is pinned and cancelled by the container margin so the\n ICON GLYPH (not the button box) sits flush with the budget labels'\n left edge, whatever the density tokens say. */\n .settings-budget-toolbar {\n display: flex;\n justify-content: flex-start;\n align-items: center;\n gap: 4px;\n margin-inline-start: -4px;\n }\n\n .settings-budget-refresh::part(control) {\n padding-inline: 4px;\n }\n\n .settings-budget-updated {\n color: var(--neutral-foreground-hint);\n font-size: 11px;\n }\n\n /*\n * ONE rhythm for every meter row. The outer wrapper holds the context\n * indicator and the budget meter; the inner one holds the budget meter's\n * vendor rows — and both gap by the same 2 units (half the section body's 4)\n * so that visually identical meter rows are evenly spaced whatever mix of\n * them is on screen. Without the wrapper the context row inherits the body's\n * 4-unit gap while the vendor rows keep 2, and three identical-looking rows\n * read as an uneven 4/2/2. The wrapper renders only when at least one meter\n * does — see settingsUsageMetersTemplate for why an always-on one would cost\n * a stray gap.\n */\n .settings-usage-meters,\n .settings-budget-usage {\n display: flex;\n flex-direction: column;\n gap: calc(var(--design-unit) * 2px);\n }\n\n .settings-current-build,\n .settings-lifetime-usage {\n background: var(--neutral-layer-2);\n border: 1px solid var(--neutral-stroke-rest);\n border-radius: calc(var(--control-corner-radius) * 1.5px);\n padding: calc(var(--design-unit) * 4px);\n margin: calc(var(--design-unit) * 3px) 0;\n }\n\n /*\n * Deliberately quieter than the live block: no pulsing badge and no accent, so the eye lands\n * on what is being built now — which is why that block leads the tab and this one follows,\n * under the history heading whose rows it totals.\n */\n .settings-lifetime-usage-top {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: calc(var(--design-unit) * 2px);\n margin-bottom: calc(var(--design-unit) * 3px);\n }\n\n .settings-lifetime-label {\n font-size: 10px;\n line-height: 1;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n color: var(--neutral-foreground-hint);\n }\n\n .settings-current-build-top {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: calc(var(--design-unit) * 2px);\n margin-bottom: calc(var(--design-unit) * 3px);\n }\n\n /*\n * Optically centring these 10px caps against the 14px project name beside them. Flex offers\n * only two alignments and neither lands it, because a box centre and a text baseline are\n * different lines:\n * - align-items: center sits the badge baseline about 1.2px high\n * - align-items: baseline matches baselines, but the caps then read as bottom-aligned\n * against the name's ascenders, so the badge looks low\n * The eye wants the midpoint, so: centre the boxes, then translate down by the ~1px of\n * overshoot. This is a nudge, but it corrects a fixed relationship between two font sizes\n * rather than some neighbouring element's layout, so unlike a margin tuned to a sibling it\n * cannot rot when that sibling changes. In em, so it tracks the badge text.\n *\n * display: block, not inline-flex: a flex container has no text baseline of its own and\n * synthesizes one from its first child, which here is the dot, an empty span. As a block box\n * the badge takes the baseline of its first line, i.e. the text.\n */\n .settings-live-badge {\n display: block;\n font-size: 10px;\n line-height: 1;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n color: var(--accent-fill-rest);\n white-space: nowrap;\n transform: translateY(0.1em);\n }\n\n .settings-live-dot {\n display: inline-block;\n width: 7px;\n height: 7px;\n\n /* Replaces the flex gap the badge no longer has. */\n margin-right: calc(var(--design-unit) * 1px);\n\n /* Centres the dot on the text beside it without disturbing that text's baseline. */\n vertical-align: middle;\n border-radius: 50%;\n background: var(--accent-fill-rest);\n animation: settings-live-pulse 1.6s ease-out infinite;\n }\n\n @keyframes settings-live-pulse {\n 0% {\n box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent-fill-rest) 55%, transparent);\n }\n\n 70% {\n box-shadow: 0 0 0 7px transparent;\n }\n\n 100% {\n box-shadow: 0 0 0 0 transparent;\n }\n }\n\n .settings-current-build-name {\n font-size: 14px;\n line-height: 1;\n font-weight: 600;\n }\n\n /*\n * Cost and the token buckets side by side: one number in its own box, the four buckets\n * sharing the box next to it. Stretch, so the two are the same height however tall the 22px\n * cost value makes the left one.\n */\n .settings-usage-row {\n display: flex;\n flex-wrap: wrap;\n align-items: stretch;\n gap: calc(var(--design-unit) * 3px);\n margin-bottom: calc(var(--design-unit) * 3px);\n }\n\n .settings-usage-row-tokens {\n display: flex;\n flex: 1 1 auto;\n min-width: 0;\n }\n\n .settings-build-stat {\n min-width: 0;\n background: var(--neutral-layer-1);\n border: 1px solid var(--neutral-stroke-rest);\n border-radius: calc(var(--control-corner-radius) * 1px);\n padding: calc(var(--design-unit) * 3px);\n }\n\n .settings-build-stat-label {\n font-size: 10px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n color: var(--neutral-foreground-hint);\n }\n\n .settings-build-stat-value {\n font-size: 22px;\n font-weight: 700;\n margin-top: calc(var(--design-unit) * 1px);\n color: var(--neutral-foreground-rest);\n }\n\n .settings-build-stat-value .prefix,\n .settings-build-stat-value .suffix {\n color: var(--accent-fill-rest);\n }\n\n /*\n * Per-bucket token split, beneath the headline cost. Small paired label/value cells\n * rather than four more stat cards: they are the detail behind the cost above, so they\n * have to read as subordinate to it rather than compete with it. The four buckets ARE\n * the token figure now — there is no merged total anywhere for them to break down.\n */\n .settings-token-breakdown {\n display: flex;\n flex-direction: column;\n\n /*\n * Tight against the buckets below. One design unit rather than two, because each bucket\n * separates its own label from its value by only 2px — at two units the title sat four\n * times further from the bucket labels than they sit from their own numbers, which read\n * as a gap between unrelated blocks instead of a heading over its content.\n */\n gap: calc(var(--design-unit) * 1px);\n margin-bottom: calc(var(--design-unit) * 3px);\n padding: calc(var(--design-unit) * 2px) calc(var(--design-unit) * 3px);\n background: var(--neutral-layer-1);\n border: 1px solid var(--neutral-stroke-rest);\n border-radius: calc(var(--control-corner-radius) * 1px);\n }\n\n /*\n * Titles the box, because nothing else in it named the unit. Same styling and position as\n * the cost box's label, so two boxes sitting side by side are titled the same way; the\n * hierarchy against the bucket labels comes from position, not a second type size.\n */\n .settings-token-breakdown-label {\n font-size: 10px;\n line-height: 1;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n color: var(--neutral-foreground-hint);\n }\n\n .settings-token-buckets {\n display: flex;\n flex-wrap: wrap;\n gap: calc(var(--design-unit) * 4px);\n }\n\n .settings-token-bucket {\n display: flex;\n\n /*\n * min-content (not 0) so a bucket cannot be shrunk narrower than its own unbreakable label.\n * With the label set to nowrap, a zero minimum would overflow it into the next column instead\n * of wrapping — so the container's flex-wrap takes over and moves a bucket to a second row,\n * which is the degradation we want when the modal is dragged narrow.\n */\n min-width: min-content;\n flex-direction: column;\n gap: 2px;\n }\n\n .settings-token-bucket-label {\n font-size: 10px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n color: var(--neutral-foreground-hint);\n\n /*\n * One line, always. The rate marker is a separate inline box after collapsible whitespace, so\n * a wrappable label gives it a break opportunity of its own — in a narrow column it orphaned\n * onto line 2, which also pushed that bucket's value a line below the other three and broke\n * the row of figures. nowrap removes every break opportunity in the label, the space before\n * the marker included, so no glue character is needed.\n */\n white-space: nowrap;\n }\n\n .settings-token-bucket-value {\n font-size: 13px;\n font-weight: 600;\n color: var(--neutral-foreground-rest);\n }\n\n /*\n * The price-direction marker on a cache bucket's label — down for reads (a fraction of the\n * uncached input rate), up for writes (a premium over it). Three tiers, so a bucket with no\n * marker is the reference rate rather than an unlabelled fourth case.\n *\n * Carried by the label rather than the value, which is what this used to do: cache reads are\n * usually the largest bucket by a wide margin in an agentic session, so the value was muted to\n * stop the biggest number reading as the dominant cost driver. That priced the treatment to the\n * wrong thing — a dimmed 0 (every Gemini cache write) looked disabled next to a normal 0, and a\n * rate is a property of the bucket, not of this turn's figure.\n *\n * Sized down and nudged up rather than tinted: the label is already the hint colour, so a\n * dimmer marker would vanish and a brighter one would out-shout the word it qualifies.\n */\n .settings-token-bucket-rate {\n font-size: 8px;\n line-height: 1;\n vertical-align: 0.15em;\n letter-spacing: 0;\n }\n\n /*\n * Usage-row overrides. Placed after the base rules they refine rather than up beside\n * .settings-usage-row itself, because stylelint's no-descending-specificity rejects a\n * compound selector appearing before the plain one it overrides.\n */\n\n /* Sized by its content; the buckets take the rest, since four cells need the width. */\n .settings-usage-row > .settings-build-stat {\n flex: 0 1 auto;\n }\n\n .settings-usage-row .settings-token-breakdown {\n flex: 1 1 auto;\n\n /* The row owns the spacing now; this centres the column against the taller cost box. */\n justify-content: center;\n margin-bottom: 0;\n }\n\n /*\n * Basis auto, not 0: a zero basis splits the row into four equal columns, so CACHE WRITE is\n * squeezed exactly as hard as INPUT despite being twice the label. Sizing from content gives the\n * long labels the room their text needs and lets the short ones concede it, which is what makes\n * white-space: nowrap above safe — every column starts at least as wide as its own content,\n * then shares the slack. Still grows and shrinks, so the four stay flush edge to edge.\n */\n .settings-usage-row .settings-token-bucket {\n flex: 1 1 auto;\n }\n\n .settings-model-tags {\n display: flex;\n flex-wrap: wrap;\n gap: calc(var(--design-unit) * 2px);\n }\n\n .settings-model-tag {\n font-size: 11px;\n font-weight: 600;\n padding: 3px 9px;\n border-radius: 20px;\n font-family: ui-monospace, monospace;\n border: 1px solid var(--neutral-stroke-rest);\n }\n\n .settings-model-tag,\n .settings-cost-history-icon {\n --model-tag-color: var(--neutral-foreground-rest);\n\n background: color-mix(\n in srgb,\n var(--model-tag-color) var(--ai-tier-badge-bg-mix),\n var(--neutral-layer-1)\n );\n border-color: color-mix(in srgb, var(--model-tag-color) 20%, var(--neutral-layer-1));\n color: color-mix(in srgb, var(--model-tag-color), white var(--ai-tier-badge-text-lighten));\n }\n\n .settings-cost-history-title {\n font-size: 11px;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n color: var(--neutral-foreground-hint);\n font-weight: 600;\n margin: calc(var(--design-unit) * 4px) 0 calc(var(--design-unit) * 2px);\n }\n\n /*\n * The heading labels the lifetime card as well as the rows, so the card gives up its own top\n * margin and the gap becomes the heading's alone. Adjacent siblings collapse to the LARGER of\n * the two margins, so leaving both would space the caps 3 units off the block they label while\n * the rows below sit at 2 — reading as though the heading belonged to neither.\n */\n .settings-cost-history-title + .settings-lifetime-usage {\n margin-top: 0;\n }\n\n .settings-cost-history {\n display: flex;\n flex-direction: column;\n gap: calc(var(--design-unit) * 2px);\n padding-bottom: calc(var(--design-unit) * 2px);\n }\n\n .settings-cost-history-icon {\n flex: 0 0 auto;\n width: 36px;\n height: 36px;\n border-radius: calc(var(--control-corner-radius) * 1px);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-size: 16px;\n font-weight: 700;\n border: 1px solid var(--neutral-stroke-rest);\n }\n\n .settings-cost-history-row {\n display: flex;\n flex-direction: column;\n background: var(--neutral-layer-1);\n border: 1px solid var(--neutral-stroke-rest);\n border-radius: calc(var(--control-corner-radius) * 1.5px);\n padding: calc(var(--design-unit) * 4px);\n }\n\n .settings-cost-history-row-content {\n display: flex;\n align-items: flex-start;\n gap: calc(var(--design-unit) * 2px);\n }\n\n /*\n * All of these carry a ?hidden binding, and all declare display: flex — which silently beat the\n * UA [hidden] { display: none } rule, because author-origin declarations outrank user-agent\n * origin whatever the specificity. Without this, showTokens: false still rendered the full\n * breakdown in the lifetime block, the live block and every history row, and an empty row list\n * still occupied its padding under the lifetime total. Sibling elements that hide correctly do\n * so only by declaring no display at all. Same treatment as .settings-modal-tab-panel[hidden]\n * near the top of this file.\n */\n .settings-usage-row-tokens[hidden],\n .settings-cost-history[hidden],\n .settings-cost-history-row-content[hidden] {\n display: none !important;\n }\n\n /*\n * The buckets inside a history row drop the panel treatment they carry in the summary\n * blocks — the row is already a bordered card, and a second box inside it reads as a\n * nested panel. The adjacent-sibling rule above supplies the separator instead.\n */\n .settings-cost-history-row-breakdown .settings-token-breakdown {\n flex: 1 1 auto;\n margin-bottom: 0;\n padding: 0;\n background: none;\n border: none;\n }\n\n .settings-cost-history-row-content + .settings-cost-history-row-content {\n border-top: 1px solid var(--neutral-stroke-rest);\n margin-top: calc(var(--design-unit) * 2px);\n padding-top: calc(var(--design-unit) * 2px);\n }\n\n .settings-cost-history-row-body {\n flex: 1 1 auto;\n min-width: 0;\n display: flex;\n gap: calc(var(--design-unit) * 2px);\n justify-content: space-between;\n }\n\n /*\n * Holds the cost and the delete button as one centred pair. The button used to be a sibling\n * of the whole row body, kept in place by a -2px nudge tuned for when the metrics column had\n * two lines (cost plus a merged token total). Dropping that second line left the nudge\n * compensating for nothing. Centring the two against each other needs no magic number and\n * cannot drift again if the metrics content changes.\n *\n * NB this rule previously set align-items with no display:flex, so it had no effect at all.\n */\n .settings-cost-history-row-right {\n display: flex;\n align-items: center;\n gap: calc(var(--design-unit) * 1px);\n flex: 0 0 auto;\n }\n\n .settings-cost-history-row-cost,\n .settings-cost-history-row-title {\n font-size: 14px;\n font-weight: 600;\n color: var(--neutral-foreground-rest);\n min-width: 0;\n }\n\n .settings-cost-history-row-metrics {\n display: flex;\n flex-direction: column;\n flex: 0 0 auto;\n font-size: 13px;\n font-family: ui-monospace, monospace;\n color: var(--neutral-foreground-rest);\n white-space: nowrap;\n }\n\n .settings-cost-history-row-date {\n font-size: 12px;\n color: var(--neutral-foreground-hint);\n }\n\n .settings-cost-history-delete {\n flex: 0 0 auto;\n color: var(--neutral-foreground-hint);\n }\n\n .settings-cost-history-delete:hover {\n color: var(--error-color);\n }\n\n .settings-cost-history ::slotted(*) {\n display: block;\n }\n\n .settings-modal-footer {\n width: 100%;\n }\n\n .settings-done-button {\n margin-left: auto;\n }\n\n .settings-modal-footer-actions {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: calc(var(--design-unit) * 2px);\n width: 100%;\n }\n\n .settings-disclaimer {\n font-size: 12px;\n color: var(--neutral-foreground-hint);\n max-width: 60%;\n line-height: 1.4;\n }\n\n /*\n * Inline spinner shown in the \"Download agent log\" button while the log builds\n * (GENC-1351 §5.8) — the lifetime-stitch path is an async provider round-trip.\n * Sized to the button label and tinted with currentColor so it tracks the\n * button's text colour in every appearance / theme.\n */\n [part='download-button'] .download-spinner {\n display: inline-block;\n width: 12px;\n height: 12px;\n margin-right: calc(var(--design-unit) * 1px);\n vertical-align: -1px;\n border: 2px solid currentColor;\n border-right-color: transparent;\n border-radius: 50%;\n animation: download-spin 0.6s linear infinite;\n }\n\n @keyframes download-spin {\n to {\n transform: rotate(360deg);\n }\n }\n`",
|
|
11567
|
+
"description": "Styles for the assistant settings modal (tabs, sections, toggles, cost summary)."
|
|
11568
|
+
}
|
|
11569
|
+
],
|
|
11570
|
+
"exports": [
|
|
11571
|
+
{
|
|
11572
|
+
"kind": "js",
|
|
11573
|
+
"name": "settingsModalStyles",
|
|
11574
|
+
"declaration": {
|
|
11575
|
+
"name": "settingsModalStyles",
|
|
11576
|
+
"module": "src/components/settings-modal/settings-modal.styles.ts"
|
|
11577
|
+
}
|
|
11578
|
+
}
|
|
11579
|
+
]
|
|
11580
|
+
},
|
|
11581
|
+
{
|
|
11582
|
+
"kind": "javascript-module",
|
|
11583
|
+
"path": "src/components/settings-modal/settings-modal.template.ts",
|
|
11584
|
+
"declarations": [
|
|
11585
|
+
{
|
|
11586
|
+
"kind": "function",
|
|
11587
|
+
"name": "SettingsModalTemplate",
|
|
11588
|
+
"return": {
|
|
11589
|
+
"type": {
|
|
11590
|
+
"text": "ViewTemplate<FoundationAiAssistant>"
|
|
11591
|
+
}
|
|
11592
|
+
},
|
|
11593
|
+
"parameters": [
|
|
11594
|
+
{
|
|
11595
|
+
"name": "designSystemPrefix",
|
|
11596
|
+
"type": {
|
|
11597
|
+
"text": "string"
|
|
11598
|
+
}
|
|
11599
|
+
}
|
|
11600
|
+
],
|
|
11601
|
+
"description": "Modal shell for assistant settings — Configuration and Usage tabs, section slots for\napp-specific UI, and built-in chat-bot toggles."
|
|
11602
|
+
}
|
|
11603
|
+
],
|
|
11604
|
+
"exports": [
|
|
11605
|
+
{
|
|
11606
|
+
"kind": "js",
|
|
11607
|
+
"name": "SettingsModalTemplate",
|
|
11608
|
+
"declaration": {
|
|
11609
|
+
"name": "SettingsModalTemplate",
|
|
11610
|
+
"module": "src/components/settings-modal/settings-modal.template.ts"
|
|
11611
|
+
}
|
|
11612
|
+
}
|
|
11613
|
+
]
|
|
11490
11614
|
}
|
|
11491
11615
|
]
|
|
11492
11616
|
}
|
|
@@ -36,6 +36,17 @@ export interface AiDriver extends EventTarget {
|
|
|
36
36
|
* Resolve a pending blocking interaction with the given result.
|
|
37
37
|
*/
|
|
38
38
|
resolveInteraction(interactionId: string, result: unknown): void;
|
|
39
|
+
/**
|
|
40
|
+
* Record external spend against an interaction that has ALREADY resolved.
|
|
41
|
+
*
|
|
42
|
+
* For widgets that resolve early — so the assistant stops waiting — and then keep doing paid work
|
|
43
|
+
* on the user's behalf. Reports a positive DELTA; repeated calls sum. Returns false when the
|
|
44
|
+
* spend was NOT recorded — the value was not a positive finite amount, or the message is no
|
|
45
|
+
* longer in history.
|
|
46
|
+
*
|
|
47
|
+
* @beta
|
|
48
|
+
*/
|
|
49
|
+
recordExternalCost?(interactionId: string, costUsd: number): boolean;
|
|
39
50
|
/**
|
|
40
51
|
* The live {@link InteractionContext} for a pending interaction (GENC-1390), or
|
|
41
52
|
* `undefined` if the interaction is resolved/evicted or the driver owns no
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-driver.d.ts","sourceRoot":"","sources":["../../../../src/components/ai-driver/ai-driver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,gBAAgB,EAChB,WAAW,EACX,kBAAkB,EACnB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qCAAqC,CAAC;AAC3E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAC1E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE/D,gBAAgB;AAChB,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,kBAAkB,EAAE,CAAC;CAC7B;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,QAAS,SAAQ,WAAW;IAC3C;;OAEG;IACH,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,cAAc,EAAE,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAEtF;;;;;OAKG;IACH,mBAAmB,CAAC,eAAe,CAAC,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAEhF;;OAEG;IACH,kBAAkB,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"ai-driver.d.ts","sourceRoot":"","sources":["../../../../src/components/ai-driver/ai-driver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,gBAAgB,EAChB,WAAW,EACX,kBAAkB,EACnB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qCAAqC,CAAC;AAC3E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAC1E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE/D,gBAAgB;AAChB,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,kBAAkB,EAAE,CAAC;CAC7B;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,QAAS,SAAQ,WAAW;IAC3C;;OAEG;IACH,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,cAAc,EAAE,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAEtF;;;;;OAKG;IACH,mBAAmB,CAAC,eAAe,CAAC,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAEhF;;OAEG;IACH,kBAAkB,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC;IACjE;;;;;;;;;OASG;IACH,kBAAkB,CAAC,CAAC,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;IAErE;;;;;OAKG;IACH,qBAAqB,CAAC,CAAC,aAAa,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAAC;IAE9E;;OAEG;IACH,WAAW,CAAC,QAAQ,EAAE,WAAW,EAAE,GAAG,IAAI,CAAC;IAE3C;;;;;OAKG;IACH,uBAAuB,CAAC,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAEnE;;OAEG;IACH,aAAa,CAAC,IAAI,SAAS,WAAW,EAAE,CAAC;IAEzC;;;;;;OAMG;IACH,cAAc,CAAC,IAAI,WAAW,GAAG,SAAS,CAAC;IAE3C;;;;;;;OAOG;IACH,OAAO,CAAC,IAAI,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAExC;;;;;OAKG;IACH,UAAU,CAAC,IAAI,OAAO,CAAC;IAEvB;;OAEG;IACH,MAAM,IAAI,OAAO,CAAC;IAElB;;OAEG;IACH,cAAc,CACZ,OAAO,EAAE,WAAW,EAAE,EACtB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,YAAY,CAAC,EAAE,eAAe,EAAE,GAC/B,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAErB;;;;;OAKG;IACH,gBAAgB,CAAC,IAAI,aAAa,CAAC,YAAY,CAAC,CAAC;IAEjD;;;;;OAKG;IACH,sBAAsB,CAAC,IAAI,aAAa,CAAC,eAAe,CAAC,CAAC;IAE1D;;;;;OAKG;IACH,qBAAqB,IAAI,MAAM,CAAC;IAEhC;;;;OAIG;IACH,OAAO,CAAC,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjC;;;;;OAKG;IACH,MAAM,CAAC,IAAI,IAAI,CAAC;CACjB"}
|
|
@@ -209,7 +209,7 @@ export interface ChatDriverConfig {
|
|
|
209
209
|
*
|
|
210
210
|
* Dispatches `'history-updated'` events on itself so the owning element can observe changes.
|
|
211
211
|
*
|
|
212
|
-
* @fires history-updated - Fired whenever the in-memory chat history changes (append, tool loop, interaction resolution) with the full history snapshot. detail: `ReadonlyArray<ChatMessage>`
|
|
212
|
+
* @fires history-updated - Fired whenever the in-memory chat history changes (append, tool loop, interaction resolution, post-resolve external cost) with the full history snapshot. detail: `ReadonlyArray<ChatMessage>`
|
|
213
213
|
*
|
|
214
214
|
* @beta
|
|
215
215
|
*/
|
|
@@ -871,6 +871,36 @@ export declare class ChatDriver extends EventTarget implements AiDriver {
|
|
|
871
871
|
* bubble and/or shows the "Assistant" label.
|
|
872
872
|
*/
|
|
873
873
|
requestInteraction<T>(componentName: string, data: any, options?: InteractionRequestOptions): Promise<T>;
|
|
874
|
+
/**
|
|
875
|
+
* Add external (non-LLM) spend to an interaction's message AFTER it has resolved.
|
|
876
|
+
*
|
|
877
|
+
* **Why this exists separately from `resolveInteraction`.** A widget's spend reaches the session
|
|
878
|
+
* total only through `InteractionResult.costUsd`, which is read exactly once — `resolveInteraction`
|
|
879
|
+
* does its whole job inside `if (interaction)` and then deletes the entry from
|
|
880
|
+
* `pendingInteractions`. That is correct for the resolution itself, but it assumes a widget stops
|
|
881
|
+
* costing money when it resolves, and several deliberately do not: a widget that resolves EARLY so
|
|
882
|
+
* the assistant stops waiting (options shown, a preview rendered) may stay interactive for minutes
|
|
883
|
+
* afterwards and do more paid work on the user's behalf. Re-emitting `interaction-completed` does
|
|
884
|
+
* not help — with the interaction gone from the map the call is a silent no-op — so that spend had
|
|
885
|
+
* nowhere to go and was simply missing from the figure shown to the user.
|
|
886
|
+
*
|
|
887
|
+
* Additive by design: the caller reports a DELTA, not a running total, so repeated calls sum. It
|
|
888
|
+
* touches only `externalCostUsd`; the recorded `interaction.resolved` payload is left exactly as
|
|
889
|
+
* the resolution wrote it, because this is not a second resolution and must not read like one.
|
|
890
|
+
*
|
|
891
|
+
* Deliberately does NOT require the interaction to be pending, does not resurrect it, and has no
|
|
892
|
+
* loading/turn side effects — this is bookkeeping about a turn that already happened.
|
|
893
|
+
*
|
|
894
|
+
* @param interactionId the interaction whose message owns the spend.
|
|
895
|
+
* @param costUsd a positive delta in USD. Non-finite, zero and negative values are ignored.
|
|
896
|
+
* @returns `true` when the spend was recorded; `false` when it was not — either the value was
|
|
897
|
+
* not a positive finite amount, or the message is no longer in history (trimmed or condensed
|
|
898
|
+
* away). A real outcome a caller may want to log rather than a failure to throw on; every
|
|
899
|
+
* `false` also records an `interaction.external-cost-dropped` meta event here, so money can
|
|
900
|
+
* never disappear without a trace.
|
|
901
|
+
* @beta
|
|
902
|
+
*/
|
|
903
|
+
recordExternalCost(interactionId: string, costUsd: number): boolean;
|
|
874
904
|
/**
|
|
875
905
|
* Resolve a pending interaction. The wrapper component calls this on completion.
|
|
876
906
|
* Marks the interaction message as resolved so it renders read-only on re-render.
|