feat(侧边栏): 集成标签筛选功能

- 集成 TagFilter 组件实现标签筛选
- 根据选中标签过滤对话列表
- 在对话项中显示标签(极简 # 文字样式)
- 优化对话列表布局适应标签显示
This commit is contained in:
gaoziman 2025-12-28 17:28:48 +08:00
parent c2b97f0f2d
commit bbe07e203e

View File

@ -6,11 +6,13 @@ import { Plus, PanelLeft, Trash2, MoreHorizontal, Loader2, Pencil, Check, X, Bot
import { UserMenu } from '@/components/ui/UserMenu'; import { UserMenu } from '@/components/ui/UserMenu';
import { NewChatModal } from '@/components/features/NewChatModal'; import { NewChatModal } from '@/components/features/NewChatModal';
import { SearchModal } from '@/components/features/SearchModal'; import { SearchModal } from '@/components/features/SearchModal';
import { TagFilter } from '@/components/features/Tags';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { useConversations } from '@/hooks/useConversations'; import { useConversations } from '@/hooks/useConversations';
import { useTagFilter } from '@/hooks/useTags';
import { useAuth } from '@/providers/AuthProvider'; import { useAuth } from '@/providers/AuthProvider';
import type { Conversation } from '@/drizzle/schema'; import type { Conversation } from '@/drizzle/schema';
import { useState, useRef, useEffect, useCallback } from 'react'; import { useState, useRef, useEffect, useCallback, useMemo } from 'react';
import { useHotkeys } from '@/hooks/useHotkeys'; import { useHotkeys } from '@/hooks/useHotkeys';
interface SidebarProps { interface SidebarProps {
@ -22,6 +24,7 @@ export function Sidebar({ isOpen = true }: SidebarProps) {
const pathname = usePathname(); const pathname = usePathname();
const router = useRouter(); const router = useRouter();
const { conversations, loading, deleteConversation, updateConversation } = useConversations(); const { conversations, loading, deleteConversation, updateConversation } = useConversations();
const { selectedTags, selectTag, clearFilter, matchesFilter } = useTagFilter();
const { user } = useAuth(); const { user } = useAuth();
const [menuOpen, setMenuOpen] = useState<string | null>(null); const [menuOpen, setMenuOpen] = useState<string | null>(null);
const [editingId, setEditingId] = useState<string | null>(null); const [editingId, setEditingId] = useState<string | null>(null);
@ -32,6 +35,12 @@ export function Sidebar({ isOpen = true }: SidebarProps) {
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
const menuRef = useRef<HTMLDivElement>(null); const menuRef = useRef<HTMLDivElement>(null);
// 根据标签筛选对话
const filteredConversations = useMemo(() => {
if (selectedTags.length === 0) return conversations;
return conversations.filter((conv) => matchesFilter(conv.tags as string[] | null));
}, [conversations, selectedTags, matchesFilter]);
// 聚焦输入框 // 聚焦输入框
useEffect(() => { useEffect(() => {
if (editingId && inputRef.current) { if (editingId && inputRef.current) {
@ -157,8 +166,8 @@ export function Sidebar({ isOpen = true }: SidebarProps) {
} }
}; };
// 按时间分组对话 // 按时间分组对话(使用筛选后的列表)
const groupedConversations = groupConversationsByTime(conversations); const groupedConversations = groupConversationsByTime(filteredConversations);
return ( return (
<> <>
@ -200,6 +209,17 @@ export function Sidebar({ isOpen = true }: SidebarProps) {
</button> </button>
</div> </div>
{/* 标签筛选 */}
<TagFilter
onFilterChange={(tags) => {
if (tags.length > 0) {
selectTag(tags[0]);
} else {
clearFilter();
}
}}
/>
{/* 助手库入口 */} {/* 助手库入口 */}
<div className="px-4 pb-2"> <div className="px-4 pb-2">
<Link <Link
@ -301,13 +321,23 @@ export function Sidebar({ isOpen = true }: SidebarProps) {
<Link <Link
href={`/chat/${conversation.conversationId}`} href={`/chat/${conversation.conversationId}`}
className={cn( className={cn(
'block px-3 py-2 rounded-lg text-sm cursor-pointer transition-colors truncate pr-8', 'block px-3 py-2 rounded-lg text-sm cursor-pointer transition-colors pr-8',
isActive isActive
? 'bg-[var(--color-bg-tertiary)] text-[var(--color-text-primary)]' ? 'bg-[var(--color-bg-tertiary)] text-[var(--color-text-primary)]'
: 'text-[var(--color-text-secondary)] hover:bg-[var(--color-bg-hover)]' : 'text-[var(--color-text-secondary)] hover:bg-[var(--color-bg-hover)]'
)} )}
> >
{conversation.title} <div className="truncate">{conversation.title}</div>
{/* 标签 - 极简文字式 */}
{conversation.tags && (conversation.tags as string[]).length > 0 && (
<div className="mt-1 text-[10px] text-[var(--color-text-muted)] truncate">
{(conversation.tags as string[]).slice(0, 3).map((tag, index) => (
<span key={tag} className="text-[var(--color-text-tertiary)]">
<span className="opacity-50">#</span>{tag}{index < Math.min((conversation.tags as string[]).length, 3) - 1 ? ' ' : ''}
</span>
))}
</div>
)}
</Link> </Link>
)} )}