去掉大模型
This commit is contained in:
-237
@@ -15,19 +15,6 @@ const PERIOD_TTL_MS = 4 * 60 * 60 * 1000;
|
||||
|
||||
let statsData = null;
|
||||
let currentView = "today";
|
||||
let llmPollTimer = null;
|
||||
let llmInterpretMap = {};
|
||||
let llmRunState = {
|
||||
running: false,
|
||||
batch_id: "",
|
||||
current_symbol: "",
|
||||
done: 0,
|
||||
total: 0,
|
||||
};
|
||||
let llmSymbolOrder = [];
|
||||
const llmExpandedSymbols = new Set();
|
||||
const llmSeenDone = new Set();
|
||||
|
||||
const SORT_KEYS = {
|
||||
rank: (r) => Number(r.rank) || 0,
|
||||
symbol: (r) => String(r.symbol || ""),
|
||||
@@ -297,8 +284,6 @@ function renderStatsTable() {
|
||||
return;
|
||||
}
|
||||
|
||||
llmSymbolOrder = items.map((r) => r.symbol);
|
||||
|
||||
wrap.innerHTML = `
|
||||
<table data-table="stats" class="stats-table">
|
||||
<thead><tr>
|
||||
@@ -307,7 +292,6 @@ function renderStatsTable() {
|
||||
<th>昨日排名</th><th>昨日涨跌</th><th>昨日成交额</th>
|
||||
<th>前日排名</th><th>前日涨跌</th><th>前日成交额</th>
|
||||
<th>三日总成交额</th>
|
||||
<th class="llm-col-head">AI解读</th>
|
||||
</tr></thead>
|
||||
<tbody id="stats-body"></tbody>
|
||||
</table>`;
|
||||
@@ -327,97 +311,9 @@ function renderStatsTable() {
|
||||
${cell("yesterday", "rank")}${cell("yesterday", "pct")}${cell("yesterday", "vol")}
|
||||
${cell("daybefore", "rank")}${cell("daybefore", "pct")}${cell("daybefore", "vol")}
|
||||
<td class="stats-total-vol">${formatVol(row.total_quote_volume)}</td>
|
||||
<td class="llm-col">${buildLlmCellHtml(row.symbol)}</td>
|
||||
</tr>`;
|
||||
})
|
||||
.join("");
|
||||
bindLlmFoldHandlers();
|
||||
}
|
||||
|
||||
function getLlmCellState(symbol) {
|
||||
const llm = llmInterpretMap[symbol];
|
||||
if (llm?.content) {
|
||||
const failed = String(llm.content).startsWith("[解读失败]");
|
||||
return { kind: failed ? "failed" : "done", llm };
|
||||
}
|
||||
if (!llmRunState.running) return { kind: "idle" };
|
||||
if (symbol === llmRunState.current_symbol) return { kind: "running" };
|
||||
const idx = llmSymbolOrder.indexOf(symbol);
|
||||
if (idx >= 0 && idx < llmRunState.done) return { kind: "done", llm: null };
|
||||
return { kind: "pending" };
|
||||
}
|
||||
|
||||
function buildLlmCellHtml(symbol) {
|
||||
const st = getLlmCellState(symbol);
|
||||
if (st.kind === "done" && !st.llm) {
|
||||
return `<div class="llm-placeholder done">已完成,点「刷新解读」</div>`;
|
||||
}
|
||||
if (st.kind === "done" && st.llm) {
|
||||
const t = (st.llm.created_at || "").replace("T", " ").slice(0, 19);
|
||||
const open = llmExpandedSymbols.has(symbol) ? " open" : "";
|
||||
return `<details class="llm-fold"${open} data-symbol="${symbol}">
|
||||
<summary class="llm-summary done">查看解读 <span class="llm-time">${t}</span></summary>
|
||||
<div class="llm-text">${escapeHtml(st.llm.content)}</div>
|
||||
</details>`;
|
||||
}
|
||||
if (st.kind === "failed" && st.llm) {
|
||||
return `<details class="llm-fold" data-symbol="${symbol}">
|
||||
<summary class="llm-summary failed">解读失败</summary>
|
||||
<div class="llm-text llm-err">${escapeHtml(st.llm.content)}</div>
|
||||
</details>`;
|
||||
}
|
||||
if (st.kind === "running") {
|
||||
return `<div class="llm-placeholder running">解读中…</div>`;
|
||||
}
|
||||
if (st.kind === "pending") {
|
||||
return `<div class="llm-placeholder pending">排队等待</div>`;
|
||||
}
|
||||
return `<span class="muted">—</span>`;
|
||||
}
|
||||
|
||||
function bindLlmFoldHandlers() {
|
||||
document.querySelectorAll("details.llm-fold").forEach((el) => {
|
||||
el.ontoggle = () => {
|
||||
const sym = el.dataset.symbol;
|
||||
if (!sym) return;
|
||||
if (el.open) llmExpandedSymbols.add(sym);
|
||||
else llmExpandedSymbols.delete(sym);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function updateStatsLlmRows() {
|
||||
const body = document.getElementById("stats-body");
|
||||
if (!body) return;
|
||||
|
||||
document.querySelectorAll("tr.stats-row[data-symbol]").forEach((tr) => {
|
||||
const sym = tr.dataset.symbol;
|
||||
const td = tr.querySelector("td.llm-col");
|
||||
if (!td) return;
|
||||
const wasOpen = td.querySelector("details.llm-fold")?.open;
|
||||
td.innerHTML = buildLlmCellHtml(sym);
|
||||
const det = td.querySelector("details.llm-fold");
|
||||
if (det) {
|
||||
det.ontoggle = () => {
|
||||
if (det.open) llmExpandedSymbols.add(sym);
|
||||
else llmExpandedSymbols.delete(sym);
|
||||
};
|
||||
if (wasOpen || llmExpandedSymbols.has(sym)) det.open = true;
|
||||
}
|
||||
});
|
||||
|
||||
for (const sym of Object.keys(llmInterpretMap)) {
|
||||
if (llmSeenDone.has(sym)) continue;
|
||||
const llm = llmInterpretMap[sym];
|
||||
if (llm?.content && !String(llm.content).startsWith("[解读失败]")) {
|
||||
llmSeenDone.add(sym);
|
||||
const det = document.querySelector(`details.llm-fold[data-symbol="${sym}"]`);
|
||||
if (det) {
|
||||
det.open = true;
|
||||
llmExpandedSymbols.add(sym);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function escapeHtml(s) {
|
||||
@@ -434,134 +330,6 @@ function formatVol(v) {
|
||||
return String(Math.round(v));
|
||||
}
|
||||
|
||||
function applyLlmPayload(data) {
|
||||
if (data.batch_id) llmRunState.batch_id = data.batch_id;
|
||||
if (data.running != null) {
|
||||
llmRunState.running = !!data.running;
|
||||
llmRunState.batch_id = data.batch_id || llmRunState.batch_id;
|
||||
llmRunState.current_symbol = data.current_symbol || "";
|
||||
llmRunState.done = data.done ?? llmRunState.done;
|
||||
llmRunState.total = data.total ?? llmRunState.total;
|
||||
}
|
||||
const nextMap = { ...llmInterpretMap };
|
||||
for (const item of data.items || []) {
|
||||
if (item.symbol) nextMap[item.symbol] = item;
|
||||
}
|
||||
llmInterpretMap = nextMap;
|
||||
if (document.getElementById("stats-body")) {
|
||||
updateStatsLlmRows();
|
||||
} else if (statsData?.ok) {
|
||||
renderStatsTable();
|
||||
}
|
||||
}
|
||||
|
||||
async function loadLlmInterpretations() {
|
||||
const q = llmRunState.batch_id
|
||||
? `?batch_id=${encodeURIComponent(llmRunState.batch_id)}`
|
||||
: "";
|
||||
const res = await fetch(`/api/llm/interpretations${q}`);
|
||||
if (!res.ok) throw new Error("加载解读失败");
|
||||
const data = await res.json();
|
||||
applyLlmPayload(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
function updateLlmStatusText(st) {
|
||||
const label = document.getElementById("llm-model-label");
|
||||
const text = document.getElementById("llm-status-text");
|
||||
if (label) label.textContent = st.enabled ? st.model : "未配置";
|
||||
if (!text) return;
|
||||
if (st.running) {
|
||||
text.textContent = `解读中 ${st.done}/${st.total} · 当前 ${st.current_symbol || "—"}`;
|
||||
} else {
|
||||
text.textContent = st.enabled
|
||||
? `就绪 · 已完成 ${Object.keys(llmInterpretMap).length} 条 · 批次 ${st.batch_id || dataBatchId(st)}`
|
||||
: "请在 .env 配置 LLM_API_KEY";
|
||||
}
|
||||
}
|
||||
|
||||
function dataBatchId(st) {
|
||||
const keys = Object.keys(llmInterpretMap);
|
||||
return keys.length ? llmInterpretMap[keys[0]]?.batch_id || "—" : "—";
|
||||
}
|
||||
|
||||
async function refreshLlmStatus() {
|
||||
const res = await fetch("/api/llm/status");
|
||||
if (!res.ok) throw new Error("状态获取失败");
|
||||
const st = await res.json();
|
||||
llmRunState.running = !!st.running;
|
||||
llmRunState.batch_id = st.batch_id || llmRunState.batch_id;
|
||||
llmRunState.current_symbol = st.current_symbol || "";
|
||||
llmRunState.done = st.done ?? 0;
|
||||
llmRunState.total = st.total ?? 0;
|
||||
updateLlmStatusText(st);
|
||||
return st;
|
||||
}
|
||||
|
||||
function startLlmPolling() {
|
||||
if (llmPollTimer) return;
|
||||
llmPollTimer = setInterval(async () => {
|
||||
try {
|
||||
await refreshLlmStatus();
|
||||
await loadLlmInterpretations();
|
||||
const st = await fetch("/api/llm/status").then((r) => r.json());
|
||||
if (!st.running) {
|
||||
clearInterval(llmPollTimer);
|
||||
llmPollTimer = null;
|
||||
await loadLlmInterpretations();
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("LLM poll:", e);
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
function stopLlmPolling() {
|
||||
if (llmPollTimer) {
|
||||
clearInterval(llmPollTimer);
|
||||
llmPollTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshLlmAll() {
|
||||
const text = document.getElementById("llm-status-text");
|
||||
if (text) text.textContent = "刷新中…";
|
||||
try {
|
||||
const st = await refreshLlmStatus();
|
||||
await loadLlmInterpretations();
|
||||
if (st.running) startLlmPolling();
|
||||
else stopLlmPolling();
|
||||
} catch (e) {
|
||||
if (text) text.textContent = "刷新失败";
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
async function runLlmInterpret() {
|
||||
const btn = document.getElementById("btn-llm-run");
|
||||
if (btn) btn.disabled = true;
|
||||
try {
|
||||
const res = await fetch("/api/llm/interpret/run", { method: "POST" });
|
||||
const data = await res.json();
|
||||
if (!data.ok) {
|
||||
alert(data.message || "启动失败");
|
||||
return;
|
||||
}
|
||||
llmInterpretMap = {};
|
||||
llmSeenDone.clear();
|
||||
if (data.batch_id) llmRunState.batch_id = data.batch_id;
|
||||
llmRunState.running = true;
|
||||
await refreshLlmStatus();
|
||||
await loadLlmInterpretations();
|
||||
startLlmPolling();
|
||||
if (document.getElementById("stats-body")) updateStatsLlmRows();
|
||||
} catch (e) {
|
||||
alert(e.message);
|
||||
} finally {
|
||||
if (btn) btn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
function renderWecomDayRow(label, row) {
|
||||
if (!row?.rank) {
|
||||
return `<div class="wecom-day muted"><span class="wecom-day-label">${label}</span>—</div>`;
|
||||
@@ -653,7 +421,6 @@ async function loadStats() {
|
||||
const res = await fetch("/api/stats/three-day");
|
||||
statsData = await res.json();
|
||||
renderStatsTable();
|
||||
await refreshLlmAll();
|
||||
await loadWecomPreview();
|
||||
} catch (e) {
|
||||
document.getElementById("stats-table-wrap").innerHTML = `<p class="error">${e.message}</p>`;
|
||||
@@ -690,7 +457,6 @@ function switchView(view) {
|
||||
|
||||
if (view === "stats") {
|
||||
if (!statsData) loadStats();
|
||||
else refreshLlmAll();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -723,9 +489,6 @@ document.getElementById("btn-refresh").addEventListener("click", async () => {
|
||||
if (currentView === "stats") await loadStats();
|
||||
});
|
||||
|
||||
document.getElementById("btn-llm-run")?.addEventListener("click", runLlmInterpret);
|
||||
document.getElementById("btn-llm-refresh")?.addEventListener("click", () => refreshLlmAll());
|
||||
|
||||
document.getElementById("btn-reload-stats")?.addEventListener("click", () => {
|
||||
statsData = null;
|
||||
loadStats();
|
||||
|
||||
+1
-7
@@ -9,7 +9,7 @@
|
||||
<body>
|
||||
<header class="site-header">
|
||||
<h1>币安 U本位合约 · 成交额排名</h1>
|
||||
<p class="subtitle">北京时间 08:00 切日 · Top30 · 今日每4小时自动刷新+手动 · 08:05 AI解读三日交集</p>
|
||||
<p class="subtitle">北京时间 08:00 切日 · Top30 · 今日每4小时自动刷新+手动 · 08:10 企微推送三日交集</p>
|
||||
</header>
|
||||
|
||||
<nav class="main-nav" id="main-nav">
|
||||
@@ -75,15 +75,9 @@
|
||||
<button type="button" class="btn-secondary" id="btn-export-stats">导出 CSV</button>
|
||||
<button type="button" class="btn-secondary" id="btn-push-preview">预览企微推送</button>
|
||||
<button type="button" class="btn-secondary" id="btn-push-test">测试推送企微</button>
|
||||
<button type="button" class="btn-secondary" id="btn-llm-run">开始解读</button>
|
||||
<button type="button" class="btn-secondary" id="btn-llm-refresh">刷新解读</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="stats-desc" id="stats-desc"></p>
|
||||
<p class="stats-desc llm-status-line">
|
||||
大模型 <span class="llm-model" id="llm-model-label">—</span>
|
||||
· <span id="llm-status-text">—</span>
|
||||
</p>
|
||||
<section class="wecom-preview-panel hidden" id="wecom-preview-panel">
|
||||
<h3>企微推送预览 <span class="wecom-preview-meta" id="wecom-preview-meta"></span></h3>
|
||||
<p class="stats-desc">仅包含「三日 Top30 交集」币种;实际发到企微为下方同款列表排版(非宽表格)。</p>
|
||||
|
||||
-116
@@ -357,127 +357,11 @@ button:hover {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.llm-panel {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.llm-model {
|
||||
font-size: 0.75rem;
|
||||
color: var(--accent);
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.llm-list {
|
||||
display: grid;
|
||||
gap: 0.75rem;
|
||||
max-height: 420px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.llm-card {
|
||||
background: #121a26;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
|
||||
.llm-card h4 {
|
||||
margin: 0 0 0.5rem;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.llm-card h4 small {
|
||||
color: var(--muted);
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.llm-text {
|
||||
font-size: 0.88rem;
|
||||
line-height: 1.55;
|
||||
color: var(--text);
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.llm-status-line {
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.stats-table .stats-total-vol {
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.llm-col-head,
|
||||
.llm-col {
|
||||
min-width: 200px;
|
||||
max-width: 360px;
|
||||
font-size: 0.82rem;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.llm-fold {
|
||||
margin-top: 0.15rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
padding: 0.35rem 0.5rem;
|
||||
background: #0f1520;
|
||||
}
|
||||
|
||||
.llm-fold .llm-text {
|
||||
margin-top: 0.5rem;
|
||||
padding-top: 0.5rem;
|
||||
border-top: 1px solid var(--border);
|
||||
font-size: 0.84rem;
|
||||
line-height: 1.55;
|
||||
max-height: 220px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.llm-summary {
|
||||
cursor: pointer;
|
||||
color: var(--accent);
|
||||
font-size: 0.85rem;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.llm-summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.llm-summary.done::before {
|
||||
content: "▸ ";
|
||||
}
|
||||
|
||||
details[open] > .llm-summary.done::before {
|
||||
content: "▾ ";
|
||||
}
|
||||
|
||||
.llm-summary.failed {
|
||||
color: #f6465d;
|
||||
}
|
||||
|
||||
.llm-time {
|
||||
color: var(--muted);
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.llm-placeholder {
|
||||
font-size: 0.82rem;
|
||||
padding: 0.25rem 0;
|
||||
}
|
||||
|
||||
.llm-placeholder.running {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.llm-placeholder.pending {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.llm-err {
|
||||
color: #f6465d;
|
||||
}
|
||||
|
||||
.muted {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user