fix: sanitize observability and review UI copy
All checks were successful
CD Pipeline / deploy (push) Successful in 1m12s
All checks were successful
CD Pipeline / deploy (push) Successful in 1m12s
This commit is contained in:
@@ -103,7 +103,7 @@
|
||||
'competitor_price': '競品價格',
|
||||
'sales_anomaly': '業績異常',
|
||||
'budget_strategy': '預算策略',
|
||||
'rag_feedback': 'RAG 反饋',
|
||||
'rag_feedback': '知識反饋',
|
||||
'ppt_audit': 'PPT 審核',
|
||||
'quality_issue': '品質問題',
|
||||
'promotion': '活動促銷',
|
||||
@@ -116,9 +116,9 @@
|
||||
|
||||
{% macro provider(value, fallback='未分類供應商') -%}
|
||||
{%- set labels = {
|
||||
'gcp_ollama': '主力 Ollama',
|
||||
'ollama_secondary': '備援 Ollama',
|
||||
'ollama_111': '111 Ollama',
|
||||
'gcp_ollama': '主力 AI 模型',
|
||||
'ollama_secondary': '備援 AI 模型',
|
||||
'ollama_111': '第三 AI 模型',
|
||||
'nim_via_elephant': 'NIM Elephant',
|
||||
'gemini': 'Gemini',
|
||||
'claude': 'Claude',
|
||||
|
||||
@@ -9,20 +9,20 @@
|
||||
</style>
|
||||
|
||||
<div class="container-fluid mt-3">
|
||||
<section class="agent-hero"><div class="agent-kicker"><i class="fas fa-network-wired me-1"></i> AI 分工指揮台 · {{ hours }} 小時視窗</div><h1 class="agent-title">AI 分工指揮台</h1><p class="agent-subtitle">確認 AI 分工、Ollama 占比、知識命中與 MCP 編排是否支撐業績決策。</p><form method="get" class="agent-filter"><select name="hours" class="form-select form-select-sm" onchange="this.form.submit()">{% for h in [1,6,24,72,168] %}<option value="{{ h }}" {% if hours == h %}selected{% endif %}>{% if h < 24 %}過去 {{ h }} 小時{% else %}過去 {{ h//24 }} 天{% endif %}</option>{% endfor %}</select></form>{% if overall %}<div class="agent-command"><div class="agent-signal"><div class="agent-label">呼叫總量</div><span class="agent-value">{{ "{:,}".format(overall.total_calls) }}</span><small class="text-muted">{{ "{:,}".format(overall.total_tokens) }} 用量</small></div><div class="agent-signal"><div class="agent-label">Ollama 占比</div><span class="agent-value status-good">{{ "%.0f"|format(overall.local_pct) }}%</span><small class="text-muted">{{ "{:,}".format(overall.local_calls) }} 次本地呼叫</small></div><div class="agent-signal"><div class="agent-label">付費成本</div><span class="agent-value {% if overall.total_cost > 0 %}status-warn{% else %}status-good{% endif %}">${{ "%.2f"|format(overall.total_cost) }}</span><small class="text-muted">{{ "{:,}".format(overall.paid_calls) }} 次付費呼叫</small></div><div class="agent-signal"><div class="agent-label">知識命中率</div><span class="agent-value status-blue">{{ "%.0f"|format(overall.rag_rate) }}%</span><small class="text-muted">{{ "{:,}".format(overall.rag_hits) }} 次命中</small></div></div>{% endif %}</section>
|
||||
<section class="agent-hero"><div class="agent-kicker"><i class="fas fa-network-wired me-1"></i> AI 分工指揮台 · {{ hours }} 小時視窗</div><h1 class="agent-title">AI 分工指揮台</h1><p class="agent-subtitle">確認 AI 分工、本地模型、知識命中與工具編排是否支撐業績決策。</p><form method="get" class="agent-filter"><select name="hours" class="form-select form-select-sm" onchange="this.form.submit()">{% for h in [1,6,24,72,168] %}<option value="{{ h }}" {% if hours == h %}selected{% endif %}>{% if h < 24 %}過去 {{ h }} 小時{% else %}過去 {{ h//24 }} 天{% endif %}</option>{% endfor %}</select></form>{% if overall %}<div class="agent-command"><div class="agent-signal"><div class="agent-label">呼叫總量</div><span class="agent-value">{{ "{:,}".format(overall.total_calls) }}</span><small class="text-muted">{{ "{:,}".format(overall.total_tokens) }} 用量</small></div><div class="agent-signal"><div class="agent-label">本地模型占比</div><span class="agent-value status-good">{{ "%.0f"|format(overall.local_pct) }}%</span><small class="text-muted">{{ "{:,}".format(overall.local_calls) }} 次本地呼叫</small></div><div class="agent-signal"><div class="agent-label">付費成本</div><span class="agent-value {% if overall.total_cost > 0 %}status-warn{% else %}status-good{% endif %}">${{ "%.2f"|format(overall.total_cost) }}</span><small class="text-muted">{{ "{:,}".format(overall.paid_calls) }} 次付費呼叫</small></div><div class="agent-signal"><div class="agent-label">知識命中率</div><span class="agent-value status-blue">{{ "%.0f"|format(overall.rag_rate) }}%</span><small class="text-muted">{{ "{:,}".format(overall.rag_hits) }} 次命中</small></div></div>{% endif %}</section>
|
||||
{% if error %}<div class="alert alert-warning mt-3"><strong><i class="fas fa-triangle-exclamation me-1"></i></strong>{{ error }}</div>{% endif %}
|
||||
|
||||
<section class="agent-grid">
|
||||
<div class="agent-stack">
|
||||
<article class="agent-table-shell"><div class="agent-table-title"><div><div class="agent-label">AI 分工矩陣</div><h3>模型、工具與知識命中矩陣</h3></div></div><div class="table-responsive"><table class="table mb-0"><thead class="table-light"><tr><th>分工</th><th class="text-end">呼叫</th><th class="text-end">成本</th><th class="text-end">Ollama</th><th class="text-end">付費</th><th class="text-end">MCP</th><th class="text-end">RAG</th><th class="text-end">錯誤</th><th class="text-end">耗時</th></tr></thead><tbody>{% for ag in agent_matrix %}<tr><td><strong>{{ ag.label }}</strong><small class="d-block text-muted">{{ ag.desc }}</small></td><td class="text-end">{% if ag.calls > 0 %}<strong>{{ "{:,}".format(ag.calls) }}</strong><small class="d-block text-muted">{{ "{:,}".format(ag.tokens) }} 用量</small>{% else %}<small class="text-muted">—</small>{% endif %}</td><td class="text-end">{% if ag.calls > 0 %}${{ "%.2f"|format(ag.cost) }}{% else %}<small class="text-muted">—</small>{% endif %}</td><td class="text-end">{% if ag.calls > 0 %}<strong class="status-good">{{ "%.0f"|format(ag.ollama_pct) }}%</strong><small class="d-block text-muted">A {{ ag.ollama_gcp_a }} · B {{ ag.ollama_gcp_b }} · 111 {{ ag.ollama_111 }}</small>{% else %}<small class="text-muted">—</small>{% endif %}</td><td class="text-end">{% if ag.calls > 0 %}<strong class="{% if ag.paid_pct > 50 %}status-bad{% elif ag.paid_pct > 20 %}status-warn{% endif %}">{{ "%.0f"|format(ag.paid_pct) }}%</strong><small class="d-block text-muted">Gemini {{ ag.gemini }}{% if ag.other_paid %} · 其他 {{ ag.other_paid }}{% endif %}</small>{% else %}<small class="text-muted">—</small>{% endif %}</td><td class="text-end">{% if ag.calls > 0 %}<strong class="{% if ag.mcp_rate >= 30 %}status-blue{% elif ag.mcp_rate >= 10 %}status-warn{% else %}text-muted{% endif %}">{{ "%.1f"|format(ag.mcp_rate) }}%</strong><small class="d-block text-muted">{{ ag.mcp_calls }}</small>{% else %}<small class="text-muted">—</small>{% endif %}</td><td class="text-end">{% if ag.calls > 0 %}<strong class="status-blue">{{ "%.1f"|format(ag.rag_rate) }}%</strong><small class="d-block text-muted">{{ ag.rag_hits }}</small>{% else %}<small class="text-muted">—</small>{% endif %}</td><td class="text-end">{% if ag.calls > 0 %}<strong class="{% if ag.error_rate >= 15 %}status-bad{% elif ag.error_rate >= 5 %}status-warn{% else %}status-good{% endif %}">{{ "%.1f"|format(ag.error_rate) }}%</strong><small class="d-block text-muted">{{ ag.errors }}</small>{% else %}<small class="text-muted">—</small>{% endif %}</td><td class="text-end">{% if ag.calls > 0 %}{{ ag.avg_ms }} ms{% else %}<small class="text-muted">—</small>{% endif %}</td></tr>{% endfor %}</tbody></table></div></article>
|
||||
<article class="agent-table-shell"><div class="agent-table-title"><div><div class="agent-label">AI 分工矩陣</div><h3>模型、工具與知識命中矩陣</h3></div></div><div class="table-responsive"><table class="table mb-0"><thead class="table-light"><tr><th>分工</th><th class="text-end">呼叫</th><th class="text-end">成本</th><th class="text-end">本地模型</th><th class="text-end">付費</th><th class="text-end">工具</th><th class="text-end">知識</th><th class="text-end">錯誤</th><th class="text-end">耗時</th></tr></thead><tbody>{% for ag in agent_matrix %}<tr><td><strong>{{ ag.label }}</strong><small class="d-block text-muted">{{ ag.desc }}</small></td><td class="text-end">{% if ag.calls > 0 %}<strong>{{ "{:,}".format(ag.calls) }}</strong><small class="d-block text-muted">{{ "{:,}".format(ag.tokens) }} 用量</small>{% else %}<small class="text-muted">—</small>{% endif %}</td><td class="text-end">{% if ag.calls > 0 %}${{ "%.2f"|format(ag.cost) }}{% else %}<small class="text-muted">—</small>{% endif %}</td><td class="text-end">{% if ag.calls > 0 %}<strong class="status-good">{{ "%.0f"|format(ag.ollama_pct) }}%</strong><small class="d-block text-muted">主力 {{ ag.ollama_gcp_a }} · 備援 {{ ag.ollama_gcp_b }} · 第三 {{ ag.ollama_111 }}</small>{% else %}<small class="text-muted">—</small>{% endif %}</td><td class="text-end">{% if ag.calls > 0 %}<strong class="{% if ag.paid_pct > 50 %}status-bad{% elif ag.paid_pct > 20 %}status-warn{% endif %}">{{ "%.0f"|format(ag.paid_pct) }}%</strong><small class="d-block text-muted">Gemini {{ ag.gemini }}{% if ag.other_paid %} · 其他 {{ ag.other_paid }}{% endif %}</small>{% else %}<small class="text-muted">—</small>{% endif %}</td><td class="text-end">{% if ag.calls > 0 %}<strong class="{% if ag.mcp_rate >= 30 %}status-blue{% elif ag.mcp_rate >= 10 %}status-warn{% else %}text-muted{% endif %}">{{ "%.1f"|format(ag.mcp_rate) }}%</strong><small class="d-block text-muted">{{ ag.mcp_calls }}</small>{% else %}<small class="text-muted">—</small>{% endif %}</td><td class="text-end">{% if ag.calls > 0 %}<strong class="status-blue">{{ "%.1f"|format(ag.rag_rate) }}%</strong><small class="d-block text-muted">{{ ag.rag_hits }}</small>{% else %}<small class="text-muted">—</small>{% endif %}</td><td class="text-end">{% if ag.calls > 0 %}<strong class="{% if ag.error_rate >= 15 %}status-bad{% elif ag.error_rate >= 5 %}status-warn{% else %}status-good{% endif %}">{{ "%.1f"|format(ag.error_rate) }}%</strong><small class="d-block text-muted">{{ ag.errors }}</small>{% else %}<small class="text-muted">—</small>{% endif %}</td><td class="text-end">{% if ag.calls > 0 %}{{ ag.avg_ms }} ms{% else %}<small class="text-muted">—</small>{% endif %}</td></tr>{% endfor %}</tbody></table></div></article>
|
||||
</div>
|
||||
<aside class="agent-stack">
|
||||
<article class="agent-panel"><div class="agent-panel-head"><div><div class="agent-label">分工卡片</div><h2 class="agent-panel-title">分工健康速覽</h2></div></div><div class="agent-panel-body">{% for ag in agent_matrix %}<div class="agent-card"><div class="agent-card-top"><div><strong>{{ ag.label }}</strong><small class="d-block text-muted">{{ ag.desc }}</small></div><span class="badge {% if ag.error_rate >= 15 %}bg-danger{% elif ag.calls == 0 %}bg-secondary{% else %}bg-success{% endif %}">{{ ag.calls }} 次呼叫</span></div><div class="agent-meter"><span style="width:{{ ag.ollama_pct|round|int if ag.calls > 0 else 0 }}%"></span></div><small class="text-muted">Ollama {{ "%.0f"|format(ag.ollama_pct) if ag.calls > 0 else 0 }}% · RAG {{ "%.0f"|format(ag.rag_rate) if ag.calls > 0 else 0 }}% · MCP {{ "%.0f"|format(ag.mcp_rate) if ag.calls > 0 else 0 }}%</small></div>{% endfor %}</div></article>
|
||||
<article class="agent-panel"><div class="agent-panel-head"><div><div class="agent-label">分工卡片</div><h2 class="agent-panel-title">分工健康速覽</h2></div></div><div class="agent-panel-body">{% for ag in agent_matrix %}<div class="agent-card"><div class="agent-card-top"><div><strong>{{ ag.label }}</strong><small class="d-block text-muted">{{ ag.desc }}</small></div><span class="badge {% if ag.error_rate >= 15 %}bg-danger{% elif ag.calls == 0 %}bg-secondary{% else %}bg-success{% endif %}">{{ ag.calls }} 次呼叫</span></div><div class="agent-meter"><span style="width:{{ ag.ollama_pct|round|int if ag.calls > 0 else 0 }}%"></span></div><small class="text-muted">本地模型 {{ "%.0f"|format(ag.ollama_pct) if ag.calls > 0 else 0 }}% · 知識 {{ "%.0f"|format(ag.rag_rate) if ag.calls > 0 else 0 }}% · 工具 {{ "%.0f"|format(ag.mcp_rate) if ag.calls > 0 else 0 }}%</small></div>{% endfor %}</div></article>
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
{% if recommendations %}<section class="agent-panel mt-3"><div class="agent-panel-head"><div><div class="agent-label">策略規則</div><h2 class="agent-panel-title">編排策略自動建議</h2></div></div><div class="agent-panel-body">{% for r in recommendations %}<div class="rec-card"><span class="badge {% if r.severity == 'high' %}bg-danger{% elif r.severity == 'med' %}bg-warning{% else %}bg-info{% endif %} me-1">{{ r.severity|upper }}</span><strong>{{ r.agent }}</strong><div class="small mt-1"><i class="fas fa-search me-1"></i><strong>發現:</strong>{{ r.finding }}</div><div class="small text-muted"><i class="fas fa-arrow-right me-1"></i><strong>建議:</strong>{{ r.suggestion }}</div></div>{% endfor %}</div></section>{% endif %}
|
||||
{% if mcp_matrix %}<section class="agent-table-shell"><div class="agent-table-title"><div><div class="agent-label">工具服務明細</div><h3>工具服務 × 呼叫端工作量</h3></div></div><div class="table-responsive"><table class="table table-sm mb-0"><thead class="table-light"><tr><th>工具服務</th><th>呼叫端</th><th class="text-end">工具呼叫</th><th class="text-end">快取</th><th class="text-end">快取率</th><th class="text-end">成本</th></tr></thead><tbody>{% for m in mcp_matrix %}<tr><td><code>{{ m.server }}</code></td><td><code>{{ m.caller }}</code></td><td class="text-end">{{ "{:,}".format(m.calls) }}</td><td class="text-end">{{ m.cache_hits }}</td><td class="text-end"><span class="{% if m.cache_rate >= 50 %}status-good{% elif m.cache_rate >= 20 %}status-warn{% endif %}">{{ "%.0f"|format(m.cache_rate) }}%</span></td><td class="text-end">${{ "%.4f"|format(m.cost) }}</td></tr>{% endfor %}</tbody></table></div></section>{% endif %}
|
||||
<p class="text-muted mt-3"><small><i class="fas fa-robot me-1"></i>Ollama 優先策略 — AI 分工指揮台</small></p>
|
||||
<p class="text-muted mt-3"><small><i class="fas fa-robot me-1"></i>AI 分工指揮台</small></p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
<section class="calls-hero">
|
||||
<div class="calls-kicker"><i class="fas fa-chart-bar me-1"></i> AI 流量管制 · {{ hours }} 小時視窗</div>
|
||||
<h1 class="calls-title">AI 流量控制塔</h1>
|
||||
<p class="calls-subtitle">看成本、錯誤率與 RAG 命中,確保 AI 建議穩定支援業績判斷。</p>
|
||||
<p class="calls-subtitle">看成本、錯誤率與知識命中,確保 AI 建議穩定支援業績判斷。</p>
|
||||
<div class="calls-actions">
|
||||
<button class="btn btn-warning btn-sm" onclick="triggerCodeReview()"><i class="fas fa-microscope me-1"></i>觸發程式碼審查管線</button>
|
||||
<form method="get" class="calls-filter">
|
||||
@@ -72,7 +72,7 @@
|
||||
<div class="calls-signal"><div class="calls-label">用量</div><span class="calls-value">{{ "{:,}".format(summary.total_tokens or 0) }}</span><div class="calls-note">{{ avg_tokens }} 單位/次</div></div>
|
||||
<div class="calls-signal"><div class="calls-label">成本</div><span class="calls-value">${{ "%.2f"|format(summary.total_cost or 0) }}</span>{% if hourly_trend %}<canvas data-spark="cost" height="26"></canvas>{% endif %}</div>
|
||||
<div class="calls-signal"><div class="calls-label">延遲</div><span class="calls-value">{{ summary.avg_duration or 0 }}ms</span><div class="calls-note">{{ summary.cache_hits or 0 }} 次快取命中</div></div>
|
||||
<div class="calls-signal"><div class="calls-label">RAG 命中</div><span class="calls-value status-blue">{{ "%.1f"|format(rag_rate) }}%</span><div class="calls-note">{{ summary.rag_hits or 0 }} 次命中</div></div>
|
||||
<div class="calls-signal"><div class="calls-label">知識命中</div><span class="calls-value status-blue">{{ "%.1f"|format(rag_rate) }}%</span><div class="calls-note">{{ summary.rag_hits or 0 }} 次命中</div></div>
|
||||
<div class="calls-signal"><div class="calls-label">錯誤</div><span class="calls-value {% if error_rate >= 15 %}status-bad{% elif error_rate >= 5 %}status-warn{% else %}status-good{% endif %}">{{ errors }}</span>{% if hourly_trend %}<canvas data-spark="errors" height="26"></canvas>{% endif %}</div>
|
||||
</section>
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
{% if caller_richness %}
|
||||
<article class="calls-table-shell">
|
||||
<div class="calls-table-title"><div><div class="calls-label">呼叫端編排</div><h3>呼叫端 × 知識與工具編排矩陣</h3></div></div>
|
||||
<div class="table-responsive"><table class="table table-sm mb-0"><thead class="table-light"><tr><th>呼叫端</th><th class="text-end">總呼叫</th><th class="text-end">RAG 命中</th><th class="text-end">MCP 編排</th><th class="text-end">RAG 反饋</th><th class="text-end">筆數</th></tr></thead><tbody>{% for c in caller_richness %}<tr><td><code>{{ c.caller }}</code></td><td class="text-end">{{ "{:,}".format(c.total_calls) }}</td><td class="text-end"><strong class="{% if c.rag_hit_rate >= 50 %}status-good{% elif c.rag_hit_rate >= 20 %}status-warn{% else %}text-muted{% endif %}">{{ "%.1f"|format(c.rag_hit_rate) }}%</strong> <small class="text-muted">({{ c.rag_hits }})</small></td><td class="text-end"><strong class="{% if c.mcp_rate >= 30 %}status-blue{% elif c.mcp_rate >= 10 %}status-warn{% endif %}">{{ "%.1f"|format(c.mcp_rate) }}%</strong></td><td class="text-end">{% if c.feedback_count > 0 %}<strong class="{% if c.avg_rag_feedback >= 4 %}status-good{% elif c.avg_rag_feedback >= 3 %}status-warn{% else %}status-bad{% endif %}">{{ "%.2f"|format(c.avg_rag_feedback) }}/5</strong>{% else %}<small class="text-muted">—</small>{% endif %}</td><td class="text-end">{{ c.feedback_count }}</td></tr>{% endfor %}</tbody></table></div>
|
||||
<div class="table-responsive"><table class="table table-sm mb-0"><thead class="table-light"><tr><th>呼叫端</th><th class="text-end">總呼叫</th><th class="text-end">知識命中</th><th class="text-end">工具編排</th><th class="text-end">知識反饋</th><th class="text-end">筆數</th></tr></thead><tbody>{% for c in caller_richness %}<tr><td><code>{{ c.caller }}</code></td><td class="text-end">{{ "{:,}".format(c.total_calls) }}</td><td class="text-end"><strong class="{% if c.rag_hit_rate >= 50 %}status-good{% elif c.rag_hit_rate >= 20 %}status-warn{% else %}text-muted{% endif %}">{{ "%.1f"|format(c.rag_hit_rate) }}%</strong> <small class="text-muted">({{ c.rag_hits }})</small></td><td class="text-end"><strong class="{% if c.mcp_rate >= 30 %}status-blue{% elif c.mcp_rate >= 10 %}status-warn{% endif %}">{{ "%.1f"|format(c.mcp_rate) }}%</strong></td><td class="text-end">{% if c.feedback_count > 0 %}<strong class="{% if c.avg_rag_feedback >= 4 %}status-good{% elif c.avg_rag_feedback >= 3 %}status-warn{% else %}status-bad{% endif %}">{{ "%.2f"|format(c.avg_rag_feedback) }}/5</strong>{% else %}<small class="text-muted">—</small>{% endif %}</td><td class="text-end">{{ c.feedback_count }}</td></tr>{% endfor %}</tbody></table></div>
|
||||
</article>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -125,10 +125,10 @@
|
||||
|
||||
<section class="calls-table-shell">
|
||||
<div class="calls-table-title"><div><div class="calls-label">最近呼叫</div><h3>最近呼叫 100 筆</h3></div></div>
|
||||
<div class="table-responsive"><table class="table table-sm table-striped mb-0"><thead class="table-light"><tr><th>編號</th><th>時間</th><th>呼叫端</th><th>供應商</th><th>模型</th><th class="text-end">輸入</th><th class="text-end">輸出</th><th class="text-end">耗時</th><th>狀態</th><th class="text-end">成本</th><th>標記</th></tr></thead><tbody>{% for r in recent %}<tr {% if r.status not in ['ok','cache_only'] %}class="table-warning"{% endif %}><td>{{ r.id }}</td><td><small>{{ r.called_at }}</small></td><td><code>{{ r.caller_display or r.caller }}</code>{% if r.caller_display and r.caller_display != r.caller %}<br><small class="text-muted">原始:{{ r.caller }}</small>{% endif %}</td><td><small>{{ obs_label.provider(r.provider) }}</small></td><td><small>{{ r.model[:25] }}</small></td><td class="text-end">{{ r.in_tokens }}</td><td class="text-end">{{ r.out_tokens }}</td><td class="text-end">{{ r.duration_ms }}</td><td><small>{{ obs_label.status(r.status, '-') }}</small></td><td class="text-end">${{ "%.4f"|format(r.cost) }}</td><td>{% for badge in r.route_badges %}<span class="badge bg-secondary me-1">{{ badge }}</span>{% endfor %}{% if r.cache_hit %}<span class="badge bg-success">快取</span>{% endif %}{% if r.rag_hit %}<span class="badge bg-info">RAG</span>{% endif %}</td></tr>{% endfor %}</tbody></table></div>
|
||||
<div class="table-responsive"><table class="table table-sm table-striped mb-0"><thead class="table-light"><tr><th>編號</th><th>時間</th><th>呼叫端</th><th>供應商</th><th>模型</th><th class="text-end">輸入</th><th class="text-end">輸出</th><th class="text-end">耗時</th><th>狀態</th><th class="text-end">成本</th><th>標記</th></tr></thead><tbody>{% for r in recent %}<tr {% if r.status not in ['ok','cache_only'] %}class="table-warning"{% endif %}><td>{{ r.id }}</td><td><small>{{ r.called_at }}</small></td><td><code>{{ r.caller_display or r.caller }}</code>{% if r.caller_display and r.caller_display != r.caller %}<br><small class="text-muted">原始:{{ r.caller }}</small>{% endif %}</td><td><small>{{ obs_label.provider(r.provider) }}</small></td><td><small>{{ r.model[:25] }}</small></td><td class="text-end">{{ r.in_tokens }}</td><td class="text-end">{{ r.out_tokens }}</td><td class="text-end">{{ r.duration_ms }}</td><td><small>{{ obs_label.status(r.status, '-') }}</small></td><td class="text-end">${{ "%.4f"|format(r.cost) }}</td><td>{% for badge in r.route_badges %}<span class="badge bg-secondary me-1">{{ badge }}</span>{% endfor %}{% if r.cache_hit %}<span class="badge bg-success">快取</span>{% endif %}{% if r.rag_hit %}<span class="badge bg-info">知識命中</span>{% endif %}</td></tr>{% endfor %}</tbody></table></div>
|
||||
</section>
|
||||
|
||||
<p class="text-muted mt-3"><small><i class="fas fa-robot me-1"></i>Ollama 優先策略 v5.0 — AI 流量控制塔</small></p>
|
||||
<p class="text-muted mt-3"><small><i class="fas fa-robot me-1"></i>AI 流量控制塔</small></p>
|
||||
</div>
|
||||
|
||||
{% set ai_calls_payload = {
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
|
||||
<div class="container-fluid mt-3">
|
||||
<section class="gov-hero">
|
||||
<div class="gov-kicker"><i class="fas fa-wallet me-1"></i> AI 成本治理 · 預算 / 節流 / RAG 策略</div>
|
||||
<div class="gov-kicker"><i class="fas fa-wallet me-1"></i> AI 成本治理 · 預算 / 節流 / 知識策略</div>
|
||||
<h1 class="gov-title">AI 成本治理艙</h1>
|
||||
<p class="gov-subtitle">控制 AI 花費與節流狀態,把預算留給能推動業績的任務。</p>
|
||||
<div class="gov-actions"><button class="btn btn-warning btn-sm" onclick="forceThrottle()"><i class="fas fa-bolt me-1"></i>立即重算節流狀態</button><span class="text-muted small">預算超線時立即重算節流。</span></div>
|
||||
@@ -76,14 +76,14 @@
|
||||
</section>
|
||||
|
||||
{% if budget_strategies %}
|
||||
<section class="gov-panel mt-3"><div class="gov-panel-head"><div><div class="gov-label">RAG 策略</div><h2 class="gov-panel-title">RAG 自動策略建議</h2></div></div><div class="gov-panel-body">{% for s in budget_strategies %}<div class="strategy-card"><span class="badge bg-info me-1">{{ obs_label.insight(s.insight_type) }}</span><span class="badge bg-secondary me-1">相似度 {{ "%.2f"|format(s.similarity) }}</span><span>{{ s.content }}{% if s.content|length >= 240 %}…{% endif %}</span></div>{% endfor %}</div></section>
|
||||
<section class="gov-panel mt-3"><div class="gov-panel-head"><div><div class="gov-label">知識策略</div><h2 class="gov-panel-title">知識策略建議</h2></div></div><div class="gov-panel-body">{% for s in budget_strategies %}<div class="strategy-card"><span class="badge bg-info me-1">{{ obs_label.insight(s.insight_type) }}</span><span class="badge bg-secondary me-1">相似度 {{ "%.2f"|format(s.similarity) }}</span><span>{{ s.content }}{% if s.content|length >= 240 %}…{% endif %}</span></div>{% endfor %}</div></section>
|
||||
{% endif %}
|
||||
|
||||
{% if price_rec_7d %}
|
||||
<section class="gov-panel mt-3"><div class="gov-panel-head"><div><div class="gov-label">商業產出</div><h2 class="gov-panel-title">AI 價格決策 7 日</h2></div></div><div class="gov-panel-body"><div class="gov-mini-grid">{% for p in price_rec_7d %}<div class="gov-mini"><span class="gov-label">{{ obs_label.strategy(p.strategy) }}</span><strong>{{ p.count }}</strong><small class="text-muted">信心 {{ "%.2f"|format(p.avg_confidence) }}</small></div>{% endfor %}</div></div></section>
|
||||
{% endif %}
|
||||
|
||||
<p class="text-muted mt-3"><small><i class="fas fa-robot me-1"></i>Ollama 優先策略 v5.0 — AI 成本治理艙</small></p>
|
||||
<p class="text-muted mt-3"><small><i class="fas fa-robot me-1"></i>AI 成本治理艙</small></p>
|
||||
</div>
|
||||
|
||||
{% set budget_payload = {
|
||||
|
||||
@@ -59,13 +59,13 @@
|
||||
|
||||
<div class="container-fluid mt-3">
|
||||
<section class="runtime-hero">
|
||||
<div class="runtime-kicker"><i class="fas fa-heartbeat me-1"></i> 基礎設施生命線 · Ollama / MCP / AIOps</div>
|
||||
<div class="runtime-kicker"><i class="fas fa-heartbeat me-1"></i> 基礎設施生命線 · AI 模型 / 工具服務 / 自癒</div>
|
||||
<h1 class="runtime-title">基礎設施生命線</h1>
|
||||
<p class="runtime-subtitle">先看 Ollama、MCP 與自癒是否正常,避免 AI 建議與比價流程中斷。</p>
|
||||
<p class="runtime-subtitle">先看 AI 模型、工具服務與自癒是否正常,避免 AI 建議與比價流程中斷。</p>
|
||||
<div class="runtime-command">
|
||||
<div class="runtime-signal"><div class="runtime-label">Ollama 離線</div><span class="runtime-value {% if down.count > 0 %}status-bad{% else %}status-good{% endif %}">{{ down.count }}</span><small class="text-muted">{{ ollama_hosts|length }} 台即時探測</small></div>
|
||||
<div class="runtime-signal"><div class="runtime-label">AI 模型離線</div><span class="runtime-value {% if down.count > 0 %}status-bad{% else %}status-good{% endif %}">{{ down.count }}</span><small class="text-muted">{{ ollama_hosts|length }} 台即時探測</small></div>
|
||||
<div class="runtime-signal"><div class="runtime-label">AIOps 未解</div><span class="runtime-value {% if aiops_summary and aiops_summary.incidents_open > 0 %}status-bad{% else %}status-good{% endif %}">{{ aiops_summary.incidents_open if aiops_summary else '—' }}</span><small class="text-muted">7 日事件未解決</small></div>
|
||||
<div class="runtime-signal"><div class="runtime-label">自癒成功率</div><span class="runtime-value {% if aiops_summary and aiops_summary.heal_success_rate >= 80 %}status-good{% elif aiops_summary and aiops_summary.heal_success_rate >= 50 %}status-warn{% else %}status-bad{% endif %}">{{ "%.0f"|format(aiops_summary.heal_success_rate) if aiops_summary else '—' }}{% if aiops_summary %}%{% endif %}</span><small class="text-muted">ADR-013 自癒成功率</small></div>
|
||||
<div class="runtime-signal"><div class="runtime-label">自癒成功率</div><span class="runtime-value {% if aiops_summary and aiops_summary.heal_success_rate >= 80 %}status-good{% elif aiops_summary and aiops_summary.heal_success_rate >= 50 %}status-warn{% else %}status-bad{% endif %}">{{ "%.0f"|format(aiops_summary.heal_success_rate) if aiops_summary else '—' }}{% if aiops_summary %}%{% endif %}</span><small class="text-muted">近 7 日自癒成功率</small></div>
|
||||
<div class="runtime-signal"><div class="runtime-label">節流供應商</div><span class="runtime-value {% if throttled.count > 0 %}status-warn{% else %}status-good{% endif %}">{{ throttled.count }}</span><small class="text-muted">成本節流供應商</small></div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -74,7 +74,7 @@
|
||||
<div class="runtime-stack">
|
||||
<article class="runtime-panel">
|
||||
<div class="runtime-panel-head">
|
||||
<div><div class="runtime-label">主機級聯</div><h2 class="runtime-panel-title">Ollama 三主機</h2></div>
|
||||
<div><div class="runtime-label">模型服務</div><h2 class="runtime-panel-title">AI 模型主機</h2></div>
|
||||
<span class="badge {% if down.count > 0 %}bg-danger{% else %}bg-success{% endif %}">{{ '需要處理' if down.count > 0 else '全部在線' }}</span>
|
||||
</div>
|
||||
<div class="runtime-panel-body">
|
||||
@@ -128,11 +128,11 @@
|
||||
{% endif %}
|
||||
|
||||
<article class="runtime-panel">
|
||||
<div class="runtime-panel-head"><div><div class="runtime-label">MCP / Budget</div><h2 class="runtime-panel-title">工具層與節流</h2></div></div>
|
||||
<div class="runtime-panel-head"><div><div class="runtime-label">工具服務 / 預算</div><h2 class="runtime-panel-title">工具層與節流</h2></div></div>
|
||||
<div class="runtime-panel-body">
|
||||
<div class="runtime-mini-grid">
|
||||
<div class="runtime-mini"><span class="runtime-label">MCP 服務</span><strong>{{ mcp_status|length }}</strong></div>
|
||||
<div class="runtime-mini"><span class="runtime-label">MCP 24 小時呼叫</span><strong>{{ "{:,}".format(mcp_24h|sum(attribute='total_calls')) if mcp_24h else 0 }}</strong></div>
|
||||
<div class="runtime-mini"><span class="runtime-label">工具服務</span><strong>{{ mcp_status|length }}</strong></div>
|
||||
<div class="runtime-mini"><span class="runtime-label">工具 24 小時呼叫</span><strong>{{ "{:,}".format(mcp_24h|sum(attribute='total_calls')) if mcp_24h else 0 }}</strong></div>
|
||||
<div class="runtime-mini"><span class="runtime-label">自癒劇本</span><strong>{{ active_playbooks.count }}/{{ playbook_ranking|length }}</strong></div>
|
||||
<div class="runtime-mini"><span class="runtime-label">嵌入佇列</span><strong class="{% if embed_queue_failed > 0 %}status-bad{% elif embed_queue_pending > 0 %}status-warn{% else %}status-good{% endif %}">{{ embed_queue_pending }}/{{ embed_queue_failed }}</strong></div>
|
||||
</div>
|
||||
@@ -150,25 +150,25 @@
|
||||
|
||||
{% if mcp_24h %}
|
||||
<section class="runtime-table-shell">
|
||||
<div class="runtime-table-title"><div><div class="runtime-label">MCP 工作量</div><h3>MCP 服務 24h 工作量</h3></div></div>
|
||||
<div class="runtime-table-title"><div><div class="runtime-label">工具工作量</div><h3>工具服務 24h 工作量</h3></div></div>
|
||||
<div class="table-responsive"><table class="table mb-0"><thead class="table-light"><tr><th>服務</th><th class="text-end">呼叫</th><th class="text-end">成功率</th><th class="text-end">快取</th><th class="text-end">工具</th><th class="text-end">平均</th><th class="text-end">成本</th></tr></thead><tbody>{% for s in mcp_24h %}<tr><td><code>{{ s.server }}</code></td><td class="text-end">{{ "{:,}".format(s.total_calls) }}</td><td class="text-end"><strong class="{% if s.success_rate >= 95 %}status-good{% elif s.success_rate >= 80 %}status-warn{% else %}status-bad{% endif %}">{{ "%.1f"|format(s.success_rate) }}%</strong></td><td class="text-end">{{ "%.1f"|format(s.cache_rate) }}%</td><td class="text-end">{{ s.tools_used }}</td><td class="text-end">{{ s.avg_ms }} ms</td><td class="text-end">${{ "%.4f"|format(s.total_cost) }}</td></tr>{% endfor %}</tbody></table></div>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
<section class="runtime-main">
|
||||
<div class="runtime-stack">
|
||||
<article class="runtime-table-shell"><div class="runtime-table-title"><div><div class="runtime-label">事件紀錄</div><h3>最近 10 筆事件</h3></div></div><div class="table-responsive">{% if recent_incidents %}{% set task_labels = {'unknown_task': '待確認任務', 'ElephantAlphaAutonomousEngine': 'AI 自癒監控', 'run_icaim_analysis_task': '市場分析任務'} %}{% set error_labels = {'ollama_unhealthy': 'AI 模型主機不穩', 'scheduler_task_failure': '排程任務異常', 'crawler_timeout': '資料擷取逾時', 'python_exception': '程式例外'} %}<table class="table table-sm mb-0"><thead class="table-light"><tr><th>時間</th><th>任務</th><th>問題</th><th>等級</th><th>狀態</th><th>處置提醒</th></tr></thead><tbody>{% for i in recent_incidents %}{% set incident_msg = i.error_message or '' %}<tr><td><small>{{ i.created_at }}</small></td><td><span>{{ task_labels.get(i.task_name, '系統任務') }}</span></td><td><span class="badge bg-secondary">{{ error_labels.get(i.error_type, '系統異常') }}</span></td><td><span class="badge {% if i.severity in ('P0','P1') %}bg-danger{% elif i.severity == 'P2' %}bg-warning{% else %}bg-info{% endif %}">{{ i.severity }}</span></td><td>{{ i.status }}</td><td><small class="text-muted">{% if 'Ollama' in incident_msg %}AI 模型主機暫時不穩,已進入自癒監控。{% elif 'not defined' in incident_msg or 'Traceback' in incident_msg %}資料流程發生程式例外,需由修復流程處理。{% elif 'scheduler_task_failure' in incident_msg %}排程任務異常,需確認下次執行是否恢復。{% else %}{{ incident_msg|replace('_', ' ')|truncate(90) }}{% endif %}</small></td></tr>{% endfor %}</tbody></table>{% else %}<div class="text-muted text-center p-3 small">尚無事件紀錄</div>{% endif %}</div></article>
|
||||
<article class="runtime-table-shell"><div class="runtime-table-title"><div><div class="runtime-label">事件紀錄</div><h3>最近 10 筆事件</h3></div></div><div class="table-responsive">{% if recent_incidents %}{% set task_labels = {'ElephantAlphaAutonomousEngine': 'AI 自癒監控', 'run_icaim_analysis_task': '市場分析任務'} %}{% set error_labels = {'ollama_unhealthy': 'AI 模型主機不穩', 'scheduler_task_failure': '排程任務異常', 'crawler_timeout': '資料擷取逾時', 'python_exception': '程式例外'} %}<table class="table table-sm mb-0"><thead class="table-light"><tr><th>時間</th><th>任務</th><th>問題</th><th>等級</th><th>狀態</th><th>處置提醒</th></tr></thead><tbody>{% for i in recent_incidents %}<tr><td><small>{{ i.created_at }}</small></td><td><span>{{ task_labels.get(i.task_name, '系統任務') }}</span></td><td><span class="badge bg-secondary">{{ error_labels.get(i.error_type, '系統異常') }}</span></td><td><span class="badge {% if i.severity in ('P0','P1') %}bg-danger{% elif i.severity == 'P2' %}bg-warning{% else %}bg-info{% endif %}">{{ i.severity }}</span></td><td>{{ i.status }}</td><td><small class="text-muted">{% if i.error_type == 'ollama_unhealthy' %}AI 模型主機暫時不穩,已進入自癒監控。{% elif i.error_type == 'python_exception' %}資料流程發生程式例外,需由修復流程處理。{% elif i.error_type == 'scheduler_task_failure' %}排程任務異常,需確認下次執行是否恢復。{% else %}系統事件已記錄,請依狀態追蹤後續是否恢復。{% endif %}</small></td></tr>{% endfor %}</tbody></table>{% else %}<div class="text-muted text-center p-3 small">尚無事件紀錄</div>{% endif %}</div></article>
|
||||
<article class="runtime-table-shell"><div class="runtime-table-title"><div><div class="runtime-label">自癒紀錄</div><h3>最近 10 筆自癒</h3></div></div><div class="table-responsive">{% if recent_heals %}<table class="table table-sm mb-0"><thead class="table-light"><tr><th>時間</th><th>動作</th><th>結果</th><th class="text-end">耗時</th><th>細節</th></tr></thead><tbody>{% for h in recent_heals %}<tr><td><small>{{ h.created_at }}</small></td><td><span class="badge bg-info">{{ h.action_type or '—' }}</span></td><td>{% if h.result == 'success' %}<span class="badge bg-success">成功</span>{% elif h.result == 'failed' %}<span class="badge bg-danger">失敗</span>{% else %}<span class="badge bg-secondary">{{ h.result }}</span>{% endif %}</td><td class="text-end">{{ h.duration_ms }} ms</td><td><small class="text-muted">{{ h.action_detail }}</small></td></tr>{% endfor %}</tbody></table>{% else %}<div class="text-muted text-center p-3 small">尚無自癒紀錄</div>{% endif %}</div></article>
|
||||
</div>
|
||||
<aside class="runtime-stack">
|
||||
<article class="runtime-table-shell"><div class="runtime-table-title"><div><div class="runtime-label">自癒劇本</div><h3>AutoHeal 劇本</h3></div></div><div class="table-responsive">{% if playbook_ranking %}{% set playbook_error_labels = {'python_exception': '程式例外', 'scheduler_task_failure': '排程異常', 'ollama_unhealthy': 'AI 模型主機不穩'} %}{% set playbook_action_labels = {'CODE_FIX': '程式修復', 'SERVICE_RESTART': '服務重啟', 'ALERT_ONLY': '告警追蹤'} %}<table class="table table-sm mb-0"><thead class="table-light"><tr><th>名稱</th><th>成功率</th><th>狀態</th><th>切換</th></tr></thead><tbody>{% for p in playbook_ranking %}<tr><td><strong>{{ p.name }}</strong><br><small class="text-muted">{{ playbook_error_labels.get(p.error_type, '系統異常') }} · {{ playbook_action_labels.get(p.action_type, '自癒處理') }}</small></td><td>{% if (p.success + p.fail) > 0 %}<strong>{{ "%.0f"|format(p.success_rate) }}%</strong>{% else %}<span class="text-muted">—</span>{% endif %}</td><td>{% if p.is_active %}<span class="badge bg-success">啟用</span>{% else %}<span class="badge bg-secondary">停用</span>{% endif %}</td><td><button class="btn btn-sm btn-outline-secondary" onclick="togglePlaybook({{ p.id }}, {{ p.name|tojson }})">切換</button></td></tr>{% endfor %}</tbody></table>{% else %}<div class="text-muted text-center p-3 small">尚無劇本資料</div>{% endif %}</div></article>
|
||||
<article class="runtime-table-shell"><div class="runtime-table-title"><div><div class="runtime-label">自癒劇本</div><h3>自癒劇本</h3></div></div><div class="table-responsive">{% if playbook_ranking %}{% set playbook_error_labels = {'python_exception': '程式例外', 'scheduler_task_failure': '排程異常', 'ollama_unhealthy': 'AI 模型主機不穩'} %}{% set playbook_action_labels = {'CODE_FIX': '程式修復', 'SERVICE_RESTART': '服務重啟', 'ALERT_ONLY': '告警追蹤'} %}<table class="table table-sm mb-0"><thead class="table-light"><tr><th>名稱</th><th>成功率</th><th>狀態</th><th>切換</th></tr></thead><tbody>{% for p in playbook_ranking %}<tr><td><strong>{{ p.name }}</strong><br><small class="text-muted">{{ playbook_error_labels.get(p.error_type, '系統異常') }} · {{ playbook_action_labels.get(p.action_type, '自癒處理') }}</small></td><td>{% if (p.success + p.fail) > 0 %}<strong>{{ "%.0f"|format(p.success_rate) }}%</strong>{% else %}<span class="text-muted">—</span>{% endif %}</td><td>{% if p.is_active %}<span class="badge bg-success">啟用</span>{% else %}<span class="badge bg-secondary">停用</span>{% endif %}</td><td><button class="btn btn-sm btn-outline-secondary" onclick="togglePlaybook({{ p.id }}, {{ p.name|tojson }})">切換</button></td></tr>{% endfor %}</tbody></table>{% else %}<div class="text-muted text-center p-3 small">尚無劇本資料</div>{% endif %}</div></article>
|
||||
<article class="runtime-table-shell"><div class="runtime-table-title"><div><div class="runtime-label">備份</div><h3>備份歷史 7 日</h3></div></div><div class="table-responsive">{% if backup_history %}<table class="table table-sm mb-0"><thead class="table-light"><tr><th>時間</th><th>狀態</th><th class="text-end">MB</th></tr></thead><tbody>{% for b in backup_history %}<tr><td><small>{{ b.created_at }}</small></td><td>{% if b.status == 'success' %}<span class="badge bg-success">成功</span>{% else %}<span class="badge bg-danger">{{ b.status }}</span>{% endif %}</td><td class="text-end">{{ b.size_mb }}</td></tr>{% endfor %}</tbody></table>{% else %}<div class="text-muted text-center p-3 small">過去 7 日無備份紀錄</div>{% endif %}</div></article>
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
{% if embed_queue_pending > 0 or embed_queue_failed > 0 %}<div class="alert alert-warning mt-3"><strong>Embedding 重試佇列:</strong>待處理 {{ embed_queue_pending }} 筆 · 失敗 {{ embed_queue_failed }} 筆</div>{% endif %}
|
||||
|
||||
<p class="text-muted mt-3"><small><i class="fas fa-robot me-1"></i>Ollama 優先策略 v5.0 — 基礎設施生命線</small></p>
|
||||
<p class="text-muted mt-3"><small><i class="fas fa-robot me-1"></i>AI 基礎設施生命線</small></p>
|
||||
</div>
|
||||
|
||||
{% set host_health_payload = {
|
||||
|
||||
@@ -422,7 +422,7 @@
|
||||
<span class="obs-kicker"><i class="fas fa-satellite-dish"></i> 01 指揮總覽 <span class="obs-kicker-date">· {{ today }}</span></span>
|
||||
<h1 class="obs-title">AI 觀測戰情室</h1>
|
||||
<p class="obs-lede">
|
||||
私有 AI 中樞的第一入口:三主機、AI 呼叫、RAG 學習、MCP、AIOps、預算與 PPT 視覺審核收斂到同一張工作台。所有數字只讀正式資料來源;缺資料時呈現可診斷空狀態。
|
||||
私有 AI 中樞的第一入口:模型主機、AI 呼叫、知識學習、工具編排、自癒、預算與 PPT 視覺審核收斂到同一張工作台。所有數字只讀正式資料來源;缺資料時呈現可診斷空狀態。
|
||||
</p>
|
||||
|
||||
<div class="obs-command-strip">
|
||||
@@ -442,7 +442,7 @@
|
||||
<div class="obs-signal-note">當月累計 ${{ "%.2f"|format(summary.month_cost|default(0)) }}</div>
|
||||
</div>
|
||||
<div class="obs-signal">
|
||||
<div class="obs-signal-label">RAG 命中率</div>
|
||||
<div class="obs-signal-label">知識命中率</div>
|
||||
<div class="obs-signal-value obs-status-blue">{{ "%.1f"|format(ai.rag_rate) if ai else '—' }}{% if ai %}%{% endif %}</div>
|
||||
<div class="obs-signal-note">快取命中 {{ "%.0f"|format(ai.cache_rate) if ai else '—' }}{% if ai %}%{% endif %}</div>
|
||||
</div>
|
||||
@@ -535,7 +535,7 @@
|
||||
<div class="obs-metric-grid">
|
||||
<div class="obs-mini"><span class="obs-microcopy">錯誤率</span><strong class="{% if ai.error_rate >= 15 %}obs-status-bad{% elif ai.error_rate >= 5 %}obs-status-warn{% else %}obs-status-good{% endif %}">{{ "%.1f"|format(ai.error_rate) }}%</strong></div>
|
||||
<div class="obs-mini"><span class="obs-microcopy">失敗</span><strong>{{ ai.errors }}</strong></div>
|
||||
<div class="obs-mini"><span class="obs-microcopy">RAG 命中</span><strong class="obs-status-blue">{{ ai.rag_hits }}</strong></div>
|
||||
<div class="obs-mini"><span class="obs-microcopy">知識命中</span><strong class="obs-status-blue">{{ ai.rag_hits }}</strong></div>
|
||||
<div class="obs-mini"><span class="obs-microcopy">快取命中</span><strong>{{ ai.cache_hits }}</strong></div>
|
||||
</div>
|
||||
{% else %}
|
||||
@@ -548,7 +548,7 @@
|
||||
<div class="obs-panel-head">
|
||||
<div>
|
||||
<div class="obs-section-eyebrow">學習閉環</div>
|
||||
<h2 class="obs-panel-title">RAG 學習閘</h2>
|
||||
<h2 class="obs-panel-title">知識學習閘</h2>
|
||||
</div>
|
||||
<a class="obs-link-button" href="/observability/promotion_review"><i class="fas fa-brain"></i>審核</a>
|
||||
</div>
|
||||
@@ -577,7 +577,7 @@
|
||||
<div class="obs-metric-grid">
|
||||
<div class="obs-mini"><span class="obs-microcopy">AIOps 未解</span><strong class="{% if summary.aiops and summary.aiops.incidents_open > 0 %}obs-status-bad{% else %}obs-status-good{% endif %}">{{ summary.aiops.incidents_open if summary.aiops else '—' }}</strong></div>
|
||||
<div class="obs-mini"><span class="obs-microcopy">自癒成功率</span><strong>{{ "%.0f"|format(summary.aiops.heal_rate) if summary.aiops else '—' }}{% if summary.aiops %}%{% endif %}</strong></div>
|
||||
<div class="obs-mini"><span class="obs-microcopy">MCP 呼叫</span><strong>{{ "{:,}".format(summary.mcp.total) if summary.mcp else '—' }}</strong></div>
|
||||
<div class="obs-mini"><span class="obs-microcopy">工具呼叫</span><strong>{{ "{:,}".format(summary.mcp.total) if summary.mcp else '—' }}</strong></div>
|
||||
<div class="obs-mini"><span class="obs-microcopy">PPT 通過率</span><strong>{{ "%.0f"|format(summary.ppt.pass_rate) if summary.ppt and summary.ppt.total > 0 else '—' }}{% if summary.ppt and summary.ppt.total > 0 %}%{% endif %}</strong></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -599,27 +599,27 @@
|
||||
<div class="obs-section-eyebrow">系統成本</div>
|
||||
<h3>系統與成本</h3>
|
||||
<div class="obs-route-list">
|
||||
<a class="obs-route-card" href="/observability/host_health"><span class="obs-route-icon"><i class="fas fa-heartbeat"></i></span><span><span class="obs-route-title">主機健康</span><span class="obs-route-desc">三主機、MCP、AIOps、AutoHeal。</span></span><span class="obs-route-code">04</span></a>
|
||||
<a class="obs-route-card" href="/observability/host_health"><span class="obs-route-icon"><i class="fas fa-heartbeat"></i></span><span><span class="obs-route-title">主機健康</span><span class="obs-route-desc">模型主機、工具服務與自癒。</span></span><span class="obs-route-code">04</span></a>
|
||||
<a class="obs-route-card" href="/observability/ai_calls"><span class="obs-route-icon"><i class="fas fa-chart-bar"></i></span><span><span class="obs-route-title">AI 呼叫</span><span class="obs-route-desc">用量、成本、錯誤、知識與工具矩陣。</span></span><span class="obs-route-code">05</span></a>
|
||||
<a class="obs-route-card" href="/observability/budget"><span class="obs-route-icon"><i class="fas fa-wallet"></i></span><span><span class="obs-route-title">預算控管</span><span class="obs-route-desc">供應商成本與強制節流。</span></span><span class="obs-route-code">06</span></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="obs-route-group">
|
||||
<div class="obs-section-eyebrow">RAG 品質</div>
|
||||
<h3>RAG 與品質</h3>
|
||||
<div class="obs-section-eyebrow">知識品質</div>
|
||||
<h3>知識與品質</h3>
|
||||
<div class="obs-route-list">
|
||||
<a class="obs-route-card" href="/observability/promotion_review"><span class="obs-route-icon"><i class="fas fa-brain"></i></span><span><span class="obs-route-title">RAG 晉升審核</span><span class="obs-route-desc">晉升守門與人工審核。</span></span><span class="obs-route-code">07</span></a>
|
||||
<a class="obs-route-card" href="/observability/rag_queries"><span class="obs-route-icon"><i class="fas fa-magnifying-glass-chart"></i></span><span><span class="obs-route-title">RAG 召回詳情</span><span class="obs-route-desc">查詢命中、節省呼叫、反饋追蹤。</span></span><span class="obs-route-code">08</span></a>
|
||||
<a class="obs-route-card" href="/observability/promotion_review"><span class="obs-route-icon"><i class="fas fa-brain"></i></span><span><span class="obs-route-title">知識晉升審核</span><span class="obs-route-desc">晉升守門與人工審核。</span></span><span class="obs-route-code">07</span></a>
|
||||
<a class="obs-route-card" href="/observability/rag_queries"><span class="obs-route-icon"><i class="fas fa-magnifying-glass-chart"></i></span><span><span class="obs-route-title">知識召回詳情</span><span class="obs-route-desc">查詢命中、節省呼叫、反饋追蹤。</span></span><span class="obs-route-code">08</span></a>
|
||||
<a class="obs-route-card" href="/observability/quality_trend"><span class="obs-route-icon"><i class="fas fa-comments"></i></span><span><span class="obs-route-title">反饋趨勢</span><span class="obs-route-desc">呼叫端品質、蒸餾池、根因建議。</span></span><span class="obs-route-code">09</span></a>
|
||||
<a class="obs-route-card" href="/observability/ppt_audit_history"><span class="obs-route-icon"><i class="fas fa-search"></i></span><span><span class="obs-route-title">PPT 視覺審核</span><span class="obs-route-desc">PPT 審核、RAG 修法、AiderHeal。</span></span><span class="obs-route-code">10</span></a>
|
||||
<a class="obs-route-card" href="/observability/ppt_audit_history"><span class="obs-route-icon"><i class="fas fa-search"></i></span><span><span class="obs-route-title">PPT 視覺審核</span><span class="obs-route-desc">PPT 審核、修法建議、修復流程。</span></span><span class="obs-route-code">10</span></a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<p class="obs-microcopy mt-3">
|
||||
<i class="fas fa-database me-1"></i>
|
||||
資料來源:主機探測、AI 呼叫、預算、學習事件、RAG 查詢、MCP 呼叫、事件與自癒、PPT 審核。
|
||||
資料來源:主機探測、AI 呼叫、預算、學習事件、知識查詢、工具呼叫、事件與自癒、PPT 審核。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -62,10 +62,10 @@
|
||||
<section class="ppt-diagnostic-strip" id="ppt-runtime-diagnostic">
|
||||
<div>
|
||||
<div class="ppt-label">視覺 QA 尚未就緒</div>
|
||||
<strong>先補齊視覺 QA runtime,再判斷簡報品質。</strong>
|
||||
<strong>先補齊視覺 QA 執行條件,再判斷簡報品質。</strong>
|
||||
<small class="text-muted">{{ vision_status.summary }}</small>
|
||||
</div>
|
||||
<div class="ppt-diagnostic-checks" aria-label="視覺 QA runtime checklist">
|
||||
<div class="ppt-diagnostic-checks" aria-label="視覺 QA 執行條件檢查">
|
||||
{% for check in vision_status.readiness_checks %}
|
||||
<div class="ppt-diagnostic-check is-{{ check.status }}">
|
||||
<span>{{ check.label }}</span>
|
||||
@@ -89,9 +89,9 @@
|
||||
|
||||
<section class="ppt-aider-status{% if not aider_heal_active_count %} is-empty{% endif %}" data-ppt-aider-status aria-live="polite">
|
||||
<div class="ppt-aider-status-main">
|
||||
<span class="ppt-run-status is-planned"><i class="fas fa-wrench me-1" aria-hidden="true"></i>AiderHeal</span>
|
||||
<span class="ppt-run-status is-planned"><i class="fas fa-wrench me-1" aria-hidden="true"></i>修復流程</span>
|
||||
<div>
|
||||
<strong data-ppt-aider-status-title>{% if aider_heal_active_count %}AiderHeal 執行中 · {{ aider_heal_active_count }}{% else %}AiderHeal 待命{% endif %}</strong>
|
||||
<strong data-ppt-aider-status-title>{% if aider_heal_active_count %}修復流程執行中 · {{ aider_heal_active_count }}{% else %}修復流程待命{% endif %}</strong>
|
||||
<small data-ppt-aider-status-meta>{% if aider_heal_active_count %}修復完成後回報處理結果。{% else %}有問題的審核紀錄可直接派工。{% endif %}</small>
|
||||
</div>
|
||||
</div>
|
||||
@@ -120,7 +120,7 @@
|
||||
<div class="ppt-vision-job is-runtime">
|
||||
<span>執行環境</span>
|
||||
<strong>{{ vision_status.status_label }}</strong>
|
||||
<small>{{ vision_status.model }} · {{ vision_status.converter or '無轉檔器' }}</small>
|
||||
<small>{{ vision_status.model_label }} · {{ vision_status.converter_label }}</small>
|
||||
</div>
|
||||
{% if vision_audit_status.last_run %}
|
||||
<div class="ppt-vision-job">
|
||||
@@ -282,7 +282,7 @@
|
||||
</div>
|
||||
<div class="ppt-action-foot">
|
||||
<span class="ppt-run-status is-{{ lane.status }}">{{ item.status_label }}</span>
|
||||
{% if item.filename and lane.key in ['preview', 'database', 'triage', 'audit'] %}
|
||||
{% if item.filename and lane.key in ['preview', 'records', 'triage', 'audit'] %}
|
||||
<a class="btn btn-outline-primary btn-sm"
|
||||
href="{{ url_for('admin_observability.ppt_audit_file', filename=item.filename) }}"
|
||||
target="_blank"
|
||||
@@ -588,7 +588,7 @@
|
||||
data-ppt-aider-heal
|
||||
data-ppt-filename="{{ r.pptx_filename }}"
|
||||
data-ppt-error="{{ r.issue_summary or r.error_msg or '' }}">
|
||||
<i class="fas fa-wrench me-1"></i>AiderHeal
|
||||
<i class="fas fa-wrench me-1"></i>修復流程
|
||||
</button>
|
||||
{% endif %}
|
||||
</td>
|
||||
@@ -744,7 +744,7 @@
|
||||
{% if rag_fixes %}
|
||||
<section class="ppt-panel mt-3">
|
||||
<div class="ppt-panel-head">
|
||||
<div><div class="ppt-label">RAG 修法建議</div><h2 class="ppt-panel-title">RAG 自動修法建議</h2></div>
|
||||
<div><div class="ppt-label">修復建議</div><h2 class="ppt-panel-title">相似修法建議</h2></div>
|
||||
</div>
|
||||
<div class="ppt-panel-body">
|
||||
{% for fix in rag_fixes %}
|
||||
@@ -768,10 +768,10 @@
|
||||
<div class="alert alert-info mt-3">
|
||||
<strong>先補視覺 QA 條件</strong>
|
||||
<ul class="mb-0 small mt-2">
|
||||
<li>PPT_VISION_ENABLED=false</li>
|
||||
<li>188 主機需安裝 LibreOffice</li>
|
||||
<li>需 Ollama 拉取 minicpm-v 模型</li>
|
||||
<li>啟用後可立即補跑,或由每日 22:00 排程寫入 ppt_audit_results</li>
|
||||
<li>視覺 QA 尚未啟用</li>
|
||||
<li>正式主機需具備簡報轉檔服務</li>
|
||||
<li>視覺模型需完成準備</li>
|
||||
<li>啟用後可立即補跑,或由每日 22:00 排程更新審核紀錄</li>
|
||||
</ul>
|
||||
</div>
|
||||
{% elif files|length == 0 %}
|
||||
@@ -784,7 +784,7 @@
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
<p class="text-muted mt-3"><small><i class="fas fa-robot me-1"></i>Ollama 優先策略 v5.0 — PPT 視覺 QA 產線</small></p>
|
||||
<p class="text-muted mt-3"><small><i class="fas fa-robot me-1"></i>AI 視覺 QA 產線</small></p>
|
||||
</div>
|
||||
|
||||
<div class="ppt-preview-modal" data-ppt-preview-modal hidden aria-hidden="true">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "ewoooc_base.html" %}
|
||||
|
||||
{% block title %}RAG 知識晉升閘{% endblock %}
|
||||
{% block title %}知識晉升審核{% endblock %}
|
||||
|
||||
{% block ewooo_content %}
|
||||
<style>
|
||||
@@ -41,8 +41,8 @@
|
||||
|
||||
<div class="container-fluid mt-3">
|
||||
<section class="gate-hero">
|
||||
<div class="gate-kicker"><i class="fas fa-brain me-1"></i> RAG 知識晉升閘 · 人工審核 / 去重 / 防污染</div>
|
||||
<h1 class="gate-title">RAG 知識晉升閘</h1>
|
||||
<div class="gate-kicker"><i class="fas fa-brain me-1"></i> 知識晉升審核 · 人工審核 / 去重 / 防污染</div>
|
||||
<h1 class="gate-title">知識晉升審核</h1>
|
||||
<p class="gate-subtitle">先審核高權重學習片段,避免錯誤知識污染業績建議。</p>
|
||||
<div class="gate-command">
|
||||
<div class="gate-signal"><div class="gate-label">待審核</div><span class="gate-value {% if episodes|length > 0 %}status-warn{% else %}status-good{% endif %}">{{ episodes|length }}</span><small class="text-muted">高權重待審片段</small></div>
|
||||
@@ -87,7 +87,7 @@
|
||||
<article class="gate-panel"><div class="gate-panel-head"><div><div class="gate-label">蒸餾池</div><h2 class="gate-panel-title">30 日狀態分布</h2></div></div><div class="gate-panel-body"><div class="obs-chart-frame obs-chart-frame-tall"><canvas id="episodeDistChart"></canvas></div></div></article>
|
||||
{% endif %}
|
||||
{% if strategy_weights %}
|
||||
<article class="gate-panel"><div class="gate-panel-head"><div><div class="gate-label">OpenClaw 權重</div><h2 class="gate-panel-title">策略權重 Top</h2></div></div><div class="gate-panel-body"><div class="gate-mini-grid">{% for s in strategy_weights[:6] %}<div class="gate-mini"><span class="gate-label">{{ obs_label.strategy(s.strategy_key) }}</span><strong>{{ "%.2f"|format(s.weight) }}</strong><small class="text-muted">成功 {{ s.success }} · 失敗 {{ s.fail }}</small></div>{% endfor %}</div></div></article>
|
||||
<article class="gate-panel"><div class="gate-panel-head"><div><div class="gate-label">策略權重</div><h2 class="gate-panel-title">策略權重 Top</h2></div></div><div class="gate-panel-body"><div class="gate-mini-grid">{% for s in strategy_weights[:6] %}<div class="gate-mini"><span class="gate-label">{{ obs_label.strategy(s.strategy_key) }}</span><strong>{{ "%.2f"|format(s.weight) }}</strong><small class="text-muted">成功 {{ s.success }} · 失敗 {{ s.fail }}</small></div>{% endfor %}</div></div></article>
|
||||
{% endif %}
|
||||
</aside>
|
||||
</section>
|
||||
@@ -96,7 +96,7 @@
|
||||
<section class="gate-table-shell"><div class="gate-table-title"><div><div class="gate-label">知識庫</div><h3>最近 10 筆 ai_insights</h3></div></div><div class="table-responsive"><table class="table table-sm mb-0"><thead class="table-light"><tr><th>#</th><th>類型</th><th>期間</th><th>SKU</th><th>建立時間</th><th>預覽</th></tr></thead><tbody>{% for i in latest_insights %}<tr><td><code>#{{ i.id }}</code></td><td><span class="badge bg-info">{{ i.insight_type }}</span></td><td><small>{{ i.period or '—' }}</small></td><td><small>{{ i.product_sku or '—' }}</small></td><td><small>{{ i.created_at }}</small></td><td><small class="text-muted">{{ i.preview }}{% if i.preview|length >= 160 %}…{% endif %}</small></td></tr>{% endfor %}</tbody></table></div></section>
|
||||
{% endif %}
|
||||
|
||||
<p class="text-muted mt-3"><small><i class="fas fa-robot me-1"></i>Ollama 優先策略 v5.0 — RAG 知識晉升閘</small></p>
|
||||
<p class="text-muted mt-3"><small><i class="fas fa-robot me-1"></i>知識晉升審核</small></p>
|
||||
</div>
|
||||
|
||||
<template id="obs-promotion-review-data">{{ episode_distribution_30d | default({}) | tojson }}</template>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
{% set rag_total = (rag_overall_dist | sum(attribute='count')) if rag_overall_dist else 0 %}
|
||||
|
||||
<div class="container-fluid mt-3">
|
||||
<section class="quality-hero"><div class="quality-kicker"><i class="fas fa-comments me-1"></i> 品質診斷 · {{ days }} 日視窗</div><h1 class="quality-title">AI 品質診斷台</h1><p class="quality-subtitle">用反饋、RAG 分數與行動閉環檢查 AI 建議是否可靠。</p><form method="get" class="quality-filter"><select name="days" class="form-select form-select-sm">{% for d in [7,14,30,90] %}<option value="{{ d }}" {% if days == d %}selected{% endif %}>{{ d }} 日</option>{% endfor %}</select><button class="btn btn-primary btn-sm">查詢</button></form><div class="quality-command"><div class="quality-signal"><div class="quality-label">反饋總量</div><span class="quality-value">{{ total_feedback.value }}</span><small class="text-muted">呼叫端反饋總量</small></div><div class="quality-signal"><div class="quality-label">最差均分</div><span class="quality-value {% if worst_avg.value >= 4 %}status-good{% elif worst_avg.value >= 3 %}status-warn{% else %}status-bad{% endif %}">{{ "%.2f"|format(worst_avg.value) }}</span><small class="text-muted">最差呼叫端平均分</small></div><div class="quality-signal"><div class="quality-label">蒸餾樣本</div><span class="quality-value status-blue">{{ episode_total }}</span><small class="text-muted">蒸餾池 {{ days }} 日</small></div><div class="quality-signal"><div class="quality-label">RAG 評分</div><span class="quality-value">{{ rag_total }}</span><small class="text-muted">已回饋 RAG 查詢</small></div></div></section>
|
||||
<section class="quality-hero"><div class="quality-kicker"><i class="fas fa-comments me-1"></i> 品質診斷 · {{ days }} 日視窗</div><h1 class="quality-title">AI 品質診斷台</h1><p class="quality-subtitle">用反饋、知識分數與行動閉環檢查 AI 建議是否可靠。</p><form method="get" class="quality-filter"><select name="days" class="form-select form-select-sm">{% for d in [7,14,30,90] %}<option value="{{ d }}" {% if days == d %}selected{% endif %}>{{ d }} 日</option>{% endfor %}</select><button class="btn btn-primary btn-sm">查詢</button></form><div class="quality-command"><div class="quality-signal"><div class="quality-label">反饋總量</div><span class="quality-value">{{ total_feedback.value }}</span><small class="text-muted">呼叫端反饋總量</small></div><div class="quality-signal"><div class="quality-label">最差均分</div><span class="quality-value {% if worst_avg.value >= 4 %}status-good{% elif worst_avg.value >= 3 %}status-warn{% else %}status-bad{% endif %}">{{ "%.2f"|format(worst_avg.value) }}</span><small class="text-muted">最差呼叫端平均分</small></div><div class="quality-signal"><div class="quality-label">蒸餾樣本</div><span class="quality-value status-blue">{{ episode_total }}</span><small class="text-muted">蒸餾池 {{ days }} 日</small></div><div class="quality-signal"><div class="quality-label">知識評分</div><span class="quality-value">{{ rag_total }}</span><small class="text-muted">已回饋知識查詢</small></div></div></section>
|
||||
{% if error %}<div class="alert alert-warning mt-3"><strong><i class="fas fa-triangle-exclamation me-1"></i></strong>{{ error }}</div>{% endif %}
|
||||
|
||||
<section class="quality-grid">
|
||||
@@ -27,15 +27,15 @@
|
||||
{% if action_plans_status %}<article class="quality-table-shell"><div class="quality-table-title"><div><div class="quality-label">行動計畫</div><h3>行動計畫狀態分布</h3></div></div><div class="table-responsive"><table class="table table-sm mb-0"><thead class="table-light"><tr><th>狀態</th><th>計畫類型</th><th class="text-end">數量</th></tr></thead><tbody>{% for a in action_plans_status %}<tr><td><span class="badge {% if a.status == 'approved' %}bg-success{% elif a.status == 'pending' %}bg-warning{% elif a.status == 'rejected' %}bg-danger{% else %}bg-secondary{% endif %}">{{ obs_label.status(a.status) }}</span></td><td><code>{{ obs_label.plan_type(a.plan_type) }}</code></td><td class="text-end">{{ a.count }}</td></tr>{% endfor %}</tbody></table></div></article>{% endif %}
|
||||
</div>
|
||||
<aside class="quality-stack">
|
||||
{% if rag_overall_dist %}<article class="quality-panel"><div class="quality-panel-head"><div><div class="quality-label">RAG 反饋總量</div><h2 class="quality-panel-title">RAG 分數分布</h2></div></div><div class="quality-panel-body"><div class="obs-chart-frame"><canvas id="ragFeedbackPieChart"></canvas></div></div></article>{% endif %}
|
||||
{% if rag_overall_dist %}<article class="quality-panel"><div class="quality-panel-head"><div><div class="quality-label">知識反饋總量</div><h2 class="quality-panel-title">知識分數分布</h2></div></div><div class="quality-panel-body"><div class="obs-chart-frame"><canvas id="ragFeedbackPieChart"></canvas></div></div></article>{% endif %}
|
||||
{% if episode_distribution %}<article class="quality-panel"><div class="quality-panel-head"><div><div class="quality-label">學習池</div><h2 class="quality-panel-title">蒸餾池狀態</h2></div></div><div class="quality-panel-body"><div class="quality-mini-grid">{% for status, cnt in episode_distribution.items() %}<div class="quality-mini"><span class="quality-label">{{ obs_label.status(status) }}</span><strong>{{ cnt }}</strong></div>{% endfor %}</div></div></article>{% endif %}
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
{% if rag_root_causes %}<section class="quality-panel mt-3"><div class="quality-panel-head"><div><div class="quality-label">根因分析</div><h2 class="quality-panel-title">RAG 自動根因建議</h2></div></div><div class="quality-panel-body">{% for rc in rag_root_causes %}<div class="root-card"><strong><code>{{ rc.caller }}</code></strong><span class="badge bg-danger ms-1">{{ "%.2f"|format(rc.avg_score) }}/5</span><span class="badge bg-secondary ms-1">{{ rc.feedback_n }} 筆</span><ul class="list-unstyled mt-2 mb-0 small">{% for h in rc.hits %}<li class="mb-1"><span class="badge bg-info me-1">{{ obs_label.insight(h.insight_type) }}</span><span class="badge bg-light text-dark me-1">相似度 {{ "%.2f"|format(h.similarity) }}</span>{{ h.content }}{% if h.content|length >= 200 %}…{% endif %}</li>{% endfor %}</ul></div>{% endfor %}</div></section>{% endif %}
|
||||
{% if rag_root_causes %}<section class="quality-panel mt-3"><div class="quality-panel-head"><div><div class="quality-label">根因分析</div><h2 class="quality-panel-title">知識根因建議</h2></div></div><div class="quality-panel-body">{% for rc in rag_root_causes %}<div class="root-card"><strong><code>{{ rc.caller }}</code></strong><span class="badge bg-danger ms-1">{{ "%.2f"|format(rc.avg_score) }}/5</span><span class="badge bg-secondary ms-1">{{ rc.feedback_n }} 筆</span><ul class="list-unstyled mt-2 mb-0 small">{% for h in rc.hits %}<li class="mb-1"><span class="badge bg-info me-1">{{ obs_label.insight(h.insight_type) }}</span><span class="badge bg-light text-dark me-1">相似度 {{ "%.2f"|format(h.similarity) }}</span>{{ h.content }}{% if h.content|length >= 200 %}…{% endif %}</li>{% endfor %}</ul></div>{% endfor %}</div></section>{% endif %}
|
||||
{% if recommendations %}<section class="quality-panel mt-3"><div class="quality-panel-head"><div><div class="quality-label">智能建議</div><h2 class="quality-panel-title">智能建議</h2></div></div><div class="quality-panel-body"><ul class="mb-0">{% for rec in recommendations %}<li>{% if rec.action == 'review' %}<i class="fas fa-triangle-exclamation status-warn me-1"></i>{% else %}<i class="fas fa-check status-good me-1"></i>{% endif %}<code>{{ rec.caller }}</code>:{{ rec.reason }}</li>{% endfor %}</ul></div></section>{% endif %}
|
||||
{% if action_outcomes_stats %}<section class="quality-panel mt-3"><div class="quality-panel-head"><div><div class="quality-label">動作成效</div><h2 class="quality-panel-title">實際動作成效</h2></div></div><div class="quality-panel-body"><div class="quality-mini-grid">{% set total_ao = (action_outcomes_stats | sum(attribute='count')) or 1 %}{% for r in action_outcomes_stats %}<div class="quality-mini"><span class="quality-label">{{ obs_label.verdict(r.verdict) }}</span><strong class="{% if r.verdict == 'effective' %}status-good{% elif r.verdict == 'backfired' %}status-bad{% endif %}">{{ r.count }}</strong><small class="text-muted">{{ "%.1f"|format(r.count / total_ao * 100) }}%</small></div>{% endfor %}</div></div></section>{% endif %}
|
||||
<p class="text-muted mt-3"><small><i class="fas fa-robot me-1"></i>Ollama 優先策略 v5.0 — AI 品質診斷台</small></p>
|
||||
<p class="text-muted mt-3"><small><i class="fas fa-robot me-1"></i>AI 品質診斷台</small></p>
|
||||
</div>
|
||||
|
||||
<template id="obs-quality-trend-data">{{ rag_overall_dist | default([]) | tojson }}</template>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "ewoooc_base.html" %}
|
||||
|
||||
{% block title %}RAG 召回雷達{% endblock %}
|
||||
{% block title %}知識召回雷達{% endblock %}
|
||||
|
||||
{% block ewooo_content %}
|
||||
<style>
|
||||
@@ -18,9 +18,9 @@
|
||||
|
||||
<div class="container-fluid mt-3">
|
||||
<section class="qa-hero">
|
||||
<div class="qa-kicker"><i class="fas fa-magnifying-glass-chart me-1"></i> RAG 召回雷達 · {{ hours }} 小時視窗</div>
|
||||
<h1 class="qa-title">RAG 召回雷達</h1>
|
||||
<p class="qa-subtitle">追蹤 RAG 是否命中、是否省下 LLM 呼叫,避免業績建議缺少根據。</p>
|
||||
<div class="qa-kicker"><i class="fas fa-magnifying-glass-chart me-1"></i> 知識召回雷達 · {{ hours }} 小時視窗</div>
|
||||
<h1 class="qa-title">知識召回雷達</h1>
|
||||
<p class="qa-subtitle">追蹤知識是否命中、是否省下模型呼叫,避免業績建議缺少根據。</p>
|
||||
<form method="get" class="qa-filter"><select name="hours" class="form-select form-select-sm" onchange="this.form.submit()">{% for h in [1,6,24,72,168] %}<option value="{{ h }}" {% if hours == h %}selected{% endif %}>{% if h < 24 %}{{ h }} 小時{% else %}{{ h//24 }} 天{% endif %}</option>{% endfor %}</select><select name="caller" class="form-select form-select-sm" onchange="this.form.submit()"><option value="">全部呼叫端</option>{% for c in callers %}<option value="{{ c }}" {% if caller_filter == c %}selected{% endif %}>{{ c }}</option>{% endfor %}</select><label class="form-check-label small d-flex align-items-center gap-2"><input class="form-check-input" type="checkbox" name="saved_only" value="1" {% if saved_only %}checked{% endif %} onchange="this.form.submit()">僅看已省下 LLM 呼叫</label></form>
|
||||
{% if summary and summary.total > 0 %}<div class="qa-command"><div class="qa-signal"><div class="qa-label">查詢數</div><span class="qa-value">{{ "{:,}".format(summary.total) }}</span><div class="qa-note">{{ summary.distinct_callers }} 個呼叫端</div></div><div class="qa-signal"><div class="qa-label">命中率</div><span class="qa-value {% if summary.hit_rate >= 70 %}status-good{% elif summary.hit_rate >= 40 %}status-warn{% else %}status-bad{% endif %}">{{ "%.1f"|format(summary.hit_rate) }}%</span><div class="qa-note">{{ summary.with_hits }} 次命中 · {{ summary.no_hits }} 次未命中</div></div><div class="qa-signal"><div class="qa-label">省下呼叫</div><span class="qa-value status-blue">{{ "%.1f"|format(summary.saved_rate) }}%</span><div class="qa-note">{{ summary.saved }} 次省下 LLM</div></div><div class="qa-signal"><div class="qa-label">反饋分</div><span class="qa-value {% if summary.avg_score >= 4 %}status-good{% elif summary.avg_score >= 3 %}status-warn{% else %}status-bad{% endif %}">{{ "%.2f"|format(summary.avg_score) }}</span><div class="qa-note">{{ summary.feedback_count }} 筆 · 平均 {{ summary.avg_hits }} 次命中</div></div></div>{% endif %}
|
||||
</section>
|
||||
@@ -28,16 +28,16 @@
|
||||
|
||||
<section class="qa-grid">
|
||||
<div class="qa-stack">
|
||||
<article class="qa-table-shell"><div class="qa-table-title"><div><div class="qa-label">查詢串流</div><h3>最近 50 筆查詢詳情</h3></div></div><div class="table-responsive">{% if queries %}<table class="table table-sm mb-0"><thead class="table-light"><tr><th>時間</th><th>呼叫端</th><th>查詢</th><th class="text-end">top_k</th><th class="text-end">門檻</th><th class="text-end">命中</th><th>已省下</th><th>反饋</th><th>動作</th></tr></thead><tbody>{% for q in queries %}<tr><td><small>{{ q.queried_at }}</small></td><td><code>{{ q.caller }}</code></td><td><small>{{ q.query_text }}{% if q.query_text|length >= 200 %}…{% endif %}</small></td><td class="text-end">{{ q.top_k }}</td><td class="text-end">{{ q.threshold }}</td><td class="text-end">{% if q.hit_count > 0 %}<strong class="status-good">{{ q.hit_count }}</strong>{% else %}<small class="text-muted">0</small>{% endif %}</td><td>{% if q.saved_call %}<span class="badge bg-success">已省下</span>{% else %}<small class="text-muted">—</small>{% endif %}</td><td>{% if q.feedback_score is not none %}<span class="badge {% if q.feedback_score >= 4 %}bg-success{% elif q.feedback_score >= 3 %}bg-warning{% else %}bg-danger{% endif %}">{{ q.feedback_score }}/5</span>{% else %}<small class="text-muted">—</small>{% endif %}</td><td>{% if q.hit_count > 0 %}<button class="btn btn-sm btn-outline-info" onclick="showHits({{ q.id }})"><i class="fas fa-eye me-1"></i>查命中</button>{% endif %}</td></tr>{% endfor %}</tbody></table>{% else %}<div class="alert alert-info m-3">過去 {{ hours }} 小時無符合條件的 RAG 查詢紀錄。</div>{% endif %}</div></article>
|
||||
<article class="qa-table-shell"><div class="qa-table-title"><div><div class="qa-label">查詢串流</div><h3>最近 50 筆查詢詳情</h3></div></div><div class="table-responsive">{% if queries %}<table class="table table-sm mb-0"><thead class="table-light"><tr><th>時間</th><th>呼叫端</th><th>查詢</th><th class="text-end">top_k</th><th class="text-end">門檻</th><th class="text-end">命中</th><th>已省下</th><th>反饋</th><th>動作</th></tr></thead><tbody>{% for q in queries %}<tr><td><small>{{ q.queried_at }}</small></td><td><code>{{ q.caller }}</code></td><td><small>{{ q.query_text }}{% if q.query_text|length >= 200 %}…{% endif %}</small></td><td class="text-end">{{ q.top_k }}</td><td class="text-end">{{ q.threshold }}</td><td class="text-end">{% if q.hit_count > 0 %}<strong class="status-good">{{ q.hit_count }}</strong>{% else %}<small class="text-muted">0</small>{% endif %}</td><td>{% if q.saved_call %}<span class="badge bg-success">已省下</span>{% else %}<small class="text-muted">—</small>{% endif %}</td><td>{% if q.feedback_score is not none %}<span class="badge {% if q.feedback_score >= 4 %}bg-success{% elif q.feedback_score >= 3 %}bg-warning{% else %}bg-danger{% endif %}">{{ q.feedback_score }}/5</span>{% else %}<small class="text-muted">—</small>{% endif %}</td><td>{% if q.hit_count > 0 %}<button class="btn btn-sm btn-outline-info" onclick="showHits({{ q.id }})"><i class="fas fa-eye me-1"></i>查命中</button>{% endif %}</td></tr>{% endfor %}</tbody></table>{% else %}<div class="alert alert-info m-3">過去 {{ hours }} 小時無符合條件的知識查詢紀錄。</div>{% endif %}</div></article>
|
||||
</div>
|
||||
<aside class="qa-stack">
|
||||
{% if by_caller %}<article class="qa-panel"><div class="qa-panel-head"><div><div class="qa-label">呼叫端品質</div><h2 class="qa-panel-title">各呼叫端 RAG 表現</h2></div></div><div class="qa-panel-body">{% for c in by_caller %}<div class="caller-card"><div class="caller-top"><code>{{ c.caller }}</code><strong class="{% if c.hit_rate >= 70 %}status-good{% elif c.hit_rate >= 40 %}status-warn{% else %}text-muted{% endif %}">{{ "%.1f"|format(c.hit_rate) }}%</strong></div><div class="caller-meter"><span style="width: {{ c.hit_rate|round|int }}%"></span></div><small class="text-muted">{{ c.total }} 次查詢 · 省下 {{ "%.1f"|format(c.saved_rate) }}% · 反饋 {{ c.fb_count }} 筆</small></div>{% endfor %}</div></article>{% endif %}
|
||||
{% if by_caller %}<article class="qa-panel"><div class="qa-panel-head"><div><div class="qa-label">呼叫端品質</div><h2 class="qa-panel-title">各呼叫端知識表現</h2></div></div><div class="qa-panel-body">{% for c in by_caller %}<div class="caller-card"><div class="caller-top"><code>{{ c.caller }}</code><strong class="{% if c.hit_rate >= 70 %}status-good{% elif c.hit_rate >= 40 %}status-warn{% else %}text-muted{% endif %}">{{ "%.1f"|format(c.hit_rate) }}%</strong></div><div class="caller-meter"><span style="width: {{ c.hit_rate|round|int }}%"></span></div><small class="text-muted">{{ c.total }} 次查詢 · 省下 {{ "%.1f"|format(c.saved_rate) }}% · 反饋 {{ c.fb_count }} 筆</small></div>{% endfor %}</div></article>{% endif %}
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
<p class="text-muted mt-3"><small><i class="fas fa-robot me-1"></i>Ollama 優先策略 v5.0 — RAG 召回雷達</small></p>
|
||||
<p class="text-muted mt-3"><small><i class="fas fa-robot me-1"></i>知識召回雷達</small></p>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="hitsModal" tabindex="-1"><div class="modal-dialog modal-lg"><div class="modal-content"><div class="modal-header"><h5 class="modal-title"><i class="fas fa-eye me-2"></i>RAG 命中內容</h5><button type="button" class="btn-close" data-bs-dismiss="modal"></button></div><div class="modal-body" id="hitsModalBody"><div class="text-center"><i class="fas fa-spinner fa-spin"></i> 載入中...</div></div></div></div></div>
|
||||
<div class="modal fade" id="hitsModal" tabindex="-1"><div class="modal-dialog modal-lg"><div class="modal-content"><div class="modal-header"><h5 class="modal-title"><i class="fas fa-eye me-2"></i>知識命中內容</h5><button type="button" class="btn-close" data-bs-dismiss="modal"></button></div><div class="modal-body" id="hitsModalBody"><div class="text-center"><i class="fas fa-spinner fa-spin"></i> 載入中...</div></div></div></div></div>
|
||||
<script src="{{ url_for('static', filename='js/observability-charts.js') }}"></script>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user