修改前端漏斗

This commit is contained in:
dekun
2026-05-18 11:05:32 +08:00
parent 82a7237063
commit 1d38b2c574
4 changed files with 123 additions and 8 deletions
+61 -6
View File
@@ -77,6 +77,53 @@ function formatIsoToBeijing(iso) {
return s.replace("T", " ");
}
const FUNNEL_WINDOW_LS_KEY = "funnel_display_hours";
const FUNNEL_WINDOW_DEFAULT = 24;
const FUNNEL_WINDOW_MIN = 1;
const FUNNEL_WINDOW_MAX = 168;
function getFunnelWindowHours() {
try {
const n = Number(localStorage.getItem(FUNNEL_WINDOW_LS_KEY));
if (Number.isFinite(n) && n >= FUNNEL_WINDOW_MIN && n <= FUNNEL_WINDOW_MAX) {
return Math.round(n);
}
} catch (_) {
/* ignore */
}
return FUNNEL_WINDOW_DEFAULT;
}
function setFunnelWindowHours(raw) {
const n = Math.round(Number(raw));
const h =
Number.isFinite(n) && n >= FUNNEL_WINDOW_MIN && n <= FUNNEL_WINDOW_MAX
? n
: FUNNEL_WINDOW_DEFAULT;
try {
localStorage.setItem(FUNNEL_WINDOW_LS_KEY, String(h));
} catch (_) {
/* ignore */
}
const inp = document.getElementById("funnelWindowHoursInput");
if (inp) inp.value = String(h);
return h;
}
function initFunnelWindowControls() {
const inp = document.getElementById("funnelWindowHoursInput");
if (inp) inp.value = String(getFunnelWindowHours());
const btn = document.getElementById("applyFunnelWindowBtn");
if (btn) {
btn.addEventListener("click", () => {
const h = setFunnelWindowHours(getInputNumber("funnelWindowHoursInput"));
const msg = document.getElementById("funnelWindowMsg");
if (msg) msg.textContent = `// 展示窗口已设为最近 ${h} 小时(本浏览器记忆)`;
refresh().catch(console.error);
});
}
}
function tickClock() {
const el = document.getElementById("liveClock");
if (!el) return;
@@ -132,8 +179,9 @@ function renderFunnel(items, funnelCtx) {
if (!items.length) {
const empty = document.createElement("div");
empty.className = "matrix-hint matrix-hint-empty";
const winH = Number(ctx.windowHours) || FUNNEL_WINDOW_DEFAULT;
let why =
"// 暂无漏斗记录:本面板只展示 <code>source=gemma_funnel</code> 的排序结果(需配置开启且 Ollama 跑完一轮后写入告警表)。";
`// 暂无漏斗记录:本面板只展示最近 <code>${winH}h</code> 内 <code>source=gemma_funnel</code> 的排序结果(需配置开启且 Ollama 跑完一轮后写入告警表)。`;
if (!gemmaOn) {
why += " 当前 <code>gemma.enabled=false</code>,漏斗未运行。";
} else if (cycleMsg === "funnel_pending") {
@@ -162,8 +210,10 @@ function renderFunnel(items, funnelCtx) {
const card = document.createElement("article");
card.className = "matrix-card" + (pushed ? " hot" : "");
const vol = (d.programmatic && d.programmatic.est_quote_vol_24h_usdt) || "—";
const updatedCn = formatIsoToBeijing(a.created_at);
card.innerHTML = `
<div class="matrix-card-title">${a.symbol}</div>
<div class="matrix-card-title">${escapeHtml(a.symbol || "—")}</div>
<div class="matrix-card-meta time">更新 ${escapeHtml(updatedCn)}(北京时间)</div>
<div class="matrix-card-meta">
COMPOSITE <strong>${comp.toFixed(1)}</strong> · P${g.priority || "?"} ·
结构 ${g.daily_structure || "?"} · 量 ${g.volume_view || "?"} ·
@@ -324,12 +374,13 @@ async function saveDailyReportSettings() {
async function refresh() {
try {
const funnelWindowH = getFunnelWindowHours();
const [status, alerts, logs, config, funnel, dailyReport] = await Promise.all([
fetchJson("/api/status"),
fetchJson("/api/alerts"),
fetchJson("/api/logs"),
fetchJson("/api/config"),
fetchJson("/api/funnel"),
fetchJson(`/api/funnel?window_hours=${encodeURIComponent(funnelWindowH)}`),
fetchJson("/api/daily-report"),
]);
updateHud(status);
@@ -340,10 +391,13 @@ async function refresh() {
if (cf) cf.textContent = pretty(config);
const runState = (status && status.state) || {};
const funnelWindowApplied =
funnel && funnel.window_hours != null ? Number(funnel.window_hours) : funnelWindowH;
renderFunnel(funnel.items || [], {
gemmaEnabled: !!(config.gemma && config.gemma.enabled),
cycleMsg: runState.gemma_cycle_msg || "",
lastFunnelAt: runState.last_funnel_at || "",
windowHours: funnelWindowApplied,
});
renderDailyReport(dailyReport);
try {
@@ -370,12 +424,12 @@ async function refresh() {
if (fm) {
let line =
`// 浏览器刚拉完 API${pullCn} HUD 的 LAST:上一轮 Gate 扫描整轮结束(可与本行差约 ${poll}s)| ` +
`矩阵卡片 ${fc} 条:来自告警库「每币最新一条 记忆体 last_funnel 更新:${lfAt} 后轮 gemma${gmsg}`;
`矩阵卡片 ${fc} 条:最近 ${funnelWindowApplied}h 内 gemma_funnel每币最新一条 记忆体 last_funnel 更新:${lfAt} 后轮 gemma${gmsg}`;
if (String(gmsg).includes("funnel_ranked=0") && fc > 0) {
line +=
" | 说明:本轮后台漏斗未写入新排名(常见:4h 内同一币已跑过 FUNNEL-GEMMA 被跳过、或候选在取日线/Ollama 前被滤掉),卡片仍是历史结果,不是前端卡死。";
` | 说明:本轮后台漏斗未写入新排名(常见:4h 内同一币已跑过 FUNNEL-GEMMA 被跳过),仍显示 ${funnelWindowApplied}h 内已有卡片。`;
} else {
line += " | 若文案长期不变=近期没有新的 gemma_funnel 入库。";
line += ` 超过 ${funnelWindowApplied}h 的漏斗记录不再显示(库内仍在)。`;
}
fm.textContent = line;
}
@@ -613,6 +667,7 @@ loadOrderExecutors().catch(console.error);
tickClock();
setInterval(tickClock, 1000);
initMatrixRain();
initFunnelWindowControls();
refresh();
setInterval(refresh, 4000);
document.addEventListener("visibilitychange", () => {