@dxos/react-ui-chat 0.8.4-main.f5c0578 → 0.8.4-main.fcc0d83b33

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