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

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 (52) hide show
  1. package/LICENSE +102 -5
  2. package/dist/lib/browser/index.mjs +219 -340
  3. package/dist/lib/browser/index.mjs.map +4 -4
  4. package/dist/lib/browser/meta.json +1 -1
  5. package/dist/lib/browser/translations.mjs +14 -0
  6. package/dist/lib/browser/translations.mjs.map +7 -0
  7. package/dist/lib/node-esm/index.mjs +219 -340
  8. package/dist/lib/node-esm/index.mjs.map +4 -4
  9. package/dist/lib/node-esm/meta.json +1 -1
  10. package/dist/lib/node-esm/translations.mjs +16 -0
  11. package/dist/lib/node-esm/translations.mjs.map +7 -0
  12. package/dist/types/src/components/ChatDialog/ChatDialog.d.ts +17 -1
  13. package/dist/types/src/components/ChatDialog/ChatDialog.d.ts.map +1 -1
  14. package/dist/types/src/components/ChatDialog/ChatDialog.stories.d.ts +35 -5
  15. package/dist/types/src/components/ChatDialog/ChatDialog.stories.d.ts.map +1 -1
  16. package/dist/types/src/components/ChatEditor/ChatEditor.d.ts +7 -8
  17. package/dist/types/src/components/ChatEditor/ChatEditor.d.ts.map +1 -1
  18. package/dist/types/src/components/ChatEditor/ChatEditor.stories.d.ts +20 -5
  19. package/dist/types/src/components/ChatEditor/ChatEditor.stories.d.ts.map +1 -1
  20. package/dist/types/src/components/ChatEditor/references.d.ts +4 -0
  21. package/dist/types/src/components/ChatEditor/references.d.ts.map +1 -1
  22. package/dist/types/src/components/ChatStatus/ChatStatus.d.ts +40 -0
  23. package/dist/types/src/components/ChatStatus/ChatStatus.d.ts.map +1 -0
  24. package/dist/types/src/components/ChatStatus/ChatStatus.stories.d.ts +27 -0
  25. package/dist/types/src/components/ChatStatus/ChatStatus.stories.d.ts.map +1 -0
  26. package/dist/types/src/components/ChatStatus/format.d.ts +7 -0
  27. package/dist/types/src/components/ChatStatus/format.d.ts.map +1 -0
  28. package/dist/types/src/components/ChatStatus/format.test.d.ts +2 -0
  29. package/dist/types/src/components/ChatStatus/format.test.d.ts.map +1 -0
  30. package/dist/types/src/components/ChatStatus/index.d.ts +3 -0
  31. package/dist/types/src/components/ChatStatus/index.d.ts.map +1 -0
  32. package/dist/types/src/components/index.d.ts +1 -0
  33. package/dist/types/src/components/index.d.ts.map +1 -1
  34. package/dist/types/src/translations.d.ts +2 -35
  35. package/dist/types/src/translations.d.ts.map +1 -1
  36. package/dist/types/tsconfig.tsbuildinfo +1 -1
  37. package/package.json +53 -43
  38. package/src/components/ChatDialog/ChatDialog.stories.tsx +9 -10
  39. package/src/components/ChatDialog/ChatDialog.tsx +30 -20
  40. package/src/components/ChatEditor/ChatEditor.stories.tsx +25 -24
  41. package/src/components/ChatEditor/ChatEditor.tsx +35 -67
  42. package/src/components/ChatEditor/references.ts +19 -13
  43. package/src/components/ChatStatus/ChatStatus.stories.tsx +130 -0
  44. package/src/components/ChatStatus/ChatStatus.tsx +183 -0
  45. package/src/components/ChatStatus/format.test.ts +48 -0
  46. package/src/components/ChatStatus/format.ts +27 -0
  47. package/src/components/ChatStatus/index.ts +6 -0
  48. package/src/components/index.ts +1 -0
  49. package/src/translations.ts +2 -41
  50. package/dist/types/src/components/ChatEditor/autocomplete.d.ts +0 -25
  51. package/dist/types/src/components/ChatEditor/autocomplete.d.ts.map +0 -1
  52. package/src/components/ChatEditor/autocomplete.ts +0 -218
