Files
cc-switch/src/components/providers/ProviderCard.tsx

157 lines
4.7 KiB
TypeScript
Raw Normal View History

2025-10-16 10:49:56 +08:00
import { useMemo } from "react";
import { GripVertical, Link } from "lucide-react";
import { useTranslation } from "react-i18next";
import type {
DraggableAttributes,
DraggableSyntheticListeners,
} from "@dnd-kit/core";
import type { Provider } from "@/types";
import type { AppType } from "@/lib/api";
import { cn } from "@/lib/utils";
import { ProviderActions } from "@/components/providers/ProviderActions";
import UsageFooter from "@/components/UsageFooter";
interface DragHandleProps {
attributes: DraggableAttributes;
listeners: DraggableSyntheticListeners;
isDragging: boolean;
}
interface ProviderCardProps {
provider: Provider;
isCurrent: boolean;
appType: AppType;
onSwitch: (provider: Provider) => void;
onEdit: (provider: Provider) => void;
onDelete: (provider: Provider) => void;
onConfigureUsage: (provider: Provider) => void;
onOpenWebsite: (url: string) => void;
dragHandleProps?: DragHandleProps;
}
const extractApiUrl = (provider: Provider, fallbackText: string) => {
if (provider.websiteUrl) {
return provider.websiteUrl;
}
const config = provider.settingsConfig;
if (config && typeof config === "object") {
const envBase = (config as Record<string, any>)?.env?.ANTHROPIC_BASE_URL;
if (typeof envBase === "string" && envBase.trim()) {
return envBase;
}
const baseUrl = (config as Record<string, any>)?.config;
if (typeof baseUrl === "string" && baseUrl.includes("base_url")) {
const match = baseUrl.match(/base_url\s*=\s*['"]([^'"]+)['"]/);
if (match?.[1]) {
return match[1];
}
}
}
return fallbackText;
};
export function ProviderCard({
provider,
isCurrent,
appType,
onSwitch,
onEdit,
onDelete,
onConfigureUsage,
onOpenWebsite,
dragHandleProps,
}: ProviderCardProps) {
const { t } = useTranslation();
const fallbackUrlText = t("provider.notConfigured", {
defaultValue: "未配置接口地址",
});
const displayUrl = useMemo(() => {
return extractApiUrl(provider, fallbackUrlText);
}, [provider, fallbackUrlText]);
const usageEnabled = provider.meta?.usage_script?.enabled ?? false;
const handleOpenWebsite = () => {
if (!displayUrl || displayUrl === fallbackUrlText) {
return;
}
onOpenWebsite(displayUrl);
};
return (
<div
className={cn(
"rounded-lg border bg-card p-4 shadow-sm transition-[box-shadow,transform] duration-200",
isCurrent
? "border-primary/70 bg-primary/5"
: "border-border hover:border-primary/40",
feat: complete stage 4 cleanup and code formatting This commit completes stage 4 of the refactoring plan, focusing on cleanup and optimization of the modernized codebase. ## Key Changes ### Code Cleanup - Remove legacy `src/lib/styles.ts` (no longer needed) - Remove old modal components (`ImportProgressModal.tsx`, `ProviderList.tsx`) - Streamline `src/lib/tauri-api.ts` from 712 lines to 17 lines (-97.6%) - Remove global `window.api` pollution - Keep only event listeners (`tauriEvents.onProviderSwitched`) - All API calls now use modular `@/lib/api/*` layer ### Type System - Clean up `src/vite-env.d.ts` (remove 156 lines of outdated types) - Remove obsolete global type declarations - All TypeScript checks pass with zero errors ### Code Formatting - Format all source files with Prettier (82 files) - Fix formatting issues in 15 files: - App.tsx and core components - MCP management components - Settings module components - Provider management components - UI components ### Documentation Updates - Update `REFACTORING_CHECKLIST.md` with stage 4 progress - Mark completed tasks in `REFACTORING_MASTER_PLAN.md` ## Impact **Code Reduction:** - Total: -1,753 lines, +384 lines (net -1,369 lines) - tauri-api.ts: 712 → 17 lines (-97.6%) - Removed styles.ts: -82 lines - Removed vite-env.d.ts declarations: -156 lines **Quality Improvements:** - ✅ Zero TypeScript errors - ✅ Zero TODO/FIXME comments - ✅ 100% Prettier compliant - ✅ Zero `window.api` references - ✅ Fully modular API layer ## Testing - [x] TypeScript compilation passes - [x] Code formatting validated - [x] No linting errors Stage 4 completion: 100% Ready for stage 5 (testing and bug fixes)
2025-10-16 12:13:51 +08:00
dragHandleProps?.isDragging &&
"cursor-grabbing border-primary/60 shadow-lg",
2025-10-16 10:49:56 +08:00
)}
>
<div className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
<div className="flex flex-1 items-start gap-3">
<button
type="button"
className={cn(
"mt-1 flex h-8 w-8 items-center justify-center rounded-md border border-transparent text-muted-foreground transition-colors hover:border-muted hover:text-foreground",
dragHandleProps?.isDragging && "border-primary text-primary",
)}
aria-label={t("provider.dragHandle", { defaultValue: "拖拽排序" })}
{...(dragHandleProps?.attributes ?? {})}
{...(dragHandleProps?.listeners ?? {})}
>
<GripVertical className="h-4 w-4" />
</button>
<div className="space-y-1">
<div className="flex flex-wrap items-center gap-2">
<h3 className="text-base font-semibold leading-none">
{provider.name}
</h3>
{isCurrent && (
<span className="rounded-full bg-primary/10 px-2 py-0.5 text-xs font-medium text-primary">
{t("provider.currentlyUsing", { defaultValue: "当前使用" })}
</span>
)}
</div>
{displayUrl && (
<button
type="button"
onClick={handleOpenWebsite}
className="inline-flex items-center gap-1 text-sm text-primary transition-colors hover:underline"
title={displayUrl}
>
<Link className="h-3.5 w-3.5" />
<span className="truncate">{displayUrl}</span>
</button>
)}
</div>
</div>
<ProviderActions
isCurrent={isCurrent}
onSwitch={() => onSwitch(provider)}
onEdit={() => onEdit(provider)}
onConfigureUsage={() => onConfigureUsage(provider)}
onDelete={() => onDelete(provider)}
/>
</div>
<UsageFooter
providerId={provider.id}
appType={appType}
usageEnabled={usageEnabled}
/>
</div>
);
}