Skip to content
Closed
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
8 changes: 7 additions & 1 deletion static/app/views/seerExplorer/blockComponents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
interface BlockProps {
block: Block;
blockIndex: number;
editEnabled?: boolean;
isAwaitingFileApproval?: boolean;
isAwaitingQuestion?: boolean;
isFocused?: boolean;
Expand Down Expand Up @@ -136,6 +137,7 @@ function BlockComponent({
isLatestTodoBlock,
isFocused,
isPolling,
editEnabled = true,
onClick,
onDelete,
onMouseEnter,
Expand Down Expand Up @@ -275,7 +277,11 @@ function BlockComponent({
};

const showActions =
isFocused && !block.loading && !isAwaitingFileApproval && !isAwaitingQuestion;
isFocused &&
!block.loading &&
!isAwaitingFileApproval &&
!isAwaitingQuestion &&
editEnabled; // Update when there are more actions than restart

return (
<Block
Expand Down
48 changes: 32 additions & 16 deletions static/app/views/seerExplorer/explorerPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {createPortal} from 'react-dom';
import {useFeedbackForm} from 'sentry/utils/useFeedbackForm';
import useOrganization from 'sentry/utils/useOrganization';
import useProjects from 'sentry/utils/useProjects';
import {useUser} from 'sentry/utils/useUser';
import AskUserQuestionBlock from 'sentry/views/seerExplorer/askUserQuestionBlock';
import BlockComponent from 'sentry/views/seerExplorer/blockComponents';
import EmptyState from 'sentry/views/seerExplorer/emptyState';
Expand Down Expand Up @@ -87,6 +88,11 @@ function ExplorerPanel({isVisible = false}: ExplorerPanelProps) {
// Get blocks from session data or empty array
const blocks = useMemo(() => sessionData?.blocks || [], [sessionData]);

// Check owner id to determine edit permission
const userId = useUser().id;
const ownerUserId = sessionData?.owner_user_id;
const canEdit = ownerUserId === undefined || ownerUserId.toString() === userId;

// Get PR widget data for menu
const {menuItems: prWidgetItems, menuFooter: prWidgetFooter} = usePRWidgetData({
blocks,
Expand Down Expand Up @@ -203,6 +209,9 @@ function ExplorerPanel({isVisible = false}: ExplorerPanelProps) {
}, [focusedBlockIndex]);

const handleInputKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (!canEdit) {
return;
}
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
if (inputValue.trim() && !isPolling) {
Expand Down Expand Up @@ -398,22 +407,26 @@ function ExplorerPanel({isVisible = false}: ExplorerPanelProps) {
const handleKeyDown = (e: KeyboardEvent) => {
const isPrintableChar = e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey;

if (
e.key === 'Escape' &&
isPolling &&
!interruptRequested &&
!isFileApprovalPending
) {
e.preventDefault();
interruptRun();
} else if (e.key === 'Escape' && !isFileApprovalPending) {
// Don't minimize if file approval is pending (Escape is used to reject)
e.preventDefault();
setIsMinimized(true);
} else if (isPrintableChar) {
// Don't auto-type if file approval or question is pending (textarea isn't visible)
if (focusedBlockIndex !== -1 && !isFileApprovalPending && !isQuestionPending) {
// If a block is focused, auto-focus input when user starts typing.
if (e.key === 'Escape') {
if (isPolling && canEdit && !interruptRequested && !isFileApprovalPending) {
e.preventDefault();
interruptRun();
} else if (!isFileApprovalPending) {
// Don't minimize if file approval is pending (Escape is used to reject)
e.preventDefault();
setIsMinimized(true);
}
}

if (isPrintableChar) {
// If a block is focused, auto-focus input when user starts typing.
// Don't do this if file approval or question is pending (textarea isn't visible)
if (
canEdit &&
focusedBlockIndex !== -1 &&
!isFileApprovalPending &&
!isQuestionPending
) {
e.preventDefault();
setFocusedBlockIndex(-1);
textareaRef.current?.focus();
Expand All @@ -437,6 +450,7 @@ function ExplorerPanel({isVisible = false}: ExplorerPanelProps) {
isVisible,
isMenuOpen,
isPolling,
canEdit,
focusedBlockIndex,
interruptRun,
interruptRequested,
Expand Down Expand Up @@ -533,6 +547,7 @@ function ExplorerPanel({isVisible = false}: ExplorerPanelProps) {
}
isFocused={focusedBlockIndex === index}
isPolling={isPolling}
editEnabled={canEdit}
onClick={() => handleBlockClick(index)}
onMouseEnter={() => {
// Don't change focus while menu is open, if already on this block, or if hover is disabled
Expand Down Expand Up @@ -586,6 +601,7 @@ function ExplorerPanel({isVisible = false}: ExplorerPanelProps) {
)}
</BlocksContainer>
<InputSection
enabled={canEdit}
focusedBlockIndex={focusedBlockIndex}
inputValue={inputValue}
interruptRequested={interruptRequested}
Expand Down
1 change: 1 addition & 0 deletions static/app/views/seerExplorer/hooks/useSeerExplorer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export type SeerExplorerResponse = {
blocks: Block[];
status: 'processing' | 'completed' | 'error' | 'awaiting_user_input';
updated_at: string;
owner_user_id?: number;
pending_user_input?: PendingUserInput | null;
repo_pr_states?: Record<string, RepoPRState>;
run_id?: number;
Expand Down
33 changes: 29 additions & 4 deletions static/app/views/seerExplorer/inputSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ interface QuestionActions {
}

interface InputSectionProps {
enabled: boolean;
focusedBlockIndex: number;
inputValue: string;
interruptRequested: boolean;
Expand All @@ -47,6 +48,7 @@ interface InputSectionProps {
}

function InputSection({
enabled,
inputValue,
focusedBlockIndex,
isMinimized = false,
Expand All @@ -62,6 +64,9 @@ function InputSection({
questionActions,
}: InputSectionProps) {
const getPlaceholder = () => {
if (!enabled) {
return 'This conversation is owned by another user and is read-only';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be cool if we can look up the user name from the user ID. should be possible in the frontend.

And some friendlier language like "Reading Andrew's conversation with Seer. Last updated 3hr ago." would be nice. (there should also be a util for the "ago" formatting)

}
if (focusedBlockIndex !== -1) {
return 'Press Tab ⇥ to return here';
}
Expand All @@ -70,7 +75,7 @@ function InputSection({

// Handle keyboard shortcuts for file approval
useEffect(() => {
if (!fileApprovalActions || !isVisible || isMinimized) {
if (!fileApprovalActions || !isVisible || isMinimized || !enabled) {
return undefined;
}

Expand All @@ -94,11 +99,11 @@ function InputSection({

document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}, [fileApprovalActions, isVisible, isMinimized]);
}, [fileApprovalActions, isVisible, isMinimized, enabled]);

// Handle keyboard shortcuts for questions
useEffect(() => {
if (!questionActions || !isVisible || isMinimized) {
if (!questionActions || !isVisible || isMinimized || !enabled) {
return undefined;
}

Expand Down Expand Up @@ -134,7 +139,27 @@ function InputSection({

document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}, [questionActions, isVisible, isMinimized]);
}, [questionActions, isVisible, isMinimized, enabled]);

if (!enabled) {
return (
<InputBlock>
<StyledInputGroup>
<InputGroup.TextArea
disabled
ref={textAreaRef}
value={inputValue}
onChange={onInputChange}
onKeyDown={onKeyDown}
onClick={onInputClick}
placeholder={getPlaceholder()}
rows={1}
data-test-id="seer-explorer-input"
/>
</StyledInputGroup>
</InputBlock>
);
}

// Render file approval action bar instead of entire input section
if (fileApprovalActions) {
Expand Down
Loading