implement portable static file server and backend process manager for local AI image generation

This commit is contained in:
techjarves committed 2026-06-04 21:18:13 +05:30
1 parent 86f95f5095
commit 4e16a6288a
7 files changed
+177 -146

No files matched your search

+36 -19
View File
@@ -10,7 +10,7 @@ const GalleryItem = memo(({ img, idx, isSelected, onClick }) => {
<div
className={`gallery-item ${isSelected ? "selected" : ""}`}
onClick={handleClick}
title={`Prompt: ${img.prompt}\nSeed: ${img.seed}`}
title={`Prompt: ${img.prompt}\nSeed: ${img.seed}${img.duration_sec ? `\nTime: ${img.duration_sec.toFixed(1)}s` : ""}`}
>
<img src={img.url} className="gallery-thumb" alt="Thumbnail" loading="lazy" decoding="async" />
{isSelected && (
@@ -55,6 +55,7 @@ function Generator({
const [isRestartingBackend, setIsRestartingBackend] = useState(false);
const [restartLoadProgress, setRestartLoadProgress] = useState(null);
const [errorMsg, setErrorMsg] = useState(null);
const [isDecoding, setIsDecoding] = useState(false);
const [selectedGalleryIndexes, setSelectedGalleryIndexes] = useState([]);
const timerRef = useRef(null);
const fileInputRef = useRef(null);
@@ -170,6 +171,7 @@ function Generator({
setCurrentStep(0);
setGenerationSpeed("");
setIsCpuFallback(false);
setIsDecoding(false);
setGenDuration(null);
setOutputImage(null);
@@ -194,16 +196,25 @@ function Generator({
try {
const progress = await getGenerationProgress();
if (progress && progress.active) {
const step = progress.step || 0;
const decoding = progress.decoding || false;
setIsDecoding(decoding);
const step = decoding ? (progress.steps || constraints.steps || 20) : (progress.step || 0);
const steps = progress.steps || constraints.steps || 20;
const speed = progress.speed || "";
const speed = decoding ? "" : (progress.speed || "");
setCurrentStep(step);
setGenerationSpeed(speed);
if (!decoding) {
setGenerationSpeed(speed);
} else {
setGenerationSpeed("decoding");
}
// Calculate progress percentage
let progressPercent = 0;
if (steps > 0) {
if (decoding) {
progressPercent = 99;
} else if (steps > 0) {
progressPercent = Math.round((step / steps) * 100);
}
progressPercent = Math.min(99, progressPercent);
@@ -211,17 +222,21 @@ function Generator({
// Calculate remaining time
let remaining = 0;
const numericSpeed = parseFloat(speed);
if (!isNaN(numericSpeed) && numericSpeed > 0) {
const remainingSteps = Math.max(0, steps - step);
if (speed.includes("it/s")) {
remaining = remainingSteps / numericSpeed;
} else if (speed.includes("s/it")) {
remaining = remainingSteps * numericSpeed;
}
if (decoding) {
remaining = 3;
} else {
const activeStepTime = gpuSelected ? baseGpuStepTime : baseCpuStepTime;
remaining = Math.max(0, (steps - step) * activeStepTime);
const numericSpeed = parseFloat(speed);
if (!isNaN(numericSpeed) && numericSpeed > 0) {
const remainingSteps = Math.max(0, steps - step);
if (speed.includes("it/s")) {
remaining = remainingSteps / numericSpeed;
} else if (speed.includes("s/it")) {
remaining = remainingSteps * numericSpeed;
}
} else {
const activeStepTime = gpuSelected ? baseGpuStepTime : baseCpuStepTime;
remaining = Math.max(0, (steps - step) * activeStepTime);
}
}
// Detect CPU fallback / slow generation if GPU was requested but it runs slow
@@ -482,7 +497,7 @@ function Generator({
}));
setOutputImage(item.url);
setOutputSeed(item.seed);
setGenDuration(null);
setGenDuration(item.duration_sec ?? item.durationSec ?? null);
}, [setPrompt, setNegativePrompt, setConstraints]);
const toggleGallerySelection = useCallback((idx, event) => {
@@ -733,7 +748,9 @@ function Generator({
) : (
<>
<div className="progress-spinner"></div>
<div className="progress-text" style={{ fontSize: "1.1rem", fontWeight: 700 }}>Generating Locally...</div>
<div className="progress-text" style={{ fontSize: "1.1rem", fontWeight: 700 }}>
{isDecoding ? "Decoding Latents..." : "Generating Locally..."}
</div>
{isCpuFallback && (
<div style={{ fontSize: "0.8rem", color: "var(--md-sys-color-primary)", fontWeight: 600, background: "var(--md-sys-color-primary-container)", padding: "4px 12px", borderRadius: "12px", margin: "4px 0 8px 0", animation: "pulse 2s infinite" }}>
@@ -744,10 +761,10 @@ function Generator({
{/* Real-time stats display */}
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "16px", width: "80%", margin: "8px 0 0 0", fontSize: "0.85rem", opacity: 0.9 }}>
<div style={{ textAlign: "left" }}>
<strong>Step:</strong> {currentStep} / {constraints.steps} ({constraints.steps - currentStep} left)
<strong>Step:</strong> {currentStep} / {constraints.steps} ({Math.max(0, constraints.steps - currentStep)} left)
</div>
<div style={{ textAlign: "right" }}>
<strong>Elapsed:</strong> {elapsedTime}s{generationSpeed && ` (${generationSpeed})`}
<strong>Elapsed:</strong> {elapsedTime}s{generationSpeed && ` (${generationSpeed === "decoding" ? "decoding" : generationSpeed})`}
</div>
</div>
+5 -2
View File
@@ -318,6 +318,7 @@ export async function listModelsFromDisk() {
// Handles API calls to sd-server or mocks them if server is unreachable
export async function generateImage(prompt, negativePrompt, constraints, activeModelName, inputImageBase64, onProgress, signal) {
console.log("Initiating image generation:", { prompt, negativePrompt, constraints, activeModelName });
const startTime = Date.now();
const port = 8080;
const baseUrl = `http://127.0.0.1:${port}`;
@@ -495,11 +496,12 @@ export async function generateImage(prompt, negativePrompt, constraints, activeM
const base64Svg = btoa(unescape(encodeURIComponent(svgString)));
const mockImageUrl = `data:image/svg+xml;base64,${base64Svg}`;
const durationSec = parseFloat(((Date.now() - startTime) / 1000).toFixed(1));
return {
image: mockImageUrl,
seed: payload.seed,
duration_sec: 3.0,
duration_sec: durationSec,
};
};
@@ -540,10 +542,11 @@ export async function generateImage(prompt, negativePrompt, constraints, activeM
// Response: { data: [{ b64_json: "..." }] }
const imgB64 = data?.data?.[0]?.b64_json ?? data?.images?.[0];
if (imgB64) {
const durationSec = parseFloat(((Date.now() - startTime) / 1000).toFixed(1));
return {
image: `data:image/png;base64,${imgB64}`,
seed: data.data?.[0]?.seed ?? payload.seed,
duration_sec: 4.5,
duration_sec: durationSec,
};
}
} else {