mirror of
https://github.com/Anil-matcha/Open-Generative-AI.git
synced 2026-08-30 17:33:11 +08:00
fix(local-ai): show startup progress heartbeat
This commit is contained in:
@@ -9,6 +9,7 @@ const {
|
||||
pickBinaryAssetForPlatform,
|
||||
} = require('./localInferenceAssets');
|
||||
const {
|
||||
formatStartupProgressMessage,
|
||||
parseGenerationProgressChunk,
|
||||
resolveGenerationSteps,
|
||||
resolveGuidanceScale,
|
||||
@@ -481,7 +482,30 @@ async function generate(params, mainWindow) {
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
send({ step: 0, totalSteps: steps, status: 'starting', progress: 0 });
|
||||
const startupStartedAt = Date.now();
|
||||
let startupHeartbeat = null;
|
||||
let samplingStarted = false;
|
||||
|
||||
const sendStartupProgress = () => {
|
||||
send({
|
||||
step: 0,
|
||||
totalSteps: steps,
|
||||
status: 'starting',
|
||||
progress: 0,
|
||||
message: formatStartupProgressMessage(Date.now() - startupStartedAt),
|
||||
});
|
||||
};
|
||||
const stopStartupHeartbeat = () => {
|
||||
if (startupHeartbeat) {
|
||||
clearInterval(startupHeartbeat);
|
||||
startupHeartbeat = null;
|
||||
}
|
||||
};
|
||||
|
||||
sendStartupProgress();
|
||||
startupHeartbeat = setInterval(() => {
|
||||
if (!samplingStarted) sendStartupProgress();
|
||||
}, 5000);
|
||||
|
||||
console.log('[sd-cli] command:', BINARY_PATH, args.join(' '));
|
||||
// DYLD_LIBRARY_PATH lets macOS find libstable-diffusion.dylib next to sd-cli
|
||||
@@ -495,6 +519,8 @@ async function generate(params, mainWindow) {
|
||||
outputLines.push(line.trimEnd());
|
||||
const progressEvents = parseGenerationProgressChunk(line, progressState);
|
||||
for (const event of progressEvents) {
|
||||
samplingStarted = true;
|
||||
stopStartupHeartbeat();
|
||||
send({ ...event, status: 'generating' });
|
||||
}
|
||||
};
|
||||
@@ -503,6 +529,7 @@ async function generate(params, mainWindow) {
|
||||
activeProcess.stderr.on('data', handleOutput);
|
||||
|
||||
activeProcess.on('close', (code) => {
|
||||
stopStartupHeartbeat();
|
||||
activeProcess = null;
|
||||
const allOutput = outputLines.filter(l => l.trim()).join('\n');
|
||||
console.error('[sd-cli] full output:\n' + allOutput);
|
||||
@@ -527,6 +554,7 @@ async function generate(params, mainWindow) {
|
||||
});
|
||||
|
||||
activeProcess.on('error', (err) => {
|
||||
stopStartupHeartbeat();
|
||||
activeProcess = null;
|
||||
reject(err);
|
||||
});
|
||||
|
||||
@@ -28,6 +28,16 @@ function resolveGuidanceScale(params, model) {
|
||||
return 7.5;
|
||||
}
|
||||
|
||||
function formatStartupProgressMessage(elapsedMs) {
|
||||
const seconds = Math.max(0, Math.floor((Number(elapsedMs) || 0) / 1000));
|
||||
if (seconds < 10) return 'Starting local model...';
|
||||
if (seconds < 60) return `Loading local model (${seconds}s)...`;
|
||||
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const remainingSeconds = seconds % 60;
|
||||
return `Loading local model (${minutes}m ${remainingSeconds}s)...`;
|
||||
}
|
||||
|
||||
function stripAnsiSequences(text) {
|
||||
return text.replace(/\u001b\[[0-9;?]*[ -/]*[@-~]/g, '');
|
||||
}
|
||||
@@ -86,6 +96,7 @@ function parseGenerationProgressChunk(chunk, state = { tail: '', lastStep: 0, la
|
||||
module.exports = {
|
||||
coerceFiniteNumber,
|
||||
extractProgressEvents,
|
||||
formatStartupProgressMessage,
|
||||
parseGenerationProgressChunk,
|
||||
resolveGenerationSteps,
|
||||
resolveGuidanceScale,
|
||||
|
||||
@@ -1183,11 +1183,12 @@ export function ImageStudio() {
|
||||
progressWrap.classList.remove('hidden');
|
||||
progressWrap.classList.add('flex');
|
||||
|
||||
const unsub = localAI.onProgress(({ progress, status }) => {
|
||||
const unsub = localAI.onProgress(({ progress, status, message }) => {
|
||||
const pct = Math.round((progress ?? 0) * 100);
|
||||
const label = message || (status === 'starting' ? 'Starting...' : `${pct}%`);
|
||||
if (progressFill) progressFill.style.width = `${pct}%`;
|
||||
if (progressPct) progressPct.textContent = status === 'starting' ? 'Starting...' : `${pct}%`;
|
||||
generateBtn.innerHTML = `<span class="animate-spin inline-block mr-2 text-black">◌</span> ${status === 'starting' ? '...' : pct + '%'}`;
|
||||
if (progressPct) progressPct.textContent = label;
|
||||
generateBtn.innerHTML = `<span class="animate-spin inline-block mr-2 text-black">◌</span> ${label}`;
|
||||
});
|
||||
|
||||
let hadError = false;
|
||||
|
||||
@@ -1133,9 +1133,10 @@ export function VideoStudio() {
|
||||
// For local generations, surface step progress in the button label.
|
||||
let unsubscribeProgress = null;
|
||||
if (isLocal) {
|
||||
unsubscribeProgress = localAI.onProgress(({ status, progress }) => {
|
||||
unsubscribeProgress = localAI.onProgress(({ status, progress, message }) => {
|
||||
const pct = typeof progress === 'number' ? Math.round(progress * 100) : null;
|
||||
generateBtn.innerHTML = `<span class="animate-spin inline-block mr-2 text-black">◌</span> ${status || t('common.generating')}${pct != null ? ` ${pct}%` : '…'}`;
|
||||
const label = message || `${status || t('common.generating')}${pct != null ? ` ${pct}%` : '...'}`;
|
||||
generateBtn.innerHTML = `<span class="animate-spin inline-block mr-2 text-black">◌</span> ${label}`;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
const {
|
||||
formatStartupProgressMessage,
|
||||
parseGenerationProgressChunk,
|
||||
resolveGenerationSteps,
|
||||
resolveGuidanceScale,
|
||||
@@ -23,6 +24,12 @@ test('stripAnsiSequences removes terminal control codes', () => {
|
||||
assert.equal(cleaned, 'hello');
|
||||
});
|
||||
|
||||
test('formatStartupProgressMessage surfaces elapsed startup time', () => {
|
||||
assert.equal(formatStartupProgressMessage(0), 'Starting local model...');
|
||||
assert.equal(formatStartupProgressMessage(12_400), 'Loading local model (12s)...');
|
||||
assert.equal(formatStartupProgressMessage(65_000), 'Loading local model (1m 5s)...');
|
||||
});
|
||||
|
||||
test('parseGenerationProgressChunk extracts sd-cli sampling progress from carriage-return output', () => {
|
||||
const state = { tail: '', lastStep: 0, lastTotalSteps: 0 };
|
||||
const chunk = '\r |==> | 1/20 - 7.16s/it\u001b[K\r |=====> | 2/20 - 6.98s/it\u001b[K';
|
||||
|
||||
Reference in New Issue
Block a user