Repository navigation
服务器模式:同一张截图重复上传时不再重复处理 - #1
Merged
Merged
Conversation
- server:POST /api/screenshots 写盘后先算 SHA-256 判重;命中时不建行、不调模型、 删掉刚写盘的文件,返回 200 + 已有截图当前的卡片、duplicate_of_screenshot_id 与提示; 没命中与改动前逐字段一致(201),整条处理成功后才记哈希 - server:db.ts 打开数据库时建服务器专用表 server_screenshot_hashes(sha256 索引, 不动 PRAGMA user_version);deleteScreenshotUploadArtifacts 同步删哈希,外键级联兜底 - server:第一次判重前给已处理完成、图片文件还在的老记录补算哈希 - app:ScreenshotUploadResponse 加 duplicate_of_screenshot_id;重复响应的卡片不进本批, 单张直接打开已有详情,多张只标「之前传过」、点开在原位加载;本地模式不受影响 - README 中英文同步已知限制与后续工作 - 测试:同字节重复、不同字节、失败后重传、老数据补齐、专用表建两次、删除一致性、 app 流程层重复响应与无该字段时行为不变;全部使用合成数据 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KTuFAfAdgU7H5k9kFMWkVP
- app:重复项记下上一张截图还剩几张待确认;未打开且还有待确认卡片的「之前传过」项 会拦住自动跳洞察页,并在分组里提示张数(比如上传回包丢了、服务器其实已处理完再重传) - app:查看上次结果的视图里,只要在本页确认或跳过过卡片,就恢复处理完自动跳洞察页 - app:整批都是重复时,上传页「查看待确认卡片」回退到第一张待确认卡片; 「之前都上传过」的提示只在整批都是重复时出现 - app:「查看上次的结果」按钮用独立的加载令牌,不会打断本页自己的加载 - server:记录哈希失败只记日志,已成功的上传照常返回 201,下次启动的补齐会补上 - server:补齐只处理服务器截图目录里的文件,不碰 CLI 指向的外部文件 - 测试:记哈希失败仍 201 且重启后能认出;目录外文件不补齐;待确认重复项的拦截规则 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KTuFAfAdgU7H5k9kFMWkVP
AliceLJY
marked this pull request as ready for review
September 25, 2026 18:12
This was referenced Sep 25, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
分步计划
main(4004351)开出claude/screenshot-dedup,先在未改动的代码上跑 server / app 的npm ci && npm test && npm run typecheck,记下现状。server/src/db.ts打开数据库时建服务器专用表server_screenshot_hashes(不经共享 schema,不动PRAGMA user_version),加「按哈希查找 / 记录哈希 / 列出缺哈希的老记录」三个方法;所有删除 screenshot 行的路径同步删哈希。POST /api/screenshots在图片写盘后、建 screenshot 行和调模型之前,算 SHA-256(node:crypto)并判重。命中返回 200 + 已有结果,并删掉刚写盘的文件;没命中照旧 201,整条处理成功后才记哈希。ScreenshotUploadResponse加duplicate_of_screenshot_id?。流程层不把重复响应的卡片当新卡,展示 notice;单张直接打开已有详情,多张只标记那一张,点开再看。本地模式不受影响。321800f里修掉,详见文末「审查后的补充修改」。响应格式的变化
POST /api/screenshots:没命中(新图片):和改动前逐字段一致,仍是
201,data只有screenshot_id、cards(测试对整个响应做了deepEqual)。命中(文件字节完全相同):返回
200。不建新行、不调perceiveScreenshot、不产生卡片或 observation,刚写盘的重复文件会被删掉:{ "ok": true, "data": { "screenshot_id": 12, "cards": ["…第 12 张截图当前的卡片,状态按数据库现状,不重置…"], "duplicate_of_screenshot_id": 12, "processing_notice": "这张截图之前上传过,没有重复处理,已为你显示上次的结果。" } }shared/types.ts与app/src/types.ts的ScreenshotUploadResponse都加了可选的duplicate_of_screenshot_id?: number。shared/types.ts同时补了processing_notice?: string:app 侧类型早就有这个字段,服务器要发它需要这个类型。/api/notes(粘贴文本)不变。哈希表结构与补齐方式
在
server/src/db.ts的MailuoDb构造函数里、共享 schema 初始化之后执行:shared/core/schema.ts/migrations.ts,不改PRAGMA user_version。测试流程:先建只有共享 schema 的库,再用MailuoDb打开两次;两次都不报错,user_version前后都是 1。screenshots,只返回还存在的截图。MailuoDb.deleteScreenshotUploadArtifacts会删 screenshot 行。它的调用方是cleanupPartialScreenshotUpload(/api/screenshots与/api/notes的失败清理),以及server/src/cli.ts里runE2eFlow的失败清理;cli.ts 没有独立的清理命令。现在这个方法在同一事务里先删哈希行;外键ON DELETE CASCADE再给以后新增的删除路径兜底(MailuoDb一直开着PRAGMA foreign_keys = ON)。saveScreenshotAnalysis成功之后才调recordScreenshotSha256。感知、归并、提议、落库任何一步失败,这次上传都会被清理掉;同一张图可以再传,也会被正常处理。测试还在处理进行中检查过哈希表,确认那时是空的。server/src/screenshot-hash.ts):每个进程在第一次判重前跑一次,之后的上传直接复用结果。如果补齐本身出错,只记日志、放行这次上传,下次上传再试。只补同时满足三个条件的行:已处理完成(raw_extraction非空)、文件在服务器自己的截图目录里(SCREENSHOT_DIR,默认server/data/screenshots/)、文件还读得到。服务器存的文件名唯一、从不改写;CLI 跑e2e时指向的外部文件可能被改写,所以不补。文件已经不在的、粘贴文本行(data:URI)、没处理完的残留行也都跳过。补到几条会写一条 info 日志:Backfilled hashes for earlier screenshot uploads。// simplified: no lock; concurrent identical uploads may both be processed。app 侧
只有收到带
duplicate_of_screenshot_id的响应时才走新逻辑。本地模式永远不返回这个字段,所有新分支都挂在这个字段上。app/src/flow-context.tsx):重复响应对应的批次项是status: "success"、cards: [],processingNotice是服务器给的提示,新字段duplicateOf记已有截图的 id,以及服务器回复时那张截图还剩几张待确认卡片。它的卡片不进本批待确认卡片,不算作卡片来源,也不抢「当前截图」。打开已有详情(getScreenshotDetail)时,详情挂到这一项上,批次里的其它项保持不动;原来这一步会整批替换。/review/<已有 id>,页面加载已有详情,显示提示和那张截图当前的卡片(已确认、已跳过、待确认都按数据库现状)。刚打开时就算没有待确认卡片,也不会自动跳到洞察页;只有在这一页确认或跳过过卡片、全部处理完之后,才照常跳过去。动了哪些文件
服务器
server/src/db.ts:建服务器专用表;新增findScreenshotIdBySha256、recordScreenshotSha256、listScreenshotsMissingSha256;deleteScreenshotUploadArtifacts同步删哈希。server/src/screenshot-hash.ts(新):hashFileSha256、backfillScreenshotHashes。server/src/app.ts:POST /api/screenshots判重、返回 200、成功后记哈希、每个进程补齐一次。shared/types.ts:响应类型加两个可选字段。app
app/src/types.ts:响应类型加duplicate_of_screenshot_id?。app/src/upload-batch.ts:isDuplicateUploadResponse、getDuplicateUploadItems、getUploadReviewScreenshotId。app/src/flow-context.tsx:批次项新增duplicateOf(类型FlowDuplicateOf);重复响应不加卡;findFlowItemForScreenshot、applyScreenshotDetailToItems、applyUploadResponseCards、hasUnopenedPendingDuplicate。app/app/(tabs)/index.tsx:决定进哪张确认页;整批重复时的提示;「批次处理结果」里的「之前传过」和「查看上次的结果」。app/app/review/[screenshotId].tsx:「之前传过」标记;在原位打开已有详情;看已有详情时不自动跳洞察页。文档:
README.md、README_EN.md的「已知限制 / 后续工作」,中英文各写各的。测试
server/src/tests/screenshot-hash.test.ts、app/src/tests/duplicate-upload.test.ts。另外server/src/tests/app.test.ts加了 5 个路由测试,server/src/tests/db.test.ts加了 3 个。server/src/tests/app.test.ts的「keeps interaction cards while only high-confidence progress…」原本把同一张fixtures/screenshot-1.png传两次,用来走两个置信度分支。现在同一文件会被判重,所以第二次改传fixtures/screenshot-2.png,测试本意(两次独立处理)不变。server/src/tests/db.test.ts的「initializes the full M1 schema」在表清单里加上server_screenshot_hashes。app/src/tests/transition-mitigation.test.ts检查「进确认页的入口都走scheduleReviewPush」,入口数从 2 改为 3。新入口openPreviousUpload也走这条防闪退路径;「全文件只有一处router.push('/review/…')」的断言保持不变。duplicateOf: null。示例联系人一类的占位名,以及fixtures/里已有的虚构截图。没做什么
app/src/local/)完全没动,本地模式重复上传仍会重复处理。app/src/upload-image.ts:最长边 1280、质量 0.8),所以「同一个文件」实际是同一端 app 对同一张原图压缩后的结果。/api/notes粘贴文本不判重。sqlite3命令行手动删screenshots行时(它默认不开外键),对应的哈希行不会跟着删;之后如果新行复用了同一个 id,可能认错。app 里所有删除路径都会同步删哈希,所以只在手动改库时才会遇到。shared/core/schema.ts、shared/core/migrations.ts、deploy/、.github/、PLAN.md、docs/PLAN-V2.md、版本号、tag、打包脚本;没加任何依赖(只用node:crypto)。本机验收(owner)
先备份
server/data/mailuo.sqlite和server/data/screenshots/,再用这个分支重启服务器。网页版需要重新构建:bash scripts/build-web.sh。sqlite3 server/data/mailuo.sqlite 'PRAGMA user_version; .schema server_screenshot_hashes'。user_version应和升级前一样(目前是 1),并能看到新表和索引。Backfilled hashes for earlier screenshot uploads,时机是本次进程的第一次上传。server/data/screenshots/里没有多出新文件,SELECT COUNT(*) FROM screenshots;的结果不变。DASHSCOPE_API_KEY改错,传一张新图,应该失败。改回来后再传同一张,应被正常处理(出新卡片),不会显示「之前传过」。验证命令输出
两条命令都在本次会话里原样跑过。完整 TAP 输出很长,下面只保留版本、安装摘要、测试汇总和 typecheck 结果。
以下是第二个提交
321800f上的结果。server:
cd server && npm ci && npm test && npm run typecheck,整条命令退出码 0。app:
cd app && npm ci && npm test && npm run typecheck。npm test有 5 个失败,退出码 1,&&链因此没走到 typecheck;typecheck 单独跑,退出码 0。这 5 个失败与本改动无关,只出现在这个云端容器里:
app/src/tests/review-fields.test.ts,报错是ERR_INVALID_RETURN_PROPERTY_VALUE: Expected a string, an ArrayBuffer, or a TypedArray to be returned for the "source" from the "load" hook but got undefined。node:module的registerHooks加载review-fields.tsx。app/package.json没有"type": "module",所以这个文件按 CommonJS 解析。容器里只有 Node 22.22.2,它的同步 load hook 不接受 CommonJS 返回空 source;CI 用的是 Node 22.23.2,没有这个问题。origin/main(4004351)上用同一个容器跑npm test,结果同样是# tests 193 / # pass 188 / # fail 5,失败的正是这 5 个。main最近一次 CI(run 35947581051,Node 22.23.2)是 193/193 全过。3737860的 CI 里 app 是 198/198、server 全过。审查后的补充修改(
321800f)独立审查没找到阻断性问题。下面几处它指出的边界情况都已修掉。第 2、4、6 条落在流程层或服务器上,补了测试,并临时改回旧逻辑确认测试会变红;第 1、3、5 条是页面上的接线,这里跑不了页面,只靠类型检查和逐步推演验证,需要在本机验收时顺带看一下:
两处有意没改:
🤖 Generated with Claude Code
https://claude.ai/code/session_01KTuFAfAdgU7H5k9kFMWkVP