@genesislcap/ai-assistant 15.4.1 → 15.5.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.
Files changed (91) hide show
  1. package/dist/ai-assistant.api.json +544 -82
  2. package/dist/ai-assistant.d.ts +324 -36
  3. package/dist/chat-driver.cjs +94 -22
  4. package/dist/chat-driver.cjs.map +3 -3
  5. package/dist/chat-driver.mjs +94 -22
  6. package/dist/chat-driver.mjs.map +3 -3
  7. package/dist/custom-elements.json +303 -36
  8. package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
  9. package/dist/dts/components/settings-modal/settings-modal.styles.d.ts.map +1 -1
  10. package/dist/dts/components/settings-modal/settings-modal.template.d.ts +9 -2
  11. package/dist/dts/components/settings-modal/settings-modal.template.d.ts.map +1 -1
  12. package/dist/dts/index.d.ts +1 -0
  13. package/dist/dts/index.d.ts.map +1 -1
  14. package/dist/dts/main/cost-session-banking.test.d.ts +2 -0
  15. package/dist/dts/main/cost-session-banking.test.d.ts.map +1 -0
  16. package/dist/dts/main/main.d.ts +184 -24
  17. package/dist/dts/main/main.d.ts.map +1 -1
  18. package/dist/dts/provider/assistant-app-settings.d.ts +30 -5
  19. package/dist/dts/provider/assistant-app-settings.d.ts.map +1 -1
  20. package/dist/dts/state/ai-assistant-slice.d.ts +11 -8
  21. package/dist/dts/state/ai-assistant-slice.d.ts.map +1 -1
  22. package/dist/dts/state/persistence/session-persistence-provider.d.ts +23 -0
  23. package/dist/dts/state/persistence/session-persistence-provider.d.ts.map +1 -1
  24. package/dist/dts/state/persistence/session-snapshot.d.ts.map +1 -1
  25. package/dist/dts/state/session-store.d.ts +1 -2
  26. package/dist/dts/state/session-store.d.ts.map +1 -1
  27. package/dist/dts/styles/settings-section.d.ts +29 -0
  28. package/dist/dts/styles/settings-section.d.ts.map +1 -0
  29. package/dist/dts/utils/cost-session-history.d.ts +103 -12
  30. package/dist/dts/utils/cost-session-history.d.ts.map +1 -1
  31. package/dist/dts/utils/resolve-cost-history-config.d.ts +9 -3
  32. package/dist/dts/utils/resolve-cost-history-config.d.ts.map +1 -1
  33. package/dist/dts/utils/sum-costs.d.ts.map +1 -1
  34. package/dist/dts/utils/sum-tokens.d.ts +8 -8
  35. package/dist/dts/utils/sum-tokens.d.ts.map +1 -1
  36. package/dist/dts/utils/sum-usage.d.ts +59 -0
  37. package/dist/dts/utils/sum-usage.d.ts.map +1 -0
  38. package/dist/dts/utils/sum-usage.test.d.ts +2 -0
  39. package/dist/dts/utils/sum-usage.test.d.ts.map +1 -0
  40. package/dist/esm/components/chat-driver/chat-driver.js +6 -0
  41. package/dist/esm/components/settings-modal/settings-modal.styles.js +237 -18
  42. package/dist/esm/components/settings-modal/settings-modal.template.js +229 -73
  43. package/dist/esm/index.js +1 -0
  44. package/dist/esm/main/cost-session-banking.test.js +308 -0
  45. package/dist/esm/main/main.js +424 -71
  46. package/dist/esm/state/ai-assistant-slice.js +11 -8
  47. package/dist/esm/state/ai-assistant-slice.test.js +12 -5
  48. package/dist/esm/state/debug-event-log.js +2 -2
  49. package/dist/esm/state/persistence/session-persistence.integration.test.js +5 -1
  50. package/dist/esm/state/persistence/session-persister.js +2 -2
  51. package/dist/esm/state/persistence/session-persister.test.js +10 -1
  52. package/dist/esm/state/persistence/session-snapshot.js +6 -2
  53. package/dist/esm/state/persistence/session-snapshot.test.js +4 -1
  54. package/dist/esm/state/persistence/stateful-restore.e2e.test.js +10 -1
  55. package/dist/esm/styles/settings-section.js +39 -0
  56. package/dist/esm/utils/cost-session-history.js +92 -15
  57. package/dist/esm/utils/cost-session-history.test.js +155 -13
  58. package/dist/esm/utils/resolve-cost-history-config.js +2 -1
  59. package/dist/esm/utils/sum-costs.js +2 -13
  60. package/dist/esm/utils/sum-tokens.js +10 -27
  61. package/dist/esm/utils/sum-tokens.test.js +1 -5
  62. package/dist/esm/utils/sum-usage.js +123 -0
  63. package/dist/esm/utils/sum-usage.test.js +120 -0
  64. package/dist/tsconfig.tsbuildinfo +1 -1
  65. package/package.json +17 -17
  66. package/src/components/chat-driver/chat-driver.ts +6 -0
  67. package/src/components/settings-modal/settings-modal.styles.ts +237 -18
  68. package/src/components/settings-modal/settings-modal.template.ts +270 -81
  69. package/src/index.ts +1 -0
  70. package/src/main/cost-session-banking.test.ts +407 -0
  71. package/src/main/main.ts +433 -68
  72. package/src/provider/assistant-app-settings.ts +31 -5
  73. package/src/state/ai-assistant-slice.test.ts +12 -5
  74. package/src/state/ai-assistant-slice.ts +21 -13
  75. package/src/state/debug-event-log.ts +2 -2
  76. package/src/state/persistence/session-persistence-provider.ts +24 -0
  77. package/src/state/persistence/session-persistence.integration.test.ts +8 -1
  78. package/src/state/persistence/session-persister.test.ts +10 -1
  79. package/src/state/persistence/session-persister.ts +2 -2
  80. package/src/state/persistence/session-snapshot.test.ts +4 -1
  81. package/src/state/persistence/session-snapshot.ts +5 -1
  82. package/src/state/persistence/stateful-restore.e2e.test.ts +9 -1
  83. package/src/styles/settings-section.ts +40 -0
  84. package/src/utils/cost-session-history.test.ts +187 -16
  85. package/src/utils/cost-session-history.ts +142 -23
  86. package/src/utils/resolve-cost-history-config.ts +10 -3
  87. package/src/utils/sum-costs.ts +2 -9
  88. package/src/utils/sum-tokens.test.ts +1 -11
  89. package/src/utils/sum-tokens.ts +10 -26
  90. package/src/utils/sum-usage.test.ts +140 -0
  91. package/src/utils/sum-usage.ts +130 -0
