Files
CC-Switch/src/components/providers/ProviderList.tsx
T
YoVinchen 86ebb524f7 refactor(ui): update frontend components for stream check
Update frontend components to use stream check API:
- Refactor ModelTestConfigPanel to use stream check config
- Update API layer to use stream_check commands
- Add HealthStatus type and StreamCheckResult interface
- Update ProviderList to use new health check integration
- Update AutoFailoverConfigPanel with stream check references
- Improve UI layout and configuration options

This completes the frontend migration from model_test to stream_check.
2025-12-10 15:59:55 +08:00

210 lines
5.6 KiB
TypeScript

import { CSS } from "@dnd-kit/utilities";
import { DndContext, closestCenter } from "@dnd-kit/core";
import {
SortableContext,
useSortable,
verticalListSortingStrategy,
} from "@dnd-kit/sortable";
import { useMemo } from "react";
import type { CSSProperties } from "react";
import type { Provider } from "@/types";
import type { AppId } from "@/lib/api";
import { useDragSort } from "@/hooks/useDragSort";
import { useStreamCheck } from "@/hooks/useStreamCheck";
import { ProviderCard } from "@/components/providers/ProviderCard";
import { ProviderEmptyState } from "@/components/providers/ProviderEmptyState";
interface ProviderListProps {
providers: Record<string, Provider>;
currentProviderId: string;
appId: AppId;
onSwitch: (provider: Provider) => void;
onEdit: (provider: Provider) => void;
onDelete: (provider: Provider) => void;
onDuplicate: (provider: Provider) => void;
onConfigureUsage?: (provider: Provider) => void;
onOpenWebsite: (url: string) => void;
onCreate?: () => void;
isLoading?: boolean;
isProxyRunning?: boolean; // 代理服务运行状态
}
export function ProviderList({
providers,
currentProviderId,
appId,
onSwitch,
onEdit,
onDelete,
onDuplicate,
onConfigureUsage,
onOpenWebsite,
onCreate,
isLoading = false,
isProxyRunning = false, // 默认值为 false
}: ProviderListProps) {
const { sortedProviders, sensors, handleDragEnd } = useDragSort(
providers,
appId,
);
// 流式健康检查
const { checkProvider, isChecking } = useStreamCheck(appId);
// 计算代理目标的实际优先级映射 (P1, P2, P3...)
const proxyPriorityMap = useMemo(() => {
// 获取所有启用代理目标的供应商
const proxyTargets = sortedProviders.filter((p) => p.isProxyTarget);
// 按 sortIndex 排序
const sortedTargets = proxyTargets.sort((a, b) => {
const indexA = a.sortIndex ?? Number.MAX_SAFE_INTEGER;
const indexB = b.sortIndex ?? Number.MAX_SAFE_INTEGER;
return indexA - indexB;
});
// 创建优先级映射
const map = new Map<string, number>();
sortedTargets.forEach((provider, index) => {
map.set(provider.id, index + 1); // P1, P2, P3...
});
return map;
}, [sortedProviders]);
const handleTest = (provider: Provider) => {
checkProvider(provider.id, provider.name);
};
if (isLoading) {
return (
<div className="space-y-3">
{[0, 1, 2].map((index) => (
<div
key={index}
className="h-28 w-full rounded-lg border border-dashed border-muted-foreground/40 bg-muted/40"
/>
))}
</div>
);
}
if (sortedProviders.length === 0) {
return <ProviderEmptyState onCreate={onCreate} />;
}
return (
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragEnd={handleDragEnd}
>
<SortableContext
items={sortedProviders.map((provider) => provider.id)}
strategy={verticalListSortingStrategy}
>
<div
className="space-y-3 animate-slide-up"
style={{ animationDelay: "0.1s" }}
>
{sortedProviders.map((provider) => (
<SortableProviderCard
key={provider.id}
provider={provider}
isCurrent={provider.id === currentProviderId}
appId={appId}
onSwitch={onSwitch}
onEdit={onEdit}
onDelete={onDelete}
onDuplicate={onDuplicate}
onConfigureUsage={onConfigureUsage}
onOpenWebsite={onOpenWebsite}
onTest={handleTest}
isTesting={isChecking(provider.id)}
isProxyRunning={isProxyRunning}
proxyPriority={proxyPriorityMap.get(provider.id)}
allProviders={sortedProviders}
/>
))}
</div>
</SortableContext>
</DndContext>
);
}
interface SortableProviderCardProps {
provider: Provider;
isCurrent: boolean;
appId: AppId;
onSwitch: (provider: Provider) => void;
onEdit: (provider: Provider) => void;
onDelete: (provider: Provider) => void;
onDuplicate: (provider: Provider) => void;
onConfigureUsage?: (provider: Provider) => void;
onOpenWebsite: (url: string) => void;
onTest: (provider: Provider) => void;
isTesting: boolean;
isProxyRunning: boolean;
proxyPriority?: number; // 代理目标的实际优先级 (1, 2, 3...)
allProviders?: Provider[]; // 所有供应商列表
}
function SortableProviderCard({
provider,
isCurrent,
appId,
onSwitch,
onEdit,
onDelete,
onDuplicate,
onConfigureUsage,
onOpenWebsite,
onTest,
isTesting,
isProxyRunning,
proxyPriority,
allProviders,
}: SortableProviderCardProps) {
const {
setNodeRef,
attributes,
listeners,
transform,
transition,
isDragging,
} = useSortable({ id: provider.id });
const style: CSSProperties = {
transform: CSS.Transform.toString(transform),
transition,
};
return (
<div ref={setNodeRef} style={style}>
<ProviderCard
provider={provider}
isCurrent={isCurrent}
appId={appId}
onSwitch={onSwitch}
onEdit={onEdit}
onDelete={onDelete}
onDuplicate={onDuplicate}
onConfigureUsage={
onConfigureUsage ? (item) => onConfigureUsage(item) : () => undefined
}
onOpenWebsite={onOpenWebsite}
onTest={onTest}
isTesting={isTesting}
isProxyRunning={isProxyRunning}
proxyPriority={proxyPriority}
allProviders={allProviders}
dragHandleProps={{
attributes,
listeners,
isDragging,
}}
/>
</div>
);
}