From b5f0c4bf7394ee2548d7bc168d7c99ff3ac53ee7 Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Sun, 17 Nov 2024 19:43:08 +0000 Subject: [PATCH] [Electron] Terminal commands (#1531) * Add live terminal output * Fix scrolling * Refactor loading * Fallback to polling if endpoint fails * Comment * Move clientId to executionStore Refactor types * Remove polling * wip terminal command input * Refactor to use node-pty * Hide tabs if not electron * Lint fix * ts fix * Refactor tab components --- .../bottomPanel/tabs/IntegratedTerminal.vue | 104 ------------------ .../tabs/terminal/BaseTerminal.vue | 30 +++++ .../tabs/terminal/CommandTerminal.vue | 73 ++++++++++++ .../tabs/terminal/LogsTerminal.vue | 90 +++++++++++++++ .../bottomPanelTabs/integratedTerminalTab.ts | 14 --- src/hooks/bottomPanelTabs/terminalTabs.ts | 25 +++++ src/hooks/bottomPanelTabs/useTerminal.ts | 69 ++++++++++++ src/i18n.ts | 1 + src/stores/workspace/bottomPanelStore.ts | 11 +- 9 files changed, 297 insertions(+), 120 deletions(-) delete mode 100644 src/components/bottomPanel/tabs/IntegratedTerminal.vue create mode 100644 src/components/bottomPanel/tabs/terminal/BaseTerminal.vue create mode 100644 src/components/bottomPanel/tabs/terminal/CommandTerminal.vue create mode 100644 src/components/bottomPanel/tabs/terminal/LogsTerminal.vue delete mode 100644 src/hooks/bottomPanelTabs/integratedTerminalTab.ts create mode 100644 src/hooks/bottomPanelTabs/terminalTabs.ts create mode 100644 src/hooks/bottomPanelTabs/useTerminal.ts diff --git a/src/components/bottomPanel/tabs/IntegratedTerminal.vue b/src/components/bottomPanel/tabs/IntegratedTerminal.vue deleted file mode 100644 index 84a6567f0..000000000 --- a/src/components/bottomPanel/tabs/IntegratedTerminal.vue +++ /dev/null @@ -1,104 +0,0 @@ - - - - - diff --git a/src/components/bottomPanel/tabs/terminal/BaseTerminal.vue b/src/components/bottomPanel/tabs/terminal/BaseTerminal.vue new file mode 100644 index 000000000..1e5f97d06 --- /dev/null +++ b/src/components/bottomPanel/tabs/terminal/BaseTerminal.vue @@ -0,0 +1,30 @@ + + + + + diff --git a/src/components/bottomPanel/tabs/terminal/CommandTerminal.vue b/src/components/bottomPanel/tabs/terminal/CommandTerminal.vue new file mode 100644 index 000000000..764a78e99 --- /dev/null +++ b/src/components/bottomPanel/tabs/terminal/CommandTerminal.vue @@ -0,0 +1,73 @@ + + + + + diff --git a/src/components/bottomPanel/tabs/terminal/LogsTerminal.vue b/src/components/bottomPanel/tabs/terminal/LogsTerminal.vue new file mode 100644 index 000000000..460d56b78 --- /dev/null +++ b/src/components/bottomPanel/tabs/terminal/LogsTerminal.vue @@ -0,0 +1,90 @@ + + + + + diff --git a/src/hooks/bottomPanelTabs/integratedTerminalTab.ts b/src/hooks/bottomPanelTabs/integratedTerminalTab.ts deleted file mode 100644 index 7c92da551..000000000 --- a/src/hooks/bottomPanelTabs/integratedTerminalTab.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { useI18n } from 'vue-i18n' -import { markRaw } from 'vue' -import IntegratedTerminal from '@/components/bottomPanel/tabs/IntegratedTerminal.vue' -import { BottomPanelExtension } from '@/types/extensionTypes' - -export const useIntegratedTerminalTab = (): BottomPanelExtension => { - const { t } = useI18n() - return { - id: 'integrated-terminal', - title: t('terminal'), - component: markRaw(IntegratedTerminal), - type: 'vue' - } -} diff --git a/src/hooks/bottomPanelTabs/terminalTabs.ts b/src/hooks/bottomPanelTabs/terminalTabs.ts new file mode 100644 index 000000000..7357b5f3d --- /dev/null +++ b/src/hooks/bottomPanelTabs/terminalTabs.ts @@ -0,0 +1,25 @@ +import { useI18n } from 'vue-i18n' +import { markRaw } from 'vue' +import { BottomPanelExtension } from '@/types/extensionTypes' +import LogsTerminal from '@/components/bottomPanel/tabs/terminal/LogsTerminal.vue' +import CommandTerminal from '@/components/bottomPanel/tabs/terminal/CommandTerminal.vue' + +export const useLogsTerminalTab = (): BottomPanelExtension => { + const { t } = useI18n() + return { + id: 'logs-terminal', + title: t('logs'), + component: markRaw(LogsTerminal), + type: 'vue' + } +} + +export const useCommandTerminalTab = (): BottomPanelExtension => { + const { t } = useI18n() + return { + id: 'command-terminal', + title: t('terminal'), + component: markRaw(CommandTerminal), + type: 'vue' + } +} diff --git a/src/hooks/bottomPanelTabs/useTerminal.ts b/src/hooks/bottomPanelTabs/useTerminal.ts new file mode 100644 index 000000000..e7a8742a6 --- /dev/null +++ b/src/hooks/bottomPanelTabs/useTerminal.ts @@ -0,0 +1,69 @@ +import { FitAddon } from '@xterm/addon-fit' +import { Terminal } from '@xterm/xterm' +import { debounce } from 'lodash' +import { onMounted, onUnmounted, Ref } from 'vue' +import '@xterm/xterm/css/xterm.css' + +export function useTerminal(element: Ref) { + const fitAddon = new FitAddon() + const terminal = new Terminal({ + convertEol: true + }) + terminal.loadAddon(fitAddon) + + onMounted(async () => { + terminal.open(element.value) + }) + + onUnmounted(() => { + terminal.dispose() + }) + + return { + terminal, + useAutoSize( + root: Ref, + autoRows: boolean = true, + autoCols: boolean = true, + onResize?: () => void + ) { + const ensureValidRows = (rows: number | undefined) => { + if (rows == null || isNaN(rows)) { + return root.value?.clientHeight / 20 + } + return rows + } + + const ensureValidCols = (cols: number | undefined): number => { + if (cols == null || isNaN(cols)) { + // Sometimes this is NaN if so, estimate. + return root.value?.clientWidth / 8 + } + return cols + } + + const resize = () => { + const dims = fitAddon.proposeDimensions() + // Sometimes propose returns NaN, so we may need to estimate. + terminal.resize( + autoCols ? ensureValidCols(dims?.cols) : terminal.cols, + autoRows ? ensureValidRows(dims?.rows) : terminal.rows + ) + onResize?.() + } + + const resizeObserver = new ResizeObserver(debounce(resize, 25)) + + onMounted(async () => { + resizeObserver.observe(root.value) + resize() + }) + + onUnmounted(() => { + resizeObserver.disconnect() + }) + + return { resize } + } + } +} diff --git a/src/i18n.ts b/src/i18n.ts index 2da0c5da4..66614af1a 100644 --- a/src/i18n.ts +++ b/src/i18n.ts @@ -57,6 +57,7 @@ const messages = { loadAllFolders: 'Load All Folders', refresh: 'Refresh', terminal: 'Terminal', + logs: 'Logs', videoFailedToLoad: 'Video failed to load', extensionName: 'Extension Name', reloadToApplyChanges: 'Reload to apply changes', diff --git a/src/stores/workspace/bottomPanelStore.ts b/src/stores/workspace/bottomPanelStore.ts index 62a3c8575..e3a74ff35 100644 --- a/src/stores/workspace/bottomPanelStore.ts +++ b/src/stores/workspace/bottomPanelStore.ts @@ -2,8 +2,12 @@ import type { BottomPanelExtension } from '@/types/extensionTypes' import { defineStore } from 'pinia' import { computed, ref } from 'vue' import { useCommandStore } from '@/stores/commandStore' -import { useIntegratedTerminalTab } from '@/hooks/bottomPanelTabs/integratedTerminalTab' +import { + useLogsTerminalTab, + useCommandTerminalTab +} from '@/hooks/bottomPanelTabs/terminalTabs' import { ComfyExtension } from '@/types/comfy' +import { isElectron } from '@/utils/envUtil' export const useBottomPanelStore = defineStore('bottomPanel', () => { const bottomPanelVisible = ref(false) @@ -49,7 +53,10 @@ export const useBottomPanelStore = defineStore('bottomPanel', () => { } const registerCoreBottomPanelTabs = () => { - registerBottomPanelTab(useIntegratedTerminalTab()) + registerBottomPanelTab(useLogsTerminalTab()) + if (isElectron()) { + registerBottomPanelTab(useCommandTerminalTab()) + } } const registerExtensionBottomPanelTabs = (extension: ComfyExtension) => {