@dxos/react-ui-chat 0.8.4-main.84f28bd

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 (43) hide show
  1. package/LICENSE +8 -0
  2. package/README.md +1 -0
  3. package/dist/lib/browser/index.mjs +480 -0
  4. package/dist/lib/browser/index.mjs.map +7 -0
  5. package/dist/lib/browser/meta.json +1 -0
  6. package/dist/lib/node-esm/index.mjs +482 -0
  7. package/dist/lib/node-esm/index.mjs.map +7 -0
  8. package/dist/lib/node-esm/meta.json +1 -0
  9. package/dist/types/src/components/ChatDialog/ChatDialog.d.ts +34 -0
  10. package/dist/types/src/components/ChatDialog/ChatDialog.d.ts.map +1 -0
  11. package/dist/types/src/components/ChatDialog/ChatDialog.stories.d.ts +8 -0
  12. package/dist/types/src/components/ChatDialog/ChatDialog.stories.d.ts.map +1 -0
  13. package/dist/types/src/components/ChatDialog/index.d.ts +2 -0
  14. package/dist/types/src/components/ChatDialog/index.d.ts.map +1 -0
  15. package/dist/types/src/components/ChatEditor/ChatEditor.d.ts +21 -0
  16. package/dist/types/src/components/ChatEditor/ChatEditor.d.ts.map +1 -0
  17. package/dist/types/src/components/ChatEditor/ChatEditor.stories.d.ts +10 -0
  18. package/dist/types/src/components/ChatEditor/ChatEditor.stories.d.ts.map +1 -0
  19. package/dist/types/src/components/ChatEditor/autocomplete.d.ts +25 -0
  20. package/dist/types/src/components/ChatEditor/autocomplete.d.ts.map +1 -0
  21. package/dist/types/src/components/ChatEditor/index.d.ts +3 -0
  22. package/dist/types/src/components/ChatEditor/index.d.ts.map +1 -0
  23. package/dist/types/src/components/ChatEditor/references.d.ts +30 -0
  24. package/dist/types/src/components/ChatEditor/references.d.ts.map +1 -0
  25. package/dist/types/src/components/index.d.ts +3 -0
  26. package/dist/types/src/components/index.d.ts.map +1 -0
  27. package/dist/types/src/index.d.ts +2 -0
  28. package/dist/types/src/index.d.ts.map +1 -0
  29. package/dist/types/src/translations.d.ts +40 -0
  30. package/dist/types/src/translations.d.ts.map +1 -0
  31. package/dist/types/tsconfig.tsbuildinfo +1 -0
  32. package/package.json +74 -0
  33. package/src/components/ChatDialog/ChatDialog.stories.tsx +65 -0
  34. package/src/components/ChatDialog/ChatDialog.tsx +193 -0
  35. package/src/components/ChatDialog/index.ts +5 -0
  36. package/src/components/ChatEditor/ChatEditor.stories.tsx +96 -0
  37. package/src/components/ChatEditor/ChatEditor.tsx +91 -0
  38. package/src/components/ChatEditor/autocomplete.ts +218 -0
  39. package/src/components/ChatEditor/index.ts +7 -0
  40. package/src/components/ChatEditor/references.ts +175 -0
  41. package/src/components/index.ts +6 -0
  42. package/src/index.ts +5 -0
  43. package/src/translations.ts +54 -0
