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 参数的预处理逻辑:
-
获取收藏 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() -
ID 列表控制 (Include/Exclude):
-
当
liked=True(请求收藏页) : 将include_ids设置为上述查询到的liked_conversation_ids。如果同时存在pinned过滤,则取交集。 -
当
liked=False(请求历史页) : 将liked_conversation_ids添加到exclude_ids中,确保历史页签不显示已收藏的内容(具体视需求而定,代码逻辑倾向于隔离)。
-
-
服务调用 : 将处理后的
liked状态以及隐含的include_ids/exclude_ids传递给底层分页服务。Python
return WebConversationService.pagination_by_last_id( # ... 其他参数 liked=liked, # 传递处理后的参数 # ... )
3. 前端详细修改
3.1 API 服务层 (service/share.ts)
-
fetchConversations修改 : 函数签名增加liked?: boolean参数,并在请求 params 中构造liked: 'true'/'false'。 -
新增
updateConversationLike: 实现PATCH请求,用于变更会话的收藏状态。TypeScript
export const updateConversationLike = async (..., liked: boolean) => { return getAction('patch', ...)(url, { body: { liked } }) }
3.2 状态管理 Hook (useChatWithHistory.ts)
-
双 SWR 数据源: 保留原有的历史会话 SWR,新增专门用于获取收藏列表的 SWR:
TypeScript
const { data: appStarredConversationData, ... } = useSWR( appId ? ['appConversationData', ..., true] : null, // Key 中包含 true 以区分缓存 () => fetchConversations(..., true), // 传入 liked=true { ... } ) -
表单默认值逻辑修正 : 在处理
inputsForms时,修复了defaultValue变量作用域和覆盖的问题,确保file-list等特殊类型能正确初始化。 -
操作函数实现 : 新增
handleLikeConversation和handleUnlikeConversation。-
包含 App ID 非空校验 (Guard Clause)。
-
调用 API 成功后,触发
handleUpdateConversationList同时刷新历史和收藏两个列表。
-
3.3 UI 组件层 (Sidebar.tsx)
-
页签状态管理 : 引入
activeTab状态 ('history' | 'starred')。 -
导航栏渲染 : 新增 Tab 切换区域,根据
activeTab切换样式。 -
列表条件渲染:
-
activeTab === 'history':渲染置顶列表 + 普通历史列表(带时间分组)。 -
activeTab === 'starred':渲染filteredStarredConversations(纯列表,不带分组)。
-
-
操作绑定 : 在
handleOperate中增加like和unlike的分支处理,连接到 Context 中的操作函数。
4. 遇到的问题与解决方案
在开发过程中,主要解决了以下几个关键问题:
4.1 TypeScript 类型与 Context 同步问题
-
问题 :在
Sidebar中使用starredConversationList报错"属性不存在",或者 Hook 返回值与 Context 定义不匹配。 -
解决:
-
修改
ChatWithHistoryContextValue类型定义,显式增加starredConversationList及操作函数类型。 -
修改 Hook 的
return语句,确保返回的对象键名 (starredConversationList) 与 Context 类型完全一致(之前错误写成了starredConversation)。 -
在
ChatWithHistoryWrap的Providervalue 中正确传入这些新属性。
-
4.2 ESLint 代码质量报错
-
问题 :
no-dead-store和unused-vars报错,主要是handleDelete和handleRename定义了但未被 JSX 使用。 -
解决:
-
恢复被注释的
handleDelete和handleRename函数定义。 -
在 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 调用时的参数安全性。
需要源码私我