@@ -62,12 +62,14 @@ import {
6262 SelectTrigger ,
6363} from "@/components/ui/select" ;
6464import { toast } from "@/components/ui/use-toast" ;
65+ import { DelayMount } from "@/components/utils/delay-mount" ;
6566import { useRequestClient } from "@/core/network/requests" ;
6667import { filenameAtom } from "@/core/saving/file-state" ;
6768import { store } from "@/core/state/jotai" ;
6869import { Functions } from "@/utils/functions" ;
6970import { Paths } from "@/utils/paths" ;
7071import { FileAttachmentPill } from "../chat-components" ;
72+ import { ReadyToChatBlock } from "./blocks" ;
7173import {
7274 convertFilesToResourceLinks ,
7375 parseContextFromPrompt ,
@@ -548,6 +550,52 @@ const ChatContent = memo<ChatContentProps>(
548550 }
549551 } , [ notifications . length , isScrolledToBottom , scrollToBottom ] ) ;
550552
553+ const renderThread = ( ) => {
554+ if ( hasNotifications ) {
555+ return (
556+ < AgentThread
557+ isConnected = { connectionState . status === "connected" }
558+ notifications = { notifications }
559+ onRetryConnection = { onRetryConnection }
560+ onRetryLastAction = { onRetryLastAction }
561+ />
562+ ) ;
563+ }
564+
565+ const isConnected = connectionState . status === "connected" ;
566+ if ( isConnected ) {
567+ return < ReadyToChatBlock /> ;
568+ }
569+
570+ return (
571+ < div className = "flex items-center justify-center h-full min-h-[200px] flex-col" >
572+ < PanelEmptyState
573+ title = "Waiting for agent"
574+ description = "Your AI agent will appear here when active"
575+ icon = { < BotMessageSquareIcon /> }
576+ />
577+ { isDisconnected && agentId && (
578+ < AgentDocs
579+ className = "border-t pt-6 px-5"
580+ title = "Make sure you're connected to an agent"
581+ description = "Run this command in your terminal:"
582+ agents = { [ agentId ] }
583+ />
584+ ) }
585+ { isDisconnected && (
586+ < Button
587+ variant = "outline"
588+ onClick = { onRetryConnection }
589+ type = "button"
590+ className = "mt-4"
591+ >
592+ Retry
593+ </ Button >
594+ ) }
595+ </ div >
596+ ) ;
597+ } ;
598+
551599 return (
552600 < div className = "flex-1 flex flex-col overflow-hidden flex-shrink-0 relative" >
553601 { pendingPermission && (
@@ -569,43 +617,7 @@ const ChatContent = memo<ChatContentProps>(
569617 Session ID: { sessionId }
570618 </ div >
571619 ) }
572- { hasNotifications ? (
573- < div className = "space-y-2" >
574- < AgentThread
575- isConnected = { connectionState . status === "connected" }
576- notifications = { notifications }
577- onRetryConnection = { onRetryConnection }
578- onRetryLastAction = { onRetryLastAction }
579- onDismissError = { onDismissError }
580- />
581- </ div >
582- ) : (
583- < div className = "flex items-center justify-center h-full min-h-[200px] flex-col" >
584- < PanelEmptyState
585- title = "Waiting for agent"
586- description = "Your AI agent will appear here when active"
587- icon = { < BotMessageSquareIcon /> }
588- />
589- { isDisconnected && agentId && (
590- < AgentDocs
591- className = "border-t pt-6 px-5"
592- title = "Make sure you're connected to an agent"
593- description = "Run this command in your terminal:"
594- agents = { [ agentId ] }
595- />
596- ) }
597- { isDisconnected && (
598- < Button
599- variant = "outline"
600- onClick = { onRetryConnection }
601- type = "button"
602- className = "mt-4"
603- >
604- Retry
605- </ Button >
606- ) }
607- </ div >
608- ) }
620+ { renderThread ( ) }
609621 </ div >
610622
611623 < ScrollToBottomButton
@@ -671,6 +683,7 @@ const AgentPanel: React.FC = () => {
671683 const {
672684 connect,
673685 disconnect,
686+ setActiveSessionId,
674687 connectionState,
675688 notifications,
676689 pendingPermission,
@@ -695,14 +708,18 @@ const AgentPanel: React.FC = () => {
695708
696709 // Auto-connect to agent when we have an active session, but only once per session
697710 useEffect ( ( ) => {
711+ setActiveSessionId ( null ) ;
712+
698713 if ( wsUrl === NO_WS_SET ) {
699714 return ;
700715 }
701716
702717 logger . debug ( "Auto-connecting to agent" , {
703718 sessionId : activeSessionId ,
704719 } ) ;
705- connect ( ) ;
720+ void connect ( ) . catch ( ( error ) => {
721+ logger . error ( "Failed to connect to agent" , { error } ) ;
722+ } ) ;
706723
707724 return ( ) => {
708725 // We don't want to disconnect so users can switch between different
@@ -969,6 +986,92 @@ const AgentPanel: React.FC = () => {
969986 ) ;
970987 }
971988
989+ const renderBody = ( ) => {
990+ const isConnecting = connectionState . status === "connecting" ;
991+ const delay = 200 ; // ms
992+ if ( isConnecting ) {
993+ return (
994+ < DelayMount milliseconds = { delay } >
995+ < div className = "flex items-center justify-center h-full min-h-[200px] flex-col" >
996+ < Spinner size = "medium" className = "text-primary" />
997+ < span className = "text-sm text-muted-foreground" >
998+ Connecting to the agent...
999+ </ span >
1000+ </ div >
1001+ </ DelayMount >
1002+ ) ;
1003+ }
1004+
1005+ const isLoadingSession =
1006+ tabLastActiveSessionId == null && connectionState . status === "connected" ;
1007+ if ( isLoadingSession ) {
1008+ return (
1009+ < DelayMount milliseconds = { delay } >
1010+ < div className = "flex items-center justify-center h-full min-h-[200px] flex-col" >
1011+ < Spinner size = "medium" className = "text-primary" />
1012+ < span className = "text-sm text-muted-foreground" >
1013+ Creating a new session...
1014+ </ span >
1015+ </ div >
1016+ </ DelayMount >
1017+ ) ;
1018+ }
1019+
1020+ return (
1021+ < >
1022+ < ChatContent
1023+ key = { activeSessionId }
1024+ agentId = { selectedTab ?. agentId }
1025+ sessionId = { selectedTab ?. externalAgentSessionId ?? null }
1026+ hasNotifications = { hasNotifications }
1027+ connectionState = { connectionState }
1028+ notifications = { notifications }
1029+ pendingPermission = { pendingPermission }
1030+ onResolvePermission = { ( option ) => {
1031+ logger . debug ( "Resolving permission request" , {
1032+ sessionId : activeSessionId ,
1033+ option,
1034+ } ) ;
1035+ resolvePermission ( option ) ;
1036+ } }
1037+ onRetryConnection = { handleManualConnect }
1038+ />
1039+
1040+ < LoadingIndicator
1041+ isLoading = { isLoading }
1042+ isRequestingPermission = { ! ! pendingPermission }
1043+ onStop = { handleStop }
1044+ />
1045+
1046+ { files && files . length > 0 && (
1047+ < div className = "flex flex-row gap-1 flex-wrap p-3 border-t" >
1048+ { files . map ( ( file ) => (
1049+ < FileAttachmentPill
1050+ file = { file }
1051+ key = { file . name }
1052+ onRemove = { ( ) => handleRemoveFile ( file ) }
1053+ />
1054+ ) ) }
1055+ </ div >
1056+ ) }
1057+
1058+ < PromptArea
1059+ isLoading = { isLoading }
1060+ activeSessionId = { activeSessionId }
1061+ promptValue = { promptValue }
1062+ onPromptValueChange = { setPromptValue }
1063+ onPromptSubmit = { handlePromptSubmit }
1064+ onAddFiles = { handleAddFiles }
1065+ onStop = { handleStop }
1066+ fileInputRef = { fileInputRef }
1067+ commands = { availableCommands }
1068+ sessionMode = { sessionMode }
1069+ onModeChange = { handleModeChange }
1070+ />
1071+ </ >
1072+ ) ;
1073+ } ;
1074+
9721075 return (
9731076 < div className = "flex flex-col flex-1 overflow-hidden mo-agent-panel" >
9741077 < AgentPanelHeader
@@ -982,54 +1085,7 @@ const AgentPanel: React.FC = () => {
9821085 />
9831086 < SessionTabs />
9841087
985- < ChatContent
986- agentId = { selectedTab ?. agentId }
987- sessionId = { activeSessionId }
988- hasNotifications = { hasNotifications }
989- connectionState = { connectionState }
990- notifications = { notifications }
991- pendingPermission = { pendingPermission }
992- onResolvePermission = { ( option ) => {
993- logger . debug ( "Resolving permission request" , {
994- sessionId : activeSessionId ,
995- option,
996- } ) ;
997- resolvePermission ( option ) ;
998- } }
999- onRetryConnection = { handleManualConnect }
1000- />
1001-
1002- < LoadingIndicator
1003- isLoading = { isLoading }
1004- isRequestingPermission = { ! ! pendingPermission }
1005- onStop = { handleStop }
1006- />
1007-
1008- { files && files . length > 0 && (
1009- < div className = "flex flex-row gap-1 flex-wrap p-3 border-t" >
1010- { files . map ( ( file ) => (
1011- < FileAttachmentPill
1012- file = { file }
1013- key = { file . name }
1014- onRemove = { ( ) => handleRemoveFile ( file ) }
1015- />
1016- ) ) }
1017- </ div >
1018- ) }
1019-
1020- < PromptArea
1021- isLoading = { isLoading }
1022- activeSessionId = { activeSessionId }
1023- promptValue = { promptValue }
1024- onPromptValueChange = { setPromptValue }
1025- onPromptSubmit = { handlePromptSubmit }
1026- onAddFiles = { handleAddFiles }
1027- onStop = { handleStop }
1028- fileInputRef = { fileInputRef }
1029- commands = { availableCommands }
1030- sessionMode = { sessionMode }
1031- onModeChange = { handleModeChange }
1032- />
1088+ { renderBody ( ) }
10331089 </ div >
10341090 ) ;
10351091} ;
0 commit comments