@@ -1,121 +1,97 @@
1
1
  // src/components/ChatDialog/ChatDialog.tsx
2
- import { useSignals as _useSignals } from "@preact-signals/safe-react/tracking";
3
2
  import { createContext } from "@radix-ui/react-context";
4
3
  import React, { useEffect, useState } from "react";
5
4
  import { Dialog, Icon, IconButton, useControlledState } from "@dxos/react-ui";
6
- import { resizeAttributes, ResizeHandle, sizeStyle } from "@dxos/react-ui-dnd";
7
- import { mx } from "@dxos/react-ui-theme";
5
+ import { ResizeHandle, resizeAttributes, sizeStyle } from "@dxos/react-ui-dnd";
6
+ import { mx } from "@dxos/ui-theme";
8
7
  var preventDefault = (event) => event.preventDefault();
9
8
  var Endcap = ({ children }) => {
10
- var _effect = _useSignals();
11
- try {
12
- return /* @__PURE__ */ React.createElement("div", {
13
- className: "grid w-[var(--rail-action)] h-[var(--rail-action)] items-center justify-center"
14
- }, children);
15
- } finally {
16
- _effect.f();
17
- }
9
+ return /* @__PURE__ */ React.createElement("div", {
10
+ className: "grid w-[var(--dx-rail-action)] h-[var(--dx-rail-action)] items-center justify-center"
11
+ }, children);
18
12
  };
19
13
  var [ChatDialogContextProvider, useChatDialogContext] = createContext("ChatDialog");
