From 017cc581fc212fcac659d24e3b5d3af6769bfb80 Mon Sep 17 00:00:00 2001 From: Prashant Vasudevan <71649489+vprashrex@users.noreply.github.com> Date: Sat, 9 May 2026 09:51:59 +0530 Subject: [PATCH 1/5] feat: Integrate Google Gemini model configurations and update assessment model types --- app/lib/data/assessmentModels.ts | 116 ++++++++++++++++++++++++++++++- app/lib/types/assessment.ts | 2 +- 2 files changed, 114 insertions(+), 4 deletions(-) diff --git a/app/lib/data/assessmentModels.ts b/app/lib/data/assessmentModels.ts index 4f82229..daf8f7c 100644 --- a/app/lib/data/assessmentModels.ts +++ b/app/lib/data/assessmentModels.ts @@ -1,6 +1,7 @@ import type { AssessmentModelConfig, ConfigParamDefinition, + ModelOption, } from "@/app/lib/types/assessment"; import type { ConfigBlob } from "@/app/lib/types/configs"; @@ -21,7 +22,42 @@ export const GPT4_STYLE_CONFIG = { }, } as const satisfies Record; +// ── Gemini param configs ───────────────────────────────────────── + +const GEMINI_TEMPERATURE_CONFIG = { + temperature: { + max: 2.0, + min: 0.0, + type: "float", + default: 0.4, + description: "Controls randomness. Lower = more deterministic.", + }, +} as const satisfies Record; + +const GEMINI_THINKING_CONFIG = { + ...GEMINI_TEMPERATURE_CONFIG, + thinking_level: { + type: "enum", + default: "LOW", + options: ["MINIMAL", "LOW", "MEDIUM", "HIGH"], + description: "Controls how much the model thinks before responding.", + }, +} as const satisfies Record; + +const GEMINI_THINKING_NO_MINIMAL_CONFIG = { + ...GEMINI_TEMPERATURE_CONFIG, + thinking_level: { + type: "enum", + default: "LOW", + options: ["LOW", "MEDIUM", "HIGH"], + description: "Controls how much the model thinks before responding.", + }, +} as const satisfies Record; + +// ── All model configs ──────────────────────────────────────────── + export const ASSESSMENT_MODEL_CONFIGS: AssessmentModelConfig[] = [ + // OpenAI { provider: "openai", model_name: "gpt-4o-mini", config: GPT4_STYLE_CONFIG }, { provider: "openai", model_name: "gpt-4o", config: GPT4_STYLE_CONFIG }, { provider: "openai", model_name: "gpt-4.1", config: GPT4_STYLE_CONFIG }, @@ -215,9 +251,84 @@ export const ASSESSMENT_MODEL_CONFIGS: AssessmentModelConfig[] = [ }, }, }, + // Google (Gemini) + { + provider: "google", + model_name: "gemini-2.0-flash-lite", + config: GEMINI_TEMPERATURE_CONFIG, + }, + { + provider: "google", + model_name: "gemini-2.0-flash", + config: GEMINI_TEMPERATURE_CONFIG, + }, + { + provider: "google", + model_name: "gemini-2.5-flash-lite", + config: GEMINI_TEMPERATURE_CONFIG, + }, + { + provider: "google", + model_name: "gemini-2.5-flash", + config: GEMINI_TEMPERATURE_CONFIG, + }, + { + provider: "google", + model_name: "gemini-2.5-pro", + config: GEMINI_TEMPERATURE_CONFIG, + }, + { + provider: "google", + model_name: "gemini-3.1-flash-lite-preview", + config: GEMINI_THINKING_CONFIG, + }, + { + provider: "google", + model_name: "gemini-3.1-pro-preview", + config: GEMINI_THINKING_NO_MINIMAL_CONFIG, + }, + { + provider: "google", + model_name: "gemini-3-flash-preview", + config: GEMINI_THINKING_CONFIG, + }, ]; -export const PROVIDER_OPTIONS = [{ value: "openai", label: "OpenAI" }] as const; +export const PROVIDER_OPTIONS = [ + { value: "openai", label: "OpenAI" }, + { value: "google", label: "Google (Gemini)" }, +] as const; + +export function getModelsByProvider(provider: string): ModelOption[] { + return ASSESSMENT_MODEL_CONFIGS.filter((m) => m.provider === provider).map( + ({ model_name }) => ({ value: model_name, label: model_name }), + ); +} + +export function getDefaultModelForProvider(provider: string): string { + return ( + ASSESSMENT_MODEL_CONFIGS.find((m) => m.provider === provider)?.model_name ?? + "gpt-4o-mini" + ); +} + +export function getModelConfigDefinition( + modelName: string, +): Record { + return ( + ASSESSMENT_MODEL_CONFIGS.find((item) => item.model_name === modelName) + ?.config ?? GPT4_STYLE_CONFIG + ); +} + +export function buildDefaultParams( + modelName: string, +): Record { + const definition = getModelConfigDefinition(modelName); + return Object.fromEntries( + Object.entries(definition).map(([key, value]) => [key, value.default]), + ); +} export const ASSESSMENT_DEFAULT_CONFIG: ConfigBlob = { completion: { @@ -226,8 +337,7 @@ export const ASSESSMENT_DEFAULT_CONFIG: ConfigBlob = { params: { model: "gpt-4o-mini", instructions: "", - top_p: GPT4_STYLE_CONFIG.top_p.default, - temperature: GPT4_STYLE_CONFIG.temperature.default, + ...buildDefaultParams("gpt-4o-mini"), }, }, }; diff --git a/app/lib/types/assessment.ts b/app/lib/types/assessment.ts index 5edf69b..f9c0adf 100644 --- a/app/lib/types/assessment.ts +++ b/app/lib/types/assessment.ts @@ -105,7 +105,7 @@ export interface ConfigParamDefinition { } export interface AssessmentModelConfig { - provider: "openai"; + provider: "openai" | "google"; model_name: string; config: Record; } From 01f625a46d116b593276f1bd23de49ef7e5c31e7 Mon Sep 17 00:00:00 2001 From: Prashant Vasudevan <71649489+vprashrex@users.noreply.github.com> Date: Mon, 11 May 2026 10:21:17 +0530 Subject: [PATCH 2/5] chore: Clean up comments in assessmentModels.ts for clarity --- app/lib/data/assessmentModels.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/lib/data/assessmentModels.ts b/app/lib/data/assessmentModels.ts index daf8f7c..41ee16a 100644 --- a/app/lib/data/assessmentModels.ts +++ b/app/lib/data/assessmentModels.ts @@ -22,7 +22,7 @@ export const GPT4_STYLE_CONFIG = { }, } as const satisfies Record; -// ── Gemini param configs ───────────────────────────────────────── +// Gemini param configs const GEMINI_TEMPERATURE_CONFIG = { temperature: { @@ -54,8 +54,6 @@ const GEMINI_THINKING_NO_MINIMAL_CONFIG = { }, } as const satisfies Record; -// ── All model configs ──────────────────────────────────────────── - export const ASSESSMENT_MODEL_CONFIGS: AssessmentModelConfig[] = [ // OpenAI { provider: "openai", model_name: "gpt-4o-mini", config: GPT4_STYLE_CONFIG }, From 2689da2a9ac455f9d296279aee5d8027660882c2 Mon Sep 17 00:00:00 2001 From: Ayush <80516839+Ayush8923@users.noreply.github.com> Date: Mon, 11 May 2026 11:09:24 +0530 Subject: [PATCH 3/5] Apply suggestions from code review Co-authored-by: Ayush <80516839+Ayush8923@users.noreply.github.com> --- app/lib/data/assessmentModels.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/lib/data/assessmentModels.ts b/app/lib/data/assessmentModels.ts index 41ee16a..b8c2266 100644 --- a/app/lib/data/assessmentModels.ts +++ b/app/lib/data/assessmentModels.ts @@ -22,8 +22,6 @@ export const GPT4_STYLE_CONFIG = { }, } as const satisfies Record; -// Gemini param configs - const GEMINI_TEMPERATURE_CONFIG = { temperature: { max: 2.0, From 806c908cb1522e2e69e96400499b072bb0d2226e Mon Sep 17 00:00:00 2001 From: Prashant Vasudevan <71649489+vprashrex@users.noreply.github.com> Date: Tue, 12 May 2026 10:48:59 +0530 Subject: [PATCH 4/5] feat: Integrate dataset preview functionality and enhance file handling in assessment components --- .../assessment/datasets/[dataset_id]/route.ts | 97 +------------------ .../assessment/ColumnMapperStep.tsx | 12 +-- app/components/assessment/DataViewModal.tsx | 13 ++- app/components/assessment/DatasetsTab.tsx | 52 +++++++++- .../assessment/datasets/CreatePanel.tsx | 2 +- .../output-schema/OutputSchemaModal.tsx | 8 +- app/hooks/useAssessmentResults.ts | 5 +- app/lib/assessment/constants.ts | 3 + app/lib/assessment/results.ts | 9 +- app/lib/types/assessment.ts | 23 ++++- app/lib/utils/assessment.ts | 61 ++++-------- 11 files changed, 129 insertions(+), 156 deletions(-) diff --git a/app/api/assessment/datasets/[dataset_id]/route.ts b/app/api/assessment/datasets/[dataset_id]/route.ts index 7296408..a4f035b 100644 --- a/app/api/assessment/datasets/[dataset_id]/route.ts +++ b/app/api/assessment/datasets/[dataset_id]/route.ts @@ -1,58 +1,19 @@ -// BFF proxy — GET (with optional S3 file fetch, max 10 MB) + DELETE /api/v1/assessment/datasets/:id +// BFF proxy — GET (optional preview via limit_rows) + DELETE /api/v1/assessment/datasets/:id import { NextRequest, NextResponse } from "next/server"; import { apiClient } from "@/app/lib/apiClient"; import { proxyErrorResponse, withQueryParams } from "@/app/api/_routeProxy"; -const MAX_DATASET_PROXY_BYTES = 10 * 1024 * 1024; - -async function readFileAsBase64WithLimit(response: Response): Promise { - const contentLength = response.headers.get("content-length"); - if (contentLength) { - const size = Number.parseInt(contentLength, 10); - if (Number.isFinite(size) && size > MAX_DATASET_PROXY_BYTES) { - throw new Error("FILE_TOO_LARGE"); - } - } - - const reader = response.body?.getReader(); - if (!reader) { - throw new Error("FILE_STREAM_UNAVAILABLE"); - } - - const chunks: Uint8Array[] = []; - let totalBytes = 0; - - while (true) { - const { done, value } = await reader.read(); - if (done) break; - if (!value) continue; - - totalBytes += value.byteLength; - if (totalBytes > MAX_DATASET_PROXY_BYTES) { - throw new Error("FILE_TOO_LARGE"); - } - chunks.push(value); - } - - return Buffer.concat(chunks).toString("base64"); -} - export async function GET( request: NextRequest, { params }: { params: Promise<{ dataset_id: string }> }, ) { try { const { dataset_id } = await params; - const fetchContent = - request.nextUrl.searchParams.get("fetch_content") === "true"; + const limitRows = request.nextUrl.searchParams.get("limit_rows"); - // Always request signed URL when fetch_content is needed const backendParams = new URLSearchParams(); - if (fetchContent) { - backendParams.set("fetch_content", "true"); - } - if (fetchContent) { - backendParams.set("include_signed_url", "true"); + if (limitRows) { + backendParams.set("limit_rows", limitRows); } const endpoint = withQueryParams( `/api/v1/assessment/datasets/${dataset_id}`, @@ -63,56 +24,6 @@ export async function GET( method: "GET", }); - if (status >= 400) { - return NextResponse.json(data, { status }); - } - - // Download file from S3 server-side and return as base64 - if (fetchContent) { - const signedUrl = - (data as { data?: { signed_url?: string }; signed_url?: string })?.data - ?.signed_url || - (data as { data?: { signed_url?: string }; signed_url?: string }) - ?.signed_url; - - if (!signedUrl) { - return NextResponse.json( - { error: "No signed URL available" }, - { status: 404 }, - ); - } - - const fileResponse = await fetch(signedUrl); - if (!fileResponse.ok) { - return NextResponse.json( - { error: "Failed to fetch file from storage" }, - { status: 502 }, - ); - } - - let base64: string; - try { - base64 = await readFileAsBase64WithLimit(fileResponse); - } catch (error) { - if (error instanceof Error && error.message === "FILE_TOO_LARGE") { - return NextResponse.json( - { error: "File too large" }, - { status: 413 }, - ); - } - - return NextResponse.json( - { error: "Failed to read file from storage" }, - { status: 502 }, - ); - } - - return NextResponse.json( - { ...(data as Record), file_content: base64 }, - { status: 200 }, - ); - } - return NextResponse.json(data, { status }); } catch (error: unknown) { return proxyErrorResponse("Assessment dataset details proxy error:", error); diff --git a/app/components/assessment/ColumnMapperStep.tsx b/app/components/assessment/ColumnMapperStep.tsx index 41a09a1..0fa075d 100644 --- a/app/components/assessment/ColumnMapperStep.tsx +++ b/app/components/assessment/ColumnMapperStep.tsx @@ -158,19 +158,19 @@ export default function ColumnMapperStep({
-
-
-
+
+
+
- + {column}
-
+
{ASSESSMENT_ROLE_OPTIONS.map((option) => { const isGroundTruth = option.value === "ground_truth"; const isActive = config.role === option.value; diff --git a/app/components/assessment/DataViewModal.tsx b/app/components/assessment/DataViewModal.tsx index 6e2fd19..a960eb2 100644 --- a/app/components/assessment/DataViewModal.tsx +++ b/app/components/assessment/DataViewModal.tsx @@ -45,14 +45,14 @@ export default function DataViewModal({
- +
- + {headers.map((header, i) => ( @@ -66,8 +66,11 @@ export default function DataViewModal({ {rowIdx + 1} {row.map((cell, cellIdx) => ( - diff --git a/app/components/assessment/DatasetsTab.tsx b/app/components/assessment/DatasetsTab.tsx index 246ef75..5c05d59 100644 --- a/app/components/assessment/DatasetsTab.tsx +++ b/app/components/assessment/DatasetsTab.tsx @@ -7,12 +7,18 @@ import { useToast } from "@/app/components/Toast"; import { useAuth } from "@/app/lib/context/AuthContext"; import { extractCreatedDataset, - fetchAndParseDatasetFile, + fetchDatasetPreview, handleForbiddenError, isAllowedDatasetFile, } from "@/app/lib/utils/assessment"; +import { PREVIEW_ROW_LIMIT } from "@/app/lib/assessment/results"; +import { + DATASET_SAMPLE_ROW_LIMIT, + MAX_DATASET_FILE_BYTES, +} from "@/app/lib/assessment/constants"; import type { CreateDatasetResponse, + DatasetPreview, DatasetResponse, DatasetsTabProps, DatasetViewModalData, @@ -49,6 +55,9 @@ export default function DatasetsTab({ useState(null); const [confirmDeleteId, setConfirmDeleteId] = useState(null); const [deletingId, setDeletingId] = useState(null); + const [previewCache, setPreviewCache] = useState< + Record + >({}); const loadDatasets = useCallback(async () => { if (!isAuthenticated) return; @@ -91,6 +100,14 @@ export default function DatasetsTab({ return; } + if (file.size > MAX_DATASET_FILE_BYTES) { + toast.error( + `File too large. Max ${MAX_DATASET_FILE_BYTES / (1024 * 1024)}MB allowed.`, + ); + event.target.value = ""; + return; + } + setUploadedFile(file); if (!datasetName) { setDatasetName(file.name.replace(/\.(csv|xlsx|xls)$/i, "")); @@ -163,7 +180,13 @@ export default function DatasetsTab({ setIsLoadingColumns(true); try { - const parsed = await fetchAndParseDatasetFile(id, apiKey); + const cached = previewCache[id]; + const parsed = + cached ?? + (await fetchDatasetPreview(id, apiKey, DATASET_SAMPLE_ROW_LIMIT)); + if (!cached) { + setPreviewCache((prev) => ({ ...prev, [id]: parsed })); + } const firstRow = parsed.rows[0] || []; const sampleRow = Object.fromEntries( parsed.headers.map((header, index) => [ @@ -188,9 +211,25 @@ export default function DatasetsTab({ }; const handleViewDataset = async (selectedDatasetId: number, name: string) => { + const cacheKey = String(selectedDatasetId); + const cached = previewCache[cacheKey]; + if (cached) { + setViewModalData({ + name, + headers: cached.headers, + rows: cached.rows, + }); + return; + } + setViewingId(selectedDatasetId); try { - const parsed = await fetchAndParseDatasetFile(selectedDatasetId, apiKey); + const parsed = await fetchDatasetPreview( + selectedDatasetId, + apiKey, + PREVIEW_ROW_LIMIT, + ); + setPreviewCache((prev) => ({ ...prev, [cacheKey]: parsed })); setViewModalData({ name, headers: parsed.headers, @@ -235,6 +274,13 @@ export default function DatasetsTab({ const file = event.dataTransfer.files?.[0]; if (!file || !isAllowedDatasetFile(file.name)) return; + if (file.size > MAX_DATASET_FILE_BYTES) { + toast.error( + `File too large. Max ${MAX_DATASET_FILE_BYTES / (1024 * 1024)}MB allowed.`, + ); + return; + } + const dataTransfer = new DataTransfer(); dataTransfer.items.add(file); if (!fileInputRef.current) return; diff --git a/app/components/assessment/datasets/CreatePanel.tsx b/app/components/assessment/datasets/CreatePanel.tsx index 976705f..d238b96 100644 --- a/app/components/assessment/datasets/CreatePanel.tsx +++ b/app/components/assessment/datasets/CreatePanel.tsx @@ -72,7 +72,7 @@ export default function CreatePanel({ />
{ + setSchema(draftSchema); + onClose(); + }; const handleReset = () => { setDraftSchema([createProperty()]); }; @@ -52,7 +56,7 @@ export default function OutputSchemaModal({ return createPortal(
{header} -
+
+
{cell || }