@mvriu5/payload-ai 1.2.0 → 1.3.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 (51) hide show
  1. package/README.md +42 -0
  2. package/dist/components/Icons.d.ts +0 -8
  3. package/dist/components/Icons.js +4 -216
  4. package/dist/components/{ActionToast.d.ts → action-toast/ActionToast.d.ts} +1 -2
  5. package/dist/components/{ActionToast.js → action-toast/ActionToast.js} +25 -34
  6. package/dist/components/{ActionToast.module.css → action-toast/ActionToast.module.css} +0 -79
  7. package/dist/components/ai-input/AIInput.js +433 -0
  8. package/dist/components/{AIInput.module.css → ai-input/AIInput.module.css} +111 -43
  9. package/dist/components/ai-input/badge.d.ts +14 -0
  10. package/dist/components/ai-input/badge.js +85 -0
  11. package/dist/components/{AuditLogList.d.ts → audit-log-list/AuditLogList.d.ts} +4 -8
  12. package/dist/components/{AuditLogList.js → audit-log-list/AuditLogList.js} +49 -21
  13. package/dist/components/{AuditLogList.module.css → audit-log-list/AuditLogList.module.css} +11 -65
  14. package/dist/components/dashboard/Dashboard.d.ts +2 -0
  15. package/dist/components/dashboard/Dashboard.js +15 -0
  16. package/dist/components/dashboard/Dashboard.module.css +13 -0
  17. package/dist/components/{DiffDialog.d.ts → diff-dialog/DiffDialog.d.ts} +2 -2
  18. package/dist/components/{DiffDialog.js → diff-dialog/DiffDialog.js} +200 -189
  19. package/dist/components/{DiffDialog.module.css → diff-dialog/DiffDialog.module.css} +17 -35
  20. package/dist/components/hooks/useAIChatStream.d.ts +39 -0
  21. package/dist/components/hooks/useAIChatStream.js +217 -0
  22. package/dist/components/hooks/useAISettings.js +26 -25
  23. package/dist/components/hooks/useAuditLog.d.ts +11 -0
  24. package/dist/components/hooks/useAuditLog.js +41 -0
  25. package/dist/components/hooks/useDocumentMentionSuggestions.d.ts +1 -1
  26. package/dist/components/hooks/useDocumentMentionSuggestions.js +31 -27
  27. package/dist/components/hooks/useMentions.d.ts +58 -0
  28. package/dist/components/hooks/useMentions.js +276 -0
  29. package/dist/components/hooks/usePluginConfig.d.ts +52 -0
  30. package/dist/components/hooks/usePluginConfig.js +20 -0
  31. package/dist/components/{MentionPopover.d.ts → mention-popover/MentionPopover.d.ts} +2 -4
  32. package/dist/components/{MentionPopover.js → mention-popover/MentionPopover.js} +1 -8
  33. package/dist/components/{MentionPopover.module.css → mention-popover/MentionPopover.module.css} +1 -1
  34. package/dist/exports/client.d.ts +2 -2
  35. package/dist/exports/client.js +2 -2
  36. package/dist/handlers/applyActionHandler.js +27 -7
  37. package/dist/handlers/chatHandler.js +302 -58
  38. package/dist/handlers/mediaUploadHandler.d.ts +7 -0
  39. package/dist/handlers/mediaUploadHandler.js +99 -0
  40. package/dist/handlers/mentionSuggestionHandler.js +16 -14
  41. package/dist/handlers/proposalDiffHandler.js +41 -29
  42. package/dist/index.d.ts +6 -0
  43. package/dist/index.js +39 -7
  44. package/dist/payload/collectionPermissions.js +3 -1
  45. package/dist/payload/normalizeData.d.ts +0 -6
  46. package/dist/payload/normalizeData.js +25 -13
  47. package/dist/payload/proposalData.js +4 -1
  48. package/dist/payload/schemaContext.js +98 -43
  49. package/package.json +6 -6
  50. package/dist/components/AIInput.js +0 -896
  51. /package/dist/components/{AIInput.d.ts → ai-input/AIInput.d.ts} +0 -0