20
- var ChatDialogRoot = ({ children, open: _open = false, expanded: _expanded = false, onOpenChange, onExpandedChange, onEscape }) => {
21
- var _effect = _useSignals();
22
- try {
23
- const [size, setSize] = useState("min-content");
24
- const [open, setOpen] = useControlledState(_open, onOpenChange);
25
- const [expanded, setExpanded] = useControlledState(_expanded, onExpandedChange);
26
- return /* @__PURE__ */ React.createElement(ChatDialogContextProvider, {
27
- open,
28
- setOpen,
29
- expanded,
30
- setExpanded,
31
- size,
32
- setSize
33
- }, /* @__PURE__ */ React.createElement(Dialog.Root, {
34
- modal: false,
35
- open,
36
- onOpenChange: setOpen
37
- }, /* @__PURE__ */ React.createElement("div", {
38
- role: "none",
39
- className: "dx-dialog__overlay bg-transparent pointer-events-none",
40
- "data-block-align": "end"
41
- }, /* @__PURE__ */ React.createElement(Dialog.Content, {
42
- inOverlayLayout: true,
43
- classNames: [
44
- "grid grid-rows-[var(--rail-action)_1fr_min-content] p-0 overflow-hidden",
45
- "box-content md:is-[35rem] md:max-is-none pointer-events-auto"
46
- ],
47
- onEscapeKeyDown: onEscape,
48
- onInteractOutside: preventDefault
49
- }, children))));
50
- } finally {
51
- _effect.f();
52
- }
14
+ var ChatDialogRoot = ({ children, open: openProp = false, expanded: expandedProp = false, onOpenChange, onExpandedChange, onEscape }) => {
15
+ const [size, setSize] = useState("min-content");
16
+ const [open, setOpen] = useControlledState(openProp, onOpenChange);
17
+ const [expanded, setExpanded] = useControlledState(expandedProp, onExpandedChange);
18
+ return /* @__PURE__ */ React.createElement(ChatDialogContextProvider, {
19
+ open,
20
+ setOpen,
21
+ expanded,
22
+ setExpanded,
23
+ size,
24
+ setSize
25
+ }, /* @__PURE__ */ React.createElement(Dialog.Root, {
26
+ modal: false,
27
+ open,
28
+ onOpenChange: setOpen
29
+ }, /* @__PURE__ */ React.createElement("div", {
30
+ className: "dx-dialog__overlay bg-transparent pointer-events-none",
31
+ "data-block-align": "end"
32
+ }, /* @__PURE__ */ React.createElement(Dialog.Content, {
33
+ size: "md",
34
+ inOverlayLayout: true,
35
+ classNames: [
36
+ "grid grid-rows-[var(--dx-rail-action)_1fr_min-content] p-0 overflow-hidden box-content pointer-events-auto"
37
+ ],
38
+ onEscapeKeyDown: onEscape,
39
+ onInteractOutside: preventDefault
40
+ }, children))));
53
41
  };
54
42
  ChatDialogRoot.displayName = "ChatDialog.Root";
43
+ var CHAT_DIALOG_HEADER_NAME = "ChatDialog.Header";
55
44
  var ChatDialogHeader = ({ classNames, title }) => {
56
- var _effect = _useSignals();
57
- try {
58
- const { expanded, setExpanded } = useChatDialogContext(ChatDialogHeader.displayName);
59
- return /* @__PURE__ */ React.createElement("div", {
60
- className: mx("grid grid-cols-[var(--rail-action)_1fr_min-content] items-center overflow-hidden", classNames)
61
- }, /* @__PURE__ */ React.createElement(Endcap, null, /* @__PURE__ */ React.createElement(Dialog.Close, null, /* @__PURE__ */ React.createElement(Icon, {
62
- icon: "ph--x--regular"
63
- }))), /* @__PURE__ */ React.createElement(Dialog.Title, {
64
- classNames: "flex w-full justify-center text-sm text-subdued select-none cursor-pointer",
65
- onClick: () => setExpanded((expanded2) => !expanded2)
66
- }, title), /* @__PURE__ */ React.createElement(Endcap, null, /* @__PURE__ */ React.createElement(IconButton, {
67
- variant: "ghost",
68
- icon: "ph--caret-up--regular",
69
- iconOnly: true,
70
- classNames: mx("!p-1 [&>svg]:transition [&>svg]:duration-200", expanded && "[&>svg]:rotate-180"),
71
- label: expanded ? "Close" : "Open",
72
- onClick: () => setExpanded((expanded2) => !expanded2)
73
- })));
74
- } finally {
75
- _effect.f();
76
- }
45
+ const { expanded, setExpanded } = useChatDialogContext(CHAT_DIALOG_HEADER_NAME);
46
+ return /* @__PURE__ */ React.createElement("div", {
47
+ className: mx("grid grid-cols-[var(--dx-rail-action)_1fr_min-content] items-center overflow-hidden", classNames)
48
+ }, /* @__PURE__ */ React.createElement(Endcap, null, /* @__PURE__ */ React.createElement(Dialog.Close, null, /* @__PURE__ */ React.createElement(Icon, {
49
+ icon: "ph--x--regular"
50
+ }))), /* @__PURE__ */ React.createElement(Dialog.Title, {
51
+ classNames: "flex w-full justify-center text-sm text-subdued select-none cursor-pointer",
52
+ onClick: () => setExpanded((expanded2) => !expanded2)
53
+ }, title), /* @__PURE__ */ React.createElement(Endcap, null, /* @__PURE__ */ React.createElement(IconButton, {
54
+ variant: "ghost",
55
+ icon: "ph--caret-up--regular",
56
+ iconOnly: true,
57
+ classNames: mx("p-1! [&>svg]:transition [&>svg]:duration-200", expanded && "[&>svg]:rotate-180"),
58
+ label: expanded ? "Close" : "Open",
59
+ onClick: () => setExpanded((expanded2) => !expanded2)
60
+ })));
77
61
  };
78
- ChatDialogHeader.displayName = "ChatDialog.Header";
62
+ ChatDialogHeader.displayName = CHAT_DIALOG_HEADER_NAME;
63
+ var CHAT_DIALOG_CONTENT_NAME = "ChatDialog.Content";
79
64
  var ChatDialogContent = ({ children, classNames }) => {
80
- var _effect = _useSignals();
81
- try {
82
- const { expanded, size, setSize } = useChatDialogContext(ChatDialogContent.displayName);
83
- useEffect(() => {
84
- setSize(expanded ? "min-content" : 0);
85
- }, [
86
- expanded
87
- ]);
88
- return /* @__PURE__ */ React.createElement("div", {
89
- className: mx("flex flex-col overflow-y-auto border-t border-b border-subduedSeparator", 'transition-[block-size] ease-in-out duration-0 [&:not([data-dx-resizing="true"])]:duration-200', classNames),
90
- style: {
91
- ...sizeStyle(size, "vertical", true),
92
- maxBlockSize: "calc(100dvh - env(safe-area-inset-bottom) - env(safe-area-inset-top) - 14rem)"
93
- },
94
- ...resizeAttributes
95
- }, /* @__PURE__ */ React.createElement(ResizeHandle, {
96
- side: "block-start",
97
- iconPosition: "center",
98
- defaultSize: "min-content",
99
- fallbackSize: 0,
100
- minSize: 0,
101
- onSizeChange: setSize
102
- }), children);
103
- } finally {
104
- _effect.f();
105
- }
65
+ const { expanded, size, setSize } = useChatDialogContext(CHAT_DIALOG_CONTENT_NAME);
66
+ useEffect(() => {
67
+ setSize(expanded ? "min-content" : 0);
68
+ }, [
69
+ expanded
70
+ ]);
71
+ return /* @__PURE__ */ React.createElement("div", {
72
+ className: mx("border-t border-b border-subdued-separator", 'transition ease-in-out duration-0 [&:not([data-dx-resizing="true"])]:duration-200', classNames),
73
+ style: {
74
+ ...sizeStyle(size, "vertical", true),
75
+ maxBlockSize: "calc(100dvh - env(safe-area-inset-bottom) - env(safe-area-inset-top) - 14rem)"
76
+ },
77
+ ...resizeAttributes
78
+ }, /* @__PURE__ */ React.createElement(ResizeHandle, {
79
+ side: "block-start",
80
+ iconPosition: "center",
81
+ defaultSize: "min-content",
82
+ fallbackSize: 0,
83
+ minSize: 0,
84
+ onSizeChange: setSize
85
+ }), children);
106
86
  };
107
- ChatDialogContent.displayName = "ChatDialog.Content";
87
+ ChatDialogContent.displayName = CHAT_DIALOG_CONTENT_NAME;
88
+ var CHAT_DIALOG_FOOTER_NAME = "ChatDialog.Footer";
108
89
  var ChatDialogFooter = ({ children, classNames }) => {
109
- var _effect = _useSignals();
110
- try {
111
- return /* @__PURE__ */ React.createElement("div", {
112
- className: mx(classNames)
113
- }, children);
114
- } finally {
115
- _effect.f();
116
- }
90
+ return /* @__PURE__ */ React.createElement("div", {
91
+ className: mx(classNames)
92
+ }, children);
117
93
  };
118
- ChatDialogFooter.displayName = "ChatDialog.Footer";
94
+ ChatDialogFooter.displayName = CHAT_DIALOG_FOOTER_NAME;
119
95
  var ChatDialog = {
120
96
  Root: ChatDialogRoot,
121
97
  Header: ChatDialogHeader,
@@ -131,20 +107,12 @@ import { Mutex } from "@dxos/async";
131
107
  import { invariant } from "@dxos/invariant";
132
108
  var __dxlog_file = "/__w/dxos/dxos/packages/ui/react-ui-chat/src/components/ChatEditor/references.ts";
133
109
  var references = ({ provider, triggerCharacter = "@", debug = false }) => {
134
- invariant(triggerCharacter.length === 1, void 0, {
135
- F: __dxlog_file,
136
- L: 50,
137
- S: void 0,
138
- A: [
139
- "triggerCharacter.length === 1",
140
- ""
141
- ]
142
- });
110
+ invariant(triggerCharacter.length === 1, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file, L: 14, S: void 0, A: ["triggerCharacter.length === 1", ""] });
143
111
  const decorationField = ViewPlugin.fromClass(class ReferenceView {
112
+ // TODO(burdon): Why?
113
+ _mutex = new Mutex();
114
+ decorations = Decoration.set([]);
144
115
  constructor(view) {
145
- // TODO(burdon): Why?
146
- this._mutex = new Mutex();
147
- this.decorations = Decoration.set([]);
148
116
  queueMicrotask(async () => {
149
117
  const guard = await this._mutex.acquire();
150
118
  try {
@@ -186,7 +154,7 @@ var references = ({ provider, triggerCharacter = "@", debug = false }) => {
186
154
  }
187
155
  }, {
188
156
  decorations: (v) => v.decorations,
189
- provide: (plugin) => [
157
+ provide: () => [
190
158
  EditorView.atomicRanges.of((view) => view.plugin(decorationField)?.decorations ?? RangeSet.empty)
191
159
  ]
192
160
  });
@@ -201,7 +169,7 @@ var references = ({ provider, triggerCharacter = "@", debug = false }) => {
201
169
  }
202
170
  }),
203
171
  autocompletion({
204
- tooltipClass: () => "shadow rounded",
172
+ tooltipClass: () => "shadow rounded-sm",
205
173
  activateOnTyping: true,
206
174
  aboveCursor: true,
207
175
  closeOnBlur: !debug,
@@ -230,251 +198,162 @@ var references = ({ provider, triggerCharacter = "@", debug = false }) => {
230
198
  ];
231
199
  };
232
200
  var ReferenceWidget = class extends WidgetType {
201
+ data;
233
202
  constructor(data) {
234
203
  super(), this.data = data;
235
204
  }
205
+ ignoreEvent() {
206
+ return true;
207
+ }
208
+ eq(other) {
209
+ return this.data.uri === other.data.uri;
210
+ }
236
211
  toDOM() {
237
212
  const span = document.createElement("span");
238
- span.textContent = `@ ${this.data.label}`;
213
+ span.textContent = `@${this.data.label}`;
239
214
  span.className = "cm-reference-pill";
240
215
  return span;
241
216
  }
242
- eq(other) {
243
- return other.data.uri === this.data.uri;
244
- }
245
- ignoreEvent() {
246
- return true;
247
- }
248
217
  };
249
218
 
250
219
  // src/components/ChatEditor/ChatEditor.tsx
251
- import { useSignals as _useSignals2 } from "@preact-signals/safe-react/tracking";
252
- import React2, { forwardRef, useImperativeHandle } from "react";
220
+ import React2, { forwardRef, useMemo } from "react";
253
221
  import { useThemeContext } from "@dxos/react-ui";
254
- import { createBasicExtensions, createThemeExtensions, useTextEditor } from "@dxos/react-ui-editor";
255
- import { mx as mx2 } from "@dxos/react-ui-theme";
256
- import { isNonNullable } from "@dxos/util";
257
-
258
- // src/components/ChatEditor/autocomplete.ts
259
- import { Prec } from "@codemirror/state";
260
- import { EditorView as EditorView2, Decoration as Decoration2, ViewPlugin as ViewPlugin2, keymap as keymap2, WidgetType as WidgetType2 } from "@codemirror/view";
261
- var autocomplete = ({ onSubmit, onSuggest, onCancel }) => {
262
- const suggest = ViewPlugin2.fromClass(class {
263
- constructor(view) {
264
- this._currentSuggestion = null;
265
- this._decorations = this.computeDecorations(view);
266
- }
267
- update(update) {
268
- if (update.docChanged || update.selectionSet) {
269
- this._decorations = this.computeDecorations(update.view);
270
- }
271
- }
272
- computeDecorations(view) {
273
- const text = view.state.doc.toString();
274
- const suggestions = onSuggest?.(text) ?? [];
275
- if (!suggestions.length) {
276
- this._currentSuggestion = null;
277
- return Decoration2.none;
278
- }
279
- this._currentSuggestion = suggestions[0];
280
- const suffix = this._currentSuggestion.slice(text.length);
281
- if (!suffix) {
282
- return Decoration2.none;
283
- }
284
- return Decoration2.set([
285
- Decoration2.widget({
286
- widget: new InlineSuggestionWidget(suffix),
287
- side: 1
288
- }).range(view.state.doc.length)
289
- ]);
290
- }
291
- completeSuggestion(view) {
292
- if (!this._currentSuggestion) {
293
- return false;
294
- }
295
- const text = view.state.doc.toString();
296
- const suffix = this._currentSuggestion.slice(text.length);
297
- if (!suffix) {
298
- return false;
299
- }
300
- view.dispatch({
301
- changes: {
302
- from: view.state.doc.length,
303
- insert: suffix
304
- },
305
- selection: {
306
- anchor: view.state.doc.length + suffix.length
307
- }
308
- });
309
- return true;
310
- }
311
- }, {
312
- decorations: (v) => v._decorations
313
- });
314
- return [
315
- suggest,
316
- EditorView2.theme({
317
- ".cm-inline-suggestion": {
318
- opacity: 0.4
319
- }
222
+ import { Editor } from "@dxos/react-ui-editor";
223
+ import { createBasicExtensions, createThemeExtensions, submit, xmlFormatting } from "@dxos/ui-editor";
224
+ import { isTruthy } from "@dxos/util";
225
+ var useChatExtensions = ({ extensions, lineWrapping = false, placeholder, onSubmit }) => {
226
+ const { themeMode } = useThemeContext();
227
+ return useMemo(() => [
228
+ createThemeExtensions({
229
+ themeMode
320
230
  }),
321
- Prec.highest(keymap2.of([
322
- {
323
- key: "Tab",
324
- preventDefault: true,
325
- run: (view) => {
326
- const plugin = view.plugin(suggest);
327
- return plugin?.completeSuggestion(view) ?? false;
328
- }
329
- },
330
- {
331
- key: "ArrowRight",
332
- preventDefault: true,
333
- run: (view) => {
334
- if (view.state.selection.main.head !== view.state.doc.length) {
335
- return false;
336
- }
337
- const plugin = view.plugin(suggest);
338
- return plugin?.completeSuggestion(view) ?? false;
339
- }
340
- },
341
- {
342
- key: "Enter",
343
- preventDefault: true,
344
- run: (view) => {
345
- const text = view.state.doc.toString().trim();
346
- if (text.length > 0 && onSubmit) {
347
- const reset = onSubmit(text);
348
- if (reset) {
349
- view.dispatch({
350
- changes: {
351
- from: 0,
352
- to: view.state.doc.length,
353
- insert: ""
354
- }
355
- });
356
- }
357
- }
358
- return true;
359
- }
360
- },
361
- {
362
- key: "Shift-Enter",
363
- preventDefault: true,
364
- run: (view) => {
365
- view.dispatch({
366
- changes: {
367
- from: view.state.selection.main.head,
368
- insert: "\n"
369
- },
370
- selection: {
371
- anchor: view.state.selection.main.head + 1,
372
- head: view.state.selection.main.head + 1
373
- }
374
- });
375
- return true;
376
- }
377
- },
378
- {
379
- key: "Escape",
380
- preventDefault: true,
381
- run: (view) => {
382
- view.dispatch({
383
- changes: {
384
- from: 0,
385
- to: view.state.doc.length,
386
- insert: ""
387
- }
388
- });
389
- onCancel?.();
390
- return true;
391
- }
392
- }
393
- ]))
394
- ];
231
+ createBasicExtensions({
232
+ bracketMatching: false,
233
+ lineWrapping,
234
+ placeholder
235
+ }),
236
+ xmlFormatting(),
237
+ submit({
238
+ onSubmit
239
+ }),
240
+ extensions
241
+ ].filter(isTruthy), [
242
+ themeMode,
243
+ extensions,
244
+ onSubmit
245
+ ]);
395
246
  };
396
- var InlineSuggestionWidget = class extends WidgetType2 {
397
- constructor(suffix) {
398
- super(), this.suffix = suffix;
399
- }
400
- toDOM() {
401
- const span = document.createElement("span");
402
- span.textContent = this.suffix;
403
- span.className = "cm-inline-suggestion";
404
- return span;
247
+ var ChatEditor = /* @__PURE__ */ forwardRef(({ classNames, autoFocus, extensions: extensionsProp, lineWrapping = false, placeholder, onSubmit }, forwardRef3) => {
248
+ const extensions = useChatExtensions({
249
+ extensions: extensionsProp,
250
+ lineWrapping,
251
+ placeholder,
252
+ onSubmit
253
+ });
254
+ return /* @__PURE__ */ React2.createElement(Editor.Root, {
255
+ ref: forwardRef3
256
+ }, /* @__PURE__ */ React2.createElement(Editor.View, {
257
+ classNames,
258
+ autoFocus,
259
+ extensions
260
+ }));
261
+ });
262
+
263
+ // src/components/ChatStatus/ChatStatus.tsx
264
+ import { createContext as createContext2 } from "@radix-ui/react-context";
265
+ import React3, { forwardRef as forwardRef2, useEffect as useEffect2, useImperativeHandle, useState as useState2 } from "react";
266
+ import { mx as mx2 } from "@dxos/ui-theme";
267
+
268
+ // src/components/ChatStatus/format.ts
269
+ var SECOND = 1e3;
270
+ var MINUTE = 60 * SECOND;
271
+ var HOUR = 60 * MINUTE;
272
+ var formatElapsed = (ms) => {
273
+ const safe = Math.max(0, ms);
274
+ if (safe < MINUTE) {
275
+ return `${Math.floor(safe / SECOND)}s`;
405
276
  }
406
- eq(other) {
407
- return other.suffix === this.suffix;
277
+ if (safe < HOUR) {
278
+ const m2 = Math.floor(safe / MINUTE);
279
+ const s = Math.floor(safe % MINUTE / SECOND);
280
+ return `${m2}m ${s}s`;
408
281
  }
282
+ const h = Math.floor(safe / HOUR);
283
+ const m = Math.floor(safe % HOUR / MINUTE);
284
+ return `${h}h ${m}m`;
409
285
  };
410
286
 
411
- // src/components/ChatEditor/ChatEditor.tsx
412
- var ChatEditor = /* @__PURE__ */ forwardRef(({ classNames, extensions, references: references2, autoFocus, lineWrapping = false, placeholder, onSubmit, onSuggest, onCancel }, forwardRef2) => {
413
- var _effect = _useSignals2();
414
- try {
415
- const { themeMode } = useThemeContext();
416
- const { parentRef, view } = useTextEditor({
417
- debug: true,
418
- autoFocus,
419
- extensions: [
420
- createThemeExtensions({
421
- themeMode
422
- }),
423
- autocomplete({
424
- onSubmit,
425
- onSuggest,
426
- onCancel
427
- }),
428
- references2 ? references({
429
- provider: references2.provider
430
- }) : [],
431
- createBasicExtensions({
432
- bracketMatching: false,
433
- lineWrapping,
434
- placeholder
435
- }),
436
- extensions
437
- ].filter(isNonNullable)
438
- }, [
439
- themeMode,
440
- extensions,
441
- onSubmit,
442
- onSuggest
443
- ]);
444
- useImperativeHandle(forwardRef2, () => {
445
- return {
446
- focus: () => {
447
- view?.focus();
448
- },
449
- setText: (text) => {
450
- view?.dispatch({
451
- changes: {
452
- from: 0,
453
- to: view.state.doc.length,
454
- insert: text
455
- },
456
- selection: {
457
- anchor: text.length,
458
- head: text.length
459
- }
460
- });
461
- }
462
- };
463
- }, [
464
- view,
465
- onSubmit
466
- ]);
467
- return /* @__PURE__ */ React2.createElement("div", {
468
- ref: parentRef,
469
- className: mx2("is-full", classNames)
470
- });
471
- } finally {
472
- _effect.f();
473
- }
287
+ // src/components/ChatStatus/ChatStatus.tsx
288
+ var TICK_MS = 1e3;
289
+ var [ChatStatusProvider, useChatStatusContext] = createContext2("ChatStatus");
290
+ var Root = /* @__PURE__ */ forwardRef2(({ classNames, children, defaultRunning = true }, forwardedRef) => {
291
+ const [elapsed, setElapsed] = useState2(0);
292
+ const [running, setRunning] = useState2(defaultRunning);
293
+ useEffect2(() => {
294
+ if (!running) {
295
+ return;
296
+ }
297
+ const startedAt = Date.now() - elapsed * TICK_MS;
298
+ let timeout;
299
+ const tick = () => {
300
+ setElapsed(Math.floor((Date.now() - startedAt) / TICK_MS));
301
+ timeout = setTimeout(tick, TICK_MS - Date.now() % TICK_MS);
302
+ };
303
+ timeout = setTimeout(tick, TICK_MS - Date.now() % TICK_MS);
304
+ return () => clearTimeout(timeout);
305
+ }, [
306
+ running
307
+ ]);
308
+ useImperativeHandle(forwardedRef, () => ({
309
+ start: () => setRunning(true),
310
+ stop: () => setRunning(false)
311
+ }), []);
312
+ return /* @__PURE__ */ React3.createElement(ChatStatusProvider, {
313
+ elapsed,
314
+ running
315
+ }, /* @__PURE__ */ React3.createElement("span", {
316
+ className: mx2("inline-flex items-center gap-2 text-description font-mono tabular-nums", classNames)
317
+ }, children));
474
318
  });
319
+ Root.displayName = "ChatChatStatus.Root";
320
+ var Icon2 = ({ classNames, children }) => {
321
+ if (children !== void 0) {
322
+ return /* @__PURE__ */ React3.createElement(React3.Fragment, null, children);
323
+ }
324
+ return /* @__PURE__ */ React3.createElement("span", {
325
+ "aria-hidden": "true",
326
+ className: mx2("inline-block size-2 rounded-full bg-current animate-halo-pulse", classNames)
327
+ });
328
+ };
329
+ var Stopwatch = ({ classNames, offset = 0 }) => {
330
+ const { elapsed } = useChatStatusContext("ChatChatStatus.Stopwatch");
331
+ return /* @__PURE__ */ React3.createElement("span", {
332
+ "aria-live": "off",
333
+ className: mx2(classNames)
334
+ }, formatElapsed((elapsed + offset) * 1e3));
335
+ };
336
+ var Separator = ({ classNames }) => /* @__PURE__ */ React3.createElement("span", {
337
+ "aria-hidden": "true",
338
+ className: mx2("opacity-50", classNames)
339
+ }, "\xB7");
340
+ var Text = ({ classNames, children }) => /* @__PURE__ */ React3.createElement("span", {
341
+ className: mx2(classNames)
342
+ }, children);
343
+ var ChatStatus = {
344
+ Root,
345
+ Icon: Icon2,
346
+ Stopwatch,
347
+ Separator,
348
+ Text
349
+ };
475
350
  export {
476
351
  ChatDialog,
477
352
  ChatEditor,
478
- references
353
+ ChatStatus,
354
+ formatElapsed,
355
+ references,
356
+ useChatExtensions,
357
+ useChatStatusContext
479
358
  };
480
359
  //# sourceMappingURL=index.mjs.map