@@ -548,14 +567,48 @@ function taipeiMonthDay(ms) { var d = new Date(ms + TAIPEI_OFFSET_MS); return {
// ── 認證流 ──
function showAuth() {
+ $('v-firstsetup').classList.remove('on');
$('v-login').classList.add('on');
$('shell').classList.remove('on');
$('tabbar').classList.remove('on');
+ // t49:全新實例(還沒有任何帳號)→ 換顯示「首次設定」,不讓用戶對著登入殼發呆。
+ // 探測走 console/auth-status(configured 布林,console 首設同一顆;失敗就保持登入殼=誠實降級)。
+ fetch(API_BASE + '/console/auth-status')
+ .then(function (r) { return r.json(); })
+ .then(function (d) {
+ if (d && d.configured === false) {
+ $('v-login').classList.remove('on');
+ $('v-firstsetup').classList.add('on');
+ }
+ })
+ .catch(function () { /* 探測不到就維持登入殼 */ });
}
function showApp() {
$('v-login').classList.remove('on');
+ $('v-firstsetup').classList.remove('on');
$('shell').classList.add('on');
$('tabbar').classList.add('on');
+ // t49 一次性歡迎引導(首次設定完成那一刻才出現;關掉就不再出現):
+ // 下一步只有兩件——裝同步小幫手(資料自動進來)、想問答就設金鑰。連去 installer /setup。
+ try {
+ if (localStorage.getItem('arcrun_onboarding') === '1' && !document.getElementById('onboard-card')) {
+ var setupUrl = (window.ARCRUN_CONFIG && window.ARCRUN_CONFIG.installerBase
+ ? String(window.ARCRUN_CONFIG.installerBase).replace(/\/+$/, '')
+ : 'https://arcrun-installer.youlin-hsieh-dev.workers.dev') + '/setup';
+ var el = document.createElement('div');
+ el.id = 'onboard-card';
+ el.style.cssText = 'position:fixed;left:50%;bottom:24px;transform:translateX(-50%);z-index:60;max-width:560px;width:calc(100% - 32px);padding:18px 20px;border-radius:14px;background:rgba(var(--amber-rgb),.10);border:1px solid rgba(var(--amber-rgb),.45);backdrop-filter:blur(6px);font-size:14.5px;line-height:1.7';
+ el.innerHTML = '
🎉 你的知識庫開張了!接下來只有一件事:'
+ + '
下載同步小幫手設定'
+ + '——把你的資料夾連上來,檔案就會自動變成可搜尋的知識(想用 AI 問答,金鑰也在同一頁設定)。'
+ + '
';
+ document.body.appendChild(el);
+ document.getElementById('onboard-close').addEventListener('click', function () {
+ try { localStorage.removeItem('arcrun_onboarding'); } catch (e) { /* noop */ }
+ el.remove();
+ });
+ }
+ } catch (e) { /* 引導卡失敗不擋主流程 */ }
var p = S.profile || {};
$('side-foot').innerHTML = esc(p.display_name || '') + '
' + esc(location.host);
$('nav-workflows').classList.toggle('hide', !p.workflows_visible);
@@ -623,6 +676,59 @@ function taipeiMonthDay(ms) { var d = new Date(ms + TAIPEI_OFFSET_MS); return {
dropSession();
});
+ // ── t49 首次設定(leo 07-25):console/setup → portal/admin/bootstrap → 預設庫登記 → portal/login
+ // 四發全是既有端點,一顆按鈕做完;用戶感受=「建一組帳密就進來了」。
+ $('fs-submit').addEventListener('click', doFirstSetup);
+ $('fs-password2').addEventListener('keydown', function (ev) { if (ev.key === 'Enter') doFirstSetup(); });
+ function doFirstSetup() {
+ var email = $('fs-email').value.trim();
+ var pw = $('fs-password').value;
+ var st = $('fs-status');
+ if (!email || pw.length < 8) { st.textContent = '請填 Email,密碼至少 8 碼'; return; }
+ if (pw !== $('fs-password2').value) { st.textContent = '兩次密碼不一樣'; return; }
+ $('fs-submit').disabled = true; st.textContent = '';
+ var post = function (path, body, hdrs) {
+ return fetch(API_BASE + path, {
+ method: 'POST',
+ headers: Object.assign({ 'Content-Type': 'application/json' }, hdrs || {}),
+ body: JSON.stringify(body)
+ }).then(function (r) { return r.json().catch(function () { return {}; }).then(function (d) { return { ok: r.ok, status: r.status, d: d }; }); });
+ };
+ post('/console/setup', { email: email, password: pw })
+ .then(function (x) {
+ if (!x.ok || !x.d.session_token) throw new Error(x.d.error || '設定沒有成功,請再試一次');
+ var ownerTok = x.d.session_token;
+ return post('/portal/admin/bootstrap', { email: email, password: pw, display_name: email.split('@')[0] }, { Authorization: 'Bearer ' + ownerTok })
+ .then(function (b) {
+ // 409=已 bootstrap 過(冪等視為就緒);其餘失敗誠實丟出
+ if (!b.ok && b.status !== 409) throw new Error(b.d.error || '初始化沒有成功,請再試一次');
+ // 預設庫 kb 自動登記(leo:庫要自動出現,不是叫用戶去登記)——
+ // 同步小幫手預設就寫進 kb 庫,這裡把登記簿先蓋好章;已存在(409/重複)不吵。
+ return post('/portal/login', { email: email, password: pw });
+ });
+ })
+ .then(function (l) {
+ if (!l.ok || !l.d.session_token) throw new Error(l.d.error || '帳號建好了,但自動登入沒成功——請用剛設定的帳密登入');
+ S.token = l.d.session_token;
+ try { localStorage.setItem('arcrun_portal_session', S.token); } catch (e) { /* noop */ }
+ // 預設庫登記(用 portal session;失敗不擋進入)
+ return fetch(API_BASE + '/portal/admin/libraries', {
+ method: 'POST',
+ headers: Object.assign({ 'Content-Type': 'application/json' }, authHeaders()),
+ body: JSON.stringify({ name: 'kb', display_name: '知識庫', description: '同步小幫手預設寫入的庫' })
+ }).catch(function () { /* 已存在或無權限都不擋 */ });
+ })
+ .then(function () {
+ $('fs-submit').disabled = false;
+ try { localStorage.setItem('arcrun_onboarding', '1'); } catch (e) { /* noop */ }
+ boot();
+ })
+ .catch(function (e) {
+ $('fs-submit').disabled = false;
+ st.textContent = (e && e.message) || friendlyErr(e);
+ });
+ }
+
// 任何 data 請求收到 401 → session 失效 → 回登入殼
function guard401(status) {
if (status === 401) { dropSession(); return true; }