// Arcrun 桌面 App — 前端(t193) // // 版面照 leo 08-04 指定(附 Google Drive 截圖): // 「可以完全模仿 gdrive,**左方側邊欄,在右方替換頁面**」 // ⇒ 設定不再開 popup,全部是右側的頁;只有「確認刪除」才用覆蓋層。 import './arcrun-cis.css'; // 共用底層(色票/字體/紋理)——唯一真相源在 arcrun-cis/css/ import './style.css'; // 本 App 的版面 const go = window.go.main.App; const $ = (id) => document.getElementById(id); const esc = (s) => String(s).replace(/[&<>"]/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c])); // 跟隨系統深淺色(CIS 兩套色票都在 arcrun-cis.css 定義好了) const mq = window.matchMedia('(prefers-color-scheme: dark)'); const applyTheme = () => document.documentElement.setAttribute('data-theme', mq.matches ? 'dark' : 'light'); mq.addEventListener('change', applyTheme); applyTheme(); let state = null; let page = 'home'; let updateInfo = null; // { current, latest, available, notes, staged } // ── 覆蓋層(只給必須打斷的確認)── function openSheet(html, wire) { $('sheet').innerHTML = html; $('overlay').classList.add('on'); if (wire) wire(); } function closeSheet() { $('overlay').classList.remove('on'); } $('overlay').addEventListener('click', (e) => { if (e.target.id === 'overlay') closeSheet(); }); document.addEventListener('keydown', (e) => { if (e.key === 'Escape') closeSheet(); }); // ── 側邊欄換頁 ── document.querySelectorAll('#side .nav').forEach((el) => { el.onclick = () => { page = el.dataset.page; document.querySelectorAll('#side .nav').forEach((n) => n.classList.toggle('on', n === el)); renderPage(); }; }); // ── 各頁 ── function pageHome(s) { const folders = (s.accounts || []).reduce((n, a) => n + (a.folders || []).length, 0); if (!s.accounts || !s.accounts.length) return onboarding(); return `

目前狀況

${s.accounts.length}
個知識庫
${folders}
個資料夾在看守
${s.extractedOK || 0}
份已整理

快速動作

`; } function pageFolders(s) { if (!s.accounts || !s.accounts.length) return onboarding(); return (s.accounts).map((a) => `
${esc(a.name)}${esc(a.host)}
${(a.folders || []).map((f) => `
${esc(f.path)} 自動同步中
`).join('') || `
還沒有資料夾
`} `).join('') + `
`; } function pageAI(s) { const gem = s.engine === 'gemma'; return `

用哪個 AI 幫你整理文件?

Gemini 金鑰${s.geminiKey ? '(目前已設定,清空並儲存即可刪除)' : ''}
`; } // leo:「檢查更新連到 docs 的檢查更新,不正確,**是直接在這裡進行檢查更新**, // 應該是跳到一個頁面顯示**現在版本與最新版本**,然後**下載可以進行安裝**」 function pageUpdate(s) { const u = updateInfo; const latest = u ? (u.latest || '查詢中…') : '查詢中…'; let action = ``; let note = ''; if (u && u.staged) { action = ``; note = `
新版 ${esc(u.latest)} 已下載完成,重新啟動就會套用。
`; } else if (u && u.available) { action = ``; note = u.notes ? `
${esc(u.notes)}
` : ''; } else if (u && u.err) { note = `
${esc(u.err)}
`; } else if (u) { note = `
你已經是最新版本。
`; } return `

版本與更新

${esc(s.version || '—')}
目前版本
${esc(latest)}
最新版本
${note}
${action}

需要協助?