@@ -0,0 +1,433 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { Button, PlusIcon, useConfig } from "@payloadcms/ui";
4
+ import { formatAdminURL } from "payload/shared";
5
+ import { useEffect, useRef, useState } from "react";
6
+ import { ActionToast } from "../action-toast/ActionToast.js";
7
+ import { useAIChatStream } from "../hooks/useAIChatStream.js";
8
+ import { useAISettings } from "../hooks/useAISettings.js";
9
+ import { useAuditLog } from "../hooks/useAuditLog.js";
10
+ import { getTextBeforeCaret, useMentions } from "../hooks/useMentions.js";
11
+ import { usePluginConfig } from "../hooks/usePluginConfig.js";
12
+ import { ClaudeIcon, GoogleGeminiIcon, MistralAiIcon, OpenaiIcon, OpenrouterIcon } from "../Icons.js";
13
+ import { MentionPopover } from "../mention-popover/MentionPopover.js";
14
+ import styles from "./AIInput.module.css";
15
+ const getProviderIcon = (provider)=>{
16
+ const iconProps = {
17
+ "aria-hidden": true,
18
+ className: styles.selectProviderIcon
19
+ };
20
+ switch(provider){
21
+ case "claude":
22
+ return /*#__PURE__*/ _jsx(ClaudeIcon, {
23
+ ...iconProps
24
+ });
25
+ case "google":
26
+ return /*#__PURE__*/ _jsx(GoogleGeminiIcon, {
27
+ ...iconProps
28
+ });
29
+ case "mistral":
30
+ return /*#__PURE__*/ _jsx(MistralAiIcon, {
31
+ ...iconProps
32
+ });
33
+ case "openai":
34
+ return /*#__PURE__*/ _jsx(OpenaiIcon, {
35
+ ...iconProps
36
+ });
37
+ case "openrouter":
38
+ return /*#__PURE__*/ _jsx(OpenrouterIcon, {
39
+ ...iconProps
40
+ });
41
+ default:
42
+ return null;
43
+ }
44
+ };
45
+ const getFileSignature = (file)=>`${file.name}:${file.size}:${file.lastModified}:${file.type}`;
46
+ const AIInput = ()=>{
47
+ const editorRef = useRef(null);
48
+ const fileInputRef = useRef(null);
49
+ const [prompt, setPrompt] = useState("");
50
+ const [isApplying, setIsApplying] = useState(false);
51
+ const [isUploadingMedia, setIsUploadingMedia] = useState(false);
52
+ const [selectedFiles, setSelectedFiles] = useState([]);
53
+ const [mediaAttachments, setMediaAttachments] = useState([]);
54
+ const selectedFilesRef = useRef([]);
55
+ const mediaAttachmentsRef = useRef([]);
56
+ const uploadedFileSignaturesRef = useRef(new Set());
57
+ const { config } = useConfig();
58
+ const { aiModelConfig, isCollectionMentionEnabled, locales, defaultLocale, media } = usePluginConfig(config);
59
+ const acceptedMimeTypes = media?.acceptedMimeTypes?.join(",");
60
+ const mediaEnabled = Boolean(media?.enabled);
61
+ const { loadRecentChanges, prependChange } = useAuditLog({
62
+ adminRoute: config.routes.admin,
63
+ apiRoute: config.routes.api
64
+ });
65
+ const { selectedModel, setSelectedModel, settingsProvider } = useAISettings({
66
+ adminUserSlug: config.admin?.user,
67
+ apiRoute: config.routes.api,
68
+ defaultModels: aiModelConfig.defaults
69
+ });
70
+ const { clearMentions, insertMention, mentionPopoverPosition, mentionRange, mentionSuggestions, mentionsRef, updateMentionState } = useMentions({
71
+ apiRoute: config.routes.api,
72
+ config,
73
+ defaultLocale,
74
+ editorRef,
75
+ isCollectionMentionEnabled,
76
+ locales,
77
+ setPrompt,
78
+ styles
79
+ });
80
+ const updateSelectedFiles = (updater)=>{
81
+ const nextFiles = typeof updater === "function" ? updater(selectedFilesRef.current) : updater;
82
+ selectedFilesRef.current = nextFiles;
83
+ setSelectedFiles(nextFiles);
84
+ };
85
+ const updateMediaAttachments = (updater)=>{
86
+ const nextAttachments = typeof updater === "function" ? updater(mediaAttachmentsRef.current) : updater;
87
+ mediaAttachmentsRef.current = nextAttachments;
88
+ setMediaAttachments(nextAttachments);
89
+ };
90
+ const clearInput = ()=>{
91
+ setPrompt("");
92
+ updateSelectedFiles([]);
93
+ updateMediaAttachments([]);
94
+ uploadedFileSignaturesRef.current = new Set();
95
+ clearMentions();
96
+ if (editorRef.current) editorRef.current.textContent = "";
97
+ };
98
+ const handleSelectFile = (event)=>{
99
+ const files = Array.from(event.target.files || []);
100
+ updateSelectedFiles((currentFiles)=>{
101
+ const selectedFileSignatures = new Set(currentFiles.map(getFileSignature));
102
+ const nextFiles = [
103
+ ...currentFiles
104
+ ];
105
+ for (const file of files){
106
+ const fileSignature = getFileSignature(file);
107
+ if (selectedFileSignatures.has(fileSignature)) continue;
108
+ selectedFileSignatures.add(fileSignature);
109
+ nextFiles.push(file);
110
+ }
111
+ return nextFiles;
112
+ });
113
+ event.target.value = "";
114
+ };
115
+ const removeSelectedFile = (fileIndex)=>{
116
+ updateSelectedFiles((currentFiles)=>currentFiles.filter((_, index)=>index !== fileIndex));
117
+ };
118
+ const removeMediaAttachment = (attachmentIndex)=>{
119
+ updateMediaAttachments((currentAttachments)=>currentAttachments.filter((_, index)=>index !== attachmentIndex));
120
+ };
121
+ const { dismissChat, error, isLoading, proposals, resetChatState, response, setError, setProposals, setResponse, submit, tokenUsage } = useAIChatStream({
122
+ apiRoute: config.routes.api,
123
+ clearInput,
124
+ mentionsRef,
125
+ prompt,
126
+ selectedModel
127
+ });
128
+ const uploadSelectedFiles = async ()=>{
129
+ const filesToUpload = selectedFilesRef.current;
130
+ if (filesToUpload.length === 0) return [];
131
+ const uploadedAttachments = [];
132
+ for (const file of filesToUpload){
133
+ const fileSignature = getFileSignature(file);
134
+ if (uploadedFileSignaturesRef.current.has(fileSignature)) continue;
135
+ uploadedFileSignaturesRef.current.add(fileSignature);
136
+ const formData = new FormData();
137
+ formData.append("file", file);
138
+ const res = await fetch(formatAdminURL({
139
+ apiRoute: config.routes.api,
140
+ path: "/ai-upload-media"
141
+ }), {
142
+ body: formData,
143
+ method: "POST"
144
+ });
145
+ const result = await res.json().catch(()=>null);
146
+ if (!res.ok || !result?.attachment) {
147
+ uploadedFileSignaturesRef.current.delete(fileSignature);
148
+ throw new Error(result?.error || `Could not upload ${file.name}`);
149
+ }
150
+ uploadedAttachments.push(result.attachment);
151
+ }
152
+ return uploadedAttachments;
153
+ };
154
+ const handleSubmit = async ()=>{
155
+ if (isUploadingMedia) return;
156
+ setError("");
157
+ setIsUploadingMedia(true);
158
+ try {
159
+ const uploadedAttachments = await uploadSelectedFiles();
160
+ const nextAttachments = [
161
+ ...mediaAttachmentsRef.current,
162
+ ...uploadedAttachments
163
+ ];
164
+ if (uploadedAttachments.length > 0) {
165
+ updateMediaAttachments(nextAttachments);
166
+ updateSelectedFiles([]);
167
+ }
168
+ await submit({
169
+ attachments: nextAttachments
170
+ });
171
+ } catch (err) {
172
+ setError(err instanceof Error ? err.message : "Media upload failed");
173
+ } finally{
174
+ setIsUploadingMedia(false);
175
+ }
176
+ };
177
+ useEffect(()=>{
178
+ if (isLoading || error || proposals.length > 0 || !response) return;
179
+ const timeout = window.setTimeout(()=>{
180
+ setResponse("");
181
+ clearInput();
182
+ }, 10000);
183
+ return ()=>window.clearTimeout(timeout);
184
+ }, [
185
+ error,
186
+ isLoading,
187
+ proposals.length,
188
+ response
189
+ ]);
190
+ const getProposalViewURL = (proposal)=>{
191
+ const adminRoute = config.routes.admin || "/admin";
192
+ if (proposal.action === "updateGlobal" && proposal.slug) {
193
+ return `${adminRoute}/globals/${proposal.slug}`;
194
+ }
195
+ if (proposal.collection && proposal.id) {
196
+ return `${adminRoute}/collections/${proposal.collection}/${proposal.id}`;
197
+ }
198
+ return null;
199
+ };
200
+ const handleApplyProposal = async (proposal)=>{
201
+ setIsApplying(true);
202
+ setError("");
203
+ try {
204
+ const res = await fetch(formatAdminURL({
205
+ apiRoute: config.routes.api,
206
+ path: "/ai-apply-action"
207
+ }), {
208
+ body: JSON.stringify({
209
+ aiResponse: response,
210
+ prompt,
211
+ proposal,
212
+ tokenUsage
213
+ }),
214
+ headers: {
215
+ "Content-Type": "application/json"
216
+ },
217
+ method: "POST"
218
+ });
219
+ const result = await res.json();
220
+ if (!res.ok) {
221
+ setProposals([]);
222
+ setResponse("");
223
+ throw new Error(result.error || "Could not apply proposal");
224
+ }
225
+ resetChatState();
226
+ if (result.change) {
227
+ prependChange(result.change);
228
+ window.dispatchEvent(new CustomEvent("payload-ai:audit-log-updated"));
229
+ }
230
+ void loadRecentChanges().catch(()=>undefined);
231
+ clearInput();
232
+ } catch (err) {
233
+ setError(err instanceof Error ? err.message : "Could not apply proposal");
234
+ } finally{
235
+ setIsApplying(false);
236
+ }
237
+ };
238
+ return /*#__PURE__*/ _jsx("div", {
239
+ className: styles.chatLayout,
240
+ children: /*#__PURE__*/ _jsxs("div", {
241
+ className: styles.chat,
242
+ children: [
243
+ /*#__PURE__*/ _jsx("div", {
244
+ className: styles.chatHeader,
245
+ children: /*#__PURE__*/ _jsx("h2", {
246
+ className: styles.chatTitle,
247
+ children: "AI Assistant"
248
+ })
249
+ }),
250
+ /*#__PURE__*/ _jsxs("div", {
251
+ className: styles.chatInputRow,
252
+ children: [
253
+ /*#__PURE__*/ _jsxs("div", {
254
+ className: styles.chatInputSurface,
255
+ onClick: ()=>editorRef.current?.focus(),
256
+ children: [
257
+ /*#__PURE__*/ _jsx("div", {
258
+ id: "ai-input",
259
+ className: styles.chatInput,
260
+ ref: editorRef,
261
+ role: "textbox",
262
+ "aria-label": "AIInput",
263
+ "data-placeholder": "Ask AI...",
264
+ tabIndex: 0,
265
+ contentEditable: proposals.length === 0 && !isLoading && Boolean(settingsProvider),
266
+ onInput: (event)=>{
267
+ const value = event.target.innerText;
268
+ setPrompt(value);
269
+ if (!value.trim()) clearMentions();
270
+ updateMentionState(getTextBeforeCaret(event.target));
271
+ },
272
+ onKeyDown: (event)=>{
273
+ if (event.key === "ArrowDown" && mentionRange && mentionSuggestions.length > 0) {
274
+ const firstOption = editorRef.current?.querySelector("button");
275
+ if (firstOption) {
276
+ event.preventDefault();
277
+ firstOption.focus();
278
+ return;
279
+ }
280
+ }
281
+ if (event.key === "Enter" && !event.shiftKey) {
282
+ event.preventDefault();
283
+ void handleSubmit();
284
+ }
285
+ }
286
+ }),
287
+ (selectedFiles.length > 0 || mediaAttachments.length > 0) && /*#__PURE__*/ _jsxs("div", {
288
+ className: styles.attachmentTray,
289
+ "aria-label": "Attached media",
290
+ children: [
291
+ selectedFiles.map((file, index)=>/*#__PURE__*/ _jsxs("span", {
292
+ className: styles.attachmentPill,
293
+ children: [
294
+ /*#__PURE__*/ _jsx("span", {
295
+ className: styles.attachmentName,
296
+ title: file.name,
297
+ children: file.name
298
+ }),
299
+ /*#__PURE__*/ _jsx("button", {
300
+ type: "button",
301
+ className: styles.attachmentRemove,
302
+ disabled: isUploadingMedia,
303
+ onClick: ()=>removeSelectedFile(index),
304
+ "aria-label": `Remove ${file.name}`,
305
+ children: "X"
306
+ })
307
+ ]
308
+ }, `${file.name}-${file.size}-${file.lastModified}-${index}`)),
309
+ mediaAttachments.map((attachment, index)=>/*#__PURE__*/ _jsxs("span", {
310
+ className: styles.attachmentPill,
311
+ children: [
312
+ /*#__PURE__*/ _jsx("span", {
313
+ className: styles.attachmentName,
314
+ title: attachment.filename,
315
+ children: attachment.filename
316
+ }),
317
+ /*#__PURE__*/ _jsx("button", {
318
+ type: "button",
319
+ className: styles.attachmentRemove,
320
+ disabled: isUploadingMedia,
321
+ onClick: ()=>removeMediaAttachment(index),
322
+ "aria-label": `Remove ${attachment.filename}`,
323
+ children: "X"
324
+ })
325
+ ]
326
+ }, `${attachment.collection}-${attachment.id}`))
327
+ ]
328
+ })
329
+ ]
330
+ }),
331
+ mentionRange && /*#__PURE__*/ _jsx(MentionPopover, {
332
+ onSelect: insertMention,
333
+ style: mentionPopoverPosition ? {
334
+ left: `${mentionPopoverPosition.left}px`,
335
+ top: `${mentionPopoverPosition.top}px`
336
+ } : undefined,
337
+ suggestions: mentionSuggestions
338
+ })
339
+ ]
340
+ }),
341
+ /*#__PURE__*/ _jsxs("div", {
342
+ className: styles.chatActionsRow,
343
+ children: [
344
+ /*#__PURE__*/ _jsx("div", {
345
+ className: styles.settings,
346
+ children: /*#__PURE__*/ _jsxs("label", {
347
+ className: styles.setting,
348
+ children: [
349
+ /*#__PURE__*/ _jsx("span", {
350
+ className: styles.settingLabel,
351
+ children: "Model"
352
+ }),
353
+ /*#__PURE__*/ _jsxs("div", {
354
+ className: styles.selectWrapper,
355
+ children: [
356
+ settingsProvider && getProviderIcon(settingsProvider),
357
+ /*#__PURE__*/ _jsxs("select", {
358
+ className: styles.select,
359
+ style: {
360
+ paddingLeft: settingsProvider ? "34px" : "12px"
361
+ },
362
+ disabled: !settingsProvider,
363
+ onChange: (event)=>setSelectedModel(event.target.value),
364
+ value: selectedModel,
365
+ children: [
366
+ !settingsProvider && /*#__PURE__*/ _jsx("option", {
367
+ value: "",
368
+ children: "No provider selected"
369
+ }),
370
+ settingsProvider && aiModelConfig.providers[settingsProvider].map((model)=>/*#__PURE__*/ _jsx("option", {
371
+ value: model.value,
372
+ children: model.label
373
+ }, model.value))
374
+ ]
375
+ })
376
+ ]
377
+ })
378
+ ]
379
+ })
380
+ }),
381
+ /*#__PURE__*/ _jsxs("div", {
382
+ className: styles.actions,
383
+ children: [
384
+ mediaEnabled && /*#__PURE__*/ _jsxs(_Fragment, {
385
+ children: [
386
+ /*#__PURE__*/ _jsx("input", {
387
+ ref: fileInputRef,
388
+ type: "file",
389
+ accept: acceptedMimeTypes,
390
+ className: styles.fileInput,
391
+ multiple: true,
392
+ onChange: handleSelectFile
393
+ }),
394
+ /*#__PURE__*/ _jsx(Button, {
395
+ buttonStyle: "tab",
396
+ "aria-label": "Attach media",
397
+ margin: false,
398
+ disabled: isLoading || isUploadingMedia || proposals.length > 0,
399
+ onClick: ()=>fileInputRef.current?.click(),
400
+ children: /*#__PURE__*/ _jsx(PlusIcon, {})
401
+ })
402
+ ]
403
+ }),
404
+ /*#__PURE__*/ _jsx(Button, {
405
+ buttonStyle: "primary",
406
+ "aria-label": "Send",
407
+ margin: false,
408
+ disabled: !prompt.trim() || !settingsProvider || !selectedModel || isLoading || isUploadingMedia || Boolean(error) || Boolean(response) || proposals.length > 0,
409
+ onClick: ()=>void handleSubmit(),
410
+ children: isUploadingMedia || isLoading ? "Sending..." : "Send"
411
+ })
412
+ ]
413
+ })
414
+ ]
415
+ }),
416
+ (proposals.length > 0 || response) && /*#__PURE__*/ _jsx(ActionToast, {
417
+ apiRoute: config.routes.api,
418
+ description: response,
419
+ error: error,
420
+ getViewURL: getProposalViewURL,
421
+ isApplying: isApplying,
422
+ onDismiss: ()=>dismissChat(),
423
+ onDismissError: ()=>setError(""),
424
+ onApply: (proposal, _index)=>void handleApplyProposal(proposal),
425
+ proposals: proposals,
426
+ prompt: prompt,
427
+ tokenUsage: tokenUsage
428
+ })
429
+ ]
430
+ })
431
+ });
432
+ };
433
+ export default AIInput;
@@ -1,15 +1,20 @@
1
1
  .chatLayout {
2
- align-items: start;
3
- display: grid;
4
- gap: 16px;
5
- grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
6
- margin-bottom: 24px;
7
- max-width: 1280px;
2
+ width: 100%;
3
+ max-width: none;
4
+ display: flex;
5
+ flex-direction: column;
6
+ height: 332px;
7
+ overflow: hidden;
8
8
  }
