feat(侧边栏): 集成标签筛选功能
- 集成 TagFilter 组件实现标签筛选 - 根据选中标签过滤对话列表 - 在对话项中显示标签(极简 # 文字样式) - 优化对话列表布局适应标签显示
This commit is contained in:
parent
c2b97f0f2d
commit
bbe07e203e
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user