聊天附件设计:文本提取与文件存储分开
FodyChat 将附件操作拆成文本提取和原文件上传:前者先把提取结果放入输入框供用户确认,后者作为可移除附件随消息发送。两种用途需要分别设计大小限制、错误处理和界面,客服截图也未必需要交给模型读取。
AI 深度解读
给 AI 聊天窗口加附件,难点往往不在模型能不能读文件,而在文件交给谁、经过哪里、允许多大,以及何时真正删除。FodyChat 作者的结论是:先把文件当成客服业务里的独立对象,再决定哪些环节需要 AI。
这篇经验值得前端和全栈开发者读几分钟。它把两个上传入口、浏览器直传、恶意文件检查和清理机制串成了一条完整链路,也暴露了「套餐不限量」与运行平台硬限制之间的冲突。不过,作者给出的是早期产品的工程取舍,没有性能测试或安全效果的对照数据。
两个上传按钮,分别解决什么问题
作者把用户上传文件的意图分成两类:「帮我读」和「替我保存」。
前者需要提取文本,再交给模型理解;后者可能只是提交损坏零件的照片、收据或合同,目标是把文件送到接收者手里,并不一定需要解析。两种行为的大小限制、失败提示和交互方式都不同,放进一个回形针入口,容易让用户猜测接下来会发生什么。
因此,FodyChat 在「+」下面提供两个选项:
- 「Extract text from a file」把提取结果放进输入框,让用户检查后再发送。
- 「Upload a file」把文件附到下一条消息上,以可移除的附件标签展示。
这里有个很实用的细节:提取成功不等于立即发送。用户能先检查内容,决定哪些文字应该进入对话。
图片也是如此。作者差点默认让 AI 读取访客截图,后来意识到,客服场景中最需要看懂截图的,通常是 Telegram、Slack 或邮件另一端的人工客服。
他们于是把视觉转写,也就是 OCR 加简短描述,优先交给接收方。访客上传后,客服收到原文件和转写内容;客服上传后,访客看到文件卡片和文字说明。让聊天 AI 理解图片,则是付费套餐的附加能力。
文件先服务于沟通,模型读取只是其中一种用途。
浏览器直传,解决的是哪一层限制
FodyChat 默认把文件放进自己的 R2 bucket,但部分客户希望自行掌握存储位置。作者为此抽出了一个只有 put、get、delete 的存储接口,支持 R2,也支持客户提供的任意 S3-compatible bucket。
接口还提供 afterPut、afterDelete 生命周期钩子,可以向客户 webhook 发起 POST,让客户接上 OCR、病毒扫描或索引流程。
这层抽象随后解决了另一个问题:套餐允许上传,不代表运行环境接得住。
按作者描述,他们的 Cloudflare Workers 环境受到 100 MB 请求体和免费方案 128 MB 内存的限制。接收一个 100 MB 文件后,再转成 base64 发给扫描器,内存就可能超标。因此,定价表里的「unlimited」无法直接兑现为单次上传能力。
他们做了两层处理。普通上传同时受套餐限制和全局 MAX_UPLOAD_BYTES 约束,后者默认 25 MB;确实需要大文件时,则由 Worker 签发预签名 PUT URL,浏览器直接把文件传进客户的 S3 bucket,文件内容不经过 Worker。
预签名 URL 可以理解为一张临时上传凭证。Worker 负责授权,浏览器负责传输,服务器不必充当文件中转站。按照作者的设计,这条路径的文件大小上限转由客户的 S3 限制决定。
不过,原文没有展开直传完成后如何核验文件、如何衔接扫描,以及如何确认附件已经可以发送。这些仍是落地时需要补齐的环节。
上传完成以后,扫描和删除才刚开始
客服收到的是任意用户提交的文件,最终可能在自己的电脑上打开。作者没有在 Worker 里塞进完整杀毒引擎,因为仅 ClamAV 的特征库就有数百 MB;团队当时还没有客户,也不知道实际需求会有多大。
他们先做了两层检查:在 Worker 内根据文件魔数拒绝 MZ、ELF 等可执行文件,并检查图片内容是否符合图片格式;需要更完整的扫描时,再配置 AV_SCAN_URL,把文件送往外部扫描服务,收到恶意判定就拒绝。
扫描器故障时,他们选择 fail-open:记录日志,但继续放行文件,避免整个聊天功能随扫描服务一起不可用。作者承认这不是企业级杀毒方案,所谓免费的「80%」也没有对应的检出率数据,应当理解为经验判断。
文件生命周期的另一端是清理。未发送的孤立上传会在 24 小时后过期,已发送附件按套餐保留,租户离开时则级联清理文件。
删掉数据库记录,不等于删掉了文件。
FodyChat 的设计是在消息删除、保留期到期和租户退出时,同时删除存储中的字节与数据库记录。客户自带 S3 或自行部署时,原文件留在客户存储中,作者称他们不会另留副本。
但原文没有说明两次删除中途失败怎么办,也没有交代重试和核对机制。「真正删除」是明确的目标,跨数据库与对象存储的一致性实现仍未说清。
我的判断
我认同作者把上传意图、接收者和生命周期排在模型能力前面。对客服产品而言,文件能否准确送达、是否会误发、多久后被清理,比默认触发一次图片理解更贴近实际需求。
两个入口也比一个自动判断所有意图的入口更容易解释,但它有场景前提:用户确实同时需要「提取文字」和「提交原件」。如果产品只做文档问答,增加保存入口未必有收益。
安全方面,这套方案适合作为早期防线。文件魔数和图片格式检查覆盖的是明显异常,扫描器故障放行则把可用性放在前面。面对合同、收据等业务文件,团队应根据接收者打开文件的方式选择故障策略。作者没有给出与完整扫描方案的对照数据,不能据此认定这套检查已经足够。
存储接口和直传设计有参考价值,但接入真实业务时,还要把扫描、发送和删除之间的状态衔接补完整。
能拿来做什么
- 做 AI 聊天附件时,先区分「提取后提交」和「发送原文件」。提取结果进入可编辑输入框,附件则在发送前允许移除,让用户明确知道会提交什么。
- 用 Node 或 Worker 接收文件时,同时核对套餐额度、单次请求上限和内存开销;如果还要转 base64 或调用扫描服务,不能只按原文件大小估算资源。
- 需要大文件上传时,可以采用服务端签发预签名 PUT URL、浏览器直传对象存储的路径,并补上上传完成确认和扫描状态处理。
- 给附件功能列出消息删除、上传未发送、保留期到期、租户退出四类清理入口,逐一确认数据库记录与对象文件都有对应的删除动作。