// == Pi Lab Page AI Runtime == // MNote-native adapter for Pi-first Page AI Lab. // Renders the MNote-owned Pi Rust Page AI surface. Retired hosts stay in // recycle; this runtime must stay independent. // 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/gpt-5.4-mini (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', RPC_COMMAND: '/api/page-ai/pi/rpc-command', UI_RESPONSE: '/api/page-ai/pi/ui-response', EVENTS: '/api/page-ai/pi/events', BOOTSTRAP: '/api/page-ai/pi/bootstrap', SESSIONS: '/api/page-ai/pi/sessions', DIFF: '/api/page-ai/pi/artifacts', FORK: '/api/page-ai/pi/fork', STATE: '/api/page-ai/pi/state', COMPACT: '/api/page-ai/pi/compact', CONFIGURE: '/api/page-ai/pi/configure', QUEUE_CONFIG: '/api/page-ai/pi/queue-config', DIAGNOSTICS: '/api/page-ai/pi/diagnostics', EFFECTIVE: '/api/ai-settings/effective', }; var DEFAULT_MODEL_PROVIDER = 'omniroute'; var DEFAULT_MODEL_ID = 'gpt-5.4-mini'; var DEFAULT_THINKING_LEVEL = 'medium'; var DEFAULT_PERMISSION_MODE = 'confirm'; var piLabInstalled = false; var piLabPanelEl = null; var piLabDrawerEl = null; var piLabLauncherEl = null; var piLabEventSource = null; var piLabStartPromise = null; var piLabState = { status: STATE_IDLE, enabled: false, active: false, standalone: false, sessionId: null, providerSessionId: null, defaultModelProvider: DEFAULT_MODEL_PROVIDER, defaultModelId: DEFAULT_MODEL_ID, defaultThinkingLevel: DEFAULT_THINKING_LEVEL, modelProvider: null, modelId: null, thinkingLevel: DEFAULT_THINKING_LEVEL, modelMenuOpen: false, thinkingMenuOpen: false, permissionMode: DEFAULT_PERMISSION_MODE, permissionMenuOpen: false, pendingPermissionModeApply: false, pendingPermissionMode: null, applyingPermissionMode: false, actionMenuOpen: false, runtimeMode: null, runtimeImplementation: null, runtimeBinary: null, runtimeAvailable: true, runtimeInstallHint: '', runtimeError: '', runtimePid: null, advancedRuntime: {}, warmupRunning: false, warmupSessionId: null, managedBuiltinTools: [], piExtensionSources: [], piExtensionToolNames: [], session: null, currentToolEventId: null, allowedRootsSummary: '', selectionSummary: '', selectionText: '', changedFiles: [], messages: [], diagnostics: '', receipts: [], modelOptions: [], history: [], pendingQueue: { steering: [], followUp: [] }, pendingImages: [], viewingHistorySessionId: null, contextSelection: { currentPage: false, currentFolder: false, selection: false, lightrag: false, }, queuedMessages: [], pendingMessageCount: 0, isCompacting: false, contextUsage: {}, }; var streamingAssistantMsg = null; var piRunViewModel = createPiRunViewModel(); var activePiUiDialog = null; var piCustomUiState = { panel: null, widgetKey: '', title: '', lines: [], selectedOptionIndex: 0, keyQueue: [], submitted: false, pendingCustomRequest: null, }; var piToastSeq = 0; function createPiRunViewModel(seed) { seed = seed || {}; return { turns: Array.isArray(seed.turns) ? seed.turns : [], messages: Array.isArray(seed.messages) ? seed.messages : [], toolCallsById: seed.toolCallsById || {}, pendingQueue: seed.pendingQueue || { steering: [], followUp: [] }, currentAssistant: seed.currentAssistant || null, runtimeStatus: seed.runtimeStatus || STATE_IDLE, abortResponse: seed.abortResponse || null, lastEventType: seed.lastEventType || '', lastToolCall: null, }; } function normalizeQueueItems(value) { if (!Array.isArray(value)) return []; return value.map(function (item) { if (item == null) return ''; if (typeof item === 'string') return item; return { id: item.id || item.messageId || item.queueId || '', text: item.text || item.message || item.prompt || '', createdAt: item.createdAt || item.timestamp || '', }; }).filter(function (item) { return typeof item === 'string' ? item.trim() : (item.text || item.id); }); } function rebuildPiRunToolIndex(vm) { vm.toolCallsById = {}; (vm.messages || []).forEach(function (msg) { (msg && Array.isArray(msg.toolCalls) ? msg.toolCalls : []).forEach(function (tc) { if (!tc) return; var key = tc.id || tc.name || ('tool-' + Object.keys(vm.toolCallsById).length); vm.toolCallsById[key] = tc; }); }); return vm.toolCallsById; } function syncPiRunViewModelFromState() { piRunViewModel.messages = piLabState.messages; piRunViewModel.pendingQueue = piLabState.pendingQueue || { steering: [], followUp: [] }; piRunViewModel.currentAssistant = streamingAssistantMsg; piRunViewModel.runtimeStatus = piLabState.status; rebuildPiRunToolIndex(piRunViewModel); return piRunViewModel; } function syncPiRunViewModelToState() { piLabState.messages = piRunViewModel.messages; piLabState.pendingQueue = piRunViewModel.pendingQueue || { steering: [], followUp: [] }; streamingAssistantMsg = piRunViewModel.currentAssistant || null; return piRunViewModel; } function resetPiRunViewModel(options) { piRunViewModel = createPiRunViewModel({ runtimeStatus: piLabState.status }); if (!options || options.sync !== false) syncPiRunViewModelToState(); return piRunViewModel; } function enterHistoryReplayMode(sessionId) { piLabState.viewingHistorySessionId = sessionId || piLabState.sessionId || null; if (piLabEventSource) { piLabEventSource.close(); piLabEventSource = null; } } function exitHistoryReplayMode() { piLabState.viewingHistorySessionId = null; } function ensurePiRunAssistant(vm) { if (!vm.currentAssistant) { vm.currentAssistant = { role: 'assistant', text: '', toolCalls: [], citations: [], diffSummary: null, status: 'streaming' }; vm.messages.push(vm.currentAssistant); } vm.currentAssistant.status = vm.currentAssistant.status || 'streaming'; vm.runtimeStatus = STATE_STREAMING; return vm.currentAssistant; } function findLastAssistant(vm) { return (vm.messages || []).slice().reverse().find(function (message) { return message && message.role === 'assistant'; }) || null; } function piRunUpsertToolCall(vm, raw, status, patch) { var msg = vm.currentAssistant || findLastAssistant(vm) || ensurePiRunAssistant(vm); var payload = raw || {}; var calls = ensureToolCalls(msg); var id = normalizeToolCallId(payload); var name = toolDisplayName(payload); var found = null; if (id) found = calls.find(function (item) { return item && item.id === id; }); if (!found && name) { found = calls.slice().reverse().find(function (item) { return item && item.name === name && (item.status === 'running' || item.status === 'pending'); }); } if (!found) { found = { id: id || ('tool-' + (calls.length + 1)), name: name, args: {}, status: status || 'running' }; calls.push(found); } found.id = id || found.id; found.name = name || found.name; found.status = status || found.status || 'running'; if (payload.args || payload.input || payload.params) found.args = payload.args || payload.input || payload.params; if (payload.details) found.details = payload.details; Object.keys(patch || {}).forEach(function (key) { found[key] = patch[key]; }); vm.lastToolCall = found; rebuildPiRunToolIndex(vm); return found; } function reducePiRunViewModel(vm, event) { vm = vm || createPiRunViewModel(); event = event || {}; var payload = event.payload || {}; vm.lastEventType = event.type || ''; if (event.type === 'clear') { vm.messages = []; vm.toolCallsById = {}; vm.pendingQueue = { steering: [], followUp: [] }; vm.currentAssistant = null; vm.abortResponse = null; return vm; } if (event.type === 'queue_update') { vm.pendingQueue = { steering: normalizeQueueItems(payload.steering), followUp: normalizeQueueItems(payload.followUp), }; return vm; } if (event.type === 'user_prompt') { vm.messages.push({ role: 'user', text: event.text || payload.text || payload.message || '', meta: event.meta || payload.meta || '' }); return vm; } if (event.type === 'assistant_begin') { ensurePiRunAssistant(vm); return vm; } if (event.type === 'assistant_delta') { var deltaMsg = ensurePiRunAssistant(vm); if (event.delta || payload.delta) deltaMsg.text += event.delta || payload.delta; if ((event.text || payload.text) && !(event.delta || payload.delta)) deltaMsg.text = event.text || payload.text; return vm; } if (event.type === 'assistant_text_end') { var textEndMsg = ensurePiRunAssistant(vm); textEndMsg.text = event.text || payload.text || payload.content || textEndMsg.text; return vm; } if (event.type === 'assistant_done') { var doneMsg = ensurePiRunAssistant(vm); if (event.text || payload.text) doneMsg.text = event.text || payload.text; if (event.toolCalls || payload.toolCalls) doneMsg.toolCalls = event.toolCalls || payload.toolCalls; if (event.citations || payload.citations) doneMsg.citations = event.citations || payload.citations; if (event.diffSummary || payload.diffSummary) doneMsg.diffSummary = event.diffSummary || payload.diffSummary; doneMsg.status = 'done'; vm.currentAssistant = null; vm.runtimeStatus = STATE_STARTED; rebuildPiRunToolIndex(vm); return vm; } if (event.type === 'assistant_abort') { // S4: abort is idempotent — second apply (SSE after optimistic) must not mutate again. if (vm.runtimeStatus === STATE_ABORTED && vm.abortResponse) { return vm; } var abortMsg = vm.currentAssistant || findLastAssistant(vm) || ensurePiRunAssistant(vm); if (abortMsg.status === 'aborted' && !vm.currentAssistant) { return vm; } abortMsg.status = 'aborted'; abortMsg.abortReason = event.reason || payload.reason || payload.stopReason || payload.error || payload.message || '已中止'; abortMsg.abortResponse = payload || { reason: abortMsg.abortReason }; vm.abortResponse = payload || { reason: abortMsg.abortReason }; vm.currentAssistant = null; vm.runtimeStatus = STATE_ABORTED; return vm; } if (event.type === 'assistant_error') { var errMsg = ensurePiRunAssistant(vm); errMsg.text += (errMsg.text ? '\n' : '') + '错误: ' + (event.message || payload.error || payload.message || 'Pi runtime error'); errMsg.status = 'done'; vm.currentAssistant = null; vm.runtimeStatus = STATE_ERROR; return vm; } if (event.type === 'tool_upsert') { piRunUpsertToolCall(vm, payload.raw || payload, payload.status || event.status, payload.patch || event.patch || {}); return vm; } if (event.type === 'citation') { var citMsg = ensurePiRunAssistant(vm); citMsg.citations.push({ source: payload.source || '', title: payload.title || '', url: payload.url || '#' }); return vm; } if (event.type === 'diff') { var diffMsg = ensurePiRunAssistant(vm); diffMsg.diffSummary = diffMsg.diffSummary || { files: [], toolEventId: '' }; diffMsg.diffSummary.files = payload.files || []; if (payload.toolEventId) diffMsg.diffSummary.toolEventId = payload.toolEventId; return vm; } return vm; } function applyPiRunEvent(event) { syncPiRunViewModelFromState(); reducePiRunViewModel(piRunViewModel, event); syncPiRunViewModelToState(); return piRunViewModel; } 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{position:relative;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:50px;margin:0 14px 12px;padding:0 12px;border:1px solid #e4e7ef;border-radius:14px;background:#fff;display:flex;align-items:center;gap:8px;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:32px;height:32px;border:0;background:transparent;color:#3c424c;display:grid;place-items:center;cursor:pointer;border-radius:9px}' + '.wolai-page-ai-pi-lab-commandbar .wolai-page-ai-pi-lab-tool-icon svg,.wolai-page-ai-pi-lab-square svg,.wolai-page-ai-pi-lab-icon-btn svg{width:17px;height:17px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}' + '.wolai-page-ai-pi-lab-commandbar .wolai-page-ai-pi-lab-tool-icon:hover,.wolai-page-ai-pi-lab-commandbar .wolai-page-ai-pi-lab-tool-icon[data-active="true"]{background:#f3f5f8;color:#111827}' + '.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:0 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-layer{position:absolute;inset:0;z-index:28;display:block;pointer-events:auto}' + '.wolai-page-ai-pi-lab-history-layer[hidden]{display:none!important}' + '.wolai-page-ai-pi-lab-history-scrim{position:absolute;inset:0;background:rgba(15,23,42,.22);backdrop-filter:blur(1px);border-radius:18px}' + '.wolai-page-ai-pi-lab-history-panel{position:absolute;left:0;top:0;bottom:0;width:min(340px,calc(100vw - 34px));border-right:1px solid #dfe4ec;background:#fff;box-shadow:18px 0 42px rgba(15,23,42,.18);display:flex;flex-direction:column;overflow:hidden}' + '.wolai-page-ai-pi-lab-history-head{display:flex;align-items:center;justify-content:space-between;gap:8px;min-height:48px;padding:0 14px;border-bottom:1px solid #edf0f5;background:#fff;color:#1f2937;font-size:14px;font-weight:750}' + '.wolai-page-ai-pi-lab-history-close{position:relative;width:26px;height:26px;border:1px solid #e1e4eb;border-radius:8px;background:#fff;color:#4a4f58;display:grid;place-items:center;cursor:pointer;font-size:0;line-height:1}' + '.wolai-page-ai-pi-lab-history-close::before,.wolai-page-ai-pi-lab-history-close::after{content:"";position:absolute;left:7px;right:7px;top:12px;height:1.5px;background:currentColor;border-radius:999px}' + '.wolai-page-ai-pi-lab-history-close::before{transform:rotate(45deg)}.wolai-page-ai-pi-lab-history-close::after{transform:rotate(-45deg)}' + '.wolai-page-ai-pi-lab-history-close svg{width:14px;height:14px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}' + '.wolai-page-ai-pi-lab-history-toolbar{display:flex;align-items:center;gap:8px;padding:12px 14px 10px;border-bottom:1px solid #f0f2f6;background:#fafbfc}' + '.wolai-page-ai-pi-lab-history-new{height:32px;flex:1;border:1px solid #1f6feb;border-radius:8px;background:#1f6feb;color:#fff;font:12px/1 inherit;font-weight:700;cursor:pointer}' + '.wolai-page-ai-pi-lab-history-refresh,.wolai-page-ai-pi-lab-history-clear{height:32px;border:1px solid #d9dee8;border-radius:8px;background:#fff;color:#4b5563;font:12px/1 inherit;padding:0 9px;cursor:pointer}' + '.wolai-page-ai-pi-lab-history-clear{color:#b42318;border-color:#f2c4bd}' + '.wolai-page-ai-pi-lab-history-meta{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:8px 14px;color:#6b7280;font-size:11px;border-bottom:1px solid #f0f2f6}' + '.wolai-page-ai-pi-lab-history-list{min-height:0;overflow:auto;flex:1}' + '.wolai-page-ai-pi-lab-history-row{border-bottom:1px solid #f0eee9;background:#fff;padding:11px 12px;display:grid;grid-template-columns:10px minmax(0,1fr) auto;gap:10px;align-items:center}' + '.wolai-page-ai-pi-lab-history-row:hover{background:#f8faff}' + '.wolai-page-ai-pi-lab-history-dot{width:7px;height:7px;border-radius:999px;background:#1f6feb;opacity:.35}' + '.wolai-page-ai-pi-lab-history-row[data-active="true"] .wolai-page-ai-pi-lab-history-dot{opacity:1}' + '.wolai-page-ai-pi-lab-history-row strong{display:block;font-size:12px;color:#1f2937}' + '.wolai-page-ai-pi-lab-history-row span{font-size:11px;color:#6b7280}' + '.wolai-page-ai-pi-lab-history-main{min-width:0;border:0;background:transparent;padding:0;text-align:left;cursor:pointer}' + '.wolai-page-ai-pi-lab-history-main strong,.wolai-page-ai-pi-lab-history-main span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}' + '.wolai-page-ai-pi-lab-history-actions{display:flex;gap:4px;align-items:center}' + '.wolai-page-ai-pi-lab-history-action{border:1px solid #d8dee8;border-radius:6px;background:#fff;padding:4px 6px;font-size:11px;color:#4b5563;cursor:pointer;white-space:nowrap}' + '.wolai-page-ai-pi-lab-history-action:hover{background:#f3f6fb}' + '.wolai-page-ai-pi-lab-history-danger{color:#b42318;border-color:#f2c4bd}' + '.wolai-page-ai-pi-lab-history-row[data-active="true"]{background:#f4f7ff}.wolai-page-ai-pi-lab-history-row[data-active="true"] .wolai-page-ai-pi-lab-history-main strong{color:#1f3f8f}' + '.wolai-page-ai-pi-lab-replay-banner{margin:0 18px 10px;padding:8px 10px;border:1px solid #dbe5ff;border-radius:8px;background:#f6f8ff;color:#36507d;font-size:12px;display:flex;align-items:center;justify-content:space-between;gap:10px}' + '.wolai-page-ai-pi-lab-replay-banner[hidden]{display:none!important}.wolai-page-ai-pi-lab-replay-banner button{border:1px solid #cfd9f5;border-radius:7px;background:#fff;color:#24406d;height:26px;padding:0 9px;font:12px/1 inherit;cursor:pointer}' + '.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{position:relative;display:block;min-height:0;flex:1;background:#f5f6fa;padding:0 14px 12px}' + '.wolai-page-ai-pi-lab-main{position:relative;display:flex;flex-direction:column;min-width:0;min-height:0;height:100%;overflow:hidden}' + '.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:16px 16px 0 0;box-shadow:0 10px 28px rgba(15,23,42,.07)}' + '.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{position:relative;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-markdown{font-size:14px;line-height:1.62;color:#272521}' + '.wolai-page-ai-pi-lab-markdown>*:first-child{margin-top:0}' + '.wolai-page-ai-pi-lab-markdown>*:last-child{margin-bottom:0}' + '.wolai-page-ai-pi-lab-markdown p{margin:0 0 10px}' + '.wolai-page-ai-pi-lab-markdown h1,.wolai-page-ai-pi-lab-markdown h2,.wolai-page-ai-pi-lab-markdown h3{margin:14px 0 8px;font-weight:720;letter-spacing:0;line-height:1.28;color:#202124}' + '.wolai-page-ai-pi-lab-markdown h1{font-size:20px}.wolai-page-ai-pi-lab-markdown h2{font-size:17px}.wolai-page-ai-pi-lab-markdown h3{font-size:15px}' + '.wolai-page-ai-pi-lab-markdown ul,.wolai-page-ai-pi-lab-markdown ol{margin:0 0 10px 20px;padding:0}' + '.wolai-page-ai-pi-lab-markdown li{margin:3px 0;padding-left:2px}' + '.wolai-page-ai-pi-lab-markdown blockquote{margin:0 0 10px;padding:2px 0 2px 12px;border-left:3px solid #d8dce6;color:#5f6368}' + '.wolai-page-ai-pi-lab-markdown pre{margin:8px 0 12px;padding:10px 12px;border:1px solid #e4e7ef;border-radius:8px;background:#f7f8fb;overflow:auto;white-space:pre;font:12px/1.55 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;color:#242833}' + '.wolai-page-ai-pi-lab-markdown code{border:1px solid #e5e7eb;border-radius:5px;background:#f7f8fb;padding:1px 4px;font:12px/1.45 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;color:#1f2937}' + '.wolai-page-ai-pi-lab-markdown pre code{border:0;background:transparent;padding:0;font:inherit;color:inherit}' + '.wolai-page-ai-pi-lab-markdown table{width:100%;border-collapse:collapse;margin:8px 0 12px;font-size:13px}' + '.wolai-page-ai-pi-lab-markdown th,.wolai-page-ai-pi-lab-markdown td{border:1px solid #e4e7ef;padding:6px 8px;text-align:left;vertical-align:top}' + '.wolai-page-ai-pi-lab-markdown th{background:#f7f8fb;font-weight:650}' + '.wolai-page-ai-pi-lab-markdown a{color:#1f6feb;text-decoration:none}.wolai-page-ai-pi-lab-markdown a:hover{text-decoration:underline}' + '.wolai-page-ai-pi-lab-text.streaming-cursor::after,.wolai-page-ai-pi-lab-markdown.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-timeline{margin-top:10px;border:1px solid #e4e7ef;border-radius:8px;background:#fafbfc;overflow:hidden}' + '.wolai-page-ai-pi-lab-tool-timeline>summary{min-height:32px;display:flex;align-items:center;justify-content:space-between;gap:8px;padding:0 10px;cursor:pointer;list-style:none;color:#41464f;font-size:12px;font-weight:650}' + '.wolai-page-ai-pi-lab-tool-timeline>summary::-webkit-details-marker{display:none}' + '.wolai-page-ai-pi-lab-tool-timeline>summary::after{content:"▸";font-size:10px;color:#7c8593}' + '.wolai-page-ai-pi-lab-tool-timeline[open]>summary::after{content:"▾"}' + '.wolai-page-ai-pi-lab-tool-timeline-body{display:flex;flex-direction:column;gap:6px;padding:8px;border-top:1px solid #e4e7ef}' + '.wolai-page-ai-pi-lab-tool-call{border:1px solid #e5e0d8;border-radius:9px;background:#fff;overflow:hidden}' + '.wolai-page-ai-pi-lab-tool-call[data-status="running"]{border-color:#bcd7ff;background:#f7fbff}' + '.wolai-page-ai-pi-lab-tool-call[data-status="error"]{border-color:#f2c4bd;background:#fff8f7}' + '.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-summary-line{padding:0 9px 7px;color:#6f6a60;font-size:11px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}' + '.wolai-page-ai-pi-lab-tool-call-details{border-top:1px solid #ebe7de;background:#fff}' + '.wolai-page-ai-pi-lab-tool-call-details>summary{min-height:28px;display:flex;align-items:center;gap:6px;padding:0 9px;cursor:pointer;list-style:none;color:#5d594f;font-size:11px}' + '.wolai-page-ai-pi-lab-tool-call-details>summary::-webkit-details-marker{display:none}' + '.wolai-page-ai-pi-lab-tool-call-details>summary::before{content:"▸";font-size:10px;color:#8a8378}.wolai-page-ai-pi-lab-tool-call-details[open]>summary::before{content:"▾"}' + '.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-tool-policy{display:inline-flex;align-items:center;border-radius:999px;border:1px solid #ded7ca;background:#fff;padding:1px 6px;font-size:10px;color:#6f6557;margin-left:6px}' + '.wolai-page-ai-pi-lab-tool-policy[data-policy="ask"]{border-color:#e1b870;background:#fff8e8;color:#8a5c13}' + '.wolai-page-ai-pi-lab-tool-policy[data-approved="true"]{border-color:#83b99a;background:#eef9f1;color:#27613b}' + '.wolai-page-ai-pi-lab-tool-policy[data-approved="false"]{border-color:#e0a39a;background:#fff0ee;color:#9f352c}' + '.wolai-page-ai-pi-lab-abort-note{margin-top:8px;border:1px solid #f2c4bd;border-radius:8px;background:#fff8f7;color:#9f352c;padding:7px 9px;font-size:12px}' + '.wolai-page-ai-pi-lab-message-actions{position:absolute;right:8px;top:8px;display:flex;gap:6px;opacity:0;pointer-events:none;transition:opacity .12s ease}' + '.wolai-page-ai-pi-lab-bubble:hover .wolai-page-ai-pi-lab-message-actions,.wolai-page-ai-pi-lab-bubble:focus-within .wolai-page-ai-pi-lab-message-actions{opacity:1;pointer-events:auto}' + '.wolai-page-ai-pi-lab-message-actions button{width:28px;height:28px;border:1px solid #e1e4eb;border-radius:8px;background:#fff;color:#4a4f58;padding:0;display:grid;place-items:center;cursor:pointer;box-shadow:0 4px 14px rgba(15,23,42,.08)}' + '.wolai-page-ai-pi-lab-message-actions button:hover{background:#f8fafc}' + '.wolai-page-ai-pi-lab-message-actions svg{width:15px;height:15px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}' + '.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{position:relative;flex:0 0 auto;border:1px solid #e4e7ef;border-top:0;border-radius:0 0 16px 16px;background:#fff;box-shadow:0 14px 34px rgba(15,23,42,.09);overflow:visible}' + '.wolai-page-ai-pi-lab-input-wrap{position:relative;background:#f7f8fb}' + '.wolai-page-ai-pi-lab-input{width:100%;min-height:98px;max-height:190px;box-sizing:border-box;border:0;resize:none;padding:18px 62px 12px 22px;font:18px/1.45 inherit;color:#242424;background:transparent;outline:none}' + '.wolai-page-ai-pi-lab-input::placeholder{color:#b6bbc5}' + '.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{min-height:46px;display:flex;align-items:center;gap:8px;padding:6px 12px;border-top:1px solid #eef0f5;background:#fff;box-sizing:border-box}' + '.wolai-page-ai-pi-lab-square{width:34px;height:34px;flex:0 0 34px;border:1px solid #e1e4eb;border-radius:9px;background:#fff;color:#242833;display:grid;place-items:center;cursor:pointer}' + '.wolai-page-ai-pi-lab-square:hover{background:#f8fafc}' + '.wolai-page-ai-pi-lab-square[data-active="true"],.wolai-page-ai-pi-lab-chip-button[data-active="true"]{border-color:#9ec5fe;background:#eef6ff;color:#1558a6}' + '.wolai-page-ai-pi-lab-chip-button{height:34px;display:inline-flex;align-items:center;gap:6px;border:1px solid #e1e4eb;border-radius:10px;background:#fff;color:#4a4f58;padding:0 10px;font:12px/1 inherit;white-space:nowrap;cursor:pointer}' + '.wolai-page-ai-pi-lab-chip-button svg{width:14px;height:14px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}' + '.wolai-page-ai-pi-lab-chip-button[data-tone="warn"]{border-color:#f0d8aa;background:#fff9ed;color:#865b13}.wolai-page-ai-pi-lab-chip-button[data-tone="danger"]{border-color:#f2c4bd;background:#fff8f7;color:#9f352c}.wolai-page-ai-pi-lab-chip-button[data-tone="ok"]{border-color:#bfe6ac;background:#f4fff0;color:#27613b}' + '.wolai-page-ai-pi-lab-control-wrap{position:relative;display:inline-flex;min-width:0;flex:0 0 auto}' + '.wolai-page-ai-pi-lab-model-control-wrap{flex:1 1 160px;max-width:210px}' + '.wolai-page-ai-pi-lab-control-button{height:34px;min-width:0;width:100%;border:1px solid #e1e4eb;border-radius:10px;background:#fff;color:#4a4f58;padding:0 9px;display:inline-flex;align-items:center;justify-content:space-between;gap:7px;font:12px/1 inherit;white-space:nowrap;cursor:pointer}' + '.wolai-page-ai-pi-lab-model-control{font-size:13px}' + '.wolai-page-ai-pi-lab-thinking-control{max-width:118px}' + '.wolai-page-ai-pi-lab-control-button:hover{background:#f8fafc}.wolai-page-ai-pi-lab-control-button[data-active="true"]{border-color:#9ec5fe;background:#eef6ff;color:#1558a6}.wolai-page-ai-pi-lab-control-button:disabled{opacity:.45;cursor:default}' + '.wolai-page-ai-pi-lab-control-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}' + '.wolai-page-ai-pi-lab-control-caret{flex:0 0 auto;color:#8a9099;font-size:11px;line-height:1;transform:translateY(-1px)}' + '.wolai-page-ai-pi-lab-control-menu{position:absolute;left:0;bottom:40px;z-index:20;width:min(280px,calc(100vw - 28px));max-height:min(280px,calc(100vh - 160px));overflow:auto;border:1px solid #dfe3ea;border-radius:12px;background:#fff;box-shadow:0 18px 46px rgba(15,23,42,.22);padding:6px;display:none;color:#242424}' + '.wolai-page-ai-pi-lab-thinking-control-wrap .wolai-page-ai-pi-lab-control-menu{width:180px}' + '.wolai-page-ai-pi-lab-control-wrap[data-open="true"] .wolai-page-ai-pi-lab-control-menu{display:block}' + '.wolai-page-ai-pi-lab-control-option{width:100%;min-height:34px;border:0;border-radius:8px;background:#fff;color:#242424;display:grid;grid-template-columns:minmax(0,1fr) 18px;gap:8px;align-items:center;text-align:left;padding:7px 9px;font:13px/1.25 inherit;cursor:pointer}' + '.wolai-page-ai-pi-lab-control-option:hover{background:#f7f8fb}.wolai-page-ai-pi-lab-control-option[data-active="true"]{background:#f3f6fb;color:#1558a6}' + '.wolai-page-ai-pi-lab-control-option-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.wolai-page-ai-pi-lab-control-option-check{text-align:right;color:#6b7280}' + '.wolai-page-ai-pi-lab-permission-wrap{position:relative;display:inline-flex;flex:0 0 auto}' + '.wolai-page-ai-pi-lab-permission-menu{position:absolute;left:0;bottom:40px;z-index:18;width:min(306px,calc(100vw - 28px));border:1px solid #dfe3ea;border-radius:12px;background:#fff;box-shadow:0 18px 46px rgba(15,23,42,.22);padding:6px;display:none;color:#242424}' + '.wolai-page-ai-pi-lab-permission-wrap[data-open="true"] .wolai-page-ai-pi-lab-permission-menu{display:block}' + '.wolai-page-ai-pi-lab-permission-option{width:100%;min-height:58px;border:0;border-radius:9px;background:#fff;color:#242424;display:grid;grid-template-columns:22px 1fr 18px;gap:10px;align-items:center;text-align:left;padding:8px 9px;font:13px/1.25 inherit;cursor:pointer}' + '.wolai-page-ai-pi-lab-permission-option:hover{background:#f7f8fb}.wolai-page-ai-pi-lab-permission-option[data-active="true"]{background:#f3f6fb}' + '.wolai-page-ai-pi-lab-permission-option svg{width:17px;height:17px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round;color:#5d6470}' + '.wolai-page-ai-pi-lab-permission-option strong{display:block;font-size:13px;font-weight:650}.wolai-page-ai-pi-lab-permission-option small{display:block;margin-top:4px;color:#6b7280;font-size:12px;line-height:1.25}.wolai-page-ai-pi-lab-permission-check{color:#6b7280;text-align:right}' + '.wolai-page-ai-pi-lab-action-menu{position:absolute;left:12px;bottom:52px;z-index:16;width:min(300px,calc(100% - 24px));border:1px solid #dfe3ea;border-radius:12px;background:#fff;box-shadow:0 18px 46px rgba(15,23,42,.22);padding:6px;display:none;color:#242424}' + '.wolai-page-ai-pi-lab-composer[data-menu-open="true"] .wolai-page-ai-pi-lab-action-menu{display:block}' + '.wolai-page-ai-pi-lab-menu-section{padding:5px 7px 4px;color:#8a9099;font-size:11px;font-weight:650}' + '.wolai-page-ai-pi-lab-menu-item{width:100%;min-height:34px;border:0;border-radius:8px;background:#fff;color:#242424;display:flex;align-items:center;gap:9px;text-align:left;padding:0 9px;font:13px/1.2 inherit;cursor:pointer}' + '.wolai-page-ai-pi-lab-menu-item:hover{background:#f7f8fb}.wolai-page-ai-pi-lab-menu-item[data-active="true"]{background:#eef6ff;color:#1558a6}.wolai-page-ai-pi-lab-menu-item[disabled]{opacity:.45;cursor:default}.wolai-page-ai-pi-lab-menu-item[disabled]:hover{background:#fff}' + '.wolai-page-ai-pi-lab-menu-item svg{width:15px;height:15px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round;flex:0 0 auto}' + '.wolai-page-ai-pi-lab-menu-item strong{margin-left:auto;color:#1558a6;font-size:13px}' + '.wolai-page-ai-pi-lab-menu-sep{height:1px;background:#edf0f5;margin:6px 3px}' + '.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;white-space:nowrap}' + '.wolai-page-ai-pi-lab-segment button[data-active=\"true\"]{background:#111827;color:#fff}' + '.wolai-page-ai-pi-lab-stream-controls{display:inline-flex;align-items:center;gap:8px}' + '.wolai-page-ai-pi-lab-queue-count{height:22px;min-width:22px;padding:0 6px;border-radius:999px;background:#eef2ff;color:#334155;display:inline-flex;align-items:center;justify-content:center;font-size:11px}' + '.wolai-page-ai-pi-lab-spacer{flex:1}' + '.wolai-page-ai-pi-lab-send{position:absolute;right:12px;bottom:11px;width:38px;height:38px;border:0;border-radius:12px;background:#1d9bf0;color:#fff;display:grid;place-items:center;cursor:pointer;box-shadow:0 10px 24px rgba(29,155,240,.25)}' + '.wolai-page-ai-pi-lab-send svg{width:18px;height:18px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}' + '.wolai-page-ai-pi-lab-send:disabled{opacity:.45;cursor:default}' + '.wolai-page-ai-pi-lab-rail{position:absolute;right:14px;top:0;bottom:104px;width:260px;z-index:4;display:none;flex-direction:column;gap:8px;min-height:0;overflow:auto;padding:8px;border:1px solid #e4e7ef;border-radius:14px;background:#f7f8fb;box-shadow:0 18px 46px rgba(15,23,42,.18)}' + '.wolai-page-ai-pi-lab-body[data-rail-open="true"] .wolai-page-ai-pi-lab-rail{display:flex}' + '.wolai-page-ai-pi-lab-rail-head{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:0 2px 2px;color:#42403a;font-size:12px;font-weight:650}' + '.wolai-page-ai-pi-lab-rail-close{width:24px;height:24px;border:1px solid #e1e4eb;border-radius:7px;background:#fff;color:#4a4f58;display:grid;place-items:center;cursor:pointer}' + '.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-artifacts{display:flex;flex-direction:column;gap:6px;padding:8px;max-height:250px;overflow:auto}' + '.wolai-page-ai-pi-lab-artifact{border:1px solid #e6e2da;border-radius:8px;background:#fff;color:#42403a;padding:7px;font-size:11px;line-height:1.42;overflow:hidden}' + '.wolai-page-ai-pi-lab-artifact[data-kind="citation"]{border-color:#d9e6ff;background:#f7faff}.wolai-page-ai-pi-lab-artifact[data-kind="diff"]{border-color:#f0dcb7;background:#fff9ed}.wolai-page-ai-pi-lab-artifact[data-kind="mcp"]{border-color:#dfe6f2;background:#fafcff}' + '.wolai-page-ai-pi-lab-artifact strong{display:block;font-size:11px;font-weight:700;color:#2f343b;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.wolai-page-ai-pi-lab-artifact span{display:block;margin-top:3px;color:#6f6a60;word-break:break-word}.wolai-page-ai-pi-lab-artifact-actions{display:flex;flex-wrap:wrap;gap:5px}.wolai-page-ai-pi-lab-artifact button,.wolai-page-ai-pi-lab-artifact a,.wolai-page-ai-pi-lab-rail-action{display:inline-flex;align-items:center;height:24px;margin-top:6px;border:1px solid #e1e4eb;border-radius:7px;background:#fff;color:#315d9f;text-decoration:none;padding:0 7px;font-size:11px;cursor:pointer}' + '.wolai-page-ai-pi-lab-rail-action{margin:8px 8px 0}.wolai-page-ai-pi-lab-rail-action:hover,.wolai-page-ai-pi-lab-artifact button:hover,.wolai-page-ai-pi-lab-artifact a:hover{background:#f8fafc}' + '.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-ui-backdrop{position:relative;z-index:12;display:block;padding:10px 12px 0;background:#fff;pointer-events:none}' + '.wolai-page-ai-pi-lab-ui-dialog{width:100%;border:1px solid #dfe4ec;border-radius:12px;background:#fff;box-shadow:0 10px 28px rgba(15,23,42,.12);overflow:hidden;color:#242424;pointer-events:auto}' + '.wolai-page-ai-pi-lab-ui-dialog[data-collapsed="true"] .wolai-page-ai-pi-lab-ui-dialog-body,.wolai-page-ai-pi-lab-ui-dialog[data-collapsed="true"] .wolai-page-ai-pi-lab-ui-dialog-actions{display:none}' + '.wolai-page-ai-pi-lab-ui-dialog-header{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:8px;align-items:start;padding:11px 12px 9px;border-bottom:1px solid #edf0f5;background:#fafbfc}.wolai-page-ai-pi-lab-ui-dialog-header strong{display:block;font-size:13px;font-weight:720;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.wolai-page-ai-pi-lab-ui-dialog-header span{display:block;margin-top:3px;font-size:12px;color:#6f6a60;white-space:pre-wrap}.wolai-page-ai-pi-lab-ui-collapse{width:28px;height:28px;border:1px solid #e1e4eb;border-radius:8px;background:#fff;color:#4a4f58;cursor:pointer}' + '.wolai-page-ai-pi-lab-ui-dialog-body{padding:10px 12px;display:flex;flex-direction:column;gap:8px;max-height:min(320px,38vh);overflow:auto}.wolai-page-ai-pi-lab-ui-dialog-body input,.wolai-page-ai-pi-lab-ui-dialog-body textarea{width:100%;box-sizing:border-box;border:1px solid #ddd8cf;border-radius:8px;background:#fff;color:#242424;padding:8px 9px;font:13px/1.45 inherit;outline:none}.wolai-page-ai-pi-lab-ui-dialog-body textarea{min-height:76px;resize:vertical}' + '.wolai-page-ai-pi-lab-ui-question{border:1px solid #edf0f5;border-radius:10px;background:#fff;padding:9px}.wolai-page-ai-pi-lab-ui-question-title{font-size:12px;font-weight:700;color:#2f343b;margin-bottom:3px}.wolai-page-ai-pi-lab-ui-question-prompt{font-size:12px;color:#6f6a60;white-space:pre-wrap;margin-bottom:7px}.wolai-page-ai-pi-lab-ui-option{width:100%;min-height:34px;border:1px solid #e1e4eb;border-radius:8px;background:#fff;color:#242424;text-align:left;padding:7px 9px;cursor:pointer;display:flex;align-items:flex-start;gap:8px}.wolai-page-ai-pi-lab-ui-option:hover,.wolai-page-ai-pi-lab-ui-option[data-selected="true"]{background:#f3f7ff;border-color:#bcd7ff}.wolai-page-ai-pi-lab-ui-option small{display:block;margin-top:2px;color:#737a86;font-size:11px;line-height:1.35}.wolai-page-ai-pi-lab-ui-option-mark{flex:0 0 auto;color:#5d6675}.wolai-page-ai-pi-lab-ui-note{border-top:1px solid #edf0f5;padding-top:8px}.wolai-page-ai-pi-lab-ui-note label{display:block;margin-bottom:4px;font-size:11px;color:#737a86}' + '.wolai-page-ai-pi-lab-ui-dialog-actions{display:flex;justify-content:flex-end;gap:8px;padding:9px 12px 11px;border-top:1px solid #edf0f5}.wolai-page-ai-pi-lab-ui-dialog-actions button{height:30px;border:1px solid #ddd8cf;border-radius:8px;background:#fff;color:#4a4f58;padding:0 11px;cursor:pointer}.wolai-page-ai-pi-lab-ui-dialog-actions button[data-primary="true"]{border-color:#111827;background:#111827;color:#fff}' + '.wolai-page-ai-pi-lab-ui-tui{margin:0;padding:8px 9px;border:1px solid #edf0f5;border-radius:8px;background:#fafbfc;color:#242833;white-space:pre-wrap;font:12px/1.45 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;overflow:auto}.wolai-page-ai-pi-lab-ui-tui-options{display:flex;flex-direction:column;gap:6px}.wolai-page-ai-pi-lab-ui-option[data-custom-selected="true"]{background:#eef6ff;border-color:#9ec5fe}' + '.wolai-page-ai-pi-lab-toast-stack{position:absolute;left:18px;right:18px;bottom:166px;z-index:13;display:flex;flex-direction:column;align-items:flex-end;gap:8px;pointer-events:none}.wolai-page-ai-pi-lab-toast{width:min(340px,100%);border:1px solid #dbe3ef;border-radius:10px;background:#fff;color:#242424;box-shadow:0 12px 34px rgba(15,23,42,.18);padding:10px 12px;font-size:13px;line-height:1.45;pointer-events:auto}.wolai-page-ai-pi-lab-toast[data-type="warning"]{border-color:#f0dcb7;background:#fff9ed}.wolai-page-ai-pi-lab-toast[data-type="error"]{border-color:#f2c4bd;background:#fff8f7}.wolai-page-ai-pi-lab-toast[data-type="success"]{border-color:#bfe6ac;background:#f4fff0}' + '.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-diagnostic-actions{display:flex;flex-wrap:wrap;gap:6px;align-items:center;padding:0 12px 8px}.wolai-page-ai-pi-lab-diagnostic-actions button{height:26px;border:1px solid #ddd8cf;border-radius:7px;background:#fff;color:#42403a;padding:0 8px;font-size:11px;cursor:pointer}.wolai-page-ai-pi-lab-diagnostic-actions button:hover{background:#f8f7f3}.wolai-page-ai-pi-lab-diagnostic-actions input{height:26px;min-width:120px;border:1px solid #ddd8cf;border-radius:7px;background:#fff;color:#42403a;padding:0 8px;font-size:11px;outline:none}.wolai-page-ai-pi-lab-diagnostic-output[hidden]{display:none!important}.wolai-page-ai-pi-lab-diagnostic-output{border-top:1px solid #eee9e0;background:#faf9f6}' + '.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-history-panel{width:min(320px,calc(100vw - 34px))}.wolai-page-ai-pi-lab-rail{left:14px;right:14px;top:auto;bottom:132px;width:auto;max-height:min(360px,calc(100% - 164px))}.wolai-page-ai-pi-lab-config{grid-template-columns:1fr}}' + '@media (max-width: 520px){.wolai-page-ai-pi-lab-topbar{height:54px;padding:0 14px}.wolai-page-ai-pi-lab-title strong{font-size:21px}.wolai-page-ai-pi-lab-commandbar{height:50px;margin:0 14px 12px;gap:6px;overflow:hidden}.wolai-page-ai-pi-lab-history-panel{top:116px;left:12px;right:12px;max-height:min(300px,calc(100% - 300px))}.wolai-page-ai-pi-lab-history-row{grid-template-columns:1fr}.wolai-page-ai-pi-lab-history-actions{justify-content:flex-start}.wolai-page-ai-pi-lab-input{min-height:88px;padding:16px 58px 10px 18px;font-size:16px}.wolai-page-ai-pi-lab-modebar{min-height:48px;flex-wrap:nowrap;gap:6px;padding:6px 10px}.wolai-page-ai-pi-lab-model-control-wrap{flex:1 1 130px;min-width:92px;max-width:none}.wolai-page-ai-pi-lab-toast-stack{left:14px;right:14px;bottom:154px}.wolai-page-ai-pi-lab-toast{width:100%}}'; /* Split diff overlay styles */ var diffStyles = '.wolai-page-ai-pi-lab-diff-overlay{position:fixed;top:0;left:0;right:0;bottom:0;z-index:9999;background:rgba(0,0,0,0.4);display:flex;flex-direction:column;padding:40px 60px}' + '.wolai-page-ai-pi-lab-diff-header{display:flex;align-items:center;justify-content:space-between;padding:8px 16px;background:#f0f0ed;border-radius:8px 8px 0 0;font:600 14px/1.4 system-ui}' + '.wolai-page-ai-pi-lab-diff-close{background:none;border:none;font:18px/1 sans-serif;cursor:pointer;padding:4px 8px;border-radius:4px}' + '.wolai-page-ai-pi-lab-diff-close:hover{background:rgba(0,0,0,0.08)}' + '.wolai-page-ai-pi-lab-diff-body{flex:1;overflow:auto;background:#fff;border-radius:0 0 8px 8px;padding:0;font:12px/1.5 Monaco,Consolas,monospace}' + '.wolai-page-ai-pi-lab-diff-file-header{background:#f8f8f5;font-weight:600;padding:6px 12px;border-bottom:1px solid #e5e5e0}' + '.wolai-page-ai-pi-lab-diff-hunk-header{background:#f0f0ed;color:#666;padding:4px 12px;border-bottom:1px solid #e5e5e0;font-size:11px}' + '.wolai-page-ai-pi-lab-diff-line{padding:1px 12px;border-bottom:1px solid #f0f0ed;white-space:pre}' + '.wolai-page-ai-pi-lab-diff-line-add{background:#e6ffec}' + '.wolai-page-ai-pi-lab-diff-line-del{background:#ffebe9}' + '.wolai-page-ai-pi-lab-diff-line-ctx{background:#f8f8f5;color:#666}' + '.wolai-page-ai-pi-lab-diff-line-num{display:inline-block;width:40px;text-align:right;color:#999;padding-right:12px;user-select:none}' + '.wolai-page-ai-pi-lab-diff-collapse{cursor:pointer;text-align:center;color:#888;background:#f5f5f2;padding:2px;font-size:11px;border-bottom:1px solid #e5e5e0}' + '.wolai-page-ai-pi-lab-diff-collapse:hover{background:#eee}'; style.textContent += diffStyles; document.head.appendChild(style); } function escapeHtml(str) { if (str == null) return ''; return String(str) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function escapeAttr(str) { return escapeHtml(str).replace(/`/g, '`'); } function renderMarkdownInline(text) { var placeholders = []; function stash(html) { var key = '\u0000MD' + placeholders.length + '\u0000'; placeholders.push(html); return key; } var value = escapeHtml(text || ''); value = value.replace(/`([^`]+)`/g, function (_, code) { return stash('' + code + ''); }); value = value.replace(/\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)/g, function (_, label, href) { return stash('' + label + ''); }); value = value.replace(/\*\*([^*]+)\*\*/g, '$1'); value = value.replace(/(^|[^*])\*([^*\n]+)\*/g, '$1$2'); placeholders.forEach(function (html, index) { value = value.replace(new RegExp('\u0000MD' + index + '\u0000', 'g'), html); }); return value; } function isTableDivider(line) { return /^\s*\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|?\s*$/.test(line || ''); } function splitTableRow(line) { var trimmed = String(line || '').trim(); if (trimmed.charAt(0) === '|') trimmed = trimmed.slice(1); if (trimmed.charAt(trimmed.length - 1) === '|') trimmed = trimmed.slice(0, -1); return trimmed.split('|').map(function (cell) { return cell.trim(); }); } function renderMarkdownTable(lines, start) { var header = splitTableRow(lines[start]); var rows = []; var i = start + 2; while (i < lines.length && /\|/.test(lines[i] || '') && String(lines[i] || '').trim()) { rows.push(splitTableRow(lines[i])); i += 1; } return { html: '' + header.map(function (cell) { return ''; }).join('') + '' + rows.map(function (row) { return '' + header.map(function (_, index) { return ''; }).join('') + ''; }).join('') + '
' + renderMarkdownInline(cell) + '
' + renderMarkdownInline(row[index] || '') + '
', next: i, }; } function renderMarkdownBlock(text) { var lines = String(text || '').replace(/\r\n/g, '\n').split('\n'); var html = []; var paragraph = []; var list = null; var inCode = false; var codeLines = []; var codeLang = ''; function flushParagraph() { if (!paragraph.length) return; html.push('

