Skip to content

Repository files navigation

雌小鬼转换器 ♡

一个部署在 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

API 地址:

https://api.zako.mcgeelee.com

简洁翻译接口

POST /v1/translate

请求体:

{
  "text": "你这里写错了,重新改一下吧。",
  "address": "杂鱼前辈",
  "style": "甜蜜嘲讽",
  "intensity": 2,
  "stream": true
}

只有 text 必填。默认值为:

  • address: 杂鱼前辈
  • style: 甜蜜嘲讽
  • intensity: 2
  • stream: 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 Chat Completions 兼容接口

将 OpenAI 客户端的 baseURL / base_url 指向:

https://api.zako.mcgeelee.com/v1

支持:

  • POST /v1/chat/completions
  • GET /v1/models
  • 非流式 Chat Completion JSON
  • stream=true 的 data-only SSE
  • stream_options.include_usage=true
  • 字符串消息和 {"type":"text","text":"..."} 内容块
  • temperaturemax_tokensmax_completion_tokens
  • 扩展字段 addressstyleintensity

请求示例:

{
  "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/translateOPTIONS /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

GitHub → Cloudflare 自动部署

网页端和 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

API 服务构建设置

设置
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,避免在限额失效时继续产生上游费用。

配置 DeepSeek Secret

在 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。

这是朋友之间的玩梗工具。请尊重对方感受和边界,不要把轻度可爱嘲讽变成骚扰或伤害。

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages