import React from 'react'; import { RiFileEditLine, RiArrowDownSLine, RiArrowUpSLine } from '@remixicon/react'; import type { ToolPart } from '@opencode-ai/sdk/v2'; import { Popover } from '@base-ui/react/popover'; import { useDirectoryStore } from '@/stores/useDirectoryStore'; import { useIsGitRepo } from '@/stores/useGitStore'; import { useUIStore } from '@/stores/useUIStore'; import { RuntimeAPIContext } from '@/contexts/runtimeAPIContext'; import { type ChangedFile, type ChangedFileEntry, FILE_EDIT_TOOLS, extractChangedFiles, isGitFile, toRelativePath, } from './changedFiles'; import { ChangedFilesList } from './ChangedFilesList'; import { changedFilesPopoverClassName, changedFilesPopoverStyle } from './changedFilesPopover'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import type { TurnActivityRecord } from './lib/turns/types'; interface TurnChangedFilesDropdownProps { activityParts: TurnActivityRecord[] | undefined; } export const TurnChangedFilesDropdown: React.FC = React.memo(({ activityParts }) => { const [isExpanded, setIsExpanded] = React.useState(false); const [portalContainer, setPortalContainer] = React.useState(null); const triggerButtonRef = React.useRef(null); const currentDirectory = useDirectoryStore((s) => s.currentDirectory); const runtime = React.useContext(RuntimeAPIContext); const isGitRepo = useIsGitRepo(currentDirectory); const changedFiles = React.useMemo(() => { // Skip work entirely in git repos — the global PendingChangesBar handles those. if (isGitRepo !== false) return []; if (!activityParts || activityParts.length === 0) return []; const toolParts: ToolPart[] = []; for (const activity of activityParts) { const part = activity.part; if (part.type !== 'tool') continue; if (!FILE_EDIT_TOOLS.has(part.tool)) continue; toolParts.push(part); } if (toolParts.length === 0) return []; return extractChangedFiles(toolParts); }, [activityParts, isGitRepo]); if (changedFiles.length === 0) return null; const syncPortalContainer = () => { const container = triggerButtonRef.current?.closest('[data-slot="dialog-content"], [role="dialog"]') as HTMLElement | null; setPortalContainer(container || null); }; const handleOpenFile = (file: ChangedFileEntry) => { if (!currentDirectory) return; if (isGitFile(file)) return; const absolutePath = file.path.startsWith('/') ? file.path : (currentDirectory.endsWith('/') ? currentDirectory : currentDirectory + '/') + file.path; const editor = runtime?.editor; if (editor) { void editor.openFile(absolutePath); setIsExpanded(false); return; } const store = useUIStore.getState(); if (!store.isMobile) { store.openContextFile(currentDirectory, absolutePath); setIsExpanded(false); return; } store.navigateToDiff(toRelativePath(file.path, currentDirectory)); store.setRightSidebarOpen(false); setIsExpanded(false); }; const fileCount = changedFiles.length; const label = `${fileCount} file${fileCount !== 1 ? 's' : ''}`; return ( {label} {isExpanded ? ( ) : ( )} } /> {label} changed in this turn ); }); TurnChangedFilesDropdown.displayName = 'TurnChangedFilesDropdown';