subagents

This commit is contained in:
2026-03-20 17:20:24 +03:00
parent b89b9a66f2
commit 1149e8df1d
36 changed files with 4313 additions and 327 deletions

View File

@@ -196,6 +196,33 @@ async function postApi(endpoint, body) {
}
// [/DEF:postApi:Function]
// [DEF:deleteApi:Function]
// @PURPOSE: Generic DELETE request wrapper.
// @PRE: endpoint is provided.
// @POST: Returns Promise resolving to JSON data or throws on error.
// @PARAM: endpoint (string) - API endpoint.
// @RETURN: Promise<any> - JSON response.
async function deleteApi(endpoint) {
try {
console.log(`[api.deleteApi][Action] Deleting from context={{'endpoint': '${endpoint}'}}`);
const response = await fetch(`${API_BASE_URL}${endpoint}`, {
method: 'DELETE',
headers: getAuthHeaders(),
});
console.log(`[api.deleteApi][Action] Received response context={{'status': ${response.status}, 'ok': ${response.ok}}}`);
if (!response.ok) {
throw await buildApiError(response);
}
if (response.status === 204) return null;
return await response.json();
} catch (error) {
console.error(`[api.deleteApi][Coherence:Failed] Error deleting from ${endpoint}:`, error);
notifyApiError(error);
throw error;
}
}
// [/DEF:deleteApi:Function]
// [DEF:requestApi:Function]
// @PURPOSE: Generic request wrapper.
// @PRE: endpoint and method are provided.
@@ -237,6 +264,7 @@ async function requestApi(endpoint, method = 'GET', body = null) {
export const api = {
fetchApi,
postApi,
deleteApi,
requestApi,
getPlugins: () => fetchApi('/plugins'),
getTasks: (options = {}) => {

View File

@@ -91,11 +91,7 @@
return $t.dataset_review?.preview?.stale_body;
}
if (effectiveState === "failed") {
return (
preview?.error_details ||
preview?.error_code ||
$t.dataset_review?.preview?.error_body
);
return $t.dataset_review?.preview?.error_body;
}
if (effectiveState === "missing") {
return $t.dataset_review?.preview?.missing_body;
@@ -103,6 +99,12 @@
return $t.dataset_review?.preview?.ready_body;
}
function buildPreviewTechnicalDetails() {
return [preview?.error_code, preview?.error_details].filter(Boolean).join("\n\n");
}
const previewTechnicalDetails = $derived(buildPreviewTechnicalDetails());
async function requestPreview() {
if (!sessionId || disabled || localStatus === "saving") {
return;

View File

@@ -217,9 +217,18 @@
<div class="mt-4 space-y-3">
{#each launchBlockers as blocker}
<div class="rounded-xl border border-red-200 bg-white p-3">
<div class="text-sm font-medium text-slate-900">{blocker.label}</div>
<div class="text-sm font-medium leading-6 text-slate-900 break-words [overflow-wrap:anywhere]">
{blocker.label}
</div>
{#if blocker.detail}
<div class="mt-1 break-all text-xs text-slate-600">{blocker.detail}</div>
<div class="mt-2 max-h-28 overflow-auto rounded-lg border border-slate-200 bg-slate-50 px-3 py-2">
<div
data-testid="launch-blocker-detail"
class="text-xs leading-5 text-slate-600 whitespace-pre-wrap break-words [overflow-wrap:anywhere]"
>
{blocker.detail}
</div>
</div>
{/if}
<button
type="button"
@@ -242,7 +251,7 @@
<div class="text-xs uppercase tracking-wide text-slate-500">
{$t.dataset_review?.launch?.dataset_ref_label}
</div>
<div class="mt-1 text-sm font-medium text-slate-900">
<div class="mt-1 text-sm font-medium text-slate-900 break-words [overflow-wrap:anywhere]">
{session?.dataset_ref || ($t.common?.unknown || "unknown")}
</div>
</div>

View File

@@ -81,6 +81,18 @@
const normalized = String(action || "");
return $t.dataset_review?.workspace?.actions?.[normalized] || normalized;
}
function getFindingMessage(finding) {
return String(finding?.message || "").trim();
}
function getFindingTechnicalReference(finding) {
return String(finding?.caused_by_ref || "").trim();
}
function getFindingResolutionNote(finding) {
return String(finding?.resolution_note || "").trim();
}
</script>
<div class="rounded-2xl border border-slate-200 bg-white p-5 shadow-sm">

View File

@@ -654,6 +654,25 @@
"audit": "Audit"
}
},
"workspace_entry": {
"resume_eyebrow": "Resume session",
"resume_title": "Continue an existing review session",
"resume_description": "If a dataset review was already started, open the relevant session and continue from its latest saved state.",
"resume_available_badge": "Sessions available",
"resume_loading": "Loading existing sessions...",
"resume_load_failed": "Failed to load existing review sessions.",
"resume_empty_title": "No saved sessions yet",
"resume_empty_body": "Start a new review session from the source intake panel to make it available here for reopening later.",
"resume_action": "Resume",
"session_id_label": "Session ID",
"dataset_ref_label": "Dataset reference",
"environment_id_label": "Environment",
"status_label": "Status",
"readiness_label": "Readiness",
"phase_label": "Phase",
"updated_at_label": "Updated",
"last_activity_at_label": "Last activity"
},
"workspace": {
"eyebrow": "Dataset orchestration",
"title": "Dataset review workspace",

View File

@@ -652,6 +652,25 @@
"audit": "Аудит"
}
},
"workspace_entry": {
"resume_eyebrow": "Возобновление сессии",
"resume_title": "Продолжить существующую review-сессию",
"resume_description": "Если review уже была начата, откройте нужную сессию и продолжайте с последнего сохраненного состояния.",
"resume_available_badge": "Доступно сессий",
"resume_loading": "Загрузка доступных сессий...",
"resume_load_failed": "Не удалось загрузить существующие review-сессии.",
"resume_empty_title": "Сохраненных сессий пока нет",
"resume_empty_body": "Запустите новую review-сессию через блок источника справа, чтобы она появилась здесь для повторного открытия.",
"resume_action": "Возобновить",
"session_id_label": "ID сессии",
"dataset_ref_label": "Ссылка на датасет",
"environment_id_label": "Окружение",
"status_label": "Статус",
"readiness_label": "Готовность",
"phase_label": "Фаза",
"updated_at_label": "Обновлено",
"last_activity_at_label": "Последняя активность"
},
"workspace": {
"eyebrow": "Оркестрация датасета",
"title": "Workspace review датасета",