Skip to content

Commit

Permalink
Add terminal (#783)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kitenite authored Nov 15, 2024
1 parent 0efefa7 commit 271dcbe
Show file tree
Hide file tree
Showing 9 changed files with 205 additions and 10 deletions.
14 changes: 7 additions & 7 deletions apps/studio/electron/main/chat/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ enum OPEN_AI_MODELS {
GPT_4_TURBO = 'gpt-4-turbo',
}

class LLMService {
private static instance: LLMService;
class LlmManager {
private static instance: LlmManager;
private provider = LLMProvider.ANTHROPIC;
private model: LanguageModelV1;
private abortController: AbortController | null = null;
Expand Down Expand Up @@ -95,11 +95,11 @@ class LLMService {
}
}

public static getInstance(): LLMService {
if (!LLMService.instance) {
LLMService.instance = new LLMService();
public static getInstance(): LlmManager {
if (!LlmManager.instance) {
LlmManager.instance = new LlmManager();
}
return LLMService.instance;
return LlmManager.instance;
}

getSystemMessage(): CoreSystemMessage {
Expand Down Expand Up @@ -201,4 +201,4 @@ class LLMService {
}
}

export default LLMService.getInstance();
export default LlmManager.getInstance();
2 changes: 2 additions & 0 deletions apps/studio/electron/main/events/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { listenForChatMessages } from './chat';
import { listenForCodeMessages } from './code';
import { listenForCreateMessages } from './create';
import { listenForStorageMessages } from './storage';
import { listenForTerminalMessages } from './terminal';

export function listenForIpcMessages() {
listenForGeneralMessages();
Expand All @@ -20,6 +21,7 @@ export function listenForIpcMessages() {
listenForAuthMessages();
listenForCreateMessages();
listenForChatMessages();
listenForTerminalMessages();
}

function listenForGeneralMessages() {
Expand Down
25 changes: 25 additions & 0 deletions apps/studio/electron/main/events/terminal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { MainChannels } from '@onlook/models/constants';
import { ipcMain } from 'electron';
import terminal from '../terminal';

export function listenForTerminalMessages() {
ipcMain.handle(MainChannels.TERMINAL_CREATE, (e: Electron.IpcMainInvokeEvent, args) => {
const { id, options } = args as { id: string; options: { cwd?: string } };
terminal.createTerminal(id, options);
});

ipcMain.handle(MainChannels.TERMINAL_INPUT, (_, args) => {
const { id, data } = args;
terminal.write(id, data);
});

ipcMain.handle(MainChannels.TERMINAL_RESIZE, (_, args) => {
const { id, cols, rows } = args;
terminal.resize(id, cols, rows);
});

ipcMain.handle(MainChannels.TERMINAL_KILL, (_, args) => {
const { id } = args;
terminal.kill(id);
});
}
64 changes: 64 additions & 0 deletions apps/studio/electron/main/terminal/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { MainChannels } from '@onlook/models/constants';
import * as pty from 'node-pty';
import os from 'os';
import { mainWindow } from '..';

class TerminalManager {
private static instance: TerminalManager;
private processes: Map<string, pty.IPty>;

private constructor() {
this.processes = new Map();
}

static getInstance(): TerminalManager {
if (!TerminalManager.instance) {
TerminalManager.instance = new TerminalManager();
}
return TerminalManager.instance;
}

createTerminal(id: string, options?: { cwd?: string }): void {
const shell = os.platform() === 'win32' ? 'powershell.exe' : 'bash';

const ptyProcess = pty.spawn(shell, [], {
name: 'xterm-color',
cols: 80,
rows: 24,
cwd: options?.cwd ?? process.env.HOME,
env: process.env,
});

ptyProcess.onData((data: string) => {
mainWindow?.webContents.send(MainChannels.TERMINAL_DATA_STREAM, {
id,
data,
});
});

this.processes.set(id, ptyProcess);
}

write(id: string, data: string): void {
this.processes.get(id)?.write(data);
}

resize(id: string, cols: number, rows: number): void {
this.processes.get(id)?.resize(cols, rows);
}

kill(id: string): void {
const process = this.processes.get(id);
if (process) {
process.kill();
this.processes.delete(id);
}
}

killAll(): void {
this.processes.forEach((process) => process.kill());
this.processes.clear();
}
}

export default TerminalManager.getInstance();
6 changes: 4 additions & 2 deletions apps/studio/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
"@opentelemetry/sdk-node": "^0.54.2",
"@shikijs/monaco": "^1.22.0",
"@supabase/supabase-js": "^2.45.6",
"@xterm/addon-fit": "^0.10.0",
"@xterm/xterm": "^5.6.0-beta.70",
"ai": "^3.4.29",
"electron-log": "^5.2.0",
"electron-updater": "^6.3.4",
Expand All @@ -63,6 +65,7 @@
"mixpanel": "^0.18.0",
"monaco-editor": "^0.52.0",
"nanoid": "^5.0.7",
"node-pty": "^1.1.0-beta22",
"partial-json": "^0.1.7",
"prosemirror-commands": "^1.6.0",
"prosemirror-history": "^1.4.1",
Expand Down Expand Up @@ -98,8 +101,7 @@
"@vitejs/plugin-react": "^4.2.1",
"autoprefixer": "^10.4.20",
"css-tree": "^2.3.1",
"electron": "33.0.2",
"electron-builder": "^25.1.8",
"electron": "32.2.1",
"eslint": "8.x",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-react": "^7.34.3",
Expand Down
94 changes: 94 additions & 0 deletions apps/studio/src/routes/editor/Toolbar/Terminal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { MainChannels } from '@onlook/models/constants';
import { FitAddon } from '@xterm/addon-fit';
import { useEffect, useRef } from 'react';
import { Terminal as XTerm } from 'xterm';
import 'xterm/css/xterm.css';

interface TerminalProps {
id?: string;
}

interface TerminalMessage {
id: string;
data: string;
}

const TERMINAL_CONFIG = {
cursorBlink: true,
fontSize: 14,
fontFamily: 'monospace',
} as const;

const Terminal = ({ id = 'default' }: TerminalProps) => {
const terminalRef = useRef<HTMLDivElement>(null);
const xtermRef = useRef<XTerm>();

useEffect(() => {
if (!terminalRef.current) {
return;
}

const setupTerminal = async () => {
await window.api.invoke(MainChannels.TERMINAL_CREATE, { id });

const term = new XTerm(TERMINAL_CONFIG);
const fitAddon = new FitAddon();

initializeTerminal(term, fitAddon);
setupEventListeners(term, fitAddon);

xtermRef.current = term;
return term;
};

const cleanup = setupTerminal();

return () => {
cleanup.then((term) => {
term.dispose();
window.api.invoke(MainChannels.TERMINAL_KILL, { id });
});
};
}, [id]);

const initializeTerminal = (term: XTerm, fitAddon: FitAddon) => {
term.loadAddon(fitAddon);
term.open(terminalRef.current!);
fitAddon.fit();
};

const setupEventListeners = (term: XTerm, fitAddon: FitAddon) => {
const handleResize = () => {
fitAddon.fit();
const { cols, rows } = term;
window.api.invoke(MainChannels.TERMINAL_RESIZE, { id, cols, rows });
};

const handleTerminalData = (message: TerminalMessage) => {
if (message.id === id) {
term.write(message.data);
}
};

term.onData((data) => {
window.api.invoke(MainChannels.TERMINAL_INPUT, { id, data });
});

window.api.on(MainChannels.TERMINAL_DATA_STREAM, handleTerminalData);
window.addEventListener('resize', handleResize);

handleResize();

return () => {
window.removeEventListener('resize', handleResize);
};
};

return (
<div className="p-2 bg-black">
<div ref={terminalRef} className="h-full w-full" />
</div>
);
};

export default Terminal;
3 changes: 2 additions & 1 deletion apps/studio/src/routes/editor/Toolbar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,11 @@ const Toolbar = observer(() => {
return (
<div
className={cn(
'border p-1 flex bg-background/30 dark:bg-background/85 backdrop-blur rounded-lg drop-shadow-xl items-center justify-center',
'flex flex-col border p-1 bg-background/30 dark:bg-background/85 backdrop-blur rounded-lg drop-shadow-xl items-center justify-center',
editorEngine.mode === EditorMode.INTERACT ? 'hidden' : 'visible',
)}
>
{/* <Terminal /> */}
<ToggleGroup
type="single"
value={mode}
Expand Down
Binary file modified bun.lockb
Binary file not shown.
7 changes: 7 additions & 0 deletions packages/models/src/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,13 @@ export enum MainChannels {
GET_CONVERSATIONS_BY_PROJECT = 'get-conversations-by-project',
SAVE_CONVERSATION = 'save-conversation',
DELETE_CONVERSATION = 'delete-conversation',

// Terminal
TERMINAL_CREATE = 'terminal-create',
TERMINAL_DATA_STREAM = 'terminal-data-stream',
TERMINAL_INPUT = 'terminal-input',
TERMINAL_RESIZE = 'terminal-resize',
TERMINAL_KILL = 'terminal-kill',
}

export enum Links {
Expand Down

0 comments on commit 271dcbe

Please sign in to comment.