agent_detail.html 8.82 KB
{% extends "base.html" %}
{% block title %}{{ profile_name }} - Agent Detail{% endblock %}
{% block page_title %}Agent: {{ profile_name }}{% endblock %}
{% block body %}
<style>
body{overflow-y:auto!important;height:auto!important;}
.agent-detail-container{max-width:960px;margin:0 auto;padding:20px;}
.agent-detail-header{display:flex;align-items:center;gap:12px;margin-bottom:20px;}
.agent-detail-header h1{font-size:20px;color:#2c3e50;margin:0;}
.agent-detail-header .default-badge{font-size:10px;background:#27ae60;color:white;padding:2px 8px;border-radius:8px;}
.agent-back{margin-bottom:16px;}
.agent-back a{color:#3498db;text-decoration:none;font-size:14px;}
.agent-section{background:white;border-radius:8px;padding:16px;margin-bottom:16px;box-shadow:0 1px 2px rgba(0,0,0,0.05);}
.agent-section h2{font-size:15px;color:#2c3e50;margin:0 0 12px;border-bottom:1px solid #f0f0f0;padding-bottom:8px;}
.agent-config{display:flex;flex-wrap:wrap;gap:8px;}
.agent-config-item{background:#f8f9fa;padding:6px 12px;border-radius:6px;font-size:13px;}
.agent-config-item strong{color:#2c3e50;}
.agent-soul{font-size:13px;color:#555;white-space:pre-wrap;max-height:400px;overflow-y:auto;line-height:1.6;}
.agent-skills{display:flex;flex-wrap:wrap;gap:6px;}
.agent-skill-badge{background:#e8f4fd;color:#3498db;padding:3px 10px;border-radius:12px;font-size:12px;}
.agent-meta-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px;}
.agent-meta-item{font-size:13px;color:#666;}
.agent-meta-item strong{color:#2c3e50;}
@media(max-width:768px){
  .agent-meta-grid{grid-template-columns:1fr;}
}
</style>
<div class="agent-detail-container">
<div class="agent-back">
<a href="/agents">← 返回 Agents</a>
</div>
<div class="agent-detail-header">
<h1>🤖 {{ profile_name }}</h1>
{% if is_default %}<span class="default-badge">◆ default</span>{% endif %}
</div>

<div class="agent-meta-grid" style="margin-bottom:16px;">
<div class="agent-meta-item"><strong>Skills:</strong> {{ skills_count }}</div>
<div class="agent-meta-item"><strong>Sessions:</strong> {{ sessions_count }}</div>
<div class="agent-meta-item"><strong>Memory:</strong> {% if memory_exists %}{{ memory_size }} bytes{% else %}—{% endif %}</div>
<div class="agent-meta-item"><strong>.env:</strong> {% if env_exists %}✅{% else %}—{% endif %}</div>
</div>

<div class="agent-section">
<h2>Config</h2>
<div class="agent-config">
{% for key, val in config.items() %}
<div class="agent-config-item"><strong>{{ key }}:</strong> {{ val }}</div>
{% endfor %}
</div>
</div>

<div class="agent-section">
<h2>执行日志</h2>
<div id="log-loading" class="loading"><div class="spinner"></div><div>加载中...</div></div>
<div id="log-empty" class="empty-state" style="display:none">
<div class="icon">📝</div>
<div>暂无执行日志</div>
</div>
<div id="log-list"></div>
</div>

<div class="agent-section">
<h2>SOUL.md</h2>
<div class="agent-soul">{{ soul_text }}</div>
</div>

{% if skills %}
<div class="agent-section">
<h2>Skills ({{ skills_count }})</h2>
<div class="agent-skills">
{% for skill in skills %}
<span class="agent-skill-badge">{{ skill.name }}</span>
{% endfor %}
</div>
</div>
{% endif %}
</div>

<script>
(function(){
function escHtml(s){return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');}
function fmtTs(ts){return ts ? new Date(ts*1000).toLocaleString('zh-CN') : '-'}

var agentName = escHtml('{{ profile_name }}');
var listEl = document.getElementById('log-list');
var loadingEl = document.getElementById('log-loading');
var emptyEl = document.getElementById('log-empty');

function toggleDetail(sid, el){
  var detail = document.getElementById('log-detail-' + sid);
  if(detail){
    detail.style.display = detail.style.display === 'none' ? 'block' : 'none';
    return;
  }
  detail = document.createElement('div');
  detail.id = 'log-detail-' + sid;
  detail.className = 'log-detail';
  detail.style.display = 'block';
  detail.innerHTML = '<div class="loading"><div class="spinner"></div><div>加载消息...</div></div>';
  el.parentNode.insertBefore(detail, el.nextSibling);
  fetch('/api/agent/' + encodeURIComponent(agentName) + '/session/' + encodeURIComponent(sid))
    .then(function(r){return r.json();})
    .then(function(j){
      if(!j.ok || !j.data || !j.data.messages){
        detail.innerHTML = '<div class="log-error">加载失败</div>';
        return;
      }
      var msgs = j.data.messages;
      var html = '<div class="log-messages">';
      for(var i=0;i<msgs.length;i++){
        var m = msgs[i];
        var roleClass = m.role === 'user' ? 'msg-user' : (m.role === 'assistant' ? 'msg-assistant' : 'msg-tool');
        var meta = [];
        if(m.tool_name) meta.push('tool:' + escHtml(m.tool_name));
        if(m.tool_calls_summary && m.tool_calls_summary.length) meta.push('calls:' + escHtml(m.tool_calls_summary.join(',')));
        html += '<div class="log-message ' + roleClass + '">';
        html += '<div class="log-message-role">' + escHtml(m.role) + (meta.length ? ' <span class="log-message-meta">' + meta.join(' | ') + '</span>' : '') + '</div>';
        html += '<div class="log-message-content">' + escHtml(m.content_summary || m.content || '').replace(/\n/g,'<br>') + '</div>';
        html += '<div class="log-message-time">' + escHtml(m.timestamp_str || '') + '</div>';
        html += '</div>';
      }
      html += '</div>';
      detail.innerHTML = html;
    })
    .catch(function(e){
      detail.innerHTML = '<div class="log-error">加载失败: ' + escHtml(String(e)) + '</div>';
    });
}

function renderList(sessions){
  if(!sessions || !sessions.length){
    loadingEl.style.display = 'none';
    emptyEl.style.display = 'block';
    return;
  }
  var html = '';
  for(var i=0;i<sessions.length;i++){
    var s = sessions[i];
    var statusClass = s.status === 'running' ? 'status-running' : (s.status === 'error' ? 'status-error' : 'status-done');
    var silentTag = s.is_silent ? '<span class="log-tag silent">SILENT</span>' : '';
    html += '<div class="log-item" data-sid="' + escHtml(s.session_id) + '">';
    html += '<div class="log-item-header" onclick="__toggleLogDetail(this)">';
    html += '<div class="log-item-title">' + escHtml(s.title || s.session_id) + silentTag + '</div>';
    html += '<div class="log-item-meta">';
    html += '<span class="log-status ' + statusClass + '">' + escHtml(s.status) + '</span>';
    html += '<span>' + escHtml(s.started_at_str || '') + '</span>';
    html += '<span>msgs ' + (s.message_count||0) + '</span>';
    html += '<span>tools ' + (s.tool_call_count||0) + '</span>';
    html += '<span>apis ' + (s.api_call_count||0) + '</span>';
    html += '</div>';
    html += '</div>';
    html += '</div>';
  }
  listEl.innerHTML = html;
  loadingEl.style.display = 'none';
  emptyEl.style.display = 'none';
}

window.__toggleLogDetail = function(header){
  var item = header.parentElement;
  var sid = item.getAttribute('data-sid');
  toggleDetail(sid, item);
};

fetch('/api/agent/' + encodeURIComponent(agentName) + '/sessions')
  .then(function(r){return r.json();})
  .then(function(j){
    if(!j.ok){ loadingEl.innerHTML = '<div class="log-error">加载失败</div>'; return; }
    renderList(j.data);
  })
  .catch(function(e){
    loadingEl.innerHTML = '<div class="log-error">加载失败: ' + escHtml(String(e)) + '</div>';
  });
})();
</script>

<style>
.log-item{border:1px solid #e8e8e8;border-radius:6px;margin-bottom:8px;background:#fff;overflow:hidden;}
.log-item-header{padding:12px 14px;cursor:pointer;transition:.15s;}
.log-item-header:hover{background:#f8f9fa;}
.log-item-title{font-size:13px;font-weight:600;color:#2c3e50;margin-bottom:4px;word-break:break-all;}
.log-item-meta{font-size:12px;color:#888;display:flex;flex-wrap:wrap;gap:10px;align-items:center;}
.log-status{font-size:11px;padding:2px 8px;border-radius:10px;}
.log-status.status-done{background:#d4edda;color:#155724;}
.log-status.status-running{background:#fff3cd;color:#856404;}
.log-status.status-error{background:#f8d7da;color:#721c24;}
.log-tag{font-size:10px;padding:1px 6px;border-radius:8px;margin-left:6px;}
.log-tag.silent{background:#e8e8e8;color:#666;}
.log-detail{border-top:1px solid #f0f0f0;padding:12px 14px;background:#fafbfc;}
.log-messages{display:flex;flex-direction:column;gap:10px;}
.log-message{padding:10px 12px;border-radius:6px;background:#fff;border:1px solid #eee;}
.log-message-role{font-size:11px;font-weight:600;color:#666;margin-bottom:4px;text-transform:uppercase;}
.log-message-meta{font-weight:normal;color:#999;font-size:10px;}
.log-message-content{font-size:13px;color:#333;line-height:1.5;word-break:break-all;max-height:200px;overflow-y:auto;}
.log-message-time{font-size:11px;color:#bbb;margin-top:4px;}
.log-message.msg-assistant{background:#e8f4fd;border-color:#d4e8f7;}
.log-message.msg-user{background:#e6ffed;border-color:#c9f0d4;}
.log-message.msg-tool{background:#fff8e6;border-color:#f5ebc6;}
.log-error{color:#721c24;padding:12px 0;}
</style>
{% endblock %}