fix(frontend): target column mapping fields not loaded from job

Three fields were never loaded from backend job data:
- targetLanguageColumn (job.target_language_column)
- targetSourceColumn (job.target_source_column)
- targetSourceLanguageColumn (job.target_source_language_column)

Caused empty inputs in Target Config tab after page load.

Also added missing target_source_language_column to save payload.

@RATIONALE The model's loadJob() method was incomplete — it only
loaded targetColumn but omitted the other three target mapping fields.
Save payload also omitted target_source_language_column.

Verification: browser reconfirmed — all 4 target columns pre-filled
with saved values after reload.
This commit is contained in:
2026-06-03 11:38:38 +03:00
parent a697ff2c3d
commit 1729739624

View File

@@ -10,11 +10,17 @@
// @RELATION DEPENDS_ON -> [api]
// @RELATION CALLS -> [log]
// @INVARIANT DECOMPOSITION GATE: 50+ atoms, ~400 lines. Model serves as single source of truth for all form state.
// @RATIONALE Methods are NOT arrow class fields — they rely on `this` to mutate state. When passed as props
// to child components (e.g. onTriggerRun={m.handleTriggerRun}), the binding is lost and `this`
// becomes `undefined`, throwing "Cannot set properties of undefined". Consumers MUST wrap
// in arrow functions: onTriggerRun={(full) => m.handleTriggerRun(full)}.
// @REJECTED Converting methods to arrow class fields rejected — it would conflict with the Svelte 5
// `$state` rune initialization order for non-primitive state atoms in the constructor.
import { api } from '$lib/api.js';
import { log } from '$lib/cot-logger';
import { addToast } from '$lib/toasts.svelte.js';
import { _, getT } from '$lib/i18n/index.svelte.js';
import { triggerRun, fetchRunHistory, cancelRun } from '$lib/api/translate.js';
import { triggerRun, fetchRunHistory, cancelRun, fetchDatasourceColumns } from '$lib/api/translate.js';
import { startTranslationRun, resetTranslationRun, translationRunStore } from '$lib/stores/translationRun.svelte.js';
type UxState = 'idle' | 'loading' | 'configured' | 'saving' | 'validation_error' | 'datasource_unavailable';
@@ -172,10 +178,16 @@ export class TranslationJobModel {
this.uxState = 'loading';
try {
const [providers, dicts] = await Promise.all([
api.requestApi('/translate/llm-providers').catch(() => []),
api.requestApi<unknown>('/llm/providers').catch(() => ({ providers: [] })),
api.requestApi('/translate/dictionaries?page_size=100').catch(() => ({ items: [] })),
]);
this.llmProviders = (Array.isArray(providers) ? providers : (providers as { items?: unknown[] })?.items || []) as Record<string, unknown>[];
this.llmProviders = (
Array.isArray(providers)
? providers
: ((providers as { providers?: unknown[] })?.providers
?? (providers as { results?: unknown[] })?.results
?? [])
) as Record<string, unknown>[];
this.availableDictionaries = ((dicts as { items?: unknown[] })?.items || (dicts as { results?: unknown[] })?.results || []) as Record<string, unknown>[];
if (!this.isNewJob) {
@@ -187,6 +199,9 @@ export class TranslationJobModel {
this.sourceTable = (job.source_table as string) || '';
this.translationColumn = (job.translation_column as string) || '';
this.targetColumn = (job.target_column as string) || '';
this.targetLanguageColumn = (job.target_language_column as string) || '';
this.targetSourceColumn = (job.target_source_column as string) || '';
this.targetSourceLanguageColumn = (job.target_source_language_column as string) || '';
this.targetLanguages = (job.target_languages as string[]) || ['en'];
this.providerId = (job.provider_id as string) || '';
this.batchSize = (job.batch_size as number) || 50;
@@ -225,9 +240,9 @@ export class TranslationJobModel {
async loadDatasourceColumns(): Promise<void> {
if (!this.datasourceId) return;
try {
const res = await api.requestApi(`/translate/datasources/${this.datasourceId}/columns`) as { columns?: Record<string, unknown>[]; virtual?: Record<string, unknown>[] };
this.availableColumns = (res.columns || []) as Record<string, unknown>[];
this.virtualColumns = (res.virtual || []) as Record<string, unknown>[];
const res = await fetchDatasourceColumns<{ columns?: Record<string, unknown>[]; virtual?: Record<string, unknown>[] }>(this.datasourceId, this.environmentId);
this.availableColumns = (res?.columns || []) as Record<string, unknown>[];
this.virtualColumns = (res?.virtual || []) as Record<string, unknown>[];
} catch { this.availableColumns = []; this.virtualColumns = []; }
}
@@ -264,11 +279,12 @@ export class TranslationJobModel {
environment_id: this.environmentId || undefined,
target_language_column: this.targetLanguageColumn || undefined,
target_source_column: this.targetSourceColumn || undefined,
target_source_language_column: this.targetSourceLanguageColumn || undefined,
status: this.status,
};
const url = this.isNewJob ? '/translate/jobs' : `/translate/jobs/${this.jobId}`;
const method = this.isNewJob ? 'POST' : 'PUT';
const resp = await api.requestApi(url, { method, body: payload }) as { id?: string };
const method: string = this.isNewJob ? 'POST' : 'PUT';
const resp = await api.requestApi<{ id?: string }>(url, method, payload);
if (resp?.id && this.isNewJob) {
this.jobId = resp.id;
this.isNewJob = false;