优化:多Agent协作表单按模式动态展示 + DAG画布文字可读性

1. 多Agent协作:
- 选择不同模式时表单只显示该模式专属参数(主管:参与Agent+上下文;评审:+返工轮次/分数线;辩论:+质询轮次/立场)
- 参数改为下拉选择(轮次/分数线预设档位),提示文案按模式动态切换,点模式卡片可直接切换

2. DAG画布:
- 修复文字看不清:CSS .dag-node fill 覆盖节点内联状态色 → 移除,文字改纯白+半透明白
- 节点加宽 110→140px,标题超 13 字符自动省略号,不再溢出
- 节点描边加亮(#5a6b8f)提升状态区分度
This commit is contained in:
2026-08-12 23:23:36 +08:00
parent 5deb447c8e
commit cf092da69a
4 changed files with 48 additions and 24 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

+44 -21
View File
@@ -353,6 +353,11 @@ function drawDag() {
const ready = tasks.filter(t => (t.status === 'todo' || t.status === 'failed') && (!t.depends_on || t.depends_on.every(d => idMap[d]?.status === 'done')));
const body = $('#tab-body');
if (!body) return;
const NW = 140, NHW = 70; // 节点宽 / 半宽
const dagText = t => {
const raw = `#${t.id} ${t.title}`;
return raw.length > 13 ? raw.slice(0, 13) + '…' : raw;
};
body.innerHTML = `
<div class="toolbar" style="margin-bottom:10px">
<span class="pager-tip">共 ${tasks.length} 个节点 · ${edges.length} 条依赖边${ready.length ? ` · ${ready.length} 个任务就绪` : ''} · 拖节点调位 / 滚轮缩放 / 拖空白平移 / 双击复位</span>
@@ -367,14 +372,14 @@ function drawDag() {
if (!a || !b) return '';
const mx = (a.x + b.x) / 2;
const act = idMap[e.from].status === 'done';
return `<path data-e="${e.from}-${e.to}" class="dag-edge${act ? ' act' : ''}" d="M${a.x + 55},${a.y} C${mx},${a.y} ${mx},${b.y} ${b.x - 55},${b.y}"/>`;
return `<path data-e="${e.from}-${e.to}" class="dag-edge${act ? ' act' : ''}" d="M${a.x + NHW},${a.y} C${mx},${a.y} ${mx},${b.y} ${b.x - NHW},${b.y}"/>`;
}).join('')}
${tasks.map(t => {
const p = dagState.pos[t.id];
const col = colors[t.status];
return `<g class="dag-node" data-id="${t.id}" style="cursor:grab">
<rect x="${p.x - 55}" y="${p.y - 24}" width="110" height="48" fill="${col}" stroke="#3a4a6a"/>
<text class="dag-label" x="${p.x}" y="${p.y - 2}" fill="#fff">#${t.id} ${esc(t.title).slice(0, 10)}</text>
<rect x="${p.x - NHW}" y="${p.y - 24}" width="${NW}" height="48" fill="${col}" stroke="#5a6b8f"/>
<text class="dag-label" x="${p.x}" y="${p.y - 2}">${esc(dagText(t))}</text>
<text class="dag-sub" x="${p.x}" y="${p.y + 15}">${STATUS[t.status].label}${t.depends_on?.length ? ' · 前置' + t.depends_on.length : ''}</text>
</g>`;
}).join('')}
@@ -429,7 +434,7 @@ function bindDagEvents() {
const nid = Number(g.dataset.id);
const pp = s.pos[nid];
if (!pp) return;
g.querySelector('rect').setAttribute('x', pp.x - 55);
g.querySelector('rect').setAttribute('x', pp.x - 70);
g.querySelector('rect').setAttribute('y', pp.y - 24);
const ts = g.querySelectorAll('text');
ts[0].setAttribute('x', pp.x); ts[0].setAttribute('y', pp.y - 2);
@@ -451,7 +456,7 @@ function bindDagEvents() {
const path = svgEl.querySelector(`path[data-e="${d}-${tid}"]`);
if (!path) return;
const mx = (b.x + a.x) / 2;
path.setAttribute('d', `M${b.x + 55},${b.y} C${mx},${b.y} ${mx},${a.y} ${a.x - 55},${a.y}`);
path.setAttribute('d', `M${b.x + 70},${b.y} C${mx},${b.y} ${mx},${a.y} ${a.x - 70},${a.y}`);
});
});
});
@@ -1124,6 +1129,20 @@ function renderAgents() {
const {modes, workers, runs} = agentsCtx;
const wOpts = workers.map(w =>
`<option value="${w.id}">${esc(w.name)}${esc(w.model)}</option>`).join('');
// 各模式专属参数定义(m: 适用模式列表)
const paramRows = [
{id: 'ag-workers', m: 'all', html: `<label data-modes="all">参与 Agent(可多选)<select id="ag-workers" multiple size="3">${wOpts}</select></label>`},
{id: 'ag-rounds', m: 'review', html: `<label data-modes="review">最大返工轮次<select id="ag-rounds"><option value="1">1 轮(快)</option><option value="2" selected>2 轮(推荐)</option><option value="3">3 轮(严苛)</option></select></label>`},
{id: 'ag-pass', m: 'review', html: `<label data-modes="review">通过分数线<select id="ag-pass"><option value="60">60 分(宽松)</option><option value="70" selected>70 分(标准)</option><option value="80">80 分(严格)</option><option value="90">90 分(严苛)</option></select></label>`},
{id: 'ag-debate-rounds', m: 'debate', html: `<label data-modes="debate">质询轮次<select id="ag-debate-rounds"><option value="1" selected>1 轮</option><option value="2">2 轮</option><option value="3">3 轮</option></select></label>`},
{id: 'ag-stances', m: 'debate', html: `<label data-modes="debate">辩论立场(逗号分隔,按辩手顺序分配)<input id="ag-stances" placeholder="正方,反方,中立"></label>`},
{id: 'ag-context', m: 'all', html: `<label data-modes="all" style="grid-column:span 2">背景上下文<textarea id="ag-context" rows="2" placeholder="选填:背景资料、约束条件、参考知识"></textarea></label>`},
];
const modeHints = {
supervisor: '💡 第 1 个 Agent 担任主管(负责拆解与最终合成),其余 Agent 并行执行子任务。',
review: '💡 第 1 个 Agent 为执行者,第 2 个为评审(只选 1 个时由同一 Agent 自评)。低于分数线自动返工,最多重试所选轮次。',
debate: '💡 除最后 1 个 Agent 为裁判外,其余均为辩手;立场按顺序分配,裁判综合裁决输出共识。',
};
$('#main').innerHTML = `
<h1 class="page-title">多 Agent 协作<small>主管 / 评审 / 辩论 三种团队协作模式</small></h1>
<div class="grid g3">${modes.map(m => `
@@ -1131,25 +1150,18 @@ function renderAgents() {
<div style="font-size:26px">${m.icon}</div>
<div class="sec-title" style="margin:6px 0 4px">${esc(m.name)}</div>
<div style="font-size:12px;color:var(--muted);min-height:34px">${esc(m.desc)}</div>
<div style="margin-top:8px"><span class="tag">${m.params.length} 项参数</span></div>
<div style="margin-top:8px"><span class="tag">${m.id === 'supervisor' ? '拆解+委派+合成' : m.id === 'review' ? '产出+评审+返工' : '立论+质询+裁决'}</span></div>
</div>`).join('')}</div>
<div class="card" style="margin-top:14px">
<div class="sec-title" style="margin-top:0">⚡ 发起协作运行</div>
<div class="form-grid">
<label>协作模式<select id="ag-mode">${modes.map(m => `<option value="${m.id}">${m.icon} ${esc(m.name)}</option>`).join('')}</select></label>
<label>标题<input id="ag-title" placeholder="选填,默认自动生成"></label>
<label>任务主题 / 议题<textarea id="ag-topic" rows="2" placeholder="例如:为新产品策划上市方案…"></textarea></label>
<label>参与 Agent(可多选)<select id="ag-workers" multiple size="3">${wOpts}</select></label>
<label>最大返工轮次(评审)<input id="ag-rounds" type="number" value="2"></label>
<label>通过分数线(评审)<input id="ag-pass" type="number" value="70"></label>
<label>质询轮次(辩论)<input id="ag-debate-rounds" type="number" value="1"></label>
<label>辩论立场(逗号分隔)<input id="ag-stances" placeholder="正方,反方,裁判"></label>
<label style="grid-column:span 2">背景上下文<textarea id="ag-context" rows="2" placeholder="选填:背景资料、约束条件、参考知识"></textarea></label>
</div>
<div style="margin-top:10px">
<button class="btn primary" id="ag-run">🚀 发起运行</button>
<span class="tab-note">💡 评审模式第 1 个 Agent 为执行者,第 2 个(若有)为评审;辩论模式最后一个为裁判。</span>
<label style="grid-column:span 2">任务主题 / 议题<textarea id="ag-topic" rows="2" placeholder="例如:为新产品策划上市方案…"></textarea></label>
${paramRows.map(r => r.html).join('')}
</div>
<div class="tab-note" id="ag-hint" style="margin-top:10px">${modeHints.supervisor}</div>
<div style="margin-top:10px"><button class="btn primary" id="ag-run">🚀 发起运行</button></div>
</div>
<div class="card" style="margin-top:14px">
<div class="sec-title" style="margin-top:0">📋 运行记录 <button class="btn mini" style="float:right" onclick="location.hash='#/agents'">刷新</button></div>
@@ -1166,10 +1178,21 @@ function renderAgents() {
</tr>`).join('') || '<tr><td colspan="8" class="empty-td">暂无运行记录</td></tr>'}
</tbody></table>
</div>`;
$('#ag-mode').addEventListener('change', e => {
const m = modes.find(x => x.id === e.target.value);
toast('已选择:' + m.name, '');
});
// 模式切换:动态显示/隐藏专属参数
const applyMode = mode => {
$$('.form-grid [data-modes]').forEach(el => {
const ms = el.dataset.modes.split(',');
el.style.display = (ms.includes('all') || ms.includes(mode)) ? '' : 'none';
});
$('#ag-hint').textContent = modeHints[mode] || '';
$$('.mode-card').forEach(c => c.style.outline = c.dataset.mode === mode ? '2px solid var(--accent)' : '');
};
$('#ag-mode').addEventListener('change', e => applyMode(e.target.value));
$$('.mode-card').forEach(c => c.addEventListener('click', () => {
$('#ag-mode').value = c.dataset.mode;
applyMode(c.dataset.mode);
}));
applyMode($('#ag-mode').value);
$('#ag-run').addEventListener('click', async () => {
const mode = $('#ag-mode').value;
const topic = $('#ag-topic').value.trim();
+4 -3
View File
@@ -133,10 +133,11 @@ tr:hover td{background:var(--panel2)}
.tabs a.active{color:var(--accent);border-bottom-color:var(--accent)}
.dag-box{background:var(--panel);border:1px solid var(--border);border-radius:12px;overflow:hidden;height:520px;position:relative;touch-action:none;user-select:none;cursor:grab}
.dag-box svg{display:block;transition:transform .05s linear}
.dag-node{fill:var(--panel2);stroke:var(--border);stroke-width:1.5;rx:10;cursor:pointer}
.dag-node{stroke:var(--border);stroke-width:1.5;rx:10;cursor:grab}
.dag-node:hover{stroke:var(--accent)}
.dag-label{font-size:12px;fill:var(--text);text-anchor:middle}
.dag-sub{font-size:10px;fill:var(--muted);text-anchor:middle}
.dag-node rect{stroke-width:1.5;rx:10}
.dag-label{font-size:12px;fill:#ffffff;text-anchor:middle;pointer-events:none}
.dag-sub{font-size:10px;fill:rgba(255,255,255,.78);text-anchor:middle;pointer-events:none}
.dag-edge{stroke:var(--border);stroke-width:1.5;fill:none;marker-end:url(#arrow)}
.dag-edge.act{stroke:var(--accent)}
.alert-row{border-left:3px solid var(--border);padding:12px 14px;margin-bottom:8px;background:var(--panel2);border-radius:0 10px 10px 0;cursor:pointer}