Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions web/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
--provider-antigravity: oklch(0.7123 0.2345 345.6789); /* #EC4899 粉色 */
--provider-kiro: oklch(0.689 0.1456 195.6789); /* #00BCD4 青色 */
--provider-codex: oklch(0.6789 0.12 145.6789); /* #10A37F OpenAI 绿色 */
--provider-claude: oklch(0.65 0.15 28); /* Anthropic 橙色/珊瑚色 */

/* Client 品牌色 (引用 Provider 颜色) */
--client-claude: var(--provider-anthropic);
Expand Down Expand Up @@ -366,6 +367,7 @@
--color-provider-antigravity: var(--provider-antigravity);
--color-provider-kiro: var(--provider-kiro);
--color-provider-codex: var(--provider-codex);
--color-provider-claude: var(--provider-claude);

/* Client 颜色映射 (Tailwind 可用) */
--color-client-claude: var(--client-claude);
Expand Down
3 changes: 2 additions & 1 deletion web/src/lib/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ export type ProviderType =
| 'custom'
| 'antigravity'
| 'kiro'
| 'codex';
| 'codex'
| 'claude';

/**
* Client 类型定义
Expand Down
33 changes: 33 additions & 0 deletions web/src/lib/transport/http-transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import type {
CodexTokenValidationResult,
CodexUsageResponse,
CodexQuotaData,
ClaudeTokenValidationResult,
AuthStatus,
AuthVerifyResult,
APIToken,
Expand Down Expand Up @@ -543,6 +544,38 @@ export class HttpTransport implements Transport {
return data;
}

// ===== Claude API =====

async validateClaudeToken(refreshToken: string): Promise<ClaudeTokenValidationResult> {
const { data } = await axios.post<ClaudeTokenValidationResult>('/api/claude/validate-token', {
refreshToken,
});
return data;
}

async startClaudeOAuth(): Promise<{ authURL: string; state: string }> {
const { data } = await axios.post<{ authURL: string; state: string }>('/api/claude/oauth/start');
return data;
}

async exchangeClaudeOAuthCallback(
code: string,
state: string,
): Promise<import('./types').ClaudeOAuthResult> {
const { data } = await axios.post<import('./types').ClaudeOAuthResult>(
'/api/claude/oauth/exchange',
{ code, state },
);
return data;
}

async refreshClaudeProviderInfo(providerId: number): Promise<ClaudeTokenValidationResult> {
const { data } = await axios.post<ClaudeTokenValidationResult>(
`/api/claude/provider/${providerId}/refresh`,
);
return data;
}

// ===== Cooldown API =====

async getCooldowns(): Promise<Cooldown[]> {
Expand Down
3 changes: 3 additions & 0 deletions web/src/lib/transport/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ export type {
ProviderConfigCodex,
CodexTokenValidationResult,
CodexOAuthResult,
ProviderConfigClaude,
ClaudeTokenValidationResult,
ClaudeOAuthResult,
CodexUsageWindow,
CodexRateLimitInfo,
CodexUsageResponse,
Expand Down
8 changes: 8 additions & 0 deletions web/src/lib/transport/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ import type {
CodexUsageResponse,
CodexQuotaData,
CodexOAuthResult,
ClaudeTokenValidationResult,
ClaudeOAuthResult,
AuthStatus,
AuthVerifyResult,
APIToken,
Expand Down Expand Up @@ -167,6 +169,12 @@ export interface Transport {
refreshCodexQuotas(): Promise<{ success: boolean; refreshed: boolean }>;
sortCodexRoutes(): Promise<{ success: boolean }>;

// ===== Claude API =====
validateClaudeToken(refreshToken: string): Promise<ClaudeTokenValidationResult>;
startClaudeOAuth(): Promise<{ authURL: string; state: string }>;
exchangeClaudeOAuthCallback(code: string, state: string): Promise<ClaudeOAuthResult>;
refreshClaudeProviderInfo(providerId: number): Promise<ClaudeTokenValidationResult>;

// ===== Cooldown API =====
getCooldowns(): Promise<Cooldown[]>;
clearCooldown(providerId: number): Promise<void>;
Expand Down
34 changes: 34 additions & 0 deletions web/src/lib/transport/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,22 @@ export interface ProviderConfigCodex {
useCLIProxyAPI?: boolean;
}

export interface ProviderConfigClaude {
email: string;
refreshToken: string;
accessToken?: string;
expiresAt?: string; // RFC3339 format
organizationId?: string;
modelMapping?: Record<string, string>;
}

export interface ProviderConfig {
disableErrorCooldown?: boolean;
custom?: ProviderConfigCustom;
antigravity?: ProviderConfigAntigravity;
kiro?: ProviderConfigKiro;
codex?: ProviderConfigCodex;
claude?: ProviderConfigClaude;
}

export interface Provider {
Expand Down Expand Up @@ -314,6 +324,7 @@ export type WSMessageType =
| 'log_message'
| 'antigravity_oauth_result'
| 'codex_oauth_result'
| 'claude_oauth_result'
| 'new_session_pending'
| 'session_pending_cancelled'
| 'cooldown_update'
Expand Down Expand Up @@ -557,6 +568,29 @@ export interface CodexBatchQuotaResult {
quotas: Record<number, CodexQuotaData>; // providerId -> quota
}

// ===== Claude 类型 =====

export interface ClaudeTokenValidationResult {
valid: boolean;
error?: string;
email?: string;
organizationId?: string;
accessToken?: string;
refreshToken?: string;
expiresAt?: string; // RFC3339 format
}

export interface ClaudeOAuthResult {
state: string;
success: boolean;
accessToken?: string;
refreshToken?: string;
expiresAt?: string; // RFC3339 format
email?: string;
organizationId?: string;
error?: string;
}

// ===== 回调类型 =====

export type EventCallback<T = unknown> = (data: T) => void;
Expand Down
60 changes: 60 additions & 0 deletions web/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,9 @@
"notFound": "Provider not found",
"antigravityType": "Antigravity Provider",
"codexType": "Codex Provider",
"claudeType": "Claude Provider",
"claudeAccountDetails": "Account Details",
"organizationId": "Organization ID",
"subscription": "Subscription",
"planType": "Plan Type",
"subscriptionPeriod": "Subscription Period",
Expand Down Expand Up @@ -400,6 +403,59 @@
"validateFirst": "Please validate the token first"
}
},
"claudeTokenImport": {
"title": "Add Claude Account",
"connectTitle": "Connect Claude Account",
"connectDescription": "Sign in with your Anthropic account or import a refresh token manually.",
"oauthLogin": "OAuth Login",
"tokenImport": "Token Import",
"anthropicOauth": "Anthropic OAuth",
"anthropicOauthDesc": "Sign in with your Anthropic account",
"signInWithAnthropic": "Sign in with Anthropic",
"popupClosed": "Popup window closed",
"waitingAuth": "Waiting for authorization...",
"pasteCallbackHint": "You can paste the callback URL below to continue",
"completeSignIn": "Complete the sign-in in the popup window",
"copyAuthUrlHint": "Copy the auth URL to open in your browser, then paste the callback URL.",
"popupNotWorkingHint": "Popup window not working? Copy the auth URL or paste the callback URL manually.",
"copyAuthUrl": "Copy Auth URL",
"pasteCallbackUrl": "Paste Callback URL",
"callbackNote": "After signing in, copy the URL from your browser's address bar (it will show an error page, that's expected).",
"exchanging": "Exchanging...",
"submitCallbackUrl": "Submit Callback URL",
"authorizationSuccessful": "Authorization Successful",
"signedInAs": "Signed in as",
"creatingProvider": "Creating Provider...",
"completeSetup": "Complete Setup",
"credentials": "Credentials",
"credentialsDesc": "Enter your Anthropic refresh token",
"emailAddress": "Email Address",
"optional": "Optional",
"emailPlaceholder": "e.g. user@example.com",
"displayOnlyNote": "Used for display purposes only. Auto-detected if valid token provided.",
"refreshToken": "Refresh Token",
"refreshTokenPlaceholder": "Paste your Anthropic refresh token here...",
"chars": "chars",
"validatingToken": "Validating Token...",
"revalidate": "Re-validate",
"validateToken": "Validate Token",
"tokenVerified": "Token Verified Successfully",
"readyToConnectAs": "Ready to connect as",
"error": "Error",
"errors": {
"oauthFailed": "OAuth authorization failed",
"invalidCallbackUrl": "Invalid callback URL. Please paste the complete URL from the browser address bar.",
"stateMismatch": "State mismatch. Please make sure you are using the callback URL from the current OAuth session.",
"exchangeFailed": "Failed to exchange callback",
"startOAuthFailed": "Failed to start OAuth flow",
"invalidRefreshToken": "Please enter a valid refresh token",
"tokenValidationFailed": "Token validation failed",
"validationFailed": "Validation failed",
"oauthResultMissing": "No valid OAuth result",
"createFailed": "Failed to create provider",
"validateFirst": "Please validate the token first"
}
},
"kiroTokenImport": {
"title": "Add Kiro Account",
"importTitle": "Import Kiro Social Token",
Expand Down Expand Up @@ -1060,6 +1116,10 @@
"name": "Kiro (Q Developer)",
"description": "AWS CodeWhisperer / Q Developer"
},
"claude": {
"name": "Claude",
"description": "Anthropic Claude with OAuth authentication"
},
"custom": {
"name": "Custom Provider",
"description": "Configure your own API endpoint"
Expand Down
60 changes: 60 additions & 0 deletions web/src/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,9 @@
"notFound": "提供商未找到",
"antigravityType": "Antigravity 提供商",
"codexType": "Codex 提供商",
"claudeType": "Claude 提供商",
"claudeAccountDetails": "账号详情",
"organizationId": "组织 ID",
"subscription": "订阅信息",
"planType": "订阅计划",
"subscriptionPeriod": "订阅周期",
Expand Down Expand Up @@ -400,6 +403,59 @@
"validateFirst": "请先验证 token"
}
},
"claudeTokenImport": {
"title": "添加 Claude 账号",
"connectTitle": "连接 Claude 账号",
"connectDescription": "使用 Anthropic 账号登录或手动导入 refresh token。",
"oauthLogin": "OAuth 登录",
"tokenImport": "Token 导入",
"anthropicOauth": "Anthropic OAuth",
"anthropicOauthDesc": "使用 Anthropic 账号登录",
"signInWithAnthropic": "使用 Anthropic 登录",
"popupClosed": "弹窗已关闭",
"waitingAuth": "等待授权...",
"pasteCallbackHint": "你可以在下方粘贴回调地址以继续",
"completeSignIn": "请在弹窗中完成登录",
"copyAuthUrlHint": "复制授权地址在浏览器打开,然后粘贴回调地址。",
"popupNotWorkingHint": "弹窗不可用?复制授权地址或手动粘贴回调地址。",
"copyAuthUrl": "复制授权地址",
"pasteCallbackUrl": "粘贴回调地址",
"callbackNote": "登录后请从浏览器地址栏复制回调 URL(会显示错误页,这是正常的)。",
"exchanging": "正在交换...",
"submitCallbackUrl": "提交回调地址",
"authorizationSuccessful": "授权成功",
"signedInAs": "已登录为",
"creatingProvider": "正在创建提供商...",
"completeSetup": "完成设置",
"credentials": "凭据",
"credentialsDesc": "输入 Anthropic refresh token",
"emailAddress": "邮箱地址",
"optional": "可选",
"emailPlaceholder": "例如 user@example.com",
"displayOnlyNote": "仅用于显示,如提供有效 token 将自动识别。",
"refreshToken": "Refresh Token",
"refreshTokenPlaceholder": "在此粘贴 Anthropic refresh token...",
"chars": "字符",
"validatingToken": "正在验证 Token...",
"revalidate": "重新验证",
"validateToken": "验证 Token",
"tokenVerified": "Token 验证成功",
"readyToConnectAs": "将以以下身份连接",
"error": "错误",
"errors": {
"oauthFailed": "OAuth 授权失败",
"invalidCallbackUrl": "回调地址无效,请粘贴完整的浏览器地址栏 URL。",
"stateMismatch": "State 不匹配,请确保使用当前 OAuth 会话的回调地址。",
"exchangeFailed": "交换回调失败",
"startOAuthFailed": "启动 OAuth 流程失败",
"invalidRefreshToken": "请输入有效的 refresh token",
"tokenValidationFailed": "Token 验证失败",
"validationFailed": "验证失败",
"oauthResultMissing": "未获取到有效的 OAuth 结果",
"createFailed": "创建失败",
"validateFirst": "请先验证 token"
}
},
"kiroTokenImport": {
"title": "添加 Kiro 账号",
"importTitle": "导入 Kiro Social Token",
Expand Down Expand Up @@ -1059,6 +1115,10 @@
"name": "Kiro (Q Developer)",
"description": "AWS CodeWhisperer / Q Developer"
},
"claude": {
"name": "Claude",
"description": "Anthropic Claude,支持 OAuth 认证"
},
"custom": {
"name": "自定义提供商",
"description": "配置您自己的 API 端点"
Expand Down
Loading