@dxos/plugin-automation 0.7.5-labs.f5080a1 → 0.7.5-labs.ff2ff30

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 (176) hide show
  1. package/dist/lib/browser/AutomationPanel-VW2XIUPU.mjs +8 -0
  2. package/dist/lib/browser/ChatContainer-5TAVTN3T.mjs +12 -0
  3. package/dist/lib/browser/{ai-client-UJLNYP7B.mjs → ai-client-5CNY6JBF.mjs} +3 -3
  4. package/dist/lib/browser/{app-graph-builder-3H5TCRG4.mjs → app-graph-builder-FZGK55G7.mjs} +39 -4
  5. package/dist/lib/browser/app-graph-builder-FZGK55G7.mjs.map +7 -0
  6. package/dist/lib/browser/{chunk-BQHXJZ4K.mjs → chunk-2H2EUYXL.mjs} +2 -2
  7. package/dist/lib/browser/{chunk-DL7XA62G.mjs → chunk-DVE33EZL.mjs} +224 -153
  8. package/dist/lib/browser/{chunk-DL7XA62G.mjs.map → chunk-DVE33EZL.mjs.map} +4 -4
  9. package/dist/lib/browser/{chunk-PQGFC2ZO.mjs → chunk-MJK7GL5P.mjs} +61 -38
  10. package/dist/lib/browser/chunk-MJK7GL5P.mjs.map +7 -0
  11. package/dist/lib/browser/{chunk-Z54KIF6H.mjs → chunk-NQFZ6XRX.mjs} +3 -2
  12. package/dist/lib/browser/{chunk-Z54KIF6H.mjs.map → chunk-NQFZ6XRX.mjs.map} +2 -2
  13. package/dist/lib/browser/{chunk-43WRHV2L.mjs → chunk-Q4IMHYGH.mjs} +2 -2
  14. package/dist/lib/browser/{chunk-DQ7ZSYJJ.mjs → chunk-R4JH4TLE.mjs} +3 -1
  15. package/dist/lib/browser/chunk-R4JH4TLE.mjs.map +7 -0
  16. package/dist/lib/browser/index.mjs +16 -11
  17. package/dist/lib/browser/index.mjs.map +3 -3
  18. package/dist/lib/browser/{intent-resolver-5YVZJFS3.mjs → intent-resolver-BWAXKT27.mjs} +3 -3
  19. package/dist/lib/browser/meta.json +1 -1
  20. package/dist/lib/browser/{react-surface-WRHRCEV5.mjs → react-surface-ILBDBZCN.mjs} +15 -7
  21. package/dist/lib/browser/react-surface-ILBDBZCN.mjs.map +7 -0
  22. package/dist/lib/browser/types/index.mjs +2 -2
  23. package/dist/lib/node/{AutomationPanel-CO26O75P.cjs → AutomationPanel-G6EDDYWW.cjs} +7 -7
  24. package/dist/lib/node/{AutomationPanel-CO26O75P.cjs.map → AutomationPanel-G6EDDYWW.cjs.map} +2 -2
  25. package/dist/lib/node/{ChatContainer-5URBEXQD.cjs → ChatContainer-EN24W3K4.cjs} +10 -10
  26. package/dist/lib/node/ChatContainer-EN24W3K4.cjs.map +7 -0
  27. package/dist/lib/node/{ai-client-AOB6TLNW.cjs → ai-client-FKLPDELV.cjs} +7 -7
  28. package/dist/lib/node/{app-graph-builder-CDEQJEHY.cjs → app-graph-builder-T76NYV42.cjs} +48 -14
  29. package/dist/lib/node/app-graph-builder-T76NYV42.cjs.map +7 -0
  30. package/dist/lib/node/{chunk-NH7STAX6.cjs → chunk-CJGJXNY3.cjs} +245 -174
  31. package/dist/lib/node/chunk-CJGJXNY3.cjs.map +7 -0
  32. package/dist/lib/node/{chunk-TQEDPRY5.cjs → chunk-EQYHOTGG.cjs} +7 -4
  33. package/dist/lib/node/chunk-EQYHOTGG.cjs.map +7 -0
  34. package/dist/lib/node/{chunk-H3RSMGJG.cjs → chunk-GB7245FH.cjs} +6 -6
  35. package/dist/lib/node/{chunk-D2QQXWOY.cjs → chunk-HMBKP6VG.cjs} +80 -58
  36. package/dist/lib/node/chunk-HMBKP6VG.cjs.map +7 -0
  37. package/dist/lib/node/{chunk-OCW5GAVZ.cjs → chunk-QXIHYOMF.cjs} +8 -7
  38. package/dist/lib/node/{chunk-OCW5GAVZ.cjs.map → chunk-QXIHYOMF.cjs.map} +2 -2
  39. package/dist/lib/node/{chunk-AWZVJZ2I.cjs → chunk-U5Z7LFWB.cjs} +6 -6
  40. package/dist/lib/node/index.cjs +79 -74
  41. package/dist/lib/node/index.cjs.map +3 -3
  42. package/dist/lib/node/{intent-resolver-MJFZT5IU.cjs → intent-resolver-C6OKFVEW.cjs} +8 -8
  43. package/dist/lib/node/meta.json +1 -1
  44. package/dist/lib/node/{react-surface-HDAVE6NU.cjs → react-surface-LWDY7SQG.cjs} +23 -17
  45. package/dist/lib/node/react-surface-LWDY7SQG.cjs.map +7 -0
  46. package/dist/lib/node/types/index.cjs +11 -11
  47. package/dist/lib/node/types/index.cjs.map +1 -1
  48. package/dist/lib/node-esm/{AutomationPanel-VQZUKPK2.mjs → AutomationPanel-V3IWQAMO.mjs} +3 -3
  49. package/dist/lib/node-esm/{ChatContainer-23QIVDG5.mjs → ChatContainer-CNTY3C2D.mjs} +5 -5
  50. package/dist/lib/node-esm/{ai-client-RUCCJ7JZ.mjs → ai-client-XGNA6SJ5.mjs} +3 -3
  51. package/dist/lib/node-esm/{app-graph-builder-GR3URVNX.mjs → app-graph-builder-IJQEN7WT.mjs} +39 -4
  52. package/dist/lib/node-esm/app-graph-builder-IJQEN7WT.mjs.map +7 -0
  53. package/dist/lib/node-esm/{chunk-NSVQZ3EH.mjs → chunk-6HLBYDUI.mjs} +3 -2
  54. package/dist/lib/node-esm/{chunk-NSVQZ3EH.mjs.map → chunk-6HLBYDUI.mjs.map} +2 -2
  55. package/dist/lib/node-esm/{chunk-7JO77AAS.mjs → chunk-DNCXRGAF.mjs} +2 -2
  56. package/dist/lib/node-esm/{chunk-JFHI22MF.mjs → chunk-EMVA6QUT.mjs} +3 -1
  57. package/dist/lib/node-esm/chunk-EMVA6QUT.mjs.map +7 -0
  58. package/dist/lib/node-esm/{chunk-LSSWQIQD.mjs → chunk-IJRTDSKN.mjs} +2 -2
  59. package/dist/lib/node-esm/{chunk-JJFWFS6P.mjs → chunk-QP47VJT6.mjs} +224 -153
  60. package/dist/lib/node-esm/{chunk-JJFWFS6P.mjs.map → chunk-QP47VJT6.mjs.map} +4 -4
  61. package/dist/lib/node-esm/{chunk-VN2AFV25.mjs → chunk-ZLIAMW45.mjs} +61 -38
  62. package/dist/lib/node-esm/chunk-ZLIAMW45.mjs.map +7 -0
  63. package/dist/lib/node-esm/index.mjs +16 -11
  64. package/dist/lib/node-esm/index.mjs.map +3 -3
  65. package/dist/lib/node-esm/{intent-resolver-FCKNRTKQ.mjs → intent-resolver-DCP4ZDBA.mjs} +3 -3
  66. package/dist/lib/node-esm/meta.json +1 -1
  67. package/dist/lib/node-esm/{react-surface-FZ5OFRDE.mjs → react-surface-SBDXFVIN.mjs} +15 -7
  68. package/dist/lib/node-esm/react-surface-SBDXFVIN.mjs.map +7 -0
  69. package/dist/lib/node-esm/types/index.mjs +2 -2
  70. package/dist/types/src/capabilities/app-graph-builder.d.ts +23 -22
  71. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
  72. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  73. package/dist/types/src/components/AmbientChatDialog/AmbientChatDialog.d.ts +3 -0
  74. package/dist/types/src/components/AmbientChatDialog/AmbientChatDialog.d.ts.map +1 -0
  75. package/dist/types/src/components/AmbientChatDialog/index.d.ts +2 -0
  76. package/dist/types/src/components/AmbientChatDialog/index.d.ts.map +1 -0
  77. package/dist/types/src/components/Box/StatusLine.d.ts.map +1 -0
  78. package/dist/types/src/components/Box/StatusLine.stories.d.ts.map +1 -0
  79. package/dist/types/src/components/Box/Tabbed.d.ts +15 -0
  80. package/dist/types/src/components/Box/Tabbed.d.ts.map +1 -0
  81. package/dist/types/src/components/Box/Tabbed.stories.d.ts.map +1 -0
  82. package/dist/types/src/components/{Thread → Box}/ToggleContainer.d.ts +3 -3
  83. package/dist/types/src/components/Box/ToggleContainer.d.ts.map +1 -0
  84. package/dist/types/src/components/Box/ToggleContainer.stories.d.ts.map +1 -0
  85. package/dist/types/src/components/Box/index.d.ts +4 -0
  86. package/dist/types/src/components/Box/index.d.ts.map +1 -0
  87. package/dist/types/src/components/Prompt/Prompt.d.ts +7 -0
  88. package/dist/types/src/components/Prompt/Prompt.d.ts.map +1 -0
  89. package/dist/types/src/components/Prompt/Prompt.stories.d.ts +8 -0
  90. package/dist/types/src/components/Prompt/Prompt.stories.d.ts.map +1 -0
  91. package/dist/types/src/components/Prompt/index.d.ts +2 -0
  92. package/dist/types/src/components/Prompt/index.d.ts.map +1 -0
  93. package/dist/types/src/components/Prompt/prompt-autocomplete.d.ts +20 -0
  94. package/dist/types/src/components/Prompt/prompt-autocomplete.d.ts.map +1 -0
  95. package/dist/types/src/components/Thread/Thread.stories.d.ts +1 -0
  96. package/dist/types/src/components/Thread/Thread.stories.d.ts.map +1 -1
  97. package/dist/types/src/components/Thread/ThreadMessage.d.ts.map +1 -1
  98. package/dist/types/src/components/TriggerEditor/TriggerEditor.d.ts.map +1 -1
  99. package/dist/types/src/components/index.d.ts +1 -0
  100. package/dist/types/src/components/index.d.ts.map +1 -1
  101. package/dist/types/src/meta.d.ts +1 -0
  102. package/dist/types/src/meta.d.ts.map +1 -1
  103. package/dist/types/src/translations.d.ts +3 -0
  104. package/dist/types/src/translations.d.ts.map +1 -1
  105. package/package.json +52 -50
  106. package/src/AutomationPlugin.tsx +5 -5
  107. package/src/capabilities/app-graph-builder.ts +34 -3
  108. package/src/capabilities/react-surface.tsx +8 -2
  109. package/src/components/AmbientChatDialog/AmbientChatDialog.tsx +26 -0
  110. package/src/components/AmbientChatDialog/index.ts +5 -0
  111. package/src/components/{Thread → Box}/Tabbed.stories.tsx +1 -2
  112. package/src/components/Box/Tabbed.tsx +89 -0
  113. package/src/components/{Thread → Box}/ToggleContainer.stories.tsx +0 -1
  114. package/src/components/{Thread → Box}/ToggleContainer.tsx +28 -23
  115. package/src/components/Box/index.ts +7 -0
  116. package/src/components/Prompt/Prompt.stories.tsx +50 -0
  117. package/src/components/Prompt/Prompt.tsx +36 -0
  118. package/src/components/Prompt/index.ts +5 -0
  119. package/src/components/Prompt/prompt-autocomplete.ts +200 -0
  120. package/src/components/PromptEditor/PromptEditor.stories.tsx +3 -3
  121. package/src/components/Thread/Thread.stories.tsx +8 -1
  122. package/src/components/Thread/Thread.tsx +1 -1
  123. package/src/components/Thread/ThreadMessage.tsx +41 -19
  124. package/src/components/TriggerEditor/TriggerEditor.tsx +6 -1
  125. package/src/components/index.ts +1 -0
  126. package/src/meta.ts +2 -0
  127. package/src/translations.ts +1 -0
  128. package/dist/lib/browser/AutomationPanel-YPD3AGQN.mjs +0 -8
  129. package/dist/lib/browser/ChatContainer-ODZECATM.mjs +0 -12
  130. package/dist/lib/browser/app-graph-builder-3H5TCRG4.mjs.map +0 -7
  131. package/dist/lib/browser/chunk-DQ7ZSYJJ.mjs.map +0 -7
  132. package/dist/lib/browser/chunk-PQGFC2ZO.mjs.map +0 -7
  133. package/dist/lib/browser/react-surface-WRHRCEV5.mjs.map +0 -7
  134. package/dist/lib/node/ChatContainer-5URBEXQD.cjs.map +0 -7
  135. package/dist/lib/node/app-graph-builder-CDEQJEHY.cjs.map +0 -7
  136. package/dist/lib/node/chunk-D2QQXWOY.cjs.map +0 -7
  137. package/dist/lib/node/chunk-NH7STAX6.cjs.map +0 -7
  138. package/dist/lib/node/chunk-TQEDPRY5.cjs.map +0 -7
  139. package/dist/lib/node/react-surface-HDAVE6NU.cjs.map +0 -7
  140. package/dist/lib/node-esm/app-graph-builder-GR3URVNX.mjs.map +0 -7
  141. package/dist/lib/node-esm/chunk-JFHI22MF.mjs.map +0 -7
  142. package/dist/lib/node-esm/chunk-VN2AFV25.mjs.map +0 -7
  143. package/dist/lib/node-esm/react-surface-FZ5OFRDE.mjs.map +0 -7
  144. package/dist/types/src/components/Thread/ScrollContainer.d.ts +0 -15
  145. package/dist/types/src/components/Thread/ScrollContainer.d.ts.map +0 -1
  146. package/dist/types/src/components/Thread/StatusLine.d.ts.map +0 -1
  147. package/dist/types/src/components/Thread/StatusLine.stories.d.ts.map +0 -1
  148. package/dist/types/src/components/Thread/Tabbed.d.ts +0 -9
  149. package/dist/types/src/components/Thread/Tabbed.d.ts.map +0 -1
  150. package/dist/types/src/components/Thread/Tabbed.stories.d.ts.map +0 -1
  151. package/dist/types/src/components/Thread/ToggleContainer.d.ts.map +0 -1
  152. package/dist/types/src/components/Thread/ToggleContainer.stories.d.ts.map +0 -1
  153. package/src/components/Thread/ScrollContainer.tsx +0 -92
  154. package/src/components/Thread/Tabbed.tsx +0 -72
  155. /package/dist/lib/browser/{AutomationPanel-YPD3AGQN.mjs.map → AutomationPanel-VW2XIUPU.mjs.map} +0 -0
  156. /package/dist/lib/browser/{ChatContainer-ODZECATM.mjs.map → ChatContainer-5TAVTN3T.mjs.map} +0 -0
  157. /package/dist/lib/browser/{ai-client-UJLNYP7B.mjs.map → ai-client-5CNY6JBF.mjs.map} +0 -0
  158. /package/dist/lib/browser/{chunk-BQHXJZ4K.mjs.map → chunk-2H2EUYXL.mjs.map} +0 -0
  159. /package/dist/lib/browser/{chunk-43WRHV2L.mjs.map → chunk-Q4IMHYGH.mjs.map} +0 -0
  160. /package/dist/lib/browser/{intent-resolver-5YVZJFS3.mjs.map → intent-resolver-BWAXKT27.mjs.map} +0 -0
  161. /package/dist/lib/node/{ai-client-AOB6TLNW.cjs.map → ai-client-FKLPDELV.cjs.map} +0 -0
  162. /package/dist/lib/node/{chunk-H3RSMGJG.cjs.map → chunk-GB7245FH.cjs.map} +0 -0
  163. /package/dist/lib/node/{chunk-AWZVJZ2I.cjs.map → chunk-U5Z7LFWB.cjs.map} +0 -0
  164. /package/dist/lib/node/{intent-resolver-MJFZT5IU.cjs.map → intent-resolver-C6OKFVEW.cjs.map} +0 -0
  165. /package/dist/lib/node-esm/{AutomationPanel-VQZUKPK2.mjs.map → AutomationPanel-V3IWQAMO.mjs.map} +0 -0
  166. /package/dist/lib/node-esm/{ChatContainer-23QIVDG5.mjs.map → ChatContainer-CNTY3C2D.mjs.map} +0 -0
  167. /package/dist/lib/node-esm/{ai-client-RUCCJ7JZ.mjs.map → ai-client-XGNA6SJ5.mjs.map} +0 -0
  168. /package/dist/lib/node-esm/{chunk-7JO77AAS.mjs.map → chunk-DNCXRGAF.mjs.map} +0 -0
  169. /package/dist/lib/node-esm/{chunk-LSSWQIQD.mjs.map → chunk-IJRTDSKN.mjs.map} +0 -0
  170. /package/dist/lib/node-esm/{intent-resolver-FCKNRTKQ.mjs.map → intent-resolver-DCP4ZDBA.mjs.map} +0 -0
  171. /package/dist/types/src/components/{Thread → Box}/StatusLine.d.ts +0 -0
  172. /package/dist/types/src/components/{Thread → Box}/StatusLine.stories.d.ts +0 -0
  173. /package/dist/types/src/components/{Thread → Box}/Tabbed.stories.d.ts +0 -0
  174. /package/dist/types/src/components/{Thread → Box}/ToggleContainer.stories.d.ts +0 -0
  175. /package/src/components/{Thread → Box}/StatusLine.stories.tsx +0 -0
  176. /package/src/components/{Thread → Box}/StatusLine.tsx +0 -0
