@genesislcap/ai-assistant 14.403.0-ai-assistant.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 (244) hide show
  1. package/api-extractor.json +4 -0
  2. package/dist/ai-assistant.api.json +4028 -0
  3. package/dist/ai-assistant.d.ts +396 -0
  4. package/dist/dts/channel/ai-activity-channel.d.ts +32 -0
  5. package/dist/dts/channel/ai-activity-channel.d.ts.map +1 -0
  6. package/dist/dts/components/activity-halo/activity-halo.d.ts +31 -0
  7. package/dist/dts/components/activity-halo/activity-halo.d.ts.map +1 -0
  8. package/dist/dts/components/chat-bubble/chat-bubble.d.ts +52 -0
  9. package/dist/dts/components/chat-bubble/chat-bubble.d.ts.map +1 -0
  10. package/dist/dts/components/chat-bubble/chat-bubble.styles.d.ts +2 -0
  11. package/dist/dts/components/chat-bubble/chat-bubble.styles.d.ts.map +1 -0
  12. package/dist/dts/components/chat-bubble/chat-bubble.template.d.ts +4 -0
  13. package/dist/dts/components/chat-bubble/chat-bubble.template.d.ts.map +1 -0
  14. package/dist/dts/components/chat-bubble/index.d.ts +2 -0
  15. package/dist/dts/components/chat-bubble/index.d.ts.map +1 -0
  16. package/dist/dts/components/chat-driver/chat-driver.d.ts +49 -0
  17. package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -0
  18. package/dist/dts/components/chat-driver/index.d.ts +2 -0
  19. package/dist/dts/components/chat-driver/index.d.ts.map +1 -0
  20. package/dist/dts/components/chat-interaction-wrapper/chat-interaction-wrapper.d.ts +19 -0
  21. package/dist/dts/components/chat-interaction-wrapper/chat-interaction-wrapper.d.ts.map +1 -0
  22. package/dist/dts/components/chat-interaction-wrapper/chat-interaction-wrapper.styles.d.ts +2 -0
  23. package/dist/dts/components/chat-interaction-wrapper/chat-interaction-wrapper.styles.d.ts.map +1 -0
  24. package/dist/dts/components/chat-interaction-wrapper/chat-interaction-wrapper.template.d.ts +3 -0
  25. package/dist/dts/components/chat-interaction-wrapper/chat-interaction-wrapper.template.d.ts.map +1 -0
  26. package/dist/dts/components/chat-interaction-wrapper/index.d.ts +4 -0
  27. package/dist/dts/components/chat-interaction-wrapper/index.d.ts.map +1 -0
  28. package/dist/dts/components/chat-markdown/chat-markdown.d.ts +6 -0
  29. package/dist/dts/components/chat-markdown/chat-markdown.d.ts.map +1 -0
  30. package/dist/dts/components/chat-markdown/index.d.ts +2 -0
  31. package/dist/dts/components/chat-markdown/index.d.ts.map +1 -0
  32. package/dist/dts/components/halo-overlay.d.ts +25 -0
  33. package/dist/dts/components/halo-overlay.d.ts.map +1 -0
  34. package/dist/dts/config/config.d.ts +51 -0
  35. package/dist/dts/config/config.d.ts.map +1 -0
  36. package/dist/dts/config/configure.d.ts +1 -0
  37. package/dist/dts/config/configure.d.ts.map +1 -0
  38. package/dist/dts/config/index.d.ts +2 -0
  39. package/dist/dts/config/index.d.ts.map +1 -0
  40. package/dist/dts/index.d.ts +10 -0
  41. package/dist/dts/index.d.ts.map +1 -0
  42. package/dist/dts/main/index.d.ts +5 -0
  43. package/dist/dts/main/index.d.ts.map +1 -0
  44. package/dist/dts/main/main.d.ts +103 -0
  45. package/dist/dts/main/main.d.ts.map +1 -0
  46. package/dist/dts/main/main.styles.d.ts +2 -0
  47. package/dist/dts/main/main.styles.d.ts.map +1 -0
  48. package/dist/dts/main/main.template.d.ts +4 -0
  49. package/dist/dts/main/main.template.d.ts.map +1 -0
  50. package/dist/dts/main/main.types.d.ts +54 -0
  51. package/dist/dts/main/main.types.d.ts.map +1 -0
  52. package/dist/dts/styles/index.d.ts +2 -0
  53. package/dist/dts/styles/index.d.ts.map +1 -0
  54. package/dist/dts/styles/styles.d.ts +5 -0
  55. package/dist/dts/styles/styles.d.ts.map +1 -0
  56. package/dist/dts/tags/index.d.ts +1 -0
  57. package/dist/dts/tags/index.d.ts.map +1 -0
  58. package/dist/dts/types/ai-chat-widget.d.ts +12 -0
  59. package/dist/dts/types/ai-chat-widget.d.ts.map +1 -0
  60. package/dist/dts/utils/index.d.ts +2 -0
  61. package/dist/dts/utils/index.d.ts.map +1 -0
  62. package/dist/dts/utils/logger.d.ts +2 -0
  63. package/dist/dts/utils/logger.d.ts.map +1 -0
  64. package/dist/esm/channel/ai-activity-channel.js +2 -0
  65. package/dist/esm/components/activity-halo/activity-halo.js +119 -0
  66. package/dist/esm/components/chat-bubble/chat-bubble.js +381 -0
  67. package/dist/esm/components/chat-bubble/chat-bubble.styles.js +193 -0
  68. package/dist/esm/components/chat-bubble/chat-bubble.template.js +50 -0
  69. package/dist/esm/components/chat-bubble/index.js +1 -0
  70. package/dist/esm/components/chat-driver/chat-driver.js +209 -0
  71. package/dist/esm/components/chat-driver/index.js +1 -0
  72. package/dist/esm/components/chat-interaction-wrapper/chat-interaction-wrapper.js +90 -0
  73. package/dist/esm/components/chat-interaction-wrapper/chat-interaction-wrapper.styles.js +6 -0
  74. package/dist/esm/components/chat-interaction-wrapper/chat-interaction-wrapper.template.js +4 -0
  75. package/dist/esm/components/chat-interaction-wrapper/index.js +3 -0
  76. package/dist/esm/components/chat-markdown/chat-markdown.js +95 -0
  77. package/dist/esm/components/chat-markdown/index.js +1 -0
  78. package/dist/esm/components/halo-overlay.js +118 -0
  79. package/dist/esm/config/config.js +16 -0
  80. package/dist/esm/config/configure.js +0 -0
  81. package/dist/esm/config/index.js +1 -0
  82. package/dist/esm/index.js +8 -0
  83. package/dist/esm/main/index.js +4 -0
  84. package/dist/esm/main/main.js +491 -0
  85. package/dist/esm/main/main.styles.js +356 -0
  86. package/dist/esm/main/main.template.js +272 -0
  87. package/dist/esm/main/main.types.js +22 -0
  88. package/dist/esm/styles/index.js +1 -0
  89. package/dist/esm/styles/styles.js +237 -0
  90. package/dist/esm/tags/index.js +0 -0
  91. package/dist/esm/types/ai-chat-widget.js +1 -0
  92. package/dist/esm/utils/index.js +1 -0
  93. package/dist/esm/utils/logger.js +2 -0
  94. package/dist/tsconfig.tsbuildinfo +1 -0
  95. package/dist/tsdoc-metadata.json +11 -0
  96. package/docs/.gitattributes +2 -0
  97. package/docs/api/ai-assistant.ai_activity_channel_name.md +11 -0
  98. package/docs/api/ai-assistant.aiactivityevents.md +24 -0
  99. package/docs/api/ai-assistant.aiactivityhalo.channelname.md +16 -0
  100. package/docs/api/ai-assistant.aiactivityhalo.connectedcallback.md +18 -0
  101. package/docs/api/ai-assistant.aiactivityhalo.disconnectedcallback.md +18 -0
  102. package/docs/api/ai-assistant.aiactivityhalo.haloactive.md +14 -0
  103. package/docs/api/ai-assistant.aiactivityhalo.md +176 -0
  104. package/docs/api/ai-assistant.aiactivityhalo.minshowtime.md +16 -0
  105. package/docs/api/ai-assistant.aiactivityhalo.tools.md +16 -0
  106. package/docs/api/ai-assistant.aiassistantanimation.md +18 -0
  107. package/docs/api/ai-assistant.aiassistantanimationdef.label.md +16 -0
  108. package/docs/api/ai-assistant.aiassistantanimationdef.md +80 -0
  109. package/docs/api/ai-assistant.aiassistantanimationdef.tooltip.md +16 -0
  110. package/docs/api/ai-assistant.aiassistantconfig.chatconfig.md +16 -0
  111. package/docs/api/ai-assistant.aiassistantconfig.debugstatefactory.md +16 -0
  112. package/docs/api/ai-assistant.aiassistantconfig.headertitle.md +16 -0
  113. package/docs/api/ai-assistant.aiassistantconfig.md +157 -0
  114. package/docs/api/ai-assistant.aiassistantconfig.systemprompt.md +16 -0
  115. package/docs/api/ai-assistant.aiassistantconfig.tooldefinitions.md +16 -0
  116. package/docs/api/ai-assistant.aiassistantconfig.toolhandlers.md +16 -0
  117. package/docs/api/ai-assistant.aiassistantserializedstate.enabledanimations.md +14 -0
  118. package/docs/api/ai-assistant.aiassistantserializedstate.md +118 -0
  119. package/docs/api/ai-assistant.aiassistantserializedstate.messages.md +14 -0
  120. package/docs/api/ai-assistant.aiassistantserializedstate.showthinkingsteps.md +14 -0
  121. package/docs/api/ai-assistant.aiassistantserializedstate.showtoolcalls.md +14 -0
  122. package/docs/api/ai-assistant.aiassistantstate.md +16 -0
  123. package/docs/api/ai-assistant.aichatbubble.bubbleleft.md +11 -0
  124. package/docs/api/ai-assistant.aichatbubble.bubbletop.md +11 -0
  125. package/docs/api/ai-assistant.aichatbubble.channelname.md +11 -0
  126. package/docs/api/ai-assistant.aichatbubble.connectedcallback.md +15 -0
  127. package/docs/api/ai-assistant.aichatbubble.designsystemprefix.md +11 -0
  128. package/docs/api/ai-assistant.aichatbubble.dialogcontent.md +11 -0
  129. package/docs/api/ai-assistant.aichatbubble.dialogcontentchanged.md +64 -0
  130. package/docs/api/ai-assistant.aichatbubble.dialogheight.md +11 -0
  131. package/docs/api/ai-assistant.aichatbubble.dialogleft.md +11 -0
  132. package/docs/api/ai-assistant.aichatbubble.dialogopen.md +11 -0
  133. package/docs/api/ai-assistant.aichatbubble.dialogopenchanged.md +64 -0
  134. package/docs/api/ai-assistant.aichatbubble.dialogtop.md +11 -0
  135. package/docs/api/ai-assistant.aichatbubble.dialogwidth.md +11 -0
  136. package/docs/api/ai-assistant.aichatbubble.disconnectedcallback.md +15 -0
  137. package/docs/api/ai-assistant.aichatbubble.hovered.md +11 -0
  138. package/docs/api/ai-assistant.aichatbubble.imagesrc.md +11 -0
  139. package/docs/api/ai-assistant.aichatbubble.md +396 -0
  140. package/docs/api/ai-assistant.aichatbubble.onbubblemousedown.md +50 -0
  141. package/docs/api/ai-assistant.aichatbubble.onclosedialog.md +15 -0
  142. package/docs/api/ai-assistant.aichatbubble.ondialogheadermousedown.md +53 -0
  143. package/docs/api/ai-assistant.aichatbubble.onpopout.md +15 -0
  144. package/docs/api/ai-assistant.aichatbubble.title.md +11 -0
  145. package/docs/api/ai-assistant.aichatbubble.tooltipstyle.md +11 -0
  146. package/docs/api/ai-assistant.aichatwidget.data.md +14 -0
  147. package/docs/api/ai-assistant.aichatwidget.md +80 -0
  148. package/docs/api/ai-assistant.aichatwidget.resolved.md +16 -0
  149. package/docs/api/ai-assistant.animation_defs.md +25 -0
  150. package/docs/api/ai-assistant.chatdriver._constructor_.md +99 -0
  151. package/docs/api/ai-assistant.chatdriver.gethistory.md +18 -0
  152. package/docs/api/ai-assistant.chatdriver.isbusy.md +18 -0
  153. package/docs/api/ai-assistant.chatdriver.loadhistory.md +55 -0
  154. package/docs/api/ai-assistant.chatdriver.md +158 -0
  155. package/docs/api/ai-assistant.chatdriver.requestinteraction.md +73 -0
  156. package/docs/api/ai-assistant.chatdriver.resolveinteraction.md +69 -0
  157. package/docs/api/ai-assistant.chatdriver.sendmessage.md +69 -0
  158. package/docs/api/ai-assistant.chathistoryupdatedevent.md +16 -0
  159. package/docs/api/ai-assistant.defaultaiassistantconfig.md +16 -0
  160. package/docs/api/ai-assistant.foundationaiassistant.aiprovider.md +14 -0
  161. package/docs/api/ai-assistant.foundationaiassistant.applystate.md +55 -0
  162. package/docs/api/ai-assistant.foundationaiassistant.attachmenterrors.md +14 -0
  163. package/docs/api/ai-assistant.foundationaiassistant.attachments.md +14 -0
  164. package/docs/api/ai-assistant.foundationaiassistant.channelname.md +14 -0
  165. package/docs/api/ai-assistant.foundationaiassistant.chatconfig.md +14 -0
  166. package/docs/api/ai-assistant.foundationaiassistant.connectedcallback.md +18 -0
  167. package/docs/api/ai-assistant.foundationaiassistant.debugstatefactory.md +14 -0
  168. package/docs/api/ai-assistant.foundationaiassistant.designsystemprefix.md +14 -0
  169. package/docs/api/ai-assistant.foundationaiassistant.disconnectedcallback.md +18 -0
  170. package/docs/api/ai-assistant.foundationaiassistant.downloadhistory.md +18 -0
  171. package/docs/api/ai-assistant.foundationaiassistant.enabledanimations.md +16 -0
  172. package/docs/api/ai-assistant.foundationaiassistant.handlefileselect.md +53 -0
  173. package/docs/api/ai-assistant.foundationaiassistant.handleinteractioncompleted.md +53 -0
  174. package/docs/api/ai-assistant.foundationaiassistant.handlepopout.md +20 -0
  175. package/docs/api/ai-assistant.foundationaiassistant.handlesendclick.md +18 -0
  176. package/docs/api/ai-assistant.foundationaiassistant.hasactivependinginteraction.md +16 -0
  177. package/docs/api/ai-assistant.foundationaiassistant.headertitle.md +14 -0
  178. package/docs/api/ai-assistant.foundationaiassistant.imagesrc.md +14 -0
  179. package/docs/api/ai-assistant.foundationaiassistant.inputvalue.md +14 -0
  180. package/docs/api/ai-assistant.foundationaiassistant.md +826 -0
  181. package/docs/api/ai-assistant.foundationaiassistant.messages.md +14 -0
  182. package/docs/api/ai-assistant.foundationaiassistant.messageschanged.md +18 -0
  183. package/docs/api/ai-assistant.foundationaiassistant.onchatheadermousedown.md +53 -0
  184. package/docs/api/ai-assistant.foundationaiassistant.placeholder.md +14 -0
  185. package/docs/api/ai-assistant.foundationaiassistant.popoutmode.md +16 -0
  186. package/docs/api/ai-assistant.foundationaiassistant.removeattachment.md +53 -0
  187. package/docs/api/ai-assistant.foundationaiassistant.removeattachmenterror.md +53 -0
  188. package/docs/api/ai-assistant.foundationaiassistant.setenabledanimations.md +53 -0
  189. package/docs/api/ai-assistant.foundationaiassistant.settingsopen.md +16 -0
  190. package/docs/api/ai-assistant.foundationaiassistant.showhalo.md +16 -0
  191. package/docs/api/ai-assistant.foundationaiassistant.showhalochanged.md +18 -0
  192. package/docs/api/ai-assistant.foundationaiassistant.showloadingindicator.md +16 -0
  193. package/docs/api/ai-assistant.foundationaiassistant.showloadingindicatorchanged.md +18 -0
  194. package/docs/api/ai-assistant.foundationaiassistant.showthinkingsteps.md +16 -0
  195. package/docs/api/ai-assistant.foundationaiassistant.showtoolcalls.md +16 -0
  196. package/docs/api/ai-assistant.foundationaiassistant.state.md +14 -0
  197. package/docs/api/ai-assistant.foundationaiassistant.statechanged.md +18 -0
  198. package/docs/api/ai-assistant.foundationaiassistant.systemprompt.md +14 -0
  199. package/docs/api/ai-assistant.foundationaiassistant.togglesettings.md +18 -0
  200. package/docs/api/ai-assistant.foundationaiassistant.toggleshowthinkingsteps.md +18 -0
  201. package/docs/api/ai-assistant.foundationaiassistant.toggleshowtoolcalls.md +18 -0
  202. package/docs/api/ai-assistant.foundationaiassistant.tooldefinitions.md +14 -0
  203. package/docs/api/ai-assistant.foundationaiassistant.toolhandlers.md +14 -0
  204. package/docs/api/ai-assistant.foundationaiassistant.triggerfileinput.md +18 -0
  205. package/docs/api/ai-assistant.foundationaiassistant.visiblemessages.md +16 -0
  206. package/docs/api/ai-assistant.foundationaiassistanttemplate.md +50 -0
  207. package/docs/api/ai-assistant.md +262 -0
  208. package/docs/api/ai-assistant.popoutmode.md +16 -0
  209. package/docs/api/index.md +30 -0
  210. package/docs/api-report.md.api.md +280 -0
  211. package/license.txt +46 -0
  212. package/package.json +92 -0
  213. package/src/channel/ai-activity-channel.ts +30 -0
  214. package/src/components/activity-halo/activity-halo.ts +113 -0
  215. package/src/components/chat-bubble/chat-bubble.styles.ts +194 -0
  216. package/src/components/chat-bubble/chat-bubble.template.ts +63 -0
  217. package/src/components/chat-bubble/chat-bubble.ts +389 -0
  218. package/src/components/chat-bubble/index.ts +1 -0
  219. package/src/components/chat-driver/chat-driver.ts +254 -0
  220. package/src/components/chat-driver/index.ts +1 -0
  221. package/src/components/chat-interaction-wrapper/chat-interaction-wrapper.styles.ts +7 -0
  222. package/src/components/chat-interaction-wrapper/chat-interaction-wrapper.template.ts +6 -0
  223. package/src/components/chat-interaction-wrapper/chat-interaction-wrapper.ts +89 -0
  224. package/src/components/chat-interaction-wrapper/index.ts +3 -0
  225. package/src/components/chat-markdown/chat-markdown.ts +87 -0
  226. package/src/components/chat-markdown/index.ts +1 -0
  227. package/src/components/halo-overlay.ts +115 -0
  228. package/src/config/config.ts +59 -0
  229. package/src/config/configure.ts +0 -0
  230. package/src/config/index.ts +1 -0
  231. package/src/index.ts +9 -0
  232. package/src/main/index.ts +4 -0
  233. package/src/main/main.styles.ts +357 -0
  234. package/src/main/main.template.ts +364 -0
  235. package/src/main/main.ts +474 -0
  236. package/src/main/main.types.ts +58 -0
  237. package/src/styles/index.ts +1 -0
  238. package/src/styles/styles.ts +238 -0
  239. package/src/tags/index.ts +0 -0
  240. package/src/types/ai-chat-widget.ts +11 -0
  241. package/src/utils/index.ts +1 -0
  242. package/src/utils/logger.ts +3 -0
  243. package/temp/api-report.md.api.md +280 -0
  244. package/tsconfig.json +11 -0
