Stop idle Comfy after 30 minutes and add a Wake GPU poke.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -9,6 +9,8 @@ COMFY_LAUNCH_CMD=
|
||||
# Windows host agent (scripts/comfy-host-agent.mjs) so Coolify can start Comfy remotely
|
||||
COMFY_CONTROL_URL=http://192.168.77.7:8199
|
||||
COMFY_CONTROL_TOKEN=
|
||||
# Idle-stop lives on the Beast host agent only (default 30 min). Set on the desktop, not here.
|
||||
# COMFY_IDLE_MS=1800000
|
||||
|
||||
NUXT_SESSION_SECRET=change-me
|
||||
NUXT_PUBLIC_AUTH_ENABLED=true
|
||||
|
||||
+63
-8
@@ -10,6 +10,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-3 text-sm">
|
||||
<div class="flex items-center gap-2">
|
||||
<span
|
||||
class="hidden items-center gap-2 rounded-full border px-3 py-1 sm:inline-flex"
|
||||
:class="rigBadge.className"
|
||||
@@ -18,6 +19,18 @@
|
||||
<span class="h-2 w-2 rounded-full" :class="rigBadge.dot" />
|
||||
{{ rigBadge.label }}
|
||||
</span>
|
||||
<button
|
||||
v-if="showWakeGpu"
|
||||
type="button"
|
||||
class="rounded-full border px-3 py-1 text-zinc-300 hover:border-amber-300/50 hover:text-white disabled:cursor-wait disabled:opacity-80"
|
||||
:class="gpuWaking ? 'border-amber-300/40 text-amber-100' : 'border-white/10'"
|
||||
:disabled="gpuWaking"
|
||||
:title="gpuWakeError || (gpuWaking ? 'Starting Comfy Desktop on the GPU' : 'Poke / Wake GPU — start Comfy before generating')"
|
||||
@click="pokeGpu"
|
||||
>
|
||||
{{ gpuWaking ? 'Starting GPU…' : 'Wake GPU' }}
|
||||
</button>
|
||||
</div>
|
||||
<span class="hidden md:block text-zinc-400">{{ userName }}</span>
|
||||
<button type="button" class="rounded-full border border-white/10 px-3 py-1 text-zinc-300 hover:border-amber-300/50 hover:text-white" @click="settingsOpen = true">
|
||||
Settings
|
||||
@@ -2358,6 +2371,9 @@ const playerSize = ref({ width: 1344, height: 768 })
|
||||
const toasts = ref<{ id: string; message: string }[]>([])
|
||||
const comfyOk = ref(false)
|
||||
const comfyBusy = ref(false)
|
||||
const comfyAsleep = ref(false)
|
||||
const gpuWaking = ref(false)
|
||||
const gpuWakeError = ref('')
|
||||
const userName = ref('')
|
||||
const authEnabled = ref(false)
|
||||
const authMode = ref('')
|
||||
@@ -2574,13 +2590,24 @@ const liveMaxStep = computed(() => (
|
||||
const rigBusy = computed(() => (
|
||||
comfyBusy.value || imageComfyBusy.value || videoBusy.value || editBusy.value
|
||||
))
|
||||
const showWakeGpu = computed(() => gpuWaking.value || (!comfyOk.value && !imageComfyOk.value))
|
||||
const rigBadge = computed(() => {
|
||||
if (gpuWaking.value) {
|
||||
return {
|
||||
label: 'Starting…',
|
||||
className: 'border-amber-400/40 text-amber-200',
|
||||
dot: 'bg-amber-400 animate-pulse',
|
||||
title: 'Starting Comfy Desktop on the GPU'
|
||||
}
|
||||
}
|
||||
if (!comfyOk.value && !imageComfyOk.value) {
|
||||
return {
|
||||
label: 'Offline',
|
||||
className: 'border-red-400/30 text-red-300',
|
||||
dot: 'bg-red-400',
|
||||
title: 'Desktop GPU is unreachable'
|
||||
title: comfyAsleep.value
|
||||
? 'Desktop GPU is asleep. Click Wake GPU or generate to start Comfy.'
|
||||
: 'Desktop GPU is unreachable'
|
||||
}
|
||||
}
|
||||
if (rigBusy.value) {
|
||||
@@ -2696,7 +2723,7 @@ const recommendHint = computed(() => {
|
||||
const editBlocked = computed(() => {
|
||||
if (!file.value || !prompt.value.trim() || !folderId.value) return true
|
||||
if (editPassQueue.value.some(item => !item.prompt.trim())) return true
|
||||
if (!comfyOk.value && !imageComfyOk.value) return true
|
||||
if (!comfyOk.value && !imageComfyOk.value && !gpuWaking.value) return true
|
||||
return false
|
||||
})
|
||||
const editBlockReason = computed(() => {
|
||||
@@ -2705,7 +2732,7 @@ const editBlockReason = computed(() => {
|
||||
if (!folderId.value) return 'Choose a library folder before editing.'
|
||||
if (editPassQueue.value.some(item => !item.prompt.trim())) return 'Each extra pass needs a prompt.'
|
||||
if (!imageComfyConfigured.value && !comfyOk.value) return 'Image edit is not configured. Set COMFY_HOST.'
|
||||
if (!comfyOk.value && !imageComfyOk.value) return 'Desktop GPU is offline.'
|
||||
if (!comfyOk.value && !imageComfyOk.value && !gpuWaking.value) return 'Desktop GPU is offline. Click Wake GPU first.'
|
||||
return ''
|
||||
})
|
||||
const stillBHint = computed(() => {
|
||||
@@ -2719,7 +2746,7 @@ const editV2Blocked = computed(() => {
|
||||
if (v2Mode.value !== 'generate' && !file.value) return true
|
||||
if (v2Mode.value === 'compose' && !editRefFile.value) return true
|
||||
if (v2Mode.value === 'refine' && !refineMaskDirty.value) return true
|
||||
if (!comfyOk.value && !imageComfyOk.value) return true
|
||||
if (!comfyOk.value && !imageComfyOk.value && !gpuWaking.value) return true
|
||||
return false
|
||||
})
|
||||
const editV2BlockReason = computed(() => {
|
||||
@@ -2729,7 +2756,7 @@ const editV2BlockReason = computed(() => {
|
||||
if (!prompt.value.trim()) return 'Write a prompt first.'
|
||||
if (!folderId.value) return 'Choose a library folder before generating.'
|
||||
if (!imageComfyConfigured.value && !comfyOk.value) return 'Image v2 is not configured. Set COMFY_HOST.'
|
||||
if (!comfyOk.value && !imageComfyOk.value) return 'Desktop GPU is offline.'
|
||||
if (!comfyOk.value && !imageComfyOk.value && !gpuWaking.value) return 'Desktop GPU is offline. Click Wake GPU first.'
|
||||
return ''
|
||||
})
|
||||
const editV2SubmitLabel = computed(() => {
|
||||
@@ -3206,7 +3233,7 @@ function scheduleLibraryRefresh() {
|
||||
|
||||
let healthFailStreak = 0
|
||||
function applyComfyHealth(health: {
|
||||
comfy?: { ok?: boolean; busy?: boolean }
|
||||
comfy?: { ok?: boolean; busy?: boolean; asleep?: boolean }
|
||||
imageComfy?: {
|
||||
ok?: boolean
|
||||
busy?: boolean
|
||||
@@ -3219,7 +3246,11 @@ function applyComfyHealth(health: {
|
||||
const nextComfyOk = Boolean(health?.comfy?.ok)
|
||||
const nextImageOk = Boolean(health?.imageComfy?.ok)
|
||||
const reachable = nextComfyOk || nextImageOk
|
||||
if (reachable) healthFailStreak = 0
|
||||
if (reachable) {
|
||||
healthFailStreak = 0
|
||||
gpuWaking.value = false
|
||||
gpuWakeError.value = ''
|
||||
}
|
||||
else healthFailStreak += 1
|
||||
const keepLast = !reachable && (healthFailStreak < 3 || videoBusy.value || editBusy.value)
|
||||
if (!keepLast) {
|
||||
@@ -3227,6 +3258,7 @@ function applyComfyHealth(health: {
|
||||
imageComfyOk.value = nextImageOk
|
||||
imageBeastOk.value = Boolean(health?.imageComfy?.beast?.ok)
|
||||
imageSidecarOk.value = Boolean(health?.imageComfy?.sidecar?.ok)
|
||||
comfyAsleep.value = Boolean(health?.comfy?.asleep) && !reachable
|
||||
}
|
||||
comfyBusy.value = Boolean((keepLast ? comfyOk.value : nextComfyOk) && health?.comfy?.busy)
|
||||
imageComfyConfigured.value = Boolean(health?.imageComfy?.configured)
|
||||
@@ -3246,10 +3278,33 @@ async function pollLoras() {
|
||||
}
|
||||
|
||||
async function pollComfyHealth() {
|
||||
const health = await $fetch<{ comfy?: { ok?: boolean; busy?: boolean } }>('/api/health').catch(() => ({ comfy: { ok: false, busy: false } }))
|
||||
const health = await $fetch<{ comfy?: { ok?: boolean; busy?: boolean; asleep?: boolean } }>('/api/health').catch(() => ({ comfy: { ok: false, busy: false } }))
|
||||
applyComfyHealth(health)
|
||||
}
|
||||
|
||||
async function pokeGpu() {
|
||||
if (gpuWaking.value) return
|
||||
gpuWaking.value = true
|
||||
gpuWakeError.value = ''
|
||||
try {
|
||||
await $fetch('/api/comfy/wake', { method: 'POST' })
|
||||
const deadline = Date.now() + 180_000
|
||||
while (Date.now() < deadline) {
|
||||
await pollComfyHealth()
|
||||
if (comfyOk.value || imageComfyOk.value) {
|
||||
gpuWaking.value = false
|
||||
return
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 3000))
|
||||
}
|
||||
gpuWakeError.value = 'Started Comfy but it is not answering yet. If the instance picker is showing, open ComfyUI (1).'
|
||||
} catch (error: any) {
|
||||
gpuWakeError.value = error?.data?.statusMessage || error?.statusMessage || error?.message || 'Could not wake the GPU.'
|
||||
} finally {
|
||||
gpuWaking.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
withSound.value = localStorage.getItem('aigen-generate-sound') !== 'false'
|
||||
|
||||
@@ -10,6 +10,16 @@ const port = Number(process.env.COMFY_CONTROL_PORT || 8199)
|
||||
const proxyPort = Number(process.env.COMFY_PROXY_PORT || 8198)
|
||||
const token = process.env.COMFY_CONTROL_TOKEN || ''
|
||||
const defaultHttp = (process.env.COMFY_HOST || 'http://127.0.0.1:8188').replace(/\/$/, '')
|
||||
const idleMs = Math.max(60_000, Number(process.env.COMFY_IDLE_MS || 30 * 60 * 1000) || 30 * 60 * 1000)
|
||||
|
||||
let lastWorkAt = Date.now()
|
||||
let lastQueueRunning = 0
|
||||
let stoppedByAgent = false
|
||||
|
||||
function markWork() {
|
||||
lastWorkAt = Date.now()
|
||||
stoppedByAgent = false
|
||||
}
|
||||
|
||||
function json(res, status, body) {
|
||||
const payload = JSON.stringify(body)
|
||||
@@ -110,7 +120,11 @@ function ensureProxy(targetPort) {
|
||||
}
|
||||
client.on('error', fail)
|
||||
upstream.on('error', fail)
|
||||
client.once('data', (chunk) => {
|
||||
if (isWorkHttp(chunk)) markWork()
|
||||
upstream.write(chunk)
|
||||
client.pipe(upstream)
|
||||
})
|
||||
upstream.pipe(client)
|
||||
})
|
||||
proxyServer.on('error', (error) => {
|
||||
@@ -151,6 +165,7 @@ function launchCommand() {
|
||||
}
|
||||
|
||||
function startComfy() {
|
||||
markWork()
|
||||
const command = launchCommand()
|
||||
const cwd = process.env.COMFY_LAUNCH_CWD || undefined
|
||||
const child = spawn(command, { shell: true, detached: true, stdio: 'ignore', windowsHide: true, cwd })
|
||||
@@ -158,6 +173,96 @@ function startComfy() {
|
||||
return { started: true, command, cwd: cwd || null }
|
||||
}
|
||||
|
||||
async function fetchLocalQueue(portNum) {
|
||||
if (!portNum) return { running: 0, pending: 0, ok: false }
|
||||
try {
|
||||
const res = await fetch(`http://127.0.0.1:${portNum}/queue`, { signal: AbortSignal.timeout(2500) })
|
||||
if (!res.ok) return { running: 0, pending: 0, ok: false }
|
||||
const payload = await res.json()
|
||||
return {
|
||||
running: Array.isArray(payload?.queue_running) ? payload.queue_running.length : 0,
|
||||
pending: Array.isArray(payload?.queue_pending) ? payload.queue_pending.length : 0,
|
||||
ok: true
|
||||
}
|
||||
} catch {
|
||||
return { running: 0, pending: 0, ok: false }
|
||||
}
|
||||
}
|
||||
|
||||
function isWorkHttp(chunk) {
|
||||
const head = chunk.toString('latin1', 0, Math.min(chunk.length, 256))
|
||||
const line = head.split('\r\n')[0] || ''
|
||||
const match = line.match(/^(GET|HEAD|POST|PUT|PATCH|DELETE)\s+(\S+)/i)
|
||||
if (!match) return false
|
||||
const method = match[1].toUpperCase()
|
||||
let path = String(match[2] || '').split('?')[0] || ''
|
||||
try { path = decodeURIComponent(path) } catch { /* keep raw */ }
|
||||
if (method === 'GET' || method === 'HEAD') return false
|
||||
if (/^\/(system_stats|queue|object_info|internal)\b/i.test(path)) return false
|
||||
return true
|
||||
}
|
||||
|
||||
async function taskkillImage(image) {
|
||||
try {
|
||||
await execFileAsync('taskkill', ['/IM', image, '/F', '/T'], { windowsHide: true, timeout: 8000 })
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async function stopComfyProcesses() {
|
||||
const killed = []
|
||||
if (await taskkillImage('Comfy Desktop.exe')) killed.push('Comfy Desktop.exe')
|
||||
if (await taskkillImage('ComfyUI.exe')) killed.push('ComfyUI.exe')
|
||||
try {
|
||||
const { stdout } = await execFileAsync('powershell.exe', [
|
||||
'-NoProfile', '-Command',
|
||||
"Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -match 'ComfyUI\\\\main.py|ComfyUI/main.py' } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue; $_.ProcessId }"
|
||||
], { windowsHide: true, timeout: 8000 })
|
||||
if (String(stdout).trim()) killed.push('main.py')
|
||||
} catch { /* ignore */ }
|
||||
return killed
|
||||
}
|
||||
|
||||
async function noteQueue(portNum) {
|
||||
const queue = await fetchLocalQueue(portNum)
|
||||
if (!queue.ok) return queue
|
||||
if (queue.running > 0 || queue.pending > 0) {
|
||||
markWork()
|
||||
} else if (lastQueueRunning > 0 && queue.running === 0) {
|
||||
lastWorkAt = Date.now()
|
||||
}
|
||||
lastQueueRunning = queue.running
|
||||
return queue
|
||||
}
|
||||
|
||||
async function maybeIdleStop(healthyPort) {
|
||||
if (healthyPort) {
|
||||
stoppedByAgent = false
|
||||
const queue = await noteQueue(healthyPort)
|
||||
if (!queue.ok) return
|
||||
if (queue.running > 0 || queue.pending > 0) return
|
||||
} else if (await processUp()) {
|
||||
stoppedByAgent = false
|
||||
} else {
|
||||
stoppedByAgent = true
|
||||
return
|
||||
}
|
||||
if (Date.now() - lastWorkAt < idleMs) return
|
||||
console.log(JSON.stringify({
|
||||
src: 'comfy-host-agent',
|
||||
event: 'idle-stop',
|
||||
idleMs,
|
||||
lastActivityAt: new Date(lastWorkAt).toISOString(),
|
||||
http: Boolean(healthyPort)
|
||||
}))
|
||||
const killed = await stopComfyProcesses()
|
||||
stoppedByAgent = true
|
||||
lastQueueRunning = 0
|
||||
console.log(JSON.stringify({ src: 'comfy-host-agent', event: 'stopped', reason: 'idle', killed }))
|
||||
}
|
||||
|
||||
function sharedRoots() {
|
||||
const local = process.env.LOCALAPPDATA || ''
|
||||
return {
|
||||
@@ -274,10 +379,16 @@ const server = http.createServer(async (req, res) => {
|
||||
const url = new URL(req.url || '/', 'http://localhost')
|
||||
if (req.method === 'GET' && url.pathname === '/status') {
|
||||
const [healthyPort, process] = await Promise.all([syncProxy(), processUp()])
|
||||
if (healthyPort) await noteQueue(healthyPort)
|
||||
const processUpNow = Boolean(process || healthyPort)
|
||||
return json(res, 200, {
|
||||
ok: true,
|
||||
http: Boolean(healthyPort),
|
||||
process,
|
||||
process: processUpNow,
|
||||
processUp: processUpNow,
|
||||
asleep: Boolean(stoppedByAgent || (!healthyPort && !process)),
|
||||
lastActivityAt: new Date(lastWorkAt).toISOString(),
|
||||
idleMs,
|
||||
port: healthyPort || null,
|
||||
proxyPort: healthyPort ? proxyPort : null
|
||||
})
|
||||
@@ -285,10 +396,28 @@ const server = http.createServer(async (req, res) => {
|
||||
if (req.method === 'POST' && url.pathname === '/start') {
|
||||
const healthyPort = await syncProxy()
|
||||
if (healthyPort) {
|
||||
return json(res, 200, { ok: true, started: false, already: true, port: healthyPort, proxyPort })
|
||||
markWork()
|
||||
return json(res, 200, { ok: true, started: false, already: true, asleep: false, port: healthyPort, proxyPort })
|
||||
}
|
||||
const launched = startComfy()
|
||||
return json(res, 200, { ok: true, ...launched })
|
||||
return json(res, 200, { ok: true, asleep: false, ...launched })
|
||||
}
|
||||
if (req.method === 'POST' && url.pathname === '/stop') {
|
||||
const healthyPort = await syncProxy()
|
||||
const queue = healthyPort ? await fetchLocalQueue(healthyPort) : { running: 0, pending: 0, ok: false }
|
||||
if (queue.ok && (queue.running > 0 || queue.pending > 0)) {
|
||||
return json(res, 409, {
|
||||
ok: false,
|
||||
error: 'queue-busy',
|
||||
running: queue.running,
|
||||
pending: queue.pending
|
||||
})
|
||||
}
|
||||
const killed = await stopComfyProcesses()
|
||||
stoppedByAgent = true
|
||||
lastQueueRunning = 0
|
||||
console.log(JSON.stringify({ src: 'comfy-host-agent', event: 'stopped', reason: 'stop', killed }))
|
||||
return json(res, 200, { ok: true, stopped: true, asleep: true, killed })
|
||||
}
|
||||
if (req.method === 'POST' && url.pathname === '/purge') {
|
||||
const body = await readJson(req)
|
||||
@@ -299,15 +428,23 @@ const server = http.createServer(async (req, res) => {
|
||||
|
||||
server.listen(port, '0.0.0.0', async () => {
|
||||
const healthyPort = await syncProxy()
|
||||
if (healthyPort) await noteQueue(healthyPort)
|
||||
console.log(JSON.stringify({
|
||||
src: 'comfy-host-agent',
|
||||
event: 'listen',
|
||||
port,
|
||||
proxyPort,
|
||||
comfyPort: healthyPort || null,
|
||||
idleMs,
|
||||
candidates: candidatePorts()
|
||||
}))
|
||||
let lastIdleCheck = 0
|
||||
setInterval(() => {
|
||||
syncProxy().catch(() => {})
|
||||
syncProxy().then(async (nextPort) => {
|
||||
const now = Date.now()
|
||||
if (now - lastIdleCheck < 10_000) return
|
||||
lastIdleCheck = now
|
||||
await maybeIdleStop(nextPort)
|
||||
}).catch(() => {})
|
||||
}, 3000)
|
||||
})
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
' Launch the Comfy host agent with no console window.
|
||||
' Window style 0 = hidden. Wait = True so Task Scheduler keeps the task marked Running.
|
||||
Option Explicit
|
||||
Dim sh, root, node, agent
|
||||
Set sh = CreateObject("WScript.Shell")
|
||||
root = CreateObject("Scripting.FileSystemObject").GetParentFolderName(WScript.ScriptFullName)
|
||||
root = CreateObject("Scripting.FileSystemObject").GetParentFolderName(root)
|
||||
node = """C:\Program Files\nodejs\node.exe"""
|
||||
agent = """" & root & "\scripts\comfy-host-agent.mjs"""
|
||||
sh.CurrentDirectory = root
|
||||
sh.Run node & " " & agent, 0, True
|
||||
@@ -1,3 +1,4 @@
|
||||
@echo off
|
||||
cd /d "%~dp0.."
|
||||
node "%~dp0comfy-host-agent.mjs"
|
||||
REM Starts the host agent hidden. Do not leave a Command Prompt open for this.
|
||||
REM Agent stays running after idle-stop (default 30 min, override with COMFY_IDLE_MS).
|
||||
start "" wscript.exe //B "%~dp0start-comfy-host-agent-hidden.vbs"
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import { requestComfyWake } from '~/server/utils/comfyLifecycle'
|
||||
|
||||
export default defineEventHandler(async () => {
|
||||
return await requestComfyWake()
|
||||
})
|
||||
@@ -1,4 +1,4 @@
|
||||
import { checkComfyHttp, fetchQueue } from '~/server/utils/comfyLifecycle'
|
||||
import { checkComfyHttp, fetchQueue, fetchComfyControlStatus } from '~/server/utils/comfyLifecycle'
|
||||
import { comfyConfigured } from '~/server/utils/comfy'
|
||||
import { imageComfyConfigured, getBeastImageHost, getSidecarImageHost, sameImageHost } from '~/server/utils/imageComfy'
|
||||
import { checkImageComfyHttp, fetchQueueAt } from '~/server/utils/imageComfyLifecycle'
|
||||
@@ -9,7 +9,7 @@ export default defineEventHandler(async () => {
|
||||
const beastHost = getBeastImageHost()
|
||||
const promptHost = getSidecarImageHost()
|
||||
const promptIsBeast = Boolean(promptHost && beastHost && sameImageHost(promptHost, beastHost))
|
||||
const [comfy, queue, beast, beastQueue, prompt, promptQueue] = await Promise.all([
|
||||
const [comfy, queue, beast, beastQueue, prompt, promptQueue, control] = await Promise.all([
|
||||
videoConfigured
|
||||
? checkComfyHttp().catch(() => ({ ok: false, host: '', stats: null }))
|
||||
: Promise.resolve({ ok: false, host: '', stats: null }),
|
||||
@@ -21,7 +21,8 @@ export default defineEventHandler(async () => {
|
||||
promptHost && !promptIsBeast
|
||||
? checkImageComfyHttp(5000, promptHost).catch(() => ({ ok: false, host: promptHost, stats: null }))
|
||||
: Promise.resolve({ ok: false, host: promptHost, stats: null }),
|
||||
promptHost && !promptIsBeast ? fetchQueueAt(promptHost) : Promise.resolve({ running: 0, pending: 0, ok: false })
|
||||
promptHost && !promptIsBeast ? fetchQueueAt(promptHost) : Promise.resolve({ running: 0, pending: 0, ok: false }),
|
||||
fetchComfyControlStatus().catch(() => null)
|
||||
])
|
||||
const comfyReachable = Boolean(comfy.ok || queue.ok)
|
||||
const beastReachable = Boolean(beast.ok || beastQueue.ok || comfyReachable)
|
||||
@@ -43,6 +44,9 @@ export default defineEventHandler(async () => {
|
||||
host: comfy.host || '',
|
||||
queueRunning: queue.running,
|
||||
queuePending: queue.pending,
|
||||
asleep: Boolean(control?.asleep) && !comfyReachable,
|
||||
processUp: Boolean(control?.process ?? control?.processUp),
|
||||
lastActivityAt: control?.lastActivityAt || null,
|
||||
stats: comfy.ok ? comfy.stats : undefined
|
||||
},
|
||||
imageComfy: {
|
||||
|
||||
@@ -98,17 +98,59 @@ async function controlRequest(path: string, method = 'GET') {
|
||||
ok?: boolean
|
||||
http?: boolean
|
||||
process?: boolean
|
||||
processUp?: boolean
|
||||
asleep?: boolean
|
||||
lastActivityAt?: string | null
|
||||
idleMs?: number
|
||||
pid?: number
|
||||
started?: boolean
|
||||
already?: boolean
|
||||
port?: number | null
|
||||
proxyPort?: number | null
|
||||
stopped?: boolean
|
||||
error?: string
|
||||
status?: number
|
||||
}
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchComfyControlStatus() {
|
||||
return await controlRequest('/status')
|
||||
}
|
||||
|
||||
export async function requestComfyWake() {
|
||||
const { controlUrl } = settings()
|
||||
if (!controlUrl) {
|
||||
throw createError({
|
||||
statusCode: 503,
|
||||
statusMessage: 'COMFY_CONTROL_URL is not set. The studio cannot poke the Beast host agent.'
|
||||
})
|
||||
}
|
||||
const remote = await controlRequest('/start', 'POST')
|
||||
if (!remote) {
|
||||
throw createError({
|
||||
statusCode: 503,
|
||||
statusMessage: 'Beast host agent is unreachable. Confirm it is running on the desktop.'
|
||||
})
|
||||
}
|
||||
if (remote.ok === false) {
|
||||
throw createError({
|
||||
statusCode: Number(remote.status) || 502,
|
||||
statusMessage: 'Beast host agent refused to start Comfy.'
|
||||
})
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
started: Boolean(remote.started),
|
||||
already: Boolean(remote.already),
|
||||
asleep: false,
|
||||
port: remote.port ?? null,
|
||||
proxyPort: remote.proxyPort ?? null
|
||||
}
|
||||
}
|
||||
|
||||
async function findLocalComfyProcess() {
|
||||
const platform = process.platform
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user