alphax/static/chat_logs.html
2026-06-07 20:58:35 +08:00

179 lines
12 KiB
HTML

{% extends "base.html" %}
{% block title %}问答日志 · AlphaX Agent{% endblock %}
{% block extra_head_css %}
<style>
main{max-width:1320px;margin:0 auto;width:100%;padding:24px;display:flex;flex-direction:column;gap:16px}
.page-head{display:flex;align-items:flex-end;justify-content:space-between;gap:14px;flex-wrap:wrap}
.page-title{font-size:24px;font-weight:900;color:var(--ink);letter-spacing:-.4px}
.page-sub{margin-top:4px;font-size:13px;color:var(--stone)}
.summary-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px}
.summary-card{padding:14px 15px;border:1px solid var(--hairline-soft);border-radius:var(--radius-md);background:var(--canvas);min-width:0}
.summary-card span{display:block;color:var(--stone);font-size:11px;font-weight:900}
.summary-card b{display:block;margin-top:6px;color:var(--ink);font-size:24px;line-height:1;font-weight:900;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.layout{display:grid;grid-template-columns:minmax(0,1.1fr) minmax(0,.9fr);gap:14px;align-items:start}
.card{background:var(--canvas);border:1px solid var(--hairline-soft);border-radius:var(--radius-md);overflow:hidden}
.toolbar{display:flex;gap:8px;flex-wrap:wrap;padding:14px;border-bottom:1px solid var(--hairline-soft)}
.toolbar input,.toolbar select{min-height:38px;padding:8px 12px;background:var(--surface);border:1px solid var(--hairline);border-radius:var(--radius-md);color:var(--ink);font-size:13px;outline:none}
.toolbar input{flex:1;min-width:220px}
.toolbar button{padding:8px 16px;border:none;border-radius:var(--radius-md);background:var(--primary);color:var(--on-primary);font-size:13px;font-weight:800;cursor:pointer}
.table-wrap{overflow-x:auto}
table{width:100%;border-collapse:collapse;min-width:900px;font-size:13px}
th{text-align:left;padding:10px 12px;color:var(--stone);font-weight:900;border-bottom:1px solid var(--hairline-soft);font-size:11px;text-transform:uppercase;letter-spacing:.4px;background:var(--surface)}
td{padding:11px 12px;border-bottom:1px solid var(--hairline-soft);color:var(--ink);vertical-align:top}
tr:hover td{background:var(--surface)}
.badge{display:inline-flex;align-items:center;height:22px;padding:0 8px;border-radius:var(--radius-full);font-size:11px;font-weight:900;border:1px solid var(--hairline-soft);background:var(--surface);color:var(--stone);white-space:nowrap}
.badge-blue{background:rgba(66,98,255,.10);color:var(--blue);border-color:rgba(66,98,255,.18)}
.badge-yellow{background:rgba(255,208,47,.14);color:var(--yellow-dark);border-color:rgba(255,208,47,.25)}
.badge-green{background:rgba(0,180,115,.10);color:var(--green);border-color:rgba(0,180,115,.18)}
.badge-red{background:rgba(229,62,62,.10);color:var(--red);border-color:rgba(229,62,62,.18)}
.pagination{display:flex;justify-content:center;align-items:center;gap:12px;padding:14px;font-size:13px;color:var(--stone)}
.pagination button{padding:6px 14px;background:var(--surface);border:1px solid var(--hairline);border-radius:var(--radius-md);color:var(--ink);font-size:13px;cursor:pointer}
.pagination button:disabled{opacity:.4;cursor:default}
.panel{padding:16px}
.mini-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}
.mini{border:1px solid var(--hairline-soft);border-radius:var(--radius-md);background:var(--surface);padding:12px}
.mini span{display:block;color:var(--stone);font-size:11px;font-weight:900}
.mini b{display:block;margin-top:6px;color:var(--ink);font-size:18px;font-weight:900;line-height:1.2}
.topic-list{display:grid;gap:8px}
.topic{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:10px 12px;border:1px solid var(--hairline-soft);border-radius:var(--radius-md);background:var(--surface)}
.topic b{font-size:13px;color:var(--ink)}
.topic span{font-size:12px;color:var(--stone);white-space:nowrap}
.question-list{display:grid;gap:8px}
.question{padding:11px 12px;border:1px solid var(--hairline-soft);border-radius:var(--radius-md);background:var(--surface)}
.question b{display:block;font-size:13px;color:var(--ink);line-height:1.45}
.question .meta{display:flex;gap:8px;flex-wrap:wrap;margin-top:6px;font-size:11px;color:var(--stone)}
.empty{text-align:center;padding:34px 14px;color:var(--stone);font-size:13px}
@media(max-width:960px){main{padding:18px}.layout{grid-template-columns:1fr}.summary-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media(max-width:560px){.summary-grid{grid-template-columns:1fr}.page-title{font-size:21px}}
</style>
{% endblock %}
{% block content %}
<main>
<div class="page-head">
<div>
<div class="page-title">问答日志</div>
<div class="page-sub">查看智能问答里用户在问什么,以及哪些问题最常出现。</div>
</div>
</div>
<div class="summary-grid" id="summaryGrid">
<div class="summary-card"><span>加载中</span><b>--</b></div>
</div>
<div class="layout">
<div class="card">
<div class="toolbar">
<input type="text" id="search" placeholder="搜索问题、会话、用户..." onkeydown="if(event.key==='Enter')loadQuestions(0)">
<select id="intent" onchange="loadQuestions(0)">
<option value="all">全部意图</option>
<option value="coin_analysis">单币分析</option>
<option value="market_overview">市场问答</option>
<option value="recommendation_explain">推荐解释</option>
<option value="sentiment">舆情解读</option>
<option value="review">复盘查询</option>
<option value="restricted">受限内容</option>
</select>
<select id="hours" onchange="loadQuestions(0)">
<option value="24">近 24h</option>
<option value="168" selected>近 7 天</option>
<option value="720">近 30 天</option>
<option value="0">全部</option>
</select>
<button onclick="loadQuestions(0)">查询</button>
</div>
<div class="table-wrap">
<table>
<thead><tr><th>时间</th><th>用户</th><th>意图</th><th>问题</th><th>币种</th><th>会话</th></tr></thead>
<tbody id="table"><tr><td colspan="6" class="empty">加载中...</td></tr></tbody>
</table>
</div>
<div class="pagination" id="pagination"></div>
</div>
<div class="card panel">
<div class="mini-grid" id="sideStats">
<div class="mini"><span>热门意图</span><b>--</b></div>
</div>
<div style="height:14px"></div>
<div class="section-title" style="font-size:12px;font-weight:900;color:var(--stone);text-transform:uppercase;letter-spacing:.4px;margin-bottom:8px">热门问题</div>
<div id="topQuestions" class="question-list"></div>
<div style="height:14px"></div>
<div class="section-title" style="font-size:12px;font-weight:900;color:var(--stone);text-transform:uppercase;letter-spacing:.4px;margin-bottom:8px">热门币种</div>
<div id="topSymbols" class="topic-list"></div>
</div>
</div>
</main>
{% endblock %}
{% block password_modal %}{% endblock %}
{% block extra_script %}
<script>
var API='',PAGE_SIZE=50,OFFSET=0,TOTAL=0;
async function init(){
try{var chk=await fetch(API+'/api/admin/check');if(!chk.ok){window.location.href='/subscription';return}
var info=await chk.json();if(!info.is_admin){window.location.href='/subscription';return}}catch(e){window.location.href='/subscription';return}
loadOverview();loadQuestions(0);
}
async function loadOverview(){
var hours=document.getElementById('hours').value||168;
try{
var r=await fetch(API+'/api/admin/chat-logs/overview?hours='+encodeURIComponent(hours));
if(!r.ok)throw new Error(r.status);
var d=await r.json();
document.getElementById('summaryGrid').innerHTML=[
['提问总数',d.total_questions||0,'近 '+d.hours+'h 用户提问'],
['会话数',d.total_sessions||0,'涉及 '+(d.total_users||0)+' 位用户'],
['消息总数',d.total_messages||0,'包含用户与助手消息'],
['热门意图',((d.top_intents&&d.top_intents[0]&&d.top_intents[0].intent)||'--'),'当前最常见']
].map(function(x){return '<div class="summary-card"><span>'+esc(x[0])+'</span><b>'+esc(x[1])+'</b><div class="sub">'+esc(x[2])+'</div></div>';}).join('');
document.getElementById('sideStats').innerHTML=(d.top_intents||[]).slice(0,4).map(function(x){return '<div class="mini"><span>'+esc(x.intent)+'</span><b>'+esc(x.n)+'</b></div>';}).join('')||'<div class="mini"><span>统计</span><b>暂无数据</b></div>';
document.getElementById('topQuestions').innerHTML=(d.recent_questions||[]).slice(0,8).map(function(x){return '<div class="question"><b>'+esc(shortText(x.content_text||'--',140))+'</b><div class="meta"><span>'+esc(fmtDate(x.created_at))+'</span><span>'+esc(x.user_email||'--')+'</span><span>'+esc(x.intent||'unknown')+'</span></div></div>';}).join('')||'<div class="empty">暂无问题</div>';
document.getElementById('topSymbols').innerHTML=(d.top_symbols||[]).map(function(x){return '<div class="topic"><b>'+esc(x.symbol)+'</b><span>'+esc(x.n)+' 次</span></div>';}).join('')||'<div class="empty">暂无币种</div>';
}catch(e){
document.getElementById('summaryGrid').innerHTML='<div class="summary-card"><span>统计</span><b>加载失败</b></div>';
}
}
async function loadQuestions(offset){
OFFSET=offset;
var q=document.getElementById('search').value.trim();
var intent=document.getElementById('intent').value;
var hours=document.getElementById('hours').value;
document.getElementById('table').innerHTML='<tr><td colspan="6" class="empty">加载中...</td></tr>';
try{
var url=API+'/api/admin/chat-logs?search='+encodeURIComponent(q)+'&intent='+encodeURIComponent(intent)+'&hours='+encodeURIComponent(hours)+'&offset='+offset+'&limit='+PAGE_SIZE;
var r=await fetch(url);if(!r.ok)throw new Error(r.status);
var d=await r.json();TOTAL=d.total||0;renderRows(d.items||[]);renderPagination();
loadOverview();
}catch(e){
document.getElementById('table').innerHTML='<tr><td colspan="6" class="empty" style="color:var(--red)">加载失败</td></tr>';
}
}
function renderRows(items){
var tb=document.getElementById('table');
if(!items.length){tb.innerHTML='<tr><td colspan="6" class="empty">暂无提问</td></tr>';return}
tb.innerHTML=items.map(function(x){
return '<tr>'+
'<td style="color:var(--stone);font-size:12px">'+fmtDate(x.created_at)+'</td>'+
'<td>'+esc(x.user_email||'--')+'</td>'+
'<td><span class="badge badge-blue">'+esc(x.intent||'unknown')+'</span></td>'+
'<td style="line-height:1.5;max-width:480px;white-space:normal">'+esc(shortText(x.content_text||'--',180))+'</td>'+
'<td>'+esc(x.symbol||'--')+'</td>'+
'<td style="color:var(--stone);font-size:12px">'+esc(shortText(x.session_title||('会话 #'+x.session_id),28))+'</td>'+
'</tr>';
}).join('');
}
function renderPagination(){
var pg=document.getElementById('pagination'),totalPages=Math.ceil(TOTAL/PAGE_SIZE),cur=Math.floor(OFFSET/PAGE_SIZE)+1;
pg.innerHTML='<button '+(OFFSET===0?'disabled':'')+' onclick="loadQuestions('+(OFFSET-PAGE_SIZE)+')">上一页</button>'+
'<span>第 '+cur+' / '+Math.max(1,totalPages)+' 页 · 共 '+TOTAL+' 条</span>'+
'<button '+((OFFSET+PAGE_SIZE>=TOTAL)?'disabled':'')+' onclick="loadQuestions('+(OFFSET+PAGE_SIZE)+')">下一页</button>';
}
function esc(s){return String(s||'').replace(/[&<>"]/g,function(c){return{'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]})}
function shortText(s,n){s=String(s||'');return s.length>n?s.slice(0,n)+'…':s}
function fmtDate(ts){if(!ts)return'--';var d=new Date(ts);if(isNaN(d.getTime()))return String(ts).slice(0,19).replace('T',' ');return (d.getMonth()+1)+'/'+d.getDate()+' '+('0'+d.getHours()).slice(-2)+':'+('0'+d.getMinutes()).slice(-2)}
init();
</script>
{% endblock %}