@dxos/plugin-automation 0.7.5-labs.5f04cf6 → 0.7.5-labs.e27f9b9

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 (242) hide show
  1. package/dist/lib/browser/AutomationPanel-WFJZAW4F.mjs +8 -0
  2. package/dist/lib/browser/AutomationPanel-WFJZAW4F.mjs.map +7 -0
  3. package/dist/lib/browser/ChatContainer-OFCOZ5T2.mjs +12 -0
  4. package/dist/lib/browser/ChatContainer-OFCOZ5T2.mjs.map +7 -0
  5. package/dist/lib/browser/{ai-client-6CRYUC6D.mjs → ai-client-AARXEMMJ.mjs} +2 -2
  6. package/dist/lib/browser/{ai-client-6CRYUC6D.mjs.map → ai-client-AARXEMMJ.mjs.map} +3 -3
  7. package/dist/lib/browser/{app-graph-builder-IJTTULDP.mjs → app-graph-builder-B4U34VSR.mjs} +32 -1
  8. package/dist/lib/browser/{app-graph-builder-IJTTULDP.mjs.map → app-graph-builder-B4U34VSR.mjs.map} +3 -3
  9. package/dist/lib/browser/chunk-5SLV6AUA.mjs +1367 -0
  10. package/dist/lib/browser/chunk-5SLV6AUA.mjs.map +7 -0
  11. package/dist/lib/browser/chunk-7XADMUOW.mjs +205 -0
  12. package/dist/lib/browser/chunk-7XADMUOW.mjs.map +7 -0
  13. package/dist/lib/browser/{chunk-EKJVAFT2.mjs → chunk-IYSMXX6Q.mjs} +64 -7
  14. package/dist/lib/{node-esm/chunk-5QUXIXNB.mjs.map → browser/chunk-IYSMXX6Q.mjs.map} +4 -4
  15. package/dist/lib/browser/chunk-RAVNWHNE.mjs +242 -0
  16. package/dist/lib/browser/chunk-RAVNWHNE.mjs.map +7 -0
  17. package/dist/lib/browser/index.mjs +43 -38
  18. package/dist/lib/browser/index.mjs.map +3 -3
  19. package/dist/lib/browser/intent-resolver-754MPV7H.mjs +29 -0
  20. package/dist/lib/browser/intent-resolver-754MPV7H.mjs.map +7 -0
  21. package/dist/lib/browser/meta.json +1 -1
  22. package/dist/lib/browser/{react-surface-IGMBLSV7.mjs → react-surface-4QZ6AKBF.mjs} +22 -21
  23. package/dist/lib/browser/react-surface-4QZ6AKBF.mjs.map +7 -0
  24. package/dist/lib/browser/types/index.mjs +9 -3
  25. package/dist/lib/node/AutomationPanel-LRDEDGXI.cjs +26 -0
  26. package/dist/lib/node/AutomationPanel-LRDEDGXI.cjs.map +7 -0
  27. package/dist/lib/node/ChatContainer-6LZX4K2Z.cjs +33 -0
  28. package/dist/lib/node/ChatContainer-6LZX4K2Z.cjs.map +7 -0
  29. package/dist/lib/node/{ai-client-A3RRU55B.cjs → ai-client-SA35GN5Q.cjs} +5 -5
  30. package/dist/lib/node/{ai-client-A3RRU55B.cjs.map → ai-client-SA35GN5Q.cjs.map} +3 -3
  31. package/dist/lib/node/{app-graph-builder-MF5M4QRS.cjs → app-graph-builder-ENVDOPS4.cjs} +35 -4
  32. package/dist/lib/node/{app-graph-builder-MF5M4QRS.cjs.map → app-graph-builder-ENVDOPS4.cjs.map} +3 -3
  33. package/dist/lib/node/chunk-6VMSH4P6.cjs +1364 -0
  34. package/dist/lib/node/chunk-6VMSH4P6.cjs.map +7 -0
  35. package/dist/lib/node/{chunk-VEGLN4YN.cjs → chunk-HEYQONXC.cjs} +76 -24
  36. package/dist/lib/node/chunk-HEYQONXC.cjs.map +7 -0
  37. package/dist/lib/node/chunk-LU4HQWJD.cjs +226 -0
  38. package/dist/lib/node/chunk-LU4HQWJD.cjs.map +7 -0
  39. package/dist/lib/node/{AutomationPanel-RXJDVLCF.cjs → chunk-WWU5FVAO.cjs} +120 -7
  40. package/dist/lib/node/chunk-WWU5FVAO.cjs.map +7 -0
  41. package/dist/lib/node/index.cjs +72 -66
  42. package/dist/lib/node/index.cjs.map +3 -3
  43. package/dist/lib/node/{intent-resolver-TBHYXBDI.cjs → intent-resolver-CNVBSG4E.cjs} +19 -14
  44. package/dist/lib/node/intent-resolver-CNVBSG4E.cjs.map +7 -0
  45. package/dist/lib/node/meta.json +1 -1
  46. package/dist/lib/node/{react-surface-BVUZMKYS.cjs → react-surface-5HYLBDC3.cjs} +23 -22
  47. package/dist/lib/node/react-surface-5HYLBDC3.cjs.map +7 -0
  48. package/dist/lib/node/types/index.cjs +14 -8
  49. package/dist/lib/node/types/index.cjs.map +2 -2
  50. package/dist/lib/node-esm/AutomationPanel-ZV7VEEPP.mjs +9 -0
  51. package/dist/lib/node-esm/AutomationPanel-ZV7VEEPP.mjs.map +7 -0
  52. package/dist/lib/node-esm/ChatContainer-PPVMC2FC.mjs +13 -0
  53. package/dist/lib/node-esm/ChatContainer-PPVMC2FC.mjs.map +7 -0
  54. package/dist/lib/node-esm/{ai-client-2GBZRHBA.mjs → ai-client-2ZA4TYFZ.mjs} +2 -2
  55. package/dist/lib/node-esm/{ai-client-2GBZRHBA.mjs.map → ai-client-2ZA4TYFZ.mjs.map} +3 -3
  56. package/dist/lib/node-esm/{app-graph-builder-5N7OK23B.mjs → app-graph-builder-IYOUCQZT.mjs} +32 -1
  57. package/dist/lib/node-esm/{app-graph-builder-5N7OK23B.mjs.map → app-graph-builder-IYOUCQZT.mjs.map} +3 -3
  58. package/dist/lib/node-esm/{chunk-5QUXIXNB.mjs → chunk-3KB5HRXA.mjs} +64 -7
  59. package/dist/lib/{browser/chunk-EKJVAFT2.mjs.map → node-esm/chunk-3KB5HRXA.mjs.map} +4 -4
  60. package/dist/lib/node-esm/chunk-EVTLHDM2.mjs +206 -0
  61. package/dist/lib/node-esm/chunk-EVTLHDM2.mjs.map +7 -0
  62. package/dist/lib/node-esm/chunk-MS7OIGVR.mjs +243 -0
  63. package/dist/lib/node-esm/chunk-MS7OIGVR.mjs.map +7 -0
  64. package/dist/lib/node-esm/chunk-WISKXX7U.mjs +1368 -0
  65. package/dist/lib/node-esm/chunk-WISKXX7U.mjs.map +7 -0
  66. package/dist/lib/node-esm/index.mjs +43 -38
  67. package/dist/lib/node-esm/index.mjs.map +3 -3
  68. package/dist/lib/node-esm/{intent-resolver-DPJJHFGK.mjs → intent-resolver-RQBXW442.mjs} +18 -13
  69. package/dist/lib/node-esm/intent-resolver-RQBXW442.mjs.map +7 -0
  70. package/dist/lib/node-esm/meta.json +1 -1
  71. package/dist/lib/node-esm/{react-surface-77S6XJOM.mjs → react-surface-SS5WCRJ2.mjs} +22 -21
  72. package/dist/lib/node-esm/react-surface-SS5WCRJ2.mjs.map +7 -0
  73. package/dist/lib/node-esm/types/index.mjs +9 -3
  74. package/dist/types/src/AutomationPlugin.d.ts.map +1 -1
  75. package/dist/types/src/artifacts.stories.d.ts +10 -24
  76. package/dist/types/src/artifacts.stories.d.ts.map +1 -1
  77. package/dist/types/src/capabilities/app-graph-builder.d.ts +22 -22
  78. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
  79. package/dist/types/src/capabilities/index.d.ts +27 -27
  80. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  81. package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -1
  82. package/dist/types/src/components/{ChatContainer.d.ts → ChatContainer/ChatContainer.d.ts} +2 -2
  83. package/dist/types/src/components/ChatContainer/ChatContainer.d.ts.map +1 -0
  84. package/dist/types/src/components/ChatContainer/index.d.ts +4 -0
  85. package/dist/types/src/components/ChatContainer/index.d.ts.map +1 -0
  86. package/dist/types/src/components/MarkdownViewer/MarkdownViewer.d.ts +14 -0
  87. package/dist/types/src/components/MarkdownViewer/MarkdownViewer.d.ts.map +1 -0
  88. package/dist/types/src/components/MarkdownViewer/MarkdownViewer.stories.d.ts +8 -0
  89. package/dist/types/src/components/MarkdownViewer/MarkdownViewer.stories.d.ts.map +1 -0
  90. package/dist/types/src/components/MarkdownViewer/index.d.ts +2 -0
  91. package/dist/types/src/components/MarkdownViewer/index.d.ts.map +1 -0
  92. package/dist/types/src/components/ServiceRegistry/ServiceRegistry.d.ts +6 -0
  93. package/dist/types/src/components/ServiceRegistry/ServiceRegistry.d.ts.map +1 -0
  94. package/dist/types/src/components/ServiceRegistry/ServiceRegistry.stories.d.ts +8 -0
  95. package/dist/types/src/components/ServiceRegistry/ServiceRegistry.stories.d.ts.map +1 -0
  96. package/dist/types/src/components/ServiceRegistry/index.d.ts +2 -0
  97. package/dist/types/src/components/ServiceRegistry/index.d.ts.map +1 -0
  98. package/dist/types/src/components/Thread/ScrollContainer.d.ts +15 -0
  99. package/dist/types/src/components/Thread/ScrollContainer.d.ts.map +1 -0
  100. package/dist/types/src/components/Thread/StatusLine.d.ts +11 -0
  101. package/dist/types/src/components/Thread/StatusLine.d.ts.map +1 -0
  102. package/dist/types/src/components/Thread/StatusLine.stories.d.ts +9 -0
  103. package/dist/types/src/components/Thread/StatusLine.stories.d.ts.map +1 -0
  104. package/dist/types/src/components/Thread/Tabbed.d.ts +9 -0
  105. package/dist/types/src/components/Thread/Tabbed.d.ts.map +1 -0
  106. package/dist/types/src/components/Thread/Tabbed.stories.d.ts +8 -0
  107. package/dist/types/src/components/Thread/Tabbed.stories.d.ts.map +1 -0
  108. package/dist/types/src/components/Thread/Thread.d.ts +6 -9
  109. package/dist/types/src/components/Thread/Thread.d.ts.map +1 -1
  110. package/dist/types/src/components/Thread/Thread.stories.d.ts +2 -4
  111. package/dist/types/src/components/Thread/Thread.stories.d.ts.map +1 -1
  112. package/dist/types/src/components/Thread/ThreadMessage.d.ts +12 -0
  113. package/dist/types/src/components/Thread/ThreadMessage.d.ts.map +1 -0
  114. package/dist/types/src/components/Thread/ToggleContainer.d.ts +13 -0
  115. package/dist/types/src/components/Thread/ToggleContainer.d.ts.map +1 -0
  116. package/dist/types/src/components/Thread/ToggleContainer.stories.d.ts +9 -0
  117. package/dist/types/src/components/Thread/ToggleContainer.stories.d.ts.map +1 -0
  118. package/dist/types/src/components/index.d.ts +5 -2
  119. package/dist/types/src/components/index.d.ts.map +1 -1
  120. package/dist/types/src/hooks/index.d.ts +1 -0
  121. package/dist/types/src/hooks/index.d.ts.map +1 -1
  122. package/dist/types/src/hooks/processor.d.ts +32 -13
  123. package/dist/types/src/hooks/processor.d.ts.map +1 -1
  124. package/dist/types/src/hooks/useServiceRegistry.d.ts +3 -0
  125. package/dist/types/src/hooks/useServiceRegistry.d.ts.map +1 -0
  126. package/dist/types/src/tools/function.d.ts +5 -0
  127. package/dist/types/src/tools/function.d.ts.map +1 -0
  128. package/dist/types/src/tools/index.d.ts +3 -0
  129. package/dist/types/src/tools/index.d.ts.map +1 -0
  130. package/dist/types/src/tools/openapi.d.ts +10 -0
  131. package/dist/types/src/tools/openapi.d.ts.map +1 -0
  132. package/dist/types/src/tools/openapi.test.d.ts +2 -0
  133. package/dist/types/src/tools/openapi.test.d.ts.map +1 -0
  134. package/dist/types/src/translations.d.ts +9 -6
  135. package/dist/types/src/translations.d.ts.map +1 -1
  136. package/dist/types/src/types/index.d.ts +1 -0
  137. package/dist/types/src/types/index.d.ts.map +1 -1
  138. package/dist/types/src/types/registry.d.ts +9 -0
  139. package/dist/types/src/types/registry.d.ts.map +1 -0
  140. package/dist/types/src/types/schema.d.ts +131 -7
  141. package/dist/types/src/types/schema.d.ts.map +1 -1
  142. package/dist/types/src/types/types.d.ts +2 -2
  143. package/dist/types/src/types/types.d.ts.map +1 -1
  144. package/dist/types/tsconfig.tsbuildinfo +1 -1
  145. package/package.json +54 -49
  146. package/src/AutomationPlugin.tsx +12 -20
  147. package/src/artifacts.stories.tsx +126 -65
  148. package/src/capabilities/ai-client.ts +1 -1
  149. package/src/capabilities/app-graph-builder.ts +31 -2
  150. package/src/capabilities/intent-resolver.ts +14 -9
  151. package/src/capabilities/react-surface.tsx +11 -11
  152. package/src/components/ChatContainer/ChatContainer.tsx +117 -0
  153. package/src/components/ChatContainer/index.ts +8 -0
  154. package/src/components/MarkdownViewer/MarkdownViewer.stories.tsx +56 -0
  155. package/src/components/MarkdownViewer/MarkdownViewer.tsx +79 -0
  156. package/src/components/MarkdownViewer/index.ts +5 -0
  157. package/src/components/ServiceRegistry/ServiceRegistry.stories.tsx +48 -0
  158. package/src/components/ServiceRegistry/ServiceRegistry.tsx +75 -0
  159. package/src/components/ServiceRegistry/index.ts +5 -0
  160. package/src/components/Thread/ScrollContainer.tsx +92 -0
  161. package/src/components/Thread/StatusLine.stories.tsx +52 -0
  162. package/src/components/Thread/StatusLine.tsx +76 -0
  163. package/src/components/Thread/Tabbed.stories.tsx +52 -0
  164. package/src/components/Thread/Tabbed.tsx +72 -0
  165. package/src/components/Thread/Thread.stories.tsx +145 -97
  166. package/src/components/Thread/Thread.tsx +115 -108
  167. package/src/components/Thread/ThreadMessage.tsx +195 -0
  168. package/src/components/Thread/ToggleContainer.stories.tsx +111 -0
  169. package/src/components/Thread/ToggleContainer.tsx +103 -0
  170. package/src/components/index.ts +4 -1
  171. package/src/hooks/index.ts +2 -0
  172. package/src/hooks/processor.ts +100 -60
  173. package/src/hooks/useServiceRegistry.ts +22 -0
  174. package/src/tools/function.ts +47 -0
  175. package/src/tools/index.ts +6 -0
  176. package/src/tools/openapi.test.ts +227 -0
  177. package/src/tools/openapi.ts +331 -0
  178. package/src/translations.ts +8 -6
  179. package/src/types/index.ts +1 -0
  180. package/src/types/registry.ts +89 -0
  181. package/src/types/schema.ts +78 -3
  182. package/src/types/types.ts +2 -2
  183. package/dist/lib/browser/AssistantPanel-KO6DZBTU.mjs +0 -340
  184. package/dist/lib/browser/AssistantPanel-KO6DZBTU.mjs.map +0 -7
  185. package/dist/lib/browser/AutomationPanel-BMLM533Z.mjs +0 -131
  186. package/dist/lib/browser/AutomationPanel-BMLM533Z.mjs.map +0 -7
  187. package/dist/lib/browser/ChatContainer-SIAJFRFF.mjs +0 -300
  188. package/dist/lib/browser/ChatContainer-SIAJFRFF.mjs.map +0 -7
  189. package/dist/lib/browser/chunk-C3VRGDR6.mjs +0 -75
  190. package/dist/lib/browser/chunk-C3VRGDR6.mjs.map +0 -7
  191. package/dist/lib/browser/chunk-JSNPW6JF.mjs +0 -14
  192. package/dist/lib/browser/chunk-JSNPW6JF.mjs.map +0 -7
  193. package/dist/lib/browser/chunk-KPSDH6XZ.mjs +0 -118
  194. package/dist/lib/browser/chunk-KPSDH6XZ.mjs.map +0 -7
  195. package/dist/lib/browser/chunk-YZF3SDZL.mjs +0 -139
  196. package/dist/lib/browser/chunk-YZF3SDZL.mjs.map +0 -7
  197. package/dist/lib/browser/intent-resolver-AHDQ3KQP.mjs +0 -24
  198. package/dist/lib/browser/intent-resolver-AHDQ3KQP.mjs.map +0 -7
  199. package/dist/lib/browser/react-surface-IGMBLSV7.mjs.map +0 -7
  200. package/dist/lib/node/AssistantPanel-PPFS2HKK.cjs +0 -360
  201. package/dist/lib/node/AssistantPanel-PPFS2HKK.cjs.map +0 -7
  202. package/dist/lib/node/AutomationPanel-RXJDVLCF.cjs.map +0 -7
  203. package/dist/lib/node/ChatContainer-GS2SC5OG.cjs +0 -318
  204. package/dist/lib/node/ChatContainer-GS2SC5OG.cjs.map +0 -7
  205. package/dist/lib/node/chunk-BUQOZA4N.cjs +0 -97
  206. package/dist/lib/node/chunk-BUQOZA4N.cjs.map +0 -7
  207. package/dist/lib/node/chunk-DNLBVFR7.cjs +0 -147
  208. package/dist/lib/node/chunk-DNLBVFR7.cjs.map +0 -7
  209. package/dist/lib/node/chunk-TWDGP26W.cjs +0 -172
  210. package/dist/lib/node/chunk-TWDGP26W.cjs.map +0 -7
  211. package/dist/lib/node/chunk-VEGLN4YN.cjs.map +0 -7
  212. package/dist/lib/node/chunk-Z2YFE5SJ.cjs +0 -49
  213. package/dist/lib/node/chunk-Z2YFE5SJ.cjs.map +0 -7
  214. package/dist/lib/node/intent-resolver-TBHYXBDI.cjs.map +0 -7
  215. package/dist/lib/node/react-surface-BVUZMKYS.cjs.map +0 -7
  216. package/dist/lib/node-esm/AssistantPanel-XSSKKCJY.mjs +0 -341
  217. package/dist/lib/node-esm/AssistantPanel-XSSKKCJY.mjs.map +0 -7
  218. package/dist/lib/node-esm/AutomationPanel-GQZZ4UBI.mjs +0 -132
  219. package/dist/lib/node-esm/AutomationPanel-GQZZ4UBI.mjs.map +0 -7
  220. package/dist/lib/node-esm/ChatContainer-57ILGC3R.mjs +0 -301
  221. package/dist/lib/node-esm/ChatContainer-57ILGC3R.mjs.map +0 -7
  222. package/dist/lib/node-esm/chunk-ICHNUP5M.mjs +0 -119
  223. package/dist/lib/node-esm/chunk-ICHNUP5M.mjs.map +0 -7
  224. package/dist/lib/node-esm/chunk-JCYVS5GG.mjs +0 -76
  225. package/dist/lib/node-esm/chunk-JCYVS5GG.mjs.map +0 -7
  226. package/dist/lib/node-esm/chunk-L2B6VGMG.mjs +0 -16
  227. package/dist/lib/node-esm/chunk-L2B6VGMG.mjs.map +0 -7
  228. package/dist/lib/node-esm/chunk-U7HXYMPZ.mjs +0 -141
  229. package/dist/lib/node-esm/chunk-U7HXYMPZ.mjs.map +0 -7
  230. package/dist/lib/node-esm/intent-resolver-DPJJHFGK.mjs.map +0 -7
  231. package/dist/lib/node-esm/react-surface-77S6XJOM.mjs.map +0 -7
  232. package/dist/types/src/components/AssistantPanel/AssistantPanel.d.ts +0 -8
  233. package/dist/types/src/components/AssistantPanel/AssistantPanel.d.ts.map +0 -1
  234. package/dist/types/src/components/AssistantPanel/index.d.ts +0 -3
  235. package/dist/types/src/components/AssistantPanel/index.d.ts.map +0 -1
  236. package/dist/types/src/components/AssistantPanel/system-instructions.d.ts +0 -6
  237. package/dist/types/src/components/AssistantPanel/system-instructions.d.ts.map +0 -1
  238. package/dist/types/src/components/ChatContainer.d.ts.map +0 -1
  239. package/src/components/AssistantPanel/AssistantPanel.tsx +0 -232
  240. package/src/components/AssistantPanel/index.ts +0 -7
  241. package/src/components/AssistantPanel/system-instructions.ts +0 -166
  242. package/src/components/ChatContainer.tsx +0 -80
