Spaces:
Sleeping
Sleeping
Download src/components/ModelPicker.tsx from Orion-zhen/tokenize-it: direct link, hf CLI and curl.
- Browser
- Download file 7.79 kB
-
https://huggingface.co/spaces/Orion-zhen/tokenize-it/resolve/main/src/components/ModelPicker.tsx
- Command line
-
hf download hf://spaces/Orion-zhen/tokenize-it/src/components/ModelPicker.tsx
-
curl -L -o ModelPicker.tsx https://huggingface.co/spaces/Orion-zhen/tokenize-it/resolve/main/src/components/ModelPicker.tsx
7.79 kB
| import { | |
| Check, | |
| ChevronDown, | |
| CloudDownload, | |
| LoaderCircle, | |
| Search, | |
| Waypoints, | |
| } from "lucide-react"; | |
| import { useEffect, useRef } from "react"; | |
| import { formatBytes, shortSha } from "../lib/format"; | |
| import type { LoadProgress, TokenizerMetadata } from "../tokenizer/types"; | |
| interface ModelPickerProps { | |
| open: boolean; | |
| reference: string; | |
| metadata: TokenizerMetadata | null; | |
| progress: LoadProgress | null; | |
| loading: boolean; | |
| onOpenChange: (open: boolean) => void; | |
| onReferenceChange: (value: string) => void; | |
| onLoad: () => void; | |
| } | |
| const SUGGESTIONS = [ | |
| { name: "Qwen 3.8", repo: "Qwen/Qwen3.8-2.4T-A95B", format: "Tokenizers" }, | |
| { name: "GLM 5.3 Flash", repo: "zai-org/GLM-5.3-Flash", format: "Tokenizers" }, | |
| { name: "Kimi K3", repo: "moonshotai/Kimi-K3", format: "Tiktoken" }, | |
| { | |
| name: "DeepSeek V4 Pro", | |
| repo: "deepseek-ai/DeepSeek-V4-Pro-0813", | |
| format: "Tokenizers", | |
| }, | |
| { name: "Gemma 4", repo: "google/gemma-4-31B-it", format: "Tokenizers" }, | |
| { | |
| name: "Muse Glimmer", | |
| repo: "meta-models/Muse-Glimmer-30B", | |
| format: "Tokenizers", | |
| }, | |
| { | |
| name: "Mistral Small 4", | |
| repo: "mistralai/Mistral-Small-4-119B-2603", | |
| format: "Tokenizers", | |
| }, | |
| { name: "GPT-OSS", repo: "openai/gpt-oss-20b", format: "Tokenizers" }, | |
| ]; | |
| function modelName(reference: string, metadata: TokenizerMetadata | null): string { | |
| const repo = metadata?.repoId ?? reference.split("@")[0] ?? reference; | |
| return repo.split("/").at(-1) || "选择模型"; | |
| } | |
| export function ModelPicker({ | |
| open, | |
| reference, | |
| metadata, | |
| progress, | |
| loading, | |
| onOpenChange, | |
| onReferenceChange, | |
| onLoad, | |
| }: ModelPickerProps) { | |
| const containerRef = useRef<HTMLDivElement>(null); | |
| useEffect(() => { | |
| if (!open) return; | |
| const closeOnOutsideClick = (event: PointerEvent) => { | |
| if (!containerRef.current?.contains(event.target as Node)) onOpenChange(false); | |
| }; | |
| const closeOnEscape = (event: KeyboardEvent) => { | |
| if (event.key === "Escape") onOpenChange(false); | |
| }; | |
| document.addEventListener("pointerdown", closeOnOutsideClick); | |
| document.addEventListener("keydown", closeOnEscape); | |
| return () => { | |
| document.removeEventListener("pointerdown", closeOnOutsideClick); | |
| document.removeEventListener("keydown", closeOnEscape); | |
| }; | |
| }, [open, onOpenChange]); | |
| const percent = | |
| progress?.total && progress.total > 0 | |
| ? Math.min(100, (progress.loaded / progress.total) * 100) | |
| : null; | |
| return ( | |
| <div className="relative min-w-0" ref={containerRef}> | |
| <span className="toolbar-label">Model</span> | |
| <button | |
| className="toolbar-control w-[min(19rem,72vw)] justify-between" | |
| type="button" | |
| aria-expanded={open} | |
| onClick={() => onOpenChange(!open)} | |
| > | |
| <span className="flex min-w-0 items-center gap-2.5"> | |
| {loading ? ( | |
| <LoaderCircle className="shrink-0 animate-spin text-[var(--accent)]" size={17} /> | |
| ) : ( | |
| <Waypoints className="shrink-0 text-[var(--accent)]" size={17} /> | |
| )} | |
| <span className="truncate">{modelName(reference, metadata)}</span> | |
| </span> | |
| <ChevronDown | |
| className={`shrink-0 text-[var(--muted)] transition-transform ${open ? "rotate-180" : ""}`} | |
| size={16} | |
| /> | |
| </button> | |
| {open ? ( | |
| <div className="popover-panel absolute right-0 top-[calc(100%+10px)] z-30 w-[min(27rem,calc(100vw-2rem))] p-3"> | |
| <form | |
| className="border-b border-[var(--line)] p-2 pb-4" | |
| onSubmit={(event) => { | |
| event.preventDefault(); | |
| onLoad(); | |
| }} | |
| > | |
| <label className="field-label" htmlFor="model-reference"> | |
| Hugging Face 仓库 | |
| </label> | |
| <div className="relative mt-2"> | |
| <Search | |
| className="pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-[var(--muted)]" | |
| size={15} | |
| aria-hidden="true" | |
| /> | |
| <input | |
| id="model-reference" | |
| className="text-field py-3 pl-9 pr-24 font-mono text-[12px]" | |
| value={reference} | |
| onChange={(event) => onReferenceChange(event.target.value)} | |
| placeholder="owner/model@revision" | |
| spellCheck={false} | |
| autoComplete="off" | |
| /> | |
| <button | |
| className="primary-button absolute bottom-1.5 right-1.5 top-1.5 px-3" | |
| type="submit" | |
| disabled={loading || !reference.trim()} | |
| > | |
| {loading ? <LoaderCircle className="animate-spin" size={14} /> : <CloudDownload size={14} />} | |
| 加载 | |
| </button> | |
| </div> | |
| <p className="mt-2 text-[10px] leading-4 text-[var(--muted)]"> | |
| 使用 <code>@revision</code> 可以固定模型版本。 | |
| </p> | |
| </form> | |
| {loading && progress ? ( | |
| <div className="m-2 rounded-xl bg-[var(--soft)] p-3" role="status"> | |
| <div className="flex items-center justify-between gap-3 text-[11px]"> | |
| <span className="text-[var(--muted-strong)]">{progress.message}</span> | |
| <span className="font-mono text-[var(--muted)]"> | |
| {progress.total ? `${Math.round(percent ?? 0)}%` : "•••"} | |
| </span> | |
| </div> | |
| <div className="mt-2 h-1 overflow-hidden rounded-full bg-[var(--line-strong)]"> | |
| <div | |
| className={`h-full rounded-full bg-[var(--accent)] ${percent === null ? "w-1/3 animate-[loading_1.4s_ease-in-out_infinite]" : ""}`} | |
| style={percent === null ? undefined : { width: `${percent}%` }} | |
| /> | |
| </div> | |
| </div> | |
| ) : null} | |
| <div className="max-h-[min(26rem,50vh)] space-y-1 overflow-y-auto p-2 pt-3"> | |
| <p className="mb-2 px-2 text-[9px] font-bold uppercase tracking-[0.18em] text-[var(--muted)]"> | |
| Quick access | |
| </p> | |
| {SUGGESTIONS.map((suggestion) => { | |
| const selected = metadata?.repoId === suggestion.repo; | |
| return ( | |
| <button | |
| className="model-option" | |
| type="button" | |
| key={suggestion.repo} | |
| onClick={() => onReferenceChange(suggestion.repo)} | |
| > | |
| <span className="grid size-8 shrink-0 place-items-center rounded-lg border border-[var(--line)] bg-[var(--soft)] text-[var(--accent)]"> | |
| <Waypoints size={15} /> | |
| </span> | |
| <span className="min-w-0 flex-1 text-left"> | |
| <span className="block truncate text-xs font-semibold text-[var(--ink)]"> | |
| {suggestion.name} | |
| </span> | |
| <span className="mt-0.5 block truncate font-mono text-[9px] text-[var(--muted)]"> | |
| {suggestion.format} · {suggestion.repo} | |
| </span> | |
| </span> | |
| {selected ? <Check className="text-[var(--accent)]" size={15} /> : null} | |
| </button> | |
| ); | |
| })} | |
| </div> | |
| {metadata ? ( | |
| <div className="mx-2 mb-2 flex items-center justify-between gap-3 rounded-xl border border-[var(--line)] bg-[var(--soft)] px-3 py-2 text-[9px] text-[var(--muted)]"> | |
| <span>{metadata.format} · {formatBytes(metadata.fileSize)}</span> | |
| <span className="font-mono">{shortSha(metadata.commitSha)}</span> | |
| </div> | |
| ) : null} | |
| </div> | |
| ) : null} | |
| </div> | |
| ); | |
| } | |