diff --git a/console-ui/public/portal/index.html b/console-ui/public/portal/index.html
index 11696ba..b0a51b8 100644
--- a/console-ui/public/portal/index.html
+++ b/console-ui/public/portal/index.html
@@ -802,8 +802,20 @@ function taipeiMonthDay(ms) { var d = new Date(ms + TAIPEI_OFFSET_MS); return {
// 不再給 zip——zip 解開就是 .app,使用者很可能直接在「下載」資料夾雙擊啟動,
// 而自更新會蓋錯位置(t184 Oscar 的病)。
// ⚠️ 用**固定檔名**(不帶版號),否則每出一版都要改這裡的 code。
+ //
+ // 🔴 2026-08-06 升級(leo:「Portal 和 rag.arcrun.dev 應該顯示同步器的版本⋯⋯
+ // **連我都沒辦法確認**,所以用戶到底是否最新版他自己也不知道」):
+ // 下面兩個常數**降級為退路**,正常情況改向 `/api/latest` 取
+ // `daemon.version` 與 `daemon.downloads`(真相源=bundles 的 manifest)。
+ // ⚠️ 這**沒有違背**上面那條「不要每出一版就改 code」——網址現在是**取來的**,
+ // 一樣不用改 code;而且順便解掉固定別名的兩個老問題:
+ // ① 別名指向 @main,會吃到 CDN/ref 快取拿到舊檔(08-04 撞過)
+ // ② 檔名不帶版號 ⇒ 頁面上無從顯示「這是哪一版」=leo 這次抱怨的正題
+ // 取不到就退回這兩個固定檔名,按鈕不會變死連結。
var DAEMON_MAC = 'ArcrunRAG-mac.dmg';
var DAEMON_WIN = 'ArcrunRAG-win-unsigned.zip';
+ // 由 /api/latest 填入(見下方 loadDaemonLatest);null=還沒取到或取不到。
+ var DAEMON_LATEST = null;
// Mac 那顆 > jsDelivr 單檔 20MB 上限(實測回 "File size exceeded...")→ 一律走 raw;
// Windows 同走 raw 保持單一來源、少一個會壞的地方。
function daemonBase() {
@@ -826,8 +838,11 @@ function taipeiMonthDay(ms) { var d = new Date(ms + TAIPEI_OFFSET_MS); return {
var isMobile = /iPhone|iPad|iPod|Android|Mobile/i.test(ua);
var isWin = !isMobile && /Windows NT/i.test(ua);
var isMac = !isMobile && /Macintosh|Mac OS X/i.test(ua) && !/Windows/i.test(ua);
- var mac = { os: 'mac', label: '下載 Mac 版', url: base + DAEMON_MAC };
- var win = { os: 'win', label: '下載 Windows 版', url: base + DAEMON_WIN };
+ // 有取到真相源就用它的網址(帶版號、指向釘點 sha),否則退回固定別名。
+ var dlm = (DAEMON_LATEST && DAEMON_LATEST.downloads && DAEMON_LATEST.downloads.mac) || (base + DAEMON_MAC);
+ var dlw = (DAEMON_LATEST && DAEMON_LATEST.downloads && DAEMON_LATEST.downloads.win) || (base + DAEMON_WIN);
+ var mac = { os: 'mac', label: '下載 Mac 版', url: dlm };
+ var win = { os: 'win', label: '下載 Windows 版', url: dlw };
if (isWin) return { pick: win, other: mac, sure: true };
if (isMac) return { pick: mac, other: win, sure: true };
return { pick: null, other: null, sure: false, mac: mac, win: win };
@@ -839,31 +854,65 @@ function taipeiMonthDay(ms) { var d = new Date(ms + TAIPEI_OFFSET_MS); return {
return '(開啟後把 Arcrun 拖進「應用程式」,再從啟動台開啟;封測版未簽章,第一次請右鍵→打開)';
}
// 07-27:設定頁的常駐入口(下載小幫手/換 AI 金鑰)——與一次性卡片同一組 API
- (function () {
+ // 🔴 2026-08-06 改成「先用退路畫、取到真相源再重畫」:
+ // fetch 是非同步的,若等它回來才畫,網路慢時使用者會看到一顆沒有網址的按鈕。
+ // ⇒ 先用固定別名畫出可用的按鈕,取到 /api/latest 後再重畫成帶版號的網址+版本行。
+ // renderDaemonDownload 必須**可重複呼叫**(第二次要清掉第一次插進去的兄弟節點),
+ // 否則重畫會疊出兩份「不是這個系統?」。
+ function renderDaemonDownload() {
var dl = $('st-daemon-dl');
- if (dl) {
- var d = daemonPick();
- if (d.sure) {
- dl.setAttribute('href', d.pick.url);
- dl.textContent = d.pick.label;
- // 判對了也要留另一版的路(UA 會判錯)
- var alt = document.createElement('span');
- alt.className = 'muted';
- alt.style.cssText = 'font-size:12.5px;margin-left:8px';
- alt.innerHTML = '不是這個系統?' + d.other.label + '';
- if (dl.parentNode) dl.parentNode.insertBefore(alt, dl.nextSibling);
- } else {
- // 判不出來=兩個都給,不預設 Mac
- dl.setAttribute('href', d.mac.url);
- dl.textContent = d.mac.label;
- var both = document.createElement('span');
- both.className = 'muted';
- both.style.cssText = 'font-size:12.5px;margin-left:8px';
- both.innerHTML = '或 ' + d.win.label + '';
- if (dl.parentNode) dl.parentNode.insertBefore(both, dl.nextSibling);
- }
+ if (!dl) return;
+ // 清掉上一輪插入的節點(用 id 標記,才不會誤刪別人的東西)
+ ['st-daemon-alt', 'st-daemon-ver'].forEach(function (id) {
+ var old = $(id); if (old && old.parentNode) old.parentNode.removeChild(old);
+ });
+ var d = daemonPick();
+ var alt = document.createElement('span');
+ alt.id = 'st-daemon-alt';
+ alt.className = 'muted';
+ alt.style.cssText = 'font-size:12.5px;margin-left:8px';
+ if (d.sure) {
+ dl.setAttribute('href', d.pick.url);
+ dl.textContent = d.pick.label;
+ // 判對了也要留另一版的路(UA 會判錯)
+ alt.innerHTML = '不是這個系統?' + d.other.label + '';
+ } else {
+ // 判不出來=兩個都給,不預設 Mac
+ dl.setAttribute('href', d.mac.url);
+ dl.textContent = d.mac.label;
+ alt.innerHTML = '或 ' + d.win.label + '';
}
- })();
+ if (dl.parentNode) dl.parentNode.insertBefore(alt, dl.nextSibling);
+
+ // 版本行:leo 要的「用戶看得出自己是不是最新版」。
+ // 取不到就**不顯示**,不要編一個數字(同 landing 的原則:寧可空著也不說謊)。
+ if (DAEMON_LATEST && DAEMON_LATEST.version) {
+ var ver = document.createElement('div');
+ ver.id = 'st-daemon-ver';
+ ver.className = 'muted';
+ ver.style.cssText = 'font-size:12.5px;margin-top:6px';
+ ver.innerHTML = '最新的同步器版本是 ' + esc(DAEMON_LATEST.version) + ''
+ + '(你手上那支的版本,在同步器視窗左下角)'
+ + ' · 這一版改了什麼';
+ if (alt.parentNode) alt.parentNode.insertBefore(ver, alt.nextSibling);
+ }
+ }
+
+ function loadDaemonLatest() {
+ // 不用 INSTALLER_ORIGIN:它宣告在本區塊之後(var 提升 ⇒ 這裡是 undefined)。
+ fetch('https://install.arcrun.dev/api/latest')
+ .then(function (r) { return r.ok ? r.json() : null; })
+ .then(function (j) {
+ if (j && j.daemon && j.daemon.version) {
+ DAEMON_LATEST = j.daemon;
+ renderDaemonDownload(); // 取到了才重畫
+ }
+ })
+ .catch(function () { /* 取不到就維持退路的固定別名,按鈕仍可用 */ });
+ }
+
+ renderDaemonDownload();
+ loadDaemonLatest();
// t176(leo 08-03):AI 設定的前端邏輯整段移除。
// 雲端聊天走 Workers AI(免金鑰);地端萃取金鑰改由同步小幫手托盤「AI 設定…」自己設。