Files
hermes-glass-ui-personal/src/index.html

1140 lines
60 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<meta name="theme-color" content="#1a0f08">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="爱马仕">
<link rel="manifest" href="./manifest.webmanifest">
<link rel="icon" type="image/svg+xml" href="./icon.svg">
<link rel="apple-touch-icon" href="./icon.svg">
<title>爱马仕 · AI</title>
<link rel="stylesheet" href="./styles.css">
</head>
<body>
<!-- SVG 折射滤镜 -->
<svg style="position:absolute;width:0;height:0" aria-hidden="true">
<defs>
<filter id="glass-distortion" x="-20%" y="-20%" width="140%" height="140%">
<feTurbulence type="fractalNoise" baseFrequency="0.008 0.012" numOctaves="2" seed="17" result="turbulence"/>
<feGaussianBlur in="turbulence" stdDeviation="2" result="softMap"/>
<feSpecularLighting in="softMap" surfaceScale="5" specularConstant="1" specularExponent="100" lighting-color="white" result="specLight">
<fePointLight x="-200" y="-200" z="300"/>
</feSpecularLighting>
<feDisplacementMap in="SourceGraphic" in2="softMap" scale="80" xChannelSelector="R" yChannelSelector="G"/>
</filter>
</defs>
</svg>
<!-- 动态背景 -->
<div class="bg-aurora" aria-hidden="true">
<div class="blob blob-1"></div>
<div class="blob blob-2"></div>
<div class="blob blob-3"></div>
<div class="blob blob-4"></div>
</div>
<div class="app-shell">
<!-- 侧栏 -->
<aside class="sidebar">
<div class="side-brand">
<div class="hermes-tag" aria-label="爱马仕">
<span class="hermes-tag-top">HERMÈS</span>
<span class="hermes-tag-mid">PARIS</span>
</div>
<div class="side-brand-text">爱马仕 · AI</div>
</div>
<nav class="side-nav">
<button class="side-item active" data-tab="chat">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
<span>对话</span>
</button>
<button class="side-item" data-tab="research">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
<span>研究</span>
</button>
<button class="side-item" data-tab="agent">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2a5 5 0 0 0-5 5v2H5a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-9a2 2 0 0 0-2-2h-2V7a5 5 0 0 0-5-5z"/><circle cx="9" cy="14" r="1"/><circle cx="15" cy="14" r="1"/></svg>
<span>Agent</span>
</button>
<button class="side-item" data-tab="studio">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2L2 7v10l10 5 10-5V7L12 2z"/><path d="M2 7l10 5 10-5"/><path d="M12 12v10"/></svg>
<span>Skill Studio</span>
</button>
<button class="side-item" data-tab="cron">
<svg 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"/><polyline points="12 6 12 12 16 14"/></svg>
<span>定时任务</span>
</button>
<button class="side-item" data-tab="memory">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3 3 3 0 0 0 3-3V5a3 3 0 0 0-3-3z"/><path d="M6 10a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3 3 3 0 0 0 3-3v-2a3 3 0 0 0-3-3z"/><path d="M18 10a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3 3 3 0 0 0 3-3v-2a3 3 0 0 0-3-3z"/><path d="M12 18a3 3 0 0 0-3 3"/><path d="M12 18a3 3 0 0 1 3 3"/></svg>
<span>记忆</span>
</button>
<button class="side-item" data-tab="tools">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></svg>
<span>工具</span>
</button>
<button class="side-item" data-tab="runs">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>
<span>异步任务</span>
</button>
<button class="side-item" data-tab="help">
<svg 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="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
<span>帮助</span>
</button>
<button class="side-item" data-tab="dashboard">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="9"/><rect x="14" y="3" width="7" height="5"/><rect x="14" y="12" width="7" height="9"/><rect x="3" y="16" width="7" height="5"/></svg>
<span>仪表盘</span>
</button>
<button class="side-item" data-tab="settings">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09a1.65 1.65 0 0 0-1-1.51 1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
<span>设置</span>
</button>
</nav>
<div class="side-search">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
<input type="text" id="searchInput" placeholder="搜索对话 / 标签…">
<button class="search-clear" id="searchClear" onclick="clearSearch()" style="display:none">×</button>
</div>
<div class="side-history" id="sideHistory">
<div class="side-history-label">对话历史</div>
</div>
<!-- 重命名 / 标签 Modal -->
<div class="modal-mask" id="renameModal" onclick="if(event.target===this)closeRenameModal()">
<div class="modal">
<div class="modal-head">
<h3>编辑对话</h3>
<button class="modal-close" onclick="closeRenameModal()">×</button>
</div>
<div class="modal-body">
<div class="settings-field">
<label for="renameTitle">标题</label>
<input type="text" id="renameTitle" placeholder="对话标题" maxlength="80">
</div>
<div class="settings-field">
<label for="renameTags">标签 (逗号分隔)</label>
<input type="text" id="renameTags" placeholder="例如: 工作, 研究, 灵感">
<div class="settings-help">用标签快速分类,在搜索框里输入标签名可筛选。常用: 工作 · 研究 · 灵感 · 代码 · 写作 · 米乐</div>
</div>
</div>
<div class="modal-foot">
<button class="glass-btn-sm" onclick="closeRenameModal()">取消</button>
<button class="glass-btn-sm primary" onclick="saveRename()">保存</button>
</div>
</div>
</div>
<!-- Flow 编排管理 Modal -->
<div class="modal-mask" id="flowModal" onclick="if(event.target===this)closeFlowManager()">
<div class="modal modal-lg">
<div class="modal-head">
<h3>Skill 编排 · 技能组合预设</h3>
<button class="modal-close" onclick="closeFlowManager()">×</button>
</div>
<div class="modal-body">
<div class="flow-intro">
把常用的 Skill 组合打包成"编排",一键套用到任意智能体。技能按编排里的顺序应用,前面的优先级更高。
</div>
<div class="flow-toolbar">
<button class="glass-btn-sm primary" onclick="openFlowEdit()">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg>
新建编排
</button>
</div>
<div class="flow-list" id="flowList"></div>
</div>
</div>
</div>
<!-- Flow 编辑 Modal -->
<div class="modal-mask" id="flowEditModal" onclick="if(event.target===this)closeFlowEdit()">
<div class="modal modal-md">
<div class="modal-head">
<h3 id="flowEditTitle">新建编排</h3>
<button class="modal-close" onclick="closeFlowEdit()">×</button>
</div>
<div class="modal-body">
<div class="settings-field">
<label>图标</label>
<div class="emoji-input-wrap">
<div class="emoji-preview" id="flowEmojiPreview" onclick="openEmojiPicker('flow')">🧠</div>
<button type="button" class="glass-btn-sm" onclick="openEmojiPicker('flow')">选择图标</button>
<input type="hidden" id="flowEmoji" value="🧠">
</div>
</div>
<div class="settings-field">
<label>名称</label>
<input type="text" id="flowName" placeholder="例如: 研究型 / 开发型 / 小红书种草" maxlength="30">
</div>
<div class="settings-field">
<label>简介</label>
<input type="text" id="flowDesc" placeholder="这个编排适合什么场景" maxlength="60">
</div>
<div class="settings-field">
<label>技能组合 (上下拖动调整顺序,编号即优先级)</label>
<div class="flow-skill-picker" id="flowSkillPicker"></div>
</div>
</div>
<div class="modal-foot">
<button class="glass-btn-sm danger-btn" id="flowDeleteBtn" onclick="deleteFlow()" style="margin-right:auto;display:none">删除</button>
<button class="glass-btn-sm" onclick="closeFlowEdit()">取消</button>
<button class="glass-btn-sm primary" onclick="saveFlow()">保存</button>
</div>
</div>
</div>
<!-- 快速应用编排 Modal -->
<div class="modal-mask" id="flowApplyModal" onclick="if(event.target===this)closeFlowApply()">
<div class="modal modal-md">
<div class="modal-head">
<h3>应用编排到当前智能体</h3>
<button class="modal-close" onclick="closeFlowApply()">×</button>
</div>
<div class="modal-body">
<div class="flow-intro" style="margin-bottom:14px">选一个编排,它会替换当前智能体的 skill 选择并保持顺序。</div>
<div class="flow-apply-list" id="flowApplyList"></div>
</div>
</div>
</div>
<!-- 自定义 Skill Modal -->
<div class="modal-mask" id="skillModal" onclick="if(event.target===this)closeSkillModal()">
<div class="modal">
<div class="modal-head">
<h3 id="skillModalTitle">新建技能</h3>
<button class="modal-close" onclick="closeSkillModal()">×</button>
</div>
<div class="modal-body">
<div class="settings-field">
<label>图标</label>
<div class="emoji-input-wrap">
<div class="emoji-preview" id="skillEmojiPreview" onclick="openEmojiPicker('skill')"></div>
<button type="button" class="glass-btn-sm" onclick="openEmojiPicker('skill')">选择图标</button>
<input type="hidden" id="skillEmoji" value="✨">
</div>
</div>
<div class="settings-field">
<label>名称</label>
<input type="text" id="skillName" placeholder="例如: 电商文案腔调" maxlength="20">
</div>
<div class="settings-field">
<label>指令 Prompt</label>
<textarea id="skillPrompt" rows="5" placeholder="告诉智能体这个技能要做什么,例如:&#10;回答时使用小红书风格,多用 emoji、短句、分行,结尾带 2-3 个话题标签。"></textarea>
<div class="settings-help">这段话会在发消息时,自动拼到智能体的 system prompt 后面</div>
</div>
</div>
<div class="modal-foot">
<button class="glass-btn-sm" id="skillDeleteBtn" onclick="deleteCurrentSkill()" style="margin-right:auto;display:none">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/></svg>
删除
</button>
<button class="glass-btn-sm" onclick="closeSkillModal()">取消</button>
<button class="glass-btn-sm primary" onclick="saveCustomSkill()">保存</button>
</div>
</div>
</div>
<!-- 通用 Agent Picker (邀请 / 派分支) -->
<div class="modal-mask" id="agentPicker" onclick="if(event.target===this)closeAgentPicker()">
<div class="modal modal-md">
<div class="modal-head">
<h3 id="agentPickerTitle">选择智能体</h3>
<button class="modal-close" onclick="closeAgentPicker()">×</button>
</div>
<div class="modal-body">
<div class="agent-picker-list" id="agentPickerList"></div>
</div>
</div>
</div>
<!-- Emoji / 品牌图标 选择器 -->
<div class="modal-mask" id="emojiPicker" onclick="if(event.target===this)closeEmojiPicker()">
<div class="modal modal-md">
<div class="modal-head">
<h3>选择图标</h3>
<button class="modal-close" onclick="closeEmojiPicker()">×</button>
</div>
<div class="modal-body">
<div class="emoji-section-label">🏢 品牌</div>
<div class="emoji-grid brand-grid" id="brandGrid"></div>
<div class="emoji-section-label">😀 表情</div>
<div class="emoji-search-wrap">
<input type="text" id="emojiSearch" placeholder="搜索表情 (输入中文/英文关键词)">
</div>
<div class="emoji-grid" id="emojiGrid"></div>
</div>
</div>
</div>
<div class="side-bottom">
<button class="side-new" onclick="newChat()">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg>
<span>新对话</span>
</button>
<button class="theme-toggle" onclick="toggleTheme()" aria-label="切换明暗">
<svg class="icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"/></svg>
<svg class="icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
<span class="theme-label"></span>
</button>
<div class="side-status" id="sideStatus">
<span class="dot"></span>
<span id="statusText">连接中…</span>
</div>
</div>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- TAB: 对话 -->
<section class="tab-panel active" id="tab-chat">
<div class="chat-topbar">
<div class="chat-topbar-title" id="chatTitleText" onclick="renameCurrent()" title="点击编辑标题和标签">新对话</div>
<button class="chat-clear" onclick="renameCurrent()" title="编辑标题/标签"></button>
<button class="chat-agent-badge" id="chatAgentBtn" onclick="openAgentPickerForChat()" title="邀请智能体到当前对话">
<div class="ag-emoji">+</div>
<span>邀请智能体</span>
</button>
<div class="chat-model-pick">
<select id="modelPick">
<option value="gemini-3-pro-preview">Gemini 3 Pro</option>
<option value="gemini-2.5-pro">Gemini 2.5 Pro</option>
<option value="gemini-2.5-flash">Gemini 2.5 Flash</option>
</select>
</div>
<button class="chat-clear" id="clearBtn" title="清空对话">清空</button>
</div>
<div class="gpt-messages" id="chatMessages"></div>
<form class="gpt-input-wrap" id="chatForm" autocomplete="off">
<div class="pending-agent-bar" id="pendingAgentBar" style="display:none"></div>
<div class="gpt-input-box">
<button type="button" class="input-at" onclick="useAgentOnce()" title="本次消息 @ 一个智能体回答">
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-4 8"/></svg>
</button>
<textarea id="chatInput" placeholder="给爱马仕发消息…" rows="1" autofocus></textarea>
<button type="submit" class="send-btn" aria-label="发送">
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M13 5l7 7-7 7"/></svg>
</button>
</div>
<div class="gpt-footnote">⏎ 发送 · Shift+⏎ 换行 · @ 本次对话用指定智能体</div>
</form>
</section>
<!-- TAB: Agent -->
<section class="tab-panel" id="tab-agent">
<div class="panel-head">
<div class="panel-head-row">
<div>
<h2>智能体</h2>
<p>创建不同角色的 AI 助手,单独对话或组成集群并行协作。</p>
</div>
<div class="panel-head-actions">
<button class="glass-btn-sm primary" onclick="openAgentModal()">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg>
新建智能体
</button>
<button class="glass-btn-sm" onclick="openFlowManager()">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><circle cx="5" cy="6" r="2"/><circle cx="12" cy="6" r="2"/><circle cx="19" cy="6" r="2"/><path d="M7 6h3M14 6h3"/><path d="M5 8v10M12 8v10M19 8v10"/><path d="M5 18h14"/></svg>
Skill 编排
</button>
<button class="glass-btn-sm" onclick="openClusterMode()">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><circle cx="6" cy="6" r="2"/><circle cx="18" cy="6" r="2"/><circle cx="12" cy="18" r="2"/><path d="M7.4 7.4 11 16.6M16.6 7.4 13 16.6M8 6h8"/></svg>
集群对话
</button>
</div>
</div>
</div>
<div class="agent-grid" id="agentGrid"></div>
</section>
<!-- 新建/编辑智能体 Modal -->
<div class="modal-mask" id="agentModal" onclick="if(event.target===this)closeAgentModal()">
<div class="modal">
<div class="modal-head">
<h3 id="agentModalTitle">新建智能体</h3>
<button class="modal-close" onclick="closeAgentModal()">×</button>
</div>
<div class="modal-body">
<div class="setting-row">
<label>头像</label>
<div class="emoji-input-wrap">
<div class="emoji-preview" id="agentEmojiPreview" onclick="openEmojiPicker('agent')">🤖</div>
<button type="button" class="glass-btn-sm" onclick="openEmojiPicker('agent')">选择图标</button>
<input type="hidden" id="agentEmoji" value="🤖">
</div>
</div>
<div class="setting-row">
<label>名称</label>
<input type="text" id="agentName" placeholder="例如: 代码专家" maxlength="40">
</div>
<div class="setting-row">
<label>简介</label>
<input type="text" id="agentDesc" placeholder="一句话说明这个智能体擅长什么" maxlength="80">
</div>
<div class="setting-row">
<label>模型</label>
<select id="agentModel">
<option value="gemini-3-pro-preview">Gemini 3 Pro</option>
<option value="gemini-2.5-pro">Gemini 2.5 Pro</option>
<option value="gemini-2.5-flash">Gemini 2.5 Flash</option>
</select>
</div>
<div class="setting-row setting-row-full">
<label>角色设定 (System Prompt)</label>
<textarea id="agentPrompt" rows="5" placeholder="告诉爱马仕这个智能体的角色、语气、规则、能力边界..."></textarea>
</div>
<div class="setting-row setting-row-full">
<label>
技能 Skills
<span style="opacity:0.6;font-weight:500;font-size:11px">顺序决定执行优先级,可上下调整</span>
<button type="button" class="glass-btn-sm" style="margin-left:8px;padding:4px 10px;font-size:10px" onclick="openFlowApply()">应用编排 →</button>
</label>
<div class="skills-picker" id="skillsPicker"></div>
</div>
</div>
<div class="modal-foot">
<button class="glass-btn-sm" onclick="closeAgentModal()">取消</button>
<button class="glass-btn-sm primary" onclick="saveAgent()">保存</button>
</div>
</div>
</div>
<!-- 集群对话 Modal -->
<div class="modal-mask" id="clusterModal" onclick="if(event.target===this)closeClusterMode()">
<div class="modal modal-lg">
<div class="modal-head">
<h3>集群对话 — 多智能体并行响应</h3>
<button class="modal-close" onclick="closeClusterMode()">×</button>
</div>
<div class="modal-body">
<div class="cluster-pick">
<div class="cluster-label">选择要参与的智能体</div>
<div class="cluster-agent-list" id="clusterAgentList"></div>
</div>
<div class="cluster-input">
<textarea id="clusterPrompt" rows="3" placeholder="输入问题,所有选中的智能体将并行回答..."></textarea>
<button class="glass-btn-sm primary" onclick="runCluster()">并行执行</button>
</div>
<div class="cluster-results" id="clusterResults"></div>
</div>
</div>
</div>
<!-- TAB: Skill Studio (全屏编排) -->
<section class="tab-panel" id="tab-studio">
<div class="panel-head">
<div class="panel-head-row">
<div>
<h2>Skill Studio</h2>
<p id="studioSubtitle">技能库 + 编排画布 · 接入 Hermes 真实 skill(75 个,26 类) + 前端 prompt 技能</p>
</div>
<div class="panel-head-actions">
<button class="glass-btn-sm" onclick="studioNewFlow()">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg>
新建编排
</button>
<button class="glass-btn-sm" onclick="studioLoadFlow()">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 7h18M3 12h18M3 17h18"/></svg>
载入编排
</button>
<button class="glass-btn-sm primary" id="studioSaveBtn" onclick="studioSaveFlow()">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
保存编排
</button>
<button class="glass-btn-sm" onclick="studioApplyToAgent()">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2a5 5 0 0 0-5 5v2H5a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-9a2 2 0 0 0-2-2h-2V7a5 5 0 0 0-5-5z"/><path d="M12 13v5M9 15l3-3 3 3"/></svg>
应用到智能体
</button>
</div>
</div>
</div>
<div class="studio-grid">
<!-- 左: 技能库 -->
<div class="studio-col studio-library">
<div class="studio-col-head">
<div class="studio-tabs">
<button class="studio-tab active" data-lib="hermes">Hermes 库</button>
<button class="studio-tab" data-lib="builtin">Prompt 内建</button>
<button class="studio-tab" data-lib="custom">自定义</button>
</div>
<input type="text" class="studio-search" id="studioSearch" placeholder="搜索…">
</div>
<div class="studio-list" id="studioLibrary">
<div class="history-empty">加载中…</div>
</div>
</div>
<!-- 中: 编排画布 -->
<div class="studio-col studio-canvas-col">
<div class="studio-col-head">
<input type="text" class="studio-flow-title" id="studioFlowName" placeholder="编排名称(例如: 产品上线评估流)">
<div class="studio-flow-meta">
<div class="emoji-preview studio-flow-emoji" id="studioFlowEmojiPreview" onclick="openEmojiPicker('studioFlow')">🎯</div>
<input type="text" id="studioFlowDesc" placeholder="一句话描述场景" class="studio-desc">
<input type="hidden" id="studioFlowEmoji" value="🎯">
</div>
</div>
<div class="studio-canvas" id="studioCanvas">
<div class="studio-stage" data-stage="pre">
<div class="studio-stage-head">
<span class="studio-stage-badge">1</span>
<div class="studio-stage-name">前置 · Prep</div>
<div class="studio-stage-desc">理解目标 / 拆解任务 / 澄清</div>
</div>
<div class="studio-stage-slots" data-slots="pre"></div>
</div>
<div class="studio-stage-arrow"></div>
<div class="studio-stage" data-stage="exec">
<div class="studio-stage-head">
<span class="studio-stage-badge">2</span>
<div class="studio-stage-name">执行 · Execute</div>
<div class="studio-stage-desc">主任务 / 工具调用 / 生成内容</div>
</div>
<div class="studio-stage-slots" data-slots="exec"></div>
</div>
<div class="studio-stage-arrow"></div>
<div class="studio-stage" data-stage="post">
<div class="studio-stage-head">
<span class="studio-stage-badge">3</span>
<div class="studio-stage-name">收尾 · Review</div>
<div class="studio-stage-desc">自查 / 格式化输出 / 风险提示</div>
</div>
<div class="studio-stage-slots" data-slots="post"></div>
</div>
</div>
</div>
<!-- 右: 详情预览 -->
<div class="studio-col studio-preview-col">
<div class="studio-col-head">
<div class="studio-tab active" style="pointer-events:none">详情预览</div>
</div>
<div class="studio-preview" id="studioPreview">
<div class="history-empty">点左侧技能查看详情 · 点"+ 加入编排"放入画布</div>
</div>
</div>
</div>
</section>
<!-- TAB: 定时任务 (Cron,真实对接 Hermes /api/jobs) -->
<section class="tab-panel" id="tab-cron">
<div class="panel-head">
<div class="panel-head-row">
<div>
<h2>定时任务</h2>
<p>直接对接 Hermes 后端 <code>/api/jobs</code>,所有创建/更新/删除都是真实后端操作</p>
</div>
<div class="panel-head-actions">
<button class="glass-btn-sm" onclick="refreshCron()">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 0 1 15.5-6.3L21 8"/><path d="M21 3v5h-5"/><path d="M21 12a9 9 0 0 1-15.5 6.3L3 16"/><path d="M3 21v-5h5"/></svg>
刷新
</button>
<button class="glass-btn-sm primary" onclick="openCronModal()">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg>
新建任务
</button>
</div>
</div>
</div>
<div class="cron-list" id="cronList">
<div class="history-empty">加载中…</div>
</div>
</section>
<!-- Cron 创建/编辑 Modal -->
<div class="modal-mask" id="cronModal" onclick="if(event.target===this)closeCronModal()">
<div class="modal">
<div class="modal-head">
<h3 id="cronModalTitle">新建定时任务</h3>
<button class="modal-close" onclick="closeCronModal()">×</button>
</div>
<div class="modal-body">
<div class="settings-field">
<label>任务名称</label>
<input type="text" id="cronName" placeholder="例如: 每日晨报生成" maxlength="60">
</div>
<div class="settings-field">
<label>调度(支持 cron / 间隔 / 一次性 / 自然语言)</label>
<input type="text" id="cronSchedule" placeholder="0 9 * * * 或 every 2h 或 30m 或 2026-04-15T09:00" maxlength="80">
<div class="settings-help">
<b>Cron 5 段</b>: <code>0 9 * * *</code> 每天 9 点 · <code>*/15 * * * *</code> 每 15 分钟<br>
<b>间隔</b>: <code>every 30m</code> · <code>every 2h</code><br>
<b>一次性延迟</b>: <code>30m</code> · <code>2h</code><br>
<b>绝对时间</b>: <code>2026-04-15T14:00</code>
</div>
</div>
<div class="settings-field setting-row-full">
<label>Prompt(任务执行时发给 AI 的指令)</label>
<textarea id="cronPrompt" rows="4" placeholder="例如: 用 200 字概括今天科技圈最大的 3 件事"></textarea>
</div>
<div class="settings-field">
<label>投递目标</label>
<input type="text" id="cronDeliver" placeholder="local (默认) 或 telegram / discord / telegram:12345" maxlength="60">
<div class="settings-help"><code>local</code> 保存到 <code>~/.hermes/cron/output/</code> · <code>telegram</code> / <code>discord</code> 投到聊天频道</div>
</div>
<div class="settings-field toggle-field">
<div>
<label>启用</label>
<div class="settings-help">关闭后任务保留但不会触发</div>
</div>
<label class="switch"><input type="checkbox" id="cronEnabled" checked><span class="slider"></span></label>
</div>
</div>
<div class="modal-foot">
<button class="glass-btn-sm danger-btn" id="cronDeleteBtn" onclick="deleteCronJob()" style="margin-right:auto;display:none">删除</button>
<button class="glass-btn-sm" onclick="closeCronModal()">取消</button>
<button class="glass-btn-sm primary" onclick="saveCronJob()">保存</button>
</div>
</div>
</div>
<!-- TAB: 记忆 (真实 Hermes SOUL.md + sessions 快照) -->
<section class="tab-panel" id="tab-memory">
<div class="panel-head">
<div class="panel-head-row">
<div>
<h2>记忆</h2>
<p>Hermes 后端 <code>SOUL.md</code> + 会话历史快照 · 每分钟自动同步</p>
</div>
<div class="panel-head-actions">
<button class="glass-btn-sm" onclick="refreshMemory()">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 0 1 15.5-6.3L21 8"/><path d="M21 3v5h-5"/><path d="M21 12a9 9 0 0 1-15.5 6.3L3 16"/><path d="M3 21v-5h5"/></svg>
刷新
</button>
</div>
</div>
</div>
<div class="memory-grid">
<div class="memory-pane">
<div class="memory-pane-head">SOUL.md · 人格文档</div>
<pre class="memory-body" id="memorySoul">加载中…</pre>
</div>
<div class="memory-pane">
<div class="memory-pane-head">会话历史 · <span id="memorySessCount">0</span></div>
<div class="memory-sessions" id="memorySessions">加载中…</div>
</div>
</div>
</section>
<!-- TAB: 工具 (真实 Hermes tools list) -->
<section class="tab-panel" id="tab-tools">
<div class="panel-head">
<div class="panel-head-row">
<div>
<h2>工具集</h2>
<p>Hermes 已注册工具包 · <code>hermes tools list</code> 输出 · 每分钟同步</p>
</div>
<div class="panel-head-actions">
<button class="glass-btn-sm" onclick="refreshTools()">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 0 1 15.5-6.3L21 8"/><path d="M21 3v5h-5"/><path d="M21 12a9 9 0 0 1-15.5 6.3L3 16"/><path d="M3 21v-5h5"/></svg>
刷新
</button>
</div>
</div>
</div>
<div class="tools-grid" id="toolsGrid">加载中…</div>
</section>
<!-- TAB: 异步任务 (/v1/runs + SSE 事件流) -->
<section class="tab-panel" id="tab-runs">
<div class="panel-head">
<div class="panel-head-row">
<div>
<h2>异步任务</h2>
<p>POST <code>/v1/runs</code> 启动长任务,SSE 实时事件流展示 agent 调工具/思考/结果</p>
</div>
<div class="panel-head-actions">
<button class="glass-btn-sm" id="runCancelBtn" onclick="cancelRun()" style="display:none">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="5" width="14" height="14"/></svg>
断开流
</button>
<button class="glass-btn-sm primary" id="runStartBtn" onclick="startRun()">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 3"/></svg>
运行
</button>
</div>
</div>
</div>
<div class="runs-layout">
<div class="runs-left">
<div class="runs-input-head">任务指令</div>
<textarea id="runPrompt" placeholder="例如: 帮我研究一下特斯拉最新的 Robotaxi 进展,给出 5 条关键信息 + 来源"></textarea>
<div class="runs-hint">长任务 / 带工具调用 / 需要流式看中间步骤时用这个。短问题用对话 tab 即可。</div>
<div class="runs-active" id="runsActive"></div>
</div>
<div class="runs-right">
<div class="runs-events-head">
<span>事件流</span>
<span class="runs-meta" id="runsMeta">未启动</span>
</div>
<div class="runs-events" id="runsEvents">
<div class="history-empty">点「运行」启动一个任务,事件会实时显示在这里</div>
</div>
</div>
</div>
</section>
<!-- TAB: 帮助 · 官方 Console (iframe) -->
<section class="tab-panel" id="tab-help">
<div class="panel-head">
<div class="panel-head-row">
<div>
<h2>官方 Console</h2>
<p>内嵌 Hermes 官方 Web UI(React + Vite 构建),7 个页面含完整架构文档 / 技能 / 记忆 / 工具 / 定时任务说明</p>
</div>
<div class="panel-head-actions">
<a href="/classic/" target="_blank" class="glass-btn-sm">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>
新窗口打开
</a>
</div>
</div>
</div>
<iframe src="/classic/" class="help-iframe" title="Hermes 官方 Console" id="helpIframe"></iframe>
</section>
<!-- TAB: 帮助(旧版本自写文档,保留结构防脚本错误,不显示) -->
<section class="tab-panel" id="tab-help-old" style="display:none !important">
<div class="help-scroll">
<div class="help-card">
<h3>🏗 总体架构</h3>
<p>爱马仕 UI 是 <b>纯前端 SPA</b>(无构建),通过 nginx 反代对接 Hermes Agent 后端。Hermes 后端部署在 Incus LXC 容器里,内嵌 Docker 容器跑 Python Gateway,暴露 OpenAI 兼容的 HTTP API。</p>
<pre class="md-pre"><code>浏览器
└─ https://hermes.kang-kang.com/
└─ 宿主 nginx (cookie 门禁 + Bearer 注入)
├─ / 静态 UI (index.html + app.js + styles.css)
├─ /_auth/verify htpasswd 登录(kang)
├─ /v1/* → Hermes API (LXC incusbr0:8642)
├─ /api/jobs* → 定时任务 CRUD
├─ /hermes-skills/ 真实 78 个 SKILL.md(docker cp 快照)
└─ /memory/ SOUL.md + sessions + tools.txt(systemd timer 每 1 分钟同步)</code></pre>
</div>
<div class="help-card">
<h3>🔌 各 tab 对应的后端调用</h3>
<table class="md-table">
<thead><tr><th>Tab</th><th>调用</th><th>数据源</th></tr></thead>
<tbody>
<tr><td>对话</td><td><code>POST /v1/chat/completions</code> (SSE 流式)</td><td>实时 · 前端 localStorage 存会话</td></tr>
<tr><td>研究</td><td>跳回对话 + 预填 prompt</td><td>纯前端</td></tr>
<tr><td>Agent</td><td>前端 CRUD + system prompt 组装</td><td>localStorage (hermes-ui-agents-v1)</td></tr>
<tr><td>Skill Studio</td><td><code>GET /hermes-skills/</code> autoindex JSON</td><td>docker cp 快照 (78 个真实 SKILL.md)</td></tr>
<tr><td>定时任务</td><td><code>GET/POST/PATCH/DELETE /api/jobs</code></td><td>Hermes 后端实时</td></tr>
<tr><td>记忆</td><td><code>GET /memory/SOUL.md</code> + <code>/memory/sessions.json</code> + <code>/memory/sessions/{id}.json</code></td><td>systemd timer 每 1 分钟 sync</td></tr>
<tr><td>工具</td><td><code>GET /memory/tools.txt</code></td><td><code>hermes tools list</code> 输出每分钟刷新</td></tr>
<tr><td>异步任务</td><td><code>POST /v1/runs</code> + EventSource <code>/v1/runs/{id}/events</code></td><td>实时 SSE</td></tr>
<tr><td>仪表盘</td><td><code>GET /v1/models</code> + localStorage 聚合</td><td>实时 + 本地</td></tr>
</tbody>
</table>
</div>
<div class="help-card">
<h3>🔐 认证与安全</h3>
<ul>
<li><b>Cookie 门禁</b>: <code>hermes_auth=ok</code>,HttpOnly + Secure + 24h 有效</li>
<li><b>登录流程</b>: 前端 login.html → fetch <code>/_auth/verify</code> Basic Auth → nginx 校验 htpasswd → 返回 Set-Cookie</li>
<li><b>Bearer 注入</b>: nginx 在 proxy_pass 时自动加 <code>Authorization: Bearer ...</code>,浏览器永远看不到真 API key</li>
<li><b>用户</b>: <code>kang</code>(bcrypt 存在 <code>/etc/nginx/.htpasswd-hermes-kang</code>)</li>
</ul>
</div>
<div class="help-card">
<h3>🧠 Skill Studio 编排原理</h3>
<p>不是真的在后端运行编排,而是前端<b>组装复合 system prompt</b>:</p>
<pre class="md-pre"><code>Agent.stages.pre → 阶段一 · 前置(理解目标)
Agent.stages.exec → 阶段二 · 执行(工具调用 / 生成)
Agent.stages.post → 阶段三 · 收尾(自查 / 格式化)
每个 stage 把选中的 skill 文本拼起来
→ 作为 messages[0] system 发给 /v1/chat/completions
→ Gemini 3 Pro 按阶段指令输出</code></pre>
<p>真实 Hermes skill(SKILL.md)会把前 1200 字节正文注入 prompt,内建 skill 是简短的 prompt 片段。</p>
</div>
<div class="help-card">
<h3>⚙ 部署 / 更新</h3>
<pre class="md-pre"><code># 本机改 src/ 后
cd ~/Projects/code/20260421-hermes-glass-ui-personal
# 同步个人 VPS
rsync -az src/ root@76.13.31.179:/var/www/hermes-kang/
# Git
git push # Gitea kangwan/hermes-glass-ui-personal
# 如果改了 JS/CSS 要让浏览器刷新,记得 bump sw.js 的 CACHE 版本号</code></pre>
</div>
<div class="help-card">
<h3>🛠 常见问题</h3>
<ul>
<li><b>新改的东西没显示</b> → Service Worker 缓存顽固,bump <code>sw.js</code><code>CACHE</code> 版本号,再 F12 → Application → Unregister</li>
<li><b>VPS 后端换模型</b> → 改容器内 <code>/opt/hermes-agent/config.yaml + .env</code>,<code>docker restart hermes-agent</code></li>
<li><b>记忆 tab 看不到最新会话</b> → systemd timer 1 分钟一次,急的话 <code>ssh root@76.13.31.179 /usr/local/bin/sync-hermes-memory.sh</code></li>
<li><b>Cron 任务不触发</b> → 看 Hermes 容器日志 <code>docker logs hermes-agent</code></li>
</ul>
</div>
<div class="help-card">
<h3>📦 技术栈</h3>
<ul>
<li>前端:纯 HTML + CSS + JS,无构建,<b>无任何 npm 依赖</b>,约 120KB app.js + 75KB styles.css</li>
<li>风格:Apple Liquid Glass(iOS/macOS 26 风格)+ Hermès 橙 #FF6900</li>
<li>后端对接:OpenAI 兼容 HTTP + SSE,不走 WebSocket</li>
<li>持久化:前端 localStorage(6 个 key),后端 Hermes session DB</li>
<li>PWA:manifest + service worker,可装 Dock App(macOS 本地版)</li>
</ul>
</div>
</div>
</section>
<!-- TAB: 研究 -->
<section class="tab-panel" id="tab-research">
<div class="panel-head">
<h2>研究</h2>
<p>爱马仕能帮你深度调研、解读文档、调用工具。</p>
</div>
<div class="grid-cards">
<div class="card">
<div class="card-icon">🔍</div>
<div class="card-title">深度研究</div>
<div class="card-desc">给一个主题,自动拆解 → 搜索 → 总结 → 输出报告。</div>
<button class="glass-btn-sm" onclick="startResearch()">开始</button>
</div>
<div class="card">
<div class="card-icon">📄</div>
<div class="card-title">文档问答</div>
<div class="card-desc">粘贴长文档或 URL,就文档内容提问。</div>
<button class="glass-btn-sm" onclick="switchTab('chat');fillPrompt('请粘贴文档内容或 URL,我来帮你解读')">去对话</button>
</div>
<div class="card">
<div class="card-icon">🛠</div>
<div class="card-title">工具调用</div>
<div class="card-desc">浏览器 / 文件 / 终端 — Hermes 的工具集通过后端执行。</div>
<button class="glass-btn-sm" onclick="switchTab('chat');fillPrompt('列出你现在有哪些工具可以用')">查询工具</button>
</div>
<div class="card">
<div class="card-icon">🧠</div>
<div class="card-title">记忆体系</div>
<div class="card-desc">对话历史沉淀进 SQLite FTS5,未来 skill 可自动提炼。</div>
<button class="glass-btn-sm" onclick="openLog()">查看日志</button>
</div>
</div>
</section>
<!-- TAB: 仪表盘 -->
<section class="tab-panel" id="tab-dashboard">
<div class="panel-head">
<div class="panel-head-row">
<div>
<h2>仪表盘</h2>
<p>用量、系统状态和实时日志。</p>
</div>
<div class="panel-head-actions">
<button class="glass-btn-sm" onclick="refreshDashboard()">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 0 1 15.5-6.3L21 8"/><path d="M21 3v5h-5"/><path d="M21 12a9 9 0 0 1-15.5 6.3L3 16"/><path d="M3 21v-5h5"/></svg>
刷新
</button>
</div>
</div>
</div>
<div class="dash-scroll">
<!-- Hero 数据区 -->
<div class="dash-hero">
<div class="dash-hero-main">
<div class="dash-hero-label">今日 Token 消耗</div>
<div class="dash-hero-num" id="stTodayTokens">0</div>
<div class="dash-hero-sub">
<span id="stTodaySub">0 条消息</span>
<span class="dash-hero-dot">·</span>
<span id="stTodayConvosText">0 个对话</span>
</div>
</div>
<div class="dash-hero-side">
<div class="hero-side-item">
<div class="hero-side-num" id="stWeek">0</div>
<div class="hero-side-lbl">本周消息</div>
<div class="hero-side-sub" id="stWeekSub">0 tokens</div>
</div>
<div class="hero-side-item">
<div class="hero-side-num" id="stMonth">0</div>
<div class="hero-side-lbl">本月消息</div>
<div class="hero-side-sub" id="stMonthSub">0 tokens</div>
</div>
<div class="hero-side-item">
<div class="hero-side-num" id="stAll">0</div>
<div class="hero-side-lbl">总对话</div>
<div class="hero-side-sub" id="stAllSub">0 条消息</div>
</div>
</div>
</div>
<!-- 运行状态 + 最常用智能体 -->
<div class="dash-grid-lg">
<div class="stat stat-lg">
<div class="stat-icon">
<svg width="22" height="22" 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"/><circle cx="12" cy="12" r="4"/></svg>
</div>
<div class="stat-body">
<div class="stat-label">API 状态</div>
<div class="stat-value" id="statApi"></div>
<div class="stat-sub" id="statApiSub">127.0.0.1:8642</div>
</div>
</div>
<div class="stat stat-lg">
<div class="stat-icon">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>
</div>
<div class="stat-body">
<div class="stat-label">当前模型</div>
<div class="stat-value" id="statModel">Gemini 3 Pro</div>
<div class="stat-sub">Google AI Studio</div>
</div>
</div>
<div class="stat stat-lg">
<div class="stat-icon">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="M6 8h.01M10 8h.01"/></svg>
</div>
<div class="stat-body">
<div class="stat-label">本机地址</div>
<div class="stat-value" id="statIP"></div>
<div class="stat-sub">Mac mini M4</div>
</div>
</div>
<div class="stat stat-lg">
<div class="stat-icon">
<svg width="22" height="22" 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>
</div>
<div class="stat-body">
<div class="stat-label">代理</div>
<div class="stat-value">Clash <span class="pill-ok"></span></div>
<div class="stat-sub">127.0.0.1:7897</div>
</div>
</div>
<div class="stat stat-lg">
<div class="stat-icon">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2a5 5 0 0 0-5 5v2H5a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-9a2 2 0 0 0-2-2h-2V7a5 5 0 0 0-5-5z"/></svg>
</div>
<div class="stat-body">
<div class="stat-label">智能体</div>
<div class="stat-value" id="statAgents">0</div>
<div class="stat-sub">已创建</div>
</div>
</div>
<div class="stat stat-lg">
<div class="stat-icon">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5v14c0 1.7 4 3 9 3s9-1.3 9-3V5"/><path d="M3 12c0 1.7 4 3 9 3s9-1.3 9-3"/></svg>
</div>
<div class="stat-body">
<div class="stat-label">存储占用</div>
<div class="stat-value" id="statStorage"></div>
<div class="stat-sub">localStorage</div>
</div>
</div>
</div>
<!-- 最常用智能体(大) -->
<div class="top-agent-card" id="topAgentCard">
<div class="top-agent-avatar" id="topAgentAvatar"></div>
<div class="top-agent-info">
<div class="top-agent-label">最常使用的智能体</div>
<div class="top-agent-name" id="stTopAgent">还未使用智能体</div>
<div class="top-agent-sub" id="stTopAgentSub">创建一个智能体开始对话</div>
</div>
</div>
<!-- 热力图 -->
<div class="dash-section-label">活动热力图 · 最近 8 周</div>
<div class="heatmap-wrap">
<div class="heatmap" id="heatmap"></div>
<div class="heatmap-legend">
<span></span>
<div class="hm-cell lvl-0"></div>
<div class="hm-cell lvl-1"></div>
<div class="hm-cell lvl-2"></div>
<div class="hm-cell lvl-3"></div>
<div class="hm-cell lvl-4"></div>
<span></span>
</div>
</div>
<!-- 日详情 -->
<div class="dash-section-label" id="daydetailLabel">日详情</div>
<div class="day-detail" id="dayDetail">
<div class="history-empty">点上面热力图中的任意一天查看详情</div>
</div>
</div>
</section>
<!-- TAB: 设置 -->
<section class="tab-panel" id="tab-settings">
<div class="panel-head">
<h2>设置</h2>
<p>调整连接、偏好、数据和外观。</p>
</div>
<div class="settings-scroll">
<!-- 连接 -->
<div class="settings-group">
<div class="settings-group-head">
<div class="settings-group-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>
</div>
<div>
<div class="settings-group-title">连接</div>
<div class="settings-group-desc">爱马仕后端的 API 地址和密钥</div>
</div>
</div>
<div class="settings-group-body">
<div class="settings-field">
<label for="apiBase">API 地址</label>
<input type="text" id="apiBase" value="/api/v1">
<div class="settings-help">Hermes API Server 的 OpenAI 兼容端点。本地默认 <code>/api/v1</code>(nginx 反代到 127.0.0.1:8642)</div>
</div>
<div class="settings-field">
<label for="apiKey">API Key</label>
<input type="password" id="apiKey" value="hermes-mini-local-key-2026">
<div class="settings-help">任意字符串,只要和 Hermes <code>.env</code> 里的 <code>API_SERVER_KEY</code> 一致</div>
</div>
</div>
</div>
<!-- 对话偏好 -->
<div class="settings-group">
<div class="settings-group-head">
<div class="settings-group-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
</div>
<div>
<div class="settings-group-title">对话偏好</div>
<div class="settings-group-desc">控制消息发送行为</div>
</div>
</div>
<div class="settings-group-body">
<div class="settings-field toggle-field">
<div>
<label>流式输出</label>
<div class="settings-help">打开后 AI 边生成边显示,更接近打字机效果</div>
</div>
<label class="switch"><input type="checkbox" id="streamMode" checked><span class="slider"></span></label>
</div>
</div>
</div>
<!-- 外观 -->
<div class="settings-group">
<div class="settings-group-head">
<div class="settings-group-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="13.5" cy="6.5" r=".5"/><circle cx="17.5" cy="10.5" r=".5"/><circle cx="8.5" cy="7.5" r=".5"/><circle cx="6.5" cy="12.5" r=".5"/><path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.926 0 1.648-.746 1.648-1.688 0-.437-.18-.835-.437-1.125-.29-.289-.438-.652-.438-1.125a1.64 1.64 0 0 1 1.668-1.668h1.996c3.051 0 5.555-2.503 5.555-5.554C21.965 6.012 17.461 2 12 2z"/></svg>
</div>
<div>
<div class="settings-group-title">外观</div>
<div class="settings-group-desc">主题、字体、动画</div>
</div>
</div>
<div class="settings-group-body">
<div class="settings-field toggle-field">
<div>
<label>主题</label>
<div class="settings-help">左下角"明亮/暗色"按钮切换</div>
</div>
<button class="theme-chip" onclick="toggleTheme()">切换主题</button>
</div>
</div>
</div>
<!-- 数据 -->
<div class="settings-group wide">
<div class="settings-group-head">
<div class="settings-group-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5v14c0 1.7 4 3 9 3s9-1.3 9-3V5"/><path d="M3 12c0 1.7 4 3 9 3s9-1.3 9-3"/></svg>
</div>
<div>
<div class="settings-group-title">数据</div>
<div class="settings-group-desc">导出、导入、清空本地数据</div>
</div>
</div>
<div class="settings-group-body">
<div class="settings-actions">
<button class="glass-btn-sm" onclick="exportData()">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
导出 JSON
</button>
<button class="glass-btn-sm" onclick="document.getElementById('importFile').click()">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
导入 JSON
</button>
<input type="file" id="importFile" accept=".json" style="display:none" onchange="importData(event)">
<button class="glass-btn-sm danger-btn" onclick="wipeAll()">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
清空全部数据
</button>
</div>
</div>
</div>
<!-- 关于 -->
<div class="settings-group wide">
<div class="settings-group-head">
<div class="settings-group-icon">
<svg 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="M12 16v-4M12 8h.01"/></svg>
</div>
<div>
<div class="settings-group-title">关于</div>
<div class="settings-group-desc">爱马仕 · AI</div>
</div>
</div>
<div class="settings-group-body">
<div class="about-grid">
<div class="about-item">
<div class="about-lbl">版本</div>
<div class="about-val">v0.2 · Liquid Glass</div>
</div>
<div class="about-item">
<div class="about-lbl">运行于</div>
<div class="about-val">Mac mini M4 · macOS 26.3</div>
</div>
<div class="about-item">
<div class="about-lbl">模型</div>
<div class="about-val">Gemini 3 Pro · Google AI Studio</div>
</div>
<div class="about-item">
<div class="about-lbl">代理</div>
<div class="about-val">Clash Verge · 127.0.0.1:7897</div>
</div>
</div>
</div>
</div>
<div class="settings-save-bar">
<button class="glass-btn-sm primary" onclick="saveSettings()">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
保存设置
</button>
</div>
</div>
</section>
</main>
</div>
<script src="./app.js"></script>
</body>
</html>