fix(console): leo 實測三回饋——深淺主題預設淺色/登入頁置中/fetch 斷網韌性 (Arcrun#3 console 系)
1. 深/淺主題切換+預設淺色(leo 明示):CSS custom properties 兩份色板
一套結構——:root=淺色紙感(宣紙米白紙紋+墨色文字+琥珀調深 #8a5f1e,
WCAG 對比實算正文 12.2:1/琥珀 4.82/ok 4.58/err 5.17 全過 AA),
:root[data-theme=dark]=原定稿暖黑 2a 原值不動。選擇存 localStorage
(arcrun_console_theme),head 預載腳本防閃色。切換入口:登入/首次
設定頁小鈕、桌面側欄項、設定頁正式開關;/console/dashboard 免登入頁
同步支援(pagehead 小圖示)。金琥珀例外兩版刻意不變(btn 漸層/狀態球/
toast/gcenter)。
2. 登入/首次設定頁置中:根因=.view.on{display:block} 蓋掉 .authwrap 的
flex → 內容靠左;補一行 .authwrap.view.on{display:flex} 水平垂直置中。
3. fetch 斷網韌性:friendlyErr() 把裸 Failed to fetch/Load failed 轉
「連線中斷」誠實文案(非斷網錯誤原樣保留);駕駛艙與 dashboard 的
60 秒定時器常駐、錯誤文案標明自動重試,網路恢復自動刷回。
驗證:tsc --noEmit exit 0;vitest 26/27(1 失敗=pre-existing 基準);
三層跳脫驗證(mistakes #24:template 求值→node --check→行為斷言)全綠。
不部署,總管接手。
This commit is contained in:
@@ -198,65 +198,92 @@ function renderDashboardHtml(): string {
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Mira 駕駛艙</title>
|
||||
<script>
|
||||
// 主題預載(防閃色):預設淺色(leo 2026-07-04 明示),與 /console 共用同一 localStorage key
|
||||
document.documentElement.setAttribute('data-theme', (function () {
|
||||
try { return localStorage.getItem('arcrun_console_theme') === 'dark' ? 'dark' : 'light'; } catch (e) { return 'light'; }
|
||||
})());
|
||||
</script>
|
||||
<style>
|
||||
/* Mira Console 定稿視覺(紙感暖黑「2a」,Mira Style Guide 2026-07-04):
|
||||
紙紋底 repeating-linear-gradient、明體標題級聯、琥珀 #e8b45a 強調、呼吸狀態球嵌單字。 */
|
||||
/* Mira Console 定稿視覺(紙感「2a」,Mira Style Guide 2026-07-04):
|
||||
紙紋底 repeating-linear-gradient、明體標題級聯、琥珀強調、呼吸狀態球嵌單字。
|
||||
2026-07-04 二輪:CSS custom properties 兩份色板——預設淺色(宣紙米白+墨字),深色=原定稿暖黑不動。 */
|
||||
* { box-sizing: border-box; }
|
||||
html, body { margin: 0; background: repeating-linear-gradient(0deg,#191410 0px,#191410 3px,#1b1611 3px,#1b1611 4px); color: #ede4d3;
|
||||
:root {
|
||||
--paper-a: #f4eddc; --paper-b: #f1e9d6;
|
||||
--ink: #2f2a20; --ink-rgb: 30,24,14;
|
||||
--amber: #8a5f1e; --amber-rgb: 138,95,30;
|
||||
--ok: #1d7a48; --ok-rgb: 29,122,72;
|
||||
--err: #b03a26; --err-rgb: 176,58,38;
|
||||
--track: rgba(30,24,14,.12);
|
||||
}
|
||||
:root[data-theme="dark"] {
|
||||
--paper-a: #191410; --paper-b: #1b1611;
|
||||
--ink: #ede4d3; --ink-rgb: 237,228,211;
|
||||
--amber: #e8b45a; --amber-rgb: 232,180,90;
|
||||
--ok: #7fe0a8; --ok-rgb: 63,190,120;
|
||||
--err: #e58575; --err-rgb: 217,95,76;
|
||||
--track: rgba(255,255,255,.08);
|
||||
}
|
||||
html, body { margin: 0; background: repeating-linear-gradient(0deg,var(--paper-a) 0px,var(--paper-a) 3px,var(--paper-b) 3px,var(--paper-b) 4px); color: var(--ink);
|
||||
font-family: -apple-system, "PingFang TC", "Microsoft JhengHei", system-ui, sans-serif; font-size: 16px; -webkit-font-smoothing: antialiased; }
|
||||
.serif { font-family: 'Songti TC','LiSong Pro',PMingLiU,serif; }
|
||||
main { max-width: 560px; margin: 0 auto; padding: 0 20px 40px; }
|
||||
.pagehead { padding: 22px 2px 14px; border-bottom: 2px solid rgba(232,180,90,.4); display: flex; justify-content: space-between; align-items: baseline; }
|
||||
.pagehead { padding: 22px 2px 14px; border-bottom: 2px solid rgba(var(--amber-rgb),.4); display: flex; justify-content: space-between; align-items: baseline; }
|
||||
.pagehead .title { font-family: 'Songti TC','LiSong Pro',PMingLiU,serif; font-size: 23px; letter-spacing: .2em; }
|
||||
.pagehead .title small { font-size: 14px; letter-spacing: .3em; color: rgba(237,228,211,.5); }
|
||||
.pagehead .date { font-family: 'Songti TC','LiSong Pro',PMingLiU,serif; font-size: 14px; color: rgba(237,228,211,.55); }
|
||||
.pagehead .title small { font-size: 14px; letter-spacing: .3em; color: rgba(var(--ink-rgb),.5); }
|
||||
.pagehead .date { font-family: 'Songti TC','LiSong Pro',PMingLiU,serif; font-size: 14px; color: rgba(var(--ink-rgb),.55); }
|
||||
.orb-row { display: flex; align-items: center; gap: 20px; padding: 26px 2px 20px; }
|
||||
.orb { width: 84px; height: 84px; border-radius: 50%; flex: none; display: grid; place-items: center; }
|
||||
.orb span { font-family: 'Songti TC','LiSong Pro',PMingLiU,serif; font-size: 30px; font-weight: 600; color: rgba(10,20,14,.85); text-shadow: 0 1px 0 rgba(255,255,255,.25); }
|
||||
.orb-title { font-family: 'Songti TC','LiSong Pro',PMingLiU,serif; font-size: 23px; font-weight: 600; }
|
||||
.orb-sub { margin-top: 5px; font-size: 15px; color: rgba(237,228,211,.6); line-height: 1.55; }
|
||||
@keyframes breatheGreen { 0%,100% { box-shadow: 0 0 24px 6px rgba(63,190,120,.35); } 50% { box-shadow: 0 0 42px 14px rgba(63,190,120,.55); } }
|
||||
@keyframes breatheAmber { 0%,100% { box-shadow: 0 0 24px 6px rgba(232,180,90,.35); } 50% { box-shadow: 0 0 42px 14px rgba(232,180,90,.6); } }
|
||||
@keyframes breatheRed { 0%,100% { box-shadow: 0 0 24px 6px rgba(217,95,76,.4); } 50% { box-shadow: 0 0 44px 16px rgba(217,95,76,.65); } }
|
||||
.orb-sub { margin-top: 5px; font-size: 15px; color: rgba(var(--ink-rgb),.6); line-height: 1.55; }
|
||||
@keyframes breatheGreen { 0%,100% { box-shadow: 0 0 24px 6px rgba(var(--ok-rgb),.35); } 50% { box-shadow: 0 0 42px 14px rgba(var(--ok-rgb),.55); } }
|
||||
@keyframes breatheAmber { 0%,100% { box-shadow: 0 0 24px 6px rgba(var(--amber-rgb),.35); } 50% { box-shadow: 0 0 42px 14px rgba(var(--amber-rgb),.6); } }
|
||||
@keyframes breatheRed { 0%,100% { box-shadow: 0 0 24px 6px rgba(var(--err-rgb),.4); } 50% { box-shadow: 0 0 44px 16px rgba(var(--err-rgb),.65); } }
|
||||
.bricks { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
||||
.brick { padding: 16px; border-radius: 12px; }
|
||||
.brick.amber { background: rgba(232,180,90,.07); border: 1px solid rgba(232,180,90,.22); }
|
||||
.brick.plain { background: rgba(237,228,211,.04); border: 1px solid rgba(237,228,211,.14); }
|
||||
.brick .lbl { font-size: 13.5px; color: rgba(237,228,211,.55); margin-bottom: 6px; }
|
||||
.brick .num { font-family: ui-monospace, Menlo, monospace; font-size: 26px; color: #e8b45a; }
|
||||
.brick .num small { font-size: 15px; color: rgba(237,228,211,.5); }
|
||||
.bar { margin-top: 10px; height: 6px; border-radius: 3px; background: rgba(255,255,255,.08); }
|
||||
.brick.amber { background: rgba(var(--amber-rgb),.07); border: 1px solid rgba(var(--amber-rgb),.22); }
|
||||
.brick.plain { background: rgba(var(--ink-rgb),.04); border: 1px solid rgba(var(--ink-rgb),.14); }
|
||||
.brick .lbl { font-size: 13.5px; color: rgba(var(--ink-rgb),.55); margin-bottom: 6px; }
|
||||
.brick .num { font-family: ui-monospace, Menlo, monospace; font-size: 26px; color: var(--amber); }
|
||||
.brick .num small { font-size: 15px; color: rgba(var(--ink-rgb),.5); }
|
||||
.bar { margin-top: 10px; height: 6px; border-radius: 3px; background: var(--track); }
|
||||
.bar > i { display: block; height: 100%; border-radius: 3px; background: linear-gradient(90deg,#b98330,#e8b45a); transition: width .6s; }
|
||||
.wait-box { margin-top: 14px; padding: 20px; border-radius: 12px; border: 1px dashed rgba(63,190,120,.3); background: rgba(63,190,120,.05); }
|
||||
.wait-box.has { border-color: rgba(232,180,90,.45); background: rgba(232,180,90,.05); }
|
||||
.wait-head { font-family: 'Songti TC','LiSong Pro',PMingLiU,serif; font-size: 16px; letter-spacing: .2em; color: rgba(237,228,211,.6); margin-bottom: 10px; text-align: center; }
|
||||
.wait-none { font-family: 'Songti TC','LiSong Pro',PMingLiU,serif; font-size: 20px; color: #7fe0a8; letter-spacing: .08em; text-align: center; }
|
||||
.wait-item { display: flex; align-items: center; gap: 12px; padding: 12px 14px; margin-top: 8px; border-radius: 10px; background: rgba(232,180,90,.1); border: 1px solid rgba(232,180,90,.3); font-size: 16px; line-height: 1.5; }
|
||||
.wait-item .dm { color: #e8b45a; font-size: 17px; flex: none; }
|
||||
.wait-box { margin-top: 14px; padding: 20px; border-radius: 12px; border: 1px dashed rgba(var(--ok-rgb),.3); background: rgba(var(--ok-rgb),.05); }
|
||||
.wait-box.has { border-color: rgba(var(--amber-rgb),.45); background: rgba(var(--amber-rgb),.05); }
|
||||
.wait-head { font-family: 'Songti TC','LiSong Pro',PMingLiU,serif; font-size: 16px; letter-spacing: .2em; color: rgba(var(--ink-rgb),.6); margin-bottom: 10px; text-align: center; }
|
||||
.wait-none { font-family: 'Songti TC','LiSong Pro',PMingLiU,serif; font-size: 20px; color: var(--ok); letter-spacing: .08em; text-align: center; }
|
||||
.wait-item { display: flex; align-items: center; gap: 12px; padding: 12px 14px; margin-top: 8px; border-radius: 10px; background: rgba(var(--amber-rgb),.1); border: 1px solid rgba(var(--amber-rgb),.3); font-size: 16px; line-height: 1.5; }
|
||||
.wait-item .dm { color: var(--amber); font-size: 17px; flex: none; }
|
||||
.subhead { display: flex; justify-content: space-between; align-items: baseline; margin: 24px 0 10px; }
|
||||
.subhead .t { font-family: 'Songti TC','LiSong Pro',PMingLiU,serif; font-size: 16px; letter-spacing: .2em; color: rgba(237,228,211,.6); }
|
||||
.subhead .m { font-size: 13px; color: rgba(237,228,211,.4); }
|
||||
.subhead .t { font-family: 'Songti TC','LiSong Pro',PMingLiU,serif; font-size: 16px; letter-spacing: .2em; color: rgba(var(--ink-rgb),.6); }
|
||||
.subhead .m { font-size: 13px; color: rgba(var(--ink-rgb),.4); }
|
||||
ul.route { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
|
||||
ul.route li { display: flex; align-items: flex-start; gap: 12px; padding: 13px 16px; border-radius: 11px; background: rgba(237,228,211,.045); border: 1px solid transparent; font-size: 16px; line-height: 1.4; }
|
||||
ul.route li.doing { background: rgba(232,180,90,.09); border-color: rgba(232,180,90,.3); }
|
||||
ul.route li { display: flex; align-items: flex-start; gap: 12px; padding: 13px 16px; border-radius: 11px; background: rgba(var(--ink-rgb),.045); border: 1px solid transparent; font-size: 16px; line-height: 1.4; }
|
||||
ul.route li.doing { background: rgba(var(--amber-rgb),.09); border-color: rgba(var(--amber-rgb),.3); }
|
||||
ul.route li .ic { flex: none; font-size: 15px; margin-top: 2px; }
|
||||
ul.route li.done { color: rgba(237,228,211,.65); }
|
||||
ul.route li.done .ic { color: #7fe0a8; }
|
||||
ul.route li.doing .ic { color: #e8b45a; }
|
||||
ul.route li.todo { color: rgba(237,228,211,.6); }
|
||||
ul.route li.todo .ic { color: rgba(237,228,211,.35); }
|
||||
ul.route li.blocked .ic { color: #e58575; }
|
||||
.muted { color: rgba(237,228,211,.45); font-size: 14px; }
|
||||
.err { color: #e58575; font-size: 14px; }
|
||||
.stamp { margin: 16px 0 8px; text-align: center; font-size: 12.5px; color: rgba(237,228,211,.35); line-height: 1.8; }
|
||||
.enter { display: block; text-align: center; font-size: 13.5px; color: rgba(232,180,90,.75); text-decoration: none; margin-top: 6px; }
|
||||
ul.route li.done { color: rgba(var(--ink-rgb),.65); }
|
||||
ul.route li.done .ic { color: var(--ok); }
|
||||
ul.route li.doing .ic { color: var(--amber); }
|
||||
ul.route li.todo { color: rgba(var(--ink-rgb),.6); }
|
||||
ul.route li.todo .ic { color: rgba(var(--ink-rgb),.35); }
|
||||
ul.route li.blocked .ic { color: var(--err); }
|
||||
.muted { color: rgba(var(--ink-rgb),.45); font-size: 14px; }
|
||||
.err { color: var(--err); font-size: 14px; }
|
||||
.stamp { margin: 16px 0 8px; text-align: center; font-size: 12.5px; color: rgba(var(--ink-rgb),.35); line-height: 1.8; }
|
||||
.enter { display: block; text-align: center; font-size: 13.5px; color: rgba(var(--amber-rgb),.75); text-decoration: none; margin-top: 6px; }
|
||||
.theme-btn { flex: none; margin-left: 12px; width: 34px; height: 34px; border-radius: 50%; border: 1px solid rgba(var(--ink-rgb),.25); background: none; color: rgba(var(--ink-rgb),.65); font-size: 16px; cursor: pointer; line-height: 1; align-self: center; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<div class="pagehead">
|
||||
<div class="title serif">Mira<small> 駕駛艙</small></div>
|
||||
<div class="date serif" id="date-str"></div>
|
||||
<div style="display:flex;align-items:baseline">
|
||||
<div class="date serif" id="date-str"></div>
|
||||
<button class="theme-btn" id="theme-btn" title="切換深/淺色">☾</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="orb-row">
|
||||
<div class="orb" id="orb" style="background:radial-gradient(circle at 36% 30%,#8fe8b4,#3fbe78 55%,#22754a 100%)"><span id="orb-char">…</span></div>
|
||||
@@ -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 = '<span class="err">' + esc(e.message) + '</span>';
|
||||
$('orb-sub').innerHTML = '<span class="err">' + esc(friendlyErr(e)) + '・每 60 秒自動重試</span>';
|
||||
}
|
||||
}
|
||||
load();
|
||||
|
||||
@@ -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 {
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Mira Console</title>
|
||||
<script>
|
||||
// 主題預載(防閃色):預設淺色(leo 2026-07-04 明示),選擇存 localStorage
|
||||
document.documentElement.setAttribute('data-theme', (function () {
|
||||
try { return localStorage.getItem('arcrun_console_theme') === 'dark' ? 'dark' : 'light'; } catch (e) { return 'light'; }
|
||||
})());
|
||||
</script>
|
||||
<style>
|
||||
* { box-sizing: border-box; }
|
||||
html, body { margin: 0; background: repeating-linear-gradient(0deg,#191410 0px,#191410 3px,#1b1611 3px,#1b1611 4px); color: #ede4d3;
|
||||
/* ── 主題色板(一套結構兩份色值):預設淺色=宣紙米白紙紋+墨色文字;深色=定稿紙感暖黑 2a 原值不動 ── */
|
||||
:root {
|
||||
--paper-a: #f4eddc; --paper-b: #f1e9d6; /* 宣紙米白紙紋 */
|
||||
--ink: #2f2a20; --ink-rgb: 30,24,14; /* 墨色文字 */
|
||||
--amber: #8a5f1e; --amber-rgb: 138,95,30; /* 琥珀沿用、調深到淺底可讀 */
|
||||
--ok: #1d7a48; --ok-rgb: 29,122,72; --ok-soft: #1d6b40;
|
||||
--err: #b03a26; --err-rgb: 176,58,38;
|
||||
--well: rgba(30,24,14,.06); --well2: rgba(30,24,14,.08);
|
||||
--bar-bg: #efe7d3; --track: rgba(30,24,14,.12); --knob: #ffffff;
|
||||
}
|
||||
:root[data-theme="dark"] {
|
||||
--paper-a: #191410; --paper-b: #1b1611;
|
||||
--ink: #ede4d3; --ink-rgb: 237,228,211;
|
||||
--amber: #e8b45a; --amber-rgb: 232,180,90;
|
||||
--ok: #7fe0a8; --ok-rgb: 63,190,120; --ok-soft: #a8e8c4;
|
||||
--err: #e58575; --err-rgb: 217,95,76;
|
||||
--well: rgba(0,0,0,.25); --well2: rgba(0,0,0,.3);
|
||||
--bar-bg: #17120e; --track: rgba(255,255,255,.08); --knob: #ede4d3;
|
||||
}
|
||||
html, body { margin: 0; background: repeating-linear-gradient(0deg,var(--paper-a) 0px,var(--paper-a) 3px,var(--paper-b) 3px,var(--paper-b) 4px); color: var(--ink);
|
||||
font-family: -apple-system, "PingFang TC", "Microsoft JhengHei", system-ui, sans-serif; font-size: 16px; -webkit-font-smoothing: antialiased; }
|
||||
input, textarea, button { font-family: inherit; }
|
||||
::placeholder { color: rgba(237,228,211,.35); }
|
||||
input:focus, textarea:focus { outline: 2px solid rgba(232,180,90,.5); outline-offset: 1px; }
|
||||
a { color: #e8b45a; }
|
||||
::placeholder { color: rgba(var(--ink-rgb),.35); }
|
||||
input:focus, textarea:focus { outline: 2px solid rgba(var(--amber-rgb),.5); outline-offset: 1px; }
|
||||
a { color: var(--amber); }
|
||||
.serif { font-family: 'Songti TC','LiSong Pro',PMingLiU,serif; }
|
||||
.mono { font-family: ui-monospace, Menlo, monospace; }
|
||||
.muted { color: rgba(237,228,211,.55); }
|
||||
.dim { color: rgba(237,228,211,.4); }
|
||||
.err { color: #e58575; }
|
||||
.ok { color: #7fe0a8; }
|
||||
@keyframes breatheGreen { 0%,100% { box-shadow: 0 0 24px 6px rgba(63,190,120,.35); } 50% { box-shadow: 0 0 42px 14px rgba(63,190,120,.55); } }
|
||||
@keyframes breatheAmber { 0%,100% { box-shadow: 0 0 24px 6px rgba(232,180,90,.35); } 50% { box-shadow: 0 0 42px 14px rgba(232,180,90,.6); } }
|
||||
@keyframes breatheRed { 0%,100% { box-shadow: 0 0 24px 6px rgba(217,95,76,.4); } 50% { box-shadow: 0 0 44px 16px rgba(217,95,76,.65); } }
|
||||
.muted { color: rgba(var(--ink-rgb),.55); }
|
||||
.dim { color: rgba(var(--ink-rgb),.4); }
|
||||
.err { color: var(--err); }
|
||||
.ok { color: var(--ok); }
|
||||
@keyframes breatheGreen { 0%,100% { box-shadow: 0 0 24px 6px rgba(var(--ok-rgb),.35); } 50% { box-shadow: 0 0 42px 14px rgba(var(--ok-rgb),.55); } }
|
||||
@keyframes breatheAmber { 0%,100% { box-shadow: 0 0 24px 6px rgba(var(--amber-rgb),.35); } 50% { box-shadow: 0 0 42px 14px rgba(var(--amber-rgb),.6); } }
|
||||
@keyframes breatheRed { 0%,100% { box-shadow: 0 0 24px 6px rgba(var(--err-rgb),.4); } 50% { box-shadow: 0 0 44px 16px rgba(var(--err-rgb),.65); } }
|
||||
|
||||
/* ── auth 全屏 ── */
|
||||
.authwrap { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 24px; }
|
||||
.authbox { width: 100%; max-width: 400px; display: flex; flex-direction: column; gap: 24px; text-align: center; }
|
||||
.brand { font-family: 'Songti TC','LiSong Pro',PMingLiU,serif; font-size: 44px; letter-spacing: .24em; color: #e8b45a; }
|
||||
.brand-sub { margin-top: 10px; font-size: 15px; color: rgba(237,228,211,.55); letter-spacing: .1em; }
|
||||
.brand { font-family: 'Songti TC','LiSong Pro',PMingLiU,serif; font-size: 44px; letter-spacing: .24em; color: var(--amber); }
|
||||
.brand-sub { margin-top: 10px; font-size: 15px; color: rgba(var(--ink-rgb),.55); letter-spacing: .1em; }
|
||||
|
||||
/* ── 版面骨架 ── */
|
||||
#shell { display: none; min-height: 100vh; }
|
||||
@@ -75,46 +109,48 @@ function renderConsoleHtml(registryBase: string): string {
|
||||
.page { max-width: 980px; margin: 0 auto; padding: 0 20px 40px; }
|
||||
.page.narrow { max-width: 760px; }
|
||||
.page.wide { max-width: 1080px; }
|
||||
.pagehead { padding: 22px 2px 14px; border-bottom: 2px solid rgba(232,180,90,.4); display: flex; justify-content: space-between; align-items: baseline; gap: 10px; }
|
||||
.pagehead { padding: 22px 2px 14px; border-bottom: 2px solid rgba(var(--amber-rgb),.4); display: flex; justify-content: space-between; align-items: baseline; gap: 10px; }
|
||||
.pagehead .t { font-family: 'Songti TC','LiSong Pro',PMingLiU,serif; font-size: 23px; letter-spacing: .2em; white-space: nowrap; }
|
||||
.pagehead .m { font-size: 13.5px; color: rgba(237,228,211,.45); }
|
||||
#tabbar { position: fixed; left: 0; right: 0; bottom: 0; background: #17120e; border-top: 2px solid rgba(232,180,90,.4); display: none; z-index: 50; padding-bottom: env(safe-area-inset-bottom); }
|
||||
.pagehead .m { font-size: 13.5px; color: rgba(var(--ink-rgb),.45); }
|
||||
#tabbar { position: fixed; left: 0; right: 0; bottom: 0; background: var(--bar-bg); border-top: 2px solid rgba(var(--amber-rgb),.4); display: none; z-index: 50; padding-bottom: env(safe-area-inset-bottom); }
|
||||
#tabbar.on { display: flex; }
|
||||
#tabbar .tab { flex: 1; text-align: center; padding: 15px 0 13px; font-size: 14.5px; cursor: pointer; min-height: 44px; color: rgba(237,228,211,.5); border-top: 2px solid transparent; margin-top: -2px; }
|
||||
#tabbar .tab.on { color: #e8b45a; font-weight: 600; border-top-color: #e8b45a; }
|
||||
#tabbar .tab { flex: 1; text-align: center; padding: 15px 0 13px; font-size: 14.5px; cursor: pointer; min-height: 44px; color: rgba(var(--ink-rgb),.5); border-top: 2px solid transparent; margin-top: -2px; }
|
||||
#tabbar .tab.on { color: var(--amber); font-weight: 600; border-top-color: var(--amber); }
|
||||
@media (min-width: 1024px) {
|
||||
#tabbar.on { display: none; }
|
||||
main { padding-bottom: 20px; }
|
||||
#sidenav { display: flex; width: 216px; flex: none; position: sticky; top: 0; height: 100vh; border-right: 2px solid rgba(232,180,90,.35); padding: 28px 0; flex-direction: column; gap: 4px; }
|
||||
#sidenav .logo { font-family: 'Songti TC','LiSong Pro',PMingLiU,serif; font-size: 26px; letter-spacing: .22em; color: #e8b45a; padding: 0 24px 20px; }
|
||||
#sidenav .nav { padding: 13px 24px; font-size: 16px; cursor: pointer; letter-spacing: .08em; color: rgba(237,228,211,.6); border-right: 2px solid transparent; }
|
||||
#sidenav .nav.on { color: #e8b45a; background: rgba(232,180,90,.08); border-right-color: #e8b45a; font-family: 'Songti TC','LiSong Pro',PMingLiU,serif; font-weight: 600; }
|
||||
#sidenav .foot { margin-top: auto; padding: 0 24px; font-size: 12.5px; color: rgba(237,228,211,.35); line-height: 1.7; }
|
||||
#sidenav { display: flex; width: 216px; flex: none; position: sticky; top: 0; height: 100vh; border-right: 2px solid rgba(var(--amber-rgb),.35); padding: 28px 0; flex-direction: column; gap: 4px; }
|
||||
#sidenav .logo { font-family: 'Songti TC','LiSong Pro',PMingLiU,serif; font-size: 26px; letter-spacing: .22em; color: var(--amber); padding: 0 24px 20px; }
|
||||
#sidenav .nav { padding: 13px 24px; font-size: 16px; cursor: pointer; letter-spacing: .08em; color: rgba(var(--ink-rgb),.6); border-right: 2px solid transparent; }
|
||||
#sidenav .nav.on { color: var(--amber); background: rgba(var(--amber-rgb),.08); border-right-color: var(--amber); font-family: 'Songti TC','LiSong Pro',PMingLiU,serif; font-weight: 600; }
|
||||
#sidenav .foot { margin-top: auto; padding: 0 24px; font-size: 12.5px; color: rgba(var(--ink-rgb),.35); line-height: 1.7; }
|
||||
}
|
||||
.view { display: none; }
|
||||
.view.on { display: block; }
|
||||
/* 登入/首次設定=flex 置中(.view.on 的 display:block 會蓋掉 .authwrap 的 flex → 內容靠左,leo 實測回饋修正) */
|
||||
.authwrap.view.on { display: flex; }
|
||||
|
||||
/* ── 元件 ── */
|
||||
.btn { padding: 13px; font-size: 16px; font-weight: 600; border-radius: 10px; border: none; background: linear-gradient(90deg,#b98330,#e8b45a); color: #241804; cursor: pointer; }
|
||||
.btn:disabled { opacity: .45; cursor: default; }
|
||||
.btn2 { padding: 11px 16px; font-size: 15px; border-radius: 10px; border: 1px solid rgba(232,180,90,.6); background: none; color: #e8b45a; cursor: pointer; }
|
||||
.btn3 { padding: 11px 16px; font-size: 15px; border-radius: 10px; border: 1px solid rgba(237,228,211,.25); background: none; color: rgba(237,228,211,.7); cursor: pointer; }
|
||||
.btn-danger { padding: 12px 18px; font-size: 15px; border-radius: 10px; border: 1px solid rgba(217,95,76,.5); background: none; color: #e58575; cursor: pointer; }
|
||||
.btn2 { padding: 11px 16px; font-size: 15px; border-radius: 10px; border: 1px solid rgba(var(--amber-rgb),.6); background: none; color: var(--amber); cursor: pointer; }
|
||||
.btn3 { padding: 11px 16px; font-size: 15px; border-radius: 10px; border: 1px solid rgba(var(--ink-rgb),.25); background: none; color: rgba(var(--ink-rgb),.7); cursor: pointer; }
|
||||
.btn-danger { padding: 12px 18px; font-size: 15px; border-radius: 10px; border: 1px solid rgba(var(--err-rgb),.5); background: none; color: var(--err); cursor: pointer; }
|
||||
.btn-danger:disabled { opacity: .4; cursor: default; }
|
||||
.txt { width: 100%; padding: 13px 15px; font-size: 16px; border-radius: 10px; border: 1px solid rgba(237,228,211,.2); background: rgba(237,228,211,.05); color: #ede4d3; }
|
||||
.txt { width: 100%; padding: 13px 15px; font-size: 16px; border-radius: 10px; border: 1px solid rgba(var(--ink-rgb),.2); background: rgba(var(--ink-rgb),.05); color: var(--ink); }
|
||||
textarea.txt { resize: vertical; font-family: ui-monospace, Menlo, monospace; font-size: 14.5px; }
|
||||
.panel { padding: 18px; border-radius: 13px; background: rgba(237,228,211,.04); border: 1px solid rgba(237,228,211,.12); }
|
||||
.card-item { border-radius: 13px; background: rgba(237,228,211,.045); border: 1px solid rgba(237,228,211,.1); }
|
||||
.card-item.hl { border-color: rgba(232,180,90,.3); }
|
||||
.tag { display: inline-block; padding: 3px 9px; border-radius: 5px; background: rgba(232,180,90,.12); color: #e8b45a; font-size: 12.5px; }
|
||||
.tag.green { background: rgba(63,190,120,.12); color: #7fe0a8; }
|
||||
.chip { flex: none; padding: 9px 16px; font-size: 14.5px; border-radius: 999px; cursor: pointer; border: 1px solid rgba(237,228,211,.18); background: none; color: rgba(237,228,211,.6); }
|
||||
.chip.on { border-color: rgba(232,180,90,.6); background: rgba(232,180,90,.14); color: #e8b45a; font-weight: 600; }
|
||||
.panel { padding: 18px; border-radius: 13px; background: rgba(var(--ink-rgb),.04); border: 1px solid rgba(var(--ink-rgb),.12); }
|
||||
.card-item { border-radius: 13px; background: rgba(var(--ink-rgb),.045); border: 1px solid rgba(var(--ink-rgb),.1); }
|
||||
.card-item.hl { border-color: rgba(var(--amber-rgb),.3); }
|
||||
.tag { display: inline-block; padding: 3px 9px; border-radius: 5px; background: rgba(var(--amber-rgb),.12); color: var(--amber); font-size: 12.5px; }
|
||||
.tag.green { background: rgba(var(--ok-rgb),.12); color: var(--ok); }
|
||||
.chip { flex: none; padding: 9px 16px; font-size: 14.5px; border-radius: 999px; cursor: pointer; border: 1px solid rgba(var(--ink-rgb),.18); background: none; color: rgba(var(--ink-rgb),.6); }
|
||||
.chip.on { border-color: rgba(var(--amber-rgb),.6); background: rgba(var(--amber-rgb),.14); color: var(--amber); font-weight: 600; }
|
||||
.hscroll { display: flex; gap: 8px; overflow-x: auto; padding-bottom: 4px; }
|
||||
.subhead { font-family: 'Songti TC','LiSong Pro',PMingLiU,serif; font-size: 16px; letter-spacing: .2em; color: rgba(237,228,211,.6); }
|
||||
.honest { padding: 22px; border-radius: 13px; border: 1px dashed rgba(232,180,90,.4); background: rgba(232,180,90,.06); text-align: center; }
|
||||
.honest .h { font-family: 'Songti TC','LiSong Pro',PMingLiU,serif; font-size: 19px; color: #e8b45a; margin-bottom: 8px; }
|
||||
.honest .b { font-size: 15px; color: rgba(237,228,211,.6); line-height: 1.7; }
|
||||
.subhead { font-family: 'Songti TC','LiSong Pro',PMingLiU,serif; font-size: 16px; letter-spacing: .2em; color: rgba(var(--ink-rgb),.6); }
|
||||
.honest { padding: 22px; border-radius: 13px; border: 1px dashed rgba(var(--amber-rgb),.4); background: rgba(var(--amber-rgb),.06); text-align: center; }
|
||||
.honest .h { font-family: 'Songti TC','LiSong Pro',PMingLiU,serif; font-size: 19px; color: var(--amber); margin-bottom: 8px; }
|
||||
.honest .b { font-size: 15px; color: rgba(var(--ink-rgb),.6); line-height: 1.7; }
|
||||
#toast { position: fixed; left: 50%; bottom: 110px; transform: translateX(-50%); z-index: 99; padding: 13px 22px; border-radius: 11px; background: #e8b45a; color: #241804; font-size: 15.5px; font-weight: 600; box-shadow: 0 6px 24px rgba(0,0,0,.5); white-space: nowrap; display: none; }
|
||||
|
||||
/* ── 駕駛艙 ── */
|
||||
@@ -122,44 +158,44 @@ function renderConsoleHtml(registryBase: string): string {
|
||||
.orb { width: 84px; height: 84px; border-radius: 50%; flex: none; display: grid; place-items: center; }
|
||||
.orb span { font-family: 'Songti TC','LiSong Pro',PMingLiU,serif; font-size: 30px; font-weight: 600; color: rgba(10,20,14,.85); text-shadow: 0 1px 0 rgba(255,255,255,.25); }
|
||||
.orb-title { font-family: 'Songti TC','LiSong Pro',PMingLiU,serif; font-size: 23px; font-weight: 600; }
|
||||
.orb-sub { margin-top: 5px; font-size: 15px; color: rgba(237,228,211,.6); line-height: 1.55; }
|
||||
.orb-sub { margin-top: 5px; font-size: 15px; color: rgba(var(--ink-rgb),.6); line-height: 1.55; }
|
||||
.cockgrid { display: grid; grid-template-columns: 1fr; gap: 0 40px; }
|
||||
@media (min-width: 1024px) { .cockgrid { grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); } }
|
||||
.bricks { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
||||
.brick { padding: 16px; border-radius: 12px; }
|
||||
.brick.amber { background: rgba(232,180,90,.07); border: 1px solid rgba(232,180,90,.22); }
|
||||
.brick.plain { background: rgba(237,228,211,.04); border: 1px solid rgba(237,228,211,.14); cursor: pointer; }
|
||||
.brick .lbl { font-size: 13.5px; color: rgba(237,228,211,.55); margin-bottom: 6px; }
|
||||
.brick .num { font-family: ui-monospace, Menlo, monospace; font-size: 26px; color: #e8b45a; }
|
||||
.brick .num small { font-size: 15px; color: rgba(237,228,211,.5); }
|
||||
.bar { margin-top: 10px; height: 6px; border-radius: 3px; background: rgba(255,255,255,.08); }
|
||||
.brick.amber { background: rgba(var(--amber-rgb),.07); border: 1px solid rgba(var(--amber-rgb),.22); }
|
||||
.brick.plain { background: rgba(var(--ink-rgb),.04); border: 1px solid rgba(var(--ink-rgb),.14); cursor: pointer; }
|
||||
.brick .lbl { font-size: 13.5px; color: rgba(var(--ink-rgb),.55); margin-bottom: 6px; }
|
||||
.brick .num { font-family: ui-monospace, Menlo, monospace; font-size: 26px; color: var(--amber); }
|
||||
.brick .num small { font-size: 15px; color: rgba(var(--ink-rgb),.5); }
|
||||
.bar { margin-top: 10px; height: 6px; border-radius: 3px; background: var(--track); }
|
||||
.bar > i { display: block; height: 100%; border-radius: 3px; background: linear-gradient(90deg,#b98330,#e8b45a); transition: width .6s; }
|
||||
.wait-box { margin-top: 14px; padding: 20px; border-radius: 12px; border: 1px dashed rgba(63,190,120,.3); background: rgba(63,190,120,.05); }
|
||||
.wait-box.has { border-color: rgba(232,180,90,.45); background: rgba(232,180,90,.05); }
|
||||
.wait-head { font-family: 'Songti TC','LiSong Pro',PMingLiU,serif; font-size: 16px; letter-spacing: .2em; color: rgba(237,228,211,.6); margin-bottom: 10px; text-align: center; }
|
||||
.wait-none { font-family: 'Songti TC','LiSong Pro',PMingLiU,serif; font-size: 20px; color: #7fe0a8; letter-spacing: .08em; text-align: center; }
|
||||
.wait-item { display: flex; align-items: center; gap: 12px; padding: 12px 14px; margin-top: 8px; border-radius: 10px; background: rgba(232,180,90,.1); border: 1px solid rgba(232,180,90,.3); font-size: 16px; line-height: 1.5; }
|
||||
.wait-item .dm { color: #e8b45a; font-size: 17px; flex: none; }
|
||||
.wait-box { margin-top: 14px; padding: 20px; border-radius: 12px; border: 1px dashed rgba(var(--ok-rgb),.3); background: rgba(var(--ok-rgb),.05); }
|
||||
.wait-box.has { border-color: rgba(var(--amber-rgb),.45); background: rgba(var(--amber-rgb),.05); }
|
||||
.wait-head { font-family: 'Songti TC','LiSong Pro',PMingLiU,serif; font-size: 16px; letter-spacing: .2em; color: rgba(var(--ink-rgb),.6); margin-bottom: 10px; text-align: center; }
|
||||
.wait-none { font-family: 'Songti TC','LiSong Pro',PMingLiU,serif; font-size: 20px; color: var(--ok); letter-spacing: .08em; text-align: center; }
|
||||
.wait-item { display: flex; align-items: center; gap: 12px; padding: 12px 14px; margin-top: 8px; border-radius: 10px; background: rgba(var(--amber-rgb),.1); border: 1px solid rgba(var(--amber-rgb),.3); font-size: 16px; line-height: 1.5; }
|
||||
.wait-item .dm { color: var(--amber); font-size: 17px; flex: none; }
|
||||
ul.route { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
|
||||
ul.route li { display: flex; align-items: flex-start; gap: 12px; padding: 13px 16px; border-radius: 11px; background: rgba(237,228,211,.045); border: 1px solid transparent; font-size: 16px; line-height: 1.4; }
|
||||
ul.route li.doing { background: rgba(232,180,90,.09); border-color: rgba(232,180,90,.3); }
|
||||
ul.route li { display: flex; align-items: flex-start; gap: 12px; padding: 13px 16px; border-radius: 11px; background: rgba(var(--ink-rgb),.045); border: 1px solid transparent; font-size: 16px; line-height: 1.4; }
|
||||
ul.route li.doing { background: rgba(var(--amber-rgb),.09); border-color: rgba(var(--amber-rgb),.3); }
|
||||
ul.route li .ic { flex: none; font-size: 15px; margin-top: 2px; }
|
||||
ul.route li.done { color: rgba(237,228,211,.65); } ul.route li.done .ic { color: #7fe0a8; }
|
||||
ul.route li.doing .ic { color: #e8b45a; }
|
||||
ul.route li.todo { color: rgba(237,228,211,.6); } ul.route li.todo .ic { color: rgba(237,228,211,.35); }
|
||||
ul.route li.blocked .ic { color: #e58575; }
|
||||
ul.route li.done { color: rgba(var(--ink-rgb),.65); } ul.route li.done .ic { color: var(--ok); }
|
||||
ul.route li.doing .ic { color: var(--amber); }
|
||||
ul.route li.todo { color: rgba(var(--ink-rgb),.6); } ul.route li.todo .ic { color: rgba(var(--ink-rgb),.35); }
|
||||
ul.route li.blocked .ic { color: var(--err); }
|
||||
|
||||
/* ── 搜尋 ── */
|
||||
.searchrow { display: flex; gap: 10px; align-items: stretch; }
|
||||
.searchrow input { flex: 1; min-width: 0; padding: 16px 18px; font-size: 18px; border-radius: 13px; border: 1.5px solid rgba(232,180,90,.35); background: rgba(237,228,211,.05); color: #ede4d3; }
|
||||
.sembtn { flex: none; padding: 0 20px; font-size: 16px; border-radius: 13px; cursor: pointer; letter-spacing: .1em; border: 1.5px solid rgba(237,228,211,.2); background: none; color: rgba(237,228,211,.55); }
|
||||
.sembtn.on { border-color: #e8b45a; background: rgba(232,180,90,.15); color: #e8b45a; font-weight: 600; }
|
||||
.searchrow input { flex: 1; min-width: 0; padding: 16px 18px; font-size: 18px; border-radius: 13px; border: 1.5px solid rgba(var(--amber-rgb),.35); background: rgba(var(--ink-rgb),.05); color: var(--ink); }
|
||||
.sembtn { flex: none; padding: 0 20px; font-size: 16px; border-radius: 13px; cursor: pointer; letter-spacing: .1em; border: 1.5px solid rgba(var(--ink-rgb),.2); background: none; color: rgba(var(--ink-rgb),.55); }
|
||||
.sembtn.on { border-color: var(--amber); background: rgba(var(--amber-rgb),.15); color: var(--amber); font-weight: 600; }
|
||||
.kgrid { display: grid; grid-template-columns: 1fr; gap: 12px; padding-bottom: 8px; }
|
||||
@media (min-width: 700px) { .kgrid { grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); } }
|
||||
.kcard { padding: 18px; border-radius: 13px; background: rgba(237,228,211,.045); border: 1px solid rgba(237,228,211,.1); cursor: pointer; display: flex; flex-direction: column; gap: 9px; }
|
||||
.kcard:hover { border-color: rgba(232,180,90,.45); background: rgba(232,180,90,.06); }
|
||||
.kcard { padding: 18px; border-radius: 13px; background: rgba(var(--ink-rgb),.045); border: 1px solid rgba(var(--ink-rgb),.1); cursor: pointer; display: flex; flex-direction: column; gap: 9px; }
|
||||
.kcard:hover { border-color: rgba(var(--amber-rgb),.45); background: rgba(var(--amber-rgb),.06); }
|
||||
.kcard .kt { font-family: 'Songti TC','LiSong Pro',PMingLiU,serif; font-size: 18.5px; font-weight: 600; line-height: 1.45; word-break: break-word; }
|
||||
.kcard .ks { font-size: 15px; color: rgba(237,228,211,.65); line-height: 1.65; word-break: break-word; }
|
||||
.kcard .ks { font-size: 15px; color: rgba(var(--ink-rgb),.65); line-height: 1.65; word-break: break-word; }
|
||||
.kcard .km { display: flex; gap: 10px; align-items: center; margin-top: auto; font-size: 13px; flex-wrap: wrap; }
|
||||
|
||||
/* ── 卡片詳頁 ── */
|
||||
@@ -168,42 +204,42 @@ function renderConsoleHtml(registryBase: string): string {
|
||||
.cardside { flex: 1 1 360px; min-width: 0; }
|
||||
.cardtitle { font-family: 'Songti TC','LiSong Pro',PMingLiU,serif; font-size: 28px; font-weight: 600; line-height: 1.4; word-break: break-word; }
|
||||
.md { margin-top: 18px; }
|
||||
.md p { font-size: 16.5px; line-height: 1.85; color: rgba(237,228,211,.82); margin: 8px 0; word-break: break-word; }
|
||||
.md h1, .md h2, .md h3, .md h4 { font-family: 'Songti TC','LiSong Pro',PMingLiU,serif; font-weight: 600; color: #e8b45a; margin: 18px 0 6px; line-height: 1.4; }
|
||||
.md p { font-size: 16.5px; line-height: 1.85; color: rgba(var(--ink-rgb),.82); margin: 8px 0; word-break: break-word; }
|
||||
.md h1, .md h2, .md h3, .md h4 { font-family: 'Songti TC','LiSong Pro',PMingLiU,serif; font-weight: 600; color: var(--amber); margin: 18px 0 6px; line-height: 1.4; }
|
||||
.md h1 { font-size: 22px; } .md h2 { font-size: 20px; } .md h3 { font-size: 18px; } .md h4 { font-size: 17px; }
|
||||
.md ul, .md ol { margin: 8px 0; padding-left: 24px; }
|
||||
.md li { font-size: 16.5px; line-height: 1.8; color: rgba(237,228,211,.82); margin: 4px 0; word-break: break-word; }
|
||||
.md blockquote { margin: 10px 0; padding: 12px 18px; border-left: 3px solid rgba(232,180,90,.5); font-family: 'Songti TC','LiSong Pro',PMingLiU,serif; font-size: 17px; line-height: 1.8; color: rgba(237,228,211,.7); }
|
||||
.md code { font-family: ui-monospace, Menlo, monospace; font-size: 14.5px; background: rgba(0,0,0,.3); border-radius: 4px; padding: 1px 6px; word-break: break-all; }
|
||||
.md pre { background: rgba(0,0,0,.3); border: 1px solid rgba(237,228,211,.1); border-radius: 10px; padding: 14px; overflow-x: auto; }
|
||||
.md li { font-size: 16.5px; line-height: 1.8; color: rgba(var(--ink-rgb),.82); margin: 4px 0; word-break: break-word; }
|
||||
.md blockquote { margin: 10px 0; padding: 12px 18px; border-left: 3px solid rgba(var(--amber-rgb),.5); font-family: 'Songti TC','LiSong Pro',PMingLiU,serif; font-size: 17px; line-height: 1.8; color: rgba(var(--ink-rgb),.7); }
|
||||
.md code { font-family: ui-monospace, Menlo, monospace; font-size: 14.5px; background: var(--well2); border-radius: 4px; padding: 1px 6px; word-break: break-all; }
|
||||
.md pre { background: var(--well2); border: 1px solid rgba(var(--ink-rgb),.1); border-radius: 10px; padding: 14px; overflow-x: auto; }
|
||||
.md pre code { background: none; padding: 0; }
|
||||
.srcline { margin-top: 24px; display: flex; align-items: center; gap: 12px; padding: 14px 16px; border-radius: 11px; background: rgba(237,228,211,.045); border: 1px solid rgba(237,228,211,.12); }
|
||||
.graphbox { position: relative; height: 340px; border-radius: 14px; background: rgba(0,0,0,.22); border: 1px solid rgba(237,228,211,.1); overflow: hidden; }
|
||||
.gnode { position: absolute; transform: translate(-50%,-50%); max-width: 96px; padding: 8px 11px; border-radius: 999px; text-align: center; font-size: 12.5px; line-height: 1.3; cursor: pointer; word-break: break-word; background: rgba(63,190,120,.1); border: 1px solid rgba(63,190,120,.35); color: #a8e8c4; }
|
||||
.gcenter { position: absolute; left: 50%; top: 50%; transform: translate(-50%,-50%); width: 104px; height: 104px; border-radius: 50%; background: radial-gradient(circle at 36% 30%,rgba(242,209,148,.95),rgba(232,180,90,.9) 55%,rgba(138,95,30,.95)); display: grid; place-items: center; text-align: center; padding: 8px; box-shadow: 0 0 30px rgba(232,180,90,.3); }
|
||||
.srcline { margin-top: 24px; display: flex; align-items: center; gap: 12px; padding: 14px 16px; border-radius: 11px; background: rgba(var(--ink-rgb),.045); border: 1px solid rgba(var(--ink-rgb),.12); }
|
||||
.graphbox { position: relative; height: 340px; border-radius: 14px; background: var(--well); border: 1px solid rgba(var(--ink-rgb),.1); overflow: hidden; }
|
||||
.gnode { position: absolute; transform: translate(-50%,-50%); max-width: 96px; padding: 8px 11px; border-radius: 999px; text-align: center; font-size: 12.5px; line-height: 1.3; cursor: pointer; word-break: break-word; background: rgba(var(--ok-rgb),.1); border: 1px solid rgba(var(--ok-rgb),.35); color: var(--ok-soft); }
|
||||
.gcenter { position: absolute; left: 50%; top: 50%; transform: translate(-50%,-50%); width: 104px; height: 104px; border-radius: 50%; background: radial-gradient(circle at 36% 30%,rgba(242,209,148,.95),rgba(232,180,90,.9) 55%,rgba(138,95,30,.95)); display: grid; place-items: center; text-align: center; padding: 8px; box-shadow: 0 0 30px rgba(var(--amber-rgb),.3); }
|
||||
.gcenter span { font-family: 'Songti TC','LiSong Pro',PMingLiU,serif; font-size: 13.5px; font-weight: 600; line-height: 1.35; color: #241804; word-break: break-word; }
|
||||
.nbrow { display: flex; align-items: center; gap: 11px; padding: 12px 14px; border-radius: 10px; cursor: pointer; background: rgba(237,228,211,.045); border: 1px solid rgba(237,228,211,.1); font-size: 15.5px; }
|
||||
.nbrow { display: flex; align-items: center; gap: 11px; padding: 12px 14px; border-radius: 10px; cursor: pointer; background: rgba(var(--ink-rgb),.045); border: 1px solid rgba(var(--ink-rgb),.1); font-size: 15.5px; }
|
||||
|
||||
/* ── 工作流 / 憑證 / 收件匣 清單 ── */
|
||||
.listcol { display: flex; flex-direction: column; gap: 10px; padding: 16px 0 8px; }
|
||||
.rowhead { display: flex; align-items: center; gap: 12px; padding: 16px 18px; cursor: pointer; }
|
||||
.rowbody { padding: 0 18px 18px; border-top: 1px solid rgba(237,228,211,.1); }
|
||||
.pinbtn { flex: none; width: 34px; height: 34px; border-radius: 8px; border: none; cursor: pointer; font-size: 17px; background: rgba(237,228,211,.06); color: rgba(237,228,211,.4); }
|
||||
.pinbtn.on { background: rgba(232,180,90,.15); color: #e8b45a; }
|
||||
.urlbox { display: flex; align-items: center; gap: 10px; padding: 12px 14px; border-radius: 10px; background: rgba(0,0,0,.25); border: 1px solid rgba(237,228,211,.1); min-width: 0; }
|
||||
.urlbox .u { font-family: ui-monospace, Menlo, monospace; font-size: 13.5px; color: #e8b45a; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
|
||||
.avatar { width: 38px; height: 38px; border-radius: 10px; background: rgba(232,180,90,.1); border: 1px solid rgba(232,180,90,.25); display: grid; place-items: center; font-family: 'Songti TC','LiSong Pro',PMingLiU,serif; font-size: 17px; color: #e8b45a; flex: none; }
|
||||
.maskline { display: flex; align-items: center; gap: 10px; padding: 14px; border-radius: 10px; background: rgba(0,0,0,.25); border: 1px solid rgba(237,228,211,.1); }
|
||||
.rowbody { padding: 0 18px 18px; border-top: 1px solid rgba(var(--ink-rgb),.1); }
|
||||
.pinbtn { flex: none; width: 34px; height: 34px; border-radius: 8px; border: none; cursor: pointer; font-size: 17px; background: rgba(var(--ink-rgb),.06); color: rgba(var(--ink-rgb),.4); }
|
||||
.pinbtn.on { background: rgba(var(--amber-rgb),.15); color: var(--amber); }
|
||||
.urlbox { display: flex; align-items: center; gap: 10px; padding: 12px 14px; border-radius: 10px; background: var(--well); border: 1px solid rgba(var(--ink-rgb),.1); min-width: 0; }
|
||||
.urlbox .u { font-family: ui-monospace, Menlo, monospace; font-size: 13.5px; color: var(--amber); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
|
||||
.avatar { width: 38px; height: 38px; border-radius: 10px; background: rgba(var(--amber-rgb),.1); border: 1px solid rgba(var(--amber-rgb),.25); display: grid; place-items: center; font-family: 'Songti TC','LiSong Pro',PMingLiU,serif; font-size: 17px; color: var(--amber); flex: none; }
|
||||
.maskline { display: flex; align-items: center; gap: 10px; padding: 14px; border-radius: 10px; background: var(--well); border: 1px solid rgba(var(--ink-rgb),.1); }
|
||||
.inbox-item { padding: 16px 18px; border-radius: 12px; }
|
||||
.inbox-item.new { background: rgba(232,180,90,.07); border: 1px solid rgba(232,180,90,.28); }
|
||||
.inbox-item.done { background: rgba(237,228,211,.04); border: 1px solid rgba(237,228,211,.1); }
|
||||
.pulse-dot { width: 7px; height: 7px; border-radius: 50%; background: #e8b45a; flex: none; animation: breatheAmber 2.6s ease-in-out infinite; }
|
||||
.inbox-item.new { background: rgba(var(--amber-rgb),.07); border: 1px solid rgba(var(--amber-rgb),.28); }
|
||||
.inbox-item.done { background: rgba(var(--ink-rgb),.04); border: 1px solid rgba(var(--ink-rgb),.1); }
|
||||
.pulse-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--amber); flex: none; animation: breatheAmber 2.6s ease-in-out infinite; }
|
||||
|
||||
/* ── 設定 ── */
|
||||
.setcol { display: flex; flex-direction: column; gap: 14px; padding: 20px 0 40px; }
|
||||
.switch { flex: none; margin-left: auto; width: 56px; height: 32px; border-radius: 999px; padding: 3px; background: rgba(237,228,211,.15); transition: background .25s; }
|
||||
.switch.on { background: rgba(63,190,120,.5); }
|
||||
.switch i { display: block; width: 26px; height: 26px; border-radius: 50%; background: #ede4d3; transition: transform .25s; }
|
||||
.switch { flex: none; margin-left: auto; width: 56px; height: 32px; border-radius: 999px; padding: 3px; background: rgba(var(--ink-rgb),.15); transition: background .25s; }
|
||||
.switch.on { background: rgba(var(--ok-rgb),.5); }
|
||||
.switch i { display: block; width: 26px; height: 26px; border-radius: 50%; background: var(--knob); box-shadow: 0 1px 3px rgba(0,0,0,.25); transition: transform .25s; }
|
||||
.switch.on i { transform: translateX(24px); }
|
||||
.kvline { display: flex; justify-content: space-between; gap: 12px; font-size: 15px; margin: 5px 0; }
|
||||
</style>
|
||||
@@ -223,8 +259,9 @@ function renderConsoleHtml(registryBase: string): string {
|
||||
<button class="btn" id="login-submit" style="margin-top:6px;padding:15px;font-size:17px;letter-spacing:.2em">進入</button>
|
||||
<div id="login-status" class="err" style="font-size:14px;min-height:1.2em"></div>
|
||||
</div>
|
||||
<div style="font-size:13.5px;color:rgba(237,228,211,.4);line-height:1.7">這是一個人的智慧總部。<br>若你不是擁有者,這裡沒有你要找的東西。</div>
|
||||
<a href="/console/dashboard" style="font-size:13.5px;color:rgba(232,180,90,.7)">免登入看駕駛艙 ›</a>
|
||||
<div style="font-size:13.5px;color:rgba(var(--ink-rgb),.4);line-height:1.7">這是一個人的智慧總部。<br>若你不是擁有者,這裡沒有你要找的東西。</div>
|
||||
<a href="/console/dashboard" style="font-size:13.5px;color:rgba(var(--amber-rgb),.7)">免登入看駕駛艙 ›</a>
|
||||
<button class="btn3 themelabel" data-themetoggle style="align-self:center;padding:8px 16px;font-size:13.5px;border-radius:999px">☾ 切深色</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -234,7 +271,7 @@ function renderConsoleHtml(registryBase: string): string {
|
||||
<div style="text-align:center">
|
||||
<div class="brand" style="font-size:38px">Mira</div>
|
||||
<div class="serif" style="margin-top:12px;font-size:21px">首次設定</div>
|
||||
<div style="margin-top:8px;font-size:15px;color:rgba(237,228,211,.55);line-height:1.7">系統偵測到尚未設定擁有者帳號。<br>設定一組 Email 與密碼,之後只有你能進入。</div>
|
||||
<div style="margin-top:8px;font-size:15px;color:rgba(var(--ink-rgb),.55);line-height:1.7">系統偵測到尚未設定擁有者帳號。<br>設定一組 Email 與密碼,之後只有你能進入。</div>
|
||||
</div>
|
||||
<div style="display:flex;flex-direction:column;gap:12px">
|
||||
<input type="email" id="setup-email" class="txt" placeholder="你的 Email" style="padding:15px 16px;font-size:17px;border-radius:11px">
|
||||
@@ -243,6 +280,7 @@ function renderConsoleHtml(registryBase: string): string {
|
||||
<button class="btn" id="setup-submit" style="margin-top:6px;padding:15px;font-size:17px;letter-spacing:.12em">完成設定並進入</button>
|
||||
<div id="setup-status" class="err" style="font-size:14px;min-height:1.2em"></div>
|
||||
</div>
|
||||
<button class="btn3 themelabel" data-themetoggle style="align-self:center;padding:8px 16px;font-size:13.5px;border-radius:999px">☾ 切深色</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -256,13 +294,14 @@ function renderConsoleHtml(registryBase: string): string {
|
||||
<div class="nav" data-nav="creds">憑證管理</div>
|
||||
<div class="nav" data-nav="inbox">收件匣</div>
|
||||
<div class="nav" data-nav="settings">設定</div>
|
||||
<div class="nav themelabel" data-themetoggle style="margin-top:8px;font-size:14.5px">☾ 切深色</div>
|
||||
<div class="foot" id="side-foot"></div>
|
||||
</div>
|
||||
<main>
|
||||
|
||||
<!-- 駕駛艙 -->
|
||||
<div class="view page" id="v-cockpit">
|
||||
<div class="pagehead"><span class="t">Mira<span style="font-size:14px;letter-spacing:.3em;color:rgba(237,228,211,.5)"> 控制台</span></span><span class="m serif" id="ck-date"></span></div>
|
||||
<div class="pagehead"><span class="t">Mira<span style="font-size:14px;letter-spacing:.3em;color:rgba(var(--ink-rgb),.5)"> 控制台</span></span><span class="m serif" id="ck-date"></span></div>
|
||||
<div class="cockgrid">
|
||||
<div>
|
||||
<div class="orb-row">
|
||||
@@ -291,12 +330,12 @@ function renderConsoleHtml(registryBase: string): string {
|
||||
</div>
|
||||
<div>
|
||||
<div style="display:flex;justify-content:space-between;align-items:baseline;margin:24px 0 10px">
|
||||
<span class="subhead">今日路線</span><span style="font-size:13px;color:rgba(237,228,211,.4)">狀態即時同步</span>
|
||||
<span class="subhead">今日路線</span><span style="font-size:13px;color:rgba(var(--ink-rgb),.4)">狀態即時同步</span>
|
||||
</div>
|
||||
<ul class="route" id="ck-today"><li class="todo"><span class="ic">○</span><span class="muted">載入中…</span></li></ul>
|
||||
<div class="subhead" id="ck-weekhead" style="display:none;margin:18px 0 10px">本週</div>
|
||||
<ul class="route" id="ck-week"></ul>
|
||||
<div style="margin:16px 0 8px;text-align:center;font-size:12.5px;color:rgba(237,228,211,.35);line-height:1.8" id="ck-stamp">每 60 秒自動刷新</div>
|
||||
<div style="margin:16px 0 8px;text-align:center;font-size:12.5px;color:rgba(var(--ink-rgb),.35);line-height:1.8" id="ck-stamp">每 60 秒自動刷新</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -311,7 +350,7 @@ function renderConsoleHtml(registryBase: string): string {
|
||||
</div>
|
||||
<div class="hscroll" id="se-chips" style="margin-top:12px"></div>
|
||||
<div id="se-banner"></div>
|
||||
<div id="se-count" style="margin:20px 0 12px;font-size:14px;color:rgba(237,228,211,.5)"></div>
|
||||
<div id="se-count" style="margin:20px 0 12px;font-size:14px;color:rgba(var(--ink-rgb),.5)"></div>
|
||||
<div class="kgrid" id="se-results"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -320,7 +359,7 @@ function renderConsoleHtml(registryBase: string): string {
|
||||
<div class="view page wide" id="v-card">
|
||||
<div class="pagehead" style="justify-content:flex-start;gap:14px">
|
||||
<button class="btn2" style="border:none;padding:0;font-size:16px" data-nav="search">‹ 搜尋</button>
|
||||
<span class="serif" style="font-size:17px;letter-spacing:.14em;color:rgba(237,228,211,.55)">知識卡片</span>
|
||||
<span class="serif" style="font-size:17px;letter-spacing:.14em;color:rgba(var(--ink-rgb),.55)">知識卡片</span>
|
||||
</div>
|
||||
<div class="cardwrap">
|
||||
<div class="cardmain" id="cd-main"><div class="muted" style="padding:24px 0">載入中…</div></div>
|
||||
@@ -350,11 +389,11 @@ function renderConsoleHtml(registryBase: string): string {
|
||||
<!-- 憑證管理 -->
|
||||
<div class="view page narrow" id="v-creds">
|
||||
<div class="pagehead"><span class="t">憑證管理</span><button class="btn2" id="cred-addbtn" style="padding:9px 16px;font-size:14.5px">+ 新增憑證</button></div>
|
||||
<div style="margin-top:16px;padding:13px 16px;border-radius:11px;background:rgba(63,190,120,.05);border:1px solid rgba(63,190,120,.2);font-size:14px;line-height:1.7;color:rgba(237,228,211,.65)">
|
||||
<span class="ok">🛡 保險箱原則</span>:系統只保存目錄與加密後的值,<b style="color:#ede4d3">任何頁面都看不到密文內容</b>。只能整筆替換或刪除。
|
||||
<div style="margin-top:16px;padding:13px 16px;border-radius:11px;background:rgba(var(--ok-rgb),.05);border:1px solid rgba(var(--ok-rgb),.2);font-size:14px;line-height:1.7;color:rgba(var(--ink-rgb),.65)">
|
||||
<span class="ok">🛡 保險箱原則</span>:系統只保存目錄與加密後的值,<b style="color:var(--ink)">任何頁面都看不到密文內容</b>。只能整筆替換或刪除。
|
||||
</div>
|
||||
<div id="cred-form" style="display:none;margin-top:14px" class="panel">
|
||||
<div class="serif" style="font-size:17px;color:#e8b45a;margin-bottom:10px">新增憑證</div>
|
||||
<div class="serif" style="font-size:17px;color:var(--amber);margin-bottom:10px">新增憑證</div>
|
||||
<div style="display:flex;flex-direction:column;gap:10px">
|
||||
<input id="cred-name" class="txt" placeholder="名稱(英數與底線,例:openai_main_key)">
|
||||
<input id="cred-service" class="txt" placeholder="服務(例:OpenAI)">
|
||||
@@ -376,21 +415,30 @@ function renderConsoleHtml(registryBase: string): string {
|
||||
<div class="view page narrow" id="v-settings">
|
||||
<div class="pagehead"><span class="t">設定</span></div>
|
||||
<div class="setcol">
|
||||
<div class="panel">
|
||||
<div style="display:flex;align-items:center;gap:14px">
|
||||
<div style="min-width:0">
|
||||
<div style="font-size:17px;font-weight:600">深色模式</div>
|
||||
<div style="margin-top:4px;font-size:14px;line-height:1.65;color:rgba(var(--ink-rgb),.55)">預設淺色(紙感)。切換立即生效,選擇記在這台裝置(localStorage)。</div>
|
||||
</div>
|
||||
<div class="switch" id="st-theme-switch" data-themetoggle style="cursor:pointer"><i></i></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel">
|
||||
<div style="display:flex;align-items:center;gap:14px">
|
||||
<div style="min-width:0">
|
||||
<div style="font-size:17px;font-weight:600">語意搜尋(vectorize)</div>
|
||||
<div id="st-vec" style="margin-top:4px;font-size:14px;line-height:1.65;color:rgba(237,228,211,.55)">狀態偵測中…</div>
|
||||
<div id="st-vec" style="margin-top:4px;font-size:14px;line-height:1.65;color:rgba(var(--ink-rgb),.55)">狀態偵測中…</div>
|
||||
</div>
|
||||
<div class="switch" id="st-vec-switch" title="開關即將開通"><i></i></div>
|
||||
</div>
|
||||
<div style="margin-top:12px;padding:12px 14px;border-radius:10px;background:rgba(232,180,90,.07);border:1px dashed rgba(232,180,90,.35);font-size:14px;line-height:1.7;color:rgba(237,228,211,.65)">
|
||||
誠實提示:這裡的開關<b style="color:#e8b45a">即將開通</b>——啟用/停用需在部署端設定(acr config 開 kbdb_embed 後重新部署),Console 目前只如實顯示狀態,不假裝能遠端開啟。
|
||||
<div style="margin-top:12px;padding:12px 14px;border-radius:10px;background:rgba(var(--amber-rgb),.07);border:1px dashed rgba(var(--amber-rgb),.35);font-size:14px;line-height:1.7;color:rgba(var(--ink-rgb),.65)">
|
||||
誠實提示:這裡的開關<b style="color:var(--amber)">即將開通</b>——啟用/停用需在部署端設定(acr config 開 kbdb_embed 後重新部署),Console 目前只如實顯示狀態,不假裝能遠端開啟。
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel">
|
||||
<div style="font-size:17px;font-weight:600">更換帳號密碼</div>
|
||||
<div style="margin-top:4px;font-size:14px;color:rgba(237,228,211,.55)">需輸入舊密碼驗證身分</div>
|
||||
<div style="margin-top:4px;font-size:14px;color:rgba(var(--ink-rgb),.55)">需輸入舊密碼驗證身分</div>
|
||||
<div style="margin-top:14px;display:flex;flex-direction:column;gap:10px">
|
||||
<input type="password" id="st-pw-old" class="txt" placeholder="舊密碼">
|
||||
<input type="email" id="st-pw-email" class="txt" placeholder="Email(可沿用或換新)">
|
||||
@@ -402,9 +450,9 @@ function renderConsoleHtml(registryBase: string): string {
|
||||
<div class="panel" style="display:flex;align-items:center;gap:12px;cursor:pointer" data-nav="creds">
|
||||
<div>
|
||||
<div style="font-size:17px;font-weight:600">憑證管理</div>
|
||||
<div style="margin-top:4px;font-size:14px;color:rgba(237,228,211,.55)">只保存目錄與加密值,不可回看</div>
|
||||
<div style="margin-top:4px;font-size:14px;color:rgba(var(--ink-rgb),.55)">只保存目錄與加密值,不可回看</div>
|
||||
</div>
|
||||
<span style="margin-left:auto;color:rgba(232,180,90,.6);font-size:18px">›</span>
|
||||
<span style="margin-left:auto;color:rgba(var(--amber-rgb),.6);font-size:18px">›</span>
|
||||
</div>
|
||||
<div class="panel">
|
||||
<div style="font-size:17px;font-weight:600;margin-bottom:12px">系統資訊</div>
|
||||
@@ -461,6 +509,32 @@ function renderConsoleHtml(registryBase: string): string {
|
||||
}
|
||||
function apiHeaders() { return { 'X-Arcrun-API-Key': S.tenant }; }
|
||||
|
||||
// fetch 失敗(斷網)的裸 TypeError 訊息(Failed to fetch / Load failed)→ 友善誠實文案
|
||||
function friendlyErr(e) {
|
||||
var m = e && e.message ? String(e.message) : String(e);
|
||||
if (/failed to fetch|load failed|networkerror|network request failed/i.test(m)) return '連線中斷——請檢查網路後重試';
|
||||
return m;
|
||||
}
|
||||
|
||||
// ── 主題(深/淺):預設淺色,選擇存 localStorage(head 預載腳本已先套用防閃色)──
|
||||
function getTheme() { return document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light'; }
|
||||
function setTheme(t) {
|
||||
document.documentElement.setAttribute('data-theme', t);
|
||||
try { localStorage.setItem('arcrun_console_theme', t); } catch (e) { /* 私密模式存不了就只影響本次 */ }
|
||||
syncThemeUI();
|
||||
}
|
||||
function syncThemeUI() {
|
||||
var dark = getTheme() === 'dark';
|
||||
document.querySelectorAll('.themelabel').forEach(function (el) { el.textContent = dark ? '☀ 切淺色' : '☾ 切深色'; });
|
||||
var sw = $('st-theme-switch');
|
||||
if (sw) sw.classList.toggle('on', dark);
|
||||
}
|
||||
document.addEventListener('click', function (ev) {
|
||||
var t = ev.target.closest('[data-themetoggle]');
|
||||
if (t) setTheme(getTheme() === 'dark' ? 'light' : 'dark');
|
||||
});
|
||||
syncThemeUI();
|
||||
|
||||
// created_at 容錯:epoch 秒 / 毫秒 / ISO / 'YYYY-MM-DD HH:MM:SS'
|
||||
function parseAtMs(v) {
|
||||
if (v == null || v === '') return null;
|
||||
@@ -599,7 +673,7 @@ function renderConsoleHtml(registryBase: string): string {
|
||||
fetch('/console/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: email, password: password }) })
|
||||
.then(function (r) { return r.json().then(function (d) { return { ok: r.ok, d: d }; }); })
|
||||
.then(function (x) { if (!x.ok) { $('login-status').textContent = x.d.error || '登入失敗'; return; } handleAuthResponse(x.d); })
|
||||
.catch(function (e) { $('login-status').textContent = '請求失敗:' + e.message; });
|
||||
.catch(function (e) { $('login-status').textContent = '請求失敗:' + friendlyErr(e); });
|
||||
});
|
||||
$('login-password').addEventListener('keydown', function (ev) { if (ev.key === 'Enter') $('login-submit').click(); });
|
||||
$('setup-submit').addEventListener('click', function () {
|
||||
@@ -609,7 +683,7 @@ function renderConsoleHtml(registryBase: string): string {
|
||||
fetch('/console/setup', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: email, password: pw }) })
|
||||
.then(function (r) { return r.json().then(function (d) { return { ok: r.ok, d: d }; }); })
|
||||
.then(function (x) { if (!x.ok) { $('setup-status').textContent = x.d.error || '設定失敗'; return; } handleAuthResponse(x.d); })
|
||||
.catch(function (e) { $('setup-status').textContent = '請求失敗:' + e.message; });
|
||||
.catch(function (e) { $('setup-status').textContent = '請求失敗:' + friendlyErr(e); });
|
||||
});
|
||||
|
||||
// ── 駕駛艙 ──
|
||||
@@ -662,9 +736,10 @@ function renderConsoleHtml(registryBase: string): string {
|
||||
.catch(function (e) {
|
||||
$('ck-char').textContent = '?';
|
||||
$('ck-title').textContent = '讀不到狀態';
|
||||
$('ck-sub').innerHTML = '<span class="err">' + esc(e.message) + '</span>';
|
||||
$('ck-sub').innerHTML = '<span class="err">' + esc(friendlyErr(e)) + '・每 60 秒自動重試</span>';
|
||||
});
|
||||
}
|
||||
// 60 秒定時器無條件常駐:斷網期間照跑,網路恢復後下一輪自動刷新回正常畫面
|
||||
setInterval(function () { if (currentRoute().view === 'cockpit' && $('shell').classList.contains('on')) loadCockpit(); }, 60000);
|
||||
|
||||
// ── 總庫搜尋 ──
|
||||
@@ -759,7 +834,7 @@ function renderConsoleHtml(registryBase: string): string {
|
||||
'<span class="dim mono" style="margin-left:auto">' + esc(fmtDate(e.created_at)) + '</span></div></div>';
|
||||
}).join('');
|
||||
})
|
||||
.catch(function (e) { $('se-count').innerHTML = '<span class="err">請求失敗:' + esc(e.message) + '</span>'; });
|
||||
.catch(function (e) { $('se-count').innerHTML = '<span class="err">請求失敗:' + esc(friendlyErr(e)) + '</span>'; });
|
||||
}
|
||||
$('se-results').addEventListener('click', function (ev) {
|
||||
var t = ev.target.closest('[data-card]');
|
||||
@@ -783,14 +858,14 @@ function renderConsoleHtml(registryBase: string): string {
|
||||
'<span class="muted mono">' + esc(fmtDateTime(e.created_at)) + '</span></div>' +
|
||||
'<div class="md">' + mdRender(e.content || '(此條目沒有內文)') + '</div>' +
|
||||
(e.source
|
||||
? '<div class="srcline"><span style="font-size:13px;letter-spacing:.14em;color:rgba(237,228,211,.5);flex:none">來源回溯</span><span class="mono" style="font-size:14px;color:#e8b45a;word-break:break-all">' + esc(e.source) + '</span></div>'
|
||||
? '<div class="srcline"><span style="font-size:13px;letter-spacing:.14em;color:rgba(var(--ink-rgb),.5);flex:none">來源回溯</span><span class="mono" style="font-size:14px;color:var(--amber);word-break:break-all">' + esc(e.source) + '</span></div>'
|
||||
: '');
|
||||
$('cd-main').innerHTML = html;
|
||||
var nodeName = e.page_name || title;
|
||||
S.graphCenter = nodeName;
|
||||
loadNeighbors(nodeName);
|
||||
})
|
||||
.catch(function (er) { $('cd-main').innerHTML = '<div class="err" style="padding:24px 0">請求失敗:' + esc(er.message) + '</div>'; renderGraphEmpty('請求失敗'); });
|
||||
.catch(function (er) { $('cd-main').innerHTML = '<div class="err" style="padding:24px 0">請求失敗:' + esc(friendlyErr(er)) + '</div>'; renderGraphEmpty('請求失敗'); });
|
||||
}
|
||||
function renderGraphEmpty(msg) {
|
||||
$('cd-graph').innerHTML = '<div class="graphbox" style="display:grid;place-items:center;height:160px"><span class="muted" style="font-size:14px;padding:0 20px;text-align:center">' + esc(msg) + '</span></div>';
|
||||
@@ -811,10 +886,10 @@ function renderConsoleHtml(registryBase: string): string {
|
||||
return '<div class="nbrow" data-gnode="' + esc(other) + '">' +
|
||||
'<span class="tag green" style="flex:none">' + esc(t.predicate || '關聯') + '</span>' +
|
||||
'<span style="word-break:break-word">' + esc(other) + '</span>' +
|
||||
'<span style="margin-left:auto;color:rgba(232,180,90,.6);flex:none">›</span></div>';
|
||||
'<span style="margin-left:auto;color:rgba(var(--amber-rgb),.6);flex:none">›</span></div>';
|
||||
}).join('');
|
||||
})
|
||||
.catch(function (e) { renderGraphEmpty('關聯服務不可達:' + e.message); });
|
||||
.catch(function (e) { renderGraphEmpty('關聯服務不可達:' + friendlyErr(e)); });
|
||||
}
|
||||
function renderGraph(center, nodes) {
|
||||
var n = nodes.length || 1;
|
||||
@@ -822,14 +897,14 @@ function renderConsoleHtml(registryBase: string): string {
|
||||
for (var i = 0; i < n; i++) {
|
||||
var a = (-90 + i * (360 / n)) * Math.PI / 180;
|
||||
var x = 50 + 39 * Math.cos(a), y = 50 + 37 * Math.sin(a);
|
||||
lines += '<line x1="50" y1="50" x2="' + x.toFixed(2) + '" y2="' + y.toFixed(2) + '" stroke="rgba(63,190,120,.3)" stroke-width="1" vector-effect="non-scaling-stroke" stroke-dasharray="4 4"/>';
|
||||
lines += '<line x1="50" y1="50" x2="' + x.toFixed(2) + '" y2="' + y.toFixed(2) + '" stroke="rgba(var(--ok-rgb),.3)" stroke-width="1" vector-effect="non-scaling-stroke" stroke-dasharray="4 4"/>';
|
||||
chips += '<div class="gnode" style="left:' + x.toFixed(2) + '%;top:' + y.toFixed(2) + '%" data-gnode="' + esc(nodes[i]) + '">' + esc(nodes[i]) + '</div>';
|
||||
}
|
||||
var shortCenter = center.length > 16 ? center.slice(0, 16) + '…' : center;
|
||||
$('cd-graph').innerHTML = '<div class="graphbox">' +
|
||||
'<svg viewBox="0 0 100 100" preserveAspectRatio="none" style="position:absolute;inset:0;width:100%;height:100%">' + lines + '</svg>' +
|
||||
'<div class="gcenter"><span>' + esc(shortCenter) + '</span></div>' + chips +
|
||||
'<div style="position:absolute;right:12px;bottom:10px;font-size:11.5px;color:rgba(237,228,211,.35)">╌ 知識圖譜鄰居(點節點展開)</div></div>';
|
||||
'<div style="position:absolute;right:12px;bottom:10px;font-size:11.5px;color:rgba(var(--ink-rgb),.35)">╌ 知識圖譜鄰居(點節點展開)</div></div>';
|
||||
}
|
||||
document.addEventListener('click', function (ev) {
|
||||
var t = ev.target.closest('[data-gnode]');
|
||||
@@ -849,7 +924,7 @@ function renderConsoleHtml(registryBase: string): string {
|
||||
$('wf-count').textContent = S.wfCache.length + ' 條自動化';
|
||||
renderWorkflows();
|
||||
})
|
||||
.catch(function (e) { $('wf-list').innerHTML = '<div class="err">請求失敗:' + esc(e.message) + '</div>'; });
|
||||
.catch(function (e) { $('wf-list').innerHTML = '<div class="err">請求失敗:' + esc(friendlyErr(e)) + '</div>'; });
|
||||
}
|
||||
$('wf-q').addEventListener('input', function () { renderWorkflows(); });
|
||||
function renderWorkflows(openName) {
|
||||
@@ -868,19 +943,19 @@ function renderConsoleHtml(registryBase: string): string {
|
||||
'<div class="rowhead" data-wftoggle="' + esc(w.name) + '">' +
|
||||
'<button class="pinbtn' + (pinned ? ' on' : '') + '" data-wfpin="' + esc(w.name) + '" title="釘選">' + (pinned ? '★' : '☆') + '</button>' +
|
||||
'<div style="min-width:0"><div style="font-size:17px;font-weight:600;line-height:1.4;word-break:break-word">' + esc(w.name) + '</div>' +
|
||||
'<div style="margin-top:3px;font-size:13.5px;color:rgba(237,228,211,.5)">' + esc(w.cron_expr ? '排程 ' + w.cron_expr : '事件觸發') + (w.created_at ? '・建立 ' + esc(fmtDate(w.created_at)) : '') + '</div></div>' +
|
||||
'<span style="margin-left:auto;color:rgba(232,180,90,.6);flex:none">' + (open ? '▾' : '›') + '</span></div>' +
|
||||
'<div style="margin-top:3px;font-size:13.5px;color:rgba(var(--ink-rgb),.5)">' + esc(w.cron_expr ? '排程 ' + w.cron_expr : '事件觸發') + (w.created_at ? '・建立 ' + esc(fmtDate(w.created_at)) : '') + '</div></div>' +
|
||||
'<span style="margin-left:auto;color:rgba(var(--amber-rgb),.6);flex:none">' + (open ? '▾' : '›') + '</span></div>' +
|
||||
(open ? renderWfBody(w) : '') + '</div>';
|
||||
}).join('');
|
||||
if (openName) fetchWfRuns(openName);
|
||||
}
|
||||
function renderWfBody(w) {
|
||||
return '<div class="rowbody">' +
|
||||
'<div style="margin-top:14px;font-size:15.5px;line-height:1.7;color:rgba(237,228,211,.7)">' + (w.description ? esc(w.description) : '<span class="dim">(未填描述——re-deploy 時補一句「能做什麼」它才搜得到)</span>') + '</div>' +
|
||||
'<div class="urlbox" style="margin-top:14px"><span style="font-size:12.5px;letter-spacing:.1em;color:rgba(237,228,211,.45);flex:none">觸發網址</span>' +
|
||||
'<div style="margin-top:14px;font-size:15.5px;line-height:1.7;color:rgba(var(--ink-rgb),.7)">' + (w.description ? esc(w.description) : '<span class="dim">(未填描述——re-deploy 時補一句「能做什麼」它才搜得到)</span>') + '</div>' +
|
||||
'<div class="urlbox" style="margin-top:14px"><span style="font-size:12.5px;letter-spacing:.1em;color:rgba(var(--ink-rgb),.45);flex:none">觸發網址</span>' +
|
||||
'<span class="u">' + esc(w.webhook_url || '') + '</span>' +
|
||||
'<button class="btn2" style="margin-left:auto;flex:none;padding:7px 13px;font-size:13.5px" data-wfcopy="' + esc(w.webhook_url || '') + '">複製</button></div>' +
|
||||
'<div style="margin-top:14px;font-size:13px;letter-spacing:.14em;color:rgba(237,228,211,.5)">最近執行</div>' +
|
||||
'<div style="margin-top:14px;font-size:13px;letter-spacing:.14em;color:rgba(var(--ink-rgb),.5)">最近執行</div>' +
|
||||
'<div id="wf-runs" style="margin-top:8px;display:flex;flex-direction:column;gap:6px"><span class="dim" style="font-size:14px">載入中…</span></div>' +
|
||||
'<button class="btn" style="margin-top:16px;width:100%" data-wfrun="' + esc(w.name) + '">手動觸發</button>' +
|
||||
'<div id="wf-runmsg" style="margin-top:8px;font-size:14px;min-height:1.2em"></div></div>';
|
||||
@@ -926,8 +1001,8 @@ function renderConsoleHtml(registryBase: string): string {
|
||||
box.innerHTML = runs.map(function (r) {
|
||||
var okRun = r.verdict === 'success';
|
||||
return '<div style="display:flex;gap:10px;align-items:baseline;font-size:14.5px">' +
|
||||
'<span style="flex:none;font-size:10px;color:' + (okRun ? '#7fe0a8' : '#e58575') + '">●</span>' +
|
||||
'<span style="color:rgba(237,228,211,.7)">' + (okRun ? '成功' : '失敗') + (typeof r.duration_ms === 'number' ? '・' + r.duration_ms + 'ms' : '') + (r.message && !okRun ? '・' + esc(String(r.message).slice(0, 60)) : '') + '</span>' +
|
||||
'<span style="flex:none;font-size:10px;color:' + (okRun ? 'var(--ok)' : 'var(--err)') + '">●</span>' +
|
||||
'<span style="color:rgba(var(--ink-rgb),.7)">' + (okRun ? '成功' : '失敗') + (typeof r.duration_ms === 'number' ? '・' + r.duration_ms + 'ms' : '') + (r.message && !okRun ? '・' + esc(String(r.message).slice(0, 60)) : '') + '</span>' +
|
||||
'<span class="dim mono" style="margin-left:auto;font-size:12.5px">' + esc(r.recorded_at ? fmtDateTime(r.recorded_at) : (r.timestamp ? fmtDateTime(Number(r.timestamp)) : '')) + '</span></div>';
|
||||
}).join('');
|
||||
})
|
||||
@@ -954,7 +1029,7 @@ function renderConsoleHtml(registryBase: string): string {
|
||||
})
|
||||
.catch(function (e) {
|
||||
btn.disabled = false; btn.textContent = '手動觸發';
|
||||
if (msg) msg.innerHTML = '<span class="err">請求失敗:' + esc(e.message) + '</span>';
|
||||
if (msg) msg.innerHTML = '<span class="err">請求失敗:' + esc(friendlyErr(e)) + '</span>';
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1025,7 +1100,7 @@ function renderConsoleHtml(registryBase: string): string {
|
||||
toast('已加密保存');
|
||||
loadCreds();
|
||||
})
|
||||
.catch(function (e) { st.innerHTML = '<span class="err">請求失敗:' + esc(e.message) + '</span>'; });
|
||||
.catch(function (e) { st.innerHTML = '<span class="err">請求失敗:' + esc(friendlyErr(e)) + '</span>'; });
|
||||
});
|
||||
var credOpen = '';
|
||||
var credCache = [];
|
||||
@@ -1040,7 +1115,7 @@ function renderConsoleHtml(registryBase: string): string {
|
||||
credCache = x.d.credentials || [];
|
||||
renderCreds();
|
||||
})
|
||||
.catch(function (e) { $('cred-list').innerHTML = '<div class="err">請求失敗:' + esc(e.message) + '</div>'; });
|
||||
.catch(function (e) { $('cred-list').innerHTML = '<div class="err">請求失敗:' + esc(friendlyErr(e)) + '</div>'; });
|
||||
}
|
||||
function renderCreds() {
|
||||
if (!credCache.length) {
|
||||
@@ -1054,11 +1129,11 @@ function renderConsoleHtml(registryBase: string): string {
|
||||
'<div class="rowhead" data-credtoggle="' + esc(c.name) + '">' +
|
||||
'<span class="avatar">' + esc(initial) + '</span>' +
|
||||
'<div style="min-width:0"><div style="font-size:16.5px;font-weight:600;word-break:break-all">' + esc(c.name) + '</div>' +
|
||||
'<div style="margin-top:2px;font-size:13.5px;color:rgba(237,228,211,.5)">' + esc(c.service || '未填服務') + '・建立於 ' + esc(fmtDate(c.created_at) || '—') + '</div></div>' +
|
||||
'<div style="margin-left:auto;text-align:right;font-size:12.5px;color:rgba(237,228,211,.4);flex:none">最後使用<br>' + esc(c.last_used_at ? fmtDate(c.last_used_at) : '尚未記錄') + '</div></div>' +
|
||||
'<div style="margin-top:2px;font-size:13.5px;color:rgba(var(--ink-rgb),.5)">' + esc(c.service || '未填服務') + '・建立於 ' + esc(fmtDate(c.created_at) || '—') + '</div></div>' +
|
||||
'<div style="margin-left:auto;text-align:right;font-size:12.5px;color:rgba(var(--ink-rgb),.4);flex:none">最後使用<br>' + esc(c.last_used_at ? fmtDate(c.last_used_at) : '尚未記錄') + '</div></div>' +
|
||||
(open
|
||||
? '<div class="rowbody">' +
|
||||
'<div class="maskline" style="margin-top:14px"><span class="mono" style="font-size:16px;letter-spacing:.3em;color:rgba(237,228,211,.4)">••••••••••••••••</span><span style="margin-left:auto;font-size:12.5px;color:rgba(237,228,211,.4)">已加密・不可回看</span></div>' +
|
||||
'<div class="maskline" style="margin-top:14px"><span class="mono" style="font-size:16px;letter-spacing:.3em;color:rgba(var(--ink-rgb),.4)">••••••••••••••••</span><span style="margin-left:auto;font-size:12.5px;color:rgba(var(--ink-rgb),.4)">已加密・不可回看</span></div>' +
|
||||
'<textarea class="txt" id="cred-newval" placeholder="貼上新值以整筆替換…" rows="2" style="margin-top:10px"></textarea>' +
|
||||
'<div style="display:flex;gap:10px;margin-top:10px;flex-wrap:wrap">' +
|
||||
'<button class="btn" style="flex:1;min-width:140px" data-credreplace="' + esc(c.name) + '">整筆替換</button>' +
|
||||
@@ -1091,7 +1166,7 @@ function renderConsoleHtml(registryBase: string): string {
|
||||
toast('「' + name + '」已整筆替換');
|
||||
loadCreds();
|
||||
})
|
||||
.catch(function (e) { rep.disabled = false; if (msg) msg.innerHTML = '<span class="err">請求失敗:' + esc(e.message) + '</span>'; });
|
||||
.catch(function (e) { rep.disabled = false; if (msg) msg.innerHTML = '<span class="err">請求失敗:' + esc(friendlyErr(e)) + '</span>'; });
|
||||
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 += '<div class="serif" style="margin:20px 0 10px;font-size:16px;letter-spacing:.18em;color:#e8b45a">新的・' + news.length + ' 條</div>';
|
||||
html += '<div class="serif" style="margin:20px 0 10px;font-size:16px;letter-spacing:.18em;color:var(--amber)">新的・' + news.length + ' 條</div>';
|
||||
html += news.length
|
||||
? '<div style="display:flex;flex-direction:column;gap:9px">' + news.map(function (m) {
|
||||
return '<div class="inbox-item new"><div style="font-size:16.5px;line-height:1.6;word-break:break-word">' + esc(m.text) + '</div>' +
|
||||
'<div style="margin-top:8px;display:flex;gap:10px;align-items:center;font-size:13px;color:rgba(237,228,211,.45)"><span class="pulse-dot"></span><span>等待 AI 處理</span><span class="mono" style="margin-left:auto">' + esc(fmtDateTime(m.at)) + '</span></div></div>';
|
||||
'<div style="margin-top:8px;display:flex;gap:10px;align-items:center;font-size:13px;color:rgba(var(--ink-rgb),.45)"><span class="pulse-dot"></span><span>等待 AI 處理</span><span class="mono" style="margin-left:auto">' + esc(fmtDateTime(m.at)) + '</span></div></div>';
|
||||
}).join('') + '</div>'
|
||||
: '<div class="muted" style="font-size:14px">沒有待處理的指令</div>';
|
||||
html += '<div class="serif" style="margin:26px 0 10px;font-size:16px;letter-spacing:.18em;color:rgba(237,228,211,.55)">已處理・' + done.length + ' 條</div>';
|
||||
html += '<div class="serif" style="margin:26px 0 10px;font-size:16px;letter-spacing:.18em;color:rgba(var(--ink-rgb),.55)">已處理・' + done.length + ' 條</div>';
|
||||
html += done.length
|
||||
? '<div style="display:flex;flex-direction:column;gap:9px;padding-bottom:20px">' + done.map(function (m) {
|
||||
return '<div class="inbox-item done"><div style="font-size:16px;line-height:1.6;color:rgba(237,228,211,.7);word-break:break-word">' + esc(m.text) + '</div>' +
|
||||
return '<div class="inbox-item done"><div style="font-size:16px;line-height:1.6;color:rgba(var(--ink-rgb),.7);word-break:break-word">' + esc(m.text) + '</div>' +
|
||||
'<div style="margin-top:8px;display:flex;gap:9px;align-items:baseline;font-size:14px"><span class="ok" style="flex:none">✓</span>' +
|
||||
'<span style="color:rgba(127,224,168,.8);line-height:1.5;word-break:break-word">' + esc(m.result || '已處理') + '</span>' +
|
||||
'<span class="dim mono" style="margin-left:auto;flex:none;font-size:13px">' + esc(fmtDateTime(m.at)) + '</span></div></div>';
|
||||
@@ -1139,7 +1214,7 @@ function renderConsoleHtml(registryBase: string): string {
|
||||
: '<div class="muted" style="font-size:14px;padding-bottom:20px">還沒有已處理的紀錄</div>';
|
||||
out.innerHTML = html;
|
||||
})
|
||||
.catch(function (e) { out.innerHTML = '<div class="err" style="padding:14px 0">請求失敗:' + esc(e.message) + '</div>'; });
|
||||
.catch(function (e) { out.innerHTML = '<div class="err" style="padding:14px 0">請求失敗:' + esc(friendlyErr(e)) + '</div>'; });
|
||||
}
|
||||
|
||||
// ── 設定 ──
|
||||
@@ -1164,10 +1239,10 @@ function renderConsoleHtml(registryBase: string): string {
|
||||
var kb = rs[1].status === 'fulfilled' ? rs[1].value : null;
|
||||
var rows = '';
|
||||
rows += '<div class="kvline"><span class="muted">服務</span><span class="mono" style="font-size:14px">' + esc(svc.service || 'arcrun-cypher-executor') + '</span></div>';
|
||||
rows += '<div class="kvline"><span class="muted">版本</span><span class="mono" style="color:#e8b45a">' + esc(svc.version || '—') + '</span></div>';
|
||||
rows += '<div class="kvline"><span class="muted">版本</span><span class="mono" style="color:var(--amber)">' + esc(svc.version || '—') + '</span></div>';
|
||||
rows += '<div class="kvline"><span class="muted">部署位置</span><span class="mono" style="font-size:14px">' + esc(location.host) + '</span></div>';
|
||||
rows += '<div class="kvline"><span class="muted">租戶</span><span class="mono">' + esc(S.tenant) + '</span></div>';
|
||||
rows += '<div class="kvline"><span class="muted">總庫規模</span><span class="mono" style="color:#e8b45a">' + (kb && typeof kb.total === 'number' ? kb.total.toLocaleString() + ' 筆' : '讀取失敗') + '</span></div>';
|
||||
rows += '<div class="kvline"><span class="muted">總庫規模</span><span class="mono" style="color:var(--amber)">' + (kb && typeof kb.total === 'number' ? kb.total.toLocaleString() + ' 筆' : '讀取失敗') + '</span></div>';
|
||||
$('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 = '<span class="err">請求失敗:' + esc(e.message) + '</span>'; });
|
||||
.catch(function (e) { st.innerHTML = '<span class="err">請求失敗:' + esc(friendlyErr(e)) + '</span>'; });
|
||||
});
|
||||
$('st-vec-switch').addEventListener('click', function () {
|
||||
toast('開關即將開通——目前需在部署端啟用 kbdb_embed');
|
||||
|
||||
@@ -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.<subdomain>.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。
|
||||
|
||||
Reference in New Issue
Block a user