portal 顯示同步器版本+下載網址改讀真相源(leo 08-06)

leo:「Portal 和 rag.arcrun.dev 應該顯示同步器的版本⋯⋯因為你說最新 0.18.5
連我都沒辦法確認,所以用戶到底是否最新版他自己也不知道」。

## 病
release(雲端知識庫)與 daemon(桌面同步器)是兩條版本線。
版本卡只顯示前者;下載按鈕的檔名是寫死的固定別名
(DAEMON_MAC/DAEMON_WIN)⇒ 頁面上完全看不出「同步器是哪一版」。

## 改
- 下載網址改向 /api/latest 取 daemon.downloads(真相源=bundles manifest),
  取不到才退回原本的固定別名 ⇒ 按鈕永遠可用。
  ⚠️ 沒有違背 fe0ee82 立的「不要每出一版就改 code」——網址是取來的,
     一樣不必改 code;還順便解掉固定別名的兩個老問題:
     ① 別名指向 @main 會吃 CDN/ref 快取拿到舊檔(08-04 撞過)
     ② 檔名不帶版號 ⇒ 無從顯示「這是哪一版」=leo 這次抱怨的正題
- 版本卡下方加一行「最新的同步器版本是 X(你手上那支在同步器視窗左下角)」
  +連到版本說明頁。取不到就**不顯示**,不編數字。
- 渲染改成「先用退路畫、取到真相源再重畫」,renderDaemonDownload 做成可重複呼叫
  (清掉上一輪插入的節點,否則會疊出兩份「不是這個系統?」)。

## 驗
· node --test os-split.test.mjs safejson.test.mjs → 8 passed, 0 failed
· index.html 三個 script 區塊逐塊 node --check → 全部語法 OK
· 未驗:線上畫面(要等 bundle 出貨後才有 daemon 欄位可讀)⇒ ◐
This commit is contained in:
uncle6me-web
2026-08-06 13:12:15 +08:00
parent 05b215c9f7
commit 36a5630c63
+74 -25
View File
@@ -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 = '不是這個系統?<a href="' + d.other.url + '">' + d.other.label + '</a>';
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 = '或 <a href="' + d.win.url + '">' + d.win.label + '</a>';
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 = '不是這個系統?<a href="' + d.other.url + '">' + d.other.label + '</a>';
} else {
// 判不出來=兩個都給,不預設 Mac
dl.setAttribute('href', d.mac.url);
dl.textContent = d.mac.label;
alt.innerHTML = '或 <a href="' + d.win.url + '">' + d.win.label + '</a>';
}
})();
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 = '最新的同步器版本是 <strong>' + esc(DAEMON_LATEST.version) + '</strong>'
+ '(你手上那支的版本,在同步器視窗左下角)'
+ ' · <a href="https://rag.arcrun.dev/docs/help/changelog/" target="_blank" rel="noopener">這一版改了什麼</a>';
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();
// t176leo 08-03):AI 設定的前端邏輯整段移除。
// 雲端聊天走 Workers AI(免金鑰);地端萃取金鑰改由同步小幫手托盤「AI 設定…」自己設。