@@ -1,7 +1,7 @@
1
1
  import { html, ref, repeat, slotted, when } from '@genesislcap/web-core';
2
2
  import { ANIMATION_DEFS } from '../../main/main.types';
3
3
  import { formatCostSessionDate, } from '../../utils/cost-session-history';
4
- import { formatCompactTokenCountPlain, formatCompactTokenCountSafe } from '../../utils/sum-tokens';
4
+ import { formatCompactTokenCountSafe } from '../../utils/sum-tokens';
5
5
  const SESSION_COST_DECIMALS = 2;
6
6
  /** Animation options grouped by {@link ANIMATION_DEFS} category for the settings list. */
7
7
  const animationCategoryGroups = (() => {
@@ -69,9 +69,14 @@ const animationsPickerTemplate = (switchTag) => html `
69
69
  `)}
70
70
  </div>
71
71
  `;
72
+ const SETTINGS_ROW_LAYOUT_CLASS = {
73
+ row: '',
74
+ 'row-centered': 'is-centered',
75
+ column: 'is-stacked',
76
+ };
72
77
  /** Shared label + control row used for switches, buttons, and multiselects. */
73
78
  const settingsToggleRow = (title, description, part, control, layout = 'row') => html `
74
- <div class="settings-toggle-row ${layout === 'column' ? 'is-stacked' : ''}" part="${part}-row">
79
+ <div class="settings-toggle-row ${SETTINGS_ROW_LAYOUT_CLASS[layout]}" part="${part}-row">
75
80
  <div class="settings-toggle-text">
