mirror of
https://github.com/agent0ai/agent-zero.git
synced 2026-10-21 13:51:06 +00:00
Inline the icon guard from vendor/google/google-icons.css into the index critical style with absolute font URLs and remove the last render-blocking stylesheet link, so the carried startup overlay and its logo paint immediately after the document write instead of waiting for a stylesheet. Gate the startup reveal on the main stylesheet with a bounded wait so the app never fades to unstyled white while the non-render-blocking main stylesheet is still loading; the overlay itself carries the startup background, so no html-level paint rules are needed. Validation: startup asset, icons, offline asset, message queue static, and secrets suites pass on the merged state; the two remaining startup failures pre-exist on clean origin/ready.
322 lines
15 KiB
HTML
322 lines
15 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
|
<title>Agent Zero</title>
|
|
|
|
<style id="startup-transition-critical">
|
|
#startup-transition {
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 2147483647;
|
|
display: grid;
|
|
place-items: center;
|
|
margin: 0;
|
|
background: #131313;
|
|
color: #fff;
|
|
opacity: 1;
|
|
transition: opacity 240ms ease;
|
|
}
|
|
body.light-mode #startup-transition { background: #fafafa; color: #000; }
|
|
#startup-transition > svg {
|
|
width: clamp(12rem, 34vw, 23rem);
|
|
max-width: 72vw;
|
|
height: auto;
|
|
opacity: 1;
|
|
animation: none;
|
|
}
|
|
#startup-transition > p {
|
|
position: absolute;
|
|
width: 1px;
|
|
height: 1px;
|
|
overflow: hidden;
|
|
clip-path: inset(50%);
|
|
}
|
|
#startup-transition.startup-transition-leaving {
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
}
|
|
@media (prefers-reduced-motion: reduce) {
|
|
#startup-transition { transition: none; }
|
|
}
|
|
/* Icon guard inlined from vendor/google/google-icons.css so no stylesheet blocks first paint */
|
|
@font-face {
|
|
font-family: 'Material Symbols Outlined';
|
|
font-style: normal;
|
|
font-weight: 400;
|
|
font-display: block;
|
|
src: url(/vendor/google/google-icons.woff2) format('woff2'), url(/vendor/google/google-icons.ttf) format('truetype');
|
|
}
|
|
x-icon, .material-symbols-outlined, .material-icons-outlined {
|
|
font-family: 'Material Symbols Outlined';
|
|
font-weight: normal;
|
|
font-style: normal;
|
|
font-size: 24px;
|
|
line-height: 1;
|
|
letter-spacing: normal;
|
|
text-transform: none;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
box-sizing: border-box;
|
|
width: 1em !important;
|
|
min-width: 1em !important;
|
|
max-width: 1em !important;
|
|
height: 1em !important;
|
|
overflow: hidden !important;
|
|
flex: 0 0 1em !important;
|
|
vertical-align: middle;
|
|
white-space: nowrap;
|
|
word-wrap: normal;
|
|
direction: ltr;
|
|
}
|
|
html:not(.material-icons-ready) x-icon,
|
|
html:not(.material-icons-ready) .material-symbols-outlined,
|
|
html:not(.material-icons-ready) .material-icons-outlined { color: transparent !important; }
|
|
</style>
|
|
|
|
<script>
|
|
if (location.pathname === "/safe") {
|
|
const safeUrl = new URL(location.href)
|
|
safeUrl.searchParams.delete("__direct")
|
|
history.replaceState(null, "", `${safeUrl.pathname}${safeUrl.search}${safeUrl.hash}`)
|
|
if ("serviceWorker" in navigator) {
|
|
navigator.serviceWorker.getRegistrations()
|
|
.then((registrations) => Promise.allSettled(
|
|
registrations.map((registration) => registration.unregister()),
|
|
))
|
|
.catch((error) => console.warn("Unable to disable service workers.", error))
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<link rel="icon" type="image/svg+xml" href="public/favicon.svg">
|
|
<link rel="preload" href="/vendor/google/google-icons.woff2" as="font" type="font/woff2" crossorigin>
|
|
<script type="module" src="/js/icons.js"></script>
|
|
<script>
|
|
(() => {
|
|
const root = document.documentElement
|
|
let applicationReady = false
|
|
let revealStarted = false
|
|
|
|
const mainStylesheetReady = () => {
|
|
const link = document.querySelector('link[href="index.css"], link[href="/index.css"]')
|
|
return !link || Boolean(link.sheet)
|
|
}
|
|
|
|
const waitForStylesheets = () => new Promise((resolve) => {
|
|
if (mainStylesheetReady()) return resolve()
|
|
const timer = setTimeout(finish, 2000)
|
|
const poll = setInterval(() => { if (mainStylesheetReady()) finish() }, 50)
|
|
function finish() { clearInterval(poll); clearTimeout(timer); resolve() }
|
|
})
|
|
|
|
const revealApplication = async (force = false) => {
|
|
if (revealStarted || (!force && !applicationReady)) return
|
|
const overlay = document.getElementById("startup-transition")
|
|
if (!overlay) return
|
|
revealStarted = true
|
|
// Non-render-blocking stylesheets may still be pending; never fade to unstyled content
|
|
await waitForStylesheets()
|
|
requestAnimationFrame(() => requestAnimationFrame(() => {
|
|
const removeOverlay = () => overlay.remove()
|
|
overlay.classList.add("startup-transition-leaving")
|
|
overlay.addEventListener("transitionend", removeOverlay, { once: true })
|
|
setTimeout(removeOverlay, 600)
|
|
}))
|
|
}
|
|
|
|
document.addEventListener("webui-extensions-loaded", () => {
|
|
applicationReady = true
|
|
revealApplication()
|
|
}, { once: true })
|
|
|
|
const loadMaterialIcons = () => {
|
|
const fontLoad = document.fonts?.load
|
|
? document.fonts.load('24px "Material Symbols Outlined"')
|
|
: Promise.resolve([])
|
|
fontLoad.then((faces) => {
|
|
if (faces.length > 0) root.classList.add("material-icons-ready")
|
|
}).catch((error) => {
|
|
console.warn("Material Symbols font unavailable.", error)
|
|
})
|
|
}
|
|
|
|
// Firefox can resolve FontFaceSet.load() with no matching faces when
|
|
// it runs while the splash is replacing the document. Wait until
|
|
// the replacement document is parsed and its font set is stable.
|
|
if (document.readyState === "loading") {
|
|
document.addEventListener("DOMContentLoaded", loadMaterialIcons, { once: true })
|
|
} else {
|
|
loadMaterialIcons()
|
|
}
|
|
|
|
setTimeout(() => revealApplication(true), 8000)
|
|
})()
|
|
</script>
|
|
<link rel="preload" as="style" href="/vendor/fonts/fonts.css" onload="this.onload=null;this.rel='stylesheet'">
|
|
<link rel="preload" as="style" href="index.css" onload="this.onload=null;this.rel='stylesheet'">
|
|
<link rel="preload" as="style" href="css/loading-indicators.css" onload="this.onload=null;this.rel='stylesheet'">
|
|
<link rel="preload" as="style" href="css/messages.css" onload="this.onload=null;this.rel='stylesheet'">
|
|
<link rel="preload" as="style" href="components/messages/action-buttons/simple-action-buttons.css" onload="this.onload=null;this.rel='stylesheet'">
|
|
<link rel="preload" as="style" href="components/messages/process-group/process-group.css" onload="this.onload=null;this.rel='stylesheet'">
|
|
<link rel="preload" as="style" href="css/toast.css" onload="this.onload=null;this.rel='stylesheet'">
|
|
<link rel="preload" as="style" href="css/settings.css" onload="this.onload=null;this.rel='stylesheet'">
|
|
<link rel="preload" as="style" href="css/modals.css" onload="this.onload=null;this.rel='stylesheet'">
|
|
<link rel="preload" as="style" href="css/surfaces.css" onload="this.onload=null;this.rel='stylesheet'">
|
|
<link rel="preload" as="style" href="css/scheduler-datepicker.css" onload="this.onload=null;this.rel='stylesheet'">
|
|
<link rel="preload" as="style" href="css/scheduler.css" onload="this.onload=null;this.rel='stylesheet'">
|
|
<link rel="preload" as="style" href="css/notification.css" onload="this.onload=null;this.rel='stylesheet'">
|
|
<link rel="preload" as="style" href="css/buttons.css" onload="this.onload=null;this.rel='stylesheet'">
|
|
<link rel="preload" as="style" href="css/overflow.css" onload="this.onload=null;this.rel='stylesheet'">
|
|
<link rel="preload" as="style" href="css/tables.css" onload="this.onload=null;this.rel='stylesheet'">
|
|
<link rel="preload" as="style" href="components/canvas/right-canvas.css" onload="this.onload=null;this.rel='stylesheet'">
|
|
|
|
<!-- Flatpickr for datetime picker -->
|
|
<link rel="preload" as="style" href="vendor/flatpickr/flatpickr.min.css" onload="this.onload=null;this.rel='stylesheet'">
|
|
<script defer src="vendor/flatpickr/flatpickr.min.js"></script>
|
|
|
|
<script>
|
|
globalThis.safeCall = function (name, ...args) {
|
|
if (window[name]) window[name](...args)
|
|
}
|
|
</script>
|
|
|
|
<script type="module" src="index.js"></script>
|
|
|
|
<!-- Bootstrap JS (only for logic, importing bundled CSS => UI conflicts) -->
|
|
<script defer src="vendor/bootstrap/bootstrap.bundle.min.js"></script>
|
|
|
|
<!-- Then load Alpine.js -->
|
|
<script defer src="vendor/ace-min/ace.js"></script>
|
|
<script defer src="vendor/qrcode.min.js"></script>
|
|
<script type="module" src="js/initFw.js"></script>
|
|
|
|
<link rel="preload" as="style" href="vendor/ace-min/ace.min.css" onload="this.onload=null;this.rel='stylesheet'">
|
|
<!-- KaTeX CSS -->
|
|
<link rel="preload" as="style" href="vendor/katex/katex.min.css" crossorigin="anonymous" onload="this.onload=null;this.rel='stylesheet'">
|
|
|
|
<!-- KaTeX javascript -->
|
|
<script defer src="vendor/katex/katex.min.js" crossorigin="anonymous"></script>
|
|
<script defer src="vendor/katex/katex.auto-render.min.js" crossorigin="anonymous"></script>
|
|
|
|
<!-- Link the PWA manifest file -->
|
|
<link rel="manifest" href="js/manifest.json">
|
|
|
|
<script>
|
|
// Expose git info for sidebar component
|
|
globalThis.gitinfo = { version: "{{version_no}}", commit_time: "{{version_time}}" };
|
|
|
|
// Expose runtime info for frontend components (development gating, runtime-scoped cookies).
|
|
globalThis.runtimeInfo = {
|
|
...(globalThis.runtimeInfo || {}),
|
|
id: "{{runtime_id}}",
|
|
isDevelopment: "{{runtime_is_development}}" === "true",
|
|
loggedIn: "{{logged_in}}" === "true",
|
|
timezone: "{{user_timezone_setting}}",
|
|
timeFormat: "{{user_time_format_setting}}",
|
|
uiControlVisibility: {{user_ui_control_visibility}},
|
|
webuiExtensions: {{webui_extension_manifest}},
|
|
};
|
|
</script>
|
|
<!-- Plugin head injections (scripts, stylesheets) -->
|
|
<x-extension id="page-head"></x-extension>
|
|
</head>
|
|
|
|
<body class="dark-mode device-pointer" x-data>
|
|
<script>
|
|
try {
|
|
if (localStorage.getItem("darkMode") === "false") document.body.classList.replace("dark-mode", "light-mode")
|
|
} catch (_) {}
|
|
</script>
|
|
<div class="container">
|
|
<!-- Sidebar Overlay -->
|
|
<div id="sidebar-overlay" x-data>
|
|
<template x-if="$store.sidebar">
|
|
<div class="sidebar-overlay" :class="{'visible': $store.sidebar.isOpen && $store.sidebar.isMobile()}" @click="$store.sidebar.close()"></div>
|
|
</template>
|
|
</div>
|
|
<!-- Left Sidebar (Header Icons, Quick Actions, Tabs, Chats, Tasks) -->
|
|
<x-component path="sidebar/left-sidebar.html"></x-component>
|
|
|
|
<!-- Right Panel (Message History and Input Section) -->
|
|
<div id="right-panel" class="panel"
|
|
:class="{ 'chat-active': $store.welcomeStore && !$store.welcomeStore.isVisible }">
|
|
|
|
<!-- top section with time, status etc. -->
|
|
<x-component path="chat/top-section/chat-top.html"></x-component>
|
|
|
|
<!-- Welcome Screen Component -->
|
|
<div x-data>
|
|
<template x-if="$store.welcomeStore && $store.welcomeStore.isVisible">
|
|
<div class="welcome-host">
|
|
<x-component path="welcome/welcome-screen.html"></x-component>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
<!-- Message History (actual messages) -->
|
|
<div id="chat-area-wrapper" x-data x-show="!$store.welcomeStore || !$store.welcomeStore.isVisible">
|
|
<div id="chat-history"></div>
|
|
|
|
<div id="chat-loading-splash"
|
|
class="message-window-loader is-loading chat-loading-splash"
|
|
role="status"
|
|
aria-live="polite"
|
|
hidden>
|
|
<span class="loading-indicator-label">Loading chat</span>
|
|
</div>
|
|
|
|
<!-- Chat Navigation Buttons (floating over chat area) -->
|
|
<div id="chat-nav-buttons" aria-label="Chat navigation" x-cloak>
|
|
<button class="btn-icon-action" title="Scroll to top" @click="$store.chatNavigation.scrollToTop()">
|
|
<x-icon name="vertical_align_top"></x-icon>
|
|
</button>
|
|
<button class="btn-icon-action" title="Previous user message" @click="$store.chatNavigation.scrollToPrevUserMessage()">
|
|
<x-icon name="keyboard_arrow_up"></x-icon>
|
|
</button>
|
|
<button class="btn-icon-action" title="Next user message" @click="$store.chatNavigation.scrollToNextUserMessage()">
|
|
<x-icon name="keyboard_arrow_down"></x-icon>
|
|
</button>
|
|
<button class="btn-icon-action" title="Scroll to bottom" @click="$store.chatNavigation.scrollToBottom()">
|
|
<x-icon name="vertical_align_bottom"></x-icon>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- NEW: Toast Stack Component -->
|
|
<div style="position: relative; height: 0;">
|
|
<x-component path="notifications/notification-toast-stack.html"></x-component>
|
|
</div>
|
|
|
|
<div id="toast" class="toast">
|
|
<div class="toast__content">
|
|
<div class="toast__title"></div>
|
|
<div class="toast__separator"></div>
|
|
<div class="toast__message"></div>
|
|
</div>
|
|
<button class="toast__copy" style="display: none;">Copy</button>
|
|
<button class="toast__close">Close</button>
|
|
</div>
|
|
<!-- Chat Input Area -->
|
|
<div x-data>
|
|
<template x-if="!$store.welcomeStore || !$store.welcomeStore.isVisible">
|
|
<div>
|
|
<!-- Progress Bar -->
|
|
<x-component path="chat/input/progress.html"></x-component>
|
|
<!-- Input Section -->
|
|
<x-component path="chat/input/chat-bar.html"></x-component>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
<x-component path="canvas/right-canvas.html"></x-component>
|
|
</div>
|
|
|
|
<!-- Drag and Drop Overlay Component -->
|
|
<x-component path="chat/attachments/dragDropOverlay.html"></x-component>
|
|
|
|
</body>
|
|
|
|
</html>
|