LeLab / src /components /landing /DatasetPicker.tsx
GitHub CI
Sync from leLab @ 7317f7103e3a9d7f45fe4c0d6e4660a8f9d295e3
fc9bd9f
import React, { useState } from "react";
import { Plus, ExternalLink } from "lucide-react";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command";
import { DatasetItem } from "@/lib/replayApi";
interface DatasetPickerProps {
datasets: DatasetItem[];
loading: boolean;
onPickExisting: (item: DatasetItem) => void;
onCreateNew: (name: string) => void;
onOpenCustom: (repoId: string) => void;
children: React.ReactNode;
}
const REPO_ID_RE = /^[\w.\-]+\/[\w.\-]+$/;
const NAME_RE = /^[A-Za-z0-9._-]+$/;
const DatasetPicker: React.FC<DatasetPickerProps> = ({
datasets,
loading,
onPickExisting,
onCreateNew,
onOpenCustom,
children,
}) => {
const [open, setOpen] = useState(false);
const [query, setQuery] = useState("");
const trimmed = query.trim();
const matchesExisting = datasets.some(
(d) => d.repo_id.toLowerCase() === trimmed.toLowerCase(),
);
const isRepoId = REPO_ID_RE.test(trimmed);
const isName = NAME_RE.test(trimmed) && !trimmed.includes("/");
const canCreate = trimmed.length > 0 && isName && !matchesExisting;
const canOpenCustom = isRepoId && !matchesExisting;
const createDisabled = matchesExisting || (trimmed !== "" && !canCreate);
const createLabel = matchesExisting
? "Already exists"
: trimmed === ""
? "Create new dataset…"
: canCreate
? `Create "${trimmed}"`
: 'Use a name without "/"';
const handleFooterCreate = () => {
if (createDisabled) return;
onCreateNew(trimmed);
reset();
};
const localDatasets = datasets.filter((d) => d.source === "local" || d.source === "both");
const hubDatasets = datasets.filter((d) => d.source === "hub");
const reset = () => {
setQuery("");
setOpen(false);
};
const handlePick = (item: DatasetItem) => {
onPickExisting(item);
reset();
};
const handleCreate = () => {
if (!canCreate) return;
onCreateNew(trimmed);
reset();
};
const handleOpenCustom = () => {
if (!canOpenCustom) return;
onOpenCustom(trimmed);
reset();
};
const renderItem = (d: DatasetItem) => (
<CommandItem
key={d.repo_id}
value={d.repo_id}
onSelect={() => handlePick(d)}
className="text-white aria-selected:bg-gray-700"
>
<span className="flex-1 truncate">{d.repo_id}</span>
{d.source === "both" && (
<span className="text-xs text-gray-400 mr-2">on Hub</span>
)}
{d.private && (
<span className="text-xs text-amber-400">private</span>
)}
</CommandItem>
);
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>{children}</PopoverTrigger>
<PopoverContent
className="w-[320px] p-0 bg-gray-800 border-gray-700 text-white"
align="end"
>
<Command className="bg-gray-800">
<CommandInput
placeholder="Search, type a new name, or org/name…"
value={query}
onValueChange={(v) => setQuery(v.replace(/[^A-Za-z0-9._\-/]/g, "_"))}
onKeyDown={(e) => {
if (e.key !== "Enter") return;
if (canCreate) {
e.preventDefault();
handleCreate();
} else if (canOpenCustom) {
e.preventDefault();
handleOpenCustom();
}
}}
className="text-white"
/>
<CommandList>
{datasets.length === 0 && !canCreate && !canOpenCustom && (
<CommandEmpty className="py-4 text-sm text-gray-400 text-center">
{loading
? "Loading datasets…"
: "No datasets yet. Type a name to create one."}
</CommandEmpty>
)}
{localDatasets.length > 0 && (
<CommandGroup heading="Local">
{localDatasets.map(renderItem)}
</CommandGroup>
)}
{hubDatasets.length > 0 && (
<CommandGroup heading="Hugging Face">
{hubDatasets.map(renderItem)}
</CommandGroup>
)}
{canOpenCustom && (
<CommandGroup heading="Custom repo">
<CommandItem
value={`__open__${trimmed}`}
onSelect={handleOpenCustom}
className="text-white aria-selected:bg-gray-700"
>
<ExternalLink className="mr-2 h-4 w-4" />
Open &quot;{trimmed}&quot; in viewer
</CommandItem>
</CommandGroup>
)}
</CommandList>
<button
type="button"
onClick={handleFooterCreate}
disabled={createDisabled}
className="flex w-full items-center gap-2 border-t border-gray-700 px-3 py-2 text-sm text-white hover:bg-gray-700 disabled:cursor-not-allowed disabled:text-gray-500 disabled:hover:bg-transparent"
>
<Plus className="h-4 w-4" />
{createLabel}
</button>
</Command>
</PopoverContent>
</Popover>
);
};
export default DatasetPicker;