import { knowledgetoolUrl } from '@/hooks/useExpertAuthBootstrap'; import { Archive, Loader2, MessageSquareText, Plus, RefreshCw, Trash2, X } from 'lucide-react'; import { useCallback, useEffect, useMemo, useState } from 'react'; export interface SkillCreatorThread { id: number; title: string; archived: boolean; expert_agent_id: number | null; created_at: string; updated_at: string; } interface ThreadListResponse { threads?: SkillCreatorThread[]; archived_threads?: SkillCreatorThread[]; } interface SkillCreatorThreadSidebarProps { authedFetch: (input: Parameters[0], init?: Parameters[1]) => Promise; currentThreadId: string; isDarkMode: boolean; isOpen: boolean; isBusy?: boolean; createThreadPayload?: Record; createButtonText?: string; emptyText?: string; subtitle?: string; threadFilter?: (thread: SkillCreatorThread) => boolean; onClose: () => void; onThreadSelect: (threadId: string) => void; } const THREADS_API_URL = `${knowledgetoolUrl}/api/v1/expert-agent-chat/threads`; const sortThreads = (threads: SkillCreatorThread[]) => [...threads].sort((a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime()); export default function SkillCreatorThreadSidebar({ authedFetch, currentThreadId, isDarkMode, isOpen, isBusy = false, createThreadPayload, createButtonText = '新建聊天', emptyText = '暂无记录', subtitle = '切换或管理当前会话', threadFilter, onClose, onThreadSelect, }: SkillCreatorThreadSidebarProps) { const [threads, setThreads] = useState([]); const [archivedThreads, setArchivedThreads] = useState([]); const [isLoading, setIsLoading] = useState(false); const [isCreating, setIsCreating] = useState(false); const [deletingThreadId, setDeletingThreadId] = useState(null); const [error, setError] = useState(''); const applyFilter = useCallback( (items: SkillCreatorThread[]) => (threadFilter ? items.filter(threadFilter) : items), [threadFilter], ); const refreshThreads = useCallback(async () => { setIsLoading(true); setError(''); try { const response = await authedFetch(THREADS_API_URL, { method: 'GET' }); if (!response.ok) { throw new Error(`获取会话列表失败: ${response.status}`); } const data = (await response.json()) as ThreadListResponse; const nextThreads = Array.isArray(data.threads) ? data.threads : []; const nextArchivedThreads = Array.isArray(data.archived_threads) ? data.archived_threads : []; setThreads(sortThreads(applyFilter(nextThreads))); setArchivedThreads(sortThreads(applyFilter(nextArchivedThreads))); } catch (fetchError) { console.error('获取会话列表失败', fetchError); setError('会话列表加载失败,请稍后重试'); } finally { setIsLoading(false); } }, [applyFilter, authedFetch]); useEffect(() => { if (!isOpen) return; void refreshThreads(); }, [isOpen, refreshThreads]); const allThreads = useMemo(() => [...threads, ...archivedThreads], [archivedThreads, threads]); const handleCreateThread = async () => { if (isBusy || isCreating) return; setIsCreating(true); setError(''); try { const response = await authedFetch(THREADS_API_URL, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ title: '新建技能', archived: false, is_real_chat: true, ...createThreadPayload, }), }); if (!response.ok) { throw new Error(`创建会话失败: ${response.status}`); } const newThread = (await response.json()) as SkillCreatorThread; if (!threadFilter || threadFilter(newThread)) { setThreads((prev) => sortThreads([newThread, ...prev])); } onThreadSelect(String(newThread.id)); } catch (createError) { console.error('创建会话失败', createError); setError('新建会话失败,请稍后重试'); } finally { setIsCreating(false); } }; const handleDeleteThread = async (thread: SkillCreatorThread) => { if (isBusy || deletingThreadId === thread.id) return; const confirmed = window.confirm(`确认删除会话“${thread.title || `会话 ${thread.id}`}”吗?`); if (!confirmed) return; setDeletingThreadId(thread.id); setError(''); try { const response = await authedFetch(`${THREADS_API_URL}/${thread.id}`, { method: 'DELETE', }); if (!response.ok) { throw new Error(`删除会话失败: ${response.status}`); } const nextThreads = threads.filter((item) => item.id !== thread.id); const nextArchivedThreads = archivedThreads.filter((item) => item.id !== thread.id); setThreads(nextThreads); setArchivedThreads(nextArchivedThreads); if (currentThreadId === String(thread.id)) { const fallbackThread = [...nextThreads, ...nextArchivedThreads][0]; onThreadSelect(fallbackThread ? String(fallbackThread.id) : ''); } } catch (deleteError) { console.error('删除会话失败', deleteError); setError('删除会话失败,请稍后重试'); } finally { setDeletingThreadId(null); } }; const renderThreadSection = (title: string, items: SkillCreatorThread[], archived = false) => (
{archived ? : } {title}
{items.length}
{items.length === 0 ? (
{emptyText}
) : (
{items.map((thread) => { const isActive = currentThreadId === String(thread.id); const isDeleting = deletingThreadId === thread.id; return ( ); })}
)}
); return ( ); }