Spaces:
Running
Running
| <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> | |
| <link rel="icon" type="image/svg+xml" href="public/favicon.svg"> | |
| <link rel="stylesheet" href="index.css"> | |
| <link rel="stylesheet" href="css/messages.css"> | |
| <link rel="stylesheet" href="components/messages/action-buttons/simple-action-buttons.css"> | |
| <link rel="stylesheet" href="css/toast.css"> | |
| <link rel="stylesheet" href="css/settings.css"> | |
| <link rel="stylesheet" href="css/file_browser.css"> | |
| <link rel="stylesheet" href="css/modals.css"> | |
| <link rel="stylesheet" href="css/modals2.css"> | |
| <link rel="stylesheet" href="css/speech.css"> | |
| <link rel="stylesheet" href="css/history.css"> | |
| <link rel="stylesheet" href="css/scheduler-datepicker.css"> | |
| <link rel="stylesheet" href="css/tunnel.css"> | |
| <link rel="stylesheet" href="css/notification.css"> | |
| <!-- Flatpickr for datetime picker --> | |
| <link rel="stylesheet" href="vendor/flatpickr/flatpickr.min.css"> | |
| <script src="vendor/flatpickr/flatpickr.min.js"></script> | |
| <style> | |
| .chat-button.loading .loader { | |
| display: block; | |
| } | |
| .chat-button .loader { | |
| display: none; | |
| border: 2px solid #f3f3f3; | |
| border-top: 2px solid #3498db; | |
| border-radius: 50%; | |
| width: 18px; | |
| height: 18px; | |
| animation: spin 1s linear infinite; | |
| margin: auto; | |
| } | |
| @keyframes spin { | |
| 0% { transform: rotate(0deg); } | |
| 100% { transform: rotate(360deg); } | |
| } | |
| .chat-button.loading svg { | |
| display: none; | |
| } | |
| </style> | |
| <script> | |
| globalThis.safeCall = function (name, ...args) { | |
| if (window[name]) window[name](...args) | |
| } | |
| </script> | |
| <!-- Pre-initialize schedulerSettings to ensure Alpine doesn't miss it --> | |
| <script> | |
| // Pre-define schedulerSettings skeleton to ensure it's available to Alpine | |
| globalThis.schedulerSettings = function() { | |
| return { | |
| tasks: [], | |
| isLoading: true, | |
| selectedTask: null, | |
| expandedTaskId: null, | |
| sortField: 'name', | |
| sortDirection: 'asc', | |
| filterType: 'all', | |
| filterState: 'all', | |
| pollingInterval: null, | |
| pollingActive: false, | |
| editingTask: { | |
| name: '', | |
| type: 'scheduled', | |
| state: 'idle', | |
| schedule: { | |
| minute: '*', | |
| hour: '*', | |
| day: '*', | |
| month: '*', | |
| weekday: '*', | |
| timezone: '' | |
| }, | |
| token: '', | |
| plan: { | |
| todo: [], | |
| in_progress: null, | |
| done: [] | |
| }, | |
| system_prompt: '', | |
| prompt: '', | |
| attachments: [] | |
| }, | |
| isCreating: false, | |
| isEditing: false, | |
| showLoadingState: false, | |
| viewMode: 'list', | |
| selectedTaskForDetail: null, | |
| attachmentsText: '', | |
| filteredTasks: [], | |
| // Minimal init to avoid errors | |
| init() { | |
| console.log('Basic schedulerSettings initialized'); | |
| // Watch for task type changes | |
| this.$watch('editingTask.type', (newType) => { | |
| if (newType === 'planned') { | |
| // When switching to planned task type, initialize the datetime picker | |
| this.$nextTick(() => { | |
| if (this.initFlatpickr) { | |
| if (this.isCreating) { | |
| this.initFlatpickr('create'); | |
| } else if (this.isEditing) { | |
| this.initFlatpickr('edit'); | |
| } | |
| } | |
| }); | |
| } | |
| }); | |
| } | |
| }; | |
| }; | |
| </script> | |
| <!-- Load module scripts first --> | |
| <script type="module" src="js/scheduler.js"></script> | |
| <script type="module" src="js/history.js"></script> | |
| <script type="module" src="index.js"></script> | |
| <!-- Then load Alpine.js --> | |
| <script defer src="vendor/alpine/alpine.collapse.min.js"></script> | |
| <!-- <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.3/dist/cdn.min.js"></script> --> | |
| <script type="module" src="js/initFw.js"></script> | |
| <script src="vendor/ace-min/ace.js"></script> | |
| <link href="vendor/ace-min/ace.min.css" rel="stylesheet"> | |
| <!-- KaTeX CSS --> | |
| <link rel="stylesheet" href="vendor/katex/katex.min.css" crossorigin="anonymous"> | |
| <!-- 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> | |
| <!-- QR Code Library --> | |
| <script src="vendor/qrcode.min.js"></script> | |
| <!-- Google Icons --> | |
| <link rel="stylesheet" href="vendor/google/google-icons.css" /> | |
| <!-- Non-module scripts after Alpine.js --> | |
| <script type="text/javascript" src="js/settings.js"></script> | |
| <script type="text/javascript" src="js/file_browser.js"></script> | |
| <script type="text/javascript" src="js/modal.js"></script> | |
| <script type="module" src="js/tunnel.js"></script> | |
| </head> | |
| <body class="dark-mode device-pointer"> | |
| <div class="container"> | |
| <div id="sidebar-overlay" class="sidebar-overlay hidden"></div> | |
| <div class="icons-section" id="hide-button" x-data="{ connected: true }"> | |
| <!--Sidebar--> | |
| <!-- Sidebar Toggle Button --> | |
| <button id="toggle-sidebar" class="toggle-sidebar-button" aria-label="Toggle Sidebar" aria-expanded="false"> | |
| <span aria-hidden="true"> | |
| <!-- Hamburger Icon --> | |
| <svg id="sidebar-hamburger-svg" xmlns="http://www.w3.org/2000/svg" width="22" height="22" | |
| viewBox="0 0 24 24" fill="CurrentColor"> | |
| <path d="M3 13h18v-2H3v2zm0 4h18v-2H3v2zm0-8h18V7H3v2z"></path> | |
| </svg> | |
| </span> | |
| </button> | |
| <div id="logo-container"> | |
| <a href="https://github.com/agent0ai/agent-zero" target="_blank" rel="noopener noreferrer"> | |
| <img src="./public/splash.jpg" alt="a0" width="22" height="22"> | |
| </a> | |
| </div> | |
| </div> | |
| <div id="left-panel" class="panel"> | |
| <!--Sidebar upper elements--> | |
| <div class="left-panel-top"> | |
| <div class="config-section" x-data="{ showQuickActions: true }"> | |
| <button class="config-button" id="resetChat" @click="resetChat()">Reset Chat</button> | |
| <button class="config-button" id="newChat" @click="newChat()">New Chat</button> | |
| <button class="config-button" id="loadChats" @click="loadChats()">Load Chat</button> | |
| <button class="config-button" id="loadChat" @click="saveChat()">Save Chat</button> | |
| <button class="config-button" id="restart" @click="restart()">Restart</button> | |
| <button class="config-button" id="settings" @click="settingsModalProxy.openModal()"><svg | |
| xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="-5.0 -17.0 110.0 135.0" | |
| fill="currentColor" width="24" height="24"> | |
| <path | |
| d="m52.301 90.102h-4.1016c-3 0-5 0-6.8984-1.3984-1.8984-1.3984-2.5-3.3008-3.5-6.1016l-1.1016-3.6016c-0.19922-0.60156-0.69922-1.1992-1.3984-1.6016l-1.1016-0.60156c-0.60156-0.30078-1.5-0.39844-2.3008-0.19922l-4.3008 1.1992c-3.1016 0.89844-5.1016 1.5-7.3984 0.5-2.3008-0.89844-3.3984-2.8008-5-5.6016l-1.8008-3.1016c-1.5-2.5-2.5-4.3008-2.3008-6.6992 0.19922-2.3984 1.6016-3.8008 3.6016-6.1016l3.8008-4.1992c0.19922-0.19922 0.60156-1.3984 0.60156-2.6016 0-1.1016-0.5-2.3008-0.80078-2.6992l-3.6016-4c-2-2.1992-3.3008-3.6992-3.6016-6.1016-0.19922-2.3984 0.80078-4.1992 2.3008-6.6992l1.8008-3.1016c1.6016-2.8008 2.6992-4.6016 5-5.6016 2.3008-0.89844 4.3008-0.39844 7.3984 0.5l4.3984 1.1992c0.69922 0.10156 1.5 0 2.3008-0.30078l1.1016-0.60156c0.5-0.30078 1-0.89844 1.3008-1.6992l1.1992-3.5c0.89844-2.8008 1.6016-4.6992 3.5-6.1016 1.8984-1.3984 3.8984-1.3984 6.8984-1.3984h4.1016c3 0 5 0 6.8984 1.3984 1.8984 1.3984 2.5 3.1992 3.5 6.1016l1.1992 3.6016c0.19922 0.60156 0.69922 1.1992 1.3984 1.6016l1.1016 0.60156c0.60156 0.30078 1.5 0.39844 2.3008 0.19922l4.3008-1.1992c3.1016-0.89844 5.1016-1.5 7.3984-0.5 2.3008 0.89844 3.3984 2.8008 5 5.5l1.8008 3.1016c1.3984 2.5 2.5 4.3008 2.3008 6.6992-0.19922 2.3984-1.6016 3.8008-3.6016 6.1016l-3.9961 4.4062c-0.19922 0.19922-0.60156 1.3984-0.60156 2.6016 0 1.1016 0.5 2.3008 0.80078 2.6992l3.6016 4c2 2.1992 3.3008 3.6992 3.6016 6.1016 0.19922 2.3984-0.80078 4.1992-2.3008 6.6992l-1.8008 3.1016c-1.6016 2.8008-2.6992 4.6016-5 5.6016-2.3008 0.89844-4.3984 0.39844-7.3984-0.5l-4.3984-1.1992c-0.69922-0.10156-1.5 0-2.3008 0.30078l-1.1016 0.60156c-0.5 0.30078-1 0.89844-1.3008 1.6992l-1.1992 3.5c-0.89844 2.8008-1.6016 4.6992-3.5 6.1016-1.8008 1.293-3.8008 1.293-6.8008 1.293zm-6.6016-7.3008c0.5 0.10156 1.6016 0.10156 2.6016 0.10156h4.1016c1 0 2 0 2.6016-0.10156 0.19922-0.5 0.60156-1.5 0.89844-2.3984l1.1992-3.6016c0.89844-2.3008 2.3984-4.1992 4.3984-5.3984l1.3984-0.80078c2.3984-1.3008 5.1016-1.6016 7.6016-1l4.6016 1.3008c1 0.30078 2.101... [truncated] | |
| </path> | |
| <path | |
| d="m50.301 66.5c-9 0-16.398-7.3008-16.398-16.398 0-9.1016 7.3008-16.398 16.398-16.398 9.1016 0 16.398 7.3008 16.398 16.398 0 9.0977-7.3984 16.398-16.398 16.398zm0-25.5c-5 0-9.1016 4.1016-9.1016 9.1016s4.1016 9.1016 9.1016 9.1016 9.1016-4.1016 9.1016-9.1016c-0.003906-5-4.1016-9.1016-9.1016-9.1016z"> | |
| </path> | |
| </svg>Settings</button> | |
| </div> | |
| <!-- Tabs container --> | |
| <div class="tabs-container"> | |
| <div class="tabs"> | |
| <div class="tab active" id="chats-tab">Chats</div> | |
| <div class="tab" id="tasks-tab">Tasks</div> | |
| </div> | |
| </div> | |
| <!-- Chats List --> | |
| <div class="config-section" id="chats-section" x-data="{ contexts: [], selected: '' }" | |
| x-show="contexts.length > 0 || true"> | |
| <div class="chats-list-container"> | |
| <ul class="config-list" x-show="contexts.length > 0"> | |
| <template x-for="context in contexts"> | |
| <li> | |
| <div :class="{'chat-list-button': true, 'font-bold': context.id === selected}" | |
| @click="selected = context.id; selectChat(context.id)"> | |
| <span class="chat-name" :title="context.name ? context.name : 'Chat #' + context.no" | |
| x-text="context.name ? context.name : 'Chat #' + context.no"></span> | |
| </div> | |
| <button class="edit-button" @click="killChat(context.id)">X</button> | |
| </li> | |
| </template> | |
| </ul> | |
| <div class="empty-list-message" x-show="contexts.length === 0"> | |
| <p><i>No chats to list.</i></p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Tasks List --> | |
| <div class="config-section" id="tasks-section" x-data="{ | |
| tasks: [], | |
| selected: '', | |
| openTaskDetail(taskId) { | |
| globalThis.openTaskDetail(taskId); | |
| } | |
| }" | |
| style="display: none;"> | |
| <div class="tasks-list-container"> | |
| <ul class="config-list" x-show="tasks.length > 0"> | |
| <template x-for="task in tasks"> | |
| <li> | |
| <div :class="{'chat-list-button': true, 'font-bold': task.id === selected, 'has-task-container': true}" | |
| @click="selected = task.id; selectChat(task.id)"> | |
| <!-- Task container with a vertical layout --> | |
| <div class="task-container task-container-vertical"> | |
| <!-- Task name on its own line with full width --> | |
| <span class="task-name" :title="(task.task_name || `Task #${task.no}`) + ' (' + (task.name || `Chat #${task.no}`) + ')'" | |
| x-text="(task.task_name || `Task #${task.no}`) + ' (' + (task.name || `Chat #${task.no}`) + ')'" | |
| :data-task-id="task.id"></span> | |
| <!-- Second line with status badge and action button --> | |
| <div class="task-info-line"> | |
| <!-- Status badge (reusing scheduler styling) --> | |
| <span class="scheduler-status-badge scheduler-status-badge-small" | |
| :class="task.state ? `scheduler-status-${task.state}` : 'scheduler-status-idle'" | |
| x-text="task.state || 'idle'"></span> | |
| <!-- Action buttons --> | |
| <button class="edit-button" | |
| @click.stop="openTaskDetail(task.id)" | |
| title="View task details" style="margin-left: auto; margin-right: 5px;"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 512 512" fill="var(--color-primary)" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <path d="M256 0c70.69 0 134.69 28.66 181.02 74.98C483.34 121.3 512 185.31 512 256c0 70.69-28.66 134.7-74.98 181.02C390.69 483.34 326.69 512 256 512c-70.69 0-134.69-28.66-181.02-74.98C28.66 390.69 0 326.69 0 256c0-70.69 28.66-134.69 74.98-181.02C121.31 28.66 185.31 0 256 0zm-9.96 161.03c0-4.28.76-8.26 2.27-11.91 1.5-3.63 3.77-6.94 6.79-9.91 3-2.95 6.29-5.2 9.84-6.7 3.57-1.5 7.41-2.28 11.52-2.28 4.12 0 7.96.78 11.49 2.27 3.54 1.51 6.78 3.76 9.75 6.73 2.95 2.97 5.16 6.26 6.64 9.91 1.49 3.63 2.22 7.61 2.22 11.89 0 4.17-.73 8.08-2.21 11.69-1.48 3.6-3.68 6.94-6.65 9.97-2.94 3.03-6.18 5.32-9.72 6.84-3.54 1.51-7.38 2.29-11.52 2.29-4.22 0-8.14-.76-11.75-2.26-3.58-1.51-6.86-3.79-9.83-6.79-2.94-3.02-5.16-6.34-6.63-9.97-1.48-3.62-2.21-7.54-2.21-11.77zm13.4 178.16c-1.11 3.97-3.35 11.76 3.3 11.76 1.44 0 3.27-.81 5.46-2.4 2.37-1.71 5.09-4.31 8.13-7.75 3.09-3.5 6.32-7.65 9.67-12.42 3.33-4.76 6.84-10.22 10.49-16.31.37-.65 1.23-.87 1.89-.48l12.36 9.18c.6.43.73 1.25.35 1.86-5.69 9.88-11.44 18.51-17.26 25.88-5.85 7.41-11.79 13.57-17.8 18.43l-.1.06c-6.02 4.88-12.19 8.55-18.51 11.01-17.58 6.81-45.36 5.7-53.32-14.83-5.02-12.96-.9-27.69 3.06-40.37l19.96-60.44c1.28-4.58 2.89-9.62 3.47-14.33.97-7.87-2.49-12.96-11.06-12.96h-17.45c-.76 0-1.38-.62-1.38-1.38l.08-.48 4.58-16.68c.16-.62.73-1.04 1.35-1.02l89.12-2.79c.76-.03 1.41.57 1.44 1.33l-.07.43-37.76 124.7zm158.3-244.93c-41.39-41.39-98.58-67-161.74-67-63.16 0-120.35 25.61-161.74 67-41.39 41.39-67 98.58-67 161.74 0 63.16 25.61 120.35 67 161.74 41.39 41.39 98.58 67 161.74 67 63.16 0 120.35-25.61 161.74-67 41.39-41.39 67-98.58 67-161.74 0-63.16-25.61-120.35-67-161.74z"/> | |
| </svg> | |
| </button> | |
| <button class="edit-button" | |
| @click.stop="resetChat(task.id)" | |
| style="margin-right: 5px;" | |
| title="Clear task chat"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 122.88 121.1" fill="var(--color-primary)" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <path d="M62.89,56.03c1.11-0.35,2.34-0.25,3.72,0.37l10.4,7.87c2.26,1.71,4.24,3.78,2.73,6.9 c-0.51,1.06-1.4,2.1-2.38,3.49l-0.53,0.75c-1.97,2.8-2.61,2-5.71,1.83c0.56,13.37,1.75,27.82-2.64,40.88 c-0.87,2.7-3.32,3.44-6.95,2.71l-6.1-2.03c4.11-6.14,6.16-13.85,6.44-22.89c-3.46,8.58-6.8,16.96-10.68,20.86l-6.28-2.08 c0.61-3.05,1.05-5.43,0.35-6.9l-4.07,4.24l-9.33-5.77c6.36-3.36,11.62-7.87,15.6-13.73c-6.69,5.01-12.76,8.1-18.14,8.99 c-2.75,0.83-4.49,0.35-5.16-1.53c-0.48-1.34-0.05-1.77,0.81-2.86c1.11-1.41,2.61-2.67,4.35-3.79c-3.13,1.1-4.64,0.95-6.37,1.51 c-4.9,1.59-9.94-1.86-8.26-6.9c1.07-3.23,3.54-3.09,6.67-4.07l5.42-1.69c-5.19,0.28-10.32,0.45-15.02-0.25 c-5.4-0.8-5.31-0.99-8.24-5.38c-3.94-5.91-6.25-11.45,2.52-9.16c16.73,3.18,33.56,5.34,51.25-0.98c-0.76-1.32-0.9-2.57-0.5-3.73 C57.37,60.94,61.13,56.58,62.89,56.03L62.89,56.03z M113.8,2.42L74.45,51.53c-4.71,6.68,3.2,11.91,8.39,5.64l39.2-49.27 C125.12,1.86,119.13-3.16,113.8,2.42L113.8,2.42z"/> | |
| </svg> | |
| </button> | |
| <button title="Delete task" class="edit-button" @click.stop="deleteTaskGlobal(task.id)">X</button> | |
| </div> | |
| </div> | |
| </div> | |
| </li> | |
| </template> | |
| </ul> | |
| <div class="empty-list-message" x-show="tasks.length === 0"> | |
| <p><i>No tasks to list.</i></p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!--Sidebar lower elements--> | |
| <div class="left-panel-bottom"> | |
| <!-- Preferences --> | |
| <div class="pref-section" x-data="{ prefOpen: true }"> | |
| <span> | |
| <h3 class="pref-header" @click="prefOpen = !prefOpen"> | |
| Preferences | |
| <svg class="arrow-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" | |
| stroke="currentColor" stroke-width="2" width="16" height="16" | |
| :class="{'rotated': !prefOpen}"> | |
| <path d="M8 4l8 8-8 8" /> | |
| </svg> | |
| </h3> | |
| <ul class="config-list" id="pref-list" x-show="prefOpen" x-collapse x-transition> | |
| <!-- Preferences Items --> | |
| <li x-data="{ autoScroll: true }"> | |
| <span>Autoscroll</span> | |
| <label class="switch"> | |
| <input id="auto-scroll-switch" type="checkbox" x-model="autoScroll" | |
| x-effect="globalThis.safeCall('toggleAutoScroll',autoScroll)"> | |
| <span class="slider"></span> | |
| </label> | |
| </li> | |
| <li x-data="{ darkMode: localStorage.getItem('darkMode') != 'false' }" | |
| x-init="$watch('darkMode', val => toggleDarkMode(val))"> | |
| <span class="switch-label">Dark mode</span> | |
| <label class="switch"> | |
| <input type="checkbox" x-model="darkMode"> | |
| <span class="slider"></span> | |
| </label> | |
| </li> | |
| <li x-data="{ speech: localStorage.getItem('speech') == 'true' }" | |
| x-init="$watch('speech', val => toggleSpeech(val))"> | |
| <span class="switch-label">Speech</span> | |
| <label class="switch"> | |
| <input type="checkbox" x-model="speech"> | |
| <span class="slider"></span> | |
| </label> | |
| </li> | |
| <li x-data="{ showThoughts: true }"> | |
| <span>Show thoughts</span> | |
| <label class="switch"> | |
| <input type="checkbox" x-model="showThoughts" | |
| x-effect="globalThis.safeCall('toggleThoughts',showThoughts)"> | |
| <span class="slider"></span> | |
| </label> | |
| </li> | |
| <li x-data="{ showJson: false }"> | |
| <span>Show JSON</span> | |
| <label class="switch"> | |
| <input type="checkbox" x-model="showJson" | |
| x-effect="globalThis.safeCall('toggleJson',showJson)"> | |
| <span class="slider"></span> | |
| </label> | |
| </li> | |
| <li x-data="{ showUtils: false }"> | |
| <span>Show utility messages</span> | |
| <label class="switch"> | |
| <input type="checkbox" x-model="showUtils" | |
| x-effect="globalThis.safeCall('toggleUtils',showUtils)"> | |
| <span class="slider"></span> | |
| </label> | |
| </li> | |
| </ul> | |
| </span> | |
| </div> | |
| <!-- Version Info --> | |
| <div class="version-info"> | |
| <span id="a0version">Version {{version_no}} {{version_time}}</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="right-panel" class="panel"> | |
| <!--Chat--> | |
| <div id="time-date-container"> | |
| <div id="time-date"></div> | |
| <div class="status-icon" x-data="{ connected: true }"> | |
| <svg viewBox="0 0 30 30"> | |
| <!-- Connected State (filled circle) --> | |
| <circle class="connected-circle" cx="15" cy="15" r="8" | |
| x-bind:fill="connected ? '#00c340' : 'none'" x-bind:opacity="connected ? 1 : 0" /> | |
| <!-- Disconnected State (outline circle) --> | |
| <circle class="disconnected-circle" cx="15" cy="15" r="12" fill="none" stroke="#e40138" | |
| stroke-width="3" x-bind:opacity="connected ? 0 : 1" /> | |
| </svg> | |
| </div> | |
| <!-- Notification Toggle positioned next to time-date --> | |
| <x-component path="notifications/notification-icons.html"></x-component> | |
| </div> | |
| <div id="chat-history"> | |
| </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> | |
| <div id="progress-bar-box"> | |
| <h4 id="progress-bar-h"> | |
| <span id="progress-bar-i">|></span><span id="progress-bar"></span> | |
| </h4> | |
| <h4 id="progress-bar-stop-speech" x-data x-cloak x-show="$store.speech.isSpeaking"> | |
| <span id="stop-speech" @click="$store.speech.stop()" style="cursor: pointer">Stop Speech</span> | |
| </h4> | |
| </div> | |
| <div id="input-section" x-data="{ | |
| paused: false | |
| }"> | |
| <!-- Attachment Preview section --> | |
| <div> | |
| <x-component path="/chat/attachments/inputPreview.html" /> | |
| </div> | |
| <!-- Top row with input and buttons --> | |
| <div class="input-row"> | |
| <!-- Attachment icon with tooltip --> | |
| <div class="attachment-wrapper" x-data="{ showTooltip: false }"> | |
| <label for="file-input" class="attachment-icon" @mouseover="showTooltip = true" | |
| @mouseleave="showTooltip = false"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" | |
| fill="currentColor"> | |
| <path | |
| d="M16.5 6v11.5c0 2.21-1.79 4-4 4s-4-1.79-4-4V5c0-1.38 1.12-2.5 2.5-2.5s2.5 1.12 2.5 2.5v10.5c0 .55-.45 1-1 1s-1-.45-1-1V6H10v9.5c0 1.38 1.12 2.5 2.5 2.5s2.5-1.12 2.5-2.5V5c0-2.21-1.79-4-4-4S7 2.79 7 5v12.5c0 3.04 2.46 5.5 5.5 5.5s5.5-2.46 5.5-5.5V6h-1.5z" /> | |
| </svg> | |
| </label> | |
| <input type="file" id="file-input" accept="*" multiple style="display: none" @change="$store.chatAttachments.handleFileUpload($event)"> | |
| <div x-show="showTooltip" class="tooltip"> | |
| Add attachments to the message | |
| </div> | |
| </div> | |
| <!-- Container for textarea and button --> | |
| <div id="chat-input-container" style="position: relative;"> | |
| <textarea id="chat-input" placeholder="Type your message here..." rows="1"></textarea> | |
| <!-- Expand button inside the textarea container --> | |
| <button id="expand-button" @click="$store.fullScreenInputModal.openModal()" aria-label="Expand input"> | |
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"> | |
| <path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/> | |
| </svg> | |
| </button> | |
| </div> | |
| <div id="chat-buttons-wrapper"> | |
| <!-- Send button --> | |
| <button class="chat-button" id="send-button" aria-label="Send message"> | |
| <div class="loader"></div> | |
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"> | |
| <path d="M25 20 L75 50 L25 80" fill="none" stroke="currentColor" stroke-width="15"> | |
| </path> | |
| </svg> | |
| </button> | |
| <!-- Microphone button --> | |
| <button class="chat-button mic-inactive" id="microphone-button" | |
| aria-label="Start/Stop recording" | |
| @click="$store.speech.handleMicrophoneClick()" | |
| x-effect="$store.speech.updateMicrophoneButtonUI()"> | |
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 18" fill="currentColor"> | |
| <path | |
| d="m8,12c1.66,0,3-1.34,3-3V3c0-1.66-1.34-3-3-3s-3,1.34-3,3v6c0,1.66,1.34,3,3,3Zm-1,1.9c-2.7-.4-4.8-2.6-5-5.4H0c.2,3.8,3.1,6.9,7,7.5v2h2v-2c3.9-.6,6.8-3.7,7-7.5h-2c-.2,2.8-2.3,5-5,5.4h-2Z" /> | |
| </svg> | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Bottom row with text buttons --> | |
| <div class="text-buttons-row"> | |
| <button class="text-button" @click="pauseAgent(!paused)"> | |
| <!-- Dynamic path that switches between pause and play icons --> | |
| <template x-if="!paused"> | |
| <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24" | |
| stroke-width="1.5" stroke="currentColor" width="14" height="14"> | |
| <path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"></path> | |
| </svg> | |
| </template> | |
| <template x-if="paused"> | |
| <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24" | |
| stroke-width="1.8" stroke="currentColor" width="14" height="14"> | |
| <path d="M8 5v14l11-7z"></path> | |
| </svg> | |
| </template> | |
| </svg> | |
| <span x-text="paused ? 'Resume Agent' : 'Pause Agent'"></span> | |
| </button> | |
| <button class="text-button" @click="loadKnowledge()"><svg xmlns="http://www.w3.org/2000/svg" | |
| fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> | |
| <path stroke-linecap="round" stroke-linejoin="round" | |
| d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5m-13.5-9L12 3m0 0 4.5 4.5M12 3v13.5"> | |
| </path> | |
| </svg> | |
| <p>Import knowledge</p> | |
| </button> | |
| <button class="text-button" id="work_dir_browser" @click="fileBrowserModalProxy.openModal()"> | |
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 123.37 92.59"> | |
| <path | |
| d="m5.72,11.5l-3.93,8.73h119.77s-3.96-8.73-3.96-8.73h-60.03c-1.59,0-2.88-1.29-2.88-2.88V1.75H13.72v6.87c0,1.59-1.29,2.88-2.88,2.88h-5.12Z" | |
| fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="7"></path> | |
| <path | |
| d="m6.38,20.23H1.75l7.03,67.03c.11,1.07.55,2.02,1.2,2.69.55.55,1.28.89,2.11.89h97.1c.82,0,1.51-.33,2.05-.87.68-.68,1.13-1.67,1.28-2.79l9.1-66.94H6.38Z" | |
| fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="8"></path> | |
| </svg> | |
| <p>Files</p> | |
| </button> | |
| <button class="text-button" id="history_inspect" @click="globalThis.openHistoryModal()"> | |
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="5 10 85 85"> | |
| <path fill="currentColor" | |
| d="m59.572,57.949c-.41,0-.826-.105-1.207-.325l-9.574-5.528c-.749-.432-1.21-1.231-1.21-2.095v-14.923c0-1.336,1.083-2.419,2.419-2.419s2.419,1.083,2.419,2.419v13.526l8.364,4.829c1.157.668,1.554,2.148.886,3.305-.448.776-1.261,1.21-2.097,1.21Zm30.427-7.947c0,10.684-4.161,20.728-11.716,28.283-6.593,6.59-15.325,10.69-24.59,11.544-1.223.113-2.448.169-3.669.169-7.492,0-14.878-2.102-21.22-6.068l-15.356,5.733c-.888.331-1.887.114-2.557-.556s-.887-1.669-.556-2.557l5.733-15.351c-4.613-7.377-6.704-16.165-5.899-24.891.854-9.266,4.954-17.998,11.544-24.588,7.555-7.555,17.6-11.716,28.285-11.716s20.73,4.161,28.285,11.716c7.555,7.555,11.716,17.599,11.716,28.283Zm-15.137-24.861c-13.71-13.71-36.018-13.71-49.728,0-11.846,11.846-13.682,30.526-4.365,44.417.434.647.53,1.464.257,2.194l-4.303,11.523,11.528-4.304c.274-.102.561-.153.846-.153.474,0,.944.139,1.348.41,13.888,9.315,32.568,7.479,44.417-4.365,13.707-13.708,13.706-36.014,0-49.723Zm-24.861-4.13c-15.989,0-28.996,13.006-28.996,28.992s13.008,28.992,28.996,28.992c1.336,0,2.419-1.083,2.419-2.419s-1.083-2.419-2.419-2.419c-13.32,0-24.157-10.835-24.157-24.153s10.837-24.153,24.157-24.153,24.153,10.835,24.153,24.153c0,1.336,1.083,2.419,2.419,2.419s2.419-1.083,2.419-2.419c0-15.986-13.006-28.992-28.992-28.992Zm25.041,33.531c-1.294.347-2.057,1.673-1.71,2.963.343,1.289,1.669,2.057,2.963,1.71,1.289-.343,2.053-1.669,1.71-2.963-.347-1.289-1.673-2.057-2.963-1.71Zm-2.03,6.328c-1.335,0-2.419,1.084-2.419,2.419s1.084,2.419,2.419,2.419,2.419-1.084,2.419-2.419-1.084-2.419-2.419-2.419Zm-3.598,5.587c-1.289-.347-2.615.416-2.963,1.71-.343,1.289.421,2.615,1.71,2.963,1.294.347,2.62-.421,2.963-1.71.347-1.294-.416-2.62-1.71-2.963Zm-4.919,4.462c-1.157-.667-2.638-.27-3.306.887-.667,1.157-.27,2.638.887,3.305,1.157.668,2.638.27,3.306-.887.667-1.157.27-2.638-.887-3.306Zm-9.327,3.04c-.946.946-.946,2.478,0,3.42.942.946,2.473.946,3.42,0,.946-.942.946-2.473,0-3.42-.946-.946-2.478-.946-3.42,0Z"> | |
| </path> | |
| </svg> | |
| <p>History</p> | |
| </button> | |
| <button class="text-button" id="ctx_window" @click="globalThis.openCtxWindowModal()"> | |
| <svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="17 15 70 70" fill="currentColor"> | |
| <path | |
| d="m63 25c1.1016 0 2-0.89844 2-2s-0.89844-2-2-2h-26c-1.1016 0-2 0.89844-2 2s0.89844 2 2 2z"> | |
| </path> | |
| <path | |
| d="m63 79c1.1016 0 2-0.89844 2-2s-0.89844-2-2-2h-26c-1.1016 0-2 0.89844-2 2s0.89844 2 2 2z"> | |
| </path> | |
| <path | |
| d="m68 39h-36c-6.0703 0-11 4.9297-11 11s4.9297 11 11 11h36c6.0703 0 11-4.9297 11-11s-4.9297-11-11-11zm0 18h-36c-3.8594 0-7-3.1406-7-7s3.1406-7 7-7h36c3.8594 0 7 3.1406 7 7s-3.1406 7-7 7z"> | |
| </path> | |
| </svg> | |
| <p>Context</p> | |
| </button> | |
| <button class="text-button" id="nudges_window" @click="nudge()"> | |
| <svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 49 58" | |
| fill="currentColor"> | |
| <path | |
| d="m11.97,16.32c-.46,0-.91-.25-1.15-.68-.9-1.63-1.36-3.34-1.36-5.1C9.45,4.73,14.18,0,20,0s10.55,4.73,10.55,10.55c0,.87-.13,1.75-.41,2.76-.19.7-.9,1.13-1.62.93-.7-.19-1.12-.92-.93-1.62.21-.79.31-1.44.31-2.07,0-4.36-3.55-7.91-7.91-7.91s-7.91,3.55-7.91,7.91c0,1.3.35,2.59,1.03,3.82.36.64.13,1.44-.51,1.79-.21.11-.42.17-.64.17Z" | |
| stroke-width="0.5" stroke="currentColor" /> | |
| <path | |
| d="m34.5,58h-6.18c-3.17,0-6.15-1.23-8.39-3.47L1.16,35.75c-1.54-1.54-1.54-4.05,0-5.59,2.4-2.4,6.27-2.68,8.99-.64l4.58,3.44V10.55c0-2.91,2.36-5.27,5.27-5.27s5.27,2.36,5.27,5.27v8.62c.78-.45,1.68-.71,2.64-.71,2.3,0,4.26,1.48,4.98,3.53.84-.56,1.85-.89,2.93-.89,2.3,0,4.26,1.48,4.98,3.53.84-.56,1.85-.89,2.93-.89,2.91,0,5.27,2.36,5.27,5.27v14.5c0,8-6.51,14.5-14.5,14.5ZM6.03,30.79c-1.1,0-2.19.42-3.01,1.23-.51.51-.51,1.35,0,1.86l18.77,18.78c1.74,1.74,4.06,2.7,6.53,2.7h6.18c6.54,0,11.86-5.32,11.86-11.86v-14.5c0-1.45-1.18-2.64-2.64-2.64s-2.64,1.18-2.64,2.64v1.32c0,.73-.59,1.32-1.32,1.32s-1.32-.59-1.32-1.32v-3.95c0-1.45-1.18-2.64-2.64-2.64s-2.64,1.18-2.64,2.64v3.95c0,.73-.59,1.32-1.32,1.32s-1.32-.59-1.32-1.32v-6.59c0-1.45-1.18-2.64-2.64-2.64s-2.64,1.18-2.64,2.64v6.59c0,.73-.59,1.32-1.32,1.32s-1.32-.59-1.32-1.32V10.55c0-1.45-1.18-2.64-2.64-2.64s-2.64,1.18-2.64,2.64v25.05c0,.5-.28.95-.73,1.18s-.98.18-1.38-.12l-6.69-5.02c-.75-.56-1.65-.84-2.54-.84Z" | |
| stroke-width="0.5" stroke="currentColor" /> | |
| </svg> | |
| <p>Nudge</p> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="settingsModal" x-data="settingsModalProxy"> | |
| <template x-teleport="body"> | |
| <div x-show="isOpen" class="modal-overlay" @click.self="handleCancel()" | |
| @keydown.escape.window="isOpen && handleCancel()" x-transition> | |
| <div class="modal-container"> | |
| <div class="modal-header"> | |
| <h2 x-text="settings.title"></h2> | |
| <button class="modal-close" @click="handleCancel()">×</button> | |
| </div> | |
| <div class="modal-content"> | |
| <!-- Tab Navigation --> | |
| <div class="settings-tabs-container"> | |
| <div class="settings-tabs"> | |
| <div class="settings-tab" | |
| :class="{'active': activeTab === 'agent'}" | |
| @click="switchTab('agent')" | |
| title="Agent Settings">Agent Settings</div> | |
| <div class="settings-tab" | |
| :class="{'active': activeTab === 'external'}" | |
| @click="switchTab('external')" | |
| title="External Services">External Services</div> | |
| <div class="settings-tab" | |
| :class="{'active': activeTab === 'mcp'}" | |
| @click="switchTab('mcp')" | |
| title="MCP/A2A">MCP/A2A</div> | |
| <div class="settings-tab" | |
| :class="{'active': activeTab === 'developer'}" | |
| @click="switchTab('developer')" | |
| title="Developer">Developer</div> | |
| <div class="settings-tab" | |
| :class="{'active': activeTab === 'scheduler'}" | |
| @click="switchTab('scheduler')" | |
| title="Task Scheduler">Task Scheduler</div> | |
| <div class="settings-tab" | |
| :class="{'active': activeTab === 'backup'}" | |
| @click="switchTab('backup')" | |
| title="Backup & Restore">Backup & Restore</div> | |
| </div> | |
| </div> | |
| <!-- Display settings sections for agent, external, developer, mcp, backup tabs --> | |
| <div id="settings-sections" x-show="activeTab !== 'scheduler'"> | |
| <nav> | |
| <ul> | |
| <template x-for="(section, index) in filteredSections" :key="section.title"> | |
| <li> | |
| <a :href="'#section' + (index + 1)"> | |
| <img :src="'/public/' + section.id +'.svg'" | |
| :alt="section.title"> | |
| <span x-text="section.title"></span> | |
| </a> | |
| </li> | |
| </template> | |
| <!-- Tunnel navigation entry - only visible in the External Services tab --> | |
| <li x-show="activeTab === 'external'"> | |
| <a href="#section-tunnel"> | |
| <img src="/public/tunnel.svg" alt="Tunnel"> | |
| <span>Flare Tunnel</span> | |
| </a> | |
| </li> | |
| </ul> | |
| </nav> | |
| <template x-for="(section, sectionIndex) in filteredSections" :key="sectionIndex"> | |
| <div :id="'section' + (sectionIndex + 1)" class="section"> | |
| <div class="section-title" x-text="section.title"></div> | |
| <div class="section-description" x-html="section.description"></div> | |
| <template x-for="(field, fieldIndex) in section.fields.filter(f => !f.hidden)" :key="fieldIndex"> | |
| <div :class="{'field': true, 'field-full': field.type === 'textarea'}"> | |
| <div class="field-label" x-show="field.title || field.description"> | |
| <div class="field-title" x-text="field.title" x-show="field.title"></div> | |
| <div class="field-description" x-html="field.description || ''" x-show="field.description"></div> | |
| </div> | |
| <div class="field-control"> | |
| <!-- Input field --> | |
| <template x-if="field.type === 'text'"> | |
| <input type="text" :class="field.classes" :value="field.value" | |
| :readonly="field.readonly === true" | |
| @input="field.value = $event.target.value"> | |
| </template> | |
| <!-- Number field --> | |
| <template x-if="field.type === 'number'"> | |
| <input type="number" :class="field.classes" :value="field.value" | |
| :readonly="field.readonly === true" | |
| @input="field.value = $event.target.value" | |
| :min="field.min" :max="field.max" :step="field.step"> | |
| </template> | |
| <!-- Password field --> | |
| <template x-if="field.type === 'password'"> | |
| <input type="password" :class="field.classes" :value="field.value" | |
| :readonly="field.readonly === true" | |
| :id="field.id" | |
| autocomplete="off" | |
| @input="field.value = $event.target.value"> | |
| </template> | |
| <!-- Textarea field --> | |
| <template x-if="field.type === 'textarea'"> | |
| <textarea :class="field.classes" :value="field.value" | |
| :readonly="field.readonly === true" | |
| @input="field.value = $event.target.value"></textarea> | |
| </template> | |
| <!-- Switch field --> | |
| <template x-if="field.type === 'switch'"> | |
| <label class="toggle"> | |
| <input type="checkbox" :checked="field.value" | |
| :disabled="field.readonly === true" | |
| @change="field.value = $event.target.checked"> | |
| <span class="toggler"></span> | |
| </label> | |
| </template> | |
| <!-- Range field --> | |
| <template x-if="field.type === 'range'"> | |
| <div class="field-control"> | |
| <input type="range" :min="field.min" :max="field.max" | |
| :step="field.step" :value="field.value" | |
| :disabled="field.readonly === true" | |
| @input="field.value = $event.target.value" | |
| :class="field.classes"> | |
| <span class="range-value" x-text="field.value"></span> | |
| </div> | |
| </template> | |
| <!-- Button field --> | |
| <template x-if="field.type === 'button'"> | |
| <button class="btn btn-field" :class="field.classes" | |
| :disabled="field.readonly === true" | |
| @click="handleFieldButton(field)" x-text="field.value"></button> | |
| </template> | |
| <!-- Select field --> | |
| <template x-if="field.type === 'select'"> | |
| <select :class="field.classes" x-model="field.value" | |
| :disabled="field.readonly === true"> | |
| <template x-for="option in field.options" :key="option.value"> | |
| <option :value="option.value" x-text="option.label" | |
| :selected="option.value === field.value"></option> | |
| </template> | |
| </select> | |
| </template> | |
| <!-- HTML field --> | |
| <template x-if="field.type === 'html'"> | |
| <div :class="field.classes" x-html="field.value"></div> | |
| </template> | |
| </div> | |
| </div> | |
| </template> | |
| </div> | |
| </template> | |
| <!-- Tunnel section content - only visible in External Services tab --> | |
| <div id="section-tunnel" class="section" x-show="activeTab === 'external'"> | |
| <div class="section-title">Flare Tunnel</div> | |
| <div class="section-description">Create a secure public URL to access your Agent Zero instance anytime, anywhere.</div> | |
| <!-- Tunnel content UI --> | |
| <div class="tunnel-container" x-data="tunnelSettings"> | |
| <div class="field"> | |
| <div class="field-label"> | |
| <div class="field-title">Tunnel provider</div> | |
| <div class="field-description">Select provider for public tunnel</div> | |
| </div> | |
| <div class="field-control"> | |
| <select id="tunnel-provider" x-model="provider" :disabled="isLoading"> | |
| <option value="cloudflared">Cloudflare</option> | |
| <option value="serveo">Serveo</option> | |
| </select> | |
| </div> | |
| </div> | |
| <!-- Loading spinner for tunnel operations --> | |
| <div class="loading-spinner" x-show="isLoading"> | |
| <span class="icon material-symbols-outlined spin">progress_activity</span> | |
| <span x-text="loadingText || 'Processing tunnel request...'"></span> | |
| </div> | |
| <!-- Tunnel content when not loading --> | |
| <div x-show="!isLoading"> | |
| <!-- Tunnel link display when generated --> | |
| <div class="tunnel-link-container" x-show="linkGenerated"> | |
| <div class="tunnel-link-field"> | |
| <input type="text" class="tunnel-link-input" :value="tunnelLink" readonly> | |
| <div class="buttons-row"> | |
| <button class="copy-link-button" @click="copyToClipboard()" title="Copy to clipboard"> | |
| <span class="icon material-symbols-outlined">content_copy</span> Copy | |
| </button> | |
| <button class="refresh-link-button" @click="refreshLink()" title="Generate new URL"> | |
| <span class="icon material-symbols-outlined">refresh</span> Refresh | |
| </button> | |
| </div> | |
| </div> | |
| <div class="tunnel-qr-container"> | |
| <div class="tunnel-qr-code" id="qrcode-tunnel"> | |
| <!-- QR code will be generated here --> | |
| </div> | |
| <div class="tunnel-qr-label">Scan with mobile device</div> | |
| </div> | |
| <div class="tunnel-link-info"> | |
| Share this URL to allow others to access your Agent Zero instance. | |
| </div> | |
| <div class="tunnel-link-persistence"> | |
| This URL will persist until you stop the tunnel or restart the Docker container. | |
| </div> | |
| <div class="stop-tunnel-container"> | |
| <button class="btn btn-danger" @click="stopTunnel()"> | |
| <span class="icon material-symbols-outlined">stop_circle</span> Stop Tunnel | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Generate tunnel button when no link exists --> | |
| <div class="tunnel-actions" x-show="!linkGenerated"> | |
| <button class="btn btn-ok" @click="generateLink()"> | |
| <span class="icon material-symbols-outlined">play_circle</span> Create Tunnel | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Task Scheduler Tab Content --> | |
| <div id="scheduler-tab-content" x-show="activeTab === 'scheduler'" x-cloak> | |
| <!-- Settings section structure for task scheduler --> | |
| <nav> | |
| <ul> | |
| <li> | |
| <a href="#section-task-scheduler"> | |
| <img src="/public/task_scheduler.svg" alt="Task Scheduler"> | |
| <span>Task Scheduler</span> | |
| </a> | |
| </li> | |
| </ul> | |
| </nav> | |
| <div id="section-task-scheduler" class="section" | |
| x-data="schedulerSettings"> | |
| <div class="section-title">Task Scheduler</div> | |
| <div class="section-description">Manage scheduled tasks and automated processes for Agent Zero.</div> | |
| <!-- Create Task Form --> | |
| <div class="scheduler-form" x-show="isCreating"> | |
| <div class="scheduler-form-header"> | |
| <div class="scheduler-form-title">Create New Task</div> | |
| <div class="scheduler-form-actions"> | |
| <button class="btn btn-ok btn-field" @click="saveTask()"> | |
| Save | |
| </button> | |
| <button class="btn btn-cancel" @click="cancelEdit()"> | |
| Cancel | |
| </button> | |
| </div> | |
| </div> | |
| <div class="scheduler-form-grid"> | |
| <!-- Task Name --> | |
| <div class="scheduler-form-field"> | |
| <div class="label-help-wrapper"> | |
| <label class="scheduler-form-label">Task Name</label> | |
| <div class="scheduler-form-help">A unique name to identify this task</div> | |
| </div> | |
| <input type="text" x-model="editingTask.name" placeholder="Enter task name"> | |
| </div> | |
| <!-- Task Type Selection --> | |
| <div class="scheduler-form-field"> | |
| <div class="label-help-wrapper"> | |
| <label class="scheduler-form-label">Type</label> | |
| <div class="scheduler-form-help">Task execution method</div> | |
| </div> | |
| <select x-model="editingTask.type"> | |
| <option value="scheduled">Scheduled (Cron)</option> | |
| <option value="adhoc">Ad-hoc (Manual)</option> | |
| <option value="planned">Planned (Specific Times)</option> | |
| </select> | |
| </div> | |
| <!-- Task State in Create Form - Add after Task Type --> | |
| <div class="scheduler-form-field" x-show="isCreating"> | |
| <div class="label-help-wrapper"> | |
| <label class="scheduler-form-label">State</label> | |
| <div class="scheduler-form-help">Select the initial state of the task</div> | |
| </div> | |
| <div> | |
| <div class="scheduler-state-selector"> | |
| <span class="scheduler-status-badge scheduler-status-idle" | |
| :class="{'scheduler-status-selected': editingTask.state === 'idle'}" | |
| @click="editingTask.state = 'idle'">idle</span> | |
| <span class="scheduler-status-badge scheduler-status-running" | |
| :class="{'scheduler-status-selected': editingTask.state === 'running'}" | |
| @click="editingTask.state = 'running'">running</span> | |
| <span class="scheduler-status-badge scheduler-status-disabled" | |
| :class="{'scheduler-status-selected': editingTask.state === 'disabled'}" | |
| @click="editingTask.state = 'disabled'">disabled</span> | |
| <span class="scheduler-status-badge scheduler-status-error" | |
| :class="{'scheduler-status-selected': editingTask.state === 'error'}" | |
| @click="editingTask.state = 'error'">error</span> | |
| </div> | |
| <div class="scheduler-state-explanation"> | |
| <span x-show="editingTask.state === 'idle'"><strong>idle</strong>: ready to run</span> | |
| <span x-show="editingTask.state === 'running'"><strong>running</strong>: currently executing</span> | |
| <span x-show="editingTask.state === 'disabled'"><strong>disabled</strong>: won't execute automatically</span> | |
| <span x-show="editingTask.state === 'error'"><strong>error</strong>: task encountered an error</span> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Schedule (only for scheduled tasks) --> | |
| <div class="scheduler-form-field full-width" x-show="editingTask.type === 'scheduled'"> | |
| <div class="label-help-wrapper"> | |
| <label class="scheduler-form-label">Schedule</label> | |
| <div class="scheduler-form-help">Cron schedule for automated execution (minute hour day month weekday)</div> | |
| </div> | |
| <div class="scheduler-schedule-builder"> | |
| <div class="scheduler-schedule-field"> | |
| <span class="scheduler-schedule-label">Minute</span> | |
| <input type="text" x-model="editingTask.schedule.minute" placeholder="*" maxlength="9"> | |
| </div> | |
| <div class="scheduler-schedule-field"> | |
| <span class="scheduler-schedule-label">Hour</span> | |
| <input type="text" x-model="editingTask.schedule.hour" placeholder="*" maxlength="9"> | |
| </div> | |
| <div class="scheduler-schedule-field"> | |
| <span class="scheduler-schedule-label">Day</span> | |
| <input type="text" x-model="editingTask.schedule.day" placeholder="*" maxlength="9"> | |
| </div> | |
| <div class="scheduler-schedule-field"> | |
| <span class="scheduler-schedule-label">Month</span> | |
| <input type="text" x-model="editingTask.schedule.month" placeholder="*" maxlength="9"> | |
| </div> | |
| <div class="scheduler-schedule-field"> | |
| <span class="scheduler-schedule-label">Weekday</span> | |
| <input type="text" x-model="editingTask.schedule.weekday" placeholder="*" maxlength="9"> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Plan (for planned tasks) --> | |
| <div class="scheduler-form-field full-width" x-show="editingTask.type === 'planned'"> | |
| <div class="label-help-wrapper"> | |
| <label class="scheduler-form-label">Plan</label> | |
| <div class="scheduler-form-help">Specific execution times for this task</div> | |
| </div> | |
| <div class="scheduler-plan-builder"> | |
| <div class="scheduler-plan-todo"> | |
| <span class="scheduler-plan-label">Upcoming Executions</span> | |
| <div class="scheduler-todo-list"> | |
| <template x-if="editingTask.plan && Array.isArray(editingTask.plan.todo) && editingTask.plan.todo.length > 0"> | |
| <template x-for="(time, index) in editingTask.plan.todo" :key="index"> | |
| <div class="scheduler-todo-item"> | |
| <span x-text="formatDate(time)"></span> | |
| <button @click.prevent="editingTask.plan.todo.splice(index, 1)" class="scheduler-todo-remove"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <line x1="18" y1="6" x2="6" y2="18"></line> | |
| <line x1="6" y1="6" x2="18" y2="18"></line> | |
| </svg> | |
| </button> | |
| </div> | |
| </template> | |
| </template> | |
| <template x-if="!editingTask.plan || !Array.isArray(editingTask.plan.todo) || editingTask.plan.todo.length === 0"> | |
| <div class="scheduler-empty-plan"> | |
| No scheduled execution times yet. Add one below. | |
| </div> | |
| </template> | |
| <div class="scheduler-add-todo"> | |
| <!-- Create form planned task input --> | |
| <input type="text" id="newPlannedTime-create" | |
| x-ref="plannedTimeCreate" | |
| class="scheduler-flatpickr-input" | |
| placeholder="Select date and time"> | |
| <!-- Create Task Form Add Time Button --> | |
| <button @click.prevent=" | |
| const input = $refs.plannedTimeCreate; | |
| if (!input) { | |
| console.error('Input reference not found for plannedTimeCreate'); | |
| return; | |
| } | |
| // Ensure plan structure exists | |
| if (!editingTask.plan) { | |
| editingTask.plan = { todo: [], in_progress: null, done: [] }; | |
| } | |
| if (!Array.isArray(editingTask.plan.todo)) { | |
| editingTask.plan.todo = []; | |
| } | |
| // Get date from Flatpickr if available | |
| let selectedDate; | |
| if (input._flatpickr && input._flatpickr.selectedDates.length > 0) { | |
| selectedDate = input._flatpickr.selectedDates[0]; | |
| } else if (input.value) { | |
| selectedDate = new Date(input.value); | |
| } | |
| if (!selectedDate || isNaN(selectedDate.getTime())) { | |
| alert('Please select a valid date and time'); | |
| return; | |
| } | |
| // Convert to ISO string and add to plan | |
| editingTask.plan.todo.push(selectedDate.toISOString()); | |
| // Sort by date (earliest first) | |
| editingTask.plan.todo.sort(); | |
| // Clear the input | |
| if (input._flatpickr) { | |
| input._flatpickr.clear(); | |
| } else { | |
| input.value = ''; | |
| } | |
| " class="scheduler-add-todo-button"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right: 4px;"> | |
| <line x1="12" y1="5" x2="12" y2="19"></line> | |
| <line x1="5" y1="12" x2="19" y2="12"></line> | |
| </svg> | |
| Add Time | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Token (for ad-hoc tasks) --> | |
| <div class="scheduler-form-field full-width" x-show="editingTask.type === 'adhoc'"> | |
| <div class="label-help-wrapper"> | |
| <label class="scheduler-form-label">Token</label> | |
| <div class="scheduler-form-help">Token used to trigger this task externally</div> | |
| </div> | |
| <div class="input-group"> | |
| <input type="text" x-model="editingTask.token" placeholder="Token for ad-hoc task"> | |
| <button class="scheduler-task-action" @click="editingTask.token = generateRandomToken()"> | |
| Generate | |
| </button> | |
| </div> | |
| </div> | |
| <!-- System Prompt --> | |
| <div class="scheduler-form-field full-width"> | |
| <div class="label-help-wrapper"> | |
| <label class="scheduler-form-label">System Prompt</label> | |
| <div class="scheduler-form-help">System-level instructions for the assistant</div> | |
| </div> | |
| <textarea x-model="editingTask.system_prompt" placeholder="System instructions for the AI"></textarea> | |
| </div> | |
| <!-- User Prompt --> | |
| <div class="scheduler-form-field full-width"> | |
| <div class="label-help-wrapper"> | |
| <label class="scheduler-form-label">User Prompt</label> | |
| <div class="scheduler-form-help">The main task prompt that will be executed</div> | |
| </div> | |
| <textarea x-model="editingTask.prompt" placeholder="User message for the AI"></textarea> | |
| </div> | |
| <!-- Attachments Field --> | |
| <div class="scheduler-form-field full-width"> | |
| <div class="label-help-wrapper"> | |
| <label class="scheduler-form-label">Attachments</label> | |
| <div class="scheduler-form-help">Container file paths or URLs, one per line</div> | |
| </div> | |
| <textarea x-model="attachmentsText" placeholder="Enter file paths or URLs, one per line"></textarea> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Edit Task Form --> | |
| <div class="scheduler-form" x-show="isEditing"> | |
| <div class="scheduler-form-header"> | |
| <div class="scheduler-form-title">Edit Task</div> | |
| <div class="scheduler-form-actions"> | |
| <button class="btn btn-ok btn-field" @click="saveTask()"> | |
| Save | |
| </button> | |
| <button class="btn btn-cancel" @click="cancelEdit()"> | |
| Cancel | |
| </button> | |
| </div> | |
| </div> | |
| <div class="scheduler-form-grid"> | |
| <!-- Task Name --> | |
| <div class="scheduler-form-field"> | |
| <div class="label-help-wrapper"> | |
| <label class="scheduler-form-label">Task Name</label> | |
| <div class="scheduler-form-help">A unique name to identify this task</div> | |
| </div> | |
| <input type="text" x-model="editingTask.name" placeholder="Enter task name"> | |
| </div> | |
| <!-- Task Type (disabled when editing) --> | |
| <div class="scheduler-form-field"> | |
| <div class="label-help-wrapper"> | |
| <label class="scheduler-form-label">Task Type</label> | |
| <div class="scheduler-form-help">Task type cannot be changed after creation</div> | |
| </div> | |
| <select x-model="editingTask.type" disabled> | |
| <option value="scheduled">Scheduled Task</option> | |
| <option value="adhoc">Ad-hoc Task</option> | |
| <option value="planned">Planned Task</option> | |
| </select> | |
| </div> | |
| <!-- Task State in Edit Form - Add after Task Type --> | |
| <div class="scheduler-form-field" x-show="isEditing"> | |
| <div class="label-help-wrapper"> | |
| <label class="scheduler-form-label">State</label> | |
| <div class="scheduler-form-help">Change the task's state</div> | |
| </div> | |
| <div> | |
| <div class="scheduler-state-selector"> | |
| <span class="scheduler-status-badge scheduler-status-idle" | |
| :class="{'scheduler-status-selected': editingTask.state === 'idle'}" | |
| @click="editingTask.state = 'idle'">idle</span> | |
| <span class="scheduler-status-badge scheduler-status-running" | |
| :class="{'scheduler-status-selected': editingTask.state === 'running'}" | |
| @click="editingTask.state = 'running'">running</span> | |
| <span class="scheduler-status-badge scheduler-status-disabled" | |
| :class="{'scheduler-status-selected': editingTask.state === 'disabled'}" | |
| @click="editingTask.state = 'disabled'">disabled</span> | |
| <span class="scheduler-status-badge scheduler-status-error" | |
| :class="{'scheduler-status-selected': editingTask.state === 'error'}" | |
| @click="editingTask.state = 'error'">error</span> | |
| </div> | |
| <div class="scheduler-state-explanation"> | |
| <span x-show="editingTask.state === 'idle'"><strong>idle</strong>: ready to run</span> | |
| <span x-show="editingTask.state === 'running'"><strong>running</strong>: currently executing</span> | |
| <span x-show="editingTask.state === 'disabled'"><strong>disabled</strong>: won't execute automatically</span> | |
| <span x-show="editingTask.state === 'error'"><strong>error</strong>: task encountered an error</span> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Schedule (for scheduled tasks) --> | |
| <div class="scheduler-form-field full-width" x-show="editingTask && editingTask.type === 'scheduled'"> | |
| <div class="label-help-wrapper"> | |
| <label class="scheduler-form-label">Schedule (Cron Expression)</label> | |
| <div class="scheduler-form-help">Format: minute hour day month weekday (e.g., "* * * * *" for every minute)</div> | |
| </div> | |
| <div class="scheduler-schedule-builder"> | |
| <div class="scheduler-schedule-field"> | |
| <span class="scheduler-schedule-label">Minute</span> | |
| <input type="text" x-model="editingTask.schedule.minute" placeholder="*" maxlength="9"> | |
| </div> | |
| <div class="scheduler-schedule-field"> | |
| <span class="scheduler-schedule-label">Hour</span> | |
| <input type="text" x-model="editingTask.schedule.hour" placeholder="*" maxlength="9"> | |
| </div> | |
| <div class="scheduler-schedule-field"> | |
| <span class="scheduler-schedule-label">Day</span> | |
| <input type="text" x-model="editingTask.schedule.day" placeholder="*" maxlength="9"> | |
| </div> | |
| <div class="scheduler-schedule-field"> | |
| <span class="scheduler-schedule-label">Month</span> | |
| <input type="text" x-model="editingTask.schedule.month" placeholder="*" maxlength="9"> | |
| </div> | |
| <div class="scheduler-schedule-field"> | |
| <span class="scheduler-schedule-label">Weekday</span> | |
| <input type="text" x-model="editingTask.schedule.weekday" placeholder="*" maxlength="9"> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Plan (for planned tasks) --> | |
| <div class="scheduler-form-field full-width" x-show="editingTask.type === 'planned'"> | |
| <div class="label-help-wrapper"> | |
| <label class="scheduler-form-label">Plan</label> | |
| <div class="scheduler-form-help">Specific execution times for this task</div> | |
| </div> | |
| <div class="scheduler-plan-builder"> | |
| <div class="scheduler-plan-todo"> | |
| <span class="scheduler-plan-label">Upcoming Executions</span> | |
| <div class="scheduler-todo-list"> | |
| <template x-if="editingTask.plan && Array.isArray(editingTask.plan.todo) && editingTask.plan.todo.length > 0"> | |
| <template x-for="(time, index) in editingTask.plan.todo" :key="index"> | |
| <div class="scheduler-todo-item"> | |
| <span x-text="formatDate(time)"></span> | |
| <button @click.prevent="editingTask.plan.todo.splice(index, 1)" class="scheduler-todo-remove"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <line x1="18" y1="6" x2="6" y2="18"></line> | |
| <line x1="6" y1="6" x2="18" y2="18"></line> | |
| </svg> | |
| </button> | |
| </div> | |
| </template> | |
| </template> | |
| <template x-if="!editingTask.plan || !Array.isArray(editingTask.plan.todo) || editingTask.plan.todo.length === 0"> | |
| <div class="scheduler-empty-plan"> | |
| No scheduled execution times yet. Add one below. | |
| </div> | |
| </template> | |
| <div class="scheduler-add-todo"> | |
| <!-- Edit form planned task input --> | |
| <input type="text" id="newPlannedTime-edit" | |
| x-ref="plannedTimeEdit" | |
| class="scheduler-flatpickr-input" | |
| placeholder="Select date and time"> | |
| <!-- Edit Task Form Add Time Button --> | |
| <button @click.prevent=" | |
| const input = $refs.plannedTimeEdit; | |
| if (!input) { | |
| console.error('Input reference not found for plannedTimeEdit'); | |
| return; | |
| } | |
| // Ensure plan structure exists | |
| if (!editingTask.plan) { | |
| editingTask.plan = { todo: [], in_progress: null, done: [] }; | |
| } | |
| if (!Array.isArray(editingTask.plan.todo)) { | |
| editingTask.plan.todo = []; | |
| } | |
| // Get date from Flatpickr if available | |
| let selectedDate; | |
| if (input._flatpickr && input._flatpickr.selectedDates.length > 0) { | |
| selectedDate = input._flatpickr.selectedDates[0]; | |
| } else if (input.value) { | |
| selectedDate = new Date(input.value); | |
| } | |
| if (!selectedDate || isNaN(selectedDate.getTime())) { | |
| alert('Please select a valid date and time'); | |
| return; | |
| } | |
| // Convert to ISO string and add to plan | |
| editingTask.plan.todo.push(selectedDate.toISOString()); | |
| // Sort by date (earliest first) | |
| editingTask.plan.todo.sort(); | |
| // Clear the input | |
| if (input._flatpickr) { | |
| input._flatpickr.clear(); | |
| } else { | |
| input.value = ''; | |
| } | |
| " class="scheduler-add-todo-button"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right: 4px;"> | |
| <line x1="12" y1="5" x2="12" y2="19"></line> | |
| <line x1="5" y1="12" x2="19" y2="12"></line> | |
| </svg> | |
| Add Time | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Token (for ad-hoc tasks) --> | |
| <div class="scheduler-form-field full-width" x-show="editingTask.type === 'adhoc'"> | |
| <div class="label-help-wrapper"> | |
| <label class="scheduler-form-label">Token</label> | |
| <div class="scheduler-form-help">Token used to trigger this task externally</div> | |
| </div> | |
| <div class="input-group"> | |
| <input type="text" x-model="editingTask.token" placeholder="Token for ad-hoc task"> | |
| <button class="scheduler-task-action" @click="editingTask.token = generateRandomToken()"> | |
| Generate | |
| </button> | |
| </div> | |
| </div> | |
| <!-- System Prompt --> | |
| <div class="scheduler-form-field full-width"> | |
| <div class="label-help-wrapper"> | |
| <label class="scheduler-form-label">System Prompt</label> | |
| <div class="scheduler-form-help">System-level instructions for the assistant</div> | |
| </div> | |
| <textarea x-model="editingTask.system_prompt" placeholder="System instructions for the AI"></textarea> | |
| </div> | |
| <!-- User Prompt --> | |
| <div class="scheduler-form-field full-width"> | |
| <div class="label-help-wrapper"> | |
| <label class="scheduler-form-label">User Prompt</label> | |
| <div class="scheduler-form-help">The main task prompt that will be executed</div> | |
| </div> | |
| <textarea x-model="editingTask.prompt" placeholder="User message for the AI"></textarea> | |
| </div> | |
| <!-- Attachments Field --> | |
| <div class="scheduler-form-field full-width"> | |
| <div class="label-help-wrapper"> | |
| <label class="scheduler-form-label">Attachments</label> | |
| <div class="scheduler-form-help">Container file paths or URLs, one per line</div> | |
| </div> | |
| <textarea x-model="attachmentsText" placeholder="Enter file paths or URLs, one per line"></textarea> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Task List View --> | |
| <div class="scheduler-container" x-show="!isCreating && !isEditing && viewMode === 'list'"> | |
| <!-- Header with Actions --> | |
| <div class="scheduler-header"> | |
| <h2>Task Management</h2> | |
| <div class="scheduler-actions"> | |
| <button class="btn btn-ok" @click="startCreateTask()"> | |
| New Task | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Filters --> | |
| <div class="scheduler-filters"> | |
| <div class="scheduler-filter-group"> | |
| <span class="scheduler-filter-label">Type:</span> | |
| <select class="scheduler-filter-select" x-model="filterType"> | |
| <option value="all">All Types</option> | |
| <option value="scheduled">Scheduled</option> | |
| <option value="adhoc">Ad-hoc</option> | |
| <option value="planned">Planned</option> | |
| </select> | |
| </div> | |
| <div class="scheduler-filter-group"> | |
| <span class="scheduler-filter-label">State:</span> | |
| <select class="scheduler-filter-select" x-model="filterState"> | |
| <option value="all">All States</option> | |
| <option value="idle">Idle</option> | |
| <option value="running">Running</option> | |
| <option value="disabled">Disabled</option> | |
| <option value="error">Error</option> | |
| </select> | |
| </div> | |
| </div> | |
| <!-- Loading State --> | |
| <!-- <div class="scheduler-loading" x-show="isLoading"> | |
| Loading tasks... | |
| </div> --> | |
| <!-- Empty State --> | |
| <div class="scheduler-empty" | |
| x-show="!isLoading && filteredTasks.length === 0" | |
| x-effect="$el.style.display = (!isLoading && filteredTasks.length === 0) ? '' : 'none'"> | |
| <!-- <div class="scheduler-empty-icon">📋</div> --> | |
| <div class="scheduler-empty-text">No tasks found</div> | |
| <button class="btn btn-ok" @click="startCreateTask()">Create your first task</button> | |
| </div> | |
| <!-- Task List Table --> | |
| <table class="scheduler-task-list" | |
| x-show="!isLoading && filteredTasks.length > 0" | |
| x-effect="$el.style.display = (!isLoading && filteredTasks.length > 0) ? '' : 'none'"> | |
| <thead> | |
| <tr> | |
| <th @click="changeSort('name')"> | |
| Name | |
| <span class="scheduler-sort-indicator" x-show="sortField === 'name'" | |
| :class="{'scheduler-sort-desc': sortDirection === 'desc'}">↑</span> | |
| </th> | |
| <th @click="changeSort('state')"> | |
| State | |
| <span class="scheduler-sort-indicator" x-show="sortField === 'state'" | |
| :class="{'scheduler-sort-desc': sortDirection === 'desc'}">↑</span> | |
| </th> | |
| <th>Type</th> | |
| <th>Schedule</th> | |
| <th @click="changeSort('last_run')"> | |
| Last Run | |
| <span class="scheduler-sort-indicator" x-show="sortField === 'last_run'" | |
| :class="{'scheduler-sort-desc': sortDirection === 'desc'}">↑</span> | |
| </th> | |
| <th>Actions</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <template x-for="task in filteredTasks" :key="task.uuid"> | |
| <tr @click="showTaskDetail(task.uuid)"> | |
| <td> | |
| <span x-text="task.name"></span> | |
| </td> | |
| <td> | |
| <span class="scheduler-status-badge" :class="getStateBadgeClass(task.state)" x-text="task.state"></span> | |
| </td> | |
| <td x-text="task.type"></td> | |
| <td> | |
| <span x-show="task.type === 'scheduled'" x-text="formatSchedule(task)"></span> | |
| <span x-show="task.type === 'adhoc'" class="scheduler-no-schedule">—</span> | |
| <span x-show="task.type === 'planned'" x-html="formatPlan(task).replace(/ | |
| /g, '<br>')"></span> | |
| </td> | |
| <td x-text="formatDate(task.last_run)"></td> | |
| <td @click.stop> | |
| <div class="scheduler-task-actions"> | |
| <button class="scheduler-task-action" @click="runTask(task.uuid)" title="Run Task"> | |
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="16" height="16"> | |
| <path d="M8 5v14l11-7z"/> | |
| </svg> | |
| </button> | |
| <button class="scheduler-task-action" @click="resetTaskState(task.uuid)" title="Reset State"> | |
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="16" height="16"> | |
| <path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/> | |
| </svg> | |
| </button> | |
| <button class="scheduler-task-action" @click="startEditTask(task.uuid)" title="Edit Task"> | |
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="16" height="16"> | |
| <path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/> | |
| </svg> | |
| </button> | |
| <button class="scheduler-task-action" @click="deleteTask(task.uuid)" title="Delete Task"> | |
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="16" height="16"> | |
| <path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/> | |
| </svg> | |
| </button> | |
| </div> | |
| </td> | |
| </tr> | |
| </template> | |
| </tbody> | |
| </table> | |
| </div> | |
| <!-- Task Detail View --> | |
| <div class="scheduler-detail-view" x-show="!isCreating && !isEditing && viewMode === 'detail' && selectedTaskForDetail"> | |
| <div class="scheduler-detail-header"> | |
| <h2 class="scheduler-detail-title" x-text="selectedTaskForDetail ? selectedTaskForDetail.name : ''"></h2> | |
| <div class="scheduler-status-badge" | |
| :class="selectedTaskForDetail ? getStateBadgeClass(selectedTaskForDetail.state) : ''" | |
| x-text="selectedTaskForDetail ? selectedTaskForDetail.state : ''"> | |
| </div> | |
| <button class="btn btn-cancel" @click="closeTaskDetail()">Close</button> | |
| </div> | |
| <div class="scheduler-detail-content"> | |
| <div class="scheduler-details-grid"> | |
| <div class="scheduler-details-label">Type:</div> | |
| <div class="scheduler-details-value" x-text="selectedTaskForDetail ? selectedTaskForDetail.type : ''"></div> | |
| <div class="scheduler-details-label">Created:</div> | |
| <div class="scheduler-details-value" x-text="selectedTaskForDetail ? formatDate(selectedTaskForDetail.created_at) : ''"></div> | |
| <div class="scheduler-details-label">Last Updated:</div> | |
| <div class="scheduler-details-value" x-text="selectedTaskForDetail ? formatDate(selectedTaskForDetail.updated_at) : ''"></div> | |
| <div class="scheduler-details-label">Last Run:</div> | |
| <div class="scheduler-details-value" x-text="selectedTaskForDetail ? formatDate(selectedTaskForDetail.last_run) : ''"></div> | |
| <div class="scheduler-details-label" x-show="selectedTaskForDetail && selectedTaskForDetail.type === 'scheduled'">Schedule:</div> | |
| <div class="scheduler-details-value" x-show="selectedTaskForDetail && selectedTaskForDetail.type === 'scheduled'" x-text="selectedTaskForDetail ? formatSchedule(selectedTaskForDetail) : ''"></div> | |
| <div class="scheduler-details-label" x-show="selectedTaskForDetail && selectedTaskForDetail.type === 'adhoc'">Token:</div> | |
| <div class="scheduler-details-value" x-show="selectedTaskForDetail && selectedTaskForDetail.type === 'adhoc'" x-text="selectedTaskForDetail ? selectedTaskForDetail.token : ''"></div> | |
| <div class="scheduler-details-label" x-show="selectedTaskForDetail && selectedTaskForDetail.type === 'planned'">Plan:</div> | |
| <div class="scheduler-details-value" x-show="selectedTaskForDetail && selectedTaskForDetail.type === 'planned'"> | |
| <div x-show="selectedTaskForDetail && selectedTaskForDetail.plan"> | |
| <div><strong>Upcoming:</strong></div> | |
| <template x-if="selectedTaskForDetail && selectedTaskForDetail.plan && selectedTaskForDetail.plan.todo && selectedTaskForDetail.plan.todo.length > 0"> | |
| <div> | |
| <template x-for="(time, index) in selectedTaskForDetail.plan.todo" :key="index"> | |
| <div x-text="formatDate(time)"></div> | |
| </template> | |
| </div> | |
| </template> | |
| <template x-if="!selectedTaskForDetail || !selectedTaskForDetail.plan || !selectedTaskForDetail.plan.todo || selectedTaskForDetail.plan.todo.length === 0"> | |
| <div>No upcoming executions</div> | |
| </template> | |
| <div><strong>In Progress:</strong></div> | |
| <div x-text="selectedTaskForDetail && selectedTaskForDetail.plan && selectedTaskForDetail.plan.in_progress ? formatDate(selectedTaskForDetail.plan.in_progress) : 'None'"></div> | |
| <div><strong>Completed:</strong></div> | |
| <template x-if="selectedTaskForDetail && selectedTaskForDetail.plan && selectedTaskForDetail.plan.done && selectedTaskForDetail.plan.done.length > 0"> | |
| <div> | |
| <template x-for="(time, index) in selectedTaskForDetail.plan.done" :key="index"> | |
| <div x-text="formatDate(time)"></div> | |
| </template> | |
| </div> | |
| </template> | |
| <template x-if="!selectedTaskForDetail || !selectedTaskForDetail.plan || !selectedTaskForDetail.plan.done || selectedTaskForDetail.plan.done.length === 0"> | |
| <div>No completed executions</div> | |
| </template> | |
| </div> | |
| </div> | |
| <div class="scheduler-details-label">Last Result:</div> | |
| <div class="scheduler-details-value" x-text="selectedTaskForDetail && selectedTaskForDetail.last_result ? selectedTaskForDetail.last_result : 'No results yet'"></div> | |
| <div class="scheduler-details-label">System Prompt:</div> | |
| <div class="scheduler-details-value" x-text="selectedTaskForDetail ? selectedTaskForDetail.system_prompt : ''"></div> | |
| <div class="scheduler-details-label">User Prompt:</div> | |
| <div class="scheduler-details-value" x-text="selectedTaskForDetail ? selectedTaskForDetail.prompt : ''"></div> | |
| <div class="scheduler-details-label">Attachments:</div> | |
| <div class="scheduler-details-value"> | |
| <template x-if="selectedTaskForDetail && selectedTaskForDetail.attachments && selectedTaskForDetail.attachments.length > 0"> | |
| <div> | |
| <template x-for="(attachment, index) in selectedTaskForDetail.attachments" :key="index"> | |
| <div x-text="attachment"></div> | |
| </template> | |
| </div> | |
| </template> | |
| <template x-if="!selectedTaskForDetail || !selectedTaskForDetail.attachments || selectedTaskForDetail.attachments.length === 0"> | |
| <div>No attachments</div> | |
| </template> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="modal-footer"> | |
| <div id="buttons-container"> | |
| <template x-for="button in settings.buttons" :key="button.id"> | |
| <button :class="button.classes" @click="handleButton(button.id)" | |
| x-text="button.title"></button> | |
| </template> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </template> | |
| </div> | |
| <!-- work_dir Browser Modal --> | |
| <div id="fileBrowserModal" x-data="fileBrowserModalProxy"> | |
| <template x-teleport="body"> | |
| <div x-show="isOpen" class="modal-overlay" @click.self="handleClose()" | |
| @keydown.escape.window="handleClose()" x-transition> | |
| <div class="modal-container"> | |
| <div class="modal-header"> | |
| <h2 class="modal-title" x-text="browser.title"></h2> | |
| <button class="modal-close" @click="handleClose()">×</button> | |
| </div> | |
| <div class="modal-content"> | |
| <div x-show="isLoading" class="loading-spinner"> | |
| Loading... | |
| </div> | |
| <div x-show="!isLoading"> | |
| <div class="path-navigator"> | |
| <!-- Up Button --> | |
| <button class="text-button back-button" @click="navigateUp()" aria-label="Navigate Up"> | |
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10.5 15"> | |
| <path d="m.75,5.25L5.25.75m0,0l4.5,4.5M5.25.75v13.5" fill="none" | |
| stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" | |
| stroke-width="1.5"></path> | |
| </svg> | |
| Up | |
| </button> | |
| <div id="current-path"> | |
| <span id="path-text" x-text="browser.currentPath"></span> | |
| </div> | |
| </div> | |
| <div class="files-list"> | |
| <!-- Header --> | |
| <div class="file-header"> | |
| <div class="file-cell" @click="toggleSort('name')"> | |
| Name | |
| <span x-show="browser.sortBy === 'name'" | |
| x-text="browser.sortDirection === 'asc' ? '↑' : '↓'"> | |
| </span> | |
| </div> | |
| <div class="file-cell-size" @click="toggleSort('size')"> | |
| Size | |
| <span x-show="browser.sortBy === 'size'" | |
| x-text="browser.sortDirection === 'asc' ? '↑' : '↓'"> | |
| </span> | |
| </div> | |
| <div class="file-cell-date" @click="toggleSort('date')"> | |
| Modified | |
| <span x-show="browser.sortBy === 'date'" | |
| x-text="browser.sortDirection === 'asc' ? '↑' : '↓'"> | |
| </span> | |
| </div> | |
| </div> | |
| <!-- File List --> | |
| <template x-if="browser.entries.length"> | |
| <template x-for="file in sortFiles(browser.entries)" :key="file.path"> | |
| <div class="file-item" :data-is-dir="file.is_dir"> | |
| <div class="file-name" | |
| @click="file.is_dir ? navigateToFolder(file.path) : downloadFile(file)"> | |
| <img :src="'/public/' + (file.type === 'unknown' ? 'file' : (isArchive(file.name) ? 'archive' : file.type)) + '.svg'" | |
| class="file-icon" :alt="file.type"> | |
| <span x-text="file.name"></span> | |
| </div> | |
| <div class="file-size" x-text="formatFileSize(file.size)"></div> | |
| <div class="file-date" x-text="formatDate(file.modified)"></div> | |
| <div class="file-actions"> | |
| <button class="action-button download-button" | |
| @click.stop="downloadFile(file)"> | |
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19.5 19.5"> | |
| <path | |
| d="m.75,14.25v2.25c0,1.24,1.01,2.25,2.25,2.25h13.5c1.24,0,2.25-1.01,2.25-2.25v-2.25m-4.5-4.5l-4.5,4.5m0,0l-4.5-4.5m4.5,4.5V.75" | |
| fill="none" stroke="currentColor" stroke-linecap="round" | |
| stroke-linejoin="round" stroke-width="1.5"></path> | |
| </svg> | |
| </button> | |
| <button class="delete-button" @click.stop="deleteFile(file)"> | |
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15.03 22.53" | |
| fill="currentColor"> | |
| <path | |
| d="m14.55,7.82H4.68L14.09,3.19c.83-.41,1.17-1.42.77-2.25-.41-.83-1.42-1.17-2.25-.77l-3.16,1.55-.15-.31c-.22-.44-.59-.76-1.05-.92-.46-.16-.96-.13-1.39.09l-2.08,1.02c-.9.44-1.28,1.54-.83,2.44l.15.31-3.16,1.55c-.83.41-1.17,1.42-.77,2.25.29.59.89.94,1.51.94.25,0,.5-.06.74-.17l.38-.19s.09.03.14.03h11.14v11.43c0,.76-.62,1.38-1.38,1.38h-.46v-11.28c0-.26-.21-.47-.47-.47s-.47.21-.47.47v11.28h-2.39v-11.28c0-.26-.21-.47-.47-.47s-.47.21-.47.47v11.28h-2.39v-11.28c0-.26-.21-.47-.47-.47s-.47.21-.47.47v11.28h-.46c-.76,0-1.38-.62-1.38-1.38v-9.9c0-.26-.21-.47-.47-.47s-.47.21-.47.47v9.9c0,1.28,1.04,2.32,2.32,2.32h8.55c1.28,0,2.32-1.04,2.32-2.32v-11.91c0-.26-.21-.47-.47-.47ZM5.19,2.46l2.08-1.02c.12-.06.25-.09.39-.09.09,0,.19.02.28.05.22.08.4.23.5.44l.15.31-.19.09-3.46,1.7-.15-.31c-.21-.43-.03-.96.4-1.17Zm-3.19,5.62c-.36.18-.8.03-.98-.33-.18-.36-.03-.8.33-.98l5.8-2.85,2.72-1.34,3.16-1.55c.1-.05.21-.07.32-.07.27,0,.53.15.66.41.09.17.1.37.04.56-.06.18-.19.33-.37.42L2,8.08Z" | |
| stroke-width="0"></path> | |
| </svg> | |
| </button> | |
| </div> | |
| </div> | |
| </template> | |
| </template> | |
| <!-- Empty State --> | |
| <template x-if="!browser.entries.length"> | |
| <div class="no-files"> | |
| No files found | |
| </div> | |
| </template> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="modal-footer"> | |
| <div id="buttons-container"> | |
| <label class="btn btn-upload"><svg xmlns="http://www.w3.org/2000/svg" fill="none" | |
| viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> | |
| <path stroke-linecap="round" stroke-linejoin="round" | |
| d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5m-13.5-9L12 3m0 0 4.5 4.5M12 3v13.5"> | |
| </path> | |
| </svg> | |
| Upload Files | |
| <input type="file" multiple="" accept="all" @change="handleFileUpload" | |
| style="display: none;"> | |
| </label> | |
| <button class="btn btn-cancel" @click="handleClose()">Close Browser</button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </template> | |
| </div> | |
| <!-- generic modal --> | |
| <div id="genericModal" x-data="genericModalProxy"> | |
| <template x-teleport="body"> | |
| <div x-show="isOpen" class="modal-overlay" @click.self="handleClose()" | |
| @keydown.escape.window="handleClose()" x-transition> | |
| <div class="modal-container"> | |
| <div class="modal-header"> | |
| <h2 class="modal-title" x-text="title"></h2> | |
| <button class="modal-close" @click="handleClose()">×</button> | |
| </div> | |
| <div class="modal-description" x-text="description"></div> | |
| <div class="modal-content" id="viewer"> | |
| <div class="html-pre" x-html="html"></div> | |
| </div> | |
| <!-- <div class="modal-footer"> | |
| <div id="buttons-container"> | |
| <button class="btn btn-cancel" @click="handleClose()">Close</button> | |
| </div> | |
| </div> --> | |
| </div> | |
| </template> | |
| </div> | |
| <!-- Full Screen Input Modal --> | |
| <div id="fullScreenInputModal" x-data="fullScreenInputModalProxy"> | |
| <template x-teleport="body"> | |
| <div x-show="isOpen" class="modal-overlay" @click.self="handleClose()" | |
| @keydown.escape.window="handleClose()" x-transition> | |
| <div class="modal-container full-screen-input-modal"> | |
| <div class="modal-content"> | |
| <button class="modal-close" @click="handleClose()">×</button> | |
| <!-- Add toolbar --> | |
| <div class="editor-toolbar"> | |
| <div class="toolbar-group"> | |
| <button class="toolbar-button" @click="undo()" :disabled="!canUndo" title="Undo (Ctrl+Z)"> | |
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | |
| <path d="M3 7v6h6"></path> | |
| <path d="M21 17a9 9 0 00-9-9 9 9 0 00-6 2.3L3 13"></path> | |
| </svg> | |
| </button> | |
| <button class="toolbar-button" @click="redo()" :disabled="!canRedo" title="Redo (Ctrl+Shift+Z)"> | |
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | |
| <path d="M21 7v6h-6"></path> | |
| <path d="M3 17a9 9 0 019-9 9 9 0 016 2.3l3 2.7"></path> | |
| </svg> | |
| </button> | |
| </div> | |
| <div class="toolbar-group"> | |
| <button class="toolbar-button" @click="clearText()" title="Clear Text"> | |
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | |
| <path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"></path> | |
| <line x1="10" y1="11" x2="10" y2="17"></line> | |
| <line x1="14" y1="11" x2="14" y2="17"></line> | |
| </svg> | |
| </button> | |
| <button class="toolbar-button" @click="toggleWrap()" :class="{ active: wordWrap }" title="Toggle Word Wrap"> | |
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | |
| <path d="M3 6h18M3 12h15l3 3-3 3M3 18h18"></path> | |
| </svg> | |
| </button> | |
| </div> | |
| </div> | |
| <textarea id="full-screen-input" x-model="inputText" | |
| placeholder="Type your message here..." | |
| @keydown.ctrl.enter="handleClose()" | |
| @keydown.ctrl.z.prevent="undo()" | |
| @keydown.ctrl.shift.z.prevent="redo()" | |
| :style="{ 'white-space': wordWrap ? 'pre-wrap' : 'pre' }" | |
| @input="updateHistory()"></textarea> | |
| </div> | |
| <div class="modal-footer"> | |
| <div id="buttons-container"> | |
| <button class="btn btn-ok" @click="handleClose()">Done (Ctrl+Enter)</button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </template> | |
| </div> | |
| <!-- Drag and Drop Overlay Component --> | |
| <x-component path="chat/attachments/dragDropOverlay.html"></x-component> | |
| </body> | |
| </html> | |