Files
2026-08-14 23:41:57 +08:00

220 lines
12 KiB
HTML
Raw Permalink 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">
<title>BLACK BEAN · 黑豆</title>
<link rel="stylesheet" href="/static/style.css?v=20260814a">
</head>
<body>
<div class="app">
<!-- ===== 侧栏 ===== -->
<aside class="sidebar" id="sidebar">
<div class="brand">
<div class="brand-mark"></div>
<div class="brand-copy">
<strong>BLACK&nbsp;BEAN</strong>
<span>黑豆 · 智能体</span>
</div>
</div>
<button class="new-chat-btn" id="new-chat-btn">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
<path d="M12 5v14M5 12h14"/>
</svg>
新建任务
</button>
<div class="sidebar-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="7"/>
<path d="m21 21-4.3-4.3"/>
</svg>
<input id="session-search" type="text" placeholder="搜索会话…" autocomplete="off" spellcheck="false">
</div>
<div class="sidebar-label">会话</div>
<nav class="session-list" id="session-list"></nav>
<div class="sidebar-footer">
<span class="model-chip" id="model-chip">模型未配置</span>
<div class="sidebar-footer-actions">
<button class="icon-btn" id="refresh-btn" title="刷新">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
<path d="M21 12a9 9 0 1 1-2.64-6.36M21 3v6h-6"/>
</svg>
</button>
<button class="icon-btn" id="settings-btn" title="设置">
<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 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.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 1 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 1 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 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/>
</svg>
</button>
</div>
</div>
</aside>
<!-- ===== 主区域 ===== -->
<main class="main">
<header class="topbar">
<button class="icon-btn menu-btn" id="menu-btn" title="侧栏">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
<path d="M4 6h16M4 12h16M4 18h16"/>
</svg>
</button>
<div class="topbar-title-wrap">
<div class="topbar-title" id="session-title">新任务</div>
<div class="topbar-sub" id="session-sub"></div>
</div>
<div class="topbar-right">
<span class="ctx-chip hidden" id="ctx-chip"></span>
<div class="topbar-status">
<span class="status-dot" id="status-dot"></span>
<span id="status-text">就绪</span>
</div>
</div>
</header>
<!-- 协作任务实时状态 -->
<div class="coop-panel hidden" id="coop-panel">
<div class="coop-panel-header" id="coop-panel-header">
<span class="coop-panel-title">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/>
<circle cx="9" cy="7" r="4"/>
<path d="M23 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75"/>
</svg>
worker 任务
</span>
<span class="coop-panel-count" id="coop-count"></span>
</div>
<div class="coop-list" id="coop-list"></div>
</div>
<!-- 任务时间轴 -->
<div class="timeline" id="chat">
<div class="empty-state" id="empty-state">
<div class="empty-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="3"/>
<path d="M12 2v3M12 19v3M2 12h3M19 12h3M4.9 4.9l2.1 2.1M17 17l2.1 2.1M19.1 4.9 17 7M7 17l-2.1 2.1"/>
</svg>
</div>
<h1>黑豆 · 听君差遣</h1>
<p>运行命令、读写文件、执行 Python、操作 Docker,并可调度 worker 任务处理复杂任务。工具皆于本地工作区真实执行。</p>
<div class="examples">
<button class="example" data-prompt="列出当前工作区目录结构,并说明每个文件的作用。">浏览工作区并说明结构</button>
<button class="example" data-prompt="分析 sample_server.log,找出响应时间最长的 5 条请求并给出结论。">分析日志定位慢请求</button>
<button class="example" data-prompt="写一个 Python 脚本统计 access 日志中每个 IP 的请求次数,并输出 Top 10。">用 Python 统计数据</button>
</div>
</div>
</div>
<!-- 返回底部 -->
<button class="scroll-bottom hidden" id="scroll-bottom-btn" title="回到底部">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m6 9 6 6 6-6"/>
</svg>
</button>
<!-- 输入台 -->
<div class="composer-wrap">
<div class="composer">
<textarea id="input" rows="1" placeholder="落笔命题,如:分析 server.log 中响应时间最长的 5 条请求..."></textarea>
<div class="composer-actions">
<span class="context-hint" id="context-hint"></span>
<button class="stop-btn hidden" id="stop-btn" title="停止">
<svg viewBox="0 0 24 24" fill="currentColor">
<rect x="7" y="7" width="10" height="10" rx="2"/>
</svg>
</button>
<button class="send-btn" id="send-btn" title="投递">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m5 12 14-7-4.5 14-3.5-6.5L5 12z"/>
</svg>
</button>
</div>
</div>
<p class="disclaimer">工具调用于本地工作区真实执行 · Enter 投递 · Shift + Enter 换行</p>
</div>
</main>
</div>
<!-- ===== 设置弹窗 ===== -->
<div class="modal-overlay hidden" id="settings-modal">
<div class="modal">
<div class="modal-header">
<strong>设置</strong>
<button class="icon-btn" id="close-settings-btn" title="关闭">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
<path d="M6 6l12 12M18 6 6 18"/>
</svg>
</button>
</div>
<div class="modal-body">
<div class="settings-group">
<div class="settings-group-title">模型 API</div>
<label class="modal-label" for="provider-select">接口类型</label>
<select id="provider-select">
<option value="openai">OpenAI 兼容(SiliconFlow / OpenRouter / 中转站)</option>
<option value="anthropic">AnthropicClaude Messages API</option>
</select>
<label class="modal-label" for="api-key-input">API Key</label>
<input type="password" id="api-key-input" autocomplete="off" spellcheck="false" placeholder="未配置">
<label class="modal-label" for="base-url-input">Base URL</label>
<input type="text" id="base-url-input" autocomplete="off" spellcheck="false" placeholder="https://api.siliconflow.cn/v1/chat/completions">
<label class="modal-label" for="model-input">模型</label>
<input type="text" id="model-input" autocomplete="off" spellcheck="false" placeholder="Qwen/Qwen3-32B">
<p class="modal-hint" id="provider-hint">OpenAI 兼容模式使用 Bearer Token 认证,Base URL 填写 /v1/chat/completions 地址。</p>
<p class="modal-hint">字段留空表示不修改当前配置;API Key 输入新值即替换,保存后立即生效。</p>
</div>
<div class="settings-group">
<div class="settings-group-title">Docker</div>
<label class="modal-label" for="docker-socket-input">Docker Socket 地址</label>
<input type="text" id="docker-socket-input" autocomplete="off" spellcheck="false" placeholder="npipe:////./pipe/docker_engine">
<p class="modal-hint">默认操作本机 Docker。可填写远程 Docker 服务器的 socket 地址,例如 <code>unix:///var/run/docker.sock</code><code>npipe:////./pipe/docker_engine</code><code>tcp://192.168.1.10:2375</code>。保存后 Agent 的 Docker 工具立即生效。</p>
<label class="modal-label" for="coop-engine-select">协作引擎</label>
<select id="coop-engine-select">
<option value="pi">pipi-coding-agent,支持 openai + anthropic</option>
<option value="pigo">pigopigo,支持 openai + anthropic</option>
<option value="claude">Claude Code(仅支持 anthropic 协议)</option>
</select>
<p class="modal-hint">协作任务的默认引擎。run_coop 未显式指定 engine 时使用此默认值。Claude Code 仅支持 Anthropic 协议端点;pi 与 pigo 同时支持 openai 与 anthropic。</p>
<label class="modal-label" for="coop-mode-select">Worker 管理模式</label>
<select id="coop-mode-select">
<option value="local">本地子进程(无需 Docker,默认)</option>
<option value="docker">Docker 容器(需本机/远程 Docker daemon</option>
</select>
<p class="modal-hint">控制层对 worker 的管理方式。本地模式通过子进程直接运行 supervisor.sh,无需 Docker,适用于托管沙箱等环境;Docker 模式通过容器隔离运行,需 Docker daemon。</p>
</div>
</div>
<div class="modal-footer">
<button class="btn" id="reset-settings-btn">恢复默认</button>
<button class="btn btn-primary" id="save-settings-btn">保存</button>
</div>
</div>
</div>
<!-- ===== 授权弹窗(仅启用授权时展示) ===== -->
<div class="modal-overlay hidden" id="auth-modal">
<div class="modal">
<div class="modal-header">
<strong>授权验证</strong>
</div>
<div class="modal-body">
<p class="modal-hint">此实例已启用访问授权。请输入部署时配置的授权码以继续使用。</p>
<label class="modal-label" for="auth-code-input">授权码</label>
<input type="password" id="auth-code-input" autocomplete="off" spellcheck="false" placeholder="请输入授权码">
<p class="modal-hint auth-error hidden" id="auth-error"></p>
</div>
<div class="modal-footer">
<button class="btn btn-primary" id="auth-submit-btn">验证</button>
</div>
</div>
</div>
<div class="toast" id="toast"></div>
<script src="/static/app.js?v=20260814a"></script>
</body>
</html>