Files
llm-in-text/AGENTS.md
T
2026-06-27 22:28:01 +08:00

197 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# LLM in Text 仓库指引 (v0.2.0)
本文件适用于整个仓库。进入更深层目录后,子目录中的 AGENTS.md 优先于本文件。
## 项目定位
- 这是一个智能 Markdown 编辑器,前端负责编辑器 UI、上传导出、补全交互和设置状态,后端负责 LLM、OCR、文件转换和 TTS 接口。
- 前端技术栈:Vue 3 + Vite + Milkdown/Crepe + Pinia + Vue Router。
- 后端技术栈:FastAPI + Python + OpenAI-compatible LLM endpoint + Redis Streams。
- 仓库内另有 `InTEX/` iOS 客户端工程,技术栈为 SwiftUI + SwiftData;当前仍是独立的 Xcode 初始工程,尚未接入 Web/Docker 后端。
- 项目版本:v0.2.0(自 b82c6d3 之后的全栈架构升级版本)。
## 功能块系统(核心概念)
- **统一命名**:文档块、PRO 块、上传块统称为"功能块"。
- **禁止嵌套**:所有功能块的 schema 均设 `atom: true, isolating: true`ProseMirror 层面强制禁止互相嵌套。DocBlockCrepe.vue 的嵌套 Crepe 编辑器不注册任何功能块插件,从架构上杜绝深层嵌套。
- **无数量限制**:一篇文档可包含任意数量的功能块,彼此独立存在。
- **导入自动解析**:从 Markdown 文件导入后,各功能块语法必须通过对应的 Remark/parseMarkdown 解析器自动识别并还原为交互卡片。
- **导出可复原**:通过 toMarkdown/leafText 序列化器将功能块还原为 Markdown 语法,确保导出后再次导入能完整复原。doc_block toMarkdown 输出 legacy HTML tag`getExportMarkdown()` 中通过 `transformLegacyDocBlocksForExport()` 转换为 fenced code block。
| 类型 | Node Type | Markdown 语法 | Plugin 文件 | Utility 文件 |
|------|-----------|---------------|-------------|--------------|
| 文档块 | `doc_block` | \`\`\`llm-file fenced code / `<doc_type=...>` legacy HTML tag | `plugins/docBlockPlugin.ts` | `utils/docBlock.js` |
| PRO 块 | `pro_block` | `[PRO]` / `[PRO]{指令}` | `plugins/proBlockPlugin.ts` | `utils/proBlock.js` |
| 上传块 | `upload_block` | `{{{}}}` / `{{{upload file type:...}}}` | `plugins/uploadBlockPlugin.ts` | `utils/uploadBlock.js` |
- **已验证**:当前代码完全符合"禁止嵌套、自动解析复原"的要求。修改功能块相关逻辑时需验证:1) schema 的 atom/isolating 属性不被移除;2) Remark/parseMarkdown/toMarkdown 解析链路完整。
- 当前代码中可以确认的主功能是:AI 补全、OCR、文档转 Markdown、TTS/ASR、Markdown/DOCX/PDF 导入导出。
- 历史文档中有一部分 TTS/ASR、Apple Silicon、Whisper、离线模式说明已经落后于当前代码;出现冲突时以实际代码和测试为准。
## 先看哪里
- 项目概览和运行说明:README.md
- 前端入口:src/main.js
- 路由:src/router/index.js
- 编辑器主组件:src/components/MilkdownEditor.vue
- AI 补全核心:src/plugins/copilotPlugin.ts、src/plugins/copilotTypes.ts(类型定义)
- 前端请求层:src/utils/api.js、src/utils/fetch.js(安全 fetch wrapper
- 前端配置:src/utils/config.js
- 设置状态:src/stores/settings.js
- 后端入口和主路由:backend/main.py
- LLM 和 OCR 调用:backend/llm.py
- Prompt 组装:backend/prompt.py(含 PRO 模式模板)
- TTS 路由:backend/tts_asr.py
- **任务队列系统**backend/job_system.pyRedis Streams 异步任务管理)
- **Worker 进程入口**backend/worker.py(消费 Redis Streams 的独立 worker
- **任务处理器注册表**backend/job_handlers.pycompletion/PRO/web_search/compress/OCR/convert/TTS/ASR 处理器)
- **会话管理**backend/session_store.py(内存 + PostgreSQL 双后端)
- **API/LLM 审计日志**backend/audit_store.pyPostgreSQL 持久化)
- **风控配置**backend/risk_config.py(环境变量驱动的配置数据类)
- **风控引擎**backend/risk_control.py(速率限制、并发控制、熔断器、预算追踪)
- **验证码路由**backend/captcha_api.pyFastAPI router
- **文档存储**backend/docs_store.pyMIME 类型检测、文本/二进制分类)
- **LLM 策略解析**backend/llm_policy.py(按 job_type 解析模型配置)
- **网页搜索块组件**src/components/WebSearchBlockCrepe.vue(可折叠/压缩/删除的搜索结果卡片)
- **网页搜索块插件**src/plugins/webSearchBlockPlugin.ts```llm-websearch fenced code 语法)
- **OCR 图片包装器**src/components/OCRImageWrapper.vue(加载/成功/失败状态覆盖层)
- **验证码组件**src/components/CaptchaComponent.vuevue3-captcha 封装)
- **SSE 流式解析**src/utils/sse.tsServer-Sent Events 事件解析)
- **文档管理 API**src/utils/docsApi.js(上传/预览/删除接口客户端)
- **PRO 功能接受追踪**src/utils/proAccept.js(使用分析和统计)
- **Cookie 策略管理**src/utils/cookie_policy.jsSameSite/Secure 标志处理)
- **字符串工具**src/utils/string.ts(长度计算、编码检测)
- **网页搜索上下文提取**src/utils/webSearch.js(搜索结果 Markdown 构建与解析)
- **测试配置和入口**pytest.ini、backend/tests/run_tests.py
- **iOS 客户端工程**InTEX/InTEX.xcodeprojscheme: `InTEX`
- **iOS 客户端入口**InTEX/InTEX/InTEXApp.swift、InTEX/InTEX/ContentView.swift
## 稳定事实
- **功能块禁止嵌套**`doc_block``pro_block``upload_block` 的 schema 均设 `atom: true, isolating: true`ProseMirror 层面强制禁止互相嵌套。DocBlockCrepe.vue 的嵌套 Crepe 编辑器仅注册 copilotPlugin + hiddenText*,不注册任何功能块插件。修改时不得移除 atom/isolating 属性或在嵌套编辑器中引入功能块插件。
- **功能块导入解析**:各功能块的 Remark 插件(`docBlockRemark`, `proBlockRemark`, `uploadBlockRemark`)负责从 Markdown AST 识别对应语法并转换为节点。doc_block Remark 同时支持 fenced code (`llm-file`) 和 legacy HTML tag (`<doc_type=...>`)。解析链路必须保持完整,否则导入后无法自动复原。
- **功能块导出序列化**doc_block 的 `toMarkdown` runner 输出 legacy HTML tag,但 `getExportMarkdown()` 中通过 `transformLegacyDocBlocksForExport()` 转换为 fenced code block。pro_block/upload_block 的 toMarkdown/leafText 直接输出标准语法。修改时需验证导出后再次导入能完整复原。
- **Web Search 块语法**web_search_block 使用 fenced code ```llm-websearch date=... 语法,与 doc_block(llm-file)、pro_block([PRO])、upload_block({{{}}}) 并列。触发语法为 [WEBSEARCH](不区分大小写),由 `webSearchBlockPlugin.ts` 处理。
- **Store 同步格式**`scheduleMarkdownSync()` emit markdown 不做转换(doc_block 为 legacy HTML tag),store 中始终是 legacy format。`syncInitialMarkdown()` / `getExportMarkdown()` 负责格式转换。
- **上传块生命周期**upload_block 是临时占位符,文件上传后被替换为 image node(图片)或 doc_block(文档),不会与 pro_block 共存。
- **PRO block escape/unescape**`escapeProBlockContent()` / `unescapeProBlockSyntax()` 处理 `\`, `]`, `}`, newline,确保指令 round-trip 正确。
- **补全接口当前不是 SSE**;前端用普通 POST 请求拿 JSON 响应。
- **任务队列架构(v0.2.0 新增)**:后端从同步端点转向 Redis Streams 异步队列。`job_system.py` 定义 JOB_TYPEScompletion/pro_completion/web_search/compress/ocr/convert/tts/asr),`worker.py` 消费队列,`job_handlers.py` 注册各类型处理器。所有任务支持并发控制、速率限制和熔断器模式。
- **会话追踪**`session_store.py` 提供 InMemorySessionStore(开发)和 PostgresSessionStore(生产),通过 session_hash + ip_hash 追踪请求身份。
- **风控系统**`risk_config.py` + `risk_control.py` 实现速率限制(滑动窗口)、并发控制、熔断器模式和预算追踪,所有阈值通过环境变量配置。
- 前端会生成 X-Request-Id,并在请求被中止时额外调用 /v1/completions/cancel。
- 文档超过 32 KB 时,AI 补全会在前端和插件层被禁用。
- OCR 文本和文档块内容会被注入补全上下文,但这些内容属于隐藏上下文,不应被直接当作用户可见文本重复输出。
- /v1/convert 当前支持 txt、docx、pptx、pdf,非 txt 文件通过 MarkItDown 转成 Markdown,之后会清理图片标记。
- **AI 开关是全局广播状态**MilkdownEditor.vue 通过 `llm-in-text:copilot-toggle` 同步主编辑器、文档块嵌套编辑器、网页搜索块嵌套编辑器;修 ghost text 时要同时检查这三处。
- **设置项统一使用 country**:前后端请求、prompt、store、设置面板只使用 `country`
- **DOCX/PDF 导出改为纯前端**:不再依赖 `/v1/export/pdf`。当前策略是先展开所有功能块,再从编辑器 HTML 构建导出内容;`src/utils/richExport.js` 负责 HTML -> PDF / DOCX。
- **上传单文件限制统一为 100MB**:前端校验和后端 OCR 风控上限都按 100MB 处理。
- **视频解析策略**:上传视频时,后端 `/v1/ocr` 接收 `media_type=video`,视频画面走 OCR 模型,音轨通过 ffmpeg 抽取后走 ASR 模型,最终合并为“视频画面 OCR + 视频音频 ASR”文本。
- **OCR 明确关闭思考**backend/llm.py 的 OCR payload 显式下发 `options.think = False``temperature = 0`
- **LLM/PRO/OCR 模型统一**`LLM_MODEL``PRO_LLM_MODEL``VLM_MODEL` 的当前默认值和 Docker 运行值统一为 `Nex-N2-mini-mlx-OptiQ-8bit-MTP`。网页搜索模型仍可通过 `RISK_WEB_SEARCH_MODEL` 独立配置。
- **OCR 完成状态不等于识别正确**:Redis 任务、审计入库和 SSE `done` 只能证明链路完成。2026-06-27 的清晰文本图片真实测试中,Nex OCR 仍可能返回未识别文本;用户已明确暂停继续修复 OCR 精度,不要把链路完成误报为 OCR 正常。
- **审计 SQL 参数必须严格对齐**:`llm_call_audit` 当前包含 `queue_ms``run_ms``total_ms`,修改 INSERT 时必须保持列数、`%s` 占位符数和参数数一致;`backend/tests/test_audit_store.py` 覆盖此回归。
- **TTS/ASR 当前真实实现**backend/tts_asr.py 统一通过 `LLM_BASE_URL` + `LLM_API_KEY` 调用 OpenAI-compatible Speech API,默认模型为 `Qwen3-TTS-12Hz-1.7B-VoiceDesign-8bit``Qwen3-ASR-0.6B-8bit`。VoiceDesign 请求字段必须使用复数 `instructions`;空声音描述必须回退到 `TTS_DEFAULT_INSTRUCTIONS`,不能向上游发送空指令。
- **六按钮收缩菜单**MilkdownEditor.vue 的 upload/import/export/AI/template/clear 六个动作统一收进右下角 `.more-actions`。视口宽度 `>= 520px` 横向展开,窄屏纵向展开;再次点击主按钮、再次点击已打开的 export/template、点击 backdrop 或按 Escape 都必须完整收缩回右下角省略号。
- **iOS 工程边界**`InTEX/` 当前是 SwiftUI + SwiftData 示例骨架,包含 `InTEX``InTEXTests``InTEXUITests` 三个 target。不要声称它已接入编辑器或 Docker API;提交时禁止加入 `xcuserdata``*.xcuserstate` 和 DerivedData。
## 常用命令
- 前端安装:npm install
- 前端开发:npm run dev
- 前端构建:npm run build
- 后端安装:pip install -r backend/requirements.txt
- 后端启动:python backend/main.py
- 可选启动方式:uvicorn backend.main:app --reload --port 8001
- iOS 工程检查:xcodebuild -project InTEX/InTEX.xcodeproj -list
- iOS 模拟器构建:优先使用 XcodeBuildMCP;命令行回退可用 `xcodebuild -project InTEX/InTEX.xcodeproj -scheme InTEX -sdk iphonesimulator CODE_SIGNING_ALLOWED=NO build`
- 全量测试:pytest
- 常用窄测试:
- pytest backend/tests/test_main_endpoints.py -v
- pytest backend/tests/test_main_cancel.py -v
- pytest backend/tests/test_prompt.py -v
- pytest backend/tests/test_llm.py -v
## Docker 部署约定
- 本机部署目录固定在 `/Users/allenyuan/lit/` 下,不在仓库外再散落数据库或 Docker 持久化目录。
- 当前推荐的部署工作目录是 `/Users/allenyuan/lit/llm-in-text/`;把仓库同步到该目录后,从该目录执行 `docker compose up -d --build`
- 不再使用 `/Volumes/New Volume/lit/` 部署本项目;迁移时可放弃旧 PostgreSQL 数据,从新部署目录初始化空数据库。
- **docker-compose.yml 定义的服务**apiFastAPI)、worker(任务消费者)、frontendNginx)、postgres(审计+会话存储)、redis(任务队列)、searxng(网页搜索后端)、firecrawl(网页内容抓取)。每次 `docker compose up --build` 后,必须验证所有目标服务(api/worker/frontend/postgres/redis)是否处于 Up 状态。
- **前端网络硬约定**:前端 API 必须调用 `https://api.imageteach.tech:8002/` 反向代理,不要让前端调用 Docker 内 `api` 服务、本机 `localhost:8001``localhost:8081` 或同源 `/v1` 代理。网络和反向代理由外部配置处理,除非用户明确要求,不要新增或恢复前端到 Docker 后端的代理。
- 每次修改会影响 Docker 运行效果的代码后,不能只停留在本地测试;必须同步更新当前 Docker 环境中的代码,并验证容器内代码已经变化。
- 首选更新方式:
1. 在仓库根目录执行 `docker compose up -d --build`
2. 执行 `docker compose ps` 确认 `api``worker``frontend` 等目标服务已重新创建并处于 Up。
3. 对关键修复点执行容器内验证,例如 `docker compose exec -T worker sh -lc "python - <<'PY'\nfrom pathlib import Path\nprint('_normalize_preferences' in Path('/app/backend/prompt.py').read_text())\nPY"`
- 如果 `docker compose build` 因 Docker Hub、镜像源、网络 token 超时等外部原因无法拉基础镜像,仍然必须更新正在运行的 Docker 环境。可用应急方式:
1. 先执行 `npm run build` 生成最新前端产物。
2.`docker cp` 将改动后的后端文件复制到 `api``worker` 容器的 `/app/backend/`,必要时将 `dist/` 复制到 `frontend` 容器的 `/usr/share/nginx/html/`
3. 执行 `docker compose restart api worker frontend` 重启受影响服务。
4. 执行 `docker commit llm-in-text-api-1 llm-in-text-api:latest``docker commit llm-in-text-worker-1 llm-in-text-worker:latest`;如果更新了前端,也执行 `docker commit llm-in-text-frontend-1 llm-in-text-frontend:latest`
5. 执行 `docker compose up -d --no-build --force-recreate api worker frontend`,确保新容器来自已更新镜像。
6. 再次用 `docker compose exec -T ...` 验证容器内文件和行为,不能只看本地文件。
- Docker 持久化数据统一落在部署目录内的 `docker-data/`,包括 PostgreSQL、Redis 和任务共享临时目录。
- Docker BuildKit 导出缓存统一落在 `docker-data/build-cache/{frontend,api,worker}``docker-compose.yml``cache_from/cache_to` 与 Dockerfile 的 apt/pip/npm cache mount 必须保留;首次构建填充缓存,后续相同源码构建应全部命中缓存。
- `.dockerignore` 必须排除 `backend/models``backend/.env``docker-data``node_modules``.git` 等大目录或敏感文件。`backend/models` 是约 4.9GB 的旧本地模型目录,当前 API-based TTS/ASR 不应把它发送进构建上下文。
- 容器内访问宿主机模型服务时,不要继续使用 `localhost`;应改成 `host.docker.internal` 之类的容器可达地址。
- 当前 Docker 部署的 `backend/requirements.docker.txt` 已包含 OCR、转换、队列和基础 API 依赖;`backend/Dockerfile` 额外安装 `ffmpeg` 以支持视频拆音轨。
- **Worker 容器**worker.py 作为独立服务运行,通过 Redis Streams 消费任务队列。修改 job_handlers.py 或 worker.py 后需要验证 worker 容器内的代码已更新,可通过 `docker compose exec -T worker sh -lc "python -c 'from backend.job_handlers import get_handler; print(get_handler(\"completion\").__name__)'"` 验证。
- **Redis Streams 架构**:任务队列使用 Redis Streams,支持并发控制、速率限制和熔断器。job_system.py 定义 JOB_TYPES 和队列配置,worker.py 注册处理器并运行事件循环。
- 修改 Docker 相关文件时,除了代码本身,还要同步检查:
- `docker-compose.yml`
- `backend/Dockerfile`
- `backend/requirements.docker.txt`
- `Dockerfile.frontend`
- `docker/nginx.conf`
- `backend/.env.example` 与实际部署用 `backend/.env`
## 代码约定
- 不要把整个仓库当成“全小写+短横线命名”项目。当前实际情况是:
- Vue 组件和视图多为 PascalCase
- 前端工具模块多为小写 .js
- 插件层使用 TypeScript
- Python 使用 snake_case
- 以就地风格为准,不要顺手做全仓格式统一。
- UI 文案和代理回复默认使用中文。
- 不要修改 milkdown-docs/,它是只读参考资料。
- 不要新增硬编码密钥、空 catch/except、as any、@ts-ignore 之类的扩散式技术债。
- 代理在这个仓库里应优先做局部、可验证的修改,不要做无关重构。
## 调试路径
- 补全问题:
src/components/MilkdownEditor.vue
-> src/plugins/copilotPlugin.ts
-> src/utils/api.js
-> backend/main.py
-> backend/prompt.py / backend/llm.py
- OCR 问题:
src/components/MilkdownEditor.vue
-> backend/main.py
-> backend/llm.py
- 文档转换问题:
src/utils/convert.js
-> backend/main.py
- TTS 问题:
src/components/TTSMenu.vue / src/components/TTSPlayer.vue / src/components/MilkdownEditor.vue
-> src/utils/api.js
-> backend/tts_asr.py
## 测试和产物
- pytest.ini 对 backend.main、backend.llm、backend.prompt、backend.geoip、backend.prompts、backend.tts_asr 设了覆盖率门槛,低于 90% 会失败。
- 默认测试目录是 backend/tests。
- 常见生成产物包括 dist、htmlcov、.pytest_cache、api_performance_report.md;它们不是源代码。
## 文档注意事项
- README.md 对产品功能有参考价值,但其中补全、TTS/ASR 和部分接口说明已经比代码旧。
- backend/TTS_ASR_MACOS_FIX.md 和 backend/tests/TESTING_GUIDE.md 更适合作为历史背景,不应在与代码冲突时被当成事实来源。
- 修改行为时,优先参考实现代码和对应测试,再决定是否同步普通文档。