diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 270f88d..291ae9c 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -14,6 +14,7 @@ { "label": "main", "title": "", + "titleBarStyle": "Overlay", "width": 1000, "height": 650, "minWidth": 900, diff --git a/src/components/env/EnvWarningBanner.tsx b/src/components/env/EnvWarningBanner.tsx index 76a167f..3a06db9 100644 --- a/src/components/env/EnvWarningBanner.tsx +++ b/src/components/env/EnvWarningBanner.tsx @@ -110,7 +110,7 @@ export function EnvWarningBanner({ return ( <> -
+
@@ -229,8 +229,8 @@ export function EnvWarningBanner({ {isDeleting ? t("env.actions.deleting") : t("env.actions.deleteSelected", { - count: selectedConflicts.size, - })} + count: selectedConflicts.size, + })}
@@ -241,7 +241,7 @@ export function EnvWarningBanner({
- + diff --git a/src/components/mcp/UnifiedMcpPanel.tsx b/src/components/mcp/UnifiedMcpPanel.tsx index b0134ad..03fd2a2 100644 --- a/src/components/mcp/UnifiedMcpPanel.tsx +++ b/src/components/mcp/UnifiedMcpPanel.tsx @@ -1,14 +1,7 @@ import React, { useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; -import { Plus, Server, Check } from "lucide-react"; +import { Server } from "lucide-react"; import { Button } from "@/components/ui/button"; -import { - Dialog, - DialogContent, - DialogFooter, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; import { Switch } from "@/components/ui/switch"; import { useAllMcpServers, useToggleMcpApp } from "@/hooks/useMcp"; import type { McpServer } from "@/types"; @@ -22,7 +15,6 @@ import { mcpPresets } from "@/config/mcpPresets"; import { toast } from "sonner"; interface UnifiedMcpPanelProps { - open: boolean; onOpenChange: (open: boolean) => void; } @@ -30,10 +22,13 @@ interface UnifiedMcpPanelProps { * 统一 MCP 管理面板 * v3.7.0 新架构:所有 MCP 服务器统一管理,每个服务器通过复选框控制应用到哪些客户端 */ -const UnifiedMcpPanel: React.FC = ({ - open, +export interface UnifiedMcpPanelHandle { + openAdd: () => void; +} + +const UnifiedMcpPanel = React.forwardRef(({ onOpenChange, -}) => { +}, ref) => { const { t } = useTranslation(); const [isFormOpen, setIsFormOpen] = useState(false); const [editingId, setEditingId] = useState(null); @@ -90,6 +85,10 @@ const UnifiedMcpPanel: React.FC = ({ setIsFormOpen(true); }; + React.useImperativeHandle(ref, () => ({ + openAdd: handleAdd + })); + const handleDelete = (id: string) => { setConfirmDialog({ isOpen: true, @@ -115,78 +114,53 @@ const UnifiedMcpPanel: React.FC = ({ }; return ( - <> - - - -
- {t("mcp.unifiedPanel.title")} - -
-
+
+ {/* Info Section */} +
+
+ {t("mcp.serverCount", { count: serverEntries.length })} ·{" "} + {t("mcp.unifiedPanel.apps.claude")}: {enabledCounts.claude} ·{" "} + {t("mcp.unifiedPanel.apps.codex")}: {enabledCounts.codex} ·{" "} + {t("mcp.unifiedPanel.apps.gemini")}: {enabledCounts.gemini} +
+
- {/* Info Section */} -
-
- {t("mcp.serverCount", { count: serverEntries.length })} ·{" "} - {t("mcp.unifiedPanel.apps.claude")}: {enabledCounts.claude} ·{" "} - {t("mcp.unifiedPanel.apps.codex")}: {enabledCounts.codex} ·{" "} - {t("mcp.unifiedPanel.apps.gemini")}: {enabledCounts.gemini} -
+ {/* Content - Scrollable */} +
+ {isLoading ? ( +
+ {t("mcp.loading")}
- - {/* Content - Scrollable */} -
- {isLoading ? ( -
- {t("mcp.loading")} -
- ) : serverEntries.length === 0 ? ( -
-
- -
-

- {t("mcp.unifiedPanel.noServers")} -

-

- {t("mcp.emptyDescription")} -

-
- ) : ( -
- {serverEntries.map(([id, server]) => ( - - ))} -
- )} + ) : serverEntries.length === 0 ? ( +
+
+ +
+

+ {t("mcp.unifiedPanel.noServers")} +

+

+ {t("mcp.emptyDescription")} +

- - - - - -
+ ) : ( +
+ {serverEntries.map(([id, server]) => ( + + ))} +
+ )} +
{/* Form Modal */} {isFormOpen && ( @@ -215,9 +189,11 @@ const UnifiedMcpPanel: React.FC = ({ onCancel={() => setConfirmDialog(null)} /> )} - + ); -}; +}); + +UnifiedMcpPanel.displayName = "UnifiedMcpPanel"; /** * 统一 MCP 列表项组件 diff --git a/src/components/providers/AddProviderDialog.tsx b/src/components/providers/AddProviderDialog.tsx index 1f2b3d6..ef9c43a 100644 --- a/src/components/providers/AddProviderDialog.tsx +++ b/src/components/providers/AddProviderDialog.tsx @@ -1,15 +1,8 @@ import { useCallback } from "react"; import { useTranslation } from "react-i18next"; import { Plus } from "lucide-react"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; +import { FullScreenPanel } from "@/components/common/FullScreenPanel"; import type { Provider, CustomEndpoint } from "@/types"; import type { AppId } from "@/lib/api"; import { @@ -58,8 +51,6 @@ export function AddProviderDialog({ if (!hasCustomEndpoints) { // 收集端点候选(仅在缺少自定义端点时兜底) - // 1. 从预设配置中获取 endpointCandidates - // 2. 从当前配置中提取 baseUrl (ANTHROPIC_BASE_URL 或 Codex base_url) const urlSet = new Set(); const addUrl = (rawUrl?: string) => { @@ -170,34 +161,40 @@ export function AddProviderDialog({ ? t("provider.addCodexProvider") : t("provider.addGeminiProvider"); + const footer = ( + <> + + + + ); + return ( - - - - {submitLabel} - {t("provider.addProviderHint")} - - -
- onOpenChange(false)} - showButtons={false} - /> -
- - - - - -
-
+ onOpenChange(false)} + footer={footer} + > + onOpenChange(false)} + showButtons={false} + /> + ); } diff --git a/src/components/providers/EditProviderDialog.tsx b/src/components/providers/EditProviderDialog.tsx index aa221f7..4a350c4 100644 --- a/src/components/providers/EditProviderDialog.tsx +++ b/src/components/providers/EditProviderDialog.tsx @@ -1,15 +1,8 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { Save } from "lucide-react"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; +import { FullScreenPanel } from "@/components/common/FullScreenPanel"; import type { Provider } from "@/types"; import { ProviderForm, @@ -34,7 +27,7 @@ export function EditProviderDialog({ }: EditProviderDialogProps) { const { t } = useTranslation(); - // 默认使用传入的 provider.settingsConfig,若当前编辑对象是“当前生效供应商”,则尝试读取实时配置替换初始值 + // 默认使用传入的 provider.settingsConfig,若当前编辑对象是"当前生效供应商",则尝试读取实时配置替换初始值 const [liveSettings, setLiveSettings] = useState - - - {t("provider.editProvider")} - - {t("provider.editProviderHint")} - - - -
- onOpenChange(false)} - initialData={{ - name: provider.name, - notes: provider.notes, - websiteUrl: provider.websiteUrl, - // 若读取到实时配置则优先使用 - settingsConfig: initialSettingsConfig, - category: provider.category, - meta: provider.meta, - }} - showButtons={false} - /> -
- - - - - -
- + onOpenChange(false)} + > + onOpenChange(false)} + initialData={{ + name: provider.name, + notes: provider.notes, + websiteUrl: provider.websiteUrl, + // 若读取到实时配置则优先使用 + settingsConfig: initialSettingsConfig, + category: provider.category, + meta: provider.meta, + }} + showButtons={false} + /> +
+ +
+
); } diff --git a/src/components/skills/SkillCard.tsx b/src/components/skills/SkillCard.tsx index 74dffbd..689a558 100644 --- a/src/components/skills/SkillCard.tsx +++ b/src/components/skills/SkillCard.tsx @@ -57,7 +57,8 @@ export function SkillCard({ skill, onInstall, onUninstall }: SkillCardProps) { skill.directory.trim().toLowerCase() !== skill.name.trim().toLowerCase(); return ( - + +
@@ -95,7 +96,7 @@ export function SkillCard({ skill, onInstall, onUninstall }: SkillCardProps) { {skill.description || t("skills.noDescription")}

- + {skill.readmeUrl && ( - -
-
- - {/* 描述 */} -

- {t("skills.description")} -

-
+
+ {/* 顶部操作栏(固定区域)已移除,由 App.tsx 接管 */} {/* 技能网格(可滚动详情区域) */} -
+
{loading ? (
@@ -176,15 +153,18 @@ export function SkillsPage({ onClose: _onClose }: SkillsPageProps = {}) { )}
- {/* 仓库管理对话框 */} - + {/* 仓库管理面板 */} + {repoManagerOpen && ( + setRepoManagerOpen(false)} + /> + )}
); -} +}); + +SkillsPage.displayName = "SkillsPage"; diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index f92f297..dd51dcb 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -27,7 +27,8 @@ "formatSuccess": "Formatted successfully", "formatError": "Format failed: {{error}}", "copy": "Copy", - "view": "View" + "view": "View", + "back": "Back" }, "apiKeyInput": { "placeholder": "Enter API Key", @@ -314,7 +315,8 @@ "pleaseAddEndpoint": "Please add an endpoint first", "testUnavailable": "Speed test unavailable", "noResult": "No result returned", - "testFailed": "Speed test failed: {{error}}" + "testFailed": "Speed test failed: {{error}}", + "status": "Status: {{code}}" }, "codexConfig": { "authJson": "auth.json (JSON) *", @@ -361,6 +363,9 @@ "title": "Configure Usage Query", "enableUsageQuery": "Enable usage query", "presetTemplate": "Preset template", + "requestUrl": "Request URL", + "requestUrlPlaceholder": "e.g. https://api.example.com", + "method": "HTTP method", "templateCustom": "Custom", "templateGeneral": "General", "templateNewAPI": "NewAPI", @@ -373,11 +378,14 @@ "queryFailedMessage": "Query failed", "queryScript": "Query script (JavaScript)", "timeoutSeconds": "Timeout (seconds)", + "headers": "Headers", + "body": "Body", "timeoutHint": "Range: 2-30 seconds", "timeoutMustBeInteger": "Timeout must be an integer, decimal part ignored", "timeoutCannotBeNegative": "Timeout cannot be negative", + "autoIntervalMinutes": "Auto query interval (minutes)", "autoQueryInterval": "Auto Query Interval (minutes)", - "autoQueryIntervalHint": "0 to disable, recommend 5-60 minutes", + "autoQueryIntervalHint": "0 to disable; recommend 5-60 minutes", "intervalMustBeInteger": "Interval must be an integer, decimal part ignored", "intervalCannotBeNegative": "Interval cannot be negative", "intervalAdjusted": "Interval adjusted to {{value}} minutes", @@ -398,6 +406,9 @@ "formatSuccess": "Format successful", "formatFailed": "Format failed", "variablesHint": "Supported variables: {{apiKey}}, {{baseUrl}} | extractor function receives API response JSON object", + "scriptConfig": "Request configuration", + "extractorCode": "Extractor code", + "extractorHint": "Return object should include remaining quota fields", "fieldIsValid": "• isValid: Boolean, whether plan is valid", "fieldInvalidMessage": "• invalidMessage: String, reason for expiration (shown when isValid is false)", "fieldRemaining": "• remaining: Number, remaining quota", diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json index 1e135a6..df54b58 100644 --- a/src/i18n/locales/zh.json +++ b/src/i18n/locales/zh.json @@ -27,7 +27,8 @@ "formatSuccess": "格式化成功", "formatError": "格式化失败:{{error}}", "copy": "复制", - "view": "查看" + "view": "查看", + "back": "返回" }, "apiKeyInput": { "placeholder": "请输入API Key", @@ -314,7 +315,8 @@ "pleaseAddEndpoint": "请先添加端点", "testUnavailable": "测速功能不可用", "noResult": "未返回结果", - "testFailed": "测速失败: {{error}}" + "testFailed": "测速失败: {{error}}", + "status": "状态码:{{code}}" }, "codexConfig": { "authJson": "auth.json (JSON) *", @@ -361,6 +363,9 @@ "title": "配置用量查询", "enableUsageQuery": "启用用量查询", "presetTemplate": "预设模板", + "requestUrl": "请求地址", + "requestUrlPlaceholder": "例如:https://api.example.com", + "method": "HTTP 方法", "templateCustom": "自定义", "templateGeneral": "通用模板", "templateNewAPI": "NewAPI", @@ -373,11 +378,14 @@ "queryFailedMessage": "查询失败", "queryScript": "查询脚本(JavaScript)", "timeoutSeconds": "超时时间(秒)", + "headers": "请求头", + "body": "请求 Body", "timeoutHint": "范围: 2-30 秒", "timeoutMustBeInteger": "超时时间必须为整数,小数部分已忽略", "timeoutCannotBeNegative": "超时时间不能为负数", + "autoIntervalMinutes": "自动查询间隔(分钟)", "autoQueryInterval": "自动查询间隔(分钟)", - "autoQueryIntervalHint": "0 表示不自动查询,建议设置 5-60 分钟", + "autoQueryIntervalHint": "0 表示不自动查询,建议 5-60 分钟", "intervalMustBeInteger": "自动查询间隔必须为整数,小数部分已忽略", "intervalCannotBeNegative": "自动查询间隔不能为负数", "intervalAdjusted": "自动查询间隔已调整为 {{value}} 分钟", @@ -398,6 +406,9 @@ "formatSuccess": "格式化成功", "formatFailed": "格式化失败", "variablesHint": "支持变量: {{apiKey}}, {{baseUrl}} | extractor 函数接收 API 响应的 JSON 对象", + "scriptConfig": "请求配置", + "extractorCode": "提取器代码", + "extractorHint": "返回对象需包含剩余额度等字段", "fieldIsValid": "• isValid: 布尔值,套餐是否有效", "fieldInvalidMessage": "• invalidMessage: 字符串,失效原因说明(当 isValid 为 false 时显示)", "fieldRemaining": "• remaining: 数字,剩余额度", diff --git a/src/lib/query/mutations.ts b/src/lib/query/mutations.ts index 1013568..b78340d 100644 --- a/src/lib/query/mutations.ts +++ b/src/lib/query/mutations.ts @@ -169,7 +169,6 @@ export const useSwitchProviderMutation = (appId: AppId) => { export const useSaveSettingsMutation = () => { const queryClient = useQueryClient(); - const { t } = useTranslation(); return useMutation({ mutationFn: async (settings: Settings) => { @@ -177,19 +176,6 @@ export const useSaveSettingsMutation = () => { }, onSuccess: async () => { await queryClient.invalidateQueries({ queryKey: ["settings"] }); - toast.success( - t("notifications.settingsSaved", { - defaultValue: "设置已保存", - }), - ); - }, - onError: (error: Error) => { - toast.error( - t("notifications.settingsSaveFailed", { - defaultValue: "保存设置失败: {{error}}", - error: error.message, - }), - ); }, }); };