mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-06-16 21:03:58 +08:00
Compare commits
7 Commits
@@ -10,6 +10,7 @@ api.md
|
||||
usage.json
|
||||
CLAUDE.md
|
||||
AGENTS.md
|
||||
management-api*
|
||||
antigravity_usage.json
|
||||
codex_usage.json
|
||||
style.md
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Router-For.ME
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,23 +1,23 @@
|
||||
# CLI Proxy API Management Center
|
||||
|
||||
A single-file WebUI (React + TypeScript) for operating and troubleshooting the **CLI Proxy API** via its **Management API** (config, credentials, logs, and usage).
|
||||
A single-file Web UI (React + TypeScript) for operating and troubleshooting the **CLI Proxy API** via its **Management API** (config, credentials, logs, and usage).
|
||||
|
||||
[中文文档](README_CN.md)
|
||||
|
||||
**Main Project**: https://github.com/router-for-me/CLIProxyAPI
|
||||
**Example URL**: https://remote.router-for.me/
|
||||
**Minimum Required Version**: ≥ 6.3.0 (recommended ≥ 6.5.0)
|
||||
**Minimum Required Version**: ≥ 6.8.0 (recommended ≥ 6.8.15)
|
||||
|
||||
Since version 6.0.19, the WebUI ships with the main program; access it via `/management.html` on the API port once the service is running.
|
||||
Since version 6.0.19, the Web UI ships with the main program; access it via `/management.html` on the API port once the service is running.
|
||||
|
||||
## What this is (and isn’t)
|
||||
|
||||
- This repository is the WebUI only. It talks to the CLI Proxy API **Management API** (`/v0/management`) to read/update config, upload credentials, view logs, and inspect usage.
|
||||
- This repository is the Web UI only. It talks to the CLI Proxy API **Management API** (`/v0/management`) to read/update config, upload credentials, view logs, and inspect usage.
|
||||
- It is **not** a proxy and does not forward traffic.
|
||||
|
||||
## Quick start
|
||||
|
||||
### Option A: Use the WebUI bundled in CLIProxyAPI (recommended)
|
||||
### Option A: Use the Web UI bundled in CLI Proxy API (recommended)
|
||||
|
||||
1. Start your CLI Proxy API service.
|
||||
2. Open: `http://<host>:<api_port>/management.html`
|
||||
@@ -32,7 +32,7 @@ npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Open `http://localhost:5173`, then connect to your CLI Proxy API instance.
|
||||
Open `http://localhost:5173`, then connect to your CLI Proxy API backend instance.
|
||||
|
||||
### Option C: Build a single HTML file
|
||||
|
||||
@@ -42,7 +42,7 @@ npm run build
|
||||
```
|
||||
|
||||
- Output: `dist/index.html` (all assets are inlined).
|
||||
- For CLIProxyAPI bundling, the release workflow renames it to `management.html`.
|
||||
- For CLI Proxy API bundling, the release workflow renames it to `management.html`.
|
||||
- To preview locally: `npm run preview`
|
||||
|
||||
Tip: opening `dist/index.html` via `file://` may be blocked by browser CORS; serving it (preview/static server) is more reliable.
|
||||
@@ -74,19 +74,48 @@ See `api.md` for the full authentication rules, server-side limits, and edge cas
|
||||
## What you can manage (mapped to the UI pages)
|
||||
|
||||
- **Dashboard**: connection status, server version/build date, quick counts, model availability snapshot.
|
||||
- **Basic Settings**: debug, proxy URL, request retry, quota fallback (switch project/preview models), usage statistics, request logging, file logging, WebSocket auth.
|
||||
- **Basic Settings**: debug, proxy URL, request retry, quota fallback (switch project or preview models when limits reached), usage statistics, request logging, file logging, WebSocket auth.
|
||||
- **API Keys**: manage proxy `api-keys` (add/edit/delete).
|
||||
- **AI Providers**:
|
||||
- Gemini/Codex/Claude key entries (base URL, headers, proxy, model aliases, excluded models, prefix).
|
||||
- OpenAI-compatible providers (multiple API keys, custom headers, model alias import via `/v1/models`, optional browser-side “chat/completions” test).
|
||||
- Gemini/Codex/Claude/Vertex key entries (base URL, headers, proxy, model aliases, excluded models, prefix).
|
||||
- OpenAI-compatible providers (multiple API keys, custom headers, model alias import via `/v1/models`, optional browser-side "chat/completions" test).
|
||||
- Ampcode integration (upstream URL/key, force mappings, model mapping table).
|
||||
- **Auth Files**: upload/download/delete JSON credentials, filter/search/pagination, runtime-only indicators, view supported models per credential (when the server supports it), manage OAuth excluded models (supports `*` wildcards).
|
||||
- **Auth Files**: upload/download/delete JSON credentials, filter/search/pagination, runtime-only indicators, view supported models per credential (when the server supports it), manage OAuth excluded models (supports `*` wildcards), configure OAuth model alias mappings.
|
||||
- **OAuth**: start OAuth/device flows for supported providers, poll status, optionally submit callback `redirect_url`; includes iFlow cookie import.
|
||||
- **Quota Management**: manage quota limits and usage for Claude, Antigravity, Codex, Gemini CLI, and other providers.
|
||||
- **Usage**: requests/tokens charts (hour/day), per-API & per-model breakdown, cached/reasoning token breakdown, RPM/TPM window, optional cost estimation with locally-saved model pricing.
|
||||
- **Config**: edit `/config.yaml` in-browser with YAML highlighting + search, then save/reload.
|
||||
- **Logs**: tail logs with incremental polling, auto-refresh, search, hide management traffic, clear logs; download request error log files.
|
||||
- **System**: quick links + fetch `/v1/models` (grouped view). Requires at least one proxy API key to query models.
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- React 19 + TypeScript 5.9
|
||||
- Vite 7 (single-file build)
|
||||
- Zustand (state management)
|
||||
- Axios (HTTP client)
|
||||
- react-router-dom v7 (HashRouter)
|
||||
- Chart.js (data visualization)
|
||||
- CodeMirror 6 (YAML editor)
|
||||
- SCSS Modules (styling)
|
||||
- i18next (internationalization)
|
||||
|
||||
## Internationalization
|
||||
|
||||
Currently supports three languages:
|
||||
|
||||
- English (en)
|
||||
- Simplified Chinese (zh-CN)
|
||||
- Russian (ru)
|
||||
|
||||
The UI language is automatically detected from browser settings and can be manually switched at the bottom of the page.
|
||||
|
||||
## Browser Compatibility
|
||||
|
||||
- Build target: `ES2015`
|
||||
- Supports modern browsers (Chrome, Firefox, Safari, Edge)
|
||||
- Responsive layout for mobile and tablet access
|
||||
|
||||
## Build & release notes
|
||||
|
||||
- Vite produces a **single HTML** output (`dist/index.html`) with all assets inlined (via `vite-plugin-singlefile`).
|
||||
|
||||
+40
-12
@@ -1,14 +1,14 @@
|
||||
# CLI Proxy API 管理中心
|
||||
|
||||
用于管理与排障 **CLI Proxy API** 的单文件 WebUI(React + TypeScript),通过 **Management API** 完成配置、凭据、日志与统计等运维工作。
|
||||
用于管理与故障排查 **CLI Proxy API** 的单文件 Web UI(React + TypeScript),通过 **Management API** 完成配置、凭据、日志与统计等管理操作。
|
||||
|
||||
[English](README.md)
|
||||
|
||||
**主项目**: https://github.com/router-for-me/CLIProxyAPI
|
||||
**示例地址**: https://remote.router-for.me/
|
||||
**最低版本要求**: ≥ 6.3.0(推荐 ≥ 6.5.0)
|
||||
**最低版本要求**: ≥ 6.8.0(推荐 ≥ 6.8.15)
|
||||
|
||||
Since version 6.0.19, the WebUI ships with the main program; access it via `/management.html` on the API port once the service is running.
|
||||
从6.0.19版本开始,Web UI 随主程序一起提供;服务运行后,通过 API 端口上的"/management.html"访问它。
|
||||
|
||||
## 这是什么(以及不是什么)
|
||||
|
||||
@@ -17,7 +17,7 @@ Since version 6.0.19, the WebUI ships with the main program; access it via `/man
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 方式 A:使用 CLIProxyAPI 自带的 WebUI(推荐)
|
||||
### 方式 A:使用 CLI Proxy API 自带的 Web UI(推荐)
|
||||
|
||||
1. 启动 CLI Proxy API 服务。
|
||||
2. 打开:`http://<host>:<api_port>/management.html`
|
||||
@@ -32,7 +32,7 @@ npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
打开 `http://localhost:5173`,然后连接到你的 CLI Proxy API 实例。
|
||||
打开 `http://localhost:5173`,然后连接到你的 CLI Proxy API 后端实例。
|
||||
|
||||
### 方式 C:构建单文件 HTML
|
||||
|
||||
@@ -42,7 +42,7 @@ npm run build
|
||||
```
|
||||
|
||||
- 构建产物:`dist/index.html`(资源已全部内联)。
|
||||
- 在 CLIProxyAPI 的发布流程里会重命名为 `management.html`。
|
||||
- 在 CLI Proxy API 的发布流程里会重命名为 `management.html`。
|
||||
- 本地预览:`npm run preview`
|
||||
|
||||
提示:直接用 `file://` 打开 `dist/index.html` 可能遇到浏览器 CORS 限制;更稳妥的方式是用预览/静态服务器打开。
|
||||
@@ -51,7 +51,7 @@ npm run build
|
||||
|
||||
### API 地址怎么填
|
||||
|
||||
以下格式均可,WebUI 会自动归一化:
|
||||
以下格式均可,Web UI 会自动归一化:
|
||||
|
||||
- `localhost:8317`
|
||||
- `http://192.168.1.10:8317`
|
||||
@@ -64,29 +64,57 @@ npm run build
|
||||
|
||||
- `Authorization: Bearer <MANAGEMENT_KEY>`(默认)
|
||||
|
||||
这与 WebUI 中“API Keys”页面管理的 `api-keys` 不同:后者是代理对外接口(如 OpenAI 兼容接口)给客户端使用的鉴权 key。
|
||||
这与 Web UI 中"API Keys"页面管理的 `api-keys` 不同:后者是代理对外接口(如 OpenAI 兼容接口)给客户端使用的鉴权 key。
|
||||
|
||||
### 远程管理
|
||||
|
||||
当你从非 localhost 的浏览器访问时,服务端通常需要开启远程管理(例如 `allow-remote-management: true`)。
|
||||
完整鉴权规则、限制与边界情况请查看 `api.md`。
|
||||
|
||||
## 功能一览(按页面对应)
|
||||
|
||||
- **仪表盘**:连接状态、服务版本/构建时间、关键数量概览、可用模型概览。
|
||||
- **基础设置**:调试开关、代理 URL、请求重试、配额回退(切项目/切预览模型)、使用统计、请求日志、文件日志、WebSocket 鉴权。
|
||||
- **基础设置**:调试开关、代理 URL、请求重试、配额回退(达到上限时切换项目或预览模型)、使用统计、请求日志、文件日志、WebSocket 鉴权。
|
||||
- **API Keys**:管理代理 `api-keys`(增/改/删)。
|
||||
- **AI 提供商**:
|
||||
- Gemini/Codex/Claude 配置(Base URL、Headers、代理、模型别名、排除模型、Prefix)。
|
||||
- Gemini/Codex/Claude/Vertex 配置(Base URL、Headers、代理、模型别名、排除模型、Prefix)。
|
||||
- OpenAI 兼容提供商(多 Key、Header、自助从 `/v1/models` 拉取并导入模型别名、可选浏览器侧 `chat/completions` 测试)。
|
||||
- Ampcode 集成(上游地址/密钥、强制映射、模型映射表)。
|
||||
- **认证文件**:上传/下载/删除 JSON 凭据,筛选/搜索/分页,标记 runtime-only;查看单个凭据可用模型(依赖后端支持);管理 OAuth 排除模型(支持 `*` 通配符)。
|
||||
- **认证文件**:上传/下载/删除 JSON 凭据,筛选/搜索/分页,标记 runtime-only;查看单个凭据可用模型(依赖后端支持);管理 OAuth 排除模型(支持 `*` 通配符);配置 OAuth 模型别名映射。
|
||||
- **OAuth**:对支持的提供商发起 OAuth/设备码流程,轮询状态;可选提交回调 `redirect_url`;包含 iFlow Cookie 导入。
|
||||
- **配额管理**:管理 Claude、Antigravity、Codex、Gemini CLI 等提供商的配额上限与使用情况。
|
||||
- **使用统计**:按小时/天图表、按 API 与按模型统计、缓存/推理 Token 拆分、RPM/TPM 时间窗、可选本地保存的模型价格用于费用估算。
|
||||
- **配置文件**:浏览器内编辑 `/config.yaml`(YAML 高亮 + 搜索),保存/重载。
|
||||
- **日志**:增量拉取日志、自动刷新、搜索、隐藏管理端流量、清空日志;下载请求错误日志文件。
|
||||
- **系统信息**:快捷链接 + 拉取 `/v1/models` 并分组展示(需要至少一个代理 API Key 才能查询模型)。
|
||||
|
||||
## 技术栈
|
||||
|
||||
- React 19 + TypeScript 5.9
|
||||
- Vite 7(单文件构建)
|
||||
- Zustand(状态管理)
|
||||
- Axios(HTTP 客户端)
|
||||
- react-router-dom v7(HashRouter)
|
||||
- Chart.js(数据可视化)
|
||||
- CodeMirror 6(YAML 编辑器)
|
||||
- SCSS Modules(样式)
|
||||
- i18next(国际化)
|
||||
|
||||
## 多语言支持
|
||||
|
||||
目前支持三种语言:
|
||||
|
||||
- 英文 (en)
|
||||
- 简体中文 (zh-CN)
|
||||
- 俄文 (ru)
|
||||
|
||||
界面语言会根据浏览器设置自动切换,也可在页面底部手动切换。
|
||||
|
||||
## 浏览器兼容性
|
||||
|
||||
- 构建目标:`ES2015`
|
||||
- 支持 Chrome、Firefox、Safari、Edge 等现代浏览器
|
||||
- 支持移动端响应式布局,可通过手机/平板访问
|
||||
|
||||
## 构建与发布说明
|
||||
|
||||
- 使用 Vite 输出 **单文件 HTML**(`dist/index.html`),资源全部内联(`vite-plugin-singlefile`)。
|
||||
|
||||
@@ -125,6 +125,12 @@ export function GeminiSection({
|
||||
<span className={styles.fieldValue}>{item.baseUrl}</span>
|
||||
</div>
|
||||
)}
|
||||
{item.proxyUrl && (
|
||||
<div className={styles.fieldRow}>
|
||||
<span className={styles.fieldLabel}>{t('common.proxy_url')}:</span>
|
||||
<span className={styles.fieldValue}>{item.proxyUrl}</span>
|
||||
</div>
|
||||
)}
|
||||
{headerEntries.length > 0 && (
|
||||
<div className={styles.headerBadgeList}>
|
||||
{headerEntries.map(([key, value]) => (
|
||||
|
||||
@@ -213,11 +213,14 @@ const buildCodexQuotaWindows = (payload: CodexUsagePayload, t: TFunction): Codex
|
||||
|
||||
const rateLimit = payload.rate_limit ?? payload.rateLimit ?? undefined;
|
||||
const codeReviewLimit = payload.code_review_rate_limit ?? payload.codeReviewRateLimit ?? undefined;
|
||||
const additionalRateLimits = payload.additional_rate_limits ?? payload.additionalRateLimits ?? [];
|
||||
const windows: CodexQuotaWindow[] = [];
|
||||
|
||||
const addWindow = (
|
||||
id: string,
|
||||
labelKey: string,
|
||||
label: string,
|
||||
labelKey: string | undefined,
|
||||
labelParams: Record<string, string | number> | undefined,
|
||||
window?: CodexUsageWindow | null,
|
||||
limitReached?: boolean,
|
||||
allowed?: boolean
|
||||
@@ -229,8 +232,9 @@ const buildCodexQuotaWindows = (payload: CodexUsagePayload, t: TFunction): Codex
|
||||
const usedPercent = usedPercentRaw ?? (isLimitReached && resetLabel !== '-' ? 100 : null);
|
||||
windows.push({
|
||||
id,
|
||||
label: t(labelKey),
|
||||
label,
|
||||
labelKey,
|
||||
labelParams,
|
||||
usedPercent,
|
||||
resetLabel,
|
||||
});
|
||||
@@ -245,12 +249,13 @@ const buildCodexQuotaWindows = (payload: CodexUsagePayload, t: TFunction): Codex
|
||||
const rawAllowed = rateLimit?.allowed;
|
||||
|
||||
const pickClassifiedWindows = (
|
||||
limitInfo?: CodexRateLimitInfo | null
|
||||
limitInfo?: CodexRateLimitInfo | null,
|
||||
options?: { allowOrderFallback?: boolean }
|
||||
): { fiveHourWindow: CodexUsageWindow | null; weeklyWindow: CodexUsageWindow | null } => {
|
||||
const rawWindows = [
|
||||
limitInfo?.primary_window ?? limitInfo?.primaryWindow ?? null,
|
||||
limitInfo?.secondary_window ?? limitInfo?.secondaryWindow ?? null,
|
||||
];
|
||||
const allowOrderFallback = options?.allowOrderFallback ?? true;
|
||||
const primaryWindow = limitInfo?.primary_window ?? limitInfo?.primaryWindow ?? null;
|
||||
const secondaryWindow = limitInfo?.secondary_window ?? limitInfo?.secondaryWindow ?? null;
|
||||
const rawWindows = [primaryWindow, secondaryWindow];
|
||||
|
||||
let fiveHourWindow: CodexUsageWindow | null = null;
|
||||
let weeklyWindow: CodexUsageWindow | null = null;
|
||||
@@ -265,20 +270,34 @@ const buildCodexQuotaWindows = (payload: CodexUsagePayload, t: TFunction): Codex
|
||||
}
|
||||
}
|
||||
|
||||
// For legacy payloads without window duration, fallback to primary/secondary ordering.
|
||||
if (allowOrderFallback) {
|
||||
if (!fiveHourWindow) {
|
||||
fiveHourWindow = primaryWindow && primaryWindow !== weeklyWindow ? primaryWindow : null;
|
||||
}
|
||||
if (!weeklyWindow) {
|
||||
weeklyWindow = secondaryWindow && secondaryWindow !== fiveHourWindow ? secondaryWindow : null;
|
||||
}
|
||||
}
|
||||
|
||||
return { fiveHourWindow, weeklyWindow };
|
||||
};
|
||||
|
||||
const rateWindows = pickClassifiedWindows(rateLimit);
|
||||
addWindow(
|
||||
WINDOW_META.codeFiveHour.id,
|
||||
t(WINDOW_META.codeFiveHour.labelKey),
|
||||
WINDOW_META.codeFiveHour.labelKey,
|
||||
undefined,
|
||||
rateWindows.fiveHourWindow,
|
||||
rawLimitReached,
|
||||
rawAllowed
|
||||
);
|
||||
addWindow(
|
||||
WINDOW_META.codeWeekly.id,
|
||||
t(WINDOW_META.codeWeekly.labelKey),
|
||||
WINDOW_META.codeWeekly.labelKey,
|
||||
undefined,
|
||||
rateWindows.weeklyWindow,
|
||||
rawLimitReached,
|
||||
rawAllowed
|
||||
@@ -289,19 +308,67 @@ const buildCodexQuotaWindows = (payload: CodexUsagePayload, t: TFunction): Codex
|
||||
const codeReviewAllowed = codeReviewLimit?.allowed;
|
||||
addWindow(
|
||||
WINDOW_META.codeReviewFiveHour.id,
|
||||
t(WINDOW_META.codeReviewFiveHour.labelKey),
|
||||
WINDOW_META.codeReviewFiveHour.labelKey,
|
||||
undefined,
|
||||
codeReviewWindows.fiveHourWindow,
|
||||
codeReviewLimitReached,
|
||||
codeReviewAllowed
|
||||
);
|
||||
addWindow(
|
||||
WINDOW_META.codeReviewWeekly.id,
|
||||
t(WINDOW_META.codeReviewWeekly.labelKey),
|
||||
WINDOW_META.codeReviewWeekly.labelKey,
|
||||
undefined,
|
||||
codeReviewWindows.weeklyWindow,
|
||||
codeReviewLimitReached,
|
||||
codeReviewAllowed
|
||||
);
|
||||
|
||||
const normalizeWindowId = (raw: string) =>
|
||||
raw
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/^-+|-+$/g, '');
|
||||
|
||||
if (Array.isArray(additionalRateLimits)) {
|
||||
additionalRateLimits.forEach((limitItem, index) => {
|
||||
const rateInfo = limitItem?.rate_limit ?? limitItem?.rateLimit ?? null;
|
||||
if (!rateInfo) return;
|
||||
|
||||
const limitName =
|
||||
normalizeStringValue(limitItem?.limit_name ?? limitItem?.limitName) ??
|
||||
normalizeStringValue(limitItem?.metered_feature ?? limitItem?.meteredFeature) ??
|
||||
`additional-${index + 1}`;
|
||||
|
||||
const idPrefix = normalizeWindowId(limitName) || `additional-${index + 1}`;
|
||||
const additionalPrimaryWindow = rateInfo.primary_window ?? rateInfo.primaryWindow ?? null;
|
||||
const additionalSecondaryWindow = rateInfo.secondary_window ?? rateInfo.secondaryWindow ?? null;
|
||||
const additionalLimitReached = rateInfo.limit_reached ?? rateInfo.limitReached;
|
||||
const additionalAllowed = rateInfo.allowed;
|
||||
|
||||
addWindow(
|
||||
`${idPrefix}-five-hour-${index}`,
|
||||
t('codex_quota.additional_primary_window', { name: limitName }),
|
||||
'codex_quota.additional_primary_window',
|
||||
{ name: limitName },
|
||||
additionalPrimaryWindow,
|
||||
additionalLimitReached,
|
||||
additionalAllowed
|
||||
);
|
||||
addWindow(
|
||||
`${idPrefix}-weekly-${index}`,
|
||||
t('codex_quota.additional_secondary_window', { name: limitName }),
|
||||
'codex_quota.additional_secondary_window',
|
||||
{ name: limitName },
|
||||
additionalSecondaryWindow,
|
||||
additionalLimitReached,
|
||||
additionalAllowed
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return windows;
|
||||
};
|
||||
|
||||
@@ -493,7 +560,9 @@ const renderCodexItems = (
|
||||
const clampedUsed = used === null ? null : Math.max(0, Math.min(100, used));
|
||||
const remaining = clampedUsed === null ? null : Math.max(0, Math.min(100, 100 - clampedUsed));
|
||||
const percentLabel = remaining === null ? '--' : `${Math.round(remaining)}%`;
|
||||
const windowLabel = window.labelKey ? t(window.labelKey) : window.label;
|
||||
const windowLabel = window.labelKey
|
||||
? t(window.labelKey, window.labelParams as Record<string, string | number>)
|
||||
: window.label;
|
||||
|
||||
return h(
|
||||
'div',
|
||||
|
||||
@@ -194,6 +194,8 @@
|
||||
"gemini_keys_add_btn": "Add Key",
|
||||
"gemini_base_url_label": "Base URL (Optional):",
|
||||
"gemini_base_url_placeholder": "e.g.: https://generativelanguage.googleapis.com",
|
||||
"gemini_add_modal_proxy_label": "Proxy URL (Optional):",
|
||||
"gemini_add_modal_proxy_placeholder": "e.g.: socks5://proxy.example.com:1080",
|
||||
"gemini_edit_modal_title": "Edit Gemini API Key",
|
||||
"gemini_edit_modal_key_label": "API Key:",
|
||||
"gemini_delete_confirm": "Are you sure you want to delete this Gemini key?",
|
||||
@@ -490,6 +492,8 @@
|
||||
"secondary_window": "Weekly limit",
|
||||
"code_review_primary_window": "Code review 5-hour limit",
|
||||
"code_review_secondary_window": "Code review weekly limit",
|
||||
"additional_primary_window": "{{name}} 5-hour limit",
|
||||
"additional_secondary_window": "{{name}} weekly limit",
|
||||
"plan_label": "Plan",
|
||||
"plan_plus": "Plus",
|
||||
"plan_team": "Team",
|
||||
|
||||
@@ -194,6 +194,8 @@
|
||||
"gemini_keys_add_btn": "Добавить ключ",
|
||||
"gemini_base_url_label": "Базовый URL (необязательно):",
|
||||
"gemini_base_url_placeholder": "например: https://generativelanguage.googleapis.com",
|
||||
"gemini_add_modal_proxy_label": "URL прокси (необязательно):",
|
||||
"gemini_add_modal_proxy_placeholder": "например: socks5://proxy.example.com:1080",
|
||||
"gemini_edit_modal_title": "Редактирование API-ключа Gemini",
|
||||
"gemini_edit_modal_key_label": "API-ключ:",
|
||||
"gemini_delete_confirm": "Удалить этот ключ Gemini?",
|
||||
@@ -493,6 +495,8 @@
|
||||
"secondary_window": "Недельный лимит",
|
||||
"code_review_primary_window": "Лимит code review на 5 часов",
|
||||
"code_review_secondary_window": "Недельный лимит code review",
|
||||
"additional_primary_window": "{{name}}: лимит на 5 часов",
|
||||
"additional_secondary_window": "{{name}}: недельный лимит",
|
||||
"plan_label": "Тариф",
|
||||
"plan_plus": "Plus",
|
||||
"plan_team": "Team",
|
||||
|
||||
@@ -194,6 +194,8 @@
|
||||
"gemini_keys_add_btn": "添加密钥",
|
||||
"gemini_base_url_label": "Base URL (可选)",
|
||||
"gemini_base_url_placeholder": "例如: https://generativelanguage.googleapis.com",
|
||||
"gemini_add_modal_proxy_label": "代理 URL (可选):",
|
||||
"gemini_add_modal_proxy_placeholder": "例如: socks5://proxy.example.com:1080",
|
||||
"gemini_edit_modal_title": "编辑Gemini API密钥",
|
||||
"gemini_edit_modal_key_label": "API密钥:",
|
||||
"gemini_delete_confirm": "确定要删除这个Gemini密钥吗?",
|
||||
@@ -490,6 +492,8 @@
|
||||
"secondary_window": "周限额",
|
||||
"code_review_primary_window": "代码审查 5 小时限额",
|
||||
"code_review_secondary_window": "代码审查周限额",
|
||||
"additional_primary_window": "{{name}} 5 小时限额",
|
||||
"additional_secondary_window": "{{name}} 周限额",
|
||||
"plan_label": "套餐",
|
||||
"plan_plus": "Plus",
|
||||
"plan_team": "Team",
|
||||
|
||||
@@ -21,6 +21,7 @@ const buildEmptyForm = (): GeminiFormState => ({
|
||||
apiKey: '',
|
||||
prefix: '',
|
||||
baseUrl: '',
|
||||
proxyUrl: '',
|
||||
headers: [],
|
||||
excludedModels: [],
|
||||
excludedText: '',
|
||||
@@ -138,6 +139,7 @@ export function AiProvidersGeminiEditPage() {
|
||||
apiKey: form.apiKey.trim(),
|
||||
prefix: form.prefix?.trim() || undefined,
|
||||
baseUrl: form.baseUrl?.trim() || undefined,
|
||||
proxyUrl: form.proxyUrl?.trim() || undefined,
|
||||
headers: buildHeaderObject(form.headers),
|
||||
excludedModels: parseExcludedModels(form.excludedText),
|
||||
};
|
||||
@@ -218,6 +220,13 @@ export function AiProvidersGeminiEditPage() {
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, baseUrl: e.target.value }))}
|
||||
disabled={disableControls || saving}
|
||||
/>
|
||||
<Input
|
||||
label={t('ai_providers.gemini_add_modal_proxy_label')}
|
||||
placeholder={t('ai_providers.gemini_add_modal_proxy_placeholder')}
|
||||
value={form.proxyUrl ?? ''}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, proxyUrl: e.target.value }))}
|
||||
disabled={disableControls || saving}
|
||||
/>
|
||||
<HeaderInputList
|
||||
entries={form.headers}
|
||||
onChange={(entries) => setForm((prev) => ({ ...prev, headers: entries }))}
|
||||
|
||||
@@ -99,6 +99,7 @@ const serializeGeminiKey = (config: GeminiKeyConfig) => {
|
||||
const payload: Record<string, unknown> = { 'api-key': config.apiKey };
|
||||
if (config.prefix?.trim()) payload.prefix = config.prefix.trim();
|
||||
if (config.baseUrl) payload['base-url'] = config.baseUrl;
|
||||
if (config.proxyUrl) payload['proxy-url'] = config.proxyUrl;
|
||||
const headers = serializeHeaders(config.headers);
|
||||
if (headers) payload.headers = headers;
|
||||
if (config.excludedModels && config.excludedModels.length) {
|
||||
|
||||
@@ -153,6 +153,8 @@ const normalizeGeminiKeyConfig = (item: unknown): GeminiKeyConfig | null => {
|
||||
if (prefix) config.prefix = prefix;
|
||||
const baseUrl = record ? record['base-url'] ?? record.baseUrl ?? record['base_url'] : undefined;
|
||||
if (baseUrl) config.baseUrl = String(baseUrl);
|
||||
const proxyUrl = record ? record['proxy-url'] ?? record.proxyUrl ?? record['proxy_url'] : undefined;
|
||||
if (proxyUrl) config.proxyUrl = String(proxyUrl);
|
||||
const headers = normalizeHeaders(record?.headers);
|
||||
if (headers) config.headers = headers;
|
||||
const excludedModels = normalizeExcludedModels(record?.['excluded-models'] ?? record?.excludedModels);
|
||||
|
||||
@@ -20,6 +20,7 @@ export interface GeminiKeyConfig {
|
||||
apiKey: string;
|
||||
prefix?: string;
|
||||
baseUrl?: string;
|
||||
proxyUrl?: string;
|
||||
headers?: Record<string, string>;
|
||||
excludedModels?: string[];
|
||||
}
|
||||
|
||||
@@ -88,6 +88,15 @@ export interface CodexRateLimitInfo {
|
||||
secondaryWindow?: CodexUsageWindow | null;
|
||||
}
|
||||
|
||||
export interface CodexAdditionalRateLimit {
|
||||
limit_name?: string;
|
||||
limitName?: string;
|
||||
metered_feature?: string;
|
||||
meteredFeature?: string;
|
||||
rate_limit?: CodexRateLimitInfo | null;
|
||||
rateLimit?: CodexRateLimitInfo | null;
|
||||
}
|
||||
|
||||
export interface CodexUsagePayload {
|
||||
plan_type?: string;
|
||||
planType?: string;
|
||||
@@ -95,6 +104,8 @@ export interface CodexUsagePayload {
|
||||
rateLimit?: CodexRateLimitInfo | null;
|
||||
code_review_rate_limit?: CodexRateLimitInfo | null;
|
||||
codeReviewRateLimit?: CodexRateLimitInfo | null;
|
||||
additional_rate_limits?: CodexAdditionalRateLimit[] | null;
|
||||
additionalRateLimits?: CodexAdditionalRateLimit[] | null;
|
||||
}
|
||||
|
||||
// Claude API payload types
|
||||
@@ -174,6 +185,7 @@ export interface CodexQuotaWindow {
|
||||
id: string;
|
||||
label: string;
|
||||
labelKey?: string;
|
||||
labelParams?: Record<string, string | number>;
|
||||
usedPercent: number | null;
|
||||
resetLabel: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user