@@ -0,0 +1,474 @@
1
+ import { AIProvider } from '@genesislcap/foundation-ai';
2
+ import type {
3
+ ChatAttachment,
4
+ ChatConfig,
5
+ ChatMessage,
6
+ ChatToolDefinition,
7
+ ChatToolHandlers,
8
+ } from '@genesislcap/foundation-ai';
9
+ import { createTypedBroadcastChannel } from '@genesislcap/foundation-broadcast-channel';
10
+ import type { TypedBroadcastChannel } from '@genesislcap/foundation-broadcast-channel';
11
+ import {
12
+ customElement,
13
+ html,
14
+ GenesisElement,
15
+ observable,
16
+ volatile,
17
+ attr,
18
+ } from '@genesislcap/web-core';
19
+ import { AI_ACTIVITY_CHANNEL_NAME } from '../channel/ai-activity-channel';
20
+ import type { AiActivityEvents } from '../channel/ai-activity-channel';
21
+ import type { AiAssistantSerializedState } from '../channel/ai-activity-channel';
22
+ import { ChatDriver } from '../components/chat-driver/chat-driver';
23
+ import { AiChatInteractionWrapper } from '../components/chat-interaction-wrapper/chat-interaction-wrapper';
24
+ import { AiChatMarkdown } from '../components/chat-markdown/chat-markdown';
25
+ import { AiHaloOverlay } from '../components/halo-overlay';
26
+ import { logger } from '../utils/logger';
27
+ import { styles } from './main.styles';
28
+ import { FoundationAiAssistantTemplate } from './main.template';
29
+ import { ALL_ANIMATIONS } from './main.types';
30
+ import type { AiAssistantAnimation, AiAssistantState, PopoutMode } from './main.types';
31
+
32
+ AiChatMarkdown;
33
+ AiChatInteractionWrapper;
34
+ AiHaloOverlay;
35
+
36
+ /**
37
+ * Foundation AI Assistant component.
38
+ *
39
+ * @remarks
40
+ * Lazy-loaded via `configure()`. Inject an `AIProvider` and `AiAssistantConfig` through the DI
41
+ * container. The component creates a `ChatDriver` to manage the conversation loop.
42
+ *
43
+ * @beta
44
+ */
45
+ @customElement({
46
+ name: 'foundation-ai-assistant',
47
+ template: html`
48
+ ${(x: FoundationAiAssistant) => FoundationAiAssistantTemplate(x.designSystemPrefix)}
49
+ `,
50
+ styles,
51
+ })
52
+ export class FoundationAiAssistant extends GenesisElement {
53
+ @AIProvider aiProvider!: AIProvider;
54
+
55
+ @observable designSystemPrefix: string = 'rapid';
56
+ @attr({ attribute: 'header-title' }) headerTitle?: string;
57
+ @attr({ attribute: 'channel-name' }) channelName: string = AI_ACTIVITY_CHANNEL_NAME;
58
+ @attr({ attribute: 'image-src' }) imageSrc?: string;
59
+ @attr() placeholder: string = 'Message assistant...';
60
+ /**
61
+ * Controls the pop-out button shown next to the settings cog.
62
+ * "expand" — in bubble, expands to layout panel (fires chat-popout).
63
+ * "collapse" — in layout panel, collapses back to bubble (fires chat-popin).
64
+ * Absent — no button (default standalone behaviour, no breaking change).
65
+ */
66
+ @attr({ attribute: 'popout-mode' }) popoutMode?: PopoutMode;
67
+ @observable systemPrompt?: string;
68
+ @observable toolDefinitions?: ChatToolDefinition[];
69
+ @observable toolHandlers?: ChatToolHandlers;
70
+ @observable chatConfig: ChatConfig = {};
71
+ @observable debugStateFactory?: () => unknown;
72
+
73
+ @observable messages: ChatMessage[] = [];
74
+ @observable state: AiAssistantState = 'idle';
75
+ @observable inputValue = '';
76
+ @observable attachments: ChatAttachment[] = [];
77
+ @observable attachmentErrors: string[] = [];
78
+
79
+ /** Current user-facing toggle state for tool call visibility. */
80
+ @observable showToolCalls = false;
81
+ /** Current user-facing toggle state for thinking step visibility. */
82
+ @observable showThinkingSteps = false;
83
+ /** Currently enabled animations. */
84
+ @observable enabledAnimations: AiAssistantAnimation[] = [];
85
+ /** Whether the loading spinner is currently visible. Controlled by the loading delay timer. */
86
+ @observable showLoadingIndicator = false;
87
+ /** Whether the settings panel is open. */
88
+ @observable settingsOpen = false;
89
+
90
+ private driver?: ChatDriver;
91
+ private loadingTimer: ReturnType<typeof setTimeout> | undefined;
92
+ private activityChannel?: TypedBroadcastChannel<AiActivityEvents>;
93
+ private haloStartPublished = false;
94
+
95
+ /**
96
+ * Whether the halo animation should be shown.
97
+ * True when the AI is actively computing (loading state, no pending interaction).
98
+ */
99
+ @observable showHalo: boolean = false;
100
+
101
+ private syncShowHalo() {
102
+ if (this.state !== 'loading') {
103
+ this.showHalo = false;
104
+ return;
105
+ }
106
+ const last = this.messages[this.messages.length - 1];
107
+ this.showHalo = !last?.interaction;
108
+ }
109
+
110
+ /** True when there is a pending (unresolved) interaction — disables the popout button. */
111
+ @volatile
112
+ get hasActivePendingInteraction(): boolean {
113
+ if (this.state !== 'loading') return false;
114
+ const last = this.messages[this.messages.length - 1];
115
+ return !!last?.interaction && !last.interaction.resolved;
116
+ }
117
+
118
+ showHaloChanged() {
119
+ if (!this.enabledAnimations?.includes('halo')) return;
120
+ if (!this.showHalo) {
121
+ this.activityChannel?.postMessage('halo-stop');
122
+ this.haloStartPublished = false;
123
+ }
124
+ // halo-start is published exclusively from messagesChanged when toolCalls arrive,
125
+ // so we never re-activate grids on non-toolCall transitions (e.g. tool results
126
+ // arriving after an interaction resolves).
127
+ }
128
+
129
+ private getActiveToolNames(): string[] {
130
+ for (let i = this.messages.length - 1; i >= 0; i -= 1) {
131
+ const msg = this.messages[i];
132
+ if (msg.toolCalls?.length) {
133
+ return msg.toolCalls.map((tc) => tc.name);
134
+ }
135
+ }
136
+ return [];
137
+ }
138
+
139
+ /**
140
+ * Messages filtered by the current toggle state.
141
+ * Tool-related messages (those with toolCalls or toolResult) are hidden when
142
+ * `showToolCalls` is false.
143
+ */
144
+ @volatile
145
+ get visibleMessages(): ChatMessage[] {
146
+ return this.messages.filter((m) => {
147
+ // Never show tool messages to the user
148
+ if (m.role === 'tool') {
149
+ return false;
150
+ }
151
+ // Filter thinking messages based on toggle
152
+ if (m.thinking && !this.showThinkingSteps) {
153
+ return false;
154
+ }
155
+ // Filter out empty assistant messages that might have slipped through
156
+ if (m.role === 'assistant' && !m.content?.trim() && !m.toolCalls?.length && !m.interaction) {
157
+ return false;
158
+ }
159
+ const isToolRelated = !!(m.toolCalls?.length || m.toolResult);
160
+ return !isToolRelated || this.showToolCalls;
161
+ });
162
+ }
163
+
164
+ connectedCallback() {
165
+ super.connectedCallback();
166
+ this.activityChannel = createTypedBroadcastChannel<AiActivityEvents>(this.channelName);
167
+ const { showToolCalls, showThinkingSteps, animations } = this.chatConfig;
168
+ this.showToolCalls = showToolCalls === true;
169
+ this.showThinkingSteps = showThinkingSteps === true;
170
+ this.enabledAnimations =
171
+ (animations?.enabled as AiAssistantAnimation[]) ?? (animations ? [...ALL_ANIMATIONS] : []);
172
+
173
+ this.driver = new ChatDriver(
174
+ this.aiProvider,
175
+ this.toolHandlers ?? {},
176
+ this.toolDefinitions ?? [],
177
+ this.systemPrompt,
178
+ );
179
+ this.driver.addEventListener('history-updated', (e: Event) => {
180
+ this.messages = [...(e as CustomEvent<ChatMessage[]>).detail];
181
+ });
182
+
183
+ // When embedded in a bubble (expand mode), listen for chat-popin to restore state
184
+ // when the layout panel collapses back.
185
+ if (this.popoutMode === 'expand') {
186
+ this.activityChannel.addEventListener('message', this.onChannelMessage);
187
+ }
188
+
189
+ logger.debug('FoundationAiAssistant connected');
190
+ }
191
+
192
+ disconnectedCallback() {
193
+ super.disconnectedCallback();
194
+ this.stopLoadingTimer();
195
+ this.state = 'idle';
196
+ this.activityChannel?.removeEventListener('message', this.onChannelMessage);
197
+ this.activityChannel?.close();
198
+ this.activityChannel = undefined;
199
+ this.driver = undefined;
200
+ }
201
+
202
+ private onChannelMessage = (event: MessageEvent) => {
203
+ if (event.data?.type === 'chat-popin' && event.data.detail?.state) {
204
+ this.applyState(event.data.detail.state as AiAssistantSerializedState);
205
+ }
206
+ };
207
+
208
+ stateChanged() {
209
+ this.syncShowHalo();
210
+ }
211
+
212
+ messagesChanged() {
213
+ // Reset the loading timer when an assistant message arrives mid-loop so each
214
+ // individual step gets a fresh window before the spinner appears.
215
+ // If the last message is a blocking interaction, stop the timer — the AI is
216
+ // waiting for the user, not computing.
217
+ if (this.state === 'loading') {
218
+ const last = this.messages[this.messages.length - 1];
219
+ if (last?.interaction) {
220
+ this.stopLoadingTimer();
221
+ } else if (last?.role === 'assistant') {
222
+ this.startLoadingTimer();
223
+ }
224
+ }
225
+ this.syncShowHalo();
226
+ // Publish halo-start whenever a new toolCalls message arrives.
227
+ // If we've already published one this turn, send halo-stop first so grids
228
+ // not relevant to the new tools begin deactivating (fix #2).
229
+ // Never publish with empty toolNames (fix #3).
230
+ if (this.showHalo && this.enabledAnimations?.includes('halo')) {
231
+ const last = this.messages[this.messages.length - 1];
232
+ if (last?.toolCalls?.length) {
233
+ const toolNames = this.getActiveToolNames();
234
+ if (toolNames.length) {
235
+ if (this.haloStartPublished) {
236
+ this.activityChannel?.postMessage('halo-stop');
237
+ }
238
+ this.activityChannel?.postMessage('halo-start', { toolNames });
239
+ this.haloStartPublished = true;
240
+ }
241
+ }
242
+ }
243
+ this.scrollToBottom();
244
+ }
245
+
246
+ showLoadingIndicatorChanged() {
247
+ // Scroll to bottom when the spinner appears so it is visible.
248
+ if (this.showLoadingIndicator) {
249
+ this.scrollToBottom();
250
+ }
251
+ }
252
+
253
+ // Double rAF: first frame lets the framework flush the DOM update,
254
+ // second frame reads the correct scrollHeight after layout.
255
+ private scrollToBottom() {
256
+ requestAnimationFrame(() => {
257
+ requestAnimationFrame(() => {
258
+ const el = this.shadowRoot?.querySelector('.messages');
259
+ if (el) el.scrollTop = el.scrollHeight;
260
+ });
261
+ });
262
+ }
263
+
264
+ private static readonly DEFAULT_LOADING_DELAY_S = 5;
265
+ private static readonly MS_PER_SECOND = 1000;
266
+
267
+ private startLoadingTimer() {
268
+ this.clearLoadingTimer();
269
+ const delay = this.chatConfig.loadingDelay ?? FoundationAiAssistant.DEFAULT_LOADING_DELAY_S;
270
+ if (delay === 0) {
271
+ this.showLoadingIndicator = true;
272
+ } else {
273
+ this.loadingTimer = setTimeout(() => {
274
+ this.showLoadingIndicator = true;
275
+ }, delay * FoundationAiAssistant.MS_PER_SECOND);
276
+ }
277
+ }
278
+
279
+ private clearLoadingTimer() {
280
+ if (this.loadingTimer !== undefined) {
281
+ clearTimeout(this.loadingTimer);
282
+ this.loadingTimer = undefined;
283
+ }
284
+ }
285
+
286
+ private stopLoadingTimer() {
287
+ this.clearLoadingTimer();
288
+ this.showLoadingIndicator = false;
289
+ }
290
+
291
+ /** Called when the user clicks the popout button. */
292
+ handlePopout(): void {
293
+ if (this.popoutMode === 'expand') {
294
+ this.activityChannel?.postMessage('chat-popout', { state: this.serializeState() });
295
+ } else if (this.popoutMode === 'collapse') {
296
+ this.activityChannel?.postMessage('chat-popin', { state: this.serializeState() });
297
+ }
298
+ }
299
+
300
+ /** Applies a serialized state snapshot to this live instance (used on collapse path). */
301
+ applyState(state: AiAssistantSerializedState): void {
302
+ this.messages = [...state.messages];
303
+ this.showToolCalls = state.showToolCalls;
304
+ this.showThinkingSteps = state.showThinkingSteps;
305
+ this.enabledAnimations = [...state.enabledAnimations];
306
+ this.driver?.loadHistory(state.messages);
307
+ }
308
+
309
+ private serializeState(): AiAssistantSerializedState {
310
+ return {
311
+ messages: [...this.messages],
312
+ showToolCalls: this.showToolCalls,
313
+ showThinkingSteps: this.showThinkingSteps,
314
+ enabledAnimations: [...this.enabledAnimations],
315
+ };
316
+ }
317
+
318
+ toggleSettings() {
319
+ this.settingsOpen = !this.settingsOpen;
320
+ }
321
+
322
+ toggleShowToolCalls() {
323
+ this.showToolCalls = !this.showToolCalls;
324
+ }
325
+
326
+ toggleShowThinkingSteps() {
327
+ this.showThinkingSteps = !this.showThinkingSteps;
328
+ }
329
+
330
+ setEnabledAnimations(animations: AiAssistantAnimation[]) {
331
+ this.enabledAnimations = animations;
332
+ }
333
+
334
+ downloadHistory() {
335
+ const timestamp = new Date().toISOString().replace(/:/g, '-');
336
+ const agentName = (this.headerTitle ?? 'chat').toLowerCase().replace(/\s+/g, '-');
337
+ const payload = {
338
+ messages: this.messages,
339
+ debug: this.debugStateFactory?.(),
340
+ };
341
+ const blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' });
342
+ const url = URL.createObjectURL(blob);
343
+ const a = document.createElement('a');
344
+ a.href = url;
345
+ a.download = `${agentName}-${timestamp}.json`;
346
+ a.click();
347
+ URL.revokeObjectURL(url);
348
+ }
349
+
350
+ triggerFileInput(): void {
351
+ this.shadowRoot?.querySelector<HTMLInputElement>('.file-input')?.click();
352
+ }
353
+
354
+ handleFileSelect(e: Event): void {
355
+ void this.loadSelectedFiles(e);
356
+ }
357
+
358
+ removeAttachment(attachment: ChatAttachment): void {
359
+ this.attachments = this.attachments.filter((a) => a !== attachment);
360
+ }
361
+
362
+ removeAttachmentError(message: string): void {
363
+ const idx = this.attachmentErrors.indexOf(message);
364
+ if (idx !== -1) {
365
+ this.attachmentErrors = [
366
+ ...this.attachmentErrors.slice(0, idx),
367
+ ...this.attachmentErrors.slice(idx + 1),
368
+ ];
369
+ }
370
+ }
371
+
372
+ private isAcceptedFile(file: File): boolean {
373
+ const accepted = this.chatConfig.acceptedFiles;
374
+ if (!accepted) return false;
375
+
376
+ const acceptedList = accepted.split(',').map((s) => s.trim().toLowerCase());
377
+ const ext = `.${file.name.split('.').pop()?.toLowerCase() ?? ''}`;
378
+
379
+ return acceptedList.some(
380
+ (a) =>
381
+ a === ext ||
382
+ a === file.type ||
383
+ (a.endsWith('/*') && file.type.startsWith(a.replace('/*', '/'))),
384
+ );
385
+ }
386
+
387
+ private readFileAsText(file: File): Promise<string> {
388
+ return new Promise((resolve, reject) => {
389
+ const reader = new FileReader();
390
+ reader.onload = () => resolve(reader.result as string);
391
+ reader.onerror = () => reject(new Error(`Failed to read file: ${file.name}`));
392
+ reader.readAsText(file);
393
+ });
394
+ }
395
+
396
+ private async loadSelectedFiles(e: Event): Promise<void> {
397
+ const input = e.target as HTMLInputElement;
398
+ const files = Array.from(input.files ?? []);
399
+ input.value = '';
400
+
401
+ type FileResult = { ok: true; attachment: ChatAttachment } | { ok: false; message: string };
402
+ const results = await Promise.all(
403
+ files.map(async (file): Promise<FileResult> => {
404
+ if (!this.isAcceptedFile(file)) {
405
+ return { ok: false, message: `"${file.name}" is not an accepted file type.` };
406
+ }
407
+ try {
408
+ const content = await this.readFileAsText(file);
409
+ return {
410
+ ok: true,
411
+ attachment: { name: file.name, content, mimeType: file.type || 'text/plain' },
412
+ };
413
+ } catch (readError) {
414
+ logger.error('Failed to read file:', readError);
415
+ return { ok: false, message: `Failed to read "${file.name}".` };
416
+ }
417
+ }),
418
+ );
419
+
420
+ const newAttachments = results
421
+ .filter((r): r is Extract<FileResult, { ok: true }> => r.ok)
422
+ .map((r) => r.attachment);
423
+ const newErrors = results
424
+ .filter((r): r is Extract<FileResult, { ok: false }> => !r.ok)
425
+ .map((r) => r.message);
426
+
427
+ if (newAttachments.length) this.attachments = [...this.attachments, ...newAttachments];
428
+ if (newErrors.length) this.attachmentErrors = [...this.attachmentErrors, ...newErrors];
429
+ }
430
+
431
+ handleSendClick() {
432
+ this.send();
433
+ }
434
+
435
+ private async send() {
436
+ const input = this.inputValue.trim();
437
+ if ((!input && !this.attachments.length) || this.state === 'loading') return;
438
+ const pendingAttachments = this.attachments.length ? [...this.attachments] : undefined;
439
+ this.inputValue = '';
440
+ this.attachments = [];
441
+ this.attachmentErrors = [];
442
+ this.state = 'loading';
443
+ this.startLoadingTimer();
444
+ const displayInput = pendingAttachments?.length
445
+ ? `${input}\n\n*Attached: ${pendingAttachments.map((a) => a.name).join(', ')}*`
446
+ : input;
447
+ try {
448
+ await this.driver?.sendMessage(displayInput, pendingAttachments);
449
+ } finally {
450
+ this.stopLoadingTimer();
451
+ this.state = 'idle';
452
+ }
453
+ }
454
+
455
+ onChatHeaderMouseDown(e: MouseEvent) {
456
+ if (this.popoutMode !== 'expand') return;
457
+ e.preventDefault();
458
+ this.dispatchEvent(
459
+ new CustomEvent('chat-header-mousedown', {
460
+ bubbles: true,
461
+ composed: true,
462
+ detail: { clientX: e.clientX, clientY: e.clientY },
463
+ }),
464
+ );
465
+ }
466
+
467
+ handleInteractionCompleted(e: Event) {
468
+ const detail = (e as CustomEvent).detail;
469
+ if (detail && detail.interactionId) {
470
+ this.startLoadingTimer();
471
+ this.driver?.resolveInteraction(detail.interactionId, detail);
472
+ }
473
+ }
474
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * State of the AI assistant component.
3
+ *
4
+ * @beta
5
+ */
6
+ export type AiAssistantState = 'idle' | 'loading' | 'error';
7
+
8
+ /**
9
+ * Controls the pop-out button shown in the assistant header.
10
+ * - `"expand"` — assistant is embedded in the bubble dialog; button expands it to a layout panel.
11
+ * - `"collapse"` — assistant is in a layout panel; button collapses it back to the bubble.
12
+ *
13
+ * @beta
14
+ */
15
+ export type PopoutMode = 'expand' | 'collapse';
16
+
17
+ /**
18
+ * Metadata describing a UI animation in the assistant.
19
+ *
20
+ * @beta
21
+ */
22
+ export interface AiAssistantAnimationDef {
23
+ /** Display label shown in the settings multiselect. */
24
+ label: string;
25
+ /** Short description shown as a tooltip on the option. */
26
+ tooltip: string;
27
+ }
28
+
29
+ /**
30
+ * Registry of all available animations with their display metadata.
31
+ * Adding an entry here automatically extends the {@link AiAssistantAnimation} type.
32
+ *
33
+ * @beta
34
+ */
35
+ export const ANIMATION_DEFS = {
36
+ loading: {
37
+ label: 'Loading indicator',
38
+ tooltip: 'Shows a pulsing animation while the assistant is generating a response.',
39
+ },
40
+ halo: {
41
+ label: 'Halo',
42
+ tooltip: 'Displays a glowing halo around the assistant avatar while a response is streaming.',
43
+ },
44
+ } satisfies Record<string, AiAssistantAnimationDef>;
45
+
46
+ /**
47
+ * Union of all available animation names, derived from {@link ANIMATION_DEFS}.
48
+ *
49
+ * @beta
50
+ */
51
+ export type AiAssistantAnimation = keyof typeof ANIMATION_DEFS;
52
+
53
+ /**
54
+ * All available animation names, derived from the registry.
55
+ *
56
+ * @internal
57
+ */
58
+ export const ALL_ANIMATIONS = Object.keys(ANIMATION_DEFS) as AiAssistantAnimation[];
@@ -0,0 +1 @@
1
+ export * from './styles';