@arqel-dev/ai 0.8.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.
@@ -0,0 +1,1342 @@
1
+ import { Textarea, Button, Alert, AlertDescription, Badge, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, Label, Card, CardContent } from '@arqel-dev/ui';
2
+ import { lazy, useId, useState, useCallback, useMemo } from 'react';
3
+ import { jsx, jsxs } from 'react/jsx-runtime';
4
+ import { registerField } from '@arqel-dev/ui/form';
5
+
6
+ var __defProp = Object.defineProperty;
7
+ var __getOwnPropNames = Object.getOwnPropertyNames;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
11
+ var __export = (target, all) => {
12
+ for (var name in all)
13
+ __defProp(target, name, { get: all[name], enumerable: true });
14
+ };
15
+
16
+ // src/AiTextInput.tsx
17
+ var AiTextInput_exports = {};
18
+ __export(AiTextInput_exports, {
19
+ AiTextInput: () => AiTextInput,
20
+ default: () => AiTextInput_default
21
+ });
22
+ function buildGenerateUrl(override, resource, field) {
23
+ if (override !== void 0 && override !== "") {
24
+ return override;
25
+ }
26
+ if (resource && field) {
27
+ return `/admin/${resource}/fields/${field}/generate`;
28
+ }
29
+ return null;
30
+ }
31
+ function Spinner() {
32
+ return /* @__PURE__ */ jsxs(
33
+ "svg",
34
+ {
35
+ width: "14",
36
+ height: "14",
37
+ viewBox: "0 0 24 24",
38
+ fill: "none",
39
+ xmlns: "http://www.w3.org/2000/svg",
40
+ "aria-hidden": "true",
41
+ className: "animate-spin",
42
+ children: [
43
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeOpacity: "0.25", strokeWidth: "4" }),
44
+ /* @__PURE__ */ jsx(
45
+ "path",
46
+ {
47
+ d: "M22 12a10 10 0 0 1-10 10",
48
+ stroke: "currentColor",
49
+ strokeWidth: "4",
50
+ strokeLinecap: "round"
51
+ }
52
+ )
53
+ ]
54
+ }
55
+ );
56
+ }
57
+ function AiTextInput(props) {
58
+ const {
59
+ name,
60
+ value,
61
+ onChange,
62
+ props: fieldProps,
63
+ resource,
64
+ field,
65
+ formData,
66
+ generateUrl,
67
+ csrfToken
68
+ } = props;
69
+ const buttonLabel = fieldProps?.buttonLabel ?? DEFAULT_BUTTON_LABEL;
70
+ const maxLength = fieldProps?.maxLength ?? null;
71
+ const reactId = useId();
72
+ const textareaId = `arqel-ai-text-${reactId}`;
73
+ const [internalValue, setInternalValue] = useState(value);
74
+ const isControlled = onChange !== void 0;
75
+ const currentValue = isControlled ? value : internalValue;
76
+ const [isLoading, setIsLoading] = useState(false);
77
+ const [error, setError] = useState(null);
78
+ const [hasGenerated, setHasGenerated] = useState(false);
79
+ const handleTextareaChange = useCallback(
80
+ (event) => {
81
+ const next = event.target.value;
82
+ if (isControlled && onChange) {
83
+ onChange(next);
84
+ } else {
85
+ setInternalValue(next);
86
+ }
87
+ },
88
+ [isControlled, onChange]
89
+ );
90
+ const applyGeneratedText = useCallback(
91
+ (next) => {
92
+ if (isControlled && onChange) {
93
+ onChange(next);
94
+ } else {
95
+ setInternalValue(next);
96
+ }
97
+ },
98
+ [isControlled, onChange]
99
+ );
100
+ const handleGenerate = useCallback(async () => {
101
+ const url = buildGenerateUrl(generateUrl, resource, field);
102
+ if (url === null) {
103
+ setError("Missing generate URL: provide `generateUrl` or both `resource` and `field`.");
104
+ return;
105
+ }
106
+ setIsLoading(true);
107
+ setError(null);
108
+ try {
109
+ const response = await fetch(url, {
110
+ method: "POST",
111
+ credentials: "same-origin",
112
+ headers: {
113
+ "Content-Type": "application/json",
114
+ Accept: "application/json",
115
+ "X-CSRF-TOKEN": csrfToken ?? ""
116
+ },
117
+ body: JSON.stringify({ formData: formData ?? {} })
118
+ });
119
+ if (!response.ok) {
120
+ setError(`Generation failed (HTTP ${String(response.status)}).`);
121
+ return;
122
+ }
123
+ const body = await response.json();
124
+ const text = typeof body.text === "string" ? body.text : "";
125
+ applyGeneratedText(text);
126
+ setHasGenerated(true);
127
+ } catch {
128
+ setError("Generation failed: network error.");
129
+ } finally {
130
+ setIsLoading(false);
131
+ }
132
+ }, [applyGeneratedText, csrfToken, field, formData, generateUrl, resource]);
133
+ const triggerLabel = hasGenerated ? REGENERATE_LABEL : buttonLabel;
134
+ const charCount = currentValue.length;
135
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", "data-arqel-field": "aiText", "data-field-name": name, children: [
136
+ /* @__PURE__ */ jsx(
137
+ Textarea,
138
+ {
139
+ id: textareaId,
140
+ name,
141
+ value: currentValue,
142
+ onChange: handleTextareaChange,
143
+ ...maxLength !== null ? { maxLength } : {},
144
+ rows: 6
145
+ }
146
+ ),
147
+ maxLength !== null ? /* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground self-end", "aria-live": "polite", children: [
148
+ charCount,
149
+ " / ",
150
+ maxLength
151
+ ] }) : null,
152
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsxs(
153
+ Button,
154
+ {
155
+ variant: "outline",
156
+ size: "sm",
157
+ onClick: () => {
158
+ void handleGenerate();
159
+ },
160
+ disabled: isLoading,
161
+ "aria-label": triggerLabel,
162
+ children: [
163
+ isLoading ? /* @__PURE__ */ jsx("span", { role: "status", "aria-label": "Generating", children: /* @__PURE__ */ jsx(Spinner, {}) }) : null,
164
+ /* @__PURE__ */ jsx("span", { children: triggerLabel })
165
+ ]
166
+ }
167
+ ) }),
168
+ error !== null ? /* @__PURE__ */ jsx(Alert, { variant: "destructive", children: /* @__PURE__ */ jsx(AlertDescription, { children: error }) }) : null
169
+ ] });
170
+ }
171
+ var DEFAULT_BUTTON_LABEL, REGENERATE_LABEL, AiTextInput_default;
172
+ var init_AiTextInput = __esm({
173
+ "src/AiTextInput.tsx"() {
174
+ DEFAULT_BUTTON_LABEL = "Generate with AI";
175
+ REGENERATE_LABEL = "Regenerate";
176
+ AiTextInput_default = AiTextInput;
177
+ }
178
+ });
179
+
180
+ // src/AiTranslateInput.tsx
181
+ var AiTranslateInput_exports = {};
182
+ __export(AiTranslateInput_exports, {
183
+ AiTranslateInput: () => AiTranslateInput,
184
+ default: () => AiTranslateInput_default
185
+ });
186
+ function buildTranslateUrl(override, resource, field) {
187
+ if (override !== void 0 && override !== "") {
188
+ return override;
189
+ }
190
+ if (resource && field) {
191
+ return `/admin/${resource}/fields/${field}/translate`;
192
+ }
193
+ return null;
194
+ }
195
+ function isStringRecord(input) {
196
+ if (input === null || typeof input !== "object") {
197
+ return false;
198
+ }
199
+ for (const v of Object.values(input)) {
200
+ if (typeof v !== "string") {
201
+ return false;
202
+ }
203
+ }
204
+ return true;
205
+ }
206
+ function isMissing(translations, lang) {
207
+ const v = translations[lang];
208
+ return v === void 0 || v === null || v === "";
209
+ }
210
+ function Spinner2() {
211
+ return /* @__PURE__ */ jsxs(
212
+ "svg",
213
+ {
214
+ width: "14",
215
+ height: "14",
216
+ viewBox: "0 0 24 24",
217
+ fill: "none",
218
+ xmlns: "http://www.w3.org/2000/svg",
219
+ "aria-hidden": "true",
220
+ className: "animate-spin",
221
+ children: [
222
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeOpacity: "0.25", strokeWidth: "4" }),
223
+ /* @__PURE__ */ jsx(
224
+ "path",
225
+ {
226
+ d: "M22 12a10 10 0 0 1-10 10",
227
+ stroke: "currentColor",
228
+ strokeWidth: "4",
229
+ strokeLinecap: "round"
230
+ }
231
+ )
232
+ ]
233
+ }
234
+ );
235
+ }
236
+ function AiTranslateInput(props) {
237
+ const {
238
+ name,
239
+ value,
240
+ onChange,
241
+ props: fieldProps,
242
+ resource,
243
+ field,
244
+ translateUrl,
245
+ csrfToken
246
+ } = props;
247
+ const languages = useMemo(() => fieldProps?.languages ?? [], [fieldProps?.languages]);
248
+ const defaultLanguage = fieldProps?.defaultLanguage ?? languages[0] ?? "";
249
+ const reactId = useId();
250
+ const tablistId = `arqel-ai-translate-${reactId}`;
251
+ const [internalValue, setInternalValue] = useState(value ?? {});
252
+ const isControlled = onChange !== void 0;
253
+ const currentValue = isControlled ? value ?? {} : internalValue;
254
+ const initialActive = languages.includes(defaultLanguage) ? defaultLanguage : languages[0] ?? defaultLanguage;
255
+ const [activeLang, setActiveLang] = useState(initialActive);
256
+ const [loadingLangs, setLoadingLangs] = useState(() => /* @__PURE__ */ new Set());
257
+ const [error, setError] = useState(null);
258
+ const applyValue = useCallback(
259
+ (next) => {
260
+ if (isControlled && onChange) {
261
+ onChange(next);
262
+ } else {
263
+ setInternalValue(next);
264
+ }
265
+ },
266
+ [isControlled, onChange]
267
+ );
268
+ const handleTextareaChange = useCallback(
269
+ (event) => {
270
+ const next = { ...currentValue, [activeLang]: event.target.value };
271
+ applyValue(next);
272
+ },
273
+ [activeLang, applyValue, currentValue]
274
+ );
275
+ const setLoadingFor = useCallback((langs, on) => {
276
+ setLoadingLangs((prev) => {
277
+ const next = new Set(prev);
278
+ for (const l of langs) {
279
+ if (on) {
280
+ next.add(l);
281
+ } else {
282
+ next.delete(l);
283
+ }
284
+ }
285
+ return next;
286
+ });
287
+ }, []);
288
+ const performTranslate = useCallback(
289
+ async (targetLanguages) => {
290
+ if (targetLanguages.length === 0) {
291
+ return;
292
+ }
293
+ const url = buildTranslateUrl(translateUrl, resource, field);
294
+ if (url === null) {
295
+ setError("Missing translate URL: provide `translateUrl` or both `resource` and `field`.");
296
+ return;
297
+ }
298
+ setError(null);
299
+ setLoadingFor(targetLanguages, true);
300
+ try {
301
+ const response = await fetch(url, {
302
+ method: "POST",
303
+ credentials: "same-origin",
304
+ headers: {
305
+ "Content-Type": "application/json",
306
+ Accept: "application/json",
307
+ "X-CSRF-TOKEN": csrfToken ?? ""
308
+ },
309
+ body: JSON.stringify({
310
+ sourceLanguage: defaultLanguage,
311
+ targetLanguages,
312
+ sourceText: currentValue[defaultLanguage] ?? ""
313
+ })
314
+ });
315
+ if (!response.ok) {
316
+ setError(`Translation failed (HTTP ${String(response.status)}).`);
317
+ return;
318
+ }
319
+ const body = await response.json();
320
+ const incoming = body.translations;
321
+ if (!isStringRecord(incoming)) {
322
+ setError("Translation failed: invalid response body.");
323
+ return;
324
+ }
325
+ const merged = { ...currentValue, ...incoming };
326
+ applyValue(merged);
327
+ } catch {
328
+ setError("Translation failed: network error.");
329
+ } finally {
330
+ setLoadingFor(targetLanguages, false);
331
+ }
332
+ },
333
+ [
334
+ applyValue,
335
+ csrfToken,
336
+ currentValue,
337
+ defaultLanguage,
338
+ field,
339
+ resource,
340
+ setLoadingFor,
341
+ translateUrl
342
+ ]
343
+ );
344
+ const handleTranslateOne = useCallback(
345
+ (lang) => {
346
+ void performTranslate([lang]);
347
+ },
348
+ [performTranslate]
349
+ );
350
+ const handleTranslateAllMissing = useCallback(() => {
351
+ const missing = languages.filter(
352
+ (lang) => lang !== defaultLanguage && isMissing(currentValue, lang)
353
+ );
354
+ void performTranslate(missing);
355
+ }, [currentValue, defaultLanguage, languages, performTranslate]);
356
+ const missingCount = languages.filter(
357
+ (lang) => lang !== defaultLanguage && isMissing(currentValue, lang)
358
+ ).length;
359
+ const activeText = currentValue[activeLang] ?? "";
360
+ const activeIsLoading = loadingLangs.has(activeLang);
361
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", "data-arqel-field": "aiTranslate", "data-field-name": name, children: [
362
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-end", children: /* @__PURE__ */ jsx(
363
+ Button,
364
+ {
365
+ variant: "outline",
366
+ size: "sm",
367
+ onClick: handleTranslateAllMissing,
368
+ disabled: missingCount === 0 || loadingLangs.size > 0,
369
+ children: "Translate all missing"
370
+ }
371
+ ) }),
372
+ /* @__PURE__ */ jsx("div", { role: "tablist", id: tablistId, className: "flex items-center gap-1 border-b border-border", children: languages.map((lang) => {
373
+ const selected = lang === activeLang;
374
+ const missing = isMissing(currentValue, lang);
375
+ return /* @__PURE__ */ jsxs(
376
+ "button",
377
+ {
378
+ type: "button",
379
+ role: "tab",
380
+ "aria-selected": selected,
381
+ "aria-controls": `${tablistId}-panel-${lang}`,
382
+ "data-missing": missing ? "true" : "false",
383
+ onClick: () => setActiveLang(lang),
384
+ className: "inline-flex items-center gap-1 px-3 py-1.5 text-sm font-medium border-b-2 -mb-px transition-colors " + (selected ? "border-primary text-foreground" : "border-transparent text-muted-foreground hover:text-foreground"),
385
+ children: [
386
+ /* @__PURE__ */ jsx("span", { children: lang }),
387
+ missing ? /* @__PURE__ */ jsx(
388
+ Badge,
389
+ {
390
+ variant: "secondary",
391
+ role: "img",
392
+ "aria-label": "Missing translation",
393
+ "data-testid": `missing-dot-${lang}`,
394
+ className: "px-1 py-0",
395
+ children: "\u2022"
396
+ }
397
+ ) : null
398
+ ]
399
+ },
400
+ lang
401
+ );
402
+ }) }),
403
+ /* @__PURE__ */ jsxs("div", { role: "tabpanel", id: `${tablistId}-panel-${activeLang}`, className: "flex flex-col gap-2", children: [
404
+ /* @__PURE__ */ jsx(
405
+ Textarea,
406
+ {
407
+ name: `${name}[${activeLang}]`,
408
+ value: activeText,
409
+ onChange: handleTextareaChange,
410
+ rows: 6,
411
+ "aria-label": `Translation in ${activeLang}`
412
+ }
413
+ ),
414
+ activeLang !== defaultLanguage ? /* @__PURE__ */ jsx("div", { className: "flex items-center", children: /* @__PURE__ */ jsxs(
415
+ Button,
416
+ {
417
+ variant: "outline",
418
+ size: "sm",
419
+ onClick: () => handleTranslateOne(activeLang),
420
+ disabled: activeIsLoading,
421
+ children: [
422
+ activeIsLoading ? /* @__PURE__ */ jsx("span", { role: "status", "aria-label": "Translating", children: /* @__PURE__ */ jsx(Spinner2, {}) }) : null,
423
+ /* @__PURE__ */ jsxs("span", { children: [
424
+ "Translate from ",
425
+ defaultLanguage
426
+ ] })
427
+ ]
428
+ }
429
+ ) }) : null
430
+ ] }),
431
+ error !== null ? /* @__PURE__ */ jsx(Alert, { variant: "destructive", children: /* @__PURE__ */ jsx(AlertDescription, { children: error }) }) : null
432
+ ] });
433
+ }
434
+ var AiTranslateInput_default;
435
+ var init_AiTranslateInput = __esm({
436
+ "src/AiTranslateInput.tsx"() {
437
+ AiTranslateInput_default = AiTranslateInput;
438
+ }
439
+ });
440
+
441
+ // src/AiSelectInput.tsx
442
+ var AiSelectInput_exports = {};
443
+ __export(AiSelectInput_exports, {
444
+ AiSelectInput: () => AiSelectInput,
445
+ default: () => AiSelectInput_default
446
+ });
447
+ function buildClassifyUrl(override, resource, field) {
448
+ if (override !== void 0 && override !== "") {
449
+ return override;
450
+ }
451
+ if (resource && field) {
452
+ return `/admin/${resource}/fields/${field}/classify`;
453
+ }
454
+ return null;
455
+ }
456
+ function Spinner3() {
457
+ return /* @__PURE__ */ jsxs(
458
+ "svg",
459
+ {
460
+ width: "14",
461
+ height: "14",
462
+ viewBox: "0 0 24 24",
463
+ fill: "none",
464
+ xmlns: "http://www.w3.org/2000/svg",
465
+ "aria-hidden": "true",
466
+ className: "animate-spin",
467
+ children: [
468
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeOpacity: "0.25", strokeWidth: "4" }),
469
+ /* @__PURE__ */ jsx(
470
+ "path",
471
+ {
472
+ d: "M22 12a10 10 0 0 1-10 10",
473
+ stroke: "currentColor",
474
+ strokeWidth: "4",
475
+ strokeLinecap: "round"
476
+ }
477
+ )
478
+ ]
479
+ }
480
+ );
481
+ }
482
+ function AiSelectInput(props) {
483
+ const {
484
+ name,
485
+ value,
486
+ onChange,
487
+ props: fieldProps,
488
+ resource,
489
+ field,
490
+ formData,
491
+ classifyUrl,
492
+ csrfToken
493
+ } = props;
494
+ const options = fieldProps?.options ?? {};
495
+ const fallbackOption = fieldProps?.fallbackOption ?? null;
496
+ const hasContextFields = fieldProps?.hasContextFields ?? false;
497
+ const reactId = useId();
498
+ const selectId = `arqel-ai-select-${reactId}`;
499
+ const [internalValue, setInternalValue] = useState(value);
500
+ const isControlled = onChange !== void 0;
501
+ const currentValue = isControlled ? value : internalValue;
502
+ const [isLoading, setIsLoading] = useState(false);
503
+ const [error, setError] = useState(null);
504
+ const [suggestion, setSuggestion] = useState(null);
505
+ const applyValue = useCallback(
506
+ (next) => {
507
+ if (isControlled && onChange) {
508
+ onChange(next);
509
+ } else {
510
+ setInternalValue(next);
511
+ }
512
+ },
513
+ [isControlled, onChange]
514
+ );
515
+ const handleSelectChange = useCallback(
516
+ (next) => {
517
+ applyValue(next === "" ? null : next);
518
+ setSuggestion(null);
519
+ },
520
+ [applyValue]
521
+ );
522
+ const handleClassify = useCallback(async () => {
523
+ const url = buildClassifyUrl(classifyUrl, resource, field);
524
+ if (url === null) {
525
+ setError("Missing classify URL: provide `classifyUrl` or both `resource` and `field`.");
526
+ return;
527
+ }
528
+ setIsLoading(true);
529
+ setError(null);
530
+ setSuggestion(null);
531
+ try {
532
+ const response = await fetch(url, {
533
+ method: "POST",
534
+ credentials: "same-origin",
535
+ headers: {
536
+ "Content-Type": "application/json",
537
+ Accept: "application/json",
538
+ "X-CSRF-TOKEN": csrfToken ?? ""
539
+ },
540
+ body: JSON.stringify({ formData: formData ?? {} })
541
+ });
542
+ if (!response.ok) {
543
+ setError(`Classification failed (HTTP ${String(response.status)}).`);
544
+ return;
545
+ }
546
+ const body = await response.json();
547
+ const key = typeof body.key === "string" ? body.key : null;
548
+ if (key !== null) {
549
+ applyValue(key);
550
+ setSuggestion("ai");
551
+ return;
552
+ }
553
+ if (fallbackOption !== null && fallbackOption !== "") {
554
+ applyValue(fallbackOption);
555
+ setSuggestion("fallback");
556
+ return;
557
+ }
558
+ setError("Could not classify.");
559
+ } catch {
560
+ setError("Classification failed: network error.");
561
+ } finally {
562
+ setIsLoading(false);
563
+ }
564
+ }, [applyValue, classifyUrl, csrfToken, fallbackOption, field, formData, resource]);
565
+ const buttonDisabled = isLoading || !hasContextFields;
566
+ const buttonTitle = !hasContextFields ? NO_CONTEXT_TOOLTIP : void 0;
567
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", "data-arqel-field": "aiSelect", "data-field-name": name, children: [
568
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
569
+ /* @__PURE__ */ jsxs(Select, { value: currentValue ?? "", onValueChange: handleSelectChange, name, children: [
570
+ /* @__PURE__ */ jsx(SelectTrigger, { id: selectId, className: "flex-1", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "Select..." }) }),
571
+ /* @__PURE__ */ jsx(SelectContent, { children: Object.entries(options).map(([key, label]) => /* @__PURE__ */ jsx(SelectItem, { value: key, children: label }, key)) })
572
+ ] }),
573
+ /* @__PURE__ */ jsxs(
574
+ Button,
575
+ {
576
+ variant: "outline",
577
+ size: "sm",
578
+ onClick: () => {
579
+ void handleClassify();
580
+ },
581
+ disabled: buttonDisabled,
582
+ "aria-label": DEFAULT_BUTTON_LABEL2,
583
+ ...buttonTitle !== void 0 ? { title: buttonTitle } : {},
584
+ children: [
585
+ isLoading ? /* @__PURE__ */ jsx("span", { role: "status", "aria-label": "Classifying", children: /* @__PURE__ */ jsx(Spinner3, {}) }) : null,
586
+ /* @__PURE__ */ jsx("span", { children: DEFAULT_BUTTON_LABEL2 })
587
+ ]
588
+ }
589
+ )
590
+ ] }),
591
+ suggestion !== null ? /* @__PURE__ */ jsxs("div", { role: "status", className: "flex items-center gap-2", children: [
592
+ /* @__PURE__ */ jsx(
593
+ Badge,
594
+ {
595
+ variant: "secondary",
596
+ className: suggestion === "ai" ? "border-emerald-500/30 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300" : "border-amber-500/30 bg-amber-500/10 text-amber-700 dark:text-amber-300",
597
+ children: suggestion === "ai" ? "Suggested by AI" : "Used fallback"
598
+ }
599
+ ),
600
+ /* @__PURE__ */ jsx(
601
+ Button,
602
+ {
603
+ variant: "ghost",
604
+ size: "sm",
605
+ onClick: () => {
606
+ setSuggestion(null);
607
+ },
608
+ children: "Accept"
609
+ }
610
+ ),
611
+ /* @__PURE__ */ jsx(
612
+ Button,
613
+ {
614
+ variant: "ghost",
615
+ size: "sm",
616
+ onClick: () => {
617
+ setSuggestion(null);
618
+ },
619
+ children: "Pick another"
620
+ }
621
+ )
622
+ ] }) : null,
623
+ error !== null ? /* @__PURE__ */ jsx(Alert, { variant: "destructive", children: /* @__PURE__ */ jsx(AlertDescription, { children: error }) }) : null
624
+ ] });
625
+ }
626
+ var DEFAULT_BUTTON_LABEL2, NO_CONTEXT_TOOLTIP, AiSelectInput_default;
627
+ var init_AiSelectInput = __esm({
628
+ "src/AiSelectInput.tsx"() {
629
+ DEFAULT_BUTTON_LABEL2 = "Classify with AI";
630
+ NO_CONTEXT_TOOLTIP = "No context fields configured. Add `classifyFromFields` to enable AI classification.";
631
+ AiSelectInput_default = AiSelectInput;
632
+ }
633
+ });
634
+
635
+ // src/AiExtractInput.tsx
636
+ var AiExtractInput_exports = {};
637
+ __export(AiExtractInput_exports, {
638
+ AiExtractInput: () => AiExtractInput,
639
+ default: () => AiExtractInput_default
640
+ });
641
+ function buildExtractUrl(override, resource, field) {
642
+ if (override !== void 0 && override !== "") {
643
+ return override;
644
+ }
645
+ if (resource && field) {
646
+ return `/admin/${resource}/fields/${field}/extract`;
647
+ }
648
+ return null;
649
+ }
650
+ function isPlainRecord(input) {
651
+ return input !== null && typeof input === "object" && !Array.isArray(input);
652
+ }
653
+ function coerceSourceText(raw) {
654
+ if (typeof raw === "string") {
655
+ return raw;
656
+ }
657
+ if (raw === null || raw === void 0) {
658
+ return "";
659
+ }
660
+ if (typeof raw === "number" || typeof raw === "boolean") {
661
+ return String(raw);
662
+ }
663
+ return "";
664
+ }
665
+ function formatPreviewValue(value) {
666
+ if (value === null || value === void 0) {
667
+ return "";
668
+ }
669
+ if (typeof value === "string") {
670
+ return value;
671
+ }
672
+ if (typeof value === "number" || typeof value === "boolean") {
673
+ return String(value);
674
+ }
675
+ try {
676
+ return JSON.stringify(value);
677
+ } catch {
678
+ return String(value);
679
+ }
680
+ }
681
+ function Spinner4() {
682
+ return /* @__PURE__ */ jsxs(
683
+ "svg",
684
+ {
685
+ width: "14",
686
+ height: "14",
687
+ viewBox: "0 0 24 24",
688
+ fill: "none",
689
+ xmlns: "http://www.w3.org/2000/svg",
690
+ "aria-hidden": "true",
691
+ className: "animate-spin",
692
+ children: [
693
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeOpacity: "0.25", strokeWidth: "4" }),
694
+ /* @__PURE__ */ jsx(
695
+ "path",
696
+ {
697
+ d: "M22 12a10 10 0 0 1-10 10",
698
+ stroke: "currentColor",
699
+ strokeWidth: "4",
700
+ strokeLinecap: "round"
701
+ }
702
+ )
703
+ ]
704
+ }
705
+ );
706
+ }
707
+ function AiExtractInput(props) {
708
+ const {
709
+ name,
710
+ value,
711
+ onChange,
712
+ props: fieldProps,
713
+ resource,
714
+ field,
715
+ formData,
716
+ extractUrl,
717
+ csrfToken,
718
+ onPopulateField
719
+ } = props;
720
+ const sourceField = fieldProps?.sourceField ?? "";
721
+ const targetFields = fieldProps?.targetFields ?? [];
722
+ const buttonLabel = fieldProps?.buttonLabel ?? "Extract with AI";
723
+ const reactId = useId();
724
+ const previewId = `arqel-ai-extract-${reactId}`;
725
+ const [extracted, setExtracted] = useState(value);
726
+ const [isLoading, setIsLoading] = useState(false);
727
+ const [error, setError] = useState(null);
728
+ const applyAll = useCallback(
729
+ (next) => {
730
+ if (onPopulateField !== void 0) {
731
+ for (const key of Object.keys(next)) {
732
+ onPopulateField(key, next[key]);
733
+ }
734
+ return;
735
+ }
736
+ onChange?.(next);
737
+ },
738
+ [onChange, onPopulateField]
739
+ );
740
+ const applyOne = useCallback(
741
+ (target, val) => {
742
+ if (onPopulateField !== void 0) {
743
+ onPopulateField(target, val);
744
+ return;
745
+ }
746
+ onChange?.({ [target]: val });
747
+ },
748
+ [onChange, onPopulateField]
749
+ );
750
+ const handleExtract = useCallback(async () => {
751
+ const url = buildExtractUrl(extractUrl, resource, field);
752
+ if (url === null) {
753
+ setError("Missing extract URL: provide `extractUrl` or both `resource` and `field`.");
754
+ return;
755
+ }
756
+ const sourceText = coerceSourceText(
757
+ formData !== void 0 && sourceField !== "" ? formData[sourceField] : ""
758
+ );
759
+ setIsLoading(true);
760
+ setError(null);
761
+ try {
762
+ const response = await fetch(url, {
763
+ method: "POST",
764
+ credentials: "same-origin",
765
+ headers: {
766
+ "Content-Type": "application/json",
767
+ Accept: "application/json",
768
+ "X-CSRF-TOKEN": csrfToken ?? ""
769
+ },
770
+ body: JSON.stringify({ sourceText })
771
+ });
772
+ if (!response.ok) {
773
+ let message = null;
774
+ try {
775
+ const body2 = await response.json();
776
+ if (typeof body2.message === "string" && body2.message !== "") {
777
+ message = body2.message;
778
+ }
779
+ } catch {
780
+ message = null;
781
+ }
782
+ setError(message ?? `Extraction failed (HTTP ${String(response.status)}).`);
783
+ return;
784
+ }
785
+ const body = await response.json();
786
+ if (!isPlainRecord(body.extracted)) {
787
+ setError("Extraction failed: invalid response body.");
788
+ return;
789
+ }
790
+ setExtracted(body.extracted);
791
+ } catch {
792
+ setError("Extraction failed: network error.");
793
+ } finally {
794
+ setIsLoading(false);
795
+ }
796
+ }, [csrfToken, extractUrl, field, formData, resource, sourceField]);
797
+ const hasExtraction = extracted !== null && Object.keys(extracted).length > 0;
798
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", "data-arqel-field": "aiExtract", "data-field-name": name, children: [
799
+ /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsxs(Label, { className: "text-muted-foreground", children: [
800
+ "Source: ",
801
+ sourceField
802
+ ] }) }),
803
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
804
+ /* @__PURE__ */ jsxs(
805
+ Button,
806
+ {
807
+ variant: "outline",
808
+ size: "sm",
809
+ onClick: () => {
810
+ void handleExtract();
811
+ },
812
+ disabled: isLoading,
813
+ "aria-label": buttonLabel,
814
+ children: [
815
+ isLoading ? /* @__PURE__ */ jsx("span", { role: "status", "aria-label": "Extracting", children: /* @__PURE__ */ jsx(Spinner4, {}) }) : null,
816
+ /* @__PURE__ */ jsx("span", { children: buttonLabel })
817
+ ]
818
+ }
819
+ ),
820
+ hasExtraction && extracted !== null ? /* @__PURE__ */ jsx(
821
+ Button,
822
+ {
823
+ variant: "default",
824
+ size: "sm",
825
+ onClick: () => {
826
+ applyAll(extracted);
827
+ },
828
+ children: "Apply all"
829
+ }
830
+ ) : null
831
+ ] }),
832
+ hasExtraction && extracted !== null ? /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx(CardContent, { className: "p-4", children: /* @__PURE__ */ jsx("dl", { id: previewId, className: "flex flex-col gap-2", children: (targetFields.length > 0 ? targetFields : Object.keys(extracted)).map((target) => {
833
+ const val = extracted[target];
834
+ return /* @__PURE__ */ jsxs(
835
+ "div",
836
+ {
837
+ className: "flex items-center justify-between gap-2 border-b border-border pb-2 last:border-b-0 last:pb-0",
838
+ children: [
839
+ /* @__PURE__ */ jsx("dt", { children: /* @__PURE__ */ jsx(Badge, { variant: "outline", children: target }) }),
840
+ /* @__PURE__ */ jsxs("dd", { className: "flex items-center gap-2 flex-1 justify-end", children: [
841
+ /* @__PURE__ */ jsx(
842
+ "span",
843
+ {
844
+ className: "text-sm text-foreground truncate",
845
+ "data-testid": `extract-value-${target}`,
846
+ children: formatPreviewValue(val)
847
+ }
848
+ ),
849
+ /* @__PURE__ */ jsx(
850
+ Button,
851
+ {
852
+ variant: "ghost",
853
+ size: "sm",
854
+ onClick: () => {
855
+ applyOne(target, val);
856
+ },
857
+ "aria-label": `Apply ${target}`,
858
+ children: "Apply"
859
+ }
860
+ )
861
+ ] })
862
+ ]
863
+ },
864
+ target
865
+ );
866
+ }) }) }) }) : /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground italic", children: "No extraction yet \u2014 click button to start." }),
867
+ error !== null ? /* @__PURE__ */ jsx(Alert, { variant: "destructive", children: /* @__PURE__ */ jsx(AlertDescription, { children: error }) }) : null
868
+ ] });
869
+ }
870
+ var AiExtractInput_default;
871
+ var init_AiExtractInput = __esm({
872
+ "src/AiExtractInput.tsx"() {
873
+ AiExtractInput_default = AiExtractInput;
874
+ }
875
+ });
876
+
877
+ // src/AiImageInput.tsx
878
+ var AiImageInput_exports = {};
879
+ __export(AiImageInput_exports, {
880
+ AiImageInput: () => AiImageInput,
881
+ default: () => AiImageInput_default
882
+ });
883
+ function buildAnalyzeUrl(override, resource, field) {
884
+ if (override !== void 0 && override !== "") {
885
+ return override;
886
+ }
887
+ if (resource && field) {
888
+ return `/admin/${resource}/fields/${field}/analyze-image`;
889
+ }
890
+ return null;
891
+ }
892
+ function isStringRecord2(input) {
893
+ if (input === null || typeof input !== "object" || Array.isArray(input)) {
894
+ return false;
895
+ }
896
+ for (const value of Object.values(input)) {
897
+ if (typeof value !== "string") {
898
+ return false;
899
+ }
900
+ }
901
+ return true;
902
+ }
903
+ function readFileAsDataUrl(file) {
904
+ return new Promise((resolve, reject) => {
905
+ const reader = new FileReader();
906
+ reader.onload = () => {
907
+ const result = reader.result;
908
+ if (typeof result === "string") {
909
+ resolve(result);
910
+ return;
911
+ }
912
+ reject(new Error("FileReader returned non-string result."));
913
+ };
914
+ reader.onerror = () => {
915
+ reject(reader.error ?? new Error("FileReader error."));
916
+ };
917
+ reader.readAsDataURL(file);
918
+ });
919
+ }
920
+ function formatBytes(bytes) {
921
+ if (bytes >= 1048576) {
922
+ return `${(bytes / 1048576).toFixed(1)} MB`;
923
+ }
924
+ if (bytes >= 1024) {
925
+ return `${(bytes / 1024).toFixed(1)} KB`;
926
+ }
927
+ return `${String(bytes)} B`;
928
+ }
929
+ function Spinner5() {
930
+ return /* @__PURE__ */ jsxs(
931
+ "svg",
932
+ {
933
+ width: "14",
934
+ height: "14",
935
+ viewBox: "0 0 24 24",
936
+ fill: "none",
937
+ xmlns: "http://www.w3.org/2000/svg",
938
+ "aria-hidden": "true",
939
+ className: "animate-spin",
940
+ children: [
941
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeOpacity: "0.25", strokeWidth: "4" }),
942
+ /* @__PURE__ */ jsx(
943
+ "path",
944
+ {
945
+ d: "M22 12a10 10 0 0 1-10 10",
946
+ stroke: "currentColor",
947
+ strokeWidth: "4",
948
+ strokeLinecap: "round"
949
+ }
950
+ )
951
+ ]
952
+ }
953
+ );
954
+ }
955
+ function AiImageInput(props) {
956
+ const {
957
+ name,
958
+ value,
959
+ onChange,
960
+ props: fieldProps,
961
+ resource,
962
+ field,
963
+ analyzeUrl,
964
+ csrfToken,
965
+ onPopulateField
966
+ } = props;
967
+ const analyses = fieldProps?.analyses ?? [];
968
+ const populateFields = fieldProps?.populateFields ?? {};
969
+ const acceptedMimes = fieldProps?.acceptedMimes ?? [];
970
+ const maxFileSize = fieldProps?.maxFileSize ?? 0;
971
+ const buttonLabel = fieldProps?.buttonLabel ?? "Analyze with AI";
972
+ const reactId = useId();
973
+ const inputId = `arqel-ai-image-${reactId}`;
974
+ const [file, setFile] = useState(null);
975
+ const [previewUrl, setPreviewUrl] = useState(null);
976
+ const [results, setResults] = useState(null);
977
+ const [populateMapping, setPopulateMapping] = useState(populateFields);
978
+ const [isLoading, setIsLoading] = useState(false);
979
+ const [error, setError] = useState(null);
980
+ const handleFileChange = useCallback(
981
+ (event) => {
982
+ const next = event.target.files?.[0] ?? null;
983
+ setError(null);
984
+ setResults(null);
985
+ if (next === null) {
986
+ setFile(null);
987
+ setPreviewUrl(null);
988
+ return;
989
+ }
990
+ if (maxFileSize > 0 && next.size > maxFileSize) {
991
+ setError(`File too large: ${formatBytes(next.size)} (max ${formatBytes(maxFileSize)}).`);
992
+ setFile(null);
993
+ setPreviewUrl(null);
994
+ return;
995
+ }
996
+ setFile(next);
997
+ try {
998
+ setPreviewUrl(URL.createObjectURL(next));
999
+ } catch {
1000
+ setPreviewUrl(null);
1001
+ }
1002
+ },
1003
+ [maxFileSize]
1004
+ );
1005
+ const handleAnalyze = useCallback(async () => {
1006
+ if (file === null) {
1007
+ return;
1008
+ }
1009
+ const url = buildAnalyzeUrl(analyzeUrl, resource, field);
1010
+ if (url === null) {
1011
+ setError("Missing analyze URL: provide `analyzeUrl` or both `resource` and `field`.");
1012
+ return;
1013
+ }
1014
+ setIsLoading(true);
1015
+ setError(null);
1016
+ try {
1017
+ const imageBase64 = await readFileAsDataUrl(file);
1018
+ const response = await fetch(url, {
1019
+ method: "POST",
1020
+ credentials: "same-origin",
1021
+ headers: {
1022
+ "Content-Type": "application/json",
1023
+ Accept: "application/json",
1024
+ "X-CSRF-TOKEN": csrfToken ?? ""
1025
+ },
1026
+ body: JSON.stringify({ imageBase64 })
1027
+ });
1028
+ if (!response.ok) {
1029
+ let message = null;
1030
+ try {
1031
+ const body2 = await response.json();
1032
+ if (typeof body2.message === "string" && body2.message !== "") {
1033
+ message = body2.message;
1034
+ }
1035
+ } catch {
1036
+ message = null;
1037
+ }
1038
+ setError(message ?? `Analysis failed (HTTP ${String(response.status)}).`);
1039
+ return;
1040
+ }
1041
+ const body = await response.json();
1042
+ if (!isStringRecord2(body.analyses)) {
1043
+ setError("Analysis failed: invalid response body.");
1044
+ return;
1045
+ }
1046
+ setResults(body.analyses);
1047
+ if (isStringRecord2(body.populateMapping)) {
1048
+ setPopulateMapping(body.populateMapping);
1049
+ }
1050
+ if (onChange !== void 0) {
1051
+ onChange(imageBase64);
1052
+ }
1053
+ } catch {
1054
+ setError("Analysis failed: network error.");
1055
+ } finally {
1056
+ setIsLoading(false);
1057
+ }
1058
+ }, [analyzeUrl, csrfToken, field, file, onChange, resource]);
1059
+ const applyOne = useCallback(
1060
+ (analysisKey, val) => {
1061
+ const target = populateMapping[analysisKey];
1062
+ if (target === void 0 || target === "") {
1063
+ return;
1064
+ }
1065
+ onPopulateField?.(target, val);
1066
+ },
1067
+ [onPopulateField, populateMapping]
1068
+ );
1069
+ const applyAll = useCallback(
1070
+ (entries) => {
1071
+ for (const key of Object.keys(entries)) {
1072
+ const target = populateMapping[key];
1073
+ const val = entries[key];
1074
+ if (target === void 0 || target === "" || val === void 0) {
1075
+ continue;
1076
+ }
1077
+ onPopulateField?.(target, val);
1078
+ }
1079
+ },
1080
+ [onPopulateField, populateMapping]
1081
+ );
1082
+ const accept = acceptedMimes.join(",");
1083
+ const hasResults = results !== null && Object.keys(results).length > 0;
1084
+ const canAnalyze = file !== null && !isLoading;
1085
+ return /* @__PURE__ */ jsxs(
1086
+ "div",
1087
+ {
1088
+ className: "flex flex-col gap-2",
1089
+ "data-arqel-field": "aiImage",
1090
+ "data-field-name": name,
1091
+ "data-has-value": value !== null && value !== "" ? "true" : "false",
1092
+ children: [
1093
+ /* @__PURE__ */ jsxs(
1094
+ "label",
1095
+ {
1096
+ htmlFor: inputId,
1097
+ className: "flex items-center justify-center w-full min-h-[6rem] rounded-sm border-2 border-dashed border-border bg-muted/40 px-4 py-6 text-sm text-muted-foreground hover:bg-muted cursor-pointer transition-colors",
1098
+ children: [
1099
+ /* @__PURE__ */ jsx("span", { children: file !== null ? file.name : `Click or drop image (${accept !== "" ? accept : "any"})` }),
1100
+ /* @__PURE__ */ jsx(
1101
+ "input",
1102
+ {
1103
+ id: inputId,
1104
+ type: "file",
1105
+ accept,
1106
+ onChange: handleFileChange,
1107
+ className: "sr-only",
1108
+ "aria-label": "Image file"
1109
+ }
1110
+ )
1111
+ ]
1112
+ }
1113
+ ),
1114
+ previewUrl !== null ? /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx(CardContent, { className: "p-2 flex items-center justify-center", children: /* @__PURE__ */ jsx(
1115
+ "img",
1116
+ {
1117
+ src: previewUrl,
1118
+ alt: "Selected preview",
1119
+ "data-testid": "image-preview",
1120
+ className: "max-h-64 max-w-full rounded-sm"
1121
+ }
1122
+ ) }) }) : null,
1123
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1124
+ /* @__PURE__ */ jsxs(
1125
+ Button,
1126
+ {
1127
+ variant: "outline",
1128
+ size: "sm",
1129
+ onClick: () => {
1130
+ void handleAnalyze();
1131
+ },
1132
+ disabled: !canAnalyze,
1133
+ "aria-label": buttonLabel,
1134
+ children: [
1135
+ isLoading ? /* @__PURE__ */ jsx("span", { role: "status", "aria-label": "Analyzing", children: /* @__PURE__ */ jsx(Spinner5, {}) }) : null,
1136
+ /* @__PURE__ */ jsx("span", { children: buttonLabel })
1137
+ ]
1138
+ }
1139
+ ),
1140
+ hasResults && results !== null ? /* @__PURE__ */ jsx(
1141
+ Button,
1142
+ {
1143
+ variant: "default",
1144
+ size: "sm",
1145
+ onClick: () => {
1146
+ applyAll(results);
1147
+ },
1148
+ children: "Apply all"
1149
+ }
1150
+ ) : null
1151
+ ] }),
1152
+ hasResults && results !== null ? /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx(CardContent, { className: "p-4", children: /* @__PURE__ */ jsx("dl", { className: "flex flex-col gap-2", children: (analyses.length > 0 ? analyses : Object.keys(results)).map((key) => {
1153
+ const val = results[key];
1154
+ if (val === void 0) {
1155
+ return null;
1156
+ }
1157
+ const target = populateMapping[key];
1158
+ const hasTarget = target !== void 0 && target !== "";
1159
+ return /* @__PURE__ */ jsxs(
1160
+ "div",
1161
+ {
1162
+ className: "flex items-center justify-between gap-2 border-b border-border pb-2 last:border-b-0 last:pb-0",
1163
+ children: [
1164
+ /* @__PURE__ */ jsx("dt", { children: /* @__PURE__ */ jsx(Badge, { variant: "outline", children: key }) }),
1165
+ /* @__PURE__ */ jsxs("dd", { className: "flex items-center gap-2 flex-1 justify-end", children: [
1166
+ /* @__PURE__ */ jsx(
1167
+ "span",
1168
+ {
1169
+ className: "text-sm text-foreground truncate",
1170
+ "data-testid": `analysis-value-${key}`,
1171
+ children: val
1172
+ }
1173
+ ),
1174
+ hasTarget ? /* @__PURE__ */ jsx(
1175
+ Button,
1176
+ {
1177
+ variant: "ghost",
1178
+ size: "sm",
1179
+ onClick: () => {
1180
+ applyOne(key, val);
1181
+ },
1182
+ "aria-label": `Apply ${key}`,
1183
+ children: "Apply"
1184
+ }
1185
+ ) : null
1186
+ ] })
1187
+ ]
1188
+ },
1189
+ key
1190
+ );
1191
+ }) }) }) }) : null,
1192
+ error !== null ? /* @__PURE__ */ jsx(Alert, { variant: "destructive", children: /* @__PURE__ */ jsx(AlertDescription, { children: error }) }) : null
1193
+ ]
1194
+ }
1195
+ );
1196
+ }
1197
+ var AiImageInput_default;
1198
+ var init_AiImageInput = __esm({
1199
+ "src/AiImageInput.tsx"() {
1200
+ AiImageInput_default = AiImageInput;
1201
+ }
1202
+ });
1203
+ function adaptToAiTextInput(Component) {
1204
+ return function AiTextInputAdapter(registryProps) {
1205
+ const { field, value, onChange, resource, formData, csrfToken } = registryProps;
1206
+ const fieldProps = field.props;
1207
+ const stringValue = typeof value === "string" ? value : "";
1208
+ return /* @__PURE__ */ jsx(
1209
+ Component,
1210
+ {
1211
+ name: field.name,
1212
+ value: stringValue,
1213
+ props: fieldProps,
1214
+ ...onChange !== void 0 ? { onChange } : {},
1215
+ ...resource !== void 0 ? { resource } : {},
1216
+ field: field.name,
1217
+ ...formData !== void 0 ? { formData } : {},
1218
+ ...csrfToken !== void 0 ? { csrfToken } : {}
1219
+ }
1220
+ );
1221
+ };
1222
+ }
1223
+ function adaptToAiTranslateInput(Component) {
1224
+ return function AiTranslateInputAdapter(registryProps) {
1225
+ const { field, value, onChange, resource, csrfToken } = registryProps;
1226
+ const fieldProps = field.props;
1227
+ const onChangeAdapted = onChange !== void 0 ? (next) => {
1228
+ onChange(next);
1229
+ } : void 0;
1230
+ const objectValue = value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
1231
+ return /* @__PURE__ */ jsx(
1232
+ Component,
1233
+ {
1234
+ name: field.name,
1235
+ value: objectValue,
1236
+ props: fieldProps,
1237
+ ...onChangeAdapted !== void 0 ? { onChange: onChangeAdapted } : {},
1238
+ ...resource !== void 0 ? { resource } : {},
1239
+ field: field.name,
1240
+ ...csrfToken !== void 0 ? { csrfToken } : {}
1241
+ }
1242
+ );
1243
+ };
1244
+ }
1245
+ function adaptToAiSelectInput(Component) {
1246
+ return function AiSelectInputAdapter(registryProps) {
1247
+ const { field, value, onChange, resource, formData, csrfToken } = registryProps;
1248
+ const fieldProps = field.props;
1249
+ const onChangeAdapted = onChange !== void 0 ? (next) => {
1250
+ onChange(next);
1251
+ } : void 0;
1252
+ const stringValue = typeof value === "string" && value !== "" ? value : null;
1253
+ return /* @__PURE__ */ jsx(
1254
+ Component,
1255
+ {
1256
+ name: field.name,
1257
+ value: stringValue,
1258
+ props: fieldProps,
1259
+ ...onChangeAdapted !== void 0 ? { onChange: onChangeAdapted } : {},
1260
+ ...resource !== void 0 ? { resource } : {},
1261
+ field: field.name,
1262
+ ...formData !== void 0 ? { formData } : {},
1263
+ ...csrfToken !== void 0 ? { csrfToken } : {}
1264
+ }
1265
+ );
1266
+ };
1267
+ }
1268
+ function adaptToAiExtractInput(Component) {
1269
+ return function AiExtractInputAdapter(registryProps) {
1270
+ const { field, value, onChange, resource, formData, csrfToken } = registryProps;
1271
+ const fieldProps = field.props;
1272
+ const onChangeAdapted = onChange !== void 0 ? (next) => {
1273
+ onChange(next);
1274
+ } : void 0;
1275
+ const objectValue = value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
1276
+ return /* @__PURE__ */ jsx(
1277
+ Component,
1278
+ {
1279
+ name: field.name,
1280
+ value: objectValue,
1281
+ props: fieldProps,
1282
+ ...onChangeAdapted !== void 0 ? { onChange: onChangeAdapted } : {},
1283
+ ...resource !== void 0 ? { resource } : {},
1284
+ field: field.name,
1285
+ ...formData !== void 0 ? { formData } : {},
1286
+ ...csrfToken !== void 0 ? { csrfToken } : {}
1287
+ }
1288
+ );
1289
+ };
1290
+ }
1291
+ var LazyAiTextInput = lazy(async () => {
1292
+ const mod = await Promise.resolve().then(() => (init_AiTextInput(), AiTextInput_exports));
1293
+ return { default: adaptToAiTextInput(mod.AiTextInput) };
1294
+ });
1295
+ var LazyAiTranslateInput = lazy(async () => {
1296
+ const mod = await Promise.resolve().then(() => (init_AiTranslateInput(), AiTranslateInput_exports));
1297
+ return { default: adaptToAiTranslateInput(mod.AiTranslateInput) };
1298
+ });
1299
+ var LazyAiSelectInput = lazy(async () => {
1300
+ const mod = await Promise.resolve().then(() => (init_AiSelectInput(), AiSelectInput_exports));
1301
+ return { default: adaptToAiSelectInput(mod.AiSelectInput) };
1302
+ });
1303
+ var LazyAiExtractInput = lazy(async () => {
1304
+ const mod = await Promise.resolve().then(() => (init_AiExtractInput(), AiExtractInput_exports));
1305
+ return { default: adaptToAiExtractInput(mod.AiExtractInput) };
1306
+ });
1307
+ function adaptToAiImageInput(Component) {
1308
+ return function AiImageInputAdapter(registryProps) {
1309
+ const { field, value, onChange, resource, csrfToken } = registryProps;
1310
+ const fieldProps = field.props;
1311
+ const stringValue = typeof value === "string" && value !== "" ? value : null;
1312
+ return /* @__PURE__ */ jsx(
1313
+ Component,
1314
+ {
1315
+ name: field.name,
1316
+ value: stringValue,
1317
+ props: fieldProps,
1318
+ ...onChange !== void 0 ? { onChange } : {},
1319
+ ...resource !== void 0 ? { resource } : {},
1320
+ field: field.name,
1321
+ ...csrfToken !== void 0 ? { csrfToken } : {}
1322
+ }
1323
+ );
1324
+ };
1325
+ }
1326
+ var LazyAiImageInput = lazy(async () => {
1327
+ const mod = await Promise.resolve().then(() => (init_AiImageInput(), AiImageInput_exports));
1328
+ return { default: adaptToAiImageInput(mod.AiImageInput) };
1329
+ });
1330
+ registerField("AiTextInput", LazyAiTextInput);
1331
+ registerField(
1332
+ "AiTranslateInput",
1333
+ LazyAiTranslateInput
1334
+ );
1335
+ registerField("AiSelectInput", LazyAiSelectInput);
1336
+ registerField(
1337
+ "AiExtractInput",
1338
+ LazyAiExtractInput
1339
+ );
1340
+ registerField("AiImageInput", LazyAiImageInput);
1341
+ //# sourceMappingURL=register.js.map
1342
+ //# sourceMappingURL=register.js.map