@@ -2,13 +2,13 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { Capabilities, contributes, type PluginsContext } from '@dxos/app-framework';
5
+ import { Capabilities, contributes, createIntent, LayoutAction, type PluginsContext } from '@dxos/app-framework';
6
6
  import { ClientCapabilities } from '@dxos/plugin-client';
7
- import { createExtension, toSignal } from '@dxos/plugin-graph';
7
+ import { createExtension, type Node, ROOT_ID, toSignal } from '@dxos/plugin-graph';
8
8
  import { memoizeQuery } from '@dxos/plugin-space';
9
9
  import { getTypename, parseId, SpaceState } from '@dxos/react-client/echo';
10
10
 
11
- import { AUTOMATION_PLUGIN } from '../meta';
11
+ import { AMBIENT_CHAT_DIALOG, AUTOMATION_PLUGIN } from '../meta';
12
12
 
13
13
  export default (context: PluginsContext) => {
14
14
  const resolve = (typename: string) =>
@@ -46,6 +46,37 @@ export default (context: PluginsContext) => {
46
46
  };
47
47
  },
48
48
  }),
49
+ createExtension({
50
+ id: `${AUTOMATION_PLUGIN}/ambient-chat`,
51
+ filter: (node): node is Node<null> => node.id === ROOT_ID,
52
+ actions: () => [
53
+ {
54
+ id: `${LayoutAction.UpdateDialog._tag}/ambient-chat/open`,
55
+ data: async () => {
56
+ const { dispatchPromise: dispatch } = context.requestCapability(Capabilities.IntentDispatcher);
57
+ await dispatch(
58
+ createIntent(LayoutAction.UpdateDialog, {
59
+ part: 'dialog',
60
+ subject: AMBIENT_CHAT_DIALOG,
61
+ options: {
62
+ state: true,
63
+ blockAlign: 'end',
64
+ },
65
+ }),
66
+ );
67
+ },
68
+ properties: {
69
+ label: ['open ambient chat label', { ns: AUTOMATION_PLUGIN }],
70
+ icon: 'ph--chat-centered-text--regular',
71
+ disposition: 'pin-end',
72
+ keyBinding: {
73
+ macos: 'shift+meta+k',
74
+ windows: 'shift+ctrl+k',
75
+ },
76
+ },
77
+ },
78
+ ],
79
+ }),
49
80
  createExtension({
50
81
  id: `${AUTOMATION_PLUGIN}/automation-for-subject`,
51
82
  resolver: ({ id }) => {
@@ -7,8 +7,8 @@ import React from 'react';
7
7
  import { Capabilities, contributes, createSurface } from '@dxos/app-framework';
8
8
  import { getSpace, isEchoObject, isSpace, type ReactiveEchoObject } from '@dxos/react-client/echo';
9
9
 
10
- import { AutomationPanel, ChatContainer, ServiceRegistry } from '../components';
11
- import { AUTOMATION_PLUGIN } from '../meta';
10
+ import { AmbientChatDialog, AutomationPanel, ChatContainer, ServiceRegistry } from '../components';
11
+ import { AUTOMATION_PLUGIN, AMBIENT_CHAT_DIALOG } from '../meta';
12
12
  import { AIChatType } from '../types';
13
13
 
14
14
  export default () =>
@@ -26,6 +26,12 @@ export default () =>
26
26
  <ServiceRegistry space={isSpace(data.subject) ? data.subject : getSpace(data.subject)!} />
27
27
  ),
28
28
  }),