76
81
  <div class="settings-toggle-title">${title}</div>
77
82
  <div class="settings-toggle-desc">${description}</div>
@@ -127,7 +132,27 @@ const chatBotSettingsTemplate = (switchTag, buttonTag) => html `
127
132
  }))}
128
133
  ${when((x) => { var _a, _b; return ((_b = (_a = x.chatConfig.ui) === null || _a === void 0 ? void 0 : _a.animations) === null || _b === void 0 ? void 0 : _b.userConfigurable) === true; }, settingsToggleRow('Animations', 'Choose which loading and activity indicators appear while the assistant is working.', 'toggle-animations', animationsPickerTemplate(switchTag), 'column'))}
129
134
  `;
130
- const appSettingsTemplate = (switchTag) => html `
135
+ const appSettingsTemplate = (switchTag, iconTag) => html `
136
+ <!--
137
+ Slot FIRST, ahead of the provider toggles. The assistant renders no heading for this
138
+ section, so whatever the host slots in has to be able to title it — and a heading below the
139
+ rows it names would title nothing. Anything the host wants under the toggles it can render
140
+ below them within its own slotted block.
141
+ -->
142
+ <slot name="settings-app" ${slotted('settingsAppSlotted')}></slot>
143
+ <!--
144
+ Sub-heading for the toggle group, rendered only when the host asks for one — both the text
145
+ and the icon come from its provider. It exists so a host whose slotted content covers other
146
+ ground can still mark these rows off from it.
147
+ -->
148
+ ${when((x) => !!x.appSettingsTogglesHeading, html `
149
+ <h4 class="ai-settings-section-title settings-app-toggles-title" part="app-toggles-title">
150
+ ${when((x) => { var _a; return !!((_a = x.appSettingsTogglesHeading) === null || _a === void 0 ? void 0 : _a.icon); }, html `
151
+ <${iconTag} name="${(x) => { var _a; return (_a = x.appSettingsTogglesHeading) === null || _a === void 0 ? void 0 : _a.icon; }}"></${iconTag}>
152
+ `)}
153
+ ${(x) => { var _a; return (_a = x.appSettingsTogglesHeading) === null || _a === void 0 ? void 0 : _a.title; }}
154
+ </h4>
155
+ `)}
131
156
  ${repeat((x) => x.visibleAppSettingsToggles, html `
132
157
  <div class="settings-app-toggle-block">
133
158
  <div
@@ -154,8 +179,131 @@ const appSettingsTemplate = (switchTag) => html `
154
179
  `)}
155
180
  </div>
156
181
  `)}
