async function backfillData() { const button = document.querySelector("#backfillButton"); button.disabled = true; setLoading(true, "正在回补历史交易日"); try { const payload = await apiRequest("/api/backfill", "POST", { start_date: document.querySelector("#backfillStart").value, end_date: document.querySelector("#backfillEnd").value, }); showToast(`历史回补完成,共处理 ${payload.results.length} 个工作日`); state.sentimentHistory = null; state.sentimentHistoryKey = ""; if (state.activeView === "sentimentCycleView") { await loadSentimentHistory(true); } await openAdminSettings(true); } catch (error) { showToast(error.message); } finally { setLoading(false); button.disabled = false; } } async function openAdminSettings(refreshOnly = false) { if (state.user?.role !== "admin") return; if (!refreshOnly) openModalDialog(elements.adminDialog); const status = document.querySelector("#adminConnectionStatus"); status.textContent = "正在读取系统状态"; try { const payload = await apiRequest("/api/admin/settings"); const data = payload.data || {}; const ifind = data.ifind || {}; const llm = payload.llm || {}; const membership = payload.membership || {}; status.textContent = `Tushare ${data.configured ? "已配置" : "未配置"} · iFinD ${ifind.configured ? "已配置" : "未配置"} · ${number(data.snapshot_dates)} 个交易日`; status.classList.toggle("connected", Boolean(data.configured)); setText("systemDataStatus", data.background_refresh_enabled ? "后台刷新已启用" : "后台刷新已暂停"); document.querySelector("#systemTokenInput").value = ""; document.querySelector("#systemIfindTokenInput").value = ""; document.querySelector("#systemBackgroundRefresh").checked = Boolean(data.background_refresh_enabled); document.querySelector("#memberDailyLimit").value = number(membership.member_daily_limit) || 50; renderModelPool(llm.models || [], llm.primary_model_id || "", llm.fallback_model_id || ""); renderAdminUsers(payload.users || []); } catch (error) { status.textContent = error.message || "系统配置读取失败"; } } function selectAdminPanel(panel) { const selected = ["market", "models", "members"].includes(panel) ? panel : "market"; document.querySelector("#adminSectionSelect").value = selected; document.querySelectorAll("[data-admin-panel]").forEach((item) => { item.hidden = item.dataset.adminPanel !== selected; }); } function renderModelPool(models, primaryId = "", fallbackId = "") { state.adminModels = models.map((item) => ({ ...item, api_key: item.api_key || "" })); const container = document.querySelector("#modelPoolList"); container.innerHTML = state.adminModels.map((item, index) => `
${escapeHtml(item.name || `模型 ${index + 1}`)}${item.configured ? "已保存密钥" : "待配置"}
未测试
`).join("") || emptyStateHtml("模型池为空,请先添加模型"); updateModelRoleOptions(primaryId, fallbackId); container.querySelectorAll("[data-test-model]").forEach((button) => button.addEventListener("click", () => testPlatformModel(button.closest("[data-model-id]")))); container.querySelectorAll("[data-delete-model]").forEach((button) => button.addEventListener("click", () => deletePlatformModel(button.closest("[data-model-id]")))); container.querySelectorAll("[data-model-field='name']").forEach((input) => input.addEventListener("input", updateModelRoleLabels)); refreshIcons(); } function collectModelPool() { const saved = new Map(state.adminModels.map((item) => [item.id, item])); return [...document.querySelectorAll("#modelPoolList [data-model-id]")].map((row) => ({ id: row.dataset.modelId, name: row.querySelector("[data-model-field='name']").value.trim(), base_url: row.querySelector("[data-model-field='base_url']").value.trim(), model: row.querySelector("[data-model-field='model']").value.trim(), api_key: row.querySelector("[data-model-field='api_key']").value.trim(), configured: Boolean(saved.get(row.dataset.modelId)?.configured), })); } function updateModelRoleOptions(primaryId = document.querySelector("#platformPrimaryModelSelect").value, fallbackId = document.querySelector("#platformFallbackModelSelect").value) { const models = collectModelPool(); const options = models.map((item) => ``).join(""); const primary = document.querySelector("#platformPrimaryModelSelect"); const fallback = document.querySelector("#platformFallbackModelSelect"); primary.innerHTML = models.length ? options : ''; fallback.innerHTML = `${options}`; primary.value = models.some((item) => item.id === primaryId) ? primaryId : models[0]?.id || ""; fallback.value = models.some((item) => item.id === fallbackId) && fallbackId !== primary.value ? fallbackId : ""; } function updateModelRoleLabels() { updateModelRoleOptions(); } function addPlatformModel() { const models = collectModelPool(); const id = `model-${Date.now()}-${Math.floor(Math.random() * 10000)}`; models.push({ id, name: `模型 ${models.length + 1}`, base_url: "https://api.openai.com/v1", model: "", api_key: "", configured: false }); renderModelPool(models, document.querySelector("#platformPrimaryModelSelect").value || id, document.querySelector("#platformFallbackModelSelect").value); document.querySelector(`[data-model-id="${CSS.escape(id)}"] [data-model-field="name"]`)?.focus(); } function deletePlatformModel(row) { if (!row) return; const id = row.dataset.modelId; const primary = document.querySelector("#platformPrimaryModelSelect").value; const fallback = document.querySelector("#platformFallbackModelSelect").value; if (id === primary || id === fallback) { showToast("请先为主模型或辅助模型选择其他模型,再删除当前模型"); return; } const models = collectModelPool().filter((item) => item.id !== id); renderModelPool(models, primary, fallback); } function renderAdminUsers(users) { const container = document.querySelector("#adminUsersList"); container.innerHTML = users.map((user) => { const admin = user.role === "admin"; const member = Boolean(user.membership_subscribed); const identityLabels = [admin ? "管理员" : "", member ? "会员有效" : "普通用户"].filter(Boolean).join(" · "); const expiry = member ? (user.membership_expires_at ? `有效至 ${membershipDateDisplay(user.membership_expires_at)}` : "永久有效") : user.membership_status === "suspended" ? "会员已停用" : user.membership_status === "active" && user.membership_expires_at ? `已于 ${membershipDateDisplay(user.membership_expires_at)} 到期` : "尚未开通"; return `
${escapeHtml(user.username)}${escapeHtml(identityLabels)}${escapeHtml(expiry)}
今日调用 ${number(user.used_today)}
当前到期${escapeHtml(expiry)}
`; }).join("") || emptyStateHtml("暂无注册用户"); container.querySelectorAll(".membership-form").forEach((form) => form.addEventListener("submit", saveMembership)); } async function saveMembership(event) { event.preventDefault(); const form = event.currentTarget; const data = Object.fromEntries(new FormData(form).entries()); const button = form.querySelector("button[type='submit']"); button.disabled = true; try { const payload = await apiRequest("/api/admin/membership", "POST", data); renderAdminUsers(payload.users || []); showToast("会员状态已更新"); } catch (error) { showToast(error.message || "会员状态保存失败"); } finally { button.disabled = false; } } async function saveMarketSettings(event) { event.preventDefault(); const button = event.currentTarget.querySelector("button[type='submit']"); button.disabled = true; try { await apiRequest("/api/admin/settings", "POST", { tushare_token: document.querySelector("#systemTokenInput").value.trim(), ifind_refresh_token: document.querySelector("#systemIfindTokenInput").value.trim(), background_refresh_enabled: document.querySelector("#systemBackgroundRefresh").checked, }); document.querySelector("#systemTokenInput").value = ""; document.querySelector("#systemIfindTokenInput").value = ""; showToast("行情配置已保存"); await openAdminSettings(true); } catch (error) { showToast(error.message || "系统配置保存失败"); } finally { button.disabled = false; } } async function saveModelPool(event) { event.preventDefault(); const button = event.currentTarget.querySelector("button[type='submit']"); button.disabled = true; try { await apiRequest("/api/admin/settings", "POST", { models: collectModelPool(), primary_model_id: document.querySelector("#platformPrimaryModelSelect").value, fallback_model_id: document.querySelector("#platformFallbackModelSelect").value, }); showToast("模型池已保存"); await openAdminSettings(true); } catch (error) { showToast(error.message || "模型池保存失败"); } finally { button.disabled = false; } } async function saveMembershipSettings(event) { event.preventDefault(); const button = event.currentTarget.querySelector("button[type='submit']"); button.disabled = true; try { await apiRequest("/api/admin/settings", "POST", { member_daily_limit: number(document.querySelector("#memberDailyLimit").value), }); showToast("会员调用额度已保存"); await openAdminSettings(true); } catch (error) { showToast(error.message || "会员调用额度保存失败"); } finally { button.disabled = false; } } async function testPlatformModel(row) { if (!row) return; const button = row.querySelector("[data-test-model]"); const status = row.querySelector(".model-test-status"); const profile = collectModelPool().find((item) => item.id === row.dataset.modelId) || {}; button.disabled = true; status.textContent = "连接中"; try { const payload = await apiRequest("/api/admin/settings/test", "POST", { model_id: row.dataset.modelId, profile }); status.textContent = `已连通 · ${number(payload.result.latency_ms)} ms`; status.className = "model-test-status success"; } catch (error) { status.textContent = error.message; status.className = "model-test-status failure"; } finally { button.disabled = false; } } function bindAdminEvents() { document.querySelector("#settingsButton").addEventListener("click", () => openAdminSettings()); document.querySelector("#closeAdminDialog").addEventListener("click", () => elements.adminDialog.close()); document.querySelector("#backfillButton").addEventListener("click", backfillData); document.querySelector("#adminSectionSelect").addEventListener("change", (event) => selectAdminPanel(event.target.value)); document.querySelector("#systemMarketForm").addEventListener("submit", saveMarketSettings); document.querySelector("#systemModelsForm").addEventListener("submit", saveModelPool); document.querySelector("#membershipSettingsForm").addEventListener("submit", saveMembershipSettings); document.querySelector("#addPlatformModel").addEventListener("click", addPlatformModel); document.querySelector("#adminRefreshButton").addEventListener("click", startAdminRefresh); }