mirror of
https://github.com/agent0ai/agent-zero.git
synced 2026-08-03 13:13:35 +00:00
Introduce the x-icon custom element and migrate first-party WebUI and bundled plugin markup while retaining legacy Material icon compatibility. Defer icon-font readiness until the splash-installed document is parsed so Firefox does not leave icons transparent. Centralize chat message collapse handling, avoid false Show More controls on short user messages, and align grouped-message pagination styling. Update DOX and add regression coverage for the icon and collapse behavior.
275 lines
13 KiB
HTML
275 lines
13 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;
|
|
}
|
|
#startup-transition[data-theme="light"],
|
|
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; }
|
|
}
|
|
</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>
|
|
<link rel="stylesheet" href="vendor/google/google-icons.css">
|
|
<script type="module" src="/js/icons.js"></script>
|
|
<script>
|
|
(() => {
|
|
const root = document.documentElement
|
|
let applicationReady = false
|
|
let revealStarted = false
|
|
|
|
const revealApplication = (force = false) => {
|
|
if (revealStarted || (!force && !applicationReady)) return
|
|
const overlay = document.getElementById("startup-transition")
|
|
if (!overlay) return
|
|
revealStarted = true
|
|
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/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>
|