' + renderMarkdownInline(paragraph.join(' ')) + '

'); paragraph = []; } function flushList() { if (!list) return; html.push('<' + list.type + '>' + list.items.map(function (item) { return '
  • ' + renderMarkdownInline(item) + '
  • '; }).join('') + ''); list = null; } for (var i = 0; i < lines.length; i += 1) { var line = lines[i]; var trimmed = line.trim(); var fence = trimmed.match(/^```([A-Za-z0-9_-]*)\s*$/); if (fence) { if (inCode) { html.push('
    ' + escapeHtml(codeLines.join('\n')) + '
    '); inCode = false; codeLines = []; codeLang = ''; } else { flushParagraph(); flushList(); inCode = true; codeLang = fence[1] || ''; } continue; } if (inCode) { codeLines.push(line); continue; } if (!trimmed) { flushParagraph(); flushList(); continue; } if (i + 1 < lines.length && /\|/.test(line) && isTableDivider(lines[i + 1])) { flushParagraph(); flushList(); var renderedTable = renderMarkdownTable(lines, i); html.push(renderedTable.html); i = renderedTable.next - 1; continue; } var heading = trimmed.match(/^(#{1,3})\s+(.+)$/); if (heading) { flushParagraph(); flushList(); html.push('' + renderMarkdownInline(heading[2]) + ''); continue; } var quote = trimmed.match(/^>\s?(.*)$/); if (quote) { flushParagraph(); flushList(); html.push('
    ' + renderMarkdownInline(quote[1]) + '
    '); continue; } var unordered = trimmed.match(/^[-*+]\s+(.+)$/); var ordered = trimmed.match(/^\d+\.\s+(.+)$/); if (unordered || ordered) { flushParagraph(); var type = ordered ? 'ol' : 'ul'; if (!list || list.type !== type) flushList(); if (!list) list = { type: type, items: [] }; list.items.push((unordered || ordered)[1]); continue; } paragraph.push(trimmed); } if (inCode) html.push('
    ' + escapeHtml(codeLines.join('\n')) + '
    '); flushParagraph(); flushList(); return html.join(''); } 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 compactJson(value) { if (value == null || value === '') return ''; if (typeof value === 'string') return value; try { return JSON.stringify(value, null, 2); } catch (_) { return String(value); } } function extractResultText(value) { if (value == null) return ''; if (typeof value === 'string') return value; var content = value.content; if (Array.isArray(content)) { var text = content.map(function (part) { if (!part) return ''; if (typeof part === 'string') return part; if (typeof part.text === 'string') return part.text; return ''; }).filter(Boolean).join(''); if (text) return text; } return compactJson(value); } function truncateSingleLine(value, limit) { var text = String(value || '').replace(/\s+/g, ' ').trim(); var max = limit || 120; return text.length > max ? text.slice(0, max - 1) + '…' : text; } function copyTextToClipboard(text) { text = String(text || ''); if (!text) return Promise.resolve(false); if (navigator.clipboard && navigator.clipboard.writeText) { return navigator.clipboard.writeText(text).then(function () { return true; }).catch(function () { return false; }); } return Promise.resolve(false); } function messageToMarkdown(msg) { if (!msg) return ''; var lines = []; if (msg.text) lines.push(String(msg.text)); if (Array.isArray(msg.citations) && msg.citations.length) { lines.push('', 'References:'); msg.citations.forEach(function (cit, index) { lines.push((index + 1) + '. ' + (cit.title || cit.source || 'Citation') + (cit.url ? ' - ' + cit.url : '')); }); } if (msg.diffSummary && Array.isArray(msg.diffSummary.files) && msg.diffSummary.files.length) { lines.push('', 'Changed files:'); msg.diffSummary.files.forEach(function (file) { lines.push('- ' + file); }); } if (Array.isArray(msg.toolCalls) && msg.toolCalls.length) { lines.push('', 'Tool calls:'); msg.toolCalls.forEach(function (tc) { lines.push('- ' + toolDisplayName({ toolName: tc.name, args: tc.args, details: tc.details }) + ' · ' + (tc.status || '')); }); } return lines.join('\n').trim(); } function currentSessionEventsUrl() { if (!piLabState.sessionId) return ''; return API.SESSIONS + '/' + encodeURIComponent(piLabState.sessionId) + '/events?limit=1000'; } function openCurrentSessionJsonl() { var url = currentSessionEventsUrl(); if (!url) { showPiToast('No Pi session to open', 'warning'); return Promise.resolve(false); } return fetch(url, { credentials: 'same-origin', cache: 'no-store' }) .then(function (response) { if (!response.ok) throw new Error('Session events failed: ' + response.status); return response.json(); }) .then(function (payload) { var lines = (Array.isArray(payload.events) ? payload.events : []).map(function (event) { return JSON.stringify({ eventType: event.eventType, createdAt: event.createdAt, payload: event.payload || {}, }); }).join('\n'); var blob = new Blob([lines + (lines ? '\n' : '')], { type: 'application/x-ndjson;charset=utf-8' }); var objectUrl = URL.createObjectURL(blob); window.open(objectUrl, '_blank', 'noopener'); setTimeout(function () { URL.revokeObjectURL(objectUrl); }, 30000); showPiToast('Session JSONL opened', 'success'); return true; }) .catch(function (error) { updateDiagnostics(error.message || '打开 session JSONL 失败'); showPiToast('Open session JSONL failed', 'error'); return false; }); } function normalizeToolCallId(payload) { payload = payload || {}; return String(payload.toolCallId || payload.tool_call_id || payload.id || payload.callId || '').trim(); } function normalizeToolName(payload) { payload = payload || {}; return String(payload.toolName || payload.tool_name || payload.name || payload.tool || 'tool').trim(); } function parseMcpArgs(args) { if (!args) return {}; if (typeof args === 'string') { try { return JSON.parse(args); } catch (_) { return { raw: args }; } } return args; } function toolDisplayName(payload) { var name = normalizeToolName(payload); var args = parseMcpArgs(payload && payload.args); var details = payload && payload.details ? payload.details : {}; var server = String(args.server || details.server || '').trim(); var tool = String(args.tool || details.tool || '').trim(); if (name === 'mcp' && server) return 'mcp:' + server + (tool ? '/' + tool : '/list'); return name; } function assistantToolCallPayload(msgData) { if (!msgData) return null; var toolCall = msgData.toolCall || null; if (!toolCall && msgData.partial && msgData.partial.type === 'toolCall') toolCall = msgData.partial; if (!toolCall && msgData.toolName) toolCall = msgData; if (!toolCall && msgData.name) toolCall = msgData; if (!toolCall || !(toolCall.name || toolCall.toolName || toolCall.tool_name || toolCall.id || toolCall.toolCallId)) return null; return { toolCallId: toolCall.toolCallId || toolCall.id || msgData.toolCallId || msgData.id || '', toolName: toolCall.toolName || toolCall.tool_name || toolCall.name || msgData.toolName || msgData.name || '', args: toolCall.arguments || toolCall.args || msgData.arguments || msgData.args || {}, details: msgData.details || null, result: msgData.result || null, }; } function ensureToolCalls(msg) { if (!msg.toolCalls) msg.toolCalls = []; return msg.toolCalls; } function upsertToolCall(raw, status, patch) { var viewModel = applyPiRunEvent({ type: 'tool_upsert', payload: { raw: raw || {}, status: status || 'running', patch: patch || {}, }, }); updateMessages(); return viewModel.lastToolCall; } function citationFromMarkdown(markdown, fallbackTitle) { var value = String(markdown || '').trim(); if (!value || value[0] !== '[' || value[value.length - 1] !== ')') return null; var split = value.lastIndexOf(']('); if (split <= 0) return null; var title = value.slice(1, split).replace(/\\([\[\]])/g, '$1').trim(); var url = value.slice(split + 2, -1).trim(); if (!url) return null; return { source: String(fallbackTitle || title || url), title: title || String(fallbackTitle || '资料库引用'), url: url, }; } function collectKnowledgeRagCitations(value) { var citations = []; var seen = {}; function add(markdown, fallbackTitle) { var citation = citationFromMarkdown(markdown, fallbackTitle); if (!citation || seen[citation.url] || citations.length >= 8) return; seen[citation.url] = true; citations.push(citation); } function visit(node) { if (node == null || citations.length >= 8) return; if (Array.isArray(node)) { node.forEach(visit); return; } if (typeof node !== 'object') return; var title = String(node.sourceRootRelativePath || node.citationLabel || node.title || '').trim(); if (typeof node.citationMarkdown === 'string') add(node.citationMarkdown, title); Object.keys(node).forEach(function (key) { if (key !== 'citationMarkdown') visit(node[key]); }); } visit(value); return citations; } function appendKnowledgeRagCitations(value) { var citations = collectKnowledgeRagCitations(value); if (!citations.length) return; syncPiRunViewModelFromState(); var current = piRunViewModel.currentAssistant || findLastAssistant(piRunViewModel); if (!current) current = ensurePiRunAssistant(piRunViewModel); if (!Array.isArray(current.citations)) current.citations = []; var existing = {}; current.citations.forEach(function (citation) { existing[String(citation && citation.url || '')] = true; }); citations.forEach(function (citation) { if (existing[citation.url]) return; existing[citation.url] = true; current.citations.push(citation); }); syncPiRunViewModelToState(); } function renderToolCallCard(tc) { var displayName = toolDisplayName({ toolName: tc.name, args: tc.args, details: tc.details }); var resultText = extractResultText(tc.result || tc.partialResult || ''); var summaryParts = []; if (tc.args && Object.keys(tc.args).length) { var args = parseMcpArgs(tc.args); if (args.server) summaryParts.push('server=' + args.server); if (args.tool) summaryParts.push('tool=' + args.tool); } if (resultText) summaryParts.push(truncateSingleLine(resultText, 140)); var item = el('div', { className: 'wolai-page-ai-pi-lab-tool-call', 'data-page-ai-pi-lab-tool-card': tc.id || displayName || 'tool', 'data-page-ai-pi-lab-tool-call': displayName || 'tool', 'data-status': tc.status || '', }); var policy = tc.toolPolicy || (tc.approvalRequired ? 'ask' : ''); var policyBadge = policy ? '' + escapeHtml(tc.approvalRequired ? (tc.approvalConfirmed ? 'approved' : 'approval required') : policy) + '' : ''; item.appendChild(el('div', { className: 'wolai-page-ai-pi-lab-tool-call-header', 'data-page-ai-pi-lab-tool-summary': 'true', innerHTML: '' + escapeHtml(displayName || 'tool') + policyBadge + '' + escapeHtml(tc.status || '') + '', })); if (summaryParts.length) { item.appendChild(el('div', { className: 'wolai-page-ai-pi-lab-tool-call-summary-line', 'data-page-ai-pi-lab-tool-compact-summary': 'true', }, summaryParts.join(' · '))); } var hasDetails = (tc.args && Object.keys(tc.args).length) || (tc.details && Object.keys(tc.details).length) || tc.partialResult || tc.result; if (hasDetails) { var details = el('details', { className: 'wolai-page-ai-pi-lab-tool-call-details', 'data-page-ai-pi-lab-tool-details': displayName || 'tool', }); details.appendChild(el('summary', {}, 'Input / Output / Raw')); if (tc.args && Object.keys(tc.args).length) details.appendChild(el('pre', { textContent: 'Input\n' + compactJson(tc.args) })); if (tc.details && Object.keys(tc.details).length) details.appendChild(el('pre', { textContent: 'Details\n' + compactJson(tc.details) })); if (tc.partialResult && tc.status === 'running') details.appendChild(el('pre', { textContent: 'Partial output\n' + extractResultText(tc.partialResult) })); if (tc.result) details.appendChild(el('pre', { textContent: 'Output\n' + resultText })); item.appendChild(details); } return item; } 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\/([^/]+)/); if (match) return decodeURIComponent(match[1]); var missingPage = String(currentUrl().searchParams.get('missingPage') || currentUrl().searchParams.get('pageId') || '').trim(); return missingPage ? missingPage : ''; } function currentWorkspaceId() { return currentUrl().searchParams.get('workspaceId') || (document.body && document.body.dataset ? String(document.body.dataset.workspaceId || '').trim() : ''); } function currentRootUri() { var fromUrl = String(currentUrl().searchParams.get('rootUri') || '').trim(); if (fromUrl) return fromUrl; var fromBody = document.body instanceof HTMLElement ? String(document.body.getAttribute('data-mnote-root-uri') || '').trim() : ''; if (fromBody) return fromBody; return String((piLabState.session && piLabState.session.rootUri) || '').trim(); } function localMarkdownRelativePathFromDocumentId(documentId) { var value = String(documentId || '').trim(); if (!value.startsWith('local-md:')) return ''; var encoded = value.slice('local-md:'.length); try { return decodeURIComponent(encoded.replace(/~([0-9A-Fa-f]{2})/g, '%$1')); } catch (_) { return encoded.replace(/~2F/gi, '/'); } } function cssEscape(value) { var text = String(value || ''); if (window.CSS && typeof window.CSS.escape === 'function') return window.CSS.escape(text); return text.replace(/["\\]/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 activeFileTreeWorkspacePath(documentId) { try { var relativePath = localMarkdownRelativePathFromDocumentId(documentId); var selector = '#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"]'; if (relativePath) selector += '[data-local-relative-path="' + cssEscape(relativePath) + '"]'; var row = document.querySelector(selector); var reader = window.__mnoteFileTreeRuntime && typeof window.__mnoteFileTreeRuntime.readWorkspacePathFromRow === 'function' ? window.__mnoteFileTreeRuntime.readWorkspacePathFromRow : null; if (row instanceof HTMLElement && reader) { var workspacePath = reader(row); if (workspacePath && typeof workspacePath === 'object') { return Object.assign({}, workspacePath, { relativePath: workspacePath.relativePath || row.getAttribute('data-local-relative-path') || '', rootUri: workspacePath.rootUri || row.getAttribute('data-root-uri') || '', sourceKind: workspacePath.sourceKind || row.getAttribute('data-source-kind') || '', documentId: workspacePath.documentId || row.getAttribute('data-document-id') || documentId || '', }); } } if (row instanceof HTMLElement) { return { relativePath: row.getAttribute('data-local-relative-path') || '', rootUri: row.getAttribute('data-root-uri') || '', sourceKind: row.getAttribute('data-source-kind') || '', documentId: row.getAttribute('data-document-id') || documentId || '', }; } } catch (_) {} return null; } function currentPageContext() { var active = activeEditorSnapshot() || {}; var activeWorkspacePath = active.workspacePath && typeof active.workspacePath === 'object' ? active.workspacePath : {}; var urlDocumentId = currentDocumentId(); var urlPagePath = localMarkdownRelativePathFromDocumentId(urlDocumentId); var documentId = String(urlDocumentId || activeWorkspacePath.documentId || active.documentId || '').trim(); var fileTreeWorkspacePath = activeFileTreeWorkspacePath(documentId) || {}; var workspacePath = Object.assign({}, activeWorkspacePath, fileTreeWorkspacePath); // Do not fall back to piLabState.session.* — that re-amplifies stale context after navigation. var pagePath = normalizeSlashes(urlPagePath || workspacePath.relativePath || workspacePath.path || active.relativePath || active.path || localMarkdownRelativePathFromDocumentId(documentId) || ''); var resourceKind = String(workspacePath.resourceKind || workspacePath.kind || active.resourceKind || active.kind || '').trim().toLowerCase(); var isDirectory = resourceKind === 'directory' || resourceKind === 'folder' || resourceKind === 'workspace'; var isStandalonePiPage = currentUrl().pathname === '/page-ai/pi'; var isLocalMarkdownPage = documentId.indexOf('local-md:') === 0 || /\.md$/i.test(pagePath); if (isDirectory || (isStandalonePiPage && pagePath && !isLocalMarkdownPage)) pagePath = ''; var rootUri = String(currentRootUri() || workspacePath.rootUri || active.rootUri || '').trim(); var workspaceId = String(currentWorkspaceId() || workspacePath.workspaceId || active.workspaceId || '').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 currentFolderPathFromPagePath(pagePath) { var normalized = normalizeSlashes(pagePath || '').replace(/^\/+/, ''); if (!normalized) return null; var parts = normalized.split('/').filter(Boolean); if (parts.length <= 1) return ''; parts.pop(); return parts.join('/'); } function contextSelectionState() { if (!piLabState.contextSelection || typeof piLabState.contextSelection !== 'object') { piLabState.contextSelection = {}; } if (typeof piLabState.contextSelection.currentPage !== 'boolean') piLabState.contextSelection.currentPage = false; if (typeof piLabState.contextSelection.currentFolder !== 'boolean') piLabState.contextSelection.currentFolder = false; if (typeof piLabState.contextSelection.selection !== 'boolean') piLabState.contextSelection.selection = false; if (typeof piLabState.contextSelection.lightrag !== 'boolean') piLabState.contextSelection.lightrag = false; return piLabState.contextSelection; } function selectedContextRefs() { var selection = contextSelectionState(); var refs = []; if (selection.currentPage) refs.push('current_page'); if (selection.currentFolder) refs.push('folder'); if (selection.selection) refs.push('selection'); if (selection.lightrag) refs.push('lightrag'); return refs; } function selectedContextPayload(context) { context = context || currentPageContext(); var selection = contextSelectionState(); var folderPath = currentFolderPathFromPagePath(context.pagePath); return { refs: selectedContextRefs(), currentPage: selection.currentPage ? { rootUri: context.rootUri || null, workspaceId: context.workspaceId || null, pagePath: context.pagePath || null, pageTitle: context.pageTitle || null, } : null, currentFolder: selection.currentFolder ? { rootUri: context.rootUri || null, workspaceId: context.workspaceId || null, folderPath: folderPath === null ? null : folderPath, } : null, selection: selection.selection ? { text: context.selection || piLabState.selectionText || '', source: 'mnote_sidebar_host', } : null, lightrag: selection.lightrag ? { enabled: true, provider: 'lightrag', } : null, }; } 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 = {}; // Always sync (including empty) so navigation away from a page clears stale context. piLabState.session.pagePath = context.pagePath || ''; piLabState.session.pageTitle = context.pageTitle || ''; piLabState.session.rootUri = context.rootUri || ''; piLabState.session.workspaceId = context.workspaceId || ''; var folderPath = currentFolderPathFromPagePath(context.pagePath); piLabState.session.folderPath = folderPath === null ? '' : folderPath; updateContextStrip(); return context; } function setContextQuickActive(kind, active) { var selection = contextSelectionState(); if (kind === 'read-page') selection.currentPage = active !== false; if (kind === 'current-folder') selection.currentFolder = active !== false; if (kind === 'selection') selection.selection = active !== false; if (kind === 'rag') selection.lightrag = active !== false; updateContextStrip(); updateButtons(); } function toggleContextQuick(kind) { var selection = contextSelectionState(); var current = false; if (kind === 'read-page') current = selection.currentPage; else if (kind === 'current-folder') current = selection.currentFolder; else if (kind === 'selection') current = selection.selection; else if (kind === 'rag') current = selection.lightrag; setContextQuickActive(kind, !current); } 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 modelControlOptions() { var source = piLabState.modelOptions.length ? piLabState.modelOptions : [{ provider: piLabState.defaultModelProvider || DEFAULT_MODEL_PROVIDER, id: piLabState.defaultModelId || DEFAULT_MODEL_ID, name: piLabState.defaultModelId || DEFAULT_MODEL_ID, }]; source = source.concat([{ provider: DEFAULT_MODEL_PROVIDER, id: 'freefirst', name: DEFAULT_MODEL_PROVIDER + '/freefirst', }, { provider: DEFAULT_MODEL_PROVIDER, id: DEFAULT_MODEL_ID, name: DEFAULT_MODEL_ID, }]); var seen = {}; return source.map(function (item) { var provider = String(item.provider || piLabState.defaultModelProvider || DEFAULT_MODEL_PROVIDER); var modelId = String(item.id || item.modelId || item.name || DEFAULT_MODEL_ID); if (modelId.indexOf(provider + '/') === 0) modelId = modelId.slice(provider.length + 1); var key = provider + '/' + modelId; if (seen[key]) return null; seen[key] = true; return { provider: provider, modelId: modelId, label: String(item.name || key), key: key, }; }).filter(Boolean); } function normalizeThinkingLevel(value) { var level = String(value || '').trim().toLowerCase(); return ['off', 'minimal', 'low', 'medium', 'high', 'xhigh'].indexOf(level) >= 0 ? level : DEFAULT_THINKING_LEVEL; } function thinkingLevelOptions() { return [ { level: 'off', label: '思考 关' }, { level: 'minimal', label: '思考 极低' }, { level: 'low', label: '思考 低' }, { level: 'medium', label: '思考 中' }, { level: 'high', label: '思考 高' }, { level: 'xhigh', label: '思考 最高' }, ]; } function currentThinkingLabel() { var labels = { off: '关', minimal: '极低', low: '低', medium: '中', high: '高', xhigh: '最高', }; return labels[normalizeThinkingLevel(piLabState.thinkingLevel || piLabState.defaultThinkingLevel)] || '中'; } function normalizePermissionMode(value) { var mode = String(value || '').trim().toLowerCase(); if (mode === 'ask') return 'confirm'; return ['confirm', 'auto_edit', 'plan', 'full_access'].indexOf(mode) >= 0 ? mode : DEFAULT_PERMISSION_MODE; } function permissionModeOptions() { return [ { mode: 'confirm', icon: 'status', label: '变更前确认', description: '改文件、执行命令前先问我。' }, { mode: 'auto_edit', icon: 'zap', label: '自动编辑', description: '授权目录内读写自动放行。' }, { mode: 'plan', icon: 'settings', label: '计划模式', description: '只读分析,禁止写入和命令。' }, { mode: 'full_access', icon: 'shield', label: '完全访问', description: '本会话内尽量不再确认。' }, ]; } function permissionModeLabel(mode) { mode = normalizePermissionMode(mode); var found = permissionModeOptions().filter(function (item) { return item.mode === mode; })[0]; return found ? found.label : '变更前确认'; } function currentRuntimePolicy() { return (piLabState.session && (piLabState.session.runtimePolicySnapshot || piLabState.session.runtimePolicy)) || {}; } function toolPolicyValues() { var policy = currentRuntimePolicy(); var policies = policy.mnoteToolPolicies || policy.mnote_tool_policies || {}; return Object.keys(policies).map(function (key) { return String(policies[key] || '').toLowerCase(); }).filter(Boolean); } function permissionSummary() { var selectedMode = normalizePermissionMode(piLabState.permissionMode); if (selectedMode === 'plan') return { label: '计划模式', tone: 'warn' }; if (selectedMode === 'auto_edit') return { label: '自动编辑', tone: 'ok' }; if (selectedMode === 'full_access') return { label: '完全访问', tone: 'ok' }; var values = toolPolicyValues(); var hasAsk = values.indexOf('ask') >= 0; var hasDeny = values.indexOf('deny') >= 0; var rootsText = String(piLabState.allowedRootsSummary || '').toLowerCase(); var readonly = rootsText.indexOf('read') >= 0 || rootsText.indexOf('只读') >= 0; if (hasDeny || readonly) return { label: hasAsk ? '受限/审批' : '受限', tone: 'danger' }; if (hasAsk || !values.length) return { label: '变更前确认', tone: 'warn' }; return { label: '自动工具', tone: 'ok' }; } function updateThinkingControl() { if (!piLabPanelEl) return; var wrap = piLabPanelEl.querySelector('[data-page-ai-pi-lab-thinking-menu-wrap]'); var control = piLabPanelEl.querySelector('[data-page-ai-pi-lab-thinking-menu-toggle]'); var label = piLabPanelEl.querySelector('[data-page-ai-pi-lab-thinking-label]'); var menu = piLabPanelEl.querySelector('[data-page-ai-pi-lab-thinking-menu]'); var level = normalizeThinkingLevel(piLabState.thinkingLevel || piLabState.defaultThinkingLevel); piLabState.thinkingLevel = level; if (wrap) wrap.setAttribute('data-open', piLabState.thinkingMenuOpen ? 'true' : 'false'); if (control) { control.setAttribute('data-active', piLabState.thinkingMenuOpen ? 'true' : 'false'); control.setAttribute('aria-expanded', piLabState.thinkingMenuOpen ? 'true' : 'false'); control.title = '思考深度:' + currentThinkingLabel() + ';启动本次 Pi 会话时应用'; } if (label) label.textContent = '思考 ' + currentThinkingLabel(); if (menu) menu.innerHTML = thinkingLevelOptions().map(function (item) { var active = item.level === level; return ''; }).join(''); } function updatePermissionControl() { if (!piLabPanelEl) return; var control = piLabPanelEl.querySelector('[data-page-ai-pi-lab-permission]'); var label = piLabPanelEl.querySelector('[data-page-ai-pi-lab-permission-label]'); var wrap = piLabPanelEl.querySelector('[data-page-ai-pi-lab-permission-wrap]'); var summary = permissionSummary(); if (label) label.textContent = summary.label; if (control) { control.setAttribute('data-tone', summary.tone || ''); control.setAttribute('data-active', piLabState.permissionMenuOpen ? 'true' : 'false'); control.title = 'Pi 模式:' + permissionModeLabel(piLabState.permissionMode) + ';目录范围:' + (piLabState.allowedRootsSummary || 'pending'); } if (wrap) wrap.setAttribute('data-open', piLabState.permissionMenuOpen ? 'true' : 'false'); Array.prototype.slice.call(piLabPanelEl.querySelectorAll('[data-page-ai-pi-lab-permission-mode]')).forEach(function (node) { node.setAttribute('data-active', node.getAttribute('data-page-ai-pi-lab-permission-mode') === normalizePermissionMode(piLabState.permissionMode) ? 'true' : 'false'); var check = node.querySelector('[data-page-ai-pi-lab-permission-check]'); if (check) check.textContent = node.getAttribute('data-active') === 'true' ? '✓' : ''; }); } function setPermissionMenuOpen(open) { if (open) { setActionMenuOpen(false); setModelMenuOpen(false); setThinkingMenuOpen(false); } piLabState.permissionMenuOpen = !!open; updatePermissionControl(); } function setModelMenuOpen(open) { if (open) { setActionMenuOpen(false); setPermissionMenuOpen(false); setThinkingMenuOpen(false); } piLabState.modelMenuOpen = !!open; updateModelLabel(); } function setThinkingMenuOpen(open) { if (open) { setActionMenuOpen(false); setPermissionMenuOpen(false); setModelMenuOpen(false); } piLabState.thinkingMenuOpen = !!open; updateThinkingControl(); } function closeComposerControlMenus() { if (!piLabState.actionMenuOpen && !piLabState.permissionMenuOpen && !piLabState.modelMenuOpen && !piLabState.thinkingMenuOpen) return; setActionMenuOpen(false); setPermissionMenuOpen(false); setModelMenuOpen(false); setThinkingMenuOpen(false); } function selectModelControl(provider, modelId) { piLabState.modelProvider = String(provider || piLabState.defaultModelProvider || DEFAULT_MODEL_PROVIDER); piLabState.modelId = String(modelId || piLabState.defaultModelId || DEFAULT_MODEL_ID); setModelMenuOpen(false); updateModelLabel(); applyModelConfigToRuntime(); } function selectThinkingControl(level) { piLabState.thinkingLevel = normalizeThinkingLevel(level); setThinkingMenuOpen(false); updateThinkingControl(); applyModelConfigToRuntime(); } function selectPermissionMode(mode) { piLabState.permissionMode = normalizePermissionMode(mode); setPermissionMenuOpen(false); updatePermissionControl(); if (piLabState.sessionId && piLabState.status !== STATE_STREAMING && piLabState.status !== STATE_STARTING && !piLabState.applyingPermissionMode) { return applyPermissionModeToRuntime(); } if (piLabState.sessionId && (piLabState.status === STATE_STREAMING || piLabState.status === STATE_STARTING || piLabState.applyingPermissionMode)) { piLabState.pendingPermissionModeApply = true; piLabState.pendingPermissionMode = piLabState.permissionMode; showPiToast('当前回复结束后自动应用:' + permissionModeLabel(piLabState.permissionMode), 'warning'); return Promise.resolve({ queued: true }); } showPiToast('Pi 模式已切换为:' + permissionModeLabel(piLabState.permissionMode), 'info'); return Promise.resolve({ queued: false }); } function maybeApplyPendingPermissionMode() { if (!piLabState.pendingPermissionModeApply || piLabState.applyingPermissionMode) return; if (!piLabState.sessionId || !piLabState.enabled) return; if (piLabState.status === STATE_STREAMING || piLabState.status === STATE_STARTING) return; window.setTimeout(function () { if (!piLabState.pendingPermissionModeApply || piLabState.applyingPermissionMode) return; if (piLabState.status === STATE_STREAMING || piLabState.status === STATE_STARTING) return; if (piLabState.pendingPermissionMode) { piLabState.permissionMode = normalizePermissionMode(piLabState.pendingPermissionMode); piLabState.pendingPermissionMode = null; updatePermissionControl(); } applyPermissionModeToRuntime().catch(function () {}); }, 0); } function applyPermissionModeToRuntime() { if (!piLabState.sessionId || !piLabState.enabled) return Promise.resolve({ skipped: true }); if (piLabState.applyingPermissionMode) { piLabState.pendingPermissionModeApply = true; piLabState.pendingPermissionMode = normalizePermissionMode(piLabState.permissionMode); return Promise.resolve({ queued: true }); } var context = applyCurrentContextToState(); var nextMode = normalizePermissionMode(piLabState.permissionMode); ensurePiToolCapableModel(); piLabState.pendingPermissionModeApply = false; piLabState.pendingPermissionMode = null; piLabState.applyingPermissionMode = true; setState(STATE_STARTING); updateDiagnostics('Applying Pi mode'); return fetch(API.START, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId: piLabState.sessionId, rootUri: context.rootUri || (piLabState.session && piLabState.session.rootUri) || undefined, workspaceId: context.workspaceId || (piLabState.session && piLabState.session.workspaceId) || undefined, pagePath: context.pagePath || (piLabState.session && piLabState.session.pagePath) || undefined, pageTitle: context.pageTitle || (piLabState.session && piLabState.session.pageTitle) || undefined, modelProvider: piLabState.modelProvider || piLabState.defaultModelProvider, modelId: piLabState.modelId || piLabState.defaultModelId, thinkingLevel: normalizeThinkingLevel(piLabState.thinkingLevel || piLabState.defaultThinkingLevel), permissionMode: nextMode, }), }) .then(function (r) { if (!r.ok) throw new Error('Mode switch 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.permissionMode = normalizePermissionMode(session.permissionMode || data.permissionMode || (session.runtimePolicySnapshot && session.runtimePolicySnapshot.permissionMode) || nextMode); piLabState.session = session || piLabState.session || null; piLabState.runtimeMode = session.runtimeMode || data.runtimeMode || piLabState.runtimeMode || null; piLabState.runtimePid = session.runtimePid || data.pid || piLabState.runtimePid || null; piLabState.piExtensionSources = data.piExtensionSources || piLabState.piExtensionSources || []; piLabState.piExtensionToolNames = data.piExtensionToolNames || piLabState.piExtensionToolNames || []; connectEventSource(); piLabState.applyingPermissionMode = false; setState(STATE_STARTED); updateDiagnostics('Pi mode applied'); updatePermissionControl(); showPiToast('Pi 模式已应用:' + permissionModeLabel(piLabState.permissionMode), 'success'); maybeApplyPendingPermissionMode(); return data; }) .catch(function (error) { piLabState.applyingPermissionMode = false; setState(STATE_ERROR); updateDiagnostics(error && error.message ? error.message : 'Pi mode switch failed'); showPiToast('Pi 模式切换失败', 'error'); throw error; }); } function setActionMenuOpen(open) { if (open) { setPermissionMenuOpen(false); setModelMenuOpen(false); setThinkingMenuOpen(false); } piLabState.actionMenuOpen = !!open; if (!piLabPanelEl) return; var composer = piLabPanelEl.querySelector('.wolai-page-ai-pi-lab-composer'); var toggle = piLabPanelEl.querySelector('[data-page-ai-pi-lab-action-menu-toggle]'); if (composer) composer.setAttribute('data-menu-open', piLabState.actionMenuOpen ? 'true' : 'false'); if (toggle) toggle.setAttribute('data-active', piLabState.actionMenuOpen ? 'true' : 'false'); } function closeActionMenu() { setActionMenuOpen(false); } function permissionModeMenuHtml() { return permissionModeOptions().map(function (item) { var active = item.mode === normalizePermissionMode(piLabState.permissionMode); return ''; }).join(''); } function handleActionMenuAction(action) { closeActionMenu(); if (action === 'read-page' || action === 'current-folder' || action === 'selection' || action === 'rag') { handleQuickAction(action); return; } if (action === 'directory-permission') { window.location.assign('/user/ai#ai-admin-access'); return; } if (action === 'attach-file' || action === 'camera') { pickPiPromptImages(action === 'camera'); return; } if (action === 'send-steer') { sendCurrentInput({ streamingBehavior: 'steer' }); return; } if (action === 'send-followup') { sendCurrentInput({ streamingBehavior: 'followUp' }); return; } if (action === 'abort-bash') { callPiRpcCommand('abort_bash', {}, 5000).then(function () { showPiToast('已请求中止 bash', 'info'); }).catch(function (error) { showPiToast(error && error.message ? error.message : 'bash 中止失败', 'warning'); }); return; } if (action === 'plan-mode') { var input = piLabPanelEl && piLabPanelEl.querySelector('[data-page-ai-pi-lab-input]'); var current = input ? input.value.trim() : ''; piLabState.permissionMode = 'plan'; updatePermissionControl(); if (piLabState.sessionId && piLabState.status !== STATE_STREAMING && piLabState.status !== STATE_STARTING) { applyPermissionModeToRuntime().catch(function () {}); } else { piLabState.pendingPermissionModeApply = true; } var planPrompt = current || '请先只做计划评审,不要修改文件。'; if (input) { input.value = planPrompt; input.focus(); updateButtons(); } showPiToast('已切换到 MNote 计划评审模式;发送后由后端只读 wrapper 处理', 'info'); } } 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 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; ensurePiToolCapableModel(); piLabState.permissionMode = normalizePermissionMode(piLabState.permissionMode); updateModelLabel(); updateThinkingControl(); updatePermissionControl(); } function applyModelConfigToRuntime() { if (!piLabState.sessionId) return Promise.resolve(); if (piLabState.status === STATE_STREAMING || piLabState.status === STATE_STARTING) { piLabState.pendingModelConfigApply = true; return Promise.resolve(); } piLabState.pendingModelConfigApply = false; return fetch(API.CONFIGURE, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId: piLabState.sessionId, modelProvider: piLabState.modelProvider, modelId: piLabState.modelId, thinkingLevel: piLabState.thinkingLevel, }), }).then(function (r) { return r.json(); }).then(function (data) { if (data && (data.message || data.error)) { showPiToast(data.message || data.error, 'warning'); } return data; }).catch(function (err) { showPiToast('配置应用失败:' + (err.message || ''), 'error'); }); } function maybeApplyPendingModelConfig() { if (!piLabState.pendingModelConfigApply || !piLabState.sessionId) return; if (piLabState.status === STATE_STREAMING || piLabState.status === STATE_STARTING) return; piLabState.pendingModelConfigApply = false; setTimeout(function () { applyModelConfigToRuntime().catch(function () {}); }, 0); } 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 stripModelProviderPrefix(provider, modelId) { var normalizedProvider = String(provider || DEFAULT_MODEL_PROVIDER).trim(); var normalizedModelId = String(modelId || '').trim(); if (normalizedProvider && normalizedModelId.indexOf(normalizedProvider + '/') === 0) { return normalizedModelId.slice(normalizedProvider.length + 1); } return normalizedModelId; } function ensurePiToolCapableModel() { var provider = piLabState.modelProvider || piLabState.defaultModelProvider || DEFAULT_MODEL_PROVIDER; var modelId = piLabState.modelId || piLabState.defaultModelId || DEFAULT_MODEL_ID; modelId = stripModelProviderPrefix(provider, modelId); piLabState.modelProvider = provider || DEFAULT_MODEL_PROVIDER; piLabState.modelId = modelId || DEFAULT_MODEL_ID; return true; } 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; if (!piLabState.modelProvider && !piLabState.modelId) { piLabState.modelProvider = piLabState.defaultModelProvider; piLabState.modelId = piLabState.defaultModelId; } ensurePiToolCapableModel(); piLabState.defaultThinkingLevel = normalizeThinkingLevel(data.defaultThinkingLevel || data.default_thinking_level || piLabState.defaultThinkingLevel); if (!piLabState.thinkingLevel) piLabState.thinkingLevel = piLabState.defaultThinkingLevel; piLabState.modelOptions = Array.isArray(data.models) ? data.models : []; piLabState.modelOptions = piLabState.modelOptions.concat([{ provider: DEFAULT_MODEL_PROVIDER, id: 'freefirst', name: DEFAULT_MODEL_PROVIDER + '/freefirst', }, { provider: DEFAULT_MODEL_PROVIDER, id: DEFAULT_MODEL_ID, name: DEFAULT_MODEL_ID, }]); 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 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; updateModelLabel(); updateThinkingControl(); 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 piIcon(name) { var icons = { plus: '', history: '', external: '', artifacts: '', status: '', bell: '', play: '', stop: '', trash: '', settings: '', zap: '', file: '', selection: '', database: '', shield: '', brain: '', folder: '', image: '', camera: '', queue: '', compact: '', copy: '', send: '', close: '', minus: '', }; return ''; } function piLabPanelHTML() { return '' + '
    ' + '
    ' + '
    ' + '' + '
    Pi Lab 平台默认模型:omniroute/gpt-5.4-mini
    ' + '
    ' + '' + '' + '
    ' + '
    ' + '' + '' + '' + '' + '' + '' + '' + '
    ' + '
    ' + '
    ' + 'idle设置omniroute/gpt-5.4-mini' + '
    ' + '' + '' + '' + 'runtimeruntime pending' + 'builtin managedread/write/edit/bash' + 'Pi 扩展pending' + '
    ' + '
    ' + '
    ' + '操作见顶部工具栏' + '
    ' + '
    ' + '' + '
    ' + '上下文未绑定0 changed' + '
    ' + '选区无选区' + 'allowed rootspending' + 'LightRAGdefault' + '
    ' + '
    ' + '
    ' + '
    ' + '' + '
    ' + '
    ' + '
    ' + '' + '' + '
    ' + '
    ' + '
    上下文
    ' + '' + '' + '' + '' + '
    ' + '
    权限与附件
    ' + '' + '' + '' + '
    ' + '
    执行中发送
    ' + '' + '' + '' + '' + '
    ' + '
    ' + '' + '
    ' + '' + '' + '
    ' + '
    ' + '' + '' + '
    ' + '
    ' + '' + '
    ' + permissionModeMenuHtml() + '
    ' + '
    ' + '' + '' + '' + '' + '
    ' + '
    ' + '
    ' + '' + '
    ' + '
    ' + '诊断' + '
    ' + '' + '' + '' + '' + '' + '' + '' + '
    ' + '
    ' +
              '' +
            '
    ' + '
    '; } function renderEmptyState(container) { container.innerHTML = '' + '
    ' + '
    ?
    ' + '开始对话' + '

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

    ' + '
    ' + '' + '' + '' + '' + '' + '
    ' + '
    '; } function historyTitle(session) { return (session && (session.title || session.preview || session.pageTitle || session.pagePath)) || '未命名会话'; } function formatHistoryTime(value) { if (!value) return '刚刚'; var time = Date.parse(value); if (!Number.isFinite(time)) return String(value); var diff = Date.now() - time; if (diff < 60 * 1000) return '刚刚'; if (diff < 60 * 60 * 1000) return Math.floor(diff / (60 * 1000)) + ' 分钟前'; if (diff < 24 * 60 * 60 * 1000) return Math.floor(diff / (60 * 60 * 1000)) + ' 小时前'; if (diff < 7 * 24 * 60 * 60 * 1000) return Math.floor(diff / (24 * 60 * 60 * 1000)) + ' 天前'; return new Date(time).toLocaleDateString(); } function historyShell(innerHtml) { var count = Array.isArray(piLabState.history) ? piLabState.history.length : 0; return '
    历史对话
    ' + '
    ' + '' + '' + '' + '
    ' + '
    共 ' + count + ' 条历史记录Pi Rust
    ' + '
    ' + 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 = historyShell('
    暂无历史对话
    '); return; } panel.innerHTML = historyShell(piLabState.history.map(function (session) { var title = escapeHtml(historyTitle(session)); var meta = escapeHtml([formatHistoryTime(session.updatedAt || session.createdAt), session.modelId || session.status].filter(Boolean).join(' · ')); var sessionId = escapeHtml(session.sessionId || ''); var active = piLabState.sessionId === (session.sessionId || ''); return '
    ' + '' + '' + '' + '' + '' + '' + '' + '
    '; }).join('')); } function setHistoryPanelVisible(visible) { if (!piLabPanelEl) return Promise.resolve(); var layer = piLabPanelEl.querySelector('[data-page-ai-pi-lab-history-layer]'); var panel = piLabPanelEl.querySelector('[data-page-ai-pi-lab-history-panel]'); var historyBtn = piLabPanelEl.querySelector('[data-page-ai-pi-lab-history]'); if (!layer || !panel) return Promise.resolve(); layer.hidden = !visible; if (historyBtn) historyBtn.setAttribute('data-active', visible ? 'true' : 'false'); if (visible) { var body = piLabPanelEl.querySelector('[data-page-ai-pi-lab-body]'); if (body) body.setAttribute('data-rail-open', 'false'); return loadHistory().catch(function (error) { panel.innerHTML = historyShell('
    ' + escapeHtml(error.message || '历史加载失败') + '
    '); }); } return Promise.resolve(); } 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 loadSessionTree(sessionId) { if (!sessionId) return Promise.resolve(null); return fetch(API.SESSIONS + '/' + encodeURIComponent(sessionId) + '/tree', { credentials: 'same-origin', cache: 'no-store' }).then(function (r) { if (!r.ok) return null; return r.json(); }).then(function (data) { if (data && data.schema === 'mnote.page_ai_pi.session_tree.v1' && Array.isArray(data.entries)) { return data; } if (data && Array.isArray(data.entries)) return data; return null; }).catch(function () { return null; }); } function treeEntriesToMessages(entries) { if (!Array.isArray(entries)) return []; return entries.map(function (entry) { var msg = { entryId: entry.id || '', role: entry.role === 'user' ? 'user' : 'assistant', text: entry.text || entry.content || entry.preview || '', meta: entry.meta || '', toolCalls: Array.isArray(entry.toolCalls) ? entry.toolCalls : [], citations: Array.isArray(entry.citations) ? entry.citations : [], diffSummary: entry.diffSummary || null, }; return msg; }).filter(function (m) { return m.text || m.toolCalls.length || m.citations.length || m.diffSummary; }); } function applyReplayMessages(messages) { (Array.isArray(messages) ? messages : []).forEach(function (msg) { if (msg.role === 'user') { applyPiRunEvent({ type: 'user_prompt', text: msg.text, meta: msg.meta }); } else if (msg.abortReason) { applyPiRunEvent({ type: 'assistant_abort', payload: { reason: msg.abortReason } }); } else { applyPiRunEvent({ type: 'assistant_done', text: msg.text, meta: msg.meta, toolCalls: msg.toolCalls, citations: msg.citations, diffSummary: msg.diffSummary }); } }); } function openHistorySession(sessionId) { if (!sessionId) return Promise.resolve(); if (piLabEventSource) { piLabEventSource.close(); piLabEventSource = null; } return fetch(API.SESSIONS + '/' + encodeURIComponent(sessionId), { credentials: 'same-origin', cache: 'no-store' }).then(function (r) { return r.json(); }).then(function (detail) { var detailSession = detail.session || detail; var runtime = detailSession.runtime || {}; var runtimePolicy = runtime.runtimePolicy || runtime.runtime_policy || {}; piLabState.sessionId = detailSession.sessionId || sessionId; piLabState.providerSessionId = runtime.providerSessionId || detailSession.providerSessionId || null; piLabState.session = Object.assign({}, runtime, detailSession, { sessionId: piLabState.sessionId, providerSessionId: piLabState.providerSessionId, pagePath: runtime.pagePath || detailSession.pagePath, pageTitle: runtime.pageTitle || detailSession.pageTitle || detailSession.title, rootUri: runtime.rootUri || detailSession.rootUri, workspaceId: runtime.workspaceId || detailSession.workspaceId, modelProvider: runtime.modelProvider || detailSession.modelProvider, modelId: runtime.modelId || detailSession.modelId, thinkingLevel: runtime.thinkingLevel || detailSession.thinkingLevel, runtimePolicySnapshot: runtimePolicy, }); piLabState.modelProvider = piLabState.session.modelProvider || piLabState.modelProvider || null; piLabState.modelId = piLabState.session.modelId || piLabState.modelId || null; piLabState.thinkingLevel = normalizeThinkingLevel(piLabState.session.thinkingLevel || piLabState.thinkingLevel || piLabState.defaultThinkingLevel); piLabState.permissionMode = normalizePermissionMode(runtimePolicy.permissionMode || runtimePolicy.permission_mode || detailSession.permissionMode || piLabState.permissionMode); piLabState.allowedRootsSummary = summarizeAllowedRoots(runtime.allowedRootsSnapshot || piLabState.session.allowedRootsSnapshot); exitHistoryReplayMode(); resetPiRunViewModel(); return loadSessionTree(sessionId).then(function (treeData) { var treeMessages = treeData && Array.isArray(treeData.messages) && treeData.messages.length ? treeData.messages : (treeData && treeData.entries && treeData.entries.length ? treeEntriesToMessages(treeData.entries) : []); applyReplayMessages(treeMessages); }).then(function () { if (streamingAssistantMsg && String(streamingAssistantMsg.text || '').trim()) { applyPiRunEvent({ type: 'assistant_done', text: streamingAssistantMsg.text }); } setState(STATE_IDLE); updateDiagnostics('已打开历史 session,可直接继续发送。'); updateMessages(); updateButtons(); updateArtifactRail(); updateReceiptDisplay(); updateQueueDisplay(); setHistoryPanelVisible(false); }); }); } function currentHistorySession(sessionId) { return (piLabState.history || []).find(function (item) { return item && item.sessionId === sessionId; }) || null; } function renameHistorySession(sessionId) { if (!sessionId) return Promise.resolve(); var existing = currentHistorySession(sessionId); var nextTitle = window.prompt('重命名 Pi session', (existing && (existing.title || existing.preview || existing.pageTitle)) || ''); if (nextTitle == null) return Promise.resolve(); nextTitle = String(nextTitle || '').trim(); if (!nextTitle) { showPiToast('标题不能为空', 'warning'); return Promise.resolve(); } return fetch(API.SESSIONS + '/' + encodeURIComponent(sessionId), { method: 'PATCH', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', accept: 'application/json' }, body: JSON.stringify({ title: nextTitle, workspaceId: (piLabState.session && piLabState.session.workspaceId) || currentWorkspaceId() || undefined, }), }).then(function (response) { return response.json().catch(function () { return {}; }).then(function (payload) { if (!response.ok) throw new Error(payload.message || ('Rename failed: ' + response.status)); showPiToast('Session renamed', 'success'); return loadHistory(); }); }); } function resetCurrentSessionAfterHistoryDelete(sessionId) { if (piLabState.sessionId !== sessionId && piLabState.viewingHistorySessionId !== sessionId) return; if (piLabEventSource) { piLabEventSource.close(); piLabEventSource = null; } exitHistoryReplayMode(); piLabState.sessionId = null; piLabState.providerSessionId = null; piLabState.status = STATE_IDLE; piLabState.session = null; resetPiRunViewModel(); piLabState.receipts = []; updateMessages(); updateReceiptDisplay(); updateQueueDisplay(); updateConfig(); } async function deleteHistorySession(sessionId) { if (!sessionId) return Promise.resolve(); var existing = currentHistorySession(sessionId); var title = existing ? historyTitle(existing) : sessionId; if (!(await window.mnote.confirm('删除历史对话“' + title + '”?'))) return Promise.resolve(); return fetch(API.SESSIONS + '/' + encodeURIComponent(sessionId), { method: 'DELETE', credentials: 'same-origin', headers: { accept: 'application/json' }, }).then(function (response) { return response.json().catch(function () { return {}; }).then(function (payload) { if (!response.ok) throw new Error(payload.message || ('Delete failed: ' + response.status)); resetCurrentSessionAfterHistoryDelete(sessionId); showPiToast('历史对话已删除', 'success'); return loadHistory(); }); }); } async function clearHistorySessions() { var count = Array.isArray(piLabState.history) ? piLabState.history.length : 0; if (!count) return Promise.resolve(); if (!(await window.mnote.confirm('清空全部 ' + count + ' 条 Pi 历史对话?'))) return Promise.resolve(); return fetch(API.SESSIONS + '?limit=1000', { method: 'DELETE', credentials: 'same-origin', headers: { accept: 'application/json' }, }).then(function (response) { return response.json().catch(function () { return {}; }).then(function (payload) { if (!response.ok) throw new Error(payload.message || ('Clear failed: ' + response.status)); resetCurrentSessionAfterHistoryDelete(piLabState.sessionId || piLabState.viewingHistorySessionId); piLabState.history = []; renderHistory(); showPiToast('历史对话已清空', 'success'); }); }); } function exportHistorySession(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=1000', { credentials: 'same-origin', cache: 'no-store' }).then(function (r) { return r.json(); }), ]).then(function (results) { var payload = { schema: 'mnote.page_ai_pi.export_session.v1', exportedAt: new Date().toISOString(), session: results[0] && results[0].session || null, events: results[1] && Array.isArray(results[1].events) ? results[1].events : [], }; var blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json;charset=utf-8' }); var objectUrl = URL.createObjectURL(blob); var link = document.createElement('a'); link.href = objectUrl; link.download = 'pi-session-' + sessionId + '.json'; document.body.appendChild(link); link.click(); link.remove(); window.setTimeout(function () { URL.revokeObjectURL(objectUrl); }, 1000); showPiToast('历史对话已导出', 'success'); }); } function forkHistorySession(sessionId) { return openHistorySession(sessionId).then(function () { var oldSessionId = piLabState.sessionId; var source = piLabState.session || {}; if (piLabEventSource) { piLabEventSource.close(); piLabEventSource = null; } piLabState.sessionId = null; piLabState.providerSessionId = null; piLabState.status = STATE_IDLE; exitHistoryReplayMode(); resetPiRunViewModel(); piLabState.receipts = []; piLabState.session = Object.assign({}, source, { sessionId: null, providerSessionId: null, pageTitle: ((source.pageTitle || source.title || 'Pi session') + ' fork').slice(0, 120), }); updateMessages(); updateReceiptDisplay(); updateConfig(); updateDiagnostics('Forked from ' + oldSessionId + '; start creates a new Pi runtime'); showPiToast('Fork prepared', 'success'); return startRuntime(); }).then(function (data) { return loadHistory().then(function () { return data; }).catch(function () { return data; }); }); } function forkHistoryEntry(sessionId, entryId) { if (!sessionId) return Promise.reject(new Error('sessionId required')); if (!entryId) return forkHistorySession(sessionId); return fetch(API.FORK, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId: sessionId, entryId: entryId }), }).then(function (r) { return r.json(); }).then(function (data) { if (data && data.sessionId) { showPiToast('从 entry 创建的新会话已就绪', 'success'); return openHistorySession(data.sessionId); } throw new Error((data && (data.message || data.error)) || 'fork failed'); }); } 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.status === 'aborted' ? 'aborted' : (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) { var rendersMarkdown = msg.role === 'assistant' || msg.role === 'system'; bubble.appendChild(el('div', { className: (rendersMarkdown ? 'wolai-page-ai-pi-lab-markdown' : 'wolai-page-ai-pi-lab-text') + (msg.status === 'streaming' ? ' streaming-cursor' : ''), innerHTML: rendersMarkdown ? renderMarkdownBlock(msg.text) : escapeHtml(msg.text).replace(/\n/g, '
    '), })); } if (msg.toolCalls && msg.toolCalls.length) { var tools = el('details', { className: 'wolai-page-ai-pi-lab-tool-timeline', 'data-page-ai-pi-lab-tool-timeline': 'true', 'data-page-ai-pi-tool-timeline': 'true', }); tools.appendChild(el('summary', {}, [ el('span', {}, '工具调用'), el('span', { className: 'wolai-page-ai-pi-lab-rail-count' }, String(msg.toolCalls.length)), ])); var toolsBody = el('div', { className: 'wolai-page-ai-pi-lab-tool-timeline-body' }); msg.toolCalls.forEach(function (tc) { toolsBody.appendChild(renderToolCallCard(tc)); }); tools.appendChild(toolsBody); 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(', '))); } if (msg.status === 'aborted' || msg.abortReason) { var abortPayload = msg.abortResponse || {}; var abortLines = [msg.abortReason || '已中止']; if (abortPayload.schema) abortLines.push('schema=' + abortPayload.schema); if (abortPayload.stopReason) abortLines.push('stopReason=' + abortPayload.stopReason); if (abortPayload.command) abortLines.push('command=' + abortPayload.command); var queue = piLabState.pendingQueue || {}; var steering = Array.isArray(queue.steering) ? queue.steering.length : 0; var followUp = Array.isArray(queue.followUp) ? queue.followUp.length : 0; abortLines.push('queued messages=' + (steering + followUp)); bubble.appendChild(el('div', { className: 'wolai-page-ai-pi-lab-abort-note', 'data-page-ai-pi-lab-abort-note': 'true', 'data-page-ai-pi-lab-stop-reason': abortPayload.stopReason || 'aborted', }, abortLines.join(' · '))); } if (msg.role === 'assistant' && (msg.text || msg.toolCalls || msg.citations || msg.diffSummary)) { bubble.appendChild(el('div', { className: 'wolai-page-ai-pi-lab-message-actions' }, [ el('button', { type: 'button', title: '复制 Markdown', 'aria-label': '复制 Markdown', 'data-page-ai-pi-lab-copy-markdown': messageToMarkdown(msg), innerHTML: piIcon('copy'), }), ])); } if (piLabState.viewingHistorySessionId && msg.role !== 'system') { var forkEntryBtn = el('div', { className: 'wolai-page-ai-pi-lab-message-actions wolai-page-ai-pi-lab-fork-entry' }, [ el('button', { type: 'button', title: '从该条消息继续', 'aria-label': '从该条消息继续', 'data-page-ai-pi-lab-fork-entry': msg.entryId || msg._entryIndex || '', innerHTML: '从这里继续', }), ]); bubble.appendChild(forkEntryBtn); } card.appendChild(bubble); return card; } function isVisibleMessage(msg) { if (!msg) return false; if (msg.role === 'system' && /^Pi session started:/.test(String(msg.text || ''))) return false; if (msg.status === 'streaming') return true; if (msg.status === 'aborted' || msg.abortReason) 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 modelWrap = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-menu-wrap]'); var modelControl = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-menu-toggle]'); var modelLabel = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-label]'); var modelMenu = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-menu]'); 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; modelId = stripModelProviderPrefix(provider, modelId); piLabState.modelProvider = provider; piLabState.modelId = modelId; if (titleLabel) titleLabel.textContent = '默认模型:' + currentModelLabel(); if (modelChip) modelChip.textContent = currentModelLabel(); if (modelWrap) modelWrap.setAttribute('data-open', piLabState.modelMenuOpen ? 'true' : 'false'); if (modelControl) { modelControl.setAttribute('data-active', piLabState.modelMenuOpen ? 'true' : 'false'); modelControl.setAttribute('aria-expanded', piLabState.modelMenuOpen ? 'true' : 'false'); modelControl.title = '模型:' + currentModelLabel(); } if (modelLabel) modelLabel.textContent = currentModelLabel(); if (modelMenu) { modelMenu.innerHTML = modelControlOptions().map(function (item) { var active = item.provider === provider && item.modelId === modelId; return ''; }).join(''); } if (providerSelect && providerSelect.value !== provider) providerSelect.value = provider; if (modelSelect && modelSelect.value !== modelId) { if (Array.prototype.some.call(modelSelect.options, function (option) { return option.value === modelId; })) { modelSelect.value = modelId; } else if (Array.prototype.some.call(modelSelect.options, function (option) { return option.value === provider + '/' + modelId; })) { modelSelect.value = provider + '/' + modelId; } } if (customInput) customInput.value = modelId !== DEFAULT_MODEL_ID && modelId !== 'freefirst' ? modelId : ''; } function updateConfig() { if (!piLabPanelEl) return; updateModelLabel(); updateThinkingControl(); 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-managed]'); var extensionCountNode = piLabPanelEl.querySelector('[data-page-ai-pi-lab-extension-count]'); var replayBanner = piLabPanelEl.querySelector('[data-page-ai-pi-lab-replay-banner]'); 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) { var managed = Array.isArray(piLabState.managedBuiltinTools) ? piLabState.managedBuiltinTools : []; builtinNode.style.display = managed.length ? '' : 'none'; var strong = builtinNode.querySelector('strong'); if (strong) strong.textContent = managed.length ? managed.join('/') : 'pending'; } if (extensionCountNode) { var extensions = Array.isArray(piLabState.piExtensionSources) ? piLabState.piExtensionSources : []; extensionCountNode.textContent = extensions.length ? String(extensions.length) + ' packages' : 'pending'; extensionCountNode.title = extensions.join('\n'); } if (replayBanner) { replayBanner.hidden = !piLabState.viewingHistorySessionId; var replayText = replayBanner.querySelector('span'); if (replayText) replayText.textContent = piLabState.viewingHistorySessionId ? '正在查看历史,只读' : ''; } updateContextStrip(); updatePermissionControl(); updateButtons(); updateQueueDisplay(); 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 = {}; (Array.isArray(piLabState.changedFiles) ? piLabState.changedFiles : []).forEach(function (file) { if (file) files[file] = true; }); (Array.isArray(piLabState.messages) ? 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); } var selected = contextSelectionState(); Array.prototype.slice.call(piLabPanelEl.querySelectorAll('[data-page-ai-pi-lab-quick]')).forEach(function (btn) { var kind = btn.getAttribute('data-page-ai-pi-lab-quick') || ''; var active = (kind === 'read-page' && selected.currentPage) || (kind === 'current-folder' && selected.currentFolder) || (kind === 'selection' && selected.selection) || (kind === 'rag' && selected.lightrag); btn.setAttribute('data-active', active ? 'true' : 'false'); btn.setAttribute('aria-pressed', active ? 'true' : 'false'); }); Array.prototype.slice.call(piLabPanelEl.querySelectorAll('[data-page-ai-pi-lab-menu-action]')).forEach(function (btn) { var action = btn.getAttribute('data-page-ai-pi-lab-menu-action') || ''; var active = (action === 'read-page' && selected.currentPage) || (action === 'current-folder' && selected.currentFolder) || (action === 'selection' && selected.selection) || (action === 'rag' && selected.lightrag); if (['read-page', 'current-folder', 'selection', 'rag'].indexOf(action) >= 0) { btn.setAttribute('data-active', active ? 'true' : 'false'); btn.setAttribute('aria-pressed', active ? 'true' : 'false'); } }); Array.prototype.slice.call(piLabPanelEl.querySelectorAll('[data-page-ai-pi-lab-context-mark]')).forEach(function (mark) { var action = mark.getAttribute('data-page-ai-pi-lab-context-mark') || ''; var active = (action === 'read-page' && selected.currentPage) || (action === 'current-folder' && selected.currentFolder) || (action === 'selection' && selected.selection) || (action === 'rag' && selected.lightrag); mark.textContent = active ? '✓' : ''; }); updatePermissionControl(); } function updateButtons() { if (!piLabPanelEl) return; 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 modelControl = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-menu-toggle]'); var thinkingControl = piLabPanelEl.querySelector('[data-page-ai-pi-lab-thinking-menu-toggle]'); var customInput = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-custom]'); var isReplay = !!piLabState.viewingHistorySessionId; 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; var compactBtn = piLabPanelEl.querySelector('[data-page-ai-pi-lab-btn-compact]'); if (sendBtn) { sendBtn.disabled = isReplay || !piLabState.enabled || !input || !input.value.trim(); } if (abortBtn) abortBtn.hidden = !(isStreaming || piLabState.status === STATE_STARTING); if (compactBtn) compactBtn.disabled = isReplay || piLabState.isCompacting; if (input) { input.disabled = isReplay; input.placeholder = isReplay ? '历史对话为只读,Fork 后继续' : '问 Pi...'; } var controlsDisabled = isReplay || piLabState.status === STATE_STREAMING || piLabState.status === STATE_STARTING; [providerSelect, modelSelect, modelControl, thinkingControl, customInput].forEach(function (node) { if (node) node.disabled = isReplay || piLabState.status === STATE_STREAMING || piLabState.status === STATE_STARTING; }); if (controlsDisabled && (piLabState.modelMenuOpen || piLabState.thinkingMenuOpen)) { piLabState.modelMenuOpen = false; piLabState.thinkingMenuOpen = false; updateModelLabel(); updateThinkingControl(); } } function updateQueueDisplay() { if (!piLabPanelEl) return; var queue = piLabState.pendingQueue || {}; var steering = Array.isArray(queue.steering) ? queue.steering.length : 0; var followUp = Array.isArray(queue.followUp) ? queue.followUp.length : 0; var count = steering + followUp; var node = piLabPanelEl.querySelector('[data-page-ai-pi-lab-queue-count]'); if (node) { node.textContent = String(count); node.title = '排队消息 ' + count; } } 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); updateArtifactRail(); return; } visibleMessages.forEach(function (msg) { container.appendChild(renderMessage(msg)); }); container.scrollTop = container.scrollHeight; updateContextStrip(); updateQueueDisplay(); updateArtifactRail(); } 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('advancedRuntime=' + advancedRuntimeSummary(piLabState.advancedRuntime)); lines.push('managedBuiltinTools=' + ((Array.isArray(piLabState.managedBuiltinTools) && piLabState.managedBuiltinTools.length) ? piLabState.managedBuiltinTools.join('/') : 'pending')); if (Array.isArray(piLabState.piExtensionSources) && piLabState.piExtensionSources.length) { lines.push('piExtensions=' + piLabState.piExtensionSources.join(',')); } if (piLabState.allowedRootsSummary) lines.push('allowedRoots=' + piLabState.allowedRootsSummary); if (piLabState.diagnostics) lines.push('message=' + piLabState.diagnostics); pre.textContent = lines.join('\n'); } function advancedRuntimeSummary(config) { config = config || {}; var pairs = [ ['extensionPolicy', config.extensionPolicy], ['repairPolicy', config.repairPolicy], ['sessionDurability', config.sessionDurability], ['requestTimeoutSecs', config.requestTimeoutSecs], ['maxToolIterations', config.maxToolIterations], ['hideCwdInPrompt', config.hideCwdInPrompt === true ? 'true' : 'false'], ].filter(function (item) { return item[1] !== undefined && item[1] !== null && item[1] !== ''; }); return pairs.length ? pairs.map(function (item) { return item[0] + '=' + item[1]; }).join(',') : 'default'; } function setDiagnosticOutput(payload) { if (!piLabPanelEl) return; var output = piLabPanelEl.querySelector('[data-page-ai-pi-lab-diagnostic-output]'); var details = piLabPanelEl.querySelector('[data-page-ai-pi-lab-diagnostics]'); if (!output) return; if (details) details.open = true; output.hidden = false; output.textContent = typeof payload === 'string' ? payload : JSON.stringify(payload || {}, null, 2); } function updatePiFloatingLayerPosition(layer) { if (!layer || !piLabPanelEl) return; var composer = piLabPanelEl.querySelector('.wolai-page-ai-pi-lab-composer'); if (!composer) return; layer.style.bottom = Math.max(0, Math.ceil(composer.offsetHeight) + 6) + 'px'; } function ensureToastStack() { var stack = document.querySelector('[data-page-ai-pi-lab-toast-stack]'); if (stack) { updatePiFloatingLayerPosition(stack); return stack; } var host = piLabPanelEl && piLabPanelEl.querySelector('.wolai-page-ai-pi-lab-main'); stack = el('div', { className: 'wolai-page-ai-pi-lab-toast-stack', 'data-page-ai-pi-lab-toast-stack': 'true', }); (host || document.body).appendChild(stack); updatePiFloatingLayerPosition(stack); return stack; } function showPiToast(message, type) { var stack = ensureToastStack(); var toast = el('div', { className: 'wolai-page-ai-pi-lab-toast', 'data-page-ai-pi-lab-toast': String(++piToastSeq), 'data-type': type || 'info', textContent: message || '', }); stack.appendChild(toast); setTimeout(function () { if (toast.parentNode) toast.parentNode.removeChild(toast); }, 4200); return toast; } function closePiUiDialog() { if (activePiUiDialog && activePiUiDialog.parentNode) activePiUiDialog.parentNode.removeChild(activePiUiDialog); activePiUiDialog = null; } function closePiCustomUiDialog() { if (piCustomUiState.panel && piCustomUiState.panel.parentNode) piCustomUiState.panel.parentNode.removeChild(piCustomUiState.panel); piCustomUiState.panel = null; piCustomUiState.lines = []; piCustomUiState.title = ''; piCustomUiState.selectedOptionIndex = 0; piCustomUiState.submitted = false; piCustomUiState.pendingCustomRequest = null; } function sendPiUiResponse(request, response) { if (!request || !request.id || !piLabState.sessionId) return Promise.resolve(null); var body = { sessionId: piLabState.sessionId, requestId: request.id, method: request.method || '', }; if (request.mnoteApproval) body.mnoteApproval = request.mnoteApproval; Object.keys(response || {}).forEach(function (key) { body[key] = response[key]; }); return fetch(API.UI_RESPONSE, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }).catch(function (error) { updateDiagnostics('Extension UI response failed: ' + (error && error.message ? error.message : String(error))); }); } function stripPiAnsi(text) { return String(text == null ? '' : text) .replace(/\x1b\][^\x07]*(?:\x07|\x1b\\)/g, '') .replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, '') .replace(/\x1b[@-Z\\-_]/g, ''); } function piCustomUiWidth() { var panel = piCustomUiState.panel; var body = panel && panel.querySelector('.wolai-page-ai-pi-lab-ui-dialog-body'); var px = (body && body.clientWidth) || (panel && panel.clientWidth) || 640; return Math.max(40, Math.min(120, Math.floor(px / 8))); } function parsePiCustomUiOptions(lines) { var options = []; (Array.isArray(lines) ? lines : []).forEach(function (line) { var clean = stripPiAnsi(line).replace(/[│┃║]/g, '').trim(); var match = clean.match(/^(?:>\s*)?(\d+)\.\s+(.+?)\s*(?:[✎*])?$/); if (!match) return; var label = match[2].trim(); if (!label) return; options.push({ index: Number(match[1]) - 1, label: label }); }); return options; } function renderPiCustomUiDialog(request) { request = request || {}; var widgetKey = String(request.widgetKey || request.widget_key || piCustomUiState.widgetKey || '__pi_custom_overlay'); if (request.clear || request.close) { closePiCustomUiDialog(); return null; } if (piCustomUiState.submitted) return null; var lines = Array.isArray(request.lines) ? request.lines : (Array.isArray(request.widgetLines) ? request.widgetLines : String(request.content || '').split('\n')); piCustomUiState.widgetKey = widgetKey; piCustomUiState.lines = lines.map(stripPiAnsi); piCustomUiState.title = String(request.title || piCustomUiState.title || 'Pi 需要输入').trim(); var options = parsePiCustomUiOptions(piCustomUiState.lines); if (piCustomUiState.selectedOptionIndex >= options.length) piCustomUiState.selectedOptionIndex = 0; var panel = piCustomUiState.panel; if (!panel) { panel = el('div', { className: 'wolai-page-ai-pi-lab-ui-backdrop', 'data-page-ai-pi-lab-ui-dialog': 'custom', 'data-page-ai-pi-lab-ui-widget-key': widgetKey, }); var composer = piLabPanelEl && piLabPanelEl.querySelector('.wolai-page-ai-pi-lab-composer'); if (composer) composer.insertBefore(panel, composer.firstChild); else document.body.appendChild(panel); piCustomUiState.panel = panel; } panel.setAttribute('data-page-ai-pi-lab-ui-widget-key', widgetKey); panel.innerHTML = ''; var dialog = el('section', { className: 'wolai-page-ai-pi-lab-ui-dialog', role: 'dialog', 'aria-modal': 'false' }); dialog.appendChild(el('div', { className: 'wolai-page-ai-pi-lab-ui-dialog-header' }, [ el('div', {}, [ el('strong', {}, piCustomUiState.title || 'Pi 需要输入'), el('span', {}, '官方 Pi Rust custom UI'), ]), el('button', { type: 'button', className: 'wolai-page-ai-pi-lab-ui-collapse', 'data-page-ai-pi-lab-ui-collapse': 'true', title: '折叠/展开' }, '▾'), ])); var body = el('div', { className: 'wolai-page-ai-pi-lab-ui-dialog-body' }); body.appendChild(el('pre', { className: 'wolai-page-ai-pi-lab-ui-tui' }, piCustomUiState.lines.join('\n'))); if (options.length) { var optionList = el('div', { className: 'wolai-page-ai-pi-lab-ui-tui-options' }); options.forEach(function (option, visibleIndex) { var selected = visibleIndex === piCustomUiState.selectedOptionIndex; optionList.appendChild(el('button', { type: 'button', className: 'wolai-page-ai-pi-lab-ui-option', 'data-page-ai-pi-lab-ui-option': option.label, 'data-custom-option-index': String(visibleIndex), 'data-custom-selected': selected ? 'true' : 'false', }, [ el('span', { className: 'wolai-page-ai-pi-lab-ui-option-mark' }, selected ? '◉' : '○'), el('span', {}, option.label), ])); }); body.appendChild(optionList); } dialog.appendChild(body); var actions = el('div', { className: 'wolai-page-ai-pi-lab-ui-dialog-actions' }); actions.appendChild(el('button', { type: 'button', 'data-page-ai-pi-lab-ui-cancel': 'true' }, '取消')); actions.appendChild(el('button', { type: 'button', 'data-page-ai-pi-lab-ui-submit': 'true', 'data-primary': 'true' }, '提交')); dialog.appendChild(actions); panel.appendChild(dialog); panel.onclick = function (event) { if (event.target && event.target.closest && event.target.closest('[data-page-ai-pi-lab-ui-collapse]')) { var collapsed = dialog.getAttribute('data-collapsed') === 'true'; dialog.setAttribute('data-collapsed', collapsed ? 'false' : 'true'); event.target.textContent = collapsed ? '▾' : '▸'; return; } var option = event.target && event.target.closest ? event.target.closest('[data-custom-option-index]') : null; if (option) { piCustomUiState.selectedOptionIndex = Number(option.getAttribute('data-custom-option-index')) || 0; renderPiCustomUiDialog(request); return; } if (event.target && event.target.closest && event.target.closest('[data-page-ai-pi-lab-ui-cancel]')) { var cancelRequest = piCustomUiState.pendingCustomRequest; piCustomUiState.pendingCustomRequest = null; piCustomUiState.submitted = true; if (panel.parentNode) panel.parentNode.removeChild(panel); piCustomUiState.panel = null; if (cancelRequest) sendPiUiResponse(cancelRequest, { value: { width: piCustomUiWidth(), key: '\x1b' } }); else piCustomUiState.keyQueue.push('\x1b'); return; } if (event.target && event.target.closest && event.target.closest('[data-page-ai-pi-lab-ui-submit]')) { var selectedIndex = Math.max(0, piCustomUiState.selectedOptionIndex || 0); var submitKeys = []; for (var i = 0; i < selectedIndex; i += 1) submitKeys.push('\x1b[B'); submitKeys.push('\r'); var submitRequest = piCustomUiState.pendingCustomRequest; piCustomUiState.pendingCustomRequest = null; piCustomUiState.submitted = true; if (panel.parentNode) panel.parentNode.removeChild(panel); piCustomUiState.panel = null; if (submitRequest) { var firstKey = submitKeys.shift(); sendPiUiResponse(submitRequest, { value: { width: piCustomUiWidth(), key: firstKey } }); } submitKeys.forEach(function (key) { piCustomUiState.keyQueue.push(key); }); } }; return panel; } function respondToPiCustomUiPoll(request) { var value = { width: piCustomUiWidth() }; var key = piCustomUiState.keyQueue.shift(); if (key) value.key = key; return sendPiUiResponse(request, { value: value }); } function normalizePiAskQuestions(request) { var source = Array.isArray(request.questions) && request.questions.length ? request.questions : [{ header: request.header || request.question || request.title || 'Pi 需要选择', tab: request.tab || 'answer', prompt: request.prompt || request.message || request.context || '', options: request.options || [], multiSelect: request.allowMultiple === true || request.multiSelect === true, allowSkip: request.allowSkip !== false, allowFreeform: request.allowFreeform !== false, }]; return source.map(function (question, index) { return { header: String(question.header || question.title || question.question || ('问题 ' + (index + 1))).trim(), tab: String(question.tab || question.id || ('q' + (index + 1))).trim(), prompt: String(question.prompt || question.description || '').trim(), options: Array.isArray(question.options) ? question.options : [], multiSelect: question.multiSelect === true || question.allowMultiple === true, allowSkip: question.allowSkip !== false, allowFreeform: question.allowFreeform !== false, }; }); } function renderPiAskQuestion(body, question, index) { var section = el('div', { className: 'wolai-page-ai-pi-lab-ui-question', 'data-page-ai-pi-lab-ui-question': String(index), 'data-tab': question.tab }); section.appendChild(el('div', { className: 'wolai-page-ai-pi-lab-ui-question-title' }, question.header)); if (question.prompt) section.appendChild(el('div', { className: 'wolai-page-ai-pi-lab-ui-question-prompt' }, question.prompt)); question.options.forEach(function (option, optionIndex) { var label = typeof option === 'string' ? option : String((option && (option.label || option.title || option.value)) || ''); var description = typeof option === 'string' ? '' : String((option && option.description) || ''); var value = typeof option === 'string' ? option : String((option && (option.value || option.label || option.title)) || label); var row = el('button', { type: 'button', className: 'wolai-page-ai-pi-lab-ui-option', 'data-page-ai-pi-lab-ui-option': value, 'data-question-index': String(index), 'data-option-index': String(optionIndex), 'data-selected': 'false', }, [ el('span', { className: 'wolai-page-ai-pi-lab-ui-option-mark' }, question.multiSelect ? '□' : '○'), el('span', {}, [ el('span', {}, label || value), description ? el('small', {}, description) : null, ]), ]); section.appendChild(row); }); if (question.allowFreeform) { section.appendChild(el('textarea', { 'data-page-ai-pi-lab-ui-custom': String(index), placeholder: 'Type something...', })); } body.appendChild(section); } function collectPiAskResponse(panel, questions) { var answers = questions.map(function (question, index) { var section = panel.querySelector('[data-page-ai-pi-lab-ui-question="' + index + '"]'); var selected = Array.from(section ? section.querySelectorAll('[data-page-ai-pi-lab-ui-option][data-selected="true"]') : []) .map(function (node) { return node.getAttribute('data-page-ai-pi-lab-ui-option') || ''; }) .filter(Boolean); var customNode = section ? section.querySelector('[data-page-ai-pi-lab-ui-custom="' + index + '"]') : null; var custom = customNode ? customNode.value.trim() : ''; var answer = { tab: question.tab }; if (question.multiSelect) { if (selected.length) answer.answers = selected; if (custom) answer.custom = custom; if (!selected.length && !custom) answer.answers = []; } else if (selected[0]) { answer.answer = selected[0]; } else if (custom) { answer.custom = custom; } else { answer.skipped = true; } return answer; }); var noteNode = panel.querySelector('[data-page-ai-pi-lab-ui-note]'); var value = { cancelled: false, answers: answers }; if (noteNode && noteNode.value.trim()) value.message = noteNode.value.trim(); return { value: value }; } function showPiUiDialog(request) { closePiUiDialog(); request = request || {}; var method = String(request.method || '').trim(); var isAskUser = method === 'ask_user' || method === 'ask-user' || method === 'questionnaire' || Array.isArray(request.questions); var title = String(request.title || request.question || (method === 'confirm' ? '确认操作' : 'Pi 需要输入')).trim(); var message = String(request.message || request.context || '').trim(); var panel = el('div', { className: 'wolai-page-ai-pi-lab-ui-backdrop', 'data-page-ai-pi-lab-ui-dialog': method || 'dialog', }); var dialog = el('section', { className: 'wolai-page-ai-pi-lab-ui-dialog', role: 'dialog', 'aria-modal': 'false' }); dialog.appendChild(el('div', { className: 'wolai-page-ai-pi-lab-ui-dialog-header' }, [ el('div', {}, [ el('strong', {}, title), el('span', {}, message || (method === 'select' ? '请选择一个选项' : '')), ]), el('button', { type: 'button', className: 'wolai-page-ai-pi-lab-ui-collapse', 'data-page-ai-pi-lab-ui-collapse': 'true', title: '折叠/展开' }, '▾'), ])); var body = el('div', { className: 'wolai-page-ai-pi-lab-ui-dialog-body' }); var inputNode = null; var askQuestions = []; var finish = function (response) { closePiUiDialog(); sendPiUiResponse(request, response || {}); }; if (isAskUser) { askQuestions = normalizePiAskQuestions(request); askQuestions.forEach(function (question, index) { renderPiAskQuestion(body, question, index); }); body.appendChild(el('div', { className: 'wolai-page-ai-pi-lab-ui-note' }, [ el('label', {}, 'Note to assistant'), el('textarea', { 'data-page-ai-pi-lab-ui-note': 'true', placeholder: '可选补充说明' }), ])); } else if (method === 'select') { (Array.isArray(request.options) ? request.options : []).forEach(function (option) { var value = typeof option === 'string' ? option : String((option && (option.value || option.label)) || ''); var label = typeof option === 'string' ? option : String((option && (option.label || option.value)) || ''); body.appendChild(el('button', { type: 'button', className: 'wolai-page-ai-pi-lab-ui-option', 'data-page-ai-pi-lab-ui-option': value, textContent: label || value, })); }); } else if (method === 'input') { inputNode = el('input', { 'data-page-ai-pi-lab-ui-input': 'true', placeholder: request.placeholder || '', value: request.prefill || '', }); body.appendChild(inputNode); } else if (method === 'editor') { inputNode = el('textarea', { 'data-page-ai-pi-lab-ui-input': 'true', placeholder: request.placeholder || '', textContent: request.prefill || '', }); body.appendChild(inputNode); } if (!body.childNodes.length && method !== 'confirm') { body.appendChild(el('div', { textContent: message || 'Pi extension UI request' })); } dialog.appendChild(body); var actions = el('div', { className: 'wolai-page-ai-pi-lab-ui-dialog-actions' }); actions.appendChild(el('button', { type: 'button', 'data-page-ai-pi-lab-ui-cancel': 'true' }, '取消')); if (method !== 'select') actions.appendChild(el('button', { type: 'button', 'data-page-ai-pi-lab-ui-submit': 'true', 'data-primary': 'true' }, method === 'confirm' ? '确认' : '提交')); dialog.appendChild(actions); panel.appendChild(dialog); var composer = piLabPanelEl && piLabPanelEl.querySelector('.wolai-page-ai-pi-lab-composer'); if (composer) composer.insertBefore(panel, composer.firstChild); else document.body.appendChild(panel); activePiUiDialog = panel; var timeout = Number(request.timeout || request.timeoutMs || 0); var timer = timeout > 0 ? setTimeout(function () { finish({ cancelled: true }); }, timeout) : null; var finishOnce = function (response) { if (timer) clearTimeout(timer); finish(response); }; panel.addEventListener('click', function (event) { if (event.target && event.target.closest && event.target.closest('[data-page-ai-pi-lab-ui-collapse]')) { var collapsed = dialog.getAttribute('data-collapsed') === 'true'; dialog.setAttribute('data-collapsed', collapsed ? 'false' : 'true'); event.target.textContent = collapsed ? '▾' : '▸'; return; } var option = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-ui-option]') : null; if (option && isAskUser) { var questionIndex = option.getAttribute('data-question-index'); var question = askQuestions[Number(questionIndex)]; if (question && question.multiSelect) { var nextSelected = option.getAttribute('data-selected') !== 'true'; option.setAttribute('data-selected', nextSelected ? 'true' : 'false'); var mark = option.querySelector('.wolai-page-ai-pi-lab-ui-option-mark'); if (mark) mark.textContent = nextSelected ? '▣' : '□'; } else { panel.querySelectorAll('[data-question-index="' + questionIndex + '"][data-selected="true"]').forEach(function (node) { node.setAttribute('data-selected', 'false'); var oldMark = node.querySelector('.wolai-page-ai-pi-lab-ui-option-mark'); if (oldMark) oldMark.textContent = '○'; }); option.setAttribute('data-selected', 'true'); var selectedMark = option.querySelector('.wolai-page-ai-pi-lab-ui-option-mark'); if (selectedMark) selectedMark.textContent = '◉'; } return; } if (option) { finishOnce({ value: option.getAttribute('data-page-ai-pi-lab-ui-option') || option.textContent || '' }); return; } if (event.target && event.target.closest && event.target.closest('[data-page-ai-pi-lab-ui-cancel]')) { finishOnce(isAskUser ? { value: { cancelled: true, answers: [] }, cancelled: true } : { cancelled: true }); return; } if (event.target && event.target.closest && event.target.closest('[data-page-ai-pi-lab-ui-submit]')) { if (method === 'confirm') finishOnce({ confirmed: true }); else if (isAskUser) finishOnce(collectPiAskResponse(panel, askQuestions)); else finishOnce({ value: inputNode ? inputNode.value : '' }); } }); panel.addEventListener('keydown', function (event) { if (event.key === 'Escape') { event.preventDefault(); finishOnce(isAskUser ? { value: { cancelled: true, answers: [] }, cancelled: true } : { cancelled: true }); } if (event.key === 'Enter' && method === 'input') { event.preventDefault(); finishOnce({ value: inputNode ? inputNode.value : '' }); } }); setTimeout(function () { var focusTarget = inputNode || panel.querySelector('[data-page-ai-pi-lab-ui-option], [data-page-ai-pi-lab-ui-submit]'); if (focusTarget && typeof focusTarget.focus === 'function') focusTarget.focus(); }, 20); return panel; } function handlePiExtensionUiRequest(payload) { if (!payload || payload.type !== 'extension_ui_request') return false; if (payload.method === 'notify') { showPiToast(payload.message || payload.title || 'Pi notification', payload.notifyType || 'info'); return true; } if (payload.method === 'setWidget' || payload.method === 'set_widget') { renderPiCustomUiDialog(payload); return true; } if (payload.method === 'custom') { if (payload.close || payload.mode === 'close') { closePiCustomUiDialog(); sendPiUiResponse(payload, { value: { closed: true } }); } else { if (Array.isArray(payload.lines) || Array.isArray(payload.widgetLines) || payload.content) renderPiCustomUiDialog(payload); if (piCustomUiState.keyQueue.length > 0 || piCustomUiState.submitted || !piCustomUiState.panel) { respondToPiCustomUiPoll(payload); } else { piCustomUiState.pendingCustomRequest = payload; } } return true; } if (payload.method === 'confirm' || payload.method === 'select' || payload.method === 'input' || payload.method === 'editor' || payload.method === 'ask_user' || payload.method === 'ask-user' || payload.method === 'questionnaire' || Array.isArray(payload.questions)) { showPiUiDialog(payload); return true; } return false; } function updateBuiltinManaged() { 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.approvalRequired) label += receipt.approvalConfirmed ? ' · approved' : ' · approval required'; 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 collectPiArtifacts() { var artifacts = []; var seen = {}; (Array.isArray(piLabState.messages) ? piLabState.messages : []).forEach(function (msg) { if (!msg) return; (Array.isArray(msg.citations) ? msg.citations : []).forEach(function (cit, index) { var key = 'citation:' + (cit.url || cit.source || cit.title || index); if (seen[key]) return; seen[key] = true; artifacts.push({ kind: 'citation', title: cit.title || cit.source || 'Citation', summary: cit.source || cit.url || '', url: cit.url || '#', }); }); if (msg.diffSummary && Array.isArray(msg.diffSummary.files)) { msg.diffSummary.files.forEach(function (file) { if (!file) return; var key = 'diff:' + file; if (seen[key]) return; seen[key] = true; artifacts.push({ kind: 'diff', title: file, summary: '文件变更', toolEventId: msg.diffSummary.toolEventId || piLabState.currentToolEventId || '', sessionId: piLabState.sessionId || '', }); }); } (Array.isArray(msg.toolCalls) ? msg.toolCalls : []).forEach(function (tc) { if (!tc) return; var name = toolDisplayName({ toolName: tc.name, args: tc.args, details: tc.details }); if (String(name || '').indexOf('mcp:') !== 0) return; var key = 'mcp:' + (tc.id || name); if (seen[key]) return; seen[key] = true; artifacts.push({ kind: 'mcp', title: name, summary: extractResultText(tc.result || tc.partialResult || tc.details || tc.args).slice(0, 280), raw: tc.result || tc.details || tc.args || {}, }); }); }); (Array.isArray(piLabState.changedFiles) ? piLabState.changedFiles : []).forEach(function (file) { if (!file || seen['diff:' + file]) return; seen['diff:' + file] = true; artifacts.push({ kind: 'diff', title: file, summary: '文件变更' }); }); return artifacts; } function renderArtifactItem(artifact) { var item = el('div', { className: 'wolai-page-ai-pi-lab-artifact', 'data-page-ai-pi-lab-artifact': artifact.kind || 'artifact', 'data-kind': artifact.kind || 'artifact', }); item.appendChild(el('strong', {}, artifact.title || 'Artifact')); if (artifact.summary) item.appendChild(el('span', {}, artifact.summary)); var actions = el('div', { className: 'wolai-page-ai-pi-lab-artifact-actions' }); var markdown = '- ' + (artifact.title || 'Artifact') + (artifact.summary ? ': ' + artifact.summary : '') + (artifact.url ? ' (' + artifact.url + ')' : ''); if (artifact.kind === 'citation') { actions.appendChild(el('a', { href: artifact.url || '#', target: '_blank', rel: 'noopener', 'data-page-ai-pi-lab-artifact-open': artifact.url || '#', 'data-page-ai-pi-lab-artifact-title': artifact.title || 'Citation', }, '打开引用')); } if (artifact.kind === 'diff' && artifact.toolEventId) { actions.appendChild(el('button', { type: 'button', 'data-page-ai-pi-lab-artifact-view-diff': artifact.toolEventId, 'data-page-ai-pi-lab-artifact-diff-sid': artifact.sessionId || '', }, '查看 diff')); } actions.appendChild(el('button', { type: 'button', 'data-page-ai-pi-lab-artifact-copy-markdown': markdown, }, '复制 MD')); if (artifact.raw) { actions.appendChild(el('button', { type: 'button', 'data-page-ai-pi-lab-artifact-copy': compactJson(artifact.raw), }, '复制 raw')); } item.appendChild(actions); return item; } function updateArtifactRail() { if (!piLabPanelEl) return; var container = piLabPanelEl.querySelector('[data-page-ai-pi-lab-artifacts]'); var count = piLabPanelEl.querySelector('[data-page-ai-pi-lab-artifact-count]'); var body = piLabPanelEl.querySelector('[data-page-ai-pi-lab-body]'); if (!container) return; var artifacts = collectPiArtifacts(); if (count) count.textContent = String(artifacts.length); if (!artifacts.length && body) body.setAttribute('data-rail-open', 'false'); container.innerHTML = ''; if (!artifacts.length) { container.appendChild(el('div', { className: 'wolai-page-ai-pi-lab-receipt' }, 'No artifact yet')); return; } artifacts.slice(-12).reverse().forEach(function (artifact) { container.appendChild(renderArtifactItem(artifact)); }); } function setState(newState) { piLabState.status = newState; if (piRunViewModel) piRunViewModel.runtimeStatus = newState; updateConfig(); maybeApplyPendingPermissionMode(); maybeApplyPendingModelConfig(); } function piLabDraftKey() { var wid = piLabState.workspaceId || currentWorkspaceId() || 'default'; var root = piLabState.rootUri || currentRootUri() || ''; var page = piLabState.pagePath || ''; var sid = piLabState.sessionId || piLabState.viewingHistorySessionId || 'new'; return 'pi-lab:' + wid + ':' + root + ':' + page + ':' + sid; } function savePiLabDraft() { if (!piLabPanelEl) return; var input = piLabPanelEl.querySelector('[data-page-ai-pi-lab-input]'); if (!input) return; var text = input.value || ''; if (text.trim()) { try { localStorage.setItem(piLabDraftKey(), text); } catch (e) {} } else { clearPiLabDraft(); } } function restorePiLabDraft() { if (!piLabPanelEl) return; var input = piLabPanelEl.querySelector('[data-page-ai-pi-lab-input]'); if (!input) return; try { var saved = localStorage.getItem(piLabDraftKey()); if (saved) { input.value = saved; updateButtons(); } } catch (e) {} } function clearPiLabDraft() { try { localStorage.removeItem(piLabDraftKey()); } catch (e) {} } function syncRuntimeState() { if (!piLabState.sessionId && !piLabState.viewingHistorySessionId) return; var sid = piLabState.sessionId || piLabState.viewingHistorySessionId; fetch(API.STATE, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId: sid }), }).then(function (r) { return r.json(); }).then(function (data) { if (!data) return; if (data.queuedMessages !== undefined) piLabState.queuedMessages = data.queuedMessages; if (data.pendingMessageCount !== undefined) piLabState.pendingMessageCount = data.pendingMessageCount; if (data.isCompacting !== undefined) piLabState.isCompacting = data.isCompacting; if (data.contextUsage !== undefined) piLabState.contextUsage = data.contextUsage; if (data.modelProvider) piLabState.modelProvider = data.modelProvider; if (data.modelId) piLabState.modelId = data.modelId; if (data.thinkingLevel) piLabState.thinkingLevel = normalizeThinkingLevel(data.thinkingLevel); updateQueueDisplay(); updateConfig(); }).catch(function () {}); } function callPiRpcCommand(type, params, timeoutMs) { if (!piLabState.sessionId) return Promise.reject(new Error('Pi 会话尚未就绪')); return fetch(API.RPC_COMMAND, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId: piLabState.sessionId, type: type, params: params || {}, timeoutMs: timeoutMs || 10000, }), }).then(function (r) { return responseJsonOrError(r, 'Pi RPC command failed'); }); } function readFileAsPiImage(file) { return new Promise(function (resolve, reject) { if (!file || !/^image\//i.test(file.type || '')) { reject(new Error('只支持图片附件;普通文件请在输入中使用 @文件路径')); return; } var reader = new FileReader(); reader.onload = function () { var dataUrl = String(reader.result || ''); var comma = dataUrl.indexOf(','); if (comma < 0) { reject(new Error('图片读取失败')); return; } resolve({ type: 'image', source: { type: 'base64', mediaType: file.type || 'image/png', data: dataUrl.slice(comma + 1), }, }); }; reader.onerror = function () { reject(new Error('图片读取失败')); }; reader.readAsDataURL(file); }); } function pickPiPromptImages(useCamera) { var input = document.createElement('input'); input.type = 'file'; input.accept = 'image/*'; input.multiple = !useCamera; if (useCamera) input.setAttribute('capture', 'environment'); input.addEventListener('change', function () { var files = Array.prototype.slice.call(input.files || []); if (!files.length) return; Promise.all(files.map(readFileAsPiImage)).then(function (images) { piLabState.pendingImages = (piLabState.pendingImages || []).concat(images); showPiToast('已添加 ' + images.length + ' 张图片', 'success'); updateButtons(); }).catch(function (error) { showPiToast(error && error.message ? error.message : '附件读取失败', 'warning'); }); }); input.click(); } function responseJsonOrError(response, fallbackMessage) { return response.json().catch(function () { return {}; }).then(function (payload) { if (!response.ok) { var message = (payload && (payload.message || payload.error)) || (fallbackMessage + ': ' + response.status); var error = new Error(message); error.payload = payload; throw error; } return payload; }); } function runPiDiagnostic(command) { var input = piLabPanelEl && piLabPanelEl.querySelector('[data-page-ai-pi-lab-diagnostic-query]'); var keyword = input ? String(input.value || '').trim() : ''; var params = {}; if (command === 'doctor') params.format = 'json'; if (command === 'context-preview') { params.format = 'json'; params.query = [keyword || 'MNote Pi Rust diagnostics']; } if (command === 'search') { if (!keyword) { showPiToast('请输入 search keyword', 'warning'); return Promise.resolve(); } params.query = keyword; params.limit = 10; } if (command === 'info') { if (!keyword) { showPiToast('请输入 info name/id', 'warning'); return Promise.resolve(); } params.name = keyword; } updateDiagnostics('Pi diagnostics: ' + command); setDiagnosticOutput('Running ' + command + '...'); return fetch(API.DIAGNOSTICS, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ command: command, params: params, rootUri: piLabState.session && piLabState.session.rootUri || undefined, workspaceId: piLabState.session && piLabState.session.workspaceId || undefined, }), }).then(function (response) { return responseJsonOrError(response, 'Diagnostics failed'); }).then(function (payload) { setDiagnosticOutput(payload); updateDiagnostics('Pi diagnostics done: ' + command); return payload; }).catch(function (error) { setDiagnosticOutput({ ok: false, command: command, error: error.message || String(error), payload: error.payload || null }); updateDiagnostics('Pi diagnostics failed: ' + (error.message || String(error))); showPiToast('诊断失败', 'error'); }); } 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] || {}; var effectiveData = results[1] || null; piLabState.enabled = !!data.enabled; piLabState.sessionId = data.sessionId || piLabState.sessionId || null; piLabState.providerSessionId = data.providerSessionId || piLabState.providerSessionId || null; // S1: prefer effective AI settings as management-surface truth; status is // fallback only when effective load failed. Do not let env-stale status // overwrite a successfully loaded effective defaultModel. if (effectiveData) { applyEffectiveConfig(effectiveData); } else { if (data.defaultModelProvider) piLabState.defaultModelProvider = data.defaultModelProvider; if (data.defaultModelId) piLabState.defaultModelId = data.defaultModelId; } if (data.defaultThinkingLevel && !piLabState.defaultThinkingLevel) { piLabState.defaultThinkingLevel = normalizeThinkingLevel(data.defaultThinkingLevel); } // Active session model wins; otherwise keep effective defaults already applied. piLabState.modelProvider = (data.session && data.session.modelProvider) || piLabState.modelProvider || piLabState.defaultModelProvider || null; piLabState.modelId = (data.session && data.session.modelId) || piLabState.modelId || piLabState.defaultModelId || null; var sessionThinkingLevel = data.session && data.session.thinkingLevel; if (sessionThinkingLevel) { piLabState.thinkingLevel = normalizeThinkingLevel(sessionThinkingLevel); } else if (!piLabState.thinkingLevel) { piLabState.thinkingLevel = piLabState.defaultThinkingLevel; } piLabState.permissionMode = normalizePermissionMode((data.session && (data.session.permissionMode || (data.session.runtimePolicySnapshot && data.session.runtimePolicySnapshot.permissionMode))) || data.permissionMode || piLabState.permissionMode); 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.runtimeImplementation = data.runtimeImplementation || data.runtime_implementation || piLabState.runtimeImplementation || null; piLabState.runtimeBinary = data.runtimeBinary || data.runtime_binary || piLabState.runtimeBinary || null; piLabState.runtimeAvailable = data.runtimeAvailable !== false; piLabState.runtimeInstallHint = data.runtimeInstallHint || data.runtime_install_hint || ''; piLabState.runtimeError = data.runtimeError || (data.session && data.session.runtimeError) || ''; piLabState.runtimePid = data.pid || (data.session && data.session.runtimePid) || piLabState.runtimePid || null; piLabState.advancedRuntime = data.advancedRuntime || (data.session && data.session.advancedRuntime) || (data.session && data.session.runtimePolicySnapshot && data.session.runtimePolicySnapshot.piRuntime) || piLabState.advancedRuntime || {}; piLabState.warmupRunning = data.warmupRunning === true; piLabState.warmupSessionId = data.warmupSessionId || null; piLabState.managedBuiltinTools = data.managedPiBuiltinTools || data.managedBuiltinTools || piLabState.managedBuiltinTools || []; piLabState.piExtensionSources = data.piExtensionSources || (data.session && data.session.piExtensionSources) || piLabState.piExtensionSources || []; piLabState.piExtensionToolNames = data.piExtensionToolNames || piLabState.piExtensionToolNames || []; 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); } updateBuiltinManaged(); updateModelLabel(); updateThinkingControl(); updatePermissionControl(); updateReceiptDisplay(); updateDiagnostics(''); updateMessages(); return data; }) .catch(function (error) { piLabState.enabled = false; setState(STATE_ERROR); updateDiagnostics(error && error.message ? error.message : 'status failed'); }); } function startRuntime() { exitHistoryReplayMode(); if (piLabStartPromise) return piLabStartPromise; if (piLabState.status === STATE_STREAMING) return Promise.resolve({ skipped: true }); if (piLabState.status === STATE_STARTED && piLabState.sessionId) return Promise.resolve({ ok: true, session: piLabState.session }); piLabStartPromise = Promise.resolve() .then(function () { return piLabState.enabled ? null : checkStatus(); }) .then(function () { if (!piLabState.enabled) throw new Error('Pi Rust 服务未启用'); if (piLabState.runtimeAvailable === false) throw new Error(piLabState.runtimeInstallHint || 'Pi Rust runtime 不可用'); var context = applyCurrentContextToState(); applyModelControls(); ensurePiToolCapableModel(); setState(STATE_STARTING); updateDiagnostics(piLabState.warmupRunning ? 'Binding current page Pi session' : 'Starting Pi Rust runtime'); return fetch(API.START, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId: piLabState.sessionId || undefined, rootUri: context.rootUri || (piLabState.session && piLabState.session.rootUri) || undefined, workspaceId: context.workspaceId || (piLabState.session && piLabState.session.workspaceId) || undefined, pagePath: context.pagePath || (piLabState.session && piLabState.session.pagePath) || undefined, pageTitle: context.pageTitle || (piLabState.session && piLabState.session.pageTitle) || undefined, modelProvider: piLabState.modelProvider || piLabState.defaultModelProvider, modelId: piLabState.modelId || piLabState.defaultModelId, thinkingLevel: normalizeThinkingLevel(piLabState.thinkingLevel || piLabState.defaultThinkingLevel), permissionMode: normalizePermissionMode(piLabState.permissionMode), }), }).then(function (response) { return responseJsonOrError(response, 'Start failed'); }); }) .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.thinkingLevel = normalizeThinkingLevel(session.thinkingLevel || data.thinkingLevel || piLabState.thinkingLevel); piLabState.permissionMode = normalizePermissionMode(session.permissionMode || data.permissionMode || (session.runtimePolicySnapshot && session.runtimePolicySnapshot.permissionMode) || piLabState.permissionMode); piLabState.session = session || piLabState.session || null; piLabState.runtimeMode = session.runtimeMode || data.runtimeMode || piLabState.runtimeMode || null; piLabState.runtimeImplementation = data.runtimeImplementation || data.runtime_implementation || piLabState.runtimeImplementation || null; piLabState.runtimeBinary = data.runtimeBinary || data.runtime_binary || piLabState.runtimeBinary || null; piLabState.runtimeAvailable = data.runtimeAvailable !== false; piLabState.runtimeInstallHint = data.runtimeInstallHint || data.runtime_install_hint || ''; piLabState.runtimeError = session.runtimeError || data.runtimeError || ''; piLabState.runtimePid = session.runtimePid || data.pid || piLabState.runtimePid || null; piLabState.advancedRuntime = data.advancedRuntime || session.advancedRuntime || (session.runtimePolicySnapshot && session.runtimePolicySnapshot.piRuntime) || piLabState.advancedRuntime || {}; piLabState.managedBuiltinTools = data.managedPiBuiltinTools || data.managedBuiltinTools || piLabState.managedBuiltinTools || []; piLabState.piExtensionSources = data.piExtensionSources || piLabState.piExtensionSources || []; piLabState.piExtensionToolNames = data.piExtensionToolNames || piLabState.piExtensionToolNames || []; connectEventSource(); setState(STATE_STARTED); updateDiagnostics('Pi runtime ready'); updateMessages(); return data; }) .catch(function (err) { setState(STATE_ERROR); updateDiagnostics('Start error: ' + err.message); throw err; }) .finally(function () { piLabStartPromise = null; }); return piLabStartPromise; } function handleToolCallEventPayload(payload) { payload = payload || {}; // Fail-closed: only explicit allowed===true triggers side effects / changedFiles. var allowed = payload.allowed === true; applyToolCallSideEffects({ ok: allowed, toolName: payload.toolName, result: { rootUri: payload.rootUri, relativePath: payload.relativePath, diffSummary: payload.diffSummary, }, }); piLabState.receipts.push({ toolName: payload.toolName, allowed: allowed, denyReason: payload.denyReason || null, diffSummary: payload.diffSummary || null, citationCount: payload.citationCount || 0, beforeFileVersion: payload.beforeFileVersion || null, afterFileVersion: payload.afterFileVersion || null, approvalRequired: payload.approvalRequired === true, approvalConfirmed: payload.approvalConfirmed === true, }); upsertToolCall({ toolCallId: payload.toolCallId || payload.id || ('receipt:' + (payload.toolName || 'tool') + ':' + piLabState.receipts.length), toolName: payload.toolName || 'tool', args: payload.params || payload.args || {}, }, allowed ? 'done' : 'denied', { result: { allowed: allowed, denyReason: payload.denyReason || null, diffSummary: payload.diffSummary || null, citationCount: payload.citationCount || 0, }, approvalRequired: payload.approvalRequired === true, approvalConfirmed: payload.approvalConfirmed === true, toolPolicy: payload.toolPolicy || '', }); if (allowed && payload.normalizedFilePath && payload.diffSummary) { piLabState.changedFiles.push(payload.normalizedFilePath); } updateReceiptDisplay(); updateContextStrip(); } function fetchQueueConfig() { if (!piLabState.sessionId) return Promise.resolve(null); return fetch(API.QUEUE_CONFIG, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId: piLabState.sessionId }), }).then(function (r) { return r.json(); }).then(function (data) { if (data && data.steering !== undefined) piLabState.pendingQueue.steering = data.steering; if (data && data.followUp !== undefined) piLabState.pendingQueue.followUp = data.followUp; updateQueueDisplay(); return data; }).catch(function () { return null; }); } function handleAtMention(inputEl) { if (!inputEl) return; var text = inputEl.value || ''; var atIndex = text.lastIndexOf('@'); if (atIndex < 0 || (atIndex > 0 && text[atIndex - 1] !== ' ' && text[atIndex - 1] !== '\n' && text[atIndex - 1] !== '\t')) { var existingMenu = document.querySelector('.wolai-page-ai-pi-lab-mention-menu'); if (existingMenu) existingMenu.remove(); return; } var query = text.slice(atIndex + 1).split(/\s/)[0] || ''; var candidates = []; if (piLabState.allowedRootsSummary && piLabState.allowedRootsSummary !== 'pending') { candidates.push({ label: '@' + piLabState.allowedRootsSummary, value: piLabState.allowedRootsSummary, type: 'root' }); } var pagePath = piLabState.pagePath || currentFolderPathFromPagePath(currentPageContext().pagePath) || ''; if (pagePath) { var parts = pagePath.split('/'); var folderName = parts[parts.length - 1] || pagePath; candidates.push({ label: '@' + folderName, value: folderName, type: 'folder' }); } if (query) { candidates = candidates.filter(function (c) { return c.label.toLowerCase().indexOf(query.toLowerCase()) >= 0; }); } var existingMenu = document.querySelector('.wolai-page-ai-pi-lab-mention-menu'); if (existingMenu) existingMenu.remove(); if (!candidates.length) return; var menu = document.createElement('div'); menu.className = 'wolai-page-ai-pi-lab-mention-menu'; menu.style.cssText = 'position:fixed;bottom:100px;left:50%;transform:translateX(-50%);background:#fff;border:1px solid #e0e0dc;border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,0.1);padding:4px 0;z-index:10000;max-height:160px;overflow-y:auto;min-width:200px;font:13px/1.5 system-ui'; candidates.forEach(function (cand) { var item = document.createElement('button'); item.type = 'button'; item.style.cssText = 'display:block;width:100%;text-align:left;padding:6px 14px;border:none;background:none;cursor:pointer;color:#333'; item.textContent = cand.label; item.addEventListener('click', function () { var before = text.slice(0, atIndex); var rest = text.slice(atIndex + 1 + query.length); inputEl.value = before + '@' + cand.value + ' ' + rest; inputEl.focus(); menu.remove(); updateButtons(); savePiLabDraft(); }); item.addEventListener('mouseenter', function () { item.style.background = '#f0f0ed'; }); item.addEventListener('mouseleave', function () { item.style.background = 'none'; }); menu.appendChild(item); }); document.body.appendChild(menu); } 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.managedBuiltinTools = payload.managedBuiltinTools || payload.managedPiBuiltinTools || piLabState.managedBuiltinTools; piLabState.advancedRuntime = payload.advancedRuntime || piLabState.advancedRuntime || {}; piLabState.piExtensionSources = payload.piExtensionSources || piLabState.piExtensionSources || []; piLabState.piExtensionToolNames = payload.piExtensionToolNames || piLabState.piExtensionToolNames || []; 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; handleToolCallEventPayload(payload); } catch (_) {} }); es.addEventListener('runtime_aborted', function (e) { var payload = { reason: '已中止', stopReason: 'aborted' }; try { var data = JSON.parse(e.data); payload = Object.assign(payload, data.payload || data || {}); } catch (_) {} if (piLabEventSource === es) { es.close(); piLabEventSource = null; } // S4: optimistic abort already applied UI; ignore duplicate SSE abort. if (piLabState.status === STATE_ABORTED) return; applyPiRunEvent({ type: 'assistant_abort', payload: payload }); setState(STATE_ABORTED); updateMessages(); }); es.addEventListener('runtime_stdout_closed', function (e) { var payload = { message: 'Pi Rust runtime stdout closed' }; try { var data = JSON.parse(e.data); payload = Object.assign(payload, data.payload || data || {}); } catch (_) {} piLabState.runtimePid = null; if (payload.duringTurn || piLabState.status === STATE_STREAMING) { applyPiRunEvent({ type: 'assistant_error', payload: { error: payload.message || 'Pi Rust runtime 已退出,未返回回复' }, }); setState(STATE_ERROR); updateMessages(); } else { setState(STATE_IDLE); } updateDiagnostics(payload.message || 'runtime_stdout_closed'); }); es.addEventListener('error', function () { if (piLabEventSource && piLabEventSource.readyState === EventSource.CLOSED) updateDiagnostics('SSE connection closed'); }); piLabEventSource = es; } function ensureStreamingAssistant() { if (!streamingAssistantMsg) applyPiRunEvent({ type: 'assistant_begin' }); 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 currentTurnToolCalls() { var current = streamingAssistantMsg || (piRunViewModel && piRunViewModel.currentAssistant); return current && Array.isArray(current.toolCalls) ? current.toolCalls : []; } function finishEmptyAssistantTurn(message) { if (!message || message.role !== 'assistant') return false; var content = Array.isArray(message.content) ? message.content : []; if (content.some(function (part) { return part && (part.type === 'text' || part.type === 'toolCall'); })) return false; var toolCalls = currentTurnToolCalls(); if (toolCalls.length > 0) { var failed = toolCalls.filter(function (tool) { return tool && (tool.status === 'error' || tool.isError === true); }); var labels = toolCalls.map(function (tool) { var name = String((tool && (tool.toolName || tool.name)) || 'tool'); return name + (tool && (tool.status === 'error' || tool.isError === true) ? '(失败)' : '(完成)'); }); var summary = (failed.length > 0 ? '工具检查已结束,其中有失败项:' : '工具检查已完成:') + labels.join('、') + '。'; applyPiRunEvent({ type: 'assistant_done', text: summary }); setState(STATE_STARTED); updateMessages(); updateDiagnostics('Pi assistant completed with tool-only result'); return true; } applyPiRunEvent({ type: 'assistant_error', payload: { error: 'Pi runtime 返回了空回复;请新建会话后重试。', }, }); setState(STATE_ERROR); updateMessages(); updateDiagnostics('Pi assistant turn ended without text or tool calls'); return true; } function handlePiMessageEvent(payload) { var message = payload && payload.message; if (!message || !message.role) return false; if (message.role === 'assistant') { var content = Array.isArray(message.content) ? message.content : []; var toolParts = content.filter(function (part) { return part && part.type === 'toolCall'; }); toolParts.forEach(function (part) { upsertToolCall({ toolCallId: part.id || part.toolCallId || '', toolName: part.name || part.toolName || 'tool', args: part.arguments || part.args || {}, }, 'running'); }); var visibleText = extractVisibleAssistantText(message); if (visibleText) { applyPiRunEvent({ type: 'assistant_done', text: visibleText }); setState(STATE_STARTED); updateMessages(); } if (toolParts.length > 0 || !!visibleText) return true; updateDiagnostics('Pi assistant message is empty; waiting for agent_end'); return true; } if (message.role === 'toolResult') { var toolResultPayload = { toolCallId: message.toolCallId || message.tool_call_id || '', toolName: message.toolName || message.tool_name || 'tool', details: message.details || {}, }; upsertToolCall(toolResultPayload, message.isError ? 'error' : 'done', { result: { content: message.content || [], details: message.details || {}, }, isError: message.isError === true, }); if (/knowledge_rag|knowledge\.rag/i.test(String(toolResultPayload.toolName || ''))) { appendKnowledgeRagCitations(message.details || {}); updateMessages(); } return true; } return false; } function handlePiRpcEvent(payload) { if (!payload) return; var eventType = payload.type; var msgData = payload.assistantMessageEvent || payload; var msgDataType = msgData && msgData.type; if (handlePiExtensionUiRequest(payload)) return; if (msgDataType === 'thinking_start' || msgDataType === 'thinking_delta' || msgDataType === 'thinking_end') { updateDiagnostics('Pi thinking event hidden from visible reply'); return; } if (eventType === 'queue_update') { applyPiRunEvent({ type: 'queue_update', payload: payload }); updateQueueDisplay(); return; } if (eventType === 'message' && handlePiMessageEvent(payload)) return; if (msgDataType === 'text_start') { setState(STATE_STREAMING); applyPiRunEvent({ type: 'assistant_begin' }); updateMessages(); return; } if (msgDataType === 'text_delta') { applyPiRunEvent({ type: 'assistant_delta', payload: msgData }); updateMessages(); } else if (msgDataType === 'text_end') { applyPiRunEvent({ type: 'assistant_text_end', payload: msgData }); updateMessages(); } else if (eventType === 'message_end' && payload.message && payload.message.role === 'toolResult') { handlePiMessageEvent(payload); } else if (eventType === 'message_end' && payload.message && payload.message.role === 'assistant') { var visibleText = extractVisibleAssistantText(payload.message); if (!visibleText) { updateDiagnostics('Pi assistant message_end has no text; waiting for agent_end'); return; } applyPiRunEvent({ type: 'assistant_done', text: visibleText }); setState(STATE_STARTED); updateMessages(); } else if (eventType === 'agent_end') { var finalMessages = Array.isArray(payload.messages) ? payload.messages : []; finalMessages.forEach(function (message) { if ( message && message.role === 'toolResult' && /knowledge_rag|knowledge\.rag/i.test(String(message.toolName || message.tool_name || '')) ) { appendKnowledgeRagCitations(message.details || {}); } }); 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; } if (!finalText && finalAssistant && finishEmptyAssistantTurn(finalAssistant)) return; applyPiRunEvent({ type: 'assistant_done', text: finalText }); setState(STATE_STARTED); updateMessages(); } else if (eventType === 'tool_execution_start' || eventType === 'tool_call_start' || msgDataType === 'tool_call_start' || msgDataType === 'toolcall_start') { var startToolPayload = eventType === 'tool_execution_start' || eventType === 'tool_call_start' ? payload : assistantToolCallPayload(msgData); if (startToolPayload) upsertToolCall(startToolPayload, 'running'); } else if (eventType === 'tool_execution_update') { upsertToolCall(payload, 'running', { partialResult: payload.partialResult || payload.result || null }); } else if (eventType === 'tool_execution_end' || eventType === 'tool_call_end' || msgDataType === 'tool_call_end' || msgDataType === 'toolcall_end') { var endToolPayload = eventType === 'tool_execution_end' || eventType === 'tool_call_end' ? payload : assistantToolCallPayload(msgData); if (endToolPayload) { upsertToolCall(endToolPayload, payload.isError ? 'error' : 'done', { result: payload.result || msgData.result || endToolPayload.result || {}, isError: payload.isError === true, }); } } else if (msgDataType === 'toolcall_delta') { var deltaToolPayload = assistantToolCallPayload(msgData); if (deltaToolPayload) upsertToolCall(deltaToolPayload, 'running', { args: deltaToolPayload.args || {} }); } else if (eventType === 'response' && payload.command === 'abort') { // S4: ignore duplicate abort after optimistic UI apply. if (piLabState.status !== STATE_ABORTED) { applyPiRunEvent({ type: 'assistant_abort', payload: payload }); setState(STATE_ABORTED); updateMessages(); } } else if (eventType === 'citation' || msgDataType === 'citation') { applyPiRunEvent({ type: 'citation', payload: { source: payload.source || msgData.source || '', title: payload.title || msgData.title || '', url: payload.url || msgData.url || '#' } }); updateMessages(); } else if (eventType === 'diff' || msgDataType === 'diff') { var diffFiles = payload.files || msgData.files || []; var diffToolEventId = payload.toolEventId || msgData.toolEventId || ''; applyPiRunEvent({ type: 'diff', payload: { files: diffFiles, toolEventId: diffToolEventId } }); diffFiles.forEach(function (file) { if (file) piLabState.changedFiles.push(file); }); if (diffToolEventId) { piLabState.currentToolEventId = diffToolEventId; } updateMessages(); } else if (eventType === 'done' || msgDataType === 'done') { applyPiRunEvent({ type: 'assistant_done', payload: { text: msgData.text, toolCalls: msgData.toolCalls, citations: msgData.citations, diffSummary: msgData.diffSummary, }, }); setState(STATE_STARTED); updateMessages(); } else if (eventType === 'error' || msgDataType === 'error') { applyPiRunEvent({ type: 'assistant_error', payload: { error: payload.error || msgData.message || 'Pi runtime error' } }); setState(STATE_STARTED); updateMessages(); } } function pageAiBlockingDirtyStateLocal(dirtyState) { var state = String(dirtyState || '').trim(); var normalized = state.toLowerCase(); if (normalized === 'dirty') return 'Dirty'; if (normalized === 'stale') return 'Stale'; if (normalized === 'deleted') return 'Deleted'; if (normalized === 'externalmodified' || normalized === 'external-change-conflict' || normalized === 'hasexternalconflict') return 'ExternalModified'; return ''; } function buildPiLabAgentTargetPackage(context) { var active = activeEditorSnapshot() || {}; var activeWorkspacePath = active.workspacePath && typeof active.workspacePath === 'object' ? active.workspacePath : {}; var relativePath = normalizeSlashes(context && context.pagePath || activeWorkspacePath.relativePath || active.path || ''); var rootUri = String(context && context.rootUri || activeWorkspacePath.rootUri || active.rootUri || '').trim(); var workspaceId = String(context && context.workspaceId || activeWorkspacePath.workspaceId || active.workspaceId || '').trim(); var documentId = String(context && context.pageId || active.documentId || activeWorkspacePath.documentId || '').trim(); var resourceKind = String(activeWorkspacePath.resourceKind || active.resourceKind || active.kind || 'markdown_page').trim() || 'markdown_page'; var objectIdentity = String(active.objectIdentity || activeWorkspacePath.objectIdentity || documentId || relativePath || '').trim(); var onlyofficeSessionId = String(active.onlyofficeSessionId || active.bridgeSessionId || '').trim(); var dirtyState = String(active.dirtyState || active.dirtyGuard || '').trim(); var primaryTargetId = objectIdentity || documentId || relativePath; var workspacePath = { schema: 'mnote.workspace_path.v1', workspaceId: workspaceId, sourceKind: 'local_folder', rootUri: rootUri, relativePath: relativePath, documentId: documentId, objectIdentity: objectIdentity, resourceKind: resourceKind, }; var targetEntry = { targetId: primaryTargetId, objectIdentity: objectIdentity, documentId: documentId, workspaceId: workspaceId, sourceKind: 'local_folder', rootUri: rootUri, relativePath: relativePath, resourceKind: resourceKind, dirtyState: dirtyState, onlyofficeSessionId: onlyofficeSessionId, bridgeSessionId: onlyofficeSessionId, paneRole: active.paneRole || 'primary', title: context && context.pageTitle || active.title || '', policy: { permission: relativePath ? 'read_write' : 'read', writeRequiresCleanBuffer: true, conflictPolicy: 'fail_on_dirty_or_stale' } }; return { schema: 'mnote.agent_target_package.v1', source: 'pi_lab_send_target_snapshot', frozenAt: Date.now(), primaryTargetId: primaryTargetId, onlyofficeSessionId: onlyofficeSessionId, bridgeSessionId: onlyofficeSessionId, workspaceId: workspaceId, sourceKind: 'local_folder', rootUri: rootUri, documentId: documentId, objectIdentity: objectIdentity, resourceKind: resourceKind, workspacePath: workspacePath, currentFile: relativePath ? { rootUri: rootUri, relativePath: relativePath, documentId: documentId, objectIdentity: objectIdentity, resourceKind: resourceKind } : null, allowedFiles: relativePath ? [relativePath] : [], targets: [targetEntry], policy: { writeRequiresExplicitTarget: true, allowedFilesSource: 'selected_page_ai_target', conflictPolicy: 'fail_on_dirty_or_stale' } }; } function fetchPiLabTargetBufferState(context, targetPackage) { var workspacePath = targetPackage && targetPackage.workspacePath || {}; var documentId = String(context && context.pageId || workspacePath.documentId || '').trim(); var rootUri = String(context && context.rootUri || workspacePath.rootUri || '').trim(); if (!documentId || !rootUri) return Promise.resolve(null); var relativePath = String(workspacePath.relativePath || context && context.pagePath || '').trim(); var url = new URL('/api/documents/buffer-state', window.location.origin); url.searchParams.set('documentId', documentId); url.searchParams.set('sourceKind', 'local_folder'); url.searchParams.set('rootUri', rootUri); var workspaceId = String(context && context.workspaceId || workspacePath.workspaceId || '').trim(); if (workspaceId) url.searchParams.set('workspaceId', workspaceId); if (relativePath) url.searchParams.set('relativePath', relativePath); return fetch(url.toString(), { cache: 'no-store', credentials: 'same-origin', headers: { accept: 'application/json' } }) .then(function (response) { return response.json().catch(function () { return null; }).then(function (payload) { if (!response.ok || !payload || payload.ok !== true) return null; return payload.result || null; }); }) .catch(function () { return null; }); } function assertPiLabTargetWritable(context, targetPackage) { var active = activeEditorSnapshot() || {}; var snapshotDirty = pageAiBlockingDirtyStateLocal(active.dirtyState || active.dirtyGuard); return fetchPiLabTargetBufferState(context, targetPackage).then(function (bufferState) { var bufferDirty = pageAiBlockingDirtyStateLocal(bufferState && bufferState.dirtyState); var blockedState = bufferDirty || snapshotDirty; if (!blockedState) return bufferState; var error = new Error('目标文档存在未保存或外部变更状态(' + blockedState + '),请先保存、解决冲突或刷新后再让 AI 写入。'); error.code = 'page_ai_target_buffer_not_writable'; error.documentId = String(context && context.pageId || '').trim(); error.dirtyState = blockedState; throw error; }); } function sendPrompt(text, options) { var prompt = String(text || '').trim(); if (!prompt) return Promise.resolve(); var streamingBehavior = options && options.streamingBehavior; var midStream = piLabState.status === STATE_STREAMING && streamingAssistantMsg; var context = applyCurrentContextToState(); var contextSelection = selectedContextPayload(context); var refs = contextSelection.refs || []; var selectedFolderPath = currentFolderPathFromPagePath(context.pagePath); var includeAnyContext = refs.length > 0; var includePageContext = refs.indexOf('current_page') >= 0 || refs.indexOf('selection') >= 0 || refs.indexOf('lightrag') >= 0; var includeFolderContext = refs.indexOf('folder') >= 0; var targetPackage = buildPiLabAgentTargetPackage(context); // S7: block send when open target buffer is dirty/stale (fail_on_dirty_or_stale). var writableGate = (context && context.pagePath) ? assertPiLabTargetWritable(context, targetPackage) : Promise.resolve(null); return writableGate.then(function () { applyPiRunEvent({ type: 'user_prompt', text: prompt, meta: midStream ? (streamingBehavior === 'followUp' ? 'follow-up queued' : 'steer') : '' }); if (!midStream) applyPiRunEvent({ type: 'assistant_begin' }); setState(STATE_STREAMING); updateMessages(); var body = { sessionId: piLabState.sessionId, message: prompt, rootUri: includeAnyContext ? (context.rootUri || undefined) : undefined, workspaceId: includeAnyContext ? (context.workspaceId || undefined) : undefined, pagePath: includePageContext ? (context.pagePath || undefined) : undefined, pageTitle: includePageContext ? (context.pageTitle || undefined) : undefined, folderPath: includeFolderContext && selectedFolderPath !== null ? selectedFolderPath : undefined, contextRefs: contextSelection.refs, selectedContext: contextSelection, targetPackage: targetPackage, }; if (streamingBehavior) body.streamingBehavior = streamingBehavior; if (piLabState.pendingImages && piLabState.pendingImages.length) { body.images = piLabState.pendingImages; piLabState.pendingImages = []; } return fetch(API.SEND, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }); }) .then(function (r) { return responseJsonOrError(r, 'Send failed'); }) .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) { var message = err && err.message ? err.message : 'Send failed'; if (err && err.code === 'page_ai_target_buffer_not_writable') { showPiToast(message, 'warning'); updateDiagnostics(message); setState(piLabState.sessionId ? STATE_STARTED : STATE_IDLE); updateMessages(); return; } if (streamingAssistantMsg) applyPiRunEvent({ type: 'assistant_error', message: message }); setState(STATE_STARTED); updateMessages(); }); } function ensureRuntimeReady() { if (piLabState.status === STATE_STARTED && piLabState.sessionId) { return Promise.resolve({ ok: true, session: piLabState.session }); } if (piLabState.status === STATE_STREAMING && piLabState.sessionId) { return Promise.resolve({ ok: true, session: piLabState.session }); } if (piLabState.status === STATE_STARTING || 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)); if (payload && payload.ok === false) { var result = payload.result || {}; throw new Error(result.message || payload.message || result.code || 'Tool call failed'); } 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) { showPiToast('当前页未绑定文件上下文', 'warning'); updateDiagnostics('当前页缺少 rootUri/pagePath,无法作为 Pi 上下文'); return; } toggleContextQuick(kind); showPiToast((contextSelectionState().currentPage ? '已选择' : '已取消') + '当前页上下文', 'info'); return; } if (kind === 'current-folder') { if (!context.rootUri || currentFolderPathFromPagePath(context.pagePath) === null) { showPiToast('当前页没有可用文件夹上下文', 'warning'); updateDiagnostics('当前页缺少 rootUri/folderPath,无法作为 Pi 文件夹上下文'); return; } toggleContextQuick(kind); showPiToast((contextSelectionState().currentFolder ? '已选择' : '已取消') + '当前文件夹上下文', 'info'); return; } if (kind === 'selection') { var selection = refreshSelectionSummary(); if (!selection && !contextSelectionState().selection) showPiToast('当前没有选区', 'warning'); toggleContextQuick(kind); return; } toggleContextQuick('rag'); showPiToast((contextSelectionState().lightrag ? '已启用' : '已关闭') + ' LightRAG 上下文', 'info'); } function sendCurrentInput(options) { if (!piLabPanelEl) return; var input = piLabPanelEl.querySelector('[data-page-ai-pi-lab-input]'); var text = input ? input.value.trim() : ''; if (!text) return; var startToast = piLabState.warmupRunning ? '正在绑定当前页 Pi 会话,完成后自动发送' : 'Pi Rust 正在启动,启动后自动发送'; if (piLabState.status === STATE_IDLE || piLabState.status === STATE_ABORTED || piLabState.status === STATE_ERROR || !piLabState.sessionId) { showPiToast(startToast, 'info'); ensureRuntimeReady().then(function () { if (!piLabState.sessionId) throw new Error('Pi Rust 会话尚未就绪'); if (input) input.value = ''; updateButtons(); return sendPrompt(text, { streamingBehavior: undefined }); }).catch(function (err) { showPiToast(err && err.message ? err.message : 'Pi Rust 启动失败', 'warning'); updateDiagnostics(err && err.message ? err.message : 'Pi Rust 启动失败'); updateButtons(); }); return; } if (piLabState.status === STATE_STARTING) { showPiToast(startToast, 'info'); ensureRuntimeReady().then(function () { if (!piLabState.sessionId) throw new Error('Pi Rust 会话尚未就绪'); if (input) input.value = ''; updateButtons(); return sendPrompt(text, { streamingBehavior: undefined }); }).catch(function (err) { showPiToast(err && err.message ? err.message : 'Pi Rust 启动失败', 'warning'); updateDiagnostics(err && err.message ? err.message : 'Pi Rust 启动失败'); updateButtons(); }); return; } if (input) { input.value = ''; clearPiLabDraft(); } updateButtons(); var isMidStream = piLabState.status === STATE_STREAMING; var streamingBehavior = isMidStream ? ((options && options.streamingBehavior) || 'steer') : undefined; sendPrompt(text, { streamingBehavior: streamingBehavior }).catch(function () {}); } function abortPrompt() { var piInput = piLabPanelEl && piLabPanelEl.querySelector('[data-page-ai-pi-lab-input]'); if (piInput && piInput.value.trim()) savePiLabDraft(); if (piLabEventSource) { piLabEventSource.close(); piLabEventSource = null; } // S4: apply abort UI state exactly once (optimistic). Do not re-apply from ABORT response. applyPiRunEvent({ type: 'assistant_abort', payload: { reason: '已中止', stopReason: 'aborted' } }); setState(STATE_ABORTED); updateMessages(); if (piLabState.sessionId) { fetch(API.ABORT, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId: piLabState.sessionId }), }).catch(function () {}); } } function startNewConversation() { var previousSessionId = piLabState.sessionId; var preservedContext = currentPageContext(); if (piLabEventSource) { piLabEventSource.close(); piLabEventSource = null; } if (previousSessionId && !piLabState.viewingHistorySessionId && (piLabState.status === STATE_STARTED || piLabState.status === STATE_STREAMING || piLabState.status === STATE_STARTING)) { fetch(API.ABORT, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId: previousSessionId }), }).catch(function () {}); } closeActionMenu(); setHistoryPanelVisible(false); exitHistoryReplayMode(); piLabState.sessionId = null; piLabState.providerSessionId = null; piLabState.session = { rootUri: preservedContext.rootUri || undefined, workspaceId: preservedContext.workspaceId || undefined, pagePath: preservedContext.pagePath || undefined, pageTitle: preservedContext.pageTitle || undefined, }; piLabState.contextSelection = { currentPage: false, currentFolder: false, selection: false, lightrag: false, }; piLabState.thinkingLevel = normalizeThinkingLevel(piLabState.defaultThinkingLevel || DEFAULT_THINKING_LEVEL); piLabState.status = STATE_IDLE; resetPiRunViewModel(); piLabState.receipts = []; updateMessages(); updateReceiptDisplay(); updateQueueDisplay(); updateThinkingControl(); updateConfig(); } function triggerCompact() { if (piLabState.status === STATE_STREAMING) return; if (piLabState.viewingHistorySessionId) return; if (!piLabState.sessionId) return; piLabState.isCompacting = true; updateQueueDisplay(); fetch(API.COMPACT, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId: piLabState.sessionId }), }).then(function (r) { return r.json(); }).then(function (data) { piLabState.isCompacting = false; if (data && data.summary) { updateDiagnostics('Compaction: ' + data.summary); showPiToast('会话压缩完成', 'success'); } else if (data && data.message) { showPiToast(data.message, 'info'); } else { showPiToast('会话压缩完成', 'success'); } if (data && data.messages && data.messages.length) { var compactCard = { role: 'system', text: '会话压缩已完成,共 ' + data.messages.length + ' 条消息', type: 'compaction', 'data-page-ai-pi-lab-compaction-result': 'true' }; piLabState.messages.push(compactCard); updateMessages(); } syncRuntimeState(); }).catch(function (err) { piLabState.isCompacting = false; showPiToast('压缩失败:' + (err.message || ''), 'error'); syncRuntimeState(); }); } function parseUnifiedPatch(patchText) { if (!patchText) return []; var files = []; // Real newlines (JSON-parsed patch text), not the two-char sequence \ + n. var fileLines = String(patchText).replace(/\r\n/g, '\n').split('\n'); var currentFile = null; var currentHunk = null; for (var i = 0; i < fileLines.length; i++) { var line = fileLines[i]; if (line.slice(0, 4) === '--- ') { if (currentFile) files.push(currentFile); currentFile = { oldPath: line.slice(4).trim(), newPath: '', hunks: [] }; if (i + 1 < fileLines.length && fileLines[i + 1].slice(0, 4) === '+++ ') { currentFile.newPath = fileLines[i + 1].slice(4).trim(); i++; } } else if (line.slice(0, 2) === '@@' && currentFile) { currentHunk = { header: line, lines: [] }; currentFile.hunks.push(currentHunk); } else if (currentHunk) { var type = 'ctx'; if (line[0] === '+') type = 'add'; else if (line[0] === '-') type = 'del'; else if (line[0] === '\\') continue; currentHunk.lines.push({ type: type, text: line }); } } if (currentFile) files.push(currentFile); return files; } function showDiffOverlay(title, html) { var overlay = document.createElement('div'); overlay.className = 'wolai-page-ai-pi-lab-diff-overlay'; overlay.innerHTML = '
    ' + escapeHtml(title) + '
    ' + html + '
    '; overlay.querySelector('.wolai-page-ai-pi-lab-diff-close').addEventListener('click', function () { overlay.remove(); }); overlay.addEventListener('click', function (e) { if (e.target === overlay) overlay.remove(); }); document.body.appendChild(overlay); } function renderSplitDiff(files) { if (!files || !files.length) return '
    无 diff 数据
    '; var html = ''; files.forEach(function (file) { html += '
    ' + escapeHtml(file.newPath || file.oldPath || 'unknown') + '
    '; (file.hunks || []).forEach(function (hunk, hi) { if (hi > 0) html += '
    ' + escapeHtml(hunk.header) + '
    '; var ctxCount = 0; var ctxLines = []; hunk.lines.forEach(function (ln) { if (ln.type === 'ctx') { ctxCount++; ctxLines.push(ln); if (ctxCount >= 6) { if (ctxLines.length > 6) { html += '
    ⋯ 展开 ' + (ctxLines.length - 6) + ' 行上下文
    '; html += ''; } ctxLines.slice(-6).forEach(function (cl) { html += '
    ' + escapeHtml(cl.text) + '
    '; }); ctxCount = 0; ctxLines = []; return; } } else { if (ctxLines.length) { ctxLines.forEach(function (cl) { html += '
    ' + escapeHtml(cl.text) + '
    '; }); ctxLines = []; ctxCount = 0; } html += '
    ' + escapeHtml(ln.text) + '
    '; } }); if (ctxLines.length) { ctxLines.forEach(function (cl) { html += '
    ' + escapeHtml(cl.text) + '
    '; }); } }); }); return html; } function fetchAndShowDiff(toolEventId, sessionId) { if (!toolEventId) { showPiToast('无 diff 数据', 'warning'); return; } var sid = sessionId || piLabState.sessionId || piLabState.viewingHistorySessionId; if (!sid) { showPiToast('缺少 session ID', 'warning'); return; } fetch(API.DIFF + '/' + encodeURIComponent(toolEventId) + '/diff?sessionId=' + encodeURIComponent(sid), { credentials: 'same-origin', cache: 'no-store' }).then(function (r) { if (!r.ok) throw new Error('Diff fetch failed: ' + r.status); return r.json(); }).then(function (data) { var patchText = data.patch || data.diff || data.text || ''; if (!patchText) { showDiffOverlay('Diff: ' + toolEventId, '
    无 diff 内容' + (data.message ? ': ' + escapeHtml(data.message) : '') + '
    '); return; } var files = parseUnifiedPatch(patchText); var title = 'Diff: ' + (data.toolEventId || toolEventId); showDiffOverlay(title, renderSplitDiff(files)); }).catch(function (err) { showPiToast('加载 diff 失败:' + (err.message || ''), 'error'); }); } function ensurePanel() { if (piLabPanelEl) return piLabPanelEl; var container = document.createElement('div'); container.innerHTML = piLabPanelHTML(); piLabPanelEl = container.firstElementChild; wireEvents(); updateReceiptDisplay(); updateArtifactRail(); 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().then(function () { if (piLabState.enabled && piLabState.runtimeAvailable !== false && !piLabState.sessionId && piLabState.status !== STATE_STARTING) { return startRuntime().then(function () { syncRuntimeState(); }); } syncRuntimeState(); updateButtons(); return null; }).then(function () { updateButtons(); }).catch(function (error) { updateDiagnostics(error && error.message ? error.message : 'Pi current page session prepare failed'); updateButtons(); }); var input = piLabPanelEl && piLabPanelEl.querySelector('[data-page-ai-pi-lab-input]'); if (input) { restorePiLabDraft(); setTimeout(function () { input.focus(); }, 60); } } function hidePiLab() { piLabState.active = false; closeComposerControlMenus(); if (piLabDrawerEl) setDrawerVisible(false); updateLauncherState(); } function updateLauncherState() { if (!piLabLauncherEl) return; piLabLauncherEl.setAttribute('data-state', piLabState.status); piLabLauncherEl.hidden = !!piLabState.active; } function installLauncher() { if (piLabLauncherEl) return; var hostButton = document.querySelector('[data-mnote-action="open-page-ai-pi-lab"], [data-testid="wolai-floating-ai"]'); if (hostButton instanceof HTMLElement) { piLabLauncherEl = hostButton; piLabLauncherEl.setAttribute('data-page-ai-pi-lab-launcher', 'true'); piLabLauncherEl.setAttribute('data-mnote-action', 'open-page-ai-pi-lab'); piLabLauncherEl.setAttribute('aria-label', '打开 Pi Lab'); piLabLauncherEl.setAttribute('title', '打开 Pi Lab'); piLabLauncherEl.textContent = 'π'; } else { 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', }, 'π'); document.body.appendChild(piLabLauncherEl); } piLabLauncherEl.addEventListener('click', function (event) { event.preventDefault(); event.stopPropagation(); showPiLab(); }); } 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 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 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 compactBtn = piLabPanelEl.querySelector('[data-page-ai-pi-lab-btn-compact]'); var newBtns = piLabPanelEl.querySelectorAll('[data-page-ai-pi-lab-new]'); if (sendBtn) sendBtn.addEventListener('click', sendCurrentInput); if (abortBtn) abortBtn.addEventListener('click', abortPrompt); if (compactBtn) compactBtn.addEventListener('click', triggerCompact); if (clearBtn) clearBtn.addEventListener('click', function () { resetPiRunViewModel(); piLabState.receipts = []; updateMessages(); updateReceiptDisplay(); updateQueueDisplay(); }); 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', function () { updateButtons(); handleAtMention(input); savePiLabDraft(); }); input.addEventListener('keydown', function (e) { if (e.isComposing || e.key === 'Process') return; if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendCurrentInput(e.altKey ? { streamingBehavior: 'followUp' } : undefined); } if (e.key === 'Escape' && piLabState.status === STATE_STREAMING) { e.preventDefault(); abortPrompt(); } }); } [providerSelect, modelSelect].forEach(function (node) { if (node) node.addEventListener('change', function () { applyModelControls(); applyModelConfigToRuntime(); }); }); if (customInput) customInput.addEventListener('input', applyModelControls); if (settingsBtn) { settingsBtn.addEventListener('click', function () { window.location.assign('/user/ai#ai-admin-access'); }); } if (historyBtn) { historyBtn.addEventListener('click', function () { var layer = piLabPanelEl.querySelector('[data-page-ai-pi-lab-history-layer]'); if (!layer) return; setHistoryPanelVisible(layer.hidden); }); } Array.prototype.slice.call(newBtns || []).forEach(function (btn) { btn.addEventListener('click', startNewConversation); }); piLabPanelEl.addEventListener('click', function (event) { var historyNew = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-new-small]') : null; if (historyNew) { startNewConversation(); return; } var actionMenuToggle = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-action-menu-toggle]') : null; if (actionMenuToggle) { setActionMenuOpen(!piLabState.actionMenuOpen); return; } var modelMenuToggle = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-model-menu-toggle]') : null; if (modelMenuToggle && !modelMenuToggle.disabled) { setModelMenuOpen(!piLabState.modelMenuOpen); return; } var modelOption = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-model-option]') : null; if (modelOption && !modelOption.disabled) { selectModelControl( modelOption.getAttribute('data-provider'), modelOption.getAttribute('data-model-id') ); return; } var thinkingMenuToggle = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-thinking-menu-toggle]') : null; if (thinkingMenuToggle && !thinkingMenuToggle.disabled) { setThinkingMenuOpen(!piLabState.thinkingMenuOpen); return; } var thinkingOption = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-thinking-option]') : null; if (thinkingOption && !thinkingOption.disabled) { selectThinkingControl(thinkingOption.getAttribute('data-page-ai-pi-lab-thinking-option')); return; } var menuAction = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-menu-action]') : null; if (menuAction && !menuAction.disabled) { handleActionMenuAction(menuAction.getAttribute('data-page-ai-pi-lab-menu-action')); return; } var permissionBtn = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-permission]') : null; if (permissionBtn) { closeActionMenu(); setPermissionMenuOpen(!piLabState.permissionMenuOpen); return; } var permissionMode = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-permission-mode]') : null; if (permissionMode) { selectPermissionMode(permissionMode.getAttribute('data-page-ai-pi-lab-permission-mode')); return; } var closeHistory = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-history-close], [data-page-ai-pi-lab-history-scrim]') : null; if (closeHistory) { setHistoryPanelVisible(false); return; } var refreshHistory = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-history-refresh]') : null; if (refreshHistory) { loadHistory().catch(function (error) { updateDiagnostics(error.message || '历史刷新失败'); showPiToast('历史刷新失败', 'error'); }); return; } var clearHistory = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-history-clear]') : null; if (clearHistory && !clearHistory.disabled) { clearHistorySessions().catch(function (error) { updateDiagnostics(error.message || '历史清空失败'); showPiToast('历史清空失败', 'error'); }); return; } var forkCurrent = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-history-fork-current]') : null; if (forkCurrent) { forkHistorySession(piLabState.viewingHistorySessionId || piLabState.sessionId).catch(function (error) { updateDiagnostics(error.message || '历史会话 fork 失败'); showPiToast('Fork failed', 'error'); }); return; var forkEntry = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-fork-entry]') : null; if (forkEntry) { var forkEntryId = forkEntry.getAttribute('data-page-ai-pi-lab-fork-entry'); forkHistoryEntry(piLabState.viewingHistorySessionId, forkEntryId).catch(function (error) { updateDiagnostics(error.message || 'fork 失败'); showPiToast('Fork failed', 'error'); }); return; } } var closeArtifacts = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-close-artifacts]') : null; if (closeArtifacts) { var closeBody = piLabPanelEl.querySelector('[data-page-ai-pi-lab-body]'); if (closeBody) closeBody.setAttribute('data-rail-open', 'false'); return; } var copyMarkdown = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-copy-markdown]') : null; if (copyMarkdown) { copyTextToClipboard(copyMarkdown.getAttribute('data-page-ai-pi-lab-copy-markdown') || '').then(function (ok) { showPiToast(ok ? 'Markdown copied' : '复制失败(剪贴板不可用)', ok ? 'success' : 'warning'); }); return; } var openSessionJsonl = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-open-session-jsonl]') : null; if (openSessionJsonl) { openCurrentSessionJsonl(); return; } var diagnosticCommand = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-diagnostic-command]') : null; if (diagnosticCommand) { runPiDiagnostic(diagnosticCommand.getAttribute('data-page-ai-pi-lab-diagnostic-command') || 'doctor'); return; } var openArtifact = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-artifact-open]') : null; if (openArtifact) { var artUrl = String(openArtifact.getAttribute('data-page-ai-pi-lab-artifact-open') || openArtifact.getAttribute('href') || '').trim(); if (!artUrl || artUrl === '#') { showPiToast('引用无效或无法访问', 'warning'); return; } window.dispatchEvent(new CustomEvent('mnote:open-reference', { detail: { source: 'page_ai_pi_lab_artifact', title: openArtifact.getAttribute('data-page-ai-pi-lab-artifact-title') || openArtifact.textContent || '', url: artUrl, }, })); return; } var viewDiff = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-artifact-view-diff]') : null; if (viewDiff) { var diffTeid = viewDiff.getAttribute('data-page-ai-pi-lab-artifact-view-diff'); var diffSid = viewDiff.getAttribute('data-page-ai-pi-lab-artifact-diff-sid'); fetchAndShowDiff(diffTeid, diffSid); return; } var copyArtifactMarkdown = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-artifact-copy-markdown]') : null; if (copyArtifactMarkdown) { copyTextToClipboard(copyArtifactMarkdown.getAttribute('data-page-ai-pi-lab-artifact-copy-markdown') || '').then(function (ok) { showPiToast(ok ? 'Artifact markdown copied' : '复制失败(剪贴板不可用)', ok ? 'success' : 'warning'); }); return; } var copyArtifact = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-artifact-copy]') : null; if (copyArtifact) { var raw = copyArtifact.getAttribute('data-page-ai-pi-lab-artifact-copy') || ''; copyTextToClipboard(raw).then(function (ok) { showPiToast(ok ? 'Artifact raw copied' : '复制失败(剪贴板不可用)', ok ? 'success' : 'warning'); }); return; } var deleteTarget = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-history-delete]') : null; if (deleteTarget) { deleteHistorySession(deleteTarget.getAttribute('data-page-ai-pi-lab-history-delete')).catch(function (error) { updateDiagnostics(error.message || '历史会话删除失败'); showPiToast('Delete failed', 'error'); }); return; } var exportTarget = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-history-export]') : null; if (exportTarget) { exportHistorySession(exportTarget.getAttribute('data-page-ai-pi-lab-history-export')).catch(function (error) { updateDiagnostics(error.message || '历史会话导出失败'); showPiToast('Export failed', 'error'); }); return; } var renameTarget = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-history-rename]') : null; if (renameTarget) { renameHistorySession(renameTarget.getAttribute('data-page-ai-pi-lab-history-rename')).catch(function (error) { updateDiagnostics(error.message || '历史会话重命名失败'); showPiToast('Rename failed', 'error'); }); return; } var forkTarget = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-history-fork]') : null; if (forkTarget) { forkHistorySession(forkTarget.getAttribute('data-page-ai-pi-lab-history-fork')).catch(function (error) { updateDiagnostics(error.message || '历史会话 fork 失败'); showPiToast('Fork failed', 'error'); }); return; } 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')); }); }); document.addEventListener('pointerdown', function (event) { if (!piLabState.active || !piLabPanelEl) return; var target = event.target; var controlSurface = target && target.closest ? target.closest( '[data-page-ai-pi-lab-action-menu-toggle], [data-page-ai-pi-lab-action-menu], ' + '[data-page-ai-pi-lab-model-menu-wrap], [data-page-ai-pi-lab-thinking-menu-wrap], ' + '[data-page-ai-pi-lab-permission-wrap]' ) : null; if (!controlSurface) closeComposerControlMenus(); }, true); } 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; if (window.__MNOTE_PI_LAB_TEST__ === true) window.__mnotePiLabReducerTest = { createPiRunViewModel: createPiRunViewModel, reducePiRunViewModel: reducePiRunViewModel, rebuildPiRunToolIndex: rebuildPiRunToolIndex, }; if (window.__MNOTE_PI_LAB_TEST__ === true) window.__mnotePiLabTest = { emitRpcEvent: handlePiRpcEvent, emitToolCall: handleToolCallEventPayload, showToast: showPiToast, closeDialog: closePiUiDialog, getCurrentContext: currentPageContext, getState: function () { return piLabState; }, }; } })();