${item.builtin ? '内置应用' : '商店应用 · 示例'}
${variant === 'cards' ? item.summary : item.detail}
/* global document, window, history, location, matchMedia, innerWidth, Event, setTimeout, clearTimeout */
const paths = {
note: '
对应“显示魔法笔记入口”,移除后仍可从应用中心打开。
在左侧入口显示数量,超过 99 项显示 99+。本 Demo 使用 3 项示例。
${noteModes[noteSettings.mode]} · ${noteFormats[noteSettings.format]}
选择后即时保留于本次 Demo 会话,不调用 AI、不修改真实设置。点击 +,选择常驻应用。
'; const subtitles = { dock: '', grid: `${installed.size} 个已安装应用`, cards: '笔记 · 知识 · 自动化', launcher: 'Ctrl / ⌘ K', favorites: `${residentApps.length} 个应用常驻菜单`, scenes: '发现你的下一个好应用' }; $('#center-entry').innerHTML = ``; } function renderCatalog() { const query = $('#search').value.trim().toLowerCase(); const matches = items.filter(item => (catalogMode === 'store' ? !item.builtin : installed.has(item.id)) && `${item.name}${item.detail}`.toLowerCase().includes(query)); if (variant === 'favorites') matches.sort((a, b) => Number(residentApps.includes(b.id)) - Number(residentApps.includes(a.id))); $('#catalog-items').innerHTML = matches.map(item => { const ready = installed.has(item.id); const pinned = residentApps.includes(item.id); return `${item.builtin ? '内置应用' : '商店应用 · 示例'}
${variant === 'cards' ? item.summary : item.detail}
没有匹配的应用。可以换个关键词,或切换到商店。
'; document.querySelectorAll('.center-switch button').forEach(button => button.setAttribute('aria-pressed', String(button.dataset.catalog === catalogMode))); } function openCatalog(mode = 'apps') { settingsOpen = false; $('#back-to-apps').hidden = true; $('#app-settings').hidden = true; for (const selector of ['.center-switch', '#search', '#catalog-items']) $(selector).hidden = false; catalogMode = mode; $('#catalog').dataset.variant = variant; $('#catalog-title').textContent = mode === 'store' ? '发现新应用' : '应用中心'; $('#catalog-description').textContent = mode === 'store' ? '安装你需要的应用,自主决定是否常驻左侧菜单。' : '打开应用,或把常用应用留在左侧。系统工具请使用右侧工作栏。'; $('#search').value = ''; renderCatalog(); if (!$('#catalog').open) $('#catalog').showModal(); $('#search').focus(); } function openPage(name) { if ($('#catalog').open) $('#catalog').close(); $('#page-label').textContent = name; $('#page-content').replaceChildren(); const title = document.createElement('h1'); title.textContent = name; const description = document.createElement('p'); description.className = 'muted'; description.textContent = '主内容区页面预览。右侧工作栏保持当前工具状态。'; $('#page-content').append(title, description); $('#page-actions').innerHTML = name === '魔法笔记' ? `` : ''; if (name === '魔法笔记' || name === '设置') { const summary = document.createElement('p'); summary.id = 'note-settings-summary'; summary.className = 'muted'; $('#page-content').append(summary); if (name === '设置') { const button = document.createElement('button'); button.dataset.noteSettings = ''; button.textContent = '魔法笔记设置'; $('#page-content').append(button); } renderNoteSettings(); } document.querySelectorAll('.navigation button').forEach(button => button.classList.toggle('selected', button.dataset.page === name)); renderApps(); notify(`已打开${name}`); } function selectVariant(name) { variant = name; $('.app').dataset.variant = name; document.querySelectorAll('button[data-variant]').forEach(button => button.setAttribute('aria-pressed', String(button.dataset.variant === name))); [$('#variant-label').textContent, $('#variant-description').textContent, $('#design-tradeoff').textContent] = variants[name]; history.replaceState(null, '', `#${name}`); renderApps(); } function selectTool(id) { const content = { files: '本机 / goodbuddy · 示例PS > git status\nOn branch main\nworking tree clean\n\nPS >', browser: '浏览器 · 静态示例
磁盘 IO:读 12 MB/s · 写 3 MB/s
' }; $('#tool-content').innerHTML = content[id]; $('#tool-content').setAttribute('aria-labelledby', `tab-${id}`); document.querySelectorAll('[data-tool]').forEach(button => { button.setAttribute('aria-selected', String(button.dataset.tool === id)); button.tabIndex = button.dataset.tool === id ? 0 : -1; }); } document.addEventListener('click', event => { const button = event.target.closest('button'); if (!button) return; const data = button.dataset; if (data.variant) selectVariant(data.variant); if (data.catalog) openCatalog(data.catalog); if (data.page) openPage(data.page); if (data.open) openPage(items.find(item => item.id === data.open).name); if (data.tool) selectTool(data.tool); if (button.hasAttribute('data-note-settings')) showNoteSettings(); if (data.noteToggle || data.noteMode || data.noteFormat) { if (data.noteToggle === 'resident') residentApps = residentApps.includes('notes') ? residentApps.filter(id => id !== 'notes') : [...residentApps, 'notes']; if (data.noteToggle === 'showCount') noteSettings.showCount = !noteSettings.showCount; if (data.noteMode) noteSettings.mode = data.noteMode; if (data.noteFormat) noteSettings.format = data.noteFormat; renderApps(); renderNoteSettings(); const selector = data.noteToggle ? `[data-note-toggle="${data.noteToggle}"]` : data.noteMode ? `[data-note-mode="${data.noteMode}"]` : `[data-note-format="${data.noteFormat}"]`; $(selector).focus(); } if (button.id === 'open-center') openCatalog(variant === 'scenes' ? 'store' : 'apps'); if (button.id === 'edit-apps') { editingApps = !editingApps; renderApps(); $('#edit-apps').focus(); } if (data.pin || data.install || data.installPin) { const id = data.pin || data.install || data.installPin; if (data.install || data.installPin) installed.add(id); if (data.pin || data.installPin) residentApps = residentApps.includes(id) ? residentApps.filter(entry => entry !== id) : [...residentApps, id]; renderApps(); if ($('#catalog').open) { renderCatalog(); ($(`#catalog [data-pin="${id}"]`) || $('#search')).focus(); } else $('#edit-apps').focus(); } if (data.move) { const from = residentApps.indexOf(data.move); const to = from + Number(data.direction); if (to >= 0 && to < residentApps.length) [residentApps[from], residentApps[to]] = [residentApps[to], residentApps[from]]; renderApps(); $(`#resident-list [data-pin="${data.move}"]`).focus(); } }); $('#resident-list').addEventListener('dragstart', event => { draggedApp = event.target.closest('[data-resident]')?.dataset.resident || ''; if (!editingApps || !draggedApp) { event.preventDefault(); return; } event.dataTransfer.setData('text/plain', draggedApp); }); $('#resident-list').addEventListener('dragover', event => { if (editingApps && draggedApp) event.preventDefault(); }); $('#resident-list').addEventListener('drop', event => { event.preventDefault(); const target = event.target.closest('[data-resident]')?.dataset.resident; if (!editingApps || !residentApps.includes(draggedApp) || !target || target === draggedApp) return; const to = residentApps.indexOf(target); residentApps.splice(residentApps.indexOf(draggedApp), 1); residentApps.splice(to, 0, draggedApp); draggedApp = ''; renderApps(); $('#edit-apps').focus(); }); $('#resident-list').addEventListener('dragend', () => { draggedApp = ''; }); $('#search').addEventListener('input', renderCatalog); $('#close-catalog').onclick = () => $('#catalog').close(); $('#back-to-apps').onclick = () => { const query = $('#search').value; openCatalog(catalogMode); $('#search').value = query; renderCatalog(); ($('#catalog [data-note-settings]') || $('#search')).focus(); }; function toggleWorkbar(visible) { $('.workbar').hidden = !visible; $('#toggle-workbar').setAttribute('aria-expanded', String(visible)); } $('#toggle-workbar').onclick = () => toggleWorkbar($('.workbar').hidden); $('#close-tool').onclick = () => { toggleWorkbar(false); $('#toggle-workbar').focus(); }; $('.tool-tabs').addEventListener('keydown', event => { const tabs = [...document.querySelectorAll('[data-tool]')]; const index = tabs.indexOf(event.target); if (index < 0 || !['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) return; event.preventDefault(); const next = event.key === 'Home' ? 0 : event.key === 'End' ? tabs.length - 1 : (index + (event.key === 'ArrowRight' ? 1 : -1) + tabs.length) % tabs.length; selectTool(tabs[next].dataset.tool); tabs[next].focus(); }); document.addEventListener('keydown', event => { if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'k') { event.preventDefault(); openCatalog(); } if (!$('#catalog').open || settingsOpen || variant !== 'launcher') return; const actions = [...document.querySelectorAll('#catalog [data-open]')]; if (!actions.length) return; if (event.key === 'ArrowDown' || event.key === 'ArrowUp') { event.preventDefault(); const index = actions.indexOf(document.activeElement); const next = index < 0 ? (event.key === 'ArrowDown' ? 0 : actions.length - 1) : (index + (event.key === 'ArrowDown' ? 1 : -1) + actions.length) % actions.length; actions[next].focus(); } if (event.key === 'Enter' && document.activeElement === $('#search')) { event.preventDefault(); actions[0].click(); } }); $('#theme').onclick = () => { const dark = document.documentElement.dataset.theme !== 'dark'; document.documentElement.dataset.theme = dark ? 'dark' : 'light'; $('#theme').textContent = dark ? '切换浅色' : '切换深色'; }; $('#reset').onclick = () => { installed = new Set(items.filter(item => item.builtin).map(item => item.id)); residentApps = ['notes', 'knowledge']; editingApps = false; renderApps(); notify('应用安装与常驻配置已恢复默认'); }; let demoRunning = false; let demoRecording = false; let demoAttachments = []; let demoQueue = []; let demoReplyTimer; function renderComposer() { const tags = $('#composer-tags'); tags.replaceChildren(); const addTag = (text, clear) => { const button = document.createElement('button'); button.textContent = `${text} ×`; button.setAttribute('aria-label', `移除${text}`); button.onclick = () => { clear(); renderComposer(); $('#message').focus(); }; tags.append(button); }; demoAttachments.forEach((name, index) => addTag(name, () => demoAttachments.splice(index, 1))); for (const id of ['composer-expert', 'composer-agent', 'composer-command']) { const select = $(`#${id}`); if (select.value) addTag(select.selectedOptions[0].textContent, () => { select.value = ''; }); } document.querySelectorAll('[data-knowledge]:checked').forEach(input => addTag(input.dataset.knowledge, () => { input.checked = false; })); if ($('#composer-retrieval').value === 'always') addTag('每次检索', () => { $('#composer-retrieval').value = 'auto'; }); tags.hidden = !tags.childElementCount; $('#stop').hidden = !demoRunning; $('#send').disabled = !$('#message').value.trim() && $('#composer-command').value !== 'command'; $('#send').title = demoRunning ? '加入队列' : '发送'; $('#send').setAttribute('aria-label', demoRunning ? '加入演示队列' : '发送演示消息'); $('#composer-status').textContent = demoRecording ? '模拟录音中 · 再次点击结束' : demoRunning ? '模拟回复中 · 可继续发送加入队列' : '交互演示 · 不调用 AI'; for (const id of ['composer-runtime', 'composer-mode', 'composer-expert', 'composer-agent', 'composer-command']) $(`#${id}`).disabled = demoRunning; $('#compact').disabled = demoRunning; $('#runtime-options').hidden = $('#composer-runtime').value === '直连模型'; $('#compact').hidden = $('#composer-runtime').value === '直连模型'; $('#demo-queue').replaceChildren(); demoQueue.forEach((text, index) => { const row = document.createElement('div'); row.className = 'queue-row'; const label = document.createElement('span'); label.textContent = text; label.title = text; const insert = document.createElement('button'); insert.textContent = '立即插入'; insert.setAttribute('aria-label', `立即中断并插入:${text}`); insert.onclick = () => { demoQueue.splice(index, 1); startDemoReply(text); }; const remove = document.createElement('button'); remove.textContent = '×'; remove.setAttribute('aria-label', `删除待发送消息:${text}`); remove.onclick = () => { demoQueue.splice(index, 1); renderComposer(); }; row.append(label, insert, remove); $('#demo-queue').append(row); }); $('#demo-queue').hidden = !demoQueue.length; } function startDemoReply(text) { clearTimeout(demoReplyTimer); demoRunning = true; if ($('#composer-options').matches(':popover-open')) $('#composer-options').hidePopover(); notify(`模拟处理:${text}`); renderComposer(); demoReplyTimer = setTimeout(() => { demoRunning = false; if (demoQueue.length) startDemoReply(demoQueue.shift()); else { renderComposer(); notify('演示回复已完成,未调用 AI'); } }, 8000); } $('#send').onclick = () => { if ($('#send').disabled) return; const text = $('#composer-command').value === 'command' ? `/init ${$('#message').value.trim()}` : $('#message').value.trim(); $('#message').value = ''; $('#message').style.height = ''; demoAttachments = []; if (demoRunning) { demoQueue.push(text); renderComposer(); } else startDemoReply(text); }; $('#stop').onclick = () => { clearTimeout(demoReplyTimer); demoRunning = false; if (demoQueue.length) startDemoReply(demoQueue.shift()); else { renderComposer(); notify('已停止演示回复'); } }; $('#message').addEventListener('input', () => { $('#message').style.height = 'auto'; $('#message').style.height = `${Math.min(180, $('#message').scrollHeight)}px`; renderComposer(); }); $('#message').addEventListener('keydown', event => { if (event.key === 'Enter' && !event.shiftKey && !event.isComposing) { event.preventDefault(); $('#send').click(); } }); $('#attach').onclick = () => $('#attachment-input').click(); $('#attachment-input').onchange = () => { demoAttachments.push(...Array.from($('#attachment-input').files, file => file.name)); $('#attachment-input').value = ''; renderComposer(); }; $('#voice').onclick = () => { demoRecording = !demoRecording; $('#voice').setAttribute('aria-pressed', String(demoRecording)); if (!demoRecording) { $('#message').value += '请帮我整理一下今天的工作计划。'; $('#message').dispatchEvent(new Event('input')); } renderComposer(); }; $('#composer-options').addEventListener('change', event => { if (event.target.id === 'composer-command' && event.target.value === 'prompt') { $('#message').value = '请总结当前讨论,列出关键结论和下一步行动。'; event.target.value = ''; $('#message').dispatchEvent(new Event('input')); } renderComposer(); }); $('#composer-runtime').onchange = () => { $('#composer-agent').value = ''; $('#composer-command').value = ''; $('#agent-label').textContent = $('#composer-runtime').value === 'Continue' ? '预设' : 'Agent'; $('#composer-command').querySelector('[value="command"]').hidden = $('#composer-runtime').value !== 'OpenCode'; renderComposer(); }; $('#compact').onclick = () => { $('#context-value').textContent = '18%'; $('#context-progress').value = 18; $('#context-description').textContent = '压缩后对话估算:36k / 200k tokens'; $('#context-details').hidePopover(); notify('已模拟压缩上下文'); }; for (const [panelId, triggerId] of [['composer-options', 'composer-options-trigger'], ['context-details', 'context-trigger']]) { const panel = $(`#${panelId}`); const trigger = $(`#${triggerId}`); const position = () => { const rect = trigger.getBoundingClientRect(); panel.style.left = `${Math.max(16, Math.min(rect.right - panel.offsetWidth, innerWidth - panel.offsetWidth - 16))}px`; panel.style.top = `${Math.max(16, rect.top - panel.offsetHeight - 8)}px`; }; panel.addEventListener('toggle', event => { trigger.setAttribute('aria-expanded', String(event.newState === 'open')); if (event.newState === 'open') position(); }); window.addEventListener('resize', () => { if (panel.matches(':popover-open')) position(); }); window.addEventListener('scroll', () => { if (panel.matches(':popover-open')) position(); }, true); } renderComposer(); document.querySelectorAll('[data-icon]').forEach(element => { element.innerHTML = icon(element.dataset.icon); }); window.addEventListener('hashchange', () => { if (Object.hasOwn(variants, location.hash.slice(1))) selectVariant(location.hash.slice(1)); }); selectVariant(Object.hasOwn(variants, location.hash.slice(1)) ? location.hash.slice(1) : 'dock'); selectTool('files'); if (matchMedia('(max-width: 1080px)').matches) toggleWorkbar(false);