diff --git a/README.md b/README.md index ce2bca8..7f3b98d 100644 --- a/README.md +++ b/README.md @@ -1,57 +1,86 @@ -# 食谱翻译工具 +# 食谱工具(recipe_tool) -一款基于 AI 的食谱文档翻译工具,支持解析 Word 文档、智能翻译、批量导出。 +一款面向幼儿园食堂的食谱处理工具集,目前包含两大功能: + +1. **食谱翻译工具** —— 基于 AI 的食谱文档翻译:解析 Word 文档、智能翻译菜名、批量导出双语对照 Word +2. **135编辑器转换器** —— 将幼儿园周食谱 `.docx` 一键转换可直接粘贴进 [135编辑器](https://www.135editor.com/) / 微信公众号排版器的 HTML ## 功能特性 -- **Word 文档解析** - 解析 `.docx` 格式的食谱文档,提取菜品信息 -- **AI 智能翻译** - 支持 OpenAI / GLM 等大模型 API,批量翻译菜品名称 -- **翻译词库管理** - 管理常用词组翻译对照表 -- **多格式导出** - 导出双语对照的 Word 文档 -- **导出设置** - 自定义未翻译文本颜色、页面方向、第二页表头等 +### 食谱翻译工具 + +- **Word 文档解析**:解析 `.docx` 格式的食谱文档,提取菜品信息 +- **AI 智能翻译**:支持 OpenAI / 智谱 GLM 等大模型 API,批量翻译菜品名称 +- **翻译词库管理**:管理常用词组翻译对照表,命中词库免调 API +- **多格式导出**:导出双语对照的 Word 文档 +- **导出设置**:自定义未翻译文本颜色、页面方向、第二页表头等 + +### 135编辑器转换器(`/editor135`) + +- **一键转换**:上传周食谱 `.docx` → 自动解析「星期 × 餐次(早餐/早点/午餐/体弱儿餐/午点)」中英菜名 → 填充进 135 模板生成 HTML +- **三视图展示**:左侧源代码(可复制/下载)与解析结果(按星期折叠面板),右侧 375px 移动端宽度实时预览(与公众号阅读宽度一致,内部滚动、吸顶) +- **缺失星期自动删除**:docx 里缺哪天(含周六/周日、间隔天)就整段移除该天内容,导出的 HTML 不包含;支持「周一/礼拜天」等别名归一化 +- **逐条回读校验**:填充后重新解析输出 HTML,与原始数据逐条比对,`mismatch` 必须为空才可信 +- **样式零污染**:填充只改文本节点,不改样式与结构;构建前后做样式/图片/section 数快照对比 +- **微信图片代理**:预览时微信 CDN(`mmbiz.qpic.cn`)图片走本地代理转发,解决「此图片来自微信公众平台」防盗链占位图;复制/下载的 HTML 仍保持原始 URL,不影响公众号内使用 +- **智能容错**:裸文本菜名自动套用模板样式块、条目数与模板不符时自动克隆补足/删除多余 ## 技术栈 -- **框架**: Next.js 16 (App Router) -- **语言**: TypeScript -- **样式**: Tailwind CSS + shadcn/ui -- **文档**: docx (Word 生成) -- **数据库**: MySQL 8.0 -- **AI**: OpenAI API / 智谱 GLM +- **框架**:Next.js 16(App Router, Turbopack) +- **语言**:TypeScript(strict) +- **运行时**:Node.js ≥ 22(jsdom 30 依赖新 Node API) +- **包管理**:pnpm ≥ 11(配置全部位于 `pnpm-workspace.yaml`) +- **样式**:Tailwind CSS 4 + shadcn/ui + radix-ui +- **文档解析**:mammoth(docx → HTML)、cheerio、jsdom(模板填充) +- **文档生成**:docx +- **数据库**:MySQL 8.0(mysql2) +- **AI**:OpenAI SDK / 智谱 GLM ## 项目结构 ``` -├── app/ # Next.js App Router -│ ├── api/ # API 路由 -│ │ ├── ai/ # AI 翻译接口 -│ │ ├── config/ # 配置管理接口 -│ │ ├── recipe/ # 食谱解析接口 -│ │ ├── translate/ # 翻译接口 -│ │ └── words/ # 词库管理接口 -│ ├── settings/ # 设置页面 -│ │ ├── ai/ # AI 配置 -│ │ ├── database/ # 数据库配置 -│ │ ├── export/ # 导出设置 -│ │ └── translation/ # 翻译词库 -│ └── page.tsx # 首页 -├── components/ # React 组件 -├── lib/ # 工具函数 -│ ├── config.ts # 配置管理 -│ ├── database.ts # 数据库连接 -│ └── ai-translate.ts # AI 翻译 -├── config.json # 配置文件 -├── Dockerfile # Docker 构建 -└── docker-compose.yml # Docker 编排 +├── app/ # Next.js App Router +│ ├── api/ # API 路由 +│ │ ├── ai/ # AI 翻译接口 +│ │ ├── config/ # 配置管理接口 +│ │ ├── editor135/ +│ │ │ ├── render/ # 135转换:解析docx并填充模板 +│ │ │ └── img/ # 微信CDN图片代理(仅允许 *.qpic.cn) +│ │ ├── recipe/ # 食谱解析接口 +│ │ ├── translate/ # 翻译接口 +│ │ ├── update/ # 应用自更新(check/execute) +│ │ └── words/ # 词库管理接口 +│ ├── editor135/ # 135转换器页面 +│ ├── settings/ # 设置页面 +│ │ ├── ai/ # AI 配置 +│ │ ├── export/ # 导出设置 +│ │ ├── translation/ # 翻译词库 +│ │ └── update/ # 应用更新 +│ └── page.tsx # 首页(功能导航) +├── components/ # React 组件 +├── lib/ +│ ├── ai-translate.ts # AI 翻译 +│ ├── config.ts # 配置管理 +│ ├── database.ts # 数据库连接 +│ ├── editor135-core.ts # 135转换核心库(解析/填充/缺失天删除/校验) +│ └── update.ts # 自更新逻辑 +├── public/ +│ └── template135.html # 135模板(运行时读取,替换此文件即可换模板) +├── scripts/ +│ └── test-remove-days.ts # 135转换核心库回归测试(6场景) +├── config.json # 配置文件(首次启动由环境变量初始化) +├── Dockerfile # Docker 构建(node:22-alpine 双阶段) +└── docker-compose.yml # Docker 编排 ``` ## 快速开始 ### 环境要求 -- Node.js 20+ -- MySQL 8.0 -- pnpm +- Node.js **22+**(jsdom 30 要求) +- pnpm 11+(构建脚本批准配置在 `pnpm-workspace.yaml`) +- MySQL 8.0(翻译功能需要) ### 本地开发 @@ -60,21 +89,26 @@ pnpm install # 2. 配置环境变量 -cp .env.docker .env -# 编辑 .env 填入数据库和 AI API 配置 +# 参考下方「环境变量」表格创建 .env,或从已有部署复制 # 3. 启动开发服务器 pnpm dev ``` -访问 http://localhost:3000 +访问 http://localhost:3000 ,翻译工具在首页,135转换器在 `/editor135` 或首页右上角「135编辑器转换」入口。 + +### 生产构建 + +```bash +pnpm build +pnpm start +``` ### Docker 部署 ```bash # 1. 配置环境变量 -cp .env.docker .env -# 编辑 .env 填入配置 +cp .env.docker .env # 或手动创建,见下方表格 # 2. 构建并启动 docker-compose up -d @@ -87,18 +121,16 @@ docker-compose logs -f app ### 配置文件 -所有配置存储在 `config.json` 中,可通过设置页面修改: +所有配置存储在 `config.json` 中,可通过设置页面修改(首次启动由环境变量初始化,之后以 `config.json` 为准): | 配置项 | 说明 | |--------|------| -| `db` | 数据库连接配置 | -| `ai` | AI 翻译 API 配置 | -| `export` | 导出文档格式配置 | +| `db` | 数据库连接配置(host / port / user / password / database) | +| `ai` | AI 翻译 API 配置(apiKey / baseUrl / model) | +| `export` | 导出文档格式配置(untranslatedColor / orientation / secondPageHeader) | ### 环境变量 -首次启动时,环境变量会初始化 `config.json`。之后配置以 `config.json` 为准。 - | 变量名 | 说明 | 默认值 | |--------|------|--------| | `DB_HOST` | 数据库地址 | 127.0.0.1 | @@ -109,9 +141,15 @@ docker-compose logs -f app | `AI_API_KEY` | AI API Key | - | | `AI_BASE_URL` | API 地址 | https://api.openai.com/v1 | | `AI_MODEL` | 模型名称 | gpt-3.5-turbo | +| `EXPORT_UNTRANSLATED_COLOR` | 未翻译文本颜色 | #FF0000 | +| `EXPORT_ORIENTATION` | 页面方向 | landscape | +| `ONEDEV_ACCESS_TOKEN` | 自更新拉取镜像的令牌 | - | +| `REGISTRY_IMAGE` | 更新镜像仓库地址 | - | ## API 接口 +### 翻译工具 + | 接口 | 方法 | 说明 | |------|------|------| | `/api/config` | GET/POST | 获取/保存配置 | @@ -121,13 +159,62 @@ docker-compose logs -f app | `/api/words` | GET/POST | 词库列表/添加 | | `/api/words/[id]` | PATCH/DELETE | 编辑/删除词条 | -## 导出设置 +### 135编辑器转换器 -| 选项 | 说明 | -|------|------| -| 未翻译文本颜色 | 未翻译菜品在文档中的显示颜色 | -| 页面方向 | 横向/纵向 | -| 第二页表头 | 多页时后续页显示表头行 | +| 接口 | 方法 | 说明 | +|------|------|------| +| `/api/editor135/render` | POST | 上传 `.docx`(multipart `file` 字段),返回 `{ fileName, summary, recipe, html, report }` | +| `/api/editor135/img` | GET | 微信图片代理:`?u=`,仅允许 `*.qpic.cn` 域名 | + +`render` 返回的 `report` 结构: + +```jsonc +{ + "filled": [ // 每个餐次的填充明细 + { "day": "星期一", "meal": "早餐", "replaced": 4, "added": 0, "removed": 0, "oldCount": 4, "newCount": 4 } + ], + "warnings": [], // 非致命提示(如模板中无该餐次) + "repaired": [], // 裸文本菜名自动套样式的修复记录 + "mismatch": [], // 回读校验差异,正常必须为空数组 + "removedDays": ["星期五"], // docx 缺失、已从输出整段删除的星期 + "verification": { // 构建前后快照对比 + "stylesAdded": 0, "stylesRemoved": 0, // 样式变化数,正常为 0 + "imgsUnchanged": true, // 图片是否未被改动 + "sections": "570 -> 570", "elements": "1502 -> 1242" + } +} +``` + +## 135编辑器转换器工作原理 + +1. **解析**:mammoth 将 docx 转为 HTML,逐叶子节点扫描,识别星期标题(支持 周一/礼拜一 等别名归一化)、餐次标题、中文菜名与紧跟的英文菜名,按「星期 → 餐次 → 条目」收集 +2. **删除缺失天**:对比 docx 与模板中出现的星期,缺失的天用「逐节点精确区间删除」从模板 DOM 移除(从该天标题节点删到下一现存天标题之前;最后一天以「三审三校/温馨提示」为尾界)——算法处理了相邻天共用容器、深层嵌套等结构,不会误删保留天 +3. **填充**:就地替换文本节点(文本未变不触碰),条目数不符时以模板最后一条为锚点克隆补足或从后往前删除多余 +4. **校验**:重新收集输出 DOM 与原始数据逐条比对(`mismatch`),并对比构建前后的样式/图片/元素快照(`verification`) +5. **输出**:序列化 HTML 返回,可直接粘贴进 135编辑器 / 公众号后台 + +### 更换模板 + +替换 `public/template135.html` 即可。模板需包含:星期标题(星期一~星期日文本)、餐次标题(早餐/早点/午餐/体弱儿餐/午点)、以及至少一组「中文菜名块 + 紧邻的英文菜名块」作为克隆锚点。 + +### 运行测试 + +```bash +# Node 22+ 直接跑 TS(无需编译) +node --experimental-strip-types scripts/test-remove-days.ts +``` + +测试覆盖 6 个场景:间隔天(一/三/五)、含周末、仅周末、中间连续缺、条数增删路径、完整周回归。 + +## Docker 构建注意事项 + +- 基础镜像为 `node:22-alpine`(jsdom 30 需 Node ≥ 22,`builder` 与 `runner` 两个 stage 都要用 22) +- pnpm 11 的构建脚本批准配置在 `pnpm-workspace.yaml` 的 `allowBuilds`,该文件必须在 `pnpm install` 之前 COPY 进镜像 +- `pnpm-workspace.yaml` 中已放行 `msw` / `sharp` / `unrs-resolver` 的构建脚本(pnpm 11 在 CI 下未批准会直接报 `ERR_PNPM_IGNORED_BUILDS`) + +## 自更新机制 + +部署后可通过设置页「应用更新」拉取镜像仓库新版本:`/api/update/check` 检查、`/api/update/execute` 执行 `docker pull` 并重启容器(需要挂载 docker.sock 及 `REGISTRY_IMAGE` / `ONEDEV_ACCESS_TOKEN` 配置)。 ## License