diff --git a/src-tauri/src/settings.rs b/src-tauri/src/settings.rs index 55962ff7..9d59e95e 100644 --- a/src-tauri/src/settings.rs +++ b/src-tauri/src/settings.rs @@ -190,6 +190,12 @@ pub struct AppSettings { /// 是否在主页面启用本地代理功能(默认关闭) #[serde(default)] pub enable_local_proxy: bool, + /// User has confirmed the local proxy first-run notice + #[serde(default, skip_serializing_if = "Option::is_none")] + pub proxy_confirmed: Option, + /// User has confirmed the usage query first-run notice + #[serde(default, skip_serializing_if = "Option::is_none")] + pub usage_confirmed: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub language: Option, @@ -266,6 +272,8 @@ impl Default for AppSettings { launch_on_startup: false, silent_startup: false, enable_local_proxy: false, + proxy_confirmed: None, + usage_confirmed: None, language: None, visible_apps: None, claude_config_dir: None, diff --git a/src/components/ConfirmDialog.tsx b/src/components/ConfirmDialog.tsx index 0fdae882..1437c676 100644 --- a/src/components/ConfirmDialog.tsx +++ b/src/components/ConfirmDialog.tsx @@ -7,7 +7,7 @@ import { DialogTitle, } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; -import { AlertTriangle } from "lucide-react"; +import { AlertTriangle, Info } from "lucide-react"; import { useTranslation } from "react-i18next"; interface ConfirmDialogProps { @@ -16,6 +16,7 @@ interface ConfirmDialogProps { message: string; confirmText?: string; cancelText?: string; + variant?: "destructive" | "info"; onConfirm: () => void; onCancel: () => void; } @@ -26,11 +27,16 @@ export function ConfirmDialog({ message, confirmText, cancelText, + variant = "destructive", onConfirm, onCancel, }: ConfirmDialogProps) { const { t } = useTranslation(); + const IconComponent = variant === "info" ? Info : AlertTriangle; + const iconClass = + variant === "info" ? "h-5 w-5 text-blue-500" : "h-5 w-5 text-destructive"; + return ( - + {title} @@ -54,7 +60,10 @@ export function ConfirmDialog({ - diff --git a/src/components/UsageScriptModal.tsx b/src/components/UsageScriptModal.tsx index 6bbbb90d..697da49c 100644 --- a/src/components/UsageScriptModal.tsx +++ b/src/components/UsageScriptModal.tsx @@ -4,7 +4,8 @@ import { toast } from "sonner"; import { useTranslation } from "react-i18next"; import { useQueryClient } from "@tanstack/react-query"; import { Provider, UsageScript, UsageData } from "@/types"; -import { usageApi, type AppId } from "@/lib/api"; +import { usageApi, settingsApi, type AppId } from "@/lib/api"; +import { useSettingsQuery } from "@/lib/query"; import { extractCodexBaseUrl } from "@/utils/providerConfigUtils"; import JsonEditor from "./JsonEditor"; import * as prettier from "prettier/standalone"; @@ -15,6 +16,7 @@ import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Switch } from "@/components/ui/switch"; import { FullScreenPanel } from "@/components/common/FullScreenPanel"; +import { ConfirmDialog } from "@/components/ConfirmDialog"; import { cn } from "@/lib/utils"; interface UsageScriptModalProps { @@ -112,6 +114,8 @@ const UsageScriptModal: React.FC = ({ }) => { const { t } = useTranslation(); const queryClient = useQueryClient(); + const { data: settingsData } = useSettingsQuery(); + const [showUsageConfirm, setShowUsageConfirm] = useState(false); // 生成带国际化的预设模板 const PRESET_TEMPLATES = generatePresetTemplates(t); @@ -247,6 +251,27 @@ const UsageScriptModal: React.FC = ({ const [showApiKey, setShowApiKey] = useState(false); const [showAccessToken, setShowAccessToken] = useState(false); + const handleEnableToggle = (checked: boolean) => { + if (checked && !settingsData?.usageConfirmed) { + setShowUsageConfirm(true); + } else { + setScript({ ...script, enabled: checked }); + } + }; + + const handleUsageConfirm = async () => { + setShowUsageConfirm(false); + try { + if (settingsData) { + await settingsApi.save({ ...settingsData, usageConfirmed: true }); + await queryClient.invalidateQueries({ queryKey: ["settings"] }); + } + } catch (error) { + console.error("Failed to save usage confirmed:", error); + } + setScript({ ...script, enabled: true }); + }; + const handleSave = () => { if (script.enabled && !script.code.trim()) { toast.error(t("usageScript.scriptEmpty")); @@ -436,9 +461,7 @@ const UsageScriptModal: React.FC = ({

- setScript({ ...script, enabled: checked }) - } + onCheckedChange={handleEnableToggle} aria-label={t("usageScript.enableUsageQuery")} /> @@ -844,6 +867,16 @@ const UsageScriptModal: React.FC = ({ )} + + void handleUsageConfirm()} + onCancel={() => setShowUsageConfirm(false)} + /> ); }; diff --git a/src/components/settings/ProxyTabContent.tsx b/src/components/settings/ProxyTabContent.tsx index 9515aa0b..825f34b3 100644 --- a/src/components/settings/ProxyTabContent.tsx +++ b/src/components/settings/ProxyTabContent.tsx @@ -1,3 +1,4 @@ +import { useState } from "react"; import * as AccordionPrimitive from "@radix-ui/react-accordion"; import { Server, Activity, ChevronDown, Zap, Globe } from "lucide-react"; import { motion } from "framer-motion"; @@ -17,6 +18,7 @@ import { AutoFailoverConfigPanel } from "@/components/proxy/AutoFailoverConfigPa import { FailoverQueueManager } from "@/components/proxy/FailoverQueueManager"; import { RectifierConfigPanel } from "@/components/settings/RectifierConfigPanel"; import { GlobalProxySettings } from "@/components/settings/GlobalProxySettings"; +import { ConfirmDialog } from "@/components/ConfirmDialog"; import { useProxyStatus } from "@/hooks/useProxyStatus"; import type { SettingsFormState } from "@/hooks/useSettings"; @@ -30,6 +32,7 @@ export function ProxyTabContent({ onAutoSave, }: ProxyTabContentProps) { const { t } = useTranslation(); + const [showProxyConfirm, setShowProxyConfirm] = useState(false); const { isRunning, @@ -42,6 +45,8 @@ export function ProxyTabContent({ try { if (!checked) { await stopWithRestore(); + } else if (!settings?.proxyConfirmed) { + setShowProxyConfirm(true); } else { await startProxyServer(); } @@ -50,6 +55,16 @@ export function ProxyTabContent({ } }; + const handleProxyConfirm = async () => { + setShowProxyConfirm(false); + try { + await onAutoSave({ proxyConfirmed: true }); + await startProxyServer(); + } catch (error) { + console.error("Proxy confirm failed:", error); + } + }; + return ( + + void handleProxyConfirm()} + onCancel={() => setShowProxyConfirm(false)} + /> ); } diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index b3899828..c19e4026 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -180,7 +180,17 @@ "deleteProvider": "Delete Provider", "deleteProviderMessage": "Are you sure you want to delete provider \"{{name}}\"? This action cannot be undone.", "removeProvider": "Remove Provider", - "removeProviderMessage": "Are you sure you want to remove provider \"{{name}}\" from the configuration?\n\nAfter removal, this provider will no longer be active, but the configuration data will be retained in CC Switch. You can re-add it at any time." + "removeProviderMessage": "Are you sure you want to remove provider \"{{name}}\" from the configuration?\n\nAfter removal, this provider will no longer be active, but the configuration data will be retained in CC Switch. You can re-add it at any time.", + "proxy": { + "title": "Enable Local Proxy", + "message": "Local proxy is an advanced feature. Please make sure you understand how it works before enabling.\n\nWe recommend consulting the relevant documentation or your provider for proper configuration.", + "confirm": "I understand, enable" + }, + "usage": { + "title": "Configure Usage Query", + "message": "Usage query requires a custom script or API parameters. Please make sure you have obtained the necessary information from your provider.\n\nIf unsure how to configure, please consult your provider's documentation first.", + "confirm": "I understand, configure" + } }, "settings": { "title": "Settings", diff --git a/src/i18n/locales/ja.json b/src/i18n/locales/ja.json index 427f1326..7cdab09d 100644 --- a/src/i18n/locales/ja.json +++ b/src/i18n/locales/ja.json @@ -180,7 +180,17 @@ "deleteProvider": "プロバイダーを削除", "deleteProviderMessage": "プロバイダー「{{name}}」を削除してもよろしいですか?この操作は元に戻せません。", "removeProvider": "プロバイダーを解除", - "removeProviderMessage": "プロバイダー「{{name}}」を設定から解除してもよろしいですか?\n\n解除後、このプロバイダーは無効になりますが、設定データは CC Switch に保持されます。いつでも再追加できます。" + "removeProviderMessage": "プロバイダー「{{name}}」を設定から解除してもよろしいですか?\n\n解除後、このプロバイダーは無効になりますが、設定データは CC Switch に保持されます。いつでも再追加できます。", + "proxy": { + "title": "ローカルプロキシの有効化", + "message": "ローカルプロキシは上級機能です。有効にする前に、その仕組みを理解していることをご確認ください。\n\n適切な設定方法については、関連ドキュメントまたはプロバイダーにご相談ください。", + "confirm": "理解しました、有効にする" + }, + "usage": { + "title": "使用量クエリの設定", + "message": "使用量クエリにはカスタムスクリプトまたは API パラメータが必要です。プロバイダーから必要な情報を取得していることをご確認ください。\n\n設定方法が不明な場合は、プロバイダーのドキュメントを先にご確認ください。", + "confirm": "理解しました、設定する" + } }, "settings": { "title": "設定", diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json index 8d25fedc..459ba2f2 100644 --- a/src/i18n/locales/zh.json +++ b/src/i18n/locales/zh.json @@ -180,7 +180,17 @@ "deleteProvider": "删除供应商", "deleteProviderMessage": "确定要删除供应商 \"{{name}}\" 吗?此操作无法撤销。", "removeProvider": "移除供应商", - "removeProviderMessage": "确定要从配置中移除供应商 \"{{name}}\" 吗?\n\n移除后该供应商将不再生效,但配置数据会保留在 CC Switch 中,您可以随时重新添加。" + "removeProviderMessage": "确定要从配置中移除供应商 \"{{name}}\" 吗?\n\n移除后该供应商将不再生效,但配置数据会保留在 CC Switch 中,您可以随时重新添加。", + "proxy": { + "title": "启用本地代理服务", + "message": "本地代理是一项高级功能,启用前请确保您已了解其工作原理。\n\n建议先查阅相关文档或咨询您的供应商,以获取正确的配置方式。", + "confirm": "我已了解,继续启用" + }, + "usage": { + "title": "配置用量查询", + "message": "用量查询需要配置专用的查询脚本或 API 参数,请确保您已从供应商处获取相关信息。\n\n如不确定如何配置,请先查阅供应商文档。", + "confirm": "我已了解,继续配置" + } }, "settings": { "title": "设置", diff --git a/src/types.ts b/src/types.ts index da3a00d3..77d97c4f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -214,6 +214,10 @@ export interface Settings { silentStartup?: boolean; // 是否启用主页面本地代理功能(默认关闭) enableLocalProxy?: boolean; + // User has confirmed the local proxy first-run notice + proxyConfirmed?: boolean; + // User has confirmed the usage query first-run notice + usageConfirmed?: boolean; // 首选语言(可选,默认中文) language?: "en" | "zh" | "ja";