import React, { useState } from "react"; import { Play, Wand2 } from "lucide-react"; import { toast } from "sonner"; import { useTranslation } from "react-i18next"; import { Provider, UsageScript } from "../types"; import { usageApi, type AppId } from "@/lib/api"; import JsonEditor from "./JsonEditor"; import * as prettier from "prettier/standalone"; import * as parserBabel from "prettier/parser-babel"; import * as pluginEstree from "prettier/plugins/estree"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; interface UsageScriptModalProps { provider: Provider; appId: AppId; isOpen: boolean; onClose: () => void; onSave: (script: UsageScript) => void; } // 预设模板(JS 对象字面量格式) const PRESET_TEMPLATES: Record = { 通用模板: `({ request: { url: "{{baseUrl}}/user/balance", method: "GET", headers: { "Authorization": "Bearer {{apiKey}}", "User-Agent": "cc-switch/1.0" } }, extractor: function(response) { return { isValid: response.is_active || true, remaining: response.balance, unit: "USD" }; } })`, NewAPI: `({ request: { url: "{{baseUrl}}/api/user/self", method: "GET", headers: { "Content-Type": "application/json", "Authorization": "Bearer {{accessToken}}", "New-Api-User": "{{userId}}" }, }, extractor: function (response) { if (response.success && response.data) { return { planName: response.data.group || "默认套餐", remaining: response.data.quota / 500000, used: response.data.used_quota / 500000, total: (response.data.quota + response.data.used_quota) / 500000, unit: "USD", }; } return { isValid: false, invalidMessage: response.message || "查询失败" }; }, })`, }; const UsageScriptModal: React.FC = ({ provider, appId, isOpen, onClose, onSave, }) => { const { t } = useTranslation(); const [script, setScript] = useState(() => { return ( provider.meta?.usage_script || { enabled: false, language: "javascript", code: PRESET_TEMPLATES[ t("usageScript.presetTemplate") === "预设模板" ? "通用模板" : "General" ], timeout: 10, } ); }); const [testing, setTesting] = useState(false); const handleSave = () => { // 验证脚本格式 if (script.enabled && !script.code.trim()) { toast.error(t("usageScript.scriptEmpty")); return; } // 基本的 JS 语法检查(检查是否包含 return 语句) if (script.enabled && !script.code.includes("return")) { toast.error(t("usageScript.mustHaveReturn"), { duration: 5000 }); return; } onSave(script); onClose(); }; const handleTest = async () => { setTesting(true); try { const result = await usageApi.query(provider.id, appId); if (result.success && result.data && result.data.length > 0) { // 显示所有套餐数据 const summary = result.data .map((plan) => { const planInfo = plan.planName ? `[${plan.planName}]` : ""; return `${planInfo} ${t("usage.remaining")} ${plan.remaining} ${plan.unit}`; }) .join(", "); toast.success(`${t("usageScript.testSuccess")}${summary}`, { duration: 3000, }); } else { toast.error( `${t("usageScript.testFailed")}: ${result.error || t("endpointTest.noResult")}`, { duration: 5000, }, ); } } catch (error: any) { toast.error( `${t("usageScript.testFailed")}: ${error?.message || t("common.unknown")}`, { duration: 5000, }, ); } finally { setTesting(false); } }; const handleFormat = async () => { try { const formatted = await prettier.format(script.code, { parser: "babel", plugins: [parserBabel as any, pluginEstree as any], semi: true, singleQuote: false, tabWidth: 2, printWidth: 80, }); setScript({ ...script, code: formatted.trim() }); toast.success(t("usageScript.formatSuccess"), { duration: 1000 }); } catch (error: any) { toast.error( `${t("usageScript.formatFailed")}: ${error?.message || t("jsonEditor.invalidJson")}`, { duration: 3000, }, ); } }; const handleUsePreset = (presetName: string) => { const preset = PRESET_TEMPLATES[presetName]; if (preset) { setScript({ ...script, code: preset }); } }; return ( !open && onClose()}> {t("usageScript.title")} - {provider.name} {/* Content - Scrollable */}
{/* 启用开关 */} {script.enabled && ( <> {/* 预设模板选择 */}
{Object.keys(PRESET_TEMPLATES).map((name) => ( ))}
{/* 脚本编辑器 */}
setScript({ ...script, code })} height="300px" language="javascript" />

{t("usageScript.variablesHint", { apiKey: "{{apiKey}}", baseUrl: "{{baseUrl}}", })}

{/* 配置选项 */}
{/* 高级配置:Access Token 和 User ID */}

🔑 高级配置(可选,用于需要登录的接口)

💡 在脚本中使用:{`{{accessToken}}`} 和 {`{{userId}}`}

{/* 脚本说明 */}

{t("usageScript.scriptHelp")}

{t("usageScript.configFormat")}
                      {`({
  request: {
    url: "{{baseUrl}}/api/usage",
    method: "POST",
    headers: {
      "Authorization": "Bearer {{apiKey}}",
      "User-Agent": "cc-switch/1.0"
    },
    body: JSON.stringify({ key: "value" })  // 可选
  },
  extractor: function(response) {
    // response 是 API 返回的 JSON 数据
    return {
      isValid: !response.error,
      remaining: response.balance,
      unit: "USD"
    };
  }
})`}
                    
{t("usageScript.extractorFormat")}
  • {t("usageScript.fieldIsValid")}
  • {t("usageScript.fieldInvalidMessage")}
  • {t("usageScript.fieldRemaining")}
  • {t("usageScript.fieldUnit")}
  • {t("usageScript.fieldPlanName")}
  • {t("usageScript.fieldTotal")}
  • {t("usageScript.fieldUsed")}
  • {t("usageScript.fieldExtra")}
{t("usageScript.tips")}
  • {t("usageScript.tip1", { apiKey: "{{apiKey}}", baseUrl: "{{baseUrl}}", })}
  • {t("usageScript.tip2")}
  • {t("usageScript.tip3")}
)}
{/* Footer */} {/* Left side - Test and Format buttons */}
{/* Right side - Cancel and Save buttons */}
); }; export default UsageScriptModal;