mirror of
https://github.com/m5stack/ESP-Claw.git
synced 2026-05-20 11:51:49 -07:00
524 lines
32 KiB
HTML
524 lines
32 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title data-i18n-title="docTitle">ESP-Claw Settings</title>
|
|
<link rel="stylesheet" href="/styles.css">
|
|
<script type="importmap">
|
|
{
|
|
"imports": {
|
|
"lean-qr": "/lean-qr.min.mjs"
|
|
}
|
|
}
|
|
</script>
|
|
<script type="module">
|
|
import { generate } from "lean-qr";
|
|
|
|
window.renderWechatQr = (canvas, content) => {
|
|
if (!canvas || !content) {
|
|
return;
|
|
}
|
|
const code = generate(content);
|
|
code.toCanvas(canvas);
|
|
};
|
|
</script>
|
|
</head>
|
|
|
|
<body>
|
|
<div class="site-container">
|
|
|
|
<!-- ── Navigation ── -->
|
|
<nav>
|
|
<a class="nav-brand" href="/">
|
|
<svg class="nav-logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 686.46 191.26">
|
|
<path fill="currentColor"
|
|
d="M274.61 59.98v13.2h-37.7v15.3h34.6v12.2h-34.6v17.5h38.5v13.2h-54.2v-71.4zm23.4 53.8q1.305 2.505 3.45 4.05c1.43 1.03 3.12 1.8 5.05 2.3q2.895.75 6 .75c1.4 0 2.9-.12 4.5-.35q2.4-.345 4.5-1.35c1.4-.67 2.57-1.58 3.5-2.75s1.4-2.65 1.4-4.45c0-1.93-.62-3.5-1.85-4.7s-2.85-2.2-4.85-3-4.27-1.5-6.8-2.1-5.1-1.27-7.7-2c-2.67-.67-5.27-1.48-7.8-2.45s-4.8-2.22-6.8-3.75-3.62-3.45-4.85-5.75-1.85-5.08-1.85-8.35c0-3.67.78-6.85 2.35-9.55q2.355-4.05 6.15-6.75c2.53-1.8 5.4-3.13 8.6-4s6.4-1.3 9.6-1.3c3.73 0 7.32.42 10.75 1.25q5.145 1.245 9.15 4.05c2.67 1.87 4.78 4.25 6.35 7.15s2.35 6.42 2.35 10.55h-15.2c-.13-2.13-.58-3.9-1.35-5.3s-1.78-2.5-3.05-3.3-2.72-1.37-4.35-1.7-3.42-.5-5.35-.5c-1.27 0-2.53.13-3.8.4s-2.42.73-3.45 1.4-1.88 1.5-2.55 2.5-1 2.27-1 3.8c0 1.4.27 2.53.8 3.4q.795 1.305 3.15 2.4c1.57.73 3.73 1.47 6.5 2.2s6.38 1.67 10.85 2.8c1.33.27 3.18.75 5.55 1.45s4.72 1.82 7.05 3.35 4.35 3.58 6.05 6.15 2.55 5.85 2.55 9.85c0 3.27-.63 6.3-1.9 9.1s-3.15 5.22-5.65 7.25c-2.5 2.04-5.6 3.62-9.3 4.75s-7.98 1.7-12.85 1.7c-3.93 0-7.75-.48-11.45-1.45-3.7-.96-6.97-2.48-9.8-4.55s-5.08-4.7-6.75-7.9q-2.505-4.8-2.4-11.4h15.2c0 2.4.43 4.43 1.3 6.1m85.1-53.8q6.705 0 11.4 1.95c3.13 1.3 5.68 3.02 7.65 5.15 1.96 2.13 3.4 4.57 4.3 7.3s1.35 5.57 1.35 8.5-.45 5.68-1.35 8.45-2.33 5.22-4.3 7.35-4.52 3.85-7.65 5.15q-4.695 1.95-11.4 1.95h-16.5v25.6h-15.7v-71.4zm-4.3 33.6c1.8 0 3.53-.13 5.2-.4s3.13-.78 4.4-1.55 2.28-1.85 3.05-3.25 1.15-3.23 1.15-5.5-.38-4.1-1.15-5.5-1.78-2.48-3.05-3.25-2.73-1.28-4.4-1.55-3.4-.4-5.2-.4h-12.2v21.4zm36.06 10.09h24.74v9.18h-24.74z" />
|
|
<path fill="#e8362d"
|
|
d="M487.08 78.23c-3.09-2.2-6.76-3.29-11.01-3.29-3.63 0-6.73.69-9.31 2.08s-4.7 3.25-6.38 5.59c-1.67 2.34-2.9 5.01-3.7 8.02a36.7 36.7 0 0 0-1.19 9.36c0 3.53.4 6.86 1.19 10.01s2.03 5.9 3.7 8.24 3.81 4.21 6.42 5.59c2.61 1.39 5.72 2.08 9.35 2.08 2.66 0 5.03-.45 7.1-1.34s3.85-2.14 5.36-3.73c1.5-1.59 2.68-3.48 3.53-5.68q1.275-3.3 1.53-7.11h8.08c-.79 7.8-3.43 13.87-7.9 18.21-4.48 4.33-10.6 6.5-18.36 6.5-4.7 0-8.81-.82-12.33-2.47-3.51-1.65-6.43-3.92-8.75-6.81s-4.07-6.3-5.23-10.23q-1.74-5.895-1.74-12.66c0-6.765.62-8.74 1.87-12.7s3.07-7.41 5.48-10.36 5.41-5.27 9.01-6.98c3.6-1.7 7.72-2.56 12.37-2.56 3.17 0 6.18.43 9.01 1.3s5.36 2.14 7.57 3.81c2.21 1.68 4.05 3.77 5.53 6.29 1.47 2.51 2.44 5.42 2.89 8.71h-8.08c-.91-4.39-2.9-7.69-5.99-9.88zm33.87-8.76v54.97h32.13v6.94h-40.21v-61.9h8.08zm65.71 0 23.71 61.9h-8.92l-6.63-18.64h-25.67l-6.8 18.64h-8.25l23.63-61.9h8.92zm5.61 36.33-10.12-28.87h-.17l-10.29 28.87h20.57zm69.53 25.58-13.86-52.02h-.17l-14.02 52.02h-8.42l-15.56-61.9h8.25l11.9 51.5h.17l13.6-51.5h8.76l13.43 51.5h.17l12.33-51.5h8.08l-16.24 61.9zM165.38 113v.06l-.04.54v.04c-1.29 20.81-10.26 39.52-24.09 53.36-15 15-35.69 24.26-58.57 24.26-9.69 0-18.97-1.66-27.6-4.71C24.43 175.72 2.07 147.36.03 113.53c-.01-.15-.03-.32-.03-.47 0-1.27.52-2.42 1.36-3.26s1.98-1.36 3.26-1.36h156.15c1.29 0 2.43.52 3.27 1.36.82.82 1.34 1.96 1.36 3.2zM55.07 37.15v52.87c0 1.27-.52 2.42-1.36 3.26-.82.84-1.98 1.36-3.26 1.36H6.65c-2.54 0-4.61-2.07-4.61-4.61 0-.31.03-.6.08-.88l.13-.49a82.4 82.4 0 0 1 21.86-38.77c7.03-7.03 15.3-12.8 24.47-16.95.03-.01.04-.01.07-.03.54-.24 1.16-.36 1.8-.36 1.27 0 2.43.52 3.26 1.36.84.82 1.36 1.98 1.36 3.26zm108.26 52.87c0 1.27-.52 2.42-1.36 3.26s-2 1.36-3.27 1.36H87.28c-2.54 0-4.61-2.07-4.61-4.61V4.61c0-1.27.52-2.43 1.36-3.27a4.645 4.645 0 0 1 5.06-.98c.14.06.27.13.41.2 14.45 6.65 27.52 15.83 38.64 26.96 15.44 15.44 27.14 34.65 33.58 56.13.03.06.04.13.06.18.46 1.5.88 3.01 1.27 4.53.03.1.06.2.07.31.01.03.01.06.03.08.03.13.07.25.08.38.07.29.1.6.1.91z" />
|
|
</svg>
|
|
</a>
|
|
<div class="nav-right">
|
|
<details class="lang-switcher" id="lang-switcher">
|
|
<summary>
|
|
<svg class="lang-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
|
stroke-linecap="round" stroke-linejoin="round">
|
|
<circle cx="12" cy="12" r="10" />
|
|
<path d="M2 12h20" />
|
|
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
|
|
</svg>
|
|
<span id="current-locale-label">English</span>
|
|
<svg class="lang-chevron" width="10" height="10" viewBox="0 0 10 10" fill="none">
|
|
<path d="M2.5 4L5 6.5L7.5 4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"
|
|
stroke-linejoin="round" />
|
|
</svg>
|
|
</summary>
|
|
<ul class="lang-switcher-menu" id="locale-menu"></ul>
|
|
</details>
|
|
</div>
|
|
</nav>
|
|
|
|
<!-- ── Page Header ── -->
|
|
<div class="page-header">
|
|
<h1 data-i18n="pageTitle">Device Settings</h1>
|
|
<p data-i18n="pageSubtitle">Configure credentials, runtime settings, and manage FATFS storage on your ESP device.
|
|
</p>
|
|
</div>
|
|
|
|
<main>
|
|
<!-- ── Status Bar ── -->
|
|
<div class="status-bar" id="status-bar">
|
|
<div class="status-info">
|
|
<span class="status-badge offline" id="status-badge">
|
|
<span class="status-dot"></span>
|
|
<span id="status-text"></span>
|
|
</span>
|
|
<span class="status-meta-sep" id="meta-sep-ip" style="display:none">·</span>
|
|
<span class="status-meta-item" id="info-ip" style="display:none"></span>
|
|
<span class="status-meta-sep" id="meta-sep-storage" style="display:none">·</span>
|
|
<span class="status-meta-item" id="info-storage" style="display:none"></span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── Main Content Card ── -->
|
|
<div class="main-content">
|
|
|
|
<!-- Tabs -->
|
|
<div class="tabs">
|
|
<button type="button" class="tab-btn active" data-tab="config" data-i18n="tabConfig">Configuration</button>
|
|
<button type="button" class="tab-btn" data-tab="memory" data-i18n="tabMemory">Memory</button>
|
|
<button type="button" class="tab-btn" data-tab="files" data-i18n="tabFiles">File Manager</button>
|
|
</div>
|
|
|
|
<!-- ═══ CONFIG TAB ═══ -->
|
|
<div class="tab-panel active" id="tab-config">
|
|
<div class="config-banner-wrap">
|
|
<div id="configBanner" class="banner"></div>
|
|
</div>
|
|
|
|
<!-- Wi-Fi Settings (default open) -->
|
|
<details class="config-section" id="section-wifi" open>
|
|
<summary class="section-header">
|
|
<span class="section-title" data-i18n="sectionWifi">Wi-Fi Settings</span>
|
|
<svg class="section-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9l6 6 6-6"/></svg>
|
|
</summary>
|
|
<div class="section-body">
|
|
<div class="form-grid">
|
|
<div class="form-group">
|
|
<label for="wifi_ssid" data-i18n="wifiSsid">Wi-Fi SSID</label>
|
|
<input type="text" id="wifi_ssid" name="wifi_ssid" autocomplete="off">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="wifi_password" data-i18n="wifiPassword">Wi-Fi Password</label>
|
|
<input type="password" id="wifi_password" name="wifi_password" autocomplete="off">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</details>
|
|
|
|
<!-- LLM Settings (default open) -->
|
|
<details class="config-section" id="section-llm" open>
|
|
<summary class="section-header">
|
|
<span class="section-title" data-i18n="sectionLlm">LLM Settings</span>
|
|
<svg class="section-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9l6 6 6-6"/></svg>
|
|
</summary>
|
|
<div class="section-body">
|
|
<div class="form-grid">
|
|
<div class="form-group">
|
|
<label for="llm_provider_preset" data-i18n="llmProvider">LLM Provider</label>
|
|
<select id="llm_provider_preset" name="llm_provider_preset">
|
|
<option value="openai" data-i18n-option="llmProviderOpenai">OpenAI</option>
|
|
<option value="qwen" data-i18n-option="llmProviderQwen">Qwen Compatible</option>
|
|
<option value="deepseek" data-i18n-option="llmProviderDeepSeek">DeepSeek</option>
|
|
<option value="anthropic" data-i18n-option="llmProviderAnthropic">Anthropic</option>
|
|
<option value="custom" data-i18n-option="llmProviderCustom">Custom</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="llm_api_key" data-i18n="llmApiKey">API Key</label>
|
|
<input type="password" id="llm_api_key" name="llm_api_key" autocomplete="off">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="llm_model" data-i18n="llmModel">Model</label>
|
|
<input type="text" id="llm_model" name="llm_model">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="llm_timeout_ms" data-i18n="llmTimeout">Timeout (ms)</label>
|
|
<input type="text" id="llm_timeout_ms" name="llm_timeout_ms" data-i18n-placeholder="llmTimeoutPlaceholder">
|
|
</div>
|
|
</div>
|
|
<details class="advanced-section" style="margin-top: 0.85rem;">
|
|
<summary>
|
|
<span data-i18n="llmAdvanced">LLM Advanced Options</span>
|
|
<svg class="adv-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9l6 6 6-6"/></svg>
|
|
</summary>
|
|
<div class="advanced-body">
|
|
<div class="form-grid">
|
|
<div class="form-group">
|
|
<label for="llm_backend_type" data-i18n="llmBackend">Backend Type</label>
|
|
<input type="text" id="llm_backend_type" name="llm_backend_type" data-i18n-placeholder="llmBackendPlaceholder">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="llm_profile" data-i18n="llmProfile">Profile</label>
|
|
<input type="text" id="llm_profile" name="llm_profile" data-i18n-placeholder="llmProfilePlaceholder">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="llm_base_url" data-i18n="llmBaseUrl">Base URL</label>
|
|
<input type="url" id="llm_base_url" name="llm_base_url" data-i18n-placeholder="llmBaseUrlPlaceholder">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="llm_auth_type" data-i18n="llmAuthType">Auth Type</label>
|
|
<input type="text" id="llm_auth_type" name="llm_auth_type" data-i18n-placeholder="llmAuthTypePlaceholder">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</details>
|
|
</div>
|
|
</details>
|
|
|
|
<!-- IM Settings (default open) -->
|
|
<details class="config-section" id="section-im" open>
|
|
<summary class="section-header">
|
|
<span class="section-title" data-i18n="sectionIm">Instant Messaging (IM)</span>
|
|
<svg class="section-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9l6 6 6-6"/></svg>
|
|
</summary>
|
|
<div class="section-body">
|
|
<!-- WeChat (first, inputs collapsed by default) -->
|
|
<details class="advanced-section" id="im-wechat-section">
|
|
<summary>
|
|
<span data-i18n="imWechatTitle">WeChat</span>
|
|
<svg class="adv-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9l6 6 6-6"/></svg>
|
|
</summary>
|
|
<div class="advanced-body">
|
|
<div class="wechat-login-panel">
|
|
<div class="wechat-login-header">
|
|
<span class="wechat-login-title" data-i18n="wechatLoginDesc">QR Code Sign-in</span>
|
|
<div class="wechat-login-actions">
|
|
<button type="button" class="btn-secondary btn-sm" id="wechatLoginStartButton" data-i18n="wechatLoginGenerate">Generate QR</button>
|
|
<button type="button" class="btn-secondary btn-sm" id="wechatLoginCancelButton" data-i18n="wechatLoginCancel">Cancel</button>
|
|
</div>
|
|
</div>
|
|
<div id="wechatLoginBanner" class="banner"></div>
|
|
<p id="wechatLoginMeta" class="section-note" data-i18n="wechatLoginStatus">Status: idle</p>
|
|
<canvas id="wechatLoginQr" class="wechat-qr hidden" aria-label="WeChat QR code"></canvas>
|
|
<p><a id="wechatLoginQrLink" class="wechat-login-link hidden" target="_blank" rel="noopener noreferrer" data-i18n="wechatLoginOpenLink">Open login link</a></p>
|
|
<div class="form-grid" style="margin-top: 0.85rem;">
|
|
<div class="form-group">
|
|
<label for="wechat_token" data-i18n="wechatToken">WeChat Token</label>
|
|
<input type="password" id="wechat_token" name="wechat_token">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="wechat_account_id" data-i18n="wechatAccountId">WeChat Account ID</label>
|
|
<input type="text" id="wechat_account_id" name="wechat_account_id" data-i18n-placeholder="wechatAccountIdPlaceholder">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="wechat_base_url" data-i18n="wechatBaseUrl">WeChat Base URL</label>
|
|
<input type="text" id="wechat_base_url" name="wechat_base_url" data-i18n-placeholder="wechatBaseUrlPlaceholder">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="wechat_cdn_base_url" data-i18n="wechatCdnBaseUrl">WeChat CDN Base URL</label>
|
|
<input type="text" id="wechat_cdn_base_url" name="wechat_cdn_base_url" data-i18n-placeholder="wechatCdnBaseUrlPlaceholder">
|
|
</div>
|
|
</div>
|
|
<p class="wechat-login-note" data-i18n="wechatLoginNote">Generate a QR code here to obtain wechat_token automatically. The token is saved to NVS after confirmation. Restart the device to apply the new login.</p>
|
|
</div>
|
|
</div>
|
|
</details>
|
|
|
|
<!-- QQ -->
|
|
<details class="advanced-section">
|
|
<summary>
|
|
<span>QQ</span>
|
|
<svg class="adv-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9l6 6 6-6"/></svg>
|
|
</summary>
|
|
<div class="advanced-body">
|
|
<div class="form-grid">
|
|
<div class="form-group">
|
|
<label for="qq_app_id" data-i18n="qqAppId">QQ App ID</label>
|
|
<input type="text" id="qq_app_id" name="qq_app_id">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="qq_app_secret" data-i18n="qqAppSecret">QQ App Secret</label>
|
|
<input type="password" id="qq_app_secret" name="qq_app_secret">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</details>
|
|
|
|
<!-- Feishu -->
|
|
<details class="advanced-section">
|
|
<summary>
|
|
<span data-i18n="imFeishuTitle">Feishu</span>
|
|
<svg class="adv-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9l6 6 6-6"/></svg>
|
|
</summary>
|
|
<div class="advanced-body">
|
|
<div class="form-grid">
|
|
<div class="form-group">
|
|
<label for="feishu_app_id" data-i18n="feishuAppId">Feishu App ID</label>
|
|
<input type="text" id="feishu_app_id" name="feishu_app_id">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="feishu_app_secret" data-i18n="feishuAppSecret">Feishu App Secret</label>
|
|
<input type="password" id="feishu_app_secret" name="feishu_app_secret">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</details>
|
|
|
|
<!-- Telegram -->
|
|
<details class="advanced-section">
|
|
<summary>
|
|
<span>Telegram</span>
|
|
<svg class="adv-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9l6 6 6-6"/></svg>
|
|
</summary>
|
|
<div class="advanced-body">
|
|
<div class="form-grid">
|
|
<div class="form-group full">
|
|
<label for="tg_bot_token" data-i18n="tgBotToken">Telegram Bot Token</label>
|
|
<input type="password" id="tg_bot_token" name="tg_bot_token">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</details>
|
|
</div>
|
|
</details>
|
|
|
|
<!-- Search Settings (default open) -->
|
|
<details class="config-section" id="section-search" open>
|
|
<summary class="section-header">
|
|
<span class="section-title" data-i18n="sectionSearch">Search (Optional)</span>
|
|
<svg class="section-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9l6 6 6-6"/></svg>
|
|
</summary>
|
|
<div class="section-body">
|
|
<p class="section-note" data-i18n="searchNote">If set, ESP-Claw can search online. Tip: Built-in weather skill also relies on online search.</p>
|
|
<div class="form-grid">
|
|
<div class="form-group">
|
|
<label for="search_brave_key" data-i18n="searchBraveKey">Brave Search API Key</label>
|
|
<input type="password" id="search_brave_key" name="search_brave_key" autocomplete="off">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="search_tavily_key" data-i18n="searchTavilyKey">Tavily API Key</label>
|
|
<input type="password" id="search_tavily_key" name="search_tavily_key" autocomplete="off">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</details>
|
|
|
|
<!-- Capability Groups (default open) -->
|
|
<details class="config-section" id="section-capabilities" open>
|
|
<summary class="section-header">
|
|
<span class="section-title" data-i18n="sectionCapabilities">Capability Groups</span>
|
|
<svg class="section-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9l6 6 6-6"/></svg>
|
|
</summary>
|
|
<div class="section-body">
|
|
<div class="capabilities-header">
|
|
<label data-i18n="enabledCapGroups">Enabled Capability Groups</label>
|
|
<div class="capabilities-actions">
|
|
<button type="button" class="btn-secondary btn-sm" id="capabilitySelectAllButton" data-i18n="capabilitySelectAll">Select All</button>
|
|
<button type="button" class="btn-secondary btn-sm" id="capabilityClearAllButton" data-i18n="capabilityClearAll">Clear All</button>
|
|
<button type="button" class="btn-secondary btn-sm" id="capabilityLlmSelectDefaultsButton" data-i18n="capabilityLlmDefaults">LLM Defaults</button>
|
|
<button type="button" class="btn-secondary btn-sm" id="capabilityLlmClearAllButton" data-i18n="capabilityLlmClear">Clear LLM</button>
|
|
</div>
|
|
</div>
|
|
<div id="capabilitySelectionSummary" class="section-note">Loading capabilities...</div>
|
|
<div id="capabilityCheckboxList" class="capability-list"></div>
|
|
<small class="form-hint" data-i18n="enabledCapGroupsHelp">Choose which compiled capability groups can be used after boot, and which enabled groups stay visible to the LLM. Changes take effect after restart.</small>
|
|
|
|
<div class="capabilities-header" style="margin-top: 1rem;">
|
|
<label data-i18n="enabledLuaModules">Enabled Lua Modules</label>
|
|
<div class="capabilities-actions">
|
|
<button type="button" class="btn-secondary btn-sm" id="luaModuleSelectAllButton" data-i18n="luaModuleSelectAll">Select All</button>
|
|
<button type="button" class="btn-secondary btn-sm" id="luaModuleClearAllButton" data-i18n="luaModuleClearAll">Clear All</button>
|
|
</div>
|
|
</div>
|
|
<div id="luaModuleSelectionSummary" class="section-note">Loading Lua modules...</div>
|
|
<div id="luaModuleCapabilityNote" class="section-note hidden" data-i18n="luaModuleCapabilityRequired">Enable the cap_lua capability group above before Lua module settings can take effect.</div>
|
|
<div id="luaModuleCheckboxList" class="capability-list"></div>
|
|
<small class="form-hint" data-i18n="enabledLuaModulesHelp">Choose which compiled Lua modules are registered when cap_lua starts. Changes take effect after restart.</small>
|
|
</div>
|
|
</details>
|
|
|
|
<!-- Advanced Settings (default collapsed) -->
|
|
<details class="config-section" id="section-advanced">
|
|
<summary class="section-header">
|
|
<span class="section-title" data-i18n="sectionAdvanced">Advanced Settings</span>
|
|
<svg class="section-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9l6 6 6-6"/></svg>
|
|
</summary>
|
|
<div class="section-body">
|
|
<div class="form-grid">
|
|
<div class="form-group">
|
|
<label for="time_timezone" data-i18n="timezone">Timezone</label>
|
|
<input type="text" id="time_timezone" name="time_timezone" data-i18n-placeholder="timezonePlaceholder">
|
|
<small class="form-hint" data-i18n="timezoneHelp">POSIX TZ string only. Sign is inverted: Beijing (UTC+8) must be written as 'CST-8'.</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</details>
|
|
|
|
<!-- Save Actions -->
|
|
<div class="config-actions">
|
|
<button type="button" class="btn-primary" id="saveConfigButton" data-i18n="saveBtn">Save Changes</button>
|
|
<span class="config-footer-note" data-i18n="configNote">Changes are stored in NVS. Choose an LLM provider first, then fill the shared API key and model fields. Restart the device after updating Wi-Fi, Feishu, core LLM, capability, or Lua module settings.</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ═══ MEMORY TAB ═══ -->
|
|
<div class="tab-panel" id="tab-memory">
|
|
<div class="memory-panel">
|
|
<div id="memoryBanner" class="banner"></div>
|
|
|
|
<div class="memory-header">
|
|
<div>
|
|
<h2 class="memory-title" data-i18n="memoryTitle">Memory Files</h2>
|
|
<p class="memory-note" data-i18n="memoryDescription">Manage the device memory files.</p>
|
|
</div>
|
|
<div class="memory-toolbar">
|
|
<button type="button" class="btn-secondary btn-sm" id="refreshMemoryButton" data-i18n="memoryRefreshAll">Refresh All</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="memory-grid">
|
|
<section class="memory-card">
|
|
<div class="memory-card-header">
|
|
<h3 class="memory-card-title" data-i18n="memoryCardLongTerm">Long-term Memory</h3>
|
|
<div class="memory-card-actions">
|
|
<button type="button" class="btn-secondary btn-sm memory-refresh-btn" data-path="/memory/memory.md" data-i18n="memoryRefresh">Refresh</button>
|
|
</div>
|
|
</div>
|
|
<div id="memoryMissingState" class="memory-inline-empty hidden">
|
|
<p class="memory-note" data-i18n="memoryMissing">Long-term memory file does not exist yet.</p>
|
|
</div>
|
|
<textarea id="memoryEditor" class="memory-editor hidden" spellcheck="false"></textarea>
|
|
</section>
|
|
|
|
<section class="memory-card">
|
|
<div class="memory-card-header">
|
|
<h3 class="memory-card-title" data-i18n="memoryCardSoul">Soul</h3>
|
|
<div class="memory-card-actions">
|
|
<button type="button" class="btn-secondary btn-sm memory-refresh-btn" data-path="/memory/soul.md" data-i18n="memoryRefresh">Refresh</button>
|
|
<button type="button" class="btn-secondary btn-sm memory-edit-btn" data-path="/memory/soul.md" data-i18n="fileEdit">Edit</button>
|
|
</div>
|
|
</div>
|
|
<div id="soulMissingState" class="memory-inline-empty hidden">
|
|
<p class="memory-note" data-i18n="memoryAuxMissing">Memory file does not exist yet.</p>
|
|
</div>
|
|
<textarea id="soulEditor" class="memory-editor memory-editor-sm hidden" spellcheck="false"></textarea>
|
|
</section>
|
|
|
|
<section class="memory-card">
|
|
<div class="memory-card-header">
|
|
<h3 class="memory-card-title" data-i18n="memoryCardIdentity">Identity</h3>
|
|
<div class="memory-card-actions">
|
|
<button type="button" class="btn-secondary btn-sm memory-refresh-btn" data-path="/memory/identity.md" data-i18n="memoryRefresh">Refresh</button>
|
|
<button type="button" class="btn-secondary btn-sm memory-edit-btn" data-path="/memory/identity.md" data-i18n="fileEdit">Edit</button>
|
|
</div>
|
|
</div>
|
|
<div id="identityMissingState" class="memory-inline-empty hidden">
|
|
<p class="memory-note" data-i18n="memoryAuxMissing">Memory file does not exist yet.</p>
|
|
</div>
|
|
<textarea id="identityEditor" class="memory-editor memory-editor-sm hidden" spellcheck="false"></textarea>
|
|
</section>
|
|
|
|
<section class="memory-card">
|
|
<div class="memory-card-header">
|
|
<h3 class="memory-card-title" data-i18n="memoryCardUserInfo">User Info</h3>
|
|
<div class="memory-card-actions">
|
|
<button type="button" class="btn-secondary btn-sm memory-refresh-btn" data-path="/memory/user.md" data-i18n="memoryRefresh">Refresh</button>
|
|
<button type="button" class="btn-secondary btn-sm memory-edit-btn" data-path="/memory/user.md" data-i18n="fileEdit">Edit</button>
|
|
</div>
|
|
</div>
|
|
<div id="userMissingState" class="memory-inline-empty hidden">
|
|
<p class="memory-note" data-i18n="memoryAuxMissing">Memory file does not exist yet.</p>
|
|
</div>
|
|
<textarea id="userEditor" class="memory-editor memory-editor-sm hidden" spellcheck="false"></textarea>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ═══ FILES TAB ═══ -->
|
|
<div class="tab-panel" id="tab-files">
|
|
<div class="file-panel">
|
|
<div id="fileBanner" class="banner"></div>
|
|
|
|
<div class="file-toolbar">
|
|
<button type="button" class="btn-secondary btn-sm" id="upDirButton" data-i18n="fileUpDir">Up One
|
|
Level</button>
|
|
<code class="file-path" id="currentPath">/</code>
|
|
<div class="file-toolbar-right">
|
|
<button type="button" class="btn-secondary btn-sm" id="devModeButton"
|
|
data-i18n="fileDevMode">Dev Mode</button>
|
|
<button type="button" class="btn-secondary btn-sm" id="refreshFilesButton"
|
|
data-i18n="fileRefresh">Refresh</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="file-create-row" id="fileCreateRow">
|
|
<input type="text" id="newFolderInput" data-i18n-placeholder="fileNewFolder">
|
|
<button type="button" class="btn-secondary btn-sm" id="createFolderButton"
|
|
data-i18n="fileCreateFolder">Create Folder</button>
|
|
</div>
|
|
|
|
<div class="file-upload-row" id="fileUploadRow">
|
|
<input type="text" id="uploadPathInput" data-i18n-placeholder="fileUploadPath">
|
|
<div class="file-picker">
|
|
<input type="file" id="uploadFileInput">
|
|
<button type="button" class="btn-ghost btn-sm" id="chooseFileButton" data-i18n="fileChoose">Choose
|
|
File</button>
|
|
<span class="file-name" id="selectedFileName" data-i18n="fileNoFileSelected">No file selected</span>
|
|
</div>
|
|
<button type="button" class="btn-primary btn-sm" id="uploadButton" data-i18n="fileUpload">Upload</button>
|
|
</div>
|
|
|
|
<div class="table-wrap">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th data-i18n="fileColName">Name</th>
|
|
<th data-i18n="fileColType">Type</th>
|
|
<th data-i18n="fileColSize">Size</th>
|
|
<th data-i18n="fileColActions">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="fileTableBody"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div><!-- /.main-content -->
|
|
</main>
|
|
|
|
<div id="fileEditorModal" class="modal-overlay hidden">
|
|
<div class="modal-dialog">
|
|
<div class="modal-header">
|
|
<div>
|
|
<h2 class="modal-title" data-i18n="fileEditorTitle">Edit File</h2>
|
|
<code class="file-path" id="fileEditorPath">/</code>
|
|
</div>
|
|
<button type="button" class="btn-secondary btn-sm" id="closeFileEditorButton" data-i18n="fileEditorClose">Close</button>
|
|
</div>
|
|
<div id="fileEditorBanner" class="banner"></div>
|
|
<textarea id="fileEditorTextarea" class="modal-editor" spellcheck="false"></textarea>
|
|
<div class="modal-actions">
|
|
<button type="button" class="btn-secondary btn-sm" id="refreshFileEditorButton" data-i18n="fileEditorRefresh">Refresh</button>
|
|
<button type="button" class="btn-primary btn-sm" id="saveFileEditorButton" data-i18n="fileEditorSave">Save</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── Footer ── -->
|
|
<footer>
|
|
<span class="footer-text">
|
|
Copyright © 2026 <a href="https://www.espressif.com" target="_blank" rel="noopener"><span
|
|
class="footer-highlight">Espressif Systems</span></a>
|
|
</span>
|
|
</footer>
|
|
|
|
</div><!-- /.site-container -->
|
|
<script src="/app.js"></script>
|
|
</body>
|
|
|
|
</html>
|