mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-28 00:35:32 +08:00
feat(copilot): add GitHub Copilot reverse proxy support (#930)
* refactor(toolsearch): replace binary patch with ENABLE_TOOL_SEARCH env var toggle
- Remove toolsearch_patch.rs binary patching mechanism (~590 lines)
- Delete `toolsearch_patch.rs` and `commands/toolsearch.rs`
- Remove auto-patch startup logic and command registration from lib.rs
- Remove `tool_search_bypass` field from settings.rs
- Remove frontend settings ToggleRow, useSettings hook sync logic, and API methods
- Clean up zh/en/ja i18n keys (notifications + settings)
- Add ENABLE_TOOL_SEARCH toggle to Claude provider form
- Add checkbox in CommonConfigEditor.tsx (alongside teammates toggle)
- When enabled, writes `"env": { "ENABLE_TOOL_SEARCH": "true" }`
- When disabled, removes the key; takes effect on provider switch
- Add zh/en/ja i18n key: `claudeConfig.enableToolSearch`
Claude Code 2.1.76+ natively supports this env var, eliminating the need for binary patching.
* feat(claude): add effortLevel high toggle to provider form
- Add "high-effort thinking" checkbox to Claude provider config form
- When checked, writes `"effortLevel": "high"`; when unchecked, removes the field
- Add zh/en/ja i18n translations
* refactor(claude): remove deprecated alwaysThinking toggle
- Claude Code now enables extended thinking by default; alwaysThinkingEnabled is a no-op
- Thinking control is now handled via effortLevel (added in prior commit)
- Remove state, switch case, and checkbox UI from CommonConfigEditor
- Clean up alwaysThinking i18n keys across zh/en/ja locales
* feat(opencode): add setCacheKey: true to all provider presets
- Add setCacheKey: true to options in all 33 regular presets
- Add setCacheKey: true to OPENCODE_DEFAULT_CONFIG for custom providers
- Exclude 2 OMO presets (Oh My OpenCode / Slim) which have their own config mechanism
Closes #1523
* fix(codex): resolve 1M context window toggle causing MCP editor flicker
- Add localValueRef to short-circuit duplicate CodeMirror updateListener callbacks,
breaking the React state → CodeMirror → stale onChange → React state feedback loop
- Use localValueRef.current in handleContextWindowToggle and handleCompactLimitChange
to avoid stale closure reads
- Change compact limit input from type="number" to type="text" with inputMode="numeric"
to remove unnecessary spinner buttons
* feat(codex): add 1M context window toggle utilities and i18n keys
- Add extractCodexTopLevelInt, setCodexTopLevelInt, removeCodexTopLevelField
TOML helpers in providerConfigUtils.ts
- Add i18n keys for contextWindow1M, autoCompactLimit in zh/en/ja locales
* feat(claude): collapse model mapping fields by default
- Wrap 5 model mapping inputs in a Collapsible, collapsed by default
- Auto-expand when any model value is present (including preset-filled)
- Show hint text when collapsed explaining most users need no config
- Add zh/en/ja i18n keys for toggle label and collapsed hint
- Use variant={null} to avoid ghost button hover style clash in dark mode
* feat(claude): merge advanced fields into single collapsible section
- Merge API format, auth field, and model mapping into a unified "Advanced Options" collapsible
- Extend smart-expand logic to detect non-default values across all advanced fields
- Preserve model mapping sub-header and hint with a separator line
- Update zh/en/ja i18n keys (advancedOptionsToggle, advancedOptionsHint, modelMappingLabel, modelMappingHint)
* feat(copilot): add GitHub Copilot reverse proxy support
Add GitHub Copilot as a Claude provider variant with OAuth device code
authentication and Anthropic ↔ OpenAI format transformation.
Backend:
- Add CopilotAuthManager for GitHub OAuth device code flow
- Implement Copilot token auto-refresh (60s before expiry)
- Persist GitHub token to ~/.cc-switch/copilot_auth.json
- Add ProviderType::GitHubCopilot and AuthStrategy::GitHubCopilot
- Modify forwarder to use /chat/completions for Copilot
- Add Copilot-specific headers (Editor-Version, Editor-Plugin-Version)
Frontend:
- Add CopilotAuthSection component for OAuth UI
- Add useCopilotAuth hook for OAuth state management
- Auto-copy user code to clipboard and open browser
- Use 8-second polling interval to avoid GitHub rate limits
- Skip API Key validation for Copilot providers
- Add GitHub Copilot preset with claude-sonnet-4 model
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
* fix(copilot): remove is_expired() calls from tests
Remove references to deleted is_expired() method in test code.
Only is_expiring_soon() is needed for token refresh logic.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
* feat(copilot): add real-time model listing from Copilot API
- Add fetch_models() to CopilotAuthManager calling GET /models endpoint
- Add copilot_get_models Tauri command
- Add copilotGetModels() frontend API wrapper
- Modify ClaudeFormFields to show model dropdown for Copilot providers
- Fetches available models on component mount when isCopilotPreset
- Groups models by vendor (Anthropic, OpenAI, Google, etc.)
- Input + dropdown button combo allows both manual entry and selection
- Non-Copilot providers keep original plain Input behavior
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(copilot): add usage query integration
- Add Copilot usage API integration (fetch_usage method)
- Add copilot_get_usage Tauri command
- Add GitHub Copilot template in usage query modal
- Unify naming: copilot → github_copilot
- Add constants management (TEMPLATE_TYPES, PROVIDER_TYPES)
- Improve error handling with detailed error messages
- Add database migration (v5 → v6) for template type update
- Add i18n translations (zh, en, ja)
- Improve type safety with TemplateType
- Apply code formatting (cargo fmt, prettier)
* 修复github 登录和注销问题 ,模型选择问题
* feat(copilot): add multi-account support for GitHub Copilot
- Add multi-account storage structure with v1 to v2 migration
- Add per-account token caching and auto-refresh
- Add new Tauri commands for account management
- Integrate account selection in Proxy forwarder
- Add account selection UI in CopilotAuthSection
- Save githubAccountId to ProviderMeta
- Add i18n translations for multi-account features (zh/en/ja)
* 修复用量查询Reset字段出现多余字符
* refactor(auth-binding): introduce generic provider auth binding primitives
- add shared authBinding types in Rust and TypeScript while keeping githubAccountId as a compatibility field\n- resolve Copilot token, models, and usage through provider-bound account lookup instead of only the implicit default account\n- fix the Unix build regression in settings.rs by restoring std::io::Write for write_all()\n- remove the accidental .github ignore entry and drop leftover Copilot form debug logs\n- keep the first migration step non-breaking by writing both authBinding and the legacy githubAccountId field from the form
* refactor(auth-service): add managed auth command surface and explicit default account state
- introduce generic managed auth commands and frontend auth API wrappers for provider-scoped login, status, account listing, removal, logout, and default-account selection\n- store an explicit Copilot default_account_id instead of relying on HashMap iteration order, and use it consistently for fallback token/model/usage resolution\n- sort managed accounts deterministically and surface default-account state to the UI\n- refactor the Copilot form hook to wrap a generic useManagedAuth implementation while preserving the existing component contract\n- add default-account controls to the Copilot auth section and extend Copilot auth status serialization/tests for the new state
* feat(auth-center): add a dedicated settings entrypoint for managed OAuth accounts
- add an Auth Center tab to Settings so managed OAuth accounts are no longer hidden inside individual provider forms\n- introduce a first AuthCenterPanel that hosts GitHub Copilot account management as the initial managed auth provider\n- keep the provider form experience intact while establishing a global account-management surface for future providers such as OpenAI\n- validate that the new settings tab works cleanly with the generic managed auth hook and existing Copilot account controls
* feat(add-provider): expose managed OAuth sources alongside universal providers
- add an OAuth tab to the Add Provider flow so managed auth sources sit beside app-specific and universal providers\n- reuse the new Auth Center panel inside the dialog, keeping account management discoverable during provider creation\n- make the dialog footer adapt to the OAuth tab so account setup does not pretend to create a provider directly\n- align the add-provider UX with the new architecture where OAuth accounts are global assets and providers bind to them later
* fix(auth-reliability): harden managed auth persistence and refresh behavior
- replace direct Copilot auth store writes with private temp-file writes and atomic rename semantics, and document the local token storage limitation\n- add per-account refresh locks plus a double-check path so concurrent requests do not stampede GitHub token refresh\n- surface legacy migration failures through auth status, expose them in the UI, and add translated copy for the new account-state labels\n- stop writing the legacy githubAccountId field from the provider form while keeping compatibility reads in place\n- add logout error recovery and Copilot model-load toasts so auth failures are no longer silently swallowed
* refactor(copilot-detection): prefer provider type before URL fallbacks
- update forwarder endpoint rewriting to treat providerType as the primary GitHub Copilot signal\n- keep githubcopilot.com string matching only as a compatibility fallback for older provider records without providerType\n- reduce one more path where Copilot behavior depended purely on URL heuristics
* fix(copilot-auth): add cancel button to error state in CopilotAuthSection
- 错误状态下仅有"重试"按钮,用户无法退出(如不可恢复的 403 未订阅错误)
- 新增"取消"按钮,复用已有的 cancelAuth 逻辑重置为 idle 状态
* 修复打包后github账号头像显示异常
* 修复github copilot 来源的模型测试报错
* feat(copilot-preset): add default model presets for GitHub Copilot
- 补充 Copilot 预设的默认模型配置,用户选完预设即可直接使用
- ANTHROPIC_MODEL: claude-opus-4.6
- ANTHROPIC_DEFAULT_HAIKU_MODEL: claude-haiku-4.5
- ANTHROPIC_DEFAULT_SONNET_MODEL: claude-sonnet-4.6
- ANTHROPIC_DEFAULT_OPUS_MODEL: claude-opus-4.6
---------
Co-authored-by: Jason <farion1231@gmail.com>
Co-authored-by: 周梦泽 <mengze.zhou@dafeng-tech.com>
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
+180
-105
@@ -5,7 +5,9 @@ import { useTranslation } from "react-i18next";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { Provider, UsageScript, UsageData } from "@/types";
|
||||
import { usageApi, settingsApi, type AppId } from "@/lib/api";
|
||||
import { copilotGetUsage, copilotGetUsageForAccount } from "@/lib/api/copilot";
|
||||
import { useSettingsQuery } from "@/lib/query";
|
||||
import { resolveManagedAccountId } from "@/lib/authBinding";
|
||||
import { extractCodexBaseUrl } from "@/utils/providerConfigUtils";
|
||||
import JsonEditor from "./JsonEditor";
|
||||
import * as prettier from "prettier/standalone";
|
||||
@@ -18,6 +20,7 @@ import { Switch } from "@/components/ui/switch";
|
||||
import { FullScreenPanel } from "@/components/common/FullScreenPanel";
|
||||
import { ConfirmDialog } from "@/components/ConfirmDialog";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { TEMPLATE_TYPES, PROVIDER_TYPES } from "@/config/constants";
|
||||
|
||||
interface UsageScriptModalProps {
|
||||
provider: Provider;
|
||||
@@ -27,18 +30,11 @@ interface UsageScriptModalProps {
|
||||
onSave: (script: UsageScript) => void;
|
||||
}
|
||||
|
||||
// 预设模板键名(用于国际化)
|
||||
const TEMPLATE_KEYS = {
|
||||
CUSTOM: "custom",
|
||||
GENERAL: "general",
|
||||
NEW_API: "newapi",
|
||||
} as const;
|
||||
|
||||
// 生成预设模板的函数(支持国际化)
|
||||
const generatePresetTemplates = (
|
||||
t: (key: string) => string,
|
||||
): Record<string, string> => ({
|
||||
[TEMPLATE_KEYS.CUSTOM]: `({
|
||||
[TEMPLATE_TYPES.CUSTOM]: `({
|
||||
request: {
|
||||
url: "",
|
||||
method: "GET",
|
||||
@@ -52,7 +48,7 @@ const generatePresetTemplates = (
|
||||
}
|
||||
})`,
|
||||
|
||||
[TEMPLATE_KEYS.GENERAL]: `({
|
||||
[TEMPLATE_TYPES.GENERAL]: `({
|
||||
request: {
|
||||
url: "{{baseUrl}}/user/balance",
|
||||
method: "GET",
|
||||
@@ -70,7 +66,7 @@ const generatePresetTemplates = (
|
||||
}
|
||||
})`,
|
||||
|
||||
[TEMPLATE_KEYS.NEW_API]: `({
|
||||
[TEMPLATE_TYPES.NEW_API]: `({
|
||||
request: {
|
||||
url: "{{baseUrl}}/api/user/self",
|
||||
method: "GET",
|
||||
@@ -96,13 +92,17 @@ const generatePresetTemplates = (
|
||||
};
|
||||
},
|
||||
})`,
|
||||
|
||||
// GitHub Copilot 模板不需要脚本,使用专用 API
|
||||
[TEMPLATE_TYPES.GITHUB_COPILOT]: "",
|
||||
});
|
||||
|
||||
// 模板名称国际化键映射
|
||||
const TEMPLATE_NAME_KEYS: Record<string, string> = {
|
||||
[TEMPLATE_KEYS.CUSTOM]: "usageScript.templateCustom",
|
||||
[TEMPLATE_KEYS.GENERAL]: "usageScript.templateGeneral",
|
||||
[TEMPLATE_KEYS.NEW_API]: "usageScript.templateNewAPI",
|
||||
[TEMPLATE_TYPES.CUSTOM]: "usageScript.templateCustom",
|
||||
[TEMPLATE_TYPES.GENERAL]: "usageScript.templateGeneral",
|
||||
[TEMPLATE_TYPES.NEW_API]: "usageScript.templateNewAPI",
|
||||
[TEMPLATE_TYPES.GITHUB_COPILOT]: "usageScript.templateCopilot",
|
||||
};
|
||||
|
||||
const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
||||
@@ -167,7 +167,7 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
||||
const defaultScript = {
|
||||
enabled: false,
|
||||
language: "javascript" as const,
|
||||
code: PRESET_TEMPLATES[TEMPLATE_KEYS.GENERAL],
|
||||
code: PRESET_TEMPLATES[TEMPLATE_TYPES.GENERAL],
|
||||
timeout: 10,
|
||||
};
|
||||
|
||||
@@ -230,6 +230,10 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
||||
const [selectedTemplate, setSelectedTemplate] = useState<string | null>(
|
||||
() => {
|
||||
const existingScript = provider.meta?.usage_script;
|
||||
// Copilot 供应商默认使用 Copilot 模板
|
||||
if (provider.meta?.providerType === PROVIDER_TYPES.GITHUB_COPILOT) {
|
||||
return TEMPLATE_TYPES.GITHUB_COPILOT;
|
||||
}
|
||||
// 优先使用保存的 templateType
|
||||
if (existingScript?.templateType) {
|
||||
return existingScript.templateType;
|
||||
@@ -237,14 +241,14 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
||||
// 向后兼容:根据字段推断模板类型
|
||||
// 检测 NEW_API 模板(有 accessToken 或 userId)
|
||||
if (existingScript?.accessToken || existingScript?.userId) {
|
||||
return TEMPLATE_KEYS.NEW_API;
|
||||
return TEMPLATE_TYPES.NEW_API;
|
||||
}
|
||||
// 检测 GENERAL 模板(有 apiKey 或 baseUrl)
|
||||
if (existingScript?.apiKey || existingScript?.baseUrl) {
|
||||
return TEMPLATE_KEYS.GENERAL;
|
||||
return TEMPLATE_TYPES.GENERAL;
|
||||
}
|
||||
// 新配置或无凭证:默认使用 GENERAL(与默认代码模板一致)
|
||||
return TEMPLATE_KEYS.GENERAL;
|
||||
return TEMPLATE_TYPES.GENERAL;
|
||||
},
|
||||
);
|
||||
|
||||
@@ -273,13 +277,16 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
if (script.enabled && !script.code.trim()) {
|
||||
toast.error(t("usageScript.scriptEmpty"));
|
||||
return;
|
||||
}
|
||||
if (script.enabled && !script.code.includes("return")) {
|
||||
toast.error(t("usageScript.mustHaveReturn"), { duration: 5000 });
|
||||
return;
|
||||
// Copilot 模板不需要脚本验证
|
||||
if (selectedTemplate !== TEMPLATE_TYPES.GITHUB_COPILOT) {
|
||||
if (script.enabled && !script.code.trim()) {
|
||||
toast.error(t("usageScript.scriptEmpty"));
|
||||
return;
|
||||
}
|
||||
if (script.enabled && !script.code.includes("return")) {
|
||||
toast.error(t("usageScript.mustHaveReturn"), { duration: 5000 });
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 保存时记录当前选择的模板类型
|
||||
const scriptWithTemplate = {
|
||||
@@ -288,6 +295,7 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
||||
| "custom"
|
||||
| "general"
|
||||
| "newapi"
|
||||
| "github_copilot"
|
||||
| undefined,
|
||||
};
|
||||
onSave(scriptWithTemplate);
|
||||
@@ -297,6 +305,38 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
||||
const handleTest = async () => {
|
||||
setTesting(true);
|
||||
try {
|
||||
// Copilot 模板使用专用 API
|
||||
if (selectedTemplate === TEMPLATE_TYPES.GITHUB_COPILOT) {
|
||||
const accountId = resolveManagedAccountId(
|
||||
provider.meta,
|
||||
PROVIDER_TYPES.GITHUB_COPILOT,
|
||||
);
|
||||
const usage = accountId
|
||||
? await copilotGetUsageForAccount(accountId)
|
||||
: await copilotGetUsage();
|
||||
const premium = usage.quota_snapshots.premium_interactions;
|
||||
const used = premium.entitlement - premium.remaining;
|
||||
const summary = `[${usage.copilot_plan}] ${t("usage.remaining")} ${premium.remaining}/${premium.entitlement} (${t("usageScript.resetDate")}: ${usage.quota_reset_date})`;
|
||||
toast.success(`${t("usageScript.testSuccess")}${summary}`, {
|
||||
duration: 3000,
|
||||
closeButton: true,
|
||||
});
|
||||
// 更新缓存
|
||||
queryClient.setQueryData(["usage", provider.id, appId], {
|
||||
success: true,
|
||||
data: [
|
||||
{
|
||||
planName: usage.copilot_plan,
|
||||
remaining: premium.remaining,
|
||||
total: premium.entitlement,
|
||||
used: used,
|
||||
unit: t("usageScript.premiumRequests"),
|
||||
},
|
||||
],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await usageApi.testScript(
|
||||
provider.id,
|
||||
appId,
|
||||
@@ -370,7 +410,7 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
||||
const handleUsePreset = (presetName: string) => {
|
||||
const preset = PRESET_TEMPLATES[presetName];
|
||||
if (preset) {
|
||||
if (presetName === TEMPLATE_KEYS.CUSTOM) {
|
||||
if (presetName === TEMPLATE_TYPES.CUSTOM) {
|
||||
// 🔧 自定义模式:用户应该在脚本中直接写完整 URL 和凭证,而不是依赖变量替换
|
||||
// 这样可以避免同源检查导致的问题
|
||||
// 如果用户想使用变量,需要手动在配置中设置 baseUrl/apiKey
|
||||
@@ -383,27 +423,37 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
||||
accessToken: undefined,
|
||||
userId: undefined,
|
||||
});
|
||||
} else if (presetName === TEMPLATE_KEYS.GENERAL) {
|
||||
} else if (presetName === TEMPLATE_TYPES.GENERAL) {
|
||||
setScript({
|
||||
...script,
|
||||
code: preset,
|
||||
accessToken: undefined,
|
||||
userId: undefined,
|
||||
});
|
||||
} else if (presetName === TEMPLATE_KEYS.NEW_API) {
|
||||
} else if (presetName === TEMPLATE_TYPES.NEW_API) {
|
||||
setScript({
|
||||
...script,
|
||||
code: preset,
|
||||
apiKey: undefined,
|
||||
});
|
||||
} else if (presetName === TEMPLATE_TYPES.GITHUB_COPILOT) {
|
||||
// Copilot 模板不需要脚本和凭证,使用专用 API
|
||||
setScript({
|
||||
...script,
|
||||
code: "",
|
||||
apiKey: undefined,
|
||||
baseUrl: undefined,
|
||||
accessToken: undefined,
|
||||
userId: undefined,
|
||||
});
|
||||
}
|
||||
setSelectedTemplate(presetName);
|
||||
}
|
||||
};
|
||||
|
||||
const shouldShowCredentialsConfig =
|
||||
selectedTemplate === TEMPLATE_KEYS.GENERAL ||
|
||||
selectedTemplate === TEMPLATE_KEYS.NEW_API;
|
||||
selectedTemplate === TEMPLATE_TYPES.GENERAL ||
|
||||
selectedTemplate === TEMPLATE_TYPES.NEW_API;
|
||||
|
||||
const footer = (
|
||||
<>
|
||||
@@ -474,30 +524,40 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
||||
{t("usageScript.presetTemplate")}
|
||||
</Label>
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
{Object.keys(PRESET_TEMPLATES).map((name) => {
|
||||
const isSelected = selectedTemplate === name;
|
||||
return (
|
||||
<Button
|
||||
key={name}
|
||||
type="button"
|
||||
variant={isSelected ? "default" : "outline"}
|
||||
size="sm"
|
||||
className={cn(
|
||||
"rounded-lg border",
|
||||
isSelected
|
||||
? "shadow-sm"
|
||||
: "bg-background text-muted-foreground hover:bg-accent hover:text-accent-foreground",
|
||||
)}
|
||||
onClick={() => handleUsePreset(name)}
|
||||
>
|
||||
{t(TEMPLATE_NAME_KEYS[name])}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
{Object.keys(PRESET_TEMPLATES)
|
||||
.filter((name) => {
|
||||
const isCopilotProvider =
|
||||
provider.meta?.providerType === "github_copilot";
|
||||
// Copilot 供应商只显示 copilot 模板,其他供应商不显示 copilot 模板
|
||||
if (isCopilotProvider) {
|
||||
return name === TEMPLATE_TYPES.GITHUB_COPILOT;
|
||||
}
|
||||
return name !== TEMPLATE_TYPES.GITHUB_COPILOT;
|
||||
})
|
||||
.map((name) => {
|
||||
const isSelected = selectedTemplate === name;
|
||||
return (
|
||||
<Button
|
||||
key={name}
|
||||
type="button"
|
||||
variant={isSelected ? "default" : "outline"}
|
||||
size="sm"
|
||||
className={cn(
|
||||
"rounded-lg border",
|
||||
isSelected
|
||||
? "shadow-sm"
|
||||
: "bg-background text-muted-foreground hover:bg-accent hover:text-accent-foreground",
|
||||
)}
|
||||
onClick={() => handleUsePreset(name)}
|
||||
>
|
||||
{t(TEMPLATE_NAME_KEYS[name])}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* 自定义模式:变量提示和具体值 */}
|
||||
{selectedTemplate === TEMPLATE_KEYS.CUSTOM && (
|
||||
{selectedTemplate === TEMPLATE_TYPES.CUSTOM && (
|
||||
<div className="space-y-2 border-t border-white/10 pt-3">
|
||||
<h4 className="text-sm font-medium text-foreground">
|
||||
{t("usageScript.supportedVariables")}
|
||||
@@ -564,6 +624,15 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Copilot 模式:自动认证提示 */}
|
||||
{selectedTemplate === TEMPLATE_TYPES.GITHUB_COPILOT && (
|
||||
<div className="space-y-2 border-t border-white/10 pt-3">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t("usageScript.copilotAutoAuth")}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 凭证配置 */}
|
||||
{shouldShowCredentialsConfig && (
|
||||
<div className="space-y-4">
|
||||
@@ -577,7 +646,7 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
{selectedTemplate === TEMPLATE_KEYS.GENERAL && (
|
||||
{selectedTemplate === TEMPLATE_TYPES.GENERAL && (
|
||||
<>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="usage-api-key">
|
||||
@@ -641,7 +710,7 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
||||
</>
|
||||
)}
|
||||
|
||||
{selectedTemplate === TEMPLATE_KEYS.NEW_API && (
|
||||
{selectedTemplate === TEMPLATE_TYPES.NEW_API && (
|
||||
<>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="usage-newapi-base-url">
|
||||
@@ -789,34 +858,39 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 提取器代码 */}
|
||||
<div className="space-y-4 glass rounded-xl border border-white/10 p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-base font-medium">
|
||||
{t("usageScript.extractorCode")}
|
||||
</Label>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{t("usageScript.extractorHint")}
|
||||
{/* 提取器代码 - Copilot 模板不需要 */}
|
||||
{selectedTemplate !== TEMPLATE_TYPES.GITHUB_COPILOT && (
|
||||
<div className="space-y-4 glass rounded-xl border border-white/10 p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-base font-medium">
|
||||
{t("usageScript.extractorCode")}
|
||||
</Label>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{t("usageScript.extractorHint")}
|
||||
</div>
|
||||
</div>
|
||||
<JsonEditor
|
||||
id="usage-code"
|
||||
value={script.code || ""}
|
||||
onChange={(value) => setScript({ ...script, code: value })}
|
||||
height={480}
|
||||
language="javascript"
|
||||
showMinimap={false}
|
||||
/>
|
||||
</div>
|
||||
<JsonEditor
|
||||
id="usage-code"
|
||||
value={script.code || ""}
|
||||
onChange={(value) => setScript({ ...script, code: value })}
|
||||
height={480}
|
||||
language="javascript"
|
||||
showMinimap={false}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 帮助信息 */}
|
||||
<div className="glass rounded-xl border border-white/10 p-6 text-sm text-foreground/90">
|
||||
<h4 className="font-medium mb-2">{t("usageScript.scriptHelp")}</h4>
|
||||
<div className="space-y-3 text-xs">
|
||||
<div>
|
||||
<strong>{t("usageScript.configFormat")}</strong>
|
||||
<pre className="mt-1 p-2 bg-black/20 text-foreground rounded border border-white/10 text-[10px] overflow-x-auto">
|
||||
{`({
|
||||
{/* 帮助信息 - Copilot 模板不需要 */}
|
||||
{selectedTemplate !== TEMPLATE_TYPES.GITHUB_COPILOT && (
|
||||
<div className="glass rounded-xl border border-white/10 p-6 text-sm text-foreground/90">
|
||||
<h4 className="font-medium mb-2">
|
||||
{t("usageScript.scriptHelp")}
|
||||
</h4>
|
||||
<div className="space-y-3 text-xs">
|
||||
<div>
|
||||
<strong>{t("usageScript.configFormat")}</strong>
|
||||
<pre className="mt-1 p-2 bg-black/20 text-foreground rounded border border-white/10 text-[10px] overflow-x-auto">
|
||||
{`({
|
||||
request: {
|
||||
url: "{{baseUrl}}/api/usage",
|
||||
method: "POST",
|
||||
@@ -833,38 +907,39 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
||||
};
|
||||
}
|
||||
})`}
|
||||
</pre>
|
||||
</div>
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<strong>{t("usageScript.extractorFormat")}</strong>
|
||||
<ul className="mt-1 space-y-0.5 ml-2">
|
||||
<li>{t("usageScript.fieldIsValid")}</li>
|
||||
<li>{t("usageScript.fieldInvalidMessage")}</li>
|
||||
<li>{t("usageScript.fieldRemaining")}</li>
|
||||
<li>{t("usageScript.fieldUnit")}</li>
|
||||
<li>{t("usageScript.fieldPlanName")}</li>
|
||||
<li>{t("usageScript.fieldTotal")}</li>
|
||||
<li>{t("usageScript.fieldUsed")}</li>
|
||||
<li>{t("usageScript.fieldExtra")}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<strong>{t("usageScript.extractorFormat")}</strong>
|
||||
<ul className="mt-1 space-y-0.5 ml-2">
|
||||
<li>{t("usageScript.fieldIsValid")}</li>
|
||||
<li>{t("usageScript.fieldInvalidMessage")}</li>
|
||||
<li>{t("usageScript.fieldRemaining")}</li>
|
||||
<li>{t("usageScript.fieldUnit")}</li>
|
||||
<li>{t("usageScript.fieldPlanName")}</li>
|
||||
<li>{t("usageScript.fieldTotal")}</li>
|
||||
<li>{t("usageScript.fieldUsed")}</li>
|
||||
<li>{t("usageScript.fieldExtra")}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="text-muted-foreground">
|
||||
<strong>{t("usageScript.tips")}</strong>
|
||||
<ul className="mt-1 space-y-0.5 ml-2">
|
||||
<li>
|
||||
{t("usageScript.tip1", {
|
||||
apiKey: "{{apiKey}}",
|
||||
baseUrl: "{{baseUrl}}",
|
||||
})}
|
||||
</li>
|
||||
<li>{t("usageScript.tip2")}</li>
|
||||
<li>{t("usageScript.tip3")}</li>
|
||||
</ul>
|
||||
<div className="text-muted-foreground">
|
||||
<strong>{t("usageScript.tips")}</strong>
|
||||
<ul className="mt-1 space-y-0.5 ml-2">
|
||||
<li>
|
||||
{t("usageScript.tip1", {
|
||||
apiKey: "{{apiKey}}",
|
||||
baseUrl: "{{baseUrl}}",
|
||||
})}
|
||||
</li>
|
||||
<li>{t("usageScript.tip2")}</li>
|
||||
<li>{t("usageScript.tip3")}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user