From 5fd78a103b0dac0a94bf13c0810bfb10ce1d0a41 Mon Sep 17 00:00:00 2001 From: musistudio Date: Sun, 3 Aug 2025 16:42:59 +0800 Subject: [PATCH] feat ui: add import and export config --- ui/src/App.tsx | 212 ++++++++++++++++++++++--------- ui/src/components/ui/tooltip.tsx | 28 ++++ ui/src/locales/en.json | 5 +- ui/src/locales/zh.json | 5 +- ui/tsconfig.tsbuildinfo | 2 +- 5 files changed, 190 insertions(+), 62 deletions(-) create mode 100644 ui/src/components/ui/tooltip.tsx diff --git a/ui/src/App.tsx b/ui/src/App.tsx index 9a96f86..ffcb2b4 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from "react"; +import { useState, useEffect, useRef } from "react"; import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; import { SettingsDialog } from "@/components/SettingsDialog"; @@ -9,23 +9,30 @@ import { JsonEditor } from "@/components/JsonEditor"; import { Button } from "@/components/ui/button"; import { useConfig } from "@/components/ConfigProvider"; import { api } from "@/lib/api"; -import { Settings, Languages, Save, RefreshCw, FileJson } from "lucide-react"; +import { Settings, Languages, Save, RefreshCw, FileJson, Upload, Download } from "lucide-react"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; import { Toast } from "@/components/ui/toast"; import "@/styles/animations.css"; function App() { const { t, i18n } = useTranslation(); const navigate = useNavigate(); - const { config, error } = useConfig(); + const { config, setConfig, error } = useConfig(); const [isSettingsOpen, setIsSettingsOpen] = useState(false); const [isJsonEditorOpen, setIsJsonEditorOpen] = useState(false); const [isCheckingAuth, setIsCheckingAuth] = useState(true); const [toast, setToast] = useState<{ message: string; type: 'success' | 'error' | 'warning' } | null>(null); + const fileInputRef = useRef(null); useEffect(() => { const checkAuth = async () => { @@ -153,6 +160,43 @@ function App() { } }; + const exportConfig = () => { + if (!config) return; + + const configString = JSON.stringify(config, null, 2); + const blob = new Blob([configString], { type: "application/json" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = "claude-code-router-config.json"; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + }; + + const importConfig = () => { + fileInputRef.current?.click(); + }; + + const handleFileChange = (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file) return; + + const reader = new FileReader(); + reader.onload = (event) => { + try { + const configString = event.target?.result as string; + const importedConfig = JSON.parse(configString); + setConfig(importedConfig); + } catch (error) { + console.error("Failed to parse config file:", error); + setToast({ message: t('settings.import_error'), type: 'error' }); + } + }; + reader.readAsText(file); + }; + if (isCheckingAuth) { return ( @@ -180,51 +224,93 @@ function App() { } return ( -
-
-

{t('app.title')}

-
- - - - - - - -
- - -
-
-
- - -
-
+ + +

{t('app.export_config')}

+
+ + + + + + +

{t('app.settings')}

+
+
+ + + + + +

{t('json_editor.title')}

+
+
+ + + + + + + + +

{t('app.change_language')}

+
+
+ +
+ + +
+
+
+ + +
+
@@ -238,20 +324,28 @@ function App() {
- - setToast({ message, type })} - /> - {toast && ( - setToast(null)} + + setToast({ message, type })} /> - )} - + {toast && ( + setToast(null)} + /> + )} + + + ); } diff --git a/ui/src/components/ui/tooltip.tsx b/ui/src/components/ui/tooltip.tsx new file mode 100644 index 0000000..61b717e --- /dev/null +++ b/ui/src/components/ui/tooltip.tsx @@ -0,0 +1,28 @@ +import * as React from "react" +import * as TooltipPrimitive from "@radix-ui/react-tooltip" + +import { cn } from "@/lib/utils" + +const TooltipProvider = TooltipPrimitive.Provider + +const Tooltip = TooltipPrimitive.Root + +const TooltipTrigger = TooltipPrimitive.Trigger + +const TooltipContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + +)) +TooltipContent.displayName = TooltipPrimitive.Content.displayName + +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } \ No newline at end of file diff --git a/ui/src/locales/en.json b/ui/src/locales/en.json index 9cf93fa..6940066 100644 --- a/ui/src/locales/en.json +++ b/ui/src/locales/en.json @@ -12,7 +12,10 @@ "config_saved_success": "Config saved successfully", "config_saved_failed": "Failed to save config", "config_saved_restart_success": "Config saved and service restarted successfully", - "config_saved_restart_failed": "Failed to save config and restart service" + "config_saved_restart_failed": "Failed to save config and restart service", + "import_config": "Import Config", + "export_config": "Export Config", + "change_language": "Change Language" }, "login": { "title": "Sign in to your account", diff --git a/ui/src/locales/zh.json b/ui/src/locales/zh.json index 3a4828e..c0b057c 100644 --- a/ui/src/locales/zh.json +++ b/ui/src/locales/zh.json @@ -12,7 +12,10 @@ "config_saved_success": "配置保存成功", "config_saved_failed": "配置保存失败", "config_saved_restart_success": "配置保存并服务重启成功", - "config_saved_restart_failed": "配置保存并服务重启失败" + "config_saved_restart_failed": "配置保存并服务重启失败", + "import_config": "导入配置", + "export_config": "导出配置", + "change_language": "切换语言" }, "login": { "title": "登录到您的账户", diff --git a/ui/tsconfig.tsbuildinfo b/ui/tsconfig.tsbuildinfo index 62ad4f2..b42216f 100644 --- a/ui/tsconfig.tsbuildinfo +++ b/ui/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/app.tsx","./src/i18n.ts","./src/main.tsx","./src/routes.tsx","./src/types.ts","./src/vite-env.d.ts","./src/components/configprovider.tsx","./src/components/jsoneditor.tsx","./src/components/login.tsx","./src/components/protectedroute.tsx","./src/components/providerlist.tsx","./src/components/providers.tsx","./src/components/publicroute.tsx","./src/components/router.tsx","./src/components/settingsdialog.tsx","./src/components/transformerlist.tsx","./src/components/transformers.tsx","./src/components/ui/badge.tsx","./src/components/ui/button.tsx","./src/components/ui/card.tsx","./src/components/ui/combo-input.tsx","./src/components/ui/combobox.tsx","./src/components/ui/command.tsx","./src/components/ui/dialog.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/multi-combobox.tsx","./src/components/ui/popover.tsx","./src/components/ui/switch.tsx","./src/components/ui/toast.tsx","./src/lib/api.ts","./src/lib/utils.ts"],"version":"5.8.3"} \ No newline at end of file +{"root":["./src/app.tsx","./src/i18n.ts","./src/main.tsx","./src/routes.tsx","./src/types.ts","./src/vite-env.d.ts","./src/components/configprovider.tsx","./src/components/jsoneditor.tsx","./src/components/login.tsx","./src/components/protectedroute.tsx","./src/components/providerlist.tsx","./src/components/providers.tsx","./src/components/publicroute.tsx","./src/components/router.tsx","./src/components/settingsdialog.tsx","./src/components/transformerlist.tsx","./src/components/transformers.tsx","./src/components/ui/badge.tsx","./src/components/ui/button.tsx","./src/components/ui/card.tsx","./src/components/ui/combo-input.tsx","./src/components/ui/combobox.tsx","./src/components/ui/command.tsx","./src/components/ui/dialog.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/multi-combobox.tsx","./src/components/ui/popover.tsx","./src/components/ui/switch.tsx","./src/components/ui/toast.tsx","./src/components/ui/tooltip.tsx","./src/lib/api.ts","./src/lib/utils.ts"],"version":"5.8.3"} \ No newline at end of file