142 lines
4.1 KiB
TypeScript
142 lines
4.1 KiB
TypeScript
import React from 'react';
|
|
|
|
export default function ComfyUI() {
|
|
const [isDark, setIsDark] = React.useState(true);
|
|
const [available, setAvailable] = React.useState(false);
|
|
const [message, setMessage] = React.useState('正在检查外部 ComfyUI...');
|
|
const iframeRef = React.useRef<HTMLIFrameElement | null>(null);
|
|
|
|
const openFullscreen = () => {
|
|
window.open('/api/comfyui/proxy/', '_blank', 'noopener,noreferrer');
|
|
};
|
|
|
|
const reloadFrame = () => {
|
|
if (iframeRef.current) {
|
|
iframeRef.current.src = '/api/comfyui/proxy/';
|
|
}
|
|
};
|
|
|
|
React.useEffect(() => {
|
|
const el = document.documentElement;
|
|
const check = () => setIsDark(el.getAttribute('data-theme') !== 'light');
|
|
check();
|
|
const mo = new MutationObserver(check);
|
|
mo.observe(el, { attributes: true, attributeFilter: ['data-theme'] });
|
|
return () => mo.disconnect();
|
|
}, []);
|
|
|
|
React.useEffect(() => {
|
|
let cancelled = false;
|
|
const controller = new AbortController();
|
|
void fetch('/api/comfyui/proxy/', { method: 'GET', signal: controller.signal })
|
|
.then((response) => {
|
|
if (cancelled) return;
|
|
setAvailable(response.ok);
|
|
setMessage(response.ok
|
|
? ''
|
|
: '外部 ComfyUI 不可达。请在 config.toml 中配置 comfyui.base_url,或使用工作流。');
|
|
})
|
|
.catch((err) => {
|
|
if (cancelled || err?.name === 'AbortError') return;
|
|
setAvailable(false);
|
|
setMessage('外部 ComfyUI 不可达。请在 config.toml 中配置 comfyui.base_url,或使用工作流。');
|
|
});
|
|
return () => {
|
|
cancelled = true;
|
|
controller.abort();
|
|
};
|
|
}, []);
|
|
|
|
return (
|
|
<div
|
|
style={{
|
|
width: '100%',
|
|
height: 'calc(100vh - 56px)',
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
background: isDark ? '#1a1a2e' : '#f5f5f5',
|
|
}}
|
|
>
|
|
<div style={toolbarStyle}>
|
|
<div style={toolbarTitleStyle}>外部 ComfyUI</div>
|
|
<div style={toolbarActionsStyle}>
|
|
<button type="button" onClick={reloadFrame} disabled={!available} style={toolbarButtonStyle}>
|
|
刷新
|
|
</button>
|
|
<button type="button" onClick={openFullscreen} disabled={!available} style={primaryToolbarButtonStyle}>
|
|
新窗口打开
|
|
</button>
|
|
</div>
|
|
</div>
|
|
{available ? (
|
|
<iframe
|
|
ref={iframeRef}
|
|
src="/api/comfyui/proxy/"
|
|
style={{ width: '100%', height: '100%', border: 'none' }}
|
|
title="External ComfyUI"
|
|
allow="clipboard-read; clipboard-write"
|
|
/>
|
|
) : (
|
|
<div style={{
|
|
height: '100%',
|
|
display: 'grid',
|
|
placeItems: 'center',
|
|
color: 'var(--color-text-muted)',
|
|
fontSize: 14,
|
|
textAlign: 'center',
|
|
padding: 24,
|
|
}}>
|
|
<div>
|
|
<div style={{ fontWeight: 700, color: 'var(--color-text-primary)', marginBottom: 8 }}>
|
|
内置 Docker ComfyUI 工作台已移除
|
|
</div>
|
|
<div style={{ maxWidth: 560, lineHeight: 1.7 }}>{message}</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const toolbarStyle: React.CSSProperties = {
|
|
minHeight: 44,
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
gap: 12,
|
|
padding: '8px 12px',
|
|
borderBottom: '1px solid var(--color-border-light)',
|
|
background: 'var(--color-surface)',
|
|
};
|
|
|
|
const toolbarTitleStyle: React.CSSProperties = {
|
|
fontSize: 14,
|
|
fontWeight: 800,
|
|
color: 'var(--color-text-primary)',
|
|
};
|
|
|
|
const toolbarActionsStyle: React.CSSProperties = {
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: 8,
|
|
};
|
|
|
|
const toolbarButtonStyle: React.CSSProperties = {
|
|
minHeight: 30,
|
|
border: '1px solid var(--color-border)',
|
|
borderRadius: 8,
|
|
padding: '5px 10px',
|
|
background: 'var(--color-bg)',
|
|
color: 'var(--color-text-primary)',
|
|
cursor: 'pointer',
|
|
fontSize: 12,
|
|
};
|
|
|
|
const primaryToolbarButtonStyle: React.CSSProperties = {
|
|
...toolbarButtonStyle,
|
|
borderColor: 'var(--color-primary)',
|
|
background: 'var(--color-primary)',
|
|
color: '#fff',
|
|
fontWeight: 700,
|
|
};
|