diff --git a/app/(main)/assessment/page.tsx b/app/(main)/assessment/page.tsx index c099335..a0ac358 100644 --- a/app/(main)/assessment/page.tsx +++ b/app/(main)/assessment/page.tsx @@ -67,7 +67,7 @@ const PAGE_TABS: ReadonlyArray = [ function PageContent() { const router = useRouter(); - const toast = useToast(); + const { error: showToastError, success: showToastSuccess } = useToast(); const { activeKey } = useAuth(); const [activeTab, setActiveTab] = useState("datasets"); const [configStep, setConfigStep] = useState(1); @@ -100,7 +100,7 @@ function PageContent() { featureRedirectingRef.current = true; if (options?.notify) { - toast.error( + showToastError( "Assessment feature is disabled for this organization/project.", ); } @@ -113,7 +113,7 @@ function PageContent() { router.replace("/"); } }, - [router, toast], + [router, showToastError], ); const handleForbiddenWithNotify = useCallback(() => { @@ -148,27 +148,27 @@ function PageContent() { const handleSubmit = useCallback(async () => { if (!datasetId) { - toast.error("Dataset is required"); + showToastError("Dataset is required"); return; } if (columnMapping.textColumns.length === 0) { - toast.error("Map at least one text column"); + showToastError("Map at least one text column"); return; } if (!promptTemplate.trim()) { - toast.error("Prompt is required"); + showToastError("Prompt is required"); return; } if (!outputSchema.some((field) => field.name.trim())) { - toast.error("Response format is required"); + showToastError("Response format is required"); return; } if (configs.length === 0) { - toast.error("Select at least one configuration"); + showToastError("Select at least one configuration"); return; } if (!experimentName.trim()) { - toast.error("Experiment name is required"); + showToastError("Experiment name is required"); return; } @@ -193,7 +193,7 @@ function PageContent() { }), }); - toast.success("Assessment submitted!"); + showToastSuccess("Assessment submitted!"); setConfigStep(1); setCompletedConfigSteps(new Set()); setExperimentName(""); @@ -205,7 +205,7 @@ function PageContent() { setActiveTab("results"); } catch (error) { if (handleForbiddenError(error, handleForbiddenWithNotify)) return; - toast.error( + showToastError( `Failed to submit: ${error instanceof Error ? error.message : "Unknown error"}`, ); } finally { @@ -223,7 +223,8 @@ function PageContent() { promptTemplate, activeKey, systemInstruction, - toast, + showToastError, + showToastSuccess, ]); const formState: AssessmentFormState = { diff --git a/app/components/assessment/PromptAndConfigStep.tsx b/app/components/assessment/PromptAndConfigStep.tsx index 4d1eff5..33fdfa1 100644 --- a/app/components/assessment/PromptAndConfigStep.tsx +++ b/app/components/assessment/PromptAndConfigStep.tsx @@ -439,7 +439,7 @@ export default function PromptAndConfigStep({ versionStateByConfig={versionStateByConfig} loadingSelectionKeys={loadingSelectionKeys} isSelected={isSelected} - onLoadMoreConfigs={(skip) => void loadConfigs(skip, false)} + onLoadMoreConfigs={(skip) => loadConfigs(skip, false)} onLoadVersions={(configId, skip) => void loadVersions(configId, skip) } diff --git a/app/components/assessment/prompt-config/AssessmentConfiguration.tsx b/app/components/assessment/prompt-config/AssessmentConfiguration.tsx index a853b55..b2948f5 100644 --- a/app/components/assessment/prompt-config/AssessmentConfiguration.tsx +++ b/app/components/assessment/prompt-config/AssessmentConfiguration.tsx @@ -30,7 +30,7 @@ interface AssessmentConfigurationProps extends Omit< versionStateByConfig: Record; loadingSelectionKeys: Record; isSelected: (configId: string, version: number) => boolean; - onLoadMoreConfigs: (skip: number) => void; + onLoadMoreConfigs: (skip: number) => void | Promise; onLoadVersions: (configId: string, skip: number) => void; onToggleConfigExpansion: ValueSetter; onToggleVersionSelection: ( diff --git a/app/components/assessment/prompt-config/SavedConfigs.tsx b/app/components/assessment/prompt-config/SavedConfigs.tsx index 83a6953..ffd4187 100644 --- a/app/components/assessment/prompt-config/SavedConfigs.tsx +++ b/app/components/assessment/prompt-config/SavedConfigs.tsx @@ -1,3 +1,4 @@ +import { useEffect, useRef, useState } from "react"; import { Button } from "@/app/components"; import Loader from "@/app/components/Loader"; import type { ConfigPublic } from "@/app/lib/types/configs"; @@ -5,6 +6,8 @@ import type { ValueSetter, VersionListState } from "@/app/lib/types/assessment"; import { buildInitialAssessmentVersionState } from "@/app/lib/utils/assessmentFetcher"; import SavedConfigCard from "./SavedConfigCard"; +const CONFIGS_VISIBLE_BATCH_SIZE = 2; + interface SavedConfigsProps { configCards: ConfigPublic[]; searchQuery: string; @@ -16,7 +19,7 @@ interface SavedConfigsProps { versionStateByConfig: Record; loadingSelectionKeys: Record; isSelected: (configId: string, version: number) => boolean; - onLoadMoreConfigs: (skip: number) => void; + onLoadMoreConfigs: (skip: number) => void | Promise; onLoadVersions: (configId: string, skip: number) => void; onToggleConfigExpansion: ValueSetter; onToggleVersionSelection: ( @@ -41,6 +44,51 @@ export default function SavedConfigs({ onToggleConfigExpansion, onToggleVersionSelection, }: SavedConfigsProps) { + const listRef = useRef(null); + const [visibleConfigCount, setVisibleConfigCount] = useState( + CONFIGS_VISIBLE_BATCH_SIZE, + ); + const [isLoadingMore, setIsLoadingMore] = useState(false); + const visibleConfigCards = configCards.slice(0, visibleConfigCount); + const hasHiddenLoadedConfigs = visibleConfigCount < configCards.length; + const canViewMoreConfigs = hasHiddenLoadedConfigs || hasMoreConfigs; + + useEffect(() => { + setVisibleConfigCount(CONFIGS_VISIBLE_BATCH_SIZE); + }, [searchQuery]); + + const scrollToListEnd = () => { + requestAnimationFrame(() => { + const listElement = listRef.current; + if (!listElement) return; + listElement.scrollTo({ + top: listElement.scrollHeight, + behavior: "smooth", + }); + }); + }; + + const handleViewMore = async () => { + if (isLoadingMore) return; + if (hasHiddenLoadedConfigs) { + setVisibleConfigCount((prev) => + Math.min(prev + CONFIGS_VISIBLE_BATCH_SIZE, configCards.length), + ); + scrollToListEnd(); + return; + } + if (!hasMoreConfigs) return; + + setIsLoadingMore(true); + try { + await onLoadMoreConfigs(nextConfigSkip); + setVisibleConfigCount((prev) => prev + CONFIGS_VISIBLE_BATCH_SIZE); + scrollToListEnd(); + } finally { + setIsLoadingMore(false); + } + }; + return (
@@ -64,8 +112,11 @@ export default function SavedConfigs({ : "No saved behaviors found."}
) : ( -
- {configCards.map((config) => ( +
+ {visibleConfigCards.map((config) => ( ))} - {hasMoreConfigs && ( + {canViewMoreConfigs && ( )}
diff --git a/app/lib/utils/assessmentFetcher.ts b/app/lib/utils/assessmentFetcher.ts index 2859464..4a87825 100644 --- a/app/lib/utils/assessmentFetcher.ts +++ b/app/lib/utils/assessmentFetcher.ts @@ -242,6 +242,7 @@ export async function saveAssessmentConfig(params: { const configCreate: ConfigCreate = { name: trimmedName, description: "Assessment configuration", + tag: ASSESSMENT_TAG, config_blob: normalizedBlob, commit_message: params.commitMessage.trim() || "Initial assessment configuration",