@springbrand/message-panel 0.1.0 → 0.1.2

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 (99) hide show
  1. package/dist/demo/camel-chat-showcase.d.ts +1 -0
  2. package/dist/demo/camel-chat-showcase.js +1007 -0
  3. package/dist/demo/fixtures.d.ts +32 -0
  4. package/dist/demo/fixtures.js +250 -0
  5. package/dist/demo/index.js +4 -0
  6. package/dist/demo/message-panel-chat-showcase.d.ts +1 -0
  7. package/dist/demo/message-panel-chat-showcase.js +114 -0
  8. package/dist/demo/message-panel-gallery.d.ts +1 -0
  9. package/dist/demo/message-panel-gallery.js +130 -0
  10. package/dist/src/adapters/ai-sdk.d.ts +4 -0
  11. package/dist/src/adapters/ai-sdk.js +25 -0
  12. package/dist/src/camel/camel-chat-messages.d.ts +44 -0
  13. package/dist/src/camel/camel-chat-messages.js +421 -0
  14. package/dist/src/camel/camel-model-select.d.ts +13 -0
  15. package/dist/src/camel/camel-model-select.js +7 -0
  16. package/dist/src/camel/camel-plan-bar.d.ts +5 -0
  17. package/dist/src/camel/camel-plan-bar.js +24 -0
  18. package/dist/src/camel/camel-prompt-input.d.ts +32 -0
  19. package/dist/src/camel/camel-prompt-input.js +182 -0
  20. package/dist/src/camel/camel-tool-presentation.d.ts +21 -0
  21. package/dist/src/camel/camel-tool-presentation.js +784 -0
  22. package/dist/src/camel/camel-turn.d.ts +57 -0
  23. package/dist/src/camel/camel-turn.js +203 -0
  24. package/dist/src/camel/index.js +7 -0
  25. package/dist/src/camel/turn-summary-bar.d.ts +8 -0
  26. package/dist/src/camel/turn-summary-bar.js +12 -0
  27. package/dist/src/chat-summary-panel.d.ts +22 -0
  28. package/dist/src/chat-summary-panel.js +26 -0
  29. package/dist/src/composer/chat-composer.d.ts +4 -0
  30. package/dist/src/composer/chat-composer.js +90 -0
  31. package/dist/src/composer/composer-attachments.d.ts +13 -0
  32. package/dist/src/composer/composer-attachments.js +47 -0
  33. package/dist/src/composer/composer-trigger-popover.d.ts +31 -0
  34. package/dist/src/composer/composer-trigger-popover.js +29 -0
  35. package/dist/src/composer/composer.d.ts +15 -0
  36. package/dist/src/composer/composer.js +180 -0
  37. package/dist/src/composer/drop-controller.d.ts +8 -0
  38. package/dist/src/composer/drop-controller.js +69 -0
  39. package/dist/src/composer/index.js +6 -0
  40. package/dist/src/composer/types.d.ts +62 -0
  41. package/dist/src/composer/types.js +1 -0
  42. package/dist/src/contracts.d.ts +94 -0
  43. package/dist/src/contracts.js +1 -0
  44. package/dist/src/conversation.d.ts +13 -0
  45. package/dist/src/conversation.js +18 -0
  46. package/dist/src/index.js +12 -0
  47. package/dist/src/internal/cn.d.ts +1 -0
  48. package/dist/src/internal/cn.js +3 -0
  49. package/dist/src/message-panel.d.ts +4 -0
  50. package/dist/src/message-panel.js +144 -0
  51. package/dist/src/message.d.ts +36 -0
  52. package/dist/src/message.js +83 -0
  53. package/dist/src/parts/index.d.ts +187 -0
  54. package/dist/src/parts/index.js +332 -0
  55. package/dist/src/parts/kind.d.ts +3 -0
  56. package/dist/src/parts/kind.js +69 -0
  57. package/dist/src/parts/plan.d.ts +10 -0
  58. package/dist/src/parts/plan.js +40 -0
  59. package/dist/src/parts/primitives.d.ts +70 -0
  60. package/dist/src/parts/primitives.js +153 -0
  61. package/dist/src/registry.d.ts +4 -0
  62. package/dist/src/registry.js +31 -0
  63. package/dist/src/status.d.ts +26 -0
  64. package/dist/src/status.js +74 -0
  65. package/package.json +17 -8
  66. package/demo/camel-chat-showcase.tsx +0 -1265
  67. package/demo/fixtures.ts +0 -292
  68. package/demo/message-panel-chat-showcase.tsx +0 -250
  69. package/demo/message-panel-gallery.tsx +0 -389
  70. package/src/adapters/ai-sdk.ts +0 -32
  71. package/src/camel/camel-chat-messages.tsx +0 -1420
  72. package/src/camel/camel-model-select.tsx +0 -88
  73. package/src/camel/camel-plan-bar.tsx +0 -115
  74. package/src/camel/camel-prompt-input.tsx +0 -540
  75. package/src/camel/camel-tool-presentation.tsx +0 -1270
  76. package/src/camel/camel-turn.ts +0 -315
  77. package/src/camel/turn-summary-bar.tsx +0 -81
  78. package/src/chat-summary-panel.tsx +0 -229
  79. package/src/composer/chat-composer.tsx +0 -216
  80. package/src/composer/composer-attachments.tsx +0 -138
  81. package/src/composer/composer-trigger-popover.tsx +0 -236
  82. package/src/composer/composer.tsx +0 -425
  83. package/src/composer/drop-controller.ts +0 -80
  84. package/src/composer/types.ts +0 -79
  85. package/src/contracts.ts +0 -139
  86. package/src/conversation.tsx +0 -76
  87. package/src/internal/cn.ts +0 -3
  88. package/src/message-panel.tsx +0 -368
  89. package/src/message.tsx +0 -254
  90. package/src/parts/index.tsx +0 -1149
  91. package/src/parts/kind.ts +0 -67
  92. package/src/parts/plan.ts +0 -63
  93. package/src/parts/primitives.tsx +0 -439
  94. package/src/registry.ts +0 -44
  95. package/src/status.tsx +0 -132
  96. /package/{demo/index.ts → dist/demo/index.d.ts} +0 -0
  97. /package/{src/camel/index.ts → dist/src/camel/index.d.ts} +0 -0
  98. /package/{src/composer/index.ts → dist/src/composer/index.d.ts} +0 -0
  99. /package/{src/index.ts → dist/src/index.d.ts} +0 -0
