Files
Jyotisha/frontend/src/components/model-selector.tsx
T
2026-07-17 14:20:42 +08:00

88 lines
3.2 KiB
TypeScript

"use client";
import { Popover } from "@base-ui/react/popover";
import { Check, ChevronDown } from "lucide-react";
import { useId, useState } from "react";
import type { KeyboardEvent } from "react";
import type { PublicLanguageModel } from "@/lib/public-models";
type ModelSelectorProps = {
readonly models: readonly PublicLanguageModel[];
readonly selectedModelId: string;
readonly disabled?: boolean;
readonly onSelect: (modelId: string) => void;
};
export function ModelSelector({
models,
selectedModelId,
disabled = false,
onSelect,
}: ModelSelectorProps) {
const [open, setOpen] = useState(false);
const groupName = useId();
const selectedModel = models.find((model) => model.id === selectedModelId);
const unavailable = models.length === 0;
function moveRadioSelection(event: KeyboardEvent<HTMLInputElement>, index: number) {
const step = event.key === "ArrowDown" || event.key === "ArrowRight"
? 1
: event.key === "ArrowUp" || event.key === "ArrowLeft"
? -1
: 0;
if (step === 0 || models.length < 2) return;
event.preventDefault();
const nextIndex = (index + step + models.length) % models.length;
const nextModel = models[nextIndex];
if (!nextModel) return;
onSelect(nextModel.id);
setOpen(false);
}
return (
<Popover.Root open={open} onOpenChange={setOpen}>
<Popover.Trigger
className="model-selector-trigger"
type="button"
disabled={disabled || unavailable}
aria-label={selectedModel ? `当前模型:${selectedModel.label},点击切换` : "模型暂不可用"}
>
<b>{selectedModel?.label ?? "暂不可用"}</b>
<ChevronDown aria-hidden="true" />
</Popover.Trigger>
<Popover.Portal>
<Popover.Positioner className="model-selector-positioner" side="top" align="start" sideOffset={8} collisionPadding={12}>
<Popover.Popup className="model-selector-popup">
<Popover.Title className="model-selector-title"></Popover.Title>
<fieldset className="model-selector-options">
<legend className="sr-only">使</legend>
{models.map((model, index) => (
<label className="model-selector-option" data-selected={model.id === selectedModelId ? "" : undefined} key={model.id}>
<input
className="sr-only"
type="radio"
name={groupName}
value={model.id}
checked={model.id === selectedModelId}
onChange={() => onSelect(model.id)}
onClick={() => {
if (model.id === selectedModelId) onSelect(model.id);
setOpen(false);
}}
onKeyDown={(event) => moveRadioSelection(event, index)}
/>
<span className="model-selector-copy">
<b>{model.label}</b>
</span>
<Check className="model-selector-check" aria-hidden="true" />
</label>
))}
</fieldset>
</Popover.Popup>
</Popover.Positioner>
</Popover.Portal>
</Popover.Root>
);
}