@mvriu5/payload-ai 1.1.1 → 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 (55) 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} +2 -2
  5. package/dist/components/{ActionToast.js → action-toast/ActionToast.js} +27 -35
  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} +119 -42
  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 +341 -112
  37. package/dist/handlers/chatHandler.js +940 -84
  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 +117 -50
  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/logging.d.ts +9 -0
  46. package/dist/payload/logging.js +14 -0
  47. package/dist/payload/normalizeData.d.ts +16 -12
  48. package/dist/payload/normalizeData.js +47 -14
  49. package/dist/payload/proposalData.d.ts +31 -0
  50. package/dist/payload/proposalData.js +578 -0
  51. package/dist/payload/schemaContext.d.ts +3 -7
  52. package/dist/payload/schemaContext.js +147 -66
  53. package/package.json +6 -6
  54. package/dist/components/AIInput.js +0 -747
  55. /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,15 +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
115
  font: inherit;
105
- min-height: 84px;
116
+ flex: 1 1 0;
117
+ height: 100%;
118
+ min-height: 0;
119
+ overflow-y: auto;
120
+ overscroll-behavior: contain;
106
121
  padding: 0;
107
122
  white-space: pre-wrap;
123
+ word-break: break-word;
108
124
  width: 100%;
125
+ display: block;
109
126
  }
110
127
 
111
128
  .chatInput:focus {
@@ -118,9 +135,17 @@
118
135
  }
119
136
 
120
137
  .chatInputSurface {
138
+ position: relative;
121
139
  background: var(--theme-input-bg);
122
140
  border: 1px solid var(--theme-elevation-150);
141
+ box-sizing: border-box;
123
142
  border-radius: 4px;
143
+ height: 100%;
144
+ min-height: 0;
145
+ display: flex;
146
+ flex-direction: column;
147
+ gap: 8px;
148
+ overflow: hidden;
124
149
  padding: 12px;
125
150
  width: 100%;
126
151
  }
@@ -129,6 +154,75 @@
129
154
  border-color: var(--theme-success-500);
130
155
  }
131
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
+
132
226
  .inlineBadge {
133
227
  margin: 0 2px;
134
228
  vertical-align: middle;
@@ -193,31 +287,6 @@
193
287
  border-color: color-mix(in srgb, #2f5da8 28%, var(--theme-elevation-150));
194
288
  }
195
289
 
196
- .chatButton {
197
- align-items: center;
198
- background: var(--theme-text);
199
- border: 0;
200
- border-radius: 4px;
201
- color: var(--theme-bg);
202
- cursor: pointer;
203
- display: inline-flex;
204
- font: inherit;
205
- gap: 6px;
206
- justify-content: center;
207
- line-height: 24px;
208
- min-height: 24px;
209
- padding: 0 14px;
210
- }
211
-
212
- .chatButton svg {
213
- flex: 0 0 auto;
214
- }
215
-
216
- .chatButton:disabled {
217
- cursor: not-allowed;
218
- opacity: 0.45;
219
- }
220
-
221
290
  .chatError {
222
291
  color: var(--theme-error-500);
223
292
  font-size: 13px;
@@ -239,6 +308,11 @@
239
308
  white-space: pre-wrap;
240
309
  }
241
310
 
311
+ .debugDetails {
312
+ margin: 8px 0 0;
313
+ white-space: pre-wrap;
314
+ }
315
+
242
316
  .chatResponse {
243
317
  background: var(--theme-elevation-50);
244
318
  border: 1px solid var(--theme-elevation-100);
@@ -268,6 +342,10 @@
268
342
  flex-direction: column;
269
343
  }
270
344
 
345
+ .actions {
346
+ justify-content: flex-end;
347
+ }
348
+
271
349
  .settings {
272
350
  align-items: stretch;
273
351
  width: 100%;
@@ -282,8 +360,7 @@
282
360
  width: 100%;
283
361
  }
284
362
 
285
- .chatButton {
286
- align-self: stretch;
363
+ .attachmentPill {
364
+ max-width: 100%;
287
365
  }
288
-
289
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;