zcbot/web/static/js/main.js

142 lines
6.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// zcbot dev 控制台入口 + 编排:enterApp(应用初始化)、loadStorage(存储用量)、
// Esc 关弹窗栈、boot。功能逻辑全在各模块(chat/files/preview/media/auth/newtask/embed/layout/…),
// 经各模块顶层 import 拉入依赖图(layout/markdown/media 由 chat 等引入,副作用照常)。
import { state, EMBED, setIdentity, userDisplayName, userDisplayTitle } from "./state.js";
import { humanSize, fmtTime } from "./format.js";
import { $ } from "./dom.js";
import { api } from "./api.js";
import { closeChpwModal } from "./auth.js";
import { closeSkillsModal } from "./skills.js";
import { closeMemoryModal } from "./memory.js";
import { closeKbModal } from "./kb.js";
import { closeCronsModal } from "./crons.js";
import { closeWechatModal } from "./wechat.js";
import { closeChangelogModal, markVersion } from "./changelog.js";
import { closeFilePreview, closeMiniPreview } from "./preview.js";
import { closeSrcPicker, loadFiles } from "./files.js";
import { loadFolderSuggestions } from "./newtask.js";
import { embedInit } from "./embed.js";
import { loadTaskList, loadModels, loadChannelCards, selectTask, showNewConversationDraft } from "./chat.js";
import { refreshProcs } from "./procs.js";
// ───── enter app ─────
export function enterApp(initialTaskId = "") {
$("login").style.display = "none";
$("app").classList.add("ready");
renderWho(); // 顶栏用户:默认显 name(兜底 user_name/email/uid8),hover 显完整身份
if (initialTaskId) {
// platform / App 带 task_id 进入时直接选中目标 taskselectTask 会同步 working_dir
// 不先初始化新对话草稿和 user_root 文件面板,避免右栏短暂或最终停在根目录。
selectTask(initialTaskId);
} else {
showNewConversationDraft(); // 登录空态与“+ 新对话”共用同一草稿页,不先落 tasks 行
loadFiles(); // 文件面板与 task 解耦 — 无初始 task 时启动即拉 user_root
}
loadTaskList();
loadChannelCards(); // 渠道镜像对话(微信 / 企业微信)固定卡片,与列表并行拉
loadModels(); // 模型清单缓存:chat-meta 下拉 + 新建对话框 + 历史小标
loadFolderSuggestions(); // 灌 filter-wd select(modal 打开时会重拉,这里让左 pane 先有选项)
loadStorage(); // 顶栏存储用量(后台扫描快照,非实时)
loadRole(); // 拉 /v1/me,admin 才显「管理」入口(/static/admin.html)
refreshProcs(); // 后台进程(bg proc):上个会话遗留的 running proc 恢复轮询/展示
}
// 顶栏用户名:默认显 name(兜底 user_name → email → uid8),title 悬浮给完整身份。
function renderWho() {
const el = $("hd-who");
if (!el) return;
el.textContent = userDisplayName();
el.title = userDisplayTitle();
}
// 当前用户身份 + 角色:/v1/me 返 {user_id, role, name, user_name, email}。
// admin → 显顶栏「管理」链接;并用服务端权威值校准顶栏用户名(platform_key 登录 / 老 token
// 升级后,name/user_name 这一刻才到齐)。失败静默(增量功能,拉不到就维持登录时的兜底值)。
async function loadRole() {
const link = $("hd-admin");
try {
const me = await api("GET", "/v1/me");
if (me) {
setIdentity({ user_id: me.user_id, name: me.name, user_name: me.user_name, email: me.email });
renderWho();
}
if (link) link.style.display = (me && me.role === "admin") ? "" : "none";
} catch (e) { if (link) link.style.display = "none"; }
}
// 存储用量:拉 /v1/user/storage 渲染文件面板底部进度条。用量来自后台 15min 扫描,
// 故无需高频刷新 —— enterApp 拉一次即可。无配额上限时只显已用、不画进度条(nolimit)。
async function loadStorage() {
let s;
try { s = await api("GET", "/v1/user/storage"); } catch (e) { return; }
const el = $("storage-foot");
const used = s.bytes_used || 0;
const limit = s.limit_bytes;
if (limit && limit > 0) {
const pct = Math.min(100, Math.round(used / limit * 100));
$("storage-foot-bar").style.width = pct + "%";
$("storage-foot-txt").textContent = `${humanSize(used)} / ${humanSize(limit)}`;
el.classList.remove("nolimit");
el.classList.toggle("over", used >= limit);
} else {
// 不限额:只显已用,隐藏进度条
$("storage-foot-txt").textContent = humanSize(used);
el.classList.add("nolimit");
el.classList.remove("over");
}
const when = s.scanned_at ? fmtTime(s.scanned_at) : "尚未统计";
el.title = `已用 ${humanSize(used)} · ${s.file_count || 0} 个文件\n统计于 ${when}(后台每 15 分钟扫描,非实时)`;
el.classList.add("show");
}
// 版本号 + 品牌名:/healthz 是 auth 豁免接口,embed / 未登录都拿得到 → boot 时无条件拉一次。
// 版本号填左栏底部;品牌名(ZCBOT_BRAND_NAME,默认「总院科研辅助助手」)覆盖页面标题 /
// 顶栏 / 登录卡 —— 静态 HTML 里写的是默认品牌,env 改名时这里统一刷。失败静默。
async function loadVersion() {
try {
const r = await fetch("/healthz");
const d = await r.json();
const el = $("app-version");
if (el && d && d.version) el.textContent = "v" + d.version;
if (d && d.version) markVersion(d.version); // 与 localStorage 比对,决定新版本红点
if (d && d.brand) {
document.title = d.brand;
const hd = document.querySelector("header .brand .title");
if (hd) hd.textContent = d.brand;
const li = document.querySelector("#login .brand .name");
if (li) li.textContent = d.brand;
}
} catch (e) {}
}
// ───── Esc 关弹窗栈(跨模块协调:chpw/选入/文件预览/小预览)─────
document.addEventListener("keydown", (e) => {
if (e.key !== "Escape") return;
// 多模态共存:优先关靠前栈顶 — 小预览(z 116)→ 文件预览(z 114,可从 kb 等
// 112 层 modal 内打开,须先于它们关)→ 各 112 层 modal → 选入(z 95)→ 新任务(z 80)
if ($("mini-preview-modal").classList.contains("show")) { closeMiniPreview(); return; }
if ($("file-preview-modal").classList.contains("show")) { closeFilePreview(); return; }
if ($("chpw-modal").classList.contains("show")) { closeChpwModal(); return; }
if ($("skills-modal").classList.contains("show")) { closeSkillsModal(); return; }
if ($("memory-modal").classList.contains("show")) { closeMemoryModal(); return; }
if ($("kb-modal").classList.contains("show")) { closeKbModal(); return; }
if ($("crons-modal").classList.contains("show")) { closeCronsModal(); return; }
if ($("wechat-modal").classList.contains("show")) { closeWechatModal(); return; }
if ($("changelog-modal").classList.contains("show")) { closeChangelogModal(); return; }
if ($("src-picker-modal").classList.contains("show")) { closeSrcPicker(); return; }
});
// ───── boot ─────
loadVersion(); // 与登录态无关,立即拉
if (EMBED) {
embedInit();
} else if (state.token) {
// 已有 token:试探一下,失败回登录页
enterApp();
} else {
$("li-email").focus();
}