Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 13 additions & 12 deletions app/(main)/assessment/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ const PAGE_TABS: ReadonlyArray<AssessmentTab> = [

function PageContent() {
const router = useRouter();
const toast = useToast();
const { error: showToastError, success: showToastSuccess } = useToast();
const { activeKey } = useAuth();
const [activeTab, setActiveTab] = useState<AssessmentTabId>("datasets");
const [configStep, setConfigStep] = useState(1);
Expand Down Expand Up @@ -100,7 +100,7 @@ function PageContent() {
featureRedirectingRef.current = true;

if (options?.notify) {
toast.error(
showToastError(
"Assessment feature is disabled for this organization/project.",
);
}
Expand All @@ -113,7 +113,7 @@ function PageContent() {
router.replace("/");
}
},
[router, toast],
[router, showToastError],
);

const handleForbiddenWithNotify = useCallback(() => {
Expand Down Expand Up @@ -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;
}

Expand All @@ -193,7 +193,7 @@ function PageContent() {
}),
});

toast.success("Assessment submitted!");
showToastSuccess("Assessment submitted!");
setConfigStep(1);
setCompletedConfigSteps(new Set());
setExperimentName("");
Expand All @@ -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 {
Expand All @@ -223,7 +223,8 @@ function PageContent() {
promptTemplate,
activeKey,
systemInstruction,
toast,
showToastError,
showToastSuccess,
]);

const formState: AssessmentFormState = {
Expand Down
2 changes: 1 addition & 1 deletion app/components/assessment/PromptAndConfigStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ interface AssessmentConfigurationProps extends Omit<
versionStateByConfig: Record<string, VersionListState>;
loadingSelectionKeys: Record<string, boolean>;
isSelected: (configId: string, version: number) => boolean;
onLoadMoreConfigs: (skip: number) => void;
onLoadMoreConfigs: (skip: number) => void | Promise<void>;
onLoadVersions: (configId: string, skip: number) => void;
onToggleConfigExpansion: ValueSetter<string>;
onToggleVersionSelection: (
Expand Down
66 changes: 59 additions & 7 deletions app/components/assessment/prompt-config/SavedConfigs.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
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";
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;
Expand All @@ -16,7 +19,7 @@ interface SavedConfigsProps {
versionStateByConfig: Record<string, VersionListState>;
loadingSelectionKeys: Record<string, boolean>;
isSelected: (configId: string, version: number) => boolean;
onLoadMoreConfigs: (skip: number) => void;
onLoadMoreConfigs: (skip: number) => void | Promise<void>;
onLoadVersions: (configId: string, skip: number) => void;
onToggleConfigExpansion: ValueSetter<string>;
onToggleVersionSelection: (
Expand All @@ -41,6 +44,51 @@ export default function SavedConfigs({
onToggleConfigExpansion,
onToggleVersionSelection,
}: SavedConfigsProps) {
const listRef = useRef<HTMLDivElement>(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 (
<div>
<div className="mb-3">
Expand All @@ -64,8 +112,11 @@ export default function SavedConfigs({
: "No saved behaviors found."}
</div>
) : (
<div className="max-h-[560px] space-y-3 overflow-y-auto pr-1">
{configCards.map((config) => (
<div
ref={listRef}
className="max-h-[560px] space-y-3 overflow-y-auto pr-1"
>
{visibleConfigCards.map((config) => (
<SavedConfigCard
key={config.id}
config={config}
Expand All @@ -81,16 +132,17 @@ export default function SavedConfigs({
onToggleVersionSelection={onToggleVersionSelection}
/>
))}
{hasMoreConfigs && (
{canViewMoreConfigs && (
<Button
type="button"
variant="outline"
variant="primary"
size="sm"
fullWidth
onClick={() => onLoadMoreConfigs(nextConfigSkip)}
onClick={() => void handleViewMore()}
disabled={isLoadingMore}
className="!rounded-xl !py-2 !text-xs"
>
Load more
{isLoadingMore ? "Loading..." : "View more"}
</Button>
)}
</div>
Expand Down
1 change: 1 addition & 0 deletions app/lib/utils/assessmentFetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down