1529 lines
83 KiB
JavaScript
1529 lines
83 KiB
JavaScript
// == 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, '"')
|
||
.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 '<option value="' + escapeHtml(provider) + '">' + escapeHtml(provider) + '</option>';
|
||
}).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 '<option value="' + escapeHtml(id) + '">' + escapeHtml(label) + '</option>';
|
||
}).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 '' +
|
||
'<div class="wolai-page-ai-pi-lab-shell" data-page-ai-pi-lab="panel">' +
|
||
'<div class="wolai-page-ai-pi-lab-topbar">' +
|
||
'<div class="wolai-page-ai-pi-lab-brand">' +
|
||
'<div class="wolai-page-ai-pi-lab-logo">π</div>' +
|
||
'<div class="wolai-page-ai-pi-lab-title" data-page-ai-pi-lab-title><strong>Pi Lab 平台</strong><span data-page-ai-pi-lab-model>默认模型:omniroute/freefirst</span></div>' +
|
||
'</div>' +
|
||
'<button type="button" class="wolai-page-ai-pi-lab-icon-btn" data-page-ai-pi-lab-minimize aria-label="最小化 Pi Lab">−</button>' +
|
||
'<button type="button" class="wolai-page-ai-pi-lab-icon-btn" data-page-ai-pi-lab-close aria-label="关闭 Pi Lab">×</button>' +
|
||
'</div>' +
|
||
'<div class="wolai-page-ai-pi-lab-commandbar" data-page-ai-pi-lab-commandbar>' +
|
||
'<button type="button" class="wolai-page-ai-pi-lab-tool-icon" data-page-ai-pi-lab-new title="新对话" aria-label="新对话">⊞</button>' +
|
||
'<button type="button" class="wolai-page-ai-pi-lab-tool-icon" data-page-ai-pi-lab-history title="历史" aria-label="历史">◷</button>' +
|
||
'<button type="button" class="wolai-page-ai-pi-lab-tool-icon" data-page-ai-pi-lab-open title="打开" aria-label="打开">↗</button>' +
|
||
'<button type="button" class="wolai-page-ai-pi-lab-tool-icon" data-page-ai-pi-lab-clock title="运行状态" aria-label="运行状态">◴</button>' +
|
||
'<button type="button" class="wolai-page-ai-pi-lab-tool-icon" data-page-ai-pi-lab-notify title="通知" aria-label="通知">♧</button>' +
|
||
'<span class="wolai-page-ai-pi-lab-commandbar-spacer"></span>' +
|
||
'<button type="button" class="wolai-page-ai-pi-lab-tool-icon" data-page-ai-pi-lab-btn-start title="启动 Pi runtime" aria-label="启动 Pi runtime">▶</button>' +
|
||
'<button type="button" class="wolai-page-ai-pi-lab-tool-icon" data-page-ai-pi-lab-btn-abort title="中止" aria-label="中止" hidden>■</button>' +
|
||
'<button type="button" class="wolai-page-ai-pi-lab-tool-icon" data-page-ai-pi-lab-btn-clear title="清空" aria-label="清空">⌫</button>' +
|
||
'<button type="button" class="wolai-page-ai-pi-lab-tool-icon" data-page-ai-pi-lab-open-settings title="设置" aria-label="设置">⚙</button>' +
|
||
'</div>' +
|
||
'<div class="wolai-page-ai-pi-lab-config">' +
|
||
'<details class="wolai-page-ai-pi-lab-settings" data-page-ai-pi-lab-settings>' +
|
||
'<summary><span class="wolai-page-ai-pi-lab-chip" data-page-ai-pi-lab-status-chip><strong data-page-ai-pi-lab-status-text>idle</strong></span><span>设置</span><strong data-page-ai-pi-lab-model-chip>omniroute/freefirst</strong></summary>' +
|
||
'<div class="wolai-page-ai-pi-lab-settings-grid">' +
|
||
'<label class="wolai-page-ai-pi-lab-field">Provider<select data-page-ai-pi-lab-model-provider><option value="omniroute">Omniroute</option></select></label>' +
|
||
'<label class="wolai-page-ai-pi-lab-field">Model<select data-page-ai-pi-lab-model-id><option value="freefirst">freefirst</option></select></label>' +
|
||
'<label class="wolai-page-ai-pi-lab-field">自定义模型<input data-page-ai-pi-lab-model-custom placeholder="可选,重启后生效"></label>' +
|
||
'<span class="wolai-page-ai-pi-lab-chip"><span>runtime</span><strong data-page-ai-pi-lab-runtime-chip>runtime pending</strong></span>' +
|
||
'<span class="wolai-page-ai-pi-lab-chip" data-page-ai-pi-lab-builtin-disabled><span>builtin disabled</span><strong>bash/read/write/edit</strong></span>' +
|
||
'</div>' +
|
||
'</details>' +
|
||
'<div class="wolai-page-ai-pi-lab-actions">' +
|
||
'<span class="wolai-page-ai-pi-lab-chip"><span>操作</span><strong>见顶部工具栏</strong></span>' +
|
||
'</div>' +
|
||
'</div>' +
|
||
'<div class="wolai-page-ai-pi-lab-history-panel" data-page-ai-pi-lab-history-panel hidden>' +
|
||
'<div class="wolai-page-ai-pi-lab-empty">加载历史...</div>' +
|
||
'</div>' +
|
||
'<details class="wolai-page-ai-pi-lab-context-strip" data-page-ai-pi-lab-context-strip>' +
|
||
'<summary><span class="wolai-page-ai-pi-lab-context-title">上下文</span><span class="wolai-page-ai-pi-lab-context-current" data-page-ai-pi-lab-current-page>未绑定</span><span class="wolai-page-ai-pi-lab-context-count"><strong data-page-ai-pi-lab-changed-files>0</strong> changed</span></summary>' +
|
||
'<div class="wolai-page-ai-pi-lab-context-content">' +
|
||
'<span class="wolai-page-ai-pi-lab-chip" data-page-ai-pi-lab-context="selection"><span>选区</span><strong data-page-ai-pi-lab-selection>无选区</strong></span>' +
|
||
'<span class="wolai-page-ai-pi-lab-chip" data-page-ai-pi-lab-context="allowed-roots"><span>allowed roots</span><strong data-page-ai-pi-lab-allowed-roots>pending</strong></span>' +
|
||
'<span class="wolai-page-ai-pi-lab-chip" data-page-ai-pi-lab-context="lightrag"><span>LightRAG</span><strong data-page-ai-pi-lab-lightrag>default</strong></span>' +
|
||
'</div>' +
|
||
'</details>' +
|
||
'<div class="wolai-page-ai-pi-lab-body">' +
|
||
'<div class="wolai-page-ai-pi-lab-main">' +
|
||
'<div class="wolai-page-ai-pi-lab-messages" data-page-ai-pi-lab-messages></div>' +
|
||
'<div class="wolai-page-ai-pi-lab-composer">' +
|
||
'<textarea class="wolai-page-ai-pi-lab-input" data-page-ai-pi-lab-input placeholder="问 Pi..." rows="3"></textarea>' +
|
||
'<div class="wolai-page-ai-pi-lab-composer-bar">' +
|
||
'<button type="button" class="wolai-page-ai-pi-lab-quick" data-page-ai-pi-lab-quick="read-page">当前页</button>' +
|
||
'<button type="button" class="wolai-page-ai-pi-lab-quick" data-page-ai-pi-lab-quick="selection">选区</button>' +
|
||
'<button type="button" class="wolai-page-ai-pi-lab-quick" data-page-ai-pi-lab-quick="rag">LightRAG</button>' +
|
||
'<span class="wolai-page-ai-pi-lab-spacer"></span>' +
|
||
'<button type="button" class="wolai-page-ai-pi-lab-send" data-page-ai-pi-lab-btn-send disabled aria-label="发送">↑</button>' +
|
||
'</div>' +
|
||
'<div class="wolai-page-ai-pi-lab-modebar" data-page-ai-pi-lab-modebar>' +
|
||
'<button type="button" class="wolai-page-ai-pi-lab-square" data-page-ai-pi-lab-new-small title="新对话">+</button>' +
|
||
'<span class="wolai-page-ai-pi-lab-segment"><button type="button" data-active="true">Build</button><button type="button">Plan</button></span>' +
|
||
'<select class="wolai-page-ai-pi-lab-model-control" data-page-ai-pi-lab-model-id-bottom><option value="freefirst">omniroute/freefirst</option></select>' +
|
||
'<button type="button" class="wolai-page-ai-pi-lab-square" data-page-ai-pi-lab-lightning title="快速">ϟ</button>' +
|
||
'<span class="wolai-page-ai-pi-lab-budget">不限</span>' +
|
||
'<button type="button" class="wolai-page-ai-pi-lab-square" data-page-ai-pi-lab-quick="read-page" title="当前页">▤</button>' +
|
||
'<button type="button" class="wolai-page-ai-pi-lab-square" data-page-ai-pi-lab-quick="selection" title="选区">▣</button>' +
|
||
'</div>' +
|
||
'</div>' +
|
||
'</div>' +
|
||
'<aside class="wolai-page-ai-pi-lab-rail" data-page-ai-pi-lab-rail>' +
|
||
'<details class="wolai-page-ai-pi-lab-rail-section">' +
|
||
'<summary>MNote tools <span class="wolai-page-ai-pi-lab-rail-count">5</span></summary>' +
|
||
'<div class="wolai-page-ai-pi-lab-tool-list" data-page-ai-pi-lab-tool-list>' +
|
||
'<span class="wolai-page-ai-pi-lab-tool-pill">mnote.current_page.read</span>' +
|
||
'<span class="wolai-page-ai-pi-lab-tool-pill">mnote.selection.read</span>' +
|
||
'<span class="wolai-page-ai-pi-lab-tool-pill">mnote.local_file.patch</span>' +
|
||
'<span class="wolai-page-ai-pi-lab-tool-pill">mnote.knowledge_rag.query</span>' +
|
||
'<span class="wolai-page-ai-pi-lab-tool-pill">mnote.reference.open</span>' +
|
||
'</div>' +
|
||
'</details>' +
|
||
'<details class="wolai-page-ai-pi-lab-rail-section" data-page-ai-pi-lab-receipts-section>' +
|
||
'<summary>Receipts <span class="wolai-page-ai-pi-lab-rail-count" data-page-ai-pi-lab-receipt-count>0</span></summary>' +
|
||
'<div class="wolai-page-ai-pi-lab-receipts" data-page-ai-pi-lab-receipts></div>' +
|
||
'</details>' +
|
||
'</aside>' +
|
||
'</div>' +
|
||
'<details class="wolai-page-ai-pi-lab-diagnostics" data-page-ai-pi-lab-diagnostics>' +
|
||
'<summary>诊断</summary>' +
|
||
'<pre data-page-ai-pi-lab-diag-text></pre>' +
|
||
'</details>' +
|
||
'</div>';
|
||
}
|
||
|
||
function renderEmptyState(container) {
|
||
container.innerHTML = '' +
|
||
'<div class="wolai-page-ai-pi-lab-empty">' +
|
||
'<div class="wolai-page-ai-pi-lab-empty-icon">?</div>' +
|
||
'<strong>开始对话</strong>' +
|
||
'<p>试试以下示例,或直接输入您的指令</p>' +
|
||
'<div class="wolai-page-ai-pi-lab-examples">' +
|
||
'<button type="button" class="wolai-page-ai-pi-lab-example" data-page-ai-pi-lab-example="帮我解释当前页面的逻辑"><span>▤</span>帮我解释当前页面的逻辑</button>' +
|
||
'<button type="button" class="wolai-page-ai-pi-lab-example" data-page-ai-pi-lab-example="如何优化这段内容的结构?"><span>?</span>如何优化这段内容的结构?</button>' +
|
||
'<button type="button" class="wolai-page-ai-pi-lab-example" data-page-ai-pi-lab-example="用 LightRAG 查询当前页面相关资料"><span>▥</span>用 LightRAG 查询当前页面相关资料</button>' +
|
||
'<button type="button" class="wolai-page-ai-pi-lab-example" data-page-ai-pi-lab-example="帮我把选区改写成更清晰的版本"><span>↗</span>帮我把选区改写成更清晰的版本</button>' +
|
||
'<button type="button" class="wolai-page-ai-pi-lab-example" data-page-ai-pi-lab-example="检查当前 Markdown 有什么问题"><span>⌫</span>检查当前 Markdown 有什么问题</button>' +
|
||
'</div>' +
|
||
'</div>';
|
||
}
|
||
|
||
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 = '<div class="wolai-page-ai-pi-lab-empty">暂无 Pi Lab 历史</div>';
|
||
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 '<button type="button" class="wolai-page-ai-pi-lab-history-row" data-page-ai-pi-lab-history-session="' +
|
||
escapeHtml(session.sessionId || '') + '"><span><strong>' + title + '</strong><span>' + meta +
|
||
'</span></span><span>打开</span></button>';
|
||
}).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, '<br>'),
|
||
}));
|
||
}
|
||
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: '<strong>' + escapeHtml(tc.name || 'tool') + '</strong><span>' + escapeHtml(tc.status || '') + '</span>' }));
|
||
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 = '<div class="wolai-page-ai-pi-lab-empty">' + escapeHtml(error.message || '历史加载失败') + '</div>';
|
||
});
|
||
});
|
||
}
|
||
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;
|
||
}
|
||
})();
|