/* ========================================================================== fmode 移动端 UI Demo · 路由 / 渲染 / 交互 形态:全屏移动端 App(无手机壳);参照 WorkBuddy 的版式语言,配色走项目令牌 交互范围:浏览态全部可点;写操作(发送/新建/删除/上传)只弹提示 —— 静态数据演示 ========================================================================== */ (function () { 'use strict'; const D = window.DEMO; const $ = (sel, root) => (root || document).querySelector(sel); const esc = s => String(s == null ? '' : s).replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c])); const icon = (name, cls) => ``; const initial = name => (name || '?').trim().charAt(0).toUpperCase(); /* ---------------- 全局状态 ---------------- */ const store = { get(key, fallback) { try { const v = localStorage.getItem(key); return v == null ? fallback : v; } catch (e) { return fallback; } }, // 每次读取都以存储为准:登录态可能在别的标签页/脚本里被改过(演示版无跨标签同步) getFresh(key, fallback) { return this.get(key, fallback); }, set(key, value) { try { localStorage.setItem(key, value); } catch (e) { /* 隐私模式忽略 */ } }, }; const state = { palette: store.get('fmode_palette', 'platinum'), dark: store.get('fmode_theme', 'light') === 'dark', fsBody: Number(store.get('fmode_fs', 15)), filters: { messages: 'all', contacts: 'all', tasks: 'all', deliverables: 'project' }, expanded: { people: {}, tree: { workspace: true, daily: true, archived: false } }, search: { messages: '' }, authed: store.get('fmode_demo_auth', '') === '1', login: { stage: 'phone', phone: '', code: '', agreed: false, token: '' }, }; const VALID_PALETTES = ['platinum', 'ice', 'clay', 'slate']; if (VALID_PALETTES.indexOf(state.palette) < 0) state.palette = 'platinum'; /* ---------------- 主题 ---------------- */ const FS_STEPS = [11, 12, 13, 14, 15, 16, 20, 26]; // 字样板(滑块档位) function applyTheme() { const html = document.documentElement; html.dataset.palette = state.palette; html.classList.toggle('dark', state.dark); html.style.setProperty('--fs-body', state.fsBody + 'px'); const canvas = getComputedStyle(html).getPropertyValue('--canvas').trim(); const meta = $('meta[name="theme-color"]'); if (meta && canvas) meta.setAttribute('content', canvas); } function setPalette(p) { state.palette = p; store.set('fmode_palette', p); applyTheme(); render(); } function setDark(on) { state.dark = !!on; store.set('fmode_theme', on ? 'dark' : 'light'); applyTheme(); render(); } function setFs(v) { const n = Number(v); state.fsBody = FS_STEPS.reduce((a, b) => (Math.abs(b - n) < Math.abs(a - n) ? b : a), FS_STEPS[0]); store.set('fmode_fs', state.fsBody); applyTheme(); render(); } /* ---------------- 登录(演示版:只校验格式,不发起请求) ---------------- */ function loginNext(step) { // 登录分两步:手机号 → 验证码;格式不合法就停在当前步 state.login.stage = step; render(); } function doLogin() { const { agreed } = state.login; if (!agreed) return toast('请先阅读并同意服务协议和隐私政策'); store.set('fmode_demo_auth', '1'); state.authed = true; toast('演示版:已进入体验账号'); go('/messages'); } function logout() { store.set('fmode_demo_auth', ''); state.authed = false; closeAllLayers(); go('/login'); } /* ---------------- 路由 ---------------- */ // 路由模式:#/messages、#/chat/s-1、#/agent/a-nova、#/tasks/t-1 …… function parseHash() { const raw = (location.hash || '#/messages').replace(/^#\/?/, ''); const [path, query] = raw.split('?'); const parts = path.split('/').filter(Boolean); return { page: parts[0] || 'messages', id: parts[1] || null, query: query || '' }; } function go(path) { if (location.hash === '#' + path) { render(); return; } location.hash = path; } const TAB_PAGES = ['messages', 'contacts', 'workbench', 'mine']; /* ---------------- 通用片段 ---------------- */ const agentAvatar = (a, cls) => `${esc(initial(a.name))}`; const personAvatar = (p, cls) => `${esc(initial(p.name))}`; const statusBadge = a => `${D.STATUS_TEXT[a.status] || a.status}`; const taskBadge = t => `${D.TASK_STATUS_TEXT[t.status] || t.status}`; function navbar({ title, sub, back, right, onTab }) { return ``; } function empty(text) { return `
${esc(text)}
`; } /* ---------------- 页面渲染 ---------------- */ const PAGES = { /* 03 新建对话引导(落地态) */ home() { const recent = D.sessions.filter(s => s.group !== 'archived').slice(0, 3); return `
${navbar({ title: '', right: ``, onTab: true })}

fmode

人机共生中枢

和数字生命一起干活 · 每条会话都可追踪

最近会话
${recent.map(s => { const a = D.agentById(s.agentId); return ``; }).join('')}
${icon('paperclip', 'icon-sm')}
`; }, /* 05 会话列表 */ messages() { const q = state.search.messages.trim().toLowerCase(); const f = state.filters.messages; const list = D.sessions.filter(s => { if (f === 'unread' && !s.unread) return false; if (f === 'workspace' && s.group !== 'workspace') return false; if (q && !(s.title.toLowerCase().includes(q) || D.agentById(s.agentId).name.toLowerCase().includes(q))) return false; return true; }); return `
${navbar({ title: '会话', onTab: true, right: `` })}
${[['all', '全部'], ['unread', '未读'], ['workspace', '工作区']].map(([k, t]) => ``).join('')}
${list.length ? `
${list.map(s => { const a = D.agentById(s.agentId); return ``; }).join('')}
` : empty('没有匹配的会话')}
`; }, /* 02 登录 */ login() { const L = state.login; const phoneOk = /^1\d{10}$/.test(L.phone); return `

fmode

欢迎使用

人机共生中枢 · 登录后继续

${L.stage === 'phone' ? `
使用 Token 直接登录
` : L.stage === 'code' ? `
返回修改手机号
` : `
返回手机号登录
`}

演示版:不会真的发送短信或请求接口

`; }, /* 06 会话详情 */ chat(id) { const s = D.sessionById(id) || D.sessions[0]; const a = D.agentById(s.agentId); const msgs = D.messages[s.id] || []; return `
${navbar({ title: s.title, sub: `${a.name} · ${D.STATUS_TEXT[a.status]}`, back: true, right: ``, })}
${msgs.length ? msgs.map(m => m.sender === 'me' ? msgMe(m) : msgAgent(m)).join('') : empty('这个会话还没有消息')}
${icon('mic', 'icon-sm')}
`; }, /* 09 通讯录 */ contacts() { const f = state.filters.contacts; const agents = f === 'people' ? [] : D.agents; const people = f === 'agents' ? [] : D.people; return `
${navbar({ title: '通讯录', onTab: true })}
${[['all', '全部'], ['agents', '数字生命'], ['people', '同事']].map(([k, t]) => ``).join('')}
${agents.length ? `
数字生命 · ${agents.length}
${agents.map(a => ``).join('')}
` : ''} ${people.length ? `
同事 · ${people.length}
${people.map(p => { const open = state.expanded.people[p.id]; return ` ${open ? p.agents.map(id => { const a = D.agentById(id); return ``; }).join('') : ''}`; }).join('')}
` : ''}
`; }, /* 10 数字生命详情 */ agent(id) { const a = D.agentById(id) || D.agents[0]; const hist = D.deliverables.filter(d => d.agentId === a.id); return `
${navbar({ title: '数字生命', back: true, right: `` })}
${agentAvatar(a, 'lg')}
${esc(a.name)}
${esc(a.specialty)}
${statusBadge(a)}${a.sessionStatus !== 'ok' ? `状态未上报` : ''}
运行指标心跳 ${esc(a.heartbeatAt)}
CPU${a.host.cpuPercent}%
内存${a.host.memPercent}%
本会话 Token${a.usage} 万
余额¥${a.balance.remainCny}
基本信息
当前任务
${esc(a.currentTask)}
会话数
${a.usageSessions}
通知通道
${a.channels.length ? a.channels.map(c => `${esc(c)}`).join(' ') : '未配置'}
Matrix
${a.matrixReady ? '已连接' : '未连接'}
权限设置
允许群内任何人指挥关闭后仅创建者可下达指令
对外共享交付成果开启后同项目成员可下载
最近交付
${hist.length ? `
${hist.map(d => ``).join('')}
` : empty('暂无交付')}
`; }, /* 11 项目(列表 / 详情同页) */ projects(id) { if (!id) { return `
${navbar({ title: '项目', onTab: false, back: true })}
${D.projects.map(p => ``).join('')}
`; } const p = D.projectById(id) || D.projects[0]; const sess = p.sessionIds.map(D.sessionById); const dels = p.deliverableIds.map(D.deliverableById); const tasks = D.tasks.filter(t => t.projectId === p.id); return `
${navbar({ title: p.name, back: true, right: '' })}
${esc(p.name)}${p.hasBlocked ? '有阻塞' : `${esc(p.status)}`}

${esc(p.description)}

编号
${esc(p.code)}
负责人
${esc(p.owner)}
截止
${esc(p.due)}
数字生命
${p.agentIds.map(i => esc(D.agentById(i).name)).join('、')}
关联任务 · ${tasks.length}
${tasks.map(t => taskCard(t)).join('') || empty('暂无任务')}
关联会话
${sess.map(s => ``).join('')}
交付成果 · ${dels.length}
${dels.length ? `
${dels.map(d => ``).join('')}
` : empty('暂无交付')}
`; }, /* 12 任务看板 / 任务详情 */ tasks(id) { if (!id) { const f = state.filters.tasks; const order = ['triage', 'todo', 'scheduled', 'ready', 'running', 'blocked', 'done']; const counts = order.map(k => D.tasks.filter(t => t.status === k).length); const list = f === 'all' ? D.tasks : D.tasks.filter(t => t.status === f); return `
${navbar({ title: '任务', back: true })}
${order.map((k, i) => ``).join('')}
${list.map(t => taskCard(t)).join('') || empty('该状态下暂无任务')}
`; } const t = D.taskById(id) || D.tasks[0]; const a = D.agentById(t.agentId); return `
${navbar({ title: '任务详情', back: true })}
${esc(t.title)}${taskBadge(t)}
${esc(a.name)}·${esc(D.projectById(t.projectId).name)}·更新于 ${esc(t.updatedAt)} ${t.status === 'blocked' ? `已阻塞 ${t.blockedHours} 小时` : ''}
${t.status === 'blocked' ? `
${icon('alert', 'icon-sm')}阻塞原因:${esc(t.blockKind)}
` : ''}

${esc(t.body)}

运行流水
${t.runs.length ? `
${t.runs.map(r => `
${esc(r.text)}
`).join('')}
` : empty('暂无运行记录')}
附件 · ${t.attachments.length}
${t.attachments.length ? `
${t.attachments.map(f => ``).join('')}
` : empty('暂无附件')}
评论 · ${t.comments.length}
${t.comments.length ? `
${t.comments.map(c => `
${personAvatar({ name: c.who }, 'sm')}${esc(c.who)} ${esc(c.at)}${esc(c.text)}
`).join('')}
` : empty('暂无评论')}
`; }, /* 13 成果 */ deliverables() { const f = state.filters.deliverables; const groups = f === 'project' ? D.projects.map(p => ({ title: p.name, items: D.deliverables.filter(d => d.projectId === p.id) })).filter(g => g.items.length) : [{ title: '按时间线', items: D.deliverables.slice().sort((x, y) => x.deliveredAt < y.deliveredAt ? 1 : -1) }]; return `
${navbar({ title: '成果', back: true })}
${[['project', '按项目'], ['timeline', '按时间线']].map(([k, t]) => ``).join('')}
${groups.map(g => `
${esc(g.title)} · ${g.items.length}
${g.items.map(d => ``).join('')}
`).join('')}
`; }, /* 14 事件 */ events() { const days = []; D.events.forEach(e => { const g = days.find(d => d.day === e.day); (g ? g.items : (days.push({ day: e.day, items: [] }), days[days.length - 1].items)).push(e); }); const tone = { delivery: 'success', execution: 'info', alert: 'danger' }; const label = { delivery: '交付', execution: '执行', alert: '告警' }; return `
${navbar({ title: '事件', back: true })}
${days.map(d => `
${esc(d.day)}${d.items.length} 条
${d.items.map(e => ``).join('')}
`).join('')}
`; }, /* 15 工作台 */ workbench() { return `
${navbar({ title: '工作台', onTab: true, right: `` })}
${D.agents.map(a => `
${agentAvatar(a)}
${esc(a.name)}
${esc(a.specialty)}
${statusBadge(a)}
${esc(a.currentTask)}
CPU${a.host.cpuPercent}%
内存${a.host.memPercent}%
`).join('')}
`; }, /* 16 我的 */ mine() { const palettes = [ ['platinum', '铂金曜黑'], ['ice', '冰白靛蓝'], ['clay', '暖瓷陶橘'], ['slate', '岩灰鸢紫'], ]; return `
${navbar({ title: '我的', onTab: true })}
${esc(initial(D.me.name))}
${esc(D.me.name)}
${esc(D.me.mobile)} · ${esc(D.me.companyName)}
账号
外观
整体配色
${palettes.map(([k, t]) => ``).join('')}
深色模式跟随手动切换
正文字号${state.fsBody}px
`; }, }; function msgMe(m) { return `
${esc(m.text)}
`; } function msgAgent(m) { const procs = m.processes || []; return `
${procs.every(p => p.state === 'done') ? '已完成' : '执行中'}
共 ${procs.length} 段过程 ${procs.length ? '收起' : ''}
${procs.map(p => `
${esc(p.label)}${p.state === 'done' ? `已完成 ${icon('chevron-right', 'icon-sm')}` : '执行中'}
`).join('')}
${(m.markdown || []).map(b => block(b)).join('')}
${(m.files || []).map(f => ``).join('')}
内容由 AI 生成${icon('volume', 'icon-sm')}${icon('copy', 'icon-sm')}${icon('share', 'icon-sm')}
`; } function block(b) { if (b.type === 'p') return `

${esc(b.text)}

`; if (b.type === 'table') return `${b.rows.map(r => `${r.map((c, i) => i === 0 ? `` : ``).join('')}`).join('')}
${esc(c)}${esc(c)}
`; return ''; } function taskCard(t) { const a = D.agentById(t.agentId); return ``; } /* ---------------- 渲染入口 ---------------- */ function render() { applyTheme(); state.authed = store.get('fmode_demo_auth', '') === '1'; let r = parseHash(); // 权限守卫:未登录一律渲染登录页;已登录时访问登录页则直接渲染会话 // 说明:守卫只决定「渲染什么」,不修改 hash —— 改 hash 会触发新一轮 hashchange, // 在 file:// 下(history 顺序不同步)与本次渲染互相覆盖,导致停在错误页面 if (!state.authed && r.page !== 'login') r = { page: 'login', id: null }; else if (state.authed && r.page === 'login') r = { page: 'messages', id: null }; const fn = PAGES[r.page] || PAGES.home; $('#views').innerHTML = `
${fn(r.id)}
`; renderTabbar(r.page); if (window.scrollY) window.scrollTo(0, 0); } function renderTabbar(current) { const tabs = [ ['messages', 'message', '会话'], ['contacts', 'users', '通讯录'], ['workbench', 'grid', '工作台'], ['mine', 'user', '我的'], ]; $('#tabbar').style.display = TAB_PAGES.includes(current) ? 'flex' : 'none'; const unread = D.sessions.reduce((n, s) => n + (s.group === 'archived' ? 0 : s.unread), 0); $('#tabbar').innerHTML = tabs.map(([k, ic, label]) => ``).join(''); } /* ---------------- 浮层:抽屉 / 底部弹层 / 预览 / toast ---------------- */ const layerRoot = $('#layers'); function layerShell(inner, cls, onClose) { const wrap = document.createElement('div'); wrap.style.cssText = 'position:absolute;inset:0;pointer-events:none;z-index:var(--z-modal)'; wrap.innerHTML = `
${inner}`; const mask = wrap.querySelector('.sheet-mask'); mask.onclick = () => { closeLayer(wrap); onClose && onClose(); }; wrap.querySelectorAll('.drawer,.sheet,.sheet-body-wrap').forEach(n => (n.style.pointerEvents = 'auto')); layerRoot.appendChild(wrap); requestAnimationFrame(() => { mask.classList.add('show'); wrap.querySelector('.drawer,.sheet')?.classList.add('show'); }); return wrap; } function closeLayer(wrap) { const mask = wrap.querySelector('.sheet-mask'); const panel = wrap.querySelector('.drawer,.sheet'); mask.classList.remove('show'); if (panel) panel.classList.remove('show'); setTimeout(() => wrap.remove(), 260); } function toast(msg) { const t = document.createElement('div'); t.className = 'toast'; t.textContent = msg; $('#app').appendChild(t); setTimeout(() => t.remove(), 1800); } /* 04 抽屉:新建对话 + 一级入口 + 会话树(工作区 / 日常对话 / 已归档) */ function treeHtml(sessions) { return sessions.map(s => ``).join('') || `

暂无会话

`; } function openDrawer() { const groups = [ ['工作区', 'workspace'], ['日常对话', 'daily'], ['已归档', 'archived'], ]; const tree = groups.map(([label, key]) => { const items = D.sessions.filter(s => s.group === key); const open = !!state.expanded.tree[key]; return `
${treeHtml(items)}
`; }).join(''); return layerShell(` `); } /* 分组折叠:只切换 aria-expanded + hidden,不重建抽屉(避免整块重绘导致的跳动) */ function toggleTree(btn) { const key = btn.dataset.key; const open = !state.expanded.tree[key]; state.expanded.tree[key] = open; btn.setAttribute('aria-expanded', String(open)); const panel = document.getElementById('tree-' + key); if (panel) panel.hidden = !open; } /* 新建对话:底部弹层选择数字生命(对标桌面「选择容器新建对话」) */ function openNewChat() { closeAllLayers(); const usable = D.agents.filter(a => a.status === 'running' || a.status === 'idle'); const draft = D.sessions.filter(s => s.group === 'daily').slice(0, 3); return layerShell(` `); } /* 07 会话内成果抽屉:任务 / 文件 / 成果 / 状态 */ function openChatSheet(sessionId) { const s = D.sessionById(sessionId) || D.sessions[0]; const a = D.agentById(s.agentId); const tasks = D.tasks.filter(t => t.agentId === a.id); const dels = D.deliverables.filter(d => d.agentId === a.id); const tab = (k, t) => ``; const wrap = layerShell(` `); const body = wrap.querySelector('[data-sheet-body]'); const panels = { task: () => tasks.length ? `
${tasks.map(t => ``).join('')}
` : empty('暂无任务'), file: () => (D.messages[s.id] || []).some(m => m.files) ? `
${(D.messages[s.id] || []).flatMap(m => m.files || []).map(f => ``).join('')}
` : empty('暂无文件'), deliver: () => dels.length ? `
${dels.map(d => ``).join('')}
` : empty('暂无成果'), state: () => `
状态
${D.STATUS_TEXT[a.status]}
当前任务
${esc(a.currentTask)}
心跳
${esc(a.heartbeatAt)}
Matrix
${a.matrixReady ? '已连接' : '未连接'}
Token
${a.usage} 万
`, }; const setTab = k => { body.innerHTML = panels[k](); wrap.querySelectorAll('[data-tab]').forEach(b => b.classList.toggle('on', b.dataset.tab === k)); }; wrap.querySelectorAll('[data-tab]').forEach(b => (b.onclick = () => setTab(b.dataset.tab))); setTab('task'); return wrap; } /* 通道 bottom-sheet */ function openChannels(agentId) { const a = D.agentById(agentId) || D.agents[0]; return layerShell(` `); } /* 08 文件预览层 */ function openPreview(deliverableId) { const d = D.deliverableById(deliverableId) || D.deliverables[0]; const a = d.agentId ? D.agentById(d.agentId) : null; // 兼容消息里的临时文件对象(只有文件名/元信息) const filename = d.filename || d.name; const type = (d.fileType || filename.split('.').pop() || '').toLowerCase(); const sub = [d.title || d.name, d.version, d.size, a ? a.name : ''].filter(Boolean).map(esc).join(' · '); const wrap = document.createElement('div'); wrap.innerHTML = ``; layerRoot.appendChild(wrap.firstElementChild); } /* ---------------- 事件委托 ---------------- */ document.addEventListener('click', ev => { const el = ev.target.closest('[data-act]'); if (!el) return; const act = el.dataset.act; switch (act) { case 'tab': go('/' + el.dataset.to); break; case 'go': go(el.dataset.to); closeAllLayers(); break; case 'back': if (history.length > 1) history.back(); else go('/' + (TAB_PAGES.includes(parseHash().page) ? 'messages' : DEFAULT_TAB[parseHash().page] || 'messages')); break; case 'drawer': openDrawer(); break; case 'tree-toggle': toggleTree(el); break; case 'new-chat': openNewChat(); break; case 'expand-person': { const id = el.dataset.id; state.expanded.people[id] = !state.expanded.people[id]; render(); break; } // 与 core/agent-route.service 一致:可用(running/idle)且有活跃会话 → 进会话;否则进详情 case 'agent-route': { const a = D.agentById(el.dataset.id); const s = D.sessions.find(x => x.agentId === a.id && x.group !== 'archived'); const usable = a.status === 'running' || a.status === 'idle'; closeAllLayers(); go(usable && s ? `/chat/${s.id}` : `/agent/${a.id}`); break; } case 'filter': state.filters[el.dataset.scope] = el.dataset.key; render(); break; case 'sheet-chat': { const r = parseHash(); closeAllLayers(); openChatSheet(r.page === 'chat' ? r.id : null); break; } case 'sheet-channels': closeAllLayers(); openChannels(el.dataset.id); break; case 'preview': openPreview(el.dataset.id); break; case 'preview-dummy': openPreview({ filename: el.dataset.name, title: el.dataset.name, version: el.dataset.meta, size: '', agentId: 'a-orbit' }); break; case 'palette': setPalette(el.dataset.key); break; case 'toggle-dark': setDark(!state.dark); closeAllLayers(); break; case 'switch-static': toastStatic('演示版:设置项不保存'); break; case 'toast-static': toastStatic(); break; case 'login-next': loginNext(el.dataset.step); break; case 'login-submit': doLogin(); break; case 'login-agree': state.login.agreed = !state.login.agreed; render(); break; case 'send-static': { const dock = el.closest('.dock'); const ta = dock && dock.querySelector('textarea'); if (ta && ta.value.trim()) toast('演示版:数据为静态示例,不会真的发送'); else ta && ta.focus(); break; } case 'logout': confirmLogout(); break; case 'logout-confirm': logout(); break; case 'close': { const preview = el.closest('.preview'); if (preview) { preview.remove(); break; } const wrap = el.closest('#layers > div'); if (wrap) { closeLayer(wrap); break; } // 没有关闭按钮的浮层(退出确认):点取消直接移除 if (el.closest('.sheet-body-wrap')) closeAllLayers(); break; } } }); // input 事件:搜索与输入坞 document.addEventListener('input', ev => { const el = ev.target; if (el.dataset.act === 'search-messages') { state.search.messages = el.value; const pos = el.selectionStart; render(); const next = $('[data-act="search-messages"]'); if (next) { next.focus(); next.setSelectionRange(pos, pos); } } if (el.dataset.act === 'login-phone') { state.login.phone = el.value.replace(/\D/g, '').slice(0, 11); const pos = state.login.phone.length; render(); const next = $('[data-act="login-phone"]'); if (next) { next.focus(); next.setSelectionRange(pos, pos); } return; } if (el.dataset.act === 'login-code') { state.login.code = el.value.replace(/\D/g, '').slice(0, 6); const pos = state.login.code.length; render(); const next = $('[data-act="login-code"]'); if (next) { next.focus(); next.setSelectionRange(pos, pos); } return; } if (el.dataset.act === 'login-token') { state.login.token = el.value; const btn = $('[data-act="login-submit"]'); if (btn) btn.disabled = !el.value.trim(); return; } if (el.dataset.act === 'dock-input') { const send = el.closest('.dock').querySelector('.send'); send.classList.toggle('ready', !!el.value.trim()); el.style.height = 'auto'; el.style.height = Math.min(el.scrollHeight, 120) + 'px'; } }); function confirmLogout() { const wrap = layerShell(`

退出登录

退出后需要重新登录才能继续使用。演示版不会真的退出。

`); wrap.querySelectorAll('.sheet').forEach(s => s.classList.add('show')); } function toastStatic(msg) { toast(msg || '演示版:数据为静态示例'); } function closeAllLayers() { layerRoot.querySelectorAll(':scope > div').forEach(w => closeLayer(w)); layerRoot.querySelectorAll(':scope > .preview').forEach(p => p.remove()); } const DEFAULT_TAB = { chat: 'messages', agent: 'contacts', tasks: 'workbench', projects: 'messages', events: 'messages', deliverables: 'workbench' }; /* ---------------- 启动 ---------------- */ window.addEventListener('hashchange', render); // 入口:首次进入落到新建对话引导页 if (!location.hash) location.replace(state.authed ? '#/messages' : '#/login'); applyTheme(); render(); })();