Files
pikasTech-HWLAB/web/hwlab-cloud-web/src/hooks/useProviderProfileOptions.ts
T
2026-06-08 04:58:29 +08:00

96 lines
4.1 KiB
TypeScript

import { useEffect, useMemo, useState } from "react";
import { api } from "../services/api/client";
import type { ProviderProfile, ProviderProfileStatusItem } from "../types/domain";
export interface ProviderProfileOption {
value: ProviderProfile;
label: string;
configured?: boolean;
}
export const builtinProviderProfileOrder: readonly ProviderProfile[] = Object.freeze(["deepseek", "dsflash-go", "codex-api", "minimax-m3"]);
const builtinProviderProfileLabels: Readonly<Record<string, string>> = Object.freeze({
deepseek: "DeepSeek",
"dsflash-go": "DeepSeek V4 Flash",
"codex-api": "Codex API",
"minimax-m3": "MiniMax-M3"
});
export function providerProfileLabel(profile: ProviderProfile): string {
return builtinProviderProfileLabels[profile] ?? profile;
}
export function useProviderProfileOptions(selectedProfile: ProviderProfile): { options: ProviderProfileOption[] } {
const [options, setOptions] = useState<ProviderProfileOption[]>(() => defaultProviderProfileOptions(selectedProfile));
useEffect(() => {
let cancelled = false;
void (async () => {
const response = await api.providerProfileCatalog();
if (cancelled) return;
if (!response.ok) {
setOptions(defaultProviderProfileOptions(selectedProfile));
return;
}
const items = profileItems(response.data);
setOptions(items.length > 0 ? optionsFromItems(items, selectedProfile) : defaultProviderProfileOptions(selectedProfile));
})();
return () => {
cancelled = true;
};
}, [selectedProfile]);
const normalized = useMemo(() => ensureSelectedProfile(options, selectedProfile), [options, selectedProfile]);
return { options: normalized };
}
function profileItems(payload: unknown): ProviderProfileStatusItem[] {
const record = payload && typeof payload === "object" ? payload as { items?: ProviderProfileStatusItem[]; data?: { items?: ProviderProfileStatusItem[] } } : {};
return Array.isArray(record.items) ? record.items : Array.isArray(record.data?.items) ? record.data.items : [];
}
function optionsFromItems(items: ProviderProfileStatusItem[], selectedProfile: ProviderProfile): ProviderProfileOption[] {
const ordered = [...items]
.filter((item) => typeof item.profile === "string" && item.profile.trim().length > 0)
.sort((left, right) => compareProviderProfiles(left.profile, right.profile))
.map((item) => ({ value: item.profile, label: providerProfileLabel(item.profile), configured: item.configured } satisfies ProviderProfileOption));
return ensureSelectedProfile(uniqueOptions(ordered), selectedProfile);
}
function defaultProviderProfileOptions(selectedProfile: ProviderProfile): ProviderProfileOption[] {
const base = builtinProviderProfileOrder.map((profile) => ({ value: profile, label: providerProfileLabel(profile) } satisfies ProviderProfileOption));
return ensureSelectedProfile(base, selectedProfile);
}
function ensureSelectedProfile(options: ProviderProfileOption[], selectedProfile: ProviderProfile): ProviderProfileOption[] {
const normalized = String(selectedProfile ?? "").trim();
if (!normalized) return uniqueOptions(options);
if (options.some((option) => option.value === normalized)) return uniqueOptions(options);
return uniqueOptions([{ value: normalized, label: providerProfileLabel(normalized) }, ...options]);
}
function uniqueOptions(options: ProviderProfileOption[]): ProviderProfileOption[] {
const seen = new Set<string>();
const result: ProviderProfileOption[] = [];
for (const option of options) {
const value = String(option.value ?? "").trim();
if (!value || seen.has(value)) continue;
seen.add(value);
result.push({ ...option, value });
}
return result;
}
function compareProviderProfiles(left: ProviderProfile, right: ProviderProfile): number {
const leftIndex = builtinProviderProfileOrder.indexOf(left);
const rightIndex = builtinProviderProfileOrder.indexOf(right);
if (leftIndex >= 0 || rightIndex >= 0) {
if (leftIndex < 0) return 1;
if (rightIndex < 0) return -1;
if (leftIndex !== rightIndex) return leftIndex - rightIndex;
}
return left.localeCompare(right);
}