فهرست منبع

feat: parse new terminal output (#3582)

sp.wack 1 سال پیش
والد
کامیت
eaf8e5c8a7

+ 17 - 0
frontend/src/components/terminal/Terminal.test.tsx

@@ -102,6 +102,23 @@ describe("Terminal", () => {
     expect(mockTerminal.write).toHaveBeenCalledWith("$ ");
     expect(mockTerminal.write).toHaveBeenCalledWith("$ ");
   });
   });
 
 
+  it("should display a custom symbol if output contains a custom symbol", () => {
+    renderTerminal([
+      { type: "input", content: "echo Hello" },
+      {
+        type: "output",
+        content:
+          "Hello\r\n\r\n[Python Interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]\nopenhands@659478cb008c:/workspace $ ",
+      },
+    ]);
+
+    expect(mockTerminal.writeln).toHaveBeenNthCalledWith(1, "echo Hello");
+    expect(mockTerminal.writeln).toHaveBeenNthCalledWith(2, "Hello");
+    expect(mockTerminal.write).toHaveBeenCalledWith(
+      "\nopenhands@659478cb008c:/workspace $ ",
+    );
+  });
+
   // This test fails because it expects `disposeMock` to have been called before the component is unmounted.
   // This test fails because it expects `disposeMock` to have been called before the component is unmounted.
   it.skip("should dispose the terminal on unmount", () => {
   it.skip("should dispose the terminal on unmount", () => {
     const { unmount } = renderWithProviders(<Terminal />);
     const { unmount } = renderWithProviders(<Terminal />);

+ 6 - 3
frontend/src/hooks/useTerminal.ts

@@ -3,6 +3,7 @@ import { Terminal } from "@xterm/xterm";
 import React from "react";
 import React from "react";
 import { Command } from "#/state/commandSlice";
 import { Command } from "#/state/commandSlice";
 import { sendTerminalCommand } from "#/services/terminalService";
 import { sendTerminalCommand } from "#/services/terminalService";
+import { parseTerminalOutput } from "#/utils/parseTerminalOutput";
 
 
 /*
 /*
   NOTE: Tests for this hook are indirectly covered by the tests for the XTermTerminal component.
   NOTE: Tests for this hook are indirectly covered by the tests for the XTermTerminal component.
@@ -101,14 +102,16 @@ export const useTerminal = (commands: Command[] = []) => {
       // Start writing commands from the last command index
       // Start writing commands from the last command index
       for (let i = lastCommandIndex.current; i < commands.length; i += 1) {
       for (let i = lastCommandIndex.current; i < commands.length; i += 1) {
         const command = commands[i];
         const command = commands[i];
-        const lines = command.content.split("\n");
+        const lines = parseTerminalOutput(command.content).output.split("\n");
 
 
         lines.forEach((line: string) => {
         lines.forEach((line: string) => {
-          terminal.current?.writeln(line);
+          terminal.current?.writeln(parseTerminalOutput(line).output);
         });
         });
 
 
         if (command.type === "output") {
         if (command.type === "output") {
-          terminal.current.write("\n$ ");
+          terminal.current.write(
+            `\n${parseTerminalOutput(command.content).symbol} `,
+          );
         }
         }
       }
       }
 
 

+ 21 - 0
frontend/src/utils/parseTerminalOutput.test.ts

@@ -0,0 +1,21 @@
+import { describe, it, expect } from "vitest";
+import { parseTerminalOutput } from "./parseTerminalOutput";
+
+describe("parseTerminalOutput", () => {
+  it("should parse the command, env, and symbol", () => {
+    const raw =
+      "web_scraper.py\r\n\r\n[Python Interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]\nopenhands@659478cb008c:/workspace $ ";
+
+    const parsed = parseTerminalOutput(raw);
+
+    expect(parsed.output).toBe("web_scraper.py");
+    expect(parsed.symbol).toBe("openhands@659478cb008c:/workspace $");
+  });
+
+  it("should return raw output if unable to parse", () => {
+    const raw = "web_scraper.py";
+
+    const parsed = parseTerminalOutput(raw);
+    expect(parsed.output).toBe("web_scraper.py");
+  });
+});

+ 27 - 0
frontend/src/utils/parseTerminalOutput.ts

@@ -0,0 +1,27 @@
+/**
+ * Parses the raw output from the terminal into the command and symbol
+ * @param raw The raw output to be displayed in the terminal
+ * @returns The parsed output
+ *
+ * @example
+ * const raw =
+ *  "web_scraper.py\r\n\r\n[Python Interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]\nopenhands@659478cb008c:/workspace $ ";
+ *
+ * const parsed = parseTerminalOutput(raw);
+ *
+ * console.log(parsed.output); // web_scraper.py
+ * console.log(parsed.symbol); // openhands@659478cb008c:/workspace $
+ */
+export const parseTerminalOutput = (raw: string) => {
+  const envRegex = /\[Python Interpreter: (.*)\]/;
+  const env = raw.match(envRegex);
+  let fullOutput = raw;
+  if (env && env[0]) fullOutput = fullOutput.replace(`${env[0]}\n`, "");
+  const [output, s] = fullOutput.split("\r\n\r\n");
+  const symbol = s || "$";
+
+  return {
+    output: output.trim(),
+    symbol: symbol.trim(),
+  };
+};