Files
hermes-glass-ui-personal/src/index.html
2026-05-11 18:39:59 +08:00

1551 lines
86 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?v=20260511-modules-ia-v36">
</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="sessions">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M3 12h18"/><path d="M3 18h18"/></svg>
<span>会话</span>
</button>
<button class="side-item" data-tab="agents">
<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>档案</span>
</button>
<button class="side-item" data-tab="office">
<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="18" height="18" rx="2"/><path d="M3 9h18"/><path d="M9 21V9"/></svg>
<span>工作区</span>
</button>
<button class="side-item" data-tab="models">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2v20"/><path d="M2 12h20"/><path d="M4.93 4.93 19.07 19.07"/><path d="M19.07 4.93 4.93 19.07"/></svg>
<span>模型</span>
</button>
<button class="side-item" data-tab="providers">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 7h.01"/><path d="M7.5 7h3.75a3.75 3.75 0 0 1 0 7.5H9L6 18v-3.5H5A3.5 3.5 0 0 1 5 7h2.5z"/><path d="M15 14.5h1a3.5 3.5 0 0 0 0-7h-1"/></svg>
<span>提供商</span>
</button>
<button class="side-item" data-tab="skills">
<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>技能</span>
</button>
<button class="side-item" data-tab="soul">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3l1.8 5.2L19 10l-5.2 1.8L12 17l-1.8-5.2L5 10l5.2-1.8L12 3z"/><path d="M19 15l.8 2.2L22 18l-2.2.8L19 21l-.8-2.2L16 18l2.2-.8L19 15z"/></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="schedules">
<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="gateway">
<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="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/><path d="M10 6.5h4"/><path d="M6.5 10v4"/><path d="M17.5 10v4"/><path d="M10 17.5h4"/></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="google/gemini-3.1-pro-preview">Gemini 3.1 Pro · OpenRouter</option>
<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>
<datalist id="modelOptions">
<option value="google/gemini-3.1-pro-preview" label="Gemini 3.1 Pro · OpenRouter"></option>
<option value="gemini-3-pro-preview" label="Gemini 3 Pro"></option>
<option value="gemini-2.5-pro" label="Gemini 2.5 Pro"></option>
<option value="gemini-2.5-flash" label="Gemini 2.5 Flash"></option>
</datalist>
</div>
<button class="chat-clear" onclick="saveWeeklyReportFromChat()" title="保存最近一条回答为周报记录">存周报</button>
<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: 档案 -->
<section class="tab-panel" id="tab-agents">
<div class="panel-head">
<div class="panel-head-row">
<div>
<h2>档案</h2>
<p>每个档案都是独立的 Hermes 工作区,可绑定模型 Profile、技能、编排阶段和提示词。</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>模型 ID</label>
<input type="text" id="agentModel" list="modelOptions" placeholder="google/gemini-3.1-pro-preview" autocomplete="off">
</div>
<div class="setting-row setting-row-full">
<label>模型 Profile</label>
<select id="agentModelProfile" onchange="applyAgentModelProfileSelection()">
<option value="">跟随对话默认 / 仅使用模型 ID</option>
</select>
<div class="settings-help">Profile 记录 provider / base URL / API Key 引用;当前请求仍按模型 ID 发给 Hermes API。</div>
</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: 技能 -->
<section class="tab-panel" id="tab-skills">
<div class="panel-head">
<div class="panel-head-row">
<div>
<h2>技能</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-schedules">
<div class="panel-head">
<div class="panel-head-row">
<div>
<h2>计划任务</h2>
<p>通过定时运行 Agent 自动完成任务;直接对接 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) -->
<section class="tab-panel" id="tab-soul">
<div class="panel-head">
<div class="panel-head-row">
<div>
<h2>人格</h2>
<p>通过 <code>SOUL.md</code> 定义 Agent 的人格、语气和长期指令。</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">说明</div>
<div class="memory-sessions">
<div class="settings-help">每次对话会加载这份长期指令。编辑能力暂未开放在前端,当前页面用于读取线上 Hermes 同步快照。</div>
<button class="glass-btn-sm" onclick="switchTab('chat');fillPrompt('请总结当前 SOUL.md 的人格设定和约束')">让 Agent 解读人格</button>
</div>
</div>
</div>
</section>
<!-- TAB: 记忆 -->
<section class="tab-panel" id="tab-memory">
<div class="panel-head">
<div class="panel-head-row">
<div>
<h2>记忆</h2>
<p>Hermes 在不同会话之间沉淀的偏好、事实和可检索上下文。</p>
</div>
</div>
</div>
<div class="memory-grid">
<div class="memory-pane">
<div class="memory-pane-head">代理记忆</div>
<div class="memory-sessions">
<div class="settings-help">当前个人版已同步 SOUL.md 和会话快照;更细的外部记忆 Provider 仍由 Hermes 后端配置。</div>
<button class="glass-btn-sm" onclick="switchTab('soul')">查看人格</button>
</div>
</div>
<div class="memory-pane">
<div class="memory-pane-head">相关入口</div>
<div class="memory-sessions">
<button class="glass-btn-sm" onclick="switchTab('sessions')">查看会话</button>
<button class="glass-btn-sm" onclick="switchTab('tools');fillPrompt('列出当前可用的记忆相关工具')">查看记忆工具</button>
</div>
</div>
</div>
</section>
<!-- TAB: 提供商 -->
<section class="tab-panel" id="tab-providers">
<div class="panel-head">
<div class="panel-head-row">
<div>
<h2>提供商</h2>
<p>配置 LLM Provider、运行模型、Base URL 和服务器端 API Key 引用。</p>
</div>
<div class="panel-head-actions">
<button class="glass-btn-sm" onclick="refreshHermesConfig(true)">
<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="models-scroll settings-scroll">
<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"><path d="M15 7h.01"/><path d="M7.5 7h3.75a3.75 3.75 0 0 1 0 7.5H9L6 18v-3.5H5A3.5 3.5 0 0 1 5 7h2.5z"/><path d="M15 14.5h1a3.5 3.5 0 0 0 0-7h-1"/></svg>
</div>
<div>
<div class="settings-group-title">运行 Provider</div>
<div class="settings-group-desc">线上 Hermes agent 当前默认调用的模型和 Provider 配置</div>
</div>
</div>
<div class="settings-group-body">
<div class="settings-grid-3">
<div class="settings-field">
<label for="hermesModelDefault">默认模型 ID</label>
<input type="text" id="hermesModelDefault" placeholder="google/gemini-3.1-pro-preview" autocomplete="off">
</div>
<div class="settings-field">
<label for="hermesModelProvider">Provider</label>
<input type="text" id="hermesModelProvider" placeholder="openrouter" autocomplete="off">
</div>
<div class="settings-field">
<label for="hermesModelBaseUrl">Base URL</label>
<input type="text" id="hermesModelBaseUrl" placeholder="https://openrouter.ai/api/v1" autocomplete="off">
</div>
</div>
<div class="settings-grid-3">
<div class="settings-field">
<label for="hermesModelApiKeyRef">AI API Key</label>
<input type="password" id="hermesModelApiKeyRef" placeholder="服务器环境变量,不在浏览器显示" disabled>
<div class="settings-help">真实 Key 仍只放在服务器环境变量;这里不保存、不回显密钥。多 Agent 的专属 Key 引用在「模型」Profile 里维护。</div>
</div>
</div>
<div class="settings-actions">
<button class="glass-btn-sm" onclick="refreshHermesConfig(true)">读取 Provider 配置</button>
<button class="glass-btn-sm primary" id="hermesModelSaveBtn" onclick="saveModelConfig()">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.3" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><path d="M17 21v-8H7v8"/><path d="M7 3v5h8"/></svg>
保存 Provider 并重启
</button>
<div class="settings-help" id="hermesModelStatus">打开提供商页后自动读取。</div>
</div>
</div>
</div>
</div>
</section>
<!-- TAB: 模型 -->
<section class="tab-panel" id="tab-models">
<div class="panel-head">
<div class="panel-head-row">
<div>
<h2>模型</h2>
<p>管理模型库和 Agent 可绑定的模型 ProfilesProvider 和运行模型放在「提供商」。</p>
</div>
<div class="panel-head-actions">
<button class="glass-btn-sm" onclick="clearModelProfileForm()">新增 Profile</button>
</div>
</div>
</div>
<div class="models-scroll settings-scroll">
<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"><rect x="4" y="4" width="16" height="16" rx="2"/><path d="M9 9h6v6H9z"/><path d="M9 1v3"/><path d="M15 1v3"/><path d="M9 20v3"/><path d="M15 20v3"/><path d="M20 9h3"/><path d="M20 14h3"/><path d="M1 9h3"/><path d="M1 14h3"/></svg>
</div>
<div>
<div class="settings-group-title">模型 Profiles</div>
<div class="settings-group-desc">给不同 Agent 绑定不同模型、Provider、Base URL 和服务器端 Key 引用</div>
</div>
</div>
<div class="settings-group-body">
<div class="settings-subpanel">
<div class="settings-subpanel-head">
<div>
<div class="settings-subtitle">已配置 Profiles</div>
<div class="settings-help" id="modelProfilesCount">用于给不同智能体绑定不同模型接入信息。</div>
</div>
<button class="glass-btn-sm" type="button" onclick="clearModelProfileForm()">新增 Profile</button>
</div>
<div class="model-profile-list" id="modelProfilesList">
<div class="settings-help">正在读取服务器共享配置...</div>
</div>
<div class="model-profile-form">
<div class="settings-subtitle" id="modelProfileFormTitle">新增模型 Profile</div>
<div class="settings-grid-3">
<div class="settings-field">
<label for="modelProfileName">显示名</label>
<input type="text" id="modelProfileName" placeholder="例如 OpenRouter Gemini" autocomplete="off">
</div>
<div class="settings-field">
<label for="modelProfileProvider">Provider</label>
<input type="text" id="modelProfileProvider" placeholder="openrouter / openai / anthropic" autocomplete="off">
</div>
<div class="settings-field">
<label for="modelProfileModel">模型 ID</label>
<input type="text" id="modelProfileModel" placeholder="google/gemini-3.1-pro-preview" autocomplete="off">
</div>
<div class="settings-field">
<label for="modelProfileBaseUrl">Base URL</label>
<input type="text" id="modelProfileBaseUrl" placeholder="https://openrouter.ai/api/v1" autocomplete="off">
</div>
<div class="settings-field">
<label for="modelProfileApiKeyRef">API Key 引用</label>
<input type="text" id="modelProfileApiKeyRef" placeholder="OPENROUTER_API_KEY / 服务器环境变量" autocomplete="off">
<div class="settings-help">只填服务器环境变量名;前端不保存真实 Key。Agent 使用该 Profile 时会由桥接服务代理请求。</div>
</div>
<div class="settings-field model-profile-checks">
<label class="settings-checkline"><input type="checkbox" id="modelProfileEnabled" checked> 启用</label>
<label class="settings-checkline"><input type="checkbox" id="modelProfileDefault"> 作为默认</label>
</div>
</div>
<div class="settings-actions">
<button class="glass-btn-sm primary" type="button" onclick="saveModelProfile()">保存 Profile</button>
<button class="glass-btn-sm" type="button" onclick="clearModelProfileForm()">清空表单</button>
<div class="settings-help" id="sharedConfigStatus">共享配置会保存到服务器,供同事端同步。</div>
</div>
</div>
</div>
</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-scroll">
<div class="tools-grid" id="toolsGrid">加载中…</div>
<div class="settings-group wide tools-mcp-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 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><path d="M3.3 7 12 12l8.7-5"/><path d="M12 22V12"/></svg>
</div>
<div>
<div class="settings-group-title">MCP 工具接入</div>
<div class="settings-group-desc">外部工具、知识库和服务调用配置</div>
</div>
</div>
<div class="settings-group-body">
<div class="settings-field">
<label for="mcpServersYaml">MCP Servers YAML</label>
<textarea id="mcpServersYaml" rows="8" spellcheck="false" placeholder='mcp_servers:
time:
command: uvx
args: ["mcp-server-time"]'></textarea>
<div class="settings-help">留空会移除 <code>mcp_servers</code>;保存只改工具接入,不要求同时填写模型配置。</div>
</div>
<div class="settings-actions">
<button class="glass-btn-sm" onclick="refreshHermesConfig(true)">读取 MCP 配置</button>
<button class="glass-btn-sm primary" id="hermesMcpSaveBtn" onclick="saveMcpConfig()">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.3" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><path d="M17 21v-8H7v8"/><path d="M7 3v5h8"/></svg>
保存 MCP 并重启
</button>
<div class="settings-help" id="hermesMcpStatus">打开工具页后自动读取。</div>
</div>
</div>
</div>
</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>本地对话列表 + <code>/memory/sessions.json</code> + <code>/memory/sessions/{id}.json</code></td><td>localStorage + systemd timer sync</td></tr>
<tr><td>档案</td><td>前端 CRUD + system prompt 组装</td><td>localStorage (hermes-ui-agents-v1)</td></tr>
<tr><td>工作区</td><td><code>GET /v1/models</code> + localStorage 聚合</td><td>实时 + 本地</td></tr>
<tr><td>模型</td><td><code>GET/POST /feishu/ui-config</code></td><td>服务器共享模型 Profiles</td></tr>
<tr><td>提供商</td><td><code>GET/PUT /feishu/hermes-config</code><code>model</code></td><td>Hermes 运行模型配置</td></tr>
<tr><td>技能</td><td><code>GET /hermes-skills/</code> autoindex JSON</td><td>docker cp 快照 (78 个真实 SKILL.md)</td></tr>
<tr><td>人格</td><td><code>GET /memory/SOUL.md</code></td><td>systemd timer sync</td></tr>
<tr><td>记忆</td><td><code>GET /memory/sessions.json</code> 入口聚合</td><td>systemd timer sync</td></tr>
<tr><td>工具</td><td><code>GET /memory/tools.txt</code> + <code>GET/PUT /feishu/hermes-config</code><code>mcp_servers</code></td><td><code>hermes tools list</code> + MCP 配置</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/POST/DELETE /feishu/apps</code></td><td>外部消息入口</td></tr>
<tr><td>异步任务</td><td><code>POST /v1/runs</code> + EventSource <code>/v1/runs/{id}/events</code></td><td>实时 SSE</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>🧠 技能编排原理</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 要让浏览器刷新,更新 index.html 里的资源 query 版本</code></pre>
</div>
<div class="help-card">
<h3>🛠 常见问题</h3>
<ul>
<li><b>新改的东西没显示</b> → 刷新页面;旧浏览器如果曾安装 Service Worker当前 <code>sw.js</code> 会自动清理并注销</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-sessions">
<div class="panel-head">
<div class="panel-head-row">
<div>
<h2>会话</h2>
<p>查看本地对话和 Hermes 后端同步的会话快照,可导入继续聊。</p>
</div>
<div class="panel-head-actions">
<button class="glass-btn-sm primary" onclick="newChat()">新建聊天</button>
<button class="glass-btn-sm" onclick="renderSessionsPanel();refreshMemory()">刷新</button>
</div>
</div>
</div>
<div class="memory-grid">
<div class="memory-pane">
<div class="memory-pane-head">本地会话</div>
<div class="memory-sessions" id="localSessionsList">加载中…</div>
</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: 工作区 -->
<section class="tab-panel" id="tab-office">
<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">
<!-- 快捷入口 -->
<div class="deploy-links">
<a href="https://hermes.kang-kang.com/" target="_blank" rel="noopener" class="deploy-link">
<div class="deploy-link-icon">
<svg width="18" height="18" 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"/><line x1="2" y1="12" x2="22" y2="12"/><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="deploy-link-body">
<div class="deploy-link-label">个人版 · 主站</div>
<div class="deploy-link-url">hermes.kang-kang.com</div>
</div>
<svg class="deploy-link-arrow" 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="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>
<a href="https://hermes.kang-kang.com/api/v1/models" target="_blank" rel="noopener" class="deploy-link">
<div class="deploy-link-icon">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>
</div>
<div class="deploy-link-body">
<div class="deploy-link-label">后端 · OpenAI 兼容</div>
<div class="deploy-link-url">/api/v1/models</div>
</div>
<svg class="deploy-link-arrow" 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="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>
<a href="https://hermes.kang-kang.com/feishu/apps" target="_blank" rel="noopener" class="deploy-link">
<div class="deploy-link-icon">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a4 4 0 0 1-4 4H8l-5 3V7a4 4 0 0 1 4-4h10a4 4 0 0 1 4 4z"/><path d="M8 9h8"/><path d="M8 13h5"/></svg>
</div>
<div class="deploy-link-body">
<div class="deploy-link-label">飞书 · 机器人列表</div>
<div class="deploy-link-url">/feishu/apps</div>
</div>
<svg class="deploy-link-arrow" 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="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>
<a href="https://styles.kang-kang.com/" target="_blank" rel="noopener" class="deploy-link">
<div class="deploy-link-icon">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M4 4.5A2.5 2.5 0 0 1 6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5z"/></svg>
</div>
<div class="deploy-link-body">
<div class="deploy-link-label">文档 · 解析</div>
<div class="deploy-link-url">styles.kang-kang.com</div>
</div>
<svg class="deploy-link-arrow" 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="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>
<!-- 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">google/gemini-3.1-pro-preview</div>
<div class="stat-sub" id="statModelSub">Provider 以提供商页为准</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="heatmap-wrap">
<div class="heatmap-head">
<div>
<div class="heatmap-kicker">活动热力图</div>
<h3>最近使用节奏</h3>
<p>按天聚合消息量,颜色越深代表当天对话越活跃。</p>
</div>
<div class="heatmap-metrics">
<span id="hmActiveDays">0 个活跃日</span>
<span id="hmPeakDay">峰值 0</span>
</div>
</div>
<div class="heatmap-board">
<div class="heatmap-months" id="heatmapMonths"></div>
<div class="heatmap" id="heatmap"></div>
</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-gateway">
<div class="panel-head">
<h2>网关</h2>
<p>统一管理飞书、微信、WhatsApp、Telegram、Discord 等外部消息入口。</p>
</div>
<div class="integrations-scroll">
<div class="integration-grid">
<div class="integration-channel-card active">
<div class="integration-channel-head">
<div class="integration-channel-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a4 4 0 0 1-4 4H8l-5 3V7a4 4 0 0 1 4-4h10a4 4 0 0 1 4 4z"/><path d="M8 9h8"/><path d="M8 13h5"/></svg>
</div>
<span class="integration-status ready">已接入</span>
</div>
<div class="integration-channel-title">飞书机器人</div>
<div class="integration-channel-desc">接收私聊、群聊、进群和消息事件,支持多个飞书 App。</div>
</div>
<div class="integration-channel-card muted">
<div class="integration-channel-head">
<div class="integration-channel-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M7 10a5 5 0 0 1 9.6-2"/><path d="M17 14a5 5 0 0 1-9.6 2"/><path d="M3 14a6 6 0 0 0 6 6h2"/><path d="M21 10a6 6 0 0 0-6-6h-2"/></svg>
</div>
<span class="integration-status pending">待接入</span>
</div>
<div class="integration-channel-title">微信</div>
<div class="integration-channel-desc">预留企业微信或个人微信网关入口。</div>
</div>
<div class="integration-channel-card muted">
<div class="integration-channel-head">
<div class="integration-channel-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12a7 7 0 1 1 3 5.74L4 19l1.26-4A6.98 6.98 0 0 1 5 12z"/><path d="M9 10h.01"/><path d="M12 10h.01"/><path d="M15 10h.01"/></svg>
</div>
<span class="integration-status pending">待接入</span>
</div>
<div class="integration-channel-title">WhatsApp</div>
<div class="integration-channel-desc">预留 WhatsApp Business Webhook。</div>
</div>
<div class="integration-channel-card muted">
<div class="integration-channel-head">
<div class="integration-channel-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.5 4.5 3 11.5l7 2 2 7 9.5-16z"/><path d="m10 13.5 5-5"/></svg>
</div>
<span class="integration-status pending">待接入</span>
</div>
<div class="integration-channel-title">Telegram</div>
<div class="integration-channel-desc">预留 Bot Token、Webhook 和群组授权。</div>
</div>
<div class="integration-channel-card muted">
<div class="integration-channel-head">
<div class="integration-channel-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="5" width="16" height="13" rx="4"/><path d="M8 18 6 21"/><path d="m16 18 2 3"/><path d="M9 11h.01"/><path d="M15 11h.01"/><path d="M10 14h4"/></svg>
</div>
<span class="integration-status pending">待接入</span>
</div>
<div class="integration-channel-title">Discord</div>
<div class="integration-channel-desc">预留 Discord Bot、频道事件和 Slash Commands。</div>
</div>
</div>
<div class="integration-panel" id="feishuSettingsGroup">
<div class="integration-panel-head">
<div>
<div class="integration-panel-title">飞书集成</div>
<div class="integration-panel-desc">当前已接入的飞书机器人和事件回调地址</div>
</div>
<button class="glass-btn-sm" onclick="refreshFeishuApps()">
<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="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 class="integration-panel-body">
<div class="feishu-toolbar">
<div class="settings-help">只显示 App ID、回调地址和服务状态Secret 与 Token 只保存在服务器环境文件。</div>
</div>
<div class="feishu-apps" id="feishuApps">
<div class="settings-help">打开网关页后自动读取飞书桥接服务。</div>
</div>
<form class="feishu-form" id="feishuAddForm" onsubmit="saveFeishuApp(event)">
<div class="settings-field">
<label for="feishuAppId">App ID</label>
<input type="text" id="feishuAppId" placeholder="cli_xxxxxxxxxxxxxxxx" autocomplete="off">
</div>
<div class="settings-field">
<label for="feishuAppSecret">App Secret</label>
<input type="password" id="feishuAppSecret" placeholder="只写入服务器,不保存在浏览器" autocomplete="off">
</div>
<div class="settings-field">
<label for="feishuVerifyToken">Verification Token</label>
<input type="password" id="feishuVerifyToken" placeholder="飞书加密策略里的 Verification Token" autocomplete="off">
</div>
<div class="feishu-form-actions">
<button class="glass-btn-sm primary" type="submit">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.3" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14"/><path d="M5 12h14"/></svg>
添加 / 更新机器人
</button>
<div class="settings-help">保存前会校验 App ID / Secret成功后复制回调地址到飞书事件配置。</div>
</div>
</form>
</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 wide settings-connection-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 class="settings-actions">
<button class="glass-btn-sm" onclick="saveSettings()">保存 API</button>
<button class="glass-btn-sm" onclick="testApiConnection()">测试连接</button>
</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" id="weeklyReportGroup">
<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="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M4 4.5A2.5 2.5 0 0 1 6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5z"/><path d="M8 7h8"/><path d="M8 11h8"/></svg>
</div>
<div>
<div class="settings-group-title">周报记录</div>
<div class="settings-group-desc">保存任务描述、优化过程和最终周报</div>
</div>
</div>
<div class="settings-group-body">
<div class="weekly-toolbar">
<div class="settings-help">对话页点「存周报」会把最近一次任务描述和回答存到这里,之后可以打开、复制或删除。</div>
<button class="glass-btn-sm" onclick="renderWeeklyReports()">刷新</button>
</div>
<div class="weekly-report-list" id="weeklyReportsList">
<div class="settings-help">还没有保存过周报记录。</div>
</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" id="aboutModelValue">以「模型」页配置为准</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?v=20260511-modules-ia-v36"></script>
</body>
</html>