@@ -0,0 +1,195 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import React, { type FC } from 'react';
6
+
7
+ import { type MessageContentBlock, type Message } from '@dxos/artifact';
8
+ import { invariant } from '@dxos/invariant';
9
+ import { Button, ButtonGroup, Icon, type ThemedClassName } from '@dxos/react-ui';
10
+ import { Json } from '@dxos/react-ui-syntax-highlighter';
11
+ import { mx } from '@dxos/react-ui-theme';
12
+ import { safeParseJson } from '@dxos/util';
13
+
14
+ import { StatusLine } from './StatusLine';
15
+ import { ToggleContainer } from './ToggleContainer';
16
+ import { MarkdownViewer } from '../MarkdownViewer';
17
+
18
+ export type ThreadMessageProps = ThemedClassName<{
19
+ message: Message;
20
+ collapse?: boolean;
21
+ debug?: boolean;
22
+ onSuggest?: (text: string) => void;
23
+ onDelete?: (id: string) => void;
24
+ }>;
25
+
26
+ export const ThreadMessage: FC<ThreadMessageProps> = ({
27
+ classNames,
28
+ message,
29
+ collapse,
30
+ debug,
31
+ onSuggest,
32
+ onDelete,
33
+ }) => {
34
+ if (typeof message !== 'object') {
35
+ return <div className={mx(classNames)}>{message}</div>;
36
+ }
37
+
38
+ const { role, content = [] } = message;
39
+
40
+ // TODO(burdon): Factor out tool blocks.
41
+ const tools = content.filter((block) => block.type === 'tool_use' || block.type === 'tool_result');
42
+ if (collapse && tools.length > 0) {
43
+ let request: MessageContentBlock & { type: 'tool_use' };
44
+ const lines = tools.map((tool) => {
45
+ switch (tool.type) {
46
+ case 'tool_use': {
47
+ request = tool;
48
+ // TODO(burdon): Get plugin name.
49
+ return `Calling ${tool.name}...`;
50
+ }
51
+ case 'tool_result': {
52
+ if (!request) {
53
+ return 'Error';
54
+ }
55
+
56
+ return `Processed ${request.name}`;
57
+ }
58
+ default:
59
+ return 'Error';
60
+ }
61
+ });
62
+
63
+ return (
64
+ <div className={mx('flex', classNames)}>
65
+ <div className='w-full p-1 px-2 overflow-hidden rounded-md bg-base'>
66
+ <ToggleContainer title={<StatusLine lines={lines} autoAdvance />} toggle>
67
+ <Json data={content[content.length - 1]} classNames='!p-1 text-xs' />
68
+ </ToggleContainer>
69
+ </div>
70
+ </div>
71
+ );
72
+ }
73
+
74
+ return (
75
+ <div className={mx('flex flex-col shrink-0 gap-2')}>
76
+ {debug && (
77
+ <div className='text-xs text-subdued'>
78
+ {message.id}{' '}
79
+ {onDelete && (
80
+ <span className='cursor-pointer underline' onClick={() => onDelete(message.id)}>
81
+ delete
82
+ </span>
83
+ )}
84
+ </div>
85
+ )}
86
+ {content.map((block, idx) => (
87
+ <div key={idx} className={mx('flex', classNames, block.type === 'text' && role === 'user' && 'justify-end')}>
88
+ <Block role={role} block={block} onSuggest={onSuggest ?? (() => {})} />
89
+ </div>
90
+ ))}
91
+ </div>
92
+ );
93
+ };
94
+
95
+ const Block = ({
96
+ block,
97
+ role,
98
+ onSuggest,
99
+ }: Pick<Message, 'role'> & { block: MessageContentBlock; onSuggest: (text: string) => void }) => {
100
+ const Component = componentMap[block.type] ?? componentMap.default;
101
+ return (
102
+ <div
103
+ className={mx(
104
+ 'p-1 px-2 overflow-hidden rounded-md',
105
+ (block.type !== 'text' || block.disposition) && 'w-full bg-base',
106
+ block.type === 'text' && role === 'user' && 'bg-blue-200 dark:bg-blue-800',
107
+ )}
108
+ >
109
+ <Component block={block} onSuggest={onSuggest} />
110
+ </div>
111
+ );
112
+ };
113
+
114
+ const titles: Record<string, string> = {
115
+ ['cot' as const]: 'Chain of thought',
116
+
117
+ // TODO(burdon): Only show if debugging.
118
+ ['tool_use' as const]: 'Tool request',
119
+ ['tool_result' as const]: 'Tool result',
120
+
121
+ ['artifact' as const]: 'Artifact',
122
+ };
123
+
124
+ type BlockComponent = FC<{ block: MessageContentBlock; onSuggest: (text: string) => void }>;
125
+
126
+ const componentMap: Record<string, BlockComponent> = {
127
+ text: ({ block }) => {
128
+ invariant(block.type === 'text');
129
+ const title = block.disposition ? titles[block.disposition] : undefined;
130
+ if (!title) {
131
+ return (
132
+ <MarkdownViewer content={block.text} classNames={[block.disposition === 'cot' && 'text-sm text-subdued']} />
133
+ );
134
+ }
135
+
136
+ return (
137
+ <ToggleContainer
138
+ title={title}
139
+ icon={
140
+ block.pending ? (
141
+ <Icon icon={'ph--circle-notch--regular'} classNames='text-subdued ml-2 animate-spin' size={4} />
142
+ ) : undefined
143
+ }
144
+ defaultOpen={block.disposition === 'cot'}
145
+ toggle
146
+ >
147
+ <MarkdownViewer content={block.text} classNames={[block.disposition === 'cot' && 'text-sm text-subdued']} />
148
+ </ToggleContainer>
149
+ );
150
+ },
151
+
152
+ json: ({ block, onSuggest }) => {
153
+ invariant(block.type === 'json');
154
+
155
+ switch (block.disposition) {
156
+ case 'suggest': {
157
+ const { text = '' }: { text: string } = safeParseJson(block.json ?? '{}') ?? ({} as any);
158
+ return <Button onClick={() => onSuggest(text)}>{text}</Button>;
159
+ }
160
+ case 'select': {
161
+ const { options = [] }: { options: string[] } = safeParseJson(block.json ?? '{}') ?? ({} as any);
162
+ return (
163
+ <ButtonGroup>
164
+ {options.map((option) => (
165
+ <Button key={option} onClick={() => onSuggest(option)}>
166
+ {option}
167
+ </Button>
168
+ ))}
169
+ </ButtonGroup>
170
+ );
171
+ }
172
+ default: {
173
+ const title = block.disposition ? titles[block.disposition] : undefined;
174
+ return (
175
+ <ToggleContainer title={title ?? 'JSON'} toggle>
176
+ <Json data={safeParseJson(block.json ?? block)} classNames='!p-1 text-xs' />
177
+ </ToggleContainer>
178
+ );
179
+ }
180
+ }
181
+ },
182
+
183
+ default: ({ block }) => {
184
+ let title = titles[block.type];
185
+ if (block.type === 'tool_use') {
186
+ title = `Tool [${block.name}]`; // TODO(burdon): Get label from tool.
187
+ }
188
+
189
+ return (
190
+ <ToggleContainer title={title ?? 'JSON'} toggle>
191
+ <Json data={block} classNames='!p-1 text-xs' />
192
+ </ToggleContainer>
193
+ );
194
+ },
195
+ };
@@ -0,0 +1,111 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import '@dxos-theme';
6
+
7
+ import { computed, type ReadonlySignal, signal } from '@preact/signals-core';
8
+ import { type StoryObj, type Meta } from '@storybook/react';
9
+ import React, { useEffect, useMemo, useState } from 'react';
10
+
11
+ import { faker } from '@dxos/random';
12
+ import { Icon, Input, Toolbar } from '@dxos/react-ui';
13
+ import { mx } from '@dxos/react-ui-theme';
14
+ import { withLayout, withTheme, withSignals } from '@dxos/storybook-utils';
15
+
16
+ import { ToggleContainer, type ToggleContainerProps } from './ToggleContainer';
17
+ import { MarkdownViewer } from '../MarkdownViewer';
18
+
19
+ class Generator {
20
+ private readonly _current = signal<string>(faker.lorem.sentence(5));
21
+ private readonly _lines = signal<string[]>([]);
22
+ private _running: NodeJS.Timeout | undefined;
23
+
24
+ readonly count = computed(() => this._lines.value.length);
25
+ readonly text: ReadonlySignal<string[]> = computed(() => [...this._lines.value, this._current.value]);
26
+
27
+ start() {
28
+ this.stop();
29
+ this._running = setInterval(() => {
30
+ if (this._current.value.length > 0) {
31
+ this._current.value += ' ';
32
+ }
33
+ this._current.value += faker.lorem.words(Math.ceil(Math.random() * 2));
34
+ if (Math.random() > 0.95) {
35
+ this._lines.value = [...this._lines.value, this._current.value + '.'];
36
+ this._current.value = '';
37
+ }
38
+ }, 100);
39
+ }
40
+
41
+ stop() {
42
+ if (this._running) {
43
+ clearInterval(this._running);
44
+ this._running = undefined;
45
+ }
46
+ }
47
+ }
48
+
49
+ const Render = ({ shrinkX, ...props }: ToggleContainerProps) => {
50
+ const generator = useMemo(() => new Generator(), []);
51
+ const [running, setRunning] = useState(false);
52
+ useEffect(() => {
53
+ if (running) {
54
+ generator.start();
55
+ } else {
56
+ generator.stop();
57
+ }
58
+ }, [running]);
59
+
60
+ return (
61
+ <div className='flex flex-col w-[500px]'>
62
+ <Toolbar.Root classNames='p-4'>
63
+ <Input.Root>
64
+ <Input.Switch checked={running} onCheckedChange={(checked) => setRunning(checked)} />
65
+ </Input.Root>
66
+ <div className='grow' />
67
+ <div>{generator.count.value}</div>
68
+ </Toolbar.Root>
69
+ <div className='flex p-4'>
70
+ <div className={mx('border border-border rounded-md p-2', !shrinkX && 'w-full')}>
71
+ <ToggleContainer
72
+ {...props}
73
+ shrinkX={shrinkX}
74
+ toggle
75
+ icon={
76
+ running ? (
77
+ <Icon icon={'ph--circle-notch--regular'} classNames='text-subdued ml-2 animate-spin' size={4} />
78
+ ) : undefined
79
+ }
80
+ >
81
+ <MarkdownViewer classNames='text-sm' content={generator.text.value.join('\n\n')} />
82
+ </ToggleContainer>
83
+ </div>
84
+ </div>
85
+ </div>
86
+ );
87
+ };
88
+
89
+ const meta: Meta<typeof ToggleContainer> = {
90
+ title: 'plugins/plugin-automation/ToggleContainer',
91
+ component: ToggleContainer,
92
+ render: Render,
93
+ decorators: [withSignals, withTheme, withLayout({ fullscreen: true, classNames: 'justify-center bg-base' })],
94
+ };
95
+
96
+ export default meta;
97
+
98
+ type Story = StoryObj<typeof ToggleContainer>;
99
+
100
+ export const Default: Story = {
101
+ args: {
102
+ title: 'Markdown',
103
+ },
104
+ };
105
+
106
+ export const Shrink: Story = {
107
+ args: {
108
+ title: 'Markdown',
109
+ shrinkX: true,
110
+ },
111
+ };
@@ -0,0 +1,103 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import React, { type JSX, type PropsWithChildren, useEffect, useState } from 'react';
6
+
7
+ import { Icon, type ThemedClassName } from '@dxos/react-ui';
8
+ import { mx } from '@dxos/react-ui-theme';
9
+
10
+ export type ToggleContainerProps = ThemedClassName<
11
+ PropsWithChildren<{
12
+ title?: string | JSX.Element;
13
+ icon?: JSX.Element;
14
+ toggle?: boolean;
15
+ defaultOpen?: boolean;
16
+ duration?: number;
17
+ /** Should shrink the width when closed. */
18
+ shrinkX?: boolean;
19
+ }>
20
+ >;
21
+
22
+ // TODO(burdon): Externalize toggle state.
23
+ export const ToggleContainer = ({
24
+ title,
25
+ icon,
26
+ toggle,
27
+ defaultOpen,
28
+ duration = 400,
29
+ shrinkX = false,
30
+ children,
31
+ classNames,
32
+ }: ToggleContainerProps) => {
33
+ const [expand, setExpand] = useState(defaultOpen || !toggle);
34
+ const [expandX, setExpandX] = useState(shrinkX ? expand : true);
35
+ const [expandY, setExpandY] = useState(expand);
36
+
37
+ useEffect(() => {
38
+ let t: NodeJS.Timeout;
39
+ if (expand) {
40
+ if (shrinkX) {
41
+ setExpandX(true);
42
+ }
43
+ t = setTimeout(
44
+ () => {
45
+ setExpandY(true);
46
+ },
47
+ shrinkX ? duration : 0,
48
+ );
49
+ } else {
50
+ setExpandY(false);
51
+ if (shrinkX) {
52
+ t = setTimeout(() => {
53
+ setExpandX(false);
54
+ }, duration);
55
+ }
56
+ }
57
+
58
+ return () => clearTimeout(t);
59
+ }, [expand]);
60
+
61
+ return (
62
+ <div className={mx('overflow-hidden', classNames)}>
63
+ {title && (
64
+ <div
65
+ className='flex gap-1 py-1 items-center text-sm text-subdued cursor-pointer select-none'
66
+ onClick={toggle ? () => setExpand((open) => !open) : undefined}
67
+ >
68
+ {toggle && (
69
+ <div className='flex w-[24px] h-[24px] items-center justify-center'>
70
+ <Icon
71
+ size={4}
72
+ icon={'ph--caret-right--regular'}
73
+ style={{ transitionDuration: `${shrinkX ? duration * 2 : duration}ms` }}
74
+ classNames={['transition transition-transform ease-in-out', expand ? 'rotate-90' : 'transform-none']}
75
+ />
76
+ </div>
77
+ )}
78
+ <div className='flex-1 pis-1 pie-1 truncate'>{title}</div>
79
+ {icon}
80
+ </div>
81
+ )}
82
+ <div
83
+ style={{ transitionDuration: `${duration}ms` }}
84
+ className={mx(
85
+ 'grid transition-[grid-template-columns] ease-in-out',
86
+ expandX ? 'grid-cols-[1fr]' : 'grid-cols-[0fr]',
87
+ )}
88
+ >
89
+ <div className='overflow-hidden'>
90
+ <div
91
+ style={{ transitionDuration: `${duration}ms` }}
92
+ className={mx(
93
+ 'grid transition-[grid-template-rows] ease-in-out',
94
+ expandY ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]',
95
+ )}
96
+ >
97
+ <div className={mx('flex overflow-hidden transition-opacity')}>{children}</div>
98
+ </div>
99
+ </div>
100
+ </div>
101
+ </div>
102
+ );
103
+ };
@@ -4,10 +4,13 @@
4
4
 
