diff --git a/src/App.tsx b/src/App.tsx index 477d116b4..8f1c0d837 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,33 +1,21 @@ import { useState } from "react"; import { Sidebar } from "./components/Sidebar"; import { Dashboard } from "./components/Dashboard"; -import { Providers } from "./components/Providers"; import { SettingsPage } from "./components/settings"; -import { SwitchPage } from "./components/switch"; -import { ClientsPage } from "./components/clients"; -import { McpPage } from "./components/mcp"; -import { PromptsPage } from "./components/prompts"; import { ApiServerPage } from "./components/api-server/ApiServerPage"; -import { SkillsPage } from "./components/skills"; import { ProviderPoolPage } from "./components/provider-pool"; -import { RoutingPage } from "./components/routing"; -import { ResiliencePage } from "./components/resilience"; -import { ConfigPage } from "./components/config"; +import { RoutingManagementPage } from "./components/routing/RoutingManagementPage"; +import { ConfigManagementPage } from "./components/config/ConfigManagementPage"; +import { ExtensionsPage } from "./components/extensions"; type Page = | "dashboard" - | "clients" - | "api-server" - | "providers" - | "settings" - | "switch" - | "mcp" - | "prompts" - | "skills" | "provider-pool" - | "routing" - | "resilience" - | "config"; + | "routing-management" + | "config-management" + | "extensions" + | "api-server" + | "settings"; function App() { const [currentPage, setCurrentPage] = useState("dashboard"); @@ -38,28 +26,16 @@ function App() { return ; case "provider-pool": return ; - case "clients": - return ; + case "routing-management": + return ; + case "config-management": + return ; + case "extensions": + return ; case "api-server": return ; - case "providers": - return ; case "settings": return ; - case "switch": - return ; - case "mcp": - return ; - case "prompts": - return ; - case "skills": - return ; - case "routing": - return ; - case "resilience": - return ; - case "config": - return ; default: return ; } diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index d59d3cbeb..7499fb898 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -1,32 +1,22 @@ import { LayoutDashboard, Settings, - Plug, - MessageSquare, - Monitor, Globe, - Boxes, Database, Route, - Shield, FileCode, + Puzzle, } from "lucide-react"; import { cn } from "@/lib/utils"; type Page = | "dashboard" - | "clients" - | "api-server" - | "providers" - | "settings" - | "switch" - | "mcp" - | "prompts" - | "skills" | "provider-pool" - | "routing" - | "resilience" - | "config"; + | "routing-management" + | "config-management" + | "extensions" + | "api-server" + | "settings"; interface SidebarProps { currentPage: Page; @@ -36,14 +26,10 @@ interface SidebarProps { const navItems = [ { id: "dashboard" as Page, label: "仪表盘", icon: LayoutDashboard }, { id: "provider-pool" as Page, label: "凭证池", icon: Database }, - { id: "routing" as Page, label: "智能路由", icon: Route }, - { id: "resilience" as Page, label: "容错配置", icon: Shield }, - { id: "config" as Page, label: "配置管理", icon: FileCode }, - { id: "clients" as Page, label: "配置切换", icon: Monitor }, + { id: "routing-management" as Page, label: "路由管理", icon: Route }, + { id: "config-management" as Page, label: "配置管理", icon: FileCode }, + { id: "extensions" as Page, label: "扩展", icon: Puzzle }, { id: "api-server" as Page, label: "API Server", icon: Globe }, - { id: "mcp" as Page, label: "MCP", icon: Plug }, - { id: "prompts" as Page, label: "Prompts", icon: MessageSquare }, - { id: "skills" as Page, label: "Skills", icon: Boxes }, { id: "settings" as Page, label: "设置", icon: Settings }, ]; diff --git a/src/components/clients/ClientsPage.tsx b/src/components/clients/ClientsPage.tsx index 0307c9f4c..127756b39 100644 --- a/src/components/clients/ClientsPage.tsx +++ b/src/components/clients/ClientsPage.tsx @@ -4,18 +4,24 @@ import { AppTabs } from "./AppTabs"; import { ProviderList } from "./ProviderList"; import { HelpTip } from "@/components/HelpTip"; -export function ClientsPage() { +interface ClientsPageProps { + hideHeader?: boolean; +} + +export function ClientsPage({ hideHeader = false }: ClientsPageProps) { const [activeApp, setActiveApp] = useState("claude"); return (
-
-

配置切换

-

- 一键切换 Claude Code / Codex / Gemini CLI 的 API 配置,快速在不同 - Provider 间切换 -

-
+ {!hideHeader && ( +
+

配置切换

+

+ 一键切换 Claude Code / Codex / Gemini CLI 的 API 配置,快速在不同 + Provider 间切换 +

+
+ )}

diff --git a/src/components/config/ConfigManagementPage.tsx b/src/components/config/ConfigManagementPage.tsx new file mode 100644 index 000000000..2620a8149 --- /dev/null +++ b/src/components/config/ConfigManagementPage.tsx @@ -0,0 +1,50 @@ +import { useState } from "react"; +import { Monitor, FileCode } from "lucide-react"; +import { cn } from "@/lib/utils"; +import { ClientsPage } from "../clients/ClientsPage"; +import { ConfigPage } from "./ConfigPage"; + +type Tab = "switch" | "config"; + +const tabs = [ + { id: "switch" as Tab, label: "配置切换", icon: Monitor }, + { id: "config" as Tab, label: "配置文件", icon: FileCode }, +]; + +export function ConfigManagementPage() { + const [activeTab, setActiveTab] = useState("switch"); + + return ( +

+
+

配置管理

+

管理客户端配置和配置文件

+
+ + {/* Tab 切换 */} +
+ {tabs.map((tab) => ( + + ))} +
+ + {/* Tab 内容 */} +
+ {activeTab === "switch" && } + {activeTab === "config" && } +
+
+ ); +} diff --git a/src/components/config/ConfigPage.tsx b/src/components/config/ConfigPage.tsx index 50dc32ada..6cc697b49 100644 --- a/src/components/config/ConfigPage.tsx +++ b/src/components/config/ConfigPage.tsx @@ -17,160 +17,190 @@ export interface ConfigPageRef { type TabType = "editor" | "import-export" | "settings"; -export const ConfigPage = forwardRef((_props, ref) => { - const [activeTab, setActiveTab] = useState("editor"); - const [config, setConfig] = useState(null); - const [pathInfo, setPathInfo] = useState(null); - const [isLoading, setIsLoading] = useState(true); - const [error, setError] = useState(null); +interface ConfigPageProps { + hideHeader?: boolean; +} - const loadConfig = async () => { - setIsLoading(true); - setError(null); - try { - // Load current config from app state - const currentConfig = await invoke("get_config"); - setConfig(currentConfig); +export const ConfigPage = forwardRef( + ({ hideHeader = false }, ref) => { + const [activeTab, setActiveTab] = useState("editor"); + const [config, setConfig] = useState(null); + const [pathInfo, setPathInfo] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); - // Load path info - const paths = await configApi.getConfigPaths(); - setPathInfo(paths); - } catch (err) { - setError(`加载配置失败: ${err}`); - } finally { - setIsLoading(false); + const loadConfig = async () => { + setIsLoading(true); + setError(null); + try { + // Load current config from app state + const currentConfig = await invoke("get_config"); + setConfig(currentConfig); + + // Load path info + const paths = await configApi.getConfigPaths(); + setPathInfo(paths); + } catch (err) { + setError(`加载配置失败: ${err}`); + } finally { + setIsLoading(false); + } + }; + + useEffect(() => { + loadConfig(); + }, []); + + useImperativeHandle(ref, () => ({ + refresh: loadConfig, + })); + + const handleConfigChange = async (newConfig: Config) => { + setConfig(newConfig); + // Save config to backend + try { + await invoke("save_config", { config: newConfig }); + } catch (err) { + setError(`保存配置失败: ${err}`); + } + }; + + const handleOpenConfigFolder = async () => { + try { + await invoke("open_config_folder", { appType: "ProxyCast" }); + } catch (err) { + setError(`打开文件夹失败: ${err}`); + } + }; + + const tabs: { id: TabType; label: string; icon?: React.ReactNode }[] = [ + { id: "editor", label: "YAML 编辑器" }, + { id: "import-export", label: "导入/导出" }, + { id: "settings", label: "设置", icon: }, + ]; + + if (isLoading) { + return ( +
+
加载中...
+
+ ); } - }; - useEffect(() => { - loadConfig(); - }, []); - - useImperativeHandle(ref, () => ({ - refresh: loadConfig, - })); - - const handleConfigChange = async (newConfig: Config) => { - setConfig(newConfig); - // Save config to backend - try { - await invoke("save_config", { config: newConfig }); - } catch (err) { - setError(`保存配置失败: ${err}`); - } - }; - - const handleOpenConfigFolder = async () => { - try { - await invoke("open_config_folder", { appType: "ProxyCast" }); - } catch (err) { - setError(`打开文件夹失败: ${err}`); - } - }; - - const tabs: { id: TabType; label: string; icon?: React.ReactNode }[] = [ - { id: "editor", label: "YAML 编辑器" }, - { id: "import-export", label: "导入/导出" }, - { id: "settings", label: "设置", icon: }, - ]; - - if (isLoading) { return ( -
-
加载中...
+
+ {!hideHeader && ( +
+
+

+ + 配置管理 +

+

+ 编辑 YAML 配置文件,导入导出配置 +

+
+
+ + +
+
+ )} + + {hideHeader && ( +
+ + +
+ )} + + {/* Config path info */} + {pathInfo && ( +
+
+ 配置文件: + + {pathInfo.yaml_path} + + {pathInfo.yaml_exists ? ( + 存在 + ) : ( + 不存在 + )} +
+
+ )} + + {/* Error display */} + {error && ( +
+ {error} +
+ )} + + {/* Tabs */} +
+ {tabs.map((tab) => ( + + ))} +
+ + {/* Tab content */} +
+ {activeTab === "editor" && ( + + )} + {activeTab === "import-export" && ( + + )} + {activeTab === "settings" && ( + + )} +
); - } - - return ( -
-
-
-

- - 配置管理 -

-

- 编辑 YAML 配置文件,导入导出配置 -

-
-
- - -
-
- - {/* Config path info */} - {pathInfo && ( -
-
- 配置文件: - - {pathInfo.yaml_path} - - {pathInfo.yaml_exists ? ( - 存在 - ) : ( - 不存在 - )} -
-
- )} - - {/* Error display */} - {error && ( -
- {error} -
- )} - - {/* Tabs */} -
- {tabs.map((tab) => ( - - ))} -
- - {/* Tab content */} -
- {activeTab === "editor" && ( - - )} - {activeTab === "import-export" && ( - - )} - {activeTab === "settings" && ( - - )} -
-
- ); -}); + }, +); ConfigPage.displayName = "ConfigPage"; diff --git a/src/components/config/index.ts b/src/components/config/index.ts index a559acfcb..23b3ebb4c 100644 --- a/src/components/config/index.ts +++ b/src/components/config/index.ts @@ -2,3 +2,4 @@ export { ConfigPage } from "./ConfigPage"; export { ConfigEditor } from "./ConfigEditor"; export { ImportExport } from "./ImportExport"; export { AuthDirSettings } from "./AuthDirSettings"; +export { ConfigManagementPage } from "./ConfigManagementPage"; diff --git a/src/components/extensions/ExtensionsPage.tsx b/src/components/extensions/ExtensionsPage.tsx new file mode 100644 index 000000000..648841faa --- /dev/null +++ b/src/components/extensions/ExtensionsPage.tsx @@ -0,0 +1,55 @@ +import { useState } from "react"; +import { Plug, MessageSquare, Boxes } from "lucide-react"; +import { cn } from "@/lib/utils"; +import { McpPage } from "../mcp/McpPage"; +import { PromptsPage } from "../prompts/PromptsPage"; +import { SkillsPage } from "../skills/SkillsPage"; + +type Tab = "mcp" | "prompts" | "skills"; + +const tabs = [ + { id: "mcp" as Tab, label: "MCP", icon: Plug }, + { id: "prompts" as Tab, label: "Prompts", icon: MessageSquare }, + { id: "skills" as Tab, label: "Skills", icon: Boxes }, +]; + +export function ExtensionsPage() { + const [activeTab, setActiveTab] = useState("mcp"); + + return ( +
+
+

扩展

+

+ 管理 MCP 服务器、Prompts 和 Skills +

+
+ + {/* Tab 切换 */} +
+ {tabs.map((tab) => ( + + ))} +
+ + {/* Tab 内容 */} +
+ {activeTab === "mcp" && } + {activeTab === "prompts" && } + {activeTab === "skills" && } +
+
+ ); +} diff --git a/src/components/extensions/index.ts b/src/components/extensions/index.ts new file mode 100644 index 000000000..a1e02bb41 --- /dev/null +++ b/src/components/extensions/index.ts @@ -0,0 +1 @@ +export { ExtensionsPage } from "./ExtensionsPage"; diff --git a/src/components/mcp/McpPage.tsx b/src/components/mcp/McpPage.tsx index 1b273acac..70f2fb845 100644 --- a/src/components/mcp/McpPage.tsx +++ b/src/components/mcp/McpPage.tsx @@ -59,7 +59,11 @@ const defaultServerConfig = JSON.stringify( 2, ); -export function McpPage() { +interface McpPageProps { + hideHeader?: boolean; +} + +export function McpPage({ hideHeader = false }: McpPageProps) { const { servers, loading, @@ -234,14 +238,72 @@ export function McpPage() { return (
-
-
-

MCP 服务器

-

- 管理 Model Context Protocol 服务器配置,同步到外部应用 -

+ {!hideHeader && ( +
+
+

MCP 服务器

+

+ 管理 Model Context Protocol 服务器配置,同步到外部应用 +

+
+
+ {/* 从外部导入按钮 */} +
+ + {showImportMenu && ( +
+ + + + +
+ )} +
+ {/* 同步到外部按钮 */} + +
-
+ )} + + {hideHeader && ( +
{/* 从外部导入按钮 */}
-
+ )}
    diff --git a/src/components/prompts/PromptsPage.tsx b/src/components/prompts/PromptsPage.tsx index 52b94b2a3..55669c992 100644 --- a/src/components/prompts/PromptsPage.tsx +++ b/src/components/prompts/PromptsPage.tsx @@ -55,7 +55,11 @@ function ToggleSwitch({ ); } -export function PromptsPage() { +interface PromptsPageProps { + hideHeader?: boolean; +} + +export function PromptsPage({ hideHeader = false }: PromptsPageProps) { const [activeApp, setActiveApp] = useState("claude"); const { prompts, @@ -179,12 +183,14 @@ export function PromptsPage() { return (
    -
    -

    Prompts

    -

    - 管理不同应用的系统提示词({currentApp.filename}) -

    -
    + {!hideHeader && ( +
    +

    Prompts

    +

    + 管理不同应用的系统提示词({currentApp.filename}) +

    +
    + )}
      diff --git a/src/components/resilience/ResiliencePage.tsx b/src/components/resilience/ResiliencePage.tsx index 9010b0b34..2ef5c91d0 100644 --- a/src/components/resilience/ResiliencePage.tsx +++ b/src/components/resilience/ResiliencePage.tsx @@ -9,7 +9,14 @@ export interface ResiliencePageRef { type TabType = "retry" | "failover"; -export const ResiliencePage = forwardRef((_props, ref) => { +interface ResiliencePageProps { + hideHeader?: boolean; +} + +export const ResiliencePage = forwardRef< + ResiliencePageRef, + ResiliencePageProps +>(({ hideHeader = false }, ref) => { const [activeTab, setActiveTab] = useState("retry"); const [refreshKey, setRefreshKey] = useState(0); @@ -28,22 +35,36 @@ export const ResiliencePage = forwardRef((_props, ref) => { return (
      -
      -
      -

      - - 容错配置 -

      -

      配置重试机制和故障转移策略

      + {!hideHeader && ( +
      +
      +

      + + 容错配置 +

      +

      配置重试机制和故障转移策略

      +
      +
      - -
      + )} + + {hideHeader && ( +
      + +
      + )} {/* Tabs */}
      diff --git a/src/components/routing/RoutingManagementPage.tsx b/src/components/routing/RoutingManagementPage.tsx new file mode 100644 index 000000000..e5ea74dba --- /dev/null +++ b/src/components/routing/RoutingManagementPage.tsx @@ -0,0 +1,68 @@ +import { useState } from "react"; +import { Route, Shield } from "lucide-react"; +import { cn } from "@/lib/utils"; +import { RoutingPage } from "./RoutingPage"; +import { ResiliencePage } from "../resilience/ResiliencePage"; + +type Tab = "routing" | "resilience"; + +const tabs = [ + { id: "routing" as Tab, label: "智能路由", icon: Route }, + { id: "resilience" as Tab, label: "容错配置", icon: Shield }, +]; + +export function RoutingManagementPage() { + const [activeTab, setActiveTab] = useState("routing"); + + return ( +
      +
      +

      路由管理

      +

      配置智能路由规则和容错策略

      +
      + + {/* Tab 切换 */} +
      + {tabs.map((tab) => ( + + ))} +
      + + {/* Tab 内容 */} +
      + {activeTab === "routing" && } + {activeTab === "resilience" && } +
      +
      + ); +} + +// 路由页面内容(去掉标题) +function RoutingPageContent() { + return ( +
      + +
      + ); +} + +// 容错页面内容(去掉标题) +function ResiliencePageContent() { + return ( +
      + +
      + ); +} diff --git a/src/components/routing/RoutingPage.tsx b/src/components/routing/RoutingPage.tsx index a65e771da..b453eefe0 100644 --- a/src/components/routing/RoutingPage.tsx +++ b/src/components/routing/RoutingPage.tsx @@ -21,367 +21,413 @@ export interface RoutingPageRef { type TabType = "aliases" | "rules" | "exclusions" | "injection"; -export const RoutingPage = forwardRef((_props, ref) => { - const [activeTab, setActiveTab] = useState("aliases"); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); +interface RoutingPageProps { + hideHeader?: boolean; +} - // Data state - const [aliases, setAliases] = useState([]); - const [rules, setRules] = useState([]); - const [exclusions, setExclusions] = useState>( - {} as Record, - ); - const [injectionRules, setInjectionRules] = useState([]); - const [injectionEnabled, setInjectionEnabled] = useState(false); +export const RoutingPage = forwardRef( + ({ hideHeader = false }, ref) => { + const [activeTab, setActiveTab] = useState("aliases"); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); - // Presets state - const [presets, setPresets] = useState([]); - const [showPresets, setShowPresets] = useState(false); - const [applyingPreset, setApplyingPreset] = useState(null); + // Data state + const [aliases, setAliases] = useState([]); + const [rules, setRules] = useState([]); + const [exclusions, setExclusions] = useState< + Record + >({} as Record); + const [injectionRules, setInjectionRules] = useState([]); + const [injectionEnabled, setInjectionEnabled] = useState(false); - const refresh = async () => { - setLoading(true); - setError(null); - try { - const [ - aliasesData, - rulesData, - exclusionsData, - injectionConfig, - presetsData, - ] = await Promise.all([ - routerApi.getModelAliases(), - routerApi.getRoutingRules(), - routerApi.getExclusions(), - injectionApi.getInjectionConfig(), - routerApi.getRecommendedPresets(), - ]); - setAliases(aliasesData); - setRules(rulesData); - setExclusions(exclusionsData); - setInjectionRules(injectionConfig.rules); - setInjectionEnabled(injectionConfig.enabled); - setPresets(presetsData); - } catch (e) { - setError(e instanceof Error ? e.message : String(e)); - } finally { - setLoading(false); - } - }; + // Presets state + const [presets, setPresets] = useState([]); + const [showPresets, setShowPresets] = useState(false); + const [applyingPreset, setApplyingPreset] = useState(null); - const handleApplyPreset = async ( - presetId: string, - merge: boolean = false, - ) => { - setApplyingPreset(presetId); - try { - await routerApi.applyRecommendedPreset(presetId, merge); + const refresh = async () => { + setLoading(true); + setError(null); + try { + const [ + aliasesData, + rulesData, + exclusionsData, + injectionConfig, + presetsData, + ] = await Promise.all([ + routerApi.getModelAliases(), + routerApi.getRoutingRules(), + routerApi.getExclusions(), + injectionApi.getInjectionConfig(), + routerApi.getRecommendedPresets(), + ]); + setAliases(aliasesData); + setRules(rulesData); + setExclusions(exclusionsData); + setInjectionRules(injectionConfig.rules); + setInjectionEnabled(injectionConfig.enabled); + setPresets(presetsData); + } catch (e) { + setError(e instanceof Error ? e.message : String(e)); + } finally { + setLoading(false); + } + }; + + const handleApplyPreset = async ( + presetId: string, + merge: boolean = false, + ) => { + setApplyingPreset(presetId); + try { + await routerApi.applyRecommendedPreset(presetId, merge); + await refresh(); + setShowPresets(false); + } catch (e) { + setError(e instanceof Error ? e.message : String(e)); + } finally { + setApplyingPreset(null); + } + }; + + const handleClearAll = async () => { + if (!confirm("确定要清空所有路由配置吗?此操作不可撤销。")) return; + try { + await routerApi.clearAllRoutingConfig(); + await refresh(); + } catch (e) { + setError(e instanceof Error ? e.message : String(e)); + } + }; + + useImperativeHandle(ref, () => ({ + refresh, + })); + + useEffect(() => { + refresh(); + }, []); + + // Alias handlers + const handleAddAlias = async (alias: string, actual: string) => { + await routerApi.addModelAlias(alias, actual); await refresh(); - setShowPresets(false); - } catch (e) { - setError(e instanceof Error ? e.message : String(e)); - } finally { - setApplyingPreset(null); - } - }; + }; - const handleClearAll = async () => { - if (!confirm("确定要清空所有路由配置吗?此操作不可撤销。")) return; - try { - await routerApi.clearAllRoutingConfig(); + const handleRemoveAlias = async (alias: string) => { + await routerApi.removeModelAlias(alias); await refresh(); - } catch (e) { - setError(e instanceof Error ? e.message : String(e)); - } - }; + }; - useImperativeHandle(ref, () => ({ - refresh, - })); + // Rule handlers + const handleAddRule = async (rule: RoutingRule) => { + await routerApi.addRoutingRule(rule); + await refresh(); + }; - useEffect(() => { - refresh(); - }, []); + const handleRemoveRule = async (pattern: string) => { + await routerApi.removeRoutingRule(pattern); + await refresh(); + }; - // Alias handlers - const handleAddAlias = async (alias: string, actual: string) => { - await routerApi.addModelAlias(alias, actual); - await refresh(); - }; + const handleUpdateRule = async (pattern: string, rule: RoutingRule) => { + await routerApi.updateRoutingRule(pattern, rule); + await refresh(); + }; - const handleRemoveAlias = async (alias: string) => { - await routerApi.removeModelAlias(alias); - await refresh(); - }; + // Exclusion handlers + const handleAddExclusion = async ( + provider: ProviderType, + pattern: string, + ) => { + await routerApi.addExclusion(provider, pattern); + await refresh(); + }; - // Rule handlers - const handleAddRule = async (rule: RoutingRule) => { - await routerApi.addRoutingRule(rule); - await refresh(); - }; + const handleRemoveExclusion = async ( + provider: ProviderType, + pattern: string, + ) => { + await routerApi.removeExclusion(provider, pattern); + await refresh(); + }; - const handleRemoveRule = async (pattern: string) => { - await routerApi.removeRoutingRule(pattern); - await refresh(); - }; + // Injection handlers + const handleToggleInjection = async (enabled: boolean) => { + await injectionApi.setInjectionEnabled(enabled); + setInjectionEnabled(enabled); + }; - const handleUpdateRule = async (pattern: string, rule: RoutingRule) => { - await routerApi.updateRoutingRule(pattern, rule); - await refresh(); - }; + const handleAddInjectionRule = async (rule: InjectionRule) => { + await injectionApi.addInjectionRule(rule); + await refresh(); + }; - // Exclusion handlers - const handleAddExclusion = async ( - provider: ProviderType, - pattern: string, - ) => { - await routerApi.addExclusion(provider, pattern); - await refresh(); - }; + const handleRemoveInjectionRule = async (id: string) => { + await injectionApi.removeInjectionRule(id); + await refresh(); + }; - const handleRemoveExclusion = async ( - provider: ProviderType, - pattern: string, - ) => { - await routerApi.removeExclusion(provider, pattern); - await refresh(); - }; + const handleUpdateInjectionRule = async ( + id: string, + rule: InjectionRule, + ) => { + await injectionApi.updateInjectionRule(id, rule); + await refresh(); + }; - // Injection handlers - const handleToggleInjection = async (enabled: boolean) => { - await injectionApi.setInjectionEnabled(enabled); - setInjectionEnabled(enabled); - }; + const tabs: { id: TabType; label: string; count: number }[] = [ + { id: "aliases", label: "模型别名", count: aliases.length }, + { id: "rules", label: "路由规则", count: rules.length }, + { + id: "exclusions", + label: "排除列表", + count: Object.values(exclusions).flat().length, + }, + { id: "injection", label: "参数注入", count: injectionRules.length }, + ]; - const handleAddInjectionRule = async (rule: InjectionRule) => { - await injectionApi.addInjectionRule(rule); - await refresh(); - }; - - const handleRemoveInjectionRule = async (id: string) => { - await injectionApi.removeInjectionRule(id); - await refresh(); - }; - - const handleUpdateInjectionRule = async (id: string, rule: InjectionRule) => { - await injectionApi.updateInjectionRule(id, rule); - await refresh(); - }; - - const tabs: { id: TabType; label: string; count: number }[] = [ - { id: "aliases", label: "模型别名", count: aliases.length }, - { id: "rules", label: "路由规则", count: rules.length }, - { - id: "exclusions", - label: "排除列表", - count: Object.values(exclusions).flat().length, - }, - { id: "injection", label: "参数注入", count: injectionRules.length }, - ]; - - return ( -
      -
      -
      -

      - - 智能路由 -

      -

      - 配置模型映射、路由规则和排除列表 -

      -
      -
      - - - -
      -
      - - {/* Presets Modal */} - {showPresets && ( -
      -
      -
      -

      - - 推荐配置 -

      + return ( +
      + {!hideHeader && ( +
      +
      +

      + + 智能路由 +

      +

      + 配置模型映射、路由规则和排除列表 +

      +
      +
      + +
      -

      - 选择一个预设配置快速设置路由规则和模型别名 -

      -
      - {presets.map((preset) => ( -
      + )} + + {/* 当隐藏标题时,显示操作按钮 */} + {hideHeader && ( +
      + + + +
      + )} + + {/* Presets Modal */} + {showPresets && ( +
      +
      +
      +

      + + 推荐配置 +

      + +
      +

      + 选择一个预设配置快速设置路由规则和模型别名 +

      +
      + {presets.map((preset) => ( +
      +
      +
      +

      {preset.name}

      +

      + {preset.description} +

      +
      + {preset.aliases.length} 个别名 + {preset.rules.length} 条规则 +
      +
      +
      + +
      -
      - - -
      -
      - ))} + ))} +
      + )} + + +
        +
      • + 模型别名 + :使用熟悉的模型名(如 gpt-4)映射到实际模型 +
      • +
      • + 路由规则 + :将特定模型路由到指定 Provider,支持通配符匹配 +
      • +
      • + 排除列表 + :从特定 Provider 排除某些模型 +
      • +
      • 精确匹配规则优先于通配符规则
      • +
      +
      + + {error && ( +
      + {error} +
      + )} + + {/* Tabs */} +
      + {tabs.map((tab) => ( + + ))}
      - )} - -
        -
      • - 模型别名 - :使用熟悉的模型名(如 gpt-4)映射到实际模型 -
      • -
      • - 路由规则 - :将特定模型路由到指定 Provider,支持通配符匹配 -
      • -
      • - 排除列表 - :从特定 Provider 排除某些模型 -
      • -
      • 精确匹配规则优先于通配符规则
      • -
      -
      - - {error && ( -
      - {error} -
      - )} - - {/* Tabs */} -
      - {tabs.map((tab) => ( - - ))} + {activeTab === "rules" && ( + + )} + {activeTab === "exclusions" && ( + + )} + {activeTab === "injection" && ( + + )} +
      + )}
      - - {/* Tab content */} - {loading ? ( -
      - -
      - ) : ( -
      - {activeTab === "aliases" && ( - - )} - {activeTab === "rules" && ( - - )} - {activeTab === "exclusions" && ( - - )} - {activeTab === "injection" && ( - - )} -
      - )} -
      - ); -}); + ); + }, +); RoutingPage.displayName = "RoutingPage"; diff --git a/src/components/routing/index.ts b/src/components/routing/index.ts index 3de5b30cc..3e98d7cc3 100644 --- a/src/components/routing/index.ts +++ b/src/components/routing/index.ts @@ -4,3 +4,4 @@ export { ExclusionList } from "./ExclusionList"; export { InjectionRules } from "./InjectionRules"; export { RoutingPage } from "./RoutingPage"; export type { RoutingPageRef } from "./RoutingPage"; +export { RoutingManagementPage } from "./RoutingManagementPage"; diff --git a/src/components/skills/SkillsPage.tsx b/src/components/skills/SkillsPage.tsx index 1d014af9f..bae9929ee 100644 --- a/src/components/skills/SkillsPage.tsx +++ b/src/components/skills/SkillsPage.tsx @@ -8,6 +8,7 @@ import type { AppType } from "@/lib/api/skills"; interface SkillsPageProps { initialApp?: AppType; + hideHeader?: boolean; } export interface SkillsPageRef { @@ -16,7 +17,7 @@ export interface SkillsPageRef { } export const SkillsPage = forwardRef( - ({ initialApp = "claude" }, ref) => { + ({ initialApp = "claude", hideHeader = false }, ref) => { const [app] = useState(initialApp); const [searchQuery, setSearchQuery] = useState(""); const [filterStatus, setFilterStatus] = useState< @@ -92,14 +93,38 @@ export const SkillsPage = forwardRef( return (
      -
      -
      -

      Skills

      -

      - 浏览和安装 Claude Code Skills -

      + {!hideHeader && ( +
      +
      +

      Skills

      +

      + 浏览和安装 Claude Code Skills +

      +
      +
      + + +
      -
      + )} + + {hideHeader && ( +
      -
      + )}