(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))a(o);new MutationObserver(o=>{for(const i of o)if(i.type==="childList")for(const s of i.addedNodes)s.tagName==="LINK"&&s.rel==="modulepreload"&&a(s)}).observe(document,{childList:!0,subtree:!0});function n(o){const i={};return o.integrity&&(i.integrity=o.integrity),o.referrerPolicy&&(i.referrerPolicy=o.referrerPolicy),o.crossOrigin==="use-credentials"?i.credentials="include":o.crossOrigin==="anonymous"?i.credentials="omit":i.credentials="same-origin",i}function a(o){if(o.ep)return;o.ep=!0;const i=n(o);fetch(o.href,i)}})();const ve="/niannian",Es={"admin-users":"admin/users"};function z(e){const t=e.replace(/^\/+/,"").split("/")[0]||"chat",n=Es[t]||t;return`${ve}/${n}`}function Ps(){const e=window.location.pathname;if(e===ve||e===`${ve}/`||!e.startsWith(`${ve}/`))return"chat";const t=e.slice(ve.length+1);return t==="admin/users"||t.startsWith("admin/users/")?"admin-users":t.split("/")[0]||"chat"}function Is(){const e=window.location.pathname;if(e==="/"||e===""){history.replaceState(null,"",z("chat"));return}if(e===ve||e===`${ve}/`){history.replaceState(null,"",z("chat"));return}e.startsWith(`${ve}/`)||history.replaceState(null,"",z("chat"))}const Uo={auto:"default.png",default:"default.png",companion:"companion.png",work:"work.png",education:"education.png"},jo=`${ve}/assets/niannian/avatars`,Bs=`${jo}/default.png`;function ke(e){const t=String(e??"auto").trim();return t in Uo?t:"auto"}function On(e){const t=ke(e),n=Uo[t];return`${jo}/${n}`}function Cs(e){return`/v1/virtual-personas/${encodeURIComponent(e)}/avatar`}function Ts(e={}){const t=e.equippedVpId!=null?String(e.equippedVpId).trim():"";return t&&e.hasVpAvatar?{url:e.vpAvatarUrl||Cs(t),kind:"vp"}:{url:On(e.chatMode),kind:"niannian"}}function kt(e="nn-avatar-img",t="念念",n){const a=n??Bs,o=t?` alt="${t}"`:' alt="" aria-hidden="true"';return`<img class="${e}" src="${a}"${o} loading="lazy" decoding="async" draggable="false" />`}function Ls(e,t,n){var a;if(e==="assistant"){const o=Ts(n??{}),i=((a=n==null?void 0:n.vpLabel)==null?void 0:a.trim())||"念念";return kt("nn-avatar-img",i,o.url)}return t}const $t="我",As="你撤回了一条消息",xs="此回复已撤回",Ms=120;function fa(e){return!!(e!=null&&e.recalled_at)}function Ct(e){return e.replace(/^[\s\r\n]+/,"")}function Ho(e,t,n){return fa(n)?e==="user"?As:xs:Ct(t)}function Os(e,t=Date.now()){if(!e)return 0;const n=Date.parse(e);if(Number.isNaN(n))return 0;const a=(t-n)/1e3;return Math.max(0,Math.ceil(Ms-a))}function Rs(e,t,n,a=Date.now()){return fa(t)?!1:Os(n,a)>0}function ha(e,t){var a;if(e==="user")return{name:"你",abbr:$t};const n=(a=t==null?void 0:t.vpLabel)==null?void 0:a.trim();return t!=null&&t.equippedVpId&&n?{name:n,abbr:n.slice(0,1)}:{name:"念念",abbr:"念"}}function Xt(e,t){if(e==="user")return t!=null&&t.userAvatarUrl?kt("nn-avatar-img",$t,t.userAvatarUrl):$t;const n=ha(e,t);return Ls(e,n.abbr,t)}function Ds(e,t){if(t)return kt("nn-avatar-img",$t,t);const n=e.trim();return n?n.slice(0,1).toUpperCase():$t}function ft(e=new Date){return e.toLocaleTimeString("zh-CN",{hour:"2-digit",minute:"2-digit"})}const va="niannian.messageInbox",Ns=80,Us="discord-message-badge",js="message-inbox-drawer",Hs="message-inbox-list",qs=".discord-sidebar";let Ye=!1;const Vs=`<svg class="discord-message-btn-svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
  <path fill="currentColor" d="M6 4.5C6 3.12 7.12 2 8.5 2h7C16.88 2 18 3.12 18 4.5V5h1.25A2.75 2.75 0 0 1 22 7.75v8.5A2.75 2.75 0 0 1 19.25 19H4.75A2.75 2.75 0 0 1 2 16.25v-8.5A2.75 2.75 0 0 1 4.75 5H6v-.5zm2.5-.5a.5.5 0 0 0-.5.5V5h8v-.5a.5.5 0 0 0-.5-.5h-7zM4 10h16v1.5H4V10zm0 3.5h10V15H4v-1.5z"/>
</svg>`;function dn(){try{const e=sessionStorage.getItem(va);if(!e)return[];const t=JSON.parse(e);return Array.isArray(t)?t.filter(n=>!!(n&&typeof n=="object"&&typeof n.message=="string")):[]}catch{return[]}}function qo(e){sessionStorage.setItem(va,JSON.stringify(e.slice(0,Ns)))}function Fs(e){return e.filter(t=>!t.read).length}function zs(e){return e==="error"?"错误":e==="info"?"提示":"成功"}function Ws(e){const t=new Date(e),n=new Date,a=t.toDateString()===n.toDateString(),o=t.toLocaleTimeString("zh-CN",{hour:"2-digit",minute:"2-digit"});return a?o:`${t.toLocaleDateString("zh-CN",{month:"numeric",day:"numeric"})} ${o}`}function Ks(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function Js(){return typeof document>"u"?null:document.querySelector(qs)}function Vo(e){Ye=e;const t=Js(),n=document.getElementById(js);t==null||t.classList.toggle("message-drawer-open",e),n==null||n.classList.toggle("is-open",e)}function Tt(){const e=document.getElementById(Hs);if(!e)return;const t=dn();if(!t.length){e.innerHTML='<p class="message-inbox-empty hint">暂无消息</p>';return}e.innerHTML=t.map(n=>`
      <article class="message-inbox-card message-inbox-card--${n.kind}${n.read?"":" unread"}">
        <div class="message-inbox-card-head">
          <span class="message-inbox-card-kind">${zs(n.kind)}</span>
          <time class="message-inbox-card-time">${Ws(n.at)}</time>
        </div>
        <p class="message-inbox-card-body">${Ks(n.message)}</p>
      </article>`).join("")}function et(){if(typeof document>"u")return;const e=document.getElementById(Us);if(!e)return;const t=Fs(dn());e.textContent=t>99?"99+":String(t),e.hidden=t===0}function ga(e,t){const n=dn(),a={id:crypto.randomUUID(),kind:e,message:t,at:Date.now(),read:!1};return n.unshift(a),qo(n),et(),Ye&&Tt(),a}function Gs(){sessionStorage.removeItem(va),et(),Tt()}function Ys(){const e=dn().map(t=>({...t,read:!0}));qo(e),et()}function Fo(e){Vo(e),e&&(Ys(),Tt()),et()}function Xs(){Fo(!Ye)}function Qs(){const e=document.getElementById("discord-message-btn");e&&(e.onclick=a=>{a.preventDefault(),a.stopPropagation(),Xs()});const t=document.getElementById("message-inbox-close");t&&(t.onclick=a=>{a.preventDefault(),a.stopPropagation(),Fo(!1)});const n=document.getElementById("message-inbox-clear");n&&(n.onclick=a=>{a.preventDefault(),a.stopPropagation(),Gs()}),Vo(Ye),et(),Ye&&Tt()}function Zs(){Ye=!1,et()}const er={chat:"聊天",core:"核心",memory:"记忆",wiki:"图谱","virtual-personas":"虚拟人物",vault:"资料库",cloud:"云盘","admin-users":"用户管理",settings:"设置",llm:"模型",token:"Token 用量",skills:"Skill",mcp:"MCP"},ja=[{label:"常用",ids:["chat","memory","vault","wiki","virtual-personas"]},{label:"工具",ids:["mcp","llm","token"]},{label:"运维",ids:["core","cloud","skills","admin-users"]}],Ha={常用:"discord-channel-group-label--common",工具:"discord-channel-group-label--tools",运维:"discord-channel-group-label--ops",其它:"discord-channel-group-label--misc"};function qa(e){return`discord-channel-group-label ${Ha[e]||Ha.其它}`}function tr(e){return er[e]||e}function nr(e,t){const n=new Map(t.filter(([i])=>i!=="settings")),a=[];for(const i of ja){const s=i.ids.filter(r=>n.has(r)).map(r=>{const c=n.get(r),l=e===r?"discord-channel active":"discord-channel";return`<a href="${z(r)}" class="${l}"><span>${c}</span></a>`});s.length&&a.push(`<div class="discord-channel-group">
        <div class="${qa(i.label)}">${i.label}</div>
        ${s.join("")}
      </div>`)}const o=t.filter(([i])=>!ja.some(s=>s.ids.includes(i)));return o.length&&a.push(`<div class="discord-channel-group">
        <div class="${qa("其它")}">其它</div>
        ${o.map(([i,s])=>{const r=e===i?"discord-channel active":"discord-channel";return`<a href="${z(i)}" class="${r}"><span>${s}</span></a>`}).join("")}
      </div>`),a.join("")}function ar(e){const t=tr(e.activePage),n=e.activePage==="settings"?" active":"";return`<div class="discord-app">
  <div id="discord-sidebar-backdrop" class="discord-sidebar-backdrop" aria-hidden="true"></div>
  <aside class="discord-rail" aria-label="快捷导航">
    <button type="button" class="discord-guild-pill discord-guild-pill--avatar${e.activePage==="chat"?" active":""}" data-console-nav="chat" title="念念 · 聊天" aria-label="念念 · 聊天">${kt("nn-avatar-img","",On("auto"))}</button>
    <div class="discord-rail-spacer"></div>
    <button type="button" class="discord-guild-pill discord-guild-pill-muted${n}" data-console-nav="settings" title="设置" aria-label="设置"><span aria-hidden="true">⚙</span></button>
  </aside>
  <div class="discord-main">
    <header class="discord-titlebar">
      <h1 class="discord-titlebar-title">${t}</h1>
      <button type="button" id="discord-menu-toggle" class="discord-menu-toggle" aria-label="切换导航" title="展开/收起导航">☰</button>
    </header>
    <div id="tier-notice-bar" class="discord-titlebar-notice">${e.tierBarHtml}</div>
    <div class="discord-content">${e.bodyHtml}</div>
  </div>
  <aside class="discord-sidebar" aria-label="频道">
    <header class="discord-sidebar-header">
      <span class="discord-sidebar-title">${kt("discord-sidebar-title-avatar nn-avatar-img","念念",On("auto"))}<span>念念</span></span>
      <div class="discord-sidebar-header-actions">
        <button type="button" id="discord-message-btn" class="discord-message-btn" aria-label="消息中心" title="消息中心">
          ${Vs}
          <span id="discord-message-badge" class="discord-message-badge" hidden>0</span>
        </button>
        <span class="discord-sidebar-badge">${e.profile}</span>
        <button type="button" id="discord-sidebar-collapse" class="discord-sidebar-collapse" aria-label="收起导航" title="收起">›</button>
      </div>
    </header>
    <div id="message-inbox-drawer" class="message-inbox-drawer">
      <div class="message-inbox-drawer-head">
        <span class="message-inbox-drawer-title">消息</span>
        <div class="message-inbox-drawer-actions">
          <button type="button" class="secondary message-inbox-clear" id="message-inbox-clear">清空</button>
          <button type="button" class="message-inbox-close" id="message-inbox-close" aria-label="关闭消息">×</button>
        </div>
      </div>
      <div id="message-inbox-list" class="message-inbox-list scroll-hidden-y"></div>
      <p class="message-inbox-foot hint">本次浏览器会话 · 刷新保留 · 关闭浏览器后清空</p>
    </div>
    <nav class="discord-channels">${e.navHtml}</nav>
    <footer class="discord-userbar">
      <div class="discord-avatar" aria-hidden="true">${Ds(e.role,e.userAvatarUrl)}</div>
      <div class="discord-user-meta">
        <div class="discord-username">${e.role}</div>
        <div class="discord-user-status">${e.profile} · Console</div>
      </div>
      <span id="discord-status-dot" class="status-dot ${e.healthClass}" title="API 状态"></span>
    </footer>
  </aside>
</div>`}const zo="niannian.sidebarCollapsed";function or(){return sessionStorage.getItem(zo)==="1"}function Va(e){sessionStorage.setItem(zo,e?"1":"0"),document.body.classList.toggle("discord-sidebar-collapsed",e),e||document.body.classList.remove("discord-sidebar-open")}function ir(){document.body.classList.toggle("discord-sidebar-collapsed",or());const e=document.getElementById("discord-menu-toggle"),t=document.getElementById("discord-sidebar-collapse"),n=document.getElementById("discord-sidebar-backdrop"),a=()=>document.body.classList.remove("discord-sidebar-open");e==null||e.addEventListener("click",o=>{if(o.stopPropagation(),window.innerWidth<=768){document.body.classList.toggle("discord-sidebar-open");return}Va(!document.body.classList.contains("discord-sidebar-collapsed"))}),t==null||t.addEventListener("click",o=>{o.stopPropagation(),Va(!0)}),n==null||n.addEventListener("click",a),Qs(),Tt()}const U={apiBase:"niannian.apiBase",apiToken:"niannian.apiToken",memorySpace:"niannian.memorySpace",projectLegacy:"niannian.project",userRole:"niannian.userRole",sessionId:"niannian.sessionId",sessionToken:"niannian.sessionToken",agentLocalBase:"niannian.agentLocalBase",controlPlaneBase:"niannian.controlPlaneBase",showTrace:"niannian.showTrace",consoleProfile:"niannian.consoleProfile",capabilities:"niannian.capabilities"};function sr(){const e=localStorage.getItem(U.memorySpace);if(e)return e==="global"?"work":e;const t=localStorage.getItem(U.projectLegacy);return t?t==="global"?"work":t:"work"}const Wo={apiBase:localStorage.getItem(U.apiBase)||window.location.origin,apiToken:localStorage.getItem(U.apiToken)||"",memorySpace:sr(),userRole:localStorage.getItem(U.userRole)||"owner",health:"unknown",runtimeMode:"unknown",sessionId:localStorage.getItem(U.sessionId),sessionToken:localStorage.getItem(U.sessionToken),agentLocalBase:localStorage.getItem(U.agentLocalBase)||"http://127.0.0.1:18765",controlPlaneBase:localStorage.getItem(U.controlPlaneBase)||localStorage.getItem(U.apiBase)||"",consoleProfile:localStorage.getItem(U.consoleProfile)||"docker",capabilities:rr(localStorage.getItem(U.capabilities)),showTrace:localStorage.getItem(U.showTrace)!=="false",tierView:null,tierAlerts:[]};function rr(e){if(!e)return[];try{const t=JSON.parse(e);return Array.isArray(t)?t.map(String):[]}catch{return[]}}function C(){return Wo}function j(e){Object.assign(Wo,e),e.apiBase!==void 0&&localStorage.setItem(U.apiBase,e.apiBase),e.agentLocalBase!==void 0&&localStorage.setItem(U.agentLocalBase,e.agentLocalBase),e.controlPlaneBase!==void 0&&localStorage.setItem(U.controlPlaneBase,e.controlPlaneBase),e.apiToken!==void 0&&localStorage.setItem(U.apiToken,e.apiToken),e.memorySpace!==void 0&&localStorage.setItem(U.memorySpace,e.memorySpace),e.userRole!==void 0&&localStorage.setItem(U.userRole,e.userRole),e.sessionId!==void 0&&(e.sessionId?localStorage.setItem(U.sessionId,e.sessionId):localStorage.removeItem(U.sessionId)),e.sessionToken!==void 0&&(e.sessionToken?localStorage.setItem(U.sessionToken,e.sessionToken):localStorage.removeItem(U.sessionToken)),e.consoleProfile!==void 0&&localStorage.setItem(U.consoleProfile,e.consoleProfile),e.capabilities!==void 0&&localStorage.setItem(U.capabilities,JSON.stringify(e.capabilities)),e.showTrace!==void 0&&localStorage.setItem(U.showTrace,String(e.showTrace))}function cr(e){j({apiBase:"http://127.0.0.1:18765"})}const lr=["/v1/chat","/v1/mcp","/v1/virtual-personas","/v1/memory","/v1/vault","/v1/wiki","/v1/llm","/v1/upload","/v1/client","/health"],dr=["/v1/auth","/v1/admin","/v1/tenant","/v1/cloud","/v1/tier","/v1/config","/v1/core"],ur="http://127.0.0.1:18765";function Rn(){return(C().agentLocalBase||ur).replace(/\/$/,"")}function un(){const e=C();return(e.controlPlaneBase||e.apiBase||"").replace(/\/$/,"")}function Ko(e){const t=C();if(t.consoleProfile==="docker")return t.apiBase.replace(/\/$/,"");const n=e.split("?")[0];return dr.some(a=>n.startsWith(a))?un():(lr.some(a=>n.startsWith(a)),Rn())}function pr(e){return C().consoleProfile==="docker"?"single":Ko(e)===un()?"control":"agent"}class Y extends Error{constructor(t,n){super(`HTTP ${t}: ${n.slice(0,200)}`),this.status=t,this.body=n}}async function se(e,t){const n=Ko(e),a=pr(e),o=tt(t==null?void 0:t.headers,a),i=`${n}${e}`;try{return await fetch(i,{...t,headers:o})}catch(s){const r=s instanceof Error?s.message:String(s);throw new Error(`无法连接 API（${i}）：${r}`)}}function tt(e,t="single"){const n=new Headers(e),a=C(),o=a.apiToken.trim();o&&(t==="single"||t==="control")&&!n.has("Authorization")&&n.set("Authorization",`Bearer ${o}`),a.sessionToken&&!n.has("X-NianNian-Session")&&n.set("X-NianNian-Session",a.sessionToken),(t==="control"||t==="single")&&!n.has("X-NianNian-Agent")&&a.consoleProfile==="client"&&n.set("X-NianNian-Agent","desktop");const s=a.userRole.trim()||"owner";return n.has("X-NianNian-Role")||n.set("X-NianNian-Role",s),n}async function y(e,t){const n=await se(e,t),a=await n.text();if(!n.ok)throw new Y(n.status,a);return a?JSON.parse(a):{}}async function N(e,t){return y(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})}function Qt(e){const t=ke(e);return t==="auto"?"auto":t==="default"?"normal":t==="companion"?"companion":t==="work"?"work":t==="education"?"education":"normal"}const mr=["global","auto","normal","work","companion","education"];function fr(e){return mr.includes(e.trim().toLowerCase())}const hr="聊天按模式写入 auto / normal / work / companion / education（学习）；念念内核使用 global，其它空间可读取 global 数据。",Jo=[{value:"auto",label:"auto（自动）"},{value:"normal",label:"normal（正常）"},{value:"work",label:"work（工作）"},{value:"companion",label:"companion（陪伴）"},{value:"education",label:"education（学习）"},{value:"global",label:"global（念念内核）"}],vr=Object.fromEntries(Jo.map(e=>[e.value,e.label]));function gr(e){const t=Jo.map(n=>n.value);return[...new Set([...e,...t])].sort()}function Go(e,t){return gr(e).map(a=>{const o=vr[a]||a;return`<option value="${a}"${a===t?" selected":""}>${o}</option>`}).join("")}async function yr(){return y("/v1/chat/personas")}async function ya(e){return y(`/v1/chat/sessions/${e}`)}async function Re(e,t){return y(`/v1/chat/sessions/${e}/meta`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})}async function ba(e){const t=await N("/v1/chat/sessions",{project:e});return j({sessionId:t.id}),t.id}function Yo(e,t){var n;if(e!==404)return!1;try{return((n=JSON.parse(t).detail)==null?void 0:n.code)==="session_not_found"}catch{return t.includes("session_not_found")}}function br(e){return e.code==="session_not_found"}async function K(e){const t=Qt(e),n=C().sessionId;if(n){const a=await se(`/v1/chat/sessions/${n}`);if(a.ok){const o=await a.json();if(o.project&&o.project!==t)j({sessionId:null});else return n}else{const o=await a.text().catch(()=>"");if(Yo(a.status,o))j({sessionId:null});else throw new Y(a.status,o)}}return ba(t)}async function Lt(e){return(await y(`/v1/chat/sessions/${e}/messages`)).messages||[]}async function Xo(e,t){return y(`/v1/chat/sessions/${e}/stickers`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sticker_id:t})})}function Fa(e,t,n){var s,r;const a=e.split(`
`);let o="message",i="";for(const c of a)c.startsWith("event:")&&(o=c.slice(6).trim()),c.startsWith("data:")&&(i+=c.slice(5).trim());if(i)try{const c=JSON.parse(i);o==="delta"&&(n.hadDelta=!0,t.onDelta(String(c.text||""))),o==="trace"&&t.onTrace(c.lines||[],c.tree||void 0),o==="sticker"&&((s=t.onSticker)==null||s.call(t,c)),o==="plan"&&((r=t.onPlan)==null||r.call(t,c)),o==="done"&&(n.ingested=!!c.ingested,t.onDone(c)),o==="error"&&(n.streamError=String(c.message||i),n.recoverable=!!c.recoverable,br(c)&&(n.sessionLost=!0),t.onError(n.streamError))}catch{o==="error"&&(n.streamError=i,t.onError(i))}}async function St(e,t){const n=e.body.getReader(),a=new TextDecoder;let o="";const i={hadDelta:!1,streamError:void 0,ingested:!1,recoverable:!1,sessionLost:!1};for(;;){const{done:s,value:r}=await n.read();if(s)break;o+=a.decode(r,{stream:!0});const c=o.split(`

`);o=c.pop()||"";for(const l of c)Fa(l,t,i)}return o+=a.decode(void 0,{stream:!1}),o.trim()&&Fa(o,t,i),i}async function Qo(e,t,n,a={}){var p;const o=C().apiBase.replace(/\/$/,""),i={text:t,stream:!0,show_trace:a.showTrace??C().showTrace,project:Qt(a.chatMode)};(p=a.parts)!=null&&p.length&&(i.parts=a.parts),a.planMode!==void 0&&(i.plan_mode=a.planMode);const s=JSON.stringify(i);async function r(m){return fetch(`${o}/v1/chat/sessions/${m}/messages`,{method:"POST",headers:tt({"Content-Type":"application/json",Accept:"text/event-stream"}),body:s,signal:AbortSignal.timeout(12e4)})}async function c(m){const g=await m.text().catch(()=>""),v=g?`HTTP ${m.status}: ${g.slice(0,200)}`:`HTTP ${m.status}`;return n.onError(v),{ok:!1,error:v}}let l=await r(e);if(!l.ok){const m=await l.text().catch(()=>"");if(Yo(l.status,m)){if(j({sessionId:null}),e=await K(),l=await r(e),!l.ok)return await c(l)}else return await c(l)}if(!l.body)return await c(l);let u=await St(l,n);if(u.sessionLost&&!u.hadDelta){if(j({sessionId:null}),e=await K(),l=await r(e),!l.ok||!l.body)return await c(l);u=await St(l,n)}else if(u.sessionLost)return{ok:!1,error:"会话已失效，请重发",ingested:u.ingested};return u.streamError&&!(u.recoverable&&u.hadDelta)?{ok:!1,error:u.streamError,ingested:u.ingested}:u.hadDelta?{ok:!0,ingested:u.ingested}:{ok:!1,error:"（无回复）",ingested:u.ingested}}async function Zo(e,t,n,a={}){const o=C().apiBase.replace(/\/$/,""),i={stream:!0,show_trace:a.showTrace??C().showTrace};a.planMode!==void 0&&(i.plan_mode=a.planMode);const s=await fetch(`${o}/v1/chat/sessions/${e}/turns/${encodeURIComponent(t)}/regenerate`,{method:"POST",headers:tt({"Content-Type":"application/json",Accept:"text/event-stream"}),body:JSON.stringify(i),signal:AbortSignal.timeout(12e4)});if(!s.ok){const c=await s.text().catch(()=>""),l=c?`HTTP ${s.status}: ${c.slice(0,200)}`:`HTTP ${s.status}`;return n.onError(l),{ok:!1,error:l}}if(!s.body)return n.onError(`HTTP ${s.status}`),{ok:!1,error:`HTTP ${s.status}`};const r=await St(s,n);return r.streamError&&!(r.recoverable&&r.hadDelta)?{ok:!1,error:r.streamError,ingested:r.ingested}:r.hadDelta?{ok:!0,ingested:r.ingested}:{ok:!1,error:"（无回复）",ingested:r.ingested}}async function ei(e,t){return y(`/v1/chat/sessions/${e}/messages/${t}/recall`,{method:"POST"})}async function wr(e,t){return y(`/v1/chat/sessions/${e}/turns/${t}/remember`,{method:"POST"})}async function ti(e,t,n){return y(`/v1/chat/sessions/${e}/messages/${t}/enrichments/${encodeURIComponent(n)}/import-vault`,{method:"POST"})}async function ni(e,t,n){return y(`/v1/chat/sessions/${e}/messages/${t}/enrichments/${encodeURIComponent(n)}/learn`,{method:"POST"})}async function ai(e,t,n){return y(`/v1/chat/sessions/${e}/messages/${t}/enrichments/${encodeURIComponent(n)}/remember`,{method:"POST"})}const _r=Object.freeze(Object.defineProperty({__proto__:null,createSession:ba,ensureSession:K,fetchChatPersonas:yr,fetchSession:ya,fetchSessionMessages:Lt,importEnrichmentVault:ti,learnEnrichment:ni,patchSessionMeta:Re,readSseStream:St,recallMessage:ei,regenerateChatStream:Zo,rememberEnrichment:ai,rememberTurn:wr,sendChatSticker:Xo,sendChatStream:Qo},Symbol.toStringTag,{value:"Module"}));function wa(e){return`/v1/virtual-personas/${encodeURIComponent(e)}/avatar`}async function oi(){return y("/v1/virtual-personas")}async function _a(e){return y(`/v1/virtual-personas/${encodeURIComponent(e)}`)}async function kr(e){return y(`/v1/virtual-personas/${encodeURIComponent(e)}/stickers`)}async function $r(e,t={}){return y(`/v1/virtual-personas/${encodeURIComponent(e)}/stickers/generate`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})}async function Sr(e,t={}){return y(`/v1/virtual-personas/${encodeURIComponent(e)}/stickers/search`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})}async function Er(e,t){return y(`/v1/virtual-personas/${encodeURIComponent(e)}/stickers/import-from-url`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})}async function Pr(e){return y("/v1/virtual-personas",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)})}const Et=["性格与做事","作品与经历","人物故事","对用户的称呼"];async function Ir(e,t){return y(`/v1/virtual-personas/${encodeURIComponent(e)}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})}async function Br(e){await y(`/v1/virtual-personas/${encodeURIComponent(e)}`,{method:"DELETE"})}async function ii(e){const t=await se(wa(e));if(t.status===404)return null;if(!t.ok){const a=await t.text();throw new Error(`HTTP ${t.status}: ${a.slice(0,200)}`)}const n=await t.blob();return URL.createObjectURL(n)}function si(e){e!=null&&e.startsWith("blob:")&&URL.revokeObjectURL(e)}async function Cr(e,t){const n=new FormData;return n.append("file",t),y(wa(e),{method:"PUT",body:n})}async function Tr(e){return y(wa(e),{method:"DELETE"})}async function Lr(e,t={}){return y(`/v1/virtual-personas/${encodeURIComponent(e)}/enrich`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})}async function Ar(e,t){return y(`/v1/virtual-personas/${encodeURIComponent(e)}/import-source`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})}async function za(e,t){return y(`/v1/chat/sessions/${e}/equip`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({vp_id:t})})}async function Wa(e){return y(`/v1/chat/sessions/${e}/unequip`,{method:"POST"})}function xr(e){var t;if(e instanceof Y)try{return((t=JSON.parse(e.body).detail)==null?void 0:t.message)||e.message}catch{return e.message}return String(e)}async function Ka(e,t){return y(`/v1/virtual-personas/${encodeURIComponent(e)}/pack`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})}async function Mr(e){return y(`/v1/virtual-personas/${encodeURIComponent(e)}/pack`)}async function bn(e){return y(`/v1/virtual-personas/${encodeURIComponent(e)}/deep-learn/start`,{method:"POST"})}async function ri(e,t){return y(`/v1/virtual-personas/${encodeURIComponent(e)}/deep-learn/run`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({task_id:t.task_id,mock:t.mock??!0})})}async function Or(e){return y(`/v1/virtual-personas/${encodeURIComponent(e)}/deep-learn/complete`,{method:"POST"})}async function Rr(e,t={}){return y(`/v1/virtual-personas/${encodeURIComponent(e)}/deep-learn/run-all`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({mock:t.mock??!0})})}const Dr={include_full_l2:!1,include_stickers:!1,include_proactive_profile:!1,include_deep_learn_runs:!1};async function Ja(e,t={},n=!1){return y(`/v1/virtual-personas/${encodeURIComponent(e)}/perfect-distill`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({export_options:{...Dr,...t},write:n})})}async function Nr(e){return y(`/v1/virtual-personas/${encodeURIComponent(e)}/exports`)}async function Ur(e){const t=new FormData;return t.append("bundle",e),y("/v1/virtual-personas/import",{method:"POST",body:t})}function ci(e,t){const n=t==="assembly_master"?"assembly-master":"profile-md";return`/v1/virtual-personas/${encodeURIComponent(e)}/${n}`}async function li(e,t){return y(ci(e,t))}async function jr(e,t,n,a){return y(ci(e,t),{method:"PUT",headers:{"Content-Type":"application/json","If-Match":a},body:JSON.stringify({content:n})})}async function Hr(e){try{return await ri(e,{task_id:"__probe__",mock:!0}),"deep_learn"}catch(t){const n=xr(t),a=n.match(/got '([^']+)'/);if((a==null?void 0:a[1])==="customize")return"customize";if((a==null?void 0:a[1])==="daily")return"daily";if((a==null?void 0:a[1])==="perfect")return"perfect";if(n.includes("tasks not found")||n.includes("unknown task_id"))return"deep_learn"}return"customize"}async function qr(e){const t=C().apiBase.replace(/\/$/,""),n=await fetch(`${t}/v1/virtual-personas/${encodeURIComponent(e)}/trial/chat`,{method:"POST",headers:tt({"Content-Type":"application/json"}),body:JSON.stringify({text:"",stream:!1,show_trace:!1}),signal:AbortSignal.timeout(6e4)});if(!n.ok){const i=await n.text().catch(()=>"");throw new Y(n.status,i)}const a=await n.json(),o=a.session_id;if(!o)throw new Error("trial session missing session_id");return j({sessionId:o}),{sessionId:o,trial:!!a.trial,equippedVpId:String(a.equipped_vp_id||e)}}async function Vr(){return y("/v1/tenant/pending-fetches")}async function Fr(e){return N("/v1/tenant/fetch-response",e)}const zr=15e3;let Dt=null,Ga=!1;function di(e,t=Date.now()){const n=Date.parse(e);return Number.isNaN(n)?!0:n<=t}async function Wr(e){if(e==="virtual_personas")return oi();throw new Error(`unsupported fetch resource: ${e}`)}async function Kr(e){if(di(e.expires_at))return!1;const t=await Wr(e.resource);return await Fr({fetch_id:e.fetch_id,push_token:e.push_token,payload:t}),!0}function pn(){const e=C();return!!e.sessionToken&&e.userRole==="tenant"}async function ui(){if(!pn())return 0;try{const e=await Vr(),t=Array.isArray(e.items)?e.items:[];let n=0;for(const a of t)if(!di(a.expires_at))try{await Kr(a)&&(n+=1)}catch(o){console.warn("controlInbox: item failed",a.fetch_id,o)}return n}catch(e){if(e instanceof Y&&(e.status===404||e.status===501))return 0;throw e}}function Jr(){document.visibilityState==="visible"&&pn()&&ui().catch(e=>console.warn("controlInbox: poll failed",e))}function Ya(){if(!pn()){ka();return}document.visibilityState!=="hidden"&&ui().catch(e=>console.warn("controlInbox: poll failed",e))}function pi(){ka(),pn()&&(Ya(),Dt=setInterval(Ya,zr),Ga||(document.addEventListener("visibilitychange",Jr),Ga=!0))}function ka(){Dt&&(clearInterval(Dt),Dt=null)}const Xa=[["chat","聊天"],["core","核心"],["memory","记忆"],["wiki","图谱"],["virtual-personas","虚拟人物"],["vault","资料库"],["cloud","云盘"],["admin-users","用户管理"],["llm","模型"],["token","Token 用量"],["skills","Skill"],["mcp","MCP"]],Gr=["chat","memory","vault","wiki","virtual-personas","mcp","llm","token"],Yr=["settings"];function be(e,t){if(!e.length)return!1;if(t.endsWith(".*")){const n=t.slice(0,-2);return e.some(a=>a===t||a===n||a.startsWith(`${n}.`))}return e.includes(t)}function mi(e,t={}){const n=t.forcedProfile;return n==="client"||n==="docker"?n:be(e,"core.*")?"docker":e.length>0?"client":t.legacyIntranet?"docker":"client"}function fi(e,t,n="owner"){return e==="client"?Xa.filter(([a])=>Gr.includes(a)):Xa.filter(([a])=>!(a==="core"&&(n==="guest"||t.length&&!be(t,"core.*"))||a==="skills"&&(n==="guest"||t.length&&!be(t,"skill.edit")&&!be(t,"skill.distill"))||a==="cloud"&&(n==="guest"||t.length&&!be(t,"tier.*")&&!be(t,"cloud.pinn")&&!be(t,"cloud.pinn.upload"))||a==="admin-users"&&(n!=="owner"||t.length&&!be(t,"admin.users.*"))))}function Xr(e,t,n,a){return Yr.includes(e)?!0:new Set(fi(t,n,a).map(([i])=>i)).has(e)}function Qr(){const e=C();return fi(e.consoleProfile,e.capabilities,e.userRole)}async function ht(){return y("/v1/config")}async function Dn(e){const t=await se("/v1/config",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)}),n=await t.text();if(!t.ok)throw new Error(`HTTP ${t.status}: ${n.slice(0,200)}`);const a=n?JSON.parse(n):{};return a.config??a}async function Zr(){return y("/v1/config/providers")}async function mn(e=8e3){return y("/v1/sync/status",{signal:AbortSignal.timeout(e)})}async function ec(e=!1){return N(`/v1/sync/push?force=${e?"true":"false"}`,{})}async function tc(){return N("/v1/sync/pull",{})}async function nc(e){const t=e?`?project=${encodeURIComponent(e)}`:"";return N(`/v1/dream${t}`,{})}async function ac(){return y("/v1/mcp/servers")}async function oc(e){return N(`/v1/mcp/servers/${encodeURIComponent(e)}/probe`,{})}const ic=25e3,sc=12e3;function hi(e){let t;if(e)try{t=new URL(e).hostname}catch{}else typeof window<"u"&&(t=window.location.hostname);return t==="127.0.0.1"||t==="localhost"?sc:ic}async function Pt(e,t){const n=e??hi(t);return y("/health",{signal:AbortSignal.timeout(n)})}const vi=["quark","baidu","onedrive"];function Qa(e){if(!e)return"—";try{return new Date(e).toLocaleString()}catch{return e}}function Nt(e,t){return t==="baidu_failover"?e==="baidu":t==="onedrive_failover"?e==="onedrive":t==="feimao_failover"?!1:e==="quark"&&t==="quark"}function rc(e){return e!=="quark"&&e!=="local_degraded"}function cc(e){if(!e)return 0;const t=e.pending_uploads;return typeof t=="number"?t:Number(t)||0}function Za(e,t){const n=t||String((e==null?void 0:e.active_role)||"quark");return{isLocalDegraded:n==="local_degraded",pendingUploads:cc(e),isFailover:rc(n)}}function lc(e){return Array.isArray(e==null?void 0:e.chain)&&e.chain.length>0?e.chain.map(String).filter(t=>t!=="feimao"):[...vi]}function wn(e,t,n,a,o,i){const s=String((n==null?void 0:n.role)||(i==null?void 0:i.role)||o),r=String((n==null?void 0:n.status)||(n==null?void 0:n.mode)||"—");return{id:e,name:t,role:s,status:r,mode:n!=null&&n.mode?String(n.mode):void 0,mirrorLast:(n==null?void 0:n.mirror_last)??null,lastError:(n==null?void 0:n.last_error)??null,failoverReady:n==null?void 0:n.failover_ready,isPrimary:Nt(e,a)}}function dc(e,t){const n=e==null?void 0:e.feimao,a=t==null?void 0:t.feimao;if(!n&&!a)return null;const o=String((n==null?void 0:n.role)||(a==null?void 0:a.role)||"disabled"),i=String((n==null?void 0:n.status)||(a==null?void 0:a.auth)||"retired");return o==="disabled"||i==="retired"||i==="disabled"?{role:o,status:i,auth:a==null?void 0:a.auth,note:"v0.2.9 已退出 tier · 仅保留 Vault 可选只读"}:{role:o,status:i,auth:a==null?void 0:a.auth,note:"飞猫已退场 · 请迁移至 OneDrive 第三顺位"}}function Zt(e){var a,o,i;if(!e)return[];const t=(a=e.tier)==null?void 0:a.recent_alerts;if(Array.isArray(t)&&t.length>0)return t;const n=(i=(o=e.storage)==null?void 0:o.tier)==null?void 0:i.recent_alerts;return Array.isArray(n)&&n.length>0?n:[]}function Ut(e,t){var w,S;const n=(e==null?void 0:e.storage)||{},a=n.credentials,o=n.tier,i=t==null?void 0:t.tier,s=o&&o.quark?o:i,r=Zt(e),c=dc(s,a);if(s&&s.quark){const B=String(s.active_role||"quark"),L=lc(s),D=s.onedrive??{},T=[wn("quark","夸克",s.quark,B,"primary"),wn("baidu","百度",s.baidu,B,"failover+mirror",a==null?void 0:a.baidu),wn("onedrive","OneDrive",D,B,"failover+mirror",a==null?void 0:a.onedrive)],H=Za(s,B);return{activeRole:B,isFailover:H.isFailover,isLocalDegraded:H.isLocalDegraded,pendingUploads:H.pendingUploads,chain:L,failoverSince:s.failover_since??null,recentAlerts:r,clouds:T,feimaoDeprecated:c}}const l=n.clouds||(t==null?void 0:t.backends)||{},u=n.backup||(t==null?void 0:t.backup)||{},p=(t==null?void 0:t.mirror)||{},m=String(n.primary_mode||(t==null?void 0:t.primary_mode)||"mirror"),g=String(l.quark||"mirror"),v=String(l.baidu||"mirror"),k=String(l.onedrive||"mirror"),E=p.baidu,I=p.onedrive,_=i,P=String((o==null?void 0:o.active_role)||(_==null?void 0:_.active_role)||((w=e==null?void 0:e.tier)==null?void 0:w.active_role)||"quark"),f=Za(_||o,P);return{activeRole:P,isFailover:f.isFailover,isLocalDegraded:f.isLocalDegraded,pendingUploads:f.pendingUploads,chain:[...vi],failoverSince:null,recentAlerts:r,feimaoDeprecated:c,clouds:[{id:"quark",name:"夸克",role:"主库（实时读写）",status:g==="live"?"ok":g,mode:m,isPrimary:Nt("quark",P)},{id:"baidu",name:"百度",role:"failover + mirror",status:v==="live"?"ok":v,mirrorLast:(E==null?void 0:E.last_run)??u.last_run??null,failoverReady:!!u.baidu_configured,isPrimary:Nt("baidu",P)},{id:"onedrive",name:"OneDrive",role:String(((S=a==null?void 0:a.onedrive)==null?void 0:S.role)||"failover+mirror"),status:k==="live"?"ok":String((I==null?void 0:I.status)||k||"unconfigured"),mirrorLast:(I==null?void 0:I.last_run)??null,isPrimary:Nt("onedrive",P)}]}}function uc(e){const t=e.toLowerCase();return t==="offline"||t==="failed"||t==="error"||t==="down"||t==="unavailable"||t==="disconnected"}function pc(e){return`${e.provider||""}|${e.code}|${e.at}|${e.message||""}`}function mc(e){const t=(e||"").toLowerCase();return t==="error"||t==="critical"?"error":"info"}function fc(e,t,n=[]){const a=[];if(t==="offline")return a.push({kind:"error",message:"API 离线 · 无法连接念念 · tier 状态未知",dedupeKey:"health:offline"}),a;if(e!=null&&e.isLocalDegraded){const s=e.pendingUploads??0;return a.push({kind:"error",message:`local_degraded · tier 链均不可达 · L0 写队列 ${s} 条 · 数据未丢，请修复网络或手动 drain`,dedupeKey:"tier:local_degraded"}),a}for(const s of n.slice(0,5)){const r=s.provider?`[${s.provider}] `:"",c=s.message||s.code||"tier 事件";a.push({kind:mc(s.severity),message:`${r}${s.code} · ${c} · ${Qa(s.at)}`,dedupeKey:pc(s)})}if(!e)return a;const o=e.clouds.find(s=>s.id==="quark"),i=o&&uc(o.status);if(e.isFailover){const s=e.failoverSince?` · 自 ${Qa(e.failoverSince)}`:"";a.push({kind:"info",message:`tier failover · active_role=${e.activeRole}${s}`,dedupeKey:`tier:failover:${e.activeRole}:${e.failoverSince||""}`})}else i?a.push({kind:"error",message:`夸克 ${o==null?void 0:o.status} · 主库异常，等待 failover`,dedupeKey:`tier:quark:${(o==null?void 0:o.status)||"offline"}`}):t==="degraded"&&a.push({kind:"info",message:`存储 degraded · active_role=${e.activeRole}`,dedupeKey:`tier:degraded:${e.activeRole}`});return a}const eo=new Set;function gi(e,t,n=[]){for(const a of fc(e,t,n))eo.has(a.dedupeKey)||(eo.add(a.dedupeKey),ga(a.kind,a.message))}const hc=2e3,vc=2;let Nn=0;function gc(e){return new Promise(t=>setTimeout(t,e))}async function yc(e){const t=hi(e);try{return await Pt(t,e)}catch{return await gc(hc),Pt(t,e)}}function yi(){const e=C(),t=document.getElementById("discord-status-dot")||document.querySelector("header .status-dot");t&&(t.className=`status-dot ${wi(e.health)}`);const n=document.getElementById("tier-notice-bar");n&&(n.innerHTML=""),gi(e.tierView,e.health,e.tierAlerts);const a=document.getElementById("sendChat");a&&(a.disabled=e.health==="offline"||e.health==="unknown")}function bc(e,t){Nn=0;const n=Ut(e,t),a=Zt(e);j({health:e.status==="degraded"?"degraded":"ok",tierView:n,tierAlerts:a})}function wc(){Nn+=1,!(Nn<vc)&&j({health:"offline"})}async function bi(e){const t=C(),n=t.consoleProfile==="client"?Rn():t.apiBase;try{const[a,o]=await Promise.all([yc(n),mn().catch(()=>null)]);bc(a,o);try{const s=(await ht()).runtime||{};j({runtimeMode:String(s.mode||"unknown")})}catch{}}catch{wc()}yi()}function wi(e){return e==="ok"?"ok":e==="degraded"?"warn":e==="offline"?"err":""}const $a="/v1/users/me/avatar";async function _i(){const e=await se($a);if(e.status===404)return null;if(!e.ok){const n=await e.text();throw new Error(`HTTP ${e.status}: ${n.slice(0,200)}`)}const t=await e.blob();return URL.createObjectURL(t)}function Sa(e){e!=null&&e.startsWith("blob:")&&URL.revokeObjectURL(e)}async function _c(e){const t=new FormData;return t.append("file",e),y($a,{method:"PUT",body:t})}async function kc(){return y($a,{method:"DELETE"})}let we=null,Un=!1,De=null;function $c(){return we}async function Ea(e=!1){return!e&&Un&&!De?we:De||(De=(async()=>{Sa(we),we=null;try{we=await _i()}catch{we=null}return Un=!0,De=null,we})(),De)}function Sc(){Sa(we),we=null,Un=!1,De=null}const Ec=3200,Pc=4e3,Ic=4;let to="",no=0;function Bc(){let e=document.getElementById("nn-toast-stack");return e||(e=document.createElement("div"),e.id="nn-toast-stack",e.className="toast-stack",e.setAttribute("aria-live","polite"),document.body.appendChild(e)),e}function ao(e){var a;const t=[...e.querySelectorAll(".toast:not(.toast-hide)")],n=t.length;for(t.forEach((o,i)=>{const s=n-1-i;o.style.setProperty("--toast-depth",String(s)),o.style.zIndex=String(1e3-s)});t.length>Ic;)(a=t[0])==null||a.remove()}function d(e,t="success"){const n=`${t}\0${e}`,a=Date.now();if(n===to&&a-no<Pc)return;to=n,no=a,ga(t,e);const o=Bc(),i=document.createElement("div");i.className=`toast toast-${t}`,i.innerHTML=`
    <div class="toast-head">
      <span class="toast-kind">${t==="error"?"错误":t==="info"?"提示":"成功"}</span>
      <span class="toast-time">刚刚</span>
    </div>
    <p class="toast-body">${e.replace(/&/g,"&amp;").replace(/</g,"&lt;")}</p>`,o.appendChild(i),requestAnimationFrame(()=>{i.classList.add("toast-show"),ao(o)});const s=()=>{i.classList.remove("toast-show"),i.classList.add("toast-hide"),window.setTimeout(()=>{i.remove(),ao(o)},280)};window.setTimeout(s,Ec)}const oo=Object.freeze(Object.defineProperty({__proto__:null,showToast:d},Symbol.toStringTag,{value:"Module"})),Cc=8e3;let io=0;function fe(e){if(e instanceof Y&&e.status===401){const n=Date.now();if(n-io<Cc)return;io=n,d("未配置 API Token：请打开「设置」，粘贴 NIANNIAN_API_TOKEN 并保存","error");return}const t=e instanceof Error?e.message:String(e);d(t.slice(0,240),"error")}async function Tc(e=7){return y(`/v1/token/usage?days=${e}`)}async function ki(){return y("/v1/token/budget")}async function Lc(e){return y("/v1/token/budget",{method:"PATCH",body:JSON.stringify(e)})}const Ac=[{value:"off",label:"关闭",hint:"不调用模型 · 规则降级回复 · 不跑记忆判定 LLM"},{value:"light",label:"轻度",hint:"仅对话主回复（小思/right）· 记忆判定用规则"},{value:"heavy",label:"重度",hint:"回复 + 记忆 ingest 判定 + 记忆查询归纳 + 后续模式 LLM（v0.17）"}],xc=[{value:"off",label:"关闭",hint:"不自动写入 · 手动「记一下」仍可用"},{value:"manual_only",label:"仅手动",hint:"日常对话不自动入库 · 在助手消息底栏点「记一下」"},{value:"batch",label:"批量提炼",hint:"手动 + 会话 idle ≥30min 后批量提炼重要对话"}],Mc=[{value:"off",label:"关闭",hint:"用户与念念均不发送表情包 · 隐藏 😊 按钮"},{value:"light",label:"轻度",hint:"用户可发 · 念念按语境/性格/语气主动回表情（短回复可纯表情）"},{value:"heavy",label:"重度",hint:"轻度 + 长回复可附带表情 · 语境/性格 label 可写入长期记忆"}],Oc=[{value:"once",label:"一次",hint:"每段安静期最多主动开口 1 次 · companion 15 分钟无回复后重置"},{value:"few",label:"少量",hint:"每段安静期随机 2–3 次 · companion 15 分钟无回复后重置"},{value:"unlimited",label:"不限",hint:"持续按 profile 主动开口 · 防抖 ≥15s"}],Rc=[{value:"auto",label:"自动",hint:"按 profile 节奏调度 · 用户反馈可自动调节"},{value:"quiet",label:"少打扰",hint:"手动锁定少打扰 · 主动间隔显著拉长"}],so={virtual_persona_enabled:{label:"虚拟人物装填",configKey:"virtual_persona_enabled · Chat 扮演角色块"},virtual_persona_enrich_enabled:{label:"虚拟人物联网建档",configKey:"virtual_persona_enrich_enabled"},virtual_persona_avatar_fetch_enabled:{label:"虚拟人物 enrich 抓头像",configKey:"virtual_persona_avatar_fetch_enabled · enrich 时下载 og:image"},proactive_chat_enabled:{label:"念念主动对话",configKey:"proactive_chat_enabled · Chat 页内话停后开口；工作模式始终关闭；正常/学习轻陪、陪伴积极引导"},dream_consolidation_enabled:{label:"离线 dream 巩固",configKey:"dream_consolidation_enabled · 预留门禁"},web_learning_enabled:{label:"联网技能自学",configKey:"web_learning_enabled · 预留门禁"},cultural_learning_enabled:{label:"文史自学",configKey:"cultural_learning_enabled · 预留门禁"},embedding_recall_enabled:{label:"向量召回 / 嵌入",configKey:"embedding_recall_enabled · 预留门禁"}};async function Dc(e){return y(`/v1/chat/proactive-profile?session_id=${encodeURIComponent(e)}`)}async function Nc(e,t,n={}){const a=C().apiBase.replace(/\/$/,""),o=await fetch(`${a}/v1/chat/sessions/${e}/proactive`,{method:"POST",headers:tt({"Content-Type":"application/json",Accept:"text/event-stream"}),body:JSON.stringify({stream:!0,scenario:n.scenario,local_hour:new Date().getHours()}),signal:AbortSignal.timeout(12e4)});if(o.status===204)return{ok:!0,status:204};if(!o.ok){const r=await o.text().catch(()=>"");return t.onError(r||`HTTP ${o.status}`),{ok:!1,status:o.status,error:r}}if(!o.body)return{ok:!1,status:o.status,error:"empty body"};let i;const s=await St(o,{...t,onDone:r=>{typeof r.turn_id=="string"&&(i=r.turn_id),t.onDone(r)}});return s.streamError&&!s.hadDelta?{ok:!1,status:o.status,error:s.streamError,turnId:i}:{ok:!0,status:o.status,turnId:i}}async function Uc(e,t,n){await y(`/v1/chat/sessions/${e}/turns/${encodeURIComponent(t)}/proactive-feedback`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({rating:n})})}function jc(e){return!(!e.enabled||e.chatMode==="work"||!e.visible||e.sending||!e.inputEmpty)}function Hc(e,t){const n=Number(e.interval_multiplier??1),a=t==="empty"?9e4:45e3,o=.85+Math.random()*.3;return Math.min(6e5,Math.max(12e3,a*n*o))}function qc(e){let t=null;const n=()=>{t!==null&&(clearTimeout(t),t=null)},a=()=>{n();const o=e.hasUserMessages()?"post_chat":"empty";if(!jc({enabled:e.enabled,chatMode:e.getChatMode(),visible:e.isVisible(),sending:e.isSending(),inputEmpty:e.isInputEmpty()}))return;const i=Hc(e.getProfile(),o);t=setTimeout(()=>{e.onFire()},i)};return{schedule:a,stop:n,bump:a}}async function Vc(e){return y(`/v1/chat/enrichment-jobs/${encodeURIComponent(e)}`)}async function ro(){return y("/v1/chat/stickers/user")}async function Fc(e,t){return(await y("/v1/chat/stickers/user",{method:"POST",body:JSON.stringify({attachment_id:e,label:t})})).sticker}async function zc(e){const t=new FormData;return t.append("file",e),y("/v1/chat/attachments",{method:"POST",body:t})}async function $i(e){const t=await se(`/v1/chat/attachments/${encodeURIComponent(e)}`);if(!t.ok){const a=await t.text();throw new Error(`HTTP ${t.status}: ${a.slice(0,200)}`)}const n=await t.blob();return URL.createObjectURL(n)}function _n(e){e!=null&&e.startsWith("blob:")&&URL.revokeObjectURL(e)}const Wc="modulepreload",Kc=function(e){return"/niannian/"+e},co={},kn=function(t,n,a){let o=Promise.resolve();if(n&&n.length>0){document.getElementsByTagName("link");const s=document.querySelector("meta[property=csp-nonce]"),r=(s==null?void 0:s.nonce)||(s==null?void 0:s.getAttribute("nonce"));o=Promise.allSettled(n.map(c=>{if(c=Kc(c),c in co)return;co[c]=!0;const l=c.endsWith(".css"),u=l?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${c}"]${u}`))return;const p=document.createElement("link");if(p.rel=l?"stylesheet":Wc,l||(p.as="script"),p.crossOrigin="",p.href=c,r&&p.setAttribute("nonce",r),document.head.appendChild(p),l)return new Promise((m,g)=>{p.addEventListener("load",m),p.addEventListener("error",()=>g(new Error(`Unable to preload CSS for ${c}`)))})}))}function i(s){const r=new Event("vite:preloadError",{cancelable:!0});if(r.payload=s,window.dispatchEvent(r),!r.defaultPrevented)throw s}return o.then(s=>{for(const r of s||[])r.status==="rejected"&&i(r.reason);return t().catch(i)})},Si=120,Jc="niannian.chatMemoryHintDismissed",lo="niannian.chatMemoryHintInboxed",Gc="日常对话不会自动写入记忆；重要的事请在念念回复下方点「记一下」。";function Yc(){return localStorage.getItem(Jc)==="1"}function Xc(e){Yc()||sessionStorage.getItem(lo)!=="1"&&(sessionStorage.setItem(lo,"1"),e("info",Gc))}function Ei(e){if(e.userRecalled||e.assistantRecalled)return null;if(e.ingestedAtomId)return"remembered";const n=e.recallWindowSeconds??Si;let a=e.recallWindowRemainingSeconds;if(a==null&&e.createdAt){const o=Date.parse(e.createdAt);Number.isNaN(o)||(a=Math.max(0,n-(Date.now()-o)/1e3))}if(e.recallPending||a!=null&&a>0)return"recall_pending";if(e.rememberable===!0)return"idle";if(e.rememberable===!1||e.rememberBlockReason)return"disabled";if(e.createdAt){const o=Date.parse(e.createdAt);if(!Number.isNaN(o)&&Date.now()-o<n*1e3)return"recall_pending"}return"idle"}function Qc(e){switch(e){case"chit_chat":return"日常问候无需写入长期记忆";case"sticker_only":return"纯贴纸对话无法写入记忆";case"too_short":return"内容过短，无需写入记忆";case"empty_assistant":return"助手回复为空";case"sensitive_pattern":return"含敏感信息，无法写入记忆";case"turn_recalled":return"已撤回的对话无法记忆";default:return"当前对话无法写入长期记忆"}}function Zc(e){const t=Math.max(0,Math.ceil(e)),n=Math.floor(t/60),a=t%60;return`${n}:${String(a).padStart(2,"0")}`}function el(e,t={}){if(e==="recall_pending"){const n=t.remainingSeconds??0;return`<div class="discord-message-memory-footer" data-state="recall_pending">
  <span class="chat-remember-pending">撤回期内暂不可记忆 · 剩余 ${Zc(n)}</span>
</div>`}if(e==="remembered"){const n=t.atomId||"",a=n?`<a class="chat-remember-link" href="#/memory?atom=${encodeURIComponent(n)}">查看</a>`:"";return`<div class="discord-message-memory-footer" data-state="remembered">
  <span class="chat-remember-done">✓ 已写入记忆</span>${a?" · "+a:""}
</div>`}return e==="disabled"?`<div class="discord-message-memory-footer" data-state="disabled" title="${Qc(t.blockReason)}">
  <button type="button" class="chat-remember-btn" disabled>记一下（不可用）</button>
</div>`:`<div class="discord-message-memory-footer" data-state="idle">
  <button type="button" class="chat-remember-btn">记一下</button>
  <span class="chat-remember-hint">仅重要对话会写入长期记忆</span>
</div>`}function Pi(e){var t;(t=e.querySelector(".discord-message-memory-footer"))==null||t.remove()}function Xe(e,t,n){const a=e.querySelector(".discord-message-content");if(!a)return;Pi(e);const o=Ei(t);if(!o)return;let i=t.recallWindowRemainingSeconds;if(i==null&&t.createdAt&&o==="recall_pending"){const l=t.recallWindowSeconds??Si,u=Date.parse(t.createdAt);Number.isNaN(u)||(i=Math.max(0,l-(Date.now()-u)/1e3))}const s=document.createElement("div");s.innerHTML=el(o,{atomId:t.ingestedAtomId,remainingSeconds:i,blockReason:t.rememberBlockReason});const r=s.firstElementChild;if(a.appendChild(r),o==="recall_pending"&&t.createdAt){tl(e,t,n);return}const c=r.querySelector(".chat-remember-btn");c&&o==="idle"&&c.addEventListener("click",()=>{nl(e,c,n)})}function tl(e,t,n){const a=()=>{if(!e.isConnected)return;if(Ei({...t,recallPending:!1,recallWindowRemainingSeconds:void 0})==="recall_pending"){Xe(e,t,n),window.setTimeout(a,1e3);return}Xe(e,{...t,recallPending:!1,rememberable:t.rememberable??!0},n)};window.setTimeout(a,1e3)}async function nl(e,t,n){if(e.dataset.turnId){t.disabled=!0,t.textContent="记一下…";try{const{ensureSession:o,rememberTurn:i}=await kn(async()=>{const{ensureSession:l,rememberTurn:u}=await Promise.resolve().then(()=>_r);return{ensureSession:l,rememberTurn:u}},void 0),{showToast:s}=await kn(async()=>{const{showToast:l}=await Promise.resolve().then(()=>oo);return{showToast:l}},void 0),r=await o(),c=String(result.ingested_atom_id||"");Xe(e,{ingestedAtomId:c},n),n==null||n(c),s("已写入记忆","success")}catch(o){const{showToast:i}=await kn(async()=>{const{showToast:s}=await Promise.resolve().then(()=>oo);return{showToast:s}},void 0);t.disabled=!1,t.textContent="记一下",i(String(o),"error")}}}const al="/niannian/assets/niannian/stickers/manifest.yaml",ol={nn_hi:"default_hi",nn_think:"default_think",nn_happy:"default_happy",nn_sorry:"default_sorry",nn_cheer:"default_cheer",nn_serious:"default_serious"};let xt=null;function $n(e){const t=ke(e);return t==="companion"||t==="work"||t==="education"?t:"default"}function uo(e,t){const a=new RegExp(`^${t}:\\s*\\n`,"m").exec(e);if(!a)return[];const o=a.index+a[0].length,i=e.slice(o),s=i.search(/^[^\s-]/m);return(s>=0?i.slice(0,s):i).split(/\n\s*-\s+id:/).slice(1).map(c=>`id:${c}`)}function il(e){var i,s,r,c,l,u,p,m,g,v,k;const t=e.match(/^pack:\s*(.+)$/m),n=((i=t==null?void 0:t[1])==null?void 0:i.trim())||"niannian_v2",a=[];for(const E of uo(e,"packs")){const I=((s=E.match(/^id:\s*(\S+)/))==null?void 0:s[1])||"",_=((c=(r=E.match(/\n\s*label:\s*(.+)/))==null?void 0:r[1])==null?void 0:c.trim())||I;I&&a.push({id:I,label:_})}const o=[];for(const E of uo(e,"stickers")){const I=((l=E.match(/^id:\s*(\S+)/))==null?void 0:l[1])||"",_=((u=E.match(/\n\s*file:\s*(\S+)/))==null?void 0:u[1])||"",P=((p=E.match(/\n\s*pack:\s*(\S+)/))==null?void 0:p[1])||"default",f=((g=(m=E.match(/\n\s*label:\s*(.+)/))==null?void 0:m[1])==null?void 0:g.trim())||I,w=((k=(v=E.match(/\n\s*tags:\s*(.+)/))==null?void 0:v[1])==null?void 0:k.trim())||"";I&&_&&o.push({id:I,pack:P,file:_,label:f,tags:w})}return{pack:n,packs:a,stickers:o}}async function fn(){if(xt)return xt;const e=await fetch(al);if(!e.ok)throw new Error(`加载表情包 manifest 失败：${e.status}`);return xt=il(await e.text()),xt}function po(e,t){return e.stickers.filter(n=>n.pack===t)}function Pa(e,t){return t!=null&&t.startsWith("user_")?`/v1/chat/stickers/user/${encodeURIComponent(t)}/image`:`/niannian/assets/niannian/stickers/${e}`}function Ia(e,t){const n=ol[t]||t,a=e.stickers.find(o=>o.id===n);if(a)return a;if(n.startsWith("user_"))return{id:n,pack:"my",file:`images/${n}.png`,label:n,tags:"自定义"}}function sl(e){return!!(e!=null&&e.sticker_id)}const Ii={default:"正常",companion:"陪伴",work:"工作",education:"学习",my:"我的"};function Sn(e){return`<button type="button" class="sticker-cell" data-sticker-id="${e.id}" title="${e.label}">
    <span class="sticker-cell-thumb">
      <img src="${Pa(e.file,e.id)}" alt="${e.label}" loading="lazy" decoding="async" draggable="false" />
    </span>
    <span class="sticker-cell-label">${e.label}</span>
  </button>`}function Bi(e){if(!e.length)return'<div class="sticker-empty">暂无自定义表情</div>';const t=e.filter(o=>!o.tags.includes("梗")),n=e.filter(o=>o.tags.includes("梗")),a=[];return t.length&&(a.push('<div class="sticker-section-label">常用</div>'),a.push(...t.map(Sn))),n.length&&(a.push('<div class="sticker-section-label sticker-section-meme">梗图</div>'),a.push(...n.map(Sn))),a.length?a.join(""):e.map(Sn).join("")}function rl(e,t){return`<div class="sticker-panel" id="stickerPanel" hidden>
    <div class="sticker-panel-header">
      <div class="sticker-panel-tabs">
        <button type="button" class="sticker-tab active" data-tab="official">官方</button>
        <button type="button" class="sticker-tab" data-tab="user">我的</button>
      </div>
      <span class="sticker-panel-title">念念表情包 · ${Ii[t]||t}</span>
      <button type="button" class="secondary sticker-panel-close" id="stickerPanelClose" aria-label="关闭">×</button>
    </div>
    <div class="sticker-grid">${Bi(e)}</div>
  </div>`}function mo(e,t,n,a){e.querySelectorAll(".sticker-cell").forEach(o=>{o.onclick=()=>{const i=o.dataset.stickerId||"",s=t.find(r=>r.id===i);s&&(n(s),a())}})}async function cl(e,t,n){const a=await fn();let o=[];try{o=(await ro()).stickers}catch{o=[]}let i="official",s=$n(n()),r=po(a,s);e.insertAdjacentHTML("beforeend",rl(r,s));const c=e.querySelector("#stickerPanel"),l=e.querySelector("#stickerPanelClose"),u=c.querySelector(".sticker-panel-title"),p=c.querySelector(".sticker-grid"),m=c.querySelectorAll(".sticker-tab");function g(){i==="user"?(r=o,u&&(u.textContent="我的表情包")):(s=$n(n()),r=po(a,s),u&&(u.textContent=`念念表情包 · ${Ii[s]||s}`)),p.innerHTML=Bi(r),mo(c,r,t,v),m.forEach(I=>{I.classList.toggle("active",I.dataset.tab===i)})}function v(){c.hidden=!0}function k(){c.hidden=!1}function E(){if(i==="official"){const I=$n(n());I!==s&&(s=I)}g()}return m.forEach(I=>{I.onclick=()=>{const _=I.dataset.tab==="user"?"user":"official";_!==i&&(i=_,g())}}),l.onclick=v,mo(c,r,t,v),{open:k,close:v,refresh:()=>{ro().then(I=>{o=I.stickers,E()}).catch(()=>E())}}}function Ci(e){const t=new URLSearchParams;for(const[a,o]of Object.entries(e))o!==void 0&&o!==""&&t.set(a,String(o));const n=t.toString();return n?`?${n}`:""}async function ll(e={}){return y(`/v1/mcp/audit${Ci(e)}`)}async function dl(e){return y(`/v1/mcp/logs${Ci(e)}`)}async function jn(){return y("/v1/mcp/capabilities")}async function fo(){return y("/v1/mcp/capabilities/for-chat")}async function ul(e){return N("/v1/mcp/capabilities",{capabilities:e})}async function pl(e,t){return y(`/v1/mcp/capabilities/${encodeURIComponent(e)}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})}async function ml(e){return N("/v1/mcp/config/preview",e)}async function fl(e){return N("/v1/mcp/envelope/preview",e)}function hl(e,t){const n=t.trim().toLowerCase();return n?e.filter(a=>[a.capability_id,a.label,a.memory_space,a.description].filter(Boolean).join(" ").toLowerCase().includes(n)):e}function vl(e){const t=e.capability_id;return{project:e.memory_space||t,label:e.label||t}}function gl(e){const t=e.capability_id,n=e.label||t;return`<button type="button" class="mcp-tool-cell" data-capability-id="${t}" title="${t}">
    <span class="mcp-tool-cell-label">${n}</span>
    <span class="mcp-tool-cell-id">${t}</span>
  </button>`}function Ti(e){return e.length?e.map(gl).join(""):'<div class="mcp-tool-empty">暂无可用工具 · 请先在 MCP 页启用</div>'}function yl(e){return`<div class="mcp-tool-panel" id="mcpToolPanel" hidden>
    <div class="mcp-tool-panel-header">
      <span class="mcp-tool-panel-title">外部记忆</span>
      <button type="button" class="secondary mcp-tool-panel-close" id="mcpToolPanelClose" aria-label="关闭">×</button>
    </div>
    ${e.length>=6?'<input type="search" class="mcp-tool-search" id="mcpToolSearch" placeholder="搜索工具…" autocomplete="off" />':""}
    <div class="mcp-tool-grid">${Ti(e)}</div>
  </div>`}function ho(e,t,n,a){e.querySelectorAll(".mcp-tool-cell").forEach(o=>{o.onclick=()=>{const i=o.dataset.capabilityId||"",s=t.find(r=>r.capability_id===i);s&&(n(vl(s)),a())}})}async function bl(e,t){let n=[];try{n=(await fo()).capabilities||[]}catch{n=[]}e.insertAdjacentHTML("beforeend",yl(n));const a=e.querySelector("#mcpToolPanel"),o=e.querySelector("#mcpToolPanelClose"),i=a.querySelector(".mcp-tool-grid"),s=e.querySelector("#mcpToolSearch");let r="";function c(){return hl(n,r)}function l(){i.innerHTML=Ti(c()),ho(a,n,t,u)}function u(){a.hidden=!0}function p(){a.hidden=!1,s&&(s.value="",r="",l(),s.focus())}async function m(){try{n=(await fo()).capabilities||[]}catch{n=[]}r=(s==null?void 0:s.value)||"",l()}return o.onclick=u,s&&s.addEventListener("input",()=>{r=s.value,l()}),ho(a,n,t,u),{open:p,close:u,refresh:m,enabledCount:()=>n.length}}const wl=`<svg class="chat-input-tool-svg chat-mcp-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
  <rect x="2" y="2" width="20" height="8" rx="2" ry="2"/>
  <rect x="2" y="14" width="20" height="8" rx="2" ry="2"/>
  <line x1="6" y1="6" x2="6.01" y2="6"/>
  <line x1="6" y1="18" x2="6.01" y2="18"/>
</svg>`,_l=[{value:"auto",label:"自动"},{value:"default",label:"正常"},{value:"companion",label:"陪伴"},{value:"work",label:"工作"},{value:"education",label:"学习"}],kl=[{value:"auto",label:"自动"},{value:"companion",label:"陪伴"},{value:"work",label:"工作"}];function $l(e){return e?kl:_l}const Te={auto:"自动",default:"正常",companion:"陪伴",work:"工作",education:"学习"},Sl=`<svg class="chat-input-tool-svg chat-mode-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
  <path d="M12 3 3 8l9 5 9-5-9-5Z"/>
  <path d="M3 13l9 5 9-5"/>
  <path d="M3 18l9 5 9-5"/>
</svg>`;function Li(e,t=!1){const n=$l(t);return`<div class="chat-mode-popover" id="chatModePopover" hidden>
    <div class="chat-mode-popover-header">念念模式</div>
    ${t?'<p class="chat-mode-popover-hint">模式归属虚拟人物</p>':""}
    <div class="chat-mode-popover-grid" role="listbox" aria-label="念念模式">
      ${n.map(o=>`<button type="button" role="option" class="chat-mode-popover-item${e===o.value?" active":""}" data-chat-mode="${o.value}" aria-selected="${e===o.value}">${o.label}</button>`).join("")}
    </div>
  </div>`}function El(e){Pl(e)}function Pl(e){document.querySelectorAll(".chat-mode-pill, .chat-mode-popover-item").forEach(n=>{const o=ke(n.dataset.chatMode||"auto")===e;n.classList.toggle("active",o),n.classList.contains("chat-mode-popover-item")&&n.setAttribute("aria-selected",o?"true":"false")});const t=document.getElementById("chatModeBtn");if(t){const n=Te[e];t.title=`模式 · ${n}`,t.setAttribute("aria-label",`模式，当前${n}`),t.classList.toggle("active",e!=="auto")}}function Ai(e,t){document.querySelectorAll(".chat-mode-popover-item").forEach(n=>{n.onclick=()=>{const a=ke(n.dataset.chatMode||"auto");e(a),t==null||t()}})}function Il(e){document.querySelectorAll(".chat-mode-pill").forEach(t=>{t.onclick=()=>{const n=ke(t.dataset.chatMode||"auto");e(n)}})}function Bl(e,t,n,a){const o=document.getElementById("chatModePopover");if(!o)return;const i=o.hidden;o.outerHTML=Li(e,t);const s=document.getElementById("chatModePopover");s&&(s.hidden=i),Ai(n,a)}function Cl(e){if(!e)return"新会话";const t=new Date(e);return Number.isNaN(t.getTime())?"新会话":t.toLocaleString("zh-CN",{month:"numeric",day:"numeric",hour:"2-digit",minute:"2-digit"})}function Tl(e){return Te[ke(e)]??e}function Ll(e,t){return`${Cl(t)} · ${Te[e]}模式`}function Al(e,t,n,a){const o=document.getElementById("chatSessionSummary");if(!o)return;o.textContent=Ll(e,t);const i=[n?`会话 ID：${n}`:"",a?`当前语境：${Tl(a)}`:""].filter(Boolean);o.title=i.join(" · ")||"当前会话"}const xl=4*1024*1024,Ml=16*1024*1024,Ol=/https?:\/\/[^\s<>"')\]]+/gi;function Hn(e){const t=e.match(Ol)||[],n=new Set,a=[];for(const o of t){const i=o.replace(/[.,;:!?)]+$/,"");n.has(i)||(n.add(i),a.push(i))}return a}function Rl(e){return e.label}function Dl(e,t){const n=[],a=e.trim();a&&n.push({type:"text",text:a});for(const o of t)o.kind==="image"?n.push({type:"image",attachment_id:o.attachmentId}):o.kind==="file"?n.push({type:"file",attachment_id:o.attachmentId,name:o.name,mime:o.mime}):o.kind==="link"&&n.push({type:"link",url:o.url});return n}function Nl(e,t){return t.length>0?!0:!!e.trim()}function Ul(e){const a=(e.type||"").toLowerCase().startsWith("image/")?xl:Ml;return e.size>a?`文件超过 ${a/1048576} MiB 限制`:null}function jl(e,t,n){const a=e.mime||t.type||"application/octet-stream";return a.startsWith("image/")?{kind:"image",attachmentId:e.attachment_id,mime:a,label:t.name||"图片",previewUrl:n}:{kind:"file",attachmentId:e.attachment_id,mime:a,name:t.name||"file",label:t.name||"文件"}}function xi(e){let t=e;try{const n=new URL(e);t=n.hostname+n.pathname.slice(0,24)}catch{t=e.slice(0,40)}return{kind:"link",url:e,label:t}}const Hl="默认仅用于本次对话，不会自动写入记忆";function ql(e,t){return e==="link"?String(t.title||"链接"):e==="file"?String(t.name||"文件"):e==="image"?"图片":e==="sticker"?String(t.label||"贴纸"):e}function Vl(e,t){return t.status==="failed"?`处理失败（${t.error_code||"unknown"}）`:t.status==="pending"?"处理中…":e==="link"?String(t.snippet||"").slice(0,120):e==="file"?String(t.extract_summary||t.name||"").slice(0,120):e==="image"?String(t.ocr_text||t.vision_summary||"").slice(0,120):""}function Fl(e,t){if(t.persisted&&t.vault_id)return[{action:"open-vault",label:"已在资料库 · 打开"}];if(t.persisted&&t.atom_id)return[{action:"open-vault",label:"已写入记忆"}];if(t.status==="failed")return[];if(t.status==="pending")return[];const n=[{action:"import-vault",label:"导入资料库"}];return e==="image"&&n.unshift({action:"add-sticker",label:"加为表情包"}),e==="link"&&n.push({action:"learn",label:"自我学习"}),n.push({action:"remember",label:"写入记忆"}),n}function it(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function zl(e){const t=Object.keys(e);if(!t.length)return"";const n=t.map(a=>{const o=e[a]||{},i=String(o.kind||"");if(i==="sticker")return"";const s=it(ql(i,o)),r=it(Vl(i,o)),l=Fl(i,o).map(u=>`<button type="button" class="enrichment-card-btn" data-enrichment-key="${it(a)}" data-enrichment-action="${u.action}">${it(u.label)}</button>`).join("");return`<div class="enrichment-card" data-enrichment-key="${it(a)}">
        <div class="enrichment-card-title">${s}</div>
        ${r?`<div class="enrichment-card-snippet">${r}</div>`:""}
        <div class="enrichment-card-hint">${Hl}</div>
        ${l?`<div class="enrichment-card-actions">${l}</div>`:""}
      </div>`}).filter(Boolean).join("");return n?`<div class="enrichment-cards">${n}</div>`:""}async function Wl(e,t,n=8){const a=new URLSearchParams({kind:e,q:t,limit:String(n)});return y(`/v1/chat/mentions/suggest?${a}`)}const vo=/@(vault|wiki)(\s+([^@\n]*))$/i,Kl=/@(?:v(?:a(?:u(?:l(?:t)?)?)?)?|w(?:i(?:k(?:i)?)?)?)?$/i;function Jl(e,t){const n=e.slice(0,Math.max(0,t)),a=n.match(vo);if(a){const o=a[1].toLowerCase(),i=a[3]??"";return{mode:"search",kind:o,query:i,replaceStart:a.index??0,replaceEnd:t}}if(Kl.test(n)&&!vo.test(n)){const o=n.lastIndexOf("@");if(o>=0)return{mode:"pick_kind",atIndex:o}}return null}function Gl(e,t,n){const a=e.slice(0,t),o=e.slice(t+1),i=`${a}@${n} ${o}`,s=t+n.length+2;return{text:i,cursor:s}}function Yl(e,t,n){const a=n.trim()||t.query.trim(),o=`@${t.kind} ${a} `,i=e.slice(0,t.replaceStart)+o+e.slice(t.replaceEnd),s=t.replaceStart+o.length;return{text:i,cursor:s}}function Xl(e){return e==="vault"?"资料库":"Wiki"}class Ql{constructor(){this.chips=[],this.previewUrls=[]}add(t){t.kind==="link"&&this.chips.some(n=>n.kind==="link"&&n.url===t.url)||this.chips.push(t)}remove(t){const n=this.chips[t];(n==null?void 0:n.kind)==="image"&&n.previewUrl&&_n(n.previewUrl),this.chips.splice(t,1)}clear(){for(const t of this.chips)t.kind==="image"&&t.previewUrl&&_n(t.previewUrl);for(const t of this.previewUrls)_n(t);this.previewUrls=[],this.chips=[]}trackPreview(t){this.previewUrls.push(t)}}function Zl(e){return e.length?e.map((t,n)=>{const a=t.kind==="image"&&t.previewUrl?`<img class="composer-chip-thumb" src="${t.previewUrl}" alt="" />`:`<span class="composer-chip-icon">${t.kind==="link"?"🔗":t.kind==="file"?"📄":"🖼"}</span>`;return`<span class="composer-chip" data-chip-index="${n}">
        ${a}
        <span class="composer-chip-label">${Rl(t)}</span>
        <button type="button" class="composer-chip-remove" data-chip-index="${n}" aria-label="移除">×</button>
      </span>`}).join(""):""}function Mi(e,t){if(!t.length){e.hidden=!0,e.innerHTML="";return}e.hidden=!1,e.innerHTML=Zl(t)}async function go(e,t){const n=[];for(const a of Array.from(e)){const o=Ul(a);if(o){d(`${a.name}: ${o}`,"error");continue}try{const i=await zc(a);let s;(i.mime||a.type).startsWith("image/")&&(s=await $i(i.attachment_id),t.trackPreview(s));const r=jl(i,a,s);t.add(r),n.push(r)}catch(i){d(String(i),"error")}}return n}function ed(e,t){const n=Hn(e);if(!n.length)return!1;for(const a of n)t.add(xi(a));return!0}function Mt(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function td(e,t){let n=t.querySelector(".mention-suggest-popover");n||(n=document.createElement("div"),n.className="mention-suggest-popover",n.hidden=!0,n.setAttribute("role","listbox"),n.setAttribute("aria-label","引用建议"),t.appendChild(n));let a=null,o=0;const i=()=>{n.hidden=!0,n.innerHTML=""},s=(p,m)=>{e.value=p,e.setSelectionRange(m,m),e.dispatchEvent(new Event("input",{bubbles:!0}))},r=p=>{n.hidden=!1,n.innerHTML=`<div class="mention-suggest-header">选择引用类型</div>
      <div class="mention-suggest-list">
        ${["vault","wiki"].map(m=>`<button type="button" class="mention-suggest-item" data-mention-kind="${m}" data-at-index="${p}">
                <span class="mention-suggest-kind">@${m}</span>
                <span class="mention-suggest-sub">${Xl(m)}</span>
              </button>`).join("")}
      </div>`},c=(p,m)=>{n.hidden=!1;const g=`@${p.kind} · ${p.query.trim()||"搜索…"}`,v=m.length?m.map(k=>`<button type="button" class="mention-suggest-item" data-mention-title="${Mt(k.title)}">
                <span class="mention-suggest-kind">${Mt(k.title)}</span>
                ${k.snippet?`<span class="mention-suggest-sub">${Mt(k.snippet.slice(0,80))}</span>`:""}
              </button>`).join(""):'<div class="mention-suggest-empty">无匹配结果</div>';n.innerHTML=`<div class="mention-suggest-header">${Mt(g)}</div><div class="mention-suggest-list">${v}</div>`;for(const k of n.querySelectorAll("[data-mention-title]"))k.dataset.mentionCtx=JSON.stringify(p)},l=()=>{const p=e.selectionStart??e.value.length,m=Jl(e.value,p);if(!m){i();return}if(m.mode==="pick_kind"){r(m.atIndex);return}const g=++o;(async()=>{try{const v=await Wl(m.kind,m.query.trim());if(g!==o)return;c(m,v.items)}catch{if(g!==o)return;i()}})()},u=()=>{a&&clearTimeout(a),a=setTimeout(l,180)};e.addEventListener("input",u),e.addEventListener("click",u),e.addEventListener("keyup",p=>{p.key==="Escape"?i():u()}),n.addEventListener("mousedown",p=>p.preventDefault()),n.addEventListener("click",p=>{const m=p.target.closest("[data-mention-kind]");if(m){const v=m.dataset.mentionKind,k=Number.parseInt(m.dataset.atIndex||"0",10),E=Gl(e.value,k,v);s(E.text,E.cursor),u();return}const g=p.target.closest("[data-mention-title]");if(g!=null&&g.dataset.mentionCtx){const v=JSON.parse(g.dataset.mentionCtx),k=g.dataset.mentionTitle||"",E=Yl(e.value,v,k);s(E.text,E.cursor),i()}}),document.addEventListener("click",p=>{if(n.hidden)return;const m=p.target;m.closest(".mention-suggest-popover")||m===e||i()})}function Ba(e){if(!e.length)return"";const t=new Map;for(const o of e){const i=o.parent_id??null,s=t.get(i)||[];s.push(o),t.set(i,s)}const n=[],a=(o,i)=>{for(const s of t.get(o)||[]){const r="  ".repeat(i);n.push(`${r}[${s.label}] ${s.text}`),a(s.id,i+1)}};return a(null,0),n.join(`
`)}function Oi(e){return`<span class="switch-config-key">${e}</span>`}function nd(e,t){const n=t?Oi(t):"";return`<span class="switch-label-text">${e}${n}</span>`}function G(e){const t=['type="checkbox"'];return e.id&&t.push(`id="${e.id}"`),e.className&&t.push(`class="${e.className}"`),e.dataKey&&t.push(`data-budget-key="${e.dataKey}"`),e.checked&&t.push("checked"),e.disabled&&t.push("disabled"),`<label class="switch-label-row"><input ${t.join(" ")}/> ${nd(e.chinese,e.configKey)}</label>`}let F={chatMode:"auto"},Qe=!1,Ue=null,jt=null,st=null,It,ge=null,ue=null,ad=!1,vt=null,Ri=null;function od(e,t){vt&&Bl(e,t,vt,Ri??void 0)}function id(e){if(e.chat_mode!=null&&String(e.chat_mode).trim())return ke(e.chat_mode);const t=String(e.regime_lock||"");return t==="companion"||t==="work"||t==="education"?t:"auto"}function sd(e){switch(e){case"default":return{chat_mode:"default",regime_lock:null,persona_override:null};case"companion":return{chat_mode:"companion",regime_lock:"companion",persona_override:{preset:"companion_warm",mode:"turn_only"}};case"work":return{chat_mode:"work",regime_lock:"work",persona_override:{preset:"work_focused",mode:"turn_only"}};case"education":return{chat_mode:"education",regime_lock:"education",persona_override:{preset:"education_scholarly",mode:"turn_only"}};default:return{chat_mode:"auto",regime_lock:null,persona_override:null}}}function rd(e){const t=e.pathway_sticky,n=e.pathway_sticky_hits;return t!=null&&t.regime?`语境：${Te[ke(t.regime)]??t.regime}`:n?`语境：累积 ${n} 轮`:"语境：—"}function cd(e){const t=e.pathway_sticky;return t!=null&&t.regime?String(t.regime):e.regime!=null&&String(e.regime).trim()?String(e.regime):null}function ld(e){return e.map(n=>n.created_at).filter(n=>!!n).sort()[0]}function yo(){const e=document.getElementById("traceBox");e&&(e.hidden=!C().showTrace)}function dd(){var o;const e=new URLSearchParams(window.location.search),t=(o=e.get("trial_vp"))==null?void 0:o.trim();if(!t)return null;e.delete("trial_vp");const n=e.toString(),a=window.location.pathname+(n?`?${n}`:"")+window.location.hash;return window.history.replaceState(null,"",a),t}function ud(e,t,n){Al(e,It,t,n),El(e)}function qn(e){const t=document.getElementById("chatToolScopeBar");if(!t)return;if(!(e!=null&&e.project)){t.hidden=!0,t.innerHTML="";return}const n=e.label||e.project;t.hidden=!1,t.innerHTML=`<span class="composer-chip chat-tool-scope-chip">
    <span class="composer-chip-icon">🗄</span>
    <span class="composer-chip-label">外部记忆 · ${n}</span>
    <button type="button" class="composer-chip-remove" id="chatToolScopeClear" aria-label="清除外部记忆">×</button>
  </span>`}function pd(e){const t=document.getElementById("chatLog");t&&(t.querySelectorAll(".discord-message-user .discord-message-avatar").forEach(n=>{n.innerHTML=Xt("user",e)}),t.querySelectorAll(".discord-message-assistant .discord-message-avatar").forEach(n=>{n.innerHTML=Xt("assistant",e)}))}async function En(e,t){if(si(st),st=null,t&&e)try{st=await ii(e)}catch{st=null}F={...F,vpAvatarUrl:st||void 0}}async function md(){const e=await Ea();F={...F,userAvatarUrl:e}}function fd(){const e=C(),t=e.consoleProfile==="client",n=F.chatMode??"auto";return`
    <div class="chat-page">
      <div class="chat-session-bar">
        <span class="chat-session-summary" id="chatSessionSummary" title="当前会话">新会话 · ${Te[n]}模式</span>
        <div class="chat-session-actions">
          <button class="secondary chat-btn-compact" id="newSession" type="button">新建</button>
          <details class="chat-advanced-drawer">
            <summary class="chat-advanced-summary">高级</summary>
            <div class="chat-advanced-panel">
              <div class="chat-advanced-section">
                <span class="chat-advanced-label">会话 ID</span>
                <span class="hint chat-session-id" id="chatSessionIdHint">—</span>
              </div>
              <div class="chat-advanced-row chat-advanced-sticky-row">
                <span class="hint chat-advanced-meta" id="chatStickyStatus">语境：—</span>
                <button class="secondary chat-btn-compact" id="clearSticky" type="button">清除语境</button>
              </div>
              <span class="hint ok chat-advanced-meta" id="chatVpStatus"></span>
              ${G({id:"chatTrace",checked:e.showTrace,chinese:"显示推理 Trace",configKey:"chat.show_trace"})}
              ${t?`<label class="chat-advanced-field">人格预设
                <select id="chatPersonaPreset" disabled><option value="">加载中…</option></select>
                <span class="switch-config-key">GET /v1/chat/personas · persona_override</span>
              </label>`:""}
            </div>
          </details>
        </div>
      </div>
      <div class="chat-log" id="chatLog" role="log" aria-live="polite"></div>
      <details id="traceBox" class="trace-box chat-trace"${e.showTrace?"":" hidden"}><summary>trace</summary><pre id="traceOut"></pre></details>
      <div class="chat-composer">
        <div class="chat-input-shell">
          <div class="chat-tool-scope-bar" id="chatToolScopeBar" hidden></div>
          <div class="chat-composer-chips" id="chatComposerChips" hidden></div>
          <div class="chat-input-main">
            <textarea id="chatInput" rows="3" placeholder="输入消息…  Enter 换行 · Ctrl+Enter 发送"></textarea>
          </div>
          <div class="chat-input-footbar">
            <div class="chat-input-tools">
              <button type="button" class="chat-input-tool chat-sticker-btn" id="chatStickerBtn" title="表情" aria-label="表情">😊</button>
              <button type="button" class="chat-input-tool chat-mcp-btn" id="chatMcpBtn" title="外部记忆" aria-label="外部记忆">${wl}</button>
              <button type="button" class="chat-input-tool chat-mode-btn" id="chatModeBtn" title="模式 · ${Te[n]}" aria-label="模式，当前${Te[n]}">${Sl}</button>
              <button type="button" class="chat-input-tool" id="chatAttachBtn" title="文件" aria-label="文件">📎</button>
              <input type="file" id="chatAttachInput" hidden multiple accept="image/*,.txt,.md,.pdf" />
              <button type="button" class="chat-input-tool" id="chatLinkBtn" title="链接" aria-label="链接">🔗</button>
              <button type="button" class="chat-input-tool chat-plan-btn" id="chatPlanBtn" title="Plan 模式" aria-label="Plan 模式">📋</button>
            </div>
            <button class="btn-primary chat-send-btn" id="sendChat" ${e.health==="offline"||e.health==="unknown"?"disabled":""}>发送</button>
          </div>
        </div>
        ${Li(n)}
      </div>
    </div>`}function Di(e,t,n){const a=ha("user"),o=n||ft(),i=document.createElement("article");i.className="discord-message discord-message-user discord-message-sticker",i.innerHTML=`
    <div class="discord-message-avatar" aria-hidden="true">${Xt("user",F)}</div>
    <div class="discord-message-content">
      <div class="discord-message-header">
        <span class="discord-message-author">${a.name}</span>
        <time class="discord-message-time">${o}</time>
      </div>
      <div class="discord-message-body discord-sticker-body">
        <img class="chat-sticker-img" src="${Pa(t.file,t.id)}" alt="${t.label}" />
        <span class="chat-sticker-caption">${t.label}</span>
      </div>
    </div>`,e.appendChild(i);const s=e.closest(".chat-log");return s&&(s.scrollTop=s.scrollHeight),i}function Ca(e,t,n={}){const a=e.querySelector(".discord-message-body");if(!a)return;const o=n.textEl??a.querySelector(".discord-message-text"),i=Ct((o==null?void 0:o.textContent)||""),s=n.stickerOnly??(!i||i==="…"||i==="（无回复）"),r=`<img class="chat-sticker-img" src="${Pa(t.file,t.id)}" alt="${t.label}" />`;if(s){e.classList.add("discord-message-sticker"),a.classList.add("discord-sticker-body"),a.innerHTML=`${r}<span class="chat-sticker-caption">${t.label}</span>`;return}a.innerHTML=`${r}<div class="discord-message-text">${i}</div>`}function hd(e,t,n){var i;if(e.querySelector(".proactive-feedback-bar"))return;const a=document.createElement("div");a.className="proactive-feedback-bar";const o=[{rating:"quiet",label:"少打扰"},{rating:"ok",label:"刚好"},{rating:"more",label:"多聊聊"}];for(const s of o){const r=document.createElement("button");r.type="button",r.textContent=s.label,r.addEventListener("click",()=>{a.querySelectorAll("button").forEach(c=>{c.disabled=!0}),(async()=>{try{await Uc(t,n,s.rating),d("好，我记住了","success"),ge==null||ge.bump()}catch(c){d(String(c),"error"),a.querySelectorAll("button").forEach(l=>{l.disabled=!1})}})()}),a.appendChild(r)}(i=e.querySelector(".discord-message-content"))==null||i.appendChild(a)}async function vd(e,t){if(t.value||!(ue!=null&&ue.enabled)||C().health==="offline"||C().health==="unknown")return;t.value=!0;const{root:n,body:a}=Ze(e,"assistant","…");n.classList.add("discord-message-proactive");let o=null;const i=async s=>{const r=String(s.sticker_id||"");if(!r)return;o=o||await fn();const c=Ia(o,r);c&&Ca(n,c,{stickerOnly:!!s.sticker_only,textEl:a})};try{const s=await K(F.chatMode??"auto"),r=await Nc(s,{onDelta:c=>{const l=a.textContent==="…"?"":a.textContent;a.textContent=Ct(l+c)},onSticker:c=>{i(c)},onDone:c=>{const l=c.turn_id;l&&(n.dataset.turnId=l,hd(n,s,l)),c.sticker_id&&i(c)},onError:c=>{a.textContent=c||"主动开口失败"}});r.status===204?n.remove():r.ok||a.textContent==="…"&&(a.textContent=r.error||"主动开口失败")}catch(s){a.textContent=String(s)}finally{t.value=!1,ge==null||ge.bump()}}function Ze(e,t,n,a={}){var k;const o=a.avatarCtx??F,i=ha(t,o),s=a.time||ft(),r=fa(a.meta),c=!!((k=a.meta)!=null&&k.superseded_by),l=Ho(t,n,a.meta),u=document.createElement("article");u.className=`discord-message discord-message-${t}${r?" discord-message-recalled":""}${c?" discord-message-superseded":""}`,a.turnId&&(u.dataset.turnId=a.turnId),a.createdAt&&(u.dataset.createdAt=a.createdAt);const p=t==="user"&&!r&&Rs("user",a.meta,a.createdAt)?'<div class="discord-message-actions"><button type="button" class="discord-message-recall-btn">撤回</button></div>':"",m=t==="assistant"&&!r&&!c?'<div class="discord-message-actions"><button type="button" class="discord-message-regenerate-btn">重答</button></div>':"";u.innerHTML=`
    <div class="discord-message-avatar" aria-hidden="true">${Xt(t,o)}</div>
    <div class="discord-message-content">
      <div class="discord-message-header">
        <span class="discord-message-author">${i.name}</span>
        <time class="discord-message-time">${s}</time>
      </div>
      <div class="discord-message-body"></div>
      ${p}
      ${m}
    </div>`;const g=u.querySelector(".discord-message-body");g.textContent=l,e.appendChild(u),Ni(u),bd(u,e);const v=e.closest(".chat-log");return v&&(v.scrollTop=v.scrollHeight),{root:u,body:g}}function Pn(e,t){var a;e.classList.add("discord-message-recalled");const n=e.querySelector(".discord-message-body");n&&(n.textContent=Ho(t,"",{recalled_at:new Date().toISOString()})),(a=e.querySelector(".discord-message-actions"))==null||a.remove(),t==="assistant"&&Pi(e)}async function gd(e){const t=e.dataset.turnId;if(t)try{const n=await K(),a=await ei(n,t);Pn(e,"user");const o=a.recalled.find(i=>i!==t);if(o){const i=document.querySelector(`.discord-message-assistant[data-turn-id="${o}"]`);i&&Pn(i,"assistant")}else{const i=e.nextElementSibling;i!=null&&i.classList.contains("discord-message-assistant")&&Pn(i,"assistant")}a.ingested_atom_id?d("已撤回；该条已写入记忆，请到记忆页订正","info"):d("已撤回","success")}catch(n){d(String(n),"error")}}function Ni(e){const t=e.querySelector(".discord-message-recall-btn");if(!t)return;t.addEventListener("click",a=>{a.stopPropagation(),gd(e)});let n;e.addEventListener("touchstart",()=>{window.matchMedia("(min-width: 768px)").matches||(n=window.setTimeout(()=>{e.classList.add("discord-message-actions-visible")},500))},{passive:!0}),e.addEventListener("touchend",()=>{n&&window.clearTimeout(n)}),e.addEventListener("touchmove",()=>{n&&window.clearTimeout(n)})}async function yd(e,t){var r;const n=e.dataset.turnId;if(!n)return;e.classList.add("discord-message-superseded"),(r=e.querySelector(".discord-message-regenerate-btn"))==null||r.remove();const a=document.getElementById("traceOut"),{root:o,body:i}=Ze(t,"assistant","…"),s=[];try{const c=await K(),l=await Zo(c,n,{onDelta:u=>{const p=i.textContent==="…"?"":i.textContent;i.textContent=Ct(p+u)},onTrace:(u,p)=>{a&&(s.push(...u),a.textContent=p!=null&&p.length?Ba(p):s.join(`
`))},onPlan:u=>Ui(o,u),onDone:u=>{const p=u.turn_id;p&&(o.dataset.turnId=p),p&&i.textContent&&i.textContent!=="…"&&Xe(o,{rememberable:!!u.rememberable,rememberBlockReason:u.remember_block_reason,recallPending:!!u.recall_pending,recallWindowRemainingSeconds:u.recall_window_remaining_seconds,createdAt:new Date().toISOString()})},onError:u=>{i.textContent==="…"&&(i.textContent=u)}},{planMode:Qe});i.textContent==="…"&&(i.textContent="（无回复）"),l.ok?d("已重答","success"):d(l.error||"重答失败","error")}catch(c){i.textContent=String(c),d(String(c),"error")}}function bd(e,t){const n=e.querySelector(".discord-message-regenerate-btn");n&&n.addEventListener("click",a=>{a.stopPropagation(),yd(e,t)})}function wd(e,t){const n=document.createElement("details");n.className="citations-box";const a=document.createElement("summary");a.textContent=`引用 ${t.length} 条记忆`,n.appendChild(a);const o=document.createElement("ul");o.className="citations-list";for(const i of t.slice(0,8)){const s=document.createElement("li");s.className="citation-row";const r=String(i.kind||"note"),c=String(i.snippet||"").slice(0,120),l=String(i.atom_id||""),u=String(i.reason||""),p=document.createElement("span");p.className=`citation-kind citation-kind--${r.replace(/[^a-z0-9_-]/gi,"")}`,p.textContent=r,s.appendChild(p);const m=document.createElement("span");if(m.className="citation-snippet",m.textContent=c||"—",u&&(m.title=u),s.appendChild(m),l){const g=document.createElement("button");g.type="button",g.className="citation-atom-link secondary",g.textContent=`#${l.slice(0,10)}`,g.title=`复制 atom ${l}`,g.addEventListener("click",v=>{v.stopPropagation(),navigator.clipboard.writeText(l).then(()=>d(`已复制 atom · ${l.slice(0,12)}`,"success"),()=>d(l,"success"))}),s.appendChild(g)}else if(r==="wiki"){const g=document.createElement("a");g.className="citation-wiki-link",g.href=`${z("wiki")}#${encodeURIComponent(c)}`,g.textContent="Wiki",g.addEventListener("click",v=>{v.preventDefault(),window.history.pushState(null,"",g.href),window.dispatchEvent(new PopStateEvent("popstate"))}),s.appendChild(g)}o.appendChild(s)}n.appendChild(o),e.appendChild(n)}function _d(e,t,n){e.textContent="";for(const o of n)if(o.kind==="image"&&o.previewUrl){const i=document.createElement("img");i.className="chat-part-image",i.src=o.previewUrl,i.alt=o.label,e.appendChild(i)}else if(o.kind==="link"){const i=document.createElement("div");i.className="chat-part-link",i.textContent=o.url,e.appendChild(i)}else if(o.kind==="file"){const i=document.createElement("div");i.className="chat-part-file",i.textContent=o.name,e.appendChild(i)}const a=t.trim();if(a){const o=document.createElement("div");o.textContent=a,e.appendChild(o)}}async function kd(e,t){for(const n of t){const a=String(n.type||"");if(a==="text"){const o=document.createElement("div");o.textContent=String(n.text||""),e.appendChild(o)}else if(a==="image"&&n.attachment_id)try{const o=await $i(String(n.attachment_id)),i=document.createElement("img");i.className="chat-part-image",i.src=o,i.alt="图片",e.appendChild(i)}catch{const o=document.createElement("div");o.className="chat-part-file",o.textContent="[图片]",e.appendChild(o)}else if(a==="link"){const o=document.createElement("div");o.className="chat-part-link",o.textContent=String(n.url||""),e.appendChild(o)}else if(a==="file"){const o=document.createElement("div");o.className="chat-part-file",o.textContent=String(n.name||"文件"),e.appendChild(o)}}}function hn(e,t,n,a){const o=e.querySelector(".discord-message-content");if(!o)return;let i=o.querySelector(".enrichment-cards-host");i||(i=document.createElement("div"),i.className="enrichment-cards-host",o.appendChild(i)),i.innerHTML=zl(a),i.querySelectorAll(".enrichment-card-btn").forEach(s=>{s.onclick=()=>{Sd(e,t,n,s)}}),$d(e,t,n,a)}async function $d(e,t,n,a){var i;const o=Object.entries(a).filter(([,s])=>s.status==="pending"&&s.job_id);if(o.length)for(let s=0;s<30;s+=1){await new Promise(p=>setTimeout(p,1e3));let r=!1;for(const[,p]of o){const m=p.job_id;if((await Vc(m)).status==="pending"){r=!0;continue}}const l=(await Lt(t)).find(p=>p.id===n),u=((i=l==null?void 0:l.meta)==null?void 0:i.enrichments)||{};if(hn(e,t,n,u),!Object.values(u).some(p=>p.status==="pending")||!r)return}}async function Sd(e,t,n,a){var s;const o=a.dataset.enrichmentKey||"",i=a.dataset.enrichmentAction||"";if(!(!o||!i)){a.disabled=!0;try{if(i==="import-vault"){const u=await ti(t,n,o);d(`已导入资料库 · ${u.vault_id}`,"success")}else if(i==="learn"){const u=await ni(t,n,o);d(`已加入自学队列 · ${u.job_id}`,"success")}else if(i==="remember"){const u=await ai(t,n,o);d(`已写入记忆 · ${u.atom_id}`,"success")}else if(i==="add-sticker"){const u=await Fc(o);d(`已加为表情包 · ${u.label}`,"success"),Ue==null||Ue()}else if(i==="open-vault"){window.location.href=z("vault");return}const c=(await Lt(t)).find(u=>u.id===n),l=((s=c==null?void 0:c.meta)==null?void 0:s.enrichments)||{};hn(e,t,n,l)}catch(r){d(String(r),"error")}finally{a.disabled=!1}}}async function Ed(e,t,n,a,o){var r,c,l,u,p,m,g,v,k;if(t.role!=="user"&&t.role!=="assistant")return;if(sl(t.meta)){const E=String(((r=t.meta)==null?void 0:r.sticker_id)||""),I=Ia(n,E);if(I){if(t.role==="user")Di(e,I,t.created_at?ft(new Date(t.created_at)):void 0);else{const{root:_}=Ze(e,"assistant",t.content||"",{time:t.created_at?ft(new Date(t.created_at)):void 0,turnId:t.id,createdAt:t.created_at,meta:t.meta});Ca(_,I,{stickerOnly:!((c=t.content)!=null&&c.trim())})}return}}const{root:i,body:s}=Ze(e,t.role,t.content,{time:t.created_at?ft(new Date(t.created_at)):void 0,turnId:t.id,createdAt:t.created_at,meta:t.meta});if((l=t.parts)!=null&&l.length&&(s.textContent="",await kd(s,t.parts)),t.role==="user"&&t.id){const E=((u=t.meta)==null?void 0:u.enrichments)||{};Object.keys(E).length&&hn(i,o,t.id,E)}if(t.role==="assistant"&&t.id){i.dataset.turnId=t.id;const E=(p=t.meta)==null?void 0:p.user_turn_id,I=a.indexOf(t);let _;if(E)_=a.find(S=>S.id===E);else for(let S=I-1;S>=0;S--)if(a[S].role==="user"){_=a[S];break}const P=!!((m=_==null?void 0:_.meta)!=null&&m.recalled_at),f=!!((g=t.meta)!=null&&g.recalled_at),w=String(((v=t.meta)==null?void 0:v.ingested_atom_id)||((k=_==null?void 0:_.meta)==null?void 0:k.ingested_atom_id)||"");!f&&!P&&t.content.trim()&&Xe(i,{ingestedAtomId:w||void 0,createdAt:t.created_at,rememberable:w?void 0:!0,userRecalled:P,assistantRecalled:f})}}async function Pd(e,t,n){if(!(C().health==="offline"||C().health==="unknown"||n.value)){n.value=!0,Di(e,t);try{const a=await K();await Xo(a,t.id),d(`已发送 ${t.label}`,"success")}catch(a){d(String(a),"error")}finally{n.value=!1}}}function Ui(e,t){const n=t.steps||[],a=String(t.goal||""),o=e.querySelector(".chat-plan-bar");o&&o.remove();const i=document.createElement("div");i.className="chat-plan-bar",i.innerHTML=`<div class="chat-plan-goal">${a?`任务：${a.slice(0,80)}`:"Plan"}</div>
    <div class="chat-plan-steps">${n.map(r=>`<span class="plan-step plan-step-${String(r.status||"pending")}">${String(r.label||r)}</span>`).join("")}</div>`;const s=e.querySelector(".discord-message-content");s==null||s.prepend(i)}async function Id(e,t,n,a,o,i,s){const r=t.value;if(!Nl(r,i.chips)||C().health==="offline"||C().health==="unknown"||o.value)return;const c=[...i.chips],l=Dl(r,c),u=r.trim()||(c.length?" ":"");o.value=!0,a.disabled=!0,t.value="",i.clear(),Mi(s,i.chips);const{root:p,body:m}=Ze(e,"user",u);_d(m,r,c);const g=p,{root:v,body:k}=Ze(e,"assistant","…"),E=v.querySelector(".discord-message-content"),I=[];let _=null;const P=async f=>{const w=String(f.sticker_id||"");if(!w)return;_=_||await fn();const S=Ia(_,w);S&&Ca(v,S,{stickerOnly:!!f.sticker_only,textEl:k})};try{const f=await K(F.chatMode??"auto"),w=await Qo(f,r.trim(),{onDelta:S=>{const B=k.textContent==="…"?"":k.textContent;k.textContent=Ct(B+S)},onSticker:S=>{P(S)},onTrace:(S,B)=>{I.push(...S),n.textContent=B!=null&&B.length?Ba(B):I.join(`
`)},onPlan:S=>{Ui(v,S)},onDone:S=>{const B=S.user_turn_id,L=S.user_turn_created_at,D=S.turn_id;g&&B&&(g.dataset.turnId=B,L&&(g.dataset.createdAt=L),Ni(g),(async()=>{var te;try{const X=(await Lt(f)).find(b=>b.id===B),Z=((te=X==null?void 0:X.meta)==null?void 0:te.enrichments)||{};Object.keys(Z).length&&hn(g,f,B,Z)}catch{}})()),D&&(v.dataset.turnId=D),S.sticker_id&&P(S);const T=!!S.sticker_id||k.textContent&&k.textContent!=="…";D&&T&&Xe(v,{rememberable:!!S.rememberable,rememberBlockReason:S.remember_block_reason,recallPending:!!S.recall_pending,recallWindowRemainingSeconds:S.recall_window_remaining_seconds,createdAt:new Date().toISOString()});const H=S.citations;H!=null&&H.length&&wd(E,H)},onError:S=>{const B=k.textContent||"";B&&B!=="…"||(k.textContent=S)}},{parts:l,chatMode:F.chatMode??"auto",planMode:Qe});k.textContent==="…"&&(k.textContent="（无回复）"),w.ok?(d("已发送","success"),It||(It=new Date().toISOString()),await Ge(f)):d(w.error||"发送失败","error")}catch(f){k.textContent=String(f),d(String(f),"error")}finally{o.value=!1,a.disabled=C().health==="offline"||C().health==="unknown",ge==null||ge.bump()}}async function Ge(e){var t;try{const n=await ya(e),a=n.meta||{};Qe=!!a.plan_mode,(t=document.getElementById("chatPlanBtn"))==null||t.classList.toggle("active",Qe);const o=id(a);F={...F,chatMode:o},await md();const i=cd(a);ud(o,e,i);const s=document.getElementById("chatSessionIdHint");s&&(s.textContent=e||"—");const r=document.getElementById("chatPersonaPreset");if(r){const p=a.persona_override;r.value=p!=null&&p.preset?String(p.preset):""}const c=document.getElementById("chatStickyStatus");c&&(c.textContent=rd(a));const l=document.getElementById("chatVpStatus");if(l){const p=a.equipped_vp_id;if(p!=null&&String(p).trim())try{const m=await _a(String(p)),g=!!m.has_avatar;await En(String(p),g),F={...F,equippedVpId:String(p),hasVpAvatar:g,vpLabel:m.label||m.id},l.textContent=`扮演：${m.label||m.id}`}catch{await En(String(p),!1),F={...F,equippedVpId:String(p),hasVpAvatar:!1},l.textContent=`扮演：${String(p)}`}else await En("",!1),F={...F,equippedVpId:void 0,hasVpAvatar:!1,vpLabel:void 0,vpAvatarUrl:void 0},l.textContent=""}pd(F),Ue==null||Ue(),jt==null||jt(),qn(n.tool_scope??null);const u=a.equipped_vp_id!=null&&String(a.equipped_vp_id).trim()!=="";ad=u,od(o,u)}catch{}}function Bd(e){var k,E,I,_,P;const t=document.getElementById("chatLog"),n=document.getElementById("chatInput"),a=document.getElementById("traceOut"),o=document.getElementById("sendChat"),i=document.getElementById("chatComposerChips"),s=new Ql,r={value:!1},c=()=>Mi(i,s.chips),l=dd();(async()=>{try{let f;if(l){f=(await qr(l)).sessionId;let L=l;try{L=(await _a(l)).label||l}catch{}d(`试讲模式 · ${L}`,"success")}else f=await K();const[w,S]=await Promise.all([Lt(f),fn()]);It=ld(w),await Ge(f);for(const B of w)await Ed(t,B,S,w,f)}catch(f){l&&d(String(f),"error")}})(),C().consoleProfile==="client"&&(async()=>{const f=document.getElementById("chatPersonaPreset");if(f)try{const S=(await fetchChatPersonas()).presets||{},B=Object.keys(S).sort();f.innerHTML=`<option value="">默认</option>${B.map(D=>{var T;return`<option value="${D}">${((T=S[D])==null?void 0:T.label)||D}</option>`}).join("")}`,f.disabled=!1;const L=C().sessionId;L&&await Ge(L)}catch(w){f.innerHTML='<option value="">（不可用）</option>',d(String(w),"error")}})(),(k=document.getElementById("chatPersonaPreset"))==null||k.addEventListener("change",async f=>{const w=f.target.value;try{const S=await K();await Re(S,{persona_override:w?{preset:w,mode:"turn_only"}:null}),await Ge(S),d(w?`人格 preset：${w}`:"已恢复默认人格","success")}catch(S){d(String(S),"error")}});const u=async f=>{try{const w=await K(f);await Re(w,{...sd(f),memory_space:Qt(f)}),await Ge(w),d(`已切换为${Te[f]}模式`,"success")}catch(w){d(String(w),"error")}},p=()=>{const f=document.getElementById("chatModePopover");f&&(f.hidden=!0)};vt=f=>void u(f),Ri=p,Il(vt),Ai(vt,p),(E=document.getElementById("clearSticky"))==null||E.addEventListener("click",async()=>{try{const f=await K();await Re(f,{clear_pathway_sticky:!0}),await Ge(f),d("已清除语境锁定","success")}catch(f){d(String(f),"error")}}),document.getElementById("newSession").onclick=async()=>{j({sessionId:null}),localStorage.removeItem("niannian.sessionId"),It=void 0,await ba(Qt(F.chatMode??"auto")),d("已新建会话","success"),e()},document.getElementById("chatTrace").onchange=f=>{j({showTrace:f.target.checked}),yo()},yo(),Xc(ga);const m=()=>void Id(t,n,a,o,r,s,i);document.getElementById("sendChat").onclick=m;const g=document.getElementById("chatAttachInput");(I=document.getElementById("chatAttachBtn"))==null||I.addEventListener("click",()=>g==null?void 0:g.click()),(_=document.getElementById("chatLinkBtn"))==null||_.addEventListener("click",()=>{const f=window.prompt("粘贴或输入链接 URL");if(!(f!=null&&f.trim()))return;const w=Hn(f.trim())[0]||f.trim();if(!/^https?:\/\//i.test(w)){d("请输入 http 或 https 链接","error");return}s.add(xi(w)),c()}),(P=document.getElementById("chatPlanBtn"))==null||P.addEventListener("click",()=>{(async()=>{var f;try{const w=await K(),S=!Qe;await Re(w,{plan_mode:S}),Qe=S,(f=document.getElementById("chatPlanBtn"))==null||f.classList.toggle("active",S),d(S?"已开启 Plan 模式":"已关闭 Plan 模式","success")}catch(w){d(String(w),"error")}})()}),g==null||g.addEventListener("change",()=>{(async()=>{var f;(f=g.files)!=null&&f.length&&(await go(g.files,s),c(),g.value="")})()}),i.addEventListener("click",f=>{const w=f.target.closest(".composer-chip-remove");if(!w)return;const S=Number.parseInt(w.getAttribute("data-chip-index")||"",10);Number.isNaN(S)||(s.remove(S),c())});const v=document.querySelector(".chat-input-shell");v==null||v.addEventListener("dragover",f=>f.preventDefault()),v==null||v.addEventListener("drop",f=>{var S;f.preventDefault();const w=(S=f.dataTransfer)==null?void 0:S.files;w!=null&&w.length&&(async()=>(await go(w,s),c()))()}),n.addEventListener("paste",f=>{var B;const w=((B=f.clipboardData)==null?void 0:B.getData("text/plain"))||"";Hn(w).length&&w.trim().replace(/https?:\/\/[^\s<>"')\]]+/gi,"").trim()===""&&f.preventDefault(),ed(w,s)&&c()}),cl(document.querySelector(".chat-composer"),f=>{Pd(t,f,r)},()=>F.chatMode??"auto").then(async({open:f,close:w,refresh:S})=>{Ue=S;const B=document.getElementById("chatStickerBtn"),L=document.getElementById("chatMcpBtn"),D=document.getElementById("chatToolScopeBar"),T=O=>{L&&(O===0?(L.disabled=!0,L.title="请先在 MCP 页启用外部记忆工具"):(L.disabled=!1,L.title="外部记忆"))},H=async O=>{try{const J=await K();await Re(J,{tool_scope:O}),qn(O),d(`外部记忆 · ${O.label}`,"success")}catch(J){d(String(J),"error")}},te=async()=>{try{const O=await K();await Re(O,{tool_scope:null}),qn(null),d("已关闭外部记忆","success")}catch(O){d(String(O),"error")}};D==null||D.addEventListener("click",O=>{O.target.closest("#chatToolScopeClear")&&te()});const{open:ce,close:X,refresh:Z,enabledCount:b}=await bl(document.querySelector(".chat-composer"),O=>{H(O)});jt=Z,T(b()),Z().then(()=>T(b()));try{const{budget:O}=await ki();O.chat_sticker_level==="off"&&B&&(B.hidden=!0)}catch{}const A=document.getElementById("chatModePopover");B.onclick=()=>{p(),X(),f()},L.onclick=()=>{L.disabled||(p(),w(),ce())},document.getElementById("chatModeBtn").onclick=O=>{if(O.stopPropagation(),!A)return;const J=A.hidden;w(),X(),A.hidden=!J},document.addEventListener("click",O=>{const J=O.target;J.closest("#chatModeBtn")||J.closest("#chatModePopover")||p()})}),n.onkeydown=f=>{f.key==="Enter"&&(f.ctrlKey||f.metaKey)&&(f.preventDefault(),m())},v&&td(n,v),(async()=>{try{const w=await K(F.chatMode??"auto");ue=await Dc(w)}catch{ue=null}const f=qc({enabled:!!(ue!=null&&ue.enabled),getChatMode:()=>F.chatMode??"auto",isVisible:()=>document.visibilityState==="visible",isSending:()=>r.value,isInputEmpty:()=>!n.value.trim(),hasUserMessages:()=>t.querySelectorAll(".discord-message-user").length>0,getProfile:()=>(ue==null?void 0:ue.profile)??{},onFire:()=>vd(t,r)});ge={bump:f.bump,stop:f.stop},document.addEventListener("visibilitychange",()=>{document.visibilityState==="visible"?f.schedule():f.stop()}),n.addEventListener("input",()=>f.bump()),f.schedule()})()}function Cd(e){return e instanceof Y&&(e.status===404||e.status===501)}async function en(e){const t=e?`/v1/upload/progress/${encodeURIComponent(e)}`:"/v1/upload/progress";try{return await y(t)}catch(n){if(Cd(n))return{};throw n}}function ut(e,t){const a=((e==null?void 0:e.storage)||{}).upload||{},o=(t==null?void 0:t.upload)||{};return{active:a.active??o.active??null,checkpoints_pending:Number(a.checkpoints_pending??o.checkpoints_pending??0)||0}}async function Td(){return y("/v1/admin/memory/maturity")}async function Ld(e="full_except_subject"){return y("/v1/admin/memory/backup",{method:"POST",body:JSON.stringify({scope:e})})}async function Ad(e){return y("/v1/admin/memory/wipe",{method:"POST",body:JSON.stringify(e)})}async function xd(e){return y("/v1/admin/memory/marginalize/preview",{method:"POST",body:JSON.stringify({percent:e})})}async function Md(e){return y("/v1/admin/memory/marginalize",{method:"POST",body:JSON.stringify({percent:e})})}const ji="apps/念念/data",Hi="https://pan.baidu.com/union",tn="https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade",nn="https://console.cloud.google.com/apis/credentials",_e={quark:{name:"夸克",hint:"P0 主库 · Cookie / refresh_token",liveLabel:"cookie_api",rank:"P0"},baidu:{name:"百度",hint:"P1 failover + daily mirror · Union PCS",liveLabel:"pcs",rank:"P1"},onedrive:{name:"OneDrive",hint:"P2 failover + daily mirror · Microsoft Graph",liveLabel:"graph",rank:"P2"}};function vn(e){const t=e.auth.toLowerCase();return t==="connected"?"connected":t==="needs_relogin"||t==="disabled"||t==="expired"?"needs-attention":t==="unconfigured"?"unconfigured":(e.mode==="mirror"||t==="degraded","degraded")}function Ta(e){return`auth-${e==="needs-attention"?"needs-attention":e}`}function Od(e){return e.tierProviders.every(t=>{const n=t.auth.toLowerCase();return n==="unconfigured"||n==="disabled"})}function Rd(e){return String((e==null?void 0:e.auth)||"unconfigured")}function Dd(e,t){var n;return(t==null?void 0:t.credentials)||((n=e==null?void 0:e.storage)==null?void 0:n.credentials)||{}}function In(e,t,n,a,o){const i=o||(e==="google_drive"?{name:"Google Drive",liveLabel:"drive_api"}:e==="feimao"?{name:"飞猫盘",liveLabel:"session"}:_e[e]),s=n==null?void 0:n[e],r=t!=null&&t.live?String(t.live):void 0;let c="mirror";return t!=null&&t.mode?c=String(t.mode):s?c=String(s):(r||(t==null?void 0:t.auth)==="connected")&&(c="live"),{id:e,name:i.name,auth:Rd(t),expiresInHours:(t==null?void 0:t.expires_in_hours)??null,error:(t==null?void 0:t.error)??null,updatedAt:(t==null?void 0:t.updated_at)??null,role:t==null?void 0:t.role,live:r||(c==="live"?i.liveLabel:void 0),mode:c,remoteRoot:(t==null?void 0:t.remote_root)||a,reachability:t==null?void 0:t.reachability,reachabilityHint:t==null?void 0:t.reachability_hint,failoverEligible:t==null?void 0:t.failover_eligible,mirrorStatus:t==null?void 0:t.mirror_status}}function Nd(e,t,n){var r,c;const a=t==null?void 0:t.remote_root;if(a)return a;const o=(c=(r=e==null?void 0:e.storage)==null?void 0:r.cloud)==null?void 0:c.remote_root;if(o)return o;const s=((n==null?void 0:n.storage)||{}).cloud||{};return String(s.remote_root||ji)}function Ud(e){const t=(e==null?void 0:e.baidu_oauth)||{};return{union_url:t.union_url||Hi,authorize_url:t.authorize_url,configured:t.configured??!!t.authorize_url,redirect_uri:t.redirect_uri||"oob",scope:t.scope||"basic,netdisk"}}function qi(e,t){var m,g;const o=((m=(((t==null?void 0:t.storage)||{}).cloud||{})[e])==null?void 0:m.oauth)||{},s=((g=((t==null?void 0:t.providers)||{})[e])==null?void 0:g.oauth)||{},r={...o,...s},c=String(r.client_id||"").trim();if(!c)return{configured:!1,portal_url:e==="onedrive"?tn:nn};if(e==="onedrive"){const v=r.tenant||"common",k=r.redirect_uri||"http://localhost:18766/oauth/callback",E=r.scope||"offline_access Files.ReadWrite",I=new URLSearchParams({client_id:c,response_type:"code",redirect_uri:k,scope:E,response_mode:"query"});return{configured:!0,authorize_url:`https://login.microsoftonline.com/${v}/oauth2/v2.0/authorize?${I}`,redirect_uri:k,scope:E,portal_url:tn}}const l=r.redirect_uri||"http://localhost:18767/oauth/callback",u=r.scope||"https://www.googleapis.com/auth/drive.file";return{configured:!0,authorize_url:`https://accounts.google.com/o/oauth2/v2/auth?${new URLSearchParams({client_id:c,response_type:"code",redirect_uri:l,scope:u,access_type:"offline",prompt:"consent"})}`,redirect_uri:l,scope:u,portal_url:nn}}function jd(e,t){const n=e==null?void 0:e.onedrive_oauth;return n!=null&&n.authorize_url||(n==null?void 0:n.configured)===!1?{portal_url:tn,redirect_uri:n.redirect_uri||"http://localhost:18766/oauth/callback",scope:n.scope||"offline_access Files.ReadWrite",...n}:qi("onedrive",t)}function Hd(e,t){const n=e==null?void 0:e.google_oauth;return n!=null&&n.authorize_url||(n==null?void 0:n.configured)===!1?{portal_url:nn,redirect_uri:n.redirect_uri||"http://localhost:18767/oauth/callback",scope:n.scope||"https://www.googleapis.com/auth/drive.file",...n}:qi("google_drive",t)}function qd(e,t,n,a){var u;const i=((e==null?void 0:e.storage)||{}).monthly_archive,s=t==null?void 0:t.monthly_archive,c={...((u=n==null?void 0:n.storage)==null?void 0:u.monthly_archive)||{},...i,...s},l=a==null?void 0:a.google_drive;return{enabled:c.enabled!==!1,lastRunAt:c.last_run_at??null,googleDriveStatus:String(c.google_drive_status||"unknown"),filesCopied:typeof c.files_copied=="number"?c.files_copied:null,lastError:c.last_error??null,cron:String(c.cron||"0 3 1 * *"),googleAuth:String((l==null?void 0:l.auth)||"unconfigured"),googleRole:l!=null&&l.role?String(l.role):"monthly_only"}}function Vd(e,t,n){var u;const a=Dd(e,t),o=Nd(e,t,n),i=(t==null?void 0:t.clouds)||((u=e==null?void 0:e.storage)==null?void 0:u.clouds),s=["quark","baidu","onedrive"].map(p=>In(p,a[p],i,o,_e[p])),r=In("google_drive",a.google_drive,i,o),c=a.feimao,l=c&&c.auth!=="unconfigured"?In("feimao",c,i,o):null;return{remoteRoot:o,tierProviders:s,googleArchive:r,feimaoDeprecated:l,monthlyArchive:qd(e,t,n,a),baiduOAuth:Ud(t),onedriveOAuth:jd(t,n),googleOAuth:Hd(t,n)}}function Fd(e){var i;const t=(i=e.notifications)==null?void 0:i.tier,n=(t==null?void 0:t.channels)||{},a=n.webhook||{},o=n.macos||{};return{enabled:(t==null?void 0:t.enabled)!==!1,dedupeHours:Number((t==null?void 0:t.dedupe_hours)??6),console:n.console!==!1,webhookEnabled:!!a.enabled,webhookUrl:String(a.url||""),webhookSecret:String(a.secret_header||""),macosEnabled:!!o.enabled}}function Vn(e,t){var i;const n=(i=e==null?void 0:e.reconcile)==null?void 0:i.pending_conflicts,a=t==null?void 0:t.reconcile,o=n??(a==null?void 0:a.pending_conflicts)??0;return typeof o=="number"?o:Number(o)||0}function Fn(e,t){return e<=0||t?"":`<div class="reconcile-notice">云恢复后存在 <strong>${e}</strong> 处冲突，默认 newest_wins；详见云盘页运维面板或 <code>sync/reconcile_report.json</code></div>`}function Le(e){if(!e)return"—";try{return new Date(e).toLocaleString()}catch{return e}}function zd(e){return e==null?"—":e<=0?"已过期":e<24?`${Math.round(e)} 小时`:`约 ${Math.round(e/24)} 天`}function zn(e){return e.replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;")}function ie(e,t){return`<div class="core-learning"><p class="hint core-learning-intro">${e}</p>${t}</div>`}function M(e,t,n){const a=n?`<p class="hint core-card-foot">${n}</p>`:"";return`<section class="core-card"><h3 class="core-card-title">${e}</h3><div class="core-card-body">${t}</div>${a}</section>`}function me(e){return`<div class="core-table-wrap">${e}</div>`}const Wd={quark:{singleStep:"≤5MB",chunked:"OSS 多分片 · CompleteMultipartUpload"},baidu:{singleStep:"≤4MB",chunked:"PCS superfile2 多分片"},onedrive:{singleStep:"≤4MB",chunked:"Graph uploadSession · Content-Range"},google_drive:{singleStep:"≤5MB",chunked:"Drive resumable · 月备 only"},feimao:{singleStep:"—",chunked:"已停用"}};function Vi(e){const t=Wd[e];return e==="feimao"?'<p class="hint cloud-upload-hint">upload 已停用（v0.2.9 退场）</p>':`<p class="hint cloud-upload-hint">上传 · 单步 <strong>${t.singleStep}</strong> · 更大文件 <strong>自动分片</strong>（v0.2.11 · ${t.chunked}）</p>`}function Kd(){return`
    <div class="cloud-upload-overview">
      <strong>大文件 upload（v0.2.11 + v0.2.12）</strong>
      <p class="hint">≤ 单步上限走原路径不变；更大文件自动分片/resumable。硬顶见 <code>storage.cloud.max_upload_bytes</code>（默认 512MB）。</p>
      <ul class="hint cloud-upload-limits">
        <li>夸克 · 单步 ≤5MB → OSS 多分片</li>
        <li>百度 · 单步 ≤4MB → superfile2</li>
        <li>OneDrive · 单步 ≤4MB → uploadSession</li>
        <li>Google 月备 · 单步 ≤5MB → resumable</li>
      </ul>
      <p class="hint cloud-checkpoint-hint"><strong>断点续传（v0.2.12）</strong>：大文件分片中断后 checkpoint 写入 <code>sync/upload_checkpoints/</code>；记忆页会显示黄条，可再次同步或 <code>niannian cloud upload-resume</code> 续传。</p>
    </div>`}const Fi=/file too large|too large for|upload_max_bytes|max_upload_bytes|exceeds.*limit/i,zi=/multipart|superfile|uploadsession|content-range|resumable|partseq|part_number/i;function Bt(e,t){const n=e instanceof Error?e.message:typeof e=="object"&&e!==null&&"error"in e?String(e.error):String(e??"未知错误"),a=n.toLowerCase(),o=t?`${Jd(t)}：`:"";return a.includes("max_upload_bytes")||/超过.*硬顶|hard cap/i.test(n)?`${o}文件超过全局硬顶 · 请缩小文件或调整 config <code>storage.cloud.max_upload_bytes</code>（默认 512MB）。念念不会 silent truncate。`:Fi.test(n)?a.includes("single upload")||a.includes("single-part")||a.includes("upload_max")?`${o}文件超过单步上限 · v0.2.11 起应自动走分片；若仍报错，请确认已升级至 v0.2.11 并检查 <code>storage.cloud.${t||"*"}</code> 的 upload/chunk 配置。`:`${o}文件过大 · 请确认 <code>storage.cloud.max_upload_bytes</code> 与分片配置；≤ 单步上限的文件行为与旧版一致。`:zi.test(n)?`${o}分片上传中断：${n} · 请重试同步或查看 worker/tier 日志；不会 partial commit 到 manifest。`:n}function Jd(e){return{quark:"夸克",baidu:"百度",onedrive:"OneDrive",google_drive:"Google Drive"}[e]||e}function La(e,t){return e?Fi.test(e)||zi.test(e)?Bt(e,t):e:""}const Gd=new Set(["init","uploading","finishing","paused"]);function Wi(e){return{quark:"夸克",baidu:"百度",onedrive:"OneDrive",google_drive:"Google Drive"}[e||""]||e||"—"}function bo(e){return e==null||Number.isNaN(e)?"—":e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(1)} KB`:`${(e/(1024*1024)).toFixed(2)} MB`}function Yd(e){if(typeof e.percent=="number"&&!Number.isNaN(e.percent))return Math.max(0,Math.min(100,e.percent));const t=e.bytes_total??0,n=e.bytes_done??0;return t>0?Math.max(0,Math.min(100,n/t*100)):0}function an(e){return e!=null&&e.job_id?Gd.has(String(e.phase||"uploading")):!1}function Xd(e){return e<=0?"":`
    <div class="upload-checkpoint-banner" role="status">
      <strong>有 ${e} 个未完成的大文件上传</strong>
      <p class="hint">分片中断后 checkpoint 已落盘（<code>sync/upload_checkpoints/</code>）。再次点「立即同步」或 CLI
        <code>niannian cloud upload-resume</code> 可续传；过期见 <code>storage.cloud.upload.checkpoint_ttl_days</code>。</p>
    </div>`}function wo(e){if(!(e!=null&&e.job_id)||e.phase!=="failed")return"";const t=e.error||e.message||"上传失败",n=Bt(t,e.provider);return`
    <div class="upload-failed-banner" role="alert">
      <strong>同步上传失败 · ${Wi(e.provider)}</strong>
      <p class="upload-failed-path"><code>${Wn(e.rel_path||"—")}</code></p>
      <p class="hint">${n}</p>
      ${e.resumable||e.checkpoint_id?'<p class="hint">checkpoint 已保留，修复网络后可重试同步续传。</p>':""}
    </div>`}function Qd(e){if(!(e!=null&&e.job_id)||!an(e))return'<div id="uploadProgressPanel" class="upload-progress-panel hidden"></div>';const t=Yd(e),n=e.chunk_total!=null&&e.chunk_total>0?`分片 ${(e.chunk_index??0)+1} / ${e.chunk_total}`:"",a=tu(e.phase);return`
    <div id="uploadProgressPanel" class="upload-progress-panel" role="progressbar" aria-valuenow="${Math.round(t)}" aria-valuemin="0" aria-valuemax="100">
      <div class="upload-progress-head">
        <span class="badge live">${Wi(e.provider)}</span>
        <span class="upload-progress-path"><code>${Wn(e.rel_path||"—")}</code></span>
        <span class="upload-progress-pct">${t.toFixed(0)}%</span>
      </div>
      <div class="upload-progress-track">
        <div class="upload-progress-fill" style="width:${t.toFixed(1)}%"></div>
      </div>
      <p class="hint upload-progress-meta">
        ${a}${n?` · ${n}`:""}
        · ${bo(e.bytes_done)} / ${bo(e.bytes_total)}
        ${e.message?` · ${Wn(e.message)}`:""}
      </p>
    </div>`}class Zd{constructor(t){this.onUpdate=t,this.timer=null}start(t){this.stop(),this.jobId=t,this.tick(),this.timer=setInterval(()=>void this.tick(),1e3)}stop(){this.timer!=null&&(clearInterval(this.timer),this.timer=null)}async tick(){try{const t=await en(this.jobId),n=t.job_id?t:null;if(this.onUpdate(n),n&&an(n))return;if((n==null?void 0:n.phase)==="failed"){this.stop();return}(n==null?void 0:n.phase)==="done"&&(this.onUpdate(null),this.stop())}catch{this.stop()}}}function eu(e){const t=e.querySelector("#uploadCheckpointBanner"),n=e.querySelector("#uploadFailedBanner"),a=e.querySelector("#uploadProgressHost"),o=(r,c=null)=>{t&&(t.innerHTML=Xd(r.checkpoints_pending??0)),n&&(n.innerHTML=wo(c))},i=r=>{a&&(a.innerHTML=Qd(r)),n&&(r==null?void 0:r.phase)==="failed"&&(n.innerHTML=wo(r))},s=new Zd(r=>{i(r),(r==null?void 0:r.phase)==="failed"&&s.stop()});return{refreshBanners:o,showProgress:i,poller:s}}function tu(e){switch(e){case"init":return"准备中";case"uploading":return"上传中";case"finishing":return"收尾";case"paused":return"已暂停";case"done":return"完成";case"failed":return"失败";default:return e||"上传中"}}function Wn(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function nu(e,t){var m,g,v;const n=(e==null?void 0:e.storage)||{},a=n.archive||{},o=Fn(Vn(e,t),(m=e==null?void 0:e.reconcile)==null?void 0:m.acknowledged),i=a.last_skipped_reason??(t==null?void 0:t.last_archive_skipped_reason)??"—",s=a.pending_distilled??(t==null?void 0:t.pending_distilled)??"—",r=a.min_distilled_atoms??"50",c=String(((g=e==null?void 0:e.runtime)==null?void 0:g.mode)??((v=t==null?void 0:t.backends)==null?void 0:v.mode)??"—"),l=n.manifest_version??(t==null?void 0:t.version)??"—",u=c==="docker"?"tmpfs（/data/cache）":"本地 cache",p=String((t==null?void 0:t.node_id)??"—");return`
    ${o}
    <p class="hint">Tier / 云盘配种见 <a href="${z("cloud")}">云盘</a> 页</p>
    <div class="memory-grid">
      <div class="stat-card">
        <h3>NAS 节点</h3>
        <p>runtime：<strong>${c}</strong> · node <code>${p}</code></p>
        <p class="hint">L0 介质：<strong>${u}</strong></p>
        <p class="hint">manifest v<strong>${l}</strong></p>
      </div>
      <div class="stat-card">
        <h3>NAS 归档</h3>
        <p>待归档 distilled：<strong>${s}</strong> / 门槛 ${r}</p>
        <p class="hint">跳过原因：${i}</p>
        <p class="hint">上次归档：${Le(n.last_archive_at)}</p>
      </div>
      <div class="stat-card">
        <h3>同步</h3>
        <p>pending atoms：<strong>${(t==null?void 0:t.pending_atoms)??"—"}</strong></p>
        <p class="hint">manifest v${l}</p>
        <p class="hint">Dream：${Le(n.last_dream_at)}</p>
      </div>
    </div>`}function au(){return`
    <div id="uploadCheckpointBanner"></div>
    <div id="uploadFailedBanner"></div>
    ${ie("记忆存储、同步与运维",`
      ${M("存储状态",'<div id="storageStatus" class="hint">加载中…</div>')}
      ${M("同步",`<div id="syncStatus" class="hint">加载中…</div>
        <div id="uploadProgressHost"></div>
        <div class="core-switch-row panel-tail-row panel-tail-row--inline">
          <button class="btn-primary" id="syncPush">立即同步</button>
          <button class="secondary" id="syncPull">从云端拉取</button>
          <button class="secondary" id="runDream">Dream 提炼</button>
        </div>
        <pre id="syncOut" class="config core-preview"></pre>`)}
      <section class="core-card memory-ops-panel" id="memoryOpsPanel" hidden>
        <h3 class="core-card-title">记忆运维</h3>
        <div class="core-card-body">
          <p id="memoryMaturityHint" class="hint">加载人格锁状态…</p>
          <details class="memory-danger-zone core-details">
            <summary>危险区 · 备份与清库</summary>
            <div class="memory-danger-body">
              <label class="core-field"><span>清库范围</span>
                <select id="wipeScope">
                  <option value="atoms_wiki">atoms + wiki 缓存</option>
                  <option value="atoms_wiki_vault">含 user vault</option>
                  <option value="full_except_subject">全清（保留 subject）</option>
                </select>
              </label>
              <div class="core-switch-row panel-tail-row panel-tail-row--inline">
                <button class="secondary" id="memoryBackupBtn" type="button">1. 备份</button>
                <input id="wipeConfirm" type="text" placeholder="WIPE-pinn" />
                <button class="btn-danger" id="memoryWipeBtn" type="button">2. 清库</button>
              </div>
              <p id="memoryOpsOut" class="hint"></p>
            </div>
          </details>
          <div class="memory-marginalize-zone" id="marginalizeZone" hidden>
            <h4>边缘化治理</h4>
            <label>比例 <input id="marginalizePercent" type="range" min="5" max="30" value="10" /> <span id="marginalizePctLabel">10%</span></label>
            <div class="core-switch-row panel-tail-row panel-tail-row--inline">
              <button class="secondary" id="marginalizePreviewBtn" type="button">预览</button>
              <button class="btn-primary" id="marginalizeApplyBtn" type="button">执行边缘化</button>
            </div>
            <pre id="marginalizeOut" class="config core-preview"></pre>
          </div>
        </div>
      </section>
    `)}`}async function Bn(e,t,n){const[a,o]=await Promise.all([Pt(),mn().catch(i=>i instanceof Y&&i.status===401?null:Promise.reject(i))]);return t.innerHTML=nu(a,o),e.textContent=o?`pending atoms: ${o.pending_atoms??"?"} · last cloud sync: ${Le(o.last_cloud_sync)}`:"同步详情需 Token · 见设置页",n(ut(a,o)),{health:a,sync:o}}async function ou(e){var a;const t=await en();if(t.job_id)return t;const n=(a=e.active)==null?void 0:a.job_id;if(n){const o=await en(n);if(o.job_id)return o}return null}function iu(e){const t=e.upload_job_id;if(typeof t=="string"&&t)return t;const n=e.upload_jobs;if(Array.isArray(n)&&n.length>0){const a=n[0];if(typeof a.job_id=="string")return a.job_id}}async function su(){const e=document.getElementById("syncStatus"),t=document.getElementById("storageStatus"),n=document.getElementById("syncOut"),a=document.getElementById("syncPush"),o=document.getElementById("syncPull"),i=document.getElementById("runDream"),s=eu(document);let r=null;const c=p=>{s.refreshBanners(p,r)},l=async()=>{await Bn(e,t,c)};try{const{health:p,sync:m}=await Bn(e,t,c),g=ut(p,m),v=await ou(g);(v==null?void 0:v.phase)==="failed"?(r=v,c(g),s.showProgress(null)):v&&an(v)&&(s.showProgress(v),s.poller.start(v.job_id))}catch(p){t.textContent=`存储状态不可用：${p}`,e.textContent=`同步不可用：${p}`}a.onclick=async()=>{a.disabled=!0,o.disabled=!0,r=null,s.showProgress({job_id:"pending",phase:"init",rel_path:"sync/…",percent:0,message:"正在推送…"}),s.poller.stop(),s.poller.start();try{const p=await ec();n.textContent=JSON.stringify(p,null,2);const m=iu(p);m&&(s.poller.stop(),s.poller.start(m));const{health:g,sync:v}=await Bn(e,t,c),k=await en(m);k.phase==="failed"?(r=k,s.showProgress(null),c(ut(g,v))):an(k)||(s.showProgress(null),s.poller.stop())}catch(p){n.textContent=String(p),s.showProgress(null),s.poller.stop(),r={job_id:"push-error",phase:"failed",error:String(p),rel_path:"sync/push"};try{const m=await Pt(),g=await mn();c(ut(m,g))}catch{c(ut(null,null))}}finally{a.disabled=!1,o.disabled=!1}},o.onclick=async()=>{o.disabled=!0,i.disabled=!0;try{n.textContent=JSON.stringify(await tc(),null,2),await l()}catch(p){n.textContent=String(p)}finally{o.disabled=!1,i.disabled=!1}},i.onclick=async()=>{o.disabled=!0,i.disabled=!0;try{n.textContent=JSON.stringify(await nc(C().memorySpace),null,2)}catch(p){n.textContent=String(p)}finally{o.disabled=!1,i.disabled=!1}};const u=document.getElementById("memoryOpsPanel");if(u&&C().userRole==="owner"){u.hidden=!1;const p=document.getElementById("memoryMaturityHint"),m=document.getElementById("marginalizeZone"),g=document.getElementById("memoryOpsOut"),v=document.getElementById("marginalizeOut"),k=document.getElementById("marginalizePercent"),E=document.getElementById("marginalizePctLabel"),I=document.getElementById("wipeScope"),_=document.getElementById("wipeConfirm");let P;k.oninput=()=>{E.textContent=`${k.value}%`};try{const w=!!(await Td()).subject_matured;p.textContent=w?"人格已稳定 · 禁止全清 · 仅可使用边缘化":"测试期 · 可先备份后清库",document.querySelector(".memory-danger-zone").hidden=w,m.hidden=!w}catch(f){p.textContent=`运维 API 不可用：${f}`}document.getElementById("memoryBackupBtn").onclick=async()=>{try{const f=I.value,w=await Ld(f);P=w.backup_id,g.textContent=`备份完成 · ${w.backup_id} · ${w.files} 文件`}catch(f){g.textContent=String(f)}},document.getElementById("memoryWipeBtn").onclick=async()=>{try{const f=await Ad({scope:I.value,confirm:_.value,backup_id:P});g.textContent=`清库完成 · ${JSON.stringify(f)}`,await l()}catch(f){g.textContent=String(f)}},document.getElementById("marginalizePreviewBtn").onclick=async()=>{try{const f=await xd(Number(k.value));v.textContent=JSON.stringify(f,null,2)}catch(f){v.textContent=String(f)}},document.getElementById("marginalizeApplyBtn").onclick=async()=>{try{const f=await Md(Number(k.value));v.textContent=JSON.stringify(f,null,2)}catch(f){v.textContent=String(f)}}}}async function ru(){try{return await y("/v1/cloud/status")}catch(e){if(e instanceof Y&&(e.status===401||e.status===404||e.status===501))return{};throw e}}async function cu(e,t){return N(`/v1/cloud/login/${encodeURIComponent(e)}`,{token:t})}async function lu(e){return N("/v1/cloud/login/baidu",{token:e.trim(),oauth:!0})}async function _o(e,t){return N(`/v1/cloud/login/${encodeURIComponent(e)}`,{token:t.trim(),oauth:!0})}async function du(){try{return await y("/v1/tier/ops")}catch(e){if(e instanceof Y&&(e.status===401||e.status===404||e.status===501))return null;throw e}}async function uu(){try{return await y("/v1/tier/reconcile")}catch(e){if(e instanceof Y&&(e.status===401||e.status===404||e.status===501))return null;throw e}}async function pu(){return N("/v1/tier/reconcile/ack",{acknowledged:!0})}async function mu(){return N("/v1/tier/pending/drain",{})}function fu(e,t){var s;const n=((s=e==null?void 0:e.storage)==null?void 0:s.tier)||(t==null?void 0:t.tier)||{},a=(e==null?void 0:e.reconcile)||(t==null?void 0:t.reconcile)||{},o=String(n.active_role||"quark");return{pending_uploads:typeof n.pending_uploads=="number"?n.pending_uploads:Number(n.pending_uploads)||0,active_role:o,queue_path:String(n.queue_path||"sync/pending_uploads.jsonl"),queue_preview:[],reconcile:{pending_conflicts:Number(a.pending_conflicts)||0,acknowledged:!!a.acknowledged,report_path:String(a.report_path||"sync/reconcile_report.json"),last_at:a.last_at??null}}}function Ki(e){if(!e)return"—";try{return new Date(e).toLocaleString()}catch{return e}}function hu(e){return e==null||Number.isNaN(e)?"—":e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(1)} KB`:`${(e/(1024*1024)).toFixed(1)} MB`}function vu(e,t){const n=e.reconcile||{},a=(t==null?void 0:t.conflicts)||[],o=typeof(t==null?void 0:t.pending_conflicts)=="number"?t.pending_conflicts:void 0,i=n.pending_conflicts??0;return{pendingConflicts:o??i??a.length,acknowledged:!!((t==null?void 0:t.acknowledged)??n.acknowledged),strategy:String((t==null?void 0:t.strategy_default)||"newest_manifest_wins"),reportPath:String(n.report_path||"sync/reconcile_report.json"),lastAt:n.last_at??(t==null?void 0:t.generated_at)??null,conflicts:a,generatedAt:(t==null?void 0:t.generated_at)??null}}function gu(e,t,n,a,o){var c,l;const i=n||fu(e,t),s=String(i.active_role||((l=(c=e==null?void 0:e.storage)==null?void 0:c.tier)==null?void 0:l.active_role)||"quark"),r=vu(i,a);return{activeRole:s,isLocalDegraded:s==="local_degraded",pendingUploads:Number(i.pending_uploads)||0,queuePath:String(i.queue_path||"sync/pending_uploads.jsonl"),queuePreview:i.queue_preview||[],reconcile:r,apiAvailable:o}}async function ko(e,t){let n=null,a=null,o=!1;try{n=await du(),o=n!=null}catch{n=null}try{a=await uu(),a&&(o=!0)}catch{a=null}return gu(e,t,n,a,o)}function yu(e){return e.isLocalDegraded?`<div class="local-degraded-banner">
    <strong>local_degraded</strong> · 三云均不可达，写入落 L0 队列（<strong>${e.pendingUploads}</strong> 条待上传）
    · 请修复夸克/百度或换网络后 drain / reconcile
  </div>`:""}function bu(e){return e.length?`<div class="table-wrap"><table><thead><tr><th>路径</th><th>大小</th><th>入队时间</th></tr></thead><tbody>${e.map(n=>`<tr><td><code>${n.rel_path}</code></td><td>${hu(n.size_bytes)}</td><td>${Ki(n.at)}</td></tr>`).join("")}</tbody></table></div>`:'<p class="hint">队列为空或未从 API 加载预览</p>'}function wu(e){return e.length?`<div class="table-wrap"><table><thead><tr><th>路径</th><th>原因</th><th>quark</th><th>baidu</th><th>winner</th></tr></thead><tbody>${e.map(n=>`<tr>
          <td><code>${n.rel_path}</code></td>
          <td class="hint">${n.reason||"—"}</td>
          <td><span class="hint">${(n.quark_sha||"—").slice(0,8)}</span></td>
          <td><span class="hint">${(n.baidu_sha||"—").slice(0,8)}</span></td>
          <td><strong>${n.winner||"—"}</strong></td>
        </tr>`).join("")}</tbody></table></div>`:'<p class="hint">（无冲突明细 · 等待 reconcile_report 或 GET /v1/tier/reconcile）</p>'}function $o(e){const t=yu(e),n=e.reconcile.pendingConflicts>0&&!e.reconcile.acknowledged,a=!e.apiAvailable||!n,o=!e.apiAvailable;return`
    ${t}
    <div class="tier-ops-grid">
      <div class="stat-card tier-ops-card">
        <h3>Pending 写队列 ${e.isLocalDegraded?'<span class="badge err">local_degraded</span>':""}</h3>
        <p>待上传：<strong>${e.pendingUploads}</strong></p>
        <p class="hint">active_role：<strong>${e.activeRole}</strong></p>
        <p class="hint">queue：<code>${e.queuePath}</code></p>
        <div class="row" style="margin-top:0.5rem">
          <button class="secondary" id="tierToggleQueue">查看队列</button>
          <button class="secondary" id="tierDrainPending" ${o?"disabled":""}>手动 drain</button>
        </div>
        <div id="tierQueuePreview" class="hidden" style="margin-top:0.75rem">
          ${bu(e.queuePreview)}
        </div>
        ${e.apiAvailable?"":'<p class="hint">/v1/tier/ops 未就绪 · 显示 health 摘要</p>'}
      </div>
      <div class="stat-card tier-ops-card">
        <h3>Reconcile 冲突 ${n?`<span class="badge warn">${e.reconcile.pendingConflicts}</span>`:""}</h3>
        <p class="hint">策略：<strong>${e.reconcile.strategy}</strong></p>
        <p class="hint">report：<code>${e.reconcile.reportPath}</code></p>
        <p class="hint">生成：${Ki(e.reconcile.generatedAt||e.reconcile.lastAt)}</p>
        ${e.reconcile.acknowledged?'<p class="hint tier-ops-ack-done">✓ 已人工确认</p>':n?`<p>待确认冲突：<strong>${e.reconcile.pendingConflicts}</strong></p>`:'<p class="hint">暂无待确认冲突</p>'}
        <div style="margin-top:0.5rem">
          <button id="tierReconcileAck" ${a?"disabled":""}>我已确认，按 newest_wins 继续</button>
        </div>
      </div>
    </div>
    <details id="tierReconcileDetails" class="tier-reconcile-details" ${n?"open":""}>
      <summary>冲突明细（${e.reconcile.conflicts.length||e.reconcile.pendingConflicts}）</summary>
      ${wu(e.reconcile.conflicts)}
    </details>`}function So(e,t){var n,a,o;(n=e.querySelector("#tierToggleQueue"))==null||n.addEventListener("click",()=>{const i=e.querySelector("#tierQueuePreview");i==null||i.classList.toggle("hidden")}),(a=e.querySelector("#tierDrainPending"))==null||a.addEventListener("click",async()=>{if(confirm("手动触发 pending 队列 drain？（需任一级云 healthy）"))try{const i=await mu();d(`drain 完成：${JSON.stringify(i)}`,"success"),await t()}catch(i){d(String(i),"error")}}),(o=e.querySelector("#tierReconcileAck"))==null||o.addEventListener("click",async()=>{if(confirm("确认已阅冲突报告，按 newest_wins 策略继续？"))try{await pu(),d("已确认 reconcile 冲突","success"),await t()}catch(i){d(String(i),"error")}})}function _u(e,t,n){e.querySelectorAll(".cloud-login-btn").forEach(s=>{s.addEventListener("click",async()=>{const r=s.dataset.provider,c=e.querySelector(`.cloud-token-input[data-provider="${r}"]`),l=c==null?void 0:c.value.trim();if(!l){d("请先粘贴 token","error");return}try{await n.onTokenLogin(r,l),c&&(c.value=""),await t()}catch(u){d(Bt(u,r),"error")}})});const a=(s,r,c)=>{var l;(l=e.querySelector(`#${s}`))==null||l.addEventListener("click",async()=>{const u=e.querySelector(`.${r}`),p=u==null?void 0:u.value.trim();if(!p){d("请先粘贴 authorization code","error");return}if(!c){d("OAuth 交换尚未就绪 · 请使用 CLI 或配种脚本","info");return}try{await c(p),u.value="",await t()}catch(m){d(Bt(m),"error")}})};a("baiduOauthSubmit","baidu-oauth-code",n.onBaiduOAuth),a("onedriveOauthSubmit","onedrive-oauth-code",n.onOnedriveOAuth),a("googleOauthSubmit","google-oauth-code",n.onGoogleOAuth);const o=(s,r,c)=>{var l;(l=e.querySelector(`#${s}`))==null||l.addEventListener("click",()=>{var m;const u=c?(m=e.querySelector(`.${c}-url`))==null?void 0:m.value:"",p=r||u;p?window.open(p,"_blank","noopener"):d("authorize_url 未配置","error")})},i=(s,r,c)=>{var l;(l=e.querySelector(`#${s}`))==null||l.addEventListener("click",()=>{var m;const u=c?(m=e.querySelector(`.${c}-url`))==null?void 0:m.value:"",p=r||u;p?(navigator.clipboard.writeText(p),d("授权 URL 已复制","success")):d("无 URL 可复制","error")})};o("baiduOauthOpen",n.baiduAuthorizeUrl,"baidu-oauth-code"),i("baiduOauthCopy",n.baiduAuthorizeUrl,"baidu-oauth-code"),o("onedriveOauthOpen",n.onedriveAuthorizeUrl,"onedrive-oauth-code"),i("onedriveOauthCopy",n.onedriveAuthorizeUrl,"onedrive-oauth-code"),o("googleOauthOpen",n.googleAuthorizeUrl,"google-oauth-code"),i("googleOauthCopy",n.googleAuthorizeUrl,"google-oauth-code")}function Ji(e){const t=vn(e),n=t==="connected"?"connected":t==="needs-attention"?e.auth:t==="unconfigured"?"unconfigured":"mirror";return`<span class="badge ${Ta(t)}">${n}</span>`}function ku(e){return(e.mode||"mirror").toLowerCase()==="mirror"?'<span class="badge mirror">mirror</span>':`<span class="badge live">live · ${e.live||(e.id in _e?_e[e.id].liveLabel:e.id==="google_drive"?"drive_api":"session")}</span>`}function $u(e){const t=vn(e);return e.id==="quark"?`
      <div class="row cloud-login-row">
        <input type="password" class="cloud-token-input" data-provider="quark" placeholder="Cookie / refresh_token" />
        <button class="${t==="unconfigured"?"btn-primary":"secondary"} cloud-login-btn" data-provider="quark">${t==="connected"?"重新配种":"保存 Cookie"}</button>
      </div>`:t==="unconfigured"?'<p class="hint">展开下方 OAuth 引导完成配种</p>':t==="needs-attention"?'<p class="hint tier-error">请重新登录 OAuth</p>':'<p class="hint">已连接 · 可展开 OAuth 重新配种</p>'}function Cn(e,t,n){const a=!!t.authorize_url,o=t.portal_url||n.portalUrl;return`
    <details class="oauth-guide-box cloud-details">
      <summary>${e}</summary>
      ${n.steps.map(i=>`<p class="hint">${i}</p>`).join("")}
      ${n.scriptHint?`<p class="hint">${n.scriptHint}</p>`:""}
      <p class="hint"><a href="${o}" target="_blank" rel="noopener">${n.portalLabel}</a> · redirect_uri=<code>${t.redirect_uri||"—"}</code></p>
      ${a?`<div class="row oauth-url-row">
              <input class="oauth-url ${n.codeClass}-url" readonly value="${zn(t.authorize_url||"")}" />
              <button type="button" class="secondary" id="${n.copyId}">复制 URL</button>
              <button type="button" class="secondary" id="${n.openId}">打开授权</button>
            </div>`:'<p class="hint tier-error">authorize_url 未就绪 · 配置 providers.local.yaml 后刷新</p>'}
      <div class="row cloud-login-row">
        <input type="text" class="oauth-code ${n.codeClass}" placeholder="粘贴 authorization code" autocomplete="off" />
        <button type="button" class="${n.submitId==="baiduOauthSubmit"&&!a?"secondary":"btn-primary"}" id="${n.submitId}">OAuth 登录</button>
      </div>
      <details class="oauth-paste-fallback">
        <summary>高级：直接粘贴 refresh_token</summary>
        <div class="row cloud-login-row">
          <input type="password" class="cloud-token-input" data-provider="${n.codeClass.includes("baidu")?"baidu":n.codeClass.includes("onedrive")?"onedrive":"google_drive"}" placeholder="refresh_token" />
          <button class="secondary cloud-login-btn" data-provider="${n.codeClass.includes("baidu")?"baidu":n.codeClass.includes("onedrive")?"onedrive":"google_drive"}">保存 token</button>
        </div>
      </details>
    </details>`}function Ht(e,t,n){const a=vn(e),o=_e[e.id];return`
    <article class="cloud-provider-card ${Ta(a)}" data-provider="${e.id}">
      <h3>${e.name} ${Ji(e)} ${n?`<span class="badge mirror">${n}</span>`:""}</h3>
      <p class="hint">${(o==null?void 0:o.hint)||e.role||""}</p>
      ${Vi(e.id)}
      <p>读写态：${ku(e)}</p>
      ${e.auth==="connected"||e.expiresInHours!=null?`<p class="hint">过期：${zd(e.expiresInHours)}</p>`:""}
      ${e.error?`<p class="hint tier-error">${La(e.error,e.id)}</p>`:""}
      ${$u(e)}
      ${t}
    </article>`}function Su(){return`
    <div class="cloud-setup-wizard">
      <h3>云盘配种向导</h3>
      <ol class="cloud-wizard-steps">
        <li><strong>① 配夸克</strong> — P0 主库 · 粘贴 Cookie</li>
        <li><strong>② 配百度</strong> — P1 failover + daily mirror · Union OAuth</li>
        <li><strong>③ 配 OneDrive</strong>（可选 P2）— Microsoft Graph OAuth</li>
      </ol>
      <p class="hint">完成夸克后可先使用 mirror；百度/OneDrive 用于 failover 与冷备。</p>
    </div>`}function Eu(e){if(Od(e)){const t=e.tierProviders.find(a=>a.id==="quark"),n=t?`<div class="cloud-provider-grid">${Ht(t,"",_e.quark.rank)}</div>`:"";return`${Su()}${n}`}return Pu(e)}function Pu(e){const t=Cn("百度 Union OAuth",e.baiduOAuth,{portalLabel:"pan.baidu.com/union",portalUrl:Hi,steps:["Union 控制台创建应用 · client_id/secret → providers.local.yaml"],copyId:"baiduOauthCopy",openId:"baiduOauthOpen",submitId:"baiduOauthSubmit",codeClass:"baidu-oauth-code"}),n=Cn("OneDrive OAuth",e.onedriveOAuth,{portalLabel:"Azure Portal",portalUrl:tn,steps:["Azure 应用注册 · Files.ReadWrite + offline_access"],copyId:"onedriveOauthCopy",openId:"onedriveOauthOpen",submitId:"onedriveOauthSubmit",codeClass:"onedrive-oauth-code",scriptHint:"或运行 <code>scripts/onedrive_cloud_login.sh</code>（localhost:18766 回调）"}),a=e.tierProviders.map(r=>r.id==="baidu"?Ht(r,t,_e.baidu.rank):r.id==="onedrive"?Ht(r,n,_e.onedrive.rank):Ht(r,"",_e.quark.rank)),o=Cn("Google Drive OAuth（月备）",e.googleOAuth,{portalLabel:"Google Cloud Console",portalUrl:nn,steps:["启用 Drive API · OAuth 客户端 · 月备不参与 failover"],copyId:"googleOauthCopy",openId:"googleOauthOpen",submitId:"googleOauthSubmit",codeClass:"google-oauth-code",scriptHint:"或运行 <code>scripts/google_cloud_login.sh</code>（localhost:18767 回调）"}),i=`
    <article class="cloud-provider-card cloud-auth-archive ${Ta(vn(e.googleArchive))}" data-provider="google_drive">
      <h3>${e.googleArchive.name} ${Ji(e.googleArchive)} <span class="badge mirror">月备</span></h3>
      <p class="hint">monthly_only · 远端 <code>${e.googleArchive.remoteRoot||ji}</code></p>
      ${Vi("google_drive")}
      ${e.googleArchive.error?`<p class="hint tier-error">${La(e.googleArchive.error,"google_drive")}</p>`:""}
      ${o}
    </article>`,s=e.feimaoDeprecated?`<details class="feimao-deprecated-box cloud-details"><summary>飞猫 · 已停用 · 可选只读</summary><p class="hint">auth=${e.feimaoDeprecated.auth}</p></details>`:"";return`<div class="cloud-provider-grid">${[...a,i].join("")}</div>${s}`}function Iu(e){const t=e.googleDriveStatus==="ok"?`<span class="badge auth-connected">${e.googleDriveStatus}</span>`:e.googleDriveStatus==="failed"?`<span class="badge auth-needs-attention">${e.googleDriveStatus}</span>`:`<span class="badge auth-unconfigured">${e.googleDriveStatus}</span>`;return`
    <section class="cloud-monthly-summary">
      <h3>月备摘要 ${e.enabled?'<span class="badge live">已启用</span>':'<span class="badge mirror">已关闭</span>'}</h3>
      <p>上次运行：<strong>${e.lastRunAt?Le(e.lastRunAt):"—"}</strong> · ${t}${e.filesCopied!=null?` · ${e.filesCopied} 文件`:""}</p>
      <p class="hint">cron <code>${e.cron}</code> · Google 连接 <strong>${e.googleAuth}</strong></p>
      ${e.lastError?`<p class="hint tier-error">${La(e.lastError,"google_drive")}</p>`:'<p class="hint"><code>sync/monthly_archive_report.json</code></p>'}
    </section>`}function Kn(e){if(!e)return'<div class="cloud-tier-bar hint">Tier 状态加载中…</div>';const n=(e.chain.length?e.chain:["quark","baidu","onedrive"]).map(o=>{const i=e.clouds.find(l=>l.id===o),s=o==="quark"?"夸克":o==="baidu"?"百度":o==="onedrive"?"OneDrive":o,r=i!=null&&i.isPrimary?" cloud-tier-step-active":"",c=e.isFailover&&(i!=null&&i.isPrimary)?" cloud-tier-step-failover":"";return`<span class="cloud-tier-step${r}${c}">${s}</span>`}).join('<span class="cloud-tier-arrow">→</span>'),a=e.isFailover?`failover · <strong>${e.activeRole}</strong>${e.failoverSince?` · 自 ${Le(e.failoverSince)}`:""}`:`active_role <strong>${e.activeRole}</strong>`;return`
    <div class="cloud-tier-bar">
      <div class="cloud-tier-chain">${n}</div>
      <p class="hint cloud-tier-meta">${a}</p>
    </div>`}function Eo(e){return`
    <div class="remote-root-banner">
      <strong>remote_root 铁律</strong>
      <code>${e}</code>
      <span class="hint">manifest → <code>${e}/sync/manifest.json</code></span>
    </div>`}function Bu(e){const t=e.webhookEnabled?e.webhookUrl?'<span class="badge live">Webhook 已配置</span>':'<span class="badge warn">Webhook 缺 URL</span>':'<span class="badge mirror">Webhook 关闭</span>',n=e.macosEnabled?'<span class="badge live">macOS 通知开启</span>':'<span class="badge mirror">macOS 通知关闭</span>';return`
    <section class="cloud-tier-notify">
      <h3>Tier 通知通道</h3>
      <p class="hint">去重 ${e.dedupeHours} 小时 · 控制台 ${e.console?"开":"关"} · ${t} · ${n}</p>
      ${G({id:"tierNotifyEnabled",checked:e.enabled,chinese:"启用 Tier 变更通知",configKey:"notifications.tier.enabled"})}
      <label class="switch-label-row" style="align-items:center">
        <span class="switch-label-text">去重间隔（小时）<span class="switch-config-key">dedupe_hours</span></span>
        <input type="number" id="tierDedupeHours" min="0" step="1" value="${e.dedupeHours}" style="width:4rem;margin-left:auto"/>
      </label>
      ${G({id:"tierConsoleChannel",checked:e.console,chinese:"控制台内通知",configKey:"channels.console"})}
      ${G({id:"tierWebhookEnabled",checked:e.webhookEnabled,chinese:"Webhook 推送",configKey:"channels.webhook.enabled"})}
      <label style="display:block;margin-top:0.5rem">Webhook 地址
        <span class="switch-config-key">webhook.url</span>
        <input id="tierWebhookUrl" value="${zn(e.webhookUrl)}" placeholder="https://..." style="width:100%;max-width:28rem;margin-top:0.25rem"/>
      </label>
      <label style="display:block;margin-top:0.5rem">Webhook 密钥请求头
        <span class="switch-config-key">webhook.secret_header</span>
        <input id="tierWebhookSecret" value="${zn(e.webhookSecret)}" placeholder="X-Tier-Secret" style="width:100%;max-width:20rem;margin-top:0.25rem"/>
      </label>
      ${G({id:"tierMacosEnabled",checked:e.macosEnabled,chinese:"macOS 系统通知",configKey:"channels.macos.enabled"})}
      <div class="panel-tail-row">
        <button class="btn-primary" id="saveTierNotify">保存通知通道</button>
      </div>
    </section>`}function Cu(e){document.getElementById("saveTierNotify").onclick=async()=>{try{const t={notifications:{tier:{enabled:document.getElementById("tierNotifyEnabled").checked,dedupe_hours:Number(document.getElementById("tierDedupeHours").value||6),channels:{console:document.getElementById("tierConsoleChannel").checked,webhook:{enabled:document.getElementById("tierWebhookEnabled").checked,url:document.getElementById("tierWebhookUrl").value.trim()||null,secret_header:document.getElementById("tierWebhookSecret").value.trim()||null},macos:{enabled:document.getElementById("tierMacosEnabled").checked}}}}};await Dn(t),d("Tier 通知通道已保存（内存生效）","success"),e()}catch(t){d(String(t),"error")}}}function Po(){return`<div class="tier-notice tier-notice-warn" style="margin-bottom:0.75rem">
    Docker 模式需 <strong>API Token</strong> 才能加载 OAuth 与运维接口。
    请到 <a href="${z("settings")}">设置</a> 粘贴与 <code>NIANNIAN_API_TOKEN</code> 相同的 Token 并保存。
    下方云盘连接态来自公开的 <code>/health</code>。
  </div>`}function rt(e,t){e&&(e.innerHTML=t)}function Tu(e){return ie("三云 OAuth 连接、Tier 状态与运维",`
    <div class="cloud-page">
      <div id="cloudReconcile"></div>
      <div id="cloudTierBar">${Kn(e)}</div>
      <div id="cloudUploadOverview">${Kd()}</div>
      <div id="cloudRemoteRoot" class="hint">加载中…</div>
      <div id="cloudProviders" class="hint">加载中…</div>
      <div id="cloudMonthly" class="hint">加载中…</div>
      <div id="cloudTierNotify" class="hint">加载中…</div>
      <details class="core-card core-details cloud-details cloud-ops-details">
        <summary class="core-card-title">Tier 运维 · reconcile / pending / drain</summary>
        <div class="core-card-body">
          <div id="cloudTierOps" class="hint">加载中…</div>
        </div>
      </details>
      <p class="hint core-card-foot">Vault <code>cloud/credentials.json</code> · <code>GET /v1/cloud/status</code></p>
    </div>
  `)}async function Lu(e){const t=document.getElementById("cloudReconcile"),n=document.getElementById("cloudRemoteRoot"),a=document.getElementById("cloudProviders"),o=document.getElementById("cloudMonthly"),i=document.getElementById("cloudTierNotify"),s=document.getElementById("cloudTierOps");let r=null,c=null,l=null;const u=async()=>{if(!r)return;const m=await ko(r,c);s.innerHTML=$o(m),So(s,()=>void u()),j({tierView:Ut(r,c),tierAlerts:Zt(r)}),e()},p=async(m=!1)=>{var g,v,k;try{const E=!C().apiToken.trim(),[I,_,P,f]=await Promise.all([Pt().catch(()=>null),mn().catch(()=>null),ht().catch(T=>T instanceof Y&&T.status===401?null:Promise.reject(T)),ru().catch(()=>null)]);r=I,c=_,l=P;const w=String(((g=r==null?void 0:r.runtime)==null?void 0:g.mode)||"").includes("docker"),S=Ut(r,c);document.getElementById("cloudTierBar").innerHTML=Kn(S),t.innerHTML=Fn(Vn(r,c),(v=r==null?void 0:r.reconcile)==null?void 0:v.acknowledged),E&&w&&(n.innerHTML=Po());const B=Vd(r,f,l),L=Ut(r,c);document.getElementById("cloudTierBar").innerHTML=Kn(L),t.innerHTML=Fn(Vn(r,c),(k=r==null?void 0:r.reconcile)==null?void 0:k.acknowledged),!E||!w?n.innerHTML=Eo(B.remoteRoot):B.remoteRoot&&(n.innerHTML=Po()+Eo(B.remoteRoot)),a.innerHTML=Eu(B),o.innerHTML=Iu(B.monthlyArchive),_u(a,()=>p(!1),{baiduAuthorizeUrl:B.baiduOAuth.authorize_url,onedriveAuthorizeUrl:B.onedriveOAuth.authorize_url,googleAuthorizeUrl:B.googleOAuth.authorize_url,onTokenLogin:async(T,H)=>{await cu(T,H),d(`${T} 凭证已写入 Vault`,"success")},onBaiduOAuth:async T=>{await lu(T),d("百度 OAuth 已写入 Vault","success")},onOnedriveOAuth:async T=>{await _o("onedrive",T),d("OneDrive OAuth 已写入 Vault","success")},onGoogleOAuth:async T=>{await _o("google_drive",T),d("Google Drive OAuth 已写入 Vault","success")}}),l?(i.innerHTML=Bu(Fd(l)),Cu(()=>void p(!1))):E&&w?i.innerHTML=`<p class="hint">通知渠道配置需 Token · <a href="${z("settings")}">去设置</a></p>`:i.innerHTML='<p class="hint">（未加载 config）</p>';const D=await ko(r,c);s.innerHTML=$o(D),So(s,()=>void u()),m&&(j({tierView:L,tierAlerts:Zt(r)}),e())}catch(E){const I=`<p class="hint tier-error">${Bt(E)}</p>`;rt(a,I),rt(n,I),rt(o,I),rt(i,I),rt(s,I)}};await p(!1)}async function Au(){return y("/v1/memory/spaces")}async function xu(e){return y("/v1/memory/spaces",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e})})}async function Mu(e){return y(`/v1/memory/spaces/${encodeURIComponent(e)}`,{method:"DELETE"})}async function Ou(){return y("/v1/vault/enhancements")}async function Ru(e){return y(`/v1/vault/tree?namespace=${encodeURIComponent(e)}`)}async function Du(e,t){const n=t.includes("/")?t.split("/").slice(1).join("/"):t;return y(`/v1/vault/pages/${e}/${n}`)}async function Nu(e){return y(`/v1/vault/pending?namespace=${encodeURIComponent(e)}`)}async function Uu(e){return y(`/v1/vault/taxonomy?namespace=${encodeURIComponent(e)}`)}async function ju(e){const t=await se("/v1/vault/taxonomy/confirm",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(!t.ok){const n=await t.text();throw new Error(n)}return t.json()}async function Aa(){return y("/v1/skills")}async function Hu(e,t){return y(`/v1/skills/${encodeURIComponent(e)}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({enabled:t})})}async function Io(e){return y(`/v1/skills/${encodeURIComponent(e)}`)}async function qu(e,t){return y(`/v1/skills/${encodeURIComponent(e)}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({content:t})})}async function Vu(e){const t=await se(`/v1/skills/custom/${encodeURIComponent(e)}`,{method:"DELETE"});if(!t.ok)throw new Y(t.status,await t.text())}async function Fu(e){const t=new FormData;return t.append("bundle",e),y("/v1/skills/import",{method:"POST",body:t})}async function zu(){const e=await se("/v1/skills/export/bundle");if(!e.ok)throw new Y(e.status,await e.text());return e.blob()}async function Wu(e,t){const n=new URLSearchParams;n.set("dry_run",e?"true":"false"),t&&n.set("project",t);const a=C().apiBase.replace(/\/$/,""),o=await fetch(`${a}/v1/skills/distill?${n}`,{method:"POST",headers:tt()});if(!o.ok)throw new Error(`distill ${o.status}`);return o.json()}async function Ku(e){return N("/v1/auth/preflight",{username:e})}async function Ju(e){return N("/v1/auth/login",e)}async function Gu(e){return y("/v1/client/pinn-sync/handoff",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)})}async function Gi(){await y("/v1/client/pinn-sync/handoff",{method:"DELETE"})}async function Yu(){return y("/v1/client/pinn-sync/status")}async function Xu(){return y("/v1/client/pinn-sync/cloud-status")}async function Qu(){return y("/v1/client/pinn-sync/settings")}async function Bo(e){return y("/v1/client/pinn-sync/settings",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)})}async function Zu(){return y("/v1/client/pinn-sync/sync-now",{method:"POST"})}async function ep(){return y("/v1/client/pinn-sync/oauth/google",{method:"POST"})}function tp(e){const t=e.replace(/-----BEGIN PUBLIC KEY-----/g,"").replace(/-----END PUBLIC KEY-----/g,"").replace(/\s/g,""),n=atob(t),a=new Uint8Array(n.length);for(let o=0;o<n.length;o+=1)a[o]=n.charCodeAt(o);return a.buffer}async function np(e){const t=tp(e.trim());return crypto.subtle.importKey("spki",t,{name:"RSA-OAEP",hash:"SHA-256"},!1,["encrypt"])}async function ap(e,t){const n=await np(t),a=new TextEncoder().encode(e),o=await crypto.subtle.encrypt({name:"RSA-OAEP"},n,a),i=new Uint8Array(o);let s="";for(let r=0;r<i.length;r+=1)s+=String.fromCharCode(i[r]);return btoa(s)}function op(){const e="client";return e==="client"||e==="docker"?e:null}function Yi(){const e=C();return e.sessionToken?`${e.userRole} · ${e.sessionToken.slice(0,8)}…`:"未登录"}function ip(){const e=C();return M("登录",`<p class="hint">tenant 无密码 · owner（pinn）需 API Token · client 走 RSA 密文</p>
      <label class="core-field"><span>用户名</span>
        <div class="core-inline-actions">
          <input id="loginUsername" placeholder="alice 或 pinn" autocomplete="username" />
          <button class="btn-primary" id="loginSubmit">登录</button>
          <button class="secondary" id="loginLogout" ${e.sessionToken?"":"disabled"}>退出</button>
        </div>
      </label>
      <p id="loginStatus" class="hint">Session：${Yi()}</p>`)}function sp(e){return new Promise(t=>{const n=document.createElement("div");n.className="modal-overlay",n.innerHTML=`
      <div class="modal-panel" role="dialog" aria-modal="true" aria-labelledby="ownerLoginTitle">
        <h3 id="ownerLoginTitle">owner 登录 · ${e}</h3>
        <p class="hint">输入 NAS API Token（与 NIANNIAN_API_TOKEN 相同），将以 RSA 加密上传。</p>
        <label>API Token</label>
        <input id="ownerApiToken" type="password" autocomplete="current-password" />
        <div class="row" style="margin-top:0.75rem">
          <button class="btn-primary" id="ownerLoginConfirm">确认登录</button>
          <button class="secondary" id="ownerLoginCancel">取消</button>
        </div>
      </div>`;const a=i=>{n.remove(),t(i)};n.querySelector("#ownerLoginCancel").addEventListener("click",()=>a(null)),n.addEventListener("click",i=>{i.target===n&&a(null)});const o=()=>{const i=n.querySelector("#ownerApiToken").value.trim();if(!i){d("请输入 API Token","error");return}a(i)};n.querySelector("#ownerLoginConfirm").addEventListener("click",o),n.querySelector("#ownerApiToken").addEventListener("keydown",i=>{i.key==="Enter"&&o(),i.key==="Escape"&&a(null)}),document.body.appendChild(n),n.querySelector("#ownerApiToken").focus()})}async function rp(e,t,n,a){const o=await ap(t,n);return{body:{username:e,password_encrypted:o,key_id:a},cacheBearer:t}}async function cp(e,t){const n=mi(e.capabilities,{forcedProfile:op(),legacyIntranet:!1});if(j({sessionToken:e.token,sessionId:e.token,userRole:e.role,capabilities:e.capabilities,consoleProfile:n,apiToken:t??""}),pi(),e.role==="owner"&&e.username.toLowerCase()==="pinn"&&t)try{await Gu({control_plane:un(),bearer_token:t,username:e.username,role:e.role,session_token:e.token})}catch{}else if(e.role!=="owner"||e.username.toLowerCase()!=="pinn")try{await Gi()}catch{}}async function lp(e){const t=e.trim();if(!t)throw new Error("请输入用户名");const n=await Ku(t);let a,o;if(n.requires_password){const s=await sp(n.username);if(!s)throw new Error("已取消登录");if(!n.public_key_pem||!n.key_id)throw new Error("preflight 缺少 RSA 公钥");const r=await rp(n.username,s,n.public_key_pem,n.key_id);o=r.body,a=r.cacheBearer}else o={username:n.username};const i=await Ju(o);return await cp(i,a),i}function dp(){ka(),Gi().catch(()=>{}),j({sessionToken:null,sessionId:null,capabilities:[],apiToken:"",userRole:"tenant"})}function up(e){var t,n;if(e instanceof Y){try{const a=JSON.parse(e.body),o=((t=a.detail)==null?void 0:t.message)||((n=a.detail)==null?void 0:n.code);if(o)return o}catch{}return e.body.slice(0,200)}return e instanceof Error?e.message:String(e)}async function pp(e){const t=document.getElementById("loginStatus"),n=()=>{t&&(t.textContent=`Session：${Yi()}`)};document.getElementById("loginSubmit").onclick=async()=>{const a=document.getElementById("loginSubmit"),o=document.getElementById("loginUsername").value;a.disabled=!0;try{const i=await lp(o);d(`已登录：${i.username}（${i.role}）`,"success"),n(),document.getElementById("loginLogout").disabled=!1,e()}catch(i){d(up(i),"error")}finally{a.disabled=!1}},document.getElementById("loginLogout").onclick=()=>{dp(),n(),document.getElementById("loginLogout").disabled=!0,d("已退出登录","info"),e()}}function mp(e){return e.consoleProfile==="docker"}function fp(e){return e.consoleProfile==="docker"&&e.userRole==="owner"}function hp(e){return e.consoleProfile==="docker"}function vp(e){return e.consoleProfile==="client"&&e.userRole==="owner"&&!!e.sessionToken}function gp(e){return vp(e)?M("云同步（pinn）",`<p class="hint">pinn 登录后自动续期本地 Vault 并推送到 SMB。同步后请在极空间手动重启 api/worker。</p>
      <label class="core-field"><span>SMB 挂载路径</span>
        <div class="core-inline-actions">
          <input id="pinnSyncMount" placeholder="/Volumes/.../niannian" />
          <button class="btn-primary core-btn-fixed" id="savePinnSyncMount">保存</button>
        </div>
      </label>
      <label class="core-field"><span>providers.local.yaml 本地路径</span>
        <div class="core-inline-actions">
          <input id="pinnSyncProviders" placeholder="~/NianNian/config/providers.local.yaml" />
          <button class="secondary core-btn-fixed" id="savePinnSyncProviders">保存</button>
        </div>
      </label>
      <div class="row" style="margin-top:0.5rem">
        <button class="btn-primary" id="pinnSyncNow" type="button">立即同步</button>
        <button class="secondary" id="pinnGoogleOAuth" type="button">Google 授权</button>
      </div>
      <pre id="pinnSyncOut" class="config hint">加载中…</pre>`):""}function yp(){const e=C(),t=fp(e),n=hp(e),a=mp(e),o=`
      ${t?`<p class="hint">profile：<strong>${e.consoleProfile}</strong> · client 时 Chat/MCP/VP 走本机 Agent，登录/同步走 NAS</p>`:""}
      <label class="core-field"><span>本机 Agent（agent_local）</span>
        <div class="core-inline-actions">
          <input id="agentLocalBase" value="${e.agentLocalBase}" placeholder="http://127.0.0.1:18765" />
          <button class="btn-primary core-btn-fixed" id="saveAgentLocal">保存</button>
        </div>
      </label>
      <label class="core-field"><span>NAS 控制面（control_plane）</span>
        <div class="core-inline-actions">
          <input id="controlPlaneBase" value="${e.controlPlaneBase||e.apiBase}" placeholder="https://niannian.example.com" />
          <button class="btn-primary core-btn-fixed" id="saveControlPlane">保存</button>
        </div>
      </label>
      ${e.consoleProfile==="client"?'<p class="hint">外网请填公网 HTTPS 域名（nginx / 内网穿透暴露 <code>/v1/auth</code> → Docker 18765）。内网仍可用 <code>http://NAS_IP:18765</code>。</p>':""}
      ${t?`<label class="core-field"><span>API 基址（Docker 单 URL 兼容）</span>
        <div class="core-inline-actions">
          <input id="apiBase" value="${e.apiBase}" />
          <button class="btn-primary core-btn-fixed" id="saveApi">保存</button>
          <button class="secondary core-btn-fixed" id="testHealth">测试</button>
        </div></label>`:""}
      ${n?`<label class="core-field"><span>API Token（Docker 必填）</span>
        <div class="core-inline-actions">
          <input id="apiToken" type="password" value="${e.apiToken}" placeholder="与 NIANNIAN_API_TOKEN 相同" />
          <button class="btn-primary core-btn-fixed" id="saveToken">保存 Token</button>
          <button class="secondary core-btn-fixed" id="testToken">测试 Token</button>
        </div></label>
        <p id="tokenTestHint" class="hint"></p>`:""}
      <p class="hint">状态：${e.health} · 运行时：${e.runtimeMode}${t?` · API：<code>${e.apiBase}</code>`:""}</p>
      ${t?`<div class="settings-connection-foot core-switch-row panel-tail-row panel-tail-row--inline">
        <button class="secondary" id="presetMac">切换mac</button>
        <button class="secondary" id="presetDocker">切换docker</button>
      </div>`:""}`,i=a?M("身份（Core 页）",`<label class="core-field"><span>用户角色 X-NianNian-Role</span>
        <div class="core-inline-actions">
          <select id="userRole">
            <option value="owner" ${e.userRole==="owner"?"selected":""}>owner（主人）</option>
            <option value="authorized_corrector" ${e.userRole==="authorized_corrector"?"selected":""}>authorized_corrector</option>
            <option value="guest" ${e.userRole==="guest"?"selected":""}>guest</option>
          </select>
          <button class="btn-primary core-btn-fixed" id="saveUserRole">保存角色</button>
        </div>
      </label>
      <p class="hint">guest 不可见「核心」「云盘」「Skill」「用户管理」等运维导航 · Core API 403</p>`):"";return ie("控制台连接、身份与运行配置",`
    ${ip()}
    ${M("用户头像",`<div class="core-switch-row" style="align-items:center">
        <div id="userAvatarPreview" class="settings-avatar-preview" aria-hidden="true" style="width:64px;height:64px;border-radius:50%;background:#243044;color:var(--text);display:flex;align-items:center;justify-content:center;font-weight:600;overflow:hidden;flex:0 0 64px">我</div>
        <div>
          <label class="secondary" style="display:inline-block;cursor:pointer;padding:0.45rem 0.75rem;border-radius:6px">
            上传头像
            <input id="userAvatarFile" type="file" accept="image/png,image/jpeg,image/webp" hidden />
          </label>
          <button class="secondary" id="removeUserAvatar" type="button" style="margin-left:0.5rem" disabled>移除</button>
        </div>
      </div>
      <p id="userAvatarHint" class="hint">未设置时将显示「我」</p>`)}
    ${M("连接",o)}
    ${gp(e)}
    ${i}
    ${M("记忆空间",`<p class="hint">${hr}</p>
      <label class="core-field"><span>当前浏览空间（记忆页 / 提炼）</span>
        <div class="core-inline-actions">
          <select id="memorySpace">${Go([],e.memorySpace)}</select>
          <button class="btn-primary core-btn-fixed" id="saveMemorySpace">保存</button>
        </div>
      </label>
      <p class="hint">保存在本机浏览器 localStorage（键 <code>niannian.memorySpace</code>），仅影响记忆页浏览与 Skill 提炼默认空间。</p>
      <label class="core-field"><span>新增 / 管理记忆空间</span>
        <div class="core-inline-actions">
          <input id="memorySpaceNewName" placeholder="小写英文名，如 novelstudio" />
          <select id="memorySpaceManageAction" class="memory-space-action-select">
            <option value="create">新增</option>
            <option value="delete">删除</option>
          </select>
          <button class="btn-primary core-btn-fixed" id="applyMemorySpaceManage">保存</button>
        </div>
      </label>
      <p class="hint">写入 NAS <code>persist/memory_spaces.json</code>；删除仅从注册表移除，不删已有碎片。内置空间（global / auto / normal / work / companion / education 学习）不可删。</p>`)}
    ${M("LLM 脑区",`<button class="secondary" id="loadLlm">读取配置</button>
      <div id="llmTable" class="core-table-wrap hint">（点击读取）</div>`)}
    ${M("资料库增强（v0.10）",`<p id="vaultEnhHint" class="hint">加载中…</p>
      <div class="core-field-grid">
        <label class="core-field"><span>FTS 全文搜索</span>
          <select id="vaultFtsMode" class="panel-field-select">
            <option value="auto">自动（超过阈值启用）</option>
            <option value="on">开</option>
            <option value="off">关（不自动开）</option>
          </select>
        </label>
        <label class="core-field"><span>Vault 增量云同步</span>
          <select id="vaultSyncMode" class="panel-field-select">
            <option value="auto">自动（超过阈值启用）</option>
            <option value="on">开</option>
            <option value="off">关（不自动开）</option>
          </select>
        </label>
      </div>
      <div class="panel-tail-row panel-tail-row--inline">
        <p class="hint">默认关；「自动」在资料篇数超过阈值后启用。「关」后即使资料很多也不会自动打开。</p>
        <button class="btn-primary" id="saveVaultEnh">保存资料库增强</button>
      </div>`)}
    ${M("高级",`<p class="hint">运维 / 开发向开关，写入服务端运行配置（内存生效，API 重启后恢复 default.yaml）。</p>
      <div class="console-switch-list">
        ${G({id:"autoIngest",chinese:"每轮对话后自动尝试写入长期记忆（默认关，推荐用手动「记一下」）",configKey:"chat.auto_ingest"})}
        ${G({id:"showTraceNames",chinese:"推理 Trace 显示内部模块名（调试）",configKey:"trace.show_internal_names"})}
        ${G({id:"deferDream",chinese:"Mac 本地将 dream 提炼交给 Docker worker",configKey:"runtime.mac.defer_dream_to_docker"})}
      </div>
      <div class="panel-tail-row">
        <button class="btn-primary" id="saveAdvanced">保存高级项</button>
      </div>`)}
  `)}async function bp(e,t){var k,E,I,_,P,f,w,S,B,L,D,T,H,te,ce,X,Z;await pp(e);const n=document.getElementById("tokenTestHint"),a=async()=>{if(!n)return!1;n.textContent="测试中…",n.className="hint";try{return await Aa(),n.textContent="Token 有效",n.className="hint ok",!0}catch(b){return n.textContent=`Token 无效：${String(b)}`,n.className="hint err",!1}};(k=document.getElementById("saveApi"))==null||k.addEventListener("click",()=>{const b=document.getElementById("apiBase").value.trim();j({apiBase:b,sessionId:null}),d(`API 基址已保存：${b}`,"success"),e()}),document.getElementById("saveAgentLocal").onclick=()=>{const b=document.getElementById("agentLocalBase").value.trim();j({agentLocalBase:b,sessionId:null}),d(`本机 Agent 已保存：${b}`,"success"),e()},document.getElementById("saveControlPlane").onclick=()=>{const b=document.getElementById("controlPlaneBase").value.trim();j({controlPlaneBase:b}),d(`NAS 控制面已保存：${b}`,"success"),e()},(E=document.getElementById("testHealth"))==null||E.addEventListener("click",()=>t()),(I=document.getElementById("presetMac"))==null||I.addEventListener("click",()=>{cr(),j({agentLocalBase:"http://127.0.0.1:18765",sessionId:null}),d("已切换为本机 Mac 地址","info"),e()}),(_=document.getElementById("presetDocker"))==null||_.addEventListener("click",()=>{j({apiBase:window.location.origin,sessionId:null}),d("已切换为当前页面地址","info"),e()}),(P=document.getElementById("saveToken"))==null||P.addEventListener("click",async()=>{j({apiToken:document.getElementById("apiToken").value.trim()}),await a()?d("Token 已保存且验证通过","success"):d("Token 已保存，但验证失败","error"),t()}),(f=document.getElementById("testToken"))==null||f.addEventListener("click",async()=>{const b=await a();d(b?"Token 验证通过":"Token 验证失败",b?"success":"error")}),document.getElementById("saveMemorySpace").onclick=()=>{const b=document.getElementById("memorySpace").value.trim();j({memorySpace:b,sessionId:null}),d(`记忆空间已保存：${b}`,"success")};const o=b=>{const A=document.getElementById("memorySpace");A&&(A.innerHTML=Go(b,C().memorySpace))},i=async()=>{try{const b=await Au();o(b.spaces||[])}catch(b){d(`无法读取记忆空间列表：${String(b)}`,"error")}};document.getElementById("applyMemorySpaceManage").onclick=async()=>{const b=document.getElementById("memorySpaceNewName").value.trim(),A=document.getElementById("memorySpaceManageAction").value;if(!b){d("请输入记忆空间名称","error");return}if(A==="delete"&&fr(b)){d(`内置记忆空间不可删除：${b}`,"error");return}try{A==="delete"?(await Mu(b),C().memorySpace===b&&(j({memorySpace:"work",sessionId:null}),o([])),d(`已删除记忆空间：${b}`,"success")):(await xu(b),d(`已新增记忆空间：${b}`,"success")),document.getElementById("memorySpaceNewName").value="",await i()}catch(O){d(String(O),"error")}},i(),document.getElementById("loadLlm").onclick=async()=>{const b=document.getElementById("llmTable");try{const O=(await ht()).api||{},J=Object.entries(O).map(([le,ne])=>`<tr><td>${le}</td><td>${ne.primary??"—"}</td><td>${ne.fallback??"—"}</td></tr>`).join("");b.innerHTML=`<table><thead><tr><th>脑区</th><th>primary</th><th>fallback</th></tr></thead><tbody>${J}</tbody></table>`,await Zr(),d("LLM 配置已读取","info")}catch(A){b.textContent=String(A),d(`读取失败：${String(A)}`,"error")}},document.getElementById("saveVaultEnh").onclick=async()=>{try{const b=document.getElementById("vaultFtsMode").value,A=document.getElementById("vaultSyncMode").value;await Dn({vault:{enhancements:{fts:{mode:b},incremental_sync:{mode:A}}}}),d("资料库增强已保存（内存生效，重启后恢复 yaml）","success"),await s()}catch(b){d(`保存失败：${String(b)}`,"error")}};const s=async()=>{var A,O,J;const b=document.getElementById("vaultEnhHint");try{const[le,ne]=await Promise.all([ht(),Ou()]),xe=((A=le.vault)==null?void 0:A.enhancements)||{},$e=xe.fts||{},Se=xe.incremental_sync||{};document.getElementById("vaultFtsMode").value=$e.mode||"auto",document.getElementById("vaultSyncMode").value=Se.mode||"auto";const Ee=Number(ne.entry_count??0),ze=Number(ne.auto_enable_above??500),nt=ne.features||{},h=(O=nt.fts)!=null&&O.active?"已启用":"未启用",$=(J=nt.incremental_sync)!=null&&J.active?"已启用":"未启用";b.textContent=`当前 ${Ee} 篇 · 自动阈值 ${ze} · FTS ${h} · 增量同步 ${$}`}catch(le){b.textContent=`无法读取：${String(le)}`}};document.getElementById("saveAdvanced").onclick=async()=>{try{await Dn({chat:{auto_ingest:document.getElementById("autoIngest").checked},trace:{show_internal_names:document.getElementById("showTraceNames").checked},runtime:{mac:{defer_dream_to_docker:document.getElementById("deferDream").checked}}}),d("高级项已保存（内存生效，重启后恢复 yaml）","success")}catch(b){d(`保存失败：${String(b)}`,"error")}},(w=document.getElementById("saveUserRole"))==null||w.addEventListener("click",()=>{const b=document.getElementById("userRole").value;j({userRole:b}),d(`角色已保存：${b} · 侧栏已按权限刷新`,"success"),e()});try{const b=await ht();document.getElementById("autoIngest").checked=!!((S=b.chat)!=null&&S.auto_ingest),document.getElementById("showTraceNames").checked=!!((B=b.trace)!=null&&B.show_internal_names),document.getElementById("deferDream").checked=!!((D=(L=b.runtime)==null?void 0:L.mac)!=null&&D.defer_dream_to_docker)}catch{}await s();let r=null;const c=document.getElementById("userAvatarPreview"),l=document.getElementById("userAvatarHint"),u=document.getElementById("removeUserAvatar"),p=document.getElementById("userAvatarFile"),m=(b,A)=>{Sa(r),r=A,b&&A?(c.innerHTML=`<img src="${A}" alt="用户头像" style="width:100%;height:100%;object-fit:cover;display:block" />`,l.textContent="已设置 · Chat 将显示你的头像",u.disabled=!1):(c.textContent="我",l.textContent="未设置时将显示「我」",u.disabled=!0)},g=async()=>{try{Sc();const b=await _i();m(!!b,b)}catch(b){l.textContent=`无法读取头像：${String(b)}`,m(!1,null)}};p.onchange=async()=>{var A;const b=(A=p.files)==null?void 0:A[0];if(p.value="",!!b){if(b.size>512*1024){d("头像不能超过 512 KiB","error");return}try{await _c(b),d("头像已上传","success"),await g()}catch(O){d(`上传失败：${String(O)}`,"error")}}},u.onclick=async()=>{try{await kc(),d("头像已移除","success"),await g()}catch(b){d(`移除失败：${String(b)}`,"error")}},await g();const v=async()=>{var A,O,J,le;const b=document.getElementById("pinnSyncOut");if(b)try{const[ne,xe,$e]=await Promise.all([Yu(),Xu(),Qu()]),Se=document.getElementById("pinnSyncMount"),Ee=document.getElementById("pinnSyncProviders");if(Se&&!Se.dataset.touched&&(Se.value=String(((A=$e.smb)==null?void 0:A.mount_path)||"")),Ee&&!Ee.dataset.touched){const ze=((J=(O=$e.sync)==null?void 0:O.config)==null?void 0:J.files)||[];Ee.value=String(((le=ze[0])==null?void 0:le.local)||"")}b.textContent=JSON.stringify({session:ne,cloud:xe,settings:$e},null,2)}catch(ne){b.textContent=String(ne)}};(T=document.getElementById("savePinnSyncMount"))==null||T.addEventListener("click",async()=>{const b=document.getElementById("pinnSyncMount").value.trim();await Bo({smb_mount_path:b}),d("SMB 路径已保存","success"),await v()}),(H=document.getElementById("savePinnSyncProviders"))==null||H.addEventListener("click",async()=>{const b=document.getElementById("pinnSyncProviders").value.trim();await Bo({providers_local:b}),d("providers.local 路径已保存","success"),await v()}),(te=document.getElementById("pinnSyncMount"))==null||te.addEventListener("input",b=>{b.target.dataset.touched="1"}),(ce=document.getElementById("pinnSyncProviders"))==null||ce.addEventListener("input",b=>{b.target.dataset.touched="1"}),(X=document.getElementById("pinnSyncNow"))==null||X.addEventListener("click",async()=>{try{const b=await Zu();d(b.ok?"同步完成":"同步跳过或失败",b.ok?"success":"error"),await v()}catch(b){d(String(b),"error")}}),(Z=document.getElementById("pinnGoogleOAuth"))==null||Z.addEventListener("click",async()=>{var b;try{const A=await ep();A.auth_url&&window.open(A.auth_url,"_blank","noopener,noreferrer"),(b=A.hints)!=null&&b.vpn&&d(A.hints.vpn,"info"),await v()}catch(A){d(String(A),"error")}}),await v()}async function wp(){return y("/v1/llm")}async function _p(e,t){const n=await se(`/v1/llm/providers/${encodeURIComponent(e)}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)}),a=await n.text();if(!n.ok)throw new Error(`HTTP ${n.status}: ${a.slice(0,300)}`)}async function kp(e,t){return(await N(`/v1/llm/providers/${encodeURIComponent(e)}/models`,{model_id:t})).models??[]}async function $p(e,t){const n=await se(`/v1/llm/providers/${encodeURIComponent(e)}/models/${encodeURIComponent(t)}`,{method:"DELETE"}),a=await n.text();if(!n.ok)throw new Error(`HTTP ${n.status}: ${a.slice(0,300)}`);return(a?JSON.parse(a):{}).models??[]}async function Sp(e){const t=await se("/v1/llm/models",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)}),n=await t.text();if(!t.ok)throw new Error(`HTTP ${t.status}: ${n.slice(0,300)}`);const a=n?JSON.parse(n):{};return a.models??a}async function Ep(e){return(await N("/v1/llm/providers/custom",e)).provider}async function Pp(e){const t=await se(`/v1/llm/providers/custom/${encodeURIComponent(e)}`,{method:"DELETE"}),n=await t.text();if(!t.ok)throw new Error(`HTTP ${t.status}: ${n.slice(0,300)}`)}const Co=["siliconflow","deepseek","custom"];function Xi(e={}){if(e.consoleProfile!==void 0&&e.capabilities!==void 0)return{consoleProfile:e.consoleProfile,capabilities:e.capabilities};const{getState:t}=require("../state"),n=t();return{consoleProfile:e.consoleProfile??n.consoleProfile,capabilities:e.capabilities??n.capabilities}}function Ip(e){const t=e.find(a=>a.startsWith("llm.platforms:"));if(!t)return new Set(Co);const n=t.slice(14).split(",").map(a=>a.trim()).filter(Boolean);return new Set(n.length?n:Co)}function At(e={}){const{consoleProfile:t,capabilities:n}=Xi(e);return t==="client"?!0:be(n,"llm.brain_primary_only")}function Bp(e,t={}){if(!At(t))return e;const{capabilities:n}=Xi(t),a=Ip(n);return e.filter(o=>a.has(o.id))}function Cp(e={}){return At(e)?["primary"]:["primary","module_fallback","global_fallback"]}const Tp={primary:"主模型",module_fallback:"模块降级",global_fallback:"全局降级"},Lp=["siliconflow","deepseek","custom","zhipu","volcengine","dashscope","minimax","baichuan","moonshot","qianfan"];function Ap(e){const t=new Map(Lp.map((a,o)=>[a,o])),n=e.map((a,o)=>({id:a,i:o}));return n.sort((a,o)=>{const i=t.get(a.id),s=t.get(o.id);return i!==void 0&&s!==void 0?i-s:i!==void 0?-1:s!==void 0||a.id==="qianfan"?1:o.id==="qianfan"?-1:a.i-o.i}),n.map(a=>a.id)}function xa(e){const t=(e.catalog??[]).map(i=>({id:String(i.id),label:String(i.label??i.id)})),n=Bp(t),a=Ap(n.map(i=>i.id)),o=new Map(n.map(i=>[i.id,i]));return a.map(i=>o.get(i)).filter(Boolean)}const xp={siliconflow:"Qwen/Qwen3-8B",zhipu:"glm-4.7-flash",deepseek:"deepseek-chat",custom:"your-model-id",moonshot:"moonshot-v1-8k",dashscope:"qwen-plus",minimax:"abab6.5s-chat",baichuan:"Baichuan4-Turbo",volcengine:"doubao-seed-2-1-turbo-260628",qianfan:"ernie-4.0-8k",spark:"xopqwen36v35b"};let gt="siliconflow",Qi=null,ct=null,Tn=!1;function ee(e){return e.replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;")}function Ie(e){fe(e)}async function We(e){if(!Tn){Tn=!0;try{await e()}finally{Tn=!1}}}function Mp(e){const t=xa(e).map(o=>e.providers[o.id]).filter(o=>!!(o&&!o.deletable));if(!t.length)return'<p class="hint">无厂商目录</p>';const n=t.flatMap(o=>o.models||[]);return`
    <datalist id="all-llm-models">${[...new Set(n)].map(o=>`<option value="${o}"></option>`).join("")}</datalist>
    <div class="llm-provider-grid-shell">
    <div class="llm-provider-grid">
      ${t.map(o=>`
        <div class="panel llm-provider-card" data-provider="${o.id}">
          <div class="row" style="justify-content:space-between;align-items:center">
            <h3 style="margin:0">${o.label}</h3>
            <span class="hint ${o.configured?"ok":""}">${o.configured?"已配置":"未配置"}</span>
          </div>
          <p class="hint"><code>${o.id}</code>${o.exclusive?" · 独占锁":""}</p>
          <label>Base URL</label>
          <input class="provider-base" data-id="${o.id}" value="${o.base_url}" />
          <label style="margin-top:0.5rem;display:block">API Key</label>
          <input class="provider-key" data-id="${o.id}" type="password" placeholder="${o.api_key_masked||"粘贴 Key"}" />
          <div class="panel-tail-row panel-tail-row--inline">
            <p class="hint">已登记 ${(o.model_entries||o.models||[]).length} 个模型</p>
            <div class="panel-tail-actions llm-provider-footer">
            ${o.docs_url?`<a class="hint" href="${o.docs_url}" target="_blank" rel="noopener">控制台 ↗</a>`:""}
            <button type="button" class="btn-primary provider-save" data-id="${ee(o.id)}">保存 Key</button>
            </div>
          </div>
        </div>`).join("")}
    </div>
    </div>`}function Ma(e){var t;return(t=e.model_entries)!=null&&t.length?e.model_entries:(e.models||[]).map(n=>({id:n,free:e.id==="siliconflow"||e.id==="zhipu",builtin:!0}))}function Op(e,t){const n=t.free?'<span class="hint ok">免费</span>':"";return`<div class="llm-model-row" data-provider="${ee(e)}" data-model="${ee(t.id)}">
    <code>${t.id}</code> ${n}
    <button type="button" class="secondary model-delete" data-provider="${ee(e)}" data-model="${ee(t.id)}">删除</button>
  </div>`}function Zi(e,t){const n=e.providers[t];if(!n)return'<p class="hint">未找到平台</p>';const a=Ma(n),o=a.map(r=>Op(t,r)).join("")||'<p class="hint">（暂无模型，可在下方添加）</p>',i=xp[t]||"model_id",s=n.configured?'<span class="hint ok llm-platform-detail-status">（已配置）</span>':'<span class="hint llm-platform-detail-status">（未配置）</span>';return`
    <div class="llm-platform-detail-inner" data-provider="${t}">
      <div class="llm-platform-detail-header">
        <h3 class="llm-platform-detail-title">${n.label}${s} <span class="hint">· ${t}</span></h3>
      </div>
      <p class="hint">${a.length} 个模型 · 供脑区下拉联想</p>
      <div class="llm-model-list">${o}</div>
      <div class="row" style="margin-top:0.75rem">
        <input class="model-add-input" data-provider="${t}" placeholder="model_id，如 ${i}" list="all-llm-models" />
        <button type="button" class="secondary model-add-btn" data-provider="${ee(t)}">添加</button>
      </div>
    </div>`}function Rp(e){var o;const t=xa(e),n=e.providers[gt]?gt:((o=t[0])==null?void 0:o.id)||"siliconflow";return gt=n,`
    <div class="llm-model-master">
      <div class="llm-platform-nav-shell">
        <div class="llm-platform-nav-list" role="navigation" aria-label="选择平台">${t.map(i=>{const s=e.providers[i.id];if(!s)return"";const r=Ma(s).length,c=s.configured?'<span class="hint ok llm-platform-nav-status">（已配置）</span>':"";return`<button type="button" class="llm-platform-nav${i.id===n?" active":""}" data-platform="${ee(i.id)}">
        <span class="llm-platform-nav-title">${s.label}${c}</span>
        <span class="hint llm-platform-nav-meta">${i.id} · ${r} 个</span>
      </button>`}).join("")}</div>
      </div>
      <div class="llm-platform-detail" id="llmPlatformDetail">${Zi(e,n)}</div>
    </div>`}function yt(e,t){const n=[];for(const a of xa(e)){const o=e.providers[a.id];if(o!=null&&o.configured)for(const i of Ma(o).map(s=>s.id)){const s=`${o.id}:${i}`;if(!t){n.push(s);continue}const r=/bge-m3|bge-large|embedding/i.test(i)&&!/rerank/i.test(i),c=/rerank|bce-reranker/i.test(i);t==="embed"&&r&&n.push(s),t==="rerank"&&c&&n.push(s)}}return t&&!n.length?yt(e):n}function Dp(e){const t=yt(e,"embed").map(o=>`<option value="${ee(o)}"></option>`).join(""),n=yt(e,"rerank").map(o=>`<option value="${ee(o)}"></option>`).join(""),a=yt(e).map(o=>`<option value="${ee(o)}"></option>`).join("");return`<datalist id="brain-embed-models">${t}</datalist>
    <datalist id="brain-rerank-models">${n}</datalist>
    <datalist id="brain-llm-models">${a}</datalist>`}function Np(e){return e==="embed"?"brain-embed-models":e==="rerank"?"brain-rerank-models":"brain-llm-models"}function Up(e){const t=At(),n=Cp(),a=yt(e).length,o=e.models.brains||{};let i="",s=!1;for(const p of e.brain_modules){p.parent_brain==="cerebellum"&&!s&&(s=!0,i+=`<tr class="brain-group-row"><td colspan="${n.length+1}"><strong>小织 · 联想检索</strong> <span class="hint">embed + rerank 分列配置</span></td></tr>`),p.parent_brain!=="cerebellum"&&(s=!1);const m=o[p.id]||{},g=p.capability,v=Np(g),k=n.map(E=>!t&&p.parent_brain==="cerebellum"&&E==="global_fallback"&&g==="rerank"?`<td><input class="brain-slot brain-slot-na" data-brain="${p.id}" data-slot="${E}" value="${m[E]??""}" placeholder="不适用全局" list="${v}" title="rerank 不继承页面底部全局 fallback" /></td>`:!t&&p.id==="ocr"&&E==="global_fallback"?`<td><input class="brain-slot brain-slot-na" data-brain="${p.id}" data-slot="${E}" value="${m[E]??""}" placeholder="不适用全局" list="${v}" title="ocr 不继承 global_fallback" /></td>`:`<td><input class="brain-slot" data-brain="${p.id}" data-slot="${E}" value="${m[E]??""}" placeholder="provider:model" list="${v}" /></td>`).join("");i+=`<tr><td>${p.label}</td>${k}</tr>`}const r=e.models.global_fallback??"",c=t?"客户端简版：仅 primary · 硅基 / DeepSeek / 自定义 API · 无 fallback":`下拉联想仅含已配置 Key 的平台（${a} 个 provider:model）；小织 embed / rerank 分列联想`,l=t?"":`<label style="margin-top:0.75rem;display:block">全局 fallback（Chat 脑区）</label>
    <div class="row panel-inline-row">
      <input id="globalFallback" value="${r}" placeholder="siliconflow:Qwen/Qwen3-8B" list="brain-llm-models" />
    </div>`,u=n.map(p=>`<th>${Tp[p]}</th>`).join("");return`
    ${Dp(e)}
    <p class="hint">${c}</p>
    <div class="table-wrap">
      <table>
        <thead><tr><th>脑区</th>${u}</tr></thead>
        <tbody>${i}</tbody>
      </table>
    </div>
    ${l}
    <div class="panel-tail-row panel-tail-row--inline">
      <p class="hint">格式：<code>provider:model_id</code> · 留空表示 null${t?"":" · <strong>不含小织</strong>（向量编码 / 向量重排仅走本行 slot）"}</p>
      <button class="btn-primary" id="saveBrains">保存脑区模型</button>
    </div>`}function jp(e){return`
    <div class="table-wrap">
      <table>
        <thead><tr><th>provider_id</th><th>名称</th><th>Base URL</th><th>状态</th><th></th></tr></thead>
        <tbody>${Object.values(e.providers).filter(n=>n.deletable).map(n=>`<tr>
        <td><code>${ee(n.id)}</code></td>
        <td>${ee(n.label)}</td>
        <td class="hint">${ee(n.base_url||"—")}</td>
        <td><span class="hint ${n.configured?"ok":""}">${n.configured?"已配置":"未配置"}</span></td>
        <td><button type="button" class="secondary custom-provider-delete" data-id="${ee(n.id)}">删除</button></td>
      </tr>`).join("")||'<tr><td colspan="5" class="hint">暂无额外自定义 API（内置 <code>custom</code> 可在下方「云端模型」配置）</td></tr>'}</tbody>
      </table>
    </div>
    <div class="panel llm-custom-add" style="margin-top:0.75rem">
      <h3 style="margin-top:0">添加自定义 API</h3>
      <p class="hint">OpenAI 兼容端点 · provider_id 小写 · 不可与内置厂商重名</p>
      <label>provider_id</label>
      <input id="customProviderId" placeholder="例如 lmstudio_local" />
      <label style="margin-top:0.5rem;display:block">显示名称</label>
      <input id="customProviderLabel" placeholder="LM Studio 本地" />
      <label style="margin-top:0.5rem;display:block">Base URL</label>
      <input id="customProviderBase" placeholder="http://127.0.0.1:1234/v1" />
      <label style="margin-top:0.5rem;display:block">API Key（可选）</label>
      <input id="customProviderKey" type="password" placeholder="无 Key 可留空" />
      <div class="panel-tail-row panel-tail-row--inline" style="margin-top:0.75rem">
        <p class="hint">添加后在「平台模型管理」登记 model_id · 脑区写 <code>provider_id:model</code></p>
        <button type="button" class="btn-primary" id="customProviderAdd">添加</button>
      </div>
    </div>`}function Hp(){const e=At(),t=e?"":M("自定义 API",`<p class="hint">Docker 专用 · 可增删多个 OpenAI 兼容厂商 · 客户端仍仅暴露硅基 / DeepSeek / 内置 custom</p>
      <div id="llmCustomProviders">加载中…</div>`);return`
    <div class="row llm-page-toolbar" style="justify-content:flex-end;margin-bottom:0.75rem">
      <button class="secondary" id="llmRefresh">刷新</button>
    </div>
    ${ie("脑区模型与云端 Provider 配置",`
      ${M("脑区模型",`<p class="hint">${e?"各脑区 primary 主模型 · 未配置则对应能力不可用":"各脑区三层 slot：primary → module_fallback → global_fallback · 小织分「向量编码 / 向量重排」两区"}</p>
        <div id="llmBrains">加载中…</div>`)}
      ${M("平台模型管理",`<p class="hint">${e?"硅基流动 · DeepSeek · 自定义 OpenAI 兼容 API":"左侧选择平台，右侧管理 model_id 列表；可增删，供脑区下拉联想。"}</p>
        <div id="llmPlatformModels">加载中…</div>`)}
      ${t}
      ${M("云端模型",`<p class="hint">${e?"填写 API Key 与 Base URL（自定义 API 必填 Base URL）· 模型列表见 <code>llm_models.local.yaml</code>":"启动时自动读取 <code>providers.local.yaml</code>（硅基 openai_compat → siliconflow，智谱 zhipu）；模型列表见 <code>llm_models.local.yaml</code>。"}</p>
        <p id="llmProvidersPath" class="hint"></p>
        <p id="llmModelsPath" class="hint"></p>
        <div id="llmProviders">加载中…</div>`)}
    `)}`}function qp(e,t){document.getElementById("llmPlatformDetail").innerHTML=Zi(e,t),document.querySelectorAll(".llm-platform-nav").forEach(n=>{n.classList.toggle("active",n.dataset.platform===t)})}function Vp(e){ct==null||ct.abort(),ct=new AbortController;const{signal:t}=ct;document.getElementById("llmRefresh").addEventListener("click",()=>{e().catch(Ie)},{signal:t}),document.getElementById("llmProviders").addEventListener("click",a=>{const o=a.target.closest(".provider-save");o&&We(async()=>{const i=o.dataset.id,s=document.querySelector(`.llm-provider-card[data-provider="${i}"]`),r=s.querySelector(".provider-key").value.trim(),c=s.querySelector(".provider-base").value.trim(),l={};if(r&&(l.api_key=r),c&&(l.base_url=c),!l.api_key&&!l.base_url){d("请填写 API Key 或 Base URL","error");return}await _p(i,l),d(`「${i}」已保存`,"success"),s.querySelector(".provider-key").value="",await e()}).catch(Ie)},{signal:t});const n=document.getElementById("llmCustomProviders");n&&n.addEventListener("click",a=>{const o=a.target.closest(".custom-provider-delete");if(o!=null&&o.dataset.id){We(async()=>{const s=o.dataset.id;window.confirm(`删除自定义 API「${s}」？`)&&(await Pp(s),d(`已删除 ${s}`,"success"),await e())}).catch(Ie);return}a.target.closest("#customProviderAdd")&&We(async()=>{const s=document.getElementById("customProviderId").value.trim(),r=document.getElementById("customProviderLabel").value.trim(),c=document.getElementById("customProviderBase").value.trim(),l=document.getElementById("customProviderKey").value.trim();if(!s||!c){d("请填写 provider_id 与 Base URL","error");return}await Ep({provider_id:s,label:r,base_url:c,api_key:l}),d(`已添加 ${s}`,"success"),document.getElementById("customProviderId").value="",document.getElementById("customProviderLabel").value="",document.getElementById("customProviderBase").value="",document.getElementById("customProviderKey").value="",await e()}).catch(Ie)},{signal:t}),document.getElementById("llmPlatformModels").addEventListener("click",a=>{const o=a.target,i=o.closest(".llm-platform-nav");if(i!=null&&i.dataset.platform){const c=i.dataset.platform;if(c===gt)return;gt=c;const l=Qi;l&&qp(l,c);return}const s=o.closest(".model-delete");if(s!=null&&s.dataset.provider&&s.dataset.model){We(async()=>{const c=s.dataset.provider,l=s.dataset.model;window.confirm(`删除模型「${l}」？`)&&(await $p(c,l),d(`已删除 ${l}`,"success"),await e())}).catch(Ie);return}const r=o.closest(".model-add-btn");r!=null&&r.dataset.provider&&We(async()=>{const c=r.dataset.provider,l=r.closest(".llm-platform-detail-inner"),u=l==null?void 0:l.querySelector(".model-add-input"),p=(u==null?void 0:u.value.trim())??"";if(!p){d("请输入 model_id","error");return}await kp(c,p),d(`已添加 ${p}`,"success"),u&&(u.value=""),await e()}).catch(Ie)},{signal:t}),document.getElementById("llmBrains").addEventListener("click",a=>{a.target.closest("#saveBrains")&&We(async()=>{var r;const i={};document.querySelectorAll(".brain-slot").forEach(c=>{const l=c,u=l.dataset.brain,p=l.dataset.slot,m=l.value.trim();i[u]??(i[u]={}),i[u][p]=m||null});let s=null;if(!At())s=((r=document.getElementById("globalFallback"))==null?void 0:r.value.trim())||null;else for(const c of Object.values(i))c.module_fallback=null,c.global_fallback=null;await Sp({brains:i,global_fallback:s}),d("脑区模型已保存（内存生效，重启后恢复 yaml）","success")}).catch(Ie)},{signal:t})}async function Fp(){const e=async()=>{const t=await wp();Qi=t,document.getElementById("llmProvidersPath").textContent=`Key 配置：${t.providers_path}`,document.getElementById("llmModelsPath").textContent=`模型列表：${t.models_path||"llm_models.local.yaml"}`,document.getElementById("llmProviders").innerHTML=Mp(t),document.getElementById("llmPlatformModels").innerHTML=Rp(t),document.getElementById("llmBrains").innerHTML=Up(t);const n=document.getElementById("llmCustomProviders");n&&(n.innerHTML=jp(t))};Vp(e);try{await e()}catch(t){Ie(t),document.getElementById("llmProviders").textContent="加载失败",document.getElementById("llmPlatformModels").textContent="加载失败",document.getElementById("llmBrains").textContent="加载失败"}}const zp=1;function Wp(e){const t=String(e||"").match(/^v(\d+)$/);return t?Number(t[1]):1}function es(){return new TextEncoder}function Kp(e){let t=4294967295;for(let n=0;n<e.length;n+=1){t^=e[n];for(let a=0;a<8;a+=1)t=t&1?t>>>1^3988292384:t>>>1}return(t^4294967295)>>>0}function V(e){return new Uint8Array([e&255,e>>>8&255])}function ae(e){return new Uint8Array([e&255,e>>>8&255,e>>>16&255,e>>>24&255])}function Ke(e){const t=e.reduce((o,i)=>o+i.length,0),n=new Uint8Array(t);let a=0;for(const o of e)n.set(o,a),a+=o.length;return n}function Jp(e){return{time:(e.getHours()<<11|e.getMinutes()<<5|e.getSeconds()/2)&65535,date:(e.getFullYear()-1980<<9|e.getMonth()+1<<5|e.getDate())&65535}}function Gp(e){const t=[],n=[];let a=0;const o=Jp(new Date);for(const c of e){const l=es().encode(c.name),u=c.data,p=Kp(u),m=Ke([ae(67324752),V(20),V(0),V(0),V(o.time),V(o.date),ae(p),ae(u.length),ae(u.length),V(l.length),V(0),l,u]);t.push(m);const g=Ke([ae(33639248),V(20),V(20),V(0),V(0),V(o.time),V(o.date),ae(p),ae(u.length),ae(u.length),V(l.length),V(0),V(0),V(0),V(0),ae(0),ae(a),l]);n.push(g),a+=m.length}const i=Ke(n),s=Ke(t),r=Ke([ae(101010256),V(0),V(0),V(e.length),V(e.length),ae(i.length),ae(s.length),V(0)]);return Ke([s,i,r])}function Yp(e,t,n){const a=Wp(t),o=[],i=[],s=es(),r=(l,u)=>{o.push(l),i.push({name:l,data:s.encode(u)})};r("profile.md",e.profile_md||""),r("assembly_summary.md",e.assembly_summary||""),n.include_full_l2&&e.assembly_master!=null&&r("assembly_master.md",String(e.assembly_master)),n.include_proactive_profile&&e.proactive_profile!=null&&r("memory/proactive_profile.json",`${JSON.stringify(e.proactive_profile,null,2)}
`),n.include_deep_learn_runs&&e.deep_learn_runs!=null&&r("deep_learn/runs.json",`${JSON.stringify(e.deep_learn_runs,null,2)}
`),n.include_stickers&&e.stickers!=null&&r("stickers/manifest.json",`${JSON.stringify({stickers:e.stickers},null,2)}
`);const c={schema_version:zp,version:a,bundle_id:t||`v${a}`,vp_id:e.vp_id,label:e.label,stage:e.stage,created_at:new Date().toISOString(),export_options:n,files:o};return i.push({name:"manifest.json",data:s.encode(`${JSON.stringify(c,null,2)}
`)}),new Blob([Gp(i)],{type:"application/zip"})}function ts(e,t){const n=URL.createObjectURL(e),a=document.createElement("a");a.href=n,a.download=t,a.click(),URL.revokeObjectURL(n)}function re(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/"/g,"&quot;")}function Xp(e){const t=e.kind||"workflow",n=e.scope||"global",a=(e.triggers||[]).join(", ")||"—";return`<span class="hint">${re(t)}</span> · <span class="hint">${re(n)}</span> · <span class="hint">${re(a)}</span>`}function Qp(e){var n;const t=["---"];return t.push(`name: ${e.name}`),e.description&&t.push(`description: ${e.description}`),e.enabled||t.push("enabled: false"),(n=e.triggers)!=null&&n.length&&t.push(`triggers: [${e.triggers.join(", ")}]`),e.priority&&t.push(`priority: ${e.priority}`),e.kind&&e.kind!=="workflow"&&t.push(`kind: ${e.kind}`),e.scope&&e.scope!=="global"&&t.push(`scope: ${e.scope}`),t.push("---","",e.body||""),t.join(`
`)}function Zp(e){const t=e.description||e.name,n=e.description?Oi(e.name):"",a=e.readonly?"":`<button class="secondary" data-edit="${re(e.name)}" type="button">编辑</button>
       <button class="secondary" data-delete="${re(e.name)}" type="button">删除</button>`;return`<div class="skill-row" data-skill="${re(e.name)}">
    <label class="switch-label-row">
      <input type="checkbox" data-name="${re(e.name)}" ${e.enabled?"checked":""}/>
      <span class="switch-label-text">${re(t)}${n}</span>
    </label>
    ${Xp(e)}
    <button class="secondary" data-view="${re(e.name)}" type="button">查看</button>
    ${a}
  </div>`}function em(){return C().consoleProfile==="client"?ie("Skill 蒸馏与管理",M("Skill",'<p class="hint">Skill 蒸馏与管理仅 Docker 全量 Console（NAS 内网）可用。瘦客户端不提供此入口。</p>')):ie("从记忆碎片蒸馏 SKILL 草稿",`
    ${M("Skill 蒸馏",`<p class="hint">从近期记忆碎片生成 SKILL 草稿（默认 dry-run 预览）。</p>
      <div class="core-switch-row panel-tail-row panel-tail-row--inline">
        <button class="btn-primary" id="distillPreview">预览草稿</button>
        <button class="secondary" id="distillPublish">发布到 skills/</button>
      </div>
      <pre id="distillOut" class="config core-preview hint">（点击预览或发布）</pre>`)}
    ${M("导入 / 导出",`<p class="hint">上传 export bundle zip 写入 skills/custom/ · 下载当前启用 Skill 打包（需 API Token）</p>
      <div class="core-switch-row panel-tail-row panel-tail-row--inline">
        <input type="file" id="skillImportFile" accept=".zip,application/zip" />
        <button class="btn-primary" id="skillImportBtn" type="button">导入 bundle</button>
        <button class="secondary" id="skillExportBtn" type="button">下载 bundle</button>
      </div>
      <p class="hint" id="skillImportStatus"></p>`)}
    ${M("Skill 管理",`<p class="hint">kind · scope · triggers 列于每行 · custom 可编辑/删除</p>
      <div id="skillsList" class="hint">加载中…</div>
      <div id="skillEditor" class="skill-editor hidden"></div>`)}
  `)}function tm(e,t,n){e.classList.remove("hidden"),e.innerHTML=`<h3>${re(t)}</h3><pre class="config">${re(n||"")}</pre>`}function nm(e,t,n,a){e.classList.remove("hidden"),e.innerHTML=`<h3>编辑 ${re(n)}</h3>
    <textarea id="skillEditArea" rows="20" class="config" spellcheck="false"></textarea>
    <div class="panel-tail-row panel-tail-row--inline">
      <button class="btn-primary" id="skillSaveBtn" type="button">保存</button>
      <button class="secondary" id="skillCancelBtn" type="button">取消</button>
    </div>`,document.getElementById("skillEditArea").value=a,document.getElementById("skillSaveBtn").onclick=async()=>{const o=document.getElementById("skillEditArea");try{await qu(n,o.value),d(`Skill「${n}」已保存`,"success"),e.classList.add("hidden"),await bt(t,e)}catch(i){d(String(i),"error")}},document.getElementById("skillCancelBtn").onclick=()=>{e.classList.add("hidden")}}let lt,Ot;function am(e,t){e.querySelectorAll("input[type=checkbox]").forEach(n=>{n.addEventListener("change",async()=>{const a=n,o=a.dataset.name,i=!a.checked;try{await Hu(o,a.checked),d(`Skill「${o}」已${a.checked?"启用":"禁用"}`,"success")}catch(s){a.checked=i,d(String(s),"error")}})}),e.querySelectorAll("button[data-view]").forEach(n=>{n.addEventListener("click",async()=>{const a=n.dataset.view;try{const o=await Io(a);tm(t,a,o.body||"")}catch(o){d(String(o),"error")}})}),e.querySelectorAll("button[data-edit]").forEach(n=>{n.addEventListener("click",async()=>{const a=n.dataset.edit;try{const o=await Io(a);nm(t,e,a,Qp(o))}catch(o){d(String(o),"error")}})}),e.querySelectorAll("button[data-delete]").forEach(n=>{n.addEventListener("click",async()=>{const a=n.dataset.delete;if(confirm(`删除 custom Skill「${a}」？此操作不可撤销。`))try{await Vu(a),d(`Skill「${a}」已删除`,"success"),t.classList.add("hidden"),await bt(e,t)}catch(o){d(String(o),"error")}})})}async function bt(e,t){const n=await Aa();e.innerHTML=n.map(a=>Zp(a)).join("")||"（无 Skill）",am(e,t)}async function om(){if(C().consoleProfile==="client")return;lt=document.getElementById("skillsList"),Ot=document.getElementById("skillEditor");const e=document.getElementById("distillOut"),t=document.getElementById("skillImportStatus"),n=async a=>{e.textContent="蒸馏中…";try{const o=await Wu(a,C().memorySpace);e.textContent=[a?"【预览】":"【已发布】",`name: ${o.name}`,`confidence: ${o.confidence??"—"} · atoms: ${o.atom_count??"—"}`,"",o.content].join(`
`),a||(d("草稿已发布到 skills/","success"),await bt(lt,Ot))}catch(o){e.textContent=String(o)}};document.getElementById("distillPreview").onclick=()=>void n(!0),document.getElementById("distillPublish").onclick=()=>{confirm("将草稿写入 skills/ 目录？")&&n(!1)},document.getElementById("skillImportBtn").onclick=async()=>{var i;const a=document.getElementById("skillImportFile"),o=(i=a.files)==null?void 0:i[0];if(!o){d("请选择 zip 文件","error");return}t.textContent="导入中…";try{const s=await Fu(o),r=s.imported.map(c=>c.name).join(", ")||"—";t.textContent=`已导入：${r}`,d(`导入 ${s.imported.length} 个 Skill`,"success"),a.value="",await bt(lt,Ot)}catch(s){t.textContent=String(s),d(String(s),"error")}},document.getElementById("skillExportBtn").onclick=async()=>{t.textContent="打包下载中…";try{const a=await zu();ts(a,"niannian-skills-bundle.zip"),t.textContent="bundle 已下载",d("bundle 已下载","success")}catch(a){t.textContent=String(a),d(String(a),"error")}};try{await bt(lt,Ot)}catch(a){lt.textContent=String(a)}}const im={capability_id:"对方项目名，须与注册表一致",project:"记忆空间，通常与 capability_id 相同",function:"功能子目录，如 habit_corrections",operation:"add 新建 · overwrite 覆盖 · delete 删除",occurred_at:"业务真实发生时间，可早于写入，用于回溯",payload:"结构化数据体，非自然语言碎片",dedup_key:"同一业务对象幂等键，重复 add 返回 422",record_id:"显式记录 ID；缺省由 dedup_key 推导","caller.client":"调用方客户端名，须在白名单","caller.client_version":"对方版本，便于兼容排查","caller.device":"设备标识","caller.session_id":"对方会话 ID","provenance.trace_id":"单次调用链 ID，关联审计与日志","provenance.actor.user_id":"上报人（念念用户 ID 或对方映射）","provenance.actor.display_name":"展示名","provenance.reported_at":"对方上报时间","provenance.correlation_id":"批次/任务 ID，便于按 job 追查","provenance.parent_record_id":"上游记录，拼溯源链（纠正←原样本）","provenance.source_ref":"来源 URI/文件/样本 ID","learn.mode":"off=只存不学 · auto=写入后自动摘要 · manual=需手动点学习","learn.hint":"给摘要模型的短提示，如「用户纠正误标」"},sm=["capability_id","project","function","operation","occurred_at","payload","dedup_key","record_id","caller.client","caller.client_version","caller.device","caller.session_id","provenance.trace_id","provenance.actor.user_id","provenance.actor.display_name","provenance.reported_at","provenance.correlation_id","provenance.parent_record_id","provenance.source_ref","learn.mode","learn.hint"],rm=["capability_id","project","function","operation","occurred_at","payload","caller.client","provenance.trace_id","provenance.actor.user_id","provenance.reported_at"];let qt="connect";function q(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/"/g,"&quot;")}function cm(e){return e===!1?'<span class="badge warn">失败</span>':'<span class="badge ok">成功</span>'}function lm(){return`
    <div class="core-params-row mcp-filters">
      <label class="core-field"><span>方向</span>
        <select id="auditDirection">
          <option value="">全部</option>
          <option value="in">in</option>
          <option value="out">out</option>
        </select>
      </label>
      <label class="core-field"><span>工具</span>
        <input id="auditTool" placeholder="niannian_external_data" />
      </label>
      <label class="core-field"><span>capability</span>
        <input id="auditCapability" placeholder="imagebrain" />
      </label>
      <label class="core-field"><span>since</span>
        <input id="auditSince" placeholder="ISO 时间" />
      </label>
      <button class="secondary core-btn-fixed" id="auditRefresh" type="button">刷新</button>
    </div>`}function dm(e){if(!e.length)return me('<p class="hint">暂无审计记录</p>');const t=e.map(n=>{const a=n.capability_id||"—",o=n.capability_id?`<button class="secondary mcp-jump-logs" type="button" data-project="${q(n.capability_id)}">跳日志</button>`:"";return`<tr data-capability="${q(a)}">
        <td><small>${q(Le(n.ts))}</small></td>
        <td><code>${q(n.direction)}</code></td>
        <td>${q(a)}</td>
        <td><code>${q(n.tool)}</code></td>
        <td>${q(n.client_id||"—")}</td>
        <td>${cm(n.ok)}</td>
        <td>${n.duration_ms??"—"} ms</td>
        <td>${o}</td>
      </tr>`}).join("");return me(`<table class="core-table" id="mcpAuditTable">
    <thead><tr>
      <th>时间</th><th>方向</th><th>capability</th><th>工具</th><th>客户端</th><th>结果</th><th>耗时</th><th></th>
    </tr></thead>
    <tbody>${t}</tbody>
  </table>`)}function um(){return`
    <div class="core-params-row mcp-filters">
      <label class="core-field"><span>project</span>
        <input id="logProject" placeholder="imagebrain" value="imagebrain" />
      </label>
      <label class="core-field"><span>actor</span>
        <input id="logActor" placeholder="user_id" />
      </label>
      <label class="core-field"><span>trace_id</span>
        <input id="logTrace" placeholder="trace-…" />
      </label>
      <label class="core-field"><span>since</span>
        <input id="logSince" placeholder="ISO" />
      </label>
      <label class="core-field"><span>until</span>
        <input id="logUntil" placeholder="ISO" />
      </label>
      <button class="secondary core-btn-fixed" id="logRefresh" type="button">刷新</button>
    </div>`}function pm(e){const t=e.map(n=>({id:n.record_id||`${n.trace_id}-${n.occurred_at}`,label:n.operation,text:`${n.function||"—"} · ${n.record_id||"—"} · ${Le(n.occurred_at)}`,parent_id:n.parent_record_id||null}));return Ba(t)}function mm(e){if(!e.length)return me('<p class="hint">暂无工具岛审计</p>');const t=new Map;for(const a of e){const o=a.trace_id||"—",i=t.get(o)||[];i.push(a),t.set(o,i)}const n=e.map(a=>{var l;const o=((l=a.actor)==null?void 0:l.user_id)||"—",i=a.trace_id||"—",s=t.get(i)||[a],r=q(pm(s)),c=s.length>1||a.parent_record_id?`<details class="mcp-trace-fold"><summary>溯源链 (${s.length})</summary><pre class="config core-preview">${r}</pre></details>`:"";return`<tr>
        <td><small>${q(Le(a.occurred_at))}</small></td>
        <td>${q(o)}</td>
        <td><code>${q(i)}</code></td>
        <td>${q(a.operation)}</td>
        <td><code>${q(a.record_id||"—")}</code></td>
        <td>${a.learn_applied?"是":"否"}</td>
        <td>${c}</td>
      </tr>`}).join("");return me(`<table class="core-table" id="mcpLogsTable">
    <thead><tr>
      <th>occurred_at</th><th>actor</th><th>trace_id</th><th>operation</th><th>record_id</th><th>learn</th><th>溯源</th>
    </tr></thead>
    <tbody>${n}</tbody>
  </table>`)}function ns(){return`
    <nav class="core-tabs" aria-label="MCP 使用说明">${[{id:"connect",label:"接入念念"},{id:"tools",label:"工具速查"},{id:"protocol",label:"外部工具协议"}].map(a=>`<button type="button" class="core-tab${qt===a.id?" active":""}" data-mcp-doc="${a.id}">${a.label}</button>`).join("")}</nav>
    <div class="mcp-doc-panel">${qt==="connect"?`<p>外部工具通过 MCP 向念念写入<strong>结构化记忆</strong>，与 atom ingest 硬隔离。数据落在 <code>external/{project}/{function}/</code>，不进 atoms / dream。</p>
       <ul class="core-list">
         <li>在 Cursor / Codex 配置 <code>mcpServers.niannian</code>（见下方「快速生成配置」）</li>
         <li>注册 capability（<code>config/external_tools.yaml</code> 或 Console ⑤）</li>
         <li>调用 <code>niannian_external_data</code> 携带 v2 信封</li>
         <li>Chat 仅在 <code>tool_scope</code> 开启时读取工具岛</li>
       </ul>`:qt==="tools"?`<table class="core-table"><thead><tr><th>工具</th><th>说明</th></tr></thead><tbody>
           <tr><td><code>niannian_external_data</code></td><td>写入 v2 信封</td></tr>
           <tr><td><code>niannian_external_recall</code></td><td>工具岛内检索</td></tr>
           <tr><td><code>niannian_external_list</code></td><td>列 function / 记录</td></tr>
           <tr><td><code>niannian_external_learn</code></td><td>手动触发摘要</td></tr>
           <tr><td><code>niannian_external_audit</code></td><td>查 _audit.jsonl</td></tr>
           <tr><td><code>niannian_external_trace</code></td><td>record_id 因果链</td></tr>
         </tbody></table>
         <p class="hint"><code>niannian_assist</code> 不读 external/</p>`:`<p>协议：<code>niannian.external_data/2</code> · v1 兼容（缺 provenance 时 actor=unknown 仅告警）</p>
         <p>必填：capability_id · project · function · operation · occurred_at · caller.client · provenance.trace_id · provenance.actor.user_id · provenance.reported_at</p>
         <p class="hint">learn.mode 默认 <strong>off</strong>（只存不学）· 摘要仅进 <code>_learned/</code>，不进 atoms</p>
         <p class="hint">详细字段见下方「需求字段配置」勾选表</p>`}</div>
    <details class="core-details mcp-advanced-probe">
      <summary>高级 · 对内 MCP Server Probe</summary>
      <div id="mcpServerList" class="hint">加载中…</div>
    </details>`}function fm(){const e=C().apiBase;return`
    <div class="core-params-row">
      <label class="core-field"><span>连接方式</span>
        <select id="cfgTransport">
          <option value="url">URL（HTTP/SSE）</option>
          <option value="stdio">stdio（niannian-mcp）</option>
        </select>
      </label>
      <label class="core-field"><span>MCP URL</span>
        <input id="cfgUrl" value="${q(`${e}/mcp`)}" />
      </label>
      <label class="core-field"><span>API URL（stdio）</span>
        <input id="cfgApiUrl" value="${q(e)}" />
      </label>
      <label class="core-field"><span>Bearer</span>
        <input id="cfgBearer" type="password" placeholder="可选" />
      </label>
      <label class="core-field"><span>memory_space</span>
        <input id="cfgMemorySpace" value="imagebrain" />
      </label>
      <button class="btn-primary core-btn-fixed" id="cfgPreview" type="button">生成预览</button>
    </div>
    <pre class="config core-preview" id="cfgPreviewOut">（点击生成预览）</pre>
    <div class="panel-tail-row panel-tail-row--inline">
      <button class="secondary" id="cfgCopy" type="button">复制 mcp.json</button>
    </div>`}function hm(e){const t=im[e]||"",n=rm.includes(e)?"checked":"";return`<tr>
    <td><label><input type="checkbox" class="env-field" data-field="${q(e)}" ${n} /> <code>${q(e)}</code></label></td>
    <td><span class="field-hint">${q(t)}</span></td>
  </tr>`}function vm(e){if(!e.length)return me('<p class="hint">暂无 capability · 请先在 ⑤ 需求字段配置 注册 capability</p>');const t=e.map(n=>{const a=n.chat_enabled===!0,o=a?"开":"关";return`<tr>
        <td>${q(n.label||n.capability_id)}</td>
        <td><code>${q(n.capability_id)}</code></td>
        <td class="mcp-chat-toggle-cell">
          <label class="switch-label-row">
            <input type="checkbox" class="mcp-chat-toggle" data-capability-id="${q(n.capability_id)}" ${a?"checked":""} />
            <span class="mcp-chat-toggle-state">${o}</span>
          </label>
        </td>
      </tr>`}).join("");return me(`<table class="core-table mcp-chat-tools-table">
    <thead><tr><th>名称</th><th>capability_id</th><th>Chat</th></tr></thead>
    <tbody>${t}</tbody>
  </table>`)}function gm(){const e=sm.map(hm).join("");return`
    <div class="core-params-row mcp-cap-form">
      <label class="core-field"><span>capability_id</span><input id="capId" placeholder="imagebrain" /></label>
      <label class="core-field"><span>label</span><input id="capLabel" placeholder="图片管理" /></label>
      <label class="core-field"><span>memory_space</span><input id="capMemory" placeholder="imagebrain" /></label>
      <label class="core-field"><span>client_allowlist</span><input id="capClients" placeholder="imagebrain" /></label>
      <label class="core-field"><span>functions</span><input id="capFunctions" placeholder="habit_corrections" /></label>
      <label class="core-field"><span>scopes</span><input id="capScopes" value="external:write,external:read,external:learn" /></label>
      <label class="core-field"><span>auto_learn_default</span>
        <select id="capLearnDefault"><option value="off">off</option><option value="auto">auto</option><option value="manual">manual</option></select>
      </label>
    </div>
    ${me(`<table class="core-table mcp-field-table">
      <thead><tr><th>字段</th><th>说明</th></tr></thead>
      <tbody>${e}</tbody>
    </table>`)}
    <div class="panel-tail-row panel-tail-row--inline">
      <button class="secondary" id="genYaml" type="button">生成 yaml 片段</button>
      <button class="secondary" id="genEnvelope" type="button">生成 envelope 示例</button>
      <button class="secondary" id="genConnect" type="button">生成连接说明</button>
      <button class="secondary" id="saveCapability" type="button">保存到注册表</button>
    </div>
    <pre class="config core-preview" id="fieldGenOut">（选择字段后点击生成）</pre>`}function ym(){return ie("外部工具 MCP · 审计、配置与信封向导",`
    ${M("① MCP 审计",`${lm()}<div id="mcpAuditHost" class="hint">加载中…</div>`)}
    ${M("② MCP 日志",`${um()}<div id="mcpLogsHost" class="hint">选择 project 后刷新</div>`,"数据源 external/{project}/_audit.jsonl")}
    ${M("③ 使用说明",ns())}
    ${M("④ 快速生成 MCP 配置",fm())}
    ${M("⑤ 需求字段配置",gm(),"勾选字段生成 yaml / envelope / 连接说明")}
    ${M("⑥ 聊天可用工具管理",'<div id="mcpChatToolsHost" class="hint">加载中…</div>',"仅启用的工具出现在 Chat「外部记忆」选择器；默认全部禁用")}
  `)}function Rt(){const e=document.getElementById("capId").value.trim(),t=document.getElementById("capLabel").value.trim(),n=document.getElementById("capMemory").value.trim()||e,a=document.getElementById("capClients").value.split(",").map(r=>r.trim()).filter(Boolean),o=document.getElementById("capFunctions").value.split(",").map(r=>r.trim()).filter(Boolean),i=document.getElementById("capScopes").value.split(",").map(r=>r.trim()).filter(Boolean),s=document.getElementById("capLearnDefault").value;return{capability_id:e,label:t||e,memory_space:n,client_allowlist:a.length?a:[e],functions:o,scopes:i,auto_learn_default:s}}function bm(e){document.getElementById("capId").value=e.capability_id||"",document.getElementById("capLabel").value=e.label||"",document.getElementById("capMemory").value=e.memory_space||e.capability_id||"",document.getElementById("capClients").value=(e.client_allowlist||[]).join(", "),document.getElementById("capFunctions").value=(e.functions||[]).join(", "),document.getElementById("capScopes").value=(e.scopes||[]).join(",")||"external:write,external:read,external:learn",document.getElementById("capLearnDefault").value=e.auto_learn_default||"off"}function wm(){return Array.from(document.querySelectorAll(".env-field:checked")).map(e=>e.dataset.field).filter(Boolean)}function _m(e){return["capabilities:","  - capability_id: "+e.capability_id,"    label: "+(e.label||e.capability_id),"    memory_space: "+(e.memory_space||e.capability_id),"    client_allowlist: ["+(e.client_allowlist||[]).join(", ")+"]","    functions: ["+(e.functions||[]).join(", ")+"]","    scopes: ["+(e.scopes||[]).join(", ")+"]","    auto_learn_default: "+(e.auto_learn_default||"off")].join(`
`)}function km(e){const t=new Date().toISOString(),n=(e.functions||[])[0]||"habit_corrections",a=(e.client_allowlist||[])[0]||e.capability_id;return{capability_id:e.capability_id,project:e.memory_space||e.capability_id,function:n,operation:"add",occurred_at:t,payload:{note:"example"},dedup_key:`${e.capability_id}:example:001`,record_id:"","caller.client":a,"caller.client_version":"0.1.0","caller.device":"dev","caller.session_id":"sess-example","provenance.trace_id":"trace-example","provenance.actor.user_id":"user-example","provenance.actor.display_name":"示例用户","provenance.reported_at":t,"provenance.correlation_id":"job-example","provenance.parent_record_id":"","provenance.source_ref":"file://example.jpg","learn.mode":e.auto_learn_default||"off","learn.hint":""}}function $m(e){const t=C().apiBase,n=e.memory_space||e.capability_id,a=(e.client_allowlist||[])[0]||e.capability_id;return[`# ${e.label||e.capability_id} → 念念 MCP 连接说明`,"","1. 在 Cursor 设置 MCP，使用 §④ 生成的 mcp.json（url 或 stdio）",`2. API 基址：${t}`,`3. 注册 capability_id=${e.capability_id}，memory_space=${n}`,`4. 调用方 client 须在白名单：${a}`,"5. 写入工具：niannian_external_data · 协议 niannian.external_data/2","6. 检索：niannian_external_recall / niannian_external_list（Chat 需 tool_scope）","","信封必填字段见 Console ⑤ 勾选表；learn.mode 默认 off。"].join(`
`)}async function To(){const e=document.getElementById("mcpAuditHost");try{const t=await ll({direction:document.getElementById("auditDirection").value,tool:document.getElementById("auditTool").value.trim(),capability_id:document.getElementById("auditCapability").value.trim(),since:document.getElementById("auditSince").value.trim(),limit:100});e.innerHTML=dm(t.entries),e.querySelectorAll(".mcp-jump-logs").forEach(n=>{n.addEventListener("click",()=>{var i;const a=n.dataset.project||"",o=document.getElementById("logProject");o.value=a,(i=document.getElementById("mcpLogsHost"))==null||i.scrollIntoView({behavior:"smooth",block:"start"}),Jn()})})}catch(t){e.textContent=String(t)}}function Sm(){document.querySelectorAll(".mcp-chat-toggle").forEach(e=>{e.addEventListener("change",()=>void Em(e))})}async function Em(e){var i;const t=e.dataset.capabilityId||"";if(!t)return;const n=e.checked,a=(i=e.closest("label"))==null?void 0:i.querySelector(".mcp-chat-toggle-state"),o=!n;e.disabled=!0;try{await pl(t,{chat_enabled:n}),a&&(a.textContent=n?"开":"关"),d(`${t} · Chat ${n?"已启用":"已禁用"}`,"success")}catch(s){e.checked=o,a&&(a.textContent=o?"开":"关"),d(String(s),"error")}finally{e.disabled=!1}}async function Lo(){const e=document.getElementById("mcpChatToolsHost");if(e)try{const t=await jn();e.innerHTML=vm(t.capabilities||[]),Sm()}catch(t){e.textContent=String(t)}}async function Jn(){const e=document.getElementById("mcpLogsHost"),t=document.getElementById("logProject").value.trim();if(!t){e.innerHTML='<p class="hint">请填写 project</p>';return}try{const n=await dl({project:t,actor:document.getElementById("logActor").value.trim(),trace_id:document.getElementById("logTrace").value.trim(),since:document.getElementById("logSince").value.trim(),until:document.getElementById("logUntil").value.trim()});e.innerHTML=mm(n.entries)}catch(n){e.textContent=String(n)}}async function as(){const e=document.getElementById("mcpServerList");if(e)try{const t=await ac();e.innerHTML=(t.servers||[]).map(n=>`<div class="skill-row">${q(String(n.name))} · ${n.enabled?"enabled":"disabled"} · ${q(String(n.transport||""))}
            <button class="secondary" data-probe="${q(String(n.name))}" type="button">Probe</button></div>`).join("")||"（无 server 配置，见 config/mcp.yaml）",e.querySelectorAll("button[data-probe]").forEach(n=>{n.addEventListener("click",async()=>{const a=n.dataset.probe;try{const o=await oc(a);d(JSON.stringify(o),"info")}catch(o){d(String(o),"error")}})})}catch(t){e.textContent=String(t)}}function os(){document.querySelectorAll("[data-mcp-doc]").forEach(e=>{e.addEventListener("click",()=>{qt=e.dataset.mcpDoc;const n=e.closest(".core-card");if(n){const a=n.querySelector(".core-card-body");if(a){const o=ns(),i=document.createElement("div");i.innerHTML=o,a.innerHTML=i.innerHTML,os(),as()}}})})}async function Pm(){var e,t,n,a,o,i,s,r,c;os(),as(),(e=document.getElementById("auditRefresh"))==null||e.addEventListener("click",()=>void To()),(t=document.getElementById("logRefresh"))==null||t.addEventListener("click",()=>void Jn()),(n=document.getElementById("cfgPreview"))==null||n.addEventListener("click",async()=>{const l=document.getElementById("cfgTransport").value,u=document.getElementById("cfgPreviewOut");try{const p=await ml({transport:l,url:document.getElementById("cfgUrl").value.trim(),api_url:document.getElementById("cfgApiUrl").value.trim(),bearer_token:document.getElementById("cfgBearer").value.trim(),memory_space:document.getElementById("cfgMemorySpace").value.trim()});u.textContent=JSON.stringify(p.mcp_json,null,2)}catch(p){u.textContent=String(p),d(String(p),"error")}}),(a=document.getElementById("cfgCopy"))==null||a.addEventListener("click",()=>{var u;const l=((u=document.getElementById("cfgPreviewOut"))==null?void 0:u.textContent)||"";if(!l||l.startsWith("（")){d("请先生成预览","error");return}navigator.clipboard.writeText(l),d("mcp.json 已复制","success")}),(o=document.getElementById("genYaml"))==null||o.addEventListener("click",()=>{const l=Rt();if(!l.capability_id){d("请填写 capability_id","error");return}const u=document.getElementById("fieldGenOut");u.textContent=_m(l)}),(i=document.getElementById("genEnvelope"))==null||i.addEventListener("click",async()=>{const l=Rt(),u=wm();if(!u.length){d("请至少勾选一个字段","error");return}const p=document.getElementById("fieldGenOut");try{const m=await fl({fields:u,defaults:km(l)});p.textContent=JSON.stringify(m.envelope,null,2)}catch(m){p.textContent=String(m),d(String(m),"error")}}),(s=document.getElementById("genConnect"))==null||s.addEventListener("click",()=>{const l=Rt();if(!l.capability_id){d("请填写 capability_id","error");return}const u=document.getElementById("fieldGenOut");u.textContent=$m(l)}),(r=document.getElementById("saveCapability"))==null||r.addEventListener("click",async()=>{const l=Rt();if(!l.capability_id){d("请填写 capability_id","error");return}try{const p=(await jn()).capabilities||[],m=p.findIndex(v=>v.capability_id===l.capability_id),g=m>=0?[...p.slice(0,m),l,...p.slice(m+1)]:[...p,l];await ul(g),d(`${l.capability_id} 已保存到注册表`,"success"),await Lo()}catch(u){d(String(u),"error")}});try{const u=(c=(await jn()).capabilities)==null?void 0:c[0];if(u){bm(u);const p=document.getElementById("logProject");p&&!p.value&&(p.value=u.memory_space||u.capability_id),document.getElementById("cfgMemorySpace").value=u.memory_space||u.capability_id}}catch{}await To(),await Jn(),await Lo()}function Pe(e){return e.toLocaleString("zh-CN")}function Im(e){return`
    <div class="token-chat-llm core-card">
      <h4 class="core-card-title">聊天 LLM 强度</h4>
      <div class="core-card-body">
        <p class="hint">控制聊天链路中的模型调用档位 · 每位用户各自保存 · guest / 普通用户均可修改</p>
        <label class="core-field"><span>档位</span>
          <select id="chatLlmLevel" class="panel-field-select" data-budget-level-key="chat_llm_level">${Ac.map(n=>`<option value="${n.value}"${e===n.value?" selected":""}>${n.label} — ${n.hint}</option>`).join("")}</select>
        </label>
        <span class="switch-config-key">chat_llm_level · off / light / heavy</span>
      </div>
    </div>`}function Bm(e){return`
    <div class="token-chat-memory core-card">
      <h4 class="core-card-title">聊天记忆模式</h4>
      <div class="core-card-body">
        <p class="hint">控制对话写入长期记忆的方式 · 每位用户各自保存</p>
        <label class="core-field"><span>模式</span>
          <select id="chatMemoryMode" class="panel-field-select" data-budget-level-key="chat_memory_mode">${xc.map(n=>`<option value="${n.value}"${e===n.value?" selected":""}>${n.label} — ${n.hint}</option>`).join("")}</select>
        </label>
        <span class="switch-config-key">chat_memory_mode · off / manual_only / batch</span>
      </div>
    </div>`}function Cm(e){return`
    <div class="token-chat-sticker core-card">
      <h4 class="core-card-title">聊天 · 表情</h4>
      <div class="core-card-body">
        <p class="hint">控制用户/念念表情包与语境学习 · 每位用户各自保存</p>
        <label class="core-field"><span>档位</span>
          <select id="chatStickerLevel" class="panel-field-select" data-budget-level-key="chat_sticker_level">${Mc.map(n=>`<option value="${n.value}"${e===n.value?" selected":""}>${n.label} — ${n.hint}</option>`).join("")}</select>
        </label>
        <span class="switch-config-key">chat_sticker_level · off / light / heavy</span>
      </div>
    </div>`}function Tm(e){return`
    <div class="token-proactive-burst core-card">
      <h4 class="core-card-title">主动开口 · 频率</h4>
      <div class="core-card-body">
        <p class="hint">控制每段安静期念念主动开口次数 · 需开启「念念主动对话」</p>
        <label class="core-field"><span>频率</span>
          <select id="proactiveChatBurst" class="panel-field-select" data-budget-level-key="proactive_chat_burst">${Oc.map(n=>`<option value="${n.value}"${e===n.value?" selected":""}>${n.label} — ${n.hint}</option>`).join("")}</select>
        </label>
        <span class="switch-config-key">proactive_chat_burst · once / few / unlimited</span>
      </div>
    </div>`}function Lm(e){return`
    <div class="token-proactive-disturb core-card">
      <h4 class="core-card-title">主动开口 · 打扰锁</h4>
      <div class="core-card-body">
        <p class="hint">手动锁定少打扰模式 · 覆盖 profile 自动调节</p>
        <label class="core-field"><span>模式</span>
          <select id="proactiveDisturbLock" class="panel-field-select" data-budget-level-key="proactive_disturb_lock">${Rc.map(n=>`<option value="${n.value}"${e===n.value?" selected":""}>${n.label} — ${n.hint}</option>`).join("")}</select>
        </label>
        <span class="switch-config-key">proactive_disturb_lock · auto / quiet</span>
      </div>
    </div>`}function Am(e,t){const n=(e==null?void 0:e.backend)==="memory"?"内存（NAS/Docker · 7 天自动清理）":"本地目录 state/system/token/",a=e==null?void 0:e.totals,o=(e==null?void 0:e.by_module)||[],i=(e==null?void 0:e.by_day)||[],s=Object.keys(so).map(m=>{const g=so[m];return G({dataKey:m,checked:(t==null?void 0:t[m])??!0,chinese:g.label,configKey:g.configKey})}).join(""),r=(t==null?void 0:t.chat_llm_level)??"heavy",c=(t==null?void 0:t.chat_memory_mode)??"manual_only",l=(t==null?void 0:t.chat_sticker_level)??"light",u=(t==null?void 0:t.proactive_chat_burst)??"few",p=(t==null?void 0:t.proactive_disturb_lock)??"auto";return ie("Token 用量统计与消耗能力管理",`
    ${M("Token 用量",`<p class="hint">存储：<strong>${n}</strong> · 保留 <strong>${(e==null?void 0:e.retention_days)??7}</strong> 天 · 用户 <code>${(e==null?void 0:e.user_id)||"—"}</code></p>
        <div class="token-stat-grid">
          <div class="token-stat"><span class="token-stat-label">总 Token</span><strong>${Pe((a==null?void 0:a.total_tokens)??0)}</strong></div>
          <div class="token-stat"><span class="token-stat-label">输入</span><strong>${Pe((a==null?void 0:a.prompt_tokens)??0)}</strong></div>
          <div class="token-stat"><span class="token-stat-label">输出</span><strong>${Pe((a==null?void 0:a.completion_tokens)??0)}</strong></div>
          <div class="token-stat"><span class="token-stat-label">调用次数</span><strong>${Pe((a==null?void 0:a.calls)??0)}</strong></div>
        </div>
        <div class="panel-tail-row panel-tail-row--inline">
          <button class="secondary" id="refreshTokenUsage" type="button">刷新</button>
        </div>`)}
    ${M("按脑区 / 模块",me(`<table class="core-table">
          <thead><tr><th>脑区模块</th><th>总 Token</th><th>输入</th><th>输出</th><th>次数</th></tr></thead>
          <tbody>${o.length?o.map(m=>`<tr><td>${m.module}</td><td>${Pe(m.total_tokens)}</td><td>${Pe(m.prompt_tokens)}</td><td>${Pe(m.completion_tokens)}</td><td>${m.calls}</td></tr>`).join(""):'<tr><td colspan="5" class="hint">暂无记录</td></tr>'}</tbody>
        </table>`))}
    ${M("按日汇总",me(`<table class="core-table">
          <thead><tr><th>日期</th><th>总 Token</th><th>次数</th></tr></thead>
          <tbody>${i.length?i.map(m=>`<tr><td>${m.date}</td><td>${Pe(m.total_tokens)}</td><td>${m.calls}</td></tr>`).join(""):'<tr><td colspan="3" class="hint">暂无记录</td></tr>'}</tbody>
        </table>`))}
    ${M("消耗能力管理",`<p class="hint">关闭后对应能力返回 403 或 Chat 内跳过（省 Token）。配置保存在 <code>users/{user}/token/budget.yaml</code>。</p>
        ${Im(r)}
        ${Bm(c)}
        ${Cm(l)}
        ${Tm(u)}
        ${Lm(p)}
        <div class="token-budget-list console-switch-list">${s}</div>
        <div class="panel-tail-row">
          <button class="btn-primary" id="saveTokenBudget" type="button">保存设置</button>
        </div>`)}
  `)}function xm(e){var t,n;(t=document.getElementById("refreshTokenUsage"))==null||t.addEventListener("click",()=>e()),(n=document.getElementById("saveTokenBudget"))==null||n.addEventListener("click",async()=>{const a={};document.querySelectorAll("input[data-budget-key]").forEach(l=>{const u=l.dataset.budgetKey;a[u]=l.checked});const o=document.getElementById("chatLlmLevel");o&&(a.chat_llm_level=o.value);const i=document.getElementById("chatMemoryMode");i&&(a.chat_memory_mode=i.value);const s=document.getElementById("chatStickerLevel");s&&(a.chat_sticker_level=s.value);const r=document.getElementById("proactiveChatBurst");r&&(a.proactive_chat_burst=r.value);const c=document.getElementById("proactiveDisturbLock");c&&(a.proactive_disturb_lock=c.value);try{await Lc(a),d("Token 设置已保存","success"),e()}catch(l){d(String(l),"error")}})}async function Mm(){const[e,t]=await Promise.all([Tc(7),ki()]);return{usage:e,budget:t.budget}}async function Om(e="subject"){return y(`/v1/wiki/graph?namespace=${encodeURIComponent(e)}`)}async function Rm(e){return y(`/v1/wiki/pages/${encodeURIComponent(e)}`)}async function is(e){return y(`/v1/wiki/resolve?vault_id=${encodeURIComponent(e)}`)}function Dm(e,t){if(!t.trim())return null;const n=e.nodes||{};if(n[t])return t;for(const[a,o]of Object.entries(n))if((o.label||"")===t||a.replace(/^ent:/,"")===t)return a;return null}async function ss(e){var a;const t=await Rm(e),n=typeof((a=t.meta)==null?void 0:a.vault_path)=="string"?t.meta.vault_path:void 0;return{pageId:e,text:`${t.title}

${t.summary||""}

atoms: ${(t.atom_ids||[]).join(", ")}`,vaultRelPath:n}}function rs(e){return`${z("vault")}?rel_path=${encodeURIComponent(e)}`}function Nm(e){const t=new Map,n=e.length||1,a=Math.max(120,n*28);return e.forEach((o,i)=>{const s=2*Math.PI*i/n;t.set(o,{x:320+a*Math.cos(s),y:240+a*Math.sin(s)})}),t}function Um(e){if(!e)return'<p class="hint">加载中…</p>';const t=Object.keys(e.nodes||{});if(!t.length)return'<p class="hint">暂无图谱节点（先 ingest 带 tag 的记忆）</p>';const n=Nm(t),a=(e.edges||[]).map(i=>{const s=n.get(i.from),r=n.get(i.to);return!s||!r?"":`<line x1="${s.x}" y1="${s.y}" x2="${r.x}" y2="${r.y}" stroke="#94a3b8" stroke-width="1" />`}).join(""),o=t.map(i=>{var l;const s=n.get(i),c=(((l=e.nodes[i])==null?void 0:l.label)||i.replace(/^ent:/,"")).slice(0,8);return`<g class="wiki-node" data-id="${i}"><circle cx="${s.x}" cy="${s.y}" r="18" fill="#3b82f6" opacity="0.85" />
        <text x="${s.x}" y="${s.y+32}" text-anchor="middle" font-size="11" fill="#e2e8f0">${c}</text></g>`}).join("");return`<svg id="wikiGraphSvg" viewBox="0 0 640 480" width="100%" height="420" style="background:#0f172a;border-radius:8px">${a}${o}</svg>`}function jm(e,t,n=null){const a=n?`<p id="wikiVaultLink"><a href="${rs(n)}">在资料库中查看</a></p>`:'<p id="wikiVaultLink" class="hint" hidden></p>';return ie("小织知识图谱 · 只读 subject 视图",`
    ${M("知识图谱",`<div id="wikiGraphWrap">${Um(e)}</div>
      ${a}
      <pre id="wikiPageDetail" class="core-preview hint">${t||"点击节点查看详情"}</pre>`,"点击节点查看概念页")}
  `)}async function Hm(){var e;(e=document.getElementById("wikiGraphSvg"))==null||e.addEventListener("click",async t=>{const n=t.target.closest(".wiki-node");if(!n)return;const a=n.getAttribute("data-id");if(a)try{const o=await ss(a),i=document.getElementById("wikiPageDetail");i&&(i.textContent=o.text);const s=document.getElementById("wikiVaultLink");s&&(o.vaultRelPath?(s.hidden=!1,s.className="",s.innerHTML=`<a href="${rs(o.vaultRelPath)}">在资料库中查看</a>`):(s.hidden=!0,s.innerHTML=""))}catch(o){fe(o)}})}async function qm(e,t){const n=t.get("page"),a=t.get("concept"),o=t.get("vault_id");let i=n;return!i&&a&&(i=Dm(e,a)),!i&&o&&(i=(await is(o)).page_id),i?ss(i):null}async function Vm(){return{graph:await Om("subject"),detail:""}}function cs(e,t,n){return{match:{proposed_subproject:e},target:{category:t,subproject:n}}}function ls(e){return e?JSON.stringify(e,null,2):"（勾选「加 routing rule」或点「以后都放这里」后写入 vault/_rules/routing_rules.yaml）"}function Gn(e){var i,s,r;const t=e.getAttribute("data-proposed")||"",n=((i=e.querySelector(".vault-pending-cat"))==null?void 0:i.value)||"学习",a=((s=e.querySelector(".vault-pending-sub"))==null?void 0:s.value)||t,o=!!((r=e.querySelector(".vault-pending-rule"))!=null&&r.checked);return{proposed:t,category:n,subproject:a,addRule:o}}function Yn(e){const{proposed:t,category:n,subproject:a,addRule:o}=Gn(e),i=e.querySelector(".vault-rule-preview");if(!i)return;const s=o?cs(t,n,a):null;i.textContent=ls(s)}async function Fm(e,t){const{proposed:n,category:a,subproject:o}=Gn(e),i=t||Gn(e).addRule;if(t){const s=e.querySelector(".vault-pending-rule");s&&(s.checked=!0),Yn(e)}await ju({action:"adopt",category:a,subproject:o,proposed_subproject:n,add_routing_rule:i?cs(n,a,o):void 0}),d(i?`已采纳 ${n} → ${a}/${o}，并写入 routing rule`:`已采纳 ${n} → ${a}/${o}`)}async function zm(e,t){const n=typeof t.wiki_page_id=="string"?t.wiki_page_id.trim():"";if(n)return`${z("wiki")}?page=${encodeURIComponent(n)}&vault_id=${encodeURIComponent(e)}`;try{const a=await is(e);return`${z("wiki")}?page=${encodeURIComponent(a.page_id)}&vault_id=${encodeURIComponent(e)}`}catch{return null}}async function ds(e){const t=e.replace(/^vault\//,""),n=t.startsWith("niannian/")?"niannian":"user",a=await Du(n,t),o=a.frontmatter||{},i=String(o.id||""),s=i?await zm(i,o):null;return{ns:n,detail:`${String(o.title||"")}

${a.body||""}`.trim(),relPath:t,vaultId:i,wikiHref:s}}function Wm(e,t){if(!e)return'<p class="hint">加载中…</p>';const n=e.categories||{},a=Object.keys(n).filter(o=>o!=="待确认");return a.length?a.map(o=>{const i=n[o]||{},s=Object.keys(i).map(r=>{const l=(i[r]||[]).map(u=>`<li><a href="#" class="vault-link" data-rel="${u.rel_path}">${u.title||u.vault_id}</a></li>`).join("");return`<div><strong>${r}</strong><ul>${l}</ul></div>`}).join("");return`<details open><summary>${o}</summary>${s}</details>`}).join(""):`<p class="hint">暂无 ${t} 资料（POST /v1/vault/import 入库）</p>`}function Km(e,t){if(!e.length)return'<p class="hint">无待确认条目</p>';const n=(t==null?void 0:t.categories)||[],a=n.filter(o=>o.id!=="待确认").map(o=>`<option value="${o.id}">${o.label||o.id}</option>`).join("");return e.map(o=>{var s;return(((s=n.find(r=>r.id!=="待确认"))==null?void 0:s.subprojects)||[]).map(r=>`<option value="${r}">${r}</option>`).join(""),`
      <div class="vault-pending-row core-card" data-proposed="${o.proposed_subproject}">
        <div class="core-card-body">
        <div><strong>${o.title}</strong> · 提议子项目：${o.proposed_subproject}</div>
        <div class="core-switch-row" style="margin-top:0.35rem">
          <label class="core-field"><span>分类</span><select class="vault-pending-cat">${a}</select></label>
          <label class="core-field"><span>子项目</span><input class="vault-pending-sub" value="${o.proposed_subproject}" /></label>
          ${G({className:"vault-pending-rule",chinese:"写入路由规则",configKey:"vault.routing_rule"})}
          <button type="button" class="vault-adopt-btn secondary core-btn-fixed" data-proposed="${o.proposed_subproject}">采纳</button>
          <button type="button" class="vault-rule-here-btn secondary core-btn-fixed" data-proposed="${o.proposed_subproject}">以后都放这里</button>
        </div>
        <pre class="vault-rule-preview core-preview hint">${ls(null)}</pre>
        </div>
      </div>`}).join("")}function Jm(e,t,n,a,o,i=null,s=null,r=null){const c=s&&(i!=null&&i.length)?`<p class="hint">文史批次 <code>${s}</code> · ${i.length} 篇 MD</p>`:s?`<p class="hint">文史批次 <code>${s}</code> · 暂无 MD</p>`:"",l=i!=null&&i.length?`<ul>${i.map(p=>`<li><a href="#" class="vault-link" data-rel="${p.rel_path}">${p.title}</a></li>`).join("")}</ul>`:"",u=t==="user"?M("待确认",`<p class="hint">采纳 → 写入 taxonomy 并移动 MD；「以后都放这里」= 采纳 + 写入 routing rule</p>
        <div id="vaultPending">${Km(a,o)}</div>`):"";return ie("user / niannian 资料库 · 点击条目预览 MD",`
    ${u}
    ${M("资料库",`<div class="core-switch-row" style="margin-bottom:0.75rem">
        <button type="button" id="vaultNsUser" class="${t==="user"?"active":""}">user</button>
        <button type="button" id="vaultNsNiannian" class="${t==="niannian"?"active":""}">niannian</button>
      </div>
      ${c}
      ${l}
      <div id="vaultTree">${Wm(e,t)}</div>
      <p id="vaultWikiLink" class="hint" ${r?"":"hidden"}>${r?`<a href="${r}">在图谱中查看</a>`:""}</p>
      <pre id="vaultDetail" class="core-preview hint">${n||"点击条目查看"}</pre>`)}
  `)}async function Gm(e,t){var n,a,o,i,s;(n=document.getElementById("vaultNsUser"))==null||n.addEventListener("click",()=>e("user")),(a=document.getElementById("vaultNsNiannian"))==null||a.addEventListener("click",()=>e("niannian")),(o=document.getElementById("vaultTree"))==null||o.addEventListener("click",async r=>{const c=r.target.closest(".vault-link");if(!c)return;r.preventDefault();const l=c.getAttribute("data-rel");if(l){l.startsWith("niannian/");try{const u=document.getElementById("vaultDetail"),p=document.getElementById("vaultWikiLink");if(u){const m=await ds(l);u.textContent=m.detail,p&&(m.wikiHref?(p.hidden=!1,p.innerHTML=`<a href="${m.wikiHref}">在图谱中查看</a>`):(p.hidden=!0,p.innerHTML=""))}}catch(u){fe(u)}}}),(i=document.getElementById("vaultPending"))==null||i.addEventListener("input",r=>{const c=r.target.closest(".vault-pending-row");c&&Yn(c)}),(s=document.getElementById("vaultPending"))==null||s.addEventListener("click",async r=>{var p;const c=r.target.closest(".vault-rule-here-btn"),l=r.target.closest(".vault-adopt-btn"),u=(p=c||l)==null?void 0:p.closest(".vault-pending-row");if(u)try{await Fm(u,!!c),await t()}catch(m){fe(m)}}),document.querySelectorAll(".vault-pending-row").forEach(r=>Yn(r))}async function us(e,t=null){const[n,a,o,i]=await Promise.all([Ru(e),e==="user"?Nu(e):Promise.resolve({items:[]}),e==="user"?Uu(e):Promise.resolve({categories:[]}),t?fetchVaultSearch({namespace:"niannian",cultural_batch_id:t,limit:20}):Promise.resolve({items:[],count:0})]);return{tree:n,detail:"",pending:a.items||[],taxonomy:o,batchHits:i.items||[]}}function R(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function Ve(e,t,n){return`<div class="vp-stage-panel${t?" active":""}" data-vp-panel="${e}">${n}</div>`}function Ym(e){const t=e||"";let n="角色";for(const i of t.split(`
`))if(i.startsWith("# ")&&!i.startsWith("## ")){n=i.slice(2).trim()||n;break}const a=Object.fromEntries(Et.map(i=>[i,""])),o=[...t.matchAll(/^## (.+)$/gm)];for(let i=0;i<o.length;i+=1){const s=o[i][1].trim();if(!(s in a))continue;const r=o[i].index+o[i][0].length,c=i+1<o.length?o[i+1].index:t.length;a[s]=t.slice(r,c).trim()}return{title:n,sections:a}}function Xm(e){const{sections:t}=Ym(e);return Et.map(n=>{const a=`vpProfileSection_${n}`;return`<label class="vp-profile-section">
      <span class="section-title">${R(n)}</span>
      <textarea id="${a}" rows="5" data-vp-profile-section="${R(n)}">${R(t[n]||"")}</textarea>
    </label>`}).join("")}function Qm(){return`<div class="vp-proactive-section">
    <p class="section-title">主动陪伴 profile · v0.26.1</p>
    <p class="hint">装填后取向保存在 <code>virtual_personas/{id}/memory/proactive_profile.json</code> · 编辑与调度稍后开放</p>
  </div>`}function Zm(e){return e?`<div class="vp-import-source">
    <p class="section-title">链接导入</p>
    <p class="hint">粘贴网页或图片 URL · 摘要写入 sources 与 deep_learn 记录 · 视频暂不支持</p>
    <div class="panel-inline-row">
      <input type="url" id="vpImportSourceUrl" placeholder="https://..." class="flex-grow" />
      <select id="vpImportSourceKind">
        <option value="web">网页</option>
        <option value="image">图片</option>
        <option value="video">视频（暂不支持）</option>
      </select>
      <button type="button" id="vpImportSourceBtn" class="secondary">导入</button>
    </div>
    <p class="hint" id="vpImportSourceHint"></p>
  </div>`:""}function ef(e,t,n){return e.length?`<div class="vp-equipped-skills">
    <p class="section-title">装备 Persona Skills</p>
    <p class="hint">装填 VP 后注入 character_block · 仅 kind=persona scope=vp</p>
    <div class="vp-skill-checks">${e.map(o=>{const i=t.includes(o.name)?" checked":"",s=n?"":" disabled",r=o.description?` · ${R(o.description)}`:"";return`<label class="vp-skill-check"><input type="checkbox" data-vp-equipped-skill="${R(o.name)}"${i}${s} /> ${R(o.name)}${r}</label>`}).join("")}</div>
  </div>`:`<div class="vp-equipped-skills">
      <p class="section-title">装备 Persona Skills</p>
      <p class="hint">暂无 kind=persona · scope=vp 的 Skill · 可在 Skill 页导入</p>
    </div>`}function tf(){const e=[];return document.querySelectorAll("[data-vp-equipped-skill]").forEach(t=>{const n=t;if(n.checked){const a=n.dataset.vpEquippedSkill||"";a&&e.push(a)}}),e}function nf(e,t,n,a,o=[],i=[]){const s=`${n}
      ${Zm(t)}
      ${ef(o,i,t)}
      <div class="vp-profile-sections">${Xm(e.profile_md||"")}</div>
      <div class="panel-tail-row">
        ${t?'<button type="button" id="vpSaveBtn" class="btn-primary">保存 profile</button>':""}
        ${t?'<button type="button" id="vpDeleteBtn" class="secondary">删除</button>':""}
        ${t?'<button type="button" id="vpEnterDeepLearnBtn" class="btn-primary">进入深度学习</button>':""}
        <button type="button" id="vpEquipBtn" class="secondary">装填到当前 Chat</button>
        <button type="button" id="vpUnequipBtn" class="secondary">移除装填</button>
      </div>
      ${Qm()}
      <details><summary>assembly 预览</summary><pre id="vpAssemblyPreview">${R(e.assembly_block||"")}</pre></details>`;return Ve("customize",a,s)}const ps=["customize","settings_md","deep_learn","daily","perfect","stickers","canon"],qe={customize:"定制",settings_md:"设定 MD",deep_learn:"深度学习",daily:"日常",perfect:"完美",stickers:"表情包",canon:"考据"};function gn(e){return ps.includes(e)}function Oa(e){const n=(e.startsWith("#")?e.slice(1):e).match(/^vp\/([^/]+)\/([^/]+)$/);if(!n)return{vpId:null,panel:null};const a=decodeURIComponent(n[1]),o=decodeURIComponent(n[2]);return gn(o)?{vpId:a,panel:o}:{vpId:a,panel:null}}function af(e,t){return`#vp/${encodeURIComponent(e)}/${t}`}function ms(e,t){const n=`${window.location.pathname}${window.location.search}`;if(e&&t){const a=af(e,t);window.location.hash!==a&&history.replaceState(null,"",`${n}${a}`);return}window.location.hash.startsWith("#vp/")&&history.replaceState(null,"",n)}function fs(e){return gn(e)?e:"customize"}function of(e,t){return e.length?`<p class="hint vp-dl-progress">进度 ${e.filter(a=>a.status==="done").length}/${e.length}</p>
    <ul class="vp-dl-task-list">
      ${e.map(a=>{const o=a.status==="done"?'<span class="hint ok">已完成</span>':'<span class="hint">待运行</span>',i=t?`<button type="button" class="secondary vp-dl-run-btn" data-task-id="${R(a.task_id)}">运行</button>`:"";return`<li class="vp-dl-task-row">
            <span class="vp-dl-task-topic">${R(a.topic)}</span>
            ${o}
            ${i}
          </li>`}).join("")}
    </ul>`:'<p class="hint">尚无任务 · 点击「开始 / 刷新任务」从 profile 生成</p>'}function Ao(){const e=document.getElementById("vpDlMockCheckbox");return(e==null?void 0:e.checked)??!0}function sf(e,t,n,a){const o=e!=="deep_learn",i=o?`<p class="hint">当前阶段为「${qe[e]||e}」· 请先在「定制」完成 profile 并进入深度学习</p>`:"",s=n&&!o?`<div class="panel-tail-row panel-tail-row--inline">${G({id:"vpDlMockCheckbox",checked:!0,chinese:"演示模式（mock）",configKey:"web_learning.yaml"})}</div>`:"",r=n&&!o?`<div class="panel-tail-row">
        <button type="button" id="vpDlRunAllBtn" class="btn-primary">一键全网深度学习</button>
        <button type="button" id="vpDlStartBtn" class="secondary">开始 / 刷新任务</button>
        <button type="button" id="vpDlCompleteBtn" class="secondary">完成深度学习</button>
        <button type="button" id="vpTrialChatBtn" class="secondary">试讲 Chat</button>
      </div>`:"",c=`<p class="hint">按章节联网检索公开资料，完善 assembly_master；不覆盖 profile 手稿。</p>
      <p class="hint">边界：定制 enrich 仅补空白节、不写 assembly · 深度学习详写 assembly · 考据 canon 待审 merge。</p>
      <p class="hint">未配置搜索时请在 <code>config/soul/web_learning.yaml</code> 配置 API Key，或勾选演示模式（mock）。</p>
      ${i}
      ${of(o?[]:t,n&&!o)}
      ${s}
      ${r}`;return Ve("deep_learn",a,c)}function rf(e){var o;const t=String(e||""),n=/^## Daily notes\s*\n([\s\S]*?)(?=^## |\Z)/m,a=t.match(n);return((o=a==null?void 0:a[1])==null?void 0:o.trim())||""}function cf(e){return e.split(`
`).filter(t=>/^\s*\[/.test(t)).length}function lf(e){if(!e.trim())return'<p class="hint vp-daily-empty">尚无 Daily notes · 装填后在 Chat 相处将自动增量写入 assembly_master</p>';const t=e.length>1600?`${e.slice(0,1600)}
…`:e;return`<p class="hint vp-daily-stats">Daily notes · ${cf(e)} 条相处记录</p>
    <pre class="vp-daily-preview">${R(t)}</pre>`}function df(e,t,n,a,o,i){const s=e!=="daily"&&e!=="perfect",r=s?`<p class="hint">当前阶段为「${qe[e]||e}」· 完成深度学习并进入日常阶段后可用</p>`:"",c=n===a,l=o&&!s?`<div class="panel-tail-row">
        <button type="button" id="vpDailyEquipBtn" class="btn-primary">${c?"已装填 · 重新装填":"装填到当前 Chat"}</button>
        <button type="button" id="vpDailyUnequipBtn" class="secondary"${c?"":" disabled"}>移除装填</button>
        <button type="button" id="vpDailyOpenChatBtn" class="secondary">打开 Chat</button>
        ${e==="daily"?'<button type="button" id="vpDailyToPerfectBtn" class="secondary">进入完美蒸馏</button>':""}
      </div>`:"",p=`<p class="hint">日常阶段：正式装填、主动陪伴与 L2 积累 · 增量蒸馏在 Chat 回合结束时自动执行</p>
      ${r}
      ${c?'<p class="hint ok">当前 Chat 已装填此角色 · 对话将增量写入 Daily notes</p>':'<p class="hint">装填后 Chat 注入 assembly_master · 每轮相处摘要 append 到 Daily notes</p>'}
      ${s?"":lf(t)}
      ${l}`;return Ve("daily",i,p)}function uf(){return z("chat")}const hs=[{key:"include_full_l2",label:"L2 全量",hint:"assembly_master.md"},{key:"include_stickers",label:"贴纸清单",hint:"stickers/manifest.json"},{key:"include_proactive_profile",label:"主动陪伴 profile",hint:"memory/proactive_profile.json"},{key:"include_deep_learn_runs",label:"深度学习记录",hint:"deep_learn/runs.json"}];function pf(e){return e==="daily"||e==="perfect"}function mf(e,t){return hs.map(n=>{const a=`vpExportOpt_${n.key}`,o=t?"":" disabled",i=e[n.key]?" checked":"";return`<label class="vp-export-opt">
      <input type="checkbox" id="${a}" data-vp-export-opt="${n.key}"${i}${o} />
      <span>${R(n.label)}</span>
      <span class="hint">${R(n.hint)}</span>
    </label>`}).join("")}function ff(e){if(!e)return'<p class="hint" id="vpPerfectPreviewHint">勾选导出项后点击「预览」</p>';const t=Object.keys(e).filter(o=>!["vp_id","label","stage","export_options"].includes(o)),n=e.public_summary?`<details open><summary>public_summary</summary><pre class="vp-export-preview">${R(e.public_summary)}</pre></details>`:"",a=t.map(o=>{const i=e[o],s=typeof i=="string"?i:i!=null?JSON.stringify(i,null,2):"",r=s.length>1200?`${s.slice(0,1200)}
…`:s;return`<details><summary>${R(o)}</summary><pre class="vp-export-preview">${R(r)}</pre></details>`}).join("");return`${n}${a}`}function hf(e){return e.length?`<ul class="vp-export-history">${e.map(n=>{var i;const a=(i=n.files)!=null&&i.length?n.files.join(", "):"—",o=n.created_at?R(n.created_at):"—";return`<li class="vp-export-history-row">
        <strong>${R(n.bundle_id)}</strong>
        <span class="hint">${o}</span>
        <span class="hint">${R(a)}</span>
      </li>`}).join("")}</ul>`:'<p class="hint">尚无导出记录 · 预览后点击「导出并写盘」</p>'}function Ln(){const e={include_full_l2:!1,include_stickers:!1,include_proactive_profile:!1,include_deep_learn_runs:!1};for(const t of hs){const n=document.getElementById(`vpExportOpt_${t.key}`);n&&(e[t.key]=n.checked)}return e}function vf(e,t,n,a,o){const i=!pf(e),s=i?`<p class="hint">当前阶段为「${qe[e]||e}」· 需 daily 或 perfect 才可完美蒸馏</p>`:"",r=(n==null?void 0:n.export_options)??{include_full_l2:!1,include_stickers:!1,include_proactive_profile:!1,include_deep_learn_runs:!1},c=a&&!i?`<div class="panel-tail-row">
        <button type="button" id="vpPerfectPreviewBtn" class="secondary">预览</button>
        <button type="button" id="vpPerfectWriteBtn" class="btn-primary">导出并写盘</button>
        <button type="button" id="vpPerfectDownloadBtn" class="secondary"${n?"":" disabled"}>下载 bundle.zip</button>
        <button type="button" id="vpPerfectDailyTabBtn" class="secondary">查看 Daily notes</button>
      </div>`:"",l=`<p class="hint">完美蒸馏：勾选 export_options · 预览脱敏摘要 · 写 export_bundle · 下载或导入</p>
      <p class="hint">数据源：assembly_master · Daily notes 相处记录 · 可选 L2 / 贴纸 / 深度学习记录</p>
      ${s}
      <div class="vp-export-options">${mf(r,a&&!i)}</div>
      <div class="vp-export-preview-wrap">${ff(n)}</div>
      ${c}
      <div class="vp-export-history-section">
        <p class="section-title">历史导出</p>
        ${hf(t)}
      </div>`;return Ve("perfect",o,l)}function gf(){return`<div class="vp-mode-hint">
    <p class="section-title">装填后 Chat 模式</p>
    <p class="hint">装填虚拟人物后，Chat 模式仅可选 <strong>companion</strong>（陪伴）、<strong>work</strong>（工作）、<strong>auto</strong>（自动）。</p>
    <p class="hint"><strong>auto</strong> 在 companion 与 work 之间切换，不会进入正常 / 教育模式；表情包优先使用本 VP 私有贴纸 pack，无贴纸时回退念念 mode 套。</p>
  </div>`}function yf(e){return e?`<div class="vp-sticker-search">
    <p class="section-title">联网查找 · v0.28</p>
    <div class="row vp-sticker-search-row">
      <input id="vpStickerSearchQuery" type="text" placeholder="搜索关键词（留空则用角色名）" />
      <input id="vpStickerSearchMeme" type="text" placeholder="梗图关键词，逗号分隔" />
      <button type="button" id="vpStickerSearchBtn" class="btn-primary">查找</button>
    </div>
    <p id="vpStickerSearchHint" class="hint">命中 allowlist 的图片 URL · Token 页可关闭 VP 贴纸联网查找</p>
    <ul id="vpStickerSearchResults" class="vp-sticker-search-results"></ul>
  </div>`:'<p class="hint">只读 · 联网查找不可用</p>'}function bf(e,t){return e.length?e.map((n,a)=>{const o=n.title||n.url,i=o.slice(0,40);return`<li class="vp-sticker-search-row-item">
        <span class="vp-sticker-search-title">${R(o)}</span>
        <span class="hint">${R(n.url)}</span>
        <button type="button" class="secondary vp-sticker-import-btn" data-sticker-url="${R(n.url)}" data-sticker-label="${R(i)}" data-sticker-idx="${a}">导入</button>
      </li>`}).join(""):`<li class="hint">未找到候选 · query: ${R(t)}</li>`}function wf(e,t,n){var p;const a=(e==null?void 0:e.sticker_count)??0,o=!!(e!=null&&e.image_gen_enabled),i=(p=e==null?void 0:e.stickers)!=null&&p.length?e.stickers.map(m=>`<li class="vp-sticker-row">
            <span class="vp-sticker-label">${R(m.label||m.slug)}</span>
            <span class="hint">${R(m.slug)}${m.source?` · ${R(m.source)}`:""}</span>
          </li>`).join(""):'<li class="hint vp-sticker-empty">暂无 VP 贴纸 · 装填后将回退念念 mode 套</li>',s=!o,c=t?`<button type="button" id="vpStickerGenerateBtn" class="secondary" title="${R(s?"未配置生图 API，可使用联网查找或上传":"AI 生成 Q 版表情")}"${s?" disabled":""}>AI 生成</button>`:"",u=`<div class="vp-stickers-section">
      <div class="vp-stickers-header">
        <p class="section-title">VP 贴纸 <span class="hint">(${a})</span></p>
        <div class="vp-stickers-actions">
          ${c}
          ${t?'<button type="button" id="vpStickerAddBtn" class="secondary">从用户包添加</button>':""}
        </div>
      </div>
      <ul class="vp-sticker-list">${i}</ul>
      ${yf(t)}
      ${gf()}
    </div>`;return Ve("stickers",n,u)}function _f(e){return Ve("canon",e,`<p class="hint">联网考据 · 同人策略 · 来源链接 · v0.29 M2 后续开放</p>
    <p class="hint">canon-lookup API 将写入待审候选，确认后 merge 进 assembly</p>`)}const kf=[{kind:"profile",label:"profile.md",path:"profile-md"},{kind:"assembly_master",label:"assembly_master.md",path:"assembly-master"}];function $f(e,t){const a=`<p class="hint">整文件 Markdown 直编 · 保存走 PUT + If-Match · 不触发 enrich</p>
    <nav class="vp-md-file-tabs" aria-label="MD 文件">${kf.map(o=>`<button type="button" class="vp-md-file-tab${o.kind==="profile"?" active":""}" data-vp-md-file="${o.kind}">${R(o.label)}</button>`).join("")}</nav>
    <textarea id="vpMdEditor" rows="18" class="vp-md-editor" spellcheck="false"></textarea>
    <p class="hint" id="vpMdEditorHint"></p>
    <div class="panel-tail-row">
      ${e?'<button type="button" id="vpMdReloadBtn" class="secondary">重新加载</button>':""}
      ${e?'<button type="button" id="vpMdSaveBtn" class="btn-primary">保存 MD</button>':""}
    </div>`;return Ve("settings_md",t,a)}function An(){const e=document.querySelector(".vp-md-file-tab.active");return(e==null?void 0:e.dataset.vpMdFile)==="assembly_master"?"assembly_master":"profile"}function Sf(e){document.querySelectorAll(".vp-md-file-tab").forEach(t=>{t.classList.toggle("active",t.dataset.vpMdFile===e)})}let pe="customize",oe="customize",Ce=[],xo=null,Vt=[],Mo=null,Ne=null,pt=null,xn=null,Ra=[],Ft="";function Ef(e){return e==="daily"||e==="perfect"}function Pf(e){const t=gn(e)?qe[e]:e;return`<span class="vp-stage-badge hint ok">${R(t)}</span>`}function If(){const e={};for(const t of Et){const n=document.getElementById(`vpProfileSection_${t}`);e[t]=(n==null?void 0:n.value)??""}return e}function Oo(e){const t=e||"",n=Object.fromEntries(Et.map(o=>[o,""])),a=[...t.matchAll(/^## (.+)$/gm)];for(let o=0;o<a.length;o+=1){const i=a[o][1].trim();if(!(i in n))continue;const s=a[o].index+a[o][0].length,r=o+1<a.length?a[o+1].index:t.length;n[i]=t.slice(s,r).trim()}for(const o of Et){const i=document.getElementById(`vpProfileSection_${o}`);i&&(i.value=n[o]??"")}}function Bf(){const e=C().userRole;return e==="owner"||e==="authorized_corrector"}function Cf(e){return e==="cloud"?'<span class="hint ok">可上云</span>':'<span class="hint">仅本地</span>'}function Tf(e){return e?'<span class="hint ok">有头像</span>':""}function Lf(e){return e==null||e<=0?"":`<span class="hint ok">${e} 贴纸</span>`}function Af(e){return`<nav class="vp-stage-tabs" aria-label="VP 工作台">${ps.map(n=>`<button type="button" class="vp-stage-tab${e===n?" active":""}" data-vp-tab="${n}">${qe[n]}</button>`).join("")}</nav>`}function dt(e,t){oe=e,document.querySelectorAll(".vp-stage-tab").forEach(n=>{n.classList.toggle("active",n.dataset.vpTab===e)}),document.querySelectorAll(".vp-stage-panel").forEach(n=>{n.classList.toggle("active",n.dataset.vpPanel===e)}),t&&ms(t,e)}function xf(e){const t=Oa(window.location.hash);return t.vpId&&e&&t.vpId===e&&t.panel?(oe=t.panel,t.panel):null}async function Mf(){var n;const e=await oi();let t=null;try{const a=await K(),i=(n=(await ya(a)).meta)==null?void 0:n.equipped_vp_id;t=i!=null&&String(i).trim()?String(i):null}catch{t=null}return{personas:e.personas||[],userId:e.user_id,equippedVpId:t}}function Of(e,t,n,a,o=null){const i=Bf(),s=e.length?e.map(u=>{const p=u.id===a?" active":"",m=u.id===n?' <span class="hint ok">已装填</span>':"";return`<button type="button" class="vp-card${p}" data-vp-id="${R(u.id)}">
            <strong>${R(u.label||u.id)}</strong>
            <span class="hint">${R(u.id)}</span>
            ${Cf(u.storage_tier)}${Tf(u.has_avatar)}${Lf(u.sticker_count)}${m}
          </button>`}).join(""):'<p class="hint">暂无虚拟人物，点击下方新建。</p>',r=i?`<div class="row vp-create">
        <input id="vpNewId" placeholder="id（如 sherlock_vp）" />
        <input id="vpNewLabel" placeholder="显示名" />
        <button type="button" id="vpCreateBtn" class="btn-primary">新建</button>
      </div>
      <div class="row vp-import">
        <input type="file" id="vpImportFile" accept=".zip,application/zip" hidden />
        <button type="button" id="vpImportBtn" class="secondary">导入角色包 (.zip)</button>
      </div>`:'<p class="hint">当前角色只读，不可新建或编辑。</p>',c=t?`<div class="vp-avatar-row">
        <div id="vpAvatarPreview" class="vp-avatar-preview" aria-hidden="true">${t.has_avatar?"":R((t.label||t.id).slice(0,1))}</div>
        <div class="vp-avatar-actions">
          <p id="vpAvatarHint" class="hint">${t.has_avatar?"已设置头像":"未设置时将使用念念默认图"}</p>
          ${i?'<input type="file" id="vpAvatarFile" accept="image/png,image/jpeg,image/webp" hidden />':""}
          ${i?'<button type="button" id="vpAvatarUploadBtn" class="secondary">上传头像</button>':""}
          ${i?'<button type="button" id="vpAvatarRefetchBtn" class="secondary">重检头像</button>':""}
          ${i?'<button type="button" id="vpAvatarRemoveBtn" class="secondary" '+(t.has_avatar?"":"disabled")+">移除头像</button>":""}
        </div>
      </div>`:"",l=t?`<div class="vp-editor">
        <h3>${R(t.label||t.id)} ${Pf(pe)}</h3>
        ${Af(oe)}
        ${nf(t,i,c,oe==="customize",vpPersonaSkillsCache,Ra)}
        ${$f(i,oe==="settings_md")}
        ${sf(pe,Ce,i,oe==="deep_learn")}
        ${df(pe,Ft,n,t.id,i,oe==="daily")}
        ${vf(pe,Vt,Ne,i,oe==="perfect")}
        ${wf(o,i,oe==="stickers")}
        ${_f(oe==="canon")}
      </div>`:'<p class="hint">选择左侧卡片查看或编辑。</p>';return ie("per-user 角色卡 · 装填后 Chat 代入性格与语气",M("虚拟人物",`${r}
      <div class="vp-layout">
        <div class="vp-list">${s}</div>
        <div class="vp-detail">${l}</div>
      </div>`,"PersonaPack 自动 detach"))}async function Rf(e,t){var k,E,I,_,P,f,w,S,B,L,D,T,H,te,ce,X,Z,b,A,O,J,le,ne,xe,$e,Se,Ee,ze,nt;const n=()=>{if(!e)return;const h=Oa(window.location.hash);h.vpId===e&&h.panel&&h.panel!==oe&&dt(h.panel,e)};if(window.addEventListener("hashchange",n),document.querySelectorAll(".vp-stage-tab").forEach(h=>{h.addEventListener("click",()=>{const $=h.dataset.vpTab;!$||!gn($)||dt($,e)})}),(k=document.getElementById("vpCreateBtn"))==null||k.addEventListener("click",async()=>{const h=document.getElementById("vpNewId").value.trim(),$=document.getElementById("vpNewLabel").value.trim();if(!h||!$){d("请填写 id 与显示名","error");return}try{await Pr({id:h,label:$}),d("已创建","success"),t(h)}catch(x){d(String(x),"error")}}),(E=document.getElementById("vpImportBtn"))==null||E.addEventListener("click",()=>{var h;(h=document.getElementById("vpImportFile"))==null||h.click()}),(I=document.getElementById("vpImportFile"))==null||I.addEventListener("change",async()=>{var x;const h=document.getElementById("vpImportFile"),$=(x=h==null?void 0:h.files)==null?void 0:x[0];if(h&&(h.value=""),!!$)try{const W=await Ur($);d(`已导入：${W.vp_id}`,"success"),t(W.vp_id)}catch(W){d(String(W),"error")}}),document.querySelectorAll(".vp-card").forEach(h=>{h.addEventListener("click",()=>{const $=h.dataset.vpId;$&&t($)})}),!e)return;e&&ms(e,oe);let a=null;const o=document.getElementById("vpAvatarPreview"),i=document.getElementById("vpAvatarHint"),s=document.getElementById("vpAvatarRemoveBtn"),r=document.getElementById("vpAvatarFile"),c=((_=o==null?void 0:o.textContent)==null?void 0:_.trim())||"?",l=(h,$)=>{!o||!i||!s||(si(a),a=$,h&&$?(o.innerHTML=`<img src="${$}" alt="VP 头像" style="width:100%;height:100%;object-fit:cover;display:block" />`,i.textContent="已设置头像 · Chat 装填后将显示",s.disabled=!1):(o.textContent=c,i.textContent="未设置时将使用念念默认图",s.disabled=!0))},u=async()=>{if(o)try{const h=await ii(e);l(!!h,h)}catch(h){i&&(i.textContent=`无法读取头像：${String(h)}`),l(!1,null)}};o&&await u(),(P=document.getElementById("vpAvatarUploadBtn"))==null||P.addEventListener("click",()=>{r==null||r.click()}),r==null||r.addEventListener("change",async()=>{var $;const h=($=r.files)==null?void 0:$[0];if(r.value="",!!h)try{await Cr(e,h),d("头像已上传","success"),await u(),t(e)}catch(x){d(String(x),"error")}}),(f=document.getElementById("vpAvatarRefetchBtn"))==null||f.addEventListener("click",async()=>{try{const h=await Lr(e,{fetch_avatar:!0,mock:!0,max_results:3});if(h.has_avatar?(d("已从联网结果抓取头像","success"),await u(),t(e)):d("联网 enrich 完成，未找到可用头像","success"),h.profile_preview){Oo(h.profile_preview);const $=document.getElementById("vpAssemblyPreview");$&&h.assembly_block&&($.textContent=h.assembly_block)}}catch(h){d(String(h),"error")}}),s==null||s.addEventListener("click",async()=>{try{await Tr(e),d("头像已移除","success"),l(!1,null),t(e)}catch(h){d(String(h),"error")}}),(w=document.getElementById("vpImportSourceBtn"))==null||w.addEventListener("click",async()=>{const h=document.getElementById("vpImportSourceBtn"),$=document.getElementById("vpImportSourceUrl"),x=document.getElementById("vpImportSourceKind"),W=document.getElementById("vpImportSourceHint");if(!h||!$||!x)return;const Me=$.value.trim();if(!Me){d("请输入 URL","error");return}const yn=x.value||"web";h.disabled=!0,W&&(W.textContent="导入中…");try{const Oe=await Ar(e,{url:Me,kind:yn});W&&(W.textContent=Oe.run_id?`已导入 · run ${Oe.run_id}`:"已导入"),d("链接已导入","success"),t(e)}catch(Oe){const at=String(Oe);W&&(W.textContent=at),d(at,"error")}finally{h.disabled=!1}}),(S=document.getElementById("vpSaveBtn"))==null||S.addEventListener("click",async()=>{const h=If(),$=tf();try{const[x]=await Promise.all([Ir(e,{profile_sections:h}),Ka(e,{equipped_skills:$})]);Ra=$,Oo(x.profile_md||"");const W=document.getElementById("vpAssemblyPreview");W&&(W.textContent=x.assembly_block||""),d("已保存","success")}catch(x){d(String(x),"error")}});const p=document.getElementById("vpMdEditor"),m=document.getElementById("vpMdEditorHint"),g=h=>{xn=h,h.file,p&&(p.value=h.content||""),m&&(m.textContent=`etag ${h.etag}`)},v=async(h=An())=>{if(p)try{const $=await li(e,h);g($)}catch($){m&&(m.textContent=String($)),d(String($),"error")}};document.querySelectorAll(".vp-md-file-tab").forEach(h=>{h.addEventListener("click",async()=>{const $=h.dataset.vpMdFile;$&&(Sf($),await v($))})}),(B=document.getElementById("vpMdReloadBtn"))==null||B.addEventListener("click",async()=>{await v(An()),d("已重新加载","success")}),(L=document.getElementById("vpMdSaveBtn"))==null||L.addEventListener("click",async()=>{if(!p||!xn){d("请先加载 MD","error");return}const h=An();try{const $=await jr(e,h,p.value,xn.etag);g($),d("MD 已保存","success"),h==="profile"&&t(e)}catch($){const x=String($);d(x,"error"),(x.includes("409")||x.includes("md_stale"))&&await v(h)}}),p&&v("profile"),(D=document.getElementById("vpDeleteBtn"))==null||D.addEventListener("click",async()=>{if(window.confirm(`删除虚拟人物 ${e}？`))try{await Br(e),d("已删除","success"),t(null)}catch(h){d(String(h),"error")}}),(T=document.getElementById("vpEquipBtn"))==null||T.addEventListener("click",async()=>{try{const h=await K(),$=await za(h,e);d(`已装填：${$.label||$.equipped_vp_id}`,"success"),t(e)}catch(h){d(String(h),"error")}}),(H=document.getElementById("vpUnequipBtn"))==null||H.addEventListener("click",async()=>{try{const h=await K();await Wa(h),d("已移除装填","success"),t(e)}catch(h){d(String(h),"error")}}),(te=document.getElementById("vpStickerAddBtn"))==null||te.addEventListener("click",()=>{d("从用户包添加 · v0.28","success")}),(ce=document.getElementById("vpStickerGenerateBtn"))==null||ce.addEventListener("click",async()=>{const h=document.getElementById("vpStickerGenerateBtn");if(!(!h||h.disabled)){h.disabled=!0;try{const $=await $r(e,{count:4});d(`已生成 ${$.count} 张贴纸`,"success"),t(e)}catch($){d(String($),"error")}finally{h.disabled=!1}}}),(X=document.getElementById("vpStickerSearchBtn"))==null||X.addEventListener("click",async()=>{var Oe,at,Na;const h=document.getElementById("vpStickerSearchBtn"),$=document.getElementById("vpStickerSearchResults"),x=document.getElementById("vpStickerSearchHint");if(!h||!$)return;const W=((Oe=document.getElementById("vpStickerSearchQuery"))==null?void 0:Oe.value.trim())||void 0,Me=((at=document.getElementById("vpStickerSearchMeme"))==null?void 0:at.value.trim())||"",yn=Me?Me.split(/[,，]/).map(de=>de.trim()).filter(Boolean):void 0;h.disabled=!0,x&&(x.textContent="查找中…");try{const de=await Sr(e,{query:W,meme_keywords:yn});$.innerHTML=bf(de.candidates||[],de.query||""),x&&(x.textContent=(Na=de.meme_keywords)!=null&&Na.length?`query: ${de.query} · meme: ${de.meme_keywords.join(", ")}`:`query: ${de.query}`),document.querySelectorAll(".vp-sticker-import-btn").forEach(ot=>{ot.addEventListener("click",async()=>{const Ua=ot.dataset.stickerUrl,$s=ot.dataset.stickerLabel;if(Ua){ot.disabled=!0;try{await Er(e,{url:Ua,label:$s||void 0}),d("贴纸已导入","success"),t(e)}catch(Ss){d(String(Ss),"error"),ot.disabled=!1}}})})}catch(de){x&&(x.textContent=String(de)),$.innerHTML="",d(String(de),"error")}finally{h.disabled=!1}}),(Z=document.getElementById("vpEnterDeepLearnBtn"))==null||Z.addEventListener("click",async()=>{try{const h=await bn(e);pe=h.stage,Ce=h.tasks,dt("deep_learn",e),d("已进入深度学习","success"),t(e)}catch(h){d(String(h),"error")}}),(b=document.getElementById("vpDlStartBtn"))==null||b.addEventListener("click",async()=>{try{const h=await bn(e);pe=h.stage,Ce=h.tasks,d(`已加载 ${h.tasks.length} 项任务`,"success"),t(e)}catch(h){d(String(h),"error")}}),(A=document.getElementById("vpDlRunAllBtn"))==null||A.addEventListener("click",async()=>{const h=document.getElementById("vpDlRunAllBtn");if(h){h.disabled=!0;try{const $=Ao(),x=await Rr(e,{mock:$});Ce=(await bn(e)).tasks;const Me=x.failed>0?"error":"success";d(`全网深度学习：完成 ${x.completed} 项，失败 ${x.failed} 项`,Me),t(e)}catch($){d(String($),"error")}finally{h.disabled=!1}}}),(O=document.getElementById("vpDlCompleteBtn"))==null||O.addEventListener("click",async()=>{try{const h=await Or(e);pe=h.stage,Ce=[],d(`深度学习完成 → ${qe[fs(h.stage)]}`,"success"),t(e)}catch(h){d(String(h),"error")}}),(J=document.getElementById("vpTrialChatBtn"))==null||J.addEventListener("click",()=>{const h=`${z("chat")}?trial_vp=${encodeURIComponent(e)}`;d("试讲模式 · 跳转 Chat","success"),window.history.pushState(null,"",h),window.dispatchEvent(new PopStateEvent("popstate"))}),document.querySelectorAll(".vp-dl-run-btn").forEach(h=>{h.addEventListener("click",async()=>{const $=h.dataset.taskId;if($){h.disabled=!0;try{const x=await ri(e,{task_id:$,mock:Ao()});Ce=Ce.map(W=>W.task_id===$?{...W,status:"done",last_run_id:x.run_id}:W),d(`已完善：${x.topic}`,"success"),t(e)}catch(x){d(String(x),"error")}finally{h.disabled=!1}}})}),(le=document.getElementById("vpPerfectPreviewBtn"))==null||le.addEventListener("click",async()=>{const h=Ln();try{const $=await Ja(e,h,!1);Ne=$.preview,pt=$.bundle_id,d("预览已更新","success"),t(e)}catch($){d(String($),"error")}}),(ne=document.getElementById("vpPerfectWriteBtn"))==null||ne.addEventListener("click",async()=>{const h=Ln();try{const $=await Ja(e,h,!0);Ne=$.preview,pt=$.bundle_id,d(`已写盘：${$.bundle_id||"bundle"}`,"success"),t(e)}catch($){d(String($),"error")}}),(xe=document.getElementById("vpPerfectDownloadBtn"))==null||xe.addEventListener("click",()=>{if(!Ne){d("请先预览或导出","error");return}const h=Ln(),$=Yp(Ne,pt,h),x=Ne.vp_id||e;ts($,`${x}-${pt||"preview"}.zip`),d("bundle.zip 已开始下载","success")}),($e=document.getElementById("vpPerfectDailyTabBtn"))==null||$e.addEventListener("click",()=>{dt("daily",e)}),(Se=document.getElementById("vpDailyEquipBtn"))==null||Se.addEventListener("click",async()=>{try{const h=await K(),$=await za(h,e);d(`已装填：${$.label||$.equipped_vp_id}`,"success"),t(e)}catch(h){d(String(h),"error")}}),(Ee=document.getElementById("vpDailyUnequipBtn"))==null||Ee.addEventListener("click",async()=>{try{const h=await K();await Wa(h),d("已移除装填","success"),t(e)}catch(h){d(String(h),"error")}}),(ze=document.getElementById("vpDailyOpenChatBtn"))==null||ze.addEventListener("click",()=>{window.history.pushState(null,"",uf()),window.dispatchEvent(new PopStateEvent("popstate"))}),(nt=document.getElementById("vpDailyToPerfectBtn"))==null||nt.addEventListener("click",async()=>{try{pe=(await Ka(e,{stage:"perfect"})).stage,d(`已进入完美蒸馏 · ${qe.perfect}`,"success"),dt("perfect",e),t(e)}catch(h){d(String(h),"error")}})}async function Df(e){if(pe=await Hr(e),xo!==e&&(Ce=[],xo=e,Ne=null,pt=null),xf(e)||(oe=fs(pe)),Mo!==e&&(Vt=[],Mo=e),Ef(pe)){try{Vt=(await Nr(e)).exports||[]}catch{Vt=[]}try{const i=await li(e,"assembly_master");Ft=rf(i.content||"")}catch{Ft=""}}else Ft="";const[n,a,o]=await Promise.all([_a(e),Mr(e),Aa()]);return Ra=Array.isArray(a.pack.equipped_skills)?a.pack.equipped_skills:[],vpPersonaSkillsCache=o.filter(i=>i.kind==="persona"&&i.scope==="vp"),n}async function Nf(e){return kr(e)}function Uf(){return Oa(window.location.hash).vpId}async function jf(){return y("/v1/admin/users")}async function Hf(e){return N("/v1/admin/users",{username:e})}async function qf(e){return y(`/v1/admin/users/${encodeURIComponent(e)}`,{method:"DELETE"})}async function Ro(e,t){return y(`/v1/admin/users/${encodeURIComponent(e)}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})}async function Vf(){return y("/v1/admin/tenant-auth")}async function Ff(e){return y("/v1/admin/tenant-auth",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({login_enabled:e})})}async function zf(e,t){return N(`/v1/admin/users/${encodeURIComponent(e)}/fetch/${encodeURIComponent(t)}`,{})}async function Wf(e,t){return y(`/v1/admin/users/${encodeURIComponent(e)}/fetch/${encodeURIComponent(t)}`)}async function vs(){return y("/v1/admin/tenant-init")}async function Kf(e,t){return N("/v1/admin/tenant-init/export",{label:e,set_active:t})}async function Jf(e,t){return y("/v1/admin/tenant-init/active",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({init_id:e,label:null})})}function he(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/"/g,"&quot;")}function Gf(e){var i;const t=(i=e.cloud_fetch_policy)!=null&&i.virtual_personas?"checked":"",n=e.bootstrap||{},a=e.last_fetch_status||"—",o=e.last_login_at||"—";return`
    <tr data-user="${he(e.username)}">
      <td><strong>${he(e.username)}</strong></td>
      <td>${he(e.status)}</td>
      <td>${he(o)}</td>
      <td>${he(a)}</td>
      <td>
        <label><input type="checkbox" class="policy-vp" ${t} /> VP 拉取</label>
      </td>
      <td class="row-actions">
        <button class="secondary btn-fetch-vp" type="button">获取 VP</button>
        <span class="fetch-status hint"></span>
        <button class="secondary btn-force-reinit" type="button">强制重初始化</button>
        <button class="secondary btn-remove" type="button">移除</button>
      </td>
      <td class="hint">${n.bootstrapped_at?`bootstrap ${n.init_id||""}`:"未 bootstrap"}</td>
    </tr>`}function Yf(e,t,n){const a=n==null?void 0:n.active,o=(n==null?void 0:n.archives)||[],i=o.map(s=>`<option value="${he(s.id)}">${he(s.label||s.id)}</option>`).join("");return ie("租户用户管理与初始化包",`
    ${M("租户登录",`${G({id:"tenantLoginEnabled",checked:t,chinese:"允许租户登录",configKey:"tenant_login_enabled"})}
      <p class="hint">关闭后租户无法登录 · fetch 触发亦不可用</p>`)}
    ${M("用户",`${me(`<table class="core-table data-table" id="adminUsersTable">
          <thead><tr>
            <th>用户名</th><th>状态</th><th>最近登录</th><th>拉取状态</th><th>策略</th><th>操作</th><th>初始化</th>
          </tr></thead>
          <tbody>${e.map(s=>Gf(s)).join("")||"<tr><td colspan='7'>（无租户）</td></tr>"}</tbody>
        </table>`)}
      <div class="core-inline-actions" style="margin-top:0.75rem">
        <input id="newUsername" placeholder="新用户名 (a-z0-9_-, 3-32)" />
        <button class="btn-primary" id="addUserBtn" type="button">添加用户</button>
      </div>`)}
    <details class="core-card core-details" id="tenantInitPanel" open>
      <summary class="core-card-title">初始化包（§9.5）</summary>
      <div class="core-card-body">
        <p class="hint">备份永久保留 · 切换 active 仅对新用户/未 bootstrap 客户端生效 · 已有用户需单独确认重初始化</p>
        <p id="activeInitInfo">${a?`Active: <strong>${he(a.active_init_id)}</strong> · ${he(a.label||"")} · ${he(a.activated_at||"")}`:"（尚未定稿 active 包）"}</p>
        <div class="core-switch-row">
          <input id="exportLabel" placeholder="定稿标签" value="产品 baseline" />
          ${G({id:"exportSetActive",checked:!0,chinese:"导出后设为当前生效包",configKey:"tenant_init.active"})}
          <button class="btn-primary core-btn-fixed" id="exportInitBtn" type="button">导出租户初始化包</button>
        </div>
        <div class="core-switch-row">
          <select id="switchActiveSelect">${i||"<option value=''>（无 archive）</option>"}</select>
          <button class="secondary core-btn-fixed" id="switchActiveBtn" type="button">切换 active</button>
        </div>
        <pre class="config core-preview hint" id="archivesList">${o.length?o.map(s=>`${s.id} · ${s.label||""} · ${s.created_at||""}`).join(`
`):"（无 archive）"}</pre>
      </div>
    </details>
  `)}async function Xf(e){var n,a,o;const t=document.getElementById("tenantLoginEnabled");t==null||t.addEventListener("change",async()=>{try{await Ff(t.checked),d("租户登录开关已更新","success")}catch(i){d(String(i)),t.checked=!t.checked}}),(n=document.getElementById("addUserBtn"))==null||n.addEventListener("click",async()=>{const i=document.getElementById("newUsername"),s=i.value.trim();if(s)try{await Hf(s),i.value="",d(`已添加 ${s}`,"success"),await e()}catch(r){d(String(r))}}),(a=document.getElementById("exportInitBtn"))==null||a.addEventListener("click",async()=>{const i=document.getElementById("exportLabel").value.trim()||"baseline",s=document.getElementById("exportSetActive").checked;try{const r=await Kf(i,s);d(`已导出 ${r.export.init_id}`,"success"),await e()}catch(r){d(String(r))}}),(o=document.getElementById("switchActiveBtn"))==null||o.addEventListener("click",async()=>{const s=document.getElementById("switchActiveSelect").value;if(s)try{await Jf(s),d(`active → ${s}`,"success"),await e()}catch(r){d(String(r))}}),document.querySelectorAll("#adminUsersTable tbody tr[data-user]").forEach(i=>{var l,u,p,m;const s=i,r=s.dataset.user,c=s.querySelector(".fetch-status");(l=s.querySelector(".policy-vp"))==null||l.addEventListener("change",async g=>{const v=g.target.checked;try{await Ro(r,{cloud_fetch_policy:{virtual_personas:v}}),d(`${r} VP 策略已更新`,"success")}catch(k){d(String(k)),g.target.checked=!v}}),(u=s.querySelector(".btn-remove"))==null||u.addEventListener("click",async()=>{if(window.confirm(`移除用户 ${r}？`))try{await qf(r),d(`已移除 ${r}`,"success"),await e()}catch(g){d(String(g))}}),(p=s.querySelector(".btn-force-reinit"))==null||p.addEventListener("click",async()=>{var k;const v=(k=(await vs()).active)==null?void 0:k.active_init_id;if(!v){d("无 active 初始化包");return}if(window.confirm(`标记 ${r} 强制重初始化（init ${v}）？`))try{await Ro(r,{force_reinit_pending:!0,force_reinit_init_id:v}),d(`${r} 已标记 force_reinit`,"success"),await e()}catch(E){d(String(E))}}),(m=s.querySelector(".btn-fetch-vp"))==null||m.addEventListener("click",async()=>{c.textContent="触发中…";try{const g=await zf(r,"virtual_personas");c.textContent=`pending ${g.fetch_id.slice(0,8)}…`;const v=Date.now()+65e3;for(;Date.now()<v;){await new Promise(E=>setTimeout(E,1500));const k=await Wf(r,g.fetch_id);if(k.status==="ok"){c.textContent="ok",d(`${r} VP 已缓存`,"success"),await e();return}if(k.status==="timeout"||k.status==="expired"){c.textContent=k.status,await e();return}}c.textContent="timeout"}catch(g){c.textContent="error",d(String(g))}})})}async function Qf(){const[e,t,n]=await Promise.all([jf(),Vf(),vs()]);return{users:e.users||[],loginEnabled:!!t.login_enabled,initOverview:n}}async function Zf(){return y("/v1/core/mind")}async function eh(e=50){return y(`/v1/core/memories?scope=subject&limit=${e}`)}async function th(e){return N("/v1/core/baseline/snapshot",{label:e})}async function nh(){return y("/v1/core/planes")}async function ah(){return y("/v1/core/personas")}async function oh(e){return N("/v1/core/personas/preview",e)}async function ih(){return y("/v1/core/pathways")}async function sh(e){return N("/v1/core/pathways/preview",e)}async function rh(){return y("/v1/core/organs")}async function ch(){return y("/v1/core/cultural-learning")}async function lh(e){return y("/v1/core/cultural-learning",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)})}async function dh(e){return N("/v1/core/cultural-learning/run-batch",e||{mock:!0})}async function uh(){return y("/v1/core/web-learning")}async function Mn(e){return y("/v1/core/web-learning",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)})}async function ph(e,t){return y(`/v1/core/web-learning/topics/${encodeURIComponent(e)}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})}async function mh(e){return y(`/v1/core/web-learning/topics/${encodeURIComponent(e)}`,{method:"DELETE"})}async function fh(e){return N("/v1/core/web-learning/run-batch",e||{mock:!0})}async function hh(e){return N("/v1/core/correct",e)}async function vh(e){return N("/v1/core/recall/debug",e)}let zt=null;function gh(e){return e==="critical"?"drift-critical":e==="warn"?"drift-warn":"drift-ok"}function Fe(e,t){return ie(e,t)}function ye(e,t,n){return M(e,t,n)}function Ae(e){return me(e)}function yh(e){var a,o,i;if(!e)return'<p class="hint">加载中…</p>';const t=(((a=e.narrative)==null?void 0:a.lines)||[]).slice(0,5).map(s=>`<li>${String(s.text||"").slice(0,160)}</li>`).join(""),n=(e.main_thoughts||[]).slice(0,5).map(s=>`<li><small>${s.source}</small> ${s.text.slice(0,120)}</li>`).join("");return`
    <div class="core-grid">
      <div class="stat-card"><h3>三观 · SL5</h3><p>${e.values_summary}</p>
        <p class="hint">mood：<strong>${((o=e.mood)==null?void 0:o.mood)||"calm"}</strong></p></div>
      <div class="stat-card"><h3>SL4 叙事</h3><ul class="core-list">${t||'<li class="hint">（暂无）</li>'}</ul></div>
      <div class="stat-card"><h3>主要想法</h3><ul class="core-list">${n||'<li class="hint">（暂无）</li>'}</ul>
        <p class="hint">小意 pending：<strong>${((i=e.reflection)==null?void 0:i.pending_count)??0}</strong></p></div>
    </div>`}function bh(e){if(!e.length)return'<p class="hint">暂无主体记忆</p>';const t=e.map(n=>`<tr><td><code>${n.id.slice(0,8)}</code></td><td>${n.text_preview.slice(0,100)}</td>
        <td>${n.reality_anchor?`<span class="badge ok">${n.reality_anchor}</span>`:'<span class="badge warn">待验证</span>'}</td>
        <td><small>${n.created_at}</small></td></tr>`).join("");return Ae(`<table class="core-table"><thead><tr><th>ID</th><th>摘要</th><th>锚点</th><th>时间</th></tr></thead><tbody>${t}</tbody></table>`)}function wh(e,t,n){const a=t?Object.keys(t):[],o=(e||[]).map(s=>{var r;return`<tr><td><code>${s.id}</code></td><td>${s.label||s.name}</td><td>${((r=s.brains)==null?void 0:r.join(", "))||"—"}</td>
        <td>${s.tier||"—"}</td><td>${s.enabled===!1?"关":"开"}</td></tr>`}).join(""),i=`
    <div class="core-params-row">
      <label class="core-field"><span>Pack</span><select id="corePackSelect">${a.map(s=>`<option value="${s}">${s}</option>`).join("")}</select></label>
      <button class="secondary core-btn-fixed" id="corePlanesPreview">预览</button>
    </div>
    <p class="hint" id="corePlanesTrace">选择 Pack 后点预览</p>
    ${Ae(`<table class="core-table"><thead><tr><th>ID</th><th>名称</th><th>九脑</th><th>tier</th><th>enabled</th></tr></thead>
    <tbody>${o||'<tr><td colspan="5" class="hint">加载中…</td></tr>'}</tbody></table>`)}`;return Fe("九脑能力面注册表 · 选择 Pack 预览 trace",ye("能力面列表",i))}function _h(e,t){const n=e?Object.keys(e):[],a=n.map(i=>{var r;const s=((r=e==null?void 0:e[i])==null?void 0:r.label)||i;return`<tr><td><code>${i}</code></td><td>${s}</td></tr>`}).join(""),o=`
    <div class="core-params-row">
      <label class="core-field"><span>Preset</span><select id="corePersonaSelect">${n.map(i=>{var s;return`<option value="${i}">${((s=e==null?void 0:e[i])==null?void 0:s.label)||i}</option>`}).join("")}</select></label>
      <button class="secondary core-btn-fixed" id="corePersonaPreview">预览</button>
    </div>
    <pre class="core-preview" id="corePersonaBlock">选择 Preset 后点预览</pre>
    ${Ae(`<table class="core-table"><thead><tr><th>ID</th><th>名称</th></tr></thead>
    <tbody>${a||'<tr><td colspan="2" class="hint">加载中…</td></tr>'}</tbody></table>`)}`;return Fe("人格预设注册表 · 预览 assembly block",ye("人格预设",o,"OT4 客体 default_preset 请通过 API PATCH /v1/core/objects/{id}/persona"))}function kh(e,t,n){const a=(e||[]).map(s=>{var r;return`<tr><td><code>${s.id}</code></td><td>${((r=s.match)==null?void 0:r.regime)||"—"}</td>
        <td>${s.persona_preset||"—"}</td></tr>`}).join(""),o=`Sticky：${t!=null&&t.enabled?"开":"关"} · ${(t==null?void 0:t.hits_to_sticky)??3} 轮生效`,i=`
    <div class="core-params-row">
      <label class="core-field"><span>预览文本</span><input id="corePathwayText" value="你好呀" /></label>
      <button class="secondary core-btn-fixed" id="corePathwayPreview">预览 Fast/Warm/Cold</button>
    </div>
    <p class="hint" id="corePathwayTrace">点预览查看 tier</p>
    ${Ae(`<table class="core-table"><thead><tr><th>route</th><th>regime</th><th>persona</th></tr></thead>
    <tbody>${a||'<tr><td colspan="3" class="hint">加载中…</td></tr>'}</tbody></table>`)}`;return Fe("链路路由注册表 · 预览 tier 与 persona",ye("路由列表",i,o))}function $h(e,t){const n=(e||[]).map(o=>`<tr><td><code>${o.id}</code></td><td>${o.persona}</td><td>${o.plane}</td><td>${o.label||"—"}</td>
        <td>${o.open_threads_count??"—"}</td></tr>`).join(""),a=Ae(`<table class="core-table"><thead><tr><th>模块</th><th>脑</th><th>Plane</th><th>标签</th><th>open_threads</th></tr></thead>
    <tbody>${n||'<tr><td colspan="5" class="hint">加载中…</td></tr>'}</tbody></table>`);return Fe("十脑器官模块注册表",ye("器官列表",a,`P15 反思面：${t?"已注册":"未注册"}`))}function Sh(e){if(!e)return'<p class="hint">加载中…</p>';const n=(e.recent_batches||[]).slice(0,8).map(s=>{const r=String(s.id||""),c=r?`${z("vault")}?batch=${encodeURIComponent(r)}`:z("vault");return`<tr><td><code>${r.slice(0,10)}</code></td><td>${s.domain||"—"}</td>
        <td>${String(s.title||"").slice(0,40)}</td><td>${s.status||"—"}</td><td><small>${s.at||""}</small></td>
        <td><a href="${c}">资料库</a></td></tr>`}).join(""),a=(e.domains||[]).map(s=>`<option value="${s.id}">${s.label||s.id}</option>`).join(""),o=C().userRole==="owner",i=`${e.schedule_label||"人格启动期"} · 今日已跑：<strong>${e.runs_today??0}</strong> · 最近：${e.last_batch_at||"无"}`;return Fe("文史人格自学 · 建立三观与叙事基线",`
    <section class="core-card">
      <h3 class="core-card-title">开关</h3>
      <div class="core-card-body core-switch-row">
        ${G({id:"culturalEnabled",checked:e.enabled,disabled:!o,chinese:"开放文史自学",configKey:"cultural_learning.enabled"})}
        <button class="secondary core-btn-fixed" id="culturalSave" ${o?"":"disabled"}>保存开关</button>
        <button class="btn-primary core-btn-fixed" id="culturalRunBatch" ${o&&e.enabled?"":"disabled"}>跑一批（mock）</button>
      </div>
    </section>
    <section class="core-card">
      <h3 class="core-card-title">人格状态</h3>
      <div class="core-card-body core-switch-row">
        ${G({id:"culturalBootstrapDone",checked:e.personality_bootstrap_complete,disabled:!o,chinese:"人格已建立",configKey:"personality_bootstrap_complete"})}
        ${G({id:"culturalAllowValues",checked:e.allow_values_update,disabled:!o,chinese:"允许继续改观三观",configKey:"allow_values_update · 须主人开启"})}
      </div>
      <p class="hint core-card-foot">${i}</p>
    </section>
    <section class="core-card">
      <h3 class="core-card-title">跑批域</h3>
      <div class="core-card-body">
        <label class="core-field"><span>域</span><select id="culturalDomain">${a}</select></label>
      </div>
    </section>
    ${ye("近期批次",Ae(`<table class="core-table"><thead><tr><th>批次</th><th>域</th><th>标题</th><th>状态</th><th>时间</th><th>资料库</th></tr></thead>
    <tbody>${n||'<tr><td colspan="6" class="hint">暂无批次</td></tr>'}</tbody></table>`))}
  `)}function gs(e){var c;const t=e.layers||{},n={summaries:(t.summaries||[]).length,threads:(t.threads||[]).length,atoms:(t.atoms||[]).length,wiki:(t.wiki||[]).length,vault:(t.vault||[]).length},a=(e.recall_trace||[]).join(`
`),o=e.context_block||"",i=e.pathway_preview,s=(t.vault||[]).map(l=>{const u=l.rel_path||"";return`<li><a href="${u?`${z("vault")}?rel_path=${encodeURIComponent(u)}`:z("vault")}">${l.title||l.vault_id||u||"vault"}</a> <small>${u}</small></li>`}).join(""),r=(t.wiki||[]).map(l=>{const u=l.concept||"";return`<li><a href="${u?`${z("wiki")}?concept=${encodeURIComponent(u)}`:z("wiki")}">${u||"wiki"}</a> <small>${String(l.summary||"").slice(0,60)}</small></li>`}).join("");return`
    <p class="hint">pack <code>${e.pack_id}</code> · policy <strong>${e.policy}</strong>
      ${(c=i==null?void 0:i.active_planes)!=null&&c.length?` · active planes: ${i.active_planes.join(", ")}`:""}</p>
    ${i!=null&&i.trace_preview?`<p class="hint">${i.trace_preview}</p>`:""}
    <details style="margin-bottom:0.75rem"><summary>context_block</summary>
      <pre class="core-preview">${o.slice(0,4e3)}</pre></details>
    <p class="hint">layers: summaries=${n.summaries} · threads=${n.threads} · atoms=${n.atoms} · wiki=${n.wiki} · vault=${n.vault}</p>
    ${s?`<p><strong>Vault</strong><ul class="core-list">${s}</ul></p>`:""}
    ${r?`<p><strong>Wiki</strong><ul class="core-list">${r}</ul></p>`:""}
    <pre class="core-preview" id="recallTrace">${a||"（无 trace）"}</pre>`}function Eh(e){const t=`
    <div class="core-recall-form">
      <div class="core-recall-field core-recall-field--full">
        <label for="recallQuery">query</label>
        <textarea id="recallQuery" class="core-recall-query" rows="3" placeholder="任务文本…"></textarea>
      </div>
      <div class="core-recall-params">
        <div class="core-recall-field">
          <label for="recallPolicy">policy</label>
          <select id="recallPolicy" class="panel-field-select">
            <option value="assist_default">assist_default</option>
            <option value="memory_query">memory_query</option>
            <option value="loop_continue">loop_continue</option>
          </select>
        </div>
        <div class="core-recall-field">
          <label for="recallScope">scope</label>
          <select id="recallScope" class="panel-field-select">
            <option value="subject">subject</option>
            <option value="object">object</option>
          </select>
        </div>
        <div class="core-recall-field">
          <label for="recallPathway">pathway</label>
          <select id="recallPathway" class="panel-field-select">
            <option value="companion">companion</option>
            <option value="work">work</option>
            <option value="hybrid">hybrid</option>
            <option value="education">education</option>
          </select>
        </div>
      </div>
      <div class="core-recall-field core-recall-object-row" id="recallObjectIdRow" hidden>
        <label for="recallObjectId">object_id</label>
        <input id="recallObjectId" class="panel-field-select" placeholder="object_id" />
      </div>
      <div class="core-recall-actions panel-tail-row panel-tail-row--inline">
        ${G({id:"recallRerank",chinese:"启用向量重排",configKey:"recall.rerank"})}
        <button class="btn-primary" id="recallRun" type="button">运行</button>
      </div>
    </div>`,n=`<div id="recallOutput" class="core-recall-output">
      ${e?gs(e):'<p class="hint">填写 query 后点「运行」</p>'}
    </div>`;return Fe("单次召回调试 · policy / scope / rerank 仅影响本请求",`
    ${ye("查询参数",t)}
    ${ye("召回结果",n)}
  `)}function Ph(e,t){const n=e.filter(a=>t.has(String(a.id)));return n.length?n.length<=3?n.map(a=>a.label||a.id).join("、"):`已选 ${n.length} 个领域`:"请选择自学领域"}function Ih(){var o,i;const e=document.getElementById("webTopicMs"),t=document.getElementById("webMsPanel"),n=document.getElementById("webMsTrigger");if(!e||!t||!n)return;const a=()=>{const s=document.querySelectorAll(".webActiveTopic:checked"),r=document.getElementById("webMsLabel");if(!r)return;const c=Array.from(s).map(l=>l.dataset.label||l.value);r.textContent=c.length?c.length<=3?c.join("、"):`已选 ${c.length} 个领域`:"请选择自学领域"};n.addEventListener("click",s=>{s.stopPropagation(),t.toggleAttribute("hidden")}),t.addEventListener("click",s=>s.stopPropagation()),document.body.dataset.webMsBound||(document.body.dataset.webMsBound="1",document.addEventListener("click",()=>{var s;(s=document.getElementById("webMsPanel"))==null||s.setAttribute("hidden","")})),document.querySelectorAll(".webActiveTopic").forEach(s=>{s.addEventListener("change",a)}),(o=document.getElementById("webMsAll"))==null||o.addEventListener("click",s=>{s.stopPropagation(),document.querySelectorAll(".webActiveTopic:not(:disabled)").forEach(r=>{r.checked=!0}),a()}),(i=document.getElementById("webMsNone"))==null||i.addEventListener("click",s=>{s.stopPropagation(),document.querySelectorAll(".webActiveTopic:not(:disabled)").forEach(r=>{r.checked=!1}),a()})}function Bh(e){if(!e)return'<p class="hint">加载中…</p>';const n=(e.recent_batches||[]).slice(0,8).map(v=>`<tr><td><code>${String(v.id||"").slice(0,10)}</code></td><td>${v.topic||"—"}</td>
        <td>${String(v.title||v.query||"").slice(0,40)}</td><td>${v.status||"—"}</td><td><small>${v.at||""}</small></td></tr>`).join(""),a=new Set((e.active_topic_ids||[]).map(String)),o=e.topics||[],i=C().userRole==="owner",s=e.search_provider||"mock",r=e.personality_bootstrap_complete?"已完成":"未完成（请先完成文史人格）",c=e.schedule||{},l=c.window_start||"00:00",u=c.window_end||"01:00",p=Ph(o,a),m=o.map(v=>`<label class="web-ms-item"><input type="checkbox" class="webActiveTopic" value="${v.id}" data-label="${String(v.label||v.id).replace(/"/g,"&quot;")}" ${a.has(String(v.id))?"checked":""} ${i?"":"disabled"} /><span>${v.label||v.id}</span></label>`).join(""),g=o.map(v=>{const k=(v.seed_queries||[]).slice(0,2).join("；")||"—";return`<tr>
        <td><code>${v.id}</code></td>
        <td>${v.label||"—"}</td>
        <td><small>${k}</small></td>
        <td>${i?`<button type="button" class="secondary webTopicEdit" data-id="${v.id}" data-label="${String(v.label||"").replace(/"/g,"&quot;")}" data-seeds="${String((v.seed_queries||[]).join(`
`)).replace(/"/g,"&quot;")}">编辑</button>
          <button type="button" class="secondary webTopicDelete" data-id="${v.id}">删除</button>`:"—"}</td>
      </tr>`}).join("");return`
    <div class="web-learning">
      <p class="hint web-learning-intro">每日技能自学 · 勾选多个领域 · <strong>不改动总体三观</strong></p>

      <section class="web-card">
        <h3 class="web-card-title">开关</h3>
        <div class="web-card-body web-switch-row">
          ${G({id:"webEnabled",checked:e.enabled,disabled:!i,chinese:"开放技能自学",configKey:"web_learning.enabled"})}
          ${G({id:"webAutoLearning",checked:!!(e.auto_learning??e.auto_web_search),disabled:!i,chinese:"自动学习",configKey:"web_learning.auto_learning"})}
          <button class="secondary web-btn-fixed" id="webSave" ${i?"":"disabled"}>保存开关</button>
          <button class="btn-primary web-btn-fixed" id="webRunBatch" ${i&&e.enabled?"":"disabled"}>手动跑一批</button>
        </div>
      </section>

      <section class="web-card">
        <h3 class="web-card-title">自学时段</h3>
        <div class="web-card-body web-schedule-grid">
          <label class="web-field"><span>开始</span><input type="time" id="webWindowStart" value="${l}" ${i?"":"disabled"} /></label>
          <label class="web-field"><span>结束</span><input type="time" id="webWindowEnd" value="${u}" ${i?"":"disabled"} /></label>
          <label class="web-field"><span>每日上限（分钟）</span><input type="number" id="webDuration" min="15" max="240" value="${e.duration_minutes??60}" ${i?"":"disabled"} /></label>
          <div class="web-field web-field-action">
            <button class="secondary" id="webSaveSchedule" ${i?"":"disabled"}>保存时段</button>
          </div>
        </div>
        <p class="hint web-card-foot">每日 ${l}–${u} 可自动自学 · ${e.schedule_label||""}</p>
      </section>

      <section class="web-card">
        <h3 class="web-card-title">自学领域</h3>
        <div class="web-card-body">
          <div class="web-ms" id="webTopicMs">
            <button type="button" class="web-ms-trigger" id="webMsTrigger" ${i?"":"disabled"}>
              <span id="webMsLabel">${p}</span>
              <span class="web-ms-caret">▾</span>
            </button>
            <div class="web-ms-panel" id="webMsPanel" hidden>
              <div class="web-ms-toolbar">
                <button type="button" class="secondary web-ms-mini" id="webMsAll">全选</button>
                <button type="button" class="secondary web-ms-mini" id="webMsNone">清空</button>
              </div>
              <div class="web-ms-grid">${m||'<p class="hint">暂无领域</p>'}</div>
            </div>
          </div>
          <div class="web-inline-actions">
            <button class="secondary" id="webSaveActive" ${i?"":"disabled"}>保存领域选择</button>
            <input id="webQuery" placeholder="可选：自定义查询（手动跑批时用）" ${i?"":"disabled"} />
          </div>
        </div>
        <p class="hint web-card-foot">人格：<strong>${r}</strong> · 今日 <strong>${e.learning_minutes_today??0}</strong> / ${e.duration_minutes??60} 分钟 · 后端 ${s} · 批次 ${e.runs_today??0}</p>
      </section>

      <details class="web-card web-details">
        <summary class="web-card-title">领域管理（增删改）</summary>
        <div class="web-card-body">
          ${Ae(`<table class="core-table"><thead><tr><th>ID</th><th>名称</th><th>种子查询</th><th>操作</th></tr></thead>
          <tbody>${g||'<tr><td colspan="4" class="hint">暂无</td></tr>'}</tbody></table>`)}
          <div class="web-manage-form">
            <input id="webTopicId" placeholder="id（如 foreign_language）" ${i?"":"disabled"} />
            <input id="webTopicLabel" placeholder="显示名称" ${i?"":"disabled"} />
            <textarea id="webTopicSeeds" rows="2" placeholder="种子查询，每行一条" ${i?"":"disabled"}></textarea>
            <button class="secondary" id="webTopicSave" ${i?"":"disabled"}>保存领域</button>
          </div>
        </div>
      </details>

      <section class="web-card">
        <h3 class="web-card-title">近期批次</h3>
        <div class="web-card-body">
          ${Ae(`<table class="core-table"><thead><tr><th>批次</th><th>主题</th><th>查询</th><th>状态</th><th>时间</th></tr></thead>
        <tbody>${n||'<tr><td colspan="5" class="hint">暂无批次</td></tr>'}</tbody></table>`)}
        </div>
      </section>
    </div>`}function Ch(e){const t=C().userRole==="owner",a=((e==null?void 0:e.correction_thread_tail)||[]).slice(-6).map(i=>`<li><small>${i.role}</small> ${String(i.content||"").slice(0,160)}</li>`).join("");return`
    <div class="core-correction-form">
      <div class="core-correction-params">
        <label class="core-field"><span>动作</span><select id="coreCorrectAction">${[{v:"auto",label:"自动"},...t?[{v:"restore_baseline",label:"恢复基线"}]:[],{v:"mark_hypothesis",label:"标为假设"},...t?[{v:"promote_subject",label:"晋升 guest 记忆"}]:[],{v:"patch_narrative",label:"更新叙事 SL4"}].map(i=>`<option value="${i.v}">${i.label}</option>`).join("")}</select></label>
        <label class="core-field"><span>atom id</span><input id="coreCorrectAtomId" placeholder="标假设时填写" /></label>
      </div>
      <textarea id="coreCorrectMessage" rows="3" placeholder="纠错说明…"></textarea>
      <div class="panel-tail-row panel-tail-row--inline">
        <button class="btn-primary" id="coreCorrectSend">发送纠错</button>
      </div>
      <ul class="core-list">${a||'<li class="hint">（暂无纠错记录）</li>'}</ul>
    </div>`}function Th(e,t){var a,o;const n=e?`mood：<strong>${((a=e.mood)==null?void 0:a.mood)||"calm"}</strong> · 小意 pending：<strong>${((o=e.reflection)==null?void 0:o.pending_count)??0}</strong>`:"";return Fe("主体思维、记忆与纠错",`
    ${ye("思维概览",yh(e),n)}
    ${ye("主体记忆",bh(t))}
    ${ye("纠错对话",Ch(e))}
  `)}function Lh(e,t,n,a,o,i,s,r,c,l,u,p,m,g,v,k){const E=t==null?void 0:t.drift,I=(E==null?void 0:E.status)||"unknown";return`
    <div class="core-toolbar">
      <nav class="core-tabs">
        <button type="button" class="core-tab ${e==="mind"?"active":""}" data-tab="mind">思维</button>
        <button type="button" class="core-tab ${e==="planes"?"active":""}" data-tab="planes">能力面</button>
        <button type="button" class="core-tab ${e==="persona"?"active":""}" data-tab="persona">人格</button>
        <button type="button" class="core-tab ${e==="pathway"?"active":""}" data-tab="pathway">链路</button>
        <button type="button" class="core-tab ${e==="organs"?"active":""}" data-tab="organs">器官</button>
        <button type="button" class="core-tab ${e==="cultural"?"active":""}" data-tab="cultural">文史</button>
        <button type="button" class="core-tab ${e==="web"?"active":""}" data-tab="web">联网</button>
        <button type="button" class="core-tab ${e==="recall"?"active":""}" data-tab="recall">召回调试</button>
      </nav>
      <span class="drift-lamp ${gh(I)}" title="${(E==null?void 0:E.summary)||""}"></span>
      <span class="hint">drift：<strong>${I}</strong></span>
      <button class="secondary" id="coreRefresh">刷新</button>
      <button class="btn-primary" id="coreBaseline" ${C().userRole!=="owner"?"disabled":""}>同步为纠偏基线</button>
    </div>
    ${e==="mind"?Th(t,n):e==="planes"?wh(a,o):e==="persona"?_h(s):e==="pathway"?kh(c,l):e==="cultural"?Sh(g):e==="web"?Bh(v):e==="recall"?Eh(k):$h(p,m)}`}async function Ah(e,t){var o,i,s,r,c,l,u,p,m,g,v,k,E,I;document.querySelectorAll(".core-tab").forEach(_=>{_.addEventListener("click",()=>{const P=_.dataset.tab;t(P)})}),(o=document.getElementById("coreRefresh"))==null||o.addEventListener("click",()=>t(e)),(i=document.getElementById("coreBaseline"))==null||i.addEventListener("click",async()=>{try{const _=await th("core-ui");d(`已创建基线 ${_.id}`),t(e)}catch(_){d(String(_ instanceof Y?_.message:_))}}),(s=document.getElementById("corePlanesPreview"))==null||s.addEventListener("click",async()=>{var P;const _=((P=document.getElementById("corePackSelect"))==null?void 0:P.value)||"companion";try{const f=await previewCorePlanes({text:"你好",regime:_,pack:_}),w=document.getElementById("corePlanesTrace");w&&(w.textContent=f.trace_preview)}catch(f){d(String(f))}}),(r=document.getElementById("corePersonaPreview"))==null||r.addEventListener("click",async()=>{var P;const _=((P=document.getElementById("corePersonaSelect"))==null?void 0:P.value)||"companion_warm";try{const f=await oh({preset:_,regime:"companion"}),w=document.getElementById("corePersonaBlock");w&&(w.textContent=f.assembly_block||"（detach）")}catch(f){d(String(f))}}),(c=document.getElementById("corePathwayPreview"))==null||c.addEventListener("click",async()=>{var P;const _=((P=document.getElementById("corePathwayText"))==null?void 0:P.value)||"你好";try{const f=await sh({text:_}),w=document.getElementById("corePathwayTrace");w&&(w.textContent=f.trace_preview||f.tier||"")}catch(f){d(String(f))}}),(l=document.getElementById("culturalSave"))==null||l.addEventListener("click",async()=>{var _,P,f;try{await lh({enabled:(_=document.getElementById("culturalEnabled"))==null?void 0:_.checked,personality_bootstrap_complete:(P=document.getElementById("culturalBootstrapDone"))==null?void 0:P.checked,allow_values_update:(f=document.getElementById("culturalAllowValues"))==null?void 0:f.checked}),d("文史开关已保存"),t("cultural")}catch(w){d(String(w))}}),(u=document.getElementById("culturalRunBatch"))==null||u.addEventListener("click",async()=>{var P;const _=(P=document.getElementById("culturalDomain"))==null?void 0:P.value;try{const f=await dh({domain_id:_||void 0,mock:!0});d(f.ok?`批次完成 · SL4 +${f.sl4_applied??0}`:"批次未完成"),t("cultural")}catch(f){d(String(f))}}),(p=document.getElementById("webSave"))==null||p.addEventListener("click",async()=>{var _,P;try{await Mn({enabled:(_=document.getElementById("webEnabled"))==null?void 0:_.checked,auto_learning:(P=document.getElementById("webAutoLearning"))==null?void 0:P.checked}),d("联网开关已保存"),t("web")}catch(f){d(String(f))}}),(m=document.getElementById("webSaveSchedule"))==null||m.addEventListener("click",async()=>{var w,S,B;const _=(w=document.getElementById("webWindowStart"))==null?void 0:w.value,P=(S=document.getElementById("webWindowEnd"))==null?void 0:S.value,f=Number(((B=document.getElementById("webDuration"))==null?void 0:B.value)||60);try{await Mn({schedule:{window_start:_||"00:00",window_end:P||"01:00",duration_minutes:f}}),d("自学时段已保存"),t("web")}catch(L){d(String(L))}}),(g=document.getElementById("webSaveActive"))==null||g.addEventListener("click",async()=>{const _=Array.from(document.querySelectorAll(".webActiveTopic:checked")).map(P=>P.value);try{await Mn({active_topic_ids:_}),d(`已保存 ${_.length} 个自学领域`),t("web")}catch(P){d(String(P))}}),(v=document.getElementById("webTopicSave"))==null||v.addEventListener("click",async()=>{var S,B,L,D,T;const _=(B=(S=document.getElementById("webTopicId"))==null?void 0:S.value)==null?void 0:B.trim(),P=(D=(L=document.getElementById("webTopicLabel"))==null?void 0:L.value)==null?void 0:D.trim(),w=(((T=document.getElementById("webTopicSeeds"))==null?void 0:T.value)||"").split(`
`).map(H=>H.trim()).filter(Boolean);if(!_||!P){d("请填写 id 与名称");return}try{await ph(_,{label:P,seed_queries:w}),d("领域已保存"),t("web")}catch(H){d(String(H))}}),document.querySelectorAll(".webTopicEdit").forEach(_=>{_.addEventListener("click",()=>{const P=_;document.getElementById("webTopicId").value=P.dataset.id||"",document.getElementById("webTopicLabel").value=P.dataset.label||"",document.getElementById("webTopicSeeds").value=P.dataset.seeds||""})}),document.querySelectorAll(".webTopicDelete").forEach(_=>{_.addEventListener("click",async()=>{const P=_.dataset.id;if(!(!P||!window.confirm(`删除领域 ${P}？`)))try{await mh(P),d("领域已删除"),t("web")}catch(f){d(String(f))}})}),(k=document.getElementById("webRunBatch"))==null||k.addEventListener("click",async()=>{var P,f;const _=(f=(P=document.getElementById("webQuery"))==null?void 0:P.value)==null?void 0:f.trim();try{const w=await fh({query:_||void 0,mock:!0});d(w.ok?`批次完成 · SL4 +${w.sl4_applied??0}`:"批次未完成"),t("web")}catch(w){d(String(w))}}),e==="web"&&Ih(),(E=document.getElementById("coreCorrectSend"))==null||E.addEventListener("click",async()=>{var w,S,B,L,D;const _=(S=(w=document.getElementById("coreCorrectMessage"))==null?void 0:w.value)==null?void 0:S.trim(),P=((B=document.getElementById("coreCorrectAction"))==null?void 0:B.value)||"auto",f=(D=(L=document.getElementById("coreCorrectAtomId"))==null?void 0:L.value)==null?void 0:D.trim();if(!_){d("请输入纠错内容");return}try{const T=await hh({message:_,action:P,target_atom_id:f||null});d(T.reply.slice(0,80)),t("mind")}catch(T){d(String(T instanceof Y?T.message:T))}});const n=document.getElementById("recallScope"),a=()=>{const _=(n==null?void 0:n.value)==="object",P=document.getElementById("recallObjectIdRow");P&&(P.hidden=!_)};n==null||n.addEventListener("change",a),a(),(I=document.getElementById("recallRun"))==null||I.addEventListener("click",async()=>{var L,D,T,H,te,ce,X,Z;const _=((D=(L=document.getElementById("recallQuery"))==null?void 0:L.value)==null?void 0:D.trim())||"",P=((T=document.getElementById("recallPolicy"))==null?void 0:T.value)||"assist_default",f=((H=document.getElementById("recallScope"))==null?void 0:H.value)||"subject",w=(ce=(te=document.getElementById("recallObjectId"))==null?void 0:te.value)==null?void 0:ce.trim(),S=((X=document.getElementById("recallPathway"))==null?void 0:X.value)||"companion",B=((Z=document.getElementById("recallRerank"))==null?void 0:Z.checked)??!1;try{zt=await vh({query:_,policy:P,recall_scope:f,object_id:f==="object"&&w||null,pathway:S,rerank:B}),d(`召回完成 ${zt.pack_id}`);const b=document.getElementById("recallOutput");b&&(b.innerHTML=gs(zt))}catch(b){d(String(b instanceof Y?b.message:b))}})}async function xh(e){if(e==="planes"){const a=await nh();return{mind:null,memories:[],planes:a.planes,packs:a.packs,preview:null,personas:null,personaPreview:null,pathways:null,pathwaySticky:null,pathwayPreview:null,organs:null,p15Registered:!1,cultural:null,web:null,recall:null}}if(e==="persona"){const a=await ah();return{mind:null,memories:[],planes:null,packs:null,preview:null,personas:a.presets,personaPreview:null,pathways:null,pathwaySticky:null,pathwayPreview:null,organs:null,p15Registered:!1,cultural:null,web:null,recall:null}}if(e==="pathway"){const a=await ih();return{mind:null,memories:[],planes:null,packs:null,preview:null,personas:null,personaPreview:null,pathways:a.routes,pathwaySticky:a.sticky,pathwayPreview:null,organs:null,p15Registered:!1,cultural:null,web:null,recall:null}}if(e==="cultural"){const a=await ch();return{mind:null,memories:[],planes:null,packs:null,preview:null,personas:null,personaPreview:null,pathways:null,pathwaySticky:null,pathwayPreview:null,organs:null,p15Registered:!1,cultural:a,web:null,recall:null}}if(e==="web"){const a=await uh();return{mind:null,memories:[],planes:null,packs:null,preview:null,personas:null,personaPreview:null,pathways:null,pathwaySticky:null,pathwayPreview:null,organs:null,p15Registered:!1,cultural:null,web:a,recall:null}}if(e==="recall")return{mind:null,memories:[],planes:null,packs:null,preview:null,personas:null,personaPreview:null,pathways:null,pathwaySticky:null,pathwayPreview:null,organs:null,p15Registered:!1,cultural:null,web:null,recall:zt};if(e==="organs"){const a=await rh();return{mind:null,memories:[],planes:null,packs:null,preview:null,personas:null,personaPreview:null,pathways:null,pathwaySticky:null,pathwayPreview:null,organs:a.modules,p15Registered:a.p15_registered,cultural:null,web:null,recall:null}}const[t,n]=await Promise.all([Zf(),eh()]);return{mind:t,memories:n.items,planes:null,packs:null,preview:null,personas:null,personaPreview:null,pathways:null,pathwaySticky:null,pathwayPreview:null,organs:null,p15Registered:!1,cultural:null,web:null,recall:null}}function ys(){const e=document.getElementById("app");if(!e)throw new Error("Missing #app mount node");return e}let Wt="mind",Xn=null,Qn=[],Zn=null,ea=null,ta=null,na=null,aa=null,oa=null,ia=!1,sa=null,ra=null,ca=null,mt=null,Kt="",Jt=null,on=null,wt="",_t=null,sn=[],rn=null,je="user",He=new URLSearchParams(window.location.search).get("batch"),cn=[],la=[],Gt=null,Yt=null,Be=null,bs=null,da=[],ws=!1,ua=null,pa=null,ma=null,ln=0;const Mh={categories:{}};function Da(e,t){return t!==void 0&&t!==ln?!1:ks()===e}function Je(e,t){Da(e,t)&&Q({bindOnly:!0})}async function _s(){const e=ln,t=await us(je,He);Da("vault",e)&&(on=t.tree,wt="",_t=null,sn=t.pending,rn=t.taxonomy,cn=t.batchHits,He&&(je="niannian"),Q({bindOnly:!0}))}async function Oh(e){je=e,e!=="niannian"&&(He=null),await _s()}async function Rh(){const e=C();let t=[...e.capabilities];const n=e.sessionToken;if(n)try{const s=await fetch(`${un()}/v1/auth/me`,{headers:{"X-NianNian-Session":n,"X-NianNian-Agent":"desktop"},signal:AbortSignal.timeout(4e3)});if(s.ok){const r=await s.json();Array.isArray(r.capabilities)&&(t=r.capabilities)}}catch{}const i=mi(t,{forcedProfile:"client",legacyIntranet:!n});j({capabilities:t,consoleProfile:i}),C().sessionToken&&pi()}function ks(){const e=Ps(),t=C();return Xr(e,t.consoleProfile,t.capabilities,t.userRole)?e:(history.replaceState(null,"",z("chat")),"chat")}function Q(e){ln+=1;const t=ks(),n=C();let a=fd();t==="core"&&(a=Lh(Wt,Xn,Qn,Zn,ea,null,ta,null,na,aa,null,oa,ia,sa,ra,ca)),t==="memory"&&(a=au()),t==="wiki"&&(a=jm(mt,Kt,Jt)),t==="virtual-personas"&&(a=Of(la,Gt,bs,Be,Yt)),t==="vault"&&(a=Jm(on,je,wt,sn,rn,cn,He,_t)),t==="cloud"&&(a=Tu(n.tierView)),t==="settings"&&(a=yp()),t==="llm"&&(a=Hp()),t==="token"&&(a=Am(pa,ma)),t==="skills"&&(a=em()),t==="mcp"&&(a=ym()),t==="admin-users"&&(a=Yf(da,ws,ua)),ys().innerHTML=ar({activePage:t,bodyHtml:t==="chat"?a:`<div class="console-page">${a}</div>`,navHtml:nr(t,Qr()),profile:n.consoleProfile,role:n.userRole,healthClass:wi(n.health),tierBarHtml:"",userAvatarUrl:$c()}),ir(),gi(n.tierView,n.health,n.tierAlerts),Dh(t,ln,(e==null?void 0:e.bindOnly)===!0)}async function Dh(e,t,n=!1){const a=()=>Da(e,t),o=()=>{a()&&Q()},i=()=>void bi();if(e==="settings"){await bp(()=>{Q(),Ea(!0).then(()=>yi())},i);return}if(e==="core"){const s=r=>{r&&(Wt=r),a()&&Q()};if(n){if(!a())return;await Ah(Wt,s);return}try{const r=await xh(Wt);Xn=r.mind,Qn=r.memories,Zn=r.planes,ea=r.packs,ta=r.personas,na=r.pathways,aa=r.pathwaySticky,oa=r.organs,ia=r.p15Registered,sa=r.cultural,ra=r.web,ca=r.recall,Je("core",t)}catch(r){Xn=null,Qn=[],Zn=null,ea=null,ta=null,na=null,aa=null,oa=null,ia=!1,sa=null,ra=null,ca=null,fe(r),Je("core",t)}return}if(e==="memory"){await su();return}if(e==="virtual-personas"){const s=async c=>{c!==void 0&&(Be=c);try{const l=await Mf();if(la=l.personas,bs=l.equippedVpId,Be?(Gt=await Df(Be),Yt=await Nf(Be)):(Gt=null,Yt=null),!a())return;Q({bindOnly:!0})}catch(l){if(la=[],Gt=null,Yt=null,fe(l),!a())return;Q({bindOnly:!0})}};if(n){if(!a())return;await Rf(Be,s);return}const r=Uf();r&&(Be=r),await s(Be);return}if(e==="wiki"){if(n){if(!a())return;await Hm();return}const s=new URLSearchParams(window.location.search);try{const r=await Vm();if(mt=r.graph,Kt=r.detail,Jt=null,mt){const c=await qm(mt,s);c&&(Kt=c.text,Jt=c.vaultRelPath||null)}Je("wiki",t)}catch(r){mt=null,Kt="",Jt=null,fe(r),Je("wiki",t)}return}if(e==="vault"){if(n){if(!a())return;await Gm(Oh,_s);return}const s=new URLSearchParams(window.location.search);He=s.get("batch");const r=s.get("rel_path");try{if(_t=null,r){const l=await ds(r);je=l.ns,wt=l.detail,_t=l.wikiHref}const c=await us(je,He);on=c.tree,r||(wt=c.detail),sn=c.pending,rn=c.taxonomy,cn=c.batchHits,He&&(je="niannian"),Je("vault",t)}catch(c){on=Mh,wt="",_t=null,sn=[],rn=null,cn=[],fe(c),Je("vault",t)}return}if(e==="cloud"){await Lu(i);return}if(e==="skills"){await om();return}if(e==="llm"){await Fp();return}if(e==="token"){const s=async()=>{try{const r=await Mm();if(pa=r.usage,ma=r.budget,!a())return;Q({bindOnly:!0})}catch(r){if(pa=null,ma=null,fe(r),!a())return;Q({bindOnly:!0})}};if(n){if(!a())return;xm(s);return}await s();return}if(e==="mcp"){await Pm();return}if(e==="admin-users"){const s=async()=>{try{const r=await Qf();if(da=r.users,ws=r.loginEnabled,ua=r.initOverview,!a())return;Q({bindOnly:!0})}catch(r){if(da=[],ua=null,fe(r),!a())return;Q({bindOnly:!0})}};if(n){if(!a())return;await Xf(s);return}await s();return}Bd(o)}function Do(e){const t=new URL(e,window.location.origin),n=`${t.pathname}${t.search}`;`${window.location.pathname}${window.location.search}`!==n&&history.pushState(null,"",n),Q()}function Nh(){ys().addEventListener("click",e=>{const t=e.target.closest("[data-console-nav]");if(t instanceof HTMLButtonElement){const a=t.getAttribute("data-console-nav");if(!a)return;e.preventDefault(),e.stopPropagation(),Do(z(a)),t.blur(),document.body.classList.contains("discord-sidebar-open")&&document.body.classList.remove("discord-sidebar-open");return}const n=e.target.closest("a");if(n instanceof HTMLAnchorElement){const a=n.getAttribute("href");if(a!=null&&a.startsWith(`${ve}/`)){e.preventDefault(),e.stopPropagation(),Do(a),document.body.classList.contains("discord-sidebar-open")&&document.body.classList.remove("discord-sidebar-open");return}}document.body.classList.contains("discord-sidebar-open")&&(e.target.closest(".discord-sidebar")||document.body.classList.remove("discord-sidebar-open"))})}function No(){Is(),Nh(),window.addEventListener("popstate",Q),(async()=>(Zs(),Q(),await Rh(),await Ea(),Q(),bi()))()}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",No):No();
