feat: complete stage 2 core refactor
This commit is contained in:
614
src/App.tsx
614
src/App.tsx
@@ -1,399 +1,335 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Provider } from "./types";
|
||||
import { AppType } from "./lib/tauri-api";
|
||||
import ProviderList from "./components/ProviderList";
|
||||
import AddProviderModal from "./components/AddProviderModal";
|
||||
import EditProviderModal from "./components/EditProviderModal";
|
||||
import { ConfirmDialog } from "./components/ConfirmDialog";
|
||||
import { AppSwitcher } from "./components/AppSwitcher";
|
||||
import SettingsModal from "./components/SettingsModal";
|
||||
import { UpdateBadge } from "./components/UpdateBadge";
|
||||
import { Plus, Settings, Moon, Sun } from "lucide-react";
|
||||
import McpPanel from "./components/mcp/McpPanel";
|
||||
import { buttonStyles } from "./lib/styles";
|
||||
import { useDarkMode } from "./hooks/useDarkMode";
|
||||
import { extractErrorMessage } from "./utils/errorUtils";
|
||||
import { toast } from "sonner";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { Plus, Settings } from "lucide-react";
|
||||
import type { Provider, UsageScript } from "@/types";
|
||||
import {
|
||||
useProvidersQuery,
|
||||
useAddProviderMutation,
|
||||
useUpdateProviderMutation,
|
||||
useDeleteProviderMutation,
|
||||
useSwitchProviderMutation,
|
||||
} from "@/lib/query";
|
||||
import { providersApi, type AppType } from "@/lib/api";
|
||||
import { extractErrorMessage } from "@/utils/errorUtils";
|
||||
import { AppSwitcher } from "@/components/AppSwitcher";
|
||||
import { ModeToggle } from "@/components/mode-toggle";
|
||||
import { ProviderList } from "@/components/providers/ProviderList";
|
||||
import { AddProviderDialog } from "@/components/providers/AddProviderDialog";
|
||||
import { EditProviderDialog } from "@/components/providers/EditProviderDialog";
|
||||
import { ConfirmDialog } from "@/components/ConfirmDialog";
|
||||
import SettingsModal from "@/components/SettingsModal";
|
||||
import { UpdateBadge } from "@/components/UpdateBadge";
|
||||
import UsageScriptModal from "@/components/UsageScriptModal";
|
||||
import McpPanel from "@/components/mcp/McpPanel";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
interface ProviderSwitchEvent {
|
||||
appType: string;
|
||||
providerId: string;
|
||||
}
|
||||
|
||||
function App() {
|
||||
const { t } = useTranslation();
|
||||
const { isDarkMode, toggleDarkMode } = useDarkMode();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const [activeApp, setActiveApp] = useState<AppType>("claude");
|
||||
const [providers, setProviders] = useState<Record<string, Provider>>({});
|
||||
const [currentProviderId, setCurrentProviderId] = useState<string>("");
|
||||
const [isAddModalOpen, setIsAddModalOpen] = useState(false);
|
||||
const [editingProviderId, setEditingProviderId] = useState<string | null>(
|
||||
null,
|
||||
);
|
||||
const [notification, setNotification] = useState<{
|
||||
message: string;
|
||||
type: "success" | "error";
|
||||
} | null>(null);
|
||||
const [isNotificationVisible, setIsNotificationVisible] = useState(false);
|
||||
const [confirmDialog, setConfirmDialog] = useState<{
|
||||
isOpen: boolean;
|
||||
title: string;
|
||||
message: string;
|
||||
onConfirm: () => void;
|
||||
} | null>(null);
|
||||
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
|
||||
const [isAddOpen, setIsAddOpen] = useState(false);
|
||||
const [isMcpOpen, setIsMcpOpen] = useState(false);
|
||||
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const [editingProvider, setEditingProvider] = useState<Provider | null>(null);
|
||||
const [usageProvider, setUsageProvider] = useState<Provider | null>(null);
|
||||
const [confirmDelete, setConfirmDelete] = useState<Provider | null>(null);
|
||||
|
||||
// 设置通知的辅助函数
|
||||
const showNotification = (
|
||||
message: string,
|
||||
type: "success" | "error",
|
||||
duration = 3000,
|
||||
) => {
|
||||
// 清除之前的定时器
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
}
|
||||
const { data, isLoading, refetch } = useProvidersQuery(activeApp);
|
||||
const providers = useMemo(() => data?.providers ?? {}, [data]);
|
||||
const currentProviderId = data?.currentProviderId ?? "";
|
||||
|
||||
// 立即显示通知
|
||||
setNotification({ message, type });
|
||||
setIsNotificationVisible(true);
|
||||
const addProviderMutation = useAddProviderMutation(activeApp);
|
||||
const updateProviderMutation = useUpdateProviderMutation(activeApp);
|
||||
const deleteProviderMutation = useDeleteProviderMutation(activeApp);
|
||||
const switchProviderMutation = useSwitchProviderMutation(activeApp);
|
||||
|
||||
// 设置淡出定时器
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
setIsNotificationVisible(false);
|
||||
// 等待淡出动画完成后清除通知
|
||||
setTimeout(() => {
|
||||
setNotification(null);
|
||||
timeoutRef.current = null;
|
||||
}, 300); // 与CSS动画时间匹配
|
||||
}, duration);
|
||||
};
|
||||
|
||||
// 加载供应商列表
|
||||
useEffect(() => {
|
||||
loadProviders();
|
||||
}, [activeApp]); // 当切换应用时重新加载
|
||||
|
||||
// 清理定时器
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
// 监听托盘切换事件(包括菜单切换)
|
||||
useEffect(() => {
|
||||
let unlisten: (() => void) | null = null;
|
||||
let unsubscribe: (() => void) | undefined;
|
||||
|
||||
const setupListener = async () => {
|
||||
try {
|
||||
unlisten = await window.api.onProviderSwitched(async (data) => {
|
||||
if (import.meta.env.DEV) {
|
||||
console.log(t("console.providerSwitchReceived"), data);
|
||||
}
|
||||
|
||||
// 如果当前应用类型匹配,则重新加载数据
|
||||
if (data.appType === activeApp) {
|
||||
await loadProviders();
|
||||
}
|
||||
|
||||
// 若为 Claude,则同步插件配置
|
||||
if (data.appType === "claude") {
|
||||
await syncClaudePlugin(data.providerId, true);
|
||||
}
|
||||
});
|
||||
unsubscribe = await window.api.onProviderSwitched(
|
||||
async (event: ProviderSwitchEvent) => {
|
||||
if (event.appType === activeApp) {
|
||||
await refetch();
|
||||
}
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(t("console.setupListenerFailed"), error);
|
||||
console.error("[App] Failed to subscribe provider switch event", error);
|
||||
}
|
||||
};
|
||||
|
||||
setupListener();
|
||||
|
||||
// 清理监听器
|
||||
return () => {
|
||||
if (unlisten) {
|
||||
unlisten();
|
||||
}
|
||||
unsubscribe?.();
|
||||
};
|
||||
}, [activeApp]);
|
||||
}, [activeApp, refetch]);
|
||||
|
||||
const loadProviders = async () => {
|
||||
const loadedProviders = await window.api.getProviders(activeApp);
|
||||
const currentId = await window.api.getCurrentProvider(activeApp);
|
||||
setProviders(loadedProviders);
|
||||
setCurrentProviderId(currentId);
|
||||
|
||||
// 如果供应商列表为空,尝试自动从 live 导入一条默认供应商
|
||||
if (Object.keys(loadedProviders).length === 0) {
|
||||
await handleAutoImportDefault();
|
||||
}
|
||||
};
|
||||
|
||||
// 生成唯一ID
|
||||
const generateId = () => {
|
||||
return crypto.randomUUID();
|
||||
};
|
||||
|
||||
const handleAddProvider = async (provider: Omit<Provider, "id">) => {
|
||||
const newProvider: Provider = {
|
||||
...provider,
|
||||
id: generateId(),
|
||||
createdAt: Date.now(), // 添加创建时间戳
|
||||
};
|
||||
await window.api.addProvider(newProvider, activeApp);
|
||||
await loadProviders();
|
||||
setIsAddModalOpen(false);
|
||||
// 更新托盘菜单
|
||||
await window.api.updateTrayMenu();
|
||||
};
|
||||
|
||||
const handleEditProvider = async (provider: Provider) => {
|
||||
try {
|
||||
await window.api.updateProvider(provider, activeApp);
|
||||
await loadProviders();
|
||||
setEditingProviderId(null);
|
||||
// 显示编辑成功提示
|
||||
showNotification(t("notifications.providerSaved"), "success", 2000);
|
||||
// 更新托盘菜单
|
||||
await window.api.updateTrayMenu();
|
||||
} catch (error) {
|
||||
console.error(t("console.updateProviderFailed"), error);
|
||||
setEditingProviderId(null);
|
||||
const errorMessage = extractErrorMessage(error);
|
||||
const message = errorMessage
|
||||
? t("notifications.saveFailed", { error: errorMessage })
|
||||
: t("notifications.saveFailedGeneric");
|
||||
showNotification(message, "error", errorMessage ? 6000 : 3000);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteProvider = async (id: string) => {
|
||||
const provider = providers[id];
|
||||
setConfirmDialog({
|
||||
isOpen: true,
|
||||
title: t("confirm.deleteProvider"),
|
||||
message: t("confirm.deleteProviderMessage", { name: provider?.name }),
|
||||
onConfirm: async () => {
|
||||
await window.api.deleteProvider(id, activeApp);
|
||||
await loadProviders();
|
||||
setConfirmDialog(null);
|
||||
showNotification(t("notifications.providerDeleted"), "success");
|
||||
// 更新托盘菜单
|
||||
await window.api.updateTrayMenu();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 同步 Claude 插件配置(按设置决定是否联动;开启时:非官方写入,官方移除)
|
||||
const syncClaudePlugin = async (providerId: string, silent = false) => {
|
||||
try {
|
||||
const settings = await window.api.getSettings();
|
||||
if (!(settings as any)?.enableClaudePluginIntegration) {
|
||||
// 未开启联动:不执行写入/移除
|
||||
return;
|
||||
}
|
||||
const provider = providers[providerId];
|
||||
if (!provider) return;
|
||||
const isOfficial = provider.category === "official";
|
||||
await window.api.applyClaudePluginConfig({ official: isOfficial });
|
||||
if (!silent) {
|
||||
showNotification(
|
||||
isOfficial
|
||||
? t("notifications.removedFromClaudePlugin")
|
||||
: t("notifications.appliedToClaudePlugin"),
|
||||
"success",
|
||||
2000,
|
||||
);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error("同步 Claude 插件失败:", error);
|
||||
if (!silent) {
|
||||
const message =
|
||||
error?.message || t("notifications.syncClaudePluginFailed");
|
||||
showNotification(message, "error", 5000);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleSwitchProvider = async (id: string) => {
|
||||
try {
|
||||
const success = await window.api.switchProvider(id, activeApp);
|
||||
if (success) {
|
||||
setCurrentProviderId(id);
|
||||
// 显示重启提示
|
||||
const appName = t(`apps.${activeApp}`);
|
||||
showNotification(
|
||||
t("notifications.switchSuccess", { appName }),
|
||||
"success",
|
||||
2000,
|
||||
);
|
||||
// 更新托盘菜单
|
||||
await window.api.updateTrayMenu();
|
||||
|
||||
if (activeApp === "claude") {
|
||||
await syncClaudePlugin(id, true);
|
||||
}
|
||||
const handleNotify = useCallback(
|
||||
(message: string, type: "success" | "error", duration?: number) => {
|
||||
const options = duration ? { duration } : undefined;
|
||||
if (type === "error") {
|
||||
toast.error(message, options);
|
||||
} else {
|
||||
showNotification(t("notifications.switchFailed"), "error");
|
||||
toast.success(message, options);
|
||||
}
|
||||
} catch (error) {
|
||||
const detail = extractErrorMessage(error);
|
||||
const msg = detail
|
||||
? `${t("notifications.switchFailed")}: ${detail}`
|
||||
: t("notifications.switchFailed");
|
||||
// 详细错误展示稍长时间,便于用户阅读
|
||||
showNotification(msg, "error", detail ? 6000 : 3000);
|
||||
}
|
||||
};
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const handleImportSuccess = async () => {
|
||||
await loadProviders();
|
||||
try {
|
||||
await window.api.updateTrayMenu();
|
||||
} catch (error) {
|
||||
console.error("[App] Failed to refresh tray menu after import", error);
|
||||
}
|
||||
};
|
||||
|
||||
// 自动从 live 导入一条默认供应商(仅首次初始化时)
|
||||
const handleAutoImportDefault = async () => {
|
||||
try {
|
||||
const result = await window.api.importCurrentConfigAsDefault(activeApp);
|
||||
|
||||
if (result.success) {
|
||||
await loadProviders();
|
||||
showNotification(t("notifications.autoImported"), "success", 3000);
|
||||
// 更新托盘菜单
|
||||
await window.api.updateTrayMenu();
|
||||
const handleOpenWebsite = useCallback(
|
||||
async (url: string) => {
|
||||
try {
|
||||
await window.api.openExternal(url);
|
||||
} catch (error) {
|
||||
const detail =
|
||||
extractErrorMessage(error) ||
|
||||
t("notifications.openLinkFailed", {
|
||||
defaultValue: "链接打开失败",
|
||||
});
|
||||
toast.error(detail);
|
||||
}
|
||||
// 如果导入失败(比如没有现有配置),静默处理,不显示错误
|
||||
} catch (error) {
|
||||
console.error(t("console.autoImportFailed"), error);
|
||||
// 静默处理,不影响用户体验
|
||||
},
|
||||
[t],
|
||||
);
|
||||
|
||||
const handleAddProvider = useCallback(
|
||||
async (provider: Omit<Provider, "id">) => {
|
||||
await addProviderMutation.mutateAsync(provider);
|
||||
},
|
||||
[addProviderMutation],
|
||||
);
|
||||
|
||||
const handleEditProvider = useCallback(
|
||||
async (provider: Provider) => {
|
||||
try {
|
||||
await updateProviderMutation.mutateAsync(provider);
|
||||
await providersApi.updateTrayMenu();
|
||||
setEditingProvider(null);
|
||||
} catch {
|
||||
// 错误提示由 mutation 统一处理
|
||||
}
|
||||
},
|
||||
[updateProviderMutation],
|
||||
);
|
||||
|
||||
const handleSyncClaudePlugin = useCallback(
|
||||
async (provider: Provider) => {
|
||||
if (activeApp !== "claude") return;
|
||||
|
||||
try {
|
||||
const settings = await window.api.getSettings();
|
||||
if (!settings?.enableClaudePluginIntegration) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isOfficial = provider.category === "official";
|
||||
await window.api.applyClaudePluginConfig({ official: isOfficial });
|
||||
|
||||
toast.success(
|
||||
isOfficial
|
||||
? t("notifications.appliedToClaudePlugin", {
|
||||
defaultValue: "已同步为官方配置",
|
||||
})
|
||||
: t("notifications.removedFromClaudePlugin", {
|
||||
defaultValue: "已移除 Claude 插件配置",
|
||||
}),
|
||||
{ duration: 2200 },
|
||||
);
|
||||
} catch (error) {
|
||||
const detail =
|
||||
extractErrorMessage(error) ||
|
||||
t("notifications.syncClaudePluginFailed", {
|
||||
defaultValue: "同步 Claude 插件失败",
|
||||
});
|
||||
toast.error(detail, { duration: 4200 });
|
||||
}
|
||||
},
|
||||
[activeApp, t],
|
||||
);
|
||||
|
||||
const handleSwitchProvider = useCallback(
|
||||
async (provider: Provider) => {
|
||||
try {
|
||||
await switchProviderMutation.mutateAsync(provider.id);
|
||||
await handleSyncClaudePlugin(provider);
|
||||
} catch {
|
||||
// 错误提示由 mutation 与同步函数处理
|
||||
}
|
||||
},
|
||||
[switchProviderMutation, handleSyncClaudePlugin],
|
||||
);
|
||||
|
||||
const handleRequestDelete = useCallback((provider: Provider) => {
|
||||
setConfirmDelete(provider);
|
||||
}, []);
|
||||
|
||||
const handleConfirmDelete = useCallback(async () => {
|
||||
if (!confirmDelete) return;
|
||||
try {
|
||||
await deleteProviderMutation.mutateAsync(confirmDelete.id);
|
||||
} finally {
|
||||
setConfirmDelete(null);
|
||||
}
|
||||
};
|
||||
}, [confirmDelete, deleteProviderMutation]);
|
||||
|
||||
const handleImportSuccess = useCallback(async () => {
|
||||
await refetch();
|
||||
try {
|
||||
await providersApi.updateTrayMenu();
|
||||
} catch (error) {
|
||||
console.error("[App] Failed to refresh tray menu", error);
|
||||
}
|
||||
}, [refetch]);
|
||||
|
||||
const handleSaveUsageScript = useCallback(
|
||||
async (provider: Provider, script: UsageScript) => {
|
||||
try {
|
||||
const updatedProvider: Provider = {
|
||||
...provider,
|
||||
meta: {
|
||||
...provider.meta,
|
||||
usage_script: script,
|
||||
},
|
||||
};
|
||||
|
||||
await providersApi.update(updatedProvider, activeApp);
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: ["providers", activeApp],
|
||||
});
|
||||
toast.success(
|
||||
t("provider.usageSaved", {
|
||||
defaultValue: "用量查询配置已保存",
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
const detail =
|
||||
extractErrorMessage(error) ||
|
||||
t("provider.usageSaveFailed", {
|
||||
defaultValue: "用量查询配置保存失败",
|
||||
});
|
||||
toast.error(detail);
|
||||
}
|
||||
},
|
||||
[activeApp, queryClient, t],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="h-screen flex flex-col bg-gray-50 dark:bg-gray-950">
|
||||
{/* 顶部导航区域 - 固定高度 */}
|
||||
<header className="flex-shrink-0 bg-white border-b border-gray-200 dark:bg-gray-900 dark:border-gray-800 px-6 py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex h-screen flex-col bg-gray-50 dark:bg-gray-950">
|
||||
<header className="flex-shrink-0 border-b border-gray-200 bg-white px-6 py-4 dark:border-gray-800 dark:bg-gray-900">
|
||||
<div className="flex flex-wrap items-center justify-between gap-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<a
|
||||
href="https://github.com/farion1231/cc-switch"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-xl font-semibold text-blue-500 dark:text-blue-400 hover:text-blue-600 dark:hover:text-blue-300 transition-colors"
|
||||
title={t("header.viewOnGithub")}
|
||||
rel="noreferrer"
|
||||
className="text-xl font-semibold text-blue-500 transition-colors hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300"
|
||||
>
|
||||
CC Switch
|
||||
</a>
|
||||
<button
|
||||
onClick={toggleDarkMode}
|
||||
className={buttonStyles.icon}
|
||||
title={
|
||||
isDarkMode
|
||||
? t("header.toggleLightMode")
|
||||
: t("header.toggleDarkMode")
|
||||
}
|
||||
<ModeToggle />
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setIsSettingsOpen(true)}
|
||||
>
|
||||
{isDarkMode ? <Sun size={18} /> : <Moon size={18} />}
|
||||
</button>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => setIsSettingsOpen(true)}
|
||||
className={buttonStyles.icon}
|
||||
title={t("common.settings")}
|
||||
>
|
||||
<Settings size={18} />
|
||||
</button>
|
||||
<UpdateBadge onClick={() => setIsSettingsOpen(true)} />
|
||||
</div>
|
||||
<Settings className="h-4 w-4" />
|
||||
</Button>
|
||||
<UpdateBadge onClick={() => setIsSettingsOpen(true)} />
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<AppSwitcher activeApp={activeApp} onSwitch={setActiveApp} />
|
||||
|
||||
<button
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setIsMcpOpen(true)}
|
||||
className="inline-flex items-center gap-2 px-7 py-2 text-sm font-medium rounded-lg transition-colors bg-emerald-500 text-white hover:bg-emerald-600 dark:bg-emerald-600 dark:hover:bg-emerald-700"
|
||||
>
|
||||
MCP
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => setIsAddModalOpen(true)}
|
||||
className={`inline-flex items-center gap-2 ${buttonStyles.primary}`}
|
||||
>
|
||||
<Plus size={16} />
|
||||
{t("header.addProvider")}
|
||||
</button>
|
||||
</Button>
|
||||
<Button onClick={() => setIsAddOpen(true)}>
|
||||
<Plus className="h-4 w-4" />
|
||||
{t("header.addProvider", { defaultValue: "添加供应商" })}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* 主内容区域 - 独立滚动 */}
|
||||
<main className="flex-1 overflow-y-scroll">
|
||||
<div className="pt-3 px-6 pb-6">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
{/* 通知组件 - 相对于视窗定位 */}
|
||||
{notification && (
|
||||
<div
|
||||
className={`fixed top-20 left-1/2 transform -translate-x-1/2 z-[80] px-4 py-3 rounded-lg shadow-lg transition-all duration-300 ${
|
||||
notification.type === "error"
|
||||
? "bg-red-500 text-white"
|
||||
: "bg-green-500 text-white"
|
||||
} ${isNotificationVisible ? "opacity-100 translate-y-0" : "opacity-0 -translate-y-2"}`}
|
||||
>
|
||||
{notification.message}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ProviderList
|
||||
providers={providers}
|
||||
currentProviderId={currentProviderId}
|
||||
onSwitch={handleSwitchProvider}
|
||||
onDelete={handleDeleteProvider}
|
||||
onEdit={setEditingProviderId}
|
||||
appType={activeApp}
|
||||
onNotify={showNotification}
|
||||
onProvidersUpdated={loadProviders}
|
||||
/>
|
||||
</div>
|
||||
<main className="flex-1 overflow-y-auto">
|
||||
<div className="mx-auto max-w-4xl px-6 py-6">
|
||||
<ProviderList
|
||||
providers={providers}
|
||||
currentProviderId={currentProviderId}
|
||||
appType={activeApp}
|
||||
isLoading={isLoading}
|
||||
onSwitch={handleSwitchProvider}
|
||||
onEdit={setEditingProvider}
|
||||
onDelete={handleRequestDelete}
|
||||
onConfigureUsage={setUsageProvider}
|
||||
onOpenWebsite={handleOpenWebsite}
|
||||
onCreate={() => setIsAddOpen(true)}
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{isAddModalOpen && (
|
||||
<AddProviderModal
|
||||
<AddProviderDialog
|
||||
open={isAddOpen}
|
||||
onOpenChange={setIsAddOpen}
|
||||
appType={activeApp}
|
||||
onSubmit={handleAddProvider}
|
||||
/>
|
||||
|
||||
<EditProviderDialog
|
||||
open={Boolean(editingProvider)}
|
||||
provider={editingProvider}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
setEditingProvider(null);
|
||||
}
|
||||
}}
|
||||
onSubmit={handleEditProvider}
|
||||
/>
|
||||
|
||||
{usageProvider && (
|
||||
<UsageScriptModal
|
||||
provider={usageProvider}
|
||||
appType={activeApp}
|
||||
onAdd={handleAddProvider}
|
||||
onClose={() => setIsAddModalOpen(false)}
|
||||
onClose={() => setUsageProvider(null)}
|
||||
onSave={(script) => {
|
||||
void handleSaveUsageScript(usageProvider, script);
|
||||
}}
|
||||
onNotify={handleNotify}
|
||||
/>
|
||||
)}
|
||||
|
||||
{editingProviderId && providers[editingProviderId] && (
|
||||
<EditProviderModal
|
||||
appType={activeApp}
|
||||
provider={providers[editingProviderId]}
|
||||
onSave={handleEditProvider}
|
||||
onClose={() => setEditingProviderId(null)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{confirmDialog && (
|
||||
<ConfirmDialog
|
||||
isOpen={confirmDialog.isOpen}
|
||||
title={confirmDialog.title}
|
||||
message={confirmDialog.message}
|
||||
onConfirm={confirmDialog.onConfirm}
|
||||
onCancel={() => setConfirmDialog(null)}
|
||||
/>
|
||||
)}
|
||||
<ConfirmDialog
|
||||
isOpen={Boolean(confirmDelete)}
|
||||
title={t("confirm.deleteProvider", { defaultValue: "删除供应商" })}
|
||||
message={
|
||||
confirmDelete
|
||||
? t("confirm.deleteProviderMessage", {
|
||||
name: confirmDelete.name,
|
||||
defaultValue: `确定删除 ${confirmDelete.name} 吗?`,
|
||||
})
|
||||
: ""
|
||||
}
|
||||
onConfirm={() => void handleConfirmDelete()}
|
||||
onCancel={() => setConfirmDelete(null)}
|
||||
/>
|
||||
|
||||
{isSettingsOpen && (
|
||||
<SettingsModal
|
||||
onClose={() => setIsSettingsOpen(false)}
|
||||
onImportSuccess={handleImportSuccess}
|
||||
onNotify={showNotification}
|
||||
onNotify={handleNotify}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -401,7 +337,7 @@ function App() {
|
||||
<McpPanel
|
||||
appType={activeApp}
|
||||
onClose={() => setIsMcpOpen(false)}
|
||||
onNotify={showNotification}
|
||||
onNotify={handleNotify}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user