Dify二次开发-新增聊天会话收藏(页签)功能实现方案

1. 核心思路

本项目标是在不修改核心会话表结构(即不新增 is_starred 字段)的前提下,利用现有的反馈机制或关联表,实现会话的收藏与展示分离。

  • 数据源逻辑 :后端通过查询 MessageFeedback 表(或关联表)中标记为 like 的记录来确定哪些 conversation_id 属于收藏会话。

  • API 过滤策略:

    • 请求 liked=true:后端计算出收藏的 ID 列表,并通过 include_ids 强制只返回这些会话。

    • 请求 liked=false (或默认):后端计算出收藏的 ID 列表,并通过 exclude_ids 将其排除(或视业务逻辑而定),实现"历史"与"收藏"的数据隔离。

  • 前端双流架构:

    • 前端维护两个独立的数据流(SWR Hooks):一个获取常规历史记录,一个专门获取收藏列表。

    • 通过客户端状态 activeTab 控制 UI 展示,避免频繁刷新整个页面。


2. 后端详细修改

涉及文件 :ConversationListApi (Controller层)

2.1 参数解析扩展

在 get 方法的 RequestParser 中新增了 liked 参数的接收:

Python

复制代码
# 接收前端传递的 liked 过滤参数 ('true', 'false' 或 None)
parser.add_argument("liked", type=str, choices=["true", "false", None], location="args")

2.2 核心过滤逻辑实现

在调用 Service 层之前,插入了针对 liked 参数的预处理逻辑:

  1. 获取收藏 ID 集合 : 查询 MessageFeedback 表,筛选出当前用户、当前 App 下 rating == 'like' 的所有 conversation_id。

    Python

    复制代码
    liked_stmt = (
        select(MessageFeedback.conversation_id)
        .where(
            MessageFeedback.app_id == app_model.id,
            MessageFeedback.rating == 'like',
            # ... 用户身份校验 ...
        )
        .distinct()
    )
    liked_conversation_ids = session.scalars(liked_stmt).all()
  2. ID 列表控制 (Include/Exclude):

    • 当 liked=True (请求收藏页) : 将 include_ids 设置为上述查询到的 liked_conversation_ids。如果同时存在 pinned 过滤,则取交集。

    • 当 liked=False (请求历史页) : 将 liked_conversation_ids 添加到 exclude_ids 中,确保历史页签不显示已收藏的内容(具体视需求而定,代码逻辑倾向于隔离)。

  3. 服务调用 : 将处理后的 liked 状态以及隐含的 include_ids/exclude_ids 传递给底层分页服务。

    Python

    复制代码
    return WebConversationService.pagination_by_last_id(
        # ... 其他参数
        liked=liked, # 传递处理后的参数
        # ...
    )

3. 前端详细修改

3.1 API 服务层 (service/share.ts)

  1. fetchConversations 修改 : 函数签名增加 liked?: boolean 参数,并在请求 params 中构造 liked: 'true'/'false'。

  2. 新增 updateConversationLike : 实现 PATCH 请求,用于变更会话的收藏状态。

    TypeScript

    复制代码
    export const updateConversationLike = async (..., liked: boolean) => {
      return getAction('patch', ...)(url, { body: { liked } })
    }

3.2 状态管理 Hook (useChatWithHistory.ts)

  1. 双 SWR 数据源: 保留原有的历史会话 SWR,新增专门用于获取收藏列表的 SWR:

    TypeScript

    复制代码
    const { data: appStarredConversationData, ... } = useSWR(
      appId ? ['appConversationData', ..., true] : null, // Key 中包含 true 以区分缓存
      () => fetchConversations(..., true), // 传入 liked=true
      { ... }
    )
  2. 表单默认值逻辑修正 : 在处理 inputsForms 时,修复了 defaultValue 变量作用域和覆盖的问题,确保 file-list 等特殊类型能正确初始化。

  3. 操作函数实现 : 新增 handleLikeConversation 和 handleUnlikeConversation。

    • 包含 App ID 非空校验 (Guard Clause)。

    • 调用 API 成功后,触发 handleUpdateConversationList 同时刷新历史和收藏两个列表。

3.3 UI 组件层 (Sidebar.tsx)

  1. 页签状态管理 : 引入 activeTab 状态 ('history' | 'starred')。

  2. 导航栏渲染 : 新增 Tab 切换区域,根据 activeTab 切换样式。

  3. 列表条件渲染:

    • activeTab === 'history':渲染置顶列表 + 普通历史列表(带时间分组)。

    • activeTab === 'starred':渲染 filteredStarredConversations(纯列表,不带分组)。

  4. 操作绑定 : 在 handleOperate 中增加 like 和 unlike 的分支处理,连接到 Context 中的操作函数。


4. 遇到的问题与解决方案

在开发过程中,主要解决了以下几个关键问题:

4.1 TypeScript 类型与 Context 同步问题

  • 问题 :在 Sidebar 中使用 starredConversationList 报错"属性不存在",或者 Hook 返回值与 Context 定义不匹配。

  • 解决:

    1. 修改 ChatWithHistoryContextValue 类型定义,显式增加 starredConversationList 及操作函数类型。

    2. 修改 Hook 的 return 语句,确保返回的对象键名 (starredConversationList) 与 Context 类型完全一致(之前错误写成了 starredConversation)。

    3. 在 ChatWithHistoryWrap 的 Provider value 中正确传入这些新属性。

4.2 ESLint 代码质量报错

  • 问题 :no-dead-store 和 unused-vars 报错,主要是 handleDelete 和 handleRename 定义了但未被 JSX 使用。

  • 解决:

    1. 恢复被注释的 handleDelete 和 handleRename 函数定义。

    2. 在 JSX 底部正确渲染 <Confirm> 和 <RenameModal> 组件,并将上述函数作为 onConfirm 属性传入,形成闭环引用。

4.3 表单初始化逻辑缺陷

  • 问题 :defaultValue 在 forEach 循环中被重新赋值为 item.default,导致针对 file-list 或 role 的特殊处理逻辑失效。

  • 解决 :优化逻辑,确保特殊处理后的 defaultValue 直接赋值给 conversationInputs,不再被后续代码覆盖。

4.4 AppID 类型安全

  • 问题 :调用 updateConversationLike 时,appId 可能为 undefined,导致 TS 报错或运行时隐患。

  • 解决 :在操作函数开头增加 if (!appId) return 的保护性判断,确保 API 调用时的参数安全性。

需要源码私我

相关推荐
子兮曰4 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰4 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
默_笙4 天前
🍙 给每个请求过安检:FastAPI 是怎么把校验写进类型注解的
python
前端小万4 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝4 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
qq_426003964 天前
启动playwright录制codegen生成自动化测试脚本
python·自动化
虎头金猫4 天前
4K 视频总卡在公网带宽?用 N1 + OpenList 把网盘播放链路重新理顺
运维·服务器·网络·python·容器·beautifulsoup·pandas
三十而立洋4 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
长沙三为智能科技4 天前
家政小程序开发从0到上线:五阶段交付流程与验收清单
python
伞伞悦读4 天前
【第38期】Python 模块与包详解:import、from、模块搜索路径、包结构和 __init__
开发语言·python