fix(studio): UI fixes for chat and studio routes (#4419)
Co-authored-by: Daniel Han <danielhanchen@gmail.com>
This commit is contained in:
parent
65acefd2a6
commit
7b07ad0fa3
5 changed files with 72 additions and 21 deletions
|
|
@ -151,7 +151,7 @@ function ModelSelectorContent({
|
|||
<button
|
||||
type="button"
|
||||
onClick={onEject}
|
||||
className="flex w-full items-center justify-center gap-1.5 rounded-md px-2 py-1.5 text-xs text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
|
||||
className="flex w-full items-center justify-center gap-1.5 rounded-md px-2 py-1.5 text-xs text-destructive transition-colors hover:bg-destructive/10"
|
||||
title="Eject model"
|
||||
>
|
||||
<HugeiconsIcon icon={Logout01Icon} className="size-3.5" />
|
||||
|
|
|
|||
|
|
@ -310,8 +310,8 @@ export function DatasetSection() {
|
|||
if (datasetSource !== "upload") return;
|
||||
if (!uploadedFile) return;
|
||||
if (selectedLocalDataset) return;
|
||||
// Don't clear if this is a direct file upload (not a recipe directory)
|
||||
if (isLikelyLocalDatasetRef(uploadedFile)) return;
|
||||
// Don't clear if this is a direct file upload (e.g. user uploaded a .jsonl/.csv)
|
||||
if (/\.(jsonl|json|csv|parquet|arrow)$/i.test(uploadedFile)) return;
|
||||
selectLocalDataset(null);
|
||||
}, [
|
||||
datasetSource,
|
||||
|
|
|
|||
|
|
@ -372,7 +372,7 @@ export function ModelSection() {
|
|||
|
||||
<div data-tour="studio-base-model" className="flex flex-col gap-2">
|
||||
<span className="flex items-center gap-1.5 text-xs font-medium text-muted-foreground">
|
||||
Base Model
|
||||
Hugging Face Model
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild={true}>
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -9,6 +9,14 @@ import {
|
|||
CollapsibleTrigger,
|
||||
} from "@/components/ui/collapsible";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
Combobox,
|
||||
ComboboxContent,
|
||||
ComboboxEmpty,
|
||||
ComboboxInput,
|
||||
ComboboxItem,
|
||||
ComboboxList,
|
||||
} from "@/components/ui/combobox";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
|
|
@ -37,7 +45,7 @@ import {
|
|||
Settings04Icon,
|
||||
} from "@hugeicons/core-free-icons";
|
||||
import { HugeiconsIcon } from "@hugeicons/react";
|
||||
import { type ReactElement, type ReactNode, useState } from "react";
|
||||
import { type ReactElement, type ReactNode, useRef, useState } from "react";
|
||||
|
||||
function Row({
|
||||
label,
|
||||
|
|
@ -120,6 +128,9 @@ export function ParamsSection(): ReactElement {
|
|||
const showVisionLora = store.isVisionModel && store.isDatasetImage === true;
|
||||
const [loraOpen, setLoraOpen] = useState(false);
|
||||
const [hyperOpen, setHyperOpen] = useState(false);
|
||||
const [ctxInput, setCtxInput] = useState(String(store.contextLength));
|
||||
const ctxAnchorRef = useRef<HTMLDivElement>(null);
|
||||
const ctxItems = CONTEXT_LENGTHS.map(String);
|
||||
const { useEpochs, toggleUseEpochs } = useMaxStepsEpochsToggle({
|
||||
maxSteps: store.maxSteps,
|
||||
epochs: store.epochs,
|
||||
|
|
@ -259,21 +270,61 @@ export function ParamsSection(): ReactElement {
|
|||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</span>
|
||||
<Select
|
||||
value={String(store.contextLength)}
|
||||
onValueChange={(v) => store.setContextLength(Number(v))}
|
||||
>
|
||||
<SelectTrigger className="w-full font-mono">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{CONTEXT_LENGTHS.map((len) => (
|
||||
<SelectItem key={len} value={String(len)} className="font-mono">
|
||||
{len.toLocaleString()}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div ref={ctxAnchorRef}>
|
||||
<Combobox
|
||||
items={ctxItems}
|
||||
filteredItems={ctxItems}
|
||||
filter={null}
|
||||
value={String(store.contextLength)}
|
||||
onValueChange={(v) => {
|
||||
if (v) {
|
||||
const n = Number(v);
|
||||
if (Number.isInteger(n) && n > 0) {
|
||||
store.setContextLength(n);
|
||||
setCtxInput(v);
|
||||
}
|
||||
}
|
||||
}}
|
||||
onInputValueChange={setCtxInput}
|
||||
itemToStringValue={(id) => Number(id).toLocaleString()}
|
||||
autoHighlight={false}
|
||||
>
|
||||
<ComboboxInput
|
||||
placeholder={String(store.contextLength)}
|
||||
className="w-full font-mono"
|
||||
onBlur={() => {
|
||||
const n = Number(ctxInput);
|
||||
if (Number.isInteger(n) && n > 0) {
|
||||
store.setContextLength(n);
|
||||
}
|
||||
setCtxInput(String(store.contextLength));
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key !== "Enter") { return; }
|
||||
const n = Number(ctxInput);
|
||||
if (!Number.isInteger(n) || n <= 0) { return; }
|
||||
// If the typed value isn't an exact preset, prevent the
|
||||
// combobox from selecting a highlighted dropdown item.
|
||||
if (!ctxItems.includes(ctxInput.trim())) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
store.setContextLength(n);
|
||||
setCtxInput(String(n));
|
||||
}}
|
||||
/>
|
||||
<ComboboxContent anchor={ctxAnchorRef}>
|
||||
<ComboboxEmpty>Enter a custom value</ComboboxEmpty>
|
||||
<ComboboxList className="p-1">
|
||||
{(id: string) => (
|
||||
<ComboboxItem key={id} value={id} className="font-mono">
|
||||
{Number(id).toLocaleString()}
|
||||
</ComboboxItem>
|
||||
)}
|
||||
</ComboboxList>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
</div>
|
||||
<p className="text-[10px] text-muted-foreground">
|
||||
Max sequence length for training samples
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { ReadMore, type TourStep } from "@/features/tour";
|
|||
export const studioBaseModelStep: TourStep = {
|
||||
id: "base-model",
|
||||
target: "studio-base-model",
|
||||
title: "Base model from Hugging Face",
|
||||
title: "Hugging Face Model",
|
||||
body: (
|
||||
<>
|
||||
Paste <span className="font-mono">org/model</span> or search. Pick a base
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue