@genesislcap/ai-assistant 15.19.0 → 15.19.1

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 (288) hide show
  1. package/package.json +17 -18
  2. package/api-extractor.json +0 -11
  3. package/dist/dts/components/chat-driver/chat-driver.compact.test.d.ts +0 -2
  4. package/dist/dts/components/chat-driver/chat-driver.compact.test.d.ts.map +0 -1
  5. package/dist/dts/components/chat-driver/chat-driver.invocation-scope.test.d.ts +0 -2
  6. package/dist/dts/components/chat-driver/chat-driver.invocation-scope.test.d.ts.map +0 -1
  7. package/dist/dts/components/chat-driver/chat-driver.test.d.ts +0 -2
  8. package/dist/dts/components/chat-driver/chat-driver.test.d.ts.map +0 -1
  9. package/dist/dts/components/chat-driver/chat-driver.thinking-policy.test.d.ts +0 -2
  10. package/dist/dts/components/chat-driver/chat-driver.thinking-policy.test.d.ts.map +0 -1
  11. package/dist/dts/components/chat-driver/chat-driver.trace-capture.test.d.ts +0 -2
  12. package/dist/dts/components/chat-driver/chat-driver.trace-capture.test.d.ts.map +0 -1
  13. package/dist/dts/components/chat-driver/chat-driver.turn-usage.test.d.ts +0 -2
  14. package/dist/dts/components/chat-driver/chat-driver.turn-usage.test.d.ts.map +0 -1
  15. package/dist/dts/components/chat-interaction-wrapper/chat-interaction-wrapper.test.d.ts +0 -2
  16. package/dist/dts/components/chat-interaction-wrapper/chat-interaction-wrapper.test.d.ts.map +0 -1
  17. package/dist/dts/components/orchestrating-driver/orchestrating-driver.budget.test.d.ts +0 -2
  18. package/dist/dts/components/orchestrating-driver/orchestrating-driver.budget.test.d.ts.map +0 -1
  19. package/dist/dts/components/orchestrating-driver/orchestrating-driver.cost.test.d.ts +0 -2
  20. package/dist/dts/components/orchestrating-driver/orchestrating-driver.cost.test.d.ts.map +0 -1
  21. package/dist/dts/components/orchestrating-driver/orchestrating-driver.pin.test.d.ts +0 -2
  22. package/dist/dts/components/orchestrating-driver/orchestrating-driver.pin.test.d.ts.map +0 -1
  23. package/dist/dts/components/settings-modal/settings-modal.styles.test.d.ts +0 -2
  24. package/dist/dts/components/settings-modal/settings-modal.styles.test.d.ts.map +0 -1
  25. package/dist/dts/components/settings-modal/settings-modal.template.test.d.ts +0 -2
  26. package/dist/dts/components/settings-modal/settings-modal.template.test.d.ts.map +0 -1
  27. package/dist/dts/config/define-stateful-agent.test.d.ts +0 -2
  28. package/dist/dts/config/define-stateful-agent.test.d.ts.map +0 -1
  29. package/dist/dts/config/validate-providers.test.d.ts +0 -2
  30. package/dist/dts/config/validate-providers.test.d.ts.map +0 -1
  31. package/dist/dts/main/blocked-state.test.d.ts +0 -2
  32. package/dist/dts/main/blocked-state.test.d.ts.map +0 -1
  33. package/dist/dts/main/budget-meter.test.d.ts +0 -2
  34. package/dist/dts/main/budget-meter.test.d.ts.map +0 -1
  35. package/dist/dts/main/cost-session-banking.test.d.ts +0 -2
  36. package/dist/dts/main/cost-session-banking.test.d.ts.map +0 -1
  37. package/dist/dts/main/file-attachments.test.d.ts +0 -2
  38. package/dist/dts/main/file-attachments.test.d.ts.map +0 -1
  39. package/dist/dts/main/interaction-cost.test.d.ts +0 -2
  40. package/dist/dts/main/interaction-cost.test.d.ts.map +0 -1
  41. package/dist/dts/main/main.styles.test.d.ts +0 -2
  42. package/dist/dts/main/main.styles.test.d.ts.map +0 -1
  43. package/dist/dts/main/persistence-broken-sources.test.d.ts +0 -2
  44. package/dist/dts/main/persistence-broken-sources.test.d.ts.map +0 -1
  45. package/dist/dts/main/popout-interaction-gate.test.d.ts +0 -2
  46. package/dist/dts/main/popout-interaction-gate.test.d.ts.map +0 -1
  47. package/dist/dts/state/ai-assistant-slice.test.d.ts +0 -2
  48. package/dist/dts/state/ai-assistant-slice.test.d.ts.map +0 -1
  49. package/dist/dts/state/debug-event-log.test.d.ts +0 -2
  50. package/dist/dts/state/debug-event-log.test.d.ts.map +0 -1
  51. package/dist/dts/state/driver-registry.test.d.ts +0 -2
  52. package/dist/dts/state/driver-registry.test.d.ts.map +0 -1
  53. package/dist/dts/state/interaction-context.test.d.ts +0 -2
  54. package/dist/dts/state/interaction-context.test.d.ts.map +0 -1
  55. package/dist/dts/state/persistence/diagnostics-cursors.test.d.ts +0 -2
  56. package/dist/dts/state/persistence/diagnostics-cursors.test.d.ts.map +0 -1
  57. package/dist/dts/state/persistence/diagnostics.test.d.ts +0 -2
  58. package/dist/dts/state/persistence/diagnostics.test.d.ts.map +0 -1
  59. package/dist/dts/state/persistence/session-persistence-provider.test.d.ts +0 -2
  60. package/dist/dts/state/persistence/session-persistence-provider.test.d.ts.map +0 -1
  61. package/dist/dts/state/persistence/session-persistence.integration.test.d.ts +0 -2
  62. package/dist/dts/state/persistence/session-persistence.integration.test.d.ts.map +0 -1
  63. package/dist/dts/state/persistence/session-persister.test.d.ts +0 -2
  64. package/dist/dts/state/persistence/session-persister.test.d.ts.map +0 -1
  65. package/dist/dts/state/persistence/session-snapshot.test.d.ts +0 -2
  66. package/dist/dts/state/persistence/session-snapshot.test.d.ts.map +0 -1
  67. package/dist/dts/state/persistence/stateful-restore.e2e.test.d.ts +0 -2
  68. package/dist/dts/state/persistence/stateful-restore.e2e.test.d.ts.map +0 -1
  69. package/dist/dts/utils/animation-exclusivity.test.d.ts +0 -2
  70. package/dist/dts/utils/animation-exclusivity.test.d.ts.map +0 -1
  71. package/dist/dts/utils/collect-session-models.test.d.ts +0 -2
  72. package/dist/dts/utils/collect-session-models.test.d.ts.map +0 -1
  73. package/dist/dts/utils/condense-history.test.d.ts +0 -2
  74. package/dist/dts/utils/condense-history.test.d.ts.map +0 -1
  75. package/dist/dts/utils/cost-session-history.test.d.ts +0 -2
  76. package/dist/dts/utils/cost-session-history.test.d.ts.map +0 -1
  77. package/dist/dts/utils/derive-cost-session-title.test.d.ts +0 -2
  78. package/dist/dts/utils/derive-cost-session-title.test.d.ts.map +0 -1
  79. package/dist/dts/utils/flatten-sub-agent-messages.test.d.ts +0 -2
  80. package/dist/dts/utils/flatten-sub-agent-messages.test.d.ts.map +0 -1
  81. package/dist/dts/utils/history-transform.test.d.ts +0 -2
  82. package/dist/dts/utils/history-transform.test.d.ts.map +0 -1
  83. package/dist/dts/utils/message-partition.test.d.ts +0 -2
  84. package/dist/dts/utils/message-partition.test.d.ts.map +0 -1
  85. package/dist/dts/utils/resolve-cost-history-config.test.d.ts +0 -2
  86. package/dist/dts/utils/resolve-cost-history-config.test.d.ts.map +0 -1
  87. package/dist/dts/utils/resolve-preference-baseline.test.d.ts +0 -2
  88. package/dist/dts/utils/resolve-preference-baseline.test.d.ts.map +0 -1
  89. package/dist/dts/utils/strip-agent-handlers.test.d.ts +0 -2
  90. package/dist/dts/utils/strip-agent-handlers.test.d.ts.map +0 -1
  91. package/dist/dts/utils/sum-costs.test.d.ts +0 -2
  92. package/dist/dts/utils/sum-costs.test.d.ts.map +0 -1
  93. package/dist/dts/utils/sum-tokens.test.d.ts +0 -2
  94. package/dist/dts/utils/sum-tokens.test.d.ts.map +0 -1
  95. package/dist/dts/utils/sum-usage.test.d.ts +0 -2
  96. package/dist/dts/utils/sum-usage.test.d.ts.map +0 -1
  97. package/dist/dts/utils/usage-rows.test.d.ts +0 -2
  98. package/dist/dts/utils/usage-rows.test.d.ts.map +0 -1
  99. package/dist/esm/components/chat-driver/chat-driver.compact.test.js +0 -93
  100. package/dist/esm/components/chat-driver/chat-driver.invocation-scope.test.js +0 -238
  101. package/dist/esm/components/chat-driver/chat-driver.test.js +0 -2770
  102. package/dist/esm/components/chat-driver/chat-driver.thinking-policy.test.js +0 -137
  103. package/dist/esm/components/chat-driver/chat-driver.trace-capture.test.js +0 -200
  104. package/dist/esm/components/chat-driver/chat-driver.turn-usage.test.js +0 -268
  105. package/dist/esm/components/chat-interaction-wrapper/chat-interaction-wrapper.test.js +0 -104
  106. package/dist/esm/components/orchestrating-driver/orchestrating-driver.budget.test.js +0 -312
  107. package/dist/esm/components/orchestrating-driver/orchestrating-driver.cost.test.js +0 -76
  108. package/dist/esm/components/orchestrating-driver/orchestrating-driver.pin.test.js +0 -61
  109. package/dist/esm/components/settings-modal/settings-modal.styles.test.js +0 -80
  110. package/dist/esm/components/settings-modal/settings-modal.template.test.js +0 -91
  111. package/dist/esm/config/define-stateful-agent.test.js +0 -53
  112. package/dist/esm/config/validate-providers.test.js +0 -100
  113. package/dist/esm/main/blocked-state.test.js +0 -1065
  114. package/dist/esm/main/budget-meter.test.js +0 -359
  115. package/dist/esm/main/cost-session-banking.test.js +0 -538
  116. package/dist/esm/main/file-attachments.test.js +0 -164
  117. package/dist/esm/main/interaction-cost.test.js +0 -107
  118. package/dist/esm/main/main.styles.test.js +0 -161
  119. package/dist/esm/main/persistence-broken-sources.test.js +0 -180
  120. package/dist/esm/main/popout-interaction-gate.test.js +0 -54
  121. package/dist/esm/state/ai-assistant-slice.test.js +0 -229
  122. package/dist/esm/state/debug-event-log.test.js +0 -117
  123. package/dist/esm/state/driver-registry.test.js +0 -55
  124. package/dist/esm/state/interaction-context.test.js +0 -78
  125. package/dist/esm/state/persistence/diagnostics-cursors.test.js +0 -30
  126. package/dist/esm/state/persistence/diagnostics.test.js +0 -280
  127. package/dist/esm/state/persistence/session-persistence-provider.test.js +0 -110
  128. package/dist/esm/state/persistence/session-persistence.integration.test.js +0 -113
  129. package/dist/esm/state/persistence/session-persister.test.js +0 -613
  130. package/dist/esm/state/persistence/session-snapshot.test.js +0 -256
  131. package/dist/esm/state/persistence/stateful-restore.e2e.test.js +0 -158
  132. package/dist/esm/utils/animation-exclusivity.test.js +0 -52
  133. package/dist/esm/utils/collect-session-models.test.js +0 -53
  134. package/dist/esm/utils/condense-history.test.js +0 -547
  135. package/dist/esm/utils/cost-session-history.test.js +0 -174
  136. package/dist/esm/utils/derive-cost-session-title.test.js +0 -37
  137. package/dist/esm/utils/flatten-sub-agent-messages.test.js +0 -139
  138. package/dist/esm/utils/history-transform.test.js +0 -158
  139. package/dist/esm/utils/message-partition.test.js +0 -90
  140. package/dist/esm/utils/resolve-cost-history-config.test.js +0 -32
  141. package/dist/esm/utils/resolve-preference-baseline.test.js +0 -69
  142. package/dist/esm/utils/strip-agent-handlers.test.js +0 -81
  143. package/dist/esm/utils/sum-costs.test.js +0 -114
  144. package/dist/esm/utils/sum-tokens.test.js +0 -51
  145. package/dist/esm/utils/sum-usage.test.js +0 -164
  146. package/dist/esm/utils/usage-rows.test.js +0 -189
  147. package/dist/tsconfig.tsbuildinfo +0 -1
  148. package/license.txt +0 -46
  149. package/sandbox/tsconfig.json +0 -8
  150. package/src/channel/ai-activity-bus.ts +0 -209
  151. package/src/channel/ai-activity-channel.ts +0 -146
  152. package/src/chat-driver-node.ts +0 -159
  153. package/src/components/activity-halo/activity-halo.ts +0 -106
  154. package/src/components/agent-picker/agent-picker.constants.ts +0 -2
  155. package/src/components/agent-picker/agent-picker.styles.ts +0 -24
  156. package/src/components/agent-picker/agent-picker.template.ts +0 -54
  157. package/src/components/agent-picker/agent-picker.ts +0 -99
  158. package/src/components/agent-picker/index.ts +0 -1
  159. package/src/components/ai-driver/ai-driver.ts +0 -166
  160. package/src/components/ai-driver/index.ts +0 -1
  161. package/src/components/chat-bubble/chat-bubble.styles.ts +0 -194
  162. package/src/components/chat-bubble/chat-bubble.template.ts +0 -67
  163. package/src/components/chat-bubble/chat-bubble.ts +0 -436
  164. package/src/components/chat-bubble/index.ts +0 -1
  165. package/src/components/chat-driver/align-event-globals.ts +0 -23
  166. package/src/components/chat-driver/chat-driver.compact.test.ts +0 -131
  167. package/src/components/chat-driver/chat-driver.invocation-scope.test.ts +0 -285
  168. package/src/components/chat-driver/chat-driver.test.ts +0 -3679
  169. package/src/components/chat-driver/chat-driver.thinking-policy.test.ts +0 -185
  170. package/src/components/chat-driver/chat-driver.trace-capture.test.ts +0 -251
  171. package/src/components/chat-driver/chat-driver.ts +0 -3925
  172. package/src/components/chat-driver/chat-driver.turn-usage.test.ts +0 -362
  173. package/src/components/chat-driver/index.ts +0 -1
  174. package/src/components/chat-interaction-wrapper/chat-interaction-wrapper.styles.ts +0 -7
  175. package/src/components/chat-interaction-wrapper/chat-interaction-wrapper.template.ts +0 -6
  176. package/src/components/chat-interaction-wrapper/chat-interaction-wrapper.test.ts +0 -144
  177. package/src/components/chat-interaction-wrapper/chat-interaction-wrapper.ts +0 -243
  178. package/src/components/chat-interaction-wrapper/index.ts +0 -3
  179. package/src/components/chat-markdown/chat-markdown.ts +0 -89
  180. package/src/components/chat-markdown/index.ts +0 -1
  181. package/src/components/flowing-waves-indicator.ts +0 -286
  182. package/src/components/halo-overlay.ts +0 -184
  183. package/src/components/orchestrating-driver/index.ts +0 -1
  184. package/src/components/orchestrating-driver/orchestrating-driver.budget.test.ts +0 -438
  185. package/src/components/orchestrating-driver/orchestrating-driver.cost.test.ts +0 -107
  186. package/src/components/orchestrating-driver/orchestrating-driver.pin.test.ts +0 -96
  187. package/src/components/orchestrating-driver/orchestrating-driver.ts +0 -813
  188. package/src/components/plasma-orb-indicator.ts +0 -281
  189. package/src/components/popout-manager/index.ts +0 -1
  190. package/src/components/popout-manager/popout-manager.ts +0 -212
  191. package/src/components/settings-modal/index.ts +0 -2
  192. package/src/components/settings-modal/settings-modal.styles.test.ts +0 -94
  193. package/src/components/settings-modal/settings-modal.styles.ts +0 -853
  194. package/src/components/settings-modal/settings-modal.template.test.ts +0 -124
  195. package/src/components/settings-modal/settings-modal.template.ts +0 -943
  196. package/src/components/waves-indicator.ts +0 -241
  197. package/src/config/config.ts +0 -608
  198. package/src/config/define-stateful-agent.test.ts +0 -64
  199. package/src/config/define-stateful-agent.ts +0 -642
  200. package/src/config/fallback-agents.ts +0 -29
  201. package/src/config/index.ts +0 -2
  202. package/src/config/validate-providers.test.ts +0 -148
  203. package/src/config/validate-providers.ts +0 -58
  204. package/src/index.ts +0 -29
  205. package/src/main/blocked-state.test.ts +0 -1444
  206. package/src/main/budget-meter.test.ts +0 -477
  207. package/src/main/cost-session-banking.test.ts +0 -679
  208. package/src/main/file-attachments.test.ts +0 -215
  209. package/src/main/index.ts +0 -3
  210. package/src/main/interaction-cost.test.ts +0 -140
  211. package/src/main/main.styles.test.ts +0 -216
  212. package/src/main/main.styles.ts +0 -995
  213. package/src/main/main.template.ts +0 -1119
  214. package/src/main/main.ts +0 -5432
  215. package/src/main/main.types.ts +0 -225
  216. package/src/main/persistence-broken-sources.test.ts +0 -219
  217. package/src/main/popout-interaction-gate.test.ts +0 -87
  218. package/src/provider/ai-provider-switcher.ts +0 -40
  219. package/src/provider/assistant-app-settings.ts +0 -84
  220. package/src/state/ai-assistant-slice.test.ts +0 -336
  221. package/src/state/ai-assistant-slice.ts +0 -604
  222. package/src/state/debug-event-log.test.ts +0 -154
  223. package/src/state/debug-event-log.ts +0 -358
  224. package/src/state/driver-registry.test.ts +0 -70
  225. package/src/state/driver-registry.ts +0 -79
  226. package/src/state/interaction-context.test.ts +0 -90
  227. package/src/state/interaction-context.ts +0 -101
  228. package/src/state/persistence/build-timeline-entries.ts +0 -66
  229. package/src/state/persistence/diagnostics-cursors.test.ts +0 -53
  230. package/src/state/persistence/diagnostics-cursors.ts +0 -80
  231. package/src/state/persistence/diagnostics.test.ts +0 -346
  232. package/src/state/persistence/diagnostics.ts +0 -220
  233. package/src/state/persistence/index.ts +0 -10
  234. package/src/state/persistence/persister-registry.ts +0 -55
  235. package/src/state/persistence/session-persistence-provider.test.ts +0 -136
  236. package/src/state/persistence/session-persistence-provider.ts +0 -226
  237. package/src/state/persistence/session-persistence.integration.test.ts +0 -141
  238. package/src/state/persistence/session-persister.test.ts +0 -787
  239. package/src/state/persistence/session-persister.ts +0 -917
  240. package/src/state/persistence/session-snapshot.test.ts +0 -352
  241. package/src/state/persistence/session-snapshot.ts +0 -344
  242. package/src/state/persistence/stateful-restore.e2e.test.ts +0 -229
  243. package/src/state/session-store.ts +0 -56
  244. package/src/styles/ai-colours.ts +0 -30
  245. package/src/styles/menu.ts +0 -187
  246. package/src/styles/settings-section.ts +0 -40
  247. package/src/suggestions/chat-suggestions.ts +0 -186
  248. package/src/tags/index.ts +0 -1
  249. package/src/types/ai-chat-widget.ts +0 -67
  250. package/src/types/interaction-context.ts +0 -82
  251. package/src/utils/animated-panel-toggle.ts +0 -62
  252. package/src/utils/animation-exclusivity.test.ts +0 -72
  253. package/src/utils/animation-exclusivity.ts +0 -40
  254. package/src/utils/banked-usage-baselines.ts +0 -90
  255. package/src/utils/collect-session-models.test.ts +0 -82
  256. package/src/utils/collect-session-models.ts +0 -65
  257. package/src/utils/condense-history.test.ts +0 -746
  258. package/src/utils/condense-history.ts +0 -448
  259. package/src/utils/cost-session-history.test.ts +0 -226
  260. package/src/utils/cost-session-history.ts +0 -218
  261. package/src/utils/derive-cost-session-title.test.ts +0 -63
  262. package/src/utils/derive-cost-session-title.ts +0 -76
  263. package/src/utils/flatten-sub-agent-messages.test.ts +0 -163
  264. package/src/utils/flatten-sub-agent-messages.ts +0 -88
  265. package/src/utils/format-usd.ts +0 -26
  266. package/src/utils/history-transform.test.ts +0 -228
  267. package/src/utils/history-transform.ts +0 -163
  268. package/src/utils/index.ts +0 -3
  269. package/src/utils/logger.ts +0 -3
  270. package/src/utils/message-partition.test.ts +0 -129
  271. package/src/utils/message-partition.ts +0 -115
  272. package/src/utils/resolve-cost-history-config.test.ts +0 -38
  273. package/src/utils/resolve-cost-history-config.ts +0 -38
  274. package/src/utils/resolve-preference-baseline.test.ts +0 -84
  275. package/src/utils/resolve-preference-baseline.ts +0 -80
  276. package/src/utils/strip-agent-handlers.test.ts +0 -99
  277. package/src/utils/strip-agent-handlers.ts +0 -53
  278. package/src/utils/sum-costs.test.ts +0 -136
  279. package/src/utils/sum-costs.ts +0 -18
  280. package/src/utils/sum-tokens.test.ts +0 -93
  281. package/src/utils/sum-tokens.ts +0 -68
  282. package/src/utils/sum-usage.test.ts +0 -191
  283. package/src/utils/sum-usage.ts +0 -208
  284. package/src/utils/tool-fold.ts +0 -181
  285. package/src/utils/usage-rows.test.ts +0 -237
  286. package/src/utils/usage-rows.ts +0 -187
  287. package/src/utils/with-timeout.ts +0 -24
  288. package/tsconfig.json +0 -9
@@ -1,943 +0,0 @@
1
- import type { AggregateUsage } from '@genesislcap/foundation-ai';
2
- import { html, ref, repeat, slotted, when, type ViewTemplate } from '@genesislcap/web-core';
3
- import type { FoundationAiAssistant } from '../../main/main';
4
- import {
5
- ANIMATION_DEFS,
6
- type AiAssistantAnimation,
7
- type SettingsBudgetRow,
8
- } from '../../main/main.types';
9
- import type { AssistantAppSettingsToggle } from '../../provider/assistant-app-settings';
10
- import {
11
- formatCostSessionDate,
12
- type CostSessionModelEntry,
13
- type CostSessionRecord,
14
- } from '../../utils/cost-session-history';
15
- import { formatUsd, formatUsdAmount } from '../../utils/format-usd';
16
- import { formatCompactTokenCountSafe } from '../../utils/sum-tokens';
17
-
18
- type AnimationCategoryGroup = {
19
- category: string;
20
- items: Array<{ value: AiAssistantAnimation; label: string; description: string }>;
21
- };
22
-
23
- type AnimationPickerRow =
24
- | { kind: 'category'; category: string }
25
- | {
26
- kind: 'option';
27
- value: AiAssistantAnimation;
28
- label: string;
29
- description: string;
30
- };
31
-
32
- /** Animation options grouped by {@link ANIMATION_DEFS} category for the settings list. */
33
- const animationCategoryGroups: AnimationCategoryGroup[] = (() => {
34
- const groups = new Map<string, AnimationCategoryGroup['items']>();
35
- for (const [value, def] of Object.entries(ANIMATION_DEFS) as Array<
36
- [AiAssistantAnimation, (typeof ANIMATION_DEFS)[AiAssistantAnimation]]
37
- >) {
38
- const items = groups.get(def.category) ?? [];
39
- items.push({ value, label: def.label, description: def.description });
40
- groups.set(def.category, items);
41
- }
42
- return [...groups.entries()].map(([category, items]) => ({ category, items }));
43
- })();
44
-
45
- const animationPickerRows: AnimationPickerRow[] = animationCategoryGroups.flatMap((group) => [
46
- { kind: 'category', category: group.category },
47
- ...group.items.map(
48
- (item): AnimationPickerRow => ({
49
- kind: 'option',
50
- value: item.value,
51
- label: item.label,
52
- description: item.description,
53
- }),
54
- ),
55
- ]);
56
-
57
- const toggleAnimationEnabled = (
58
- host: FoundationAiAssistant,
59
- value: AiAssistantAnimation,
60
- enabled: boolean,
61
- ): void => {
62
- const next = enabled
63
- ? [...host.enabledAnimations, value]
64
- : host.enabledAnimations?.filter((animation) => animation !== value);
65
- host.setEnabledAnimations(next);
66
- };
67
-
68
- const readSwitchChecked = (event: Event): boolean => (event.target as HTMLInputElement).checked;
69
-
70
- /** Inline animation picker — categories and options always visible (no multiselect popup). */
71
- const animationsPickerTemplate = (
72
- switchTag: string,
73
- ): ViewTemplate<FoundationAiAssistant> => html<FoundationAiAssistant>`
74
- <div class="settings-animation-list" part="toggle-animations">
75
- ${repeat(
76
- (x) => {
77
- void x.enabledAnimations;
78
- return animationPickerRows;
79
- },
80
- html<AnimationPickerRow, FoundationAiAssistant>`
81
- ${when(
82
- (row) => row.kind === 'category',
83
- html<AnimationPickerRow>`
84
- <div class="settings-animation-category-label" part="animation-category">
85
- ${(row) => (row.kind === 'category' ? row.category : '')}
86
- </div>
87
- `,
88
- )}
89
- ${when(
90
- (row) => row.kind === 'option',
91
- html<AnimationPickerRow, FoundationAiAssistant>`
92
- <div class="settings-toggle-row settings-animation-option" part="animation-option">
93
- <div class="settings-toggle-text">
94
- <div class="settings-toggle-title">
95
- ${(row) => (row.kind === 'option' ? row.label : '')}
96
- </div>
97
- <div class="settings-toggle-desc">
98
- ${(row) => (row.kind === 'option' ? row.description : '')}
99
- </div>
100
- </div>
101
- <div class="settings-toggle-control">
102
- <${switchTag}
103
- part="animation-toggle"
104
- :checked=${(row, c) =>
105
- row.kind === 'option' && c.parent.enabledAnimations.includes(row.value)}
106
- @change=${(row, c) => {
107
- if (row.kind !== 'option') return;
108
- toggleAnimationEnabled(c.parent, row.value, readSwitchChecked(c.event));
109
- }}
110
- ></${switchTag}>
111
- </div>
112
- </div>
113
- `,
114
- )}
115
- `,
116
- )}
117
- </div>
118
- `;
119
-
120
- /**
121
- * Tab identifiers for the modal tab bar.
122
- *
123
- * The values are deliberately NOT renamed to match the visible labels ("Configuration" and
124
- * "Usage"): they are the public API that hosts read and write via `settingsModalTab`, and are
125
- * matched by the `settings-modal-tab-*` shadow parts. Renaming a label is cosmetic; renaming
126
- * these would break consumers.
127
- */
128
- export type SettingsModalTab = 'settings' | 'cost';
129
-
130
- /**
131
- * How a settings row places its control.
132
- *
133
- * `row` top-aligns it, which is what a switch wants — it then sits level with the title rather
134
- * than floating beside a two-line description. `row-centered` is for a taller control such as a
135
- * button, which top-aligned reads as hanging above the text it belongs to. `column` stacks the
136
- * control under the text, for controls that need the full width.
137
- */
138
- type SettingsRowLayout = 'row' | 'row-centered' | 'column';
139
-
140
- const SETTINGS_ROW_LAYOUT_CLASS: Record<SettingsRowLayout, string> = {
141
- row: '',
142
- 'row-centered': 'is-centered',
143
- column: 'is-stacked',
144
- };
145
-
146
- /** Shared label + control row used for switches, buttons, and multiselects. */
147
- const settingsToggleRow = (
148
- title: string,
149
- description: string,
150
- part: string,
151
- control: ViewTemplate<FoundationAiAssistant>,
152
- layout: SettingsRowLayout = 'row',
153
- ) => html<FoundationAiAssistant>`
154
- <div class="settings-toggle-row ${SETTINGS_ROW_LAYOUT_CLASS[layout]}" part="${part}-row">
155
- <div class="settings-toggle-text">
156
- <div class="settings-toggle-title">${title}</div>
157
- <div class="settings-toggle-desc">${description}</div>
158
- </div>
159
- <div class="settings-toggle-control">${control}</div>
160
- </div>
161
- `;
162
-
163
- const chatBotToggleRow = (
164
- title: string,
165
- description: string,
166
- switchTag: string,
167
- part: string,
168
- checked: (x: FoundationAiAssistant) => boolean,
169
- onChange: (x: FoundationAiAssistant, c: { event: Event }) => void,
170
- ) =>
171
- settingsToggleRow(
172
- title,
173
- description,
174
- part,
175
- html<FoundationAiAssistant>`
176
- <${switchTag}
177
- part="${part}"
178
- :checked=${checked}
179
- @change=${onChange}
180
- ></${switchTag}>
181
- `,
182
- );
183
-
184
- const platformContextUsageTemplate = (
185
- progressTag: string,
186
- ): ViewTemplate<FoundationAiAssistant> => html<FoundationAiAssistant>`
187
- ${when(
188
- // The getter, not a copy of its expression: `settingsUsageMetersTemplate`
189
- // gates its shared wrapper on this same getter, and the two must agree —
190
- // a drifted copy would leave the wrapper rendering around nothing.
191
- (x) => x.settingsContextUsageVisible,
192
- html<FoundationAiAssistant>`
193
- <div class="settings-context-usage" part="context-usage">
194
- <div class="settings-context-header">
195
- <span class="settings-context-label">Context window</span>
196
- <span class="settings-context-tokens">
197
- ${(x) => x.contextTokens!.toLocaleString()} /
198
- ${(x) => x.contextLimit!.toLocaleString()} tokens
199
- (${(x) => Math.round((x.contextTokens! / x.contextLimit!) * 100)}%)
200
- </span>
201
- </div>
202
- <${progressTag}
203
- part="context-progress"
204
- value="${(x) => Math.min(100, Math.round((x.contextTokens! / x.contextLimit!) * 100))}"
205
- ></${progressTag}>
206
- </div>
207
- `,
208
- )}
209
- `;
210
-
211
- /**
212
- * Per-vendor AI budget meter (GENC-1464) — the money sibling of
213
- * {@link platformContextUsageTemplate}, one row per vendor the host has fed
214
- * figures for. The row text is unclamped (overshoot is real and shown); only
215
- * the bar clamps — both computed in `settingsBudgetRows`, so the bindings here
216
- * stay pure reads and the arithmetic is unit-testable.
217
- */
218
- const platformBudgetUsageTemplate = (
219
- progressTag: string,
220
- buttonTag: string,
221
- iconTag: string,
222
- ): ViewTemplate<FoundationAiAssistant> => html<FoundationAiAssistant>`
223
- ${when(
224
- (x) => x.settingsBudgetUsageVisible,
225
- html<FoundationAiAssistant>`
226
- <div class="settings-budget-usage" part="budget-usage">
227
- ${repeat(
228
- (x) => x.settingsBudgetRows,
229
- html<SettingsBudgetRow, FoundationAiAssistant>`
230
- <div class="settings-budget-vendor">
231
- <div class="settings-budget-header">
232
- <span class="settings-budget-label">${(row) => row.label} budget</span>
233
- <span class="settings-budget-figures">${(row) => row.figures}</span>
234
- </div>
235
- <${progressTag}
236
- part="budget-progress"
237
- value="${(row) => row.barValue}"
238
- ></${progressTag}>
239
- </div>
240
- `,
241
- )}
242
- <!--
243
- Freshness footer (GENC-1464): the rows above update per turn and at
244
- the wall, which reads as "stale" to anyone watching the modal — the
245
- label says when the figures arrived, the button asks the host for new
246
- ones (see FoundationAiAssistant.requestBudgetRefresh). Below the rows
247
- and left-aligned, reading as a footnote to the figures it describes.
248
- -->
249
- <div class="settings-budget-toolbar" part="budget-toolbar">
250
- <${buttonTag}
251
- appearance="stealth"
252
- class="settings-budget-refresh"
253
- part="budget-refresh"
254
- title="Refresh budget figures"
255
- aria-label="Refresh budget figures"
256
- @click=${(x: FoundationAiAssistant) => x.requestBudgetRefresh()}
257
- >
258
- <${iconTag} name="arrows-rotate"></${iconTag}>
259
- </${buttonTag}>
260
- <span class="settings-budget-updated">${(x) => x.settingsBudgetUpdatedLabel}</span>
261
- </div>
262
- </div>
263
- `,
264
- )}
265
- `;
266
-
267
- /**
268
- * The two usage meters in one shared, evenly-gapped column.
269
- *
270
- * Without it the context row and the FIRST budget row sit the section body's
271
- * 4 units apart while the budget rows sit 2 apart — three visually identical
272
- * meter rows reading as an uneven 4/2/2. The wrapper puts every meter row on
273
- * the same 2-unit rhythm; see `.settings-usage-meters` in the styles.
274
- *
275
- * CONDITIONAL on purpose. An always-present wrapper is a permanent flex child
276
- * of `.settings-modal-section-body`, so a host with neither meter would get a
277
- * stray 4-unit gap after its model slot from a div that renders nothing. The
278
- * gate is exactly the disjunction of the two children's own gates — both of
279
- * which are `when`, which emplaces only comment markers when false, so a
280
- * hidden meter contributes no flex child inside the wrapper either.
281
- *
282
- * Exported for `settings-modal.template.test.ts`, which renders it in isolation
283
- * to pin both of those properties; `index.ts` does not re-export it, so it is
284
- * not package API.
285
- *
286
- * @internal
287
- */
288
- export const settingsUsageMetersTemplate = (
289
- progressTag: string,
290
- buttonTag: string,
291
- iconTag: string,
292
- ): ViewTemplate<FoundationAiAssistant> => html<FoundationAiAssistant>`
293
- ${when(
294
- (x) => x.settingsContextUsageVisible || x.settingsBudgetUsageVisible,
295
- html<FoundationAiAssistant>`
296
- <div class="settings-usage-meters">
297
- ${platformContextUsageTemplate(progressTag)}
298
- ${platformBudgetUsageTemplate(progressTag, buttonTag, iconTag)}
299
- </div>
300
- `,
301
- )}
302
- `;
303
-
304
- const chatBotSettingsTemplate = (
305
- switchTag: string,
306
- buttonTag: string,
307
- ): ViewTemplate<FoundationAiAssistant> => html<FoundationAiAssistant>`
308
- ${when(
309
- (x) => x.chatConfig.ui?.showToolCalls != null,
310
- chatBotToggleRow(
311
- 'Show tool calls',
312
- "Display each tool the chatbot invokes — file reads, searches, edits and commands — inline as it works, so you can follow exactly what it's doing behind the scenes.",
313
- switchTag,
314
- 'toggle-tool-calls',
315
- (x) => x.showToolCalls,
316
- (x, c) => {
317
- x.showToolCalls = readSwitchChecked(c.event);
318
- },
319
- ),
320
- )}
321
- ${when(
322
- (x) => x.chatConfig.ui?.showThinkingSteps != null,
323
- chatBotToggleRow(
324
- 'Show thinking',
325
- "Reveal the assistant's reasoning as it works through your request, surfacing its intermediate thought process before it commits to a response.",
326
- switchTag,
327
- 'toggle-thinking',
328
- (x) => x.showThinkingSteps,
329
- (x, c) => {
330
- x.showThinkingSteps = readSwitchChecked(c.event);
331
- },
332
- ),
333
- )}
334
- ${when(
335
- (x) => x.chatConfig.ui?.showNarration != null,
336
- chatBotToggleRow(
337
- 'Show working',
338
- "Show the assistant's running commentary — the short notes it writes alongside its actions as it works, separate from its reasoning and its final answer.",
339
- switchTag,
340
- 'toggle-narration',
341
- (x) => x.showNarration,
342
- (x, c) => {
343
- x.showNarration = readSwitchChecked(c.event);
344
- },
345
- ),
346
- )}
347
- ${when(
348
- (x) => x.chatConfig.ui?.showAgentSwitchIndicator != null,
349
- chatBotToggleRow(
350
- 'Show delegation',
351
- 'Show in the chat window when tasks are delegated to different agents.',
352
- switchTag,
353
- 'toggle-agent-switch',
354
- (x) => x.showAgentSwitchIndicator,
355
- (x, c) => {
356
- x.showAgentSwitchIndicator = readSwitchChecked(c.event);
357
- },
358
- ),
359
- )}
360
- ${when(
361
- (x) => x.persistenceToggleable,
362
- chatBotToggleRow(
363
- 'Remember chat',
364
- 'Save this conversation so it can be restored after a page refresh. Turning this off deletes the saved copy.',
365
- switchTag,
366
- 'toggle-remember-chat',
367
- (x) => x.persistenceActive,
368
- (x, c) => {
369
- x.setPersistenceEnabled(readSwitchChecked(c.event));
370
- },
371
- ),
372
- )}
373
- ${when(
374
- (x) => x.chatConfig.ui?.animations?.userConfigurable === true,
375
- settingsToggleRow(
376
- 'Animations',
377
- 'Choose which loading and activity indicators appear while the assistant is working.',
378
- 'toggle-animations',
379
- animationsPickerTemplate(switchTag),
380
- 'column',
381
- ),
382
- )}
383
- `;
384
-
385
- const appSettingsTemplate = (
386
- switchTag: string,
387
- iconTag: string,
388
- ): ViewTemplate<FoundationAiAssistant> => html<FoundationAiAssistant>`
389
- <!--
390
- Slot FIRST, ahead of the provider toggles. The assistant renders no heading for this
391
- section, so whatever the host slots in has to be able to title it — and a heading below the
392
- rows it names would title nothing. Anything the host wants under the toggles it can render
393
- below them within its own slotted block.
394
- -->
395
- <slot name="settings-app" ${slotted('settingsAppSlotted')}></slot>
396
- <!--
397
- Sub-heading for the toggle group, rendered only when the host asks for one — both the text
398
- and the icon come from its provider. It exists so a host whose slotted content covers other
399
- ground can still mark these rows off from it.
400
- -->
401
- ${when(
402
- (x) => !!x.appSettingsTogglesHeading,
403
- html<FoundationAiAssistant>`
404
- <h4 class="ai-settings-section-title settings-app-toggles-title" part="app-toggles-title">
405
- ${when(
406
- (x) => !!x.appSettingsTogglesHeading?.icon,
407
- html<FoundationAiAssistant>`
408
- <${iconTag} name="${(x) => x.appSettingsTogglesHeading?.icon}"></${iconTag}>
409
- `,
410
- )}
411
- ${(x) => x.appSettingsTogglesHeading?.title}
412
- </h4>
413
- `,
414
- )}
415
- ${repeat(
416
- (x) => x.visibleAppSettingsToggles,
417
- html<AssistantAppSettingsToggle, FoundationAiAssistant>`
418
- <div class="settings-app-toggle-block">
419
- <div
420
- class="settings-toggle-row"
421
- part="${(toggle) => `${toggle.part ?? `app-toggle-${toggle.id}`}-row`}"
422
- >
423
- <div class="settings-toggle-text">
424
- <div class="settings-toggle-title">${(toggle) => toggle.title}</div>
425
- <div class="settings-toggle-desc">${(toggle) => toggle.description}</div>
426
- </div>
427
- <div class="settings-toggle-control">
428
- <${switchTag}
429
- part="${(toggle) => toggle.part ?? `app-toggle-${toggle.id}`}"
430
- :checked=${(toggle, c) => c.parent.getAppSettingValue(toggle.id)}
431
- ?disabled=${(toggle) => toggle.disabled?.() ?? false}
432
- @change=${(toggle, c) => {
433
- c.parent.setAppSettingValue(toggle.id, readSwitchChecked(c.event));
434
- }}
435
- ></${switchTag}>
436
- </div>
437
- </div>
438
- ${when(
439
- (toggle) => !!toggle.hint?.(),
440
- html<AssistantAppSettingsToggle>`
441
- <div class="settings-toggle-hint">${(toggle) => toggle.hint?.() ?? ''}</div>
442
- `,
443
- )}
444
- </div>
445
- `,
446
- )}
447
- `;
448
-
449
- /**
450
- * One model chip. Shared by the live session and the history rows so the two can't drift — both
451
- * already reduce to a distinct-model list, the live one via `uniqueProviderModels` and a row via
452
- * `collectSessionModels`.
453
- */
454
- const modelTagTemplate = html<CostSessionModelEntry>`
455
- <span class="settings-model-tag vendor-${(entry) => entry.provider || 'generic'}">
456
- ${(entry) => entry.model}
457
- </span>
458
- `;
459
-
460
- /**
461
- * The four billing buckets for one `AggregateUsage`, wherever it comes from — the lifetime
462
- * total, the live session, or a history row.
463
- *
464
- * Why four numbers instead of one: each bucket bills at a different rate, so a single token
465
- * total can't explain a session that processed millions of tokens for pennies. Uncached input,
466
- * cache read and cache write partition the prompt (they sum to it, never add on top), and
467
- * output is what the model generated.
468
- *
469
- * Ordered input, output, cache read, cache write to match how Claude Code reports usage, so the
470
- * two can be read side by side — comparing against it is what this breakdown exists for. Note
471
- * this puts output between the three buckets that partition the prompt, so their relationship is
472
- * carried by the tooltips rather than by adjacency.
473
- *
474
- * The two cache buckets carry a direction marker on their LABEL, not their value: below the input
475
- * rate for reads, above it for writes. On the label because the rate is a fixed property of the
476
- * bucket, whereas the value changes every turn — and because styling the values instead means a
477
- * bucket reading 0 (every Gemini cache write, which has no write bucket to bill) renders in a
478
- * dimmed treatment that looks disabled beside a normal 0. All four values stay identical.
479
- *
480
- * `partPrefix` keeps the shadow-part names caller-specific (`session-tokens-input`,
481
- * `lifetime-tokens-input`, …) so a host can style one context without hitting the others.
482
- */
483
- const tokenBreakdownTemplate = <T>(
484
- usageOf: (x: T) => AggregateUsage,
485
- partPrefix: string,
486
- ): ViewTemplate<T> => html<T>`
487
- <div class="settings-token-breakdown" part="${partPrefix}-token-breakdown">
488
- <!--
489
- Titles the box. Nothing in it otherwise said "tokens": the four bucket names are the
490
- token TYPES, not the unit, so the panel read as four unexplained numbers unless the
491
- reader found the per-bucket tooltips.
492
- -->
493
- <div class="settings-token-breakdown-label">Tokens</div>
494
- <div class="settings-token-buckets">
495
- <div class="settings-token-bucket" part="${partPrefix}-tokens-input">
496
- <span
497
- class="settings-token-bucket-label"
498
- title="Prompt tokens billed at the full uncached rate"
499
- >
500
- Input
501
- </span>
502
- <span
503
- class="settings-token-bucket-value"
504
- :innerHTML="${(x: T) => formatCompactTokenCountSafe(usageOf(x).uncachedInputTokens)}"
505
- ></span>
506
- </div>
507
- <div class="settings-token-bucket" part="${partPrefix}-tokens-output">
508
- <span
509
- class="settings-token-bucket-label"
510
- title="Tokens the model generated, including reasoning tokens billed as output"
511
- >
512
- Output
513
- </span>
514
- <span
515
- class="settings-token-bucket-value"
516
- :innerHTML="${(x: T) => formatCompactTokenCountSafe(usageOf(x).outputTokens)}"
517
- ></span>
518
- </div>
519
- <div class="settings-token-bucket" part="${partPrefix}-tokens-cache-read">
520
- <span
521
- class="settings-token-bucket-label"
522
- title="Prompt tokens served from the provider cache — billed at a fraction of the uncached rate"
523
- >
524
- Cache read
525
- <!--
526
- aria-hidden: the marker is a restatement of the tooltip this label already carries, and
527
- a screen reader announcing "black down-pointing small triangle" mid-heading is noise.
528
- -->
529
- <span class="settings-token-bucket-rate" aria-hidden="true">▾</span>
530
- </span>
531
- <span
532
- class="settings-token-bucket-value"
533
- :innerHTML="${(x: T) => formatCompactTokenCountSafe(usageOf(x).cacheReadTokens)}"
534
- ></span>
535
- </div>
536
- <div class="settings-token-bucket" part="${partPrefix}-tokens-cache-write">
537
- <span
538
- class="settings-token-bucket-label"
539
- title="Prompt tokens written to the provider cache — billed at a premium over the uncached rate"
540
- >
541
- Cache write
542
- <span class="settings-token-bucket-rate" aria-hidden="true">▴</span>
543
- </span>
544
- <span
545
- class="settings-token-bucket-value"
546
- :innerHTML="${(x: T) => formatCompactTokenCountSafe(usageOf(x).cacheWriteTokens)}"
547
- ></span>
548
- </div>
549
- </div>
550
- </div>
551
- `;
552
-
553
- /**
554
- * Lifetime usage across every project in the history scope.
555
- *
556
- * Sits under the "Build history" heading, above the rows it totals — the live block leads the tab
557
- * instead, because the project open now is what a user came to check. Being inside the history
558
- * grouping is also what identifies this figure: it is the sum of the rows below it, not a rival
559
- * reading of the block above it.
560
- *
561
- * It must not sit inside the live block, which is where it began life as a lone "Total build
562
- * cost": the live badge and project name directly above it implied the total belonged to the open
563
- * project.
564
- */
565
- const lifetimeUsageTemplate =
566
- (): ViewTemplate<FoundationAiAssistant> => html<FoundationAiAssistant>`
567
- ${when(
568
- (x) => x.settingsCostLifetimeVisible,
569
- html<FoundationAiAssistant>`
570
- <div class="settings-lifetime-usage" part="lifetime-usage">
571
- <div class="settings-lifetime-usage-top">
572
- <span class="settings-lifetime-label">Lifetime usage</span>
573
- </div>
574
- <div class="settings-usage-row">
575
- <div
576
- class="settings-build-stat"
577
- part="lifetime-cost"
578
- ?hidden=${(x) => !x.resolvedCostHistory.showCost}
579
- >
580
- <div class="settings-build-stat-label">Total build cost</div>
581
- <div class="settings-build-stat-value">
582
- <span class="prefix">$</span>
583
- <span class="value">${(x) => formatUsdAmount(x.cumulativeCostUsd)}</span>
584
- </div>
585
- </div>
586
- <div
587
- class="settings-usage-row-tokens"
588
- ?hidden=${(x) => !x.resolvedCostHistory.showTokens}
589
- >
590
- ${tokenBreakdownTemplate<FoundationAiAssistant>((x) => x.cumulativeUsage, 'lifetime')}
591
- </div>
592
- </div>
593
- </div>
594
- `,
595
- )}
596
- `;
597
-
598
- const sessionCostSummaryTemplate =
599
- (): ViewTemplate<FoundationAiAssistant> => html<FoundationAiAssistant>`
600
- ${when(
601
- (x) => x.settingsCostSummaryVisible,
602
- html<FoundationAiAssistant>`
603
- <div class="settings-current-build" part="session-cost-summary">
604
- <div class="settings-current-build-top">
605
- <span class="settings-live-badge">
606
- <span class="settings-live-dot"></span>
607
- ${(x) => x.resolvedCostHistory.badgeLabel}
608
- </span>
609
- ${when(
610
- (x) => !!x.liveCostSessionTitle,
611
- html<FoundationAiAssistant>`
612
- <span class="settings-current-build-name">${(x) => x.liveCostSessionTitle}</span>
613
- `,
614
- )}
615
- </div>
616
- <div class="settings-usage-row">
617
- <div
618
- class="settings-build-stat"
619
- part="session-cost"
620
- ?hidden=${(x) => !x.resolvedCostHistory.showCost}
621
- >
622
- <!--
623
- Label states the scope rather than implying one. These figures are whatever
624
- the open transcript proves, so they are the project's whole history exactly
625
- when that transcript is restored on load, and only this page-load's when it
626
- isn't. Same number, two meanings — so the caller can't be left guessing.
627
- -->
628
- <div class="settings-build-stat-label">
629
- ${(x) => (x.liveUsageIsProjectLifetime ? 'Project cost' : 'Session cost')}
630
- </div>
631
- <div class="settings-build-stat-value">
632
- <span class="prefix">$</span>
633
- <span class="value">${(x) => formatUsdAmount(x.sessionCostUsd)}</span>
634
- </div>
635
- </div>
636
- <div
637
- class="settings-usage-row-tokens"
638
- ?hidden=${(x) => !x.resolvedCostHistory.showTokens}
639
- >
640
- ${tokenBreakdownTemplate<FoundationAiAssistant>((x) => x.sessionUsage, 'session')}
641
- </div>
642
- </div>
643
- ${when(
644
- (x) => x.chatConfig.ui?.showActiveModel !== false && x.uniqueProviderModels.length > 0,
645
- html<FoundationAiAssistant>`
646
- <div class="settings-model-tags" part="session-model-tags">
647
- ${repeat((x) => x.uniqueProviderModels, modelTagTemplate)}
648
- </div>
649
- `,
650
- )}
651
- </div>
652
- `,
653
- )}
654
- `;
655
-
656
- const costSessionHistoryTemplate = (
657
- iconTag: string,
658
- buttonTag: string,
659
- ): ViewTemplate<FoundationAiAssistant> => html<FoundationAiAssistant>`
660
- ${repeat(
661
- (x) => x.sortedCostSessionHistory,
662
- html<CostSessionRecord>`
663
- <div class="settings-cost-history-row" part="cost-history-row">
664
- <div class="settings-cost-history-row-content">
665
- <span class="settings-cost-history-icon" part="cost-history-icon" aria-hidden="true">
666
- <${iconTag} name="table-cells-large"></${iconTag}>
667
- </span>
668
- <div class="settings-cost-history-row-body">
669
- <div class="settings-cost-history-row-left">
670
- <div class="settings-cost-history-row-title">
671
- ${(record) => record.title}
672
- </div>
673
- <div class="settings-cost-history-row-date">
674
- ${(record) => formatCostSessionDate(record.updatedAt)}
675
- </div>
676
- </div>
677
- <!--
678
- Cost and the delete control live together so they centre against each other.
679
- The button used to sit outside the row body, held in place by a negative margin
680
- tuned for when this column had a second line (a merged token total); that line
681
- is gone, and a nudge measured against a removed sibling is exactly the kind that
682
- rots silently.
683
- -->
684
- <div class="settings-cost-history-row-right">
685
- <div class="settings-cost-history-row-metrics">
686
- <div class="settings-cost-history-row-cost">
687
- ${(record) => formatUsd(record.usage.costUsd)}
688
- </div>
689
- </div>
690
- <${buttonTag}
691
- class="settings-cost-history-delete"
692
- part="cost-history-delete"
693
- appearance="stealth"
694
- aria-label="Remove from build history"
695
- @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))}
696
- >
697
- <${iconTag} name="trash"></${iconTag}>
698
- </${buttonTag}>
699
- </div>
700
- </div>
701
- </div>
702
- <!--
703
- The same four buckets as the live block, on its own line below the row rather than
704
- beside the cost — the row's own width is spoken for by title, cost and delete.
705
- Replaces a merged total that could not explain a huge token count at a small cost
706
- without hovering for a tooltip.
707
- -->
708
- <div
709
- class="settings-cost-history-row-content settings-cost-history-row-breakdown"
710
- ?hidden=${(record, c) =>
711
- !(c.parent as FoundationAiAssistant).resolvedCostHistory.showTokens}
712
- >
713
- ${tokenBreakdownTemplate<CostSessionRecord>((record) => record.usage, 'cost-history-row')}
714
- </div>
715
- ${when(
716
- (record) => record.models.length > 0,
717
- html<CostSessionRecord>`
718
- <div
719
- class="settings-cost-history-row-content settings-model-tags settings-cost-history-model-tags"
720
- >
721
- ${repeat((record) => record.models, modelTagTemplate)}
722
- </div>
723
- `,
724
- )}
725
- </div>
726
- `,
727
- )}
728
- `;
729
-
730
- /**
731
- * Modal shell for assistant settings — Configuration and Usage tabs, section slots for
732
- * app-specific UI, and built-in chat-bot toggles.
733
- */
734
- export const SettingsModalTemplate = (
735
- designSystemPrefix: string,
736
- ): ViewTemplate<FoundationAiAssistant> => {
737
- const modalTag = `${designSystemPrefix}-modal`;
738
- const buttonTag = `${designSystemPrefix}-button`;
739
- const switchTag = `${designSystemPrefix}-switch`;
740
- const iconTag = `${designSystemPrefix}-icon`;
741
- const progressTag = `${designSystemPrefix}-progress`;
742
- const tabsTag = `${designSystemPrefix}-tabs`;
743
- const tabTag = `${designSystemPrefix}-tab`;
744
- const tabPanelTag = `${designSystemPrefix}-tab-panel`;
745
-
746
- const settingsTabPanel = html<FoundationAiAssistant>`
747
- <${tabPanelTag}
748
- slot="tabpanel"
749
- class="settings-modal-tab-panel"
750
- part="settings-tab-panel"
751
- >
752
- <div class="settings-modal-body" part="settings-modal-body">
753
- <section
754
- class="settings-modal-section"
755
- part="settings-section-model"
756
- ?hidden=${(x) => !x.settingsModelSectionVisible}
757
- >
758
- <h3 class="ai-settings-section-title">
759
- <${iconTag} name="microchip"></${iconTag}> AI Model Settings
760
- </h3>
761
- <div class="settings-modal-section-body">
762
- <slot name="settings-model" ${slotted('settingsModelSlotted')}></slot>
763
- ${settingsUsageMetersTemplate(progressTag, buttonTag, iconTag)}
764
- </div>
765
- </section>
766
- <!--
767
- Above the host section, deliberately. This is the assistant's own control, so it belongs
768
- with the assistant's other sections rather than trailing the host's — where, now that the
769
- host section carries no heading of its own, it read as one more of the host's settings.
770
- -->
771
- ${when(
772
- (x) => x.chatConfig.ui?.allowDebugDownload === true,
773
- html<FoundationAiAssistant>`
774
- <section class="settings-modal-section" part="settings-section-agent-log">
775
- <div class="settings-modal-section-body">
776
- ${settingsToggleRow(
777
- 'Download agent log',
778
- "Export a structured debug timeline of this session's agent activity.",
779
- 'download-agent-log',
780
- html<FoundationAiAssistant>`
781
- <${buttonTag}
782
- part="download-button"
783
- appearance="outline"
784
- ?disabled=${(x) => x.downloadingDebugLog}
785
- @click=${(x) => x.downloadDebugLog()}
786
- >
787
- ${when(
788
- (x) => x.downloadingDebugLog,
789
- html<FoundationAiAssistant>`
790
- <span
791
- class="download-spinner"
792
- part="download-spinner"
793
- aria-hidden="true"
794
- ></span>
795
- `,
796
- )}
797
- ${(x) => (x.downloadingDebugLog ? 'Downloading…' : 'Download')}
798
- </${buttonTag}>
799
- `,
800
- 'row-centered',
801
- )}
802
- </div>
803
- </section>
804
- `,
805
- )}
806
- <!--
807
- No heading here, by design. This section belongs to the host: only it knows what its
808
- settings are called, so the assistant supplies neither text nor icon and the host titles
809
- the section from the slot. See assistantSettingsSectionTitleStyles for the heading style
810
- it can apply to match the sections above and below.
811
- -->
812
- <section
813
- class="settings-modal-section"
814
- part="settings-section-app"
815
- ?hidden=${(x) => !x.settingsAppSectionVisible}
816
- >
817
- <div class="settings-modal-section-body">
818
- ${appSettingsTemplate(switchTag, iconTag)}
819
- </div>
820
- </section>
821
- <section class="settings-modal-section" part="settings-section-chat">
822
- <h3 class="ai-settings-section-title">
823
- <${iconTag} name="comment"></${iconTag}> AI Chat Bot Settings
824
- </h3>
825
- <p class="settings-modal-section-note">These options only control what you see in the chat window and do not control model behaviour.</p>
826
- <div class="settings-modal-section-body">
827
- ${chatBotSettingsTemplate(switchTag, buttonTag)}
828
- </div>
829
- </section>
830
- </div>
831
- </${tabPanelTag}>
832
- `;
833
-
834
- const costTabPanel = html<FoundationAiAssistant>`
835
- <${tabPanelTag}
836
- slot="tabpanel"
837
- class="settings-modal-tab-panel"
838
- part="cost-tab-panel"
839
- >
840
- <div class="settings-modal-body" part="settings-modal-body">
841
- ${sessionCostSummaryTemplate()}
842
- <div
843
- class="settings-cost-current"
844
- part="settings-cost-current"
845
- ?hidden=${(x) => !x.settingsCostCurrentSectionVisible}
846
- >
847
- <slot name="settings-cost-current" ${slotted('settingsCostCurrentSlotted')}></slot>
848
- </div>
849
- <!--
850
- The heading covers everything below it, lifetime total included — that total IS the sum of
851
- the history rows, so it reads as their summary rather than a rival figure. Hence the
852
- heading's own visibility gate, which is broader than the rows'.
853
- -->
854
- <p class="settings-cost-history-title" ?hidden=${(x) => !x.settingsCostHistoryHeadingVisible}>
855
- Build history
856
- </p>
857
- ${lifetimeUsageTemplate()}
858
- <div
859
- class="settings-cost-history"
860
- part="settings-cost-history"
861
- ?hidden=${(x) => !x.settingsCostHistorySectionVisible}
862
- >
863
- ${costSessionHistoryTemplate(iconTag, buttonTag)}
864
- <slot name="settings-cost-history" ${slotted('settingsCostHistorySlotted')}></slot>
865
- </div>
866
- </div>
867
- </${tabPanelTag}>
868
- `;
869
-
870
- return html<FoundationAiAssistant>`
871
- <${modalTag}
872
- class="settings-modal"
873
- part="settings-modal"
874
- exportparts="dialog: settings-dialog, content: settings-content, top: settings-top, bottom: settings-footer"
875
- ${ref('settingsModal')}
876
- initial-width="620"
877
- @close=${(x) => x.onSettingsModalClosed()}
878
- >
879
- <div slot="top" class="settings-modal-header" part="settings-modal-header">
880
- <h2 class="settings-modal-title">
881
- <${iconTag} name="cog"></${iconTag}> Settings
882
- </h2>
883
- </div>
884
- <div class="settings-modal-content" part="settings-modal-content">
885
- <${tabsTag}
886
- class="settings-modal-tabs"
887
- part="settings-modal-tabs"
888
- appearance="secondary"
889
- ${ref('settingsTabsEl')}
890
- @change=${(x, c) => x.onSettingsModalTabsChange(c.event)}
891
- ><${tabTag}
892
- slot="tab"
893
- id="settings"
894
- appearance="secondary"
895
- class="settings-modal-tab"
896
- part="settings-modal-tab-settings"
897
- ><${iconTag} name="sliders"></${iconTag}> Configuration</${tabTag}>${when(
898
- (x) => x.settingsCostTabVisible,
899
- html<FoundationAiAssistant>`<${tabTag}
900
- slot="tab"
901
- id="cost"
902
- appearance="secondary"
903
- class="settings-modal-tab"
904
- part="settings-modal-tab-cost"
905
- ><${iconTag} name="chart-simple"></${iconTag}> Usage</${tabTag}>`,
906
- )}${settingsTabPanel}${when((x) => x.settingsCostTabVisible, costTabPanel)}</${tabsTag}>
907
- </div>
908
- <div slot="bottom" class="settings-modal-footer" part="settings-modal-footer">
909
- <div class="settings-modal-footer-actions">
910
- ${when(
911
- (x) => x.settingsModalTab === 'cost',
912
- html<FoundationAiAssistant>`
913
- <span class="settings-disclaimer">
914
- ${when(
915
- (x) => !!x.resolvedCostHistory.summaryHint,
916
- html<FoundationAiAssistant>`
917
- ${(x) => x.resolvedCostHistory.summaryHint}
918
- `,
919
- )}Costs
920
- are estimated from token usage at list
921
- price.${when(
922
- // Only where the markers actually render. Every place they appear is gated on
923
- // showTokens, so with cost figures but no token buckets this sentence would
924
- // define two glyphs that are nowhere on screen.
925
- (x) => x.resolvedCostHistory.showTokens,
926
- html<FoundationAiAssistant>`
927
- Cache reads bill below the input rate (▾) and cache writes above it (▴).
928
- `,
929
- )}
930
- </span>
931
- `,
932
- )}
933
- <${buttonTag}
934
- appearance="outline"
935
- part="settings-done-button"
936
- class="settings-done-button"
937
- @click=${(x) => x.closeSettingsModal()}
938
- >Done</${buttonTag}>
939
- </div>
940
- </div>
941
- </${modalTag}>
942
- `;
943
- };