From 8751f71f5a4577b2e2d15bcc62c8e5c093bc1ba8 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Thu, 23 Jul 2026 14:49:16 +0800 Subject: [PATCH] =?UTF-8?q?feat(web):=20=E7=A7=BB=E5=8A=A8=E7=AB=AF?= =?UTF-8?q?=E5=AF=B9=E8=AF=9D=E9=99=84=E4=BB=B6=E5=85=A5=E5=8F=A3(?= =?UTF-8?q?=E6=8B=8D=E7=85=A7/=E7=85=A7=E7=89=87=E5=BA=93/=E9=80=89?= =?UTF-8?q?=E6=96=87=E4=BB=B6)+=20bump=200.59.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit chat 输入区此前仅粘贴/拖拽两条附件路,移动端双双不可用。加 [附件] 按钮: 触屏弹三项菜单(拍照 capture=environment / 照片库 accept=image,video / 选取文件无 accept),桌面端直开文件选择器;三入口 change 统一走既有 uploadAttachFiles 链路,只读镜像随 applyChannelComposerLock 禁用。 纯前端改动,无后端/schema 变化。 Co-Authored-By: Claude Fable 5 --- CHANGELOG.md | 4 ++++ PROGRESS.md | 1 + core/__init__.py | 2 +- web/static/dev.html | 5 +++++ web/static/js/chat.js | 25 +++++++++++++++++++++++++ 5 files changed, 36 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 840c1e1..cbe8fe2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ > 所以不是每个版本号都有条目。条目格式 `## <版本> — <日期>`,新条目加在最上面。 > 工程口径的完整记录见 `PROGRESS.md` / git log。 +## 0.59.2 — 2026-07-23 + +- 手机端也能发文件了:对话输入框下新增「📎 附件」按钮,点开可拍照、从相册选照片 / 视频、或选取文件,上传后随消息一起发给助手;电脑端点击直接打开文件选择器(原有的粘贴 / 拖拽方式照旧)。 + ## 0.59.0 — 2026-07-22 - 新增「个人知识库」:左下角新入口,可以建多个库(如「行业标准」「项目资料」),把常用的 PDF / Word / PPT / Excel / 网页 / 文本资料上传进去,系统自动转成文字版、写好摘要索引;之后在对话里直接提问,我会按需查你的库作答并标注来源。扫描件 PDF 也会自动识别文字入库。库和文档可随时在面板里查看、删除。 diff --git a/PROGRESS.md b/PROGRESS.md index 4847011..49e9bd5 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -23,6 +23,7 @@ ### 2026-07 +- **07-23 / 0.59.2 / 移动端对话附件入口(📎 拍照 / 照片库 / 选文件)**:chat 输入区此前仅粘贴 / 拖拽两条附件路(移动端双双不可用,手机没有任何传文件入口)。加 📎 按钮:触屏(`pointer: coarse`)弹 showMenu 三项菜单——拍照(`capture="environment"` 直起后置相机)/ 照片库(`accept="image/*,video/*"`)/ 选取文件(无 accept);桌面端点击直开系统文件选择器(拖拽 / 粘贴照旧)。三个隐藏 input 的 change 统一走既有 `uploadAttachFiles()`(chip 托盘 / 发送注入行零改动);change 后清 value 允许连选同一文件;微信 / 企业微信只读镜像随 `applyChannelComposerLock` 禁用按钮 + 点击侧 `_composerLocked()` 双保险。纯前端(dev.html / chat.js),无后端改动。裸 file input 方案(靠 iOS 原生三选单)因 Android 行为不确定被弃,三项菜单跨平台确定。 - **07-23 / 0.59.1 / 架构审查落地:15 commit 内部重构大扫除(对外行为零变化)**:全库审查(web/核心循环/工具渲染/数据层四路并行探查)后按优先级逐项执行。①**web 拆分**——app.py 3972→197 行:73 路由迁 11 个 `web/routers/*`(register 范式同 admin.py),lifespan 后台协程按域析出(`background`/`scheduler_runner`/`wechat_runner`),schemas/model_gate/runs/userfiles/common 各归位;②**计费收口**——units JSONB 读侧唯一出口 `core/storage/usage_report.py`(web 层清零 cast),写侧拆 `telemetry.py`(四类 cost=0 留痕与真计费分家,kind 常量成 loop↔toolfail 契约单一事实源);③**loop 拆分**——传输健壮性层(畸形/吐空检测、留痕、非流式降级重试)析出 `core/llm_transport.py`(loop 1158→812),`_execute_tool_call` 148 行拆 4 个正交方法,测试打桩缝(`_collect_stream_once`/`_nonstream_once`)原样保留;④**工具注册声明式化**——`core/tool_registry.py` (组名,gate,factory) 表收敛四种 env-gate 写法(§7.5 #7 获代码强制),32 工具挂载逐名比对不变;⑤**媒体工具去重**——五工具同构(配额闸/落盘命名/记账兜底/超时重试/递归找 URL)收 `tools/media_common.py`,净减 122 行,banner/轮询等刻意差异不动;⑥**渲染统一**——块收集器(fence 闭合/表格/blockquote/段落并合)与 brief inline 切分下沉 `rendering/common`,**golden 基线**(四 profile 渲富 fixture、document.xml 逐字节对账,`tests/golden/`)零 diff 验收,分派状态机(in_refs 等)留 profile;⑦**测试 286→351**——scheduler 服务层、usage 口径、media 原语、wecom 加解密(自造密文覆盖失败路径)、路由两套(无 DB 面:11 router 鉴权门 19 端点 + kb/skills/memory/files FS;DB 面:tasks CRUD 全链 + files 顶层目录 DB-aware 四分支 + upload);测试库基建 = docker PG@5433 + `ZCBOT_TEST_DB_URL` 显式门控(RUN.md 一键命令)。**修 2 个真 bug**:restore 恢复真软删过的任务必 500(server-side onupdate 列 flush 后 expired → DetachedInstanceError,幂等路径掩盖多时,路由测试抓出);DB 测试曾沿 .env 连到生产隧道(127.0.0.1:6012)——插入的到点 job 被生产 green 调度守护真跑,门控收紧为只认 ZCBOT_TEST_DB_URL + connect_timeout=5 兜挂库,产物已全量清理。附带:scripts 50→29(已结案诊断/backfill 归档 `scripts/archive/`)、`tools/output.py`(输出处理自 base.py 析出)、export_docx 复用 rendering.common 字体助手(rendering 保持无 core 依赖的沙箱可用性,修正审查"迁 rendering"建议)。 - **07-22 / 0.59.0 / 个人知识库(纯文件 .kb/ 机制,照记忆范式)**:方案 07-22 对齐后落地(DESIGN §3.8)。**core**:`core/kb.py`(状态/视图层:库名与文件名校验、INDEX 单行格式 `- [标题](docs/x.md)|来源 sources/x.pdf|摘要:…|关键词:…` 的 parse/format、"已入库判据 = INDEX 有条目"派生 pending、删单篇连带原件与 INDEX 行、`kb_block` 注入)+ `core/kb_ingest.py`(入库管线:markitdown Python API → 扫描件 PDF 文本近零走方舟 OCR 兜底(§8.13 同通道)→ flash 单次 chat 写标题/摘要/关键词(失败降级不阻塞)→ 追加 INDEX;编排 create_task+to_thread+per-(user,库) 锁,进度内存态供轮询)。**记账**:0022 迁移放宽 `usage_events.task_id` 可 NULL(kb 入库无 task 上下文),溯源 = kind="kb_ingest"/"vision" + units `{"kb","source"}`(record_chat/vision_usage 签名同步放宽)。**API**:`/v1/kb*` 8 端点(列/建/删库、详情带入库进度、上传即入库、手动 ingest、看/删单篇),不设 HTTP 检索端点。**agent 侧零新工具**:agent_builder 在 memory_block 后注 `kb_block`(有库才注,docker/host 路径换算同 .memory);documents skill「何时不用」补自建资料路由行。**前端**:`kb.js` 两栏 modal(建/删库、上传 XHR、入库进度 3s 轮询、看/删单篇),rail 底部四按钮改图标+小字两行布局(用户 07-22 定)。冒烟:core 层 28 项全过(真实 markitdown + flash 摘要),TestClient API 15 项全过(usage 行实测 task_id=NULL + units 溯源),受影响存量测试(usage_accounting / system_prompt_paths)绿。 - **07-21 / 0.58.55 / 扫描件 PDF 直读(read_document,方舟文档理解)**:markitdown 只抽文本层,扫描件(老标准/检测报告/红头指南)转出为空=死路。探针(`scripts/probe_ark_doc.py`)验证方舟 chat file 内容块直读 PDF:格式 `{"type":"file","file":{filename,file_data}}` + `data:application/pdf;base64,` 前缀、base64 内联 17MB 可用(免 TOS)、单页栅格化 3600 万像素硬限(PIL 存 PDF 需标对 dpi)、~1300 输入 token/页(约 1 厘/页)、100 页魔术串全覆盖。落地 `tools/read_document.py`(seed_2_lite 同 variant 同 key,记账走 record_vision_usage):体积/页数双闸(30MB/100 页,pdfminer 软探页数免白付撞上下文)+ `finish_reason=length` 截断提示 + **多页 OCR `save_md` 全文落盘只返 1500 字预览**(防上下文爆);`image_ref.py` 抽 `load_pdf_as_data_url` 复用三形态路径解析与 user_root 边界。agent_builder 注册 + 系统提示 `_MEDIA_READDOC_SEG`(何时调/何时不调防重复花钱);六 skill(paper/patent/standard/proposal/rebuttal/ppt)摄取段加扫描件兜底一行。冒烟 `scripts/smoke_read_document.py` 全过(3 页 ¥0.0066,表格→md 表、公式→LaTeX)。选型对比(外部解析 API=新增第三方数据面 / 本地 OCR=过度投资)见 DESIGN §8.13。host 侧工具,**无需重建沙箱镜像**。 diff --git a/core/__init__.py b/core/__init__.py index ab696c1..02acc8b 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -1,3 +1,3 @@ # zcbot 版本号单一事实源:web/app.py 的 FastAPI version、/healthz 返回、前端展示都引这里。 # 改版本只动这一行。 -__version__ = "0.59.1" +__version__ = "0.59.2" diff --git a/web/static/dev.html b/web/static/dev.html index c9d31db..4b0c50c 100644 --- a/web/static/dev.html +++ b/web/static/dev.html @@ -1807,11 +1807,16 @@
就绪 +
+ + + + diff --git a/web/static/js/chat.js b/web/static/js/chat.js index 2b1b7f0..b5b1d48 100644 --- a/web/static/js/chat.js +++ b/web/static/js/chat.js @@ -1451,6 +1451,29 @@ $("chat-form").addEventListener("drop", (e) => { if (files.length) uploadAttachFiles(files); }); +// 📎 附件按钮:触屏(pointer: coarse)弹三项菜单(拍照 / 照片库 / 选取文件), +// 桌面直接开系统文件选择器(拍照/相册对桌面无意义,拖拽/粘贴照旧)。 +// 三个隐藏 input 只是入口差异(capture/accept),选中后与粘贴/拖拽同链路 uploadAttachFiles。 +["attach-camera-input", "attach-photo-input", "attach-file-input"].forEach((id) => { + $(id).addEventListener("change", (e) => { + const files = Array.from(e.target.files || []); + e.target.value = ""; // 清掉 value,允许连续两次选同一文件仍触发 change + if (files.length) uploadAttachFiles(files); + }); +}); +$("chat-attach-btn").addEventListener("click", (e) => { + if (_composerLocked()) return; + if (!window.matchMedia("(pointer: coarse)").matches) { + $("attach-file-input").click(); + return; + } + showMenu(e.currentTarget, [ + { act: "camera", label: "📷 拍照", onclick: () => $("attach-camera-input").click() }, + { act: "photo", label: "🖼 照片库", onclick: () => $("attach-photo-input").click() }, + { act: "file", label: "📄 选取文件", onclick: () => $("attach-file-input").click() }, + ]); +}); + // 上传一批文件并把结果 chip 追加进托盘。状态写 #chat-hint(进度 / 已上传),不碰 chip。 async function uploadAttachFiles(files) { if (!files || !files.length) return; @@ -1554,6 +1577,8 @@ function applyChannelComposerLock(meta) { } const mic = $("chat-mic"); if (mic) mic.disabled = !!cfg; // 只读镜像没有发送入口,语音输入同样锁掉 + const attach = $("chat-attach-btn"); + if (attach) attach.disabled = !!cfg; // 附件按钮同口径(与拖拽/粘贴的 _composerLocked 一致) } // 润色:同步调后端,把 textarea 内容替成优化后文本。用 execCommand('insertText')