feat(usage-query): decouple credentials from provider config

Add independent credential fields for usage query to support different
query endpoints and authentication methods.

Changes:
- Add `apiKey` and `baseUrl` fields to UsageScript struct
- Remove dependency on provider config credentials in query_usage
- Update test_usage_script to accept independent credential parameters
- Add credential input fields in UsageScriptModal based on template:
  * General: apiKey + baseUrl
  * NewAPI: baseUrl + accessToken + userId
  * Custom: no additional fields (full freedom)
- Auto-clear irrelevant fields when switching templates
- Add i18n text for "credentialsConfig"

Benefits:
- Query API can use different endpoint/key than provider config
- Better separation of concerns
- More flexible for various usage query scenarios
This commit is contained in:
Jason
2025-11-10 15:28:09 +08:00
parent 23d06515ad
commit 7096957b40
8 changed files with 178 additions and 92 deletions

View File

@@ -166,6 +166,8 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
appId,
script.code,
script.timeout,
script.apiKey,
script.baseUrl,
script.accessToken,
script.userId,
);
@@ -225,23 +227,40 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
const handleUsePreset = (presetName: string) => {
const preset = PRESET_TEMPLATES[presetName];
if (preset) {
// 如果选择的不是 NewAPI 模板,清空高级配置字段
if (presetName !== TEMPLATE_KEYS.NEW_API) {
// 根据模板类型清空不同的字段
if (presetName === TEMPLATE_KEYS.CUSTOM) {
// 自定义:清空所有凭证字段
setScript({
...script,
code: preset,
apiKey: undefined,
baseUrl: undefined,
accessToken: undefined,
userId: undefined,
});
} else if (presetName === TEMPLATE_KEYS.GENERAL) {
// 通用:保留 apiKey 和 baseUrl清空 NewAPI 字段
setScript({
...script,
code: preset,
accessToken: undefined,
userId: undefined,
});
} else {
setScript({ ...script, code: preset });
} else if (presetName === TEMPLATE_KEYS.NEW_API) {
// NewAPI清空 apiKeyNewAPI 不使用通用的 apiKey
setScript({
...script,
code: preset,
apiKey: undefined,
});
}
setSelectedTemplate(presetName); // 记录选择的模板
}
};
// 判断是否应该显示高级配置(仅 NewAPI 模板需要)
const shouldShowAdvancedConfig = selectedTemplate === TEMPLATE_KEYS.NEW_API;
// 判断是否应该显示凭证配置区域
const shouldShowCredentialsConfig =
selectedTemplate === TEMPLATE_KEYS.GENERAL || selectedTemplate === TEMPLATE_KEYS.NEW_API;
return (
<Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>
@@ -296,38 +315,97 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
</div>
</div>
{/* 高级配置Access Token 和 User ID NewAPI 模板显示 */}
{shouldShowAdvancedConfig && (
{/* 凭证配置区域:通用和 NewAPI 模板显示 */}
{shouldShowCredentialsConfig && (
<div className="space-y-3 p-4 bg-gray-50 dark:bg-gray-800/50 rounded-lg">
<label className="block">
<span className="text-xs text-gray-600 dark:text-gray-400">
{t("usageScript.accessToken")}
</span>
<input
type="text"
value={script.accessToken || ""}
onChange={(e) =>
setScript({ ...script, accessToken: e.target.value })
}
placeholder={t("usageScript.accessTokenPlaceholder")}
className="mt-1 w-full px-3 py-2 border border-border-default dark:border-border-default rounded bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm"
/>
</label>
<h4 className="text-sm font-medium text-gray-700 dark:text-gray-300">
{t("usageScript.credentialsConfig")}
</h4>
<label className="block">
<span className="text-xs text-gray-600 dark:text-gray-400">
{t("usageScript.userId")}
</span>
<input
type="text"
value={script.userId || ""}
onChange={(e) =>
setScript({ ...script, userId: e.target.value })
}
placeholder={t("usageScript.userIdPlaceholder")}
className="mt-1 w-full px-3 py-2 border border-border-default dark:border-border-default rounded bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm"
/>
</label>
{/* 通用模板:显示 apiKey + baseUrl */}
{selectedTemplate === TEMPLATE_KEYS.GENERAL && (
<>
<label className="block">
<span className="text-xs text-gray-600 dark:text-gray-400">
API Key
</span>
<input
type="password"
value={script.apiKey || ""}
onChange={(e) =>
setScript({ ...script, apiKey: e.target.value })
}
placeholder="sk-xxxxx"
className="mt-1 w-full px-3 py-2 border border-border-default dark:border-border-default rounded bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm"
/>
</label>
<label className="block">
<span className="text-xs text-gray-600 dark:text-gray-400">
Base URL
</span>
<input
type="text"
value={script.baseUrl || ""}
onChange={(e) =>
setScript({ ...script, baseUrl: e.target.value })
}
placeholder="https://api.example.com"
className="mt-1 w-full px-3 py-2 border border-border-default dark:border-border-default rounded bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm"
/>
</label>
</>
)}
{/* NewAPI 模板:显示 baseUrl + accessToken + userId */}
{selectedTemplate === TEMPLATE_KEYS.NEW_API && (
<>
<label className="block">
<span className="text-xs text-gray-600 dark:text-gray-400">
Base URL
</span>
<input
type="text"
value={script.baseUrl || ""}
onChange={(e) =>
setScript({ ...script, baseUrl: e.target.value })
}
placeholder="https://api.newapi.com"
className="mt-1 w-full px-3 py-2 border border-border-default dark:border-border-default rounded bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm"
/>
</label>
<label className="block">
<span className="text-xs text-gray-600 dark:text-gray-400">
{t("usageScript.accessToken")}
</span>
<input
type="text"
value={script.accessToken || ""}
onChange={(e) =>
setScript({ ...script, accessToken: e.target.value })
}
placeholder={t("usageScript.accessTokenPlaceholder")}
className="mt-1 w-full px-3 py-2 border border-border-default dark:border-border-default rounded bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm"
/>
</label>
<label className="block">
<span className="text-xs text-gray-600 dark:text-gray-400">
{t("usageScript.userId")}
</span>
<input
type="text"
value={script.userId || ""}
onChange={(e) =>
setScript({ ...script, userId: e.target.value })
}
placeholder={t("usageScript.userIdPlaceholder")}
className="mt-1 w-full px-3 py-2 border border-border-default dark:border-border-default rounded bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm"
/>
</label>
</>
)}
</div>
)}

View File

@@ -356,6 +356,7 @@
"templateCustom": "Custom",
"templateGeneral": "General",
"templateNewAPI": "NewAPI",
"credentialsConfig": "Credentials",
"accessToken": "Access Token",
"accessTokenPlaceholder": "Generate in 'Security Settings'",
"userId": "User ID",

View File

@@ -356,6 +356,7 @@
"templateCustom": "自定义",
"templateGeneral": "通用模板",
"templateNewAPI": "NewAPI",
"credentialsConfig": "凭证配置",
"accessToken": "访问令牌",
"accessTokenPlaceholder": "在'安全设置'里生成",
"userId": "用户 ID",

View File

@@ -32,6 +32,8 @@ export const usageApi = {
appId: AppId,
scriptCode: string,
timeout?: number,
apiKey?: string,
baseUrl?: string,
accessToken?: string,
userId?: string,
): Promise<UsageResult> {
@@ -41,6 +43,8 @@ export const usageApi = {
app: appId,
scriptCode: scriptCode,
timeout: timeout,
apiKey: apiKey,
baseUrl: baseUrl,
accessToken: accessToken,
userId: userId,
});

View File

@@ -45,8 +45,10 @@ export interface UsageScript {
language: "javascript"; // 脚本语言
code: string; // 脚本代码JSON 格式配置)
timeout?: number; // 超时时间(秒,默认 10
accessToken?: string; // 访问令牌(用于需要登录的接口
userId?: string; // 用户ID用于需要用户标识的接口
apiKey?: string; // 用量查询专用的 API Key通用模板使用
baseUrl?: string; // 用量查询专用的 Base URL通用和 NewAPI 模板使用
accessToken?: string; // 访问令牌NewAPI 模板使用)
userId?: string; // 用户IDNewAPI 模板使用)
autoQueryInterval?: number; // 自动查询间隔单位分钟0 表示禁用)
}