// == Pi Lab Page AI Runtime == // MNote-native adapter for Pi-first Page AI Lab. // Renders an independent floating launcher and drawer. OpenHub remains the // default Page AI surface; Pi Lab never mounts inside the OpenHub drawer, iframe // page, provider tab, or page container. NO setInterval polling. // // UI note: @earendil-works/pi-web-ui@0.75.3 was checked as the mature upstream UI. // It expects browser-side pi-agent-core, IndexedDB storage, API-key dialogs, model // selection and builtin tools/artifacts. MNote's lab backend owns Pi RPC, access // scope and tools, so this file keeps a host-owned adapter while matching the // upstream ChatPanel/AgentInterface layout: message list, streaming composer, // tool timeline, model/runtime controls and artifacts-style right rail. // // Default model: Omniroute/freefirst (consumed from backend status/default fields). (function () { 'use strict'; var STATE_IDLE = 'idle'; var STATE_STARTING = 'starting'; var STATE_STARTED = 'started'; var STATE_STREAMING = 'streaming'; var STATE_ABORTED = 'aborted'; var STATE_ERROR = 'error'; var API = { STATUS: '/api/page-ai/pi/status', START: '/api/page-ai/pi/start', SEND: '/api/page-ai/pi/send', ABORT: '/api/page-ai/pi/abort', EVENTS: '/api/page-ai/pi/events', BOOTSTRAP: '/api/page-ai/pi/bootstrap', SESSIONS: '/api/page-ai/pi/sessions', EFFECTIVE: '/api/ai-settings/effective', }; var DEFAULT_MODEL_PROVIDER = 'omniroute'; var DEFAULT_MODEL_ID = 'freefirst'; var piLabInstalled = false; var piLabPanelEl = null; var piLabDrawerEl = null; var piLabLauncherEl = null; var piLabEventSource = null; var piLabState = { status: STATE_IDLE, enabled: false, active: false, standalone: false, sessionId: null, providerSessionId: null, defaultModelProvider: DEFAULT_MODEL_PROVIDER, defaultModelId: DEFAULT_MODEL_ID, modelProvider: null, modelId: null, runtimeMode: null, runtimePid: null, disabledBuiltinTools: null, session: null, allowedRootsSummary: '', selectionSummary: '', selectionText: '', changedFiles: [], messages: [], diagnostics: '', receipts: [], modelOptions: [], history: [], }; var streamingAssistantMsg = null; function injectStyles() { var styleId = 'pi-lab-runtime-style'; if (document.getElementById(styleId)) return; var style = document.createElement('style'); style.id = styleId; style.textContent = '.wolai-page-ai-pi-lab-launcher{position:fixed;right:26px;bottom:92px;z-index:2600;width:36px;height:36px;border-radius:18px;border:1px solid rgba(37,37,33,.14);background:#fff;color:#252521;box-shadow:0 8px 24px rgba(15,23,42,.16);display:grid;place-items:center;font:600 15px/1 system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;cursor:pointer}' + '.wolai-page-ai-pi-lab-launcher:hover{background:#f7f7f5;box-shadow:0 10px 28px rgba(15,23,42,.20)}' + '.wolai-page-ai-pi-lab-launcher[data-state="streaming"]::after{content:"";position:absolute;right:4px;top:4px;width:8px;height:8px;border-radius:50%;background:#10a37f;box-shadow:0 0 0 3px rgba(16,163,127,.14)}' + '.wolai-page-ai-pi-lab-drawer{position:fixed;right:18px;bottom:18px;top:64px;z-index:2590;width:min(760px,calc(100vw - 36px));border:1px solid rgba(35,35,30,.10);border-radius:20px;background:#f5f6fa;box-shadow:0 24px 70px rgba(15,23,42,.22);overflow:hidden;display:flex;flex-direction:column}' + '.wolai-page-ai-pi-lab-drawer[hidden]{display:none!important}' + '.wolai-page-ai-pi-lab-drawer[data-minimized="true"]{top:auto;height:54px;width:min(420px,calc(100vw - 36px))}' + '.wolai-page-ai-pi-lab-drawer[data-minimized="true"] .wolai-page-ai-pi-lab-config,.wolai-page-ai-pi-lab-drawer[data-minimized="true"] .wolai-page-ai-pi-lab-context-strip,.wolai-page-ai-pi-lab-drawer[data-minimized="true"] .wolai-page-ai-pi-lab-body,.wolai-page-ai-pi-lab-drawer[data-minimized="true"] .wolai-page-ai-pi-lab-diagnostics{display:none!important}' + '.wolai-page-ai-pi-lab-shell{display:flex;flex-direction:column;height:100%;min-height:0;background:#f5f6fa;color:#242424;font:13px/1.45 system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;overflow:hidden}' + '.wolai-page-ai-pi-lab-shell[hidden]{display:none!important}' + '.wolai-page-ai-pi-lab-topbar{height:58px;display:flex;align-items:center;gap:10px;padding:0 18px;background:#f5f6fa;flex:0 0 auto}' + '.wolai-page-ai-pi-lab-brand{display:flex;align-items:center;gap:8px;min-width:0;flex:1}' + '.wolai-page-ai-pi-lab-logo{width:30px;height:30px;border-radius:9px;display:grid;place-items:center;background:#1f6feb;color:#fff;font-weight:700}' + '.wolai-page-ai-pi-lab-title{display:flex;flex-direction:column;gap:1px;min-width:0}' + '.wolai-page-ai-pi-lab-title strong{font-size:22px;font-weight:760;letter-spacing:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}' + '.wolai-page-ai-pi-lab-title span{font-size:11px;color:#6f6a60;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}' + '.wolai-page-ai-pi-lab-icon-btn{width:30px;height:30px;border:1px solid #e2ded6;border-radius:8px;background:#fff;color:#514f49;display:grid;place-items:center;cursor:pointer}' + '.wolai-page-ai-pi-lab-icon-btn:hover{background:#f7f6f2}' + '.wolai-page-ai-pi-lab-commandbar{height:52px;margin:0 14px 28px;padding:0 14px;border:1px solid #e5e8ef;border-radius:14px;background:#fff;display:flex;align-items:center;gap:14px;box-shadow:0 2px 8px rgba(15,23,42,.04);flex:0 0 auto}' + '.wolai-page-ai-pi-lab-commandbar .wolai-page-ai-pi-lab-tool-icon{width:28px;height:28px;border:0;background:transparent;color:#30343b;display:grid;place-items:center;font-size:19px;cursor:pointer;border-radius:8px}' + '.wolai-page-ai-pi-lab-commandbar .wolai-page-ai-pi-lab-tool-icon:hover{background:#f3f5f8}' + '.wolai-page-ai-pi-lab-commandbar .wolai-page-ai-pi-lab-tool-icon[disabled]{opacity:.35;cursor:default}' + '.wolai-page-ai-pi-lab-commandbar-spacer{flex:1}' + '.wolai-page-ai-pi-lab-config{display:none;grid-template-columns:minmax(0,1fr) auto;gap:10px;margin:-18px 14px 12px;padding:10px 12px;border:1px solid #e5e8ef;border-radius:12px;background:#fff;box-shadow:0 4px 14px rgba(15,23,42,.05);flex:0 0 auto}' + '.wolai-page-ai-pi-lab-drawer[data-config-open="true"] .wolai-page-ai-pi-lab-config{display:grid}' + '.wolai-page-ai-pi-lab-model-row{display:flex;flex-wrap:wrap;gap:6px;min-width:0}' + '.wolai-page-ai-pi-lab-settings{border:0;min-width:0}' + '.wolai-page-ai-pi-lab-settings>summary{display:inline-flex;align-items:center;gap:6px;min-height:28px;cursor:pointer;list-style:none;color:#514f49;font-size:12px}' + '.wolai-page-ai-pi-lab-settings>summary::-webkit-details-marker{display:none}' + '.wolai-page-ai-pi-lab-settings>summary::before{content:"▸";font-size:10px;color:#8a8378}' + '.wolai-page-ai-pi-lab-settings[open]>summary::before{content:"▾"}' + '.wolai-page-ai-pi-lab-settings-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;margin-top:8px;max-width:560px}' + '.wolai-page-ai-pi-lab-history-panel{margin:0 18px 10px;border:1px solid #e2ded6;border-radius:8px;background:#fff;max-height:220px;overflow:auto}' + '.wolai-page-ai-pi-lab-history-panel[hidden]{display:none!important}' + '.wolai-page-ai-pi-lab-history-row{width:100%;border:0;border-bottom:1px solid #f0eee9;background:#fff;padding:10px 12px;text-align:left;cursor:pointer;display:flex;justify-content:space-between;gap:10px}' + '.wolai-page-ai-pi-lab-history-row:hover{background:#f8f7f4}' + '.wolai-page-ai-pi-lab-history-row strong{display:block;font-size:12px;color:#2e2c28}' + '.wolai-page-ai-pi-lab-history-row span{font-size:11px;color:#817b72}' + '.wolai-page-ai-pi-lab-field{display:flex;flex-direction:column;gap:4px;font-size:11px;color:#6f6a60}' + '.wolai-page-ai-pi-lab-field select,.wolai-page-ai-pi-lab-field input{height:30px;border:1px solid #ddd8cf;border-radius:8px;background:#fff;color:#242424;padding:0 8px;font:12px/1.3 inherit;min-width:0}' + '.wolai-page-ai-pi-lab-context-strip{display:none;padding:0;margin:-6px 14px 12px;border:1px solid #ebe7de;border-radius:12px;background:#faf9f6;flex:0 0 auto}' + '.wolai-page-ai-pi-lab-drawer[data-config-open="true"] .wolai-page-ai-pi-lab-context-strip{display:block}' + '.wolai-page-ai-pi-lab-context-strip>summary{display:flex;align-items:center;gap:8px;min-height:34px;padding:0 12px;cursor:pointer;list-style:none;color:#5d594f;font-size:12px}' + '.wolai-page-ai-pi-lab-context-strip>summary::-webkit-details-marker{display:none}' + '.wolai-page-ai-pi-lab-context-strip>summary::before{content:"▸";font-size:10px;color:#8a8378}' + '.wolai-page-ai-pi-lab-context-strip[open]>summary::before{content:"▾"}' + '.wolai-page-ai-pi-lab-context-title{font-weight:650;color:#42403a}' + '.wolai-page-ai-pi-lab-context-current{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#242424;font-weight:650}' + '.wolai-page-ai-pi-lab-context-count{margin-left:auto;color:#8a8378;font-size:11px}' + '.wolai-page-ai-pi-lab-context-content{display:flex;align-items:center;gap:6px;padding:0 12px 8px;overflow-x:auto}' + '.wolai-page-ai-pi-lab-chip{height:24px;display:inline-flex;align-items:center;gap:5px;border:1px solid #e4e0d8;border-radius:999px;background:#f8f7f3;color:#514f49;padding:0 8px;font-size:11px;max-width:100%}' + '.wolai-page-ai-pi-lab-chip strong{font-weight:650;color:#242424;min-width:0;overflow:hidden;text-overflow:ellipsis}' + '.wolai-page-ai-pi-lab-context-strip .wolai-page-ai-pi-lab-chip{flex:0 0 auto}' + '.wolai-page-ai-pi-lab-chip[data-tone="ok"]{background:#eef8f3;border-color:#ccebdc;color:#176b4b}' + '.wolai-page-ai-pi-lab-chip[data-tone="warn"]{background:#fff6e6;border-color:#f2dfb5;color:#865b13}' + '.wolai-page-ai-pi-lab-chip[data-tone="danger"]{background:#fff0ee;border-color:#f2c4bd;color:#a73b2f}' + '.wolai-page-ai-pi-lab-actions{display:flex;align-items:center;gap:6px;justify-content:flex-end}' + '.wolai-page-ai-pi-lab-btn{height:28px;border:1px solid #d9d4ca;border-radius:7px;background:#fff;color:#383631;font-size:12px;padding:0 10px;cursor:pointer;white-space:nowrap}' + '.wolai-page-ai-pi-lab-btn:hover{background:#f7f6f2}' + '.wolai-page-ai-pi-lab-btn:disabled{opacity:.45;cursor:default}' + '.wolai-page-ai-pi-lab-btn-primary{background:#1f6feb;border-color:#1f6feb;color:#fff}' + '.wolai-page-ai-pi-lab-btn-primary:hover{background:#1b61cf}' + '.wolai-page-ai-pi-lab-btn-danger{background:#d93025;border-color:#d93025;color:#fff}' + '.wolai-page-ai-pi-lab-body{display:block;min-height:0;flex:1;background:#f5f6fa;padding:0 14px 12px}' + '.wolai-page-ai-pi-lab-main{display:flex;flex-direction:column;min-width:0;min-height:0;height:100%}' + '.wolai-page-ai-pi-lab-messages{flex:1;min-height:0;overflow-y:auto;padding:22px 18px 20px;display:flex;flex-direction:column;gap:12px;overscroll-behavior:contain;background:#fff;border:1px solid #e4e7ef;border-radius:22px 22px 0 0;box-shadow:0 12px 36px rgba(15,23,42,.08)}' + '.wolai-page-ai-pi-lab-empty{margin:auto;max-width:460px;text-align:center;color:#8a9099;background:transparent;border:0;border-radius:0;padding:10px 0 4px;box-shadow:none}' + '.wolai-page-ai-pi-lab-empty-icon{width:76px;height:76px;margin:0 auto 18px;border-radius:50%;background:#f0f2f6;color:#8a93a2;display:grid;place-items:center;font-size:42px;font-weight:400}' + '.wolai-page-ai-pi-lab-empty strong{display:block;color:#242833;margin-bottom:8px;font-size:25px;font-weight:760;letter-spacing:0}' + '.wolai-page-ai-pi-lab-empty p{margin:0 0 20px;color:#8a9099;font-size:16px}' + '.wolai-page-ai-pi-lab-examples{display:flex;flex-direction:column;align-items:center;gap:10px}' + '.wolai-page-ai-pi-lab-example{height:42px;min-width:min(390px,100%);border:1px solid #e1e4eb;border-radius:12px;background:#fff;color:#4a4f58;font-size:16px;display:flex;align-items:center;justify-content:flex-start;gap:12px;padding:0 18px;box-shadow:0 3px 9px rgba(15,23,42,.05);cursor:pointer}' + '.wolai-page-ai-pi-lab-example:hover{background:#f8fafc}' + '.wolai-page-ai-pi-lab-example span{font-size:18px;color:#5c6470}' + '.wolai-page-ai-pi-lab-message{display:flex;flex-direction:column;gap:6px;max-width:92%}' + '.wolai-page-ai-pi-lab-message[data-role="user"]{align-self:flex-end;align-items:flex-end}' + '.wolai-page-ai-pi-lab-message[data-role="assistant"],.wolai-page-ai-pi-lab-message[data-role="system"]{align-self:stretch}' + '.wolai-page-ai-pi-lab-role{display:flex;align-items:center;gap:6px;color:#777166;font-size:11px;font-weight:650}' + '.wolai-page-ai-pi-lab-message[data-role="user"] .wolai-page-ai-pi-lab-role{justify-content:flex-end}' + '.wolai-page-ai-pi-lab-bubble{border:1px solid #e6e2da;border-radius:12px;background:#fff;padding:10px 12px;color:#272521;word-break:break-word;box-shadow:0 4px 14px rgba(15,23,42,.035)}' + '.wolai-page-ai-pi-lab-message[data-role="user"] .wolai-page-ai-pi-lab-bubble{background:#f3f7ff;border-color:#d9e6ff}' + '.wolai-page-ai-pi-lab-text{white-space:pre-wrap}' + '.wolai-page-ai-pi-lab-text.streaming-cursor::after{content:"";display:inline-block;width:7px;height:14px;margin-left:3px;vertical-align:-2px;background:#1f6feb;animation:pi-blink .85s steps(2,start) infinite}' + '@keyframes pi-blink{50%{opacity:.18}}' + '.wolai-page-ai-pi-lab-tool-calls,.wolai-page-ai-pi-lab-citations{display:flex;flex-direction:column;gap:6px;margin-top:8px}' + '.wolai-page-ai-pi-lab-tool-call{border:1px solid #e5e0d8;border-radius:9px;background:#faf9f6;overflow:hidden}' + '.wolai-page-ai-pi-lab-tool-call-header{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:7px 9px;font-size:12px;font-weight:650;color:#42403a}' + '.wolai-page-ai-pi-lab-tool-call-header span{font-size:11px;color:#777166;font-weight:500}' + '.wolai-page-ai-pi-lab-tool-call pre{margin:0;border-top:1px solid #ebe7de;background:#fff;padding:8px;max-height:140px;overflow:auto;font:11px/1.45 ui-monospace,SFMono-Regular,Menlo,monospace;color:#42403a;white-space:pre-wrap}' + '.wolai-page-ai-pi-lab-citation{display:inline-flex;align-items:center;gap:6px;width:max-content;max-width:100%;border:1px solid #d9e6ff;border-radius:999px;background:#f3f7ff;color:#2456a6;text-decoration:none;padding:4px 8px;font-size:11px}' + '.wolai-page-ai-pi-lab-diff{display:inline-flex;align-items:center;width:max-content;max-width:100%;margin-top:8px;border:1px solid #f0dcb7;border-radius:999px;background:#fff7e8;color:#865b13;padding:4px 8px;font-size:11px}' + '.wolai-page-ai-pi-lab-composer{flex:0 0 auto;border:1px solid #e4e7ef;border-top:0;border-radius:0 0 22px 22px;background:#fff;box-shadow:0 14px 34px rgba(15,23,42,.09);overflow:hidden}' + '.wolai-page-ai-pi-lab-input{width:100%;min-height:94px;max-height:190px;box-sizing:border-box;border:0;resize:none;padding:20px 22px 10px;font:18px/1.45 inherit;color:#242424;background:#f7f8fb;outline:none}' + '.wolai-page-ai-pi-lab-input::placeholder{color:#b6bbc5}' + '.wolai-page-ai-pi-lab-composer-bar{height:48px;display:flex;align-items:center;gap:8px;padding:0 14px;border-top:1px solid #eef0f5;background:#fff}' + '.wolai-page-ai-pi-lab-quick{height:24px;border:1px solid #e5e0d8;border-radius:999px;background:#f8f7f3;color:#5d594f;font-size:11px;padding:0 8px;cursor:pointer}' + '.wolai-page-ai-pi-lab-modebar{height:52px;display:flex;align-items:center;gap:10px;padding:0 14px;border-top:1px solid #eef0f5;background:#fff}' + '.wolai-page-ai-pi-lab-square{width:36px;height:36px;border:1px solid #e1e4eb;border-radius:9px;background:#fff;color:#242833;display:grid;place-items:center;font-size:19px;cursor:pointer}' + '.wolai-page-ai-pi-lab-segment{display:inline-flex;border:1px solid #e1e4eb;border-radius:10px;overflow:hidden;background:#fff}' + '.wolai-page-ai-pi-lab-segment button{height:36px;border:0;background:#fff;color:#4a4f58;padding:0 16px;font:14px/1 inherit;cursor:pointer}' + '.wolai-page-ai-pi-lab-segment button[data-active=\"true\"]{background:#111827;color:#fff}' + '.wolai-page-ai-pi-lab-model-control{height:36px;min-width:190px;border:1px solid #e1e4eb;border-radius:10px;background:#fff;color:#4a4f58;padding:0 12px;font:13px/1 inherit}' + '.wolai-page-ai-pi-lab-budget{height:34px;border:1px solid #bfe6ac;border-radius:10px;background:#f4fff0;color:#329125;padding:0 12px;display:inline-flex;align-items:center;font-size:14px}' + '.wolai-page-ai-pi-lab-spacer{flex:1}' + '.wolai-page-ai-pi-lab-send{width:52px;height:52px;border:0;border-radius:15px;background:#1d9bf0;color:#fff;display:grid;place-items:center;cursor:pointer;font-size:24px;box-shadow:0 10px 24px rgba(29,155,240,.25)}' + '.wolai-page-ai-pi-lab-send:disabled{opacity:.45;cursor:default}' + '.wolai-page-ai-pi-lab-rail{display:none}' + '.wolai-page-ai-pi-lab-rail-section{border:1px solid #e5e0d8;border-radius:10px;background:#fff;overflow:hidden}' + '.wolai-page-ai-pi-lab-rail-section>summary{display:flex;align-items:center;justify-content:space-between;gap:8px;cursor:pointer;list-style:none;padding:8px 9px;font-size:11px;font-weight:650;color:#5d594f;background:#faf9f6}' + '.wolai-page-ai-pi-lab-rail-section>summary::-webkit-details-marker{display:none}' + '.wolai-page-ai-pi-lab-rail-section>summary::after{content:"▸";font-size:10px;color:#8a8378}' + '.wolai-page-ai-pi-lab-rail-section[open]>summary::after{content:"▾"}' + '.wolai-page-ai-pi-lab-rail-count{font-weight:600;color:#8a8378}' + '.wolai-page-ai-pi-lab-rail-title{padding:8px 9px;border-bottom:1px solid #eee9e0;font-size:11px;font-weight:650;color:#5d594f;background:#faf9f6}' + '.wolai-page-ai-pi-lab-tool-list{display:flex;flex-direction:column;gap:5px;padding:8px}' + '.wolai-page-ai-pi-lab-tool-pill{border:1px solid #e6e2da;border-radius:7px;background:#fff;color:#42403a;padding:5px 7px;font-size:11px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}' + '.wolai-page-ai-pi-lab-receipts{display:flex;flex-direction:column;gap:5px;padding:8px;max-height:170px;overflow:auto}' + '.wolai-page-ai-pi-lab-receipt{border-radius:7px;background:#f8f7f3;border:1px solid #e6e2da;padding:6px;font-size:11px;color:#4d4941}' + '.wolai-page-ai-pi-lab-receipt[data-allowed="false"]{background:#fff0ee;border-color:#f2c4bd;color:#9f352c}' + '.wolai-page-ai-pi-lab-diagnostics{display:none;border-top:1px solid #e6e2da;background:#fff;flex:0 0 auto}' + '.wolai-page-ai-pi-lab-drawer[data-config-open="true"] .wolai-page-ai-pi-lab-diagnostics{display:block}' + '.wolai-page-ai-pi-lab-diagnostics>summary{cursor:pointer;padding:8px 12px;font-size:11px;color:#6f6a60}' + '.wolai-page-ai-pi-lab-diagnostics pre{margin:0;padding:0 12px 10px;white-space:pre-wrap;word-break:break-word;font:11px/1.45 ui-monospace,SFMono-Regular,Menlo,monospace;color:#5d594f}' + '@media (max-width: 760px){.wolai-page-ai-pi-lab-drawer{left:10px;right:10px;top:58px;bottom:10px;width:auto}.wolai-page-ai-pi-lab-body{grid-template-columns:1fr}.wolai-page-ai-pi-lab-rail{display:none}.wolai-page-ai-pi-lab-config{grid-template-columns:1fr}}'; document.head.appendChild(style); } function escapeHtml(str) { if (str == null) return ''; return String(str) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function el(tag, attrs, children) { var elem = document.createElement(tag); if (attrs) { Object.keys(attrs).forEach(function (key) { if (key === 'className') elem.className = attrs[key]; else if (key === 'textContent') elem.textContent = attrs[key]; else if (key === 'innerHTML') elem.innerHTML = attrs[key]; else if (key === 'style' && attrs[key] && typeof attrs[key] === 'object') Object.keys(attrs[key]).forEach(function (styleKey) { elem.style[styleKey] = attrs[key][styleKey]; }); else elem.setAttribute(key, attrs[key]); }); } if (children) { (Array.isArray(children) ? children : [children]).forEach(function (child) { if (typeof child === 'string') elem.appendChild(document.createTextNode(child)); else if (child instanceof Node) elem.appendChild(child); }); } return elem; } function normalizeSlashes(value) { return String(value || '').trim().replace(/\\/g, '/'); } function currentUrl() { try { return new URL(window.location.href); } catch (_) { return new URL('/', window.location.origin); } } function currentDocumentId() { var fromBody = document.body && document.body.dataset ? String(document.body.dataset.documentId || '').trim() : ''; if (fromBody) return fromBody; var pageTab = document.querySelector('[data-mnote-main-tab="page"]'); if (pageTab instanceof HTMLElement) { var tabDocumentId = String(pageTab.getAttribute('data-document-id') || '').trim(); if (tabDocumentId) return tabDocumentId; } var match = currentUrl().pathname.match(/^\/documents\/([^/]+)/); return match ? decodeURIComponent(match[1]) : ''; } function currentWorkspaceId() { return currentUrl().searchParams.get('workspaceId') || (document.body && document.body.dataset ? String(document.body.dataset.workspaceId || '').trim() : ''); } function currentRootUri() { return currentUrl().searchParams.get('rootUri') || ''; } function localMarkdownRelativePathFromDocumentId(documentId) { var value = String(documentId || '').trim(); if (!value.startsWith('local-md:')) return ''; return value.slice('local-md:'.length).replace(/~2F/g, '/'); } function currentSelectionText() { try { var selection = window.getSelection ? window.getSelection() : null; return selection ? String(selection.toString() || '').trim() : ''; } catch (_) { return ''; } } function activeEditorSnapshot() { try { var snapshot = window.__mnoteDocumentPaneRuntime && typeof window.__mnoteDocumentPaneRuntime.getOpenEditorsSnapshot === 'function' ? window.__mnoteDocumentPaneRuntime.getOpenEditorsSnapshot() : null; if (!snapshot || typeof snapshot !== 'object') return null; if (snapshot.activeEditor) return snapshot.activeEditor; var entries = [] .concat(Array.isArray(snapshot.editors) ? snapshot.editors : []) .concat(Array.isArray(snapshot.resourceEditors) ? snapshot.resourceEditors : []); return entries.find(function (entry) { return entry && entry.active === true; }) || entries[0] || null; } catch (_) { return null; } } function currentPageContext() { var active = activeEditorSnapshot() || {}; var workspacePath = active.workspacePath && typeof active.workspacePath === 'object' ? active.workspacePath : {}; var documentId = String(workspacePath.documentId || active.documentId || currentDocumentId() || '').trim(); var pagePath = normalizeSlashes(workspacePath.relativePath || workspacePath.path || active.relativePath || active.path || localMarkdownRelativePathFromDocumentId(documentId)); var rootUri = String(workspacePath.rootUri || active.rootUri || currentRootUri() || '').trim(); var workspaceId = String(workspacePath.workspaceId || active.workspaceId || currentWorkspaceId() || '').trim(); var titleNode = document.querySelector('[data-page-title-current="true"]') || document.querySelector('.wolai-breadcrumb-current'); var title = String(active.title || workspacePath.title || (titleNode && titleNode.textContent) || document.title || '').trim(); var selection = currentSelectionText(); return { pageId: documentId, pagePath: pagePath, pageTitle: title, rootUri: rootUri, workspaceId: workspaceId, selection: selection, }; } function refreshSelectionSummary() { var selection = currentSelectionText(); if (selection) piLabState.selectionText = selection; var visibleSelection = selection || piLabState.selectionText; piLabState.selectionSummary = visibleSelection ? ('已选中 ' + visibleSelection.length + ' 字') : ''; return visibleSelection; } function applyCurrentContextToState() { var context = currentPageContext(); refreshSelectionSummary(); if (!piLabState.session) piLabState.session = {}; if (context.pagePath && !piLabState.session.pagePath) piLabState.session.pagePath = context.pagePath; if (context.pageTitle && !piLabState.session.pageTitle) piLabState.session.pageTitle = context.pageTitle; if (context.rootUri && !piLabState.session.rootUri) piLabState.session.rootUri = context.rootUri; if (context.workspaceId && !piLabState.session.workspaceId) piLabState.session.workspaceId = context.workspaceId; updateContextStrip(); return context; } function currentModelLabel() { var provider = piLabState.modelProvider || piLabState.defaultModelProvider || DEFAULT_MODEL_PROVIDER; var modelId = piLabState.modelId || piLabState.defaultModelId || DEFAULT_MODEL_ID; if (String(modelId).indexOf(provider + '/') === 0) modelId = String(modelId).slice(provider.length + 1); return [provider, modelId].filter(Boolean).join('/'); } function applyModelControls() { if (!piLabPanelEl) return; var providerSelect = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-provider]'); var modelSelect = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-id]'); var bottomModelSelect = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-id-bottom]'); var customInput = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-custom]'); if (providerSelect && providerSelect.value) piLabState.modelProvider = providerSelect.value; if (customInput && customInput.value.trim()) piLabState.modelId = customInput.value.trim(); else if (modelSelect && modelSelect.value) piLabState.modelId = modelSelect.value; else if (bottomModelSelect && bottomModelSelect.value) piLabState.modelId = bottomModelSelect.value; updateModelLabel(); } function splitModelRef(value) { var raw = String(value || '').trim(); var slash = raw.indexOf('/'); if (slash < 0) return { provider: DEFAULT_MODEL_PROVIDER, modelId: raw || DEFAULT_MODEL_ID }; return { provider: raw.slice(0, slash), modelId: raw.slice(slash + 1) }; } function applyEffectiveConfig(data) { data = data || {}; var defaultRef = splitModelRef(data.defaultModel || data.default_model || ''); piLabState.defaultModelProvider = defaultRef.provider || DEFAULT_MODEL_PROVIDER; piLabState.defaultModelId = defaultRef.modelId || DEFAULT_MODEL_ID; piLabState.modelOptions = Array.isArray(data.models) ? data.models : []; if (Array.isArray(data.allowedRoots)) { piLabState.allowedRootsSummary = data.allowedRoots.length + ' roots'; } if (!piLabPanelEl) return; var providerSelect = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-provider]'); var modelSelect = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-id]'); var bottomModelSelect = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-id-bottom]'); var providers = []; piLabState.modelOptions.forEach(function (item) { var provider = String(item.provider || DEFAULT_MODEL_PROVIDER); if (providers.indexOf(provider) < 0) providers.push(provider); }); if (!providers.length) providers.push(piLabState.defaultModelProvider); if (providerSelect) { providerSelect.innerHTML = providers.map(function (provider) { return ''; }).join(''); providerSelect.value = piLabState.modelProvider || piLabState.defaultModelProvider; } var modelOptions = piLabState.modelOptions.length ? piLabState.modelOptions : [{ provider: piLabState.defaultModelProvider, id: piLabState.defaultModelId, name: piLabState.defaultModelId, }]; var optionHtml = modelOptions.map(function (item) { var id = String(item.id || item.modelId || item.name || DEFAULT_MODEL_ID); var label = String(item.name || id); return ''; }).join(''); if (modelSelect) modelSelect.innerHTML = optionHtml; if (bottomModelSelect) bottomModelSelect.innerHTML = optionHtml; updateModelLabel(); updateContextStrip(); } function loadEffectiveConfig() { return fetch(API.EFFECTIVE, { credentials: 'same-origin', cache: 'no-store' }) .then(function (response) { if (!response.ok) throw new Error('Effective config failed: ' + response.status); return response.json(); }) .then(function (data) { applyEffectiveConfig(data); return data; }); } function currentRuntimeLabel() { var parts = []; if (piLabState.runtimeMode) parts.push(piLabState.runtimeMode); if (piLabState.runtimePid) parts.push('pid ' + piLabState.runtimePid); return parts.join(' · ') || 'runtime pending'; } function statusTone() { if (!piLabState.enabled || piLabState.status === STATE_ERROR) return 'danger'; if (piLabState.status === STATE_STREAMING || piLabState.status === STATE_STARTED) return 'ok'; if (piLabState.status === STATE_STARTING || piLabState.status === STATE_ABORTED) return 'warn'; return ''; } function statusText() { if (!piLabState.enabled) return 'disabled'; if (piLabState.status === STATE_IDLE) return 'idle'; if (piLabState.status === STATE_STARTING) return 'starting'; if (piLabState.status === STATE_STARTED) return 'ready'; if (piLabState.status === STATE_STREAMING) return 'streaming'; if (piLabState.status === STATE_ABORTED) return 'aborted'; return 'error'; } function piLabPanelHTML() { return '' + '
' + '
' + '
' + '' + '
Pi Lab 平台默认模型:omniroute/freefirst
' + '
' + '' + '' + '
' + '
' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '
' + '
' + '
' + 'idle设置omniroute/freefirst' + '
' + '' + '' + '' + 'runtimeruntime pending' + 'builtin disabledbash/read/write/edit' + '
' + '
' + '
' + '操作见顶部工具栏' + '
' + '
' + '' + '
' + '上下文未绑定0 changed' + '
' + '选区无选区' + 'allowed rootspending' + 'LightRAGdefault' + '
' + '
' + '
' + '
' + '
' + '
' + '' + '
' + '' + '' + '' + '' + '' + '
' + '
' + '' + '' + '' + '' + '不限' + '' + '' + '
' + '
' + '
' + '' + '
' + '
' + '诊断' + '
' +
        '