29
+ createSurface({
30
+ id: AMBIENT_CHAT_DIALOG,
31
+ role: 'dialog',
32
+ filter: (data): data is any => data.component === AMBIENT_CHAT_DIALOG,
33
+ component: () => <AmbientChatDialog />,
34
+ }),
29
35
  createSurface({
30
36
  id: `${AUTOMATION_PLUGIN}/automation`,
31
37
  role: 'complementary--automation',
@@ -0,0 +1,26 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import React from 'react';
6
+
7
+ import { Dialog, Icon, useTranslation } from '@dxos/react-ui';
8
+
9
+ import { AUTOMATION_PLUGIN } from '../../meta';
10
+
11
+ const preventDefault = (event: Event) => event.preventDefault();
12
+
13
+ export const AmbientChatDialog = () => {
14
+ const { t } = useTranslation(AUTOMATION_PLUGIN);
15
+ return (
16
+ <div role='none' className='dx-dialog__overlay bg-transparent pointer-events-none' data-block-align='end'>
17
+ <Dialog.Content onInteractOutside={preventDefault} classNames='pointer-events-auto' inOverlayLayout>
18
+ <Dialog.Title classNames='sr-only'>{t('ambient chat dialog title')}</Dialog.Title>
19
+ <Dialog.Close>
20
+ <Icon icon='ph--x--regular' size={4} />
21
+ </Dialog.Close>
22
+ <h1>Hello</h1>
23
+ </Dialog.Content>
24
+ </div>
25
+ );
26
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export * from './AmbientChatDialog';
@@ -34,10 +34,9 @@ export const Default: Story = {
34
34
  return (
35
35
  <div className='flex flex-col w-[500px] p-4 bg-attention'>
36
36
  <ToggleContainer
37
+ open
37
38
  title={content[selected].title}
38
39
  classNames='p-1 rounded-lg bg-baseSurface border border-neutral-500'
39
- toggle
40
- defaultOpen
41
40
  >
42
41
  <div className='flex w-full overflow-hidden'>
43
42
  <Tabs length={content.length} selected={selected} onSelect={setSelected} />
@@ -0,0 +1,89 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import React, { forwardRef } from 'react';
6
+
7
+ import { Icon, type ThemedClassName } from '@dxos/react-ui';
8
+ import { mx } from '@dxos/react-ui-theme';
9
+
10
+ const numbers = [
11
+ { regular: 'ph--number-circle-zero--thin', active: 'ph--number-circle-zero--duotone' },
12
+ { regular: 'ph--number-circle-one--thin', active: 'ph--number-circle-one--duotone' },
13
+ { regular: 'ph--number-circle-two--thin', active: 'ph--number-circle-two--duotone' },
14
+ { regular: 'ph--number-circle-three--thin', active: 'ph--number-circle-three--duotone' },
15
+ { regular: 'ph--number-circle-four--thin', active: 'ph--number-circle-four--duotone' },
16
+ { regular: 'ph--number-circle-five--thin', active: 'ph--number-circle-five--duotone' },
17
+ { regular: 'ph--number-circle-six--thin', active: 'ph--number-circle-six--duotone' },
18
+ { regular: 'ph--number-circle-seven--thin', active: 'ph--number-circle-seven--duotone' },
19
+ { regular: 'ph--number-circle-eight--thin', active: 'ph--number-circle-eight--duotone' },
20
+ { regular: 'ph--number-circle-nine--thin', active: 'ph--number-circle-nine--duotone' },
21
+ ];
22
+
23
+ export type TabsProps = ThemedClassName<{
24
+ length: number;
25
+ selected?: number;
26
+ onSelect?: (index: number) => void;
27
+ }>;
28
+
29
+ export const Tabs = forwardRef<HTMLDivElement, TabsProps>(
30
+ ({ classNames, length, selected = 0, onSelect }, forwardedRef) => {
31
+ return (
32
+ <div
33
+ ref={forwardedRef}
34
+ className={mx('flex flex-col overflow-hidden outline-none', classNames)}
35
+ tabIndex={-1}
36
+ onKeyDown={(ev) => {
37
+ // TODO(burdon): Focus when open Toggle.
38
+ switch (ev.key) {
39
+ case 'ArrowDown':
40
+ case 'ArrowRight': {
41
+ ev.preventDefault();
42
+ ev.stopPropagation();
43
+ if (selected < length - 1) {
44
+ onSelect?.(selected + 1);
45
+ }
46
+ break;
47
+ }
48
+ case 'ArrowUp':
49
+ case 'ArrowLeft': {
50
+ ev.preventDefault();
51
+ ev.stopPropagation();
52
+ if (selected > 0) {
53
+ onSelect?.(selected - 1);
54
+ }
55
+ break;
56
+ }
57
+
58
+ case 'Enter': {
59
+ ev.preventDefault();
60
+ ev.stopPropagation();
61
+ onSelect?.(selected);
62
+ break;
63
+ }
64
+ }
65
+ }}
66
+ >
67
+ {Array.from({ length }).map((_, i) => {
68
+ const icon = numbers[i + 1];
69
+ return (
70
+ <div
71
+ key={i}
72
+ className={mx(
73
+ 'relative flex w-[24px] h-[28px] justify-center cursor-pointer text-subdued',
74
+ selected === i && '!text-cyan-550 !dark:text-cyan-300',
75
+ )}
76
+ >
77
+ {i < length - 1 && <div className='absolute left-[11.5px] top-[21px] w-[1px] h-[10px] bg-neutral-400' />}
78
+ <Icon
79
+ icon={selected === i ? icon.regular : icon.regular}
80
+ classNames='z-10 !p-0 !w-[24px] !h-[24px] outline-none'
81
+ onClick={() => onSelect?.(i)}
82
+ />
83
+ </div>
84
+ );
85
+ })}
86
+ </div>
87
+ );
88
+ },
89
+ );
@@ -71,7 +71,6 @@ const Render = ({ shrinkX, ...props }: ToggleContainerProps) => {
71
71
  <ToggleContainer
72
72
  {...props}
73
73
  shrinkX={shrinkX}
74
- toggle
75
74
  icon={
76
75
  running ? (
77
76
  <Icon icon={'ph--circle-notch--regular'} classNames='text-subdued ml-2 animate-spin' size={4} />
@@ -4,39 +4,38 @@
4
4
 
5
5
  import React, { type JSX, type PropsWithChildren, useEffect, useState } from 'react';
6
6
 
7
- import { Icon, type ThemedClassName } from '@dxos/react-ui';
7
+ import { Icon, useControlledState, type ThemedClassName } from '@dxos/react-ui';
8
8
  import { mx } from '@dxos/react-ui-theme';
9
9
 
10
10
  export type ToggleContainerProps = ThemedClassName<
11
11
  PropsWithChildren<{
12
12
  title?: string | JSX.Element;
13
13
  icon?: JSX.Element;
14
- toggle?: boolean;
15
- defaultOpen?: boolean;
14
+ open?: boolean;
16
15
  duration?: number;
17
16
  /** Should shrink the width when closed. */
18
17
  shrinkX?: boolean;
18
+ onChangeOpen?: (open: boolean) => void;
19
19
  }>
20
20
  >;
21
21
 
22
- // TODO(burdon): Externalize toggle state.
23
22
  export const ToggleContainer = ({
23
+ classNames,
24
24
  title,
25
25
  icon,
26
- toggle,
27
- defaultOpen,
26
+ open: _open,
28
27
  duration = 400,
29
28
  shrinkX = false,
30
29
  children,
31
- classNames,
30
+ onChangeOpen,
32
31
  }: ToggleContainerProps) => {
33
- const [expand, setExpand] = useState(defaultOpen || !toggle);
34
- const [expandX, setExpandX] = useState(shrinkX ? expand : true);
35
- const [expandY, setExpandY] = useState(expand);
32
+ const [open, setOpen] = useControlledState(_open);
33
+ const [expandX, setExpandX] = useState(shrinkX ? open : true);
34
+ const [expandY, setExpandY] = useState(open);
36
35
 
37
36
  useEffect(() => {
38
37
  let t: NodeJS.Timeout;
39
- if (expand) {
38
+ if (open) {
40
39
  if (shrinkX) {
41
40
  setExpandX(true);
42
41
  }
@@ -56,25 +55,31 @@ export const ToggleContainer = ({
56
55
  }
57
56
 
58
57
  return () => clearTimeout(t);
59
- }, [expand]);
58
+ }, [open]);
59
+
60
+ const handleToggle = () => {
61
+ if (onChangeOpen) {
62
+ onChangeOpen(!open);
63
+ } else {
64
+ setOpen((open) => !open);
65
+ }
66
+ };
60
67
 
61
68
  return (
62
69
  <div className={mx('overflow-hidden', classNames)}>
63
70
  {title && (
64
71
  <div
65
72
  className='flex gap-1 py-1 items-center text-sm text-subdued cursor-pointer select-none'
66
- onClick={toggle ? () => setExpand((open) => !open) : undefined}
73
+ onClick={handleToggle}
67
74
  >
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
- )}
75
+ <div className='flex w-[24px] h-[24px] items-center justify-center'>
76
+ <Icon
77
+ size={4}
78
+ icon={'ph--caret-right--regular'}
79
+ style={{ transitionDuration: `${shrinkX ? duration * 2 : duration}ms` }}
80
+ classNames={['transition transition-transform ease-in-out', open ? 'rotate-90' : 'transform-none']}
81
+ />
82
+ </div>
78
83
  <div className='flex-1 pis-1 pie-1 truncate'>{title}</div>
79
84
  {icon}
80
85
  </div>
@@ -0,0 +1,7 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export * from './StatusLine';
6
+ export * from './Tabbed';
7
+ export * from './ToggleContainer';
@@ -0,0 +1,50 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import '@dxos-theme';
6
+
7
+ import { type StoryObj, type Meta } from '@storybook/react';
8
+
9
+ import { withTheme } from '@dxos/storybook-utils';
10
+
11
+ import { Prompt } from './Prompt';
12
+
13
+ const meta: Meta<typeof Prompt> = {
14
+ title: 'plugins/plugin-automation/Prompt',
15
+ component: Prompt,
16
+ decorators: [withTheme],
17
+ parameters: {
18
+ layout: 'centered',
19
+ },
20
+ };
21
+
22
+ export default meta;
23
+
24
+ type Story = StoryObj<typeof Prompt>;
25
+
26
+ export const Default: Story = {
27
+ args: {
28
+ classNames: 'w-96 p-4 rounded outline outline-gray-200',
29
+ autoFocus: true,
30
+ onEnter: (text) => {
31
+ console.log('onEnter', text);
32
+ },
33
+ onSuggest: (text) => {
34
+ const trimmed = text.trim().toLowerCase();
35
+ if (trimmed.length < 2) {
36
+ return [];
37
+ }
38
+
39
+ const suggestions = [
40
+ 'Create a CRM',
41
+ 'Create a new project',
42
+ 'Find flights to Tokyo',
43
+ "Let's play chess",
44
+ 'Show me Paris on a map',
45
+ ];
46
+
47
+ return suggestions.filter((s) => s.toLowerCase().startsWith(text));
48
+ },
49
+ },
50
+ };
@@ -0,0 +1,36 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import React from 'react';
6
+
7
+ import { type ThemedClassName, useThemeContext } from '@dxos/react-ui';
8
+ import {
9
+ createBasicExtensions,
10
+ createThemeExtensions,
11
+ useTextEditor,
12
+ type UseTextEditorProps,
13
+ } from '@dxos/react-ui-editor';
14
+ import { mx } from '@dxos/react-ui-theme';
15
+
16
+ import { createAutocompleteExtension, type AutocompleteOptions } from './prompt-autocomplete';
17
+
18
+ export type PromptProps = ThemedClassName<AutocompleteOptions & Pick<UseTextEditorProps, 'autoFocus'>>;
19
+
20
+ export const Prompt = ({ classNames, autoFocus, onEnter, onSuggest }: PromptProps) => {
21
+ const { themeMode } = useThemeContext();
22
+ const { parentRef } = useTextEditor({
23
+ autoFocus,
24
+ extensions: [
25
+ createBasicExtensions({
26
+ bracketMatching: false,
27
+ lineWrapping: false,
28
+ placeholder: 'Ask a question...',
29
+ }),
30
+ createThemeExtensions({ themeMode }),
31
+ createAutocompleteExtension({ onEnter, onSuggest }),
32
+ ],
33
+ });
34
+
35
+ return <div ref={parentRef} className={mx(classNames)} />;
36
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export * from './Prompt';
@@ -0,0 +1,200 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { Prec, type Extension } from '@codemirror/state';
6
+ import {
7
+ EditorView,
8
+ Decoration,
9
+ ViewPlugin,
10
+ keymap,
11
+ type DecorationSet,
12
+ type ViewUpdate,
13
+ WidgetType,
14
+ } from '@codemirror/view';
15
+
16
+ export type AutocompleteOptions = {
17
+ /**
18
+ * Callback triggered when Enter is pressed.
19
+ * @param text The current text in the editor
20
+ */
21
+ onEnter?: (text: string) => void;
22
+
23
+ /**
24
+ * Function that returns a list of suggestions based on the current text.
25
+ * @param text The current text before the cursor
26
+ * @returns Array of suggestion strings
27
+ */
28
+ onSuggest: (text: string) => string[];
29
+ };
30
+
31
+ class InlineSuggestionWidget extends WidgetType {
32
+ constructor(private suffix: string) {
33
+ super();
34
+ }
35
+
36
+ override toDOM() {
37
+ const span = document.createElement('span');
38
+ span.textContent = this.suffix;
39
+ span.className = 'cm-inline-suggestion';
40
+ return span;
41
+ }
42
+
43
+ override eq(other: InlineSuggestionWidget) {
44
+ return other.suffix === this.suffix;
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Creates an autocomplete extension that shows inline suggestions.
50
+ * Pressing Tab will complete the suggestion.
51
+ */
52
+ export const createAutocompleteExtension = ({ onEnter, onSuggest }: AutocompleteOptions): Extension => {
53
+ const suggestionPlugin = ViewPlugin.fromClass(
54
+ class {
55
+ _decorations: DecorationSet;
56
+ _currentSuggestion: string | null = null;
57
+
58
+ constructor(view: EditorView) {
59
+ this._decorations = this.computeDecorations(view);
60
+ }
61
+
62
+ update(update: ViewUpdate) {
63
+ if (update.docChanged || update.selectionSet) {
64
+ this._decorations = this.computeDecorations(update.view);
65
+ }
66
+ }
67
+
68
+ private computeDecorations(view: EditorView): DecorationSet {
69
+ const text = view.state.doc.toString();
70
+ const suggestions = onSuggest(text);
71
+ if (!suggestions.length) {
72
+ this._currentSuggestion = null;
73
+ return Decoration.none;
74
+ }
75
+
76
+ // Get the first suggestion.
77
+ this._currentSuggestion = suggestions[0];
78
+ const suffix = this._currentSuggestion.slice(text.length);
79
+ if (!suffix) {
80
+ return Decoration.none;
81
+ }
82
+
83
+ // Always show ghost text at the end of the document.
84
+ return Decoration.set([
85
+ Decoration.widget({
86
+ widget: new InlineSuggestionWidget(suffix),
87
+ side: 1,
88
+ }).range(view.state.doc.length),
89
+ ]);
90
+ }
91
+
92
+ completeSuggestion(view: EditorView): boolean {
93
+ if (!this._currentSuggestion) {
94
+ return false;
95
+ }
96
+
97
+ const text = view.state.doc.toString();
98
+ const suffix = this._currentSuggestion.slice(text.length);
99
+ if (!suffix) {
100
+ return false;
101
+ }
102
+
103
+ view.dispatch({
104
+ changes: {
105
+ from: view.state.doc.length,
106
+ insert: suffix,
107
+ },
108
+ selection: {
109
+ anchor: view.state.doc.length + suffix.length,
110
+ },
111
+ });
112
+
113
+ return true;
114
+ }
115
+ },
116
+ {
117
+ decorations: (v) => v._decorations,
118
+ },
119
+ );
120
+
121
+ return [
122
+ suggestionPlugin,
123
+ EditorView.theme({
124
+ '.cm-inline-suggestion': {
125
+ opacity: 0.4,
126
+ },
127
+ }),
128
+
129
+ // Accept the current suggestion.
130
+ Prec.highest(
131
+ keymap.of([
132
+ {
133
+ key: 'Tab',
134
+ run: (view) => {
135
+ const plugin = view.plugin(suggestionPlugin);
136
+ return plugin?.completeSuggestion(view) ?? false;
137
+ },
138
+ },
139
+ {
140
+ key: 'ArrowRight',
141
+ run: (view) => {
142
+ // Only complete if cursor is at the end
143
+ if (view.state.selection.main.head !== view.state.doc.length) {
144
+ return false;
145
+ }
146
+
147
+ const plugin = view.plugin(suggestionPlugin);
148
+ return plugin?.completeSuggestion(view) ?? false;
149
+ },
150
+ },
151
+ {
152
+ key: 'Enter',
153
+ preventDefault: true,
154
+ run: (view) => {
155
+ const text = view.state.doc.toString();
156
+ if (onEnter) {
157
+ onEnter(text);
158
+ // Clear the document after calling onEnter
159
+ view.dispatch({
160
+ changes: {
161
+ from: 0,
162
+ to: view.state.doc.length,
163
+ insert: '',
164
+ },
165
+ });
166
+ return true;
167
+ }
168
+ return false;
169
+ },
170
+ },
171
+ {
172
+ key: 'Shift-Enter',
173
+ run: (view) => {
174
+ view.dispatch({
175
+ changes: {
176
+ from: view.state.selection.main.head,
177
+ insert: '\n',
178
+ },
179
+ });
180
+ return true;
181
+ },
182
+ },
183
+ {
184
+ key: 'Escape',
185
+ run: (view) => {
186
+ // Clear the entire document.
187
+ view.dispatch({
188
+ changes: {
189
+ from: 0,
190
+ to: view.state.doc.length,
191
+ insert: '',
192
+ },
193
+ });
194
+ return true;
195
+ },
196
+ },
197
+ ]),
198
+ ),
199
+ ];
200
+ };
@@ -28,7 +28,7 @@ const template = [
28
28
  '{input}',
29
29
  ].join('\n');
30
30
 
31
- const DefaultStory = () => {
31
+ const Render = () => {
32
32
  const client = useClient();
33
33
  const [chain] = useState(() => {
34
34
  const space = client.spaces.default;
@@ -49,8 +49,8 @@ const DefaultStory = () => {
49
49
  export const Default = {};
50
50
 
51
51
  const meta: Meta = {
52
- title: 'plugins/plugin-automation/PromptTemplate',
53
- render: DefaultStory,
52
+ title: 'plugins/plugin-automation/PromptEditor',
53
+ render: Render,
54
54
  decorators: [
55
55
  withClientProvider({ createIdentity: true, createSpace: true, types: [ChainType, ChainPromptType] }),
56
56
  withLayout({ fullscreen: true, classNames: 'flex justify-center m-2' }),
@@ -159,7 +159,7 @@ const TEST_MESSAGES: Message[] = [
159
159
 
160
160
  export const Default: Story = {
161
161
  args: {
162
- debug: true,
162
+ // debug: true,
163
163
  messages: TEST_MESSAGES,
164
164
  },
165
165
  };
@@ -170,6 +170,13 @@ export const Input: Story = {
170
170
  },
171
171
  };
172
172
 
173
+ export const Collapse: Story = {
174
+ args: {
175
+ collapse: true,
176
+ messages: TEST_MESSAGES,
177
+ },
178
+ };
179
+
173
180
  export const Incremental: Story = {
174
181
  render: () => {
175
182
  const [messages, setMessages] = useState<Message[]>([]);
@@ -6,10 +6,10 @@ import React, { type KeyboardEventHandler, useCallback, useMemo, useRef, useStat
6
6
 
7
7
  import { type Message } from '@dxos/artifact';
8
8
  import { IconButton, Input, useTranslation } from '@dxos/react-ui';
9
+ import { ScrollContainer, type ScrollController } from '@dxos/react-ui-components';
9
10
  import { Spinner } from '@dxos/react-ui-sfx';
10
11
  import { mx } from '@dxos/react-ui-theme';
11
12
 
12
- import { ScrollContainer, type ScrollController } from './ScrollContainer';
13
13
  import { ThreadMessage, type ThreadMessageProps } from './ThreadMessage';
14
14
  import { AUTOMATION_PLUGIN } from '../../meta';
15
15