import React, { useState } from "react"; import { Play, Wand2 } from "lucide-react"; import { Provider, UsageScript } from "../types"; import { usageApi, type AppType } 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; appType: AppType; isOpen: boolean; onClose: () => void; onSave: (script: UsageScript) => void; onNotify?: ( message: string, type: "success" | "error", duration?: number, ) => 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/usage/token", method: "GET", headers: { Authorization: "Bearer {{apiKey}}", }, }, extractor: function (response) { if (response.code) { if (response.data.unlimited_quota) { return { planName: response.data.name, total: -1, used: response.data.total_used / 500000, unit: "USD", }; } return { isValid: true, planName: response.data.name, total: response.data.total_granted / 500000, used: response.data.total_used / 500000, remaining: response.data.total_available / 500000, unit: "USD", }; } if (response.error) { return { isValid: false, invalidMessage: response.error.message, }; } }, })`, }; const UsageScriptModal: React.FC = ({ provider, appType, isOpen, onClose, onSave, onNotify, }) => { const [script, setScript] = useState(() => { return ( provider.meta?.usage_script || { enabled: false, language: "javascript", code: PRESET_TEMPLATES["通用模板"], timeout: 10, } ); }); const [testing, setTesting] = useState(false); const handleSave = () => { // 验证脚本格式 if (script.enabled && !script.code.trim()) { onNotify?.("脚本配置不能为空", "error"); return; } // 基本的 JS 语法检查(检查是否包含 return 语句) if (script.enabled && !script.code.includes("return")) { onNotify?.("脚本必须包含 return 语句", "error", 5000); return; } onSave(script); onClose(); onNotify?.("用量查询配置已保存", "success", 2000); }; const handleTest = async () => { setTesting(true); try { const result = await usageApi.query(provider.id, appType); if (result.success && result.data && result.data.length > 0) { // 显示所有套餐数据 const summary = result.data .map((plan) => { const planInfo = plan.planName ? `[${plan.planName}]` : ""; return `${planInfo} 剩余: ${plan.remaining} ${plan.unit}`; }) .join(", "); onNotify?.(`测试成功!${summary}`, "success", 3000); } else { onNotify?.(`测试失败: ${result.error || "无数据返回"}`, "error", 5000); } } catch (error: any) { onNotify?.(`测试失败: ${error?.message || "未知错误"}`, "error", 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() }); onNotify?.("格式化成功", "success", 1000); } catch (error: any) { onNotify?.(`格式化失败: ${error?.message || "语法错误"}`, "error", 3000); } }; const handleUsePreset = (presetName: string) => { const preset = PRESET_TEMPLATES[presetName]; if (preset) { setScript({ ...script, code: preset }); } }; return ( !open && onClose()}> 配置用量查询 - {provider.name} {/* Content - Scrollable */}
{/* 启用开关 */} {script.enabled && ( <> {/* 预设模板选择 */}
{Object.keys(PRESET_TEMPLATES).map((name) => ( ))}
{/* 脚本编辑器 */}
setScript({ ...script, code })} height="300px" language="javascript" />

支持变量: {"{{apiKey}}"},{" "} {"{{baseUrl}}"} | extractor 函数接收 API 响应的 JSON 对象

{/* 配置选项 */}
{/* 脚本说明 */}

脚本编写说明:

配置格式:
                      {`({
  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"
    };
  }
})`}
                    
extractor 返回格式(所有字段均为可选):
  • isValid: 布尔值,套餐是否有效
  • invalidMessage: 字符串,失效原因说明(当 isValid 为 false 时显示)
  • remaining: 数字,剩余额度
  • unit: 字符串,单位(如 "USD")
  • planName: 字符串,套餐名称
  • total: 数字,总额度
  • used: 数字,已用额度
  • extra: 字符串,扩展字段,可自由补充需要展示的文本
💡 提示:
  • • 变量 {"{{apiKey}}"} 和{" "} {"{{baseUrl}}"} 会自动替换
  • • extractor 函数在沙箱环境中执行,支持 ES2020+ 语法
  • • 整个配置必须用 (){" "} 包裹,形成对象字面量表达式
)}
{/* Footer */} {/* Left side - Test and Format buttons */}
{/* Right side - Cancel and Save buttons */}
); }; export default UsageScriptModal;