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 {
這是一個人的智慧總部。
若你不是擁有者,這裡沒有你要找的東西。
- 免登入看駕駛艙 ›
+ 若你不是擁有者,這裡沒有你要找的東西。
這是一個人的智慧總部。
若你不是擁有者,這裡沒有你要找的東西。
+ 免登入看駕駛艙 ›
+
@@ -234,7 +271,7 @@ function renderConsoleHtml(registryBase: string): string {
若你不是擁有者,這裡沒有你要找的東西。
Mira
首次設定
- 系統偵測到尚未設定擁有者帳號。
設定一組 Email 與密碼,之後只有你能進入。
+ 設定一組 Email 與密碼,之後只有你能進入。
系統偵測到尚未設定擁有者帳號。
設定一組 Email 與密碼,之後只有你能進入。
設定一組 Email 與密碼,之後只有你能進入。
@@ -243,6 +280,7 @@ function renderConsoleHtml(registryBase: string): string {
+
@@ -256,13 +294,14 @@ function renderConsoleHtml(registryBase: string): string {
+
-
@@ -311,7 +350,7 @@ function renderConsoleHtml(registryBase: string): string {
-
+
@@ -320,7 +359,7 @@ function renderConsoleHtml(registryBase: string): string {
Mira 控制台
+ Mira 控制台
@@ -291,12 +330,12 @@ function renderConsoleHtml(registryBase: string): string {
- 今日路線狀態即時同步
+ 今日路線狀態即時同步
- ○載入中…
每 60 秒自動刷新
+ 每 60 秒自動刷新
- 知識卡片
+ 知識卡片
載入中…
憑證管理
-
- 🛡 保險箱原則:系統只保存目錄與加密後的值,任何頁面都看不到密文內容。只能整筆替換或刪除。
+
' +
(open
? '
+ 🛡 保險箱原則:系統只保存目錄與加密後的值,任何頁面都看不到密文內容。只能整筆替換或刪除。
';
}).join('');
if (openName) fetchWfRuns(openName);
}
function renderWfBody(w) {
return '' +
- '
' +
+ '' + (w.description ? esc(w.description) : '(未填描述——re-deploy 時補一句「能做什麼」它才搜得到)') + '
' +
- '觸發網址' +
+ '
';
@@ -926,8 +1001,8 @@ function renderConsoleHtml(registryBase: string): string {
box.innerHTML = runs.map(function (r) {
var okRun = r.verdict === 'success';
return '' + (w.description ? esc(w.description) : '(未填描述——re-deploy 時補一句「能做什麼」它才搜得到)') + '
' +
+ '觸發網址' +
'' + esc(w.webhook_url || '') + '' +
'
' +
- '最近執行
' +
+ '最近執行
' +
'載入中…
' +
'' +
'' +
- '●' +
- '' + (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.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) : '尚未記錄') + '
' + esc(c.last_used_at ? fmtDate(c.last_used_at) : '尚未記錄') + '
' +
- '
••••••••••••••••已加密・不可回看
' +
+ '••••••••••••••••已加密・不可回看
' +
'' +
'' +
'' +
@@ -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 '';
+ '
';
}).join('') + '' + esc(m.text) + '
' +
- '等待 AI 處理' + esc(fmtDateTime(m.at)) + '
等待 AI 處理' + esc(fmtDateTime(m.at)) + '
沒有待處理的指令
';
- html += '已處理・' + done.length + ' 條
';
+ html += '已處理・' + done.length + ' 條
';
html += done.length
? '' + done.map(function (m) {
- return '';
@@ -1139,7 +1214,7 @@ function renderConsoleHtml(registryBase: string): string {
: '.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。
' + esc(m.text) + '
' +
+ return '' + esc(m.text) + '
' +
'✓' +
'' + esc(m.result || '已處理') + '' +
'' + esc(fmtDateTime(m.at)) + '
還沒有已處理的紀錄
';
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.