Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import React, { FormEvent, FormHTMLAttributes, Fragment, KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react';


import { useAccessor, useChatThreadsState, useChatThreadsStreamState, useSettingsState, useActiveURI, useCommandBarState, useFullChatThreadsStreamState } from '../util/services.js';
import { useAccessor, useChatThreadsState, useChatThreadsStreamState, useSettingsState, useActiveURI, useFullChatThreadsStreamState } from '../util/services.js';

import { ChatMarkdownRender, ChatMessageLocation, getApplyBoxId } from '../markdown/ChatMarkdownRender.js';
import { URI } from '../../../../../../../base/common/uri.js';
Expand All @@ -18,6 +18,7 @@ import { VoidChatArea, ButtonSubmit, ButtonStop } from './composer/VoidChatArea.
import { SelectedFiles } from './composer/SelectedFiles.js';
import { ScrollToBottomContainer } from './composer/ScrollToBottomContainer.js';
import { ContextUsageBar } from './composer/ContextUsageBar.js';
import { CommandBarInChat } from './composer/CommandBarInChat.js';
import { LandingPage } from './landing/LandingPage.js';
import { ComposerTabs } from './chrome/ComposerTabs.js';
import { ThreadHeader } from './chrome/ThreadHeader.js';
Expand All @@ -30,7 +31,7 @@ import { getModelCapabilities, getIsReasoningEnabledState, getReservedOutputToke
import { AlertTriangle, File, Ban, Check, ChevronRight, Dot, FileIcon, Pencil, Undo, Undo2, X, Flag, Copy as CopyIcon, Info, CirclePlus, Ellipsis, CircleEllipsis, Folder, ALargeSmall, TypeOutline, Text } from 'lucide-react';
import { ChatMessage, CheckpointEntry, StagingSelectionItem, ToolMessage, PlanMessage, ReviewMessage, PlanStep, StepStatus, PlanApprovalState } from '../../../../common/chatThreadServiceTypes.js';
import { approvalTypeOfBuiltinToolName, BuiltinToolCallParams, BuiltinToolName, ToolName, LintErrorItem, ToolApprovalType, toolApprovalTypes } from '../../../../common/toolsServiceTypes.js';
import { CopyButton, EditToolAcceptRejectButtonsHTML, IconShell1, JumpToFileButton, JumpToTerminalButton, StatusIndicator, StatusIndicatorForApplyButton, useApplyStreamState, useEditToolStreamState } from '../markdown/ApplyBlockHoverButtons.js';
import { CopyButton, EditToolAcceptRejectButtonsHTML, JumpToFileButton, JumpToTerminalButton, StatusIndicatorForApplyButton, useApplyStreamState, useEditToolStreamState } from '../markdown/ApplyBlockHoverButtons.js';
import { IsRunningType } from '../../../chatThreadService.js';
import { acceptAllBg, acceptBorder, buttonFontSize, buttonTextColor, rejectAllBg, rejectBg, rejectBorder } from '../../../../common/helpers/colors.js';
import { builtinToolNames, isABuiltinToolName, MAX_FILE_CHARS_PAGE, MAX_TERMINAL_INACTIVE_TIME } from '../../../../common/prompt/prompts.js';
Expand Down Expand Up @@ -60,276 +61,12 @@ export { getBasename, getFolderName, getRelative, voidOpenFileFn } from './share
export { ToolChildrenWrapper, CodeChildren, ListableToolItem } from './tools/ToolPrimitives.js';
export { VoidChatArea, ButtonSubmit, ButtonStop } from './composer/VoidChatArea.js';
export { SelectedFiles } from './composer/SelectedFiles.js';
export { CommandBarInChat } from './composer/CommandBarInChat.js';
export { ChatBubble } from './chat/ChatBubble.js';





const CommandBarInChat = () => {
const { stateOfURI: commandBarStateOfURI, sortedURIs: sortedCommandBarURIs } = useCommandBarState()
const numFilesChanged = sortedCommandBarURIs.length

const accessor = useAccessor()
const editCodeService = accessor.get('IEditCodeService')
const commandService = accessor.get('ICommandService')
const chatThreadsState = useChatThreadsState()
const commandBarState = useCommandBarState()
const chatThreadsStreamState = useChatThreadsStreamState(chatThreadsState.currentThreadId)

// (
// <IconShell1
// Icon={CopyIcon}
// onClick={copyChatToClipboard}
// data-tooltip-id='cortex-tooltip'
// data-tooltip-place='top'
// data-tooltip-content='Copy chat JSON'
// />
// )

const [fileDetailsOpenedState, setFileDetailsOpenedState] = useState<'auto-opened' | 'auto-closed' | 'user-opened' | 'user-closed'>('auto-closed');
const isFileDetailsOpened = fileDetailsOpenedState === 'auto-opened' || fileDetailsOpenedState === 'user-opened';


useEffect(() => {
// close the file details if there are no files
// this converts 'user-closed' to 'auto-closed'
if (numFilesChanged === 0) {
setFileDetailsOpenedState('auto-closed')
}
// open the file details if it hasnt been closed
if (numFilesChanged > 0 && fileDetailsOpenedState !== 'user-closed') {
setFileDetailsOpenedState('auto-opened')
}
}, [fileDetailsOpenedState, setFileDetailsOpenedState, numFilesChanged])


const isFinishedMakingThreadChanges = (
// there are changed files
commandBarState.sortedURIs.length !== 0
// none of the files are streaming
&& commandBarState.sortedURIs.every(uri => !commandBarState.stateOfURI[uri.fsPath]?.isStreaming)
)

// ======== status of agent ========
// This icon answers the question "is the LLM doing work on this thread?"
// assume it is single threaded for now
// green = Running
// orange = Requires action
// dark = Done

const threadStatus = (
chatThreadsStreamState?.isRunning === 'awaiting_user'
? { title: 'Needs Approval', color: 'yellow', } as const
: (chatThreadsStreamState?.isRunning === 'LLM' || chatThreadsStreamState?.isRunning === 'tool' || chatThreadsStreamState?.isRunning === 'preparing')
? { title: chatThreadsStreamState?.isRunning === 'preparing' ? 'Preparing' : 'Running', color: 'orange', } as const
: { title: 'Done', color: 'dark', } as const
)


const threadStatusHTML = <StatusIndicator className='mx-1' indicatorColor={threadStatus.color} title={threadStatus.title} />


// ======== info about changes ========
// num files changed
// acceptall + rejectall
// popup info about each change (each with num changes + acceptall + rejectall of their own)

const numFilesChangedStr = numFilesChanged === 0 ? 'No files with changes'
: `${sortedCommandBarURIs.length} file${numFilesChanged === 1 ? '' : 's'} with changes`




const acceptRejectAllButtons = <div
// do this with opacity so that the height remains the same at all times
className={`flex items-center gap-0.5
${isFinishedMakingThreadChanges ? '' : 'opacity-0 pointer-events-none'}`
}
>
<IconShell1 // RejectAllButtonWrapper
// text="Reject All"
// className="text-xs"
Icon={X}
onClick={() => {
sortedCommandBarURIs.forEach(uri => {
editCodeService.acceptOrRejectAllDiffAreas({
uri,
removeCtrlKs: true,
behavior: "reject",
_addToHistory: true,
});
});
}}
data-tooltip-id='cortex-tooltip'
data-tooltip-place='top'
data-tooltip-content='Reject all'
/>

<IconShell1 // AcceptAllButtonWrapper
// text="Accept All"
// className="text-xs"
Icon={Check}
onClick={() => {
sortedCommandBarURIs.forEach(uri => {
editCodeService.acceptOrRejectAllDiffAreas({
uri,
removeCtrlKs: true,
behavior: "accept",
_addToHistory: true,
});
});
}}
data-tooltip-id='cortex-tooltip'
data-tooltip-place='top'
data-tooltip-content='Accept all'
/>



</div>


// !select-text cursor-auto
const fileDetailsContent = <div className="px-2 gap-1 w-full overflow-y-auto">
{sortedCommandBarURIs.map((uri, i) => {
const basename = getBasename(uri.fsPath)

const { sortedDiffIds, isStreaming } = commandBarStateOfURI[uri.fsPath] ?? {}
const isFinishedMakingFileChanges = !isStreaming

const numDiffs = sortedDiffIds?.length || 0

const fileStatus = (isFinishedMakingFileChanges
? { title: 'Done', color: 'dark', } as const
: { title: 'Running', color: 'orange', } as const
)

const fileNameHTML = <div
className="flex items-center gap-1.5 text-void-fg-3 hover:brightness-125 transition-all duration-200 cursor-pointer"
onClick={() => voidOpenFileFn(uri, accessor)}
>
{/* <FileIcon size={14} className="text-void-fg-3" /> */}
<span className="text-void-fg-3">{basename}</span>
</div>




const detailsContent = <div className='flex px-4'>
<span className="text-void-fg-3 opacity-80">{numDiffs} diff{numDiffs !== 1 ? 's' : ''}</span>
</div>

const acceptRejectButtons = <div
// do this with opacity so that the height remains the same at all times
className={`flex items-center gap-0.5
${isFinishedMakingFileChanges ? '' : 'opacity-0 pointer-events-none'}
`}
>
{/* <JumpToFileButton
uri={uri}
data-tooltip-id='cortex-tooltip'
data-tooltip-place='top'
data-tooltip-content='Go to file'
/> */}
<IconShell1 // RejectAllButtonWrapper
Icon={X}
onClick={() => { editCodeService.acceptOrRejectAllDiffAreas({ uri, removeCtrlKs: true, behavior: "reject", _addToHistory: true, }); }}
data-tooltip-id='cortex-tooltip'
data-tooltip-place='top'
data-tooltip-content='Reject file'

/>
<IconShell1 // AcceptAllButtonWrapper
Icon={Check}
onClick={() => { editCodeService.acceptOrRejectAllDiffAreas({ uri, removeCtrlKs: true, behavior: "accept", _addToHistory: true, }); }}
data-tooltip-id='cortex-tooltip'
data-tooltip-place='top'
data-tooltip-content='Accept file'
/>

</div>

const fileStatusHTML = <StatusIndicator className='mx-1' indicatorColor={fileStatus.color} title={fileStatus.title} />

return (
// name, details
<div key={uri.toString()} className="flex justify-between items-center">
<div className="flex items-center">
{fileNameHTML}
{detailsContent}
</div>
<div className="flex items-center gap-2">
{acceptRejectButtons}
{fileStatusHTML}
</div>
</div>
)
})}
</div>

const fileDetailsButton = (
<button
className={`flex items-center gap-1 rounded ${numFilesChanged === 0 ? 'cursor-pointer' : 'cursor-pointer hover:brightness-125 transition-all duration-200'}`}
onClick={() => isFileDetailsOpened ? setFileDetailsOpenedState('user-closed') : setFileDetailsOpenedState('user-opened')}
type='button'
disabled={numFilesChanged === 0}
>
<svg
className="transition-transform duration-200 size-3.5"
style={{
transform: isFileDetailsOpened ? 'rotate(0deg)' : 'rotate(180deg)',
transition: 'transform 0.2s cubic-bezier(0.25, 0.1, 0.25, 1)'
}}
xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="18 15 12 9 6 15"></polyline>
</svg>
{numFilesChangedStr}
</button>
)

return (
<>
{/* file details */}
<div className='px-2'>
<div
className={`
select-none
flex w-full rounded-t-lg bg-void-bg-3
text-void-fg-3 text-xs text-nowrap

overflow-hidden transition-all duration-200 ease-in-out
${isFileDetailsOpened ? 'max-h-24' : 'max-h-0'}
`}
>
{fileDetailsContent}
</div>
</div>
{/* main content */}
<div
className={`
select-none
flex w-full rounded-t-lg bg-void-bg-3
text-void-fg-3 text-xs text-nowrap
border-t border-l border-r border-zinc-300/10

px-2 py-1
justify-between
`}
>
<div className="flex gap-2 items-center">
{fileDetailsButton}
</div>
<div className="flex gap-2 items-center">
{acceptRejectAllButtons}
{threadStatusHTML}
</div>
</div>
</>
)
}



export const SidebarChat = () => {
const textAreaRef = useRef<HTMLTextAreaElement | null>(null)
const textAreaFnsRef = useRef<TextAreaFns | null>(null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,9 +218,12 @@ const PlanComponent = React.memo(({ message, isCheckpointGhost, threadId, messag
<div className="flex items-center justify-between">
<div className="flex items-center gap-2 flex-1 min-w-0">
<button
type='button'
onClick={() => setIsCollapsed(!isCollapsed)}
className="flex-shrink-0 p-1 hover:bg-void-bg-2 rounded transition-colors"
disabled={isCheckpointGhost}
aria-expanded={!isCollapsed}
aria-label={isCollapsed ? 'Expand plan' : 'Collapse plan'}
>
<ChevronRight
size={16}
Expand Down Expand Up @@ -374,8 +377,11 @@ const PlanComponent = React.memo(({ message, isCheckpointGhost, threadId, messag
{/* Expandable Details */}
{hasDetails && (
<button
type='button'
onClick={() => toggleStepExpanded(step.stepNumber)}
className="mt-2 flex items-center gap-1 text-void-fg-3 hover:text-void-fg-2 text-xs transition-colors"
aria-expanded={isExpanded}
aria-label={isExpanded ? `Hide details for step ${step.stepNumber}` : `Show details for step ${step.stepNumber}`}
>
<ChevronRight
size={12}
Expand Down
Loading
Loading