Improve HF wrapper landing and proxy docs
Browse files- .env.example +50 -0
- Dockerfile +9 -2
- README.md +20 -2
- entrypoint.sh +34 -0
- nginx.conf +55 -0
- static/index.html +410 -0
.env.example
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ── Server ───────────────────────────────────────────────────────────────────
|
| 2 |
+
# PORT=8080
|
| 3 |
+
# Hugging Face Spaces: the wrapper listens on :7860 and proxies to the upstream app.
|
| 4 |
+
# Minimum HF Secrets: CLAUDE_SESSION_KEYS, CLAUDE_API_KEY, ADMIN_PASS.
|
| 5 |
+
# DB/Redis are not required for HF Simple mode.
|
| 6 |
+
|
| 7 |
+
# ── Mode ─────────────────────────────────────────────────────────────────────
|
| 8 |
+
# Simple mode: set CLAUDE_SESSION_KEYS to skip PostgreSQL entirely.
|
| 9 |
+
# Separate multiple keys with commas.
|
| 10 |
+
# CLAUDE_SESSION_KEYS=sk-ant-sid01-xxx,sk-ant-sid01-yyy
|
| 11 |
+
|
| 12 |
+
# ── PostgreSQL ────────────────────────────────────────────────────────────────
|
| 13 |
+
# Used by both the claude2api service and the postgres container.
|
| 14 |
+
DB_HOST=postgres
|
| 15 |
+
DB_PORT=5432
|
| 16 |
+
DB_USER=claude2api
|
| 17 |
+
DB_PASS=change_me_please # ← REQUIRED: set a strong password
|
| 18 |
+
DB_NAME=claude2api
|
| 19 |
+
|
| 20 |
+
# ── Redis ─────────────────────────────────────────────────────────────────────
|
| 21 |
+
# REDIS_PASS must match the password set in docker-compose.yml (redis container).
|
| 22 |
+
REDIS_PASS=redis_secret # ← REQUIRED: set a strong password
|
| 23 |
+
REDIS_URL=redis://:redis_secret@redis:6379/0 # keep in sync with REDIS_PASS
|
| 24 |
+
|
| 25 |
+
# ── Residential Proxy (leave empty to connect directly) ───────────────────────
|
| 26 |
+
PROXY_HOST=
|
| 27 |
+
PROXY_PORT=3010
|
| 28 |
+
PROXY_USER=
|
| 29 |
+
PROXY_PASS=
|
| 30 |
+
PROXY_REGION=US
|
| 31 |
+
|
| 32 |
+
# ── Admin Dashboard ───────────────────────────────────────────────────────────
|
| 33 |
+
# ADMIN_PASS is a startup input. Even if upstream runtime lets you change some settings
|
| 34 |
+
# in the admin UI, that does not mean Hugging Face Secrets or this file were updated.
|
| 35 |
+
# Verified against the current upstream image in Simple mode: API key changes can take
|
| 36 |
+
# effect in-process, but restart falls back to env/Secrets, and admin password is not
|
| 37 |
+
# currently exposed here as a durable runtime setting.
|
| 38 |
+
ADMIN_USER=admin
|
| 39 |
+
ADMIN_PASS=change_me_please # ← REQUIRED: change before going live
|
| 40 |
+
|
| 41 |
+
# ── Rate Limiting ─────────────────────────────────────────────────────────────
|
| 42 |
+
CLAUDE_DAILY_LIMIT=0 # 0 = unlimited
|
| 43 |
+
MAX_RETRIES=3
|
| 44 |
+
COOLDOWN_MINUTES=5
|
| 45 |
+
|
| 46 |
+
# ── API Key Protection (optional) ─────────────────────────────────────────────
|
| 47 |
+
# Startup default for protected API routes. If changed from the upstream admin UI,
|
| 48 |
+
# the new value may apply only to the current running process unless upstream-backed
|
| 49 |
+
# persistence is configured and verified.
|
| 50 |
+
# CLAUDE_API_KEY=sk-your-api-key
|
Dockerfile
CHANGED
|
@@ -1,5 +1,12 @@
|
|
| 1 |
-
FROM pushzx/claude2api:latest
|
| 2 |
|
| 3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
EXPOSE 7860
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM pushzx/claude2api:latest AS upstream
|
| 2 |
|
| 3 |
+
FROM nginx:1.27-alpine
|
| 4 |
+
|
| 5 |
+
COPY --from=upstream /claude2api /usr/local/bin/claude2api
|
| 6 |
+
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
| 7 |
+
COPY static/index.html /usr/share/nginx/html/index.html
|
| 8 |
+
COPY entrypoint.sh /entrypoint.sh
|
| 9 |
|
| 10 |
EXPOSE 7860
|
| 11 |
+
|
| 12 |
+
ENTRYPOINT ["/bin/sh", "/entrypoint.sh"]
|
README.md
CHANGED
|
@@ -14,6 +14,16 @@ Claude.ai Web → Anthropic API 兼容代理。将 Claude.ai 账号会话转换
|
|
| 14 |
|
| 15 |
## Hugging Face Spaces
|
| 16 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
### 推荐模式
|
| 18 |
|
| 19 |
Hugging Face Spaces 推荐先使用 **Simple 模式**。Spaces 只运行单容器,当前仓库中的 `postgres`、`redis`、`watchtower` 仅适用于 `docker-compose.yml` 的本地 / VPS 部署,不直接用于 HF。
|
|
@@ -26,9 +36,10 @@ Hugging Face Spaces 推荐先使用 **Simple 模式**。Spaces 只运行单容
|
|
| 26 |
|
| 27 |
### 推荐 Variables
|
| 28 |
|
| 29 |
-
- `LISTEN_ADDR=:7860`
|
| 30 |
- `ADMIN_USER=admin`
|
| 31 |
|
|
|
|
|
|
|
| 32 |
### 可选 Secrets
|
| 33 |
|
| 34 |
- `PROXY_HOST`
|
|
@@ -47,12 +58,17 @@ Hugging Face Spaces 推荐先使用 **Simple 模式**。Spaces 只运行单容
|
|
| 47 |
|
| 48 |
- `config.yaml` 不参与此服务配置,运行时参数全部来自环境变量
|
| 49 |
- 若仅提供 `CLAUDE_SESSION_KEYS`,可跳过 PostgreSQL / Redis
|
| 50 |
-
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
- 对外开放时,建议始终设置 `CLAUDE_API_KEY`
|
| 52 |
|
| 53 |
### 部署后验证
|
| 54 |
|
| 55 |
```bash
|
|
|
|
| 56 |
curl https://<space>.hf.space/health
|
| 57 |
|
| 58 |
curl https://<space>.hf.space/v1/messages \
|
|
@@ -65,6 +81,8 @@ curl https://<space>.hf.space/v1/messages \
|
|
| 65 |
}'
|
| 66 |
```
|
| 67 |
|
|
|
|
|
|
|
| 68 |
---
|
| 69 |
|
| 70 |
## 快速部署
|
|
|
|
| 14 |
|
| 15 |
## Hugging Face Spaces
|
| 16 |
|
| 17 |
+
### 当前包装层职责
|
| 18 |
+
|
| 19 |
+
当前 `Dockerfile` 不再直接把 `pushzx/claude2api:latest` 暴露到 `:7860`,而是改成了:
|
| 20 |
+
|
| 21 |
+
- `/`:wrapper 提供的静态 landing page
|
| 22 |
+
- 除 `/`、`/index.html`、`/favicon.ico` 外的其他路径:反向代理到上游 `claude2api`
|
| 23 |
+
- 容器对外固定监听 `:7860`,上游进程在容器内监听 `127.0.0.1:8080`
|
| 24 |
+
|
| 25 |
+
这也是旧版根路径看起来像 `404 page not found` 的原因:之前只是原样暴露上游镜像,而上游本身没有自定义 `/` 首页。
|
| 26 |
+
|
| 27 |
### 推荐模式
|
| 28 |
|
| 29 |
Hugging Face Spaces 推荐先使用 **Simple 模式**。Spaces 只运行单容器,当前仓库中的 `postgres`、`redis`、`watchtower` 仅适用于 `docker-compose.yml` 的本地 / VPS 部署,不直接用于 HF。
|
|
|
|
| 36 |
|
| 37 |
### 推荐 Variables
|
| 38 |
|
|
|
|
| 39 |
- `ADMIN_USER=admin`
|
| 40 |
|
| 41 |
+
通常不需要为 HF Space 手动设置 `LISTEN_ADDR=:7860`;`7860` 现在由 wrapper 前置层负责,上游在容器内固定监听 `127.0.0.1:8080`。
|
| 42 |
+
|
| 43 |
### 可选 Secrets
|
| 44 |
|
| 45 |
- `PROXY_HOST`
|
|
|
|
| 58 |
|
| 59 |
- `config.yaml` 不参与此服务配置,运行时参数全部来自环境变量
|
| 60 |
- 若仅提供 `CLAUDE_SESSION_KEYS`,可跳过 PostgreSQL / Redis
|
| 61 |
+
- Private Space 的 Hugging Face 访问控制,与应用自身的 `CLAUDE_API_KEY` 是两层独立鉴权
|
| 62 |
+
- HF Secrets 是启动时输入,不会被管理页面反向写回
|
| 63 |
+
- 若需要管理面板持久化能力,应改用上游支持的外部 PostgreSQL / Redis,而不是复用 compose sidecar
|
| 64 |
+
- 本地针对 `pushzx/claude2api:latest` 的 Simple 模式验证结果:管理接口可在当前进程内更新 `api_key`,但当前 UI / API 没有生效地修改 `ADMIN_PASS`;容器重启后 `api_key` 会回退到环境变量值
|
| 65 |
+
- 因此“持久化配置优先于 HF Secrets / env”不能由 wrapper 保证,只有在上游运行时验证成立后才能当作正式能力描述
|
| 66 |
- 对外开放时,建议始终设置 `CLAUDE_API_KEY`
|
| 67 |
|
| 68 |
### 部署后验证
|
| 69 |
|
| 70 |
```bash
|
| 71 |
+
curl https://<space>.hf.space/
|
| 72 |
curl https://<space>.hf.space/health
|
| 73 |
|
| 74 |
curl https://<space>.hf.space/v1/messages \
|
|
|
|
| 81 |
}'
|
| 82 |
```
|
| 83 |
|
| 84 |
+
如果 Space 设为 private,先确认请求方已经通过 Hugging Face 侧访问控制;随后再按上游应用的 `CLAUDE_API_KEY` 语义访问 API。
|
| 85 |
+
|
| 86 |
---
|
| 87 |
|
| 88 |
## 快速部署
|
entrypoint.sh
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/sh
|
| 2 |
+
set -eu
|
| 3 |
+
|
| 4 |
+
nginx -t
|
| 5 |
+
|
| 6 |
+
LISTEN_ADDR="127.0.0.1:8080" /usr/local/bin/claude2api &
|
| 7 |
+
upstream_pid=$!
|
| 8 |
+
|
| 9 |
+
nginx -g 'daemon off;' &
|
| 10 |
+
nginx_pid=$!
|
| 11 |
+
|
| 12 |
+
terminate() {
|
| 13 |
+
kill -TERM "$upstream_pid" "$nginx_pid" 2>/dev/null || true
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
trap terminate INT TERM
|
| 17 |
+
|
| 18 |
+
while :; do
|
| 19 |
+
if ! kill -0 "$upstream_pid" 2>/dev/null; then
|
| 20 |
+
wait "$upstream_pid" || true
|
| 21 |
+
kill -TERM "$nginx_pid" 2>/dev/null || true
|
| 22 |
+
wait "$nginx_pid" || true
|
| 23 |
+
exit 1
|
| 24 |
+
fi
|
| 25 |
+
|
| 26 |
+
if ! kill -0 "$nginx_pid" 2>/dev/null; then
|
| 27 |
+
wait "$nginx_pid" || true
|
| 28 |
+
kill -TERM "$upstream_pid" 2>/dev/null || true
|
| 29 |
+
wait "$upstream_pid" || true
|
| 30 |
+
exit 1
|
| 31 |
+
fi
|
| 32 |
+
|
| 33 |
+
sleep 1
|
| 34 |
+
done
|
nginx.conf
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
upstream claude2api_upstream {
|
| 2 |
+
server 127.0.0.1:8080;
|
| 3 |
+
keepalive 32;
|
| 4 |
+
}
|
| 5 |
+
|
| 6 |
+
server {
|
| 7 |
+
listen 7860;
|
| 8 |
+
listen [::]:7860;
|
| 9 |
+
server_name _;
|
| 10 |
+
|
| 11 |
+
charset utf-8;
|
| 12 |
+
client_max_body_size 50m;
|
| 13 |
+
|
| 14 |
+
location = / {
|
| 15 |
+
root /usr/share/nginx/html;
|
| 16 |
+
try_files /index.html =404;
|
| 17 |
+
add_header Cache-Control "no-store";
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
location = /index.html {
|
| 21 |
+
root /usr/share/nginx/html;
|
| 22 |
+
add_header Cache-Control "no-store";
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
location = /favicon.ico {
|
| 26 |
+
return 204;
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
location /v1/messages {
|
| 30 |
+
proxy_pass http://claude2api_upstream;
|
| 31 |
+
proxy_http_version 1.1;
|
| 32 |
+
proxy_buffering off;
|
| 33 |
+
proxy_read_timeout 3600s;
|
| 34 |
+
proxy_send_timeout 3600s;
|
| 35 |
+
proxy_set_header Host $host;
|
| 36 |
+
proxy_set_header X-Real-IP $remote_addr;
|
| 37 |
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
| 38 |
+
proxy_set_header X-Forwarded-Host $host;
|
| 39 |
+
proxy_set_header X-Forwarded-Proto $scheme;
|
| 40 |
+
proxy_set_header Connection "";
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
location / {
|
| 44 |
+
proxy_pass http://claude2api_upstream;
|
| 45 |
+
proxy_http_version 1.1;
|
| 46 |
+
proxy_read_timeout 3600s;
|
| 47 |
+
proxy_send_timeout 3600s;
|
| 48 |
+
proxy_set_header Host $host;
|
| 49 |
+
proxy_set_header X-Real-IP $remote_addr;
|
| 50 |
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
| 51 |
+
proxy_set_header X-Forwarded-Host $host;
|
| 52 |
+
proxy_set_header X-Forwarded-Proto $scheme;
|
| 53 |
+
proxy_set_header Connection "";
|
| 54 |
+
}
|
| 55 |
+
}
|
static/index.html
ADDED
|
@@ -0,0 +1,410 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8" />
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
| 6 |
+
<title>Claude2API</title>
|
| 7 |
+
<style>
|
| 8 |
+
:root {
|
| 9 |
+
--bg: #0b0908;
|
| 10 |
+
--bg-soft: #151210;
|
| 11 |
+
--panel: rgba(24, 20, 18, 0.82);
|
| 12 |
+
--panel-strong: rgba(34, 28, 25, 0.92);
|
| 13 |
+
--line: rgba(241, 231, 220, 0.11);
|
| 14 |
+
--line-strong: rgba(241, 231, 220, 0.2);
|
| 15 |
+
--text: #f1e7dc;
|
| 16 |
+
--muted: #b7a898;
|
| 17 |
+
--accent: #e3c9a5;
|
| 18 |
+
--accent-strong: #f3e0c4;
|
| 19 |
+
--shadow: 0 28px 84px rgba(0, 0, 0, 0.42);
|
| 20 |
+
--radius: 30px;
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
* {
|
| 24 |
+
box-sizing: border-box;
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
html {
|
| 28 |
+
color-scheme: dark;
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
body {
|
| 32 |
+
margin: 0;
|
| 33 |
+
min-height: 100vh;
|
| 34 |
+
color: var(--text);
|
| 35 |
+
font-family: "Helvetica Neue", "Segoe UI", ui-sans-serif, sans-serif;
|
| 36 |
+
background:
|
| 37 |
+
radial-gradient(circle at 14% 12%, rgba(227, 201, 165, 0.14), transparent 24%),
|
| 38 |
+
radial-gradient(circle at 88% 10%, rgba(140, 98, 59, 0.18), transparent 18%),
|
| 39 |
+
linear-gradient(180deg, #15110f 0%, var(--bg-soft) 38%, var(--bg) 100%);
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
body::before {
|
| 43 |
+
content: "";
|
| 44 |
+
position: fixed;
|
| 45 |
+
inset: 0;
|
| 46 |
+
pointer-events: none;
|
| 47 |
+
opacity: 0.08;
|
| 48 |
+
background-image: linear-gradient(rgba(255, 255, 255, 0.03) 1px, transparent 1px);
|
| 49 |
+
background-size: 100% 3px;
|
| 50 |
+
mix-blend-mode: soft-light;
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
a {
|
| 54 |
+
color: inherit;
|
| 55 |
+
text-decoration: none;
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
code,
|
| 59 |
+
pre {
|
| 60 |
+
font-family: "JetBrains Mono", "SFMono-Regular", ui-monospace, monospace;
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
h1,
|
| 64 |
+
h2 {
|
| 65 |
+
font-family: "Baskerville", "Times New Roman", Georgia, serif;
|
| 66 |
+
letter-spacing: -0.04em;
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
.page {
|
| 70 |
+
width: min(1080px, calc(100vw - 28px));
|
| 71 |
+
margin: 0 auto;
|
| 72 |
+
padding: 22px 0 34px;
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
.frame {
|
| 76 |
+
position: relative;
|
| 77 |
+
overflow: hidden;
|
| 78 |
+
border: 1px solid var(--line);
|
| 79 |
+
border-radius: var(--radius);
|
| 80 |
+
background: linear-gradient(180deg, rgba(35, 29, 25, 0.94), rgba(19, 16, 14, 0.93));
|
| 81 |
+
box-shadow: var(--shadow);
|
| 82 |
+
backdrop-filter: blur(18px);
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
.frame::after {
|
| 86 |
+
content: "";
|
| 87 |
+
position: absolute;
|
| 88 |
+
inset: auto -10% 0;
|
| 89 |
+
height: 1px;
|
| 90 |
+
background: linear-gradient(90deg, transparent, rgba(243, 224, 196, 0.22), transparent);
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
.masthead {
|
| 94 |
+
display: grid;
|
| 95 |
+
grid-template-columns: minmax(0, 1.2fr) minmax(280px, 0.8fr);
|
| 96 |
+
gap: 22px;
|
| 97 |
+
padding: 32px;
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
.tag {
|
| 101 |
+
display: inline-flex;
|
| 102 |
+
align-items: center;
|
| 103 |
+
padding: 8px 14px;
|
| 104 |
+
border-radius: 999px;
|
| 105 |
+
border: 1px solid rgba(227, 201, 165, 0.16);
|
| 106 |
+
background: rgba(227, 201, 165, 0.08);
|
| 107 |
+
color: var(--accent);
|
| 108 |
+
font-size: 11px;
|
| 109 |
+
font-weight: 700;
|
| 110 |
+
letter-spacing: 0.18em;
|
| 111 |
+
text-transform: uppercase;
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
h1 {
|
| 115 |
+
margin: 18px 0 12px;
|
| 116 |
+
font-size: clamp(3rem, 8vw, 5.6rem);
|
| 117 |
+
line-height: 0.92;
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
.lede {
|
| 121 |
+
max-width: 680px;
|
| 122 |
+
margin: 0;
|
| 123 |
+
color: var(--muted);
|
| 124 |
+
font-size: 1.03rem;
|
| 125 |
+
line-height: 1.86;
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
.actions {
|
| 129 |
+
display: flex;
|
| 130 |
+
flex-wrap: wrap;
|
| 131 |
+
gap: 12px;
|
| 132 |
+
margin-top: 28px;
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
.button {
|
| 136 |
+
display: inline-flex;
|
| 137 |
+
align-items: center;
|
| 138 |
+
justify-content: center;
|
| 139 |
+
min-width: 150px;
|
| 140 |
+
min-height: 46px;
|
| 141 |
+
padding: 0 18px;
|
| 142 |
+
border-radius: 999px;
|
| 143 |
+
border: 1px solid var(--line);
|
| 144 |
+
background: rgba(255, 255, 255, 0.04);
|
| 145 |
+
color: var(--text);
|
| 146 |
+
font-size: 14px;
|
| 147 |
+
font-weight: 600;
|
| 148 |
+
transition:
|
| 149 |
+
transform 0.16s ease,
|
| 150 |
+
border-color 0.16s ease,
|
| 151 |
+
background 0.16s ease;
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
.button:hover {
|
| 155 |
+
transform: translateY(-1px);
|
| 156 |
+
border-color: var(--line-strong);
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
.button.primary {
|
| 160 |
+
border-color: transparent;
|
| 161 |
+
background: linear-gradient(135deg, var(--accent-strong), var(--accent));
|
| 162 |
+
color: #1a140f;
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
.rail {
|
| 166 |
+
display: grid;
|
| 167 |
+
gap: 12px;
|
| 168 |
+
align-content: start;
|
| 169 |
+
}
|
| 170 |
+
|
| 171 |
+
.glance {
|
| 172 |
+
padding: 18px;
|
| 173 |
+
border-radius: 22px;
|
| 174 |
+
border: 1px solid var(--line);
|
| 175 |
+
background: rgba(255, 255, 255, 0.03);
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
.glance span {
|
| 179 |
+
display: block;
|
| 180 |
+
margin-bottom: 8px;
|
| 181 |
+
color: var(--muted);
|
| 182 |
+
font-size: 11px;
|
| 183 |
+
font-weight: 700;
|
| 184 |
+
letter-spacing: 0.14em;
|
| 185 |
+
text-transform: uppercase;
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
.glance strong {
|
| 189 |
+
display: block;
|
| 190 |
+
font-size: 1rem;
|
| 191 |
+
line-height: 1.65;
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
.content {
|
| 195 |
+
display: grid;
|
| 196 |
+
gap: 18px;
|
| 197 |
+
margin-top: 18px;
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
.section {
|
| 201 |
+
padding: 26px 28px;
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
.section-head {
|
| 205 |
+
display: flex;
|
| 206 |
+
flex-wrap: wrap;
|
| 207 |
+
align-items: flex-end;
|
| 208 |
+
justify-content: space-between;
|
| 209 |
+
gap: 12px;
|
| 210 |
+
margin-bottom: 18px;
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
.section-head h2 {
|
| 214 |
+
margin: 0;
|
| 215 |
+
font-size: 2rem;
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
.section-head p,
|
| 219 |
+
.note,
|
| 220 |
+
.route p,
|
| 221 |
+
.aside p {
|
| 222 |
+
margin: 0;
|
| 223 |
+
color: var(--muted);
|
| 224 |
+
line-height: 1.78;
|
| 225 |
+
}
|
| 226 |
+
|
| 227 |
+
.routes {
|
| 228 |
+
display: grid;
|
| 229 |
+
grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
|
| 230 |
+
gap: 14px;
|
| 231 |
+
}
|
| 232 |
+
|
| 233 |
+
.route,
|
| 234 |
+
.aside {
|
| 235 |
+
padding: 18px;
|
| 236 |
+
border-radius: 22px;
|
| 237 |
+
border: 1px solid var(--line);
|
| 238 |
+
background: rgba(255, 255, 255, 0.03);
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
.route h3 {
|
| 242 |
+
margin: 0 0 10px;
|
| 243 |
+
font-size: 1.06rem;
|
| 244 |
+
}
|
| 245 |
+
|
| 246 |
+
.route code,
|
| 247 |
+
.aside code {
|
| 248 |
+
color: var(--accent-strong);
|
| 249 |
+
}
|
| 250 |
+
|
| 251 |
+
.split {
|
| 252 |
+
display: grid;
|
| 253 |
+
grid-template-columns: minmax(0, 1.08fr) minmax(0, 0.92fr);
|
| 254 |
+
gap: 18px;
|
| 255 |
+
}
|
| 256 |
+
|
| 257 |
+
.code {
|
| 258 |
+
margin: 0;
|
| 259 |
+
padding: 18px;
|
| 260 |
+
overflow: auto;
|
| 261 |
+
border-radius: 22px;
|
| 262 |
+
border: 1px solid var(--line);
|
| 263 |
+
background: #0e0a08;
|
| 264 |
+
color: #f6ecdf;
|
| 265 |
+
line-height: 1.72;
|
| 266 |
+
}
|
| 267 |
+
|
| 268 |
+
.aside-stack {
|
| 269 |
+
display: grid;
|
| 270 |
+
gap: 14px;
|
| 271 |
+
}
|
| 272 |
+
|
| 273 |
+
.note {
|
| 274 |
+
margin-top: 16px;
|
| 275 |
+
}
|
| 276 |
+
|
| 277 |
+
@media (max-width: 900px) {
|
| 278 |
+
.masthead,
|
| 279 |
+
.split {
|
| 280 |
+
grid-template-columns: 1fr;
|
| 281 |
+
}
|
| 282 |
+
}
|
| 283 |
+
|
| 284 |
+
@media (max-width: 720px) {
|
| 285 |
+
.page {
|
| 286 |
+
width: min(100vw - 18px, 1080px);
|
| 287 |
+
padding-top: 14px;
|
| 288 |
+
}
|
| 289 |
+
|
| 290 |
+
.masthead,
|
| 291 |
+
.section {
|
| 292 |
+
padding: 22px;
|
| 293 |
+
}
|
| 294 |
+
|
| 295 |
+
h1 {
|
| 296 |
+
font-size: clamp(2.6rem, 13vw, 4.2rem);
|
| 297 |
+
}
|
| 298 |
+
}
|
| 299 |
+
</style>
|
| 300 |
+
</head>
|
| 301 |
+
<body>
|
| 302 |
+
<main class="page">
|
| 303 |
+
<section class="frame masthead">
|
| 304 |
+
<div>
|
| 305 |
+
<div class="tag">Hugging Face wrapper</div>
|
| 306 |
+
<h1>Claude2API</h1>
|
| 307 |
+
<p class="lede">
|
| 308 |
+
Claude.ai sessions, surfaced as an Anthropic-compatible API. This root page exists to
|
| 309 |
+
make the Space legible at first glance; the actual admin dashboard, health probe, and
|
| 310 |
+
API behavior still come from the upstream <code>claude2api</code> runtime.
|
| 311 |
+
</p>
|
| 312 |
+
<div class="actions">
|
| 313 |
+
<a class="button primary" href="/admin">Open Admin</a>
|
| 314 |
+
<a class="button" href="/health">Health</a>
|
| 315 |
+
</div>
|
| 316 |
+
</div>
|
| 317 |
+
|
| 318 |
+
<aside class="rail">
|
| 319 |
+
<div class="glance">
|
| 320 |
+
<span>Root</span>
|
| 321 |
+
<strong>Static landing page served by the wrapper.</strong>
|
| 322 |
+
</div>
|
| 323 |
+
<div class="glance">
|
| 324 |
+
<span>Runtime</span>
|
| 325 |
+
<strong>All non-root routes pass through to the upstream service.</strong>
|
| 326 |
+
</div>
|
| 327 |
+
<div class="glance">
|
| 328 |
+
<span>Access</span>
|
| 329 |
+
<strong>Private Space auth and app-level API keys can both apply.</strong>
|
| 330 |
+
</div>
|
| 331 |
+
</aside>
|
| 332 |
+
</section>
|
| 333 |
+
|
| 334 |
+
<div class="content">
|
| 335 |
+
<section class="frame section">
|
| 336 |
+
<div class="section-head">
|
| 337 |
+
<div>
|
| 338 |
+
<h2>Where to go</h2>
|
| 339 |
+
</div>
|
| 340 |
+
<p>
|
| 341 |
+
The wrapper fixes the root experience only. It does not replace upstream routing,
|
| 342 |
+
persistence, or configuration semantics.
|
| 343 |
+
</p>
|
| 344 |
+
</div>
|
| 345 |
+
|
| 346 |
+
<div class="routes">
|
| 347 |
+
<article class="route">
|
| 348 |
+
<h3><code>/admin</code></h3>
|
| 349 |
+
<p>Upstream dashboard for accounts, model mapping, proxy settings, and API key changes.</p>
|
| 350 |
+
</article>
|
| 351 |
+
<article class="route">
|
| 352 |
+
<h3><code>/health</code></h3>
|
| 353 |
+
<p>Direct passthrough to the upstream health endpoint, kept simple for probes and checks.</p>
|
| 354 |
+
</article>
|
| 355 |
+
<article class="route">
|
| 356 |
+
<h3><code>/v1/messages</code></h3>
|
| 357 |
+
<p>Anthropic-compatible request path. Auth and streaming semantics remain upstream-defined.</p>
|
| 358 |
+
</article>
|
| 359 |
+
</div>
|
| 360 |
+
</section>
|
| 361 |
+
|
| 362 |
+
<section class="frame section">
|
| 363 |
+
<div class="section-head">
|
| 364 |
+
<div>
|
| 365 |
+
<h2>Quick request</h2>
|
| 366 |
+
</div>
|
| 367 |
+
<p>
|
| 368 |
+
Use the Space URL as the base URL. If the Space is private, Hugging Face must allow the
|
| 369 |
+
request before the app can evaluate its own token checks.
|
| 370 |
+
</p>
|
| 371 |
+
</div>
|
| 372 |
+
|
| 373 |
+
<div class="split">
|
| 374 |
+
<pre class="code"><code>curl https://YOUR-SPACE.hf.space/v1/messages \
|
| 375 |
+
-H "Content-Type: application/json" \
|
| 376 |
+
-H "Authorization: Bearer $CLAUDE_API_KEY" \
|
| 377 |
+
-d '{
|
| 378 |
+
"model": "claude-sonnet-4-6",
|
| 379 |
+
"max_tokens": 128,
|
| 380 |
+
"messages": [
|
| 381 |
+
{"role": "user", "content": "hello"}
|
| 382 |
+
]
|
| 383 |
+
}'</code></pre>
|
| 384 |
+
|
| 385 |
+
<div class="aside-stack">
|
| 386 |
+
<div class="aside">
|
| 387 |
+
<p>
|
| 388 |
+
Runtime edits made in <code>/admin</code> do not mean Hugging Face Secrets were
|
| 389 |
+
updated. Treat wrapper config, Secrets, and any upstream persistence layer as
|
| 390 |
+
separate concerns.
|
| 391 |
+
</p>
|
| 392 |
+
</div>
|
| 393 |
+
<div class="aside">
|
| 394 |
+
<p>
|
| 395 |
+
This page intentionally avoids live status, mutable controls, and any secret-derived
|
| 396 |
+
values. It is an entrance, not a console.
|
| 397 |
+
</p>
|
| 398 |
+
</div>
|
| 399 |
+
</div>
|
| 400 |
+
</div>
|
| 401 |
+
|
| 402 |
+
<p class="note">
|
| 403 |
+
For stable persisted settings after restart, rely on upstream-supported storage and verify
|
| 404 |
+
the behavior explicitly instead of assuming the wrapper can override startup precedence.
|
| 405 |
+
</p>
|
| 406 |
+
</section>
|
| 407 |
+
</div>
|
| 408 |
+
</main>
|
| 409 |
+
</body>
|
| 410 |
+
</html>
|