11'use client'
22
3- import { useState } from 'react'
3+ import { useEffect , useRef , useState } from 'react'
44import { ExternalLink , LoaderCircle , Plus , RefreshCw , TerminalSquare , X } from 'lucide-react'
5- import { Button , buttonVariants } from '@/components/ui/button'
65import { cn } from '@/lib/utils'
76import { getInstanceTerminalConnection } from '@/lib/terminal'
87import type { Instance } from '@/types/instance'
98
9+ const LOAD_TIMEOUT_MS = 8000
10+
1011interface InstanceTerminalWorkspaceProps {
1112 instance : Instance
1213 className ?: string
@@ -31,6 +32,18 @@ export function InstanceTerminalWorkspace({
3132 const [ activeSessionId , setActiveSessionId ] = useState ( 'terminal-1' )
3233 const [ nextSessionNumber , setNextSessionNumber ] = useState ( 2 )
3334 const [ loadedSessions , setLoadedSessions ] = useState < Record < string , boolean > > ( { } )
35+ const [ useFallback , setUseFallback ] = useState ( false )
36+ const loadTimers = useRef < Record < string , ReturnType < typeof setTimeout > > > ( { } )
37+
38+ const terminalUrl = useFallback && connection . directUrl
39+ ? connection . directUrl
40+ : connection . preferredUrl
41+
42+ useEffect ( ( ) => {
43+ return ( ) => {
44+ Object . values ( loadTimers . current ) . forEach ( clearTimeout )
45+ }
46+ } , [ ] )
3447
3548 if ( instance . status !== 'running' ) {
3649 return (
@@ -50,7 +63,7 @@ export function InstanceTerminalWorkspace({
5063 )
5164 }
5265
53- if ( ! connection . preferredUrl ) {
66+ if ( ! terminalUrl ) {
5467 return (
5568 < div className = "flex min-h-[28rem] items-center justify-center rounded-xl border bg-card p-8 text-center" >
5669 < div className = "max-w-sm space-y-3" >
@@ -64,9 +77,24 @@ export function InstanceTerminalWorkspace({
6477 )
6578 }
6679
67- const terminalUrl = connection . preferredUrl
6880 const activeSession = sessions . find ( ( s ) => s . id === activeSessionId ) ?? sessions [ 0 ]
6981
82+ function startLoadTimer ( sessionId : string ) {
83+ clearTimeout ( loadTimers . current [ sessionId ] )
84+ if ( ! useFallback && connection . directUrl && connection . mode === 'proxied' ) {
85+ loadTimers . current [ sessionId ] = setTimeout ( ( ) => {
86+ setUseFallback ( true )
87+ setLoadedSessions ( { } )
88+ setSessions ( ( prev ) => prev . map ( ( s ) => ( { ...s , version : s . version + 1 } ) ) )
89+ } , LOAD_TIMEOUT_MS )
90+ }
91+ }
92+
93+ function handleLoad ( sessionId : string ) {
94+ clearTimeout ( loadTimers . current [ sessionId ] )
95+ setLoadedSessions ( ( prev ) => ( { ...prev , [ sessionId ] : true } ) )
96+ }
97+
7098 function createNewSession ( ) {
7199 const session = createSession ( nextSessionNumber )
72100 setSessions ( ( prev ) => [ ...prev , session ] )
@@ -84,6 +112,7 @@ export function InstanceTerminalWorkspace({
84112
85113 function closeSession ( sessionId : string ) {
86114 if ( sessions . length === 1 ) return
115+ clearTimeout ( loadTimers . current [ sessionId ] )
87116 const idx = sessions . findIndex ( ( s ) => s . id === sessionId )
88117 const remaining = sessions . filter ( ( s ) => s . id !== sessionId )
89118 setSessions ( remaining )
@@ -99,9 +128,7 @@ export function InstanceTerminalWorkspace({
99128
100129 return (
101130 < div className = { cn ( 'flex flex-col gap-0 overflow-hidden rounded-xl border bg-[#0a0a0a]' , className ) } >
102- { /* Toolbar */ }
103131 < div className = "flex items-center gap-1 border-b border-white/10 bg-[#111] px-2 py-1.5" >
104- { /* Tabs */ }
105132 < div className = "flex min-w-0 flex-1 items-center gap-1 overflow-x-auto" >
106133 { sessions . map ( ( session ) => {
107134 const isActive = session . id === activeSession . id
@@ -145,7 +172,6 @@ export function InstanceTerminalWorkspace({
145172 </ button >
146173 </ div >
147174
148- { /* Actions */ }
149175 < div className = "flex items-center gap-1" >
150176 < button
151177 type = "button"
@@ -156,7 +182,7 @@ export function InstanceTerminalWorkspace({
156182 < RefreshCw className = "h-3.5 w-3.5" />
157183 </ button >
158184 < a
159- href = { connection . preferredUrl }
185+ href = { terminalUrl }
160186 target = "_blank"
161187 rel = "noopener noreferrer"
162188 className = "rounded-md p-1.5 text-white/40 transition-colors hover:bg-white/5 hover:text-white/70"
@@ -167,7 +193,6 @@ export function InstanceTerminalWorkspace({
167193 </ div >
168194 </ div >
169195
170- { /* Terminal iframe area */ }
171196 < div className = "relative min-h-[calc(100dvh-16rem)] bg-[#0a0a0a]" >
172197 { sessions . map ( ( session ) => {
173198 const isActive = session . id === activeSession . id
@@ -191,13 +216,14 @@ export function InstanceTerminalWorkspace({
191216 ) }
192217
193218 < iframe
194- key = { `${ session . id } :${ session . version } ` }
219+ key = { `${ session . id } :${ session . version } : ${ terminalUrl } ` }
195220 src = { terminalUrl }
196221 title = { session . title }
197222 className = "block h-full w-full"
198223 style = { { colorScheme : 'dark' , background : '#0a0a0a' } }
199224 allow = "clipboard-read; clipboard-write; fullscreen"
200- onLoad = { ( ) => setLoadedSessions ( ( prev ) => ( { ...prev , [ session . id ] : true } ) ) }
225+ ref = { ( el ) => { if ( el && isActive ) startLoadTimer ( session . id ) } }
226+ onLoad = { ( ) => handleLoad ( session . id ) }
201227 />
202228 </ div >
203229 )
0 commit comments