refactor: establish standalone application boundary

This commit is contained in:
leefer
2026-08-03 21:42:25 +08:00
parent cc5fb8d73e
commit e1e76cd51e
324 changed files with 63090 additions and 44743 deletions
+256
View File
@@ -0,0 +1,256 @@
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} 个工作日`);
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) => `
<article class="model-pool-row" data-model-id="${escapeHtml(item.id)}">
<div class="model-pool-heading"><strong>${escapeHtml(item.name || `模型 ${index + 1}`)}</strong><span>${item.configured ? "已保存密钥" : "待配置"}</span></div>
<div class="model-pool-fields">
<label class="form-field"><span>显示名称 *</span><input data-model-field="name" maxlength="50" value="${escapeHtml(item.name || "")}" required></label>
<label class="form-field"><span>API Base URL *</span><input data-model-field="base_url" type="url" value="${escapeHtml(item.base_url || "https://api.openai.com/v1")}" required></label>
<label class="form-field"><span>模型标识 *</span><input data-model-field="model" maxlength="100" value="${escapeHtml(item.model || "")}" required></label>
<label class="form-field"><span>API Key${item.configured ? "" : " *"}</span><input data-model-field="api_key" type="password" autocomplete="off" maxlength="300" placeholder="${item.configured ? "留空保留已保存的 Key" : "输入 API Key"}" ${item.configured ? "" : "required"}></label>
</div>
<div class="model-test-row"><button class="button" type="button" data-test-model>测试连接</button><span class="model-test-status" aria-live="polite">未测试</span><button class="icon-button model-delete-button" type="button" data-delete-model aria-label="删除模型" title="删除模型"><i data-lucide="trash-2"></i></button></div>
</article>
`).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) => `<option value="${escapeHtml(item.id)}">${escapeHtml(item.name || item.model || "未命名模型")}</option>`).join("");
const primary = document.querySelector("#platformPrimaryModelSelect");
const fallback = document.querySelector("#platformFallbackModelSelect");
primary.innerHTML = models.length ? options : '<option value="">暂无模型</option>';
fallback.innerHTML = `<option value="">不启用辅助模型</option>${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 `<article class="admin-user-row" data-admin-user="${number(user.id)}">
<div class="admin-user-identity"><strong>${escapeHtml(user.username)}</strong><span>${escapeHtml(identityLabels)}</span><small>${escapeHtml(expiry)}</small></div>
<div class="admin-user-usage">今日调用 <b>${number(user.used_today)}</b></div>
<form class="membership-form">
<input type="hidden" name="user_id" value="${number(user.id)}">
<label><span>状态</span><select name="status"><option value="inactive" ${user.membership_status === "inactive" ? "selected" : ""}>未开通</option><option value="active" ${user.membership_status === "active" ? "selected" : ""}>有效</option><option value="suspended" ${user.membership_status === "suspended" ? "selected" : ""}>停用</option></select></label>
<label><span>开通 / 续期时长</span><select name="duration"><option value="">选择时长</option><option value="1_month">1个月</option><option value="3_months">3个月</option><option value="12_months">12个月</option><option value="3_years">3年</option><option value="permanent">永久</option></select></label>
<div class="membership-expiry"><span>当前到期</span><strong>${escapeHtml(expiry)}</strong></div>
<button class="button" type="submit">应用</button>
</form>
</article>`;
}).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);
}