Files
maestro/web/index.html
T
wangjia 6bf2b3619c feat(web): 项目配置面板补全——checks/自动放行/agentRules/预算/角色模型编辑
此前 checks、autoApprovePlan/Exec、agentRules、budgetUsd、models 只能经 API 改,
看板无编辑口。现扩展项目「配置」面板:
- 分项检查:lint / typecheck / build 三命令(拼成 checks JSON,全空=null)
- 硬闸/自动放行:autoApprovePlan / autoApproveExec 两个勾选
- 角色模型:executor/planner/reviewer/conflict 四个输入(填模型 id 字符串,
  或 {"easy":..,"hard":..} JSON;留空=默认 opus-4.8;非法 JSON 提示报错)
- 项目 Agent 规范(agentRules)textarea
- 成本预算:budgetUsd + budgetPeriod(按天/月)
toggle-config 回填全部新字段;save-config 一并 PATCH。新增 zh/en i18n 标签 +
.cfg-subhead/.cfg-check 样式。web/ 静态、无需 rebuild。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 07:34:49 +08:00

270 lines
19 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">
<title>MAESTRO · 任务调度台</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&family=Noto+Sans+SC:wght@400;500;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="app">
<!-- ════════ 左栏:项目 ════════ -->
<aside id="sidebar">
<div class="logo">
<div class="logo-word">MAESTRO<span class="cursor"></span></div>
<div class="logo-sub" data-i18n="logoSub">多项目任务调度台</div>
</div>
<div class="side-head">
<span class="head-mark"></span><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 7a2 2 0 0 1 2-2h4l2 3h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2Z"/></svg> <span data-i18n="projects">项目</span>
<button class="btn btn-ghost btn-xs" data-action="open-new-project"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="12" y1="4" x2="12" y2="20" stroke="var(--icon-accent,#5fdd7d)"/><line x1="4" y1="12" x2="20" y2="12" stroke="var(--icon-accent,#5fdd7d)"/></svg> <span data-i18n="newProject">新建</span></button>
</div>
<ul id="projectList" class="project-list"></ul>
<div class="side-foot">
<span id="wsDot" class="ws-dot off"></span>
<span id="wsText" data-i18n="connecting">连接中…</span>
</div>
</aside>
<!-- ════════ 中栏:闸 + 任务树 ════════ -->
<main id="main">
<header id="topbar">
<div>
<div id="projTitle" class="proj-title"></div>
<div id="projMeta" class="proj-meta"></div>
</div>
<div class="spacer"></div>
<div id="badgeGroup" class="badge-group" hidden>
<span id="bdgGate" class="bdg bdg-gate" data-i18n-title="badgeGateTip" title="等待你裁决的审批闸">
<span class="bdg-ico"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3L22 21L2 21Z"/><line x1="12" y1="10" x2="12" y2="14" stroke="var(--icon-accent,#b88ef5)"/><line x1="12" y1="17.5" x2="12" y2="17.51" stroke="var(--icon-accent,#b88ef5)"/></svg></span><span class="bdg-n">0</span><span class="bdg-label" data-i18n="badgeGate">待审批</span>
</span>
<span id="bdgReady" class="bdg bdg-ready" data-i18n-title="badgeReadyTip" title="可执行(依赖已满足)+ 排队中">
<span class="bdg-ico"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M7 4L19 12L7 20Z" stroke="var(--icon-accent,#5fdd7d)"/></svg></span><span class="bdg-n">0</span><span class="bdg-label" data-i18n="badgeReady">待执行</span>
</span>
<span id="bdgRun" class="bdg bdg-run" data-i18n-title="badgeRunTip" title="agent 正在执行">
<span class="bdg-ico"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="8"/><circle cx="12" cy="12" r="3" fill="var(--icon-accent,#59c8d8)" stroke="none"/></svg></span><span class="bdg-n">0</span><span class="bdg-label" data-i18n="badgeRun">执行中</span>
</span>
<span id="bdgBlocked" class="bdg bdg-blocked" data-i18n-title="badgeBlockedTip" title="被依赖阻塞(依赖完成后自动放行)">
<span class="bdg-ico"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="7" height="7" rx="1.5"/><rect x="14" y="14" width="7" height="7" rx="1.5"/><line x1="10" y1="10" x2="14" y2="14" stroke="var(--icon-accent,#f0b429)"/></svg></span><span class="bdg-n">0</span><span class="bdg-label" data-i18n="badgeBlocked">被阻塞</span>
</span>
<span id="bdgTotal" class="bdg bdg-total" data-i18n-title="badgeTotalTip" title="未完成任务总量(不含 done / 已拆解容器 / 取消)">
<span class="bdg-ico">Σ</span><span class="bdg-n">0</span><span class="bdg-label" data-i18n="badgeTotal">总量</span>
</span>
</div>
<div id="topActions" class="top-actions" hidden>
<button class="btn btn-xs" data-action="toggle-config" data-i18n-title="configTip" title="项目配置(并发 / 工作模式 / todo 同步)"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="3" y1="6" x2="12" y2="6"/><circle cx="15" cy="6" r="2.5" stroke="var(--icon-accent,#5fdd7d)"/><line x1="18" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="5.5" y2="12"/><circle cx="9" cy="12" r="2.5" stroke="var(--icon-accent,#5fdd7d)"/><line x1="12.5" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="12.5" y2="18"/><circle cx="16" cy="18" r="2.5" stroke="var(--icon-accent,#5fdd7d)"/><line x1="19.5" y1="18" x2="21" y2="18"/></svg> <span data-i18n="config">配置</span></button>
</div>
<button id="langBtn" class="btn btn-xs btn-lang" data-action="toggle-lang" data-i18n-title="langTip" title="切换语言 / Switch language">中文</button>
</header>
<section id="configPanel" class="panel config-panel" hidden>
<div class="form-row">
<label class="narrow"><span data-i18n="maxConcurrency">最大并发</span>
<input id="cfgConcurrency" type="number" min="1" step="1" value="1">
</label>
<label><span data-i18n="workMode">工作模式</span>
<select id="cfgAutonomy">
<option value="manual">manual · 手动</option>
<option value="auto-easy">auto-easy · 自动执行 Easy</option>
<option value="auto-approved">auto-approved · 自动执行已批准</option>
</select>
</label>
<span id="cfgCurrent" class="cfg-current"></span>
<button class="btn btn-solid" data-action="save-config" data-i18n="save">保存配置</button>
<button class="btn" data-action="toggle-config" data-i18n="collapse">收起</button>
</div>
<div class="form-row">
<label class="grow"><span data-i18n="projLogo">Logo</span>
<input id="cfgLogo" type="text" data-i18n-ph="logoPh" placeholder="图片 URL 或仓库内相对路径;留空=自动(仓库 logo→git 头像→首字母)">
</label>
</div>
<div class="form-row">
<label class="grow"><span data-i18n="verifyCmd">校验命令</span>
<input id="cfgVerifyCmd" type="text" data-i18n-ph="verifyPh" placeholder="npm test · go test ./... (可空)" autocomplete="off">
</label>
<label class="grow"><span data-i18n="model">项目 Model</span>
<input id="cfgModel" type="text" data-i18n-ph="modelPh" placeholder="claude-opus-4-5(可空,留空用全局默认)" autocomplete="off">
</label>
</div>
<div class="cfg-subhead" data-i18n="cfgChecks">分项检查(lint / typecheck / build</div>
<div class="form-row">
<label class="grow"><span data-i18n="cfgLint">lint 命令</span>
<input id="cfgLint" type="text" data-i18n-ph="cfgCmdPh" placeholder="留空=跳过此项" autocomplete="off">
</label>
<label class="grow"><span data-i18n="cfgTypecheck">typecheck 命令</span>
<input id="cfgTypecheck" type="text" data-i18n-ph="cfgCmdPh" placeholder="留空=跳过此项" autocomplete="off">
</label>
<label class="grow"><span data-i18n="cfgBuild">build 命令</span>
<input id="cfgBuild" type="text" data-i18n-ph="cfgCmdPh" placeholder="留空=跳过此项" autocomplete="off">
</label>
</div>
<div class="cfg-subhead" data-i18n="cfgGates">硬闸 / 自动放行</div>
<div class="form-row cfg-checks-row">
<label class="cfg-check"><input id="cfgAutoPlan" type="checkbox"> <span data-i18n="cfgAutoPlan">自动放行 plan</span></label>
<label class="cfg-check"><input id="cfgAutoExec" type="checkbox"> <span data-i18n="cfgAutoExec">自动放行 exec</span></label>
</div>
<div class="cfg-subhead" data-i18n="cfgModels">角色模型(留空=默认 opus-4.8)</div>
<div class="form-row">
<label class="grow"><span data-i18n="cfgMdlExecutor">executor</span>
<input id="cfgMdlExecutor" type="text" placeholder="claude-opus-4-8" autocomplete="off">
</label>
<label class="grow"><span data-i18n="cfgMdlPlanner">planner</span>
<input id="cfgMdlPlanner" type="text" placeholder="claude-opus-4-8" autocomplete="off">
</label>
<label class="grow"><span data-i18n="cfgMdlReviewer">reviewer</span>
<input id="cfgMdlReviewer" type="text" placeholder="claude-opus-4-8" autocomplete="off">
</label>
<label class="grow"><span data-i18n="cfgMdlConflict">conflict</span>
<input id="cfgMdlConflict" type="text" placeholder="claude-opus-4-8" autocomplete="off">
</label>
</div>
<div class="form-row">
<label class="grow"><span data-i18n="cfgAgentRules">项目 Agent 规范</span>
<textarea id="cfgAgentRules" rows="3" data-i18n-ph="cfgAgentRulesPh" placeholder="项目专属执行约定、风格、禁忌……(可空)" autocomplete="off"></textarea>
</label>
</div>
<div class="form-row">
<label class="narrow"><span data-i18n="cfgBudget">成本预算</span>
<input id="cfgBudget" type="number" min="0" step="0.5" data-i18n-ph="cfgBudgetPh" placeholder="USD 上限(可空=不限)" autocomplete="off">
</label>
<label class="narrow"><span>&nbsp;</span>
<select id="cfgBudgetPeriod">
<option value="month" data-i18n="cfgBudgetMonth">按月</option>
<option value="day" data-i18n="cfgBudgetDay">按天</option>
</select>
</label>
</div>
<div class="form-row cfg-sync-row">
<span id="syncMeta" class="sync-meta"></span>
<button id="btnSync" class="btn btn-xs" data-action="sync-todo"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8"/><path d="M21 3v5h-5" stroke="var(--icon-accent,#5fdd7d)"/></svg> <span data-i18n="sync">同步 todo</span></button>
</div>
</section>
<section id="agentSection" class="agent-section">
<div class="sec-head"><span class="head-mark cyan"></span><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="8"/><circle cx="12" cy="12" r="3" fill="var(--icon-accent,#59c8d8)" stroke="none"/></svg> <span data-i18n="agentSection">Agent 执行</span><span id="agentTotal" class="agent-total"></span></div>
<div id="agentBody"></div>
</section>
<section id="gateSection" class="gate-section" hidden>
<div class="gate-stripe"></div>
<div class="sec-head gate-head"><span class="head-mark violet"></span><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3L22 21L2 21Z"/><line x1="12" y1="10" x2="12" y2="14" stroke="var(--icon-accent,#b88ef5)"/><line x1="12" y1="17.5" x2="12" y2="17.51" stroke="var(--icon-accent,#b88ef5)"/></svg> <span data-i18n="gateSection">审批闸 · 等待裁决</span></div>
<div id="gateList"></div>
</section>
<section id="taskSection">
<div class="sec-head">
<span class="head-mark"></span><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="4" y="4" width="16" height="16" rx="2.5"/><polyline points="8 12 11 15 16 9" stroke="var(--icon-accent,#5fdd7d)"/></svg> <span data-i18n="taskSection">任务树</span>
<button class="btn btn-ghost btn-xs" data-action="toggle-new-task"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="12" y1="4" x2="12" y2="20" stroke="var(--icon-accent,#5fdd7d)"/><line x1="4" y1="12" x2="20" y2="12" stroke="var(--icon-accent,#5fdd7d)"/></svg> <span data-i18n="newTask">新建任务</span></button>
</div>
<form id="newTaskPanel" class="panel form-panel" hidden>
<div class="form-row">
<label class="grow"><span data-i18n="titleLabel">标题</span> <span class="req">*</span>
<input name="title" type="text" data-i18n-ph="titlePlaceholder" placeholder="要做什么" autocomplete="off">
</label>
</div>
<div class="form-row">
<label><span data-i18n="complexity">复杂度</span> <span class="req">*</span>
<span class="seg" id="cplxSeg">
<input type="radio" name="complexity" value="auto" id="cx-a" checked><label for="cx-a" class="seg-a" title="由模型判断复杂度">AUTO</label>
<input type="radio" name="complexity" value="hard" id="cx-h"><label for="cx-h" class="seg-h">HARD</label>
<input type="radio" name="complexity" value="medium" id="cx-m"><label for="cx-m" class="seg-m">MEDIUM</label>
<input type="radio" name="complexity" value="easy" id="cx-e"><label for="cx-e" class="seg-e">EASY</label>
</span>
</label>
<label><span data-i18n="parentTask">父任务</span>
<select name="parentId"><option value="">(顶层)</option></select>
</label>
<label class="narrow"><span data-i18n="priority">优先级</span>
<select name="priority">
<option value="0" data-i18n="p0">P0 · 高</option>
<option value="1" selected data-i18n="p1">P1 · 中</option>
<option value="2" data-i18n="p2">P2 · 低</option>
</select>
</label>
</div>
<div class="form-row form-actions">
<button type="submit" class="btn btn-solid" data-i18n="create">创建任务</button>
<button type="button" class="btn" data-action="toggle-new-task" data-i18n="cancel">取消</button>
</div>
</form>
<div id="filterBar" class="filter-bar" hidden>
<div class="filter-row">
<span class="filter-search-wrap">
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="10.5" cy="10.5" r="6.5"/><line x1="15.5" y1="15.5" x2="21" y2="21" stroke="var(--icon-accent,#5fdd7d)"/></svg>
<input id="filterKw" type="text" data-i18n-ph="searchPh" placeholder="搜索标题…" autocomplete="off">
</span>
<span id="filterCplx" class="fchips"></span>
<span class="spacer"></span>
<span id="filterCount" class="filter-count"></span>
<button class="btn btn-ghost btn-xs" id="filterClear" data-action="filter-clear" hidden><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="5" y1="5" x2="19" y2="19"/><line x1="19" y1="5" x2="5" y2="19"/></svg> <span data-i18n="clearFilter">✕ 清除筛选</span></button>
</div>
<div id="filterStatus" class="filter-row"></div>
</div>
<div id="taskTree"></div>
</section>
<!-- ════════ 指标面板(执行时长 / 成功率 / 复审 / 重试 / 额度估算) ════════ -->
<section id="metricsSection" class="metrics-section" hidden>
<div class="sec-head"><span class="head-mark cyan"></span><span data-i18n="metricsSection">指标 · 健康度与成本</span><span id="metricsScope" class="metrics-scope"></span></div>
<div id="metricsBody"></div>
</section>
<!-- ════════ 已归档(done / 取消,时间倒序分页) ════════ -->
<section id="archiveSection" hidden>
<div class="sec-head"><span class="head-mark"></span><span data-i18n="archiveSection">已归档</span> · <span id="archiveCount">0</span></div>
<div id="archiveList"></div>
<div id="archivePager" class="archive-pager"></div>
</section>
</main>
<!-- ════════ 右栏:事件流 ════════ -->
<aside id="eventPanel">
<div class="sec-head"><span class="head-mark"></span><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="4" y1="12" x2="19" y2="12"/><polyline points="13 6 19 12 13 18" stroke="var(--icon-accent,#59c8d8)"/></svg> <span data-i18n="events">事件流</span></div>
<ul id="eventList" class="event-list"></ul>
</aside>
</div>
<!-- 新建项目 模态 -->
<div id="modalRoot" class="modal-root" hidden>
<div class="modal-mask" data-action="close-modal"></div>
<form id="newProjectForm" class="modal panel">
<div class="sec-head"><span class="head-mark"></span><span data-i18n="newProject">新建项目</span></div>
<label><span data-i18n="titleLabel">名称</span> <span class="req">*</span>
<input name="name" type="text" placeholder="my-project" autocomplete="off">
</label>
<label><span>Repo path</span> <span class="req">*</span>
<input name="repoPath" type="text" placeholder="/path/to/repo" autocomplete="off">
</label>
<label><span data-i18n="branch">Default branch</span>
<input name="defaultBranch" type="text" placeholder="main">
</label>
<label><span data-i18n="verifyCmd">Verify command</span>
<input name="verifyCmd" type="text" data-i18n-ph="verifyPh" placeholder="npm test(可空)">
</label>
<div class="form-row form-actions">
<button type="submit" class="btn btn-solid" data-i18n="create">创建</button>
<button type="button" class="btn" data-action="close-modal" data-i18n="cancel">取消</button>
</div>
</form>
</div>
<div id="previewRoot" class="preview-root" hidden></div>
<div id="archiveModalRoot" class="preview-root" hidden></div>
<div id="toastRoot" class="toast-root"></div>
<script src="app.js"></script>
</body>
</html>