added cancel training button on the overlay

This commit is contained in:
samit 2026-02-22 12:03:26 -08:00
commit 7dcaa52083
5 changed files with 82 additions and 5 deletions

View file

@ -237,6 +237,7 @@ export function ProgressSection(): ReactElement {
onClick={() => {
setStopRequested(true);
setStopDialogOpen(false);
useTrainingRuntimeStore.getState().setStopRequested(true);
void stopTrainingRun(false).then((ok) => {
if (!ok) setStopRequested(false);
});
@ -248,6 +249,7 @@ export function ProgressSection(): ReactElement {
onClick={() => {
setStopRequested(true);
setStopDialogOpen(false);
useTrainingRuntimeStore.getState().setStopRequested(true);
void stopTrainingRun(true).then((ok) => {
if (!ok) setStopRequested(false);
});

View file

@ -45,11 +45,16 @@ export function StudioPage(): ReactElement {
const dialogInitial = useDatasetPreviewDialogStore((s) => s.initialData);
const closeDialog = useDatasetPreviewDialogStore((s) => s.close);
const stopRequested = useTrainingRuntimeStore((state) => state.stopRequested);
const canGoBack =
showTrainingView &&
!isTrainingRunning &&
!isHydratingRuntime &&
(runtimePhase === "stopped" || runtimePhase === "error" || runtimePhase === "completed" || runtimePhase === "idle");
(stopRequested ||
(!isTrainingRunning &&
(runtimePhase === "stopped" ||
runtimePhase === "error" ||
runtimePhase === "completed" ||
runtimePhase === "idle")));
const tourEnabled = hasHydratedRuntime && !isHydratingRuntime;
const isConfigTour = !showTrainingView;
const tourSteps = showTrainingView ? studioTrainingTourSteps : studioTourSteps;

View file

@ -1,9 +1,23 @@
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
import {
AnimatedSpan,
Terminal,
TypingAnimation,
} from "@/components/ui/terminal"
import type { ReactElement } from "react"
} from "@/components/ui/terminal";
import { useTrainingActions, useTrainingRuntimeStore } from "@/features/training";
import { StopIcon } from "@hugeicons/core-free-icons";
import { HugeiconsIcon } from "@hugeicons/react";
import { useEffect, useState, type ReactElement } from "react";
type TrainingStartOverlayProps = {
message: string
@ -14,9 +28,58 @@ export function TrainingStartOverlay({
message,
currentStep,
}: TrainingStartOverlayProps): ReactElement {
const { stopTrainingRun } = useTrainingActions();
const isStarting = useTrainingRuntimeStore((s) => s.isStarting);
const [cancelDialogOpen, setCancelDialogOpen] = useState(false);
const [cancelRequested, setCancelRequested] = useState(false);
useEffect(() => {
if (!isStarting) {
setCancelRequested(false);
}
}, [isStarting]);
return (
<div className="pointer-events-none absolute inset-0 z-30 flex items-center justify-center rounded-2xl bg-background/45 backdrop-blur-[1px]">
<div className="absolute inset-0 z-30 flex items-center justify-center rounded-2xl bg-background/45 backdrop-blur-[1px]">
<div className="flex w-[860px] max-w-[calc(100%-2rem)] flex-col items-center gap-4">
<div className="absolute right-4 top-4 flex justify-end">
<AlertDialog open={cancelDialogOpen} onOpenChange={setCancelDialogOpen}>
<Button
variant="destructive"
size="sm"
className={`h-7 px-3 text-xs ${cancelRequested ? "cursor-not-allowed opacity-60" : "cursor-pointer"}`}
onClick={() => setCancelDialogOpen(true)}
disabled={cancelRequested}
>
<HugeiconsIcon icon={StopIcon} className="size-3" />
{cancelRequested ? "Cancelling…" : "Cancel Training"}
</Button>
<AlertDialogContent overlayClassName="bg-background/40 supports-backdrop-filter:backdrop-blur-[1px]">
<AlertDialogHeader>
<AlertDialogTitle>Cancel Training</AlertDialogTitle>
<AlertDialogDescription>
Do you want to cancel the current training run?
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Continue Training</AlertDialogCancel>
<AlertDialogAction
variant="destructive"
onClick={() => {
setCancelRequested(true);
setCancelDialogOpen(false);
useTrainingRuntimeStore.getState().setStopRequested(true);
void stopTrainingRun(false).then((ok) => {
if (!ok) setCancelRequested(false);
});
}}
>
Cancel Training
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
<img
src="/Sloth emojis/large sloth wave.png"
alt="Unsloth mascot"

View file

@ -37,6 +37,7 @@ const initialState: TrainingRuntimeState = {
gradNormHistory: [],
evalLossHistory: [],
resetGeneration: 0,
stopRequested: false,
};
function sortSeries(points: TrainingSeriesPoint[]): TrainingSeriesPoint[] {
@ -110,6 +111,7 @@ function applyMetricHistoryFromStatus(payload: TrainingStatusResponse): {
export const useTrainingRuntimeStore = create<TrainingRuntimeStore>()((set) => ({
...initialState,
setStopRequested: (value) => set({ stopRequested: value }),
setHydrating: (value) => set({ isHydrating: value }),
setHasHydrated: (value) => set({ hasHydrated: value }),
setStarting: (value) => set({ isStarting: value }),
@ -173,12 +175,15 @@ export const useTrainingRuntimeStore = create<TrainingRuntimeStore>()((set) => (
const detailLoss = payload.details?.loss;
const detailLr = payload.details?.learning_rate;
const detailEpoch = payload.details?.epoch;
const stopRequested =
payload.is_training_running ? state.stopRequested : false;
return {
...state,
jobId: payload.job_id || state.jobId,
phase: payload.phase,
isTrainingRunning: payload.is_training_running,
stopRequested,
evalEnabled: payload.eval_enabled ?? state.evalEnabled,
message: payload.message,
error: payload.error,

View file

@ -95,9 +95,11 @@ export interface TrainingRuntimeState {
gradNormHistory: TrainingSeriesPoint[];
evalLossHistory: TrainingSeriesPoint[];
resetGeneration: number;
stopRequested: boolean;
}
export interface TrainingRuntimeActions {
setStopRequested: (value: boolean) => void;
setHydrating: (value: boolean) => void;
setHasHydrated: (value: boolean) => void;
setStarting: (value: boolean) => void;