diff --git a/src/App.tsx b/src/App.tsx index e72469b..1fa285b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -59,7 +59,7 @@ function App() { if (event.appType === activeApp) { await refetch(); } - } + }, ); } catch (error) { console.error("[App] Failed to subscribe provider switch event", error); @@ -99,6 +99,22 @@ function App() { setConfirmDelete(null); }; + // 复制供应商 + const handleDuplicateProvider = async (provider: Provider) => { + const duplicatedProvider: Omit = { + name: `${provider.name} copy`, + settingsConfig: JSON.parse(JSON.stringify(provider.settingsConfig)), // 深拷贝 + websiteUrl: provider.websiteUrl, + category: provider.category, + meta: provider.meta + ? JSON.parse(JSON.stringify(provider.meta)) + : undefined, // 深拷贝 + // sortIndex 不复制,让它按 createdAt 自然排序 + }; + + await addProvider(duplicatedProvider); + }; + // 导入配置成功后刷新 const handleImportSuccess = async () => { await refetch(); @@ -136,7 +152,7 @@ function App() { size="icon" onClick={() => setIsEditMode(!isEditMode)} title={t( - isEditMode ? "header.exitEditMode" : "header.enterEditMode" + isEditMode ? "header.exitEditMode" : "header.enterEditMode", )} className={ isEditMode @@ -177,6 +193,7 @@ function App() { onSwitch={switchProvider} onEdit={setEditingProvider} onDelete={setConfirmDelete} + onDuplicate={handleDuplicateProvider} onConfigureUsage={setUsageProvider} onOpenWebsite={handleOpenWebsite} onCreate={() => setIsAddOpen(true)} diff --git a/src/components/providers/ProviderCard.tsx b/src/components/providers/ProviderCard.tsx index e6011e1..abe407b 100644 --- a/src/components/providers/ProviderCard.tsx +++ b/src/components/providers/ProviderCard.tsx @@ -1,5 +1,5 @@ import { useMemo } from "react"; -import { MoveVertical } from "lucide-react"; +import { MoveVertical, Copy } from "lucide-react"; import { useTranslation } from "react-i18next"; import type { DraggableAttributes, @@ -27,6 +27,7 @@ interface ProviderCardProps { onDelete: (provider: Provider) => void; onConfigureUsage: (provider: Provider) => void; onOpenWebsite: (url: string) => void; + onDuplicate: (provider: Provider) => void; dragHandleProps?: DragHandleProps; } @@ -66,6 +67,7 @@ export function ProviderCard({ onDelete, onConfigureUsage, onOpenWebsite, + onDuplicate, dragHandleProps, }: ProviderCardProps) { const { t } = useTranslation(); @@ -107,7 +109,8 @@ export function ProviderCard({ isEditMode ? "w-8 mr-3 border-muted hover:border-border-hover hover:text-foreground hover:bg-muted/50 opacity-100" : "w-0 mr-0 border-transparent opacity-0 pointer-events-none", - dragHandleProps?.isDragging && "border-border-active text-primary bg-primary/10 cursor-grabbing", + dragHandleProps?.isDragging && + "border-border-active text-primary bg-primary/10 cursor-grabbing", )} aria-label={t("provider.dragHandle")} {...(dragHandleProps?.attributes ?? {})} @@ -116,6 +119,21 @@ export function ProviderCard({ + +

@@ -124,7 +142,7 @@ export function ProviderCard({ {t("provider.currentlyUsing")} diff --git a/src/components/providers/ProviderList.tsx b/src/components/providers/ProviderList.tsx index 5531c2b..0481b56 100644 --- a/src/components/providers/ProviderList.tsx +++ b/src/components/providers/ProviderList.tsx @@ -20,6 +20,7 @@ interface ProviderListProps { 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; @@ -34,6 +35,7 @@ export function ProviderList({ onSwitch, onEdit, onDelete, + onDuplicate, onConfigureUsage, onOpenWebsite, onCreate, @@ -82,6 +84,7 @@ export function ProviderList({ onSwitch={onSwitch} onEdit={onEdit} onDelete={onDelete} + onDuplicate={onDuplicate} onConfigureUsage={onConfigureUsage} onOpenWebsite={onOpenWebsite} /> @@ -100,6 +103,7 @@ interface SortableProviderCardProps { onSwitch: (provider: Provider) => void; onEdit: (provider: Provider) => void; onDelete: (provider: Provider) => void; + onDuplicate: (provider: Provider) => void; onConfigureUsage?: (provider: Provider) => void; onOpenWebsite: (url: string) => void; } @@ -112,6 +116,7 @@ function SortableProviderCard({ onSwitch, onEdit, onDelete, + onDuplicate, onConfigureUsage, onOpenWebsite, }: SortableProviderCardProps) { @@ -139,6 +144,7 @@ function SortableProviderCard({ onSwitch={onSwitch} onEdit={onEdit} onDelete={onDelete} + onDuplicate={onDuplicate} onConfigureUsage={ onConfigureUsage ? (item) => onConfigureUsage(item) : () => undefined } diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 96073c2..934858f 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -72,6 +72,7 @@ "removeFromClaudePlugin": "Remove from Claude plugin", "dragToReorder": "Drag to reorder", "dragHandle": "Drag to reorder", + "duplicate": "Duplicate", "sortUpdateFailed": "Failed to update sort order", "configureUsage": "Configure usage query", "name": "Provider Name", diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json index bf91abb..9f8bf9c 100644 --- a/src/i18n/locales/zh.json +++ b/src/i18n/locales/zh.json @@ -72,6 +72,7 @@ "removeFromClaudePlugin": "从 Claude 插件移除", "dragToReorder": "拖拽以重新排序", "dragHandle": "拖拽排序", + "duplicate": "复制", "sortUpdateFailed": "排序更新失败", "configureUsage": "配置用量查询", "name": "供应商名称",