feat(nas): 模型调用统计镜像与界面 - sync_model_calls 表+拉取+聚合统计;fam-ui 新增'🤖 模型统计'页(按模型成功/失败/成功率/平均耗时 + 最近调用明细含失败原因)

This commit is contained in:
ericwyuan
2026-08-21 12:23:32 +08:00
parent b87b38b140
commit 3e94c2a8b0
4 changed files with 171 additions and 3 deletions

View File

@@ -317,7 +317,7 @@ st.sidebar.markdown(
# 顶部横向导航
page = st.segmented_control(
"功能页面",
["🕒 事件时间轴", "💬 AI 对话", "📝 对话历史", "👤 人物管理", "📈 统计图表"],
["🕒 事件时间轴", "💬 AI 对话", "📝 对话历史", "👤 人物管理", "📈 统计图表", "🤖 模型统计"],
default="🕒 事件时间轴",
label_visibility="collapsed"
) or "🕒 事件时间轴"
@@ -901,3 +901,83 @@ elif page == "📈 统计图表":
finally:
if conn:
conn.close()
# ============================================================
# 模型统计页(云端模型识别成功/失败统计)
# ============================================================
elif page == "🤖 模型统计":
page_header('🤖', '云端模型统计', '请求时间 · 耗时 · 成功/失败 · 失败原因')
conn = None
try:
conn = get_db_conn()
cursor = conn.cursor()
# ---- 按模型聚合:成功/失败/成功率/平均耗时 ----
st.markdown('<div style="font-size:15px;font-weight:700;color:#f1f5f9;'
'margin:6px 0 14px 0;">按模型聚合</div>', unsafe_allow_html=True)
cursor.execute(
"""SELECT provider, model,
SUM(CASE WHEN success=1 THEN 1 ELSE 0 END) AS ok_cnt,
SUM(CASE WHEN success=0 THEN 1 ELSE 0 END) AS fail_cnt,
ROUND(AVG(duration_sec),1) AS avg_dur,
COUNT(*) AS total,
MAX(created_at) AS last_call
FROM sync_model_calls GROUP BY provider, model
ORDER BY total DESC""")
agg = cursor.fetchall()
if agg:
rows = []
for r in agg:
total = r['total'] or 0
ok = r['ok_cnt'] or 0
rate = (ok / total * 100) if total else 0
rows.append({
"模型": f"{r['provider']} / {r['model']}",
"成功": ok,
"失败": r['fail_cnt'] or 0,
"成功率%": round(rate, 1),
"平均耗时s": r['avg_dur'],
"最后调用": str(r['last_call'] or '')[:19],
})
st.dataframe(rows, use_container_width=True, hide_index=True)
else:
st.markdown('<div class="fam-empty">暂无模型调用记录(视频处理中或尚未同步)</div>',
unsafe_allow_html=True)
# ---- 最近调用明细 ----
st.markdown('<div style="font-size:15px;font-weight:700;color:#f1f5f9;'
'margin:22px 0 14px 0;">最近调用明细</div>', unsafe_allow_html=True)
cursor.execute(
"""SELECT provider, model, started_at, duration_sec, success, error,
filename, video_id
FROM sync_model_calls ORDER BY id DESC LIMIT 100""")
calls = cursor.fetchall()
if calls:
import pandas as pd
rows = []
for c in calls:
rows.append({
"请求时间": str(c['started_at'] or '')[:19],
"模型": f"{c['provider']} / {c['model']}",
"耗时s": round(c['duration_sec'] or 0, 1),
"状态": "✅ 成功" if c['success'] else "❌ 失败",
"失败原因": (c['error'] or '')[:60],
"视频": (c['filename'] or '')[:40],
})
st.dataframe(rows, use_container_width=True, hide_index=True)
else:
st.markdown('<div class="fam-empty">暂无调用明细</div>', unsafe_allow_html=True)
# ---- 说明 ----
st.markdown(
'<div style="font-size:11px;color:#64748b;margin-top:10px;">'
'统计来自甲骨文端每次云端模型请求的记录(经 30 分钟同步拉取到本地镜像)。'
'失败原因取值429_quota配额耗尽/ timeout / 503_overload过载重试/ http_xxx / json_parse_failed 等。'
'</div>', unsafe_allow_html=True)
except Exception as e:
st.error(f"模型统计查询失败: {e}")
finally:
if conn:
conn.close()