tour steps split
This commit is contained in:
parent
ddffa2bbdc
commit
2b6229f049
13 changed files with 170 additions and 64 deletions
|
|
@ -5,10 +5,10 @@ import {
|
|||
useTrainingRuntimeLifecycle,
|
||||
useTrainingRuntimeStore,
|
||||
} from "@/features/training";
|
||||
import { GuidedTour, type TourStep } from "@/features/tour";
|
||||
import { GuidedTour, studioTourSteps } from "@/features/tour";
|
||||
import { ArrowLeft01Icon } from "@hugeicons/core-free-icons";
|
||||
import { HugeiconsIcon } from "@hugeicons/react";
|
||||
import { type ReactElement, useEffect, useMemo, useState } from "react";
|
||||
import { type ReactElement, useEffect, useState } from "react";
|
||||
import { DatasetSection } from "./sections/dataset-section";
|
||||
import { ModelSection } from "./sections/model-section";
|
||||
import { ParamsSection } from "./sections/params-section";
|
||||
|
|
@ -30,60 +30,6 @@ export function StudioPage(): ReactElement {
|
|||
const tourEnabled = hasHydratedRuntime && !isHydratingRuntime && !showTrainingView;
|
||||
const [tourOpen, setTourOpen] = useState(false);
|
||||
|
||||
const tourSteps = useMemo<TourStep[]>(
|
||||
() => [
|
||||
{
|
||||
id: "nav",
|
||||
target: "navbar",
|
||||
title: "Quick orientation",
|
||||
body: "Studio is where you fine-tune. Export ships results. Chat is for poking at models. This tour is Studio-only (for now).",
|
||||
},
|
||||
{
|
||||
id: "local-model",
|
||||
target: "studio-local-model",
|
||||
title: "Local model path",
|
||||
body: "Point to a local folder (`./models/...`) or a custom HF repo. Use this when you already downloaded weights.",
|
||||
},
|
||||
{
|
||||
id: "base-model",
|
||||
target: "studio-base-model",
|
||||
title: "Base model from Hugging Face",
|
||||
body: "Search Hub here. Paste `org/model` too. Pick something close to your target domain to save compute.",
|
||||
},
|
||||
{
|
||||
id: "method",
|
||||
target: "studio-method",
|
||||
title: "Method: QLoRA vs LoRA vs Full",
|
||||
body: "QLoRA: lowest VRAM (4-bit). LoRA: fast + solid (16-bit adapters). Full: slowest, highest cost, updates all weights.",
|
||||
},
|
||||
{
|
||||
id: "dataset",
|
||||
target: "studio-dataset",
|
||||
title: "Dataset",
|
||||
body: "Search Hub or paste `user/dataset`. Preview a few rows before you burn hours of compute.",
|
||||
},
|
||||
{
|
||||
id: "params",
|
||||
target: "studio-params",
|
||||
title: "Dial hyperparams",
|
||||
body: "Epochs + context length + LR. Keep it boring: small changes, one at a time.",
|
||||
},
|
||||
{
|
||||
id: "start",
|
||||
target: "studio-start",
|
||||
title: "Start training",
|
||||
body: "One click. If it fails, the error text is the first place to look (token, path, config).",
|
||||
},
|
||||
{
|
||||
id: "save",
|
||||
target: "studio-save",
|
||||
title: "Save config",
|
||||
body: "Save good runs. Repeatability beats vibe. You can iterate from a known baseline.",
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!tourEnabled) return;
|
||||
if (localStorage.getItem(STUDIO_TOUR_KEY)) return;
|
||||
|
|
@ -96,7 +42,7 @@ export function StudioPage(): ReactElement {
|
|||
<GuidedTour
|
||||
open={tourOpen}
|
||||
onOpenChange={setTourOpen}
|
||||
steps={tourSteps}
|
||||
steps={studioTourSteps}
|
||||
onSkip={() => localStorage.setItem(STUDIO_TOUR_KEY, "skipped")}
|
||||
onComplete={() => localStorage.setItem(STUDIO_TOUR_KEY, "done")}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -9,23 +9,31 @@ import {
|
|||
} from "@hugeicons/core-free-icons";
|
||||
import { Dialog as DialogPrimitive } from "radix-ui";
|
||||
import { AnimatePresence, motion } from "motion/react";
|
||||
import { useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
|
||||
import {
|
||||
type ReactNode,
|
||||
useEffect,
|
||||
useId,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
|
||||
export type TourStep = {
|
||||
id: string;
|
||||
target: string; // data-tour="<target>"
|
||||
title: string;
|
||||
body: string;
|
||||
body: ReactNode;
|
||||
};
|
||||
|
||||
type Rect = { x: number; y: number; w: number; h: number };
|
||||
type Placement = "right" | "left" | "top" | "bottom";
|
||||
|
||||
function clamp(n: number, min: number, max: number) {
|
||||
function clamp(n: number, min: number, max: number): number {
|
||||
return Math.min(max, Math.max(min, n));
|
||||
}
|
||||
|
||||
function cssEscape(value: string) {
|
||||
function cssEscape(value: string): string {
|
||||
if (typeof CSS !== "undefined" && typeof CSS.escape === "function") {
|
||||
return CSS.escape(value);
|
||||
}
|
||||
|
|
@ -70,7 +78,7 @@ function computeCardPos(
|
|||
vw: number,
|
||||
vh: number,
|
||||
gap: number,
|
||||
) {
|
||||
): { left: number; top: number } {
|
||||
let left = 12;
|
||||
let top = 12;
|
||||
|
||||
|
|
@ -182,7 +190,7 @@ export function GuidedTour({
|
|||
|
||||
const spotlightRect = useMemo(() => {
|
||||
if (!targetRect || !vw || !vh) return null;
|
||||
const pad = step?.target === "navbar" ? 8 : 14;
|
||||
const pad = step?.target === "navbar" ? 6 : 14;
|
||||
return padded(targetRect, pad, vw, vh);
|
||||
}, [step?.target, targetRect, vw, vh]);
|
||||
|
||||
|
|
@ -246,12 +254,14 @@ export function GuidedTour({
|
|||
const ro = new ResizeObserver(() => schedule());
|
||||
ro.observe(el);
|
||||
window.addEventListener("scroll", schedule, { capture: true, passive: true });
|
||||
window.addEventListener("resize", schedule, { passive: true });
|
||||
|
||||
return () => {
|
||||
window.cancelAnimationFrame(raf);
|
||||
window.clearTimeout(t);
|
||||
ro.disconnect();
|
||||
window.removeEventListener("scroll", schedule, true);
|
||||
window.removeEventListener("resize", schedule);
|
||||
if (rafRef.current != null) {
|
||||
window.cancelAnimationFrame(rafRef.current);
|
||||
rafRef.current = null;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
export { GuidedTour } from "./guided-tour";
|
||||
export type { TourStep } from "./guided-tour";
|
||||
|
||||
export { studioTourSteps } from "./steps/studio";
|
||||
|
|
|
|||
11
studio/frontend/src/features/tour/steps/read-more.tsx
Normal file
11
studio/frontend/src/features/tour/steps/read-more.tsx
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
export function ReadMore({ href = "#" }: { href?: string }) {
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
className="text-emerald-600 underline underline-offset-2 hover:text-emerald-700"
|
||||
>
|
||||
Read more
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
import type { TourStep } from "@/features/tour";
|
||||
import { ReadMore } from "../read-more";
|
||||
|
||||
export const studioBaseModelStep: TourStep = {
|
||||
id: "base-model",
|
||||
target: "studio-base-model",
|
||||
title: "Base model from Hugging Face",
|
||||
body: (
|
||||
<>
|
||||
Search Hub here. Paste <span className="font-mono">org/model</span> too.
|
||||
Pick something close to your domain to save compute. <ReadMore />
|
||||
</>
|
||||
),
|
||||
};
|
||||
|
||||
15
studio/frontend/src/features/tour/steps/studio/dataset.tsx
Normal file
15
studio/frontend/src/features/tour/steps/studio/dataset.tsx
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import type { TourStep } from "@/features/tour";
|
||||
import { ReadMore } from "../read-more";
|
||||
|
||||
export const studioDatasetStep: TourStep = {
|
||||
id: "dataset",
|
||||
target: "studio-dataset",
|
||||
title: "Dataset",
|
||||
body: (
|
||||
<>
|
||||
Search Hub or paste <span className="font-mono">user/dataset</span>.
|
||||
Preview a few rows before you burn hours of compute. <ReadMore />
|
||||
</>
|
||||
),
|
||||
};
|
||||
|
||||
21
studio/frontend/src/features/tour/steps/studio/index.tsx
Normal file
21
studio/frontend/src/features/tour/steps/studio/index.tsx
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import type { TourStep } from "@/features/tour";
|
||||
import { studioBaseModelStep } from "./base-model";
|
||||
import { studioDatasetStep } from "./dataset";
|
||||
import { studioLocalModelStep } from "./local-model";
|
||||
import { studioMethodStep } from "./method";
|
||||
import { studioNavStep } from "./nav";
|
||||
import { studioParamsStep } from "./params";
|
||||
import { studioSaveStep } from "./save";
|
||||
import { studioStartStep } from "./start";
|
||||
|
||||
export const studioTourSteps: TourStep[] = [
|
||||
studioNavStep,
|
||||
studioLocalModelStep,
|
||||
studioBaseModelStep,
|
||||
studioMethodStep,
|
||||
studioDatasetStep,
|
||||
studioParamsStep,
|
||||
studioStartStep,
|
||||
studioSaveStep,
|
||||
];
|
||||
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import type { TourStep } from "@/features/tour";
|
||||
import { ReadMore } from "../read-more";
|
||||
|
||||
export const studioLocalModelStep: TourStep = {
|
||||
id: "local-model",
|
||||
target: "studio-local-model",
|
||||
title: "Local model path",
|
||||
body: (
|
||||
<>
|
||||
Point to a local folder (<span className="font-mono">./models/...</span>)
|
||||
or a custom HF repo. Use this when you already downloaded weights.{" "}
|
||||
<ReadMore />
|
||||
</>
|
||||
),
|
||||
};
|
||||
|
||||
15
studio/frontend/src/features/tour/steps/studio/method.tsx
Normal file
15
studio/frontend/src/features/tour/steps/studio/method.tsx
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import type { TourStep } from "@/features/tour";
|
||||
import { ReadMore } from "../read-more";
|
||||
|
||||
export const studioMethodStep: TourStep = {
|
||||
id: "method",
|
||||
target: "studio-method",
|
||||
title: "Method: QLoRA vs LoRA vs Full",
|
||||
body: (
|
||||
<>
|
||||
QLoRA: lowest VRAM (4-bit). LoRA: fast + solid (16-bit adapters). Full:
|
||||
slowest, highest cost, updates all weights. <ReadMore />
|
||||
</>
|
||||
),
|
||||
};
|
||||
|
||||
14
studio/frontend/src/features/tour/steps/studio/nav.tsx
Normal file
14
studio/frontend/src/features/tour/steps/studio/nav.tsx
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import type { TourStep } from "@/features/tour";
|
||||
|
||||
export const studioNavStep: TourStep = {
|
||||
id: "nav",
|
||||
target: "navbar",
|
||||
title: "Quick orientation",
|
||||
body: (
|
||||
<>
|
||||
Studio is where you fine-tune. Export ships results. Chat is for poking at
|
||||
models. This tour is Studio-only (for now).
|
||||
</>
|
||||
),
|
||||
};
|
||||
|
||||
15
studio/frontend/src/features/tour/steps/studio/params.tsx
Normal file
15
studio/frontend/src/features/tour/steps/studio/params.tsx
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import type { TourStep } from "@/features/tour";
|
||||
import { ReadMore } from "../read-more";
|
||||
|
||||
export const studioParamsStep: TourStep = {
|
||||
id: "params",
|
||||
target: "studio-params",
|
||||
title: "Dial hyperparams",
|
||||
body: (
|
||||
<>
|
||||
Epochs + context length + LR. Keep it boring: small changes, one at a
|
||||
time. <ReadMore />
|
||||
</>
|
||||
),
|
||||
};
|
||||
|
||||
14
studio/frontend/src/features/tour/steps/studio/save.tsx
Normal file
14
studio/frontend/src/features/tour/steps/studio/save.tsx
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import type { TourStep } from "@/features/tour";
|
||||
|
||||
export const studioSaveStep: TourStep = {
|
||||
id: "save",
|
||||
target: "studio-save",
|
||||
title: "Save config",
|
||||
body: (
|
||||
<>
|
||||
Save good runs. Repeatability beats vibe. You can iterate from a known
|
||||
baseline.
|
||||
</>
|
||||
),
|
||||
};
|
||||
|
||||
14
studio/frontend/src/features/tour/steps/studio/start.tsx
Normal file
14
studio/frontend/src/features/tour/steps/studio/start.tsx
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import type { TourStep } from "@/features/tour";
|
||||
|
||||
export const studioStartStep: TourStep = {
|
||||
id: "start",
|
||||
target: "studio-start",
|
||||
title: "Start training",
|
||||
body: (
|
||||
<>
|
||||
One click. If it fails, the error text is the first place to look (token,
|
||||
path, config).
|
||||
</>
|
||||
),
|
||||
};
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue