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 调用时的参数安全性。

需要源码私我

相关推荐
wafrdggh1 小时前
2026电商快递批量查询怎么用?固乔助手模板设置评测
大数据·数据库·小程序
一个天蝎座 白勺 程序猿1 小时前
KingbaseES数据库归档重做日志文件管理:从配置到运维的实战指南
运维·数据库·oracle·kingbasees·金仓数据库
红尘散仙2 小时前
谁说终端只能敲键盘?我用 Rust 给小说阅读器加上了鼠标
前端·后端·rust
特立独行的猫A2 小时前
我给女儿写了个打字练习:一边敲键盘,一边把小学英语单词记牢
java·后端·python
cpolar技术支持2 小时前
「我这边连不上你那个服务」—— 用清单驱动的 TCP 连通性只读看板,cpolar 让远端同事自己取证
python·内网穿透·tcp·cpolar·网络诊断
陈随易3 小时前
C++26 草案被否,二十八年来头一回
前端·后端·程序员
ZhengEnCi3 小时前
Agent 工具怎么折叠海量输出才「爽」:Trae、Claude Code 的渐进式披露设计原理
前端·agent
云和数据.ChenGuang3 小时前
LangGraph 异步 Node 的设计与写法实践
开发语言·人工智能·python·机器学习·php
天诰4 小时前
SA-K-11 检索会消失吗与四个未解问题
java·服务器·人工智能·python·机器学习·ai编程
龙腾AI白云5 小时前
仿真到现实(Sim-to-Real)迁移:跨越仿真与现实鸿沟
数据库·机器学习·plotly·知识图谱·tornado