Spaces:
Paused
Paused
| <html> | |
| <head> | |
| <title>Welcome to Agent Zero</title> | |
| <script type="module"> | |
| import { store } from "/plugins/_onboarding/webui/onboarding-store.js"; | |
| </script> | |
| <style> | |
| .onboarding-logo { | |
| text-align: center; | |
| margin-bottom: 24px; | |
| } | |
| .onboarding-logo img { | |
| width: 200px; | |
| max-width: 100%; | |
| height: auto; | |
| } | |
| .onboarding-welcome-text { | |
| text-align: center; | |
| margin: var(--spacing-md) 0; | |
| color: var(--text-2); | |
| font-size: 1.1rem; | |
| line-height: 1.5; | |
| } | |
| .onboarding-welcome-title { | |
| color: var(--text-1); | |
| font-size: 1.5rem; | |
| font-weight: 600; | |
| margin-bottom: 12px; | |
| } | |
| .onboarding-success { | |
| text-align: center; | |
| padding: 40px 0; | |
| } | |
| .onboarding-success-icon { | |
| font-size: 64px; | |
| color: var(--success, #22c55e); | |
| margin-bottom: 24px; | |
| } | |
| .onboarding-success-text { | |
| margin-bottom: 0; | |
| } | |
| .onboarding-advanced-link { | |
| text-align: center; | |
| margin-top: 32px; | |
| padding-top: 16px; | |
| border-top: 1px solid var(--surface-3); | |
| } | |
| .onboarding-advanced-link a { | |
| color: var(--text-3); | |
| text-decoration: none; | |
| font-size: 0.9rem; | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 4px; | |
| } | |
| .onboarding-advanced-link a:hover { | |
| color: var(--text-1); | |
| } | |
| .onboarding-advanced-link-icon { | |
| font-size: 16px; | |
| } | |
| /* Scoped overrides to make the fields look nice here */ | |
| .onboarding-body .model-section { | |
| background: var(--surface-2); | |
| border-radius: 8px; | |
| border: 1px solid var(--surface-3); | |
| } | |
| .onboarding-body .section-title { margin-bottom: 8px; } | |
| .onboarding-body .section-description { margin-bottom: 24px; } | |
| .onboarding-body .loading-container { height: 200px; } | |
| .onboarding-body .input-with-icon { padding-right: 32px; } | |
| .onboarding-body .relative-container { position: relative; } | |
| .onboarding-footer-left { flex: 1; display: flex; gap: 8px; } | |
| .onboarding-footer-right { display: flex; gap: 8px; } | |
| .onboarding-icon-right { font-size: 18px; margin-left: 4px; } | |
| .onboarding-banner-btn-container { margin-top: 12px; } | |
| /* Same as plugins/_model_config/webui/config.html: icons sit inside padded inputs */ | |
| .onboarding-body .eye-toggle { | |
| position: absolute; | |
| right: 8px; | |
| top: 50%; | |
| transform: translateY(-50%); | |
| font-size: 18px; | |
| cursor: pointer; | |
| user-select: none; | |
| opacity: 0.6; | |
| z-index: 1; | |
| } | |
| .onboarding-body .eye-toggle:hover { | |
| opacity: 1; | |
| } | |
| .onboarding-body .model-search-btn { | |
| position: absolute; | |
| right: 8px; | |
| top: 50%; | |
| transform: translateY(-50%); | |
| width: 20px; | |
| height: 20px; | |
| display: grid; | |
| place-items: center; | |
| cursor: pointer; | |
| user-select: none; | |
| opacity: 0.6; | |
| z-index: 1; | |
| } | |
| .onboarding-body .model-search-btn:hover { | |
| opacity: 1; | |
| } | |
| .onboarding-body .model-search-btn > span { | |
| grid-area: 1 / 1; | |
| font-size: 18px; | |
| transition: opacity 0.15s; | |
| } | |
| .onboarding-body .model-search-spinner { | |
| animation: onboarding-model-search-spin 0.8s linear infinite; | |
| } | |
| @keyframes onboarding-model-search-spin { | |
| from { transform: rotate(0deg); } | |
| to { transform: rotate(360deg); } | |
| } | |
| .onboarding-body .model-search-results { | |
| position: absolute; | |
| top: calc(100% + 4px); | |
| left: 0; | |
| right: 0; | |
| max-height: 200px; | |
| overflow-y: auto; | |
| background: var(--color-input); | |
| border: 1px solid var(--color-border); | |
| border-radius: 6px; | |
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); | |
| z-index: 50; | |
| padding: 4px; | |
| } | |
| .onboarding-body .model-search-item { | |
| padding: 5px 8px; | |
| font-size: 0.8rem; | |
| border-radius: 4px; | |
| cursor: pointer; | |
| word-break: break-all; | |
| } | |
| .onboarding-body .model-search-item:hover { | |
| background: var(--color-background-hover, rgba(255,255,255,0.06)); | |
| } | |
| .onboarding-body .model-search-item.disabled { | |
| opacity: 0.4; | |
| cursor: default; | |
| font-style: italic; | |
| } | |
| .onboarding-body .model-search-item.matched { | |
| font-weight: 500; | |
| } | |
| .onboarding-body .model-search-separator { | |
| height: 1px; | |
| margin: 4px 8px; | |
| background: var(--color-border); | |
| opacity: 0.5; | |
| } | |
| .onboarding-body .model-search-item.disabled:hover { | |
| background: transparent; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div x-data> | |
| <template x-if="$store.onboarding"> | |
| <div x-init="$store.onboarding.onOpen()" x-destroy="$store.onboarding.cleanup()"> | |
| <div class="modal-header"> | |
| <div class="onboarding-logo"> | |
| <img src="/public/a0-fullDark.svg" alt="Agent Zero"> | |
| </div> | |
| </div> | |
| <div class="modal-scroll"> | |
| <div class="modal-bd onboarding-body"> | |
| <div x-show="$store.onboarding.loading" class="loading loading-container"></div> | |
| <template x-if="!$store.onboarding.loading && $store.onboarding.config"> | |
| <div> | |
| <!-- Step 1: Main Model --> | |
| <div x-show="$store.onboarding.step === 1"> | |
| <div class="onboarding-welcome-text"> | |
| <div class="onboarding-welcome-title">Welcome to Agent Zero</div> | |
| Let's get your models configured. The <b>Main Model</b> handles chat, tool calls, skills, and browser automation.<br> We recommend a capable model like Claude Sonnet 4.6, GPT-5.4, Kimi 2.5, or similar. | |
| </div> | |
| <div class="model-section"> | |
| <div class="section-title" x-text="$store.modelConfig.MODEL_SECTIONS[0].title"></div> | |
| <div class="section-description" x-text="$store.modelConfig.MODEL_SECTIONS[0].desc"></div> | |
| <!-- Provider --> | |
| <div class="field"> | |
| <div class="field-label"> | |
| <div class="field-title">Provider</div> | |
| </div> | |
| <div class="field-control"> | |
| <select x-model="$store.onboarding.config.chat_model.provider" | |
| x-effect="$nextTick(() => { if ($store.modelConfig.getProviders('chat_model').length) $el.value = $store.onboarding.config.chat_model.provider })"> | |
| <template x-for="p in $store.modelConfig.getProviders('chat_model')" :key="p.value"> | |
| <option :value="p.value" x-text="p.label"></option> | |
| </template> | |
| </select> | |
| </div> | |
| </div> | |
| <!-- Model search --> | |
| <div class="field"> | |
| <div class="field-label"> | |
| <div class="field-title">Model name</div> | |
| <div class="field-description">Model identifier. Click the search icon to browse available models.</div> | |
| </div> | |
| <div class="field-control relative-container" | |
| x-data="{ results: [], open: false, searching: false, | |
| doSearch() { this.searching = true; $store.modelConfig.searchModels($store.onboarding.config.chat_model.provider, $store.onboarding.config.chat_model.name, $store.modelConfig.getSearchType('chat_model'), $store.onboarding.config.chat_model.api_base).then(r => { this.results = r; this.open = true; }).finally(() => this.searching = false); }, | |
| grouped() { return $store.modelConfig.groupResults(this.results, $store.onboarding.config.chat_model.name); } | |
| }" | |
| @click.outside="open = false"> | |
| <input type="text" x-model="$store.onboarding.config.chat_model.name" class="input-with-icon" @keydown.enter.prevent="doSearch()" /> | |
| <span class="model-search-btn" @click="if (!searching) doSearch()" title="Search available models"> | |
| <span class="material-symbols-outlined" :style="searching && 'opacity:0'">search</span> | |
| <span class="material-symbols-outlined model-search-spinner" :style="!searching && 'opacity:0'">progress_activity</span> | |
| </span> | |
| <div class="model-search-results" x-show="open && results.length > 0" x-transition.opacity> | |
| <template x-for="m in grouped().matched" :key="'m_'+m"> | |
| <div class="model-search-item matched" @click="$store.onboarding.config.chat_model.name = m; open = false;" x-text="m"></div> | |
| </template> | |
| <div class="model-search-separator" x-show="grouped().matched.length > 0 && grouped().rest.length > 0"></div> | |
| <template x-for="m in grouped().rest" :key="'r_'+m"> | |
| <div class="model-search-item" @click="$store.onboarding.config.chat_model.name = m; open = false;" x-text="m"></div> | |
| </template> | |
| </div> | |
| <div class="model-search-results" x-show="open && results.length === 0 && !searching"> | |
| <div class="model-search-item disabled">No models found</div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- API Key --> | |
| <div class="field"> | |
| <div class="field-label"> | |
| <div class="field-title">API key</div> | |
| </div> | |
| <div class="field-control relative-container" x-data="{ showKey: false }"> | |
| <input :type="showKey ? 'text' : 'password'" | |
| x-model="$store.modelConfig.apiKeyValues[$store.onboarding.config.chat_model.provider]" | |
| :placeholder="$store.modelConfig.apiKeyStatus[$store.onboarding.config.chat_model.provider] ? '••••••••••••' : ''" | |
| autocomplete="off" | |
| class="input-with-icon" | |
| @input="$store.modelConfig.touchApiKey($store.onboarding.config.chat_model.provider)" /> | |
| <span class="material-symbols-outlined eye-toggle" | |
| @click=" | |
| showKey = !showKey; | |
| const prov = $store.onboarding.config.chat_model.provider; | |
| if (showKey && !$store.modelConfig.apiKeyValues[prov] && $store.modelConfig.apiKeyStatus[prov]) { | |
| $store.modelConfig.revealApiKey(prov).then(v => { if (v) $store.modelConfig.apiKeyValues[prov] = v; }); | |
| } | |
| " | |
| x-text="showKey ? 'visibility' : 'visibility_off'"></span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Step 2: Utility Model --> | |
| <div x-show="$store.onboarding.step === 2"> | |
| <div class="onboarding-welcome-text"> | |
| <div class="onboarding-welcome-title">Almost there!</div> | |
| The <b>Utility Model</b> handles background tasks like summarization and memory updates.<br> A fast, cheap model like GPT-5.4-mini, Gemini 3.1 Flash Lite, or similar works best here. | |
| </div> | |
| <div class="model-section"> | |
| <div class="section-title" x-text="$store.modelConfig.MODEL_SECTIONS[1].title"></div> | |
| <div class="section-description" x-text="$store.modelConfig.MODEL_SECTIONS[1].desc"></div> | |
| <!-- Provider --> | |
| <div class="field"> | |
| <div class="field-label"> | |
| <div class="field-title">Provider</div> | |
| </div> | |
| <div class="field-control"> | |
| <select x-model="$store.onboarding.config.utility_model.provider" | |
| x-effect="$nextTick(() => { if ($store.modelConfig.getProviders('utility_model').length) $el.value = $store.onboarding.config.utility_model.provider })"> | |
| <template x-for="p in $store.modelConfig.getProviders('utility_model')" :key="p.value"> | |
| <option :value="p.value" x-text="p.label"></option> | |
| </template> | |
| </select> | |
| </div> | |
| </div> | |
| <!-- Model search --> | |
| <div class="field"> | |
| <div class="field-label"> | |
| <div class="field-title">Model name</div> | |
| <div class="field-description">Model identifier. Click the search icon to browse available models.</div> | |
| </div> | |
| <div class="field-control relative-container" | |
| x-data="{ results: [], open: false, searching: false, | |
| doSearch() { this.searching = true; $store.modelConfig.searchModels($store.onboarding.config.utility_model.provider, $store.onboarding.config.utility_model.name, $store.modelConfig.getSearchType('utility_model'), $store.onboarding.config.utility_model.api_base).then(r => { this.results = r; this.open = true; }).finally(() => this.searching = false); }, | |
| grouped() { return $store.modelConfig.groupResults(this.results, $store.onboarding.config.utility_model.name); } | |
| }" | |
| @click.outside="open = false"> | |
| <input type="text" x-model="$store.onboarding.config.utility_model.name" class="input-with-icon" @keydown.enter.prevent="doSearch()" /> | |
| <span class="model-search-btn" @click="if (!searching) doSearch()" title="Search available models"> | |
| <span class="material-symbols-outlined" :style="searching && 'opacity:0'">search</span> | |
| <span class="material-symbols-outlined model-search-spinner" :style="!searching && 'opacity:0'">progress_activity</span> | |
| </span> | |
| <div class="model-search-results" x-show="open && results.length > 0" x-transition.opacity> | |
| <template x-for="m in grouped().matched" :key="'m_'+m"> | |
| <div class="model-search-item matched" @click="$store.onboarding.config.utility_model.name = m; open = false;" x-text="m"></div> | |
| </template> | |
| <div class="model-search-separator" x-show="grouped().matched.length > 0 && grouped().rest.length > 0"></div> | |
| <template x-for="m in grouped().rest" :key="'r_'+m"> | |
| <div class="model-search-item" @click="$store.onboarding.config.utility_model.name = m; open = false;" x-text="m"></div> | |
| </template> | |
| </div> | |
| <div class="model-search-results" x-show="open && results.length === 0 && !searching"> | |
| <div class="model-search-item disabled">No models found</div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- API Key --> | |
| <div class="field"> | |
| <div class="field-label"> | |
| <div class="field-title">API key</div> | |
| </div> | |
| <div class="field-control relative-container" x-data="{ showKey: false }"> | |
| <input :type="showKey ? 'text' : 'password'" | |
| x-model="$store.modelConfig.apiKeyValues[$store.onboarding.config.utility_model.provider]" | |
| :placeholder="$store.modelConfig.apiKeyStatus[$store.onboarding.config.utility_model.provider] ? '••••••••••••' : ''" | |
| autocomplete="off" | |
| class="input-with-icon" | |
| @input="$store.modelConfig.touchApiKey($store.onboarding.config.utility_model.provider)" /> | |
| <span class="material-symbols-outlined eye-toggle" | |
| @click=" | |
| showKey = !showKey; | |
| const prov = $store.onboarding.config.utility_model.provider; | |
| if (showKey && !$store.modelConfig.apiKeyValues[prov] && $store.modelConfig.apiKeyStatus[prov]) { | |
| $store.modelConfig.revealApiKey(prov).then(v => { if (v) $store.modelConfig.apiKeyValues[prov] = v; }); | |
| } | |
| " | |
| x-text="showKey ? 'visibility' : 'visibility_off'"></span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Step 3: Success --> | |
| <div x-show="$store.onboarding.step === 3" class="onboarding-success"> | |
| <div class="material-symbols-outlined onboarding-success-icon">check_circle</div> | |
| <div class="onboarding-welcome-title">Ready to chat!</div> | |
| <div class="onboarding-welcome-text onboarding-success-text"> | |
| Your models are configured. You can change these anytime in Settings. | |
| </div> | |
| </div> | |
| <div class="onboarding-advanced-link" x-show="$store.onboarding.step < 3"> | |
| <a href="#" @click.prevent="$store.onboarding.openAdvancedSettings()"> | |
| Advanced Settings <span class="material-symbols-outlined onboarding-advanced-link-icon">arrow_drop_down</span> | |
| </a> | |
| </div> | |
| </div> | |
| </template> | |
| </div> | |
| </div> | |
| <div class="modal-footer" data-modal-footer> | |
| <div class="onboarding-footer-left"> | |
| <button class="btn btn-cancel" @click="window.closeModal()" :disabled="$store.onboarding.loading">Cancel</button> | |
| </div> | |
| <div class="onboarding-footer-right"> | |
| <button class="btn" x-show="$store.onboarding.step > 1" @click="$store.onboarding.prev()" :disabled="$store.onboarding.loading"> | |
| Back | |
| </button> | |
| <button class="btn btn-ok" x-show="$store.onboarding.step < 3" @click="$store.onboarding.next()" :disabled="$store.onboarding.loading"> | |
| Next <span class="material-symbols-outlined onboarding-icon-right">arrow_forward</span> | |
| </button> | |
| <button class="btn btn-ok" x-show="$store.onboarding.step === 3" @click="$store.onboarding.finish()" :disabled="$store.onboarding.loading"> | |
| Start Chatting | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </template> | |
| </div> | |
| </body> | |
| </html> |