178 lines
8.6 KiB
HTML
178 lines
8.6 KiB
HTML
{% extends 'base.html' %}
|
|
{% block title %}运行 #{{ run.display_id }} - 天璇{% endblock %}
|
|
{% block content %}
|
|
<style>
|
|
.svd-card { background:#f8f9fa; border:1px solid #e0e0e0; border-radius:8px; padding:0.8rem; }
|
|
.svd-card h4 { margin:0 0 0.3rem; font-size:0.9rem; }
|
|
.var-bar { height:8px; border-radius:4px; background:#4361ee; margin:0.2rem 0; transition:width 0.3s; }
|
|
.feat-tag { display:inline-block; background:#e8f0fe; padding:0.1rem 0.4rem; border-radius:3px; font-size:0.75rem; margin:0.1rem; }
|
|
</style>
|
|
|
|
<div class="card">
|
|
<div style="display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:0.5rem;">
|
|
<div>
|
|
<h2 style="margin:0;">运行 #{{ run.display_id }}</h2>
|
|
<p style="margin:0.25rem 0 0;color:#666;font-size:0.9rem;">
|
|
{{ run.created_at|date:"Y-m-d H:i" }}
|
|
— 状态: <span class="badge {% if run.status == 'completed' %}badge-success{% elif run.status == 'failed' %}badge-danger{% else %}badge-info{% endif %}">{{ run.get_status_display }}</span>
|
|
</p>
|
|
</div>
|
|
<a href="{% url 'analysis:cluster_overview' run.display_id %}" class="btn btn-primary" style="font-size:0.85rem;">聚类概览 →</a>
|
|
</div>
|
|
{% if run.error_message %}
|
|
<pre style="background:#1a1a2e;color:#e0e0e0;padding:0.5rem;border-radius:4px;font-size:0.78rem;margin-top:0.5rem;max-height:200px;overflow:auto;"><code>{{ run.error_message }}</code></pre>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="grid-3">
|
|
<div class="card" style="text-align:center;">
|
|
<div class="stat">{{ run.total_flows|default:"0" }}</div>
|
|
<div class="stat-label">总流数</div>
|
|
</div>
|
|
<div class="card" style="text-align:center;">
|
|
<div class="stat">{{ run.entity_count|default:"0" }}</div>
|
|
<div class="stat-label">行数</div>
|
|
</div>
|
|
<div class="card" style="text-align:center;">
|
|
<div class="stat">{{ run.cluster_count|default:"0" }}</div>
|
|
<div class="stat-label">聚类数</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── Global SVD Features ── -->
|
|
{% if svd_features %}
|
|
<div class="card">
|
|
<div style="display:flex;justify-content:space-between;align-items:center;">
|
|
<h3>🔬 全域 SVD 特征分析</h3>
|
|
<span style="font-size:0.8rem;color:#888;">基于 {{ run.total_flows }} 行数据</span>
|
|
</div>
|
|
<p style="font-size:0.82rem;color:#666;margin-top:0.3rem;">奇异值分解 (TruncatedSVD) 降维后的各主成分解释方差比例与贡献特征。</p>
|
|
<div style="display:grid;grid-template-columns:1fr 1fr;gap:0.75rem;margin-top:0.75rem;">
|
|
{% for svd in svd_features %}
|
|
<div class="svd-card">
|
|
<h4>PC{{ svd.component }}</h4>
|
|
<div style="display:flex;justify-content:space-between;font-size:0.78rem;color:#555;">
|
|
<span>解释方差: {{ svd.explained_var }}%</span>
|
|
<span>累积: {{ svd.cumulative_var }}%</span>
|
|
</div>
|
|
<div class="var-bar" style="width:{{ svd.explained_var }}%;"></div>
|
|
<div style="margin-top:0.4rem;">
|
|
{% for f in svd.top_features %}
|
|
<span class="feat-tag">{{ f.feature }}: {{ f.strength }}</span>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- ── LLM 自动分析流程 ── -->
|
|
{% if run.run_type == 'auto' and llm_timeline_json %}
|
|
<div class="card">
|
|
<h3>🧠 LLM 自动分析流程</h3>
|
|
<div id="llmTimeline"></div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- ── 聚类列表 ── -->
|
|
<div class="card">
|
|
<h3>📊 聚类结果</h3>
|
|
{% if clusters %}
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>簇</th>
|
|
<th>大小</th>
|
|
<th>比例</th>
|
|
<th>轮廓系数</th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for c in clusters %}
|
|
<tr>
|
|
<td><span class="badge badge-info">#{{ c.cluster_label }}</span></td>
|
|
<td>{{ c.size }}</td>
|
|
<td>{{ c.proportion|floatformat:2 }}</td>
|
|
<td>{{ c.silhouette_score|floatformat:4|default:"-" }}</td>
|
|
<td><a href="{% url 'analysis:cluster_detail' run.display_id c.cluster_label %}" class="btn btn-primary">详情</a></td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
{% else %}
|
|
<div class="empty-state"><p>无聚类结果</p></div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{% if run.run_type == 'auto' and llm_timeline_json %}
|
|
<script>
|
|
const llmData = {{ llm_timeline_json|safe }};
|
|
(function() {
|
|
const container = document.getElementById('llmTimeline');
|
|
if (!container) return;
|
|
|
|
function escapeHtml(s) { if (s==null) return ''; return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'); }
|
|
function prettyJson(o) { try { return JSON.stringify(o,null,2); } catch(e) { return String(o); } }
|
|
|
|
var thinkingByStep = {}, thinkOrder = [];
|
|
if (llmData.thinking) {
|
|
var parts = llmData.thinking.split(/\n(?=\[\d+\])/);
|
|
for (var pi = 0; pi < parts.length; pi++) {
|
|
var m = parts[pi].match(/^\[(\d+)\]\s*(.*)/s);
|
|
if (m) {
|
|
var step = parseInt(m[1]);
|
|
if (!(step in thinkingByStep)) thinkOrder.push(step);
|
|
thinkingByStep[step] = (thinkingByStep[step]||'')+(thinkingByStep[step]?'\n':'')+m[2].trim();
|
|
}
|
|
}
|
|
}
|
|
var toolCalls = llmData.tool_calls || [];
|
|
var toolMap = {};
|
|
for (var ti = 0; ti < toolCalls.length; ti++) {
|
|
var tc = toolCalls[ti];
|
|
if (!toolMap[tc.step]) toolMap[tc.step] = [];
|
|
toolMap[tc.step].push(tc);
|
|
}
|
|
var allSteps = new Set(thinkOrder.concat(Object.keys(toolMap).map(Number)));
|
|
var sortedSteps = Array.from(allSteps).sort(function(a,b){return a-b;});
|
|
|
|
for (var si = 0; si < sortedSteps.length; si++) {
|
|
var stp = sortedSteps[si];
|
|
var hdr = document.createElement('div');
|
|
hdr.style.cssText = 'font-weight:700;font-size:0.85rem;color:#4361ee;padding:0.6rem 0 0.3rem;border-top:1px solid #eee;';
|
|
hdr.textContent = '步骤 ' + stp;
|
|
container.appendChild(hdr);
|
|
if (thinkingByStep[stp] !== undefined) {
|
|
var tdiv = document.createElement('div');
|
|
tdiv.style.cssText = 'background:#eef2ff;padding:0.5rem;border-radius:6px;font-size:0.78rem;line-height:1.6;color:#333;max-height:300px;overflow:auto;margin-bottom:0.3rem;border:1px solid #d0d8f0;';
|
|
tdiv.innerHTML = '<span style="font-weight:600;color:#4361ee;font-size:0.75rem;display:block;margin-bottom:0.25rem;">💭 思考</span>' + escapeHtml(thinkingByStep[stp]);
|
|
container.appendChild(tdiv);
|
|
}
|
|
if (toolMap[stp]) {
|
|
for (var tj = 0; tj < toolMap[stp].length; tj++) {
|
|
var tc2 = toolMap[stp][tj];
|
|
var card = document.createElement('div');
|
|
card.style.cssText = 'border:1px solid #e0e0e0;border-radius:6px;margin-bottom:0.3rem;overflow:hidden;background:#fff;';
|
|
var inputStr = prettyJson(tc2.input);
|
|
var outputStr = prettyJson(tc2.output);
|
|
card.innerHTML =
|
|
'<div onclick="this.classList.toggle(\'open\');var b=this.nextElementSibling;if(b)b.classList.toggle(\'open\');" style="padding:0.4rem 0.8rem;cursor:pointer;display:flex;justify-content:space-between;align-items:center;background:#f8f9fa;user-select:none;">' +
|
|
'<span><span style="background:#43aa8b;color:#fff;border-radius:3px;padding:0 5px;font-size:0.7rem;font-weight:600;margin-right:6px;">🔧</span>' +
|
|
'<span style="font-weight:600;font-size:0.8rem;">' + escapeHtml(tc2.name||'?') + '</span></span>' +
|
|
'<span style="font-size:0.75rem;color:#888;">▶</span></div>' +
|
|
'<div style="display:none;padding:0.5rem 0.8rem;background:#fafbfc;">' +
|
|
'<div style="font-weight:600;font-size:0.72rem;color:#555;margin-bottom:0.2rem;">输入:</div>' +
|
|
'<pre style="background:#1a1a2e;color:#e0e0e0;padding:0.5rem;border-radius:4px;font-size:0.7rem;max-height:200px;overflow:auto;margin:0 0 0.3rem;"><code>' + escapeHtml(inputStr) + '</code></pre>' +
|
|
'<div style="font-weight:600;font-size:0.72rem;color:#555;margin-bottom:0.2rem;">输出:</div>' +
|
|
'<pre style="background:#1a1a2e;color:#e0e0e0;padding:0.5rem;border-radius:4px;font-size:0.7rem;max-height:200px;overflow:auto;margin:0;"><code>' + escapeHtml(outputStr) + '</code></pre>' +
|
|
'</div>';
|
|
container.appendChild(card);
|
|
}
|
|
}
|
|
}
|
|
})();
|
|
</script>
|
|
{% endif %}
|
|
{% endblock %} |