1140 lines
60 KiB
HTML
1140 lines
60 KiB
HTML
<!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="告诉智能体这个技能要做什么,例如: 回答时使用小红书风格,多用 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>
|