@arqel-dev/ai 0.15.0 → 0.16.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.
- package/dist/index.js +136 -69
- package/dist/index.js.map +1 -1
- package/dist/register.js +145 -73
- package/dist/register.js.map +1 -1
- package/package.json +5 -3
package/dist/register.js
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
|
+
import { useArqelTranslations, useArqelLocale } from '@arqel-dev/react/utils';
|
|
1
2
|
import { Textarea, Button, Alert, AlertDescription, Badge, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, Label, Card, CardContent } from '@arqel-dev/ui';
|
|
2
|
-
import { lazy, useId, useState, useCallback
|
|
3
|
+
import { lazy, useMemo, useId, useState, useCallback } from 'react';
|
|
3
4
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
4
5
|
import { registerField } from '@arqel-dev/ui/form';
|
|
5
6
|
|
|
6
7
|
var __defProp = Object.defineProperty;
|
|
7
8
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
8
|
-
var __esm = (fn, res) => function __init() {
|
|
9
|
-
|
|
9
|
+
var __esm = (fn, res, err) => function __init() {
|
|
10
|
+
if (err) throw err[0];
|
|
11
|
+
try {
|
|
12
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
13
|
+
} catch (e) {
|
|
14
|
+
throw err = [e], e;
|
|
15
|
+
}
|
|
10
16
|
};
|
|
11
17
|
var __export = (target, all) => {
|
|
12
18
|
for (var name in all)
|
|
@@ -66,7 +72,11 @@ function AiTextInput(props) {
|
|
|
66
72
|
generateUrl,
|
|
67
73
|
csrfToken
|
|
68
74
|
} = props;
|
|
69
|
-
const
|
|
75
|
+
const t = useArqelTranslations();
|
|
76
|
+
const locale = useArqelLocale();
|
|
77
|
+
const numberFormatter = useMemo(() => new Intl.NumberFormat(locale), [locale]);
|
|
78
|
+
const serverLabel = fieldProps?.buttonLabel;
|
|
79
|
+
const buttonLabel = serverLabel !== void 0 && serverLabel !== "" ? serverLabel : t("arqel.ai.generate", DEFAULT_BUTTON_LABEL);
|
|
70
80
|
const maxLength = fieldProps?.maxLength ?? null;
|
|
71
81
|
const reactId = useId();
|
|
72
82
|
const textareaId = `arqel-ai-text-${reactId}`;
|
|
@@ -100,7 +110,12 @@ function AiTextInput(props) {
|
|
|
100
110
|
const handleGenerate = useCallback(async () => {
|
|
101
111
|
const url = buildGenerateUrl(generateUrl, resource, field);
|
|
102
112
|
if (url === null) {
|
|
103
|
-
setError(
|
|
113
|
+
setError(
|
|
114
|
+
t(
|
|
115
|
+
"arqel.ai.missing_generate_url",
|
|
116
|
+
"Missing generate URL: provide `generateUrl` or both `resource` and `field`."
|
|
117
|
+
)
|
|
118
|
+
);
|
|
104
119
|
return;
|
|
105
120
|
}
|
|
106
121
|
setIsLoading(true);
|
|
@@ -117,7 +132,11 @@ function AiTextInput(props) {
|
|
|
117
132
|
body: JSON.stringify({ formData: formData ?? {} })
|
|
118
133
|
});
|
|
119
134
|
if (!response.ok) {
|
|
120
|
-
setError(
|
|
135
|
+
setError(
|
|
136
|
+
t("arqel.ai.error_http", "Generation failed (HTTP :status).", {
|
|
137
|
+
status: String(response.status)
|
|
138
|
+
})
|
|
139
|
+
);
|
|
121
140
|
return;
|
|
122
141
|
}
|
|
123
142
|
const body = await response.json();
|
|
@@ -125,12 +144,12 @@ function AiTextInput(props) {
|
|
|
125
144
|
applyGeneratedText(text);
|
|
126
145
|
setHasGenerated(true);
|
|
127
146
|
} catch {
|
|
128
|
-
setError("Generation failed: network error.");
|
|
147
|
+
setError(t("arqel.ai.error_network", "Generation failed: network error."));
|
|
129
148
|
} finally {
|
|
130
149
|
setIsLoading(false);
|
|
131
150
|
}
|
|
132
|
-
}, [applyGeneratedText, csrfToken, field, formData, generateUrl, resource]);
|
|
133
|
-
const triggerLabel = hasGenerated ? REGENERATE_LABEL : buttonLabel;
|
|
151
|
+
}, [applyGeneratedText, csrfToken, field, formData, generateUrl, resource, t]);
|
|
152
|
+
const triggerLabel = hasGenerated ? t("arqel.ai.regenerate", REGENERATE_LABEL) : buttonLabel;
|
|
134
153
|
const charCount = currentValue.length;
|
|
135
154
|
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", "data-arqel-field": "aiText", "data-field-name": name, children: [
|
|
136
155
|
/* @__PURE__ */ jsx(
|
|
@@ -145,9 +164,9 @@ function AiTextInput(props) {
|
|
|
145
164
|
}
|
|
146
165
|
),
|
|
147
166
|
maxLength !== null ? /* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground self-end", "aria-live": "polite", children: [
|
|
148
|
-
charCount,
|
|
167
|
+
numberFormatter.format(charCount),
|
|
149
168
|
" / ",
|
|
150
|
-
maxLength
|
|
169
|
+
numberFormatter.format(maxLength)
|
|
151
170
|
] }) : null,
|
|
152
171
|
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsxs(
|
|
153
172
|
Button,
|
|
@@ -160,7 +179,7 @@ function AiTextInput(props) {
|
|
|
160
179
|
disabled: isLoading,
|
|
161
180
|
"aria-label": triggerLabel,
|
|
162
181
|
children: [
|
|
163
|
-
isLoading ? /* @__PURE__ */ jsx("span", { role: "status", "aria-label": "Generating", children: /* @__PURE__ */ jsx(Spinner, {}) }) : null,
|
|
182
|
+
isLoading ? /* @__PURE__ */ jsx("span", { role: "status", "aria-label": t("arqel.ai.status_generating", "Generating"), children: /* @__PURE__ */ jsx(Spinner, {}) }) : null,
|
|
164
183
|
/* @__PURE__ */ jsx("span", { children: triggerLabel })
|
|
165
184
|
]
|
|
166
185
|
}
|
|
@@ -244,6 +263,7 @@ function AiTranslateInput(props) {
|
|
|
244
263
|
translateUrl,
|
|
245
264
|
csrfToken
|
|
246
265
|
} = props;
|
|
266
|
+
const t = useArqelTranslations();
|
|
247
267
|
const languages = useMemo(() => fieldProps?.languages ?? [], [fieldProps?.languages]);
|
|
248
268
|
const defaultLanguage = fieldProps?.defaultLanguage ?? languages[0] ?? "";
|
|
249
269
|
const reactId = useId();
|
|
@@ -292,7 +312,12 @@ function AiTranslateInput(props) {
|
|
|
292
312
|
}
|
|
293
313
|
const url = buildTranslateUrl(translateUrl, resource, field);
|
|
294
314
|
if (url === null) {
|
|
295
|
-
setError(
|
|
315
|
+
setError(
|
|
316
|
+
t(
|
|
317
|
+
"arqel.ai.missing_translate_url",
|
|
318
|
+
"Missing translate URL: provide `translateUrl` or both `resource` and `field`."
|
|
319
|
+
)
|
|
320
|
+
);
|
|
296
321
|
return;
|
|
297
322
|
}
|
|
298
323
|
setError(null);
|
|
@@ -313,19 +338,25 @@ function AiTranslateInput(props) {
|
|
|
313
338
|
})
|
|
314
339
|
});
|
|
315
340
|
if (!response.ok) {
|
|
316
|
-
setError(
|
|
341
|
+
setError(
|
|
342
|
+
t("arqel.ai.translate_error_http", "Translation failed (HTTP :status).", {
|
|
343
|
+
status: String(response.status)
|
|
344
|
+
})
|
|
345
|
+
);
|
|
317
346
|
return;
|
|
318
347
|
}
|
|
319
348
|
const body = await response.json();
|
|
320
349
|
const incoming = body.translations;
|
|
321
350
|
if (!isStringRecord(incoming)) {
|
|
322
|
-
setError(
|
|
351
|
+
setError(
|
|
352
|
+
t("arqel.ai.translate_error_invalid", "Translation failed: invalid response body.")
|
|
353
|
+
);
|
|
323
354
|
return;
|
|
324
355
|
}
|
|
325
356
|
const merged = { ...currentValue, ...incoming };
|
|
326
357
|
applyValue(merged);
|
|
327
358
|
} catch {
|
|
328
|
-
setError("Translation failed: network error.");
|
|
359
|
+
setError(t("arqel.ai.translate_error_network", "Translation failed: network error."));
|
|
329
360
|
} finally {
|
|
330
361
|
setLoadingFor(targetLanguages, false);
|
|
331
362
|
}
|
|
@@ -338,6 +369,7 @@ function AiTranslateInput(props) {
|
|
|
338
369
|
field,
|
|
339
370
|
resource,
|
|
340
371
|
setLoadingFor,
|
|
372
|
+
t,
|
|
341
373
|
translateUrl
|
|
342
374
|
]
|
|
343
375
|
);
|
|
@@ -366,7 +398,7 @@ function AiTranslateInput(props) {
|
|
|
366
398
|
size: "sm",
|
|
367
399
|
onClick: handleTranslateAllMissing,
|
|
368
400
|
disabled: missingCount === 0 || loadingLangs.size > 0,
|
|
369
|
-
children: "Translate all missing"
|
|
401
|
+
children: t("arqel.ai.translate_all_missing", "Translate all missing")
|
|
370
402
|
}
|
|
371
403
|
) }),
|
|
372
404
|
/* @__PURE__ */ jsx("div", { role: "tablist", id: tablistId, className: "flex items-center gap-1 border-b border-border", children: languages.map((lang) => {
|
|
@@ -389,7 +421,7 @@ function AiTranslateInput(props) {
|
|
|
389
421
|
{
|
|
390
422
|
variant: "secondary",
|
|
391
423
|
role: "img",
|
|
392
|
-
"aria-label": "Missing translation",
|
|
424
|
+
"aria-label": t("arqel.ai.missing_translation", "Missing translation"),
|
|
393
425
|
"data-testid": `missing-dot-${lang}`,
|
|
394
426
|
className: "px-1 py-0",
|
|
395
427
|
children: "\u2022"
|
|
@@ -408,7 +440,9 @@ function AiTranslateInput(props) {
|
|
|
408
440
|
value: activeText,
|
|
409
441
|
onChange: handleTextareaChange,
|
|
410
442
|
rows: 6,
|
|
411
|
-
"aria-label":
|
|
443
|
+
"aria-label": t("arqel.ai.translate_textarea_aria", "Translation in :language", {
|
|
444
|
+
language: activeLang
|
|
445
|
+
})
|
|
412
446
|
}
|
|
413
447
|
),
|
|
414
448
|
activeLang !== defaultLanguage ? /* @__PURE__ */ jsx("div", { className: "flex items-center", children: /* @__PURE__ */ jsxs(
|
|
@@ -419,11 +453,10 @@ function AiTranslateInput(props) {
|
|
|
419
453
|
onClick: () => handleTranslateOne(activeLang),
|
|
420
454
|
disabled: activeIsLoading,
|
|
421
455
|
children: [
|
|
422
|
-
activeIsLoading ? /* @__PURE__ */ jsx("span", { role: "status", "aria-label": "Translating", children: /* @__PURE__ */ jsx(Spinner2, {}) }) : null,
|
|
423
|
-
/* @__PURE__ */
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
] })
|
|
456
|
+
activeIsLoading ? /* @__PURE__ */ jsx("span", { role: "status", "aria-label": t("arqel.ai.status_translating", "Translating"), children: /* @__PURE__ */ jsx(Spinner2, {}) }) : null,
|
|
457
|
+
/* @__PURE__ */ jsx("span", { children: t("arqel.ai.translate_from", "Translate from :language", {
|
|
458
|
+
language: defaultLanguage
|
|
459
|
+
}) })
|
|
427
460
|
]
|
|
428
461
|
}
|
|
429
462
|
) }) : null
|
|
@@ -491,9 +524,11 @@ function AiSelectInput(props) {
|
|
|
491
524
|
classifyUrl,
|
|
492
525
|
csrfToken
|
|
493
526
|
} = props;
|
|
527
|
+
const t = useArqelTranslations();
|
|
494
528
|
const options = fieldProps?.options ?? {};
|
|
495
529
|
const fallbackOption = fieldProps?.fallbackOption ?? null;
|
|
496
530
|
const hasContextFields = fieldProps?.hasContextFields ?? false;
|
|
531
|
+
const buttonLabel = t("arqel.ai.classify", DEFAULT_BUTTON_LABEL2);
|
|
497
532
|
const reactId = useId();
|
|
498
533
|
const selectId = `arqel-ai-select-${reactId}`;
|
|
499
534
|
const [internalValue, setInternalValue] = useState(value);
|
|
@@ -522,7 +557,12 @@ function AiSelectInput(props) {
|
|
|
522
557
|
const handleClassify = useCallback(async () => {
|
|
523
558
|
const url = buildClassifyUrl(classifyUrl, resource, field);
|
|
524
559
|
if (url === null) {
|
|
525
|
-
setError(
|
|
560
|
+
setError(
|
|
561
|
+
t(
|
|
562
|
+
"arqel.ai.missing_classify_url",
|
|
563
|
+
"Missing classify URL: provide `classifyUrl` or both `resource` and `field`."
|
|
564
|
+
)
|
|
565
|
+
);
|
|
526
566
|
return;
|
|
527
567
|
}
|
|
528
568
|
setIsLoading(true);
|
|
@@ -540,7 +580,11 @@ function AiSelectInput(props) {
|
|
|
540
580
|
body: JSON.stringify({ formData: formData ?? {} })
|
|
541
581
|
});
|
|
542
582
|
if (!response.ok) {
|
|
543
|
-
setError(
|
|
583
|
+
setError(
|
|
584
|
+
t("arqel.ai.classify_error_http", "Classification failed (HTTP :status).", {
|
|
585
|
+
status: String(response.status)
|
|
586
|
+
})
|
|
587
|
+
);
|
|
544
588
|
return;
|
|
545
589
|
}
|
|
546
590
|
const body = await response.json();
|
|
@@ -555,19 +599,19 @@ function AiSelectInput(props) {
|
|
|
555
599
|
setSuggestion("fallback");
|
|
556
600
|
return;
|
|
557
601
|
}
|
|
558
|
-
setError("Could not classify.");
|
|
602
|
+
setError(t("arqel.ai.classify_error_none", "Could not classify."));
|
|
559
603
|
} catch {
|
|
560
|
-
setError("Classification failed: network error.");
|
|
604
|
+
setError(t("arqel.ai.classify_error_network", "Classification failed: network error."));
|
|
561
605
|
} finally {
|
|
562
606
|
setIsLoading(false);
|
|
563
607
|
}
|
|
564
|
-
}, [applyValue, classifyUrl, csrfToken, fallbackOption, field, formData, resource]);
|
|
608
|
+
}, [applyValue, classifyUrl, csrfToken, fallbackOption, field, formData, resource, t]);
|
|
565
609
|
const buttonDisabled = isLoading || !hasContextFields;
|
|
566
|
-
const buttonTitle = !hasContextFields ?
|
|
610
|
+
const buttonTitle = !hasContextFields ? t("arqel.ai.classify_no_context_tooltip", NO_CONTEXT_TOOLTIP_FALLBACK) : void 0;
|
|
567
611
|
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", "data-arqel-field": "aiSelect", "data-field-name": name, children: [
|
|
568
612
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
569
613
|
/* @__PURE__ */ jsxs(Select, { value: currentValue ?? "", onValueChange: handleSelectChange, name, children: [
|
|
570
|
-
/* @__PURE__ */ jsx(SelectTrigger, { id: selectId, className: "flex-1", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "Select..." }) }),
|
|
614
|
+
/* @__PURE__ */ jsx(SelectTrigger, { id: selectId, className: "flex-1", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: t("arqel.ai.select_placeholder", "Select...") }) }),
|
|
571
615
|
/* @__PURE__ */ jsx(SelectContent, { children: Object.entries(options).map(([key, label]) => /* @__PURE__ */ jsx(SelectItem, { value: key, children: label }, key)) })
|
|
572
616
|
] }),
|
|
573
617
|
/* @__PURE__ */ jsxs(
|
|
@@ -579,11 +623,11 @@ function AiSelectInput(props) {
|
|
|
579
623
|
void handleClassify();
|
|
580
624
|
},
|
|
581
625
|
disabled: buttonDisabled,
|
|
582
|
-
"aria-label":
|
|
626
|
+
"aria-label": buttonLabel,
|
|
583
627
|
...buttonTitle !== void 0 ? { title: buttonTitle } : {},
|
|
584
628
|
children: [
|
|
585
|
-
isLoading ? /* @__PURE__ */ jsx("span", { role: "status", "aria-label": "Classifying", children: /* @__PURE__ */ jsx(Spinner3, {}) }) : null,
|
|
586
|
-
/* @__PURE__ */ jsx("span", { children:
|
|
629
|
+
isLoading ? /* @__PURE__ */ jsx("span", { role: "status", "aria-label": t("arqel.ai.status_classifying", "Classifying"), children: /* @__PURE__ */ jsx(Spinner3, {}) }) : null,
|
|
630
|
+
/* @__PURE__ */ jsx("span", { children: buttonLabel })
|
|
587
631
|
]
|
|
588
632
|
}
|
|
589
633
|
)
|
|
@@ -594,7 +638,7 @@ function AiSelectInput(props) {
|
|
|
594
638
|
{
|
|
595
639
|
variant: "secondary",
|
|
596
640
|
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"
|
|
641
|
+
children: suggestion === "ai" ? t("arqel.ai.suggestion_ai", "Suggested by AI") : t("arqel.ai.suggestion_fallback", "Used fallback")
|
|
598
642
|
}
|
|
599
643
|
),
|
|
600
644
|
/* @__PURE__ */ jsx(
|
|
@@ -605,7 +649,7 @@ function AiSelectInput(props) {
|
|
|
605
649
|
onClick: () => {
|
|
606
650
|
setSuggestion(null);
|
|
607
651
|
},
|
|
608
|
-
children: "Accept"
|
|
652
|
+
children: t("arqel.ai.suggestion_accept", "Accept")
|
|
609
653
|
}
|
|
610
654
|
),
|
|
611
655
|
/* @__PURE__ */ jsx(
|
|
@@ -616,18 +660,18 @@ function AiSelectInput(props) {
|
|
|
616
660
|
onClick: () => {
|
|
617
661
|
setSuggestion(null);
|
|
618
662
|
},
|
|
619
|
-
children: "Pick another"
|
|
663
|
+
children: t("arqel.ai.suggestion_pick_another", "Pick another")
|
|
620
664
|
}
|
|
621
665
|
)
|
|
622
666
|
] }) : null,
|
|
623
667
|
error !== null ? /* @__PURE__ */ jsx(Alert, { variant: "destructive", children: /* @__PURE__ */ jsx(AlertDescription, { children: error }) }) : null
|
|
624
668
|
] });
|
|
625
669
|
}
|
|
626
|
-
var DEFAULT_BUTTON_LABEL2,
|
|
670
|
+
var DEFAULT_BUTTON_LABEL2, NO_CONTEXT_TOOLTIP_FALLBACK, AiSelectInput_default;
|
|
627
671
|
var init_AiSelectInput = __esm({
|
|
628
672
|
"src/AiSelectInput.tsx"() {
|
|
629
673
|
DEFAULT_BUTTON_LABEL2 = "Classify with AI";
|
|
630
|
-
|
|
674
|
+
NO_CONTEXT_TOOLTIP_FALLBACK = "No context fields configured. Add `classifyFromFields` to enable AI classification.";
|
|
631
675
|
AiSelectInput_default = AiSelectInput;
|
|
632
676
|
}
|
|
633
677
|
});
|
|
@@ -717,9 +761,11 @@ function AiExtractInput(props) {
|
|
|
717
761
|
csrfToken,
|
|
718
762
|
onPopulateField
|
|
719
763
|
} = props;
|
|
764
|
+
const t = useArqelTranslations();
|
|
720
765
|
const sourceField = fieldProps?.sourceField ?? "";
|
|
721
766
|
const targetFields = fieldProps?.targetFields ?? [];
|
|
722
|
-
const
|
|
767
|
+
const serverLabel = fieldProps?.buttonLabel;
|
|
768
|
+
const buttonLabel = serverLabel !== void 0 && serverLabel !== "" ? serverLabel : t("arqel.ai.extract", "Extract with AI");
|
|
723
769
|
const reactId = useId();
|
|
724
770
|
const previewId = `arqel-ai-extract-${reactId}`;
|
|
725
771
|
const [extracted, setExtracted] = useState(value);
|
|
@@ -750,7 +796,12 @@ function AiExtractInput(props) {
|
|
|
750
796
|
const handleExtract = useCallback(async () => {
|
|
751
797
|
const url = buildExtractUrl(extractUrl, resource, field);
|
|
752
798
|
if (url === null) {
|
|
753
|
-
setError(
|
|
799
|
+
setError(
|
|
800
|
+
t(
|
|
801
|
+
"arqel.ai.missing_extract_url",
|
|
802
|
+
"Missing extract URL: provide `extractUrl` or both `resource` and `field`."
|
|
803
|
+
)
|
|
804
|
+
);
|
|
754
805
|
return;
|
|
755
806
|
}
|
|
756
807
|
const sourceText = coerceSourceText(
|
|
@@ -779,27 +830,28 @@ function AiExtractInput(props) {
|
|
|
779
830
|
} catch {
|
|
780
831
|
message = null;
|
|
781
832
|
}
|
|
782
|
-
setError(
|
|
833
|
+
setError(
|
|
834
|
+
message ?? t("arqel.ai.extract_error_http", "Extraction failed (HTTP :status).", {
|
|
835
|
+
status: String(response.status)
|
|
836
|
+
})
|
|
837
|
+
);
|
|
783
838
|
return;
|
|
784
839
|
}
|
|
785
840
|
const body = await response.json();
|
|
786
841
|
if (!isPlainRecord(body.extracted)) {
|
|
787
|
-
setError("Extraction failed: invalid response body.");
|
|
842
|
+
setError(t("arqel.ai.extract_error_invalid", "Extraction failed: invalid response body."));
|
|
788
843
|
return;
|
|
789
844
|
}
|
|
790
845
|
setExtracted(body.extracted);
|
|
791
846
|
} catch {
|
|
792
|
-
setError("Extraction failed: network error.");
|
|
847
|
+
setError(t("arqel.ai.extract_error_network", "Extraction failed: network error."));
|
|
793
848
|
} finally {
|
|
794
849
|
setIsLoading(false);
|
|
795
850
|
}
|
|
796
|
-
}, [csrfToken, extractUrl, field, formData, resource, sourceField]);
|
|
851
|
+
}, [csrfToken, extractUrl, field, formData, resource, sourceField, t]);
|
|
797
852
|
const hasExtraction = extracted !== null && Object.keys(extracted).length > 0;
|
|
798
853
|
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__ */
|
|
800
|
-
"Source: ",
|
|
801
|
-
sourceField
|
|
802
|
-
] }) }),
|
|
854
|
+
/* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Label, { className: "text-muted-foreground", children: t("arqel.ai.source", "Source: :field", { field: sourceField }) }) }),
|
|
803
855
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
804
856
|
/* @__PURE__ */ jsxs(
|
|
805
857
|
Button,
|
|
@@ -812,7 +864,7 @@ function AiExtractInput(props) {
|
|
|
812
864
|
disabled: isLoading,
|
|
813
865
|
"aria-label": buttonLabel,
|
|
814
866
|
children: [
|
|
815
|
-
isLoading ? /* @__PURE__ */ jsx("span", { role: "status", "aria-label": "Extracting", children: /* @__PURE__ */ jsx(Spinner4, {}) }) : null,
|
|
867
|
+
isLoading ? /* @__PURE__ */ jsx("span", { role: "status", "aria-label": t("arqel.ai.status_extracting", "Extracting"), children: /* @__PURE__ */ jsx(Spinner4, {}) }) : null,
|
|
816
868
|
/* @__PURE__ */ jsx("span", { children: buttonLabel })
|
|
817
869
|
]
|
|
818
870
|
}
|
|
@@ -825,7 +877,7 @@ function AiExtractInput(props) {
|
|
|
825
877
|
onClick: () => {
|
|
826
878
|
applyAll(extracted);
|
|
827
879
|
},
|
|
828
|
-
children: "Apply all"
|
|
880
|
+
children: t("arqel.ai.apply_all", "Apply all")
|
|
829
881
|
}
|
|
830
882
|
) : null
|
|
831
883
|
] }),
|
|
@@ -854,8 +906,8 @@ function AiExtractInput(props) {
|
|
|
854
906
|
onClick: () => {
|
|
855
907
|
applyOne(target, val);
|
|
856
908
|
},
|
|
857
|
-
"aria-label":
|
|
858
|
-
children: "Apply"
|
|
909
|
+
"aria-label": t("arqel.ai.apply_field", "Apply :field", { field: target }),
|
|
910
|
+
children: t("arqel.ai.apply", "Apply")
|
|
859
911
|
}
|
|
860
912
|
)
|
|
861
913
|
] })
|
|
@@ -863,7 +915,7 @@ function AiExtractInput(props) {
|
|
|
863
915
|
},
|
|
864
916
|
target
|
|
865
917
|
);
|
|
866
|
-
}) }) }) }) : /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground italic", children: "No extraction yet \u2014 click button to start." }),
|
|
918
|
+
}) }) }) }) : /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground italic", children: t("arqel.ai.extract_empty", "No extraction yet \u2014 click button to start.") }),
|
|
867
919
|
error !== null ? /* @__PURE__ */ jsx(Alert, { variant: "destructive", children: /* @__PURE__ */ jsx(AlertDescription, { children: error }) }) : null
|
|
868
920
|
] });
|
|
869
921
|
}
|
|
@@ -917,14 +969,15 @@ function readFileAsDataUrl(file) {
|
|
|
917
969
|
reader.readAsDataURL(file);
|
|
918
970
|
});
|
|
919
971
|
}
|
|
920
|
-
function formatBytes(bytes) {
|
|
972
|
+
function formatBytes(bytes, locale) {
|
|
973
|
+
const round1 = (value) => new Intl.NumberFormat(locale, { maximumFractionDigits: 1 }).format(value);
|
|
921
974
|
if (bytes >= 1048576) {
|
|
922
|
-
return `${(bytes / 1048576)
|
|
975
|
+
return `${round1(bytes / 1048576)} MB`;
|
|
923
976
|
}
|
|
924
977
|
if (bytes >= 1024) {
|
|
925
|
-
return `${(bytes / 1024)
|
|
978
|
+
return `${round1(bytes / 1024)} KB`;
|
|
926
979
|
}
|
|
927
|
-
return `${
|
|
980
|
+
return `${round1(bytes)} B`;
|
|
928
981
|
}
|
|
929
982
|
function Spinner5() {
|
|
930
983
|
return /* @__PURE__ */ jsxs(
|
|
@@ -968,7 +1021,10 @@ function AiImageInput(props) {
|
|
|
968
1021
|
const populateFields = fieldProps?.populateFields ?? {};
|
|
969
1022
|
const acceptedMimes = fieldProps?.acceptedMimes ?? [];
|
|
970
1023
|
const maxFileSize = fieldProps?.maxFileSize ?? 0;
|
|
971
|
-
const
|
|
1024
|
+
const t = useArqelTranslations();
|
|
1025
|
+
const locale = useArqelLocale();
|
|
1026
|
+
const serverLabel = fieldProps?.buttonLabel;
|
|
1027
|
+
const buttonLabel = serverLabel !== void 0 && serverLabel !== "" ? serverLabel : t("arqel.ai.analyze", "Analyze with AI");
|
|
972
1028
|
const reactId = useId();
|
|
973
1029
|
const inputId = `arqel-ai-image-${reactId}`;
|
|
974
1030
|
const [file, setFile] = useState(null);
|
|
@@ -988,7 +1044,12 @@ function AiImageInput(props) {
|
|
|
988
1044
|
return;
|
|
989
1045
|
}
|
|
990
1046
|
if (maxFileSize > 0 && next.size > maxFileSize) {
|
|
991
|
-
setError(
|
|
1047
|
+
setError(
|
|
1048
|
+
t("arqel.ai.file_too_large", "File too large: :size (max :max).", {
|
|
1049
|
+
size: formatBytes(next.size, locale),
|
|
1050
|
+
max: formatBytes(maxFileSize, locale)
|
|
1051
|
+
})
|
|
1052
|
+
);
|
|
992
1053
|
setFile(null);
|
|
993
1054
|
setPreviewUrl(null);
|
|
994
1055
|
return;
|
|
@@ -1000,7 +1061,7 @@ function AiImageInput(props) {
|
|
|
1000
1061
|
setPreviewUrl(null);
|
|
1001
1062
|
}
|
|
1002
1063
|
},
|
|
1003
|
-
[maxFileSize]
|
|
1064
|
+
[locale, maxFileSize, t]
|
|
1004
1065
|
);
|
|
1005
1066
|
const handleAnalyze = useCallback(async () => {
|
|
1006
1067
|
if (file === null) {
|
|
@@ -1008,7 +1069,12 @@ function AiImageInput(props) {
|
|
|
1008
1069
|
}
|
|
1009
1070
|
const url = buildAnalyzeUrl(analyzeUrl, resource, field);
|
|
1010
1071
|
if (url === null) {
|
|
1011
|
-
setError(
|
|
1072
|
+
setError(
|
|
1073
|
+
t(
|
|
1074
|
+
"arqel.ai.missing_analyze_url",
|
|
1075
|
+
"Missing analyze URL: provide `analyzeUrl` or both `resource` and `field`."
|
|
1076
|
+
)
|
|
1077
|
+
);
|
|
1012
1078
|
return;
|
|
1013
1079
|
}
|
|
1014
1080
|
setIsLoading(true);
|
|
@@ -1035,12 +1101,16 @@ function AiImageInput(props) {
|
|
|
1035
1101
|
} catch {
|
|
1036
1102
|
message = null;
|
|
1037
1103
|
}
|
|
1038
|
-
setError(
|
|
1104
|
+
setError(
|
|
1105
|
+
message ?? t("arqel.ai.analyze_error_http", "Analysis failed (HTTP :status).", {
|
|
1106
|
+
status: String(response.status)
|
|
1107
|
+
})
|
|
1108
|
+
);
|
|
1039
1109
|
return;
|
|
1040
1110
|
}
|
|
1041
1111
|
const body = await response.json();
|
|
1042
1112
|
if (!isStringRecord2(body.analyses)) {
|
|
1043
|
-
setError("Analysis failed: invalid response body.");
|
|
1113
|
+
setError(t("arqel.ai.analyze_error_invalid", "Analysis failed: invalid response body."));
|
|
1044
1114
|
return;
|
|
1045
1115
|
}
|
|
1046
1116
|
setResults(body.analyses);
|
|
@@ -1051,11 +1121,11 @@ function AiImageInput(props) {
|
|
|
1051
1121
|
onChange(imageBase64);
|
|
1052
1122
|
}
|
|
1053
1123
|
} catch {
|
|
1054
|
-
setError("Analysis failed: network error.");
|
|
1124
|
+
setError(t("arqel.ai.analyze_error_network", "Analysis failed: network error."));
|
|
1055
1125
|
} finally {
|
|
1056
1126
|
setIsLoading(false);
|
|
1057
1127
|
}
|
|
1058
|
-
}, [analyzeUrl, csrfToken, field, file, onChange, resource]);
|
|
1128
|
+
}, [analyzeUrl, csrfToken, field, file, onChange, resource, t]);
|
|
1059
1129
|
const applyOne = useCallback(
|
|
1060
1130
|
(analysisKey, val) => {
|
|
1061
1131
|
const target = populateMapping[analysisKey];
|
|
@@ -1096,7 +1166,9 @@ function AiImageInput(props) {
|
|
|
1096
1166
|
htmlFor: inputId,
|
|
1097
1167
|
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
1168
|
children: [
|
|
1099
|
-
/* @__PURE__ */ jsx("span", { children: file !== null ? file.name :
|
|
1169
|
+
/* @__PURE__ */ jsx("span", { children: file !== null ? file.name : t("arqel.ai.image_dropzone", "Click or drop image (:accept)", {
|
|
1170
|
+
accept: accept !== "" ? accept : t("arqel.ai.image_accept_any", "any")
|
|
1171
|
+
}) }),
|
|
1100
1172
|
/* @__PURE__ */ jsx(
|
|
1101
1173
|
"input",
|
|
1102
1174
|
{
|
|
@@ -1105,7 +1177,7 @@ function AiImageInput(props) {
|
|
|
1105
1177
|
accept,
|
|
1106
1178
|
onChange: handleFileChange,
|
|
1107
1179
|
className: "sr-only",
|
|
1108
|
-
"aria-label": "Image file"
|
|
1180
|
+
"aria-label": t("arqel.ai.image_file", "Image file")
|
|
1109
1181
|
}
|
|
1110
1182
|
)
|
|
1111
1183
|
]
|
|
@@ -1115,7 +1187,7 @@ function AiImageInput(props) {
|
|
|
1115
1187
|
"img",
|
|
1116
1188
|
{
|
|
1117
1189
|
src: previewUrl,
|
|
1118
|
-
alt: "Selected preview",
|
|
1190
|
+
alt: t("arqel.ai.selected_preview_alt", "Selected preview"),
|
|
1119
1191
|
"data-testid": "image-preview",
|
|
1120
1192
|
className: "max-h-64 max-w-full rounded-sm"
|
|
1121
1193
|
}
|
|
@@ -1132,7 +1204,7 @@ function AiImageInput(props) {
|
|
|
1132
1204
|
disabled: !canAnalyze,
|
|
1133
1205
|
"aria-label": buttonLabel,
|
|
1134
1206
|
children: [
|
|
1135
|
-
isLoading ? /* @__PURE__ */ jsx("span", { role: "status", "aria-label": "Analyzing", children: /* @__PURE__ */ jsx(Spinner5, {}) }) : null,
|
|
1207
|
+
isLoading ? /* @__PURE__ */ jsx("span", { role: "status", "aria-label": t("arqel.ai.status_analyzing", "Analyzing"), children: /* @__PURE__ */ jsx(Spinner5, {}) }) : null,
|
|
1136
1208
|
/* @__PURE__ */ jsx("span", { children: buttonLabel })
|
|
1137
1209
|
]
|
|
1138
1210
|
}
|
|
@@ -1145,7 +1217,7 @@ function AiImageInput(props) {
|
|
|
1145
1217
|
onClick: () => {
|
|
1146
1218
|
applyAll(results);
|
|
1147
1219
|
},
|
|
1148
|
-
children: "Apply all"
|
|
1220
|
+
children: t("arqel.ai.apply_all", "Apply all")
|
|
1149
1221
|
}
|
|
1150
1222
|
) : null
|
|
1151
1223
|
] }),
|
|
@@ -1179,8 +1251,8 @@ function AiImageInput(props) {
|
|
|
1179
1251
|
onClick: () => {
|
|
1180
1252
|
applyOne(key, val);
|
|
1181
1253
|
},
|
|
1182
|
-
"aria-label":
|
|
1183
|
-
children: "Apply"
|
|
1254
|
+
"aria-label": t("arqel.ai.apply_field", "Apply :field", { field: key }),
|
|
1255
|
+
children: t("arqel.ai.apply", "Apply")
|
|
1184
1256
|
}
|
|
1185
1257
|
) : null
|
|
1186
1258
|
] })
|