157
- <slot name="settings-app" ${slotted('settingsAppSlotted')}></slot>
158
182
  `;
183
+ /**
184
+ * One model chip. Shared by the live session and the history rows so the two can't drift — both
185
+ * already reduce to a distinct-model list, the live one via `uniqueProviderModels` and a row via
186
+ * `collectSessionModels`.
187
+ */
188
+ const modelTagTemplate = html `
189
+ <span class="settings-model-tag vendor-${(entry) => entry.provider || 'generic'}">
190
+ ${(entry) => entry.model}
191
+ </span>
192
+ `;
193
+ /**
194
+ * The four billing buckets for one `AggregateUsage`, wherever it comes from — the lifetime
195
+ * total, the live session, or a history row.
196
+ *
197
+ * Why four numbers instead of one: each bucket bills at a different rate, so a single token
198
+ * total can't explain a session that processed millions of tokens for pennies. Uncached input,
199
+ * cache read and cache write partition the prompt (they sum to it, never add on top), and
200
+ * output is what the model generated.
201
+ *
202
+ * Ordered input, output, cache read, cache write to match how Claude Code reports usage, so the
203
+ * two can be read side by side — comparing against it is what this breakdown exists for. Note
204
+ * this puts output between the three buckets that partition the prompt, so their relationship is
205
+ * carried by the tooltips rather than by adjacency.
206
+ *
207
+ * `partPrefix` keeps the shadow-part names caller-specific (`session-tokens-input`,
208
+ * `lifetime-tokens-input`, …) so a host can style one context without hitting the others.
209
+ */
210
+ const tokenBreakdownTemplate = (usageOf, partPrefix) => html `
211
+ <div class="settings-token-breakdown" part="${partPrefix}-token-breakdown">
212
+ <!--
213
+ Titles the box. Nothing in it otherwise said "tokens": the four bucket names are the
214
+ token TYPES, not the unit, so the panel read as four unexplained numbers unless the
215
+ reader found the per-bucket tooltips.
216
+ -->
217
+ <div class="settings-token-breakdown-label">Tokens</div>
218
+ <div class="settings-token-buckets">
219
+ <div class="settings-token-bucket" part="${partPrefix}-tokens-input">
220
+ <span
221
+ class="settings-token-bucket-label"
222
+ title="Prompt tokens billed at the full uncached rate"
223
+ >
224
+ Input
225
+ </span>
226
+ <span
227
+ class="settings-token-bucket-value"
228
+ :innerHTML="${(x) => formatCompactTokenCountSafe(usageOf(x).uncachedInputTokens)}"
229
+ ></span>
230
+ </div>
231
+ <div class="settings-token-bucket" part="${partPrefix}-tokens-output">
232
+ <span
233
+ class="settings-token-bucket-label"
234
+ title="Tokens the model generated, including reasoning tokens billed as output"
235
+ >
236
+ Output
237
+ </span>
238
+ <span
239
+ class="settings-token-bucket-value"
240
+ :innerHTML="${(x) => formatCompactTokenCountSafe(usageOf(x).outputTokens)}"
241
+ ></span>
242
+ </div>
243
+ <div class="settings-token-bucket is-discounted" part="${partPrefix}-tokens-cache-read">
244
+ <span
245
+ class="settings-token-bucket-label"
246
+ title="Prompt tokens served from the provider cache — billed at a fraction of the uncached rate"
247
+ >
248
+ Cache read
249
+ </span>
250
+ <span
251
+ class="settings-token-bucket-value"
252
+ :innerHTML="${(x) => formatCompactTokenCountSafe(usageOf(x).cacheReadTokens)}"
253
+ ></span>
254
+ </div>
255
+ <div class="settings-token-bucket" part="${partPrefix}-tokens-cache-write">
256
+ <span
257
+ class="settings-token-bucket-label"
258
+ title="Prompt tokens written to the provider cache — billed at a premium over the uncached rate"
259
+ >
260
+ Cache write
261
+ </span>
262
+ <span
263
+ class="settings-token-bucket-value"
264
+ :innerHTML="${(x) => formatCompactTokenCountSafe(usageOf(x).cacheWriteTokens)}"
265
+ ></span>
266
+ </div>
267
+ </div>
268
+ </div>
269
+ `;
270
+ /**
271
+ * Lifetime usage across every project in the history scope.
272
+ *
273
+ * Sits above the live session block because it is the wider figure. It used to live inside
274
+ * that block as a lone "Total build cost", where the live badge and project name directly
275
+ * above it implied the total belonged to the open project.
276
+ */
277
+ const lifetimeUsageTemplate = () => html `
278
+ ${when((x) => x.settingsCostLifetimeVisible, html `
279
+ <div class="settings-lifetime-usage" part="lifetime-usage">
280
+ <div class="settings-lifetime-usage-top">
281
+ <span class="settings-lifetime-label">Lifetime usage</span>
282
+ </div>
283
+ <div class="settings-usage-row">
284
+ <div
285
+ class="settings-build-stat"
286
+ part="lifetime-cost"
287
+ ?hidden=${(x) => !x.resolvedCostHistory.showCost}
288
+ >
289
+ <div class="settings-build-stat-label">Total build cost</div>
290
+ <div class="settings-build-stat-value">
291
+ <span class="prefix">$</span>
292
+ <span class="value">
293
+ ${(x) => x.cumulativeCostUsd.toFixed(SESSION_COST_DECIMALS)}
294
+ </span>
295
+ </div>
296
+ </div>
297
+ <div
298
+ class="settings-usage-row-tokens"
299
+ ?hidden=${(x) => !x.resolvedCostHistory.showTokens}
300
+ >
301
+ ${tokenBreakdownTemplate((x) => x.cumulativeUsage, 'lifetime')}
302
+ </div>
303
+ </div>
304
+ </div>
305
+ `)}
306
+ `;
159
307
  const sessionCostSummaryTemplate = () => html `
