From 85cf1c4c1f6d7a092a0bebea3b390b8ac578a417 Mon Sep 17 00:00:00 2001 From: Prashant Vasudevan <71649489+vprashrex@users.noreply.github.com> Date: Tue, 15 Sep 2026 12:42:31 +0530 Subject: [PATCH 01/11] feat(assessment): rebuild module on BATCH API (UI/UX v2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the tabbed Datasets/Config/Result surface with the approved v2 design: a two-panel Home (assessors | runs) and one 4-step wizard (Submission → Pre-filter → Assessment → Run) with new / edit-version / run entry modes. - prompt editor: two zones per step, token highlighting, two-stage @-mentions (column → Text/Image/PDF), live preview, split pane, anchored scroll sync - per-step model picker; Review & save bumps a version, gated on a real diff - results: row-detail modal, CSV export, polling until terminal status - moves onto /api/assessment/batch; fetchers split into lib/assessment/api/*, configBlob maps the wizard draft to the stored ASSESSMENT config blob - column mapping dropped — text and attachment columns come from prompt tokens - retires the legacy wizard and the 1-4 config fan-out (~6.7k LOC removed) Refs #263 --- .../assessment/results/[runId]/page.tsx | 136 +++--- .../assessment/batch/[assessment_id]/route.ts | 23 + app/api/assessment/batch/route.ts | 35 ++ .../assessment/AssessmentChildRunCard.tsx | 171 -------- app/components/assessment/ChildRunActions.tsx | 98 ----- .../assessment/ColumnMapperStep.tsx | 401 ----------------- app/components/assessment/ConfigPanel.tsx | 160 ------- app/components/assessment/DatasetsTab.tsx | 147 ------- .../assessment/DownloadDropdown.tsx | 111 ----- app/components/assessment/EvaluationsTab.tsx | 265 ----------- .../assessment/OutputSchemaEditor.tsx | 37 -- .../assessment/OutputSchemaStep.tsx | 44 -- app/components/assessment/PageLayout.tsx | 56 +-- .../assessment/PostProcessingStep.tsx | 8 +- app/components/assessment/PrefilterStep.tsx | 311 ------------- .../assessment/PromptAndConfigStep.tsx | 178 -------- app/components/assessment/ReviewStep.tsx | 154 ------- app/components/assessment/SpreadsheetView.tsx | 16 +- app/components/assessment/Stepper.tsx | 129 ++++-- .../assessment/datasets/CreatePanel.tsx | 149 ++++--- .../assessment/datasets/DatasetList.tsx | 167 ------- .../assessment/datasets/SubmissionList.tsx | 155 +++++++ .../assessment/editor/EditorStepLayout.tsx | 64 +++ .../assessment/editor/MentionDropdown.tsx | 172 ++++++++ .../assessment/editor/PreviewPane.tsx | 152 +++++++ .../assessment/editor/PromptZoneCard.tsx | 74 ++++ .../assessment/editor/PromptZoneEditor.tsx | 217 +++++++++ .../assessment/home/AssessorRow.tsx | 110 +++++ .../assessment/home/AssessorVersionChips.tsx | 73 +++ .../assessment/home/AssessorVersionList.tsx | 75 ++++ .../assessment/home/AssessorsPanel.tsx | 127 ++++++ .../assessment/home/DeleteAssessorDialog.tsx | 70 +++ app/components/assessment/home/HomePanel.tsx | 69 +++ app/components/assessment/home/HomeView.tsx | 91 ++++ app/components/assessment/home/RunRow.tsx | 63 +++ .../assessment/home/RunRowActions.tsx | 64 +++ app/components/assessment/home/RunRowMeta.tsx | 36 ++ app/components/assessment/home/RunsPanel.tsx | 76 ++++ app/components/assessment/model/ModelChip.tsx | 79 ++++ .../assessment/model/ModelPicker.tsx | 102 +++++ .../output-schema/OutputSchemaModal.tsx | 101 ----- .../prompt-config/AssessmentConfiguration.tsx | 120 ----- .../prompt-config/ConfigCreator.tsx | 115 ----- .../assessment/prompt-config/PromptEditor.tsx | 157 ------- .../assessment/prompt-config/PromptPanel.tsx | 50 --- .../prompt-config/ResponseSchema.tsx | 47 -- .../prompt-config/SavedConfigCard.tsx | 286 ------------ .../assessment/prompt-config/SavedConfigs.tsx | 156 ------- .../prompt-config/SelectedConfigs.tsx | 53 --- .../prompt-config/SetupProgress.tsx | 37 -- .../assessment/prompt-config/SystemPrompt.tsx | 63 --- .../assessment/prompt-config/UserPrompt.tsx | 74 ---- .../assessment/prompt-config/index.ts | 14 - .../assessment/results/ResultRowModal.tsx | 94 ++++ .../assessment/results/ResultsHeader.tsx | 73 --- .../assessment/results/ResultsTable.tsx | 78 ++++ .../assessment/results/ResultsToolbar.tsx | 69 +++ .../assessment/review/ColumnsReview.tsx | 52 --- .../assessment/review/ConfigsReview.tsx | 52 --- .../assessment/review/DatasetReview.tsx | 28 -- .../assessment/review/ExperimentReview.tsx | 29 -- .../assessment/review/InputReview.tsx | 165 ------- .../review/PostProcessingReview.tsx | 104 ----- .../assessment/review/PrefilterReview.tsx | 116 ----- .../assessment/review/ReviewSection.tsx | 84 ---- .../assessment/review/SchemaReview.tsx | 52 --- .../assessment/review/SubmitReview.tsx | 64 --- .../assessment/wizard/AssessmentStep.tsx | 89 ++++ .../assessment/wizard/PrefilterStep.tsx | 104 +++++ .../assessment/wizard/ReviewSaveModal.tsx | 181 ++++++++ app/components/assessment/wizard/RunStep.tsx | 130 ++++++ .../assessment/wizard/SavedNextModal.tsx | 48 ++ .../assessment/wizard/SubmissionStep.tsx | 97 ++++ .../assessment/wizard/WizardFooter.tsx | 49 +++ .../assessment/wizard/WizardStepBody.tsx | 63 +++ .../assessment/wizard/WizardView.tsx | 120 +++++ .../assessment/wizard/wizardCopy.ts | 121 +++++ .../datasets/UploadDatasetModal.tsx | 2 +- .../document/UploadDocumentModal.tsx | 2 +- app/components/icons/common/HomeIcon.tsx | 23 + app/components/icons/common/ImageIcon.tsx | 25 ++ .../icons/common/LockClosedIcon.tsx | 20 + .../icons/document/CloudUploadIcon.tsx | 2 +- app/components/icons/index.tsx | 3 + app/components/ui/CursorPager.tsx | 55 +++ app/components/ui/Pager.tsx | 72 +++ app/components/ui/Select.tsx | 3 +- app/components/ui/SplitPane.tsx | 95 ++++ app/components/ui/index.ts | 3 + app/hooks/index.ts | 3 + app/hooks/useAssessmentData.ts | 17 + app/hooks/useAssessmentDatasetsTab.ts | 361 --------------- app/hooks/useAssessmentDownload.ts | 94 ---- app/hooks/useAssessmentFeatureGuard.ts | 40 ++ app/hooks/useAssessmentHome.ts | 163 +++++++ app/hooks/useAssessmentHomeData.ts | 251 +++++++++++ app/hooks/useAssessmentResults.ts | 331 -------------- app/hooks/useAssessmentWizard.ts | 176 ++++++++ app/hooks/useAssessmentWorkflow.ts | 311 ++----------- app/hooks/useAssessorSave.ts | 67 +++ app/hooks/useAssessorVersionContext.ts | 111 +++++ app/hooks/useLatestConfigModels.ts | 60 --- app/hooks/usePromptAndConfigStep.ts | 414 ------------------ app/hooks/usePromptMentions.ts | 334 ++++++++++++++ app/hooks/usePromptPlaceholderEditor.ts | 292 ------------ app/hooks/useRunNameDraft.ts | 38 ++ app/hooks/useRunResults.ts | 132 ++++++ app/hooks/useRunSubmit.ts | 52 +++ app/hooks/useSubmissionForm.ts | 98 +++++ app/hooks/useSubmissionList.ts | 64 +++ app/hooks/useSubmissionStep.ts | 185 ++++++++ app/hooks/useSyncedScroll.ts | 275 ++++++++++++ app/hooks/useVersionSaveFlow.ts | 118 +++++ app/hooks/useWizardDraft.ts | 126 ++++++ app/lib/assessment/api/assessors.ts | 147 +++++++ app/lib/assessment/api/runs.ts | 84 ++++ app/lib/assessment/api/submissions.ts | 71 +++ app/lib/assessment/apiSource.ts | 34 ++ app/lib/assessment/batchResults.ts | 116 +++++ app/lib/assessment/configBlob.ts | 240 ++++++++++ app/lib/assessment/constants.ts | 192 ++------ app/lib/assessment/draft.ts | 216 +++++++++ app/lib/assessment/home.ts | 117 +++++ app/lib/assessment/promptCopy.ts | 35 ++ app/lib/assessment/promptTokens.ts | 197 +++++++++ app/lib/assessment/resultRow.ts | 107 +++++ app/lib/assessment/results.ts | 134 +++--- app/lib/assessment/scrollMap.ts | 118 +++++ app/lib/assessment/wizard.ts | 105 +++++ app/lib/types/assessment/batch.ts | 96 ++++ app/lib/types/assessment/core.ts | 10 +- app/lib/types/assessment/dataSource.ts | 113 +++++ app/lib/types/assessment/dataset.ts | 6 +- app/lib/types/assessment/home.ts | 61 +++ app/lib/types/assessment/index.ts | 6 + app/lib/types/assessment/prompt.ts | 79 ++++ app/lib/types/assessment/results.ts | 30 +- app/lib/types/assessment/submission.ts | 38 ++ app/lib/types/assessment/wizard.ts | 53 +++ app/lib/types/assessment/workflow.ts | 26 +- app/lib/utils/assessment.ts | 89 +++- app/lib/utils/assessmentFetcher.ts | 10 +- 142 files changed, 8229 insertions(+), 6719 deletions(-) create mode 100644 app/api/assessment/batch/[assessment_id]/route.ts create mode 100644 app/api/assessment/batch/route.ts delete mode 100644 app/components/assessment/AssessmentChildRunCard.tsx delete mode 100644 app/components/assessment/ChildRunActions.tsx delete mode 100644 app/components/assessment/ColumnMapperStep.tsx delete mode 100644 app/components/assessment/ConfigPanel.tsx delete mode 100644 app/components/assessment/DatasetsTab.tsx delete mode 100644 app/components/assessment/DownloadDropdown.tsx delete mode 100644 app/components/assessment/EvaluationsTab.tsx delete mode 100644 app/components/assessment/OutputSchemaEditor.tsx delete mode 100644 app/components/assessment/OutputSchemaStep.tsx delete mode 100644 app/components/assessment/PrefilterStep.tsx delete mode 100644 app/components/assessment/PromptAndConfigStep.tsx delete mode 100644 app/components/assessment/ReviewStep.tsx delete mode 100644 app/components/assessment/datasets/DatasetList.tsx create mode 100644 app/components/assessment/datasets/SubmissionList.tsx create mode 100644 app/components/assessment/editor/EditorStepLayout.tsx create mode 100644 app/components/assessment/editor/MentionDropdown.tsx create mode 100644 app/components/assessment/editor/PreviewPane.tsx create mode 100644 app/components/assessment/editor/PromptZoneCard.tsx create mode 100644 app/components/assessment/editor/PromptZoneEditor.tsx create mode 100644 app/components/assessment/home/AssessorRow.tsx create mode 100644 app/components/assessment/home/AssessorVersionChips.tsx create mode 100644 app/components/assessment/home/AssessorVersionList.tsx create mode 100644 app/components/assessment/home/AssessorsPanel.tsx create mode 100644 app/components/assessment/home/DeleteAssessorDialog.tsx create mode 100644 app/components/assessment/home/HomePanel.tsx create mode 100644 app/components/assessment/home/HomeView.tsx create mode 100644 app/components/assessment/home/RunRow.tsx create mode 100644 app/components/assessment/home/RunRowActions.tsx create mode 100644 app/components/assessment/home/RunRowMeta.tsx create mode 100644 app/components/assessment/home/RunsPanel.tsx create mode 100644 app/components/assessment/model/ModelChip.tsx create mode 100644 app/components/assessment/model/ModelPicker.tsx delete mode 100644 app/components/assessment/output-schema/OutputSchemaModal.tsx delete mode 100644 app/components/assessment/prompt-config/AssessmentConfiguration.tsx delete mode 100644 app/components/assessment/prompt-config/ConfigCreator.tsx delete mode 100644 app/components/assessment/prompt-config/PromptEditor.tsx delete mode 100644 app/components/assessment/prompt-config/PromptPanel.tsx delete mode 100644 app/components/assessment/prompt-config/ResponseSchema.tsx delete mode 100644 app/components/assessment/prompt-config/SavedConfigCard.tsx delete mode 100644 app/components/assessment/prompt-config/SavedConfigs.tsx delete mode 100644 app/components/assessment/prompt-config/SelectedConfigs.tsx delete mode 100644 app/components/assessment/prompt-config/SetupProgress.tsx delete mode 100644 app/components/assessment/prompt-config/SystemPrompt.tsx delete mode 100644 app/components/assessment/prompt-config/UserPrompt.tsx delete mode 100644 app/components/assessment/prompt-config/index.ts create mode 100644 app/components/assessment/results/ResultRowModal.tsx delete mode 100644 app/components/assessment/results/ResultsHeader.tsx create mode 100644 app/components/assessment/results/ResultsTable.tsx create mode 100644 app/components/assessment/results/ResultsToolbar.tsx delete mode 100644 app/components/assessment/review/ColumnsReview.tsx delete mode 100644 app/components/assessment/review/ConfigsReview.tsx delete mode 100644 app/components/assessment/review/DatasetReview.tsx delete mode 100644 app/components/assessment/review/ExperimentReview.tsx delete mode 100644 app/components/assessment/review/InputReview.tsx delete mode 100644 app/components/assessment/review/PostProcessingReview.tsx delete mode 100644 app/components/assessment/review/PrefilterReview.tsx delete mode 100644 app/components/assessment/review/ReviewSection.tsx delete mode 100644 app/components/assessment/review/SchemaReview.tsx delete mode 100644 app/components/assessment/review/SubmitReview.tsx create mode 100644 app/components/assessment/wizard/AssessmentStep.tsx create mode 100644 app/components/assessment/wizard/PrefilterStep.tsx create mode 100644 app/components/assessment/wizard/ReviewSaveModal.tsx create mode 100644 app/components/assessment/wizard/RunStep.tsx create mode 100644 app/components/assessment/wizard/SavedNextModal.tsx create mode 100644 app/components/assessment/wizard/SubmissionStep.tsx create mode 100644 app/components/assessment/wizard/WizardFooter.tsx create mode 100644 app/components/assessment/wizard/WizardStepBody.tsx create mode 100644 app/components/assessment/wizard/WizardView.tsx create mode 100644 app/components/assessment/wizard/wizardCopy.ts create mode 100644 app/components/icons/common/HomeIcon.tsx create mode 100644 app/components/icons/common/ImageIcon.tsx create mode 100644 app/components/icons/common/LockClosedIcon.tsx create mode 100644 app/components/ui/CursorPager.tsx create mode 100644 app/components/ui/Pager.tsx create mode 100644 app/components/ui/SplitPane.tsx create mode 100644 app/hooks/useAssessmentData.ts delete mode 100644 app/hooks/useAssessmentDatasetsTab.ts delete mode 100644 app/hooks/useAssessmentDownload.ts create mode 100644 app/hooks/useAssessmentFeatureGuard.ts create mode 100644 app/hooks/useAssessmentHome.ts create mode 100644 app/hooks/useAssessmentHomeData.ts delete mode 100644 app/hooks/useAssessmentResults.ts create mode 100644 app/hooks/useAssessmentWizard.ts create mode 100644 app/hooks/useAssessorSave.ts create mode 100644 app/hooks/useAssessorVersionContext.ts delete mode 100644 app/hooks/useLatestConfigModels.ts delete mode 100644 app/hooks/usePromptAndConfigStep.ts create mode 100644 app/hooks/usePromptMentions.ts delete mode 100644 app/hooks/usePromptPlaceholderEditor.ts create mode 100644 app/hooks/useRunNameDraft.ts create mode 100644 app/hooks/useRunResults.ts create mode 100644 app/hooks/useRunSubmit.ts create mode 100644 app/hooks/useSubmissionForm.ts create mode 100644 app/hooks/useSubmissionList.ts create mode 100644 app/hooks/useSubmissionStep.ts create mode 100644 app/hooks/useSyncedScroll.ts create mode 100644 app/hooks/useVersionSaveFlow.ts create mode 100644 app/hooks/useWizardDraft.ts create mode 100644 app/lib/assessment/api/assessors.ts create mode 100644 app/lib/assessment/api/runs.ts create mode 100644 app/lib/assessment/api/submissions.ts create mode 100644 app/lib/assessment/apiSource.ts create mode 100644 app/lib/assessment/batchResults.ts create mode 100644 app/lib/assessment/configBlob.ts create mode 100644 app/lib/assessment/draft.ts create mode 100644 app/lib/assessment/home.ts create mode 100644 app/lib/assessment/promptCopy.ts create mode 100644 app/lib/assessment/promptTokens.ts create mode 100644 app/lib/assessment/resultRow.ts create mode 100644 app/lib/assessment/scrollMap.ts create mode 100644 app/lib/assessment/wizard.ts create mode 100644 app/lib/types/assessment/batch.ts create mode 100644 app/lib/types/assessment/dataSource.ts create mode 100644 app/lib/types/assessment/home.ts create mode 100644 app/lib/types/assessment/prompt.ts create mode 100644 app/lib/types/assessment/submission.ts create mode 100644 app/lib/types/assessment/wizard.ts diff --git a/app/(main)/assessment/results/[runId]/page.tsx b/app/(main)/assessment/results/[runId]/page.tsx index 12242bc2..25f992f7 100644 --- a/app/(main)/assessment/results/[runId]/page.tsx +++ b/app/(main)/assessment/results/[runId]/page.tsx @@ -1,21 +1,27 @@ "use client"; -import { useEffect, useState } from "react"; -import { useParams, useSearchParams } from "next/navigation"; +import { useState } from "react"; +import { useParams, useRouter, useSearchParams } from "next/navigation"; import dynamic from "next/dynamic"; import { Loader } from "@/app/components/ui"; -import { useToast } from "@/app/hooks/useToast"; -import { useAuth } from "@/app/lib/context/AuthContext"; -import { apiFetch } from "@/app/lib/apiClient"; -import { jsonResultsToTableData } from "@/app/lib/assessment/results"; -import { SPREADSHEET_PREVIEW_ROW_LIMIT } from "@/app/lib/assessment/constants"; +import ResultRowModal from "@/app/components/assessment/results/ResultRowModal"; +import ResultsTable from "@/app/components/assessment/results/ResultsTable"; +import ResultsToolbar, { + type ResultsViewMode, +} from "@/app/components/assessment/results/ResultsToolbar"; +import { useRunResults } from "@/app/hooks/useRunResults"; +import type { AssessmentMethodValue } from "@/app/lib/types/assessment"; +import { + downloadCsv, + jsonResultsToTableData, +} from "@/app/lib/assessment/results"; const SpreadsheetView = dynamic( () => import("@/app/components/assessment/SpreadsheetView"), { ssr: false, loading: () => ( -
+
), @@ -25,85 +31,71 @@ const SpreadsheetView = dynamic( export default function AssessmentResultsPage() { const params = useParams<{ runId: string }>(); const searchParams = useSearchParams(); - const toast = useToast(); - const { apiKeys, isAuthenticated, isHydrated } = useAuth(); - const apiKey = apiKeys[0]?.key ?? ""; + const router = useRouter(); + const [view, setView] = useState("table"); + const [openRow, setOpenRow] = useState(null); - const [headers, setHeaders] = useState(null); - const [rows, setRows] = useState(null); - const [error, setError] = useState(null); - - const runId = Number(params?.runId); - const title = searchParams.get("title") ?? `Run ${runId}`; - - useEffect(() => { - if (!isHydrated) return; - if (!isAuthenticated) { - setError("You must be signed in to view this run."); - return; - } - if (!Number.isFinite(runId) || runId <= 0) { - setError("Invalid run id."); - return; - } - - let cancelled = false; - (async () => { - try { - const json = await apiFetch< - { data?: Record[] } | Record[] - >(`/api/assessment/runs/${runId}/results?export_format=json`, apiKey); - const results: Record[] = Array.isArray(json) - ? json - : json.data || []; - const table = jsonResultsToTableData(results, { - rowLimit: SPREADSHEET_PREVIEW_ROW_LIMIT, - }); - if (cancelled) return; - if (results.length > SPREADSHEET_PREVIEW_ROW_LIMIT) { - toast.warning( - `Preview capped at ${SPREADSHEET_PREVIEW_ROW_LIMIT} rows. Download CSV for full data.`, - ); - } - setHeaders(table.headers); - setRows(table.rows); - } catch (err) { - if (cancelled) return; - const msg = - err instanceof Error ? err.message : "Failed to load results"; - setError(msg); - toast.error(msg); - } - })(); - - return () => { - cancelled = true; - }; - }, [apiKey, isAuthenticated, isHydrated, runId, toast]); + const assessmentId = params?.runId ?? ""; + const method = + (searchParams.get("method") as AssessmentMethodValue | null) ?? "BATCH"; + const title = searchParams.get("title") ?? "Run results"; + const { results, headers, rows, isLoading, error } = useRunResults( + assessmentId ? { assessment_id: assessmentId, method } : null, + ); if (error) { return ( -
+

{error}

); } - if (!headers || !rows) { + if (isLoading) { return ( -
+
); } return ( - +
+ router.push("/assessment")} + onDownload={() => { + // The table is capped for rendering; the CSV carries every row. + const full = jsonResultsToTableData(results); + downloadCsv(title, [full.headers, ...full.rows]); + }} + /> + + {view === "table" ? ( + <> +

+ Click any row to open the full assessment — scores, reasons and the + feedback in the submitter’s language. +

+ + + ) : ( + + )} + + setOpenRow(null)} + /> +
); } diff --git a/app/api/assessment/batch/[assessment_id]/route.ts b/app/api/assessment/batch/[assessment_id]/route.ts new file mode 100644 index 00000000..246e16e4 --- /dev/null +++ b/app/api/assessment/batch/[assessment_id]/route.ts @@ -0,0 +1,23 @@ +import { NextRequest } from "next/server"; +import { proxyErrorResponse, proxyJsonResponse } from "@/app/api/_routeProxy"; +import type { RouteContext } from "@/app/lib/types/assessment"; + +/** + * GET /api/assessment/batch/[assessment_id] — status plus every row so far. + * Safe to poll; stops being worth polling once the status is terminal. + */ +export async function GET( + request: NextRequest, + context: RouteContext<"assessment_id">, +) { + try { + const { assessment_id } = await context.params; + return await proxyJsonResponse( + request, + `/api/v1/assessments/${assessment_id}`, + { method: "GET" }, + ); + } catch (error: unknown) { + return proxyErrorResponse("Assessment batch detail proxy error:", error); + } +} diff --git a/app/api/assessment/batch/route.ts b/app/api/assessment/batch/route.ts new file mode 100644 index 00000000..be5131f1 --- /dev/null +++ b/app/api/assessment/batch/route.ts @@ -0,0 +1,35 @@ +import { NextRequest } from "next/server"; +import { + proxyErrorResponse, + proxyJsonResponse, + withQueryParams, +} from "@/app/api/_routeProxy"; + +const BATCH_ENDPOINT = "/api/v1/assessments"; + +/** GET /api/assessment/batch — list assessments, optionally by config + version. */ +export async function GET(request: NextRequest) { + try { + const queryParams = new URLSearchParams(request.nextUrl.searchParams); + return await proxyJsonResponse( + request, + withQueryParams(BATCH_ENDPOINT, queryParams), + { method: "GET" }, + ); + } catch (error: unknown) { + return proxyErrorResponse("Assessment batch list proxy error:", error); + } +} + +/** POST /api/assessment/batch — submit a batch assessment run. */ +export async function POST(request: NextRequest) { + try { + const body = await request.json(); + return await proxyJsonResponse(request, BATCH_ENDPOINT, { + method: "POST", + body: JSON.stringify(body), + }); + } catch (error: unknown) { + return proxyErrorResponse("Assessment batch create proxy error:", error); + } +} diff --git a/app/components/assessment/AssessmentChildRunCard.tsx b/app/components/assessment/AssessmentChildRunCard.tsx deleted file mode 100644 index 2faa07d1..00000000 --- a/app/components/assessment/AssessmentChildRunCard.tsx +++ /dev/null @@ -1,171 +0,0 @@ -"use client"; - -import { useToast } from "@/app/hooks/useToast"; -import PostProcessingPanel from "./PostProcessingPanel"; -import ChildRunStageProgress from "./ChildRunStageProgress"; -import ChildRunActions from "./ChildRunActions"; -import { - getStageProgress, - isCompletedStatus, - isFailedStatus, -} from "@/app/lib/assessment/results"; -import { formatRelativeTime } from "@/app/lib/utils"; -import type { - AssessmentChildRun, - ConfigRunDetail, - ExportFormat, - PostProcessingConfig, -} from "@/app/lib/types/assessment"; - -interface AssessmentChildRunCardProps { - childRun: AssessmentChildRun; - configDetailsByKey: Record; - configLoadingKeys: Record; - configErrorKeys: Record; - rerunningId: number | null; - resumingId: number | null; - downloadingId: string | null; - onPreview: (runId: number, label: string) => void; - onRunDownload: (runId: number, format: ExportFormat) => void; - onResume: (run: AssessmentChildRun) => void; - onRerun: (run: AssessmentChildRun) => void; - onFetchRunColumns: (runId: number) => Promise; - onSavePostProcessing: ( - runId: number, - config: PostProcessingConfig | null, - ) => Promise; -} - -export default function AssessmentChildRunCard({ - childRun, - configDetailsByKey, - configLoadingKeys, - configErrorKeys, - rerunningId, - resumingId, - downloadingId, - onPreview, - onRunDownload, - onResume, - onRerun, - onFetchRunColumns, - onSavePostProcessing, -}: AssessmentChildRunCardProps) { - const toast = useToast(); - - const isFailedChild = isFailedStatus(childRun.status); - const isCompletedChild = isCompletedStatus(childRun.status); - const stageProgress = getStageProgress(childRun); - const isRerunning = rerunningId === childRun.id; - const isResuming = resumingId === childRun.id; - const configKey = - childRun.config_id && childRun.config_version - ? `${childRun.config_id}:${childRun.config_version}` - : null; - const configDetail = configKey ? configDetailsByKey[configKey] : null; - const isConfigLoading = configKey - ? Boolean(configLoadingKeys[configKey]) - : false; - const configError = configKey ? configErrorKeys[configKey] : null; - const fallbackName = childRun.config_id - ? `Config ${childRun.config_id.slice(0, 8)}` - : "Configuration"; - const configName = configDetail?.name || fallbackName; - const previewLabel = `${configName}${childRun.config_version ? ` v${childRun.config_version}` : ""}`; - - return ( -
-
-
-
-
- - {configName} - - {childRun.config_version !== null && ( - - v{childRun.config_version} - - )} - {configDetail?.provider && configDetail?.model && ( - - {configDetail.provider}/{configDetail.model} - - )} -
- -
- {isConfigLoading - ? "Loading configuration details..." - : configDetail?.description || - configDetail?.commitMessage || - "No description available for this configuration."} -
- -
- {childRun.total_items} items - {childRun.updated_at && ( - {formatRelativeTime(childRun.updated_at)} - )} - {childRun.config_id && ( - - ID {childRun.config_id.slice(0, 8)} - - )} -
- {childRun.prefilter_total_rows != null && ( -
- Prefilter: {childRun.prefilter_total_passed ?? 0}/ - {childRun.prefilter_total_rows} passed - {childRun.prefilter_total_rejected != null && - childRun.prefilter_total_rejected > 0 && ( - - · {childRun.prefilter_total_rejected} rejected - - )} -
- )} - - - - {configError && ( -
- {configError} -
- )} - {isFailedChild && childRun.error_message && ( -
- {childRun.error_message} -
- )} -
- - -
-
- {isCompletedChild && ( - onFetchRunColumns(childRun.id)} - onSave={async (cfg: PostProcessingConfig) => { - await onSavePostProcessing(childRun.id, cfg); - toast.success( - "Post-processing saved. Re-open preview to see updated results.", - ); - }} - /> - )} -
- ); -} diff --git a/app/components/assessment/ChildRunActions.tsx b/app/components/assessment/ChildRunActions.tsx deleted file mode 100644 index 53289a49..00000000 --- a/app/components/assessment/ChildRunActions.tsx +++ /dev/null @@ -1,98 +0,0 @@ -"use client"; - -import { Button } from "@/app/components/ui"; -import { EyeIcon } from "@/app/components/icons"; -import DownloadDropdown from "./DownloadDropdown"; -import { - formatStatusLabel, - getResultTone, - hasViewableResults, - isCompletedStatus, - isFailedStatus, -} from "@/app/lib/assessment/results"; -import { STATUS_BADGE_CLASSES } from "@/app/lib/assessment/constants"; -import type { - AssessmentChildRun, - ExportFormat, -} from "@/app/lib/types/assessment"; - -interface ChildRunActionsProps { - childRun: AssessmentChildRun; - previewLabel: string; - isResuming: boolean; - isRerunning: boolean; - downloadingId: string | null; - onPreview: (runId: number, label: string) => void; - onRunDownload: (runId: number, format: ExportFormat) => void; - onResume: (run: AssessmentChildRun) => void; - onRerun: (run: AssessmentChildRun) => void; -} - -export default function ChildRunActions({ - childRun, - previewLabel, - isResuming, - isRerunning, - downloadingId, - onPreview, - onRunDownload, - onResume, - onRerun, -}: ChildRunActionsProps) { - const childStatusClass = STATUS_BADGE_CLASSES[getResultTone(childRun.status)]; - const isFailedChild = isFailedStatus(childRun.status); - const isCompletedChild = isCompletedStatus(childRun.status); - const canPreview = hasViewableResults(childRun); - const canResume = Boolean(childRun.stage); - - return ( -
- - {formatStatusLabel(childRun.status)} - - {canPreview && ( - - )} - {isCompletedChild && ( - onRunDownload(childRun.id, fmt)} - loading={downloadingId === `run-${childRun.id}`} - /> - )} - {isFailedChild && canResume && ( - - )} - {isFailedChild && ( - - )} -
- ); -} diff --git a/app/components/assessment/ColumnMapperStep.tsx b/app/components/assessment/ColumnMapperStep.tsx deleted file mode 100644 index fd29e869..00000000 --- a/app/components/assessment/ColumnMapperStep.tsx +++ /dev/null @@ -1,401 +0,0 @@ -"use client"; - -import { useEffect, useState } from "react"; -import { Button, Select } from "@/app/components/ui"; -import { - ASSESSMENT_ROLE_OPTION_MAP, - ASSESSMENT_ROLE_OPTIONS, - ATTACHMENT_FORMATS, -} from "@/app/lib/assessment/constants"; -import type { - Attachment, - ColumnConfig, - ColumnRole, - ColumnMapperStepProps, -} from "@/app/lib/types/assessment"; -import { buildColumnConfigs, colorMapping } from "@/app/lib/utils/assessment"; - -export default function ColumnMapperStep({ - columns, - columnMapping, - setColumnMapping, - onNext, - onBack, -}: ColumnMapperStepProps) { - const [columnConfigs, setColumnConfigs] = useState(() => - buildColumnConfigs(columns, columnMapping), - ); - - useEffect(() => { - setColumnConfigs(buildColumnConfigs(columns, columnMapping)); - }, [columns, columnMapping]); - - const updateRole = (index: number, role: ColumnRole) => { - if (role === "ground_truth") { - return; - } - - setColumnConfigs((prev) => { - const current = prev[index]; - const next = [...prev]; - - if (role !== "attachment") { - next[index] = { role }; - return next; - } - - next[index] = { - role, - attachmentType: current?.attachmentType || "mixed", - attachmentFormat: current?.attachmentFormat || "url", - }; - return next; - }); - }; - - const updateAttachmentType = ( - index: number, - type: "image" | "pdf" | "mixed", - ) => { - setColumnConfigs((prev) => { - const next = [...prev]; - next[index] = { - ...prev[index], - role: "attachment", - attachmentType: type, - attachmentFormat: "url", - }; - return next; - }); - }; - - const updateAttachmentFormat = (index: number, format: string) => { - setColumnConfigs((prev) => { - const next = [...prev]; - next[index] = { - ...prev[index], - role: "attachment", - attachmentFormat: format, - }; - return next; - }); - }; - - const patchAttachment = (index: number, patch: Partial) => { - setColumnConfigs((prev) => { - const next = [...prev]; - next[index] = { ...prev[index], role: "attachment", ...patch }; - return next; - }); - }; - - const handleNext = () => { - const textColumns: string[] = []; - const attachments: Attachment[] = []; - - columnConfigs.forEach((config, index) => { - const column = columns[index]; - if (!column) return; - - if (config.role === "text") { - textColumns.push(column); - } else if ( - config.role === "attachment" && - config.attachmentType && - config.attachmentFormat - ) { - const attachment: Attachment = { - column, - type: config.attachmentType, - format: config.attachmentFormat as Attachment["format"], - }; - if (config.attachmentType === "mixed" && config.attachmentTypeColumn) { - const map: Record = {}; - const split = (s?: string) => - (s || "") - .split(",") - .map((v) => v.trim()) - .filter(Boolean); - split(config.attachmentImageValues).forEach( - (v) => (map[v] = "image"), - ); - split(config.attachmentPdfValues).forEach((v) => (map[v] = "pdf")); - if (Object.keys(map).length > 0) { - attachment.type_column = config.attachmentTypeColumn; - attachment.type_value_map = map; - } - } - attachments.push(attachment); - } - }); - - setColumnMapping({ textColumns, attachments, groundTruthColumns: [] }); - onNext(); - }; - - const mappedCount = columnConfigs.filter( - (config) => config.role !== "unmapped", - ).length; - const hasMappedColumn = columnConfigs.some( - (config) => config.role === "text" || config.role === "attachment", - ); - - return ( -
-
-
-
-

- Map Columns -

-

- Choose a role for each column. -

-
-
- {mappedCount}/{columns.length} mapped -
-
- - {columns.length === 0 ? ( -
-

- No columns found. -

-

- Go back and select a dataset first. -

-
- ) : ( -
- {columns.map((column, index) => { - const config = columnConfigs[index] || { - role: "unmapped" as ColumnRole, - }; - const activeOption = - ASSESSMENT_ROLE_OPTION_MAP[config.role] || - ASSESSMENT_ROLE_OPTION_MAP.unmapped; - const roleVisuals = colorMapping(activeOption.value); - - return ( -
-
-
-
-
- - - {column} - -
-
- -
- {ASSESSMENT_ROLE_OPTIONS.map((option) => { - const isGroundTruth = option.value === "ground_truth"; - const isActive = config.role === option.value; - return ( - - ); - })} -
-
- - {config.role === "attachment" && ( - <> -
- -
- - {(config.attachmentType || "mixed") === "mixed" && ( -
- - Mixed: pick a column whose value tells each - row's type, then list which values mean image - vs PDF. - - - -
- )} -
- )} - - )} -
-
- ); - })} -
- )} -
- -
-
- - -
- - {hasMappedColumn - ? "Ready to continue." - : "Map at least one Text or Attachment column."} - - -
-
-
-
- ); -} diff --git a/app/components/assessment/ConfigPanel.tsx b/app/components/assessment/ConfigPanel.tsx deleted file mode 100644 index ad3edabe..00000000 --- a/app/components/assessment/ConfigPanel.tsx +++ /dev/null @@ -1,160 +0,0 @@ -"use client"; - -import { Button } from "@/app/components/ui"; -import { DatabaseIcon } from "@/app/components/icons"; -import { ASSESSMENT_CONFIG_STEPS } from "@/app/lib/assessment/constants"; -import type { ConfigPanelProps } from "@/app/lib/types/assessment"; -import ColumnMapperStep from "./ColumnMapperStep"; -import PrefilterStep from "./PrefilterStep"; -import PostProcessingStep from "./PostProcessingStep"; -import PromptAndConfigStep from "./PromptAndConfigStep"; -import ReviewStep from "./ReviewStep"; -import Stepper from "./Stepper"; - -export default function ConfigPanel({ - canSubmitAssessment, - columns, - columnMapping, - completedSteps, - configStep, - configs, - datasetId, - experimentName, - formState, - hasDataset, - isSubmitting, - prefilterConfig, - outputSchema, - systemInstruction, - promptTemplate, - postProcessingConfig, - setPostProcessingConfig, - sampleRow, - setActiveTabToDatasets, - setColumnMapping, - setConfigStep, - setConfigs, - setExperimentName, - setPrefilterConfig, - setOutputSchema, - setSystemInstruction, - setPromptTemplate, - submitBlockerMessage, - onSubmit, - onStepComplete, -}: ConfigPanelProps) { - if (!hasDataset) { - return ( -
-
- -

- No dataset selected -

-

- Select a dataset first from the Datasets tab -

- -
-
- ); - } - - return ( - <> - -
-
- onStepComplete(1)} - onBack={setActiveTabToDatasets} - /> -
- -
- a.column)} - prefilterConfig={prefilterConfig} - setPrefilterConfig={setPrefilterConfig} - onNext={() => onStepComplete(2)} - onBack={() => setConfigStep(1)} - /> -
- -
- onStepComplete(3)} - onBack={() => setConfigStep(2)} - /> -
- -
- onStepComplete(4)} - onBack={() => setConfigStep(3)} - /> -
- -
- setConfigStep(4)} - onEditStep={setConfigStep} - /> -
-
- - ); -} diff --git a/app/components/assessment/DatasetsTab.tsx b/app/components/assessment/DatasetsTab.tsx deleted file mode 100644 index b45fd705..00000000 --- a/app/components/assessment/DatasetsTab.tsx +++ /dev/null @@ -1,147 +0,0 @@ -"use client"; - -import { Button, Modal } from "@/app/components/ui"; -import { useAssessmentDatasetsTab } from "@/app/hooks/useAssessmentDatasetsTab"; -import type { DatasetsTabProps } from "@/app/lib/types/assessment"; -import { WarningIcon } from "@/app/components/icons"; -import DataViewModal from "@/app/components/assessment/DataViewModal"; -import CreatePanel from "@/app/components/assessment/datasets/CreatePanel"; -import DatasetList from "@/app/components/assessment/datasets/DatasetList"; - -export default function DatasetsTab(props: DatasetsTabProps) { - const { datasetId, onNext } = props; - const { - datasets, - isLoading, - isLoadingColumns, - viewingId, - canProceed, - datasetName, - datasetDescription, - uploadedFile, - isDragging, - isUploading, - fileInputRef, - viewModalData, - confirmDeleteId, - deletingId, - datasetPendingDelete, - setDatasetName, - setDatasetDescription, - setUploadedFile, - setIsDragging, - setConfirmDeleteId, - setViewModalData, - handleFileSelect, - resetForm, - handleCreateDataset, - handleDatasetSelect, - handleViewDataset, - handleDeleteDataset, - handleDrop, - } = useAssessmentDatasetsTab(props); - - return ( -
- - - { - event.preventDefault(); - setIsDragging(true); - }} - onDragLeave={() => setIsDragging(false)} - onDrop={handleDrop} - onRemoveFile={() => { - setUploadedFile(null); - if (fileInputRef.current) fileInputRef.current.value = ""; - }} - onResetForm={resetForm} - onCreateDataset={handleCreateDataset} - /> - - {viewModalData && ( - setViewModalData(null)} - /> - )} - - {confirmDeleteId !== null && ( - setConfirmDeleteId(null)} - maxWidth="max-w-md" - maxHeight="max-h-[90vh]" - showClose={false} - > -
-
-
- - - -
-
-

- Delete dataset -

-

- Are you sure you want to delete -

-

- {datasetPendingDelete?.dataset_name}? -

-

- This action cannot be undone. -

-
-
-
-
- - -
-
- )} -
- ); -} diff --git a/app/components/assessment/DownloadDropdown.tsx b/app/components/assessment/DownloadDropdown.tsx deleted file mode 100644 index 201e4168..00000000 --- a/app/components/assessment/DownloadDropdown.tsx +++ /dev/null @@ -1,111 +0,0 @@ -"use client"; - -import { useEffect, useLayoutEffect, useRef, useState } from "react"; -import { createPortal } from "react-dom"; -import { Button, Loader } from "@/app/components/ui"; -import { ChevronDownIcon, DownloadIcon } from "@/app/components/icons"; -import { DOWNLOAD_MENU_WIDTH } from "@/app/lib/assessment/constants"; -import type { ExportFormat } from "@/app/lib/types/assessment"; - -interface DownloadDropdownProps { - onDownload: (format: ExportFormat) => void; - disabled?: boolean; - loading?: boolean; -} - -export default function DownloadDropdown({ - onDownload, - disabled, - loading, -}: DownloadDropdownProps) { - const [open, setOpen] = useState(false); - const triggerRef = useRef(null); - const menuRef = useRef(null); - const [rect, setRect] = useState<{ top: number; left: number } | null>(null); - - // Position the portal menu under the trigger, right-aligned. - useLayoutEffect(() => { - if (!open || !triggerRef.current) return; - const r = triggerRef.current.getBoundingClientRect(); - setRect({ top: r.bottom + 4, left: r.right - DOWNLOAD_MENU_WIDTH }); - }, [open]); - - useEffect(() => { - if (!open) return; - const handlePointer = (e: MouseEvent) => { - const t = e.target as Node; - if (!triggerRef.current?.contains(t) && !menuRef.current?.contains(t)) { - setOpen(false); - } - }; - // Fixed menu can't follow scroll — close instead of drift. - const handleScrollOrResize = () => setOpen(false); - document.addEventListener("mousedown", handlePointer); - window.addEventListener("scroll", handleScrollOrResize, true); - window.addEventListener("resize", handleScrollOrResize); - return () => { - document.removeEventListener("mousedown", handlePointer); - window.removeEventListener("scroll", handleScrollOrResize, true); - window.removeEventListener("resize", handleScrollOrResize); - }; - }, [open]); - - return ( -
- - {open && - rect && - typeof document !== "undefined" && - createPortal( -
- {( - [ - ["csv", "CSV File"], - ["xlsx", "Excel Sheet"], - ] as const - ).map(([fmt, label]) => ( - - ))} -
, - document.body, - )} -
- ); -} diff --git a/app/components/assessment/EvaluationsTab.tsx b/app/components/assessment/EvaluationsTab.tsx deleted file mode 100644 index d9372758..00000000 --- a/app/components/assessment/EvaluationsTab.tsx +++ /dev/null @@ -1,265 +0,0 @@ -"use client"; - -// Assessment Evaluations tab — shows run cards with status, retry, and CSV export. -import { RunsListSkeleton } from "@/app/components"; -import { Button, Select } from "@/app/components/ui"; -import { useToast } from "@/app/hooks/useToast"; -import { - DatabaseIcon, - ClipboardIcon, - RefreshIcon, -} from "@/app/components/icons"; -import DownloadDropdown from "./DownloadDropdown"; -import AssessmentChildRunCard from "./AssessmentChildRunCard"; -import { - canRetryStatus, - formatStatusLabel, - getResultTone, -} from "@/app/lib/assessment/results"; -import { - ASSESSMENT_CARD_CLASSES, - STATUS_BADGE_CLASSES, - STATUS_FILTER_OPTIONS, -} from "@/app/lib/assessment/constants"; -import { formatRelativeTime } from "@/app/lib/utils"; -import type { EvaluationsTabProps } from "@/app/lib/types/assessment"; -import useAssessmentResults from "@/app/hooks/useAssessmentResults"; - -export default function EvaluationsTab({ onForbidden }: EvaluationsTabProps) { - const toast = useToast(); - const { - assessments, - filteredRuns, - childRunsByAssessment, - configDetailsByKey, - configLoadingKeys, - configErrorKeys, - isLoading, - statusFilter, - setStatusFilter, - rerunningId, - resumingId, - retryingAssessmentId, - expandedId, - downloadingId, - loadAssessments, - handleExpand, - handleRetryAssessment, - handleRerun, - handleResume, - handlePreview, - handleAssessmentDownload, - handleRunDownload, - handleSavePostProcessing, - handleFetchRunColumns, - } = useAssessmentResults({ onForbidden, toast }); - - return ( -
-
-
-

- Evaluation Runs -

-
-