import { useEffect, useRef, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { Archive, ArchiveRestore, LoaderCircle, MessageSquarePlus, MoreHorizontal, Pencil, Settings2, MessageSquare, } from 'lucide-react'; import * as DropdownMenu from '@radix-ui/react-dropdown-menu'; import { toast } from 'sonner'; import { codingWorkspaceStore, useCodingWorkspaceStore } from '@/stores/coding-workspace'; import { codingConversationStore, useCodingConversationStore } from '@/stores/coding-conversations'; import type { CodingConversationMetadata } from '@/types/coding-project'; import { CodingConversationRenameDialog } from './CodingConversationRenameDialog'; import { createLocalConversationSnapshot } from './coding-chat-snapshot'; export function CodingProjectConversations({ projectId, compact = false }: { projectId: string; compact?: boolean }) { const navigate = useNavigate(); const conversations = useCodingWorkspaceStore((state) => state.conversations); const activeProjectId = useCodingWorkspaceStore((state) => state.activeProjectId); const selected = useCodingConversationStore((state) => state.selectedConversationId); const summaries = useCodingConversationStore((state) => state.summariesByConversationId); const [archived, setArchived] = useState(false); const [busy, setBusy] = useState(null); const [rename, setRename] = useState(null); const selectedConversation = conversations.find((item) => item.id === selected); const previousSelection = useRef(selectedConversation); useEffect(() => { const previous = previousSelection.current; if ( previous?.id === selectedConversation?.id && previous?.archivedAt && !selectedConversation?.archivedAt ) setArchived(false); previousSelection.current = selectedConversation; }, [selectedConversation]); if (activeProjectId !== projectId) return null; const act = async (key: string, operation: () => Promise) => { setBusy(key); try { await operation(); } catch (error) { toast.error(error instanceof Error ? error.message : '操作失败'); } finally { setBusy(null); } }; const select = async (conversation: CodingConversationMetadata) => { if (codingWorkspaceStore.getState().activeProjectId !== projectId) return; const state = codingConversationStore.getState(); state.primeConversation(createLocalConversationSnapshot(projectId, conversation)); state.markUnread(conversation.id, false); if (conversation.unread) void codingWorkspaceStore .getState() .patchConversation(conversation.id, { unread: false }) .catch(() => undefined); navigate('/chat'); await state.selectConversation(conversation.id); }; return (
{archived &&

已归档

} {conversations .filter((item) => Boolean(item.archivedAt) === archived) .sort((a, b) => (archived ? b.archivedAt! : b.updatedAt).localeCompare( archived ? a.archivedAt! : a.updatedAt ) ) .map((item) => { const summary = summaries[item.id]; const running = summary && ['queued', 'running', 'retrying', 'compacting', 'aborting'].includes(summary.runStatus); return (
navigate('/project-config?agent=' + encodeURIComponent(item.agentId)) } > 会话编程设置 setRename(item)} > 重命名 void act(item.id, async () => { await codingWorkspaceStore .getState() .patchConversation(item.id, { archived: !item.archivedAt }); if ( !item.archivedAt && codingConversationStore.getState().selectedConversationId === item.id ) codingConversationStore.getState().clearConversationSelection(); }) } > {item.archivedAt ? ( ) : ( )} {item.archivedAt ? '恢复到最近对话' : running ? '归档(任务继续运行)' : '归档'}
); })} {rename && ( setRename(null)} onSave={async (title) => { if (codingWorkspaceStore.getState().activeProjectId !== projectId) throw new Error('项目已切换'); await codingWorkspaceStore.getState().patchConversation(rename.id, { title }); }} /> )}
); }