160
308
  ${when((x) => x.settingsCostSummaryVisible, html `
161
309
  <div class="settings-current-build" part="session-cost-summary">
@@ -168,59 +316,43 @@ const sessionCostSummaryTemplate = () => html `
168
316
  <span class="settings-current-build-name">${(x) => x.liveCostSessionTitle}</span>
169
317
  `)}
170
318
  </div>
171
- <div class="settings-current-build-stats">
319
+ <div class="settings-usage-row">
172
320
  <div
173
321
  class="settings-build-stat"
174
322
  part="session-cost"
175
323
  ?hidden=${(x) => !x.resolvedCostHistory.showCost}
176
324
  >
177
- <div class="settings-build-stat-label">Current session cost</div>
325
+ <!--
326
+ Label states the scope rather than implying one. These figures are whatever
327
+ the open transcript proves, so they are the project's whole history exactly
328
+ when that transcript is restored on load, and only this page-load's when it
329
+ isn't. Same number, two meanings — so the caller can't be left guessing.
330
+ -->
331
+ <div class="settings-build-stat-label">
332
+ ${(x) => (x.liveUsageIsProjectLifetime ? 'Project cost' : 'Session cost')}
333
+ </div>
178
334
  <div class="settings-build-stat-value">
179
335
  <span class="prefix">$</span>
180
336
  <span class="value">${(x) => x.sessionCostUsd.toFixed(SESSION_COST_DECIMALS)}</span>
181
337
  </div>
182
338
  </div>
183
339
  <div
184
- class="settings-build-stat"
185
- part="session-tokens"
340
+ class="settings-usage-row-tokens"
186
341
  ?hidden=${(x) => !x.resolvedCostHistory.showTokens}
187
342
  >
188
- <div class="settings-build-stat-label">Tokens consumed</div>
189
- <div
190
- class="settings-build-stat-value"
191
- :innerHTML="${(x) => formatCompactTokenCountSafe(x.sessionTokensConsumed)}"
192
- ></div>
193
- </div>
194
- <div
195
- class="settings-build-stat"
196
- part="session-cumulative-cost"
197
- ?hidden=${(x) => !x.resolvedCostHistory.showCost}
198
- >
199
- <div class="settings-build-stat-label">Total build cost</div>
200
- <div class="settings-build-stat-value">
201
- <span class="prefix">$</span>
202
- <span class="value">
203
- ${(x) => x.cumulativeCostUsd.toFixed(SESSION_COST_DECIMALS)}
204
- </span>
205
- </div>
343
+ ${tokenBreakdownTemplate((x) => x.sessionUsage, 'session')}
206
344
  </div>
207
345
  </div>
208
- ${when((x) => { var _a; return ((_a = x.chatConfig.ui) === null || _a === void 0 ? void 0 : _a.showActiveModel) !== false && x.providerStatuses.length > 0; }, html `
346
+ ${when((x) => { var _a; return ((_a = x.chatConfig.ui) === null || _a === void 0 ? void 0 : _a.showActiveModel) !== false && x.uniqueProviderModels.length > 0; }, html `
209
347
  <div class="settings-model-tags" part="session-model-tags">
210
- ${repeat((x) => x.providerStatuses, html `
211
- <span
212
- class="settings-model-tag vendor-${(entry) => { var _a; return ((_a = entry.status) === null || _a === void 0 ? void 0 : _a.provider) || 'generic'; }}"
213
- >
214
- ${(entry) => { var _a, _b; return (_b = (_a = entry.status) === null || _a === void 0 ? void 0 : _a.model) !== null && _b !== void 0 ? _b : entry.name; }}
215
- </span>
216
- `)}
348
+ ${repeat((x) => x.uniqueProviderModels, modelTagTemplate)}
217
349
  </div>
218
350
  `)}
219
351
  </div>
220
352
  `)}
221
353
  `;
222
354
  const costSessionHistoryTemplate = (iconTag, buttonTag) => html `
223
- ${repeat((x) => x.costSessionHistory, html `
355
+ ${repeat((x) => x.sortedCostSessionHistory, html `
224
356
  <div class="settings-cost-history-row" part="cost-history-row">
225
357
  <div class="settings-cost-history-row-content">
226
358
  <span class="settings-cost-history-icon" part="cost-history-icon" aria-hidden="true">
@@ -232,46 +364,58 @@ const costSessionHistoryTemplate = (iconTag, buttonTag) => html `
232
364
  ${(record) => record.title}
233
365
  </div>
234
366
  <div class="settings-cost-history-row-date">
235
- ${(record) => formatCostSessionDate(record.endedAt)}
367
+ ${(record) => formatCostSessionDate(record.updatedAt)}
236
368
  </div>
237
369
  </div>
370
+ <!--
371
+ Cost and the delete control live together so they centre against each other.
372
+ The button used to sit outside the row body, held in place by a negative margin
373
+ tuned for when this column had a second line (a merged token total); that line
374
+ is gone, and a nudge measured against a removed sibling is exactly the kind that
375
+ rots silently.
376
+ -->
238
377
  <div class="settings-cost-history-row-right">
239
378
  <div class="settings-cost-history-row-metrics">
240
379
  <div class="settings-cost-history-row-cost">
241
- $${(record) => record.costUsd.toFixed(SESSION_COST_DECIMALS)}
242
- </div>
243
- <div class="settings-cost-history-row-tokens">
244
- ${(record) => formatCompactTokenCountPlain(record.tokensConsumed)} tokens
380
+ $${(record) => record.usage.costUsd.toFixed(SESSION_COST_DECIMALS)}
245
381
  </div>
246
382
  </div>
383
+ <${buttonTag}
384
+ class="settings-cost-history-delete"
385
+ part="cost-history-delete"
386
+ appearance="stealth"
387
+ aria-label="Remove from build history"
388
+ @click=${(record, c) => c.parent.showConfirmationDialog('Are you sure you want to remove this build from history?', c.parent.settingsModal, () => c.parent.removeCostSessionFromHistory(record.id))}
389
+ >
390
+ <${iconTag} name="trash"></${iconTag}>
391
+ </${buttonTag}>
247
392
  </div>
248
393
  </div>
249
- <${buttonTag}
250
- class="settings-cost-history-delete"
251
- part="cost-history-delete"
252
- appearance="stealth"
253
- aria-label="Remove from build history"
254
- @click=${(record, c) => c.parent.showConfirmationDialog('Are you sure you want to remove this build from history?', c.parent.settingsModal, () => c.parent.removeCostSessionFromHistory(record.id))}
255
- >
256
- <${iconTag} name="trash"></${iconTag}>
257
- </${buttonTag}>
394
+ </div>
395
+ <!--
396
+ The same four buckets as the live block, on its own line below the row rather than
397
+ beside the cost — the row's own width is spoken for by title, cost and delete.
398
+ Replaces a merged total that could not explain a huge token count at a small cost
399
+ without hovering for a tooltip.
400
+ -->
401
+ <div
402
+ class="settings-cost-history-row-content settings-cost-history-row-breakdown"
403
+ ?hidden=${(record, c) => !c.parent.resolvedCostHistory.showTokens}
404
+ >
405
+ ${tokenBreakdownTemplate((record) => record.usage, 'cost-history-row')}
258
406
  </div>
259
407
  ${when((record) => record.models.length > 0, html `
260
408
  <div
261
409
  class="settings-cost-history-row-content settings-model-tags settings-cost-history-model-tags"
262
410
  >
263
- ${repeat((record) => record.models, html `
264
- <span class="settings-model-tag vendor-${(model) => model.provider || 'generic'}">
265
- ${(model) => model.model}
266
- </span>
267
- `)}
411
+ ${repeat((record) => record.models, modelTagTemplate)}
268
412
  </div>
269
413
  `)}
270
414
  </div>
271
415
  `)}
272
416
  `;
273
417
  /**
274
- * Modal shell for assistant settings — Settings and Cost tabs, section slots for
418
+ * Modal shell for assistant settings — Configuration and Usage tabs, section slots for
275
419
  * app-specific UI, and built-in chat-bot toggles.
276
420
  */
277
421
  export const SettingsModalTemplate = (designSystemPrefix) => {
@@ -295,7 +439,7 @@ export const SettingsModalTemplate = (designSystemPrefix) => {
295
439
  part="settings-section-model"
296
440
  ?hidden=${(x) => !x.settingsModelSectionVisible}
297
441
  >
298
- <h3 class="settings-modal-section-title">
442
+ <h3 class="ai-settings-section-title">
299
443
  <${iconTag} name="microchip"></${iconTag}> AI Model Settings
300
444
  </h3>
301
445
  <div class="settings-modal-section-body">
@@ -303,18 +447,11 @@ export const SettingsModalTemplate = (designSystemPrefix) => {
303
447
  ${platformContextUsageTemplate(progressTag)}
304
448
  </div>
305
449
  </section>
306
- <section
307
- class="settings-modal-section"
308
- part="settings-section-app"
309
- ?hidden=${(x) => !x.settingsAppSectionVisible}
310
- >
311
- <h3 class="settings-modal-section-title">
312
- <${iconTag} name="home"></${iconTag}> ${(x) => x.appSettingsSectionTitle}
313
- </h3>
314
- <div class="settings-modal-section-body">
315
- ${appSettingsTemplate(switchTag)}
316
- </div>
317
- </section>
450
+ <!--
451
+ Above the host section, deliberately. This is the assistant's own control, so it belongs
452
+ with the assistant's other sections rather than trailing the host's — where, now that the
453
+ host section carries no heading of its own, it read as one more of the host's settings.
454
+ -->
318
455
  ${when((x) => { var _a; return ((_a = x.chatConfig.ui) === null || _a === void 0 ? void 0 : _a.allowDebugDownload) === true; }, html `
319
456
  <section class="settings-modal-section" part="settings-section-agent-log">
320
457
  <div class="settings-modal-section-body">
@@ -334,12 +471,27 @@ export const SettingsModalTemplate = (designSystemPrefix) => {
334
471
  `)}
335
472
  ${(x) => (x.downloadingDebugLog ? 'Downloading…' : 'Download')}
336
473
  </${buttonTag}>
337
- `)}
474
+ `, 'row-centered')}
338
475
  </div>
339
476
  </section>
340
477
  `)}
478
+ <!--
479
+ No heading here, by design. This section belongs to the host: only it knows what its
480
+ settings are called, so the assistant supplies neither text nor icon and the host titles
481
+ the section from the slot. See assistantSettingsSectionTitleStyles for the heading style
482
+ it can apply to match the sections above and below.
483
+ -->
484
+ <section
485
+ class="settings-modal-section"
486
+ part="settings-section-app"
487
+ ?hidden=${(x) => !x.settingsAppSectionVisible}
488
+ >
489
+ <div class="settings-modal-section-body">
490
+ ${appSettingsTemplate(switchTag, iconTag)}
491
+ </div>
492
+ </section>
341
493
  <section class="settings-modal-section" part="settings-section-chat">
342
- <h3 class="settings-modal-section-title">
494
+ <h3 class="ai-settings-section-title">
343
495
  <${iconTag} name="comment"></${iconTag}> AI Chat Bot Settings
344
496
  </h3>
345
497
  <p class="settings-modal-section-note">These options only control what you see in the chat window and do not control model behaviour.</p>
@@ -357,6 +509,7 @@ export const SettingsModalTemplate = (designSystemPrefix) => {
357
509
  part="cost-tab-panel"
358
510
  >
359
511
  <div class="settings-modal-body" part="settings-modal-body">
512
+ ${lifetimeUsageTemplate()}
360
513
  ${sessionCostSummaryTemplate()}
361
514
  <div
362
515
  class="settings-cost-current"
@@ -406,19 +559,22 @@ export const SettingsModalTemplate = (designSystemPrefix) => {
406
559
  appearance="secondary"
407
560
  class="settings-modal-tab"
408
561
  part="settings-modal-tab-settings"
409
- ><${iconTag} name="cog"></${iconTag}> AI Assistant</${tabTag}>${when((x) => x.settingsCostTabVisible, html `<${tabTag}
562
+ ><${iconTag} name="sliders"></${iconTag}> Configuration</${tabTag}>${when((x) => x.settingsCostTabVisible, html `<${tabTag}
410
563
  slot="tab"
411
564
  id="cost"
412
565
  appearance="secondary"
413
566
  class="settings-modal-tab"
414
567
  part="settings-modal-tab-cost"
415
- ><${iconTag} name="dollar-sign"></${iconTag}> Cost</${tabTag}>`)}${settingsTabPanel}${when((x) => x.settingsCostTabVisible, costTabPanel)}</${tabsTag}>
568
+ ><${iconTag} name="chart-simple"></${iconTag}> Usage</${tabTag}>`)}${settingsTabPanel}${when((x) => x.settingsCostTabVisible, costTabPanel)}</${tabsTag}>
416
569
  </div>
417
570
  <div slot="bottom" class="settings-modal-footer" part="settings-modal-footer">
418
571
  <div class="settings-modal-footer-actions">
419
572
  ${when((x) => x.settingsModalTab === 'cost', html `
420
573
  <span class="settings-disclaimer">
421
- Costs are estimated from token usage at list price.
574
+ ${when((x) => !!x.resolvedCostHistory.summaryHint, html `
575
+ ${(x) => x.resolvedCostHistory.summaryHint}
576
+ `)}Costs
577
+ are estimated from token usage at list price.
422
578
  </span>
423
579
  `)}
424
580
  <${buttonTag}
package/dist/esm/index.js CHANGED
@@ -15,4 +15,5 @@ export * from './state/persistence';
15
15
  export * from './provider/ai-provider-switcher';
16
16
  export * from './provider/assistant-app-settings';
17
17
  export * from './utils/tool-fold';
18
+ export { assistantSettingsSectionTitleStyles } from './styles/settings-section';
18
19
  export { AiChatMarkdown } from './components/chat-markdown/chat-markdown';