diff --git a/cypher-executor/src/routes/console-dashboard.ts b/cypher-executor/src/routes/console-dashboard.ts index 6e2a31b..58f6ee9 100644 --- a/cypher-executor/src/routes/console-dashboard.ts +++ b/cypher-executor/src/routes/console-dashboard.ts @@ -198,65 +198,92 @@ function renderDashboardHtml(): string { Mira 駕駛艙 +
Mira 駕駛艙
-
+
+
+ +
@@ -309,6 +336,20 @@ function renderDashboardHtml(): string { function cnDay(n) { return n <= 10 ? CNUM[n] : (n < 20 ? '十' + (n % 10 ? CNUM[n % 10] : '') : CNUM[Math.floor(n / 10)] + '十' + (n % 10 ? CNUM[n % 10] : '')); } const now = new Date(); $('date-str').textContent = CNUM[now.getMonth() + 1] + '月' + cnDay(now.getDate()) + '日'; + // 深/淺切換(與 /console 共用 arcrun_console_theme;預設淺色) + function syncThemeBtn() { $('theme-btn').textContent = document.documentElement.getAttribute('data-theme') === 'dark' ? '☀' : '☾'; } + $('theme-btn').addEventListener('click', () => { + const next = document.documentElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark'; + document.documentElement.setAttribute('data-theme', next); + try { localStorage.setItem('arcrun_console_theme', next); } catch (e) { /* 私密模式忽略 */ } + syncThemeBtn(); + }); + syncThemeBtn(); + // fetch 失敗(斷網)的裸訊息 → 友善誠實文案;60 秒定時器常駐,網路恢復自動刷回 + function friendlyErr(e) { + const m = e && e.message ? String(e.message) : String(e); + return /failed to fetch|load failed|networkerror|network request failed/i.test(m) ? '連線中斷' : m; + } async function load() { try { const res = await fetch('/console/dashboard-data'); @@ -346,7 +387,7 @@ function renderDashboardHtml(): string { } catch (e) { $('orb-char').textContent = '?'; $('orb-title').textContent = '讀不到狀態'; - $('orb-sub').innerHTML = '' + esc(e.message) + ''; + $('orb-sub').innerHTML = '' + esc(friendlyErr(e)) + '・每 60 秒自動重試'; } } load(); diff --git a/cypher-executor/src/routes/console.ts b/cypher-executor/src/routes/console.ts index 302fb1a..a3a62b8 100644 --- a/cypher-executor/src/routes/console.ts +++ b/cypher-executor/src/routes/console.ts @@ -3,7 +3,7 @@ * * 視覺:claude design 定稿「紙感暖黑 2a」(system-dev/docs/6-user/Mira Console 設計規劃/ * Mira Console.dc.html + Mira Style Guide.dc.html)——紙紋底 repeating-linear-gradient、 - * 明體標題(Songti TC 級聯)、琥珀 #e8b45a 強調、墨綠呼吸球嵌「安」字、正文 ≥16px、 + * 明體標題(Songti TC 級聯)、琥珀 var(--amber) 強調、墨綠呼吸球嵌「安」字、正文 ≥16px、 * 手機優先 390px + ≥1024px 側欄、頁面本體不出水平捲軸。 * * 資訊架構:brief 8 頁(docs/1-vision/mira-console-design-brief.md)= @@ -30,6 +30,15 @@ * 實際打 /kbdb/* 用後端回的固定租戶字串(CONSOLE_TENANT)。駕駛艙免登入版在 /console/dashboard。 * * v0 的 components/recipes 查詢區(Arcrun#3 原搜尋台)收進「工作流」頁下方折疊區,不砍功能。 + * + * 2026-07-04 leo 實測三回饋(總管派工二輪): + * 1. 深/淺主題:CSS custom properties 兩份色板(:root=淺色紙感宣紙米白+墨字, + * :root[data-theme=dark]=原定稿暖黑不動),預設淺色,選擇存 localStorage + * (key arcrun_console_theme),head 預載腳本防閃色。切換入口=登入/首次設定頁小鈕、 + * 側欄項、設定頁正式開關。金琥珀例外(按鈕漸層/狀態球/toast/gcenter)兩版不變。 + * 2. 登入/首次設定置中:.authwrap.view.on 補 display:flex(原被 .view.on 的 block 蓋掉 → 靠左)。 + * 3. fetch 韌性:friendlyErr() 把裸 Failed to fetch / Load failed 轉「連線中斷」誠實文案; + * 駕駛艙 60 秒定時器常駐,網路恢復自動刷回。 */ import { Hono } from 'hono'; import type { Bindings } from '../types'; @@ -43,29 +52,54 @@ function renderConsoleHtml(registryBase: string): string { Mira Console + @@ -223,8 +259,9 @@ function renderConsoleHtml(registryBase: string): string {
-
這是一個人的智慧總部。
若你不是擁有者,這裡沒有你要找的東西。
- 免登入看駕駛艙 › +
這是一個人的智慧總部。
若你不是擁有者,這裡沒有你要找的東西。
+ 免登入看駕駛艙 › + @@ -234,7 +271,7 @@ function renderConsoleHtml(registryBase: string): string {
Mira
首次設定
-
系統偵測到尚未設定擁有者帳號。
設定一組 Email 與密碼,之後只有你能進入。
+
系統偵測到尚未設定擁有者帳號。
設定一組 Email 與密碼,之後只有你能進入。
@@ -243,6 +280,7 @@ function renderConsoleHtml(registryBase: string): string {
+ @@ -256,13 +294,14 @@ function renderConsoleHtml(registryBase: string): string { +
-
Mira 控制台
+
Mira 控制台
@@ -291,12 +330,12 @@ function renderConsoleHtml(registryBase: string): string {
- 今日路線狀態即時同步 + 今日路線狀態即時同步
  • 載入中…
    -
    每 60 秒自動刷新
    +
    每 60 秒自動刷新
    @@ -311,7 +350,7 @@ function renderConsoleHtml(registryBase: string): string {
    -
    +
    @@ -320,7 +359,7 @@ function renderConsoleHtml(registryBase: string): string {
    - 知識卡片 + 知識卡片
    載入中…
    @@ -350,11 +389,11 @@ function renderConsoleHtml(registryBase: string): string {
    憑證管理
    -
    - 🛡 保險箱原則:系統只保存目錄與加密後的值,任何頁面都看不到密文內容。只能整筆替換或刪除。 +
    + 🛡 保險箱原則:系統只保存目錄與加密後的值,任何頁面都看不到密文內容。只能整筆替換或刪除。
    '; }).join(''); if (openName) fetchWfRuns(openName); } function renderWfBody(w) { return '
    ' + - '
    ' + (w.description ? esc(w.description) : '(未填描述——re-deploy 時補一句「能做什麼」它才搜得到)') + '
    ' + - '
    觸發網址' + + '
    ' + (w.description ? esc(w.description) : '(未填描述——re-deploy 時補一句「能做什麼」它才搜得到)') + '
    ' + + '
    觸發網址' + '' + esc(w.webhook_url || '') + '' + '
    ' + - '
    最近執行
    ' + + '
    最近執行
    ' + '
    載入中…
    ' + '' + '
    '; @@ -926,8 +1001,8 @@ function renderConsoleHtml(registryBase: string): string { box.innerHTML = runs.map(function (r) { var okRun = r.verdict === 'success'; return '
    ' + - '' + - '' + (okRun ? '成功' : '失敗') + (typeof r.duration_ms === 'number' ? '・' + r.duration_ms + 'ms' : '') + (r.message && !okRun ? '・' + esc(String(r.message).slice(0, 60)) : '') + '' + + '' + + '' + (okRun ? '成功' : '失敗') + (typeof r.duration_ms === 'number' ? '・' + r.duration_ms + 'ms' : '') + (r.message && !okRun ? '・' + esc(String(r.message).slice(0, 60)) : '') + '' + '' + esc(r.recorded_at ? fmtDateTime(r.recorded_at) : (r.timestamp ? fmtDateTime(Number(r.timestamp)) : '')) + '
    '; }).join(''); }) @@ -954,7 +1029,7 @@ function renderConsoleHtml(registryBase: string): string { }) .catch(function (e) { btn.disabled = false; btn.textContent = '手動觸發'; - if (msg) msg.innerHTML = '請求失敗:' + esc(e.message) + ''; + if (msg) msg.innerHTML = '請求失敗:' + esc(friendlyErr(e)) + ''; }); } @@ -1025,7 +1100,7 @@ function renderConsoleHtml(registryBase: string): string { toast('已加密保存'); loadCreds(); }) - .catch(function (e) { st.innerHTML = '請求失敗:' + esc(e.message) + ''; }); + .catch(function (e) { st.innerHTML = '請求失敗:' + esc(friendlyErr(e)) + ''; }); }); var credOpen = ''; var credCache = []; @@ -1040,7 +1115,7 @@ function renderConsoleHtml(registryBase: string): string { credCache = x.d.credentials || []; renderCreds(); }) - .catch(function (e) { $('cred-list').innerHTML = '
    請求失敗:' + esc(e.message) + '
    '; }); + .catch(function (e) { $('cred-list').innerHTML = '
    請求失敗:' + esc(friendlyErr(e)) + '
    '; }); } function renderCreds() { if (!credCache.length) { @@ -1054,11 +1129,11 @@ function renderConsoleHtml(registryBase: string): string { '
    ' + '' + esc(initial) + '' + '
    ' + esc(c.name) + '
    ' + - '
    ' + esc(c.service || '未填服務') + '・建立於 ' + esc(fmtDate(c.created_at) || '—') + '
    ' + - '
    最後使用
    ' + esc(c.last_used_at ? fmtDate(c.last_used_at) : '尚未記錄') + '
    ' + + '
    ' + esc(c.service || '未填服務') + '・建立於 ' + esc(fmtDate(c.created_at) || '—') + '
    ' + + '
    最後使用
    ' + esc(c.last_used_at ? fmtDate(c.last_used_at) : '尚未記錄') + '
    ' + (open ? '
    ' + - '
    ••••••••••••••••已加密・不可回看
    ' + + '
    ••••••••••••••••已加密・不可回看
    ' + '' + '
    ' + '' + @@ -1091,7 +1166,7 @@ function renderConsoleHtml(registryBase: string): string { toast('「' + name + '」已整筆替換'); loadCreds(); }) - .catch(function (e) { rep.disabled = false; if (msg) msg.innerHTML = '請求失敗:' + esc(e.message) + ''; }); + .catch(function (e) { rep.disabled = false; if (msg) msg.innerHTML = '請求失敗:' + esc(friendlyErr(e)) + ''; }); return; } var tog = ev.target.closest('[data-credtoggle]'); @@ -1121,17 +1196,17 @@ function renderConsoleHtml(registryBase: string): string { var news = items.filter(function (i) { return i.status !== 'done'; }); var done = items.filter(function (i) { return i.status === 'done'; }); var html = ''; - html += '
    新的・' + news.length + ' 條
    '; + html += '
    新的・' + news.length + ' 條
    '; html += news.length ? '
    ' + news.map(function (m) { return '
    ' + esc(m.text) + '
    ' + - '
    等待 AI 處理' + esc(fmtDateTime(m.at)) + '
    '; + '
    等待 AI 處理' + esc(fmtDateTime(m.at)) + '
    '; }).join('') + '
    ' : '
    沒有待處理的指令
    '; - html += '
    已處理・' + done.length + ' 條
    '; + html += '
    已處理・' + done.length + ' 條
    '; html += done.length ? '
    ' + done.map(function (m) { - return '
    ' + esc(m.text) + '
    ' + + return '
    ' + esc(m.text) + '
    ' + '
    ' + '' + esc(m.result || '已處理') + '' + '' + esc(fmtDateTime(m.at)) + '
    '; @@ -1139,7 +1214,7 @@ function renderConsoleHtml(registryBase: string): string { : '
    還沒有已處理的紀錄
    '; out.innerHTML = html; }) - .catch(function (e) { out.innerHTML = '
    請求失敗:' + esc(e.message) + '
    '; }); + .catch(function (e) { out.innerHTML = '
    請求失敗:' + esc(friendlyErr(e)) + '
    '; }); } // ── 設定 ── @@ -1164,10 +1239,10 @@ function renderConsoleHtml(registryBase: string): string { var kb = rs[1].status === 'fulfilled' ? rs[1].value : null; var rows = ''; rows += '
    服務' + esc(svc.service || 'arcrun-cypher-executor') + '
    '; - rows += '
    版本' + esc(svc.version || '—') + '
    '; + rows += '
    版本' + esc(svc.version || '—') + '
    '; rows += '
    部署位置' + esc(location.host) + '
    '; rows += '
    租戶' + esc(S.tenant) + '
    '; - rows += '
    總庫規模' + (kb && typeof kb.total === 'number' ? kb.total.toLocaleString() + ' 筆' : '讀取失敗') + '
    '; + rows += '
    總庫規模' + (kb && typeof kb.total === 'number' ? kb.total.toLocaleString() + ' 筆' : '讀取失敗') + '
    '; $('st-info').innerHTML = rows; }); } @@ -1190,7 +1265,7 @@ function renderConsoleHtml(registryBase: string): string { $('st-pw-old').value = ''; $('st-pw-email').value = ''; $('st-pw-new').value = ''; toast('密碼已更新'); }) - .catch(function (e) { st.innerHTML = '請求失敗:' + esc(e.message) + ''; }); + .catch(function (e) { st.innerHTML = '請求失敗:' + esc(friendlyErr(e)) + ''; }); }); $('st-vec-switch').addEventListener('click', function () { toast('開關即將開通——目前需在部署端啟用 kbdb_embed'); diff --git a/system-dev/wiki/status.md b/system-dev/wiki/status.md index 13239a8..7f214eb 100644 --- a/system-dev/wiki/status.md +++ b/system-dev/wiki/status.md @@ -22,6 +22,7 @@ metadata: > - **驗證(本機,未部署)**:tsc exit 0;vitest 26/27(1 失敗 stash 複驗=pre-existing「不存在的零件回傳失敗」);`wrangler deploy --dry-run` 打包過;inline JS 求值後 `node --check` + 純函式行為測試全過(XSS escape/時間容錯,驗證法固化 mistakes #24)。 > - **✅ 已部署 leo21c(2026-07-04 總管親跑,leo 明示「部署」)**:手工注入(live worker settings API 拉真實 binding 對齊)→ dry-run 核對 7KV/D1/13SVC/6vars 與 live 一致 → wrangler deploy(Version 13b86bda)→ toml 已還原乾淨。**驗收全綠**:/console 200+新視覺(title=Mira Console);/console/dashboard 200+dashboard-data 真資料(green light/progress-guard beat);/kbdb/search 401=auth by-design 非迴歸;/credentials/catalog 401 誠實要 key(非 404,端點在);/console/inbox-data 401 session 鎖生效。 > - **部署注意(留總管)**:不跑 acr update(mistakes #23 codeload 陷阱),走 leo21c wrangler 直推 cypher-executor 單 worker;關聯視圖需 leo21c 有部署 kbdb-graph-plugin(沒部署→前端誠實顯示不可達,不擋其他頁);`CONSOLE_TENANT`/`WORKER_SUBDOMAIN` 沿既有注入。SDD 缺口沿 07-02 未解(search-console SDD 未建,rule 4.3 需 richblack 確認,本次照派工先實作、誠實記錄)。 +> - **二輪(leo 實測三回饋,本機已修未部署)**:① 深/淺主題切換+**預設淺色**(CSS custom properties 兩份色板:淺=宣紙米白紙紋+墨字、琥珀調深 #8a5f1e 過 AA;深=原定稿原值不動;localStorage `arcrun_console_theme`,head 預載防閃色;入口=登入/setup 小鈕+側欄項+設定頁開關,dashboard 頁同步支援)② 登入/首次設定置中(根因 `.view.on{display:block}` 蓋掉 authwrap flex → 補 `.authwrap.view.on{display:flex}`)③ fetch 斷網裸「Failed to fetch」→ `friendlyErr()` 誠實文案「連線中斷」+駕駛艙/dashboard 60 秒定時器常駐自動重試。驗證:tsc 0、vitest 26/27(同 pre-existing)、三層跳脫驗證全綠(#24 手法)、淺色板 WCAG 對比實算(正文 12.2:1/琥珀 4.82/ok 4.58/err 5.17)。金琥珀例外(按鈕漸層/狀態球/toast/gcenter)兩版刻意不變。 > > **2026-07-02 上個 session(雲端工人 T-loop1d,Arcrun#3 搜尋/控制台頁 v0,跨多 repo 第二次壓測)**: > - **實作**:`cypher-executor/src/routes/console.ts`(GET /console,單檔 HTML+原生 JS 薄殼)+ `index.ts` 掛載。三查詢區全打既有端點:`/kbdb/search`、`/workflows/search`(同源,X-Arcrun-API-Key)、components 打「同帳號」`arcrun-registry..workers.dev`(WORKER_SUBDOMAIN 現算,不硬打官方 registry.arcrun.dev)、recipes 打 `/public-recipes`(公庫免 auth)。vectorize 狀態直接讀 KBDB search 回應的 `mode`/`capability_hint`,沒開新端點。tsc/vitest 綠(既有 1 個無關失敗,`不存在的零件回傳失敗` 測試,stash 驗證 pre-existing)。commit `981dc25` 已 push Gitea main。