@dxos/react-ui-chat 0.8.4-main.f9ba587 → 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 +220 -340
  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 +220 -340
  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 -8
  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 +52 -42
  37. package/src/components/ChatDialog/ChatDialog.stories.tsx +9 -10
  38. package/src/components/ChatDialog/ChatDialog.tsx +29 -19
  39. package/src/components/ChatEditor/ChatEditor.stories.tsx +25 -24
  40. package/src/components/ChatEditor/ChatEditor.tsx +35 -67
  41. package/src/components/ChatEditor/references.ts +19 -13
  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 -25
  50. package/dist/types/src/components/ChatEditor/autocomplete.d.ts.map +0 -1
  51. package/src/components/ChatEditor/autocomplete.ts +0 -218
@@ -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
- 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
+ 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,20 +108,12 @@ 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 {
113
+ // TODO(burdon): Why?
114
+ _mutex = new Mutex();
115
+ decorations = Decoration.set([]);
144
116
  constructor(view) {
145
- // TODO(burdon): Why?
146
- this._mutex = new Mutex();
147
- this.decorations = Decoration.set([]);
148
117
  queueMicrotask(async () => {
149
118
  const guard = await this._mutex.acquire();
150
119
  try {
@@ -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,
@@ -230,251 +199,162 @@ var references = ({ provider, triggerCharacter = "@", debug = false }) => {
230
199
  ];
231
200
  };
232
201
  var ReferenceWidget = class extends WidgetType {
202
+ data;
233
203
  constructor(data) {
234
204
  super(), this.data = data;
235
205
  }
206
+ ignoreEvent() {
207
+ return true;
208
+ }
209
+ eq(other) {
210
+ return this.data.uri === other.data.uri;
211
+ }
236
212
  toDOM() {
237
213
  const span = document.createElement("span");
238
- span.textContent = `@ ${this.data.label}`;
214
+ span.textContent = `@${this.data.label}`;
239
215
  span.className = "cm-reference-pill";
240
216
  return span;
241
217
  }
242
- eq(other) {
243
- return other.data.uri === this.data.uri;
244
- }
245
- ignoreEvent() {
246
- return true;
247
- }
248
218
  };
249
219
 
250
220
  // src/components/ChatEditor/ChatEditor.tsx
251
- import { useSignals as _useSignals2 } from "@preact-signals/safe-react/tracking";
252
- import React2, { forwardRef, useImperativeHandle } from "react";
221
+ import React2, { forwardRef, useMemo } from "react";
253
222
  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
- }
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
320
231
  }),
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
- ];
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
+ ]);
395
247
  };
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;
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`;
405
277
  }
406
- eq(other) {
407
- 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`;
408
282
  }
283
+ const h = Math.floor(safe / HOUR);
284
+ const m = Math.floor(safe % HOUR / MINUTE);
285
+ return `${h}h ${m}m`;
409
286
  };
410
287
 
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
- }
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));
474
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
+ };
475
351
  export {
476
352
  ChatDialog,
477
353
  ChatEditor,
478
- references
354
+ ChatStatus,
355
+ formatElapsed,
356
+ references,
357
+ useChatExtensions,
358
+ useChatStatusContext
479
359
  };
480
360
  //# sourceMappingURL=index.mjs.map