fix(llm): add fetch-models endpoint, fix SQL Lab INSERT (client_id truncation, sync mode, target_column, timestamp normalization)

- Add POST /api/llm/providers/fetch-models route with LLMClient.fetch_models()
- Add target_column to TranslationJob model/schema/service/orchestrator
- Fix SQL Lab execute: truncate client_id to 11 chars (varchar(11))
- Switch SQL Lab to sync mode (runAsync: false) — no Celery workers
- Fix polling: unwrap nested result from Superset query API
- Fix ClickHouse timestamp: normalize float timestamps to YYYY-MM-DD
This commit is contained in:
2026-05-13 20:06:15 +03:00
parent ffcfec9acf
commit 10803a5019
276 changed files with 1923 additions and 1822 deletions

View File

@@ -2,7 +2,6 @@
<!-- @BRIEF Component component: components/RepositoryDashboardGrid.svelte -->
<!-- @LAYER UI -->
<!--
@COMPLEXITY: 3
@SEMANTICS: dashboard, grid, selection, pagination
@PURPOSE: Displays a grid of dashboards with selection and pagination.
@LAYER: Component
@@ -98,7 +97,7 @@
);
// [/SECTION]
// [DEF:handleSort:Function]
// #region handleSort:Function [TYPE Function]
// @PURPOSE: Toggles sort direction or changes sort column.
// @PRE: column name is provided.
// @POST: sortColumn and sortDirection state updated.
@@ -110,9 +109,9 @@
sortDirection = "asc";
}
}
// [/DEF:handleSort:Function]
// #endregion handleSort:Function
// [DEF:handleSelectionChange:Function]
// #region handleSelectionChange:Function [TYPE Function]
// @PURPOSE: Handles individual checkbox changes.
// @PRE: dashboard ID and checked status provided.
// @POST: selectedIds array updated.
@@ -125,9 +124,9 @@
}
selectedIds = newSelected;
}
// [/DEF:handleSelectionChange:Function]
// #endregion handleSelectionChange:Function
// [DEF:handleSelectAll:Function]
// #region handleSelectAll:Function [TYPE Function]
// @PURPOSE: Handles select all checkbox.
// @PRE: checked status provided.
// @POST: selectedIds array updated for all paginated items.
@@ -144,9 +143,9 @@
}
selectedIds = newSelected;
}
// [/DEF:handleSelectAll:Function]
// #endregion handleSelectAll:Function
// [DEF:goToPage:Function]
// #region goToPage:Function [TYPE Function]
// @PURPOSE: Changes current page.
// @PRE: page index is provided.
// @POST: currentPage state updated if within valid range.
@@ -155,9 +154,9 @@
currentPage = page;
}
}
// [/DEF:goToPage:Function]
// #endregion goToPage:Function
// [DEF:getRepositoryStatusToken:Function]
// #region getRepositoryStatusToken:Function [TYPE Function]
/**
* @purpose Returns normalized repository status token for a dashboard.
* @pre Dashboard exists.
@@ -166,17 +165,17 @@
function getRepositoryStatusToken(dashboardId: number): string {
return repositoryStatusByDashboardId[dashboardId] || "loading";
}
// [/DEF:getRepositoryStatusToken:Function]
// #endregion getRepositoryStatusToken:Function
// [DEF:isRepositoryReady:Function]
// #region isRepositoryReady:Function [TYPE Function]
// @PURPOSE: Determines whether git actions can run for a dashboard.
function isRepositoryReady(dashboardId: number): boolean {
const token = getRepositoryStatusToken(dashboardId);
return token !== "loading" && token !== "no_repo" && token !== "error";
}
// [/DEF:isRepositoryReady:Function]
// #endregion isRepositoryReady:Function
// [DEF:invalidateRepositoryStatuses:Function]
// #region invalidateRepositoryStatuses:Function [TYPE Function]
// @PURPOSE: Marks dashboard statuses as loading so they are refetched.
function invalidateRepositoryStatuses(dashboardIds: number[]): void {
if (dashboardIds.length === 0) return;
@@ -186,9 +185,9 @@
});
repositoryStatusByDashboardId = nextStatuses;
}
// [/DEF:invalidateRepositoryStatuses:Function]
// #endregion invalidateRepositoryStatuses:Function
// [DEF:resolveRepositoryStatusToken:Function]
// #region resolveRepositoryStatusToken:Function [TYPE Function]
/**
* @purpose Converts git status payload into a stable UI status token.
*/
@@ -220,9 +219,9 @@
return hasChanges ? "changes" : "synced";
}
// [/DEF:resolveRepositoryStatusToken:Function]
// #endregion resolveRepositoryStatusToken:Function
// [DEF:loadRepositoryStatuses:Function]
// #region loadRepositoryStatuses:Function [TYPE Function]
/**
* @purpose Hydrates repository status map for dashboards in repository mode.
*/
@@ -279,9 +278,9 @@
...Object.fromEntries(entries),
};
}
// [/DEF:loadRepositoryStatuses:Function]
// #endregion loadRepositoryStatuses:Function
// [DEF:runBulkGitAction:Function]
// #region runBulkGitAction:Function [TYPE Function]
// @PURPOSE: Executes git action for selected dashboards with limited parallelism.
async function runBulkGitAction(
actionToken: string,
@@ -333,15 +332,15 @@
bulkActionRunning = false;
}
}
// [/DEF:runBulkGitAction:Function]
// #endregion runBulkGitAction:Function
// [DEF:handleBulkSync:Function]
// #region handleBulkSync:Function [TYPE Function]
async function handleBulkSync(): Promise<void> {
await runBulkGitAction("sync", (dashboardId) => gitService.sync(dashboardId, null, envId));
}
// [/DEF:handleBulkSync:Function]
// #endregion handleBulkSync:Function
// [DEF:handleBulkCommit:Function]
// #region handleBulkCommit:Function [TYPE Function]
async function handleBulkCommit(): Promise<void> {
const message = prompt($t.git?.commit_message);
if (!message?.trim()) return;
@@ -349,21 +348,21 @@
gitService.commit(dashboardId, message.trim(), [], envId),
);
}
// [/DEF:handleBulkCommit:Function]
// #endregion handleBulkCommit:Function
// [DEF:handleBulkPull:Function]
// #region handleBulkPull:Function [TYPE Function]
async function handleBulkPull(): Promise<void> {
await runBulkGitAction("pull", (dashboardId) => gitService.pull(dashboardId, envId));
}
// [/DEF:handleBulkPull:Function]
// #endregion handleBulkPull:Function
// [DEF:handleBulkPush:Function]
// #region handleBulkPush:Function [TYPE Function]
async function handleBulkPush(): Promise<void> {
await runBulkGitAction("push", (dashboardId) => gitService.push(dashboardId, envId));
}
// [/DEF:handleBulkPush:Function]
// #endregion handleBulkPush:Function
// [DEF:handleBulkDelete:Function]
// #region handleBulkDelete:Function [TYPE Function]
// @PURPOSE: Removes selected repositories from storage and binding table.
async function handleBulkDelete(): Promise<void> {
if (!confirm($t.git?.confirm_delete_repo || "Удалить выбранные репозитории?")) return;
@@ -374,9 +373,9 @@
dashboards = dashboards.filter((dashboard) => !idsToDelete.includes(dashboard.id));
selectedIds = [];
}
// [/DEF:handleBulkDelete:Function]
// #endregion handleBulkDelete:Function
// [DEF:handleManageSelected:Function]
// #region handleManageSelected:Function [TYPE Function]
// @PURPOSE: Opens Git manager for exactly one selected dashboard.
async function handleManageSelected(): Promise<void> {
if (selectedIds.length !== 1) {
@@ -390,9 +389,9 @@
);
openGitManagerForDashboard(selectedDashboard || null);
}
// [/DEF:handleManageSelected:Function]
// #endregion handleManageSelected:Function
// [DEF:resolveDashboardRef:Function]
// #region resolveDashboardRef:Function [TYPE Function]
// @PURPOSE: Resolves dashboard slug from payload fields.
// @PRE: Dashboard metadata is provided.
// @POST: Returns slug string or null if unavailable.
@@ -411,9 +410,9 @@
if (!slugMatch?.[1]) return null;
return decodeURIComponent(slugMatch[1]);
}
// [/DEF:resolveDashboardRef:Function]
// #endregion resolveDashboardRef:Function
// [DEF:openGitManagerForDashboard:Function]
// #region openGitManagerForDashboard:Function [TYPE Function]
// @PURPOSE: Opens Git manager for provided dashboard metadata.
function openGitManagerForDashboard(dashboard: DashboardMetadata | null): void {
if (!dashboard) return;
@@ -426,9 +425,9 @@
gitDashboardTitle = dashboard.title || "";
showGitManager = true;
}
// [/DEF:openGitManagerForDashboard:Function]
// #endregion openGitManagerForDashboard:Function
// [DEF:handleInitializeRepositories:Function]
// #region handleInitializeRepositories:Function [TYPE Function]
// @PURPOSE: Opens Git manager from bulk actions to initialize selected repository.
async function handleInitializeRepositories(): Promise<void> {
if (selectedIds.length !== 1) {
@@ -439,9 +438,9 @@
const selectedDashboard = dashboards.find((dashboard) => dashboard.id === selectedDashboardId) || null;
openGitManagerForDashboard(selectedDashboard);
}
// [/DEF:handleInitializeRepositories:Function]
// #endregion handleInitializeRepositories:Function
// [DEF:getSortStatusValue:Function]
// #region getSortStatusValue:Function [TYPE Function]
/**
* @purpose Returns sort value for status column based on mode.
*/
@@ -451,9 +450,9 @@
}
return String(dashboard.status || "").toLowerCase();
}
// [/DEF:getSortStatusValue:Function]
// #endregion getSortStatusValue:Function
// [DEF:getStatusLabel:Function]
// #region getStatusLabel:Function [TYPE Function]
/**
* @purpose Returns localized label for status column.
*/
@@ -464,9 +463,9 @@
const token = getRepositoryStatusToken(dashboard.id);
return $t.git?.repo_status?.[token] || token;
}
// [/DEF:getStatusLabel:Function]
// #endregion getStatusLabel:Function
// [DEF:getStatusBadgeClass:Function]
// #region getStatusBadgeClass:Function [TYPE Function]
/**
* @purpose Returns badge style for status column.
*/
@@ -487,7 +486,7 @@
if (token === "diverged") return "bg-purple-100 text-purple-800";
return "bg-rose-100 text-rose-800";
}
// [/DEF:getStatusBadgeClass:Function]
// #endregion getStatusBadgeClass:Function
$effect(() => {
dashboards;