' + '
'; } function renderEmptyState(container) { container.innerHTML = '' + '
' + '
?
' + '开始对话' + '

试试以下示例,或直接输入您的指令

' + '
' + '' + '' + '' + '' + '' + '
' + '
'; } function renderHistory() { if (!piLabPanelEl) return; var panel = piLabPanelEl.querySelector('[data-page-ai-pi-lab-history-panel]'); if (!panel) return; if (!piLabState.history.length) { panel.innerHTML = '
暂无 Pi Lab 历史
'; return; } panel.innerHTML = piLabState.history.map(function (session) { var title = escapeHtml(session.title || session.preview || session.pageTitle || '未命名会话'); var meta = escapeHtml([session.status, session.modelId, session.updatedAt].filter(Boolean).join(' · ')); return ''; }).join(''); } function loadHistory() { return fetch(API.SESSIONS + '?limit=50', { credentials: 'same-origin', cache: 'no-store' }) .then(function (response) { if (!response.ok) throw new Error('History failed: ' + response.status); return response.json(); }) .then(function (data) { piLabState.history = Array.isArray(data.sessions) ? data.sessions : []; renderHistory(); return data; }); } function openHistorySession(sessionId) { if (!sessionId) return Promise.resolve(); return Promise.all([ fetch(API.SESSIONS + '/' + encodeURIComponent(sessionId), { credentials: 'same-origin', cache: 'no-store' }).then(function (r) { return r.json(); }), fetch(API.SESSIONS + '/' + encodeURIComponent(sessionId) + '/events?limit=500', { credentials: 'same-origin', cache: 'no-store' }).then(function (r) { return r.json(); }), ]).then(function (results) { var detail = results[0] && results[0].session || {}; var events = results[1] && Array.isArray(results[1].events) ? results[1].events : []; piLabState.sessionId = detail.sessionId || sessionId; piLabState.session = detail.runtime || detail; piLabState.messages = []; events.forEach(function (event) { var payload = event.payload || {}; if (event.eventType === 'user_prompt' && payload.message) { piLabState.messages.push({ role: 'user', text: payload.message }); } if (event.eventType === 'pi_rpc_event') { var delta = payload.assistantMessageEvent && payload.assistantMessageEvent.delta; if (delta) piLabState.messages.push({ role: 'assistant', text: delta }); } }); updateMessages(); updateConfig(); }); } function renderMessage(msg) { var card = el('article', { className: 'wolai-page-ai-pi-lab-message', 'data-role': msg.role || 'assistant', 'data-page-ai-pi-lab-message-role': msg.role || 'assistant', }); if (msg.status === 'streaming') card.setAttribute('data-page-ai-pi-lab-streaming', 'true'); var role = msg.role === 'assistant' ? 'Pi' : (msg.role === 'user' ? 'You' : 'System'); var meta = msg.status === 'streaming' ? 'streaming' : (msg.meta || ''); card.appendChild(el('div', { className: 'wolai-page-ai-pi-lab-role' }, role + (meta ? ' · ' + meta : ''))); var bubble = el('div', { className: 'wolai-page-ai-pi-lab-bubble' }); if (msg.text) { bubble.appendChild(el('div', { className: 'wolai-page-ai-pi-lab-text' + (msg.status === 'streaming' ? ' streaming-cursor' : ''), innerHTML: escapeHtml(msg.text).replace(/\n/g, '
'), })); } if (msg.toolCalls && msg.toolCalls.length) { var tools = el('div', { className: 'wolai-page-ai-pi-lab-tool-calls' }); msg.toolCalls.forEach(function (tc) { var item = el('div', { className: 'wolai-page-ai-pi-lab-tool-call', 'data-page-ai-pi-lab-tool-call': tc.name || 'tool' }); item.appendChild(el('div', { className: 'wolai-page-ai-pi-lab-tool-call-header', innerHTML: '' + escapeHtml(tc.name || 'tool') + '' + escapeHtml(tc.status || '') + '' })); if (tc.args) item.appendChild(el('pre', { textContent: JSON.stringify(tc.args, null, 2) })); if (tc.result) item.appendChild(el('pre', { textContent: typeof tc.result === 'string' ? tc.result : JSON.stringify(tc.result, null, 2) })); tools.appendChild(item); }); bubble.appendChild(tools); } if (msg.citations && msg.citations.length) { var citations = el('div', { className: 'wolai-page-ai-pi-lab-citations' }); msg.citations.forEach(function (cit, i) { citations.appendChild(el('a', { className: 'wolai-page-ai-pi-lab-citation', href: cit.url || '#', target: '_blank', rel: 'noopener', 'data-page-ai-pi-lab-citation': cit.source || '', }, '[' + (i + 1) + '] ' + (cit.title || cit.source || 'source'))); }); bubble.appendChild(citations); } if (msg.diffSummary) { var files = msg.diffSummary.files || []; bubble.appendChild(el('div', { className: 'wolai-page-ai-pi-lab-diff', 'data-page-ai-pi-lab-diff': 'true' }, 'patch · ' + files.join(', '))); } card.appendChild(bubble); return card; } function isVisibleMessage(msg) { if (!msg) return false; if (msg.status === 'streaming') return true; if (String(msg.text || '').trim()) return true; if (msg.toolCalls && msg.toolCalls.length) return true; if (msg.citations && msg.citations.length) return true; if (msg.diffSummary) return true; return msg.role !== 'assistant'; } function updateStatusBadge() { updateConfig(); } function updateModelLabel() { if (!piLabPanelEl) return; var titleLabel = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model]'); var modelChip = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-chip]'); var providerSelect = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-provider]'); var modelSelect = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-id]'); var bottomModelSelect = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-id-bottom]'); var customInput = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-custom]'); var provider = piLabState.modelProvider || piLabState.defaultModelProvider || DEFAULT_MODEL_PROVIDER; var modelId = piLabState.modelId || piLabState.defaultModelId || DEFAULT_MODEL_ID; if (String(modelId).indexOf(provider + '/') === 0) { modelId = String(modelId).slice(provider.length + 1); piLabState.modelId = modelId; } if (titleLabel) titleLabel.textContent = '默认模型:' + currentModelLabel(); if (modelChip) modelChip.textContent = currentModelLabel(); if (providerSelect && providerSelect.value !== provider) providerSelect.value = provider; if (modelSelect && modelSelect.value !== modelId && Array.prototype.some.call(modelSelect.options, function (option) { return option.value === modelId; })) { modelSelect.value = modelId; } if (bottomModelSelect && bottomModelSelect.value !== modelId && Array.prototype.some.call(bottomModelSelect.options, function (option) { return option.value === modelId; })) { bottomModelSelect.value = modelId; } if (customInput && modelId !== DEFAULT_MODEL_ID && modelId !== 'freefirst') customInput.value = modelId; } function updateConfig() { if (!piLabPanelEl) return; updateModelLabel(); var statusChip = piLabPanelEl.querySelector('[data-page-ai-pi-lab-status-chip]'); var statusNode = piLabPanelEl.querySelector('[data-page-ai-pi-lab-status-text]'); var runtimeNode = piLabPanelEl.querySelector('[data-page-ai-pi-lab-runtime-chip]'); var builtinNode = piLabPanelEl.querySelector('[data-page-ai-pi-lab-builtin-disabled]'); if (statusChip) { var tone = statusTone(); if (tone) statusChip.setAttribute('data-tone', tone); else statusChip.removeAttribute('data-tone'); } if (statusNode) statusNode.textContent = statusText(); if (runtimeNode) runtimeNode.textContent = currentRuntimeLabel(); if (builtinNode) builtinNode.style.display = piLabState.disabledBuiltinTools ? '' : 'none'; updateContextStrip(); updateButtons(); updateLauncherState(); } function updateContextStrip() { if (!piLabPanelEl) return; var currentPage = piLabPanelEl.querySelector('[data-page-ai-pi-lab-current-page]'); var selection = piLabPanelEl.querySelector('[data-page-ai-pi-lab-selection]'); var allowedRoots = piLabPanelEl.querySelector('[data-page-ai-pi-lab-allowed-roots]'); var lightrag = piLabPanelEl.querySelector('[data-page-ai-pi-lab-lightrag]'); var changedFiles = piLabPanelEl.querySelector('[data-page-ai-pi-lab-changed-files]'); var session = piLabState.session || {}; if (currentPage) currentPage.textContent = session.pageTitle || session.pagePath || document.title || '当前页面'; if (selection) selection.textContent = piLabState.selectionSummary || '无选区'; if (allowedRoots) allowedRoots.textContent = piLabState.allowedRootsSummary || 'pending'; if (lightrag) lightrag.textContent = '唯一默认'; if (changedFiles) { var files = {}; piLabState.changedFiles.forEach(function (file) { if (file) files[file] = true; }); piLabState.messages.forEach(function (msg) { if (msg && msg.diffSummary && Array.isArray(msg.diffSummary.files)) { msg.diffSummary.files.forEach(function (file) { if (file) files[file] = true; }); } }); changedFiles.textContent = String(Object.keys(files).length); } } function updateButtons() { if (!piLabPanelEl) return; var startBtn = piLabPanelEl.querySelector('[data-page-ai-pi-lab-btn-start]'); var sendBtn = piLabPanelEl.querySelector('[data-page-ai-pi-lab-btn-send]'); var abortBtn = piLabPanelEl.querySelector('[data-page-ai-pi-lab-btn-abort]'); var input = piLabPanelEl.querySelector('[data-page-ai-pi-lab-input]'); var providerSelect = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-provider]'); var modelSelect = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-id]'); var bottomModelSelect = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-id-bottom]'); var customInput = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-custom]'); var isIdle = piLabState.status === STATE_IDLE || piLabState.status === STATE_ABORTED || piLabState.status === STATE_ERROR; var isStarted = piLabState.status === STATE_STARTED; var isStreaming = piLabState.status === STATE_STREAMING || piLabState.status === STATE_STARTING; if (startBtn) { startBtn.hidden = !isIdle; startBtn.disabled = !piLabState.enabled || piLabState.status === STATE_STARTING; } if (sendBtn) { sendBtn.disabled = !piLabState.enabled || !(isStarted || piLabState.status === STATE_IDLE || piLabState.status === STATE_ABORTED || piLabState.status === STATE_ERROR) || !input || !input.value.trim(); } if (abortBtn) abortBtn.hidden = !isStreaming; [providerSelect, modelSelect, bottomModelSelect, customInput].forEach(function (node) { if (node) node.disabled = !isIdle; }); } function updateMessages() { var container = piLabPanelEl && piLabPanelEl.querySelector('[data-page-ai-pi-lab-messages]'); if (!container) return; container.innerHTML = ''; var visibleMessages = piLabState.messages.filter(isVisibleMessage); if (!visibleMessages.length) { renderEmptyState(container); return; } visibleMessages.forEach(function (msg) { container.appendChild(renderMessage(msg)); }); container.scrollTop = container.scrollHeight; updateContextStrip(); } function updateDiagnostics(text) { piLabState.diagnostics = text || piLabState.diagnostics || ''; if (!piLabPanelEl) return; var pre = piLabPanelEl.querySelector('[data-page-ai-pi-lab-diag-text]'); if (!pre) return; var lines = []; lines.push('session=' + (piLabState.sessionId || 'none')); lines.push('providerSession=' + (piLabState.providerSessionId || 'none')); lines.push('model=' + currentModelLabel()); lines.push('runtime=' + currentRuntimeLabel()); lines.push('disabledBuiltinTools=' + (piLabState.disabledBuiltinTools ? 'bash/read/write/edit' : 'pending')); if (piLabState.allowedRootsSummary) lines.push('allowedRoots=' + piLabState.allowedRootsSummary); if (piLabState.diagnostics) lines.push('message=' + piLabState.diagnostics); pre.textContent = lines.join('\n'); } function updateBuiltinDisabled() { updateConfig(); } function updateReceiptDisplay() { if (!piLabPanelEl) return; var receipts = piLabPanelEl.querySelector('[data-page-ai-pi-lab-receipts]'); var count = piLabPanelEl.querySelector('[data-page-ai-pi-lab-receipt-count]'); if (!receipts) return; if (count) count.textContent = String(piLabState.receipts.length); receipts.innerHTML = ''; if (!piLabState.receipts.length) { receipts.appendChild(el('div', { className: 'wolai-page-ai-pi-lab-receipt' }, 'No tool receipt yet')); return; } piLabState.receipts.slice(-8).reverse().forEach(function (receipt) { var label = (receipt.toolName || 'tool') + ' · ' + (receipt.allowed === false ? 'denied' : 'allowed'); if (receipt.denyReason) label += ' · ' + receipt.denyReason; if (receipt.diffSummary) label += ' · diff ' + receipt.diffSummary; if (receipt.citationCount) label += ' · citations ' + receipt.citationCount; if (receipt.afterFileVersion) label += ' · v ' + String(receipt.afterFileVersion).slice(0, 8); receipts.appendChild(el('div', { className: 'wolai-page-ai-pi-lab-receipt', 'data-allowed': receipt.allowed === false ? 'false' : 'true', }, label)); }); } function setState(newState) { piLabState.status = newState; updateConfig(); } function checkStatus() { return Promise.all([ fetch(API.STATUS, { credentials: 'same-origin', cache: 'no-store' }).then(function (r) { return r.json(); }), loadEffectiveConfig().catch(function () { return null; }), ]) .then(function (results) { var data = results[0] || {}; piLabState.enabled = !!data.enabled; piLabState.sessionId = data.sessionId || piLabState.sessionId || null; piLabState.providerSessionId = data.providerSessionId || piLabState.providerSessionId || null; // Consume backend status/default fields when present. if (data.defaultModelProvider) piLabState.defaultModelProvider = data.defaultModelProvider; if (data.defaultModelId) piLabState.defaultModelId = data.defaultModelId; piLabState.modelProvider = (data.session && data.session.modelProvider) || piLabState.modelProvider || null; piLabState.modelId = (data.session && data.session.modelId) || piLabState.modelId || null; piLabState.session = data.session || piLabState.session || null; piLabState.allowedRootsSummary = summarizeAllowedRoots(data.session && data.session.allowedRootsSnapshot); piLabState.runtimeMode = data.runtimeMode || (data.session && data.session.runtimeMode) || piLabState.runtimeMode || null; piLabState.runtimePid = data.pid || (data.session && data.session.runtimePid) || piLabState.runtimePid || null; piLabState.disabledBuiltinTools = data.disabledPiBuiltinTools || piLabState.disabledBuiltinTools || null; if (data.session && data.session.status === 'runtime_running') { piLabState.sessionId = data.session.sessionId; setState(STATE_STARTED); if (!piLabEventSource || piLabEventSource.readyState === EventSource.CLOSED) connectEventSource(data.session.sessionId); } updateBuiltinDisabled(); updateReceiptDisplay(); updateDiagnostics(''); updateMessages(); return data; }) .catch(function (error) { piLabState.enabled = false; setState(STATE_ERROR); updateDiagnostics(error && error.message ? error.message : 'status failed'); }); } function startRuntime() { if (!piLabState.enabled || piLabState.status === STATE_STARTING || piLabState.status === STATE_STREAMING) return Promise.resolve({ skipped: true }); var context = applyCurrentContextToState(); applyModelControls(); setState(STATE_STARTING); updateDiagnostics('Starting Pi runtime'); return fetch(API.START, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ rootUri: context.rootUri || undefined, workspaceId: context.workspaceId || undefined, pagePath: context.pagePath || undefined, pageTitle: context.pageTitle || undefined, modelProvider: piLabState.modelProvider || piLabState.defaultModelProvider, modelId: piLabState.modelId || piLabState.defaultModelId, }), }) .then(function (r) { if (!r.ok) throw new Error('Start failed: ' + r.status); return r.json(); }) .then(function (data) { var session = data.session || {}; piLabState.sessionId = session.sessionId || data.sessionId || piLabState.sessionId; piLabState.providerSessionId = session.providerSessionId || data.providerSessionId || piLabState.providerSessionId; piLabState.modelProvider = session.modelProvider || piLabState.modelProvider || null; piLabState.modelId = session.modelId || piLabState.modelId || null; piLabState.runtimeMode = session.runtimeMode || data.runtimeMode || piLabState.runtimeMode || null; piLabState.runtimePid = session.runtimePid || data.pid || piLabState.runtimePid || null; piLabState.disabledBuiltinTools = data.disabledPiBuiltinTools || piLabState.disabledBuiltinTools || null; connectEventSource(); setState(STATE_STARTED); updateDiagnostics('Pi runtime ready'); piLabState.messages.push({ role: 'system', text: 'Pi session started: ' + (piLabState.sessionId || 'session') }); updateMessages(); return data; }) .catch(function (err) { setState(STATE_ERROR); updateDiagnostics('Start error: ' + err.message); throw err; }); } function connectEventSource(sessionId) { if (piLabEventSource) { piLabEventSource.close(); piLabEventSource = null; } var sid = sessionId || piLabState.sessionId; if (!sid) return; var es = new EventSource(API.EVENTS + '?sessionId=' + encodeURIComponent(sid), { withCredentials: true }); es.addEventListener('connected', function () { updateDiagnostics('SSE connected'); }); es.addEventListener('runtime_started', function (e) { try { var data = JSON.parse(e.data); var payload = data.payload || data; piLabState.runtimePid = payload.pid || piLabState.runtimePid; piLabState.runtimeMode = payload.runtimeMode || piLabState.runtimeMode; piLabState.disabledBuiltinTools = payload.disabledBuiltinTools || piLabState.disabledBuiltinTools; setState(STATE_STARTED); updateDiagnostics('runtime_started'); } catch (_) {} }); es.addEventListener('pi_rpc_event', function (e) { try { var data = JSON.parse(e.data); handlePiRpcEvent(data.payload || data); } catch (_) {} }); es.addEventListener('tool_call', function (e) { try { var data = JSON.parse(e.data); var payload = data.payload || data; applyToolCallSideEffects({ ok: payload.allowed !== false, toolName: payload.toolName, result: { rootUri: payload.rootUri, relativePath: payload.relativePath, diffSummary: payload.diffSummary, }, }); piLabState.receipts.push({ toolName: payload.toolName, allowed: payload.allowed !== false, denyReason: payload.denyReason || null, diffSummary: payload.diffSummary || null, citationCount: payload.citationCount || 0, beforeFileVersion: payload.beforeFileVersion || null, afterFileVersion: payload.afterFileVersion || null, }); if (payload.normalizedFilePath && payload.diffSummary) piLabState.changedFiles.push(payload.normalizedFilePath); updateReceiptDisplay(); updateContextStrip(); } catch (_) {} }); es.addEventListener('runtime_aborted', function () { setState(STATE_ABORTED); if (streamingAssistantMsg) { streamingAssistantMsg.status = 'done'; streamingAssistantMsg = null; } updateMessages(); }); es.addEventListener('error', function () { if (piLabEventSource && piLabEventSource.readyState === EventSource.CLOSED) updateDiagnostics('SSE connection closed'); }); piLabEventSource = es; } function ensureStreamingAssistant() { if (!streamingAssistantMsg) { streamingAssistantMsg = { role: 'assistant', text: '', toolCalls: [], citations: [], diffSummary: null, status: 'streaming' }; piLabState.messages.push(streamingAssistantMsg); setState(STATE_STREAMING); } return streamingAssistantMsg; } function extractVisibleAssistantText(message) { if (!message || !Array.isArray(message.content)) return ''; return message.content .filter(function (part) { return part && part.type === 'text' && typeof part.text === 'string'; }) .map(function (part) { return part.text; }) .join(''); } function handlePiRpcEvent(payload) { if (!payload) return; var eventType = payload.type; var msgData = payload.assistantMessageEvent || payload; var msgDataType = msgData && msgData.type; if (msgDataType === 'thinking_start' || msgDataType === 'thinking_delta' || msgDataType === 'thinking_end') { updateDiagnostics('Pi thinking event hidden from visible reply'); return; } if (msgDataType === 'text_start') { ensureStreamingAssistant(); updateMessages(); return; } if (msgDataType === 'text_delta') { var msg = ensureStreamingAssistant(); if (msgData.delta) msg.text += msgData.delta; if (msgData.text && !msgData.delta) msg.text = msgData.text; updateMessages(); } else if (msgDataType === 'text_end') { var textEndMsg = ensureStreamingAssistant(); textEndMsg.text = msgData.content || textEndMsg.text; updateMessages(); } else if (eventType === 'message_end' && payload.message && payload.message.role === 'assistant') { var messageEndMsg = ensureStreamingAssistant(); var visibleText = extractVisibleAssistantText(payload.message); if (visibleText) messageEndMsg.text = visibleText; messageEndMsg.status = 'done'; streamingAssistantMsg = null; setState(STATE_STARTED); updateMessages(); } else if (eventType === 'agent_end') { var finalMessages = Array.isArray(payload.messages) ? payload.messages : []; var finalAssistant = finalMessages.slice().reverse().find(function (message) { return message && message.role === 'assistant'; }); var finalText = extractVisibleAssistantText(finalAssistant); var lastVisibleAssistant = piLabState.messages.slice().reverse().find(function (message) { return message && message.role === 'assistant'; }); if (!streamingAssistantMsg && lastVisibleAssistant && lastVisibleAssistant.status === 'done') { if (finalText && !lastVisibleAssistant.text) { lastVisibleAssistant.text = finalText; updateMessages(); } setState(STATE_STARTED); return; } var agentEndMsg = ensureStreamingAssistant(); if (finalText) agentEndMsg.text = finalText; agentEndMsg.status = 'done'; streamingAssistantMsg = null; setState(STATE_STARTED); updateMessages(); } else if (eventType === 'tool_call_start' || msgDataType === 'tool_call_start') { var startMsg = ensureStreamingAssistant(); startMsg.toolCalls.push({ name: payload.toolName || payload.name || 'tool', args: payload.args || payload.params || {}, status: 'running' }); updateMessages(); } else if (eventType === 'tool_call_end' || msgDataType === 'tool_call_end') { var endMsg = ensureStreamingAssistant(); var last = endMsg.toolCalls[endMsg.toolCalls.length - 1]; if (last) { last.status = 'done'; last.result = payload.result || msgData.result || {}; } updateMessages(); } else if (eventType === 'citation' || msgDataType === 'citation') { var citMsg = ensureStreamingAssistant(); citMsg.citations.push({ source: payload.source || msgData.source || '', title: payload.title || msgData.title || '', url: payload.url || msgData.url || '#' }); updateMessages(); } else if (eventType === 'diff' || msgDataType === 'diff') { var diffMsg = ensureStreamingAssistant(); diffMsg.diffSummary = diffMsg.diffSummary || { files: [] }; diffMsg.diffSummary.files = payload.files || msgData.files || []; diffMsg.diffSummary.files.forEach(function (file) { if (file) piLabState.changedFiles.push(file); }); updateMessages(); } else if (eventType === 'done' || msgDataType === 'done') { var doneMsg = ensureStreamingAssistant(); doneMsg.status = 'done'; if (msgData.text) doneMsg.text = msgData.text; if (msgData.toolCalls) doneMsg.toolCalls = msgData.toolCalls; if (msgData.citations) doneMsg.citations = msgData.citations; if (msgData.diffSummary) doneMsg.diffSummary = msgData.diffSummary; streamingAssistantMsg = null; setState(STATE_STARTED); updateMessages(); } else if (eventType === 'error' || msgDataType === 'error') { var errMsg = ensureStreamingAssistant(); errMsg.text += (errMsg.text ? '\n' : '') + '错误: ' + (payload.error || msgData.message || 'Pi runtime error'); errMsg.status = 'done'; streamingAssistantMsg = null; setState(STATE_STARTED); updateMessages(); } } function sendPrompt(text) { var prompt = String(text || '').trim(); if (!prompt) return; piLabState.messages.push({ role: 'user', text: prompt }); streamingAssistantMsg = { role: 'assistant', text: '', toolCalls: [], citations: [], diffSummary: null, status: 'streaming' }; piLabState.messages.push(streamingAssistantMsg); setState(STATE_STREAMING); updateMessages(); return fetch(API.SEND, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId: piLabState.sessionId, message: prompt }), }) .then(function (r) { if (!r.ok) throw new Error('Send failed: ' + r.status); return r.json(); }) .then(function (data) { if (!data.accepted) throw new Error('Pi Lab rejected prompt'); if (!piLabEventSource || piLabEventSource.readyState === EventSource.CLOSED) connectEventSource(data.sessionId || piLabState.sessionId); }) .catch(function (err) { if (streamingAssistantMsg) { streamingAssistantMsg.text = '错误: ' + err.message; streamingAssistantMsg.status = 'done'; streamingAssistantMsg = null; setState(STATE_STARTED); updateMessages(); } }); } function ensureRuntimeReady() { if (piLabState.status === STATE_IDLE || piLabState.status === STATE_ABORTED || piLabState.status === STATE_ERROR || !piLabState.sessionId) { return startRuntime(); } return Promise.resolve({ ok: true }); } function callMNoteTool(toolName, params) { return ensureRuntimeReady().then(function () { return fetch('/api/page-ai/pi/tool-call', { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', accept: 'application/json' }, body: JSON.stringify({ sessionId: piLabState.sessionId, toolName: toolName, params: params || {}, }), }); }).then(function (response) { return response.json().catch(function () { return {}; }).then(function (payload) { if (!response.ok) throw new Error(payload.message || ('Tool call failed: ' + response.status)); return payload; }); }); } function emitLocalFolderRefresh(rootUri, relativePath, source) { var normalizedPath = normalizeSlashes(relativePath).replace(/^\/+/, ''); if (!rootUri || !normalizedPath) return false; var documentId = /\.md(?:own)?$/i.test(normalizedPath) ? 'local-md:' + normalizedPath.replace(/\//g, '~2F') : ''; if (window.__mnoteLocalFolderEventBus && typeof window.__mnoteLocalFolderEventBus.emitChangedFiles === 'function') { window.__mnoteLocalFolderEventBus.emitChangedFiles({ source: source || 'pi_lab_tool_call', reason: source || 'pi_lab_tool_call', rootUri: rootUri, workspaceId: (piLabState.session && piLabState.session.workspaceId) || currentWorkspaceId(), changedFiles: [{ relativePath: normalizedPath, documentId: documentId, changeType: 'modified', }], affectedParents: [{ relativePath: normalizedPath.indexOf('/') >= 0 ? normalizedPath.split('/').slice(0, -1).join('/') : '', reason: source || 'pi_lab_tool_call', }], }); return true; } window.dispatchEvent(new CustomEvent('tree:local-folder-watch-batch', { detail: { payload: { schema: 'mnote.local_folder.watch_batch.v1', source: source || 'pi_lab_tool_call', rootUri: rootUri, changedPaths: [{ relativePath: normalizedPath, documentId: documentId, changeType: 'modified' }], }, }, })); return true; } function refreshPatchedCurrentDocument(rootUri, relativePath, source) { emitLocalFolderRefresh(rootUri, relativePath, source || 'pi_lab_patch'); var documentId = /\.md(?:own)?$/i.test(String(relativePath || '')) ? 'local-md:' + normalizeSlashes(relativePath).replace(/^\/+/, '').replace(/\//g, '~2F') : currentDocumentId(); var api = window.__mnoteDocumentPaneRuntime; if (api && typeof api.refreshDocument === 'function') { api.refreshDocument({ documentId: documentId, workspaceId: (piLabState.session && piLabState.session.workspaceId) || currentWorkspaceId(), source: source || 'pi_lab_patch', }).catch(function () {}); } } function applyToolCallSideEffects(payload) { if (!payload || payload.ok !== true) return; var result = payload.result || {}; if (payload.toolName === 'mnote.local_file.patch' || result.diffSummary) { var rootUri = String(result.rootUri || (piLabState.session && piLabState.session.rootUri) || currentRootUri() || '').trim(); var relativePath = String(result.relativePath || '').trim(); if (!relativePath && result.path) relativePath = result.path; if (relativePath) refreshPatchedCurrentDocument(rootUri, relativePath, 'pi_lab_local_file_patch'); } } function setComposerText(text) { if (!piLabPanelEl) return; var input = piLabPanelEl.querySelector('[data-page-ai-pi-lab-input]'); if (!input) return; input.value = text || ''; updateButtons(); input.focus(); } function handleQuickAction(kind) { var context = applyCurrentContextToState(); if (kind === 'read-page') { if (!context.rootUri || !context.pagePath) { setComposerText('读取当前页面,总结页面要点。'); updateDiagnostics('当前页缺少 rootUri/pagePath,无法调用 mnote.current_page.read'); return; } callMNoteTool('mnote.current_page.read', { rootUri: context.rootUri, pagePath: context.pagePath, }).then(function (payload) { var result = payload.result || {}; var content = String(result.content || '').slice(0, 12000); piLabState.messages.push({ role: 'system', text: '已读取当前页:' + (context.pageTitle || context.pagePath) + '\nfileVersion=' + (result.fileVersion || 'unknown'), }); setComposerText('基于当前页面内容回答:\n\n' + content); updateMessages(); }).catch(function (error) { updateDiagnostics(error.message || String(error)); setComposerText('读取当前页面,总结页面要点。'); }); return; } if (kind === 'selection') { var selection = refreshSelectionSummary(); callMNoteTool('mnote.selection.read', { selection: selection, selectionSource: 'mnote_sidebar_host', rootUri: context.rootUri || null, pagePath: context.pagePath || null, }).then(function () { setComposerText(selection ? '基于当前选区给出修改建议:\n\n' + selection : '当前没有选区。请先在编辑器中选中文本,再让 Pi 读取选区。'); updateContextStrip(); }).catch(function () { setComposerText(selection || '当前没有选区。'); }); return; } setComposerText('通过 LightRAG 查询当前页面相关资料,并带 citation 回复。'); } function sendCurrentInput() { if (!piLabPanelEl) return; var input = piLabPanelEl.querySelector('[data-page-ai-pi-lab-input]'); var text = input ? input.value.trim() : ''; if (!text) return; if (input) input.value = ''; updateButtons(); var run = piLabState.status === STATE_IDLE || piLabState.status === STATE_ABORTED || piLabState.status === STATE_ERROR ? startRuntime() : Promise.resolve(); run.then(function () { return sendPrompt(text); }).catch(function () {}); } function abortPrompt() { if (piLabEventSource) { piLabEventSource.close(); piLabEventSource = null; } if (piLabState.sessionId) { fetch(API.ABORT, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId: piLabState.sessionId }), }).catch(function () {}); } if (streamingAssistantMsg) { streamingAssistantMsg.text += (streamingAssistantMsg.text ? '\n' : '') + '已中止'; streamingAssistantMsg.status = 'done'; streamingAssistantMsg = null; } setState(STATE_ABORTED); updateMessages(); } function ensurePanel() { if (piLabPanelEl) return piLabPanelEl; var container = document.createElement('div'); container.innerHTML = piLabPanelHTML(); piLabPanelEl = container.firstElementChild; wireEvents(); updateReceiptDisplay(); updateMessages(); return piLabPanelEl; } function summarizeAllowedRoots(snapshot) { if (!snapshot) return piLabState.allowedRootsSummary || 'pending'; var roots = Array.isArray(snapshot.roots) ? snapshot.roots : (Array.isArray(snapshot) ? snapshot : []); if (!roots.length) return '0 roots'; return roots.length + ' root' + (roots.length === 1 ? '' : 's'); } function ensureDrawer() { if (piLabDrawerEl) return piLabDrawerEl; piLabDrawerEl = el('section', { className: 'wolai-page-ai-pi-lab-drawer', 'data-page-ai-pi-lab': 'drawer', 'data-page-ai-pi-lab-drawer': 'true', 'aria-label': 'Pi Lab', hidden: 'true', }); piLabDrawerEl.appendChild(ensurePanel()); document.body.appendChild(piLabDrawerEl); return piLabDrawerEl; } function setDrawerVisible(visible) { var drawer = ensureDrawer(); drawer.hidden = !visible; drawer.setAttribute('data-open', visible ? 'true' : 'false'); if (visible) drawer.setAttribute('data-minimized', 'false'); } function showPiLab() { piLabState.active = true; setDrawerVisible(true); applyCurrentContextToState(); updateConfig(); checkStatus(); var input = piLabPanelEl && piLabPanelEl.querySelector('[data-page-ai-pi-lab-input]'); if (input) setTimeout(function () { input.focus(); }, 60); } function hidePiLab() { piLabState.active = false; if (piLabDrawerEl) setDrawerVisible(false); updateLauncherState(); } function updateLauncherState() { if (!piLabLauncherEl) return; piLabLauncherEl.setAttribute('data-state', piLabState.status); piLabLauncherEl.hidden = false; } function installLauncher() { if (piLabLauncherEl) return; piLabLauncherEl = el('button', { type: 'button', className: 'wolai-page-ai-pi-lab-launcher', 'data-page-ai-pi-lab-launcher': 'true', title: 'Pi Lab', 'aria-label': '打开 Pi Lab', }, 'π'); piLabLauncherEl.addEventListener('click', showPiLab); document.body.appendChild(piLabLauncherEl); } function wireEvents() { if (!piLabPanelEl || piLabPanelEl.getAttribute('data-wired') === 'true') return; piLabPanelEl.setAttribute('data-wired', 'true'); var input = piLabPanelEl.querySelector('[data-page-ai-pi-lab-input]'); var startBtn = piLabPanelEl.querySelector('[data-page-ai-pi-lab-btn-start]'); var sendBtn = piLabPanelEl.querySelector('[data-page-ai-pi-lab-btn-send]'); var abortBtn = piLabPanelEl.querySelector('[data-page-ai-pi-lab-btn-abort]'); var clearBtn = piLabPanelEl.querySelector('[data-page-ai-pi-lab-btn-clear]'); var closeBtn = piLabPanelEl.querySelector('[data-page-ai-pi-lab-close]'); var minimizeBtn = piLabPanelEl.querySelector('[data-page-ai-pi-lab-minimize]'); var providerSelect = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-provider]'); var modelSelect = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-id]'); var bottomModelSelect = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-id-bottom]'); var customInput = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-custom]'); var settingsBtn = piLabPanelEl.querySelector('[data-page-ai-pi-lab-open-settings]'); var historyBtn = piLabPanelEl.querySelector('[data-page-ai-pi-lab-history]'); var openBtn = piLabPanelEl.querySelector('[data-page-ai-pi-lab-open]'); var newBtns = piLabPanelEl.querySelectorAll('[data-page-ai-pi-lab-new], [data-page-ai-pi-lab-new-small]'); if (startBtn) startBtn.addEventListener('click', startRuntime); if (sendBtn) sendBtn.addEventListener('click', sendCurrentInput); if (abortBtn) abortBtn.addEventListener('click', abortPrompt); if (clearBtn) clearBtn.addEventListener('click', function () { piLabState.messages = []; piLabState.receipts = []; streamingAssistantMsg = null; updateMessages(); updateReceiptDisplay(); }); if (closeBtn) closeBtn.addEventListener('click', hidePiLab); if (minimizeBtn) { minimizeBtn.addEventListener('click', function () { var drawer = ensureDrawer(); var minimized = drawer.getAttribute('data-minimized') === 'true'; drawer.setAttribute('data-minimized', minimized ? 'false' : 'true'); }); } if (input) { input.addEventListener('input', updateButtons); input.addEventListener('keydown', function (e) { if (e.isComposing || e.key === 'Process') return; if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendCurrentInput(); } if (e.key === 'Escape' && piLabState.status === STATE_STREAMING) { e.preventDefault(); abortPrompt(); } }); } [providerSelect, modelSelect, bottomModelSelect].forEach(function (node) { if (node) node.addEventListener('change', applyModelControls); }); if (customInput) customInput.addEventListener('input', applyModelControls); if (settingsBtn) { settingsBtn.addEventListener('click', function () { var settings = piLabPanelEl.querySelector('[data-page-ai-pi-lab-settings]'); var drawer = ensureDrawer(); var isOpen = drawer.getAttribute('data-config-open') === 'true'; drawer.setAttribute('data-config-open', isOpen ? 'false' : 'true'); if (settings) settings.open = !isOpen; var context = piLabPanelEl.querySelector('[data-page-ai-pi-lab-context-strip]'); if (context) context.open = !isOpen; }); } if (historyBtn) { historyBtn.addEventListener('click', function () { var panel = piLabPanelEl.querySelector('[data-page-ai-pi-lab-history-panel]'); if (!panel) return; panel.hidden = !panel.hidden; if (!panel.hidden) loadHistory().catch(function (error) { panel.innerHTML = '
' + escapeHtml(error.message || '历史加载失败') + '
'; }); }); } if (openBtn) { openBtn.addEventListener('click', function () { window.location.assign('/user/ai'); }); } Array.prototype.slice.call(newBtns || []).forEach(function (btn) { btn.addEventListener('click', function () { piLabState.messages = []; piLabState.receipts = []; streamingAssistantMsg = null; updateMessages(); updateReceiptDisplay(); }); }); piLabPanelEl.addEventListener('click', function (event) { var historyTarget = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-history-session]') : null; if (historyTarget) { openHistorySession(historyTarget.getAttribute('data-page-ai-pi-lab-history-session')).catch(function (error) { updateDiagnostics(error.message || '历史会话加载失败'); }); return; } var target = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-example]') : null; if (!target) return; setComposerText(target.getAttribute('data-page-ai-pi-lab-example') || target.textContent || ''); }); Array.prototype.slice.call(piLabPanelEl.querySelectorAll('[data-page-ai-pi-lab-quick]')).forEach(function (btn) { btn.addEventListener('click', function () { handleQuickAction(btn.getAttribute('data-page-ai-pi-lab-quick')); }); }); } function setupPostMessageListener() { window.addEventListener('message', function (event) { if (!event.data || event.data.source !== 'mnote-sidebar') return; if (event.data.type === 'mnote:pi-lab-status') checkStatus(); if (event.data.type === 'mnote:pi-lab-show') showPiLab(); if (event.data.type === 'mnote:pi-lab-hide') hidePiLab(); }); } function setupSelectionListener() { document.addEventListener('selectionchange', function () { if (!piLabState.active) return; refreshSelectionSummary(); updateContextStrip(); }, true); window.addEventListener('mnote:leptos-tiptap-spike:selection', function () { if (!piLabState.active) return; refreshSelectionSummary(); updateContextStrip(); }, true); } function createSidebarPageAiPiLabRuntime(context) { if (piLabInstalled) return piLabState; piLabInstalled = true; injectStyles(); piLabState.standalone = !!(context && context.standalone); ensureDrawer(); if (piLabState.standalone) { setDrawerVisible(true); piLabState.active = true; } else { installLauncher(); } setupPostMessageListener(); setupSelectionListener(); updateConfig(); updateReceiptDisplay(); checkStatus(); return piLabState; } if (typeof window !== 'undefined') { window.createSidebarPageAiPiLabRuntime = createSidebarPageAiPiLabRuntime; } })();