From 2b6229f049f0934a2ed851f60aea033e60a7d83c Mon Sep 17 00:00:00 2001 From: Shine1i Date: Sun, 15 Feb 2026 19:57:07 +0100 Subject: [PATCH] tour steps split --- .../src/features/studio/studio-page.tsx | 60 +------------------ .../src/features/tour/guided-tour.tsx | 22 +++++-- studio/frontend/src/features/tour/index.ts | 2 +- .../src/features/tour/steps/read-more.tsx | 11 ++++ .../features/tour/steps/studio/base-model.tsx | 15 +++++ .../features/tour/steps/studio/dataset.tsx | 15 +++++ .../src/features/tour/steps/studio/index.tsx | 21 +++++++ .../tour/steps/studio/local-model.tsx | 16 +++++ .../src/features/tour/steps/studio/method.tsx | 15 +++++ .../src/features/tour/steps/studio/nav.tsx | 14 +++++ .../src/features/tour/steps/studio/params.tsx | 15 +++++ .../src/features/tour/steps/studio/save.tsx | 14 +++++ .../src/features/tour/steps/studio/start.tsx | 14 +++++ 13 files changed, 170 insertions(+), 64 deletions(-) create mode 100644 studio/frontend/src/features/tour/steps/read-more.tsx create mode 100644 studio/frontend/src/features/tour/steps/studio/base-model.tsx create mode 100644 studio/frontend/src/features/tour/steps/studio/dataset.tsx create mode 100644 studio/frontend/src/features/tour/steps/studio/index.tsx create mode 100644 studio/frontend/src/features/tour/steps/studio/local-model.tsx create mode 100644 studio/frontend/src/features/tour/steps/studio/method.tsx create mode 100644 studio/frontend/src/features/tour/steps/studio/nav.tsx create mode 100644 studio/frontend/src/features/tour/steps/studio/params.tsx create mode 100644 studio/frontend/src/features/tour/steps/studio/save.tsx create mode 100644 studio/frontend/src/features/tour/steps/studio/start.tsx diff --git a/studio/frontend/src/features/studio/studio-page.tsx b/studio/frontend/src/features/studio/studio-page.tsx index 1a6d0ace85..0a8d824545 100644 --- a/studio/frontend/src/features/studio/studio-page.tsx +++ b/studio/frontend/src/features/studio/studio-page.tsx @@ -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( - () => [ - { - 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 { localStorage.setItem(STUDIO_TOUR_KEY, "skipped")} onComplete={() => localStorage.setItem(STUDIO_TOUR_KEY, "done")} /> diff --git a/studio/frontend/src/features/tour/guided-tour.tsx b/studio/frontend/src/features/tour/guided-tour.tsx index 7c395051ef..d442da24c6 100644 --- a/studio/frontend/src/features/tour/guided-tour.tsx +++ b/studio/frontend/src/features/tour/guided-tour.tsx @@ -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="" 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; diff --git a/studio/frontend/src/features/tour/index.ts b/studio/frontend/src/features/tour/index.ts index 750edb3f4b..6dce052ca3 100644 --- a/studio/frontend/src/features/tour/index.ts +++ b/studio/frontend/src/features/tour/index.ts @@ -1,3 +1,3 @@ export { GuidedTour } from "./guided-tour"; export type { TourStep } from "./guided-tour"; - +export { studioTourSteps } from "./steps/studio"; diff --git a/studio/frontend/src/features/tour/steps/read-more.tsx b/studio/frontend/src/features/tour/steps/read-more.tsx new file mode 100644 index 0000000000..3fcd8567dd --- /dev/null +++ b/studio/frontend/src/features/tour/steps/read-more.tsx @@ -0,0 +1,11 @@ +export function ReadMore({ href = "#" }: { href?: string }) { + return ( + + Read more + + ); +} + diff --git a/studio/frontend/src/features/tour/steps/studio/base-model.tsx b/studio/frontend/src/features/tour/steps/studio/base-model.tsx new file mode 100644 index 0000000000..c22672b27e --- /dev/null +++ b/studio/frontend/src/features/tour/steps/studio/base-model.tsx @@ -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 org/model too. + Pick something close to your domain to save compute. + + ), +}; + diff --git a/studio/frontend/src/features/tour/steps/studio/dataset.tsx b/studio/frontend/src/features/tour/steps/studio/dataset.tsx new file mode 100644 index 0000000000..ef054ee975 --- /dev/null +++ b/studio/frontend/src/features/tour/steps/studio/dataset.tsx @@ -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 user/dataset. + Preview a few rows before you burn hours of compute. + + ), +}; + diff --git a/studio/frontend/src/features/tour/steps/studio/index.tsx b/studio/frontend/src/features/tour/steps/studio/index.tsx new file mode 100644 index 0000000000..a11907d085 --- /dev/null +++ b/studio/frontend/src/features/tour/steps/studio/index.tsx @@ -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, +]; + diff --git a/studio/frontend/src/features/tour/steps/studio/local-model.tsx b/studio/frontend/src/features/tour/steps/studio/local-model.tsx new file mode 100644 index 0000000000..576925ba5d --- /dev/null +++ b/studio/frontend/src/features/tour/steps/studio/local-model.tsx @@ -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 (./models/...) + or a custom HF repo. Use this when you already downloaded weights.{" "} + + + ), +}; + diff --git a/studio/frontend/src/features/tour/steps/studio/method.tsx b/studio/frontend/src/features/tour/steps/studio/method.tsx new file mode 100644 index 0000000000..42f155c595 --- /dev/null +++ b/studio/frontend/src/features/tour/steps/studio/method.tsx @@ -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. + + ), +}; + diff --git a/studio/frontend/src/features/tour/steps/studio/nav.tsx b/studio/frontend/src/features/tour/steps/studio/nav.tsx new file mode 100644 index 0000000000..740df58948 --- /dev/null +++ b/studio/frontend/src/features/tour/steps/studio/nav.tsx @@ -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). + + ), +}; + diff --git a/studio/frontend/src/features/tour/steps/studio/params.tsx b/studio/frontend/src/features/tour/steps/studio/params.tsx new file mode 100644 index 0000000000..e63909fac6 --- /dev/null +++ b/studio/frontend/src/features/tour/steps/studio/params.tsx @@ -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. + + ), +}; + diff --git a/studio/frontend/src/features/tour/steps/studio/save.tsx b/studio/frontend/src/features/tour/steps/studio/save.tsx new file mode 100644 index 0000000000..15bb37bb4f --- /dev/null +++ b/studio/frontend/src/features/tour/steps/studio/save.tsx @@ -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. + + ), +}; + diff --git a/studio/frontend/src/features/tour/steps/studio/start.tsx b/studio/frontend/src/features/tour/steps/studio/start.tsx new file mode 100644 index 0000000000..e04d2c4b4c --- /dev/null +++ b/studio/frontend/src/features/tour/steps/studio/start.tsx @@ -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). + + ), +}; +