@mvriu5/payload-ai 0.5.0

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 (43) hide show
  1. package/README.md +99 -0
  2. package/dist/ai/proposals.d.ts +12 -0
  3. package/dist/ai/proposals.js +50 -0
  4. package/dist/ai/providerOptions.d.ts +85 -0
  5. package/dist/ai/providerOptions.js +141 -0
  6. package/dist/ai/providerRuntime.d.ts +18 -0
  7. package/dist/ai/providerRuntime.js +53 -0
  8. package/dist/ai/sensitiveData.d.ts +2 -0
  9. package/dist/ai/sensitiveData.js +30 -0
  10. package/dist/components/AIActionProposalList.d.ts +25 -0
  11. package/dist/components/AIActionProposalList.js +94 -0
  12. package/dist/components/AIActionProposalList.module.css +175 -0
  13. package/dist/components/AIApiKeyField.d.ts +2 -0
  14. package/dist/components/AIApiKeyField.js +55 -0
  15. package/dist/components/AIInput.d.ts +1 -0
  16. package/dist/components/AIInput.js +386 -0
  17. package/dist/components/AIInput.module.css +237 -0
  18. package/dist/components/CollectionMentionPopover.d.ts +16 -0
  19. package/dist/components/CollectionMentionPopover.js +77 -0
  20. package/dist/components/CollectionMentionPopover.module.css +67 -0
  21. package/dist/components/hooks/useAISettings.d.ts +10 -0
  22. package/dist/components/hooks/useAISettings.js +56 -0
  23. package/dist/components/hooks/useDocumentMentionSuggestions.d.ts +16 -0
  24. package/dist/components/hooks/useDocumentMentionSuggestions.js +53 -0
  25. package/dist/components/hooks/utils.d.ts +1 -0
  26. package/dist/components/hooks/utils.js +3 -0
  27. package/dist/endpoints/aiApplyActionEndpointHandler.d.ts +6 -0
  28. package/dist/endpoints/aiApplyActionEndpointHandler.js +164 -0
  29. package/dist/endpoints/aiChatEndpointHandler.d.ts +31 -0
  30. package/dist/endpoints/aiChatEndpointHandler.js +297 -0
  31. package/dist/endpoints/aiMentionSuggestionsEndpointHandler.d.ts +6 -0
  32. package/dist/endpoints/aiMentionSuggestionsEndpointHandler.js +63 -0
  33. package/dist/exports/client.d.ts +2 -0
  34. package/dist/exports/client.js +2 -0
  35. package/dist/index.d.ts +9 -0
  36. package/dist/index.js +75 -0
  37. package/dist/payload/normalizeData.d.ts +29 -0
  38. package/dist/payload/normalizeData.js +192 -0
  39. package/dist/payload/schemaContext.d.ts +52 -0
  40. package/dist/payload/schemaContext.js +162 -0
  41. package/dist/payload/shared.d.ts +2 -0
  42. package/dist/payload/shared.js +11 -0
  43. package/package.json +126 -0
@@ -0,0 +1,175 @@
1
+ .list {
2
+ bottom: 24px;
3
+ display: flex;
4
+ flex-direction: column;
5
+ gap: 8px;
6
+ max-width: min(520px, calc(100vw - 32px));
7
+ position: fixed;
8
+ right: 24px;
9
+ width: 100%;
10
+ z-index: 1000;
11
+ }
12
+
13
+ .item {
14
+ align-items: stretch;
15
+ background: var(--theme-bg);
16
+ border: 1px solid var(--theme-elevation-150);
17
+ border-radius: 6px;
18
+ box-shadow: 0 12px 32px rgba(0, 0, 0, 0.18);
19
+ display: flex;
20
+ flex-direction: column;
21
+ gap: 12px;
22
+ max-height: calc(100vh - 48px);
23
+ padding: 12px;
24
+ }
25
+
26
+ .content {
27
+ flex: 1 1 auto;
28
+ max-height: calc(100vh - 130px);
29
+ min-width: 0;
30
+ overflow: auto;
31
+ width: 100%;
32
+ }
33
+
34
+ .errorItem {
35
+ border-color: var(--theme-error-500);
36
+ }
37
+
38
+ .label {
39
+ font-size: 13px;
40
+ font-weight: 600;
41
+ line-height: 1.4;
42
+ }
43
+
44
+ .description {
45
+ color: var(--theme-elevation-700);
46
+ font-size: 12px;
47
+ line-height: 1.45;
48
+ margin-top: 8px;
49
+ white-space: pre-wrap;
50
+ }
51
+
52
+ .meta {
53
+ color: var(--theme-elevation-600);
54
+ font-size: 12px;
55
+ line-height: 1.4;
56
+ margin-top: 2px;
57
+ }
58
+
59
+ .proposalDetails {
60
+ background: var(--theme-elevation-50);
61
+ border: 1px solid var(--theme-elevation-100);
62
+ border-radius: 4px;
63
+ color: var(--theme-elevation-700);
64
+ font-size: 11px;
65
+ line-height: 1.4;
66
+ margin: 8px 0 0;
67
+ max-height: min(360px, calc(100vh - 220px));
68
+ max-width: 100%;
69
+ overflow: auto;
70
+ padding: 8px;
71
+ white-space: pre-wrap;
72
+ word-break: break-word;
73
+ }
74
+
75
+ .details {
76
+ margin-top: 8px;
77
+ }
78
+
79
+ .summary {
80
+ color: var(--theme-elevation-600);
81
+ cursor: pointer;
82
+ font-size: 12px;
83
+ line-height: 1.4;
84
+ }
85
+
86
+ .footer {
87
+ align-items: center;
88
+ display: flex;
89
+ flex: 0 0 auto;
90
+ gap: 8px;
91
+ justify-content: space-between;
92
+ }
93
+
94
+ .viewAction {
95
+ align-items: center;
96
+ display: flex;
97
+ min-width: 0;
98
+ }
99
+
100
+ .actions {
101
+ align-items: center;
102
+ align-self: flex-end;
103
+ display: flex;
104
+ flex: 0 0 auto;
105
+ gap: 8px;
106
+ }
107
+
108
+ .button {
109
+ background: var(--theme-text);
110
+ border: 0;
111
+ border-radius: 4px;
112
+ color: var(--theme-bg);
113
+ cursor: pointer;
114
+ flex: 0 0 auto;
115
+ font: inherit;
116
+ min-height: 34px;
117
+ padding: 0 14px;
118
+ }
119
+
120
+ .secondaryButton {
121
+ background: transparent;
122
+ border: 1px solid var(--theme-elevation-150);
123
+ border-radius: 4px;
124
+ color: var(--theme-text);
125
+ cursor: pointer;
126
+ flex: 0 0 auto;
127
+ font: inherit;
128
+ line-height: 34px;
129
+ min-height: 34px;
130
+ padding: 0 14px;
131
+ text-decoration: none;
132
+ }
133
+
134
+ .button:disabled {
135
+ cursor: not-allowed;
136
+ opacity: 0.45;
137
+ }
138
+
139
+ .secondaryButton:disabled {
140
+ cursor: not-allowed;
141
+ opacity: 0.45;
142
+ }
143
+
144
+ @media (max-width: 768px) {
145
+ .list {
146
+ bottom: 16px;
147
+ left: 16px;
148
+ max-width: none;
149
+ right: 16px;
150
+ width: auto;
151
+ }
152
+
153
+ .item {
154
+ max-height: calc(100vh - 32px);
155
+ }
156
+
157
+ .content {
158
+ max-height: calc(100vh - 160px);
159
+ }
160
+
161
+ .actions {
162
+ align-self: stretch;
163
+ flex-direction: column;
164
+ }
165
+
166
+ .footer {
167
+ align-items: stretch;
168
+ flex-direction: column;
169
+ }
170
+
171
+ .button,
172
+ .secondaryButton {
173
+ width: 100%;
174
+ }
175
+ }
@@ -0,0 +1,2 @@
1
+ import type { TextFieldClientComponent } from "payload";
2
+ export declare const AIApiKeyField: TextFieldClientComponent;
@@ -0,0 +1,55 @@
1
+ "use client";
2
+ import { useField } from "@payloadcms/ui";
3
+ const getLabel = (label, fallback)=>{
4
+ if (typeof label === "string") return label;
5
+ if (label && typeof label === "object") {
6
+ const firstLabel = Object.values(label)[0];
7
+ if (typeof firstLabel === "string") return firstLabel;
8
+ }
9
+ return fallback;
10
+ };
11
+ const getFieldClassName = ({ className, isReadOnly, showError })=>[
12
+ "field-type",
13
+ "password",
14
+ className,
15
+ showError ? "error" : null,
16
+ isReadOnly ? "read-only" : null
17
+ ].filter(Boolean).join(" ");
18
+ export const AIApiKeyField = ({ field, inputRef, path, readOnly })=>{
19
+ const { disabled, errorMessage, setValue, showError, value } = useField({
20
+ path
21
+ });
22
+ const fieldID = `field-${path.replace(/\./g, "__")}`;
23
+ const isReadOnly = readOnly || disabled || field.admin?.disabled;
24
+ const handleChange = (event)=>{
25
+ setValue(event.target.value);
26
+ };
27
+ return /*#__PURE__*/ React.createElement("div", {
28
+ className: getFieldClassName({
29
+ className: field.admin?.className,
30
+ isReadOnly,
31
+ showError
32
+ })
33
+ }, /*#__PURE__*/ React.createElement("label", {
34
+ className: "field-label",
35
+ htmlFor: fieldID
36
+ }, field?.label?.toString(), field.required ? /*#__PURE__*/ React.createElement("span", {
37
+ className: "required"
38
+ }, "*") : null), /*#__PURE__*/ React.createElement("div", {
39
+ className: "field-type__wrap"
40
+ }, showError && errorMessage ? /*#__PURE__*/ React.createElement("div", {
41
+ className: "field-error"
42
+ }, errorMessage) : null, /*#__PURE__*/ React.createElement("input", {
43
+ autoComplete: "new-password",
44
+ disabled: isReadOnly,
45
+ id: fieldID,
46
+ name: path,
47
+ onChange: handleChange,
48
+ placeholder: field.admin?.placeholder?.toString(),
49
+ ref: inputRef,
50
+ type: "password",
51
+ value: value || ""
52
+ }), /*#__PURE__*/ React.createElement("div", {
53
+ className: "field-description"
54
+ }, field.admin?.description?.toString())));
55
+ };
@@ -0,0 +1 @@
1
+ export declare const AIInput: () => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,386 @@
1
+ "use client";
2
+ import { useConfig } from "@payloadcms/ui";
3
+ import { formatAdminURL } from "payload/shared";
4
+ import { useMemo, useRef, useState } from "react";
5
+ import { getResolvedAIModelConfig } from "../ai/providerOptions.js";
6
+ import { getSerializableLabel, isInternalCollection } from "../payload/shared.js";
7
+ import { AIActionProposalList } from "./AIActionProposalList.js";
8
+ import styles from "./AIInput.module.css";
9
+ import { CollectionMentionPopover } from "./CollectionMentionPopover.js";
10
+ import { useAISettings } from "./hooks/useAISettings.js";
11
+ import { useDocumentMentionSuggestions } from "./hooks/useDocumentMentionSuggestions.js";
12
+ const collectBlockOptions = ({ fields, parent })=>{
13
+ const options = [];
14
+ for (const field of fields){
15
+ if (field.type === "blocks" && field.blocks) {
16
+ for (const block of field.blocks){
17
+ options.push({
18
+ label: getSerializableLabel(block.labels?.singular, block.slug),
19
+ parent,
20
+ slug: block.slug,
21
+ type: "block"
22
+ });
23
+ options.push(...collectBlockOptions({
24
+ fields: block.fields || [],
25
+ parent: `${parent}/${block.slug}`
26
+ }));
27
+ }
28
+ }
29
+ if (field.fields) {
30
+ options.push(...collectBlockOptions({
31
+ fields: field.fields,
32
+ parent
33
+ }));
34
+ }
35
+ }
36
+ return options;
37
+ };
38
+ export const AIInput = ()=>{
39
+ const { config } = useConfig();
40
+ const editorRef = useRef(null);
41
+ const mentionPopoverRef = useRef(null);
42
+ const [prompt, setPrompt] = useState("");
43
+ const configuredModels = config.admin?.custom?.payloadAiPlugin?.models;
44
+ const aiModelConfig = useMemo(()=>getResolvedAIModelConfig(configuredModels), [
45
+ configuredModels
46
+ ]);
47
+ const { selectedModel, setSelectedModel, settingsProvider } = useAISettings({
48
+ adminUserSlug: config.admin?.user,
49
+ apiRoute: config.routes.api,
50
+ defaultModels: aiModelConfig.defaults
51
+ });
52
+ const [mentionQuery, setMentionQuery] = useState("");
53
+ const [mentionRange, setMentionRange] = useState(null);
54
+ const [mentions, setMentions] = useState([]);
55
+ const [appliedProposalIndexes, setAppliedProposalIndexes] = useState([]);
56
+ const [response, setResponse] = useState("");
57
+ const [error, setError] = useState("");
58
+ const [proposals, setProposals] = useState([]);
59
+ const [isLoading, setIsLoading] = useState(false);
60
+ const [isApplying, setIsApplying] = useState(false);
61
+ const collections = config.collections.filter((collection)=>!isInternalCollection(collection.slug)).map((collection)=>({
62
+ label: getSerializableLabel(collection.labels?.singular, collection.slug),
63
+ slug: collection.slug,
64
+ type: "collection"
65
+ }));
66
+ const globals = config.globals?.map((global)=>({
67
+ label: getSerializableLabel(global.label, global.slug),
68
+ slug: global.slug,
69
+ type: "global"
70
+ })) || [];
71
+ const blocks = [
72
+ ...config.collections.flatMap((collection)=>collectBlockOptions({
73
+ fields: collection.fields,
74
+ parent: collection.slug
75
+ })),
76
+ ...config.globals?.flatMap((global)=>collectBlockOptions({
77
+ fields: global.fields,
78
+ parent: global.slug
79
+ })) || []
80
+ ];
81
+ const mentionOptions = [
82
+ ...collections,
83
+ ...globals,
84
+ ...blocks
85
+ ];
86
+ const normalizedMentionQuery = mentionQuery.toLowerCase();
87
+ const filteredCollections = collections.filter((collection)=>collection.slug.toLowerCase().includes(normalizedMentionQuery) || collection.label.toLowerCase().includes(normalizedMentionQuery));
88
+ const filteredMentionOptions = mentionOptions.filter((option)=>option.slug.toLowerCase().includes(normalizedMentionQuery));
89
+ const documentSuggestionCollection = filteredCollections.length === 1 ? filteredCollections[0]?.slug : null;
90
+ const { documentSuggestions, resetDocumentSuggestions } = useDocumentMentionSuggestions({
91
+ apiRoute: config.routes.api,
92
+ documentSuggestionCollection,
93
+ mentionQuery,
94
+ mentionRange
95
+ });
96
+ const mentionSuggestions = [
97
+ ...filteredMentionOptions,
98
+ ...documentSuggestions
99
+ ];
100
+ const getCaretOffset = (element)=>{
101
+ const selection = window.getSelection();
102
+ if (!selection || selection.rangeCount === 0) return 0;
103
+ const range = selection.getRangeAt(0);
104
+ const clonedRange = range.cloneRange();
105
+ clonedRange.selectNodeContents(element);
106
+ clonedRange.setEnd(range.endContainer, range.endOffset);
107
+ return clonedRange.toString().length;
108
+ };
109
+ const moveCaretToEnd = (element)=>{
110
+ const selection = window.getSelection();
111
+ const range = document.createRange();
112
+ range.selectNodeContents(element);
113
+ range.collapse(false);
114
+ selection?.removeAllRanges();
115
+ selection?.addRange(range);
116
+ };
117
+ const getTextNodeAtOffset = (element, offset)=>{
118
+ const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT);
119
+ let currentOffset = 0;
120
+ let node = walker.nextNode();
121
+ while(node){
122
+ const nextOffset = currentOffset + (node.textContent?.length || 0);
123
+ if (offset <= nextOffset) {
124
+ return {
125
+ node,
126
+ offset: offset - currentOffset
127
+ };
128
+ }
129
+ currentOffset = nextOffset;
130
+ node = walker.nextNode();
131
+ }
132
+ const textNode = document.createTextNode("");
133
+ element.append(textNode);
134
+ return {
135
+ node: textNode,
136
+ offset: 0
137
+ };
138
+ };
139
+ const replaceTextRangeWithBadge = ({ badge, editor, end, start })=>{
140
+ const startPosition = getTextNodeAtOffset(editor, start);
141
+ const endPosition = getTextNodeAtOffset(editor, end);
142
+ const range = document.createRange();
143
+ const trailingSpace = document.createTextNode(" ");
144
+ range.setStart(startPosition.node, startPosition.offset);
145
+ range.setEnd(endPosition.node, endPosition.offset);
146
+ range.deleteContents();
147
+ range.insertNode(trailingSpace);
148
+ range.insertNode(badge);
149
+ const selection = window.getSelection();
150
+ const caretRange = document.createRange();
151
+ caretRange.setStartAfter(trailingSpace);
152
+ caretRange.collapse(true);
153
+ selection?.removeAllRanges();
154
+ selection?.addRange(caretRange);
155
+ };
156
+ const updateMentionState = (value, caretPosition)=>{
157
+ const valueBeforeCaret = value.slice(0, caretPosition);
158
+ const match = /(?:^|\s)@([\w-]*)$/.exec(valueBeforeCaret);
159
+ if (!match || typeof match.index !== "number") {
160
+ setMentionQuery("");
161
+ setMentionRange(null);
162
+ return;
163
+ }
164
+ const atIndex = valueBeforeCaret.lastIndexOf("@");
165
+ setMentionQuery(match[1] || "");
166
+ setMentionRange({
167
+ end: caretPosition,
168
+ start: atIndex
169
+ });
170
+ };
171
+ const clearInput = ()=>{
172
+ setPrompt("");
173
+ setMentions([]);
174
+ if (editorRef.current) editorRef.current.textContent = "";
175
+ };
176
+ const getProposalViewURL = (proposal)=>{
177
+ const adminRoute = config.routes.admin || "/admin";
178
+ if (proposal.action === "updateGlobal" && proposal.slug) {
179
+ return `${adminRoute}/globals/${proposal.slug}`;
180
+ }
181
+ if (proposal.collection && proposal.id) {
182
+ return `${adminRoute}/collections/${proposal.collection}/${proposal.id}`;
183
+ }
184
+ return null;
185
+ };
186
+ const insertMention = (suggestion)=>{
187
+ const editor = editorRef.current;
188
+ if (!mentionRange || !editor) return;
189
+ const beforeMention = prompt.slice(0, mentionRange.start);
190
+ const afterMention = prompt.slice(mentionRange.end);
191
+ const badgeType = suggestion.type === "doc" ? "document" : suggestion.type;
192
+ const badgePrefix = `${badgeType}:`;
193
+ const badgeText = `${badgePrefix} ${suggestion.label}`;
194
+ const promptText = suggestion.type === "doc" ? `${badgeText} (${suggestion.collection}/${suggestion.id})` : badgeText;
195
+ const badge = document.createElement("span");
196
+ badge.className = [
197
+ styles.badge,
198
+ styles[suggestion.type],
199
+ styles.inlineBadge
200
+ ].join(" ");
201
+ badge.contentEditable = "false";
202
+ badge.append(Object.assign(document.createElement("span"), {
203
+ className: styles.prefix,
204
+ textContent: `${badgePrefix} `
205
+ }), Object.assign(document.createElement("span"), {
206
+ className: styles.name,
207
+ textContent: suggestion.label
208
+ }));
209
+ replaceTextRangeWithBadge({
210
+ badge,
211
+ editor,
212
+ end: mentionRange.end,
213
+ start: mentionRange.start
214
+ });
215
+ editor.focus();
216
+ setPrompt(`${beforeMention}${promptText} ${afterMention}`);
217
+ setMentions((currentMentions)=>{
218
+ const mentionExists = currentMentions.some((mention)=>mention.type === suggestion.type && mention.slug === suggestion.slug && mention.parent === suggestion.parent && mention.collection === suggestion.collection && mention.id === suggestion.id);
219
+ if (mentionExists) return currentMentions;
220
+ return [
221
+ ...currentMentions,
222
+ suggestion
223
+ ];
224
+ });
225
+ setMentionQuery("");
226
+ setMentionRange(null);
227
+ resetDocumentSuggestions();
228
+ };
229
+ const handleSubmit = async ()=>{
230
+ const trimmedPrompt = prompt.trim();
231
+ if (!trimmedPrompt) return;
232
+ setIsLoading(true);
233
+ setAppliedProposalIndexes([]);
234
+ try {
235
+ const res = await fetch(formatAdminURL({
236
+ apiRoute: config.routes.api,
237
+ path: "/ai-chat"
238
+ }), {
239
+ body: JSON.stringify({
240
+ mentions,
241
+ model: selectedModel,
242
+ prompt: trimmedPrompt
243
+ }),
244
+ headers: {
245
+ "Content-Type": "application/json"
246
+ },
247
+ method: "POST"
248
+ });
249
+ const result = await res.json();
250
+ if (!res.ok) {
251
+ setProposals([]);
252
+ setResponse("");
253
+ throw new Error(result.error || "AI request failed");
254
+ }
255
+ setError("");
256
+ setResponse(result.text || "");
257
+ setProposals(result.proposals || []);
258
+ } catch (err) {
259
+ setProposals([]);
260
+ setResponse("");
261
+ setError(err instanceof Error ? err.message : "AI request failed");
262
+ } finally{
263
+ setIsLoading(false);
264
+ }
265
+ };
266
+ const handleApplyProposal = async (proposal)=>{
267
+ setIsApplying(true);
268
+ setError("");
269
+ try {
270
+ const res = await fetch(formatAdminURL({
271
+ apiRoute: config.routes.api,
272
+ path: "/ai-apply-action"
273
+ }), {
274
+ body: JSON.stringify({
275
+ proposal
276
+ }),
277
+ headers: {
278
+ "Content-Type": "application/json"
279
+ },
280
+ method: "POST"
281
+ });
282
+ const result = await res.json();
283
+ if (!res.ok) {
284
+ setProposals([]);
285
+ setResponse("");
286
+ throw new Error(result.error || "Could not apply proposal");
287
+ }
288
+ setAppliedProposalIndexes([]);
289
+ setError("");
290
+ setProposals([]);
291
+ setResponse("");
292
+ clearInput();
293
+ } catch (err) {
294
+ setError(err instanceof Error ? err.message : "Could not apply proposal");
295
+ } finally{
296
+ setIsApplying(false);
297
+ }
298
+ };
299
+ return /*#__PURE__*/ React.createElement("div", {
300
+ className: styles.chat
301
+ }, /*#__PURE__*/ React.createElement("div", {
302
+ className: styles.chatHeader
303
+ }, /*#__PURE__*/ React.createElement("div", null, /*#__PURE__*/ React.createElement("h2", {
304
+ className: styles.chatTitle
305
+ }, "AI Assistant"), /*#__PURE__*/ React.createElement("p", {
306
+ className: styles.chatDescription
307
+ }, "Ask AI to draft, improve, or analyze content."))), /*#__PURE__*/ React.createElement("div", {
308
+ className: styles.chatInputRow
309
+ }, /*#__PURE__*/ React.createElement("div", {
310
+ className: styles.chatInputSurface
311
+ }, /*#__PURE__*/ React.createElement("div", {
312
+ className: styles.chatInput,
313
+ contentEditable: true,
314
+ "data-placeholder": "Ask AI...",
315
+ onInput: (event)=>{
316
+ const value = event.currentTarget.innerText;
317
+ setPrompt(value);
318
+ if (!value.trim()) {
319
+ setMentions([]);
320
+ }
321
+ updateMentionState(value, getCaretOffset(event.currentTarget));
322
+ },
323
+ onKeyDown: (event)=>{
324
+ if (event.key === "ArrowDown" && mentionRange && mentionSuggestions.length > 0) {
325
+ const firstOption = mentionPopoverRef.current?.querySelector("button");
326
+ if (firstOption) {
327
+ event.preventDefault();
328
+ firstOption.focus();
329
+ return;
330
+ }
331
+ }
332
+ if (event.key === "Enter" && !event.shiftKey) {
333
+ event.preventDefault();
334
+ void handleSubmit();
335
+ }
336
+ },
337
+ ref: editorRef,
338
+ role: "textbox",
339
+ suppressContentEditableWarning: true
340
+ })), mentionRange ? /*#__PURE__*/ React.createElement(CollectionMentionPopover, {
341
+ containerRef: mentionPopoverRef,
342
+ onSelect: insertMention,
343
+ suggestions: mentionSuggestions
344
+ }) : null), /*#__PURE__*/ React.createElement("div", {
345
+ className: styles.chatActionsRow
346
+ }, /*#__PURE__*/ React.createElement("div", {
347
+ className: styles.settings
348
+ }, /*#__PURE__*/ React.createElement("label", {
349
+ className: styles.setting
350
+ }, /*#__PURE__*/ React.createElement("span", {
351
+ className: styles.settingLabel
352
+ }, "Model"), /*#__PURE__*/ React.createElement("select", {
353
+ className: styles.select,
354
+ disabled: !settingsProvider,
355
+ onChange: (event)=>setSelectedModel(event.target.value),
356
+ value: selectedModel
357
+ }, !settingsProvider ? /*#__PURE__*/ React.createElement("option", {
358
+ value: ""
359
+ }, "Select provider in account settings") : null, settingsProvider ? aiModelConfig.providers[settingsProvider].map((model)=>/*#__PURE__*/ React.createElement("option", {
360
+ key: model.value,
361
+ value: model.value
362
+ }, model.label)) : null))), /*#__PURE__*/ React.createElement("button", {
363
+ className: styles.chatButton,
364
+ disabled: !prompt.trim() || !settingsProvider || !selectedModel || isLoading,
365
+ onClick: ()=>void handleSubmit(),
366
+ type: "button"
367
+ }, isLoading ? "Sending..." : "Send")), /*#__PURE__*/ React.createElement(AIActionProposalList, {
368
+ appliedProposalIndexes: appliedProposalIndexes,
369
+ description: response,
370
+ error: error,
371
+ getViewURL: getProposalViewURL,
372
+ isApplying: isApplying,
373
+ onDismiss: ()=>{
374
+ setAppliedProposalIndexes([]);
375
+ setError("");
376
+ setProposals([]);
377
+ setResponse("");
378
+ clearInput();
379
+ },
380
+ onDismissError: ()=>{
381
+ setError("");
382
+ },
383
+ onApply: (proposal, _index)=>void handleApplyProposal(proposal),
384
+ proposals: proposals
385
+ }));
386
+ };