Files
mnote/rust/crates/mnote-web/browser/sidebar-page-ai-pi-lab-runtime.js
T

4800 lines
254 KiB
JavaScript
Raw Normal View History

2026-07-04 21:47:42 +08:00
// == Pi Lab Page AI Runtime ==
// MNote-native adapter for Pi-first Page AI Lab.
2026-07-10 10:54:34 +08:00
// Renders the MNote-owned Pi Rust Page AI surface. OpenHub is kept as a
// compatibility/admin boundary during migration, not as the default chat owner.
// NO setInterval polling.
2026-07-04 21:47:42 +08:00
//
// 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).
2026-07-04 21:47:42 +08:00
(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',
2026-07-10 10:54:34 +08:00
UI_RESPONSE: '/api/page-ai/pi/ui-response',
2026-07-04 21:47:42 +08:00
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',
2026-07-04 21:47:42 +08:00
EFFECTIVE: '/api/ai-settings/effective',
};
var DEFAULT_MODEL_PROVIDER = 'omniroute';
var DEFAULT_MODEL_ID = 'gpt-5.4-mini';
2026-07-10 10:54:34 +08:00
var DEFAULT_THINKING_LEVEL = 'medium';
var DEFAULT_PERMISSION_MODE = 'confirm';
2026-07-04 21:47:42 +08:00
var piLabInstalled = false;
var piLabPanelEl = null;
var piLabDrawerEl = null;
var piLabLauncherEl = null;
var piLabEventSource = null;
2026-07-10 10:54:34 +08:00
var piLabStartPromise = null;
2026-07-04 21:47:42 +08:00
var piLabState = {
status: STATE_IDLE,
enabled: false,
active: false,
standalone: false,
sessionId: null,
providerSessionId: null,
defaultModelProvider: DEFAULT_MODEL_PROVIDER,
defaultModelId: DEFAULT_MODEL_ID,
2026-07-10 10:54:34 +08:00
defaultThinkingLevel: DEFAULT_THINKING_LEVEL,
2026-07-04 21:47:42 +08:00
modelProvider: null,
modelId: null,
2026-07-10 10:54:34 +08:00
thinkingLevel: DEFAULT_THINKING_LEVEL,
modelMenuOpen: false,
thinkingMenuOpen: false,
2026-07-10 10:54:34 +08:00
permissionMode: DEFAULT_PERMISSION_MODE,
permissionMenuOpen: false,
pendingPermissionModeApply: false,
pendingPermissionMode: null,
2026-07-10 10:54:34 +08:00
applyingPermissionMode: false,
actionMenuOpen: false,
2026-07-04 21:47:42 +08:00
runtimeMode: null,
2026-07-10 10:54:34 +08:00
runtimeImplementation: null,
runtimeBinary: null,
runtimeAvailable: true,
runtimeInstallHint: '',
runtimeError: '',
2026-07-04 21:47:42 +08:00
runtimePid: null,
warmupRunning: false,
warmupSessionId: null,
2026-07-10 10:54:34 +08:00
managedBuiltinTools: [],
piExtensionSources: [],
piExtensionToolNames: [],
2026-07-04 21:47:42 +08:00
session: null,
currentToolEventId: null,
2026-07-04 21:47:42 +08:00
allowedRootsSummary: '',
selectionSummary: '',
selectionText: '',
changedFiles: [],
messages: [],
diagnostics: '',
receipts: [],
modelOptions: [],
history: [],
2026-07-10 10:54:34 +08:00
pendingQueue: { steering: [], followUp: [] },
viewingHistorySessionId: null,
contextSelection: {
currentPage: false,
currentFolder: false,
selection: false,
lightrag: false,
},
queuedMessages: [],
pendingMessageCount: 0,
isCompacting: false,
contextUsage: {},
2026-07-04 21:47:42 +08:00
};
var streamingAssistantMsg = null;
2026-07-10 10:54:34 +08:00
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') {
var abortMsg = vm.currentAssistant || findLastAssistant(vm) || ensurePiRunAssistant(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;
2026-07-10 10:54:34 +08:00
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: '' };
2026-07-10 10:54:34 +08:00
diffMsg.diffSummary.files = payload.files || [];
if (payload.toolEventId) diffMsg.diffSummary.toolEventId = payload.toolEventId;
2026-07-10 10:54:34 +08:00
return vm;
}
return vm;
}
function applyPiRunEvent(event) {
syncPiRunViewModelFromState();
reducePiRunViewModel(piRunViewModel, event);
syncPiRunViewModelToState();
return piRunViewModel;
}
2026-07-04 21:47:42 +08:00
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}' +
2026-07-10 10:54:34 +08:00
'.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}' +
2026-07-04 21:47:42 +08:00
'.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}' +
2026-07-10 10:54:34 +08:00
'.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}' +
2026-07-04 21:47:42 +08:00
'.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}' +
2026-07-10 10:54:34 +08:00
'.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}' +
2026-07-04 21:47:42 +08:00
'.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}' +
2026-07-10 10:54:34 +08:00
'.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}' +
2026-07-04 21:47:42 +08:00
'.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}' +
2026-07-10 10:54:34 +08:00
'.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)}' +
2026-07-04 21:47:42 +08:00
'.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}' +
2026-07-10 10:54:34 +08:00
'.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)}' +
2026-07-04 21:47:42 +08:00
'.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}' +
2026-07-10 10:54:34 +08:00
'.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}' +
2026-07-04 21:47:42 +08:00
'@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}' +
2026-07-10 10:54:34 +08:00
'.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}' +
2026-07-04 21:47:42 +08:00
'.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}' +
2026-07-10 10:54:34 +08:00
'.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}' +
2026-07-04 21:47:42 +08:00
'.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}' +
2026-07-10 10:54:34 +08:00
'.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}' +
2026-07-10 10:54:34 +08:00
'.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}' +
2026-07-04 21:47:42 +08:00
'.wolai-page-ai-pi-lab-segment{display:inline-flex;border:1px solid #e1e4eb;border-radius:10px;overflow:hidden;background:#fff}' +
2026-07-10 10:54:34 +08:00
'.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}' +
2026-07-04 21:47:42 +08:00
'.wolai-page-ai-pi-lab-segment button[data-active=\"true\"]{background:#111827;color:#fff}' +
2026-07-10 10:54:34 +08:00
'.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}' +
2026-07-04 21:47:42 +08:00
'.wolai-page-ai-pi-lab-spacer{flex:1}' +
2026-07-10 10:54:34 +08:00
'.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}' +
2026-07-04 21:47:42 +08:00
'.wolai-page-ai-pi-lab-send:disabled{opacity:.45;cursor:default}' +
2026-07-10 10:54:34 +08:00
'.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}' +
2026-07-04 21:47:42 +08:00
'.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}' +
2026-07-10 10:54:34 +08:00
'.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}' +
2026-07-04 21:47:42 +08:00
'.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}' +
2026-07-10 10:54:34 +08:00
'.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}' +
2026-07-04 21:47:42 +08:00
'.wolai-page-ai-pi-lab-diagnostics{display:none;border-top:1px solid #e6e2da;background:#fff;flex:0 0 auto}' +
'.wolai-page-ai-pi-lab-drawer[data-config-open="true"] .wolai-page-ai-pi-lab-diagnostics{display:block}' +
'.wolai-page-ai-pi-lab-diagnostics>summary{cursor:pointer;padding:8px 12px;font-size:11px;color:#6f6a60}' +
'.wolai-page-ai-pi-lab-diagnostics pre{margin:0;padding:0 12px 10px;white-space:pre-wrap;word-break:break-word;font:11px/1.45 ui-monospace,SFMono-Regular,Menlo,monospace;color:#5d594f}' +
2026-07-10 10:54:34 +08:00
'@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;
2026-07-04 21:47:42 +08:00
document.head.appendChild(style);
}
function escapeHtml(str) {
if (str == null) return '';
return String(str)
.replace(/&/g, '&')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
2026-07-10 10:54:34 +08:00
function escapeAttr(str) {
return escapeHtml(str).replace(/`/g, '&#96;');
}
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>' + code + '</code>');
});
value = value.replace(/\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)/g, function (_, label, href) {
return stash('<a href="' + escapeAttr(href) + '" target="_blank" rel="noopener">' + label + '</a>');
});
value = value.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
value = value.replace(/(^|[^*])\*([^*\n]+)\*/g, '$1<em>$2</em>');
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: '<table><thead><tr>' + header.map(function (cell) {
return '<th>' + renderMarkdownInline(cell) + '</th>';
}).join('') + '</tr></thead><tbody>' + rows.map(function (row) {
return '<tr>' + header.map(function (_, index) {
return '<td>' + renderMarkdownInline(row[index] || '') + '</td>';
}).join('') + '</tr>';
}).join('') + '</tbody></table>',
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('<p>' + renderMarkdownInline(paragraph.join(' ')) + '</p>');
paragraph = [];
}
function flushList() {
if (!list) return;
html.push('<' + list.type + '>' + list.items.map(function (item) {
return '<li>' + renderMarkdownInline(item) + '</li>';
}).join('') + '</' + list.type + '>');
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('<pre data-lang="' + escapeAttr(codeLang) + '"><code>' + escapeHtml(codeLines.join('\n')) + '</code></pre>');
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('<h' + heading[1].length + '>' + renderMarkdownInline(heading[2]) + '</h' + heading[1].length + '>');
continue;
}
var quote = trimmed.match(/^>\s?(.*)$/);
if (quote) {
flushParagraph();
flushList();
html.push('<blockquote>' + renderMarkdownInline(quote[1]) + '</blockquote>');
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('<pre data-lang="' + escapeAttr(codeLang) + '"><code>' + escapeHtml(codeLines.join('\n')) + '</code></pre>');
flushParagraph();
flushList();
return html.join('');
}
2026-07-04 21:47:42 +08:00
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;
}
2026-07-10 10:54:34 +08:00
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();
}
2026-07-10 10:54:34 +08:00
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 ? '<em class="wolai-page-ai-pi-lab-tool-policy" data-policy="' + escapeHtml(policy) + '" data-approved="' +
(tc.approvalRequired ? String(tc.approvalConfirmed === true) : '') + '">' +
escapeHtml(tc.approvalRequired ? (tc.approvalConfirmed ? 'approved' : 'approval required') : policy) + '</em>' : '';
item.appendChild(el('div', {
className: 'wolai-page-ai-pi-lab-tool-call-header',
'data-page-ai-pi-lab-tool-summary': 'true',
innerHTML: '<strong>' + escapeHtml(displayName || 'tool') + policyBadge + '</strong><span data-page-ai-pi-lab-tool-status>' + escapeHtml(tc.status || '') + '</span>',
}));
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;
}
2026-07-04 21:47:42 +08:00
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\/([^/]+)/);
2026-07-10 10:54:34 +08:00
if (match) return decodeURIComponent(match[1]);
var missingPage = String(currentUrl().searchParams.get('missingPage') || currentUrl().searchParams.get('pageId') || '').trim();
return missingPage ? missingPage : '';
2026-07-04 21:47:42 +08:00
}
function currentWorkspaceId() {
return currentUrl().searchParams.get('workspaceId') || (document.body && document.body.dataset ? String(document.body.dataset.workspaceId || '').trim() : '');
}
function currentRootUri() {
2026-07-10 10:54:34 +08:00
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();
2026-07-04 21:47:42 +08:00
}
function localMarkdownRelativePathFromDocumentId(documentId) {
var value = String(documentId || '').trim();
if (!value.startsWith('local-md:')) return '';
2026-07-10 10:54:34 +08:00
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, '\\$&');
2026-07-04 21:47:42 +08:00
}
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;
}
}
2026-07-10 10:54:34 +08:00
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;
}
2026-07-04 21:47:42 +08:00
function currentPageContext() {
var active = activeEditorSnapshot() || {};
2026-07-10 10:54:34 +08:00
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();
2026-07-10 10:54:34 +08:00
var fileTreeWorkspacePath = activeFileTreeWorkspacePath(documentId) || {};
var workspacePath = Object.assign({}, activeWorkspacePath, fileTreeWorkspacePath);
var session = piLabState.session || {};
var pagePath = normalizeSlashes(urlPagePath || workspacePath.relativePath || workspacePath.path || active.relativePath || active.path || localMarkdownRelativePathFromDocumentId(documentId) || session.pagePath || '');
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 || session.rootUri || '').trim();
var workspaceId = String(currentWorkspaceId() || workspacePath.workspaceId || active.workspaceId || session.workspaceId || '').trim();
2026-07-04 21:47:42 +08:00
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,
};
}
2026-07-10 10:54:34 +08:00
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,
};
}
2026-07-04 21:47:42 +08:00
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 = {};
2026-07-10 10:54:34 +08:00
if (context.pagePath) piLabState.session.pagePath = context.pagePath;
if (context.pageTitle) piLabState.session.pageTitle = context.pageTitle;
if (context.rootUri) piLabState.session.rootUri = context.rootUri;
if (context.workspaceId) piLabState.session.workspaceId = context.workspaceId;
var folderPath = currentFolderPathFromPagePath(context.pagePath);
if (folderPath !== null) piLabState.session.folderPath = folderPath;
2026-07-04 21:47:42 +08:00
updateContextStrip();
return context;
}
2026-07-10 10:54:34 +08:00
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);
}
2026-07-04 21:47:42 +08:00
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);
}
2026-07-10 10:54:34 +08:00
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: '思考 最高' },
];
}
2026-07-10 10:54:34 +08:00
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]');
2026-07-10 10:54:34 +08:00
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 会话时应用';
2026-07-10 10:54:34 +08:00
}
if (label) label.textContent = '思考 ' + currentThinkingLabel();
if (menu) menu.innerHTML = thinkingLevelOptions().map(function (item) {
var active = item.level === level;
return '<button type="button" class="wolai-page-ai-pi-lab-control-option" role="menuitemradio" aria-checked="' + String(active) + '" data-page-ai-pi-lab-thinking-option="' + escapeHtml(item.level) + '" data-active="' + String(active) + '">' +
'<span class="wolai-page-ai-pi-lab-control-option-label">' + escapeHtml(item.label) + '</span>' +
'<span class="wolai-page-ai-pi-lab-control-option-check">' + (active ? '✓' : '') + '</span>' +
'</button>';
}).join('');
2026-07-10 10:54:34 +08:00
}
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);
}
2026-07-10 10:54:34 +08:00
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();
}
2026-07-10 10:54:34 +08:00
function selectPermissionMode(mode) {
piLabState.permissionMode = normalizePermissionMode(mode);
setPermissionMenuOpen(false);
updatePermissionControl();
if (piLabState.sessionId
&& piLabState.status !== STATE_STREAMING
&& piLabState.status !== STATE_STARTING
&& !piLabState.applyingPermissionMode) {
2026-07-10 10:54:34 +08:00
return applyPermissionModeToRuntime();
}
if (piLabState.sessionId
&& (piLabState.status === STATE_STREAMING
|| piLabState.status === STATE_STARTING
|| piLabState.applyingPermissionMode)) {
2026-07-10 10:54:34 +08:00
piLabState.pendingPermissionModeApply = true;
piLabState.pendingPermissionMode = piLabState.permissionMode;
2026-07-10 10:54:34 +08:00
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();
}
2026-07-10 10:54:34 +08:00
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);
2026-07-10 10:54:34 +08:00
return Promise.resolve({ queued: true });
}
var context = applyCurrentContextToState();
var nextMode = normalizePermissionMode(piLabState.permissionMode);
ensurePiToolCapableModel();
2026-07-10 10:54:34 +08:00
piLabState.pendingPermissionModeApply = false;
piLabState.pendingPermissionMode = null;
2026-07-10 10:54:34 +08:00
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);
}
2026-07-10 10:54:34 +08:00
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 '<button type="button" class="wolai-page-ai-pi-lab-permission-option" data-page-ai-pi-lab-permission-mode="' + escapeHtml(item.mode) + '" data-active="' + String(active) + '">' +
piIcon(item.icon) +
'<span><strong>' + escapeHtml(item.label) + '</strong><small>' + escapeHtml(item.description) + '</small></span>' +
'<span class="wolai-page-ai-pi-lab-permission-check" data-page-ai-pi-lab-permission-check>' + (active ? '✓' : '') + '</span>' +
'</button>';
}).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 === 'send-steer') {
sendCurrentInput({ streamingBehavior: 'steer' });
return;
}
if (action === 'send-followup') {
sendCurrentInput({ streamingBehavior: 'followUp' });
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');
}
}
2026-07-04 21:47:42 +08:00
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();
2026-07-10 10:54:34 +08:00
piLabState.permissionMode = normalizePermissionMode(piLabState.permissionMode);
2026-07-04 21:47:42 +08:00
updateModelLabel();
2026-07-10 10:54:34 +08:00
updateThinkingControl();
updatePermissionControl();
2026-07-04 21:47:42 +08:00
}
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);
}
2026-07-04 21:47:42 +08:00
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;
}
2026-07-04 21:47:42 +08:00
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();
2026-07-10 10:54:34 +08:00
piLabState.defaultThinkingLevel = normalizeThinkingLevel(data.defaultThinkingLevel || data.default_thinking_level || piLabState.defaultThinkingLevel);
if (!piLabState.thinkingLevel) piLabState.thinkingLevel = piLabState.defaultThinkingLevel;
2026-07-04 21:47:42 +08:00
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,
}]);
2026-07-04 21:47:42 +08:00
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 '<option value="' + escapeHtml(provider) + '">' + escapeHtml(provider) + '</option>';
}).join('');
providerSelect.value = piLabState.modelProvider || piLabState.defaultModelProvider;
}
var modelOptions = piLabState.modelOptions.length ? piLabState.modelOptions : [{
provider: piLabState.defaultModelProvider,
id: piLabState.defaultModelId,
name: piLabState.defaultModelId,
}];
var optionHtml = modelOptions.map(function (item) {
var id = String(item.id || item.modelId || item.name || DEFAULT_MODEL_ID);
var label = String(item.name || id);
return '<option value="' + escapeHtml(id) + '">' + escapeHtml(label) + '</option>';
}).join('');
if (modelSelect) modelSelect.innerHTML = optionHtml;
updateModelLabel();
2026-07-10 10:54:34 +08:00
updateThinkingControl();
2026-07-04 21:47:42 +08:00
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';
}
2026-07-10 10:54:34 +08:00
function piIcon(name) {
var icons = {
plus: '<path d="M12 5v14M5 12h14"/>',
history: '<path d="M3 12a9 9 0 1 0 3-6.7"/><path d="M3 4v5h5"/><path d="M12 7v5l3 2"/>',
external: '<path d="M14 3h7v7"/><path d="M10 14 21 3"/><path d="M21 14v5a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5"/>',
artifacts: '<path d="M4 5h16v14H4z"/><path d="M9 5v14"/><path d="M15 5v14"/>',
status: '<circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/>',
bell: '<path d="M18 8a6 6 0 0 0-12 0c0 7-3 7-3 9h18c0-2-3-2-3-9"/><path d="M10 21h4"/>',
play: '<path d="m8 5 11 7-11 7z"/>',
stop: '<path d="M6 6h12v12H6z"/>',
trash: '<path d="M3 6h18"/><path d="M8 6V4h8v2"/><path d="M19 6l-1 14H6L5 6"/>',
settings: '<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.9l.1.1-2.8 2.8-.1-.1a1.7 1.7 0 0 0-1.9-.3 1.7 1.7 0 0 0-1 1.6V21h-4v-.1a1.7 1.7 0 0 0-1-1.6 1.7 1.7 0 0 0-1.9.3l-.1.1-2.8-2.8.1-.1A1.7 1.7 0 0 0 4.6 15a1.7 1.7 0 0 0-1.6-1H3v-4h.1a1.7 1.7 0 0 0 1.6-1 1.7 1.7 0 0 0-.3-1.9l-.1-.1 2.8-2.8.1.1A1.7 1.7 0 0 0 9 4.6a1.7 1.7 0 0 0 1-1.6V3h4v.1a1.7 1.7 0 0 0 1 1.6 1.7 1.7 0 0 0 1.9-.3l.1-.1 2.8 2.8-.1.1a1.7 1.7 0 0 0-.3 1.9 1.7 1.7 0 0 0 1.6 1h.1v4H21a1.7 1.7 0 0 0-1.6 1z"/>',
zap: '<path d="M13 2 3 14h8l-1 8 11-13h-8z"/>',
file: '<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/>',
selection: '<path d="M4 4h6v6H4z"/><path d="M14 4h6v6h-6z"/><path d="M4 14h6v6H4z"/><path d="M14 14h6v6h-6z"/>',
database: '<ellipse cx="12" cy="5" rx="7" ry="3"/><path d="M5 5v6c0 1.7 3.1 3 7 3s7-1.3 7-3V5"/><path d="M5 11v6c0 1.7 3.1 3 7 3s7-1.3 7-3v-6"/>',
shield: '<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>',
brain: '<path d="M9 3a3 3 0 0 0-3 3v1a3 3 0 0 0-2 5 3 3 0 0 0 2 5v1a3 3 0 0 0 5 2"/><path d="M15 3a3 3 0 0 1 3 3v1a3 3 0 0 1 2 5 3 3 0 0 1-2 5v1a3 3 0 0 1-5 2"/><path d="M12 4v16"/>',
folder: '<path d="M3 6a2 2 0 0 1 2-2h5l2 2h7a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>',
image: '<rect x="3" y="5" width="18" height="14" rx="2"/><circle cx="8" cy="10" r="2"/><path d="m21 15-5-5L5 21"/>',
camera: '<path d="M14.5 4 16 7h3a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h3l1.5-3z"/><circle cx="12" cy="13" r="3"/>',
queue: '<path d="M4 6h16"/><path d="M4 12h10"/><path d="M4 18h7"/><path d="m16 15 3 3-3 3"/>',
compact: '<path d="M4 8h16M4 16h16"/><path d="M8 4 12 8 16 4M8 20l4-4 4 4"/>',
2026-07-10 10:54:34 +08:00
copy: '<rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>',
send: '<path d="m22 2-7 20-4-9-9-4Z"/><path d="M22 2 11 13"/>',
close: '<path d="M18 6 6 18"/><path d="m6 6 12 12"/>',
minus: '<path d="M5 12h14"/>',
};
return '<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">' + (icons[name] || icons.status) + '</svg>';
}
2026-07-04 21:47:42 +08:00
function piLabPanelHTML() {
return '' +
'<div class="wolai-page-ai-pi-lab-shell" data-page-ai-pi-lab="panel">' +
'<div class="wolai-page-ai-pi-lab-topbar">' +
'<div class="wolai-page-ai-pi-lab-brand">' +
'<div class="wolai-page-ai-pi-lab-logo">π</div>' +
'<div class="wolai-page-ai-pi-lab-title" data-page-ai-pi-lab-title><strong>Pi Lab 平台</strong><span data-page-ai-pi-lab-model>默认模型:omniroute/gpt-5.4-mini</span></div>' +
2026-07-04 21:47:42 +08:00
'</div>' +
2026-07-10 10:54:34 +08:00
'<button type="button" class="wolai-page-ai-pi-lab-icon-btn" data-page-ai-pi-lab-minimize aria-label="最小化 Pi Lab" title="最小化">' + piIcon('minus') + '</button>' +
'<button type="button" class="wolai-page-ai-pi-lab-icon-btn" data-page-ai-pi-lab-close aria-label="关闭 Pi Lab" title="关闭">' + piIcon('close') + '</button>' +
2026-07-04 21:47:42 +08:00
'</div>' +
'<div class="wolai-page-ai-pi-lab-commandbar" data-page-ai-pi-lab-commandbar>' +
2026-07-10 10:54:34 +08:00
'<button type="button" class="wolai-page-ai-pi-lab-tool-icon" data-page-ai-pi-lab-new title="新对话" aria-label="新对话">' + piIcon('plus') + '</button>' +
'<button type="button" class="wolai-page-ai-pi-lab-tool-icon" data-page-ai-pi-lab-history title="历史" aria-label="历史">' + piIcon('history') + '</button>' +
'<span class="wolai-page-ai-pi-lab-commandbar-spacer"></span>' +
'<button type="button" class="wolai-page-ai-pi-lab-tool-icon" data-page-ai-pi-lab-btn-abort title="中止" aria-label="中止" hidden>' + piIcon('stop') + '</button>' +
'<button type="button" class="wolai-page-ai-pi-lab-tool-icon" data-page-ai-pi-lab-btn-clear title="清空" aria-label="清空">' + piIcon('trash') + '</button>' +
'<button type="button" class="wolai-page-ai-pi-lab-tool-icon" data-page-ai-pi-lab-btn-compact title="压缩上下文" aria-label="压缩上下文" disabled>' + piIcon('compact') + '</button>' +
'<button type="button" class="wolai-page-ai-pi-lab-tool-icon" data-page-ai-pi-lab-open-settings title="AI 管理" aria-label="AI 管理">' + piIcon('settings') + '</button>' +
2026-07-04 21:47:42 +08:00
'</div>' +
'<div class="wolai-page-ai-pi-lab-config">' +
'<details class="wolai-page-ai-pi-lab-settings" data-page-ai-pi-lab-settings>' +
'<summary><span class="wolai-page-ai-pi-lab-chip" data-page-ai-pi-lab-status-chip><strong data-page-ai-pi-lab-status-text>idle</strong></span><span>设置</span><strong data-page-ai-pi-lab-model-chip>omniroute/gpt-5.4-mini</strong></summary>' +
2026-07-04 21:47:42 +08:00
'<div class="wolai-page-ai-pi-lab-settings-grid">' +
'<label class="wolai-page-ai-pi-lab-field">Provider<select data-page-ai-pi-lab-model-provider><option value="omniroute">Omniroute</option></select></label>' +
'<label class="wolai-page-ai-pi-lab-field">Model<select data-page-ai-pi-lab-model-id><option value="gpt-5.4-mini">gpt-5.4-mini</option></select></label>' +
2026-07-10 10:54:34 +08:00
'<label class="wolai-page-ai-pi-lab-field">自定义模型<input data-page-ai-pi-lab-model-custom placeholder="可选,启动时应用"></label>' +
2026-07-04 21:47:42 +08:00
'<span class="wolai-page-ai-pi-lab-chip"><span>runtime</span><strong data-page-ai-pi-lab-runtime-chip>runtime pending</strong></span>' +
2026-07-10 10:54:34 +08:00
'<span class="wolai-page-ai-pi-lab-chip" data-page-ai-pi-lab-builtin-managed><span>builtin managed</span><strong>read/write/edit/bash</strong></span>' +
'<span class="wolai-page-ai-pi-lab-chip" data-page-ai-pi-lab-extension-chip><span>Pi 扩展</span><strong data-page-ai-pi-lab-extension-count>pending</strong></span>' +
2026-07-04 21:47:42 +08:00
'</div>' +
'</details>' +
'<div class="wolai-page-ai-pi-lab-actions">' +
'<span class="wolai-page-ai-pi-lab-chip"><span>操作</span><strong>见顶部工具栏</strong></span>' +
'</div>' +
'</div>' +
2026-07-10 10:54:34 +08:00
'<div class="wolai-page-ai-pi-lab-history-layer" data-page-ai-pi-lab-history-layer hidden>' +
'<button type="button" class="wolai-page-ai-pi-lab-history-scrim" data-page-ai-pi-lab-history-scrim aria-label="关闭历史"></button>' +
'<aside class="wolai-page-ai-pi-lab-history-panel" data-page-ai-pi-lab-history-panel aria-label="历史对话">' +
'<div class="wolai-page-ai-pi-lab-history-head"><span>历史对话</span><button type="button" class="wolai-page-ai-pi-lab-history-close" data-page-ai-pi-lab-history-close aria-label="关闭历史">×</button></div><div class="wolai-page-ai-pi-lab-empty">加载历史...</div>' +
'</aside>' +
2026-07-04 21:47:42 +08:00
'</div>' +
'<details class="wolai-page-ai-pi-lab-context-strip" data-page-ai-pi-lab-context-strip>' +
'<summary><span class="wolai-page-ai-pi-lab-context-title">上下文</span><span class="wolai-page-ai-pi-lab-context-current" data-page-ai-pi-lab-current-page>未绑定</span><span class="wolai-page-ai-pi-lab-context-count"><strong data-page-ai-pi-lab-changed-files>0</strong> changed</span></summary>' +
'<div class="wolai-page-ai-pi-lab-context-content">' +
'<span class="wolai-page-ai-pi-lab-chip" data-page-ai-pi-lab-context="selection"><span>选区</span><strong data-page-ai-pi-lab-selection>无选区</strong></span>' +
'<span class="wolai-page-ai-pi-lab-chip" data-page-ai-pi-lab-context="allowed-roots"><span>allowed roots</span><strong data-page-ai-pi-lab-allowed-roots>pending</strong></span>' +
'<span class="wolai-page-ai-pi-lab-chip" data-page-ai-pi-lab-context="lightrag"><span>LightRAG</span><strong data-page-ai-pi-lab-lightrag>default</strong></span>' +
'</div>' +
'</details>' +
2026-07-10 10:54:34 +08:00
'<div class="wolai-page-ai-pi-lab-body" data-page-ai-pi-lab-body data-rail-open="false">' +
2026-07-04 21:47:42 +08:00
'<div class="wolai-page-ai-pi-lab-main">' +
2026-07-10 10:54:34 +08:00
'<div class="wolai-page-ai-pi-lab-replay-banner" data-page-ai-pi-lab-replay-banner hidden><span>正在查看历史,只读</span><button type="button" data-page-ai-pi-lab-history-fork-current>Fork 后继续</button></div>' +
2026-07-04 21:47:42 +08:00
'<div class="wolai-page-ai-pi-lab-messages" data-page-ai-pi-lab-messages></div>' +
'<div class="wolai-page-ai-pi-lab-composer">' +
2026-07-10 10:54:34 +08:00
'<div class="wolai-page-ai-pi-lab-input-wrap">' +
'<textarea class="wolai-page-ai-pi-lab-input" data-page-ai-pi-lab-input placeholder="问 Pi..." rows="3"></textarea>' +
'<button type="button" class="wolai-page-ai-pi-lab-send" data-page-ai-pi-lab-btn-send disabled aria-label="发送" title="发送">' + piIcon('send') + '</button>' +
'</div>' +
'<div class="wolai-page-ai-pi-lab-action-menu" data-page-ai-pi-lab-action-menu>' +
'<div class="wolai-page-ai-pi-lab-menu-section">上下文</div>' +
'<button type="button" class="wolai-page-ai-pi-lab-menu-item" data-page-ai-pi-lab-menu-action="read-page">' + piIcon('file') + '<span>使用当前页</span><strong data-page-ai-pi-lab-context-mark="read-page"></strong></button>' +
'<button type="button" class="wolai-page-ai-pi-lab-menu-item" data-page-ai-pi-lab-menu-action="current-folder">' + piIcon('folder') + '<span>使用当前文件夹</span><strong data-page-ai-pi-lab-context-mark="current-folder"></strong></button>' +
'<button type="button" class="wolai-page-ai-pi-lab-menu-item" data-page-ai-pi-lab-menu-action="selection">' + piIcon('selection') + '<span>使用选区</span><strong data-page-ai-pi-lab-context-mark="selection"></strong></button>' +
'<button type="button" class="wolai-page-ai-pi-lab-menu-item" data-page-ai-pi-lab-menu-action="rag">' + piIcon('database') + '<span>启用 LightRAG</span><strong data-page-ai-pi-lab-context-mark="rag"></strong></button>' +
'<div class="wolai-page-ai-pi-lab-menu-sep"></div>' +
'<div class="wolai-page-ai-pi-lab-menu-section">权限与附件</div>' +
'<button type="button" class="wolai-page-ai-pi-lab-menu-item" data-page-ai-pi-lab-menu-action="directory-permission">' + piIcon('folder') + '<span>目录权限</span></button>' +
'<button type="button" class="wolai-page-ai-pi-lab-menu-item" data-page-ai-pi-lab-menu-action="attach-file" disabled title="Pi RPC 附件上下文尚未接入">' + piIcon('image') + '<span>添加图片和文件</span></button>' +
'<button type="button" class="wolai-page-ai-pi-lab-menu-item" data-page-ai-pi-lab-menu-action="camera" disabled title="Pi RPC 附件上下文尚未接入">' + piIcon('camera') + '<span>拍照</span></button>' +
'<div class="wolai-page-ai-pi-lab-menu-sep"></div>' +
'<div class="wolai-page-ai-pi-lab-menu-section">执行中发送</div>' +
'<button type="button" class="wolai-page-ai-pi-lab-menu-item" data-page-ai-pi-lab-menu-action="send-steer">' + piIcon('zap') + '<span>作为引导发送</span></button>' +
'<button type="button" class="wolai-page-ai-pi-lab-menu-item" data-page-ai-pi-lab-menu-action="send-followup">' + piIcon('queue') + '<span>作为排队追问发送</span></button>' +
'<button type="button" class="wolai-page-ai-pi-lab-menu-item" data-page-ai-pi-lab-menu-action="plan-mode" title="通过 Pi Rust 官方 plan-mode 扩展进入计划评审">' + piIcon('settings') + '<span>计划评审</span></button>' +
2026-07-04 21:47:42 +08:00
'</div>' +
'<div class="wolai-page-ai-pi-lab-modebar" data-page-ai-pi-lab-modebar>' +
2026-07-10 10:54:34 +08:00
'<button type="button" class="wolai-page-ai-pi-lab-square" data-page-ai-pi-lab-action-menu-toggle title="添加上下文和操作" aria-label="添加上下文和操作">' + piIcon('plus') + '</button>' +
'<div class="wolai-page-ai-pi-lab-control-wrap wolai-page-ai-pi-lab-model-control-wrap" data-page-ai-pi-lab-model-menu-wrap data-open="false">' +
'<button type="button" class="wolai-page-ai-pi-lab-control-button wolai-page-ai-pi-lab-model-control" data-page-ai-pi-lab-model-menu-toggle aria-haspopup="menu" aria-expanded="false" title="模型">' +
'<span class="wolai-page-ai-pi-lab-control-label" data-page-ai-pi-lab-model-label>omniroute/gpt-5.4-mini</span>' +
'<span class="wolai-page-ai-pi-lab-control-caret" aria-hidden="true">⌄</span>' +
'</button>' +
'<div class="wolai-page-ai-pi-lab-control-menu" data-page-ai-pi-lab-model-menu role="menu"></div>' +
'</div>' +
'<div class="wolai-page-ai-pi-lab-control-wrap wolai-page-ai-pi-lab-thinking-control-wrap" data-page-ai-pi-lab-thinking-menu-wrap data-open="false">' +
'<button type="button" class="wolai-page-ai-pi-lab-control-button wolai-page-ai-pi-lab-thinking-control" data-page-ai-pi-lab-thinking-menu-toggle aria-haspopup="menu" aria-expanded="false" title="思考深度" aria-label="思考深度">' +
'<span class="wolai-page-ai-pi-lab-control-label" data-page-ai-pi-lab-thinking-label>思考 中</span>' +
'<span class="wolai-page-ai-pi-lab-control-caret" aria-hidden="true">⌄</span>' +
'</button>' +
'<div class="wolai-page-ai-pi-lab-control-menu" data-page-ai-pi-lab-thinking-menu role="menu"></div>' +
'</div>' +
2026-07-10 10:54:34 +08:00
'<div class="wolai-page-ai-pi-lab-permission-wrap" data-page-ai-pi-lab-permission-wrap data-open="false">' +
'<button type="button" class="wolai-page-ai-pi-lab-chip-button" data-page-ai-pi-lab-permission title="Pi 模式" aria-label="Pi 模式">' + piIcon('shield') + '<span data-page-ai-pi-lab-permission-label>变更前确认</span></button>' +
'<div class="wolai-page-ai-pi-lab-permission-menu" data-page-ai-pi-lab-permission-menu>' + permissionModeMenuHtml() + '</div>' +
'</div>' +
'<button type="button" class="wolai-page-ai-pi-lab-square" data-page-ai-pi-lab-quick="read-page" title="使用当前页" aria-label="使用当前页">' + piIcon('file') + '</button>' +
'<button type="button" class="wolai-page-ai-pi-lab-square" data-page-ai-pi-lab-quick="current-folder" title="使用当前文件夹" aria-label="使用当前文件夹">' + piIcon('folder') + '</button>' +
'<button type="button" class="wolai-page-ai-pi-lab-square" data-page-ai-pi-lab-quick="selection" title="使用选区" aria-label="使用选区">' + piIcon('selection') + '</button>' +
'<button type="button" class="wolai-page-ai-pi-lab-square" data-page-ai-pi-lab-quick="rag" title="启用 LightRAG" aria-label="启用 LightRAG">' + piIcon('database') + '</button>' +
2026-07-04 21:47:42 +08:00
'</div>' +
'</div>' +
'</div>' +
'<aside class="wolai-page-ai-pi-lab-rail" data-page-ai-pi-lab-rail>' +
2026-07-10 10:54:34 +08:00
'<div class="wolai-page-ai-pi-lab-rail-head"><span>Artifacts</span><button type="button" class="wolai-page-ai-pi-lab-rail-close" data-page-ai-pi-lab-close-artifacts aria-label="关闭 Artifacts">×</button></div>' +
'<details class="wolai-page-ai-pi-lab-rail-section" data-page-ai-pi-lab-artifacts-section open>' +
'<summary>Artifacts <span class="wolai-page-ai-pi-lab-rail-count" data-page-ai-pi-lab-artifact-count>0</span></summary>' +
'<div class="wolai-page-ai-pi-lab-artifacts" data-page-ai-pi-lab-artifacts></div>' +
'</details>' +
2026-07-04 21:47:42 +08:00
'<details class="wolai-page-ai-pi-lab-rail-section">' +
'<summary>MNote tools <span class="wolai-page-ai-pi-lab-rail-count">5</span></summary>' +
'<div class="wolai-page-ai-pi-lab-tool-list" data-page-ai-pi-lab-tool-list>' +
'<span class="wolai-page-ai-pi-lab-tool-pill">mnote.current_page.read</span>' +
'<span class="wolai-page-ai-pi-lab-tool-pill">mnote.selection.read</span>' +
'<span class="wolai-page-ai-pi-lab-tool-pill">mnote.local_file.patch</span>' +
'<span class="wolai-page-ai-pi-lab-tool-pill">mnote.knowledge_rag.query</span>' +
'<span class="wolai-page-ai-pi-lab-tool-pill">mnote.reference.open</span>' +
'</div>' +
'</details>' +
2026-07-10 10:54:34 +08:00
'<details class="wolai-page-ai-pi-lab-rail-section" data-page-ai-pi-lab-receipts-section>' +
'<summary>Receipts <span class="wolai-page-ai-pi-lab-rail-count" data-page-ai-pi-lab-receipt-count>0</span></summary>' +
'<button type="button" class="wolai-page-ai-pi-lab-rail-action" data-page-ai-pi-lab-open-session-jsonl>打开 session JSONL</button>' +
'<div class="wolai-page-ai-pi-lab-receipts" data-page-ai-pi-lab-receipts></div>' +
'</details>' +
2026-07-04 21:47:42 +08:00
'</aside>' +
'</div>' +
'<details class="wolai-page-ai-pi-lab-diagnostics" data-page-ai-pi-lab-diagnostics>' +
'<summary>诊断</summary>' +
'<pre data-page-ai-pi-lab-diag-text></pre>' +
'</details>' +
'</div>';
}
function renderEmptyState(container) {
container.innerHTML = '' +
'<div class="wolai-page-ai-pi-lab-empty">' +
'<div class="wolai-page-ai-pi-lab-empty-icon">?</div>' +
'<strong>开始对话</strong>' +
'<p>试试以下示例,或直接输入您的指令</p>' +
'<div class="wolai-page-ai-pi-lab-examples">' +
'<button type="button" class="wolai-page-ai-pi-lab-example" data-page-ai-pi-lab-example="帮我解释当前页面的逻辑"><span>▤</span>帮我解释当前页面的逻辑</button>' +
'<button type="button" class="wolai-page-ai-pi-lab-example" data-page-ai-pi-lab-example="如何优化这段内容的结构?"><span>?</span>如何优化这段内容的结构?</button>' +
'<button type="button" class="wolai-page-ai-pi-lab-example" data-page-ai-pi-lab-example="用 LightRAG 查询当前页面相关资料"><span>▥</span>用 LightRAG 查询当前页面相关资料</button>' +
'<button type="button" class="wolai-page-ai-pi-lab-example" data-page-ai-pi-lab-example="帮我把选区改写成更清晰的版本"><span>↗</span>帮我把选区改写成更清晰的版本</button>' +
'<button type="button" class="wolai-page-ai-pi-lab-example" data-page-ai-pi-lab-example="检查当前 Markdown 有什么问题"><span>⌫</span>检查当前 Markdown 有什么问题</button>' +
'</div>' +
'</div>';
}
2026-07-10 10:54:34 +08:00
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 '<div class="wolai-page-ai-pi-lab-history-head"><span>历史对话</span><button type="button" class="wolai-page-ai-pi-lab-history-close" data-page-ai-pi-lab-history-close aria-label="关闭历史">×</button></div>' +
'<div class="wolai-page-ai-pi-lab-history-toolbar">' +
'<button type="button" class="wolai-page-ai-pi-lab-history-new" data-page-ai-pi-lab-new-small>新建对话</button>' +
'<button type="button" class="wolai-page-ai-pi-lab-history-refresh" data-page-ai-pi-lab-history-refresh>刷新</button>' +
'<button type="button" class="wolai-page-ai-pi-lab-history-clear" data-page-ai-pi-lab-history-clear' + (count ? '' : ' disabled') + '>清空</button>' +
'</div>' +
'<div class="wolai-page-ai-pi-lab-history-meta"><span>共 ' + count + ' 条历史记录</span><span>Pi Rust</span></div>' +
'<div class="wolai-page-ai-pi-lab-history-list">' + innerHtml + '</div>';
}
2026-07-04 21:47:42 +08:00
function renderHistory() {
if (!piLabPanelEl) return;
var panel = piLabPanelEl.querySelector('[data-page-ai-pi-lab-history-panel]');
if (!panel) return;
if (!piLabState.history.length) {
2026-07-10 10:54:34 +08:00
panel.innerHTML = historyShell('<div class="wolai-page-ai-pi-lab-empty">暂无历史对话</div>');
2026-07-04 21:47:42 +08:00
return;
}
2026-07-10 10:54:34 +08:00
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 '<div class="wolai-page-ai-pi-lab-history-row" data-active="' + (active ? 'true' : 'false') + '" data-page-ai-pi-lab-history-row="' + sessionId + '">' +
'<span class="wolai-page-ai-pi-lab-history-dot" aria-hidden="true"></span>' +
'<button type="button" class="wolai-page-ai-pi-lab-history-main" data-page-ai-pi-lab-history-session="' + sessionId + '">' +
'<strong>' + title + '</strong><span>' + meta + '</span></button>' +
'<span class="wolai-page-ai-pi-lab-history-actions">' +
'<button type="button" class="wolai-page-ai-pi-lab-history-action" data-page-ai-pi-lab-history-export="' + sessionId + '">导出</button>' +
'<button type="button" class="wolai-page-ai-pi-lab-history-action" data-page-ai-pi-lab-history-rename="' + sessionId + '">重命名</button>' +
'<button type="button" class="wolai-page-ai-pi-lab-history-action" data-page-ai-pi-lab-history-fork="' + sessionId + '">Fork</button>' +
'<button type="button" class="wolai-page-ai-pi-lab-history-action wolai-page-ai-pi-lab-history-danger" data-page-ai-pi-lab-history-delete="' + sessionId + '">删除</button>' +
'</span></div>';
}).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('<div class="wolai-page-ai-pi-lab-empty">' + escapeHtml(error.message || '历史加载失败') + '</div>');
});
}
return Promise.resolve();
2026-07-04 21:47:42 +08:00
}
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 });
}
});
}
2026-07-04 21:47:42 +08:00
function openHistorySession(sessionId) {
if (!sessionId) return Promise.resolve();
2026-07-10 10:54:34 +08:00
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 || {};
2026-07-10 10:54:34 +08:00
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, {
2026-07-10 10:54:34 +08:00
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,
2026-07-10 10:54:34 +08:00
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);
2026-07-10 10:54:34 +08:00
piLabState.allowedRootsSummary = summarizeAllowedRoots(runtime.allowedRootsSnapshot || piLabState.session.allowedRootsSnapshot);
exitHistoryReplayMode();
2026-07-10 10:54:34 +08:00
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 });
2026-07-04 21:47:42 +08:00
}
setState(STATE_IDLE);
updateDiagnostics('已打开历史 session,可直接继续发送。');
updateMessages();
updateButtons();
updateArtifactRail();
updateReceiptDisplay();
updateQueueDisplay();
setHistoryPanelVisible(false);
2026-07-04 21:47:42 +08:00
});
});
}
2026-07-10 10:54:34 +08:00
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();
}
function deleteHistorySession(sessionId) {
if (!sessionId) return Promise.resolve();
var existing = currentHistorySession(sessionId);
var title = existing ? historyTitle(existing) : sessionId;
if (!window.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();
});
});
}
function clearHistorySessions() {
var count = Array.isArray(piLabState.history) ? piLabState.history.length : 0;
if (!count) return Promise.resolve();
if (!window.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');
});
}
2026-07-10 10:54:34 +08:00
function renderMessage(msg) {
2026-07-04 21:47:42 +08:00
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');
2026-07-10 10:54:34 +08:00
var meta = msg.status === 'streaming' ? 'streaming' : (msg.status === 'aborted' ? 'aborted' : (msg.meta || ''));
2026-07-04 21:47:42 +08:00
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) {
2026-07-10 10:54:34 +08:00
var rendersMarkdown = msg.role === 'assistant' || msg.role === 'system';
2026-07-04 21:47:42 +08:00
bubble.appendChild(el('div', {
2026-07-10 10:54:34 +08:00
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, '<br>'),
2026-07-04 21:47:42 +08:00
}));
}
if (msg.toolCalls && msg.toolCalls.length) {
2026-07-10 10:54:34 +08:00
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',
2026-07-04 21:47:42 +08:00
});
2026-07-10 10:54:34 +08:00
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);
2026-07-04 21:47:42 +08:00
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);
}
2026-07-10 10:54:34 +08:00
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);
}
2026-07-04 21:47:42 +08:00
card.appendChild(bubble);
return card;
}
function isVisibleMessage(msg) {
if (!msg) return false;
2026-07-10 10:54:34 +08:00
if (msg.role === 'system' && /^Pi session started:/.test(String(msg.text || ''))) return false;
2026-07-04 21:47:42 +08:00
if (msg.status === 'streaming') return true;
2026-07-10 10:54:34 +08:00
if (msg.status === 'aborted' || msg.abortReason) return true;
2026-07-04 21:47:42 +08:00
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]');
2026-07-04 21:47:42 +08:00
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;
2026-07-04 21:47:42 +08:00
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 '<button type="button" class="wolai-page-ai-pi-lab-control-option" role="menuitemradio" aria-checked="' + String(active) + '" data-page-ai-pi-lab-model-option data-provider="' + escapeHtml(item.provider) + '" data-model-id="' + escapeHtml(item.modelId) + '" data-active="' + String(active) + '" title="' + escapeHtml(item.key) + '">' +
'<span class="wolai-page-ai-pi-lab-control-option-label">' + escapeHtml(item.label) + '</span>' +
'<span class="wolai-page-ai-pi-lab-control-option-check">' + (active ? '✓' : '') + '</span>' +
'</button>';
}).join('');
}
2026-07-04 21:47:42 +08:00
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;
}
2026-07-04 21:47:42 +08:00
}
if (customInput) customInput.value = modelId !== DEFAULT_MODEL_ID && modelId !== 'freefirst' ? modelId : '';
2026-07-04 21:47:42 +08:00
}
function updateConfig() {
if (!piLabPanelEl) return;
updateModelLabel();
2026-07-10 10:54:34 +08:00
updateThinkingControl();
2026-07-04 21:47:42 +08:00
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]');
2026-07-10 10:54:34 +08:00
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]');
2026-07-04 21:47:42 +08:00
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();
2026-07-10 10:54:34 +08:00
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 ? '正在查看历史,只读' : '';
}
2026-07-04 21:47:42 +08:00
updateContextStrip();
2026-07-10 10:54:34 +08:00
updatePermissionControl();
2026-07-04 21:47:42 +08:00
updateButtons();
2026-07-10 10:54:34 +08:00
updateQueueDisplay();
2026-07-04 21:47:42 +08:00
updateLauncherState();
}
function updateContextStrip() {
if (!piLabPanelEl) return;
var currentPage = piLabPanelEl.querySelector('[data-page-ai-pi-lab-current-page]');
var selection = piLabPanelEl.querySelector('[data-page-ai-pi-lab-selection]');
var allowedRoots = piLabPanelEl.querySelector('[data-page-ai-pi-lab-allowed-roots]');
var lightrag = piLabPanelEl.querySelector('[data-page-ai-pi-lab-lightrag]');
var changedFiles = piLabPanelEl.querySelector('[data-page-ai-pi-lab-changed-files]');
var session = piLabState.session || {};
if (currentPage) currentPage.textContent = session.pageTitle || session.pagePath || document.title || '当前页面';
if (selection) selection.textContent = piLabState.selectionSummary || '无选区';
if (allowedRoots) allowedRoots.textContent = piLabState.allowedRootsSummary || 'pending';
if (lightrag) lightrag.textContent = '唯一默认';
if (changedFiles) {
var files = {};
piLabState.changedFiles.forEach(function (file) { if (file) files[file] = true; });
piLabState.messages.forEach(function (msg) {
if (msg && msg.diffSummary && Array.isArray(msg.diffSummary.files)) {
msg.diffSummary.files.forEach(function (file) { if (file) files[file] = true; });
}
});
changedFiles.textContent = String(Object.keys(files).length);
}
2026-07-10 10:54:34 +08:00
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();
2026-07-04 21:47:42 +08:00
}
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]');
2026-07-04 21:47:42 +08:00
var customInput = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-custom]');
2026-07-10 10:54:34 +08:00
var isReplay = !!piLabState.viewingHistorySessionId;
2026-07-04 21:47:42 +08:00
var isIdle = piLabState.status === STATE_IDLE || piLabState.status === STATE_ABORTED || piLabState.status === STATE_ERROR;
var isStarted = piLabState.status === STATE_STARTED;
2026-07-10 10:54:34 +08:00
var isStreaming = piLabState.status === STATE_STREAMING;
var compactBtn = piLabPanelEl.querySelector('[data-page-ai-pi-lab-btn-compact]');
2026-07-04 21:47:42 +08:00
if (sendBtn) {
2026-07-10 10:54:34 +08:00
sendBtn.disabled = isReplay || !piLabState.enabled || !input || !input.value.trim();
2026-07-04 21:47:42 +08:00
}
2026-07-10 10:54:34 +08:00
if (abortBtn) abortBtn.hidden = !(isStreaming || piLabState.status === STATE_STARTING);
if (compactBtn) compactBtn.disabled = isReplay || piLabState.isCompacting;
2026-07-10 10:54:34 +08:00
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;
2026-07-04 21:47:42 +08:00
});
if (controlsDisabled && (piLabState.modelMenuOpen || piLabState.thinkingMenuOpen)) {
piLabState.modelMenuOpen = false;
piLabState.thinkingMenuOpen = false;
updateModelLabel();
updateThinkingControl();
}
2026-07-04 21:47:42 +08:00
}
2026-07-10 10:54:34 +08:00
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;
}
}
2026-07-04 21:47:42 +08:00
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);
2026-07-10 10:54:34 +08:00
updateArtifactRail();
2026-07-04 21:47:42 +08:00
return;
}
visibleMessages.forEach(function (msg) { container.appendChild(renderMessage(msg)); });
container.scrollTop = container.scrollHeight;
updateContextStrip();
2026-07-10 10:54:34 +08:00
updateQueueDisplay();
updateArtifactRail();
2026-07-04 21:47:42 +08:00
}
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());
2026-07-10 10:54:34 +08:00
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(','));
}
2026-07-04 21:47:42 +08:00
if (piLabState.allowedRootsSummary) lines.push('allowedRoots=' + piLabState.allowedRootsSummary);
if (piLabState.diagnostics) lines.push('message=' + piLabState.diagnostics);
pre.textContent = lines.join('\n');
}
2026-07-10 10:54:34 +08:00
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() {
2026-07-04 21:47:42 +08:00
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) {
2026-07-10 10:54:34 +08:00
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;
2026-07-04 21:47:42 +08:00
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));
});
}
2026-07-10 10:54:34 +08:00
function collectPiArtifacts() {
var artifacts = [];
var seen = {};
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 || '',
});
2026-07-10 10:54:34 +08:00
});
}
(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 || {},
});
});
});
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'));
}
2026-07-10 10:54:34 +08:00
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));
});
}
2026-07-04 21:47:42 +08:00
function setState(newState) {
piLabState.status = newState;
2026-07-10 10:54:34 +08:00
if (piRunViewModel) piRunViewModel.runtimeStatus = newState;
2026-07-04 21:47:42 +08:00
updateConfig();
2026-07-10 10:54:34 +08:00
maybeApplyPendingPermissionMode();
maybeApplyPendingModelConfig();
2026-07-10 10:54:34 +08:00
}
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 () {});
}
2026-07-10 10:54:34 +08:00
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;
});
2026-07-04 21:47:42 +08:00
}
function checkStatus() {
return Promise.all([
fetch(API.STATUS, { credentials: 'same-origin', cache: 'no-store' }).then(function (r) { return r.json(); }),
loadEffectiveConfig().catch(function () { return null; }),
])
.then(function (results) {
var data = results[0] || {};
piLabState.enabled = !!data.enabled;
piLabState.sessionId = data.sessionId || piLabState.sessionId || null;
piLabState.providerSessionId = data.providerSessionId || piLabState.providerSessionId || null;
// Consume backend status/default fields when present.
if (data.defaultModelProvider) piLabState.defaultModelProvider = data.defaultModelProvider;
if (data.defaultModelId) piLabState.defaultModelId = data.defaultModelId;
2026-07-10 10:54:34 +08:00
if (data.defaultThinkingLevel) piLabState.defaultThinkingLevel = normalizeThinkingLevel(data.defaultThinkingLevel);
2026-07-04 21:47:42 +08:00
piLabState.modelProvider = (data.session && data.session.modelProvider) || piLabState.modelProvider || null;
piLabState.modelId = (data.session && data.session.modelId) || piLabState.modelId || null;
2026-07-10 10:54:34 +08:00
var sessionThinkingLevel = data.session && data.session.thinkingLevel;
if (sessionThinkingLevel) {
2026-07-10 10:54:34 +08:00
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);
2026-07-04 21:47:42 +08:00
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;
2026-07-10 10:54:34 +08:00
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) || '';
2026-07-04 21:47:42 +08:00
piLabState.runtimePid = data.pid || (data.session && data.session.runtimePid) || piLabState.runtimePid || null;
piLabState.warmupRunning = data.warmupRunning === true;
piLabState.warmupSessionId = data.warmupSessionId || null;
2026-07-10 10:54:34 +08:00
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 || [];
2026-07-04 21:47:42 +08:00
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);
}
2026-07-10 10:54:34 +08:00
updateBuiltinManaged();
updateModelLabel();
updateThinkingControl();
updatePermissionControl();
2026-07-04 21:47:42 +08:00
updateReceiptDisplay();
updateDiagnostics('');
updateMessages();
return data;
})
.catch(function (error) {
piLabState.enabled = false;
setState(STATE_ERROR);
updateDiagnostics(error && error.message ? error.message : 'status failed');
});
}
function startRuntime() {
2026-07-10 10:54:34 +08:00
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();
2026-07-10 10:54:34 +08:00
setState(STATE_STARTING);
updateDiagnostics(piLabState.warmupRunning ? 'Binding current page Pi session' : 'Starting Pi Rust runtime');
2026-07-10 10:54:34 +08:00
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');
});
2026-07-04 21:47:42 +08:00
})
.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;
2026-07-10 10:54:34 +08:00
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;
2026-07-04 21:47:42 +08:00
piLabState.runtimeMode = session.runtimeMode || data.runtimeMode || piLabState.runtimeMode || null;
2026-07-10 10:54:34 +08:00
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 || '';
2026-07-04 21:47:42 +08:00
piLabState.runtimePid = session.runtimePid || data.pid || piLabState.runtimePid || null;
2026-07-10 10:54:34 +08:00
piLabState.managedBuiltinTools = data.managedPiBuiltinTools || data.managedBuiltinTools || piLabState.managedBuiltinTools || [];
piLabState.piExtensionSources = data.piExtensionSources || piLabState.piExtensionSources || [];
piLabState.piExtensionToolNames = data.piExtensionToolNames || piLabState.piExtensionToolNames || [];
2026-07-04 21:47:42 +08:00
connectEventSource();
setState(STATE_STARTED);
updateDiagnostics('Pi runtime ready');
updateMessages();
return data;
})
.catch(function (err) {
setState(STATE_ERROR);
updateDiagnostics('Start error: ' + err.message);
throw err;
2026-07-10 10:54:34 +08:00
})
.finally(function () {
piLabStartPromise = null;
2026-07-04 21:47:42 +08:00
});
2026-07-10 10:54:34 +08:00
return piLabStartPromise;
}
2026-07-04 21:47:42 +08:00
2026-07-10 10:54:34 +08:00
function handleToolCallEventPayload(payload) {
payload = payload || {};
applyToolCallSideEffects({
ok: payload.allowed !== false,
toolName: payload.toolName,
result: {
rootUri: payload.rootUri,
relativePath: payload.relativePath,
diffSummary: payload.diffSummary,
},
});
piLabState.receipts.push({
toolName: payload.toolName,
allowed: payload.allowed !== false,
denyReason: payload.denyReason || null,
diffSummary: payload.diffSummary || null,
citationCount: payload.citationCount || 0,
beforeFileVersion: payload.beforeFileVersion || null,
afterFileVersion: payload.afterFileVersion || null,
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 || {},
}, payload.allowed === false ? 'denied' : 'done', {
result: {
allowed: payload.allowed !== false,
denyReason: payload.denyReason || null,
diffSummary: payload.diffSummary || null,
citationCount: payload.citationCount || 0,
},
approvalRequired: payload.approvalRequired === true,
approvalConfirmed: payload.approvalConfirmed === true,
toolPolicy: payload.toolPolicy || '',
});
if (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; });
}
2026-07-10 10:54:34 +08:00
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) {
2026-07-04 21:47:42 +08:00
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;
2026-07-10 10:54:34 +08:00
piLabState.managedBuiltinTools = payload.managedBuiltinTools || payload.managedPiBuiltinTools || piLabState.managedBuiltinTools;
piLabState.piExtensionSources = payload.piExtensionSources || piLabState.piExtensionSources || [];
piLabState.piExtensionToolNames = payload.piExtensionToolNames || piLabState.piExtensionToolNames || [];
2026-07-04 21:47:42 +08:00
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 (_) {}
});
2026-07-10 10:54:34 +08:00
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;
}
2026-07-10 10:54:34 +08:00
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');
});
2026-07-04 21:47:42 +08:00
es.addEventListener('error', function () {
if (piLabEventSource && piLabEventSource.readyState === EventSource.CLOSED) updateDiagnostics('SSE connection closed');
});
piLabEventSource = es;
}
function ensureStreamingAssistant() {
2026-07-10 10:54:34 +08:00
if (!streamingAssistantMsg) applyPiRunEvent({ type: 'assistant_begin' });
setState(STATE_STREAMING);
2026-07-04 21:47:42 +08:00
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;
}
2026-07-10 10:54:34 +08:00
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;
2026-07-10 10:54:34 +08:00
}
if (message.role === 'toolResult') {
var toolResultPayload = {
2026-07-10 10:54:34 +08:00
toolCallId: message.toolCallId || message.tool_call_id || '',
toolName: message.toolName || message.tool_name || 'tool',
details: message.details || {},
};
upsertToolCall(toolResultPayload, message.isError ? 'error' : 'done', {
2026-07-10 10:54:34 +08:00
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();
}
2026-07-10 10:54:34 +08:00
return true;
}
return false;
}
2026-07-04 21:47:42 +08:00
function handlePiRpcEvent(payload) {
if (!payload) return;
var eventType = payload.type;
var msgData = payload.assistantMessageEvent || payload;
var msgDataType = msgData && msgData.type;
2026-07-10 10:54:34 +08:00
if (handlePiExtensionUiRequest(payload)) return;
2026-07-04 21:47:42 +08:00
if (msgDataType === 'thinking_start' || msgDataType === 'thinking_delta' || msgDataType === 'thinking_end') {
updateDiagnostics('Pi thinking event hidden from visible reply');
return;
}
2026-07-10 10:54:34 +08:00
if (eventType === 'queue_update') {
applyPiRunEvent({ type: 'queue_update', payload: payload });
updateQueueDisplay();
return;
}
if (eventType === 'message' && handlePiMessageEvent(payload)) return;
2026-07-04 21:47:42 +08:00
if (msgDataType === 'text_start') {
2026-07-10 10:54:34 +08:00
setState(STATE_STREAMING);
applyPiRunEvent({ type: 'assistant_begin' });
2026-07-04 21:47:42 +08:00
updateMessages();
return;
}
if (msgDataType === 'text_delta') {
2026-07-10 10:54:34 +08:00
applyPiRunEvent({ type: 'assistant_delta', payload: msgData });
2026-07-04 21:47:42 +08:00
updateMessages();
} else if (msgDataType === 'text_end') {
2026-07-10 10:54:34 +08:00
applyPiRunEvent({ type: 'assistant_text_end', payload: msgData });
2026-07-04 21:47:42 +08:00
updateMessages();
} else if (eventType === 'message_end' && payload.message && payload.message.role === 'toolResult') {
handlePiMessageEvent(payload);
2026-07-04 21:47:42 +08:00
} 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;
}
2026-07-10 10:54:34 +08:00
applyPiRunEvent({ type: 'assistant_done', text: visibleText });
2026-07-04 21:47:42 +08:00
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 || {});
}
});
2026-07-04 21:47:42 +08:00
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;
2026-07-10 10:54:34 +08:00
applyPiRunEvent({ type: 'assistant_done', text: finalText });
2026-07-04 21:47:42 +08:00
setState(STATE_STARTED);
updateMessages();
2026-07-10 10:54:34 +08:00
} 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,
});
2026-07-04 21:47:42 +08:00
}
2026-07-10 10:54:34 +08:00
} else if (msgDataType === 'toolcall_delta') {
var deltaToolPayload = assistantToolCallPayload(msgData);
if (deltaToolPayload) upsertToolCall(deltaToolPayload, 'running', { args: deltaToolPayload.args || {} });
} else if (eventType === 'response' && payload.command === 'abort') {
applyPiRunEvent({ type: 'assistant_abort', payload: payload });
setState(STATE_ABORTED);
2026-07-04 21:47:42 +08:00
updateMessages();
} else if (eventType === 'citation' || msgDataType === 'citation') {
2026-07-10 10:54:34 +08:00
applyPiRunEvent({ type: 'citation', payload: { source: payload.source || msgData.source || '', title: payload.title || msgData.title || '', url: payload.url || msgData.url || '#' } });
2026-07-04 21:47:42 +08:00
updateMessages();
} else if (eventType === 'diff' || msgDataType === 'diff') {
2026-07-10 10:54:34 +08:00
var diffFiles = payload.files || msgData.files || [];
var diffToolEventId = payload.toolEventId || msgData.toolEventId || '';
applyPiRunEvent({ type: 'diff', payload: { files: diffFiles, toolEventId: diffToolEventId } });
2026-07-10 10:54:34 +08:00
diffFiles.forEach(function (file) { if (file) piLabState.changedFiles.push(file); });
if (diffToolEventId) { piLabState.currentToolEventId = diffToolEventId; }
2026-07-04 21:47:42 +08:00
updateMessages();
} else if (eventType === 'done' || msgDataType === 'done') {
2026-07-10 10:54:34 +08:00
applyPiRunEvent({
type: 'assistant_done',
payload: {
text: msgData.text,
toolCalls: msgData.toolCalls,
citations: msgData.citations,
diffSummary: msgData.diffSummary,
},
});
2026-07-04 21:47:42 +08:00
setState(STATE_STARTED);
updateMessages();
} else if (eventType === 'error' || msgDataType === 'error') {
2026-07-10 10:54:34 +08:00
applyPiRunEvent({ type: 'assistant_error', payload: { error: payload.error || msgData.message || 'Pi runtime error' } });
2026-07-04 21:47:42 +08:00
setState(STATE_STARTED);
updateMessages();
}
}
2026-07-10 10:54:34 +08:00
function sendPrompt(text, options) {
2026-07-04 21:47:42 +08:00
var prompt = String(text || '').trim();
if (!prompt) return;
2026-07-10 10:54:34 +08:00
var streamingBehavior = options && options.streamingBehavior;
var midStream = piLabState.status === STATE_STREAMING && streamingAssistantMsg;
applyPiRunEvent({ type: 'user_prompt', text: prompt, meta: midStream ? (streamingBehavior === 'followUp' ? 'follow-up queued' : 'steer') : '' });
if (!midStream) applyPiRunEvent({ type: 'assistant_begin' });
2026-07-04 21:47:42 +08:00
setState(STATE_STREAMING);
updateMessages();
2026-07-10 10:54:34 +08:00
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 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,
};
if (streamingBehavior) body.streamingBehavior = streamingBehavior;
2026-07-04 21:47:42 +08:00
return fetch(API.SEND, {
method: 'POST',
credentials: 'same-origin',
headers: { 'Content-Type': 'application/json' },
2026-07-10 10:54:34 +08:00
body: JSON.stringify(body),
2026-07-04 21:47:42 +08:00
})
.then(function (r) {
2026-07-10 10:54:34 +08:00
return responseJsonOrError(r, 'Send failed');
2026-07-04 21:47:42 +08:00
})
.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) {
2026-07-10 10:54:34 +08:00
if (streamingAssistantMsg) applyPiRunEvent({ type: 'assistant_error', message: err.message });
setState(STATE_STARTED);
updateMessages();
2026-07-04 21:47:42 +08:00
});
}
function ensureRuntimeReady() {
2026-07-10 10:54:34 +08:00
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) {
2026-07-04 21:47:42 +08:00
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));
2026-07-10 10:54:34 +08:00
if (payload && payload.ok === false) {
var result = payload.result || {};
throw new Error(result.message || payload.message || result.code || 'Tool call failed');
}
2026-07-04 21:47:42 +08:00
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) {
2026-07-10 10:54:34 +08:00
showPiToast('当前页未绑定文件上下文', 'warning');
updateDiagnostics('当前页缺少 rootUri/pagePath,无法作为 Pi 上下文');
2026-07-04 21:47:42 +08:00
return;
}
2026-07-10 10:54:34 +08:00
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');
2026-07-04 21:47:42 +08:00
return;
}
if (kind === 'selection') {
var selection = refreshSelectionSummary();
2026-07-10 10:54:34 +08:00
if (!selection && !contextSelectionState().selection) showPiToast('当前没有选区', 'warning');
toggleContextQuick(kind);
2026-07-04 21:47:42 +08:00
return;
}
2026-07-10 10:54:34 +08:00
toggleContextQuick('rag');
showPiToast((contextSelectionState().lightrag ? '已启用' : '已关闭') + ' LightRAG 上下文', 'info');
2026-07-04 21:47:42 +08:00
}
2026-07-10 10:54:34 +08:00
function sendCurrentInput(options) {
2026-07-04 21:47:42 +08:00
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 正在启动,启动后自动发送';
2026-07-10 10:54:34 +08:00
if (piLabState.status === STATE_IDLE || piLabState.status === STATE_ABORTED || piLabState.status === STATE_ERROR || !piLabState.sessionId) {
showPiToast(startToast, 'info');
2026-07-10 10:54:34 +08:00
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');
2026-07-10 10:54:34 +08:00
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();
}
2026-07-04 21:47:42 +08:00
updateButtons();
2026-07-10 10:54:34 +08:00
var isMidStream = piLabState.status === STATE_STREAMING;
var streamingBehavior = isMidStream ? ((options && options.streamingBehavior) || 'steer') : undefined;
sendPrompt(text, { streamingBehavior: streamingBehavior }).catch(function () {});
2026-07-04 21:47:42 +08:00
}
function abortPrompt() {
var piInput = piLabPanelEl && piLabPanelEl.querySelector('[data-page-ai-pi-lab-input]');
if (piInput && piInput.value.trim()) savePiLabDraft();
2026-07-04 21:47:42 +08:00
if (piLabEventSource) {
piLabEventSource.close();
piLabEventSource = null;
}
2026-07-10 10:54:34 +08:00
if (piLabState.sessionId) {
fetch(API.ABORT, {
method: 'POST',
credentials: 'same-origin',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ sessionId: piLabState.sessionId }),
}).then(function (response) {
return response.json().catch(function () { return {}; });
}).then(function (payload) {
applyPiRunEvent({ type: 'assistant_abort', payload: Object.assign({ stopReason: 'aborted' }, payload || {}) });
updateMessages();
}).catch(function () {});
}
applyPiRunEvent({ type: 'assistant_abort', payload: { reason: '已中止', stopReason: 'aborted' } });
setState(STATE_ABORTED);
updateMessages();
}
function startNewConversation() {
var previousSessionId = piLabState.sessionId;
var preservedContext = currentPageContext();
2026-07-10 10:54:34 +08:00
if (piLabEventSource) {
piLabEventSource.close();
piLabEventSource = null;
}
if (previousSessionId && !piLabState.viewingHistorySessionId && (piLabState.status === STATE_STARTED || piLabState.status === STATE_STREAMING || piLabState.status === STATE_STARTING)) {
2026-07-04 21:47:42 +08:00
fetch(API.ABORT, {
method: 'POST',
credentials: 'same-origin',
headers: { 'Content-Type': 'application/json' },
2026-07-10 10:54:34 +08:00
body: JSON.stringify({ sessionId: previousSessionId }),
2026-07-04 21:47:42 +08:00
}).catch(function () {});
}
2026-07-10 10:54:34 +08:00
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,
};
2026-07-10 10:54:34 +08:00
piLabState.thinkingLevel = normalizeThinkingLevel(piLabState.defaultThinkingLevel || DEFAULT_THINKING_LEVEL);
piLabState.status = STATE_IDLE;
resetPiRunViewModel();
piLabState.receipts = [];
2026-07-04 21:47:42 +08:00
updateMessages();
2026-07-10 10:54:34 +08:00
updateReceiptDisplay();
updateQueueDisplay();
updateThinkingControl();
updateConfig();
2026-07-04 21:47:42 +08:00
}
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 = [];
var fileLines = patchText.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 = '<div class="wolai-page-ai-pi-lab-diff-header"><span>' + escapeHtml(title) + '</span><button type="button" class="wolai-page-ai-pi-lab-diff-close" aria-label="关闭 diff">×</button></div><div class="wolai-page-ai-pi-lab-diff-body">' + html + '</div>';
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 '<div style="padding:20px;color:#888;text-align:center">无 diff 数据</div>';
var html = '';
files.forEach(function (file) {
html += '<div class="wolai-page-ai-pi-lab-diff-file-header">' + escapeHtml(file.newPath || file.oldPath || 'unknown') + '</div>';
(file.hunks || []).forEach(function (hunk, hi) {
if (hi > 0) html += '<div class="wolai-page-ai-pi-lab-diff-hunk-header">' + escapeHtml(hunk.header) + '</div>';
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 += '<div class="wolai-page-ai-pi-lab-diff-collapse" onclick="var n=this.nextElementSibling;if(n)n.hidden=!n.hidden;this.hidden=true">⋯ 展开 ' + (ctxLines.length - 6) + ' 行上下文</div>';
html += '<div hidden>';
ctxLines.slice(0, ctxLines.length - 6).forEach(function (cl) {
html += '<div class="wolai-page-ai-pi-lab-diff-line wolai-page-ai-pi-lab-diff-line-ctx"><span class="wolai-page-ai-pi-lab-diff-line-num"></span>' + escapeHtml(cl.text) + '</div>';
});
html += '</div>';
}
ctxLines.slice(-6).forEach(function (cl) {
html += '<div class="wolai-page-ai-pi-lab-diff-line wolai-page-ai-pi-lab-diff-line-ctx"><span class="wolai-page-ai-pi-lab-diff-line-num"></span>' + escapeHtml(cl.text) + '</div>';
});
ctxCount = 0;
ctxLines = [];
return;
}
} else {
if (ctxLines.length) {
ctxLines.forEach(function (cl) {
html += '<div class="wolai-page-ai-pi-lab-diff-line wolai-page-ai-pi-lab-diff-line-ctx"><span class="wolai-page-ai-pi-lab-diff-line-num"></span>' + escapeHtml(cl.text) + '</div>';
});
ctxLines = [];
ctxCount = 0;
}
html += '<div class="wolai-page-ai-pi-lab-diff-line wolai-page-ai-pi-lab-diff-line-' + ln.type + '"><span class="wolai-page-ai-pi-lab-diff-line-num"></span>' + escapeHtml(ln.text) + '</div>';
}
});
if (ctxLines.length) {
ctxLines.forEach(function (cl) {
html += '<div class="wolai-page-ai-pi-lab-diff-line wolai-page-ai-pi-lab-diff-line-ctx"><span class="wolai-page-ai-pi-lab-diff-line-num"></span>' + escapeHtml(cl.text) + '</div>';
});
}
});
});
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, '<div style="padding:20px;color:#888;text-align:center">无 diff 内容' + (data.message ? ': ' + escapeHtml(data.message) : '') + '</div>');
return;
}
var files = parseUnifiedPatch(patchText);
var title = 'Diff: ' + (data.toolEventId || toolEventId);
showDiffOverlay(title, renderSplitDiff(files));
}).catch(function (err) {
showPiToast('加载 diff 失败:' + (err.message || ''), 'error');
});
}
2026-07-04 21:47:42 +08:00
function ensurePanel() {
if (piLabPanelEl) return piLabPanelEl;
var container = document.createElement('div');
container.innerHTML = piLabPanelHTML();
piLabPanelEl = container.firstElementChild;
wireEvents();
updateReceiptDisplay();
2026-07-10 10:54:34 +08:00
updateArtifactRail();
2026-07-04 21:47:42 +08:00
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();
2026-07-10 10:54:34 +08:00
checkStatus().then(function () {
if (piLabState.enabled && piLabState.runtimeAvailable !== false && !piLabState.sessionId && piLabState.status !== STATE_STARTING) {
return startRuntime().then(function () {
syncRuntimeState();
});
2026-07-10 10:54:34 +08:00
}
syncRuntimeState();
updateButtons();
return null;
}).then(function () {
updateButtons();
}).catch(function (error) {
updateDiagnostics(error && error.message ? error.message : 'Pi current page session prepare failed');
updateButtons();
});
2026-07-04 21:47:42 +08:00
var input = piLabPanelEl && piLabPanelEl.querySelector('[data-page-ai-pi-lab-input]');
if (input) {
restorePiLabDraft();
setTimeout(function () { input.focus(); }, 60);
}
2026-07-04 21:47:42 +08:00
}
function hidePiLab() {
piLabState.active = false;
closeComposerControlMenus();
2026-07-04 21:47:42 +08:00
if (piLabDrawerEl) setDrawerVisible(false);
updateLauncherState();
}
function updateLauncherState() {
if (!piLabLauncherEl) return;
piLabLauncherEl.setAttribute('data-state', piLabState.status);
piLabLauncherEl.hidden = !!piLabState.active;
2026-07-04 21:47:42 +08:00
}
function installLauncher() {
if (piLabLauncherEl) return;
piLabLauncherEl = el('button', {
type: 'button',
className: 'wolai-page-ai-pi-lab-launcher',
'data-page-ai-pi-lab-launcher': 'true',
title: 'Pi Lab',
'aria-label': '打开 Pi Lab',
}, 'π');
piLabLauncherEl.addEventListener('click', showPiLab);
document.body.appendChild(piLabLauncherEl);
}
function wireEvents() {
if (!piLabPanelEl || piLabPanelEl.getAttribute('data-wired') === 'true') return;
piLabPanelEl.setAttribute('data-wired', 'true');
var input = piLabPanelEl.querySelector('[data-page-ai-pi-lab-input]');
var 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]');
2026-07-10 10:54:34 +08:00
var newBtns = piLabPanelEl.querySelectorAll('[data-page-ai-pi-lab-new]');
2026-07-04 21:47:42 +08:00
if (sendBtn) sendBtn.addEventListener('click', sendCurrentInput);
if (abortBtn) abortBtn.addEventListener('click', abortPrompt);
if (compactBtn) compactBtn.addEventListener('click', triggerCompact);
2026-07-04 21:47:42 +08:00
if (clearBtn) clearBtn.addEventListener('click', function () {
2026-07-10 10:54:34 +08:00
resetPiRunViewModel();
2026-07-04 21:47:42 +08:00
piLabState.receipts = [];
updateMessages();
updateReceiptDisplay();
2026-07-10 10:54:34 +08:00
updateQueueDisplay();
2026-07-04 21:47:42 +08:00
});
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();
});
2026-07-04 21:47:42 +08:00
input.addEventListener('keydown', function (e) {
if (e.isComposing || e.key === 'Process') return;
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
2026-07-10 10:54:34 +08:00
sendCurrentInput(e.altKey ? { streamingBehavior: 'followUp' } : undefined);
2026-07-04 21:47:42 +08:00
}
if (e.key === 'Escape' && piLabState.status === STATE_STREAMING) {
e.preventDefault();
abortPrompt();
}
});
}
[providerSelect, modelSelect].forEach(function (node) {
if (node) node.addEventListener('change', function () {
applyModelControls();
applyModelConfigToRuntime();
});
2026-07-04 21:47:42 +08:00
});
if (customInput) customInput.addEventListener('input', applyModelControls);
2026-07-10 10:54:34 +08:00
if (settingsBtn) {
settingsBtn.addEventListener('click', function () {
window.location.assign('/user/ai#ai-admin-access');
2026-07-04 21:47:42 +08:00
});
}
2026-07-10 10:54:34 +08:00
if (historyBtn) {
historyBtn.addEventListener('click', function () {
var layer = piLabPanelEl.querySelector('[data-page-ai-pi-lab-history-layer]');
if (!layer) return;
setHistoryPanelVisible(layer.hidden);
});
}
2026-07-04 21:47:42 +08:00
Array.prototype.slice.call(newBtns || []).forEach(function (btn) {
2026-07-10 10:54:34 +08:00
btn.addEventListener('click', startNewConversation);
2026-07-04 21:47:42 +08:00
});
2026-07-10 10:54:34 +08:00
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;
}
2026-07-10 10:54:34 +08:00
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;
}
2026-07-10 10:54:34 +08:00
}
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 () {
showPiToast('Markdown copied', 'success');
});
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 openArtifact = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-artifact-open]') : null;
if (openArtifact) {
var artUrl = openArtifact.getAttribute('data-page-ai-pi-lab-artifact-open') || openArtifact.getAttribute('href') || '';
2026-07-10 10:54:34 +08:00
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,
2026-07-10 10:54:34 +08:00
},
}));
if (!artUrl || artUrl === '#') {
showPiToast('引用无效或无法访问', 'warning');
}
}
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;
2026-07-10 10:54:34 +08:00
}
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 () {
showPiToast('Artifact markdown copied', 'success');
});
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 () { showPiToast('Artifact raw copied', 'success'); });
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;
}
2026-07-04 21:47:42 +08:00
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);
2026-07-04 21:47:42 +08:00
}
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;
2026-07-10 10:54:34 +08:00
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; },
};
2026-07-04 21:47:42 +08:00
}
})();