安裝、更新、把知識庫接到你的 AI,說明文件都寫在這裡。
`; } function onboarding() { return `
先連上你的知識庫
Arcrun 會看守你指定的資料夾,
自動把文件整理成 AI 查得到的知識卡。
還沒有知識庫?先到 install.arcrun.dev 裝一個(免費)。
`; } // ── 渲染 ── function renderPage() { if (!state) return; const el = $('page'); el.innerHTML = page === 'folders' ? pageFolders(state) : page === 'ai' ? pageAI(state) : page === 'update' ? pageUpdate(state) : pageHome(state); wire(); } function wire() { const on = (id, fn) => { const e = $(id); if (e) e.onclick = fn; }; on('qAdd', addFolder); on('fAdd', addFolder); on('qDocs', () => go.OpenURL('https://rag.arcrun.dev/docs/')); on('uDocs', () => go.OpenURL('https://rag.arcrun.dev/docs/use/update/')); on('qPortal', () => { const a = state.accounts && state.accounts[0]; if (a) go.OpenURL('https://' + a.host.replace('arcrun-cypher-executor.', 'arcrun-rag-ui.') + '/portal/'); }); on('fAcct', showConnect); on('obConnect', showConnect); on('obInstall', () => go.OpenURL('https://install.arcrun.dev/')); on('aiSave', saveAI); on('uCheck', checkUpdate); on('uDownload', downloadUpdate); on('uApply', applyUpdate); document.querySelectorAll('[data-rm]').forEach((b) => { b.onclick = () => confirmRemove(Number(b.dataset.acc), b.dataset.rm); }); } function render(s) { const first = !state; state = s; $('ver').textContent = s.version ? s.version : ''; $('statusBig').textContent = s.statusBig; $('statusBig').classList.toggle('syncing', !!s.syncing); $('statusSub').textContent = s.statusSub; // 只有狀態列每秒更新;頁面內容不重繪,避免打斷輸入 if (first) renderPage(); } async function tick() { try { render(await go.GetState()); } catch (e) { /* 後端還沒起來 */ } } // ── 動作 ── $('btnSync').onclick = async () => { await go.SyncNow(); tick(); }; async function addFolder() { if (!state || !state.accounts.length) { showConnect(); return; } const p = await go.PickFolder(); if (!p) return; await go.AddFolder(0, p); state = await go.GetState(); renderPage(); } function confirmRemove(accIdx, path) { openSheet(`

移除這個資料夾?

「${esc(path)}」不再自動同步。
已經上傳的知識卡不會被刪除。

`, () => { $('c1').onclick = closeSheet; $('c2').onclick = async () => { await go.RemoveFolder(accIdx, path); closeSheet(); state = await go.GetState(); renderPage(); }; }); } function showConnect() { openSheet(`

連上你的知識庫

貼上你的知識庫網址,再輸入你在網站上設定的帳號密碼。

知識庫網址
帳號(Email)
密碼
`, () => { $('c1').onclick = closeSheet; $('c2').onclick = async () => { try { await go.Connect($('u').value.trim(), $('e').value.trim(), $('p').value); closeSheet(); state = await go.GetState(); renderPage(); } catch (ex) { $('err').textContent = String(ex); $('err').style.display = 'block'; } }; }); } async function saveAI() { const useGemini = document.querySelector('input[name=ai]:checked').value === 'gemini'; try { await go.SetAI(useGemini, $('k').value.trim()); state = await go.GetState(); renderPage(); } catch (ex) { $('aiErr').textContent = String(ex); $('aiErr').style.display = 'block'; } } // 更新:**在 App 裡完成**(leo 指定),不是丟去 docs async function checkUpdate() { updateInfo = { latest: '查詢中…' }; renderPage(); try { updateInfo = await go.CheckUpdate(); } catch (ex) { updateInfo = { err: String(ex) }; } renderPage(); } async function downloadUpdate() { updateInfo = Object.assign({}, updateInfo, { notes: '下載中…請稍候' }); renderPage(); try { updateInfo = await go.DownloadUpdate(); } catch (ex) { updateInfo = { err: String(ex) }; } renderPage(); } async function applyUpdate() { try { await go.ApplyUpdate(); } catch (ex) { updateInfo = { err: String(ex) }; renderPage(); } } tick(); setInterval(tick, 1000);