Enhance chat input and sidebar components with runId support and improved auto-focus behavior

This commit is contained in:
tusharmagar 2026-01-27 08:27:06 +05:30
parent f462c558f1
commit 4d2fc01f88
3 changed files with 33 additions and 11 deletions

View file

@ -282,6 +282,7 @@ interface ChatInputInnerProps {
isProcessing: boolean
presetMessage?: string
onPresetMessageConsumed?: () => void
runId?: string | null
}
function ChatInputInner({
@ -289,6 +290,7 @@ function ChatInputInner({
isProcessing,
presetMessage,
onPresetMessageConsumed,
runId,
}: ChatInputInnerProps) {
const controller = usePromptInputController()
const message = controller.textInput.value
@ -320,9 +322,9 @@ function ChatInputInner({
<div className="flex items-center gap-2 bg-background border border-border rounded-3xl shadow-xl px-4 py-2.5">
<PromptInputTextarea
placeholder="Type your message..."
disabled={isProcessing}
onKeyDown={handleKeyDown}
autoFocus
focusTrigger={runId}
className="min-h-6 py-0 border-0 shadow-none focus-visible:ring-0 rounded-none"
/>
<Button
@ -351,6 +353,7 @@ interface ChatInputWithMentionsProps {
isProcessing: boolean
presetMessage?: string
onPresetMessageConsumed?: () => void
runId?: string | null
}
function ChatInputWithMentions({
@ -361,6 +364,7 @@ function ChatInputWithMentions({
isProcessing,
presetMessage,
onPresetMessageConsumed,
runId,
}: ChatInputWithMentionsProps) {
return (
<PromptInputProvider knowledgeFiles={knowledgeFiles} recentFiles={recentFiles} visibleFiles={visibleFiles}>
@ -369,6 +373,7 @@ function ChatInputWithMentions({
isProcessing={isProcessing}
presetMessage={presetMessage}
onPresetMessageConsumed={onPresetMessageConsumed}
runId={runId}
/>
</PromptInputProvider>
)
@ -405,6 +410,7 @@ function App() {
const [currentReasoning, setCurrentReasoning] = useState<string>('')
const [, setModelUsage] = useState<LanguageModelUsage | null>(null)
const [runId, setRunId] = useState<string | null>(null)
const runIdRef = useRef<string | null>(null)
const [isProcessing, setIsProcessing] = useState(false)
const [agentId] = useState<string>('copilot')
const [presetMessage, setPresetMessage] = useState<string | undefined>(undefined)
@ -427,6 +433,11 @@ function App() {
// Onboarding state
const [showOnboarding, setShowOnboarding] = useState(false)
// Keep runIdRef in sync with runId state (for use in event handlers to avoid stale closures)
useEffect(() => {
runIdRef.current = runId
}, [runId])
// Load directory tree
const loadDirectory = useCallback(async () => {
try {
@ -723,15 +734,17 @@ function App() {
}, [])
// Listen to run events
// Listen to run events - use ref to avoid stale closure issues
useEffect(() => {
const cleanup = window.ipc.on('runs:events', ((event: unknown) => {
handleRunEvent(event as RunEventType)
}) as (event: null) => void)
return cleanup
}, [runId])
}, [])
const handleRunEvent = (event: RunEventType) => {
if (event.runId !== runId) return
// Use ref to get current runId to avoid stale closure issues
if (event.runId !== runIdRef.current) return
console.log('Run event:', event.type, event)
@ -1044,6 +1057,7 @@ function App() {
setRunId(null)
setMessage('')
setModelUsage(null)
setIsProcessing(false)
setPendingPermissionRequests(new Map())
setPendingAskHumanRequests(new Map())
setAllPermissionRequests(new Map())
@ -1716,6 +1730,7 @@ function App() {
isProcessing={isProcessing}
presetMessage={presetMessage}
onPresetMessageConsumed={() => setPresetMessage(undefined)}
runId={runId}
/>
</div>
</div>

View file

@ -906,6 +906,7 @@ export type PromptInputTextareaProps = ComponentProps<
typeof InputGroupTextarea
> & {
autoFocus?: boolean;
focusTrigger?: unknown; // When this value changes, focus the textarea
};
export const PromptInputTextarea = ({
@ -914,6 +915,7 @@ export const PromptInputTextarea = ({
placeholder = "What would you like to know?",
onKeyDown: externalOnKeyDown,
autoFocus = false,
focusTrigger,
...props
}: PromptInputTextareaProps) => {
const controller = useOptionalPromptInputController();
@ -924,16 +926,16 @@ export const PromptInputTextarea = ({
const textareaRef = useRef<HTMLTextAreaElement>(null);
// Auto-focus the textarea when requested
// Auto-focus the textarea when requested or when focusTrigger changes
useEffect(() => {
if (autoFocus) {
if (autoFocus || focusTrigger !== undefined) {
// Small delay to ensure the element is fully mounted and visible
const timer = setTimeout(() => {
textareaRef.current?.focus();
}, 50);
return () => clearTimeout(timer);
}
}, [autoFocus]);
}, [autoFocus, focusTrigger]);
const containerRef = useRef<HTMLDivElement>(null);
const highlightRef = useRef<HTMLDivElement>(null);

View file

@ -260,10 +260,16 @@ export function ChatSidebar({
document.addEventListener('mouseup', handleMouseUp)
}, [width])
// Auto-focus textarea when sidebar opens
// Auto-focus textarea when sidebar opens or when conversation is cleared (new chat)
useEffect(() => {
textareaRef.current?.focus()
}, [])
// Focus when conversation is empty (new chat started)
if (conversation.length === 0) {
const timer = setTimeout(() => {
textareaRef.current?.focus()
}, 50)
return () => clearTimeout(timer)
}
}, [conversation.length])
// Auto-populate with @currentfile when switching knowledge files
useEffect(() => {
@ -584,9 +590,8 @@ export function ChatSidebar({
onKeyDown={handleKeyDown}
onScroll={syncHighlightScroll}
placeholder="Ask anything..."
disabled={isProcessing}
rows={1}
className="relative z-10 w-full bg-transparent text-sm outline-none placeholder:text-muted-foreground disabled:opacity-50 resize-none max-h-32 min-h-6"
className="relative z-10 w-full bg-transparent text-sm outline-none placeholder:text-muted-foreground resize-none max-h-32 min-h-6"
style={{ fieldSizing: 'content' } as React.CSSProperties}
/>
</div>