feat: 对话列表支持分页、收藏、置顶

收藏(is_favorite)/置顶(is_pinned)作为布尔标记存入对话已有 metadata,
复用现有 PATCH /api/threads/{id} 与 search 往返,无需后端改动与迁移。

- hooks: 新增 useUpdateThreadMetadata + useToggleThreadPin/Favorite
- utils: 新增 isThreadPinned/isThreadFavorite/sortThreadsPinnedFirst
- 侧边栏: 置顶优先排序 + 下拉菜单收藏/置顶项 + 置顶图标
- 对话页: 置顶区 + 全部/收藏过滤 + 页码翻页(每页20) + 行内收藏/置顶按钮
- i18n: zh-CN/en-US/types 新增相关文案

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
吉鑫升andClaude Opus 4.8 committed 2026-06-08 18:01:03 +08:00
1 parent 80d2db6315
commit 50d70a5050
7 files changed
+489 -139

No files matched your search

+22
View File
@@ -81,6 +81,28 @@ export function titleOfThread(thread: AgentThread, fallback = "Untitled") {
return fallback;
}
export function isThreadPinned(thread: AgentThread): boolean {
return (thread.metadata as Record<string, unknown> | null)?.is_pinned === true;
}
export function isThreadFavorite(thread: AgentThread): boolean {
return (thread.metadata as Record<string, unknown> | null)?.is_favorite === true;
}
/**
* Stable sort that lifts pinned (置顶) threads to the top while preserving the
* incoming order (typically `updated_at` desc) within both the pinned and the
* unpinned group.
*/
export function sortThreadsPinnedFirst(threads: AgentThread[]): AgentThread[] {
const pinned: AgentThread[] = [];
const rest: AgentThread[] = [];
for (const thread of threads) {
(isThreadPinned(thread) ? pinned : rest).push(thread);
}
return [...pinned, ...rest];
}
export function sanitizeThreadTitleForDisplay(title: string | null | undefined) {
if (!title) return "Untitled";