5
5
  import { lazy } from 'react';
6
6
 
7
+ export * from './AutomationPanel';
8
+ export * from './ChatContainer';
9
+ export * from './MarkdownViewer';
7
10
  export * from './PromptEditor';
11
+ export * from './ServiceRegistry';
8
12
  export * from './Thread';
9
13
  export * from './TriggerEditor';
10
14
 
11
- export const AssistantPanel = lazy(() => import('./AssistantPanel'));
12
15
  export const AutomationPanel = lazy(() => import('./AutomationPanel'));
13
16
  export const ChatContainer = lazy(() => import('./ChatContainer'));
@@ -3,4 +3,6 @@
3
3
  //
4
4
 
5
5
  export * from './processor';
6
+
7
+ export * from './useServiceRegistry';
6
8
  export * from './useLocalTriggerManager';
@@ -2,16 +2,17 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { type ReadonlySignal, type Signal, computed, signal } from '@preact/signals-core';
5
+ import { type Signal, batch, computed, signal } from '@preact/signals-core';
6
6
 
7
7
  import { type PromiseIntentDispatcher } from '@dxos/app-framework';
8
- import { type Tool, Message } from '@dxos/artifact';
8
+ import { type Tool, Message, type MessageContentBlock } from '@dxos/artifact';
9
9
  import {
10
+ isToolUse,
11
+ runTools,
10
12
  type AIServiceClientImpl,
11
13
  type GenerateRequest,
12
14
  type GenerationStream,
13
- isToolUse,
14
- runTools,
15
+ MixedStreamParser,
15
16
  } from '@dxos/assistant';
