Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Electron] Terminal commands #1531

Merged
merged 17 commits into from
Nov 17, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Move clientId to executionStore
Refactor types
pythongosssss committed Oct 29, 2024
commit 8deb66d0abe8cd0e413f25057e2cc70a785eae9e
25 changes: 12 additions & 13 deletions src/components/bottomPanel/tabs/IntegratedTerminal.vue
Original file line number Diff line number Diff line change
@@ -14,10 +14,14 @@
import '@xterm/xterm/css/xterm.css'
import { Terminal } from '@xterm/xterm'
import { FitAddon } from '@xterm/addon-fit'
import { api, LogEntry, TerminalSize } from '@/scripts/api'
import { api } from '@/scripts/api'
import { onMounted, onUnmounted, ref } from 'vue'
import { debounce } from 'lodash'
import ProgressSpinner from 'primevue/progressspinner'
import { useExecutionStore } from '@/stores/executionStore'
import { storeToRefs } from 'pinia'
import { until } from '@vueuse/core'
import { LogEntry, LogsWsMessage, TerminalSize } from '@/types/apiTypes'

let intervalId: number
let useFallbackPolling: boolean = false
@@ -41,15 +45,7 @@ const update = (entries: Array<LogEntry>, size?: TerminalSize) => {
terminal.write(entries.map((e) => e.m).join(''))
}

const logReceived = (
e: CustomEvent<{
entries: Array<{ t: string; m: string }>
size: {
rows: number
cols: number
} | null
}>
) => {
const logReceived = (e: CustomEvent<LogsWsMessage>) => {
update(e.detail.entries, e.detail.size)
}

@@ -70,9 +66,12 @@ const watchLogs = async () => {
if (useFallbackPolling) {
intervalId = window.setInterval(loadLogText, 500)
} else {
// It is possible for a user to open the terminal before a clientid is assigned
// subscribe requires this so wait for it
await api.waitForClientId()
const { clientId } = storeToRefs(useExecutionStore())
if (!clientId.value) {
console.log('waiting')
await until(clientId).not.toBeNull()
console.log('waited', clientId.value)
}
api.subscribeLogs(true)
api.addEventListener('logs', logReceived)
}
32 changes: 3 additions & 29 deletions src/scripts/api.ts
Original file line number Diff line number Diff line change
@@ -12,7 +12,8 @@ import {
type User,
type Settings,
type UserDataFullInfo,
validateComfyNodeDef
validateComfyNodeDef,
LogsRawResponse
} from '@/types/apiTypes'
import axios from 'axios'

@@ -30,19 +31,7 @@ interface QueuePromptRequestBody {
number?: number
}

export interface LogEntry {
t: string
m: string
}

export interface TerminalSize {
cols: number
rows: number
}

class ComfyApi extends EventTarget {
#clientIdBlock: Promise<void>
#clientIdResolve: Function
#registered = new Set()
api_host: string
api_base: string
@@ -69,17 +58,6 @@ class ComfyApi extends EventTarget {
this.initialClientId = sessionStorage.getItem('clientId')
}

async waitForClientId() {
if (this.clientId) {
return this.clientId
}
if (!this.#clientIdBlock) {
this.#clientIdBlock = new Promise((res) => (this.#clientIdResolve = res))
}
await this.#clientIdBlock
return this.clientId
}

internalURL(route: string): string {
return this.api_base + '/internal' + route
}
@@ -221,7 +199,6 @@ class ComfyApi extends EventTarget {
this.clientId = clientId
window.name = clientId // use window name so it isnt reused when duplicating tabs
sessionStorage.setItem('clientId', clientId) // store in session storage so duplicate tab can load correct workflow
this.#clientIdResolve?.()
}
this.dispatchEvent(
new CustomEvent('status', { detail: msg.data.status })
@@ -748,10 +725,7 @@ class ComfyApi extends EventTarget {
return (await axios.get(this.internalURL('/logs'))).data
}

async getRawLogs(): Promise<{
size: TerminalSize
entries: Array<LogEntry>
}> {
async getRawLogs(): Promise<LogsRawResponse> {
return (await axios.get(this.internalURL('/logs/raw'))).data
}

16 changes: 15 additions & 1 deletion src/stores/executionStore.ts
Original file line number Diff line number Diff line change
@@ -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<string | null>(null)
const activePromptId = ref<string | null>(null)
const queuedPrompts = ref<Record<string, QueuedPrompt>>({})
const executingNodeId = ref<string | null>(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<ExecutionStartWsMessage>) {
@@ -140,6 +144,15 @@ export const useExecutionStore = defineStore('execution', () => {
_executingNodeProgress.value = e.detail
}

function handleStatus(e: CustomEvent<StatusWsMessage>) {
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,
21 changes: 21 additions & 0 deletions src/types/apiTypes.ts
Original file line number Diff line number Diff line change
@@ -84,6 +84,23 @@ const zDownloadModelStatus = z.object({
already_existed: z.boolean()
})

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<typeof zStatusWsMessageStatus>
export type StatusWsMessage = z.infer<typeof zStatusWsMessage>
export type ProgressWsMessage = z.infer<typeof zProgressWsMessage>
@@ -100,6 +117,7 @@ export type ExecutionInterruptedWsMessage = z.infer<
export type ExecutionErrorWsMessage = z.infer<typeof zExecutionErrorWsMessage>

export type DownloadModelStatus = z.infer<typeof zDownloadModelStatus>
export type LogsWsMessage = z.infer<typeof zLogsWsMessage>
// End of ws messages

const zPromptInputItem = z.object({
@@ -524,3 +542,6 @@ export type SystemStats = z.infer<typeof zSystemStats>
export type User = z.infer<typeof zUser>
export type UserData = z.infer<typeof zUserData>
export type UserDataFullInfo = z.infer<typeof zUserDataFullInfo>
export type TerminalSize = z.infer<typeof zTerminalSize>
export type LogEntry = z.infer<typeof zLogEntry>
export type LogsRawResponse = z.infer<typeof zLogRawResponse>