feat(monitor): 控制台新增「闭环探索日志」卡片——/api/explore 读取 docs/探索日志.md,Edge headless 截图验证渲染正常(http://127.0.0.1:8100)

This commit is contained in:
cbb
2026-10-20 05:35:42 +08:00
parent fe48b4e777
commit 29f3887be1
2 changed files with 8 additions and 0 deletions
+3
View File
@@ -144,6 +144,7 @@
.ctlrow { display: flex; gap: 10px; margin-top: 14px; align-items: center; flex-wrap: wrap; } .ctlrow { display: flex; gap: 10px; margin-top: 14px; align-items: center; flex-wrap: wrap; }
.ctlrow .hint { font-size: 12px; color: var(--dim2); } .ctlrow .hint { font-size: 12px; color: var(--dim2); }
.term { background: #070a10; border: 1px solid var(--line); border-radius: 10px; padding: 10px 12px; height: 260px; overflow: auto; font: 12px/1.55 var(--mono); color: #c9d4e3; white-space: pre-wrap; word-break: break-all; } .term { background: #070a10; border: 1px solid var(--line); border-radius: 10px; padding: 10px 12px; height: 260px; overflow: auto; font: 12px/1.55 var(--mono); color: #c9d4e3; white-space: pre-wrap; word-break: break-all; }
.explore { background: #0b0f16; border: 1px solid var(--line); border-radius: 10px; padding: 10px 12px; max-height: 560px; overflow: auto; font: 12px/1.7 var(--mono); color: #c9d4e3; white-space: pre-wrap; }
.term .e { color: var(--bad); } .term .c { color: var(--accent); } .term .g { color: var(--ok); } .term .e { color: var(--bad); } .term .c { color: var(--accent); } .term .g { color: var(--ok); }
.modal { position: fixed; inset: 0; background: #05080ed9; backdrop-filter: blur(7px); display: none; align-items: flex-start; justify-content: center; z-index: 40; padding: 5vh 16px 24px; overflow: auto; } .modal { position: fixed; inset: 0; background: #05080ed9; backdrop-filter: blur(7px); display: none; align-items: flex-start; justify-content: center; z-index: 40; padding: 5vh 16px 24px; overflow: auto; }
.modal.show { display: flex; } .modal.show { display: flex; }
@@ -213,6 +214,7 @@
<div class="card"><h2><span>部署形态评测</span><span class="r">策略 + 安全层</span></h2><div id="bench"></div></div> <div class="card"><h2><span>部署形态评测</span><span class="r">策略 + 安全层</span></h2><div id="bench"></div></div>
<div class="card"><h2><span>验收汇总</span><span class="r">方案 §9.2</span></h2><div id="accept"></div></div> <div class="card"><h2><span>验收汇总</span><span class="r">方案 §9.2</span></h2><div id="accept"></div></div>
</section> </section>
<section class="card"><h2><span>闭环探索日志</span><span class="r">docs/探索日志.md</span></h2><div class="explore" id="explore">加载中…</div></section>
<section class="card"><h2><span>终端输出</span><span class="r" id="termnote">ml/runs/console.log</span></h2><div class="term" id="term"></div></section> <section class="card"><h2><span>终端输出</span><span class="r" id="termnote">ml/runs/console.log</span></h2><div class="term" id="term"></div></section>
<footer id="foot"></footer> <footer id="foot"></footer>
</main> </main>
@@ -834,6 +836,7 @@ $('logy').onclick = () => { logyOn = !logyOn; $('logy').classList.toggle('on', l
for (const id of PREF_IDS) { $(id).onchange = prefSave; $(id).oninput = prefSave; } for (const id of PREF_IDS) { $(id).onchange = prefSave; $(id).oninput = prefSave; }
$('run').onchange = () => { userPicked = true; cur = null; poll(); }; $('run').onchange = () => { userPicked = true; cur = null; poll(); };
$('scenario').onchange = () => { api.scenario($('scenario').value); cur = null; userPicked = false; conOff = 0; $('run').innerHTML = ''; poll(); }; $('scenario').onchange = () => { api.scenario($('scenario').value); cur = null; userPicked = false; conOff = 0; $('run').innerHTML = ''; poll(); };
api('/api/explore').then(d => { const el = $('explore'); el.textContent = (d && d.md) ? d.md : '(demo 模式:无探索日志)'; }).catch(() => { const el = $('explore'); el.textContent = '(无法读取 docs/探索日志.md)'; });
$('auto').onclick = () => { const on = !$('auto').classList.contains('on'); $('auto').classList.toggle('on', on); clearInterval(timer); if (on) timer = setInterval(poll, 1000); }; $('auto').onclick = () => { const on = !$('auto').classList.contains('on'); $('auto').classList.toggle('on', on); clearInterval(timer); if (on) timer = setInterval(poll, 1000); };
timer = setInterval(poll, 1000); poll(); timer = setInterval(poll, 1000); poll();
+5
View File
@@ -608,6 +608,11 @@ class H(BaseHTTPRequestHandler):
if u.path == '/api/status': return self.send(200, json.dumps(status()), 'application/json') if u.path == '/api/status': return self.send(200, json.dumps(status()), 'application/json')
if u.path == '/api/job': return self.send(200, json.dumps(JOB.state()), 'application/json') if u.path == '/api/job': return self.send(200, json.dumps(JOB.state()), 'application/json')
if u.path == '/api/console': return self.send(200, json.dumps(console(int(q.get('offset', ['0'])[0]))), 'application/json') if u.path == '/api/console': return self.send(200, json.dumps(console(int(q.get('offset', ['0'])[0]))), 'application/json')
if u.path == '/api/explore': # 2026-10-20:闭环探索日志(docs/探索日志.md)回写控制台
try:
with open(os.path.join(ROOT, 'docs', '探索日志.md'), encoding='utf-8') as f: md = f.read()
except Exception: md = ''
return self.send(200, json.dumps({'md': md}), 'application/json')
self.send(404, 'not found', 'text/plain') self.send(404, 'not found', 'text/plain')
except Exception as e: except Exception as e:
self.send(500, json.dumps({'error': str(e)}), 'application/json') self.send(500, json.dumps({'error': str(e)}), 'application/json')