@@ -1,425 +0,0 @@
1
- import {
2
- ArrowUpIcon,
3
- Loader2Icon,
4
- PlusIcon,
5
- SquareIcon,
6
- } from "lucide-react";
7
- import {
8
- forwardRef,
9
- useCallback,
10
- useImperativeHandle,
11
- useMemo,
12
- useRef,
13
- useState,
14
- type ButtonHTMLAttributes,
15
- type ClipboardEvent,
16
- type DragEvent,
17
- type HTMLAttributes,
18
- type KeyboardEvent,
19
- } from "react";
20
- import { cn } from "../internal/cn";
21
- import {
22
- AttachmentPreview,
23
- AttachmentRemove,
24
- AttachmentReorder,
25
- AttachmentRetry,
26
- Attachments,
27
- } from "./composer-attachments";
28
- import { useComposerDrop } from "./drop-controller";
29
- import type {
30
- ComposerAttachmentView,
31
- ComposerProps,
32
- } from "./types";
33
-
34
- export const ComposerHeader = forwardRef<
35
- HTMLDivElement,
36
- HTMLAttributes<HTMLDivElement>
37
- >(({ className, ...props }, ref) => (
38
- <div ref={ref} className={cn("sb-composer__header", className)} {...props} />
39
- ));
40
- ComposerHeader.displayName = "ComposerHeader";
41
-
42
- export const ComposerBody = forwardRef<
43
- HTMLDivElement,
44
- HTMLAttributes<HTMLDivElement>
45
- >(({ className, ...props }, ref) => (
46
- <div ref={ref} className={cn("sb-composer__body", className)} {...props} />
47
- ));
48
- ComposerBody.displayName = "ComposerBody";
49
-
50
- export const ComposerFooter = forwardRef<
51
- HTMLDivElement,
52
- HTMLAttributes<HTMLDivElement>
53
- >(({ className, ...props }, ref) => (
54
- <div ref={ref} className={cn("sb-composer__footer", className)} {...props} />
55
- ));
56
- ComposerFooter.displayName = "ComposerFooter";
57
-
58
- export const ComposerTools = forwardRef<
59
- HTMLDivElement,
60
- HTMLAttributes<HTMLDivElement>
61
- >(({ className, ...props }, ref) => (
62
- <div ref={ref} className={cn("sb-composer__tools", className)} {...props} />
63
- ));
64
- ComposerTools.displayName = "ComposerTools";
65
-
66
- export const ComposerSubmit = forwardRef<
67
- HTMLButtonElement,
68
- ButtonHTMLAttributes<HTMLButtonElement> & {
69
- status?: "idle" | "submitted" | "streaming";
70
- }
71
- >(({ className, status = "idle", children, ...props }, ref) => (
72
- <button
73
- ref={ref}
74
- type="button"
75
- className={cn("sb-composer__submit", className)}
76
- {...props}
77
- >
78
- {children ??
79
- (status === "submitted" ? (
80
- <Loader2Icon size={16} className="sb-spin" />
81
- ) : status === "streaming" ? (
82
- <SquareIcon size={15} />
83
- ) : (
84
- <ArrowUpIcon size={17} />
85
- ))}
86
- </button>
87
- ));
88
- ComposerSubmit.displayName = "ComposerSubmit";
89
-
90
- export function DropOverlay({
91
- label = "松开以添加文件",
92
- }: {
93
- label?: string;
94
- }) {
95
- return (
96
- <div className="sb-composer__drop" role="status">
97
- <PlusIcon size={20} />
98
- <span>{label}</span>
99
- </div>
100
- );
101
- }
102
-
103
- const ATTACHMENT_DRAG_TYPE = "application/x-sb-message-panel-attachment";
104
-
105
- function defaultAttachment<T>(attachment: T, index: number): ComposerAttachmentView {
106
- const record =
107
- attachment != null && typeof attachment === "object"
108
- ? (attachment as Record<string, unknown>)
109
- : {};
110
- return {
111
- id: String(record.id ?? `attachment-${index}`),
112
- name: String(record.name ?? record.filename ?? "附件"),
113
- mediaType:
114
- typeof record.mediaType === "string" ? record.mediaType : undefined,
115
- size: typeof record.size === "number" ? record.size : undefined,
116
- url: typeof record.url === "string" ? record.url : undefined,
117
- status:
118
- record.status === "uploading" || record.status === "error"
119
- ? record.status
120
- : "ready",
121
- note: typeof record.note === "string" ? record.note : undefined,
122
- };
123
- }
124
-
125
- function matchesAccept(file: File, accept?: string): boolean {
126
- if (!accept?.trim()) return true;
127
- return accept
128
- .split(",")
129
- .map((value) => value.trim().toLowerCase())
130
- .some((rule) => {
131
- if (rule.startsWith(".")) return file.name.toLowerCase().endsWith(rule);
132
- if (rule.endsWith("/*")) return file.type.startsWith(rule.slice(0, -1));
133
- return file.type.toLowerCase() === rule;
134
- });
135
- }
136
-
137
- function ComposerInner<TAttachment>(
138
- {
139
- value,
140
- defaultValue = "",
141
- onValueChange,
142
- status,
143
- attachments = [],
144
- getAttachment = defaultAttachment,
145
- dropScope = "composer",
146
- dropTarget,
147
- accept,
148
- multiple = true,
149
- maxFiles,
150
- maxFileSize,
151
- onFilesAdded,
152
- onAttachmentRemove,
153
- onAttachmentRetry,
154
- onAttachmentReorder,
155
- onValidationError,
156
- onSubmit,
157
- onStop,
158
- placeholder = "问点什么…",
159
- tools,
160
- textareaProps,
161
- textareaRef: externalTextareaRef,
162
- className,
163
- ...props
164
- }: ComposerProps<TAttachment>,
165
- forwardedRef: React.ForwardedRef<HTMLDivElement>,
166
- ) {
167
- const [internalValue, setInternalValue] = useState(defaultValue);
168
- const resolvedValue = value ?? internalValue;
169
- const rootRef = useRef<HTMLDivElement | null>(null);
170
- const localTextareaRef = useRef<HTMLTextAreaElement | null>(null);
171
- const inputRef = useRef<HTMLInputElement | null>(null);
172
- useImperativeHandle(forwardedRef, () => rootRef.current as HTMLDivElement);
173
- useImperativeHandle(
174
- externalTextareaRef,
175
- () => localTextareaRef.current as HTMLTextAreaElement,
176
- );
177
- const attachmentViews = useMemo(
178
- () => attachments.map((attachment, index) => getAttachment(attachment, index)),
179
- [attachments, getAttachment],
180
- );
181
- const generating = status === "submitted" || status === "streaming";
182
- const allReady = attachmentViews.every(
183
- (attachment) => attachment.status === "ready",
184
- );
185
- const canSend =
186
- (resolvedValue.trim().length > 0 || attachments.length > 0) &&
187
- !generating &&
188
- allReady;
189
-
190
- const changeValue = useCallback(
191
- (next: string) => {
192
- if (value === undefined) setInternalValue(next);
193
- onValueChange?.(next);
194
- },
195
- [value, onValueChange],
196
- );
197
-
198
- const addFiles = useCallback(
199
- (incoming: FileList | readonly File[]) => {
200
- const all = Array.from(incoming);
201
- const remaining =
202
- maxFiles === undefined
203
- ? all.length
204
- : Math.max(0, maxFiles - attachments.length);
205
- const selected = multiple ? all.slice(0, remaining) : all.slice(0, 1);
206
- const valid = selected.filter(
207
- (file) =>
208
- matchesAccept(file, accept) &&
209
- (maxFileSize === undefined || file.size <= maxFileSize),
210
- );
211
- const invalid = selected.filter((file) => !valid.includes(file));
212
- if (all.length > selected.length) {
213
- onValidationError?.(`最多只能添加 ${maxFiles ?? 1} 个附件`, all);
214
- }
215
- if (invalid.length) {
216
- onValidationError?.("存在不支持或超过大小限制的文件", invalid);
217
- }
218
- if (valid.length) void onFilesAdded?.(valid);
219
- },
220
- [
221
- accept,
222
- attachments.length,
223
- maxFiles,
224
- maxFileSize,
225
- multiple,
226
- onFilesAdded,
227
- onValidationError,
228
- ],
229
- );
230
-
231
- const dragging = useComposerDrop({
232
- scope: dropScope,
233
- rootRef,
234
- target: dropTarget,
235
- onFiles: addFiles,
236
- });
237
-
238
- const submit = useCallback(async () => {
239
- if (!canSend) return;
240
- await onSubmit({ text: resolvedValue.trim(), attachments });
241
- if (value === undefined) setInternalValue("");
242
- }, [canSend, onSubmit, resolvedValue, attachments, value]);
243
-
244
- const handleKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {
245
- textareaProps?.onKeyDown?.(event);
246
- if (event.defaultPrevented) return;
247
- if (
248
- event.key === "Enter" &&
249
- !event.shiftKey &&
250
- !event.nativeEvent.isComposing
251
- ) {
252
- event.preventDefault();
253
- if (canSend) void submit();
254
- return;
255
- }
256
- if (
257
- event.key === "Backspace" &&
258
- event.currentTarget.value === "" &&
259
- attachments.length > 0
260
- ) {
261
- const last = attachments[attachments.length - 1];
262
- if (last) {
263
- event.preventDefault();
264
- onAttachmentRemove?.(last);
265
- }
266
- }
267
- };
268
-
269
- const handlePaste = (event: ClipboardEvent<HTMLTextAreaElement>) => {
270
- textareaProps?.onPaste?.(event);
271
- if (event.defaultPrevented) return;
272
- const files = Array.from(event.clipboardData.items)
273
- .filter((item) => item.kind === "file")
274
- .map((item) => item.getAsFile())
275
- .filter((file): file is File => file !== null);
276
- if (files.length) {
277
- event.preventDefault();
278
- addFiles(files);
279
- }
280
- };
281
-
282
- return (
283
- <div
284
- ref={rootRef}
285
- className={cn("sb-composer", className)}
286
- data-drop-scope={dropScope}
287
- data-dragging={dragging ? "true" : undefined}
288
- data-status={status}
289
- {...props}
290
- >
291
- <input
292
- ref={inputRef}
293
- className="sb-sr-only"
294
- type="file"
295
- accept={accept}
296
- multiple={multiple}
297
- tabIndex={-1}
298
- aria-label="上传文件"
299
- onChange={(event) => {
300
- if (event.currentTarget.files) addFiles(event.currentTarget.files);
301
- event.currentTarget.value = "";
302
- }}
303
- />
304
- {attachments.length > 0 && (
305
- <ComposerHeader>
306
- <Attachments>
307
- {attachments.map((attachment, index) => {
308
- const view = attachmentViews[index];
309
- return (
310
- <AttachmentPreview
311
- key={view.id}
312
- attachment={view}
313
- draggable={Boolean(onAttachmentReorder)}
314
- aria-roledescription={
315
- onAttachmentReorder ? "可排序附件" : undefined
316
- }
317
- onDragStart={(event: DragEvent<HTMLDivElement>) => {
318
- if (!onAttachmentReorder) return;
319
- event.dataTransfer.effectAllowed = "move";
320
- event.dataTransfer.setData(
321
- ATTACHMENT_DRAG_TYPE,
322
- String(index),
323
- );
324
- }}
325
- onDragOver={(event: DragEvent<HTMLDivElement>) => {
326
- if (
327
- onAttachmentReorder &&
328
- event.dataTransfer.types.includes(ATTACHMENT_DRAG_TYPE)
329
- ) {
330
- event.preventDefault();
331
- event.dataTransfer.dropEffect = "move";
332
- }
333
- }}
334
- onDrop={(event: DragEvent<HTMLDivElement>) => {
335
- if (!onAttachmentReorder) return;
336
- const fromIndex = Number(
337
- event.dataTransfer.getData(ATTACHMENT_DRAG_TYPE),
338
- );
339
- if (
340
- Number.isInteger(fromIndex) &&
341
- fromIndex >= 0 &&
342
- fromIndex < attachments.length &&
343
- fromIndex !== index
344
- ) {
345
- event.preventDefault();
346
- onAttachmentReorder(fromIndex, index);
347
- }
348
- }}
349
- actions={
350
- <>
351
- {view.status === "error" && onAttachmentRetry && (
352
- <AttachmentRetry onClick={() => void onAttachmentRetry(attachment)} />
353
- )}
354
- {onAttachmentReorder && index > 0 && (
355
- <AttachmentReorder
356
- direction="left"
357
- onClick={() => onAttachmentReorder(index, index - 1)}
358
- />
359
- )}
360
- {onAttachmentReorder && index < attachments.length - 1 && (
361
- <AttachmentReorder
362
- direction="right"
363
- onClick={() => onAttachmentReorder(index, index + 1)}
364
- />
365
- )}
366
- {onAttachmentRemove && (
367
- <AttachmentRemove
368
- onClick={() => onAttachmentRemove(attachment)}
369
- />
370
- )}
371
- </>
372
- }
373
- />
374
- );
375
- })}
376
- </Attachments>
377
- </ComposerHeader>
378
- )}
379
- <ComposerBody>
380
- <textarea
381
- {...textareaProps}
382
- ref={localTextareaRef}
383
- value={resolvedValue}
384
- rows={1}
385
- placeholder={placeholder}
386
- onChange={(event) => changeValue(event.currentTarget.value)}
387
- onKeyDown={handleKeyDown}
388
- onPaste={handlePaste}
389
- />
390
- </ComposerBody>
391
- <ComposerFooter>
392
- <ComposerTools>
393
- <button
394
- type="button"
395
- className="sb-composer__tool"
396
- aria-label="添加图片或文件"
397
- onClick={() => inputRef.current?.click()}
398
- >
399
- <PlusIcon size={17} />
400
- </button>
401
- {tools}
402
- </ComposerTools>
403
- <ComposerSubmit
404
- status={
405
- status === "submitted"
406
- ? "submitted"
407
- : status === "streaming"
408
- ? "streaming"
409
- : "idle"
410
- }
411
- disabled={generating ? !onStop : !canSend}
412
- aria-label={generating ? "停止" : "发送"}
413
- onClick={() => (generating ? void onStop?.() : void submit())}
414
- />
415
- </ComposerFooter>
416
- {dragging && <DropOverlay />}
417
- </div>
418
- );
419
- }
420
-
421
- export const Composer = forwardRef(ComposerInner) as <TAttachment>(
422
- props: ComposerProps<TAttachment> & {
423
- ref?: React.ForwardedRef<HTMLDivElement>;
424
- },
425
- ) => React.ReactElement;
@@ -1,80 +0,0 @@
1
- import { useEffect, useState, type RefObject } from "react";
2
- import type { ComposerDropScope } from "./types";
3
-
4
- function carriesFiles(event: DragEvent): boolean {
5
- return event.dataTransfer?.types?.includes("Files") === true;
6
- }
7
-
8
- export function useComposerDrop({
9
- scope,
10
- rootRef,
11
- target,
12
- onFiles,
13
- }: {
14
- scope: ComposerDropScope;
15
- rootRef: RefObject<HTMLElement | null>;
16
- target?: HTMLElement | null;
17
- onFiles: (files: FileList) => void;
18
- }) {
19
- const [dragging, setDragging] = useState(false);
20
-
21
- useEffect(() => {
22
- const element =
23
- scope === "document"
24
- ? document
25
- : scope === "panel"
26
- ? target ??
27
- rootRef.current?.closest<HTMLElement>("[data-sb-message-panel]") ??
28
- rootRef.current
29
- : rootRef.current;
30
- if (!element) return;
31
- let depth = 0;
32
-
33
- const over = (event: Event) => {
34
- const dragEvent = event as DragEvent;
35
- if (carriesFiles(dragEvent)) dragEvent.preventDefault();
36
- };
37
- const enter = (event: Event) => {
38
- const dragEvent = event as DragEvent;
39
- if (!carriesFiles(dragEvent)) return;
40
- dragEvent.preventDefault();
41
- depth += 1;
42
- setDragging(true);
43
- };
44
- const leave = (event: Event) => {
45
- const dragEvent = event as DragEvent;
46
- if (!carriesFiles(dragEvent)) return;
47
- depth = Math.max(0, depth - 1);
48
- if (depth === 0) setDragging(false);
49
- };
50
- const drop = (event: Event) => {
51
- const dragEvent = event as DragEvent;
52
- if (!carriesFiles(dragEvent)) return;
53
- dragEvent.preventDefault();
54
- depth = 0;
55
- setDragging(false);
56
- if (dragEvent.dataTransfer?.files.length) onFiles(dragEvent.dataTransfer.files);
57
- };
58
- const cancel = () => {
59
- depth = 0;
60
- setDragging(false);
61
- };
62
-
63
- element.addEventListener("dragover", over);
64
- element.addEventListener("dragenter", enter);
65
- element.addEventListener("dragleave", leave);
66
- element.addEventListener("drop", drop);
67
- window.addEventListener("dragend", cancel);
68
- window.addEventListener("blur", cancel);
69
- return () => {
70
- element.removeEventListener("dragover", over);
71
- element.removeEventListener("dragenter", enter);
72
- element.removeEventListener("dragleave", leave);
73
- element.removeEventListener("drop", drop);
74
- window.removeEventListener("dragend", cancel);
75
- window.removeEventListener("blur", cancel);
76
- };
77
- }, [scope, target, rootRef, onFiles]);
78
-
79
- return dragging;
80
- }
@@ -1,79 +0,0 @@
1
- import type {
2
- HTMLAttributes,
3
- ReactNode,
4
- RefObject,
5
- TextareaHTMLAttributes,
6
- } from "react";
7
- import type { MessagePanelStatus } from "../contracts";
8
-
9
- export type ComposerDropScope = "composer" | "panel" | "document";
10
- export type AttachmentStatus = "uploading" | "ready" | "error";
11
-
12
- export interface ComposerAttachmentView {
13
- id: string;
14
- name: string;
15
- mediaType?: string;
16
- size?: number;
17
- url?: string;
18
- status: AttachmentStatus;
19
- note?: string;
20
- }
21
-
22
- export interface ComposerMessage<TAttachment> {
23
- text: string;
24
- attachments: readonly TAttachment[];
25
- }
26
-
27
- export interface ComposerCommand {
28
- name: string;
29
- description: string;
30
- prompt: string;
31
- icon?: "plan" | "research" | "file" | "time" | "tool" | "mcp";
32
- }
33
-
34
- export interface ComposerToolbarAction {
35
- id: string;
36
- label: string;
37
- icon?: "sparkles" | "plus";
38
- disabled?: boolean;
39
- onSelect: () => void;
40
- }
41
-
42
- export interface ComposerProps<TAttachment>
43
- extends Omit<HTMLAttributes<HTMLDivElement>, "children" | "onSubmit"> {
44
- value?: string;
45
- defaultValue?: string;
46
- onValueChange?: (value: string) => void;
47
- status: MessagePanelStatus;
48
- attachments?: readonly TAttachment[];
49
- getAttachment?: (attachment: TAttachment, index: number) => ComposerAttachmentView;
50
- dropScope?: ComposerDropScope;
51
- dropTarget?: HTMLElement | null;
52
- accept?: string;
53
- multiple?: boolean;
54
- maxFiles?: number;
55
- maxFileSize?: number;
56
- onFilesAdded?: (files: readonly File[]) => void | Promise<void>;
57
- onAttachmentRemove?: (attachment: TAttachment) => void;
58
- onAttachmentRetry?: (attachment: TAttachment) => void | Promise<void>;
59
- onAttachmentReorder?: (fromIndex: number, toIndex: number) => void;
60
- onValidationError?: (message: string, files: readonly File[]) => void;
61
- onSubmit: (message: ComposerMessage<TAttachment>) => void | Promise<void>;
62
- onStop?: () => void | Promise<void>;
63
- placeholder?: string;
64
- tools?: ReactNode;
65
- textareaProps?: Omit<
66
- TextareaHTMLAttributes<HTMLTextAreaElement>,
67
- "value" | "defaultValue" | "onChange" | "onSubmit"
68
- >;
69
- textareaRef?: RefObject<HTMLTextAreaElement | null>;
70
- }
71
-
72
- export interface ChatComposerProps<TAttachment>
73
- extends ComposerProps<TAttachment> {
74
- commands?: readonly ComposerCommand[];
75
- toolbarActions?: readonly ComposerToolbarAction[];
76
- variant?: "docked" | "standalone";
77
- commandMenuLabel?: string;
78
- onCommandPick?: (command: ComposerCommand) => void;
79
- }