Refly v0.2.3 版本解析:产品引导升级、计费模型重构与知识库永久删除机制
【免费下载链接】reflyThe first open-source agent skills builder. Define skills by vibe workflow, run on Claude Code, Cursor, Codex & more. Build Clawdbot 🦞· APIs for Lovable · Bots for Slack & Lark/Feishu · Skills are infrastructure, not prompts.项目地址: https://gitcode.com/GitHub_Trending/re/refly
导读:本文以 Refly v0.2.3 更新日志为主线,深入解读该版本在产品引导、计费方案、知识库展示与数据清理四个维度的核心变更。通过对照开源仓库中 canvas.service.ts、document.service.ts、resource.service.ts 与 subscription.service.ts 的源码实现,读者可以掌握 Refly 新用户初始化流程、会话/文件维度的计费口径,以及"软删除 + 异步级联清理"的知识库永久删除机制,并理解其与全栈搜索(FTS)、向量检索(RAG)和对象存储(OSS)之间的关系。
一、版本背景与发布要点
v0.2.3 是 Refly 在开源迭代过程中的一个"体验与稳定性"关键版本。它的整体定位可以概括为三件事:
- 让新用户快速上手:从初始化设置向导到交互式使用指南,再到功能点悬浮视频教程,全链路降低上手门槛;
- 让付费更直观:计费口径从 Token / 存储容量切换为会话次数与文件数量,并配套用量统计与低用量提醒;
- 让数据更可控:支持从知识库中永久删除资源或文档,并修复了退出登录、内容丢失、文档编辑等一系列核心使用问题。
该版本中"永久删除"相关能力在源码层面有完整的实现链路,是技术含量最高的部分,本文将重点展开。
二、产品引导全面升级:从初始化向导到交互式教程
2.1 产品初始化设置向导
v0.2.3 新增了产品初始化设置向导,用于在新用户首次进入产品时集中完成三项偏好配置:
| 配置项 | 说明 |
|---|---|
| 界面语言 | 设置产品 UI 展示语言,仓库中内置 en-US 与 zh-Hans 两套语言资源,可通过 i18n 包的翻译能力实时切换 |
| AI 回答语言 | 独立于界面语言,用于控制 AI 助手在内容创作中的输出语言 |
| 操作模式 | 鼠标 / 触控板,影响画布交互的缩放、平移与选择行为 |
该向导属于"一次性引导"而非常驻设置,其价值在于把过去散落在设置页中的偏好项前置到首次启动,减少新用户的理解成本。从仓库结构看,这一能力与 ai-workspace-common 中的通用组件体系(如 common 下的设置类组件)保持同一套设计语言。
2.2 核心功能介绍与交互式使用指南
- 核心功能介绍:以图文形式向新用户展示 Refly 的核心价值(AI 辅助内容创作、画布式知识组织等),帮助用户在动手操作前建立全局认知;
- 交互式使用指南:通过分步引导(Step-by-Step),带着用户实际走一遍"创建内容 → 引用知识 → 产出文档"的完整创作流程;
- 功能点悬浮视频教程:在产品界面关键功能点悬停时展示短视频演示,直观呈现该功能的使用方式。
这三者共同构成了"先认知、再实践、后深挖"的三级引导漏斗,与传统的纯文本帮助文档相比,交互式引导能显著降低新用户的首次使用挫败感。
三、付费方案全新优化:计费口径从 Token 走向会话与文件
3.1 计费方式变更的动机
旧方案以Token 消耗 + 存储容量为计量维度。Token 对普通用户过于抽象(用户难以预估一次对话消耗多少 Token),存储容量又与内容创作的实际价值脱节。v0.2.3 将计费口径调整为:
- 会话次数:对应 AI 交互的请求量级,与用户实际使用频率直接相关;
- 文件数量:对应知识库与文档存储规模,与用户沉淀的内容量直接相关。
这一变更在源码中有清晰的印证。在 subscription.service.ts 中,订阅额度(Quota)的读写大量围绕fileCountQuota/fileCountUsed展开:
- 第 536 行附近:在订阅信息响应中返回
fileCountQuota(文件数量配额),其兜底值来自config.get('quota.storage.file'); - 第 836-838 行:通过
meter.fileCountQuota < 0判断是否不限量,否则计算剩余配额meter.fileCountQuota - meter.fileCountUsed; - 第 1255 行:
fileCountUsed: resourceCount + docCount,即已用文件数 = 资源数(Resource)与文档数(Document)之和; - 会话维度对应
t1CountQuota/t2CountQuota(第 510-512 行),以请求配额的形式落地,兜底值来自quota.request.t1/quota.request.t2。
同时,在删除资源/文档(见第四节)时,源码会调用subscriptionService.syncStorageUsage(user)(document.service.ts、resource.service.ts)实时同步存储用量,确保配额数字在删除后立刻回退,这体现了"文件数量"计费与知识库生命周期管理的强耦合设计。
3.2 配套能力:用量统计与低用量提醒
- 用量统计展示:在订阅/用量页面可视化展示当前会话与文件的使用进度(如
fileCountUsed / fileCountQuota),让用户"随时掌握使用情况"; - 用量不足提醒:当配额接近上限时给出及时提醒,并附带补充指引(升级套餐或清理冗余数据)。
这两项配套能力解决了旧方案"看不到消耗、用超了才知道"的痛点,是"计费更清晰直观"目标在体验层的落地。
四、知识库体验升级与永久删除机制(核心技术点)
4.1 文档与资源预览功能
v0.2.3 为知识库新增了文档和资源预览能力。用户在画布或知识库列表中选中某条数据时,无需进入编辑态即可预览其内容,界面更加直观。从 knowledge.controller.ts 的接口签名可以看到,知识库查询支持按canvasId过滤,说明预览/列表接口与画布上下文深度绑定,即"在画布语境下浏览该画布关联的知识库条目"。
4.2 永久删除:软删除 + 异步级联清理
在 v0.2.3 之前,删除资源或文档只影响画布节点,知识库中的原始数据仍被保留(因为知识库内容可能被多个画布/文档引用,直接物理删除存在风险)。本版本引入了两条"永久删除"路径:
- 删除节点时永久删除:在画布中删除某资源/文档节点时,可选择同时从知识库中永久删除该资源或文档;
- 删除画布时批量清理:删除整个画布时,可一并删除画布中的所有资源与文档。
4.2.1 文档的删除链路
文档删除的入口是 document.service.ts 的deleteDocument:
deleteDocument(user, { docId }) → 校验文档存在(docId + uid + deletedAt: null) → prisma.document.update 写入 deletedAt = now(软删除标记) → subscriptionService.syncStorageUsage(user)(同步文件配额) → postDeleteKnowledgeQueue.add('postDeleteKnowledgeEntity')(投递异步清理任务)真正的"永久"清理发生在异步的postDeleteDocument(第 434-459 行),它在一个清理任务中并行完成:
- 将关联的标签实例(
labelInstance)标记删除; - 调用
ragService.deleteDocumentNodes(user, docId)删除向量库(Qdrant)中的文档切片节点; - 调用
fts.deleteDocument(user, 'document', docId)删除 Elasticsearch 全栈搜索索引; - 通过
canvasQueue.add('deleteNodes')同步清理画布上对应节点; - 若存在
storageKey/stateStorageKey,调用oss.removeObject(...)从对象存储中删除原始文件与状态文件。
4.2.2 资源的删除链路
资源(Resource,如网页、上传文件等)走相同的两段式设计。deleteResource(resource.service.ts)先写deletedAt并同步配额、投递异步任务;postDeleteResource(第 912 行起)再执行:
ragService.deleteResourceNodes(user, resourceId):删除向量节点;fts.deleteDocument(user, 'resource', resourceId):删除搜索索引;canvasQueue.add('deleteNodes'):清理画布节点;- 按需从 OSS 删除
storageKey对应的原始文件。
4.2.3 删除画布时的批量清理
画布删除入口在 canvas.service.ts 的deleteCanvas。关键设计是请求体中的deleteAllFiles(默认true)参数:
- 先将画布本身标记
deletedAt = now(立即生效,避免重复删除); - 再向
postDeleteCanvasQueue投递postDeleteCanvas任务,携带uid / canvasId / deleteAllFiles; - 队列任务配置了
jobId: canvas-cleanup-{canvasId}(幂等去重)、attempts: 3与指数退避(backoff: { type: 'exponential', delay: 1000 }),保证弱网或瞬时失败下可重试; - 在桌面端(
isDesktop())场景,由于没有独立的队列进程,则直接同步调用postDeleteCanvas完成清理。
deleteAllFiles = true时,画布内全部资源与文档会级联走 4.2.1 / 4.2.2 的永久删除链路——这正是更新日志中"删除画布时删除画布中所有资源或文档"的源码级实现。
4.3 为什么要"软删除 + 异步清理"
这种两段式设计并非多此一举:
- 响应速度:删除操作只需一次数据库更新即可返回,重活(向量删除、索引删除、OSS 删除)全部异步化,弱网环境下也能快速完成主流程;
- 可恢复性:
deletedAt置位前数据仍在库中,为将来"回收站 / 恢复"能力预留了空间; - 级联一致性:知识库数据同时存在于 MySQL、Elasticsearch、Qdrant、OSS 四处,必须通过队列串行/并行清理,避免出现"库删了但搜索还能搜到"的数据孤儿。
五、核心问题优化与稳定性修复
v0.2.3 同时修复了 5 个高频问题,集中在会话安全、编辑器健壮性与弱网容错:
| 问题 | 表现 | 修复方向 |
|---|---|---|
| 🌐 频繁退出登录 | 会话被意外失效,用户反复重新登录 | 排查鉴权/会话刷新链路,从源码看 auth 模块 负责 Token 签发与刷新,相关修复围绕会话生命周期与刷新竞态展开 |
| 🔑 网页内容复制到 AI 文档导出丢失 | 网页复制内容(含富文本)导出后内容缺失 | 涉及 editor 的复制/粘贴数据转换与导出序列化链路的兼容性修复 |
| 🔄 文档标题最后一个字符无法删除 | 输入法/受控组件状态导致末字符残留 | 属于文档编辑器受控输入(Controlled Input)状态同步的边界问题 |
| 📄 文档编辑器空图片地址报错 | 空src的图片触发渲染异常 | 编辑器对空图片地址增加防御性校验,避免无效资源请求 |
| ⚡️ 弱网下删除最后画布跳转异常 | 删除后导航状态与网络结果不一致 | 结合 4.2.3 的队列重试与deleteAllFiles语义,删除主流程先落库再异步清理,降低对网络往返的依赖 |
其中"频繁退出登录"与"弱网删除异常"两个修复在架构层面与本版本的异步化、会话生命周期设计直接相关,体现了该版本"稳定性优先"的取向。
六、小结:v0.2.3 的版本价值
综合来看,v0.2.3 是 Refly 在"产品化"道路上的一个里程碑式版本:
- 面向新用户:初始化向导 + 交互式指南 + 视频教程,形成完整的上手闭环;
- 面向付费用户:会话次数 / 文件数量的计费口径配合用量统计与低用量提醒,让消费透明可控;
- 面向数据治理:文档 / 资源 / 画布三级"永久删除"能力,配合"软删除 + 队列异步级联清理"的实现,兼顾了响应速度、数据一致性与未来可恢复性。
对于希望深入源码的读者,建议重点阅读 canvas.service.ts 的deleteCanvas/postDeleteCanvas、document.service.ts 与 resource.service.ts 的删除/清理方法,以及 subscription.service.ts 中fileCountQuota/t1CountQuota等配额字段的计量逻辑——它们共同构成了本版本"体验升级 + 计费重构 + 数据可控"三条主线的实现底座。
【免费下载链接】reflyThe first open-source agent skills builder. Define skills by vibe workflow, run on Claude Code, Cursor, Codex & more. Build Clawdbot 🦞· APIs for Lovable · Bots for Slack & Lark/Feishu · Skills are infrastructure, not prompts.项目地址: https://gitcode.com/GitHub_Trending/re/refly
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考