9
9
 
10
10
  .chat {
11
+ flex: 1;
12
+ display: flex;
13
+ flex-direction: column;
11
14
  border: 1px solid var(--theme-elevation-150);
12
15
  border-radius: 8px;
16
+ min-height: 0;
17
+ overflow: hidden;
13
18
  padding: 20px;
14
19
  }
15
20
 
@@ -26,13 +31,6 @@
26
31
  margin: 0;
27
32
  }
28
33
 
29
- .chatDescription {
30
- color: var(--theme-elevation-600);
31
- font-size: 13px;
32
- line-height: 1.4;
33
- margin: 4px 0 0;
34
- }
35
-
36
34
  .settings {
37
35
  align-items: flex-end;
38
36
  display: flex;
@@ -87,6 +85,9 @@
87
85
  .chatInputRow {
88
86
  position: relative;
89
87
  width: 100%;
88
+ flex: 1 1 0;
89
+ min-height: 0;
90
+ overflow: hidden;
90
91
  }
91
92
 
92
93
  .chatActionsRow {
@@ -97,17 +98,31 @@
97
98
  margin-top: 12px;
98
99
  }
99
100
 
101
+ .actions {
102
+ align-items: center;
103
+ display: flex;
104
+ gap: 8px;
105
+ }
106
+
107
+ .fileInput {
108
+ display: none;
109
+ }
110
+
100
111
  .chatInput {
101
112
  background: transparent;
102
113
  border: 0;
103
114
  color: var(--theme-text);
104
- display: block;
105
115
  font: inherit;
106
- height: 84px;
116
+ flex: 1 1 0;
117
+ height: 100%;
118
+ min-height: 0;
107
119
  overflow-y: auto;
120
+ overscroll-behavior: contain;
108
121
  padding: 0;
109
122
  white-space: pre-wrap;
123
+ word-break: break-word;
110
124
  width: 100%;
125
+ display: block;
111
126
  }
112
127
 
113
128
  .chatInput:focus {
@@ -120,11 +135,16 @@
120
135
  }
121
136
 
122
137
  .chatInputSurface {
138
+ position: relative;
123
139
  background: var(--theme-input-bg);
124
140
  border: 1px solid var(--theme-elevation-150);
125
141
  box-sizing: border-box;
126
142
  border-radius: 4px;
127
- height: 108px;
143
+ height: 100%;
144
+ min-height: 0;
145
+ display: flex;
146
+ flex-direction: column;
147
+ gap: 8px;
128
148
  overflow: hidden;
129
149
  padding: 12px;
130
150
  width: 100%;
@@ -134,6 +154,75 @@
134
154
  border-color: var(--theme-success-500);
135
155
  }
136
156
 
157
+ .attachmentTray {
158
+ align-items: center;
159
+ display: flex;
160
+ flex: 0 0 auto;
161
+ flex-wrap: wrap;
162
+ gap: 6px;
163
+ min-height: 22px;
164
+ }
165
+
166
+ .attachmentPill {
167
+ align-items: center;
168
+ background: var(--theme-elevation-100);
169
+ border: 1px solid var(--theme-elevation-150);
170
+ border-radius: 999px;
171
+ color: var(--theme-text);
172
+ display: inline-flex;
173
+ font-size: 11px;
174
+ gap: 4px;
175
+ line-height: 1.2;
176
+ max-width: min(240px, 100%);
177
+ min-height: 24px;
178
+ min-width: 0;
179
+ padding: 2px 5px 3px 8px;
180
+ }
181
+
182
+ .attachmentName {
183
+ display: block;
184
+ min-width: 0;
185
+ overflow: hidden;
186
+ text-overflow: ellipsis;
187
+ white-space: nowrap;
188
+ }
189
+
190
+ .attachmentRemove {
191
+ align-items: center;
192
+ background: transparent;
193
+ border: 0;
194
+ color: var(--theme-elevation-600);
195
+ cursor: pointer;
196
+ display: inline-flex;
197
+ flex: 0 0 auto;
198
+ font: inherit;
199
+ font-size: 10px;
200
+ height: 14px;
201
+ justify-content: center;
202
+ line-height: 1;
203
+ padding: 0;
204
+ width: 14px;
205
+ }
206
+
207
+ .attachmentRemove:hover,
208
+ .attachmentRemove:focus-visible {
209
+ color: #fff;
210
+ }
211
+
212
+ .attachmentRemove:disabled {
213
+ cursor: not-allowed;
214
+ opacity: 0.55;
215
+ }
216
+
217
+ .attachmentRemove:disabled:hover {
218
+ color: var(--theme-elevation-600);
219
+ }
220
+
221
+ .attachmentRemove:focus-visible {
222
+ outline: 1px solid currentColor;
223
+ outline-offset: 1px;
224
+ }
225
+
137
226
  .inlineBadge {
138
227
  margin: 0 2px;
139
228
  vertical-align: middle;
@@ -198,31 +287,6 @@
198
287
  border-color: color-mix(in srgb, #2f5da8 28%, var(--theme-elevation-150));
199
288
  }
200
289
 
201
- .chatButton {
202
- align-items: center;
203
- background: var(--theme-text);
204
- border: 0;
205
- border-radius: 4px;
206
- color: var(--theme-bg);
207
- cursor: pointer;
208
- display: inline-flex;
209
- font: inherit;
210
- gap: 6px;
211
- justify-content: center;
212
- line-height: 24px;
213
- min-height: 24px;
214
- padding: 0 14px;
215
- }
216
-
217
- .chatButton svg {
218
- flex: 0 0 auto;
219
- }
220
-
221
- .chatButton:disabled {
222
- cursor: not-allowed;
223
- opacity: 0.45;
224
- }
225
-
226
290
  .chatError {
227
291
  color: var(--theme-error-500);
228
292
  font-size: 13px;
@@ -278,6 +342,10 @@
278
342
  flex-direction: column;
279
343
  }
280
344
 
345
+ .actions {
346
+ justify-content: flex-end;
347
+ }
348
+
281
349
  .settings {
282
350
  align-items: stretch;
283
351
  width: 100%;
@@ -292,7 +360,7 @@
292
360
  width: 100%;
293
361
  }
294
362
 
295
- .chatButton {
296
- align-self: stretch;
363
+ .attachmentPill {
364
+ max-width: 100%;
297
365
  }
298
366
  }
@@ -0,0 +1,14 @@
1
+ import { MentionOption } from "../mention-popover/MentionPopover.js";
2
+ export declare const createBadgePrefix: (suggestion: MentionOption, styles: {
3
+ [key: string]: string;
4
+ }) => HTMLSpanElement;
5
+ export declare const getTextNodeAtOffset: (element: HTMLElement, offset: number) => {
6
+ node: Node;
7
+ offset: number;
8
+ };
9
+ export declare const replaceTextRangeWithBadge: ({ badge, editor, end, start }: {
10
+ badge: HTMLSpanElement;
11
+ editor: HTMLElement;
12
+ end: number;
13
+ start: number;
14
+ }) => void;