16
17
  import { createStatic } from '@dxos/echo-schema';
17
18
  import { invariant } from '@dxos/invariant';
@@ -31,56 +32,95 @@ declare global {
31
32
  }
32
33
  }
33
34
 
35
+ type RequestOptions = {
36
+ history?: Message[];
37
+ onComplete?: (messages: Message[]) => void;
38
+ };
39
+
34
40
  /**
35
- * Handles interactions with an AI service.
36
- * Manages message history, and executes tools based on AI responses.
41
+ * Handles interactions with the AI service.
37
42
  * Maintains a queue of messages and handles streaming responses from the AI service.
43
+ * Executes tools based on AI responses.
38
44
  * Supports cancellation of in-progress requests.
39
45
  */
40
- // TODO(burdon): Tests.
41
46
  export class ChatProcessor {
47
+ /** SSE stream parser. */
48
+ private readonly _parser = new MixedStreamParser();
49
+
50
+ /** Current streaming response (iterator). */
51
+ private _stream: GenerationStream | undefined;
52
+
53
+ /** Pending messages (incl. the user request). */
54
+ private readonly _pending: Signal<Message[]> = signal([]);
55
+
56
+ /** Current streaming block (from the AI service). */
57
+ private readonly _block: Signal<MessageContentBlock | undefined> = signal(undefined);
58
+
42
59
  /** Prior history from queue. */
43
60
  private _history: Message[] = [];
44
61
 
45
- /** Pending messages (incl. the initial user message). */
46
- private _pending: Signal<Message[]> = signal([]);
62
+ /**
63
+ * Streaming state.
64
+ * @reactive
65
+ */
66
+ public readonly streaming: Signal<boolean> = computed(() => this._block.value !== undefined);
47
67
 
48
- /** Streaming messages (from the AI service). */
49
- private _streaming: Signal<Message[]> = signal([]);
68
+ /**
69
+ * Array of Messages (incl. the current message being streamed).
70
+ * @reactive
71
+ */
72
+ public readonly messages: Signal<Message[]> = computed(() => {
73
+ const messages = [...this._pending.value];
74
+ if (this._block.value) {
75
+ const current = messages.pop();
76
+ invariant(current);
77
+ const { content, ...rest } = current;
78
+ const message = { ...rest, content: [...content, this._block.value] };
79
+ messages.push(message);
80
+ }
50
81
 
51
- /** Current streaming response. */
52
- private _response: GenerationStream | undefined;
82
+ return messages;
83
+ });
53
84
 
54
85
  constructor(
55
86
  private readonly _client: AIServiceClientImpl,
56
87
  private readonly _tools?: Tool[],
57
88
  private readonly _extensions?: ToolContextExtensions,
58
89
  private readonly _options: Pick<GenerateRequest, 'model' | 'systemPrompt'> = defaultOptions,
59
- ) {}
60
-
61
- get isStreaming(): ReadonlySignal<boolean> {
62
- return computed(() => this._pending.value.length > 0);
63
- }
64
-
65
- get messages(): ReadonlySignal<Message[]> {
66
- return computed(() => [...this._pending.value, ...this._streaming.value]);
90
+ ) {
91
+ // Message complete.
92
+ this._parser.message.on((message) => {
93
+ batch(() => {
94
+ this._pending.value = [...this._pending.value, message];
95
+ this._block.value = undefined;
96
+ });
97
+ });
98
+
99
+ // Streaming update (happens before message complete).
100
+ this._parser.update.on((block) => {
101
+ batch(() => {
102
+ this._block.value = block;
103
+ });
104
+ });
67
105
  }
68
106
 
69
107
  /**
70
108
  * Make GPT request.
71
109
  */
72
- async request(message: string, history: Message[] = []): Promise<Message[]> {
73
- log.info('requesting...', { message, history: history.length });
74
- this._history = history;
75
- this._pending.value = [
76
- createStatic(Message, {
77
- role: 'user',
78
- content: [{ type: 'text', text: message }],
79
- }),
80
- ];
81
- this._streaming.value = [];
82
-
83
- await this.generate();
110
+ async request(message: string, options: RequestOptions = {}): Promise<Message[]> {
111
+ batch(() => {
112
+ this._history = options.history ?? [];
113
+ this._pending.value = [
114
+ createStatic(Message, {
115
+ role: 'user',
116
+ content: [{ type: 'text', text: message }],
117
+ }),
118
+ ];
119
+ this._block.value = undefined;
120
+ });
121
+
122
+ await this._generate();
123
+ options.onComplete?.(this._pending.value);
84
124
  return this._reset();
85
125
  }
86
126
 
@@ -90,51 +130,52 @@ export class ChatProcessor {
90
130
  */
91
131
  async cancel(): Promise<Message[]> {
92
132
  log.info('cancelling...');
93
- this._response?.abort();
133
+ this._stream?.abort();
94
134
  return this._reset();
95
135
  }
96
136
 
97
137
  private async _reset(): Promise<Message[]> {
98
- const pending = this._pending.value;
99
- this._history = [];
100
- this._pending.value = [];
101
- this._streaming.value = [];
102
- return pending;
138
+ const messages = this._pending.value;
139
+ batch(() => {
140
+ this._history = [];
141
+ this._pending.value = [];
142
+ this._block.value = undefined;
143
+ });
144
+
145
+ return messages;
103
146
  }
104
147
 
105
- private async generate() {
148
+ /**
149
+ * Generate a response from the AI service.
150
+ * Iterates over tool requests.
151
+ */
152
+ private async _generate() {
106
153
  try {
107
154
  let more = false;
108
155
  do {
109
- log.info('requesting...', { history: this._history.length, pending: this._pending.value.length });
110
- this._response = await this._client.generate({
156
+ log.info('requesting...', { history: this._history.length, messages: this._pending.value.length });
157
+ this._stream = await this._client.generate({
111
158
  ...this._options,
112
159
  // TODO(burdon): Rename messages or separate history/message.
113
160
  history: [...this._history, ...this._pending.value],
114
161
  tools: this._tools,
115
162
  });
116
163
 
117
- // Process the stream.
118
- queueMicrotask(async () => {
119
- invariant(this._response);
120
- for await (const event of this._response) {
121
- log.info('event', { event: event.type });
122
- this._streaming.value = this._response.accumulatedMessages.map((message) => createStatic(Message, message));
123
- }
124
- });
164
+ // Wait until complete.
165
+ await this._parser.parse(this._stream);
166
+ await this._stream.complete();
125
167
 
126
- // Update pending messages.
127
- const messages = await this._response.complete();
128
- log.info('response', { messages: messages.length });
129
- this._pending.value.push(...messages.map((message) => createStatic(Message, message)));
130
- this._streaming.value = [];
168
+ // Add messages.
169
+ log.info('response', { messages: this._pending.value });
131
170
 
132
171
  // Resolve tool use locally.
133
172
  more = false;
134
- if (messages.length > 0 && isToolUse(messages.at(-1)!)) {
173
+ const message = this._pending.value.at(-1);
174
+ invariant(message);
175
+ if (isToolUse(message)) {
135
176
  log.info('tool request...');
136
177
  const response = await runTools({
137
- message: messages.at(-1)!,
178
+ message: this._pending.value.at(-1)!,
138
179
  tools: this._tools ?? [],
139
180
  extensions: this._extensions,
140
181
  });
@@ -142,7 +183,7 @@ export class ChatProcessor {
142
183
  log.info('tool response', { response });
143
184
  switch (response.type) {
144
185
  case 'continue': {
145
- this._pending.value.push(response.message);
186
+ this._pending.value = [...this._pending.value, response.message];
146
187
  more = true;
147
188
  break;
148
189
  }
@@ -154,8 +195,7 @@ export class ChatProcessor {
154
195
  log.catch('request failed', { err });
155
196
  } finally {
156
197
  log.info('done');
157
- this._response = undefined;
158
- this._streaming.value = [];
198
+ this._stream = undefined;
159
199
  }
160
200
  }
161
201
  }