package/package.json ADDED
@@ -0,0 +1,74 @@
1
+ {
2
+ "name": "@dxos/react-ui-chat",
3
+ "version": "0.8.4-main.84f28bd",
4
+ "description": "Chat components.",
5
+ "homepage": "https://dxos.org",
6
+ "bugs": "https://github.com/dxos/dxos/issues",
7
+ "license": "MIT",
8
+ "author": "DXOS.org",
9
+ "type": "module",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/types/src/index.d.ts",
13
+ "browser": "./dist/lib/browser/index.mjs",
14
+ "node": "./dist/lib/node-esm/index.mjs"
15
+ }
16
+ },
17
+ "types": "dist/types/src/index.d.ts",
18
+ "typesVersions": {
19
+ "*": {}
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "src"
24
+ ],
25
+ "dependencies": {
26
+ "@codemirror/autocomplete": "^6.18.1",
27
+ "@codemirror/language": "^6.10.3",
28
+ "@codemirror/search": "^6.5.6",
29
+ "@codemirror/state": "^6.4.1",
30
+ "@codemirror/view": "^6.34.1",
31
+ "@preact-signals/safe-react": "^0.9.0",
32
+ "@preact/signals-core": "^1.9.0",
33
+ "@radix-ui/react-accordion": "1.2.3",
34
+ "@radix-ui/react-context": "1.1.1",
35
+ "@dxos/async": "0.8.4-main.84f28bd",
36
+ "@dxos/debug": "0.8.4-main.84f28bd",
37
+ "@dxos/echo-schema": "0.8.4-main.84f28bd",
38
+ "@dxos/invariant": "0.8.4-main.84f28bd",
39
+ "@dxos/live-object": "0.8.4-main.84f28bd",
40
+ "@dxos/log": "0.8.4-main.84f28bd",
41
+ "@dxos/react-ui": "0.8.4-main.84f28bd",
42
+ "@dxos/react-ui-components": "0.8.4-main.84f28bd",
43
+ "@dxos/react-ui-dnd": "0.8.4-main.84f28bd",
44
+ "@dxos/react-ui-sfx": "0.8.4-main.84f28bd",
45
+ "@dxos/react-ui-editor": "0.8.4-main.84f28bd",
46
+ "@dxos/react-ui-syntax-highlighter": "0.8.4-main.84f28bd",
47
+ "@dxos/react-ui-text-tooltip": "0.8.4-main.84f28bd",
48
+ "@dxos/react-ui-theme": "0.8.4-main.84f28bd",
49
+ "@dxos/react-ui-types": "0.8.4-main.84f28bd",
50
+ "@dxos/util": "0.8.4-main.84f28bd"
51
+ },
52
+ "devDependencies": {
53
+ "@types/react": "~18.2.0",
54
+ "@types/react-dom": "~18.2.0",
55
+ "effect": "3.14.21",
56
+ "react": "~18.2.0",
57
+ "react-dom": "~18.2.0",
58
+ "vite": "5.4.7",
59
+ "@dxos/echo": "0.8.4-main.84f28bd",
60
+ "@dxos/random": "0.8.4-main.84f28bd",
61
+ "@dxos/react-client": "0.8.4-main.84f28bd",
62
+ "@dxos/storybook-utils": "0.8.4-main.84f28bd"
63
+ },
64
+ "peerDependencies": {
65
+ "effect": "3.13.3",
66
+ "react": "~18.2.0",
67
+ "react-dom": "~18.2.0",
68
+ "@dxos/react-ui": "0.8.4-main.84f28bd",
69
+ "@dxos/react-ui-theme": "0.8.4-main.84f28bd"
70
+ },
71
+ "publishConfig": {
72
+ "access": "public"
73
+ }
74
+ }
@@ -0,0 +1,65 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import '@dxos-theme';
6
+
7
+ import { type StoryObj, type Meta } from '@storybook/react-vite';
8
+ import React, { useState } from 'react';
9
+
10
+ import { Input, Toolbar } from '@dxos/react-ui';
11
+ import { withTheme, withLayout } from '@dxos/storybook-utils';
12
+
13
+ import { ChatDialog } from './ChatDialog';
14
+ import { translations } from '../../translations';
15
+
16
+ const items = Array.from({ length: 100 }, (_, i) => `Item ${i}`);
17
+
18
+ const meta: Meta<typeof ChatDialog.Root> = {
19
+ title: 'ui/react-ui-chat/ChatDialog',
20
+ component: ChatDialog.Root,
21
+ render: (args) => {
22
+ const [open, setOpen] = useState(true);
23
+ const [expanded, setExpanded] = useState(true);
24
+ return (
25
+ <>
26
+ <Toolbar.Root>
27
+ <Toolbar.Button onClick={() => setOpen((open) => !open)}>Open</Toolbar.Button>
28
+ <Toolbar.Button onClick={() => setExpanded((expanded) => !expanded)}>Expand</Toolbar.Button>
29
+ </Toolbar.Root>
30
+
31
+ <ChatDialog.Root
32
+ {...args}
33
+ open={open}
34
+ onOpenChange={setOpen}
35
+ expanded={expanded}
36
+ onExpandedChange={setExpanded}
37
+ >
38
+ <ChatDialog.Header title='Chat' />
39
+ <ChatDialog.Content>
40
+ {items.map((item) => (
41
+ <div key={item} className='pis-4 pbe-1'>
42
+ {item}
43
+ </div>
44
+ ))}
45
+ </ChatDialog.Content>
46
+ <ChatDialog.Footer classNames='px-2 items-center'>
47
+ <Input.Root>
48
+ <Input.TextInput classNames='border-none' placeholder='Test' />
49
+ </Input.Root>
50
+ </ChatDialog.Footer>
51
+ </ChatDialog.Root>
52
+ </>
53
+ );
54
+ },
55
+ decorators: [withTheme, withLayout({ fullscreen: true, classNames: 'flex flex-col' })],
56
+ parameters: {
57
+ translations,
58
+ },
59
+ };
60
+
61
+ export default meta;
62
+
63
+ type Story = StoryObj<typeof ChatDialog.Root>;
64
+
65
+ export const Default: Story = {};
@@ -0,0 +1,193 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { createContext } from '@radix-ui/react-context';
6
+ import React, { type Dispatch, type PropsWithChildren, type SetStateAction, useEffect, useState } from 'react';
7
+
8
+ import { Dialog, Icon, IconButton, useControlledState, type ThemedClassName } from '@dxos/react-ui';
9
+ import { resizeAttributes, ResizeHandle, type Size, sizeStyle } from '@dxos/react-ui-dnd';
10
+ import { mx } from '@dxos/react-ui-theme';
11
+
12
+ const preventDefault = (event: Event) => event.preventDefault();
13
+
14
+ // TODO(burdon): Factor out.
15
+ const Endcap = ({ children }: PropsWithChildren) => {
16
+ return (
17
+ <div className='grid w-[var(--rail-action)] h-[var(--rail-action)] items-center justify-center'>{children}</div>
18
+ );
19
+ };
20
+
21
+ //
22
+ // Context
23
+ //
24
+
25
+ type ChatDialogContextValue = {
26
+ open: boolean;
27
+ setOpen: Dispatch<SetStateAction<boolean>>;
28
+ expanded: boolean;
29
+ setExpanded: Dispatch<SetStateAction<boolean>>;
30
+ size: Size;
31
+ setSize: Dispatch<SetStateAction<Size>>;
32
+ };
33
+
34
+ const [ChatDialogContextProvider, useChatDialogContext] = createContext<ChatDialogContextValue>('ChatDialog');
35
+
36
+ //
37
+ // Root
38
+ //
39
+
40
+ type ChatDialogRootProps = PropsWithChildren<{
41
+ open?: boolean;
42
+ expanded?: boolean;
43
+ onOpenChange?: (open: boolean) => void;
44
+ onExpandedChange?: (expanded: boolean) => void;
45
+ onEscape?: () => void;
46
+ }>;
47
+
48
+ const ChatDialogRoot = ({
49
+ children,
50
+ open: _open = false,
51
+ expanded: _expanded = false,
52
+ onOpenChange,
53
+ onExpandedChange,
54
+ onEscape,
55
+ }: ChatDialogRootProps) => {
56
+ const [size, setSize] = useState<Size>('min-content');
57
+ const [open, setOpen] = useControlledState<boolean>(_open, onOpenChange);
58
+ const [expanded, setExpanded] = useControlledState<boolean>(_expanded, onExpandedChange);
59
+
60
+ // NOTE: We set the min size to 5rem (80px), and the header and prompt bar to 40px (i.e., the rail-size) each.
61
+ // The dialog has no vertical padding and has box-content so that when closed it collapses to the size of the header and prompt bar.
62
+ return (
63
+ <ChatDialogContextProvider
64
+ open={open}
65
+ setOpen={setOpen}
66
+ expanded={expanded}
67
+ setExpanded={setExpanded}
68
+ size={size}
69
+ setSize={setSize}
70
+ >
71
+ <Dialog.Root modal={false} open={open} onOpenChange={setOpen}>
72
+ <div role='none' className='dx-dialog__overlay bg-transparent pointer-events-none' data-block-align='end'>
73
+ <Dialog.Content
74
+ inOverlayLayout
75
+ classNames={[
76
+ 'grid grid-rows-[var(--rail-action)_1fr_min-content] p-0 overflow-hidden',
77
+ 'box-content md:is-[35rem] md:max-is-none pointer-events-auto',
78
+ ]}
79
+ onEscapeKeyDown={onEscape}
80
+ onInteractOutside={preventDefault}
81
+ >
82
+ {children}
83
+ </Dialog.Content>
84
+ </div>
85
+ </Dialog.Root>
86
+ </ChatDialogContextProvider>
87
+ );
88
+ };
89
+
90
+ ChatDialogRoot.displayName = 'ChatDialog.Root';
91
+
92
+ //
93
+ // Header
94
+ //
95
+
96
+ type ChatDialogHeaderProps = ThemedClassName<{
97
+ title?: string;
98
+ }>;
99
+
100
+ const ChatDialogHeader = ({ classNames, title }: ChatDialogHeaderProps) => {
101
+ const { expanded, setExpanded } = useChatDialogContext(ChatDialogHeader.displayName);
102
+
103
+ return (
104
+ <div className={mx('grid grid-cols-[var(--rail-action)_1fr_min-content] items-center overflow-hidden', classNames)}>
105
+ <Endcap>
106
+ <Dialog.Close>
107
+ <Icon icon='ph--x--regular' />
108
+ </Dialog.Close>
109
+ </Endcap>
110
+ <Dialog.Title
111
+ classNames='flex w-full justify-center text-sm text-subdued select-none cursor-pointer'
112
+ onClick={() => setExpanded((expanded) => !expanded)}
113
+ >
114
+ {title}
115
+ </Dialog.Title>
116
+ <Endcap>
117
+ <IconButton
118
+ variant='ghost'
119
+ icon='ph--caret-up--regular'
120
+ iconOnly
121
+ classNames={mx('!p-1 [&>svg]:transition [&>svg]:duration-200', expanded && '[&>svg]:rotate-180')}
122
+ label={expanded ? 'Close' : 'Open'}
123
+ onClick={() => setExpanded((expanded) => !expanded)}
124
+ />
125
+ </Endcap>
126
+ </div>
127
+ );
128
+ };
129
+
130
+ ChatDialogHeader.displayName = 'ChatDialog.Header';
131
+
132
+ //
133
+ // Content
134
+ //
135
+
136
+ type ChatDialogContentProps = ThemedClassName<PropsWithChildren>;
137
+
138
+ const ChatDialogContent = ({ children, classNames }: ChatDialogContentProps) => {
139
+ const { expanded, size, setSize } = useChatDialogContext(ChatDialogContent.displayName);
140
+ useEffect(() => {
141
+ setSize(expanded ? 'min-content' : 0);
142
+ }, [expanded]);
143
+
144
+ return (
145
+ <div
146
+ className={mx(
147
+ 'flex flex-col overflow-y-auto border-t border-b border-subduedSeparator',
148
+ 'transition-[block-size] ease-in-out duration-0 [&:not([data-dx-resizing="true"])]:duration-200',
149
+ classNames,
150
+ )}
151
+ style={{
152
+ ...sizeStyle(size, 'vertical', true),
153
+ maxBlockSize: 'calc(100dvh - env(safe-area-inset-bottom) - env(safe-area-inset-top) - 14rem)',
154
+ }}
155
+ {...resizeAttributes}
156
+ >
157
+ <ResizeHandle
158
+ side='block-start'
159
+ iconPosition='center'
160
+ defaultSize='min-content'
161
+ fallbackSize={0}
162
+ minSize={0}
163
+ onSizeChange={setSize}
164
+ />
165
+ {children}
166
+ </div>
167
+ );
168
+ };
169
+
170
+ ChatDialogContent.displayName = 'ChatDialog.Content';
171
+
172
+ //
173
+ // Footer
174
+ //
175
+
176
+ type ChatDialogFooterProps = ThemedClassName<PropsWithChildren>;
177
+
178
+ const ChatDialogFooter = ({ children, classNames }: ChatDialogFooterProps) => {
179
+ return <div className={mx(classNames)}>{children}</div>;
180
+ };
181
+
182
+ ChatDialogFooter.displayName = 'ChatDialog.Footer';
183
+
184
+ //
185
+ // ChatDialog
186
+ //
187
+
188
+ export const ChatDialog = {
189
+ Root: ChatDialogRoot,
190
+ Header: ChatDialogHeader,
191
+ Content: ChatDialogContent,
192
+ Footer: ChatDialogFooter,
193
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export * from './ChatDialog';
@@ -0,0 +1,96 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import '@dxos-theme';
6
+
7
+ import { type StoryObj, type Meta } from '@storybook/react-vite';
8
+
9
+ import { log } from '@dxos/log';
10
+ import { withTheme } from '@dxos/storybook-utils';
11
+
12
+ import { ChatEditor } from './ChatEditor';
13
+ import { type ReferenceData } from './references';
14
+ import { translations } from '../../translations';
15
+
16
+ const meta: Meta<typeof ChatEditor> = {
17
+ title: 'ui/react-ui-chat/ChatEditor',
18
+ component: ChatEditor,
19
+ decorators: [withTheme],
20
+ parameters: {
21
+ layout: 'centered',
22
+ translations,
23
+ },
24
+ };
25
+
26
+ export default meta;
27
+
28
+ const classNames = 'w-[20rem] p-2 border border-separator';
29
+
30
+ type Story = StoryObj<typeof ChatEditor>;
31
+
32
+ export const Default: Story = {
33
+ args: {
34
+ classNames,
35
+ },
36
+ };
37
+
38
+ export const WithSuggestions: Story = {
39
+ args: {
40
+ classNames,
41
+ onSubmit: (text) => {
42
+ log('onSubmit', { text });
43
+ },
44
+ onSuggest: (text) => {
45
+ const trimmed = text.trim().toLowerCase();
46
+ if (trimmed.length < 2) {
47
+ return [];
48
+ }
49
+
50
+ const suggestions = [
51
+ 'Create a kanban board',
52
+ 'Create a new project',
53
+ 'Find flights to Tokyo',
54
+ "Let's play chess",
55
+ 'Show me Paris on a map',
56
+ ];
57
+
58
+ return suggestions.filter((s) => s.toLowerCase().startsWith(text));
59
+ },
60
+ },
61
+ };
62
+
63
+ const references: ReferenceData[] = [
64
+ {
65
+ uri: 'dxn:echo:@:AAAAAAAA',
66
+ label: 'Meeting Notes',
67
+ },
68
+ {
69
+ uri: 'dxn:echo:@:BBBBBBBB',
70
+ label: 'Project Plan',
71
+ },
72
+ {
73
+ uri: 'dxn:echo:@:CCCCCCCC',
74
+ label: 'Meeting Plan',
75
+ },
76
+ ];
77
+
78
+ export const WithReferences: Story = {
79
+ args: {
80
+ classNames,
81
+ references: {
82
+ provider: {
83
+ getReferences: async ({ query }) => {
84
+ const res = references.filter((i) => i.label.toLowerCase().startsWith(query.toLowerCase()));
85
+ log('getReferences', { query, res });
86
+ return res;
87
+ },
88
+ resolveReference: async ({ uri }) => {
89
+ const res = references.find((i) => i.uri === uri);
90
+ log('resolveReference', { uri, res });
91
+ return res ?? null;
92
+ },
93
+ },
94
+ },
95
+ },
96
+ };
@@ -0,0 +1,91 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { type Extension } from '@codemirror/state';
6
+ import React, { forwardRef, useImperativeHandle } from 'react';
7
+
8
+ import { type ThemedClassName, useThemeContext } from '@dxos/react-ui';
9
+ import {
10
+ type BasicExtensionsOptions,
11
+ type UseTextEditorProps,
12
+ createBasicExtensions,
13
+ createThemeExtensions,
14
+ useTextEditor,
15
+ } from '@dxos/react-ui-editor';
16
+ import { mx } from '@dxos/react-ui-theme';
17
+ import { isNonNullable } from '@dxos/util';
18
+
19
+ import { autocomplete, type AutocompleteOptions } from './autocomplete';
20
+ import { references as referencesExtension, type ReferencesOptions } from './references';
21
+
22
+ // TODO(burdon): Handle object references.
23
+
24
+ export interface ChatEditorController {
25
+ focus(): void;
26
+ setText(text: string): void;
27
+ }
28
+
29
+ export type ChatEditorProps = ThemedClassName<
30
+ {
31
+ extensions?: Extension;
32
+ references?: ReferencesOptions;
33
+ } & AutocompleteOptions &
34
+ Pick<UseTextEditorProps, 'autoFocus'> &
35
+ Pick<BasicExtensionsOptions, 'lineWrapping' | 'placeholder'>
36
+ >;
37
+
38
+ export const ChatEditor = forwardRef<ChatEditorController, ChatEditorProps>(
39
+ (
40
+ { classNames, extensions, references, autoFocus, lineWrapping = false, placeholder, onSubmit, onSuggest, onCancel },
41
+ forwardRef,
42
+ ) => {
43
+ const { themeMode } = useThemeContext();
44
+ const { parentRef, view } = useTextEditor(
45
+ {
46
+ debug: true,
47
+ autoFocus,
48
+ extensions: [
49
+ createThemeExtensions({ themeMode }),
50
+ autocomplete({ onSubmit, onSuggest, onCancel }),
51
+ references ? referencesExtension({ provider: references.provider }) : [],
52
+ createBasicExtensions({
53
+ bracketMatching: false,
54
+ lineWrapping,
55
+ placeholder,
56
+ }),
57
+ extensions,
58
+ ].filter(isNonNullable),
59
+ },
60
+ [themeMode, extensions, onSubmit, onSuggest],
61
+ );
62
+
63
+ // Expose editor view.
64
+ useImperativeHandle(
65
+ forwardRef,
66
+ () => {
67
+ return {
68
+ focus: () => {
69
+ view?.focus();
70
+ },
71
+ setText: (text: string) => {
72
+ view?.dispatch({
73
+ changes: {
74
+ from: 0,
75
+ to: view.state.doc.length,
76
+ insert: text,
77
+ },
78
+ selection: {
79
+ anchor: text.length,
80
+ head: text.length,
81
+ },
82
+ });
83
+ },
84
+ };
85
+ },
86
+ [view, onSubmit],
87
+ );
88
+
89
+ return <div ref={parentRef} className={mx('is-full', classNames)} />;
90
+ },
91
+ );