mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 06:20:11 +00:00
feat: auto-redirect to /connect when backend unreachable, add comfy-cli guide
When deployed to static hosting (Cloudflare Pages), the frontend now detects that no backend is available and redirects to /connect instead of hanging on "Loading ComfyUI". The connection panel includes comfy-cli quick start guide, connection tester, and "Connect & Go" button. API requests are routed to the user-configured remote backend URL. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3764,9 +3764,15 @@
|
||||
"ws": "WS",
|
||||
"status": "Connection Status",
|
||||
"connected": "Connected — backend is reachable.",
|
||||
"guide": "How to Run ComfyUI",
|
||||
"guideDescription": "Start ComfyUI with CORS enabled so this preview can connect from a different origin:",
|
||||
"corsNote": "Replace \"*\" with this page's URL for tighter security.",
|
||||
"connectAndGo": "Connect & Open ComfyUI",
|
||||
"quickStart": "Quick Start with Comfy CLI",
|
||||
"quickStartDescription": "The fastest way to get ComfyUI running locally:",
|
||||
"step1Install": "1. Install comfy-cli:",
|
||||
"step2Install": "2. Install ComfyUI:",
|
||||
"step3Launch": "3. Launch with CORS enabled:",
|
||||
"altManualSetup": "Alternative: Manual Python Setup",
|
||||
"guideDescription": "If you already have ComfyUI cloned, start it with CORS enabled:",
|
||||
"corsNote": "The --enable-cors-header flag allows this preview page to communicate with your local backend.",
|
||||
"localAccess": "Local Network Access",
|
||||
"localAccessDescription": "Your browser may prompt for permission to access local network devices. Allow it so this page can reach your local ComfyUI instance.",
|
||||
"source": "Source",
|
||||
|
||||
@@ -66,6 +66,24 @@ const router = createRouter({
|
||||
name: 'GraphView',
|
||||
component: () => import('@/views/GraphView.vue'),
|
||||
beforeEnter: async (_to, _from, next) => {
|
||||
// Check if backend is reachable before loading the graph view
|
||||
const backendUrl =
|
||||
localStorage.getItem('comfyui-preview-backend-url') || ''
|
||||
const apiBase = backendUrl ? backendUrl.replace(/\/+$/, '') : ''
|
||||
try {
|
||||
const controller = new AbortController()
|
||||
const timeout = setTimeout(() => controller.abort(), 3000)
|
||||
const res = await fetch(`${apiBase}/api/system_stats`, {
|
||||
signal: controller.signal
|
||||
})
|
||||
clearTimeout(timeout)
|
||||
if (!res.ok || !(await res.json()).system) {
|
||||
return next('/connect')
|
||||
}
|
||||
} catch {
|
||||
return next('/connect')
|
||||
}
|
||||
|
||||
// Then check user store
|
||||
const userStore = useUserStore()
|
||||
await userStore.initialize()
|
||||
|
||||
@@ -370,10 +370,19 @@ export class ComfyApi extends EventTarget {
|
||||
constructor() {
|
||||
super()
|
||||
this.user = ''
|
||||
this.api_host = location.host
|
||||
this.api_base = isCloud
|
||||
? ''
|
||||
: location.pathname.split('/').slice(0, -1).join('/')
|
||||
|
||||
const remoteBackend = localStorage.getItem('comfyui-preview-backend-url')
|
||||
if (remoteBackend) {
|
||||
const url = new URL(remoteBackend)
|
||||
this.api_host = url.host
|
||||
this.api_base = url.origin + url.pathname.replace(/\/+$/, '')
|
||||
} else {
|
||||
this.api_host = location.host
|
||||
this.api_base = isCloud
|
||||
? ''
|
||||
: location.pathname.split('/').slice(0, -1).join('/')
|
||||
}
|
||||
|
||||
this.initialClientId = sessionStorage.getItem('clientId')
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,12 @@ vi.mock('@/platform/distribution/types', () => ({
|
||||
isDesktop: false
|
||||
}))
|
||||
|
||||
vi.mock('vue-router', () => ({
|
||||
useRouter: () => ({
|
||||
push: vi.fn()
|
||||
})
|
||||
}))
|
||||
|
||||
const mockLocalStorage = vi.hoisted(() => {
|
||||
const store: Record<string, string> = {}
|
||||
return {
|
||||
@@ -83,9 +89,14 @@ describe('ConnectionPanelView', () => {
|
||||
expect(buttons.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it('displays the command-line guide', () => {
|
||||
it('displays the comfy-cli install command', () => {
|
||||
const wrapper = mountPanel()
|
||||
expect(wrapper.text()).toContain('python main.py --enable-cors-header="*"')
|
||||
expect(wrapper.text()).toContain('pip install comfy-cli')
|
||||
})
|
||||
|
||||
it('displays the comfy launch command', () => {
|
||||
const wrapper = mountPanel()
|
||||
expect(wrapper.text()).toContain('comfy launch -- --enable-cors-header="*"')
|
||||
})
|
||||
|
||||
it('shows build info in footer', () => {
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<!-- Backend URL input -->
|
||||
<section class="flex flex-col gap-2">
|
||||
<label for="backend-url" class="text-sm font-medium text-neutral-300">
|
||||
{{ t('connectionPanel.backendUrl') }}
|
||||
@@ -37,6 +38,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Connection status -->
|
||||
<section
|
||||
v-if="httpStatus !== null || wsStatus !== null"
|
||||
class="flex flex-col gap-2 rounded-md bg-neutral-800/50 p-3"
|
||||
@@ -85,25 +87,88 @@
|
||||
>
|
||||
{{ t('connectionPanel.connected') }}
|
||||
</p>
|
||||
|
||||
<!-- Connect & Go button -->
|
||||
<Button
|
||||
v-if="httpStatus === true"
|
||||
variant="primary"
|
||||
size="lg"
|
||||
class="mt-2 w-full"
|
||||
@click="connectAndGo"
|
||||
>
|
||||
{{ t('connectionPanel.connectAndGo') }}
|
||||
</Button>
|
||||
</section>
|
||||
|
||||
<section class="flex flex-col gap-2">
|
||||
<!-- Quick Start with Comfy CLI -->
|
||||
<section class="flex flex-col gap-3">
|
||||
<h2 class="text-sm font-medium text-neutral-300">
|
||||
{{ t('connectionPanel.guide') }}
|
||||
{{ t('connectionPanel.quickStart') }}
|
||||
</h2>
|
||||
<p class="text-xs text-neutral-400">
|
||||
{{ t('connectionPanel.guideDescription') }}
|
||||
{{ t('connectionPanel.quickStartDescription') }}
|
||||
</p>
|
||||
<code
|
||||
class="block rounded-md bg-neutral-800 p-3 text-xs text-neutral-200 select-all"
|
||||
>
|
||||
python main.py --enable-cors-header="*"
|
||||
</code>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-xs font-medium text-neutral-400">
|
||||
{{ t('connectionPanel.step1Install') }}
|
||||
</span>
|
||||
<code
|
||||
class="block rounded-md bg-neutral-800 p-3 text-xs text-neutral-200 select-all"
|
||||
>
|
||||
pip install comfy-cli
|
||||
</code>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-xs font-medium text-neutral-400">
|
||||
{{ t('connectionPanel.step2Install') }}
|
||||
</span>
|
||||
<code
|
||||
class="block rounded-md bg-neutral-800 p-3 text-xs text-neutral-200 select-all"
|
||||
>
|
||||
comfy install
|
||||
</code>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-xs font-medium text-neutral-400">
|
||||
{{ t('connectionPanel.step3Launch') }}
|
||||
</span>
|
||||
<code
|
||||
class="block rounded-md bg-neutral-800 p-3 text-xs text-neutral-200 select-all"
|
||||
>
|
||||
comfy launch -- --enable-cors-header="*"
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-xs text-neutral-500">
|
||||
{{ t('connectionPanel.corsNote') }}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- Alternative: manual python -->
|
||||
<details class="group">
|
||||
<summary
|
||||
class="cursor-pointer text-sm font-medium text-neutral-400 hover:text-neutral-300"
|
||||
>
|
||||
{{ t('connectionPanel.altManualSetup') }}
|
||||
</summary>
|
||||
<div class="mt-2 flex flex-col gap-2">
|
||||
<p class="text-xs text-neutral-400">
|
||||
{{ t('connectionPanel.guideDescription') }}
|
||||
</p>
|
||||
<code
|
||||
class="block rounded-md bg-neutral-800 p-3 text-xs text-neutral-200 select-all"
|
||||
>
|
||||
python main.py --enable-cors-header="*"
|
||||
</code>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<!-- Local network access -->
|
||||
<section class="flex flex-col gap-2">
|
||||
<h2 class="text-sm font-medium text-neutral-300">
|
||||
{{ t('connectionPanel.localAccess') }}
|
||||
@@ -138,7 +203,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
import BaseViewTemplate from '@/views/templates/BaseViewTemplate.vue'
|
||||
@@ -230,6 +294,13 @@ async function testConnection() {
|
||||
}
|
||||
}
|
||||
|
||||
function connectAndGo() {
|
||||
const base = normalizeUrl(backendUrl.value)
|
||||
localStorage.setItem(STORAGE_KEY, base)
|
||||
// Full page reload so ComfyApi constructor picks up the new backend URL
|
||||
window.location.href = '/'
|
||||
}
|
||||
|
||||
const version = __COMFYUI_FRONTEND_VERSION__
|
||||
const commit = __COMFYUI_FRONTEND_COMMIT__
|
||||
const branch = __CI_BRANCH__
|
||||
|
||||
Reference in New Issue
Block a user