@@ -10,43 +10,46 @@ import {
1010import { Terminal } from "@xterm/xterm" ;
1111import { AttachAddon } from "@xterm/addon-attach" ;
1212import { FitAddon } from '@xterm/addon-fit' ;
13- import { useRef , useEffect , useState } from "react" ;
13+ import { useRef , useEffect , useState , useMemo } from "react" ;
1414import { sleep } from "@/lib/utils" ;
15- import { IconButton } from "./xui/icon-button" ;
1615import "@xterm/xterm/css/xterm.css" ;
17- import { createTerminal } from "@/api/terminal" ;
18- import { ModelCreateTerminalResponse } from "@/types" ;
1916import { useParams } from 'react-router-dom' ;
2017import { Button } from "./ui/button" ;
2118import { toast } from "sonner" ;
2219import { FMCard } from "./fm" ;
20+ import useTerminal from "@/hooks/useTerminal" ;
21+ import { IconButton } from "./xui/icon-button" ;
2322
2423interface XtermProps {
2524 wsUrl : string ;
2625 setClose : React . Dispatch < React . SetStateAction < boolean > > ;
2726}
2827
2928const XtermComponent : React . FC < XtermProps & JSX . IntrinsicElements [ "div" ] > = ( { wsUrl, setClose, ...props } ) => {
30- const terminalRef = useRef < HTMLDivElement > ( null ) ;
29+ const terminalIdRef = useRef < HTMLDivElement > ( null ) ;
30+ const terminalRef = useRef < Terminal | null > ( null ) ;
3131 const wsRef = useRef < WebSocket | null > ( null ) ;
3232
3333 useEffect ( ( ) => {
3434 return ( ) => {
35- if ( wsRef . current ) {
36- wsRef . current . close ( ) ;
37- }
35+ wsRef . current ?. close ( ) ;
36+ terminalRef . current ?. dispose ( ) ;
3837 } ;
3938 } , [ ] ) ;
4039
4140 useEffect ( ( ) => {
41+ terminalRef . current = new Terminal ( {
42+ cursorBlink : true ,
43+ fontSize : 16 ,
44+ } ) ;
4245 const ws = new WebSocket ( wsUrl ) ;
4346 wsRef . current = ws ;
4447 ws . binaryType = "arraybuffer" ;
4548 ws . onopen = ( ) => {
4649 onResize ( ) ;
4750 }
4851 ws . onclose = ( ) => {
49- terminal . dispose ( ) ;
52+ terminalRef . current ? .dispose ( ) ;
5053 setClose ( true ) ;
5154 }
5255 ws . onerror = ( e ) => {
@@ -57,18 +60,12 @@ const XtermComponent: React.FC<XtermProps & JSX.IntrinsicElements["div"]> = ({ w
5760 }
5861 } , [ wsUrl ] ) ;
5962
60- const terminal = useRef (
61- new Terminal ( {
62- cursorBlink : true ,
63- fontSize : 16 ,
64- } )
65- ) . current ;
6663
6764 const fitAddon = useRef ( new FitAddon ( ) ) . current ;
6865 const sendResize = useRef ( false ) ;
6966
7067 const doResize = ( ) => {
71- if ( ! terminalRef . current ) return ;
68+ if ( ! terminalIdRef . current ) return ;
7269
7370 fitAddon . fit ( ) ;
7471
@@ -104,48 +101,27 @@ const XtermComponent: React.FC<XtermProps & JSX.IntrinsicElements["div"]> = ({ w
104101 } ;
105102
106103 useEffect ( ( ) => {
107- if ( ! wsRef . current || ! terminalRef . current ) return ;
104+ if ( ! wsRef . current || ! terminalIdRef . current || ! terminalRef . current ) return ;
108105 const attachAddon = new AttachAddon ( wsRef . current ) ;
109- terminal . loadAddon ( attachAddon ) ;
110- terminal . loadAddon ( fitAddon ) ;
111- terminal . open ( terminalRef . current ) ;
106+ terminalRef . current . loadAddon ( attachAddon ) ;
107+ terminalRef . current . loadAddon ( fitAddon ) ;
108+ terminalRef . current . open ( terminalIdRef . current ) ;
112109 window . addEventListener ( 'resize' , onResize ) ;
113110 return ( ) => {
114111 window . removeEventListener ( 'resize' , onResize ) ;
115112 if ( wsRef . current ) {
116113 wsRef . current . close ( ) ;
117114 }
118115 } ;
119- } , [ wsRef . current , terminal ] ) ;
116+ } , [ wsRef . current , terminalRef . current , terminalIdRef . current ] ) ;
120117
121- return < div ref = { terminalRef } { ...props } /> ;
118+ return < div ref = { terminalIdRef } { ...props } /> ;
122119} ;
123120
124121export const TerminalPage = ( ) => {
125- const [ terminal , setTerminal ] = useState < ModelCreateTerminalResponse | null > ( null ) ;
126- const [ open , setOpen ] = useState ( false ) ;
127-
128122 const { id } = useParams < { id : string } > ( ) ;
129-
130- const fetchTerminal = async ( ) => {
131- if ( id && ! terminal ) {
132- try {
133- const createdTerminal = await createTerminal ( Number ( id ) ) ;
134- setTerminal ( createdTerminal ) ;
135- } catch ( e ) {
136- toast ( "Terminal API Error" , {
137- description : "View console for details." ,
138- } )
139- console . error ( "fetch error" , e ) ;
140- return ;
141- }
142- }
143- }
144-
145- useEffect ( ( ) => {
146- fetchTerminal ( ) ;
147- } , [ id ] ) ;
148-
123+ const [ open , setOpen ] = useState ( false ) ;
124+ const terminal = useTerminal ( id ? parseInt ( id ) : undefined ) ;
149125 return (
150126 < div className = "px-8" >
151127 < div className = "flex mt-6 mb-4" >
@@ -158,7 +134,7 @@ export const TerminalPage = () => {
158134 </ div >
159135 { terminal ?. session_id
160136 ?
161- < XtermComponent className = "max-h-[60%] mb-5" wsUrl = { `/api/v1/ws/terminal/${ terminal . session_id } ` } setClose = { setOpen } />
137+ < XtermComponent className = "max-h-[60%] mb-5" wsUrl = { `/api/v1/ws/terminal/${ terminal ? .session_id } ` } setClose = { setOpen } />
162138 :
163139 < p > The server does not exist, or have not been connected yet.</ p >
164140 }
0 commit comments