import { error, log, xhrPost } from './logger'; import { authFetch } from './authWrap'; import { timer } from './timers'; export const appStartTime = performance.now(); let monitorLogActive = false; async function preloadImages(): Promise { const dark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; const imagePromises: Promise[] = []; const num = Math.floor(9.99 * Math.random()); const imageUrls = [ `file=ui/assets/logo-bg-${dark ? 'dark' : 'light'}.jpg`, `file=ui/assets/logo-bg-${num}.jpg`, ]; for (const url of imageUrls) { const img = new Image(); const promise = new Promise((resolve, reject) => { img.onload = () => resolve(); img.onerror = () => reject(new Error(`failed to preload image: ${url}`)); }); img.src = url; imagePromises.push(promise); } try { await Promise.all(imagePromises); return true; } catch (err) { error(`preloadImages: ${err}`); return false; } } function joinArgs(messages: unknown[]): string { let output = ''; for (let i = 0; i < messages.length; i++) { let arg = messages[i]; if (arg === undefined) arg = 'undefined'; if (arg === null) arg = 'null'; output += ' '; if (typeof arg === 'object') output += JSON.stringify(arg).replace(/["]+/g, ''); else output += arg; } return output; } function monitorLog(): void { if (window.logBufferDirty) { window.logBufferDirty = false; const maxLines = 100; // print last n logs from ring buffer to splash-log const lines: string[] = []; // print last n logs from ring buffer in time order for (let i = Math.max(0, window.logRingBuffer.length - maxLines); i < window.logRingBuffer.length; i++) { const logEntry = window.logRingBuffer[i]; let color = 'white'; if (logEntry.type === 'error') color = 'palevioletred'; else if (logEntry.type === 'debug') color = 'gray'; const html = `
${logEntry.ts}   ${joinArgs(logEntry.msg)}
`; lines.push(html); } const splashLogEl = document.getElementById('splashLog'); if (splashLogEl) splashLogEl.innerHTML = lines.join(''); } if (monitorLogActive) setTimeout(monitorLog, 250); } export async function removeSplash() { const splash = document.getElementById('splash'); if (splash) splash.remove(); log('removeSplash'); const t = Math.round(performance.now() - appStartTime); log('startupTime', t); timer('splashVisible', t); xhrPost(`${window.api}/log`, { message: `ready time=${t}` }); monitorLogActive = false; } async function createSplash() { const dark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; log('createSplash', { theme: dark ? 'dark' : 'light' }); const num = Math.floor(9.99 * Math.random()); const splash = `
`; document.body.insertAdjacentHTML('beforeend', splash); const ok = await preloadImages(); if (!ok) { removeSplash(); return; } const imgEl = `
`; const splashEl = document.getElementById('splash'); if (splashEl) splashEl.insertAdjacentHTML('afterbegin', imgEl); monitorLogActive = true; monitorLog(); await authFetch(`${window.api}/motd`) .then((res) => res.text()) .then((text) => { const clean = text.replace(/["]+/g, ''); const boldMatch = clean.match(/(.*?)<\/b>/); const boldText = boldMatch ? boldMatch[1] : clean; if (boldMatch) log('getMOTD', { version: boldText }); else log('getMOTD', { text: clean }); const motdEl = document.getElementById('motd'); if (motdEl) motdEl.innerHTML = clean; }) .catch((err) => error(`getMOTD: ${err}`)); log('loadGradioUi'); } window.onload = createSplash;