diff --git a/package-lock.json b/package-lock.json index 711afbe444..8f6a46ae82 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,8 @@ "@comfyorg/litegraph": "^0.8.10", "@primevue/themes": "^4.0.5", "@vueuse/core": "^11.0.0", + "@xterm/addon-fit": "^0.10.0", + "@xterm/xterm": "^5.5.0", "axios": "^1.7.4", "dotenv": "^16.4.5", "fuse.js": "^7.0.0", @@ -4619,6 +4621,19 @@ } } }, + "node_modules/@xterm/addon-fit": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.10.0.tgz", + "integrity": "sha512-UFYkDm4HUahf2lnEyHvio51TNGiLK66mqP2JoATy7hRZeXaGMRDr00JiSF7m63vR5WKATF605yEggJKsw0JpMQ==", + "peerDependencies": { + "@xterm/xterm": "^5.0.0" + } + }, + "node_modules/@xterm/xterm": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz", + "integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==" + }, "node_modules/abab": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", diff --git a/package.json b/package.json index 2d93ac9218..ec03de945e 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,8 @@ "@comfyorg/litegraph": "^0.8.10", "@primevue/themes": "^4.0.5", "@vueuse/core": "^11.0.0", + "@xterm/addon-fit": "^0.10.0", + "@xterm/xterm": "^5.5.0", "axios": "^1.7.4", "dotenv": "^16.4.5", "fuse.js": "^7.0.0", diff --git a/src/components/LiteGraphCanvasSplitterOverlay.vue b/src/components/LiteGraphCanvasSplitterOverlay.vue index 312047775d..93632d5cb1 100644 --- a/src/components/LiteGraphCanvasSplitterOverlay.vue +++ b/src/components/LiteGraphCanvasSplitterOverlay.vue @@ -15,7 +15,7 @@ diff --git a/src/components/bottomPanel/tabs/IntegratedTerminal.vue b/src/components/bottomPanel/tabs/IntegratedTerminal.vue index 3026e4d7e8..84a6567f01 100644 --- a/src/components/bottomPanel/tabs/IntegratedTerminal.vue +++ b/src/components/bottomPanel/tabs/IntegratedTerminal.vue @@ -1,61 +1,104 @@ + + diff --git a/src/scripts/api.ts b/src/scripts/api.ts index 4043daebab..9d3b7480ac 100644 --- a/src/scripts/api.ts +++ b/src/scripts/api.ts @@ -11,7 +11,8 @@ import { type User, type Settings, type UserDataFullInfo, - validateComfyNodeDef + validateComfyNodeDef, + LogsRawResponse } from '@/types/apiTypes' import axios from 'axios' @@ -202,11 +203,6 @@ class ComfyApi extends EventTarget { new CustomEvent('status', { detail: msg.data.status }) ) break - case 'progress': - this.dispatchEvent( - new CustomEvent('progress', { detail: msg.data }) - ) - break case 'executing': this.dispatchEvent( new CustomEvent('executing', { @@ -214,29 +210,15 @@ class ComfyApi extends EventTarget { }) ) break + case 'progress': case 'executed': - this.dispatchEvent( - new CustomEvent('executed', { detail: msg.data }) - ) - break case 'execution_start': - this.dispatchEvent( - new CustomEvent('execution_start', { detail: msg.data }) - ) - break case 'execution_success': - this.dispatchEvent( - new CustomEvent('execution_success', { detail: msg.data }) - ) - break case 'execution_error': - this.dispatchEvent( - new CustomEvent('execution_error', { detail: msg.data }) - ) - break case 'execution_cached': + case 'logs': this.dispatchEvent( - new CustomEvent('execution_cached', { detail: msg.data }) + new CustomEvent(msg.type, { detail: msg.data }) ) break default: @@ -714,6 +696,17 @@ class ComfyApi extends EventTarget { return (await axios.get(this.internalURL('/logs'))).data } + async getRawLogs(): Promise { + return (await axios.get(this.internalURL('/logs/raw'))).data + } + + async subscribeLogs(enabled: boolean): Promise { + return await axios.patch(this.internalURL('/logs/subscribe'), { + enabled, + clientId: this.clientId + }) + } + async getFolderPaths(): Promise> { return (await axios.get(this.internalURL('/folder_paths'))).data } diff --git a/src/stores/executionStore.ts b/src/stores/executionStore.ts index 38fcf80727..19f4d0931f 100644 --- a/src/stores/executionStore.ts +++ b/src/stores/executionStore.ts @@ -8,7 +8,8 @@ import type { ExecutingWsMessage, ExecutionCachedWsMessage, ExecutionStartWsMessage, - ProgressWsMessage + ProgressWsMessage, + StatusWsMessage } from '@/types/apiTypes' export interface QueuedPrompt { @@ -17,6 +18,7 @@ export interface QueuedPrompt { } export const useExecutionStore = defineStore('execution', () => { + const clientId = ref(null) const activePromptId = ref(null) const queuedPrompts = ref>({}) const executingNodeId = ref(null) @@ -84,6 +86,7 @@ export const useExecutionStore = defineStore('execution', () => { api.addEventListener('executed', handleExecuted as EventListener) api.addEventListener('executing', handleExecuting as EventListener) api.addEventListener('progress', handleProgress as EventListener) + api.addEventListener('status', handleStatus as EventListener) } function unbindExecutionEvents() { @@ -98,6 +101,7 @@ export const useExecutionStore = defineStore('execution', () => { api.removeEventListener('executed', handleExecuted as EventListener) api.removeEventListener('executing', handleExecuting as EventListener) api.removeEventListener('progress', handleProgress as EventListener) + api.removeEventListener('status', handleStatus as EventListener) } function handleExecutionStart(e: CustomEvent) { @@ -140,6 +144,15 @@ export const useExecutionStore = defineStore('execution', () => { _executingNodeProgress.value = e.detail } + function handleStatus(e: CustomEvent) { + if (api.clientId) { + clientId.value = api.clientId + + // Once we've received the clientId we no longer need to listen + api.removeEventListener('status', handleStatus as EventListener) + } + } + function storePrompt({ nodes, id, @@ -167,6 +180,7 @@ export const useExecutionStore = defineStore('execution', () => { return { isIdle, + clientId, activePromptId, queuedPrompts, executingNodeId, diff --git a/src/types/apiTypes.ts b/src/types/apiTypes.ts index 2876b828a8..c7ddc4348f 100644 --- a/src/types/apiTypes.ts +++ b/src/types/apiTypes.ts @@ -77,6 +77,23 @@ const zExecutionErrorWsMessage = zExecutionWsMessageBase.extend({ current_outputs: z.any() }) +const zTerminalSize = z.object({ + cols: z.number(), + row: z.number() +}) +const zLogEntry = z.object({ + t: z.string(), + m: z.string() +}) +const zLogsWsMessage = z.object({ + size: zTerminalSize.optional(), + entries: z.array(zLogEntry) +}) +const zLogRawResponse = z.object({ + size: zTerminalSize, + entries: z.array(zLogEntry) +}) + export type StatusWsMessageStatus = z.infer export type StatusWsMessage = z.infer export type ProgressWsMessage = z.infer @@ -91,6 +108,7 @@ export type ExecutionInterruptedWsMessage = z.infer< typeof zExecutionInterruptedWsMessage > export type ExecutionErrorWsMessage = z.infer +export type LogsWsMessage = z.infer // End of ws messages const zPromptInputItem = z.object({ @@ -516,3 +534,6 @@ export type SystemStats = z.infer export type User = z.infer export type UserData = z.infer export type UserDataFullInfo = z.infer +export type TerminalSize = z.infer +export type LogEntry = z.infer +export type LogsRawResponse = z.infer