diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index 38cc2aed8666669a93583cf4382ee49c7b5042dc..2a0c6ccaf9b4ea3b8ce783b50e6c2d347793f765 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -361,12 +361,16 @@ const VscodeEntryIcon = memo(function VscodeEntryIcon(props: { const ComposerCommandMenuItem = memo(function ComposerCommandMenuItem(props: { item: ComposerCommandItem; resolvedTheme: "light" | "dark"; + isActive: boolean; onSelect: (item: ComposerCommandItem) => void; }) { return ( { event.preventDefault(); }} @@ -400,6 +404,7 @@ const ComposerCommandMenu = memo(function ComposerCommandMenu(props: { resolvedTheme: "light" | "dark"; isLoading: boolean; triggerKind: ComposerTriggerKind | null; + activeItemId: string | null; onHighlightedItemChange: (itemId: string | null) => void; onSelect: (item: ComposerCommandItem) => void; }) { @@ -419,6 +424,7 @@ const ComposerCommandMenu = memo(function ComposerCommandMenu(props: { key={item.id} item={item} resolvedTheme={props.resolvedTheme} + isActive={props.activeItemId === item.id} onSelect={props.onSelect} /> ))} @@ -1389,6 +1395,26 @@ export default function ChatView({ threadId }: ChatViewProps) { scheduleStickToBottom(); }, [phase, scheduleStickToBottom, timelineEntries]); + useEffect(() => { + setExpandedWorkGroups({}); + }, [activeThread?.id]); + + useEffect(() => { + if (!composerMenuOpen) { + setComposerHighlightedItemId(null); + return; + } + setComposerHighlightedItemId((existing) => + existing && composerMenuItems.some((item) => item.id === existing) + ? existing + : (composerMenuItems[0]?.id ?? null), + ); + }, [composerMenuItems, composerMenuOpen]); + + useEffect(() => { + setIsRevertingCheckpoint(false); + }, [activeThread?.id]); + useEffect(() => { if (!activeThread?.id || terminalState.terminalOpen) return; const frame = window.requestAnimationFrame(() => { @@ -2274,12 +2300,14 @@ export default function ChatView({ threadId }: ChatViewProps) { workspaceEntriesQuery.isLoading || workspaceEntriesQuery.isFetching); - const onPromptChange = useCallback((nextPrompt: string, nextCursor: number) => { + const onPromptChange = useCallback((nextPrompt: string, nextCursor: number, cursorAdjacentToMention: boolean) => { promptRef.current = nextPrompt; setPrompt(nextPrompt); setComposerCursor(nextCursor); setComposerTrigger( - detectComposerTrigger(nextPrompt, expandCollapsedComposerCursor(nextPrompt, nextCursor)), + cursorAdjacentToMention + ? null + : detectComposerTrigger(nextPrompt, expandCollapsedComposerCursor(nextPrompt, nextCursor)), ); }, [setPrompt]); @@ -2301,8 +2329,8 @@ export default function ChatView({ threadId }: ChatViewProps) { const selectedItem = activeComposerMenuItemRef.current ?? currentItems[0]; if (selectedItem) { onSelectComposerItem(selectedItem); + return true; } - return true; } } @@ -2464,6 +2492,7 @@ export default function ChatView({ threadId }: ChatViewProps) { resolvedTheme={resolvedTheme} isLoading={isComposerMenuLoading} triggerKind={composerTriggerKind} + activeItemId={activeComposerMenuItem?.id ?? null} onHighlightedItemChange={onComposerMenuItemHighlighted} onSelect={onSelectComposerItem} /> diff --git a/apps/web/src/components/ComposerPromptEditor.tsx b/apps/web/src/components/ComposerPromptEditor.tsx index 6601e558edfaabbd28483963b437baac90fa5621..30dbb9dceb1bf2d8200222748152b0aca338eafc 100644 --- a/apps/web/src/components/ComposerPromptEditor.tsx +++ b/apps/web/src/components/ComposerPromptEditor.tsx @@ -395,7 +395,7 @@ interface ComposerPromptEditorProps { disabled: boolean; placeholder: string; className?: string; - onChange: (nextValue: string, nextCursor: number) => void; + onChange: (nextValue: string, nextCursor: number, cursorAdjacentToMention: boolean) => void; onCommandKeyDown?: ( key: "ArrowDown" | "ArrowUp" | "Enter" | "Tab", event: KeyboardEvent, @@ -540,6 +540,29 @@ function ComposerMentionSelectionNormalizePlugin() { return null; } +function $isCursorAdjacentToMention(): boolean { + const selection = $getSelection(); + if (!$isRangeSelection(selection) || !selection.isCollapsed()) return false; + const anchorNode = selection.anchor.getNode(); + + if (anchorNode instanceof ComposerMentionNode) return true; + + if ($isElementNode(anchorNode)) { + const childIndex = selection.anchor.offset - 1; + if (childIndex >= 0) { + const child = anchorNode.getChildAtIndex(childIndex); + if (child instanceof ComposerMentionNode) return true; + } + } + + if ($isTextNode(anchorNode) && selection.anchor.offset === 0) { + const prev = anchorNode.getPreviousSibling(); + if (prev instanceof ComposerMentionNode) return true; + } + + return false; +} + function ComposerMentionBackspacePlugin() { const [editor] = useLexicalComposerContext(); @@ -662,7 +685,7 @@ function ComposerPromptEditorInner({ value: snapshotRef.current.value, cursor: boundedCursor, }; - onChangeRef.current(snapshotRef.current.value, boundedCursor); + onChangeRef.current(snapshotRef.current.value, boundedCursor, false); }, [editor], ); @@ -715,7 +738,7 @@ function ComposerPromptEditorInner({ value: nextValue, cursor: nextCursor, }; - onChangeRef.current(nextValue, nextCursor); + onChangeRef.current(nextValue, nextCursor, $isCursorAdjacentToMention()); }); }, []);