一个部署在 Cloudflare 上的 AI 语气改写工具:网页端流式显示结果,API 服务校验请求并调用 DeepSeek 官方 API,同时向外部客户端提供 OpenAI Chat Completions 兼容接口。
在线体验 ── POST /v1/translate (SSE) ──────────┐
▼
外部 OpenAI 客户端 ── POST /v1/chat/completions ──▶ API 服务
├──▶ DailyRateLimiter DO
│ IP + 服务总量按 UTC 日期原子计数
│
└──▶ DeepSeek Chat Completions (SSE)
- 网页端:纯静态
index.html,通过浏览器 Fetch 逐块解析 OpenAI 风格 SSE。 - API 服务:TypeScript 后端;固定内部提示词和上游地址,调用方不能覆盖系统提示词。
- 模型:默认使用
deepseek-v4-flash,并关闭 thinking,避免短文本改写混入 reasoning 流。 - Secret:
DEEPSEEK_API_KEY只保存在 Cloudflare 的加密 Secret 中。 - 防护:16 KiB 请求上限、4,000 字符文本上限、120 秒上游超时、CORS、结构化错误,以及每 IP 每个 UTC 日最多 30 次的强一致额度。
- 部署:网页端与 API 服务都由 Cloudflare 从 GitHub
main分支自动拉取和部署。
仓库中的旧 app.py 是历史 Streamlit 版本,不参与 Cloudflare 构建或部署。
API 地址:
https://api.zako.mcgeelee.com
POST /v1/translate
请求体:
{
"text": "你这里写错了,重新改一下吧。",
"address": "杂鱼前辈",
"style": "甜蜜嘲讽",
"intensity": 2,
"stream": true
}只有 text 必填。默认值为:
address:杂鱼前辈style:甜蜜嘲讽intensity:2stream:false
stream=false 保持原有 JSON 响应:
{
"result": "改写后的文本",
"model": "deepseek-v4-flash",
"finishReason": "stop",
"usage": {
"promptTokens": 120,
"completionTokens": 30,
"totalTokens": 150
}
}stream=true 返回 text/event-stream,正文位于 choices[0].delta.content,以 data: [DONE] 结束。Pages 使用的就是这个模式:
curl --no-buffer --request POST \
'https://api.zako.mcgeelee.com/v1/translate' \
--header 'Accept: text/event-stream' \
--header 'Content-Type: application/json' \
--data '{"text":"这个任务明天之前记得完成。","style":"温柔怜悯","intensity":3,"stream":true}'将 OpenAI 客户端的 baseURL / base_url 指向:
https://api.zako.mcgeelee.com/v1
支持:
POST /v1/chat/completionsGET /v1/models- 非流式 Chat Completion JSON
stream=true的 data-only SSEstream_options.include_usage=true- 字符串消息和
{"type":"text","text":"..."}内容块 temperature、max_tokens或max_completion_tokens- 扩展字段
address、style、intensity
请求示例:
{
"model": "zako-zako-translator",
"messages": [
{
"role": "user",
"content": "你这里写错了,重新改一下吧。"
}
],
"stream": true,
"stream_options": {
"include_usage": true
},
"address": "杂鱼前辈",
"style": "傲娇挑衅",
"intensity": 3
}这是“翻译器兼容端点”,不是通用聊天代理:
- API 服务只改写最后一条
user纯文本消息。 - 调用方的
system/developer消息不会覆盖内部提示词。 - 模型名使用
zako-zako-translator;它映射到后端配置的 DeepSeek 模型。 - 不支持 tools、图片、音频、文件、结构化输出或
n > 1。 - 输出 token 上限为 512。
OpenAI JavaScript SDK 示例:
import OpenAI from "openai";
const client = new OpenAI({
apiKey: "public-api",
baseURL: "https://api.zako.mcgeelee.com/v1",
});
const stream = await client.chat.completions.create({
model: "zako-zako-translator",
messages: [{ role: "user", content: "这个任务明天前完成。" }],
stream: true,
stream_options: { include_usage: true },
intensity: 3,
});
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}当前外部接口没有调用方鉴权,因此 SDK 中的 apiKey 只是满足客户端构造要求,不会被后端当作 DeepSeek key 使用。DeepSeek key 永远不会发送给调用方。
OpenAI 兼容路由的错误使用标准结构,追踪 ID 位于 X-Request-Id 响应头:
{
"error": {
"message": "model 是必填字符串。",
"type": "invalid_request_error",
"param": "model",
"code": "missing_model"
}
}简洁翻译接口继续使用原有错误结构,以免破坏既有客户端:
{
"error": {
"code": "INVALID_TEXT",
"message": "待改写内容必须是字符串。",
"requestId": "..."
}
}其他端点:
GET /:服务和端点信息。GET /health:服务状态、当前模型和 DeepSeek key 是否已配置。GET /v1/models:OpenAI 模型列表。OPTIONS /v1/translate、OPTIONS /v1/chat/completions:浏览器跨域预检。
/v1/translate 与 /v1/chat/completions 同时受单 IP 额度和服务总额度约束:
DAILY_IP_LIMIT:同一CF-Connecting-IP每个 UTC 自然日的有效生成请求上限,初始值为30。DAILY_SERVICE_LIMIT:整个服务每个 UTC 自然日的有效生成请求总上限,初始值为300。- 请求通过 JSON 和字段校验后、调用 DeepSeek 前计数;无效请求不占额度。
- 任一额度耗尽都会返回 HTTP
429,并在 UTC 00:00 自动切换到新额度。 - Durable Object 按 UTC 日期分片,在同一个事务中原子检查并递增 IP 与服务总计数,避免并发请求突破任一上限。
成功响应和额度耗尽错误都会提供:
X-RateLimit-Limit: 30
X-RateLimit-Remaining: 29
X-RateLimit-Reset: <下次 UTC 00:00 的 Unix 秒>
X-RateLimit-Service-Limit: 300
X-RateLimit-Service-Remaining: 299
额度耗尽时还会返回 Retry-After。响应错误消息会说明是单 IP 额度还是服务总额度耗尽。旧日期对应的 Durable Object 会在窗口结束后自动清理存储。
需要 Node.js 22 或更新版本。
npm ci
cp .dev.vars.example .dev.vars把本地 DeepSeek key 写入 .dev.vars 后启动 API 服务:
npx wrangler dev前端默认调用生产 API。需要联调本地 API 时,将 index.html 顶部的 api-url 临时改为 http://localhost:8787/v1/translate,完成后不要提交本地地址。
验证全部代码:
npm run check单独更新 Cloudflare 生成类型:
npm run types网页端和 API 服务都连接 GitHub 仓库 McGeeLee/zako_zako_translator,生产分支为 main。Cloudflare 直接拉取提交,不使用本地 Direct Upload。
| 设置 | 值 |
|---|---|
| Root directory | / |
| Build command | npm run build:pages |
| Build output directory | dist |
| Production branch | main |
| 设置 | 值 |
|---|---|
| Service name | zako-zako-translator |
| Root directory | / |
| Build command | npm run check |
| Deploy command | npx wrangler deploy --minify |
| Production branch | main |
推送到 main 后,网页端与 API 服务会分别触发构建。API 服务构建中的 npm run check 会运行类型检查、Vitest 和网页端构建。
两个上限来自 wrangler.jsonc 的运行时环境变量,业务代码中没有固定数值:
{
"vars": {
"DAILY_IP_LIMIT": "30",
"DAILY_SERVICE_LIMIT": "300"
}
}修改环境变量并重新部署即可调整额度。两个值都必须是大于 0 的安全整数;配置缺失或格式错误时,生成接口会返回 HTTP 503,避免在限额失效时继续产生上游费用。
在 Cloudflare Dashboard 对应服务的 Variables and Secrets 中添加:
Name: DEEPSEEK_API_KEY
Type: Secret
Value: 新生成的 DeepSeek API key
也可以在已登录 Wrangler 的环境中执行:
npx wrangler secret put DEEPSEEK_API_KEY注意:
- 必须选择加密的 Secret,不要选择 Plain text。
- 如果 key 曾被保存为 Plain text 或出现在日志中,应先在 DeepSeek 撤销并重新生成,再以 Secret 保存。
wrangler.jsonc已设置keep_vars: true,Git 自动部署不会删除 Dashboard 中的普通变量。- Wrangler 部署不会删除已加密 Secret;只有显式执行 secret delete 才会删除。
- 不要把 key 放进
wrangler.jsonc、网页端环境变量、构建环境变量或任何 Git 提交。
当前 API 明确作为公开接口使用,所以 ALLOWED_ORIGINS 默认为 *。CORS 不是身份验证,不能阻止脚本直接请求。可配置的单 IP 与服务总额度可以控制每日费用上限,但无法识别共享出口或阻止分布式 IP 抢占总额度;若公开推广,应继续为外部 /v1/chat/completions 增加独立 Bearer token 或 Cloudflare Access,网页端则可增加 Turnstile。
这是朋友之间的玩梗工具。请尊重对方感受和边界,不要把轻度可爱嘲讽变成骚扰或伤害。