fix(batch): round-2 polish — TS2305 unmask, a11y dialog, BOM stripping, 24h window, Provider column, race guard, banner toast, i18n cleanup

13 fixes from independent round-2 code review:

- B-1: Fix FileRecord wrong import in batch-utils.ts (TS2305 masked by limited typecheck-core scope); include batch-utils.ts + 5 lib/batches/* in tsconfig.typecheck-core.json so future regressions are caught
- A-2: role="dialog" + aria-modal + aria-labelledby on NewBatchWizard + BatchDetailModal panels (a11y consistency with UploadFileModal)
- A-3: "Janela de conclusão de 24h" line in CostEstimateStep (spec §5 explicit)
- A-7: "campos obrigatórios válidos" appended to JsonlValidationStep success summary (spec §5)
- A-9 + B-7: 18 hardcoded UI strings → i18n keys (Size, Refresh/Refreshing, Remove, Uploading, Reading file, Ready, Large file warning, JSONL generated, etc.)
- B-4: Strip UTF-8 BOM in validateJsonl + csvToJsonl (Windows-saved files no longer fail "invalid JSON" cryptically)
- B-5: Remove dead exports WizardStep + BatchProviderConfig from types.ts
- A-1: Add Provider column with derived heuristic (gpt-/o1-/o3-/text-embedding-/dall-e- → OpenAI; claude- → Anthropic; gemini → Gemini; else "—") in BatchListTab (BatchRecord has no provider field, so derivation is display-only)
- A-5: Enter key advances wizard step via data-wizard-next button trigger (skip when focus in INPUT/TEXTAREA/SELECT)
- A-6: Auto-dismiss "Batch {id} criado" banner on /batch page after wizard completes; consumes onCreated id (was discarded as _id)
- B-2: Race guard in InputStep.processFile (early-return if isReading) + drop zone pointer-events-none while reading
- C-tests: 6 new regression tests covering body=[] Array.isArray guard, BOM stripping, alias-match pricing path, (partial) suffix on expired_with_failures, -50% inline badge on Cost column, Provider column derivation per family

Side fix: narrow Record<string,unknown> child navigation in csvToJsonl.ts:135 to silence TS2322 once file is in typecheck scope.

22 new i18n keys (filesListSizeColumn, batchListProviderColumn/Unknown/BatchCreated/Dismiss/Refreshing/Refresh, uploadFileModalRemove/Uploading, wizardInputReading/Ready/LargeFileLabel/CsvJsonlReady/LargeFileWarning, wizardCostWindow24h, wizardValidationFieldsOk) added in en.json + pt-BR.json and propagated to 40 locales via fill-missing-from-en.mjs.
This commit is contained in:
diegosouzapw 2026-05-28 18:33:31 -03:00
parent 0f1bbc58a4
commit 4e58a86cf8
59 changed files with 934 additions and 78 deletions

View file

@ -182,7 +182,12 @@ export default function BatchDetailModal({ batch, files, onClose, onActionDone }
/> />
{/* Panel */} {/* Panel */}
<div className="relative w-full sm:max-w-2xl bg-[var(--color-surface)] border border-[var(--color-border)] rounded-t-2xl sm:rounded-xl shadow-2xl animate-in fade-in slide-in-from-bottom-4 duration-200 max-h-[90vh] flex flex-col"> <div
className="relative w-full sm:max-w-2xl bg-[var(--color-surface)] border border-[var(--color-border)] rounded-t-2xl sm:rounded-xl shadow-2xl animate-in fade-in slide-in-from-bottom-4 duration-200 max-h-[90vh] flex flex-col"
role="dialog"
aria-modal="true"
aria-labelledby="batch-detail-modal-title"
>
{/* Header */} {/* Header */}
<div className="flex items-center justify-between px-6 py-4 border-b border-[var(--color-border)] flex-shrink-0"> <div className="flex items-center justify-between px-6 py-4 border-b border-[var(--color-border)] flex-shrink-0">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
@ -190,7 +195,7 @@ export default function BatchDetailModal({ batch, files, onClose, onActionDone }
pending_actions pending_actions
</span> </span>
<div> <div>
<h2 className="text-base font-semibold text-[var(--color-text-main)]"> <h2 id="batch-detail-modal-title" className="text-base font-semibold text-[var(--color-text-main)]">
Batch Details Batch Details
</h2> </h2>
<div className="flex items-center gap-2 mt-0.5"> <div className="flex items-center gap-2 mt-0.5">

View file

@ -94,6 +94,17 @@ const STATUS_STYLES: Record<string, string> = {
expired_with_failures: "bg-orange-500/15 text-orange-400 border-orange-500/25", expired_with_failures: "bg-orange-500/15 text-orange-400 border-orange-500/25",
}; };
// A-1 — Derive Provider label from model id (no provider field on BatchRecord).
// Heuristic-only display; no filter side-effect. Returns "—" for unknown shapes.
function deriveProvider(model: string | null | undefined): string {
if (!model) return "—";
const lower = model.toLowerCase();
if (lower.startsWith("gpt-") || lower.startsWith("o1") || lower.startsWith("o3") || lower.startsWith("text-embedding-") || lower.startsWith("dall-e")) return "OpenAI";
if (lower.startsWith("claude-")) return "Anthropic";
if (lower.startsWith("gemini")) return "Gemini";
return "Other";
}
const STATUS_LABELS: Record<string, string> = { const STATUS_LABELS: Record<string, string> = {
completed_with_failures: "completed with failures", completed_with_failures: "completed with failures",
in_progress_with_failures: "in progress (with failures)", in_progress_with_failures: "in progress (with failures)",
@ -371,6 +382,9 @@ export default function BatchListTab({
<th className="text-left px-4 py-3 font-medium text-[var(--color-text-muted)] uppercase text-xs tracking-wider"> <th className="text-left px-4 py-3 font-medium text-[var(--color-text-muted)] uppercase text-xs tracking-wider">
ID ID
</th> </th>
<th className="text-left px-4 py-3 font-medium text-[var(--color-text-muted)] uppercase text-xs tracking-wider">
{t("batchListProviderColumn")}
</th>
<th className="text-left px-4 py-3 font-medium text-[var(--color-text-muted)] uppercase text-xs tracking-wider"> <th className="text-left px-4 py-3 font-medium text-[var(--color-text-muted)] uppercase text-xs tracking-wider">
Endpoint Endpoint
</th> </th>
@ -395,7 +409,7 @@ export default function BatchListTab({
<tbody> <tbody>
{loading && filtered.length === 0 ? ( {loading && filtered.length === 0 ? (
<tr> <tr>
<td colSpan={9} className="px-4 py-10 text-center text-[var(--color-text-muted)]"> <td colSpan={10} className="px-4 py-10 text-center text-[var(--color-text-muted)]">
<div className="flex items-center justify-center gap-2"> <div className="flex items-center justify-center gap-2">
<div className="animate-spin rounded-full h-5 w-5 border-b-2 border-[var(--color-accent)]" /> <div className="animate-spin rounded-full h-5 w-5 border-b-2 border-[var(--color-accent)]" />
Loading Loading
@ -404,7 +418,7 @@ export default function BatchListTab({
</tr> </tr>
) : filtered.length === 0 ? ( ) : filtered.length === 0 ? (
<tr> <tr>
<td colSpan={9} className="px-4 py-10 text-center text-[var(--color-text-muted)]"> <td colSpan={10} className="px-4 py-10 text-center text-[var(--color-text-muted)]">
No batches found No batches found
</td> </td>
</tr> </tr>
@ -447,6 +461,9 @@ export default function BatchListTab({
{batch.id} {batch.id}
</span> </span>
</td> </td>
<td className="px-4 py-3 text-[var(--color-text-main)] text-xs whitespace-nowrap">
{deriveProvider(batch.model)}
</td>
<td className="px-4 py-3 text-[var(--color-text-main)] text-xs"> <td className="px-4 py-3 text-[var(--color-text-main)] text-xs">
{batch.endpoint} {batch.endpoint}
</td> </td>

View file

@ -176,7 +176,7 @@ export default function FilesListTab({
Purpose Purpose
</th> </th>
<th className="text-left px-4 py-3 font-medium text-[var(--color-text-muted)] uppercase text-xs tracking-wider"> <th className="text-left px-4 py-3 font-medium text-[var(--color-text-muted)] uppercase text-xs tracking-wider">
Size {t("filesListSizeColumn")}
</th> </th>
<th className="text-left px-4 py-3 font-medium text-[var(--color-text-muted)] uppercase text-xs tracking-wider"> <th className="text-left px-4 py-3 font-medium text-[var(--color-text-muted)] uppercase text-xs tracking-wider">
{t("filesListUsedByColumn")} {t("filesListUsedByColumn")}

View file

@ -1,4 +1,5 @@
import { BatchRecord, FileRecord } from "@/lib/db/batches"; import { BatchRecord } from "@/lib/db/batches";
import { FileRecord } from "@/lib/db/files";
export function mapBatchApiToRecord(b: any): BatchRecord { export function mapBatchApiToRecord(b: any): BatchRecord {
return { return {

View file

@ -155,10 +155,24 @@ export default function NewBatchWizard({
const t = useTranslations("common"); const t = useTranslations("common");
const [state, dispatch] = useReducer(reducer, initialState); const [state, dispatch] = useReducer(reducer, initialState);
// Escape key handler // Escape closes wizard; Enter advances to next step when allowed (A-5).
// Skip Enter when focus is inside form controls so it doesn't interfere with typing.
useEffect(() => { useEffect(() => {
function onKeyDown(e: KeyboardEvent) { function onKeyDown(e: KeyboardEvent) {
if (e.key === "Escape" && !state.creating) onClose(); if (state.creating) return;
if (e.key === "Escape") {
onClose();
return;
}
if (e.key === "Enter") {
const tag = (e.target as HTMLElement | null)?.tagName;
if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return;
// canGoNext lives below; compute lazily via reading the disabled state of the Next button
const nextBtn = document.querySelector<HTMLButtonElement>(
'button[data-wizard-next="true"]'
);
if (nextBtn && !nextBtn.disabled) nextBtn.click();
}
} }
document.addEventListener("keydown", onKeyDown); document.addEventListener("keydown", onKeyDown);
return () => document.removeEventListener("keydown", onKeyDown); return () => document.removeEventListener("keydown", onKeyDown);
@ -292,11 +306,16 @@ export default function NewBatchWizard({
/> />
{/* Panel */} {/* Panel */}
<div className="relative w-full sm:max-w-3xl max-h-[90vh] flex flex-col rounded-t-2xl sm:rounded-xl bg-[var(--color-surface)] border border-[var(--color-border)] shadow-2xl overflow-hidden"> <div
className="relative w-full sm:max-w-3xl max-h-[90vh] flex flex-col rounded-t-2xl sm:rounded-xl bg-[var(--color-surface)] border border-[var(--color-border)] shadow-2xl overflow-hidden"
role="dialog"
aria-modal="true"
aria-labelledby="new-batch-wizard-title"
>
{/* Header */} {/* Header */}
<div className="flex items-center justify-between px-4 sm:px-6 py-4 border-b border-[var(--color-border)] shrink-0"> <div className="flex items-center justify-between px-4 sm:px-6 py-4 border-b border-[var(--color-border)] shrink-0">
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<h2 className="text-base font-semibold text-[var(--color-text)]">{t("wizardTitle")}</h2> <h2 id="new-batch-wizard-title" className="text-base font-semibold text-[var(--color-text)]">{t("wizardTitle")}</h2>
<StepIndicator current={state.step} t={t} /> <StepIndicator current={state.step} t={t} />
</div> </div>
<button <button
@ -339,6 +358,7 @@ export default function NewBatchWizard({
{state.step < 4 && ( {state.step < 4 && (
<button <button
type="button" type="button"
data-wizard-next="true"
onClick={handleNext} onClick={handleNext}
disabled={!canGoNext} disabled={!canGoNext}
className="rounded-lg px-4 py-2 text-sm font-medium bg-[var(--color-accent)] text-white disabled:opacity-40 hover:opacity-90 transition-opacity" className="rounded-lg px-4 py-2 text-sm font-medium bg-[var(--color-accent)] text-white disabled:opacity-40 hover:opacity-90 transition-opacity"

View file

@ -207,7 +207,7 @@ export default function UploadFileModal({ onClose, onUploaded }: Props) {
}} }}
className="shrink-0 text-xs text-[var(--color-text-muted)] hover:text-red-400 transition-colors px-2 py-1 rounded border border-[var(--color-border)] hover:border-red-400/40" className="shrink-0 text-xs text-[var(--color-text-muted)] hover:text-red-400 transition-colors px-2 py-1 rounded border border-[var(--color-border)] hover:border-red-400/40"
> >
Remove {t("uploadFileModalRemove")}
</button> </button>
</div> </div>
)} )}
@ -229,7 +229,7 @@ export default function UploadFileModal({ onClose, onUploaded }: Props) {
{uploading ? ( {uploading ? (
<> <>
<span className="animate-spin inline-block rounded-full h-4 w-4 border-b-2 border-white" /> <span className="animate-spin inline-block rounded-full h-4 w-4 border-b-2 border-white" />
Uploading {t("uploadFileModalUploading")}
</> </>
) : ( ) : (
<> <>

View file

@ -103,6 +103,15 @@ export default function CostEstimateStep({
{estimate.estimatedOutputTokens.toLocaleString()} output tok {estimate.estimatedOutputTokens.toLocaleString()} output tok
</span> </span>
</div> </div>
{/* Completion window — spec §5 "janela 24h" (A-3) */}
<div className="flex items-center justify-between px-4 py-3">
<span className="text-xs text-[var(--color-text-muted)]">Window</span>
<span className="inline-flex items-center gap-1 text-xs text-[var(--color-text-muted)]">
<span className="material-symbols-outlined text-[12px]">schedule</span>
{t("wizardCostWindow24h")}
</span>
</div>
</div> </div>
)} )}

View file

@ -47,6 +47,8 @@ export default function InputStep({ input, onChange, destination }: InputStepPro
} }
async function processFile(file: File) { async function processFile(file: File) {
// B-2 race guard — ignore concurrent drops while a read is in flight
if (isReading) return;
const expectedExt = isJsonl ? ".jsonl" : ".csv"; const expectedExt = isJsonl ? ".jsonl" : ".csv";
if (!file.name.toLowerCase().endsWith(expectedExt)) { if (!file.name.toLowerCase().endsWith(expectedExt)) {
// Soft warning — don't block, let validation catch it // Soft warning — don't block, let validation catch it
@ -130,30 +132,33 @@ export default function InputStep({ input, onChange, destination }: InputStepPro
</button> </button>
</div> </div>
{/* Drop zone */} {/* Drop zone — disabled while reading to prevent race (B-2) */}
<div <div
role="button" role="button"
tabIndex={0} tabIndex={isReading ? -1 : 0}
onDrop={handleDrop} onDrop={handleDrop}
onDragOver={handleDragOver} onDragOver={handleDragOver}
onDragLeave={handleDragLeave} onDragLeave={handleDragLeave}
onClick={() => fileInputRef.current?.click()} onClick={() => !isReading && fileInputRef.current?.click()}
onKeyDown={(e) => { onKeyDown={(e) => {
if (isReading) return;
if (e.key === "Enter" || e.key === " ") fileInputRef.current?.click(); if (e.key === "Enter" || e.key === " ") fileInputRef.current?.click();
}} }}
className={`rounded-xl border-2 border-dashed p-8 flex flex-col items-center gap-3 cursor-pointer transition-colors aria-disabled={isReading}
className={`rounded-xl border-2 border-dashed p-8 flex flex-col items-center gap-3 transition-colors
${isReading ? "cursor-wait opacity-60 pointer-events-none" : "cursor-pointer"}
${isDragging ? "border-[var(--color-accent)] bg-[var(--color-accent)]/5" : "border-[var(--color-border)] hover:border-[var(--color-accent)]/50"}`} ${isDragging ? "border-[var(--color-accent)] bg-[var(--color-accent)]/5" : "border-[var(--color-border)] hover:border-[var(--color-accent)]/50"}`}
> >
<span className="material-symbols-outlined text-3xl text-[var(--color-text-muted)]"> <span className="material-symbols-outlined text-3xl text-[var(--color-text-muted)]">
upload_file upload_file
</span> </span>
{isReading ? ( {isReading ? (
<span className="text-sm text-[var(--color-text-muted)]">Reading file</span> <span className="text-sm text-[var(--color-text-muted)]">{t("wizardInputReading")}</span>
) : hasFile ? ( ) : hasFile ? (
<div className="flex flex-col items-center gap-1"> <div className="flex flex-col items-center gap-1">
<span className="text-sm text-[var(--color-text)] font-medium">{input.fileName}</span> <span className="text-sm text-[var(--color-text)] font-medium">{input.fileName}</span>
<span className="text-xs text-[var(--color-text-muted)]"> <span className="text-xs text-[var(--color-text-muted)]">
{isLargeFile ? "Large file — validation by sampling" : "Ready"} {isLargeFile ? t("wizardInputLargeFileLabel") : t("wizardInputReady")}
</span> </span>
</div> </div>
) : ( ) : (
@ -171,8 +176,7 @@ export default function InputStep({ input, onChange, destination }: InputStepPro
{/* Large file warning */} {/* Large file warning */}
{isLargeFile && ( {isLargeFile && (
<div className="rounded-lg border border-yellow-500/25 bg-yellow-500/10 px-3 py-2 text-xs text-yellow-400"> <div className="rounded-lg border border-yellow-500/25 bg-yellow-500/10 px-3 py-2 text-xs text-yellow-400">
Large file detected validation will run by sampling (first 5 MB + last 100 KB). Full {t("wizardInputLargeFileWarning")}
validation happens server-side.
</div> </div>
)} )}
@ -187,7 +191,7 @@ export default function InputStep({ input, onChange, destination }: InputStepPro
onJsonlReady={handleJsonlReady} onJsonlReady={handleJsonlReady}
/> />
{csvMappingReady && ( {csvMappingReady && (
<p className="mt-3 text-xs text-emerald-400">JSONL generated ready to validate.</p> <p className="mt-3 text-xs text-emerald-400">{t("wizardInputCsvJsonlReady")}</p>
)} )}
</div> </div>
)} )}

View file

@ -64,14 +64,15 @@ export default function JsonlValidationStep({
return ( return (
<div className="flex flex-col gap-5"> <div className="flex flex-col gap-5">
{/* OK / Error banner */} {/* OK / Error banner — spec §5 "campos OK" appended on success (A-7) */}
{result.ok ? ( {result.ok ? (
<div className="flex items-center gap-3 rounded-xl border border-emerald-500/25 bg-emerald-500/10 px-4 py-3"> <div className="flex items-center gap-3 rounded-xl border border-emerald-500/25 bg-emerald-500/10 px-4 py-3">
<span className="material-symbols-outlined text-emerald-400">check_circle</span> <span className="material-symbols-outlined text-emerald-400">check_circle</span>
<div className="flex flex-col gap-0.5"> <div className="flex flex-col gap-0.5">
<span className="text-sm font-medium text-emerald-400">{t("wizardValidationOk")}</span> <span className="text-sm font-medium text-emerald-400">{t("wizardValidationOk")}</span>
<span className="text-xs text-[var(--color-text-muted)]"> <span className="text-xs text-[var(--color-text-muted)]">
{result.totalLines} lines · {result.uniqueCustomIds} unique custom_ids {result.totalLines} lines · {result.uniqueCustomIds} unique custom_ids ·{" "}
{t("wizardValidationFieldsOk")}
</span> </span>
</div> </div>
</div> </div>

View file

@ -33,10 +33,18 @@ export default function BatchPage() {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [loadingMore, setLoadingMore] = useState(false); const [loadingMore, setLoadingMore] = useState(false);
const [showWizard, setShowWizard] = useState(false); const [showWizard, setShowWizard] = useState(false);
const [createdBanner, setCreatedBanner] = useState<string | null>(null);
const [providers, setProviders] = useState<Array<{ id: string; name: string; models: string[] }>>( const [providers, setProviders] = useState<Array<{ id: string; name: string; models: string[] }>>(
[], [],
); );
// Auto-dismiss "batch created" banner after 5s (A-6)
useEffect(() => {
if (!createdBanner) return;
const id = setTimeout(() => setCreatedBanner(null), 5000);
return () => clearTimeout(id);
}, [createdBanner]);
const [batchesHasMore, setBatchesHasMore] = useState(false); const [batchesHasMore, setBatchesHasMore] = useState(false);
const [batchesLastId, setBatchesLastId] = useState<string | null>(null); const [batchesLastId, setBatchesLastId] = useState<string | null>(null);
const bottomRefBatches = useRef<HTMLDivElement>(null); const bottomRefBatches = useRef<HTMLDivElement>(null);
@ -241,6 +249,26 @@ export default function BatchPage() {
{/* Concept card (F3) */} {/* Concept card (F3) */}
<BatchConceptCard /> <BatchConceptCard />
{/* "Batch created" success banner (A-6) — auto-dismiss 5s */}
{createdBanner && (
<div
role="status"
className="flex items-center justify-between gap-3 rounded-lg border border-emerald-500/25 bg-emerald-500/10 px-3 py-2"
>
<div className="flex items-center gap-2 text-sm text-emerald-400">
<span className="material-symbols-outlined text-[16px]">check_circle</span>
{t("batchListBatchCreated", { id: createdBanner })}
</div>
<button
type="button"
onClick={() => setCreatedBanner(null)}
className="text-xs text-emerald-400/80 hover:text-emerald-300 transition-colors px-2 py-0.5 rounded"
>
{t("batchListBatchCreatedDismiss")}
</button>
</div>
)}
{/* Toolbar: auto-refresh indicator + Refresh + New batch */} {/* Toolbar: auto-refresh indicator + Refresh + New batch */}
<div className="flex items-center justify-between gap-4 flex-wrap"> <div className="flex items-center justify-between gap-4 flex-wrap">
<span className="text-xs text-[var(--color-text-muted)] flex items-center gap-1"> <span className="text-xs text-[var(--color-text-muted)] flex items-center gap-1">
@ -258,7 +286,7 @@ export default function BatchPage() {
disabled:opacity-50 disabled:cursor-not-allowed" disabled:opacity-50 disabled:cursor-not-allowed"
> >
<span className="material-symbols-outlined text-[16px]">refresh</span> <span className="material-symbols-outlined text-[16px]">refresh</span>
{loading ? "Refreshing…" : "Refresh"} {loading ? t("batchListRefreshing") : t("batchListRefresh")}
</button> </button>
<button <button
onClick={() => setShowWizard(true)} onClick={() => setShowWizard(true)}
@ -287,12 +315,13 @@ export default function BatchPage() {
<div ref={bottomRefBatches} className="h-10" /> <div ref={bottomRefBatches} className="h-10" />
</div> </div>
{/* New batch wizard (F4 stub — replaced by F4 full implementation on merge) */} {/* New batch wizard */}
{showWizard && ( {showWizard && (
<NewBatchWizard <NewBatchWizard
onClose={() => setShowWizard(false)} onClose={() => setShowWizard(false)}
onCreated={(_id) => { onCreated={(id) => {
setShowWizard(false); setShowWizard(false);
setCreatedBanner(id);
void fetchData(false); void fetchData(false);
}} }}
availableProviders={providers} availableProviders={providers}

View file

@ -786,7 +786,23 @@
"filesListUsedByRoleInput": "input", "filesListUsedByRoleInput": "input",
"filesListUsedByRoleOutput": "output", "filesListUsedByRoleOutput": "output",
"filesListUsedByRoleError": "error", "filesListUsedByRoleError": "error",
"batchListProgressPartial": "(partial)" "batchListProgressPartial": "(partial)",
"filesListSizeColumn": "Size",
"batchListProviderColumn": "Provider",
"batchListProviderUnknown": "—",
"batchListBatchCreated": "Batch {id} created — refreshing list…",
"batchListBatchCreatedDismiss": "Dismiss",
"batchListRefreshing": "Refreshing…",
"batchListRefresh": "Refresh",
"uploadFileModalRemove": "Remove",
"uploadFileModalUploading": "Uploading…",
"wizardInputReading": "Reading file…",
"wizardInputReady": "Ready",
"wizardInputLargeFileLabel": "Large file — sampling validation",
"wizardInputCsvJsonlReady": "JSONL generated — ready to validate.",
"wizardInputLargeFileWarning": "Large file detected — validation runs by sampling (first 5 MB + last 100 KB). Full validation happens server-side.",
"wizardCostWindow24h": "24h completion window",
"wizardValidationFieldsOk": "required fields valid"
}, },
"sidebar": { "sidebar": {
"home": "الصفحة الرئيسية", "home": "الصفحة الرئيسية",

View file

@ -786,7 +786,23 @@
"filesListUsedByRoleInput": "input", "filesListUsedByRoleInput": "input",
"filesListUsedByRoleOutput": "output", "filesListUsedByRoleOutput": "output",
"filesListUsedByRoleError": "error", "filesListUsedByRoleError": "error",
"batchListProgressPartial": "(partial)" "batchListProgressPartial": "(partial)",
"filesListSizeColumn": "Size",
"batchListProviderColumn": "Provider",
"batchListProviderUnknown": "—",
"batchListBatchCreated": "Batch {id} created — refreshing list…",
"batchListBatchCreatedDismiss": "Dismiss",
"batchListRefreshing": "Refreshing…",
"batchListRefresh": "Refresh",
"uploadFileModalRemove": "Remove",
"uploadFileModalUploading": "Uploading…",
"wizardInputReading": "Reading file…",
"wizardInputReady": "Ready",
"wizardInputLargeFileLabel": "Large file — sampling validation",
"wizardInputCsvJsonlReady": "JSONL generated — ready to validate.",
"wizardInputLargeFileWarning": "Large file detected — validation runs by sampling (first 5 MB + last 100 KB). Full validation happens server-side.",
"wizardCostWindow24h": "24h completion window",
"wizardValidationFieldsOk": "required fields valid"
}, },
"sidebar": { "sidebar": {
"home": "Home", "home": "Home",

View file

@ -786,7 +786,23 @@
"filesListUsedByRoleInput": "input", "filesListUsedByRoleInput": "input",
"filesListUsedByRoleOutput": "output", "filesListUsedByRoleOutput": "output",
"filesListUsedByRoleError": "error", "filesListUsedByRoleError": "error",
"batchListProgressPartial": "(partial)" "batchListProgressPartial": "(partial)",
"filesListSizeColumn": "Size",
"batchListProviderColumn": "Provider",
"batchListProviderUnknown": "—",
"batchListBatchCreated": "Batch {id} created — refreshing list…",
"batchListBatchCreatedDismiss": "Dismiss",
"batchListRefreshing": "Refreshing…",
"batchListRefresh": "Refresh",
"uploadFileModalRemove": "Remove",
"uploadFileModalUploading": "Uploading…",
"wizardInputReading": "Reading file…",
"wizardInputReady": "Ready",
"wizardInputLargeFileLabel": "Large file — sampling validation",
"wizardInputCsvJsonlReady": "JSONL generated — ready to validate.",
"wizardInputLargeFileWarning": "Large file detected — validation runs by sampling (first 5 MB + last 100 KB). Full validation happens server-side.",
"wizardCostWindow24h": "24h completion window",
"wizardValidationFieldsOk": "required fields valid"
}, },
"sidebar": { "sidebar": {
"home": "Начало", "home": "Начало",

View file

@ -786,7 +786,23 @@
"filesListUsedByRoleInput": "input", "filesListUsedByRoleInput": "input",
"filesListUsedByRoleOutput": "output", "filesListUsedByRoleOutput": "output",
"filesListUsedByRoleError": "error", "filesListUsedByRoleError": "error",
"batchListProgressPartial": "(partial)" "batchListProgressPartial": "(partial)",
"filesListSizeColumn": "Size",
"batchListProviderColumn": "Provider",
"batchListProviderUnknown": "—",
"batchListBatchCreated": "Batch {id} created — refreshing list…",
"batchListBatchCreatedDismiss": "Dismiss",
"batchListRefreshing": "Refreshing…",
"batchListRefresh": "Refresh",
"uploadFileModalRemove": "Remove",
"uploadFileModalUploading": "Uploading…",
"wizardInputReading": "Reading file…",
"wizardInputReady": "Ready",
"wizardInputLargeFileLabel": "Large file — sampling validation",
"wizardInputCsvJsonlReady": "JSONL generated — ready to validate.",
"wizardInputLargeFileWarning": "Large file detected — validation runs by sampling (first 5 MB + last 100 KB). Full validation happens server-side.",
"wizardCostWindow24h": "24h completion window",
"wizardValidationFieldsOk": "required fields valid"
}, },
"sidebar": { "sidebar": {
"home": "Home", "home": "Home",

View file

@ -786,7 +786,23 @@
"filesListUsedByRoleInput": "input", "filesListUsedByRoleInput": "input",
"filesListUsedByRoleOutput": "output", "filesListUsedByRoleOutput": "output",
"filesListUsedByRoleError": "error", "filesListUsedByRoleError": "error",
"batchListProgressPartial": "(partial)" "batchListProgressPartial": "(partial)",
"filesListSizeColumn": "Size",
"batchListProviderColumn": "Provider",
"batchListProviderUnknown": "—",
"batchListBatchCreated": "Batch {id} created — refreshing list…",
"batchListBatchCreatedDismiss": "Dismiss",
"batchListRefreshing": "Refreshing…",
"batchListRefresh": "Refresh",
"uploadFileModalRemove": "Remove",
"uploadFileModalUploading": "Uploading…",
"wizardInputReading": "Reading file…",
"wizardInputReady": "Ready",
"wizardInputLargeFileLabel": "Large file — sampling validation",
"wizardInputCsvJsonlReady": "JSONL generated — ready to validate.",
"wizardInputLargeFileWarning": "Large file detected — validation runs by sampling (first 5 MB + last 100 KB). Full validation happens server-side.",
"wizardCostWindow24h": "24h completion window",
"wizardValidationFieldsOk": "required fields valid"
}, },
"sidebar": { "sidebar": {
"home": "Domov", "home": "Domov",

View file

@ -786,7 +786,23 @@
"filesListUsedByRoleInput": "input", "filesListUsedByRoleInput": "input",
"filesListUsedByRoleOutput": "output", "filesListUsedByRoleOutput": "output",
"filesListUsedByRoleError": "error", "filesListUsedByRoleError": "error",
"batchListProgressPartial": "(partial)" "batchListProgressPartial": "(partial)",
"filesListSizeColumn": "Size",
"batchListProviderColumn": "Provider",
"batchListProviderUnknown": "—",
"batchListBatchCreated": "Batch {id} created — refreshing list…",
"batchListBatchCreatedDismiss": "Dismiss",
"batchListRefreshing": "Refreshing…",
"batchListRefresh": "Refresh",
"uploadFileModalRemove": "Remove",
"uploadFileModalUploading": "Uploading…",
"wizardInputReading": "Reading file…",
"wizardInputReady": "Ready",
"wizardInputLargeFileLabel": "Large file — sampling validation",
"wizardInputCsvJsonlReady": "JSONL generated — ready to validate.",
"wizardInputLargeFileWarning": "Large file detected — validation runs by sampling (first 5 MB + last 100 KB). Full validation happens server-side.",
"wizardCostWindow24h": "24h completion window",
"wizardValidationFieldsOk": "required fields valid"
}, },
"sidebar": { "sidebar": {
"home": "Hjem", "home": "Hjem",

View file

@ -786,7 +786,23 @@
"filesListUsedByRoleInput": "input", "filesListUsedByRoleInput": "input",
"filesListUsedByRoleOutput": "output", "filesListUsedByRoleOutput": "output",
"filesListUsedByRoleError": "error", "filesListUsedByRoleError": "error",
"batchListProgressPartial": "(partial)" "batchListProgressPartial": "(partial)",
"filesListSizeColumn": "Size",
"batchListProviderColumn": "Provider",
"batchListProviderUnknown": "—",
"batchListBatchCreated": "Batch {id} created — refreshing list…",
"batchListBatchCreatedDismiss": "Dismiss",
"batchListRefreshing": "Refreshing…",
"batchListRefresh": "Refresh",
"uploadFileModalRemove": "Remove",
"uploadFileModalUploading": "Uploading…",
"wizardInputReading": "Reading file…",
"wizardInputReady": "Ready",
"wizardInputLargeFileLabel": "Large file — sampling validation",
"wizardInputCsvJsonlReady": "JSONL generated — ready to validate.",
"wizardInputLargeFileWarning": "Large file detected — validation runs by sampling (first 5 MB + last 100 KB). Full validation happens server-side.",
"wizardCostWindow24h": "24h completion window",
"wizardValidationFieldsOk": "required fields valid"
}, },
"sidebar": { "sidebar": {
"home": "Zuhause", "home": "Zuhause",

View file

@ -776,7 +776,23 @@
"filesListUsedByRoleInput": "input", "filesListUsedByRoleInput": "input",
"filesListUsedByRoleOutput": "output", "filesListUsedByRoleOutput": "output",
"filesListUsedByRoleError": "error", "filesListUsedByRoleError": "error",
"filesListSizeColumn": "Size",
"batchListProgressPartial": "(partial)", "batchListProgressPartial": "(partial)",
"batchListProviderColumn": "Provider",
"batchListProviderUnknown": "—",
"batchListBatchCreated": "Batch {id} created — refreshing list…",
"batchListBatchCreatedDismiss": "Dismiss",
"batchListRefreshing": "Refreshing…",
"batchListRefresh": "Refresh",
"uploadFileModalRemove": "Remove",
"uploadFileModalUploading": "Uploading…",
"wizardInputReading": "Reading file…",
"wizardInputReady": "Ready",
"wizardInputLargeFileLabel": "Large file — sampling validation",
"wizardInputCsvJsonlReady": "JSONL generated — ready to validate.",
"wizardInputLargeFileWarning": "Large file detected — validation runs by sampling (first 5 MB + last 100 KB). Full validation happens server-side.",
"wizardCostWindow24h": "24h completion window",
"wizardValidationFieldsOk": "required fields valid",
"filesListDelete": "Delete", "filesListDelete": "Delete",
"filesListDownload": "Download", "filesListDownload": "Download",
"batchDetailActionCancel": "Cancel batch", "batchDetailActionCancel": "Cancel batch",

View file

@ -786,7 +786,23 @@
"filesListUsedByRoleInput": "input", "filesListUsedByRoleInput": "input",
"filesListUsedByRoleOutput": "output", "filesListUsedByRoleOutput": "output",
"filesListUsedByRoleError": "error", "filesListUsedByRoleError": "error",
"batchListProgressPartial": "(partial)" "batchListProgressPartial": "(partial)",
"filesListSizeColumn": "Size",
"batchListProviderColumn": "Provider",
"batchListProviderUnknown": "—",
"batchListBatchCreated": "Batch {id} created — refreshing list…",
"batchListBatchCreatedDismiss": "Dismiss",
"batchListRefreshing": "Refreshing…",
"batchListRefresh": "Refresh",
"uploadFileModalRemove": "Remove",
"uploadFileModalUploading": "Uploading…",
"wizardInputReading": "Reading file…",
"wizardInputReady": "Ready",
"wizardInputLargeFileLabel": "Large file — sampling validation",
"wizardInputCsvJsonlReady": "JSONL generated — ready to validate.",
"wizardInputLargeFileWarning": "Large file detected — validation runs by sampling (first 5 MB + last 100 KB). Full validation happens server-side.",
"wizardCostWindow24h": "24h completion window",
"wizardValidationFieldsOk": "required fields valid"
}, },
"sidebar": { "sidebar": {
"home": "Inicio", "home": "Inicio",

View file

@ -786,7 +786,23 @@
"filesListUsedByRoleInput": "input", "filesListUsedByRoleInput": "input",
"filesListUsedByRoleOutput": "output", "filesListUsedByRoleOutput": "output",
"filesListUsedByRoleError": "error", "filesListUsedByRoleError": "error",
"batchListProgressPartial": "(partial)" "batchListProgressPartial": "(partial)",
"filesListSizeColumn": "Size",
"batchListProviderColumn": "Provider",
"batchListProviderUnknown": "—",
"batchListBatchCreated": "Batch {id} created — refreshing list…",
"batchListBatchCreatedDismiss": "Dismiss",
"batchListRefreshing": "Refreshing…",
"batchListRefresh": "Refresh",
"uploadFileModalRemove": "Remove",
"uploadFileModalUploading": "Uploading…",
"wizardInputReading": "Reading file…",
"wizardInputReady": "Ready",
"wizardInputLargeFileLabel": "Large file — sampling validation",
"wizardInputCsvJsonlReady": "JSONL generated — ready to validate.",
"wizardInputLargeFileWarning": "Large file detected — validation runs by sampling (first 5 MB + last 100 KB). Full validation happens server-side.",
"wizardCostWindow24h": "24h completion window",
"wizardValidationFieldsOk": "required fields valid"
}, },
"sidebar": { "sidebar": {
"home": "Home", "home": "Home",

View file

@ -786,7 +786,23 @@
"filesListUsedByRoleInput": "input", "filesListUsedByRoleInput": "input",
"filesListUsedByRoleOutput": "output", "filesListUsedByRoleOutput": "output",
"filesListUsedByRoleError": "error", "filesListUsedByRoleError": "error",
"batchListProgressPartial": "(partial)" "batchListProgressPartial": "(partial)",
"filesListSizeColumn": "Size",
"batchListProviderColumn": "Provider",
"batchListProviderUnknown": "—",
"batchListBatchCreated": "Batch {id} created — refreshing list…",
"batchListBatchCreatedDismiss": "Dismiss",
"batchListRefreshing": "Refreshing…",
"batchListRefresh": "Refresh",
"uploadFileModalRemove": "Remove",
"uploadFileModalUploading": "Uploading…",
"wizardInputReading": "Reading file…",
"wizardInputReady": "Ready",
"wizardInputLargeFileLabel": "Large file — sampling validation",
"wizardInputCsvJsonlReady": "JSONL generated — ready to validate.",
"wizardInputLargeFileWarning": "Large file detected — validation runs by sampling (first 5 MB + last 100 KB). Full validation happens server-side.",
"wizardCostWindow24h": "24h completion window",
"wizardValidationFieldsOk": "required fields valid"
}, },
"sidebar": { "sidebar": {
"home": "Kotiin", "home": "Kotiin",

View file

@ -786,7 +786,23 @@
"filesListUsedByRoleInput": "input", "filesListUsedByRoleInput": "input",
"filesListUsedByRoleOutput": "output", "filesListUsedByRoleOutput": "output",
"filesListUsedByRoleError": "error", "filesListUsedByRoleError": "error",
"batchListProgressPartial": "(partial)" "batchListProgressPartial": "(partial)",
"filesListSizeColumn": "Size",
"batchListProviderColumn": "Provider",
"batchListProviderUnknown": "—",
"batchListBatchCreated": "Batch {id} created — refreshing list…",
"batchListBatchCreatedDismiss": "Dismiss",
"batchListRefreshing": "Refreshing…",
"batchListRefresh": "Refresh",
"uploadFileModalRemove": "Remove",
"uploadFileModalUploading": "Uploading…",
"wizardInputReading": "Reading file…",
"wizardInputReady": "Ready",
"wizardInputLargeFileLabel": "Large file — sampling validation",
"wizardInputCsvJsonlReady": "JSONL generated — ready to validate.",
"wizardInputLargeFileWarning": "Large file detected — validation runs by sampling (first 5 MB + last 100 KB). Full validation happens server-side.",
"wizardCostWindow24h": "24h completion window",
"wizardValidationFieldsOk": "required fields valid"
}, },
"sidebar": { "sidebar": {
"home": "Accueil", "home": "Accueil",

View file

@ -786,7 +786,23 @@
"filesListUsedByRoleInput": "input", "filesListUsedByRoleInput": "input",
"filesListUsedByRoleOutput": "output", "filesListUsedByRoleOutput": "output",
"filesListUsedByRoleError": "error", "filesListUsedByRoleError": "error",
"batchListProgressPartial": "(partial)" "batchListProgressPartial": "(partial)",
"filesListSizeColumn": "Size",
"batchListProviderColumn": "Provider",
"batchListProviderUnknown": "—",
"batchListBatchCreated": "Batch {id} created — refreshing list…",
"batchListBatchCreatedDismiss": "Dismiss",
"batchListRefreshing": "Refreshing…",
"batchListRefresh": "Refresh",
"uploadFileModalRemove": "Remove",
"uploadFileModalUploading": "Uploading…",
"wizardInputReading": "Reading file…",
"wizardInputReady": "Ready",
"wizardInputLargeFileLabel": "Large file — sampling validation",
"wizardInputCsvJsonlReady": "JSONL generated — ready to validate.",
"wizardInputLargeFileWarning": "Large file detected — validation runs by sampling (first 5 MB + last 100 KB). Full validation happens server-side.",
"wizardCostWindow24h": "24h completion window",
"wizardValidationFieldsOk": "required fields valid"
}, },
"sidebar": { "sidebar": {
"home": "Home", "home": "Home",

View file

@ -786,7 +786,23 @@
"filesListUsedByRoleInput": "input", "filesListUsedByRoleInput": "input",
"filesListUsedByRoleOutput": "output", "filesListUsedByRoleOutput": "output",
"filesListUsedByRoleError": "error", "filesListUsedByRoleError": "error",
"batchListProgressPartial": "(partial)" "batchListProgressPartial": "(partial)",
"filesListSizeColumn": "Size",
"batchListProviderColumn": "Provider",
"batchListProviderUnknown": "—",
"batchListBatchCreated": "Batch {id} created — refreshing list…",
"batchListBatchCreatedDismiss": "Dismiss",
"batchListRefreshing": "Refreshing…",
"batchListRefresh": "Refresh",
"uploadFileModalRemove": "Remove",
"uploadFileModalUploading": "Uploading…",
"wizardInputReading": "Reading file…",
"wizardInputReady": "Ready",
"wizardInputLargeFileLabel": "Large file — sampling validation",
"wizardInputCsvJsonlReady": "JSONL generated — ready to validate.",
"wizardInputLargeFileWarning": "Large file detected — validation runs by sampling (first 5 MB + last 100 KB). Full validation happens server-side.",
"wizardCostWindow24h": "24h completion window",
"wizardValidationFieldsOk": "required fields valid"
}, },
"sidebar": { "sidebar": {
"home": "בית", "home": "בית",

View file

@ -786,7 +786,23 @@
"filesListUsedByRoleInput": "input", "filesListUsedByRoleInput": "input",
"filesListUsedByRoleOutput": "output", "filesListUsedByRoleOutput": "output",
"filesListUsedByRoleError": "error", "filesListUsedByRoleError": "error",
"batchListProgressPartial": "(partial)" "batchListProgressPartial": "(partial)",
"filesListSizeColumn": "Size",
"batchListProviderColumn": "Provider",
"batchListProviderUnknown": "—",
"batchListBatchCreated": "Batch {id} created — refreshing list…",
"batchListBatchCreatedDismiss": "Dismiss",
"batchListRefreshing": "Refreshing…",
"batchListRefresh": "Refresh",
"uploadFileModalRemove": "Remove",
"uploadFileModalUploading": "Uploading…",
"wizardInputReading": "Reading file…",
"wizardInputReady": "Ready",
"wizardInputLargeFileLabel": "Large file — sampling validation",
"wizardInputCsvJsonlReady": "JSONL generated — ready to validate.",
"wizardInputLargeFileWarning": "Large file detected — validation runs by sampling (first 5 MB + last 100 KB). Full validation happens server-side.",
"wizardCostWindow24h": "24h completion window",
"wizardValidationFieldsOk": "required fields valid"
}, },
"sidebar": { "sidebar": {
"home": "घर", "home": "घर",

View file

@ -786,7 +786,23 @@
"filesListUsedByRoleInput": "input", "filesListUsedByRoleInput": "input",
"filesListUsedByRoleOutput": "output", "filesListUsedByRoleOutput": "output",
"filesListUsedByRoleError": "error", "filesListUsedByRoleError": "error",
"batchListProgressPartial": "(partial)" "batchListProgressPartial": "(partial)",
"filesListSizeColumn": "Size",
"batchListProviderColumn": "Provider",
"batchListProviderUnknown": "—",
"batchListBatchCreated": "Batch {id} created — refreshing list…",
"batchListBatchCreatedDismiss": "Dismiss",
"batchListRefreshing": "Refreshing…",
"batchListRefresh": "Refresh",
"uploadFileModalRemove": "Remove",
"uploadFileModalUploading": "Uploading…",
"wizardInputReading": "Reading file…",
"wizardInputReady": "Ready",
"wizardInputLargeFileLabel": "Large file — sampling validation",
"wizardInputCsvJsonlReady": "JSONL generated — ready to validate.",
"wizardInputLargeFileWarning": "Large file detected — validation runs by sampling (first 5 MB + last 100 KB). Full validation happens server-side.",
"wizardCostWindow24h": "24h completion window",
"wizardValidationFieldsOk": "required fields valid"
}, },
"sidebar": { "sidebar": {
"home": "Otthon", "home": "Otthon",

View file

@ -786,7 +786,23 @@
"filesListUsedByRoleInput": "input", "filesListUsedByRoleInput": "input",
"filesListUsedByRoleOutput": "output", "filesListUsedByRoleOutput": "output",
"filesListUsedByRoleError": "error", "filesListUsedByRoleError": "error",
"batchListProgressPartial": "(partial)" "batchListProgressPartial": "(partial)",
"filesListSizeColumn": "Size",
"batchListProviderColumn": "Provider",
"batchListProviderUnknown": "—",
"batchListBatchCreated": "Batch {id} created — refreshing list…",
"batchListBatchCreatedDismiss": "Dismiss",
"batchListRefreshing": "Refreshing…",
"batchListRefresh": "Refresh",
"uploadFileModalRemove": "Remove",
"uploadFileModalUploading": "Uploading…",
"wizardInputReading": "Reading file…",
"wizardInputReady": "Ready",
"wizardInputLargeFileLabel": "Large file — sampling validation",
"wizardInputCsvJsonlReady": "JSONL generated — ready to validate.",
"wizardInputLargeFileWarning": "Large file detected — validation runs by sampling (first 5 MB + last 100 KB). Full validation happens server-side.",
"wizardCostWindow24h": "24h completion window",
"wizardValidationFieldsOk": "required fields valid"
}, },
"sidebar": { "sidebar": {
"home": "Rumah", "home": "Rumah",

View file

@ -786,7 +786,23 @@
"filesListUsedByRoleInput": "input", "filesListUsedByRoleInput": "input",
"filesListUsedByRoleOutput": "output", "filesListUsedByRoleOutput": "output",
"filesListUsedByRoleError": "error", "filesListUsedByRoleError": "error",
"batchListProgressPartial": "(partial)" "batchListProgressPartial": "(partial)",
"filesListSizeColumn": "Size",
"batchListProviderColumn": "Provider",
"batchListProviderUnknown": "—",
"batchListBatchCreated": "Batch {id} created — refreshing list…",
"batchListBatchCreatedDismiss": "Dismiss",
"batchListRefreshing": "Refreshing…",
"batchListRefresh": "Refresh",
"uploadFileModalRemove": "Remove",
"uploadFileModalUploading": "Uploading…",
"wizardInputReading": "Reading file…",
"wizardInputReady": "Ready",
"wizardInputLargeFileLabel": "Large file — sampling validation",
"wizardInputCsvJsonlReady": "JSONL generated — ready to validate.",
"wizardInputLargeFileWarning": "Large file detected — validation runs by sampling (first 5 MB + last 100 KB). Full validation happens server-side.",
"wizardCostWindow24h": "24h completion window",
"wizardValidationFieldsOk": "required fields valid"
}, },
"sidebar": { "sidebar": {
"home": "Home", "home": "Home",

View file

@ -786,7 +786,23 @@
"filesListUsedByRoleInput": "input", "filesListUsedByRoleInput": "input",
"filesListUsedByRoleOutput": "output", "filesListUsedByRoleOutput": "output",
"filesListUsedByRoleError": "error", "filesListUsedByRoleError": "error",
"batchListProgressPartial": "(partial)" "batchListProgressPartial": "(partial)",
"filesListSizeColumn": "Size",
"batchListProviderColumn": "Provider",
"batchListProviderUnknown": "—",
"batchListBatchCreated": "Batch {id} created — refreshing list…",
"batchListBatchCreatedDismiss": "Dismiss",
"batchListRefreshing": "Refreshing…",
"batchListRefresh": "Refresh",
"uploadFileModalRemove": "Remove",
"uploadFileModalUploading": "Uploading…",
"wizardInputReading": "Reading file…",
"wizardInputReady": "Ready",
"wizardInputLargeFileLabel": "Large file — sampling validation",
"wizardInputCsvJsonlReady": "JSONL generated — ready to validate.",
"wizardInputLargeFileWarning": "Large file detected — validation runs by sampling (first 5 MB + last 100 KB). Full validation happens server-side.",
"wizardCostWindow24h": "24h completion window",
"wizardValidationFieldsOk": "required fields valid"
}, },
"sidebar": { "sidebar": {
"home": "Casa", "home": "Casa",

View file

@ -786,7 +786,23 @@
"filesListUsedByRoleInput": "input", "filesListUsedByRoleInput": "input",
"filesListUsedByRoleOutput": "output", "filesListUsedByRoleOutput": "output",
"filesListUsedByRoleError": "error", "filesListUsedByRoleError": "error",
"batchListProgressPartial": "(partial)" "batchListProgressPartial": "(partial)",
"filesListSizeColumn": "Size",
"batchListProviderColumn": "Provider",
"batchListProviderUnknown": "—",
"batchListBatchCreated": "Batch {id} created — refreshing list…",
"batchListBatchCreatedDismiss": "Dismiss",
"batchListRefreshing": "Refreshing…",
"batchListRefresh": "Refresh",
"uploadFileModalRemove": "Remove",
"uploadFileModalUploading": "Uploading…",
"wizardInputReading": "Reading file…",
"wizardInputReady": "Ready",
"wizardInputLargeFileLabel": "Large file — sampling validation",
"wizardInputCsvJsonlReady": "JSONL generated — ready to validate.",
"wizardInputLargeFileWarning": "Large file detected — validation runs by sampling (first 5 MB + last 100 KB). Full validation happens server-side.",
"wizardCostWindow24h": "24h completion window",
"wizardValidationFieldsOk": "required fields valid"
}, },
"sidebar": { "sidebar": {
"home": "ホーム", "home": "ホーム",

View file

@ -786,7 +786,23 @@
"filesListUsedByRoleInput": "input", "filesListUsedByRoleInput": "input",
"filesListUsedByRoleOutput": "output", "filesListUsedByRoleOutput": "output",
"filesListUsedByRoleError": "error", "filesListUsedByRoleError": "error",
"batchListProgressPartial": "(partial)" "batchListProgressPartial": "(partial)",
"filesListSizeColumn": "Size",
"batchListProviderColumn": "Provider",
"batchListProviderUnknown": "—",
"batchListBatchCreated": "Batch {id} created — refreshing list…",
"batchListBatchCreatedDismiss": "Dismiss",
"batchListRefreshing": "Refreshing…",
"batchListRefresh": "Refresh",
"uploadFileModalRemove": "Remove",
"uploadFileModalUploading": "Uploading…",
"wizardInputReading": "Reading file…",
"wizardInputReady": "Ready",
"wizardInputLargeFileLabel": "Large file — sampling validation",
"wizardInputCsvJsonlReady": "JSONL generated — ready to validate.",
"wizardInputLargeFileWarning": "Large file detected — validation runs by sampling (first 5 MB + last 100 KB). Full validation happens server-side.",
"wizardCostWindow24h": "24h completion window",
"wizardValidationFieldsOk": "required fields valid"
}, },
"sidebar": { "sidebar": {
"home": "홈", "home": "홈",

View file

@ -786,7 +786,23 @@
"filesListUsedByRoleInput": "input", "filesListUsedByRoleInput": "input",
"filesListUsedByRoleOutput": "output", "filesListUsedByRoleOutput": "output",
"filesListUsedByRoleError": "error", "filesListUsedByRoleError": "error",
"batchListProgressPartial": "(partial)" "batchListProgressPartial": "(partial)",
"filesListSizeColumn": "Size",
"batchListProviderColumn": "Provider",
"batchListProviderUnknown": "—",
"batchListBatchCreated": "Batch {id} created — refreshing list…",
"batchListBatchCreatedDismiss": "Dismiss",
"batchListRefreshing": "Refreshing…",
"batchListRefresh": "Refresh",
"uploadFileModalRemove": "Remove",
"uploadFileModalUploading": "Uploading…",
"wizardInputReading": "Reading file…",
"wizardInputReady": "Ready",
"wizardInputLargeFileLabel": "Large file — sampling validation",
"wizardInputCsvJsonlReady": "JSONL generated — ready to validate.",
"wizardInputLargeFileWarning": "Large file detected — validation runs by sampling (first 5 MB + last 100 KB). Full validation happens server-side.",
"wizardCostWindow24h": "24h completion window",
"wizardValidationFieldsOk": "required fields valid"
}, },
"sidebar": { "sidebar": {
"home": "Home", "home": "Home",

View file

@ -786,7 +786,23 @@
"filesListUsedByRoleInput": "input", "filesListUsedByRoleInput": "input",
"filesListUsedByRoleOutput": "output", "filesListUsedByRoleOutput": "output",
"filesListUsedByRoleError": "error", "filesListUsedByRoleError": "error",
"batchListProgressPartial": "(partial)" "batchListProgressPartial": "(partial)",
"filesListSizeColumn": "Size",
"batchListProviderColumn": "Provider",
"batchListProviderUnknown": "—",
"batchListBatchCreated": "Batch {id} created — refreshing list…",
"batchListBatchCreatedDismiss": "Dismiss",
"batchListRefreshing": "Refreshing…",
"batchListRefresh": "Refresh",
"uploadFileModalRemove": "Remove",
"uploadFileModalUploading": "Uploading…",
"wizardInputReading": "Reading file…",
"wizardInputReady": "Ready",
"wizardInputLargeFileLabel": "Large file — sampling validation",
"wizardInputCsvJsonlReady": "JSONL generated — ready to validate.",
"wizardInputLargeFileWarning": "Large file detected — validation runs by sampling (first 5 MB + last 100 KB). Full validation happens server-side.",
"wizardCostWindow24h": "24h completion window",
"wizardValidationFieldsOk": "required fields valid"
}, },
"sidebar": { "sidebar": {
"home": "Rumah", "home": "Rumah",

View file

@ -786,7 +786,23 @@
"filesListUsedByRoleInput": "input", "filesListUsedByRoleInput": "input",
"filesListUsedByRoleOutput": "output", "filesListUsedByRoleOutput": "output",
"filesListUsedByRoleError": "error", "filesListUsedByRoleError": "error",
"batchListProgressPartial": "(partial)" "batchListProgressPartial": "(partial)",
"filesListSizeColumn": "Size",
"batchListProviderColumn": "Provider",
"batchListProviderUnknown": "—",
"batchListBatchCreated": "Batch {id} created — refreshing list…",
"batchListBatchCreatedDismiss": "Dismiss",
"batchListRefreshing": "Refreshing…",
"batchListRefresh": "Refresh",
"uploadFileModalRemove": "Remove",
"uploadFileModalUploading": "Uploading…",
"wizardInputReading": "Reading file…",
"wizardInputReady": "Ready",
"wizardInputLargeFileLabel": "Large file — sampling validation",
"wizardInputCsvJsonlReady": "JSONL generated — ready to validate.",
"wizardInputLargeFileWarning": "Large file detected — validation runs by sampling (first 5 MB + last 100 KB). Full validation happens server-side.",
"wizardCostWindow24h": "24h completion window",
"wizardValidationFieldsOk": "required fields valid"
}, },
"sidebar": { "sidebar": {
"home": "Thuis", "home": "Thuis",

View file

@ -786,7 +786,23 @@
"filesListUsedByRoleInput": "input", "filesListUsedByRoleInput": "input",
"filesListUsedByRoleOutput": "output", "filesListUsedByRoleOutput": "output",
"filesListUsedByRoleError": "error", "filesListUsedByRoleError": "error",
"batchListProgressPartial": "(partial)" "batchListProgressPartial": "(partial)",
"filesListSizeColumn": "Size",
"batchListProviderColumn": "Provider",
"batchListProviderUnknown": "—",
"batchListBatchCreated": "Batch {id} created — refreshing list…",
"batchListBatchCreatedDismiss": "Dismiss",
"batchListRefreshing": "Refreshing…",
"batchListRefresh": "Refresh",
"uploadFileModalRemove": "Remove",
"uploadFileModalUploading": "Uploading…",
"wizardInputReading": "Reading file…",
"wizardInputReady": "Ready",
"wizardInputLargeFileLabel": "Large file — sampling validation",
"wizardInputCsvJsonlReady": "JSONL generated — ready to validate.",
"wizardInputLargeFileWarning": "Large file detected — validation runs by sampling (first 5 MB + last 100 KB). Full validation happens server-side.",
"wizardCostWindow24h": "24h completion window",
"wizardValidationFieldsOk": "required fields valid"
}, },
"sidebar": { "sidebar": {
"home": "Hjem", "home": "Hjem",

View file

@ -786,7 +786,23 @@
"filesListUsedByRoleInput": "input", "filesListUsedByRoleInput": "input",
"filesListUsedByRoleOutput": "output", "filesListUsedByRoleOutput": "output",
"filesListUsedByRoleError": "error", "filesListUsedByRoleError": "error",
"batchListProgressPartial": "(partial)" "batchListProgressPartial": "(partial)",
"filesListSizeColumn": "Size",
"batchListProviderColumn": "Provider",
"batchListProviderUnknown": "—",
"batchListBatchCreated": "Batch {id} created — refreshing list…",
"batchListBatchCreatedDismiss": "Dismiss",
"batchListRefreshing": "Refreshing…",
"batchListRefresh": "Refresh",
"uploadFileModalRemove": "Remove",
"uploadFileModalUploading": "Uploading…",
"wizardInputReading": "Reading file…",
"wizardInputReady": "Ready",
"wizardInputLargeFileLabel": "Large file — sampling validation",
"wizardInputCsvJsonlReady": "JSONL generated — ready to validate.",
"wizardInputLargeFileWarning": "Large file detected — validation runs by sampling (first 5 MB + last 100 KB). Full validation happens server-side.",
"wizardCostWindow24h": "24h completion window",
"wizardValidationFieldsOk": "required fields valid"
}, },
"sidebar": { "sidebar": {
"home": "Bahay", "home": "Bahay",

View file

@ -786,7 +786,23 @@
"filesListUsedByRoleInput": "input", "filesListUsedByRoleInput": "input",
"filesListUsedByRoleOutput": "output", "filesListUsedByRoleOutput": "output",
"filesListUsedByRoleError": "error", "filesListUsedByRoleError": "error",
"batchListProgressPartial": "(partial)" "batchListProgressPartial": "(partial)",
"filesListSizeColumn": "Size",
"batchListProviderColumn": "Provider",
"batchListProviderUnknown": "—",
"batchListBatchCreated": "Batch {id} created — refreshing list…",
"batchListBatchCreatedDismiss": "Dismiss",
"batchListRefreshing": "Refreshing…",
"batchListRefresh": "Refresh",
"uploadFileModalRemove": "Remove",
"uploadFileModalUploading": "Uploading…",
"wizardInputReading": "Reading file…",
"wizardInputReady": "Ready",
"wizardInputLargeFileLabel": "Large file — sampling validation",
"wizardInputCsvJsonlReady": "JSONL generated — ready to validate.",
"wizardInputLargeFileWarning": "Large file detected — validation runs by sampling (first 5 MB + last 100 KB). Full validation happens server-side.",
"wizardCostWindow24h": "24h completion window",
"wizardValidationFieldsOk": "required fields valid"
}, },
"sidebar": { "sidebar": {
"home": "Dom", "home": "Dom",

View file

@ -776,7 +776,23 @@
"filesListUsedByRoleInput": "entrada", "filesListUsedByRoleInput": "entrada",
"filesListUsedByRoleOutput": "saída", "filesListUsedByRoleOutput": "saída",
"filesListUsedByRoleError": "erro", "filesListUsedByRoleError": "erro",
"filesListSizeColumn": "Tamanho",
"batchListProgressPartial": "(parcial)", "batchListProgressPartial": "(parcial)",
"batchListProviderColumn": "Provedor",
"batchListProviderUnknown": "—",
"batchListBatchCreated": "Batch {id} criado — atualizando lista…",
"batchListBatchCreatedDismiss": "Fechar",
"batchListRefreshing": "Atualizando…",
"batchListRefresh": "Atualizar",
"uploadFileModalRemove": "Remover",
"uploadFileModalUploading": "Enviando…",
"wizardInputReading": "Lendo arquivo…",
"wizardInputReady": "Pronto",
"wizardInputLargeFileLabel": "Arquivo grande — validação por amostragem",
"wizardInputCsvJsonlReady": "JSONL gerado — pronto para validar.",
"wizardInputLargeFileWarning": "Arquivo grande detectado — validação roda por amostragem (primeiros 5 MB + últimos 100 KB). Validação completa acontece no servidor.",
"wizardCostWindow24h": "Janela de conclusão de 24h",
"wizardValidationFieldsOk": "campos obrigatórios válidos",
"filesListDelete": "Excluir", "filesListDelete": "Excluir",
"filesListDownload": "Baixar", "filesListDownload": "Baixar",
"batchDetailActionCancel": "Cancelar batch", "batchDetailActionCancel": "Cancelar batch",

View file

@ -786,7 +786,23 @@
"filesListUsedByRoleInput": "input", "filesListUsedByRoleInput": "input",
"filesListUsedByRoleOutput": "output", "filesListUsedByRoleOutput": "output",
"filesListUsedByRoleError": "error", "filesListUsedByRoleError": "error",
"batchListProgressPartial": "(partial)" "batchListProgressPartial": "(partial)",
"filesListSizeColumn": "Size",
"batchListProviderColumn": "Provider",
"batchListProviderUnknown": "—",
"batchListBatchCreated": "Batch {id} created — refreshing list…",
"batchListBatchCreatedDismiss": "Dismiss",
"batchListRefreshing": "Refreshing…",
"batchListRefresh": "Refresh",
"uploadFileModalRemove": "Remove",
"uploadFileModalUploading": "Uploading…",
"wizardInputReading": "Reading file…",
"wizardInputReady": "Ready",
"wizardInputLargeFileLabel": "Large file — sampling validation",
"wizardInputCsvJsonlReady": "JSONL generated — ready to validate.",
"wizardInputLargeFileWarning": "Large file detected — validation runs by sampling (first 5 MB + last 100 KB). Full validation happens server-side.",
"wizardCostWindow24h": "24h completion window",
"wizardValidationFieldsOk": "required fields valid"
}, },
"sidebar": { "sidebar": {
"home": "Página inicial", "home": "Página inicial",

View file

@ -786,7 +786,23 @@
"filesListUsedByRoleInput": "input", "filesListUsedByRoleInput": "input",
"filesListUsedByRoleOutput": "output", "filesListUsedByRoleOutput": "output",
"filesListUsedByRoleError": "error", "filesListUsedByRoleError": "error",
"batchListProgressPartial": "(partial)" "batchListProgressPartial": "(partial)",
"filesListSizeColumn": "Size",
"batchListProviderColumn": "Provider",
"batchListProviderUnknown": "—",
"batchListBatchCreated": "Batch {id} created — refreshing list…",
"batchListBatchCreatedDismiss": "Dismiss",
"batchListRefreshing": "Refreshing…",
"batchListRefresh": "Refresh",
"uploadFileModalRemove": "Remove",
"uploadFileModalUploading": "Uploading…",
"wizardInputReading": "Reading file…",
"wizardInputReady": "Ready",
"wizardInputLargeFileLabel": "Large file — sampling validation",
"wizardInputCsvJsonlReady": "JSONL generated — ready to validate.",
"wizardInputLargeFileWarning": "Large file detected — validation runs by sampling (first 5 MB + last 100 KB). Full validation happens server-side.",
"wizardCostWindow24h": "24h completion window",
"wizardValidationFieldsOk": "required fields valid"
}, },
"sidebar": { "sidebar": {
"home": "Acasă", "home": "Acasă",

View file

@ -786,7 +786,23 @@
"filesListUsedByRoleInput": "input", "filesListUsedByRoleInput": "input",
"filesListUsedByRoleOutput": "output", "filesListUsedByRoleOutput": "output",
"filesListUsedByRoleError": "error", "filesListUsedByRoleError": "error",
"batchListProgressPartial": "(partial)" "batchListProgressPartial": "(partial)",
"filesListSizeColumn": "Size",
"batchListProviderColumn": "Provider",
"batchListProviderUnknown": "—",
"batchListBatchCreated": "Batch {id} created — refreshing list…",
"batchListBatchCreatedDismiss": "Dismiss",
"batchListRefreshing": "Refreshing…",
"batchListRefresh": "Refresh",
"uploadFileModalRemove": "Remove",
"uploadFileModalUploading": "Uploading…",
"wizardInputReading": "Reading file…",
"wizardInputReady": "Ready",
"wizardInputLargeFileLabel": "Large file — sampling validation",
"wizardInputCsvJsonlReady": "JSONL generated — ready to validate.",
"wizardInputLargeFileWarning": "Large file detected — validation runs by sampling (first 5 MB + last 100 KB). Full validation happens server-side.",
"wizardCostWindow24h": "24h completion window",
"wizardValidationFieldsOk": "required fields valid"
}, },
"sidebar": { "sidebar": {
"home": "Главная", "home": "Главная",

View file

@ -786,7 +786,23 @@
"filesListUsedByRoleInput": "input", "filesListUsedByRoleInput": "input",
"filesListUsedByRoleOutput": "output", "filesListUsedByRoleOutput": "output",
"filesListUsedByRoleError": "error", "filesListUsedByRoleError": "error",
"batchListProgressPartial": "(partial)" "batchListProgressPartial": "(partial)",
"filesListSizeColumn": "Size",
"batchListProviderColumn": "Provider",
"batchListProviderUnknown": "—",
"batchListBatchCreated": "Batch {id} created — refreshing list…",
"batchListBatchCreatedDismiss": "Dismiss",
"batchListRefreshing": "Refreshing…",
"batchListRefresh": "Refresh",
"uploadFileModalRemove": "Remove",
"uploadFileModalUploading": "Uploading…",
"wizardInputReading": "Reading file…",
"wizardInputReady": "Ready",
"wizardInputLargeFileLabel": "Large file — sampling validation",
"wizardInputCsvJsonlReady": "JSONL generated — ready to validate.",
"wizardInputLargeFileWarning": "Large file detected — validation runs by sampling (first 5 MB + last 100 KB). Full validation happens server-side.",
"wizardCostWindow24h": "24h completion window",
"wizardValidationFieldsOk": "required fields valid"
}, },
"sidebar": { "sidebar": {
"home": "Domov", "home": "Domov",

View file

@ -786,7 +786,23 @@
"filesListUsedByRoleInput": "input", "filesListUsedByRoleInput": "input",
"filesListUsedByRoleOutput": "output", "filesListUsedByRoleOutput": "output",
"filesListUsedByRoleError": "error", "filesListUsedByRoleError": "error",
"batchListProgressPartial": "(partial)" "batchListProgressPartial": "(partial)",
"filesListSizeColumn": "Size",
"batchListProviderColumn": "Provider",
"batchListProviderUnknown": "—",
"batchListBatchCreated": "Batch {id} created — refreshing list…",
"batchListBatchCreatedDismiss": "Dismiss",
"batchListRefreshing": "Refreshing…",
"batchListRefresh": "Refresh",
"uploadFileModalRemove": "Remove",
"uploadFileModalUploading": "Uploading…",
"wizardInputReading": "Reading file…",
"wizardInputReady": "Ready",
"wizardInputLargeFileLabel": "Large file — sampling validation",
"wizardInputCsvJsonlReady": "JSONL generated — ready to validate.",
"wizardInputLargeFileWarning": "Large file detected — validation runs by sampling (first 5 MB + last 100 KB). Full validation happens server-side.",
"wizardCostWindow24h": "24h completion window",
"wizardValidationFieldsOk": "required fields valid"
}, },
"sidebar": { "sidebar": {
"home": "Hem", "home": "Hem",

View file

@ -786,7 +786,23 @@
"filesListUsedByRoleInput": "input", "filesListUsedByRoleInput": "input",
"filesListUsedByRoleOutput": "output", "filesListUsedByRoleOutput": "output",
"filesListUsedByRoleError": "error", "filesListUsedByRoleError": "error",
"batchListProgressPartial": "(partial)" "batchListProgressPartial": "(partial)",
"filesListSizeColumn": "Size",
"batchListProviderColumn": "Provider",
"batchListProviderUnknown": "—",
"batchListBatchCreated": "Batch {id} created — refreshing list…",
"batchListBatchCreatedDismiss": "Dismiss",
"batchListRefreshing": "Refreshing…",
"batchListRefresh": "Refresh",
"uploadFileModalRemove": "Remove",
"uploadFileModalUploading": "Uploading…",
"wizardInputReading": "Reading file…",
"wizardInputReady": "Ready",
"wizardInputLargeFileLabel": "Large file — sampling validation",
"wizardInputCsvJsonlReady": "JSONL generated — ready to validate.",
"wizardInputLargeFileWarning": "Large file detected — validation runs by sampling (first 5 MB + last 100 KB). Full validation happens server-side.",
"wizardCostWindow24h": "24h completion window",
"wizardValidationFieldsOk": "required fields valid"
}, },
"sidebar": { "sidebar": {
"home": "Home", "home": "Home",

View file

@ -786,7 +786,23 @@
"filesListUsedByRoleInput": "input", "filesListUsedByRoleInput": "input",
"filesListUsedByRoleOutput": "output", "filesListUsedByRoleOutput": "output",
"filesListUsedByRoleError": "error", "filesListUsedByRoleError": "error",
"batchListProgressPartial": "(partial)" "batchListProgressPartial": "(partial)",
"filesListSizeColumn": "Size",
"batchListProviderColumn": "Provider",
"batchListProviderUnknown": "—",
"batchListBatchCreated": "Batch {id} created — refreshing list…",
"batchListBatchCreatedDismiss": "Dismiss",
"batchListRefreshing": "Refreshing…",
"batchListRefresh": "Refresh",
"uploadFileModalRemove": "Remove",
"uploadFileModalUploading": "Uploading…",
"wizardInputReading": "Reading file…",
"wizardInputReady": "Ready",
"wizardInputLargeFileLabel": "Large file — sampling validation",
"wizardInputCsvJsonlReady": "JSONL generated — ready to validate.",
"wizardInputLargeFileWarning": "Large file detected — validation runs by sampling (first 5 MB + last 100 KB). Full validation happens server-side.",
"wizardCostWindow24h": "24h completion window",
"wizardValidationFieldsOk": "required fields valid"
}, },
"sidebar": { "sidebar": {
"home": "Home", "home": "Home",

View file

@ -786,7 +786,23 @@
"filesListUsedByRoleInput": "input", "filesListUsedByRoleInput": "input",
"filesListUsedByRoleOutput": "output", "filesListUsedByRoleOutput": "output",
"filesListUsedByRoleError": "error", "filesListUsedByRoleError": "error",
"batchListProgressPartial": "(partial)" "batchListProgressPartial": "(partial)",
"filesListSizeColumn": "Size",
"batchListProviderColumn": "Provider",
"batchListProviderUnknown": "—",
"batchListBatchCreated": "Batch {id} created — refreshing list…",
"batchListBatchCreatedDismiss": "Dismiss",
"batchListRefreshing": "Refreshing…",
"batchListRefresh": "Refresh",
"uploadFileModalRemove": "Remove",
"uploadFileModalUploading": "Uploading…",
"wizardInputReading": "Reading file…",
"wizardInputReady": "Ready",
"wizardInputLargeFileLabel": "Large file — sampling validation",
"wizardInputCsvJsonlReady": "JSONL generated — ready to validate.",
"wizardInputLargeFileWarning": "Large file detected — validation runs by sampling (first 5 MB + last 100 KB). Full validation happens server-side.",
"wizardCostWindow24h": "24h completion window",
"wizardValidationFieldsOk": "required fields valid"
}, },
"sidebar": { "sidebar": {
"home": "Home", "home": "Home",

View file

@ -786,7 +786,23 @@
"filesListUsedByRoleInput": "input", "filesListUsedByRoleInput": "input",
"filesListUsedByRoleOutput": "output", "filesListUsedByRoleOutput": "output",
"filesListUsedByRoleError": "error", "filesListUsedByRoleError": "error",
"batchListProgressPartial": "(partial)" "batchListProgressPartial": "(partial)",
"filesListSizeColumn": "Size",
"batchListProviderColumn": "Provider",
"batchListProviderUnknown": "—",
"batchListBatchCreated": "Batch {id} created — refreshing list…",
"batchListBatchCreatedDismiss": "Dismiss",
"batchListRefreshing": "Refreshing…",
"batchListRefresh": "Refresh",
"uploadFileModalRemove": "Remove",
"uploadFileModalUploading": "Uploading…",
"wizardInputReading": "Reading file…",
"wizardInputReady": "Ready",
"wizardInputLargeFileLabel": "Large file — sampling validation",
"wizardInputCsvJsonlReady": "JSONL generated — ready to validate.",
"wizardInputLargeFileWarning": "Large file detected — validation runs by sampling (first 5 MB + last 100 KB). Full validation happens server-side.",
"wizardCostWindow24h": "24h completion window",
"wizardValidationFieldsOk": "required fields valid"
}, },
"sidebar": { "sidebar": {
"home": "บ้าน", "home": "บ้าน",

View file

@ -786,7 +786,23 @@
"filesListUsedByRoleInput": "input", "filesListUsedByRoleInput": "input",
"filesListUsedByRoleOutput": "output", "filesListUsedByRoleOutput": "output",
"filesListUsedByRoleError": "error", "filesListUsedByRoleError": "error",
"batchListProgressPartial": "(partial)" "batchListProgressPartial": "(partial)",
"filesListSizeColumn": "Size",
"batchListProviderColumn": "Provider",
"batchListProviderUnknown": "—",
"batchListBatchCreated": "Batch {id} created — refreshing list…",
"batchListBatchCreatedDismiss": "Dismiss",
"batchListRefreshing": "Refreshing…",
"batchListRefresh": "Refresh",
"uploadFileModalRemove": "Remove",
"uploadFileModalUploading": "Uploading…",
"wizardInputReading": "Reading file…",
"wizardInputReady": "Ready",
"wizardInputLargeFileLabel": "Large file — sampling validation",
"wizardInputCsvJsonlReady": "JSONL generated — ready to validate.",
"wizardInputLargeFileWarning": "Large file detected — validation runs by sampling (first 5 MB + last 100 KB). Full validation happens server-side.",
"wizardCostWindow24h": "24h completion window",
"wizardValidationFieldsOk": "required fields valid"
}, },
"sidebar": { "sidebar": {
"home": "Ana Sayfa", "home": "Ana Sayfa",

View file

@ -786,7 +786,23 @@
"filesListUsedByRoleInput": "input", "filesListUsedByRoleInput": "input",
"filesListUsedByRoleOutput": "output", "filesListUsedByRoleOutput": "output",
"filesListUsedByRoleError": "error", "filesListUsedByRoleError": "error",
"batchListProgressPartial": "(partial)" "batchListProgressPartial": "(partial)",
"filesListSizeColumn": "Size",
"batchListProviderColumn": "Provider",
"batchListProviderUnknown": "—",
"batchListBatchCreated": "Batch {id} created — refreshing list…",
"batchListBatchCreatedDismiss": "Dismiss",
"batchListRefreshing": "Refreshing…",
"batchListRefresh": "Refresh",
"uploadFileModalRemove": "Remove",
"uploadFileModalUploading": "Uploading…",
"wizardInputReading": "Reading file…",
"wizardInputReady": "Ready",
"wizardInputLargeFileLabel": "Large file — sampling validation",
"wizardInputCsvJsonlReady": "JSONL generated — ready to validate.",
"wizardInputLargeFileWarning": "Large file detected — validation runs by sampling (first 5 MB + last 100 KB). Full validation happens server-side.",
"wizardCostWindow24h": "24h completion window",
"wizardValidationFieldsOk": "required fields valid"
}, },
"sidebar": { "sidebar": {
"home": "додому", "home": "додому",

View file

@ -786,7 +786,23 @@
"filesListUsedByRoleInput": "input", "filesListUsedByRoleInput": "input",
"filesListUsedByRoleOutput": "output", "filesListUsedByRoleOutput": "output",
"filesListUsedByRoleError": "error", "filesListUsedByRoleError": "error",
"batchListProgressPartial": "(partial)" "batchListProgressPartial": "(partial)",
"filesListSizeColumn": "Size",
"batchListProviderColumn": "Provider",
"batchListProviderUnknown": "—",
"batchListBatchCreated": "Batch {id} created — refreshing list…",
"batchListBatchCreatedDismiss": "Dismiss",
"batchListRefreshing": "Refreshing…",
"batchListRefresh": "Refresh",
"uploadFileModalRemove": "Remove",
"uploadFileModalUploading": "Uploading…",
"wizardInputReading": "Reading file…",
"wizardInputReady": "Ready",
"wizardInputLargeFileLabel": "Large file — sampling validation",
"wizardInputCsvJsonlReady": "JSONL generated — ready to validate.",
"wizardInputLargeFileWarning": "Large file detected — validation runs by sampling (first 5 MB + last 100 KB). Full validation happens server-side.",
"wizardCostWindow24h": "24h completion window",
"wizardValidationFieldsOk": "required fields valid"
}, },
"sidebar": { "sidebar": {
"home": "Home", "home": "Home",

View file

@ -786,7 +786,23 @@
"filesListUsedByRoleInput": "input", "filesListUsedByRoleInput": "input",
"filesListUsedByRoleOutput": "output", "filesListUsedByRoleOutput": "output",
"filesListUsedByRoleError": "error", "filesListUsedByRoleError": "error",
"batchListProgressPartial": "(partial)" "batchListProgressPartial": "(partial)",
"filesListSizeColumn": "Size",
"batchListProviderColumn": "Provider",
"batchListProviderUnknown": "—",
"batchListBatchCreated": "Batch {id} created — refreshing list…",
"batchListBatchCreatedDismiss": "Dismiss",
"batchListRefreshing": "Refreshing…",
"batchListRefresh": "Refresh",
"uploadFileModalRemove": "Remove",
"uploadFileModalUploading": "Uploading…",
"wizardInputReading": "Reading file…",
"wizardInputReady": "Ready",
"wizardInputLargeFileLabel": "Large file — sampling validation",
"wizardInputCsvJsonlReady": "JSONL generated — ready to validate.",
"wizardInputLargeFileWarning": "Large file detected — validation runs by sampling (first 5 MB + last 100 KB). Full validation happens server-side.",
"wizardCostWindow24h": "24h completion window",
"wizardValidationFieldsOk": "required fields valid"
}, },
"sidebar": { "sidebar": {
"home": "Trang chủ", "home": "Trang chủ",

View file

@ -786,7 +786,23 @@
"filesListUsedByRoleInput": "input", "filesListUsedByRoleInput": "input",
"filesListUsedByRoleOutput": "output", "filesListUsedByRoleOutput": "output",
"filesListUsedByRoleError": "error", "filesListUsedByRoleError": "error",
"batchListProgressPartial": "(partial)" "batchListProgressPartial": "(partial)",
"filesListSizeColumn": "Size",
"batchListProviderColumn": "Provider",
"batchListProviderUnknown": "—",
"batchListBatchCreated": "Batch {id} created — refreshing list…",
"batchListBatchCreatedDismiss": "Dismiss",
"batchListRefreshing": "Refreshing…",
"batchListRefresh": "Refresh",
"uploadFileModalRemove": "Remove",
"uploadFileModalUploading": "Uploading…",
"wizardInputReading": "Reading file…",
"wizardInputReady": "Ready",
"wizardInputLargeFileLabel": "Large file — sampling validation",
"wizardInputCsvJsonlReady": "JSONL generated — ready to validate.",
"wizardInputLargeFileWarning": "Large file detected — validation runs by sampling (first 5 MB + last 100 KB). Full validation happens server-side.",
"wizardCostWindow24h": "24h completion window",
"wizardValidationFieldsOk": "required fields valid"
}, },
"sidebar": { "sidebar": {
"home": "首页", "home": "首页",

View file

@ -132,8 +132,9 @@ function setByPath(target: Record<string, unknown>, path: string, value: unknown
const child: unknown = typeof next === "number" ? [] : Object.create(null); const child: unknown = typeof next === "number" ? [] : Object.create(null);
safePropSet(cur, k, child); safePropSet(cur, k, child);
} }
cur = Object.prototype.hasOwnProperty.call(cur, k) ? cur[k] : undefined; const nextCur = Object.prototype.hasOwnProperty.call(cur, k) ? cur[k] : undefined;
if (cur == null) return; // bail if tree navigation failed if (nextCur == null || typeof nextCur !== "object") return; // bail if tree navigation failed
cur = nextCur as Record<string, unknown>;
} }
const lastKey = tokens.at(-1)!; const lastKey = tokens.at(-1)!;
@ -159,7 +160,9 @@ export function csvToJsonl(rawInput: CsvToJsonlInput): {
errors: Array<{ row: number; reason: string }>; errors: Array<{ row: number; reason: string }>;
} { } {
const input = csvToJsonlInputSchema.parse(rawInput); const input = csvToJsonlInputSchema.parse(rawInput);
const lines = splitLines(input.csv); // Strip UTF-8 BOM if present so the first header cell parses cleanly
const normalizedCsv = input.csv.replace(/^/, "");
const lines = splitLines(normalizedCsv);
if (lines.length < 2) { if (lines.length < 2) {
return { return {

View file

@ -3,8 +3,6 @@ import { SUPPORTED_BATCH_ENDPOINTS, type SupportedBatchEndpoint }
// ── Wizard state ───────────────────────────────────────────────────────────── // ── Wizard state ─────────────────────────────────────────────────────────────
export type WizardStep = "destination" | "input" | "validate" | "cost";
export interface WizardDestination { export interface WizardDestination {
provider: "openai" | "anthropic" | "gemini"; provider: "openai" | "anthropic" | "gemini";
endpoint: SupportedBatchEndpoint; endpoint: SupportedBatchEndpoint;
@ -75,12 +73,6 @@ export interface RetryPlan {
export const BATCH_SUPPORTED_PROVIDERS = ["openai", "anthropic", "gemini"] as const; export const BATCH_SUPPORTED_PROVIDERS = ["openai", "anthropic", "gemini"] as const;
export type BatchProvider = (typeof BATCH_SUPPORTED_PROVIDERS)[number]; export type BatchProvider = (typeof BATCH_SUPPORTED_PROVIDERS)[number];
export interface BatchProviderConfig {
provider: BatchProvider;
defaultEndpoint: SupportedBatchEndpoint;
defaultModels: string[]; // canonical model ids
}
// ── Re-exports ─────────────────────────────────────────────────────────────── // ── Re-exports ───────────────────────────────────────────────────────────────
export type { SupportedBatchEndpoint }; export type { SupportedBatchEndpoint };

View file

@ -100,7 +100,9 @@ export function validateJsonl(
const maxHead = opts.maxLinesToInspect ?? 1000; const maxHead = opts.maxLinesToInspect ?? 1000;
const maxTail = opts.tailLinesToInspect ?? 100; const maxTail = opts.tailLinesToInspect ?? 100;
const lines = content.split(/\r?\n/); // Strip UTF-8 BOM if present (Windows-saved files) so first line parses cleanly
const normalized = content.replace(/^/, "");
const lines = normalized.split(/\r?\n/);
// Drop trailing empty lines // Drop trailing empty lines
while (lines.length > 0 && lines.at(-1)!.trim() === "") lines.pop(); while (lines.length > 0 && lines.at(-1)!.trim() === "") lines.pop();

View file

@ -272,6 +272,54 @@ describe("BatchListTab — rendering", () => {
expect(text).not.toMatch(/route\.ts/); expect(text).not.toMatch(/route\.ts/);
expect(text).not.toMatch(/\.ts:\d/); expect(text).not.toMatch(/\.ts:\d/);
}); });
it("16. expired batch with failures renders (partial) suffix on progress cell (G-AUD3)", () => {
const batches = [
makeBatch({
id: "batch-expired-partial",
status: "expired",
requestCountsTotal: 100,
requestCountsCompleted: 30,
requestCountsFailed: 10,
}),
];
const el = render(
<BatchListTab batches={batches} files={[]} loading={false} />
);
// The i18n key is rendered literally in tests (mock useTranslations returns key)
expect(el.textContent).toContain("batchListProgressPartial");
});
it("17. cost cell renders -50% inline badge for batches with cost (G-AUD2)", () => {
const batches = [
makeBatch({
id: "batch-with-cost",
status: "in_progress",
requestCountsTotal: 100,
requestCountsCompleted: 10,
requestCountsFailed: 0,
}),
];
const el = render(
<BatchListTab batches={batches} files={[]} loading={false} />
);
expect(el.textContent).toContain("-50%");
});
it("18. Provider column derives provider from model id (A-1)", () => {
const batches = [
makeBatch({ id: "batch-openai", model: "gpt-4o", status: "completed" }),
makeBatch({ id: "batch-anthropic", model: "claude-3-5-sonnet-20241022", status: "completed" }),
makeBatch({ id: "batch-gemini", model: "gemini-1.5-flash", status: "completed" }),
];
const el = render(
<BatchListTab batches={batches} files={[]} loading={false} />
);
const text = el.textContent ?? "";
expect(text).toContain("OpenAI");
expect(text).toContain("Anthropic");
expect(text).toContain("Gemini");
});
}); });
// ── FilesListTab ────────────────────────────────────────────────────────────── // ── FilesListTab ──────────────────────────────────────────────────────────────

View file

@ -166,3 +166,22 @@ test("estimateBatchCost: malformed line skipped gracefully, no crash", () => {
// The valid line contributes tokens; malformed contributes 0 — so tokens > 0 // The valid line contributes tokens; malformed contributes 0 — so tokens > 0
assert.ok(result.estimatedInputTokens > 0, "valid line should contribute tokens"); assert.ok(result.estimatedInputTokens > 0, "valid line should contribute tokens");
}); });
// ── Alias-match path (case-insensitive lookup) ────────────────────────────────
test("estimateBatchCost: alias-match path triggers when model differs only in case", () => {
// gpt-4o exists in DEFAULT_PRICING with lowercase key → uppercase variant
// should be found via Pass 2 alias-match.
const result = estimateBatchCost({
jsonl: makeLine("req-1") + "\n",
model: "GPT-4O", // intentional upper-case — not stored that way in pricing
endpoint: ENDPOINT,
});
// Either alias-match (preferred when pricing table is case-sensitive) or
// exact-match (if the table normalizes); both are valid "found" results.
assert.ok(
result.pricingSource === "alias-match" || result.pricingSource === "exact-match",
`expected match (alias or exact), got ${result.pricingSource}`,
);
assert.ok(result.syncCostUsd > 0, "non-zero cost expected when pricing is found");
});

View file

@ -187,3 +187,30 @@ test("validateJsonl: errors capped at 50 even with many invalid lines", () => {
const result = validateJsonl(makeJsonl(lines), { endpoint: ENDPOINT }); const result = validateJsonl(makeJsonl(lines), { endpoint: ENDPOINT });
assert.ok(result.errors.length <= 50, `errors should be capped at 50, got ${result.errors.length}`); assert.ok(result.errors.length <= 50, `errors should be capped at 50, got ${result.errors.length}`);
}); });
// ── body must be object (not array) — Array.isArray guard ─────────────────────
test("validateJsonl: rejects body that is an array (typeof []==='object' guard)", () => {
const line = JSON.stringify({ custom_id: "req-1", method: "POST", url: ENDPOINT, body: [] });
const result = validateJsonl(line, { endpoint: ENDPOINT });
assert.ok(!result.ok, "should be invalid");
assert.ok(
result.errors.some((e) => e.field === "body"),
`should flag body field; errors=${JSON.stringify(result.errors)}`,
);
});
// ── BOM stripping (Windows-saved files) ───────────────────────────────────────
test("validateJsonl: strips UTF-8 BOM before parsing first line", () => {
const line = JSON.stringify({
custom_id: "req-1",
method: "POST",
url: ENDPOINT,
body: { model: "gpt-4o", messages: [{ role: "user", content: "hi" }] },
});
const result = validateJsonl("" + line, { endpoint: ENDPOINT });
assert.ok(result.ok, `BOM should be stripped; got errors=${JSON.stringify(result.errors)}`);
assert.equal(result.totalLines, 1);
assert.equal(result.uniqueCustomIds, 1);
});

View file

@ -10,6 +10,13 @@
"include": [], "include": [],
"files": [ "files": [
"src/app/api/settings/proxy/test/route.ts", "src/app/api/settings/proxy/test/route.ts",
"src/app/(dashboard)/dashboard/batch/batch-utils.ts",
"src/lib/batches/types.ts",
"src/lib/batches/schemas.ts",
"src/lib/batches/csvToJsonl.ts",
"src/lib/batches/validateJsonl.ts",
"src/lib/batches/costEstimator.ts",
"src/lib/batches/retryFailed.ts",
"src/lib/db/apiKeys.ts", "src/lib/db/apiKeys.ts",
"src/lib/db/cliToolState.ts", "src/lib/db/cliToolState.ts",
"src/lib/db/encryption.ts", "src/lib/db/encryption.ts",