mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
Frontend: Implement real terminal with xterm.js (#39)
* Add xterm terminal * Remove unused code blocks * Update README.md with terminal documentation * Update frontend/README.md Co-authored-by: Graham Neubig <neubig@gmail.com> --------- Co-authored-by: Graham Neubig <neubig@gmail.com>
This commit is contained in:
parent
346e992276
commit
cf97b66ff9
1
frontend/.env
Normal file
1
frontend/.env
Normal file
@ -0,0 +1 @@
|
||||
REACT_APP_TERMINAL_WS_URL="ws://localhost:8080/ws"
|
||||
@ -42,3 +42,11 @@ You don’t have to ever use `eject`. The curated feature set is suitable for sm
|
||||
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
|
||||
|
||||
To learn React, check out the [React documentation](https://reactjs.org/).
|
||||
|
||||
## Terminal
|
||||
|
||||
The OpenDevin terminal is powered by [Xterm.js](https://github.com/xtermjs/xterm.js).
|
||||
|
||||
The terminal listens for events over a WebSocket connection. The WebSocket URL is specified by the environment variable `REACT_APP_TERMINAL_WS_URL` (prepending `REACT_APP_` to environment variable names is necessary to expose them).
|
||||
|
||||
A simple websocket server can be found in the `/server` directory.
|
||||
|
||||
26
frontend/package-lock.json
generated
26
frontend/package-lock.json
generated
@ -16,13 +16,16 @@
|
||||
"@types/react": "^18.2.66",
|
||||
"@types/react-dom": "^18.2.22",
|
||||
"@types/react-syntax-highlighter": "^15.5.11",
|
||||
"@xterm/xterm": "^5.4.0",
|
||||
"eslint-config-airbnb-typescript": "^18.0.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"typescript": "^4.9.5",
|
||||
"web-vitals": "^2.1.4"
|
||||
"web-vitals": "^2.1.4",
|
||||
"xterm-addon-attach": "^0.9.0",
|
||||
"xterm-addon-fit": "^0.8.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/parser": "^5.62.0",
|
||||
@ -4630,6 +4633,11 @@
|
||||
"@xtuc/long": "4.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/xterm": {
|
||||
"version": "5.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.4.0.tgz",
|
||||
"integrity": "sha512-GlyzcZZ7LJjhFevthHtikhiDIl8lnTSgol6eTM4aoSNLcuXu3OEhnbqdCVIjtIil3jjabf3gDtb1S8FGahsuEw=="
|
||||
},
|
||||
"node_modules/@xtuc/ieee754": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
|
||||
@ -19116,6 +19124,22 @@
|
||||
"node": ">=0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/xterm-addon-attach": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/xterm-addon-attach/-/xterm-addon-attach-0.9.0.tgz",
|
||||
"integrity": "sha512-NykWWOsobVZPPK3P9eFkItrnBK9Lw0f94uey5zhqIVB1bhswdVBfl+uziEzSOhe2h0rT9wD0wOeAYsdSXeavPw==",
|
||||
"peerDependencies": {
|
||||
"xterm": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/xterm-addon-fit": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/xterm-addon-fit/-/xterm-addon-fit-0.8.0.tgz",
|
||||
"integrity": "sha512-yj3Np7XlvxxhYF/EJ7p3KHaMt6OdwQ+HDu573Vx1lRXsVxOcnVJs51RgjZOouIZOczTsskaS+CpXspK81/DLqw==",
|
||||
"peerDependencies": {
|
||||
"xterm": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/y18n": {
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||
|
||||
@ -11,13 +11,16 @@
|
||||
"@types/react": "^18.2.66",
|
||||
"@types/react-dom": "^18.2.22",
|
||||
"@types/react-syntax-highlighter": "^15.5.11",
|
||||
"@xterm/xterm": "^5.4.0",
|
||||
"eslint-config-airbnb-typescript": "^18.0.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"typescript": "^4.9.5",
|
||||
"web-vitals": "^2.1.4"
|
||||
"web-vitals": "^2.1.4",
|
||||
"xterm-addon-attach": "^0.9.0",
|
||||
"xterm-addon-fit": "^0.8.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
|
||||
@ -1,30 +1,45 @@
|
||||
import React from "react";
|
||||
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||
import { atomDark } from "react-syntax-highlighter/dist/esm/styles/prism";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import { Terminal as XtermTerminal } from "@xterm/xterm";
|
||||
import { AttachAddon } from "xterm-addon-attach";
|
||||
import { FitAddon } from "xterm-addon-fit";
|
||||
import "@xterm/xterm/css/xterm.css";
|
||||
|
||||
function Terminal(): JSX.Element {
|
||||
const terminalOutput = `> chatbot-ui@2.0.0 prepare
|
||||
> husky install
|
||||
const terminalRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
husky - Git hooks installed
|
||||
useEffect(() => {
|
||||
const terminal = new XtermTerminal({
|
||||
fontFamily: "Menlo, Monaco, 'Courier New', monospace",
|
||||
fontSize: 14,
|
||||
});
|
||||
|
||||
added 1455 packages, and audited 1456 packages in 1m
|
||||
const fitAddon = new FitAddon();
|
||||
terminal.loadAddon(fitAddon);
|
||||
|
||||
295 packages are looking for funding
|
||||
run \`npm fund\` for details
|
||||
|
||||
found 0 vulnerabilities
|
||||
npm notice
|
||||
npm notice New minor version of npm available! 10.7.3 -> 10.9.0
|
||||
...`;
|
||||
terminal.open(terminalRef.current as HTMLDivElement);
|
||||
|
||||
return (
|
||||
<div className="terminal">
|
||||
<SyntaxHighlighter language="bash" style={atomDark}>
|
||||
{terminalOutput}
|
||||
</SyntaxHighlighter>
|
||||
</div>
|
||||
);
|
||||
// Without this timeout, `fitAddon.fit()` throws the error
|
||||
// "this._renderer.value is undefined"
|
||||
setTimeout(() => {
|
||||
fitAddon.fit();
|
||||
}, 1);
|
||||
|
||||
if (!process.env.REACT_APP_TERMINAL_WS_URL) {
|
||||
throw new Error(
|
||||
"The environment variable REACT_APP_TERMINAL_WS_URL is not set. Please set it to the WebSocket URL of the terminal server.",
|
||||
);
|
||||
}
|
||||
const attachAddon = new AttachAddon(
|
||||
new WebSocket(process.env.REACT_APP_TERMINAL_WS_URL as string),
|
||||
);
|
||||
terminal.loadAddon(attachAddon);
|
||||
|
||||
return () => {
|
||||
terminal.dispose();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return <div ref={terminalRef} style={{ width: "100%", height: "100%" }} />;
|
||||
}
|
||||
|
||||
export default Terminal;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user