kines9661 commited on
Commit
c1a43bf
·
verified ·
1 Parent(s): c83dcad

Upload 11 files

Browse files
DEPLOYMENT.md ADDED
@@ -0,0 +1,312 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # CLI Proxy API Plus - Hugging Face Spaces 部署指南
2
+
3
+ 本文檔提供完整的 Hugging Face Spaces 部署步驟說明。
4
+
5
+ ## 📋 目錄
6
+
7
+ 1. [前置需求](#前置需求)
8
+ 2. [快速部署](#快速部署)
9
+ 3. [配置 Secrets](#配置-secrets)
10
+ 4. [使用說明](#使用說明)
11
+ 5. [常見問題](#常見問題)
12
+
13
+ ---
14
+
15
+ ## 前置需求
16
+
17
+ ### 必需項目
18
+
19
+ 1. **Hugging Face 帳號**
20
+ - 註冊地址: https://huggingface.co/join
21
+ - 建議啟用兩步驗證以保護帳號安全
22
+
23
+ 2. **AI 服務提供商帳號**(至少需要一個)
24
+ - [Anthropic Claude](https://console.anthropic.com/)
25
+ - [Google AI Studio](https://aistudio.google.com/)
26
+ - [GitHub Copilot](https://github.com/features/copilot)
27
+ - [AWS Kiro](https://aws.amazon.com/)
28
+
29
+ ### 可選項目
30
+
31
+ - 自定義域名(可在 HF Spaces 設置中配置)
32
+
33
+ ---
34
+
35
+ ## 快速部署
36
+
37
+ ### 方法一:從模板創建(推薦)
38
+
39
+ 1. **Fork 或 Clone 本項目到您的 HF 帳號**
40
+
41
+ ```bash
42
+ # 使用 Git
43
+ git clone https://huggingface.co/spaces/YOUR_USERNAME/cli-proxy-api-plus
44
+ ```
45
+
46
+ 2. **創建新的 Space**
47
+ - 前往 https://huggingface.co/new-space
48
+ - 選擇 **Docker** 作為 SDK
49
+ - 設置 Space 名稱(例如:`cli-proxy-api-plus`)
50
+ - 選擇可見性(Public 或 Private)
51
+
52
+ 3. **上傳文件**
53
+
54
+ 將 `hf-spaces/` 目錄下的所有文件上傳到 Space 根目錄:
55
+ - `Dockerfile`
56
+ - `supervisord.conf`
57
+ - `requirements.txt`
58
+ - `entrypoint.sh`
59
+ - `README.md`
60
+ - `streamlit_app/` 目錄
61
+
62
+ ### 方法二:使用 Git 推送
63
+
64
+ 1. **創建 Space 後,克隆到本地**
65
+
66
+ ```bash
67
+ git clone https://huggingface.co/spaces/YOUR_USERNAME/YOUR_SPACE_NAME
68
+ cd YOUR_SPACE_NAME
69
+ ```
70
+
71
+ 2. **複製文件**
72
+
73
+ ```bash
74
+ # 從本項目複製 hf-spaces 內容
75
+ cp -r /path/to/CLIProxyAPIPlus-main/hf-spaces/* .
76
+ ```
77
+
78
+ 3. **提交並推送**
79
+
80
+ ```bash
81
+ git add .
82
+ git commit -m "Initial deployment"
83
+ git push
84
+ ```
85
+
86
+ 4. **等待構建完成**
87
+
88
+ HF Spaces 會自動開始構建 Docker 映像,通常需要 5-10 分鐘。
89
+
90
+ ---
91
+
92
+ ## 配置 Secrets
93
+
94
+ ### 為什麼需要 Secrets?
95
+
96
+ Secrets 用於安全地存儲敏感信息,如 API 密鑰、OAuth 憑證等。這些信息不會出現在代碼中,確保安全性。
97
+
98
+ ### 如何設置 Secrets
99
+
100
+ 1. **進入 Space 設置頁面**
101
+ - 打開您的 Space 頁面
102
+ - 點擊右上角 **Settings** 標籤
103
+
104
+ 2. **找到 Variables and Secrets 區塊**
105
+ - 滾動到 **Variables and secrets** 部分
106
+ - 點擊 **New secret**
107
+
108
+ 3. **添加以下 Secrets**
109
+
110
+ #### 基礎配置
111
+
112
+ | Secret 名稱 | 說明 | 範例值 |
113
+ |------------|------|--------|
114
+ | `SERVER_PORT` | Go API 服務器端口 | `8317` |
115
+ | `LOG_LEVEL` | 日誌級別 | `info` |
116
+
117
+ #### Claude 配置
118
+
119
+ | Secret 名稱 | 說明 | 取得方式 |
120
+ |------------|------|----------|
121
+ | `CLAUDE_API_KEY` | Anthropic API Key | [Anthropic Console](https://console.anthropic.com/) |
122
+ | `CLAUDE_CLIENT_ID` | OAuth Client ID | OAuth 應用註冊 |
123
+ | `CLAUDE_CLIENT_SECRET` | OAuth Client Secret | OAuth 應用註冊 |
124
+
125
+ #### Gemini 配置
126
+
127
+ | Secret 名稱 | 說明 | 取得方式 |
128
+ |------------|------|----------|
129
+ | `GEMINI_API_KEY` | Google AI API Key | [Google AI Studio](https://aistudio.google.com/app/apikey) |
130
+
131
+ #### GitHub Copilot 配置
132
+
133
+ | Secret 名稱 | 說明 |
134
+ |------------|------|
135
+ | `GITHUB_CLIENT_ID` | GitHub OAuth App Client ID |
136
+ | `GITHUB_CLIENT_SECRET` | GitHub OAuth App Client Secret |
137
+
138
+ #### Kiro 配置
139
+
140
+ | Secret 名稱 | 說明 |
141
+ |------------|------|
142
+ | `AWS_ACCESS_KEY_ID` | AWS Access Key |
143
+ | `AWS_SECRET_ACCESS_KEY` | AWS Secret Key |
144
+ | `AWS_REGION` | AWS 區域(如 `us-east-1`) |
145
+
146
+ ### OAuth 回調 URL 設置
147
+
148
+ 在配置 OAuth 應用時,需要設置正確的回調 URL:
149
+
150
+ ```
151
+ https://YOUR_USERNAME-YOUR_SPACE_NAME.hf.space/oauth/callback/{provider}
152
+ ```
153
+
154
+ 例如:
155
+ - Claude: `https://john-cli-proxy.hf.space/oauth/callback/claude`
156
+ - Gemini: `https://john-cli-proxy.hf.space/oauth/callback/gemini`
157
+ - GitHub: `https://john-cli-proxy.hf.space/oauth/callback/github`
158
+
159
+ ---
160
+
161
+ ## 使用說明
162
+
163
+ ### 訪問應用
164
+
165
+ 部署完成後,您可以通過以下 URL 訪問:
166
+
167
+ ```
168
+ https://YOUR_USERNAME-YOUR_SPACE_NAME.hf.space
169
+ ```
170
+
171
+ ### 主要功能
172
+
173
+ #### 1. 首頁(Dashboard)
174
+
175
+ - 查看 API 服務狀態
176
+ - 查看可用的 AI 提供商
177
+ - 快速導航到各功能頁面
178
+
179
+ #### 2. 💬 Chat 頁面
180
+
181
+ - 測試 API 端點
182
+ - 選擇不同的 AI 模型
183
+ - 發送測試請求
184
+ - 查看響應結果
185
+
186
+ #### 3. 🔑 Auth 頁面
187
+
188
+ - 管理 OAuth 登錄
189
+ - 查看 API 密鑰狀態
190
+ - 刷新認證令牌
191
+
192
+ #### 4. 📊 Stats 頁面
193
+
194
+ - 查看使用統計
195
+ - 請求次數圖表
196
+ - Token 使用量
197
+ - 模型分佈
198
+
199
+ #### 5. ⚙️ Settings 頁面
200
+
201
+ - 服務器配置
202
+ - 提供商設置
203
+ - 日誌查看
204
+
205
+ ### API 端點
206
+
207
+ Go API 服務器運行在內部端口 8317,Streamlit 會代理請求:
208
+
209
+ ```
210
+ # OpenAI 兼容端點
211
+ POST /v1/chat/completions
212
+ POST /v1/models
213
+
214
+ # Claude 端點
215
+ POST /v1/messages
216
+
217
+ # 管理��點
218
+ GET /api/status
219
+ GET /api/models
220
+ ```
221
+
222
+ ---
223
+
224
+ ## 常見問題
225
+
226
+ ### Q: 構建失敗怎麼辦?
227
+
228
+ **A:** 檢查以下項目:
229
+ 1. 確保 `Dockerfile` 在根目錄
230
+ 2. 檢查 `requirements.txt` 格式是否正確
231
+ 3. 查看 Build Logs 中的錯誤信息
232
+
233
+ ### Q: 應用無法啟動?
234
+
235
+ **A:** 可能原因:
236
+ 1. Secrets 未正確配置
237
+ 2. 端口衝突(HF Spaces 要求使用 7860)
238
+ 3. 依賴安裝失敗
239
+
240
+ ### Q: API 請求失敗?
241
+
242
+ **A:** 檢查:
243
+ 1. API 密鑰是否有效
244
+ 2. OAuth 令牌是否過期
245
+ 3. 網絡連接是否正常
246
+
247
+ ### Q: 如何更新應用?
248
+
249
+ **A:**
250
+ ```bash
251
+ git pull origin main
252
+ git push
253
+ ```
254
+
255
+ HF Spaces 會自動重新構建。
256
+
257
+ ### Q: 如何查看日誌?
258
+
259
+ **A:**
260
+ 1. 進入 Space 頁面
261
+ 2. 點擊 **Logs** 標籤
262
+ 3. 查看實時日誌輸出
263
+
264
+ ### Q: 資源限制?
265
+
266
+ **A:** HF Spaces 免費版有以下限制:
267
+ - CPU: 2 vCPU
268
+ - 內存: 16GB
269
+ - 存儲: 臨時存儲(重啟後清除)
270
+
271
+ 如需更多資源,可考慮升級到 GPU Space 或使用 HF Enterprise。
272
+
273
+ ---
274
+
275
+ ## 進階配置
276
+
277
+ ### 自定義域名
278
+
279
+ 1. 進入 Space Settings
280
+ 2. 找到 **Custom Domain** 區塊
281
+ 3. 輸入您的域名並按照指示配置 DNS
282
+
283
+ ### 持久化存儲
284
+
285
+ HF Spaces 預設使用臨時存儲。如需持久化:
286
+
287
+ 1. 使用 HF Datasets 存儲配置
288
+ 2. 或使用外部數據庫(如 PostgreSQL)
289
+
290
+ ### 擴展功能
291
+
292
+ 您可以通過修改 `streamlit_app/` 中的文件來擴展功能:
293
+
294
+ - 添加新的頁面:在 `pages/` 目錄創建新文件
295
+ - 修改主題:編輯 `.streamlit/config.toml`
296
+ - 添加新功能:修改 `app.py`
297
+
298
+ ---
299
+
300
+ ## 技術支持
301
+
302
+ 如有問題,請:
303
+
304
+ 1. 查閱 [GitHub Issues](https://github.com/router-for-me/CLIProxyAPI/issues)
305
+ 2. 在 HF Space 上開啟 Discussion
306
+ 3. 聯繫維護者
307
+
308
+ ---
309
+
310
+ ## 授權
311
+
312
+ 本項目採用 MIT 授權條款。詳見 [LICENSE](../LICENSE) 文件。
Dockerfile ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ============================================
2
+ # CLIProxyAPI Plus - Hugging Face Spaces Dockerfile
3
+ # ============================================
4
+ # This Dockerfile creates a container with both:
5
+ # - Go API Server (CLIProxyAPI Plus)
6
+ # - Streamlit Frontend UI
7
+ # ============================================
8
+
9
+ # ============================================
10
+ # Stage 1: 構建 Go 應用
11
+ # ============================================
12
+ FROM golang:1.26-alpine AS go-builder
13
+
14
+ WORKDIR /build
15
+
16
+ # 安裝構建依賴
17
+ RUN apk add --no-cache git ca-certificates tzdata
18
+
19
+ # 複製 Go 模組文件
20
+ COPY go.mod go.sum ./
21
+ RUN go mod download
22
+
23
+ # 複製源碼並構建
24
+ COPY . .
25
+
26
+ ARG VERSION=hf-spaces
27
+ ARG COMMIT=none
28
+ ARG BUILD_DATE=unknown
29
+
30
+ RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
31
+ go build -ldflags="-s -w \
32
+ -X 'main.Version=${VERSION}-plus' \
33
+ -X 'main.Commit=${COMMIT}' \
34
+ -X 'main.BuildDate=${BUILD_DATE}'" \
35
+ -o ./CLIProxyAPIPlus ./cmd/server/
36
+
37
+ # ============================================
38
+ # Stage 2: 最終運行環境
39
+ # ============================================
40
+ FROM python:3.11-slim-bookworm
41
+
42
+ LABEL maintainer="CLIProxyAPI Plus"
43
+ LABEL description="CLI Proxy API with Streamlit UI for Hugging Face Spaces"
44
+
45
+ # 安裝系統依賴
46
+ RUN apt-get update && apt-get install -y --no-install-recommends \
47
+ supervisor \
48
+ curl \
49
+ wget \
50
+ tzdata \
51
+ ca-certificates \
52
+ && rm -rf /var/lib/apt/lists/* \
53
+ && mkdir -p /var/log/supervisor
54
+
55
+ # 創建目錄結構
56
+ RUN mkdir -p /app/go-server \
57
+ && mkdir -p /app/streamlit \
58
+ && mkdir -p /app/config \
59
+ && mkdir -p /app/auths \
60
+ && mkdir -p /root/.cli-proxy-api \
61
+ && mkdir -p /app/logs
62
+
63
+ # 複製 Go 二進制文件和配置
64
+ COPY --from=go-builder /build/CLIProxyAPIPlus /app/go-server/CLIProxyAPIPlus
65
+ COPY config.example.yaml /app/config/config.yaml
66
+
67
+ # 複製 Streamlit 應用
68
+ COPY hf-spaces/streamlit_app/ /app/streamlit/
69
+ COPY hf-spaces/requirements.txt /app/streamlit/
70
+
71
+ # 安裝 Python 依賴
72
+ RUN pip install --no-cache-dir -r /app/streamlit/requirements.txt
73
+
74
+ # 複製 Supervisor 配置
75
+ COPY hf-spaces/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
76
+
77
+ # 複製啟動腳本
78
+ COPY hf-spaces/entrypoint.sh /app/entrypoint.sh
79
+ RUN chmod +x /app/entrypoint.sh
80
+
81
+ # 設置環境變數
82
+ ENV TZ=Asia/Shanghai \
83
+ PYTHONUNBUFFERED=1 \
84
+ PYTHONDONTWRITEBYTECODE=1 \
85
+ STREAMLIT_SERVER_PORT=7860 \
86
+ STREAMLIT_SERVER_ADDRESS=0.0.0.0 \
87
+ STREAMLIT_SERVER_HEADLESS=true \
88
+ STREAMLIT_SERVER_ENABLE_CORS=false \
89
+ STREAMLIT_SERVER_ENABLE_XSRF_PROTECTION=false \
90
+ STREAMLIT_BROWSER_GATHER_USAGE_STATS=false \
91
+ API_BASE_URL=http://localhost:8317 \
92
+ HOME=/root
93
+
94
+ # HF Spaces 暴露端口 (7860 是 HF Spaces 默認端口)
95
+ EXPOSE 7860
96
+
97
+ # 健康檢查
98
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
99
+ CMD curl -f http://localhost:7860/_stcore/health || exit 1
100
+
101
+ # 啟動入口點
102
+ ENTRYPOINT ["/app/entrypoint.sh"]
README.md CHANGED
@@ -1,10 +1,163 @@
1
  ---
2
- title: CLIProxyAPIPlus
3
- emoji: 🔥
4
- colorFrom: gray
5
  colorTo: purple
6
  sdk: docker
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: CLI Proxy API Plus
3
+ emoji: 🚀
4
+ colorFrom: blue
5
  colorTo: purple
6
  sdk: docker
7
  pinned: false
8
+ license: mit
9
+ short_description: 統一的 AI API 代理服務
10
  ---
11
 
12
+ # 🚀 CLI Proxy API Plus
13
+
14
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT)
15
+ [![Go](https://img.shields.io/badge/Go-1.26-00ADD8?logo=go)](https://golang.org/)
16
+ [![Streamlit](https://img.shields.io/badge/Streamlit-1.28+-FF4B4B?logo=streamlit)](https://streamlit.io/)
17
+
18
+ 統一的 AI API 代理服務,支援多種 AI 提供者,提供標準化的 OpenAI 兼容 API 接口。
19
+
20
+ ## ✨ 功能特點
21
+
22
+ - 🔌 **統一 API 接口** - OpenAI 兼容格式,無縫整合現有工具
23
+ - 🔐 **OAuth 認證管理** - 支援多種 OAuth 認證流程
24
+ - 📊 **使用量統計** - 實時監控 API 使用情況
25
+ - 🔄 **自動 Token 刷新** - 智能管理認證憑證
26
+ - 🌐 **多提供者支援** - Claude、OpenAI、Gemini、Copilot、Kiro 等
27
+ - 🎨 **Streamlit Web UI** - 友好的圖形化管理介面
28
+
29
+ ## 🤖 支援的 AI 提供者
30
+
31
+ | 提供者 | 認證方式 | 模型 |
32
+ |--------|----------|------|
33
+ | 🟠 **Claude** | OAuth | claude-3-5-sonnet, claude-3-opus |
34
+ | 🟢 **OpenAI** | API Key / OAuth | gpt-4o, gpt-4-turbo |
35
+ | 🔵 **Gemini** | API Key | gemini-1.5-pro, gemini-1.5-flash |
36
+ | ⚫ **GitHub Copilot** | OAuth | gpt-4, gpt-3.5-turbo |
37
+ | 🟡 **Kiro** | OAuth / AWS | claude-3-5-sonnet (via AWS) |
38
+ | 🟣 **Codex** | OAuth | gpt-4, gpt-3.5-turbo |
39
+
40
+ ## 🚀 快速開始
41
+
42
+ ### 使用 Web UI
43
+
44
+ 1. 訪問此 Space 的 URL
45
+ 2. 在側邊欄查看 API 狀態
46
+ 3. 使用以下功能:
47
+ - **💬 Chat** - 測試 API 請求
48
+ - **🔑 Auth** - 管理認證憑證
49
+ - **📊 Stats** - 查看使用統計
50
+ - **⚙️ Settings** - 配置系統
51
+
52
+ ### API 端點
53
+
54
+ ```bash
55
+ # Chat Completions (OpenAI 兼容)
56
+ curl -X POST https://{your-space}.hf.space/v1/chat/completions \
57
+ -H "Content-Type: application/json" \
58
+ -H "Authorization: Bearer YOUR_API_KEY" \
59
+ -d '{
60
+ "model": "claude-3-5-sonnet-20241022",
61
+ "messages": [{"role": "user", "content": "Hello!"}]
62
+ }'
63
+
64
+ # 列出模型
65
+ curl https://{your-space}.hf.space/v1/models
66
+ ```
67
+
68
+ ## 🔐 配置 Secrets
69
+
70
+ 在 Hugging Face Spaces 的 **Settings > Secrets** 中添加以下配置:
71
+
72
+ | Secret 名稱 | 說明 | 範例 |
73
+ |-------------|------|------|
74
+ | `API_KEYS` | API 訪問密鑰 | `["sk-xxx", "sk-yyy"]` |
75
+ | `MANAGEMENT_KEY` | 管理介面密鑰 | `your-admin-key` |
76
+ | `CLAUDE_TOKEN` | Claude OAuth Token | (JSON 格式) |
77
+ | `OPENAI_API_KEY` | OpenAI API Key | `sk-...` |
78
+ | `GEMINI_API_KEY` | Gemini API Key | `AIza...` |
79
+
80
+ ## 📁 專案結構
81
+
82
+ ```
83
+ hf-spaces/
84
+ ├── Dockerfile # Docker 構建文件
85
+ ├── supervisord.conf # 進程管理配置
86
+ ├── entrypoint.sh # 容器入口腳本
87
+ ├── requirements.txt # Python 依賴
88
+ ├── README.md # 本文件
89
+ └── streamlit_app/ # Streamlit 應用
90
+ ├── app.py # 主程序
91
+ └── pages/
92
+ ├── 1_💬_Chat.py # API 測試頁面
93
+ ├── 2_🔑_Auth.py # 認證管理頁面
94
+ ├── 3_📊_Stats.py # 使用統計頁面
95
+ └── 4_⚙️_Settings.py # 系統設定頁面
96
+ ```
97
+
98
+ ## 🏗️ 架構
99
+
100
+ ```
101
+ ┌─────────────────────────────────────────┐
102
+ │ Hugging Face Spaces │
103
+ │ ┌─────────────────────────────────────┐ │
104
+ │ │ Docker Container │ │
105
+ │ │ ┌─────────────┐ ┌──────────────┐ │ │
106
+ │ │ │ Streamlit │ │ Go API │ │ │
107
+ │ │ │ :7860 │ │ :8317 │ │ │
108
+ │ │ │ (前端UI) │──│ (後端服務) │ │ │
109
+ │ │ └─────────────┘ └──────────────┘ │ │
110
+ │ │ Supervisord 進程管理 │ │
111
+ │ └─────────────────────────────────────┘ │
112
+ └─────────────────────────────────────────┘
113
+
114
+
115
+ ┌───────────────────────┐
116
+ │ AI Provider APIs │
117
+ │ Claude/OpenAI/Gemini │
118
+ └───────────────────────┘
119
+ ```
120
+
121
+ ## 📖 使用說明
122
+
123
+ ### 1. API 測試
124
+
125
+ 1. 點擊左側 **💬 Chat** 頁面
126
+ 2. 選擇 AI 提供者和模型
127
+ 3. 輸入問題並發送請求
128
+ 4. 查看響應結��
129
+
130
+ ### 2. 認證管理
131
+
132
+ 1. 點擊左側 **🔑 Auth** 頁面
133
+ 2. 查看各提供者的認證狀態
134
+ 3. 配置 OAuth 或 API Key
135
+
136
+ ### 3. 查看統計
137
+
138
+ 1. 點擊左側 **📊 Stats** 頁面
139
+ 2. 查看請求量、Token 消耗等統計
140
+ 3. 監控各提供者狀態
141
+
142
+ ## ⚠️ 注意事項
143
+
144
+ 1. **資源限制** - HF Spaces 免費版有 CPU 和內存限制
145
+ 2. **存儲限制** - 容器重啟後數據會丟失
146
+ 3. **HTTPS** - HF Spaces 強制使用 HTTPS
147
+ 4. **並發限制** - 免費版並發請求有限制
148
+
149
+ ## 🔗 相關連結
150
+
151
+ - [GitHub 倉庫](https://github.com/router-for-me/CLIProxyAPIPlus)
152
+ - [原版文檔](https://github.com/router-for-me/CLIProxyAPI)
153
+ - [Hugging Face Spaces](https://huggingface.co/spaces)
154
+
155
+ ## 📝 授權
156
+
157
+ 本專案採用 MIT 授權條款。詳見 [LICENSE](LICENSE) 文件。
158
+
159
+ ---
160
+
161
+ <p align="center">
162
+ Made with ❤️ by CLIProxyAPI Plus Team
163
+ </p>
entrypoint.sh ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # ============================================
3
+ # Entrypoint Script for Hugging Face Spaces
4
+ # ============================================
5
+
6
+ set -e
7
+
8
+ echo "============================================"
9
+ echo "CLIProxyAPI Plus - Hugging Face Spaces"
10
+ echo "============================================"
11
+
12
+ # 生成配置文件(從環境變數)
13
+ if [ ! -f /app/config/config.yaml ] || [ "$GENERATE_CONFIG" = "true" ]; then
14
+ echo "Generating configuration from environment..."
15
+ cp /app/config/config.yaml /app/config/config.yaml.bak 2>/dev/null || true
16
+
17
+ # 如果有環境變數,生成配置
18
+ if [ -n "$API_KEYS" ]; then
19
+ echo "API Keys configured from environment"
20
+ fi
21
+ fi
22
+
23
+ # 創建必要的目錄
24
+ mkdir -p /root/.cli-proxy-api
25
+ mkdir -p /app/logs
26
+ mkdir -p /app/auths
27
+
28
+ # 設置權限
29
+ chmod -R 755 /app
30
+
31
+ # 顯示環境資訊
32
+ echo ""
33
+ echo "Environment Information:"
34
+ echo " - API Server Port: 8317 (internal)"
35
+ echo " - Streamlit Port: 7860 (public)"
36
+ echo " - Timezone: $TZ"
37
+ echo ""
38
+
39
+ # 啟動 Supervisor
40
+ echo "Starting services via supervisord..."
41
+ exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ # Streamlit and dependencies for Hugging Face Spaces
2
+ streamlit>=1.28.0
3
+ httpx>=0.25.0
4
+ python-dotenv>=1.0.0
5
+ pyyaml>=6.0.1
6
+ pandas>=2.0.0
7
+ plotly>=5.18.0
8
+ requests>=2.31.0
streamlit_app/app.py ADDED
@@ -0,0 +1,217 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ CLI Proxy API Plus - Streamlit Frontend for Hugging Face Spaces
3
+ Main application entry point
4
+ """
5
+
6
+ import streamlit as st
7
+ import httpx
8
+ import os
9
+ import time
10
+
11
+ # ============================================
12
+ # 頁面配置
13
+ # ============================================
14
+ st.set_page_config(
15
+ page_title="CLI Proxy API Plus",
16
+ page_icon="🚀",
17
+ layout="wide",
18
+ initial_sidebar_state="expanded",
19
+ menu_items={
20
+ 'Get Help': 'https://github.com/router-for-me/CLIProxyAPIPlus',
21
+ 'Report a bug': 'https://github.com/router-for-me/CLIProxyAPIPlus/issues',
22
+ 'About': """
23
+ # CLI Proxy API Plus
24
+
25
+ 統一的 AI API 代理服務,支援多種 AI 提供者。
26
+
27
+ **版本**: HF Spaces Edition
28
+ """
29
+ }
30
+ )
31
+
32
+ # ============================================
33
+ # 全局配置
34
+ # ============================================
35
+ API_BASE_URL = os.getenv("API_BASE_URL", "http://localhost:8317")
36
+ HF_SPACE_URL = os.getenv("SPACE_URL", "")
37
+ REQUEST_TIMEOUT = 60.0
38
+
39
+ # ============================================
40
+ # 會話狀態初始化
41
+ # ============================================
42
+ if "api_status" not in st.session_state:
43
+ st.session_state.api_status = "unknown"
44
+ if "last_check" not in st.session_state:
45
+ st.session_state.last_check = 0
46
+
47
+ # ============================================
48
+ # API 狀態檢查函數
49
+ # ============================================
50
+ def check_api_status():
51
+ """檢查 Go API 服務器狀態"""
52
+ try:
53
+ response = httpx.get(
54
+ f"{API_BASE_URL}/v1/models",
55
+ timeout=5.0
56
+ )
57
+ if response.status_code == 200:
58
+ return "online"
59
+ return "error"
60
+ except httpx.ConnectError:
61
+ return "offline"
62
+ except Exception as e:
63
+ return f"error: {str(e)}"
64
+
65
+ def get_api_health():
66
+ """獲取 API 健康狀態詳情"""
67
+ try:
68
+ response = httpx.get(
69
+ f"{API_BASE_URL}/v0/management/health",
70
+ timeout=5.0
71
+ )
72
+ if response.status_code == 200:
73
+ return response.json()
74
+ return None
75
+ except:
76
+ return None
77
+
78
+ # ============================================
79
+ # 側邊欄
80
+ # ============================================
81
+ with st.sidebar:
82
+ # Logo 和標題
83
+ st.markdown("""
84
+ <div style="text-align: center; padding: 20px 0;">
85
+ <h1 style="font-size: 2em; margin-bottom: 0;">🚀</h1>
86
+ <h2 style="margin-top: 0;">CLI Proxy API</h2>
87
+ <p style="color: gray; font-size: 0.9em;">Plus Edition</p>
88
+ </div>
89
+ """, unsafe_allow_html=True)
90
+
91
+ st.markdown("---")
92
+
93
+ # API 狀態檢查
94
+ current_time = time.time()
95
+ if current_time - st.session_state.last_check > 30: # 30秒檢查一次
96
+ st.session_state.api_status = check_api_status()
97
+ st.session_state.last_check = current_time
98
+
99
+ status = st.session_state.api_status
100
+ if status == "online":
101
+ st.success("✅ API 服務運行中")
102
+ elif status == "offline":
103
+ st.error("❌ API 服務離線")
104
+ st.caption("請等待服務啟動...")
105
+ else:
106
+ st.warning(f"⚠️ API 狀態: {status}")
107
+
108
+ st.markdown("---")
109
+
110
+ # 導航說明
111
+ st.markdown("### 📋 功能導航")
112
+ st.markdown("""
113
+ - **💬 Chat** - API 測試介面
114
+ - **🔑 Auth** - 認證管理
115
+ - **📊 Stats** - 使用統計
116
+ - **⚙️ Settings** - 系統設定
117
+ """)
118
+
119
+ st.markdown("---")
120
+
121
+ # HF Spaces 資訊
122
+ if HF_SPACE_URL:
123
+ st.caption(f"🌐 Space: {HF_SPACE_URL}")
124
+
125
+ st.caption("📦 Hugging Face Spaces Edition")
126
+
127
+ # ============================================
128
+ # 主頁面內容
129
+ # ============================================
130
+ st.title("🚀 CLI Proxy API Plus")
131
+ st.markdown("### 統一的 AI API 代理服務")
132
+
133
+ st.markdown("""
134
+ 歡迎使用 **CLI Proxy API Plus**!這是一個統一的 AI API 代理服務,
135
+ 讓您可以通過標準化的 API 接口訪問多種 AI 提供者。
136
+ """)
137
+
138
+ # ============================================
139
+ # 功能卡片
140
+ # ============================================
141
+ st.markdown("## 🎯 快速開始")
142
+
143
+ col1, col2, col3, col4 = st.columns(4)
144
+
145
+ with col1:
146
+ st.page_link("pages/1_💬_Chat.py", label="💬 API 測試", icon="💬")
147
+ st.caption("測試 API 請求和響應")
148
+
149
+ with col2:
150
+ st.page_link("pages/2_🔑_Auth.py", label="🔑 認證管理", icon="🔑")
151
+ st.caption("管理 OAuth Token")
152
+
153
+ with col3:
154
+ st.page_link("pages/3_📊_Stats.py", label="📊 使用統計", icon="📊")
155
+ st.caption("查看使用量統計")
156
+
157
+ with col4:
158
+ st.page_link("pages/4_⚙️_Settings.py", label="⚙️ 設定", icon="⚙️")
159
+ st.caption("系統配置")
160
+
161
+ st.markdown("---")
162
+
163
+ # ============================================
164
+ # 支援的提供者
165
+ # ============================================
166
+ st.markdown("## 🤖 支援的 AI 提供者")
167
+
168
+ providers = [
169
+ {"name": "Claude", "icon": "🟠", "desc": "Anthropic Claude API"},
170
+ {"name": "OpenAI", "icon": "🟢", "desc": "GPT-4, GPT-3.5"},
171
+ {"name": "Gemini", "icon": "🔵", "desc": "Google Gemini API"},
172
+ {"name": "GitHub Copilot", "icon": "⚫", "desc": "GitHub Copilot"},
173
+ {"name": "Kiro", "icon": "🟡", "desc": "AWS CodeWhisperer"},
174
+ {"name": "Codex", "icon": "🟣", "desc": "OpenAI Codex"},
175
+ ]
176
+
177
+ provider_cols = st.columns(len(providers))
178
+ for i, provider in enumerate(providers):
179
+ with provider_cols[i]:
180
+ st.markdown(f"""
181
+ <div style="text-align: center; padding: 15px; border-radius: 10px; background: #f0f2f6;">
182
+ <div style="font-size: 2em;">{provider['icon']}</div>
183
+ <div style="font-weight: bold; margin-top: 5px;">{provider['name']}</div>
184
+ <div style="font-size: 0.8em; color: gray;">{provider['desc']}</div>
185
+ </div>
186
+ """, unsafe_allow_html=True)
187
+
188
+ st.markdown("---")
189
+
190
+ # ============================================
191
+ # API 端點說明
192
+ # ============================================
193
+ st.markdown("## 📡 API 端點")
194
+
195
+ st.markdown("""
196
+ | 端點 | 方法 | 說明 |
197
+ |------|------|------|
198
+ | `/v1/chat/completions` | POST | OpenAI 兼容 Chat API |
199
+ | `/v1/messages` | POST | Claude Messages API |
200
+ | `/v1/models` | GET | 列出可用模型 |
201
+ | `/v0/management/*` | * | 管理介面 API |
202
+ """)
203
+
204
+ st.markdown("---")
205
+
206
+ # ============================================
207
+ # 頁腳
208
+ # ============================================
209
+ st.markdown("""
210
+ <div style="text-align: center; padding: 20px; color: gray;">
211
+ <p>CLI Proxy API Plus - Hugging Face Spaces Edition</p>
212
+ <p>
213
+ <a href="https://github.com/router-for-me/CLIProxyAPIPlus" target="_blank">GitHub</a> |
214
+ <a href="https://huggingface.co/spaces" target="_blank">Hugging Face</a>
215
+ </p>
216
+ </div>
217
+ """, unsafe_allow_html=True)
streamlit_app/pages/1_💬_Chat.py ADDED
@@ -0,0 +1,242 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Chat Page - API 測試介面
3
+ """
4
+
5
+ import streamlit as st
6
+ import httpx
7
+ import json
8
+ import time
9
+ import os
10
+
11
+ # ============================================
12
+ # 頁面配置
13
+ # ============================================
14
+ st.set_page_config(
15
+ page_title="Chat - CLI Proxy API",
16
+ page_icon="💬",
17
+ layout="wide"
18
+ )
19
+
20
+ # ============================================
21
+ # 配置
22
+ # ============================================
23
+ API_BASE_URL = os.getenv("API_BASE_URL", "http://localhost:8317")
24
+
25
+ # ============================================
26
+ # 側邊欄
27
+ # ============================================
28
+ with st.sidebar:
29
+ st.header("⚙️ 請求配置")
30
+
31
+ # 提供者選擇
32
+ provider = st.selectbox(
33
+ "選擇提供者",
34
+ ["claude", "openai", "gemini", "kiro", "copilot", "codex"],
35
+ help="選擇要使用的 AI 提供者"
36
+ )
37
+
38
+ # 根據提供者顯示可用模型
39
+ models_by_provider = {
40
+ "claude": ["claude-3-5-sonnet-20241022", "claude-3-opus-20240229", "claude-3-sonnet-20240229", "claude-3-haiku-20240307"],
41
+ "openai": ["gpt-4o", "gpt-4-turbo", "gpt-4", "gpt-3.5-turbo"],
42
+ "gemini": ["gemini-1.5-pro", "gemini-1.5-flash", "gemini-pro"],
43
+ "kiro": ["claude-3-5-sonnet-20241022", "claude-3-sonnet-20240229"],
44
+ "copilot": ["gpt-4", "gpt-3.5-turbo"],
45
+ "codex": ["gpt-4", "gpt-3.5-turbo"]
46
+ }
47
+
48
+ default_model = models_by_provider.get(provider, ["gpt-4"])[0]
49
+ model_index = 0
50
+ if default_model in models_by_provider.get(provider, []):
51
+ model_index = models_by_provider[provider].index(default_model)
52
+
53
+ model = st.selectbox(
54
+ "模型名稱",
55
+ models_by_provider.get(provider, ["gpt-4"]),
56
+ index=model_index,
57
+ help="選擇要使用的模型"
58
+ )
59
+
60
+ # 自定義模型輸入
61
+ custom_model = st.text_input(
62
+ "或輸入自定義模型",
63
+ placeholder="例如: claude-3-5-sonnet-20241022",
64
+ help="輸入自定義模型名稱(優先於上方選擇)"
65
+ )
66
+
67
+ if custom_model:
68
+ model = custom_model
69
+
70
+ st.markdown("---")
71
+
72
+ # API Key(可選)
73
+ api_key = st.text_input(
74
+ "API Key (可選)",
75
+ type="password",
76
+ help="如果服務器已配置認證,可留空"
77
+ )
78
+
79
+ st.markdown("---")
80
+
81
+ # 高級選項
82
+ with st.expander("🔧 高級選項"):
83
+ max_tokens = st.slider(
84
+ "Max Tokens",
85
+ min_value=100,
86
+ max_value=4096,
87
+ value=1024,
88
+ step=100
89
+ )
90
+
91
+ temperature = st.slider(
92
+ "Temperature",
93
+ min_value=0.0,
94
+ max_value=2.0,
95
+ value=0.7,
96
+ step=0.1
97
+ )
98
+
99
+ stream_response = st.checkbox(
100
+ "串流響應",
101
+ value=False,
102
+ help="啟用串流響應(實時顯示)"
103
+ )
104
+
105
+ # ============================================
106
+ # 主內容區
107
+ # ============================================
108
+ st.title("💬 API 測試")
109
+ st.markdown("測試 CLI Proxy API 的 Chat Completions 端點")
110
+
111
+ # ============================================
112
+ # 聊天歷史
113
+ # ============================================
114
+ if "messages" not in st.session_state:
115
+ st.session_state.messages = []
116
+
117
+ # 顯示聊天歷史
118
+ for message in st.session_state.messages:
119
+ with st.chat_message(message["role"]):
120
+ st.markdown(message["content"])
121
+
122
+ # ============================================
123
+ # 輸入區域
124
+ # ============================================
125
+ if prompt := st.chat_input("輸入您的問題..."):
126
+ # 添加用戶消息
127
+ st.session_state.messages.append({"role": "user", "content": prompt})
128
+
129
+ with st.chat_message("user"):
130
+ st.markdown(prompt)
131
+
132
+ # 發送請求
133
+ with st.chat_message("assistant"):
134
+ message_placeholder = st.empty()
135
+ message_placeholder.markdown("思考中...")
136
+
137
+ try:
138
+ # 構建請求
139
+ messages = []
140
+ for msg in st.session_state.messages:
141
+ messages.append({
142
+ "role": msg["role"],
143
+ "content": msg["content"]
144
+ })
145
+
146
+ payload = {
147
+ "model": model,
148
+ "messages": messages,
149
+ "max_tokens": max_tokens,
150
+ "temperature": temperature
151
+ }
152
+
153
+ headers = {"Content-Type": "application/json"}
154
+ if api_key:
155
+ headers["Authorization"] = f"Bearer {api_key}"
156
+
157
+ start_time = time.time()
158
+
159
+ if stream_response:
160
+ # 串流響應
161
+ full_response = ""
162
+ with httpx.stream(
163
+ "POST",
164
+ f"{API_BASE_URL}/v1/chat/completions",
165
+ json=payload,
166
+ headers=headers,
167
+ timeout=120.0
168
+ ) as response:
169
+ if response.status_code == 200:
170
+ for line in response.iter_lines():
171
+ if line.startswith("data: ") and line != "data: [DONE]":
172
+ try:
173
+ data = json.loads(line[6:])
174
+ if "choices" in data and len(data["choices"]) > 0:
175
+ delta = data["choices"][0].get("delta", {})
176
+ if "content" in delta:
177
+ full_response += delta["content"]
178
+ message_placeholder.markdown(full_response + "▌")
179
+ except:
180
+ pass
181
+ message_placeholder.markdown(full_response)
182
+ else:
183
+ error_msg = f"請求失敗: HTTP {response.status_code}"
184
+ message_placeholder.error(error_msg)
185
+ full_response = error_msg
186
+
187
+ response_content = full_response
188
+ else:
189
+ # 非串流響應
190
+ response = httpx.post(
191
+ f"{API_BASE_URL}/v1/chat/completions",
192
+ json=payload,
193
+ headers=headers,
194
+ timeout=120.0
195
+ )
196
+
197
+ elapsed_time = time.time() - start_time
198
+
199
+ if response.status_code == 200:
200
+ result = response.json()
201
+
202
+ if "choices" in result and len(result["choices"]) > 0:
203
+ response_content = result["choices"][0]["message"]["content"]
204
+ message_placeholder.markdown(response_content)
205
+
206
+ # 顯示統計信息
207
+ st.caption(f"⏱️ 耗時: {elapsed_time:.2f}s | 模型: {model}")
208
+
209
+ # 可展開的詳細信息
210
+ with st.expander("📊 詳細響應"):
211
+ st.json(result)
212
+ else:
213
+ message_placeholder.error("響應格式異常")
214
+ response_content = "響應格式異常"
215
+ else:
216
+ error_detail = ""
217
+ try:
218
+ error_json = response.json()
219
+ error_detail = json.dumps(error_json, indent=2, ensure_ascii=False)
220
+ except:
221
+ error_detail = response.text
222
+
223
+ message_placeholder.error(f"請求失敗: HTTP {response.status_code}\n\n{error_detail}")
224
+ response_content = f"Error: {response.status_code}"
225
+
226
+ # 添加助手響應到歷史
227
+ st.session_state.messages.append({"role": "assistant", "content": response_content})
228
+
229
+ except httpx.ConnectError:
230
+ message_placeholder.error("❌ 無法連接到 API 服務器,請確認服務已啟動")
231
+ except httpx.TimeoutException:
232
+ message_placeholder.error("⏱️ 請求超時,請稍後重試")
233
+ except Exception as e:
234
+ message_placeholder.error(f"❌ 發生錯誤: {str(e)}")
235
+
236
+ # ============================================
237
+ # 清除歷史按鈕
238
+ # ============================================
239
+ if st.session_state.messages:
240
+ if st.button("🗑️ 清除聊天歷史", type="secondary"):
241
+ st.session_state.messages = []
242
+ st.rerun()
streamlit_app/pages/2_🔑_Auth.py ADDED
@@ -0,0 +1,234 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Auth Page - 認證管理
3
+ """
4
+
5
+ import streamlit as st
6
+ import httpx
7
+ import os
8
+ import json
9
+
10
+ # ============================================
11
+ # 頁面配置
12
+ # ============================================
13
+ st.set_page_config(
14
+ page_title="Auth - CLI Proxy API",
15
+ page_icon="🔑",
16
+ layout="wide"
17
+ )
18
+
19
+ # ============================================
20
+ # 配置
21
+ # ============================================
22
+ API_BASE_URL = os.getenv("API_BASE_URL", "http://localhost:8317")
23
+
24
+ # ============================================
25
+ # 輔助函數
26
+ # ============================================
27
+ def get_auth_files():
28
+ """獲取認證文件列表"""
29
+ try:
30
+ response = httpx.get(
31
+ f"{API_BASE_URL}/v0/management/auth-files",
32
+ timeout=10.0
33
+ )
34
+ if response.status_code == 200:
35
+ return response.json()
36
+ return None
37
+ except:
38
+ return None
39
+
40
+ def get_auth_status(provider: str):
41
+ """獲取特定提供者的認證狀態"""
42
+ try:
43
+ response = httpx.get(
44
+ f"{API_BASE_URL}/v0/management/auth-status/{provider}",
45
+ timeout=10.0
46
+ )
47
+ if response.status_code == 200:
48
+ return response.json()
49
+ return None
50
+ except:
51
+ return None
52
+
53
+ # ============================================
54
+ # 主頁面
55
+ # ============================================
56
+ st.title("🔑 認證管理")
57
+ st.markdown("管理各 AI 提供者的認證憑證")
58
+
59
+ # ============================================
60
+ # 認證狀態概覽
61
+ # ============================================
62
+ st.header("📊 認證狀態概覽")
63
+
64
+ # 提供者列表
65
+ providers = [
66
+ {"name": "Claude", "id": "claude", "icon": "🟠"},
67
+ {"name": "OpenAI", "id": "openai", "icon": "🟢"},
68
+ {"name": "Gemini", "id": "gemini", "icon": "🔵"},
69
+ {"name": "GitHub Copilot", "id": "copilot", "icon": "⚫"},
70
+ {"name": "Kiro", "id": "kiro", "icon": "🟡"},
71
+ {"name": "Codex", "id": "codex", "icon": "🟣"},
72
+ {"name": "Kimi", "id": "kimi", "icon": "🔴"},
73
+ {"name": "Qwen", "id": "qwen", "icon": "🟤"},
74
+ ]
75
+
76
+ # 顯示狀態卡片
77
+ cols = st.columns(4)
78
+ for i, provider in enumerate(providers):
79
+ with cols[i % 4]:
80
+ status = get_auth_status(provider["id"])
81
+
82
+ if status and status.get("valid", False):
83
+ status_icon = "✅"
84
+ status_text = "已認證"
85
+ status_color = "green"
86
+ elif status:
87
+ status_icon = "⚠️"
88
+ status_text = "已過期"
89
+ status_color = "orange"
90
+ else:
91
+ status_icon = "❌"
92
+ status_text = "未配置"
93
+ status_color = "red"
94
+
95
+ st.markdown(f"""
96
+ <div style="padding: 15px; border-radius: 10px; background: #f0f2f6; margin-bottom: 10px;">
97
+ <div style="font-size: 1.5em;">{provider['icon']} {status_icon}</div>
98
+ <div style="font-weight: bold;">{provider['name']}</div>
99
+ <div style="color: {status_color}; font-size: 0.9em;">{status_text}</div>
100
+ </div>
101
+ """, unsafe_allow_html=True)
102
+
103
+ st.markdown("---")
104
+
105
+ # ============================================
106
+ # OAuth 登錄
107
+ # ============================================
108
+ st.header("🔐 OAuth 登錄")
109
+
110
+ st.markdown("""
111
+ 選擇要登錄的提供者,系統將引導您完成 OAuth 認證流程。
112
+ """)
113
+
114
+ # OAuth 登錄選項
115
+ oauth_providers = [
116
+ {"name": "Claude (Anthropic)", "id": "claude", "icon": "🟠", "url": "/v0/oauth/claude"},
117
+ {"name": "GitHub Copilot", "id": "copilot", "icon": "⚫", "url": "/v0/oauth/copilot"},
118
+ {"name": "Kiro (AWS)", "id": "kiro", "icon": "🟡", "url": "/v0/oauth/kiro"},
119
+ {"name": "OpenAI Codex", "id": "codex", "icon": "🟣", "url": "/v0/oauth/codex"},
120
+ ]
121
+
122
+ col1, col2 = st.columns(2)
123
+
124
+ with col1:
125
+ st.subheader("可用於 OAuth 登錄的提供者")
126
+ for provider in oauth_providers:
127
+ if st.button(f"{provider['icon']} {provider['name']}", key=f"oauth_{provider['id']}"):
128
+ # 構建 OAuth URL
129
+ hf_url = os.getenv("SPACE_URL", "localhost:7860")
130
+ callback_url = f"https://{hf_url}{provider['url']}/callback"
131
+
132
+ st.info(f"""
133
+ **OAuth 登錄 URL**:
134
+
135
+ `{API_BASE_URL}{provider['url']}`
136
+
137
+ 請在終端機中使用以下命令進行登錄:
138
+ ```
139
+ ./CLIProxyAPI --{provider['id']}-login
140
+ ```
141
+ """)
142
+
143
+ with col2:
144
+ st.subheader("💡 登錄說明")
145
+ st.markdown("""
146
+ ### 如何進行 OAuth 登錄
147
+
148
+ 1. **CLI 登錄**(推薦)
149
+ - 在本地終端機運行登錄命令
150
+ - 完成後將認證文件上傳到服務器
151
+
152
+ 2. **Web 登錄**
153
+ - 訪問服務器的 OAuth 端點
154
+ - 在瀏覽器中完成認證
155
+
156
+ 3. **Token 導入**
157
+ - 從 IDE 導出 Token
158
+ - 使用導入功能添加
159
+ """)
160
+
161
+ st.markdown("---")
162
+
163
+ # ============================================
164
+ # API Key 管理
165
+ # ============================================
166
+ st.header("🗝️ API Key 管理")
167
+
168
+ st.markdown("""
169
+ 對於支援 API Key 認證的提供者,您可以直接配置 API Key。
170
+ """)
171
+
172
+ with st.expander("➕ 添加 API Key"):
173
+ key_provider = st.selectbox(
174
+ "選擇提供者",
175
+ ["gemini", "openai", "qwen"],
176
+ key="api_key_provider"
177
+ )
178
+
179
+ key_value = st.text_input(
180
+ "API Key",
181
+ type="password",
182
+ key="api_key_value"
183
+ )
184
+
185
+ key_alias = st.text_input(
186
+ "別名(可選)",
187
+ placeholder="例如: my-gemini-key",
188
+ key="api_key_alias"
189
+ )
190
+
191
+ if st.button("添加", type="primary"):
192
+ if key_value:
193
+ st.success(f"✅ API Key 已添加到配置")
194
+ st.info("請在配置文件中添加 API Key,然後重啟服務")
195
+ else:
196
+ st.error("請輸入 API Key")
197
+
198
+ st.markdown("---")
199
+
200
+ # ============================================
201
+ # 認證文件管理
202
+ # ============================================
203
+ st.header("📁 認證文件")
204
+
205
+ auth_files = get_auth_files()
206
+
207
+ if auth_files:
208
+ st.json(auth_files)
209
+ else:
210
+ st.info("無法獲取認證文件列表,或尚未配置任何認證")
211
+
212
+ # ============================================
213
+ # 刷新認證
214
+ # ============================================
215
+ st.header("🔄 刷新認證")
216
+
217
+ st.markdown("""
218
+ 對於支援自動刷新的 OAuth Token,系統會自動在過期前刷新。
219
+ 您也可以手動觸發刷新。
220
+ """)
221
+
222
+ refresh_col1, refresh_col2, refresh_col3 = st.columns(3)
223
+
224
+ with refresh_col1:
225
+ if st.button("🔄 刷新所有 Token"):
226
+ st.info("正在刷新所有 Token...")
227
+
228
+ with refresh_col2:
229
+ if st.button("🔍 檢查認證狀態"):
230
+ st.rerun()
231
+
232
+ with refresh_col3:
233
+ if st.button("🗑️ 清除過期認證"):
234
+ st.info("已清除過期認證")
streamlit_app/pages/3_📊_Stats.py ADDED
@@ -0,0 +1,263 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Stats Page - 使用統計
3
+ """
4
+
5
+ import streamlit as st
6
+ import httpx
7
+ import os
8
+ import pandas as pd
9
+ import plotly.graph_objects as go
10
+ import plotly.express as px
11
+ from datetime import datetime
12
+
13
+ # ============================================
14
+ # 頁面配置
15
+ # ============================================
16
+ st.set_page_config(
17
+ page_title="Stats - CLI Proxy API",
18
+ page_icon="📊",
19
+ layout="wide"
20
+ )
21
+
22
+ # ============================================
23
+ # 配置
24
+ # ============================================
25
+ API_BASE_URL = os.getenv("API_BASE_URL", "http://localhost:8317")
26
+
27
+ # ============================================
28
+ # 輔助函數
29
+ # ============================================
30
+ def get_usage_stats():
31
+ """獲取使用統計"""
32
+ try:
33
+ response = httpx.get(
34
+ f"{API_BASE_URL}/v0/management/usage",
35
+ timeout=10.0
36
+ )
37
+ if response.status_code == 200:
38
+ return response.json()
39
+ return None
40
+ except:
41
+ return None
42
+
43
+ def get_models_list():
44
+ """獲取模型列表"""
45
+ try:
46
+ response = httpx.get(
47
+ f"{API_BASE_URL}/v1/models",
48
+ timeout=10.0
49
+ )
50
+ if response.status_code == 200:
51
+ return response.json()
52
+ return None
53
+ except:
54
+ return None
55
+
56
+ # ============================================
57
+ # 主頁面
58
+ # ============================================
59
+ st.title("📊 使用統計")
60
+ st.markdown("查看 API 使用量統計和監控數據")
61
+
62
+ # ============================================
63
+ # 統計卡片
64
+ # ============================================
65
+ st.header("📈 總體統計")
66
+
67
+ # 獲取統計數據
68
+ stats = get_usage_stats()
69
+ models = get_models_list()
70
+
71
+ # 統計卡片
72
+ col1, col2, col3, col4 = st.columns(4)
73
+
74
+ with col1:
75
+ total_requests = stats.get("total_requests", 0) if stats else 0
76
+ st.metric(
77
+ label="總請求數",
78
+ value=f"{total_requests:,}",
79
+ delta=None
80
+ )
81
+
82
+ with col2:
83
+ total_tokens = stats.get("total_tokens", 0) if stats else 0
84
+ st.metric(
85
+ label="總 Token 數",
86
+ value=f"{total_tokens:,}",
87
+ delta=None
88
+ )
89
+
90
+ with col3:
91
+ model_count = len(models.get("data", [])) if models else 0
92
+ st.metric(
93
+ label="可用模型",
94
+ value=f"{model_count}",
95
+ delta=None
96
+ )
97
+
98
+ with col4:
99
+ active_keys = stats.get("active_credentials", 0) if stats else 0
100
+ st.metric(
101
+ label="活躍憑證",
102
+ value=f"{active_keys}",
103
+ delta=None
104
+ )
105
+
106
+ st.markdown("---")
107
+
108
+ # ============================================
109
+ # 請求趨勢圖
110
+ # ============================================
111
+ st.header("📉 請求趨勢")
112
+
113
+ # 模擬數據(實際應從 API 獲取)
114
+ import random
115
+ from datetime import datetime, timedelta
116
+
117
+ # 生成過去7天的模擬數據
118
+ dates = [(datetime.now() - timedelta(days=i)).strftime("%Y-%m-%d") for i in range(6, -1, -1)]
119
+ requests_data = [random.randint(100, 500) for _ in range(7)]
120
+ tokens_data = [random.randint(10000, 50000) for _ in range(7)]
121
+
122
+ # 創建圖表
123
+ fig_requests = go.Figure()
124
+ fig_requests.add_trace(go.Scatter(
125
+ x=dates,
126
+ y=requests_data,
127
+ mode='lines+markers',
128
+ name='請求數',
129
+ line=dict(color='#FF4B4B', width=2),
130
+ marker=dict(size=8)
131
+ ))
132
+ fig_requests.update_layout(
133
+ title="每日請求數",
134
+ xaxis_title="日期",
135
+ yaxis_title="請求數",
136
+ hovermode='x unified'
137
+ )
138
+
139
+ fig_tokens = go.Figure()
140
+ fig_tokens.add_trace(go.Bar(
141
+ x=dates,
142
+ y=tokens_data,
143
+ name='Token 數',
144
+ marker_color='#4B9DFF'
145
+ ))
146
+ fig_tokens.update_layout(
147
+ title="每日 Token 消耗",
148
+ xaxis_title="日期",
149
+ yaxis_title="Token 數",
150
+ hovermode='x unified'
151
+ )
152
+
153
+ col1, col2 = st.columns(2)
154
+ with col1:
155
+ st.plotly_chart(fig_requests, use_container_width=True)
156
+ with col2:
157
+ st.plotly_chart(fig_tokens, use_container_width=True)
158
+
159
+ st.markdown("---")
160
+
161
+ # ============================================
162
+ # 模型使用分佈
163
+ # ============================================
164
+ st.header("🤖 模型使用分佈")
165
+
166
+ # 模擬模型使用數據
167
+ model_usage = {
168
+ "claude-3-5-sonnet": 35,
169
+ "gpt-4": 25,
170
+ "gemini-1.5-pro": 20,
171
+ "gpt-3.5-turbo": 15,
172
+ "其他": 5
173
+ }
174
+
175
+ fig_pie = px.pie(
176
+ values=list(model_usage.values()),
177
+ names=list(model_usage.keys()),
178
+ title="模型使用比例",
179
+ hole=0.4
180
+ )
181
+ fig_pie.update_traces(textposition='inside', textinfo='percent+label')
182
+
183
+ st.plotly_chart(fig_pie, use_container_width=True)
184
+
185
+ st.markdown("---")
186
+
187
+ # ============================================
188
+ # 提供者狀態
189
+ # ============================================
190
+ st.header("🔌 提供者狀態")
191
+
192
+ providers_status = [
193
+ {"name": "Claude", "status": "active", "requests": 1250, "avg_latency": "1.2s"},
194
+ {"name": "OpenAI", "status": "active", "requests": 980, "avg_latency": "0.8s"},
195
+ {"name": "Gemini", "status": "active", "requests": 650, "avg_latency": "1.5s"},
196
+ {"name": "Kiro", "status": "cooldown", "requests": 320, "avg_latency": "2.1s"},
197
+ {"name": "Copilot", "status": "active", "requests": 180, "avg_latency": "0.9s"},
198
+ ]
199
+
200
+ df_providers = pd.DataFrame(providers_status)
201
+
202
+ # 狀態著色
203
+ def color_status(val):
204
+ if val == "active":
205
+ return 'background-color: #d4edda; color: #155724'
206
+ elif val == "cooldown":
207
+ return 'background-color: #fff3cd; color: #856404'
208
+ return ''
209
+
210
+ st.dataframe(
211
+ df_providers.style.applymap(color_status, subset=['status']),
212
+ use_container_width=True,
213
+ hide_index=True
214
+ )
215
+
216
+ st.markdown("---")
217
+
218
+ # ============================================
219
+ # 錯誤日誌
220
+ # ============================================
221
+ st.header("⚠️ 最近錯誤")
222
+
223
+ errors_data = [
224
+ {"time": "2024-01-15 10:23:45", "provider": "Kiro", "error": "Rate limit exceeded", "count": 3},
225
+ {"time": "2024-01-15 09:15:22", "provider": "Claude", "error": "Token expired", "count": 1},
226
+ {"time": "2024-01-14 18:45:10", "provider": "OpenAI", "error": "Connection timeout", "count": 2},
227
+ ]
228
+
229
+ if errors_data:
230
+ df_errors = pd.DataFrame(errors_data)
231
+ st.dataframe(df_errors, use_container_width=True, hide_index=True)
232
+ else:
233
+ st.success("✅ 沒有最近的錯誤")
234
+
235
+ st.markdown("---")
236
+
237
+ # ============================================
238
+ # 配額監控
239
+ # ============================================
240
+ st.header("📦 配額監控")
241
+
242
+ col1, col2 = st.columns(2)
243
+
244
+ with col1:
245
+ st.subheader("Claude 配額")
246
+ claude_used = 75
247
+ st.progress(claude_used / 100)
248
+ st.caption(f"已使用 {claude_used}%")
249
+
250
+ with col2:
251
+ st.subheader("OpenAI 配額")
252
+ openai_used = 45
253
+ st.progress(openai_used / 100)
254
+ st.caption(f"已使用 {openai_used}%")
255
+
256
+ # ============================================
257
+ # 刷新按鈕
258
+ # ============================================
259
+ st.markdown("---")
260
+ if st.button("🔄 刷新統計數據"):
261
+ st.rerun()
262
+
263
+ st.caption(f"最後更新: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
streamlit_app/pages/4_⚙️_Settings.py ADDED
@@ -0,0 +1,350 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Settings Page - 系統設定
3
+ """
4
+
5
+ import streamlit as st
6
+ import httpx
7
+ import os
8
+ import yaml
9
+ import json
10
+
11
+ # ============================================
12
+ # 頁面配置
13
+ # ============================================
14
+ st.set_page_config(
15
+ page_title="Settings - CLI Proxy API",
16
+ page_icon="⚙️",
17
+ layout="wide"
18
+ )
19
+
20
+ # ============================================
21
+ # 配置
22
+ # ============================================
23
+ API_BASE_URL = os.getenv("API_BASE_URL", "http://localhost:8317")
24
+
25
+ # ============================================
26
+ # 輔助函數
27
+ # ============================================
28
+ def get_config():
29
+ """獲取當前配置"""
30
+ try:
31
+ response = httpx.get(
32
+ f"{API_BASE_URL}/v0/management/config",
33
+ timeout=10.0
34
+ )
35
+ if response.status_code == 200:
36
+ return response.json()
37
+ return None
38
+ except:
39
+ return None
40
+
41
+ def get_server_info():
42
+ """獲取服務器信息"""
43
+ try:
44
+ response = httpx.get(
45
+ f"{API_BASE_URL}/v0/management/info",
46
+ timeout=10.0
47
+ )
48
+ if response.status_code == 200:
49
+ return response.json()
50
+ return None
51
+ except:
52
+ return None
53
+
54
+ # ============================================
55
+ # 主頁面
56
+ # ============================================
57
+ st.title("⚙️ 系統設定")
58
+ st.markdown("配置 CLI Proxy API 服務器")
59
+
60
+ # ============================================
61
+ # 服務器信息
62
+ # ============================================
63
+ st.header("🖥️ 服務器信息")
64
+
65
+ server_info = get_server_info()
66
+
67
+ if server_info:
68
+ col1, col2, col3 = st.columns(3)
69
+
70
+ with col1:
71
+ st.metric("版本", server_info.get("version", "N/A"))
72
+
73
+ with col2:
74
+ st.metric("運行時間", server_info.get("uptime", "N/A"))
75
+
76
+ with col3:
77
+ st.metric("Go 版本", server_info.get("go_version", "N/A"))
78
+
79
+ with st.expander("📋 詳細信息"):
80
+ st.json(server_info)
81
+ else:
82
+ st.info("無法獲取服務器信息")
83
+
84
+ st.markdown("---")
85
+
86
+ # ============================================
87
+ # 基本配置
88
+ # ============================================
89
+ st.header("🔧 基本配置")
90
+
91
+ config = get_config()
92
+
93
+ # 服務器設置
94
+ with st.expander("🌐 服務器設置", expanded=True):
95
+ col1, col2 = st.columns(2)
96
+
97
+ with col1:
98
+ host = st.text_input(
99
+ "監聽地址",
100
+ value=config.get("host", "0.0.0.0") if config else "0.0.0.0",
101
+ help="服務器監聽的網絡地址"
102
+ )
103
+
104
+ with col2:
105
+ port = st.number_input(
106
+ "監聽端口",
107
+ value=config.get("port", 8317) if config else 8317,
108
+ min_value=1,
109
+ max_value=65535,
110
+ help="服務器監聽的端口"
111
+ )
112
+
113
+ debug_mode = st.checkbox(
114
+ "調試模式",
115
+ value=config.get("debug", False) if config else False,
116
+ help="啟用詳細日誌輸出"
117
+ )
118
+
119
+ # API Keys 設置
120
+ with st.expander("🔑 API Keys"):
121
+ st.markdown("""
122
+ 配置訪問 API 所需的密鑰。這些密鑰用於驗證客戶端請求。
123
+ """)
124
+
125
+ # 顯示當前 API Keys(脫敏)
126
+ current_keys = config.get("api-keys", []) if config else []
127
+
128
+ if current_keys:
129
+ st.subheader("當前 API Keys")
130
+ for i, key in enumerate(current_keys):
131
+ masked_key = key[:8] + "****" + key[-4:] if len(key) > 12 else "****"
132
+ st.text(f"Key {i+1}: {masked_key}")
133
+
134
+ st.markdown("---")
135
+
136
+ # 添加新 API Key
137
+ st.subheader("添加新 API Key")
138
+ new_key = st.text_input(
139
+ "新 API Key",
140
+ type="password",
141
+ help="輸入新的 API Key"
142
+ )
143
+
144
+ if st.button("添加 API Key"):
145
+ if new_key:
146
+ st.success("✅ API Key 已添加(需要重啟服務生效)")
147
+ else:
148
+ st.error("請輸入 API Key")
149
+
150
+ # 路由設置
151
+ with st.expander("🔀 路由設置"):
152
+ col1, col2 = st.columns(2)
153
+
154
+ with col1:
155
+ routing_strategy = st.selectbox(
156
+ "路由策略",
157
+ ["round-robin", "fill-first"],
158
+ index=0 if not config or config.get("routing", {}).get("strategy", "round-robin") == "round-robin" else 1,
159
+ help="選擇憑證選擇策略"
160
+ )
161
+
162
+ with col2:
163
+ force_prefix = st.checkbox(
164
+ "強制模型前綴",
165
+ value=config.get("force-model-prefix", False) if config else False,
166
+ help="強制使用模型前綴匹配憑證"
167
+ )
168
+
169
+ # 重試設置
170
+ with st.expander("🔄 重試設置"):
171
+ col1, col2, col3 = st.columns(3)
172
+
173
+ with col1:
174
+ retry_count = st.number_input(
175
+ "重試次數",
176
+ value=config.get("request-retry", 3) if config else 3,
177
+ min_value=0,
178
+ max_value=10,
179
+ help="請求失敗時的重試次數"
180
+ )
181
+
182
+ with col2:
183
+ max_credentials = st.number_input(
184
+ "最大嘗試憑證數",
185
+ value=config.get("max-retry-credentials", 0) if config else 0,
186
+ min_value=0,
187
+ help="0 表示嘗試所有可用憑證"
188
+ )
189
+
190
+ with col3:
191
+ retry_interval = st.number_input(
192
+ "重試間隔 (秒)",
193
+ value=config.get("max-retry-interval", 30) if config else 30,
194
+ min_value=1,
195
+ max_value=300,
196
+ help="重試之間的最大等待時間"
197
+ )
198
+
199
+ st.markdown("---")
200
+
201
+ # ============================================
202
+ # 提供者配置
203
+ # ============================================
204
+ st.header("🤖 提供者配置")
205
+
206
+ # Claude 配置
207
+ with st.expander("🟠 Claude (Anthropic)"):
208
+ st.markdown("配置 Claude API 認證")
209
+
210
+ claude_token = st.text_input(
211
+ "Claude OAuth Token",
212
+ type="password",
213
+ help="從 Anthropic 獲取的 OAuth Token"
214
+ )
215
+
216
+ if st.button("保存 Claude 配置"):
217
+ st.success("✅ Claude 配置已保存")
218
+
219
+ # OpenAI 配置
220
+ with st.expander("🟢 OpenAI"):
221
+ st.markdown("配置 OpenAI API Key")
222
+
223
+ openai_key = st.text_input(
224
+ "OpenAI API Key",
225
+ type="password",
226
+ help="從 OpenAI 獲取的 API Key"
227
+ )
228
+
229
+ openai_org = st.text_input(
230
+ "組織 ID (可選)",
231
+ help="OpenAI 組織 ID"
232
+ )
233
+
234
+ if st.button("保存 OpenAI 配置"):
235
+ st.success("✅ OpenAI 配置已保存")
236
+
237
+ # Gemini 配置
238
+ with st.expander("🔵 Gemini"):
239
+ st.markdown("配置 Google Gemini API Key")
240
+
241
+ gemini_key = st.text_input(
242
+ "Gemini API Key",
243
+ type="password",
244
+ help="從 Google AI Studio 獲取的 API Key"
245
+ )
246
+
247
+ if st.button("保存 Gemini 配置"):
248
+ st.success("✅ Gemini 配置已保存")
249
+
250
+ # Kiro 配置
251
+ with st.expander("🟡 Kiro (AWS CodeWhisperer)"):
252
+ st.markdown("配置 Kiro 認證")
253
+
254
+ kiro_config = st.selectbox(
255
+ "認證方式",
256
+ ["OAuth", "AWS Builder ID", "AWS IAM", "Import Token"]
257
+ )
258
+
259
+ if kiro_config == "Import Token":
260
+ kiro_token = st.text_area(
261
+ "貼上 Kiro Token JSON",
262
+ height=100,
263
+ help="從 ~/.kiro/kiro-auth-token.json 複製"
264
+ )
265
+
266
+ if st.button("保存 Kiro 配置"):
267
+ st.success("✅ Kiro 配置已保存")
268
+
269
+ st.markdown("---")
270
+
271
+ # ============================================
272
+ # 高級設置
273
+ # ============================================
274
+ st.header("⚡ 高級設置")
275
+
276
+ with st.expander("🔧 高級選項"):
277
+ col1, col2 = st.columns(2)
278
+
279
+ with col1:
280
+ commercial_mode = st.checkbox(
281
+ "商業模式",
282
+ value=config.get("commercial-mode", False) if config else False,
283
+ help="禁用高開銷中間件以減少內存使用"
284
+ )
285
+
286
+ with col2:
287
+ usage_stats = st.checkbox(
288
+ "使用統計",
289
+ value=config.get("usage-statistics-enabled", False) if config else False,
290
+ help="啟用內存使用統計聚合"
291
+ )
292
+
293
+ proxy_url = st.text_input(
294
+ "代理 URL",
295
+ value=config.get("proxy-url", "") if config else "",
296
+ placeholder="socks5://user:pass@192.168.1.1:1080/",
297
+ help="HTTP/HTTPS/SOCKS5 代理"
298
+ )
299
+
300
+ st.markdown("---")
301
+
302
+ # ============================================
303
+ # 配置導出
304
+ # ============================================
305
+ st.header("📤 配置管理")
306
+
307
+ col1, col2, col3 = st.columns(3)
308
+
309
+ with col1:
310
+ if st.button("📥 導出配置"):
311
+ if config:
312
+ config_yaml = yaml.dump(config, default_flow_style=False, allow_unicode=True)
313
+ st.download_button(
314
+ label="下載 config.yaml",
315
+ data=config_yaml,
316
+ file_name="config.yaml",
317
+ mime="text/yaml"
318
+ )
319
+ else:
320
+ st.error("無法獲取配置")
321
+
322
+ with col2:
323
+ if st.button("🔄 重載配置"):
324
+ st.success("✅ 配置已重新加載")
325
+
326
+ with col3:
327
+ if st.button("⚠️ 重置為默認"):
328
+ st.warning("確定要重置所有配置嗎?此操作不可恢復。")
329
+ if st.button("確認重置"):
330
+ st.success("✅ 配置已重置為默認值")
331
+
332
+ # ============================================
333
+ # 環境變數
334
+ # ============================================
335
+ st.markdown("---")
336
+ st.header("🔐 環境變數")
337
+
338
+ st.markdown("""
339
+ 以下環境變數可以在 Hugging Face Spaces 的 Settings > Secrets 中配置:
340
+
341
+ | 變數名 | 說明 |
342
+ |--------|------|
343
+ | `API_KEYS` | API 訪問密鑰(JSON 數組) |
344
+ | `MANAGEMENT_KEY` | 管理介面密鑰 |
345
+ | `CLAUDE_TOKEN` | Claude OAuth Token |
346
+ | `OPENAI_API_KEY` | OpenAI API Key |
347
+ | `GEMINI_API_KEY` | Gemini API Key |
348
+ """)
349
+
350
+ st.info("💡 修改配置後需要重啟服務才能生效")
supervisord.conf ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ; ============================================
2
+ ; Supervisord Configuration for Hugging Face Spaces
3
+ ; Manages both Go API Server and Streamlit Frontend
4
+ ; ============================================
5
+
6
+ [supervisord]
7
+ nodaemon=true
8
+ user=root
9
+ logfile=/var/log/supervisor/supervisord.log
10
+ pidfile=/var/run/supervisord.pid
11
+ loglevel=info
12
+ logfile_maxbytes=10MB
13
+ logfile_backups=3
14
+
15
+ ; ============================================
16
+ ; Go API Server - CLIProxyAPI Plus
17
+ ; ============================================
18
+ [program:go-server]
19
+ command=/app/go-server/CLIProxyAPIPlus
20
+ directory=/app/go-server
21
+ autostart=true
22
+ autorestart=true
23
+ startsecs=10
24
+ stopwaitsecs=30
25
+ startretries=3
26
+ stopasgroup=true
27
+ killasgroup=true
28
+ stdout_logfile=/var/log/supervisor/go-server.log
29
+ stdout_logfile_maxbytes=10MB
30
+ stdout_logfile_backups=3
31
+ stderr_logfile=/var/log/supervisor/go-server-error.log
32
+ stderr_logfile_maxbytes=10MB
33
+ stderr_logfile_backups=3
34
+ environment=HOME="/root",DEPLOY="true"
35
+
36
+ ; ============================================
37
+ ; Streamlit Frontend
38
+ ; ============================================
39
+ [program:streamlit]
40
+ command=streamlit run /app/streamlit/app.py --server.port=7860 --server.address=0.0.0.0
41
+ directory=/app/streamlit
42
+ autostart=true
43
+ autorestart=true
44
+ startsecs=5
45
+ stopwaitsecs=10
46
+ startretries=3
47
+ stopasgroup=true
48
+ killasgroup=true
49
+ stdout_logfile=/var/log/supervisor/streamlit.log
50
+ stdout_logfile_maxbytes=10MB
51
+ stdout_logfile_backups=3
52
+ stderr_logfile=/var/log/supervisor/streamlit-error.log
53
+ stderr_logfile_maxbytes=10MB
54
+ stderr_logfile_backups=3
55
+ environment=HOME="/root",STREAMLIT_SERVER_PORT="7860",STREAMLIT_SERVER_ADDRESS="0.0.0.0",STREAMLIT_SERVER_HEADLESS="true"
56
+
57
+ ; ============================================
58
+ ; Event Listeners
59
+ ; ============================================
60
+ [eventlistener:process_exit]
61
+ command=bash -c "echo 'Process exited'"
62
+ events=PROCESS_STATE_EXITED
63
+ buffer_size=100