@arqel-dev/ai 0.14.0 → 0.15.1
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 +138 -71
- package/dist/register.js.map +1 -1
- package/package.json +5 -3
package/dist/register.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
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
|
|
|
@@ -66,7 +67,11 @@ function AiTextInput(props) {
|
|
|
66
67
|
generateUrl,
|
|
67
68
|
csrfToken
|
|
68
69
|
} = props;
|
|
69
|
-
const
|
|
70
|
+
const t = useArqelTranslations();
|
|
71
|
+
const locale = useArqelLocale();
|
|
72
|
+
const numberFormatter = useMemo(() => new Intl.NumberFormat(locale), [locale]);
|
|
73
|
+
const serverLabel = fieldProps?.buttonLabel;
|
|
74
|
+
const buttonLabel = serverLabel !== void 0 && serverLabel !== "" ? serverLabel : t("arqel.ai.generate", DEFAULT_BUTTON_LABEL);
|
|
70
75
|
const maxLength = fieldProps?.maxLength ?? null;
|
|
71
76
|
const reactId = useId();
|
|
72
77
|
const textareaId = `arqel-ai-text-${reactId}`;
|
|
@@ -100,7 +105,12 @@ function AiTextInput(props) {
|
|
|
100
105
|
const handleGenerate = useCallback(async () => {
|
|
101
106
|
const url = buildGenerateUrl(generateUrl, resource, field);
|
|
102
107
|
if (url === null) {
|
|
103
|
-
setError(
|
|
108
|
+
setError(
|
|
109
|
+
t(
|
|
110
|
+
"arqel.ai.missing_generate_url",
|
|
111
|
+
"Missing generate URL: provide `generateUrl` or both `resource` and `field`."
|
|
112
|
+
)
|
|
113
|
+
);
|
|
104
114
|
return;
|
|
105
115
|
}
|
|
106
116
|
setIsLoading(true);
|
|
@@ -117,7 +127,11 @@ function AiTextInput(props) {
|
|
|
117
127
|
body: JSON.stringify({ formData: formData ?? {} })
|
|
118
128
|
});
|
|
119
129
|
if (!response.ok) {
|
|
120
|
-
setError(
|
|
130
|
+
setError(
|
|
131
|
+
t("arqel.ai.error_http", "Generation failed (HTTP :status).", {
|
|
132
|
+
status: String(response.status)
|
|
133
|
+
})
|
|
134
|
+
);
|
|
121
135
|
return;
|
|
122
136
|
}
|
|
123
137
|
const body = await response.json();
|
|
@@ -125,12 +139,12 @@ function AiTextInput(props) {
|
|
|
125
139
|
applyGeneratedText(text);
|
|
126
140
|
setHasGenerated(true);
|
|
127
141
|
} catch {
|
|
128
|
-
setError("Generation failed: network error.");
|
|
142
|
+
setError(t("arqel.ai.error_network", "Generation failed: network error."));
|
|
129
143
|
} finally {
|
|
130
144
|
setIsLoading(false);
|
|
131
145
|
}
|
|
132
|
-
}, [applyGeneratedText, csrfToken, field, formData, generateUrl, resource]);
|
|
133
|
-
const triggerLabel = hasGenerated ? REGENERATE_LABEL : buttonLabel;
|
|
146
|
+
}, [applyGeneratedText, csrfToken, field, formData, generateUrl, resource, t]);
|
|
147
|
+
const triggerLabel = hasGenerated ? t("arqel.ai.regenerate", REGENERATE_LABEL) : buttonLabel;
|
|
134
148
|
const charCount = currentValue.length;
|
|
135
149
|
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", "data-arqel-field": "aiText", "data-field-name": name, children: [
|
|
136
150
|
/* @__PURE__ */ jsx(
|
|
@@ -145,9 +159,9 @@ function AiTextInput(props) {
|
|
|
145
159
|
}
|
|
146
160
|
),
|
|
147
161
|
maxLength !== null ? /* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground self-end", "aria-live": "polite", children: [
|
|
148
|
-
charCount,
|
|
162
|
+
numberFormatter.format(charCount),
|
|
149
163
|
" / ",
|
|
150
|
-
maxLength
|
|
164
|
+
numberFormatter.format(maxLength)
|
|
151
165
|
] }) : null,
|
|
152
166
|
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsxs(
|
|
153
167
|
Button,
|
|
@@ -160,7 +174,7 @@ function AiTextInput(props) {
|
|
|
160
174
|
disabled: isLoading,
|
|
161
175
|
"aria-label": triggerLabel,
|
|
162
176
|
children: [
|
|
163
|
-
isLoading ? /* @__PURE__ */ jsx("span", { role: "status", "aria-label": "Generating", children: /* @__PURE__ */ jsx(Spinner, {}) }) : null,
|
|
177
|
+
isLoading ? /* @__PURE__ */ jsx("span", { role: "status", "aria-label": t("arqel.ai.status_generating", "Generating"), children: /* @__PURE__ */ jsx(Spinner, {}) }) : null,
|
|
164
178
|
/* @__PURE__ */ jsx("span", { children: triggerLabel })
|
|
165
179
|
]
|
|
166
180
|
}
|
|
@@ -244,6 +258,7 @@ function AiTranslateInput(props) {
|
|
|
244
258
|
translateUrl,
|
|
245
259
|
csrfToken
|
|
246
260
|
} = props;
|
|
261
|
+
const t = useArqelTranslations();
|
|
247
262
|
const languages = useMemo(() => fieldProps?.languages ?? [], [fieldProps?.languages]);
|
|
248
263
|
const defaultLanguage = fieldProps?.defaultLanguage ?? languages[0] ?? "";
|
|
249
264
|
const reactId = useId();
|
|
@@ -292,7 +307,12 @@ function AiTranslateInput(props) {
|
|
|
292
307
|
}
|
|
293
308
|
const url = buildTranslateUrl(translateUrl, resource, field);
|
|
294
309
|
if (url === null) {
|
|
295
|
-
setError(
|
|
310
|
+
setError(
|
|
311
|
+
t(
|
|
312
|
+
"arqel.ai.missing_translate_url",
|
|
313
|
+
"Missing translate URL: provide `translateUrl` or both `resource` and `field`."
|
|
314
|
+
)
|
|
315
|
+
);
|
|
296
316
|
return;
|
|
297
317
|
}
|
|
298
318
|
setError(null);
|
|
@@ -313,19 +333,25 @@ function AiTranslateInput(props) {
|
|
|
313
333
|
})
|
|
314
334
|
});
|
|
315
335
|
if (!response.ok) {
|
|
316
|
-
setError(
|
|
336
|
+
setError(
|
|
337
|
+
t("arqel.ai.translate_error_http", "Translation failed (HTTP :status).", {
|
|
338
|
+
status: String(response.status)
|
|
339
|
+
})
|
|
340
|
+
);
|
|
317
341
|
return;
|
|
318
342
|
}
|
|
319
343
|
const body = await response.json();
|
|
320
344
|
const incoming = body.translations;
|
|
321
345
|
if (!isStringRecord(incoming)) {
|
|
322
|
-
setError(
|
|
346
|
+
setError(
|
|
347
|
+
t("arqel.ai.translate_error_invalid", "Translation failed: invalid response body.")
|
|
348
|
+
);
|
|
323
349
|
return;
|
|
324
350
|
}
|
|
325
351
|
const merged = { ...currentValue, ...incoming };
|
|
326
352
|
applyValue(merged);
|
|
327
353
|
} catch {
|
|
328
|
-
setError("Translation failed: network error.");
|
|
354
|
+
setError(t("arqel.ai.translate_error_network", "Translation failed: network error."));
|
|
329
355
|
} finally {
|
|
330
356
|
setLoadingFor(targetLanguages, false);
|
|
331
357
|
}
|
|
@@ -338,6 +364,7 @@ function AiTranslateInput(props) {
|
|
|
338
364
|
field,
|
|
339
365
|
resource,
|
|
340
366
|
setLoadingFor,
|
|
367
|
+
t,
|
|
341
368
|
translateUrl
|
|
342
369
|
]
|
|
343
370
|
);
|
|
@@ -366,7 +393,7 @@ function AiTranslateInput(props) {
|
|
|
366
393
|
size: "sm",
|
|
367
394
|
onClick: handleTranslateAllMissing,
|
|
368
395
|
disabled: missingCount === 0 || loadingLangs.size > 0,
|
|
369
|
-
children: "Translate all missing"
|
|
396
|
+
children: t("arqel.ai.translate_all_missing", "Translate all missing")
|
|
370
397
|
}
|
|
371
398
|
) }),
|
|
372
399
|
/* @__PURE__ */ jsx("div", { role: "tablist", id: tablistId, className: "flex items-center gap-1 border-b border-border", children: languages.map((lang) => {
|
|
@@ -389,7 +416,7 @@ function AiTranslateInput(props) {
|
|
|
389
416
|
{
|
|
390
417
|
variant: "secondary",
|
|
391
418
|
role: "img",
|
|
392
|
-
"aria-label": "Missing translation",
|
|
419
|
+
"aria-label": t("arqel.ai.missing_translation", "Missing translation"),
|
|
393
420
|
"data-testid": `missing-dot-${lang}`,
|
|
394
421
|
className: "px-1 py-0",
|
|
395
422
|
children: "\u2022"
|
|
@@ -408,7 +435,9 @@ function AiTranslateInput(props) {
|
|
|
408
435
|
value: activeText,
|
|
409
436
|
onChange: handleTextareaChange,
|
|
410
437
|
rows: 6,
|
|
411
|
-
"aria-label":
|
|
438
|
+
"aria-label": t("arqel.ai.translate_textarea_aria", "Translation in :language", {
|
|
439
|
+
language: activeLang
|
|
440
|
+
})
|
|
412
441
|
}
|
|
413
442
|
),
|
|
414
443
|
activeLang !== defaultLanguage ? /* @__PURE__ */ jsx("div", { className: "flex items-center", children: /* @__PURE__ */ jsxs(
|
|
@@ -419,11 +448,10 @@ function AiTranslateInput(props) {
|
|
|
419
448
|
onClick: () => handleTranslateOne(activeLang),
|
|
420
449
|
disabled: activeIsLoading,
|
|
421
450
|
children: [
|
|
422
|
-
activeIsLoading ? /* @__PURE__ */ jsx("span", { role: "status", "aria-label": "Translating", children: /* @__PURE__ */ jsx(Spinner2, {}) }) : null,
|
|
423
|
-
/* @__PURE__ */
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
] })
|
|
451
|
+
activeIsLoading ? /* @__PURE__ */ jsx("span", { role: "status", "aria-label": t("arqel.ai.status_translating", "Translating"), children: /* @__PURE__ */ jsx(Spinner2, {}) }) : null,
|
|
452
|
+
/* @__PURE__ */ jsx("span", { children: t("arqel.ai.translate_from", "Translate from :language", {
|
|
453
|
+
language: defaultLanguage
|
|
454
|
+
}) })
|
|
427
455
|
]
|
|
428
456
|
}
|
|
429
457
|
) }) : null
|
|
@@ -491,9 +519,11 @@ function AiSelectInput(props) {
|
|
|
491
519
|
classifyUrl,
|
|
492
520
|
csrfToken
|
|
493
521
|
} = props;
|
|
522
|
+
const t = useArqelTranslations();
|
|
494
523
|
const options = fieldProps?.options ?? {};
|
|
495
524
|
const fallbackOption = fieldProps?.fallbackOption ?? null;
|
|
496
525
|
const hasContextFields = fieldProps?.hasContextFields ?? false;
|
|
526
|
+
const buttonLabel = t("arqel.ai.classify", DEFAULT_BUTTON_LABEL2);
|
|
497
527
|
const reactId = useId();
|
|
498
528
|
const selectId = `arqel-ai-select-${reactId}`;
|
|
499
529
|
const [internalValue, setInternalValue] = useState(value);
|
|
@@ -522,7 +552,12 @@ function AiSelectInput(props) {
|
|
|
522
552
|
const handleClassify = useCallback(async () => {
|
|
523
553
|
const url = buildClassifyUrl(classifyUrl, resource, field);
|
|
524
554
|
if (url === null) {
|
|
525
|
-
setError(
|
|
555
|
+
setError(
|
|
556
|
+
t(
|
|
557
|
+
"arqel.ai.missing_classify_url",
|
|
558
|
+
"Missing classify URL: provide `classifyUrl` or both `resource` and `field`."
|
|
559
|
+
)
|
|
560
|
+
);
|
|
526
561
|
return;
|
|
527
562
|
}
|
|
528
563
|
setIsLoading(true);
|
|
@@ -540,7 +575,11 @@ function AiSelectInput(props) {
|
|
|
540
575
|
body: JSON.stringify({ formData: formData ?? {} })
|
|
541
576
|
});
|
|
542
577
|
if (!response.ok) {
|
|
543
|
-
setError(
|
|
578
|
+
setError(
|
|
579
|
+
t("arqel.ai.classify_error_http", "Classification failed (HTTP :status).", {
|
|
580
|
+
status: String(response.status)
|
|
581
|
+
})
|
|
582
|
+
);
|
|
544
583
|
return;
|
|
545
584
|
}
|
|
546
585
|
const body = await response.json();
|
|
@@ -555,19 +594,19 @@ function AiSelectInput(props) {
|
|
|
555
594
|
setSuggestion("fallback");
|
|
556
595
|
return;
|
|
557
596
|
}
|
|
558
|
-
setError("Could not classify.");
|
|
597
|
+
setError(t("arqel.ai.classify_error_none", "Could not classify."));
|
|
559
598
|
} catch {
|
|
560
|
-
setError("Classification failed: network error.");
|
|
599
|
+
setError(t("arqel.ai.classify_error_network", "Classification failed: network error."));
|
|
561
600
|
} finally {
|
|
562
601
|
setIsLoading(false);
|
|
563
602
|
}
|
|
564
|
-
}, [applyValue, classifyUrl, csrfToken, fallbackOption, field, formData, resource]);
|
|
603
|
+
}, [applyValue, classifyUrl, csrfToken, fallbackOption, field, formData, resource, t]);
|
|
565
604
|
const buttonDisabled = isLoading || !hasContextFields;
|
|
566
|
-
const buttonTitle = !hasContextFields ?
|
|
605
|
+
const buttonTitle = !hasContextFields ? t("arqel.ai.classify_no_context_tooltip", NO_CONTEXT_TOOLTIP_FALLBACK) : void 0;
|
|
567
606
|
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", "data-arqel-field": "aiSelect", "data-field-name": name, children: [
|
|
568
607
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
569
608
|
/* @__PURE__ */ jsxs(Select, { value: currentValue ?? "", onValueChange: handleSelectChange, name, children: [
|
|
570
|
-
/* @__PURE__ */ jsx(SelectTrigger, { id: selectId, className: "flex-1", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "Select..." }) }),
|
|
609
|
+
/* @__PURE__ */ jsx(SelectTrigger, { id: selectId, className: "flex-1", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: t("arqel.ai.select_placeholder", "Select...") }) }),
|
|
571
610
|
/* @__PURE__ */ jsx(SelectContent, { children: Object.entries(options).map(([key, label]) => /* @__PURE__ */ jsx(SelectItem, { value: key, children: label }, key)) })
|
|
572
611
|
] }),
|
|
573
612
|
/* @__PURE__ */ jsxs(
|
|
@@ -579,11 +618,11 @@ function AiSelectInput(props) {
|
|
|
579
618
|
void handleClassify();
|
|
580
619
|
},
|
|
581
620
|
disabled: buttonDisabled,
|
|
582
|
-
"aria-label":
|
|
621
|
+
"aria-label": buttonLabel,
|
|
583
622
|
...buttonTitle !== void 0 ? { title: buttonTitle } : {},
|
|
584
623
|
children: [
|
|
585
|
-
isLoading ? /* @__PURE__ */ jsx("span", { role: "status", "aria-label": "Classifying", children: /* @__PURE__ */ jsx(Spinner3, {}) }) : null,
|
|
586
|
-
/* @__PURE__ */ jsx("span", { children:
|
|
624
|
+
isLoading ? /* @__PURE__ */ jsx("span", { role: "status", "aria-label": t("arqel.ai.status_classifying", "Classifying"), children: /* @__PURE__ */ jsx(Spinner3, {}) }) : null,
|
|
625
|
+
/* @__PURE__ */ jsx("span", { children: buttonLabel })
|
|
587
626
|
]
|
|
588
627
|
}
|
|
589
628
|
)
|
|
@@ -594,7 +633,7 @@ function AiSelectInput(props) {
|
|
|
594
633
|
{
|
|
595
634
|
variant: "secondary",
|
|
596
635
|
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"
|
|
636
|
+
children: suggestion === "ai" ? t("arqel.ai.suggestion_ai", "Suggested by AI") : t("arqel.ai.suggestion_fallback", "Used fallback")
|
|
598
637
|
}
|
|
599
638
|
),
|
|
600
639
|
/* @__PURE__ */ jsx(
|
|
@@ -605,7 +644,7 @@ function AiSelectInput(props) {
|
|
|
605
644
|
onClick: () => {
|
|
606
645
|
setSuggestion(null);
|
|
607
646
|
},
|
|
608
|
-
children: "Accept"
|
|
647
|
+
children: t("arqel.ai.suggestion_accept", "Accept")
|
|
609
648
|
}
|
|
610
649
|
),
|
|
611
650
|
/* @__PURE__ */ jsx(
|
|
@@ -616,18 +655,18 @@ function AiSelectInput(props) {
|
|
|
616
655
|
onClick: () => {
|
|
617
656
|
setSuggestion(null);
|
|
618
657
|
},
|
|
619
|
-
children: "Pick another"
|
|
658
|
+
children: t("arqel.ai.suggestion_pick_another", "Pick another")
|
|
620
659
|
}
|
|
621
660
|
)
|
|
622
661
|
] }) : null,
|
|
623
662
|
error !== null ? /* @__PURE__ */ jsx(Alert, { variant: "destructive", children: /* @__PURE__ */ jsx(AlertDescription, { children: error }) }) : null
|
|
624
663
|
] });
|
|
625
664
|
}
|
|
626
|
-
var DEFAULT_BUTTON_LABEL2,
|
|
665
|
+
var DEFAULT_BUTTON_LABEL2, NO_CONTEXT_TOOLTIP_FALLBACK, AiSelectInput_default;
|
|
627
666
|
var init_AiSelectInput = __esm({
|
|
628
667
|
"src/AiSelectInput.tsx"() {
|
|
629
668
|
DEFAULT_BUTTON_LABEL2 = "Classify with AI";
|
|
630
|
-
|
|
669
|
+
NO_CONTEXT_TOOLTIP_FALLBACK = "No context fields configured. Add `classifyFromFields` to enable AI classification.";
|
|
631
670
|
AiSelectInput_default = AiSelectInput;
|
|
632
671
|
}
|
|
633
672
|
});
|
|
@@ -717,9 +756,11 @@ function AiExtractInput(props) {
|
|
|
717
756
|
csrfToken,
|
|
718
757
|
onPopulateField
|
|
719
758
|
} = props;
|
|
759
|
+
const t = useArqelTranslations();
|
|
720
760
|
const sourceField = fieldProps?.sourceField ?? "";
|
|
721
761
|
const targetFields = fieldProps?.targetFields ?? [];
|
|
722
|
-
const
|
|
762
|
+
const serverLabel = fieldProps?.buttonLabel;
|
|
763
|
+
const buttonLabel = serverLabel !== void 0 && serverLabel !== "" ? serverLabel : t("arqel.ai.extract", "Extract with AI");
|
|
723
764
|
const reactId = useId();
|
|
724
765
|
const previewId = `arqel-ai-extract-${reactId}`;
|
|
725
766
|
const [extracted, setExtracted] = useState(value);
|
|
@@ -750,7 +791,12 @@ function AiExtractInput(props) {
|
|
|
750
791
|
const handleExtract = useCallback(async () => {
|
|
751
792
|
const url = buildExtractUrl(extractUrl, resource, field);
|
|
752
793
|
if (url === null) {
|
|
753
|
-
setError(
|
|
794
|
+
setError(
|
|
795
|
+
t(
|
|
796
|
+
"arqel.ai.missing_extract_url",
|
|
797
|
+
"Missing extract URL: provide `extractUrl` or both `resource` and `field`."
|
|
798
|
+
)
|
|
799
|
+
);
|
|
754
800
|
return;
|
|
755
801
|
}
|
|
756
802
|
const sourceText = coerceSourceText(
|
|
@@ -779,27 +825,28 @@ function AiExtractInput(props) {
|
|
|
779
825
|
} catch {
|
|
780
826
|
message = null;
|
|
781
827
|
}
|
|
782
|
-
setError(
|
|
828
|
+
setError(
|
|
829
|
+
message ?? t("arqel.ai.extract_error_http", "Extraction failed (HTTP :status).", {
|
|
830
|
+
status: String(response.status)
|
|
831
|
+
})
|
|
832
|
+
);
|
|
783
833
|
return;
|
|
784
834
|
}
|
|
785
835
|
const body = await response.json();
|
|
786
836
|
if (!isPlainRecord(body.extracted)) {
|
|
787
|
-
setError("Extraction failed: invalid response body.");
|
|
837
|
+
setError(t("arqel.ai.extract_error_invalid", "Extraction failed: invalid response body."));
|
|
788
838
|
return;
|
|
789
839
|
}
|
|
790
840
|
setExtracted(body.extracted);
|
|
791
841
|
} catch {
|
|
792
|
-
setError("Extraction failed: network error.");
|
|
842
|
+
setError(t("arqel.ai.extract_error_network", "Extraction failed: network error."));
|
|
793
843
|
} finally {
|
|
794
844
|
setIsLoading(false);
|
|
795
845
|
}
|
|
796
|
-
}, [csrfToken, extractUrl, field, formData, resource, sourceField]);
|
|
846
|
+
}, [csrfToken, extractUrl, field, formData, resource, sourceField, t]);
|
|
797
847
|
const hasExtraction = extracted !== null && Object.keys(extracted).length > 0;
|
|
798
848
|
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
|
-
] }) }),
|
|
849
|
+
/* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Label, { className: "text-muted-foreground", children: t("arqel.ai.source", "Source: :field", { field: sourceField }) }) }),
|
|
803
850
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
804
851
|
/* @__PURE__ */ jsxs(
|
|
805
852
|
Button,
|
|
@@ -812,7 +859,7 @@ function AiExtractInput(props) {
|
|
|
812
859
|
disabled: isLoading,
|
|
813
860
|
"aria-label": buttonLabel,
|
|
814
861
|
children: [
|
|
815
|
-
isLoading ? /* @__PURE__ */ jsx("span", { role: "status", "aria-label": "Extracting", children: /* @__PURE__ */ jsx(Spinner4, {}) }) : null,
|
|
862
|
+
isLoading ? /* @__PURE__ */ jsx("span", { role: "status", "aria-label": t("arqel.ai.status_extracting", "Extracting"), children: /* @__PURE__ */ jsx(Spinner4, {}) }) : null,
|
|
816
863
|
/* @__PURE__ */ jsx("span", { children: buttonLabel })
|
|
817
864
|
]
|
|
818
865
|
}
|
|
@@ -825,7 +872,7 @@ function AiExtractInput(props) {
|
|
|
825
872
|
onClick: () => {
|
|
826
873
|
applyAll(extracted);
|
|
827
874
|
},
|
|
828
|
-
children: "Apply all"
|
|
875
|
+
children: t("arqel.ai.apply_all", "Apply all")
|
|
829
876
|
}
|
|
830
877
|
) : null
|
|
831
878
|
] }),
|
|
@@ -854,8 +901,8 @@ function AiExtractInput(props) {
|
|
|
854
901
|
onClick: () => {
|
|
855
902
|
applyOne(target, val);
|
|
856
903
|
},
|
|
857
|
-
"aria-label":
|
|
858
|
-
children: "Apply"
|
|
904
|
+
"aria-label": t("arqel.ai.apply_field", "Apply :field", { field: target }),
|
|
905
|
+
children: t("arqel.ai.apply", "Apply")
|
|
859
906
|
}
|
|
860
907
|
)
|
|
861
908
|
] })
|
|
@@ -863,7 +910,7 @@ function AiExtractInput(props) {
|
|
|
863
910
|
},
|
|
864
911
|
target
|
|
865
912
|
);
|
|
866
|
-
}) }) }) }) : /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground italic", children: "No extraction yet \u2014 click button to start." }),
|
|
913
|
+
}) }) }) }) : /* @__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
914
|
error !== null ? /* @__PURE__ */ jsx(Alert, { variant: "destructive", children: /* @__PURE__ */ jsx(AlertDescription, { children: error }) }) : null
|
|
868
915
|
] });
|
|
869
916
|
}
|
|
@@ -917,14 +964,15 @@ function readFileAsDataUrl(file) {
|
|
|
917
964
|
reader.readAsDataURL(file);
|
|
918
965
|
});
|
|
919
966
|
}
|
|
920
|
-
function formatBytes(bytes) {
|
|
967
|
+
function formatBytes(bytes, locale) {
|
|
968
|
+
const round1 = (value) => new Intl.NumberFormat(locale, { maximumFractionDigits: 1 }).format(value);
|
|
921
969
|
if (bytes >= 1048576) {
|
|
922
|
-
return `${(bytes / 1048576)
|
|
970
|
+
return `${round1(bytes / 1048576)} MB`;
|
|
923
971
|
}
|
|
924
972
|
if (bytes >= 1024) {
|
|
925
|
-
return `${(bytes / 1024)
|
|
973
|
+
return `${round1(bytes / 1024)} KB`;
|
|
926
974
|
}
|
|
927
|
-
return `${
|
|
975
|
+
return `${round1(bytes)} B`;
|
|
928
976
|
}
|
|
929
977
|
function Spinner5() {
|
|
930
978
|
return /* @__PURE__ */ jsxs(
|
|
@@ -968,7 +1016,10 @@ function AiImageInput(props) {
|
|
|
968
1016
|
const populateFields = fieldProps?.populateFields ?? {};
|
|
969
1017
|
const acceptedMimes = fieldProps?.acceptedMimes ?? [];
|
|
970
1018
|
const maxFileSize = fieldProps?.maxFileSize ?? 0;
|
|
971
|
-
const
|
|
1019
|
+
const t = useArqelTranslations();
|
|
1020
|
+
const locale = useArqelLocale();
|
|
1021
|
+
const serverLabel = fieldProps?.buttonLabel;
|
|
1022
|
+
const buttonLabel = serverLabel !== void 0 && serverLabel !== "" ? serverLabel : t("arqel.ai.analyze", "Analyze with AI");
|
|
972
1023
|
const reactId = useId();
|
|
973
1024
|
const inputId = `arqel-ai-image-${reactId}`;
|
|
974
1025
|
const [file, setFile] = useState(null);
|
|
@@ -988,7 +1039,12 @@ function AiImageInput(props) {
|
|
|
988
1039
|
return;
|
|
989
1040
|
}
|
|
990
1041
|
if (maxFileSize > 0 && next.size > maxFileSize) {
|
|
991
|
-
setError(
|
|
1042
|
+
setError(
|
|
1043
|
+
t("arqel.ai.file_too_large", "File too large: :size (max :max).", {
|
|
1044
|
+
size: formatBytes(next.size, locale),
|
|
1045
|
+
max: formatBytes(maxFileSize, locale)
|
|
1046
|
+
})
|
|
1047
|
+
);
|
|
992
1048
|
setFile(null);
|
|
993
1049
|
setPreviewUrl(null);
|
|
994
1050
|
return;
|
|
@@ -1000,7 +1056,7 @@ function AiImageInput(props) {
|
|
|
1000
1056
|
setPreviewUrl(null);
|
|
1001
1057
|
}
|
|
1002
1058
|
},
|
|
1003
|
-
[maxFileSize]
|
|
1059
|
+
[locale, maxFileSize, t]
|
|
1004
1060
|
);
|
|
1005
1061
|
const handleAnalyze = useCallback(async () => {
|
|
1006
1062
|
if (file === null) {
|
|
@@ -1008,7 +1064,12 @@ function AiImageInput(props) {
|
|
|
1008
1064
|
}
|
|
1009
1065
|
const url = buildAnalyzeUrl(analyzeUrl, resource, field);
|
|
1010
1066
|
if (url === null) {
|
|
1011
|
-
setError(
|
|
1067
|
+
setError(
|
|
1068
|
+
t(
|
|
1069
|
+
"arqel.ai.missing_analyze_url",
|
|
1070
|
+
"Missing analyze URL: provide `analyzeUrl` or both `resource` and `field`."
|
|
1071
|
+
)
|
|
1072
|
+
);
|
|
1012
1073
|
return;
|
|
1013
1074
|
}
|
|
1014
1075
|
setIsLoading(true);
|
|
@@ -1035,12 +1096,16 @@ function AiImageInput(props) {
|
|
|
1035
1096
|
} catch {
|
|
1036
1097
|
message = null;
|
|
1037
1098
|
}
|
|
1038
|
-
setError(
|
|
1099
|
+
setError(
|
|
1100
|
+
message ?? t("arqel.ai.analyze_error_http", "Analysis failed (HTTP :status).", {
|
|
1101
|
+
status: String(response.status)
|
|
1102
|
+
})
|
|
1103
|
+
);
|
|
1039
1104
|
return;
|
|
1040
1105
|
}
|
|
1041
1106
|
const body = await response.json();
|
|
1042
1107
|
if (!isStringRecord2(body.analyses)) {
|
|
1043
|
-
setError("Analysis failed: invalid response body.");
|
|
1108
|
+
setError(t("arqel.ai.analyze_error_invalid", "Analysis failed: invalid response body."));
|
|
1044
1109
|
return;
|
|
1045
1110
|
}
|
|
1046
1111
|
setResults(body.analyses);
|
|
@@ -1051,11 +1116,11 @@ function AiImageInput(props) {
|
|
|
1051
1116
|
onChange(imageBase64);
|
|
1052
1117
|
}
|
|
1053
1118
|
} catch {
|
|
1054
|
-
setError("Analysis failed: network error.");
|
|
1119
|
+
setError(t("arqel.ai.analyze_error_network", "Analysis failed: network error."));
|
|
1055
1120
|
} finally {
|
|
1056
1121
|
setIsLoading(false);
|
|
1057
1122
|
}
|
|
1058
|
-
}, [analyzeUrl, csrfToken, field, file, onChange, resource]);
|
|
1123
|
+
}, [analyzeUrl, csrfToken, field, file, onChange, resource, t]);
|
|
1059
1124
|
const applyOne = useCallback(
|
|
1060
1125
|
(analysisKey, val) => {
|
|
1061
1126
|
const target = populateMapping[analysisKey];
|
|
@@ -1096,7 +1161,9 @@ function AiImageInput(props) {
|
|
|
1096
1161
|
htmlFor: inputId,
|
|
1097
1162
|
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
1163
|
children: [
|
|
1099
|
-
/* @__PURE__ */ jsx("span", { children: file !== null ? file.name :
|
|
1164
|
+
/* @__PURE__ */ jsx("span", { children: file !== null ? file.name : t("arqel.ai.image_dropzone", "Click or drop image (:accept)", {
|
|
1165
|
+
accept: accept !== "" ? accept : t("arqel.ai.image_accept_any", "any")
|
|
1166
|
+
}) }),
|
|
1100
1167
|
/* @__PURE__ */ jsx(
|
|
1101
1168
|
"input",
|
|
1102
1169
|
{
|
|
@@ -1105,7 +1172,7 @@ function AiImageInput(props) {
|
|
|
1105
1172
|
accept,
|
|
1106
1173
|
onChange: handleFileChange,
|
|
1107
1174
|
className: "sr-only",
|
|
1108
|
-
"aria-label": "Image file"
|
|
1175
|
+
"aria-label": t("arqel.ai.image_file", "Image file")
|
|
1109
1176
|
}
|
|
1110
1177
|
)
|
|
1111
1178
|
]
|
|
@@ -1115,7 +1182,7 @@ function AiImageInput(props) {
|
|
|
1115
1182
|
"img",
|
|
1116
1183
|
{
|
|
1117
1184
|
src: previewUrl,
|
|
1118
|
-
alt: "Selected preview",
|
|
1185
|
+
alt: t("arqel.ai.selected_preview_alt", "Selected preview"),
|
|
1119
1186
|
"data-testid": "image-preview",
|
|
1120
1187
|
className: "max-h-64 max-w-full rounded-sm"
|
|
1121
1188
|
}
|
|
@@ -1132,7 +1199,7 @@ function AiImageInput(props) {
|
|
|
1132
1199
|
disabled: !canAnalyze,
|
|
1133
1200
|
"aria-label": buttonLabel,
|
|
1134
1201
|
children: [
|
|
1135
|
-
isLoading ? /* @__PURE__ */ jsx("span", { role: "status", "aria-label": "Analyzing", children: /* @__PURE__ */ jsx(Spinner5, {}) }) : null,
|
|
1202
|
+
isLoading ? /* @__PURE__ */ jsx("span", { role: "status", "aria-label": t("arqel.ai.status_analyzing", "Analyzing"), children: /* @__PURE__ */ jsx(Spinner5, {}) }) : null,
|
|
1136
1203
|
/* @__PURE__ */ jsx("span", { children: buttonLabel })
|
|
1137
1204
|
]
|
|
1138
1205
|
}
|
|
@@ -1145,7 +1212,7 @@ function AiImageInput(props) {
|
|
|
1145
1212
|
onClick: () => {
|
|
1146
1213
|
applyAll(results);
|
|
1147
1214
|
},
|
|
1148
|
-
children: "Apply all"
|
|
1215
|
+
children: t("arqel.ai.apply_all", "Apply all")
|
|
1149
1216
|
}
|
|
1150
1217
|
) : null
|
|
1151
1218
|
] }),
|
|
@@ -1179,8 +1246,8 @@ function AiImageInput(props) {
|
|
|
1179
1246
|
onClick: () => {
|
|
1180
1247
|
applyOne(key, val);
|
|
1181
1248
|
},
|
|
1182
|
-
"aria-label":
|
|
1183
|
-
children: "Apply"
|
|
1249
|
+
"aria-label": t("arqel.ai.apply_field", "Apply :field", { field: key }),
|
|
1250
|
+
children: t("arqel.ai.apply", "Apply")
|
|
1184
1251
|
}
|
|
1185
1252
|
) : null
|
|
1186
1253
|
] })
|