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(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 (
Model {open ? (
{ event.preventDefault(); onLoad(); }} >

使用 @revision 可以固定模型版本。

{loading && progress ? (
{progress.message} {progress.total ? `${Math.round(percent ?? 0)}%` : "•••"}
) : null}

Quick access

{SUGGESTIONS.map((suggestion) => { const selected = metadata?.repoId === suggestion.repo; return ( ); })}
{metadata ? (
{metadata.format} · {formatBytes(metadata.fileSize)} {shortSha(metadata.commitSha)}
) : null}
) : null}
); }